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.
- checksums.yaml +4 -4
- data/lib/y_petri/agent/petri_net_related.rb +25 -5
- data/lib/y_petri/agent/selection.rb +12 -10
- data/lib/y_petri/agent/simulation_related.rb +14 -58
- data/lib/y_petri/agent.rb +15 -17
- data/lib/y_petri/core/timed/euler.rb +13 -15
- data/lib/y_petri/core/timed/pseudo_euler.rb +22 -24
- data/lib/y_petri/core/timed/quasi_euler.rb +15 -17
- data/lib/y_petri/core/timed.rb +42 -44
- data/lib/y_petri/core/timeless/pseudo_euler.rb +12 -14
- data/lib/y_petri/core/timeless.rb +10 -7
- data/lib/y_petri/core.rb +3 -3
- data/lib/y_petri/dsl.rb +46 -46
- data/lib/y_petri/fixed_assets.rb +8 -0
- data/lib/y_petri/net/data_set.rb +238 -0
- data/lib/y_petri/net/own_state.rb +63 -0
- data/lib/y_petri/net/state/feature/delta.rb +98 -71
- data/lib/y_petri/net/state/feature/firing.rb +51 -54
- data/lib/y_petri/net/state/feature/flux.rb +51 -55
- data/lib/y_petri/net/state/feature/gradient.rb +55 -59
- data/lib/y_petri/net/state/feature/marking.rb +55 -59
- data/lib/y_petri/net/state/feature.rb +65 -67
- data/lib/y_petri/net/state/features/record.rb +150 -43
- data/lib/y_petri/net/state/features.rb +252 -96
- data/lib/y_petri/net/state.rb +114 -106
- data/lib/y_petri/net/visualization.rb +3 -2
- data/lib/y_petri/net.rb +29 -24
- data/lib/y_petri/place/arcs.rb +3 -3
- data/lib/y_petri/place/guard.rb +35 -117
- data/lib/y_petri/place/guarded.rb +86 -0
- data/lib/y_petri/place.rb +6 -3
- data/lib/y_petri/simulation/element_representation.rb +2 -2
- data/lib/y_petri/simulation/elements.rb +3 -1
- data/lib/y_petri/simulation/feature_set.rb +3 -1
- data/lib/y_petri/simulation/marking_vector.rb +3 -1
- data/lib/y_petri/simulation/place_mapping.rb +3 -1
- data/lib/y_petri/simulation/places.rb +1 -1
- data/lib/y_petri/simulation/recorder.rb +60 -54
- data/lib/y_petri/simulation/timed/recorder.rb +12 -1
- data/lib/y_petri/simulation/timed.rb +173 -172
- data/lib/y_petri/simulation/transitions/access.rb +97 -29
- data/lib/y_petri/simulation.rb +11 -9
- data/lib/y_petri/transition/{assignment.rb → A.rb} +2 -2
- data/lib/y_petri/transition/{timed.rb → T.rb} +2 -2
- data/lib/y_petri/transition/arcs.rb +3 -3
- data/lib/y_petri/transition/cocking.rb +3 -3
- data/lib/y_petri/transition/{init.rb → construction_convenience.rb} +6 -53
- data/lib/y_petri/transition/{ordinary_timeless.rb → t.rb} +2 -2
- data/lib/y_petri/transition/type.rb +103 -0
- data/lib/y_petri/transition/type_information.rb +103 -0
- data/lib/y_petri/transition/types.rb +107 -0
- data/lib/y_petri/transition/usable_without_world.rb +14 -0
- data/lib/y_petri/transition.rb +87 -101
- data/lib/y_petri/version.rb +1 -1
- data/lib/y_petri/world/dependency.rb +30 -28
- data/lib/y_petri/world.rb +10 -8
- data/test/acceptance/basic_usage_test.rb +3 -3
- data/test/acceptance/simulation_test.rb +3 -3
- data/test/acceptance/simulation_with_physical_units_test.rb +2 -2
- data/test/acceptance/token_game_test.rb +2 -2
- data/test/acceptance/visualization_test.rb +3 -3
- data/test/acceptance_tests.rb +2 -2
- data/test/agent_test.rb +1 -1
- data/test/net_test.rb +41 -17
- data/test/place_test.rb +1 -1
- data/test/simulation_test.rb +39 -39
- data/test/transition_test.rb +1 -1
- data/test/world_test.rb +1 -1
- data/test/y_petri_test.rb +1 -1
- metadata +13 -8
- data/lib/y_petri/net/state/features/dataset.rb +0 -135
- data/lib/y_petri/transition/construction.rb +0 -311
data/lib/y_petri/net/state.rb
CHANGED
@@ -1,121 +1,129 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
66
|
-
|
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
|
-
|
98
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
96
|
+
delegate :net,
|
97
|
+
:Feature,
|
98
|
+
:Features,
|
99
|
+
:features,
|
100
|
+
:marking, :firing, :gradient, :flux, :delta,
|
101
|
+
to: "self.class"
|
104
102
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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
|
-
|
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 #
|
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
|
-
|
15
|
-
|
16
|
-
|
16
|
+
★ NameMagic # ★ means include
|
17
|
+
★ ElementAccess # to be below ElementAccess
|
18
|
+
★ Visualization
|
19
|
+
★ OwnState
|
17
20
|
|
18
21
|
class << self
|
19
|
-
|
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
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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.
|
109
|
+
transitions.any? { |t| t.functional? }
|
105
110
|
end
|
106
111
|
|
107
|
-
# Is the net
|
112
|
+
# Is the net _timed_?
|
108
113
|
#
|
109
114
|
def timed?
|
110
115
|
transitions.any? { |t| t.timed? }
|
data/lib/y_petri/place/arcs.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
#
|
1
|
+
# encoding: utf-8
|
2
2
|
|
3
3
|
# Connectivity aspect of a Petri net place.
|
4
4
|
#
|
5
|
-
|
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
|
data/lib/y_petri/place/guard.rb
CHANGED
@@ -1,132 +1,50 @@
|
|
1
|
-
#
|
1
|
+
# encoding: utf-8
|
2
2
|
|
3
|
-
#
|
3
|
+
# Marking guard of a place.
|
4
4
|
#
|
5
|
-
class YPetri::Place
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
56
|
-
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
#
|
64
|
-
#
|
65
|
-
#
|
66
|
-
#
|
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
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
#
|
95
|
-
#
|
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
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
-
#
|
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
|
117
|
-
|
118
|
-
|
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
|
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
|
-
|
10
|
-
|
10
|
+
★ NameMagic # ★ means include
|
11
|
+
★ Arcs
|
12
|
+
★ Guarded
|
13
|
+
★ YPetri::World::Dependency
|
11
14
|
|
12
15
|
class << self
|
13
|
-
|
16
|
+
★ YPetri::World::Dependency
|
14
17
|
end
|
15
18
|
|
16
19
|
delegate :world, to: "self.class"
|