y_petri 2.1.3 → 2.1.6

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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/lib/y_petri/agent/petri_net_related.rb +25 -5
  3. data/lib/y_petri/agent/selection.rb +12 -10
  4. data/lib/y_petri/agent/simulation_related.rb +14 -58
  5. data/lib/y_petri/agent.rb +15 -17
  6. data/lib/y_petri/core/timed/euler.rb +13 -15
  7. data/lib/y_petri/core/timed/pseudo_euler.rb +22 -24
  8. data/lib/y_petri/core/timed/quasi_euler.rb +15 -17
  9. data/lib/y_petri/core/timed.rb +42 -44
  10. data/lib/y_petri/core/timeless/pseudo_euler.rb +12 -14
  11. data/lib/y_petri/core/timeless.rb +10 -7
  12. data/lib/y_petri/core.rb +3 -3
  13. data/lib/y_petri/dsl.rb +46 -46
  14. data/lib/y_petri/fixed_assets.rb +8 -0
  15. data/lib/y_petri/net/data_set.rb +238 -0
  16. data/lib/y_petri/net/own_state.rb +63 -0
  17. data/lib/y_petri/net/state/feature/delta.rb +98 -71
  18. data/lib/y_petri/net/state/feature/firing.rb +51 -54
  19. data/lib/y_petri/net/state/feature/flux.rb +51 -55
  20. data/lib/y_petri/net/state/feature/gradient.rb +55 -59
  21. data/lib/y_petri/net/state/feature/marking.rb +55 -59
  22. data/lib/y_petri/net/state/feature.rb +65 -67
  23. data/lib/y_petri/net/state/features/record.rb +150 -43
  24. data/lib/y_petri/net/state/features.rb +252 -96
  25. data/lib/y_petri/net/state.rb +114 -106
  26. data/lib/y_petri/net/visualization.rb +3 -2
  27. data/lib/y_petri/net.rb +29 -24
  28. data/lib/y_petri/place/arcs.rb +3 -3
  29. data/lib/y_petri/place/guard.rb +35 -117
  30. data/lib/y_petri/place/guarded.rb +86 -0
  31. data/lib/y_petri/place.rb +6 -3
  32. data/lib/y_petri/simulation/element_representation.rb +2 -2
  33. data/lib/y_petri/simulation/elements.rb +3 -1
  34. data/lib/y_petri/simulation/feature_set.rb +3 -1
  35. data/lib/y_petri/simulation/marking_vector.rb +3 -1
  36. data/lib/y_petri/simulation/place_mapping.rb +3 -1
  37. data/lib/y_petri/simulation/places.rb +1 -1
  38. data/lib/y_petri/simulation/recorder.rb +60 -54
  39. data/lib/y_petri/simulation/timed/recorder.rb +12 -1
  40. data/lib/y_petri/simulation/timed.rb +173 -172
  41. data/lib/y_petri/simulation/transitions/access.rb +97 -29
  42. data/lib/y_petri/simulation.rb +11 -9
  43. data/lib/y_petri/transition/{assignment.rb → A.rb} +2 -2
  44. data/lib/y_petri/transition/{timed.rb → T.rb} +2 -2
  45. data/lib/y_petri/transition/arcs.rb +3 -3
  46. data/lib/y_petri/transition/cocking.rb +3 -3
  47. data/lib/y_petri/transition/{init.rb → construction_convenience.rb} +6 -53
  48. data/lib/y_petri/transition/{ordinary_timeless.rb → t.rb} +2 -2
  49. data/lib/y_petri/transition/type.rb +103 -0
  50. data/lib/y_petri/transition/type_information.rb +103 -0
  51. data/lib/y_petri/transition/types.rb +107 -0
  52. data/lib/y_petri/transition/usable_without_world.rb +14 -0
  53. data/lib/y_petri/transition.rb +87 -101
  54. data/lib/y_petri/version.rb +1 -1
  55. data/lib/y_petri/world/dependency.rb +30 -28
  56. data/lib/y_petri/world.rb +10 -8
  57. data/test/acceptance/basic_usage_test.rb +3 -3
  58. data/test/acceptance/simulation_test.rb +3 -3
  59. data/test/acceptance/simulation_with_physical_units_test.rb +2 -2
  60. data/test/acceptance/token_game_test.rb +2 -2
  61. data/test/acceptance/visualization_test.rb +3 -3
  62. data/test/acceptance_tests.rb +2 -2
  63. data/test/agent_test.rb +1 -1
  64. data/test/net_test.rb +41 -17
  65. data/test/place_test.rb +1 -1
  66. data/test/simulation_test.rb +39 -39
  67. data/test/transition_test.rb +1 -1
  68. data/test/world_test.rb +1 -1
  69. data/test/y_petri_test.rb +1 -1
  70. metadata +13 -8
  71. data/lib/y_petri/net/state/features/dataset.rb +0 -135
  72. data/lib/y_petri/transition/construction.rb +0 -311
@@ -1,121 +1,129 @@
1
1
  # encoding: utf-8
2
2
 
3
- class YPetri::Net
4
- # Petri net state (marking of all its places).
5
- #
6
- class State < Array
7
- require_relative 'state/feature'
8
- require_relative 'state/features'
3
+ # An array whose elements correspond to the full marking of the net's places.
4
+ #
5
+ class YPetri::Net::State < Array
6
+ require_relative 'state/feature'
7
+ require_relative 'state/features'
9
8
 
10
- class << self
11
- # Customization of the parametrize method for the State class: Its
12
- # dependents Feature and Features (ie. feature set) are also parametrized.
13
- #
14
- def parametrize net: (fail ArgumentError, "No owning net!")
15
- Class.new( self ).tap do |subclass|
16
- subclass.define_singleton_method :net do net end
17
- subclass.param_class( { Feature: Feature,
18
- Features: Features },
19
- with: { State: subclass } )
20
- end
9
+ class << self
10
+ # Customization of the parametrize method for the State class: Its
11
+ # dependents Feature and Features (ie. feature set) are also parametrized.
12
+ #
13
+ def parametrize net: ( fail ArgumentError, "No owning net!" )
14
+ Class.new( self ).tap do |ç|
15
+ ç.define_singleton_method :net do net end
16
+ ç.param_class( { Feature: Feature,
17
+ Features: Features },
18
+ with: { State: ç } )
21
19
  end
20
+ end
22
21
 
23
- delegate :Marking,
24
- :Firing,
25
- :Gradient,
26
- :Flux,
27
- :Delta,
28
- to: "Feature()"
29
-
30
- alias __new__ new
31
-
32
- # Revives a state from a record and a given set of marking clamps.
33
- #
34
- def new record, marking_clamps: {}
35
- cc = marking_clamps.with_keys { |k| net.place k }.with_values! do |v|
36
- case v
37
- when YPetri::Place then v.marking
38
- when ~:call then v.call
39
- else v end
40
- end
41
-
42
- record = features( marking: net.pp - cc.keys ).load( record )
22
+ delegate :Marking,
23
+ :Firing,
24
+ :Gradient,
25
+ :Flux,
26
+ :Delta,
27
+ to: "Feature()"
43
28
 
44
- __new__ net.pp.map do |p|
45
- begin; cc.fetch p; rescue IndexError
46
- record.fetch Marking().of( p )
47
- end
29
+ # Returns the feature identified by the argument.
30
+ #
31
+ def feature *id
32
+ fail ArgumentError, "No feature identifier!" if id.empty?
33
+ case id.first
34
+ when Feature() then id.first
35
+ when Feature then id.first.class.new( id.first )
36
+ else
37
+ msg = "Malformed feature identifier!"
38
+ fail ArgumentError, msg unless id.size == 1 and id.first.is_a? Hash
39
+ ꜧ = id.first
40
+ fail ArgumentError, msg unless ꜧ.size == 1
41
+ key, val = ꜧ.keys.first, ꜧ.values.first
42
+ recognized = :marking, :firing, :gradient, :flux, :delta
43
+ msg = "Unrecognized feature: #{key}"
44
+ fail ArgumentError, msg unless recognized.include? key
45
+ # And now, with everything clean...
46
+ case key
47
+ when :marking then Marking( val )
48
+ when :firing then Firing( val )
49
+ when :flux then Flux( val )
50
+ when :gradient then Gradient( *val )
51
+ when :delta then Delta( *val )
48
52
  end
49
53
  end
54
+ end
50
55
 
51
- # Returns the feature identified by the argument.
52
- #
53
- def feature id
54
- case id
55
- when Feature() then id
56
- when Feature then id.class.new( id )
57
- else
58
- features( id ).tap do |ff|
59
- ff.size == 1 or fail ArgumentError, "Argument #{id} must identify " +
60
- "exactly 1 feature!"
61
- end.first
62
- end
56
+ # If the argument is an array of features, or another Features instance,
57
+ # a feature set based on this array is returned. But the real purpose of
58
+ # this method is to allow hash-type argument, with keys +:marking+,
59
+ # +:firing+, +:gradient+, +:flux+ and +:delta+, specifying the respective
60
+ # features. For +:marking+, an array of places (or Marking features) is
61
+ # expected. For +:firing+ and +:flux+, an array of transitions (or Firing
62
+ # / Flux features) is expected. For +:gradient+ and +:delta+, a hash value
63
+ # is expected, containing keys +:places+ and +:transitions+, specifying
64
+ # for which place set / transition set should gradient / delta features
65
+ # be constructed. More in detail, values supplied under keys +:marking+,
66
+ # +:firing+, +:gradient+, +:flux+ and +:delta+ are delegated to
67
+ # +Features.marking+, +Features.firing+, +Features.gradient+ and
68
+ # +Features.flux+ methods, and their results are joined into a single
69
+ # feature set.
70
+ #
71
+ def features arg
72
+ case arg
73
+ when Features(), Array then Features().new( arg )
74
+ else # the real job of the method
75
+ marking = arg[:marking] || []
76
+ firing = arg[:firing] || [] # array of tS transitions
77
+ gradient = arg[:gradient] || [ [], transitions: [] ]
78
+ flux = arg[:flux] || [] # array of TS transitions
79
+ delta = arg[:delta] || [ [], transitions: [] ]
80
+ [ Features().marking( marking ),
81
+ Features().firing( firing ),
82
+ Features().gradient( *gradient ),
83
+ Features().flux( flux ),
84
+ Features().delta( *delta ) ].reduce :+
63
85
  end
86
+ end
64
87
 
65
- # If the argument is an array of features, or another Features instance,
66
- # a feature set based on this array is returned. But the real purpose of
67
- # this method is to allow hash-type argument, with keys +:marking+,
68
- # +:firing+, +:gradient+, +:flux+ and +:delta+, specifying the respective
69
- # features. For +:marking+, an array of places (or Marking features) is
70
- # expected. For +:firing+ and +:flux+, an array of transitions (or Firing
71
- # / Flux features) is expected. For +:gradient+ and +:delta+, a hash value
72
- # is expected, containing keys +:places+ and +:transitions+, specifying
73
- # for which place set / transition set should gradient / delta features
74
- # be constructed. More in detail, values supplied under keys +:marking+,
75
- # +:firing+, +:gradient+, +:flux+ and +:delta+ are delegated to
76
- # +Features.marking+, +Features.firing+, +Features.gradient+ and
77
- # +Features.flux+ methods, and their results are joined into a single
78
- # feature set.
79
- #
80
- def features arg
81
- case arg
82
- when Features(), Array then Features().new( arg )
83
- else # the real job of the method
84
- marking = arg[:marking] || []
85
- firing = arg[:firing] || [] # array of tS transitions
86
- gradient = arg[:gradient] || [ [], transitions: [] ]
87
- flux = arg[:flux] || [] # array of TS transitions
88
- delta = arg[:delta] || [ [], transitions: [] ]
89
- [ Features().marking( marking ),
90
- Features().firing( firing ),
91
- Features().gradient( *gradient ),
92
- Features().flux( flux ),
93
- Features().delta( *delta ) ].reduce :+
94
- end
95
- end
88
+ delegate :marking, :firing, :gradient, :flux, :delta, to: "Features()"
89
+ end
96
90
 
97
- delegate :marking, :firing, :gradient, :flux, :delta, to: "Features()"
98
- end
91
+ # For non-parametrized vesion of the class, the class instance variables
92
+ # hold the non-parametrized dependent classes.
93
+ #
94
+ @Feature, @Features = Feature, Features
99
95
 
100
- # For non-parametrized vesion of the class, the class instance variables
101
- # hold the non-parametrized dependent classes.
102
- #
103
- @Feature, @Features = Feature, Features
96
+ delegate :net,
97
+ :Feature,
98
+ :Features,
99
+ :features,
100
+ :marking, :firing, :gradient, :flux, :delta,
101
+ to: "self.class"
104
102
 
105
- delegate :net,
106
- :Feature,
107
- :Features,
108
- :features,
109
- :marking, :firing, :gradient, :flux, :delta,
110
- to: "self.class"
103
+ # Given a set of clamped places, this method outputs a Record instance
104
+ # containing the marking of the free places (complementary to the supplied
105
+ # set of clamped places). I no set of clamped places is supplied, it is
106
+ # considered empty.
107
+ #
108
+ def to_record clamped_places=[]
109
+ free_places = case clamped_places
110
+ when Hash then to_record( clamped_places.keys )
111
+ else
112
+ free_places = places - places( clamped_places )
113
+ end
114
+ features( marking: free_places ).Record.load markings( free_places )
115
+ end
111
116
 
112
- # Reconstructs a simulation from the current state instance, given marking
113
- # clamps and other simulation settings.
114
- #
115
- def reconstruct marking_clamps: {}, **settings
116
- net.simulation marking: to_hash,
117
- marking_clamps: marking_clamps,
118
- **settings
119
- end
120
- end # class State
121
- end # YPetri::Net
117
+ # Marking of a single given place in this state.
118
+ #
119
+ def marking place_id
120
+ self[ places.index place( place_id ) ]
121
+ end
122
+
123
+ # Returns an array of markings of particular places in this state..
124
+ #
125
+ def markings place_ids=nil
126
+ return markings( places ) if place_ids.nil?
127
+ place_ids.map &:marking
128
+ end
129
+ end # YPetri::Net::State
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Own visualization capabilities of a Petri net.
4
4
  #
5
- class YPetri::Net
5
+ module YPetri::Net::Visualization
6
6
  # Visualizes the net with Graphviz.
7
7
  #
8
8
  def visualize
@@ -51,6 +51,7 @@ class YPetri::Net
51
51
  end
52
52
  }
53
53
  # Generate output image.
54
+ puts File.expand_path "~/y_petri_graph.png"
54
55
  γ.output png: File.expand_path( "~/y_petri_graph.png" )
55
56
  # require 'y_support/kde'
56
57
  YSupport::KDE.show_file_with_kioclient File.expand_path( "~/y_petri_graph.png" )
@@ -64,4 +65,4 @@ class YPetri::Net
64
65
  system "sleep 0.2; kioclient exec 'file:%s'" %
65
66
  File.expand_path( '.', file_name )
66
67
  end
67
- end # class YPetri::Net
68
+ end # module YPetri::Net::Visualization
data/lib/y_petri/net.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  #encoding: utf-8
2
2
 
3
- require_relative 'net/visualization'
4
3
  require_relative 'net/element_access'
4
+ require_relative 'net/visualization'
5
+ require_relative 'net/own_state'
6
+ require_relative 'net/data_set'
5
7
  require_relative 'net/state'
6
8
 
7
9
  # Represents a _Petri net_: A collection of places and transitions. The
@@ -11,12 +13,13 @@ require_relative 'net/state'
11
13
  # neighbors of elements (places or transitions).
12
14
  #
13
15
  class YPetri::Net
14
- include NameMagic
15
- include YPetri::World::Dependency # it is important for the Dependency
16
- include ElementAccess # to be below ElementAccess
16
+ NameMagic # ★ means include
17
+ ElementAccess # to be below ElementAccess
18
+ Visualization
19
+ ★ OwnState
17
20
 
18
21
  class << self
19
- include YPetri::World::Dependency
22
+ YPetri::World::Dependency
20
23
 
21
24
  # Constructs a net containing a particular set of elements.
22
25
  #
@@ -26,6 +29,7 @@ class YPetri::Net
26
29
  end
27
30
 
28
31
  delegate :world, to: "self.class"
32
+ delegate :Place, :Transition, :Net, to: :world
29
33
 
30
34
  # Takes 2 arguments (+:places+ and +:transitions+) and builds a net from them.
31
35
  #
@@ -54,6 +58,8 @@ class YPetri::Net
54
58
  def include_transition id
55
59
  tr = Transition().instance( id )
56
60
  return false if includes_transition? tr
61
+ msg = "Transition #{tr} has arcs to places outside #{self}!"
62
+ fail msg unless tr.arcs.all? { |p| includes_place? p }
57
63
  true.tap { @transitions << tr }
58
64
  end
59
65
 
@@ -79,32 +85,31 @@ class YPetri::Net
79
85
  # Includes an element in the net.
80
86
  #
81
87
  def << element_id
82
- element_type, element = begin
83
- [ :place,
84
- self.class.place( element_id ) ]
85
- rescue NameError, TypeError
86
- begin
87
- [ :transition,
88
- self.class.transition( element_id ) ]
89
- rescue NameError, TypeError => err
90
- msg = "Current world contains no place or" +
91
- "transition identified by #{element_id}!"
92
- raise TypeError, "#{msg} (#{err})"
93
- end
94
- end
95
- case element_type
96
- when :place then include_place( element )
97
- when :transition then include_transition( element )
98
- else fail "Mangled method YPetri::Net#<<!" end
88
+ begin
89
+ element = self.class.place( element_id )
90
+ type = :place
91
+ rescue NameError, TypeError
92
+ begin
93
+ element = self.class.transition( element_id )
94
+ type = :transition
95
+ rescue NameError, TypeError => err
96
+ raise TypeError, "Current world contains no place or transition " +
97
+ "identified by #{element_id}! (#{err})"
98
+ end
99
+ end
100
+ # Separated to minimize the code inside rescue clause:
101
+ if type == :place then include_place element
102
+ elsif type == :transition then include_transition element
103
+ else fail "Implementation error in YPetri::Net#<<!" end
99
104
  end
100
105
 
101
106
  # Is the net _functional_?
102
107
  #
103
108
  def functional?
104
- transitions.all? { |t| t.functional? }
109
+ transitions.any? { |t| t.functional? }
105
110
  end
106
111
 
107
- # Is the net <em>timed</em>?
112
+ # Is the net _timed_?
108
113
  #
109
114
  def timed?
110
115
  transitions.any? { |t| t.timed? }
@@ -1,8 +1,8 @@
1
- # -*- coding: utf-8 -*-
1
+ # encoding: utf-8
2
2
 
3
3
  # Connectivity aspect of a Petri net place.
4
4
  #
5
- class YPetri::Place
5
+ module YPetri::Place::Arcs
6
6
  # Transitions that can directly add/remove tokens from this place. Aliased as
7
7
  # +#upstream_transitions+ and +#ϝ+. (Digamma resembles "f", meaning function,
8
8
  # well known from existing spreadsheet software.)
@@ -93,4 +93,4 @@ class YPetri::Place
93
93
  def register_downstream_transition( transition )
94
94
  @downstream_arcs << transition
95
95
  end
96
- end # class YPetri::Place
96
+ end # class YPetri::Place::Arcs
@@ -1,132 +1,50 @@
1
- # -*- coding: utf-8 -*-
1
+ # encoding: utf-8
2
2
 
3
- # Guard mechanics aspect of a place.
3
+ # Marking guard of a place.
4
4
  #
5
- class YPetri::Place
6
- # Marking guard.
7
- #
8
- class Guard
9
- ERRMSG = -> m, of, assert do
10
- "Marking #{m}:#{m.class}" +
11
- if of then " of #{of.name || of rescue of}" else '' end +
12
- " #{assert}!"
13
- end
14
-
15
- attr_reader :place, :assertion, :block
16
-
17
- # Requires a NL guard assertion (used in GuardError messages), and a guard
18
- # block expressing the same assertion formally, in code. Attention: *Only
19
- # _false_ result is considered a failure! If the block returns _nil_, the
20
- # guard has passed!* When +YPetri::Guard+ is in action (typically via its
21
- # +#validate+ method), it raises +YPetri::GuardError+ if the guard block
22
- # returns _false_. However, the guard block is welcome to raise +GuardError+
23
- # on its own, and for this purpose, it is evaluated inside a special "Lab"
24
- # object, with +#fail+ method redefined so as to accept no arguments, and
25
- # automatically raise appropriately worded +GuardError+. See also:
26
- # {+YPetri#guard+ method}[rdoc-ref:YPetri::guard].
27
- #
28
- def initialize( assertion_NL_string, place: nil, &block )
29
- @place, @assertion, @block = place, assertion_NL_string, block
30
- @Lab = Class.new BasicObject do
31
- def initialize λ; @λ = λ end
32
- def fail; @λ.call end
33
- end
34
- end
35
-
36
- # Validates a supplied marking value against the guard block. Raises
37
- # +YPetri::GuardError+ if the guard fails, otherwise returns _true_.
38
- #
39
- def validate( marking )
40
- λ = __fail__( marking, assertion )
41
- λ.call if @Lab.new( λ ).instance_exec( marking, &block ) == false
42
- return true
43
- end
44
-
45
- private
46
-
47
- # Constructs the fail closure.
48
- #
49
- def __fail__ marking, assertion
50
- pl = place
51
- -> { fail YPetri::GuardError, ERRMSG.( marking, pl, assertion ) }
52
- end
5
+ class YPetri::Place::Guard
6
+ ERRMSG = -> m, of, assert do
7
+ "Marking #{m}:#{m.class}" +
8
+ if of then " of #{of.name || of rescue of}" else '' end +
9
+ " #{assert}!"
53
10
  end
54
11
 
55
- # Expects a guard assertion in natural language, and a guard block. Guard
56
- # block is a unary block capable of validating a marking value. The validation
57
- # is considered as having failed if:
58
- #
59
- # 1. The block returns _false_.
60
- # 2. The block raises +YPetri::GuardError+.
61
- #
62
- # In all other cases, including the block returning _nil_, the validation is
63
- # considered as having passed! The block is evaluated in the context of a
64
- # special "Lab" object, which has +#fail+ method redefined so that it can
65
- # (and must) be called without parameters, and produces an appropriately
66
- # worded +GuardError+. (Other exceptions can be still raised using +#raise+
67
- # method.)
12
+ attr_reader :place, :assertion, :block
13
+
14
+ # Requires a NL guard assertion (used in GuardError messages), and a guard
15
+ # block expressing the same assertion formally, in code. Attention: *Only
16
+ # _false_ result is considered a failure! If the block returns _nil_, the
17
+ # guard has passed!* When +YPetri::Guard+ is in action (typically via its
18
+ # +#validate+ method), it raises +YPetri::GuardError+ if the guard block
19
+ # returns _false_. However, the guard block is welcome to raise +GuardError+
20
+ # on its own, and for this purpose, it is evaluated inside a special "Lab"
21
+ # object, with +#fail+ method redefined so as to accept no arguments, and
22
+ # automatically raise appropriately worded +GuardError+. See also:
23
+ # {+YPetri#guard+ method}[rdoc-ref:YPetri::guard].
68
24
  #
69
- # As for the NL assertion, apart from self-documenting the code, it is used
70
- # for constructing appropriately worded +GuardError+ messages:
71
- #
72
- # guard "should be a number" do |m| fail unless m.is_a? Numeric end
73
- #
74
- # Then +guard! :foobar+ raises +GuardError+ with message "Marking foobar:Symbol
75
- # should be a number!"
76
- #
77
- # The method returns the reference to the +YPetri::Guard+ object, that has
78
- # been constructed and already included in the collection of this place's
79
- # guards.
80
- #
81
- # Finally, this method is overloaded in such way, that if no block is
82
- # given to it, it acts as a frontend for the +#federated_guard_closure+
83
- # method: It either applies the federated closure to the marking value given
84
- # in the argument, or returns the federated closure itself if no arguemnts
85
- # were given (behaving as +#federated_guard_closure+ alias in this case).
86
- #
87
- def guard *args, &block
88
- if block then @guards << Guard.new( *args, place: name || self, &block )
89
- elsif args.size == 1 then federated_guard_closure.( args[0] )
90
- elsif args.empty? then federated_guard_closure
25
+ def initialize( assertion_NL_string, place: nil, &block )
26
+ @place, @assertion, @block = place, assertion_NL_string, block
27
+ @Lab = Class.new BasicObject do
28
+ def initialize λ; = λ end
29
+ def fail; @λ.call end
91
30
  end
92
31
  end
93
32
 
94
- # Returns a joint guard closure, composed of all the guards defined for the
95
- # place at the moment. Joint closure passes if and only if all the guard
96
- # blocks pass for the given marking value.
33
+ # Validates a supplied marking value against the guard block. Raises
34
+ # +YPetri::GuardError+ if the guard fails, otherwise returns _true_.
97
35
  #
98
- def federated_guard_closure
99
- place_name, lineup = name.to_s, guards.dup
100
- -> m { lineup.each { |g| g.validate( m ) }; return m }
101
- end
102
-
103
- # Applies guards on the marking currently owned by the place.
104
- #
105
- def guard!
106
- guard.( marking )
36
+ def validate( marking )
37
+ λ = __fail__( marking, assertion )
38
+ λ.call if @Lab.new( λ ).instance_exec( marking, &block ) == false
39
+ return true
107
40
  end
108
41
 
109
42
  private
110
43
 
111
- # If no guards were specified by the user, this method can make them up in a
112
- # standard way, using user-supplied marking / default marking as a type
113
- # reference. Numeric types are an exception – they are considered mutually
114
- # interchangeable, except complex numbers.
44
+ # Constructs the fail closure.
115
45
  #
116
- def add_default_guards!( reference_marking )
117
- case reference_marking
118
- when Complex then marking "should be Numeric" do |m| m.is_a? Numeric end
119
- when Numeric then
120
- marking "should be Numeric" do |m| m.is_a? Numeric end
121
- marking "should not be complex" do |m| fail if m.is_a? Complex end
122
- marking "should not be negative" do |m| m >= 0 end
123
- when nil then # no guards
124
- when true, false then marking "should be Boolean" do |m| m == !!m end
125
- else
126
- reference_marking.class.tap do |klass|
127
- marking "should be a #{klass}" do |m| m.is_a? klass end
128
- end
129
- end
130
- return nil
46
+ def __fail__ marking, assertion
47
+ pl = place
48
+ -> { fail YPetri::GuardError, ERRMSG.( marking, pl, assertion ) }
131
49
  end
132
- end # class YPetri::Place
50
+ end
@@ -0,0 +1,86 @@
1
+ # encoding: utf-8
2
+
3
+ # A mixin to make a place support guards.
4
+ #
5
+ module YPetri::Place::Guarded
6
+ # Expects a guard assertion in natural language, and a guard block. Guard
7
+ # block is a unary block capable of validating a marking value. The validation
8
+ # is considered as having failed if:
9
+ #
10
+ # 1. The block returns _false_.
11
+ # 2. The block raises +YPetri::GuardError+.
12
+ #
13
+ # In all other cases, including the block returning _nil_, the validation is
14
+ # considered as having passed! The block is evaluated in the context of a
15
+ # special "Lab" object, which has +#fail+ method redefined so that it can
16
+ # (and must) be called without parameters, and produces an appropriately
17
+ # worded +GuardError+. (Other exceptions can be still raised using +#raise+
18
+ # method.)
19
+ #
20
+ # As for the NL assertion, apart from self-documenting the code, it is used
21
+ # for constructing appropriately worded +GuardError+ messages:
22
+ #
23
+ # guard "should be a number" do |m| fail unless m.is_a? Numeric end
24
+ #
25
+ # Then +guard! :foobar+ raises +GuardError+ with message "Marking foobar:Symbol
26
+ # should be a number!"
27
+ #
28
+ # The method returns the reference to the +YPetri::Guard+ object, that has
29
+ # been constructed and already included in the collection of this place's
30
+ # guards.
31
+ #
32
+ # Finally, this method is overloaded in such way, that if no block is
33
+ # given to it, it acts as a frontend for the +#federated_guard_closure+
34
+ # method: It either applies the federated closure to the marking value given
35
+ # in the argument, or returns the federated closure itself if no arguemnts
36
+ # were given (behaving as +#federated_guard_closure+ alias in this case).
37
+ #
38
+ def guard *args, &block
39
+ if block then
40
+ @guards << YPetri::Place::Guard.new( *args, place: name || self, &block )
41
+ elsif args.size == 1 then
42
+ federated_guard_closure.( args[0] )
43
+ elsif args.empty? then
44
+ federated_guard_closure
45
+ end
46
+ end
47
+
48
+ # Returns a joint guard closure, composed of all the guards defined for the
49
+ # place at the moment. Joint closure passes if and only if all the guard
50
+ # blocks pass for the given marking value.
51
+ #
52
+ def federated_guard_closure
53
+ place_name, lineup = name.to_s, guards.dup
54
+ -> m { lineup.each { |g| g.validate( m ) }; return m }
55
+ end
56
+
57
+ # Applies guards on the marking currently owned by the place.
58
+ #
59
+ def guard!
60
+ guard.( marking )
61
+ end
62
+
63
+ private
64
+
65
+ # If no guards were specified by the user, this method can make them up in a
66
+ # standard way, using user-supplied marking / default marking as a type
67
+ # reference. Numeric types are an exception – they are considered mutually
68
+ # interchangeable, except complex numbers.
69
+ #
70
+ def add_default_guards!( reference_marking )
71
+ case reference_marking
72
+ when Complex then marking "should be Numeric" do |m| m.is_a? Numeric end
73
+ when Numeric then
74
+ marking "should be Numeric" do |m| m.is_a? Numeric end
75
+ marking "should not be complex" do |m| fail if m.is_a? Complex end
76
+ marking "should not be negative" do |m| m >= 0 end
77
+ when nil then # no guards
78
+ when true, false then marking "should be Boolean" do |m| m == !!m end
79
+ else
80
+ reference_marking.class.tap do |klass|
81
+ marking "should be a #{klass}" do |m| m.is_a? klass end
82
+ end
83
+ end
84
+ return nil
85
+ end
86
+ end # module YPetri::Place::Guarded
data/lib/y_petri/place.rb CHANGED
@@ -1,16 +1,19 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require_relative 'place/guard'
4
+ require_relative 'place/guarded'
4
5
  require_relative 'place/arcs'
5
6
 
6
7
  # Represents a Petri net place.
7
8
  #
8
9
  class YPetri::Place
9
- include NameMagic
10
- include YPetri::World::Dependency
10
+ NameMagic # ★ means include
11
+ Arcs
12
+ ★ Guarded
13
+ ★ YPetri::World::Dependency
11
14
 
12
15
  class << self
13
- include YPetri::World::Dependency
16
+ YPetri::World::Dependency
14
17
  end
15
18
 
16
19
  delegate :world, to: "self.class"
@@ -4,8 +4,8 @@
4
4
  #
5
5
  class YPetri::Simulation
6
6
  class ElementRepresentation
7
- include NameMagic
8
- include Dependency
7
+ NameMagic
8
+ Dependency
9
9
 
10
10
  attr_reader :source # source place
11
11