y_petri 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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).