Code coverage report for code/models/simulation.coffee

Statements: 98.11% (52 / 53)      Branches: 100% (6 / 6)      Functions: 94.74% (18 / 19)      Lines: 97.96% (48 / 49)      Ignored: none     

All files » code/models/ » simulation.coffee
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101  1 683 683 683   683 367   316 266   316 376 376 376 376 376 70   306   316   1   1   1         30 30 30 30   30       30 70 70     138 138     683           70         683       307 683       307           6 6             12       6     28 28 68 28 683 683 307 307  
 
IntegrationFunction = (t, timeStep) ->
  links = @inLinks()
  count = links.length
  nextValue = 0
 
  if count < 1
    return @currentValue
 
  if not @isAccumulator
    @currentValue = 0
 
  _.each links, (link) =>
    sourceNode = link.sourceNode
    inV = sourceNode.previousValue
    outV = @previousValue
    nextValue = link.relation.evaluate(inV, outV) * timeStep
    if @isAccumulator
      @currentValue = @currentValue + nextValue
    else
      @currentValue = @currentValue + (nextValue / count)
 
  @currentValue
 
module.exports = class Simulation
 
  @defaultInitialValue = 50
 
  @defaultReportFunc = (report) ->
    log.info report
 
 
  constructor: (@opts={}) ->
    @nodes       = @opts.nodes      or []
    @duration    = @opts.duration   or 10.0
    @timeStep    = @opts.timeStep   or 0.1
    @reportFunc  = @opts.reportFunc   or Simulation.defaultReportFunc
 
    @decorateNodes() # extend nodes with integration methods
 
 
  decorateNodes: ->
    _.each @nodes, (node) =>
      @initiaLizeValues node
      @addIntegrateMethodTo node
 
  initiaLizeValues: (node) ->
    node.initialValue  ?= Simulation.defaultInitialValue
    node.currentValue  = node.initialValue
 
  nextStep: (node) ->
    node.previousValue = node.currentValue or node.initialValue
 
  addIntegrateMethodTo: (node)->
    # Create a bound method on this node.
    # Put the functionality here rather than in the class "Node".
    # Keep all the logic for integration here in one file for clarity.
    node.integrate = IntegrationFunction.bind(node)
 
 
  # for some integrators, timeIndex might matter
  evaluateNode: (node, t) ->
    node.currentValue = node.integrate(t, @timeStep)
 
  # create an object representation of the current timeStep
  addReportFrame: (time) ->
    nodes = _.map @nodes, (node) ->
      time:  time
      title: node.title
      value: node.currentValue
      initialValue: node.initialValue
    @reportFrames.push
      time: time
      nodes: nodes
 
  # create envelope deata for the report
  report: ->
    steps = @duration / @timeStep
    data =
      steps: steps
      duration: @duration
      timeStep: @timeStep
      nodeNames: _.pluck @nodes, 'title'
      frames: @reportFrames
      endState: _.map @nodes, (n) ->
        title: n.title
        initialValue: n.initialValue
        value: n.currentValue
 
    @reportFunc(data)
 
  run: ->
    time = 0
    @reportFrames = []
    _.each @nodes, (node) => @initiaLizeValues node
    while time < @duration
      _.each @nodes, (node) => @nextStep node  # toggles previous / current val.
      _.each @nodes, (node) => @evaluateNode node, time
      time = time + @timeStep
      @addReportFrame(time)