diff --git a/.github/ISSUE_TEMPLATE/architectural-review.yml b/.github/ISSUE_TEMPLATE/architectural-review.yml index 7039f713..cff03741 100644 --- a/.github/ISSUE_TEMPLATE/architectural-review.yml +++ b/.github/ISSUE_TEMPLATE/architectural-review.yml @@ -125,3 +125,6 @@ body: - Background discussions: Link to GitHub Discussions - Related PRs: #XXX, #YYY - External references: Papers, benchmarks, etc. + +# Reviewed for: Review System Infrastructure & GitHub Integration (Review #10, 2025-11-17) +# Part of formal architectural review process implementation diff --git a/.github/PULL_REQUEST_TEMPLATE/architectural-review.md b/.github/PULL_REQUEST_TEMPLATE/architectural-review.md index 0116d06c..15e7a8cb 100644 --- a/.github/PULL_REQUEST_TEMPLATE/architectural-review.md +++ b/.github/PULL_REQUEST_TEMPLATE/architectural-review.md @@ -129,3 +129,8 @@ Reviewers: Please review the full document at `docs/reviews/YYYY-MM/[NAME]-REVIE + + diff --git a/.github/workflows/architectural-review-validation.yml b/.github/workflows/architectural-review-validation.yml index 266235ba..2cfb22dd 100644 --- a/.github/workflows/architectural-review-validation.yml +++ b/.github/workflows/architectural-review-validation.yml @@ -174,3 +174,9 @@ jobs: repo: context.repo.repo, body: message }); + +# Reviewed for: Review System Infrastructure & GitHub Integration (Review #10, 2025-11-17) +# Part of formal architectural review process implementation + +# REVIEW HISTORY: +# - Review #10 (2025-11-17): Workflow created as part of Review System Infrastructure review diff --git a/CLAUDE.md b/CLAUDE.md index 86e9b412..1bcd6586 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -44,19 +44,28 @@ - **Useful for documentation**: Contains high-level overview, design rationale, key features explanation - **Mining potential**: Executive summaries, architecture descriptions, comparison with other tools -### Planning Documents -**Location**: `planning/` +### Design Documents +**Location**: `docs/developer/design/` - **Purpose**: Architecture plans, design documents, feature roadmaps - **Status**: Mix of historical plans and current/future designs +- **Changelog**: See `docs/developer/CHANGELOG.md` for quarterly-reportable work #### Feature Plans (Historical - verify against implementation) - `parameter_system_plan.md` - Parameter system design (note: current implementation differs) - `material_properties_plan.md` - Material properties architecture - `mathematical_objects_plan.md` - Mathematical objects design (✅ IMPLEMENTED) - `claude_examples_plan.md` - Example usage patterns -- `units_system_plan.md` - Units and dimensional analysis system +- `units_system_plan.md` - ⚠️ **SUPERSEDED** by `UNITS_SIMPLIFIED_DESIGN_2025-11.md` - `MultiMaterial_ConstitutiveModel_Plan.md` - Multi-material constitutive models +#### Units System (✅ SIMPLIFIED 2025-11) +- **`UNITS_SIMPLIFIED_DESIGN_2025-11.md`** - **AUTHORITATIVE**: Current units architecture + - Gateway pattern: units at boundaries, not during symbolic ops + - `UWQuantity`: lightweight Pint-backed numbers + - `UWexpression`: preferred user-facing lazy wrapper + - Arithmetic closure: operations return unit-preserving types + - See this document for implementation requirements + #### Parallel Safety System (✅ IMPLEMENTED 2025-01-24) - `PARALLEL_PRINT_SIMPLIFIED.md` - **Main design**: `uw.pprint()` and `selective_ranks()` (✅ **IMPLEMENTED**) - `RANK_SELECTION_SPECIFICATION.md` - Comprehensive rank selection syntax (✅ **IMPLEMENTED**) @@ -72,6 +81,232 @@ **Documentation Strategy**: Mine planning documents for important information to consolidate into developer guide (`docs/developer/`), then clean up planning directory to avoid repository clutter. Developer guide should serve dual purpose as implementation reference and code patterns guide. +## Units System Design Principles ⚠️ + +### CRITICAL: String Input, Pint Object Storage (2025-11-19) + +**Principle**: Accept strings for user convenience, but ALWAYS store and return Pint objects internally. + +**Why This Matters**: +- **User API**: Strings are convenient and readable (`"Pa*s"` vs `ureg.pascal * ureg.second`) +- **Internal Operations**: Pint objects enable dimensional analysis, unit arithmetic, compatibility checking +- **Type Violations**: Returning strings from `.units` property breaks the units protocol + +**Pattern (CORRECT)**: +```python +# 1. User creates quantity with string (convenience) +viscosity = uw.quantity(1e21, "Pa*s") + +# 2. Internally convert and store as Pint object +class UWQuantity: + def __init__(self, value, units: Optional[str] = None): + if units is not None: + from ..scaling import units as ureg + self._pint_qty = value * ureg.parse_expression(units) # String → Pint + self._has_pint_qty = True + +# 3. Return Pint objects from properties (NOT strings!) + @property + def units(self): + """Get the units object for this quantity.""" + if self._has_pint_qty: + return self._pint_qty.units # Pint Unit object + return None + +# 4. Arithmetic works correctly with Pint objects +Ra = (rho0 * alpha * g * DeltaT * L**3) / (eta0 * kappa) # Units combine properly +``` + +**Anti-Pattern (WRONG)**: +```python +# DON'T return strings from .units property! +@property +def units(self) -> str: # Type hint forces wrong behavior + return str(self._pint_qty.units) # ❌ Converts to string - breaks dimensional analysis! + +# This causes errors: +model.get_scale_for_dimensionality(qty.units) +# AttributeError: 'str' object has no attribute 'items' +# Because dimensionality checking expects Pint objects, not strings! +``` + +**Historical Bug (2025-11-19)**: +- Added type annotation `-> str` to UWQuantity.units property +- Forced string conversion: `return str(self._pint_qty.units)` +- Broke Rayleigh number calculations and all unit arithmetic +- Fixed by removing type hint and returning raw Pint object + +**Testing Checklist**: +- ✅ Accept string inputs: `uw.quantity(5, "cm/year")` +- ✅ Store as Pint internally: `isinstance(qty._pint_qty, pint.Quantity)` +- ✅ Return Pint from properties: `isinstance(qty.units, pint.Unit)` +- ✅ Unit arithmetic works: `(qty1 * qty2).units` has correct dimensions +- ✅ Dimensional analysis works: `model.get_scale_for_dimensionality(qty.units)` doesn't crash + +### CRITICAL: Pint Unit vs Quantity Distinction (2025-11-19) + +**Principle**: Understand the difference between Pint **Unit** objects and **Quantity** objects. + +**The Distinction**: +```python +# Pint Quantity = value + units together +qty = 5 * ureg.meter # Quantity: has both magnitude and units +qty.magnitude # 5 +qty.units # +qty.to("km") # ✅ Can convert (has value) +qty.to_base_units() # ✅ Can convert (has value) +qty.to_reduced_units() # ✅ Can simplify (has value) + +# Pint Unit = just the unit, no value +unit = ureg.meter # Unit: just the unit definition +unit.dimensionality # ✅ Can check dimensions +unit.to("km") # ❌ AttributeError - no value to convert +unit.to_base_units() # ❌ AttributeError - no value to convert +``` + +**UWQuantity Architecture**: +```python +qty = uw.quantity(2900, "km") + +# Public API: +qty.value # 2900 (numeric value) +qty.units # - Pint Unit object (not Quantity!) +qty.magnitude # 2900 (alias for .value) + +# Conversion methods (work on UWQuantity, not on .units): +qty.to("m") # ✅ Returns new UWQuantity +qty.to_base_units() # ✅ Returns new UWQuantity +qty.to_reduced_units() # ✅ Returns new UWQuantity +qty.to_compact() # ✅ Returns new UWQuantity + +# WRONG - these fail because .units is a Unit, not a Quantity: +qty.units.to("m") # ❌ AttributeError +qty.units.to_base_units() # ❌ AttributeError +qty.units.to_reduced_units() # ❌ AttributeError + +# Internal (not part of public API): +qty._pint_qty # Full Pint Quantity object (2900 kilometer) +qty._pint_qty.to_base_units() # ✅ Works but uses private API +``` + +**Why This Matters**: +1. **`.units` is for inspection**: Check what units something has, compare compatibility +2. **Conversion methods on UWQuantity**: Use the full object, not just `.units` +3. **Error messages are correct**: `AttributeError: 'Unit' object has no attribute 'to_compact'` is expected behavior + +**Common Mistakes**: +```python +# WRONG +L = uw.quantity(2900, "km") +L.units.to_base_units() # ❌ Unit has no to_base_units method + +# CORRECT +L = uw.quantity(2900, "km") +L.to_base_units() # ✅ Returns UWQuantity(2900000, "m") +``` + +**Unit Simplification for Dimensionless Quantities**: +```python +# Problem: Mixed units create complex expressions +Ra = (rho0 * alpha * g * DeltaT * L**3) / (eta0 * kappa) +# With L in km, this shows: "kg * km³ / m⁴ / Pa / s²" +# Even though it's dimensionless! + +# Solution: Use to_reduced_units() to simplify +Ra_clean = Ra.to_reduced_units() +# Shows: "7.1e6 dimensionless" (properly simplified) + +# Then extract magnitude for calculations +Ra_value = float(Ra_clean.magnitude) # 7100000.0 +``` + +**Historical Issue (2025-11-19)**: +- User tried `L.units.to_compact()` and got AttributeError +- This is **correct behavior** - Units alone can't be compacted +- Only full Quantities (value + units) support conversion methods + +### CRITICAL: Unit Conversion on Composite Expressions (2025-11-25) ✅ FIXED + +**Problem Solved**: `.to_base_units()` and `.to_reduced_units()` were causing evaluation errors on composite expressions. + +**Root Cause**: +- Methods embedded conversion factors in expression tree: `new_expr = expr * 5617615.15` +- During nondimensional evaluation cycles, factors were **double-applied** +- Example: `sqrt((kappa * t_now))**0.5` would evaluate to wrong value after conversion + +**Fix Applied**: +- Composite expressions (containing UWexpression symbols): Only change display units, no factor embedding +- Simple expressions (no symbols): Apply conversion factors as before +- Issues UserWarning when display-only conversion occurs + +**User Guidance**: +```python +# ✅ RECOMMENDED: Use .to_compact() for unit simplification +sqrt_expr = ((kappa * t_now))**0.5 +display_expr = sqrt_expr.to_compact() # Automatic readable units, no warning + +# ⚠️ WORKS BUT WARNS: Use .to_base_units() or .to_reduced_units() +display_expr = sqrt_expr.to_base_units() # Display units only, with warning +# UserWarning: "changing display units only..." + +# ✅ SIMPLE EXPRESSIONS: Conversion factor applied +velocity = uw.quantity(5, "km/hour") +velocity_ms = velocity.to_base_units() # → 1.38889 m/s (actually converts) +``` + +**Verification**: All evaluation bugs fixed ✅ +- `evaluate(expr.to_base_units())` now equals `evaluate(expr)` +- System is "bulletproof" for evaluation with nondimensional scaling +- See: `docs/reviews/2025-11/UNITS-EVALUATION-FIXES-2025-11-25.md` + +### CRITICAL: Transparent Container Principle (2025-11-26) + +**Principle**: A container cannot know in advance what it contains. If an object is lazy-evaluated, its properties must also be lazy-evaluated. + +**The Atomic vs Container Distinction**: +| Type | Role | What it stores | +|------|------|----------------| +| **UWQuantity** | Atomic leaf node | Value + Units (indivisible, this IS the data) | +| **UWexpression** | Container | Reference to contents only (derives everything) | + +**Why This Matters**: +- **UWexpression is always a container**, whether wrapping: + - A UWQuantity (atomic) → derives `.units` from `self._value_with_units.units` + - A SymPy tree (composite) → derives `.units` from `get_units(self._sym)` +- **The container never "owns" units** - it provides access to what's inside +- **No cached state on composites** - eliminates sync issues between stored and computed values + +**Implementation Pattern**: +```python +class UWexpression: + @property + def units(self): + # Always derived, never stored separately + if self._value_with_units is not None: + return self._value_with_units.units # From contained atom + return get_units(self._sym) # From contained tree + + def __mul__(self, other): + if isinstance(other, UWexpression): + # Return raw SymPy product - units derived on demand via get_units() + # This preserves lazy evaluation and eliminates sync issues + return Symbol.__mul__(self, other) +``` + +**Anti-Pattern (WRONG)**: +```python +# DON'T store computed units on composite results! +def __mul__(self, other): + if isinstance(other, UWexpression): + result = Symbol.__mul__(self, other) + result._units = self.units * other.units # ❌ Creates sync liability + return result # ❌ Also fails: SymPy Mul is immutable! +``` + +**Key Insight**: If you design an object to be lazily evaluated, it's inconsistent to eagerly compute and store properties. Caching creates sync liability and violates the laziness contract. + +**See**: `docs/developer/design/UNITS_SIMPLIFIED_DESIGN_2025-11.md` for full architectural details. + ## Project Context Migrating Underworld3 from access context manager pattern to direct data access using NDArray_With_Callback for backward compatibility. @@ -137,8 +372,8 @@ with uw.selective_ranks(0) as should_execute: **See**: - `src/underworld3/mpi.py` - **Implementation** of `pprint()` and `selective_ranks()` - `docs/advanced/parallel-computing.qmd` - **User documentation** with comprehensive examples and migration guide -- `planning/PARALLEL_PRINT_SIMPLIFIED.md` - Original design document -- `planning/RANK_SELECTION_SPECIFICATION.md` - Complete rank selection syntax specification +- `docs/developer/design/PARALLEL_PRINT_SIMPLIFIED.md` - Original design document +- `docs/developer/design/RANK_SELECTION_SPECIFICATION.md` - Complete rank selection syntax specification ## Architecture Priorities & Module Purposes @@ -304,6 +539,174 @@ Tests reorganized by complexity level for better execution order: - Stokes solvers (1010-1050) - Advection-diffusion (1100-1120) +## Test Classification: Integrated Levels + Reliability Tiers (2025-11-15) + +### Dual Classification System + +Underworld3 uses **two orthogonal dimensions** to classify tests: + +1. **Test Levels (Number Prefix)** - Complexity/Scope (existing system from `scripts/test_levels.sh`) +2. **Reliability Tiers (Letter Markers)** - Trust Level (new system, see `docs/developer/TESTING-RELIABILITY-SYSTEM.md`) + +### Test Levels (Pytest Markers) - What Kind of Test + +**IMPORTANT**: Number prefixes (0000-9999) are for **organization only**. Actual complexity is marked explicitly. + +**Level 1** (`@pytest.mark.level_1`): Quick Core Tests +- Imports, basic setup, simple operations +- No solving, minimal computation +- Runtime: Seconds +- Examples: + - test_0000_imports.py - Basic imports + - test_1010_stokes_setup.py - Stokes mesh/variable setup (no solve) + - test_1015_stokes_bc_validation.py - Boundary condition checks (no solve) + +**Level 2** (`@pytest.mark.level_2`): Intermediate Tests +- Integration tests, units, regression +- May involve solving but simple cases +- Runtime: Minutes +- Examples: + - test_0700_units_system.py - Core units functionality + - test_0813_mesh_variable_ordering.py - Regression test + - test_1010_stokes_simple_solve.py - Basic Stokes solve (small mesh) + +**Level 3** (`@pytest.mark.level_3`): Physics/Solver Tests +- Complex solvers, time-stepping, benchmarks +- Full physics validation +- Runtime: Minutes to hours +- Examples: + - test_1010_stokes_benchmark.py - Stokes solver benchmark + - test_1110_advdiff_time_stepping.py - Time-dependent problems + - test_1150_coupled_stokes_advdiff.py - Coupled systems + +**Number Prefix Organization** (for ordering only): +- 0000-0499: Core functionality (imports, meshes, data access) +- 0500-0599: Enhanced arrays and migration +- 0600-0699: Regression tests +- 0700-0799: Units system +- 0800-0899: Unit-aware integration +- 1000-1099: Poisson/Darcy +- 1100-1199: Stokes flow +- 1200+: Advection-diffusion, coupled systems + +**Run by level**: +- `pytest -m level_1` (quick checks, ~1-2 min total) +- `pytest -m level_2` (intermediate, ~5-10 min total) +- `pytest -m "level_1 or level_2"` (skip heavy physics) +- `pixi run underworld-test` (uses number ranges, still works) + +### Reliability Tiers (Pytest Markers) - How Much to Trust + +**Tier A** (`@pytest.mark.tier_a`): Production-Ready +- Trusted for Test-Driven Development (TDD) and CI +- Long-lived (>3 months), consistently passing +- Failure indicates DEFINITE regression +- Examples: Core Stokes tests, basic mesh creation, stable units tests + +**Tier B** (`@pytest.mark.tier_b`): Validated (Use with Caution) +- Passed at least once, but not battle-tested +- New features (<3 months) or recently refactored +- Failure could be test OR code issue - needs investigation +- Examples: Recently added units integration, new reduction operations + +**Tier C** (`@pytest.mark.tier_c`): Experimental (Development Only) +- Feature may not be fully implemented +- Test OR code (or both) may be incorrect +- Mark with `@pytest.mark.xfail(reason="...")` if expected to fail +- Examples: Unimplemented features, tests under active development + +**Run by tier**: `pytest -m tier_a` (TDD-safe), `pytest -m "tier_a or tier_b"` (full validation) + +### Combined Examples: Levels + Tiers + +**Example 1**: Core units test +```python +@pytest.mark.level_2 # Intermediate - has some complexity +@pytest.mark.tier_a # Production-ready - trusted for TDD +def test_units_conversion(): + """Test basic unit conversion.""" + # File: test_0700_units_system.py (number = organization) +``` + +**Example 2**: Simple Stokes setup (no solving) +```python +@pytest.mark.level_1 # Quick - just setup, no computation +@pytest.mark.tier_a # Production-ready - stable API +def test_stokes_mesh_variable_creation(): + """Test creating Stokes mesh and variables.""" + # File: test_1010_stokes_basic.py (lives in 1010 but Level 1!) +``` + +**Example 3**: Complex Stokes benchmark +```python +@pytest.mark.level_3 # Physics - full solver with benchmarking +@pytest.mark.tier_a # Production-ready - validated against published results +def test_stokes_sinking_block_benchmark(): + """Test Stokes solver against analytical solution.""" + # File: test_1010_stokes_benchmark.py (1010 + Level 3) +``` + +**Example 4**: Experimental units feature +```python +@pytest.mark.level_2 # Intermediate complexity +@pytest.mark.tier_c # Experimental - feature in development +@pytest.mark.xfail(reason="Advanced units propagation not yet implemented") +def test_units_symbolic_propagation(): + """Test automatic unit propagation through symbolic operations.""" + # File: test_0850_units_propagation.py +``` + +**Key Insight**: Number prefix ≠ Level marker! +- `test_1010_stokes_basic.py` could have both Level 1 (setup) AND Level 3 (benchmark) tests +- Organization by topic (1010 = Stokes), not complexity + +### Integration with Pixi Tasks + +```bash +# === By number range (existing system, still works) === +pixi run underworld-test 1 # Run 0000-0499 tests +pixi run underworld-test 2 # Run 0500-0899 tests +pixi run underworld-test 3 # Run 1000+ tests +pixi run underworld-test # Run all tests + +# === By complexity level (new, more flexible) === +pytest -m level_1 # Quick tests only (~1-2 min) +pytest -m level_2 # Intermediate tests (~5-10 min) +pytest -m level_3 # Physics tests (~10+ min) +pytest -m "level_1 or level_2" # Everything except heavy physics + +# === By reliability tier (new, for TDD) === +pytest -m tier_a # Production-ready only (TDD-safe) +pytest -m "tier_a or tier_b" # Full validation suite +pytest -m "not tier_c" # Exclude experimental tests + +# === Combined filtering (powerful!) === +# Quick validation before commit +pytest -m "level_1 and tier_a" + +# All Stokes tests that are production-ready +pytest tests/test_1*stokes*.py -m tier_a + +# Intermediate tests, exclude experimental +pytest -m "level_2 and not tier_c" + +# Fast TDD cycle: Level 1+2, Tier A only +pytest -m "(level_1 or level_2) and tier_a" -v +``` + +### Current Classification Status (2025-11-15) + +**Immediate Actions**: +1. ✅ **FIXED**: JIT unwrapping bug (test_0818_stokes_nd.py: all 5 tests passing) +2. 🔄 **IN PROGRESS**: Classify 79 failing units tests into Tiers B or C +3. 📋 **TODO**: Mark all Tier A tests with `@pytest.mark.tier_a` +4. 📋 **TODO**: Mark incomplete features as Tier C with `@pytest.mark.xfail` + +**Key Documents**: +- **System Overview**: `docs/developer/TESTING-RELIABILITY-SYSTEM.md` +- **Current Analysis**: `docs/developer/TEST-CLASSIFICATION-2025-11-15.md` +- **Test Script**: `scripts/test_levels.sh` + ## Symmetric Tensor Fix (Latest) **Problem**: For symmetric tensors, `num_components` (6 in 3D) ≠ array components (9 in 3D) - `array` shape: `(N, 3, 3)` = 9 components (full tensor) diff --git a/closure_test_results.txt b/closure_test_results.txt deleted file mode 100644 index ff9f4acc..00000000 --- a/closure_test_results.txt +++ /dev/null @@ -1,183 +0,0 @@ -============================= test session starts ============================== -platform darwin -- Python 3.12.11, pytest-8.4.1, pluggy-1.6.0 -- /Users/lmoresi/+Underworld/underworld-pixi-2/.pixi/envs/default/bin/python3.12 -cachedir: .pytest_cache -rootdir: /Users/lmoresi/+Underworld/underworld-pixi-2/underworld3/tests -configfile: pytest.ini -plugins: mpi-0.6, anyio-4.9.0, timeout-2.4.0, typeguard-4.4.4 -collecting ... collected 30 items - -tests/test_0850_units_closure_comprehensive.py::test_closure_variable_multiply_variable FAILED [ 3%] -tests/test_0850_units_closure_comprehensive.py::test_units_temperature_times_velocity PASSED [ 6%] -tests/test_0850_units_closure_comprehensive.py::test_closure_temperature_times_velocity_component PASSED [ 10%] -tests/test_0850_units_closure_comprehensive.py::test_closure_scalar_times_variable FAILED [ 13%] -tests/test_0850_units_closure_comprehensive.py::test_closure_scalar_times_temperature_times_velocity_component PASSED [ 16%] -tests/test_0850_units_closure_comprehensive.py::test_units_scalar_preserves_variable_units PASSED [ 20%] -tests/test_0850_units_closure_comprehensive.py::test_closure_derivative_is_unit_aware PASSED [ 23%] -tests/test_0850_units_closure_comprehensive.py::test_units_temperature_derivative PASSED [ 26%] -tests/test_0850_units_closure_comprehensive.py::test_closure_second_derivative FAILED [ 30%] -tests/test_0850_units_closure_comprehensive.py::test_closure_temperature_divided_by_coordinate PASSED [ 33%] -tests/test_0850_units_closure_comprehensive.py::test_units_temperature_divided_by_length PASSED [ 36%] -tests/test_0850_units_closure_comprehensive.py::test_closure_variable_divided_by_variable PASSED [ 40%] -tests/test_0850_units_closure_comprehensive.py::test_closure_vector_component_access PASSED [ 43%] -tests/test_0850_units_closure_comprehensive.py::test_closure_vector_component_in_expression PASSED [ 46%] -tests/test_0850_units_closure_comprehensive.py::test_units_vector_component_preserves_units PASSED [ 50%] -tests/test_0850_units_closure_comprehensive.py::test_closure_mesh_coordinates_are_unit_aware PASSED [ 53%] -tests/test_0850_units_closure_comprehensive.py::test_closure_coordinate_in_expression PASSED [ 56%] -tests/test_0850_units_closure_comprehensive.py::test_units_coordinate_access PASSED [ 60%] -tests/test_0850_units_closure_comprehensive.py::test_units_addition_requires_compatible_units PASSED [ 63%] -tests/test_0850_units_closure_comprehensive.py::test_units_addition_incompatible_units_fails FAILED [ 66%] -tests/test_0850_units_closure_comprehensive.py::test_closure_variable_squared PASSED [ 70%] -tests/test_0850_units_closure_comprehensive.py::test_units_temperature_squared PASSED [ 73%] -tests/test_0850_units_closure_comprehensive.py::test_closure_complex_expression PASSED [ 76%] -tests/test_0850_units_closure_comprehensive.py::test_closure_derivative_of_product PASSED [ 80%] -tests/test_0850_units_closure_comprehensive.py::test_units_energy_like_expression PASSED [ 83%] -tests/test_0850_units_closure_comprehensive.py::test_closure_unit_aware_array_arithmetic PASSED [ 86%] -tests/test_0850_units_closure_comprehensive.py::test_closure_unit_aware_array_reductions PASSED [ 90%] -tests/test_0850_units_closure_comprehensive.py::test_closure_coordinate_operations PASSED [ 93%] -tests/test_0850_units_closure_comprehensive.py::test_closure_evaluate_returns_unit_aware FAILED [ 96%] -tests/test_0850_units_closure_comprehensive.py::test_summary_closure_property PASSED [100%] - -=================================== FAILURES =================================== -___________________ test_closure_variable_multiply_variable ____________________ -tests/test_0850_units_closure_comprehensive.py:105: in test_closure_variable_multiply_variable - assert hasattr(result, "units") or hasattr(result, "_units"), \ -E AssertionError: Variable * Variable should preserve unit-awareness -E assert (False or False) -E + where False = hasattr(Matrix([[{ \hspace{ 0.0004pt } {T} }(N.x, N.y)*{ \hspace{ 0.0004pt } {V} }_{ 0 }(N.x, N.y)]]), 'units') -E + and False = hasattr(Matrix([[{ \hspace{ 0.0004pt } {T} }(N.x, N.y)*{ \hspace{ 0.0004pt } {V} }_{ 0 }(N.x, N.y)]]), '_units') ----------------------------- Captured stdout setup ----------------------------- -Structured box element resolution 4 4 -______________________ test_closure_scalar_times_variable ______________________ -tests/test_0850_units_closure_comprehensive.py:144: in test_closure_scalar_times_variable - assert hasattr(result, "units") or hasattr(result, "_units"), \ -E AssertionError: Scalar * Variable should preserve unit-awareness -E assert (False or False) -E + where False = hasattr(Matrix([[2*{ \hspace{ 0.0019pt } {T} }(N.x, N.y)]]), 'units') -E + and False = hasattr(Matrix([[2*{ \hspace{ 0.0019pt } {T} }(N.x, N.y)]]), '_units') ----------------------------- Captured stdout setup ----------------------------- -Structured box element resolution 4 4 -________________________ test_closure_second_derivative ________________________ -tests/test_0850_units_closure_comprehensive.py:203: in test_closure_second_derivative - d2T_dx2 = dT_dx.diff(x) - ^^^^^^^^^^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/sympy/matrices/matrixbase.py:3416: in diff - deriv = ArrayDerivative(self, *args, evaluate=evaluate) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/sympy/tensor/array/array_derivatives.py:19: in __new__ - obj = super().__new__(cls, expr, *variables, **kwargs) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/sympy/core/function.py:1466: in __new__ - obj = cls._dispatch_eval_derivative_n_times(expr, v, count) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/sympy/tensor/array/array_derivatives.py:106: in _dispatch_eval_derivative_n_times - result = cls._call_derive_matrix_by_scalar(expr, v) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/sympy/tensor/array/array_derivatives.py:64: in _call_derive_matrix_by_scalar - return _matrix_derivative(expr, v) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/sympy/matrices/expressions/matexpr.py:538: in _matrix_derivative - return _matrix_derivative_old_algorithm(expr, x) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/sympy/matrices/expressions/matexpr.py:552: in _matrix_derivative_old_algorithm - lines = expr._eval_derivative_matrix_lines(x) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/sympy/core/expr.py:3986: in _eval_derivative_matrix_lines - return [_LeftRightArgs([S.One, S.One], higher=self._eval_derivative(x))] - ^^^^^^^^^^^^^^^^^^^^^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/sympy/matrices/matrixbase.py:3423: in _eval_derivative - return self.applyfunc(lambda x: x.diff(arg)) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/sympy/matrices/matrixbase.py:2108: in applyfunc - return self._eval_applyfunc(f) - ^^^^^^^^^^^^^^^^^^^^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/sympy/matrices/matrixbase.py:2040: in _eval_applyfunc - valmap = {v: f(v) for v in dok.values()} - ^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/sympy/matrices/matrixbase.py:3423: in - return self.applyfunc(lambda x: x.diff(arg)) - ^^^^^^^^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/sympy/core/expr.py:3606: in diff - return _derivative_dispatch(self, *symbols, **assumptions) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/sympy/core/function.py:1938: in _derivative_dispatch - return Derivative(expr, *variables, **kwargs) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/sympy/core/function.py:1466: in __new__ - obj = cls._dispatch_eval_derivative_n_times(expr, v, count) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/sympy/core/function.py:1927: in _dispatch_eval_derivative_n_times - return expr._eval_derivative_n_times(v, count) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/sympy/core/basic.py:1975: in _eval_derivative_n_times - obj = obj._eval_derivative(s) - ^^^^^^^^^^^^^^^^^^^^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/sympy/core/function.py:610: in _eval_derivative - df = self.fdiff(i) - ^^^^^^^^^^^^^ -src/underworld3/function/_function.pyx:91: in underworld3.function._function.UnderworldAppliedFunctionDeriv.fdiff - raise RuntimeError("Second derivatives of Underworld functions are not supported at this time.") -E RuntimeError: Second derivatives of Underworld functions are not supported at this time. ----------------------------- Captured stdout setup ----------------------------- -Structured box element resolution 4 4 -_________________ test_units_addition_incompatible_units_fails _________________ -tests/test_0850_units_closure_comprehensive.py:337: in test_units_addition_incompatible_units_fails - result = temperature_with_units.sym + velocity_with_units.sym[0] - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -E TypeError: unsupported operand type(s) for +: 'MutableDenseMatrix' and '{ \hspace{ 0.0084pt } {V} }_{ 0 }' - -During handling of the above exception, another exception occurred: -tests/test_0850_units_closure_comprehensive.py:344: in test_units_addition_incompatible_units_fails - assert "units" in str(e).lower() or "dimension" in str(e).lower(), \ -E AssertionError: Error should mention units/dimensions: unsupported operand type(s) for +: 'MutableDenseMatrix' and '{ \hspace{ 0.0084pt } {V} }_{ 0 }' -E assert ('units' in "unsupported operand type(s) for +: 'mutabledensematrix' and '{ \\hspace{ 0.0084pt } {v} }_{ 0 }'" or 'dimension' in "unsupported operand type(s) for +: 'mutabledensematrix' and '{ \\hspace{ 0.0084pt } {v} }_{ 0 }'") -E + where "unsupported operand type(s) for +: 'mutabledensematrix' and '{ \\hspace{ 0.0084pt } {v} }_{ 0 }'" = () -E + where = "unsupported operand type(s) for +: 'MutableDenseMatrix' and '{ \\hspace{ 0.0084pt } {V} }_{ 0 }'".lower -E + where "unsupported operand type(s) for +: 'MutableDenseMatrix' and '{ \\hspace{ 0.0084pt } {V} }_{ 0 }'" = str(TypeError("unsupported operand type(s) for +: 'MutableDenseMatrix' and '{ \\hspace{ 0.0084pt } {V} }_{ 0 }'")) -E + and "unsupported operand type(s) for +: 'mutabledensematrix' and '{ \\hspace{ 0.0084pt } {v} }_{ 0 }'" = () -E + where = "unsupported operand type(s) for +: 'MutableDenseMatrix' and '{ \\hspace{ 0.0084pt } {V} }_{ 0 }'".lower -E + where "unsupported operand type(s) for +: 'MutableDenseMatrix' and '{ \\hspace{ 0.0084pt } {V} }_{ 0 }'" = str(TypeError("unsupported operand type(s) for +: 'MutableDenseMatrix' and '{ \\hspace{ 0.0084pt } {V} }_{ 0 }'")) ----------------------------- Captured stdout setup ----------------------------- -Structured box element resolution 4 4 -___________________ test_closure_evaluate_returns_unit_aware ___________________ -../.pixi/envs/default/lib/python3.12/site-packages/underworld3/units.py:672: in dimensionalise - scale = model.get_scale_for_dimensionality(dimensionality) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/underworld3/model.py:2154: in get_scale_for_dimensionality - raise ValueError( -E ValueError: Cannot find scale for dimension 'temperature'. Available fundamental scales: ['length', 'time', 'mass']. Provide more reference quantities to derive this scale. - -During handling of the above exception, another exception occurred: -tests/test_0850_units_closure_comprehensive.py:487: in test_closure_evaluate_returns_unit_aware - result = uw.function.evaluate(temperature_with_units.sym, pts) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -../.pixi/envs/default/lib/python3.12/site-packages/underworld3/function/functions_unit_system.py:191: in evaluate - raw_result_dimensional = uw.dimensionalise( -../.pixi/envs/default/lib/python3.12/site-packages/underworld3/units.py:674: in dimensionalise - raise ValueError(f"Cannot compute scale for dimensionality {dimensionality}: {e}") -E ValueError: Cannot compute scale for dimensionality {'[temperature]': 1}: Cannot find scale for dimension 'temperature'. Available fundamental scales: ['length', 'time', 'mass']. Provide more reference quantities to derive this scale. ----------------------------- Captured stdout setup ----------------------------- -Structured box element resolution 4 4 -=============================== warnings summary =============================== -../.pixi/envs/default/lib/python3.12/site-packages/underworld3/utilities/__init__.py:32 - /Users/lmoresi/+Underworld/underworld-pixi-2/.pixi/envs/default/lib/python3.12/site-packages/underworld3/utilities/__init__.py:32: DeprecationWarning: The units_mixin module is deprecated and not used in production code. Use the hierarchical units system in enhanced_variables.py instead. This module is preserved only for historical reference. - from .units_mixin import ( - -test_0850_units_closure_comprehensive.py::test_closure_evaluate_returns_unit_aware - /Users/lmoresi/+Underworld/underworld-pixi-2/.pixi/envs/default/lib/python3.12/site-packages/underworld3/function/functions_unit_system.py:112: DeprecationWarning: unwrap() is deprecated and will be removed. Use expand() for user inspection or _unwrap_for_compilation() for solver code. - expr_unwrapped = fn_unwrap(expr) - --- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html -=========================== short test summary info ============================ -FAILED tests/test_0850_units_closure_comprehensive.py::test_closure_variable_multiply_variable -FAILED tests/test_0850_units_closure_comprehensive.py::test_closure_scalar_times_variable -FAILED tests/test_0850_units_closure_comprehensive.py::test_closure_second_derivative -FAILED tests/test_0850_units_closure_comprehensive.py::test_units_addition_incompatible_units_fails -FAILED tests/test_0850_units_closure_comprehensive.py::test_closure_evaluate_returns_unit_aware -================== 5 failed, 25 passed, 2 warnings in 11.81s =================== -Abort(868846735): Fatal error in internal_Finalize: Other MPI error, error stack: -internal_Finalize(50)............: MPI_Finalize failed -MPII_Finalize(441)...............: -MPID_Finalize(804)...............: -MPIDI_OFI_mpi_finalize_hook(1075): -flush_send_queue(1034)...........: -MPIDI_OFI_handle_cq_error(788)...: OFI poll failed (default nic=utun9: Input/output error) diff --git a/docs/beginner/tutorials/12-Units_System.ipynb b/docs/beginner/tutorials/12-Units_System.ipynb index 3b823f59..12a92963 100644 --- a/docs/beginner/tutorials/12-Units_System.ipynb +++ b/docs/beginner/tutorials/12-Units_System.ipynb @@ -19,15 +19,7 @@ "cell_type": "code", "execution_count": 1, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "PostHog telemetry failed: HTTPSConnectionPool(host='eu.i.posthog.com', port=443): Read timed out. (read timeout=10)\n" - ] - } - ], + "outputs": [], "source": [ "import nest_asyncio\n", "nest_asyncio.apply()\n", @@ -291,10 +283,50 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 40, "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "data": { + "text/plain": [ + "array([0. , 0. , 6.37 , ..., 0.49091125, 1.99139486,\n", + " 0.49345069])" + ] + }, + "execution_count": 40, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mesh.dm.getCoordinates().array" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "UnitAwareArray([[ 0. , 0. ],\n", + " [6370. , 0. ],\n", + " [ 0. , 3185. ],\n", + " ...,\n", + " [ 304.64914035, 301.35283179],\n", + " [5573.30269899, 490.91124502],\n", + " [1991.39486153, 493.45068933]]), callbacks=0, units='kilometer')" + ] + }, + "execution_count": 42, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "mesh.X.coords.to(\"km\")" + ] }, { "cell_type": "code", @@ -782,7 +814,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "140 μs ± 411 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)\n" + "141 μs ± 338 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)\n" ] } ], diff --git a/docs/beginner/tutorials/13-Scaling-problems-with-physical-units.ipynb b/docs/beginner/tutorials/13-Scaling-problems-with-physical-units.ipynb index 928121b0..255eaeb8 100644 --- a/docs/beginner/tutorials/13-Scaling-problems-with-physical-units.ipynb +++ b/docs/beginner/tutorials/13-Scaling-problems-with-physical-units.ipynb @@ -120,19 +120,16 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "cell-6", "metadata": {}, "outputs": [ { - "data": { - "text/plain": [ - "(UWQuantity(0.0, 'kelvin'), UWQuantity(0.25000000003260475, 'kelvin'))" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "✓ Units system active with automatic non-dimensionalization\n" + ] } ], "source": [ @@ -177,7 +174,7 @@ "# Store solution\n", "T_nd_solution = np.copy(T_nd.data)\n", "\n", - "T_nd.min(), T_nd.max()" + "# T_nd.min(), T_nd.max()" ] }, { @@ -192,7 +189,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "cell-8", "metadata": {}, "outputs": [ @@ -202,7 +199,7 @@ "(0.0, 0.0)" ] }, - "execution_count": 4, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -247,7 +244,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "afa56714-e5c6-4195-8ebb-d35668e3adad", "metadata": {}, "outputs": [], @@ -281,23 +278,10 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "2e97ab17-53e3-4924-9f54-230bdb83a14f", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " 0 SNES Function norm 6.271334096364e+00\n", - " Residual norms for Solver_35_ solve.\n", - " 0 KSP Residual norm 1.259100698110e+02\n", - " 1 KSP Residual norm 1.243505915271e-03\n", - " 1 SNES Function norm 3.472007437385e-05\n", - "Manual ND: v_max = (1.0, 0.441044907903762), p_max = 85.99449323705721\n" - ] - } - ], + "outputs": [], "source": [ "# Solve\n", "stokes_manual.solve()\n", @@ -311,26 +295,10 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "01c620a4-d826-4e13-9df2-939847661cd8", "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\left[\\begin{matrix}\\uplambda \\left({ \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 0,0}(\\mathbf{x}) + { \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 1,1}(\\mathbf{x})\\right) - { \\hspace{ 0.0032pt } {p_\\textrm{man}} }(\\mathbf{x}) + 2 { \\eta \\hspace{ 0.0016pt } } { \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 0,0}(\\mathbf{x}) & { \\eta \\hspace{ 0.0016pt } } \\left({ \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 0,1}(\\mathbf{x}) + { \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 1,0}(\\mathbf{x})\\right)\\\\{ \\eta \\hspace{ 0.0016pt } } \\left({ \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 0,1}(\\mathbf{x}) + { \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 1,0}(\\mathbf{x})\\right) & \\uplambda \\left({ \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 0,0}(\\mathbf{x}) + { \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 1,1}(\\mathbf{x})\\right) - { \\hspace{ 0.0032pt } {p_\\textrm{man}} }(\\mathbf{x}) + 2 { \\eta \\hspace{ 0.0016pt } } { \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 1,1}(\\mathbf{x})\\end{matrix}\\right]$" - ], - "text/plain": [ - "Matrix([\n", - "[\\uplambda*({ \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 0,0}(N.x, N.y) + { \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 1,1}(N.x, N.y)) - { \\hspace{ 0.0032pt } {p_\\textrm{man}} }(N.x, N.y) + 2*{ \\eta \\hspace{ 0.0016pt } }*{ \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 0,0}(N.x, N.y), { \\eta \\hspace{ 0.0016pt } }*({ \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 0,1}(N.x, N.y) + { \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 1,0}(N.x, N.y))],\n", - "[ { \\eta \\hspace{ 0.0016pt } }*({ \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 0,1}(N.x, N.y) + { \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 1,0}(N.x, N.y)), \\uplambda*({ \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 0,0}(N.x, N.y) + { \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 1,1}(N.x, N.y)) - { \\hspace{ 0.0032pt } {p_\\textrm{man}} }(N.x, N.y) + 2*{ \\eta \\hspace{ 0.0016pt } }*{ \\hspace{ 0.0032pt } {v_\\textrm{man}} }_{ 1,1}(N.x, N.y)]])" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "uw.unwrap(stokes_manual.F1.sym, keep_constants=True, apply_scaling=True)" ] @@ -347,20 +315,10 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "cell-14", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Scaling coefficients (with reference scales = 1.0):\n", - " V₀ = 3.168809e-10 m/s\n", - " P₀ = 3.168809e+03 Pa\n" - ] - } - ], + "outputs": [], "source": [ "# Reset and set reference quantities\n", "uw.reset_default_model()\n", @@ -400,29 +358,18 @@ "id": "7a4472e0-8f2d-4fcd-9279-da3711c0d88c", "metadata": {}, "outputs": [], - "source": [] + "source": [ + "model.get_fundamental_scales()" + ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "id": "cell-15", "metadata": { "scrolled": true }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " 0 SNES Function norm 6.271334096364e+00\n", - " Residual norms for Solver_72_ solve.\n", - " 0 KSP Residual norm 1.259100698110e+02\n", - " 1 KSP Residual norm 1.243505915261e-03\n", - " 1 SNES Function norm 3.472007437704e-05\n", - "Automatic scaling: v_max = 1.000000e+00, p_max = 8.599449e+01\n" - ] - } - ], + "outputs": [], "source": [ "# Enable ND scaling\n", "uw.use_nondimensional_scaling(True)\n", @@ -439,8 +386,6 @@ "stokes_auto.add_dirichlet_bc((uw.quantity(0.0, \"cm/yr\"), uw.quantity(0.0, \"cm/yr\")), \"Left\")\n", "stokes_auto.add_dirichlet_bc((uw.quantity(0.0, \"cm/yr\"), uw.quantity(0.0, \"cm/yr\")), \"Right\")\n", "\n", - "stokes_auto.constraints = sympy.Matrix([v_auto.divergence()])\n", - "\n", "# Solve\n", "stokes_auto.petsc_options.setValue(\"ksp_monitor\", None)\n", "stokes_auto.petsc_options.setValue(\"snes_monitor\", None)\n", @@ -465,32 +410,10 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "cell-17", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Velocity difference: 8.188e-16 (relative: 8.188e-16)\n", - "Pressure difference: 3.197e-14 (relative: 3.718e-16)\n" - ] - }, - { - "data": { - "text/plain": [ - "(8.187894806610529e-16,\n", - " 8.187894806610529e-16,\n", - " 3.197442310920451e-14,\n", - " 3.7181942593768223e-16)" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "# Compare ND values in .data arrays\n", "v_diff = np.max(np.abs(v_manual_data - v_auto_data))\n", @@ -515,243 +438,80 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "da961c08-4c70-4336-861b-05b40c8b03ad", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[-0.07306434, 0.32881428],\n", - " [-0.1013255 , 0.29520818],\n", - " [-0.12398888, 0.28902145],\n", - " [-0.06613869, 0.11618791],\n", - " [-0.02876104, 0.08110713],\n", - " [-0.05258502, 0.08444147],\n", - " [-0.05747512, -0.08855548],\n", - " [-0.04972235, -0.10760659],\n", - " [-0.08838498, -0.12462027],\n", - " [-0.09938257, 0.13158919]])" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "v_manual.array[100:110].squeeze()" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "id": "8862ca62-2f41-4611-8e37-a254a68c62c1", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "UnitAwareArray([[-2.31526920e-11, 1.04194957e-10],\n", - " [-3.21081125e-11, 9.35458288e-11],\n", - " [-3.92897043e-11, 9.15853712e-11],\n", - " [-2.09580867e-11, 3.68177261e-11],\n", - " [-9.11382463e-12, 2.57012974e-11],\n", - " [-1.66631865e-11, 2.67578878e-11],\n", - " [-1.82127657e-11, -2.80615380e-11],\n", - " [-1.57560610e-11, -3.40984718e-11],\n", - " [-2.80075086e-11, -3.94897817e-11],\n", - " [-3.14924354e-11, 4.16980984e-11]]), callbacks=0, units='meter / second')" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "v_auto.array[100:110].squeeze()" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "id": "b8f1504c-dc57-42dd-9511-0c76562284e8", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "NDArray_With_Callback([[-0.07306434, 0.32881428],\n", - " [-0.1013255 , 0.29520818],\n", - " [-0.12398888, 0.28902145],\n", - " [-0.06613869, 0.11618791],\n", - " [-0.02876104, 0.08110713],\n", - " [-0.05258502, 0.08444147],\n", - " [-0.05747512, -0.08855548],\n", - " [-0.04972235, -0.10760659],\n", - " [-0.08838498, -0.12462027],\n", - " [-0.09938257, 0.13158919]]), callbacks=1" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "v_manual.data[100:110].squeeze()" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "id": "61cea72f-0fe5-4c94-b988-764e64a70af3", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "NDArray_With_Callback([[-0.07306434, 0.32881428],\n", - " [-0.1013255 , 0.29520818],\n", - " [-0.12398888, 0.28902145],\n", - " [-0.06613869, 0.11618791],\n", - " [-0.02876104, 0.08110713],\n", - " [-0.05258502, 0.08444147],\n", - " [-0.05747512, -0.08855548],\n", - " [-0.04972235, -0.10760659],\n", - " [-0.08838498, -0.12462027],\n", - " [-0.09938257, 0.13158919]]), callbacks=1" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "v_auto.data[100:110].squeeze()" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "id": "179eb742-e4d9-4c24-b5fc-4531e5c8a0bf", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "UnitAwareArray([[[-2.31526920e-11, 1.04194957e-10]],\n", - "\n", - " [[-3.21081125e-11, 9.35458288e-11]],\n", - "\n", - " [[-3.92897043e-11, 9.15853712e-11]],\n", - "\n", - " [[-2.09580867e-11, 3.68177261e-11]],\n", - "\n", - " [[-9.11382463e-12, 2.57012974e-11]],\n", - "\n", - " [[-1.66631865e-11, 2.67578878e-11]],\n", - "\n", - " [[-1.82127657e-11, -2.80615380e-11]],\n", - "\n", - " [[-1.57560610e-11, -3.40984718e-11]],\n", - "\n", - " [[-2.80075086e-11, -3.94897817e-11]],\n", - "\n", - " [[-3.14924354e-11, 4.16980984e-11]]]), callbacks=0, units='meter / second')" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "uw.function.evaluate(v_auto, v_auto.coords[100:110]).squeeze()" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "id": "b0d271b4-bb4e-4501-a8d5-13640d91ef7c", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "NDArray_With_Callback([[ 0.06928309],\n", - " [ 0.57823914],\n", - " [ 1.71011827],\n", - " [ 5.19675035],\n", - " [ 10.23393283],\n", - " [-10.10007413],\n", - " [ -5.15333209],\n", - " [ -1.84775144],\n", - " [ -0.61619472],\n", - " [ -0.100717 ]]), callbacks=1" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "p_manual.data[20:30]" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "id": "199c6549-cc70-459a-8d70-38dfb0c0f989", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "NDArray_With_Callback([[ 0.06928309],\n", - " [ 0.57823914],\n", - " [ 1.71011827],\n", - " [ 5.19675035],\n", - " [ 10.23393283],\n", - " [-10.10007413],\n", - " [ -5.15333209],\n", - " [ -1.84775144],\n", - " [ -0.61619472],\n", - " [ -0.100717 ]]), callbacks=1" - ] - }, - "execution_count": 17, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "p_auto.data[20:30]" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "id": "945f108e-c613-45ac-a382-c9952ff737b9", "metadata": {}, - "outputs": [ - { - "data": { - "text/latex": [ - "$\\displaystyle \\left[\\begin{matrix}{ \\hspace{ 0.0053pt } {v_\\textrm{auto}} }_{ 0,0}(\\mathbf{x}) + { \\hspace{ 0.0053pt } {v_\\textrm{auto}} }_{ 1,1}(\\mathbf{x})\\end{matrix}\\right]$" - ], - "text/plain": [ - "Matrix([[{ \\hspace{ 0.0053pt } {v_\\textrm{auto}} }_{ 0,0}(N.x, N.y) + { \\hspace{ 0.0053pt } {v_\\textrm{auto}} }_{ 1,1}(N.x, N.y)]])" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "stokes_auto.PF0.sym" ] @@ -823,25 +583,10 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "id": "313d7ec1-be6b-4c87-bf54-c85974f26570", "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "978ad05a1e60417e989284dcc1f44d39", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Widget(value='