New Systems Documentation Generator¶
The mp-units documentation now has an automated systems reference generator. It extracts all quantities, units, dimensions, and their relationships directly from the library's C++ source code and documents them.
The Challenge¶
mp-units has grown to support an extensive collection of systems of quantities and units:
- 15 systems including ISQ, SI, CGS, IAU, Natural Units, IEC, Imperial, USC, and more
- 400+ quantities spanning physics, engineering, astronomy, and computer science domains
- 200+ units with proper relationships and conversions
- 150+ quantity hierarchies showing parent-child relationships
- 10+ dimensions forming the foundation of dimensional analysis
- 5+ point origins for affine space quantities (temperature, coordinates, etc.)
- 32 prefixes for scaling units
Documenting all of this by hand would be error-prone and would quickly get out of date as the library evolves. We needed to generate the reference documentation directly from the source code instead.
Why Not Doxygen¶
Doxygen extracts API documentation from comments and generates reference pages for classes and functions. That is not what we needed here:
Limited semantic understanding: Doxygen treats C++ code as text to parse for structure,
but doesn't understand the meaning of our domain-specific definitions. It can't recognize
that speed : quantity_spec<length / time> represents a physical quantity defined as the
ratio of length to time.
No cross-correlation: Doxygen can't automatically discover and document the
relationships between quantities, units, and dimensions. It won't understand that the unit
newton relates to the quantity force, which in turn is derived from mass, length,
and time.
Can't extract metadata: The rich semantic information encoded in our template
parameters, like kind_of<isq::length> indicating a quantity hierarchy or mag<1'000>
indicating a scaling factor, is invisible to Doxygen. It would simply render these as
template syntax without extracting their meaning.
Can't associate unit symbols: Doxygen can't recognize that an object in the
unit_symbol namespace represents the symbol for a unit defined elsewhere. For example, it
wouldn't understand that unit_symbol::m (the symbol object) corresponds to the metre
unit, or that unit_symbol::kg relates to kilogram. These are separate C++ entities that
only become connected through the library's type system, and discovering that connection
requires type introspection.
No custom views: We needed specialized views like:
- Quantity hierarchy diagrams showing parent-child relationships
- Quantity hierarchy index organized by their associated dimensions
- Unit tables providing information about the symbols and definition details
- Cross-references linking units, quantities, and dimensions
Doxygen's output is optimized for API documentation. We needed a tool that could understand the domain model encoded in our C++20 definitions and generate documentation reflecting the structure of physical quantities and units rather than the structure of C++ classes.
Code as Documentation¶
Thanks to mp-units' use of modern C++20 features, particularly class types as non-type template parameters (NTTPs), the library's definitions are remarkably terse and parse-friendly. Most entities are defined in single, declarative lines:
inline constexpr struct metre : named_unit<"m", kind_of<isq::length>> {} metre;
inline constexpr struct second : named_unit<"s", kind_of<isq::duration>> {} second;
inline constexpr struct speed : quantity_spec<length / time> {} speed;
inline constexpr struct acceleration : quantity_spec<speed / time> {} acceleration;
For units with special symbols, the library separates the unit definition from its symbol object:
// Unit definition
inline constexpr struct ohm : named_unit<{u8"Ω", "ohm"}, volt / ampere> {} ohm;
// Symbol object in unit_symbol namespace
namespace unit_symbols {
inline constexpr auto Ω = ohm;
}
Info
More info on unit_symbols can be found in our Quick Start chapter.
This declarative style makes it possible to parse the C++ source code and extract:
- Entity names and symbols:
metre,"m",second,"s",ohm,{u8"Ω", "ohm"} - Symbol associations:
unit_symbol::Ωcorresponds toohm - Relationships:
speedis defined aslength / time,ohmasvolt / ampere - Hierarchies: through
kind_of<>specifications and derivation from parent quantity - Namespaces: organizing systems like
mp_units::si,mp_units::isq
The generator works without requiring macros (aside from the portability QUANTITY_SPEC
macro).
What Gets Generated¶
The documentation generator creates a comprehensive systems reference with multiple views:
System-Level Documentation¶
For each system (SI, ISQ, CGS, etc.), the generator produces:
- Dimensions table: Base dimensions with their symbols
- Quantities tables:
- All quantities with their types, character, dimensions, parent quantities, and equations
- Aliases and relationships clearly marked
- Units tables:
- Base units and derived units with their symbols and definitions
- Links to related quantities
- Prefixes: SI prefixes (kilo, mega, milli, etc.) with their symbols and scaling factors
- Point Origins: Reference points for affine quantities (Celsius zero, Unix epoch, etc.)
Example: SI Systems Reference
Cross-Reference Indexes¶
Global indexes provide different views of the entire library:
- Units Index: All units across all systems
- Quantities Index: All quantities with their systems
- Dimensions Index: All dimensions used across systems
- Prefixes Index: All prefixes
- Point Origins Index: All reference points for affine quantities
Quantity Hierarchies¶
The generator also produces a quantity hierarchy visualization. For quantities that have parent-child relationships (e.g., height is a kind of length), it creates interactive Mermaid diagrams showing the complete hierarchy.
For example, the energy hierarchy
shows how various energy-related quantities relate to the base energy concept:
flowchart LR
isq_energy["<b><a href="../../../../../reference/systems_reference/systems/isq/#energy" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">isq::energy</a></b><br><i>(<a href="../../../../../reference/systems_reference/systems/isq/#mass" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">mass</a> * pow<2>(<a href="../../../../../reference/systems_reference/systems/isq/#length" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">length</a>) / pow<2>(<a href="../../../../../reference/systems_reference/systems/isq/#time" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">time</a>))</i>"]
isq_enthalpy["<b><a href="../../../../../reference/systems_reference/systems/isq/#enthalpy" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">isq::enthalpy</a></b>"]
isq_energy --- isq_enthalpy
isq_Gibbs_energy["<b><a href="../../../../../reference/systems_reference/systems/isq/#Gibbs_energy" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">isq::Gibbs_energy</a> | <a href="../../../../../reference/systems_reference/systems/isq/#Gibbs_function" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">isq::Gibbs_function</a></b>"]
isq_enthalpy --- isq_Gibbs_energy
isq_internal_energy["<b><a href="../../../../../reference/systems_reference/systems/isq/#internal_energy" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">isq::internal_energy</a> | <a href="../../../../../reference/systems_reference/systems/isq/#thermodynamic_energy" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">isq::thermodynamic_energy</a></b>"]
isq_enthalpy --- isq_internal_energy
isq_Helmholtz_energy["<b><a href="../../../../../reference/systems_reference/systems/isq/#Helmholtz_energy" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">isq::Helmholtz_energy</a> | <a href="../../../../../reference/systems_reference/systems/isq/#Helmholtz_function" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">isq::Helmholtz_function</a></b>"]
isq_internal_energy --- isq_Helmholtz_energy
isq_heat["<b><a href="../../../../../reference/systems_reference/systems/isq/#heat" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">isq::heat</a> | <a href="../../../../../reference/systems_reference/systems/isq/#amount_of_heat" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">isq::amount_of_heat</a></b>"]
isq_energy --- isq_heat
isq_latent_heat["<b><a href="../../../../../reference/systems_reference/systems/isq/#latent_heat" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">isq::latent_heat</a></b>"]
isq_heat --- isq_latent_heat
isq_mechanical_energy["<b><a href="../../../../../reference/systems_reference/systems/isq/#mechanical_energy" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">isq::mechanical_energy</a></b>"]
isq_energy --- isq_mechanical_energy
isq_kinetic_energy["<b><a href="../../../../../reference/systems_reference/systems/isq/#kinetic_energy" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">isq::kinetic_energy</a></b><br><i>(<a href="../../../../../reference/systems_reference/systems/isq/#mass" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">mass</a> * pow<2>(<a href="../../../../../reference/systems_reference/systems/isq/#speed" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">speed</a>))</i>"]
isq_mechanical_energy --- isq_kinetic_energy
isq_potential_energy["<b><a href="../../../../../reference/systems_reference/systems/isq/#potential_energy" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">isq::potential_energy</a></b>"]
isq_mechanical_energy --- isq_potential_energy
isq_radiant_energy["<b><a href="../../../../../reference/systems_reference/systems/isq/#radiant_energy" style="color: var(--md-mermaid-label-fg-color); text-decoration: none;">isq::radiant_energy</a></b>"]
isq_energy --- isq_radiant_energy
These hierarchies are essential for understanding how mp-units achieves strong type
safety. kinetic_energy cannot be used where potential_energy is expected, even though
both are forms of mechanical energy.
Interactive and Linkified¶
Every piece of generated documentation is fully cross-linked:
- Quantity definitions link to their parent quantities
- Unit definitions link to the quantities they measure (if specified)
- Equations are linkified: click on any identifier to jump to its definition
- External references link to source systems (ISQ quantities, SI base units, etc.)
The hierarchies themselves are interactive: hover over nodes, zoom, pan, and click on any linked quantity name to navigate to its detailed documentation.
Integration with User Guide¶
The generated systems reference complements the existing User's Guide. Each user-facing system chapter now includes a reference link to the complete auto-generated documentation:
- ISQ → ISQ Systems Reference
- SI → SI Systems Reference
- CGS → CGS Systems Reference
- And more...
This separation allows the User's Guide to focus on concepts, usage, and examples, while the Systems Reference provides exhaustive technical details automatically extracted from the source code.
How It Works¶
The documentation generation is seamlessly integrated into the MkDocs build process:
- During
mkdocs buildormkdocs serve: The generator automatically runs before MkDocs processes the documentation - Source code parsing: Python script parses C++ header files in
src/systems/include/mp-units/systems/ - Metadata extraction: To obtain quantities metadata a temporary C++ program is compiled to extract type information using C++ compile-time logic
- Markdown generation: Creates comprehensive Markdown files with tables, diagrams, and links
- MkDocs integration: Generated files are automatically included in the site navigation
The generator uses change detection: it only regenerates documentation when source files have been modified, which keeps builds fast during development.
Running the Generator¶
The generator runs automatically during normal documentation builds:
mkdocs build # Generates and builds all documentation
mkdocs serve # Live preview with auto-regeneration
For manual regeneration:
Technical Highlights¶
Several technical challenges were solved during development:
1. Anchor ID Generation¶
Units with subnamespaces (like si2019::speed_of_light_in_vacuum or survey1893::us_survey_foot)
required special handling to ensure anchor IDs match the actual HTML output, preventing broken
internal links.
2. Link Styling in Mermaid¶
Mermaid diagrams use closed shadow DOM, which blocks all external CSS. We solved this by generating inline styles directly in the HTML, ensuring links remain black and maintain consistent styling throughout the documentation.
3. Metadata Extraction¶
While most information can be parsed directly from C++ source, quantity relationships and character deduction require type introspection. We use a hybrid approach: compile small C++ programs to extract metadata and other reflection-like features, then combine with parsed information.
4. mkdocs.yml Integration¶
The generator automatically updates mkdocs.yml with navigation entries for all generated
pages, ensuring everything is properly indexed and searchable.
Looking Forward¶
This automated documentation system ensures that as mp-units continues to grow and evolve:
- Documentation stays in sync with the code, with no drift or outdated information
- New systems, quantities, and units are automatically documented
- Refactoring and reorganization don't break documentation
- The 15+ systems, 400+ quantities, and 200+ units are all properly cross-referenced
Modern C++ features are what made this generator possible. Because the library's definitions are terse, parse-friendly, and rich in metadata, a script can read them and build the documentation from them.
Try It Out¶
Explore the generated documentation:
- Systems Reference Index - Overview of all systems
- ISQ Systems Reference - All 350+ ISQ quantities
- SI Systems Reference - Complete SI unit catalog
- Units Index - Searchable table of all 200+ units
- Quantity Hierarchies - Interactive diagrams
The complete source code for the generator is available in
scripts/systems_reference.py.
Behind the Scenes¶
You are still with me? Great, thank you! 😊 Want to hear the real story of how this was implemented?
Yes, It Was Generated by AI¶
I am quite proficient in C++ but know very little about Python. There is no way I could have written such a complex 3,400+ line script in just two days. I had invaluable help from Claude Sonnet 4.5 working through the GitHub Copilot agents in VS Code.
However, even though I didn't write a single line of Python code myself, the AI couldn't have invented this solution on its own. While it has extensive knowledge about quantities, units, and C++ (as you can verify by asking it questions), it can't always apply that knowledge autonomously when solving a coding task. The context window and token limits are simply too constraining.
This is why, as an engineer, you need to guide the tool and prevent it from making mistakes. Some of them are minor and some are catastrophic, and they will happen sooner or later.
The Morning We Lost Half a Day¶
Case in point: on the morning of the second day, while working on making cross-references clickable between entities, the AI made... let's call it a "creative editing decision":
GitHub Copilot Chat
I see the issue! Markdown links don't work inside code blocks (backticks). I need to remove the backticks around linkified content so the links are actually clickable.
Let me fix this by not wrapping linkified definitions in backticks:
Let me check the current state of the file after the first edit and make the rest one by one:
I have a syntax error. Let me check what happened:
Read systems_reference.py, lines 2445 to 2455
I see the issue - my first replacement removed too much. Let me fix it:
Read systems_reference.py, lines 2440 to 2460
I see - I accidentally removed the rest of the code. Let me check what should be there:
Let me revert my change and start fresh:
...
... and that is how we lost a few hours of work. 😢
The lesson: always use version control. Git saved us here. We could revert the changes and start again from the last checkpoint. I personally commit code only when there are no known errors or issues, which meant we had to redo some work to catch up from the last stable version. You need to review the changes, test frequently, and be ready to roll back.
Despite the occasional mishap, working with the AI was productive. What would have taken me weeks, or more likely would never have happened at all given the limits of my Python skills and my free time, was completed in two days. Let the AI do the code generation, and keep your engineering judgment engaged to guide the process and catch the errors.
I bet you are thinking about this now 😉
I don't use AI to generate my C++ code. Not a single line of C++ code I have committed to mp-units was AI-generated. It simply makes too many mistakes, and I know C++ well enough that I can't tolerate watching it struggle. But documentation is a completely different story. AI works well as a documentation assistant. It helps with structure, clarity, and completeness, and documentation doesn't carry the precision requirements of production C++ code. Know the strengths and the limitations of your tools.
Maybe you are also wondering about this? 😉
Yes, I asked GitHub Copilot to edit and improve this chapter as his atonement 😛