lib/y_petri/simulation/timed.rb in y_petri-2.3.12 vs lib/y_petri/simulation/timed.rb in y_petri-2.4.0

- old
+ new

@@ -142,26 +142,26 @@ # Runs the simulation until the target time. Named argument :final_step has # options :just_before, :just_after and :exact, and tunes the simulation # behavior towards the end of the run. # - # just_before: last step has normal size, simulation stops before or just - # on the target time - # just_after: last step has normal size, simulation stops after or just - # on the target time_step + # stop_before: last step has normal size, simulation stops before or just + # on the target time + # stop_after: last step has normal size, simulation stops after or just + # on the target time_step # exact: simulation stops exactly on the prescribed time, last step # is shortened if necessary # def run_upto( target_time, final_step: :exact ) case final_step - when :before then + when :stop_before then step! while time + step <= target_time when :exact then step! while time + step < target_time step!( target_time - time ) @time = target_time - when :after then + when :stop_after then step! while time < target_time else fail ArgumentError, "Unrecognized :final_step option: #{final_step}" end end @@ -240,10 +240,28 @@ when Range then time_range = settings[:time] @initial_time, @target_time = time_range.begin, time_range.end @time_unit = initial_time.class.one else + # TODO: When using simulation after some time, I found this behavior + # surprising. I wanted to call simulation time: 100, expecting it + # to run until 100 (in the range 0..100). Instead, I see that it wants + # to run from 100 to infinity. While I understand how important it + # is to have a simple way to set the time of a newly constructed + # simulation to some value (for the purposes such as cloning of + # simulation, interpolation of simulations etc. -- actually, there + # is no really stateful net in YPetri at the moment, so Simulation + # class behaves somewhat as a stateful net...), not just me, but + # other users might expect :time argument to set final time with + # initial time being 0. I'm not gonna change it quite yet. + # + # The way to refactor it would be to first introduce "initial_time" + # parameter and make "time" parameter raise an error, and refactor + # the code until the tests pass. Then, to reintroduce "time" + # parameter with the new, more intuitive meaning. Interactive + # users can always modify time later (simulation.time = something). + # @initial_time = settings[:time] @time_unit = initial_time.class.one @target_time = time_unit * Float::INFINITY end else @@ -257,14 +275,89 @@ # Set up a parametrized subclas of the sampler for timed simulation. param_class( { Recorder: Recorder }, with: { simulation: self } ) reset_time! @step = settings[:step] || time_unit @default_sampling = settings[:sampling] || step - @core = if @guarded then - YPetri::Core::Timed.new( simulation: self, method: method, guarded: true ) - else - YPetri::Core::Timed.new( simulation: self, method: method, guarded: false ) - end + if method == :runge_kutta then + # This is a bit irregular for now, but since the core has to behave + # differently (that is, more like a real simulation core), at least + # for the more advanced runge_kutta method, a core under a different + # instance variable will be constructed. + @rk_core = if @guarded then + YPetri::Core::Timed.new( simulation: self, method: method, guarded: true ) + else + YPetri::Core::Timed.new( simulation: self, method: method, guarded: false ) + end + singleton_class.class_exec do + attr_reader :rk_core + delegate :simulation_method, + :firing_vector_tS, + to: :rk_core + + # This method steps the simulation forward by the prescribed step. Simulation uses the core to perform the #step! method. + # + def step! Δt=step() + # Pseudocode would be like this: + + # 1. set_state_and_time_of_core_to_the_current_simulation's_state_and_time + + # 2. explicitly tell the core the code by which to alert the sampler when necessary + # ie. when the state vector of the core progresses sufficiently for it to be + # interesting to the sampler + + # 3. explicitly tell the core the code by which to update the simulation's state, + # and when should it be updated. + # + # (Note: This can be done in several ways. For example, one possibility is to + # update the simulation only after the core is finished computing. Another + # possibility is to have some other criterion to update the simulation more + # often in the course of the core's work. Since this is some sort of sampling + # job again, there is an option of actually delegating it to the sampler, + # which would thus get closer to its role of the interface.) + # + # (Note 2: It is actually more clear what the role of the core should be rather + # than what the simulation's role should be. The core should receive the initial + # instructions, a relatively simple method of what to do, and it should be + # specialized in doing its job fast. Secondly, it should receive the method for + # alerting the superiors: simulation and/or sampler. Thirdly, it should be told + # when to stop.) + + # This should set the state of the rk_core to the marking vector of free + # places (#marking_vector method). + rk_core.marking_of_free_places.reset!( marking_vector ) + sim, rec = self, recorder + rk_core.set_user_alert_closure do |mv_free| # marking vect. of free places + # TODO: This can be done differently. For example, the simulation can hand + # the core the function which, when handed a hash, will update the marking + # vector with the hash. This is actually quite similar, but there is some + # ugliness in it... + sim.m_vector.reset! mv_free.to_hash + sim.increment_time! Δt + Kernel.print '.' + rec.alert! + end + + rk_core.step! Δt + + # TODO: In the above lines, setting rec = recorder and then calling rec!.alert in + # the block is a bit weird. It would be nicer to use recorder.alert!, but maybe + # wise Ruby closure mechanism does not allow it... + end # def step! + end + else + @core = if @guarded then + YPetri::Core::Timed.new( simulation: self, method: method, guarded: true ) + else + YPetri::Core::Timed.new( simulation: self, method: method, guarded: false ) + end + singleton_class.class_exec do + attr_reader :core + delegate :simulation_method, + :step!, + :firing_vector_tS, + to: :core + end + end @recorder = if features_to_record then # we'll have to figure out features ff = case features_to_record when Array then net.State.Features