diff --git a/common-tools/clas-detector/src/main/java/org/jlab/detector/qadb/QadbBin.java b/common-tools/clas-detector/src/main/java/org/jlab/detector/qadb/QadbBin.java new file mode 100644 index 0000000000..ec8dd97c33 --- /dev/null +++ b/common-tools/clas-detector/src/main/java/org/jlab/detector/qadb/QadbBin.java @@ -0,0 +1,317 @@ +package org.jlab.detector.qadb; + +import java.util.List; +import org.jlab.detector.scalers.DaqScalers; +import org.jlab.detector.scalers.DaqScalersSequence; + +/** + * A single bin for the Quality Assurance Database (QADB). + * It may hold arbitrary data, such as a class instance, accessible by public member {@link QadbBin#data}; + * its type is set by a generic type parameter. + *

+ * A bin contains a (sub)sequence of scaler readouts, and therefore extends {@link DaqScalersSequence}. + * @see QadbBinSequence + * @author dilks + */ +public class QadbBin extends DaqScalersSequence { + + private int binNum; + private BinType binType; + private int evnumMin; + private int evnumMax; + private long timestampMin; + private long timestampMax; + private double charge; // ungated + private double chargeGated; + + /** arbitrary data that may be held by this bin; it is just public so the user can do anything with it */ + public T data; + + // ---------------------------------------------------------------------------------- + + /** lambda type to print each bin's generic data as a string */ + public interface DataPrinter { + /** + * @param data the public member {@link QadbBin#data} + * @return a String representation of {@link QadbBin#data} + */ + String run(T data); + } + + /** bin type */ + public enum BinType { + /** the first bin, for events before the first scaler readout */ + FIRST, + /** any bin between two scaler readouts */ + INTERMEDIATE, + /** the last bin, for events after the last scaler readout */ + LAST, + } + + /** charge type */ + public enum ChargeType { + /** full charge, DAQ-ungated */ + UNGATED, + /** DAQ-gated charge */ + GATED, + } + + // ---------------------------------------------------------------------------------- + + /** + * construct a single bin + * @param binNum the bin number, in the {@link QadbBinSequence} which contains this bin + * @param binType the bin type (see {@link BinType}) + * @param inputScalers the scaler sequence for this bin + * @param initData the initial data for this bin (sets public member {@link data}) + */ + public QadbBin(int binNum, BinType binType, List inputScalers, T initData) { + super(inputScalers); + this.binNum = binNum; + this.binType = binType; + this.data = initData; + switch(this.binType) { + case INTERMEDIATE -> { + this.timestampMin = this.scalers.get(0).getTimestamp(); + this.timestampMax = this.scalers.get(scalers.size()-1).getTimestamp(); + this.evnumMin = this.scalers.get(0).getEventNum(); + this.evnumMax = this.scalers.get(scalers.size()-1).getEventNum(); + this.charge = this.getInterval().getBeamCharge(); + this.chargeGated = this.getInterval().getBeamChargeGated(); + } + case FIRST -> { + if(this.scalers.size() != 1) + throw new RuntimeException("a FIRST bin may only have ONE scaler readout"); + this.timestampMin = 0; // user may correct this using `correctLowerBound` + this.timestampMax = this.scalers.get(0).getTimestamp(); + this.evnumMin = 0; // user may correct this using `correctLowerBound` + this.evnumMax = this.scalers.get(0).getEventNum(); + this.charge = 0; // since no lower bound + this.chargeGated = 0; // since no lower bound + } + case LAST -> { + if(this.scalers.size() != 1) + throw new RuntimeException("a LAST bin may only have ONE scaler readout"); + this.timestampMin = this.scalers.get(0).getTimestamp(); + this.timestampMax = 10 * this.timestampMin; // user may correct this using `correctUpperBound` + this.evnumMin = this.scalers.get(0).getEventNum(); + this.evnumMax = 10 * this.evnumMin; // user may correct this using `correctUpperBound` + this.charge = 0; // since no upper bound + this.chargeGated = 0; // since no upper bound + } + } + } + + // ---------------------------------------------------------------------------------- + + /** @return the bin number for this bin */ + public int getBinNum() { return this.binNum; } + + /** @return minimum timestamp for this bin */ + public long getTimestampMin() { return this.timestampMin; } + + /** @return maximum timestamp for this bin */ + public long getTimestampMax() { return this.timestampMax; } + + /** @return minimum event number for this bin */ + public long getEventNumMin() { return this.evnumMin; } + + /** @return maximum event number for this bin */ + public long getEventNumMax() { return this.evnumMax; } + + /** @return the beam charge, not gated by DAQ, for this bin */ + public double getBeamCharge() { return this.charge; } + + /** @return the beam charge, gated by DAQ, for this bin */ + public double getBeamChargeGated() { return this.chargeGated; } + + /** + * @return the beam charge, gated or ungated + * @param chargeType the type of charge + */ + public double getBeamCharge(ChargeType chargeType) { + return switch(chargeType) { + case UNGATED -> this.getBeamCharge(); + case GATED -> this.getBeamChargeGated(); + }; + } + + // ---------------------------------------------------------------------------------- + + /** @return the mean livetime for this bin */ + public double getMeanLivetime() { + double sumLivetime = 0; + int numLivetimeEvents = 0; + for(int i=1; i= 0) { // filter out livetime = -1 + sumLivetime += livetime; + numLivetimeEvents++; + } + } + return numLivetimeEvents > 0 ? sumLivetime / numLivetimeEvents : 0; + } + + /** @return the duration of the bin, in seconds */ + public double getDuration() { + return (this.getTimestampMax() - this.getTimestampMin()) * 4e-9; // convert timestamp units [4ns] -> [s] + } + + // ---------------------------------------------------------------------------------- + + /** charge correction method */ + public enum ChargeCorrectionMethod { + /** interchange the DAQ-gated and ungated charges */ + BY_FLIP, + /** calculate the DAQ-gated charge as the mean livetime multiplied by the ungated charge */ + BY_MEAN_LIVETIME, + } + + /** + * correct the beam charge for this bin, using a correction method + * @param method the correction method to use + * @see ChargeCorrectionMethod + */ + public void correctCharge(ChargeCorrectionMethod method) { + logger.fine("correcting beam charge for bin " + this.binNum + " using method " + method); + logger.fine(" before: gated = " + this.chargeGated); + logger.fine(" ungated = " + this.charge); + switch(method) { + case BY_FLIP -> { // interchange the gated and ungated charge + var tmp = this.charge; + this.charge = this.chargeGated; + this.chargeGated = tmp; + } + case BY_MEAN_LIVETIME -> { // gated = * ungated + var meanLivetime = this.getMeanLivetime(); + logger.fine(" mean livetime = " + meanLivetime); + this.chargeGated = meanLivetime * this.charge; + } + } + logger.fine(" after: gated = " + this.chargeGated); + logger.fine(" ungated = " + this.charge); + } + + /** + * correct the beam charge for this bin, using specific values from the caller + * @param charge the charge, not gated by the DAQ + * @param chargeGated the DAQ-gated charge + */ + public void correctCharge(double charge, double chargeGated) { + logger.fine("correcting beam charge for bin " + this.binNum + " using user-specified values"); + logger.fine(" before: gated = " + this.chargeGated); + logger.fine(" ungated = " + this.charge); + this.charge = charge; + this.chargeGated = chargeGated; + logger.fine(" after: gated = " + this.chargeGated); + logger.fine(" ungated = " + this.charge); + } + + // ---------------------------------------------------------------------------------- + + /** + * correct the first bin's lower bound, if you know it from tag-0 events + * @param evnumMin the correct minimum event number + * @param timestampMin the correct minimum timestamp + */ + public void correctLowerBound(int evnumMin, long timestampMin) { + if(this.binType == BinType.FIRST) { + this.evnumMin = evnumMin; + this.timestampMin = timestampMin; + } + else logger.warning("not allowed to correct the lower bound of a bin with type " + this.binType); + } + + /** + * correct the last bin's upper bound, if you know it from tag-0 events + * @param evnumMax the correct maximum event number + * @param timestampMax the correct maximum timestamp + */ + public void correctUpperBound(int evnumMax, long timestampMax) { + if(this.binType == BinType.LAST) { + this.evnumMax = evnumMax; + this.timestampMax = timestampMax; + } + else logger.warning("not allowed to correct the upper bound of a bin with type " + this.binType); + } + + // ---------------------------------------------------------------------------------- + + /** extremum type, used with {@link QadbBin#getChargeExtremum} */ + public enum ExtremumType { + /** from the first scaler readout */ + FIRST, + /** from the last scaler readout */ + LAST, + /** the maximum */ + MAX, + /** the minimum */ + MIN, + } + + /** + * Get the min/max or initial/final charge. + *

+ * WARNING: this is likely NOT corrected by {@link correctCharge} + * @param extremumType the type of extremum + * @param chargeType the type of charge + * @return the charge for the given extremum + */ + public double getChargeExtremum(ExtremumType extremumType, ChargeType chargeType) { + return switch(extremumType) { + case FIRST -> getDsc2Charge(this.scalers.get(0), chargeType); + case LAST -> getDsc2Charge(this.scalers.get(this.scalers.size()-1), chargeType); + case MIN -> getDsc2Charge(this.scalers.stream().reduce((a,b) -> getDsc2Charge(a, chargeType) < getDsc2Charge(b, chargeType) ? a : b).get(), chargeType); + case MAX -> getDsc2Charge(this.scalers.stream().reduce((a,b) -> getDsc2Charge(a, chargeType) > getDsc2Charge(b, chargeType) ? a : b).get(), chargeType); + }; + } + + /** helper method for {@link getChargeExtremum} + * @param ds the scaler readout + * @param chargeType the charge type + * @return the charge from this scaler object + */ + private static double getDsc2Charge(DaqScalers ds, ChargeType chargeType) { + return switch(chargeType) { + case UNGATED -> ds.dsc2.getBeamCharge(); + case GATED -> ds.dsc2.getBeamChargeGated(); + }; + } + + // ---------------------------------------------------------------------------------- + + /** print a QA bin, and some basic information */ + public void print() { + System.out.printf("BIN %d", this.getBinNum()); + System.out.printf(" -----------\n"); + System.out.printf("%30s %d to %d\n", "timestamp interval:", this.getTimestampMin(), this.getTimestampMax()); + System.out.printf("%30s %d to %d\n", "event number interval:", this.getEventNumMin(), this.getEventNumMax()); + System.out.printf("%30s %f s\n", "duration:", this.getDuration()); + System.out.printf("%30s %d events\n", "event number range:", this.getEventNumMax() - this.getEventNumMin()); + System.out.printf("%30s %f / %f\n", "beam charge gated / ungated:", this.getBeamChargeGated(), this.getBeamCharge()); + } + + /** + * print a QA bin's stored {@link data} + * @param dataPrinter a lambda which resolves {@link data} as a {@code String} + */ + public void print(DataPrinter dataPrinter) { + System.out.printf("BIN %d :: ", this.getBinNum()); + System.out.println(dataPrinter.run(this.data)); + } + + /** + * print a QA bin's stored {@link data}, and optionally the bin's basic information + * @param dataPrinter a lambda which resolves {@link data} as a {@code String} + * @param verbose if {@code true}, print more + */ + public void print(DataPrinter dataPrinter, boolean verbose) { + if(verbose) { + this.print(); + System.out.println(dataPrinter.run(this.data)); + } + else + this.print(dataPrinter); + } + +} diff --git a/common-tools/clas-detector/src/main/java/org/jlab/detector/qadb/QadbBinSequence.java b/common-tools/clas-detector/src/main/java/org/jlab/detector/qadb/QadbBinSequence.java new file mode 100644 index 0000000000..c839f96260 --- /dev/null +++ b/common-tools/clas-detector/src/main/java/org/jlab/detector/qadb/QadbBinSequence.java @@ -0,0 +1,246 @@ +package org.jlab.detector.qadb; + +import java.util.List; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Optional; +import java.util.Iterator; + +import org.jlab.detector.scalers.DaqScalersSequence; + +import org.jlab.jnp.hipo4.io.HipoReader; +import org.jlab.jnp.hipo4.data.Event; +import org.jlab.jnp.hipo4.data.Bank; +import org.jlab.jnp.hipo4.data.SchemaFactory; + +/** + * A sequence of bins for the Quality Assurance Database (QADB). + *

+ * The bins may hold generic data, such as a class instance, accessible by {@link QadbBin#data}; the data + * type is set by a generic type parameter, and all bins will hold the same type of data. + * @see QadbBin + * @author dilks + */ +public class QadbBinSequence extends DaqScalersSequence implements Iterable> { + + /** sequence of QA bins */ + private final List> qaBins = new ArrayList<>(); + + // ---------------------------------------------------------------------------------- + + /** lambda type to initialize each bin's generic data */ + public interface DataInitializer { + /** + * @param n the bin number + * @return the initial public member {@link QadbBin#data} for bin number {@code n} + */ + T run(int n); + } + + // ---------------------------------------------------------------------------------- + + /** + * read a list of HIPO files for a run and generate a sequence of QADB bins. + * The original sequence of scalers ({@link DaqScalersSequence}) is sampled: + *

+ * @param filenames list of HIPO files to read + * @param binWidth the number of consecutive scaler-readout intervals in each bin + * @param initDataFunction a lambda to create the initial data for each bin; must be of the form {@code (binNumber) -> { return initData object }} + */ + public QadbBinSequence(List filenames, int binWidth, DataInitializer initDataFunction) { + if(binWidth <= 0) + throw new RuntimeException("binWidth must be greater than 0"); + // construct the full, sorted scaler sequence + logger.info("QadbBinSequence:: constructing DAQ scalers sequence"); + this.readFiles(filenames); + if(this.scalers.isEmpty()) + throw new RuntimeException("scalers sequence is empty"); + // validate ordering: currently, QADBs use event number for lookups, so event number vs. timestamp should monotonically increase + logger.fine("...validating ordering..."); + if(!this.validateOrdering()) + logger.severe("ERROR: scaler readout ordering is NOT VALID!"); // continue anyway, since the user may still want to see the QADB results + logger.fine("...done, now constructing QADB bin sequence..."); + logger.fine(" initial sequence size = " + this.scalers.size()); + // add an initial, empty bin; its scaler sequence just contains the first scaler readout + int binNum = 0; + this.qaBins.add(new QadbBin(binNum, QadbBin.BinType.FIRST, this.scalers.subList(0, 1), initDataFunction.run(binNum))); + // sample the original scaler sequence: make a new `QadbBin` for each subsequence + List scalersToKeep = new ArrayList<>(); // list of `scalers` indices to keep, i.e., the ones at the bin boundaries + scalersToKeep.add(0); + for(int i=0; i(binNum, QadbBin.BinType.INTERMEDIATE, this.scalers.subList(i, end+1), initDataFunction.run(binNum))); + scalersToKeep.add(end); + } + logger.fine(" scalers to keep = " + scalersToKeep); + // add a final, empty bin; its scaler sequence just contains the last scaler readout + binNum = this.qaBins.size(); + this.qaBins.add(new QadbBin(binNum, QadbBin.BinType.LAST, this.scalers.subList(this.scalers.size()-1, this.scalers.size()), initDataFunction.run(binNum))); + // remove all `scalers` elements which are not on bin boundaries + for(int i=this.scalers.size()-1; i>=0; i--) { + if(!scalersToKeep.contains(i)) + this.scalers.remove(i); + } + logger.fine(" sampled sequence size = " + this.scalers.size()); + logger.fine(" number of QADB bins = " + this.qaBins.size()); + } + + /** + * alternative constructor, with no {@link QadbBin#data} initialization parameter + *

+ * {@link QadbBin#data} will be initialized to {@code null} + * @param filenames list of HIPO files to read + * @param binWidth the number of consecutive scaler-readout intervals in each bin + */ + public QadbBinSequence(List filenames, int binWidth) { + this(filenames, binWidth, (binNum)->null); + } + + // ---------------------------------------------------------------------------------- + + /** iterable interface implementation */ + @Override + public Iterator> iterator() { + return this.qaBins.iterator(); + } + + /** @return the number of bins in this sequence */ + @Override + public int size() { + return this.qaBins.size(); + } + + /** + * @param idx bin index + * @return a bin for a given index + */ + public QadbBin getBin(int idx) { + return this.qaBins.get(idx); + } + + // ---------------------------------------------------------------------------------- + + /** + * @return the bin which contains the timestamp, if found + * @param timestamp the timestamp + */ + public Optional> findBin(long timestamp) { + logger.finest(" -> QadbBinSequence.findBin(" + timestamp + ")"); + var idx = this.findIndex(timestamp); + if(idx>=0 && idx found QADB bin at idx = " + idx); + return idx>=0 && idx filenames = new ArrayList<>(); + filenames.addAll(Arrays.asList(args)); + + // define a QADB bin sequence + // - as an example, we have each bin store an integer, which we will use to count the number of tag-0 events in the bin + // - each bin's integer is initialized to zero; the lambda argument `binNum` represents the bin number, and is unused here + // - in practice, we can use any data type instead of an integer, such as a class full of histograms + // - the lambda argument `binNum` can be used, for example, as part of the histogram titles + QadbBinSequence seq = new QadbBinSequence<>(filenames, 2000, (binNum)->0); + /* alternatively, if you do not want to store data with this class instance, use `Object` as the type, and no initializer lambda: + QadbBinSequence seeq = new QadbBinSequence<>(filenames, 2000); + for(var bin : seeq) bin.print(); + System.exit(0); + */ + + // apply a charge correction method + // for(var bin : seq) + // bin.correctCharge(QadbBin.ChargeCorrectionMethod.BY_MEAN_LIVETIME); + + // initialize a minimum and maximum event number and timestamp for tag-0 events + int evnumMin = -1; + int evnumMax = -1; + long timestampMin = -1; + long timestampMax = -1; + + // read the list of HIPO files + logger.info("===== begin event loop ===="); + for(String filename : filenames) { + HipoReader reader = new HipoReader(); + reader.setTags(0); + reader.open(filename); + SchemaFactory schema = reader.getSchemaFactory(); + + // tag-0 event loop + while(reader.hasNext()) { + Bank configBank = new Bank(schema.getSchema("RUN::config")); + Event event = new Event(); + reader.nextEvent(event); + event.read(configBank); + + // find the bin which contains this event + if(configBank.getRows()>0) { + var timestamp = configBank.getLong("timestamp", 0); + var evnum = configBank.getInt("event", 0); + var thisBin = seq.findBin(timestamp); + if(thisBin.isPresent()) { + thisBin.get().data++; // increment the counter for tag-0 events + evnumMin = evnumMin == -1 ? evnum : Math.min(evnum, evnumMin); // set event number and timestamp extrema + evnumMax = evnumMax == -1 ? evnum : Math.max(evnum, evnumMax); + timestampMin = timestampMin == -1 ? timestamp : Math.min(timestamp, timestampMin); + timestampMax = timestampMax == -1 ? timestamp : Math.max(timestamp, timestampMax); + } + else logger.warning("WARNING: failed to find a bin containing timestamp " + timestamp); + } + } + + reader.close(); + } + logger.info("===== end event loop ===="); + + // correct the first and last bin with the tag-0 event number and timestamp extrema; + // this is done such that the event number and timestamp ranges are correct for these bins + seq.correctLowerBound(evnumMin, timestampMin); + seq.correctUpperBound(evnumMax, timestampMax); + + // print the results: the bin number along with its number of events + System.out.println(">>> QA BINS <<<"); + for(var bin : seq) + bin.print((data) -> String.format("%30s %d", "counted tag-0 events:", data), true); + } + +} diff --git a/common-tools/clas-detector/src/main/java/org/jlab/detector/scalers/DaqScalers.java b/common-tools/clas-detector/src/main/java/org/jlab/detector/scalers/DaqScalers.java index abb2793e0c..108458acbc 100644 --- a/common-tools/clas-detector/src/main/java/org/jlab/detector/scalers/DaqScalers.java +++ b/common-tools/clas-detector/src/main/java/org/jlab/detector/scalers/DaqScalers.java @@ -49,13 +49,16 @@ public class DaqScalers { public Dsc2Scaler dsc2=null; public StruckScalers struck=null; private long timestamp=0; - + private int evnum=0; + public DaqScalers setTimestamp(long timestamp) { this.timestamp=timestamp; return this; } public long getTimestamp(){ return this.timestamp; } + public void setEventNum(int evnum) { this.evnum=evnum; } + public int getEventNum() { return this.evnum; } @Override public String toString() { diff --git a/common-tools/clas-detector/src/main/java/org/jlab/detector/scalers/DaqScalersSequence.java b/common-tools/clas-detector/src/main/java/org/jlab/detector/scalers/DaqScalersSequence.java index b04c03ac77..c9e6945f9b 100644 --- a/common-tools/clas-detector/src/main/java/org/jlab/detector/scalers/DaqScalersSequence.java +++ b/common-tools/clas-detector/src/main/java/org/jlab/detector/scalers/DaqScalersSequence.java @@ -31,9 +31,9 @@ public class DaqScalersSequence implements Comparator { private Bank runConfigBank=null; private Bank runScalerBank=null; - static final Logger logger = Logger.getLogger(DaqScalersSequence.class.getName()); + protected static final Logger logger = Logger.getLogger(DaqScalersSequence.class.getName()); - private DaqScalersSequence(){}; + protected DaqScalersSequence(){}; public static class Interval { private DaqScalers previous = null; @@ -83,6 +83,11 @@ public int compare(DaqScalers o1, DaqScalers o2) { if (o1.getTimestamp() > o2.getTimestamp()) return +1; return 0; } + + /** @return the number of scalers in this sequence */ + public int size() { + return scalers.size(); + } protected int findIndex(long timestamp) { if (this.scalers.isEmpty()) return -1; @@ -94,6 +99,7 @@ protected int findIndex(long timestamp) { ds.setTimestamp(timestamp); final int index=Collections.binarySearch(this.scalers,ds,new DaqScalersSequence()); final int n = index<0 ? -index-2 : index; + logger.finest(" -> DaqScalersSequence.findIndex(" + timestamp + ") -> index = " + index + " -> return " + n); return n; } @@ -102,6 +108,11 @@ public DaqScalersSequence(SchemaFactory schema) { runScalerBank=new Bank(schema.getSchema("RUN::scaler")); } + public DaqScalersSequence(List inputScalers) { + for (DaqScalers inputScaler : inputScalers) + this.add(inputScaler); + } + public void clear() { scalers.clear(); } @@ -135,11 +146,14 @@ public boolean add(Event event){ event.read(runConfigBank); if (runScalerBank.getRows() > 0) { long timestamp=0; + int evnum=0; if (runConfigBank.getRows()>0) { timestamp=runConfigBank.getLong("timestamp",0); + evnum=runConfigBank.getInt("event",0); } DaqScalers ds=DaqScalers.create(runScalerBank); ds.setTimestamp(timestamp); + ds.setEventNum(evnum); return add(ds); } return false; @@ -219,18 +233,25 @@ public Interval getInterval(Event event1, Event event2) { * @return sequence */ public static DaqScalersSequence readSequence(List filenames) { - logger.info("DaqScalersSequence:: Reading scaler sequence from "+String.join(",", filenames)); - DaqScalersSequence seq=new DaqScalersSequence(); + seq.readFiles(filenames); + return seq; + } + /** + * @param filenames list of names of HIPO files to read + */ + protected void readFiles(List filenames) { + logger.info("DaqScalersSequence:: Reading scaler sequence from "+String.join(",", filenames)); + for (String filename : filenames) { HipoReader reader = new HipoReader(); reader.setTags(1); reader.open(filename); - if (seq.runConfigBank==null) { - seq.runConfigBank = new Bank(reader.getSchemaFactory().getSchema("RUN::config")); + if (this.runConfigBank==null) { + this.runConfigBank = new Bank(reader.getSchemaFactory().getSchema("RUN::config")); } SchemaFactory schema = reader.getSchemaFactory(); @@ -245,21 +266,22 @@ public static DaqScalersSequence readSequence(List filenames) { event.read(configBank); long timestamp=0; + int evnum=0; if (scalerBank.getRows()<1) continue; if (configBank.getRows()>0) { timestamp=configBank.getLong("timestamp",0); + evnum=configBank.getInt("event",0); } DaqScalers ds=DaqScalers.create(scalerBank); ds.setTimestamp(timestamp); - seq.add(ds); + ds.setEventNum(evnum); + this.add(ds); } reader.close(); } - - return seq; } /** @@ -294,6 +316,35 @@ public static DaqScalersSequence rebuildSequence(int tags, ConstantsManager conm return seq; } + /** + * Checks if the scalers list is sorted such that the scalers' timestamp and event number orderings are consistent and monotonically increasing. + * @return {@code true} if timestamp and event number orderings are consistent + */ + public boolean validateOrdering() { + if (scalers.size() <= 1) return true; // trivial case + boolean result = true; + for (int i = 0; i < scalers.size() - 1; i++) { + var prev = scalers.get(i); + var next = scalers.get(i + 1); + var timestampComparison = Long.compare(prev.getTimestamp(), next.getTimestamp()); + var evnumComparison = Integer.compare(prev.getEventNum(), next.getEventNum()); + if (timestampComparison == 0 || evnumComparison == 0) { + logger.warning("WARNING: found possible duplicate scaler readout: evnum=" + prev.getEventNum() + " timestamp=" + prev.getTimestamp() + " i=" + i); + logger.warning(" next readout has: evnum=" + next.getEventNum() + " timestamp=" + next.getTimestamp()); + result = false; + } + // if neither is equal, they must have the same sign: negative, i.e., increasing monotonically + else { + if (Integer.signum(timestampComparison) != -1 || Integer.signum(evnumComparison) != -1) { + logger.warning("WARNING: found non-monotonic scaler ordering: evnum=" + prev.getEventNum() + " timestamp=" + prev.getTimestamp() + " i=" + i); + logger.warning(" next readout has: evnum=" + next.getEventNum() + " timestamp=" + next.getTimestamp()); + result = false; + } + } + } + return result; + } + /** * Try to fix clock rollover on the run-integrating DSC2 scaler. * 1. Assume the first clock readout has no rollover.