|
1 | | -# Code design style |
| 1 | +# Code design |
2 | 2 |
|
3 | | -## Configuration validation and reporting of possible flags: |
| 3 | +foamBO = Ax + pydantic v2 + foamlib + FastAPI. |
4 | 4 |
|
5 | | -## Motivation and Effects |
| 5 | +## Config tree |
6 | 6 |
|
7 | | -We take a banana-trick-like approach for user convenience when creating configuration files. |
8 | | -The goal is to make it easy for users and LLMs to reach a working configuration with trial-and-error. |
| 7 | +Root model: `FoamBOConfig` in `orchestrate.py`. Sub-models validated via `@field_validator`. |
| 8 | +All inherit `FoamBOBaseModel`. Field defaults + `examples=[...]` harvested by `default_config.py::get_default_config()`. |
9 | 9 |
|
10 | | -To start, we get a missing top-level key error if it's not there yet. Let's take `orchestration_settings` as an example: |
| 10 | +## Subsystems |
11 | 11 |
|
12 | | -```shell |
13 | | -omegaconf.errors.ConfigKeyError: Missing key orchestration_settings |
14 | | - full_key: orchestration_settings |
15 | | - object_type=dict |
16 | | -``` |
| 12 | +| File | Job | |
| 13 | +|---|---| |
| 14 | +| `orchestrate.py` | pydantic models (config tree) | |
| 15 | +| `optimize.py` | CLI entry, Ax client lifecycle | |
| 16 | +| `api.py` | `FoamBO` fluent builder | |
| 17 | +| `api_server.py` | FastAPI dashboard + REST + config builder | |
| 18 | +| `preflight.py` | static checks, dry-run | |
| 19 | +| `metrics.py` | `FoamJob`, `LocalJobMetric` | |
| 20 | +| `default_config.py` | defaults + `get_config_docs()` harvest | |
| 21 | +| `docs_concepts.py` | long-form concept entries | |
| 22 | +| `archive.py` | trial archival | |
| 23 | +| `analysis.py` | post-hoc analysis | |
| 24 | +| `config_upgrade.py` | version migration | |
17 | 25 |
|
18 | | -If we then assume users don't know what parameters to set in the `orchestration_settings` sub-dict, |
19 | | -figuring it out is as easy as setting: |
| 26 | +## Orchestration |
20 | 27 |
|
21 | | -```yaml |
22 | | -orchestration_settings: null |
23 | | -``` |
| 28 | +foamBO uses Ax's native `Orchestrator` (poll-based). Harvest latency is |
| 29 | +bounded by `init_seconds_between_polls` in the scheduler options. |
24 | 30 |
|
25 | | -```shell |
26 | | -ValueError: Got an empty configuration for OrchestratorOptions.__init__, but expecting fields |
27 | | -['max_trials', 'parallelism', 'global_stopping_strategy', 'tolerated_trial_failure_rate=0.5', 'initial_seconds_between_polls=0.1'] |
28 | | -``` |
| 31 | +`FoamJobRunner.poll_trial` consumes a push queue at the top of each poll |
| 32 | +cycle so remote runners (SLURM, SSH) can notify completion via HTTP POST: |
29 | 33 |
|
30 | | -> Note that defaulted arguments are optional, but it's convenient to be able to see the active defaults. |
| 34 | +- `POST /api/v1/trials/{idx}/push/status` — writes `_state.trial_status_overrides[idx]` |
| 35 | +- `POST /api/v1/trials/{idx}/push/metrics` — appends to `_state.trial_pushed_metrics[idx]` |
| 36 | +- `POST /api/v1/trials/{idx}/push/heartbeat` — liveness only |
| 37 | +- `GET /api/v1/events` — dashboard-facing event log |
31 | 38 |
|
32 | | -This carries on recursively through the configuration tree. For example, the following configuration: |
33 | | -```yaml |
34 | | -# tolerated_trial_failure_rate and initial_seconds_between_polls are defaulted |
35 | | -orchestration_settings: |
36 | | - max_trials: 30 |
37 | | - parallelism: 4 |
38 | | - global_stopping_strategy: null |
39 | | -``` |
40 | | -will result in the following error: |
41 | | -```shell |
42 | | -TypeError: OrchestratorOptions.create_global_stopping_strategy() got unexpected arguments from global_stopping_strategy sub-dict. |
43 | | -Provided: empty configuration |
44 | | -Expected: ['min_trials', 'window_size', 'improvement_bar'] |
45 | | -``` |
| 39 | +Env vars injected into every trial subprocess: |
| 40 | +`FOAMBO_API_ENDPOINT`, `FOAMBO_TRIAL_INDEX`, `FOAMBO_SESSION_ID`. |
46 | 41 |
|
47 | | -`src/foambo/common.py` defines some utilities to create objects straight from dictionaries and |
48 | | -ensure arguments are validated properly. Two usage patterns are common: |
| 42 | +Session-ID validation rejects pushes from stale jobs of prior crashed runs (HTTP 409). |
49 | 43 |
|
| 44 | +## Key subsystems |
50 | 45 |
|
51 | | -### Validate dictionary entries as function arguments |
| 46 | +- **Trial dependencies** — `TrialDependency` / `TrialSelector` / `TrialAction`. Phases: `immediate`, `pre_init`, `pre_mesh`, `pre_solve`, `post_solve`. Hook scripts exposed via `$FOAMBO_PRE_MESH` etc. |
| 47 | +- **Dimensionality reduction** — `DimensionalityReductionOptions` inside `ConfigOrchestratorOptions`. Sobol-based param freezing after N trials. |
| 48 | +- **Parameter groups** — `groups: [...]` tag on each param. Feeds group sensitivity, `matching_group` dep strategy, group-frozen sweeps, what-if in Predict tab. |
| 49 | +- **Config builder UI** — `templates/config_builder.{html,js,css}`. Pico CSS + Alpine. Endpoints: `/config-builder`, `/api/v1/config/{schema,docs,validate,preflight}`. |
52 | 50 |
|
53 | | -```python |
54 | | -# Will throw an exception if generation_config is missing required |
55 | | -# keys for it to unpack to client.configure_generation_strategy arguments |
56 | | -validate_args(client.configure_generation_strategy, generation_config) |
57 | | -client.configure_generation_strategy(**generation_config) |
58 | | -``` |
| 51 | +## Docs pipeline |
59 | 52 |
|
60 | | -### Create objects with validation for nested fields creation |
| 53 | +`default_config.py::get_config_docs()` merges: |
61 | 54 |
|
62 | | -```python |
63 | | -@dataclass |
64 | | -class MyType(): |
65 | | - setting: int |
66 | | - nested_field: AnotherType |
| 55 | +1. Pydantic Field descriptions (`harvest_docs` on `_get_doc_models()`) |
| 56 | +2. Manual YAML examples (`_examples` dict) |
| 57 | +3. `docs_concepts.py::CONCEPTS` — long-form markdown |
| 58 | +4. `load_tutorial_docs()` — `docs/*.md` summaries |
67 | 59 |
|
68 | | - __nested_fields__ : { |
69 | | - "nested_field": "create_nested_field" |
70 | | - } |
| 60 | +Served at `GET /api/v1/config/docs` for the config builder tooltips + Concepts modal. TUI browses same data via `foamBO --docs`. |
71 | 61 |
|
72 | | - @classmethod |
73 | | - def create_nested_field(cls, arg1: int, arg2: float): |
74 | | - return AnotherType(arg1=arg1, arg2=arg2) |
| 62 | +## Contributing |
75 | 63 |
|
76 | | -obj = instantiate_with_nested_fields(MyType, cfg['my_type']) |
77 | | -``` |
78 | | - |
79 | | -This then can be helpful in creating documentation entities for each configuration entry. |
| 64 | +1. Add/modify pydantic model. Use `Field(description=..., examples=[...])` — both are harvested. |
| 65 | +2. If it's a new root section, add to `FoamBOConfig` + `_get_root_models()` + `_get_doc_models()`. |
| 66 | +3. For cross-cutting examples, drop an entry in `_examples` in `default_config.py`. |
| 67 | +4. For concept-level docs, add to `CONCEPTS` in `docs_concepts.py`. |
| 68 | +5. Run `foamBO --preflight <cfg>` on changed configs before commit. |
0 commit comments