Skip to content

Add programmatic composite unit builder#72

Open
suleman-uzair wants to merge 18 commits into
mainfrom
feature/composite_units_builder
Open

Add programmatic composite unit builder#72
suleman-uzair wants to merge 18 commits into
mainfrom
feature/composite_units_builder

Conversation

@suleman-uzair

@suleman-uzair suleman-uzair commented Jul 1, 2026

Copy link
Copy Markdown
Member

This PR:

  • Adds programmatic construction of composite units, complementing Unitsml.parse.
  • A composed Formula renders through every existing format (MathML, LaTeX, AsciiMath, HTML, Unicode, UnitsML XML, Plurimath), and its representation mirrors the parser's — exponents are Unitsml::Number, and a single division matches the parsed string (Unitsml::Unit.new("W") / Unitsml::Unit.new("m") == parse("W/m")).

Three ways to build

Operator DSL* and / on Unit, Dimension and Formula:

Unitsml::Unit.new("W") / Unitsml::Unit.new("m") / Unitsml::Unit.new("sr")
Unitsml::Unit.new("m") / Unitsml::Unit.new("s", 2)   # power via the constructor

Fluent chain#unit/#dimension are sugar over *; metadata comes last:

Unitsml::Unit.new("W").unit("m", -1).unit("sr", -1).quantity("radiance")

Keyword formUnitsml.compose, passing either units: or dimensions::

Unitsml.compose(units: ["W", { unit: "m", power: -1 }, :sr], quantity: "radiance")
Unitsml.compose(dimensions: ["dim_L", { dimension: "dim_M", power: 2 }])

Separators

Like the parser, the / operator keeps the / separator and inverts the right-hand operand's power.
An explicit separator can also be placed with #extender/#ext, accepting exactly the parser's grammar (*, /, //); an Unitsml::Extender also works directly as an operand:

Unitsml::Unit.new("W").extender("/").unit("m", -1)   # == parse("W/m")
Unitsml::Unit.new("W") * Unitsml::Extender.new("/") * Unitsml::Unit.new("m", -1)

The rendered separator glyph is otherwise a display choice — pass multiplier: (:space, :nospace, or a custom string such as "·") to any to_* method.

Validation

Input is validated at the compose boundary, so bad input fails fast with a Unitsml::Errors::* (all < Unitsml::Errors::BaseError) instead of crashing at render: an unknown unit/dimension/prefix, a units/dimensions mix, an empty composition, a power the parser cannot express (e.g. a decimal exponent), or a misplaced separator (Errors::MisplacedExtenderError).
Units and dimensions are referenced by symbol id, exactly as in a parsed string.
This PR also resolves quantity by NIST id or name and emits a canonical <Quantity> element in XML.

Closes #2

suleman-uzair added 9 commits July 1, 2026 23:08
Compose units from parts instead of only parsing strings. Adds the multiplication and division operator DSL (on Unit, Dimension and Formula via a Composable mixin) and the Unitsml.compose keyword form, both producing a Formula whose output matches the equivalent parsed string across every format. Units are referenced by symbol id or short name, powers come from the constructor, and quantity/name/multiplier metadata are passed to the render methods.

Also resolves quantities by name and short slug (Quantities#find_by_name) and emits a canonical Quantity element with the NIST xml:id, quantityType and dimensionURL.

Implements #2.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a programmatic composite-unit construction API to complement Unitsml.parse, adding operator (*//) and fluent-chain composition for Unit/Dimension/Formula, plus a Unitsml.compose keyword builder. It also enhances quantity resolution (by name as well as id) and updates XML emission to output canonical NIST xml:id/quantityType metadata and better dimensionURL handling for composites.

Changes:

  • Added composition framework (Unitsml::Compose::*) powering operator DSL, fluent chain, and Unitsml.compose.
  • Added new validation errors for builder inputs (unknown unit/dimension/prefix, mixed terms, invalid powers, empty composition, invalid entries).
  • Improved quantity lookup and XML emission (canonical NIST id + quantityType, case-insensitive name lookup, explicit-quantity behavior).

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
spec/unitsml/utility_quantity_spec.rb Adds tests for quantity lookup by name and canonical <Quantity> XML emission.
spec/unitsml/composite_spec.rb Adds extensive coverage for programmatic composition surfaces and validation behavior.
lib/unitsml/utility.rb Extends quantity resolution and quantity XML emission; threads composite dimension info into <Quantity>.
lib/unitsml/unitsdb/quantities.rb Adds find_by_name for case-insensitive English/short quantity resolution.
lib/unitsml/unit.rb Adds composition mixin; validates unit references and prefixes in the constructor path.
lib/unitsml/opal.rb Requires new compose components and new error classes in Opal builds.
lib/unitsml/number.rb Normalizes stored numeric value to a String to avoid downstream crashes.
lib/unitsml/formula.rb Integrates composed-formula term contribution; adds render-option overrides for metadata; passes composite dims to quantity emission.
lib/unitsml/errors/unknown_unit_error.rb New error for invalid unit references.
lib/unitsml/errors/unknown_prefix_error.rb New error for invalid prefix references.
lib/unitsml/errors/unknown_dimension_error.rb New error for invalid dimension references.
lib/unitsml/errors/mixed_terms_error.rb New error for unit/dimension mixing in one expression.
lib/unitsml/errors/invalid_unit_entry_error.rb New error for invalid compose entries/operands/metadata.
lib/unitsml/errors/invalid_power_error.rb New error for powers the parser cannot express (e.g., decimal floats).
lib/unitsml/errors/empty_composition_error.rb New error for missing/empty units: / dimensions: in compose.
lib/unitsml/errors.rb Autoloads newly introduced builder-related error classes.
lib/unitsml/dimension.rb Adds composition mixin and parser-style source text synthesis for dimensions.
lib/unitsml/compose/term_tree.rb Adds a shared traversal/mapping abstraction for composition term trees.
lib/unitsml/compose/composite.rb Implements Unitsml.compose keyword form normalization/validation/coercion.
lib/unitsml/compose/composable.rb Implements operator DSL + fluent chain + metadata attachment.
lib/unitsml/compose/builder.rb Builds composed root Formulas without mutating operands; validates mixed terms and metadata.
lib/unitsml/compose.rb Adds shared validators for unit/dimension refs, prefixes, and exponent normalization.
lib/unitsml.rb Exposes Unitsml.compose and autoloads Compose.
docs/README.adoc Documents programmatic composition APIs, validation rules, and render-time metadata overrides.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/unitsml/unit.rb
Comment thread lib/unitsml/unit.rb
Comment thread lib/unitsml/dimension.rb
Comment thread lib/unitsml/utility.rb Outdated
Comment thread lib/unitsml/compose.rb
Comment thread lib/unitsml/errors/invalid_power_error.rb

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated 1 comment.

Comment thread lib/unitsml/compose/builder.rb
@suleman-uzair suleman-uzair changed the title [WIP] Add programmatic composite unit builder Add programmatic composite unit builder Jul 7, 2026
@suleman-uzair
suleman-uzair requested a review from Copilot July 8, 2026 07:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 3 comments.

Comment thread lib/unitsml/power_numerator.rb Outdated
Comment thread docs/README.adoc Outdated
Comment thread lib/unitsml/errors/unknown_dimension_error.rb Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 3 comments.

Comment thread lib/unitsml/utility.rb Outdated
Comment thread lib/unitsml/formula.rb
Comment thread lib/unitsml/unitsdb/quantities.rb

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 2 comments.

Comment thread lib/unitsml/compose.rb Outdated
Comment thread lib/unitsml/power_numerator.rb Outdated
@suleman-uzair
suleman-uzair marked this pull request as ready for review July 8, 2026 17:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement composite units

2 participants