/***************************************************************************** * 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.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.UIProperty; import info.limpet.data.commands.AbstractCommand; import info.limpet.data.impl.QuantityCollection; import info.limpet.data.impl.TemporalQuantityCollection; import info.limpet.data.math.SimpleMovingAverage; 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 SimpleMovingAverageOperation implements IOperation<ICollection> { public static final String SERIES_NAME_TEMPLATE = "Simple Moving Average"; private final CollectionComplianceTests aTests = new CollectionComplianceTests(); private final int _windowSize; public SimpleMovingAverageOperation(int windowSize) { this._windowSize = windowSize; } public Collection<ICommand<ICollection>> actionsFor( List<ICollection> selection, IStore destination, IContext context) { Collection<ICommand<ICollection>> res = new ArrayList<ICommand<ICollection>>(); if (appliesTo(selection)) { ICommand<ICollection> newC = new SimpleMovingAverageCommand(SERIES_NAME_TEMPLATE, selection, destination, _windowSize, context); res.add(newC); } return res; } private boolean appliesTo(List<ICollection> selection) { boolean singleSeries = selection.size() == 1; boolean allQuantity = aTests.allQuantity(selection); return singleSeries && allQuantity; } public static class SimpleMovingAverageCommand extends AbstractCommand<ICollection> { private int winSize; public SimpleMovingAverageCommand(String operationName, List<ICollection> selection, IStore store, int windowSize, IContext context) { super(operationName, "Calculates a Simple Moving Average", store, false, false, selection, context); winSize = windowSize; } @UIProperty(name = "Window", category = UIProperty.CATEGORY_CALCULATION, min = 1, max = 20) public int getWindowSize() { return winSize; } @Override protected String getOutputName() { return getContext().getInput("Generate simple moving average", NEW_DATASET_MESSAGE, "Moving average of " + super.getSubjectList()); } public void setWindowSize(int winSize) { this.winSize = winSize; // ok, we now need to update! super.dataChanged(this.getOutputs().iterator().next()); } protected IQuantityCollection<?> getOutputFor(IQuantityCollection<?> input, String outName) { final IQuantityCollection<?> res; if(input.isTemporal()) { @SuppressWarnings("unchecked") Unit<Quantity> units = (Unit<Quantity>) input.getUnits(); res = new TemporalQuantityCollection<Quantity>(outName, this, units); } else { res = new QuantityCollection<>(outName, this, input.getUnits()); } return res; } @Override public void execute() { IQuantityCollection<?> input = (IQuantityCollection<?>) getInputs().get(0); List<ICollection> outputs = new ArrayList<ICollection>(); // ok, generate the new series IQuantityCollection<?> target = getOutputFor(input, getOutputName()); outputs.add(target); // store the output super.addOutput(target); // start adding values. performCalc(outputs); // tell each series that we're a dependent Iterator<ICollection> iter = getInputs().iterator(); while (iter.hasNext()) { ICollection iCollection = iter.next(); iCollection.addDependent(this); } // ok, done List<IStoreItem> res = new ArrayList<IStoreItem>(); res.add(target); getStore().addAll(res); } @Override public 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 unit * @param outputs */ private void performCalc(List<ICollection> outputs) { IQuantityCollection<?> target = (IQuantityCollection<?>) outputs.iterator().next(); // clear out the lists, first Iterator<ICollection> iter = outputs.iterator(); while (iter.hasNext()) { IQuantityCollection<?> qC = (IQuantityCollection<?>) iter.next(); qC.clearQuiet(); } SimpleMovingAverage sma = new SimpleMovingAverage(winSize); @SuppressWarnings("unchecked") IQuantityCollection<Quantity> input = (IQuantityCollection<Quantity>) getInputs().get(0); if(input.isTemporal()) { // use temporal data ITemporalQuantityCollection<?> outT = (ITemporalQuantityCollection<?>) target; ITemporalQuantityCollection<?> inT = (ITemporalQuantityCollection<?>) input; // we need our time data List<Long> times = inT.getTimes(); Iterator<Long> tIter = times.iterator(); for(Measurable<Quantity> quantity : input.getValues()) { sma.newNum(quantity.doubleValue(input.getUnits())); outT.add(tIter.next(), sma.getAvg()); } } else { // ok, plain values for (Measurable<Quantity> quantity : input.getValues()) { sma.newNum(quantity.doubleValue(input.getUnits())); target.add(sma.getAvg()); } } // and fire the update target.fireDataChanged(); } } }