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,10 @@
1
+ """Core package for the Simjecture research runtime."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("simjecture")
7
+ except PackageNotFoundError:
8
+ __version__ = "0.1.0"
9
+
10
+ __all__ = ["__version__"]
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
4
+
@@ -0,0 +1,459 @@
1
+ """Registered handlers for the first multi-action scientific campaign."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ from datetime import datetime
8
+
9
+ from pydantic import ValidationError
10
+
11
+ from .adapters.base import SimulatorAdapter
12
+ from .benchmarks.electrostatic_pic import PICNumericalConfig
13
+ from .benchmarks.kinetic_sufficiency import build_problem
14
+ from .confirmation import (
15
+ PICConfirmationDesign,
16
+ PICConfirmationRunner,
17
+ confirmation_design_from_search,
18
+ )
19
+ from .models import Claim, ClaimDisposition, EvidenceRole, RunEvidence
20
+ from .orchestration import (
21
+ ActionContext,
22
+ ActionExecution,
23
+ ActionExecutionError,
24
+ ActionFailureKind,
25
+ ActionHandler,
26
+ ActionOrigin,
27
+ CampaignAction,
28
+ CampaignActionGraph,
29
+ CampaignBudget,
30
+ )
31
+ from .outbox import OutboxCrashPoint
32
+ from .search import (
33
+ BlindedSearchReport,
34
+ BlindedSearchRequest,
35
+ BlindedSearchRunner,
36
+ SearchStrategy,
37
+ baseline_strategies,
38
+ )
39
+ from .warpx_confirmation import (
40
+ InjectedWarpXConfirmationCrash,
41
+ QualifiedWarpXInstrument,
42
+ WarpXConfirmationCrashPoint,
43
+ WarpXConfirmationDesign,
44
+ WarpXConfirmationDisposition,
45
+ WarpXConfirmationReport,
46
+ WarpXConfirmationRunner,
47
+ default_warpx_confirmation_design,
48
+ )
49
+
50
+ BLINDED_SEARCH_ACTION = "blinded_analytic_search"
51
+ PIC_CONFIRMATION_ACTION = "frozen_pic_confirmation"
52
+ QUALIFIED_WARPX_CONFIRMATION_ACTION = "qualified_warpx_confirmation"
53
+
54
+
55
+ def _hash_output(output: dict[str, object]) -> str:
56
+ canonical = json.dumps(output, sort_keys=True, separators=(",", ":"))
57
+ return hashlib.sha256(canonical.encode()).hexdigest()
58
+
59
+
60
+ class BlindedSearchActionHandler:
61
+ def execute(
62
+ self,
63
+ context: ActionContext,
64
+ action: CampaignAction,
65
+ dependencies: dict[str, ActionExecution],
66
+ ) -> ActionExecution:
67
+ if dependencies:
68
+ raise ActionExecutionError(
69
+ ActionFailureKind.SPECIFICATION,
70
+ "blinded search action must not receive dependency outputs",
71
+ )
72
+ try:
73
+ request = BlindedSearchRequest.model_validate(action.payload["request"])
74
+ strategies = tuple(
75
+ SearchStrategy.model_validate(strategy) for strategy in action.payload["strategies"]
76
+ )
77
+ except (KeyError, TypeError, ValidationError) as error:
78
+ raise ActionExecutionError(
79
+ ActionFailureKind.SPECIFICATION,
80
+ f"invalid blinded-search payload: {error}",
81
+ ) from error
82
+ expected_units = request.evaluations_per_method * len(request.comparison_methods)
83
+ if abs(action.budget_units - expected_units) > 1e-12:
84
+ raise ActionExecutionError(
85
+ ActionFailureKind.SPECIFICATION,
86
+ "search budget units must equal the number of candidate evaluations",
87
+ )
88
+ try:
89
+ report = BlindedSearchRunner(
90
+ campaign_id=context.campaign_id,
91
+ ledger=context.ledger,
92
+ request=request,
93
+ strategies=strategies,
94
+ ).run()
95
+ except ValueError as error:
96
+ raise ActionExecutionError(
97
+ ActionFailureKind.SPECIFICATION,
98
+ f"blinded search was rejected: {error}",
99
+ ) from error
100
+ output: dict[str, object] = {"search_report": report.model_dump(mode="json")}
101
+ return ActionExecution(
102
+ action_id=action.id,
103
+ evidence_eligible=report.confirmation_candidate_id is not None,
104
+ output=output,
105
+ output_hash=_hash_output(output),
106
+ )
107
+
108
+
109
+ class PICConfirmationActionHandler:
110
+ def execute(
111
+ self,
112
+ context: ActionContext,
113
+ action: CampaignAction,
114
+ dependencies: dict[str, ActionExecution],
115
+ ) -> ActionExecution:
116
+ source_action_id = action.payload.get("source_action_id")
117
+ if not isinstance(source_action_id, str) or source_action_id not in dependencies:
118
+ raise ActionExecutionError(
119
+ ActionFailureKind.SPECIFICATION,
120
+ "confirmation source_action_id must name a completed dependency",
121
+ )
122
+ try:
123
+ search_report = BlindedSearchReport.model_validate(
124
+ dependencies[source_action_id].output["search_report"]
125
+ )
126
+ design = confirmation_design_from_search(search_report)
127
+ design = PICConfirmationDesign.model_validate(
128
+ {
129
+ **design.model_dump(mode="json"),
130
+ "seeds": action.payload["seeds"],
131
+ "velocity_beams": action.payload["velocity_beams"],
132
+ "base_config": action.payload["base_config"],
133
+ }
134
+ )
135
+ except (KeyError, TypeError, ValueError, ValidationError) as error:
136
+ raise ActionExecutionError(
137
+ ActionFailureKind.SPECIFICATION,
138
+ f"invalid PIC confirmation payload: {error}",
139
+ ) from error
140
+ expected_units = 2 * len(design.configurations())
141
+ if abs(action.budget_units - expected_units) > 1e-12:
142
+ raise ActionExecutionError(
143
+ ActionFailureKind.SPECIFICATION,
144
+ "confirmation budget units must equal the number of PIC case executions",
145
+ )
146
+ report = PICConfirmationRunner(
147
+ campaign_id=context.campaign_id,
148
+ ledger=context.ledger,
149
+ design=design,
150
+ ).run()
151
+ output: dict[str, object] = {"confirmation_report": report.model_dump(mode="json")}
152
+ return ActionExecution(
153
+ action_id=action.id,
154
+ evidence_eligible=all(attempt.eligible for attempt in report.attempts),
155
+ output=output,
156
+ output_hash=_hash_output(output),
157
+ )
158
+
159
+
160
+ def _warpx_evidence_and_claim(
161
+ context: ActionContext,
162
+ report: WarpXConfirmationReport,
163
+ ) -> tuple[tuple[RunEvidence, ...], Claim]:
164
+ hypothesis, _ = build_problem()
165
+ evidence: list[RunEvidence] = []
166
+ group = f"warpx_picmi_qualified_{report.design.qualification_hash[:16]}"
167
+ for attempt in report.attempts:
168
+ observables = attempt.normalized_result.observables
169
+ identity = hashlib.sha256(
170
+ f"{context.campaign_id}:{attempt.resolution_id}:{attempt.seed}".encode()
171
+ ).hexdigest()[:20]
172
+ evidence.append(
173
+ RunEvidence(
174
+ id=f"evidence_{identity}",
175
+ source_attempt_id=(
176
+ f"attempt_warpx_confirmation_{attempt.resolution_id}_seed_{attempt.seed}_v1"
177
+ ),
178
+ role=EvidenceRole.CONFIRMATION,
179
+ eligible=attempt.confirmed,
180
+ eligibility_reason=(
181
+ "fresh qualified WarpX pair passed every preregistered confirmation gate"
182
+ if attempt.confirmed
183
+ else "WarpX pair failed one or more preregistered confirmation gates"
184
+ ),
185
+ observable_values={
186
+ "maxwellian_growth_rate": float(observables["maxwellian_growth_rate"]),
187
+ "two_stream_growth_rate": float(observables["two_stream_growth_rate"]),
188
+ "outcome_separation": float(observables["outcome_separation"]),
189
+ },
190
+ independence_group=group,
191
+ artifact_hashes=attempt.normalized_result.artifact_hashes,
192
+ )
193
+ )
194
+ for failure in report.failures:
195
+ identity = hashlib.sha256(
196
+ f"{context.campaign_id}:{failure.resolution_id}:{failure.seed}".encode()
197
+ ).hexdigest()[:20]
198
+ evidence.append(
199
+ RunEvidence(
200
+ id=f"evidence_{identity}",
201
+ source_attempt_id=f"attempt_{failure.experiment_id.removeprefix('experiment_')}",
202
+ role=EvidenceRole.CONFIRMATION,
203
+ eligible=False,
204
+ eligibility_reason=(
205
+ f"{failure.kind.value} execution failure is not physical evidence: "
206
+ f"{failure.detail}"
207
+ ),
208
+ observable_values={},
209
+ independence_group=group,
210
+ )
211
+ )
212
+ completed_event = next(
213
+ event
214
+ for event in reversed(context.ledger.load(context.campaign_id))
215
+ if event.event_type == "qualified_warpx_confirmation_completed"
216
+ )
217
+ refuted = report.disposition is WarpXConfirmationDisposition.CONFIRMED
218
+ evidence_ids = tuple(item.id for item in evidence)
219
+ claim = Claim(
220
+ id=f"claim_{hashlib.sha256(context.campaign_id.encode()).hexdigest()[:20]}",
221
+ hypothesis_id=hypothesis.id,
222
+ statement=(
223
+ "The low-order-moment predictive-sufficiency hypothesis is independently "
224
+ "refuted by the qualified WarpX confirmation matrix."
225
+ if refuted
226
+ else "The qualified WarpX confirmation matrix did not resolve the hypothesis."
227
+ ),
228
+ disposition=(
229
+ ClaimDisposition.REFUTED_WITHIN_MODEL if refuted else ClaimDisposition.UNRESOLVED
230
+ ),
231
+ scope=(
232
+ "the registered one-dimensional electrostatic WarpX/PICMI model at "
233
+ "k lambda_D = 0.5 and the frozen qualified parameter envelope"
234
+ ),
235
+ evidence_ids=evidence_ids,
236
+ limitations=(
237
+ "all confirmation attempts share one WarpX implementation and estimator family",
238
+ "the result does not establish sufficiency or insufficiency outside the "
239
+ "qualified scope",
240
+ "finite seeds and two resolutions bound, but do not eliminate, sampling uncertainty",
241
+ ),
242
+ created_at=datetime.fromisoformat(completed_event.created_at),
243
+ )
244
+ return tuple(evidence), claim
245
+
246
+
247
+ class QualifiedWarpXConfirmationActionHandler:
248
+ def __init__(
249
+ self,
250
+ *,
251
+ instrument: QualifiedWarpXInstrument,
252
+ adapter: SimulatorAdapter,
253
+ crash_at: WarpXConfirmationCrashPoint | None = None,
254
+ crash_ordinal: int | None = None,
255
+ outbox_crash_at: OutboxCrashPoint | None = None,
256
+ ) -> None:
257
+ self.instrument = instrument
258
+ self.adapter = adapter
259
+ self.crash_at = crash_at
260
+ self.crash_ordinal = crash_ordinal
261
+ self.outbox_crash_at = outbox_crash_at
262
+
263
+ def execute(
264
+ self,
265
+ context: ActionContext,
266
+ action: CampaignAction,
267
+ dependencies: dict[str, ActionExecution],
268
+ ) -> ActionExecution:
269
+ source_action_id = action.payload.get("source_action_id")
270
+ if not isinstance(source_action_id, str) or source_action_id not in dependencies:
271
+ raise ActionExecutionError(
272
+ ActionFailureKind.SPECIFICATION,
273
+ "WarpX confirmation source_action_id must name a completed dependency",
274
+ )
275
+ try:
276
+ if action.payload["instrument_id"] != self.instrument.id:
277
+ raise ValueError("action names a different registered instrument")
278
+ if action.payload["qualification_hash"] != self.instrument.qualification_hash:
279
+ raise ValueError("action qualification hash differs from the instrument")
280
+ search_report = BlindedSearchReport.model_validate(
281
+ dependencies[source_action_id].output["search_report"]
282
+ )
283
+ design = WarpXConfirmationDesign.model_validate(action.payload["design"])
284
+ if search_report.confirmation_candidate is None:
285
+ raise ValueError("analytic discovery did not freeze a confirmation candidate")
286
+ if search_report.confirmation_candidate != design.physical.candidate:
287
+ raise ValueError(
288
+ "frozen analytic candidate is outside the exact qualified WarpX scope"
289
+ )
290
+ expected_units = 2 * len(design.seeds) * len(design.resolutions)
291
+ if abs(action.budget_units - expected_units) > 1e-12:
292
+ raise ValueError(
293
+ "WarpX budget units must equal the paired case executions in the matrix"
294
+ )
295
+ report = WarpXConfirmationRunner(
296
+ campaign_id=context.campaign_id,
297
+ ledger=context.ledger,
298
+ instrument=self.instrument,
299
+ adapter=self.adapter,
300
+ design=design,
301
+ control=context.control,
302
+ crash_at=self.crash_at,
303
+ crash_ordinal=self.crash_ordinal,
304
+ outbox_crash_at=self.outbox_crash_at,
305
+ ).run()
306
+ except (KeyError, TypeError, ValueError, ValidationError) as error:
307
+ raise ActionExecutionError(
308
+ ActionFailureKind.SPECIFICATION,
309
+ f"invalid qualified WarpX confirmation action: {error}",
310
+ ) from error
311
+ except InjectedWarpXConfirmationCrash:
312
+ raise
313
+ evidence, claim = _warpx_evidence_and_claim(context, report)
314
+ output: dict[str, object] = {
315
+ "instrument_id": self.instrument.id,
316
+ "qualification_hash": self.instrument.qualification_hash,
317
+ "confirmation_report": report.model_dump(mode="json"),
318
+ "evidence": [item.model_dump(mode="json") for item in evidence],
319
+ "claim": claim.model_dump(mode="json"),
320
+ }
321
+ return ActionExecution(
322
+ action_id=action.id,
323
+ evidence_eligible=(
324
+ report.disposition is WarpXConfirmationDisposition.CONFIRMED
325
+ and bool(evidence)
326
+ and all(item.eligible for item in evidence)
327
+ ),
328
+ output=output,
329
+ output_hash=_hash_output(output),
330
+ artifact_hashes=tuple(
331
+ artifact for item in evidence for artifact in item.artifact_hashes
332
+ ),
333
+ )
334
+
335
+
336
+ def blinded_campaign_handlers() -> dict[str, ActionHandler]:
337
+ return {
338
+ BLINDED_SEARCH_ACTION: BlindedSearchActionHandler(),
339
+ PIC_CONFIRMATION_ACTION: PICConfirmationActionHandler(),
340
+ }
341
+
342
+
343
+ def qualified_warpx_campaign_handlers(
344
+ *,
345
+ instrument: QualifiedWarpXInstrument,
346
+ adapter: SimulatorAdapter,
347
+ crash_at: WarpXConfirmationCrashPoint | None = None,
348
+ crash_ordinal: int | None = None,
349
+ outbox_crash_at: OutboxCrashPoint | None = None,
350
+ ) -> dict[str, ActionHandler]:
351
+ return {
352
+ BLINDED_SEARCH_ACTION: BlindedSearchActionHandler(),
353
+ QUALIFIED_WARPX_CONFIRMATION_ACTION: QualifiedWarpXConfirmationActionHandler(
354
+ instrument=instrument,
355
+ adapter=adapter,
356
+ crash_at=crash_at,
357
+ crash_ordinal=crash_ordinal,
358
+ outbox_crash_at=outbox_crash_at,
359
+ ),
360
+ }
361
+
362
+
363
+ def build_qualified_warpx_campaign_graph(
364
+ request: BlindedSearchRequest,
365
+ ai_strategy: SearchStrategy,
366
+ instrument: QualifiedWarpXInstrument,
367
+ ) -> CampaignActionGraph:
368
+ strategies = (ai_strategy, *baseline_strategies(request))
369
+ analytic_evaluations = request.evaluations_per_method * len(request.comparison_methods)
370
+ design = default_warpx_confirmation_design(instrument.qualification)
371
+ warpx_case_executions = 2 * len(design.seeds) * len(design.resolutions)
372
+ discovery = CampaignAction(
373
+ id="action_blinded_analytic_discovery_v1",
374
+ action_type=BLINDED_SEARCH_ACTION,
375
+ purpose="select and analytically evaluate a blinded matched-moment candidate batch",
376
+ evidence_role=EvidenceRole.DISCOVERY,
377
+ independence_group="analytic_gaussian_mixture_dispersion_solver_v1",
378
+ origin=ActionOrigin.MIXED,
379
+ budget_units=float(analytic_evaluations),
380
+ payload={
381
+ "request": request.model_dump(mode="json"),
382
+ "strategies": [strategy.model_dump(mode="json") for strategy in strategies],
383
+ },
384
+ )
385
+ confirmation = CampaignAction(
386
+ id="action_qualified_warpx_confirmation_v1",
387
+ action_type=QUALIFIED_WARPX_CONFIRMATION_ACTION,
388
+ purpose=(
389
+ "test the frozen analytic witness using the registered qualified WarpX "
390
+ "instrument on fresh seeds and two resolutions"
391
+ ),
392
+ dependencies=(discovery.id,),
393
+ evidence_role=EvidenceRole.CONFIRMATION,
394
+ independence_group=f"qualified_warpx_{instrument.qualification_hash[:16]}",
395
+ origin=ActionOrigin.DETERMINISTIC,
396
+ budget_units=float(warpx_case_executions),
397
+ payload={
398
+ "source_action_id": discovery.id,
399
+ "instrument_id": instrument.id,
400
+ "qualification_hash": instrument.qualification_hash,
401
+ "design": design.model_dump(mode="json"),
402
+ },
403
+ )
404
+ return CampaignActionGraph(
405
+ id="action_graph_blinded_qualified_warpx_confirmation_v1",
406
+ actions=(discovery, confirmation),
407
+ budget=CampaignBudget(
408
+ total_units=float(analytic_evaluations + warpx_case_executions),
409
+ unit_name="physics_case_evaluation",
410
+ ),
411
+ )
412
+
413
+
414
+ def build_blinded_multi_action_graph(
415
+ request: BlindedSearchRequest,
416
+ ai_strategy: SearchStrategy,
417
+ ) -> CampaignActionGraph:
418
+ strategies = (ai_strategy, *baseline_strategies(request))
419
+ analytic_evaluations = request.evaluations_per_method * len(request.comparison_methods)
420
+ seeds = (1, 7, 19)
421
+ velocity_beams = (192, 384)
422
+ pic_case_executions = 2 * len(seeds) * len(velocity_beams)
423
+ discovery = CampaignAction(
424
+ id="action_blinded_analytic_discovery_v1",
425
+ action_type=BLINDED_SEARCH_ACTION,
426
+ purpose="select and analytically evaluate a blinded matched-moment candidate batch",
427
+ evidence_role=EvidenceRole.DISCOVERY,
428
+ independence_group="analytic_gaussian_mixture_dispersion_solver_v1",
429
+ origin=ActionOrigin.MIXED,
430
+ budget_units=float(analytic_evaluations),
431
+ payload={
432
+ "request": request.model_dump(mode="json"),
433
+ "strategies": [strategy.model_dump(mode="json") for strategy in strategies],
434
+ },
435
+ )
436
+ confirmation = CampaignAction(
437
+ id="action_frozen_pic_confirmation_v1",
438
+ action_type=PIC_CONFIRMATION_ACTION,
439
+ purpose="confirm the frozen AI witness with fresh independent PIC attempts",
440
+ dependencies=(discovery.id,),
441
+ evidence_role=EvidenceRole.CONFIRMATION,
442
+ independence_group="electrostatic_pic_inverse_cdf_quiet_start_v1",
443
+ origin=ActionOrigin.DETERMINISTIC,
444
+ budget_units=float(pic_case_executions),
445
+ payload={
446
+ "source_action_id": discovery.id,
447
+ "seeds": list(seeds),
448
+ "velocity_beams": list(velocity_beams),
449
+ "base_config": PICNumericalConfig().model_dump(mode="json"),
450
+ },
451
+ )
452
+ return CampaignActionGraph(
453
+ id="action_graph_blinded_discovery_confirmation_v1",
454
+ actions=(discovery, confirmation),
455
+ budget=CampaignBudget(
456
+ total_units=float(analytic_evaluations + pic_case_executions),
457
+ unit_name="physics_case_evaluation",
458
+ ),
459
+ )
@@ -0,0 +1,7 @@
1
+ """Simulator adapter contracts and implementations."""
2
+
3
+ from .base import SimulatorAdapter
4
+ from .fake import DeterministicKineticAdapter
5
+ from .warpx import WarpXAdapter
6
+
7
+ __all__ = ["DeterministicKineticAdapter", "SimulatorAdapter", "WarpXAdapter"]
@@ -0,0 +1,96 @@
1
+ """Simulator-neutral execution contract."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import StrEnum
6
+ from typing import Any, Protocol, runtime_checkable
7
+
8
+ from pydantic import Field
9
+
10
+ from ..models import ExperimentSpec, StrictModel
11
+
12
+
13
+ class JobState(StrEnum):
14
+ QUEUED = "queued"
15
+ RUNNING = "running"
16
+ COMPLETED = "completed"
17
+ FAILED = "failed"
18
+ CANCELLED = "cancelled"
19
+ UNKNOWN = "unknown"
20
+
21
+
22
+ class CapabilityManifest(StrictModel):
23
+ adapter_name: str
24
+ adapter_version: str
25
+ supported_actions: tuple[str, ...]
26
+ supported_models: tuple[str, ...]
27
+ supported_diagnostics: tuple[str, ...]
28
+ supported_coordinates: tuple[str, ...]
29
+ supported_observable_kinds: tuple[str, ...]
30
+ supports_checkpoint: bool
31
+ deterministic: bool
32
+
33
+
34
+ class ValidationReport(StrictModel):
35
+ valid: bool
36
+ errors: tuple[str, ...] = ()
37
+ warnings: tuple[str, ...] = ()
38
+
39
+
40
+ class CostEstimate(StrictModel):
41
+ compute_units: float = Field(ge=0)
42
+ wall_seconds: float = Field(ge=0)
43
+ storage_bytes: int = Field(ge=0)
44
+
45
+
46
+ class RunPackage(StrictModel):
47
+ experiment_id: str
48
+ adapter_name: str
49
+ payload: dict[str, Any]
50
+ package_hash: str
51
+
52
+
53
+ class JobReference(StrictModel):
54
+ job_id: str
55
+ experiment_id: str
56
+ idempotency_key: str
57
+
58
+
59
+ class JobStatus(StrictModel):
60
+ job_id: str
61
+ state: JobState
62
+ detail: str = ""
63
+
64
+
65
+ class RawResult(StrictModel):
66
+ job_id: str
67
+ payload: dict[str, Any]
68
+ artifact_hashes: tuple[str, ...] = ()
69
+
70
+
71
+ class NormalizedResult(StrictModel):
72
+ experiment_id: str
73
+ observables: dict[str, float | bool | str]
74
+ diagnostics: dict[str, Any]
75
+ artifact_hashes: tuple[str, ...] = ()
76
+
77
+
78
+ @runtime_checkable
79
+ class SimulatorAdapter(Protocol):
80
+ def capabilities(self) -> CapabilityManifest: ...
81
+
82
+ def validate(self, experiment: ExperimentSpec) -> ValidationReport: ...
83
+
84
+ def estimate_cost(self, experiment: ExperimentSpec) -> CostEstimate: ...
85
+
86
+ def compile_input(self, experiment: ExperimentSpec) -> RunPackage: ...
87
+
88
+ def submit(self, run: RunPackage, *, idempotency_key: str) -> JobReference: ...
89
+
90
+ def monitor(self, job: JobReference) -> JobStatus: ...
91
+
92
+ def retrieve(self, job: JobReference) -> RawResult: ...
93
+
94
+ def normalize(self, result: RawResult) -> NormalizedResult: ...
95
+
96
+ def cancel(self, job: JobReference) -> JobStatus: ...