test/simulation_test.rb in y_petri-2.0.15 vs test/simulation_test.rb in y_petri-2.1.3

- old
+ new

@@ -1,281 +1,355 @@ #! /usr/bin/ruby -# -*- coding: utf-8 -*- +# encoding: utf-8 require 'minitest/spec' require 'minitest/autorun' require_relative '../lib/y_petri' # tested component itself # require 'y_petri' # require 'sy' -describe ::YPetri::Simulation do +describe YPetri::Simulation do before do - @pç = pç = Class.new( ::YPetri::Place ) - @tç = tç = Class.new( ::YPetri::Transition ) - @nç = nç = Class.new( ::YPetri::Net ) - [ @pç, @tç, @nç ].each { |klass| - klass.namespace! - klass.class_exec { - private - define_method :Place do pç end - define_method :Transition do tç end - define_method :Net do nç end - } - } - @p1 = @pç.new name: "P1", default_marking: 1 - @p2 = @pç.new name: "P2", default_marking: 2 - @p3 = @pç.new name: "P3", default_marking: 3 - @p4 = @pç.new name: "P4", default_marking: 4 - @p5 = @pç.new name: "P5", default_marking: 5 - @t1 = @tç.new name: "T1", - s: { @p1 => -1, @p2 => -1, @p4 => 1 }, - rate: 0.1 - @t2 = @tç.new name: "T2", - s: { @p1 => -1, @p3 => 1 }, - rate: -> a { a * 0.5 } - @t3 = @tç.new name: "T3", - s: { @p1 => -1, @p2 => -1, @p4 => 1 }, - domain: @p3, - rate: -> a { a * 0.5 } - @net = @nç.new << @p1 << @p2 << @p3 << @p4 << @p5 - @net.include_transition! @t1 - @net.include_transition! @t2 - @net << @t3 - @s = YPetri::Simulation.new net: @net, - marking_clamps: { @p1 => 2.0, @p5 => 2.0 }, - initial_marking: { @p2 => @p2.default_marking, - @p3 => @p3.default_marking, - @p4 => @p4.default_marking } + @w = YPetri::World.new end - it "exposes the net" do - @s.net.must_equal @net - @s.net.places.size.must_equal 5 - @s.net.transitions.size.must_equal 3 - assert @net.include? @t1 - assert @s.net.include? @t1 - assert @net.include? @t2 - assert @s.net.include? @t2 - assert @net.include? @t3 - assert @s.net.include? @t3 - @s.net.transitions.size.must_equal 3 + it "should allow for creation of an empty simulation" do + net = @w.Net.new + sim = net.simulation + sim.pp.must_equal [] + sim.pp( *[] ).must_equal [] + sim.tt.must_equal( [] ) + sim.tt( *[] ).must_equal [] end - it "exposes Petri net places" do - @s.places.must_equal [ @p1, @p2, @p3, @p4, @p5 ] - @s.pp.must_equal [ :P1, :P2, :P3, :P4, :P5 ] - @s.places( :pp ).must_equal( { @p1 => :P1, @p2 => :P2, @p3 => :P3, - @p4 => :P4, @p5 => :P5 } ) - @s.pp( :pp ).must_equal( { P1: :P1, P2: :P2, P3: :P3, P4: :P4, P5: :P5 } ) - end + describe "simulation setup" do + before do + @p = @w.Place.new name: :A, default_marking: 1 + @q = @w.Place.new name: :B, default_marking: 2 + @net = @w.Net.of @p, @q + end - it "exposes Petri net transitions" do - @s.transitions.must_equal [ @t1, @t2, @t3 ] - @s.tt.must_equal [ :T1, :T2, :T3 ] - @s.transitions( :tt ).must_equal( { @t1 => :T1, @t2 => :T2, @t3 => :T3 } ) - @s.tt( :tt ).must_equal( { T1: :T1, T2: :T2, T3: :T3 } ) - end + it "should allow to set up a simplistic simulation instance" do + @net.simulation + @net.simulation marking_clamps: { @q => 42 } # one clamp + @net.simulation initial_marking: { @p => 42, @q => 43 } + @net.simulation marking_clamps: { @p => 42 }, initial_marking: { @q => 43 } + @net.simulation initial_marking: { A: 42 } + end - it "exposes place clamps" do - @s.clamped_places( :place_clamps ).must_equal( { @p1 => 2, @p5 => 2 } ) - @s.clamped_pp( :place_clamps ).must_equal( { P1: 2, P5: 2 } ) - end + it "should fail with malformed arguments" do + -> { @net.simulation use_default_marking: false }.must_raise TypeError + -> { @net.simulation initial_marking: { Foo: 1 } }.must_raise NameError + end - it "presents free places" do - @s.free_places.must_equal [ @p2, @p3, @p4 ] - @s.free_pp.must_equal [ :P2, :P3, :P4 ] - @s.free_places( :free_pp ) - .must_equal( { @p2 => :P2, @p3 => :P3, @p4 => :P4 } ) - @s.free_pp( :free_pp ) - .must_equal( { P2: :P2, P3: :P3, P4: :P4 } ) - end + describe "place representation aspects" do + before do + @s = YPetri::Simulation.new( net: @net, + initial_marking: { A: 42 }, + marking_clamps: { B: 43 } ) + end - it "presents clamped places" do - @s.clamped_places.must_equal [ @p1, @p5 ] - @s.clamped_pp.must_equal [ :P1, :P5 ] - @s.clamped_places( :clamped_pp ).must_equal( { @p1 => :P1, @p5 => :P5 } ) - @s.clamped_pp( :clamped_pp ).must_equal( { P1: :P1, P5: :P5 } ) - end + it "should have elements/access" do + @s.send( :place, :A ) + .must_be_kind_of YPetri::Simulation::PlaceRepresentation + @s.send( :place, :B ) + .must_be_kind_of YPetri::Simulation::PlaceRepresentation + @s.net.places.names.must_equal [:A, :B] + @s.pn.must_equal [:A, :B] + @s.send( :places ).free.size.must_equal 1 + @s.send( :free_places ).names.must_equal [:A] + @s.send( :places ).clamped.size.must_equal 1 + @s.send( :clamped_places ).names.must_equal [:B] + @s.send( :places, [:A] ).map( &:source ).must_equal [@p] + @s.send( :transitions, [] ).must_equal [] + @s.send( :places, [:A] ).map( &:source ).must_equal [@p] + @s.send( :places, [] ).must_equal [] + end - it "exposes initial marking" do - @s.free_places( :im ).must_equal( { @p2 => 2, @p3 => 3, @p4 => 4 } ) - @s.free_pp( :im ).must_equal( { P2: 2, P3: 3, P4: 4 } ) - @s.im.must_equal [ 2, 3, 4 ] - @s.im_vector.must_equal Matrix[[2], [3], [4]] - @s.im_vector.must_equal @s.iᴍ - end + describe "marking vector representation" do + it "should work" do + @s.instance_variable_get( :@m_vector ).must_equal @s.m_vector + @s.m_vector.must_be_kind_of YPetri::Simulation::MarkingVector + @s.m_vector.size.must_equal 2 + @s.m_vector.to_a.must_equal [42, 43] + @s.m.must_equal [42, 43] + @s.marking.must_equal [42] + @s.marking_clamps.keys_to_names.must_equal( { B: 43 } ) + end + end + end # describe simulation step - it "exposes marking (simulation state)" do - @s.m.must_equal [2, 3, 4] # (we're after reset) - @s.free_places( :m ).must_equal( { @p2 => 2, @p3 => 3, @p4 => 4 } ) - @s.free_pp( :m ).must_equal( { P2: 2, P3: 3, P4: 4 } ) - @s.ᴍ.must_equal Matrix[[2], [3], [4]] - end + describe "transition representation aspects" do + before do + @ts = @w.Transition.new name: "T_ts", codomain: :A, action: -> { 1 } + @tS = @w.Transition.new name: "T_tS", s: { B: -1, A: 1 }, action: proc { 1 } + @Ts = @w.Transition.new name: "T_Ts", codomain: :A, rate: -> { 1 } + @TS = @w.Transition.new name: "T_TS", s: { B: -1, A: 1 }, rate: proc { 1 } + end - it "separately exposes marking of clamped places" do - @s.m_clamped.must_equal [ 2, 2 ] - @s.clamped_places( :m_clamped ).must_equal( { @p1 => 2, @p5 => 2 } ) - @s.clamped_pp( :m_clamped ).must_equal( { P1: 2, P5: 2 } ) - @s.ᴍ_clamped.must_equal Matrix[[2], [2]] - end + it "should be what intended" do + @ts.type.must_equal :ts + @ts.domain.must_equal [] + @ts.codomain.must_equal [@p] + @tS.type.must_equal :tS + @tS.domain.must_equal [@q] # inferred + @tS.codomain.must_equal [@q, @p] + @Ts.type.must_equal :Ts + @Ts.domain.must_equal [] + @Ts.codomain.must_equal [@p] + @TS.type.must_equal :TS + @TS.domain.must_equal [@q] # inferred + @TS.codomain.must_equal [@q, @p] + end - it "exposes marking of all places (with capitalized M)" do - @s.marking.must_equal [ 2, 2, 3, 4, 2 ] - @s.places( :marking ) - .must_equal( { @p1 => 2, @p2 => 2, @p3 => 3, @p4 => 4, @p5 => 2 } ) - @s.pp( :marking ).must_equal( { P1: 2, P2: 2, P3: 3, P4: 4, P5: 2 } ) - @s.marking_vector.must_equal Matrix[[2], [2], [3], [4], [2]] - end + describe "ts transition" do + before do + @net = @w.Net.of @p, @q, @ts + end - it "has #S_for / #stoichiometry_matrix_for" do - assert_equal Matrix.empty(3, 0), @s.S_for( [] ) - assert_equal Matrix[[-1], [0], [1]], @s.S_for( [@t1] ) - x = Matrix[[-1, -1], [0, 0], [1, 1]] - x.must_equal @s.S_for( [@t1, @t3] ) - x.must_equal( @s.S_for( [@t1, @t3] ) ) - @s.stoichiometry_matrix_for( [] ).must_equal Matrix.empty( 5, 0 ) - end + describe "no clamps" do + before do + @sim = @net.simulation net: @net + end - it "has stoichiometry matrix for 3. tS transitions" do - @s.S_tS.must_equal Matrix.empty( 3, 0 ) - end + it "should behave" do + @sim.tt.size.must_equal 1 + @ts.codomain.names.must_equal [:A] + @sim.ts_tt.first.codomain.names.must_equal [:A] + @ts.domain.names.must_equal [] + @sim.ts_tt.first.domain.names.must_equal [] + @sim.timed?.must_equal false + @sim.m.must_equal [1, 2] + @sim.pm.must_equal( { A: 1, B: 2 } ) + @sim.recording.must_equal( { 0 => [1, 2]} ) + @sim.simulation_method.must_equal :pseudo_euler + @sim.core.must_be_kind_of YPetri::Core + @sim.ts_tt.first.domain.must_equal [] + @sim.send( :ts_transitions ).first.domain_access_code.must_equal '' + λ = @sim.send( :transitions ).ts.first.delta_closure + λ.arity.must_equal 0 + λ.call.must_equal 1 + cc = @sim.send( :transitions ).ts.delta_closures + cc.map( &:call ).must_equal [1] + cl = @sim.send( :transitions ).ts.delta_closure + cl.call.must_equal Matrix[ [1], [0] ] + @sim.step! + @sim.pm.must_equal( { A: 2, B: 2 } ) # marking of A goes up by 1 + @sim.recording.must_equal( { 0 => [1, 2], 1 => [2, 2] } ) + end + end - it "has stoichiometry matrix for 4. Sr transitions" do - @s.S_TSr.must_equal Matrix.empty( 3, 0 ) - end + describe "with clamps" do + before do + @sim = @net.simulation marking_clamps: { B: 42 } + end - it "has stoichiometry matrix for 6. SR transitions" do - @s.S_SR.must_equal Matrix[[-1, 0, -1], [0, 1, 0], [1, 0, 1]] - @s.S.must_equal @s.S_SR - end + it "should behave" do + @sim.recording.must_equal( { 0 => [1] } ) + @sim.step! + @sim.recording.must_equal( { 0 => [1], 1 => [2] } ) + end + end + end # ts transition - it "presents 1. ts" do - assert_equal [], @s.ts_transitions - assert_equal( {}, @s.ts_transitions( :ts_transitions ) ) - assert_equal [], @s.ts_tt - assert_equal( {}, @s.ts_tt( :ts_tt ) ) - end + describe "tS transition" do + before do + @net = @w.Net.of @p, @q, @tS + end - it "presents 2. tS transitions" do - assert_equal [], @s.tS_transitions - assert_equal( {}, @s.tS_transitions( :tS_transitions ) ) - assert_equal [], @s.tS_tt - assert_equal( {}, @s.tS_tt( :tS_tt ) ) - end + describe "no clamps" do + before do + @sim = @net.simulation net: @net + end - it "presents 3. Tsr transitions" do - assert_equal [], @s.Tsr_transitions - assert_equal( {}, @s.Tsr_transitions( :Tsr_transitions ) ) - assert_equal [], @s.Tsr_tt - assert_equal( {}, @s.Tsr_tt( :Tsr_tt ) ) - end + it "should behave" do + @sim.recording.must_equal( { 0 => [1, 2] } ) + @sim.step! + @sim.recording.must_equal( { 0 => [1, 2], 1 => [2, 1] } ) + end + end - it "presents 4. TSr transitions" do - assert_equal [], @s.TSr_transitions - assert_equal( {}, @s.TSr_transitions( :TSr_tt ) ) - assert_equal [], @s.TSr_tt - assert_equal( {}, @s.TSr_tt( :TSr_tt ) ) - end + describe "with clamps" do + before do + @sim = @net.simulation marking_clamps: { B: 43 } + end - it "presents 5. sR transitions" do - assert_equal [], @s.sR_transitions - assert_equal( {}, @s.sR_transitions( :sR_transitions ) ) - assert_equal [], @s.sR_tt - assert_equal( {}, @s.sR_tt( :sR_tt ) ) - end + it "should behave" do + @sim.recording.must_equal( { 0 => [1] } ) + 3.times do @sim.step! end + @sim.recording.must_equal( { 0 => [1], 1 => [2], 2 => [3], 3 => [4] } ) + end + end + end # tS transition - it "presents SR transitions" do - assert_equal [@t1, @t2, @t3], @s.SR_transitions - assert_equal( { @t1 => :T1, @t2 => :T2, @t3 => :T3 }, - @s.SR_transitions( :SR_tt ) ) - assert_equal [:T1, :T2, :T3], @s.SR_tt - assert_equal( { T1: :T1, T2: :T2, T3: :T3 }, @s.SR_tt( :SR_tt ) ) - end + describe "Ts transition" do + before do + @net = @w.Net.of @p, @q, @Ts + end - it "presents A transitions" do - assert_equal [], @s.A_transitions - assert_equal( {}, @s.A_transitions( :A_tt ) ) - assert_equal [], @s.A_tt - assert_equal( {}, @s.A_tt( :A_tt ) ) - end + describe "no clamps" do + before do + @sim = @net.simulation sampling: 1 + end - it "presents S transitions" do - assert_equal [@t1, @t2, @t3], @s.S_transitions - assert_equal [:T1, :T2, :T3], @s.S_tt - assert_equal( { T1: :T1, T2: :T2, T3: :T3 }, @s.S_tt( :S_tt ) ) - end + it "should behave" do + @sim.timed?.must_equal true + @sim.simulation_method.must_equal :pseudo_euler + @sim.Ts_tt.size.must_equal 1 + @sim.send( :transitions ).Ts.first.gradient_closure.call.must_equal 1 + @sim.Ts_tt.first.codomain.names.must_equal [:A] + @sim.recording.must_equal( { 0.0 => [1, 2] } ) + @sim.step! 1 + @sim.recording.must_equal( { 0.0 => [1, 2], 1.0 => [2, 2] } ) + end + end - it "presents s transitions" do - assert_equal [], @s.s_transitions - assert_equal [], @s.s_tt - assert_equal( {}, @s.s_tt( :s_tt ) ) - end + describe "with clamps" do + before do + @sim = @net.simulation sampling: 1, marking_clamps: { B: 43 } + end - it "presents R transitions" do - assert_equal [@t1, @t2, @t3], @s.R_transitions - assert_equal [:T1, :T2, :T3], @s.R_tt - assert_equal( { T1: :T1, T2: :T2, T3: :T3 }, @s.R_tt( :R_tt ) ) - end + it "should behave" do + @sim.recording.must_equal( { 0.0 => [1] } ) + 3.times do @sim.step! 1 end + @sim.recording.must_equal( { 0.0 => [1], 1.0 => [2], 2.0 => [3], 3.0 => [4] } ) + end + end + end # Ts transition - it "presents r transitions" do - assert_equal [], @s.r_transitions - assert_equal [], @s.r_tt - end + describe "TS transition" do + before do + @net = @w.Net.of @p, @q, @TS + end - it "1. handles ts transitions" do - @s.Δ_closures_for_tsa.must_equal [] - @s.Δ_if_tsa_fire_once.must_equal Matrix.zero( @s.free_pp.size, 1 ) - end + describe "no clamps" do + before do + @sim = @net.simulation sampling: 1 + end - it "2. handles Tsr transitions" do - @s.Δ_closures_for_Tsr.must_equal [] - @s.Δ_Tsr( 1.0 ).must_equal Matrix.zero( @s.free_pp.size, 1 ) - end + it "should behave" do + @sim.recording.must_be_kind_of YPetri::Net::State::Features::Dataset + @sim.recording.must_equal @net.State.marking.new_dataset.update( 0.0 => [1, 2] ) + @sim.recording.must_equal( { 0.0 => [1, 2] } ) + @sim.step! 1 + @sim.recording.must_equal( { 0.0 => [1, 2], 1.0 => [2, 1] } ) + end + end - it "3. handles tS transitions" do - @s.action_closures_for_tS.must_equal [] - @s.action_vector_for_tS.must_equal Matrix.column_vector( [] ) - @s.ᴀ_t.must_equal Matrix.column_vector( [] ) - @s.Δ_if_tS_fire_once.must_equal Matrix.zero( @s.free_pp.size, 1 ) - end + describe "with clamps" do + before do + @sim = @net.simulation sampling: 1, marking_clamps: { B: 43 } + end - it "4. handles TSr transitions" do - @s.action_closures_for_TSr.must_equal [] - @s.action_closures_for_Tr.must_equal [] - @s.action_vector_for_TSr( 1.0 ).must_equal Matrix.column_vector( [] ) - @s.action_vector_for_Tr( 1.0 ).must_equal Matrix.column_vector( [] ) - @s.Δ_TSr( 1.0 ).must_equal Matrix.zero( @s.free_pp.size, 1 ) + it "should behave" do + @sim.recording.must_equal( { 0.0 => [1] } ) + 3.times do @sim.step! end + @sim.recording.must_equal( { 0.0 => [1], 1.0 => [2], 2.0 => [3], 3.0 => [4] } ) + end + end + end # TS transition + end # transition representation aspects end +end - it "5. handles sR transitions" do - assert_equal [], @s.rate_closures_for_sR - assert_equal [], @s.rate_closures_for_s - # @s.gradient_for_sR.must_equal Matrix.zero( @s.free_pp.size, 1 ) - @s.Δ_sR( 1.0 ).must_equal Matrix.zero( @s.free_pp.size, 1 ) + +describe YPetri::Simulation do + before do + self.class.class_exec { include YPetri } + U = Place m!: 2.5 + V = Place m!: 2.5 + Uplus = Transition codomain: :U do 1 end # s transition + U2V = Transition s: { U: -1, V: 1 } # S transition + set_ssc :Timeless, YPetri::Simulation::DEFAULT_SETTINGS.call + new_simulation ssc: :Timeless + 5.times do simulation.step! end end - it "6. handles stoichiometric transitions with rate" do - @s.rate_closures_for_SR.size.must_equal 3 - @s.rate_closures_for_S.size.must_equal 3 - @s.rate_closures.size.must_equal 3 - @s.flux_vector_for_SR.must_equal Matrix.column_vector( [ 0.4, 1.0, 1.5 ] ) - @s.φ_for_SR.must_equal @s.flux_vector - @s.SR_tt( :φ_for_SR ).must_equal( { T1: 0.4, T2: 1.0, T3: 1.5 } ) - @s.first_order_action_vector_for_SR( 1 ) - .must_equal Matrix.column_vector [ 0.4, 1.0, 1.5 ] - @s.SR_tt( :first_order_action_for_SR, 1 ).must_equal( T1: 0.4, T2: 1.0, T3: 1.5 ) - @s.Δ_SR( 1 ).must_equal Matrix[[-1.9], [1.0], [1.9]] - @s.free_pp( :Δ_SR, 1 ).must_equal( { P2: -1.9, P3: 1.0, P4: 1.9 } ) + it "should behave" do + simulation.tap do |s| + assert ! s.timed? + s.core.must_be_kind_of YPetri::Core::Timeless::PseudoEuler + s.recording.size.must_equal 6 + s.recording.events.must_equal [0, 1, 2, 3, 4, 5] + s.recording.reconstruct( event: 2 ).pm.must_equal( { U: 2.5, V: 4.5 } ) + s.recording.marking.slice( 2..4 ).series + .must_equal [[2.5, 2.5, 2.5], [4.5, 5.5, 6.5]] + s.recording.marking( slice: 2..4 ) + .must_equal( { 2 => [2.5, 4.5], + 3 => [2.5, 5.5], + 4 => [2.5, 6.5] } ) + s.recording.firing_series( slice: 1..2 ) + .must_equal [[1, 1]] + s.recording.firing_series( transitions: [:U2V] ) + .must_equal [[1, 1, 1, 1, 1, 1]] + s.recording.delta_series( places: [:U], transitions: [:Uplus] ) + .must_equal [[1.0, 1.0, 1.0, 1.0, 1.0, 1.0]] + tmp = s.recording.features( marking: [:U], firing: [:U2V] ) + tmp.delete( :features ) + .must_equal( { marking: [:U], firing: [:U2V], + delta: { places: [], transitions: [] } } ) + tmp.must_equal( { 0 => [2.5, 1], 1 => [2.5, 1], 2 => [2.5, 1], + 3 => [2.5, 1], 4 => [2.5, 1], 5 => [2.5, 1] } ) + end end +end - it "presents sparse stoichiometry vectors for its transitions" do - @s.sparse_σ( @t1 ).must_equal Matrix.cv( [-1, 0, 1] ) - @s.sparse_stoichiometry_vector( @t1 ) - .must_equal Matrix.cv( [-1, -1, 0, 1, 0] ) + +describe YPetri::Simulation::Timed do + before do + skip + self.class.class_exec { include YPetri } + A = Place m!: 0.5 + B = Place m!: 0.5 + A_pump = T s: { A: -1 } do 0.005 end + B_decay = Transition s: { B: -1 }, rate: 0.05 + run! end - it "presents correspondence matrices free, clamped => all places" do - @s.F2A.must_equal Matrix[[0, 0, 0], [1, 0, 0], [0, 1, 0], - [0, 0, 1], [0, 0, 0]] - @s.C2A.must_equal Matrix[[1, 0], [0, 0], [0, 0], [0, 0], [0, 1]] + it "should behave" do + places.map( &:marking ).must_equal [0.5, 0.5] # marking unaffected + simulation.tap do |s| + s.settings.must_equal( { method: :pseudo_euler, guarded: false, + step: 0.1, sampling: 5, time: 0..60 } ) + assert s.recording.to_csv.start_with?( "0.0,0.5,0.5\n" + + "5.0,0.475,0.38916\n" + + "10.0,0.45,0.30289\n" + + "15.0,0.425,0.23574\n" + + "20.0,0.4,0.18348\n" + + "25.0,0.375,0.1428\n" ) + assert s.recording.to_csv.end_with?( "60.0,0.2,0.02471" ) + s.recording.events.must_equal [ 0.0, 5.0, 10.0, 15.0, 20.0, + 25.0, 30.0, 35.0, 40.0, 45.0, + 50.0, 55.0, 60.0 ] + s.recording.values_at( 5, 10 ) + .must_equal [ [0.475, 0.38916], [0.45, 0.30289] ] + s.recording.slice( 2..12 ) + .must_equal( { 5.0 => [0.475, 0.38916], 10.0=>[0.45, 0.30289] } ) + s.recording.net + .must_equal net + s.recording.features + .must_equal net.State.marking( [:A, :B] ) + net.State.Features.State + .must_equal net.State + s.recording.State + .must_equal net.State + s.recording.series( marking: [:A] ) + .must_equal [ [ 0.5, 0.475, 0.45, 0.425, 0.4, 0.375, 0.35, 0.325, + 0.3, 0.275, 0.25, 0.225, 0.2 ] ] + s.recording.firing.series + .must_equal [] + s.recording.firing + .must_equal( [*0..12].map { |n| n * 5.0 } >> [[]] * 13 ) + # It is obvious why this fails: Given delta feature is not in the + # recording. It has to be generated, and since the net is timed, + # it also requires Δt to be generated. + + # I'll comment it out for a while to see what the plot is doing. +=begin + s.recording.delta( [:A], transitions: [:A_pump] ) + .must_equal [ [ -0.0005 ] * 13 ] +=end + plot_state + sleep 5 + end end end