y_petri 2.1.3 → 2.1.6

Sign up to get free protection for your applications and to get access to all the features.
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