y_petri 2.2.4 → 2.3.2

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