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

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