source: trunk/FACT++/src/ratecontrol.cc@ 15027

Last change on this file since 15027 was 15027, checked in by tbretz, 12 years ago
Added CALIBRATE_BY_CURRENT command which allows to set the global minimum threshold from the measured current.
File size: 22.1 KB
Line 
1#include <valarray>
2
3#include "Dim.h"
4#include "Event.h"
5#include "Shell.h"
6#include "StateMachineDim.h"
7#include "Connection.h"
8#include "Configuration.h"
9#include "Console.h"
10
11#include "tools.h"
12
13#include "LocalControl.h"
14
15#include "HeadersFTM.h"
16#include "HeadersDrive.h"
17#include "HeadersRateScan.h"
18#include "HeadersRateControl.h"
19
20namespace ba = boost::asio;
21namespace bs = boost::system;
22namespace dummy = ba::placeholders;
23
24using namespace std;
25
26// ------------------------------------------------------------------------
27
28#include "DimDescriptionService.h"
29#include "DimState.h"
30
31// ------------------------------------------------------------------------
32
33class StateMachineRateControl : public StateMachineDim//, public DimInfoHandler
34{
35private:
36 bool fTriggerOn;
37
38 vector<bool> fBlock;
39
40 DimVersion fDim;
41 DimDescribedState fDimFTM;
42 DimDescribedState fDimRS;
43 DimDescribedState fDimDrive;
44
45 DimDescribedService fDimThreshold;
46
47 float fTargetRate;
48 float fTriggerRate;
49
50 uint16_t fThresholdMin;
51 uint16_t fThresholdReference;
52
53 deque<pair<Time,float>> fCurrents;
54
55 bool fVerbose;
56 bool fCalibrateByCurrent;
57
58 uint64_t fCounter;
59
60 Time fCalibrationTimeStart;
61
62 bool CheckEventSize(const EventImp &evt, size_t size)
63 {
64 if (size_t(evt.GetSize())==size)
65 return true;
66
67 if (evt.GetSize()==0)
68 return false;
69
70 ostringstream msg;
71 msg << evt.GetName() << " - Received event has " << evt.GetSize() << " bytes, but expected " << size << ".";
72 Fatal(msg);
73 return false;
74 }
75
76 vector<uint16_t> fThresholds;
77
78 void PrintThresholds(const FTM::DimStaticData &sdata)
79 {
80 //if (!fVerbose)
81 // return;
82
83 if (fThresholds.size()==0)
84 return;
85
86 Out() << "Min. DAC=" << fThresholdMin << endl;
87
88 int t=0;
89 for (t=0; t<160; t++)
90 if (sdata.fThreshold[t]!=fThresholds[t])
91 break;
92
93 if (t==160)
94 return;
95
96 for (int j=0; j<10; j++)
97 {
98 for (int k=0; k<4; k++)
99 {
100 for (int i=0; i<4; i++)
101 if (fThresholds[i+k*4+j*16]!=fThresholdMin)
102 Out() << setw(3) << fThresholds[i+k*4+j*16] << " ";
103 else
104 Out() << " - ";
105 Out() << " ";
106 }
107 Out() << endl;
108 }
109 Out() << endl;
110 }
111
112 void Step(int idx, float step)
113 {
114 uint16_t diff = fThresholds[idx]+int16_t(truncf(step));
115 if (diff<fThresholdMin)
116 diff=fThresholdMin;
117
118 if (diff==fThresholds[idx])
119 return;
120
121 if (fVerbose)
122 {
123 Out() << idx/40 << "|" << (idx/4)%10 << "|" << idx%4;
124 Out() << (step>0 ? " += " : " -= ");
125 Out() << fabs(step) << " (" << diff << ")" << endl;
126 }
127
128 const uint32_t val[2] = { idx, diff };
129 DimClient::sendCommandNB("FTM_CONTROL/SET_THRESHOLD", (void*)val, 8);
130
131 fBlock[idx/4] = true;
132 }
133
134 void ProcessPatches(const FTM::DimTriggerRates &sdata)
135 {
136
137 // Caluclate Median and deviation
138 vector<float> medb(sdata.fBoardRate, sdata.fBoardRate+40);
139 vector<float> medp(sdata.fPatchRate, sdata.fPatchRate+160);
140
141 sort(medb.begin(), medb.end());
142 sort(medp.begin(), medp.end());
143
144 vector<float> devb(40);
145 for (int i=0; i<40; i++)
146 devb[i] = fabs(sdata.fBoardRate[i]-medb[i]);
147
148 vector<float> devp(160);
149 for (int i=0; i<160; i++)
150 devp[i] = fabs(sdata.fPatchRate[i]-medp[i]);
151
152 sort(devb.begin(), devb.end());
153 sort(devp.begin(), devp.end());
154
155 double mb = (medb[19]+medb[20])/2;
156 double mp = (medp[79]+medp[80])/2;
157
158 double db = devb[27];
159 double dp = devp[109];
160
161 // If any is zero there is something wrong
162 if (mb==0 || mp==0 || db==0 || dp==0)
163 return;
164
165 if (fVerbose)
166 {
167 Out() << "Patch: Median=" << mp << " Dev=" << dp << endl;
168 Out() << "Board: Median=" << mb << " Dev=" << db << endl;
169 }
170
171 for (int i=0; i<40; i++)
172 {
173 if (fBlock[i])
174 {
175 fBlock[i] = false;
176 continue;
177 }
178
179 int maxi = -1;
180
181 const float dif = fabs(sdata.fBoardRate[i]-mb)/db;
182 if (dif>5)
183 {
184 if (fVerbose)
185 Out() << "B" << i << ": " << dif << endl;
186
187 float max = sdata.fPatchRate[i*4];
188 maxi = 0;
189
190 for (int j=1; j<4; j++)
191 if (sdata.fPatchRate[i*4+j]>max)
192 {
193 max = sdata.fPatchRate[i*4+j];
194 maxi = j;
195 }
196 }
197
198 for (int j=0; j<4; j++)
199 {
200 // For the noise pixel correct down to median+3*deviation
201 if (maxi==j)
202 {
203 // This is the step which has to be performed to go from
204 // a NSB rate of sdata.fPatchRate[i*4+j]
205
206
207 const float step = (log10(sdata.fPatchRate[i*4+j])-log10(mp+5*dp))/0.039;
208 // * (dif-5)/dif
209 Step(i*4+j, step);
210 continue;
211 }
212
213 // For pixels below the meadian correct also back to median+3*deviation
214 if (sdata.fPatchRate[i*4+j]<mp)
215 {
216 const float step = (log10(sdata.fPatchRate[i*4+j])-log10(mp+3*dp))/0.039;
217 Step(i*4+j, step);
218 continue;
219 }
220
221 const float step = -1.5*(log10(mp+dp)-log10(mp))/0.039;
222 Step(i*4+j, step);
223 }
224 }
225 }
226
227 int ProcessCamera(const FTM::DimTriggerRates &sdata)
228 {
229 if (fCounter++==0)
230 return GetCurrentState();
231
232 // Caluclate Median and deviation
233 vector<float> medb(sdata.fBoardRate, sdata.fBoardRate+40);
234
235 sort(medb.begin(), medb.end());
236
237 vector<float> devb(40);
238 for (int i=0; i<40; i++)
239 devb[i] = fabs(sdata.fBoardRate[i]-medb[i]);
240
241 sort(devb.begin(), devb.end());
242
243 double mb = (medb[19]+medb[20])/2;
244 double db = devb[27];
245
246 // If any is zero there is something wrong
247 if (mb==0 || db==0)
248 {
249 Warn("The median or the deviation of all board rates is zero... cannot calibrate.");
250 return GetCurrentState();
251 }
252
253 double avg = 0;
254 int num = 0;
255
256 for (int i=0; i<40; i++)
257 {
258 if ( fabs(sdata.fBoardRate[i]-mb)<2.5*db)
259 {
260 avg += sdata.fBoardRate[i];
261 num++;
262 }
263 }
264
265 fTriggerRate = avg/num * 40;
266
267 if (fVerbose)
268 {
269 Out() << "Board: Median=" << mb << " Dev=" << db << endl;
270 Out() << "Camera: " << fTriggerRate << " (" << sdata.fTriggerRate << ", n=" << num << ")" << endl;
271 Out() << "Target: " << fTargetRate << endl;
272 }
273
274 if (sdata.fTriggerRate<fTriggerRate)
275 fTriggerRate = sdata.fTriggerRate;
276
277 // ----------------------
278
279 /*
280 if (avg>0 && avg<fTargetRate)
281 {
282 // I am assuming here (and at other places) the the answer from the FTM when setting
283 // the new threshold always arrives faster than the next rate update.
284 fThresholdMin = fThresholds[0];
285 Out() << "Setting fThresholdMin to " << fThresholds[0] << endl;
286 }
287 */
288
289 if (fTriggerRate>0 && fTriggerRate<fTargetRate)
290 {
291 fThresholds.assign(160, fThresholdMin);
292
293 const RateControl::DimThreshold data = { fThresholdMin, fCalibrationTimeStart.Mjd(), Time().Mjd() };
294 fDimThreshold.setQuality(0);
295 fDimThreshold.Update(data);
296
297 ostringstream out;
298 out << setprecision(3);
299 out << "Measured rate " << fTriggerRate << "Hz below target rate " << fTargetRate << "... mininum threshold set to " << fThresholdMin;
300 Info(out);
301
302 return RateControl::State::kGlobalThresholdSet;
303 }
304
305 // This is a step towards a threshold at which the NSB rate is equal the target rate
306 // +1 to avoid getting a step of 0
307 const float step = (log10(fTriggerRate)-log10(fTargetRate))/0.039 + 1;
308
309 const uint16_t diff = fThresholdMin+int16_t(truncf(step));
310 if (diff<=fThresholdMin)
311 {
312 const RateControl::DimThreshold data = { fThresholdMin, fCalibrationTimeStart.Mjd(), Time().Mjd() };
313 fDimThreshold.setQuality(1);
314 fDimThreshold.Update(data);
315
316 ostringstream out;
317 out << setprecision(3);
318 out << "Next step would be 0... mininum threshold set to " << fThresholdMin;
319 Info(out);
320
321 return RateControl::State::kGlobalThresholdSet;
322 }
323
324 if (fVerbose)
325 {
326 //Out() << idx/40 << "|" << (idx/4)%10 << "|" << idx%4;
327 Out() << fThresholdMin;
328 Out() << (step>0 ? " += " : " -= ");
329 Out() << step << " (" << diff << ")" << endl;
330 }
331
332 const uint32_t val[2] = { -1, diff };
333 DimClient::sendCommandNB("FTM_CONTROL/SET_THRESHOLD", (void*)val, 8);
334
335 fThresholdMin = diff;
336
337 return GetCurrentState();
338 }
339
340 int HandleStaticData(const EventImp &evt)
341 {
342 if (!CheckEventSize(evt, sizeof(FTM::DimStaticData)))
343 return GetCurrentState();
344
345 const FTM::DimStaticData &sdata = *static_cast<const FTM::DimStaticData*>(evt.GetData());
346 fTriggerOn = sdata.HasTrigger();
347
348 PrintThresholds(sdata);
349
350 fThresholds.assign(sdata.fThreshold, sdata.fThreshold+160);
351
352 return GetCurrentState();
353 }
354
355 int HandleTriggerRates(const EventImp &evt)
356 {
357 if (fThresholds.size()==0)
358 return GetCurrentState();
359
360 if (GetCurrentState()<=RateControl::State::kConnected ||
361 GetCurrentState()==RateControl::State::kGlobalThresholdSet)
362 return GetCurrentState();
363
364 if (!CheckEventSize(evt, sizeof(FTM::DimTriggerRates)))
365 return GetCurrentState();
366
367 const FTM::DimTriggerRates &sdata = *static_cast<const FTM::DimTriggerRates*>(evt.GetData());
368
369 if (GetCurrentState()==RateControl::State::kSettingGlobalThreshold && !fCalibrateByCurrent)
370 return ProcessCamera(sdata);
371
372 if (GetCurrentState()==RateControl::State::kInProgress)
373 ProcessPatches(sdata);
374
375 return GetCurrentState();
376 }
377
378 int HandleCalibratedCurrents(const EventImp &evt)
379 {
380 // Check if received event is valid
381 if (!CheckEventSize(evt, (416+6)*4))
382 return GetCurrentState();
383
384 // Record only currents when the drive is tracking to avoid
385 // bias from the movement
386 if (fDimDrive.state()<Drive::State::kTracking)
387 return GetCurrentState();
388
389 // Get time and median current (FIXME: check N?)
390 const Time &time = evt.GetTime();
391 const float med = evt.Get<float>(416*4+4+4);
392
393 // Keep all median currents of the past 10 seconds
394 fCurrents.push_back(make_pair(time, med));
395 while (fCurrents.size()>0)
396 if (time-fCurrents.front().first>boost::posix_time::seconds(10))
397 fCurrents.pop_front();
398
399 // If we are not doing a calibration no further action necessary
400 if (!fCalibrateByCurrent)
401 return GetCurrentState();
402
403 if (GetCurrentState()!=RateControl::State::kSettingGlobalThreshold)
404 return GetCurrentState();
405
406 // We want at least 8 values for averaging
407 if (fCurrents.size()<8)
408 return GetCurrentState();
409
410 // Calculate avera and rms of median
411 double avg = 0;
412 double rms = 0;
413 for (auto it=fCurrents.begin(); it!=fCurrents.end(); it++)
414 {
415 avg += it->second;
416 rms += it->second*it->second;
417 }
418 avg /= fCurrents.size();
419 rms /= fCurrents.size();
420
421 rms = sqrt(rms-avg*avg);
422
423 fThresholdMin = 36.0833*pow(avg, 0.638393)+184.037;
424 fThresholds.assign(160, fThresholdMin);
425
426 const RateControl::DimThreshold data = { fThresholdMin, fCalibrationTimeStart.Mjd(), Time().Mjd() };
427 fDimThreshold.setQuality(0);
428 fDimThreshold.Update(data);
429
430 ostringstream out;
431 out << setprecision(3);
432 out << "Measured average current " << avg << "uA +- " << rms << "uA [N=" << fCurrents.size() << "]... mininum threshold set to " << fThresholdMin;
433 Info(out);
434
435 return RateControl::State::kGlobalThresholdSet;
436 }
437
438 int Calibrate()
439 {
440 if (!fTriggerOn)
441 {
442 Info("Physics trigger not enabled... CALIBRATE command ignored.");
443 return RateControl::State::kGlobalThresholdSet;
444 }
445
446 const int32_t val[2] = { -1, fThresholdReference };
447 Dim::SendCommand("FTM_CONTROL/SET_THRESHOLD", val);
448
449 fThresholds.assign(160, fThresholdReference);
450
451 fThresholdMin = fThresholdReference;
452 fTriggerRate = -1;
453 fCounter = 0;
454
455 fCalibrateByCurrent = false;
456 fCalibrationTimeStart = Time();
457
458 ostringstream out;
459 out << "Rate calibration started at a threshold of " << fThresholdReference << " with a target rate of " << fTargetRate << " Hz";
460 Info(out);
461
462 return RateControl::State::kSettingGlobalThreshold;
463 }
464
465 int CalibrateByCurrent()
466 {
467 if (!fTriggerOn)
468 {
469 Info("Physics trigger not enabled... CALIBRATE command ignored.");
470 return RateControl::State::kGlobalThresholdSet;
471 }
472
473 if (fDimDrive.state()<Drive::State::kMoving)
474 Warn("Drive not even moving...");
475
476 fCounter = 0;
477 fCalibrateByCurrent = true;
478 fCalibrationTimeStart = Time();
479
480 ostringstream out;
481 out << "Rate calibration by current " << fThresholdReference << " with a target rate of " << fTargetRate << " Hz";
482 Info(out);
483
484 return RateControl::State::kSettingGlobalThreshold;
485 }
486
487 int StopRC()
488 {
489 return RateControl::State::kConnected;
490 }
491
492 int SetMinThreshold(const EventImp &evt)
493 {
494 if (!CheckEventSize(evt, 4))
495 return kSM_FatalError;
496
497 // FIXME: Check missing
498
499 fThresholdReference = evt.GetUShort();
500
501 return GetCurrentState();
502 }
503
504 int SetTargetRate(const EventImp &evt)
505 {
506 if (!CheckEventSize(evt, 4))
507 return kSM_FatalError;
508
509 fTargetRate = evt.GetFloat();
510
511 return GetCurrentState();
512 }
513
514 int Print() const
515 {
516 Out() << fDim << endl;
517 Out() << fDimFTM << endl;
518 Out() << fDimRS << endl;
519 Out() << fDimDrive << endl;
520
521 return GetCurrentState();
522 }
523
524 int SetVerbosity(const EventImp &evt)
525 {
526 if (!CheckEventSize(evt, 1))
527 return kSM_FatalError;
528
529 fVerbose = evt.GetBool();
530
531 return GetCurrentState();
532 }
533
534 int Execute()
535 {
536 if (!fDim.online())
537 return RateControl::State::kDimNetworkNA;
538
539 // All subsystems are not connected
540 if (fDimFTM.state()<FTM::State::kConnected || fDimDrive.state()<Drive::State::kConnected)
541 return RateControl::State::kDisconnected;
542
543 const bool inprog = fTriggerOn && fDimRS.state()<RateScan::State::kConfiguring;
544
545 switch (GetCurrentState())
546 {
547 case RateControl::State::kSettingGlobalThreshold:
548 return RateControl::State::kSettingGlobalThreshold;
549
550 case RateControl::State::kGlobalThresholdSet:
551 if (!inprog)
552 return RateControl::State::kGlobalThresholdSet;
553
554 case RateControl::State::kInProgress:
555 if (inprog)
556 return RateControl::State::kInProgress;
557 }
558
559 return RateControl::State::kConnected;
560 }
561
562public:
563 StateMachineRateControl(ostream &out=cout) : StateMachineDim(out, "RATE_CONTROL"),
564 fTriggerOn(false), fBlock(40),
565 fDimFTM("FTM_CONTROL"),
566 fDimRS("RATE_SCAN"),
567 fDimDrive("DRIVE_CONTROL"),
568
569 fDimThreshold("RATE_CONTROL/THRESHOLD", "S:1;D:1;D:1",
570 "Resulting threshold after calibration"
571 "|threshold[dac]:Resulting threshold from calibration"
572 "|begin[mjd]:Start time of calibration"
573 "|end[mjd]:End time of calibration")
574 {
575 // ba::io_service::work is a kind of keep_alive for the loop.
576 // It prevents the io_service to go to stopped state, which
577 // would prevent any consecutive calls to run()
578 // or poll() to do nothing. reset() could also revoke to the
579 // previous state but this might introduce some overhead of
580 // deletion and creation of threads and more.
581
582 fDim.Subscribe(*this);
583 fDimFTM.Subscribe(*this);
584 fDimRS.Subscribe(*this);
585
586 Subscribe("FTM_CONTROL/TRIGGER_RATES")
587 (bind(&StateMachineRateControl::HandleTriggerRates, this, placeholders::_1));
588 Subscribe("FTM_CONTROL/STATIC_DATA")
589 (bind(&StateMachineRateControl::HandleStaticData, this, placeholders::_1));
590 Subscribe("FEEDBACK/CALIBRATED_CURRENTS")
591 (bind(&StateMachineRateControl::HandleCalibratedCurrents, this, placeholders::_1));
592
593 // State names
594 AddStateName(RateControl::State::kDimNetworkNA, "DimNetworkNotAvailable",
595 "The Dim DNS is not reachable.");
596
597 AddStateName(RateControl::State::kDisconnected, "Disconnected",
598 "The Dim DNS is reachable, but the required subsystems are not available.");
599
600 AddStateName(RateControl::State::kConnected, "Connected",
601 "All needed subsystems are connected to their hardware, no action is performed.");
602
603 AddStateName(RateControl::State::kSettingGlobalThreshold, "Calibrating",
604 "A global minimum thrshold is currently determined.");
605
606 AddStateName(RateControl::State::kGlobalThresholdSet, "GlobalThresholdSet",
607 "A global threshold has ben set, waiting for the trigger to be switched on.");
608
609 AddStateName(RateControl::State::kInProgress, "InProgress",
610 "Rate control in progress.");
611
612 AddEvent("CALIBRATE")
613 (bind(&StateMachineRateControl::Calibrate, this))
614 ("Start a search for a reasonable minimum global threshold");
615
616 AddEvent("CALIBRATE_BY_CURRENT")
617 (bind(&StateMachineRateControl::CalibrateByCurrent, this))
618 ("Set the global threshold from the median current");
619
620 AddEvent("STOP", RateControl::State::kSettingGlobalThreshold, RateControl::State::kGlobalThresholdSet, RateControl::State::kInProgress)
621 (bind(&StateMachineRateControl::StopRC, this))
622 ("Stop a calibration or ratescan in progress");
623
624 AddEvent("SET_MIN_THRESHOLD", "I:1")
625 (bind(&StateMachineRateControl::SetMinThreshold, this, placeholders::_1))
626 ("Set a minimum threshold at which th rate control starts calibrating");
627
628 AddEvent("SET_TARGET_RATE", "F:1")
629 (bind(&StateMachineRateControl::SetTargetRate, this, placeholders::_1))
630 ("Set a target trigger rate for the calibration");
631
632 AddEvent("PRINT")
633 (bind(&StateMachineRateControl::Print, this))
634 ("Print current status");
635
636 AddEvent("SET_VERBOSE", "B")
637 (bind(&StateMachineRateControl::SetVerbosity, this, placeholders::_1))
638 ("set verbosity state"
639 "|verbosity[bool]:disable or enable verbosity for received data (yes/no), except dynamic data");
640
641 }
642
643 int EvalOptions(Configuration &conf)
644 {
645 fVerbose = !conf.Get<bool>("quiet");
646
647 fThresholdReference = 300;
648 fThresholdMin = 300;
649 fTargetRate = 75;
650
651 return -1;
652 }
653};
654
655// ------------------------------------------------------------------------
656
657#include "Main.h"
658
659template<class T>
660int RunShell(Configuration &conf)
661{
662 return Main::execute<T, StateMachineRateControl>(conf);
663}
664
665void SetupConfiguration(Configuration &conf)
666{
667 po::options_description control("Rate control options");
668 control.add_options()
669 ("quiet,q", po_bool(), "Disable printing more informations during rate control.")
670 //("max-wait", var<uint16_t>(150), "The maximum number of seconds to wait to get the anticipated resolution for a point.")
671 // ("resolution", var<double>(0.05) , "The minimum resolution required for a single data point.")
672 ;
673
674 conf.AddOptions(control);
675}
676
677/*
678 Extract usage clause(s) [if any] for SYNOPSIS.
679 Translators: "Usage" and "or" here are patterns (regular expressions) which
680 are used to match the usage synopsis in program output. An example from cp
681 (GNU coreutils) which contains both strings:
682 Usage: cp [OPTION]... [-T] SOURCE DEST
683 or: cp [OPTION]... SOURCE... DIRECTORY
684 or: cp [OPTION]... -t DIRECTORY SOURCE...
685 */
686void PrintUsage()
687{
688 cout <<
689 "The ratecontrol program is a keep the rate reasonable low.\n"
690 "\n"
691 "Usage: ratecontrol [-c type] [OPTIONS]\n"
692 " or: ratecontrol [OPTIONS]\n";
693 cout << endl;
694}
695
696void PrintHelp()
697{
698 Main::PrintHelp<StateMachineRateControl>();
699
700 /* Additional help text which is printed after the configuration
701 options goes here */
702
703 /*
704 cout << "bla bla bla" << endl << endl;
705 cout << endl;
706 cout << "Environment:" << endl;
707 cout << "environment" << endl;
708 cout << endl;
709 cout << "Examples:" << endl;
710 cout << "test exam" << endl;
711 cout << endl;
712 cout << "Files:" << endl;
713 cout << "files" << endl;
714 cout << endl;
715 */
716}
717
718int main(int argc, const char* argv[])
719{
720 Configuration conf(argv[0]);
721 conf.SetPrintUsage(PrintUsage);
722 Main::SetupConfiguration(conf);
723 SetupConfiguration(conf);
724
725 if (!conf.DoParse(argc, argv, PrintHelp))
726 return 127;
727
728 if (!conf.Has("console"))
729 return RunShell<LocalStream>(conf);
730
731 if (conf.Get<int>("console")==0)
732 return RunShell<LocalShell>(conf);
733 else
734 return RunShell<LocalConsole>(conf);
735
736 return 0;
737}
Note: See TracBrowser for help on using the repository browser.