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

Last change on this file since 19685 was 19684, checked in by Daniela Dorner, 5 years ago
added new algorithms (visiblity ratio, time-shift) and featues (plotting, zd-histogram)
File size: 41.2 KB
Line 
1#include "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' (equiv. '2016-12-24 12:00:00' to '2016-12-25 11:59:59')")
21 ("svg", var<string>(), "File name to write a SVG file. Use together with --loop")
22 ("hist", var<string>(), "File name to write a histogram file. Use together with --loop; default: false")
23 ("loop", var<uint16_t>(uint16_t(0)), "Number of days to loop over (0:default, >=1:turns off database entry)")
24 ("source-database", var<string>()->required(), "Database link as in\n\tuser:password@server[:port]/database[?compress=0|1].")
25 ("schedule-database", var<string>(), "Database link as in\n\tuser:password@server[:port]/database[?compress=0|1].")
26 ("max-current", var<double>(90), "Global maximum current limit in uA")
27 ("max-zd", var<double>(75), "Global zenith distance limit in degree")
28 ("min-moon", var<double>(5), "Minimum angular separation to moon in degree")
29 ("exponent-visibility-ratio", var<double>(1), "Global exponent for visibility ratio")
30 ("exponent-time-shift", var<double>(1), "Global exponent for time shift visibility ratio")
31 ("source", vars<string>(), "List of all TeV sources to be included, names according to the database")
32 ("setup.*", var<double>(), "Setup for the sources to be observed")
33 ("preobs.*", vars<string>(), "Prescheduled observations")
34 ("startup.offset", var<double>(15), "Determines how many minutes the startup is scheduled before data-taking.start [0;120]")
35 ("data-taking.start", var<double>(-12), "Begin of data-taking in degree of sun below horizon")
36 ("data-taking.end", var<double>(-13.75), "End of data-taking in degree of sun below horizon")
37 ("use-visibility-ratio", po_bool(), "Use visibility ratio for scaling of threshold)")
38 ("use-lst-shadow", po_bool(), "Respect shadow from LST for scheduling")
39 ("print-hist", var<bool>(), "Print histogram to console; default: false)")
40 ("enter-schedule-into-database", var<bool>(), "Enter schedule into database (required schedule-database, false: dry-run)")
41 ;
42
43 po::positional_options_description p;
44 p.add("date", 1); // The first positional options
45 p.add("loop", 1); // The first positional options
46
47 conf.AddOptions(control);
48 conf.SetArgumentPositions(p);
49}
50
51void PrintUsage()
52{
53 cout <<
54 "makeschedule - Creates an automatic schedule\n"
55 "\n"
56 "Usage: makeschedule [yyyy-mm-dd [N]]\n";
57 cout << endl;
58}
59
60void PrintHelp()
61{
62 cout <<
63 "First for each minute of the night a list is calculated of all "
64 "selected sources fulfilling all global and all source specific "
65 "constraints, e.g. on the zenith distance or the current.\n"
66 "\n"
67 "The remaining source list is sorted by the relative threshold, "
68 "while the threshold is weighted with a user defined source "
69 "specific penalty. The first source of the list is taken to "
70 "be observed.\n"
71 "\n"
72 "In a next step the first and last source of the resulting schedule "
73 "are evaluated. If their observation time is below 40', it is tried "
74 "to extend it to 40min. If this violates one of the criteria mentioned "
75 "above or gives an observation time for the neighbouring source of "
76 "less than 40min, try to replace it by the neighbouring source. "
77 "If this also does not fulfil the requirements, the original "
78 "schedule remains unchanged.\n"
79 "\n"
80 "Now a similar check is run for all intermediate sources. They are "
81 "checked (from the beginning to the end, one by one), if they have "
82 "an observation time of less than 40min. In this case, it is tried "
83 "to remove them. The observation of the two neighbouring sources is "
84 "extended to their penalized point of equal relative threshold. "
85 "If this solution would not fulfil all criteria, no change is made.\n"
86 "\n"
87 "In a last step, all remaining sources with less than 5min "
88 "observation time are replaced with sleep and sleep after startup "
89 "or before shutdown are removed.\n"
90 "\n"
91 "\n"
92 "Examples:\n"
93 "\n"
94 " makeschedule 2016-12-24\n"
95 "\n"
96 "Calculate the Christmas schedule for 2016 using all TeV sources from the\n"
97 "database. If the date is omitted the current date is used.\n"
98 "\n"
99 " makeschedule --source='Mrk 421' --source='Mrk 501' --source='Crab'\n"
100 "\n"
101 "Use only the mentioned sources to calculate the schedule.\n"
102 "\n"
103 " makeschedule --source=Crab --setup.Crab.max-zd=30\n"
104 "\n"
105 "Limit the zenith distance of Crab into the range [0;30]deg.\n"
106 "\n"
107 " makeschedule --source=Crab --setup.Crab.max-current=50\n"
108 "\n"
109 "Limit the maximum estimated current of Crab at 50uA.\n"
110 "\n"
111 " makeschedule --source='IC 310' '--setup.IC 310.penalty=1.2'\n"
112 "\n"
113 "Multiply IC310's estimated relative threshold by a factor 1.2\n"
114 "\n";
115 cout << endl;
116}
117
118
119struct MyDouble
120{
121 double val;
122 bool valid;
123 MyDouble(Configuration &conf, const string &str) : val(0)
124 {
125 valid = conf.Has(str);
126 if (valid)
127 val = conf.Get<double>(str);
128 }
129 MyDouble() : val(0), valid(false) {}
130};
131
132/*
133struct MinMax
134{
135 MyDouble min;
136 MyDouble max;
137 MinMax(Configuration &conf, const string &str)
138 {
139 min = MyDouble(conf, str+".min");
140 max = MyDouble(conf, str+".max");
141 }
142 MinMax() {}
143};
144*/
145
146struct Source
147{
148 // Global limits
149 static double max_current;
150 static double max_zd;
151 static double min_moon;
152 static double vis_exponent;
153 static double ts_exponent;
154 static bool vis_ratio;
155 static bool lst_shadow;
156
157 // Source description
158 string name;
159 uint16_t key;
160 EquPosn equ;
161
162 // Source specific limits
163 MyDouble maxzd;
164 MyDouble maxcurrent;
165 double penalty;
166 double time_shift;
167 double visexponent;
168 double tsexponent;
169
170 // Derived values
171 double visratio;
172 double timeshift;
173
174 // Possible observation time
175 double begin;
176 double end;
177
178 // Threshold (sorting reference)
179 double threshold;
180
181 double duration() const { return end-begin; };
182
183 // Pre-observations (e.g. ratescan)
184 vector<string> preobs;
185
186 Source(const string &n="", uint16_t k=-1) : name(n), key(k), begin(0), threshold(std::numeric_limits<double>::max()) { }
187
188 //bool IsSpecial() const { return threshold==std::numeric_limits<double>::max(); }
189
190 //distribution of zenith distance
191 int zddistr[90];
192
193 double zd(const double &jd) const
194 {
195 return 90-GetHrzFromEqu(equ, jd).alt;
196 }
197
198 /*
199 // See https://www.fact-project.org/logbook/showthread.php?tid=6601
200
201 75 -135 outmost knot left side
202 70 -134 tower left side
203 65 -128 dish left side
204 58 -125 intermediate position of dish on left side
205 52 -118 intermediate position of dish on left side
206 50 ~ -110 top of dish - to have the rail outside the FoV zd has to be 49 (see attached picture taken at zd50, az-113.5)
207 52 -100 intermediate position of dish on right side
208 58 -92 intermediate position of dish on right side
209 65 -89 dish right side
210 71 -85 tower right side
211 75 -84 outmost knot right side
212 */
213
214 bool HasShadowFromLST(const ZdAzPosn &hrz) const
215 {
216 return (hrz.zd > 0.0379373*pow(hrz.az+109.23, 2) + 49.1224)
217 || (hrz.az>-79 && hrz.az<=-59 && hrz.zd>=70);
218 }
219
220 bool valid(const SolarObjects &so) const
221 {
222 const ZdAzPosn hrz = GetHrzFromEqu(equ, so.fJD+timeshift);
223 const ZdAzPosn true_hrz = timeshift==0 ? hrz : ZdAzPosn(GetHrzFromEqu(equ, so.fJD));
224 const double current = FACT::PredictI(so, equ);
225
226 if (current>max_current)
227 return false;
228
229 if (true_hrz.zd>max_zd)
230 return false;
231
232 if (maxzd.valid && true_hrz.zd>maxzd.val)
233 return false;
234
235 if (lst_shadow && HasShadowFromLST(true_hrz))
236 return false;
237
238 if (maxcurrent.valid && current>maxcurrent.val)
239 return false;
240
241 if (Nova::GetAngularSeparation(so.fMoonEqu, equ)<min_moon)
242 return false;
243
244 return true;
245 }
246
247 bool IsRangeValid(const double &jd_begin, const double &jd_end) const
248 {
249 const uint32_t n = nearbyint((jd_end-jd_begin)*24*60);
250 for (uint32_t i=0; i<n; i++)
251 if (!valid(SolarObjects(jd_begin+i/24./60.)))
252 return false;
253
254 return true;
255 }
256
257 double getThreshold(const SolarObjects &so) const
258 {
259 const ZdAzPosn hrz = GetHrzFromEqu(equ, so.fJD+timeshift);
260 const double current = FACT::PredictI(so, equ);
261
262 const double ratio = pow(cos(hrz.zd*M_PI/180), -2.664);
263 const double ratio2 = pow(current/6.2, 0.394);
264
265 return penalty*visratio*ratio*ratio2;
266 }
267
268 bool calcThreshold(const SolarObjects &so)
269 {
270 const ZdAzPosn hrz = GetHrzFromEqu(equ, so.fJD+timeshift);
271 const ZdAzPosn true_hrz = timeshift==0 ? hrz : ZdAzPosn(GetHrzFromEqu(equ, so.fJD));
272 const double current = FACT::PredictI(so, equ);
273
274 if (current>max_current)
275 return false;
276
277 if (true_hrz.zd>max_zd)
278 return false;
279
280 if (maxzd.valid && true_hrz.zd>maxzd.val)
281 return false;
282
283 if (lst_shadow && HasShadowFromLST(true_hrz))
284 return false;
285
286 if (maxcurrent.valid && current>maxcurrent.val)
287 return false;
288
289 if (Nova::GetAngularSeparation(so.fMoonEqu, equ)<min_moon)
290 return false;
291
292 const double ratio = pow(cos(hrz.zd*M_PI/180), -2.664);
293 const double ratio2 = pow(current/6.2, 0.394);
294
295 threshold = penalty*visratio*ratio*ratio2;
296
297 return true;
298 }
299};
300
301double Source::max_zd;
302double Source::max_current;
303double Source::min_moon;
304double Source::vis_exponent;
305double Source::ts_exponent;
306bool Source::vis_ratio;
307bool Source::lst_shadow;
308
309bool SortByThreshold(const Source &i, const Source &j) { return i.threshold<j.threshold; }
310
311bool RescheduleFirstSources(vector<Source> &obs)
312{
313 if (obs.size()<2 || obs[0].duration()>=40./24/60 || obs[0].name=="SLEEP" || obs[1].name=="SLEEP")
314 return false;
315
316 cout << "First source [" << obs[0].name << "] detected < 40min" << endl;
317
318 const double obs1_duration = obs[1].end - obs[0].begin - 40./24/60;
319 const double obs0_end = obs[0].begin + 40./24/60;
320
321 // Check that:
322 // - the duration for the shrunken source obs[1] is still above 40min
323 // - obs[0] does not exceed 60deg at the end of its new window
324 // - obs[0] does not exceed any limit within the new window
325
326 if (obs1_duration>=40./24/60 && obs[0].IsRangeValid(obs[0].end, obs0_end))
327 {
328 obs[0].end = obs0_end;
329 obs[1].begin = obs0_end;
330
331 cout << "First source [" << obs[0].name << "] extended to 40min" << endl;
332
333 return false;
334 }
335
336 // Try to remove the first source, check if second source fullfills all limits
337 if (obs[1].IsRangeValid(obs[0].begin, obs[0].end))
338 {
339 cout << "First source [" << obs[0].name << "] removed" << endl;
340
341 obs[1].begin = obs[0].begin;
342 obs.erase(obs.begin());
343
344 return true;
345 }
346
347 // Try to remove the second source, check if the first source fullfills all limits
348 if (obs[0].IsRangeValid(obs[1].begin, obs[1].end))
349 {
350 cout << "Second source [" << obs[1].name << "] removed" << endl;
351
352 obs[0].end = obs[1].end;
353 obs.erase(obs.begin()+1);
354
355 if (obs.size()==0 || obs[0].name!=obs[1].name)
356 return true;
357
358 obs[0].end = obs[1].end;
359 obs.erase(obs.begin()+1);
360
361 cout << "Combined first two indentical sources [" << obs[0].name << "] into one observation" << endl;
362
363 return true;
364 }
365
366 cout << "No reschedule possible within limit." << endl;
367
368 return false;
369}
370
371bool RescheduleLastSources(vector<Source> &obs)
372{
373 // If observation time is smaller than 40min for the first source
374 // extend it to 40min if zenith angle will not go above 60deg.
375 const int last = obs.size()-1;
376 if (obs.size()<2 || obs[last].duration()>=40./24/60 || obs[last].name=="SLEEP" || obs[last-1].name=="SLEEP")
377 return false;
378
379 cout << "Last source [" << obs[last].name << "] detected < 40min" << endl;
380
381 const double obs1_duration = obs[last].end - 40./24/60 - obs[last-1].begin;
382 const double obs0_begin = obs[last].end - 40./24/60;
383
384 // Check that:
385 // - the duration for the shrunken source obs[1] is still above 40min
386 // - obs[0] does not exceed 60deg at the end of its new window
387 // - obs[0] does not exceed any limit within the new window
388
389 if (obs1_duration>=40./24/60 && obs[last].IsRangeValid(obs0_begin, obs[last].begin))
390 {
391 obs[last].begin = obs0_begin;
392 obs[last-1].end = obs0_begin;
393
394 cout << "Last source [" << obs[last].name << "] extended to 40min" << endl;
395
396 return false;
397 }
398
399 // Try to remove the last source, check if second source fullfills all limits
400 if (obs[last-1].IsRangeValid(obs[last].begin, obs[last].end))
401 {
402 cout << "Last source [" << obs[last].name << "] removed" << endl;
403
404 obs[last-1].end = obs[last].end;
405 obs.pop_back();
406
407 return true;
408 }
409
410 // Try to remove the second last source, check if the first source fullfills all limits
411 if (obs[last].IsRangeValid(obs[last-1].begin, obs[last-1].end))
412 {
413 cout << "Second last source [" << obs[last-1].name << "] removed" << endl;
414
415 obs[last].begin = obs[last-1].begin;
416 obs.erase(obs.begin()+obs.size()-2);
417
418 if (obs.size()==0 || obs[last-1].name!=obs[last-2].name)
419 return true;
420
421 obs[last-2].end = obs[last-1].end;
422 obs.pop_back();
423
424 cout << "Combined last two indentical sources [" << obs[last-1].name << "] into one observation" << endl;
425
426 return true;
427 }
428
429 cout << "No reschedule possible within limit." << endl;
430
431 return false;
432}
433
434bool RescheduleIntermediateSources(vector<Source> &obs)
435{
436 bool changed = false;
437
438 for (size_t i=1; i<obs.size()-1; i++)
439 {
440 if (obs[i].duration()>=40./24/60)
441 continue;
442
443 if (obs[i-1].name=="SLEEP" && obs[i+1].name=="SLEEP")
444 continue;
445
446 cout << "Intermediate source [" << obs[i].name << "] detected < 40min" << endl;
447
448 double intersection = -1;
449
450 if (obs[i-1].name=="SLEEP")
451 intersection = obs[i].begin;
452
453 if (obs[i+1].name=="SLEEP")
454 intersection = obs[i].end;
455
456 if (obs[i-1].name==obs[i+1].name)
457 intersection = obs[i].begin;
458
459 if (intersection<0)
460 {
461 const uint32_t n = nearbyint((obs[i].end-obs[i].begin)*24*60);
462 for (uint32_t ii=0; ii<n; ii++)
463 {
464 const double jd = obs[i].begin+ii/24./60.;
465
466 const SolarObjects so(jd);
467 if (obs[i-1].getThreshold(so)>=obs[i+1].getThreshold(so))
468 {
469 intersection = jd;
470 break;
471 }
472 }
473 }
474
475 if ((obs[i-1].name!="SLEEP" && !obs[i-1].IsRangeValid(obs[i-1].end, intersection)) ||
476 (obs[i+1].name!="SLEEP" && !obs[i+1].IsRangeValid(intersection, obs[i+1].begin)))
477 {
478 cout << "No reschedule possible within limits." << endl;
479 continue;
480 }
481
482 cout << "Intermediate source [" << obs[i].name << "] removed" << endl;
483
484 // const bool underflow = obs[i-1].duration()*24*60<40 || obs[i+1].duration()*24*60<40;
485
486 obs[i-1].end = intersection;
487 obs[i+1].begin = intersection;
488 obs.erase(obs.begin()+i);
489
490 i--;
491
492 changed = true;
493
494 if (obs.size()>1 && obs[i].name==obs[i+1].name)
495 {
496 obs[i].end = obs[i+1].end;
497 obs.erase(obs.begin()+i+1);
498
499 cout << "Combined two surrounding indentical sources [" << obs[i].name << "] into one observation" << endl;
500
501 i--;
502
503 continue;
504 }
505
506 // if (underflow)
507 // cout << "WARNING - Neighbor source < 40min as well." << endl;
508 }
509 return changed;
510}
511
512// Remove mini source is able to violate limits
513bool RemoveMiniSources(vector<Source> &obs)
514{
515 bool changed = false;
516
517 for (size_t i=1; i<obs.size()-1; i++)
518 {
519 if (obs[i].duration()>=5./24/60)
520 continue;
521
522 // if (obs[i-1].name=="SLEEP" && obs[i+1].name=="SLEEP")
523 // continue;
524
525 cout << "Mini source [" << obs[i].name << "] detected < 5min" << endl;
526
527 if (obs[i-1].name==obs[i+1].name)
528 {
529 cout << "Combined surrounding identical sources [" << obs[i-1].name << "]" << endl;
530
531 obs[i-1].end = obs[i+1].end;
532 obs.erase(obs.begin()+i, obs.begin()+i+1);
533 i -= 2;
534
535 changed = true;
536
537 continue;
538 }
539
540 /*
541 if (obs[i-1].name=="SLEEP" && obs[i+1].name=="SLEEP")
542 {
543 obs[i-1].end = obs[i+2].begin;
544
545 obs.erase(obs.begin()+i+1);
546 obs.erase(obs.begin()+i);
547
548 i -= 2;
549
550 changed = true;
551
552 cout << "Combined two surrounding sleep into one" << endl;
553
554 continue;
555 }*/
556
557 // Note that this (intentionally) violates our safety limits
558
559 if (obs[i-1].name=="SLEEP")
560 {
561 cout << "Preceding SLEEP detected... extended next source [" << obs[i+1].name << "]" << endl;
562
563 obs[i-1].end = obs[i+1].begin;
564 obs.erase(obs.begin()+i);
565 i--;
566
567 changed = true;
568
569 continue;
570 }
571
572 if (obs[i+1].name=="SLEEP")
573 {
574 cout << "Following SLEEP detected... extended previous source [" << obs[i-1].name << "]" << endl;
575
576 obs[i-1].end = obs[i+1].begin;
577 obs.erase(obs.begin()+i);
578 i--;
579
580 changed = true;
581
582 continue;
583 }
584
585 // Algorithm based on RescheduleIntermediateSources
586 // Note that this (intentionally) violates our safety limits
587
588 const SolarObjects so1(obs[i-1].end);
589 const SolarObjects so2(obs[i+1].begin);
590
591 const double th1 = obs[i-1].getThreshold(so1);
592 const double th2 = obs[i+1].getThreshold(so2);
593
594 const double intersection = th1<th2 ? obs[i+1].begin : obs[i-1].end;
595
596 cout << "Intermediate mini source [" << obs[i].name << "] detected" << endl;
597 cout << (th1<th2?"Previous":"Next") << " source [" << (th1<th2?obs[i-1].name:obs[i+1].name) << "] extended" << endl;
598
599 obs[i-1].end = intersection;
600 obs[i+1].begin = intersection;
601 obs.erase(obs.begin()+i);
602
603 i--;
604
605 changed = true;
606 }
607
608 return changed;
609}
610
611void CheckStartupAndShutdown(vector<Source> &obs)
612{
613 if (obs.front().name=="SLEEP")
614 {
615 obs.erase(obs.begin());
616 cout << "Detected sleep after startup... removed." << endl;
617 }
618
619 if (obs.back().name=="SLEEP")
620 {
621 obs.pop_back();
622 cout << "Detected sleep before shutdown... removed." << endl;
623 }
624}
625
626void Print(char id, const vector<Source> &obs, double startup_offset)
627{
628 cout << id << " " << Time(obs[0].begin-startup_offset).GetAsStr() << " STARTUP\n";
629 for (const auto& src: obs)
630 {
631 string tm = Time(src.begin).GetAsStr();
632 if (src.preobs.size()>0)
633 {
634 for (const auto& pre: src.preobs)
635 {
636 cout << id << " " << tm << " " << pre << "\n";
637 tm = " ";
638 }
639 }
640
641 cout << id << " " << tm << " " << src.name << " [";
642 cout << src.duration()*24*60 << "'";
643 if (src.name!="SLEEP")
644 cout << Tools::Form("; %.1f/%.1f; %.2f; %.2fh", src.zd(src.begin), src.zd(src.end), src.visratio, src.timeshift*24);
645 cout << "]";
646
647 if (src.duration()*24*60<40)
648 cout << " (!)";
649
650 cout << "\n";
651 }
652 cout << id << " " << Time(obs.back().end).GetAsStr() << " SHUTDOWN" << endl;
653}
654
655void PrintRect(ofstream &svg, const uint16_t &x, const int16_t &y, const uint16_t &w, const uint16_t &h, const Source &src)
656{
657 static vector<string> colors =
658 {
659 "gray", // rgb(0,0,0)
660 "red",
661 "olive",
662 "blue",
663 "maroon",
664 "green",
665 "teal",
666 "navy",
667 "purple",
668 "silver",
669 "lime",
670 "fuchsia",
671 "yellow",
672 };
673
674 static map<uint16_t, uint16_t> col_keys;
675
676 const auto ic = col_keys.find(src.key);
677 if (ic==col_keys.end())
678 {
679 if (col_keys.size()>=colors.size())
680 cerr << "WARNING: More different sources (" << col_keys.size() << ") than colors (" << colors.size() << ")." << endl;
681
682 const auto n = col_keys.size();
683
684 col_keys[src.key] = n%colors.size();
685
686 svg << "<g>\n";
687 svg << " <rect x=\"" << 0 << "\" y=\"" << n*18 << "\" width=\"140\" height=\"18\" fill=\"" << colors[col_keys[src.key]] << "\"/>\n";
688 svg << " <text x=\"" << 0 << "\" y=\"" << n*18+14 << "\" font-family=\"Verdana\" font-size=\"16\" fill=\"white\">" << src.name << "</text>\n";
689 svg << "</g>\n";
690 }
691
692 const string color = colors[col_keys[src.key]];
693
694 svg << "<g>\n";
695 svg << " <defs>\n";
696 svg << " <linearGradient x1=\"0\" x2=\"0\" y1=\"0\" y2=\"1\" id=\"grad\">\n";
697
698 for (int i=0; i<11; i++)
699 {
700 const double opa = 1-src.zd(src.begin+(src.end-src.begin)*0.10*i)/(src.maxzd.valid?src.maxzd.val:src.max_zd);
701 svg << " <stop style=\"stop-color:" << color << "; stop-opacity:" << opa << "\" offset=\"" << i*10 << "%\"/>\n";
702 }
703 svg << " </linearGradient>\n";
704 svg << " </defs>\n";
705
706 svg << " <rect";
707 svg << " x=\"" << x*w+150 << "\"";
708 svg << " y=\"" << y << "\"";
709 svg << " width=\"" << w << "\"";
710 svg << " height=\"" << h << "\"";
711 svg << " style=\"fill:url(#grad);\"";
712 svg << "/>\n";
713 svg << "</g>\n";
714}
715
716
717void PrintSVG(ofstream &svg, const uint16_t &scale, const Time &T0, const vector<Source> &obs)
718{
719 const double jd = floor(T0.JD());
720
721 for (const auto& src: obs)
722 {
723 if (src.preobs.size()>0 || src.key==0 || src.name=="SLEEP")
724 continue;
725
726 const double diff = src.begin-jd;
727
728 const uint32_t b = 24*60*diff;
729 const uint32_t d = 24*60*(src.end-src.begin)+0.5;
730
731 PrintRect(svg, diff, b%(24*60)-6*60, scale, d, src);
732 }
733}
734
735int FillSql(Database &db, int enter, const vector<Source> &obs, double startup_offset)
736{
737 const string query0 = "SELECT COUNT(*) FROM Schedule WHERE DATE(ADDTIME(fStart, '-12:00')) = '"+Time(obs[0].begin).GetAsStr("%Y-%m-%d")+"'";
738
739 const mysqlpp::StoreQueryResult res0 = db.query(query0).store();
740
741 if (res0.num_rows()!=1)
742 {
743 cout << "Check for schedule size failed." << endl;
744 return 10;
745 }
746
747 if (uint32_t(res0[0][0])!=0)
748 {
749 cout << "Schedule not empty." << endl;
750 return 11;
751 }
752
753 const mysqlpp::StoreQueryResult res1 = db.query("SELECT fMeasurementTypeName, fMeasurementTypeKEY FROM MeasurementType").store();
754 map<string, uint32_t> types;
755 for (const auto &row: res1)
756 types.insert(make_pair(string(row[0]), uint32_t(row[1])));
757
758 ostringstream str;
759 str << "INSERT INTO Schedule (fStart, fUser, fMeasurementID, fMeasurementTypeKEY, fSourceKEY) VALUES ";
760
761 str << "('" << Time(obs[0].begin-startup_offset).GetAsStr() << "', 'auto', 0, " << types["Startup"] << ", NULL),\n"; // [Startup]\n";
762 for (const auto& src: obs)
763 {
764 string tm = Time(src.begin).GetAsStr();
765
766 /*
767 if (src.preobs.size()>0)
768 {
769 for (const auto& pre: src.preobs)
770 {
771 str << tm << " " << pre << "\n";
772 tm = " ";
773 }
774 }*/
775
776 if (src.name!="SLEEP")
777 str << "('" << tm << "', 'auto', 0, " << types["Data"] << ", " << src.key << "),\n"; // [Data: " << src.name << "]\n";
778 else
779 str << "('" << tm << "', 'auto', 0, " << types["Sleep"] << ", NULL),\n"; // [Sleep]\n";
780 }
781
782 str << "('" << Time(obs.back().end).GetAsStr() << "', 'auto', 0, " << types["Shutdown"] << ", NULL)";// [Shutdown]";
783
784 if (enter<0)
785 {
786 cout << str.str() << endl;
787 return 0;
788 }
789
790 db.query(str.str()).exec();
791
792 cout << "Schedule entered successfully into database." << endl;
793 return 0;
794}
795
796vector<Source> GetSources(Configuration &conf)
797{
798 const vector<string> sourcenames = conf.Vec<string>("source");
799 cout << "Nsources = " << sourcenames.size() << "\n";
800
801 string query = "SELECT fSourceName, fSourceKEY, fRightAscension, fDeclination FROM Source WHERE fSourceTypeKEY=1";
802 if (sourcenames.size()>0)
803 query += " AND fSourceName IN ('" + boost::algorithm::join(sourcenames, "', '")+"')";
804
805 const string sourcedb = conf.Get<string>("source-database");
806 const mysqlpp::StoreQueryResult res =
807 Database(sourcedb).query(query).store();
808
809 // ------------------ Eval config ---------------------
810
811 vector<Source> sources;
812 for (const auto &row: res)
813 {
814 //cout << endl;
815 const string name = string(row[0]);
816
817 Source src(name, row[1]);
818
819 src.equ.ra = double(row[2])*15;
820 src.equ.dec = double(row[3]);
821
822 src.maxzd = MyDouble(conf, "setup."+name+".max-zd");
823 src.maxcurrent = MyDouble(conf, "setup."+name+".max-current");
824 src.penalty = conf.Has("setup."+name+".penalty") ?
825 conf.Get<double>("setup."+name+".penalty") : 1;
826 src.visexponent = conf.Has("setup."+name+".exponent-visibility-ratio") ?
827 conf.Get<double>("setup."+name+".exponent-visibility-ratio") : 1;
828 src.tsexponent = conf.Has("setup."+name+".exponent-time-shift") ?
829 conf.Get<double>("setup."+name+".exponent-time-shift") : 1;
830 src.time_shift = conf.Has("setup."+name+".time-shift") ?
831 conf.Get<double>("setup."+name+".time-shift")/24 : 0;
832
833 src.preobs = conf.Vec<string>("preobs."+name);
834
835 cout << "[" << src.key << ":" << name << "]";
836
837 if (src.maxzd.valid)
838 cout << " Zd<" << src.maxzd.val;
839 if (src.penalty!=1)
840 cout << " Penalty=" << src.penalty;
841 if (src.visexponent!=1)
842 cout << " Exponent [VR]=" << src.visexponent;
843 if (src.tsexponent!=1)
844 cout << " Exponent [TS]=" << src.tsexponent;
845 if (src.time_shift!=0)
846 cout << " Time-shift=" << src.time_shift*24 << "h";
847
848 cout << " " << boost::algorithm::join(src.preobs, "+") << endl;
849
850 for (int k=0; k<90; k++)
851 src.zddistr[k]=0;
852 /*
853 RstTime t1 = GetObjectRst(floor(sunset)-1, src.equ);
854 RstTime t2 = GetObjectRst(floor(sunset), src.equ);
855
856 src.rst.transit = t1.transit<floor(sunset) ? t2.transit : t1.transit;
857 src.rst.rise = t1.rise>src.rst.transit ? t2.rise : t1.rise;
858 src.rst.set = t1.set <src.rst.transit ? t2.set : t1.set;
859 */
860
861 sources.emplace_back(src);
862 }
863 cout << endl;
864
865 return sources;
866}
867
868int main(int argc, const char* argv[])
869{
870// gROOT->SetBatch();
871
872 Configuration conf(argv[0]);
873 conf.SetPrintUsage(PrintUsage);
874 SetupConfiguration(conf);
875
876 if (!conf.DoParse(argc, argv, PrintHelp))
877 return 127;
878
879 // ------------------ Eval config ---------------------
880
881 const int enter = conf.Has("enter-schedule-into-database") ? (conf.Get<bool>("enter-schedule-into-database") ? 1 : -1) : 0;
882 if (enter && !conf.Has("schedule-database"))
883 throw runtime_error("enter-schedule-into-database requires schedule-database.");
884
885 Time time;
886 if (conf.Has("date"))
887 time.SetFromStr(conf.Get<string>("date")+" 12:00:00");
888 else
889 time=floor(Time().JD());
890
891 /*
892 cout << "-------> " << setprecision(12) << time.JD() << endl;
893 cout << " " << time.GetAsStr()
894 << " " << ceil(Time().JD())
895 << " " << floor(Time().JD())
896 << endl;
897 */
898 const uint16_t loop = conf.Get<uint16_t>("loop");
899
900 if (enter && floor(time.JD())<ceil(Time().JD()))
901 throw runtime_error("Only future schedules can be entered into the database.");
902
903 Source::max_current = conf.Get<double>("max-current");
904 Source::max_zd = conf.Get<double>("max-zd");
905 Source::min_moon = conf.Get<double>("min-moon");
906 Source::vis_exponent= conf.Get<double>("exponent-visibility-ratio");
907 Source::ts_exponent = conf.Get<double>("exponent-time-shift");
908 Source::vis_ratio = conf.Get<bool>("use-visibility-ratio");
909 Source::lst_shadow = conf.Get<bool>("use-lst-shadow");
910
911 const double startup_offset = conf.Get<double>("startup.offset")/60/24;
912
913 const double angle_sun_set = conf.Get<double>("data-taking.start");
914 const double angle_sun_rise = conf.Get<double>("data-taking.end");
915
916 if (startup_offset<0 || startup_offset>120)
917 throw runtime_error("Only values [0;120] are allowed for startup.offset");
918
919 if (angle_sun_set>-6)
920 throw runtime_error("datataking.start not allowed before sun at -6deg");
921
922 if (angle_sun_rise>-6)
923 throw runtime_error("datataking.end not allowed after sun at -6deg");
924
925 cout << "\n";
926
927 cout << "Global maximum current allowed: " << Source::max_current << " uA/pix\n";
928 cout << "Global maximum zenith distance: " << Source::max_zd << " deg\n";
929 cout << "Min. angular separation to moon: " << Source::min_moon << " deg\n";
930
931 cout << "\n";
932
933 // ------------- SVG ---------------------------------------------
934
935 const uint16_t svgscale = loop>0 ? ::min((2*1080)/loop, 5) : 0;
936
937 ofstream svg;
938 if (conf.Has("svg") && loop>0)
939 {
940 svg.open(conf.Get<string>("svg"));
941 if (!svg)
942 {
943 cerr << "ERROR: Writing to '" << conf.Get<string>("svg") << "' failed: " << strerror(errno) << endl;
944 return 1;
945 }
946
947 svg << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
948 svg << "<svg width=\"" << loop*svgscale+150 << "\" height=\"840\" xmlns=\"http://www.w3.org/2000/svg\">\n";
949 svg << "<rect width=\"" << loop*svgscale+150 << "\" height=\"840\" fill=\"black\"/>\n";
950
951 //svg << "<svg width=\"" << loop*svgscale+150 << "\" height=\"840\">\n";
952 //svg << "<rect x=\"0\" y=\"0\" width=\"" << loop*svgscale+150 << "\" height=\"840\" fill=\"black\"/>\n";
953 }
954
955 // ------------- Get Sources from databasse ---------------------
956
957 vector<Source> sources = GetSources(conf);
958
959 //cout << "VR: " << Source::vis_ratio << endl;
960
961 const uint16_t N = ::max<uint16_t>(loop, 1);
962 for (int iday=0; iday<N; iday++)
963 {
964 // ----------- Define time slots for the day ---------------------
965
966 // -12: nautical
967 // Sun set with the same date than th provided date
968 // Sun rise on the following day
969 const RstTime sun_set = GetSolarRst(floor(time.JD()+iday)-0.5, angle_sun_set);
970 const RstTime sun_rise = GetSolarRst(floor(time.JD()+iday)+0.5, angle_sun_rise);
971
972 const double sunset = ceil(sun_set.set*24*60) /24/60 + 1e-9;
973 const double sunrise = floor(sun_rise.rise*24*60)/24/60 + 1e-9;
974
975 cout << "Date: " << Time(floor(sunset)).GetAsStr() << "\n";
976 cout << "Set: " << Time(sunset).GetAsStr() << " [" << Time(sun_set.set) << "]\n";
977 cout << "Rise: " << Time(sunrise).GetAsStr() << " [" << Time(sun_rise.rise) << "]\n";
978 cout << "\n";
979
980 const double vis_sun = sun_rise.rise - sun_set.set;
981
982 for (auto& src: sources)
983 {
984 const double maxzd = min(Source::max_zd, src.maxzd.valid?src.maxzd.val:90);
985
986 int8_t sign = 0;
987
988 RstTime rst;
989 const int rc = GetObjectRst(rst, src.equ, sun_set.set, 90-maxzd);
990 switch (rc)
991 {
992 case -1:
993 // circumpolar below horizon - not visible
994 src.visratio = 0;
995 break;
996
997 case 0:
998 {
999 // Although, according to the documentation, everything should be alinged
1000 // between JD and JD+1, it is not... therefore, we align ourselves
1001 if (rst.rise-sun_set.set>1)
1002 rst.rise -= 1;
1003 if (rst.set-sun_set.set>1)
1004 rst.set -= 1;
1005 if (rst.rise-sun_set.set<0)
1006 rst.rise += 1;
1007 if (rst.set-sun_set.set<0)
1008 rst.set += 1;
1009
1010 // Now: rst.rise/rst.set is aligned between sunset and sunset+1:
1011 //
1012 // JD JD+1
1013 // 1) SUN.SET | | sun.rise RST.RISE rst.set ... SUN.SET |
1014 // 2) SUN.SET |++++++++++++++++++++++++| sun.rise rst.set RST.RISE ... SUN.SET |
1015 // 3) SUN.SET | RST.RISE+++++++++| sun.rise rst.set ... SUN.SET |
1016 // 4) SUN.SET |++++++++rst.set | sun.rise rst.rise ... SUN.SET |
1017 // 5) SUN.SET | RST.RISE+++rst.set | sun.rise ... SUN.SET |
1018 // 6) SUN.SET |+++rst.set RST.RISE+++| sun.rise ... SUN.SET |
1019
1020 if (rst.rise<sun_rise.rise && sun_rise.rise<rst.set)
1021 sign = 1;
1022 if (rst.set<sun_rise.rise && sun_rise.rise<rst.rise)
1023 sign = -1;
1024
1025 // Identify case 6
1026 const bool case6 = rst.set<rst.rise && rst.rise<sun_rise.rise;
1027
1028 // make sure that the visibility calculation of the source
1029 // yields positive valiues and the overlap calculation works
1030 if (rst.set<rst.rise)
1031 rst.rise -= 1;
1032
1033 // Now: rst.rise is always before rst.set
1034 //
1035 // JD JD+1
1036 // 1) ... SUN.SET | | sun.rise RST.RISE rst.set ...
1037 // 2) ... RST.RISE SUN.SET |++++++++++++++++++++++++| sun.rise rst.set ...
1038 // 3) ... SUN.SET | RST.RISE+++++++++| sun.rise rst.set ...
1039 // 4) ... RST.RISE SUN.SET |++++++++rst.set | sun.rise rst.rise ...
1040 // 5) ... SUN.SET | RST.RISE+++rst.set | sun.rise ...
1041 // 6) ... RST.RISE SUN.SET |+++rst.set [RST.RISE]++| sun.rise ...
1042
1043 // Total visibility of the source itself (this is now always positive)
1044 const double vis_rst = rst.set-rst.rise;
1045
1046 // Calculate overlap region
1047 // Visibility of the source between sunset and sunrise
1048 const double vis_night = case6 ? vis_sun - (1-vis_rst) : min(rst.set, sun_rise.rise) - max(rst.rise, sun_set.set);
1049
1050 //vis_sun-(1-vis_rst) = vis_sun-(1-(set-rise)) = vis_sun-(1-(set-(rise-1))) = vis_sun-(1-set+(rise-1)) = vis_sun-(rise-set)
1051
1052 // General visibility of the source (but maximum one night)
1053 const double vis_total = min(vis_rst, vis_sun);
1054
1055 // Special treatment of case 1 (vis_night<0 means that there is no overlap)
1056 src.visratio = vis_night<0 ? 0 : vis_night / vis_total;
1057
1058 // cout << src.name << " " << Time(rst.rise).GetAsStr() << " " << Time(rst.set).GetAsStr() << " " << vis_night << " " << vis_total << " " << vis_rst << " " << vis_sun << " " << case6 << " " << test << endl;
1059 break;
1060 }
1061
1062 case 1:
1063 // circumpolar above horizon -> always visible
1064 src.visratio = 1;
1065 break;
1066 }
1067
1068 src.timeshift = src.time_shift * sign*(1-pow(src.visratio, src.tsexponent*Source::ts_exponent));
1069
1070 src.visratio = Source::vis_ratio ? pow(src.visratio, src.visexponent*Source::vis_exponent) : 1;
1071 }
1072
1073 // -------------------------------------------------------------------------
1074
1075 vector<Source> obs;
1076
1077 const uint32_t n = nearbyint((sunrise-sunset)*24*60);
1078 for (uint32_t i=0; i<n; i++)
1079 {
1080 const double jd = sunset + i/24./60.;
1081
1082 const SolarObjects so(jd);
1083
1084 vector<Source> vis;
1085 for (auto& src: sources)
1086 {
1087 if (src.calcThreshold(so))
1088 vis.emplace_back(src);
1089 }
1090
1091 // In case no source was found, add a sleep source
1092 Source src("SLEEP");
1093 vis.emplace_back(src);
1094
1095 // Source has higher priority if minimum observation time not yet fullfilled
1096 sort(vis.begin(), vis.end(), SortByThreshold);
1097
1098 if (obs.size()>0 && obs.back().name==vis[0].name)
1099 continue;
1100
1101 vis[0].begin = jd;
1102 obs.emplace_back(vis[0]);
1103 }
1104
1105 if (obs.size()==0)
1106 {
1107 cout << "No source found." << endl;
1108 return 1;
1109 }
1110
1111 // -------------------------------------------------------------------------
1112
1113 for (auto it=obs.begin(); it<obs.end()-1; it++)
1114 it[0].end = it[1].begin;
1115 obs.back().end = sunrise;
1116
1117 // -------------------------------------------------------------------------
1118
1119 Print('-', obs, startup_offset);
1120 cout << endl;
1121
1122 // -------------------------------------------------------------------------
1123
1124 while (RescheduleFirstSources(obs));
1125 while (RescheduleLastSources(obs));
1126 while (RescheduleIntermediateSources(obs));
1127 while (RemoveMiniSources(obs));
1128
1129 CheckStartupAndShutdown(obs);
1130
1131 // ---------------------------------------------------------------------
1132
1133 cout << endl;
1134 Print('*', obs, startup_offset);
1135 cout << endl;
1136
1137 // ---------------------------------------------------------------------
1138
1139 //fill zd-distribution
1140 for (auto& src2: sources)
1141 {
1142 //cout << "source: " << src2.name << " " << src2.key << endl;
1143 for (const auto& src: obs)
1144 {
1145 //cout << "source: " << src.name << " " << src.key << endl;
1146 const uint32_t n2 = nearbyint((src.end-src.begin)*24*60);
1147 for (uint32_t i=0; i<n2; i++)
1148 {
1149 const double jd = src.begin + i/24./60.;
1150 const int zd = (int)src.zd(jd);
1151 //cout << " zd:" << floor(zd) << " " << zd << endl;
1152 //cout << " zd: " << zd << " " << src2.zddistr[zd] << flush;
1153 if (src.key==src2.key)
1154 {
1155 //cout << " -> " << flush;
1156 src2.zddistr[zd]++;
1157 }
1158 //cout << " " << src2.zddistr[zd] << endl;
1159 }
1160 }
1161 }
1162
1163 // ---------------------------------------------------------------------
1164
1165 if (svg.is_open())
1166 PrintSVG(svg, svgscale, time, obs);
1167
1168 // ---------------------------------------------------------------------
1169
1170 if (loop>0)
1171 continue;
1172
1173 if (!enter)
1174 return 0;
1175
1176 const string scheduledb = conf.Get<string>("schedule-database");
1177
1178 Database db(scheduledb);
1179
1180 if (enter>0)
1181 db.query("LOCK TABLES Schedule WRITE");
1182
1183 const int rc = FillSql(db, enter, obs, startup_offset);
1184
1185 if (enter>0)
1186 db.query("UNLOCK TABLES");
1187
1188 return rc;
1189 }
1190
1191 if (svg.is_open())
1192 svg << "</svg>" << endl;
1193
1194 // ---------------------------------------------------------------------
1195 if (conf.Has("print-hist") && conf.Get<bool>("print-hist"))
1196 {
1197 for (const auto& src: sources)
1198 {
1199 cout << "ZD-DISTRIBUTION [" << src.name << "]: " << flush;
1200 //print zd-distr
1201 for (int k=0; k<90; k++)
1202 cout << src.zddistr[k] << " " << flush;
1203 cout << endl;
1204 //plot zd-distr
1205 for (int k=0; k<90; k++)
1206 {
1207 if (src.zddistr[k]==0)
1208 continue;
1209 printf("%02d", k);
1210 for (int k2=0; k2<(int)(src.zddistr[k]/loop); k2++)
1211 cout << "*" << flush;
1212 cout << endl;
1213 }
1214 }
1215 }
1216
1217 if (!conf.Has("hist"))
1218 return 0;
1219
1220 ofstream hist(conf.Get<string>("hist").c_str());
1221 if (!hist)
1222 {
1223 cerr << "ERROR: Writing to '" << conf.Get<string>("hist") << "' failed: " << strerror(errno) << endl;
1224 return 1;
1225 }
1226
1227 hist << "# " << flush;
1228 for (const auto& src: sources)
1229 hist << src.name << " " << flush;
1230 hist << endl;
1231
1232 hist << "# " << flush;
1233 for (const auto& src: sources)
1234 hist << src.key << " " << flush;
1235 hist << endl;
1236
1237 for (int k=0; k<90; k++)
1238 {
1239 hist << k << " " << flush;
1240 for (const auto& src: sources)
1241 hist << src.zddistr[k] << " " << flush;
1242 hist << endl;
1243 }
1244
1245 return 0;
1246}
Note: See TracBrowser for help on using the repository browser.