gmat-sweep 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.
- gmat_sweep-0.1.0/.github/CODEOWNERS +2 -0
- gmat_sweep-0.1.0/.github/ISSUE_TEMPLATE/backend-request.yml +41 -0
- gmat_sweep-0.1.0/.github/ISSUE_TEMPLATE/bug.yml +82 -0
- gmat_sweep-0.1.0/.github/ISSUE_TEMPLATE/chore.yml +25 -0
- gmat_sweep-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- gmat_sweep-0.1.0/.github/ISSUE_TEMPLATE/feature.yml +39 -0
- gmat_sweep-0.1.0/.github/ISSUE_TEMPLATE/use-case-fit.yml +41 -0
- gmat_sweep-0.1.0/.github/workflows/ci.yml +140 -0
- gmat_sweep-0.1.0/.github/workflows/docs.yml +57 -0
- gmat_sweep-0.1.0/.github/workflows/release.yml +74 -0
- gmat_sweep-0.1.0/.gitignore +40 -0
- gmat_sweep-0.1.0/.python-version +1 -0
- gmat_sweep-0.1.0/CHANGELOG.md +77 -0
- gmat_sweep-0.1.0/CONTRIBUTING.md +91 -0
- gmat_sweep-0.1.0/LICENSE +21 -0
- gmat_sweep-0.1.0/PKG-INFO +207 -0
- gmat_sweep-0.1.0/README.md +161 -0
- gmat_sweep-0.1.0/docs/api.md +53 -0
- gmat_sweep-0.1.0/docs/examples/01_sma_scan.ipynb +414 -0
- gmat_sweep-0.1.0/docs/examples/02_epoch_arrival_grid.ipynb +474 -0
- gmat_sweep-0.1.0/docs/examples/03_killed_sweep_recovery.ipynb +483 -0
- gmat_sweep-0.1.0/docs/examples/index.md +21 -0
- gmat_sweep-0.1.0/docs/examples/leo_keplerian.script +38 -0
- gmat_sweep-0.1.0/docs/examples/leo_short.script +39 -0
- gmat_sweep-0.1.0/docs/examples/transfer_porkchop.script +47 -0
- gmat_sweep-0.1.0/docs/faq.md +63 -0
- gmat_sweep-0.1.0/docs/getting-started.md +93 -0
- gmat_sweep-0.1.0/docs/index.md +52 -0
- gmat_sweep-0.1.0/docs/manifest-schema.md +143 -0
- gmat_sweep-0.1.0/docs/parameter-spec.md +130 -0
- gmat_sweep-0.1.0/docs/supported-versions.md +64 -0
- gmat_sweep-0.1.0/mkdocs.yml +88 -0
- gmat_sweep-0.1.0/pyproject.toml +154 -0
- gmat_sweep-0.1.0/src/gmat_sweep/__init__.py +54 -0
- gmat_sweep-0.1.0/src/gmat_sweep/aggregate.py +193 -0
- gmat_sweep-0.1.0/src/gmat_sweep/api.py +131 -0
- gmat_sweep-0.1.0/src/gmat_sweep/backends/__init__.py +8 -0
- gmat_sweep-0.1.0/src/gmat_sweep/backends/base.py +78 -0
- gmat_sweep-0.1.0/src/gmat_sweep/backends/joblib.py +100 -0
- gmat_sweep-0.1.0/src/gmat_sweep/cli.py +245 -0
- gmat_sweep-0.1.0/src/gmat_sweep/distributions.py +3 -0
- gmat_sweep-0.1.0/src/gmat_sweep/errors.py +76 -0
- gmat_sweep-0.1.0/src/gmat_sweep/grids.py +88 -0
- gmat_sweep-0.1.0/src/gmat_sweep/manifest.py +238 -0
- gmat_sweep-0.1.0/src/gmat_sweep/py.typed +0 -0
- gmat_sweep-0.1.0/src/gmat_sweep/spec.py +197 -0
- gmat_sweep-0.1.0/src/gmat_sweep/sweep.py +194 -0
- gmat_sweep-0.1.0/src/gmat_sweep/worker.py +131 -0
- gmat_sweep-0.1.0/tests/__init__.py +0 -0
- gmat_sweep-0.1.0/tests/conftest.py +117 -0
- gmat_sweep-0.1.0/tests/data/golden/sma_16.parquet +0 -0
- gmat_sweep-0.1.0/tests/data/leo_basic.script +73 -0
- gmat_sweep-0.1.0/tests/test_aggregate.py +220 -0
- gmat_sweep-0.1.0/tests/test_api.py +234 -0
- gmat_sweep-0.1.0/tests/test_backends_base.py +101 -0
- gmat_sweep-0.1.0/tests/test_backends_joblib.py +144 -0
- gmat_sweep-0.1.0/tests/test_cli.py +398 -0
- gmat_sweep-0.1.0/tests/test_errors.py +53 -0
- gmat_sweep-0.1.0/tests/test_failure_modes.py +291 -0
- gmat_sweep-0.1.0/tests/test_grids.py +188 -0
- gmat_sweep-0.1.0/tests/test_import.py +16 -0
- gmat_sweep-0.1.0/tests/test_manifest.py +511 -0
- gmat_sweep-0.1.0/tests/test_manifest_replay_contract.py +227 -0
- gmat_sweep-0.1.0/tests/test_param_roundtrip.py +121 -0
- gmat_sweep-0.1.0/tests/test_reference_sweep.py +91 -0
- gmat_sweep-0.1.0/tests/test_run_roundtrip.py +102 -0
- gmat_sweep-0.1.0/tests/test_spec.py +171 -0
- gmat_sweep-0.1.0/tests/test_sweep.py +324 -0
- gmat_sweep-0.1.0/tests/test_worker.py +350 -0
- gmat_sweep-0.1.0/uv.lock +4862 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Backend request
|
|
2
|
+
description: Request a new execution backend behind the Pool abstraction.
|
|
3
|
+
title: "Backend: <name>"
|
|
4
|
+
labels: ["type:backend-request"]
|
|
5
|
+
body:
|
|
6
|
+
- type: input
|
|
7
|
+
id: backend_name
|
|
8
|
+
attributes:
|
|
9
|
+
label: Backend
|
|
10
|
+
description: Name of the execution surface (e.g. Slurm, Kubernetes, MPI, AWS Batch).
|
|
11
|
+
validations:
|
|
12
|
+
required: true
|
|
13
|
+
- type: textarea
|
|
14
|
+
id: motivation
|
|
15
|
+
attributes:
|
|
16
|
+
label: Why this backend
|
|
17
|
+
description: What can't you do with the shipped backends (LocalJoblibPool, DaskPool, RayPool)? Cluster you already have? Org policy? Scale ceiling?
|
|
18
|
+
validations:
|
|
19
|
+
required: true
|
|
20
|
+
- type: textarea
|
|
21
|
+
id: subprocess_isolation
|
|
22
|
+
attributes:
|
|
23
|
+
label: Subprocess-isolation contract
|
|
24
|
+
description: |
|
|
25
|
+
gmat-sweep guarantees one fresh Python interpreter per run (gmatpy cannot
|
|
26
|
+
be cleanly re-initialised in-process). Any backend that doesn't honour
|
|
27
|
+
this contract is rejected at import time. Briefly describe how the
|
|
28
|
+
proposed backend would satisfy it (one task per pod / one srun job per
|
|
29
|
+
run / etc.).
|
|
30
|
+
validations:
|
|
31
|
+
required: true
|
|
32
|
+
- type: textarea
|
|
33
|
+
id: prior_art
|
|
34
|
+
attributes:
|
|
35
|
+
label: Prior art
|
|
36
|
+
description: Existing Python wrappers, recipes, libraries you'd want us to consider.
|
|
37
|
+
- type: textarea
|
|
38
|
+
id: willingness
|
|
39
|
+
attributes:
|
|
40
|
+
label: Willingness to contribute
|
|
41
|
+
description: Are you offering a PR, an evaluation environment, sample workloads, or just flagging the need?
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
name: Bug
|
|
2
|
+
description: Report a defect in gmat-sweep.
|
|
3
|
+
title: "<short summary>"
|
|
4
|
+
labels: ["type:bug"]
|
|
5
|
+
body:
|
|
6
|
+
- type: textarea
|
|
7
|
+
id: what_happened
|
|
8
|
+
attributes:
|
|
9
|
+
label: What happened
|
|
10
|
+
description: Observed behavior. Include error messages and tracebacks verbatim.
|
|
11
|
+
validations:
|
|
12
|
+
required: true
|
|
13
|
+
- type: textarea
|
|
14
|
+
id: what_expected
|
|
15
|
+
attributes:
|
|
16
|
+
label: What you expected
|
|
17
|
+
validations:
|
|
18
|
+
required: true
|
|
19
|
+
- type: textarea
|
|
20
|
+
id: repro
|
|
21
|
+
attributes:
|
|
22
|
+
label: Minimal reproduction
|
|
23
|
+
description: Smallest snippet that triggers the bug. A `.script` fragment plus the Python that drives the sweep is ideal.
|
|
24
|
+
render: python
|
|
25
|
+
validations:
|
|
26
|
+
required: true
|
|
27
|
+
- type: input
|
|
28
|
+
id: gmat_sweep_version
|
|
29
|
+
attributes:
|
|
30
|
+
label: gmat-sweep version
|
|
31
|
+
placeholder: "e.g. 0.1.0, or git sha"
|
|
32
|
+
validations:
|
|
33
|
+
required: true
|
|
34
|
+
- type: input
|
|
35
|
+
id: gmat_run_version
|
|
36
|
+
attributes:
|
|
37
|
+
label: gmat-run version
|
|
38
|
+
placeholder: "e.g. 0.4.0"
|
|
39
|
+
validations:
|
|
40
|
+
required: true
|
|
41
|
+
- type: input
|
|
42
|
+
id: gmat_version
|
|
43
|
+
attributes:
|
|
44
|
+
label: GMAT version
|
|
45
|
+
placeholder: "e.g. R2026a"
|
|
46
|
+
validations:
|
|
47
|
+
required: true
|
|
48
|
+
- type: input
|
|
49
|
+
id: python_version
|
|
50
|
+
attributes:
|
|
51
|
+
label: Python version
|
|
52
|
+
placeholder: "e.g. 3.12.3"
|
|
53
|
+
validations:
|
|
54
|
+
required: true
|
|
55
|
+
- type: dropdown
|
|
56
|
+
id: backend
|
|
57
|
+
attributes:
|
|
58
|
+
label: Backend
|
|
59
|
+
options:
|
|
60
|
+
- LocalJoblibPool (default)
|
|
61
|
+
- DaskPool
|
|
62
|
+
- RayPool
|
|
63
|
+
- Other / unsure
|
|
64
|
+
validations:
|
|
65
|
+
required: true
|
|
66
|
+
- type: dropdown
|
|
67
|
+
id: os
|
|
68
|
+
attributes:
|
|
69
|
+
label: Operating system
|
|
70
|
+
options:
|
|
71
|
+
- Linux
|
|
72
|
+
- Windows
|
|
73
|
+
- macOS
|
|
74
|
+
- Other
|
|
75
|
+
validations:
|
|
76
|
+
required: true
|
|
77
|
+
- type: textarea
|
|
78
|
+
id: logs
|
|
79
|
+
attributes:
|
|
80
|
+
label: Logs
|
|
81
|
+
description: Relevant per-worker logs, the sweep manifest, captured stderr, pytest output.
|
|
82
|
+
render: shell
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: Chore
|
|
2
|
+
description: Tooling, infra, hygiene, or other non-feature work.
|
|
3
|
+
title: "<short summary>"
|
|
4
|
+
labels: ["type:chore"]
|
|
5
|
+
body:
|
|
6
|
+
- type: textarea
|
|
7
|
+
id: what
|
|
8
|
+
attributes:
|
|
9
|
+
label: What
|
|
10
|
+
description: What needs to happen.
|
|
11
|
+
validations:
|
|
12
|
+
required: true
|
|
13
|
+
- type: textarea
|
|
14
|
+
id: why
|
|
15
|
+
attributes:
|
|
16
|
+
label: Why
|
|
17
|
+
description: Why now. What's blocked or painful without it.
|
|
18
|
+
validations:
|
|
19
|
+
required: true
|
|
20
|
+
- type: textarea
|
|
21
|
+
id: done
|
|
22
|
+
attributes:
|
|
23
|
+
label: Definition of done
|
|
24
|
+
value: |
|
|
25
|
+
- [ ]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Feature
|
|
2
|
+
description: Propose a new capability or a non-trivial change.
|
|
3
|
+
title: "<short summary>"
|
|
4
|
+
labels: ["type:feature"]
|
|
5
|
+
body:
|
|
6
|
+
- type: textarea
|
|
7
|
+
id: summary
|
|
8
|
+
attributes:
|
|
9
|
+
label: Summary
|
|
10
|
+
description: One or two sentences describing what this feature is.
|
|
11
|
+
validations:
|
|
12
|
+
required: true
|
|
13
|
+
- type: textarea
|
|
14
|
+
id: motivation
|
|
15
|
+
attributes:
|
|
16
|
+
label: Motivation
|
|
17
|
+
description: Why this matters. What can't users do today that they should be able to do?
|
|
18
|
+
validations:
|
|
19
|
+
required: true
|
|
20
|
+
- type: textarea
|
|
21
|
+
id: acceptance
|
|
22
|
+
attributes:
|
|
23
|
+
label: Acceptance criteria
|
|
24
|
+
description: Concrete, testable checkboxes. An outside reviewer should be able to read these and tell whether the feature is done.
|
|
25
|
+
value: |
|
|
26
|
+
- [ ]
|
|
27
|
+
- [ ]
|
|
28
|
+
validations:
|
|
29
|
+
required: true
|
|
30
|
+
- type: textarea
|
|
31
|
+
id: out_of_scope
|
|
32
|
+
attributes:
|
|
33
|
+
label: Out of scope / non-goals
|
|
34
|
+
description: What this issue deliberately does not cover. Helps keep scope honest.
|
|
35
|
+
- type: textarea
|
|
36
|
+
id: notes
|
|
37
|
+
attributes:
|
|
38
|
+
label: Notes
|
|
39
|
+
description: Design notes, links to charter sections, references to prior art, open questions.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Use-case fit
|
|
2
|
+
description: I want to use gmat-sweep for X — does it fit?
|
|
3
|
+
title: "<short summary of the use case>"
|
|
4
|
+
labels: ["type:use-case-fit"]
|
|
5
|
+
body:
|
|
6
|
+
- type: textarea
|
|
7
|
+
id: use_case
|
|
8
|
+
attributes:
|
|
9
|
+
label: What you want to do
|
|
10
|
+
description: Describe the analysis or workflow you have in mind. A launch-window scan? A dispersion study? Something else?
|
|
11
|
+
validations:
|
|
12
|
+
required: true
|
|
13
|
+
- type: textarea
|
|
14
|
+
id: parameters
|
|
15
|
+
attributes:
|
|
16
|
+
label: What you'd be sweeping
|
|
17
|
+
description: Which fields would vary, and roughly how (grid? Monte Carlo? Latin hypercube? something else)? Order-of-magnitude run count.
|
|
18
|
+
validations:
|
|
19
|
+
required: true
|
|
20
|
+
- type: textarea
|
|
21
|
+
id: existing_workflow
|
|
22
|
+
attributes:
|
|
23
|
+
label: How you do this today
|
|
24
|
+
description: Hand-rolled multiprocessing? A spreadsheet? Paramat? Nothing yet? Helps us understand the pain point.
|
|
25
|
+
- type: textarea
|
|
26
|
+
id: blockers
|
|
27
|
+
attributes:
|
|
28
|
+
label: What's stopping you from just trying it
|
|
29
|
+
description: Missing feature, unclear docs, uncertainty about scale, license concern, scope question?
|
|
30
|
+
validations:
|
|
31
|
+
required: true
|
|
32
|
+
- type: input
|
|
33
|
+
id: gmat_version
|
|
34
|
+
attributes:
|
|
35
|
+
label: GMAT version
|
|
36
|
+
placeholder: "e.g. R2026a"
|
|
37
|
+
- type: textarea
|
|
38
|
+
id: notes
|
|
39
|
+
attributes:
|
|
40
|
+
label: Notes
|
|
41
|
+
description: Links to a paper, prior project, GMAT script — anything that helps us understand the use case.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
name: test (${{ matrix.os }}, py${{ matrix.python-version }}, ${{ matrix.gmat-version }})
|
|
15
|
+
runs-on: ${{ matrix.os }}
|
|
16
|
+
timeout-minutes: 30
|
|
17
|
+
strategy:
|
|
18
|
+
fail-fast: false
|
|
19
|
+
# 2 OSes × 3 Python minors × 2 GMAT releases = 12 cells. macOS is
|
|
20
|
+
# deferred to v0.2 per the charter; R2026a is the primary target,
|
|
21
|
+
# R2025a is the previous release we still support.
|
|
22
|
+
matrix:
|
|
23
|
+
os: [ubuntu-latest, windows-latest]
|
|
24
|
+
python-version: ['3.10', '3.11', '3.12']
|
|
25
|
+
gmat-version: [R2025a, R2026a]
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
|
|
29
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
30
|
+
uses: actions/setup-python@v5
|
|
31
|
+
with:
|
|
32
|
+
python-version: ${{ matrix.python-version }}
|
|
33
|
+
|
|
34
|
+
- name: Install uv
|
|
35
|
+
uses: astral-sh/setup-uv@v4
|
|
36
|
+
with:
|
|
37
|
+
enable-cache: true
|
|
38
|
+
cache-dependency-glob: uv.lock
|
|
39
|
+
|
|
40
|
+
- uses: astro-tools/setup-gmat@v0
|
|
41
|
+
with:
|
|
42
|
+
version: ${{ matrix.gmat-version }}
|
|
43
|
+
cache: true
|
|
44
|
+
|
|
45
|
+
- name: Install project
|
|
46
|
+
run: uv sync --all-groups
|
|
47
|
+
|
|
48
|
+
- name: Run pytest
|
|
49
|
+
# `-m "integration or not integration"` overrides the default
|
|
50
|
+
# marker filter in pyproject.toml so CI runs both unit and
|
|
51
|
+
# integration suites in a single invocation.
|
|
52
|
+
run: uv run pytest -m "integration or not integration" --cov --cov-report=term-missing
|
|
53
|
+
|
|
54
|
+
# Coverage gates run on a single matrix combination — the .coverage
|
|
55
|
+
# data is identical across runners. Overall floor matches the v0.1
|
|
56
|
+
# charter DoD; the four per-file gates match CONTRIBUTING.md.
|
|
57
|
+
|
|
58
|
+
- name: Enforce overall coverage (>=80%)
|
|
59
|
+
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' && matrix.gmat-version == 'R2026a'
|
|
60
|
+
run: uv run coverage report --fail-under=80
|
|
61
|
+
|
|
62
|
+
- name: Enforce grids.py coverage (>=95%)
|
|
63
|
+
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' && matrix.gmat-version == 'R2026a'
|
|
64
|
+
run: uv run coverage report --include='src/gmat_sweep/grids.py' --fail-under=95
|
|
65
|
+
|
|
66
|
+
- name: Enforce distributions.py coverage (>=95%)
|
|
67
|
+
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' && matrix.gmat-version == 'R2026a'
|
|
68
|
+
run: uv run coverage report --include='src/gmat_sweep/distributions.py' --fail-under=95
|
|
69
|
+
|
|
70
|
+
- name: Enforce manifest.py coverage (>=95%)
|
|
71
|
+
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' && matrix.gmat-version == 'R2026a'
|
|
72
|
+
run: uv run coverage report --include='src/gmat_sweep/manifest.py' --fail-under=95
|
|
73
|
+
|
|
74
|
+
- name: Enforce aggregate.py coverage (>=95%)
|
|
75
|
+
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' && matrix.gmat-version == 'R2026a'
|
|
76
|
+
run: uv run coverage report --include='src/gmat_sweep/aggregate.py' --fail-under=95
|
|
77
|
+
|
|
78
|
+
# Smoke-execute the example notebooks end-to-end. Catches "someone changed
|
|
79
|
+
# the public surface and forgot to refresh the committed notebook"
|
|
80
|
+
# regressions without paying the cost of nbval-style strict comparison
|
|
81
|
+
# (notebooks are committed with cleared outputs). One matrix combo is
|
|
82
|
+
# enough — the underlying surface is covered cross-platform by the rest
|
|
83
|
+
# of the test job. Notebook 03 uses signal.SIGINT against a child
|
|
84
|
+
# process, so it is Linux-only (Windows lacks the matching semantics).
|
|
85
|
+
- name: Smoke-execute example notebooks
|
|
86
|
+
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' && matrix.gmat-version == 'R2026a'
|
|
87
|
+
working-directory: docs/examples
|
|
88
|
+
run: |
|
|
89
|
+
uv sync --all-groups --extra examples
|
|
90
|
+
uv run jupyter execute 01_sma_scan.ipynb
|
|
91
|
+
uv run jupyter execute 02_epoch_arrival_grid.ipynb
|
|
92
|
+
uv run jupyter execute 03_killed_sweep_recovery.ipynb
|
|
93
|
+
|
|
94
|
+
lint:
|
|
95
|
+
name: lint
|
|
96
|
+
runs-on: ubuntu-latest
|
|
97
|
+
timeout-minutes: 5
|
|
98
|
+
steps:
|
|
99
|
+
- uses: actions/checkout@v4
|
|
100
|
+
- uses: actions/setup-python@v5
|
|
101
|
+
with:
|
|
102
|
+
python-version: '3.12'
|
|
103
|
+
- uses: astral-sh/setup-uv@v4
|
|
104
|
+
with:
|
|
105
|
+
enable-cache: true
|
|
106
|
+
cache-dependency-glob: uv.lock
|
|
107
|
+
- run: uv sync --all-groups
|
|
108
|
+
- run: uv run ruff check
|
|
109
|
+
- run: uv run ruff format --check
|
|
110
|
+
|
|
111
|
+
typecheck:
|
|
112
|
+
name: typecheck
|
|
113
|
+
runs-on: ubuntu-latest
|
|
114
|
+
timeout-minutes: 10
|
|
115
|
+
steps:
|
|
116
|
+
- uses: actions/checkout@v4
|
|
117
|
+
- uses: actions/setup-python@v5
|
|
118
|
+
with:
|
|
119
|
+
python-version: '3.12'
|
|
120
|
+
- uses: astral-sh/setup-uv@v4
|
|
121
|
+
with:
|
|
122
|
+
enable-cache: true
|
|
123
|
+
cache-dependency-glob: uv.lock
|
|
124
|
+
- run: uv sync --all-groups
|
|
125
|
+
- run: uv run mypy
|
|
126
|
+
|
|
127
|
+
# Smoke-check that `pip install gmat-sweep` (no extras, no dev group)
|
|
128
|
+
# imports cleanly. Catches an extras-only or dev-only dependency leaking
|
|
129
|
+
# into the default install path.
|
|
130
|
+
minimal-install:
|
|
131
|
+
name: minimal install smoke
|
|
132
|
+
runs-on: ubuntu-latest
|
|
133
|
+
timeout-minutes: 5
|
|
134
|
+
steps:
|
|
135
|
+
- uses: actions/checkout@v4
|
|
136
|
+
- uses: actions/setup-python@v5
|
|
137
|
+
with:
|
|
138
|
+
python-version: '3.12'
|
|
139
|
+
- run: pip install .
|
|
140
|
+
- run: python -c "import gmat_sweep"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags: ['v*']
|
|
7
|
+
pull_request:
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: docs-${{ github.workflow }}-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build:
|
|
16
|
+
name: build
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
timeout-minutes: 5
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: '3.12'
|
|
24
|
+
- uses: astral-sh/setup-uv@v4
|
|
25
|
+
with:
|
|
26
|
+
enable-cache: true
|
|
27
|
+
cache-dependency-glob: uv.lock
|
|
28
|
+
- run: uv sync --all-groups
|
|
29
|
+
- run: uv run mkdocs build --strict
|
|
30
|
+
|
|
31
|
+
deploy:
|
|
32
|
+
name: deploy
|
|
33
|
+
needs: build
|
|
34
|
+
if: |
|
|
35
|
+
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) ||
|
|
36
|
+
github.event_name == 'workflow_dispatch'
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
timeout-minutes: 5
|
|
39
|
+
permissions:
|
|
40
|
+
contents: write
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v4
|
|
43
|
+
with:
|
|
44
|
+
fetch-depth: 0
|
|
45
|
+
- uses: actions/setup-python@v5
|
|
46
|
+
with:
|
|
47
|
+
python-version: '3.12'
|
|
48
|
+
- uses: astral-sh/setup-uv@v4
|
|
49
|
+
with:
|
|
50
|
+
enable-cache: true
|
|
51
|
+
cache-dependency-glob: uv.lock
|
|
52
|
+
- run: uv sync --all-groups
|
|
53
|
+
- name: Configure git identity
|
|
54
|
+
run: |
|
|
55
|
+
git config user.name "github-actions[bot]"
|
|
56
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
57
|
+
- run: uv run mkdocs gh-deploy --force --no-history
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ['v*']
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: release-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: false
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
build:
|
|
16
|
+
name: build sdist + wheel
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
timeout-minutes: 5
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version: '3.12'
|
|
24
|
+
- uses: astral-sh/setup-uv@v4
|
|
25
|
+
with:
|
|
26
|
+
enable-cache: true
|
|
27
|
+
cache-dependency-glob: uv.lock
|
|
28
|
+
- run: uv build
|
|
29
|
+
- uses: actions/upload-artifact@v4
|
|
30
|
+
with:
|
|
31
|
+
name: dist
|
|
32
|
+
path: dist/
|
|
33
|
+
if-no-files-found: error
|
|
34
|
+
|
|
35
|
+
publish-pypi:
|
|
36
|
+
name: publish to PyPI
|
|
37
|
+
needs: build
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
timeout-minutes: 10
|
|
40
|
+
environment:
|
|
41
|
+
name: pypi
|
|
42
|
+
url: https://pypi.org/p/gmat-sweep
|
|
43
|
+
permissions:
|
|
44
|
+
id-token: write
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/download-artifact@v4
|
|
47
|
+
with:
|
|
48
|
+
name: dist
|
|
49
|
+
path: dist/
|
|
50
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
51
|
+
with:
|
|
52
|
+
skip-existing: true
|
|
53
|
+
|
|
54
|
+
github-release:
|
|
55
|
+
name: github release
|
|
56
|
+
needs: publish-pypi
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
timeout-minutes: 5
|
|
59
|
+
permissions:
|
|
60
|
+
contents: write
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/download-artifact@v4
|
|
63
|
+
with:
|
|
64
|
+
name: dist
|
|
65
|
+
path: dist/
|
|
66
|
+
- name: Create GitHub Release
|
|
67
|
+
env:
|
|
68
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
69
|
+
run: |
|
|
70
|
+
gh release create "${GITHUB_REF_NAME}" \
|
|
71
|
+
--repo "${GITHUB_REPOSITORY}" \
|
|
72
|
+
--title "${GITHUB_REF_NAME}" \
|
|
73
|
+
--generate-notes \
|
|
74
|
+
dist/*
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
*.egg
|
|
7
|
+
*.egg-info/
|
|
8
|
+
.eggs/
|
|
9
|
+
build/
|
|
10
|
+
dist/
|
|
11
|
+
wheels/
|
|
12
|
+
*.whl
|
|
13
|
+
|
|
14
|
+
# Environments
|
|
15
|
+
.venv/
|
|
16
|
+
venv/
|
|
17
|
+
env/
|
|
18
|
+
.env
|
|
19
|
+
|
|
20
|
+
# uv
|
|
21
|
+
.uv-cache/
|
|
22
|
+
|
|
23
|
+
# Tooling caches
|
|
24
|
+
.pytest_cache/
|
|
25
|
+
.mypy_cache/
|
|
26
|
+
.ruff_cache/
|
|
27
|
+
.coverage
|
|
28
|
+
.coverage.*
|
|
29
|
+
coverage.xml
|
|
30
|
+
htmlcov/
|
|
31
|
+
|
|
32
|
+
# Docs build
|
|
33
|
+
site/
|
|
34
|
+
.cache/
|
|
35
|
+
|
|
36
|
+
# IDE / editor
|
|
37
|
+
.vscode/
|
|
38
|
+
.idea/
|
|
39
|
+
*.swp
|
|
40
|
+
.DS_Store
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to gmat-sweep 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
|
+
|
|
8
|
+
## [0.1.0] — 2026-05-04
|
|
9
|
+
|
|
10
|
+
Initial public release. The MVP slice of the charter: full-factorial parameter
|
|
11
|
+
sweeps over a `gmat-run` mission, parallelised across subprocess workers,
|
|
12
|
+
aggregated into a `(run_id, time)`-MultiIndexed `pandas.DataFrame`, and backed
|
|
13
|
+
by a durable JSON Lines manifest.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `sweep(mission, grid=..., workers=...)` — public entry point for
|
|
18
|
+
full-factorial parameter grids. Materialises the cartesian product into one
|
|
19
|
+
[`RunSpec`][gmat_sweep.RunSpec] per cell, dispatches through the default
|
|
20
|
+
[`LocalJoblibPool`][gmat_sweep.Pool] backend, and returns the aggregated
|
|
21
|
+
multi-indexed DataFrame (#9).
|
|
22
|
+
- `gmat_sweep.full_factorial` and `gmat_sweep.expand_grid_to_run_specs` —
|
|
23
|
+
cartesian-product expansion with deterministic, lexicographic key ordering
|
|
24
|
+
and zero-based `run_id` assignment, the contract every manifest and future
|
|
25
|
+
resume flow depends on (#4).
|
|
26
|
+
- [`Pool`][gmat_sweep.Pool] ABC with the `subprocess_isolated` invariant
|
|
27
|
+
enforced at class-definition time, plus the default
|
|
28
|
+
[`LocalJoblibPool`][gmat_sweep.Pool] implementation backed by joblib's loky
|
|
29
|
+
executor — every run spawns a fresh Python interpreter that imports
|
|
30
|
+
`gmatpy` once, sidestepping the well-known reinit limitation (#7).
|
|
31
|
+
- Single-run worker callable that imports `gmat_run`, applies overrides via the
|
|
32
|
+
dotted-path setter, runs the mission, serialises every `ReportFile` to
|
|
33
|
+
Parquet, and converts every exception into a `RunOutcome.failed(...)` so a
|
|
34
|
+
single bad run never aborts the sweep (#6).
|
|
35
|
+
- Lazy `(run_id, time)`-MultiIndex aggregation from per-run Parquet via
|
|
36
|
+
pyarrow's dataset API. Failed and skipped runs surface as one row with the
|
|
37
|
+
`__status` column populated (#8).
|
|
38
|
+
- JSON Lines manifest with one header line and one fsync'd
|
|
39
|
+
[`ManifestEntry`][gmat_sweep.ManifestEntry] per run. `Manifest.load`
|
|
40
|
+
tolerates a single torn last line (a `Ctrl-C`'d sweep leaves a parseable
|
|
41
|
+
file). Header captures canonical script SHA-256, `gmat_sweep` /
|
|
42
|
+
`gmat_run` / GMAT install / Python / OS versions, the materialised parameter
|
|
43
|
+
spec, and the run count. Includes `find_failed` / `find_missing` lookup
|
|
44
|
+
helpers for the v0.2 resume flow (#5).
|
|
45
|
+
- Typed exception hierarchy under `gmat_sweep.errors` rooted at
|
|
46
|
+
[`GmatSweepError`][gmat_sweep.GmatSweepError] —
|
|
47
|
+
[`SweepConfigError`][gmat_sweep.SweepConfigError],
|
|
48
|
+
[`RunFailed`][gmat_sweep.RunFailed],
|
|
49
|
+
[`BackendError`][gmat_sweep.BackendError], and
|
|
50
|
+
[`ManifestCorruptError`][gmat_sweep.ManifestCorruptError] — alongside
|
|
51
|
+
JSON-serialisable [`RunSpec`][gmat_sweep.RunSpec] /
|
|
52
|
+
[`SweepSpec`][gmat_sweep.SweepSpec] / [`RunOutcome`][gmat_sweep.RunOutcome]
|
|
53
|
+
dataclasses (#3).
|
|
54
|
+
- `gmat-sweep` console script with `run` and `show` subcommands. `run`
|
|
55
|
+
accepts repeated `--grid name=lo:hi:count` linspace and `--grid
|
|
56
|
+
name=v1,v2,v3` explicit-list axes; `show` prints a one-line summary of an
|
|
57
|
+
existing manifest (#10).
|
|
58
|
+
- Validation test suite — per-run round-trip, parameter-spec round-trip
|
|
59
|
+
(float / int / datetime / vector / str enum), 16-run reference-sweep
|
|
60
|
+
regression, failure-mode coverage (invalid override, divergent solver, bad
|
|
61
|
+
script, OOM), and a manifest-replay contract test that pins the v0.1
|
|
62
|
+
manifest format ahead of v0.2's resume flow (#11).
|
|
63
|
+
- MkDocs-Material documentation site auto-deployed to GitHub Pages on tag
|
|
64
|
+
pushes, with mkdocstrings-driven API reference, parameter-spec and
|
|
65
|
+
manifest-schema reference pages, supported-versions table, and FAQ
|
|
66
|
+
(#12, #28).
|
|
67
|
+
- Three runnable example notebooks rendered into the docs site —
|
|
68
|
+
single-axis SMA scan, two-axis epoch × time-of-flight grid, and a
|
|
69
|
+
surviving-a-kill walkthrough demonstrating manifest durability (#13, #29).
|
|
70
|
+
- CI on Ubuntu + Windows × Python 3.10 / 3.11 / 3.12 × R2025a / R2026a (12
|
|
71
|
+
cells) via `astro-tools/setup-gmat`, with both unit and integration suites
|
|
72
|
+
enabled. Coverage gates: ≥ 80 % overall and ≥ 95 % each on `grids.py`,
|
|
73
|
+
`distributions.py`, `manifest.py`, and `aggregate.py` (#2).
|
|
74
|
+
- Release workflow: `uv build` → PyPI trusted publishing →
|
|
75
|
+
`gh release create --generate-notes` on `v*` tags (#2).
|
|
76
|
+
|
|
77
|
+
[0.1.0]: https://github.com/astro-tools/gmat-sweep/releases/tag/v0.1.0
|