Skip to content

Commit c2229d2

Browse files
wmaynerclaude
andcommitted
Merge campaign-followups: scoped-CES campaign follow-ups
limit= threading with a single planning walk, order-dependent purview caps via CESScope.purview_axis, memory-aware shard sizing with stratified packing and per-task request_memory, the scoped multi-cell sweep (substrates/states/subsets/formalisms under one scope, collected as a SweepResult), SIA short-circuit handling in shards, and the fat-node crossover documentation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQE3L1ewZHCnsQh6gwsUVM
2 parents 66251be + 9c14847 commit c2229d2

26 files changed

Lines changed: 1337 additions & 360 deletions
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The campaigns how-to describes when one node with native parallelism beats a sharded campaign.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added `limit=` to `pyphi.campaign.prepare_ces`, bounding the planning walk for large scoped systems.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Campaign tasks now request memory individually: shard requests are estimated from their largest purview repertoire, packed within memory classes, and floored by `request_memory`.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
`pyphi.campaign.prepare_ces` sweeps substrates × states × subsets × formalisms under one shared scope; multi-cell campaigns collect into a `SweepResult`.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
`CESScope` accepts `max_purview_order_by_mechanism_order`, an explicit table bounding purview order per mechanism order, applied identically in planning, cost estimation, execution, and collection.

docs/howto/campaigns.md

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ my-campaign/
5757
tasks/ one task file per condor job
5858
outputs/ filled in by the jobs
5959
logs/ condor stdout/err/log
60-
remaining.txt task ids not yet done
60+
remaining.txt task id, memory rows not yet done
6161
run_task.sh the wrapper each job executes
6262
pyphi.sub the generated submit file
6363
```
@@ -76,7 +76,8 @@ container section of {doc}`chtc`; the image path can be customized with
7676
$ condor_submit pyphi.sub
7777
```
7878

79-
The submit file queues one job per task id in `remaining.txt`. Each job runs
79+
The submit file queues one job per `task id, memory` row in `remaining.txt`,
80+
requesting each task's own memory. Each job runs
8081
`run_task.sh` inside the container, which executes the task file and writes
8182
its output document; condor transfers the output back into `outputs/`.
8283

@@ -93,7 +94,8 @@ print(status)
9394
A task is **done** when its output file exists, loads, and every cell in it
9495
succeeded or was skipped; **failed** when the output records an error;
9596
**pending** when there is no output yet. `status` rewrites `remaining.txt`
96-
with the failed and pending ids, so resubmission is simply:
97+
with the failed and pending rows (memory column included), so resubmission is
98+
simply:
9799

98100
```console
99101
$ condor_submit pyphi.sub
@@ -178,29 +180,72 @@ scoped — a partial sweep would silently change φ.
178180
`pyphi.estimate_analysis(substrate, compute="ces", scope=scope)` prices the
179181
scoped workload before you commit to it.
180182

181-
## Distribute one system's cause-effect structure
183+
## Distribute scoped cause-effect structures
182184

183-
`prepare_ces` turns one system's scoped analysis into a campaign:
185+
`prepare_ces` turns a scoped analysis into a campaign. It takes the same
186+
axes as `prepare` — substrates, states, subsets, formalisms, with scalars
187+
accepted anywhere — all sharing one scope:
184188

185189
```python
186190
status = campaign.prepare_ces(
187191
substrate,
188-
state=(1, 0, 0),
192+
states=(1, 0, 0),
189193
scope=scope,
190194
directory="ces-campaign",
191195
units_per_job=1e6,
192196
seed=42,
193197
)
194198
```
195199

200+
A sweep over many states (or substrates, or formalisms) under the same
201+
scope is one campaign directory rather than many:
202+
203+
```python
204+
status = campaign.prepare_ces(
205+
substrate,
206+
states=[(1, 0, 0), (1, 1, 0), (1, 0, 1)],
207+
scope=scope,
208+
directory="ces-sweep-campaign",
209+
units_per_job=1e6,
210+
seed=42,
211+
)
212+
```
213+
214+
The shard plan depends only on the substrate, subset, and formalism — not
215+
the state — so cells differing only by state share one planning pass and
216+
replicate its shard tasks. For large scoped systems whose planning walk
217+
exceeds the default work budget, raise `limit=`.
218+
196219
The planner descends only as deep as the budget requires: whole mechanisms
197220
are cost-balanced into jobs; a mechanism over budget splits its purview
198221
list into ranges; a single (mechanism, purview) pair over budget splits its
199222
partition sweep into interleaved strides. System-partition strides for the
200223
SIA are planned the same way — unless you pass a precomputed `sia=`, or a
201224
`resolution_state=` (in which case the collected structure carries no Φₛ
202225
and congruence resolves against the given state, or the system's
203-
intrinsic-information state by default).
226+
intrinsic-information state by default); both apply to single-cell
227+
campaigns only.
228+
229+
Every shard requests memory sized to the largest purview repertoire it
230+
holds, and packing groups purviews by memory class, so small work never
231+
occupies a big-memory slot. `request_memory=` is the floor under those
232+
estimates (default `"4GB"`); a large floor effectively opts out of
233+
stratification.
234+
235+
When purview size should track mechanism size, give the scope an explicit
236+
order table instead of one permissive fixed cap:
237+
238+
```python
239+
scope = CESScope(
240+
mechanisms=AxisScope(max_order=5),
241+
max_purview_order_by_mechanism_order=(
242+
(1, 3), (2, 5), (3, 7), (4, 9), (5, 11),
243+
),
244+
)
245+
```
246+
247+
Mechanism orders absent from the table fall back to the static purview
248+
axes alone.
204249

205250
On sparse substrates, pass `ordering="bottleneck_first"` so each stride
206251
evaluates partitions that sever the fewest present connections first —
@@ -210,8 +255,11 @@ never affects results.
210255
Submission, monitoring, and resubmission work exactly as for sweep
211256
campaigns. `collect()` merges the shards exactly — tie sets preserved,
212257
identical to what a single machine would have produced over the same
213-
scope — and assembles the `CauseEffectStructure` through the standard
214-
analysis path:
258+
scope — and assembles each cell's `CauseEffectStructure` through the
259+
standard analysis path. A single-cell campaign returns the structure; a
260+
multi-cell campaign returns the same `SweepResult` a whole-cell
261+
`compute="ces"` sweep produces, and `scope_report` returns one report per
262+
cell keyed by `(label, formalism, subset, state)`:
215263

216264
```python
217265
ces = campaign.collect("ces-campaign")
@@ -225,3 +273,24 @@ the full structure (partial structures are exact substructures), and the
225273
measured upper bounds on Σφ_r and Φ come from the certified bound
226274
machinery. Missing shard groups (from failed or pending tasks collected
227275
with `partial=True`) are listed separately from scope exclusions.
276+
277+
## When one fat node beats a sharded campaign
278+
279+
For a sparse, scoped, mid-size system (tens of units, mechanism order
280+
capped), the alternative to sharding is one job per state that runs the
281+
whole scoped analysis with native parallelism: `request_cpus = 32`,
282+
`request_memory` sized to the analysis, and `pyphi.config.parallel`
283+
enabled. Prefer the fat-node pattern when:
284+
285+
- the shard count at your budget is large (thousands) while a single
286+
state's whole analysis fits comfortably in one slot's memory and a
287+
72-hour window — per-shard scheduling overhead then dominates; or
288+
- most shards' memory requests approach the whole-analysis footprint
289+
anyway (peak memory is set by the largest purview repertoire, which
290+
sharding cannot reduce).
291+
292+
Prefer sharding when a single state cannot finish in one slot, when
293+
big-memory slots are scarce (stratified shard requests keep small work in
294+
small slots), or when you need per-shard retry granularity on a busy
295+
pool. Per-shard memory requests are estimated automatically, so holds
296+
from underestimated memory are no longer the deciding factor.

docs/howto/chtc.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,11 @@ saved results afterwards.
6363
ready-to-submit directory (task files, substrates, submit file, cost-balanced
6464
packing), and `status`/`collect` handle monitoring, resubmission, and
6565
reassembly into the exact local-sweep result; `prepare_ces` does the same
66-
for one system's scoped cause-effect analysis. Everything below the campaign
67-
workflow is generated for you.
66+
for scoped cause-effect analyses (one system, or a sweep of states and
67+
substrates under one scope). Everything below the campaign workflow is
68+
generated for you. For sparse scoped systems of moderate size, one fat node
69+
per state can beat sharding; see the crossover criteria in
70+
{doc}`campaigns`.
6871

6972
For workloads campaigns don't express — heterogeneous per-job logic,
7073
non-PyPhi pipeline stages, DAGMan workflows — write your own submit file

0 commit comments

Comments
 (0)