source: trunk/FACT++/src/makeschedule.cc@ 18437

Last change on this file since 18437 was 18437, checked in by tbretz, 9 years ago
get the source keys already with the first query.
File size: 20.5 KB
Line 
1#include "externals/Prediction.h"
2
3#include <boost/algorithm/string/join.hpp>
4
5#include "Database.h"
6
7#include "tools.h"
8#include "Time.h"
9#include "Configuration.h"
10
11using namespace std;
12using namespace Nova;
13
14// -----------------------------------------------------------------------
15
16void SetupConfiguration(Configuration &conf)
17{
18 po::options_description control("Makeschedule");
19 control.add_options()
20 ("date", var<string>(), "SQL time (UTC), e.g. '2016-12-24'")
21 ("source-database", var<string>(""), "Database link as in\n\tuser:password@server[:port]/database.")
22 ("schedule-database", var<string>(""), "Database link as in\n\tuser:password@server[:port]/database.")
23 ("max-current", var<double>(90), "Global maximum current limit in uA")
24 ("max-zd", var<double>(75), "Global zenith distance limit in degree")
25 ("source", vars<string>(), "List of all TeV sources to be included, names according to the database")
26 ("setup.*", var<double>(), "Setup for the sources to be observed")
27 ("preobs.*", vars<string>(), "Prescheduled observations")
28 ("startup.offset", var<double>(15), "Determines how many minutes the startup is scheduled before data-taking.start [0;120]")
29 ("data-taking.start", var<double>(-12), "Begin of data-taking in degree of sun below horizon")
30 ("data-taking.end", var<double>(-13.75), "End of data-taking in degree of sun below horizon")
31 ("enter-schedule-into-database", var<bool>(), "Enter schedule into database (required schedule-database, false: dry-run)")
32 ;
33
34 po::positional_options_description p;
35 p.add("date", 1); // The first positional options
36
37 conf.AddOptions(control);
38 conf.SetArgumentPositions(p);
39}
40
41void PrintUsage()
42{
43 cout <<
44 "makeschedule - Creates an automatic schedule\n"
45 "\n"
46 "Usage: makeschedule [yyyy-mm-dd]\n";
47 cout << endl;
48}
49
50void PrintHelp()
51{
52#ifdef HAVE_ROOT
53 cout <<
54 "\n"
55 "Examples:\n"
56 "\n"
57 " makeschedule 2016-12-24\n"
58 "\n"
59 "Calculate the Christmas schedule for 2016 using all TeV sources from the\n"
60 "database. If the date is omitted the current date is used.\n"
61 "\n"
62 " makeschedule --source='Mrk 421' --source='Mrk 501' --source='Crab'\n"
63 "\n"
64 "Use only the mentioned sources to calculate the schedule.\n"
65 "\n"
66 " makeschedule --source=Crab --setup.Crab.max-zd=30\n"
67 "\n"
68 "Limit the zenith distance of Crab into the range [0;30]deg.\n"
69 "\n"
70 " makeschedule --source=Crab --setup.Crab.max-current=50\n"
71 "\n"
72 "Limit the maximum estimated current of Crab at 50uA.\n"
73 "\n"
74 " makeschedule --source='IC 310' '--setup.IC 310.penalty=1.2'\n"
75 "\n"
76 "Multiply IC310's estimated relative threshold by a factor 1.2\n"
77 "\n";
78 cout << endl;
79#endif
80}
81
82
83struct MyDouble
84{
85 double val;
86 bool valid;
87 MyDouble(Configuration &conf, const string &str) : val(0)
88 {
89 valid = conf.Has(str);
90 if (valid)
91 val = conf.Get<double>(str);
92 }
93 MyDouble() : val(0), valid(false) {}
94};
95
96/*
97struct MinMax
98{
99 MyDouble min;
100 MyDouble max;
101 MinMax(Configuration &conf, const string &str)
102 {
103 min = MyDouble(conf, str+".min");
104 max = MyDouble(conf, str+".max");
105 }
106 MinMax() {}
107};
108*/
109
110struct Source
111{
112 // Global limits
113 static double max_current;
114 static double max_zd;
115
116 // Source descrition
117 string name;
118 uint16_t key;
119 EquPosn equ;
120
121 // Source specific limits
122 MyDouble maxzd;
123 MyDouble maxcurrent;
124 double penalty;
125
126 // Possible observation time
127 double begin;
128 double end;
129
130 // Threshold (sorting reference)
131 double threshold;
132
133 double duration() const { return end-begin; };
134
135 // Pre-observations (e.g. ratescan)
136 vector<string> preobs;
137
138 Source(const string &n="", uint16_t k=-1) : name(n), key(k), begin(0), threshold(std::numeric_limits<double>::max()) { }
139
140 //bool IsSpecial() const { return threshold==std::numeric_limits<double>::max(); }
141
142 double zd(const double &jd) const
143 {
144 return 90-GetHrzFromEqu(equ, jd).alt;
145 }
146
147 bool valid(const SolarObjects &so) const
148 {
149 const HrzPosn hrz = GetHrzFromEqu(equ, so.fJD);
150 const double current = FACT::PredictI(so, equ);
151
152 if (current>max_current)
153 return false;
154
155 if (hrz.alt<=0 || 90-hrz.alt>max_zd)
156 return false;
157
158 if (maxzd.valid && 90-hrz.alt>maxzd.val)
159 return false;
160
161 if (maxcurrent.valid && current>maxcurrent.val)
162 return false;
163
164 return true;
165 }
166
167 bool IsRangeValid(const double &jd_begin, const double &jd_end) const
168 {
169 const double step = 1./24/60;
170 for (double jd=jd_begin; jd<jd_end+step/2; jd++)
171 if (!valid(SolarObjects(jd)))
172 return false;
173
174 return true;
175 }
176
177 double getThreshold(const SolarObjects &so) const
178 {
179 const HrzPosn hrz = GetHrzFromEqu(equ, so.fJD);
180 const double current = FACT::PredictI(so, equ);
181
182 const double ratio = pow(cos((90-hrz.alt)*M_PI/180), -2.664);
183
184 return penalty*ratio*pow(current/6.2, 0.394);
185 }
186
187 bool calcThreshold(const SolarObjects &so)
188 {
189 const HrzPosn hrz = GetHrzFromEqu(equ, so.fJD);
190 const double current = FACT::PredictI(so, equ);
191
192 if (current>max_current)
193 return false;
194
195 if (hrz.alt<=0 || 90-hrz.alt>max_zd)
196 return false;
197
198 if (maxzd.valid && 90-hrz.alt>maxzd.val)
199 return false;
200
201 if (maxcurrent.valid && current>maxcurrent.val)
202 return false;
203
204 const double ratio = pow(cos((90-hrz.alt)*M_PI/180), -2.664);
205 threshold = penalty*ratio*pow(current/6.2, 0.394);
206
207 return true;
208 }
209};
210
211double Source::max_zd;
212double Source::max_current;
213
214bool SortByThreshold(const Source &i, const Source &j) { return i.threshold<j.threshold; }
215
216bool RescheduleFirstSources(vector<Source> &obs)
217{
218 if (obs.size()<2 || obs[0].duration()>=40./24/60 || obs[0].name=="SLEEP" || obs[1].name=="SLEEP")
219 return false;
220
221 cout << "First source [" << obs[0].name << "] detected < 40min" << endl;
222
223 const double obs1_duration = obs[1].end - obs[0].begin - 40./24/60;
224 const double obs0_end = obs[0].begin + 40./24/60;
225
226 // Check that:
227 // - the duration for the shrunken source obs[1] is still above 40min
228 // - obs[0] does not exceed 60deg at the end of its new window
229 // - obs[0] does not exceed any limit within the new window
230
231 if (obs1_duration>=40./24/60 && obs[0].IsRangeValid(obs[0].end, obs0_end))
232 {
233 obs[0].end = obs0_end;
234 obs[1].begin = obs0_end;
235
236 cout << "First source [" << obs[0].name << "] extended to 40min" << endl;
237
238 return false;
239 }
240
241 // Try to remove the first source, check if second source fullfills all limits
242 if (obs[1].IsRangeValid(obs[0].begin, obs[0].end))
243 {
244 cout << "First source [" << obs[0].name << "] removed" << endl;
245
246 obs[1].begin = obs[0].begin;
247 obs.erase(obs.begin());
248
249 return true;
250 }
251
252 // Try to remove the second source, check if the first source fullfills all limits
253 if (obs[0].IsRangeValid(obs[1].begin, obs[1].end))
254 {
255 cout << "Second source [" << obs[1].name << "] removed" << endl;
256
257 obs[0].end = obs[1].end;
258 obs.erase(obs.begin()+1);
259
260 if (obs.size()==0 || obs[0].name!=obs[1].name)
261 return true;
262
263 obs[0].end = obs[1].end;
264 obs.erase(obs.begin()+1);
265
266 cout << "Combined first two indentical sources [" << obs[0].name << "] into one observation" << endl;
267
268 return true;
269 }
270
271 cout << "No reschedule possible within limit." << endl;
272
273 return false;
274}
275
276bool RescheduleLastSources(vector<Source> &obs)
277{
278 // If observation time is smaller than 40min for the first source
279 // extend it to 40min if zenith angle will not go above 60deg.
280 const int last = obs.size()-1;
281 if (obs.size()<2 || obs[last].duration()>=40./24/60 || obs[last].name=="SLEEP" || obs[last-1].name=="SLEEP")
282 return false;
283
284 cout << "Last source [" << obs[last].name << "] detected < 40min" << endl;
285
286 const double obs1_duration = obs[last].end - 40./24/60 - obs[last-1].begin;
287 const double obs0_begin = obs[last].end - 40./24/60;
288
289 // Check that:
290 // - the duration for the shrunken source obs[1] is still above 40min
291 // - obs[0] does not exceed 60deg at the end of its new window
292 // - obs[0] does not exceed any limit within the new window
293
294 if (obs1_duration>=40./24/60 && obs[last].IsRangeValid(obs0_begin, obs[last].begin))
295 {
296 obs[last].begin = obs0_begin;
297 obs[last-1].end = obs0_begin;
298
299 cout << "Last source [" << obs[last].name << "] extended to 40min" << endl;
300
301 return false;
302 }
303
304 // Try to remove the last source, check if second source fullfills all limits
305 if (obs[last-1].IsRangeValid(obs[last].begin, obs[last].end))
306 {
307 cout << "Last source [" << obs[last].name << "] removed" << endl;
308
309 obs[last-1].end = obs[last].end;
310 obs.pop_back();
311
312 return true;
313 }
314
315 // Try to remove the second last source, check if the first source fullfills all limits
316 if (obs[last].IsRangeValid(obs[last-1].begin, obs[last-1].end))
317 {
318 cout << "Second last source [" << obs[last-1].name << "] removed" << endl;
319
320 obs[last].begin = obs[last-1].begin;
321 obs.erase(obs.begin()+obs.size()-2);
322
323 if (obs.size()==0 || obs[last-1].name!=obs[last-2].name)
324 return true;
325
326 obs[last-2].end = obs[last-1].end;
327 obs.pop_back();
328
329 cout << "Combined last two indentical sources [" << obs[last-1].name << "] into one observation" << endl;
330
331 return true;
332 }
333
334 cout << "No reschedule possible within limit." << endl;
335
336 return false;
337}
338
339bool RescheduleIntermediateSources(vector<Source> &obs)
340{
341 for (size_t i=1; i<obs.size()-1; i++)
342 {
343 if (obs[i].duration()>=40./24/60)
344 continue;
345
346 if (obs[i-1].name=="SLEEP" && obs[i+1].name=="SLEEP")
347 continue;
348
349 cout << "Intermediate source [" << obs[i].name << "] detected < 40min" << endl;
350
351 double intersection = -1;
352
353 if (obs[i-1].name=="SLEEP")
354 intersection = obs[i].begin;
355
356 if (obs[i+1].name=="SLEEP")
357 intersection = obs[i].end;
358
359 if (obs[i-1].name==obs[i+1].name)
360 intersection = obs[i].begin;
361
362 if (intersection<0)
363 {
364 const double step = 1./24/60;
365 for (double jd=obs[i].begin; jd<obs[i].end-step/2; jd+=step)
366 {
367 const SolarObjects so(jd);
368 if (obs[i-1].getThreshold(so)>=obs[i+1].getThreshold(so))
369 {
370 intersection = jd;
371 break;
372 }
373 }
374 }
375
376 if ((obs[i-1].name!="SLEEP" && !obs[i-1].IsRangeValid(obs[i-1].end, intersection)) ||
377 (obs[i+1].name!="SLEEP" && !obs[i+1].IsRangeValid(intersection, obs[i+1].begin)))
378 {
379 cout << "No reschedule possible within limits." << endl;
380 continue;
381 }
382
383 cout << "Intermediate source [" << obs[i].name << "] removed" << endl;
384
385 const bool underflow = obs[i-1].duration()*24*60<40 || obs[i+1].duration()*24*60<40;
386
387 obs[i-1].end = intersection;
388 obs[i+1].begin = intersection;
389 obs.erase(obs.begin()+i);
390
391 i--;
392
393 if (obs.size()>1 && obs[i].name==obs[i+1].name)
394 {
395 obs[i].end = obs[i+1].end;
396 obs.erase(obs.begin()+i+1);
397
398 cout << "Combined two surrounding indentical sources [" << obs[i].name << "] into one observation" << endl;
399
400 i--;
401
402 continue;
403 }
404
405 if (underflow)
406 cout << "WARNING - Neighbor source < 40min as well." << endl;
407 }
408 return false;
409}
410
411void Print(const vector<Source> &obs, double startup_offset)
412{
413 cout << Time(obs[0].begin-startup_offset).GetAsStr() << " STARTUP\n";
414 for (const auto& src: obs)
415 {
416 string tm = Time(src.begin).GetAsStr();
417
418 if (src.preobs.size()>0)
419 {
420 for (const auto& pre: src.preobs)
421 {
422 cout << tm << " " << pre << "\n";
423 tm = " ";
424 }
425 }
426
427 cout << tm << " " << src.name << " [";
428 cout << src.duration()*24*60 << "'";
429 if (src.name!="SLEEP")
430 cout << Tools::Form("; %.1f/%.1f", src.zd(src.begin), src.zd(src.end));
431 cout << "]";
432
433 if (src.duration()*24*60<40)
434 cout << " (!)";
435
436 cout << "\n";
437 }
438 cout << Time(obs.back().end).GetAsStr() << " SHUTDOWN" << endl;
439}
440
441int FillSql(Database &db, int enter, const vector<Source> &obs, double startup_offset)
442{
443 const string query0 = "SELECT COUNT(*) FROM Schedule WHERE DATE(ADDTIME(fStart, '-12:00')) = '"+Time(obs[0].begin).GetAsStr("%Y-%m-%d")+"'";
444
445 const mysqlpp::StoreQueryResult res0 = db.query(query0).store();
446
447 if (res0.num_rows()!=1)
448 {
449 cout << "Check for schedule size failed." << endl;
450 return 10;
451 }
452
453 if (uint32_t(res0[0][0])!=0)
454 {
455 cout << "Schedule not empty." << endl;
456 return 11;
457 }
458
459 const mysqlpp::StoreQueryResult res1 = db.query("SELECT fMeasurementTypeName, fMeasurementTypeKEY FROM MeasurementType").store();
460 map<string, uint32_t> types;
461 for (const auto &row: res1)
462 types.emplace(string(row[0]), uint32_t(row[1]));
463
464 ostringstream str;
465 str << "INSERT INTO Schedule (fStart, fUser, fMeasurementID, fMeasurementTypeKEY, fSourceKEY) VALUES\n";
466
467 str << "('" << Time(obs[0].begin-startup_offset).GetAsStr() << "', auto, 0, " << types["Startup"] << ", NULL), [Startup]\n";
468 for (const auto& src: obs)
469 {
470 string tm = Time(src.begin).GetAsStr();
471
472 /*
473 if (src.preobs.size()>0)
474 {
475 for (const auto& pre: src.preobs)
476 {
477 str << tm << " " << pre << "\n";
478 tm = " ";
479 }
480 }*/
481
482 if (src.name!="SLEEP")
483 str << "('" << tm << "', auto, 0, " << types["Data"] << ", " << src.key << "), [Data: " << src.name << "]\n";
484 else
485 str << "('" << tm << "', auto, 0, " << types["Sleep"] << ", NULL), [Sleep]\n";
486 }
487
488 str << "('" << Time(obs.back().end).GetAsStr() << "', auto, 0, " << types["Shutdown"] << ", NULL) [Shutdown]";
489
490 if (enter<0)
491 {
492 cout << str.str() << endl;
493 return 0;
494 }
495
496 db.query(str.str());
497
498 cout << "Schedule entered successfully into database." << endl;
499 return 0;
500}
501
502int main(int argc, const char* argv[])
503{
504// gROOT->SetBatch();
505
506 Configuration conf(argv[0]);
507 conf.SetPrintUsage(PrintUsage);
508 SetupConfiguration(conf);
509
510 if (!conf.DoParse(argc, argv, PrintHelp))
511 return 127;
512
513 // ------------------ Eval config ---------------------
514
515 const int enter = conf.Has("enter-schedule-into-database") ? (conf.Get<bool>("enter-schedule-into-database") ? 1 : -1) : 0;
516 if (enter && !conf.Has("schedule-database"))
517 throw runtime_error("enter-schedule-into-database required schedule-database.");
518
519 Time time;
520 if (conf.Has("date"))
521 time.SetFromStr(conf.Get<string>("date"));
522
523 if (enter && floor(time.JD())<ceil(Time().JD()))
524 throw runtime_error("Only future schedules can be entered into the database.");
525
526 Source::max_current = conf.Get<double>("max-current");
527 Source::max_zd = conf.Get<double>("max-zd");
528
529 const double startup_offset = conf.Get<double>("startup.offset")/60/24;
530
531 const double angle_sun_set = conf.Get<double>("data-taking.start");
532 const double angle_sun_rise = conf.Get<double>("data-taking.end");
533
534 if (startup_offset<0 || startup_offset>120)
535 throw runtime_error("Only values [0;120] are allowed for startup.offset");
536
537 if (angle_sun_set>-6)
538 throw runtime_error("datataking.start not allowed before sun at -6deg");
539
540 if (angle_sun_rise>-6)
541 throw runtime_error("datataking.end not allowed after sun at -6deg");
542
543 // -12: nautical
544 // Sun set with the same date than th provided date
545 // Sun rise on the following day
546 const RstTime sun_set = GetSolarRst(floor(time.JD())-0.5, angle_sun_set);
547 const RstTime sun_rise = GetSolarRst(floor(time.JD())+0.5, angle_sun_rise);
548
549 const double sunset = ceil(sun_set.set*24*60)/24/60;
550 const double sunrise = floor(sun_rise.rise*24*60)/24/60;
551
552 cout << "\n";
553 cout << "Date: " << Time(floor(sunset)) << "\n";
554 cout << "Set: " << Time(sun_set.set) << "\n";
555 cout << "Rise: " << Time(sun_rise.rise) << "\n";
556 cout << endl;
557
558 // ------------- Get Sources from databasse ---------------------
559
560 const vector<string> ourcenames = conf.Vec<string>("source");
561 const vector<string> sourcenames = conf.Vec<string>("source");
562 cout << "Nsources = " << sourcenames.size() << "\n";
563
564 string query = "SELECT fSourceName, fSourceKEY, fRightAscension, fDeclination FROM Source WHERE fSourceTypeKEY=1";
565 if (sourcenames.size()>0)
566 query += " AND fSourceName IN ('" + boost::algorithm::join(sourcenames, "', '")+"')";
567
568 const string sourcedb = conf.Get<string>("source-database");
569 const mysqlpp::StoreQueryResult res =
570 Database(sourcedb).query(query).store();
571
572 // ------------------ Eval config ---------------------
573
574 vector<Source> sources;
575 for (const auto &row: res)
576 {
577 const string name = string(row[0]);
578
579 Source src(name, row[1]);
580
581 src.equ.ra = double(row[2])*15;
582 src.equ.dec = double(row[3]);
583
584 src.maxzd = MyDouble(conf, "setup."+name+".max-zd");
585 src.maxcurrent = MyDouble(conf, "setup."+name+".max-current");
586 src.penalty = conf.Has("setup."+name+".penalty") ?
587 conf.Get<double>("setup."+name+".penalty") : 1;
588
589 src.preobs = conf.Vec<string>("preobs."+name);
590
591
592 cout << "[" << name << "]";
593
594 if (src.maxzd.valid)
595 cout << " Zd<" << src.maxzd.val;
596 if (src.penalty!=1)
597 cout << " Penalty=" << src.penalty;
598
599 cout << " " << boost::algorithm::join(src.preobs, "+") << endl;
600
601 /*
602 RstTime t1 = GetObjectRst(floor(sunset)-1, src.equ);
603 RstTime t2 = GetObjectRst(floor(sunset), src.equ);
604
605 src.rst.transit = t1.transit<floor(sunset) ? t2.transit : t1.transit;
606 src.rst.rise = t1.rise>src.rst.transit ? t2.rise : t1.rise;
607 src.rst.set = t1.set <src.rst.transit ? t2.set : t1.set;
608 */
609
610 sources.emplace_back(src);
611 }
612 cout << endl;
613
614 // -------------------------------------------------------------------------
615
616 vector<Source> obs;
617
618 const double step = 1./24/60;
619 for (double jd=sunset; jd<sunrise-step/2; jd+=step)
620 {
621 const SolarObjects so(jd);
622
623 vector<Source> vis;
624 for (auto& src: sources)
625 {
626 if (src.calcThreshold(so))
627 vis.emplace_back(src);
628 }
629
630 // In case no source was found, add a sleep source
631 Source src("SLEEP");
632 vis.emplace_back(src);
633
634 // Source has higher priority if minimum observation time not yet fullfilled
635 sort(vis.begin(), vis.end(), SortByThreshold);
636
637 if (obs.size()>0 && obs.back().name==vis[0].name)
638 continue;
639
640 vis[0].begin = jd;
641 obs.emplace_back(vis[0]);
642 }
643
644 if (obs.size()==0)
645 {
646 cout << "No source found." << endl;
647 return 1;
648 }
649
650 // -------------------------------------------------------------------------
651
652 for (auto it=obs.begin(); it<obs.end()-1; it++)
653 it[0].end = it[1].begin;
654 obs.back().end = sunrise;
655
656 // -------------------------------------------------------------------------
657
658 Print(obs, startup_offset);
659 cout << endl;
660
661 // -------------------------------------------------------------------------
662
663 while (RescheduleFirstSources(obs));
664 while (RescheduleLastSources(obs));
665 while (RescheduleIntermediateSources(obs));
666
667 // ---------------------------------------------------------------------
668
669 cout << endl;
670 Print(obs, startup_offset);
671 cout << endl;
672
673 // ---------------------------------------------------------------------
674
675 if (!enter)
676 return 0;
677
678 const string scheduledb = conf.Get<string>("schedule-database");
679
680 Database db(scheduledb);
681
682 if (enter>0)
683 db.query("LOCK TABLES Schedule WRITE");
684
685 const int rc = FillSql(db, enter, obs, startup_offset);
686
687 if (enter>0)
688 db.query("UNLOCK TABLES");
689
690 // ---------------------------------------------------------------------
691
692 return rc;
693}
Note: See TracBrowser for help on using the repository browser.