Skip to content

Two writers, one model

Both libraries write IDF. Hand them the same model and you get two files that EnergyPlus reads identically and diff does not.

That is not a defect in either one, and it is not going to be resolved. This page says what the seven differences are, why they are not being removed, and what to do instead of diffing.

The seven differences, measured

Measured on 5ZoneAirCooled.idf from EnergyPlus 26.1.0, read and written back by each library with no options set: 359 objects, 4,031 lines against 4,125.

A measured claim reads differently from an asserted one, so the number is re-derivable: load the file, write it, count the lines.

Python TypeScript
Generator header !-Generator idfkit v… and !-Option SortedOrder none
Object ordering sorted by type name, Version first insertion order, Version first
Indent two spaces four spaces
Comment overflow a long value pushes the comment right the same, from a different padding calculation
Float rendering %g, so 30.0 becomes 30 the schema decides, so a number field keeps 30.0
Comment capitalisation every word title-cased, Number Of Timesteps Per Hour minor words lowercased, Number of Timesteps per Hour
Extensible-group comment numbering first group unsuffixed, then 2, 3 the same scheme, applied at a different point

The two that surprise people are float rendering and comment capitalisation, because they touch almost every line of a large file and neither looks like a choice until you see the other one.

Neither writer is more correct

Both outputs are valid IDF. Both are read by EnergyPlus without complaint. Both have been published for long enough that somebody's diff, somebody's test fixture and somebody's version-controlled model depend on the exact bytes.

So neither default moves. Changing Python's %g to match TypeScript would be just as much of a break as the reverse, and picking a winner would mean picking whose files churn.

Byte-identical output across the two languages is not promised, and it is not coming. If a workflow depends on it, that workflow needs to change rather than wait.

What to do instead of diffing

Pass EnergyPlus the model. It is the thing that reads IDF, it does not care which library wrote the file, and it is the only opinion that decides whether a model runs.

When you do need to compare two models, compare them as models: read both files and compare the parsed documents, which is what the conformance corpus does. It re-reads each library's own output and compares the resulting documents field by field, never the text. Two files that differ on all seven of the above compare equal that way, because all seven are presentation.

The controls, which do not change any of this

Every control now exists on both writers, closed by feature 002. They let you ask for output shaped differently. They do not make the two writers agree, because none of them touches the seven defaults above.

Control Python TypeScript Defaults
Comment-free output output_type="nocomment" comments: False on in both
Compressed output output_type="compressed" compressed: True off in both
Indent indent indent two spaces / four
Comment column comment_column commentColumn 30 in both
Object ordering ordering ordering sorted / source
Version pinned first version_first versionFirst on in both

Where the defaults differ they stay differing: a control lets you ask for the other behaviour, it does not change what you get by asking for nothing.

import idfkit

model = idfkit.load_idf("5ZoneAirCooled.idf")

# Every control, at a value that is not the default.
text = idfkit.write_idf(
    model,
    indent=4,
    comment_column=45,
    ordering="source",
    version_first=False,
)

The most aggressive is compressed output: one object per line, no comments, no blank separators, no header.

compact = idfkit.write_idf(model, output_type="compressed")

The TypeScript half of both examples is written and type-checked in idfkit-js at docs-snippets/explanation/two-writers-one-model/controls.ts. It appears here as a tab beside the Python one once idfkit-js cuts the docs release that carries it and scripts/sync_js_artifacts.py vendors it into docs/snippets/js/; that directory is vendored wholesale from the pinned [tool.idfkit.docs] level and must match it exactly, so the file cannot be added here by hand.

Compressed output from the two libraries is still not byte-identical: it removes comments, indentation and blank lines, and it does not touch float rendering. What it does guarantee, and what the corpus checks, is that a document written under any of these controls re-reads to the same document it came from.

Where this is recorded

The parity record used to carry these seven differences in its differences field, because write was a partial capability on both sides. Closing the five controls made it complete, and the record does not keep a differences field on a capability that is complete: a reader would have no way to tell a difference that still matters from one that was left behind.

So they live here. If this page goes away, the information exists nowhere.