@@ -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
8182its output document; condor transfers the output back into ` outputs/ ` .
8283
@@ -93,7 +94,8 @@ print(status)
9394A task is ** done** when its output file exists, loads, and every cell in it
9495succeeded 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
179181scoped 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
186190status = 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+
196219The planner descends only as deep as the budget requires: whole mechanisms
197220are cost-balanced into jobs; a mechanism over budget splits its purview
198221list into ranges; a single (mechanism, purview) pair over budget splits its
199222partition sweep into interleaved strides. System-partition strides for the
200223SIA are planned the same way — unless you pass a precomputed ` sia= ` , or a
201224` resolution_state= ` (in which case the collected structure carries no Φₛ
202225and 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
205250On sparse substrates, pass ` ordering="bottleneck_first" ` so each stride
206251evaluates partitions that sever the fewest present connections first —
@@ -210,8 +255,11 @@ never affects results.
210255Submission, monitoring, and resubmission work exactly as for sweep
211256campaigns. ` collect() ` merges the shards exactly — tie sets preserved,
212257identical 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
217265ces = campaign.collect(" ces-campaign" )
@@ -225,3 +273,24 @@ the full structure (partial structures are exact substructures), and the
225273measured upper bounds on Σφ_r and Φ come from the certified bound
226274machinery. Missing shard groups (from failed or pending tasks collected
227275with ` 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.
0 commit comments