y_petri 1.0.0

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