Skip to content

Batch API

Parallel simulation execution with thread-pool parallelism.

simulate_batch

idfkit.simulation.batch.simulate_batch(jobs, *, energyplus=None, max_workers=None, cache=None, progress=None, fs=None, on_progress=None)

Run multiple EnergyPlus simulations in parallel.

Uses ThreadPoolExecutor to dispatch simulations concurrently. Individual job failures are captured as failed SimulationResult entries -- the batch never raises due to a single job failing.

Parameters:

Name Type Description Default
jobs Sequence[SimulationJob]

Sequence of simulation jobs to execute.

required
energyplus EnergyPlusConfig | None

Shared EnergyPlus configuration (auto-discovered if None).

None
max_workers int | None

Maximum number of concurrent simulations. Defaults to min(len(jobs), os.cpu_count() or 1).

None
cache SimulationCache | None

Optional simulation cache for content-hash lookups.

None
progress Callable[..., None] | None

Optional callback invoked after each job completes. Called as progress(completed=N, total=M, label=label, success=bool).

None
fs FileSystem | None

Optional file system backend passed through to each simulate call.

None
on_progress Callable[[SimulationProgress], None] | None

Optional callback invoked with SimulationProgress events during each individual simulation. Events include job_index and job_label to identify which batch job they belong to. The "tqdm" shorthand is not supported for batch runners; use tqdm_progress with a custom per-job callback instead.

None

Returns:

Type Description
BatchResult

A BatchResult with results in the same order as jobs.

Raises:

Type Description
ValueError

If jobs is empty.

Source code in src/idfkit/simulation/batch.py
def simulate_batch(
    jobs: Sequence[SimulationJob],
    *,
    energyplus: EnergyPlusConfig | None = None,
    max_workers: int | None = None,
    cache: SimulationCache | None = None,
    progress: Callable[..., None] | None = None,
    fs: FileSystem | None = None,
    on_progress: Callable[[SimulationProgress], None] | None = None,
) -> BatchResult:
    """Run multiple EnergyPlus simulations in parallel.

    Uses [ThreadPoolExecutor][concurrent.futures.ThreadPoolExecutor] to dispatch
    simulations concurrently.  Individual job failures are captured as
    failed [SimulationResult][idfkit.simulation.result.SimulationResult] entries -- the batch never raises
    due to a single job failing.

    Args:
        jobs: Sequence of simulation jobs to execute.
        energyplus: Shared EnergyPlus configuration (auto-discovered if
            ``None``).
        max_workers: Maximum number of concurrent simulations.  Defaults
            to ``min(len(jobs), os.cpu_count() or 1)``.
        cache: Optional simulation cache for content-hash lookups.
        progress: Optional callback invoked after each job completes.
            Called as ``progress(completed=N, total=M, label=label,
            success=bool)``.
        fs: Optional file system backend passed through to each
            [simulate][idfkit.simulation.runner.simulate] call.
        on_progress: Optional callback invoked with
            [SimulationProgress][idfkit.simulation.progress.SimulationProgress] events
            during each individual simulation.  Events include
            ``job_index`` and ``job_label`` to identify which batch job
            they belong to.  The ``"tqdm"`` shorthand is not supported
            for batch runners; use
            [tqdm_progress][idfkit.simulation.progress_bars.tqdm_progress]
            with a custom per-job callback instead.

    Returns:
        A [BatchResult][idfkit.simulation.batch.BatchResult] with results in the same order as *jobs*.

    Raises:
        ValueError: If *jobs* is empty.
    """
    if not jobs:
        msg = "jobs must not be empty"
        raise ValueError(msg)

    if on_progress == "tqdm":
        msg = (
            'on_progress="tqdm" is not supported for batch simulations because a single '
            "progress bar cannot represent multiple concurrent jobs. Use the tqdm_progress() "
            "context manager with a custom callback instead."
        )
        raise ValueError(msg)

    progress_cb, progress_cleanup = resolve_on_progress(on_progress)

    try:
        if max_workers is None:
            max_workers = min(len(jobs), os.cpu_count() or 1)

        logger.info("Starting batch of %d jobs with %d workers", len(jobs), max_workers)

        results: list[SimulationResult | None] = [None] * len(jobs)
        completed_count = 0
        total = len(jobs)

        start = time.monotonic()

        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            future_to_index = {
                executor.submit(_run_job, idx, job, energyplus, cache, fs, progress_cb): idx
                for idx, job in enumerate(jobs)
            }

            for future in as_completed(future_to_index):
                idx = future_to_index[future]
                job = jobs[idx]
                result = future.result()  # _run_job never raises
                results[idx] = result
                completed_count += 1

                if progress is not None:
                    progress(
                        completed=completed_count,
                        total=total,
                        label=job.label,
                        success=result.success,
                    )
    finally:
        if progress_cleanup is not None:
            progress_cleanup()

    elapsed = time.monotonic() - start

    # All slots filled — assert for type checker
    final: list[SimulationResult] = []
    for r in results:
        assert r is not None  # noqa: S101
        final.append(r)

    batch_result = BatchResult(results=tuple(final), total_runtime_seconds=elapsed)
    logger.info(
        "Batch complete: %d succeeded, %d failed in %.1fs",
        len(batch_result.succeeded),
        len(batch_result.failed),
        elapsed,
    )
    return batch_result

SimulationJob

idfkit.simulation.batch.SimulationJob dataclass

Specification for a single simulation within a batch.

Attributes:

Name Type Description
model object

The EnergyPlus model to simulate.

weather str | Path

Path to the weather file.

label str

Human-readable label for progress reporting.

output_dir str | Path | None

Directory for output files (default: auto temp dir).

expand_objects bool

Run ExpandObjects before simulation.

annual bool

Run annual simulation.

design_day bool

Run design-day-only simulation.

output_prefix str

Prefix for output files.

output_suffix Literal['C', 'L', 'D']

Output file naming suffix ("C", "L", or "D").

readvars bool

Run ReadVarsESO after simulation.

timeout float

Maximum runtime in seconds.

extra_args tuple[str, ...] | None

Additional command-line arguments.

Source code in src/idfkit/simulation/batch.py
@dataclass(frozen=True, slots=True)
class SimulationJob:
    """Specification for a single simulation within a batch.

    Attributes:
        model: The EnergyPlus model to simulate.
        weather: Path to the weather file.
        label: Human-readable label for progress reporting.
        output_dir: Directory for output files (default: auto temp dir).
        expand_objects: Run ExpandObjects before simulation.
        annual: Run annual simulation.
        design_day: Run design-day-only simulation.
        output_prefix: Prefix for output files.
        output_suffix: Output file naming suffix (``"C"``, ``"L"``, or ``"D"``).
        readvars: Run ReadVarsESO after simulation.
        timeout: Maximum runtime in seconds.
        extra_args: Additional command-line arguments.
    """

    model: object  # IDFDocument — use object to avoid import at class level
    weather: str | Path
    label: str = ""
    output_dir: str | Path | None = None
    expand_objects: bool = True
    annual: bool = False
    design_day: bool = False
    output_prefix: str = "eplus"
    output_suffix: Literal["C", "L", "D"] = "C"
    readvars: bool = False
    timeout: float = 3600.0
    extra_args: tuple[str, ...] | None = None

model instance-attribute

weather instance-attribute

label = '' class-attribute instance-attribute

output_dir = None class-attribute instance-attribute

expand_objects = True class-attribute instance-attribute

annual = False class-attribute instance-attribute

design_day = False class-attribute instance-attribute

output_prefix = 'eplus' class-attribute instance-attribute

output_suffix = 'C' class-attribute instance-attribute

readvars = False class-attribute instance-attribute

timeout = 3600.0 class-attribute instance-attribute

extra_args = None class-attribute instance-attribute

BatchResult

idfkit.simulation.batch.BatchResult dataclass

Aggregated results from a batch simulation run.

Attributes:

Name Type Description
results tuple[SimulationResult, ...]

Simulation results in the same order as the input jobs.

total_runtime_seconds float

Wall-clock time for the entire batch.

Source code in src/idfkit/simulation/batch.py
@dataclass(frozen=True, slots=True)
class BatchResult:
    """Aggregated results from a batch simulation run.

    Attributes:
        results: Simulation results in the same order as the input jobs.
        total_runtime_seconds: Wall-clock time for the entire batch.
    """

    results: tuple[SimulationResult, ...]
    total_runtime_seconds: float

    @property
    def succeeded(self) -> tuple[SimulationResult, ...]:
        """Results that completed successfully."""
        return tuple(r for r in self.results if r.success)

    @property
    def failed(self) -> tuple[SimulationResult, ...]:
        """Results that failed."""
        return tuple(r for r in self.results if not r.success)

    @property
    def all_succeeded(self) -> bool:
        """Whether every job in the batch succeeded."""
        return all(r.success for r in self.results)

    def __len__(self) -> int:
        return len(self.results)

    def __getitem__(self, index: int) -> SimulationResult:
        return self.results[index]

results instance-attribute

total_runtime_seconds instance-attribute

succeeded property

Results that completed successfully.

failed property

Results that failed.

all_succeeded property

Whether every job in the batch succeeded.