orfeas_petri_flow 0.6.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/CHANGELOG.md +80 -0
- data/MIT-LICENSE +22 -0
- data/README.md +592 -0
- data/Rakefile +28 -0
- data/lib/petri_flow/colored/arc_expression.rb +163 -0
- data/lib/petri_flow/colored/color.rb +40 -0
- data/lib/petri_flow/colored/colored_net.rb +146 -0
- data/lib/petri_flow/colored/guard.rb +104 -0
- data/lib/petri_flow/core/arc.rb +63 -0
- data/lib/petri_flow/core/marking.rb +64 -0
- data/lib/petri_flow/core/net.rb +121 -0
- data/lib/petri_flow/core/place.rb +54 -0
- data/lib/petri_flow/core/token.rb +55 -0
- data/lib/petri_flow/core/transition.rb +88 -0
- data/lib/petri_flow/export/cpn_tools_exporter.rb +322 -0
- data/lib/petri_flow/export/json_exporter.rb +224 -0
- data/lib/petri_flow/export/pnml_exporter.rb +229 -0
- data/lib/petri_flow/export/yaml_exporter.rb +246 -0
- data/lib/petri_flow/export.rb +193 -0
- data/lib/petri_flow/generators/adapters/aasm_adapter.rb +69 -0
- data/lib/petri_flow/generators/adapters/state_machines_adapter.rb +83 -0
- data/lib/petri_flow/generators/state_machine_adapter.rb +47 -0
- data/lib/petri_flow/generators/workflow_generator.rb +176 -0
- data/lib/petri_flow/matrix/analyzer.rb +151 -0
- data/lib/petri_flow/matrix/causation.rb +126 -0
- data/lib/petri_flow/matrix/correlation.rb +79 -0
- data/lib/petri_flow/matrix/crud_event_mapping.rb +74 -0
- data/lib/petri_flow/matrix/lineage.rb +113 -0
- data/lib/petri_flow/matrix/reachability.rb +128 -0
- data/lib/petri_flow/railtie.rb +41 -0
- data/lib/petri_flow/registry.rb +85 -0
- data/lib/petri_flow/simulation/simulator.rb +188 -0
- data/lib/petri_flow/simulation/trace.rb +119 -0
- data/lib/petri_flow/tasks/petri_flow.rake +229 -0
- data/lib/petri_flow/verification/boundedness_checker.rb +127 -0
- data/lib/petri_flow/verification/invariant_checker.rb +144 -0
- data/lib/petri_flow/verification/liveness_checker.rb +153 -0
- data/lib/petri_flow/verification/reachability_analyzer.rb +152 -0
- data/lib/petri_flow/verification_runner.rb +287 -0
- data/lib/petri_flow/version.rb +5 -0
- data/lib/petri_flow/visualization/graphviz.rb +220 -0
- data/lib/petri_flow/visualization/mermaid.rb +191 -0
- data/lib/petri_flow/workflow.rb +228 -0
- data/lib/petri_flow.rb +164 -0
- metadata +174 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module PetriFlow
|
|
6
|
+
module Export
|
|
7
|
+
# Exports Petri nets and Colored Petri Nets to JSON format
|
|
8
|
+
# Provides a clean, human-readable, and API-friendly format
|
|
9
|
+
class JsonExporter
|
|
10
|
+
attr_reader :net
|
|
11
|
+
|
|
12
|
+
def initialize(net)
|
|
13
|
+
@net = net
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Export to JSON string
|
|
17
|
+
# @param pretty [Boolean] Whether to format JSON with indentation
|
|
18
|
+
# @return [String] JSON representation of the net
|
|
19
|
+
def to_json(pretty: true)
|
|
20
|
+
hash = to_hash
|
|
21
|
+
if pretty
|
|
22
|
+
JSON.pretty_generate(hash)
|
|
23
|
+
else
|
|
24
|
+
JSON.generate(hash)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Export to Ruby hash
|
|
29
|
+
# @return [Hash] Hash representation of the net
|
|
30
|
+
def to_hash
|
|
31
|
+
{
|
|
32
|
+
meta: meta_info,
|
|
33
|
+
net: net_structure,
|
|
34
|
+
colors: color_definitions,
|
|
35
|
+
places: places_data,
|
|
36
|
+
transitions: transitions_data,
|
|
37
|
+
arcs: arcs_data,
|
|
38
|
+
marking: current_marking,
|
|
39
|
+
statistics: statistics
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Save to JSON file
|
|
44
|
+
def save_json(filename, pretty: true)
|
|
45
|
+
File.write(filename, to_json(pretty: pretty))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def colored_net?
|
|
51
|
+
@net.is_a?(PetriFlow::Colored::ColoredNet)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def meta_info
|
|
55
|
+
{
|
|
56
|
+
format: 'PetriFlow JSON Export',
|
|
57
|
+
version: '1.0',
|
|
58
|
+
exported_at: Time.now.utc.iso8601,
|
|
59
|
+
net_type: colored_net? ? 'colored_petri_net' : 'petri_net'
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def net_structure
|
|
64
|
+
{
|
|
65
|
+
name: @net.name,
|
|
66
|
+
type: colored_net? ? 'ColoredPetriNet' : 'PetriNet',
|
|
67
|
+
place_count: @net.places.size,
|
|
68
|
+
transition_count: @net.transitions.size,
|
|
69
|
+
arc_count: @net.arcs.size
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def color_definitions
|
|
74
|
+
return [] unless colored_net?
|
|
75
|
+
|
|
76
|
+
@net.colors.map do |color_name, color|
|
|
77
|
+
{
|
|
78
|
+
name: color_name,
|
|
79
|
+
attributes: color.attributes,
|
|
80
|
+
has_validator: !color.validator.nil?
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def places_data
|
|
86
|
+
@net.places.map do |place_id, place|
|
|
87
|
+
place_hash = {
|
|
88
|
+
id: place_id,
|
|
89
|
+
name: place.name,
|
|
90
|
+
tokens: place.tokens,
|
|
91
|
+
capacity: place.capacity == Float::INFINITY ? 'infinite' : place.capacity
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# Add colored net place info
|
|
95
|
+
if colored_net? && @net.colored_places[place_id]
|
|
96
|
+
colored_info = @net.colored_places[place_id]
|
|
97
|
+
place_hash[:color] = colored_info[:color]
|
|
98
|
+
place_hash[:colored_tokens] = format_colored_tokens(place_id)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
place_hash
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def format_colored_tokens(place_id)
|
|
106
|
+
return [] unless colored_net?
|
|
107
|
+
|
|
108
|
+
tokens = @net.token_pools[place_id] || []
|
|
109
|
+
tokens.map do |token|
|
|
110
|
+
{
|
|
111
|
+
id: token.id,
|
|
112
|
+
color: token.color,
|
|
113
|
+
data: token.data,
|
|
114
|
+
timestamp: token.timestamp
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def transitions_data
|
|
120
|
+
@net.transitions.map do |trans_id, transition|
|
|
121
|
+
trans_hash = {
|
|
122
|
+
id: trans_id,
|
|
123
|
+
name: transition.name,
|
|
124
|
+
input_arcs: transition.input_arcs.size,
|
|
125
|
+
output_arcs: transition.output_arcs.size,
|
|
126
|
+
enabled: transition.enabled?
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# Add guard info
|
|
130
|
+
if transition.guard
|
|
131
|
+
trans_hash[:guard] = format_guard(transition.guard)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
trans_hash
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def format_guard(guard)
|
|
139
|
+
if guard.respond_to?(:name) && guard.name
|
|
140
|
+
{
|
|
141
|
+
type: 'named_guard',
|
|
142
|
+
name: guard.name,
|
|
143
|
+
description: guard.name
|
|
144
|
+
}
|
|
145
|
+
elsif guard.respond_to?(:condition)
|
|
146
|
+
{
|
|
147
|
+
type: 'guard',
|
|
148
|
+
description: 'Custom guard condition'
|
|
149
|
+
}
|
|
150
|
+
else
|
|
151
|
+
{
|
|
152
|
+
type: 'proc',
|
|
153
|
+
description: 'Lambda/Proc guard'
|
|
154
|
+
}
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def arcs_data
|
|
159
|
+
@net.arcs.map.with_index do |arc, index|
|
|
160
|
+
arc_hash = {
|
|
161
|
+
id: "arc_#{index}",
|
|
162
|
+
source: {
|
|
163
|
+
id: arc.source.id,
|
|
164
|
+
name: arc.source.name,
|
|
165
|
+
type: arc.source.class.name.split('::').last
|
|
166
|
+
},
|
|
167
|
+
target: {
|
|
168
|
+
id: arc.target.id,
|
|
169
|
+
name: arc.target.name,
|
|
170
|
+
type: arc.target.class.name.split('::').last
|
|
171
|
+
},
|
|
172
|
+
weight: arc.weight,
|
|
173
|
+
direction: arc.input_arc? ? 'place_to_transition' : 'transition_to_place'
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# Add arc expression info
|
|
177
|
+
if arc.expression
|
|
178
|
+
arc_hash[:expression] = format_expression(arc.expression)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
arc_hash
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def format_expression(expression)
|
|
186
|
+
if expression.respond_to?(:name) && expression.name
|
|
187
|
+
{
|
|
188
|
+
type: 'named_expression',
|
|
189
|
+
name: expression.name,
|
|
190
|
+
description: expression.name
|
|
191
|
+
}
|
|
192
|
+
elsif expression.respond_to?(:transformation)
|
|
193
|
+
{
|
|
194
|
+
type: 'arc_expression',
|
|
195
|
+
description: 'Custom arc expression'
|
|
196
|
+
}
|
|
197
|
+
else
|
|
198
|
+
{
|
|
199
|
+
type: 'proc',
|
|
200
|
+
description: 'Lambda/Proc expression'
|
|
201
|
+
}
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def current_marking
|
|
206
|
+
{
|
|
207
|
+
tokens_by_place: @net.places.transform_values(&:tokens),
|
|
208
|
+
total_tokens: @net.places.values.sum(&:tokens)
|
|
209
|
+
}.tap do |marking|
|
|
210
|
+
if colored_net?
|
|
211
|
+
marking[:colored_marking] = @net.colored_marking
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def statistics
|
|
217
|
+
@net.stats.merge({
|
|
218
|
+
enabled_transitions_list: @net.enabled_transitions.map(&:id),
|
|
219
|
+
deadlocked: @net.deadlocked?
|
|
220
|
+
})
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rexml/document'
|
|
4
|
+
|
|
5
|
+
module PetriFlow
|
|
6
|
+
module Export
|
|
7
|
+
# Exports Petri nets and Colored Petri Nets to PNML format
|
|
8
|
+
# PNML (Petri Net Markup Language) is the ISO/IEC 15909 standard
|
|
9
|
+
class PnmlExporter
|
|
10
|
+
attr_reader :net
|
|
11
|
+
|
|
12
|
+
def initialize(net)
|
|
13
|
+
@net = net
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Export to PNML XML string
|
|
17
|
+
def to_pnml
|
|
18
|
+
doc = REXML::Document.new
|
|
19
|
+
doc << REXML::XMLDecl.new('1.0', 'UTF-8')
|
|
20
|
+
|
|
21
|
+
# Root element
|
|
22
|
+
pnml = doc.add_element('pnml', {
|
|
23
|
+
'xmlns' => 'http://www.pnml.org/version-2009/grammar/pnml'
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
# Add net element
|
|
27
|
+
net_element = pnml.add_element('net', {
|
|
28
|
+
'id' => net_id,
|
|
29
|
+
'type' => net_type_url
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
# Add name
|
|
33
|
+
add_name(net_element, @net.name)
|
|
34
|
+
|
|
35
|
+
# Add page (container for net elements)
|
|
36
|
+
page = net_element.add_element('page', { 'id' => 'page1' })
|
|
37
|
+
|
|
38
|
+
# Add places
|
|
39
|
+
@net.places.each do |place_id, place|
|
|
40
|
+
add_place(page, place)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Add transitions
|
|
44
|
+
@net.transitions.each do |trans_id, transition|
|
|
45
|
+
add_transition(page, transition)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Add arcs
|
|
49
|
+
@net.arcs.each_with_index do |arc, index|
|
|
50
|
+
add_arc(page, arc, index)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Add colored net extensions if applicable
|
|
54
|
+
if colored_net?
|
|
55
|
+
add_color_declarations(net_element)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Format XML nicely
|
|
59
|
+
formatter = REXML::Formatters::Pretty.new(2)
|
|
60
|
+
formatter.compact = true
|
|
61
|
+
output = String.new
|
|
62
|
+
formatter.write(doc, output)
|
|
63
|
+
output
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Save to file
|
|
67
|
+
def save_pnml(filename)
|
|
68
|
+
File.write(filename, to_pnml)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def net_id
|
|
74
|
+
@net.name.downcase.gsub(/\s+/, '_')
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def net_type_url
|
|
78
|
+
if colored_net?
|
|
79
|
+
'http://www.pnml.org/version-2009/grammar/highlevelnet'
|
|
80
|
+
else
|
|
81
|
+
'http://www.pnml.org/version-2009/grammar/ptnet'
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def colored_net?
|
|
86
|
+
@net.is_a?(PetriFlow::Colored::ColoredNet)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def add_name(element, name)
|
|
90
|
+
name_elem = element.add_element('name')
|
|
91
|
+
text_elem = name_elem.add_element('text')
|
|
92
|
+
text_elem.text = name
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def add_place(page, place)
|
|
96
|
+
place_elem = page.add_element('place', { 'id' => place.id.to_s })
|
|
97
|
+
add_name(place_elem, place.name)
|
|
98
|
+
|
|
99
|
+
# Add initial marking
|
|
100
|
+
if place.tokens > 0
|
|
101
|
+
marking_elem = place_elem.add_element('initialMarking')
|
|
102
|
+
text_elem = marking_elem.add_element('text')
|
|
103
|
+
text_elem.text = place.tokens.to_s
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Add colored net place extensions
|
|
107
|
+
if colored_net? && @net.colored_places[place.id]
|
|
108
|
+
add_place_type(place_elem, @net.colored_places[place.id])
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Add graphics (optional - for visualization)
|
|
112
|
+
add_place_graphics(place_elem)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def add_place_type(place_elem, place_info)
|
|
116
|
+
return unless place_info[:color]
|
|
117
|
+
|
|
118
|
+
type_elem = place_elem.add_element('type')
|
|
119
|
+
text_elem = type_elem.add_element('text')
|
|
120
|
+
text_elem.text = place_info[:color].to_s
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def add_place_graphics(place_elem)
|
|
124
|
+
graphics = place_elem.add_element('graphics')
|
|
125
|
+
position = graphics.add_element('position', { 'x' => '0', 'y' => '0' })
|
|
126
|
+
dimension = graphics.add_element('dimension', { 'x' => '40', 'y' => '40' })
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def add_transition(page, transition)
|
|
130
|
+
trans_elem = page.add_element('transition', { 'id' => transition.id.to_s })
|
|
131
|
+
add_name(trans_elem, transition.name)
|
|
132
|
+
|
|
133
|
+
# Add guard if present
|
|
134
|
+
if transition.guard
|
|
135
|
+
add_guard(trans_elem, transition.guard)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Add graphics
|
|
139
|
+
add_transition_graphics(trans_elem)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def add_guard(trans_elem, guard)
|
|
143
|
+
condition_elem = trans_elem.add_element('condition')
|
|
144
|
+
text_elem = condition_elem.add_element('text')
|
|
145
|
+
|
|
146
|
+
guard_text = if guard.respond_to?(:name) && guard.name
|
|
147
|
+
guard.name
|
|
148
|
+
else
|
|
149
|
+
'[guard]'
|
|
150
|
+
end
|
|
151
|
+
text_elem.text = guard_text
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def add_transition_graphics(trans_elem)
|
|
155
|
+
graphics = trans_elem.add_element('graphics')
|
|
156
|
+
position = graphics.add_element('position', { 'x' => '0', 'y' => '0' })
|
|
157
|
+
dimension = graphics.add_element('dimension', { 'x' => '40', 'y' => '25' })
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def add_arc(page, arc, index)
|
|
161
|
+
arc_id = "arc_#{index}"
|
|
162
|
+
source_id = arc.source.id.to_s
|
|
163
|
+
target_id = arc.target.id.to_s
|
|
164
|
+
|
|
165
|
+
arc_elem = page.add_element('arc', {
|
|
166
|
+
'id' => arc_id,
|
|
167
|
+
'source' => source_id,
|
|
168
|
+
'target' => target_id
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
# Add inscription (arc weight)
|
|
172
|
+
if arc.weight != 1
|
|
173
|
+
inscription_elem = arc_elem.add_element('inscription')
|
|
174
|
+
text_elem = inscription_elem.add_element('text')
|
|
175
|
+
text_elem.text = arc.weight.to_s
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Add arc expression if present
|
|
179
|
+
if arc.expression
|
|
180
|
+
add_arc_expression(arc_elem, arc.expression)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def add_arc_expression(arc_elem, expression)
|
|
185
|
+
inscription_elem = arc_elem.add_element('inscription')
|
|
186
|
+
text_elem = inscription_elem.add_element('text')
|
|
187
|
+
|
|
188
|
+
expr_text = if expression.respond_to?(:name) && expression.name
|
|
189
|
+
expression.name
|
|
190
|
+
else
|
|
191
|
+
'[expression]'
|
|
192
|
+
end
|
|
193
|
+
text_elem.text = expr_text
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def add_color_declarations(net_element)
|
|
197
|
+
return unless colored_net? && @net.colors.any?
|
|
198
|
+
|
|
199
|
+
declaration_elem = net_element.add_element('declaration')
|
|
200
|
+
structure_elem = declaration_elem.add_element('structure')
|
|
201
|
+
|
|
202
|
+
@net.colors.each do |color_name, color|
|
|
203
|
+
add_color_declaration(structure_elem, color_name, color)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def add_color_declaration(structure_elem, color_name, color)
|
|
208
|
+
declaration = structure_elem.add_element('declaration')
|
|
209
|
+
|
|
210
|
+
name_elem = declaration.add_element('name')
|
|
211
|
+
text_elem = name_elem.add_element('text')
|
|
212
|
+
text_elem.text = color_name.to_s
|
|
213
|
+
|
|
214
|
+
# Add color attributes as structure
|
|
215
|
+
if color.attributes.any?
|
|
216
|
+
sort_elem = declaration.add_element('sort')
|
|
217
|
+
struct_elem = sort_elem.add_element('struct')
|
|
218
|
+
|
|
219
|
+
color.attributes.each do |attr_name, attr_type|
|
|
220
|
+
field_elem = struct_elem.add_element('field', {
|
|
221
|
+
'name' => attr_name.to_s,
|
|
222
|
+
'type' => attr_type.to_s
|
|
223
|
+
})
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module PetriFlow
|
|
6
|
+
module Export
|
|
7
|
+
# Exports Petri nets and Colored Petri Nets to YAML format
|
|
8
|
+
# Provides the most human-readable format, ideal for configuration and documentation
|
|
9
|
+
class YamlExporter
|
|
10
|
+
attr_reader :net
|
|
11
|
+
|
|
12
|
+
def initialize(net)
|
|
13
|
+
@net = net
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Export to YAML string
|
|
17
|
+
# @return [String] YAML representation of the net
|
|
18
|
+
def to_yaml
|
|
19
|
+
to_hash.to_yaml
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Export to Ruby hash
|
|
23
|
+
# @return [Hash] Hash representation of the net
|
|
24
|
+
def to_hash
|
|
25
|
+
{
|
|
26
|
+
'meta' => meta_info,
|
|
27
|
+
'net' => net_structure,
|
|
28
|
+
'colors' => color_definitions,
|
|
29
|
+
'places' => places_data,
|
|
30
|
+
'transitions' => transitions_data,
|
|
31
|
+
'arcs' => arcs_data,
|
|
32
|
+
'marking' => current_marking,
|
|
33
|
+
'statistics' => statistics
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Save to YAML file
|
|
38
|
+
def save_yaml(filename)
|
|
39
|
+
File.write(filename, to_yaml)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def colored_net?
|
|
45
|
+
@net.is_a?(PetriFlow::Colored::ColoredNet)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def meta_info
|
|
49
|
+
{
|
|
50
|
+
'format' => 'PetriFlow YAML Export',
|
|
51
|
+
'version' => '1.0',
|
|
52
|
+
'exported_at' => Time.now.utc.iso8601,
|
|
53
|
+
'net_type' => colored_net? ? 'colored_petri_net' : 'petri_net'
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def net_structure
|
|
58
|
+
{
|
|
59
|
+
'name' => @net.name,
|
|
60
|
+
'type' => colored_net? ? 'ColoredPetriNet' : 'PetriNet',
|
|
61
|
+
'place_count' => @net.places.size,
|
|
62
|
+
'transition_count' => @net.transitions.size,
|
|
63
|
+
'arc_count' => @net.arcs.size
|
|
64
|
+
}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def color_definitions
|
|
68
|
+
return [] unless colored_net?
|
|
69
|
+
|
|
70
|
+
@net.colors.map do |color_name, color|
|
|
71
|
+
{
|
|
72
|
+
'name' => color_name.to_s,
|
|
73
|
+
'attributes' => format_attributes(color.attributes),
|
|
74
|
+
'has_validator' => !color.validator.nil?
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def format_attributes(attributes)
|
|
80
|
+
attributes.transform_keys(&:to_s).transform_values do |v|
|
|
81
|
+
v.is_a?(Symbol) ? v.to_s : v
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def places_data
|
|
86
|
+
@net.places.map do |place_id, place|
|
|
87
|
+
place_hash = {
|
|
88
|
+
'id' => place_id.to_s,
|
|
89
|
+
'name' => place.name,
|
|
90
|
+
'tokens' => place.tokens,
|
|
91
|
+
'capacity' => place.capacity == Float::INFINITY ? 'infinite' : place.capacity
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# Add colored net place info
|
|
95
|
+
if colored_net? && @net.colored_places[place_id]
|
|
96
|
+
colored_info = @net.colored_places[place_id]
|
|
97
|
+
place_hash['color'] = colored_info[:color].to_s if colored_info[:color]
|
|
98
|
+
|
|
99
|
+
colored_tokens = format_colored_tokens(place_id)
|
|
100
|
+
place_hash['colored_tokens'] = colored_tokens if colored_tokens.any?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
place_hash
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def format_colored_tokens(place_id)
|
|
108
|
+
return [] unless colored_net?
|
|
109
|
+
|
|
110
|
+
tokens = @net.token_pools[place_id] || []
|
|
111
|
+
tokens.map do |token|
|
|
112
|
+
token_hash = {
|
|
113
|
+
'id' => token.id,
|
|
114
|
+
'color' => token.color.to_s
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
token_hash['data'] = stringify_hash(token.data) if token.data.any?
|
|
118
|
+
token_hash['timestamp'] = token.timestamp if token.timestamp
|
|
119
|
+
|
|
120
|
+
token_hash
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def stringify_hash(hash)
|
|
125
|
+
hash.transform_keys(&:to_s).transform_values do |v|
|
|
126
|
+
case v
|
|
127
|
+
when Symbol
|
|
128
|
+
v.to_s
|
|
129
|
+
when Hash
|
|
130
|
+
stringify_hash(v)
|
|
131
|
+
when Array
|
|
132
|
+
v.map { |item| item.is_a?(Hash) ? stringify_hash(item) : item }
|
|
133
|
+
else
|
|
134
|
+
v
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def transitions_data
|
|
140
|
+
@net.transitions.map do |trans_id, transition|
|
|
141
|
+
trans_hash = {
|
|
142
|
+
'id' => trans_id.to_s,
|
|
143
|
+
'name' => transition.name,
|
|
144
|
+
'input_arcs' => transition.input_arcs.size,
|
|
145
|
+
'output_arcs' => transition.output_arcs.size,
|
|
146
|
+
'enabled' => transition.enabled?
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# Add guard info
|
|
150
|
+
if transition.guard
|
|
151
|
+
trans_hash['guard'] = format_guard(transition.guard)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
trans_hash
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def format_guard(guard)
|
|
159
|
+
if guard.respond_to?(:name) && guard.name
|
|
160
|
+
{
|
|
161
|
+
'type' => 'named_guard',
|
|
162
|
+
'name' => guard.name,
|
|
163
|
+
'description' => guard.name
|
|
164
|
+
}
|
|
165
|
+
elsif guard.respond_to?(:condition)
|
|
166
|
+
{
|
|
167
|
+
'type' => 'guard',
|
|
168
|
+
'description' => 'Custom guard condition'
|
|
169
|
+
}
|
|
170
|
+
else
|
|
171
|
+
{
|
|
172
|
+
'type' => 'proc',
|
|
173
|
+
'description' => 'Lambda/Proc guard'
|
|
174
|
+
}
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def arcs_data
|
|
179
|
+
@net.arcs.map.with_index do |arc, index|
|
|
180
|
+
arc_hash = {
|
|
181
|
+
'id' => "arc_#{index}",
|
|
182
|
+
'source' => {
|
|
183
|
+
'id' => arc.source.id.to_s,
|
|
184
|
+
'name' => arc.source.name,
|
|
185
|
+
'type' => arc.source.class.name.split('::').last
|
|
186
|
+
},
|
|
187
|
+
'target' => {
|
|
188
|
+
'id' => arc.target.id.to_s,
|
|
189
|
+
'name' => arc.target.name,
|
|
190
|
+
'type' => arc.target.class.name.split('::').last
|
|
191
|
+
},
|
|
192
|
+
'weight' => arc.weight,
|
|
193
|
+
'direction' => arc.input_arc? ? 'place_to_transition' : 'transition_to_place'
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# Add arc expression info
|
|
197
|
+
if arc.expression
|
|
198
|
+
arc_hash['expression'] = format_expression(arc.expression)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
arc_hash
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def format_expression(expression)
|
|
206
|
+
if expression.respond_to?(:name) && expression.name
|
|
207
|
+
{
|
|
208
|
+
'type' => 'named_expression',
|
|
209
|
+
'name' => expression.name,
|
|
210
|
+
'description' => expression.name
|
|
211
|
+
}
|
|
212
|
+
elsif expression.respond_to?(:transformation)
|
|
213
|
+
{
|
|
214
|
+
'type' => 'arc_expression',
|
|
215
|
+
'description' => 'Custom arc expression'
|
|
216
|
+
}
|
|
217
|
+
else
|
|
218
|
+
{
|
|
219
|
+
'type' => 'proc',
|
|
220
|
+
'description' => 'Lambda/Proc expression'
|
|
221
|
+
}
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def current_marking
|
|
226
|
+
marking_hash = {
|
|
227
|
+
'tokens_by_place' => stringify_hash(@net.places.transform_values(&:tokens)),
|
|
228
|
+
'total_tokens' => @net.places.values.sum(&:tokens)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if colored_net?
|
|
232
|
+
marking_hash['colored_marking'] = stringify_hash(@net.colored_marking)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
marking_hash
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def statistics
|
|
239
|
+
stringify_hash(@net.stats.merge({
|
|
240
|
+
enabled_transitions_list: @net.enabled_transitions.map { |t| t.id.to_s },
|
|
241
|
+
deadlocked: @net.deadlocked?
|
|
242
|
+
}))
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|