simjecture 0.1.0__py3-none-any.whl

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 (75) hide show
  1. conjecture_solver/__init__.py +10 -0
  2. conjecture_solver/__main__.py +4 -0
  3. conjecture_solver/action_handlers.py +459 -0
  4. conjecture_solver/adapters/__init__.py +7 -0
  5. conjecture_solver/adapters/base.py +96 -0
  6. conjecture_solver/adapters/fake.py +155 -0
  7. conjecture_solver/adapters/pic.py +196 -0
  8. conjecture_solver/adapters/warpx.py +1375 -0
  9. conjecture_solver/autonomous_research.py +1071 -0
  10. conjecture_solver/benchmarks/__init__.py +6 -0
  11. conjecture_solver/benchmarks/electrostatic_pic.py +547 -0
  12. conjecture_solver/benchmarks/kinetic_sufficiency.py +318 -0
  13. conjecture_solver/builtin_skills/warpx/SKILL.md +151 -0
  14. conjecture_solver/builtin_skills/warpx/agents/openai.yaml +4 -0
  15. conjecture_solver/builtin_skills/warpx/examples/implicit_em_smoke.py +74 -0
  16. conjecture_solver/builtin_skills/warpx/examples/minimal_smoke.py +54 -0
  17. conjecture_solver/builtin_skills/warpx/examples/openpmd_field_smoke.py +87 -0
  18. conjecture_solver/builtin_skills/warpx/manifest.json +11 -0
  19. conjecture_solver/builtin_skills/warpx/references/2d-xz-commissioning.md +145 -0
  20. conjecture_solver/builtin_skills/warpx/references/cpu-launch-tuning.md +54 -0
  21. conjecture_solver/builtin_skills/warpx/references/diagnostics.md +139 -0
  22. conjecture_solver/builtin_skills/warpx/references/gpu-launch-tuning.md +79 -0
  23. conjecture_solver/builtin_skills/warpx/references/local-cuda-deployment.md +87 -0
  24. conjecture_solver/builtin_skills/warpx/references/numerical-risks.md +32 -0
  25. conjecture_solver/builtin_skills/warpx/references/picmi-interface.md +118 -0
  26. conjecture_solver/builtin_skills/warpx/references/resource-scaling.md +24 -0
  27. conjecture_solver/builtin_skills/warpx/references/time-integration.md +58 -0
  28. conjecture_solver/builtin_skills/warpx/scripts/benchmark_cpu_threads.py +207 -0
  29. conjecture_solver/builtin_skills/warpx/scripts/benchmark_gpu.py +242 -0
  30. conjecture_solver/builtin_skills/warpx/scripts/bootstrap_local_cuda.sh +82 -0
  31. conjecture_solver/builtin_skills/warpx/scripts/probe_local_cuda.py +110 -0
  32. conjecture_solver/builtin_skills/warpx/scripts/reduced_energy_budget.py +195 -0
  33. conjecture_solver/builtin_skills/warpx/scripts/run_local_cuda.sh +39 -0
  34. conjecture_solver/campaign.py +406 -0
  35. conjecture_solver/cli.py +963 -0
  36. conjecture_solver/confirmation.py +287 -0
  37. conjecture_solver/control.py +234 -0
  38. conjecture_solver/deployment.py +874 -0
  39. conjecture_solver/discovery.py +68 -0
  40. conjecture_solver/domains/__init__.py +40 -0
  41. conjecture_solver/domains/base.py +157 -0
  42. conjecture_solver/domains/kinetic_sufficiency.py +521 -0
  43. conjecture_solver/ledger.py +238 -0
  44. conjecture_solver/lifecycle.py +131 -0
  45. conjecture_solver/literature.py +494 -0
  46. conjecture_solver/llm.py +228 -0
  47. conjecture_solver/models.py +279 -0
  48. conjecture_solver/mvp_agent.py +3212 -0
  49. conjecture_solver/mvp_claims.py +1279 -0
  50. conjecture_solver/mvp_control.py +322 -0
  51. conjecture_solver/mvp_guidance.py +172 -0
  52. conjecture_solver/mvp_launch.py +1296 -0
  53. conjecture_solver/mvp_monitor.py +1487 -0
  54. conjecture_solver/mvp_skills.py +538 -0
  55. conjecture_solver/orchestration.py +578 -0
  56. conjecture_solver/outbox.py +526 -0
  57. conjecture_solver/parameters.py +394 -0
  58. conjecture_solver/proposals.py +427 -0
  59. conjecture_solver/research_tools.py +278 -0
  60. conjecture_solver/schema_export.py +246 -0
  61. conjecture_solver/search.py +1047 -0
  62. conjecture_solver/semantics.py +146 -0
  63. conjecture_solver/tui/__init__.py +7 -0
  64. conjecture_solver/tui/app.py +202 -0
  65. conjecture_solver/tui/claim_views.py +213 -0
  66. conjecture_solver/tui/screens.py +1014 -0
  67. conjecture_solver/warpx_analysis.py +184 -0
  68. conjecture_solver/warpx_campaign.py +148 -0
  69. conjecture_solver/warpx_confirmation.py +617 -0
  70. simjecture-0.1.0.dist-info/METADATA +400 -0
  71. simjecture-0.1.0.dist-info/RECORD +75 -0
  72. simjecture-0.1.0.dist-info/WHEEL +4 -0
  73. simjecture-0.1.0.dist-info/entry_points.txt +4 -0
  74. simjecture-0.1.0.dist-info/licenses/LICENSE +202 -0
  75. simjecture-0.1.0.dist-info/licenses/NOTICE +6 -0
@@ -0,0 +1,318 @@
1
+ """Matched-moment kinetic stability counterexample.
2
+
3
+ The normalization uses electron plasma frequency equal to one. Ions are an
4
+ immobile neutralizing background. For a Gaussian component with weight a,
5
+ drift u, and standard deviation sigma, its electrostatic susceptibility is
6
+
7
+ a / (k sigma)^2 * [1 + zeta Z(zeta)]
8
+
9
+ where zeta = (omega - k u) / (sqrt(2) k sigma) and Z is the plasma dispersion
10
+ function. The least-damped or fastest-growing root is the primary observable.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import warnings
16
+
17
+ import numpy as np
18
+ from pydantic import Field, model_validator
19
+ from scipy.optimize import root
20
+ from scipy.special import wofz
21
+
22
+ from ..models import (
23
+ DomainSpec,
24
+ EvidenceContract,
25
+ HypothesisNode,
26
+ HypothesisOrigin,
27
+ MatchedPairFormalPredicate,
28
+ ObservableSpec,
29
+ PropositionClass,
30
+ StrictModel,
31
+ )
32
+ from ..semantics import MatchedObservation, SufficiencyWitness, evaluate_predictive_sufficiency
33
+
34
+
35
+ class GaussianComponent(StrictModel):
36
+ weight: float = Field(gt=0, le=1)
37
+ drift: float
38
+ sigma: float = Field(gt=0)
39
+
40
+
41
+ class GaussianMixture(StrictModel):
42
+ components: tuple[GaussianComponent, ...] = Field(min_length=1)
43
+
44
+ @model_validator(mode="after")
45
+ def normalized_weights(self) -> GaussianMixture:
46
+ if abs(sum(component.weight for component in self.components) - 1.0) > 1e-12:
47
+ raise ValueError("component weights must sum to one")
48
+ return self
49
+
50
+ def density(self) -> float:
51
+ return sum(component.weight for component in self.components)
52
+
53
+ def mean(self) -> float:
54
+ return sum(component.weight * component.drift for component in self.components)
55
+
56
+ def second_moment(self) -> float:
57
+ return sum(
58
+ component.weight * (component.sigma**2 + component.drift**2)
59
+ for component in self.components
60
+ )
61
+
62
+ def variance(self) -> float:
63
+ return self.second_moment() - self.mean() ** 2
64
+
65
+
66
+ class DistributionMoments(StrictModel):
67
+ density: float
68
+ mean_velocity: float
69
+ variance: float = Field(ge=0)
70
+
71
+
72
+ class LinearMode(StrictModel):
73
+ frequency: float
74
+ growth_rate: float
75
+ dielectric_residual: float = Field(ge=0)
76
+
77
+
78
+ class KineticCaseResult(StrictModel):
79
+ name: str
80
+ distribution: GaussianMixture
81
+ moments: DistributionMoments
82
+ mode: LinearMode
83
+ classification: str
84
+
85
+
86
+ class KineticSufficiencyResult(StrictModel):
87
+ schema_version: str = "0.1.0"
88
+ experiment_id: str = "planted_kinetic_sufficiency_v1"
89
+ hypothesis: HypothesisNode
90
+ observable: ObservableSpec
91
+ wavenumber: float = Field(gt=0)
92
+ maxwellian: KineticCaseResult
93
+ two_stream: KineticCaseResult
94
+ moments_match: bool
95
+ witness: SufficiencyWitness
96
+
97
+
98
+ def plasma_dispersion(zeta: complex) -> complex:
99
+ return 1j * np.sqrt(np.pi) * wofz(zeta)
100
+
101
+
102
+ def dielectric(
103
+ omega: complex,
104
+ wavenumber: float,
105
+ distribution: GaussianMixture,
106
+ ) -> complex:
107
+ value = 1.0 + 0.0j
108
+ with np.errstate(all="ignore"):
109
+ for component in distribution.components:
110
+ zeta = (omega - wavenumber * component.drift) / (
111
+ np.sqrt(2.0) * wavenumber * component.sigma
112
+ )
113
+ value += component.weight / (wavenumber * component.sigma) ** 2 * (
114
+ 1.0 + zeta * plasma_dispersion(zeta)
115
+ )
116
+ return complex(value)
117
+
118
+
119
+ def solve_modes(
120
+ distribution: GaussianMixture,
121
+ *,
122
+ wavenumber: float,
123
+ residual_tolerance: float = 1e-8,
124
+ ) -> tuple[LinearMode, ...]:
125
+ guesses = (
126
+ complex(real, imaginary)
127
+ for real in np.linspace(-2.0, 2.0, 9)
128
+ for imaginary in np.linspace(-0.8, 0.6, 8)
129
+ )
130
+ modes: list[LinearMode] = []
131
+ for guess in guesses:
132
+ with warnings.catch_warnings(), np.errstate(all="ignore"):
133
+ warnings.simplefilter("ignore", RuntimeWarning)
134
+ solution = root(
135
+ lambda pair: (
136
+ dielectric(
137
+ complex(float(pair[0]), float(pair[1])),
138
+ wavenumber,
139
+ distribution,
140
+ ).real,
141
+ dielectric(
142
+ complex(float(pair[0]), float(pair[1])),
143
+ wavenumber,
144
+ distribution,
145
+ ).imag,
146
+ ),
147
+ (guess.real, guess.imag),
148
+ tol=1e-11,
149
+ )
150
+ if not solution.success or not np.all(np.isfinite(solution.x)):
151
+ continue
152
+ omega = complex(float(solution.x[0]), float(solution.x[1]))
153
+ residual = abs(dielectric(omega, wavenumber, distribution))
154
+ if residual > residual_tolerance or abs(omega) > 10:
155
+ continue
156
+ if any(
157
+ abs(omega - complex(mode.frequency, mode.growth_rate)) < 1e-6 for mode in modes
158
+ ):
159
+ continue
160
+ modes.append(
161
+ LinearMode(
162
+ frequency=omega.real,
163
+ growth_rate=omega.imag,
164
+ dielectric_residual=float(residual),
165
+ )
166
+ )
167
+ if not modes:
168
+ raise RuntimeError("dispersion solver found no validated root")
169
+ return tuple(sorted(modes, key=lambda mode: mode.growth_rate, reverse=True))
170
+
171
+
172
+ def moments(distribution: GaussianMixture) -> DistributionMoments:
173
+ return DistributionMoments(
174
+ density=distribution.density(),
175
+ mean_velocity=distribution.mean(),
176
+ variance=distribution.variance(),
177
+ )
178
+
179
+
180
+ def classify_mode(mode: LinearMode, *, zero_tolerance: float = 1e-6) -> str:
181
+ if mode.growth_rate > zero_tolerance:
182
+ return "unstable"
183
+ if mode.growth_rate < -zero_tolerance:
184
+ return "damped"
185
+ return "neutral"
186
+
187
+
188
+ def build_problem() -> tuple[HypothesisNode, ObservableSpec]:
189
+ domain = DomainSpec(
190
+ id="domain_linear_1d_vlasov_poisson_k05",
191
+ description=(
192
+ "Linear electrostatic perturbations of smooth normalized electron "
193
+ "distributions with immobile ions at k=0.5"
194
+ ),
195
+ model_family="linearized_1d_electrostatic_vlasov_poisson",
196
+ assumptions=(
197
+ "electron plasma frequency equals one",
198
+ "immobile neutralizing ion background",
199
+ "Gaussian-mixture equilibrium distributions",
200
+ ),
201
+ fixed_parameters={"wavenumber": 0.5},
202
+ )
203
+ observable = ObservableSpec(
204
+ id="observable_dominant_growth_rate",
205
+ name="dominant linear growth rate",
206
+ semantic_kind="dominant_linear_growth_rate",
207
+ mathematical_definition="maximum imaginary part among validated dielectric roots",
208
+ estimator="analytic Gaussian-mixture dielectric root solver",
209
+ units="electron_plasma_frequency",
210
+ tolerance=0.02,
211
+ )
212
+ hypothesis = HypothesisNode(
213
+ id="hypothesis_low_moments_sufficient_for_stability",
214
+ statement=(
215
+ "Density, mean velocity, and variance are sufficient to determine "
216
+ "the dominant linear growth rate in the declared distribution family."
217
+ ),
218
+ machine_predicate=(
219
+ "equal(n, mean_v, variance) implies abs(gamma_left-gamma_right) <= 0.02"
220
+ ),
221
+ formal_predicate=MatchedPairFormalPredicate(
222
+ matched_coordinates=("density", "mean_velocity", "variance"),
223
+ outcome_observable_id=observable.id,
224
+ maximum_outcome_difference=observable.tolerance,
225
+ ),
226
+ proposition_class=PropositionClass.PREDICTIVE_SUFFICIENCY,
227
+ domain=domain,
228
+ coordinates=("density", "mean_velocity", "variance"),
229
+ evidence_contract=EvidenceContract(
230
+ primary_observable_id=observable.id,
231
+ falsifying_witness=(
232
+ "a matched pair with equal declared moments and growth rates "
233
+ "separated by more than 0.02"
234
+ ),
235
+ primary_tolerance=observable.tolerance,
236
+ ),
237
+ origin=HypothesisOrigin.HUMAN,
238
+ )
239
+ return hypothesis, observable
240
+
241
+
242
+ def run_kinetic_sufficiency_benchmark(
243
+ *,
244
+ wavenumber: float = 0.5,
245
+ ) -> KineticSufficiencyResult:
246
+ if abs(wavenumber - 0.5) > 1e-12:
247
+ raise ValueError("version 1 of the planted benchmark is preregistered at k=0.5")
248
+
249
+ maxwellian_distribution = GaussianMixture(
250
+ components=(GaussianComponent(weight=1.0, drift=0.0, sigma=1.0),)
251
+ )
252
+ drift = 0.9
253
+ component_sigma = float(np.sqrt(1.0 - drift**2))
254
+ two_stream_distribution = GaussianMixture(
255
+ components=(
256
+ GaussianComponent(weight=0.5, drift=-drift, sigma=component_sigma),
257
+ GaussianComponent(weight=0.5, drift=drift, sigma=component_sigma),
258
+ )
259
+ )
260
+
261
+ maxwellian_moments = moments(maxwellian_distribution)
262
+ two_stream_moments = moments(two_stream_distribution)
263
+ maxwellian_mode = solve_modes(
264
+ maxwellian_distribution,
265
+ wavenumber=wavenumber,
266
+ )[0]
267
+ two_stream_mode = solve_modes(
268
+ two_stream_distribution,
269
+ wavenumber=wavenumber,
270
+ )[0]
271
+ hypothesis, observable = build_problem()
272
+ coordinate_names = hypothesis.coordinates
273
+ left_coordinates = {
274
+ coordinate: getattr(maxwellian_moments, coordinate) for coordinate in coordinate_names
275
+ }
276
+ right_coordinates = {
277
+ coordinate: getattr(two_stream_moments, coordinate) for coordinate in coordinate_names
278
+ }
279
+ witness = evaluate_predictive_sufficiency(
280
+ hypothesis,
281
+ MatchedObservation(
282
+ evidence_id="evidence_maxwellian_mode",
283
+ coordinates=left_coordinates,
284
+ outcome=maxwellian_mode.growth_rate,
285
+ outcome_uncertainty=maxwellian_mode.dielectric_residual,
286
+ ),
287
+ MatchedObservation(
288
+ evidence_id="evidence_two_stream_mode",
289
+ coordinates=right_coordinates,
290
+ outcome=two_stream_mode.growth_rate,
291
+ outcome_uncertainty=two_stream_mode.dielectric_residual,
292
+ ),
293
+ )
294
+ moments_match = all(
295
+ abs(left_coordinates[name] - right_coordinates[name]) <= 1e-10
296
+ for name in coordinate_names
297
+ )
298
+ return KineticSufficiencyResult(
299
+ hypothesis=hypothesis,
300
+ observable=observable,
301
+ wavenumber=wavenumber,
302
+ maxwellian=KineticCaseResult(
303
+ name="unit_variance_maxwellian",
304
+ distribution=maxwellian_distribution,
305
+ moments=maxwellian_moments,
306
+ mode=maxwellian_mode,
307
+ classification=classify_mode(maxwellian_mode),
308
+ ),
309
+ two_stream=KineticCaseResult(
310
+ name="matched_moment_symmetric_two_stream",
311
+ distribution=two_stream_distribution,
312
+ moments=two_stream_moments,
313
+ mode=two_stream_mode,
314
+ classification=classify_mode(two_stream_mode),
315
+ ),
316
+ moments_match=moments_match,
317
+ witness=witness,
318
+ )
@@ -0,0 +1,151 @@
1
+ ---
2
+ name: warpx
3
+ description: Author, commission, and diagnose simulations with the pinned WarpX 26.07 PICMI capability. Use for any hypothesis whose decisive evidence may require WarpX, especially multidimensional, collisional, electromagnetic, or fully kinetic particle-in-cell calculations.
4
+ ---
5
+
6
+ # WarpX 26.07 PICMI skill
7
+
8
+ Use this skill when a hypothesis can be challenged with a particle-in-cell
9
+ calculation supported by the installed `warpx-cpu-26.07` or
10
+ `warpx-cuda-openpmd-26.07` capability. The skill
11
+ explains the instrument; it does not prescribe the scientific question,
12
+ parameters, diagnostics, or conclusion.
13
+
14
+ ## Execution contract
15
+
16
+ - Author the PICMI Python program and any analysis programs in the writable
17
+ workspace using workspace-relative action paths such as `probe.py`, never
18
+ `/work/probe.py`. The sandbox maps the working directory to `/work` only at
19
+ execution time.
20
+ - Execute a PICMI program with `run_capability`, selecting
21
+ `warpx-cuda-openpmd-26.07` for local 2D GPU work or `warpx-cpu-26.07` for
22
+ the CPU fallback, and an argv whose first item is the workspace-relative
23
+ Python filename. Arguments are passed directly to the pinned Python
24
+ interpreter; there is no shell.
25
+ - The capability has no host home, network, provider credential, or writable
26
+ location outside `/work`.
27
+ - Before GPU production work, read `references/gpu-launch-tuning.md`. The CUDA
28
+ capability is non-MPI, 2D-only, and provides HDF5 openPMD diagnostics.
29
+ - Ordinary `run_python` does not contain `pywarpx`. Use `run_capability` for
30
+ programs that import `pywarpx`; use ordinary Python for independent analysis
31
+ when its installed packages suffice.
32
+ - Treat a zero exit status as execution success only. It is not scientific or
33
+ numerical qualification.
34
+ - The harness health-checks each exact capability build once and caches the result.
35
+ Do not repeat generic import, CUDA, or openPMD smokes inside each campaign when
36
+ `capability_preflight` is healthy.
37
+ - Use `stage=workbench` while authoring, debugging, or revising PICMI programs.
38
+ Workbench artifacts are provenance-recorded but permanently non-evidentiary and
39
+ need no claim or contract. Once the program is stable, freeze it and use
40
+ `stage=evidence` for prospective five-aspect qualification and scientific runs.
41
+
42
+ ## Recommended use
43
+
44
+ 1. Translate the active subhypothesis into observables and a falsifier.
45
+ 2. Derive scientific parameters from the active hypothesis, explicit operator
46
+ input, or sources retrieved during the campaign. Preserve that provenance in
47
+ the workspace. Then choose a supported geometry, field model, boundaries,
48
+ species distributions, time-integration scheme, and runtime. For
49
+ electromagnetic calculations, read `references/time-integration.md` before
50
+ fixing the timestep.
51
+ 3. Iterate on unknown interfaces and candidate programs in the workbench. Before
52
+ promoting a stable WarpX program, register a prospective evidence contract on
53
+ its instrument claim and transition to the complete physical commissioning
54
+ contract. Workbench success cannot be promoted retroactively; the evidence-stage
55
+ qualification must generate fresh artifacts from the frozen source.
56
+ 4. Begin workbench development with an inexpensive anchor that exercises the intended model and
57
+ diagnostic path. Make the program emit a compact JSON commissioning summary
58
+ with qualification booleans below a top-level `checks` object, and register
59
+ exact `validation_checks` such as `checks.completed` for its named
60
+ booleans. Tag checks as `representation`, `physics_controls`, `boundaries`,
61
+ `diagnostics`, or `numerical_regime`; one qualifying contract must cover all
62
+ five. `physics_controls` verifies that the interactions, forcing, sources,
63
+ or constitutive controls required by the claim are present in the realized
64
+ run and have a measured effect where relevant. An
65
+ `interface` check is useful but cannot qualify a scientific run by itself.
66
+ Commission the mathematical representation, not only execution.
67
+ Keep the commissioned program source immutable and parameterize anchors,
68
+ sweeps, and controls through arguments or configuration. Changed source must
69
+ be recommissioned before it can generate scientific evidence.
70
+ 5. Inspect the realized input and outputs. Challenge charge balance, current
71
+ and force balance, energy, boundaries, particle loading, interaction/source
72
+ activation, resolution, timestep, particle statistics, finite-domain
73
+ effects, and diagnostic definitions as relevant. Preserve quantitative
74
+ metrics behind every qualification boolean; do not make a boolean a mere
75
+ restatement of the requested inputs.
76
+ 6. Establish a numerical practice before using production runs as evidence.
77
+ 7. Preserve raw outputs and write analysis code in the workspace. Do not rely
78
+ only on terminal prose.
79
+
80
+ Read these resources as needed:
81
+
82
+ - `references/picmi-interface.md`: main Python construction sequence.
83
+ - `references/2d-xz-commissioning.md`: required reading before any 2D Cartesian
84
+ run; coordinate, field-source, equilibrium, and preflight invariants.
85
+ - `references/diagnostics.md`: field, particle, reduced, and openPMD outputs.
86
+ - `references/time-integration.md`: explicit and implicit PICMI evolve schemes,
87
+ timestep selection, and solver qualification.
88
+ - `references/numerical-risks.md`: common validity failures.
89
+ - `references/resource-scaling.md`: controlling experiment cost.
90
+ - `references/cpu-launch-tuning.md`: required before a long local-host CPU
91
+ campaign; detects MPI support, benchmarks OpenMP count/affinity and AMReX
92
+ block size, and records the Ryzen 9 9900X calibration.
93
+ - `references/gpu-launch-tuning.md`: required before a local-host CUDA run;
94
+ verifies the actual backend, supplies the WSL launch environment, benchmarks
95
+ GPU block sizes, and records RTX 4000 Ada CPU/GPU comparisons.
96
+ - `references/local-cuda-deployment.md`: required when installing or repairing
97
+ the CUDA/openPMD runtime on this or a new machine; use its pinned bootstrap,
98
+ post-install validation, and failure signatures.
99
+ - `examples/minimal_smoke.py`: a one-step neutral-plasma interface smoke test;
100
+ it is deliberately not a scientific template. Its JSON booleans are under
101
+ `checks`.
102
+ - `examples/implicit_em_smoke.py`: a one-step 2D theta-implicit electromagnetic
103
+ wiring and native-input smoke test; it is also permanently non-evidentiary.
104
+ Its JSON booleans are under `checks`. Run it in the workbench only when the
105
+ implicit interface itself is relevant; it does not prove loaded-plasma stability.
106
+ - Host repository demos and operator launch scripts outside this skill tree are
107
+ not skill resources: sandbox agents cannot `read_skill` or
108
+ `materialize_skill_resource` them.
109
+ - `examples/openpmd_field_smoke.py`: a one-step 2D field-diagnostic wiring and
110
+ openPMD/HDF5 readback smoke test; it is permanently non-evidentiary and is the
111
+ CPU and CUDA/openPMD capabilities' cached harness preflight. Campaign agents
112
+ normally need not repeat it. Its JSON booleans are under `checks`.
113
+ - `scripts/benchmark_cpu_threads.py`: run an isolated, validated OpenMP scaling
114
+ sweep for an operator-supplied PICMI program and exact argument list. Never
115
+ infer MPI support merely from the presence of `mpiexec`; the script queries
116
+ the loaded AMReX library.
117
+ - `scripts/run_local_cuda.sh`: operator-side launcher for the project-local 2D
118
+ CUDA runtime. Autonomous sandbox work uses the named CUDA capability instead.
119
+ Skill `scripts/` are host/operator tools: they are not exact-reuse sandbox
120
+ programs and typically cannot run inside the capability (for example they
121
+ may call `nvidia-smi`). Use `examples/*.py` for in-sandbox exact reuse.
122
+ - `scripts/bootstrap_local_cuda.sh`: create the pinned CUDA, non-MPI HDF5, and
123
+ openPMD environments and compile the 2D WarpX binding from an audited source
124
+ checkout.
125
+ - `scripts/probe_local_cuda.py`: prove CUDA-aware AMReX plus an openPMD HDF5
126
+ write/read round trip after deployment.
127
+ - `scripts/benchmark_gpu.py`: run a guarded, validated CUDA block-size sweep;
128
+ it passes the operator-supplied program arguments through unchanged except
129
+ for the explicitly named block-size option.
130
+ - `scripts/reduced_energy_budget.py`: materialize this deterministic sandbox-safe
131
+ reader for pinned single-level `FieldEnergy` plus `ParticleEnergy` diagnostics.
132
+ It selects exact total columns by realized header, refuses unknown schemas,
133
+ and prevents adding totals to their component/species columns.
134
+
135
+ Official WarpX 26.07 documentation:
136
+
137
+ - <https://warpx.readthedocs.io/en/latest/usage/python.html>
138
+ - <https://warpx.readthedocs.io/en/latest/usage/parameters.html>
139
+ - <https://warpx.readthedocs.io/en/latest/usage/workflows/analysis.html>
140
+
141
+ ## Boundaries
142
+
143
+ Do not infer that WarpX is preferable merely because it is installed. A
144
+ smaller analytic or ordinary-Python calculation may be a better discriminator.
145
+ Do not claim that a runtime smoke test qualifies a physical regime. Do not
146
+ silently change an observable or numerical policy after seeing a result;
147
+ record the reason and treat the changed calculation as a new attempt.
148
+ Never obtain scientific parameters, expected outcomes, acceptance thresholds,
149
+ or benchmark definitions from this skill, its smoke examples, host-performance
150
+ records, or an earlier demonstration. Retrieve or derive them within the active
151
+ campaign and preserve their source.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "WarpX PIC Simulation"
3
+ short_description: "Commission and tune kinetic WarpX simulations"
4
+ default_prompt: "Use $warpx to design, commission, tune, and diagnose a WarpX particle-in-cell simulation."
@@ -0,0 +1,74 @@
1
+ """One-step WarpX theta-implicit EM interface smoke; never scientific evidence.
2
+
3
+ Uses a tiny 2D grid so the same program runs under both the multi-dimension
4
+ CPU capability and the 2D-only CUDA/openPMD capability.
5
+ """
6
+
7
+ import json
8
+ from pathlib import Path
9
+
10
+ from pywarpx import picmi
11
+
12
+ grid = picmi.Cartesian2DGrid(
13
+ number_of_cells=[8, 8],
14
+ lower_bound=[0.0, 0.0],
15
+ upper_bound=[1.0e-3, 1.0e-3],
16
+ lower_boundary_conditions=["periodic", "periodic"],
17
+ upper_boundary_conditions=["periodic", "periodic"],
18
+ lower_boundary_conditions_particles=["periodic", "periodic"],
19
+ upper_boundary_conditions_particles=["periodic", "periodic"],
20
+ warpx_max_grid_size=8,
21
+ )
22
+ solver = picmi.ElectromagneticSolver(grid=grid, method="Yee")
23
+ nonlinear_solver = picmi.PicardNonlinearSolver(
24
+ require_convergence=True,
25
+ max_iterations=20,
26
+ relative_tolerance=1.0e-8,
27
+ absolute_tolerance=0.0,
28
+ diagnostic_file="picard_iterations.txt",
29
+ diagnostic_interval="1",
30
+ )
31
+ evolve_scheme = picmi.ThetaImplicitEMEvolveScheme(
32
+ nonlinear_solver=nonlinear_solver,
33
+ theta=0.5,
34
+ )
35
+ simulation = picmi.Simulation(
36
+ solver=solver,
37
+ time_step_size=1.0e-12,
38
+ max_steps=1,
39
+ particle_shape=1,
40
+ verbose=1,
41
+ warpx_evolve_scheme=evolve_scheme,
42
+ warpx_random_seed=1,
43
+ warpx_serialize_initial_conditions=True,
44
+ warpx_amrex_the_arena_init_size=256 * 1024 * 1024,
45
+ warpx_used_inputs_file="implicit_used_inputs",
46
+ )
47
+ for particle_type, name, frozen in (
48
+ ("electron", "electrons", False),
49
+ ("proton", "ions", True),
50
+ ):
51
+ species = picmi.Species(
52
+ particle_type=particle_type,
53
+ name=name,
54
+ initial_distribution=picmi.UniformDistribution(density=1.0e15),
55
+ warpx_do_not_push=frozen,
56
+ )
57
+ simulation.add_species(
58
+ species,
59
+ layout=picmi.GriddedLayout(grid=grid, n_macroparticle_per_cell=[1, 1]),
60
+ )
61
+
62
+ simulation.step()
63
+ used_inputs = Path("implicit_used_inputs").read_text()
64
+ checks = {
65
+ "completed": True,
66
+ "theta_implicit_native": 'algo.evolve_scheme = "theta_implicit_em"'
67
+ in used_inputs,
68
+ "picard_native": 'implicit_evolve.nonlinear_solver = "picard"' in used_inputs,
69
+ "convergence_required_native": "picard.require_convergence = 1" in used_inputs,
70
+ "scientific_evidence_eligible": False,
71
+ }
72
+ Path("implicit_capability_smoke.json").write_text(
73
+ json.dumps({"checks": checks}) + "\n"
74
+ )
@@ -0,0 +1,54 @@
1
+ """One-step WarpX/PICMI interface smoke; permanently non-evidentiary."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ from pywarpx import picmi
7
+
8
+ grid = picmi.Cartesian1DGrid(
9
+ number_of_cells=[8],
10
+ lower_bound=[0.0],
11
+ upper_bound=[1.0e-3],
12
+ lower_boundary_conditions=["periodic"],
13
+ upper_boundary_conditions=["periodic"],
14
+ lower_boundary_conditions_particles=["periodic"],
15
+ upper_boundary_conditions_particles=["periodic"],
16
+ warpx_max_grid_size=8,
17
+ )
18
+ solver = picmi.ElectrostaticSolver(grid=grid)
19
+ simulation = picmi.Simulation(
20
+ solver=solver,
21
+ time_step_size=1.0e-14,
22
+ max_steps=1,
23
+ particle_shape=1,
24
+ verbose=1,
25
+ warpx_random_seed=1,
26
+ warpx_serialize_initial_conditions=True,
27
+ warpx_used_inputs_file="warpx_used_inputs",
28
+ )
29
+ for particle_type, name, frozen in (
30
+ ("electron", "electrons", False),
31
+ ("proton", "ions", True),
32
+ ):
33
+ species = picmi.Species(
34
+ particle_type=particle_type,
35
+ name=name,
36
+ initial_distribution=picmi.UniformDistribution(density=1.0e15),
37
+ warpx_do_not_push=frozen,
38
+ )
39
+ simulation.add_species(
40
+ species,
41
+ layout=picmi.GriddedLayout(grid=grid, n_macroparticle_per_cell=[1]),
42
+ )
43
+ simulation.step()
44
+ Path("capability_smoke.json").write_text(
45
+ json.dumps(
46
+ {
47
+ "checks": {
48
+ "completed": True,
49
+ "scientific_evidence_eligible": False,
50
+ }
51
+ }
52
+ )
53
+ + "\n"
54
+ )
@@ -0,0 +1,87 @@
1
+ """One-step 2D openPMD/HDF5 field-diagnostic smoke; never scientific evidence."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ import openpmd_api as io
7
+ from pywarpx import picmi
8
+
9
+ grid = picmi.Cartesian2DGrid(
10
+ number_of_cells=[8, 8],
11
+ lower_bound=[0.0, 0.0],
12
+ upper_bound=[1.0e-3, 1.0e-3],
13
+ lower_boundary_conditions=["periodic", "periodic"],
14
+ upper_boundary_conditions=["periodic", "periodic"],
15
+ lower_boundary_conditions_particles=["periodic", "periodic"],
16
+ upper_boundary_conditions_particles=["periodic", "periodic"],
17
+ warpx_max_grid_size=8,
18
+ )
19
+ solver = picmi.ElectromagneticSolver(grid=grid, method="Yee")
20
+ simulation = picmi.Simulation(
21
+ solver=solver,
22
+ time_step_size=1.0e-14,
23
+ max_steps=1,
24
+ particle_shape=1,
25
+ verbose=1,
26
+ warpx_random_seed=1,
27
+ warpx_serialize_initial_conditions=True,
28
+ warpx_amrex_the_arena_init_size=256 * 1024 * 1024,
29
+ warpx_used_inputs_file="openpmd_used_inputs",
30
+ )
31
+ for particle_type, name in (("electron", "electrons"), ("proton", "ions")):
32
+ species = picmi.Species(
33
+ particle_type=particle_type,
34
+ name=name,
35
+ initial_distribution=picmi.UniformDistribution(
36
+ density=1.0e15,
37
+ rms_velocity=[1.0e3, 1.0e3, 1.0e3],
38
+ ),
39
+ warpx_do_not_push=True,
40
+ )
41
+ simulation.add_species(
42
+ species,
43
+ layout=picmi.GriddedLayout(
44
+ grid=grid,
45
+ n_macroparticle_per_cell=[1, 1],
46
+ ),
47
+ )
48
+
49
+ simulation.add_diagnostic(
50
+ picmi.FieldDiagnostic(
51
+ name="fields",
52
+ grid=grid,
53
+ period=1,
54
+ data_list=["E", "B", "J", "rho"],
55
+ write_dir="openpmd_diags",
56
+ warpx_format="openpmd",
57
+ warpx_openpmd_backend="h5",
58
+ warpx_openpmd_encoding="f",
59
+ )
60
+ )
61
+ simulation.step()
62
+
63
+ used_inputs = Path("openpmd_used_inputs").read_text()
64
+ h5_files = sorted(Path("openpmd_diags").rglob("*.h5"))
65
+ iteration_count = 0
66
+ mesh_record_count = 0
67
+ if h5_files:
68
+ series = io.Series(str(h5_files[0]), io.Access.read_only)
69
+ # Iteration objects have .time and .meshes, not .iteration.
70
+ iteration_count = len(series.iterations)
71
+ mesh_record_count = sum(
72
+ len(series.iterations[index].meshes) for index in series.iterations
73
+ )
74
+ series.close()
75
+
76
+ checks = {
77
+ "completed": True,
78
+ "openpmd_format_native": 'fields.format = "openpmd"' in used_inputs,
79
+ "h5_backend_native": 'fields.openpmd_backend = "h5"' in used_inputs,
80
+ "h5_file_nonempty": bool(h5_files and h5_files[0].stat().st_size > 0),
81
+ "iteration_readable": iteration_count > 0,
82
+ "mesh_records_readable": mesh_record_count > 0,
83
+ "scientific_evidence_eligible": False,
84
+ }
85
+ Path("openpmd_capability_smoke.json").write_text(
86
+ json.dumps({"checks": checks}) + "\n"
87
+ )