# encoding: utf-8 # A mixin for timed simulations, used by an +#extend+ call during init. # module YPetri::Simulation::Timed require_relative 'timed/recorder' DEFAULT_SETTINGS = -> do { step: 0.1, sampling: 5, time: 0..60 } end # True for timed simulations. # def timed? true end attr_reader :time, :time_unit, :initial_time, :target_time, :step, :default_sampling alias starting_time initial_time alias ending_time target_time delegate :flux_vector_TS, :gradient_TS, :gradient_Ts, :gradient, :flux_vector, to: :core delegate :sampling, to: :recorder # Reads the time range (initial_time..target_time) of the simulation. # def time_range initial_time..target_time end # Returns 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 # 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 # 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 # 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 # String representation of this timed simulation. # def to_s "#" % [ time, pp.size, tt.size, object_id ] end # Increments the simulation's time and alerts the recorder. # def increment_time! Δt=step @time += Δt recorder.alert end # Resets the timed simulation. # def reset! **nn @time = initial_time || time_unit * 0 super end # 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 alias at dup # 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 alias zero_∇ zero_gradient private # 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 case settings[:time] when Range then time_range = settings[:time] @initial_time, @target_time = time_range.begin, time_range.end @time_unit = initial_time.class.one else @initial_time = settings[:time] @time_unit = initial_time.class.one @target_time = time_unit * Float::INFINITY end 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.class.one @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 # 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 # 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 YPetri::Simulation::Timed