Why “Units” Not “Dimensionality” - User-Facing Terminology¶
Date: 2025-01-07 Decision: Merge DimensionalityMixin INTO UnitAwareMixin, keep “units” terminology Reason: User communication and API consistency
The User Perspective¶
What Users Say:¶
✅ “This variable has units of meters”
✅ “I need to convert units to kilometers”
✅ “The units don’t match”
❌ “This variable has dimensionality [length]”
❌ “I need to convert dimensionality”
What Users Understand:¶
Units: ✅ Concrete, everyday concept (meters, seconds, kilograms)
Dimensionality: ⚠️ Abstract physics concept ([length], [time], [mass])
What the API Shows:¶
# User-friendly - clear what's happening
var.units = "m"
var.to("km")
mesh.units = "km"
# Less clear - what does this mean?
var.dimensionality = ???
var.to_dimensionality(???)
Current Investment in “Units” Terminology¶
Documentation:¶
“Units System” (not “Dimensionality System”)
“Unit-Aware Arrays”
“Unit Conversion”
“Units Tests” (test_07*units*.py, test_08**.py)
Classes:¶
UnitAwareArray✅UnitAwareExpression✅UWQuantity(units, not dimensionality) ✅
Properties:¶
.units(everywhere).has_units(everywhere).to()- unit conversion
Functions:¶
uw.get_units()uw.quantity()- creates unit-aware quantity
Files:¶
units.pyunit_conversion.pyunit_aware_array.pytest_0700_units_system.pyUNITS_REFACTOR_PLAN.mdUNITS_TEST_RESULTS_BASELINE.md
The Problem with “Dimensionality”¶
1. Not User-Facing¶
Dimensionality is an implementation detail:
[length]¹ [time]⁻¹ is the dimensionality of velocity
“m/s” is the units of velocity
Users care about “m/s”, not “[length]/[time]”
2. Technical Jargon¶
“Dimensionality” is physics jargon:
Most users won’t know what it means
Even physicists say “units” in casual conversation
It’s a barrier to adoption
3. API Confusion¶
# Which is clearer?
var.units = "m" # ✅ Clear
var.dimensionality = "[L]" # ❌ What?
# Which is more intuitive?
var.to("km") # ✅ Obvious
var.to_dimensionality(...) # ❌ How?
What About Non-Dimensionalization?¶
The Key Insight:¶
Non-dimensionalization is a UNITS operation!
# Non-dimensionalization example
var.units = "m"
var.scaling_coefficient = 1000 # meters
# Get non-dimensional value
nd_value = var.nd_array # Array divided by 1000
# The scaling coefficient HAS UNITS (meters)!
# So non-dimensionalization IS part of the units system
Better Terminology:¶
“Reference units” instead of “dimensionality”
“Scaled units” instead of “non-dimensional”
“Unit scaling” instead of “dimensionalization”
Correct Architecture: Units First¶
UnitAwareMixin (keep this name!)¶
class UnitAwareMixin:
"""
Mixin for units tracking, conversion, and scaling.
Provides:
- Units tracking (meters, kelvin, etc.)
- Unit conversion (m → km)
- Dimensional analysis via Pint
- Non-dimensionalization (scaling)
- Reference scales
"""
def __init__(self, *args, units=None, **kwargs):
super().__init__(*args, **kwargs)
# ========================================
# UNITS TRACKING
# ========================================
self._units = None
self._pint_backend = None # Lazy init
# ========================================
# SCALING / NON-DIMENSIONALIZATION
# (absorbed from DimensionalityMixin)
# ========================================
self._scaling_coefficient = 1.0
self._is_nondimensional = False
self._original_units = None
if units:
self.set_units(units)
# ========================================
# UNITS INTERFACE (user-facing)
# ========================================
@property
def units(self) -> Optional[str]:
"""Get units string (e.g., 'm', 'kg/s')."""
if self._is_nondimensional:
return None
return self._units
@property
def has_units(self) -> bool:
"""Check if object has units."""
return self._units is not None and not self._is_nondimensional
def set_units(self, units: str):
"""Set units (e.g., 'm', 'kelvin')."""
...
def to(self, target_units: str):
"""Convert to different units."""
...
def check_units_compatibility(self, other) -> bool:
"""Check if units are compatible."""
...
# ========================================
# DIMENSIONALITY (implementation detail)
# ========================================
@property
def dimensionality(self) -> Optional[dict]:
"""
Get Pint dimensionality dict.
This is an advanced property for dimensional analysis.
Most users should use .units instead.
"""
if not self._units:
return None
backend = self._get_backend()
qty = backend.create_quantity(1.0, self._units)
return backend.get_dimensionality(qty)
# ========================================
# SCALING / NON-DIMENSIONALIZATION
# (absorbed from DimensionalityMixin)
# ========================================
@property
def scaling_coefficient(self) -> float:
"""Get reference scale for non-dimensionalization."""
return self._scaling_coefficient
@scaling_coefficient.setter
def scaling_coefficient(self, value):
"""Set reference scale (can have units)."""
...
@property
def nd_array(self):
"""Get non-dimensional array (array / scaling_coefficient)."""
return np.array(self.array) / self._scaling_coefficient
def from_nd(self, nd_value):
"""Convert from non-dimensional (nd_value * scaling_coefficient)."""
return nd_value * self._scaling_coefficient
def set_reference_scale(self, scale):
"""Set reference scale for non-dimensionalization."""
self.scaling_coefficient = scale
Migration Strategy: Absorb DimensionalityMixin¶
Step 1: Enhance UnitAwareMixin¶
File: src/underworld3/utilities/units_mixin.py
Add from DimensionalityMixin:
_scaling_coefficientattribute_is_nondimensionalattribute_original_unitsattribute.scaling_coefficientproperty.nd_arrayproperty.from_nd()method.set_reference_scale()method
Keep existing:
All current units functionality
.units,.has_units,.dimensionalityproperties.set_units(),.to()methodsPint backend integration
Result: UnitAwareMixin has everything
Step 2: Update Classes Using DimensionalityMixin Only¶
SwarmVariable (swarm.py):
# BEFORE
class SwarmVariable(DimensionalityMixin, MathematicalMixin, ...):
def __init__(self, ...):
DimensionalityMixin.__init__(self)
...
# AFTER
class SwarmVariable(UnitAwareMixin, MathematicalMixin, ...):
def __init__(self, ...):
UnitAwareMixin.__init__(self)
...
_MeshVariable (discretisation_mesh_variables.py):
# BEFORE
class _MeshVariable(DimensionalityMixin, MathematicalMixin, ...):
pass
# AFTER
class _MeshVariable(UnitAwareMixin, MathematicalMixin, ...):
pass
Step 3: Update Classes Using Both Mixins¶
UWQuantity (function/quantities.py):
# BEFORE
class UWQuantity(DimensionalityMixin, UnitAwareMixin):
pass
# AFTER
class UWQuantity(UnitAwareMixin):
pass # UnitAwareMixin has everything now!
EnhancedMeshVariable (discretisation/persistence.py):
# BEFORE
class EnhancedMeshVariable(DimensionalityMixin, UnitAwareMixin, MathematicalMixin):
pass
# AFTER
class EnhancedMeshVariable(UnitAwareMixin, MathematicalMixin):
pass
Step 4: Delete DimensionalityMixin¶
File: src/underworld3/utilities/dimensionality_mixin.py
Action: Delete entire file (functionality absorbed into UnitAwareMixin)
Step 5: Update All Imports¶
# Find all imports
grep -r "from.*dimensionality_mixin import" src/
# Replace with
from underworld3.utilities.units_mixin import UnitAwareMixin
Why This Is Better¶
1. Consistent Terminology ✅¶
Everything is “units”:
UnitAwareArrayUnitAwareExpressionUnitAwareMixin.unitspropertyget_units()functionUnits tests
2. User-Friendly ✅¶
Users understand “units”:
“Set the units to meters”
“Convert units to kilometers”
“The units don’t match”
Users don’t understand “dimensionality”:
“Set the dimensionality to [length]” ❌
What does that even mean?
3. API Clarity ✅¶
# Clear and intuitive
var.units = "m"
var.to("km")
var.has_units
# Not clear
var.dimensionality = ???
var.has_dimensionality
4. Documentation Consistency ✅¶
All docs say “units”:
UNITS_REFACTOR_PLAN.md
UNITS_TEST_RESULTS_BASELINE.md
test_07*_units*.py
Unit-aware arrays, expressions, quantities
5. Less Cognitive Load ✅¶
One concept to learn: Units
Units can be concrete (“m”, “kg/s”)
Units can be abstract ([length], [mass]/[time])
Units can be scaled (non-dimensionalization)
All under “units” umbrella
Counter-Arguments Addressed¶
“But dimensionality is the correct physics term!”¶
Response: Yes, but it’s an implementation detail.
Under the hood: We use Pint’s dimensionality system
User-facing: We expose “units” API
Property:
.dimensionalitystill exists for advanced users
# User-facing (simple)
var.units = "m/s"
# Advanced/internal (complex)
var.dimensionality # {'[length]': 1, '[time]': -1}
“Dimensionality is more general!”¶
Response: Units are already general.
Units can be compound: “kg·m/s²”
Units can be derived: “Pa”, “N”, “J”
Units have dimensionality: “Pa” → [mass]/([length]·[time]²)
Units subsume dimensionality
“Non-dimensionalization is about dimensions!”¶
Response: Non-dimensionalization is about scaled units.
# The scaling coefficient HAS UNITS
var.scaling_coefficient = UWQuantity(1000, "m")
# Non-dimensional means "divided by reference units"
nd_value = var.array / var.scaling_coefficient # Still unit arithmetic!
Estimated Effort¶
Merge DimensionalityMixin INTO UnitAwareMixin¶
Phase 1: Enhance UnitAwareMixin
Add scaling attributes (3 attributes)
Add scaling methods (3 methods)
Keep all existing functionality
Time: 2-3 hours
Phase 2: Migrate Classes
Update 4 production classes
Simple import/inheritance changes
Time: 2 hours
Phase 3: Remove DimensionalityMixin
Delete dimensionality_mixin.py
Update all imports (6-8 files)
Time: 1 hour
Phase 4: Testing
Run full test suite
Verify closure tests (24/30)
Verify units tests (85/85)
Time: 1-2 hours
Total: 6-8 hours
Comparison to other direction: Same effort, but better result!
Success Criteria¶
After Completion:¶
✅ Single mixin:
UnitAwareMixin✅ Consistent terminology: “units” everywhere
✅ All scaling functionality available
✅
.dimensionalitystill exists (for advanced users)✅ All tests passing (24/30 closure, 85/85 units)
✅ Clearer user-facing API
Recommendation: Do This!¶
Why:¶
User-facing names matter - “units” is what users say
Investment protection - keeps all your “units” terminology
Same effort - 6-8 hours either direction
Better result - clearer API, consistent naming
When:¶
Not now - finish Phase 2 (complete ✅)
After Phase 3 - bug investigation
Part of Phase 4 - refactoring phase
How:¶
Enhance
UnitAwareMixinwith scaling functionalityMigrate classes from
DimensionalityMixintoUnitAwareMixinDelete
dimensionality_mixin.pyRemove deprecation warning from
units_mixin.py
This is the right call! Keep your investment in “units” terminology.