How to run batch simulations¶
The simulate_batch() function runs multiple EnergyPlus simulations in
parallel using a thread pool, ideal for parametric studies and sensitivity
analyses.
Python only, permanently
Running a locally installed EnergyPlus and reading its results belongs to Python alone. JavaScript is not waiting on a port and will not gain a counterpart: this is a permanent boundary, so no issue tracks it, and moving the capability out of that state takes a constitutional amendment rather than a ledger edit.
Requires an EnergyPlus installation on the machine and a subprocess to drive it, then
reads the eplusout files that run leaves on disk. Neither the installation nor the
subprocess is available in a browser, which is the runtime the JavaScript library
targets. JavaScript reaches EnergyPlus by the other mechanism instead: see
browser-simulation, which is not a
workaround for this entry but a different capability.
The full entry, including the vocabulary this capability owns, is on the capability parity page.
Basic Usage¶
from idfkit.simulation import simulate_batch, SimulationJob
# Create jobs
jobs = [
SimulationJob(model=model1, weather="weather.epw", label="baseline"),
SimulationJob(model=model2, weather="weather.epw", label="improved"),
]
# Run in parallel
batch = simulate_batch(jobs, max_workers=4)
print(f"Completed: {len(batch.succeeded)}/{len(batch)}")
for i, result in enumerate(batch):
print(f" Job {i}: {'Success' if result.success else 'Failed'}")
SimulationJob¶
Define individual simulations with SimulationJob:
from idfkit.simulation import SimulationJob
job = SimulationJob(
model=my_model, # Required: IDFDocument
weather="weather.epw", # Required: Path to weather file
label="case-001", # Optional: Label for progress reporting
output_dir="./output/case1", # Optional: Output directory
design_day=True, # Optional: Design-day-only
annual=False, # Optional: Annual simulation
timeout=3600.0, # Optional: Max runtime in seconds
)
SimulationJob Attributes¶
Every field of SimulationJob, with its type and its default, is in the API reference. It is generated from the source, so it cannot fall behind the way the table that used to sit here did.
Parametric Studies¶
Create model variants for parametric analysis:
from idfkit.simulation import simulate_batch, SimulationJob
# Create variants
jobs = []
for insulation in [0.05, 0.10, 0.15, 0.20]:
variant = model.copy()
variant["Material"]["Insulation"].thickness = insulation
jobs.append(
SimulationJob(
model=variant,
weather="weather.epw",
label=f"insulation-{insulation}m",
design_day=True,
)
)
# Run all variants
batch = simulate_batch(jobs, max_workers=4)
# Analyze results
for job, result in zip(jobs, batch):
if result.success:
ts = result.sql.get_timeseries(
"Zone Mean Air Temperature",
"ZONE 1",
)
print(f"{job.label}: Max temp {max(ts.values):.1f}°C")
BatchResult¶
The BatchResult class aggregates results:
batch = simulate_batch(jobs)
# Access results
for result in batch: # A BatchResult is a Sequence of results, in job order
print(result.success)
batch.results # The same results as a tuple
batch[0] # First result (by index)
len(batch) # Number of jobs
# Filter by success
batch.succeeded # Only successful results
batch.failed # Only failed results
batch.all_succeeded # True if all succeeded
# Timing
print(f"Total time: {batch.total_runtime_seconds:.1f}s")
Progress Callbacks¶
Monitor progress with a callback function:
def on_progress(completed, total, label, success):
status = "OK" if success else "FAIL"
print(f"[{completed}/{total}] {label}: {status}")
batch = simulate_batch(jobs, progress=on_progress)
The callback receives:
| Parameter | Type | Description |
|---|---|---|
completed |
int |
Number of completed jobs |
total |
int |
Total number of jobs |
label |
str |
Label of the just-completed job |
success |
bool |
Whether the job succeeded |
Rich Progress Bar¶
from rich.progress import Progress
with Progress() as progress:
task = progress.add_task("Simulating...", total=len(jobs))
def callback(completed, total, label, success):
progress.update(task, completed=completed)
batch = simulate_batch(jobs, progress=callback)
Parallelism¶
Worker Count¶
Control concurrency with max_workers:
# Use all CPUs
batch = simulate_batch(jobs, max_workers=None) # Default
# Limit to 4 concurrent simulations
batch = simulate_batch(jobs, max_workers=4)
# Sequential (useful for debugging)
batch = simulate_batch(jobs, max_workers=1)
Default: min(len(jobs), os.cpu_count())
Thread vs Process¶
simulate_batch uses threads (not processes) because:
- EnergyPlus runs as a subprocess (releases GIL)
- Lower memory overhead than multiprocessing
- Simpler error handling
Error Handling¶
Failed simulations don't stop the batch:
batch = simulate_batch(jobs)
for i, result in enumerate(batch):
if not result.success:
print(f"Job {i} failed:")
print(f" Exit code: {result.exit_code}")
print(f" Stderr: {result.stderr}")
for err in result.errors.fatal:
print(f" Error: {err.message}")
Partial Failures¶
if not batch.all_succeeded:
failed_count = len(batch.failed)
print(f"{failed_count} jobs failed")
# Process only successful results
for result in batch.succeeded:
# ... analyze results
Caching¶
Share a cache across batch jobs:
from idfkit.simulation import SimulationCache
cache = SimulationCache()
# All jobs share the same cache
batch = simulate_batch(jobs, cache=cache)
# Re-running is instant for unchanged models
batch2 = simulate_batch(jobs, cache=cache) # Cache hits
Cloud Storage¶
Store results in S3:
from idfkit.simulation import S3FileSystem
fs = S3FileSystem(bucket="my-bucket", prefix="study-001/")
# Each job needs an explicit output_dir
jobs = [
SimulationJob(
model=variant,
weather="weather.epw",
label=f"case-{i}",
output_dir=f"case-{i}", # Required with fs
)
for i, variant in enumerate(variants)
]
batch = simulate_batch(jobs, fs=fs)
Best Practices¶
- Use labels — Makes progress tracking and debugging easier
- Set timeouts — Prevent runaway simulations from blocking
- Share caches — Avoid redundant work across similar models
- Handle failures gracefully — Check
result.successbefore accessing outputs - Start small — Test with a few jobs before running thousands
See Also¶
- How to run a simulation — Single simulation guide
- How to cache simulation results — Content-addressed caching
- Examples: Parametric Study — Complete example