How to run a simulation in the browser¶
This guide is TypeScript only. There is no Python browser runtime; to run EnergyPlus locally from Python, see How to run a simulation instead.
@idfkit/core stops at the model. To simulate one in a browser, hand the IDF
text to @idfkit/engine, which
runs EnergyPlus via WebAssembly.
The seam between the two libraries is plain IDF text, which is the practical payoff of keeping the core synchronous and string-based.
JavaScript only, permanently
Running EnergyPlus in the browser belongs to JavaScript alone. Python 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.
Delivered by @idfkit/engine, installed separately and deliberately not part of the shared install name: not a subpath, not a dependency, not an optional peer (research R19). The WebAssembly build exists to reach a runtime Python does not target, so Python has no counterpart and is not getting one. @idfkit/engine-assets is roughly 51 MB and versions on the EnergyPlus release it carries, while the loader versions on its own API, which is a second reason the facade does not carry it.
This is not a gap in Python. Python runs EnergyPlus by the other mechanism: see
local-simulation.
The full entry, including the vocabulary this capability owns, is on the capability parity page.
Install and serve the engine assets¶
The engine is installed by its own name. npm install idfkit does not reach it
and is not going to: the assets are about 51 MB of WebAssembly and they pin one
EnergyPlus release, so nothing that the shared name installs may depend on them
(FR-070).
npm install @idfkit/engine @idfkit/engine-assets@26.1
npx idfkit-engine-assets public/energyplus # copy the WASM engine to your own origin
@idfkit/core and @idfkit/schemas are separate and are what edits the model;
add them if you have not already.
This engine executes EnergyPlus 26.1
@idfkit/engine-assets is versioned by the EnergyPlus release it carries,
so @idfkit/engine-assets@26.1 runs EnergyPlus 26.1 and nothing else. A
model written for another release has to be migrated before it will run
here.
That version is not a conformance level and has no relationship to the levels in this site's footer. Those say which corpus the two libraries agree on when they read a file; this says which simulation engine your browser downloads. The two move independently and comparing them means nothing.
Edit, hand over, read back¶
import { parseIdf, writeIdf, SchemaBundle, httpSource } from '@idfkit/core';
import { createEnergyPlus } from '@idfkit/engine';
import type { TypeMap } from '@idfkit/types-v26-1';
// 1. Edit the model here.
const schema = await new SchemaBundle(httpSource('/schemas/')).load('26.1.0');
const { document } = parseIdf<TypeMap>(idfText, schema);
document.require('Zone', 'SPACE1-1').ceiling_height = 3;
// 2. Hand it over as IDF text. Loading compiles a ~28 MB binary, so create the
// engine once and reuse it across runs.
const ep = await createEnergyPlus({ assetBaseUrl: '/energyplus' });
const result = await ep.run({ idf: writeIdf(document), epw: epwText });
// 3. A failed run is data, not an exception: the err report is worth reading.
if (result.success) {
console.log(result.eso?.variables.size, 'output variables');
} else {
console.error(result.fatalError, result.err?.entries);
}
ep.dispose();
Keep the versions aligned¶
A document can be any of the 17 supported versions, and the engine executes exactly one of them, so load the schema that matches the asset package you installed.
Nothing checks this for you. A mismatch means the engine reads a model written for a different release.
HVACTemplate:* objects need no special handling¶
run() expands them with the bundled ExpandObjects preprocessor before
simulating. Call expandObjects from @idfkit/engine yourself only when you
want the expanded IDF back, and if you do, parseIdf reads it straight into a
document.
A model that reads a file needs that file handed over too¶
The seam is IDF text plus, when the model needs them, the files it names.
Schedule:File, Table:Lookup and Chiller:Electric:ASHRAE205 all point at
something on disk, and the engine cannot open a file nobody gave it.
Pass the contents in files, keyed by exactly the path written in the model:
relative to it, and case-sensitive, because the simulation filesystem is.
const result = await ep.run({
idf: writeIdf(document),
epw: epwText,
files: { 'occupancy.csv': csvText },
});
Read the key off the document rather than hardcoding it, and the two cannot drift apart:
A model naming a file that is not in files fails before the engine starts,
with success: false and a fatalError naming the object and the path, so you
get "you forgot occupancy.csv" rather than an error from inside the engine.
detectExternalFileReferences(idf), also from @idfkit/engine, lists what a
model needs before you run it.
Try it here¶
The example below is the one this page's runner executes. It is the same file
npm run typecheck:docs compiles in idfkit-js, fetched from this site and run
verbatim, so what you press is what you read.
import { IdfDocument, writeIdf } from '@idfkit/core';
import { createEnergyPlus } from '@idfkit/engine';
/**
* Build a one-zone model, simulate it, and report what happened.
*
* @param {import('@idfkit/core').Schema} schema a loaded schema for EnergyPlus 26.1
* @param {string} epwText the weather file, as text
* @param {string} assetBaseUrl where the engine's WebAssembly is served from
* @param {(message: string) => void} log called with each step, for the page to show
* @returns {Promise<import('@idfkit/engine').EngineRunResult>}
*/
export async function run(schema, epwText, assetBaseUrl, log) {
// 1. Build the model. Nothing here touches the network.
const doc = new IdfDocument(schema);
doc.add('Version', null, { version_identifier: '26.1' });
doc.add('Building', 'Live runner', { north_axis: 0, terrain: 'City' });
doc.add('Timestep', null, { number_of_timesteps_per_hour: 4 });
log(`Model built: ${doc.size} objects`);
// 2. Hand it over as IDF text. Loading compiles a ~28 MB binary, so the engine is created
// once and reused; this page creates it on activation and disposes it afterwards.
const ep = await createEnergyPlus({ assetBaseUrl });
log('Engine ready');
// 3. A failed run is data, not an exception.
const result = await ep.run({ idf: writeIdf(doc), epw: epwText });
log(result.success ? 'Simulation finished' : `Failed: ${result.fatalError}`);
ep.dispose();
return result;
}
Nothing is downloaded until you press the button. Doing so fetches about 51 MB of WebAssembly from a CDN, once.
This page hosts none of that: the assets come from the CDN at the moment you ask for them, and the built site contains no engine bytes at all, which its own build checks assert (SC-027). If the download fails, or your browser declines it, the example above is unchanged and copying it into a project is the whole of what the button was going to do.
The runner is a demonstration and not a test. Nothing in this project's CI runs EnergyPlus in a browser, so it establishes that the example runs on your machine today and nothing more. What catches a renamed engine API is the type-check, described in browser simulation.
Results do not come back through this library¶
The engine returns its own parsed err, eso, and mtr structures, along
with raw sql and html. @idfkit/core has no output-reading API and is not
planning one. Re-parsing expanded IDF is the only return path that involves it.
See also¶
- How to parse in the browser for serving the schema bundle and getting the version right
- How to run a simulation for the Python path, which drives a local EnergyPlus installation instead
- About capability parity for how absences like this one are recorded