y_petri 2.0.3 → 2.0.7

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