The Ontology Spec and Units#
This example describes how BDF’s canonical column ontology is loaded and queried through the bdf.spec module. It is aimed at those who want to inspect the quantities BDF understands, pin a specific ontology version, or perform unit conversions with the spec as the single source of truth.
from bdf import spec
from bdf.spec import COLUMN_ONTOLOGY, ColumnOntology, Quantity
The default ontology#
bdf.spec.COLUMN_ONTOLOGY is a module-level singleton built at import time from the ontology snapshot bundled with the installed package. This is the latest packaged version and is what every reader, normalizer, and validator uses by default. Iterating it yields (mr_name, Quantity) pairs.
print("ontology version:", COLUMN_ONTOLOGY.ontology_version)
print("quantity count:", len(list(COLUMN_ONTOLOGY)))
print("first 8 mr_names:", [mr for mr, _ in COLUMN_ONTOLOGY][:8])
ontology version: 1.3.0
quantity count: 63
first 8 mr_names: ['absolute_impedance_ohm', 'ac_internal_resistance_ohm', 'ambient_pressure_pa', 'ambient_temperature_celsius', 'applied_pressure_pa', 'cumulative_capacity_ah', 'cumulative_energy_wh', 'cycle_cumulative_capacity_ah']
Each quantity is a Quantity model carrying the canonical unit, the human-readable label template, the machine-readable name, the ontology IRI, accepted synonyms, and documentation metadata. Look one up by mr_name with item access (or attribute access).
q = COLUMN_ONTOLOGY["discharging_capacity_ah"]
print("mr_name: ", q.mr_name)
print("unit: ", q.unit)
print("label_template: ", q.label_template)
print("formatted_label:", q.formatted_label)
print("notation: ", q.effective_notation)
print("iri: ", q.iri)
print("required: ", q.required)
print("synonyms: ", q.synonyms[:5])
print("definition: ", q.definition)
mr_name: discharging_capacity_ah
unit: Ah
label_template: Discharging Capacity / {unit}
formatted_label: Discharging Capacity / Ah
notation: discharging_capacity_ah
iri: https://w3id.org/battery-data-alliance/ontology/battery-data-format#discharging_capacity_ah
required: False
synonyms: ['discharging-capacity', 'discharging-capacity-ah', 'discharging-capacity-ampere-hour']
definition: Cumulative electric charge transferred out of the test object during discharging, in ampere hour. Accumulates from the start of the test and never resets between steps or cycles. Increases only during discharge intervals; unchanged during rest or charge. Note: differs from step-level Q discharge as reported by some instruments (e.g. BioLogic EC-Lab), which reset to zero at each step boundary.
The required and optional label sets are derived directly from the loaded ontology, so they always reflect whichever version is active.
print("required labels:", COLUMN_ONTOLOGY.required_labels())
print("optional labels:", COLUMN_ONTOLOGY.optional_labels()[:6], "...")
required labels: ('Voltage / V', 'Test Time / s', 'Current / A')
optional labels: ('Absolute Impedance / ohm', 'AC Internal Resistance / ohm', 'Ambient Pressure / Pa', 'Ambient Temperature / degC', 'Applied Pressure / Pa', 'Cumulative Capacity / Ah') ...
Loading other versions or a custom ontology#
The default singleton is fine for everyday use, but you can build an independent ColumnOntology and point it at a different source. Construct a fresh instance with ColumnOntology.build() so you never mutate the shared COLUMN_ONTOLOGY singleton.
load_ttl(path) adopts the quantities from any local Turtle file, which is the way to use a custom or in-development ontology. Here we load the snapshot bundled with the package to show the mechanism offline.
import importlib.resources
from pathlib import Path
custom = ColumnOntology.build()
ttl_path = Path(str(importlib.resources.files("bdf.data").joinpath("bdf-ontology-snapshot.ttl")))
custom.load_ttl(ttl_path)
print("custom ontology version:", custom.ontology_version, "with", len(list(custom)), "quantities")
custom ontology version: 1.3.0 with 63 quantities
load_latest(refresh=True) fetches the live ontology from w3id.org, parses it, and caches it locally. Use it to pick up a release newer than the bundled snapshot. (This cell requires network access.)
latest = ColumnOntology.build()
try:
latest.load_latest(refresh=True)
print("live ontology version:", latest.ontology_version)
except Exception as exc:
print("live fetch unavailable, keeping bundled snapshot:", type(exc).__name__)
live ontology version: 1.3.0
You can pin a specific release explicitly with load_version. It serves the cached copy when present, and otherwise fetches that release tag, verifies its owl:versionInfo matches, and caches it for next time.
pinned = ColumnOntology.build()
pinned.load_version("1.1.0")
print("loaded pinned version:", pinned.ontology_version)
loaded pinned version: 1.1.0
Resolving labels to machine-readable names#
Given a column label as it appears in a BDF dataframe, the ontology resolves it back to its canonical quantity. mr_name_from_label returns the machine-readable name; quantity_from_label returns the matching Quantity together with the unit parsed from the label. Both prefer non-deprecated quantities when a base label is shared.
print("mr_name:", COLUMN_ONTOLOGY.mr_name_from_label("Discharging Capacity / Ah"))
matched, unit = COLUMN_ONTOLOGY.quantity_from_label("Voltage / mV")
print("matched quantity:", matched.mr_name)
print("unit from label: ", unit)
mr_name: discharging_capacity_ah
matched quantity: voltage_volt
unit from label: mV
Two module-level helpers parse labels without touching the ontology: parse_label splits a label into its base and normalized unit, and unit_from_label returns just the unit.
print("parse_label: ", spec.parse_label("Voltage / mV"))
print("unit_from_label:", spec.unit_from_label("Test Time / s"))
parse_label: ('Voltage', 'mV')
unit_from_label: s
Unit conversion#
Unit conversion is driven by the canonical unit recorded on each quantity. get_unit_conversion(src, dst) returns the (scale, offset) pair that maps a value in src units to dst units, or None when the units are dimensionally incompatible. The scale/offset form covers affine conversions such as °C to K.
print("s -> h: ", spec.get_unit_conversion("s", "h"))
print("Ah -> mAh: ", spec.get_unit_conversion("Ah", "mAh"))
print("degC -> K: ", spec.get_unit_conversion("degC", "K"))
print("V -> A: ", spec.get_unit_conversion("V", "A"))
s -> h: (0.000277777777778, 0.0)
Ah -> mAh: (1000.0, 0.0)
degC -> K: (1.0, 273.15)
V -> A: None
A Quantity knows its own canonical unit, so it can build conversions relative to itself. convert_to(dst) converts from the quantity’s unit to dst, and convert_from(src) converts a source unit into the quantity’s unit — exactly what a reader does when ingesting vendor data.
cap = COLUMN_ONTOLOGY["discharging_capacity_ah"]
print("canonical unit: ", cap.unit)
print("convert_to('mAh'): ", cap.convert_to("mAh"))
print("convert_from('mAh'): ", cap.convert_from("mAh"))
canonical unit: Ah
convert_to('mAh'): (1000.0, 0.0)
convert_from('mAh'): (0.001, 0.0)
Applying a conversion is just value * scale + offset. The example below converts a millivolt reading into the canonical volt unit using the pair returned for the voltage quantity.
voltage = COLUMN_ONTOLOGY["voltage_volt"]
scale, offset = voltage.convert_from("mV")
raw_mv = [3200.0, 3300.0, 3400.0]
converted_v = [v * scale + offset for v in raw_mv]
print("mV:", raw_mv)
print("V: ", converted_v)
mV: [3200.0, 3300.0, 3400.0]
V: [3.2, 3.3000000000000003, 3.4]