y_petri 2.0.15 → 2.1.3

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.
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