Common tasks¶
Quick recipes for the operations you'll use every day, in both languages where both have them and with the difference stated where they do not. Each section is self-contained — jump to the one you need. If you're brand new to idfkit, work through Build your first model first, then come back here.
Load a Model¶
from idfkit import load_idf
# Load an existing IDF file
model = load_idf("building.idf")
print(f"Loaded {len(model)} objects")
# For migration-only tolerant loading of legacy/noisy files:
model = load_idf("legacy_building.idf", strict_parsing=False)
print(f"Tolerant load parsed {len(model)} objects")
import { loadIdf } from '@idfkit/core/node';
import type { TypeMap } from '@idfkit/types-v26-1';
// Load an existing IDF file
const doc = await loadIdf<TypeMap>('building.idf');
console.log(`Loaded ${doc.size} objects`);
// For migration-only tolerant loading of legacy or noisy files:
const legacy = await loadIdf<TypeMap>('legacy_building.idf', { strict: false });
console.log(`Tolerant load parsed ${legacy.size} objects`);
load_idf() uses strict parsing by default (strict_parsing=True) and raises
IDFParseError for malformed objects. Use strict_parsing=False only as a
migration/compatibility fallback for legacy or noisy files.
Query Objects¶
Access objects with O(1) dictionary lookups:
Modify Fields¶
Change field values with attribute assignment:
Discover Available Fields¶
Not sure what fields an object type has? Use describe() to see all available fields:
# See all fields for a Zone
print(model.describe("Zone"))
# === Zone ===
# ...
# Fields (9):
# direction_of_relative_north (number) [deg] default=0
# x_origin (number) [m] default=0
# ...
# See required fields for a Material
desc = model.describe("Material")
print(f"Required: {desc.required_fields}")
# Required: ['roughness', 'thickness', 'conductivity', 'density', 'specific_heat']
import { describeObjectType } from '@idfkit/core';
// Every field a Zone has
const zone = describeObjectType(doc.schema, 'Zone');
for (const field of zone.fields) {
console.log(` ${field.name} (${field.fieldType}) ${field.units ?? ''}`);
}
// The fields a Material cannot do without
const material = describeObjectType(doc.schema, 'Material');
console.log(
'Required:',
material.fields.filter((field) => field.required).map((field) => field.name)
);
In REPL/Jupyter, use tab completion to explore object fields:
Both libraries catch a misspelled field name. They catch it at different moments, and the difference is the one thing worth knowing about the two type systems: Python validates when the object is built, TypeScript when the file is compiled.
// @ts-expect-error x_orgin is a typo for x_origin
doc.add('Zone', 'Office', { x_orgin: 0 }); // caught by the compiler, before it runs
// Field names are checked against the schema for the version in the type map, so
// there is nothing to switch off and nothing to pay at run time.
doc.add('Zone', 'Office', { x_origin: 0 });
IDE Support
idfkit ships type stubs for all 858 EnergyPlus object types — your IDE will autocomplete field names, show inline documentation, and catch typos. See Type-Safe Development for details.
Create a New Model¶
from idfkit import new_document
# Create a baseline model for EnergyPlus 24.1
model = new_document(version=(24, 1, 0))
# Update pre-seeded singleton objects
building = model["Building"].first()
if building is not None:
building.name = "My Building"
building.north_axis = 0
building.terrain = "City"
geometry_rules = model["GlobalGeometryRules"].first()
if geometry_rules is not None:
geometry_rules.starting_vertex_position = "UpperLeftCorner"
geometry_rules.vertex_entry_direction = "Counterclockwise"
geometry_rules.coordinate_system = "Relative"
# Add additional named objects
model.add("Zone", "Office", x_origin=0, y_origin=0, z_origin=0)
# Add an additional unnamed singleton object
model.add("Timestep", number_of_timesteps_per_hour=4)
import { IdfDocument } from '@idfkit/core';
import { schemas } from '@idfkit/core/node';
import type { TypeMap } from '@idfkit/types-v26-1';
// A new model against one EnergyPlus release
const schema = await schemas().load('26.1.0');
const doc = new IdfDocument<TypeMap>(schema);
// Nothing is pre-seeded: add the singletons you want
doc.add('Version', null, { version_identifier: '26.1' });
doc.add('Building', 'My Building', { north_axis: 0, terrain: 'City' });
doc.add('GlobalGeometryRules', null, {
starting_vertex_position: 'UpperLeftCorner',
vertex_entry_direction: 'Counterclockwise',
coordinate_system: 'Relative',
});
// Named objects
doc.add('Zone', 'Office', { x_origin: 0, y_origin: 0, z_origin: 0 });
// And unnamed singletons
doc.add('Timestep', null, { number_of_timesteps_per_hour: 4 });
Write Output¶
Run a Simulation¶
from idfkit.simulation import simulate
result = simulate(
model,
weather="weather.epw",
design_day=True, # Fast design-day run
)
print(f"Success: {result.success}")
print(f"Runtime: {result.runtime_seconds:.1f}s")
# Check for errors
if result.errors.has_fatal:
for err in result.errors.fatal:
print(f"Error: {err.message}")
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.
Query Results¶
# Get time-series data from SQLite output
ts = result.sql.get_timeseries(
variable_name="Zone Mean Air Temperature",
key_value="Office",
)
print(f"Variable: {ts.variable_name}")
print(f"Temperature range: {min(ts.values):.1f}°C to {max(ts.values):.1f}°C")
# Filter by environment if needed
ts_sizing = result.sql.get_timeseries(
variable_name="Zone Mean Air Temperature",
key_value="Office",
environment="sizing", # Design day data only
)
# Get tabular data
tables = result.sql.get_tabular_data(report_name="AnnualBuildingUtilityPerformanceSummary")
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.
Find Weather Stations¶
from idfkit.weather import StationIndex, geocode
# Load the station index (instant, no network needed)
index = StationIndex.load()
# Search by name
results = index.search("chicago ohare")
print(results[0].station.display_name)
# Find nearest station to an address
results = index.nearest(*geocode("Willis Tower, Chicago, IL"))
station = results[0].station
print(f"{station.display_name}: {results[0].distance_km:.0f} km away")
import { geocode, loadStationIndex } from '@idfkit/weather';
// The index is a file you serve; nothing is bundled into the library
const index = await loadStationIndex('/stations.json.gz');
// Search by name
const [match] = index.search('chicago ohare');
console.log(match?.station.displayName);
// Nearest to an address
const [latitude, longitude] = await geocode('Willis Tower, Chicago, IL');
const [nearest] = index.nearest(latitude, longitude);
console.log(`${nearest?.station.displayName}: ${nearest?.distanceKm.toFixed(0)} km away`);
Apply Design Days¶
Not in JavaScript yet
Design days and ASHRAE sizing conditions is available in Python and is not in JavaScript today. A temporary gap, not a boundary. The port is tracked in idfkit-js#20.
The full entry, including the vocabulary this capability owns, is on the capability parity page.
Lossless Round-Trip¶
Pass preserve_formatting=True to build a Concrete Syntax Tree (CST) so that
write_idf and save_idf reproduce the original formatting, comments, and whitespace for
unmodified objects:
from idfkit import load_idf, save_idf
# Build a CST to preserve original formatting
model = load_idf("building.idf", preserve_formatting=True)
# Modify a zone ceiling height
model["Zone"]["Office"].ceiling_height = 3.5
# Unmodified objects keep their original formatting, comments,
# and whitespace; only the changed object is re-serialised.
save_idf(model, "building_updated.idf")
Not in JavaScript yet
Formatting-preserving round-trip is available in Python and is not in JavaScript today. A temporary gap, not a boundary. The port is tracked in idfkit-js#12.
The full entry, including the vocabulary this capability owns, is on the capability parity page.
Next Steps¶
- Core Tutorial - Complete interactive walkthrough
- Simulation Guide - Deep dive into simulation features
- How to migrate models between EnergyPlus versions - Move models forward across EnergyPlus releases
- Weather Guide - Weather station search and design days
- API Reference - Full API documentation