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