campaign_layout - size a heterogeneous campaign and emit the exact commands.
WHY THIS EXISTS
---------------
run_ensemble already takes --weights (Sainte-Lague, interleaved). What it does
NOT do is tell you what the weights should be, and getting them wrong is not
cosmetic: the v2 campaign split 1104 rows evenly over four machines whose real
throughput differed by ~3x, so the fast machines idled while the slow one held
a quarter of the design. Worse, --weights MUST be byte-identical on every
participant -- a typo on one machine silently gives two machines the same rows
and leaves a hole somewhere else.
So the layout is computed ONCE, from measured throughput, written to
campaign_layout.json, and every participant is handed a command line generated
from that one file.
THROUGHPUT, NOT WALL TIME PER ROW
---------------------------------
The obvious measure -- mean wall_s per row -- is wrong under oversubscription.
A machine running 16 workers on 4 cores shows a 4x inflated wall_s per row while
its throughput is unchanged, because wall_s is elapsed time, not CPU time. The
v2 pool shows exactly this ambiguity: MATRIX-PC2 looks 12-15x slower than the
Mac on the same three design rows, but only 2.7x slower per row averaged over
its whole set. Those cannot both be capacity.
The measure that is invariant to oversubscription is
throughput = rows_completed / session_wall_s [rows/second]
which is what this module uses, read from each machine's manifest. A machine
with no manifest cannot be measured and must declare its capacity instead --
loudly, so an assumption is never mistaken for a measurement.
python campaign_layout.py --results results/ \
--declare hoffman2=64 --declare MATRIX-PC2=4