y_petri 2.0.3 → 2.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
1
  module YPetri
2
- VERSION = "2.0.3"
2
+ VERSION = "2.0.7"
3
3
  DEBUG = false
4
4
  end
@@ -2,25 +2,20 @@
2
2
  module YPetri::Workspace::ParametrizedSubclassing
3
3
  def initialize
4
4
  # Parametrized subclasses of Place, Transition and Net.
5
- @Place = place_subclass = Class.new YPetri::Place
5
+ @Place = place_subclass = Class.new( YPetri::Place )
6
6
  @Transition = transition_subclass = Class.new YPetri::Transition
7
7
  @Net = net_subclass = Class.new YPetri::Net
8
8
 
9
- # Now dependency injection: Let's tell these subclasses to work together.
10
- [ @Place, @Transition, @Net ].each { |klass|
11
- klass.class_exec {
12
- # redefine their Place, Transition, Net method
9
+ # Make them namespaces and inject dependencies:
10
+ [ @Place, @Transition, @Net ].each do |klass|
11
+ klass.namespace!.class_exec do # make'em work together
13
12
  define_method :Place do place_subclass end
14
13
  define_method :Transition do transition_subclass end
15
14
  define_method :Net do net_subclass end
16
- # I am not sure whether the following line is necessary. Place(),
17
- # Transition() and Net() methods, which have just been redefined,
18
- # are originally defined as private in klass. Is it necessary to
19
- # declare them private explicitly again after redefining?
20
- private :Place, :Transition, :Net
21
- }
22
- }
15
+ private :Place, :Transition, :Net # Redeclare private after redef???
16
+ end
17
+ end
23
18
 
24
- super # Parametrized subclassing achieved, proceed ahead normally.
19
+ super # param. subclassing achieved, proceed ahead normally
25
20
  end # def initialize
26
21
  end # module YPetri::Workspace::ParametrizedSubclassing
@@ -137,15 +137,18 @@ module YPetri::Workspace::SimulationRelatedMethods
137
137
  #
138
138
  # * default_ss = { step_size: 0.1, sampling_period: 5, target_time: 60 }
139
139
  #
140
- def new_timed_simulation( settings={} ); st = settings
141
- net_ɪ = net( st[:net] || self.Net::Top )
142
- cc_id = st.may_have( :cc, syn!: :clamp_collection ) || :Base
143
- imc_id = st.may_have( :imc, syn!: :initial_marking_collection ) || :Base
144
- ssc_id = st.may_have( :ssc, syn!: :simulation_settings_collection ) || :Base
140
+ def new_timed_simulation( net: Net()::Top, **nn )
141
+ net_ɪ = net( net )
142
+ nn.may_have :cc, syn!: :clamp_collection
143
+ nn.may_have :imc, syn!: :initial_marking_collection
144
+ nn.may_have :ssc, syn!: :simulation_settings_collection
145
+ cc_id = nn.delete( :cc ) || :Base
146
+ imc_id = nn.delete( :imc ) || :Base
147
+ ssc_id = nn.delete( :ssc ) || :Base
145
148
 
146
149
  # simulation key
147
- key = settings.may_have( :ɴ, syn!: :name ) || # either explicit
148
- { net: net_ɪ, cc: cc_id, imc: imc_id, ssc: ssc_id } # or constructed
150
+ key = nn.may_have( :ɴ, syn!: :name ) || # either explicit
151
+ { net: net_ɪ, cc: cc_id, imc: imc_id, ssc: ssc_id }.merge( nn ) # or constructed
149
152
 
150
153
  # Let's clarify what we got so far.
151
154
  simulation_settings = self.ssc( ssc_id )
@@ -173,9 +176,8 @@ module YPetri::Workspace::SimulationRelatedMethods
173
176
  else err.( [0, 1], " and #{missing.size-2} more places" ) end
174
177
 
175
178
  # Finally, create and return the simulation
176
- @simulations[ key ] =
177
- net_ɪ.new_timed_simulation( simulation_settings
178
- .merge( initial_marking: im_hash,
179
- place_clamps: clamp_hash ) )
179
+ named_args = simulation_settings.merge( initial_marking: im_hash,
180
+ marking_clamps: clamp_hash ).merge( nn )
181
+ @simulations[ key ] = net_ɪ.new_timed_simulation **named_args
180
182
  end # def new_timed_simulation
181
183
  end # module YPetri::Workspace::SimulationRelatedMethods
data/lib/y_petri.rb CHANGED
@@ -7,6 +7,7 @@ require 'y_support/respond_to'
7
7
  require 'y_support/name_magic'
8
8
  require 'y_support/unicode'
9
9
  require 'y_support/typing'
10
+ require 'y_support/conscience'; include Conscience
10
11
  require 'y_support/core_ext/hash'
11
12
  require 'y_support/core_ext/array'
12
13
  require 'y_support/stdlib_ext/matrix'
@@ -50,6 +51,8 @@ module YPetri
50
51
  target_time: 60 }
51
52
  end
52
53
 
54
+ GuardError = Class.new TypeError
55
+
53
56
  def self.included( receiver )
54
57
  # receiver.instance_variable_set :@YPetriManipulator, Manipulator.new
55
58
  # puts "included in #{receiver}"
@@ -64,7 +67,7 @@ module YPetri
64
67
 
65
68
  delegate( :workspace, to: :y_petri_manipulator )
66
69
 
67
- # Petri net-related methods.
70
+ # Petri net aspect.
68
71
  delegate( :Place, :Transition, :Net,
69
72
  :place, :transition, :pl, :tr,
70
73
  :places, :transitions, :nets,
@@ -76,7 +79,7 @@ module YPetri
76
79
  :net_point_set,
77
80
  to: :y_petri_manipulator )
78
81
 
79
- # Simulation-related methods.
82
+ # Simulation aspect.
80
83
  delegate( :simulation_point, :ssc_point, :cc_point, :imc_point,
81
84
  :simulation_selection, :ssc_selection,
82
85
  :cc_selection, :imc_selection,
@@ -99,7 +102,9 @@ module YPetri
99
102
  :simulation_settings_collection, :ssc,
100
103
  :clamp,
101
104
  :initial_marking,
102
- :set_step, :set_time, :set_sampling,
105
+ :set_step, :set_step_size,
106
+ :set_time, :set_target_time,
107
+ :set_sampling,
103
108
  :set_simulation_method,
104
109
  :new_timed_simulation,
105
110
  :run!,
@@ -0,0 +1,83 @@
1
+ #! /usr/bin/ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ require 'minitest/spec'
5
+ require 'minitest/autorun'
6
+ require_relative '../lib/y_petri' # tested component itself
7
+ # require 'y_petri'
8
+ # require 'sy'
9
+
10
+ describe YPetri::Place do
11
+ before do
12
+ @pç = pç = Class.new YPetri::Place
13
+ @p = pç.new! default_marking: 3.2,
14
+ marking: 1.1,
15
+ quantum: 0.1,
16
+ name: "P1"
17
+ end
18
+
19
+ it "should support #name" do
20
+ assert_respond_to @p, :name
21
+ assert_equal @p.name, :P1
22
+ end
23
+
24
+ it "should have marking and related methods" do
25
+ @p.marking.must_equal 1.1 # Attention, #marking overloaded with guard setup!
26
+ @p.quantum.must_equal 0.1
27
+ @p.add 1
28
+ @p.value.must_equal 2.1 # near-alias of #marking (no guard setup)
29
+ @p.subtract 0.5
30
+ @p.m.must_equal 1.6 # alias of #value
31
+ @p.reset_marking
32
+ @p.marking.must_equal 3.2
33
+ @p.marking = 42
34
+ @p.m.must_equal 42
35
+ @p.m = 43
36
+ @p.m.must_equal 43
37
+ @p.value = 44
38
+ @p.m.must_equal 44
39
+ end
40
+
41
+ it "should have decent #inspect and #to_s methods" do
42
+ assert @p.inspect.start_with? "#<Place:"
43
+ assert @p.to_s.start_with? "#{@p.name}["
44
+ end
45
+
46
+ it "should have arc getter methods" do
47
+ @p.upstream_arcs.must_equal []
48
+ @p.upstream_transitions.must_equal [] # alias of #upstream_arcs
49
+ @p.ϝ.must_equal [] # alias of #upstream_arcs
50
+ @p.downstream_arcs.must_equal []
51
+ @p.downstream_transitions.must_equal [] # alias of #downstream_arcs
52
+ @p.arcs.must_equal [] # all arcs
53
+ @p.precedents.must_equal []
54
+ @p.upstream_places.must_equal [] # alias for #precedents
55
+ @p.dependents.must_equal []
56
+ @p.downstream_places.must_equal [] # alias for #dependents
57
+ end
58
+
59
+ it "should have convenience methods to fire surrounding transitions" do
60
+ assert_respond_to @p, :fire_upstream
61
+ assert_respond_to @p, :fire_upstream!
62
+ assert_respond_to @p, :fire_downstream
63
+ assert_respond_to @p, :fire_downstream!
64
+ assert_respond_to @p, :fire_upstream_recursively
65
+ assert_respond_to @p, :fire_downstream_recursively
66
+ end
67
+
68
+ it "should have guard mechanics" do
69
+ @p.guards.size.must_equal 2 # working automatic guard construction
70
+ g1, g2 = @p.guards
71
+ g1.assertion.must_include "number"
72
+ g2.assertion.must_include "complex"
73
+ begin; g1.validate 11.1; g2.validate 11.1; @p.guard.( 11.1 ); :nothing_raised
74
+ rescue; :error end.must_equal :nothing_raised
75
+ -> { g2.validate Complex( 1, 1 ) }.must_raise YPetri::GuardError
76
+ @p.marking "must be in 0..10" do |m| fail unless ( 0..10 ) === m end
77
+ @p.guards.size.must_equal 3
78
+ g = @p.federated_guard_closure
79
+ -> { g.( 11.1 ) }.must_raise YPetri::GuardError
80
+ @p.marking = -1.11
81
+ -> { @p.guard! }.must_raise YPetri::GuardError
82
+ end
83
+ end
@@ -0,0 +1,325 @@
1
+ #! /usr/bin/ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ require 'minitest/spec'
5
+ require 'minitest/autorun'
6
+ require 'y_support/typing'
7
+
8
+ require_relative '../lib/y_petri' # tested component itself
9
+
10
+ # require 'y_petri'
11
+ # require 'sy'
12
+
13
+ # **************************************************************************
14
+ # Test of Transition class, part I.
15
+ # **************************************************************************
16
+ #
17
+ describe ::YPetri::Transition do
18
+ before do
19
+ @ç = ç = Class.new ::YPetri::Transition
20
+ @pç = pç = Class.new ::YPetri::Place
21
+ [ ç, pç ].each do |ç|
22
+ ç.class_exec {
23
+ define_method :Place do pç end
24
+ define_method :Transition do ç end
25
+ private :Place, :Transition
26
+ }
27
+ end
28
+ @p1 = pç.new default_marking: 1.0
29
+ @p2 = pç.new default_marking: 2.0
30
+ @p3 = pç.new default_marking: 3.0
31
+ @p4 = pç.new default_marking: 4.0
32
+ @p5 = pç.new default_marking: 5.0
33
+ end
34
+
35
+ describe "ts transitions (timeless nonstoichiometric)" do
36
+ # Note: ts transitions require a function, and thus are always functional
37
+ before do
38
+ @t1 = @ç.new codomain: [ @p1, @p3 ], domain: @p2, action: -> a { [ a, a ] }
39
+ # saying that the transition is timed saves the day here:
40
+ @t2 = @ç.new codomain: [ @p1, @p3 ], action: -> t { [ t, t ] }, timed: true
41
+ # Only when the domain is unary, is the closure allowed to be timeless:
42
+ @t3 = @ç.new codomain: [ @p1, @p3 ], action: -> t { [ t, t ] }, timed: false, domain: [ @p2 ]
43
+ # With nullary action closure, timeless is implied, so this is allowed
44
+ @t4 = @ç.new action: -> { [ 0.5, 0.5 ] }, codomain: [ @p1, @p3 ]
45
+ # ... also for stoichiometric variety
46
+ @t5 = @ç.new action: -> { 0.5 }, codomain: [ @p1, @p3 ], s: [ 1, 1 ]
47
+ end
48
+
49
+ it "should raise errors for bad parameters" do
50
+ # omitting the domain should raise ArgumentError about too much ambiguity:
51
+ -> { @ç.new codomain: [ @p1, @p3 ], action: -> t { [ t, t ] } }
52
+ .must_raise ArgumentError
53
+ # saying that the transition is timeless points to a conflict:
54
+ -> { @ç.new codomain: [ @p1, @p3 ], action: -> t { [ t, t ] }, timeless: true }
55
+ .must_raise ArgumentError
56
+ end
57
+
58
+ it "should initialize and perform" do
59
+ @t1.domain.must_equal [@p2]
60
+ @t1.action_arcs.must_equal [@p1, @p3]
61
+ assert @t1.functional?
62
+ assert @t1.timeless?
63
+ assert @t2.timed?
64
+ assert [@t3, @t4, @t5].all? { |t| t.timeless? }
65
+ assert @t2.rateless?
66
+ # Now let's flex them:
67
+ @t1.fire!
68
+ [@p1.m, @p3.m].must_equal [3, 5]
69
+ @t3.fire!
70
+ [@p1.m, @p3.m].must_equal [5, 7]
71
+ @t4.fire!
72
+ [@p1.m, @p3.m].must_equal [5.5, 7.5]
73
+ @t5.fire!
74
+ [@p1.m, @p3.m].must_equal [6, 8]
75
+ # now t2 for firing requires delta time
76
+ @t2.fire! 1
77
+ [@p1.m, @p3.m].must_equal [7, 9]
78
+ @t2.fire! 0.1
79
+ [@p1.m, @p3.m ].must_equal [7.1, 9.1]
80
+ # let's change @p2 marking
81
+ @p2.m = 0.1
82
+ @t1.fire!
83
+ assert_in_epsilon 7.2, @p1.marking, 1e-9
84
+ assert_in_epsilon 9.2, @p3.marking, 1e-9
85
+ # let's test #domain_marking, #codomain_marking, #zero_action
86
+ @t1.codomain_marking.must_equal [@p1.m, @p3.m]
87
+ @t1.domain_marking.must_equal [@p2.m]
88
+ @t1.zero_action.must_equal [0, 0]
89
+ end
90
+ end
91
+
92
+ describe "Tsr transitions (timed rateless non-stoichiometric)" do
93
+ #LATER: To save time, I omit the full test suite.
94
+ end
95
+
96
+ describe "tS transitions (timeless stoichiometric)" do
97
+ describe "functionless tS transitions" do
98
+ # For functionless tS transitions, stoichiometric vector must be given,
99
+ # from which the action closure is then generated.
100
+
101
+ before do
102
+ # tS transition with only stoichiometric vector, as hash
103
+ @ftS1 = @ç.new stoichiometry: { @p1 => 1 }
104
+ # tS transition with only stoichiometric vector, as array + codomain
105
+ @ftS2 = @ç.new stoichiometry: 1, codomain: @p1
106
+ # :stoichiometry keyword is aliased as :s
107
+ @ftS3 = @ç.new s: 1, codomain: @p1
108
+ # :codomain is aliased as :action_arcs
109
+ @ftS4 = @ç.new s: 1, action_arcs: @p1
110
+ # square brackets (optional for size 1 vectors)
111
+ @ftS5 = @ç.new s: [ 1 ], downstream: [ @p1 ]
112
+ # another alias of :codomain is :downstream_places
113
+ @ftS6 = @ç.new s: [ 1 ], downstream_places: [ @p1 ]
114
+ # And now, collect all of the above:
115
+ @tt = @ftS1, @ftS2, @ftS3, @ftS4, @ftS5, @ftS6
116
+ end
117
+
118
+ it "should work" do
119
+ # ...should be the same, having a single action arc:
120
+ assert @tt.all? { |t| t.action_arcs == [@p1] }
121
+ # timeless:
122
+ assert @tt.all? { |t| t.timeless? }
123
+ # rateless:
124
+ assert @tt.all? { |t| t.rateless? }
125
+ assert @tt.all? { |t| not t.has_rate? }
126
+ # no assignment action
127
+ assert @tt.all? { |t| not t.assignment_action? }
128
+ # not considered functional
129
+ assert @tt.all? { |t| t.functionless? }
130
+ assert @tt.all? { |t| not t.functional? }
131
+ # and having nullary action closure
132
+ assert @tt.all? { |t| t.action_closure.arity == 0 }
133
+ # the transitions should be able to #fire!
134
+ @ftS1.fire!
135
+ # the difference is apparent: marking of place @p1 jumped to 2:
136
+ @p1.marking.must_equal 2
137
+ # but should not #fire (no exclamation mark) unless cocked
138
+ assert !@ftS1.cocked?
139
+ @ftS1.fire
140
+ @p1.marking.must_equal 2
141
+ # cock it
142
+ @ftS1.cock
143
+ assert @ftS1.cocked?
144
+ # uncock again, just to test cocking
145
+ @ftS1.uncock
146
+ assert @ftS1.uncocked?
147
+ @ftS1.cock
148
+ assert !@ftS1.uncocked?
149
+ @ftS1.fire
150
+ @p1.marking.must_equal 3
151
+ # enough playing, we'll reset @p1 marking
152
+ @p1.reset_marking
153
+ @p1.marking.must_equal 1
154
+ # #action
155
+ assert @tt.all? { |t| t.action == [1] }
156
+ # #zero_action
157
+ assert @tt.all? { |t| t.zero_action }
158
+ # #action_after_feasibility_check
159
+ assert @tt.all? { |t| t.action_after_feasibility_check == [1] }
160
+ # #domain_marking
161
+ assert @tt.all? { |t| t.domain_marking == [] }
162
+ # #codomain_marking
163
+ assert @tt.all? { |t| t.codomain_marking == [@p1.m] }
164
+ # #enabled?
165
+ @p1.m.must_equal 1
166
+ @p1.guard.( 1 ).must_equal true
167
+ @tt.each { |t| t.enabled?.must_equal true }
168
+ end
169
+ end
170
+
171
+ describe "functional tS transitions" do
172
+ # Closure supplied to tS transitions governs their action.
173
+
174
+ before do
175
+ # stoichiometry given as hash
176
+ @FtS1 = @ç.new action_closure: ->{ 1 }, s: { @p1 => 1 }
177
+ # :action_closure has alias :action
178
+ @FtS2 = @ç.new action: ->{ 1 }, s: { @p1 => 1 }
179
+ # stoichiometry given as array of coefficients + codomain
180
+ @FtS3 = @ç.new s: 1, codomain: @p1, action: ->{ 1 }
181
+ # Specifying +timed: false+ as well as +timeless: true+ should be OK.
182
+ @FtS4 = @ç.new s: { @p1 => 1 }, action: ->{ 1 }, timed: false
183
+ @FtS5 = @ç.new s: { @p1 => 1 }, action: ->{ 1 }, timeless: true
184
+ # Even together in one statement:
185
+ @FtS6 = @ç.new s: { @p1 => 1 }, action: ->{ 1 }, timed: false, timeless: true
186
+ @tt = @FtS1, @FtS2, @FtS3, @FtS4, @FtS5, @FtS6
187
+ end
188
+
189
+ it "should reject bad parameters" do
190
+ # # +timed: true+ should raise a complaint:
191
+ # -> { @ç.new s: { @p1 => 1 }, action: ->{ 1 }, timed: true }
192
+ # .must_raise ArgumentError # constraint relaxed?
193
+ end
194
+
195
+ it "should init and perform" do
196
+ assert @tt.all? { |t| t.action_arcs == [ @p1 ] }
197
+ assert @tt.all? { |t| t.timeless? }
198
+ assert @tt.all? { |t| not t.has_rate? }
199
+ assert @tt.all? { |t| t.rateless? }
200
+ assert @tt.all? { |t| not t.assignment_action? }
201
+ assert @tt.all? { |t| not t.functionless? }
202
+ assert @tt.all? { |t| t.functional? }
203
+ # and having nullary action closure
204
+ assert @tt.all? { |t| t.action_closure.arity == 0 }
205
+ # the transitions should be able to #fire!
206
+ @FtS1.fire!
207
+ # no need for more testing here
208
+ end
209
+ end
210
+ end
211
+
212
+ describe "TSr transitions (timed rateless stoichiometric)" do
213
+ # Sr transitions have an action closure, require a function block, and thus
214
+ # are always functional. Their closure must take Δt as its first argument.
215
+
216
+ #LATER: To save time, I omit the tests of TSr transitions for now.
217
+ end
218
+
219
+ describe "sR transitions (nonstoichiometric with rate)" do
220
+ # Expect a function block with arity equal to their domain size, and output
221
+ # arity equal to the codomain size.
222
+
223
+ #LATER: To save time, I omit the full test suite.
224
+ end
225
+
226
+ describe "SR transitions (stoichiometric with rate)" do
227
+ before do
228
+ # This should give standard mass action by magic:
229
+ @SR1 = @ç.new s: { @p1 => -1, @p2 => -1, @p4 => 1 }, rate: 0.1
230
+ # While this has custom closure:
231
+ @SR2 = @ç.new s: { @p1 => -1, @p3 => 1 }, rate: -> a { a * 0.5 }
232
+ # While this one even has domain explicitly specified:
233
+ @SR3 = @ç.new s: { @p1 => -1, @p2 => -1, @p4 => 1 },
234
+ upstream_arcs: @p3, rate: -> a { a * 0.5 }
235
+ end
236
+
237
+ it "should init and work" do
238
+ @SR1.has_rate?.must_equal true
239
+ @SR1.upstream_arcs.must_equal [@p1, @p2]
240
+ @SR1.action_arcs.must_equal [@p1, @p2, @p4]
241
+ @SR2.domain.must_equal [@p1]
242
+ @SR2.action_arcs.must_equal [@p1, @p3]
243
+ @SR3.domain.must_equal [@p3]
244
+ @SR3.action_arcs.must_equal [@p1, @p2, @p4]
245
+ # and flex them
246
+ @SR1.fire! 1.0
247
+ [@p1, @p2, @p4].map( &:marking ).must_equal [0.8, 1.8, 4.2]
248
+ @SR2.fire! 1.0
249
+ [@p1, @p3].map( &:marking ).must_equal [0.4, 3.4]
250
+ # the action t3 cannot fire with delta time 1.0
251
+ -> { @SR3.fire! 1.0 }.must_raise RuntimeError
252
+ [@p1, @p2, @p3, @p4].map( &:marking ).must_equal [0.4, 1.8, 3.4, 4.2]
253
+ # but it can fire with eg. delta time 0.1
254
+ @SR3.fire! 0.1
255
+ assert_in_epsilon 0.23, @p1.marking, 1e-15
256
+ assert_in_epsilon 1.63, @p2.marking, 1e-15
257
+ assert_in_epsilon 3.4, @p3.marking, 1e-15
258
+ assert_in_epsilon 4.37, @p4.marking, 1e-15
259
+ end
260
+ end
261
+ end
262
+
263
+
264
+ # **************************************************************************
265
+ # Test of mutual knowedge of upstream/downstream arcs of places/transitions.
266
+ # **************************************************************************
267
+ #
268
+ describe "upstream and downstream reference mτs of places and transitions" do
269
+ before do
270
+ # skip "to speed up testing"
271
+ @tç = tç = Class.new YPetri::Transition
272
+ @pç = pç = Class.new YPetri::Place
273
+ [ tç, pç ].each { |ç|
274
+ ç.class_exec {
275
+ define_method :Place do pç end
276
+ define_method :Transition do tç end
277
+ private :Place, :Transition
278
+ }
279
+ }
280
+ @a = @pç.new( default_marking: 1.0 )
281
+ @b = @pç.new( default_marking: 2.0 )
282
+ @c = @pç.new( default_marking: 3.0 )
283
+ end
284
+
285
+ describe "Place" do
286
+ it "should have #register_ustream/downstream_transition methods" do
287
+ @t1 = @tç.new s: {}
288
+ @a.instance_variable_get( :@upstream_arcs ).must_equal []
289
+ @a.instance_variable_get( :@downstream_arcs ).must_equal []
290
+ @a.send :register_upstream_transition, @t1
291
+ @a.instance_variable_get( :@upstream_arcs ).must_equal [ @t1 ]
292
+ end
293
+ end
294
+
295
+ describe "upstream and downstream reference methods" do
296
+ before do
297
+ @t1 = @tç.new s: { @a => -1, @b => 1 }, rate: 1
298
+ end
299
+
300
+ it "should show on the referencers" do
301
+ @a.upstream_arcs.must_equal [@t1]
302
+ @b.downstream_arcs.must_equal []
303
+ @b.ϝ.must_equal [@t1]
304
+ @t1.upstream_arcs.must_equal [@a]
305
+ @t1.action_arcs.must_equal [@a, @b]
306
+ end
307
+ end
308
+
309
+ describe "assignment action transitions" do
310
+ before do
311
+ @p = @pç.new default_marking: 1.0
312
+ @t = @tç.new codomain: @p, action: -> { 1 }, assignment_action: true
313
+ end
314
+
315
+ it "should work" do
316
+ @p.marking = 3
317
+ @p.marking.must_equal 3
318
+ assert @t.assignment_action?
319
+ @t.domain.must_equal []
320
+ @t.action_closure.arity.must_equal 0
321
+ @t.fire!
322
+ @p.marking.must_equal 1
323
+ end
324
+ end # context assignment action transiotions
325
+ end