Skip to content

Commit a69f6af

Browse files
wmaynerclaude
andcommitted
Pad task_id in the campaign submit template to match task filenames
Task files are written zero-padded (task-{task_id:04d}.json.gz) and collect() reads them padded, but the generated submit file referenced the unpadded $(task_id), so every job failed its input-file transfer on HTCondor (looking for task-0.json.gz, not task-0000.json.gz); the output remap would likewise have missed the runner's padded output. The local runner pads correctly, so this only surfaced on real condor submission. Fill the filenames from a {pad} field set to HTCondor's $INT(task_id,%04d), which pads from the unpadded task_id column in remaining.txt that status regenerates unpadded — so the submit file stays correct across resubmits. Regression test emulates the macro expansion and asserts every submit-referenced filename matches an on-disk task file (red on the bare macro, green with the fix). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NafThGCY6Zrxo8s3Pdf6fV
1 parent 2487a95 commit a69f6af

2 files changed

Lines changed: 79 additions & 6 deletions

File tree

pyphi/campaign/__init__.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -278,21 +278,30 @@ def _pack(
278278
return [sorted(b) for b in bins]
279279

280280

281+
# Task files are written zero-padded (``task-{task_id:04d}.json.gz``), and
282+
# collection reads them back with the same padding, so every filename the
283+
# scheduler forms from ``task_id`` must pad identically. ``{pad}`` is filled
284+
# with HTCondor's ``$INT(task_id,%04d)``, which pads from the unpadded
285+
# ``task_id`` column in ``remaining.txt`` (which ``status`` regenerates
286+
# unpadded), keeping the submit file correct across resubmits. A bare
287+
# ``$(task_id)`` here would expand to ``task-0.json.gz`` and every job would
288+
# fail its input-file transfer.
289+
_TASK_ID_PAD = "$INT(task_id,%04d)"
281290
_SUBMIT_TEMPLATE = """\
282291
universe = container
283292
container_image = {container_image}
284293
executable = run_task.sh
285-
arguments = $(task_id)
286-
transfer_input_files = tasks/task-$(task_id).json.gz, substrates/
287-
transfer_output_remaps = "task-$(task_id).json.gz = outputs/task-$(task_id).json.gz"
294+
arguments = {pad}
295+
transfer_input_files = tasks/task-{pad}.json.gz, substrates/
296+
transfer_output_remaps = "task-{pad}.json.gz = outputs/task-{pad}.json.gz"
288297
should_transfer_files = YES
289298
when_to_transfer_output = ON_EXIT_OR_EVICT
290299
request_cpus = 1
291300
request_memory = $(memory)
292301
request_disk = {request_disk}
293-
log = logs/task-$(task_id).log
294-
output = logs/task-$(task_id).out
295-
error = logs/task-$(task_id).err
302+
log = logs/task-{pad}.log
303+
output = logs/task-{pad}.out
304+
error = logs/task-{pad}.err
296305
queue task_id, memory from remaining.txt
297306
"""
298307

@@ -508,6 +517,7 @@ def _write_campaign_scaffold(
508517
_SUBMIT_TEMPLATE.format(
509518
container_image=container_image,
510519
request_disk=request_disk,
520+
pad=_TASK_ID_PAD,
511521
)
512522
)
513523

test/campaign/test_prepare_ces.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,3 +321,66 @@ def test_state_keyed_mapping_requires_singleton_axes(tmp_path):
321321
units_per_job=50.0,
322322
resolution_state=resolution,
323323
)
324+
325+
326+
def _expand_condor_macros(pattern: str, task_id: str) -> str:
327+
"""Emulate HTCondor's expansion of the macros the submit template uses.
328+
329+
``$INT(task_id,%0Nd)`` -> the task id zero-padded to N digits;
330+
``$(task_id)`` -> the raw id. Enough to reconstruct the filename the
331+
scheduler would transfer for a given ``remaining.txt`` row.
332+
"""
333+
import re
334+
335+
pattern = re.sub(
336+
r"\$INT\(task_id,\s*%0(\d+)d\)",
337+
lambda m: str(int(task_id)).zfill(int(m.group(1))),
338+
pattern,
339+
)
340+
return pattern.replace("$(task_id)", task_id)
341+
342+
343+
def test_submit_filenames_match_padded_task_files(tmp_path):
344+
"""Every input file the submit template references for a task must be the
345+
zero-padded name that ``prepare`` actually wrote (regression: a bare
346+
``$(task_id)`` expanded to ``task-0.json.gz`` and held every job)."""
347+
directory = tmp_path / "camp"
348+
prepare_ces(
349+
examples.basic_substrate(),
350+
states=BASIC_STATE,
351+
formalisms="IIT_4_0_2026",
352+
directory=directory,
353+
units_per_job=50.0,
354+
)
355+
import re
356+
357+
submit = (directory / "pyphi.sub").read_text()
358+
# The value can hold commas inside ``$INT(task_id,%04d)``, so match the
359+
# ``tasks/...json.gz`` token directly instead of splitting on commas.
360+
input_pattern = next(
361+
re.search(r"tasks/\S*\.json\.gz", line).group(0)
362+
for line in submit.splitlines()
363+
if line.strip().startswith("transfer_input_files")
364+
)
365+
remap_lhs = next(
366+
line.split("=", 1)[1].split("=", 1)[0].strip().strip('"')
367+
for line in submit.splitlines()
368+
if line.strip().startswith("transfer_output_remaps")
369+
)
370+
task_ids = [
371+
line.split(",", 1)[0].strip()
372+
for line in (directory / "remaining.txt").read_text().splitlines()
373+
if line.strip()
374+
]
375+
assert task_ids
376+
for task_id in task_ids:
377+
# The file the scheduler transfers in must be the one on disk.
378+
transferred = _expand_condor_macros(input_pattern, task_id)
379+
assert (directory / transferred).exists(), (
380+
f"submit references {transferred} but it does not exist"
381+
)
382+
# The remap's source name must match what the runner writes back
383+
# (``task-{task_id:04d}.json.gz``), or output transfer silently fails.
384+
assert _expand_condor_macros(remap_lhs, task_id) == (
385+
f"task-{int(task_id):04d}.json.gz"
386+
)

0 commit comments

Comments
 (0)