kinepet 0.4.2__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 (89) hide show
  1. kinepet-0.4.2/.github/workflows/deploy-pages.yml +31 -0
  2. kinepet-0.4.2/.github/workflows/docs.yml +60 -0
  3. kinepet-0.4.2/.github/workflows/pages.yml +31 -0
  4. kinepet-0.4.2/.github/workflows/publish.yml +213 -0
  5. kinepet-0.4.2/.github/workflows/tests.yml +49 -0
  6. kinepet-0.4.2/.github/workflows/wheels.yml +80 -0
  7. kinepet-0.4.2/.gitignore +18 -0
  8. kinepet-0.4.2/.python-version +1 -0
  9. kinepet-0.4.2/CHANGELOG.md +80 -0
  10. kinepet-0.4.2/CITATION.cff +21 -0
  11. kinepet-0.4.2/CMakeLists.txt +108 -0
  12. kinepet-0.4.2/CONTRIBUTING.md +32 -0
  13. kinepet-0.4.2/Dockerfile.docs +19 -0
  14. kinepet-0.4.2/LICENSE +661 -0
  15. kinepet-0.4.2/PKG-INFO +186 -0
  16. kinepet-0.4.2/README.md +164 -0
  17. kinepet-0.4.2/THIRD_PARTY_NOTICES.md +79 -0
  18. kinepet-0.4.2/docs/architecture.md +167 -0
  19. kinepet-0.4.2/docs/assets/figures/reversible-2tcm.pdf +0 -0
  20. kinepet-0.4.2/docs/assets/figures/reversible-2tcm.svg +174 -0
  21. kinepet-0.4.2/docs/cli.md +97 -0
  22. kinepet-0.4.2/docs/data-format.md +54 -0
  23. kinepet-0.4.2/docs/development.md +86 -0
  24. kinepet-0.4.2/docs/getting-started.md +149 -0
  25. kinepet-0.4.2/docs/index.md +59 -0
  26. kinepet-0.4.2/docs/models.md +164 -0
  27. kinepet-0.4.2/docs/python-api/batch-fitting.md +126 -0
  28. kinepet-0.4.2/docs/python-api/helpers.md +93 -0
  29. kinepet-0.4.2/docs/python-api/index.md +52 -0
  30. kinepet-0.4.2/docs/python-api/results.md +56 -0
  31. kinepet-0.4.2/docs/python-api/single-tac.md +70 -0
  32. kinepet-0.4.2/docs/releasing.md +338 -0
  33. kinepet-0.4.2/docs/reproducibility.md +42 -0
  34. kinepet-0.4.2/kinepet/__init__.py +37 -0
  35. kinepet-0.4.2/kinepet/_validation.py +60 -0
  36. kinepet-0.4.2/kinepet/evaluation.py +91 -0
  37. kinepet-0.4.2/kinepet/fitting.py +174 -0
  38. kinepet-0.4.2/kinepet/models.py +91 -0
  39. kinepet-0.4.2/kinepet/py.typed +1 -0
  40. kinepet-0.4.2/kinepet/results.py +93 -0
  41. kinepet-0.4.2/mkdocs.yml +42 -0
  42. kinepet-0.4.2/pyproject.toml +57 -0
  43. kinepet-0.4.2/scripts/check_distributions.py +47 -0
  44. kinepet-0.4.2/scripts/check_release.py +54 -0
  45. kinepet-0.4.2/scripts/wheel_smoke.py +28 -0
  46. kinepet-0.4.2/src/aif.cpp +93 -0
  47. kinepet-0.4.2/src/aif.hpp +12 -0
  48. kinepet-0.4.2/src/frame_weights.cpp +30 -0
  49. kinepet-0.4.2/src/frame_weights.hpp +10 -0
  50. kinepet-0.4.2/src/lls/formulations.hpp +21 -0
  51. kinepet-0.4.2/src/lls/irreversible.cpp +78 -0
  52. kinepet-0.4.2/src/lls/regression.cpp +67 -0
  53. kinepet-0.4.2/src/lls/regression.hpp +34 -0
  54. kinepet-0.4.2/src/lls/reversible.cpp +97 -0
  55. kinepet-0.4.2/src/lls.cpp +45 -0
  56. kinepet-0.4.2/src/lls.hpp +25 -0
  57. kinepet-0.4.2/src/model.cpp +44 -0
  58. kinepet-0.4.2/src/model.hpp +40 -0
  59. kinepet-0.4.2/src/models/convolution.cpp +49 -0
  60. kinepet-0.4.2/src/models/convolution.hpp +9 -0
  61. kinepet-0.4.2/src/models/irreversible.cpp +46 -0
  62. kinepet-0.4.2/src/models/reversible.cpp +155 -0
  63. kinepet-0.4.2/src/models/tissue.hpp +19 -0
  64. kinepet-0.4.2/src/npy.cpp +224 -0
  65. kinepet-0.4.2/src/npy.hpp +36 -0
  66. kinepet-0.4.2/src/parameters.cpp +184 -0
  67. kinepet-0.4.2/src/parameters.hpp +59 -0
  68. kinepet-0.4.2/src/python_bindings.cpp +467 -0
  69. kinepet-0.4.2/src/small_linalg.hpp +115 -0
  70. kinepet-0.4.2/src/solver.cpp +523 -0
  71. kinepet-0.4.2/src/solver.hpp +61 -0
  72. kinepet-0.4.2/src/tac_batch.cpp +268 -0
  73. kinepet-0.4.2/src/tac_batch.hpp +65 -0
  74. kinepet-0.4.2/src/tac_fit_main.cpp +290 -0
  75. kinepet-0.4.2/src/tcm_model.cpp +57 -0
  76. kinepet-0.4.2/src/tcm_model.hpp +19 -0
  77. kinepet-0.4.2/src/validation.hpp +31 -0
  78. kinepet-0.4.2/tests/cpp/test_core.cpp +207 -0
  79. kinepet-0.4.2/tests/fixtures/warm_starts.txt +12 -0
  80. kinepet-0.4.2/tests/test_bindings.py +292 -0
  81. kinepet-0.4.2/tests/test_cli.py +93 -0
  82. kinepet-0.4.2/tests/test_models.py +129 -0
  83. kinepet-0.4.2/tests/test_physical_reference.py +42 -0
  84. kinepet-0.4.2/tests/test_release_notes.py +49 -0
  85. kinepet-0.4.2/tests/test_validation.py +88 -0
  86. kinepet-0.4.2/third_party_licenses/GCC-GPL-3.0.txt +674 -0
  87. kinepet-0.4.2/third_party_licenses/GCC-Runtime-Exception.txt +73 -0
  88. kinepet-0.4.2/third_party_licenses/LLVM-OpenMP.txt +361 -0
  89. kinepet-0.4.2/uv.lock +668 -0
@@ -0,0 +1,31 @@
1
+ # SPDX-FileCopyrightText: 2026 Christian Salomonsen
2
+ # SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ name: Deploy Documentation Artifact
5
+
6
+ on:
7
+ workflow_call:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: github-pages
14
+ cancel-in-progress: false
15
+
16
+ jobs:
17
+ deploy:
18
+ name: Deploy documentation to GitHub Pages
19
+ runs-on: ubuntu-latest
20
+ permissions:
21
+ pages: write
22
+ id-token: write
23
+ environment:
24
+ name: github-pages
25
+ url: ${{ steps.deployment.outputs.page_url }}
26
+ steps:
27
+ - name: Configure GitHub Pages
28
+ uses: actions/configure-pages@v5
29
+ - name: Deploy the validated artifact from this run
30
+ id: deployment
31
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,60 @@
1
+ # SPDX-FileCopyrightText: 2026 Christian Salomonsen
2
+ # SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ name: Documentation
5
+
6
+ on:
7
+ push:
8
+ branches: [main]
9
+ paths:
10
+ - "docs/**"
11
+ - "mkdocs.yml"
12
+ - "pyproject.toml"
13
+ - "uv.lock"
14
+ - ".github/workflows/docs.yml"
15
+ - ".github/workflows/pages.yml"
16
+ - ".github/workflows/deploy-pages.yml"
17
+ pull_request:
18
+ paths:
19
+ - "docs/**"
20
+ - "mkdocs.yml"
21
+ - "pyproject.toml"
22
+ - "uv.lock"
23
+ - ".github/workflows/docs.yml"
24
+ - ".github/workflows/pages.yml"
25
+ - ".github/workflows/deploy-pages.yml"
26
+ workflow_dispatch:
27
+ workflow_call:
28
+ inputs:
29
+ source_ref:
30
+ description: Source tag, branch, or commit; defaults to the triggering commit
31
+ type: string
32
+ default: ""
33
+ upload_pages:
34
+ description: Upload the built site for the Pages deployment workflow
35
+ type: boolean
36
+ default: false
37
+
38
+ permissions:
39
+ contents: read
40
+
41
+ jobs:
42
+ docs:
43
+ name: Build documentation
44
+ runs-on: ubuntu-latest
45
+ steps:
46
+ - uses: actions/checkout@v4
47
+ with:
48
+ ref: ${{ inputs.source_ref || github.sha }}
49
+ - uses: actions/setup-python@v5
50
+ with:
51
+ python-version: "3.13"
52
+ - run: python -m pip install uv
53
+ - run: uv sync --locked --group docs
54
+ - run: uv run --group docs mkdocs build --strict
55
+ - name: Upload GitHub Pages artifact
56
+ if: inputs.upload_pages
57
+ uses: actions/upload-pages-artifact@v4
58
+ with:
59
+ path: site
60
+ retention-days: 90
@@ -0,0 +1,31 @@
1
+ # SPDX-FileCopyrightText: 2026 Christian Salomonsen
2
+ # SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ name: Publish Documentation
5
+
6
+ on:
7
+ workflow_dispatch:
8
+ inputs:
9
+ source_ref:
10
+ description: Release tag or reviewed documentation-only branch/commit to publish
11
+ required: true
12
+ type: string
13
+
14
+ permissions:
15
+ contents: read
16
+
17
+ jobs:
18
+ build:
19
+ if: github.ref == 'refs/heads/main' && inputs.source_ref != ''
20
+ uses: ./.github/workflows/docs.yml
21
+ with:
22
+ source_ref: ${{ inputs.source_ref }}
23
+ upload_pages: true
24
+
25
+ deploy:
26
+ needs: build
27
+ permissions:
28
+ contents: read
29
+ pages: write
30
+ id-token: write
31
+ uses: ./.github/workflows/deploy-pages.yml
@@ -0,0 +1,213 @@
1
+ # SPDX-FileCopyrightText: 2026 Christian Salomonsen
2
+ # SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ name: Publish Distributions
5
+
6
+ on:
7
+ push:
8
+ tags: ["v*"]
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ concurrency:
15
+ group: release-${{ github.ref }}
16
+ cancel-in-progress: false
17
+
18
+ jobs:
19
+ release_metadata:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ - uses: actions/setup-python@v5
24
+ with:
25
+ python-version: "3.13"
26
+ - name: Check tag, version, license, and release notes
27
+ env:
28
+ RELEASE_REF: ${{ github.ref }}
29
+ RELEASE_TAG: ${{ github.ref_name }}
30
+ run: |
31
+ test "${RELEASE_REF#refs/tags/}" != "$RELEASE_REF"
32
+ python scripts/check_release.py --tag "$RELEASE_TAG" --notes-output "$RUNNER_TEMP/release-notes.md"
33
+
34
+ tests:
35
+ needs: release_metadata
36
+ uses: ./.github/workflows/tests.yml
37
+ with:
38
+ full_matrix: true
39
+
40
+ docs:
41
+ needs: release_metadata
42
+ uses: ./.github/workflows/docs.yml
43
+ with:
44
+ upload_pages: true
45
+
46
+ distributions:
47
+ needs: release_metadata
48
+ uses: ./.github/workflows/wheels.yml
49
+
50
+ prepare_artifacts:
51
+ name: Validate and save release artifacts
52
+ runs-on: ubuntu-latest
53
+ needs: [tests, docs, distributions]
54
+ outputs:
55
+ artifact_id: ${{ steps.upload.outputs.artifact-id }}
56
+ steps:
57
+ - uses: actions/checkout@v4
58
+ - uses: actions/setup-python@v5
59
+ with:
60
+ python-version: "3.13"
61
+ - uses: actions/download-artifact@v4
62
+ with:
63
+ name: sdist
64
+ path: release/dist
65
+ - uses: actions/download-artifact@v4
66
+ with:
67
+ pattern: wheels-*
68
+ path: release/dist
69
+ merge-multiple: true
70
+ - name: Check distribution licenses and contents
71
+ run: python scripts/check_distributions.py release/dist --require-license
72
+ - name: Check package metadata and README rendering
73
+ run: |
74
+ python -m pip install twine
75
+ python -m twine check --strict release/dist/*
76
+ - name: Prepare release notes
77
+ env:
78
+ RELEASE_TAG: ${{ github.ref_name }}
79
+ run: python scripts/check_release.py --tag "$RELEASE_TAG" --notes-output release/release-notes.md
80
+ - name: Record distribution checksums
81
+ working-directory: release/dist
82
+ run: sha256sum * > ../SHA256SUMS
83
+ - uses: actions/upload-artifact@v4
84
+ id: upload
85
+ with:
86
+ name: release-distributions
87
+ path: release/
88
+ if-no-files-found: error
89
+ retention-days: 90
90
+
91
+ testpypi:
92
+ name: Publish to TestPyPI
93
+ needs: prepare_artifacts
94
+ runs-on: ubuntu-latest
95
+ environment:
96
+ name: testpypi
97
+ url: https://test.pypi.org/p/kinepet
98
+ permissions:
99
+ contents: read
100
+ id-token: write
101
+ steps:
102
+ - uses: actions/download-artifact@v4
103
+ with:
104
+ artifact-ids: ${{ needs.prepare_artifacts.outputs.artifact_id }}
105
+ path: release
106
+ merge-multiple: true
107
+ - name: Verify saved artifacts
108
+ working-directory: release/dist
109
+ run: sha256sum --check ../SHA256SUMS
110
+ - uses: pypa/gh-action-pypi-publish@release/v1
111
+ with:
112
+ packages-dir: release/dist/
113
+ repository-url: https://test.pypi.org/legacy/
114
+
115
+ draft_release:
116
+ name: Prepare draft GitHub release
117
+ outputs:
118
+ release_id: ${{ steps.draft.outputs.result }}
119
+ needs: [prepare_artifacts, testpypi]
120
+ runs-on: ubuntu-latest
121
+ permissions:
122
+ contents: write
123
+ steps:
124
+ - uses: actions/download-artifact@v4
125
+ with:
126
+ artifact-ids: ${{ needs.prepare_artifacts.outputs.artifact_id }}
127
+ path: release
128
+ merge-multiple: true
129
+ - name: Create draft from the changelog
130
+ id: draft
131
+ uses: actions/github-script@v7
132
+ with:
133
+ script: |
134
+ const tag = context.ref.replace(/^refs\/tags\//, '');
135
+ // The tag lookup endpoint excludes drafts; list releases with write access.
136
+ const releases = await github.paginate(github.rest.repos.listReleases, {
137
+ ...context.repo, per_page: 100
138
+ });
139
+ const existing = releases.find(release => release.tag_name === tag);
140
+ if (existing && !existing.draft) {
141
+ throw new Error(`Release ${tag} is already published; do not republish this version.`);
142
+ }
143
+ // Preserve any notes the maintainer edited while reviewing a draft.
144
+ const release = existing || (await github.rest.repos.createRelease({
145
+ ...context.repo,
146
+ tag_name: tag,
147
+ name: `KinePET ${tag}`,
148
+ body: require('fs').readFileSync('release/release-notes.md', 'utf8'),
149
+ draft: true,
150
+ prerelease: /(?:a|b|rc)\d/.test(tag)
151
+ })).data;
152
+ await core.summary
153
+ .addHeading('Ready for release review')
154
+ .addLink('Review draft release notes', release.html_url)
155
+ .addRaw('\n\nVerify the TestPyPI installation, then approve the pypi environment deployment.\n')
156
+ .write();
157
+ return release.id;
158
+
159
+ pypi:
160
+ name: Approve and publish to PyPI
161
+ needs: [prepare_artifacts, draft_release]
162
+ runs-on: ubuntu-latest
163
+ environment:
164
+ name: pypi
165
+ url: https://pypi.org/p/kinepet
166
+ permissions:
167
+ contents: read
168
+ id-token: write
169
+ steps:
170
+ - uses: actions/download-artifact@v4
171
+ with:
172
+ artifact-ids: ${{ needs.prepare_artifacts.outputs.artifact_id }}
173
+ path: release
174
+ merge-multiple: true
175
+ - name: Verify the artifacts tested on TestPyPI
176
+ working-directory: release/dist
177
+ run: sha256sum --check ../SHA256SUMS
178
+ - uses: pypa/gh-action-pypi-publish@release/v1
179
+ with:
180
+ packages-dir: release/dist/
181
+
182
+ github_release:
183
+ name: Publish GitHub release
184
+ needs: [pypi, draft_release]
185
+ runs-on: ubuntu-latest
186
+ permissions:
187
+ contents: write
188
+ steps:
189
+ - uses: actions/github-script@v7
190
+ env:
191
+ RELEASE_ID: ${{ needs.draft_release.outputs.release_id }}
192
+ with:
193
+ script: |
194
+ const tag = context.ref.replace(/^refs\/tags\//, '');
195
+ const {data: release} = await github.rest.repos.getRelease({
196
+ ...context.repo, release_id: Number(process.env.RELEASE_ID)
197
+ });
198
+ if (release.tag_name !== tag) throw new Error("Release tag changed during review");
199
+ if (release.draft) {
200
+ await github.rest.repos.updateRelease({
201
+ ...context.repo, release_id: release.id, draft: false
202
+ });
203
+ }
204
+ await core.summary.addLink('Published release', release.html_url).write();
205
+
206
+ documentation:
207
+ name: Publish released documentation
208
+ needs: github_release
209
+ permissions:
210
+ contents: read
211
+ pages: write
212
+ id-token: write
213
+ uses: ./.github/workflows/deploy-pages.yml
@@ -0,0 +1,49 @@
1
+ # SPDX-FileCopyrightText: 2026 Christian Salomonsen
2
+ # SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ name: Tests
5
+
6
+ on:
7
+ push:
8
+ branches: [main]
9
+ pull_request:
10
+ workflow_dispatch:
11
+ inputs:
12
+ full_matrix:
13
+ description: Test every supported Python version
14
+ type: boolean
15
+ default: true
16
+ workflow_call:
17
+ inputs:
18
+ full_matrix:
19
+ type: boolean
20
+ default: false
21
+
22
+ permissions:
23
+ contents: read
24
+
25
+ jobs:
26
+ tests:
27
+ name: Python and C++ tests (${{ matrix.python-version }})
28
+ runs-on: ubuntu-latest
29
+ strategy:
30
+ fail-fast: false
31
+ matrix:
32
+ python-version: ${{ fromJSON(inputs.full_matrix && '["3.11", "3.12", "3.13"]' || '["3.13"]') }}
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+ - uses: actions/setup-python@v5
36
+ with:
37
+ python-version: ${{ matrix.python-version }}
38
+ - name: Install uv
39
+ run: python -m pip install --upgrade uv
40
+ - name: Install dependencies
41
+ run: uv sync --locked --group dev --python ${{ matrix.python-version }}
42
+ - name: Configure CMake
43
+ run: cmake -S . -B build -DPython_EXECUTABLE="$(uv run --python ${{ matrix.python-version }} python -c 'import sys; print(sys.executable)')"
44
+ - name: Build CLI and extension
45
+ run: cmake --build build --parallel
46
+ - name: Run C++ numerical tests
47
+ run: ctest --test-dir build --output-on-failure
48
+ - name: Run Python tests, including CLI parity
49
+ run: uv run --python ${{ matrix.python-version }} python -m pytest -q
@@ -0,0 +1,80 @@
1
+ # SPDX-FileCopyrightText: 2026 Christian Salomonsen
2
+ # SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ name: Build Distributions
5
+
6
+ on:
7
+ workflow_dispatch:
8
+ workflow_call:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ build_sdist:
15
+ name: Build sdist
16
+ runs-on: ubuntu-latest
17
+
18
+ steps:
19
+ - name: Check out source
20
+ uses: actions/checkout@v4
21
+
22
+ - name: Set up Python
23
+ uses: actions/setup-python@v5
24
+ with:
25
+ python-version: "3.11"
26
+
27
+ - name: Build sdist
28
+ run: |
29
+ python -m pip install --upgrade build
30
+ python -m build --sdist
31
+
32
+ - name: Check source archive
33
+ run: python scripts/check_distributions.py dist
34
+
35
+ - name: Upload sdist
36
+ uses: actions/upload-artifact@v4
37
+ with:
38
+ name: sdist
39
+ path: dist/*.tar.gz
40
+
41
+ build_wheels:
42
+ name: Build wheels (${{ matrix.os }})
43
+ runs-on: ${{ matrix.os }}
44
+ strategy:
45
+ fail-fast: false
46
+ matrix:
47
+ os:
48
+ - ubuntu-latest
49
+ - windows-latest
50
+ - macos-15
51
+
52
+ steps:
53
+ - name: Check out source
54
+ uses: actions/checkout@v4
55
+
56
+ - name: Set up Python
57
+ uses: actions/setup-python@v5
58
+ with:
59
+ python-version: "3.11"
60
+
61
+ - name: Install cibuildwheel
62
+ run: python -m pip install --upgrade cibuildwheel
63
+
64
+ - name: Build wheels
65
+ env:
66
+ CIBW_BUILD: cp311-* cp312-* cp313-*
67
+ CIBW_ARCHS_MACOS: arm64
68
+ CIBW_BUILD_FRONTEND: pip
69
+ CIBW_BUILD_VERBOSITY: 1
70
+ CIBW_TEST_COMMAND: python {project}/scripts/wheel_smoke.py
71
+ run: python -m cibuildwheel --output-dir wheelhouse
72
+
73
+ - name: Check wheel contents
74
+ run: python scripts/check_distributions.py wheelhouse
75
+
76
+ - name: Upload wheels
77
+ uses: actions/upload-artifact@v4
78
+ with:
79
+ name: wheels-${{ matrix.os }}
80
+ path: wheelhouse/*.whl
@@ -0,0 +1,18 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+ *.venv*
9
+ site/
10
+ .DS_Store
11
+
12
+ # Virtual environments
13
+ .venv
14
+ nohup.out
15
+ AGENTS.md
16
+ data/
17
+ output/
18
+ notebooks/
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,80 @@
1
+ # Changelog
2
+
3
+ ## 0.4.2 (2026-11-11)
4
+
5
+ This release updates packaging, release automation, and documentation hosting.
6
+ The fitting algorithms and public model API are unchanged from 0.4.1.
7
+
8
+ ### Changed
9
+
10
+ - Build distributions once, upload them to TestPyPI for review, then publish the
11
+ same files to official PyPI after approval through the `pypi` environment.
12
+ Saved artifact IDs and SHA-256 checksums preserve the files between uploads.
13
+ - Generate draft GitHub release notes from the matching changelog entry and
14
+ publish the release after the PyPI upload succeeds.
15
+ - Deploy the validated release documentation to GitHub Pages after successful
16
+ PyPI and GitHub release publication, retaining `https://kinepet.salodev.no/`.
17
+ Development pushes validate documentation without updating the live site;
18
+ manual publication requires an explicit source tag, branch, or commit.
19
+ - Run one Linux/Python configuration for routine pushes and pull requests.
20
+ Reserve the full supported Python test matrix and platform distribution builds
21
+ for release tags and manual runs.
22
+ - Document Trusted Publishing setup, release review and recovery, and GitHub
23
+ Pages configuration. Update citation metadata and add the Zenodo DOI badge.
24
+
25
+ ### Fixed
26
+
27
+ - Replace the retired `macos-14` runner with `macos-15` and explicitly build
28
+ Apple Silicon (`arm64`) wheels. The unavailable runner previously prevented
29
+ the distribution workflow from reaching publication.
30
+
31
+ ## 0.4.1 (2026-11-11)
32
+
33
+ ### Added
34
+
35
+ - Explicit Python `Irreversible2TCM` and `Reversible2TCM` descriptions with
36
+ `FittedVB()` or `FixedVB(value)`. Accepted by fitting, forward evaluation, and
37
+ bounds helpers alongside the existing `irr` / `rev` names.
38
+ - Direct C++ tests for reversible coefficient recovery, parameter layout,
39
+ rejected warm starts, and pre-refactor initialization baselines.
40
+ - Python model-configuration and CLI/API parity tests, including shared/per-TAC
41
+ inputs and optional AIF parameters. C++ numerical tests run in CLI CI.
42
+ - Architecture and equation-location guide in `docs/architecture.md`.
43
+
44
+ ### Changed
45
+
46
+ - Move paper-specific comparison scripts and tests, standalone simulations,
47
+ and the synthetic benchmark to the companion validation repository. Library
48
+ numerical tests, CLI parity checks, and wheel validation remain in KinePET.
49
+ - Group contributor and maintenance documentation separately from user guides.
50
+
51
+ - C++ models carry their blood-fraction setting explicitly; optional AIF settings
52
+ are grouped rather than passed as repeated boolean/value arguments.
53
+ - Model-specific LLS regressions and kinetic parameter recovery have separate,
54
+ named implementations with coefficient definitions beside the equations.
55
+ - Tissue kinetics, convolution, AIF transformations, vascular mixing, parameter
56
+ layout, bounds, and default frame weights have distinct source files.
57
+ - Internal per-curve C++ entrypoints are named `fit_tac` and
58
+ `fit_tac_with_initial_guess`, replacing the legacy voxel terminology.
59
+ - Python public exports, model descriptions, fitting, evaluation, validation, and
60
+ result containers have separate modules.
61
+ - Distribution builds omit C++ test targets; standalone CMake builds retain CTest.
62
+
63
+ The model API refactor preserves string-model calls, effective defaults, result
64
+ shapes, CLI flags and filenames, status semantics, bounds, and numerical algorithms.
65
+ With a model object, configure blood fraction on that object; combining it with
66
+ legacy `fit_vb` / `fixed_vb` options raises `ValueError`. A standalone evaluation
67
+ also rejects a numeric `vB` that conflicts with the model's `FixedVB`.
68
+
69
+ ### Fixes and distribution updates
70
+
71
+ - Reject nonfinite inputs, invalid time grids/weights, and invalid solver options
72
+ in the shared C++ path. Preserve per-TAC invalid status 5 in batch outputs;
73
+ guard objective, derivative, and linear-step overflow against false convergence.
74
+ - Fix in-place AIF transformation for negative delays and dispersion.
75
+ - Add numerical input regressions and independent compartment ODE checks.
76
+ - Resolve public development dependencies without a private index. Prepare
77
+ Trusted Publishing with release metadata/license gates and numerical wheel tests.
78
+ - Add dependency notices, contribution guidance, citation metadata, scientific
79
+ conventions, validation scope, and release instructions.
80
+ - Add AGPL-3.0-only licensing and SPDX notices to project source and build files.
@@ -0,0 +1,21 @@
1
+ cff-version: 1.2.0
2
+ message: "Please cite the version of KinePET used in your work, including its release number or commit identifier."
3
+ type: software
4
+ title: "KinePET: TAC-based fitting of dynamic PET two-tissue compartment models"
5
+ authors:
6
+ - family-names: "Salomonsen"
7
+ given-names: "Christian"
8
+ orcid: "https://orcid.org/0009-0007-4958-4544"
9
+ doi: 10.5281/zenodo.24132816
10
+ date-released: 2026-11-11
11
+ repository-code: "https://github.com/c-salomonsen/KinePET"
12
+ url: "https://kinepet.salodev.no/"
13
+ abstract: "A bounded nonlinear C++ solver with Python bindings for reversible and irreversible two-tissue compartment models, with linear least-squares initialization."
14
+ keywords:
15
+ - PET
16
+ - kinetic modeling
17
+ - compartment models
18
+ identifiers:
19
+ - type: doi
20
+ value: "10.5281/zenodo.24132816"
21
+ description: "The concept DOI for the collection containing all versions of the software."
@@ -0,0 +1,108 @@
1
+ # SPDX-FileCopyrightText: 2026 Christian Salomonsen
2
+ # SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ cmake_minimum_required(VERSION 3.20)
5
+
6
+ project(tcm_fit LANGUAGES CXX)
7
+
8
+ set(CMAKE_CXX_STANDARD 20)
9
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
10
+ set(CMAKE_CXX_EXTENSIONS OFF)
11
+
12
+ if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
13
+ set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
14
+ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
15
+ endif()
16
+
17
+ option(TCM_ENABLE_OPENMP "Enable OpenMP TAC parallelism when available" ON)
18
+ option(TCM_ENABLE_PYTHON_BINDINGS "Build the nanobind Python extension" ON)
19
+
20
+ add_library(
21
+ tcm_solver_core
22
+ src/model.cpp
23
+ src/parameters.cpp
24
+ src/aif.cpp
25
+ src/frame_weights.cpp
26
+ src/models/convolution.cpp
27
+ src/models/irreversible.cpp
28
+ src/models/reversible.cpp
29
+ src/lls.cpp
30
+ src/lls/regression.cpp
31
+ src/lls/irreversible.cpp
32
+ src/lls/reversible.cpp
33
+ src/solver.cpp
34
+ src/tac_batch.cpp
35
+ src/tcm_model.cpp
36
+ )
37
+
38
+ target_include_directories(tcm_solver_core PUBLIC src)
39
+ set_target_properties(tcm_solver_core PROPERTIES POSITION_INDEPENDENT_CODE ON)
40
+
41
+ if(MSVC)
42
+ target_compile_options(tcm_solver_core PRIVATE /W4 /O2)
43
+ else()
44
+ target_compile_options(tcm_solver_core PRIVATE -Wall -Wextra -Wpedantic -O3)
45
+ endif()
46
+
47
+ if(TCM_ENABLE_OPENMP)
48
+ find_package(OpenMP QUIET)
49
+ if(OpenMP_CXX_FOUND)
50
+ target_link_libraries(tcm_solver_core PUBLIC OpenMP::OpenMP_CXX)
51
+ target_compile_definitions(tcm_solver_core PUBLIC TCM_HAS_OPENMP=1)
52
+ endif()
53
+ endif()
54
+
55
+ add_executable(
56
+ tcm_fit_tacs
57
+ src/npy.cpp
58
+ src/tac_fit_main.cpp
59
+ )
60
+
61
+ target_link_libraries(tcm_fit_tacs PRIVATE tcm_solver_core)
62
+ target_include_directories(tcm_fit_tacs PRIVATE src)
63
+
64
+ if(MSVC)
65
+ target_compile_options(tcm_fit_tacs PRIVATE /W4 /O2)
66
+ else()
67
+ target_compile_options(tcm_fit_tacs PRIVATE -Wall -Wextra -Wpedantic -O3)
68
+ endif()
69
+
70
+ if(TCM_ENABLE_PYTHON_BINDINGS)
71
+ if (CMAKE_VERSION VERSION_LESS 3.18)
72
+ set(DEV_MODULE Development)
73
+ else()
74
+ set(DEV_MODULE Development.Module)
75
+ endif()
76
+
77
+ find_package(Python 3.11 COMPONENTS Interpreter ${DEV_MODULE} REQUIRED)
78
+
79
+ execute_process(
80
+ COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
81
+ RESULT_VARIABLE TCM_NANOBIND_STATUS
82
+ OUTPUT_VARIABLE nanobind_ROOT
83
+ OUTPUT_STRIP_TRAILING_WHITESPACE
84
+ )
85
+ if (NOT TCM_NANOBIND_STATUS EQUAL 0)
86
+ message(FATAL_ERROR "Could not locate nanobind. Install it in the active Python environment first.")
87
+ endif()
88
+
89
+ find_package(nanobind CONFIG REQUIRED)
90
+
91
+ nanobind_add_module(
92
+ _core
93
+ NB_STATIC
94
+ src/python_bindings.cpp
95
+ )
96
+
97
+ target_link_libraries(_core PRIVATE tcm_solver_core)
98
+ target_include_directories(_core PRIVATE src)
99
+
100
+ install(TARGETS _core LIBRARY DESTINATION kinepet)
101
+ endif()
102
+
103
+ include(CTest)
104
+ if(BUILD_TESTING)
105
+ add_executable(test_core tests/cpp/test_core.cpp)
106
+ target_link_libraries(test_core PRIVATE tcm_solver_core)
107
+ add_test(NAME core_numerics COMMAND test_core "${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures/warm_starts.txt")
108
+ endif()