MultiDuctor.cxx
Go to the documentation of this file.
2 
4 #include "WireCellUtil/Pimpos.h"
5 #include "WireCellUtil/Binning.h"
7 
8 // fixme: this needs to move out of iface!
12 
13 #include <vector>
14 
17 
18 using namespace WireCell;
19 
20 Gen::MultiDuctor::MultiDuctor(const std::string anode)
21  : m_anode_tn(anode)
22  , m_tick(0.5*units::us)
23  , m_start_time(0.0*units::ns)
24  , m_readout_time(5.0*units::ms)
25  , m_frame_count(0)
26  , m_continuous(false)
27  , m_eos(true)
28 {
29 }
30 Gen::MultiDuctor::~MultiDuctor()
31 {
32 }
33 
34 WireCell::Configuration Gen::MultiDuctor::default_configuration() const
35 {
36  // The "chain" is a list of dictionaries. Each provides:
37  // - ductor: TYPE[:NAME] of a ductor component
38  // - rule: name of a rule function to apply
39  // - args: args for the rule function
40  //
41  // Rule functions:
42  // wirebounds (list of wire ranges)
43  // bool (true/false)
44 
46  cfg["anode"] = m_anode_tn;
47  cfg["chain"] = Json::arrayValue;
48 
49  // must be consistent with subductors
50  cfg["tick"] = m_tick;
51 
52  /// The initial time for this ductor
53  cfg["start_time"] = m_start_time;
54 
55  /// The time span for each readout.
56  cfg["readout_time"] = m_readout_time;
57 
58  /// If false then determine start time of each readout based on the
59  /// input depos. This option is useful when running WCT sim on a
60  /// source of depos which have already been "chunked" in time. If
61  /// true then this Ductor will continuously simulate all time in
62  /// "readout_time" frames leading to empty frames in the case of
63  /// some readout time with no depos.
64  cfg["continuous"] = m_continuous;
65 
66  /// Allow for a custom starting frame number
67  cfg["first_frame_number"] = m_frame_count;
68 
69  return cfg;
70 }
71 
72 struct Wirebounds {
73  std::vector<const Pimpos*> pimpos;
75  Wirebounds(const std::vector<const Pimpos*>& p, Json::Value jargs) : pimpos(p), jargs(jargs) { }
76 
78 
79  if (!depo) {
80  std::cerr << "Gen::MultiDuctor::Wirebounds: error: no depo given\n";
81  return false;
82  }
83 
84  // fixme: it's possibly really slow to do all this Json::Value
85  // access deep inside this loop. If so, the Json::Values
86  // should be run through once in configure() and stored into
87  // some faster data structure.
88 
89  // return true if depo is "in" ANY region.
90  for (auto jregion : jargs) {
91  bool inregion = true;
92 
93  // depo must be in ALL ranges of a region
94  for (auto jrange: jregion) {
95  int iplane = jrange["plane"].asInt();
96  int imin = jrange["min"].asInt();
97  int imax = jrange["max"].asInt();
98 
99  //double drift = pimpos[iplane]->distance(depo->pos(), 0);
100  double pitch = pimpos[iplane]->distance(depo->pos(), 2);
101  int iwire = pimpos[iplane]->region_binning().bin(pitch); // fixme: warning: round off error?
102  inregion = inregion && (imin <= iwire && iwire <= imax);
103  if (!inregion) {
104  // std::cerr << "Wirebounds: wire: "<<iwire<<" of plane " << iplane << " not in [" << imin << "," << imax << "]\n";
105  break; // not in this view's region, no reason to keep checking.
106  }
107  }
108  if (inregion) { // only need one region
109  return true;
110  }
111  }
112  return false;
113  }
114 };
115 
116 struct ReturnBool {
117  bool ok;
118  ReturnBool(Json::Value jargs) : ok(jargs.asBool()) {}
120  if (!depo) {return false;}
121  return ok;
122  }
123 };
124 
125 
127 {
128  m_readout_time = get<double>(cfg, "readout_time", m_readout_time);
129  m_tick = get<double>(cfg, "tick", m_tick);
130  m_start_time = get<double>(cfg, "start_time", m_start_time);
131  m_frame_count = get<int>(cfg, "first_frame_number", m_frame_count);
132  m_continuous = get(cfg, "continuous", m_continuous);
133 
134  m_anode_tn = get(cfg, "anode", m_anode_tn);
135  m_anode = Factory::find_tn<IAnodePlane>(m_anode_tn);
136  if (!m_anode) {
137  std::cerr << "Gen::MultiDuctor::configure: error: unknown anode: " << m_anode_tn << std::endl;
138  return;
139  }
140  auto jchains = cfg["chains"];
141  if (jchains.isNull()) {
142  std::cerr << "Gen::MultiDuctor::configure: warning: configured with empty collection of chains\n";
143  return;
144  }
145 
146  /// fixme: this is totally going to break when going to two-faced anodes.
147  if ( m_anode->faces().size() > 1 ) {
148  std::cerr << "Gen::MultDuctor:configure: warning: I currently only support a front-faced AnodePlane.\n";
149  }
150 
151  std::vector<const Pimpos*> pimpos;
152  for (auto face : m_anode->faces()) {
153  if (face->planes().empty()) {
154  std::cerr << "Gen::MultDuctor: not given multi-plane AnodeFace for face "<<face->ident()<<"\n";
155  continue;
156  }
157  for (auto plane : face->planes()) {
158  pimpos.push_back(plane->pimpos());
159  }
160  break; // fixme:
161  }
162  if (pimpos.size() != 3) {
163  std::cerr << "Gen::MultiDuctor got unexpected number planes (" << pimpos.size() <<") from anode\n";
164  THROW(ValueError() << errmsg{"Gen::MultiDuctor got unexpected number planes"});
165  }
166 
167  for (auto jchain : jchains) {
168  std::cerr << "Gen::MultiDuctor::configure chain:\n";
169  ductorchain_t dchain;
170 
171  for (auto jrule : jchain) {
172  auto rule = jrule["rule"].asString();
173  auto ductor_tn = jrule["ductor"].asString();
174  std::cerr << "\tMultiDuctor: " << ductor_tn << " rule: " << rule << std::endl;
175  auto ductor = Factory::find_tn<IDuctor>(ductor_tn);
176  if (!ductor) {
177  THROW(KeyError() << errmsg{"Failed to find (sub) Ductor: " + ductor_tn});
178  }
179  auto jargs = jrule["args"];
180  if (rule == "wirebounds") {
181  dchain.push_back(SubDuctor(ductor_tn, Wirebounds(pimpos, jargs), ductor));
182  }
183  if (rule == "bool") {
184  dchain.push_back(SubDuctor(ductor_tn, ReturnBool(jargs), ductor));
185  }
186  }
187  m_chains.push_back(dchain);
188  } // loop to store chains of ductors
189 }
190 
191 // void Gen::MultiDuctor::reset()
192 // {
193 // // forward message
194 // for (auto& chain : m_chains) {
195 // for (auto& sd : chain) {
196 // sd.ductor->reset();
197 // }
198 // }
199 // }
200 
201 
202 // Return true if ready to start processing and capture start time if
203 // in continuous mode.
204 bool Gen::MultiDuctor::start_processing(const input_pointer& depo)
205 {
206  if (!depo) {
207  m_eos = true;
208  return true;
209  }
210  if (!m_continuous) {
211  if (depo && m_eos) {
212  m_eos = false;
213  m_start_time = depo->time();
214  return false;
215  }
216  }
217  return depo->time() > m_start_time + m_readout_time;
218 }
219 
220 
222 {
223  auto traces = frame->traces();
224  if (!traces or traces->empty()) {
225  std::cerr << msg
226  << " fid:" << frame->ident()
227  << " has no traces\n";
228  return;
229  }
230 
231  auto mm = FrameTools::tbin_range(*traces);
232  std::cerr << msg
233  << " fid:" << frame->ident()
234  << " #ch:" << traces->size()
235  << " t:" << std::setprecision(12) << m_start_time/units::us<<"us "
236  << " tbins:["<<mm.first<<","<<mm.second<<"]\n";
237 }
238 
239 // Outframes should not get a terminating nullptr even if depo is nullptr
240 void Gen::MultiDuctor::maybe_extract(const input_pointer& depo, output_queue& outframes)
241 {
242  if (!start_processing(depo)) { // may set m_start_time
243  return;
244  }
245 
246  // Must flush all sub-ductors to assure synchronization.
247  for (auto& chain : m_chains) {
248  for (auto& sd : chain) {
249  output_queue newframes;
250  (*sd.ductor)(nullptr, newframes); // flush with EOS marker
251  merge(newframes); // updates frame buffer
252  }
253  }
254 
255  // we must read out, and yet we have nothing
256  //
257  // fixme: the default behavior of this is to make sequential
258  // blocks of m_readout_time even if there be no data. Need to
259  // also handle a more isolated running where the readout window is
260  // data driven up until EOS is found.
261  if (!m_frame_buffer.size()) {
262 
263  std::cerr << "MultiDuctor: returning empty frame with "<<m_frame_count
264  << " at " << m_start_time << "\n";
265 
266  ITrace::vector traces;
267  auto frame = std::make_shared<SimpleFrame>(m_frame_count, m_start_time, traces, m_tick);
268  outframes.push_back(frame);
269 
270  m_start_time += m_readout_time;
271  ++m_frame_count;
272  return;
273  }
274 
275 
276  // At this point the m_frame_buffer has at least some frames from
277  // multiple sub-ductors, each of different time and channel extent
278  // and potentially out of order.
279 
280 
281  const double target_time = m_start_time + m_readout_time;
282  output_queue to_keep, to_extract;
283  for (auto frame: m_frame_buffer) {
284  if (!frame) {
285  std::cerr << "Gen::MultiDuctor: skipping null frame in frame buffer\n";
286  continue;
287  }
288  if (!frame->traces()) {
289  std::cerr << "Gen::MultiDuctor: skipping empty frame in frame buffer\n";
290  continue;
291  }
292 
293  {
294  const double tick = frame->tick();
295  if (std::abs(tick - m_tick) > 0.0001) {
296  std::cerr << "MultiDuctor: configuration error: got different tick in frame from sub-ductor = "
297  << tick/units::us << "us, mine = " << m_tick/units::us << "us\n";
298  THROW(ValueError() << errmsg{"tick size mismatch"});
299  }
300  }
301 
302  int cmp = FrameTools::frmtcmp(frame, target_time);
303  // std::cerr << "Gen::MultiDuctor: checking to keep: "
304  // << std::setprecision(12)
305  // << "t_target=" << target_time/units::us << "us, "
306  // << "cmp returns " << cmp << ", frame is:\n";
307  // dump_frame(frame, "\t");
308 
309  if (cmp < 0) {
310  to_extract.push_back(frame);
311  continue;
312  }
313  if (cmp > 0) {
314  to_keep.push_back(frame);
315  continue;
316  }
317 
318  // If cmp==0 above then both halves of the pair should hold a frame.
319  auto ff = FrameTools::split(frame, target_time);
320  if (ff.first) {
321  to_extract.push_back(ff.first);
322  //dump_frame(ff.first, "Gen::MultiDuctor: to extract");
323  }
324  else {
325  std::cerr << "Gen::MultiDuctor: error: early split is null\n";
326  }
327 
328  if (ff.second) {
329  to_keep.push_back(ff.second);
330  //dump_frame(ff.second, "Gen::MultiDuctor: to keep");
331  }
332  else {
333  std::cerr << "Gen::MultiDuctor: error: late split is null\n";
334  }
335 
336  }
337  m_frame_buffer = to_keep;
338 
339  if (!to_extract.size()) {
340  // we read out, and yet we have nothing
341 
342  std::cerr << "MultiDuctor: returning empty frame after sub frame sorting with "
343  << m_frame_count<<" at " << m_start_time << "\n";
344 
345  ITrace::vector traces;
346  auto frame = std::make_shared<SimpleFrame>(m_frame_count, m_start_time, traces, m_tick);
347  outframes.push_back(frame);
348  m_start_time += m_readout_time;
349  ++m_frame_count;
350  return;
351  }
352 
353 
354  /// Big fat lazy programmer FIXME: there is a bug waiting to bite
355  /// here. If somehow a sub-ductor manages to make a frame that
356  /// remains bigger than m_readout_time after the split() above, it
357  /// will cause the final output frame to extend past requested
358  /// duration. There needs to be a loop over to_extract added.
359 
360  /// Big fat lazy programmer FIXME v2: there can be multiple traces
361  /// on the same channel which may overlap in time.
362 
363  ITrace::vector traces;
364  for (auto frame: to_extract) {
365  const double tref = frame->time();
366  const int extra_tbins = (tref-m_start_time)/m_tick;
367  for (auto trace : (*frame->traces())) {
368  const int tbin = trace->tbin() + extra_tbins;
369  const int chid = trace->channel();
370  // std::cerr <<traces.size()
371  // <<" tstart="<<m_start_time/units::us<<"us"
372  // <<" tref="<<tref/units::us << "us"
373  // <<" tbin="<<tbin
374  // <<" extra bins="<< extra_tbins<<" chid="<<chid<<"\n";
375  auto mtrace = std::make_shared<SimpleTrace>(chid, tbin, trace->charge());
376  traces.push_back(mtrace);
377  }
378  }
379  auto frame = std::make_shared<SimpleFrame>(m_frame_count, m_start_time, traces, m_tick);
380  dump_frame(frame, "Gen::MultiDuctor: output frame");
381 
382  outframes.push_back(frame);
383  m_start_time += m_readout_time;
384  ++m_frame_count;
385 }
386 
387 void Gen::MultiDuctor::merge(const output_queue& newframes)
388 {
389  for (auto frame : newframes) {
390  if (!frame) { continue; } // skip internal EOS
391  auto traces = frame->traces();
392  if (!traces) { continue; }
393  if (traces->empty()) { continue; }
394 
395  //dump_frame(frame, "Gen::MultiDuctor: merging frame");
396  m_frame_buffer.push_back(frame);
397  }
398 }
399 
400 bool Gen::MultiDuctor::operator()(const input_pointer& depo, output_queue& outframes)
401 {
402  // end of stream processing
403  if (!depo) {
404  //std::cerr << "Gen::MultiDuctor: end of stream processing\n";
405  maybe_extract(depo, outframes);
406  outframes.push_back(nullptr); // pass on EOS marker
407  if (!m_frame_buffer.empty()) {
408  std::cerr << "Gen::MultiDuctor: purging " << m_frame_buffer.size() << " frames at EOS\n";
409  for (auto frame : m_frame_buffer) {
410  dump_frame(frame, "\t");
411  }
412  m_frame_buffer.clear();
413  }
414  return true;
415  }
416 
417 
418  // check each rule in the chain to find match
419  bool all_okay = true;
420  int count = 0;
421  for (auto& chain : m_chains) {
422 
423  for (auto& sd : chain) {
424 
425  if (!sd.check(depo)) {
426  continue;
427  }
428 
429  // found a matching sub-ductor
430  ++count;
431 
432  // Normal buffered processing.
433  output_queue newframes;
434  bool ok = (*sd.ductor)(depo, newframes);
435  merge(newframes);
436  all_okay = all_okay && ok;
437 
438  // got a match so abandon chain
439  break;
440  }
441  }
442  if (count == 0) {
443  std::cerr << "Gen::MultiDuctor: warning: no appropriate Ductor for depo at: "
444  << depo->pos() << std::endl;
445  }
446 
447  maybe_extract(depo, outframes);
448  if (outframes.size()) {
449  std::cerr << "Gen::MultiDuctor: returning " << outframes.size() << " frames\n";
450  }
451  return true;
452 }
453 
454 
std::shared_ptr< const IFrame > pointer
Definition: IData.h:19
Wirebounds(const std::vector< const Pimpos * > &p, Json::Value jargs)
Definition: MultiDuctor.cxx:75
void msg(const char *fmt,...)
Definition: message.cpp:107
std::string string
Definition: nybbler.cc:12
ReturnBool(Json::Value jargs)
WIRECELL_FACTORY(MultiDuctor, WireCell::Gen::MultiDuctor, WireCell::IDuctor, WireCell::IConfigurable) using namespace WireCell
boost::error_info< struct tag_errmsg, std::string > errmsg
Definition: Exceptions.h:54
std::vector< const Pimpos * > pimpos
Definition: MultiDuctor.cxx:73
STL namespace.
void dump_frame(WireCell::IFrame::pointer frame)
Definition: FrameUtils.cxx:10
cfg
Definition: dbjson.py:29
std::vector< pointer > vector
Definition: IData.h:21
bool operator()(IDepo::pointer depo)
const double tick
std::vector< SubDuctor > ductorchain_t
Definition: MultiDuctor.h:57
Q_EXPORT QTSManip setprecision(int p)
Definition: qtextstream.h:343
std::deque< output_pointer > output_queue
BinRangeList merge(const BinRangeList &br)
Return a new list with any overlaps formed into unions.
Definition: Waveform.cxx:222
T abs(T value)
def configure(cfg)
Definition: cuda.py:34
GenericValue< UTF8<> > Value
GenericValue with UTF8 encoding.
Definition: document.h:2106
#define THROW(e)
Definition: Exceptions.h:25
int frmtcmp(IFrame::pointer frame, double time)
Definition: FrameTools.cxx:14
std::pair< int, int > tbin_range(const ITrace::vector &traces)
Definition: FrameTools.cxx:143
Thrown when a wrong value has been encountered.
Definition: Exceptions.h:37
static const double mm
Definition: Units.h:73
Definition: Main.h:22
p
Definition: test.py:223
static const double us
Definition: Units.h:101
bool operator()(IDepo::pointer depo)
Definition: MultiDuctor.cxx:77
static const double ms
Definition: Units.h:100
Json::Value jargs
Definition: MultiDuctor.cxx:74
Json::Value Configuration
Definition: Configuration.h:50
std::shared_ptr< const IDepo > input_pointer
void split(std::string const &s, char c, OutIter dest)
Definition: split.h:35
const GenericPointer< typename T::ValueType > & pointer
Definition: pointer.h:1124
QAsciiDict< Entry > ns
Thrown when a wrong key or has been encountered.
Definition: Exceptions.h:43
QTextStream & endl(QTextStream &s)