y_petri 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,281 @@
1
+ #encoding: utf-8
2
+
3
+ # A descendant class of YPetri::Simulation that introduces timekeeping.
4
+ #
5
+ class YPetri::TimedSimulation < YPetri::Simulation
6
+ SAMPLING_TIME_DECIMAL_PLACES = SAMPLING_DECIMAL_PLACES
7
+ SIMULATION_METHODS =
8
+ [
9
+ [ :Euler ],
10
+ [ :Euler_with_timeless_transitions_firing_after_each_time_tick, :quasi_Euler ],
11
+ [ :Euler_with_timeless_transitions_firing_after_each_step, :pseudo_Euler ]
12
+ ]
13
+ DEFAULT_SIMULATION_METHOD = :Euler
14
+
15
+ # ==== Exposing time-related global simulation settings
16
+
17
+ # Simulation parameter: :initial_time.
18
+ #
19
+ attr_reader :initial_time
20
+
21
+ # Simulation parameter: :step_size
22
+ #
23
+ attr_accessor :step_size
24
+
25
+ # Simulation parameter: :sampling_period
26
+ #
27
+ attr_accessor :sampling_period
28
+
29
+ # Simulation parameter: :target_time
30
+ #
31
+ attr_accessor :target_time
32
+
33
+ # Reads the sampling rate.
34
+ #
35
+ def sampling_rate; 1 / sampling_period end
36
+
37
+ # Reads the time range (initial_time..target_time) of the simulation.
38
+ #
39
+ def time_range; initial_time..target_time end
40
+
41
+ # Reads simulation settings
42
+ # (:step_size, :sampling_period and :time_range).
43
+ #
44
+ def settings
45
+ { step_size: step_size,
46
+ sampling_period: sampling_period,
47
+ time_range: time_range }
48
+ end
49
+ alias simulation_settings settings
50
+
51
+ # Exposing time.
52
+ #
53
+ attr_reader :time
54
+ alias ᴛ time
55
+
56
+ # def stop; end # LATER
57
+ # def continue; end # LATER
58
+
59
+ # # Makes one Gillespie step
60
+ # def gillespie_step
61
+ # t, dt = gillespie_select( @net.transitions )
62
+ # @marking_vector += t.project( @marking_vector, @step_size )
63
+ # @time += dt
64
+ # note_state_change
65
+ # end
66
+
67
+ # # Return projection of Δᴍ by mysode-ing the interior.
68
+ # def project_mysode_interior( Δt )
69
+ # # So far, no interior
70
+ # # the internals of this method were already heavily obsolete
71
+ # # they can be seen in previous versions, if needed
72
+ # # so now, I just take use of the Δ_Euler_free
73
+ # Δ_Euler_free
74
+ # end
75
+
76
+ # In addition to the arguments required by the regular simulation
77
+ # constructor, timed simulation constructor also expects :step_size
78
+ # (alias :step), :sampling_period (alias :sampling), and :target_time
79
+ # named arguments.
80
+ #
81
+ def initialize args={}
82
+ args.must_have :step_size, syn!: :step
83
+ args.must_have :sampling_period, syn!: :sampling
84
+ args.may_have :target_time
85
+ args.may_have :initial_time
86
+ @step_size = args.delete :step_size
87
+ @sampling_period = args.delete :sampling_period
88
+ @target_time = args.delete :target_time
89
+ @initial_time = args.delete( :initial_time ) ||
90
+ @target_time.nil? ? nil : @target_time.class.zero
91
+ super args
92
+ end
93
+ # LATER: transition clamps
94
+
95
+ # Allows to explore the system at different state / time. Creates a double,
96
+ # which is set to the required state / time. In addition to the parent class,
97
+ # this version alseo sets time.
98
+ #
99
+ def at *args
100
+ oo = args.extract_options!
101
+ duplicate = super *args, oo
102
+ t = oo.may_have( :t, syn!: :ᴛ ) and duplicate.send :set_time, t
103
+ return duplicate
104
+ end
105
+
106
+ # At the moment, near alias for #run_to_arget_time!
107
+ #
108
+ def run! until_time=target_time
109
+ run_until_target_time! until_time
110
+ return self
111
+ end
112
+
113
+ # Scalar field gradient for free places.
114
+ #
115
+ def gradient_for_free_places
116
+ S_for_SR() * flux_vector_for_SR + gradient_for_sR
117
+ end
118
+
119
+ # Gradient for free places as a hash { place_name: ∂ / ∂ᴛ }.
120
+ #
121
+ def ∂
122
+ free_places :gradient_for_free_places
123
+ end
124
+
125
+ # Scalar field gradient for all places.
126
+ #
127
+ def gradient_for_all_places
128
+ F2A() * gradient_for_free_places
129
+ end
130
+ alias gradient gradient_for_all_places
131
+
132
+ # Δ state of free places that would happen by a single Euler step Δt.
133
+ #
134
+ def Δ_Euler_for_free_places( Δt=step_size )
135
+ # Here, ∂ represents all R transitions, to which TSr and Tsr are added:
136
+ gradient_for_free_places * Δt + Δ_for_TSr( Δt ) + Δ_for_Tsr( Δt )
137
+ end
138
+ alias Δ_euler_for_free_places Δ_Euler_for_free_places
139
+ alias ΔE Δ_Euler_for_free_places
140
+
141
+ # Δ state of all places that would happen by a single Euler step Δt.
142
+ #
143
+ def Δ_Euler_for_all_places( Δt=step_size )
144
+ F2A() * ΔE( Δt )
145
+ end
146
+ alias Δ_euler_for_all_places Δ_Euler_for_all_places
147
+ alias Δ_Euler Δ_Euler_for_all_places
148
+
149
+ # Makes one Euler step with T transitions. Timeless transitions are not
150
+ # affected.
151
+ #
152
+ def Euler_step!( Δt=@step_size ) # implicit Euler method
153
+ update_marking! Δ_Euler_for_free_places( Δt )
154
+ update_time! Δt
155
+ end
156
+ alias euler_step! Euler_step!
157
+
158
+ # Fires timeless transitions once. Time and timed transitions are not
159
+ # affected.
160
+ #
161
+ def timeless_transitions_all_fire!
162
+ update_marking! Δ_if_tS_fire_once + Δ_if_ts_fire_once
163
+ assignment_transitions_all_fire!
164
+ end
165
+ alias t_all_fire! timeless_transitions_all_fire!
166
+
167
+ # At the moment, near alias of #euler_step!
168
+ #
169
+ def step! Δt=step_size
170
+ case @method
171
+ when :Euler then
172
+ Euler_step! Δt
173
+ note_state_change!
174
+ when :Euler_with_timeless_transitions_firing_after_each_step,
175
+ :pseudo_Euler then
176
+ Euler_step!
177
+ timeless_transitions_all_fire!
178
+ note_state_change!
179
+ when :Euler_with_timeless_transitions_firing_after_each_time_tick,
180
+ :quasi_Euler then
181
+ raise # FIXME: quasi_Euler doesn't work yet
182
+ Euler_step!
183
+ # if time tick has elapsed, call #timeless_transitions_all_fire!
184
+ note_state_change!
185
+ else
186
+ raise "Unrecognized simulation method: #@method !!!"
187
+ end
188
+ return self
189
+ end
190
+
191
+ # Runs the simulation until the target time, using step! method. The second
192
+ # optional parameter tunes the behavior towards the end of the run, with
193
+ # alternatives :just_before, :just_after and :exact (default).
194
+ #
195
+ # just_before: all steps have normal size, simulation stops
196
+ # before or just on the target time
197
+ # just_after: all steps have normal size, simulation stops
198
+ # after or just on the target time_step
199
+ # exact: simulation stops exactly on the prescribed time,
200
+ # to make this possible last step is shortened if necessary
201
+ #
202
+ def run_until_target_time!( t=target_time, stepping_opt=:exact )
203
+ case stepping_opt
204
+ when :just_before then # step until on or just before the target
205
+ step! while @time + @step_size <= t
206
+ when :exact then # simulate to exact time
207
+ step! while @time + @step_size < t
208
+ step!( t - @time ) # make a short last step as required
209
+ @time = t # to get exactly on the prescribed time
210
+ when :just_after then # step until on or after target
211
+ step! while @time < t
212
+ else raise "Invalid stepping option: #{stepping_opt}" end
213
+ end
214
+ alias run_until! run_until_target_time!
215
+
216
+ # Produces the inspect string for this timed simulation.
217
+ #
218
+ def inspect
219
+ "#<YPetri::TimedSimulation: #{pp.size} places, #{tt.size} " +
220
+ "transitions, time: #{time}, object id: #{object_id} >"
221
+ end
222
+
223
+ # Produces a string brief
224
+ def to_s # :nodoc:
225
+ "TimedSimulation[ #{pp.size} pp, #{tt.size} tt, T: #{time} ]"
226
+ end
227
+
228
+ private
229
+
230
+ def reset!
231
+ @time = initial_time || 0
232
+ @next_sampling_time = @time
233
+ super # otherwise same as for timeless cases
234
+ end
235
+
236
+ # Records a sample, now.
237
+ def sample!
238
+ print '.'
239
+ super time.round( SAMPLING_TIME_DECIMAL_PLACES )
240
+ end
241
+
242
+ # Hook to allow Simulation to react to its state changes.
243
+ def note_state_change!
244
+ return nil unless @time.round( 9 ) >= @next_sampling_time.round( 9 )
245
+ sample!
246
+ @next_sampling_time += @sampling_period
247
+ end
248
+
249
+ def update_time! Δt=step_size
250
+ @time += Δt
251
+ end
252
+
253
+ def set_time t
254
+ @time = t
255
+ end
256
+
257
+ # Duplicate creation. TODO: Like with Simulation#duplicate, this should
258
+ # be thought over, whether this should actually be #dup or #clone method.
259
+ #
260
+ def duplicate
261
+ instance = super
262
+ instance.send :set_time, time
263
+ return instance
264
+ end
265
+ end # class YPetri::TimedSimulation
266
+
267
+ # In general, it is not required that all net elements are simulated with the
268
+ # same method. Practically, ODE systems have many good simulation methods
269
+ # available.
270
+ #
271
+ # (1) ᴍ(t) = ϝ f(ᴍ, t).dt, where f(ᴍ, t) is a known function.
272
+ #
273
+ # Many of these methods depend on the Jacobian, but that may not be available
274
+ # for some places. Therefore, the places, whose marking defines the system
275
+ # state, are divided into two categories: "A" (accelerated), for which as
276
+ # common Jacobian can be found, and "E" places, where "E" can stand either for
277
+ # "External" or "Euler".
278
+ #
279
+ # If we apply the definition of "causal orientation" on A and E places, then it
280
+ # can be said, that only the transitions causally oriented towards "A" places
281
+ # are allowed for compliance with the equation (1).