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.
- 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"
|