-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbuiltin_entities.py
3131 lines (2416 loc) · 105 KB
/
builtin_entities.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
from typing import List, Union, Optional, Any, Tuple
from collections import defaultdict
from ipydex import IPS # noqa
from .core import (
create_builtin_relation,
create_builtin_item,
Entity,
Relation,
Item,
Statement,
de,
en,
QualifierFactory,
RawQualifier,
ds,
RuleResult,
)
from .settings import BUILTINS_URI
# it is OK to access ds here in the builtin module, but this import should not be copied to other knowledge modules
from . import core
__URI__ = BUILTINS_URI
keymanager = core.KeyManager()
core.register_mod(__URI__, keymanager)
def allows_instantiation(itm: Item) -> bool:
"""
Check if `itm` is an instance of metaclass or a subclass of it. If true, this entity is considered
a class by itself and is thus allowed to have instances and subclasses
Possibilities:
I2 = itm -> True (by our definition)
I2 -R4-> itm -> True (trivial, itm is an ordinary class)
I2 -R4-> I100 -R4-> itm -> False (I100 is ordinary class → itm is ordinary instance)
I2 -R3-> itm -> True (subclasses of I2 are also metaclasses)
I2 -R3-> I100 -R4-> itm -> True (I100 is subclass of metaclass → itm is metaclass instance)
I2 -R3-> I100 -R3-> I101 -R3-> I102 -R4-> itm -> True (same)
# multiple times R4: false
I2 -R4-> I100 -R3-> I101 -R3-> I102 -R4-> itm -> False
(I100 is ordinary class → itm is ordinary instance of its sub-sub-class)
I2 -R3-> I100 -R4-> I101 -R3-> I102 -R4-> itm -> False (same)
I2 -R3-> I100 -R3-> I101 -R3-> I102 -R4-> itm -> True (same)
I2 -R4-> I100 -R3-> I101 -R3-> I102 -R3-> itm -> True (itm is an ordinary sub-sub-sub-subclass)
I2 -R3-> I100 -R4-> I101 -R3-> I102 -R3-> itm -> True
(itm is an sub-sub-subclass of I101 which is an instance of a subclass of I2)
:param itm: item to test
:return: bool
"""
taxtree = get_taxonomy_tree(itm)
# This is a list of 2-tuples like the following:
# [(None, <Item I4239["monovariate polynomial"]>),
# ('R3', <Item I4237["monovariate rational function"]>),
# ('R3', <Item I4236["mathematical expression"]>),
# ('R3', <Item I4235["mathematical object"]>),
# ('R4', <Item I2["Metaclass"]>),
# ('R3', <Item I1["general item"]>)
# ('R3', <Item I45["general entity"]>)]
if len(taxtree) < 2:
return False
relation_keys, items = zip(*taxtree)
if len(items) < 3:
# this is the case e.g. for:
# [
# ('R3', <Item I1["general item"]>)
# ('R3', <Item I45["general entity"]>)]
return False
if items[-3] is not I2["Metaclass"]:
return False
if relation_keys.count("R4") > 1:
return False
return True
def get_taxonomy_tree(itm, add_self=True) -> list:
"""
Recursively iterate over super and parent classes and
:param itm: an item
:raises NotImplementedError: DESCRIPTION
:return: list of 2-tuples like [(None, I456), ("R3", I123), ("R4", I2)]
:rtype: dict
"""
res = []
if add_self:
res.append((None, itm))
# Note:
# parent_class refers to R4__is_instance, super_class refers to R3__is_subclass_of
super_class = itm.R3__is_subclass_of
parent_class = itm.R4__is_instance_of
if (super_class is not None) and (parent_class is not None):
msg = f"currently not allowed together: R3__is_subclass_of and R4__is_instance_of (Entity: {itm}"
raise NotImplementedError(msg)
if super_class:
res.append(("R3", super_class))
res.extend(get_taxonomy_tree(super_class, add_self=False))
elif parent_class:
res.append(("R4", parent_class))
res.extend(get_taxonomy_tree(parent_class, add_self=False))
return res
def is_subclass_of(itm1: Item, itm2: Item, allow_id=False, strict=True) -> bool:
"""
Return True if itm1 is an (indirect) subclass (via) R3__is_subclass_of itm2
:param allow_id: bool, indicate that itm1 == itm2 is also considered
as valid. default: False
"""
if allow_id and itm1 == itm2:
return True
if strict:
for i, itm in enumerate((itm1, itm2), start=1):
if not allows_instantiation(itm):
msg = f"itm{i} ({itm}) is not a instantiable class"
raise core.aux.TaxonomicError(msg)
taxtree1 = get_taxonomy_tree(itm1)
# This is a list of 2-tuples like the following:
# [(None, <Item I4239["monovariate polynomial"]>),
# ('R3', <Item I4237["monovariate rational function"]>),
# ('R3', <Item I4236["mathematical expression"]>),
# ('R3', <Item I4235["mathematical object"]>),
# ('R4', <Item I2["Metaclass"]>),
# ('R3', <Item I1["general item"]>)
# ('R3', <Item I45["general entity"]>)]
# reminder: R3__is_subclass_of, R4__is_instance_of
res = ("R3", itm2) in taxtree1
return res
def is_instance_of(inst_itm: Item, cls_itm: Item, allow_R30_secondary: bool = False, strict=True) -> bool:
"""
Returns True if instance_itm.R4 is cls_itm or an (indirect) subclass (R3) of cls_itm.
:param inst_itm: Item representing the instance
:param cls_itm: Item representing the class
:param allow_R30_secondary: bool, accept also relations via R30__is_secondary_instance_of
:param strict: bool; if true we raise an exception if there is no parent class
"""
parent_class = inst_itm.R4__is_instance_of
if parent_class is None:
if strict:
msg = (
f"instance_itm ({inst_itm}) has no Statement for relation `R4__is_instance_of`. "
"You might use kwarg `strict=False`."
)
raise core.aux.TaxonomicError(msg)
else:
return False
if parent_class == cls_itm:
return True
if is_subclass_of(parent_class, cls_itm, strict=strict):
return True
if allow_R30_secondary:
for test_cls_item in inst_itm.R30__is_secondary_instance_of:
if test_cls_item == cls_itm:
return True
if is_subclass_of(test_cls_item, cls_itm, strict=strict):
return True
return False
def instance_of(
cls_entity, r1: str = None, r2: str = None, qualifiers: List[Item] = None, force_key: str = None
) -> Item:
"""
Create an instance (R4) of an item. Try to obtain the label by inspection of the calling context (if r1 is None).
:param cls_entity: the type of which an instance is created
:param r1: the label; if None use inspection to fetch it from the left hand side of the assignment
:param r2: the description (optional)
:param qualifiers: list of RawQualifiers (optional); will be passed to the R4__is_instance_of relation
if `cls_entity` has a defining scope and `qualifiers` is None, then an appropriate R20__has_defining_scope-
qualifier will be added to the R4__is_instance_of-relation of the new item.
:return: new item
"""
has_super_class = cls_entity.R3 is not None
class_scope = cls_entity.R20__has_defining_scope
# we have to determine if `cls_entity` is an instance of I2_metaclass or a subclass of it
is_instance_of_metaclass = allows_instantiation(cls_entity)
cls_exceptions = (I1["general item"], I40["general relation"])
if (not has_super_class) and (not is_instance_of_metaclass) and (cls_entity not in cls_exceptions):
msg = f"the entity '{cls_entity}' is not a class, and thus could not be instantiated"
raise TypeError(msg)
if r1 is None:
try:
r1 = core.get_key_str_by_inspection()
# TODO: make this except clause more specific
except:
# note this fallback naming can be avoided by explicitly passing r1=... as kwarg
r1 = f"{cls_entity.R1} – instance"
if r2 is None:
r2 = f'generic instance of {cls_entity.short_key}("{cls_entity.R1}")'
if force_key:
key = force_key
else:
# add prefix2 "a" for "autogenerated"
key = core.pop_uri_based_key(prefix="I", prefix2="a")
new_item = core.create_item(
key_str=key,
R1__has_label=r1,
R2__has_description=r2,
)
if not qualifiers and class_scope is not None:
qualifiers = [qff_has_defining_scope(class_scope)]
new_item.set_relation(R4["is instance of"], cls_entity, qualifiers=qualifiers)
# add consistency relevant relations:
# note that the could be overwritten with item.overwrite_statement
for rel in [
R8["has domain of argument 1"],
R9["has domain of argument 2"],
R10["has domain of argument 3"],
R11["has range of result"],
]:
obj = cls_entity.get_relations(rel.uri, return_obj=True)
if obj not in ([], None):
if isinstance(obj, list):
assert len(obj) == 1
obj = obj[0]
new_item.set_relation(rel, obj)
# TODO: solve this more elegantly
# this has to be run again after setting R4
new_item.__post_init__()
return new_item
########################################################################################################################
#
# Creation of entities
#
########################################################################################################################
core.start_mod(__URI__)
# the bootstrapping of relations is slightly unintuitive because
# a) labels and descriptions are introduced with some delay and
# b) because keys reflect historical development
R32 = create_builtin_relation(key_str="R32") # will be R32["is functional for each language"]
R1 = create_builtin_relation("R1", R32=True)
R1.set_relation(R1, "has label")
R32.set_relation(R1, "is functional for each language")
R2 = create_builtin_relation("R2", R1="has description", R32=True)
R2.set_relation(R2, "specifies a natural language description")
R1.set_relation(R2, "specifies a short natural language label")
R32.set_relation(
R2,
"specifies that for each subject there is at most one 'R30-Statement' for a given language tag (e.g. en)",
)
R22 = create_builtin_relation(
key_str="R22",
R1="is functional",
R2="specifies that the subject entity is a relation which has at most one value per item",
)
R22["is functional"].set_relation(R22["is functional"], True)
R32["is functional for each language"].set_relation(R22["is functional"], True)
# Note that R1, R22, and R32 are used extensively to control the behavior in pyirk.core
R3 = create_builtin_relation("R3", R1="is subclass of", R22__is_functional=True)
R4 = create_builtin_relation("R4", R1="is instance of", R22__is_functional=True)
R5 = create_builtin_relation("R5", R1="is part of")
R6 = create_builtin_relation("R6", R1="has defining mathematical relation", R22__is_functional=True)
R7 = create_builtin_relation("R7", R1="has arity", R22__is_functional=True)
R8 = create_builtin_relation("R8", R1="has domain of argument 1")
R9 = create_builtin_relation("R9", R1="has domain of argument 2")
R10 = create_builtin_relation("R10", R1="has domain of argument 3")
R11 = create_builtin_relation(
"R11", R1="has range of result", R2="specifies the range of the result (last arg)"
)
R12 = create_builtin_relation("R12", R1="is defined by means of")
R13 = create_builtin_relation("R13", R1="has canonical symbol", R22__is_functional=True)
R14 = create_builtin_relation("R14", R1="is subset of")
R15 = create_builtin_relation("R15", R1="is element of", R2="states that arg1 is an element of arg2")
R16 = create_builtin_relation(
key_str="R16",
R1="has property",
R2="relates an entity with a mathematical property",
# R8__has_domain_of_argument_1=I4235("mathematical object"),
# R10__has_range_of_result=...
)
# The short key R61 was chosen for historical and/or pragmatic reasons
R61 = create_builtin_relation(
key_str="R61",
R1="does not have property",
R2="relates an entity with a mathematical property that it specifically does not have",
# R8__has_domain_of_argument_1=I4235("mathematical object"),
# R10__has_range_of_result=...
)
# TODO: rule: consistency of R16 and R61
R17 = create_builtin_relation(
key_str="R17", R1="is subproperty of", R2="specifies that arg1 (subj) is a subproperty of arg2 (obj)"
)
R18 = create_builtin_relation(
"R18", R1="has usage hint", R2="specifies a hint (str) on how this relation should be used"
)
R16.set_relation(
R18["has usage hint"], "this relation should be used on concrete instances, not on generic types"
)
R61.set_relation(
R18["has usage hint"], "this relation should be used on concrete instances, not on generic types"
)
R19 = create_builtin_relation(
key_str="R19",
R1="defines method",
R2="specifies that an entity has a special method (defined by executable code)",
# R10__has_range_of_result=callable !!
)
I40 = create_builtin_item(
key_str="I40",
R1__has_label="general relation",
R2__has_description="proxy item for a relation",
R18__has_usage_hint=(
"This item (which is in no direct relation to I1__general_item) can be used as a placeholder for any relation. "
"In other words: this can be interpreted as the common superclass for all relations"
),
)
R68 = create_builtin_relation(
key_str="R68",
R1="is inverse of",
R2="specifies that the subject is the inverse relation of the object",
R8__has_domain_of_argument_1=I40["general relation"],
R11__has_range_of_result=I40["general relation"],
R22__is_functional=True,
)
R20 = create_builtin_relation(
key_str="R20",
R1="has defining scope",
R2="specifies the scope *in* which an entity or statement is defined (e.g. the premise of a theorem)",
R18=(
"Notes: This relation is functional. But an Entity (e.g. a theorem) can be parent (via R21) of multiple "
"scopes, (e.g. 'setting', 'premise', 'assertion'). Each of these items can 'contain' other items in the sense, "
"that these other items are R20_has_defining_scope-related to the scope item. Thus, R20 and R21__is_scope_of "
"are *not* inverse to each other."
),
R22__is_functional=True,
)
qff_has_defining_scope = QualifierFactory(R20["has defining scope"], registry_name="qff_has_defining_scope")
R21 = create_builtin_relation(
key_str="R21",
R1="is scope of",
R2="specifies that the subject of that relation is a (sub) scope-item of the object (statement-item)",
R18=(
"This relation is used to bind scope items to its 'semantic parents'. "
"This is *not* the inverse relation to R20. "
"This is not to be confused with R45__has_subscope."
),
R22__is_functional=True,
)
R23 = create_builtin_relation(
key_str="R23",
R1="has name in scope",
R2="specifies that the subject entity has the object-literal as unique local name",
R22__is_functional=True,
)
R24 = create_builtin_relation(
key_str="R24",
R1="has LaTeX string",
R2="specifies that the subject is associated with a string of LaTeX source",
R22__is_functional=True,
)
R25 = create_builtin_relation(
key_str="R25",
R1="has language specified string",
R2="...",
)
# Items
I1 = create_builtin_item("I1", R1="general item")
I2 = create_builtin_item(
"I2",
R1="Metaclass",
R2__has_description=(
"Parent class for other classes; subclasses of this are also metaclasses "
"instances are ordinary classes"
),
R3__is_subclass_of=I1,
)
I3 = create_builtin_item("I3", R1="Field of science", R4__is_instance_of=I2)
I4 = create_builtin_item("I4", R1="Mathematics", R4__is_instance_of=I3)
I5 = create_builtin_item("I5", R1="Engineering", R4__is_instance_of=I3)
I6 = create_builtin_item("I6", R1="mathematical operation", R4__is_instance_of=I2["Metaclass"])
# TODO: model this with a relation instead of subclassing
I7 = create_builtin_item("I7", R1="mathematical operation with arity 1", R3__is_subclass_of=I6, R7=1)
I8 = create_builtin_item("I8", R1="mathematical operation with arity 2", R3__is_subclass_of=I6, R7=2)
I9 = create_builtin_item("I9", R1="mathematical operation with arity 3", R3__is_subclass_of=I6, R7=3)
I10 = create_builtin_item(
"I10",
R1="abstract metaclass",
R3__is_subclass_of=I2,
R2__has_description=(
"Special metaclass. Instances of this class are abstract classes that should not be instantiated, "
"but subclassed instead."
),
)
I11 = create_builtin_item(
key_str="I11",
R1="general property",
R2__has_description="base class for all properties",
R4__is_instance_of=I2["Metaclass"],
R18__has_usage_hint=(
"Actual properties are instances of this class (not subclasses). "
"To create a taxonomy-like structure the relation R17__is_sub_property_of should be used."
),
)
# TODO: clarify the difference between the I12 and I18
I12 = create_builtin_item(
key_str="I12",
R1__has_label="mathematical object",
R2__has_description="base class for any knowledge object of interest in the field of mathematics",
R4__is_instance_of=I2["Metaclass"],
)
I13 = create_builtin_item(
key_str="I13",
R1__has_label="mathematical set",
R2__has_description="mathematical set",
R3__is_subclass_of=I12["mathematical object"],
)
I14 = create_builtin_item(
key_str="I14",
R1__has_label="mathematical proposition",
R2__has_description="general mathematical proposition",
# R3__is_subclass_off will be set below to I22__mathematical_knowledge_artifact
)
# I15 is defined below
I16 = create_builtin_item(
key_str="I16",
R1__has_label="scope",
R2__has_description="auxiliary class; an instance defines the scope of statements (Statement-objects)",
R4__is_instance_of=I2["Metaclass"],
)
###############################################################################
# augment the functionality of `Entity`
###############################################################################
# Once the scope item has been defined it is possible to endow the Entity class with more features
def _register_scope(self, name: str, scope_type: str = None) -> tuple[dict, "Item"]:
"""
Create a namespace-object (dict) and a Scope-Item
:param name: the name of the scope
:return:
"""
assert isinstance(self, Entity)
# TODO: obsolete assert?
assert not name.startswith("_ns_") and not name.startswith("_scope_")
ns_name = f"_ns_{name}"
scope_name = f"scp__{name}"
scope = getattr(self, scope_name, None)
if (ns := getattr(self, ns_name, None)) is None:
# namespace is yet unknown -> assume that scope is also unknown
assert scope is None
# create namespace
ns = dict()
setattr(self, ns_name, ns)
self._namespaces[ns_name] = ns
# create scope
scope = instance_of(I16["scope"], r1=scope_name, r2=f"scope of {self.R1}")
scope.set_relation(R21["is scope of"], self)
# prevent accidental overwriting
msg = f"Entity {self} already has a scope with name '{name}'.\nPossible reason: copy-paste-error."
if scope_name in self.__dict__:
raise core.aux.InvalidScopeNameError(msg)
self.__dict__[scope_name] = scope
assert isinstance(ns, dict)
assert isinstance(scope, Item) and (scope.R21__is_scope_of == self)
if scope_type is None:
scope_type = name.upper()
scope.set_relation(R64["has scope type"], scope_type)
return ns, scope
# every entity can have scopes
Entity.add_method_to_class(_register_scope)
def add_relations_to_scope(relation_tuples: Union[list, tuple], scope: Entity):
"""
Add relations defined by 3-tuples (sub, rel, obj) to the respective scope.
:param relation_tuples:
:param scope:
:return:
"""
assert scope.R21__is_scope_of is not None
assert scope.R4__is_instance_of is I16["scope"]
for rel_tup in relation_tuples:
assert isinstance(rel_tup, tuple)
# this might become >= 3 in the future, if we support multivalued relations
assert len(rel_tup) == 3
sub, rel, obj = rel_tup
assert isinstance(sub, Entity)
assert isinstance(rel, Relation)
sub.set_relation(rel, obj, scope=scope)
def get_scopes(entity: Entity) -> List[Item]:
"""
Return a list of all scope-items which are associated with this entity like
[<scope:setting>, <scope:premise>, <scope:assertion>] for a proposition-item.
:param entity:
:return:
"""
assert isinstance(entity, Entity)
# R21__is_scope_of
scope_statements = core.ds.inv_statements[entity.short_key]["R21"]
re: Statement
res = [re.relation_tuple[0] for re in scope_statements]
return res
def get_items_defined_in_scope(scope: Item) -> List[Entity]:
assert scope.R4__is_instance_of == I16["scope"]
# R20__has_defining_scope
re_list = core.ds.inv_statements[scope.short_key]["R20"]
re: Statement
entities = [re.relation_tuple[0] for re in re_list]
return entities
def add_scope_to_defining_statement(ent: Entity, scope: Item) -> None:
"""
:param ent:
:param scope:
:return: None
The motivation for this function is a usage pattern like:
```
with I3007.scope("setting") as cm:
cm.new_var(sys=p.instance_of(I5948["dynamical system"]))
)
```
ideally the `instance_of` function would notice that it was called from within a python-context which defines a
scope item. But this seems hardly achievable in a clean way. Thus, this function is called after p.instance_of,
inside cm.new_var(...).
"""
assert isinstance(ent, Entity)
assert isinstance(scope, Item)
assert scope.R4__is_instance_of == I16["scope"]
# for now all defining_relations are R4-relations (R4__is_instance_of) (there should be exactly 1)
r4_list = ent.get_relations(R4.uri)
assert len(r4_list) == 1
re = r4_list[0]
assert isinstance(re, Statement)
re.scope = scope
class ScopingCM:
"""
Context manager to for creating ("atomic") statements in the scope of other (bigger statements).
E.g. establishing a relationship between two items as part of the assertions of a theorem-item
"""
_all_instances = []
_instances = defaultdict(list)
valid_subscope_types = None
def __init__(self, itm: Item, namespace: dict, scope: Item, parent_scope_cm=None):
# prevent the accidental instantiation of abstract subclasses
assert not __class__.__name__.lower().startswith("abstract")
# the item to which the scope refers e.g. <Item I9223["definition of zero matrix"]>,
self.item: Item = itm
self.namespace: dict = namespace
# the associated scope-item (which has a R64__has_scope_type relation)
self.scope: Item = scope
self.parent_scope_cm: ScopingCM | None = parent_scope_cm
# introduced to facilitate debugging and experimentation
self._instances[type(self)].append(self)
self._all_instances.append(self)
def __enter__(self):
"""
implicitly called in the head of the with statement
:return:
"""
ds.append_scope(self.scope)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# this is the place to handle exceptions
ds.remove_scope(self.scope)
def __getattr__(self, name: str):
"""
This function allows to use `cm.<local variable> instead of I2345.<local variable> where I2345 is the
parent object of the scope.
:param name:
:return:
"""
if name in self.__dict__:
return self.dict__[name]
return getattr(self.item, name)
def new_var(self, **kwargs) -> Entity:
"""
create and register a new variable to the respective scope
:param kwargs: dict of len == 1 (to allow (almost) arbitrary variable names)
:return:
"""
assert self.namespace is not None
assert self.scope is not None
msg = "the `new_var` method of a scope-context accepts exactly one keyword argument"
assert len(kwargs) == 1, msg
variable_name, variable_object = list(kwargs.items())[0]
return self._new_var(variable_name, variable_object)
def _new_var(self, variable_name: str, variable_object: Entity) -> Entity:
self._check_scope()
variable_object: Entity
add_scope_to_defining_statement(variable_object, self.scope)
# this reflects a design assumption which might be generalized later
assert isinstance(variable_object, Entity)
# allow simple access to the variables → put them into dict (after checking that the name is still free)
msg = f"The name '{variable_name}' is already occupied in the scope `{self.scope}` of item `{self.item}`."
assert variable_name not in self.item.__dict__ and variable_name not in self.__dict__, msg
self.item.__dict__[variable_name] = variable_object
# keep track of added context vars
self.namespace[variable_name] = variable_object
# indicate that the variable object is defined in the context of `self`
assert getattr(variable_object, "R20", None) is None
variable_object.set_relation(R20["has defining scope"], self.scope)
# todo: evaluate if this makes the namespaces obsolete
variable_object.set_relation(R23["has name in scope"], variable_name)
return variable_object
# TODO: this should be renamed to new_statement
def new_rel(
self, sub: Entity, pred: Relation, obj: Entity, qualifiers=None, overwrite=False
) -> Statement:
"""
Create a new statement ("relation edge") in the current scope
:param sub: subject
:param pred: predicate (Relation-Instance)
:param obj: object
:param qualifiers: List of RawQualifiers
:param overwrite: boolean flag that the new statement should replace the old one
:return: the newly created Statement
"""
self._check_scope()
assert isinstance(sub, Entity)
assert isinstance(pred, Relation)
if isinstance(qualifiers, RawQualifier):
qualifiers = [qualifiers]
elif qualifiers is None:
qualifiers = []
if overwrite:
qff_has_defining_scope: QualifierFactory = ds.qff_dict["qff_has_defining_scope"]
qualifiers.append(qff_has_defining_scope(self.scope))
return sub.overwrite_statement(pred.uri, obj, qualifiers=qualifiers)
else:
# Note: As qualifiers is a list, it will be changed by the next call (the R20-scope qlf is appended).
res = sub.set_relation(pred, obj, scope=self.scope, qualifiers=qualifiers)
return res
def _check_scope(self):
active_scope = ds.scope_stack[-1]
if not active_scope == self.scope:
msg = f"Unexpected active scope: ({active_scope}). Expected: {self.scope}"
raise core.aux.InvalidScopeNameError(msg)
def _create_subscope_cm(self, scope_type: str, cls: type):
"""
:param scope_type: a str like "AND", "OR", "NOT"
:param cls: the class to instantiate, e.g. RulePremiseSubScopeCM
"""
assert issubclass(cls, ScopingCM) or (cls == ScopingCM)
if isinstance(self.valid_subscope_types, dict):
# assume that this is a dict mapping types to maximum number of such subscopes
try:
max_subscopes_of_this_type = self.valid_subscope_types[scope_type]
except KeyError:
msg = f"subscope of {scope_type} is not allowed in scope {self.scope}"
raise core.aux.InvalidScopeTypeError(msg)
all_sub_scopes = self.scope.get_inv_relations("R21__is_scope_of", return_subj=True)
matching_type_sub_scopes = [scp for scp in all_sub_scopes if scp.R64__has_scope_type == scope_type]
n = len(matching_type_sub_scopes)
if n >= max_subscopes_of_this_type:
msg = (
f"There already exists {n} subscope(s) of type {scope_type} for scope {self.scope}. "
"More are not allowed."
)
raise core.aux.InvalidScopeTypeError(msg)
if max_subscopes_of_this_type == 1:
name = scope_type
else:
# e.g. we allow multiple AND-subscopes
name = f"{scope_type}{n}"
namespace, scope = self.scope._register_scope(name, scope_type)
cm = cls(itm=self.item, namespace=namespace, scope=scope, parent_scope_cm=self)
cm.scope_type = scope_type
return cm
def copy_from(self, other_obj: Item, scope_name: str = None):
assert isinstance(other_obj, Item)
if scope_name is None:
other_scope = other_obj
assert other_scope.R4 is I16["scope"]
else:
assert isinstance(scope_name, str)
other_scope = other_obj.get_subscope(scope_name)
statements = other_scope.get_inv_relations("R20__has_defining_scope")
var_definitions = []
relation_stms = []
for stm in statements:
if isinstance(stm, core.QualifierStatement):
assert isinstance(stm.subject, core.Statement)
relation_stms.append(stm.subject)
elif isinstance(stm, core.Statement):
var_definitions.append(stm.subject)
if other_scope.R64__has_scope_type in ("PREMISE", "ASSERTION"):
pass
# create variables
for var_item in var_definitions:
name = var_item.R23__has_name_in_scope
class_item = var_item.R4__is_instance_of
# ensure that this variable was created with instance_of
assert is_generic_instance(var_item)
if var_item.R35__is_applied_mapping_of:
new_var_item = self._copy_mapping(var_item)
else:
new_var_item = self._new_var(
variable_name=name, variable_object=instance_of(class_item, r1=name)
)
# to keep track of which old variables correspond to which new ones
ds.scope_var_mappings[(self.scope.uri, var_item.uri)] = new_var_item
# create relations
stm: core.Statement
for stm in relation_stms:
subj, pred, obj = stm.relation_tuple
new_subj = self._get_new_var_from_old(subj)
new_obj = self._get_new_var_from_old(obj)
# TODO: handle qualifiers and overwrite flag
try:
self.new_rel(new_subj, pred, new_obj)
except core.aux.FunctionalRelationError:
if new_subj.R35__is_applied_mapping_of is not None:
res = new_subj.overwrite_statement(pred.uri, obj)
else:
raise
except:
raise
# TODO: handle ImplicationStatement (see test_c07c__scope_copying)
def _get_new_var_from_old(self, old_var: Item, strict=False) -> Item:
if isinstance(old_var, core.allowed_literal_types):
return old_var
assert isinstance(old_var, Item)
# 1st try: vars created in this scope
new_var = ds.scope_var_mappings.get((self.scope.uri, old_var.uri))
if new_var is not None:
return new_var
# 2nd try: vars created in the setting scope
this_scope_parent = self.scope.R21__is_scope_of
all_scopes = this_scope_parent.get_inv_relations("R21__is_scope_of", return_subj=True)
setting_scopes = [scp for scp in all_scopes if scp.R64__has_scope_type == "SETTING"]
assert len(setting_scopes) == 1
setting_scope = setting_scopes[0]
new_var = ds.scope_var_mappings.get((setting_scope.uri, old_var.uri))
if new_var is not None:
return new_var
# TODO: look in the premise scope?
if strict:
msg = f"Unexpected: Could not find a copied item associated to {old_var}"
raise core.aux.GeneralPyIRKError(msg)
# last resort return the original variable (because it was an external var)
return old_var
def _copy_mapping(self, mapping_item: Item) -> Item:
mapping_type = mapping_item.R35__is_applied_mapping_of
assert mapping_type is not None
name = mapping_item.R23__has_name_in_scope
assert name is not None
try:
args = mapping_item.get_arguments()
except AttributeError:
args = ()
new_args = (self._get_new_var_from_old(arg, strict=True) for arg in args)
new_mapping_item = mapping_type(*new_args)
# TODO: add R20__has_defining_scope and R23__has_name_in_scope
self._new_var(variable_name=name, variable_object=new_mapping_item)
return new_mapping_item
def _get_premise_vars(self) -> dict:
"""
return a dict of all items that were defined in the associated setting scope.
key: variable names (via R23__has_name_in_scope)
value: item objects
"""
this_scope_parent = self.scope.R21__is_scope_of
all_scopes = this_scope_parent.get_inv_relations("R21__is_scope_of", return_subj=True)
setting_scopes = [scp for scp in all_scopes if scp.R64__has_scope_type == "SETTING"]
assert len(setting_scopes) == 1
setting_scope = setting_scopes[0]
defined_items = setting_scope.get_inv_relations("R20__has_defining_scope")
settings_vars_mapping = dict(
(stm.subject.R23__has_name_in_scope, stm.subject) for stm in defined_items
)
return settings_vars_mapping
def is_generic_instance(itm: Item) -> bool:
# TODO: make this more robust
return itm.short_key[1] == "a"
class AbstractMathRelatedScopeCM(ScopingCM):
"""
Context manager containing methods which are math-related
"""
def new_equation(self, lhs: Item, rhs: Item, force_key: str = None) -> Item:
"""
convenience method to create a equation-related Statement
:param lhs:
:param rhs:
:return:
"""
# prevent accidental identity of both sides of the equation
assert lhs is not rhs
eq = new_equation(lhs, rhs, scope=self.scope, force_key=force_key)
return eq
# TODO: this makes self.new_equation obsolete, doesn't it?
def new_math_relation(self, lhs: Item, rsgn: str, rhs: Item, force_key: str = None) -> Item:
"""
convenience method to create a math_relation-related StatementObject (aka "Statement")