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.

@@ -0,0 +1,2631 @@
1
+ """
2
+ Tests for the type system and schema manipulation functions
3
+ """
4
+
5
+ import pytest
6
+ import pprint
7
+ import numpy as np
8
+ from dataclasses import asdict
9
+
10
+ from bigraph_schema import local_lookup_module, TypeSystem
11
+ from bigraph_schema.type_functions import (
12
+ accumulate, base_types, data_module, deserialize_integer,
13
+ divide_longest, to_string)
14
+ from bigraph_schema.utilities import compare_dicts, NONE_SYMBOL, state_instance
15
+ from bigraph_schema.units import units
16
+ from bigraph_schema.registry import establish_path, remove_omitted
17
+
18
+
19
+ @pytest.fixture
20
+ def core():
21
+ core = TypeSystem()
22
+ return register_test_types(core)
23
+
24
+
25
+ def register_test_types(core):
26
+ """
27
+ defines the test schemas
28
+ """
29
+ register_cube(core)
30
+
31
+ core.register('compartment', {
32
+ 'counts': 'tree[float]',
33
+ 'inner': 'tree[compartment]'})
34
+
35
+ core.register('metaedge', {
36
+ '_inherit': 'edge',
37
+ '_inputs': {
38
+ 'before': 'metaedge'},
39
+ '_outputs': {
40
+ 'after': 'metaedge'}})
41
+
42
+ return core
43
+
44
+
45
+ def register_cube(core):
46
+ """
47
+ adds an additional simple schema for the test fixture
48
+ """
49
+ cube_schema = {
50
+ 'shape': {
51
+ '_type': 'shape',
52
+ '_description': 'abstract shape type'},
53
+
54
+ 'rectangle': {
55
+ '_type': 'rectangle',
56
+ '_divide': divide_longest,
57
+ '_description': 'a two-dimensional value',
58
+ '_inherit': 'shape',
59
+ 'width': {'_type': 'integer'},
60
+ 'height': {'_type': 'integer'},
61
+ },
62
+
63
+ # cannot override existing keys unless it is of a subtype
64
+ 'cube': {
65
+ '_type': 'cube',
66
+ '_inherit': 'rectangle',
67
+ 'depth': {'_type': 'integer'},
68
+ },
69
+ }
70
+
71
+ for type_key, type_data in cube_schema.items():
72
+ core.register(type_key, type_data)
73
+
74
+ return core
75
+
76
+
77
+ def test_basic_types(core):
78
+ assert core.find('integer')
79
+ assert core.find('cube')['depth']['_type'] == 'integer'
80
+ # inheritance
81
+ assert core.find('cube')['width']['_type'] == 'integer'
82
+
83
+
84
+ def test_update_types(core):
85
+ core.update_types({'A': "payload"})
86
+ desc = "a placeholder type with no structure"
87
+ core.update_types({'A': {'_description':desc}})
88
+ assert core.access('A') == {'_type': 'payload', '_description': desc}
89
+
90
+ core.update_types({'B': "alternate", 'C': "tertiary"})
91
+ assert core.access('A') == {'_type': 'payload', '_description': desc}
92
+ assert core.access('B') == {'_type': 'alternate'}
93
+ assert core.access('C') == {'_type': 'tertiary'}
94
+
95
+
96
+ def test_reregister_type(core):
97
+ """
98
+ register raises an exception if replacing a schema with strict
99
+
100
+ it replaces the schema if not strict
101
+ """
102
+ core.register('R_A', {'_default': 'a'})
103
+ with pytest.raises(Exception) as e:
104
+ core.register(
105
+ 'R_A', {'_default': 'b'},
106
+ strict=True)
107
+
108
+ core.register('R_A', {'_default': 'b'}, strict=False)
109
+
110
+ assert core.access('R_A')['_default'] == 'b'
111
+
112
+
113
+ def test_merge_schemas(core):
114
+ a = {'foo': {'bar': [1],
115
+ 'baz': [2]}}
116
+ b = {'foo': {'bar': 3},
117
+ 'baz': 12}
118
+
119
+ assert core.merge_schemas(a, b) == \
120
+ {'foo': {'bar': 3,
121
+ 'baz': [2]},
122
+ 'baz': 12}
123
+
124
+
125
+ def test_generate_default(core):
126
+ int_default = core.default(
127
+ {'_type': 'integer'}
128
+ )
129
+
130
+ assert int_default == 0
131
+
132
+ cube_default = core.default(
133
+ {'_type': 'cube'})
134
+
135
+ assert 'width' in cube_default
136
+ assert 'height' in cube_default
137
+ assert 'depth' in cube_default
138
+
139
+ nested_default = core.default(
140
+ {'a': 'integer',
141
+ 'b': {
142
+ 'c': 'float',
143
+ 'd': 'cube'},
144
+ 'e': 'string'})
145
+
146
+ assert nested_default['b']['d']['width'] == 0
147
+
148
+
149
+ def test_apply_update(core):
150
+ schema = {'_type': 'cube'}
151
+ state = {
152
+ 'width': 11,
153
+ 'height': 13,
154
+ 'depth': 44,
155
+ }
156
+
157
+ update = {
158
+ 'depth': -5
159
+ }
160
+
161
+ new_state = core.apply(
162
+ schema,
163
+ state,
164
+ update
165
+ )
166
+
167
+ assert new_state['width'] == 11
168
+ assert new_state['depth'] == 39
169
+
170
+
171
+ def check_validation(core, library, print_log=False):
172
+ """
173
+ validate_schema returns nothing for a valid schema declaration
174
+
175
+ this helper function makes clearer reports out of the test
176
+ failures
177
+ """
178
+ for key, declaration in library.items():
179
+ errors = core.validate_schema(declaration)
180
+ assert len(errors) == 0, {'key': key, 'declaration': declaration}
181
+ if print_log:
182
+ print(f'PASS: valid schema {key}')
183
+ pprint.pprint(declaration)
184
+
185
+
186
+ def check_nonvalidation(core, library, print_log=False):
187
+ """
188
+ like check_validation but expects one or more errors
189
+ """
190
+ for key, declaration in library.items():
191
+ errors = core.validate_schema(declaration)
192
+ assert len(errors) != 0, {'key': key, 'declaration': declaration}
193
+ if print_log:
194
+ print(f'PASS: invalid schema {key}')
195
+ pprint.pprint(declaration)
196
+
197
+
198
+ def test_validate_schema(core):
199
+ # good schemas
200
+ check_validation(core, base_types)
201
+
202
+ good = {
203
+ 'not quite int': {
204
+ '_default': 0,
205
+ '_apply': accumulate,
206
+ '_serialize': to_string,
207
+ '_deserialize': deserialize_integer,
208
+ '_description': '64-bit integer'
209
+ },
210
+ 'ports match': {
211
+ 'a': {
212
+ '_type': 'integer',
213
+ '_value': 2
214
+ },
215
+ 'edge1': {
216
+ '_type': 'edge[a:integer]',
217
+ # '_type': 'edge',
218
+ # '_ports': {
219
+ # '1': {'_type': 'integer'},
220
+ # },
221
+ }
222
+ }
223
+ }
224
+ check_validation(core, good)
225
+
226
+ # bad schemas
227
+ bad = {
228
+ 'empty': None,
229
+ 'str?': 'not a schema',
230
+ 'branch is weird': {
231
+ 'left': {'_type': 'ogre'},
232
+ 'right': {'_default': 1, '_apply': accumulate},
233
+ },
234
+ }
235
+ check_nonvalidation(core, bad)
236
+ # test for ports and wires mismatch
237
+
238
+
239
+
240
+ def test_fill_integer(core):
241
+ test_schema = {
242
+ '_type': 'integer'
243
+ }
244
+
245
+ full_state = core.fill(test_schema)
246
+ direct_state = core.fill('integer')
247
+ generated_schema, generated_state = core.generate(
248
+ test_schema, None)
249
+
250
+ assert generated_schema['_type'] == 'integer'
251
+ assert full_state == direct_state == 0 == generated_state
252
+
253
+
254
+ def test_fill_cube(core):
255
+ test_schema = {'_type': 'cube'}
256
+ partial_state = {'height': 5}
257
+
258
+ full_state = core.fill(
259
+ test_schema,
260
+ state=partial_state)
261
+
262
+ assert 'width' in full_state
263
+ assert 'height' in full_state
264
+ assert 'depth' in full_state
265
+ assert full_state['height'] == 5
266
+ assert full_state['depth'] == 0
267
+
268
+
269
+ def test_fill_in_missing_nodes(core):
270
+ test_schema = {
271
+ 'edge 1': {
272
+ '_type': 'edge',
273
+ '_inputs': {
274
+ 'I': 'float'},
275
+ '_outputs': {
276
+ 'O': 'float'}}}
277
+
278
+ test_state = {
279
+ 'edge 1': {
280
+ 'inputs': {
281
+ 'I': ['a']},
282
+ 'outputs': {
283
+ 'O': ['a']}}}
284
+
285
+ filled = core.fill(
286
+ test_schema,
287
+ test_state)
288
+
289
+ assert filled == {
290
+ 'a': 0.0,
291
+ 'edge 1': {
292
+ 'inputs': {
293
+ 'I': ['a']},
294
+ 'outputs': {
295
+ 'O': ['a']}}}
296
+
297
+
298
+ def test_overwrite_existing(core):
299
+ test_schema = {
300
+ 'edge 1': {
301
+ '_type': 'edge',
302
+ '_inputs': {
303
+ 'I': 'float'},
304
+ '_outputs': {
305
+ 'O': 'float'}}}
306
+
307
+ test_state = {
308
+ 'a': 11.111,
309
+ 'edge 1': {
310
+ 'inputs': {
311
+ 'I': ['a']},
312
+ 'outputs': {
313
+ 'O': ['a']}}}
314
+
315
+ filled = core.fill(
316
+ test_schema,
317
+ test_state)
318
+
319
+ assert filled == {
320
+ 'a': 11.111,
321
+ 'edge 1': {
322
+ 'inputs': {
323
+ 'I': ['a']},
324
+ 'outputs': {
325
+ 'O': ['a']}}}
326
+
327
+
328
+ def test_fill_from_parse(core):
329
+ test_schema = {
330
+ 'edge 1': 'edge[I:float,O:float]'}
331
+
332
+ test_state = {
333
+ 'edge 1': {
334
+ 'inputs': {
335
+ 'I': ['a']},
336
+ 'outputs': {
337
+ 'O': ['a']}}}
338
+
339
+ filled = core.fill(
340
+ test_schema,
341
+ test_state)
342
+
343
+ assert filled == {
344
+ 'a': 0.0,
345
+ 'edge 1': {
346
+ 'inputs': {
347
+ 'I': ['a']},
348
+ 'outputs': {
349
+ 'O': ['a']}}}
350
+
351
+
352
+ def test_fill_in_disconnected_port(core):
353
+ test_schema = {
354
+ 'edge1': {
355
+ '_type': 'edge',
356
+ '_ports': {
357
+ '1': {'_type': 'float'}}}}
358
+
359
+ test_state = {
360
+ # TODO - js - how do I represent non connection, rather
361
+ # than a connection with an empty value?
362
+ 'edge1': {}}
363
+
364
+ filled = core.fill(
365
+ test_schema,
366
+ test_state)
367
+
368
+
369
+ # def test_fill_type_mismatch(core):
370
+ # test_schema = {
371
+ # 'a': {'_type': 'integer', '_value': 2},
372
+ # 'edge1': {
373
+ # '_type': 'edge',
374
+ # '_ports': {
375
+ # '1': {'_type': 'float'},
376
+ # '2': {'_type': 'float'}},
377
+ # 'wires': {
378
+ # '1': ['..', 'a'],
379
+ # '2': ['a']},
380
+ # 'a': 5}}
381
+ #
382
+ # test_state = {
383
+ # 'edge1': {},
384
+ # 'a': 2}
385
+ #
386
+ # filled = core.fill(
387
+ # test_schema,
388
+ # test_state)
389
+
390
+
391
+ # def test_edge_type_mismatch(core):
392
+ # test_schema = {
393
+ # 'edge1': {
394
+ # '_type': 'edge',
395
+ # '_ports': {
396
+ # '1': {'_type': 'float'}},
397
+ # 'wires': {
398
+ # '1': ['..', 'a']}},
399
+ # 'edge2': {
400
+ # '_type': 'edge',
401
+ # '_ports': {
402
+ # '1': {'_type': 'integer'}},
403
+ # 'wires': {
404
+ # '1': ['..', 'a']}}}
405
+
406
+
407
+ def test_establish_path(core):
408
+ tree = {}
409
+ destination = establish_path(
410
+ tree,
411
+ ('some',
412
+ 'where',
413
+ 'deep',
414
+ 'inside',
415
+ 'lives',
416
+ 'a',
417
+ 'tiny',
418
+ 'creature',
419
+ 'made',
420
+ 'of',
421
+ 'light'))
422
+
423
+ assert tree['some']['where']['deep']['inside']['lives']['a']['tiny']['creature']['made']['of'][
424
+ 'light'] == destination
425
+
426
+
427
+ def test_fill_ports(core):
428
+ cell_state = {
429
+ 'cell1': {
430
+ 'nucleus': {
431
+ 'transcription': {
432
+ '_type': 'edge',
433
+ 'inputs': {'DNA': ['chromosome']},
434
+ 'outputs': {
435
+ 'RNA': ['..', 'cytoplasm']}}}}}
436
+
437
+ schema, state = core.complete(
438
+ {},
439
+ cell_state)
440
+
441
+ assert 'chromosome' in schema['cell1']['nucleus']
442
+
443
+
444
+ def test_expected_schema(core):
445
+ # equivalent to previous schema:
446
+
447
+ # expected = {
448
+ # 'store1': {
449
+ # 'store1.1': {
450
+ # '_value': 1.1,
451
+ # '_type': 'float',
452
+ # },
453
+ # 'store1.2': {
454
+ # '_value': 2,
455
+ # '_type': 'integer',
456
+ # },
457
+ # 'process1': {
458
+ # '_ports': {
459
+ # 'port1': {'_type': 'type'},
460
+ # 'port2': {'_type': 'type'},
461
+ # },
462
+ # '_wires': {
463
+ # 'port1': 'store1.1',
464
+ # 'port2': 'store1.2',
465
+ # }
466
+ # },
467
+ # 'process2': {
468
+ # '_ports': {
469
+ # 'port1': {'_type': 'type'},
470
+ # 'port2': {'_type': 'type'},
471
+ # },
472
+ # '_wires': {
473
+ # 'port1': 'store1.1',
474
+ # 'port2': 'store1.2',
475
+ # }
476
+ # },
477
+ # },
478
+ # 'process3': {
479
+ # '_wires': {
480
+ # 'port1': 'store1',
481
+ # }
482
+ # }
483
+ # }
484
+
485
+ dual_process_schema = {
486
+ 'process1': 'edge[input1:float|input2:integer,output1:float|output2:integer]',
487
+ 'process2': {
488
+ '_type': 'edge',
489
+ '_inputs': {
490
+ 'input1': 'float',
491
+ 'input2': 'integer'},
492
+ '_outputs': {
493
+ 'output1': 'float',
494
+ 'output2': 'integer'}}}
495
+
496
+ core.register(
497
+ 'dual_process',
498
+ dual_process_schema,
499
+ )
500
+
501
+ test_schema = {
502
+ # 'store1': 'process1.edge[port1.float|port2.int]|process2[port1.float|port2.int]',
503
+ 'store1': 'dual_process',
504
+ 'process3': 'edge[input_process:dual_process,output_process:dual_process]'}
505
+
506
+ test_state = {
507
+ 'store1': {
508
+ 'process1': {
509
+ 'inputs': {
510
+ 'input1': ['store1.1'],
511
+ 'input2': ['store1.2']},
512
+ 'outputs': {
513
+ 'output1': ['store2.1'],
514
+ 'output2': ['store2.2']}},
515
+ 'process2': {
516
+ 'inputs': {
517
+ 'input1': ['store2.1'],
518
+ 'input2': ['store2.2']},
519
+ 'outputs': {
520
+ 'output1': ['store1.1'],
521
+ 'output2': ['store1.2']}}},
522
+ 'process3': {
523
+ 'inputs': {
524
+ 'input_process': ['store1']},
525
+ 'outputs': {
526
+ 'output_process': ['store1']}}}
527
+
528
+ outcome = core.fill(test_schema, test_state)
529
+
530
+ assert outcome == {
531
+ 'process3': {
532
+ 'inputs': {
533
+ 'input_process': ['store1']},
534
+ 'outputs': {
535
+ 'output_process': ['store1']}},
536
+ 'store1': {
537
+ 'process1': {
538
+ 'inputs': {
539
+ 'input1': ['store1.1'],
540
+ 'input2': ['store1.2']},
541
+ 'outputs': {
542
+ 'output1': ['store2.1'],
543
+ 'output2': ['store2.2']}},
544
+ 'process2': {
545
+ 'inputs': {'input1': ['store2.1'],
546
+ 'input2': ['store2.2']},
547
+ 'outputs': {'output1': ['store1.1'],
548
+ 'output2': ['store1.2']}},
549
+ 'store1.1': 0.0,
550
+ 'store1.2': 0,
551
+ 'store2.1': 0.0,
552
+ 'store2.2': 0}}
553
+
554
+
555
+ def test_link_place(core):
556
+ # TODO: this form is more fundamental than the compressed/inline dict form,
557
+ # and we should probably derive that from this form
558
+
559
+ bigraph = {
560
+ 'nodes': {
561
+ 'v0': 'integer',
562
+ 'v1': 'integer',
563
+ 'v2': 'integer',
564
+ 'v3': 'integer',
565
+ 'v4': 'integer',
566
+ 'v5': 'integer',
567
+ 'e0': 'edge[e0-0:int|e0-1:int|e0-2:int]',
568
+ 'e1': {
569
+ '_type': 'edge',
570
+ '_ports': {
571
+ 'e1-0': 'integer',
572
+ 'e2-0': 'integer'}},
573
+ 'e2': {
574
+ '_type': 'edge[e2-0:int|e2-1:int|e2-2:int]'}},
575
+
576
+ 'place': {
577
+ 'v0': None,
578
+ 'v1': 'v0',
579
+ 'v2': 'v0',
580
+ 'v3': 'v2',
581
+ 'v4': None,
582
+ 'v5': 'v4',
583
+ 'e0': None,
584
+ 'e1': None,
585
+ 'e2': None},
586
+
587
+ 'link': {
588
+ 'e0': {
589
+ 'e0-0': 'v0',
590
+ 'e0-1': 'v1',
591
+ 'e0-2': 'v4'},
592
+ 'e1': {
593
+ 'e1-0': 'v3',
594
+ 'e1-1': 'v1'},
595
+ 'e2': {
596
+ 'e2-0': 'v3',
597
+ 'e2-1': 'v4',
598
+ 'e2-2': 'v5'}},
599
+
600
+ 'state': {
601
+ 'v0': '1',
602
+ 'v1': '1',
603
+ 'v2': '2',
604
+ 'v3': '3',
605
+ 'v4': '5',
606
+ 'v5': '8',
607
+ 'e0': {
608
+ 'wires': {
609
+ 'e0-0': 'v0',
610
+ 'e0-1': 'v1',
611
+ 'e0-2': 'v4'}},
612
+ 'e1': {
613
+ 'wires': {
614
+ 'e1-0': 'v3',
615
+ 'e1-1': 'v1'}},
616
+ 'e2': {
617
+ 'e2-0': 'v3',
618
+ 'e2-1': 'v4',
619
+ 'e2-2': 'v5'}}}
620
+
621
+ placegraph = { # schema
622
+ 'v0': {
623
+ 'v1': int,
624
+ 'v2': {
625
+ 'v3': int}},
626
+ 'v4': {
627
+ 'v5': int},
628
+ 'e0': 'edge',
629
+ 'e1': 'edge',
630
+ 'e2': 'edge'}
631
+
632
+ hypergraph = { # edges
633
+ 'e0': {
634
+ 'e0-0': 'v0',
635
+ 'e0-1': 'v1',
636
+ 'e0-2': 'v4'},
637
+ 'e1': {
638
+ 'e1-0': 'v3',
639
+ 'e1-1': 'v1'},
640
+ 'e2': {
641
+ 'e2-0': 'v3',
642
+ 'e2-1': 'v4',
643
+ 'e2-2': 'v5'}}
644
+
645
+ merged = {
646
+ 'v0': {
647
+ 'v1': {},
648
+ 'v2': {
649
+ 'v3': {}}},
650
+ 'v4': {
651
+ 'v5': {}},
652
+ 'e0': {
653
+ 'wires': {
654
+ 'e0.0': ['v0'],
655
+ 'e0.1': ['v0', 'v1'],
656
+ 'e0.2': ['v4']}},
657
+ 'e1': {
658
+ 'wires': {
659
+ 'e0.0': ['v0', 'v2', 'v3'],
660
+ 'e0.1': ['v0', 'v1']}},
661
+ 'e2': {
662
+ 'wires': {
663
+ 'e0.0': ['v0', 'v2', 'v3'],
664
+ 'e0.1': ['v4'],
665
+ 'e0.2': ['v4', 'v5']}}}
666
+
667
+ result = core.link_place(placegraph, hypergraph)
668
+ # assert result == merged
669
+
670
+
671
+ def test_units(core):
672
+ schema_length = {
673
+ 'distance': {
674
+ '_type': 'length'}}
675
+
676
+ state = {'distance': 11 * units.meter}
677
+ update = {'distance': -5 * units.feet}
678
+
679
+ new_state = core.apply(
680
+ schema_length,
681
+ state,
682
+ update
683
+ )
684
+
685
+ assert new_state['distance'] == 9.476 * units.meter
686
+
687
+
688
+ def test_unit_conversion(core):
689
+ # mass * length ^ 2 / second ^ 2
690
+
691
+ units_schema = {
692
+ 'force': 'length^2*mass/time^2'}
693
+
694
+ force_units = units.meter ** 2 * units.kg / units.second ** 2
695
+
696
+ instance = {
697
+ 'force': 3.333 * force_units}
698
+
699
+
700
+ def test_serialize_deserialize(core):
701
+ schema = {
702
+ 'edge1': {
703
+ # '_type': 'edge[1:int|2:float|3:string|4:tree[int]]',
704
+ '_type': 'edge',
705
+ '_outputs': {
706
+ '1': 'integer',
707
+ '2': 'float',
708
+ '3': 'string',
709
+ '4': 'tree[integer]'}},
710
+ 'a0': {
711
+ 'a0.0': 'integer',
712
+ 'a0.1': 'float',
713
+ 'a0.2': {
714
+ 'a0.2.0': 'string'}},
715
+ 'a1': 'tree[integer]'}
716
+
717
+ instance = {
718
+ 'edge1': {
719
+ 'outputs': {
720
+ '1': ['a0', 'a0.0'],
721
+ '2': ['a0', 'a0.1'],
722
+ '3': ['a0', 'a0.2', 'a0.2.0'],
723
+ '4': ['a1']}},
724
+ 'a1': {
725
+ 'branch1': {
726
+ 'branch2': 11,
727
+ 'branch3': 22},
728
+ 'branch4': 44}}
729
+
730
+ instance = core.fill(schema, instance)
731
+
732
+ encoded = core.serialize(schema, instance)
733
+ decoded = core.deserialize(schema, encoded)
734
+
735
+ assert instance == decoded
736
+
737
+
738
+ # is this a lens?
739
+ def test_project(core):
740
+ schema = {
741
+ 'edge1': {
742
+ # '_type': 'edge[1:int|2:float|3:string|4:tree[int]]',
743
+ # '_type': 'edge',
744
+ '_type': 'edge',
745
+ '_inputs': {
746
+ '1': 'integer',
747
+ '2': 'float',
748
+ '3': 'string',
749
+ 'inner': {
750
+ 'chamber': 'tree[integer]'},
751
+ '4': 'tree[integer]'},
752
+ '_outputs': {
753
+ '1': 'integer',
754
+ '2': 'float',
755
+ '3': 'string',
756
+ 'inner': {
757
+ 'chamber': 'tree[integer]'},
758
+ '4': 'tree[integer]'}},
759
+ 'a0': {
760
+ 'a0.0': 'integer',
761
+ 'a0.1': 'float',
762
+ 'a0.2': {
763
+ 'a0.2.0': 'string'}},
764
+ 'a1': {
765
+ '_type': 'tree[integer]'}}
766
+
767
+ path_format = {
768
+ '1': 'a0>a0.0',
769
+ '2': 'a0>a0.1',
770
+ '3': 'a0>a0.2>a0.2.0'}
771
+
772
+ # TODO: support separate schema/instance, and
773
+ # instances with '_type' and type parameter keys
774
+ # TODO: support overriding various type methods
775
+ instance = {
776
+ 'a0': {
777
+ 'a0.0': 11},
778
+ 'edge1': {
779
+ 'inputs': {
780
+ '1': ['a0', 'a0.0'],
781
+ '2': ['a0', 'a0.1'],
782
+ '3': ['a0', 'a0.2', 'a0.2.0'],
783
+ 'inner': {
784
+ 'chamber': ['a1', 'a1.0']},
785
+ '4': ['a1']},
786
+ 'outputs': {
787
+ '1': ['a0', 'a0.0'],
788
+ '2': ['a0', 'a0.1'],
789
+ '3': ['a0', 'a0.2', 'a0.2.0'],
790
+ 'inner': {
791
+ 'chamber': {
792
+ 'X': ['a1', 'a1.0', 'Y']}},
793
+ '4': ['a1']}},
794
+ 'a1': {
795
+ 'a1.0': {
796
+ 'X': 555},
797
+ 'branch1': {
798
+ 'branch2': 11,
799
+ 'branch3': 22},
800
+ 'branch4': 44}}
801
+
802
+ instance = core.fill(schema, instance)
803
+
804
+ states = core.view_edge(
805
+ schema,
806
+ instance,
807
+ ['edge1'])
808
+
809
+ update = core.project_edge(
810
+ schema,
811
+ instance,
812
+ ['edge1'],
813
+ states)
814
+
815
+ assert update == {
816
+ 'a0': {
817
+ 'a0.0': 11,
818
+ 'a0.1': 0.0,
819
+ 'a0.2': {
820
+ 'a0.2.0': ''}},
821
+ 'a1': {
822
+ 'a1.0': {
823
+ 'X': 555,
824
+ 'Y': {}},
825
+ 'branch1': {
826
+ 'branch2': 11,
827
+ 'branch3': 22},
828
+ 'branch4': 44}}
829
+
830
+ # TODO: make sure apply does not mutate instance
831
+ updated_instance = core.apply(
832
+ schema,
833
+ instance,
834
+ update)
835
+
836
+ add_update = {
837
+ '4': {
838
+ 'branch6': 111,
839
+ 'branch1': {
840
+ '_add': {
841
+ 'branch7': 4444,
842
+ 'branch8': 555,
843
+ },
844
+ '_remove': ['branch2']},
845
+ '_add': {
846
+ 'branch5': 55},
847
+ '_remove': ['branch4']}}
848
+
849
+ inverted_update = core.project_edge(
850
+ schema,
851
+ updated_instance,
852
+ ['edge1'],
853
+ add_update)
854
+
855
+ modified_branch = core.apply(
856
+ schema,
857
+ updated_instance,
858
+ inverted_update)
859
+
860
+ assert modified_branch == {
861
+ 'a0': {
862
+ 'a0.0': 22,
863
+ 'a0.1': 0.0,
864
+ 'a0.2': {
865
+ 'a0.2.0': ''}},
866
+ 'edge1': {'inputs': {'1': ['a0', 'a0.0'],
867
+ '2': ['a0', 'a0.1'],
868
+ '3': ['a0', 'a0.2', 'a0.2.0'],
869
+ 'inner': {
870
+ 'chamber': ['a1', 'a1.0']},
871
+ '4': ['a1']},
872
+ 'outputs': {'1': ['a0', 'a0.0'],
873
+ '2': ['a0', 'a0.1'],
874
+ '3': ['a0', 'a0.2', 'a0.2.0'],
875
+ 'inner': {
876
+ 'chamber': {
877
+ 'X': ['a1', 'a1.0', 'Y']}},
878
+ '4': ['a1']}},
879
+ 'a1': {
880
+ 'a1.0': {
881
+ 'X': 1110,
882
+ 'Y': {}},
883
+ 'branch1': {
884
+ 'branch3': 44,
885
+ 'branch7': 4444,
886
+ 'branch8': 555, },
887
+ 'branch6': 111,
888
+ 'branch5': 55}}
889
+
890
+
891
+ def test_check(core):
892
+ assert core.check('float', 1.11)
893
+ assert core.check({'b': 'float'}, {'b': 1.11})
894
+
895
+
896
+ def test_inherits_from(core):
897
+ assert core.inherits_from(
898
+ 'float',
899
+ 'number')
900
+
901
+ assert core.inherits_from(
902
+ 'tree[float]',
903
+ 'tree[number]')
904
+
905
+ assert core.inherits_from(
906
+ 'tree[path]',
907
+ 'tree[list[mark]]')
908
+
909
+ assert not core.inherits_from(
910
+ 'tree[path]',
911
+ 'tree[list[number]]')
912
+
913
+ assert not core.inherits_from(
914
+ 'tree[float]',
915
+ 'tree[string]')
916
+
917
+ assert not core.inherits_from(
918
+ 'tree[float]',
919
+ 'list[float]')
920
+
921
+ assert core.inherits_from({
922
+ 'a': 'float',
923
+ 'b': 'schema'}, {
924
+
925
+ 'a': 'number',
926
+ 'b': 'tree'})
927
+
928
+ assert not core.inherits_from({
929
+ 'a': 'float',
930
+ 'b': 'schema'}, {
931
+
932
+ 'a': 'number',
933
+ 'b': 'number'})
934
+
935
+
936
+ def test_resolve_schemas(core):
937
+ resolved = core.resolve_schemas({
938
+ 'a': 'float',
939
+ 'b': 'map[list[string]]'}, {
940
+ 'a': 'number',
941
+ 'b': 'map[path]',
942
+ 'c': 'string'})
943
+
944
+ assert resolved['a']['_type'] == 'float'
945
+ assert resolved['b']['_value']['_type'] == 'path'
946
+ assert resolved['c']['_type'] == 'string'
947
+
948
+ raises_on_incompatible_schemas = False
949
+ try:
950
+ core.resolve_schemas({
951
+ 'a': 'string',
952
+ 'b': 'map[list[string]]'}, {
953
+ 'a': 'number',
954
+ 'b': 'map[path]',
955
+ 'c': 'string'})
956
+ except:
957
+ raises_on_incompatible_schemas = True
958
+
959
+ assert raises_on_incompatible_schemas
960
+
961
+
962
+ def test_apply_schema(core):
963
+ current = {
964
+ 'a': 'number',
965
+ 'b': 'map[path]',
966
+ 'd': ('float', 'number', 'list[string]')}
967
+
968
+ update = {
969
+ 'a': 'float',
970
+ 'b': 'map[list[string]]',
971
+ 'c': 'string',
972
+ 'd': ('number', 'float', 'path')}
973
+
974
+ applied = core.apply(
975
+ 'schema',
976
+ current,
977
+ update)
978
+
979
+ assert applied['a']['_type'] == 'float'
980
+ assert applied['b']['_value']['_type'] == 'path'
981
+ assert applied['c']['_type'] == 'string'
982
+ assert applied['d']['_0']['_type'] == 'float'
983
+ assert applied['d']['_1']['_type'] == 'float'
984
+ assert applied['d']['_2']['_type'] == 'path'
985
+
986
+
987
+ def apply_foursquare(schema, current, update, top_schema, top_state, path, core):
988
+ if isinstance(current, bool) or isinstance(update, bool):
989
+ return update
990
+ else:
991
+ for key, value in update.items():
992
+ current[key] = apply_foursquare(
993
+ schema,
994
+ current[key],
995
+ value,
996
+ top_schema=top_schema,
997
+ top_state=top_state,
998
+ path=path,
999
+ core=core)
1000
+
1001
+ return current
1002
+
1003
+
1004
+ def test_foursquare(core):
1005
+ foursquare_schema = {
1006
+ '_apply': apply_foursquare,
1007
+ '00': 'boolean~foursquare',
1008
+ '01': 'boolean~foursquare',
1009
+ '10': 'boolean~foursquare',
1010
+ '11': 'boolean~foursquare'}
1011
+
1012
+ core.register(
1013
+ 'foursquare',
1014
+ foursquare_schema)
1015
+
1016
+ example = {
1017
+ '00': True,
1018
+ '01': False,
1019
+ '10': False,
1020
+ '11': {
1021
+ '00': True,
1022
+ '01': False,
1023
+ '10': False,
1024
+ '11': {
1025
+ '00': True,
1026
+ '01': False,
1027
+ '10': False,
1028
+ '11': {
1029
+ '00': True,
1030
+ '01': False,
1031
+ '10': False,
1032
+ '11': {
1033
+ '00': True,
1034
+ '01': False,
1035
+ '10': False,
1036
+ '11': {
1037
+ '00': True,
1038
+ '01': False,
1039
+ '10': False,
1040
+ '11': False}}}}}}
1041
+
1042
+ assert core.check(
1043
+ 'foursquare',
1044
+ example)
1045
+
1046
+ example['10'] = 5
1047
+
1048
+ assert not core.check(
1049
+ 'foursquare',
1050
+ example)
1051
+
1052
+ update = {
1053
+ '01': True,
1054
+ '11': {
1055
+ '01': True,
1056
+ '11': {
1057
+ '11': True,
1058
+ '10': {
1059
+ '10': {
1060
+ '00': True,
1061
+ '11': False}}}}}
1062
+
1063
+ result = core.apply(
1064
+ 'foursquare',
1065
+ example,
1066
+ update)
1067
+
1068
+ assert result == {
1069
+ '00': True,
1070
+ '01': True,
1071
+ '10': 5,
1072
+ '11': {'00': True,
1073
+ '01': True,
1074
+ '10': False,
1075
+ '11': {'00': True,
1076
+ '01': False,
1077
+ '10': {
1078
+ '10': {
1079
+ '00': True,
1080
+ '11': False}},
1081
+ '11': True}}}
1082
+
1083
+
1084
+ def test_add_reaction(core):
1085
+ single_node = {
1086
+ 'environment': {
1087
+ '_type': 'compartment',
1088
+ 'counts': {'A': 144},
1089
+ 'inner': {
1090
+ '0': {
1091
+ 'counts': {'A': 13},
1092
+ 'inner': {}}}}}
1093
+
1094
+ add_config = {
1095
+ 'path': ['environment', 'inner'],
1096
+ 'add': {
1097
+ '1': {
1098
+ 'counts': {
1099
+ 'A': 8}}}}
1100
+
1101
+ schema, state = core.infer_schema(
1102
+ {},
1103
+ single_node)
1104
+
1105
+ assert '0' in state['environment']['inner']
1106
+ assert '1' not in state['environment']['inner']
1107
+
1108
+ result = core.apply(
1109
+ schema,
1110
+ state, {
1111
+ '_react': {
1112
+ 'add': add_config}})
1113
+
1114
+ # '_react': {
1115
+ # 'reaction': 'add',
1116
+ # 'config': add_config}})
1117
+
1118
+ assert '0' in result['environment']['inner']
1119
+ assert '1' in result['environment']['inner']
1120
+
1121
+
1122
+ def test_remove_reaction(core):
1123
+ single_node = {
1124
+ 'environment': {
1125
+ '_type': 'compartment',
1126
+ 'counts': {'A': 144},
1127
+ 'inner': {
1128
+ '0': {
1129
+ 'counts': {'A': 13},
1130
+ 'inner': {}},
1131
+ '1': {
1132
+ 'counts': {'A': 13},
1133
+ 'inner': {}}}}}
1134
+
1135
+ remove_config = {
1136
+ 'path': ['environment', 'inner'],
1137
+ 'remove': ['0']}
1138
+
1139
+ schema, state = core.infer_schema(
1140
+ {},
1141
+ single_node)
1142
+
1143
+ assert '0' in state['environment']['inner']
1144
+ assert '1' in state['environment']['inner']
1145
+
1146
+ result = core.apply(
1147
+ schema,
1148
+ state, {
1149
+ '_react': {
1150
+ 'remove': remove_config}})
1151
+
1152
+ assert '0' not in result['environment']['inner']
1153
+ assert '1' in state['environment']['inner']
1154
+
1155
+
1156
+ def test_replace_reaction(core):
1157
+ single_node = {
1158
+ 'environment': {
1159
+ '_type': 'compartment',
1160
+ 'counts': {'A': 144},
1161
+ 'inner': {
1162
+ '0': {
1163
+ 'counts': {'A': 13},
1164
+ 'inner': {}},
1165
+ '1': {
1166
+ 'counts': {'A': 13},
1167
+ 'inner': {}}}}}
1168
+
1169
+ # replace_config = {
1170
+ # 'path': ['environment', 'inner'],
1171
+ # 'before': {'0': {'A': '?1'}},
1172
+ # 'after': {
1173
+ # '2': {
1174
+ # 'counts': {
1175
+ # 'A': {'function': 'divide', 'arguments': ['?1', 0.5], }}},
1176
+ # '3': {
1177
+ # 'counts': {
1178
+ # 'A': '@1'}}}}
1179
+
1180
+ replace_config = {
1181
+ 'path': ['environment', 'inner'],
1182
+ 'before': {'0': {}},
1183
+ 'after': {
1184
+ '2': {
1185
+ 'counts': {
1186
+ 'A': 3}},
1187
+ '3': {
1188
+ 'counts': {
1189
+ 'A': 88}}}}
1190
+
1191
+ schema, state = core.infer_schema(
1192
+ {},
1193
+ single_node)
1194
+
1195
+ assert '0' in state['environment']['inner']
1196
+ assert '1' in state['environment']['inner']
1197
+
1198
+ result = core.apply(
1199
+ schema,
1200
+ state, {
1201
+ '_react': {
1202
+ 'replace': replace_config}})
1203
+
1204
+ assert '0' not in result['environment']['inner']
1205
+ assert '1' in result['environment']['inner']
1206
+ assert '2' in result['environment']['inner']
1207
+ assert '3' in result['environment']['inner']
1208
+
1209
+
1210
+ def test_reaction(core):
1211
+ single_node = {
1212
+ 'environment': {
1213
+ 'counts': {},
1214
+ 'inner': {
1215
+ '0': {
1216
+ 'counts': {}}}}}
1217
+
1218
+ # TODO: compartment type ends up as 'any' at leafs?
1219
+
1220
+ # TODO: come at divide reaction from the other side:
1221
+ # ie make a call for it, then figure out what the
1222
+ # reaction needs to be
1223
+ def divide_reaction(container, mother, divider):
1224
+ daughters = divider(mother)
1225
+
1226
+ return {
1227
+ 'redex': mother,
1228
+ 'reactum': daughters}
1229
+
1230
+ embedded_tree = {
1231
+ 'environment': {
1232
+ '_type': 'compartment',
1233
+ 'counts': {},
1234
+ 'inner': {
1235
+ 'agent1': {
1236
+ '_type': 'compartment',
1237
+ 'counts': {},
1238
+ 'inner': {
1239
+ 'agent2': {
1240
+ '_type': 'compartment',
1241
+ 'counts': {},
1242
+ 'inner': {},
1243
+ 'transport': {
1244
+ 'wires': {
1245
+ 'outer': ['..', '..'],
1246
+ 'inner': ['inner']}}}},
1247
+ 'transport': {
1248
+ 'wires': {
1249
+ 'outer': ['..', '..'],
1250
+ 'inner': ['inner']}}}}}}
1251
+
1252
+ mother_tree = {
1253
+ 'environment': {
1254
+ '_type': 'compartment',
1255
+ 'counts': {
1256
+ 'A': 15},
1257
+ 'inner': {
1258
+ 'mother': {
1259
+ '_type': 'compartment',
1260
+ 'counts': {
1261
+ 'A': 5}}}}}
1262
+
1263
+ divide_react = {
1264
+ '_react': {
1265
+ 'redex': {
1266
+ 'mother': {
1267
+ 'counts': '@counts'}},
1268
+ 'reactum': {
1269
+ 'daughter1': {
1270
+ 'counts': '@daughter1_counts'},
1271
+ 'daughter2': {
1272
+ 'counts': '@daughter2_counts'}},
1273
+ 'calls': [{
1274
+ 'function': 'divide_counts',
1275
+ 'arguments': ['@counts', [0.5, 0.5]],
1276
+ 'bindings': ['@daughter1_counts', '@daughter2_counts']}]}}
1277
+
1278
+ divide_update = {
1279
+ '_react': {
1280
+ 'reaction': 'divide_counts',
1281
+ 'config': {
1282
+ 'id': 'mother',
1283
+ 'state_key': 'counts',
1284
+ 'daughters': [
1285
+ {'id': 'daughter1', 'ratio': 0.3},
1286
+ {'id': 'daughter2', 'ratio': 0.7}]}}}
1287
+
1288
+ divide_update_concise = {
1289
+ '_react': {
1290
+ 'divide_counts': {
1291
+ 'id': 'mother',
1292
+ 'state_key': 'counts',
1293
+ 'daughters': [
1294
+ {'id': 'daughter1', 'ratio': 0.3},
1295
+ {'id': 'daughter2', 'ratio': 0.7}]}}}
1296
+
1297
+
1298
+ def A(a):
1299
+ return a * 5
1300
+
1301
+ def B(b):
1302
+ return b + 11
1303
+
1304
+ def test_function_type(core):
1305
+ A_serialized = core.serialize(
1306
+ 'function',
1307
+ A)
1308
+
1309
+ A_deserialized = core.deserialize(
1310
+ 'function',
1311
+ A_serialized)
1312
+
1313
+ C = core.apply(
1314
+ 'function',
1315
+ A_deserialized,
1316
+ B)
1317
+
1318
+ assert C(6) == 41
1319
+
1320
+
1321
+ def test_map_type(core):
1322
+ schema = 'map[integer]'
1323
+
1324
+ state = {
1325
+ 'a': 12,
1326
+ 'b': 13,
1327
+ 'c': 15,
1328
+ 'd': 18}
1329
+
1330
+ update = {
1331
+ 'b': 44,
1332
+ 'd': 111}
1333
+
1334
+ assert core.check(schema, state)
1335
+ assert core.check(schema, update)
1336
+ assert not core.check(schema, 15)
1337
+
1338
+ result = core.apply(
1339
+ schema,
1340
+ state,
1341
+ update)
1342
+
1343
+ assert result['a'] == 12
1344
+ assert result['b'] == 57
1345
+ assert result['d'] == 129
1346
+
1347
+ encode = core.serialize(schema, update)
1348
+ assert encode['d'] == '111'
1349
+
1350
+ decode = core.deserialize(schema, encode)
1351
+ assert decode == update
1352
+
1353
+
1354
+ def test_tree_type(core):
1355
+ schema = 'tree[maybe[integer]]'
1356
+
1357
+ state = {
1358
+ 'a': 12,
1359
+ 'b': 13,
1360
+ 'c': {
1361
+ 'e': 5555,
1362
+ 'f': 111},
1363
+ 'd': None}
1364
+
1365
+ update = {
1366
+ 'a': None,
1367
+ 'c': {
1368
+ 'e': 88888,
1369
+ 'f': 2222,
1370
+ 'G': None},
1371
+ 'd': 111}
1372
+
1373
+ assert core.check(schema, state)
1374
+ assert core.check(schema, update)
1375
+ assert core.check(schema, 15)
1376
+ assert core.check(schema, None)
1377
+ assert core.check(schema, {'c': {'D': None, 'e': 11111}})
1378
+ assert not core.check(schema, 'yellow')
1379
+ assert not core.check(schema, {'a': 5, 'b': 'green'})
1380
+ assert not core.check(schema, {'c': {'D': False, 'e': 11111}})
1381
+
1382
+ result = core.apply(
1383
+ schema,
1384
+ state,
1385
+ update)
1386
+
1387
+ assert result['a'] == None
1388
+ assert result['b'] == 13
1389
+ assert result['c']['f'] == 2333
1390
+ assert result['d'] == 111
1391
+
1392
+ encode = core.serialize(schema, update)
1393
+ assert encode['a'] == NONE_SYMBOL
1394
+ assert encode['d'] == '111'
1395
+
1396
+ decode = core.deserialize(schema, encode)
1397
+ assert decode == update
1398
+
1399
+
1400
+ def test_maybe_type(core):
1401
+ schema = 'map[maybe[integer]]'
1402
+
1403
+ state = {
1404
+ 'a': 12,
1405
+ 'b': 13,
1406
+ 'c': None,
1407
+ 'd': 18}
1408
+
1409
+ update = {
1410
+ 'a': None,
1411
+ 'c': 44,
1412
+ 'd': 111}
1413
+
1414
+ assert core.check(schema, state)
1415
+ assert core.check(schema, update)
1416
+ assert not core.check(schema, 15)
1417
+
1418
+ result = core.apply(
1419
+ schema,
1420
+ state,
1421
+ update)
1422
+
1423
+ assert result['a'] == None
1424
+ assert result['b'] == 13
1425
+ assert result['c'] == 44
1426
+ assert result['d'] == 129
1427
+
1428
+ encode = core.serialize(schema, update)
1429
+ assert encode['a'] == NONE_SYMBOL
1430
+ assert encode['d'] == '111'
1431
+
1432
+ decode = core.deserialize(schema, encode)
1433
+ assert decode == update
1434
+
1435
+
1436
+ def test_tuple_type(core):
1437
+ schemas = [{
1438
+ '_type': 'tuple',
1439
+ '_type_parameters': ['0', '1', '2'],
1440
+ '_0': 'string',
1441
+ '_1': 'integer',
1442
+ '_2': 'map[maybe[float]]'},
1443
+
1444
+ ('string', 'integer', 'map[maybe[float]]'),
1445
+ 'tuple[string,integer,map[maybe[float]]]',
1446
+ 'string|integer|map[maybe[float]]']
1447
+
1448
+ for schema in schemas:
1449
+ state = (
1450
+ 'aaaaa',
1451
+ 13, {
1452
+ 'a': 1.1,
1453
+ 'b': None})
1454
+
1455
+ update = (
1456
+ 'bbbbbb',
1457
+ 10, {
1458
+ 'a': 33.33,
1459
+ 'b': 4.44444})
1460
+
1461
+ assert core.check(schema, state)
1462
+ assert core.check(schema, update)
1463
+ assert not core.check(schema, 15)
1464
+
1465
+ result = core.apply(
1466
+ schema,
1467
+ state,
1468
+ update)
1469
+
1470
+ assert len(result) == 3
1471
+ assert result[0] == update[0]
1472
+ assert result[1] == 23
1473
+ assert result[2]['a'] == 34.43
1474
+ assert result[2]['b'] == update[2]['b']
1475
+
1476
+ encode = core.serialize(schema, state)
1477
+ assert encode[2]['b'] == NONE_SYMBOL
1478
+ assert encode[1] == '13'
1479
+
1480
+ decode = core.deserialize(schema, encode)
1481
+ assert decode == state
1482
+
1483
+ tuple_type = core.access('(3|4|10)')
1484
+ assert '_2' in tuple_type
1485
+ assert tuple_type['_2'] == '10'
1486
+
1487
+ tuple_type = core.access('tuple[9,float,7]')
1488
+ assert '_2' in tuple_type
1489
+ assert tuple_type['_2'] == '7'
1490
+
1491
+
1492
+ def test_union_type(core):
1493
+ schemas = [{
1494
+ '_type': 'union',
1495
+ '_type_parameters': ['0', '1', '2'],
1496
+ '_0': 'string',
1497
+ '_1': 'integer',
1498
+ '_2': 'map[maybe[float]]'},
1499
+
1500
+ 'string~integer~map[maybe[float]]']
1501
+
1502
+ for schema in schemas:
1503
+ state = {
1504
+ 'a': 1.1,
1505
+ 'b': None}
1506
+
1507
+ update = {
1508
+ 'a': 33.33,
1509
+ 'b': 4.44444}
1510
+
1511
+ assert core.check(schema, state)
1512
+ assert core.check(schema, update)
1513
+ assert core.check(schema, 15)
1514
+
1515
+ wrong_state = {
1516
+ 'a': 1.1,
1517
+ 'b': None}
1518
+
1519
+ wrong_update = 'a different type'
1520
+
1521
+ assert core.check(schema, wrong_state)
1522
+ assert core.check(schema, wrong_update)
1523
+
1524
+ # TODO: deal with union apply of different types
1525
+
1526
+ result = core.apply(
1527
+ schema,
1528
+ state,
1529
+ update)
1530
+
1531
+ assert result['a'] == 34.43
1532
+ assert result['b'] == update['b']
1533
+
1534
+ encode = core.serialize(schema, state)
1535
+ assert encode['b'] == NONE_SYMBOL
1536
+
1537
+ decode = core.deserialize(schema, encode)
1538
+ assert decode == state
1539
+
1540
+
1541
+ def test_union_values(core):
1542
+ schema = 'map[integer~string~map[maybe[float]]]'
1543
+
1544
+ state = {
1545
+ 'a': 'bbbbb',
1546
+ 'b': 15}
1547
+
1548
+ update = {
1549
+ 'a': 'aaaaa',
1550
+ 'b': 22}
1551
+
1552
+ assert core.check(schema, state)
1553
+ assert core.check(schema, update)
1554
+ assert not core.check(schema, 15)
1555
+
1556
+ result = core.apply(
1557
+ schema,
1558
+ state,
1559
+ update)
1560
+
1561
+ assert result['a'] == 'aaaaa'
1562
+ assert result['b'] == 37
1563
+
1564
+ encode = core.serialize(schema, state)
1565
+ decode = core.deserialize(schema, encode)
1566
+
1567
+ assert decode == state
1568
+
1569
+
1570
+ def test_array_type(core):
1571
+ shape = (3, 4, 10)
1572
+ shape_representation = core.representation(shape)
1573
+ shape_commas = ','.join([
1574
+ str(x)
1575
+ for x in shape])
1576
+
1577
+ schema = {
1578
+ '_type': 'map',
1579
+ '_value': {
1580
+ '_type': 'array',
1581
+ # '_shape': '(3|4|10)',
1582
+ '_shape': shape_representation,
1583
+ '_data': 'float'}}
1584
+
1585
+ schema = f'map[array[tuple[{shape_commas}],float]]'
1586
+ schema = f'map[array[{shape_representation},float]]'
1587
+
1588
+ state = {
1589
+ 'a': np.zeros(shape),
1590
+ 'b': np.ones(shape)}
1591
+
1592
+ update = {
1593
+ 'a': np.full(shape, 5.555),
1594
+ 'b': np.full(shape, 9.999)}
1595
+
1596
+ assert core.check(schema, state)
1597
+ assert core.check(schema, update)
1598
+ assert not core.check(schema, 15)
1599
+
1600
+ result = core.apply(
1601
+ schema,
1602
+ state,
1603
+ update)
1604
+
1605
+ assert result['a'][0, 0, 0] == 5.555
1606
+ assert result['b'][0, 0, 0] == 10.999
1607
+
1608
+ encode = core.serialize(schema, state)
1609
+ assert encode['b']['shape'] == list(shape)
1610
+ assert encode['a']['data'] == 'float'
1611
+
1612
+ decode = core.deserialize(schema, encode)
1613
+
1614
+ for key in state:
1615
+ assert np.equal(
1616
+ decode[key],
1617
+ state[key]).all()
1618
+
1619
+ found = core.find(
1620
+ schema)
1621
+
1622
+ default = core.default(
1623
+ found['_value'])
1624
+
1625
+ assert default.shape == shape
1626
+
1627
+
1628
+ def test_infer_edge(core):
1629
+ initial_schema = {}
1630
+ initial_state = {
1631
+ 'fade': {
1632
+ '_type': 'edge',
1633
+ '_inputs': {
1634
+ 'yellow': 'array[(3|4|10),float]'},
1635
+ '_outputs': {
1636
+ 'green': 'array[(11|5|8),float]'},
1637
+ 'inputs': {
1638
+ 'yellow': ['yellow']},
1639
+ 'outputs': {
1640
+ 'green': ['green']}}}
1641
+
1642
+ update = {
1643
+ 'yellow': np.ones((3, 4, 10)),
1644
+ 'fade': {
1645
+ 'inputs': {
1646
+ 'yellow': ['red']},
1647
+ 'outputs': {
1648
+ 'green': ['green', 'green', 'green']}}}
1649
+
1650
+ schema, state = core.complete(
1651
+ initial_schema,
1652
+ initial_state)
1653
+
1654
+ assert core.check(schema, state)
1655
+ assert not core.check(schema, 15)
1656
+
1657
+ result = core.apply(
1658
+ schema,
1659
+ state,
1660
+ update)
1661
+
1662
+ assert result['yellow'][0, 0, 0] == 1.0
1663
+ assert result['fade']['inputs']['yellow'] == ['red']
1664
+
1665
+ encode = core.serialize(schema, state)
1666
+ decode = core.deserialize(schema, encode)
1667
+
1668
+ assert np.equal(
1669
+ decode['yellow'],
1670
+ state['yellow']).all()
1671
+
1672
+
1673
+ def test_edge_type(core):
1674
+ schema = {
1675
+ 'fade': {
1676
+ '_type': 'edge',
1677
+ '_inputs': {
1678
+ 'yellow': {
1679
+ '_type': 'array',
1680
+ '_shape': 'tuple(3,4,10)',
1681
+ '_data': 'float'}},
1682
+ '_outputs': {
1683
+ 'green': {
1684
+ '_type': 'array',
1685
+ '_shape': 'tuple(11,5,8)',
1686
+ '_data': 'float'}}}}
1687
+
1688
+ initial_schema = {
1689
+ 'fade': 'edge[yellow:array[(3|4|10),float],green:array[(11|5|8),float]]'}
1690
+
1691
+ initial_state = {
1692
+ # 'yellow': np.zeros((3, 4, 10)),
1693
+ # 'green': np.ones((11, 5, 8)),
1694
+ 'fade': {
1695
+ 'inputs': {
1696
+ 'yellow': ['yellow']},
1697
+ 'outputs': {
1698
+ 'green': ['green']}}}
1699
+
1700
+ schema, state = core.complete(
1701
+ initial_schema,
1702
+ initial_state)
1703
+
1704
+ update = {
1705
+ 'yellow': np.ones((3, 4, 10)),
1706
+ 'fade': {
1707
+ 'inputs': {
1708
+ 'yellow': ['red']},
1709
+ 'outputs': {
1710
+ 'green': ['green', 'green', 'green']}}}
1711
+
1712
+ assert core.check(schema, state)
1713
+ assert not core.check(schema, 15)
1714
+
1715
+ result = core.apply(
1716
+ schema,
1717
+ state,
1718
+ update)
1719
+
1720
+ assert result['yellow'][0, 0, 0] == 1.0
1721
+ assert result['fade']['inputs']['yellow'] == ['red']
1722
+
1723
+ encode = core.serialize(schema, state)
1724
+ decode = core.deserialize(schema, encode)
1725
+
1726
+ assert np.equal(
1727
+ decode['yellow'],
1728
+ state['yellow']).all()
1729
+
1730
+
1731
+ def test_edge_complete(core):
1732
+ edge_schema = {
1733
+ '_type': 'edge',
1734
+ '_inputs': {
1735
+ 'concentration': 'float',
1736
+ 'field': 'map[boolean]'},
1737
+ '_outputs': {
1738
+ 'target': 'boolean',
1739
+ # 'inner': {
1740
+ # 'nested': 'boolean'},
1741
+ 'total': 'integer',
1742
+ 'delta': 'float'}}
1743
+
1744
+ edge_state = {
1745
+ 'inputs': {
1746
+ 'concentration': ['molecules', 'glucose'],
1747
+ 'field': ['states']},
1748
+ 'outputs': {
1749
+ 'target': ['states', 'X'],
1750
+ # 'inner': {
1751
+ # 'nested': ['states', 'A']},
1752
+ 'total': ['emitter', 'total molecules'],
1753
+ 'delta': ['molecules', 'glucose']}}
1754
+
1755
+ # edge_state = {
1756
+ # 'inputs': {
1757
+ # 'concentration': ['..', 'molecules', 'glucose'],
1758
+ # 'field': ['..', 'states']},
1759
+ # 'outputs': {
1760
+ # 'target': ['..', 'states', 'X'],
1761
+ # 'total': ['..', 'emitter', 'total molecules'],
1762
+ # 'delta': ['..', 'molecules', 'glucose']}}
1763
+
1764
+ full_schema, full_state = core.complete(
1765
+ {'edge': edge_schema},
1766
+ {'edge': edge_state})
1767
+
1768
+ assert full_schema['states']['_type'] == 'map'
1769
+
1770
+
1771
+ def test_divide(core):
1772
+ schema = {
1773
+ 'a': 'tree[maybe[float]]',
1774
+ 'b': 'float~list[string]',
1775
+ 'c': {
1776
+ 'd': 'map[edge[GGG:float,OOO:float]]',
1777
+ 'e': 'array[(3|4|10),float]'}}
1778
+
1779
+ state = {
1780
+ 'a': {
1781
+ 'x': {
1782
+ 'oooo': None,
1783
+ 'y': 1.1,
1784
+ 'z': 33.33},
1785
+ 'w': 44.444},
1786
+ 'b': ['1', '11', '111', '1111'],
1787
+ 'c': {
1788
+ 'd': {
1789
+ 'A': {
1790
+ 'inputs': {
1791
+ 'GGG': ['..', '..', 'a', 'w']},
1792
+ 'outputs': {
1793
+ 'OOO': ['..', '..', 'a', 'x', 'y']}},
1794
+ 'B': {
1795
+ 'inputs': {
1796
+ 'GGG': ['..', '..', 'a', 'x', 'y']},
1797
+ 'outputs': {
1798
+ 'OOO': ['..', '..', 'a', 'w']}}},
1799
+ 'e': np.zeros((3, 4, 10))}}
1800
+
1801
+ divisions = 3
1802
+ division = core.fold(
1803
+ schema,
1804
+ state,
1805
+ 'divide',
1806
+ {'divisions': divisions})
1807
+
1808
+ assert len(division) == divisions
1809
+ assert 'a' in division[0].keys()
1810
+ assert len(division[1]['b']) == len(state['b'])
1811
+
1812
+
1813
+ def test_merge(core):
1814
+ current_schema = {
1815
+ 'a': 'tree[maybe[float]]',
1816
+ 'b': 'float~list[string]',
1817
+ 'c': {
1818
+ 'd': 'map[edge[GGG:float,OOO:float]]',
1819
+ 'e': 'array[(3|4|10),float]'}}
1820
+
1821
+ current_state = {
1822
+ 'a': {
1823
+ 'x': {
1824
+ 'oooo': None,
1825
+ 'y': 1.1,
1826
+ 'z': 33.33},
1827
+ 'w': 44.444},
1828
+ 'b': ['1', '11', '111', '1111'],
1829
+ 'c': {
1830
+ 'd': {
1831
+ 'A': {
1832
+ 'inputs': {
1833
+ 'GGG': ['..', '..', 'a', 'w']},
1834
+ 'outputs': {
1835
+ 'OOO': ['..', '..', 'a', 'x', 'y']}},
1836
+ 'B': {
1837
+ 'inputs': {
1838
+ 'GGG': ['..', '..', 'a', 'x', 'y']},
1839
+ 'outputs': {
1840
+ 'OOO': ['..', '..', 'a', 'w']}}},
1841
+ 'e': np.zeros((3, 4, 10))}}
1842
+
1843
+ merge_state = {
1844
+ 'z': 555.55,
1845
+ 'b': ['333333333'],
1846
+ 'a': {
1847
+ 'x': {
1848
+ 'x': {
1849
+ 'o': 99999.11}}}}
1850
+
1851
+ result = core.merge_recur(
1852
+ current_schema,
1853
+ current_state,
1854
+ merge_state)
1855
+
1856
+ assert result['z'] == merge_state['z']
1857
+ assert result['b'] == merge_state['b']
1858
+ assert result['a']['x']['x']['o'] == merge_state['a']['x']['x']['o']
1859
+
1860
+
1861
+ def test_bind(core):
1862
+ current_schema = {
1863
+ 'a': 'tree[maybe[float]]',
1864
+ 'b': 'float~list[string]',
1865
+ 'c': {
1866
+ 'd': 'map[edge[GGG:float,OOO:float]]',
1867
+ 'e': 'array[(3|4|10),float]'}}
1868
+
1869
+ current_state = {
1870
+ 'a': {
1871
+ 'x': {
1872
+ 'oooo': None,
1873
+ 'y': 1.1,
1874
+ 'z': 33.33},
1875
+ 'w': 44.444},
1876
+ 'b': ['1', '11', '111', '1111'],
1877
+ 'c': {
1878
+ 'd': {
1879
+ 'A': {
1880
+ 'inputs': {
1881
+ 'GGG': ['..', '..', 'a', 'w']},
1882
+ 'outputs': {
1883
+ 'OOO': ['..', '..', 'a', 'x', 'y']}},
1884
+ 'B': {
1885
+ 'inputs': {
1886
+ 'GGG': ['..', '..', 'a', 'x', 'y']},
1887
+ 'outputs': {
1888
+ 'OOO': ['..', '..', 'a', 'w']}}},
1889
+ 'e': np.zeros((3, 4, 10))}}
1890
+
1891
+ result_schema, result_state = core.bind(
1892
+ current_schema,
1893
+ current_state,
1894
+ 'z',
1895
+ 'float',
1896
+ 555.55)
1897
+
1898
+ assert result_schema['z']['_type'] == 'float'
1899
+ assert result_state['z'] == 555.55
1900
+
1901
+
1902
+ def test_slice(core):
1903
+ schema, state = core.slice(
1904
+ 'map[float]',
1905
+ {'aaaa': 55.555},
1906
+ ['aaaa'])
1907
+
1908
+ schema, state = core.complete({}, {
1909
+ 'top': {
1910
+ '_type': 'tree[list[maybe[(float|integer)~string]]]',
1911
+ 'AAAA': {
1912
+ 'BBBB': {
1913
+ 'CCCC': [
1914
+ (1.3, 5),
1915
+ 'okay',
1916
+ (55.555, 1),
1917
+ None,
1918
+ 'what',
1919
+ 'is']}},
1920
+ 'DDDD': [
1921
+ (3333.1, 88),
1922
+ 'in',
1923
+ 'between',
1924
+ (66.8, -3),
1925
+ None,
1926
+ None,
1927
+ 'later']}})
1928
+
1929
+ float_schema, float_state = core.slice(
1930
+ schema,
1931
+ state,
1932
+ ['top', 'AAAA', 'BBBB', 'CCCC', 2, 0])
1933
+
1934
+ assert float_schema['_type'] == 'float'
1935
+ assert float_state == 55.555
1936
+
1937
+ assert core.slice(
1938
+ schema,
1939
+ state,
1940
+ ['top', 'AAAA', 'BBBB', 'CCCC', 3])[1] is None
1941
+
1942
+
1943
+ def test_star_path(core):
1944
+ nested_schema = 'map[map[green:float|yellow:integer|blue:string]]'
1945
+ nested_state = {
1946
+ 'aaa': {
1947
+ 'bbb': {
1948
+ 'green': 1.1,
1949
+ 'yellow': 55,
1950
+ 'blue': 'what'},
1951
+ 'ccc': {
1952
+ 'green': 9999.4,
1953
+ 'yellow': 11,
1954
+ 'blue': 'umbrella'}}}
1955
+
1956
+ # TODO: can you do everything the * is doing here with _path instead?
1957
+ nested_path = ['aaa', '*', 'green']
1958
+
1959
+ schema, state = core.slice(
1960
+ nested_schema,
1961
+ nested_state,
1962
+ nested_path)
1963
+
1964
+ assert schema['_value']['_type'] == 'float'
1965
+ assert state['ccc'] == 9999.4
1966
+
1967
+
1968
+ def test_star_view_project(core):
1969
+ schema = {
1970
+ 'edges': 'map[edge[view:map[float],project:map[string]]]',
1971
+ 'stores': 'map[map[green:float|yellow:integer|blue:string]]'}
1972
+
1973
+ state = {
1974
+ 'edges': {
1975
+ 'edge': {
1976
+ 'inputs': {
1977
+ 'view': ['..', 'stores', 'aaa', '*', 'green']},
1978
+ 'outputs': {
1979
+ 'project': ['..', 'stores', 'aaa', '*', 'blue']}}},
1980
+ 'stores': {
1981
+ 'aaa': {
1982
+ 'bbb': {
1983
+ 'green': 1.1,
1984
+ 'yellow': 55,
1985
+ 'blue': 'what'},
1986
+ 'ccc': {
1987
+ 'green': 9999.4,
1988
+ 'yellow': 11,
1989
+ 'blue': 'umbrella'}}}}
1990
+
1991
+ edge_path = ['edges', 'edge']
1992
+
1993
+ view = core.view_edge(
1994
+ schema,
1995
+ state,
1996
+ edge_path)
1997
+
1998
+ internal = {
1999
+ 'project': {
2000
+ 'bbb': 'everything',
2001
+ 'ccc': 'inside out'}}
2002
+
2003
+ project = core.project_edge(
2004
+ schema,
2005
+ state,
2006
+ edge_path,
2007
+ internal)
2008
+
2009
+ assert view['view']['bbb'] == state['stores']['aaa']['bbb']['green']
2010
+ assert project['stores']['aaa']['ccc']['blue'] == internal['project']['ccc']
2011
+
2012
+
2013
+ def test_set_slice(core):
2014
+ float_schema, float_state = core.set_slice(
2015
+ 'map[float]',
2016
+ {'aaaa': 55.555},
2017
+ ['bbbbb'],
2018
+ 'float',
2019
+ 888.88888)
2020
+
2021
+ assert float_schema['_type'] == 'map'
2022
+ assert float_state['bbbbb'] == 888.88888
2023
+
2024
+ schema, state = core.complete({}, {
2025
+ 'top': {
2026
+ '_type': 'tree[list[maybe[(float|integer)~string]]]',
2027
+ 'AAAA': {
2028
+ 'BBBB': {
2029
+ 'CCCC': [
2030
+ (1.3, 5),
2031
+ 'okay',
2032
+ (55.555, 1),
2033
+ None,
2034
+ 'what',
2035
+ 'is']}},
2036
+ 'DDDD': [
2037
+ (3333.1, 88),
2038
+ 'in',
2039
+ 'between',
2040
+ (66.8, -3),
2041
+ None,
2042
+ None,
2043
+ 'later']}})
2044
+
2045
+ leaf_schema, leaf_state = core.set_slice(
2046
+ schema,
2047
+ state,
2048
+ ['top', 'AAAA', 'BBBB', 'CCCC', 2, 1],
2049
+ 'integer',
2050
+ 33)
2051
+
2052
+ assert core.slice(
2053
+ leaf_schema,
2054
+ leaf_state, [
2055
+ 'top',
2056
+ 'AAAA',
2057
+ 'BBBB',
2058
+ 'CCCC',
2059
+ 2,
2060
+ 1])[1] == 33
2061
+
2062
+
2063
+ def test_dataclass(core):
2064
+ simple_schema = {
2065
+ 'a': 'float',
2066
+ 'b': 'integer',
2067
+ 'c': 'boolean',
2068
+ 'x': 'string'}
2069
+
2070
+ # TODO: accept just a string instead of only a path
2071
+ simple_dataclass = core.dataclass(
2072
+ simple_schema,
2073
+ 'simple')
2074
+
2075
+ simple_state = {
2076
+ 'a': 88.888,
2077
+ 'b': 11111,
2078
+ 'c': False,
2079
+ 'x': 'not a string'}
2080
+
2081
+ simple_new = simple_dataclass(
2082
+ a=1.11,
2083
+ b=33,
2084
+ c=True,
2085
+ x='what')
2086
+
2087
+ simple_from = state_instance(
2088
+ simple_dataclass,
2089
+ simple_state)
2090
+
2091
+ default_simple = core.default_instance(
2092
+ simple_schema,
2093
+ 'simple_default')
2094
+
2095
+ nested_schema = {
2096
+ 'a': {
2097
+ 'a': {
2098
+ 'a': 'float',
2099
+ 'b': 'float'},
2100
+ 'x': 'float'}}
2101
+
2102
+ nested_dataclass = core.dataclass(
2103
+ nested_schema,
2104
+ 'nested')
2105
+
2106
+ nested_state = {
2107
+ 'a': {
2108
+ 'a': {
2109
+ 'a': 13.4444,
2110
+ 'b': 888.88},
2111
+ 'x': 111.11111}}
2112
+
2113
+ nested_new = data_module.nested(
2114
+ data_module.nested_a(
2115
+ data_module.nested_a_a(
2116
+ a=222.22,
2117
+ b=3.3333),
2118
+ 5555.55))
2119
+
2120
+ nested_from = state_instance(
2121
+ nested_dataclass,
2122
+ nested_state)
2123
+
2124
+ complex_schema = {
2125
+ 'a': 'tree[maybe[float]]',
2126
+ 'b': 'float~list[string]',
2127
+ 'c': {
2128
+ 'd': 'map[edge[GGG:float,OOO:float]]',
2129
+ 'e': 'array[(3|4|10),float]'}}
2130
+
2131
+ complex_dataclass = core.dataclass(
2132
+ complex_schema,
2133
+ 'complex')
2134
+
2135
+ complex_state = {
2136
+ 'a': {
2137
+ 'x': {
2138
+ 'oooo': None,
2139
+ 'y': 1.1,
2140
+ 'z': 33.33},
2141
+ 'w': 44.444},
2142
+ 'b': ['1', '11', '111', '1111'],
2143
+ 'c': {
2144
+ 'd': {
2145
+ 'A': {
2146
+ 'inputs': {
2147
+ 'GGG': ['..', '..', 'a', 'w']},
2148
+ 'outputs': {
2149
+ 'OOO': ['..', '..', 'a', 'x', 'y']}},
2150
+ 'B': {
2151
+ 'inputs': {
2152
+ 'GGG': ['..', '..', 'a', 'x', 'y']},
2153
+ 'outputs': {
2154
+ 'OOO': ['..', '..', 'a', 'w']}}},
2155
+ 'e': np.zeros((3, 4, 10))}}
2156
+
2157
+ complex_from = state_instance(
2158
+ complex_dataclass,
2159
+ complex_state)
2160
+
2161
+ complex_dict = asdict(complex_from)
2162
+
2163
+ # assert complex_dict == complex_state ?
2164
+
2165
+ assert complex_from.a['x']['oooo'] is None
2166
+ assert len(complex_from.c.d['A']['inputs']['GGG'])
2167
+ assert isinstance(complex_from.c.e, np.ndarray)
2168
+
2169
+
2170
+ def test_enum_type(core):
2171
+ core.register(
2172
+ 'planet',
2173
+ 'enum[mercury,venus,earth,mars,jupiter,saturn,neptune]')
2174
+
2175
+ # this is equivalent to the above (TODO: test this)
2176
+ # core.register('planet', {
2177
+ # '_type': 'enum',
2178
+ # '_type_parameters': ['0', '1', '2', '3', '4', '5', '6'],
2179
+ # '_0': 'mercury',
2180
+ # '_1': 'venus',
2181
+ # '_2': 'earth',
2182
+ # '_3': 'mars',
2183
+ # '_4': 'jupiter',
2184
+ # '_5': 'saturn',
2185
+ # '_6': 'neptune'})
2186
+
2187
+ assert core.default('planet') == 'mercury'
2188
+
2189
+ solar_system_schema = {
2190
+ 'planets': 'map[planet]'}
2191
+
2192
+ solar_system = {
2193
+ 'planets': {
2194
+ '3': 'earth',
2195
+ '4': 'mars'}}
2196
+
2197
+ jupiter_update = {
2198
+ 'planets': {
2199
+ '5': 'jupiter'}}
2200
+
2201
+ pluto_update = {
2202
+ 'planets': {
2203
+ '7': 'pluto'}}
2204
+
2205
+ assert core.check(
2206
+ solar_system_schema,
2207
+ solar_system)
2208
+
2209
+ assert core.check(
2210
+ solar_system_schema,
2211
+ jupiter_update)
2212
+
2213
+ assert not core.check(
2214
+ solar_system_schema,
2215
+ pluto_update)
2216
+
2217
+ with_jupiter = core.apply(
2218
+ solar_system_schema,
2219
+ solar_system,
2220
+ jupiter_update)
2221
+
2222
+ try:
2223
+ core.apply(
2224
+ solar_system_schema,
2225
+ solar_system,
2226
+ pluto_update)
2227
+
2228
+ assert False
2229
+ except Exception as e:
2230
+ print(e)
2231
+ assert True
2232
+
2233
+
2234
+ def test_map_schema(core):
2235
+ schema = {
2236
+ 'greetings': 'map[hello:string]',
2237
+ 'edge': {
2238
+ '_type': 'edge',
2239
+ '_inputs': {
2240
+ 'various': {
2241
+ '_type': 'map',
2242
+ '_value': {
2243
+ 'world': 'string'}}},
2244
+ '_outputs': {
2245
+ 'referent': 'float'}}}
2246
+
2247
+ state = {
2248
+ 'edge': {
2249
+ 'inputs': {
2250
+ 'various': ['greetings']},
2251
+ 'outputs': {
2252
+ 'referent': ['where']}},
2253
+
2254
+ 'greetings': {
2255
+ 'a': {
2256
+ 'hello': 'yes'},
2257
+ 'b': {
2258
+ 'hello': 'again',
2259
+ 'world': 'present'},
2260
+ 'c': {
2261
+ 'other': 'other'}}}
2262
+
2263
+ complete_schema, complete_state = core.complete(
2264
+ schema,
2265
+ state)
2266
+
2267
+ assert complete_schema['greetings']['_value']['hello']['_type'] == 'string'
2268
+ assert complete_schema['greetings']['_value']['world']['_type'] == 'string'
2269
+
2270
+ assert 'world' in complete_state['greetings']['a']
2271
+ assert complete_schema['greetings']['_value']['world']['_type'] == 'string'
2272
+
2273
+
2274
+ def test_representation(core):
2275
+ schema_examples = [
2276
+ 'map[float]',
2277
+ '(string|float)',
2278
+ 'tree[(a:float|b:map[string])]',
2279
+ 'array[(5|11),maybe[integer]]',
2280
+ 'edge[(x:float|y:tree[(z:float)]),(w:(float|float|float))]']
2281
+
2282
+ for example in schema_examples:
2283
+ full_type = core.access(example)
2284
+ representation = core.representation(full_type)
2285
+
2286
+ if example != representation:
2287
+ raise Exception(
2288
+ f'did not receive the same type after parsing and finding the representation:\n {example}\n {representation}')
2289
+
2290
+
2291
+ def test_generate(core):
2292
+ schema = {
2293
+ 'A': 'float',
2294
+ 'B': 'enum[one,two,three]',
2295
+ 'units': 'map[float]'}
2296
+
2297
+ state = {
2298
+ 'C': {
2299
+ '_type': 'enum[x,y,z]',
2300
+ '_default': 'y'},
2301
+ 'units': {
2302
+ 'meters': 11.1111,
2303
+ 'seconds': 22.833333}}
2304
+
2305
+ generated_schema, generated_state = core.generate(
2306
+ schema,
2307
+ state)
2308
+
2309
+ assert generated_state['A'] == 0.0
2310
+ assert generated_state['B'] == 'one'
2311
+ assert generated_state['C'] == 'y'
2312
+ assert generated_state['units']['seconds'] == 22.833333
2313
+ assert 'meters' not in generated_schema['units']
2314
+
2315
+
2316
+ def test_edge_cycle(core):
2317
+ empty_schema = {}
2318
+ empty_state = {}
2319
+
2320
+ A_schema = {
2321
+ 'A': {
2322
+ '_type': 'metaedge',
2323
+ '_inputs': {
2324
+ 'before': {
2325
+ 'inputs': {'before': {'_default': ['B']}},
2326
+ 'outputs': {'after': {'_default': ['A']}}}},
2327
+ '_outputs': {
2328
+ 'after': {
2329
+ 'inputs': {'before': {'_default': ['A']}},
2330
+ 'outputs': {'after': {'_default': ['C']}}}},
2331
+ 'inputs': {'before': {'_default': ['C']}},
2332
+ 'outputs': {'after': {'_default': ['B']}}}}
2333
+
2334
+ A_state = {
2335
+ 'A': {
2336
+ '_type': 'metaedge',
2337
+ '_inputs': {
2338
+ 'before': {
2339
+ 'inputs': {'before': {'_default': ['B']}},
2340
+ 'outputs': {'after': {'_default': ['A']}}}},
2341
+ '_outputs': {
2342
+ 'after': {
2343
+ 'inputs': {'before': {'_default': ['A']}},
2344
+ 'outputs': {'after': {'_default': ['C']}}}},
2345
+ 'inputs': {'before': {'_default': ['C']}},
2346
+ 'outputs': {'after': {'_default': ['B']}}}}
2347
+
2348
+ schema_from_schema, state_from_schema = core.generate(
2349
+ A_schema,
2350
+ empty_state)
2351
+
2352
+ schema_from_state, state_from_state = core.generate(
2353
+ empty_schema,
2354
+ A_state)
2355
+
2356
+ # print(compare_dicts(schema_from_schema, schema_from_state))
2357
+ # print(compare_dicts(state_from_schema, state_from_state))
2358
+
2359
+ if schema_from_schema != schema_from_state:
2360
+ print(compare_dicts(schema_from_schema, schema_from_state))
2361
+
2362
+ if state_from_schema != state_from_state:
2363
+ print(compare_dicts(state_from_schema, state_from_state))
2364
+
2365
+ assert schema_from_schema == schema_from_state
2366
+ assert state_from_schema == state_from_state
2367
+
2368
+ for key in ['A', 'B', 'C']:
2369
+ for result in [schema_from_schema, state_from_schema, schema_from_state, state_from_state]:
2370
+ assert key in result
2371
+
2372
+
2373
+ def test_merge2(core):
2374
+ schema = {
2375
+ 'A': 'float',
2376
+ 'B': 'enum[one,two,three]',
2377
+ 'units': 'map[float]'}
2378
+
2379
+ state = {
2380
+ 'C': {
2381
+ '_type': 'enum[x,y,z]',
2382
+ '_default': 'y'},
2383
+ 'units': {
2384
+ 'x': 11.1111,
2385
+ 'y': 22.833333}}
2386
+
2387
+ generated_schema, generated_state = core.generate(
2388
+ schema,
2389
+ state)
2390
+
2391
+ edge_state = {
2392
+ '_type': 'edge',
2393
+ '_inputs': {
2394
+ 'input': 'float'},
2395
+ '_outputs': {
2396
+ 'output': 'float'},
2397
+ 'inputs': {
2398
+ 'input': ['A']},
2399
+ 'outputs': {
2400
+ 'output': ['D']}}
2401
+
2402
+ top_schema, top_state = core.merge(
2403
+ generated_schema,
2404
+ generated_state,
2405
+ ['edge'],
2406
+ {},
2407
+ edge_state)
2408
+
2409
+ assert 'D' in top_state
2410
+ assert top_schema['D']['_type'] == 'float'
2411
+
2412
+ def test_remove_omitted(core=None):
2413
+ result = remove_omitted(
2414
+ {'a': {}, 'b': {'c': {}, 'd': {}}},
2415
+ {'b': {'c': {}}},
2416
+ {'a': {'X': 1111}, 'b': {'c': {'Y': 4444}, 'd': {'Z': 99999}}})
2417
+
2418
+ assert 'a' not in result
2419
+ assert result['b']['c']['Y'] == 4444
2420
+ assert 'd' not in result['b']
2421
+
2422
+
2423
+ def test_union_key_error(core):
2424
+ schema = core.access('map[map[float]]')
2425
+ state = {
2426
+ 'a': {'b': 1.1},
2427
+ 'c': {'d': 2.2},
2428
+ 'e': 3.3 # this should be an error
2429
+ }
2430
+ generate_method = core.choose_method(schema, state, 'generate')
2431
+
2432
+ # assert that the Exception is raised
2433
+ with pytest.raises(Exception):
2434
+ result = generate_method(core, schema, state)
2435
+
2436
+
2437
+ def test_update_removed(core):
2438
+ schema = 'map[float]'
2439
+ state = {
2440
+ 'a': 11.11,
2441
+ 'b': 12.2222,
2442
+ 'c': 13.33333333}
2443
+
2444
+
2445
+ # TODO fix this test
2446
+ def fix_test_slice_edge(core):
2447
+ initial_schema = {
2448
+ 'edge': {
2449
+ '_type': 'edge',
2450
+ '_inputs': {
2451
+ 'a': 'float',
2452
+ 'b': {'c': 'float', 'd': 'string'},
2453
+ 'e': {'f': 'array[(3|3),float]'}},
2454
+ '_outputs': {
2455
+ 'g': 'float',
2456
+ 'h': {'i': {'j': 'map[integer]'}},
2457
+ 'k': {'l': 'array[(3|3),float]'}}}}
2458
+
2459
+ initial_state = {
2460
+ 'JJJJ': {'MMMM': 55555},
2461
+ 'edge': {
2462
+ 'inputs': {
2463
+ 'a': ['AAAA'],
2464
+ 'b': {
2465
+ 'c': ['CCCC'],
2466
+ 'd': ['DDDD']},
2467
+ 'e': ['EEEE']},
2468
+ 'outputs': {
2469
+ 'g': ['GGGG'],
2470
+ 'h': {'i': {'j': ['JJJJ']}},
2471
+ 'k': {'l': ['LLLL', 'LLLLL', 'LLLLLL']}}}}
2472
+
2473
+ schema, state = core.generate(initial_schema, initial_state)
2474
+
2475
+ inner_schema, inner_state = core.slice(
2476
+ schema,
2477
+ state,
2478
+ ['edge', 'outputs', 'h', 'i', 'j', 'MMMM'])
2479
+
2480
+ assert inner_schema['_type'] == 'integer'
2481
+ assert inner_state == 55555
2482
+
2483
+
2484
+ # TODO fix this test
2485
+ def fix_test_complex_wiring(core):
2486
+ initial_schema = {
2487
+ 'edge': {
2488
+ '_type': 'edge',
2489
+ '_inputs': {
2490
+ 'a': {
2491
+ 'b': 'float',
2492
+ 'c': 'float',
2493
+ 'd': 'float'}},
2494
+ '_outputs': {}}}
2495
+
2496
+ initial_state = {
2497
+ 'edge': {
2498
+ 'inputs': {
2499
+ 'a': {
2500
+ '_path': ['AAAA', 'AAAAA'],
2501
+ 'b': ['BBBB'],
2502
+ 'c': ['CCCC', 'CCCCC']}}}}
2503
+
2504
+ schema, state = core.generate(
2505
+ initial_schema,
2506
+ initial_state)
2507
+
2508
+ assert state['AAAA']['AAAAA']['BBBB'] == 0.0
2509
+ assert state['AAAA']['AAAAA']['CCCC']['CCCCC'] == 0.0
2510
+ assert state['AAAA']['AAAAA']['d'] == 0.0
2511
+
2512
+
2513
+ def test_tree_equivalence(core):
2514
+ initial_state = {
2515
+ 'store1': {
2516
+ 'store1.1': '1.0'}}
2517
+
2518
+ # create a nested store type and register it
2519
+ store11 = {
2520
+ 'store1.1': {
2521
+ '_type': 'float',
2522
+ '_default': '1.0'}}
2523
+
2524
+ core.register('store1.1', store11)
2525
+
2526
+ # create a tree schema that uses this type
2527
+ store_tree = {
2528
+ '_type': 'tree',
2529
+ '_leaf': 'store1.1'}
2530
+
2531
+ # interpret the state as a simple tree of float
2532
+ store_schema, store_state = core.generate(
2533
+ 'tree[float]',
2534
+ initial_state)
2535
+
2536
+ # use the nested type to fill in the state
2537
+ tree_schema, tree_state = core.generate(
2538
+ store_tree,
2539
+ {'store1': None})
2540
+
2541
+ # use the nested type but with an empty dict instead
2542
+ fill_schema, fill_state = core.generate(
2543
+ store_tree,
2544
+ {'store1': {}})
2545
+
2546
+ # supply the whole schema at once instead of registering
2547
+ inline_schema, inline_state = core.generate({
2548
+ '_type': 'tree',
2549
+ '_leaf': {
2550
+ 'store1.1': {
2551
+ '_type': 'float',
2552
+ '_default': '1.0'}}},
2553
+ {'store1': {}})
2554
+
2555
+ # here is the state we expect from each of these calls
2556
+ # to generate
2557
+ target_state = {
2558
+ 'store1': {
2559
+ 'store1.1': 1.0}}
2560
+
2561
+ # all of the resulting generated states are the same
2562
+ assert store_state == tree_state == fill_state == inline_state == target_state
2563
+
2564
+
2565
+ if __name__ == '__main__':
2566
+ core = TypeSystem()
2567
+ core = register_test_types(core)
2568
+
2569
+ test_basic_types(core)
2570
+ test_update_types(core)
2571
+ test_reregister_type(core)
2572
+ test_generate_default(core)
2573
+ test_apply_update(core)
2574
+ test_validate_schema(core)
2575
+ test_fill_integer(core)
2576
+ test_fill_cube(core)
2577
+ test_establish_path(core)
2578
+ test_overwrite_existing(core)
2579
+ test_fill_in_missing_nodes(core)
2580
+ test_fill_in_disconnected_port(core)
2581
+ test_fill_from_parse(core)
2582
+ test_fill_ports(core)
2583
+ test_expected_schema(core)
2584
+ test_units(core)
2585
+ test_serialize_deserialize(core)
2586
+ test_project(core)
2587
+ test_inherits_from(core)
2588
+ test_check(core)
2589
+ test_apply_schema(core)
2590
+ test_resolve_schemas(core)
2591
+ test_add_reaction(core)
2592
+ test_remove_reaction(core)
2593
+ test_replace_reaction(core)
2594
+ test_map_type(core)
2595
+ test_tree_type(core)
2596
+ test_maybe_type(core)
2597
+ test_tuple_type(core)
2598
+ test_array_type(core)
2599
+ test_function_type(core)
2600
+ test_union_type(core)
2601
+ test_union_values(core)
2602
+ test_infer_edge(core)
2603
+ test_edge_type(core)
2604
+ test_edge_complete(core)
2605
+ test_foursquare(core)
2606
+ test_divide(core)
2607
+ test_merge(core)
2608
+ test_merge2(core)
2609
+ test_bind(core)
2610
+ test_slice(core)
2611
+ test_star_path(core)
2612
+ test_set_slice(core)
2613
+ test_dataclass(core)
2614
+ test_enum_type(core)
2615
+ test_map_schema(core)
2616
+ test_representation(core)
2617
+ test_generate(core)
2618
+ test_edge_cycle(core)
2619
+ test_remove_omitted(core)
2620
+ test_union_key_error(core)
2621
+ test_tree_equivalence(core)
2622
+ test_star_view_project(core)
2623
+ test_update_removed(core)
2624
+ test_merge_schemas(core)
2625
+
2626
+ # test_reaction(core)
2627
+ # test_link_place(core)
2628
+ # test_unit_conversion(core)
2629
+ # test_slice_edge(core)
2630
+ # test_complex_wiring(core)
2631
+ # test_fill_type_mismatch(core)