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.
@@ -0,0 +1,3 @@
1
+ module YPetri
2
+ VERSION = "1.0.0"
3
+ end
@@ -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