Skip to content

k4bench.runner.ddsim

k4bench.runner.ddsim

Execute a single ddsim run and return a :class:RunResult.

Design principle

This module owns instrumentation: timing, logging, and metrics extraction. It does not own physics configuration. The caller decides which ddsim arguments to pass; the runner adds the ones it manages, times the run with :mod:k4bench.runner.process, and harvests the results (:mod:k4bench.runner.result).

The only ddsim arguments that the runner needs to know about are:

  • --compactFile — to allow per-run XML patching (geometry sweep)
  • --numberOfEvents — to compute events/sec
  • --outputFile — to measure the output file and its events

Everything else (--enableGun, --gun.particle, --runType, steering files, …) is passed through verbatim via extra_args.

Per-event timing

When available, the k4Bench C++ timing plugin is loaded automatically as a DDG4 event action. The plugin writes per-event timing metrics to JSON files inside the log directory. Per-event arrays stay in these profiling artifacts. The plugin's run-level virtual-memory peak is copied into :class:RunResult.

Event size and source

After a successful run, what the output file holds on disk, collection by collection, is written to <label>_event_size.json in the log directory (:mod:k4bench.analysis.event_size), and what each event holds too when the caller asks for it. Given a source path, the events' generator particles are written there as well, from the output file before the next run replaces it (:mod:k4bench.analysis.event_source).

run_ddsim

run_ddsim(*, xml_path: Path, label: str, n_events: int, output_file: Path, log_dir: Path, setup_script: Path | None = None, extra_args: list[str] | None = None, verbose: bool = False, per_event_content: bool = False, source_path: Path | None = None) -> RunResult

Run ddsim for one geometry configuration and return collected metrics.

The runner injects --compactFile, --numberOfEvents, and --outputFile automatically. All other ddsim options should be supplied via extra_args.

Parameters:

Name Type Description Default
xml_path Path

Compact XML file passed to --compactFile.

required
label str

Human-readable name for this run. Used as the log filename stem and stored in :attr:RunResult.label.

required
n_events int

Number of events; passed to --numberOfEvents and used to compute :attr:RunResult.events_per_sec.

required
output_file Path

EDM4hep ROOT output path passed to --outputFile. Its size, and what its events hold, are recorded after the run.

required
log_dir Path

Directory where <label>.log is written.

required
setup_script Path | None

Optional shell script sourced before ddsim.

None
extra_args list[str] | None

Additional ddsim arguments passed through verbatim.

None
verbose bool

Stream ddsim output live to stdout.

False
per_event_content bool

Also record what each event of the output file holds, collection by collection, in its event size record.

False
source_path Path | None

Where to write the events' generator particles, read from the output file of a successful run (event_source.json.gz); None writes none.

None

Returns:

Type Description
RunResult

Process-level timing, memory, and throughput metrics.

Source code in k4bench/runner/ddsim.py
def run_ddsim(
    *,
    xml_path: Path,
    label: str,
    n_events: int,
    output_file: Path,
    log_dir: Path,
    setup_script: Path | None = None,
    extra_args: list[str] | None = None,
    verbose: bool = False,
    per_event_content: bool = False,
    source_path: Path | None = None,
) -> RunResult:
    """Run ddsim for one geometry configuration and return collected metrics.

    The runner injects ``--compactFile``, ``--numberOfEvents``, and
    ``--outputFile`` automatically. All other ddsim options should be
    supplied via *extra_args*.

    Parameters
    ----------
    xml_path:
        Compact XML file passed to ``--compactFile``.

    label:
        Human-readable name for this run. Used as the log filename stem
        and stored in :attr:`RunResult.label`.

    n_events:
        Number of events; passed to ``--numberOfEvents`` and used to
        compute :attr:`RunResult.events_per_sec`.

    output_file:
        EDM4hep ROOT output path passed to ``--outputFile``.
        Its size, and what its events hold, are recorded after the run.

    log_dir:
        Directory where ``<label>.log`` is written.

    setup_script:
        Optional shell script sourced before ddsim.

    extra_args:
        Additional ddsim arguments passed through verbatim.

    verbose:
        Stream ddsim output live to stdout.

    per_event_content:
        Also record what each event of the output file holds, collection by
        collection, in its event size record.

    source_path:
        Where to write the events' generator particles, read from the output
        file of a successful run (``event_source.json.gz``); ``None`` writes
        none.

    Returns
    -------
    RunResult
        Process-level timing, memory, and throughput metrics.
    """
    log_dir.mkdir(parents=True, exist_ok=True)

    # Optional plugin output artifacts. Removed first so a run that fails to
    # write its own is never credited with a previous run's.
    event_json_path = label_path(log_dir, label, EVENTS_SUFFIX)
    event_json_path.unlink(missing_ok=True)
    region_json_path = label_path(log_dir, label, REGIONS_SUFFIX)
    region_json_path.unlink(missing_ok=True)
    event_size_path = label_path(log_dir, label, EVENT_SIZE_SUFFIX)
    event_size_path.unlink(missing_ok=True)
    # The output file is reused across runs: removed too, so a run that writes
    # none is never measured with a previous run's.
    output_file.unlink(missing_ok=True)
    if source_path is not None:
        source_path.unlink(missing_ok=True)

    env = os.environ.copy()

    plugin_available = setup_plugin_environment(
        env=env,
        event_json_path=event_json_path,
        region_json_path=region_json_path,
    )

    cmd = _build_command(
        xml_path=xml_path,
        n_events=n_events,
        output_file=output_file,
        setup_script=setup_script,
        extra_args=extra_args,
        plugin_available=plugin_available,
    )

    proc = run_timed(
        cmd,
        env=env,
        log_path=label_path(log_dir, label, LOG_SUFFIX),
        verbose=verbose,
    )

    output_size_mb: float | None = None

    if output_file.exists():
        output_size_mb = output_file.stat().st_size / 1024**2
        if proc.returncode == 0:
            record_event_size(
                event_size_path, {output_file.name: output_file}, per_event=per_event_content,
            )
            if source_path is not None:
                record_event_source(source_path, output_file, n_events=n_events, label=label)

    return run_result(
        label=label,
        n_events=n_events,
        proc=proc,
        event_json_path=event_json_path,
        output_size_mb=output_size_mb,
        instrumentation="timing-plugin" if plugin_available else None,
    )