parx 0.1.0__tar.gz

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.
Files changed (82) hide show
  1. parx-0.1.0/.envrc +3 -0
  2. parx-0.1.0/.github/workflows/ci.yml +55 -0
  3. parx-0.1.0/.github/workflows/docs.yml +72 -0
  4. parx-0.1.0/.github/workflows/release.yml +56 -0
  5. parx-0.1.0/.gitignore +61 -0
  6. parx-0.1.0/CHANGELOG.md +35 -0
  7. parx-0.1.0/CITATION.cff +31 -0
  8. parx-0.1.0/CONTRIBUTING.md +217 -0
  9. parx-0.1.0/LICENSE +21 -0
  10. parx-0.1.0/OVERVIEW.md +305 -0
  11. parx-0.1.0/PKG-INFO +376 -0
  12. parx-0.1.0/README.md +325 -0
  13. parx-0.1.0/TODO.md +442 -0
  14. parx-0.1.0/docs/concepts.md +189 -0
  15. parx-0.1.0/docs/examples.md +39 -0
  16. parx-0.1.0/docs/index.md +74 -0
  17. parx-0.1.0/docs/notebooks/demo_plotly.html +308 -0
  18. parx-0.1.0/docs/notebooks/demo_plt.html +308 -0
  19. parx-0.1.0/docs/notebooks/feature_embedding.html +308 -0
  20. parx-0.1.0/docs/notebooks/index.md +31 -0
  21. parx-0.1.0/docs/reference.md +57 -0
  22. parx-0.1.0/docs/usage.md +126 -0
  23. parx-0.1.0/examples/01_identity_network.py +94 -0
  24. parx-0.1.0/examples/02_random_mlp.py +124 -0
  25. parx-0.1.0/examples/03_epochs.py +151 -0
  26. parx-0.1.0/examples/04_feature_embedding.py +77 -0
  27. parx-0.1.0/mkdocs.yml +52 -0
  28. parx-0.1.0/notebooks/demo_plotly.py +760 -0
  29. parx-0.1.0/notebooks/demo_plt.py +700 -0
  30. parx-0.1.0/notebooks/feature_embedding.py +375 -0
  31. parx-0.1.0/pyproject.toml +109 -0
  32. parx-0.1.0/setup.cfg +4 -0
  33. parx-0.1.0/src/parx/__init__.py +111 -0
  34. parx-0.1.0/src/parx/_check.py +14 -0
  35. parx-0.1.0/src/parx/_julia_init.py +106 -0
  36. parx-0.1.0/src/parx/_lp.py +59 -0
  37. parx-0.1.0/src/parx/analysis.py +441 -0
  38. parx-0.1.0/src/parx/diagnostics.py +67 -0
  39. parx-0.1.0/src/parx/io.py +120 -0
  40. parx-0.1.0/src/parx/io_partition.py +152 -0
  41. parx-0.1.0/src/parx/julia/Manifest.toml +325 -0
  42. parx-0.1.0/src/parx/julia/Project.toml +14 -0
  43. parx-0.1.0/src/parx/julia/src/LinearRegions.jl +13 -0
  44. parx-0.1.0/src/parx/julia/src/bridge.jl +24 -0
  45. parx-0.1.0/src/parx/julia/src/exact.jl +177 -0
  46. parx-0.1.0/src/parx/julia/src/lp.jl +91 -0
  47. parx-0.1.0/src/parx/julia/src/self_test.jl +29 -0
  48. parx-0.1.0/src/parx/julia/src/sparse.jl +75 -0
  49. parx-0.1.0/src/parx/juliapkg.json +13 -0
  50. parx-0.1.0/src/parx/methods/__init__.py +87 -0
  51. parx-0.1.0/src/parx/methods/exact_julia.py +50 -0
  52. parx-0.1.0/src/parx/methods/exact_julia_fast.py +56 -0
  53. parx-0.1.0/src/parx/methods/exact_python.py +230 -0
  54. parx-0.1.0/src/parx/methods/sparse_julia.py +43 -0
  55. parx-0.1.0/src/parx/methods/sparse_python.py +79 -0
  56. parx-0.1.0/src/parx/network.py +159 -0
  57. parx-0.1.0/src/parx/partition.py +210 -0
  58. parx-0.1.0/src/parx/precompile.py +65 -0
  59. parx-0.1.0/src/parx/region.py +37 -0
  60. parx-0.1.0/src/parx/verify.py +179 -0
  61. parx-0.1.0/src/parx/viz.py +2009 -0
  62. parx-0.1.0/src/parx.egg-info/PKG-INFO +376 -0
  63. parx-0.1.0/src/parx.egg-info/SOURCES.txt +80 -0
  64. parx-0.1.0/src/parx.egg-info/dependency_links.txt +1 -0
  65. parx-0.1.0/src/parx.egg-info/requires.txt +33 -0
  66. parx-0.1.0/src/parx.egg-info/scm_file_list.json +77 -0
  67. parx-0.1.0/src/parx.egg-info/scm_version.json +8 -0
  68. parx-0.1.0/src/parx.egg-info/top_level.txt +1 -0
  69. parx-0.1.0/tests/conftest.py +13 -0
  70. parx-0.1.0/tests/test_analysis.py +447 -0
  71. parx-0.1.0/tests/test_diagnostics.py +38 -0
  72. parx-0.1.0/tests/test_io.py +116 -0
  73. parx-0.1.0/tests/test_io_partition.py +175 -0
  74. parx-0.1.0/tests/test_julia_bridge.py +326 -0
  75. parx-0.1.0/tests/test_julia_init.py +102 -0
  76. parx-0.1.0/tests/test_methods.py +215 -0
  77. parx-0.1.0/tests/test_mlp.py +217 -0
  78. parx-0.1.0/tests/test_network.py +128 -0
  79. parx-0.1.0/tests/test_partition.py +147 -0
  80. parx-0.1.0/tests/test_verify.py +180 -0
  81. parx-0.1.0/tests/test_viz.py +738 -0
  82. parx-0.1.0/uv.lock +3055 -0
parx-0.1.0/.envrc ADDED
@@ -0,0 +1,3 @@
1
+ source ~/.config/direnv/functions.zsh
2
+ source ~/.config/direnv/terminal_colours.zsh
3
+ switch_terminal_bg "$TERM_COLOR_NAVY"
@@ -0,0 +1,55 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ name: Python ${{ matrix.python-version }} / Julia ${{ matrix.julia-version }}
12
+ runs-on: ubuntu-latest
13
+
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ python-version: ["3.10", "3.11", "3.12"]
18
+ julia-version: ["1.10"]
19
+
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+
23
+ - name: Set up Julia
24
+ uses: julia-actions/setup-julia@v2
25
+ with:
26
+ version: ${{ matrix.julia-version }}
27
+
28
+ - name: Install uv
29
+ uses: astral-sh/setup-uv@v4
30
+
31
+ - name: Set up Python
32
+ run: uv python install ${{ matrix.python-version }}
33
+
34
+ - name: Create virtual environment
35
+ run: uv venv --python ${{ matrix.python-version }}
36
+
37
+ - name: Install Python dependencies
38
+ run: uv pip install -e ".[dev]"
39
+
40
+ - name: Instantiate Julia environment
41
+ run: julia --project=src/parx/julia -e "using Pkg; Pkg.instantiate()"
42
+
43
+ - name: Lint with ruff
44
+ run: uv run ruff check src/ tests/
45
+
46
+ - name: Run tests
47
+ run: uv run pytest --cov=parx --cov-report=xml
48
+ env:
49
+ JULIA_NUM_THREADS: "2"
50
+
51
+ - name: Upload coverage
52
+ uses: codecov/codecov-action@v4
53
+ with:
54
+ file: coverage.xml
55
+ continue-on-error: true
@@ -0,0 +1,72 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - "docs/**"
8
+ - "mkdocs.yml"
9
+ - "src/**"
10
+ - ".github/workflows/docs.yml"
11
+ pull_request:
12
+ branches: [main]
13
+ paths:
14
+ - "docs/**"
15
+ - "mkdocs.yml"
16
+ - "src/**"
17
+ - ".github/workflows/docs.yml"
18
+ workflow_dispatch:
19
+
20
+ permissions:
21
+ contents: read
22
+ pages: write
23
+ id-token: write
24
+
25
+ concurrency:
26
+ group: pages
27
+ cancel-in-progress: false
28
+
29
+ jobs:
30
+ build:
31
+ name: Build MkDocs site
32
+ runs-on: ubuntu-latest
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+
36
+ - name: Configure GitHub Pages
37
+ if: github.event_name != 'pull_request'
38
+ uses: actions/configure-pages@v5
39
+
40
+ - name: Install uv
41
+ uses: astral-sh/setup-uv@v4
42
+
43
+ - name: Set up Python
44
+ run: uv python install 3.12
45
+
46
+ - name: Create virtual environment
47
+ run: uv venv --python 3.12
48
+
49
+ - name: Install docs dependencies
50
+ run: uv pip install "mkdocs>=1.6" "mkdocs-material>=9.5" "mkdocstrings[python]>=0.26"
51
+
52
+ - name: Build site
53
+ run: uv run mkdocs build --strict
54
+
55
+ - name: Upload Pages artifact
56
+ if: github.event_name != 'pull_request'
57
+ uses: actions/upload-pages-artifact@v4
58
+ with:
59
+ path: site
60
+
61
+ deploy:
62
+ name: Deploy to GitHub Pages
63
+ needs: build
64
+ if: github.event_name != 'pull_request'
65
+ runs-on: ubuntu-latest
66
+ environment:
67
+ name: github-pages
68
+ url: ${{ steps.deployment.outputs.page_url }}
69
+ steps:
70
+ - name: Deploy to GitHub Pages
71
+ id: deployment
72
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,56 @@
1
+ name: Release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ build:
12
+ name: Build distribution
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ with:
17
+ # setuptools-scm needs full history + tags to compute the version;
18
+ # a shallow checkout would silently produce a 0.0.devN version.
19
+ fetch-depth: 0
20
+
21
+ - name: Install uv
22
+ uses: astral-sh/setup-uv@v4
23
+
24
+ - name: Set up Python
25
+ run: uv python install 3.12
26
+
27
+ - name: Build sdist and wheel
28
+ run: uv build
29
+
30
+ - name: Upload build artifacts
31
+ uses: actions/upload-artifact@v4
32
+ with:
33
+ name: dist
34
+ path: dist/
35
+
36
+ publish:
37
+ name: Publish to PyPI
38
+ needs: build
39
+ runs-on: ubuntu-latest
40
+ environment:
41
+ name: pypi
42
+ url: https://pypi.org/project/parx/
43
+ permissions:
44
+ id-token: write # required for Trusted Publishing (OIDC) — no API token stored
45
+ steps:
46
+ - name: Download build artifacts
47
+ uses: actions/download-artifact@v4
48
+ with:
49
+ name: dist
50
+ path: dist/
51
+
52
+ - name: Install uv
53
+ uses: astral-sh/setup-uv@v4
54
+
55
+ - name: Publish to PyPI
56
+ run: uv publish --trusted-publishing always
parx-0.1.0/.gitignore ADDED
@@ -0,0 +1,61 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .eggs/
8
+ *.egg
9
+ .venv/
10
+ venv/
11
+ .env
12
+
13
+ # uv
14
+ .uv/
15
+ uv.lock # commit this if you want locked Python deps, otherwise ignore
16
+
17
+ # Pytest / coverage
18
+ .pytest_cache/
19
+ .coverage
20
+ htmlcov/
21
+
22
+ # Ruff / mypy
23
+ .ruff_cache/
24
+ .mypy_cache/
25
+
26
+ # Julia
27
+ # Do NOT ignore Manifest.toml — it should be committed for reproducibility
28
+ *.jl.cov
29
+ *.jl.mem
30
+ /depot/
31
+
32
+ # Local packaging/release working notes — not meant for the public repo
33
+ /road2publish.md
34
+
35
+ # Claude Code
36
+ .claude/
37
+ CLAUDE.md
38
+ memory/
39
+
40
+ # Examples output
41
+ examples/output/
42
+
43
+ # Editors
44
+ .vscode/
45
+ .idea/
46
+ *.swp
47
+ *.swo
48
+ .DS_Store
49
+
50
+ # Jupyter (if you add notebooks later)
51
+ .ipynb_checkpoints/
52
+ *.ipynb
53
+
54
+ # marimo
55
+ __marimo__/
56
+ .marimo_agent_state.json
57
+ layouts/
58
+ project_overview.py
59
+
60
+ # MkDocs
61
+ site/
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
7
+ once it reaches 1.0.
8
+
9
+ ## [Unreleased]
10
+
11
+ ### Fixed
12
+ - `pyproject.toml` referenced a nonexistent `LICENSE` file while the tracked
13
+ `LICENCE` file was empty; replaced with a real MIT `LICENSE` and the modern
14
+ SPDX `license`/`license-files` metadata form.
15
+ - The built sdist/wheel silently omitted all Julia source files
16
+ (`src/parx/julia/src/*.jl`) because `package-data`'s `julia/*` glob does not
17
+ recurse into the nested `julia/src/` directory. Anyone installing from a
18
+ built artifact (rather than an editable/source checkout) would have hit a
19
+ hard failure on first import. Fixed by adding `julia/src/*` to
20
+ `package-data`.
21
+ - README referenced a `parx[analysis]` extra that did not exist in
22
+ `pyproject.toml`; added it (`scikit-learn`, used by `plot_partition_pca`).
23
+ - Stale/placeholder content in `CONTRIBUTING.md` (clone URL, test file
24
+ listing) and a broken `LICENCE` link in `README.md`.
25
+
26
+ ### Added
27
+ - `LICENSE` (MIT).
28
+ - `CITATION.cff` for GitHub's citation button and future Zenodo archival.
29
+ - CI/docs/license/Python-version badges in `README.md`.
30
+ - `road2publish.md`, tracking the path to a PyPI release and Zenodo DOI.
31
+
32
+ ### Changed
33
+ - Untracked marimo/layout tool artifacts (`.marimo_agent_state.json`,
34
+ `__marimo__/session/*.json`, `layouts/*.json`, `project_overview.py`) that
35
+ don't belong in the package; they remain on disk but are now gitignored.
@@ -0,0 +1,31 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite it as below."
3
+ title: "parx: Polyhedral Affine Region eXplorer"
4
+ abstract: >-
5
+ Exact enumeration and analysis of the linear (polyhedral activation)
6
+ regions of ReLU neural networks. A Python API layered over a Julia
7
+ backend for the combinatorially intensive region-finding algorithms.
8
+ type: software
9
+ version: 0.1.0
10
+ date-released: "2026-07-03"
11
+ license: MIT
12
+ repository-code: "https://github.com/Johanmkr/parx"
13
+ url: "https://github.com/Johanmkr/parx"
14
+ authors:
15
+ - family-names: Mylius-Kroken
16
+ given-names: Johan
17
+ email: johan.m.kroken@gmail.com
18
+ orcid: https://orcid.org/0009-0005-8580-372X
19
+ keywords:
20
+ - neural networks
21
+ - linear regions
22
+ - polyhedral
23
+ - ReLU
24
+ - piecewise linear
25
+ - affine
26
+ - interpretability
27
+ - geometry
28
+ # identifiers:
29
+ # - type: doi
30
+ # value: 10.5281/zenodo.XXXXXXX
31
+ # description: Zenodo archive of this repository (added once a DOI exists — see road2publish.md Phase 4)
@@ -0,0 +1,217 @@
1
+ # Contributing to parx
2
+
3
+ ## Prerequisites
4
+
5
+ - Python ≥ 3.10
6
+ - Julia ≥ 1.10 (install via [juliaup](https://github.com/JuliaLang/juliaup))
7
+ - [uv](https://github.com/astral-sh/uv) (recommended) or pip
8
+
9
+ ---
10
+
11
+ ## Setting Up the Development Environment
12
+
13
+ ### 1. Clone the repository
14
+
15
+ ```bash
16
+ git clone https://github.com/Johanmkr/parx
17
+ cd parx
18
+ ```
19
+
20
+ ### 2. Create a virtual environment and install dependencies
21
+
22
+ **With uv (recommended):**
23
+ ```bash
24
+ uv venv # creates .venv/
25
+ source .venv/bin/activate # Windows: .venv\Scripts\activate
26
+ uv pip install -e ".[dev]"
27
+ ```
28
+
29
+ **With pip:**
30
+ ```bash
31
+ python -m venv .venv
32
+ source .venv/bin/activate
33
+ pip install -e ".[dev]"
34
+ ```
35
+
36
+ ### 3. Verify the setup
37
+
38
+ ```bash
39
+ pytest
40
+ ```
41
+
42
+ All tests should pass.
43
+
44
+ ---
45
+
46
+ ## Project Structure
47
+
48
+ ```
49
+ parx/
50
+ ├── pyproject.toml # build config, dependencies, tool config
51
+ ├── README.md
52
+ ├── CONTRIBUTING.md
53
+ ├── LICENSE
54
+ ├── .github/
55
+ │ └── workflows/
56
+ │ ├── ci.yml # GitHub Actions CI
57
+ │ └── docs.yml # builds & deploys the docs site to GitHub Pages
58
+ ├── mkdocs.yml # docs site config
59
+ ├── docs/ # docs site content (see "Docs site" below)
60
+ ├── tests/
61
+ │ ├── conftest.py
62
+ │ ├── test_analysis.py
63
+ │ ├── test_diagnostics.py
64
+ │ ├── test_io.py
65
+ │ ├── test_io_partition.py
66
+ │ ├── test_julia_bridge.py
67
+ │ ├── test_methods.py
68
+ │ ├── test_mlp.py
69
+ │ ├── test_network.py
70
+ │ ├── test_partition.py
71
+ │ ├── test_verify.py
72
+ │ └── test_viz.py
73
+ └── src/
74
+ └── parx/
75
+ ├── __init__.py # public API surface
76
+ ├── _check.py # startup checks (Julia on PATH, etc.)
77
+ ├── _julia_init.py # Julia runtime initialization
78
+ ├── _lp.py # Chebyshev center LP helper
79
+ ├── network.py # network loading (.pth, .h5)
80
+ ├── region.py # Region dataclass
81
+ ├── partition.py # Partition object + halfspaces/route/filter
82
+ ├── methods/ # region-finding backends
83
+ │ ├── __init__.py
84
+ │ ├── sparse_julia.py
85
+ │ ├── exact_julia.py
86
+ │ ├── exact_julia_fast.py
87
+ │ ├── sparse_python.py
88
+ │ └── exact_python.py
89
+ ├── io.py # iter_state_dicts helper
90
+ ├── verify.py # overlap/coverage checks
91
+ ├── viz.py # Plotly (default) / matplotlib visualizations
92
+ ├── juliapkg.json # Julia runtime dependencies (for juliacall/juliapkg)
93
+ └── julia/
94
+ ├── LinearRegions.jl
95
+ ├── bridge.jl
96
+ ├── sparse.jl
97
+ ├── exact.jl
98
+ ├── lp.jl
99
+ ├── Project.toml # standalone Julia environment (for direct Julia testing)
100
+ └── Manifest.toml # locked deps for standalone environment
101
+ ```
102
+
103
+ ---
104
+
105
+ ## Development Workflow
106
+
107
+ ### Running tests
108
+ ```bash
109
+ pytest # run all tests
110
+ pytest tests/test_methods.py # run a specific file
111
+ pytest -x # stop on first failure
112
+ pytest --cov=parx # with coverage
113
+ ```
114
+
115
+ ### Linting and formatting
116
+ ```bash
117
+ ruff check src/ tests/ # lint
118
+ ruff format src/ tests/ # format
119
+ ```
120
+
121
+ ### Testing the Julia code independently
122
+
123
+ You can test Julia code directly without going through Python:
124
+ ```bash
125
+ cd src/parx/julia
126
+ julia --project=. -e "using LinearRegions; LinearRegions.run_tests()"
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Docs site
132
+
133
+ The docs site (published at [johanmkr.github.io/parx](https://johanmkr.github.io/parx/)) is built with MkDocs from `docs/` and deployed by `.github/workflows/docs.yml` whenever `docs/` or `mkdocs.yml` changes on `main`.
134
+
135
+ ```bash
136
+ uv pip install -e ".[docs]"
137
+ mkdocs serve # live preview at http://127.0.0.1:8000
138
+ mkdocs build --strict # what CI runs; fails on broken nav/links
139
+ ```
140
+
141
+ The three demo notebooks are embedded on the Notebooks page as pre-exported static HTML (`docs/notebooks/demo_plt.html`, `docs/notebooks/demo_plotly.html`, `docs/notebooks/feature_embedding.html`) — the docs build itself has no Julia/PyTorch dependency, so this export step is manual. **After editing any of `notebooks/demo_plt.py`, `notebooks/demo_plotly.py`, or `notebooks/feature_embedding.py`, re-export before committing:**
142
+
143
+ ```bash
144
+ marimo export html notebooks/demo_plt.py -o docs/notebooks/demo_plt.html
145
+ marimo export html notebooks/demo_plotly.py -o docs/notebooks/demo_plotly.html
146
+ marimo export html notebooks/feature_embedding.py -o docs/notebooks/feature_embedding.html
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Adding Julia Dependencies
152
+
153
+ parx uses two separate Julia environments:
154
+
155
+ - **Runtime environment** — managed by juliacall/juliapkg, stored in `.venv/julia_env`.
156
+ Declare new packages in `src/parx/juliapkg.json`. juliacall resolves and installs them
157
+ automatically on the next `import parx`. No manual `Pkg.add` needed.
158
+
159
+ - **Standalone testing environment** — `src/parx/julia/Project.toml`. Used only for
160
+ running Julia code directly (see "Testing the Julia code independently" above).
161
+ Add packages here with:
162
+
163
+ ```bash
164
+ cd src/parx/julia
165
+ julia --project=.
166
+ ```
167
+ ```julia
168
+ using Pkg
169
+ Pkg.add("SomePackage") # updates Project.toml and Manifest.toml
170
+ ```
171
+
172
+ Commit both `Project.toml` and `Manifest.toml` when changing the standalone environment.
173
+
174
+ For packages needed at runtime, edit `src/parx/juliapkg.json` and commit that file.
175
+
176
+ ---
177
+
178
+ ## Environment Variables
179
+
180
+ | Variable | Default | Description |
181
+ |---|---|---|
182
+ | `JULIA_NUM_THREADS` | `"auto"` | Number of Julia threads |
183
+ | `PYTHON_JULIACALL_HANDLE_SIGNALS` | `"yes"` | Makes the harmless segfault at exit (when Julia threads are active) happen cleanly after results are already reported, instead of during teardown. It does not prevent the segfault itself — if you see one, check the run's actual output/exit status first; a `"N passed"` line means the run succeeded despite it. |
184
+ | `PYTHON_JULIAPKG_EXE` | auto-detected on juliaup (see below) | Force juliacall to use a specific Julia binary instead of auto-resolving one. |
185
+
186
+ ---
187
+
188
+ ## Troubleshooting
189
+
190
+ ### `ERROR: could not load library ".../juliaup/bin/../lib/julia/sys.so": ... No such file or directory`
191
+
192
+ This used to happen on the **first** call that touches Julia (`ensure_julia()`, any `*_julia`/`exact_julia*` method, or the `julia_session` pytest fixture) on a machine where Julia was installed via [juliaup](https://github.com/JuliaLang/juliaup) — i.e. exactly the install method this repo recommends. **`parx` now works around it automatically** (`_julia_init.py::_resolve_juliaup_shim`) — read on for what it does and what to do if you still hit this.
193
+
194
+ **Cause:** `julia` on `PATH` is juliaup's launcher shim (`~/.juliaup/bin/julia`), not a real Julia install — juliaup keeps the actual per-version binaries elsewhere (`~/.julia/juliaup/julia-<version>+.../`). `juliapkg` tries to auto-upgrade to the newest Julia release on every resolve; when that opportunistic install fails (network hiccup, a not-yet-fully-available release, etc.) it silently falls back to the raw shim path instead of one of your already-installed, perfectly good Julia versions, and juliacall then derives the system-image path relative to the shim's directory — which never has a `lib/julia/sys.so` next to it. This is a `juliapkg` bug, not a `parx` one; it's invisible in CI because CI installs Julia directly (`julia-actions/setup-julia@v2`), never through juliaup.
195
+
196
+ **The automatic fix:** before `juliacall` is ever imported, `ensure_julia()` checks whether `julia` on `PATH` resolves to a juliaup launcher shim (`os.path.realpath` ends in `julialauncher`). If so, it reads `~/.julia/juliaup/juliaup.json` directly to find the real binary behind juliaup's *default* channel, and sets `PYTHON_JULIAPKG_EXE` to that real path (via `os.environ.setdefault`, so it never overrides an explicit value you've already set) — juliapkg then uses that binary directly instead of falling back to the broken shim path. Confirmed end-to-end: a from-scratch clone + `uv venv` + `pip install -e ".[dev]"` + `pytest`, with **zero manual env vars**, now passes cleanly (`219 passed`).
197
+
198
+ **If it still happens anyway** (e.g. juliaup's on-disk metadata format has changed since this was written, or you're using something other than juliaup — a container image with a hand-rolled Julia install, `asdf`, etc.), the detection silently no-ops and you're back to the original failure. Fall back to pointing juliacall at a known-good binary yourself:
199
+
200
+ ```bash
201
+ juliaup list # see what's installed, e.g. 1.10.11+0.x64.linux.gnu
202
+ export PYTHON_JULIAPKG_EXE="$HOME/.julia/juliaup/julia-1.10.11+0.x64.linux.gnu/bin/julia"
203
+ ```
204
+
205
+ (matching the Julia 1.10 that CI and `src/parx/julia/Manifest.toml` are pinned to is the safest bet — a newer version may work but isn't what's tested). Then delete any stale resolution and retry: `rm -rf .venv/julia_env`. If you had to do this, please open an issue — it means the auto-detection missed a case and should be taught about it.
206
+
207
+ If you'd rather not hardcode a version path, `export PYTHON_JULIAPKG_OFFLINE=yes` also avoids the buggy auto-upgrade path (it makes juliapkg reuse the newest **already-installed** juliaup version instead of trying to fetch a new one) — simpler, but it may still land you on a newer, less-tested Julia than pinning to 1.10 would.
208
+
209
+ ---
210
+
211
+ ## Pull Request Checklist
212
+
213
+ - [ ] Tests pass (`pytest`)
214
+ - [ ] Code is formatted (`ruff format`)
215
+ - [ ] No lint errors (`ruff check`)
216
+ - [ ] New functionality has tests
217
+ - [ ] `Manifest.toml` is committed if Julia deps changed
parx-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Johan Mylius-Kroken
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.