y_petri 2.0.15 → 2.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/lib/y_petri/{manipulator → agent}/hash_key_pointer.rb +2 -2
  3. data/lib/y_petri/agent/petri_net_related.rb +115 -0
  4. data/lib/y_petri/{manipulator → agent}/selection.rb +2 -1
  5. data/lib/y_petri/{manipulator/simulation_related_methods.rb → agent/simulation_related.rb} +93 -110
  6. data/lib/y_petri/agent.rb +22 -0
  7. data/lib/y_petri/core/timed/euler.rb +20 -0
  8. data/lib/y_petri/core/timed/pseudo_euler.rb +31 -0
  9. data/lib/y_petri/core/timed/quasi_euler.rb +23 -0
  10. data/lib/y_petri/core/timed.rb +70 -0
  11. data/lib/y_petri/core/timeless/pseudo_euler.rb +20 -0
  12. data/lib/y_petri/core/timeless.rb +12 -0
  13. data/lib/y_petri/core.rb +100 -0
  14. data/lib/y_petri/dsl.rb +66 -0
  15. data/lib/y_petri/fixed_assets.rb +7 -0
  16. data/lib/y_petri/net/element_access.rb +239 -0
  17. data/lib/y_petri/net/state/feature/delta.rb +88 -0
  18. data/lib/y_petri/net/state/feature/firing.rb +57 -0
  19. data/lib/y_petri/net/state/feature/flux.rb +58 -0
  20. data/lib/y_petri/net/state/feature/gradient.rb +75 -0
  21. data/lib/y_petri/net/state/feature/marking.rb +62 -0
  22. data/lib/y_petri/net/state/feature.rb +79 -0
  23. data/lib/y_petri/net/state/features/dataset.rb +135 -0
  24. data/lib/y_petri/net/state/features/record.rb +50 -0
  25. data/lib/y_petri/net/state/features.rb +126 -0
  26. data/lib/y_petri/net/state.rb +121 -0
  27. data/lib/y_petri/net/timed.rb +8 -0
  28. data/lib/y_petri/net/visualization.rb +3 -3
  29. data/lib/y_petri/net.rb +73 -77
  30. data/lib/y_petri/place.rb +8 -3
  31. data/lib/y_petri/simulation/dependency.rb +107 -0
  32. data/lib/y_petri/simulation/element_representation.rb +20 -0
  33. data/lib/y_petri/simulation/elements/access.rb +57 -0
  34. data/lib/y_petri/simulation/elements.rb +45 -0
  35. data/lib/y_petri/simulation/feature_set.rb +21 -0
  36. data/lib/y_petri/simulation/initial_marking/access.rb +55 -0
  37. data/lib/y_petri/simulation/initial_marking.rb +15 -0
  38. data/lib/y_petri/simulation/marking_clamps/access.rb +34 -0
  39. data/lib/y_petri/simulation/marking_clamps.rb +18 -0
  40. data/lib/y_petri/simulation/marking_vector/access.rb +106 -0
  41. data/lib/y_petri/simulation/marking_vector.rb +156 -0
  42. data/lib/y_petri/simulation/matrix.rb +64 -0
  43. data/lib/y_petri/simulation/place_mapping.rb +62 -0
  44. data/lib/y_petri/simulation/place_representation.rb +74 -0
  45. data/lib/y_petri/simulation/places/access.rb +121 -0
  46. data/lib/y_petri/simulation/places/clamped.rb +8 -0
  47. data/lib/y_petri/simulation/places/free.rb +8 -0
  48. data/lib/y_petri/simulation/places/types.rb +25 -0
  49. data/lib/y_petri/simulation/places.rb +41 -0
  50. data/lib/y_petri/simulation/recorder.rb +54 -0
  51. data/lib/y_petri/simulation/timed/recorder.rb +53 -0
  52. data/lib/y_petri/simulation/timed.rb +161 -261
  53. data/lib/y_petri/simulation/timeless/recorder.rb +25 -0
  54. data/lib/y_petri/simulation/timeless.rb +35 -0
  55. data/lib/y_petri/simulation/transition_representation/A.rb +58 -0
  56. data/lib/y_petri/simulation/transition_representation/S.rb +45 -0
  57. data/lib/y_petri/simulation/transition_representation/T.rb +80 -0
  58. data/lib/y_petri/simulation/transition_representation/TS.rb +46 -0
  59. data/lib/y_petri/simulation/transition_representation/Ts.rb +32 -0
  60. data/lib/y_petri/simulation/transition_representation/a.rb +30 -0
  61. data/lib/y_petri/simulation/transition_representation/s.rb +29 -0
  62. data/lib/y_petri/simulation/transition_representation/t.rb +37 -0
  63. data/lib/y_petri/simulation/transition_representation/tS.rb +38 -0
  64. data/lib/y_petri/simulation/transition_representation/ts.rb +32 -0
  65. data/lib/y_petri/simulation/transition_representation/types.rb +62 -0
  66. data/lib/y_petri/simulation/transition_representation.rb +79 -0
  67. data/lib/y_petri/simulation/transitions/A.rb +40 -0
  68. data/lib/y_petri/simulation/transitions/S.rb +24 -0
  69. data/lib/y_petri/simulation/transitions/T.rb +34 -0
  70. data/lib/y_petri/simulation/transitions/TS.rb +57 -0
  71. data/lib/y_petri/simulation/transitions/Ts.rb +60 -0
  72. data/lib/y_petri/simulation/transitions/a.rb +8 -0
  73. data/lib/y_petri/simulation/transitions/access.rb +186 -0
  74. data/lib/y_petri/simulation/transitions/s.rb +9 -0
  75. data/lib/y_petri/simulation/transitions/t.rb +22 -0
  76. data/lib/y_petri/simulation/transitions/tS.rb +55 -0
  77. data/lib/y_petri/simulation/transitions/ts.rb +58 -0
  78. data/lib/y_petri/simulation/transitions/types.rb +98 -0
  79. data/lib/y_petri/simulation/transitions.rb +21 -0
  80. data/lib/y_petri/simulation.rb +176 -781
  81. data/lib/y_petri/transition/assignment.rb +7 -5
  82. data/lib/y_petri/transition/construction.rb +119 -187
  83. data/lib/y_petri/transition/init.rb +311 -0
  84. data/lib/y_petri/transition/ordinary_timeless.rb +8 -6
  85. data/lib/y_petri/transition/timed.rb +11 -18
  86. data/lib/y_petri/transition.rb +104 -132
  87. data/lib/y_petri/version.rb +1 -1
  88. data/lib/y_petri/world/dependency.rb +40 -0
  89. data/lib/y_petri/world/petri_net_related.rb +61 -0
  90. data/lib/y_petri/{workspace/simulation_related_methods.rb → world/simulation_related.rb} +42 -49
  91. data/lib/y_petri/world.rb +27 -0
  92. data/lib/y_petri.rb +47 -99
  93. data/test/{manipulator_test.rb → agent_test.rb} +19 -17
  94. data/{lib/y_petri → test/examples}/demonstrator.rb +0 -0
  95. data/{lib/y_petri → test/examples}/demonstrator_2.rb +1 -0
  96. data/{lib/y_petri → test/examples}/demonstrator_3.rb +0 -0
  97. data/{lib/y_petri → test/examples}/demonstrator_4.rb +0 -0
  98. data/test/examples/example_2.rb +16 -0
  99. data/test/{manual_examples.rb → examples/manual_examples.rb} +0 -0
  100. data/test/net_test.rb +126 -121
  101. data/test/place_test.rb +1 -1
  102. data/test/sim_test +565 -0
  103. data/test/simulation_test.rb +338 -264
  104. data/test/transition_test.rb +77 -174
  105. data/test/world_mock.rb +12 -0
  106. data/test/{workspace_test.rb → world_test.rb} +19 -20
  107. data/test/y_petri_test.rb +4 -5
  108. metadata +101 -26
  109. data/lib/y_petri/dependency_injection.rb +0 -45
  110. data/lib/y_petri/manipulator/petri_net_related_methods.rb +0 -74
  111. data/lib/y_petri/manipulator.rb +0 -20
  112. data/lib/y_petri/net/selections.rb +0 -209
  113. data/lib/y_petri/simulation/collections.rb +0 -460
  114. data/lib/y_petri/workspace/parametrized_subclassing.rb +0 -22
  115. data/lib/y_petri/workspace/petri_net_related_methods.rb +0 -88
  116. data/lib/y_petri/workspace.rb +0 -16
  117. data/test/timed_simulation_test.rb +0 -153
@@ -1,637 +1,205 @@
1
1
  #encoding: utf-8
2
2
 
3
- require_relative 'simulation/collections'
3
+ require_relative 'simulation/matrix'
4
+ require_relative 'simulation/dependency'
5
+ require_relative 'simulation/element_representation'
6
+ require_relative 'simulation/elements'
7
+ require_relative 'simulation/elements/access'
8
+ require_relative 'simulation/place_representation'
9
+ require_relative 'simulation/places'
10
+ require_relative 'simulation/places/access'
11
+ require_relative 'simulation/transition_representation'
12
+ require_relative 'simulation/transitions'
13
+ require_relative 'simulation/transitions/access'
14
+ require_relative 'simulation/place_mapping'
15
+ require_relative 'simulation/marking_clamps'
16
+ require_relative 'simulation/marking_clamps/access'
17
+ require_relative 'simulation/initial_marking'
18
+ require_relative 'simulation/initial_marking/access'
19
+ require_relative 'simulation/marking_vector'
20
+ require_relative 'simulation/marking_vector/access'
21
+ require_relative 'simulation/recorder'
22
+ require_relative 'simulation/timeless'
4
23
  require_relative 'simulation/timed'
5
24
 
6
- # Represents a simulation of a Petri net, using certain method and settings.
7
- # Simulation concerns (simulation method and settings, initial values, marking
8
- # clamps, guards...) are separated from those Petri net domain model (existence,
9
- # naming, connectivity and function specification of the net). Again, clamps,
10
- # guards, initial values etc. <b>do not belong</b> to the model, although for
11
- # convenience, places may carry default initial marking, default guards, and
12
- # default clamps for use in simulations if none other are specified.
13
- #
25
+ # Represents a Petri net simulation, concerning are the simulation method and
26
+ # settings, initial values, marking clamps, guards and similar. Its concerns are
27
+ # are separated from those of the Petri net domain model (existence, naming,
28
+ # connectivity, functions...). Clamps, guards, initial values etc. <b>do not
29
+ # belong</b> to the model, although for convenience, places may carry default
30
+ # initial marking, guards, and clamps for use in the token game. Simulation
31
+ # instance can also use these if none other are specified.
32
+ #
33
+ # A simulation distinguishes between free and clamped places. For free places,
34
+ # initial marking has to be specified. For clamped places, marking clamps have
35
+ # to be specified. Both come as hashes:
36
+ #
14
37
  class YPetri::Simulation
15
- include Collections
38
+ include Places::Access
39
+ include Transitions::Access
40
+ include Elements::Access
41
+ include InitialMarking::Access
42
+ include MarkingClamps::Access
43
+ include MarkingVector::Access
16
44
 
17
- SAMPLING_DECIMAL_PLACES = 5
18
- SIMULATION_METHODS =
19
- [
20
- [:pseudo_Euler] # pseudo-timed simulation (like in Cell Illustrator)
21
- ]
22
- DEFAULT_SIMULATION_METHOD = :pseudo_Euler
45
+ DEFAULT_SETTINGS = -> do { method: :pseudo_euler, guarded: false } end
23
46
 
24
- # Default simulation method (accesses the constant DEFAULT_SIMULATION_METHOD
25
- # in the receiver's class).
26
- #
27
- def default_simulation_method
28
- self.class.const_get :DEFAULT_SIMULATION_METHOD
29
- end
30
-
31
- attr_reader :method, :guarded, :timed, :net
32
- alias guarded? guarded
33
- alias timed? timed
34
- attr_reader :marking_vector
35
- attr_reader :zero_ᴍ, :zero_gradient
36
- attr_reader :recording
37
- alias :r :recording
38
-
39
- # Stoichiometry matrix for *tS* transitions.
40
- #
41
- attr_reader :S_tS
42
-
43
- # Stoichiometry matrix for *TSr* transitions.
44
- #
45
- attr_reader :S_TSr
46
-
47
- # Stoichiometry matrix for *SR* transitions.
48
- #
49
- attr_reader :S_SR
50
-
51
- # Exposing Δ state closures for ts transitions.
52
- #
53
- attr_reader :Δ_closures_for_tsa
54
-
55
- # Note: 'a' in 'tsa' is needed because A (assignment) transitions can also be
56
- # regarded as a special kind of ts transitions, while they obviously do not
57
- # act through Δ state, but rather directly enforce marking of their codomain.
58
- #
59
- def Δ_if_tsa_fire_once
60
- Δ_closures_for_tsa.map( &:call ).reduce( @zero_ᴍ, :+ )
61
- end
62
-
63
- # Exposing Δ state closures for Tsr transitions.
64
- #
65
- attr_reader :Δ_closures_for_Tsr
66
-
67
- # Delta state contribution for Tsr transitions given Δt.
68
- #
69
- def Δ_Tsr( Δt )
70
- Δ_closures_for_Tsr.map { |cl| cl.( Δt ) }.reduce( @zero_ᴍ, :+ )
71
- end
72
-
73
- # Exposing action closures for tS transitions.
74
- #
75
- attr_reader :action_closures_for_tS
76
-
77
- # Action vector for if tS transitions fire once. The closures are called
78
- # in their order, but the state update is not performed between the
79
- # calls (ie. they fire "simultaneously").
80
- #
81
- def action_vector_for_tS
82
- Matrix.column_vector action_closures_for_tS.map( &:call )
83
- end
84
- alias ᴀ_tS action_vector_for_tS
85
-
86
- # Action vector if tS transitions fire once, like the previous method.
87
- # But by calling this method, the caller asserts that all timeless
88
- # transitions in this simulation are stoichiometric (or error is raised).
89
- #
90
- def action_vector_for_timeless_transitions
91
- return action_vector_for_tS if ts_transitions.empty?
92
- raise "The simulation also contains nonstoichiometric timeless " +
93
- "transitions! Consider using #action_vector_for_tS."
94
- end
95
- alias action_vector_for_t action_vector_for_timeless_transitions
96
- alias ᴀ_t action_vector_for_timeless_transitions
97
-
98
- # Δ state contribution for tS transitions.
99
- #
100
- def Δ_if_tS_fire_once
101
- S_tS() * action_vector_for_tS
102
- end
103
-
104
- # Exposing action closures for TSr transitions.
105
- #
106
- attr_reader :action_closures_for_TSr
107
-
108
- # By calling this method, the caller asserts that all timeless transitions
109
- # in this simulation are stoichiometric (or error is raised).
110
- #
111
- def action_closures_for_Tr
112
- return action_closures_for_TSr if self.TSr_transitions.empty?
113
- raise "The simulation also contains nonstoichiometric timed rateless " +
114
- "transitions! Consider using #action_closures_for_TSr."
115
- end
116
-
117
- # Action vector for timed rateless stoichiometric transitions.
118
- #
119
- def action_vector_for_TSr( Δt )
120
- Matrix.column_vector action_closures_for_TSr.map { |c| c.( Δt ) }
121
- end
122
- alias ᴀ_TSr action_vector_for_TSr
123
-
124
- # Action vector for timed rateless stoichiometric transitions
125
- # By calling this method, the caller asserts that all timeless transitions
126
- # in this simulation are stoichiometric (or error is raised).
127
- #
128
- def action_vector_for_Tr( Δt )
129
- return action_vector_for_TSr( Δt ) if TSr_transitions().empty?
130
- raise "The simulation also contains nonstoichiometric timed rateless " +
131
- "transitions! Consider using #action_vector_for_TSr."
132
- end
133
- alias ᴀ_Tr action_vector_for_Tr
134
-
135
- # State contribution of TSr transitions for the period Δt.
136
- #
137
- def Δ_TSr( Δt )
138
- S_TSr() * action_vector_for_TSr( Δt )
139
- end
140
-
141
- # Exposing rate closures for sR transitions.
142
- #
143
- attr_reader :rate_closures_for_sR
144
-
145
- # By calling this method, the caller asserts that there are no rateless
146
- # transitions in the simulation (or error is raised).
147
- #
148
- def rate_closures_for_nonstoichiometric_transitions
149
- return rate_closures_for_sR if r_transitions.empty?
150
- raise "The simulation also contains rateless transitions! Consider " +
151
- "using #rate_closures_for_sR."
152
- end
153
- alias rate_closures_for_s rate_closures_for_nonstoichiometric_transitions
154
-
155
- # State differential for sR transitions.
156
- #
157
- def gradient_for_sR
158
- rate_closures_for_sR.map( &:call ).reduce( @zero_gradient, :+ )
159
- end
160
-
161
- # State differential for sR transitions as a hash { place_name: ∂ / ∂ᴛ }.
162
- #
163
- def ∂_sR
164
- free_pp :gradient_for_sR
165
- end
166
-
167
- # First-order state contribution of sR transitions during Δt.
168
- #
169
- def Δ_sR( Δt )
170
- gradient_for_sR * Δt
171
- end
172
-
173
- # Exposing rate closures for SR transitions.
174
- #
175
- attr_reader :rate_closures_for_SR
176
-
177
- # Rate closures for SR transitions. By calling this method, the caller
178
- # asserts that there are no rateless transitions in the simulation
179
- # (or error).
180
- #
181
- def rate_closures_for_stoichiometric_transitions
182
- return rate_closures_for_SR if r_transitions.empty?
183
- raise "The simulation also contains rateless transitions! Consider " +
184
- "using #rate_closures_for_SR"
185
- end
186
- alias rate_closures_for_S rate_closures_for_stoichiometric_transitions
187
-
188
- # Rate closures for SR transitions. By calling this method, the caller
189
- # asserts that there are only SR transitions in the simulation (or error).
190
- #
191
- def rate_closures
192
- return rate_closures_for_S if s_transitions.empty?
193
- raise "The simulation contains also nonstoichiometric transitions! " +
194
- "Consider using #rate_closures_for_S."
195
- end
196
-
197
- # While rateless stoichiometric transitions provide transition's action as
198
- # their closure output, SR transitions' closures return flux, which is
199
- # ∂action / ∂t. This methods return flux for SR transitions as a column
200
- # vector.
201
- #
202
- def flux_vector_for_SR
203
- Matrix.column_vector rate_closures_for_SR.map( &:call )
204
- end
205
- alias φ_for_SR flux_vector_for_SR
206
-
207
- # Flux vector for a selected collection of SR transitions.
208
- #
209
- def flux_vector_for *transitions
210
- # TODO
211
- end
212
- alias φ_for flux_vector_for
213
-
214
- # Flux vector for SR transitions. Same as the previous method, but the
215
- # caller asserts that there are only SR transitions in the simulation
216
- # (or error).
217
- #
218
- def flux_vector
219
- return flux_vector_for_SR if s_transitions.empty? && r_transitions.empty?
220
- raise "One may only call this method when all the transitions of the " +
221
- "simulation are SR transitions. Try #flux_vector_for( *transitions ), " +
222
- "#flux_vector_for_SR, #flux_for( *transitions ), or #flux_for_SR"
223
- end
224
- alias φ flux_vector
225
-
226
- # Flux of SR transitions as an array.
227
- #
228
- def flux_for_SR
229
- flux_vector_for_SR.column( 0 ).to_a
230
- end
231
-
232
- # Flux for a selected collection of SR transitions.
233
- #
234
- def flux_for *transitions
235
- all = SR_transitions :flux_for_SR
236
- transitions.map { |t| transition t }.map { |e| all[e] }
237
- end
238
-
239
- # Same as #flux_for_SR, but with caller asserting that there are none but
240
- # SR transitions in the simulation (or error).
241
- #
242
- def flux
243
- flux_vector.column( 0 ).to_a
244
- end
245
-
246
- # Flux of SR transitions as a hash { name: flux }.
247
- #
248
- def f_SR
249
- SR_tt :flux_for_SR
250
- end
251
-
252
- # Flux for a selected collection of SR transition as hash { key => flux }.
253
- #
254
- def f_for *transitions
255
- Hash[ transitions.zip( flux_for *transitions ) ]
256
- end
257
-
258
- # Same as #f_SR, but with caller asserting that there are none but SR
259
- # transitions in the simulation (or error).
260
- #
261
- def f
262
- SR_tt :flux
263
- end
264
-
265
- # State differential for SR transitions.
266
- #
267
- def gradient_for_SR
268
- S_SR() * flux_vector_for_SR
269
- end
270
-
271
- # State differential for SR transitions as a hash { place_name: ∂ / ∂ᴛ }.
272
- #
273
- def ∂_SR
274
- free_pp :gradient_for_SR
275
- end
276
-
277
- # First-order action vector for SR transitions for the time period Δt.
278
- #
279
- def first_order_action_vector_for_SR( Δt )
280
- flux_vector_for_SR * Δt
281
- end
282
- alias ᴀ_SR first_order_action_vector_for_SR
283
-
284
- # First-order action (as array) for SR for the time period Δt.
285
- #
286
- def first_order_action_for_SR( Δt )
287
- first_order_action_vector_for_SR( Δt ).column( 0 ).to_a
288
- end
289
-
290
- # First-order state contribution of SR transitions during Δt.
291
- #
292
- def Δ_SR( Δt )
293
- gradient_for_SR * Δt
294
- end
295
-
296
- # First-order state contribution for SR transitions during Δt (as array).
297
- #
298
- def Δ_array_for_SR( Δt )
299
- Δ_Euler_for_SR( Δt ).column( 0 ).to_a
300
- end
47
+ class << self
48
+ alias __new__ new
301
49
 
302
- # Exposing assignment closures for A transitions.
303
- #
304
- attr_reader :assignment_closures_for_A
305
-
306
- # Returns the array of places to which the assignment transitions assign.
307
- #
308
- def A_target_places
309
- # TODO
310
- end
311
-
312
- # Like #A_target_places, but returns place names.
313
- #
314
- def A_target_pp
315
- # TODO
316
- end
317
-
318
- # Returns the assignments as they would if all A transitions fired now,
319
- # as a hash { place => assignment }.
320
- #
321
- def assignments
322
- # TODO
323
- end
324
-
325
- # Like #assignments, but place names are used instead { name: assignment }.
326
- #
327
- def a
328
- # TODO
329
- end
330
-
331
- # Returns the assignments as a column vector.
332
- #
333
- def A_action
334
- Matrix.column_vector( assignments.reduce( free_places { nil } ) do |α, p|
335
- α[p] = marking
336
- end )
337
- # TODO: Assignment action to a clamped place should result in a warning.
338
- end
339
-
340
- # Correspondence matrix free places => all places.
341
- #
342
- attr_reader :F2A
343
-
344
- # Correspondence matrix clamped places => all places.
345
- #
346
- attr_reader :C2A
347
-
348
- # Simulation settings.
349
- #
350
- def settings; {} end
351
- alias :simulation_settings :settings
352
-
353
- def recording_csv_string
354
- CSV.generate do |csv|
355
- @recording.keys.zip( @recording.values ).map{ |a, b| [ a ] + b.to_a }
356
- .each{ |line| csv << line }
357
- end
50
+ def new net: (fail ArgumentError, "No net supplied!"), **settings
51
+ net.simulation **settings
52
+ end
358
53
  end
359
54
 
360
- # Currently, simulation is largely immutable. Net, initial marking, clamps
361
- # and simulation settings are set upon initialization, whereupon the instance
362
- # forms their "mental image", which remains immune to any subsequent changes
363
- # to the original objects. Required parameters are :net, :marking_clamps, and
364
- # :initial_marking. Optional is :method (simulation method), and :guarded
365
- # (true/false, whether the simulation guards the transition function results).
366
- # Guard conditions can be either implicit (guarding against negative values
367
- # and against type changes in by transition action), or explicitly associated
368
- # with either places, or transition function results.
369
- #
370
- # A simulation distinguishes between free and clamped places. For free
371
- # places, initial marking has to be specified. For clamped places, marking
372
- # clamps have to be specified. Both come as hashes:
373
- #
374
- # In addition to the arguments required by the regular simulation
375
- # constructor, timed simulation constructor also expects :step_size
376
- # (alias :step), :sampling_period (alias :sampling), and :target_time
377
- # named arguments.
378
- #
379
- def initialize( method: default_simulation_method,
380
- guarded: false,
381
- marking_clamps: {},
382
- initial_marking: {},
383
- **nn )
384
- puts "constructing a simulation" if YPetri::DEBUG
385
-
386
- @method, @guarded = method, guarded
387
- @net = nn.fetch( :net )
388
- @places, @transitions = @net.places.dup, @net.transitions.dup
389
- self.singleton_class.class_exec {
390
- define_method :Place do net.send :Place end
391
- define_method :Transition do net.send :Transition end
392
- define_method :Net do net.send :Net end
393
- private :Place, :Transition, :Net
394
- }; puts "setup of :net mental image complete" if YPetri::DEBUG
55
+ # Parametrized subclasses:
56
+ attr_reader :core,
57
+ :recorder,
58
+ :guarded,
59
+ :tS_stoichiometry_matrix,
60
+ :TS_stoichiometry_matrix,
61
+ :tS_SM,
62
+ :TS_SM,
63
+ :ts_delta_closure,
64
+ :Ts_gradient_closure,
65
+ :tS_firing_closure,
66
+ :TS_rate_closure,
67
+ :A_assignment_closure,
68
+ :increment_marking_vector_closure
395
69
 
396
- @marking_clamps = marking_clamps.with_keys { |k| place k }
397
- @initial_marking = initial_marking.with_keys { |k| place k }
398
- # Enforce that keys in the hashes must be unique:
399
- @marking_clamps.keys.aT_equal @marking_clamps.keys.uniq
400
- @initial_marking.keys.aT_equal @initial_marking.keys.uniq
401
- puts "setup of clamps and initial marking done" if YPetri::DEBUG
402
-
403
- places.each { |pl| # each place must have either clamp, or initial marking
404
- pl.aT "place #{pl}", "have either clamp or initial marking" do |pl|
405
- ( @marking_clamps.keys + @initial_marking.keys ).include? pl
406
- end
407
- }; puts "clamp || initial marking test passed" if YPetri::DEBUG
408
-
409
- # @F2A * ᴍ (marking vector of free places) maps ᴍ to all places.
410
- @F2A = Matrix.correspondence_matrix( free_places, places )
411
- # @C2A * marking_vector_of_clamped_places maps it to all places.
412
- @C2A = Matrix.correspondence_matrix( clamped_places, places )
413
- puts "correspondence matrices set up" if YPetri::DEBUG
414
-
415
- # Stoichiometry matrices:
416
- @S_tS = S_for tS_transitions()
417
- @S_SR = S_for SR_transitions()
418
- @S_TSr = S_for TSr_transitions()
419
- puts "stoichiometry matrices set up" if YPetri::DEBUG
420
-
421
- # Other assets:
422
- @Δ_closures_for_tsa = create_Δ_closures_for_tsa
423
- @Δ_closures_for_Tsr = create_Δ_closures_for_Tsr
424
- @action_closures_for_tS = create_action_closures_for_tS
425
- @action_closures_for_TSr = create_action_closures_for_TSr
426
- @rate_closures_for_sR = create_rate_closures_for_sR
427
- @rate_closures_for_SR = create_rate_closures_for_SR
428
- @assignment_closures_for_A = create_assignment_closures_for_A
429
- @zero_ᴍ = compute_initial_marking_vector_of_free_places.map { |e| e * 0 }
430
- @zero_gradient = @zero_ᴍ.dup
431
- puts "other assets set up" if YPetri::DEBUG
432
-
433
- @timed = if nn.has?( :time ) || nn.has?( :step ) || nn.has?( :sampling )
434
- extend Timed
435
- true
436
- else false end
70
+ alias guarded? guarded
437
71
 
438
- if timed? then # we have to set up all the expected variables
439
- if nn[:time] then # time range given
440
- time_range = nn[:time]
441
- @initial_time, @target_time = time_range.begin, time_range.end
442
- @step_size = nn[:step] || target_time / target_time.to_f
443
- @sampling_period = nn[:sampling] || step_size
444
- else
445
- anything = nn[:step] || nn[:sampling]
446
- @initial_time, @target_time = anything * 0, anything * Float::INFINITY
447
- @step_size = nn[:step] || anything / anything.to_f
448
- @sampling_period = nn[:sampling] || step_size
449
- end
72
+ delegate :net, to: "self.class"
73
+
74
+ delegate :simulation_method,
75
+ :guarded?,
76
+ :step!,
77
+ to: :core
78
+
79
+ delegate :recording,
80
+ to: :recorder
81
+
82
+ # The basic simulation parameter is :net +YPetri::Net+ instance which to
83
+ # simulate. Net implies the collection of places and transitions. Other
84
+ # required attributes are marking clamps and initial marking. These can be
85
+ # extracted from the Place and Transition instances if not given explicitly.
86
+ # Simulation method is controlled by the :method argument, guarding is
87
+ # switched on and off by the :guarded argument (true/false). If timed
88
+ # transitions are present, the simulation is considered timed. Timed
89
+ # simulation constructor has additional arguments :time, establishing time
90
+ # range, :step, controlling the simulation step size, and :sampling,
91
+ # controlling the sampling frequency.
92
+ #
93
+ def initialize **settings
94
+ method = settings[:method] # the simulation method
95
+ @guarded = settings[:guarded] # guarding on / off
96
+ m_clamps = settings[:marking_clamps] || {}
97
+ init_m = settings[:initial_marking] || {}
98
+ use_default_marking = if settings.has? :use_default_marking then
99
+ settings[:use_default_marking]
100
+ else true end
101
+ # Time-independent simulation settings received, constructing param. classes
102
+ param_class!( { Place: PlaceRepresentation,
103
+ Places: Places,
104
+ Transition: TransitionRepresentation,
105
+ Transitions: Transitions,
106
+ PlaceMapping: PlaceMapping,
107
+ InitialMarking: InitialMarking,
108
+ MarkingClamps: MarkingClamps,
109
+ MarkingVector: MarkingVector }, with: { simulation: self } )
110
+ # Place and transition representation classes are their own namespaces.
111
+ Place().namespace!
112
+ Transition().namespace!
113
+ # Set up the places collection.
114
+ @places = Places().load( net.places )
115
+ # Clamped places' mapping to the clamp values.
116
+ @marking_clamps = MarkingClamps().load( m_clamps )
117
+ # Free places' mapping to the initial marking values.
118
+ @initial_marking = InitialMarking().load( init_m )
119
+ # Set up the place and transition collections.
120
+ @places.complete_initial_marking( use_default_marking: use_default_marking )
121
+ # Correspondence matrix free --> all
122
+ @f2a = free_places.correspondence_matrix( places )
123
+ # Correspondence matrix clamped --> all
124
+ @c2a = clamped_places.correspondence_matrix( places )
125
+ # Conditionally extend self depending on net's timedness.
126
+ extend( settings[:time] || settings[:step] || settings[:sampling] ?
127
+ Timed : Timeless )
128
+ # Initialize the marking vector.
129
+ @m_vector = MarkingVector().zero
130
+ # Set up the transitions collection.
131
+ @transitions = Transitions().load( net.transitions )
132
+ # Set up stoichiometry matrices relative to free places.
133
+ @tS_stoichiometry_matrix = transitions.tS.stoichiometry_matrix
134
+ @TS_stoichiometry_matrix = transitions.TS.stoichiometry_matrix
135
+ # Set up stoichiometry matrices relative to all places.
136
+ @tS_SM = transitions.tS.SM
137
+ @TS_SM = transitions.TS.SM
138
+ # Call timedness-dependent initialization.
139
+ init **settings
140
+ # Make timeless closures.
141
+ @ts_delta_closure = transitions.ts.delta_closure
142
+ @tS_firing_closure = transitions.tS.firing_closure
143
+ @A_assignment_closure = transitions.A.assignment_closure
144
+ @increment_marking_vector_closure = m_vector.increment_closure
145
+ # Make timed closures.
146
+ if timed? then
147
+ @Ts_gradient_closure = transitions.Ts.gradient_closure
148
+ @TS_rate_closure = transitions.TS.rate_closure
450
149
  end
451
-
452
- puts "timedness of the simulation decided" if YPetri::DEBUG
453
-
150
+ # Init the core.
151
+ @core = Core().new( method: method, guarded: guarded )
152
+ # Reset.
454
153
  reset!
455
154
  end
456
155
 
457
- # Returns a new instance of the system simulation at a specified state, with
458
- # same simulation settings. This state (:marking argument) can be specified
459
- # either as marking vector for free or all places, marking array for free or
460
- # all places, or marking hash. If vector or array is given, its size must
461
- # correspond to the number of either free, or all places. If hash is given,
462
- # it is not necessary to specify marking of every place – marking of those
463
- # left out will be left same as in the current state.
464
- #
465
- def at( marking: marking, **nn )
466
- err_msg = "Size of supplied marking must match either the number of " +
467
- "free places, or the number of all places!"
468
- update_method = case marking
469
- when Hash then :update_marking_from_a_hash
470
- when Matrix then
471
- case marking.column_to_a.size
472
- when places.size then :set_marking_vector
473
- when free_places.size then :set_ᴍ
474
- else fail TypeError, err_msg end
475
- else # marking assumed to be an array
476
- case marking.size
477
- when places.size then :set_marking
478
- when free_places.size then :set_m
479
- else fail TypeError, err_msg end
480
- end
481
- return dup( **nn ).send( update_method, marking )
482
- end
483
-
484
- # ==== Sparse stoichiometry vectors for transitions
485
-
486
- # For the transition specified by the argument, this method returns the
487
- # sparse stoichiometry vector corresponding to the free places.
488
- #
489
- def sparse_σ transition
490
- instance = transition( transition )
491
- raise AE, "Transition #{transition} not stoichiometric!" unless
492
- instance.stoichiometric?
493
- Matrix.correspondence_matrix( instance.codomain, free_places ) *
494
- Matrix.column_vector( instance.stoichiometry )
495
- end
496
-
497
- # For the transition specified by the argument, this method returns the
498
- # sparse stoichiometry vector mapped to all the places of the simulation.
156
+ # Simulation settings.
499
157
  #
500
- def sparse_stoichiometry_vector transition
501
- instance = transition( transition )
502
- raise AE, "Transition #{transition} not stoichiometric!" unless
503
- instance.stoichiometric?
504
- Matrix.correspondence_matrix( instance.codomain, places ) *
505
- Matrix.column_vector( instance.stoichiometry )
158
+ def settings all=false
159
+ return { method: simulation_method, guarded: guarded? } unless all == true
160
+ settings( false )
161
+ .update( net: net,
162
+ marking_clamps: marking_clamps.keys_to_source_places,
163
+ initial_marking: initial_marking.keys_to_source_places )
164
+ end
165
+
166
+ # Returns a new simulation instance. Unless modified by arguments, the state
167
+ # of the new instance is the same as the creator's. Arguments can partially or
168
+ # wholly modify the attributes of the duplicate.
169
+ #
170
+ def dup( marking: marking, recording: recording, **nn )
171
+ self.class.new( nn.reverse_merge! settings( true ) ).tap do |dup|
172
+ dup.recording.reset! recording: recording
173
+ dup.m_vector.reset! case marking
174
+ when Hash then
175
+ m_vector.to_hash_with_source_places
176
+ .update( PlaceMapping().load( marking ) )
177
+ .to_marking_vector
178
+ when Matrix, Array then marking
179
+ else marking.each.to_a end
180
+ end
506
181
  end
507
182
 
508
- # Produces the inspect string of the transition.
183
+ # Inspect string for this simulation.
509
184
  #
510
185
  def inspect
511
- "#<YPetri::Simulation: #{pp.size} pp, #{tt.size} tt, ID: #{object_id} >"
186
+ to_s
512
187
  end
513
188
 
514
- # Produces a string briefly describing the simulation instance.
189
+ # String representation of this simulation.
515
190
  #
516
191
  def to_s
517
- "Simulation[#{pp.size} pp, #{tt.size} tt]"
192
+ "#<Simulation: pp: %s, tt: %s, oid: %s>" % [ pp.size, tt.size, object_id ]
518
193
  end
519
194
 
520
- private
521
-
522
195
  # Resets the simulation
523
196
  #
524
- def reset!
525
- puts "Starting #reset! method" if YPetri::DEBUG
526
-
527
- mv_clamped = compute_marking_vector_of_clamped_places
528
- puts "#reset! obtained marking vector of clamped places" if YPetri::DEBUG
529
- clamped_component = C2A() * mv_clamped
530
- puts "clamped component of marking vector prepared:\n#{clamped_component}" if YPetri::DEBUG
531
-
532
- mv_free = compute_initial_marking_vector_of_free_places
533
- puts "#reset! obtained initial marking vector of free places" if YPetri::DEBUG
534
- free_component = F2A() * mv_free
535
- puts "free component of marking vector prepared:\n#{free_component}" if YPetri::DEBUG
536
-
537
- # zero_vector = Matrix.column_vector( places.map { SY::ZERO rescue 0 } ) # Float zeros
538
- zero_vector = Matrix.column_vector( places.map { 0 } ) # Float zeros
539
- puts "zero vector prepared: #{zero_vector}" if YPetri::DEBUG
540
-
541
- free_component.aT { |v|
542
- qnt = v.first.quantity rescue :no_quantity
543
- unless qnt == :no_quantity
544
- v.all? { |e| e.quantity == qnt }
545
- else true end
546
- } if YPetri::DEBUG
547
- puts "free component of marking vector checked" if YPetri::DEBUG
548
-
549
- @marking_vector = free_component + clamped_component
550
- # Matrix
551
- # .column_vector( places.map.with_index do |_, i|
552
- # clamped_component[i, 0] || free_component[i, 0]
553
- # end )
554
-
555
- puts "marking vector assembled\n#{m}\n, about to reset recording" if YPetri::DEBUG
556
- reset_recording!
557
- puts "reset recording done, about to initiate sampling process" if YPetri::DEBUG
558
- note_state_change!
559
- puts "sampling process initiated, #reset! done" if YPetri::DEBUG
560
- return self
561
- end
562
-
563
- # Resets the recording.
564
- #
565
- def reset_recording!
566
- @recording = {}
567
- end
568
-
569
- # To be called whenever the state changes. The method will cogitate, whether
570
- # the observed state change warrants calling #sample!
571
- #
572
- def note_state_change!
573
- sample! # default for vanilla Simulation: sample! at every occasion
574
- end
575
-
576
- # Performs sampling. A snapshot of the current simulation state is recorded
577
- # into @recording hash as a pair { sampling_event => simulation state }.
578
- #
579
- def sample! key=L!(:sample!)
580
- @sample_number = @sample_number + 1 rescue 0
581
- @recording[ key.ℓ?(:sample!) ? @sample_number : key ] =
582
- marking.map { |n| n.round SAMPLING_DECIMAL_PLACES }
583
- end
584
-
585
- # Called upon initialzation
586
- #
587
- def compute_initial_marking_vector_of_free_places
588
- puts "computing the marking vector of free places" if YPetri::DEBUG
589
- results = free_places.map { |p|
590
- im = @initial_marking[ p ]
591
- puts "doing free place #{p} with init. marking #{im}" if YPetri::DEBUG
592
- # unwrap places / cells
593
- im = case im
594
- when YPetri::Place then im.marking
595
- else im end
596
- case im
597
- when Proc then im.call
598
- else im end
599
- }
600
- # and create the matrix out of the results
601
- puts "about to create the column vector" if YPetri::DEBUG
602
- cv = Matrix.column_vector results
603
- puts "column vector #{cv} prepared" if YPetri::DEBUG
604
- return cv
605
- end
606
-
607
- # Called upon initialization
608
- #
609
- def compute_marking_vector_of_clamped_places
610
- puts "computing the marking vector of clamped places" if YPetri::DEBUG
611
- results = clamped_places.map { |p|
612
- clamp = @marking_clamps[ p ]
613
- puts "doing clamped place #{p} with clamp #{clamp}" if YPetri::DEBUG
614
- # unwrap places / cells
615
- clamp = case clamp
616
- when YPetri::Place then clamp.marking
617
- else clamp end
618
- # unwrap closure by calling it
619
- case clamp
620
- when Proc then clamp.call
621
- else clamp end
622
- }
623
- # and create the matrix out of the results
624
- puts "about to create the column vector" if YPetri::DEBUG
625
- cv = Matrix.column_vector results
626
- puts "column vector #{cv} prepared" if YPetri::DEBUG
627
- return cv
628
- end
629
-
630
- # Expects a Δ marking vector for free places and performs the specified
631
- # change on the marking vector for all places.
632
- #
633
- def update_marking! Δ_free_places
634
- @marking_vector += F2A() * Δ_free_places
197
+ def reset! **settings
198
+ tap do
199
+ m_vector.reset!
200
+ recorder.reset!
201
+ recorder.alert
202
+ end
635
203
  end
636
204
 
637
205
  # Guards proposed marking delta.
@@ -641,182 +209,9 @@ class YPetri::Simulation
641
209
  places.zip( ary ).each { |pl, proposed_m| pl.guard.( proposed_m ) }
642
210
  end
643
211
 
644
- # Fires all assignment transitions once.
645
- #
646
- def assignment_transitions_all_fire!
647
- assignment_closures_for_A.each_with_index do |closure, i|
648
- @marking_vector = closure.call # TODO: This offers better algorithm.
649
- end
650
- end
651
- alias A_all_fire! assignment_transitions_all_fire!
652
-
653
- # ----------------------------------------------------------------------
654
- # Methods to create other instance assets upon initialization.
655
- # These instance assets are created at the beginning, so the work
656
- # needs to be performed only once in the instance lifetime.
657
-
658
- def create_Δ_closures_for_tsa
659
- tsa_transitions.map { |t|
660
- p2d = Matrix.correspondence_matrix( places, t.domain )
661
- c2f = Matrix.correspondence_matrix( t.codomain, free_places )
662
- if guarded? then
663
- -> {
664
- domain_marking = ( p2d * marking_vector ).column_to_a
665
- # I. TODO: t.domain_guard.( domain_marking )
666
- codomain_change = Array t.action_closure.( *domain_marking )
667
- # II. TODO: t.action_guard.( codomain_change )
668
- c2f * codomain_change
669
- }
670
- else
671
- -> { c2f * t.action_closure.( *( p2d * marking_vector ).column_to_a ) }
672
- end
673
- }
674
- end
675
-
676
- def blame_tsa( marking_vect )
677
- # If, in spite of passing domain guard and action guard, marking guard
678
- # indicates an exception, the method here serves to find the candidate
679
- # transitions to blame for the exception, given certain place marking.
680
- msg = "Action closure of transition #%{t} with domain #%{dm} and " +
681
- "codomain #%{cdm} returns #%{retval} which, when added to place " +
682
- "#{p}, gives marking that would flunk place's marking guard."
683
- tsa_transitions.each { |t|
684
- p2d = Matrix.correspondence_matrix( places, t.domain )
685
- rslt = Array t.action_closure( *( p2d * marking_vect ).column_to_a )
686
- t.codomain.zip( rslt ).each { |place, Δ|
687
- fields = {
688
- t: t.name || t.object_id, p: place,
689
- dm: Hash[ t.domain_pp.zip domain_marking ],
690
- cdm: Hash[ t.codomain_pp.zip( t.codomain.map { |p| ꜧ[p] } ) ],
691
- retval: Hash[ t.codomain_pp.zip( codomain_change ) ]
692
- }
693
- rslt = ꜧ[place] + Δ
694
- raise TypeError, msg % fields unless place.marking_guard.( rslt )
695
- }
696
- }
697
- # TODO: Here, #blame_tsa simply raises. It would be however more correct
698
- # to gather all blame candidates and present them to the user all.
699
- end
700
-
701
- def create_Δ_closures_for_Tsr
702
- Tsr_transitions().map { |t|
703
- p2d = Matrix.correspondence_matrix( places, t.domain )
704
- c2f = Matrix.correspondence_matrix( t.codomain, free_places )
705
- -> Δt { c2f * t.action_closure.( Δt, *( p2d * marking_vector ).column_to_a ) }
706
- }
707
- end
708
-
709
- def create_action_closures_for_tS
710
- tS_transitions.map{ |t|
711
- p2d = Matrix.correspondence_matrix( places, t.domain )
712
- -> { t.action_closure.( *( p2d * marking_vector ).column_to_a ) }
713
- }
714
- end
715
-
716
- def create_action_closures_for_TSr
717
- TSr_transitions().map{ |t|
718
- p2d = Matrix.correspondence_matrix( places, t.domain )
719
- -> Δt { t.action_closure.( Δt, *( p2d * marking_vector ).column_to_a ) }
720
- }
721
- end
722
-
723
- def create_rate_closures_for_sR
724
- sR_transitions.map{ |t|
725
- p2d = Matrix.correspondence_matrix( places, t.domain )
726
- c2f = Matrix.correspondence_matrix( t.codomain, free_places )
727
- -> { c2f * t.rate_closure.( *( p2d * marking_vector ).column_to_a ) }
728
- }
729
- end
730
-
731
- def create_rate_closures_for_SR
732
- SR_transitions().map { |t|
733
- p2d = Matrix.correspondence_matrix( places, t.domain )
734
- puts "Marking is #{pp :marking rescue nil}" if YPetri::DEBUG
735
- -> { t.rate_closure.( *( p2d * marking_vector ).column_to_a )
736
- .tap do |r| fail YPetri::GuardError, "SR #{t.name}!!!!" if r.is_a? Complex end
737
- }
738
- }
739
- end
740
-
741
- def create_assignment_closures_for_A
742
- nils = places.map { nil }
743
- A_transitions().map { |t|
744
- p2d = Matrix.correspondence_matrix( places, t.domain )
745
- c2f = Matrix.correspondence_matrix( t.codomain, free_places )
746
- zero_vector = Matrix.column_vector( places.map { 0 } )
747
- probe = Matrix.column_vector( t.codomain.size.times.map { |a| a + 1 } )
748
- result = ( F2A() * c2f * probe ).column_to_a.map { |n| n == 0 ? nil : n }
749
- assignment_addresses = probe.column_to_a.map { |i| result.index i }
750
- -> {
751
- act = Array t.action_closure.( *( p2d * marking_vector ).column_to_a )
752
- act.each_with_index { |e, i|
753
- fail YPetri::GuardError, "Assignment transition #{t.name} with " +
754
- "domain #{t.domain_pp( domain_marking )} has produced a complex " +
755
- "number at output positon #{i} (output was #{act})!" if e.is_a?( Complex ) || i.is_a?( Complex )
756
- }
757
- assign = assignment_addresses.zip( act )
758
- .each_with_object nils.dup do |pair, o| o[pair[0]] = pair[1] end
759
- marking_vector.map { |orig_val| assign.shift || orig_val }
760
- }
761
- } # map
762
- end
763
-
764
- # Set marking vector (for all places).
212
+ # Extract a prescribed set of features.
765
213
  #
766
- def set_marking_vector marking_vector
767
- @marking_vector = marking_vector
768
- return self
214
+ def get_features arg
215
+ net.State.features( arg ).extract_from( self )
769
216
  end
770
-
771
- # Set marking vector, based on marking array of all places.
772
- #
773
- def set_marking marking_array
774
- set_marking_vector Matrix.column_vector( marking_array )
775
- end
776
-
777
- # Update marking vector, based on { place => marking } hash argument.
778
- #
779
- def update_marking_from_a_hash marking_hash
780
- to_set = place_marking.merge( marking_hash.with_keys do |k| place k end )
781
- set_marking( places.map { |pl| to_set[ pl ] } )
782
- end
783
-
784
- # Set marking vector based on marking array of free places.
785
- #
786
- def set_m marking_array_for_free_places
787
- set_marking_from_a_hash( free_places( marking_array_for_free_places ) )
788
- end
789
-
790
- # Set marking vector based on marking vector of free places.
791
- #
792
- def set_ᴍ marking_vector_for_free_places
793
- set_m( marking_vector_for_free_places.column_to_a )
794
- end
795
-
796
- # Private method for resetting recording.
797
- #
798
- def set_recording rec
799
- @recording = Hash[ rec ]
800
- return self
801
- end
802
-
803
- # Duplicate creation.
804
- #
805
- def dup( **nn )
806
- self.class.new( nn.reverse_merge!( { method: @method,
807
- guarded: @guarded,
808
- net: @net,
809
- marking_clamps: @marking_clamps,
810
- initial_marking: @initial_marking
811
- }.update( simulation_settings ) ) )
812
- .tap { |instance|
813
- instance.send :set_recording, recording
814
- instance.send :set_marking_vector, @marking_vector
815
- }
816
- end
817
-
818
- # Instance identification methods.
819
- #
820
- def place( which ); Place().instance( which ) end
821
- def transition( which ); Transition().instance( which ) end
822
217
  end # class YPetri::Simulation