diff --git a/.github/workflows/update_wiki.yml b/.github/workflows/update_wiki.yml index c7ad2b3..ae23edd 100644 --- a/.github/workflows/update_wiki.yml +++ b/.github/workflows/update_wiki.yml @@ -3,11 +3,8 @@ name: Deploy Wiki on: workflow_dispatch: push: - # Trigger only when wiki directory changes paths: - - 'wiki/pages/**' - - # Trigger only on main + - 'wiki/**' branches: [ main ] jobs: @@ -16,35 +13,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Setup Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install tools to build whl package - run: | - pip install sdist - - - name: Build whl package - run: | - python setup.py sdist bdist_wheel - env: - SEMANTIC_VERSION: 1.0.0 # not actually 1.0.0, but has no impact on wiki-building - - - name: Install do-calculus package - run: | - pip install -e . - env: - SEMANTIC_VERSION: 1.0.0 # not actually 1.0.0, but has no impact on wiki-building - - - name: Build Pages (replace stubs) - run: | - python ./wiki/build_wiki.py - - name: Push Wiki Changes - uses: Andrew-Chen-Wang/github-wiki-action@v2 + uses: Andrew-Chen-Wang/github-wiki-action@v3 env: - WIKI_DIR: wiki/pages/ + WIKI_DIR: wiki/ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_MAIL: ${{ secrets.EMAIL }} GH_NAME: ${{ github.repository_owner }} diff --git a/README.md b/README.md index 18fd54f..c45da03 100755 --- a/README.md +++ b/README.md @@ -28,3 +28,12 @@ * **Contact**: [braden.dubois@usask.ca](mailto:braden.dubois@usask.ca) See the [wiki](https://github.com/bradendubois/do-calculus/wiki) to get started. + + +## Development Status + +A full overhaul has been completed, and marks important milestones in my life and (consequently) this project's. Development on this project is halted until further notice, barring further changes to my own life, or necessary bug fixes and/or security fixes. + +## Acknowledgements + +This project represents approximately two years of part- and full-time work as part of an indescribably fulfilling undergraduate research project. This project was done under the supervision of Dr. Eric Neufeld from approximately Spring 2020 - Winter 2022. Without Dr. Neufeld's support, guidance, patience, expertise (and of course, funding), this project would never have been started, let alone completed. I cannot overstate my appreciation - you've changed my academic and professional path, and provided me with so many wonderful experiences and memories that I will never forget. Thanks, Eric. diff --git a/wiki/pages/Ancestors.md b/archive/Ancestors.md similarity index 100% rename from wiki/pages/Ancestors.md rename to archive/Ancestors.md diff --git a/wiki/pages/Backdoor Paths.md b/archive/Backdoor Paths.md similarity index 100% rename from wiki/pages/Backdoor Paths.md rename to archive/Backdoor Paths.md diff --git a/wiki/pages/Children.md b/archive/Children.md similarity index 100% rename from wiki/pages/Children.md rename to archive/Children.md diff --git a/wiki/pages/Conditional Independence.md b/archive/Conditional Independence.md similarity index 100% rename from wiki/pages/Conditional Independence.md rename to archive/Conditional Independence.md diff --git a/wiki/pages/Configuration.md b/archive/Configuration.md similarity index 100% rename from wiki/pages/Configuration.md rename to archive/Configuration.md diff --git a/wiki/pages/Deconfounding Sets.md b/archive/Deconfounding Sets.md similarity index 100% rename from wiki/pages/Deconfounding Sets.md rename to archive/Deconfounding Sets.md diff --git a/wiki/pages/Definitions.md b/archive/Definitions.md similarity index 100% rename from wiki/pages/Definitions.md rename to archive/Definitions.md diff --git a/wiki/pages/Descendants.md b/archive/Descendants.md similarity index 100% rename from wiki/pages/Descendants.md rename to archive/Descendants.md diff --git a/wiki/pages/Do API.md b/archive/Do API.md similarity index 100% rename from wiki/pages/Do API.md rename to archive/Do API.md diff --git a/wiki/pages/Exceptions.md b/archive/Exceptions.md similarity index 100% rename from wiki/pages/Exceptions.md rename to archive/Exceptions.md diff --git a/wiki/pages/GitHub.md b/archive/GitHub.md similarity index 100% rename from wiki/pages/GitHub.md rename to archive/GitHub.md diff --git a/wiki/pages/Home.md b/archive/Home.md similarity index 100% rename from wiki/pages/Home.md rename to archive/Home.md diff --git a/wiki/pages/Installation.md b/archive/Installation.md similarity index 100% rename from wiki/pages/Installation.md rename to archive/Installation.md diff --git a/wiki/pages/Joint Distribution Table.md b/archive/Joint Distribution Table.md similarity index 100% rename from wiki/pages/Joint Distribution Table.md rename to archive/Joint Distribution Table.md diff --git a/wiki/pages/Literature.md b/archive/Literature.md similarity index 100% rename from wiki/pages/Literature.md rename to archive/Literature.md diff --git a/wiki/pages/Loading a Model.md b/archive/Loading a Model.md similarity index 100% rename from wiki/pages/Loading a Model.md rename to archive/Loading a Model.md diff --git a/wiki/pages/Markovian Models.md b/archive/Markovian Models.md similarity index 100% rename from wiki/pages/Markovian Models.md rename to archive/Markovian Models.md diff --git a/wiki/pages/Output.md b/archive/Output.md similarity index 100% rename from wiki/pages/Output.md rename to archive/Output.md diff --git a/wiki/pages/Parents.md b/archive/Parents.md similarity index 100% rename from wiki/pages/Parents.md rename to archive/Parents.md diff --git a/wiki/pages/Probability Queries.md b/archive/Probability Queries.md similarity index 100% rename from wiki/pages/Probability Queries.md rename to archive/Probability Queries.md diff --git a/wiki/pages/PyPI.md b/archive/PyPI.md similarity index 100% rename from wiki/pages/PyPI.md rename to archive/PyPI.md diff --git a/archive/README.md b/archive/README.md new file mode 100644 index 0000000..4eea6b1 --- /dev/null +++ b/archive/README.md @@ -0,0 +1,5 @@ +# Archive + +Contained here is some old code that built a (now-outdated) wiki that I don't have the heart to delete. + +Don't rely on it expecting accurate information. diff --git a/wiki/pages/Resources.md b/archive/Resources.md similarity index 100% rename from wiki/pages/Resources.md rename to archive/Resources.md diff --git a/wiki/pages/Roots.md b/archive/Roots.md similarity index 100% rename from wiki/pages/Roots.md rename to archive/Roots.md diff --git a/wiki/pages/Sinks.md b/archive/Sinks.md similarity index 100% rename from wiki/pages/Sinks.md rename to archive/Sinks.md diff --git a/wiki/pages/Standard Paths.md b/archive/Standard Paths.md similarity index 100% rename from wiki/pages/Standard Paths.md rename to archive/Standard Paths.md diff --git a/wiki/pages/Topology.md b/archive/Topology.md similarity index 100% rename from wiki/pages/Topology.md rename to archive/Topology.md diff --git a/wiki/pages/_Sidebar.md b/archive/_Sidebar.md similarity index 100% rename from wiki/pages/_Sidebar.md rename to archive/_Sidebar.md diff --git a/wiki/pages/__init__.md b/archive/__init__.md similarity index 100% rename from wiki/pages/__init__.md rename to archive/__init__.md diff --git a/wiki/__init__.py b/archive/__init__.py similarity index 100% rename from wiki/__init__.py rename to archive/__init__.py diff --git a/wiki/build_wiki.py b/archive/build_wiki.py similarity index 100% rename from wiki/build_wiki.py rename to archive/build_wiki.py diff --git a/do/identification/API.py b/do/identification/API.py index 07c57fc..6ee162d 100644 --- a/do/identification/API.py +++ b/do/identification/API.py @@ -5,7 +5,7 @@ from ..core.Variables import Intervention, Outcome from .LatentGraph import latent_transform -from .Identification import Identification +from .Identification import Identification, simplify_expression from .PExpression import PExpression, TemplateExpression @@ -62,7 +62,6 @@ def _process(current: Union[PExpression, TemplateExpression], known: Mapping[str return t result = _process(expression, {v.name: v.outcome for v in y} | {v.name: v.outcome for v in x}) - return (result, expression.proof()) if include_proof else result def proof(self, y: Set[Outcome], x: Set[Intervention], model: Model) -> str: diff --git a/do/identification/LatentGraph.py b/do/identification/LatentGraph.py index c393680..d36c5b8 100644 --- a/do/identification/LatentGraph.py +++ b/do/identification/LatentGraph.py @@ -179,10 +179,10 @@ def latent_transform(g: Graph, u: Set[str]): reduction = False remove = set() - for u in Un: + for un in Un: - parents = [edge[0] for edge in E if edge[1] == u] # Edges : parent -> u - children = [edge[1] for edge in E if edge[0] == u] # Edges : u -> child + parents = [edge[0] for edge in E if edge[1] == un] # Edges : parent -> u + children = [edge[1] for edge in E if edge[0] == un] # Edges : u -> child # All parents are unobservable, all children are observable, at least one parent if all(x in u for x in parents) and len(parents) > 0 and all(x not in u for x in children): @@ -190,18 +190,18 @@ def latent_transform(g: Graph, u: Set[str]): # Remove edges from parents to u for parent in parents: - E.remove((parent, u)) + E.remove((parent, un)) # Remove edges from u to children for child in children: - E.remove((u, child)) + E.remove((un, child)) # Replace with edge from edge parent to each child for cr in product(parents, children): E.add((cr[0], cr[1])) # U can be removed entirely from graph - remove.add(u) + remove.add(un) V -= remove Un -= remove @@ -229,4 +229,5 @@ def latent_transform(g: Graph, u: Set[str]): a, b = child_edges[i], child_edges[(i + 1) % len(child_edges)] E_Bidirected.add((a[1], b[1])) + print(V, u) return LatentGraph(V, E, E_Bidirected, [x for x in g.topology_sort() if x in V - u]) diff --git a/examples/2-backdoor-paths/deconfound.py b/examples/2-backdoor-paths/deconfound.py new file mode 100644 index 0000000..3d010b7 --- /dev/null +++ b/examples/2-backdoor-paths/deconfound.py @@ -0,0 +1,19 @@ +from pathlib import Path + +from do import API + +api = API() + +file = Path("pearl-3.4.yml") +model = api.instantiate_model(file) + +backdoors = api.backdoors({"Xi"}, {"Xj"}, model.graph()) +assert len(backdoors) > 0, "No backdoor paths detected!" +assert not api.blocks({"Xi"}, {"Xj"}, model.graph(), set()), "This should not block all paths!" + +for path in backdoors: + print("Path:", path) + +backdoors = api.backdoors({"Xi"}, {"Xj"}, model.graph(), {"X1", "X4"}) +assert len(backdoors) == 0, "Expected this to block!" +assert api.blocks({"Xi"}, {"Xj"}, model.graph(), {"X1", "X4"}), "This should block!" diff --git a/examples/2-backdoor-paths/pearl-3.4.yml b/examples/2-backdoor-paths/pearl-3.4.yml new file mode 100644 index 0000000..403319c --- /dev/null +++ b/examples/2-backdoor-paths/pearl-3.4.yml @@ -0,0 +1,116 @@ +name: 'Pearl: Figure 3.4' +endogenous: + X1: + outcomes: + - x1 + - ~x1 + parents: [] + table: [ + [x1, 0.4], + [~x1, 0.6] + ] + X2: + outcomes: + - x2 + - ~x2 + parents: [] + table: [ + [x2, 0.15], + [~x2, 0.85] + ] + X3: + outcomes: + - x3 + - ~x3 + parents: + - X1 + table: [ + [x3, x1, 0.1], + [x3, ~x1, 0.3], + [~x3, x1, 0.9], + [~x3, ~x1, 0.7] + ] + X4: + outcomes: + - x4 + - ~x4 + parents: + - X1 + - X2 + table: [ + [x4, x1, x2, 0.7], + [x4, x1, ~x2, 0.9], + [x4, ~x1, x2, 0.55], + [x4, ~x1, ~x2, 0.15], + [~x4, x1, x2, 0.3], + [~x4, x1, ~x2, 0.1], + [~x4, ~x1, x2, 0.45], + [~x4, ~x1, ~x2, 0.85] + ] + X5: + outcomes: + - x5 + - ~x5 + parents: + - X2 + table: [ + [x5, x2, 0.8], + [x5, ~x2, 0.25], + [~x5, x2, 0.2], + [~x5, ~x2, 0.75] + ] + X6: + outcomes: + - x6 + - ~x6 + parents: + - Xi + table: [ + [x6, xi, 0.9], + [x6, ~xi, 0.25], + [~x6, xi, 0.1], + [~x6, ~xi, 0.75] + ] + Xi: + outcomes: + - xi + - ~xi + parents: + - X3 + - X4 + table: [ + [xi, x3, x4, 0.5], + [xi, x3, ~x4, 0.65], + [xi, ~x3, x4, 0.1], + [xi, ~x3, ~x4, 0.25], + [~xi, x3, x4, 0.5], + [~xi, x3, ~x4, 0.35], + [~xi, ~x3, x4, 0.9], + [~xi, ~x3, ~x4, 0.75] + ] + Xj: + outcomes: + - xj + - ~xj + parents: + - X6 + - X4 + - X5 + table: [ + [xj, x6, x4, x5, 0.0], + [xj, x6, x4, ~x5, 0.25], + [xj, x6, ~x4, x5, 0.7], + [xj, x6, ~x4, ~x5, 0.45], + [xj, ~x6, x4, x5, 0.15], + [xj, ~x6, x4, ~x5, 0.8], + [xj, ~x6, ~x4, x5, 0.95], + [xj, ~x6, ~x4, ~x5, 0.05], + [~xj, x6, x4, x5, 1.0], + [~xj, x6, x4, ~x5, 0.75], + [~xj, x6, ~x4, x5, 0.3], + [~xj, x6, ~x4, ~x5, 0.55], + [~xj, ~x6, x4, x5, 0.85], + [~xj, ~x6, x4, ~x5, 0.2], + [~xj, ~x6, ~x4, x5, 0.05], + [~xj, ~x6, ~x4, ~x5, 0.95] + ] diff --git a/examples/3-latent/3.4-latent.yml b/examples/3-latent/3.4-latent.yml new file mode 100644 index 0000000..4b8d6c4 --- /dev/null +++ b/examples/3-latent/3.4-latent.yml @@ -0,0 +1,90 @@ +name: 'Pearl: Figure 3.4' +endogenous: + X1: + outcomes: + - x1 + - ~x1 + parents: [] + table: [ + [x1, 0.4], + [~x1, 0.6] + ] + X2: + outcomes: + - x2 + - ~x2 + parents: [] + table: [ + [x2, 0.15], + [~x2, 0.85] + ] + X3: + outcomes: + - x3 + - ~x3 + table: [ + [x3, 0.1], + [~x3, 0.9], + ] + X4: + outcomes: + - x4 + - ~x4 + parents: + - Xi + table: [ + [x4, xi, 0.9], + [x4, ~xi, 0.25], + [~x4, xi, 0.1], + [~x4, ~xi, 0.75] + ] + Xi: + outcomes: + - xi + - ~xi + parents: + - X1 + - X2 + table: [ + [xi, x1, x2, 0.5], + [xi, x1, ~x2, 0.65], + [xi, ~x1, x2, 0.1], + [xi, ~x1, ~x2, 0.25], + [~xi, x1, x2, 0.5], + [~xi, x1, ~x2, 0.35], + [~xi, ~x1, x2, 0.9], + [~xi, ~x1, ~x2, 0.75] + ] + Xj: + outcomes: + - xj + - ~xj + parents: + - X2 + - X3 + - X4 + table: [ + [xj, x2, x3, x4, 0.0], + [xj, x2, x3, ~x4, 0.25], + [xj, x2, ~x3, x4, 0.7], + [xj, x2, ~x3, ~x4, 0.45], + [xj, ~x2, x3, x4, 0.15], + [xj, ~x2, x3, ~x4, 0.8], + [xj, ~x2, ~x3, x4, 0.95], + [xj, ~x2, ~x3, ~x4, 0.05], + [~xj, x2, x3, x4, 1.0], + [~xj, x2, x3, ~x4, 0.75], + [~xj, x2, ~x3, x4, 0.3], + [~xj, x2, ~x3, ~x4, 0.55], + [~xj, ~x2, x3, x4, 0.85], + [~xj, ~x2, x3, ~x4, 0.2], + [~xj, ~x2, ~x3, x4, 0.05], + [~xj, ~x2, ~x3, ~x4, 0.95] + ] +exogenous: + U1: + - X1 + - X2 + U2: + - X2 + - X3 diff --git a/examples/3-latent/deconfound.py b/examples/3-latent/deconfound.py new file mode 100644 index 0000000..2e58feb --- /dev/null +++ b/examples/3-latent/deconfound.py @@ -0,0 +1,16 @@ +from pathlib import Path + +from do import API, Expression, Intervention, Outcome + +api = API() + +file = Path("3.4-latent.yml") +model = api.instantiate_model(file) + +xj = Outcome("Xj", "xj") +xi = Intervention("Xi", "xi") +e = Expression(xj, [xi]) + +result, proof = api.identification([xj], [xi], model) +print(result) +print(proof) diff --git a/tests/identification/test_PExpression.py b/tests/identification/test_PExpression.py deleted file mode 100644 index eb1ae45..0000000 --- a/tests/identification/test_PExpression.py +++ /dev/null @@ -1 +0,0 @@ -... diff --git a/wiki/API.md b/wiki/API.md new file mode 100644 index 0000000..9987a2a --- /dev/null +++ b/wiki/API.md @@ -0,0 +1,28 @@ +# API + +Details on the [API](https://en.wikipedia.org/wiki/API) provided in the project. + +This assumes the steps in the [[Installation]] section have been followed, and the project is set up. + +**Note**: For simplicity of import-statements, any examples will *assume* the project was installed as [PyPI](https://pypi.org/project/do-calculus/) package. + +## Importing + +To import the package: + +```python +import do +``` + +**Important**: +- The package name on [PyPI](https://pypi.org/) is [do-calculus](https://pypi.org/project/do-calculus/), but the module to import is called ``do``. + +