bigraph-schema 0.0.71__py3-none-any.whl
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.
Potentially problematic release.
This version of bigraph-schema might be problematic. Click here for more details.
- bigraph_schema/__init__.py +8 -0
- bigraph_schema/edge.py +129 -0
- bigraph_schema/parse.py +165 -0
- bigraph_schema/protocols.py +35 -0
- bigraph_schema/registry.py +287 -0
- bigraph_schema/tests.py +2631 -0
- bigraph_schema/type_functions.py +3379 -0
- bigraph_schema/type_system.py +1798 -0
- bigraph_schema/type_system_adjunct.py +502 -0
- bigraph_schema/units.py +133 -0
- bigraph_schema/utilities.py +243 -0
- bigraph_schema-0.0.71.dist-info/METADATA +52 -0
- bigraph_schema-0.0.71.dist-info/RECORD +17 -0
- bigraph_schema-0.0.71.dist-info/WHEEL +5 -0
- bigraph_schema-0.0.71.dist-info/licenses/AUTHORS.md +6 -0
- bigraph_schema-0.0.71.dist-info/licenses/LICENSE +201 -0
- bigraph_schema-0.0.71.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
from bigraph_schema.utilities import union_keys
|
|
3
|
+
from bigraph_schema import non_schema_keys, is_schema_key
|
|
4
|
+
from bigraph_schema.type_functions import (
|
|
5
|
+
apply_schema, TYPE_SCHEMAS, type_schema_keys, resolve_path)
|
|
6
|
+
|
|
7
|
+
class TypeSystemAdjunct():
|
|
8
|
+
"""holds implementations of defunct or not yet ready TypeSystem methods"""
|
|
9
|
+
@staticmethod
|
|
10
|
+
def import_types(type_system, package, strict=False):
|
|
11
|
+
for type_key, type_data in package.items():
|
|
12
|
+
if not (strict and type_system.exists(type_key)):
|
|
13
|
+
type_system.register(
|
|
14
|
+
type_key,
|
|
15
|
+
type_data)
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def define(type_system, method_name, methods):
|
|
19
|
+
method_key = f'_{method_name}'
|
|
20
|
+
for type_key, method in methods.items():
|
|
21
|
+
type_system.register(
|
|
22
|
+
type_key,
|
|
23
|
+
{method_key: method})
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def link_place(type_system, place, link):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def compose(type_system, a, b):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def query(type_system, schema, instance, redex):
|
|
37
|
+
subschema = {}
|
|
38
|
+
return subschema
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def complete(type_system, initial_schema, initial_state):
|
|
42
|
+
full_schema = type_system.access(
|
|
43
|
+
initial_schema)
|
|
44
|
+
|
|
45
|
+
state = type_system.deserialize(
|
|
46
|
+
full_schema,
|
|
47
|
+
initial_state)
|
|
48
|
+
|
|
49
|
+
# fill in the parts of the composition schema
|
|
50
|
+
# determined by the state
|
|
51
|
+
schema, state = type_system.infer_schema(
|
|
52
|
+
full_schema,
|
|
53
|
+
state)
|
|
54
|
+
|
|
55
|
+
final_state = type_system.fill(schema, state)
|
|
56
|
+
|
|
57
|
+
# TODO: add flag to types.access(copy=True)
|
|
58
|
+
return type_system.access(schema), final_state
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def lookup(type_system, type_key, attribute):
|
|
62
|
+
return type_system.access(type_key).get(attribute)
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def resolve_parameters(type_system, type_parameters, schema):
|
|
66
|
+
"""
|
|
67
|
+
find the types associated with any type parameters in the schema
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
type_parameter: type_system.access(
|
|
72
|
+
schema.get(f'_{type_parameter}'))
|
|
73
|
+
for type_parameter in type_parameters}
|
|
74
|
+
|
|
75
|
+
@staticmethod
|
|
76
|
+
def types(type_system):
|
|
77
|
+
return {
|
|
78
|
+
type_key: type_data
|
|
79
|
+
for type_key, type_data in type_system.registry.items()}
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def merge_schemas(type_system, current, update):
|
|
83
|
+
if current == update:
|
|
84
|
+
return update
|
|
85
|
+
if current is None:
|
|
86
|
+
return update
|
|
87
|
+
if update is None:
|
|
88
|
+
return current
|
|
89
|
+
if not isinstance(current, dict):
|
|
90
|
+
return update
|
|
91
|
+
if not isinstance(update, dict):
|
|
92
|
+
return update
|
|
93
|
+
|
|
94
|
+
merged = {}
|
|
95
|
+
|
|
96
|
+
for key in union_keys(current, update):
|
|
97
|
+
if key in current:
|
|
98
|
+
if key in update:
|
|
99
|
+
subcurrent = current[key]
|
|
100
|
+
subupdate = update[key]
|
|
101
|
+
if subcurrent == current or subupdate == update:
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
merged[key] = type_system.merge_schemas(
|
|
105
|
+
subcurrent,
|
|
106
|
+
subupdate)
|
|
107
|
+
else:
|
|
108
|
+
merged[key] = current[key]
|
|
109
|
+
else:
|
|
110
|
+
merged[key] = update[key]
|
|
111
|
+
|
|
112
|
+
return merged
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# TODO: if its an edge, ensure ports match wires
|
|
117
|
+
# TODO: make this work again, return information about what is wrong
|
|
118
|
+
# with the schema
|
|
119
|
+
@staticmethod
|
|
120
|
+
def validate_state(type_system, original_schema, state):
|
|
121
|
+
schema = type_system.access(original_schema)
|
|
122
|
+
validation = {}
|
|
123
|
+
|
|
124
|
+
if '_serialize' in schema:
|
|
125
|
+
if '_deserialize' not in schema:
|
|
126
|
+
validation = {
|
|
127
|
+
'_deserialize': f'serialize found in type without deserialize: {schema}'
|
|
128
|
+
}
|
|
129
|
+
else:
|
|
130
|
+
serialize = type_system.serialize_registry.access(
|
|
131
|
+
schema['_serialize'])
|
|
132
|
+
deserialize = type_system.deserialize_registry.access(
|
|
133
|
+
schema['_deserialize'])
|
|
134
|
+
serial = serialize(state)
|
|
135
|
+
pass_through = deserialize(serial)
|
|
136
|
+
|
|
137
|
+
if state != pass_through:
|
|
138
|
+
validation = f'state and pass_through are not the same: {serial}'
|
|
139
|
+
else:
|
|
140
|
+
for key, subschema in schema.items():
|
|
141
|
+
if key not in type_schema_keys:
|
|
142
|
+
if key not in state:
|
|
143
|
+
validation[key] = f'key present in schema but not in state: {key}\nschema: {schema}\nstate: {state}\n'
|
|
144
|
+
else:
|
|
145
|
+
subvalidation = type_system.validate_state(
|
|
146
|
+
subschema,
|
|
147
|
+
state[key])
|
|
148
|
+
if not (subvalidation is None or len(subvalidation) == 0):
|
|
149
|
+
validation[key] = subvalidation
|
|
150
|
+
|
|
151
|
+
return validation
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def validate(type_system, schema, state):
|
|
157
|
+
# TODO:
|
|
158
|
+
# go through the state using the schema and
|
|
159
|
+
# return information about what doesn't match
|
|
160
|
+
|
|
161
|
+
return {}
|
|
162
|
+
|
|
163
|
+
@staticmethod
|
|
164
|
+
def apply_slice(type_system, schema, state, path, update):
|
|
165
|
+
path = path or ()
|
|
166
|
+
if len(path) == 0:
|
|
167
|
+
result = type_system.apply(
|
|
168
|
+
schema,
|
|
169
|
+
state,
|
|
170
|
+
update)
|
|
171
|
+
|
|
172
|
+
else:
|
|
173
|
+
subschema, substate = type_system.slice(
|
|
174
|
+
schema,
|
|
175
|
+
state,
|
|
176
|
+
path[0])
|
|
177
|
+
|
|
178
|
+
if len(path) == 1:
|
|
179
|
+
subresult = type_system.apply(
|
|
180
|
+
subschema,
|
|
181
|
+
substate,
|
|
182
|
+
update)
|
|
183
|
+
|
|
184
|
+
result = type_system.bind(
|
|
185
|
+
schema,
|
|
186
|
+
state,
|
|
187
|
+
path[1:],
|
|
188
|
+
subschema,
|
|
189
|
+
subresult)
|
|
190
|
+
|
|
191
|
+
else:
|
|
192
|
+
subresult = type_system.apply_slice(
|
|
193
|
+
subschema,
|
|
194
|
+
substate,
|
|
195
|
+
path[1:],
|
|
196
|
+
update)
|
|
197
|
+
|
|
198
|
+
result = state
|
|
199
|
+
|
|
200
|
+
return result
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@staticmethod
|
|
204
|
+
def set_update(type_system, schema, state, update):
|
|
205
|
+
if '_apply' in schema:
|
|
206
|
+
apply_function = type_system.apply_registry.access('set')
|
|
207
|
+
|
|
208
|
+
state = apply_function(
|
|
209
|
+
schema,
|
|
210
|
+
state,
|
|
211
|
+
update,
|
|
212
|
+
type_system)
|
|
213
|
+
|
|
214
|
+
elif isinstance(schema, str) or isinstance(schema, list):
|
|
215
|
+
schema = type_system.access(schema)
|
|
216
|
+
state = type_system.set_update(schema, state, update)
|
|
217
|
+
|
|
218
|
+
elif isinstance(update, dict):
|
|
219
|
+
for key, branch in update.items():
|
|
220
|
+
if key not in schema:
|
|
221
|
+
raise Exception(
|
|
222
|
+
f'trying to update a key that is not in the schema'
|
|
223
|
+
f'for state: {key}\n{state}\nwith schema:\n{schema}')
|
|
224
|
+
else:
|
|
225
|
+
subupdate = type_system.set_update(
|
|
226
|
+
schema[key],
|
|
227
|
+
state[key],
|
|
228
|
+
branch)
|
|
229
|
+
|
|
230
|
+
state[key] = subupdate
|
|
231
|
+
else:
|
|
232
|
+
raise Exception(
|
|
233
|
+
f'trying to apply update\n {update}\nto state\n {state}\n'
|
|
234
|
+
f'with schema\n{schema}, but the update is not a dict')
|
|
235
|
+
|
|
236
|
+
return state
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@staticmethod
|
|
240
|
+
def set(type_system, original_schema, initial, update):
|
|
241
|
+
schema = type_system.access(original_schema)
|
|
242
|
+
state = copy.deepcopy(initial)
|
|
243
|
+
|
|
244
|
+
return type_system.set_update(schema, state, update)
|
|
245
|
+
|
|
246
|
+
@staticmethod
|
|
247
|
+
def fill_ports(type_system, interface, wires=None, state=None,
|
|
248
|
+
top_schema=None, top_state=None, path=None):
|
|
249
|
+
# deal with wires
|
|
250
|
+
if wires is None:
|
|
251
|
+
wires = {}
|
|
252
|
+
if state is None:
|
|
253
|
+
state = {}
|
|
254
|
+
if top_schema is None:
|
|
255
|
+
top_schema = schema
|
|
256
|
+
if top_state is None:
|
|
257
|
+
top_state = state
|
|
258
|
+
if path is None:
|
|
259
|
+
path = []
|
|
260
|
+
|
|
261
|
+
if isinstance(interface, str):
|
|
262
|
+
interface = {'_type': interface}
|
|
263
|
+
|
|
264
|
+
for port_key, subwires in wires.items():
|
|
265
|
+
if port_key in interface:
|
|
266
|
+
port_schema = interface[port_key]
|
|
267
|
+
else:
|
|
268
|
+
port_schema, subwires = type_system.slice(
|
|
269
|
+
interface,
|
|
270
|
+
wires,
|
|
271
|
+
port_key)
|
|
272
|
+
|
|
273
|
+
if isinstance(subwires, dict):
|
|
274
|
+
if isinstance(state, dict):
|
|
275
|
+
state = type_system.fill_ports(
|
|
276
|
+
port_schema,
|
|
277
|
+
wires=subwires,
|
|
278
|
+
state=state,
|
|
279
|
+
top_schema=top_schema,
|
|
280
|
+
top_state=top_state,
|
|
281
|
+
path=path)
|
|
282
|
+
|
|
283
|
+
else:
|
|
284
|
+
if isinstance(subwires, str):
|
|
285
|
+
subwires = [subwires]
|
|
286
|
+
|
|
287
|
+
subschema, substate = type_system.set_slice(
|
|
288
|
+
top_schema,
|
|
289
|
+
top_state,
|
|
290
|
+
path[:-1] + subwires,
|
|
291
|
+
port_schema,
|
|
292
|
+
type_system.default(port_schema),
|
|
293
|
+
defer=True)
|
|
294
|
+
|
|
295
|
+
return state
|
|
296
|
+
|
|
297
|
+
# def infer_wires(self, ports, state,
|
|
298
|
+
# wires, top_schema=None,
|
|
299
|
+
# top_state=None, path=None, internal_path=None):
|
|
300
|
+
@staticmethod
|
|
301
|
+
def infer_wires(type_system, ports, wires, top_schema=None, top_state=None,
|
|
302
|
+
path=None, internal_path=None):
|
|
303
|
+
top_schema = top_schema or {}
|
|
304
|
+
top_state = top_state or state
|
|
305
|
+
path = path or ()
|
|
306
|
+
internal_path = internal_path or ()
|
|
307
|
+
|
|
308
|
+
if isinstance(ports, str):
|
|
309
|
+
ports = type_system.access(ports)
|
|
310
|
+
|
|
311
|
+
if isinstance(wires, (list, tuple)):
|
|
312
|
+
if len(wires) == 0:
|
|
313
|
+
destination_schema, destination_state = top_schema, top_state
|
|
314
|
+
|
|
315
|
+
else:
|
|
316
|
+
destination_schema, destination_state = type_system.slice(
|
|
317
|
+
top_schema,
|
|
318
|
+
top_state,
|
|
319
|
+
path[:-1] + wires)
|
|
320
|
+
|
|
321
|
+
merged_schema = apply_schema(
|
|
322
|
+
'schema',
|
|
323
|
+
destination_schema,
|
|
324
|
+
ports,
|
|
325
|
+
type_system)
|
|
326
|
+
|
|
327
|
+
merged_state = type_system.complete(
|
|
328
|
+
merged_schema,
|
|
329
|
+
destination_state)
|
|
330
|
+
|
|
331
|
+
else:
|
|
332
|
+
for port_key, port_wires in wires.items():
|
|
333
|
+
subschema, substate = type_system.slice(
|
|
334
|
+
ports,
|
|
335
|
+
{},
|
|
336
|
+
port_key)
|
|
337
|
+
|
|
338
|
+
if isinstance(port_wires, dict):
|
|
339
|
+
top_schema, top_state = type_system.infer_wires(
|
|
340
|
+
subschema,
|
|
341
|
+
# substate,
|
|
342
|
+
port_wires,
|
|
343
|
+
top_schema=top_schema,
|
|
344
|
+
top_state=top_state,
|
|
345
|
+
path=path,
|
|
346
|
+
internal_path=internal_path+(port_key,))
|
|
347
|
+
|
|
348
|
+
# port_wires must be a list
|
|
349
|
+
elif len(port_wires) == 0:
|
|
350
|
+
raise Exception(f'no wires at port "{port_key}" in ports {ports} with state {state}')
|
|
351
|
+
|
|
352
|
+
else:
|
|
353
|
+
compound_path = resolve_path(
|
|
354
|
+
path[:-1] + tuple(port_wires))
|
|
355
|
+
|
|
356
|
+
compound_schema, compound_state = type_system.set_slice(
|
|
357
|
+
{}, {},
|
|
358
|
+
compound_path,
|
|
359
|
+
subschema or 'any',
|
|
360
|
+
type_system.default(subschema))
|
|
361
|
+
|
|
362
|
+
top_schema = type_system.resolve(
|
|
363
|
+
top_schema,
|
|
364
|
+
compound_schema)
|
|
365
|
+
|
|
366
|
+
top_state = type_system.merge_recur(
|
|
367
|
+
top_schema,
|
|
368
|
+
compound_state,
|
|
369
|
+
top_state)
|
|
370
|
+
|
|
371
|
+
return top_schema, top_state
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
@staticmethod
|
|
375
|
+
def infer_edge(type_system, schema, state,
|
|
376
|
+
top_schema=None, top_state=None, path=None):
|
|
377
|
+
'''
|
|
378
|
+
given the schema and state for this edge, and its path relative to
|
|
379
|
+
the top_schema and top_state, make all the necessary completions to
|
|
380
|
+
both the schema and the state according to the input and output schemas
|
|
381
|
+
of this edge in '_inputs' and '_outputs', along the wires in its state
|
|
382
|
+
under 'inputs' and 'outputs'.
|
|
383
|
+
|
|
384
|
+
returns the top_schema and top_state, even if the edge is deeply embedded,
|
|
385
|
+
as the particular wires could have implications anywhere in the tree.
|
|
386
|
+
'''
|
|
387
|
+
|
|
388
|
+
schema = schema or {}
|
|
389
|
+
top_schema = top_schema or schema
|
|
390
|
+
top_state = top_state or state
|
|
391
|
+
path = path or ()
|
|
392
|
+
|
|
393
|
+
if type_system.check('edge', state):
|
|
394
|
+
for port_key in ['inputs', 'outputs']:
|
|
395
|
+
ports = state.get(port_key)
|
|
396
|
+
schema_key = f'_{port_key}'
|
|
397
|
+
port_schema = schema.get(schema_key, {})
|
|
398
|
+
state_schema = state.get(schema_key, {})
|
|
399
|
+
|
|
400
|
+
schema[schema_key] = type_system.resolve(
|
|
401
|
+
port_schema,
|
|
402
|
+
type_system.access(
|
|
403
|
+
state_schema))
|
|
404
|
+
|
|
405
|
+
if ports:
|
|
406
|
+
top_schema, top_state = type_system.infer_wires(
|
|
407
|
+
schema[schema_key],
|
|
408
|
+
# state,
|
|
409
|
+
ports,
|
|
410
|
+
top_schema=top_schema,
|
|
411
|
+
top_state=top_state,
|
|
412
|
+
path=path)
|
|
413
|
+
|
|
414
|
+
return top_schema, top_state
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
@staticmethod
|
|
418
|
+
def infer_schema(type_system, schema, state,
|
|
419
|
+
top_schema=None, top_state=None, path=None):
|
|
420
|
+
"""
|
|
421
|
+
Given a schema fragment and an existing state with _type keys,
|
|
422
|
+
return the full schema required to describe that state,
|
|
423
|
+
and whatever state was hydrated (edges) during this process
|
|
424
|
+
|
|
425
|
+
"""
|
|
426
|
+
|
|
427
|
+
# during recursive call, schema is kept at the top level and the
|
|
428
|
+
# path is used to access it (!)
|
|
429
|
+
|
|
430
|
+
schema = schema or {}
|
|
431
|
+
top_schema = top_schema or schema
|
|
432
|
+
top_state = top_state or state
|
|
433
|
+
path = path or ()
|
|
434
|
+
|
|
435
|
+
if isinstance(state, dict):
|
|
436
|
+
state_schema = None
|
|
437
|
+
if '_type' in state:
|
|
438
|
+
state_type = {
|
|
439
|
+
key: value
|
|
440
|
+
for key, value in state.items()
|
|
441
|
+
if is_schema_key(key)}
|
|
442
|
+
|
|
443
|
+
schema = type_system.resolve(
|
|
444
|
+
schema,
|
|
445
|
+
state_type)
|
|
446
|
+
|
|
447
|
+
if '_type' in schema:
|
|
448
|
+
hydrated_state = type_system.deserialize(
|
|
449
|
+
schema,
|
|
450
|
+
state)
|
|
451
|
+
|
|
452
|
+
top_schema, top_state = type_system.set_slice(
|
|
453
|
+
top_schema,
|
|
454
|
+
top_state,
|
|
455
|
+
path,
|
|
456
|
+
schema,
|
|
457
|
+
hydrated_state)
|
|
458
|
+
|
|
459
|
+
top_schema, top_state = type_system.infer_edge(
|
|
460
|
+
schema,
|
|
461
|
+
hydrated_state,
|
|
462
|
+
top_schema,
|
|
463
|
+
top_state,
|
|
464
|
+
path)
|
|
465
|
+
|
|
466
|
+
else:
|
|
467
|
+
for key in state:
|
|
468
|
+
inner_path = path + (key,)
|
|
469
|
+
inner_schema, inner_state = type_system.slice(
|
|
470
|
+
schema,
|
|
471
|
+
state,
|
|
472
|
+
key)
|
|
473
|
+
|
|
474
|
+
top_schema, top_state = type_system.infer_schema(
|
|
475
|
+
inner_schema,
|
|
476
|
+
inner_state,
|
|
477
|
+
top_schema=top_schema,
|
|
478
|
+
top_state=top_state,
|
|
479
|
+
path=inner_path)
|
|
480
|
+
|
|
481
|
+
elif isinstance(state, str):
|
|
482
|
+
pass
|
|
483
|
+
|
|
484
|
+
else:
|
|
485
|
+
type_schema = TYPE_SCHEMAS.get(
|
|
486
|
+
type(state).__name__,
|
|
487
|
+
'any')
|
|
488
|
+
|
|
489
|
+
top_schema, top_state = type_system.set_slice(
|
|
490
|
+
top_schema,
|
|
491
|
+
top_state,
|
|
492
|
+
path,
|
|
493
|
+
type_schema,
|
|
494
|
+
state)
|
|
495
|
+
|
|
496
|
+
return top_schema, top_state
|
|
497
|
+
|
|
498
|
+
@staticmethod
|
|
499
|
+
def hydrate(type_system, schema, state):
|
|
500
|
+
hydrated = type_system.deserialize(schema, state)
|
|
501
|
+
return type_system.fill(schema, hydrated)
|
|
502
|
+
|
bigraph_schema/units.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""
|
|
2
|
+
=====
|
|
3
|
+
Units
|
|
4
|
+
=====
|
|
5
|
+
|
|
6
|
+
Register all of the unit types from the pint unit registry
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pint import UnitRegistry
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
units = UnitRegistry()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def render_coefficient(original_power):
|
|
16
|
+
power = abs(original_power)
|
|
17
|
+
int_part = int(power)
|
|
18
|
+
root_part = power % 1
|
|
19
|
+
|
|
20
|
+
if root_part != 0.0:
|
|
21
|
+
render = str(root_part)[2:]
|
|
22
|
+
render = f'{int_part}_{render}'
|
|
23
|
+
else:
|
|
24
|
+
render = str(int_part)
|
|
25
|
+
|
|
26
|
+
return render
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def render_units_type(dimensionality):
|
|
30
|
+
unit_keys = list(dimensionality.keys())
|
|
31
|
+
unit_keys.sort()
|
|
32
|
+
|
|
33
|
+
numerator = []
|
|
34
|
+
denominator = []
|
|
35
|
+
|
|
36
|
+
for unit_key in unit_keys:
|
|
37
|
+
inner_key = unit_key.strip('[]')
|
|
38
|
+
power = dimensionality[unit_key]
|
|
39
|
+
negative = False
|
|
40
|
+
|
|
41
|
+
if power < 0:
|
|
42
|
+
negative = True
|
|
43
|
+
power = -power
|
|
44
|
+
|
|
45
|
+
if power == 1:
|
|
46
|
+
render = inner_key
|
|
47
|
+
else:
|
|
48
|
+
render = f'{inner_key}^{render_coefficient(power)}'
|
|
49
|
+
|
|
50
|
+
if negative:
|
|
51
|
+
denominator.append(render)
|
|
52
|
+
else:
|
|
53
|
+
numerator.append(render)
|
|
54
|
+
|
|
55
|
+
render = '*'.join(numerator)
|
|
56
|
+
if len(denominator) > 0:
|
|
57
|
+
render_denominator = '*'.join(denominator)
|
|
58
|
+
render = f'{render}/{render_denominator}'
|
|
59
|
+
|
|
60
|
+
return render
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def parse_coefficient(s):
|
|
64
|
+
if s is None:
|
|
65
|
+
return 1
|
|
66
|
+
elif '_' in s:
|
|
67
|
+
parts = s.split('_')
|
|
68
|
+
if len(parts) > 1:
|
|
69
|
+
base, residue = parts
|
|
70
|
+
return int(base) + (float(residue) / 10.0)
|
|
71
|
+
else:
|
|
72
|
+
return int(parts)
|
|
73
|
+
else:
|
|
74
|
+
return int(s)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def parse_dimensionality(s):
|
|
78
|
+
numerator, denominator = s.split('/')
|
|
79
|
+
numerator_terms = numerator.split('*')
|
|
80
|
+
denominator_terms = denominator.split('*')
|
|
81
|
+
|
|
82
|
+
dimensionality = {}
|
|
83
|
+
|
|
84
|
+
for term in numerator_terms:
|
|
85
|
+
base = term.split('^')
|
|
86
|
+
exponent = None
|
|
87
|
+
if len(base) > 1:
|
|
88
|
+
exponent = base[1]
|
|
89
|
+
dimensionality[f'[{base[0]}]'] = parse_coefficient(exponent)
|
|
90
|
+
|
|
91
|
+
for term in denominator_terms:
|
|
92
|
+
power = term.split('^')
|
|
93
|
+
exponent = None
|
|
94
|
+
if len(power) > 1:
|
|
95
|
+
exponent = power[1]
|
|
96
|
+
dimensionality[f'[{power[0]}]'] = -parse_coefficient(exponent)
|
|
97
|
+
|
|
98
|
+
return dimensionality
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_units_render():
|
|
102
|
+
dimensionality = units.newton.dimensionality
|
|
103
|
+
render = render_units_type(dimensionality)
|
|
104
|
+
recover = parse_dimensionality(render)
|
|
105
|
+
|
|
106
|
+
print(f'original: {dimensionality}')
|
|
107
|
+
print(f'render: {render}')
|
|
108
|
+
print(f'parsed: {recover}')
|
|
109
|
+
|
|
110
|
+
assert render == 'length*mass/time^2'
|
|
111
|
+
assert recover == dimensionality
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_roots_cycle():
|
|
115
|
+
dimensionality = {
|
|
116
|
+
'[length]': 1.5,
|
|
117
|
+
'[time]': 3,
|
|
118
|
+
'[mass]': -2.5,
|
|
119
|
+
}
|
|
120
|
+
render = render_units_type(dimensionality)
|
|
121
|
+
recover = parse_dimensionality(render)
|
|
122
|
+
|
|
123
|
+
print(f'original: {dimensionality}')
|
|
124
|
+
print(f'render: {render}')
|
|
125
|
+
print(f'parsed: {recover}')
|
|
126
|
+
|
|
127
|
+
assert render == 'length^1_5*time^3/mass^2_5'
|
|
128
|
+
assert recover == dimensionality
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
if __name__ == '__main__':
|
|
132
|
+
test_units_render()
|
|
133
|
+
test_roots_cycle()
|