/***************************************************************************** * 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.InterpMethod; import info.limpet.ITemporalQuantityCollection; 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 MultiplyQuantityOperation implements IOperation<IStoreItem> { private CollectionComplianceTests aTests = new CollectionComplianceTests(); public MultiplyQuantityOperation() { } public Collection<ICommand<IStoreItem>> actionsFor( List<IStoreItem> selection, IStore destination, IContext context) { Collection<ICommand<IStoreItem>> res = new ArrayList<ICommand<IStoreItem>>(); if (appliesTo(selection)) { // ok, temporal? final boolean suitableForInterpolated = aTests.allTemporalOrSingleton(selection); final boolean suitableForIndexed = !aTests.allNonTemporal(selection) && aTests.allEqualLengthOrSingleton(selection); if (suitableForInterpolated || suitableForIndexed) { final IBaseTemporalCollection longest; if (suitableForInterpolated) { longest = (IBaseTemporalCollection) aTests .getLongestTemporalCollections(selection); } else { longest = null; } ICommand<IStoreItem> newC = new MultiplyQuantityValues(selection, destination, longest, context); res.add(newC); } else { ICommand<IStoreItem> newC = new MultiplyQuantityValues(selection, destination, context); res.add(newC); } } return res; } private boolean appliesTo(List<IStoreItem> selection) { // first check we have quantity data if (aTests.allCollections(selection) && aTests.nonEmpty(selection) && aTests.allQuantity(selection)) { // ok, we have quantity data. See if we have series of the same length, or // singletons return aTests.allTemporal(selection) || aTests.allEqualLengthOrSingleton(selection); } else { return false; } } public static class MultiplyQuantityValues extends AbstractCommand<IStoreItem> { private IBaseTemporalCollection _timeProvider; public MultiplyQuantityValues(List<IStoreItem> selection, IStore store, IContext context) { this(selection, store, null, context); } public MultiplyQuantityValues(List<IStoreItem> selection, IStore store, IBaseTemporalCollection timeProvider, IContext context) { super("Multiply series", "Multiply series", store, false, false, selection, context); _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 protected String getOutputName() { return getContext().getInput("Multiply datasets", NEW_DATASET_MESSAGE, "Product of " + super.getSubjectList()); } @Override public void execute() { Unit<?> unit = calculateOutputUnit(); 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(unit, 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 getStore().add(target); } private Unit<?> calculateOutputUnit() { Iterator<IStoreItem> inputsIterator = getInputs().iterator(); IQuantityCollection<?> firstItem = (IQuantityCollection<?>) inputsIterator.next(); Unit<?> unit = firstItem.getUnits(); while (inputsIterator.hasNext()) { IQuantityCollection<?> nextItem = (IQuantityCollection<?>) inputsIterator.next(); unit = unit.times(nextItem.getUnits()); } return unit; } @Override protected void recalculate(IStoreItem subject) { Unit<?> unit = calculateOutputUnit(); // update the results performCalc(unit, 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 unit * the units to use * @param outputs * the list of output series */ protected void performCalc(Unit<?> unit, List<IStoreItem> outputs) { IQuantityCollection<?> target = (IQuantityCollection<?>) outputs.iterator().next(); // clear out the output list first target.clearQuiet(); if (_timeProvider != null) { Collection<Long> times = _timeProvider.getTimes(); Iterator<Long> tIter = times.iterator(); while (tIter.hasNext()) { final Long thisTime = tIter.next(); Double runningTotal = null; for (int i = 0; i < getInputs().size(); i++) { @SuppressWarnings("unchecked") IQuantityCollection<Quantity> thisC = (IQuantityCollection<Quantity>) getInputs().get(i); final double thisValue; // just check that this isn't a singleton if (thisC.getValuesCount() == 1) { thisValue = thisC.getValues().get(0).doubleValue(thisC.getUnits()); } else { ITemporalQuantityCollection<Quantity> tqc = (ITemporalQuantityCollection<Quantity>) thisC; Measurable<Quantity> thisMeasure = tqc.interpolateValue(thisTime, InterpMethod.Linear); if (thisMeasure != null) { thisValue = thisMeasure.doubleValue(thisC.getUnits()); } else { thisValue = 1; } } // first value? if (runningTotal == null) { runningTotal = thisValue; } else { runningTotal = runningTotal * thisValue; } } ITemporalQuantityCollection<?> itq = (ITemporalQuantityCollection<?>) target; itq.add(thisTime, runningTotal); } } else { // find the (non-singleton) array length int length = getNonSingletonArrayLength(getInputs()); // start adding values. for (int j = 0; j < length; j++) { Double runningTotal = null; for (int i = 0; i < getInputs().size(); i++) { @SuppressWarnings("unchecked") IQuantityCollection<Quantity> thisC = (IQuantityCollection<Quantity>) getInputs().get(i); final double thisValue; // just check that this isn't a singleton if (thisC.getValuesCount() == 1) { thisValue = thisC.getValues().get(0).doubleValue(thisC.getUnits()); } else { thisValue = thisC.getValues().get(j).doubleValue(thisC.getUnits()); } // first value? if (runningTotal == null) { runningTotal = thisValue; } else { runningTotal = runningTotal * thisValue; } } target.add(runningTotal); } } target.fireDataChanged(); } } }