Introduction

Developer guide on how to develop algorithmic blocks. Only custom data processing blocks can be built at this point.

Overview

How Algorithms work Figure 1

An algorithm usually has 3 types of blocks, i.e

The first (Block 1 & 2 figure 1) and third (Block 4 figure 1) blocks are mandatory without which an algorithm won’t work. Currently the apis are only available to build Data processing/logic blocks.

Once user requests for an algorithm’s output, the request is passed on from the result block to all the way to the datasource blocks (nested algorithms are also supported, in that case the request goes all the way till the nested algorithms datasource blocks.). In response to the request, the data flows from the datasource blocks to the result blocks. The data flows as streams instead of bulk operation i.e. the records are fetched from history only on demand record by record, this is in turn keeps the memory foot print low even while processing large data sets. In case of blocks requiring data to be processed in bulk, caching mechanism can be built into the blocks, but should be done with proper care as it carries risk of affecting the available memory space. Also care should be taken while caching the instances returned by the algorithm. See AnalyticValue

First Steps

All custom data processing blocks in the Niagara Analytics Framework should extend

BAlgorithmBlock or BOutputBlock. The properties that needs to be used as in/out for these blocks should be of type BBlockPin

Sample

BBiMathBlock extends BOutputBlock

BBiMathBlock

  @NiagaraType
  @NiagaraProperty(name = "in1", type = "BBlockPin", defaultValue = "new BBlockPin()", flags = Flags.SUMMARY)  
  @NiagaraProperty(name = "in2", type = "BBlockPin", defaultValue = "new BBlockPin()", flags = Flags.SUMMARY)
  
  public class BBiMathBlock
    extends BOutputBlock
  {
     
  
    //
    // Returns a trend of with the function applied to each row.
    //
    @Override
    public AnalyticTrend getTrend(AnalyticContext cx)
    {
      return new MyTrend(this,cx);
    }
  
    //
    // Performs the function directly on the inputs.
    //
    @Override
    public AnalyticValue getValue(AnalyticContext cx)
    {
      return eval(getInputValue(0,cx),getInputValue(1,cx));
    }
  
 
    //
    // Performs the function for both getValue and getTrend requests.
    //
    private AnalyticValue eval(AnalyticValue in1, AnalyticValue in2)
    {
      AnalyticValue ret = in1;
      double result = in1.toNumeric() + in2.toNumeric();
      int sts = in1.getStatus() | in2.getStatus();
      if (Double.isNaN(result) || Double.isInfinite(result))
        sts = sts | STATUS_NULL;
      if (ret instanceof AnalyticNumeric)
        ((AnalyticNumeric)ret).setValue(result);
      else
        ret.setValue(BDouble.make(result));
      ret.setStatus(sts);
      return ret;
    } 
  
    //
    // Calls eval for each interval.
    //
    private class MyTrend extends BlockTrend 
    {
      public MyTrend(AlgorithmBlock block, AnalyticContext cx)
      {
        super(block,cx);
      }
      protected AnalyticValue getNext()
      {
        if (!advance()) return null;
        return eval(getValue(0),getValue(1));
      }
    }
   
  }