Skip to content

Commit 2c0e780

Browse files
committed
merge with master
2 parents dd4d1ad + b4522d2 commit 2c0e780

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+2667
-1178
lines changed

.github/workflows/pypi.yml

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ on:
33
push:
44
branches:
55
- main
6-
- auto-release
76
pull_request:
87
branches: [main]
98
release:
@@ -16,10 +15,50 @@ concurrency:
1615
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
1716
cancel-in-progress: true
1817

18+
permissions: {}
19+
1920
jobs:
21+
check_changes:
22+
runs-on: ubuntu-latest
23+
outputs:
24+
should_run: ${{ steps.set_should_run.outputs.should_run }}
25+
steps:
26+
- uses: actions/checkout@v4
27+
with:
28+
persist-credentials: false
29+
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
30+
id: filter
31+
with:
32+
filters: |
33+
any_changed:
34+
- '.github/workflows/pypi.yml'
35+
- 'pyproject.toml'
36+
- 'setup.py'
37+
- 'pytensor/_version.py'
38+
- 'pytensor/scan_perform.pyx'
39+
- 'pytensor/scan_perform_ext.py'
40+
- name: Set should_run output
41+
id: set_should_run
42+
run: |
43+
if [[ "${{ github.event_name == 'release' ||
44+
github.ref == 'refs/heads/main' ||
45+
(
46+
github.event_name == 'pull_request'
47+
&& steps.filter.outputs.any_changed == 'true'
48+
)
49+
}}" == "true" ]]; then
50+
echo "should_run=true" >> $GITHUB_OUTPUT
51+
else
52+
echo "should_run=false" >> $GITHUB_OUTPUT
53+
fi
54+
2055
# The job to build precompiled pypi wheels.
2156
make_sdist:
2257
name: Make SDist
58+
needs: check_changes
59+
# Run if it's a release or if relevant files changed on main
60+
if: |
61+
needs.check_changes.outputs.should_run == 'true'
2362
runs-on: ubuntu-latest
2463
permissions:
2564
# write id-token and attestations are required to attest build provenance
@@ -49,6 +88,10 @@ jobs:
4988

5089
run_checks:
5190
name: Build & inspect our package.
91+
needs: check_changes
92+
# Run if it's a release or if relevant files changed on main
93+
if: |
94+
needs.check_changes.outputs.should_run == 'true'
5295
# Note: the resulting builds are not actually published.
5396
# This is purely for additional testing and diagnostic purposes.
5497
runs-on: ubuntu-latest
@@ -62,6 +105,10 @@ jobs:
62105

63106
build_wheels:
64107
name: Build wheels for ${{ matrix.platform }}
108+
needs: check_changes
109+
# Run if it's a release or if relevant files changed on main
110+
if: |
111+
needs.check_changes.outputs.should_run == 'true'
65112
runs-on: ${{ matrix.platform }}
66113
permissions:
67114
# write id-token and attestations are required to attest build provenance
@@ -96,6 +143,10 @@ jobs:
96143

97144
build_universal_wheel:
98145
name: Build universal wheel for Pyodide
146+
needs: check_changes
147+
# Run if it's a release or if relevant files changed on main
148+
if: |
149+
needs.check_changes.outputs.should_run == 'true'
99150
runs-on: ubuntu-latest
100151
permissions:
101152
# write id-token and attestations are required to attest build provenance
@@ -133,7 +184,7 @@ jobs:
133184

134185
check_dist:
135186
name: Check dist
136-
needs: [make_sdist,build_wheels]
187+
needs: [check_changes, make_sdist, build_wheels]
137188
runs-on: ubuntu-22.04
138189
steps:
139190
- uses: actions/download-artifact@v4

doc/conf.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import inspect
33
import sys
4+
45
import pytensor
56
from pathlib import Path
67

@@ -12,6 +13,7 @@
1213
"sphinx.ext.autodoc",
1314
"sphinx.ext.todo",
1415
"sphinx.ext.doctest",
16+
"sphinx_copybutton",
1517
"sphinx.ext.napoleon",
1618
"sphinx.ext.linkcode",
1719
"sphinx.ext.mathjax",
@@ -86,8 +88,7 @@
8688

8789
# List of directories, relative to source directories, that shouldn't be
8890
# searched for source files.
89-
exclude_dirs = ["images", "scripts", "sandbox"]
90-
exclude_patterns = ['page_footer.md', '**/*.myst.md']
91+
exclude_patterns = ["README.md", "images/*", "page_footer.md", "**/*.myst.md"]
9192

9293
# The reST default role (used for this markup: `text`) to use for all
9394
# documents.
@@ -235,24 +236,41 @@
235236
# Resolve function
236237
# This function is used to populate the (source) links in the API
237238
def linkcode_resolve(domain, info):
238-
def find_source():
239+
def find_obj() -> object:
239240
# try to find the file and line number, based on code from numpy:
240241
# https://github.com/numpy/numpy/blob/master/doc/source/conf.py#L286
241242
obj = sys.modules[info["module"]]
242243
for part in info["fullname"].split("."):
243244
obj = getattr(obj, part)
245+
return obj
244246

247+
def find_source(obj):
245248
fn = Path(inspect.getsourcefile(obj))
246-
fn = fn.relative_to(Path(__file__).parent)
249+
fn = fn.relative_to(Path(pytensor.__file__).parent)
247250
source, lineno = inspect.getsourcelines(obj)
248251
return fn, lineno, lineno + len(source) - 1
249252

253+
def fallback_source():
254+
return info["module"].replace(".", "/") + ".py"
255+
250256
if domain != "py" or not info["module"]:
251257
return None
258+
252259
try:
253-
filename = "pytensor/%s#L%d-L%d" % find_source()
260+
obj = find_obj()
254261
except Exception:
255-
filename = info["module"].replace(".", "/") + ".py"
262+
filename = fallback_source()
263+
else:
264+
try:
265+
filename = "pytensor/%s#L%d-L%d" % find_source(obj)
266+
except Exception:
267+
# warnings.warn(f"Could not find source code for {domain}:{info}")
268+
try:
269+
filename = obj.__module__.replace(".", "/") + ".py"
270+
except AttributeError:
271+
# Some objects do not have a __module__ attribute (?)
272+
filename = fallback_source()
273+
256274
import subprocess
257275

258276
tag = subprocess.Popen(

doc/environment.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ dependencies:
1313
- mock
1414
- pillow
1515
- pymc-sphinx-theme
16+
- sphinx-copybutton
1617
- sphinx-design
18+
- sphinx-sitemap
1719
- pygments
1820
- pydot
1921
- ipython
@@ -23,5 +25,4 @@ dependencies:
2325
- ablog
2426
- pip
2527
- pip:
26-
- sphinx_sitemap
2728
- -e ..

doc/library/index.rst

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,12 @@ Modules
2020
d3viz/index
2121
graph/index
2222
gradient
23-
misc/pkl_utils
2423
printing
25-
scalar/index
2624
scan
2725
sparse/index
28-
sparse/sandbox
2926
tensor/index
3027
typed_list
28+
xtensor/index
3129

3230
.. module:: pytensor
3331
:platform: Unix, Windows

doc/library/xtensor/index.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
(libdoc_xtensor)=
2+
# `xtensor` -- XTensor operations
3+
4+
This module implements as abstraction layer on regular tensor operations, that behaves like Xarray.
5+
6+
A new type {class}`pytensor.xtensor.type.XTensorType`, generalizes the {class}`pytensor.tensor.TensorType`
7+
with the addition of a `dims` attribute, that labels the dimensions of the tensor.
8+
9+
Variables of XTensorType (i.e., {class}`pytensor.xtensor.type.XTensorVariable`s) are the symbolic counterpart
10+
to xarray DataArray objects.
11+
12+
The module implements several PyTensor operations {class}`pytensor.xtensor.basic.XOp`s, whose signature mimics that of
13+
xarray (and xarray_einstats) DataArray operations. These operations, unlike most regular PyTensor operations, cannot
14+
be directly evaluated, but require a rewrite (lowering) into a regular tensor graph that can itself be evaluated as usual.
15+
16+
Like regular PyTensor, we don't need an Op for every possible method or function in the public API of xarray.
17+
If the existing XOps can be composed to produce the desired result, then we can use them directly.
18+
19+
## Coordinates
20+
For now, there's no analogous of xarray coordinates, so you won't be able to do coordinate operations like `.sel`.
21+
The graphs produced by an xarray program without coords are much more amenable to the numpy-like backend of PyTensor.
22+
Coords involve aspects of Pandas/database query and joining that are not trivially expressible in PyTensor.
23+
24+
## Example
25+
26+
27+
```{testcode}
28+
29+
import pytensor.tensor as pt
30+
import pytensor.xtensor as ptx
31+
32+
a = pt.tensor("a", shape=(3,))
33+
b = pt.tensor("b", shape=(4,))
34+
35+
ax = ptx.as_xtensor(a, dims=["x"])
36+
bx = ptx.as_xtensor(b, dims=["y"])
37+
38+
zx = ax + bx
39+
assert zx.type == ptx.type.XTensorType("float64", dims=["x", "y"], shape=(3, 4))
40+
41+
z = zx.values
42+
z.dprint()
43+
```
44+
45+
46+
```{testoutput}
47+
48+
TensorFromXTensor [id A]
49+
└─ XElemwise{scalar_op=Add()} [id B]
50+
├─ XTensorFromTensor{dims=('x',)} [id C]
51+
│ └─ a [id D]
52+
└─ XTensorFromTensor{dims=('y',)} [id E]
53+
└─ b [id F]
54+
```
55+
56+
Once we compile the graph, no XOps are left.
57+
58+
```{testcode}
59+
60+
import pytensor
61+
62+
with pytensor.config.change_flags(optimizer_verbose=True):
63+
fn = pytensor.function([a, b], z)
64+
65+
```
66+
67+
```{testoutput}
68+
69+
rewriting: rewrite lower_elemwise replaces XElemwise{scalar_op=Add()}.0 of XElemwise{scalar_op=Add()}(XTensorFromTensor{dims=('x',)}.0, XTensorFromTensor{dims=('y',)}.0) with XTensorFromTensor{dims=('x', 'y')}.0 of XTensorFromTensor{dims=('x', 'y')}(Add.0)
70+
rewriting: rewrite useless_tensor_from_xtensor replaces TensorFromXTensor.0 of TensorFromXTensor(XTensorFromTensor{dims=('x',)}.0) with a of None
71+
rewriting: rewrite useless_tensor_from_xtensor replaces TensorFromXTensor.0 of TensorFromXTensor(XTensorFromTensor{dims=('y',)}.0) with b of None
72+
rewriting: rewrite useless_tensor_from_xtensor replaces TensorFromXTensor.0 of TensorFromXTensor(XTensorFromTensor{dims=('x', 'y')}.0) with Add.0 of Add(ExpandDims{axis=1}.0, ExpandDims{axis=0}.0)
73+
74+
```
75+
76+
```{testcode}
77+
78+
fn.dprint()
79+
```
80+
81+
```{testoutput}
82+
83+
Add [id A] 2
84+
├─ ExpandDims{axis=1} [id B] 1
85+
│ └─ a [id C]
86+
└─ ExpandDims{axis=0} [id D] 0
87+
└─ b [id E]
88+
```
89+
90+
91+
## Index
92+
93+
:::{toctree}
94+
:maxdepth: 1
95+
96+
module_functions
97+
math
98+
linalg
99+
random
100+
type
101+
:::

doc/library/xtensor/linalg.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
(libdoc_xtensor_linalg)=
2+
# `xtensor.linalg` -- Linear algebra operations
3+
4+
```{eval-rst}
5+
.. automodule:: pytensor.xtensor.linalg
6+
:members:
7+
```

doc/library/xtensor/math.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
(libdoc_xtensor_math)=
2+
# `xtensor.math` Mathematical operations
3+
4+
```{eval-rst}
5+
.. automodule:: pytensor.xtensor.math
6+
:members:
7+
:exclude-members: XDot, dot
8+
```
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
(libdoc_xtensor_module_function)=
2+
# `xtensor` -- Module level operations
3+
4+
```{eval-rst}
5+
.. automodule:: pytensor.xtensor
6+
:members: broadcast, concat, dot, full_like, ones_like, zeros_like
7+
```

doc/library/xtensor/random.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
(libdoc_xtensor_random)=
2+
# `xtensor.random` Random number generator operations
3+
4+
```{eval-rst}
5+
.. automodule:: pytensor.xtensor.random
6+
:members:
7+
```

doc/library/xtensor/type.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
(libdoc_xtenor_type)=
2+
3+
# `xtensor.type` -- Types and Variables
4+
5+
## XTensorVariable creation functions
6+
7+
```{eval-rst}
8+
.. automodule:: pytensor.xtensor.type
9+
:members: xtensor, xtensor_constant, as_xtensor
10+
11+
```
12+
13+
## XTensor Type and Variable classes
14+
15+
```{eval-rst}
16+
.. automodule:: pytensor.xtensor.type
17+
:members: XTensorType, XTensorVariable, XTensorConstant
18+
```
19+
20+

0 commit comments

Comments
 (0)