Skip to content

Commit df98aa9

Browse files
authored
feat: QADB binning support (#770)
* feat: downsampling * feat: test wrapper * feat: write charge comparison table * feat: initial correction implementation * fix: need protected default constructor for inheritance * refactor: separate `readSequence` event loop to a protected method so that a derived class can use it * fix: cleanup, since downsampling moved downstream * feat: store event number * feat: QADB binning classes * feat: some example code * feat: general `coatjava` script * fix: start convincing this stuff to work * fix: fencepost error, and missing event number * feat: handle charge * feat: get charge extrema * feat: support RG-D charge override * feat: sugar * refactor: organize methods * fix: spelling * fix: +1 * fix: if on boundary take earlier bin to be consistent with the current QADB convention * fix: remove bin/coatjava * fix: cleanup diff * doc: cleanup * feat: validate scaler ordering * fix: docstrings * refactor: DeMorgan law * feat: instantiation without `data` creation * refactor: cleanup
1 parent 838c318 commit df98aa9

File tree

4 files changed

+627
-10
lines changed

4 files changed

+627
-10
lines changed
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
package org.jlab.detector.qadb;
2+
3+
import java.util.List;
4+
import org.jlab.detector.scalers.DaqScalers;
5+
import org.jlab.detector.scalers.DaqScalersSequence;
6+
7+
/**
8+
* A single bin for the Quality Assurance Database (QADB).
9+
* It may hold arbitrary data, such as a class instance, accessible by public member {@link QadbBin#data};
10+
* its type is set by a generic type parameter.
11+
* <p>
12+
* A bin contains a (sub)sequence of scaler readouts, and therefore extends {@link DaqScalersSequence}.
13+
* @see QadbBinSequence
14+
* @author dilks
15+
*/
16+
public class QadbBin<T> extends DaqScalersSequence {
17+
18+
private int binNum;
19+
private BinType binType;
20+
private int evnumMin;
21+
private int evnumMax;
22+
private long timestampMin;
23+
private long timestampMax;
24+
private double charge; // ungated
25+
private double chargeGated;
26+
27+
/** arbitrary data that may be held by this bin; it is just public so the user can do anything with it */
28+
public T data;
29+
30+
// ----------------------------------------------------------------------------------
31+
32+
/** lambda type to print each bin's generic data as a string */
33+
public interface DataPrinter<T> {
34+
/**
35+
* @param data the public member {@link QadbBin#data}
36+
* @return a String representation of {@link QadbBin#data}
37+
*/
38+
String run(T data);
39+
}
40+
41+
/** bin type */
42+
public enum BinType {
43+
/** the first bin, for events before the first scaler readout */
44+
FIRST,
45+
/** any bin between two scaler readouts */
46+
INTERMEDIATE,
47+
/** the last bin, for events after the last scaler readout */
48+
LAST,
49+
}
50+
51+
/** charge type */
52+
public enum ChargeType {
53+
/** full charge, DAQ-ungated */
54+
UNGATED,
55+
/** DAQ-gated charge */
56+
GATED,
57+
}
58+
59+
// ----------------------------------------------------------------------------------
60+
61+
/**
62+
* construct a single bin
63+
* @param binNum the bin number, in the {@link QadbBinSequence} which contains this bin
64+
* @param binType the bin type (see {@link BinType})
65+
* @param inputScalers the scaler sequence for this bin
66+
* @param initData the initial data for this bin (sets public member {@link data})
67+
*/
68+
public QadbBin(int binNum, BinType binType, List<DaqScalers> inputScalers, T initData) {
69+
super(inputScalers);
70+
this.binNum = binNum;
71+
this.binType = binType;
72+
this.data = initData;
73+
switch(this.binType) {
74+
case INTERMEDIATE -> {
75+
this.timestampMin = this.scalers.get(0).getTimestamp();
76+
this.timestampMax = this.scalers.get(scalers.size()-1).getTimestamp();
77+
this.evnumMin = this.scalers.get(0).getEventNum();
78+
this.evnumMax = this.scalers.get(scalers.size()-1).getEventNum();
79+
this.charge = this.getInterval().getBeamCharge();
80+
this.chargeGated = this.getInterval().getBeamChargeGated();
81+
}
82+
case FIRST -> {
83+
if(this.scalers.size() != 1)
84+
throw new RuntimeException("a FIRST bin may only have ONE scaler readout");
85+
this.timestampMin = 0; // user may correct this using `correctLowerBound`
86+
this.timestampMax = this.scalers.get(0).getTimestamp();
87+
this.evnumMin = 0; // user may correct this using `correctLowerBound`
88+
this.evnumMax = this.scalers.get(0).getEventNum();
89+
this.charge = 0; // since no lower bound
90+
this.chargeGated = 0; // since no lower bound
91+
}
92+
case LAST -> {
93+
if(this.scalers.size() != 1)
94+
throw new RuntimeException("a LAST bin may only have ONE scaler readout");
95+
this.timestampMin = this.scalers.get(0).getTimestamp();
96+
this.timestampMax = 10 * this.timestampMin; // user may correct this using `correctUpperBound`
97+
this.evnumMin = this.scalers.get(0).getEventNum();
98+
this.evnumMax = 10 * this.evnumMin; // user may correct this using `correctUpperBound`
99+
this.charge = 0; // since no upper bound
100+
this.chargeGated = 0; // since no upper bound
101+
}
102+
}
103+
}
104+
105+
// ----------------------------------------------------------------------------------
106+
107+
/** @return the bin number for this bin */
108+
public int getBinNum() { return this.binNum; }
109+
110+
/** @return minimum timestamp for this bin */
111+
public long getTimestampMin() { return this.timestampMin; }
112+
113+
/** @return maximum timestamp for this bin */
114+
public long getTimestampMax() { return this.timestampMax; }
115+
116+
/** @return minimum event number for this bin */
117+
public long getEventNumMin() { return this.evnumMin; }
118+
119+
/** @return maximum event number for this bin */
120+
public long getEventNumMax() { return this.evnumMax; }
121+
122+
/** @return the beam charge, not gated by DAQ, for this bin */
123+
public double getBeamCharge() { return this.charge; }
124+
125+
/** @return the beam charge, gated by DAQ, for this bin */
126+
public double getBeamChargeGated() { return this.chargeGated; }
127+
128+
/**
129+
* @return the beam charge, gated or ungated
130+
* @param chargeType the type of charge
131+
*/
132+
public double getBeamCharge(ChargeType chargeType) {
133+
return switch(chargeType) {
134+
case UNGATED -> this.getBeamCharge();
135+
case GATED -> this.getBeamChargeGated();
136+
};
137+
}
138+
139+
// ----------------------------------------------------------------------------------
140+
141+
/** @return the mean livetime for this bin */
142+
public double getMeanLivetime() {
143+
double sumLivetime = 0;
144+
int numLivetimeEvents = 0;
145+
for(int i=1; i<scalers.size(); i++) { // skip the first scaler, since that's for the previous bin
146+
var livetime = scalers.get(i).dsc2.getLivetime();
147+
if(livetime >= 0) { // filter out livetime = -1
148+
sumLivetime += livetime;
149+
numLivetimeEvents++;
150+
}
151+
}
152+
return numLivetimeEvents > 0 ? sumLivetime / numLivetimeEvents : 0;
153+
}
154+
155+
/** @return the duration of the bin, in seconds */
156+
public double getDuration() {
157+
return (this.getTimestampMax() - this.getTimestampMin()) * 4e-9; // convert timestamp units [4ns] -> [s]
158+
}
159+
160+
// ----------------------------------------------------------------------------------
161+
162+
/** charge correction method */
163+
public enum ChargeCorrectionMethod {
164+
/** interchange the DAQ-gated and ungated charges */
165+
BY_FLIP,
166+
/** calculate the DAQ-gated charge as the mean livetime multiplied by the ungated charge */
167+
BY_MEAN_LIVETIME,
168+
}
169+
170+
/**
171+
* correct the beam charge for this bin, using a correction method
172+
* @param method the correction method to use
173+
* @see ChargeCorrectionMethod
174+
*/
175+
public void correctCharge(ChargeCorrectionMethod method) {
176+
logger.fine("correcting beam charge for bin " + this.binNum + " using method " + method);
177+
logger.fine(" before: gated = " + this.chargeGated);
178+
logger.fine(" ungated = " + this.charge);
179+
switch(method) {
180+
case BY_FLIP -> { // interchange the gated and ungated charge
181+
var tmp = this.charge;
182+
this.charge = this.chargeGated;
183+
this.chargeGated = tmp;
184+
}
185+
case BY_MEAN_LIVETIME -> { // gated = <livetime> * ungated
186+
var meanLivetime = this.getMeanLivetime();
187+
logger.fine(" mean livetime = " + meanLivetime);
188+
this.chargeGated = meanLivetime * this.charge;
189+
}
190+
}
191+
logger.fine(" after: gated = " + this.chargeGated);
192+
logger.fine(" ungated = " + this.charge);
193+
}
194+
195+
/**
196+
* correct the beam charge for this bin, using specific values from the caller
197+
* @param charge the charge, not gated by the DAQ
198+
* @param chargeGated the DAQ-gated charge
199+
*/
200+
public void correctCharge(double charge, double chargeGated) {
201+
logger.fine("correcting beam charge for bin " + this.binNum + " using user-specified values");
202+
logger.fine(" before: gated = " + this.chargeGated);
203+
logger.fine(" ungated = " + this.charge);
204+
this.charge = charge;
205+
this.chargeGated = chargeGated;
206+
logger.fine(" after: gated = " + this.chargeGated);
207+
logger.fine(" ungated = " + this.charge);
208+
}
209+
210+
// ----------------------------------------------------------------------------------
211+
212+
/**
213+
* correct the first bin's lower bound, if you know it from tag-0 events
214+
* @param evnumMin the correct minimum event number
215+
* @param timestampMin the correct minimum timestamp
216+
*/
217+
public void correctLowerBound(int evnumMin, long timestampMin) {
218+
if(this.binType == BinType.FIRST) {
219+
this.evnumMin = evnumMin;
220+
this.timestampMin = timestampMin;
221+
}
222+
else logger.warning("not allowed to correct the lower bound of a bin with type " + this.binType);
223+
}
224+
225+
/**
226+
* correct the last bin's upper bound, if you know it from tag-0 events
227+
* @param evnumMax the correct maximum event number
228+
* @param timestampMax the correct maximum timestamp
229+
*/
230+
public void correctUpperBound(int evnumMax, long timestampMax) {
231+
if(this.binType == BinType.LAST) {
232+
this.evnumMax = evnumMax;
233+
this.timestampMax = timestampMax;
234+
}
235+
else logger.warning("not allowed to correct the upper bound of a bin with type " + this.binType);
236+
}
237+
238+
// ----------------------------------------------------------------------------------
239+
240+
/** extremum type, used with {@link QadbBin#getChargeExtremum} */
241+
public enum ExtremumType {
242+
/** from the first scaler readout */
243+
FIRST,
244+
/** from the last scaler readout */
245+
LAST,
246+
/** the maximum */
247+
MAX,
248+
/** the minimum */
249+
MIN,
250+
}
251+
252+
/**
253+
* Get the min/max or initial/final charge.
254+
* <p>
255+
* WARNING: this is likely NOT corrected by {@link correctCharge}
256+
* @param extremumType the type of extremum
257+
* @param chargeType the type of charge
258+
* @return the charge for the given extremum
259+
*/
260+
public double getChargeExtremum(ExtremumType extremumType, ChargeType chargeType) {
261+
return switch(extremumType) {
262+
case FIRST -> getDsc2Charge(this.scalers.get(0), chargeType);
263+
case LAST -> getDsc2Charge(this.scalers.get(this.scalers.size()-1), chargeType);
264+
case MIN -> getDsc2Charge(this.scalers.stream().reduce((a,b) -> getDsc2Charge(a, chargeType) < getDsc2Charge(b, chargeType) ? a : b).get(), chargeType);
265+
case MAX -> getDsc2Charge(this.scalers.stream().reduce((a,b) -> getDsc2Charge(a, chargeType) > getDsc2Charge(b, chargeType) ? a : b).get(), chargeType);
266+
};
267+
}
268+
269+
/** helper method for {@link getChargeExtremum}
270+
* @param ds the scaler readout
271+
* @param chargeType the charge type
272+
* @return the charge from this scaler object
273+
*/
274+
private static double getDsc2Charge(DaqScalers ds, ChargeType chargeType) {
275+
return switch(chargeType) {
276+
case UNGATED -> ds.dsc2.getBeamCharge();
277+
case GATED -> ds.dsc2.getBeamChargeGated();
278+
};
279+
}
280+
281+
// ----------------------------------------------------------------------------------
282+
283+
/** print a QA bin, and some basic information */
284+
public void print() {
285+
System.out.printf("BIN %d", this.getBinNum());
286+
System.out.printf(" -----------\n");
287+
System.out.printf("%30s %d to %d\n", "timestamp interval:", this.getTimestampMin(), this.getTimestampMax());
288+
System.out.printf("%30s %d to %d\n", "event number interval:", this.getEventNumMin(), this.getEventNumMax());
289+
System.out.printf("%30s %f s\n", "duration:", this.getDuration());
290+
System.out.printf("%30s %d events\n", "event number range:", this.getEventNumMax() - this.getEventNumMin());
291+
System.out.printf("%30s %f / %f\n", "beam charge gated / ungated:", this.getBeamChargeGated(), this.getBeamCharge());
292+
}
293+
294+
/**
295+
* print a QA bin's stored {@link data}
296+
* @param dataPrinter a lambda which resolves {@link data} as a {@code String}
297+
*/
298+
public void print(DataPrinter<T> dataPrinter) {
299+
System.out.printf("BIN %d :: ", this.getBinNum());
300+
System.out.println(dataPrinter.run(this.data));
301+
}
302+
303+
/**
304+
* print a QA bin's stored {@link data}, and optionally the bin's basic information
305+
* @param dataPrinter a lambda which resolves {@link data} as a {@code String}
306+
* @param verbose if {@code true}, print more
307+
*/
308+
public void print(DataPrinter<T> dataPrinter, boolean verbose) {
309+
if(verbose) {
310+
this.print();
311+
System.out.println(dataPrinter.run(this.data));
312+
}
313+
else
314+
this.print(dataPrinter);
315+
}
316+
317+
}

0 commit comments

Comments
 (0)