effectful 0.0.1__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
effectful/ops/types.py CHANGED
@@ -1,19 +1,22 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import abc
4
+ import collections.abc
5
+ import functools
6
+ import inspect
4
7
  import typing
5
- from typing import Any, Callable, Generic, Mapping, Sequence, Set, Type, TypeVar, Union
8
+ from collections.abc import Callable, Mapping, Sequence
9
+ from typing import Any, _ProtocolMeta, overload, runtime_checkable
6
10
 
7
- from typing_extensions import ParamSpec
8
11
 
9
- P = ParamSpec("P")
10
- Q = ParamSpec("Q")
11
- S = TypeVar("S")
12
- T = TypeVar("T")
13
- V = TypeVar("V")
12
+ class NotHandled(Exception):
13
+ """Raised by an operation when the operation should remain unhandled."""
14
+
15
+ pass
14
16
 
15
17
 
16
- class Operation(abc.ABC, Generic[Q, V]):
18
+ @functools.total_ordering
19
+ class Operation[**Q, V](abc.ABC):
17
20
  """An abstract class representing an effect that can be implemented by an effect handler.
18
21
 
19
22
  .. note::
@@ -22,6 +25,9 @@ class Operation(abc.ABC, Generic[Q, V]):
22
25
 
23
26
  """
24
27
 
28
+ __signature__: inspect.Signature
29
+ __name__: str
30
+
25
31
  @abc.abstractmethod
26
32
  def __eq__(self, other):
27
33
  raise NotImplementedError
@@ -31,7 +37,11 @@ class Operation(abc.ABC, Generic[Q, V]):
31
37
  raise NotImplementedError
32
38
 
33
39
  @abc.abstractmethod
34
- def __default_rule__(self, *args: Q.args, **kwargs: Q.kwargs) -> "Expr[V]":
40
+ def __lt__(self, other):
41
+ raise NotImplementedError
42
+
43
+ @abc.abstractmethod
44
+ def __default_rule__(self, *args: Q.args, **kwargs: Q.kwargs) -> Expr[V]:
35
45
  """The default rule is used when the operation is not handled.
36
46
 
37
47
  If no default rule is supplied, the free rule is used instead.
@@ -39,33 +49,33 @@ class Operation(abc.ABC, Generic[Q, V]):
39
49
  raise NotImplementedError
40
50
 
41
51
  @abc.abstractmethod
42
- def __free_rule__(self, *args: Q.args, **kwargs: Q.kwargs) -> "Expr[V]":
43
- """Returns a term for the operation applied to arguments."""
44
- raise NotImplementedError
45
-
46
- @abc.abstractmethod
47
- def __type_rule__(self, *args: Q.args, **kwargs: Q.kwargs) -> Type[V]:
52
+ def __type_rule__(self, *args: Q.args, **kwargs: Q.kwargs) -> type[V]:
48
53
  """Returns the type of the operation applied to arguments."""
49
54
  raise NotImplementedError
50
55
 
51
56
  @abc.abstractmethod
52
- def __fvs_rule__(self, *args: Q.args, **kwargs: Q.kwargs) -> Set["Operation"]:
53
- """Returns the free variables of the operation applied to arguments."""
54
- raise NotImplementedError
57
+ def __fvs_rule__(self, *args: Q.args, **kwargs: Q.kwargs) -> inspect.BoundArguments:
58
+ """
59
+ Returns the sets of variables that appear free in each argument and keyword argument
60
+ but not in the result of the operation, i.e. the variables bound by the operation.
55
61
 
56
- @abc.abstractmethod
57
- def __repr_rule__(self, *args: Q.args, **kwargs: Q.kwargs) -> str:
62
+ These are used by :func:`fvsof` to determine the free variables of a term by
63
+ subtracting the results of this method from the free variables of the subterms,
64
+ allowing :func:`fvsof` to be implemented in terms of :func:`evaluate` .
65
+ """
58
66
  raise NotImplementedError
59
67
 
60
68
  @typing.final
61
69
  def __call__(self, *args: Q.args, **kwargs: Q.kwargs) -> V:
62
- from effectful.internals.runtime import get_interpretation
63
70
  from effectful.ops.semantics import apply
64
71
 
65
- return apply.__default_rule__(get_interpretation(), self, *args, **kwargs) # type: ignore
72
+ return apply.__default_rule__(self, *args, **kwargs) # type: ignore
73
+
74
+ def __repr__(self):
75
+ return f"{self.__class__.__name__}({self.__name__}, {self.__signature__})"
66
76
 
67
77
 
68
- class Term(abc.ABC, Generic[T]):
78
+ class Term[T](abc.ABC):
69
79
  """A term in an effectful computation is a is a tree of :class:`Operation`
70
80
  applied to values.
71
81
 
@@ -77,34 +87,130 @@ class Term(abc.ABC, Generic[T]):
77
87
  @abc.abstractmethod
78
88
  def op(self) -> Operation[..., T]:
79
89
  """Abstract property for the operation."""
80
- pass
90
+ raise NotImplementedError
81
91
 
82
92
  @property
83
93
  @abc.abstractmethod
84
- def args(self) -> Sequence["Expr[Any]"]:
94
+ def args(self) -> Sequence[Expr[Any]]:
85
95
  """Abstract property for the arguments."""
86
- pass
96
+ raise NotImplementedError
87
97
 
88
98
  @property
89
99
  @abc.abstractmethod
90
- def kwargs(self) -> Mapping[str, "Expr[Any]"]:
100
+ def kwargs(self) -> Mapping[str, Expr[Any]]:
91
101
  """Abstract property for the keyword arguments."""
92
- pass
102
+ raise NotImplementedError
93
103
 
94
104
  def __repr__(self) -> str:
105
+ return f"{self.__class__.__name__}({self.op!r}, {self.args!r}, {self.kwargs!r})"
106
+
107
+ def __str__(self) -> str:
95
108
  from effectful.internals.runtime import interpreter
96
109
  from effectful.ops.semantics import apply, evaluate
97
110
 
98
- with interpreter({apply: lambda _, op, *a, **k: op.__repr_rule__(*a, **k)}):
99
- return evaluate(self) # type: ignore
111
+ fresh: dict[str, dict[Operation, int]] = collections.defaultdict(dict)
112
+
113
+ def op_str(op):
114
+ """Return a unique (in this term) name for the operation."""
115
+ name = op.__name__
116
+ if name not in fresh:
117
+ fresh[name] = {op: 0}
118
+ if op not in fresh[name]:
119
+ fresh[name][op] = len(fresh[name])
120
+
121
+ n = fresh[name][op]
122
+ if n == 0:
123
+ return name
124
+ return f"{name}!{n}"
125
+
126
+ def term_str(term):
127
+ if isinstance(term, Operation):
128
+ return op_str(term)
129
+ elif isinstance(term, list):
130
+ return "[" + ", ".join(map(term_str, term)) + "]"
131
+ elif isinstance(term, tuple):
132
+ return "(" + ", ".join(map(term_str, term)) + ")"
133
+ elif isinstance(term, dict):
134
+ return (
135
+ "{"
136
+ + ", ".join(
137
+ f"{term_str(k)}:{term_str(v)}" for (k, v) in term.items()
138
+ )
139
+ + "}"
140
+ )
141
+ return str(term)
142
+
143
+ def _apply(op, *args, **kwargs) -> str:
144
+ args_str = ", ".join(map(term_str, args)) if args else ""
145
+ kwargs_str = (
146
+ ", ".join(f"{k}={term_str(v)}" for k, v in kwargs.items())
147
+ if kwargs
148
+ else ""
149
+ )
150
+
151
+ ret = f"{op_str(op)}({args_str}"
152
+ if kwargs:
153
+ ret += f"{', ' if args else ''}"
154
+ ret += f"{kwargs_str})"
155
+ return ret
156
+
157
+ with interpreter({apply: _apply}):
158
+ return typing.cast(str, evaluate(self))
100
159
 
101
160
 
102
161
  #: An expression is either a value or a term.
103
- Expr = Union[T, Term[T]]
162
+ type Expr[T] = T | Term[T]
104
163
 
105
- #: An interpretation is a mapping from operations to their implementations.
106
- Interpretation = Mapping[Operation[..., T], Callable[..., V]]
107
164
 
165
+ class _InterpretationMeta(_ProtocolMeta):
166
+ def __instancecheck__(cls, instance):
167
+ return isinstance(instance, collections.abc.Mapping) and all(
168
+ isinstance(k, Operation) and callable(v) for k, v in instance.items()
169
+ )
108
170
 
109
- class ArgAnnotation:
110
- pass
171
+
172
+ @runtime_checkable
173
+ class Interpretation[T, V](typing.Protocol, metaclass=_InterpretationMeta):
174
+ """An interpretation is a mapping from operations to their implementations."""
175
+
176
+ def keys(self):
177
+ raise NotImplementedError
178
+
179
+ def values(self):
180
+ raise NotImplementedError
181
+
182
+ def items(self):
183
+ raise NotImplementedError
184
+
185
+ @overload
186
+ def get(self, key: Operation[..., T], /) -> Callable[..., V] | None:
187
+ raise NotImplementedError
188
+
189
+ @overload
190
+ def get(
191
+ self, key: Operation[..., T], default: Callable[..., V], /
192
+ ) -> Callable[..., V]:
193
+ raise NotImplementedError
194
+
195
+ @overload
196
+ def get[S](self, key: Operation[..., T], default: S, /) -> Callable[..., V] | S:
197
+ raise NotImplementedError
198
+
199
+ def __getitem__(self, key: Operation[..., T]) -> Callable[..., V]:
200
+ raise NotImplementedError
201
+
202
+ def __contains__(self, key: Operation[..., T]) -> bool:
203
+ raise NotImplementedError
204
+
205
+ def __iter__(self):
206
+ raise NotImplementedError
207
+
208
+ def __len__(self) -> int:
209
+ raise NotImplementedError
210
+
211
+
212
+ class Annotation(abc.ABC):
213
+ @classmethod
214
+ @abc.abstractmethod
215
+ def infer_annotations(cls, sig: inspect.Signature) -> inspect.Signature:
216
+ raise NotImplementedError
@@ -1,63 +1,59 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: effectful
3
- Version: 0.0.1
3
+ Version: 0.2.0
4
4
  Summary: Metaprogramming infrastructure
5
- Home-page: https://www.basis.ai/
6
5
  Author: Basis
7
- License: Apache 2.0
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://www.basis.ai/
8
8
  Project-URL: Source, https://github.com/BasisResearch/effectful
9
- Keywords: machine learning statistics probabilistic programming bayesian modeling pytorch
9
+ Project-URL: Bug Tracker, https://github.com/BasisResearch/effectful/issues
10
+ Keywords: machine learning,statistics,probabilistic programming,bayesian modeling,pytorch
10
11
  Classifier: Intended Audience :: Developers
11
12
  Classifier: Intended Audience :: Education
12
13
  Classifier: Intended Audience :: Science/Research
13
- Classifier: License :: OSI Approved :: Apache Software License
14
14
  Classifier: Operating System :: POSIX :: Linux
15
15
  Classifier: Operating System :: MacOS :: MacOS X
16
- Classifier: Programming Language :: Python :: 3.10
17
- Classifier: Programming Language :: Python :: 3.11
18
- Requires-Python: >=3.10
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Requires-Python: >=3.12
19
+ Description-Content-Type: text/x-rst
19
20
  License-File: LICENSE.md
20
- Requires-Dist: typing_extensions
21
- Requires-Dist: dm-tree
22
21
  Provides-Extra: torch
23
22
  Requires-Dist: torch; extra == "torch"
23
+ Requires-Dist: dm-tree; extra == "torch"
24
24
  Provides-Extra: pyro
25
- Requires-Dist: torch; extra == "pyro"
26
- Requires-Dist: pyro-ppl; extra == "pyro"
27
- Provides-Extra: dev
28
- Requires-Dist: torch; extra == "dev"
29
- Requires-Dist: pyro-ppl; extra == "dev"
30
- Requires-Dist: torch; extra == "dev"
31
- Requires-Dist: pytest; extra == "dev"
32
- Requires-Dist: pytest-cov; extra == "dev"
33
- Requires-Dist: pytest-xdist; extra == "dev"
34
- Requires-Dist: pytest-benchmark; extra == "dev"
35
- Requires-Dist: mypy; extra == "dev"
36
- Requires-Dist: black; extra == "dev"
37
- Requires-Dist: flake8; extra == "dev"
38
- Requires-Dist: isort; extra == "dev"
39
- Requires-Dist: sphinx; extra == "dev"
40
- Requires-Dist: sphinxcontrib-bibtex; extra == "dev"
41
- Requires-Dist: sphinx_rtd_theme; extra == "dev"
42
- Requires-Dist: myst-parser; extra == "dev"
43
- Requires-Dist: nbsphinx; extra == "dev"
44
- Requires-Dist: nbval; extra == "dev"
45
- Requires-Dist: nbqa; extra == "dev"
46
- Dynamic: author
47
- Dynamic: classifier
48
- Dynamic: description
49
- Dynamic: home-page
50
- Dynamic: keywords
51
- Dynamic: license
52
- Dynamic: project-url
53
- Dynamic: provides-extra
54
- Dynamic: requires-dist
55
- Dynamic: requires-python
56
- Dynamic: summary
25
+ Requires-Dist: pyro-ppl>=1.9.1; extra == "pyro"
26
+ Requires-Dist: dm-tree; extra == "pyro"
27
+ Provides-Extra: jax
28
+ Requires-Dist: jax; extra == "jax"
29
+ Requires-Dist: dm-tree; extra == "jax"
30
+ Provides-Extra: numpyro
31
+ Requires-Dist: numpyro>=0.19; extra == "numpyro"
32
+ Requires-Dist: dm-tree; extra == "numpyro"
33
+ Provides-Extra: docs
34
+ Requires-Dist: effectful[jax,numpyro,pyro,torch]; extra == "docs"
35
+ Requires-Dist: sphinx; extra == "docs"
36
+ Requires-Dist: sphinxcontrib-bibtex; extra == "docs"
37
+ Requires-Dist: sphinx_rtd_theme; extra == "docs"
38
+ Requires-Dist: myst-parser; extra == "docs"
39
+ Requires-Dist: nbsphinx; extra == "docs"
40
+ Requires-Dist: sphinx_autodoc_typehints; extra == "docs"
41
+ Requires-Dist: pypandoc_binary; extra == "docs"
42
+ Provides-Extra: test
43
+ Requires-Dist: effectful[docs,jax,numpyro,pyro,torch]; extra == "test"
44
+ Requires-Dist: pytest; extra == "test"
45
+ Requires-Dist: pytest-cov; extra == "test"
46
+ Requires-Dist: pytest-xdist; extra == "test"
47
+ Requires-Dist: pytest-benchmark; extra == "test"
48
+ Requires-Dist: mypy; extra == "test"
49
+ Requires-Dist: ruff; extra == "test"
50
+ Requires-Dist: nbval; extra == "test"
51
+ Requires-Dist: nbqa; extra == "test"
52
+ Dynamic: license-file
57
53
 
58
54
  .. index-inclusion-marker
59
55
 
60
- Effectful
56
+ Effectful
61
57
  =========
62
58
 
63
59
  Effectful is an algebraic effect system for Python, intended for use in the
@@ -75,14 +71,17 @@ Install From Source
75
71
  git clone git@github.com:BasisResearch/effectful.git
76
72
  cd effectful
77
73
  git checkout master
78
- pip install -e .[test]
74
+ pip install -e .[pyro]
79
75
 
80
76
  Install With Optional PyTorch/Pyro Support
81
77
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
82
78
 
83
- ``effectful`` has optional support for `PyTorch <https://pytorch.org/>`_ (tensors
84
- with named dimensions) and `Pyro <https://pyro.ai/>`_ (wrappers for Pyro
85
- effects).
79
+ ``effectful`` has optional support for:
80
+
81
+ - `PyTorch <https://pytorch.org/>`_ (tensors with named dimensions)
82
+ - `Pyro <https://pyro.ai/>`_ (wrappers for Pyro effects)
83
+ - `Jax <https://docs.jax.dev/en/latest/index.html>`_ (tensors with named dimensions)
84
+ - `Numpyro <https://num.pyro.ai>`_ (operations for Numpyro distributions)
86
85
 
87
86
  To enable PyTorch support:
88
87
 
@@ -96,6 +95,18 @@ Pyro support (which includes PyTorch support):
96
95
 
97
96
  pip install effectful[pyro]
98
97
 
98
+ Jax support:
99
+
100
+ .. code:: sh
101
+
102
+ pip install effectful[jax]
103
+
104
+ Numpyro support (which includes Jax support):
105
+
106
+ .. code:: sh
107
+
108
+ pip install effectful[numpyro]
109
+
99
110
  Getting Started
100
111
  ---------------
101
112
 
@@ -103,18 +114,15 @@ Here's an example demonstrating how ``effectful`` can be used to implement a sim
103
114
 
104
115
  .. code:: python
105
116
 
106
- import operator
107
117
  import functools
108
118
 
109
- from effectful.handlers.operator import OPERATORS
110
119
  from effectful.ops.types import Term
111
- from effectful.ops.syntax import defop
120
+ from effectful.ops.syntax import defdata, defop
112
121
  from effectful.ops.semantics import handler, evaluate, coproduct, fwd
113
- import effectful.handlers.operator
114
122
 
115
- add = OPERATORS[operator.add]
123
+ add = defdata.dispatch(int).__add__
116
124
 
117
- def beta_add(x: int, y: int) -> int:
125
+ def beta_add(x: int, y: int) -> int:
118
126
  match x, y:
119
127
  case int(), int():
120
128
  return x + y
@@ -124,14 +132,14 @@ Here's an example demonstrating how ``effectful`` can be used to implement a sim
124
132
  def commute_add(x: int, y: int) -> int:
125
133
  match x, y:
126
134
  case Term(), int():
127
- return y + x
135
+ return y + x
128
136
  case _:
129
137
  return fwd()
130
138
 
131
139
  def assoc_add(x: int, y: int) -> int:
132
140
  match x, y:
133
141
  case _, Term(op, (a, b)) if op == add:
134
- return (x + a) + b
142
+ return (x + a) + b
135
143
  case _:
136
144
  return fwd()
137
145
 
@@ -163,7 +171,7 @@ We can make the evaluation strategy smarter by taking advantage of the commutati
163
171
  >>> with handler(eager_mixed):
164
172
  >>> print(evaluate(e))
165
173
  add(8, add(x(), y()))
166
-
174
+
167
175
  Learn More
168
176
  ----------
169
177
 
@@ -0,0 +1,26 @@
1
+ effectful/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ effectful/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ effectful/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ effectful/handlers/indexed.py,sha256=ZY-8w32a1PKGVScwXjbgByI3wRHvfxuuXJVwLlp0rgw,12622
5
+ effectful/handlers/numpyro.py,sha256=RWoBNpHLr5KdotN5Vu118jY0kn_p6NaejcnJgZJLehw,19457
6
+ effectful/handlers/pyro.py,sha256=qVl1wson02pyV8YHGf93KDnYEp5pGmhKEwji95OYBl8,26486
7
+ effectful/handlers/torch.py,sha256=NNM7mxqZskEBCjsl25kHI95WlXG9aeD7FaSkXkoLZ_I,24330
8
+ effectful/handlers/jax/__init__.py,sha256=O-BygB2HCkDftP1B98mQnsrt7sOdZpvC2YoHXd61tmI,494
9
+ effectful/handlers/jax/_handlers.py,sha256=95fGOZoTQfYehs0ip_zR91TKwnJB-beK6wr_SH73Bg8,9252
10
+ effectful/handlers/jax/_terms.py,sha256=k_nLe5jvj76ZHVr4LWWFbQT4lxhGUVTEWQB6W38_M8I,16341
11
+ effectful/handlers/jax/numpy/__init__.py,sha256=Kmvya0QI-GA56pPf1as-wYOuZFngOBLtsawa35vPKhg,516
12
+ effectful/handlers/jax/numpy/linalg.py,sha256=9DiaYYG4SztmO-VkmMH3dVvULtMK-zEgbV9oNQFkFo8,350
13
+ effectful/handlers/jax/scipy/special.py,sha256=yTIECFtQVPgraonrPlyenjvcnEYchZwIZC-5CSkF-lA,299
14
+ effectful/internals/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ effectful/internals/runtime.py,sha256=aLWol7sR1yHekn7zNz1evHKHARjiT1tnkmByLHPHBGc,1811
16
+ effectful/internals/tensor_utils.py,sha256=3QCSUqdxCXod3dsY3oRMcg36Rqr8pVX-ktEyCEkeODo,1173
17
+ effectful/internals/unification.py,sha256=CTzRDVqziYKMjqedB1IKHLK4YkHK8TZrlPaP5Ivb0-o,30180
18
+ effectful/ops/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ effectful/ops/semantics.py,sha256=eFBKOKZ-ah0rN47GGUVW_hdBo1HXfk4hiuqjEgQCd-A,11596
20
+ effectful/ops/syntax.py,sha256=5YZh7vBVsWqMRNGok0_yNYLhsJXjrnq2sL1VN9aiN7M,55531
21
+ effectful/ops/types.py,sha256=W1gZJaBnX7_nFpWrG3vfCBQPSun3Gc9PqT61ls8B3EA,6599
22
+ effectful-0.2.0.dist-info/licenses/LICENSE.md,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
23
+ effectful-0.2.0.dist-info/METADATA,sha256=OXKK_zuSeSxST7LbwMCobitLX0OsOQu-wRfd7LzKJGM,5300
24
+ effectful-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
+ effectful-0.2.0.dist-info/top_level.txt,sha256=gtuJfrE2nXil_lZLCnqWF2KAbOnJs9ILNvK8WnkRzbs,10
26
+ effectful-0.2.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5