Skip to content

Commit 3281a0f

Browse files
author
Elwardi
committed
refactor: rework trial dependencies; no more actions; it is now pure resolution
1 parent 1e1a1a0 commit 3281a0f

10 files changed

Lines changed: 212 additions & 368 deletions

File tree

src/foambo/api.py

Lines changed: 20 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -456,55 +456,41 @@ def __init__(self, **kwargs):
456456

457457
# --- Trial Dependencies ---
458458

459-
def depend(self, name: str, source: str = "best", command: str | list[str] = "",
460-
phase: str = "immediate", fallback: str = "skip",
461-
enabled: bool = True) -> FoamBO:
459+
def depend(self, name: str, source: str = "best",
460+
fallback: str = "skip", enabled: bool = True,
461+
**source_kwargs) -> FoamBO:
462462
"""Add a trial dependency.
463463
464464
Args:
465465
name: Label for this dependency.
466-
source: Selection strategy (``"best"``, ``"nearest"``, ``"latest"``, ``"baseline"``).
467-
command: Shell command with ``$FOAMBO_SOURCE_TRIAL`` / ``$FOAMBO_TARGET_TRIAL`` substitution.
468-
phase: When the action runs in the trial lifecycle:
469-
470-
* ``"immediate"`` -- before the runner starts (default).
471-
* ``"pre_init"`` / ``"pre_mesh"`` / ``"pre_solve"`` / ``"post_solve"``
472-
-- deferred to hook scripts the runner invokes via
473-
``$FOAMBO_PRE_INIT``, ``$FOAMBO_PRE_MESH``, etc.
474-
466+
source: Selection strategy (``"best"``, ``"nearest"``, ``"latest"``,
467+
``"baseline"``, ``"by_index"``, ``"matching_group"``, ``"custom"``).
475468
fallback: ``"skip"`` or ``"error"`` when no source trial found.
469+
enabled: Toggle this dependency on/off.
470+
**source_kwargs: Extra fields for the source selector (e.g.
471+
``group="geometry"`` for ``matching_group``,
472+
``similarity_threshold=0.3`` for ``nearest``).
476473
477-
Hook scripts are written to ``.foambo_<phase>.sh`` in the trial case
478-
directory and default to no-op (``true``) when no actions target that
479-
phase or on the first trial when no source exists yet.
480-
481-
Environment variables available to the runner subprocess:
482-
483-
* ``$FOAMBO_PRE_INIT`` / ``$FOAMBO_PRE_MESH`` / ``$FOAMBO_PRE_SOLVE`` / ``$FOAMBO_POST_SOLVE``
484-
-- paths to hook scripts (always set, no-op when empty)
485-
* ``$FOAMBO_CASE_PATH`` / ``$FOAMBO_CASE_NAME`` -- trial case directory
486-
* ``$FOAMBO_SOURCE_TRIAL`` -- resolved source path (empty on first trial)
487-
* ``$FOAMBO_TARGET_TRIAL`` -- current trial path
474+
Resolution results are written to ``.foambo_deps.json`` in the trial
475+
case directory and exposed via ``$FOAMBO_DEPS``. The runner script
476+
reads the manifest to decide what to do.
488477
489478
Example Allrun (shell)::
490479
491480
#!/bin/bash
492-
$FOAMBO_PRE_INIT
493-
blockMesh
494-
$FOAMBO_PRE_SOLVE # e.g. mapFields from source trial
481+
deps="$FOAMBO_DEPS"
482+
if jq -e '.reuse_mesh.resolved' "$deps" >/dev/null 2>&1; then
483+
src=$(jq -r '.reuse_mesh.source_path' "$deps")
484+
cp -rT "$src/constant/polyMesh" constant/polyMesh
485+
else
486+
blockMesh
487+
fi
495488
simpleFoam
496-
$FOAMBO_POST_SOLVE
497-
498-
For non-shell runners, execute the script at the env var path or
499-
directly at ``.foambo_<phase>.sh`` in the case directory::
500-
501-
subprocess.run(os.environ["FOAMBO_PRE_SOLVE"]) # Python
502489
"""
503490
self._dependencies.append({
504491
"name": name,
505492
"enabled": enabled,
506-
"source": {"strategy": source, "fallback": fallback},
507-
"actions": [{"type": "run_command", "command": command, "phase": phase}] if command else [],
493+
"source": {"strategy": source, "fallback": fallback, **source_kwargs},
508494
})
509495
return self
510496

src/foambo/api_server.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -648,8 +648,6 @@ def _parse_trial_deps(runner, trial_index: int, trial) -> list:
648648
"name": name,
649649
"source_index": info["source_trial_index"],
650650
"source_path": info.get("source_case_path", ""),
651-
"actions_applied": info.get("actions_applied", 0),
652-
"phased_actions": info.get("phased_actions", []),
653651
})
654652
return deps
655653

src/foambo/default_config.py

Lines changed: 58 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def _get_doc_models():
111111
SeedDataNodeConfig,
112112
OptimizationOptions, FoamJobRunnerOptions, VariableSubstOptions,
113113
FileSubstOptions, BaselineOptions, StoreOptions,
114-
TrialSelector, TrialAction, TrialDependency,
114+
TrialSelector, TrialDependency,
115115
DimensionalityReductionOptions,
116116
)
117117
from .metrics import FoamJob, LocalJobMetric
@@ -135,7 +135,6 @@ def _get_doc_models():
135135
(StoreOptions, "store"),
136136
(TrialDependency, "trial_dependencies[]"),
137137
(TrialSelector, "trial_dependencies[].source"),
138-
(TrialAction, "trial_dependencies[].actions[]"),
139138
(FoamJob, "internal.FoamJob"),
140139
]
141140

@@ -210,13 +209,6 @@ def get_default_config() -> Dict[str, Any]:
210209
"strategy": "best",
211210
"fallback": "skip",
212211
},
213-
"actions": [
214-
{
215-
"type": "run_command",
216-
"command": "cp -rT $FOAMBO_SOURCE_TRIAL/0.5 $FOAMBO_TARGET_TRIAL/0",
217-
"phase": "immediate",
218-
},
219-
],
220212
},
221213
{
222214
"name": "reuse_mesh",
@@ -226,13 +218,6 @@ def get_default_config() -> Dict[str, Any]:
226218
"group": "geometry",
227219
"fallback": "skip",
228220
},
229-
"actions": [
230-
{
231-
"type": "run_command",
232-
"command": "cp -rT $FOAMBO_SOURCE_TRIAL/constant/polyMesh $FOAMBO_TARGET_TRIAL/constant/polyMesh",
233-
"phase": "pre_mesh",
234-
},
235-
],
236221
},
237222
]
238223

@@ -571,94 +556,83 @@ def get_config_docs() -> Dict[str, Any]:
571556
source:
572557
strategy: best
573558
fallback: skip
574-
actions:
575-
- type: run_command
576-
command: "cp -rT $FOAMBO_SOURCE_TRIAL/0.5 $FOAMBO_TARGET_TRIAL/0"
577-
phase: immediate # default — runs before the runner starts
578559
```
579560
580-
Copy mesh before meshing (deferred to `$FOAMBO_PRE_MESH` hook):
561+
Reuse mesh when geometry parameters haven't changed:
581562
```yaml
582563
trial_dependencies:
583-
- name: mesh_inherit
564+
- name: reuse_mesh
584565
source:
585-
strategy: nearest
566+
strategy: matching_group
567+
group: "geometry"
586568
fallback: skip
587-
actions:
588-
- type: run_command
589-
command: "cp -rT $FOAMBO_SOURCE_TRIAL/constant/polyMesh $FOAMBO_TARGET_TRIAL/constant/polyMesh"
590-
phase: pre_mesh
591-
```
592-
593-
Use mapFields after meshing (deferred to `$FOAMBO_PRE_SOLVE` hook):
594-
```yaml
595-
trial_dependencies:
596-
- name: map_fields
569+
- name: warm_fields
597570
source:
598-
strategy: best
571+
strategy: nearest
572+
similarity_threshold: 0.3
599573
fallback: skip
600-
actions:
601-
- type: run_command
602-
command: "mapFields $FOAMBO_SOURCE_TRIAL -sourceTime latestTime -case $FOAMBO_TARGET_TRIAL"
603-
phase: pre_solve
604-
```
605-
606-
With the corresponding Allrun script calling the hooks:
607-
```bash
608-
#!/bin/bash
609-
$FOAMBO_PRE_INIT # no-op unless configured
610-
blockMesh
611-
$FOAMBO_PRE_MESH # no-op unless configured
612-
$FOAMBO_PRE_SOLVE # runs mapFields from source trial
613-
simpleFoam
614-
$FOAMBO_POST_SOLVE # no-op unless configured
615574
```
616575
617-
**Phases and environment variables:**
618-
- `immediate` (default): action runs before the runner starts
619-
- `pre_init` / `pre_mesh` / `pre_solve` / `post_solve`: action is
620-
written to a hook script (`.foambo_<phase>.sh`) in the case directory
621-
and exposed via `$FOAMBO_<PHASE>` environment variable
622-
623-
Reuse mesh when geometry parameters haven't changed (`matching_group`):
576+
With parameters tagged by group:
624577
```yaml
625578
experiment:
626579
parameters:
627580
- name: angle1
628581
bounds: [20, 40]
629-
groups: ["geometry"] # tag parameters with groups
582+
groups: ["geometry"]
630583
- name: relaxation
631-
bounds: [0.1, 0.9] # no group — changes freely
632-
633-
trial_dependencies:
634-
- name: reuse_mesh
635-
source:
636-
strategy: matching_group
637-
group: "geometry" # match on all params tagged "geometry"
638-
fallback: skip # mesh from scratch if no match
639-
actions:
640-
- type: run_command
641-
command: "cp -rT $FOAMBO_SOURCE_TRIAL/constant/polyMesh $FOAMBO_TARGET_TRIAL/constant/polyMesh"
642-
phase: pre_mesh
584+
bounds: [0.1, 0.9]
643585
```
644-
When the new trial's geometry parameters exactly match a completed trial,
645-
the mesh is copied instead of regenerated. Parameters can belong to
646-
multiple groups (e.g. `groups: ["geometry", "mesh"]`).
647586
648-
All hooks default to no-op on the first trial (no source yet).
587+
**How it works:** foamBO resolves each dependency and writes
588+
`.foambo_deps.json` to the trial case directory. The manifest
589+
path is available via `$FOAMBO_DEPS`. The runner script reads
590+
it and decides what to do:
649591
650-
**Additional env vars available to the runner:**
651-
- `$FOAMBO_CASE_PATH` / `$FOAMBO_CASE_NAME` — trial case directory
652-
- `$FOAMBO_SOURCE_TRIAL` — resolved source path (unset if no match)
653-
- `$FOAMBO_TARGET_TRIAL` — current trial path
592+
```bash
593+
#!/bin/bash
594+
deps="$FOAMBO_DEPS"
595+
596+
# Mesh: reuse or generate
597+
if jq -e '.reuse_mesh.resolved' "$deps" >/dev/null 2>&1; then
598+
src=$(jq -r '.reuse_mesh.source_path' "$deps")
599+
cp -rT "$src/constant/polyMesh" constant/polyMesh
600+
else
601+
blockMesh
602+
fi
603+
604+
# Fields: warm-start or cold start
605+
if jq -e '.warm_fields.resolved' "$deps" >/dev/null 2>&1; then
606+
src=$(jq -r '.warm_fields.source_path' "$deps")
607+
mapFields "$src" -sourceTime latestTime
608+
else
609+
potentialFoam -writep > log.potentialFoam 2>&1
610+
fi
654611
655-
**Non-shell runners** (Python, binary) can execute hook scripts directly:
612+
simpleFoam
613+
```
614+
615+
**Non-shell runners** (Python, binary):
656616
```python
657-
import subprocess, os
658-
subprocess.run(os.environ["FOAMBO_PRE_SOLVE"])
659-
# or: subprocess.run("./.foambo_pre_solve.sh")
617+
import json, os
618+
deps = json.load(open(os.environ["FOAMBO_DEPS"]))
619+
if deps["reuse_mesh"]["resolved"]:
620+
shutil.copytree(deps["reuse_mesh"]["source_path"] + "/constant/polyMesh",
621+
"constant/polyMesh", dirs_exist_ok=True)
622+
```
623+
624+
**Manifest format** (`.foambo_deps.json`):
625+
```json
626+
{
627+
"reuse_mesh": {"resolved": true, "source_trial_index": 3, "source_path": "/path/to/trial_0003"},
628+
"warm_fields": {"resolved": false}
629+
}
660630
```
661631
632+
**Environment variables available to the runner:**
633+
- `$FOAMBO_DEPS` — path to `.foambo_deps.json`
634+
- `$FOAMBO_CASE_PATH` / `$FOAMBO_CASE_NAME` — trial case directory
635+
662636
The dependency resolution result is recorded in
663637
`run_metadata["dependencies"]` for traceability.
664638
""",
@@ -1064,11 +1038,8 @@ def __init__(self, **kwargs):
10641038
.stop(max_trials=50, improvement_bar=0.1)
10651039
.early_stop(type="percentile", metric_names=["residuals"],
10661040
percentile_threshold=25, min_progression=5)
1067-
.depend("warm_start", source="best",
1068-
command="cp -rT $FOAMBO_SOURCE_TRIAL/0.5 $FOAMBO_TARGET_TRIAL/0")
1069-
.depend("map_fields", source="nearest",
1070-
command="mapFields $FOAMBO_SOURCE_TRIAL -case $FOAMBO_TARGET_TRIAL -sourceTime latestTime",
1071-
phase="pre_solve")
1041+
.depend("warm_start", source="best")
1042+
.depend("reuse_mesh", source="matching_group", group="geometry")
10721043
.run(parallelism=3, poll_interval=10, ttl=600)
10731044
)
10741045
predictions = client.predict([{"x": 10, "y": "A"}])
@@ -1114,7 +1085,7 @@ def my_objective(parameters):
11141085
- `.stop(max_trials, improvement_bar)` — global stopping
11151086
- `.early_stop(type, ...)` — trial-level early stopping
11161087
- `.reduce(after_trials, min_importance)` — auto-fix irrelevant parameters
1117-
- `.depend(name, source, command)` — trial-to-trial dependencies
1088+
- `.depend(name, source)` — trial-to-trial dependencies
11181089
- `.preflight(dry_run=True)` — validate config before running
11191090
- `.run(parallelism, ...)` — execute and return the Ax Client
11201091
- `FoamBO.load(name)` — load a saved experiment for analysis

src/foambo/docs_concepts.py

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,10 @@
8282
- ``by_index``: specific trial number
8383
- ``custom``: external command that prints a trial index
8484
85-
**Phases** control when actions execute:
86-
- ``immediate``: before the runner starts (default)
87-
- ``pre_init`` / ``pre_mesh`` / ``pre_solve`` / ``post_solve``: deferred to hook scripts
88-
89-
The runner exposes hook scripts via environment variables (``$FOAMBO_PRE_MESH``, etc.)
90-
that the Allrun script calls at the right moment.""",
85+
foamBO resolves each dependency and writes a ``.foambo_deps.json`` manifest
86+
to the trial case directory. The manifest path is available via ``$FOAMBO_DEPS``.
87+
The runner script reads the manifest and decides what actions to take
88+
(copy mesh, warm-start fields, etc.).""",
9189
},
9290

9391
"concept.early_stopping": {
@@ -407,15 +405,29 @@
407405
trial_dependencies:
408406
- name: reuse_mesh
409407
source: {strategy: matching_group, group: geometry, fallback: skip}
410-
actions: [{type: run_command, phase: pre_mesh,
411-
command: "cp -rT $FOAMBO_SOURCE_TRIAL/constant/polyMesh $FOAMBO_TARGET_TRIAL/constant/polyMesh"}]
412408
- name: warm_fields
413409
source: {strategy: nearest, similarity_threshold: 0.3, fallback: skip}
414-
actions: [{type: run_command, phase: pre_solve,
415-
command: "mapFields $FOAMBO_SOURCE_TRIAL -sourceTime latestTime -case $FOAMBO_TARGET_TRIAL"}]
410+
```
411+
The Allrun reads ``$FOAMBO_DEPS`` to decide what to do:
412+
```bash
413+
deps="$FOAMBO_DEPS"
414+
if jq -e '.reuse_mesh.resolved' "$deps" >/dev/null 2>&1; then
415+
src=$(jq -r '.reuse_mesh.source_path' "$deps")
416+
cp -rT "$src/constant/polyMesh" constant/polyMesh
417+
else
418+
blockMesh
419+
fi
420+
if jq -e '.warm_fields.resolved' "$deps" >/dev/null 2>&1; then
421+
src=$(jq -r '.warm_fields.source_path' "$deps")
422+
mapFields "$src" -sourceTime latestTime
423+
else
424+
potentialFoam -writep > log.potentialFoam 2>&1
425+
fi
426+
simpleFoam
416427
```
417428
This reuses the mesh when geometry matches AND warm-starts solver fields from
418-
the nearest trial — two independent dependencies working together.""",
429+
the nearest trial — two independent dependencies, each with its own resolved
430+
source and its own fallback logic.""",
419431
},
420432

421433
"concept.orchestration": {
@@ -459,13 +471,19 @@
459471
**Allrun integration example (remote runner):**
460472
```bash
461473
#!/bin/bash
462-
blockMesh
463-
$FOAMBO_PRE_MESH
464-
snappyHexMesh
465-
$FOAMBO_PRE_SOLVE
474+
deps="$FOAMBO_DEPS"
475+
476+
# Mesh: reuse or generate
477+
if jq -e '.reuse_mesh.resolved' "$deps" >/dev/null 2>&1; then
478+
src=$(jq -r '.reuse_mesh.source_path' "$deps")
479+
cp -rT "$src/constant/polyMesh" constant/polyMesh
480+
else
481+
blockMesh
482+
snappyHexMesh
483+
fi
484+
466485
simpleFoam
467486
EXIT_CODE=$?
468-
$FOAMBO_POST_SOLVE
469487
470488
# Notify foamBO of completion
471489
curl -s -X POST "$FOAMBO_API_ENDPOINT/trials/$FOAMBO_TRIAL_INDEX/push/status" \\

0 commit comments

Comments
 (0)