├── .imgs ├── unnamed_classic_ps.png ├── unnamed_classic_proof.png ├── named_opetopicset_final.png └── named_opetopicset_graphical.png ├── requirements.txt ├── .readthedocs.yml ├── tests ├── test_namedopetope_point.py ├── test_unnamedopetope_opetopicinteger.py ├── test_unnamedopetope_arrow.py ├── test_unnamedopetope_classic.py ├── test_namedopetope_classic.py ├── test_unnamedopetopicset_arrow.py ├── test_unnamedopetope_decision_valid.py ├── test_namedopetopicsetm_example.py ├── test_namedopetopicset_example.py ├── test_unnamedopetopiccategory_tuniv.py ├── test_unnamedopetopiccategory_tclose.py ├── test_unnamedopetopiccategory_tfill.py ├── test_unnamedopetopicset_classic.py ├── test_unnamedopetopiccategory_suniv.py ├── unittest_namedopetopicsetm.py ├── unittest_namedopetopicset.py ├── unittest_unnamedopetopicset.py ├── unittest_unnamedopetope.py └── unittest_namedopetope.py ├── docs ├── common.rst ├── namedopetopicset.rst ├── namedopetopicsetm.rst ├── namedopetope.rst ├── unnamedopetopicset.rst ├── unnamedopetope.rst ├── unnamedopetopiccategory.rst ├── conf.py └── index.rst ├── opetopy ├── __init__.py ├── common.py ├── NamedOpetopicSet.py ├── NamedOpetopicSetM.py └── UnnamedOpetopicCategory.py ├── .coveragerc ├── LICENSE ├── Makefile ├── .gitignore └── README.md /.imgs/unnamed_classic_ps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altaris/opetopy/HEAD/.imgs/unnamed_classic_ps.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | coverage 2 | coveralls 3 | mypy 4 | pylint 5 | sphinx 6 | sphinxcontrib-napoleon 7 | yapf -------------------------------------------------------------------------------- /.imgs/unnamed_classic_proof.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altaris/opetopy/HEAD/.imgs/unnamed_classic_proof.png -------------------------------------------------------------------------------- /.imgs/named_opetopicset_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altaris/opetopy/HEAD/.imgs/named_opetopicset_final.png -------------------------------------------------------------------------------- /.imgs/named_opetopicset_graphical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altaris/opetopy/HEAD/.imgs/named_opetopicset_graphical.png -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | build: 2 | image: latest 3 | 4 | formats: [] 5 | 6 | python: 7 | version: 3.8 8 | 9 | requirements_file: requirements.txt -------------------------------------------------------------------------------- /tests/test_namedopetope_point.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(0, "../") 3 | 4 | from opetopy.NamedOpetope import Point 5 | 6 | pt = Point("x") 7 | print(pt.eval()) 8 | -------------------------------------------------------------------------------- /docs/common.rst: -------------------------------------------------------------------------------- 1 | Common 2 | ****** 3 | 4 | 5 | Documentation 6 | ============= 7 | 8 | 9 | .. automodule:: opetopy.common 10 | :members: 11 | :private-members: 12 | :special-members: 13 | -------------------------------------------------------------------------------- /tests/test_unnamedopetope_opetopicinteger.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(0, "../") 3 | 4 | from opetopy.UnnamedOpetope import OpetopicInteger 5 | 6 | oi5 = OpetopicInteger(5) 7 | 8 | print(oi5.eval()) 9 | -------------------------------------------------------------------------------- /tests/test_unnamedopetope_arrow.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(0, "../") 3 | 4 | from opetopy.UnnamedOpetope import Arrow, Point, Shift 5 | 6 | ar = Shift(Point()) 7 | 8 | # Faster way: 9 | # >>> ar = Arrow() 10 | 11 | print(ar.eval()) 12 | -------------------------------------------------------------------------------- /opetopy/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # from . import common 4 | # from . import NamedOpetope 5 | # from . import NamedOpetopicSet 6 | # from . import NamedOpetopicSetM 7 | # from . import UnnamedOpetope 8 | # from . import UnnamedOpetopicCategory 9 | # from . import UnnamedOpetopicSet 10 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [html] 2 | title = opetopy coverage report 3 | 4 | [report] 5 | exclude_lines = 6 | pragma: no cover 7 | raise RuntimeError 8 | raise NotImplementedError 9 | __repr__ 10 | __str__ 11 | toTex 12 | _toTex 13 | omit = 14 | tests/*.py 15 | 16 | [run] 17 | branch = True -------------------------------------------------------------------------------- /tests/test_unnamedopetope_classic.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(0, "../") 3 | 4 | from opetopy.UnnamedOpetope import address, Graft, OpetopicInteger, OpetopicTree, Shift 5 | 6 | classic = Graft( 7 | Shift(OpetopicInteger(2)), 8 | OpetopicInteger(2), 9 | address([['*']]) 10 | ) 11 | # Faster way: 12 | # >>> classic = OpetopicTree([None, [None, None]]) 13 | 14 | print(classic.eval()) 15 | -------------------------------------------------------------------------------- /tests/test_namedopetope_classic.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(0, "../") 3 | 4 | from opetopy.NamedOpetope import Graft, Point, Shift 5 | 6 | beta = Shift(Graft( 7 | Shift(Point("c"), "h"), 8 | Shift(Point("a"), "i"), 9 | "c"), 10 | "β") 11 | alpha = Shift(Graft( 12 | Shift(Point("b"), "g"), 13 | Shift(Point("a"), "f"), 14 | "b"), 15 | "α") 16 | classic = Shift(Graft(beta, alpha, "i"), "A") 17 | 18 | print(classic.eval()) 19 | -------------------------------------------------------------------------------- /tests/test_unnamedopetopicset_arrow.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(0, "../") 3 | 4 | from opetopy.UnnamedOpetopicSet import Graft, pastingDiagram, Point, \ 5 | RuleInstance, Shift 6 | from opetopy.UnnamedOpetope import address, Arrow 7 | 8 | ar = Point(None, "a") # type: RuleInstance 9 | ar = Point(ar, "b") 10 | ar = Graft( 11 | ar, pastingDiagram( 12 | Arrow(), 13 | { 14 | address([], 0): "a" 15 | })) 16 | ar = Shift(ar, "b", "f") 17 | 18 | print(ar.eval()) 19 | -------------------------------------------------------------------------------- /docs/namedopetopicset.rst: -------------------------------------------------------------------------------- 1 | Named Opetopic Sets 2 | ******************* 3 | 4 | 5 | Example 6 | ======== 7 | 8 | 9 | .. literalinclude:: ../tests/test_namedopetopicset_example.py 10 | :language: python 11 | :linenos: 12 | :lines: 4- 13 | 14 | .. literalinclude:: ../out/tests/test_namedopetopicset_example.out 15 | :linenos: 16 | 17 | 18 | Documentation 19 | ============= 20 | 21 | 22 | .. automodule:: opetopy.NamedOpetopicSet 23 | :members: 24 | :private-members: 25 | :special-members: 26 | -------------------------------------------------------------------------------- /tests/test_unnamedopetope_decision_valid.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(0, "../") 3 | 4 | from opetopy.UnnamedOpetope import address, ProofTree 5 | 6 | p = ProofTree({ 7 | address([], 2): { 8 | address([], 1): { 9 | address('*'): {} # {} represents the point 10 | }, 11 | address(['*']): { 12 | address('*'): {} 13 | } 14 | }, 15 | address([['*']]): { 16 | None: {} # indicates a degeneracy 17 | }}) 18 | 19 | print(p) 20 | print() 21 | print(p.eval()) 22 | -------------------------------------------------------------------------------- /docs/namedopetopicsetm.rst: -------------------------------------------------------------------------------- 1 | Named Opetopic Sets, mixed approach 2 | *********************************** 3 | 4 | 5 | Example 6 | ======== 7 | 8 | 9 | .. literalinclude:: ../tests/test_namedopetopicsetm_example.py 10 | :language: python 11 | :linenos: 12 | :lines: 4- 13 | 14 | .. literalinclude:: ../out/tests/test_namedopetopicsetm_example.out 15 | :linenos: 16 | 17 | 18 | Documentation 19 | ============= 20 | 21 | 22 | .. automodule:: opetopy.NamedOpetopicSetM 23 | :members: 24 | :private-members: 25 | :special-members: 26 | -------------------------------------------------------------------------------- /tests/test_namedopetopicsetm_example.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(0, "../") 3 | 4 | from opetopy.NamedOpetopicSetM import Glue, Pd, Point, RuleInstance, Shift, Sum 5 | 6 | p1 = Shift(Pd(Point("a"), "a"), "f") 7 | p1 = Shift(Pd(p1, "a"), "g") 8 | 9 | p2 = Shift(Pd(Point("b"), "b"), "h") 10 | 11 | example = Sum(p1, p2) # type: RuleInstance 12 | example = Glue(example, "b", "tf") 13 | example = Glue(example, "b", "tg") 14 | example = Shift(Pd(example, "f"), "ɑ") 15 | example = Glue(example, "b", "ttɑ") 16 | example = Glue(example, "g", "tɑ") 17 | example = Glue(example, "a", "th") 18 | 19 | print(example.eval()) 20 | -------------------------------------------------------------------------------- /tests/test_namedopetopicset_example.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(0, "../") 3 | 4 | from opetopy.NamedOpetope import Point, Shift 5 | from opetopy.NamedOpetopicSet import Glue, Repr, Sum 6 | 7 | alpha = Shift(Shift(Point("a"), "f"), "α") 8 | g = Shift(Point("c"), "g") 9 | h = Shift(Point("b"), "h") 10 | unglued = Sum(Sum(Repr(alpha), Repr(g)), Repr(h)) 11 | example = Glue(Glue(Glue(Glue(Glue(unglued, 12 | "a", 13 | "c"), 14 | "b", 15 | "tf"), 16 | "b", 17 | "tg"), 18 | "a", 19 | "th"), 20 | "g", 21 | "tα") 22 | 23 | print(example.eval()) 24 | -------------------------------------------------------------------------------- /docs/namedopetope.rst: -------------------------------------------------------------------------------- 1 | Named Opetopes 2 | ************** 3 | 4 | 5 | Examples 6 | ======== 7 | 8 | 9 | The point 10 | --------- 11 | 12 | 13 | .. literalinclude:: ../tests/test_namedopetope_point.py 14 | :language: python 15 | :linenos: 16 | :lines: 4- 17 | 18 | .. literalinclude:: ../out/tests/test_namedopetope_point.out 19 | :linenos: 20 | 21 | 22 | A classic 23 | --------- 24 | 25 | 26 | .. literalinclude:: ../tests/test_namedopetope_classic.py 27 | :language: python 28 | :linenos: 29 | :lines: 4- 30 | 31 | .. literalinclude:: ../out/tests/test_namedopetope_classic.out 32 | :linenos: 33 | 34 | 35 | Documentation 36 | ============= 37 | 38 | 39 | .. automodule:: opetopy.NamedOpetope 40 | :members: 41 | :private-members: 42 | :special-members: 43 | -------------------------------------------------------------------------------- /tests/test_unnamedopetopiccategory_tuniv.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(0, "../") 3 | 4 | from opetopy.UnnamedOpetope import address, Arrow 5 | from opetopy.UnnamedOpetopicSet import Degen, Graft, pastingDiagram, Point, \ 6 | RuleInstance, Shift 7 | from opetopy.UnnamedOpetopicCategory import TUniv, TFill 8 | 9 | # Derive a cell degenerate at point a 10 | proof = Point(None, "a") # type: RuleInstance 11 | proof = Graft(proof, pastingDiagram(Arrow(), {address('*'): "a"})) 12 | proof = Shift(proof, "a", "f") 13 | proof = Degen(proof, "a") 14 | proof = Shift(proof, "f", "δ") 15 | 16 | # Shift the empty pasting diagram at a 17 | proof = Degen(proof, "a") 18 | proof = TFill(proof, "g", "γ") 19 | 20 | # Apply the target universal property of γ 21 | proof = TUniv(proof, "γ", "δ", "ξ", "A") 22 | 23 | print(proof.eval()) 24 | -------------------------------------------------------------------------------- /tests/test_unnamedopetopiccategory_tclose.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(0, "../") 3 | 4 | from opetopy.UnnamedOpetope import address, Arrow, OpetopicInteger 5 | from opetopy.UnnamedOpetopicSet import Graft, pastingDiagram, Point, \ 6 | RuleInstance 7 | from opetopy.UnnamedOpetopicCategory import TClose, TFill 8 | 9 | proof = Point(None, "a") # type: RuleInstance 10 | 11 | # Derive a target universal arrow f of source a 12 | proof = Graft(proof, pastingDiagram(Arrow(), {address('*'): "a"})) 13 | proof = TFill(proof, "b", "f") 14 | 15 | # Derive a target universal arrow g of source b 16 | proof = Graft(proof, pastingDiagram(Arrow(), {address('*'): "b"})) 17 | proof = TFill(proof, "c", "g") 18 | 19 | # Compose f and g 20 | proof = Graft( 21 | proof, pastingDiagram( 22 | OpetopicInteger(2), 23 | { 24 | address([], 1): "g", 25 | address(['*']): "f" 26 | })) 27 | proof = TFill(proof, "h", "α") 28 | 29 | # Apply target universality closure 30 | proof = TClose(proof, "α") 31 | 32 | print(proof.eval()) 33 | -------------------------------------------------------------------------------- /tests/test_unnamedopetopiccategory_tfill.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(0, "../") 3 | 4 | from opetopy.UnnamedOpetope import address, Arrow, OpetopicInteger 5 | from opetopy.UnnamedOpetopicSet import Graft, pastingDiagram, Point, \ 6 | RuleInstance, Shift 7 | from opetopy.UnnamedOpetopicCategory import TFill 8 | 9 | # Derive points 10 | proof = Point(None, ["a", "b", "c"]) # type: RuleInstance 11 | 12 | # Derive f 13 | proof = Graft( 14 | proof, pastingDiagram( 15 | Arrow(), 16 | { 17 | address('*'): "a" 18 | })) 19 | proof = Shift(proof, "b", "f") 20 | 21 | # Derive g 22 | proof = Graft( 23 | proof, pastingDiagram( 24 | Arrow(), 25 | { 26 | address('*'): "b" 27 | })) 28 | proof = Shift(proof, "c", "g") 29 | 30 | # Derive the composition cells 31 | proof = Graft( 32 | proof, pastingDiagram( 33 | OpetopicInteger(2), 34 | { 35 | address([], 1): "g", 36 | address(['*']): "f" 37 | })) 38 | proof = TFill(proof, "h", "α") 39 | 40 | print(proof.eval()) 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Cédric HT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/unnamedopetopicset.rst: -------------------------------------------------------------------------------- 1 | Unnamed Opetopic Sets 2 | ********************* 3 | 4 | 5 | Examples 6 | ======== 7 | 8 | 9 | The arrow 10 | --------- 11 | 12 | 13 | .. literalinclude:: ../tests/test_unnamedopetopicset_arrow.py 14 | :language: python 15 | :linenos: 16 | :lines: 4- 17 | 18 | .. literalinclude:: ../out/tests/test_unnamedopetopicset_arrow.out 19 | :linenos: 20 | 21 | 22 | A classic, maximally folded 23 | --------------------------- 24 | 25 | We start by deriving in :math:`\textbf{Opt${}^?$}` (see :mod:`UnnamedOpetope`) the opetope :math:`\omega = \mathsf{Y}_{\mathbf{2}} \circ_{[[*]]} \mathsf{Y}_{\mathbf{2}}` describing the shape of the maximal cell :math:`A`. We then proceed to derive the opetopic set in :math:`\textbf{OptSet${}^?$}`. 26 | 27 | .. literalinclude:: ../tests/test_unnamedopetopicset_classic.py 28 | :language: python 29 | :linenos: 30 | :lines: 4- 31 | 32 | .. literalinclude:: ../out/tests/test_unnamedopetopicset_classic.out 33 | :linenos: 34 | 35 | 36 | Documentation 37 | ============= 38 | 39 | .. automodule:: opetopy.UnnamedOpetopicSet 40 | :members: 41 | :private-members: 42 | :special-members: 43 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PYTHON = python3.8 2 | 3 | DIR_DOCS = docs 4 | DIR_OPETOPY = opetopy 5 | DIR_OUT = out 6 | DIR_OUT_DOCS = $(DIR_OUT)/docs 7 | DIR_OUT_TEST = $(DIR_OUT)/tests 8 | 9 | TESTS = \ 10 | test_namedopetope_classic \ 11 | test_namedopetope_point \ 12 | test_namedopetopicset_example \ 13 | test_namedopetopicsetm_example \ 14 | test_unnamedopetope_arrow \ 15 | test_unnamedopetope_classic \ 16 | test_unnamedopetope_decision_valid \ 17 | test_unnamedopetope_opetopicinteger \ 18 | test_unnamedopetopiccategory_suniv \ 19 | test_unnamedopetopiccategory_tclose \ 20 | test_unnamedopetopiccategory_tfill \ 21 | test_unnamedopetopiccategory_tuniv \ 22 | test_unnamedopetopicset_arrow \ 23 | test_unnamedopetopicset_classic 24 | 25 | all: typecheck unittest 26 | 27 | .PHONY: docs 28 | docs: tests 29 | sphinx-build -b html $(DIR_DOCS)/ $(DIR_OUT_DOCS)/html/ 30 | -@xdg-open $(DIR_OUT_DOCS)/html/index.html 31 | 32 | .PHONY: format 33 | format: 34 | yapf --in-place --recursive --style pep8 --verbose $(DIR_OPETOPY) 35 | 36 | test_%: 37 | @mkdir -p $(DIR_OUT_TEST) 38 | $(PYTHON) -m tests.$@ > $(DIR_OUT_TEST)/$@.out 39 | 40 | tests: $(TESTS) 41 | 42 | .PHONY: typecheck 43 | typecheck: 44 | mypy opetopy/*.py 45 | 46 | .PHONY: unittest 47 | unittest: 48 | @$(PYTHON) -m unittest discover --start-directory tests \ 49 | --pattern "unittest*.py" --verbose 50 | -------------------------------------------------------------------------------- /opetopy/common.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | .. module:: common 4 | :synopsis: Some utilities and global definitions 5 | 6 | .. moduleauthor:: Cédric HT 7 | 8 | """ 9 | 10 | from typing import Any 11 | 12 | 13 | class AbstractRuleInstance: 14 | """ 15 | Abstract class representing a rule instance in a proof tree. 16 | """ 17 | def _toTex(self) -> str: 18 | raise NotImplementedError() 19 | 20 | def eval(self) -> Any: 21 | """ 22 | Pure virtual method evaluating a proof tree and returning the final 23 | conclusion sequent, or raising an exception if the proof is invalid. 24 | """ 25 | raise NotImplementedError() 26 | 27 | def toTex(self) -> str: 28 | """ 29 | Converts the proof tree in TeX code. 30 | """ 31 | return "\\begin{prooftree}\n\t" + self._toTex() + "\n\\end{prooftree}" 32 | 33 | 34 | class DerivationError(Exception): 35 | """ 36 | This exception is raised whenever an illegal operation on syntactical 37 | constructs relevant to opetopes is performed. 38 | """ 39 | def __init__(self, scope: str, message: str, **kwargs) -> None: 40 | self.message = message.format(**kwargs) 41 | self.scope = scope 42 | super().__init__(self, message) 43 | 44 | def __str__(self): 45 | return "[{scope}] {msg}".format(scope=self.scope, msg=self.message) 46 | -------------------------------------------------------------------------------- /tests/test_unnamedopetopicset_classic.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(0, "../") 3 | 4 | from opetopy.UnnamedOpetopicSet import Graft, pastingDiagram, Point, \ 5 | RuleInstance, Shift 6 | from opetopy.UnnamedOpetope import address, Arrow, OpetopicInteger, \ 7 | OpetopicTree 8 | from opetopy.UnnamedOpetope import Graft as OptGraft 9 | from opetopy.UnnamedOpetope import Shift as OptShift 10 | 11 | # Derivation of ω 12 | omega = OptGraft( 13 | OptShift(OpetopicInteger(2)), 14 | OpetopicInteger(2), 15 | address([['*']])) 16 | 17 | # Faster way: 18 | # >>> omega = OpetopicTree([None, [None, None]]) 19 | 20 | # Derivation of a 21 | classic = Point(None, "a") # type: RuleInstance 22 | 23 | # Derivation of f 24 | classic = Graft( 25 | classic, 26 | pastingDiagram( 27 | Arrow(), 28 | { 29 | address([], 0): "a" 30 | })) 31 | classic = Shift(classic, "a", "f") 32 | 33 | # Derivation of α 34 | classic = Graft( 35 | classic, 36 | pastingDiagram( 37 | OpetopicInteger(2), 38 | { 39 | address([], 1): "f", 40 | address(['*']): "f" 41 | })) 42 | classic = Shift(classic, "f", "α") 43 | 44 | # Derivation of β 45 | classic = Graft( 46 | classic, 47 | pastingDiagram( 48 | OpetopicInteger(3), 49 | { 50 | address([], 1): "f", 51 | address(['*']): "f", 52 | address(['*', '*']): "f" 53 | })) 54 | classic = Shift(classic, "f", "β") 55 | 56 | # Derivation of A 57 | classic = Graft( 58 | classic, 59 | pastingDiagram( 60 | omega, 61 | { 62 | address([], 2): "α", 63 | address([['*']]): "α" 64 | })) 65 | classic = Shift(classic, "β", "A") 66 | 67 | print(classic.eval()) 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Outputs 2 | out/ 3 | 4 | # VSCode 5 | *.code-workspace 6 | .vscode/ 7 | 8 | # Byte-compiled / optimized / DLL files 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | 13 | # C extensions 14 | *.so 15 | 16 | # Distribution / packaging 17 | .Python 18 | env/ 19 | build/ 20 | develop-eggs/ 21 | dist/ 22 | downloads/ 23 | eggs/ 24 | .eggs/ 25 | lib/ 26 | lib64/ 27 | parts/ 28 | sdist/ 29 | var/ 30 | wheels/ 31 | *.egg-info/ 32 | .installed.cfg 33 | *.egg 34 | 35 | # PyInstaller 36 | # Usually these files are written by a python script from a template 37 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 38 | *.manifest 39 | *.spec 40 | 41 | # Installer logs 42 | pip-log.txt 43 | pip-delete-this-directory.txt 44 | 45 | # Unit test / coverage reports 46 | htmlcov/ 47 | .tox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | .hypothesis/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # pyenv 81 | .python-version 82 | 83 | # celery beat schedule file 84 | celerybeat-schedule 85 | 86 | # SageMath parsed files 87 | *.sage.py 88 | 89 | # dotenv 90 | .env 91 | 92 | # virtualenv 93 | .venv 94 | venv/ 95 | ENV/ 96 | 97 | # Spyder project settings 98 | .spyderproject 99 | .spyproject 100 | 101 | # Rope project settings 102 | .ropeproject 103 | 104 | # mkdocs documentation 105 | /site 106 | 107 | # mypy 108 | .mypy_cache/ 109 | -------------------------------------------------------------------------------- /tests/test_unnamedopetopiccategory_suniv.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.insert(0, "../") 3 | 4 | from opetopy.UnnamedOpetope import address, Arrow, OpetopicInteger 5 | from opetopy.UnnamedOpetopicSet import Graft, pastingDiagram, Point, \ 6 | RuleInstance, Shift 7 | from opetopy.UnnamedOpetopicCategory import TUniv, SUniv, TFill 8 | 9 | # Derive points 10 | proof = Point(None, ["a", "b", "c"]) # type: RuleInstance 11 | 12 | # Derive f 13 | proof = Graft( 14 | proof, pastingDiagram( 15 | Arrow(), 16 | { 17 | address('*'): "a" 18 | })) 19 | proof = Shift(proof, "b", "f") 20 | 21 | # Derive g 22 | proof = Graft( 23 | proof, pastingDiagram( 24 | Arrow(), 25 | { 26 | address('*'): "b" 27 | })) 28 | proof = Shift(proof, "c", "g") 29 | 30 | # Derive the composition cells 31 | proof = Graft( 32 | proof, pastingDiagram( 33 | OpetopicInteger(2), 34 | { 35 | address([], 1): "g", 36 | address(['*']): "f" 37 | })) 38 | proof = TFill(proof, "h", "α") 39 | 40 | # Derive i, parallel to h 41 | proof = Graft( 42 | proof, pastingDiagram( 43 | Arrow(), 44 | { 45 | address('*'): "a" 46 | })) 47 | proof = Shift(proof, "c", "i") 48 | 49 | # Derive β 50 | proof = Graft( 51 | proof, pastingDiagram( 52 | OpetopicInteger(2), 53 | { 54 | address([], 1): "g", 55 | address(['*']): "f" 56 | })) 57 | proof = Shift(proof, "i", "β") 58 | 59 | # Apply target universality of α over β 60 | proof = TUniv(proof, "α", "β", "ξ", "A") 61 | 62 | # Again 63 | proof = TUniv(proof, "α", "β", "ζ", "B") 64 | 65 | # Apply source universality of A over B 66 | proof = SUniv(proof, "A", "B", address([], 2), "C", "Ψ") 67 | 68 | print(proof.eval()) 69 | -------------------------------------------------------------------------------- /tests/unittest_namedopetopicsetm.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import sys 4 | sys.path.insert(0, "../") 5 | 6 | from opetopy.common import DerivationError 7 | 8 | from opetopy import NamedOpetope 9 | from opetopy import NamedOpetopicSetM 10 | 11 | 12 | class Test_NamedOpetopicSetM_InferenceRules(unittest.TestCase): 13 | 14 | def setUp(self): 15 | pass 16 | 17 | def test_point(self): 18 | s = NamedOpetopicSetM.point("x") 19 | self.assertTrue(isinstance(s, NamedOpetope.OCMT)) 20 | self.assertEqual(len(s.context), 1) 21 | self.assertEqual(len(s.theory.classes), 0) 22 | 23 | def test_degen(self): 24 | s = NamedOpetopicSetM.point("x") 25 | with self.assertRaises(DerivationError): 26 | NamedOpetopicSetM.degen(s, "y") 27 | s = NamedOpetopicSetM.degen(s, "x") 28 | self.assertTrue(isinstance(s, NamedOpetope.Sequent)) 29 | self.assertEqual(len(s.context), 1) 30 | self.assertEqual(len(s.theory.classes), 0) 31 | self.assertEqual( 32 | s.typing.term, NamedOpetope.Term( 33 | NamedOpetope.Variable("x", 0), True)) 34 | 35 | def test_pd(self): 36 | s = NamedOpetopicSetM.point("x") 37 | with self.assertRaises(DerivationError): 38 | NamedOpetopicSetM.pd(s, "y") 39 | s = NamedOpetopicSetM.pd(s, "x") 40 | self.assertTrue(isinstance(s, NamedOpetope.Sequent)) 41 | self.assertEqual(len(s.context), 1) 42 | self.assertEqual(len(s.theory.classes), 0) 43 | self.assertEqual( 44 | s.typing.term, NamedOpetope.Term( 45 | NamedOpetope.Variable("x", 0), False)) 46 | 47 | def test_graft(self): 48 | pass 49 | 50 | def test_shift(self): 51 | s = NamedOpetopicSetM.Shift( 52 | NamedOpetopicSetM.Pd( 53 | NamedOpetopicSetM.Point("x"), 54 | "x"), 55 | "f").eval() 56 | self.assertTrue(isinstance(s, NamedOpetope.OCMT)) 57 | self.assertEqual(len(s.context), 3) 58 | self.assertEqual(len(s.theory.classes), 0) 59 | 60 | def test_zero(self): 61 | pass 62 | 63 | def test_sum(self): 64 | pass 65 | 66 | def test_glue(self): 67 | pass 68 | 69 | 70 | if __name__ == "__main__": 71 | unittest.main(verbosity = 2) 72 | -------------------------------------------------------------------------------- /docs/unnamedopetope.rst: -------------------------------------------------------------------------------- 1 | Unnamed Opetopes 2 | **************** 3 | 4 | 5 | In addition to the :math:`\textbf{Opt${}^?$}` derivation rules and their proof tree node counterparts, the following two functions are present: 6 | 7 | * :func:`opetopy.UnnamedOpetope.Arrow`: returns the proof tree of the arrow; 8 | * :func:`opetopy.UnnamedOpetope.OpetopicInteger`: returns the proof tree of an opetopic integer (i.e. :math:`2`-opetope). 9 | 10 | 11 | Examples 12 | ======== 13 | 14 | 15 | The arrow 16 | --------- 17 | 18 | 19 | .. literalinclude:: ../tests/test_unnamedopetope_arrow.py 20 | :language: python 21 | :linenos: 22 | :lines: 4- 23 | 24 | .. literalinclude:: ../out/tests/test_unnamedopetope_arrow.out 25 | :linenos: 26 | 27 | 28 | A classic 29 | --------- 30 | 31 | 32 | .. literalinclude:: ../tests/test_unnamedopetope_classic.py 33 | :language: python 34 | :linenos: 35 | :lines: 4- 36 | 37 | .. literalinclude:: ../out/tests/test_unnamedopetope_classic.out 38 | :linenos: 39 | 40 | 41 | Opetopic integers 42 | ----------------- 43 | 44 | 45 | Recall that for :math:`n \in \mathbb{N}`: 46 | 47 | * if :math:`n = 0`, then :math:`\mathbf{n} = \{ \{ \blacklozenge`; 48 | * if :math:`n = 1`, then :math:`\mathbf{n} = \{ [] \leftarrow \blacksquare`; 49 | * otherwise, :math:`\mathbf{n} = \mathbf{(n-1)} \circ_{[* \cdots *]} \blacksquare`, where there is :math:`(n-1)` instances of :math:`*` in the grafting address. 50 | 51 | This is exactly the implementation of :func:`opetopy.UnnamedOpetope.OpetopicInteger`. 52 | 53 | .. literalinclude:: ../tests/test_unnamedopetope_opetopicinteger.py 54 | :language: python 55 | :linenos: 56 | 57 | .. literalinclude:: ../out/tests/test_unnamedopetope_opetopicinteger.out 58 | :linenos: 59 | 60 | 61 | Deciding opetopes 62 | ----------------- 63 | 64 | 65 | The :func:`opetopy.UnnamedOpetope.ProofTree` effectively decides opetopes among 66 | preopetopes. It takes as argument a preopetopes in "convenient form" (see 67 | examples), and returns its proof tree if it is an opetope, or raises an 68 | exception otherwise. 69 | 70 | 71 | Here, we construct the proof tree of :math:`\mathsf{Y}_{\mathbf{2}} 72 | \circ_{[[*]]} \mathsf{Y}_{\mathbf{0}}` 73 | 74 | .. literalinclude:: ../tests/test_unnamedopetope_decision_valid.py 75 | :language: python 76 | :linenos: 77 | :lines: 4- 78 | 79 | .. literalinclude:: ../out/tests/test_unnamedopetope_decision_valid.out 80 | :linenos: 81 | 82 | Here, we try to construct the proof tree of the invalid :math:`\mathsf{Y}_{\mathbf{1}} \circ_{[[***]]} \mathsf{Y}_{\mathbf{1}}` 83 | 84 | .. code-block:: python 85 | :linenos: 86 | 87 | from UnnamedOpetope import address, ProofTree 88 | 89 | ProofTree({ 90 | address([], 2): { 91 | address([], 1): { 92 | address('*'): {} 93 | } 94 | }, 95 | address([['*', '*', '*']]): { 96 | address([], 1): { 97 | address('*'): {} 98 | } 99 | }}) 100 | 101 | which raises an exception. 102 | 103 | 104 | Documentation 105 | ============= 106 | 107 | 108 | .. automodule:: opetopy.UnnamedOpetope 109 | :members: 110 | :private-members: 111 | :special-members: 112 | -------------------------------------------------------------------------------- /docs/unnamedopetopiccategory.rst: -------------------------------------------------------------------------------- 1 | Opetopic categories: unnamed approach 2 | ************************************* 3 | 4 | 5 | This part of ``opetopy`` implements finite opetopic categories as defined in [Baez1998]_ and [Finster]_. 6 | 7 | .. [Baez1998] Baez, John C. and Dolan, James. Higher-dimensional algebra III: :math:`n`-categories and the algebra of opetopes. 8 | .. [Finster] Eric Finster. http://opetopic.net . 9 | 10 | 11 | Examples 12 | ======== 13 | 14 | .. _uoptcat-ex-shift-tgt: 15 | 16 | Filler of target horn 17 | --------------------- 18 | 19 | 20 | In this simple example, we use the target filling of a pasting diagram of shape :math:`\mathbf{2}`, effectively composing arrow cells :math:`f` and :math:`g`. The result is the arrow :math:`h`, and the compositor is :math:`\alpha`. 21 | 22 | 23 | .. literalinclude:: ../tests/test_unnamedopetopiccategory_tfill.py 24 | :language: python 25 | :linenos: 26 | :lines: 4- 27 | 28 | .. literalinclude:: ../out/tests/test_unnamedopetopiccategory_tfill.out 29 | :linenos: 30 | 31 | 32 | Target universal property 33 | ------------------------- 34 | 35 | 36 | In this example, we have two cells of shape :math:`\mathbf{0}`: 37 | 38 | * :math:`\delta`, degenerate at :math:`a` with target :math:`f`, constructed via rules :math:`\texttt{degen}` and :math:`\texttt{shift}` of system :math:`\textbf{OptSet${}^?$}`; 39 | * :math:`\gamma`, constructed by filling the empty pasting diagram at :math:`a`, with universal target :math:`g`. 40 | 41 | We then apply the target universal property of :math:`\gamma` over :math:`\delta` to get a factorization cell :math:`\xi` and a filler :math:`A`. 42 | 43 | .. literalinclude:: ../tests/test_unnamedopetopiccategory_tuniv.py 44 | :language: python 45 | :linenos: 46 | :lines: 4- 47 | 48 | .. literalinclude:: ../out/tests/test_unnamedopetopiccategory_tuniv.out 49 | :linenos: 50 | 51 | 52 | Source universal property 53 | ------------------------- 54 | 55 | 56 | This example is a continuation of that presented in :ref:`uoptcat-ex-shift-tgt`. We derive the following additional cells: 57 | 58 | * :math:`i`, parallel to :math:`h` (the composition of arrows :math:`f` and :math:`g`); 59 | * :math:`\beta`, from the :math:`fg` horn to :math:`i`. 60 | 61 | We then apply the target universal property of :math:`\alpha` over :math:`\beta` to obtain a factorization :math:`\xi : h \longrightarrow i` and a filler :math:`A`. We do it again, with a new factorization :math:`\zeta` and filler :math:`B`. Finally, we apply the source univerality of :math:`A` over :math:`B` to obtain a factorization :math:`C : \zeta \longrightarrow \xi`. 62 | 63 | This hints that any two factorization in the application of the target universality of the composition :math:`h` over :math:`i` are homotopic, and in fact, equivalent. 64 | 65 | .. literalinclude:: ../tests/test_unnamedopetopiccategory_suniv.py 66 | :language: python 67 | :linenos: 68 | 69 | .. literalinclude:: ../out/tests/test_unnamedopetopiccategory_suniv.out 70 | :linenos: 71 | 72 | 73 | Target universal closure 74 | ------------------------ 75 | 76 | 77 | In this example, we derive two target universal arrows :math:`f` and :math:`g`, and their composition :math:`h`. We then apply the target universal closure property to prove that :math:`h` is target universal. 78 | 79 | .. literalinclude:: ../tests/test_unnamedopetopiccategory_tclose.py 80 | :language: python 81 | :linenos: 82 | :lines: 4- 83 | 84 | .. literalinclude:: ../out/tests/test_unnamedopetopiccategory_tclose.out 85 | :linenos: 86 | 87 | 88 | Documentation 89 | ============= 90 | 91 | 92 | .. automodule:: opetopy.UnnamedOpetopicCategory 93 | :members: 94 | :private-members: 95 | :special-members: 96 | -------------------------------------------------------------------------------- /tests/unittest_namedopetopicset.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import sys 4 | sys.path.insert(0, "../") 5 | 6 | from opetopy.common import DerivationError 7 | 8 | from opetopy import NamedOpetope 9 | from opetopy import NamedOpetopicSet 10 | 11 | 12 | class Test_NamedOpetopicSet_InferenceRules(unittest.TestCase): 13 | 14 | def setUp(self): 15 | pass 16 | 17 | def test_repres(self): 18 | with self.assertRaises(DerivationError): 19 | NamedOpetopicSet.repres( 20 | NamedOpetope.Degen(NamedOpetope.Point("x")).eval()) 21 | aseq = NamedOpetopicSet.repres(NamedOpetope.Arrow().eval()) 22 | self.assertEqual(len(aseq.context), 3) 23 | self.assertEqual(len(aseq.theory.classes), 0) 24 | self.assertIn(NamedOpetope.Variable("a", 0), aseq.context) 25 | self.assertIn(NamedOpetope.Variable("tf", 0), aseq.context) 26 | self.assertIn(NamedOpetope.Variable("f", 1), aseq.context) 27 | i3seq = NamedOpetopicSet.repres(NamedOpetope.OpetopicInteger(3).eval()) 28 | self.assertEqual(len(i3seq.context), 12) 29 | self.assertEqual(len(i3seq.theory.classes), 3) 30 | self.assertIn(NamedOpetope.Variable("a_1", 0), i3seq.context) 31 | self.assertIn(NamedOpetope.Variable("a_2", 0), i3seq.context) 32 | self.assertIn(NamedOpetope.Variable("a_3", 0), i3seq.context) 33 | self.assertIn(NamedOpetope.Variable("f_1", 1), i3seq.context) 34 | self.assertIn(NamedOpetope.Variable("f_2", 1), i3seq.context) 35 | self.assertIn(NamedOpetope.Variable("f_3", 1), i3seq.context) 36 | self.assertIn(NamedOpetope.Variable("A", 2), i3seq.context) 37 | self.assertTrue(i3seq.theory.equal( 38 | NamedOpetope.Variable("a_1", 0), 39 | NamedOpetope.Variable("tf_2", 0))) 40 | self.assertTrue(i3seq.theory.equal( 41 | NamedOpetope.Variable("a_2", 0), 42 | NamedOpetope.Variable("tf_3", 0))) 43 | self.assertTrue(i3seq.theory.equal( 44 | NamedOpetope.Variable("tf_1", 0), 45 | NamedOpetope.Variable("ttA", 0))) 46 | 47 | def test_sum(self): 48 | a = NamedOpetopicSet.Repr( 49 | NamedOpetope.OpetopicInteger(3, "a", "f", "A")).eval() 50 | b = NamedOpetopicSet.Repr( 51 | NamedOpetope.OpetopicInteger(4, "a", "f", "B")).eval() 52 | c = NamedOpetopicSet.Repr( 53 | NamedOpetope.OpetopicInteger(4, "b", "g", "B")).eval() 54 | with self.assertRaises(DerivationError): 55 | NamedOpetopicSet.sum(a, b) 56 | with self.assertRaises(DerivationError): 57 | NamedOpetopicSet.sum(b, c) 58 | d = NamedOpetopicSet.sum(a, c) 59 | self.assertEqual(len(d.context), len(a.context) + len(c.context)) 60 | self.assertEqual(len(d.theory.classes), 61 | len(a.theory.classes) + len(c.theory.classes)) 62 | 63 | def test_glue(self): 64 | a = NamedOpetopicSet.Repr(NamedOpetope.OpetopicInteger(3)).eval() 65 | with self.assertRaises(DerivationError): 66 | NamedOpetopicSet.glue(a, "a_1", "f_1") 67 | with self.assertRaises(DerivationError): 68 | NamedOpetopicSet.glue(a, "f_1", "f_2") 69 | a = NamedOpetopicSet.glue(a, "a_1", "a_2") 70 | a = NamedOpetopicSet.glue(a, "a_1", "a_3") 71 | a = NamedOpetopicSet.glue(a, "a_1", "ttA") 72 | a = NamedOpetopicSet.glue(a, "f_1", "f_2") 73 | a = NamedOpetopicSet.glue(a, "f_1", "f_3") 74 | a = NamedOpetopicSet.glue(a, "f_1", "tA") 75 | self.assertEqual(len(a.theory.classes), 2) 76 | 77 | def test_zero(self): 78 | pass 79 | 80 | 81 | if __name__ == "__main__": 82 | unittest.main(verbosity = 2) 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | opetopy 2 | ======= 3 | 4 | [![Build status](https://travis-ci.com/altaris/opetopy.svg?branch=master)](https://travis-ci.com/altaris/opetopy) 5 | [![Coverage Status](https://coveralls.io/repos/github/altaris/opetopy/badge.svg?branch=master)](https://coveralls.io/github/altaris/opetopy?branch=master) 6 | [![Documentation](https://readthedocs.org/projects/opetopy/badge/?version=latest)](https://opetopy.readthedocs.io/en/latest/) 7 | ![Python 3](https://badgen.net/badge/Python/3/blue) 8 | [![MIT License](https://badgen.net/badge/license/MIT/blue)](https://choosealicense.com/licenses/mit/) 9 | 10 | This project is the Python implementation and proof of concept of the opetope 11 | derivation systems presented in [**Syntactic approaches to 12 | opetopes**](https://arxiv.org/abs/1903.05848), by [Pierre-Louis 13 | Curien](https://www.irif.fr/~curien/), [Cédric Ho 14 | Thanh](https://hothanh.fr/cedric), and [Samuel 15 | Mimram](http://www.lix.polytechnique.fr/Labo/Samuel.Mimram). It also includes 16 | some other work in progress. 17 | 18 | # Examples 19 | 20 | ## An unnamed opetope 21 | 22 | In system 'Opt?', deriving the 'classic' 3-opetope 23 | 24 | ![A 3 opetope](.imgs/unnamed_classic_ps.png) 25 | 26 | can be done with the following code 27 | ```python 28 | from opetopy.UnnamedOpetope import address, Graft, OpetopicInteger, OpetopicTree, Shift 29 | 30 | proof = Graft( 31 | Shift(OpetopicInteger(2)), 32 | OpetopicInteger(2), 33 | address([['*']]) 34 | result = proof.eval() 35 | ) 36 | ``` 37 | which corresponds to the proof tree 38 | 39 | ![A 3 opetope's proof](.imgs/unnamed_classic_proof.png) 40 | 41 | Since the proof is valid, the code executes without throwing exceptions. To get 42 | the final sequent, simply run 43 | ```python 44 | print(result) 45 | ``` 46 | which outputs 47 | 48 | ``` 49 | ctx = { 50 | [[]] ↦ [] 51 | [[*][]] ↦ [*] 52 | [[*][*]] ↦ [**] 53 | } 54 | src = { 55 | []: { 56 | []: ■ 57 | [*]: ■ 58 | } 59 | [[*]]: { 60 | []: ■ 61 | [*]: ■ 62 | } 63 | } 64 | tgt = { 65 | []: ■ 66 | [*]: ■ 67 | [**]: ■ 68 | } 69 | ``` 70 | 71 | ## A named opetopic set 72 | 73 | The following opetopic set, 74 | 75 | ![An opetopic set](.imgs/named_opetopicset_graphical.png) 76 | 77 | can be derived in 'OptSet!' as follows: 78 | 79 | ```python 80 | from opetopy.NamedOpetope import Point, Shift 81 | from opetopy.NamedOpetopicSet import Glue, Repr, Sum 82 | 83 | # Derive the main cells 84 | alpha = Shift(Shift(Point("a"), "f"), "α") 85 | g = Shift(Point("c"), "g") 86 | h = Shift(Point("b"), "h") 87 | 88 | # Sum them to obtain an 'unfolded' version 89 | unglued = Sum(Sum(Repr(alpha), Repr(g)), Repr(h)) 90 | 91 | # Apply the `glue` rule repeatedly 92 | example = Glue( 93 | Glue( 94 | Glue( 95 | Glue( 96 | Glue( 97 | unglued, "a", "c" # Glue a to c 98 | ), "b", "tf" # Glue b to the target of f 99 | ), "b", "tg" # etc. 100 | ), "a", "th" 101 | ), "g", "tα" 102 | ) 103 | result = example.eval() 104 | 105 | ``` 106 | 107 | The final sequent is 108 | 109 | ![The final OCMT](.imgs/named_opetopicset_final.png) 110 | 111 | which can be retrived by running 112 | 113 | ```python 114 | print(result) 115 | ``` 116 | 117 | ``` 118 | {tg, b, tf, ttα}, {th, a, c}, {tα, g} ▷ a : ∅, g : c ⊷ ∅, α : f ⊷ a ⊷ ∅, f : a ⊷ ∅, tf : ∅, ttα : ∅, th : ∅, c : ∅, tα : a ⊷ ∅, tg : ∅, h : b ⊷ ∅, b : ∅ 119 | ``` 120 | 121 | ## A coherence cell 122 | 123 | We show how the ``tfill`` rule can be applied to derived weak composites of 124 | 1-cells: 125 | 126 | ```python 127 | from opetopy.UnnamedOpetope import address, Arrow, OpetopicInteger 128 | from opetopy.UnnamedOpetopicSet import ( 129 | Graft, pastingDiagram, Point, RuleInstance, Shift) 130 | from opetopy.UnnamedOpetopicCategory import TFill 131 | 132 | # Derive points. 133 | # This convenient syntax allows us to derive multiple points at once. 134 | proof = Point(None, ["a", "b", "c"]) 135 | 136 | # Derive f. 137 | # Recall that in the graft rule, we must derive the shape of the pasting 138 | # diagram in system Opt? beforehand. We use the helper function Arrow() which 139 | # returns the proof tree of an arrow. 140 | proof = Graft( 141 | proof, 142 | pastingDiagram( 143 | Arrow(), 144 | { 145 | address('*'): "a" 146 | } 147 | ) 148 | ) 149 | proof = Shift(proof, "b", "f") 150 | 151 | # Derive g. 152 | proof = Graft( 153 | proof, 154 | pastingDiagram( 155 | Arrow(), 156 | { 157 | address('*'): "b" 158 | } 159 | ) 160 | ) 161 | proof = Shift(proof, "c", "g") 162 | 163 | # Derive the composition and the compositor. 164 | # As for f and g, we need to derive the shape of the compositor α before adding 165 | # it. Here, we use the function OpetopicInteger which returns proof trees for 166 | # opetopic integers. 167 | proof = Graft( 168 | proof, 169 | pastingDiagram( 170 | OpetopicInteger(2), 171 | { 172 | address([], 1): "g", 173 | address(['*']): "f" 174 | } 175 | ) 176 | ) 177 | proof = TFill(proof, "h", "α") 178 | result = proof.eval() 179 | ``` 180 | 181 | The resulting sequent is 182 | 183 | ```python 184 | print(result) 185 | ``` 186 | 187 | ``` 188 | ctx = 189 | a : ⧫ 190 | b : ⧫ 191 | c : ⧫ 192 | f : {* ← a} → b 193 | g : {* ← b} → c 194 | h : {* ← a} → c 195 | α : {[] ← g, [*] ← f} → ∀h 196 | pd = 197 | ``` 198 | We see that ``α`` is a target universal cell that witnesses the composition of 199 | ``f`` and ``g``. 200 | 201 | 202 | # Documentation 203 | 204 | Available at [readthedocs.io](https://readthedocs.io/en/latest/?badge=latest). 205 | Generating the documentation requires 206 | [Sphinx](http://www.sphinx-doc.org/en/stable/). After running 207 | ```sh 208 | make docs 209 | ``` 210 | the HTML documentation should be located at `doc/build/html/index.html`. 211 | 212 | # Tests 213 | 214 | Unit tests are located in folder [tests](tests/), and can executed by running 215 | ```sh 216 | make unittest 217 | ``` 218 | 219 | Additionaly, the code can be typechecked with [mypy](http://mypy-lang.org/) 220 | (according to [PEP484](https://www.python.org/dev/peps/pep-0484/)) by running 221 | ```sh 222 | make typecheck 223 | ``` 224 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Opetopy documentation build configuration file, created by 5 | # sphinx-quickstart on Tue May 1 10:44:25 2018. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | sys.path.insert(0, os.path.abspath('../')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | # needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.todo', 35 | 'sphinx.ext.coverage', 36 | 'sphinx.ext.mathjax', 37 | 'sphinx.ext.viewcode', 38 | ] 39 | 40 | # Add any paths that contain templates here, relative to this directory. 41 | templates_path = ['templates'] 42 | 43 | # The suffix(es) of source filenames. 44 | # You can specify multiple suffix as a list of string: 45 | # source_suffix = ['.rst', '.md'] 46 | source_suffix = '.rst' 47 | 48 | # The encoding of source files. 49 | source_encoding = 'utf-8' 50 | 51 | # The master toctree document. 52 | master_doc = 'index' 53 | 54 | # General information about the project. 55 | project = 'Opetopy' 56 | copyright = '2018, Cedric HT' 57 | author = 'Cedric HT' 58 | 59 | # The version info for the project you're documenting, acts as replacement for 60 | # |version| and |release|, also used in various other places throughout the 61 | # built documents. 62 | # 63 | # The short X.Y version. 64 | version = '1.0' 65 | # The full version, including alpha/beta/rc tags. 66 | release = '1.0' 67 | 68 | # The language for content autogenerated by Sphinx. Refer to documentation 69 | # for a list of supported languages. 70 | # 71 | # This is also used if you do content translation via gettext catalogs. 72 | # Usually you set "language" from the command line for these cases. 73 | language = None 74 | 75 | # There are two options for replacing |today|: either, you set today to some 76 | # non-false value, then it is used: 77 | #today = '' 78 | # Else, today_fmt is used as the format for a strftime call. 79 | #today_fmt = '%B %d, %Y' 80 | 81 | # List of patterns, relative to source directory, that match files and 82 | # directories to ignore when looking for source files. 83 | exclude_patterns = ['build'] 84 | 85 | # The reST default role (used for this markup: `text`) to use for all 86 | # documents. 87 | #default_role = None 88 | 89 | # If true, '()' will be appended to :func: etc. cross-reference text. 90 | #add_function_parentheses = True 91 | 92 | # If true, the current module name will be prepended to all description 93 | # unit titles (such as .. function::). 94 | add_module_names = True 95 | 96 | # If true, sectionauthor and moduleauthor directives will be shown in the 97 | # output. They are ignored by default. 98 | show_authors = True 99 | 100 | # The name of the Pygments (syntax highlighting) style to use. 101 | pygments_style = 'sphinx' 102 | 103 | # A list of ignored prefixes for module index sorting. 104 | #modindex_common_prefix = [] 105 | 106 | # If true, keep warnings as "system message" paragraphs in the built documents. 107 | #keep_warnings = False 108 | 109 | # If true, `todo` and `todoList` produce output, else they produce nothing. 110 | todo_include_todos = True 111 | 112 | 113 | # -- Options for HTML output ---------------------------------------------- 114 | 115 | # The theme to use for HTML and HTML Help pages. See the documentation for 116 | # a list of builtin themes. 117 | html_theme = 'bizstyle' 118 | 119 | # Theme options are theme-specific and customize the look and feel of a theme 120 | # further. For a list of options available for each theme, see the 121 | # documentation. 122 | #html_theme_options = {} 123 | 124 | # Add any paths that contain custom themes here, relative to this directory. 125 | #html_theme_path = [] 126 | 127 | # The name for this set of Sphinx documents. If None, it defaults to 128 | # " v documentation". 129 | #html_title = None 130 | 131 | # A shorter title for the navigation bar. Default is the same as html_title. 132 | #html_short_title = None 133 | 134 | # The name of an image file (relative to this directory) to place at the top 135 | # of the sidebar. 136 | #html_logo = None 137 | 138 | # The name of an image file (relative to this directory) to use as a favicon of 139 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 140 | # pixels large. 141 | #html_favicon = None 142 | 143 | # Add any paths that contain custom static files (such as style sheets) here, 144 | # relative to this directory. They are copied after the builtin static files, 145 | # so a file named "default.css" will overwrite the builtin "default.css". 146 | html_static_path = ['static'] 147 | 148 | # Add any extra paths that contain custom files (such as robots.txt or 149 | # .htaccess) here, relative to this directory. These files are copied 150 | # directly to the root of the documentation. 151 | #html_extra_path = [] 152 | 153 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 154 | # using the given strftime format. 155 | #html_last_updated_fmt = '%b %d, %Y' 156 | 157 | # If true, SmartyPants will be used to convert quotes and dashes to 158 | # typographically correct entities. 159 | #html_use_smartypants = True 160 | 161 | # Custom sidebar templates, maps document names to template names. 162 | #html_sidebars = {} 163 | 164 | # Additional templates that should be rendered to pages, maps page names to 165 | # template names. 166 | #html_additional_pages = {} 167 | 168 | # If false, no module index is generated. 169 | #html_domain_indices = True 170 | 171 | # If false, no index is generated. 172 | #html_use_index = True 173 | 174 | # If true, the index is split into individual pages for each letter. 175 | #html_split_index = False 176 | 177 | # If true, links to the reST sources are added to the pages. 178 | #html_show_sourcelink = True 179 | 180 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 181 | #html_show_sphinx = True 182 | 183 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 184 | #html_show_copyright = True 185 | 186 | # If true, an OpenSearch description file will be output, and all pages will 187 | # contain a tag referring to it. The value of this option must be the 188 | # base URL from which the finished HTML is served. 189 | #html_use_opensearch = '' 190 | 191 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 192 | #html_file_suffix = None 193 | 194 | # Language to be used for generating the HTML full-text search index. 195 | # Sphinx supports the following languages: 196 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 197 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' 198 | #html_search_language = 'en' 199 | 200 | # A dictionary with options for the search language support, empty by default. 201 | # Now only 'ja' uses this config value 202 | #html_search_options = {'type': 'default'} 203 | 204 | # The name of a javascript file (relative to the configuration directory) that 205 | # implements a search results scorer. If empty, the default will be used. 206 | #html_search_scorer = 'scorer.js' 207 | 208 | # Output file base name for HTML help builder. 209 | htmlhelp_basename = 'Opetopydoc' 210 | 211 | # -- Options for LaTeX output --------------------------------------------- 212 | 213 | latex_elements = { 214 | # The paper size ('letterpaper' or 'a4paper'). 215 | #'papersize': 'letterpaper', 216 | 217 | # The font size ('10pt', '11pt' or '12pt'). 218 | #'pointsize': '10pt', 219 | 220 | # Additional stuff for the LaTeX preamble. 221 | #'preamble': '', 222 | 223 | # Latex figure (float) alignment 224 | #'figure_align': 'htbp', 225 | } 226 | 227 | # Grouping the document tree into LaTeX files. List of tuples 228 | # (source start file, target name, title, 229 | # author, documentclass [howto, manual, or own class]). 230 | latex_documents = [ 231 | (master_doc, 'Opetopy.tex', 'Opetopy Documentation', 232 | 'Cedric HT', 'manual'), 233 | ] 234 | 235 | # The name of an image file (relative to this directory) to place at the top of 236 | # the title page. 237 | #latex_logo = None 238 | 239 | # For "manual" documents, if this is true, then toplevel headings are parts, 240 | # not chapters. 241 | #latex_use_parts = False 242 | 243 | # If true, show page references after internal links. 244 | #latex_show_pagerefs = False 245 | 246 | # If true, show URL addresses after external links. 247 | #latex_show_urls = False 248 | 249 | # Documents to append as an appendix to all manuals. 250 | #latex_appendices = [] 251 | 252 | # If false, no module index is generated. 253 | #latex_domain_indices = True 254 | 255 | 256 | # -- Options for manual page output --------------------------------------- 257 | 258 | # One entry per manual page. List of tuples 259 | # (source start file, name, description, authors, manual section). 260 | man_pages = [ 261 | (master_doc, 'opetopy', 'Opetopy Documentation', 262 | [author], 1) 263 | ] 264 | 265 | # If true, show URL addresses after external links. 266 | #man_show_urls = False 267 | 268 | 269 | # -- Options for Texinfo output ------------------------------------------- 270 | 271 | # Grouping the document tree into Texinfo files. List of tuples 272 | # (source start file, target name, title, author, 273 | # dir menu entry, description, category) 274 | texinfo_documents = [ 275 | ( 276 | master_doc, 277 | 'Opetopy', 278 | 'Opetopy Documentation', 279 | author, 280 | 'Opetopy', 281 | 'A Python module to derive opetopes.', 282 | 'Mathematics' 283 | ), 284 | ] 285 | 286 | # Documents to append as an appendix to all manuals. 287 | #texinfo_appendices = [] 288 | 289 | # If false, no module index is generated. 290 | #texinfo_domain_indices = True 291 | 292 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 293 | #texinfo_show_urls = 'footnote' 294 | 295 | # If true, do not generate a @detailmenu in the "Top" node's menu. 296 | #texinfo_no_detailmenu = False 297 | -------------------------------------------------------------------------------- /opetopy/NamedOpetopicSet.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | .. module:: NamedOpetopicSet 4 | :synopsis: Implementation of the named approach for opetopic sets 5 | 6 | .. moduleauthor:: Cédric HT 7 | 8 | """ 9 | 10 | from copy import deepcopy 11 | 12 | from opetopy.common import * 13 | from opetopy import NamedOpetope 14 | 15 | 16 | def repres(seq: NamedOpetope.Sequent) -> NamedOpetope.OCMT: 17 | """ 18 | The :math:`\\textbf{OptSet${}^!$}` :math:`\\texttt{repr}` rule. 19 | """ 20 | if not seq.typing.term.isVariable(): 21 | raise DerivationError( 22 | "repr rule", 23 | "Opt! sequent expected to type a variable, typing {term}", 24 | term=repr(seq.typing.term)) 25 | res = NamedOpetope.OCMT(deepcopy(seq.theory), deepcopy(seq.context)) 26 | # new context 27 | for typing in seq.context: 28 | v = typing.term.variable 29 | if v is None: 30 | raise RuntimeError("[repres rule] The premiss context types an " 31 | "invalid / null term. In valid proof trees, " 32 | "this should not happen") 33 | for i in range(1, v.dimension + 1): 34 | res.context += NamedOpetope.Typing( 35 | NamedOpetope.Term(res.target(v, i)), 36 | NamedOpetope.Type(typing.type.terms[i:])) 37 | # new theory 38 | for tup in seq.context.graftTuples(): 39 | b, a = tup 40 | res.theory += (res.target(a), b) 41 | for a in res.context.variables(): 42 | if a.dimension >= 2 and not res.source(a).degenerate: 43 | s = res.source(a).variable 44 | if s is None: 45 | raise RuntimeError( 46 | "[repres rule] The premiss context types " 47 | "the variable {a} of dimension {dim}, " 48 | "whose first source is invalid / null. In " 49 | "valid proof trees, this should not happen".format( 50 | a=str(a), dim=a.dimension)) 51 | res.theory += (res.target(a, 2), res.target(s)) 52 | for a in seq.context.variables(): 53 | for k in range(0, a.dimension - 1): 54 | if res.source(res.target(a, k)).degenerate: 55 | c = res.source(res.target(a, k)).variable 56 | if c is None: 57 | raise RuntimeError("[repres rule] The premiss context " 58 | "types the variable {var} of dimension " 59 | "{dim}, whose first source is invalid " 60 | "/ null. In valid proof trees, this " 61 | "should not happen".format( 62 | var=str(res.target(a, k)), 63 | dim=res.target(a, k).dimension)) 64 | res.theory += (res.target(a, k + 2), c) 65 | # identification of targets 66 | tmp = deepcopy(res.theory) 67 | for cls in tmp.classes: 68 | elems = list(cls) 69 | dim = elems[0].dimension 70 | for i in range(1, len(cls)): 71 | for k in range(1, dim + 1): 72 | res.theory += (res.target(elems[0], 73 | k), res.target(elems[i], k)) 74 | return res 75 | 76 | 77 | def sum(ocmt1: NamedOpetope.OCMT, 78 | ocmt2: NamedOpetope.OCMT) -> NamedOpetope.OCMT: 79 | """ 80 | The :math:`\\textbf{OptSet${}^!$}` :math:`\\texttt{sum}` rule. 81 | """ 82 | if len(ocmt1.context.variables() & ocmt2.context.variables()) != 0: 83 | raise DerivationError( 84 | "sum rule", 85 | "The two premiss OCTM are expected to have disjoint contexts, " 86 | "but intersection types the following variables {inter}", 87 | inter=ocmt1.context.variables() & ocmt2.context.variables()) 88 | return NamedOpetope.OCMT(ocmt1.theory | ocmt2.theory, 89 | ocmt1.context | ocmt2.context) 90 | 91 | 92 | def glue(ocmt: NamedOpetope.OCMT, aName: str, bName: str) -> NamedOpetope.OCMT: 93 | """ 94 | The :math:`\\textbf{OptSet${}^!$}` :math:`\\texttt{glue}` rule. 95 | """ 96 | a = ocmt.context[aName] 97 | b = ocmt.context[bName] 98 | if a.dimension != b.dimension: 99 | raise DerivationError( 100 | "glue rule", 101 | "NamedOpetope.Variables {a} and {b} cannot be identified as they " 102 | "do not have the same dimension (have respectively {da} and {db})", 103 | a=str(a), 104 | b=str(b), 105 | da=a.dimension, 106 | db=b.dimension) 107 | elif a.dimension != 0 and not \ 108 | (ocmt.equal(ocmt.source(a), ocmt.source(b)) and 109 | ocmt.theory.equal(ocmt.target(a), ocmt.target(b))): 110 | raise DerivationError( 111 | "glue rule", 112 | "NamedOpetope.Variables {a} and {b} cannot be identified as they " 113 | "are not parallel: sa = {sa}, sb = {sb}, ta = {ta}, tb = {tb}", 114 | a=str(a), 115 | b=str(b), 116 | sa=str(ocmt.source(a)), 117 | sb=str(ocmt.source(b)), 118 | ta=str(ocmt.target(a)), 119 | tb=str(ocmt.target(b))) 120 | res = deepcopy(ocmt) 121 | res.theory += (a, b) 122 | return res 123 | 124 | 125 | def zero() -> NamedOpetope.OCMT: 126 | """ 127 | The :math:`\\textbf{OptSet${}^!$}` :math:`\\texttt{zero}` rule. 128 | """ 129 | return NamedOpetope.OCMT(NamedOpetope.EquationalTheory(), 130 | NamedOpetope.Context()) 131 | 132 | 133 | class RuleInstance(AbstractRuleInstance): 134 | """ 135 | A rule instance of system :math:`\\textbf{OptSet${}^!$}`. 136 | """ 137 | def eval(self) -> NamedOpetope.OCMT: 138 | """ 139 | Pure virtual method evaluating a proof tree and returning the final 140 | conclusion sequent, or raising an exception if the proof is invalid. 141 | """ 142 | raise NotImplementedError() 143 | 144 | 145 | class Repr(RuleInstance): 146 | """ 147 | A class representing an instance of the ``repr`` rule in a proof tree. 148 | """ 149 | 150 | proofTree: NamedOpetope.RuleInstance 151 | 152 | def __init__(self, p: NamedOpetope.RuleInstance) -> None: 153 | self.proofTree = p 154 | 155 | def __repr__(self) -> str: 156 | return "Repr({})".format(repr(self.proofTree)) 157 | 158 | def __str__(self) -> str: 159 | return "Repr({})".format(str(self.proofTree)) 160 | 161 | def _toTex(self) -> str: 162 | """ 163 | Converts the proof tree in TeX code. This method should not be called 164 | directly, use :meth:`NamedOpetope.RuleInstance.toTex` 165 | instead. 166 | """ 167 | return self.proofTree._toTex() + \ 168 | "\n\t\\RightLabel{\\texttt{repr}}\n\t\\UnaryInfC{$" + \ 169 | self.eval().toTex() + "$}" 170 | 171 | def eval(self) -> NamedOpetope.OCMT: 172 | return repres(self.proofTree.eval()) 173 | 174 | 175 | class Sum(RuleInstance): 176 | """ 177 | A class representing an instance of the ``sum`` rule in a proof tree. 178 | """ 179 | 180 | proofTree1: RuleInstance 181 | proofTree2: RuleInstance 182 | 183 | def __init__(self, p1: RuleInstance, p2: RuleInstance) -> None: 184 | """ 185 | Creates an instance of the ``graft`` rule at variable ``a``, and plugs 186 | proof tree ``p1`` on the first premise, and ``p2`` on the second. 187 | 188 | :see: :func:`opetopy.NamedOpetope.graft`. 189 | """ 190 | self.proofTree1 = p1 191 | self.proofTree2 = p2 192 | 193 | def __repr__(self) -> str: 194 | return "Sum({p1}, {p2})".format(p1=repr(self.proofTree1), 195 | p2=repr(self.proofTree2)) 196 | 197 | def __str__(self) -> str: 198 | return "Sum({p1}, {p2})".format(p1=str(self.proofTree1), 199 | p2=str(self.proofTree2)) 200 | 201 | def _toTex(self) -> str: 202 | """ 203 | Converts the proof tree in TeX code. This method should not be called 204 | directly, use :meth:`NamedOpetope.RuleInstance.toTex` 205 | instead. 206 | """ 207 | return self.proofTree1._toTex() + "\n\t" + self.proofTree2._toTex() + \ 208 | "\n\t\\RightLabel{\\texttt{sum}" + \ 209 | "}\n\t\\BinaryInfC{$" + self.eval().toTex() + "$}" 210 | 211 | def eval(self) -> NamedOpetope.OCMT: 212 | return sum(self.proofTree1.eval(), self.proofTree2.eval()) 213 | 214 | 215 | class Glue(RuleInstance): 216 | """ 217 | A class representing an instance of the ``glue`` rule in a proof tree. 218 | """ 219 | 220 | proofTree: RuleInstance 221 | aName: str 222 | bName: str 223 | 224 | def __init__(self, p: RuleInstance, a: str, b: str) -> None: 225 | self.proofTree = p 226 | self.aName = a 227 | self.bName = b 228 | 229 | def __repr__(self) -> str: 230 | return "Glue({p}, {a}, {b})".format(p=repr(self.proofTree), 231 | a=repr(self.aName), 232 | b=repr(self.bName)) 233 | 234 | def __str__(self) -> str: 235 | return "Glue({p}, {a}, {b})".format(p=str(self.proofTree), 236 | a=str(self.aName), 237 | b=str(self.bName)) 238 | 239 | def _toTex(self) -> str: 240 | """ 241 | Converts the proof tree in TeX code. This method should not be called 242 | directly, use :meth:`NamedOpetope.RuleInstance.toTex` 243 | instead. 244 | """ 245 | return self.proofTree._toTex() + \ 246 | "\n\t\\RightLabel{\\texttt{glue-}$(" + self.aName + \ 247 | " = " + self.bName + ")$}\n\t\\UnaryInfC{$" + \ 248 | self.eval().toTex() + "$}" 249 | 250 | def eval(self) -> NamedOpetope.OCMT: 251 | """ 252 | Evaluates this instance of ``graft`` by first evaluating its premises, 253 | and then applying :func:`opetopy.NamedOpetope.graft` at variable 254 | `self.a` on the resulting sequents. 255 | """ 256 | return glue(self.proofTree.eval(), self.aName, self.bName) 257 | 258 | 259 | class Zero(RuleInstance): 260 | """ 261 | A class representing an instance of the ``zero`` rule in a proof tree. 262 | """ 263 | def _toTex(self) -> str: 264 | """ 265 | Converts the proof tree in TeX code. This method should not be called 266 | directly, use :meth:`NamedOpetope.RuleInstance.toTex` 267 | instead. 268 | """ 269 | return "\\AxiomC{}\n\t\\RightLabel{\\texttt{zero}\n\t\\UnaryInfC{$" + \ 270 | self.eval().toTex() + "$}" 271 | 272 | def __repr__(self) -> str: 273 | return "Zero()" 274 | 275 | def __str__(self) -> str: 276 | return "Zero()" 277 | 278 | def eval(self) -> NamedOpetope.OCMT: 279 | return zero() 280 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. role:: python(code) 2 | :language: python 3 | 4 | opetopy 5 | ******* 6 | 7 | .. image:: https://travis-ci.com/altaris/opetopy.svg?branch=master 8 | :target: https://travis-ci.com/altaris/opetopy 9 | :alt: Build Status 10 | 11 | .. image:: https://coveralls.io/repos/github/altaris/opetopy/badge.svg?branch=master 12 | :target: https://coveralls.io/github/altaris/opetopy?branch=master 13 | :alt: Coverage Status 14 | 15 | .. image:: https://readthedocs.org/projects/opetopy/badge/?version=latest 16 | :target: https://opetopy.readthedocs.io/en/latest/ 17 | :alt: Documentation Status 18 | 19 | .. image:: https://badgen.net/badge//GitHub/green?icon=github 20 | :target: https://github.com/altaris/opetopy 21 | :alt: GitHub 22 | 23 | .. image:: https://badgen.net/badge/Python/3/blue 24 | :alt: Python 3 25 | 26 | .. image:: https://badgen.net/badge/license/MIT/blue 27 | :target: https://choosealicense.com/licenses/mit/ 28 | :alt: MIT License 29 | 30 | .. contents:: Contents 31 | 32 | 33 | Introduction 34 | ============ 35 | 36 | 37 | This project is the Python implementation of the opetope derivation systems 38 | presented in [CHM19]_ and some other work in progress. 39 | 40 | The :mod:`opetopy` module is decomposed as follow: 41 | 42 | +--------------------------------+------------------------------+-------------------------------+ 43 | | Module | Syntactical construct | Derivation system | 44 | +================================+==============================+===============================+ 45 | | :mod:`NamedOpetope` | Named opetopes | :math:`\textbf{Opt${}^!$}` | 46 | +--------------------------------+------------------------------+-------------------------------+ 47 | | :mod:`NamedOpetopicSet` | Named opetopic sets | :math:`\textbf{OptSet${}^!$}` | 48 | +--------------------------------+------------------------------+-------------------------------+ 49 | | :mod:`UnnamedOpetope` | Unnamed opetopes | :math:`\textbf{Opt${}^?$}` | 50 | +--------------------------------+------------------------------+-------------------------------+ 51 | | :mod:`UnnamedOpetopicSet` | Unnamed opetopic sets | :math:`\textbf{OptSet${}^?$}` | 52 | +--------------------------------+------------------------------+-------------------------------+ 53 | | :mod:`UnnamedOpetopicCategory` | Unnamed opetopic categories | :math:`\textbf{OptCat${}^?$}` | 54 | +--------------------------------+------------------------------+-------------------------------+ 55 | 56 | Each implement the following: 57 | 58 | 1. the syntactic constructs required to describe opetopes / opetopic sets and their sequents; 59 | 2. the derivation rules of the relevant system; 60 | 3. wrappers of those rules to describe proof trees. 61 | 62 | +---------------------------------+------------------------------+------------------------------------------------+-------------------------------------------------+ 63 | | Derivation system | Rule | Implementation | Proof tree node | 64 | +=================================+==============================+================================================+=================================================+ 65 | | :math:`\textbf{Opt${}^!$}` | :math:`\texttt{point}` | :func:`opetopy.NamedOpetope.point` | :class:`opetopy.NamedOpetope.Point` | 66 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 67 | | | :math:`\texttt{degen}` | :func:`opetopy.NamedOpetope.degen` | :class:`opetopy.NamedOpetope.Degen` | 68 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 69 | | | :math:`\texttt{degen-shift}` | :func:`opetopy.NamedOpetope.degenfill` | :class:`opetopy.NamedOpetope.DegenFill` | 70 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 71 | | | :math:`\texttt{shift}` | :func:`opetopy.NamedOpetope.shift` | :class:`opetopy.NamedOpetope.Shift` | 72 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 73 | | | :math:`\texttt{graft}` | :func:`opetopy.NamedOpetope.graft` | :class:`opetopy.NamedOpetope.Graft` | 74 | +---------------------------------+------------------------------+------------------------------------------------+-------------------------------------------------+ 75 | | :math:`\textbf{OptSet${}^!$}` | :math:`\texttt{repr}` | :func:`opetopy.NamedOpetopicSet.repres` | :class:`opetopy.NamedOpetopicSet.Repr` | 76 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 77 | | | :math:`\texttt{zero}` | :func:`opetopy.NamedOpetopicSet.zero` | :class:`opetopy.NamedOpetopicSet.Zero` | 78 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 79 | | | :math:`\texttt{sum}` | :func:`opetopy.NamedOpetopicSet.sum` | :class:`opetopy.NamedOpetopicSet.Sum` | 80 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 81 | | | :math:`\texttt{glue}` | :func:`opetopy.NamedOpetopicSet.glue` | :class:`opetopy.NamedOpetopicSet.Glue` | 82 | +---------------------------------+------------------------------+------------------------------------------------+-------------------------------------------------+ 83 | | :math:`\textbf{OptSet${}^!_m$}` | :math:`\texttt{point}` | :func:`opetopy.NamedOpetopicSetM.point` | :class:`opetopy.NamedOpetopicSetM.Point` | 84 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 85 | | | :math:`\texttt{degen}` | :func:`opetopy.NamedOpetopicSetM.degen` | :class:`opetopy.NamedOpetopicSetM.Degen` | 86 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 87 | | | :math:`\texttt{pd}` | :func:`opetopy.NamedOpetopicSetM.pd` | :class:`opetopy.NamedOpetopicSetM.Pd` | 88 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 89 | | | :math:`\texttt{graft}` | :func:`opetopy.NamedOpetopicSetM.graft` | :class:`opetopy.NamedOpetopicSetM.Graft` | 90 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 91 | | | :math:`\texttt{shift}` | :func:`opetopy.NamedOpetopicSetM.shift` | :class:`opetopy.NamedOpetopicSetM.Shift` | 92 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 93 | | | :math:`\texttt{zero}` | :func:`opetopy.NamedOpetopicSetM.zero` | :class:`opetopy.NamedOpetopicSetM.Zero` | 94 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 95 | | | :math:`\texttt{sum}` | :func:`opetopy.NamedOpetopicSetM.sum` | :class:`opetopy.NamedOpetopicSetM.Sum` | 96 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 97 | | | :math:`\texttt{glue}` | :func:`opetopy.NamedOpetopicSetM.glue` | :class:`opetopy.NamedOpetopicSetM.Glue` | 98 | +---------------------------------+------------------------------+------------------------------------------------+-------------------------------------------------+ 99 | | :math:`\textbf{Opt${}^?$}` | :math:`\texttt{point}` | :func:`opetopy.UnnamedOpetope.point` | :class:`opetopy.UnnamedOpetope.Point` | 100 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 101 | | | :math:`\texttt{degen}` | :func:`opetopy.UnnamedOpetope.degen` | :class:`opetopy.UnnamedOpetope.Degen` | 102 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 103 | | | :math:`\texttt{shift}` | :func:`opetopy.UnnamedOpetope.shift` | :class:`opetopy.UnnamedOpetope.Shift` | 104 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 105 | | | :math:`\texttt{graft}` | :func:`opetopy.UnnamedOpetope.graft` | :class:`opetopy.UnnamedOpetope.Graft` | 106 | +---------------------------------+------------------------------+------------------------------------------------+-------------------------------------------------+ 107 | | :math:`\textbf{OptSet${}^?$}` | :math:`\texttt{point}` | :func:`opetopy.UnnamedOpetopicSet.point` | :class:`opetopy.UnnamedOpetopicSet.Point` | 108 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 109 | | | :math:`\texttt{degen}` | :func:`opetopy.UnnamedOpetopicSet.degen` | :class:`opetopy.UnnamedOpetopicSet.Degen` | 110 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 111 | | | :math:`\texttt{graft}` | :func:`opetopy.UnnamedOpetopicSet.graft` | :class:`opetopy.UnnamedOpetopicSet.Graft` | 112 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 113 | | | :math:`\texttt{shift}` | :func:`opetopy.UnnamedOpetopicSet.shift` | :class:`opetopy.UnnamedOpetopicSet.Shift` | 114 | +---------------------------------+------------------------------+------------------------------------------------+-------------------------------------------------+ 115 | | :math:`\textbf{OptCat${}^?$}` | :math:`\texttt{tfill}` | :func:`opetopy.UnnamedOpetopicCategory.tfill` | :class:`opetopy.UnnamedOpetopicCategory.TFill` | 116 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 117 | | | :math:`\texttt{tuniv}` | :func:`opetopy.UnnamedOpetopicCategory.tuniv` | :class:`opetopy.UnnamedOpetopicCategory.TUniv` | 118 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 119 | | | :math:`\texttt{suniv}` | :func:`opetopy.UnnamedOpetopicCategory.suniv` | :class:`opetopy.UnnamedOpetopicCategory.SUniv` | 120 | + +------------------------------+------------------------------------------------+-------------------------------------------------+ 121 | | | :math:`\texttt{tclose}` | :func:`opetopy.UnnamedOpetopicCategory.tclose` | :class:`opetopy.UnnamedOpetopicCategory.TClose` | 122 | +---------------------------------+------------------------------+------------------------------------------------+-------------------------------------------------+ 123 | 124 | 125 | Usage 126 | ===== 127 | 128 | 129 | Derivations and proof trees 130 | --------------------------- 131 | 132 | A derivation / proof tree in any of those system can then be written as a Python 133 | expression. If it evaluates without raising any exception, it is considered 134 | valid. 135 | 136 | For example, in system :math:`\textbf{Opt${}^?$}`, the unique :math:`1`-opetope 137 | has the following expression: 138 | 139 | .. code-block:: python 140 | 141 | from opetopy.UnnamedOpetope import * 142 | shift(point()) 143 | 144 | 145 | which indeed evaluates without raising exceptions. 146 | 147 | The preferred way to construct proof trees is to use the proof tree node 148 | classes (see table above), who act as instances of those rules. They behave as 149 | their function counterparts, taking proof trees instead of sequents as 150 | constructor arguments. Then, a proof tree can be evaluated with the 151 | :func:`opetopy.common.AbstractRuleInstance.eval` method. For instance, the 152 | proof tree described above is written as: 153 | 154 | .. code-block:: python 155 | 156 | from opetopy.UnnamedOpetope import * 157 | proof = Shift(Point()) 158 | 159 | 160 | and evaluated as: 161 | 162 | .. code-block:: python 163 | 164 | proof.eval() 165 | 166 | 167 | which again evaluates without raising exceptions. 168 | 169 | 170 | Exporting to :math:`\TeX` 171 | ------------------------- 172 | 173 | 174 | `opetopy`'s main classes can be translated to :math:`\TeX` code using method 175 | :func:`opetopy.common.AbstractRuleInstance.toTeX`. Here is the minimal template 176 | to compile the returned code 177 | 178 | .. code-block:: TeX 179 | 180 | \documentclass{article} 181 | 182 | \usepackage{amsmath} 183 | \usepackage{bussproofs} 184 | \usepackage{fdsymbol} 185 | \usepackage{MnSymbol} 186 | 187 | \newcommand{\degenopetope}[1]{\left\lbrace \!\! \opetope{#1} \right.} 188 | \newcommand{\opetope}[1]{\left\lbrace \begin{matrix*}[l] #1 \end{matrix*} \right.} 189 | \newcommand{\optOne}{\filledsquare} 190 | \newcommand{\optZero}{\filledlozenge} 191 | \newcommand{\sep}{\leftarrow} 192 | 193 | \begin{document} 194 | 195 | Your code here. 196 | 197 | \end{document} 198 | 199 | 200 | Documentation 201 | ============= 202 | 203 | 204 | .. toctree:: 205 | 206 | common 207 | namedopetope 208 | namedopetopicset 209 | namedopetopicsetm 210 | unnamedopetope 211 | unnamedopetopicset 212 | unnamedopetopiccategory 213 | 214 | 215 | .. [CHM19] Pierre-Louis Curien, Cédric Ho Thanh, and Samuel Mimram. Syntactic 216 | approaches to opetopes. arXiv:1903.05848 217 | -------------------------------------------------------------------------------- /tests/unittest_unnamedopetopicset.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import sys 4 | sys.path.insert(0, "../") 5 | 6 | from opetopy.common import DerivationError 7 | 8 | from opetopy import UnnamedOpetope 9 | from opetopy import UnnamedOpetopicSet 10 | 11 | 12 | class Test_UnnamedOpetopicSet_Variable(unittest.TestCase): 13 | 14 | def setUp(self): 15 | self.a = UnnamedOpetopicSet.Variable("a", UnnamedOpetope.Arrow()) 16 | self.b = UnnamedOpetopicSet.Variable("b", UnnamedOpetope.Arrow()) 17 | self.i1 = UnnamedOpetopicSet.Variable( 18 | "i1", UnnamedOpetope.OpetopicInteger(1)) 19 | self.i2 = UnnamedOpetopicSet.Variable( 20 | "i2", UnnamedOpetope.OpetopicInteger(2)) 21 | self.i3 = UnnamedOpetopicSet.Variable( 22 | "i3", UnnamedOpetope.OpetopicInteger(3)) 23 | self.c = UnnamedOpetopicSet.Variable("c", UnnamedOpetope.Graft( 24 | UnnamedOpetope.Shift(UnnamedOpetope.OpetopicInteger(2)), 25 | UnnamedOpetope.OpetopicInteger(2), 26 | UnnamedOpetope.Address.fromList([['*']], 2))) 27 | 28 | def test___eq__(self): 29 | self.assertEqual(self.a, self.a) 30 | self.assertEqual(self.b, self.b) 31 | self.assertEqual(self.i1, self.i1) 32 | self.assertEqual(self.i2, self.i2) 33 | self.assertNotEqual(self.a, self.b) 34 | self.assertNotEqual(self.a, self.i1) 35 | self.assertNotEqual(self.i1, self.i2) 36 | self.assertNotEqual(self.i2, self.i3) 37 | self.assertNotEqual(self.i3, self.c) 38 | 39 | def test_shape(self): 40 | self.assertEqual(self.a.shape, 41 | UnnamedOpetope.Arrow().eval().source) 42 | self.assertEqual(self.b.shape, 43 | UnnamedOpetope.Arrow().eval().source) 44 | self.assertEqual(self.i1.shape, 45 | UnnamedOpetope.OpetopicInteger(1).eval().source) 46 | self.assertEqual(self.i2.shape, 47 | UnnamedOpetope.OpetopicInteger(2).eval().source) 48 | self.assertEqual(self.i3.shape, 49 | UnnamedOpetope.OpetopicInteger(3).eval().source) 50 | 51 | def test_shapeTarget(self): 52 | self.assertEqual(self.a.shapeTarget(), 53 | UnnamedOpetope.Point().eval().source) 54 | self.assertEqual(self.b.shapeTarget(), 55 | UnnamedOpetope.Point().eval().source) 56 | self.assertEqual(self.i1.shapeTarget(), 57 | UnnamedOpetope.Arrow().eval().source) 58 | self.assertEqual(self.i2.shapeTarget(), 59 | UnnamedOpetope.Arrow().eval().source) 60 | self.assertEqual(self.i3.shapeTarget(), 61 | UnnamedOpetope.Arrow().eval().source) 62 | self.assertEqual(self.c.shapeTarget(), 63 | UnnamedOpetope.OpetopicInteger(3).eval().source) 64 | 65 | 66 | class Test_UnnamedOpetopicSet_PastingDiagram(unittest.TestCase): 67 | 68 | def setUp(self): 69 | pass 70 | 71 | def test___eq__(self): 72 | self.assertEqual( 73 | UnnamedOpetopicSet.pastingDiagram( 74 | UnnamedOpetope.OpetopicInteger(0), "a"), 75 | UnnamedOpetopicSet.pastingDiagram( 76 | UnnamedOpetope.OpetopicInteger(0), "a")) 77 | self.assertNotEqual( 78 | UnnamedOpetopicSet.pastingDiagram( 79 | UnnamedOpetope.OpetopicInteger(0), "a"), 80 | UnnamedOpetopicSet.pastingDiagram( 81 | UnnamedOpetope.OpetopicInteger(0), "b")) 82 | self.assertNotEqual( 83 | UnnamedOpetopicSet.pastingDiagram( 84 | UnnamedOpetope.OpetopicInteger(0), "a"), 85 | UnnamedOpetopicSet.pastingDiagram( 86 | UnnamedOpetope.Degen(UnnamedOpetope.Arrow()), "a")) 87 | self.assertNotEqual( 88 | UnnamedOpetopicSet.pastingDiagram( 89 | UnnamedOpetope.OpetopicInteger(1), 90 | {UnnamedOpetope.address([], 1): "a"}), 91 | UnnamedOpetopicSet.pastingDiagram( 92 | UnnamedOpetope.OpetopicInteger(1), 93 | {UnnamedOpetope.address([], 1): "b"})) 94 | self.assertNotEqual( 95 | UnnamedOpetopicSet.pastingDiagram( 96 | UnnamedOpetope.OpetopicInteger(1), 97 | {UnnamedOpetope.address([], 1): "a"}), 98 | UnnamedOpetopicSet.pastingDiagram( 99 | UnnamedOpetope.OpetopicInteger(2), 100 | { 101 | UnnamedOpetope.address([], 1): "a", 102 | UnnamedOpetope.address(['*']): "a" 103 | })) 104 | self.assertEqual( 105 | UnnamedOpetopicSet.pastingDiagram( 106 | UnnamedOpetope.OpetopicInteger(2), 107 | { 108 | UnnamedOpetope.address([], 1): "a", 109 | UnnamedOpetope.address(['*']): "b" 110 | }), 111 | UnnamedOpetopicSet.pastingDiagram( 112 | UnnamedOpetope.OpetopicInteger(2), 113 | { 114 | UnnamedOpetope.address(['*']): "b", 115 | UnnamedOpetope.address([], 1): "a" 116 | })) 117 | 118 | def test___getitem__(self): 119 | d = UnnamedOpetopicSet.PastingDiagram.degeneratePastingDiagram( 120 | UnnamedOpetope.OpetopicInteger(0), "d") 121 | p = UnnamedOpetopicSet.PastingDiagram.nonDegeneratePastingDiagram( 122 | UnnamedOpetope.OpetopicInteger(2), 123 | { 124 | UnnamedOpetope.Address.epsilon(1): "a", 125 | UnnamedOpetope.Address.epsilon(0).shift(): "b" 126 | }) 127 | with self.assertRaises(DerivationError): 128 | d[UnnamedOpetope.Address.epsilon(0)] 129 | self.assertEqual(p[UnnamedOpetope.Address.epsilon(1)], "a") 130 | self.assertEqual(p[UnnamedOpetope.Address.epsilon(0).shift()], "b") 131 | 132 | def test_degeneratePastingDiagram(self): 133 | UnnamedOpetopicSet.PastingDiagram.degeneratePastingDiagram( 134 | UnnamedOpetope.OpetopicInteger(0), "d") 135 | with self.assertRaises(DerivationError): 136 | UnnamedOpetopicSet.PastingDiagram.degeneratePastingDiagram( 137 | UnnamedOpetope.OpetopicInteger(1), "d") 138 | 139 | def test_point(self): 140 | UnnamedOpetopicSet.PastingDiagram.point() 141 | 142 | def test_nonDegeneratePastingDiagram(self): 143 | UnnamedOpetopicSet.PastingDiagram.nonDegeneratePastingDiagram( 144 | UnnamedOpetope.OpetopicInteger(2), 145 | { 146 | UnnamedOpetope.Address.epsilon(1): "a", 147 | UnnamedOpetope.Address.epsilon(0).shift(): "b" 148 | }) 149 | with self.assertRaises(DerivationError): 150 | UnnamedOpetopicSet.PastingDiagram.nonDegeneratePastingDiagram( 151 | UnnamedOpetope.OpetopicInteger(0), {}) 152 | with self.assertRaises(DerivationError): 153 | UnnamedOpetopicSet.PastingDiagram.nonDegeneratePastingDiagram( 154 | UnnamedOpetope.OpetopicInteger(2), 155 | { 156 | UnnamedOpetope.Address.epsilon(1): "a" 157 | }) 158 | 159 | 160 | class Test_UnnamedOpetopicSet_Type(unittest.TestCase): 161 | 162 | def setUp(self): 163 | self.s = UnnamedOpetopicSet.PastingDiagram.nonDegeneratePastingDiagram( 164 | UnnamedOpetope.OpetopicInteger(2), 165 | { 166 | UnnamedOpetope.Address.epsilon(1): "a", 167 | UnnamedOpetope.Address.epsilon(0).shift(): "b" 168 | }) 169 | 170 | def test___init__(self): 171 | UnnamedOpetopicSet.Type( 172 | self.s, UnnamedOpetopicSet.Variable("t", UnnamedOpetope.Arrow())) 173 | with self.assertRaises(DerivationError): 174 | UnnamedOpetopicSet.Type( 175 | self.s, UnnamedOpetopicSet.Variable( 176 | "t", UnnamedOpetope.Point())) 177 | UnnamedOpetopicSet.Type( 178 | UnnamedOpetopicSet.PastingDiagram.point(), None) 179 | with self.assertRaises(DerivationError): 180 | UnnamedOpetopicSet.Type(self.s, None) 181 | 182 | 183 | class Test_UnnamedOpetopicSet_Typing(unittest.TestCase): 184 | 185 | def setUp(self): 186 | self.t = UnnamedOpetopicSet.Type( 187 | UnnamedOpetopicSet.PastingDiagram.nonDegeneratePastingDiagram( 188 | UnnamedOpetope.OpetopicInteger(2), 189 | { 190 | UnnamedOpetope.Address.epsilon(1): "a", 191 | UnnamedOpetope.Address.epsilon(0).shift(): "b" 192 | }), 193 | UnnamedOpetopicSet.Variable("t", UnnamedOpetope.Arrow())) 194 | 195 | def test___init__(self): 196 | UnnamedOpetopicSet.Typing( 197 | UnnamedOpetopicSet.Variable( 198 | "x", UnnamedOpetope.OpetopicInteger(2)), self.t) 199 | with self.assertRaises(DerivationError): 200 | UnnamedOpetopicSet.Typing( 201 | UnnamedOpetopicSet.Variable( 202 | "x", UnnamedOpetope.OpetopicInteger(3)), 203 | self.t) 204 | 205 | 206 | class Test_UnnamedOpetopicSet_Context(unittest.TestCase): 207 | 208 | def setUp(self): 209 | self.p = UnnamedOpetopicSet.Typing( 210 | UnnamedOpetopicSet.Variable("p", UnnamedOpetope.Point()), 211 | UnnamedOpetopicSet.Type( 212 | UnnamedOpetopicSet.PastingDiagram.point(), None)) 213 | self.a = UnnamedOpetopicSet.Typing( 214 | UnnamedOpetopicSet.Variable( 215 | "a", UnnamedOpetope.OpetopicInteger(0)), 216 | UnnamedOpetopicSet.Type( 217 | UnnamedOpetopicSet.PastingDiagram.degeneratePastingDiagram( 218 | UnnamedOpetope.OpetopicInteger(0), "p"), 219 | UnnamedOpetopicSet.Variable("p", UnnamedOpetope.Arrow()))) 220 | self.b = UnnamedOpetopicSet.Typing( 221 | UnnamedOpetopicSet.Variable( 222 | "b", UnnamedOpetope.OpetopicInteger(0)), 223 | UnnamedOpetopicSet.Type( 224 | UnnamedOpetopicSet.PastingDiagram.degeneratePastingDiagram( 225 | UnnamedOpetope.OpetopicInteger(0), "p"), 226 | UnnamedOpetopicSet.Variable("p", UnnamedOpetope.Arrow()))) 227 | self.c = UnnamedOpetopicSet.Typing( 228 | UnnamedOpetopicSet.Variable( 229 | "c", UnnamedOpetope.OpetopicInteger(2)), 230 | UnnamedOpetopicSet.Type( 231 | UnnamedOpetopicSet.PastingDiagram.nonDegeneratePastingDiagram( 232 | UnnamedOpetope.OpetopicInteger(2), 233 | { 234 | UnnamedOpetope.Address.epsilon(1): "x", 235 | UnnamedOpetope.Address.epsilon(0).shift(): "y" 236 | }), 237 | UnnamedOpetopicSet.Variable("z", UnnamedOpetope.Arrow()))) 238 | self.ctx = UnnamedOpetopicSet.Context() + self.p + self.a + self.c 239 | 240 | def test___add__(self): 241 | with self.assertRaises(DerivationError): 242 | self.ctx + self.a 243 | self.ctx + self.b 244 | with self.assertRaises(DerivationError): 245 | self.ctx + self.c 246 | 247 | def test___contains__(self): 248 | self.assertIn(self.a.variable, self.ctx) 249 | self.assertNotIn(self.b.variable, self.ctx) 250 | self.assertIn(self.c.variable, self.ctx) 251 | 252 | def test___getitem__(self): 253 | self.assertEqual(self.ctx["a"].variable, self.a.variable) 254 | with self.assertRaises(DerivationError): 255 | self.ctx["b"] 256 | self.assertEqual(self.ctx["c"].variable, self.c.variable) 257 | 258 | def test_source(self): 259 | self.assertEqual( 260 | self.ctx.source("c", UnnamedOpetope.Address.epsilon(1)), 261 | "x") 262 | self.assertEqual( 263 | self.ctx.source("c", UnnamedOpetope.Address.epsilon(0).shift()), 264 | "y") 265 | 266 | def test_target(self): 267 | self.assertEqual(self.ctx.target("c"), "z") 268 | with self.assertRaises(DerivationError): 269 | self.ctx.target("p") 270 | 271 | 272 | class Test_UnnamedOpetopicSet_InferenceRules(unittest.TestCase): 273 | 274 | def setUp(self): 275 | self.type_point = UnnamedOpetopicSet.Type( 276 | UnnamedOpetopicSet.PastingDiagram.point(), None) 277 | self.a = UnnamedOpetopicSet.Variable("a", UnnamedOpetope.Point()) 278 | self.b = UnnamedOpetopicSet.Variable("b", UnnamedOpetope.Point()) 279 | self.c = UnnamedOpetopicSet.Variable("c", UnnamedOpetope.Point()) 280 | self.d = UnnamedOpetopicSet.Variable("d", UnnamedOpetope.Point()) 281 | self.ab = UnnamedOpetopicSet.Variable("ab", UnnamedOpetope.Arrow()) 282 | self.ac = UnnamedOpetopicSet.Variable("ac", UnnamedOpetope.Arrow()) 283 | self.bc = UnnamedOpetopicSet.Variable("bc", UnnamedOpetope.Arrow()) 284 | self.cd = UnnamedOpetopicSet.Variable("cd", UnnamedOpetope.Arrow()) 285 | self.seq = UnnamedOpetopicSet.Sequent() 286 | self.seq.context = UnnamedOpetopicSet.Context() + \ 287 | UnnamedOpetopicSet.Typing(self.a, self.type_point) + \ 288 | UnnamedOpetopicSet.Typing(self.b, self.type_point) + \ 289 | UnnamedOpetopicSet.Typing(self.c, self.type_point) + \ 290 | UnnamedOpetopicSet.Typing(self.d, self.type_point) + \ 291 | UnnamedOpetopicSet.Typing( 292 | self.ab, self.type_arrow("a", self.b)) + \ 293 | UnnamedOpetopicSet.Typing( 294 | self.ac, self.type_arrow("a", self.c)) + \ 295 | UnnamedOpetopicSet.Typing( 296 | self.bc, self.type_arrow("b", self.c)) + \ 297 | UnnamedOpetopicSet.Typing( 298 | self.cd, self.type_arrow("c", self.d)) 299 | 300 | def type_arrow( 301 | self, src: str, 302 | tgt: UnnamedOpetopicSet.Variable) -> UnnamedOpetopicSet.Type: 303 | """ 304 | Convenient function to define the type of an arrow shaped cell 305 | """ 306 | return UnnamedOpetopicSet.Type( 307 | UnnamedOpetopicSet.PastingDiagram.nonDegeneratePastingDiagram( 308 | UnnamedOpetope.Arrow(), 309 | {UnnamedOpetope.Address.epsilon(0): src}), 310 | tgt) 311 | 312 | def test_degen(self): 313 | s = UnnamedOpetopicSet.point(UnnamedOpetopicSet.Sequent(), "x") 314 | with self.assertRaises(DerivationError): 315 | UnnamedOpetopicSet.degen(s, "y") 316 | s = UnnamedOpetopicSet.degen(s, "x") 317 | self.assertIsNotNone(s.pastingDiagram.degeneracy) 318 | self.assertEqual(s.pastingDiagram.degeneracy, "x") 319 | self.assertIsNone(s.pastingDiagram.nodes) 320 | with self.assertRaises(DerivationError): 321 | UnnamedOpetopicSet.degen(s, "x") 322 | 323 | def test_shift(self): 324 | s = UnnamedOpetopicSet.graft( 325 | self.seq, 326 | UnnamedOpetopicSet.PastingDiagram.nonDegeneratePastingDiagram( 327 | UnnamedOpetope.OpetopicInteger(1), 328 | { 329 | UnnamedOpetope.Address.epsilon(1): "ac" 330 | })) 331 | with self.assertRaises(DerivationError): 332 | UnnamedOpetopicSet.shift(s, "ab", "A") 333 | with self.assertRaises(DerivationError): 334 | UnnamedOpetopicSet.shift(s, "bc", "A") 335 | UnnamedOpetopicSet.shift(s, "ac", "A") 336 | 337 | def test_graft(self): 338 | with self.assertRaises(DerivationError): 339 | UnnamedOpetopicSet.graft( 340 | UnnamedOpetopicSet.Sequent(), 341 | UnnamedOpetopicSet.PastingDiagram.degeneratePastingDiagram( 342 | UnnamedOpetope.OpetopicInteger(0), "x")) 343 | with self.assertRaises(DerivationError): 344 | UnnamedOpetopicSet.graft( 345 | UnnamedOpetopicSet.Sequent(), 346 | UnnamedOpetopicSet.PastingDiagram.nonDegeneratePastingDiagram( 347 | UnnamedOpetope.Arrow(), 348 | { 349 | UnnamedOpetope.Address.epsilon(0): "x" 350 | })) 351 | # Incorrect grafting: ab on top of cd 352 | with self.assertRaises(DerivationError): 353 | UnnamedOpetopicSet.graft( 354 | self.seq, 355 | UnnamedOpetopicSet.PastingDiagram.nonDegeneratePastingDiagram( 356 | UnnamedOpetope.OpetopicInteger(2), 357 | { 358 | UnnamedOpetope.Address.epsilon(1): "cd", 359 | UnnamedOpetope.Address.epsilon(0).shift(): "ab" 360 | })) 361 | # Correct grafting: ab on top of bc 362 | UnnamedOpetopicSet.graft( 363 | self.seq, 364 | UnnamedOpetopicSet.PastingDiagram.nonDegeneratePastingDiagram( 365 | UnnamedOpetope.OpetopicInteger(2), 366 | { 367 | UnnamedOpetope.Address.epsilon(1): "bc", 368 | UnnamedOpetope.Address.epsilon(0).shift(): "ab" 369 | })) 370 | 371 | def test_point(self): 372 | s = UnnamedOpetopicSet.point(UnnamedOpetopicSet.Sequent(), "x") 373 | s = UnnamedOpetopicSet.point(s, "y") 374 | self.assertEqual(len(s.context), 2) 375 | with self.assertRaises(DerivationError): 376 | UnnamedOpetopicSet.point(s, "x") 377 | s.pastingDiagram = UnnamedOpetopicSet.PastingDiagram.point() 378 | with self.assertRaises(DerivationError): 379 | UnnamedOpetopicSet.point(s, "z") 380 | 381 | 382 | if __name__ == "__main__": 383 | unittest.main(verbosity = 2) 384 | -------------------------------------------------------------------------------- /opetopy/NamedOpetopicSetM.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | .. module:: NamedOpetopicSetM 4 | :synopsis: Implementation of the mixed named approach for opetopic sets 5 | 6 | .. moduleauthor:: Cédric HT 7 | 8 | """ 9 | 10 | from copy import deepcopy 11 | from typing import Union 12 | 13 | from opetopy.common import * 14 | from opetopy import NamedOpetope 15 | from opetopy import NamedOpetopicSet 16 | 17 | 18 | def point(name: str) -> NamedOpetope.OCMT: 19 | """ 20 | The :math:`\\textbf{OptSet${}^!_m$}` :math:`\\texttt{point}` rule. 21 | Introduces a :math:`0`-variable with name ``x``. 22 | """ 23 | t = NamedOpetope.Typing(NamedOpetope.Term(NamedOpetope.Variable(name, 0)), 24 | NamedOpetope.Type([NamedOpetope.Term()])) 25 | return NamedOpetope.OCMT(NamedOpetope.EquationalTheory(), 26 | NamedOpetope.Context() + t) 27 | 28 | 29 | def degen(ocmt: NamedOpetope.OCMT, name: str) -> NamedOpetope.Sequent: 30 | """ 31 | The :math:`\\textbf{OptSet${}^!_m$}` :math:`\\texttt{degen}` rule. 32 | Introduces the degenerate pasting diagram on a given variable. 33 | """ 34 | var = ocmt.context[name] 35 | type = ocmt.context.typeOf(var) 36 | t = NamedOpetope.Typing( 37 | NamedOpetope.Term(var, True), 38 | NamedOpetope.Type([NamedOpetope.Term(var)] + type.terms)) 39 | return NamedOpetope.Sequent(deepcopy(ocmt.theory), deepcopy(ocmt.context), 40 | t) 41 | 42 | 43 | def pd(ocmt: NamedOpetope.OCMT, name: str) -> NamedOpetope.Sequent: 44 | """ 45 | The :math:`\\textbf{OptSet${}^!_m$}` :math:`\\texttt{pd}` rule. 46 | Introduces the trivial non degenerate pasting diagram on a given variable. 47 | """ 48 | var = ocmt.context[name] 49 | t = NamedOpetope.Typing(NamedOpetope.Term(var), ocmt.context.typeOf(var)) 50 | return NamedOpetope.Sequent(deepcopy(ocmt.theory), deepcopy(ocmt.context), 51 | t) 52 | 53 | 54 | def graft(seqt: NamedOpetope.Sequent, seqx: NamedOpetope.Sequent, 55 | name: str) -> NamedOpetope.Sequent: 56 | """ 57 | The :math:`\\textbf{OptSet${}^!_m$}` :math:`\\texttt{graft}` rule, which is 58 | the same as system :math:`\\textbf{Opt${}^!$}`'s :math:`\\texttt{graft}` 59 | rule. 60 | """ 61 | return NamedOpetope.graft(seqt, seqx, name) 62 | 63 | 64 | def shift(seq: NamedOpetope.Sequent, name: str) -> NamedOpetope.OCMT: 65 | """ 66 | The :math:`\\textbf{OptSet${}^!_m$}` :math:`\\texttt{shift}` rule. 67 | Takes a sequent ``seq`` typing a term ``t`` and introduces 68 | a new variable ``x`` having ``t`` as :math:`1`-source. 69 | """ 70 | n = seq.typing.term.dimension 71 | var = NamedOpetope.Variable(name, n + 1) 72 | if var in seq.context: 73 | raise DerivationError( 74 | "shift rule", 75 | "NamedOpetope.Variable {var} already typed in context", 76 | var=name) 77 | typing = NamedOpetope.Typing( 78 | NamedOpetope.Term(var), 79 | NamedOpetope.Type([seq.typing.term] + seq.typing.type.terms)) 80 | res = NamedOpetope.OCMT(deepcopy(seq.theory), 81 | deepcopy(seq.context) + typing) 82 | # targets of new variable 83 | for i in range(1, n + 2): 84 | res.context += NamedOpetope.Typing( 85 | NamedOpetope.Term(res.target(var, i)), 86 | NamedOpetope.Type(typing.type.terms[i:])) 87 | # additional theory 88 | termVar = seq.typing.term.variable 89 | if termVar is None: 90 | raise RuntimeError( 91 | "[shift rule] Premiss sequent types an invalid term. In valid " 92 | "proof trees, this should not happen") 93 | if seq.typing.term.degenerate: 94 | for i in range(n): 95 | res.theory += (res.target(var, i + 2), res.target(termVar, i)) 96 | elif n >= 1: 97 | seq.theory += (res.target(var, 2), res.target(termVar)) 98 | for gt in seq.typing.term.graftTuples(): 99 | seq.theory += (res.target(gt[1]), gt[0]) 100 | return res 101 | 102 | 103 | def zero() -> NamedOpetope.OCMT: 104 | """ 105 | The :math:`\\textbf{OptSet${}^!_m$}` :math:`\\texttt{zero}` rule, which is 106 | the same as system :math:`\\textbf{Opt${}^!$}`'s :math:`\\texttt{zero}` 107 | rule. 108 | """ 109 | return NamedOpetopicSet.zero() 110 | 111 | 112 | def sum(ocmt1: NamedOpetope.OCMT, 113 | ocmt2: NamedOpetope.OCMT) -> NamedOpetope.OCMT: 114 | """ 115 | The :math:`\\textbf{OptSet${}^!_m$}` :math:`\\texttt{sum}` rule, which is 116 | the same as system :math:`\\textbf{OptSet${}^!$}`'s :math:`\\texttt{sum}` 117 | rule. 118 | """ 119 | return NamedOpetopicSet.sum(ocmt1, ocmt2) 120 | 121 | 122 | def glue(ocmt: NamedOpetope.OCMT, aName: str, bName: str) -> NamedOpetope.OCMT: 123 | """ 124 | The :math:`\\textbf{OptSet${}^!_m$}` :math:`\\texttt{glue}` rule, which is 125 | the same as system :math:`\\textbf{OptSet${}^!$}`'s :math:`\\texttt{glue}` 126 | rule. 127 | """ 128 | return NamedOpetopicSet.glue(ocmt, aName, bName) 129 | 130 | 131 | class RuleInstance(AbstractRuleInstance): 132 | """ 133 | A rule instance of system :math:`\\textbf{OptSet${}^!_m$}`. 134 | """ 135 | def eval(self) -> Union[NamedOpetope.OCMT, NamedOpetope.Sequent]: 136 | """ 137 | Pure virtual method evaluating a proof tree and returning the final 138 | conclusion sequent, or raising an exception if the proof is invalid. 139 | """ 140 | raise NotImplementedError() 141 | 142 | 143 | class Point(RuleInstance): 144 | """ 145 | A class representing an instance of the ``point`` rule in a proof tree. 146 | """ 147 | 148 | variableName: str 149 | 150 | def __init__(self, name: str) -> None: 151 | self.variableName = name 152 | 153 | def __repr__(self) -> str: 154 | return "Point({})".format(self.variableName) 155 | 156 | def __str__(self) -> str: 157 | return "Point({})".format(self.variableName) 158 | 159 | def _toTex(self) -> str: 160 | """ 161 | Converts the proof tree in TeX code. This method should not be called 162 | directly, use :meth:`NamedOpetopicSetM.RuleInstance.toTex` 163 | instead. 164 | """ 165 | return "\\AxiomC{}\n\t\\RightLabel{\\texttt{point}}\n\t" + \ 166 | "\\UnaryInfC{$" + self.eval().toTex() + "$}" 167 | 168 | def eval(self) -> NamedOpetope.OCMT: 169 | """ 170 | Evaluates the proof tree, in this cases returns the point sequent by 171 | calling :func:`opetopy.NamedOpetopicSetM.point`. 172 | """ 173 | return point(self.variableName) 174 | 175 | 176 | class Degen(RuleInstance): 177 | """ 178 | A class representing an instance of the ``degen`` rule in a proof tree. 179 | """ 180 | 181 | proofTree: RuleInstance 182 | variableName: str 183 | 184 | def __init__(self, p: RuleInstance, name: str) -> None: 185 | """ 186 | Creates an instance of the ``degen`` rule and plugs proof tree ``p`` 187 | on the unique premise. 188 | """ 189 | self.proofTree = p 190 | self.variableName = name 191 | 192 | def __repr__(self) -> str: 193 | return "Degen({})".format(repr(self.proofTree)) 194 | 195 | def __str__(self) -> str: 196 | return "Degen({})".format(str(self.proofTree)) 197 | 198 | def _toTex(self) -> str: 199 | """ 200 | Converts the proof tree in TeX code. This method should not be called 201 | directly, use :meth:`NamedOpetopicSetM.RuleInstance.toTex` 202 | instead. 203 | """ 204 | return self.proofTree._toTex() + \ 205 | "\n\t\\RightLabel{\\texttt{degen}}\n\t\\UnaryInfC{$" + \ 206 | self.eval().toTex() + "$}" 207 | 208 | def eval(self) -> NamedOpetope.Sequent: 209 | """ 210 | Evaluates this instance of ``degen`` by first evaluating its premiss, 211 | and then applying :func:`opetopy.NamedOpetopicSetM.degen` on the 212 | resulting sequent. 213 | """ 214 | ocmt = self.proofTree.eval() 215 | if not isinstance(ocmt, NamedOpetope.OCMT): 216 | raise DerivationError("degen rule", 217 | "Premiss expected to be an OCMT") 218 | else: 219 | return degen(ocmt, self.variableName) 220 | 221 | 222 | class Pd(RuleInstance): 223 | """ 224 | A class representing an instance of the ``pd`` rule in a proof tree. 225 | """ 226 | 227 | proofTree: RuleInstance 228 | variableName: str 229 | 230 | def __init__(self, p: RuleInstance, name: str) -> None: 231 | """ 232 | Creates an instance of the ``pd`` rule and plugs proof tree ``p`` 233 | on the unique premise. 234 | """ 235 | self.proofTree = p 236 | self.variableName = name 237 | 238 | def __repr__(self) -> str: 239 | return "Pd({})".format(repr(self.proofTree)) 240 | 241 | def __str__(self) -> str: 242 | return "Pd({})".format(str(self.proofTree)) 243 | 244 | def _toTex(self) -> str: 245 | """ 246 | Converts the proof tree in TeX code. This method should not be called 247 | directly, use :meth:`NamedOpetopicSetM.RuleInstance.toTex` 248 | instead. 249 | """ 250 | return self.proofTree._toTex() + \ 251 | "\n\t\\RightLabel{\\texttt{pd}}\n\t\\UnaryInfC{$" + \ 252 | self.eval().toTex() + "$}" 253 | 254 | def eval(self) -> NamedOpetope.Sequent: 255 | """ 256 | Evaluates this instance of ``degen`` by first evaluating its premiss, 257 | and then applying :func:`opetopy.NamedOpetopicSetM.pd` on the 258 | resulting sequent. 259 | """ 260 | ocmt = self.proofTree.eval() 261 | if not isinstance(ocmt, NamedOpetope.OCMT): 262 | raise DerivationError("pd rule", "Premiss expected to be an OCMT") 263 | else: 264 | return pd(ocmt, self.variableName) 265 | 266 | 267 | class Graft(RuleInstance): 268 | """ 269 | A class representing an instance of the ``graft`` rule in a proof tree. 270 | """ 271 | 272 | proofTree1: RuleInstance 273 | proofTree2: RuleInstance 274 | variableName: str 275 | 276 | def __init__(self, p1: RuleInstance, p2: RuleInstance, a: str) -> None: 277 | """ 278 | Creates an instance of the ``graft`` rule at variable ``a``, and plugs 279 | proof tree ``p1`` on the first premise, and ``p2`` on the second. 280 | 281 | :see: :func:`opetopy.NamedOpetopicSetM.graft`. 282 | """ 283 | self.proofTree1 = p1 284 | self.proofTree2 = p2 285 | self.variableName = a 286 | 287 | def __repr__(self) -> str: 288 | return "Graft({p1}, {p2}, {a})".format(p1=repr(self.proofTree1), 289 | p2=repr(self.proofTree2), 290 | a=self.variableName) 291 | 292 | def __str__(self) -> str: 293 | return "Graft({p1}, {p2}, {a})".format(p1=str(self.proofTree1), 294 | p2=str(self.proofTree2), 295 | a=self.variableName) 296 | 297 | def _toTex(self) -> str: 298 | """ 299 | Converts the proof tree in TeX code. This method should not be called 300 | directly, use :meth:`NamedOpetopicSetM.RuleInstance.toTex` 301 | instead. 302 | """ 303 | return self.proofTree1._toTex() + "\n\t" + self.proofTree2._toTex() + \ 304 | "\n\t\\RightLabel{\\texttt{graft-}$" + \ 305 | self.variableName + "$}\n\t\\BinaryInfC{$" + \ 306 | self.eval().toTex() + "$}" 307 | 308 | def eval(self) -> NamedOpetope.Sequent: 309 | """ 310 | Evaluates this instance of ``graft`` by first evaluating its premises, 311 | and then applying :func:`opetopy.NamedOpetopicSetM.graft` at variable 312 | ``self.variableName`` on the resulting sequents. 313 | """ 314 | seq1 = self.proofTree1.eval() 315 | seq2 = self.proofTree2.eval() 316 | if not isinstance(seq1, NamedOpetope.Sequent): 317 | raise DerivationError("graft rule", 318 | "First premiss expected to be a sequent") 319 | elif not isinstance(seq2, NamedOpetope.Sequent): 320 | raise DerivationError("graft rule", 321 | "Second premiss expected to be a sequent") 322 | else: 323 | return graft(seq1, seq2, self.variableName) 324 | 325 | 326 | class Shift(RuleInstance): 327 | """ 328 | A class representing an instance of the ``shift`` rule in a proof tree. 329 | """ 330 | 331 | proofTree: RuleInstance 332 | variableName: str 333 | 334 | def __init__(self, p: RuleInstance, name: str) -> None: 335 | self.proofTree = p 336 | self.variableName = name 337 | 338 | def __repr__(self) -> str: 339 | return "Shift({}, {})".format(repr(self.proofTree), self.variableName) 340 | 341 | def __str__(self) -> str: 342 | return "Shift({}, {})".format(str(self.proofTree), self.variableName) 343 | 344 | def _toTex(self) -> str: 345 | """ 346 | Converts the proof tree in TeX code. This method should not be called 347 | directly, use :meth:`NamedOpetopicSetM.RuleInstance.toTex` 348 | instead. 349 | """ 350 | return self.proofTree._toTex() + \ 351 | "\n\t\\RightLabel{\\texttt{shift}}\n\t\\UnaryInfC{$" + \ 352 | self.eval().toTex() + "$}" 353 | 354 | def eval(self) -> NamedOpetope.OCMT: 355 | seq = self.proofTree.eval() 356 | if not isinstance(seq, NamedOpetope.Sequent): 357 | raise DerivationError("shift rule", 358 | "Premiss expected to be an sequent") 359 | else: 360 | return shift(seq, self.variableName) 361 | 362 | 363 | class Zero(RuleInstance): 364 | """ 365 | A class representing an instance of the ``zero`` rule in a proof tree. 366 | """ 367 | def _toTex(self) -> str: 368 | """ 369 | Converts the proof tree in TeX code. This method should not be called 370 | directly, use :meth:`NamedOpetopicSetM.RuleInstance.toTex` 371 | instead. 372 | """ 373 | return "\\AxiomC{}\n\t\\RightLabel{\\texttt{zero}\n\t\\UnaryInfC{$" + \ 374 | self.eval().toTex() + "$}" 375 | 376 | def __repr__(self) -> str: 377 | return "Zero()" 378 | 379 | def __str__(self) -> str: 380 | return "Zero()" 381 | 382 | def eval(self) -> NamedOpetope.OCMT: 383 | return zero() 384 | 385 | 386 | class Sum(RuleInstance): 387 | """ 388 | A class representing an instance of the ``sum`` rule in a proof tree. 389 | """ 390 | 391 | proofTree1: RuleInstance 392 | proofTree2: RuleInstance 393 | 394 | def __init__(self, p1: RuleInstance, p2: RuleInstance) -> None: 395 | """ 396 | Creates an instance of the ``graft`` rule at variable ``a``, and plugs 397 | proof tree ``p1`` on the first premise, and ``p2`` on the second. 398 | 399 | :see: :func:`opetopy.NamedOpetope.graft`. 400 | """ 401 | self.proofTree1 = p1 402 | self.proofTree2 = p2 403 | 404 | def __repr__(self) -> str: 405 | return "Sum({p1}, {p2})".format(p1=repr(self.proofTree1), 406 | p2=repr(self.proofTree2)) 407 | 408 | def __str__(self) -> str: 409 | return "Sum({p1}, {p2})".format(p1=str(self.proofTree1), 410 | p2=str(self.proofTree2)) 411 | 412 | def _toTex(self) -> str: 413 | """ 414 | Converts the proof tree in TeX code. This method should not be called 415 | directly, use :meth:`NamedOpetopicSetM.RuleInstance.toTex` 416 | instead. 417 | """ 418 | return self.proofTree1._toTex() + "\n\t" + self.proofTree2._toTex() + \ 419 | "\n\t\\RightLabel{\\texttt{sum}" + \ 420 | "}\n\t\\BinaryInfC{$" + self.eval().toTex() + "$}" 421 | 422 | def eval(self) -> NamedOpetope.OCMT: 423 | ocmt1 = self.proofTree1.eval() 424 | ocmt2 = self.proofTree2.eval() 425 | if not isinstance(ocmt1, NamedOpetope.OCMT): 426 | raise DerivationError("sum rule", 427 | "First premiss expected to be an OCMT") 428 | elif not isinstance(ocmt2, NamedOpetope.OCMT): 429 | raise DerivationError("sum rule", 430 | "Second premiss expected to be an OCMT") 431 | else: 432 | return sum(ocmt1, ocmt2) 433 | 434 | 435 | class Glue(RuleInstance): 436 | """ 437 | A class representing an instance of the ``glue`` rule in a proof tree. 438 | """ 439 | 440 | proofTree: RuleInstance 441 | aName: str 442 | bName: str 443 | 444 | def __init__(self, p: RuleInstance, a: str, b: str) -> None: 445 | self.proofTree = p 446 | self.aName = a 447 | self.bName = b 448 | 449 | def __repr__(self) -> str: 450 | return "Glue({p}, {a}, {b})".format(p=repr(self.proofTree), 451 | a=repr(self.aName), 452 | b=repr(self.bName)) 453 | 454 | def __str__(self) -> str: 455 | return "Glue({p}, {a}, {b})".format(p=str(self.proofTree), 456 | a=str(self.aName), 457 | b=str(self.bName)) 458 | 459 | def _toTex(self) -> str: 460 | """ 461 | Converts the proof tree in TeX code. This method should not be called 462 | directly, use :meth:`NamedOpetopicSetM.RuleInstance.toTex` 463 | instead. 464 | """ 465 | return self.proofTree._toTex() + \ 466 | "\n\t\\RightLabel{\\texttt{glue-}$(" + self.aName + \ 467 | " = " + self.bName + ")$}\n\t\\UnaryInfC{$" + \ 468 | self.eval().toTex() + "$}" 469 | 470 | def eval(self) -> NamedOpetope.OCMT: 471 | """ 472 | Evaluates this instance of ``graft`` by first evaluating its premises, 473 | and then applying :func:`opetopy.NamedOpetopicSetM.graft` at variable 474 | `self.a` on the resulting sequents. 475 | """ 476 | ocmt = self.proofTree.eval() 477 | if not isinstance(ocmt, NamedOpetope.OCMT): 478 | raise DerivationError("pd rule", "Premiss expected to be an OCMT") 479 | else: 480 | return glue(ocmt, self.aName, self.bName) 481 | 482 | 483 | class DegenFill(RuleInstance): 484 | """ 485 | A convenient class chaining an instance of the ``degen`` rule with an 486 | instance of the ``shift`` rule. 487 | """ 488 | 489 | proofTree: RuleInstance 490 | 491 | def __init__(self, p: RuleInstance, dname: str, fname: str) -> None: 492 | self.proofTree = Shift(Degen(p, dname), fname) 493 | 494 | def __repr__(self) -> str: 495 | return repr(self.proofTree) 496 | 497 | def __str__(self) -> str: 498 | return str(self.proofTree) 499 | 500 | def _toTex(self) -> str: 501 | """ 502 | Converts the proof tree in TeX code. This method should not be called 503 | directly, use :meth:`NamedOpetopicSetM.RuleInstance.toTex` 504 | instead. 505 | """ 506 | return self.proofTree._toTex() 507 | 508 | def eval(self) -> NamedOpetope.OCMT: 509 | return self.proofTree.eval() 510 | -------------------------------------------------------------------------------- /opetopy/UnnamedOpetopicCategory.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | .. module:: UnnamedOpetopicCategory 4 | :synopsis: Implementation of opetopic categories and groupoids using the 5 | unnamed approach to opetopes and opetopic sets 6 | 7 | .. moduleauthor:: Cédric HT 8 | 9 | """ 10 | 11 | from copy import deepcopy 12 | from typing import Dict, List, Optional, Set 13 | 14 | from opetopy.common import * 15 | from opetopy import UnnamedOpetope 16 | from opetopy import UnnamedOpetopicSet 17 | 18 | 19 | class Type(UnnamedOpetopicSet.Type): 20 | """ 21 | Similar to :class:`opetopy.UnnamedOpetopicSet.Type` except information 22 | about the universality of faces is also stored. 23 | """ 24 | 25 | sourceUniversal: Set[UnnamedOpetope.Address] 26 | targetUniversal: bool 27 | 28 | def __init__(self, source: UnnamedOpetopicSet.PastingDiagram, 29 | target: Optional[UnnamedOpetopicSet.Variable]) -> None: 30 | """ 31 | Inits the type as in :class:`opetopy.UnnamedOpetopicSet.Type.__init__`, 32 | and sets all faces (sources and target) as non universal. 33 | """ 34 | super().__init__(source, target) 35 | self.sourceUniversal = set() 36 | self.targetUniversal = False 37 | 38 | def __repr__(self) -> str: 39 | return str(self) 40 | 41 | def __str__(self) -> str: 42 | srcstr = str() 43 | if self.source.degeneracy is None: 44 | if self.source.nodes is None: 45 | raise RuntimeError("[Pasting diagram, to string] Both the " 46 | "degeneracy and node dict of the pasting " 47 | "diagram are None. In valid derivations, " 48 | "this should not happen") 49 | if self.source.shape == UnnamedOpetope.point().source: 50 | srcstr = "⧫" 51 | else: 52 | lines = [] # type: List[str] 53 | for addr in self.source.nodes.keys(): 54 | if self.isSourceUniversal(addr): 55 | lines += [ 56 | str(addr) + " ← ∀" + str(self.source.nodes[addr]) 57 | ] 58 | else: 59 | lines += [ 60 | str(addr) + " ← " + str(self.source.nodes[addr]) 61 | ] 62 | srcstr = "{" + ", ".join(lines) + "}" 63 | else: 64 | srcstr = "{{" + str(self.source.degeneracy) + "}}" 65 | if self.isTargetUniversal(): 66 | return srcstr + " → ∀" + str(self.target) 67 | else: 68 | return srcstr + " → " + str(self.target) 69 | 70 | def isSourceUniversal(self, addr: UnnamedOpetope.Address) -> bool: 71 | """ 72 | Tells wether this type is source universal at source address ``addr``. 73 | """ 74 | return (addr in self.sourceUniversal) 75 | 76 | def isTargetUniversal(self) -> bool: 77 | """ 78 | Tells wether this type is target universal. 79 | """ 80 | return self.targetUniversal 81 | 82 | 83 | def isTargetUniversal(t: UnnamedOpetopicSet.Type) -> bool: 84 | """ 85 | This convenient function allows to know if an instance of 86 | :class:`opetopy.UnnamedOpetopicSet.Type` is target universal, regardless of 87 | wether or not it is an actual instance of 88 | :class:`opetopy.UnnamedOpetopicCategory.Type`. 89 | """ 90 | if isinstance(t, Type): 91 | return t.isTargetUniversal() 92 | else: 93 | return False 94 | 95 | 96 | def isSourceUniversal(t: UnnamedOpetopicSet.Type, 97 | addr: UnnamedOpetope.Address) -> bool: 98 | """ 99 | This convenient function allows to know if an instance of 100 | :class:`opetopy.UnnamedOpetopicSet.Type` is source universal at a given 101 | address, regardless of wether or not it is an actual instance of 102 | :class:`opetopy.UnnamedOpetopicCategory.Type`. 103 | """ 104 | if isinstance(t, Type): 105 | return t.isSourceUniversal(addr) 106 | else: 107 | return False 108 | 109 | 110 | def tfill(seq: UnnamedOpetopicSet.Sequent, targetName: str, 111 | fillerName: str) -> UnnamedOpetopicSet.Sequent: 112 | """ 113 | This function takes a :class:`opetopy.UnnamedOpetopicSet.Sequent`, (recall 114 | that the context of a sequent derivable in :math:`\\textbf{OptSet${}^?$}` 115 | is a finite opetopic set) typing a pasting diagram :math:`\\mathbf{P}`, and 116 | solves the Kan filler problem by adding 117 | 118 | * a new cell :math:`t` with name ``targetName``; 119 | * a new cell :math:`\\alpha : \\mathbf{P} \\longrightarrow t` with name 120 | ``fillerName``. 121 | 122 | """ 123 | if seq.pastingDiagram is None: 124 | raise DerivationError( 125 | "Kan filling, target", 126 | "Argument sequent expecting to type a pasting diagram") 127 | 128 | # Source of alpha 129 | P = seq.pastingDiagram 130 | tPshapeProof = UnnamedOpetope.ProofTree(P.shapeTarget().toDict()) 131 | 132 | # Start deriving 133 | res = deepcopy(seq) 134 | res.pastingDiagram = None 135 | 136 | # Derive t 137 | if P.shape.dimension - 1 == 0: 138 | # t is a point 139 | res = UnnamedOpetopicSet.point(res, targetName) 140 | else: 141 | # Set u, target of t 142 | if P.shape.isDegenerate: 143 | u = P.degeneracyVariable() 144 | else: 145 | u = seq.context.target( 146 | P.source(UnnamedOpetope.address([], P.shape.dimension - 1))) 147 | # Derive Q, source of t 148 | if P.shapeTarget().isDegenerate: 149 | Q = UnnamedOpetopicSet.pastingDiagram(tPshapeProof, 150 | seq.context.target(u)) 151 | else: 152 | nodes = {} # type: Dict[UnnamedOpetope.Address, str] 153 | if P.shape.isDegenerate: 154 | nodes[UnnamedOpetope.address([], P.shape.dimension - 2)] = \ 155 | P.degeneracyVariable() 156 | else: 157 | readdress = P.shapeProof.eval().context 158 | for l in P.shape.leafAddresses(): 159 | p, q = l.edgeDecomposition() 160 | nodes[readdress(l)] = seq.context.source(P[p], q) 161 | Q = UnnamedOpetopicSet.pastingDiagram(tPshapeProof, nodes) 162 | if Q.shape.isDegenerate: 163 | res = UnnamedOpetopicSet.degen(res, Q.degeneracyVariable()) 164 | else: 165 | res = UnnamedOpetopicSet.graft(res, Q) 166 | # Derive t, target of alpha 167 | res = UnnamedOpetopicSet.shift(res, u, targetName) 168 | 169 | # Derive P, source of alpha 170 | if P.shape.isDegenerate: 171 | res = UnnamedOpetopicSet.degen(res, u) 172 | else: 173 | res = UnnamedOpetopicSet.graft(res, P) 174 | 175 | # Derive alpha 176 | res = UnnamedOpetopicSet.shift(res, targetName, fillerName) 177 | 178 | # Mark t as universal in the type of alpha 179 | rawFillerType = res.context[fillerName].type 180 | fillerType = Type(rawFillerType.source, rawFillerType.target) 181 | fillerType.targetUniversal = True 182 | res.context[fillerName].type = fillerType 183 | 184 | # Done 185 | return res 186 | 187 | 188 | def tuniv(seq: UnnamedOpetopicSet.Sequent, tuCell: str, cell: str, 189 | factorizationName: str, 190 | fillerName: str) -> UnnamedOpetopicSet.Sequent: 191 | """ 192 | From a target universal cell :math:`\\alpha : \\mathbf{P} \\longrightarrow 193 | t` (whose name is ``tuCell``), and another cell :math:`\\beta : \\mathbf{P} 194 | \\longrightarrow u`, creates the universal factorization. 195 | """ 196 | # Inits 197 | typealpha = seq.context[tuCell].type 198 | typebeta = seq.context[cell].type 199 | P = typealpha.source 200 | targetalpha = typealpha.target 201 | targetbeta = typebeta.target 202 | 203 | # Checks 204 | if seq.pastingDiagram is not None: 205 | raise DerivationError("Apply target univ. prop.", 206 | "Sequent cannot type a pasting diagram") 207 | elif not isTargetUniversal(typealpha): 208 | raise DerivationError("Apply target univ. prop.", 209 | "First cell is expected to be target universal") 210 | elif typebeta.source != P: 211 | raise DerivationError( 212 | "Apply target univ. prop.", 213 | "Cells are expected to have the same source pasting diagram") 214 | elif targetalpha is None or targetbeta is None: 215 | raise RuntimeError( 216 | "[Apply target univ. prop.] Target universal cell is a point. In " 217 | "valid derivations, this should not happen") 218 | 219 | # Derive the factorization cell 220 | n = targetalpha.shape.dimension 221 | res = UnnamedOpetopicSet.graft( 222 | deepcopy(seq), 223 | UnnamedOpetopicSet.pastingDiagram( 224 | UnnamedOpetope.Shift(targetalpha.shapeProof), 225 | {UnnamedOpetope.address([], n): targetalpha.name})) 226 | res = UnnamedOpetopicSet.shift(res, targetbeta.name, factorizationName) 227 | 228 | # Derive the filler 229 | res = UnnamedOpetopicSet.graft( 230 | res, 231 | UnnamedOpetopicSet.pastingDiagram( 232 | UnnamedOpetope.Graft( 233 | UnnamedOpetope.Shift( 234 | UnnamedOpetope.Shift(targetalpha.shapeProof)), 235 | P.shapeProof, UnnamedOpetope.address([[]], n + 1)), { 236 | UnnamedOpetope.address([], n + 1): factorizationName, 237 | UnnamedOpetope.address([[]], n + 1): tuCell 238 | })) 239 | res = UnnamedOpetopicSet.shift(res, cell, fillerName) 240 | 241 | # Mark the filler as target universal and source universal at the facto. 242 | rawFillerType = res.context[fillerName].type 243 | fillerType = Type(rawFillerType.source, rawFillerType.target) 244 | fillerType.targetUniversal = True 245 | fillerType.sourceUniversal.add(UnnamedOpetope.address([], n + 1)) 246 | res.context[fillerName].type = fillerType 247 | 248 | # Done 249 | return res 250 | 251 | 252 | def suniv(seq: UnnamedOpetopicSet.Sequent, suCellName: str, cellName: str, 253 | addr: UnnamedOpetope.Address, factorizationName: str, 254 | fillerName: str) -> UnnamedOpetopicSet.Sequent: 255 | """ 256 | From 257 | 258 | * an address :math:`[p]` (argument ``addr``); 259 | * a cell :math:`\\alpha : \\forall_{[p]} \\mathbf{P} \\longrightarrow u` 260 | (with name ``suCellName``); 261 | * a cell :math:`\\beta : \\mathbf{P'} \\longrightarrow u` (with name 262 | ``cellName``), where :math:`\\mathbf{P'}` is :math:`\\mathbf{P}` except 263 | at address :math:`[p]` where it is :math:`s`; 264 | 265 | applies the source universal property of :math:`\\alpha` at :math:`[p]` 266 | over :math:`\\beta`, thus creating 267 | 268 | * a factorization cell :math:`\\xi : s \\longrightarrow \\mathsf{s}_{[p]} 269 | \\mathbf{P}`; 270 | * a filler :math:`A`, target universal, and source universal at 271 | :math:`\\xi`, i.e. at address :math:`[[p]]`. 272 | """ 273 | 274 | # Inits 275 | alphatype = seq.context[suCellName].type 276 | betatype = seq.context[cellName].type 277 | P = alphatype.source 278 | Q = betatype.source 279 | u = alphatype.target 280 | 281 | # Checks & inits 282 | if seq.pastingDiagram is not None: 283 | raise DerivationError( 284 | "Apply source univ. prop.", 285 | "Sequent expected to not type a pasting diagram") 286 | elif u is None: 287 | raise RuntimeError("[Apply source univ. prop.] Source universal cell " 288 | "{sucell} is a point. In valid derivations, this " 289 | "should not happen".format(sucell=suCellName)) 290 | elif P.nodes is None: 291 | raise DerivationError( 292 | "Apply source univ. prop.", 293 | "Source universal cell {sucell} cannot be degenerate", 294 | sucell=suCellName) 295 | elif Q.nodes is None: 296 | raise DerivationError("Apply source univ. prop.", 297 | "Cell {cell} cannot be degenerate", 298 | cell=cellName) 299 | elif addr not in P.nodes.keys(): 300 | raise DerivationError("Apply source univ. prop.", 301 | "Address {addr} not in source of {sucell}", 302 | addr=addr, 303 | sucell=suCellName) 304 | elif betatype.target != u: 305 | raise DerivationError( 306 | "Apply source univ. prop.", 307 | "Cells {sucell} and {cell} are not compatible: targets differ", 308 | cell=cellName, 309 | sucell=suCellName) 310 | elif P.nodes.keys() != Q.nodes.keys(): 311 | raise DerivationError( 312 | "Apply source univ. prop.", 313 | "Cells {sucell} and {cell} are not compatible: source pasting " 314 | "diagrams do not have the same addresses", 315 | cell=cellName, 316 | sucell=suCellName) 317 | for a in P.nodes.keys(): 318 | if a != addr and P.nodes[a] != Q.nodes[a]: 319 | raise DerivationError( 320 | "Apply source univ. prop.", 321 | "Cells {sucell} and {cell} are not compatible: source pasting " 322 | "diagrams do not agree on address {a}", 323 | cell=cellName, 324 | sucell=suCellName, 325 | a=a) 326 | 327 | # Derive xi 328 | xishapeproof = seq.context[Q.source(addr)].type.source.shapeProof 329 | res = UnnamedOpetopicSet.graft( 330 | seq, 331 | UnnamedOpetopicSet.pastingDiagram(UnnamedOpetope.Shift(xishapeproof), { 332 | UnnamedOpetope.address([], Q.shape.dimension - 1): 333 | Q.source(addr) 334 | })) 335 | res = UnnamedOpetopicSet.shift(res, P.source(addr), factorizationName) 336 | 337 | # Derive A 338 | omega = UnnamedOpetope.Graft(UnnamedOpetope.Shift(P.shapeProof), 339 | UnnamedOpetope.Shift(xishapeproof), 340 | addr.shift()) 341 | res = UnnamedOpetopicSet.graft( 342 | res, 343 | UnnamedOpetopicSet.pastingDiagram( 344 | omega, { 345 | UnnamedOpetope.address([], P.shape.dimension): suCellName, 346 | addr.shift(): factorizationName 347 | })) 348 | res = UnnamedOpetopicSet.shift(res, cellName, fillerName) 349 | 350 | # Mark A as source universal at xi and target universal 351 | rawFillerType = res.context[fillerName].type 352 | fillerType = Type(rawFillerType.source, rawFillerType.target) 353 | fillerType.targetUniversal = True 354 | fillerType.sourceUniversal.add(addr.shift()) 355 | res.context[fillerName].type = fillerType 356 | 357 | # Done 358 | return res 359 | 360 | 361 | def tclose(seq: UnnamedOpetopicSet.Sequent, 362 | tuCell: str) -> UnnamedOpetopicSet.Sequent: 363 | """ 364 | From a target universal cell :math:`A : \\mathbf{P} \\longrightarrow 365 | \\forall u` such that all its faces are target universal but one, turns 366 | that one target universal as well. 367 | """ 368 | 369 | # Inits 370 | P = seq.context[tuCell].type.source 371 | u = seq.context[tuCell].type.target 372 | 373 | # Checks 374 | if seq.pastingDiagram is not None: 375 | raise DerivationError( 376 | "Apply target univ. closure", 377 | "Sequent expected to not type a pasting diagram") 378 | elif u is None: 379 | raise RuntimeError("[Apply target univ. closure] Target universal " 380 | "cell {cell} is a point. In valid derivations, " 381 | "this should not happen".format(cell=tuCell)) 382 | 383 | # If P is degenerate, make u target universal 384 | if P.shape.isDegenerate: 385 | res = deepcopy(seq) 386 | rawTargetType = res.context[u.name].type 387 | targetType = Type(rawTargetType.source, rawTargetType.target) 388 | targetType.targetUniversal = True 389 | res.context[u.name].type = targetType 390 | return res 391 | 392 | # Get non target universal source address (if any) 393 | if P.nodes is None: 394 | raise RuntimeError("[Apply target univ. closure] Target universal " 395 | "cell {cell} non degenerate, yet it source pasting " 396 | "diagram has no nodes. In valid derivations, this " 397 | "should not happen".format(cell=tuCell)) 398 | else: 399 | nonTuSource = None # type: Optional[UnnamedOpetope.Address] 400 | for addr in P.nodes.keys(): 401 | if not isTargetUniversal(seq.context[P.source(addr)].type): 402 | if nonTuSource is None: 403 | nonTuSource = addr 404 | else: 405 | raise DerivationError( 406 | "Apply target univ. closure", 407 | "Source pasting diagram has at least two non target " 408 | "universal sources: {addr1} and {addr2}".format( 409 | addr1=nonTuSource, addr2=addr)) 410 | 411 | if isTargetUniversal(seq.context[u.name].type): 412 | if nonTuSource is None: 413 | raise DerivationError( 414 | "Apply target univ. closure", 415 | "All faces of source pasting diagram are already target " 416 | "universal. You can just remove this rule instance") 417 | # Make source at nonTuSource target universal 418 | res = deepcopy(seq) 419 | rawSourceType = res.context[P.source(nonTuSource)].type 420 | sourceType = Type(rawSourceType.source, rawSourceType.target) 421 | sourceType.targetUniversal = True 422 | res.context[P.source(nonTuSource)].type = sourceType 423 | return res 424 | else: 425 | if nonTuSource is not None: 426 | raise DerivationError( 427 | "Apply target univ. closure", 428 | "Source pasting diagram has at least two non target universal " 429 | "faces: target and {addr}".format(addr=nonTuSource)) 430 | # Make u target universal 431 | res = deepcopy(seq) 432 | rawTargetType = res.context[u.name].type 433 | targetType = Type(rawTargetType.source, rawTargetType.target) 434 | targetType.targetUniversal = True 435 | res.context[u.name].type = targetType 436 | return res 437 | 438 | 439 | class TFill(UnnamedOpetopicSet.RuleInstance): 440 | """ 441 | A class representing an instance of the :math:`\\texttt{tfill}` rule in a 442 | proof tree. 443 | """ 444 | 445 | fillerName: str 446 | proofTree: UnnamedOpetopicSet.RuleInstance 447 | targetName: str 448 | 449 | def __init__(self, p: UnnamedOpetopicSet.RuleInstance, targetName: str, 450 | fillerName: str) -> None: 451 | self.fillerName = fillerName 452 | self.proofTree = p 453 | self.targetName = targetName 454 | 455 | def __str__(self) -> str: 456 | return "TFill({p}, {t}, {f})".format(p=str(self.proofTree), 457 | t=self.targetName, 458 | f=self.fillerName) 459 | 460 | def __repr__(self) -> str: 461 | return "TFill({p},{t},{f})".format(p=repr(self.proofTree), 462 | t=self.targetName, 463 | f=self.fillerName) 464 | 465 | def _toTex(self) -> str: 466 | """ 467 | Converts the proof tree in TeX code. This method should not be called 468 | directly, use :meth:`UnnamedOpetopicSet.RuleInstance.toTex` instead. 469 | """ 470 | return self.proofTree._toTex() + \ 471 | "\n\t\\RightLabel{\\texttt{tfill}}\n\t\\UnaryInfC{$" + \ 472 | self.eval().toTex() + "$}" 473 | 474 | def eval(self) -> UnnamedOpetopicSet.Sequent: 475 | return tfill(self.proofTree.eval(), self.targetName, self.fillerName) 476 | 477 | 478 | class TUniv(UnnamedOpetopicSet.RuleInstance): 479 | """ 480 | A class representing an instance of the :math:`\\texttt{tuniv}` rule in a 481 | proof tree. 482 | """ 483 | 484 | cellName: str 485 | factorizationName: str 486 | fillerName: str 487 | proofTree: UnnamedOpetopicSet.RuleInstance 488 | tuCellName: str 489 | 490 | def __init__(self, p: UnnamedOpetopicSet.RuleInstance, tuCell: str, 491 | cell: str, factorizationName: str, fillerName: str) -> None: 492 | self.cellName = cell 493 | self.factorizationName = factorizationName 494 | self.fillerName = fillerName 495 | self.proofTree = p 496 | self.tuCellName = tuCell 497 | 498 | def __str__(self) -> str: 499 | return "TUniv({p}, {tu}, {c})".format(p=str(self.proofTree), 500 | tu=self.tuCellName, 501 | c=self.cellName) 502 | 503 | def __repr__(self) -> str: 504 | return "TUniv({p},{tu},{c})".format(p=repr(self.proofTree), 505 | tu=self.tuCellName, 506 | c=self.cellName) 507 | 508 | def _toTex(self) -> str: 509 | """ 510 | Converts the proof tree in TeX code. This method should not be called 511 | directly, use :meth:`UnnamedOpetopicSet.RuleInstance.toTex` instead. 512 | """ 513 | return self.proofTree._toTex() + \ 514 | "\n\t\\RightLabel{\\texttt{tuniv-$" + self.tuCellName + "$/$" + \ 515 | self.cellName + "$}}\n\t\\UnaryInfC{$" + self.eval().toTex() + "$}" 516 | 517 | def eval(self) -> UnnamedOpetopicSet.Sequent: 518 | return tuniv(self.proofTree.eval(), self.tuCellName, self.cellName, 519 | self.factorizationName, self.fillerName) 520 | 521 | 522 | class SUniv(UnnamedOpetopicSet.RuleInstance): 523 | """ 524 | A class representing an instance of the :math:`\\texttt{suniv}` rule in a 525 | proof tree. 526 | """ 527 | 528 | address: UnnamedOpetope.Address 529 | cellName: str 530 | factorizationName: str 531 | fillerName: str 532 | proofTree: UnnamedOpetopicSet.RuleInstance 533 | suCellName: str 534 | 535 | def __init__(self, p: UnnamedOpetopicSet.RuleInstance, suCellName: str, 536 | cellName: str, addr: UnnamedOpetope.Address, 537 | factorizationName: str, fillerName: str) -> None: 538 | self.address = addr 539 | self.cellName = cellName 540 | self.factorizationName = factorizationName 541 | self.fillerName = fillerName 542 | self.proofTree = p 543 | self.suCellName = suCellName 544 | 545 | def __str__(self) -> str: 546 | return "SUniv({p}, {su}, {c})".format(p=str(self.proofTree), 547 | su=self.suCellName, 548 | c=self.cellName) 549 | 550 | def __repr__(self) -> str: 551 | return "SUniv({p},{su},{c})".format(p=repr(self.proofTree), 552 | su=self.suCellName, 553 | c=self.cellName) 554 | 555 | def _toTex(self) -> str: 556 | """ 557 | Converts the proof tree in TeX code. This method should not be called 558 | directly, use :meth:`UnnamedOpetopicSet.RuleInstance.toTex` instead. 559 | """ 560 | return self.proofTree._toTex() + \ 561 | "\n\t\\RightLabel{\\texttt{suniv-$" + self.suCellName + "$/$" + \ 562 | self.cellName + "$}}\n\t\\UnaryInfC{$" + self.eval().toTex() + "$}" 563 | 564 | def eval(self) -> UnnamedOpetopicSet.Sequent: 565 | return suniv(self.proofTree.eval(), self.suCellName, self.cellName, 566 | self.address, self.factorizationName, self.fillerName) 567 | 568 | 569 | class TClose(UnnamedOpetopicSet.RuleInstance): 570 | """ 571 | A class representing an instance of the :math:`\\texttt{tclose}` rule in a 572 | proof tree. 573 | """ 574 | 575 | proofTree: UnnamedOpetopicSet.RuleInstance 576 | tuCellName: str 577 | 578 | def __init__(self, p: UnnamedOpetopicSet.RuleInstance, 579 | tuCell: str) -> None: 580 | self.proofTree = p 581 | self.tuCellName = tuCell 582 | 583 | def __str__(self) -> str: 584 | return "TClose({p}, {tu})".format(p=str(self.proofTree), 585 | tu=self.tuCellName) 586 | 587 | def __repr__(self) -> str: 588 | return "TClose({p},{tu})".format(p=repr(self.proofTree), 589 | tu=self.tuCellName) 590 | 591 | def _toTex(self) -> str: 592 | """ 593 | Converts the proof tree in TeX code. This method should not be called 594 | directly, use :meth:`UnnamedOpetopicSet.RuleInstance.toTex` instead. 595 | """ 596 | return self.proofTree._toTex() + \ 597 | "\n\t\\RightLabel{\\texttt{tclose-$" + self.tuCellName + \ 598 | "$}}\n\t\\UnaryInfC{$" + self.eval().toTex() + "$}" 599 | 600 | def eval(self) -> UnnamedOpetopicSet.Sequent: 601 | return tclose(self.proofTree.eval(), self.tuCellName) 602 | -------------------------------------------------------------------------------- /tests/unittest_unnamedopetope.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import sys 4 | sys.path.insert(0, "../") 5 | 6 | from opetopy.common import DerivationError 7 | 8 | from opetopy import UnnamedOpetope 9 | 10 | 11 | class Test_UnnamedOpetope_Address(unittest.TestCase): 12 | 13 | def setUp(self): 14 | self.a = UnnamedOpetope.Address.epsilon(0) 15 | self.b = UnnamedOpetope.Address.epsilon(1) 16 | self.c = UnnamedOpetope.Address.fromListOfAddresses([self.a]) 17 | self.d = UnnamedOpetope.Address.fromListOfAddresses([self.a, self.a]) 18 | self.e = UnnamedOpetope.Address.fromList([['*'], ['*', '*'], []], 2) 19 | 20 | def test___add__(self): 21 | with self.assertRaises(DerivationError): 22 | self.b + self.b 23 | with self.assertRaises(DerivationError): 24 | self.a + self.b 25 | self.assertEqual( 26 | UnnamedOpetope.Address.fromListOfAddresses([self.a, self.a]), 27 | UnnamedOpetope.Address.fromListOfAddresses([self.a]) + self.a 28 | ) 29 | self.assertNotEqual( 30 | UnnamedOpetope.Address.fromListOfAddresses( 31 | [self.a, self.a, self.a]), 32 | UnnamedOpetope.Address.fromListOfAddresses([self.a]) + self.a 33 | ) 34 | 35 | def test___eq__(self): 36 | self.assertEqual(self.a, self.a) 37 | self.assertEqual(self.e, self.e) 38 | self.assertNotEqual(self.a, self.b) 39 | self.assertNotEqual(self.b, self.c) 40 | 41 | def test___init__(self): 42 | with self.assertRaises(DerivationError): 43 | UnnamedOpetope.Address(-1) 44 | UnnamedOpetope.Address(0) 45 | 46 | def test___lt__(self): 47 | with self.assertRaises(DerivationError): 48 | self.a < self.b 49 | self.assertFalse(self.c < self.c) 50 | self.assertLess(self.c, self.d) 51 | self.assertFalse(self.d < self.c) 52 | self.assertLess(self.e, self.e + self.d) 53 | self.assertLess(self.e + self.c, self.e + self.d) 54 | 55 | def test___mul__(self): 56 | with self.assertRaises(DerivationError): 57 | self.a * self.c 58 | self.assertEqual(self.a * self.a, self.a) 59 | self.assertEqual(self.b * self.b, self.b) 60 | self.assertEqual(self.c * self.c, self.d) 61 | self.assertNotEqual(self.c * self.d, self.d) 62 | 63 | def test___str__(self): 64 | self.assertEqual(str(self.a), "*") 65 | self.assertEqual(str(self.b), "[]") 66 | self.assertEqual(str(self.c), "[*]") 67 | self.assertEqual(str(self.d), "[**]") 68 | self.assertEqual(str(self.e), "[[*][**][]]") 69 | 70 | def test_epsilon(self): 71 | self.assertEqual(UnnamedOpetope.Address.epsilon(1), 72 | UnnamedOpetope.Address(1)) 73 | 74 | def test_isEpsilon(self): 75 | self.assertTrue(self.a.isEpsilon()) 76 | self.assertTrue(self.b.isEpsilon()) 77 | self.assertFalse(self.c.isEpsilon()) 78 | self.assertFalse(self.d.isEpsilon()) 79 | self.assertFalse(self.e.isEpsilon()) 80 | 81 | def test_edgeDecomposition(self): 82 | with self.assertRaises(DerivationError): 83 | self.a.edgeDecomposition() 84 | with self.assertRaises(DerivationError): 85 | self.b.edgeDecomposition() 86 | p, q = self.c.edgeDecomposition() 87 | self.assertEqual(p, self.b) 88 | self.assertEqual(q, self.a) 89 | p, q = self.d.edgeDecomposition() 90 | self.assertEqual(p, self.c) 91 | self.assertEqual(q, self.a) 92 | p, q = self.e.edgeDecomposition() 93 | self.assertEqual( 94 | p, UnnamedOpetope.Address.fromList([['*'], ['*', '*']], 2)) 95 | self.assertEqual(q, self.b) 96 | 97 | def test_fromListOfAddresses(self): 98 | with self.assertRaises(DerivationError): 99 | UnnamedOpetope.Address.fromListOfAddresses([self.a, self.b]) 100 | self.assertEqual( 101 | self.e, 102 | UnnamedOpetope.Address.fromListOfAddresses( 103 | [self.c, self.d, UnnamedOpetope.Address.epsilon(1)]) 104 | ) 105 | 106 | def test_fromList(self): 107 | with self.assertRaises(DerivationError): 108 | UnnamedOpetope.Address.fromList([], -1) 109 | self.assertEqual(UnnamedOpetope.Address.fromList([], 1), self.b) 110 | self.assertEqual(UnnamedOpetope.Address.fromList([[]], 1), self.c) 111 | self.assertEqual(UnnamedOpetope.Address.fromList(['*'], 1), self.c) 112 | self.assertEqual(UnnamedOpetope.Address.fromList([[], []], 1), 113 | self.d) 114 | 115 | def test_shift(self): 116 | with self.assertRaises(DerivationError): 117 | self.a.shift(-1) 118 | self.assertEqual(self.a.shift(0), self.a) 119 | self.assertEqual(self.b.shift(0), self.b) 120 | self.assertEqual(self.c.shift(0), self.c) 121 | self.assertEqual(self.d.shift(0), self.d) 122 | self.assertEqual(self.e.shift(0), self.e) 123 | self.assertEqual(self.a.shift(), self.c) 124 | self.assertEqual(self.a.shift(1), self.c) 125 | self.assertEqual( 126 | (self.a.shift(2) + (self.a.shift(1) + self.a)) * 127 | self.b.shift(1), 128 | self.e 129 | ) 130 | 131 | def test_substitution(self): 132 | with self.assertRaises(DerivationError): 133 | UnnamedOpetope.Address.substitution(self.d, self.a, self.c) 134 | with self.assertRaises(DerivationError): 135 | UnnamedOpetope.Address.substitution(self.d, self.c, self.a) 136 | with self.assertRaises(DerivationError): 137 | UnnamedOpetope.Address.substitution(self.e, self.b, self.b) 138 | self.assertEqual(UnnamedOpetope.Address.substitution( 139 | self.e, 140 | UnnamedOpetope.Address.epsilon(1).shift(), 141 | UnnamedOpetope.Address.epsilon(1).shift() + 142 | UnnamedOpetope.Address.epsilon(1)), 143 | self.e 144 | ) 145 | self.assertEqual(UnnamedOpetope.Address.substitution(self.d, self.c, 146 | self.c), 147 | self.d) 148 | self.assertEqual(UnnamedOpetope.Address.substitution( 149 | self.e, 150 | UnnamedOpetope.Address.epsilon(0).shift(2), 151 | self.e 152 | ), 153 | UnnamedOpetope.Address.fromList( 154 | [['*'], ['*', '*'], [], ['*', '*'], []], 2) 155 | ) 156 | 157 | 158 | class Test_UnnamedOpetope_Context(unittest.TestCase): 159 | 160 | def setUp(self): 161 | self.a = UnnamedOpetope.Context(0) 162 | self.b = UnnamedOpetope.Context(2) 163 | self.c = UnnamedOpetope.Context(2) + \ 164 | (UnnamedOpetope.Address.epsilon(1), 165 | UnnamedOpetope.Address.epsilon(0)) 166 | self.d = UnnamedOpetope.Context(2) + \ 167 | (UnnamedOpetope.Address.fromList(['*'], 1), 168 | UnnamedOpetope.Address.epsilon(0)) 169 | self.e = UnnamedOpetope.Context(3) + \ 170 | (UnnamedOpetope.Address.epsilon(2), 171 | UnnamedOpetope.Address.fromList(['*'], 1)) 172 | self.f = UnnamedOpetope.Context(3) + \ 173 | (UnnamedOpetope.Address.epsilon(1).shift(), 174 | UnnamedOpetope.Address.epsilon(1)) 175 | 176 | def test___add__(self): 177 | # Dimension mismatch in tuple 178 | with self.assertRaises(DerivationError): 179 | self.b + (UnnamedOpetope.Address.epsilon(1), 180 | UnnamedOpetope.Address.epsilon(1)) 181 | # Dimension mismatch with UnnamedOpetope.context 182 | with self.assertRaises(DerivationError): 183 | self.b + (UnnamedOpetope.Address.epsilon(2), 184 | UnnamedOpetope.Address.epsilon(1)) 185 | # Leaf already present 186 | with self.assertRaises(DerivationError): 187 | self.e + (UnnamedOpetope.Address.epsilon(2), 188 | UnnamedOpetope.Address.epsilon(1)) 189 | # Node already present 190 | with self.assertRaises(DerivationError): 191 | self.e + (UnnamedOpetope.Address.epsilon(1).shift(), 192 | UnnamedOpetope.Address.fromList(['*'], 1)) 193 | self.assertEqual( 194 | self.b + (UnnamedOpetope.Address.epsilon(1), 195 | UnnamedOpetope.Address.epsilon(0)), 196 | self.c 197 | ) 198 | self.assertEqual( 199 | self.b + (UnnamedOpetope.Address.fromList(['*'], 1), 200 | UnnamedOpetope.Address.epsilon(0)), 201 | self.d 202 | ) 203 | self.assertEqual( 204 | self.e + (UnnamedOpetope.Address.epsilon(1).shift(), 205 | UnnamedOpetope.Address.epsilon(1)), 206 | self.f + (UnnamedOpetope.Address.epsilon(2), 207 | UnnamedOpetope.Address.fromList(['*'], 1)) 208 | ) 209 | 210 | def test___call__(self): 211 | with self.assertRaises(DerivationError): 212 | self.f(UnnamedOpetope.Address.epsilon(2)) 213 | self.assertEqual(self.c(UnnamedOpetope.Address.epsilon(1)), 214 | UnnamedOpetope.Address.epsilon(0)) 215 | self.assertEqual( 216 | self.e(UnnamedOpetope.Address.epsilon(2)), 217 | UnnamedOpetope.Address.fromList(['*'], 1) 218 | ) 219 | 220 | def test___eq__(self): 221 | self.assertEqual(self.a, self.a) 222 | self.assertEqual(self.b, self.b) 223 | self.assertEqual(self.c, self.c) 224 | self.assertEqual(self.d, self.d) 225 | self.assertEqual(self.e, self.e) 226 | self.assertEqual(self.f, self.f) 227 | self.assertNotEqual(self.a, self.b) 228 | self.assertNotEqual(self.b, self.c) 229 | self.assertNotEqual(self.b, self.d) 230 | self.assertNotEqual(self.c, self.d) 231 | self.assertNotEqual(self.e, self.f) 232 | 233 | def test___init__(self): 234 | with self.assertRaises(DerivationError): 235 | UnnamedOpetope.Context(-1) 236 | self.assertEqual(len(self.b.keys()), 0) 237 | self.assertEqual(self.b.dimension, 2) 238 | 239 | def test___sub__(self): 240 | with self.assertRaises(DerivationError): 241 | self.b - UnnamedOpetope.Address.epsilon(1) 242 | with self.assertRaises(DerivationError): 243 | self.f - UnnamedOpetope.Address.epsilon(2) 244 | self.assertEqual(self.c - UnnamedOpetope.Address.epsilon(1), self.b) 245 | self.assertEqual(self.d - UnnamedOpetope.Address.fromList(['*'], 1), 246 | self.b) 247 | self.assertEqual(self.e - UnnamedOpetope.Address.epsilon(2), 248 | UnnamedOpetope.Context(3)) 249 | self.assertEqual( 250 | self.f - UnnamedOpetope.Address.epsilon(1).shift(), 251 | UnnamedOpetope.Context(3)) 252 | 253 | 254 | class Test_UnnamedOpetope_Preopetope(unittest.TestCase): 255 | 256 | def setUp(self): 257 | self.a = UnnamedOpetope.Preopetope(-1) 258 | self.b = UnnamedOpetope.Preopetope(0) 259 | self.c = UnnamedOpetope.Preopetope.fromDictOfPreopetopes({ 260 | UnnamedOpetope.Address.epsilon(0): self.b 261 | }) 262 | self.d = UnnamedOpetope.Preopetope.degenerate(self.b) 263 | self.e = UnnamedOpetope.Preopetope.fromDictOfPreopetopes({ 264 | UnnamedOpetope.Address.epsilon(1): self.c 265 | }) 266 | self.f = UnnamedOpetope.Preopetope.fromDictOfPreopetopes({ 267 | UnnamedOpetope.Address.epsilon(1): self.c, 268 | UnnamedOpetope.Address.fromList(['*'], 1): self.c 269 | }) 270 | 271 | def test___add__(self): 272 | # Adding to a degenerate 273 | with self.assertRaises(DerivationError): 274 | self.d + (UnnamedOpetope.Address.epsilon(1), self.c) 275 | # Dimension mismatch in the tuple 276 | with self.assertRaises(DerivationError): 277 | self.e + (UnnamedOpetope.Address.fromList(['*'], 1), self.a) 278 | # Dimension mismatch with the popt 279 | with self.assertRaises(DerivationError): 280 | self.e + (UnnamedOpetope.Address.epsilon(2), self.f) 281 | # UnnamedOpetope.Address already present 282 | with self.assertRaises(DerivationError): 283 | self.e + (UnnamedOpetope.Address.epsilon(1), self.c) 284 | self.assertEqual(self.e + (UnnamedOpetope.Address.fromList(['*'], 1), 285 | self.c), 286 | self.f) 287 | 288 | def test___eq__(self): 289 | self.assertEqual(self.a, self.a) 290 | self.assertEqual(self.b, self.b) 291 | self.assertEqual(self.c, self.c) 292 | self.assertEqual(self.b, self.b) 293 | self.assertEqual(self.e, self.e) 294 | self.assertEqual(self.f, self.f) 295 | self.assertNotEqual(self.a, self.b) 296 | self.assertNotEqual(self.b, self.c) 297 | self.assertNotEqual(self.c, self.d) 298 | self.assertNotEqual(self.c, self.e) 299 | self.assertNotEqual(self.e, self.f) 300 | 301 | def test___init__(self): 302 | with self.assertRaises(DerivationError): 303 | UnnamedOpetope.Preopetope(-2) 304 | UnnamedOpetope.Preopetope(-1) 305 | x = UnnamedOpetope.Preopetope(8) 306 | self.assertEqual(x.dimension, 8) 307 | self.assertFalse(x.isDegenerate) 308 | self.assertEqual(x.nodes, {}) 309 | 310 | def test___sub__(self): 311 | with self.assertRaises(DerivationError): 312 | self.e - UnnamedOpetope.Address.fromList(['*'], 1) 313 | self.assertEqual(self.f - UnnamedOpetope.Address.fromList(['*'], 1), 314 | self.e) 315 | 316 | def test_degenerate(self): 317 | with self.assertRaises(DerivationError): 318 | UnnamedOpetope.Preopetope.degenerate(self.a) 319 | self.assertEqual(self.d.degeneracy, self.b) 320 | self.assertEqual(self.d.dimension, self.b.dimension + 2) 321 | self.assertTrue(self.d.isDegenerate) 322 | self.assertEqual(self.d.nodes, {}) 323 | 324 | def test_empty(self): 325 | x = UnnamedOpetope.Preopetope.empty() 326 | self.assertEqual(x.dimension, -1) 327 | 328 | def test_fromDictOfPreopetopes(self): 329 | # Empty dict 330 | with self.assertRaises(DerivationError): 331 | UnnamedOpetope.Preopetope.fromDictOfPreopetopes({}) 332 | # Dimension mismatch in tuple 333 | with self.assertRaises(DerivationError): 334 | UnnamedOpetope.Preopetope.fromDictOfPreopetopes({ 335 | UnnamedOpetope.Address.epsilon(1): self.a 336 | }) 337 | # Dimension mismatch among tuples 338 | with self.assertRaises(DerivationError): 339 | UnnamedOpetope.Preopetope.fromDictOfPreopetopes({ 340 | UnnamedOpetope.Address.epsilon(0): self.a, 341 | UnnamedOpetope.Address.epsilon(1): self.c 342 | }) 343 | self.assertEqual(self.f.nodes[UnnamedOpetope.Address.epsilon(1)], 344 | self.c) 345 | self.assertIn(UnnamedOpetope.Address.fromList(['*'], 1), 346 | self.f.nodes.keys()) 347 | self.assertNotIn(UnnamedOpetope.Address.epsilon(2), 348 | self.f.nodes.keys()) 349 | 350 | def test_grafting(self): 351 | # Dimension mismatch btw preopetopes 352 | with self.assertRaises(DerivationError): 353 | UnnamedOpetope.Preopetope.grafting( 354 | self.e, 355 | UnnamedOpetope.Address.epsilon(0), self.b) 356 | # Dim mismatch btw addr and UnnamedOpetope.preopetope 357 | with self.assertRaises(DerivationError): 358 | UnnamedOpetope.Preopetope.grafting( 359 | self.e, 360 | UnnamedOpetope.Address.epsilon(0), self.c) 361 | self.assertEqual( 362 | UnnamedOpetope.Preopetope.grafting( 363 | self.e, 364 | UnnamedOpetope.Address.fromList(['*'], 1), 365 | self.e), 366 | self.f 367 | ) 368 | 369 | def test_improperGrafting(self): 370 | # Adding to a degenerate 371 | with self.assertRaises(DerivationError): 372 | UnnamedOpetope.Preopetope.improperGrafting( 373 | self.d, 374 | UnnamedOpetope.Address.epsilon(1), 375 | self.c 376 | ) 377 | # Dimension mismatch in the tuple 378 | with self.assertRaises(DerivationError): 379 | UnnamedOpetope.Preopetope.improperGrafting( 380 | self.e, 381 | UnnamedOpetope.Address.fromList(['*'], 1), 382 | self.a 383 | ) 384 | # Dimension mismatch with the popt 385 | with self.assertRaises(DerivationError): 386 | UnnamedOpetope.Preopetope.improperGrafting( 387 | self.e, 388 | UnnamedOpetope.Address.epsilon(2), 389 | self.f 390 | ) 391 | # UnnamedOpetope.Address already present 392 | with self.assertRaises(DerivationError): 393 | UnnamedOpetope.Preopetope.improperGrafting( 394 | self.e, 395 | UnnamedOpetope.Address.epsilon(1), 396 | self.c 397 | ) 398 | self.assertEqual( 399 | UnnamedOpetope.Preopetope.improperGrafting( 400 | self.e, 401 | UnnamedOpetope.Address.fromList(['*'], 1), 402 | self.c 403 | ), 404 | self.f 405 | ) 406 | 407 | def test_leafAddresses(self): 408 | self.assertEqual(self.b.leafAddresses(), set()) 409 | self.assertEqual(self.c.leafAddresses(), set()) 410 | self.assertEqual(self.d.leafAddresses(), set()) 411 | self.assertEqual(self.e.leafAddresses(), 412 | set([UnnamedOpetope.Address.fromList(['*'], 1)]) 413 | ) 414 | self.assertEqual(self.f.leafAddresses(), 415 | set([UnnamedOpetope.Address.fromList(['*', '*'], 1)]) 416 | ) 417 | 418 | def test_nodeAddresses(self): 419 | self.assertEqual(self.b.nodeAddresses(), set()) 420 | self.assertEqual(self.c.nodeAddresses(), 421 | set([UnnamedOpetope.Address.epsilon(0)])) 422 | self.assertEqual(self.d.nodeAddresses(), set()) 423 | self.assertEqual(self.e.nodeAddresses(), 424 | set([UnnamedOpetope.Address.epsilon(1)])) 425 | self.assertEqual(self.f.nodeAddresses(), 426 | set([UnnamedOpetope.Address.epsilon(1), 427 | UnnamedOpetope.Address.fromList(['*'], 1)])) 428 | 429 | def test_point(self): 430 | p = UnnamedOpetope.Preopetope.point() 431 | self.assertEqual(p, self.b) 432 | self.assertEqual(p.dimension, 0) 433 | self.assertFalse(p.isDegenerate) 434 | self.assertEqual(p.nodes, {}) 435 | 436 | def test_source(self): 437 | with self.assertRaises(DerivationError): 438 | self.f.source(UnnamedOpetope.Address.fromList(['*', '*'], 1)) 439 | self.assertEqual(self.c.source(UnnamedOpetope.Address.epsilon(0)), 440 | self.b) 441 | self.assertEqual(self.f.source(UnnamedOpetope.Address.epsilon(1)), 442 | self.c) 443 | self.assertEqual( 444 | self.f.source(UnnamedOpetope.Address.fromList(['*'], 1)), 445 | self.c) 446 | 447 | def test_substitution(self): 448 | i2 = UnnamedOpetope.Preopetope.fromDictOfPreopetopes({ 449 | UnnamedOpetope.Address.epsilon(1): self.c, 450 | UnnamedOpetope.Address.fromList(['*'], 1): self.c 451 | }) 452 | i4 = UnnamedOpetope.Preopetope.fromDictOfPreopetopes({ 453 | UnnamedOpetope.Address.epsilon(1): self.c, 454 | UnnamedOpetope.Address.fromList(['*'], 1): self.c, 455 | UnnamedOpetope.Address.fromList(['*', '*'], 1): self.c, 456 | UnnamedOpetope.Address.fromList(['*', '*', '*'], 1): self.c 457 | }) 458 | i5 = UnnamedOpetope.Preopetope.fromDictOfPreopetopes({ 459 | UnnamedOpetope.Address.epsilon(1): self.c, 460 | UnnamedOpetope.Address.fromList(['*'], 1): self.c, 461 | UnnamedOpetope.Address.fromList(['*', '*'], 1): self.c, 462 | UnnamedOpetope.Address.fromList(['*', '*', '*'], 1): self.c, 463 | UnnamedOpetope.Address.fromList(['*', '*', '*', '*'], 1): self.c 464 | }) 465 | ctx = UnnamedOpetope.Context( 466 | 2) + (UnnamedOpetope.Address.fromList(['*', '*'], 1), 467 | UnnamedOpetope.Address.epsilon(0)) 468 | self.assertEqual(UnnamedOpetope.Preopetope.substitution( 469 | i4, UnnamedOpetope.Address.fromList(['*', '*'], 1), ctx, i2), 470 | i5) 471 | 472 | def test_toDict(self): 473 | for i in range(5): 474 | seq = UnnamedOpetope.OpetopicInteger(i).eval() 475 | self.assertEqual( 476 | seq, 477 | UnnamedOpetope.ProofTree(seq.source.toDict()).eval()) 478 | 479 | 480 | class Test_UnnamedOpetope_InferenceRules(unittest.TestCase): 481 | 482 | def setUp(self): 483 | pass 484 | 485 | def test_point(self): 486 | s = UnnamedOpetope.point() 487 | self.assertEqual(s.context, UnnamedOpetope.Context(0)) 488 | self.assertEqual(s.source, UnnamedOpetope.Preopetope.point()) 489 | self.assertEqual(s.target, UnnamedOpetope.Preopetope.empty()) 490 | 491 | def test_degen(self): 492 | s = UnnamedOpetope.degen(UnnamedOpetope.point()) 493 | self.assertEqual( 494 | s.context, UnnamedOpetope.Context(2) + 495 | (UnnamedOpetope.Address.epsilon(1), 496 | UnnamedOpetope.Address.epsilon(0))) 497 | self.assertEqual( 498 | s.source, 499 | UnnamedOpetope.Preopetope.degenerate( 500 | UnnamedOpetope.Preopetope.point())) 501 | self.assertEqual( 502 | s.target, 503 | UnnamedOpetope.shift(UnnamedOpetope.point()).source) 504 | 505 | def test_shift(self): 506 | s1 = UnnamedOpetope.shift(UnnamedOpetope.point()) 507 | s2 = UnnamedOpetope.shift(s1) 508 | self.assertEqual( 509 | s2.context, 510 | UnnamedOpetope.Context(2) + 511 | (UnnamedOpetope.Address.epsilon(0).shift(), 512 | UnnamedOpetope.Address.epsilon(0))) 513 | p = UnnamedOpetope.Preopetope.point() 514 | a = UnnamedOpetope.Preopetope(1) 515 | a.nodes[UnnamedOpetope.Address.epsilon(0)] = p 516 | g = UnnamedOpetope.Preopetope(2) 517 | g.nodes[UnnamedOpetope.Address.epsilon(1)] = a 518 | self.assertEqual(s1.source, a) 519 | self.assertEqual(s1.target, p) 520 | self.assertEqual(s2.source, g) 521 | self.assertEqual(s2.target, a) 522 | 523 | def test_graft(self): 524 | i2 = UnnamedOpetope.OpetopicInteger(2).eval() 525 | i3 = UnnamedOpetope.OpetopicInteger(3).eval() 526 | s = UnnamedOpetope.shift(i3) 527 | s = UnnamedOpetope.graft( 528 | s, i2, UnnamedOpetope.Address.fromList([['*']], 2)) 529 | s = UnnamedOpetope.graft( 530 | s, i2, 531 | UnnamedOpetope.Address.fromList([['*', '*']], 2)) 532 | r = s.context 533 | self.assertEqual( 534 | r(UnnamedOpetope.Address.fromList([[]], 2)), 535 | UnnamedOpetope.Address.fromList([], 1)) 536 | self.assertEqual( 537 | r(UnnamedOpetope.Address.fromList([['*'], []], 2)), 538 | UnnamedOpetope.Address.fromList(['*'], 1)) 539 | self.assertEqual( 540 | r(UnnamedOpetope.Address.fromList([['*'], ['*']], 2)), 541 | UnnamedOpetope.Address.fromList(['*', '*'], 1)) 542 | self.assertEqual( 543 | r(UnnamedOpetope.Address.fromList([['*', '*'], []], 2)), 544 | UnnamedOpetope.Address.fromList(['*', '*', '*'], 1)) 545 | self.assertEqual( 546 | r(UnnamedOpetope.Address.fromList([['*', '*'], ['*']], 2)), 547 | UnnamedOpetope.Address.fromList(['*', '*', '*', '*'], 1)) 548 | 549 | 550 | class Test_UnnamedOpetope_Utils(unittest.TestCase): 551 | 552 | def setUp(self): 553 | pass 554 | 555 | def test_address(self): 556 | self.assertEqual( 557 | UnnamedOpetope.address('*'), 558 | UnnamedOpetope.Address.epsilon(0)) 559 | self.assertEqual( 560 | UnnamedOpetope.address([['*'], [], ['*', '*']]), 561 | UnnamedOpetope.Address.fromList([['*'], [], ['*', '*']], 2)) 562 | self.assertEqual( 563 | UnnamedOpetope.address([[], [], ['*', '*']]), 564 | UnnamedOpetope.Address.fromList([[], [], ['*', '*']], 2)) 565 | with self.assertRaises(DerivationError): 566 | UnnamedOpetope.address([[[]]]) 567 | with self.assertRaises(DerivationError): 568 | UnnamedOpetope.address([[[]]], 1) 569 | UnnamedOpetope.address([[[]]], 3) 570 | with self.assertRaises(DerivationError): 571 | UnnamedOpetope.address([[['*'], [['*']]]]) 572 | 573 | def test_OpetopicTree(self): 574 | self.assertEqual( 575 | UnnamedOpetope.OpetopicTree(None).eval(), 576 | UnnamedOpetope.Degen(UnnamedOpetope.Arrow()).eval()) 577 | for i in range(5): 578 | self.assertEqual( 579 | UnnamedOpetope.OpetopicTree([None] * i).eval(), 580 | UnnamedOpetope.Shift(UnnamedOpetope.OpetopicInteger(i)).eval()) 581 | for i in range(5): 582 | tree = [None] * i + [[None]] + [None] * (4 - i) 583 | self.assertEqual( 584 | UnnamedOpetope.OpetopicTree(tree).eval(), 585 | UnnamedOpetope.Graft( 586 | UnnamedOpetope.Shift(UnnamedOpetope.OpetopicInteger(5)), 587 | UnnamedOpetope.OpetopicInteger(1), 588 | UnnamedOpetope.address([['*'] * i], 2)).eval()) 589 | 590 | def test_ProofTree(self): 591 | self.assertEqual( 592 | UnnamedOpetope.ProofTree({}).eval(), 593 | UnnamedOpetope.Point().eval()) 594 | self.assertEqual( 595 | UnnamedOpetope.ProofTree({ 596 | UnnamedOpetope.address('*'): {} 597 | }).eval(), 598 | UnnamedOpetope.Arrow().eval()) 599 | self.assertEqual( 600 | UnnamedOpetope.ProofTree({ 601 | None: {} 602 | }).eval(), 603 | UnnamedOpetope.OpetopicInteger(0).eval()) 604 | self.assertEqual( 605 | UnnamedOpetope.ProofTree({ 606 | UnnamedOpetope.address([], 1): { 607 | UnnamedOpetope.address('*'): {} 608 | }, 609 | UnnamedOpetope.address(['*']): { 610 | UnnamedOpetope.address('*'): {} 611 | } 612 | }).eval(), 613 | UnnamedOpetope.OpetopicInteger(2).eval()) 614 | with self.assertRaises(DerivationError): 615 | UnnamedOpetope.ProofTree({ 616 | UnnamedOpetope.address(['*']): { 617 | UnnamedOpetope.address('*'): {} 618 | } 619 | }) 620 | with self.assertRaises(DerivationError): 621 | UnnamedOpetope.ProofTree({ 622 | UnnamedOpetope.address([], 1): { 623 | UnnamedOpetope.address('*'): {} 624 | }, 625 | UnnamedOpetope.address(['*', '*']): { 626 | UnnamedOpetope.address('*'): {} 627 | } 628 | }).eval() 629 | 630 | 631 | if __name__ == "__main__": 632 | unittest.main(verbosity = 2) 633 | -------------------------------------------------------------------------------- /tests/unittest_namedopetope.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import sys 4 | sys.path.insert(0, "../") 5 | 6 | from opetopy.common import DerivationError 7 | 8 | from opetopy import NamedOpetope 9 | 10 | 11 | class Test_NamedOpetope_Variable(unittest.TestCase): 12 | 13 | def setUp(self): 14 | self.a0 = NamedOpetope.Variable("a", 0) 15 | self.b0 = NamedOpetope.Variable("b", 0) 16 | self.c1 = NamedOpetope.Variable("c", 1) 17 | self.a1 = NamedOpetope.Variable("a", 1) 18 | 19 | def test___eq__(self): 20 | self.assertEqual(self.a0, self.a0) 21 | self.assertEqual(self.b0, self.b0) 22 | self.assertEqual(self.c1, self.c1) 23 | self.assertEqual(self.a1, self.a1) 24 | self.assertNotEqual(self.a0, self.b0) 25 | self.assertNotEqual(self.a0, self.c1) 26 | self.assertNotEqual(self.a0, self.a1) 27 | 28 | def test___init__(self): 29 | with self.assertRaises(DerivationError): 30 | NamedOpetope.Variable("x", -1) 31 | NamedOpetope.Variable("x", 0) 32 | 33 | 34 | class Test_NamedOpetope_Term(unittest.TestCase): 35 | 36 | def setUp(self): 37 | self.w = NamedOpetope.Variable("w", 2) 38 | self.x = NamedOpetope.Variable("x", 2) 39 | self.y = NamedOpetope.Variable("y", 2) 40 | self.z = NamedOpetope.Variable("z", 2) 41 | self.a = NamedOpetope.Variable("a", 1) 42 | self.b = NamedOpetope.Variable("b", 1) 43 | self.c = NamedOpetope.Variable("c", 1) 44 | self.d = NamedOpetope.Variable("d", 1) 45 | self.e = NamedOpetope.Variable("e", 1) 46 | # t = w (a <- x, b <- y (c <- z, d <- _e_)) 47 | self.tw = NamedOpetope.Term(self.w) 48 | self.tx = NamedOpetope.Term(self.x) 49 | self.ty = NamedOpetope.Term(self.y) 50 | self.tz = NamedOpetope.Term(self.z) 51 | self.ty[self.c] = self.tz 52 | self.ty[self.d] = NamedOpetope.Term(self.e, True) 53 | self.tw[self.a] = self.tx 54 | self.tw[self.b] = self.ty 55 | 56 | def test___contains__(self): 57 | self.assertIn(self.a, self.tw) 58 | self.assertIn(self.b, self.tw) 59 | self.assertIn(self.c, self.tw) 60 | self.assertIn(self.d, self.tw) 61 | self.assertNotIn(self.e, self.tw) 62 | self.assertIn(self.w, self.tw) 63 | self.assertIn(self.x, self.tw) 64 | self.assertIn(self.y, self.tw) 65 | self.assertIn(self.z, self.tw) 66 | self.assertNotIn(self.a, self.tx) 67 | self.assertNotIn(self.b, self.tx) 68 | self.assertNotIn(self.c, self.tx) 69 | self.assertNotIn(self.d, self.tx) 70 | self.assertNotIn(self.e, self.tx) 71 | self.assertNotIn(self.w, self.tx) 72 | self.assertIn(self.x, self.tx) 73 | self.assertNotIn(self.y, self.tx) 74 | self.assertNotIn(self.z, self.tx) 75 | self.assertNotIn(self.a, self.ty) 76 | self.assertNotIn(self.b, self.ty) 77 | self.assertIn(self.c, self.ty) 78 | self.assertIn(self.d, self.ty) 79 | self.assertNotIn(self.e, self.ty) 80 | self.assertNotIn(self.w, self.ty) 81 | self.assertNotIn(self.x, self.ty) 82 | self.assertIn(self.y, self.ty) 83 | self.assertIn(self.z, self.ty) 84 | self.assertNotIn(self.a, self.tz) 85 | self.assertNotIn(self.b, self.tz) 86 | self.assertNotIn(self.c, self.tz) 87 | self.assertNotIn(self.d, self.tz) 88 | self.assertNotIn(self.e, self.tz) 89 | self.assertNotIn(self.w, self.tz) 90 | self.assertNotIn(self.x, self.tz) 91 | self.assertNotIn(self.y, self.tz) 92 | self.assertIn(self.z, self.tz) 93 | 94 | def test___eq__(self): 95 | self.assertEqual(self.tw, self.tw) 96 | self.assertEqual(self.tx, self.tx) 97 | self.assertEqual(self.ty, self.ty) 98 | self.assertEqual(self.tz, self.tz) 99 | self.assertNotEqual(self.tx, self.tz) 100 | self.assertNotEqual(self.tw, NamedOpetope.Term(self.w)) 101 | self.assertNotEqual( 102 | NamedOpetope.Term(NamedOpetope.Variable("x", 0)), 103 | NamedOpetope.Term(NamedOpetope.Variable("x", 1))) 104 | 105 | def test_dim(self): 106 | self.assertEqual(self.tw.dimension, 2) 107 | self.assertEqual(self.tx.dimension, 2) 108 | self.assertEqual(self.ty.dimension, 2) 109 | self.assertEqual(self.tz.dimension, 2) 110 | self.assertEqual(NamedOpetope.Term(self.e, True).dimension, 2) 111 | 112 | def test_graftTuples(self): 113 | self.assertEqual(self.tw.graftTuples(), 114 | {(self.a, self.x), (self.b, self.y), 115 | (self.c, self.z)}) 116 | 117 | def test_isVariable(self): 118 | self.assertFalse(self.tw.isVariable()) 119 | self.assertTrue(self.tx.isVariable()) 120 | self.assertFalse(self.ty.isVariable()) 121 | self.assertTrue(self.tz.isVariable()) 122 | 123 | def test_variables(self): 124 | self.assertEqual(self.tw.variables(0), set()) 125 | self.assertEqual(self.tw.variables(1), 126 | {self.a, self.b, self.c, self.d}) 127 | self.assertEqual(self.tw.variables(2), 128 | {self.w, self.x, self.y, self.z}) 129 | 130 | 131 | class Test_NamedOpetope_Type(unittest.TestCase): 132 | 133 | def setUp(self): 134 | self.a = NamedOpetope.Variable("a", 0) 135 | self.f = NamedOpetope.Variable("f", 1) 136 | self.alpha = NamedOpetope.Variable("α", 2) 137 | self.t0 = NamedOpetope.Type([NamedOpetope.Term()]) 138 | self.t1 = NamedOpetope.Type( 139 | [NamedOpetope.Term(self.a), NamedOpetope.Term()]) 140 | self.t2 = NamedOpetope.Type( 141 | [NamedOpetope.Term(self.f), NamedOpetope.Term(self.a), 142 | NamedOpetope.Term()]) 143 | self.t3 = NamedOpetope.Type( 144 | [NamedOpetope.Term(self.alpha), NamedOpetope.Term(self.f), 145 | NamedOpetope.Term(self.a), NamedOpetope.Term()]) 146 | 147 | def test___contains__(self): 148 | self.assertNotIn(self.a, self.t0) 149 | self.assertNotIn(self.f, self.t0) 150 | self.assertNotIn(self.alpha, self.t0) 151 | self.assertIn(self.a, self.t1) 152 | self.assertNotIn(self.f, self.t1) 153 | self.assertNotIn(self.alpha, self.t1) 154 | self.assertIn(self.a, self.t2) 155 | self.assertIn(self.f, self.t2) 156 | self.assertNotIn(self.alpha, self.t2) 157 | self.assertIn(self.a, self.t3) 158 | self.assertIn(self.f, self.t3) 159 | self.assertIn(self.alpha, self.t3) 160 | self.assertNotIn(NamedOpetope.Variable("β", 2), self.t3) 161 | self.assertNotIn(NamedOpetope.Variable("a", 2), self.t3) 162 | 163 | def test___init__(self): 164 | with self.assertRaises(DerivationError): 165 | NamedOpetope.Type( 166 | [NamedOpetope.Term(NamedOpetope.Variable("α", 3)), 167 | NamedOpetope.Term(self.f), 168 | NamedOpetope.Term(self.a), 169 | NamedOpetope.Term()]) 170 | with self.assertRaises(DerivationError): 171 | NamedOpetope.Type( 172 | [NamedOpetope.Term(self.alpha), 173 | NamedOpetope.Term(self.f), 174 | NamedOpetope.Term(NamedOpetope.Variable("a", 1)), 175 | NamedOpetope.Term()]) 176 | with self.assertRaises(DerivationError): 177 | NamedOpetope.Type([]) 178 | 179 | def test_variables(self): 180 | self.assertEqual(self.t3.variables(0), {self.a}) 181 | self.assertEqual(self.t3.variables(1), {self.f}) 182 | self.assertEqual(self.t3.variables(2), {self.alpha}) 183 | self.assertEqual(self.t3.variables(3), set()) 184 | 185 | 186 | class Test_NamedOpetope_Typing(unittest.TestCase): 187 | 188 | def test___init__(self): 189 | NamedOpetope.Typing( 190 | NamedOpetope.Term(NamedOpetope.Variable("a", 0)), 191 | NamedOpetope.Type([NamedOpetope.Term()])) 192 | NamedOpetope.Typing( 193 | NamedOpetope.Term(NamedOpetope.Variable("f", 1)), 194 | NamedOpetope.Type( 195 | [NamedOpetope.Term(NamedOpetope.Variable("a", 0)), 196 | NamedOpetope.Term()])) 197 | with self.assertRaises(DerivationError): 198 | NamedOpetope.Typing( 199 | NamedOpetope.Term(NamedOpetope.Variable("a", 1)), 200 | NamedOpetope.Type([NamedOpetope.Term()])) 201 | with self.assertRaises(DerivationError): 202 | NamedOpetope.Typing( 203 | NamedOpetope.Term(NamedOpetope.Variable("f", 2)), 204 | NamedOpetope.Type( 205 | [NamedOpetope.Term(NamedOpetope.Variable("a", 0)), 206 | NamedOpetope.Term()])) 207 | with self.assertRaises(DerivationError): 208 | NamedOpetope.Typing( 209 | NamedOpetope.Term(NamedOpetope.Variable("f", 0)), 210 | NamedOpetope.Type( 211 | [NamedOpetope.Term(NamedOpetope.Variable("a", 0)), 212 | NamedOpetope.Term()])) 213 | 214 | 215 | class Test_NamedOpetope_Context(unittest.TestCase): 216 | 217 | def setUp(self): 218 | self.term1 = NamedOpetope.Term(NamedOpetope.Variable("a", 0)) 219 | self.term2 = NamedOpetope.Term(NamedOpetope.Variable("f", 1)) 220 | self.term3 = NamedOpetope.Term(NamedOpetope.Variable("α", 2)) 221 | self.term4 = NamedOpetope.Term(NamedOpetope.Variable("A", 3)) 222 | self.typing1 = NamedOpetope.Type([NamedOpetope.Term()]) 223 | self.typing2 = NamedOpetope.Type([self.term1, NamedOpetope.Term()]) 224 | self.typing3 = NamedOpetope.Type( 225 | [self.term2, self.term1, NamedOpetope.Term()]) 226 | self.typing4 = NamedOpetope.Type( 227 | [self.term3, self.term2, self.term1, NamedOpetope.Term()]) 228 | self.ctx1 = NamedOpetope.Context() 229 | self.ctx2 = self.ctx1 + NamedOpetope.Typing(self.term1, self.typing1) 230 | self.ctx3 = self.ctx2 + NamedOpetope.Typing(self.term2, self.typing2) 231 | self.ctx4 = self.ctx3 + NamedOpetope.Typing(self.term3, self.typing3) 232 | self.ctx5 = self.ctx4 + NamedOpetope.Typing(self.term4, self.typing4) 233 | 234 | def test___add__(self): 235 | with self.assertRaises(DerivationError): 236 | self.ctx5 + NamedOpetope.Typing(self.term1, self.typing1) 237 | with self.assertRaises(DerivationError): 238 | self.ctx5 + NamedOpetope.Typing(self.term2, self.typing2) 239 | with self.assertRaises(DerivationError): 240 | self.ctx5 + NamedOpetope.Typing(self.term3, self.typing3) 241 | with self.assertRaises(DerivationError): 242 | self.ctx5 + NamedOpetope.Typing(self.term4, self.typing4) 243 | term = NamedOpetope.Term(NamedOpetope.Variable("x", 2)) 244 | term[NamedOpetope.Variable("a", 1)] = NamedOpetope.Term( 245 | NamedOpetope.Variable("y", 2)) 246 | typing = NamedOpetope.Typing(term, self.typing3) 247 | with self.assertRaises(DerivationError): 248 | self.ctx5 + typing 249 | 250 | def test___contains__(self): 251 | self.assertNotIn(self.term1.variable, self.ctx1) 252 | self.assertIn(self.term1.variable, self.ctx2) 253 | self.assertIn(self.term1.variable, self.ctx3) 254 | self.assertIn(self.term1.variable, self.ctx4) 255 | self.assertIn(self.term1.variable, self.ctx5) 256 | self.assertNotIn(self.term2.variable, self.ctx1) 257 | self.assertNotIn(self.term2.variable, self.ctx2) 258 | self.assertIn(self.term2.variable, self.ctx3) 259 | self.assertIn(self.term2.variable, self.ctx4) 260 | self.assertIn(self.term2.variable, self.ctx5) 261 | self.assertNotIn(self.term3.variable, self.ctx1) 262 | self.assertNotIn(self.term3.variable, self.ctx2) 263 | self.assertNotIn(self.term3.variable, self.ctx3) 264 | self.assertIn(self.term3.variable, self.ctx4) 265 | self.assertIn(self.term3.variable, self.ctx5) 266 | self.assertNotIn(self.term4.variable, self.ctx1) 267 | self.assertNotIn(self.term4.variable, self.ctx2) 268 | self.assertNotIn(self.term4.variable, self.ctx3) 269 | self.assertNotIn(self.term4.variable, self.ctx4) 270 | self.assertIn(self.term4.variable, self.ctx5) 271 | 272 | def test_source(self): 273 | with self.assertRaises(DerivationError): 274 | self.ctx5.source(NamedOpetope.Variable("A", 3), -1) 275 | with self.assertRaises(DerivationError): 276 | self.ctx5.source(NamedOpetope.Variable("A", 3), 5) 277 | self.assertEqual(self.ctx5.source(NamedOpetope.Variable("A", 3), 0), 278 | NamedOpetope.Term(NamedOpetope.Variable("A", 3))) 279 | self.assertEqual(self.ctx5.source(NamedOpetope.Variable("A", 3), 1), 280 | NamedOpetope.Term(NamedOpetope.Variable("α", 2))) 281 | self.assertEqual(self.ctx5.source(NamedOpetope.Variable("A", 3), 2), 282 | NamedOpetope.Term(NamedOpetope.Variable("f", 1))) 283 | self.assertEqual(self.ctx5.source(NamedOpetope.Variable("A", 3), 3), 284 | NamedOpetope.Term(NamedOpetope.Variable("a", 0))) 285 | self.assertEqual(self.ctx5.source(NamedOpetope.Variable("A", 3), 4), 286 | NamedOpetope.Term()) 287 | 288 | def test_typeOf(self): 289 | with self.assertRaises(DerivationError): 290 | self.ctx1.typeOf(NamedOpetope.Variable("a", 0)) 291 | with self.assertRaises(DerivationError): 292 | self.ctx2.typeOf(NamedOpetope.Variable("b", 0)) 293 | self.assertEqual(self.ctx5.typeOf(NamedOpetope.Variable("a", 0)).terms, 294 | self.typing1.terms) 295 | self.assertEqual(self.ctx5.typeOf(NamedOpetope.Variable("f", 1)).terms, 296 | self.typing2.terms) 297 | self.assertEqual(self.ctx5.typeOf(NamedOpetope.Variable("α", 2)).terms, 298 | self.typing3.terms) 299 | self.assertEqual(self.ctx5.typeOf(NamedOpetope.Variable("A", 3)).terms, 300 | self.typing4.terms) 301 | 302 | 303 | class Test_NamedOpetope_EquationalTheory(unittest.TestCase): 304 | 305 | def setUp(self): 306 | self.a0 = NamedOpetope.Variable("a", 0) 307 | self.b0 = NamedOpetope.Variable("b", 0) 308 | self.c0 = NamedOpetope.Variable("c", 0) 309 | self.d0 = NamedOpetope.Variable("d", 0) 310 | self.e0 = NamedOpetope.Variable("e", 0) 311 | self.a1 = NamedOpetope.Variable("a", 1) 312 | self.th1 = NamedOpetope.EquationalTheory() 313 | self.th2 = self.th1 + (self.a0, self.b0) 314 | self.th3 = self.th2 + (self.c0, self.d0) 315 | self.th4 = self.th3 + (self.c0, self.e0) 316 | self.th5 = self.th4 + (self.b0, self.a0) 317 | self.th6 = self.th5 + (self.a0, self.e0) 318 | 319 | def test___add__(self): 320 | with self.assertRaises(DerivationError): 321 | NamedOpetope.EquationalTheory() + (self.a0, self.a1) 322 | self.assertEqual(len(self.th2.classes), 1) 323 | self.assertEqual(self.th2.classes[0], {self.a0, self.b0}) 324 | self.assertEqual(len(self.th3.classes), 2) 325 | self.assertEqual(self.th3.classes[0], {self.a0, self.b0}) 326 | self.assertEqual(self.th3.classes[1], {self.c0, self.d0}) 327 | self.assertEqual(len(self.th4.classes), 2) 328 | self.assertEqual(self.th4.classes[0], {self.a0, self.b0}) 329 | self.assertEqual(self.th4.classes[1], {self.c0, self.d0, self.e0}) 330 | self.assertEqual(len(self.th5.classes), 2) 331 | self.assertEqual(self.th5.classes[0], {self.a0, self.b0}) 332 | self.assertEqual(self.th5.classes[1], {self.c0, self.d0, self.e0}) 333 | self.assertEqual(len(self.th6.classes), 1) 334 | self.assertEqual(self.th6.classes[0], 335 | {self.a0, self.b0, self.c0, self.d0, self.e0}) 336 | 337 | def test___or__(self): 338 | self.assertFalse((self.th1 | self.th1).equal(self.a0, self.b0)) 339 | self.assertFalse((self.th1 | self.th2).equal(self.a0, self.c0)) 340 | self.assertTrue((self.th2 | self.th2).equal(self.a0, self.b0)) 341 | self.assertTrue( 342 | (self.th2 | (NamedOpetope.EquationalTheory() + (self.b0, self.e0))) 343 | .equal(self.e0, self.a0)) 344 | 345 | def test_classOf(self): 346 | self.assertEqual(self.th1.classOf(self.a0), set({self.a0})) 347 | self.assertEqual(self.th1.classOf(self.b0), set({self.b0})) 348 | self.assertEqual(self.th1.classOf(self.c0), set({self.c0})) 349 | self.assertEqual(self.th1.classOf(self.d0), set({self.d0})) 350 | self.assertEqual(self.th1.classOf(self.e0), set({self.e0})) 351 | self.assertEqual(self.th1.classOf(self.a1), set({self.a1})) 352 | self.assertEqual(self.th2.classOf(self.a0), 353 | set({self.a0, self.b0})) 354 | self.assertEqual(self.th2.classOf(self.b0), 355 | set({self.a0, self.b0})) 356 | self.assertEqual(self.th2.classOf(self.c0), set({self.c0})) 357 | self.assertEqual(self.th2.classOf(self.d0), set({self.d0})) 358 | self.assertEqual(self.th2.classOf(self.e0), set({self.e0})) 359 | self.assertEqual(self.th2.classOf(self.a1), set({self.a1})) 360 | self.assertEqual(self.th3.classOf(self.a0), 361 | set({self.a0, self.b0})) 362 | self.assertEqual(self.th3.classOf(self.b0), 363 | set({self.a0, self.b0})) 364 | self.assertEqual(self.th3.classOf(self.c0), 365 | set({self.c0, self.d0})) 366 | self.assertEqual(self.th3.classOf(self.d0), 367 | set({self.c0, self.d0})) 368 | self.assertEqual(self.th3.classOf(self.e0), set({self.e0})) 369 | self.assertEqual(self.th3.classOf(self.a1), set({self.a1})) 370 | self.assertEqual(self.th4.classOf(self.a0), 371 | set({self.a0, self.b0})) 372 | self.assertEqual(self.th4.classOf(self.b0), 373 | set({self.a0, self.b0})) 374 | self.assertEqual(self.th4.classOf(self.c0), 375 | set({self.c0, self.d0, self.e0})) 376 | self.assertEqual(self.th4.classOf(self.d0), 377 | set({self.c0, self.d0, self.e0})) 378 | self.assertEqual(self.th4.classOf(self.e0), 379 | set({self.c0, self.d0, self.e0})) 380 | self.assertEqual(self.th4.classOf(self.a1), set({self.a1})) 381 | self.assertEqual(self.th5.classOf(self.a0), 382 | set({self.a0, self.b0})) 383 | self.assertEqual(self.th5.classOf(self.b0), 384 | set({self.a0, self.b0})) 385 | self.assertEqual(self.th5.classOf(self.c0), 386 | set({self.c0, self.d0, self.e0})) 387 | self.assertEqual(self.th5.classOf(self.d0), 388 | set({self.c0, self.d0, self.e0})) 389 | self.assertEqual(self.th5.classOf(self.e0), 390 | set({self.c0, self.d0, self.e0})) 391 | self.assertEqual(self.th5.classOf(self.a1), set({self.a1})) 392 | self.assertEqual(self.th6.classOf(self.a0), 393 | set({self.a0, self.b0, self.c0, self.d0, 394 | self.e0})) 395 | self.assertEqual(self.th6.classOf(self.b0), 396 | set({self.a0, self.b0, self.c0, self.d0, 397 | self.e0})) 398 | self.assertEqual(self.th6.classOf(self.c0), 399 | set({self.a0, self.b0, self.c0, self.d0, 400 | self.e0})) 401 | self.assertEqual(self.th6.classOf(self.d0), 402 | set({self.a0, self.b0, self.c0, self.d0, 403 | self.e0})) 404 | self.assertEqual(self.th6.classOf(self.e0), 405 | set({self.a0, self.b0, self.c0, self.d0, 406 | self.e0})) 407 | self.assertEqual(self.th6.classOf(self.a1), set({self.a1})) 408 | 409 | def test_equal(self): 410 | self.assertTrue(self.th2.equal(self.a0, self.b0)) 411 | self.assertTrue(self.th2.equal(self.b0, self.a0)) 412 | self.assertFalse(self.th2.equal(self.a0, self.a1)) 413 | self.assertFalse(self.th2.equal(self.c0, self.d0)) 414 | self.assertTrue(self.th3.equal(self.a0, self.b0)) 415 | self.assertTrue(self.th3.equal(self.c0, self.d0)) 416 | self.assertFalse(self.th3.equal(self.a0, self.c0)) 417 | self.assertFalse(self.th3.equal(self.a0, self.d0)) 418 | self.assertFalse(self.th4.equal(self.a0, self.e0)) 419 | self.assertFalse(self.th4.equal(self.b0, self.e0)) 420 | self.assertTrue(self.th6.equal(self.a0, self.b0)) 421 | self.assertTrue(self.th6.equal(self.a0, self.c0)) 422 | self.assertTrue(self.th6.equal(self.a0, self.d0)) 423 | self.assertTrue(self.th6.equal(self.a0, self.e0)) 424 | 425 | def test_isIn(self): 426 | self.assertTrue(self.th1.isIn(self.a0, NamedOpetope.Term(self.a0))) 427 | self.assertFalse(self.th1.isIn(self.b0, NamedOpetope.Term(self.a0))) 428 | self.assertFalse(self.th1.isIn(self.c0, NamedOpetope.Term(self.a0))) 429 | self.assertFalse(self.th1.isIn(self.d0, NamedOpetope.Term(self.a0))) 430 | self.assertFalse(self.th1.isIn(self.e0, NamedOpetope.Term(self.a0))) 431 | self.assertFalse(self.th1.isIn(self.a1, NamedOpetope.Term(self.a0))) 432 | self.assertTrue(self.th2.isIn(self.a0, NamedOpetope.Term(self.a0))) 433 | self.assertTrue(self.th2.isIn(self.b0, NamedOpetope.Term(self.a0))) 434 | self.assertFalse(self.th2.isIn(self.c0, NamedOpetope.Term(self.a0))) 435 | self.assertFalse(self.th2.isIn(self.d0, NamedOpetope.Term(self.a0))) 436 | self.assertFalse(self.th2.isIn(self.e0, NamedOpetope.Term(self.a0))) 437 | self.assertFalse(self.th2.isIn(self.a1, NamedOpetope.Term(self.a0))) 438 | self.assertTrue(self.th3.isIn(self.a0, NamedOpetope.Term(self.a0))) 439 | self.assertTrue(self.th3.isIn(self.b0, NamedOpetope.Term(self.a0))) 440 | self.assertFalse(self.th3.isIn(self.c0, NamedOpetope.Term(self.a0))) 441 | self.assertFalse(self.th3.isIn(self.d0, NamedOpetope.Term(self.a0))) 442 | self.assertFalse(self.th3.isIn(self.e0, NamedOpetope.Term(self.a0))) 443 | self.assertFalse(self.th3.isIn(self.a1, NamedOpetope.Term(self.a0))) 444 | self.assertTrue(self.th4.isIn(self.a0, NamedOpetope.Term(self.a0))) 445 | self.assertTrue(self.th4.isIn(self.b0, NamedOpetope.Term(self.a0))) 446 | self.assertFalse(self.th4.isIn(self.c0, NamedOpetope.Term(self.a0))) 447 | self.assertFalse(self.th4.isIn(self.d0, NamedOpetope.Term(self.a0))) 448 | self.assertFalse(self.th4.isIn(self.e0, NamedOpetope.Term(self.a0))) 449 | self.assertFalse(self.th4.isIn(self.a1, NamedOpetope.Term(self.a0))) 450 | self.assertTrue(self.th5.isIn(self.a0, NamedOpetope.Term(self.a0))) 451 | self.assertTrue(self.th5.isIn(self.b0, NamedOpetope.Term(self.a0))) 452 | self.assertFalse(self.th5.isIn(self.c0, NamedOpetope.Term(self.a0))) 453 | self.assertFalse(self.th5.isIn(self.d0, NamedOpetope.Term(self.a0))) 454 | self.assertFalse(self.th5.isIn(self.e0, NamedOpetope.Term(self.a0))) 455 | self.assertFalse(self.th5.isIn(self.a1, NamedOpetope.Term(self.a0))) 456 | self.assertTrue(self.th6.isIn(self.a0, NamedOpetope.Term(self.a0))) 457 | self.assertTrue(self.th6.isIn(self.b0, NamedOpetope.Term(self.a0))) 458 | self.assertTrue(self.th6.isIn(self.c0, NamedOpetope.Term(self.a0))) 459 | self.assertTrue(self.th6.isIn(self.d0, NamedOpetope.Term(self.a0))) 460 | self.assertTrue(self.th6.isIn(self.e0, NamedOpetope.Term(self.a0))) 461 | self.assertFalse(self.th6.isIn(self.a1, NamedOpetope.Term(self.a0))) 462 | 463 | 464 | class Test_NamedOpetope_Sequent(unittest.TestCase): 465 | 466 | def setUp(self): 467 | self.a1 = NamedOpetope.Variable("a1", 0) 468 | self.b1 = NamedOpetope.Variable("b1", 0) 469 | self.c1 = NamedOpetope.Variable("c1", 0) 470 | self.a2 = NamedOpetope.Variable("a2", 0) 471 | self.b2 = NamedOpetope.Variable("b2", 0) 472 | self.c2 = NamedOpetope.Variable("c2", 0) 473 | self.f = NamedOpetope.Variable("f", 1) 474 | self.g = NamedOpetope.Variable("g", 1) 475 | self.h = NamedOpetope.Variable("h", 1) 476 | self.i = NamedOpetope.Variable("h", 1) 477 | ctx = NamedOpetope.Context() + \ 478 | NamedOpetope.Typing( 479 | NamedOpetope.Term(self.a1), 480 | NamedOpetope.Type([NamedOpetope.Term()])) + \ 481 | NamedOpetope.Typing( 482 | NamedOpetope.Term(self.b1), 483 | NamedOpetope.Type([NamedOpetope.Term()])) + \ 484 | NamedOpetope.Typing( 485 | NamedOpetope.Term(self.c1), 486 | NamedOpetope.Type([NamedOpetope.Term()])) + \ 487 | NamedOpetope.Typing( 488 | NamedOpetope.Term(self.f), 489 | NamedOpetope.Type( 490 | [NamedOpetope.Term(self.a2), 491 | NamedOpetope.Term()])) + \ 492 | NamedOpetope.Typing( 493 | NamedOpetope.Term(self.g), 494 | NamedOpetope.Type( 495 | [NamedOpetope.Term(self.b2), NamedOpetope.Term()])) + \ 496 | NamedOpetope.Typing( 497 | NamedOpetope.Term(self.h), 498 | NamedOpetope.Type( 499 | [NamedOpetope.Term(self.c2), NamedOpetope.Term()])) 500 | eqth = NamedOpetope.EquationalTheory() + \ 501 | (self.b1, self.b2) + \ 502 | (self.c1, self.c2) + \ 503 | (self.h, self.i) 504 | self.sequent = NamedOpetope.Sequent(eqth, ctx, None) 505 | self.fg = self.sequent.graft( 506 | NamedOpetope.Term(self.g), self.b2, NamedOpetope.Term(self.f)) 507 | self.gh = self.sequent.graft( 508 | NamedOpetope.Term(self.h), self.c2, NamedOpetope.Term(self.g)) 509 | self.fgh1 = self.sequent.graft( 510 | self.gh, self.b2, NamedOpetope.Term(self.f)) 511 | self.fgh2 = self.sequent.graft( 512 | NamedOpetope.Term(self.h), self.c2, self.fg) 513 | 514 | def test_equal(self): 515 | self.assertTrue(self.sequent.equal(self.fgh1, self.fgh2)) 516 | self.assertTrue(self.sequent.equal( 517 | NamedOpetope.Term(self.h), NamedOpetope.Term(self.i))) 518 | self.assertTrue(self.sequent.equal( 519 | self.sequent.graft(NamedOpetope.Term(self.i), self.c1, self.fg), 520 | self.fgh1)) 521 | self.assertTrue(self.sequent.equal( 522 | self.sequent.graft(NamedOpetope.Term(self.i), self.c1, self.fg), 523 | self.fgh2)) 524 | self.assertFalse(self.sequent.equal( 525 | self.gh, NamedOpetope.Term(self.h))) 526 | self.assertFalse(self.sequent.equal( 527 | self.gh, NamedOpetope.Term(self.g))) 528 | self.assertFalse(self.sequent.equal(self.gh, self.fg)) 529 | 530 | def test_graft(self): 531 | """ 532 | :todo: Test degenerate grafting 533 | """ 534 | self.assertEqual(NamedOpetope.Term(self.g), 535 | self.sequent.graft(NamedOpetope.Term(self.g), self.c1, 536 | NamedOpetope.Term(self.f))) 537 | self.assertEqual(self.fgh1, self.fgh2) 538 | self.assertEqual(len(self.fgh1.keys()), 1) 539 | self.assertTrue(self.sequent.theory.equal( 540 | list(self.fgh1.keys())[0], self.c1)) 541 | t = list(self.fgh1.values())[0] 542 | self.assertTrue(self.sequent.theory.equal(t.variable, self.g)) 543 | self.assertEqual(len(t.keys()), 1) 544 | self.assertTrue(self.sequent.theory.equal( 545 | list(t.keys())[0], self.b1)) 546 | self.assertTrue(self.sequent.theory.equal( 547 | list(t.values())[0].variable, self.f)) 548 | with self.assertRaises(DerivationError): 549 | self.sequent.graft(self.fg, self.b1, NamedOpetope.Term(self.f)) 550 | 551 | def test_substitute(self): 552 | res = self.sequent.substitute(self.fg, self.gh, self.g) 553 | self.assertIs(res[1], None) 554 | self.assertEqual(res[0], self.fgh1) 555 | res = self.sequent.substitute(self.gh, self.fg, self.g) 556 | self.assertEqual(res[0], self.fgh1) 557 | self.assertIs(res[1], None) 558 | res = self.sequent.substitute( 559 | NamedOpetope.Term(self.f), NamedOpetope.Term(self.f), self.f) 560 | self.assertEqual(res[0], NamedOpetope.Term(self.f)) 561 | self.assertIs(res[1], None) 562 | res = self.sequent.substitute( 563 | NamedOpetope.Term(self.f), NamedOpetope.Term(self.fg), self.f) 564 | self.assertEqual(res[0], NamedOpetope.Term(self.fg)) 565 | self.assertIs(res[1], None) 566 | res = self.sequent.substitute( 567 | NamedOpetope.Term(self.f), NamedOpetope.Term(self.fg), self.g) 568 | self.assertEqual(res[0], NamedOpetope.Term(self.f)) 569 | self.assertIs(res[1], None) 570 | res = self.sequent.substitute( 571 | NamedOpetope.Term(self.f), NamedOpetope.Term(self.fgh1), self.f) 572 | self.assertEqual(res[0], NamedOpetope.Term(self.fgh1)) 573 | self.assertIs(res[1], None) 574 | res = self.sequent.substitute( 575 | self.fgh1, NamedOpetope.Term(self.c1, True), self.g) 576 | self.assertEqual( 577 | res[0], self.sequent.graft(NamedOpetope.Term(self.h), self.c2, 578 | NamedOpetope.Term(self.f))) 579 | res = self.sequent.substitute( 580 | self.fgh1, NamedOpetope.Term(self.b1, True), self.f) 581 | self.assertTrue(self.sequent.equal(res[0], self.gh)) 582 | 583 | 584 | class Test_NamedOpetope_InferenceRules(unittest.TestCase): 585 | 586 | def setUp(self): 587 | pass 588 | 589 | def test_point(self): 590 | s = NamedOpetope.point("x") 591 | self.assertEqual( 592 | s.typing.term, NamedOpetope.Term(NamedOpetope.Variable("x", 0))) 593 | self.assertEqual(len(s.context), 1) 594 | 595 | def test_shift(self): 596 | pass 597 | 598 | def test_degen(self): 599 | s = NamedOpetope.point("x") 600 | s = NamedOpetope.degen(s) 601 | self.assertEqual( 602 | s.typing.term, NamedOpetope.Term( 603 | NamedOpetope.Variable("x", 0), True)) 604 | self.assertEqual(len(s.context), 1) 605 | with self.assertRaises(DerivationError): 606 | NamedOpetope.degen(s) 607 | 608 | def test_degenshift(self): 609 | pass 610 | 611 | def test_graft(self): 612 | pass 613 | 614 | 615 | if __name__ == "__main__": 616 | unittest.main(verbosity = 2) 617 | --------------------------------------------------------------------------------