y_petri 2.2.4 → 2.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +675 -0
  3. data/README.md +6 -3
  4. data/Rakefile +1 -1
  5. data/lib/y_petri/agent/{petri_net_related.rb → petri_net_aspect.rb} +34 -10
  6. data/lib/y_petri/agent/{simulation_related.rb → simulation_aspect.rb} +49 -34
  7. data/lib/y_petri/agent.rb +5 -5
  8. data/lib/y_petri/core/guarded.rb +24 -0
  9. data/lib/y_petri/core/timed/euler.rb +4 -8
  10. data/lib/y_petri/core/timed/gillespie.rb +11 -17
  11. data/lib/y_petri/core/timed/methods.rb +23 -0
  12. data/lib/y_petri/core/timed/pseudo_euler.rb +10 -13
  13. data/lib/y_petri/core/timed/quasi_euler.rb +9 -8
  14. data/lib/y_petri/core/timed/runge_kutta.rb +10 -18
  15. data/lib/y_petri/core/timed.rb +6 -14
  16. data/lib/y_petri/core/timeless/methods.rb +15 -0
  17. data/lib/y_petri/core/timeless/pseudo_euler.rb +4 -8
  18. data/lib/y_petri/core/timeless.rb +9 -4
  19. data/lib/y_petri/core.rb +44 -42
  20. data/lib/y_petri/net/data_set.rb +246 -142
  21. data/lib/y_petri/net/node_access.rb +282 -0
  22. data/lib/y_petri/net/own_state.rb +14 -4
  23. data/lib/y_petri/net/state/feature/assignment.rb +123 -0
  24. data/lib/y_petri/net/state/feature/delta.rb +55 -35
  25. data/lib/y_petri/net/state/feature/firing.rb +68 -25
  26. data/lib/y_petri/net/state/feature/flux.rb +9 -2
  27. data/lib/y_petri/net/state/feature/gradient.rb +36 -19
  28. data/lib/y_petri/net/state/feature/marking.rb +10 -5
  29. data/lib/y_petri/net/state/feature.rb +105 -11
  30. data/lib/y_petri/net/state/features/record.rb +144 -99
  31. data/lib/y_petri/net/state/features.rb +327 -200
  32. data/lib/y_petri/net/state.rb +48 -82
  33. data/lib/y_petri/net/visualization.rb +1 -1
  34. data/lib/y_petri/net.rb +62 -47
  35. data/lib/y_petri/place/arcs.rb +44 -0
  36. data/lib/y_petri/place/features.rb +115 -0
  37. data/lib/y_petri/place.rb +62 -29
  38. data/lib/y_petri/simulation/dependency.rb +31 -67
  39. data/lib/y_petri/simulation/feature_set.rb +1 -1
  40. data/lib/y_petri/simulation/initial_marking/access.rb +42 -26
  41. data/lib/y_petri/simulation/marking_clamps/access.rb +22 -17
  42. data/lib/y_petri/simulation/marking_clamps.rb +0 -2
  43. data/lib/y_petri/simulation/marking_vector/access.rb +102 -40
  44. data/lib/y_petri/simulation/marking_vector.rb +35 -37
  45. data/lib/y_petri/simulation/matrix.rb +1 -1
  46. data/lib/y_petri/simulation/node_representation.rb +25 -0
  47. data/lib/y_petri/simulation/nodes/access.rb +78 -0
  48. data/lib/y_petri/simulation/{elements.rb → nodes.rb} +14 -13
  49. data/lib/y_petri/simulation/place_mapping.rb +2 -2
  50. data/lib/y_petri/simulation/place_representation.rb +8 -7
  51. data/lib/y_petri/simulation/places/access.rb +89 -70
  52. data/lib/y_petri/simulation/places/free.rb +1 -1
  53. data/lib/y_petri/simulation/places/types.rb +20 -22
  54. data/lib/y_petri/simulation/places.rb +23 -18
  55. data/lib/y_petri/simulation/recorder.rb +23 -18
  56. data/lib/y_petri/simulation/timed/recorder.rb +19 -11
  57. data/lib/y_petri/simulation/timed.rb +93 -29
  58. data/lib/y_petri/simulation/timeless/recorder.rb +11 -6
  59. data/lib/y_petri/simulation/timeless.rb +13 -3
  60. data/lib/y_petri/simulation/transition_representation/A.rb +24 -4
  61. data/lib/y_petri/simulation/transition_representation/S.rb +11 -1
  62. data/lib/y_petri/simulation/transition_representation/T.rb +1 -1
  63. data/lib/y_petri/simulation/transition_representation/Ts.rb +1 -1
  64. data/lib/y_petri/simulation/transition_representation/a.rb +1 -1
  65. data/lib/y_petri/simulation/transition_representation/s.rb +12 -1
  66. data/lib/y_petri/simulation/transition_representation/t.rb +1 -1
  67. data/lib/y_petri/simulation/transition_representation/tS.rb +1 -1
  68. data/lib/y_petri/simulation/transition_representation/ts.rb +1 -1
  69. data/lib/y_petri/simulation/transition_representation/types.rb +1 -1
  70. data/lib/y_petri/simulation/transition_representation.rb +4 -11
  71. data/lib/y_petri/simulation/transitions/A.rb +17 -2
  72. data/lib/y_petri/simulation/transitions/S.rb +1 -1
  73. data/lib/y_petri/simulation/transitions/T.rb +1 -1
  74. data/lib/y_petri/simulation/transitions/Ts.rb +6 -5
  75. data/lib/y_petri/simulation/transitions/a.rb +1 -1
  76. data/lib/y_petri/simulation/transitions/access.rb +195 -168
  77. data/lib/y_petri/simulation/transitions/s.rb +1 -1
  78. data/lib/y_petri/simulation/transitions/t.rb +1 -1
  79. data/lib/y_petri/simulation/transitions/tS.rb +1 -1
  80. data/lib/y_petri/simulation/transitions/ts.rb +1 -1
  81. data/lib/y_petri/simulation/transitions/types.rb +1 -1
  82. data/lib/y_petri/simulation/transitions.rb +5 -7
  83. data/lib/y_petri/simulation.rb +84 -90
  84. data/lib/y_petri/transition/A.rb +8 -2
  85. data/lib/y_petri/transition/T.rb +25 -2
  86. data/lib/y_petri/transition/arcs.rb +19 -3
  87. data/lib/y_petri/transition/construction_convenience.rb +11 -10
  88. data/lib/y_petri/transition/t.rb +14 -1
  89. data/lib/y_petri/transition/types.rb +6 -1
  90. data/lib/y_petri/transition.rb +9 -12
  91. data/lib/y_petri/version.rb +1 -1
  92. data/lib/y_petri/world/dependency.rb +3 -3
  93. data/lib/y_petri/world/{petri_net_related.rb → petri_net_aspect.rb} +4 -4
  94. data/lib/y_petri/world/simulation_aspect.rb +352 -0
  95. data/lib/y_petri/world.rb +4 -4
  96. data/lib/y_petri.rb +1 -1
  97. data/test/agent_test.rb +2 -1
  98. data/test/examples/demonstrator.rb +4 -1
  99. data/test/examples/demonstrator_2.rb +5 -0
  100. data/test/examples/demonstrator_4.rb +6 -5
  101. data/test/examples/example_2.rb +2 -0
  102. data/test/examples/manual_examples.rb +4 -4
  103. data/test/net_test.rb +457 -54
  104. data/test/place_test.rb +11 -7
  105. data/test/simulation_test.rb +358 -331
  106. data/test/transition_test.rb +11 -10
  107. data/test/world_test.rb +2 -0
  108. data/test/y_petri_test.rb +2 -1
  109. data/y_petri.gemspec +24 -18
  110. metadata +71 -17
  111. data/LICENSE +0 -22
  112. data/lib/y_petri/net/element_access.rb +0 -239
  113. data/lib/y_petri/simulation/element_representation.rb +0 -20
  114. data/lib/y_petri/simulation/elements/access.rb +0 -57
  115. data/lib/y_petri/transition/type.rb +0 -103
  116. data/lib/y_petri/transition/type_information.rb +0 -103
  117. data/lib/y_petri/world/simulation_related.rb +0 -176
@@ -1,6 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
- # Firing of a Petri net tS transition.
3
+ # Firing of an S transition. (Firing is only defined on S transitions, whose
4
+ # action can be computed as firing * stoichometry_vector_of_the_transition.)
4
5
  #
5
6
  class YPetri::Net::State::Feature::Firing < YPetri::Net::State::Feature
6
7
  attr_reader :transition
@@ -10,25 +11,27 @@ class YPetri::Net::State::Feature::Firing < YPetri::Net::State::Feature
10
11
  #
11
12
  def parametrize *args
12
13
  Class.instance_method( :parametrize ).bind( self ).( *args ).tap do |ç|
13
- ç.instance_variable_set( :@instances,
14
- Hash.new do |hsh, id|
15
- case id
16
- when self then
17
- hsh[ id.transition ]
18
- when ç.net.Transition then
19
- t = begin
20
- ç.net.tS_transitions( [ id ] ).first
21
- rescue TypeError => err
22
- msg = "Transition #{id} not " +
23
- "recognized as tS transition in " +
24
- "net #{ç.net}! (%s)"
25
- raise TypeError, msg % err
26
- end
27
- hsh[ id ] = ç.__new__( t )
28
- else
29
- hsh[ ç.net.transition( id ) ]
30
- end
31
- end )
14
+ # First, prepare the hash of instances.
15
+ hsh = Hash.new do |hsh, id|
16
+ case id
17
+ when self then # missing key "id" is a Firing instance
18
+ hsh[ id.transition ]
19
+ when ç.net.Transition then
20
+ t = begin
21
+ ç.net.S_transitions( id ).first
22
+ rescue TypeError => err
23
+ msg = "Transition #{id} not " +
24
+ "recognized as tS transition in " +
25
+ "net #{ç.net}! (%s)"
26
+ raise TypeError, msg % err
27
+ end
28
+ hsh[ id ] = t.timed? ? ç.timed( t ) : ç.timeless( t )
29
+ else
30
+ hsh[ ç.net.transition( id ) ]
31
+ end
32
+ end
33
+ # And then, assign it to the :@instances variable.
34
+ ç.instance_variable_set :@instances, hsh
32
35
  end
33
36
  end
34
37
 
@@ -45,6 +48,21 @@ class YPetri::Net::State::Feature::Firing < YPetri::Net::State::Feature
45
48
  def of id
46
49
  new id
47
50
  end
51
+
52
+ # Expects a single timed transition and constructs a timed firing feature.
53
+ #
54
+ def timed id
55
+ __new__( net.T_tt( id ).first )
56
+ .tap { |i| i.instance_variable_set :@timed, true }
57
+ end
58
+
59
+ # Expects a single timeless transition and constructs a timeless firing
60
+ # feature.
61
+ #
62
+ def timeless id
63
+ __new__( net.t_tt( id ).first )
64
+ .tap { |i| i.instance_variable_set :@timed, false }
65
+ end
48
66
  end
49
67
 
50
68
  # The constructor of a marking feature takes exactly one argument (transition
@@ -54,22 +72,40 @@ class YPetri::Net::State::Feature::Firing < YPetri::Net::State::Feature
54
72
  @transition = net.transition( transition )
55
73
  end
56
74
 
57
- # Extracts the receiver marking feature from the argument. This can be
58
- # typically a simulation instance.
75
+ # Extracts the value of this feature from the target (eg. a simulation).
76
+ # If the receiver firing feature is timed, this method requires an additional
77
+ # named argument +:delta_time+, alias +:Δt+.
59
78
  #
60
- def extract_from arg, **nn
79
+ def extract_from arg, **named_args
61
80
  case arg
62
81
  when YPetri::Simulation then
63
- arg.send( :tS_transitions, [ transition ] ).first.firing
82
+ if timed? then
83
+ arg.send( :TS_transitions, transition ).first
84
+ .firing( named_args.must_have :delta_time, syn!: :Δt )
85
+ else
86
+ arg.send( :tS_transitions, transition ).first.firing
87
+ end
64
88
  else
65
89
  fail TypeError, "Argument type not supported!"
66
90
  end
67
91
  end
68
92
 
93
+ # Is the delta feature timed?
94
+ #
95
+ def timed?
96
+ @timed
97
+ end
98
+
99
+ # Opposite of +#timed?+.
100
+ #
101
+ def timeless?
102
+ ! timed?
103
+ end
104
+
69
105
  # Type of this feature.
70
106
  #
71
107
  def type
72
- :flux
108
+ :firing
73
109
  end
74
110
 
75
111
  # A string briefly describing the firing feature.
@@ -89,4 +125,11 @@ class YPetri::Net::State::Feature::Firing < YPetri::Net::State::Feature
89
125
  def inspect
90
126
  "<Feature::Firing of #{transition.name ? transition.name : transition}>"
91
127
  end
128
+
129
+ # Firing features are equal if they are of equal PS and refer
130
+ # to the same transition.
131
+ #
132
+ def == other
133
+ other.is_a? net.State.Feature.Firing and transition == other.transition
134
+ end
92
135
  end # YPetri::Net::State::Feature::Firing
@@ -17,7 +17,7 @@ class YPetri::Net::State::Feature::Flux < YPetri::Net::State::Feature
17
17
  hsh[ id.transition ]
18
18
  when ç.net.Transition then
19
19
  t = begin
20
- ç.net.TS_transitions( [ id ] ).first
20
+ ç.net.TS_transitions( id ).first
21
21
  rescue TypeError => err
22
22
  msg = "Transition #{id} not " +
23
23
  "recognized as TS transition in " +
@@ -60,7 +60,7 @@ class YPetri::Net::State::Feature::Flux < YPetri::Net::State::Feature
60
60
  def extract_from arg, **nn
61
61
  case arg
62
62
  when YPetri::Simulation then
63
- arg.send( :TS_transitions, [ transition ] ).first.flux
63
+ arg.send( :TS_transitions, transition ).first.flux
64
64
  else
65
65
  fail TypeError, "Argument type not supported!"
66
66
  end
@@ -89,4 +89,11 @@ class YPetri::Net::State::Feature::Flux < YPetri::Net::State::Feature
89
89
  def inspect
90
90
  "<Feature::Flux of #{transition.name ? transition.name : transition}>"
91
91
  end
92
+
93
+ # Flux features are equal if they are of equal PS and refer to the
94
+ # same transition.
95
+ #
96
+ def == other
97
+ other.is_a? net.State.Feature.Flux and transition == other.transition
98
+ end
92
99
  end # class YPetri::Net::State::Feature::Flux
@@ -4,6 +4,7 @@
4
4
  #
5
5
  class YPetri::Net::State::Feature::Gradient < YPetri::Net::State::Feature
6
6
  attr_reader :place, :transitions
7
+ alias tt transitions
7
8
 
8
9
  class << self
9
10
  # Customization of the Class#parametrize method.
@@ -16,25 +17,28 @@ class YPetri::Net::State::Feature::Gradient < YPetri::Net::State::Feature
16
17
  ꜧ[ [ id.place, transitions: id.transitions.sort_by( &:object_id ) ] ]
17
18
  else
18
19
  p = id.fetch( 0 )
19
- tt = id
20
- .fetch( 1 )
21
- .fetch( :transitions )
22
- if p.is_a? ç.net.Place and tt.all? { |t| t.is_a? ç.net.Transition }
23
- tt_sorted = tt.sort_by &:object_id
24
- if tt == tt_sorted then
25
- tt = begin
26
- ç.net.T_transitions( tt )
27
- rescue TypeError => err
28
- msg = "Transitions #{tt} not recognized as T " +
29
- "transitions in net #{ç.net}! (%s)"
30
- raise TypeError, msg % err
31
- end
32
- ꜧ[ id ] = ç.__new__( *id )
20
+ tt = id.fetch( 1 ).fetch( :transitions )
21
+ tt_array = Array( tt )
22
+ if tt == tt_array then
23
+ if p.is_a? ç.net.Place and tt.all? { |t| t.is_a? ç.net.Transition }
24
+ tt_sorted = tt.sort_by &:object_id
25
+ if tt == tt_sorted then
26
+ tt = begin
27
+ ç.net.T_Transitions( tt )
28
+ rescue TypeError => err
29
+ msg = "Transitions #{tt} not recognized as T " +
30
+ "transitions in net #{ç.net}! (%s)"
31
+ raise TypeError, msg % err
32
+ end
33
+ ꜧ[ id ] = ç.__new__( *id )
34
+ else
35
+ ꜧ[ [ p, transitions: tt.sort_by( &:object_id ) ] ]
36
+ end
33
37
  else
34
- ꜧ[ [ p, transitions: tt.sort_by( &:object_id ) ] ]
38
+ ꜧ[ [ ç.net.place( p ), transitions: ç.net.Transitions( tt ) ] ]
35
39
  end
36
40
  else
37
- ꜧ[ [ ç.net.place( p ), transitions: ç.net.transitions( tt ) ] ]
41
+ ꜧ[ [ p, transitions: tt_array ] ]
38
42
  end
39
43
  end
40
44
  end
@@ -63,7 +67,7 @@ class YPetri::Net::State::Feature::Gradient < YPetri::Net::State::Feature
63
67
  #
64
68
  def initialize *id
65
69
  @place = net.place id.fetch( 0 )
66
- @transitions = net.transitions id.fetch( 1 ).fetch( :transitions )
70
+ @transitions = net.Transitions id.fetch( 1 ).fetch( :transitions )
67
71
  end
68
72
 
69
73
  # Extracts the receiver gradient feature from the argument. This can be
@@ -72,7 +76,7 @@ class YPetri::Net::State::Feature::Gradient < YPetri::Net::State::Feature
72
76
  def extract_from arg, **nn
73
77
  case arg
74
78
  when YPetri::Simulation then
75
- arg.send( :T_transitions, transitions ).gradient.fetch( place )
79
+ arg.send( :T_Transitions, transitions ).gradient.fetch( place )
76
80
  else
77
81
  fail TypeError, "Argument type not supported!"
78
82
  end
@@ -93,7 +97,12 @@ class YPetri::Net::State::Feature::Gradient < YPetri::Net::State::Feature
93
97
  # Label for the gradient feature (to use in graphics etc.)
94
98
  #
95
99
  def label
96
- "∂:#{place.name}:#{transitions.size}tt"
100
+ "∂:#{place.name}:%s" %
101
+ if transitions.size == 1 then
102
+ transitions.first.name || transitions.first
103
+ else
104
+ "#{transitions.size}tt"
105
+ end
97
106
  end
98
107
 
99
108
  # Inspect string of the gradient feature.
@@ -102,4 +111,12 @@ class YPetri::Net::State::Feature::Gradient < YPetri::Net::State::Feature
102
111
  "<Feature::Gradient ∂:#{place.name || place}:[%s]>" %
103
112
  transitions.names( true ).join( ', ' )
104
113
  end
114
+
115
+ # Gradient features are equal if they are of equal PS and refer to
116
+ # the same place and transition set.
117
+ #
118
+ def == other
119
+ other.is_a? net.State.Feature.Gradient and
120
+ place == other.place && transitions == other.transitions
121
+ end
105
122
  end # class YPetri::Net::State::Feature::Gradient
@@ -19,9 +19,8 @@ class YPetri::Net::State::Feature::Marking < YPetri::Net::State::Feature
19
19
  p = begin
20
20
  ç.net.place id
21
21
  rescue TypeError => err
22
- msg = "Place #{id} not recognized " +
23
- "a place in net #{ç.net}! (%s)"
24
- raise TypeError, msg % err
22
+ raise TypeError, "Place #{id} not " +
23
+ "present in net #{ç.net}! (#{err})"
25
24
  end
26
25
  hsh[ id ] = ç.__new__( id )
27
26
  else
@@ -59,8 +58,7 @@ class YPetri::Net::State::Feature::Marking < YPetri::Net::State::Feature
59
58
  def extract_from arg, **nn
60
59
  case arg
61
60
  when YPetri::Simulation then
62
- # now we have to investigate the case when place.name is A_phase, why the heck does it fail
63
- arg.m( [ place ] ).first
61
+ arg.m( place ).first
64
62
  else
65
63
  fail TypeError, "Argument type not supported!"
66
64
  end
@@ -89,4 +87,11 @@ class YPetri::Net::State::Feature::Marking < YPetri::Net::State::Feature
89
87
  def inspect
90
88
  "<Feature::Marking of #{place.name ? place.name : place}>"
91
89
  end
90
+
91
+ # Marking features are equal if they are of equal PS and refer
92
+ # to the same place.
93
+ #
94
+ def == other
95
+ other.is_a? net.State.Feature.Marking and place == other.place
96
+ end
92
97
  end # YPetri::Net::State::Feature::Marking
@@ -8,6 +8,7 @@ class YPetri::Net::State::Feature
8
8
  require_relative 'feature/gradient'
9
9
  require_relative 'feature/flux'
10
10
  require_relative 'feature/delta'
11
+ require_relative 'feature/assignment'
11
12
 
12
13
  class << self
13
14
  def parametrize parameters
@@ -21,28 +22,40 @@ class YPetri::Net::State::Feature
21
22
  ç.instance_variable_set :@Gradient, Gradient.parametrize( State: sç )
22
23
  ç.instance_variable_set :@Flux, Flux.parametrize( State: sç )
23
24
  ç.instance_variable_set :@Delta, Delta.parametrize( State: sç )
25
+ ç.instance_variable_set :@Assignment, Assignment.parametrize( State: sç )
24
26
  end
25
27
  end
26
28
 
27
- delegate :net,
28
- to: "State()"
29
+ delegate :net, to: "State()"
29
30
 
31
+ # Marking feature constructor. Takes a single place identifying argument.
32
+ #
30
33
  def Marking id=L!
31
34
  return @Marking if id.local_object?
32
35
  case id
33
36
  when Marking() then id
34
37
  when Marking then Marking().of( id.place )
35
- else Marking().of( id ) end # assume it's a place
38
+ else Marking().of( id ) end # assume place
36
39
  end
37
40
 
41
+ # Firing feature constructor. Takes a single argument, which must identify
42
+ # an S transition (nonstoichiometric transitions don't have firing, though
43
+ # they do have action.)
44
+ #
38
45
  def Firing id=L!
39
46
  return @Firing if id.local_object?
40
47
  case id
41
48
  when Firing() then id
42
49
  when Firing then Firing().of( id.transition )
43
- else Firing().of( id ) end # assume it's a place
50
+ else Firing().of( id ) end # assume transition
44
51
  end
45
52
 
53
+ # Gradient feature constructor. Takes a single ordered argument, which must
54
+ # identify a place, and an optional named argument +:transitions+, which must
55
+ # contain an array of T transition identifyers (gradient is defined as time
56
+ # derivative, so timeless transitions are not eligible). If not given, the
57
+ # gradient feature is constructed with respect to all net's T transitions.
58
+ #
46
59
  def Gradient id=L!, transitions: net.T_tt
47
60
  return @Gradient if id.local_object?
48
61
  case id
@@ -50,32 +63,113 @@ class YPetri::Net::State::Feature
50
63
  when Gradient then
51
64
  Gradient().of( id.place, transitions: id.transitions )
52
65
  else
53
- Gradient().of( id, transitions: transitions )
54
- end # assume it's a place
66
+ Gradient().of( id, transitions: transitions ) # assume place
67
+ end
55
68
  end
56
69
 
70
+ # Flux feature constructor. Takes a single argument, which must identify
71
+ # a TS transition. Flux is defined as time derivative of firing.
72
+ #
57
73
  def Flux id=L!
58
74
  return @Flux if id.local_object?
59
75
  case id
60
76
  when Flux() then id
61
77
  when Flux then Flux().of( id.transition )
62
- else
63
- Flux().of( id )
64
- end # assume it's a place
78
+ else Flux().of( id ) end # assume transition
65
79
  end
66
80
 
81
+ # Delta feature constructor. Takes a single ordered argument, which must
82
+ # identify a place, and an optional named argument +:transitions+, which
83
+ # must contain an array of transition idetifyers. If not given, the delta
84
+ # feature is constructed with respect to all net's transitions.
85
+ #
86
+ # Furthermore, if the +:transitions+ argument is given, the transitions must
87
+ # be either all timeless, or all timed. Delta features are thus of 2 kinds:
88
+ # timed and timeless (can be inquired via +#timed?+). When used to extract
89
+ # values from the target object, timeless delta merely returns a value, while
90
+ # timed returns unary closure waiting for Δt argument to return delta for
91
+ # that Δt (if you want the rate directly, use a gradient feature).
92
+ #
67
93
  def Delta id=L!, transitions: net.tt
68
94
  return @Delta if id.local_object?
69
95
  case id
70
96
  when Delta() then id
71
97
  when Delta then
72
98
  Delta().of( id.place, transitions: id.transitions )
73
- else Delta().of( id, transitions: transitions ) end # assume it's a place
99
+ else
100
+ Delta().of( id, transitions: transitions )
101
+ end # assume place
102
+ end
103
+
104
+ # Assignment feature constructor. Takes a single ordered argument, which
105
+ # must identify a place, and an optional argument +:transition+, which
106
+ # must identify a single A (assignment) transition. The feature extracts
107
+ # the assignment action from the transition to the place. If the
108
+ # +:transition+ named argument is not given, the place's upstream arcs
109
+ # must contain exactly one A transition.
110
+ #
111
+ def Assignment id=L!, transition: L!
112
+ return @Assignment if id.local_object? && transition.local_object?
113
+ case id
114
+ when Assignment() then id
115
+ when Assignment then
116
+ Assignment().to( id.place, transition: id.transition )
117
+ else
118
+ fail ArgumentError, "No place given!" if id.local_object?
119
+ if transition.local_object? then
120
+ Assignment().to( id )
121
+ else
122
+ Assignment().to( id, transition: transition )
123
+ end
124
+ end
125
+ end
126
+
127
+ # Takes a single argument, and infers a feature from it in the following way:
128
+ # A +net.State.Feature+ instance is returned unchanged. Place or place id is
129
+ # converted to a marking feature. Otherwise, the argument is treated as a
130
+ # transition, and is converted to either a flux feature (if timed), or a
131
+ # firing feature (if timeless).
132
+ #
133
+ def infer_from_node( arg )
134
+ case arg
135
+ when self then return arg
136
+ when Marking then return Marking().of( arg.place )
137
+ when Firing then return Firing().of( arg.transition )
138
+ when Gradient then
139
+ return Gradient().of( arg.place, transitions: arg.transitions )
140
+ when Flux then return Flux().of( arg.transition )
141
+ when Delta then
142
+ return Delta().of( arg.place, transitions: arg.transitions )
143
+ when Assignment then
144
+ return Assignment().to( arg.place, transitions: arg.transition )
145
+ else # treated as a place or transition id
146
+ e, type = begin
147
+ [ net.place( arg ), :place ]
148
+ rescue TypeError, NameError
149
+ [ net.transition( arg ), :transition ]
150
+ end
151
+ end
152
+ case type
153
+ when :place then Marking( e )
154
+ when :transition then
155
+ fail TypeError, "Flux / firing features can only be auto-inferred " +
156
+ "from S transitions! (#{arg} was given)" unless e.S?
157
+ e.timed? ? Flux( e ) : Firing( e )
158
+ end
74
159
  end
75
160
  end # class << self
76
161
 
77
162
  delegate :net,
78
163
  :State,
79
- :Marking, :Firing, :Gradient, :Flux, :Delta,
80
164
  to: "self.class"
165
+
166
+ # Interpolation operator +%+ acts as an alias for the +#extract_from+ feature
167
+ # extraction method.
168
+ #
169
+ def % operand
170
+ args = Array( operand )
171
+ named_args = args.extract_options!
172
+ arg = args.first
173
+ extract_from arg, **named_args
174
+ end
81
175
  end # class YPetri::Net::State::Feature