/***************************************************************************** * Limpet - the Lightweight InforMation ProcEssing Toolkit * http://limpet.info * * (C) 2015-2016, Deep Blue C Technologies Ltd * * This library is free software; you can redistribute it and/or * modify it under the terms of the Eclipse Public License v1.0 * (http://www.eclipse.org/legal/epl-v10.html) * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *****************************************************************************/ package info.limpet.data.operations.arithmetic; import info.limpet.IBaseTemporalCollection; import info.limpet.ICollection; import info.limpet.ICommand; import info.limpet.IContext; import info.limpet.IOperation; import info.limpet.IQuantityCollection; import info.limpet.IStore; import info.limpet.IStoreItem; import info.limpet.ITemporalQuantityCollection; import info.limpet.ITemporalQuantityCollection.InterpMethod; import info.limpet.data.commands.AbstractCommand; import info.limpet.data.impl.QuantityCollection; import info.limpet.data.impl.TemporalQuantityCollection; import info.limpet.data.operations.CollectionComplianceTests; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import javax.measure.Measurable; import javax.measure.quantity.Quantity; import javax.measure.unit.Unit; public class DivideQuantityOperation implements IOperation<IStoreItem> { private final CollectionComplianceTests aTests = new CollectionComplianceTests(); public Collection<ICommand<IStoreItem>> actionsFor( List<IStoreItem> selection, IStore destination, IContext context) { Collection<ICommand<IStoreItem>> res = new ArrayList<ICommand<IStoreItem>>(); if (appliesTo(selection)) { ICollection item1 = (ICollection) selection.get(0); ICollection item2 = (ICollection) selection.get(1); final IBaseTemporalCollection longest; if (aTests.allTemporal(selection)) { longest = aTests.getLongestTemporalCollections(selection); } else { longest = null; } ICommand<IStoreItem> newC = new DivideQuantityValues("Divide " + item1.getName() + " by " + item2.getName(), selection, item1, item2, destination, longest, context); res.add(newC); newC = new DivideQuantityValues("Divide " + item2.getName() + " by " + item1.getName(), selection, item2, item1, destination, longest, context); res.add(newC); } return res; } private boolean appliesTo(List<IStoreItem> selection) { boolean res = false; // first check we have quantity data if (aTests.exactNumber(selection, 2) && aTests.allQuantity(selection)) { // ok, we have quantity data. See if we have series of the same // length, or singletons res = aTests.allTemporal(selection) || aTests.allEqualLengthOrSingleton(selection); } return res; } public static class DivideQuantityValues extends AbstractCommand<IStoreItem> { private final IQuantityCollection<Quantity> _item1; private final IQuantityCollection<Quantity> _item2; private IBaseTemporalCollection _timeProvider; @SuppressWarnings("unchecked") public DivideQuantityValues(String title, List<IStoreItem> selection, ICollection item1, ICollection item2, IStore store, IBaseTemporalCollection timeProvider, IContext context) { super(title, "Divide provided series", store, false, false, selection, context); _item1 = (IQuantityCollection<Quantity>) item1; _item2 = (IQuantityCollection<Quantity>) item2; _timeProvider = timeProvider; } /** * produce a target of the correct type * * @param input * one of the input series * @param unit * the units to use * @return */ protected IQuantityCollection<?> createQuantityTarget() { Unit<?> unit = calculateOutputUnit(); final IQuantityCollection<?> target; if (_timeProvider != null) { target = new TemporalQuantityCollection<>(getOutputName(), this, unit); } else { target = new QuantityCollection<>(getOutputName(), this, unit); } return target; } @Override public void execute() { List<IStoreItem> outputs = new ArrayList<IStoreItem>(); // ok, generate the new series IQuantityCollection<?> target = createQuantityTarget(); outputs.add(target); // store the output super.addOutput(target); // start adding values. performCalc(outputs); // tell each series that we're a dependent Iterator<IStoreItem> iter = getInputs().iterator(); while (iter.hasNext()) { ICollection iCollection = (ICollection) iter.next(); iCollection.addDependent(this); } // ok, done List<IStoreItem> res = new ArrayList<IStoreItem>(); res.add(target); getStore().addAll(res); } @SuppressWarnings("unchecked") private Unit<Quantity> calculateOutputUnit() { return (Unit<Quantity>) _item1.getUnits().divide(_item2.getUnits()); } @Override protected void recalculate(IStoreItem subject) { // update the results performCalc(getOutputs()); } /** * wrap the actual operation. We're doing this since we need to separate it from the core * "execute" operation in order to support dynamic updates * * @param outputs */ @SuppressWarnings("unchecked") private void performCalc(List<IStoreItem> outputs) { IQuantityCollection<Quantity> target = (IQuantityCollection<Quantity>) outputs.iterator().next(); // clear out the lists, first Iterator<IStoreItem> iter = outputs.iterator(); while (iter.hasNext()) { IQuantityCollection<Quantity> qC = (IQuantityCollection<Quantity>) iter.next(); qC.clearQuiet(); } if (_timeProvider != null) { // ok, temporal (interpolated) calculation Collection<Long> times = _timeProvider.getTimes(); Iterator<Long> tIter = times.iterator(); while (tIter.hasNext()) { final Long thisTime = tIter.next(); Double runningTotal = null; final double thisValue, otherValue; if (_item1.getValuesCount() == 1) { thisValue = _item1.getValues().get(0).doubleValue( (Unit<Quantity>) _item1.getUnits()); } else { ITemporalQuantityCollection<Quantity> tqc = (ITemporalQuantityCollection<Quantity>) _item1; Measurable<Quantity> thisMeasure = tqc.interpolateValue(thisTime, InterpMethod.Linear); if (thisMeasure != null) { thisValue = thisMeasure.doubleValue(_item1.getUnits()); } else { thisValue = 1; } } if (_item2.getValuesCount() == 1) { otherValue = _item2.getValues().get(0).doubleValue( (Unit<Quantity>) _item2.getUnits()); } else { ITemporalQuantityCollection<Quantity> tqc = (ITemporalQuantityCollection<Quantity>) _item2; Measurable<Quantity> thisMeasure = tqc.interpolateValue(thisTime, InterpMethod.Linear); if (thisMeasure != null) { otherValue = thisMeasure.doubleValue(_item2.getUnits()); } else { otherValue = 1; } } // first value? runningTotal = thisValue / otherValue; ITemporalQuantityCollection<?> itq = (ITemporalQuantityCollection<?>) target; itq.add(thisTime, runningTotal); } } else { // find the (non-singleton) array length final int length = getNonSingletonArrayLength(getInputs()); for (int j = 0; j < length; j++) { final double thisValue; if (_item1.getValuesCount() == 1) { thisValue = _item1.getValues().get(0).doubleValue( (Unit<Quantity>) _item1.getUnits()); } else { thisValue = _item1.getValues().get(j).doubleValue( (Unit<Quantity>) _item1.getUnits()); } final double otherValue; if (_item2.getValuesCount() == 1) { otherValue = _item2.getValues().get(0).doubleValue( (Unit<Quantity>) _item2.getUnits()); } else { otherValue = _item2.getValues().get(j).doubleValue( (Unit<Quantity>) _item2.getUnits()); } double res = thisValue / otherValue; target.add(res); } } // and fire the update target.fireDataChanged(); } @Override protected String getOutputName() { return getContext().getInput("Divide datasets", NEW_DATASET_MESSAGE, _item1 + " div by " + _item2); } } }