lib/y_petri/simulation/timed.rb in y_petri-2.0.15 vs lib/y_petri/simulation/timed.rb in y_petri-2.1.3
- old
+ new
@@ -1,284 +1,184 @@
-# -*- coding: utf-8 -*-
+# encoding: utf-8
-# A mixin for timed simulations.
-#
-module YPetri::Simulation::Timed
- SAMPLING_TIME_DECIMAL_PLACES = 5
- SIMULATION_METHODS =
- [
- [ :Euler ],
- [ :Euler_with_timeless_transitions_firing_after_each_time_tick, :quasi_Euler ],
- [ :Euler_with_timeless_transitions_firing_after_each_step, :pseudo_Euler ]
- ]
- DEFAULT_SIMULATION_METHOD = :Euler
-
- # ==== Exposing time-related global simulation settings
-
- # Simulation parameter: :initial_time.
+class YPetri::Simulation
+ # A mixin for timed simulations, used by an +#extend+ call during init.
#
- attr_reader :initial_time
-
- # Simulation parameter: :step_size
- #
- attr_accessor :step_size
-
- # Simulation parameter: :sampling_period
- #
- attr_accessor :sampling_period
-
- # Simulation parameter: :target_time
- #
- attr_accessor :target_time
-
- # Reads the sampling rate.
- #
- def sampling_rate; 1 / sampling_period end
-
- # Reads the time range (initial_time..target_time) of the simulation.
- #
- def time_range; initial_time..target_time end
-
- # Reads simulation settings
- # (:step_size, :sampling_period and :time_range).
- #
- def settings
- { step: step_size,
- sampling: sampling_period,
- time: time_range }
- end
- alias simulation_settings settings
+ module Timed
+ require_relative 'timed/recorder'
- # Exposing time.
- #
- attr_reader :time
- alias ᴛ time
+ DEFAULT_SETTINGS = -> do { step: 0.1, sampling: 5, time: 0..60 } end
- # def stop; end # LATER
- # def continue; end # LATER
+ def self.included receiver
+ receiver.Recording.class_exec { prepend Recording }
+ end
- # # Makes one Gillespie step
- # def gillespie_step
- # t, dt = gillespie_select( @net.transitions )
- # @marking_vector += t.project( @marking_vector, @step_size )
- # @time += dt
- # note_state_change
- # end
+ # True for timed simulations.
+ #
+ def timed?
+ true
+ end
- # # Return projection of Δᴍ by mysode-ing the interior.
- # def project_mysode_interior( Δt )
- # # So far, no interior
- # # the internals of this method were already heavily obsolete
- # # they can be seen in previous versions, if needed
- # # so now, I just take use of the Δ_Euler_free
- # Δ_Euler_free
- # end
+ attr_reader :time,
+ :time_unit,
+ :initial_time,
+ :target_time,
+ :step,
+ :default_sampling
- # Allows to explore the system at different state / time. Creates a double,
- # which is set to the required state / time. In addition to the parent class,
- # this version alseo sets time.
- #
- def at( time: ᴛ, **oo )
- super( **oo ).tap { |duplicate| duplicate.send :set_time, time }
- end
+ alias starting_time initial_time
+ alias ending_time target_time
- # Near alias for #run!, checks against infinite run.
- #
- def run( until_time=target_time, final_step: :exact )
- fail "Target time equals infinity!" if target_time = Float::INFINITY
- run! until_time, final_step: final_step
- end
+ delegate :flux_vector_TS,
+ :gradient_TS,
+ :gradient_Ts,
+ :gradient,
+ :flux_vector,
+ to: :core
- # Near alias for #run_until, uses @target_time as :until_time by default.
- #
- def run!( until_time=target_time, final_step: :exact )
- run_until until_time, final_step: final_step
- end
+ delegate :sampling, to: :recorder
- # Runs the simulation until the target time, using step! method. The second
- # optional parameter tunes the behavior towards the end of the run, with
- # alternatives :just_before, :just_after and :exact (default).
- #
- # just_before: all steps have normal size, simulation stops
- # before or just on the target time
- # just_after: all steps have normal size, simulation stops
- # after or just on the target time_step
- # exact: simulation stops exactly on the prescribed time,
- # to make this possible last step is shortened if necessary
- #
- def run_until( target_time, final_step: :exact )
- case final_step
- when :before then # step until on or just before the target
- step! while @time + @step_size <= target_time
- when :exact then # simulate to exact time
- step! while @time + @step_size < target_time
- step!( target_time - @time ) # make a short last step as required
- @time = target_time # to get exactly on the prescribed time
- when :after then # step until on or after target
- step! while @time < target_time
- else
- fail ArgumentError, "Unrecognized :final_step option: #{final_step}"
+ # Reads the time range (initial_time..target_time) of the simulation.
+ #
+ def time_range
+ initial_time..target_time
end
- end
- # Scalar field gradient for free places.
- #
- def gradient_for_free_places
- g_sR = gradient_for_sR
- if g_sR then
- S_SR() * flux_vector_for_SR + g_sR
- else
- S_SR() * flux_vector_for_SR
+ # Returnst the settings pertaining to the Timed aspect of the simulation,
+ # that is, +:step+, +:sampling+ and +:time+.
+ #
+ def settings all=false
+ super.update( step: step,
+ sampling: sampling,
+ time: time_range )
end
- end
- # Gradient for free places as a hash { place_name: ∂ / ∂ᴛ }.
- #
- def ∂
- free_places :gradient_for_free_places
- end
+ # Same as +#run!+, but guards against run upto infinity.
+ #
+ def run( upto: target_time, final_step: :exact )
+ fail "Upto time equals infinity!" if upto == Float::INFINITY
+ run!( upto: upto, final_step: final_step )
+ end
- # Scalar field gradient for all places.
- #
- def gradient_for_all_places
- F2A() * gradient_for_free_places
- end
- alias gradient gradient_for_all_places
+ # Near alias for +#run_upto+. Accepts +:upto+ named argument, using
+ # @target_time attribute as a default. The second optional argument,
+ # +:final_step+, has the same options as in +#run_upto+ method.
+ #
+ def run!( upto: target_time, final_step: :exact )
+ run_upto( upto, final_step: final_step )
+ end
- # Δ state of free places that would happen by a single Euler step Δt.
- #
- def Δ_Euler_for_free_places( Δt=step_size )
- # Here, ∂ represents all R transitions, to which TSr and Tsr are added:
- delta_free = gradient_for_free_places * Δt
- delta_free + Δ_TSr( Δt ) + Δ_Tsr( Δt )
- end
- alias Δ_euler_for_free_places Δ_Euler_for_free_places
- alias ΔE Δ_Euler_for_free_places
+ # 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
+ # 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
+ 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
+ step! while time < target_time
+ else
+ fail ArgumentError, "Unrecognized :final_step option: #{final_step}"
+ end
+ end
- # Δ state of all places that would happen by a single Euler step Δt.
- #
- def Δ_Euler_for_all_places( Δt=step_size )
- F2A() * ΔE( Δt )
- end
- alias Δ_euler_for_all_places Δ_Euler_for_all_places
- alias Δ_Euler Δ_Euler_for_all_places
+ # String representation of this timed simulation.
+ #
+ def to_s
+ "#<Simulation: time: %s, pp: %s, tt: %s, oid: %s>" %
+ [ time, pp.size, tt.size, object_id ]
+ end
- # Makes one Euler step with T transitions. Timeless transitions are not
- # affected.
- #
- def Euler_step!( Δt=@step_size ) # implicit Euler method
- delta = Δ_Euler_for_free_places( Δt )
- if guarded? then
- guard_Δ! delta
- update_marking! delta
- else
- update_marking! delta
+ # Increments the simulation's time and alerts the recorder.
+ #
+ def increment_time! Δt=step
+ @time += Δt
+ recorder.alert
end
- update_time! Δt
- end
- alias euler_step! Euler_step!
- # Fires timeless transitions once. Time and timed transitions are not
- # affected.
- #
- def timeless_transitions_all_fire!
- try "to update marking" do
- update_marking!( note( "Δ state if tS transitions fire once",
- is: Δ_if_tS_fire_once ) +
- note( "Δ state if tsa transitions fire once",
- is: Δ_if_tsa_fire_once ) )
+ # Resets the timed simulation.
+ #
+ def reset!
+ @time = initial_time || time_unit * 0
+ super
end
- try "to fire the assignment transitions" do
- assignment_transitions_all_fire!
+
+ # Customized dup method that allows to modify the attributes of
+ # the duplicate upon creation.
+ #
+ def dup time: time, **nn
+ super( **nn ).tap { |i| i.reset_time! time }
end
- end
- alias t_all_fire! timeless_transitions_all_fire!
+ alias at dup
- # At the moment, near alias of #euler_step!
- #
- def step! Δt=step_size
- case @method
- when :Euler then
- Euler_step! Δt
- note_state_change!
- when :Euler_with_timeless_transitions_firing_after_each_step,
- :pseudo_Euler then
- Euler_step! Δt
- timeless_transitions_all_fire!
- note_state_change!
- when :Euler_with_timeless_transitions_firing_after_each_time_tick,
- :quasi_Euler then
- raise # FIXME: quasi_Euler doesn't work yet
- Euler_step! Δt
- # if time tick has elapsed, call #timeless_transitions_all_fire!
- note_state_change!
- else
- raise "Unrecognized simulation method: #@method !!!"
+ # Returns the zero gradient. Optionally, places can be specified, for which
+ # the zero vector is returned.
+ #
+ def zero_gradient places: nil
+ return zero_gradient places: places() if places.nil?
+ places.map { |id|
+ p = place( id )
+ ( p.free? ? p.initial_marking : p.clamp ) * 0 / time_unit
+ }.to_column_vector
end
- return self
- end
+ alias zero_∇ zero_gradient
- # Produces the inspect string for this timed simulation.
- #
- def inspect
- "#<Simulation: Time: #{time}, #{pp.size} places, #{tt.size} " +
- "transitions, object id: #{object_id}>"
- end
+ private
- # Produces a string brief
- def to_s # :nodoc:
- "Simulation[T: #{time}, pp: #{pp.size}, tt: #{tt.size}]"
- end
+ # Initialization subroutine for timed simulations. Expects named arguments
+ # +:time+ (alias +:time_range+), meaning the simulation time range (a Range
+ # of initial_time..target_time), +:step+, meaning time step of the
+ # simulation, and +:sampling+, meaning sampling period of the simulation.
+ #
+ # Initializes the time-related attributes @initial_time, @target_time,
+ # @time_unit and @time (via +#reset_time!+ call). Also sets up the
+ # parametrized subclasses +@Core+ and +@Recorder+, and initializes the
+ # +@recorder+ attribute.
+ #
+ def init **settings
+ if settings.has? :time, syn!: :time_range then # time range given
+ time_range = settings[:time]
+ @initial_time, @target_time = time_range.begin, time_range.end
+ @time_unit = target_time / target_time.to_f
+ else
+ anything = settings[:step] || settings[:sampling]
+ msg = "The simulation is timed, but the constructor lacks any of the " +
+ "time-related arguments: :time, :step, or :sampling!"
+ fail ArgumentError, msg unless anything
+ @time_unit = anything / anything.to_f
+ @initial_time, @target_time = time_unit * 0, time_unit * Float::INFINITY
+ end
+ init_core_and_recorder_subclasses
+ reset_time!
+ @step = settings[:step] || time_unit
+ @default_sampling = settings[:sampling] || step
+ @recorder = Recorder().new sampling: settings[:sampling]
+ end
- private
+ # Sets up subclasses of +Core+ (the simulator) and +Recorder+ (the sampler)
+ # for timed simulations.
+ #
+ def init_core_and_recorder_subclasses
+ param_class( { Core: YPetri::Core::Timed,
+ Recorder: Recorder },
+ with: { simulation: self } )
+ end
- def reset!
- @time = initial_time || 0
- @next_sampling_time = @time
- super # otherwise same as for timeless cases
- end
-
- # Records a sample, now.
- def sample!
- print '.'
- super time.round( SAMPLING_TIME_DECIMAL_PLACES )
- end
-
- # Hook to allow Simulation to react to its state changes.
- def note_state_change!
- return nil unless @time.round( 9 ) >= @next_sampling_time.round( 9 )
- sample!
- @next_sampling_time += @sampling_period
- end
-
- def update_time! Δt=step_size
- @time += Δt
- end
-
- def set_time t
- @time = t
- end
-
- # Duplicate creation.
- #
- def dup
- instance = super
- instance.send :set_time, time
- return instance
- end
+ # Resets the time to initial time, or to the argument (if provided).
+ #
+ def reset_time! time=nil
+ @time = time.nil? ? initial_time : time
+ end
+ end # module Timed
end # module YPetri::Simulation::Timed
-
-# In general, it is not required that all net elements are simulated with the
-# same method. Practically, ODE systems have many good simulation methods
-# available.
-#
-# (1) ᴍ(t) = ϝ f(ᴍ, t).dt, where f(ᴍ, t) is a known function.
-#
-# Many of these methods depend on the Jacobian, but that may not be available
-# for some places. Therefore, the places, whose marking defines the system
-# state, are divided into two categories: "A" (accelerated), for which as
-# common Jacobian can be found, and "E" places, where "E" can stand either for
-# "External" or "Euler".
-#
-# If we apply the definition of "causal orientation" on A and E places, then it
-# can be said, that only the transitions causally oriented towards "A" places
-# are allowed for compliance with the equation (1).