-
Notifications
You must be signed in to change notification settings - Fork 0
Add random variable nodes #143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
a8f2751
rename ParameterNode to DataNode
mscroggs df6bf52
add evaluate method
mscroggs 1d0738d
mypy?
mscroggs cbb10ea
ruff
mscroggs a956b83
typeerror
mscroggs b7b3332
rufff
mscroggs abbff6b
simplify some graph code
mscroggs 43149c5
Update src/causalprog/graph/node/base.py
mscroggs 6e68264
Update src/causalprog/graph/node/base.py
mscroggs 8f64aa4
Update tests/test_algorithms/test_evaluate.py
mscroggs 752005a
Apply suggestion from @mscroggs
mscroggs 7f1db29
fix tests
mscroggs 5ed79b5
ruff
mscroggs bb1b0f3
hasattr(..., "shape") instead of type check
mscroggs 2c23e01
let KeyError happen rather than asserting types
mscroggs 2815ff8
mypy?
mscroggs 42a2844
currect type
mscroggs 2e1eb1b
type: ignore
mscroggs dae7e6b
PGH003
mscroggs 16c04df
Merge branch 'mscroggs/data-node' into mscroggs/rvnodes
mscroggs 5717c16
typing
mscroggs 209b86e
corrections
mscroggs 06d0f69
add check that labels are valid variable names
mscroggs 7700ea5
ruff
mscroggs 29f7679
update tests and labels
mscroggs ac099fe
Add random variable nodes
mscroggs 30c0216
ruff
mscroggs 13ad547
ruff
mscroggs 7e6e8c1
Merge branch 'main' into mscroggs/rvnodes2
mscroggs f548150
add assert_is_valid_value
mscroggs fb9c13c
Update tests/test_node/test_random_variables.py
mscroggs 7a0104c
-> None
mscroggs 83b6951
Merge branch 'main' into mscroggs/rvnodes2
mscroggs File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,12 @@ | ||
| """Creation and storage of graphs.""" | ||
|
|
||
| from .graph import Graph | ||
| from .node import ComponentNode, ConstantNode, DataNode, DistributionNode, Node | ||
| from .node import ( | ||
| ComponentNode, | ||
| ConstantNode, | ||
| ContinuousRandomVariableNode, | ||
| DataNode, | ||
| DiscreteRandomVariableNode, | ||
| DistributionNode, | ||
| Node, | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| """Graph nodes representing random variables.""" | ||
|
|
||
| import inspect | ||
| import typing | ||
| from abc import abstractmethod | ||
|
|
||
| import jax | ||
| import numpy as np | ||
| import numpy.typing as npt | ||
| from typing_extensions import override | ||
|
|
||
| from .base import Node | ||
|
|
||
|
|
||
| class RandomVariableNode(Node): | ||
| """A node containing a random variable (RV).""" | ||
|
|
||
| def __init__( | ||
| self, | ||
| *, | ||
| shape: tuple[int, ...] = (), | ||
| label: str, | ||
| compute: typing.Callable | None = None, | ||
| ) -> None: | ||
| """ | ||
| Initialise. | ||
|
|
||
| Args: | ||
| shape: The shape of the output of the RV | ||
| label: A unique label to identify the node | ||
| compute: A function to compute node's value from given values of parents | ||
|
|
||
| """ | ||
| super().__init__(label=label, shape=shape) | ||
| if compute is None: | ||
| self._parents = [] | ||
| else: | ||
| self._parents = list(inspect.signature(compute).parameters.keys()) | ||
| self._compute = compute | ||
|
|
||
| @override | ||
| def sample( | ||
| self, | ||
| parameter_values: dict[str, float], | ||
| sampled_dependencies: dict[str, npt.ArrayLike], | ||
| samples: int, | ||
| *, | ||
| rng_key: jax.Array, | ||
| ) -> npt.ArrayLike: | ||
| raise NotImplementedError | ||
|
|
||
| @override | ||
| def evaluate( | ||
| self, | ||
| **given_values: float | npt.NDArray[float], | ||
| ) -> float | npt.NDArray[float]: | ||
| if self.label in given_values: | ||
| value = given_values[self.label] | ||
| self.assert_is_valid_value(value) | ||
| return value | ||
|
|
||
| if self._compute is None: | ||
| msg = f"Missing input for node: {self.label}." | ||
| raise ValueError(msg) | ||
|
willGraham01 marked this conversation as resolved.
|
||
| return self._compute(**{p: given_values[p] for p in self._parents}) | ||
|
mscroggs marked this conversation as resolved.
|
||
|
|
||
| @override | ||
| @property | ||
| def parents(self) -> list[str]: | ||
| return self._parents | ||
|
|
||
| @abstractmethod | ||
| def is_valid_value(self, value: float | npt.NDArray[float]) -> bool: | ||
| """Check if a value is valid for this node.""" | ||
|
|
||
| def assert_is_valid_value(self, value: float | npt.NDArray[float]) -> None: | ||
| """Check if a value is valid for this node.""" | ||
| if not self.is_valid_value(value): | ||
| msg = ( | ||
| f"Invalid value for {self.__class__.__name__}: " | ||
| f"{self.label} cannot be {value}" | ||
| ) | ||
| raise ValueError(msg) | ||
| if self.shape != (value.shape if hasattr(value, "shape") else ()): | ||
| msg = f"Invalid value for node: {self.label}" | ||
| raise ValueError(msg) | ||
|
|
||
|
|
||
| class ContinuousRandomVariableNode(RandomVariableNode): | ||
| """A node containing a continuous random variable (RV).""" | ||
|
|
||
| @override | ||
| def __repr__(self) -> str: | ||
| return f'ContinuousRandomVariableNode(label="{self.label}")' | ||
|
|
||
| @override | ||
| def is_valid_value(self, value: float | npt.NDArray[float]) -> bool: | ||
| return True | ||
|
|
||
| @override | ||
| def copy(self) -> Node: | ||
| return ContinuousRandomVariableNode( | ||
| shape=self.shape, label=self.label, compute=self._compute | ||
| ) | ||
|
|
||
|
|
||
| class DiscreteRandomVariableNode(RandomVariableNode): | ||
| """A node containing a discrete random variable (RV).""" | ||
|
|
||
| def __init__( | ||
| self, | ||
| *, | ||
| values: list[float] | list[npt.NDArray[float]], | ||
| shape: tuple[int, ...] = (), | ||
| label: str, | ||
| compute: typing.Callable | None = None, | ||
| ) -> None: | ||
| """ | ||
| Initialise. | ||
|
|
||
| Args: | ||
| shape: The shape of the output of the RV | ||
| label: A unique label to identify the node | ||
| compute: A function to compute node's value from given values of parents | ||
|
|
||
| """ | ||
| super().__init__(label=label, shape=shape, compute=compute) | ||
| self._values = values | ||
|
|
||
| @property | ||
| def possible_values(self) -> list[float] | list[npt.NDArray[float]]: | ||
| """The values that this RV can take.""" | ||
| return self._values | ||
|
|
||
| @override | ||
| def __repr__(self) -> str: | ||
| return f'DiscreteRandomVariableNode(label="{self.label}")' | ||
|
|
||
| @override | ||
| def is_valid_value(self, value: float | npt.NDArray[float]) -> bool: | ||
| return any(np.allclose(v, value) for v in self._values) | ||
|
|
||
| @override | ||
| def copy(self) -> Node: | ||
| return DiscreteRandomVariableNode( | ||
| values=self._values, | ||
| shape=self.shape, | ||
| label=self.label, | ||
| compute=self._compute, | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import pytest | ||
|
|
||
| from causalprog._abc.labelled import Labelled | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "label", | ||
| [ | ||
| "1", | ||
| " ", | ||
| "a b", | ||
| "0a", | ||
| "a.b", | ||
| "a-b", | ||
| "a+b", | ||
| "a*b", | ||
| "a/b", | ||
| ], | ||
| ) | ||
| def test_invalid_label(label, raises_context): | ||
| with raises_context(ValueError("Label is not valid Python variable name")): | ||
| Labelled(label=label) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| "label", | ||
| [ | ||
| "a", | ||
| "A", | ||
| "a0", | ||
| "a_b", | ||
| "_a", | ||
| ], | ||
| ) | ||
| def test_valid_label(label): | ||
| Labelled(label=label) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import numpy as np | ||
|
|
||
| from causalprog.graph import ( | ||
| ContinuousRandomVariableNode, | ||
| DiscreteRandomVariableNode, | ||
| Graph, | ||
| ) | ||
|
|
||
|
|
||
| def test_random_variable_node(): | ||
| node = ContinuousRandomVariableNode(label="X") | ||
| assert np.isclose(node.evaluate(X=2.0), 2.0) | ||
|
|
||
|
|
||
| def test_missing_input(raises_context): | ||
| node = ContinuousRandomVariableNode(label="X") | ||
|
|
||
| with raises_context(ValueError("Missing input for node")): | ||
| node.evaluate() | ||
|
|
||
|
|
||
| def test_invalid_discrete_node_value(raises_context): | ||
| node = DiscreteRandomVariableNode(label="Y", values=[-0.5, 0.0, 0.5]) | ||
| with raises_context(ValueError("Invalid value for DiscreteRandomVariableNode")): | ||
| node.evaluate(Y=-1.0) | ||
|
|
||
|
|
||
| def test_evaluate_down_graph(): | ||
| graph = Graph(label="G") | ||
|
|
||
| graph.add_node(ContinuousRandomVariableNode(label="x")) | ||
| graph.add_node(DiscreteRandomVariableNode(label="y", values=[-0.5, 0.0, 0.5])) | ||
| graph.add_node(ContinuousRandomVariableNode(label="z", compute=lambda x, y: x + y)) | ||
|
|
||
| assert np.isclose(graph.get_node("z").evaluate(x=3.0, y=-0.5), 2.5) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.