openndm 0.2.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 (113) hide show
  1. openndm-0.2.0/.clang-format +24 -0
  2. openndm-0.2.0/.github/CODEOWNERS +25 -0
  3. openndm-0.2.0/.github/ISSUE_TEMPLATE/bug_report.yml +78 -0
  4. openndm-0.2.0/.github/ISSUE_TEMPLATE/config.yml +18 -0
  5. openndm-0.2.0/.github/ISSUE_TEMPLATE/feature_request.yml +49 -0
  6. openndm-0.2.0/.github/ISSUE_TEMPLATE/physics_discrepancy.yml +74 -0
  7. openndm-0.2.0/.github/dependabot.yml +32 -0
  8. openndm-0.2.0/.github/pull_request_template.md +43 -0
  9. openndm-0.2.0/.github/workflows/ci.yml +343 -0
  10. openndm-0.2.0/.github/workflows/release.yml +146 -0
  11. openndm-0.2.0/.gitignore +42 -0
  12. openndm-0.2.0/.pre-commit-config.yaml +49 -0
  13. openndm-0.2.0/.readthedocs.yaml +37 -0
  14. openndm-0.2.0/CHANGELOG.md +269 -0
  15. openndm-0.2.0/CITATION.cff +28 -0
  16. openndm-0.2.0/CMakeLists.txt +67 -0
  17. openndm-0.2.0/CODE_OF_CONDUCT.md +84 -0
  18. openndm-0.2.0/CONTRIBUTING.md +164 -0
  19. openndm-0.2.0/LICENSE +21 -0
  20. openndm-0.2.0/PKG-INFO +280 -0
  21. openndm-0.2.0/README.md +218 -0
  22. openndm-0.2.0/SECURITY.md +63 -0
  23. openndm-0.2.0/benchmarks/README.md +165 -0
  24. openndm-0.2.0/benchmarks/analytic/run.py +64 -0
  25. openndm-0.2.0/benchmarks/common.py +153 -0
  26. openndm-0.2.0/benchmarks/iaea2d/run.py +82 -0
  27. openndm-0.2.0/benchmarks/iaea3d/run.py +145 -0
  28. openndm-0.2.0/conda-recipe/README.md +40 -0
  29. openndm-0.2.0/conda-recipe/meta.yaml +88 -0
  30. openndm-0.2.0/docs/_static/.gitkeep +4 -0
  31. openndm-0.2.0/docs/api/exceptions.rst +7 -0
  32. openndm-0.2.0/docs/api/gc.rst +68 -0
  33. openndm-0.2.0/docs/api/geometry.rst +7 -0
  34. openndm-0.2.0/docs/api/index.rst +40 -0
  35. openndm-0.2.0/docs/api/model.rst +7 -0
  36. openndm-0.2.0/docs/api/plots.rst +7 -0
  37. openndm-0.2.0/docs/api/settings.rst +7 -0
  38. openndm-0.2.0/docs/api/statepoint.rst +7 -0
  39. openndm-0.2.0/docs/api/xslib.rst +7 -0
  40. openndm-0.2.0/docs/architecture.md +198 -0
  41. openndm-0.2.0/docs/changelog.rst +5 -0
  42. openndm-0.2.0/docs/conf.py +146 -0
  43. openndm-0.2.0/docs/contributing.rst +5 -0
  44. openndm-0.2.0/docs/examples/index.rst +41 -0
  45. openndm-0.2.0/docs/index.rst +75 -0
  46. openndm-0.2.0/docs/releasing.md +159 -0
  47. openndm-0.2.0/docs/requirements.md +438 -0
  48. openndm-0.2.0/docs/requirements.txt +19 -0
  49. openndm-0.2.0/docs/roadmap.md +130 -0
  50. openndm-0.2.0/docs/status.md +203 -0
  51. openndm-0.2.0/docs/theory.md +502 -0
  52. openndm-0.2.0/docs/user-guide.md +706 -0
  53. openndm-0.2.0/examples/01_getting_started.ipynb +379 -0
  54. openndm-0.2.0/examples/02_openmc_to_openndm.ipynb +633 -0
  55. openndm-0.2.0/examples/03_iaea_benchmark.ipynb +301 -0
  56. openndm-0.2.0/examples/04_branch_library_and_boron_search.ipynb +649 -0
  57. openndm-0.2.0/examples/README.md +66 -0
  58. openndm-0.2.0/include/openndm/cmfd.h +133 -0
  59. openndm-0.2.0/include/openndm/constants.h +46 -0
  60. openndm-0.2.0/include/openndm/error.h +72 -0
  61. openndm-0.2.0/include/openndm/geometry.h +134 -0
  62. openndm-0.2.0/include/openndm/kernel.h +89 -0
  63. openndm-0.2.0/include/openndm/matrix.h +118 -0
  64. openndm-0.2.0/include/openndm/settings.h +81 -0
  65. openndm-0.2.0/include/openndm/solver.h +85 -0
  66. openndm-0.2.0/include/openndm/xslib.h +142 -0
  67. openndm-0.2.0/pyproject.toml +82 -0
  68. openndm-0.2.0/python/openndm/__init__.py +71 -0
  69. openndm-0.2.0/python/openndm/exceptions.py +49 -0
  70. openndm-0.2.0/python/openndm/gc/__init__.py +55 -0
  71. openndm-0.2.0/python/openndm/gc/adf.py +286 -0
  72. openndm-0.2.0/python/openndm/gc/driver.py +354 -0
  73. openndm-0.2.0/python/openndm/gc/formfunc.py +78 -0
  74. openndm-0.2.0/python/openndm/gc/kinetics.py +191 -0
  75. openndm-0.2.0/python/openndm/gc/leakage.py +352 -0
  76. openndm-0.2.0/python/openndm/gc/mgxs.py +484 -0
  77. openndm-0.2.0/python/openndm/geometry.py +227 -0
  78. openndm-0.2.0/python/openndm/model.py +552 -0
  79. openndm-0.2.0/python/openndm/plots.py +85 -0
  80. openndm-0.2.0/python/openndm/settings.py +109 -0
  81. openndm-0.2.0/python/openndm/statepoint.py +195 -0
  82. openndm-0.2.0/python/openndm/xslib.py +481 -0
  83. openndm-0.2.0/scripts/check_notebook_paths.py +84 -0
  84. openndm-0.2.0/src/bindings.cpp +349 -0
  85. openndm-0.2.0/src/cmfd.cpp +560 -0
  86. openndm-0.2.0/src/geometry.cpp +205 -0
  87. openndm-0.2.0/src/kernel.cpp +24 -0
  88. openndm-0.2.0/src/kernel_common.h +147 -0
  89. openndm-0.2.0/src/kernel_fdm.cpp +34 -0
  90. openndm-0.2.0/src/kernel_nem.cpp +196 -0
  91. openndm-0.2.0/src/kernel_sanm.cpp +184 -0
  92. openndm-0.2.0/src/matrix.cpp +296 -0
  93. openndm-0.2.0/src/solver.cpp +324 -0
  94. openndm-0.2.0/src/xslib.cpp +302 -0
  95. openndm-0.2.0/tests/cpp/CMakeLists.txt +23 -0
  96. openndm-0.2.0/tests/cpp/test_geometry.cpp +110 -0
  97. openndm-0.2.0/tests/cpp/test_kernels.cpp +137 -0
  98. openndm-0.2.0/tests/cpp/test_matrix.cpp +115 -0
  99. openndm-0.2.0/tests/cpp/test_xslib.cpp +161 -0
  100. openndm-0.2.0/tests/python/conftest.py +149 -0
  101. openndm-0.2.0/tests/python/test_analytic.py +518 -0
  102. openndm-0.2.0/tests/python/test_benchmarks.py +189 -0
  103. openndm-0.2.0/tests/python/test_discontinuity_factors.py +397 -0
  104. openndm-0.2.0/tests/python/test_gc.py +671 -0
  105. openndm-0.2.0/tests/python/test_geometry.py +122 -0
  106. openndm-0.2.0/tests/python/test_openmc_integration.py +173 -0
  107. openndm-0.2.0/tests/python/test_solver.py +316 -0
  108. openndm-0.2.0/tests/python/test_statepoint.py +69 -0
  109. openndm-0.2.0/tests/python/test_verification.py +461 -0
  110. openndm-0.2.0/tests/python/test_xslib.py +259 -0
  111. openndm-0.2.0/tests/validation/README.md +146 -0
  112. openndm-0.2.0/tests/validation/c1_homogeneous.py +201 -0
  113. openndm-0.2.0/tests/validation/c2_assembly_adf.py +202 -0
@@ -0,0 +1,24 @@
1
+ # Matches OpenMC's C++ style (DP-1).
2
+ ---
3
+ Language: Cpp
4
+ BasedOnStyle: Google
5
+ AccessModifierOffset: -2
6
+ AlignAfterOpenBracket: DontAlign
7
+ AllowShortFunctionsOnASingleLine: Inline
8
+ AlwaysBreakTemplateDeclarations: Yes
9
+ BinPackArguments: true
10
+ BinPackParameters: true
11
+ BreakBeforeBraces: Custom
12
+ BraceWrapping:
13
+ AfterClass: false
14
+ AfterFunction: true
15
+ AfterNamespace: false
16
+ AfterStruct: false
17
+ BeforeElse: false
18
+ ColumnLimit: 80
19
+ PackConstructorInitializers: Never
20
+ DerivePointerAlignment: false
21
+ IndentWidth: 2
22
+ PointerAlignment: Left
23
+ SpaceAfterTemplateKeyword: false
24
+ Standard: c++17
@@ -0,0 +1,25 @@
1
+ # Every file needs review from the owner before it reaches main.
2
+ #
3
+ # The `main` ruleset requires code owner review, so this file is what makes
4
+ # "@rizkiokt must approve" a rule rather than a convention. A pull request
5
+ # touching any path below cannot merge without an approving review from the
6
+ # owner listed for that path.
7
+
8
+ * @rizkiokt
9
+
10
+ # The numerical core and the verification suite are the parts where a wrong
11
+ # merge is expensive and quiet: a physics regression passes every style check
12
+ # and most of the unit tests. They are listed separately so that GitHub names
13
+ # them explicitly in the review request rather than falling through to the
14
+ # catch-all above.
15
+ /src/ @rizkiokt
16
+ /include/ @rizkiokt
17
+ /tests/validation/ @rizkiokt
18
+ /tests/python/test_verification.py @rizkiokt
19
+ /benchmarks/ @rizkiokt
20
+
21
+ # Release and distribution machinery: a bad merge here publishes.
22
+ /.github/workflows/ @rizkiokt
23
+ /conda-recipe/ @rizkiokt
24
+ /pyproject.toml @rizkiokt
25
+ /CMakeLists.txt @rizkiokt
@@ -0,0 +1,78 @@
1
+ name: Bug report
2
+ description: Something crashes, raises, or behaves differently from what the docs say.
3
+ labels: ["bug"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: >-
8
+ For a result that runs fine but looks numerically wrong, use the
9
+ **Physics discrepancy** template instead — it asks for the reference
10
+ the answer is being judged against, which is what makes that class of
11
+ report actionable.
12
+
13
+ - type: textarea
14
+ id: what-happened
15
+ attributes:
16
+ label: What happened
17
+ description: What you expected, and what you got instead.
18
+ validations:
19
+ required: true
20
+
21
+ - type: textarea
22
+ id: reproducer
23
+ attributes:
24
+ label: Minimal reproducer
25
+ description: >-
26
+ A self-contained script that shows the problem. Inline the cross
27
+ sections rather than attaching a library where you can — a case that
28
+ runs on its own gets fixed much faster than one that needs a data file.
29
+ render: python
30
+ validations:
31
+ required: true
32
+
33
+ - type: textarea
34
+ id: traceback
35
+ attributes:
36
+ label: Traceback or error output
37
+ description: The complete output, not just the last line.
38
+ render: text
39
+
40
+ - type: textarea
41
+ id: version
42
+ attributes:
43
+ label: Version information
44
+ description: Output of the command below.
45
+ value: |
46
+ <!-- paste the output of:
47
+
48
+ python -c "import openndm, sys, platform; print(openndm.__version__, sys.version, platform.platform())"
49
+
50
+ -->
51
+ render: text
52
+ validations:
53
+ required: true
54
+
55
+ - type: dropdown
56
+ id: install-method
57
+ attributes:
58
+ label: How did you install OpenNDM?
59
+ options:
60
+ - conda / mamba (conda-forge)
61
+ - pip, from a wheel on PyPI
62
+ - pip, built from source
63
+ - built from source with CMake directly
64
+ - other
65
+ validations:
66
+ required: true
67
+
68
+ - type: checkboxes
69
+ id: checks
70
+ attributes:
71
+ label: Before submitting
72
+ options:
73
+ - label: >-
74
+ I checked [docs/status.md](https://github.com/rizkiokt/openndm/blob/main/docs/status.md)
75
+ and this is not a feature that is simply not implemented yet.
76
+ required: true
77
+ - label: I searched existing issues for this.
78
+ required: true
@@ -0,0 +1,18 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Question about using OpenNDM
4
+ url: https://github.com/rizkiokt/openndm/discussions
5
+ about: >-
6
+ How do I set up this geometry, why is my case not converging, what does
7
+ this setting do. Questions get answered faster in Discussions and stay
8
+ searchable for the next person.
9
+ - name: Theory and method background
10
+ url: https://github.com/rizkiokt/openndm/blob/main/docs/theory.md
11
+ about: >-
12
+ Every equation the solver implements, with its discretisation written
13
+ out. Check here before filing a physics discrepancy.
14
+ - name: What is and is not implemented yet
15
+ url: https://github.com/rizkiokt/openndm/blob/main/docs/status.md
16
+ about: >-
17
+ Requirement-by-requirement implementation status. A missing feature is
18
+ often listed here already.
@@ -0,0 +1,49 @@
1
+ name: Feature request
2
+ description: Propose a capability OpenNDM does not have.
3
+ labels: ["enhancement"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: >-
8
+ [docs/status.md](https://github.com/rizkiokt/openndm/blob/main/docs/status.md)
9
+ maps every requirement in the specification to what is actually built.
10
+ Much of the roadmap is already written down there — if your request is
11
+ on it, say so and the issue becomes a place to discuss priority rather
12
+ than scope.
13
+
14
+ - type: textarea
15
+ id: problem
16
+ attributes:
17
+ label: The calculation you cannot do today
18
+ description: >-
19
+ Describe the analysis you are trying to run, not the API you imagine.
20
+ The physics problem usually admits a better design than the one that
21
+ comes to mind first.
22
+ validations:
23
+ required: true
24
+
25
+ - type: textarea
26
+ id: proposal
27
+ attributes:
28
+ label: What you have in mind
29
+ description: Sketch the API or the method if you have one.
30
+
31
+ - type: input
32
+ id: requirement-id
33
+ attributes:
34
+ label: Related requirement ID
35
+ description: >-
36
+ If this corresponds to an ID in docs/requirements.md, give it. Leave
37
+ blank if this is new scope.
38
+ placeholder: "e.g. FR-GEO-7, or blank"
39
+
40
+ - type: dropdown
41
+ id: contribution
42
+ attributes:
43
+ label: Would you be willing to implement this?
44
+ options:
45
+ - "Yes, with guidance on where it belongs"
46
+ - "Yes, I know the codebase well enough"
47
+ - "No, I am requesting it"
48
+ validations:
49
+ required: true
@@ -0,0 +1,74 @@
1
+ name: Physics discrepancy
2
+ description: The solver converges, but the answer disagrees with a reference you trust.
3
+ labels: ["physics", "needs-verification"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: >-
8
+ A disagreement is only a bug once there is something to disagree with.
9
+ The fields below exist to establish that: what the reference is, how
10
+ tight it is, and whether the gap is larger than the discretisation
11
+ error you should expect at your mesh.
12
+
13
+ - type: textarea
14
+ id: discrepancy
15
+ attributes:
16
+ label: The numbers
17
+ description: >-
18
+ What OpenNDM produced, what the reference says, and the difference.
19
+ Give eigenvalues in pcm and distributions as a max and RMS relative
20
+ error.
21
+ value: |
22
+ quantity OpenNDM reference difference
23
+ k_eff
24
+ max pin power
25
+ RMS power
26
+ validations:
27
+ required: true
28
+
29
+ - type: input
30
+ id: reference-source
31
+ attributes:
32
+ label: Where the reference comes from
33
+ description: >-
34
+ A published benchmark and its revision, a Monte Carlo run (give the
35
+ code, version and reported uncertainty), or another deterministic code
36
+ and its options.
37
+ placeholder: "e.g. IAEA-2D, ANL-7416 Rev. 2, or OpenMC 0.14.0, 200 batches, sigma = 12 pcm"
38
+ validations:
39
+ required: true
40
+
41
+ - type: textarea
42
+ id: mesh
43
+ attributes:
44
+ label: Mesh and kernel
45
+ description: >-
46
+ Nodes per assembly, axial layers, and which kernel. Discretisation
47
+ error is the first thing to rule out: the nodal kernels are second
48
+ order in node size, so a one-node-per-assembly result is expected to
49
+ carry tens of pcm on some problems.
50
+ placeholder: "2x2 per assembly, 24 axial layers, SANM"
51
+ validations:
52
+ required: true
53
+
54
+ - type: checkboxes
55
+ id: convergence
56
+ attributes:
57
+ label: Convergence
58
+ options:
59
+ - label: The solve converged; it did not stop at the iteration limit.
60
+ required: true
61
+ - label: >-
62
+ I tightened the tolerances and refined the mesh, and the
63
+ disagreement did not shrink.
64
+
65
+ - type: textarea
66
+ id: reproducer
67
+ attributes:
68
+ label: Input
69
+ description: >-
70
+ The deck, or a link to it. If the case needs a cross section library,
71
+ say how to obtain it.
72
+ render: python
73
+ validations:
74
+ required: true
@@ -0,0 +1,32 @@
1
+ # Dependency updates arrive as pull requests, which is the only way into `main`
2
+ # anyway under the branch ruleset. They still need review and green CI.
3
+ version: 2
4
+ updates:
5
+ # The pinned action versions in the workflows. An unpinned or stale action is
6
+ # the most common supply chain exposure a repository this size actually has.
7
+ - package-ecosystem: github-actions
8
+ directory: /
9
+ schedule:
10
+ interval: weekly
11
+ day: monday
12
+ open-pull-requests-limit: 5
13
+ commit-message:
14
+ prefix: "ci"
15
+ labels: ["dependencies", "ci"]
16
+
17
+ # Build requirements in pyproject.toml. Runtime dependencies are deliberately
18
+ # loose ranges, so this mostly surfaces scikit-build-core and pybind11.
19
+ - package-ecosystem: pip
20
+ directory: /
21
+ schedule:
22
+ interval: monthly
23
+ open-pull-requests-limit: 5
24
+ commit-message:
25
+ prefix: "build"
26
+ labels: ["dependencies"]
27
+ # The linters are pinned on purpose: an unpinned formatter turns every
28
+ # upstream release into a red build on an unrelated commit. Dependabot may
29
+ # propose the bump, but it goes in as a deliberate reformat commit.
30
+ groups:
31
+ linters:
32
+ patterns: ["ruff", "clang-format"]
@@ -0,0 +1,43 @@
1
+ ## What this changes
2
+
3
+ <!-- One or two sentences. The diff says what; say why. -->
4
+
5
+ ## Type of change
6
+
7
+ - [ ] `fix` — corrects behaviour that was wrong
8
+ - [ ] `feat` — adds behaviour
9
+ - [ ] `docs` — documentation only
10
+ - [ ] `test` — tests only
11
+ - [ ] `refactor` — no behaviour change, no number changes
12
+ - [ ] `chore` / `ci` / `build` — tooling, packaging, workflows
13
+
14
+ ## Does this move any number?
15
+
16
+ A change to `k_eff`, a flux, a power distribution, a convergence order or a
17
+ benchmark result is a **physics change**, not a refactor, even when it looks
18
+ like a cleanup. See `CONTRIBUTING.md`.
19
+
20
+ - [ ] No verification or benchmark result moves.
21
+ - [ ] Some result moves, and the body below says which, by how much, and why
22
+ the new number is the correct one.
23
+
24
+ <!-- If a number moved, paste the before/after here:
25
+
26
+ case before after delta
27
+ analytic bare cuboid 1.00123456 1.00123401 -0.55 pcm
28
+ IAEA-2D k_eff 1.02958 1.02958 0.00 pcm
29
+ -->
30
+
31
+ ## Checklist
32
+
33
+ - [ ] `pytest tests/python` passes locally.
34
+ - [ ] `ruff check python tests benchmarks` is clean.
35
+ - [ ] `clang-format` is clean on any touched C++ (`pre-commit run -a` covers both).
36
+ - [ ] New behaviour has a test. A new kernel is registered in `ALL_KERNELS`
37
+ so the verification suite picks it up automatically.
38
+ - [ ] Public API changes are reflected in `docs/` and `CHANGELOG.md`.
39
+ - [ ] Commits follow conventional-commit subjects (`feat:`, `fix:`, ...).
40
+
41
+ ## Related issues
42
+
43
+ <!-- "Closes #123", or delete this section. -->
@@ -0,0 +1,343 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+ inputs:
9
+ cross_sections_url:
10
+ description: >-
11
+ URL of a nuclear data archive containing cross_sections.xml. Leave
12
+ empty to skip the OpenMC coupling validation, which cannot run
13
+ without one.
14
+ required: false
15
+ default: ""
16
+
17
+ concurrency:
18
+ group: ${{ github.workflow }}-${{ github.ref }}
19
+ cancel-in-progress: true
20
+
21
+ env:
22
+ FORCE_COLOR: 1
23
+
24
+ jobs:
25
+ lint:
26
+ name: lint
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - uses: actions/checkout@v7
30
+ - uses: actions/setup-python@v7
31
+ with:
32
+ python-version: "3.12"
33
+ # Both linters are pinned. An unpinned formatter turns every upstream
34
+ # release into a red build on an unrelated commit, and clang-format in
35
+ # particular reformats differently between major versions: the first CI
36
+ # run failed here purely because the runner had 18 and the tree had been
37
+ # formatted with 23.
38
+ - name: Install linters
39
+ run: pip install "ruff==0.14.14" "clang-format==18.1.8"
40
+ - name: ruff (NFR-QA-4)
41
+ run: ruff check python tests benchmarks
42
+ - name: clang-format (NFR-QA-4)
43
+ run: |
44
+ for f in $(find include src tests/cpp -name '*.h' -o -name '*.cpp'); do
45
+ clang-format --dry-run --Werror "$f" || exit 1
46
+ done
47
+
48
+ test:
49
+ name: ${{ matrix.os }} / ${{ matrix.compiler }} / py${{ matrix.python }}
50
+ runs-on: ${{ matrix.os }}
51
+ strategy:
52
+ fail-fast: false
53
+ matrix:
54
+ include:
55
+ - { os: ubuntu-latest, compiler: gcc, python: "3.10" }
56
+ - { os: ubuntu-latest, compiler: gcc, python: "3.12" }
57
+ - { os: ubuntu-latest, compiler: gcc, python: "3.13" }
58
+ - { os: ubuntu-latest, compiler: clang, python: "3.12" }
59
+ - { os: macos-latest, compiler: clang, python: "3.12" }
60
+ steps:
61
+ - uses: actions/checkout@v7
62
+
63
+ - uses: actions/setup-python@v7
64
+ with:
65
+ python-version: ${{ matrix.python }}
66
+
67
+ - name: Select the compiler
68
+ if: runner.os == 'Linux'
69
+ run: |
70
+ if [ "${{ matrix.compiler }}" = "clang" ]; then
71
+ echo "CC=clang" >> "$GITHUB_ENV"
72
+ echo "CXX=clang++" >> "$GITHUB_ENV"
73
+ else
74
+ echo "CC=gcc" >> "$GITHUB_ENV"
75
+ echo "CXX=g++" >> "$GITHUB_ENV"
76
+ fi
77
+
78
+ - name: Install OpenMP for AppleClang
79
+ if: runner.os == 'macOS'
80
+ run: |
81
+ brew install libomp
82
+ echo "OpenMP_ROOT=$(brew --prefix libomp)" >> "$GITHUB_ENV"
83
+
84
+ - name: Build and install
85
+ run: pip install -v '.[dev]'
86
+
87
+ - name: Python tests
88
+ run: pytest tests/python -v --durations=10
89
+
90
+ - name: Verify the analytic benchmark
91
+ working-directory: benchmarks/analytic
92
+ run: python run.py
93
+
94
+ cpp-tests:
95
+ name: C++ unit tests and coverage
96
+ runs-on: ubuntu-latest
97
+ steps:
98
+ - uses: actions/checkout@v7
99
+ - uses: actions/setup-python@v7
100
+ with:
101
+ python-version: "3.12"
102
+
103
+ - name: Install build tools
104
+ run: |
105
+ sudo apt-get update -qq
106
+ sudo apt-get install -y cmake ninja-build lcov
107
+
108
+ - name: Configure with coverage instrumentation
109
+ run: |
110
+ cmake -S . -B build -G Ninja \
111
+ -DOPENNDM_BUILD_TESTS=ON \
112
+ -DOPENNDM_BUILD_PYTHON=OFF \
113
+ -DCMAKE_BUILD_TYPE=Debug \
114
+ -DCMAKE_CXX_FLAGS="--coverage -O0"
115
+
116
+ - name: Build
117
+ run: cmake --build build -j
118
+
119
+ - name: Run (NFR-QA-1)
120
+ run: ctest --test-dir build --output-on-failure
121
+
122
+ - name: Collect coverage
123
+ run: |
124
+ # geninfo walks the fetched Catch2 sources, which are not all on
125
+ # disk, so "source" has to be ignored during capture; the _deps
126
+ # tree is removed from the report immediately afterwards anyway.
127
+ lcov --capture --directory build --output-file coverage.info \
128
+ --ignore-errors mismatch,gcov,source,empty
129
+ lcov --remove coverage.info '/usr/*' '*/_deps/*' '*/tests/*' \
130
+ --ignore-errors unused --output-file coverage.info
131
+ lcov --list coverage.info
132
+
133
+ - uses: actions/upload-artifact@v7
134
+ with:
135
+ name: cpp-coverage
136
+ path: coverage.info
137
+
138
+ no-openmc:
139
+ name: import without OpenMC (FR-OMC-14)
140
+ runs-on: ubuntu-latest
141
+ steps:
142
+ - uses: actions/checkout@v7
143
+ - uses: actions/setup-python@v7
144
+ with:
145
+ python-version: "3.12"
146
+ - run: pip install .
147
+ - name: The solver must work with OpenMC absent
148
+ run: |
149
+ python - <<'PY'
150
+ import importlib.util
151
+ import sys
152
+
153
+ assert importlib.util.find_spec("openmc") is None, \
154
+ "this job is meaningless with OpenMC installed"
155
+
156
+ import numpy as np
157
+ import openndm
158
+
159
+ lib = openndm.XSLibrary(1, 1)
160
+ lib.set_composition(0, D=[1.0], absorption=[0.08], nu_fission=[0.1],
161
+ kappa_fission=[0.1], chi=[1.0], scatter=[[0.0]])
162
+ lib.finalize()
163
+ # 16 nodes per side, where the SANM discretisation error on this
164
+ # problem is about 3.5 pcm. Eight nodes leaves 28 pcm, which is
165
+ # correct physics but too coarse to assert anything tight against.
166
+ geom = openndm.Geometry.from_lattice(
167
+ np.zeros((16, 16, 16), int), pitch=6.25,
168
+ boundaries=dict.fromkeys(
169
+ ["x_min", "x_max", "y_min", "y_max", "z_min", "z_max"],
170
+ "zero_flux"))
171
+ k = openndm.Model(geom, lib, openndm.Settings(verbosity=0)).solve().k_eff
172
+ reference = 0.1 / (0.08 + 3 * (np.pi / 100) ** 2)
173
+ error = 1e5 * (k - reference)
174
+ print(f"k_eff = {k:.8f}, {error:+.2f} pcm from analytic")
175
+ assert abs(error) < 10.0, error
176
+
177
+ # Importing openndm must not have dragged OpenMC in.
178
+ assert "openmc" not in sys.modules
179
+ PY
180
+
181
+ openmc-coupling:
182
+ name: OpenMC coupling validation (C-1, C-2)
183
+ runs-on: ubuntu-latest
184
+ # Manual trigger only, and it needs a nuclear data library URL supplied at
185
+ # dispatch time. There is no stable public URL to hard-code: the OpenMC
186
+ # official data libraries are served from rotating Box links and the page
187
+ # listing them is itself a 404 as of this writing. A job that silently
188
+ # skips when it cannot fetch data is worse than no job, because the
189
+ # silence is indistinguishable from success, so this one simply does not
190
+ # run unless it has been given somewhere to fetch from.
191
+ #
192
+ # C-1 and C-2 are otherwise run locally; see tests/validation/README.md.
193
+ if: >-
194
+ github.event_name == 'workflow_dispatch' &&
195
+ inputs.cross_sections_url != ''
196
+ defaults:
197
+ run:
198
+ shell: bash -el {0}
199
+ steps:
200
+ - uses: actions/checkout@v7
201
+
202
+ - uses: conda-incubator/setup-miniconda@v4
203
+ with:
204
+ miniforge-version: latest
205
+ activate-environment: openmc-env
206
+ channels: conda-forge
207
+
208
+ - name: Install OpenMC
209
+ run: conda install -y openmc pytest
210
+
211
+ - name: Install OpenNDM
212
+ run: pip install .
213
+
214
+ - name: Cache the nuclear data library
215
+ id: data
216
+ uses: actions/cache@v4
217
+ with:
218
+ path: nuclear-data
219
+ key: xs-${{ hashFiles('.github/workflows/ci.yml') }}-${{ inputs.cross_sections_url }}
220
+
221
+ - name: Fetch the nuclear data library
222
+ if: steps.data.outputs.cache-hit != 'true'
223
+ run: |
224
+ mkdir -p nuclear-data
225
+ curl -fsSL "${{ inputs.cross_sections_url }}" -o data.tar.xz
226
+ tar -xf data.tar.xz -C nuclear-data --strip-components=1
227
+
228
+ - name: Point OpenMC at it
229
+ run: |
230
+ xs=$(find nuclear-data -name cross_sections.xml | head -1)
231
+ test -n "$xs" || { echo "no cross_sections.xml in the archive"; exit 1; }
232
+ echo "OPENMC_CROSS_SECTIONS=$PWD/$xs" >> "$GITHUB_ENV"
233
+
234
+ - name: C-1 and C-2 as unit tests (FR-OMC-1, FR-OMC-7)
235
+ run: pytest tests/python/test_openmc_integration.py -v
236
+
237
+ - name: The full C-1 and C-2 cases
238
+ working-directory: tests/validation
239
+ run: |
240
+ python c1_homogeneous.py --particles 50000 --batches 120
241
+ python c2_assembly_adf.py --particles 20000 --batches 80
242
+
243
+ docs:
244
+ name: documentation
245
+ runs-on: ubuntu-latest
246
+ steps:
247
+ - uses: actions/checkout@v7
248
+ - uses: actions/setup-python@v7
249
+ with:
250
+ python-version: "3.12"
251
+
252
+ - name: Install pandoc
253
+ run: |
254
+ # nbsphinx shells out to pandoc to convert the notebooks' markdown
255
+ # cells. It is a system binary, so it is absent here even though a
256
+ # virtualenv on a developer machine usually finds one on PATH.
257
+ sudo apt-get update -qq
258
+ sudo apt-get install -y pandoc
259
+
260
+ - name: Install the docs toolchain
261
+ run: pip install -r docs/requirements.txt
262
+
263
+ # The API reference is autodoc against the installed package, so the
264
+ # extension has to be built. Without this the api/ pages render empty
265
+ # and the build still succeeds, which is the failure mode worth
266
+ # preventing.
267
+ - name: Install OpenNDM
268
+ run: pip install .
269
+
270
+ - name: Build (warnings are errors)
271
+ run: sphinx-build -b html -W --keep-going docs docs/_build/html
272
+
273
+ - name: Check for dead internal links
274
+ run: sphinx-build -b linkcheck docs docs/_build/linkcheck || true
275
+
276
+ - uses: actions/upload-artifact@v7
277
+ with:
278
+ name: docs-html
279
+ path: docs/_build/html
280
+
281
+ # ---------------------------------------------------------------------------
282
+ # The single check the `main` ruleset requires. Depending on this one job
283
+ # rather than on the matrix legs by name means the required-checks list does
284
+ # not have to be edited every time a Python version or a compiler is added
285
+ # or dropped, which is the usual way a protected branch quietly stops being
286
+ # protected.
287
+ ci:
288
+ name: ci
289
+ if: always()
290
+ needs: [lint, test, cpp-tests, no-openmc, docs]
291
+ runs-on: ubuntu-latest
292
+ steps:
293
+ - name: Fail if any required job did not succeed
294
+ run: |
295
+ results='${{ toJSON(needs) }}'
296
+ echo "$results"
297
+ echo "$results" | python3 -c "
298
+ import json, sys
299
+ needs = json.load(sys.stdin)
300
+ bad = {k: v['result'] for k, v in needs.items() if v['result'] != 'success'}
301
+ if bad:
302
+ for name, result in bad.items():
303
+ print(f'::error::{name}: {result}')
304
+ sys.exit(1)
305
+ print('all required jobs succeeded')
306
+ "
307
+
308
+ wheels:
309
+ name: build wheels
310
+ runs-on: ${{ matrix.os }}
311
+ if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
312
+ strategy:
313
+ fail-fast: false
314
+ matrix:
315
+ # macos-13 is the Intel runner, which GitHub is retiring; a job
316
+ # targeting it sat unassigned for over an hour. Both macOS
317
+ # architectures are produced from the arm64 runner instead, with
318
+ # cibuildwheel cross-compiling the x86_64 wheels. It skips the test
319
+ # step for a cross-compiled wheel, which it cannot execute.
320
+ os: [ubuntu-latest, macos-14]
321
+ steps:
322
+ - uses: actions/checkout@v7
323
+ - name: Build wheels (NFR-EXT-3)
324
+ uses: pypa/cibuildwheel@v4.2
325
+ env:
326
+ CIBW_BUILD: cp310-* cp311-* cp312-* cp313-*
327
+ # h5py, a runtime dependency, publishes no musllinux wheels and no
328
+ # manylinux2014 wheels either, only manylinux_2_28. Without both of
329
+ # these the test step builds it from source in a container with no
330
+ # HDF5 and the job dies after the OpenNDM wheel has built fine.
331
+ CIBW_SKIP: "*_i686 *-musllinux*"
332
+ CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28
333
+ CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28
334
+ # NFR-EXT-3 wants both macOS architectures.
335
+ CIBW_ARCHS_MACOS: "x86_64 arm64"
336
+ CIBW_TEST_REQUIRES: pytest
337
+ CIBW_TEST_COMMAND: >
338
+ pytest {project}/tests/python -q
339
+ -k "not benchmark"
340
+ - uses: actions/upload-artifact@v7
341
+ with:
342
+ name: wheels-${{ matrix.os }}
343
+ path: wheelhouse/*.whl