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.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +31 -0
- data/Rakefile +2 -0
- data/lib/y_petri/demonstrator.rb +164 -0
- data/lib/y_petri/demonstrator_2.rb +176 -0
- data/lib/y_petri/demonstrator_3.rb +150 -0
- data/lib/y_petri/demonstrator_4.rb +217 -0
- data/lib/y_petri/manipulator.rb +598 -0
- data/lib/y_petri/net.rb +458 -0
- data/lib/y_petri/place.rb +189 -0
- data/lib/y_petri/simulation.rb +1313 -0
- data/lib/y_petri/timed_simulation.rb +281 -0
- data/lib/y_petri/transition.rb +921 -0
- data/lib/y_petri/version.rb +3 -0
- data/lib/y_petri/workspace/instance_methods.rb +254 -0
- data/lib/y_petri/workspace/parametrized_subclassing.rb +26 -0
- data/lib/y_petri/workspace.rb +16 -0
- data/lib/y_petri.rb +141 -0
- data/test/simple_manual_examples.rb +28 -0
- data/test/y_petri_graph.png +0 -0
- data/test/y_petri_test.rb +1521 -0
- data/y_petri.gemspec +21 -0
- metadata +112 -0
@@ -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).
|