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
@@ -0,0 +1,311 @@
1
+ # encoding: utf-8
2
+
3
+ # Constructor syntax aspect of a transition. Large part of the functionality
4
+ # of the Transition class is the convenient constructor syntax.
5
+ #
6
+ class YPetri::Transition
7
+ # Transition class represents many different kinds of Petri net transitions.
8
+ # It makes the constructor syntax a bit more polymorphic. The type of the
9
+ # transition to construct is mostly inferred from the constructor arguments.
10
+ #
11
+ # Mandatorily, the constructor will always need a way to determine the domain
12
+ # (upstream arcs) and codomain (downstream arcs) of the transition. Also, the
13
+ # constructor must have a way to determine the transition's action. This is
14
+ # best explained by examples -- let us have 3 places A, B, C, for whe we will
15
+ # create different kinds of transitions:
16
+ #
17
+ #
18
+ # ==== TS (timed stoichiometric)
19
+ #
20
+ # Rate closure and stoichiometry has to be supplied. Rate closure arity should
21
+ # correspond to the domain size. Return arity should be 1 (to be multiplied by
22
+ # the stoichiometry vector, as in all other stoichiometric transitions).
23
+ #
24
+ # Transition.new stoichiometry: { A: -1, B: 1 },
25
+ # rate: -> a { a * 0.5 }
26
+ #
27
+ #
28
+ # ==== Ts (timed nonstoichiometric)
29
+ #
30
+ # Rate closure has to be supplied, whose arity should match the domain, and
31
+ # output arity codomain.
32
+ #
33
+ # ==== tS (timeless stoichiometric)
34
+ #
35
+ # Stoichiometry has to be supplied, action closure is optional. If supplied,
36
+ # its return arity should be 1 (to be multiplied by the stoichiometry vector).
37
+ #
38
+ # ==== ts transitions (timeless nonstoichiometric)
39
+ #
40
+ # Action closure is expected with return arity equal to the codomain size:
41
+ #
42
+ # Transition.new upstream_arcs: [A, C], downstream_arcs: [A, B],
43
+ # action_closure: proc { |m, x|
44
+ # if x > 0 then [-(m / 2), (m / 2)]
45
+ # else [1, 0] end
46
+ # }
47
+ #
48
+ def initialize *args, &block
49
+ check_in_arguments *args, &block # the big job
50
+ extend timed? ? Timed : ( assignment? ? Assignment : OrdinaryTimeless )
51
+ inform_upstream_places # that they have been connected
52
+ inform_downstream_places # that they have been connected
53
+ uncock # transitions initialize uncocked
54
+ end
55
+
56
+ private
57
+
58
+ # Checking in the arguments supplied to #initialize looks like a big job.
59
+ # I won't contest to that, but let us not, that it is basically nothing
60
+ # else then defining the duck type of the input argument collection.
61
+ # TypeError is therefore raised if invalid collection has been supplied.
62
+ #
63
+ def check_in_arguments **nn, &block
64
+ nn.update( action: block ) if block_given?
65
+ nn.may_have :domain, syn!: [ :domain_arcs, :domain_places,
66
+ :upstream, :upstream_arcs, :upstream_places ]
67
+ nn.may_have :codomain, syn!: [ :codomain_arcs, :codomain_places,
68
+ :downstream,
69
+ :downstream_arcs, :downstream_places,
70
+ :action_arcs ]
71
+ nn.may_have :rate, syn!: [ :rate_closure, :propensity,
72
+ :propensity_closure ]
73
+ nn.may_have :action, syn!: :action_closure
74
+ nn.may_have :stoichiometry, syn!: [ :stoichio, :s ]
75
+ nn.may_have :domain_guard
76
+ nn.may_have :codomain_guard
77
+
78
+ # If the rate was given, the transition is timed:
79
+ @timed = nn.has? :rate
80
+
81
+ # If stoichiometry was given, the transition is stoichiometric:
82
+ @stoichiometric = nn.has? :stoichiometry
83
+
84
+ # Downstream description involves the codomain, and the stochiometry
85
+ # (for stoichiometric transitions only):
86
+ if stoichiometric? then
87
+ @codomain, @stoichiometry = __downstream_for_S__( **nn )
88
+ else
89
+ @codomain = __downstream_for_s__( **nn )
90
+ end
91
+
92
+ # Check in the domain first, :missing symbol may be returned if the user
93
+ # has not supplied the domaing (the constructor will attempt to guessf it
94
+ # automatically).
95
+ @domain = __domain__( **nn )
96
+
97
+ # Upstream description involves the domain and the rate/action closure.
98
+ # Also, :missing domain is taken care of here.
99
+ if timed? then
100
+ @domain, @rate_closure, @functional = __upstream_for_T__( **nn )
101
+ else
102
+ @domain, @action_closure, @functional = __upstream_for_t__( **nn )
103
+ end
104
+
105
+ # Optional assignment action:
106
+ @assignment_action = __assignment_action__( **nn )
107
+
108
+ # Optional type guards for domain / codomain:
109
+ @domain_guard, @codomain_guard = __guards__( **nn )
110
+ end
111
+
112
+ # Validates that the supplied collection consists only of places of
113
+ # correct type. Second optional argument customizes the error message.
114
+ #
115
+ def sanitize_place_collection place_collection, what_is_collection=nil
116
+ c = what_is_collection ? what_is_collection.capitalize : "Collection"
117
+ Array( place_collection ).map do |pl_id|
118
+ begin
119
+ place( pl_id )
120
+ rescue NameError
121
+ raise TypeError, "#{c} member #{pl_id} does not specify a valid place!"
122
+ end
123
+ end.aT what_is_collection, "not contain duplicate places" do |coll|
124
+ coll == coll.uniq
125
+ end
126
+ end
127
+
128
+ # Private method, part of #initialize argument checking-in.
129
+ #
130
+ def __domain__( **nn )
131
+ if nn.has? :domain then
132
+ sanitize_place_collection( nn[:domain], "supplied domain" )
133
+ else
134
+ if stoichiometric? then
135
+ # take arcs with non-positive coefficients
136
+ Hash[ @codomain.zip @stoichiometry ].delete_if { |_, c| c > 0 }.keys
137
+ else
138
+ :missing # may mean empty domain, or domain == codomain
139
+ end
140
+ end
141
+ end
142
+
143
+ # Private method, part of the init process for timed transitions. Also takes
144
+ # care for :missing domain, if :missing.
145
+ #
146
+ def __upstream_for_T__( **nn )
147
+ dom = domain # this method may modify domain
148
+ fail ArgumentError, "Rate and action collision!" if nn.has? :action
149
+ # Let's figure the rate closure now.
150
+ λ = nn[:rate]
151
+ if λ.is_a? Proc then
152
+ if dom == :missing then
153
+ dom = λ.arity == 0 ? [] : codomain
154
+ else
155
+ msg = "Rate closure arity (#{λ.arity}) > domain (#{dom.size})!"
156
+ fail TypeError, msg if λ.arity.abs > dom.size
157
+ end
158
+ else # not a Proc, must guess user's intent
159
+ λ = if stoichiometric? then # standard mass action
160
+ msg = "With numeric rate, domain must not be given!"
161
+ fail TypeError, msg if nn.has? :domain
162
+ __standard_mass_action__( λ )
163
+ else # constant closure
164
+ msg = "With numeric rate and no stoichio., codomain size must be 1!"
165
+ fail TypeError, msg unless codomain.size == 1
166
+ lambda { λ }.tap do
167
+ if dom == :missing then
168
+ dom = [] # Missing domain is natural here
169
+ else # but should it was supplied explicitly, it must be empty.
170
+ msg = "Rate is a number, but domain is non-empty!"
171
+ fail TypeError, msg unless domain.empty? if nn.has? :domain
172
+ end
173
+ end
174
+ end
175
+ end
176
+ dom.aT_is_a Array
177
+ λ.aT_is_a Proc
178
+ return dom, λ, true # true here means "functional?", always true for T
179
+ end
180
+
181
+ # Private method, part of the init process when :rate is not given. Also
182
+ # takes care for missing domain (@domain == :missing).
183
+ #
184
+ def __upstream_for_t__( **nn )
185
+ dom = domain # this method may modify domain
186
+ funct = true # "functional?"
187
+ # Was action given explicitly?
188
+ if nn.has? :action then
189
+ λ = nn[:action].aT_is_a Proc, "supplied action named argument"
190
+ # Time to worry about the domain_missing, guess the user's intention:
191
+ if dom == :missing then
192
+ dom = λ.arity == 0 ? [] : codomain
193
+ else
194
+ msg = "Action closure arity (#{λ.arity}) > domain (#{dom.size})!"
195
+ fail TypeError, msg if λ.arity.abs > dom.size
196
+ end
197
+ else # "functionless"
198
+ funct = false
199
+ λ = proc { 1 }
200
+ msg = "Stoichiometry is compulsory, if no rate/action was supplied!"
201
+ fail ArgumentError, msg unless stoichiometric?
202
+ dom = [] # in any case, the domain is empty
203
+ end
204
+ return dom, λ, funct
205
+ end
206
+
207
+ # Default rate closure for SR transitions whose rate is hinted as a number.
208
+ #
209
+ def __standard_mass_action__( num )
210
+ # assume standard mass-action law
211
+ nonpositive_coeffs = stoichiometry.select { |coeff| coeff <= 0 }
212
+ # the closure takes markings of the domain as its arguments
213
+ -> *markings do
214
+ nonpositive_coeffs.size.times.reduce num do |acc, i|
215
+ marking, coeff = markings[ i ], nonpositive_coeffs[ i ]
216
+ # Stoichiometry coefficients equal to zero are taken to indicate
217
+ # plain factors, assuming that if these places were not involved
218
+ # in the transition at all, the user would not be mentioning them.
219
+ case coeff
220
+ when 0, -1 then marking * acc
221
+ else marking ** -coeff end
222
+ end
223
+ end
224
+ end
225
+
226
+ # Private method, checking in downstream specification from the argument
227
+ # field for stoichiometric transition.
228
+ #
229
+ def __downstream_for_S__( **oo )
230
+ codomain, stoichio =
231
+ case oo[:stoichiometry]
232
+ when Hash then
233
+ # contains pairs { codomain place => stoichiometry coefficient }
234
+ msg = "With hash-type stoichiometry, :codomain must not be given!"
235
+ fail ArgumentError, msg if oo.has? :codomain
236
+ oo[:stoichiometry].each_with_object [[], []] do |(cd_pl, coeff), memo|
237
+ memo[0] << cd_pl
238
+ memo[1] << coeff
239
+ end
240
+ else
241
+ # array of stoichiometry coefficients
242
+ msg = "With array-type stoichiometry, :codomain must be given!"
243
+ fail ArgumentError unless oo.has? :codomain
244
+ [ oo[:codomain], Array( oo[:stoichiometry] ) ]
245
+ end
246
+ # enforce that stoichiometry is a collection of numbers
247
+ return sanitize_place_collection( codomain, "supplied codomain" ),
248
+ stoichio.aT_all_numeric( "supplied stoichiometry" )
249
+ end
250
+
251
+ # Private method, checking in downstream specification from the argument
252
+ # field for nonstoichiometric transition.
253
+ #
254
+ def __downstream_for_s__( **oo )
255
+ # codomain must be explicitly given - no way around it:
256
+ fail ArgumentError, "For non-stoichiometric transitions, :codomain " +
257
+ "argument is compulsory." unless oo.has? :codomain
258
+ return sanitize_place_collection( oo[:codomain], "supplied codomain" )
259
+ end
260
+
261
+ # Private method, part of #initialize argument checking-in.
262
+ #
263
+ def __assignment_action__( **oo )
264
+ if oo.has? :assignment_action, syn!: [ :assignment, :assign, :A ] then
265
+ if timed? then
266
+ false.tap do
267
+ msg = "Timed transitions may not have assignment action!"
268
+ raise TypeError, msg if oo[:assignment_action]
269
+ end
270
+ else oo[:assignment_action] end # only timeless transitions are eligible
271
+ else false end # the default value
272
+ end
273
+
274
+ # Private method, part of #initialize argument checking-in
275
+ #
276
+ def __guards__( **oo )
277
+ if oo.has? :domain_guard then
278
+ oo[:domain_guard].aT_is_a Proc, "supplied domain guard"
279
+ else
280
+ place_guards = domain_places.map &:guard
281
+ -> dm do # constructing the default domain guard
282
+ fails = [domain, dm, place_guards].transpose.map { |pl, m, guard|
283
+ [ pl, m, begin; guard.( m ); true; rescue YPetri::GuardError; false end ]
284
+ }.reduce [] do |memo, triple| memo << triple unless triple[2] end
285
+ # TODO: Watch "Exceptional Ruby" video by Avdi Grimm.
286
+ unless fails.size == 0
287
+ fail YPetri::GuardError, "Domain guard of #{self} rejects marking " +
288
+ if fails.size == 1 then
289
+ p, m, _ = fails[0]
290
+ "#{m} of place #{p.name || p.object_id}!"
291
+ else
292
+ "of the following places: %s!" %
293
+ Hash[ fails.map { |pl, m, _| [pl.name || pl.object_id, m] } ]
294
+ end
295
+ end
296
+ end
297
+ end
298
+ end
299
+
300
+ # Informs upstream places that they have been connected to this transition.
301
+ #
302
+ def inform_upstream_places
303
+ upstream_places.each { |p| p.send :register_downstream_transition, self }
304
+ end
305
+
306
+ # Informs downstream places that they are connected to this transition.
307
+ #
308
+ def inform_downstream_places
309
+ downstream_places.each { |p| p.send :register_upstream_transition, self }
310
+ end
311
+ end # class YPetri::Transition
@@ -1,4 +1,4 @@
1
- # -*- coding: utf-8 -*-
1
+ # encoding: utf-8
2
2
 
3
3
  # Mixin for timed non-assignment timeless Petri net transitions.
4
4
  #
@@ -24,11 +24,13 @@ module YPetri::Transition::OrdinaryTimeless
24
24
  # Fires the transition regardless of cocking.
25
25
  #
26
26
  def fire!
27
- try "to call #fire method" do
28
- act = note "action", is: Array( action )
29
- codomain.each_with_index do |codomain_place, i|
30
- note "adding action element no. #{i} to place #{codomain_place}"
31
- codomain_place.add( note "marking change", is: act.fetch( i ) )
27
+ consciously "call #fire method" do
28
+ act = Array( action )
29
+ msg = "Wrong output arity of the action closure of #{self}!"
30
+ fail TypeError, msg if act.size != codomain.size
31
+ codomain.each_with_index do |p, i|
32
+ note "adding action element no. #{i} to #{p}"
33
+ p.add note( "marking change", is: act.fetch( i ) )
32
34
  end
33
35
  end
34
36
  return nil
@@ -6,20 +6,11 @@ module YPetri::Transition::Timed
6
6
  # Transition's action (before validation). Requires Δt as an argument.
7
7
  #
8
8
  def action Δt
9
- if has_rate? then
10
- if stoichiometric? then
11
- rate = rate_closure.( *domain_marking )
12
- stoichiometry.map { |coeff| rate * coeff * Δt }
13
- else # assuming that rate closure return value has correct arity
14
- rate_closure.( *domain_marking ).map { |e| component * Δt }
15
- end
16
- else # timed rateless
17
- if stoichiometric? then
18
- rslt = action_closure.( Δt, *domain_marking )
19
- stoichiometry.map { |coeff| rslt * coeff }
20
- else
21
- action_closure.( Δt, *domain_marking ) # caveat result arity!
22
- end
9
+ if stoichiometric? then
10
+ rate = rate_closure.( *domain_marking )
11
+ stoichiometry.map { |coeff| rate * coeff * Δt }
12
+ else
13
+ Array( rate_closure.( *domain_marking ) ).map { |e| e * Δt }
23
14
  end
24
15
  end
25
16
 
@@ -34,11 +25,13 @@ module YPetri::Transition::Timed
34
25
  # Δt as an argument.
35
26
  #
36
27
  def fire! Δt
37
- try "to call #fire method" do
28
+ consciously "call #fire method" do
38
29
  act = note "action", is: Array( action Δt )
39
- codomain.each_with_index do |codomain_place, i|
40
- note "adding action element no. #{i} to place #{codomain_place}"
41
- codomain_place.add( note "marking change", is: act.fetch( i ) )
30
+ msg = "Wrong output arity of the action closure of #{self}!"
31
+ fail TypeError, msg if act.size != codomain.size
32
+ codomain.each_with_index do |p, i|
33
+ note "adding action element no. #{i} to #{p}"
34
+ p.add note( "marking change", is: act.fetch( i ) )
42
35
  end
43
36
  end
44
37
  return nil