y_petri 1.0.0
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 +7 -0
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +31 -0
- data/Rakefile +2 -0
- data/lib/y_petri/demonstrator.rb +164 -0
- data/lib/y_petri/demonstrator_2.rb +176 -0
- data/lib/y_petri/demonstrator_3.rb +150 -0
- data/lib/y_petri/demonstrator_4.rb +217 -0
- data/lib/y_petri/manipulator.rb +598 -0
- data/lib/y_petri/net.rb +458 -0
- data/lib/y_petri/place.rb +189 -0
- data/lib/y_petri/simulation.rb +1313 -0
- data/lib/y_petri/timed_simulation.rb +281 -0
- data/lib/y_petri/transition.rb +921 -0
- data/lib/y_petri/version.rb +3 -0
- data/lib/y_petri/workspace/instance_methods.rb +254 -0
- data/lib/y_petri/workspace/parametrized_subclassing.rb +26 -0
- data/lib/y_petri/workspace.rb +16 -0
- data/lib/y_petri.rb +141 -0
- data/test/simple_manual_examples.rb +28 -0
- data/test/y_petri_graph.png +0 -0
- data/test/y_petri_test.rb +1521 -0
- data/y_petri.gemspec +21 -0
- metadata +112 -0
@@ -0,0 +1,254 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
module YPetri::Workspace::InstanceMethods
|
4
|
+
# Readers for @Place, @Transition, @Net instance variables, which should
|
5
|
+
# contain said classes, or their instance-specific subclasses.
|
6
|
+
|
7
|
+
# Place class or parametrized subclass.
|
8
|
+
#
|
9
|
+
attr_reader :Place
|
10
|
+
|
11
|
+
# Transition class or parametrized subclass.
|
12
|
+
#
|
13
|
+
attr_reader :Transition
|
14
|
+
|
15
|
+
# Net class or parametrized subclass.
|
16
|
+
#
|
17
|
+
attr_reader :Net
|
18
|
+
|
19
|
+
# Collections of clamps, initial marking vectors, and simulation settings.
|
20
|
+
#
|
21
|
+
attr_reader :clamp_collections,
|
22
|
+
:initial_marking_collections,
|
23
|
+
:simulation_settings_collections
|
24
|
+
|
25
|
+
# Instance initialization.
|
26
|
+
#
|
27
|
+
def initialize
|
28
|
+
set_up_Top_net # Sets up :Top net encompassing all places and transitions.
|
29
|
+
|
30
|
+
@simulations = {} # { simulation => its settings }
|
31
|
+
@clamp_collections = { Base: {} } # { collection name => clamp hash }
|
32
|
+
@initial_marking_collections = { Base: {} } # { collection name => im hash }
|
33
|
+
@simulation_settings_collections = # { collection name => ss hash }
|
34
|
+
{ Base: YPetri::DEFAULT_SIMULATION_SETTINGS.call }
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns a place instance identified by the argument.
|
38
|
+
#
|
39
|
+
def place which; Place().instance which end
|
40
|
+
|
41
|
+
# Returns a transition instance identified by the argument.
|
42
|
+
#
|
43
|
+
def transition which; Transition().instance which end
|
44
|
+
|
45
|
+
# Returns a net instance identified by the argument.
|
46
|
+
#
|
47
|
+
def net which; Net().instance which end
|
48
|
+
|
49
|
+
# Returns the name of a place identified by the argument.
|
50
|
+
#
|
51
|
+
def p which; place( which ).name end
|
52
|
+
|
53
|
+
# Returns the name of a transition identified by the argument.
|
54
|
+
#
|
55
|
+
def t which; transition( which ).name end
|
56
|
+
|
57
|
+
# Returns the name of a net identified by the argument.
|
58
|
+
#
|
59
|
+
def n which; net( which ).name end
|
60
|
+
|
61
|
+
# Place instances.
|
62
|
+
#
|
63
|
+
def places; Place().instances end
|
64
|
+
|
65
|
+
# Transition instances.
|
66
|
+
#
|
67
|
+
def transitions; Transition().instances end
|
68
|
+
|
69
|
+
# Net instances.
|
70
|
+
#
|
71
|
+
def nets; Net().instances end
|
72
|
+
|
73
|
+
# Hash of simulation instances and their settings.
|
74
|
+
#
|
75
|
+
def simulations; @simulations end
|
76
|
+
|
77
|
+
# Place names.
|
78
|
+
#
|
79
|
+
def pp; places.map &:name end
|
80
|
+
|
81
|
+
# Transition names.
|
82
|
+
#
|
83
|
+
def tt; transitions.map &:name end
|
84
|
+
|
85
|
+
# Net names.
|
86
|
+
#
|
87
|
+
def nn; nets.map &:name end
|
88
|
+
|
89
|
+
# Clamp collection names.
|
90
|
+
#
|
91
|
+
def clamp_collection_names; @clamp_collections.keys end
|
92
|
+
alias cc_names clamp_collection_names
|
93
|
+
|
94
|
+
# Initial marking collection names.
|
95
|
+
#
|
96
|
+
def initial_marking_collection_names; @initial_marking_collections.keys end
|
97
|
+
alias imc_names initial_marking_collection_names
|
98
|
+
|
99
|
+
# Simulation settings collection names.
|
100
|
+
#
|
101
|
+
def simulation_settings_collection_names
|
102
|
+
@simulation_settings_collections.keys
|
103
|
+
end
|
104
|
+
alias ssc_names simulation_settings_collection_names
|
105
|
+
|
106
|
+
# Clamp collection identified by the argument.
|
107
|
+
#
|
108
|
+
def clamp_collection name=:Base
|
109
|
+
@clamp_collections[name]
|
110
|
+
end
|
111
|
+
alias cc clamp_collection
|
112
|
+
|
113
|
+
# Marking collection identified by the argument.
|
114
|
+
#
|
115
|
+
def initial_marking_collection name=:Base
|
116
|
+
@initial_marking_collections[name]
|
117
|
+
end
|
118
|
+
alias imc initial_marking_collection
|
119
|
+
|
120
|
+
# Simulation settings collection specified by the argument.
|
121
|
+
#
|
122
|
+
def simulation_settings_collection name=:Base
|
123
|
+
@simulation_settings_collections[name]
|
124
|
+
end
|
125
|
+
alias ssc simulation_settings_collection
|
126
|
+
|
127
|
+
# Creates a new clamp collection. If collection identifier is not given,
|
128
|
+
# resets :Base clamp collection to new values.
|
129
|
+
#
|
130
|
+
def set_clamp_collection( name=:Base, clamp_hash )
|
131
|
+
@clamp_collections[name] = clamp_hash
|
132
|
+
end
|
133
|
+
alias set_cc set_clamp_collection
|
134
|
+
|
135
|
+
# Creates a new initial marking collection. If collection identifier is not
|
136
|
+
# given, resets :Base initial marking collection to new values.
|
137
|
+
#
|
138
|
+
def set_initial_marking_collection( name=:Base, initial_marking_hash )
|
139
|
+
@initial_marking_collections[name] = initial_marking_hash
|
140
|
+
end
|
141
|
+
alias set_imc set_initial_marking_collection
|
142
|
+
|
143
|
+
# Creates a new simulation settings collection. If collection identifier is
|
144
|
+
# not given, resets :Base simulation settings collection to new values.
|
145
|
+
#
|
146
|
+
def set_simulation_settings_collection( name=:Base, sim_set_hash )
|
147
|
+
@simulation_settings_collections[name] = sim_set_hash
|
148
|
+
end
|
149
|
+
alias set_ssc set_simulation_settings_collection
|
150
|
+
|
151
|
+
# Presents a simulation specified by the argument, which must be a hash with
|
152
|
+
# four items: :net, :clamp_collection, :inital_marking_collection and
|
153
|
+
# :simulation_settings_collection.
|
154
|
+
#
|
155
|
+
def simulation settings={}
|
156
|
+
key = case settings
|
157
|
+
when ~:may_have then # it is a hash or equivalent
|
158
|
+
settings.may_have :net
|
159
|
+
settings.may_have :cc, syn!: :clamp_collection
|
160
|
+
settings.may_have :imc, syn!: :initial_marking_collection
|
161
|
+
settings.may_have :ssc, syn!: :simulation_settings_collection
|
162
|
+
{ net: net( settings[:net] || self.Net::Top ), # the key
|
163
|
+
cc: settings[:cc] || :Base,
|
164
|
+
imc: settings[:imc] || :Base,
|
165
|
+
ssc: settings[:ssc] || :Base }
|
166
|
+
else # use the unprocessed argument itself as the key
|
167
|
+
settings
|
168
|
+
end
|
169
|
+
@simulations[ key ]
|
170
|
+
end
|
171
|
+
|
172
|
+
# Makes a new timed simulation. Named arguments for this method are the same
|
173
|
+
# as for TimedSimulation#new, but in addition, :name can be supplied.
|
174
|
+
#
|
175
|
+
# To create a simulation, simulation settings collection, initial marking
|
176
|
+
# collection, and clamp collection have to be specified. A <em>place clamp</em>,
|
177
|
+
# is a fixed value, at which the marking is held. Similarly, <em>initial
|
178
|
+
# marking</em> is the marking, which a free place receives at the beginning.
|
179
|
+
# Free places are those, that are not clamped. After initialization, marking
|
180
|
+
# of free places is allowed to change as the transition fire.
|
181
|
+
#
|
182
|
+
# For example, having places :P1..:P5, clamped :P1, :P2 can be written as eg.:
|
183
|
+
#
|
184
|
+
# * clamps = { P1: 4, P2: 5 }
|
185
|
+
#
|
186
|
+
# Places :P3, :P4, :P5 are <em>free</em>. Their initial marking has to be
|
187
|
+
# specified, which can be written as eg.:
|
188
|
+
#
|
189
|
+
# * initial_markings = { P3: 1, P4: 2, P5: 3 }
|
190
|
+
#
|
191
|
+
# As for simulation settings, their exact nature depends on the simulation
|
192
|
+
# method. For default Euler method, there are 3 important parameters:
|
193
|
+
# - <em>step_size</em>,
|
194
|
+
# - <em>sampling_period</em>,
|
195
|
+
# - <em>target_time</em>
|
196
|
+
#
|
197
|
+
# For example, default simulation settings are:
|
198
|
+
#
|
199
|
+
# * default_ss = { step_size: 0.1, sampling_period: 5, target_time: 60 }
|
200
|
+
#
|
201
|
+
def new_timed_simulation( settings={} ); st = settings
|
202
|
+
net_ɪ = net( st[:net] || self.Net::Top )
|
203
|
+
cc_id = st.may_have( :cc, syn!: :clamp_collection ) || :Base
|
204
|
+
imc_id = st.may_have( :imc, syn!: :initial_marking_collection ) || :Base
|
205
|
+
ssc_id = st.may_have( :ssc, syn!: :simulation_settings_collection ) || :Base
|
206
|
+
|
207
|
+
# simulation key
|
208
|
+
key = settings.may_have( :ɴ, syn!: :name ) || # either explicit
|
209
|
+
{ net: net_ɪ, cc: cc_id, imc: imc_id, ssc: ssc_id } # or constructed
|
210
|
+
|
211
|
+
# Let's clarify what we got so far.
|
212
|
+
simulation_settings = self.ssc( ssc_id )
|
213
|
+
clamp_hash = self.cc( cc_id )
|
214
|
+
im_hash = self.imc( imc_id )
|
215
|
+
|
216
|
+
# Use places' :default_marking in absence of explicit initial marking.
|
217
|
+
untreated = net_ɪ.places.select do |p|
|
218
|
+
! clamp_hash.map { |k, _| place k }.include? p and
|
219
|
+
! im_hash.map { |k, _| place k }.include? p
|
220
|
+
end
|
221
|
+
im_complement = Hash[ untreated.zip( untreated.map &:default_marking ) ]
|
222
|
+
|
223
|
+
# If marking can't be figured, raise nice errors.
|
224
|
+
missing = im_complement.select { |_, v| v.nil? }
|
225
|
+
err = lambda { |array, txt=''|
|
226
|
+
raise TypeError, "Missing clamp and/or initial marking for %s#{txt}!" %
|
227
|
+
Array( array ).map { |i| missing.keys[i] }.join( ', ' )
|
228
|
+
}
|
229
|
+
case missing.size
|
230
|
+
when 0 then im_hash = im_hash.merge im_complement # everything's OK
|
231
|
+
when 1 then err.( 0 )
|
232
|
+
when 2 then err.( [0, 1] )
|
233
|
+
when 3 then err.( [0, 1, 2] )
|
234
|
+
else err.( [0, 1], " and #{missing.size-2} more places" ) end
|
235
|
+
|
236
|
+
# Finally, create and return the simulation
|
237
|
+
@simulations[ key ] =
|
238
|
+
net_ɪ.new_timed_simulation( simulation_settings
|
239
|
+
.merge( initial_marking: im_hash,
|
240
|
+
place_clamps: clamp_hash ) )
|
241
|
+
end # def new_timed_simulation
|
242
|
+
|
243
|
+
private
|
244
|
+
|
245
|
+
# Creates all-encompassing Net instance named :Top.
|
246
|
+
#
|
247
|
+
def set_up_Top_net
|
248
|
+
Net().new name: :Top # all-encompassing :Top net
|
249
|
+
# Hook new places to add themselves magically to the :Top net.
|
250
|
+
Place().new_instance_closure { |new_inst| net( :Top ) << new_inst }
|
251
|
+
# Hook new transitions to add themselves magically to the :Top net.
|
252
|
+
Transition().new_instance_closure { |new_inst| net( :Top ) << new_inst }
|
253
|
+
end
|
254
|
+
end # module YPetri::Workspace::InstanceMethods
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
module YPetri::Workspace::ParametrizedSubclassing
|
3
|
+
def initialize
|
4
|
+
# Parametrized subclasses of Place, Transition and Net.
|
5
|
+
@Place = place_subclass = Class.new YPetri::Place
|
6
|
+
@Transition = transition_subclass = Class.new YPetri::Transition
|
7
|
+
@Net = net_subclass = Class.new YPetri::Net
|
8
|
+
|
9
|
+
# Now dependency injection: Let's tell these subclasses to work together.
|
10
|
+
[ @Place, @Transition, @Net ].each { |klass|
|
11
|
+
klass.class_exec {
|
12
|
+
# redefine their Place, Transition, Net method
|
13
|
+
define_method :Place do place_subclass end
|
14
|
+
define_method :Transition do transition_subclass end
|
15
|
+
define_method :Net do net_subclass end
|
16
|
+
# I am not sure whether the following line is necessary. Place(),
|
17
|
+
# Transition() and Net() methods, which have just been redefined,
|
18
|
+
# are originally defined as private in klass. Is it necessary to
|
19
|
+
# declare them private explicitly again after redefining?
|
20
|
+
private :Place, :Transition, :Net
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
super # Parametrized subclassing achieved, proceed ahead normally.
|
25
|
+
end # def initialize
|
26
|
+
end # module YPetri::Workspace::ParametrizedSubclassing
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
# Workspace holds places, transitions, nets and other assets needed for
|
4
|
+
# simulation (settings, clamps, initial markings etc.). Workspace also
|
5
|
+
# provides basic methods for their handling, but these are not too public.
|
6
|
+
# YPetri interface is defined by YPetri::Manipulator.
|
7
|
+
#
|
8
|
+
class YPetri::Workspace
|
9
|
+
include NameMagic
|
10
|
+
|
11
|
+
require_relative 'workspace/instance_methods'
|
12
|
+
require_relative 'workspace/parametrized_subclassing'
|
13
|
+
|
14
|
+
include self::InstanceMethods
|
15
|
+
prepend self::ParametrizedSubclassing
|
16
|
+
end
|
data/lib/y_petri.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
require 'gnuplot'
|
4
|
+
require 'csv'
|
5
|
+
require 'graphviz'
|
6
|
+
|
7
|
+
require 'y_support/local_object'
|
8
|
+
require 'y_support/respond_to'
|
9
|
+
require 'y_support/name_magic'
|
10
|
+
require 'y_support/unicode'
|
11
|
+
require 'y_support/typing'
|
12
|
+
require 'y_support/core_ext/hash'
|
13
|
+
require 'y_support/core_ext/array'
|
14
|
+
require 'y_support/stdlib_ext/matrix'
|
15
|
+
|
16
|
+
require 'sy/abstract_algebra'
|
17
|
+
|
18
|
+
require 'active_support/core_ext/module/delegation'
|
19
|
+
require 'active_support/core_ext/array/extract_options'
|
20
|
+
|
21
|
+
require_relative 'y_petri/version'
|
22
|
+
require_relative 'y_petri/place'
|
23
|
+
require_relative 'y_petri/transition'
|
24
|
+
require_relative 'y_petri/net'
|
25
|
+
require_relative 'y_petri/simulation'
|
26
|
+
require_relative 'y_petri/timed_simulation'
|
27
|
+
require_relative 'y_petri/workspace'
|
28
|
+
require_relative 'y_petri/manipulator'
|
29
|
+
|
30
|
+
# YPetri represents Petri net (PN) formalism.
|
31
|
+
#
|
32
|
+
# A PN consists of places and transitions. There are also arcs, that is,
|
33
|
+
# "arrows" connecting places and transitions, though arcs are not considered
|
34
|
+
# first class citizens in YPetri.
|
35
|
+
#
|
36
|
+
# At the time of PN execution (or simulation), transitions act upon places
|
37
|
+
# and change their marking by placing or removing tokens as dictated by
|
38
|
+
# their operation method ("function").
|
39
|
+
#
|
40
|
+
# Hybrid Functional Petri Net formalism, motivated by modeling cellular
|
41
|
+
# processes by their authors' Cell Illustrator software, explicitly
|
42
|
+
# introduces the possibility of both discrete and continuous places and
|
43
|
+
# transitions ('Hybrid'). YPetri does not emphasize this. Just like there is
|
44
|
+
# fluid transition between Fixnum and Bignum, there should be fluid
|
45
|
+
# transition between token amount representation as Integer (discrete) or
|
46
|
+
# Float (continuous) - the decision should be on the simulator.
|
47
|
+
#
|
48
|
+
module YPetri
|
49
|
+
DEBUG = false
|
50
|
+
|
51
|
+
DEFAULT_SIMULATION_SETTINGS = lambda do
|
52
|
+
{ step_size: 0.02,
|
53
|
+
sampling_period: 2,
|
54
|
+
target_time: 60 }
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.included( receiver )
|
58
|
+
# receiver.instance_variable_set :@YPetriManipulator, Manipulator.new
|
59
|
+
# puts "included in #{receiver}"
|
60
|
+
receiver.module_exec {
|
61
|
+
define_method :y_petri_manipulator do
|
62
|
+
singleton_class.instance_variable_get :@YPetriManipulator or
|
63
|
+
( puts "defining Manipulator for #{self} singleton class" if YPetri::DEBUG
|
64
|
+
singleton_class.instance_variable_set :@YPetriManipulator, Manipulator.new )
|
65
|
+
end
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
delegate( :workspace,
|
70
|
+
:place, :transition,
|
71
|
+
:p, :t,
|
72
|
+
:places, :transitions, :nets,
|
73
|
+
:simulations,
|
74
|
+
:pp, :tt, :nn,
|
75
|
+
:clamp_collections,
|
76
|
+
:inital_marking_collections,
|
77
|
+
:simulation_settings_collections,
|
78
|
+
:clamp_cc, :initial_marking_cc, :simulation_settings_cc,
|
79
|
+
:Place,
|
80
|
+
:Transition,
|
81
|
+
:Net,
|
82
|
+
:net_point_reset,
|
83
|
+
:net_point_to, :net→,
|
84
|
+
:net,
|
85
|
+
:simulation_point_reset,
|
86
|
+
:simulation_point_to,
|
87
|
+
:simulation,
|
88
|
+
:simulation_point_position,
|
89
|
+
:cc_point_reset,
|
90
|
+
:cc_point_to, :cc→,
|
91
|
+
:clamp_collection, :cc,
|
92
|
+
:cc_point_position,
|
93
|
+
:imc_point_reset,
|
94
|
+
:imc_point_to, :imc→,
|
95
|
+
:initial_marking_collection, :imc,
|
96
|
+
:imc_point_position,
|
97
|
+
:ssc_point_reset,
|
98
|
+
:ssc_point_to, :ssc→,
|
99
|
+
:simulation_settings_collection, :ssc,
|
100
|
+
:ssc_point_position,
|
101
|
+
:net_selection,
|
102
|
+
:simulation_selection,
|
103
|
+
:ssc_selection,
|
104
|
+
:cc_selection,
|
105
|
+
:imc_selection,
|
106
|
+
:net_selection_clear,
|
107
|
+
:net_select!,
|
108
|
+
:net_select,
|
109
|
+
:net_unselect,
|
110
|
+
:simulation_selection_clear,
|
111
|
+
:simulation_select!,
|
112
|
+
:simulation_select,
|
113
|
+
:simulation_unselect,
|
114
|
+
:cc_selection_clear,
|
115
|
+
:cc_select!,
|
116
|
+
:cc_select,
|
117
|
+
:cc_unselect,
|
118
|
+
:imc_selection_clear,
|
119
|
+
:imc_select!,
|
120
|
+
:imc_select,
|
121
|
+
:imc_unselect,
|
122
|
+
:ssc_selection_clear,
|
123
|
+
:ssc_select!,
|
124
|
+
:ssc_select,
|
125
|
+
:ssc_unselect,
|
126
|
+
:clamp,
|
127
|
+
:initial_marking, :im,
|
128
|
+
:set_step, :set_step_size,
|
129
|
+
:set_time, :set_target_time,
|
130
|
+
:set_sampling,
|
131
|
+
:set_simulation_method,
|
132
|
+
:new_timed_simulation,
|
133
|
+
:run!,
|
134
|
+
:print_recording,
|
135
|
+
:plot,
|
136
|
+
:plot_selected,
|
137
|
+
:plot_state,
|
138
|
+
:plot_flux,
|
139
|
+
:plot_all,
|
140
|
+
to: :y_petri_manipulator )
|
141
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
require 'y_petri'
|
4
|
+
include YPetri
|
5
|
+
require 'sy'
|
6
|
+
require 'mathn'
|
7
|
+
|
8
|
+
set_step 10
|
9
|
+
set_target_time 600
|
10
|
+
set_sampling 10
|
11
|
+
set_simulation_method :Euler_with_timeless_transitions_firing_after_each_step
|
12
|
+
|
13
|
+
A = Place m!: 1
|
14
|
+
B = Place m!: 10
|
15
|
+
C = Place m!: 0
|
16
|
+
|
17
|
+
Transition name: :B_disappearing,
|
18
|
+
s: { B: -1 },
|
19
|
+
action: lambda { |m| m >= 1 ? 1 : 0 }
|
20
|
+
|
21
|
+
Transition name: :C_held_at_half_B,
|
22
|
+
assignment: true,
|
23
|
+
domain: :B,
|
24
|
+
codomain: :C,
|
25
|
+
action: lambda { |x| x / 2 }
|
26
|
+
|
27
|
+
run!
|
28
|
+
plot_recording
|
Binary file
|