Skip to content

Explicit stress history for viscoelastic Stokes solver#89

Open
lmoresi wants to merge 11 commits intodevelopmentfrom
feature/viscoelastic-stress-history
Open

Explicit stress history for viscoelastic Stokes solver#89
lmoresi wants to merge 11 commits intodevelopmentfrom
feature/viscoelastic-stress-history

Conversation

@lmoresi
Copy link
Member

@lmoresi lmoresi commented Mar 22, 2026

Summary

Explicit stress history architecture for VE_Stokes: store actual computed stress
in psi_star (rather than flux), advect stored values along characteristics, and
use BDF-1/2/3 discretisation of the Maxwell constitutive law.

Key changes

  • Explicit stress history: after each solve, the actual deviatoric stress is
    projected into psi_star[0] and shifted down the history chain. This replaces
    the previous approach of storing/advecting the flux expression.
  • solver.tau property: access the computed stress tensor (projected, not the
    symbolic formula)
  • BDF-3 constitutive formulae: ve_effective_viscosity, E_eff, and stress()
    now have three-way branching for BDF-1/2/3 with correct coefficients
  • effective_order property: ramps from 1 to requested order as DDt history
    accumulates, preventing use of higher-order coefficients before distinct
    history is available
  • dt_elastic is independent of timestep: solve(timestep=) no longer
    overwrites the constitutive relaxation parameter
  • Analytical formula fix: removed spurious De factor in Maxwell oscillatory
    solution (was invisible at De=1, caused apparent errors at other De)
  • Benchmark documentation: oscillatory shear validation with convergence
    tables and resolution study

Convergence (constant shear, Maxwell analytical)

dt/t_r BDF-1 BDF-2 BDF-3
0.200 3.0e-2 4.0e-3 6.4e-3
0.100 1.5e-2 9.3e-4 1.7e-3
0.050 7.8e-3 2.3e-4 4.3e-4
0.020 3.1e-3 3.7e-5 --

BDF-2 is the recommended default (second-order convergence, 85x better than
BDF-1 at small dt).

Oscillatory validation

A shear layer with a harmonic (in time) velocity boundary condition. For a Maxwell material, the analytic solution can be derived - it shows a phase lag in stress compared to the driving velocity, and the amplitude evolves with time.

The Maxwell constitutive law for simple shear:

$$\dot{\sigma}{xy} + \frac{\sigma{xy}}{t_r} = \mu \dot{\gamma}_0 \sin(\omega t)$$

where $t_r = \eta/\mu$ is the relaxation time and $\text{De} = \omega t_r$ is the Deborah number.

Full solution with $\sigma(0) = 0$:

$$\sigma_{xy}(t) = \frac{\eta \dot{\gamma}_0}{1 + \text{De}^2}\left[\sin(\omega t) - \text{De} \cdot \cos(\omega t) + \text{De},e^{-t/t_r}\right]$$

Steady-state amplitude: $A = \eta \dot{\gamma}_0 / \sqrt{1 + \text{De}^2}$

Phase lag: $\delta = \arctan(\text{De})$

ve_oscillatory_De5_resolution

Notes on dt_elastic separation

A running-average approach for history accumulation when dt << dt_elastic was
investigated but found to be extremely diffusive for semi-Lagrangian transport
and is not implemented. To prevent unstable behaviour when timesteps become
small, we advise limiting the minimum effective viscosity in line with the
physics of the problem.

Test plan

  • Constant shear benchmark (order 1, 2, 3) against Maxwell analytical
  • Oscillatory shear validation (De=1, 1.5, 5) with phase lag and amplitude
  • Resolution study (De=5, three timestep sizes)
  • VP and VEP shear box tests
  • CI pipeline

Underworld development team with AI support from Claude Code

lmoresi added 10 commits March 21, 2026 11:06
The setuptools build/ directory (lib.*, temp.*, bdist.*) persists across
runs and silently reuses stale .py artefacts when only Python sources
change. --no-cache-dir only bypasses pip's wheel cache, not setuptools.

Underworld development team with AI support from Claude Code
Replaces the old approach where SemiLagrangian re-evaluated the stress
formula (psi_fn) at upstream points — which broke on order transitions
because UWexpression η_eff evaluates live and the formula structure
changed between order-1 and order-2.

New architecture: store the actual stress tensor after each solve, advect
only stored values. The constitutive formula is used only during the
PETSc solve (JIT) and the post-solve stress projection.

VE_Stokes.solve() flow per timestep:
  1. advect_history() — pure advection of stored stress to upstream
  2. PETSc solve using advected σ*, σ**
  3. Save advected σ*, project constitutive_model.flux → psi_star[0]
  4. Shift: psi_star[1] ← saved σ* (chained characteristic tracing)

Additional fixes:
  - effective_order property on constitutive model (checks DDt startup)
  - effective_order formula: max(1, n_solves) not max(1, n_solves+1)
  - VE_Stokes re-setups JIT when effective_order transitions
  - elastic_dt → dt_elastic typo fix in VE_Stokes.solve()
  - Removed sympy.simplify() from 5 hot-path methods (caused hangs)
  - stress_deviator_stored property for post-processing access
  - TODO annotation for tensor vtype inference in _function.pyx

Validated: Maxwell shear box test
  - Order-1: ~3% RMS error with dt/t_r=0.1
  - Order-2: ~1% RMS, 0.09% final error (16× better than order-1)
  - Performance: constant 0.4-0.8s/step (was 5-10s with linear growth)

Underworld development team with AI support from Claude Code
Pytest test (test_1051_VE_shear_box.py) validates Maxwell viscoelastic
shear against analytical solution σ_xy = η·γ̇·(1 - exp(-t/t_r)).
Tests order-1 convergence, order-2 convergence, order-2 > order-1,
and monotonic approach to steady state.

Quick validation scripts for development use:
  - run_ve_shear_quick.py: 10-step order-1 smoke test
  - run_ve_shear_order2_quick.py: 20-step order-2 with timing
  - run_ve_shear_validation.py: full comparison of both orders
  - run_ve_order2_debug.py: traces psi_star values per step

Underworld development team with AI support from Claude Code
Adds a `tau` property to SolverBaseClass that returns a MeshVariable
containing the projected constitutive flux. Created lazily on first
access — no overhead if never used. Works for:

  - Stokes: deviatoric stress tensor (SYM_TENSOR projection)
  - Poisson: diffusive flux vector (Vector projection)
  - VE_Stokes: override returns psi_star[0] directly (no projection)

This gives users a reliable way to access "what the solver actually
computed" without manually reconstructing fluxes from the constitutive
formula (which may have history terms, nonlinear corrections, etc).

Usage:
  stokes.solve()
  stress_data = stokes.tau.data     # numerical values
  stress_sym  = stokes.tau.sym      # symbolic (for further expressions)

The base `stress_deviator` property is unchanged (returns the symbolic
formula, needed for JIT compilation via F1 -> stress -> stress_deviator).

Underworld development team with AI support from Claude Code
The plastic effective viscosity in ViscoElasticPlasticFlowModel was
using order-1 only for the elastic strain rate contribution, regardless
of effective_order. This caused order-2 VEP to overshoot the yield
stress (stress reached 0.73 instead of capping at τ_y=0.5).

Fix: _plastic_effective_viscosity now uses self.E_eff.sym which already
handles effective_order correctly via the BDF coefficients.

New test scripts:
  - run_vp_shear_box.py: VP yield stress sweep (machine-precision match)
  - run_vep_shear_box.py: VEP elastic buildup + yield cap
    Order-1: caps at τ_y with ~1e-7 error
    Order-2: caps at τ_y with ~1e-7 error (was 22% error before fix)
  - run_ve_oscillatory.py: time-harmonic VE validation

Underworld development team with AI support from Claude Code
Introduces mesh.t — a symbolic time coordinate that can be used in
expressions for time-dependent boundary conditions and forcing terms.
The symbol compiles to a C variable accessible in PETSc pointwise
functions, avoiding JIT recompilation when time changes.

Also adds optional time= parameter to Scalar and Stokes solve() methods,
and UW_DMSetTime wrapper in petsc_compat.h.

NOTE: petsc_t is hardcoded to PETSC_MIN_REAL in PETSc's SNES path
(DMPlexSNESComputeResidualFEM, DMPlexSNESComputeBoundaryFEM). The
planned solution is to use PetscDS constants[] array instead, which
generalises to any mutable constant (viscosity, moduli, dt_elastic).
This commit is the prototype; the constants wiring is the next step.

New files:
  - TimeSymbol class in unit_aware_coordinates.py
  - _patch_time_units for dimensional analysis
  - UW_DMSetTime C wrapper (for future TS interface use)
  - VEP oscillatory test scripts and plot

Underworld development team with AI support from Claude Code
Constitutive model changes:
- Add effective_order property that delegates to DDt's ramp-up counter
- ve_effective_viscosity: three-way branch for BDF-1/2/3 effective viscosity
  (eta_eff = eta*mu*dt / (c0*eta + mu*dt) where c0 = 1, 3/2, 11/6)
- E_eff: three-way branch with correct BDF coefficients for history terms
- stress(): three-way branch matching the BDF discretisation

VE_Stokes.solve() changes:
- Fix elastic_dt -> dt_elastic parameter name bug
- Remove dt_elastic overwrite: solve(timestep=) no longer clobbers the
  constitutive relaxation parameter with the advection timestep
- Improved docstring clarifying timestep vs dt_elastic semantics

Convergence verified against Maxwell analytical solution:
  dt=0.2  O1: 3.0e-2  O2: 4.0e-3  O3: 6.4e-3
  dt=0.1  O1: 1.5e-2  O2: 9.3e-4  O3: 1.7e-3
  dt=0.05 O1: 7.8e-3  O2: 2.3e-4  O3: 4.3e-4
Order 2 converges at 2nd order, Order 3 at ~2nd order (BDF-3 error
constant larger for this stiffness ratio).

Underworld development team with AI support from Claude Code (https://claude.com/claude-code)
After pip install, checksums all .py source files against the installed
copies. If any differ (stale wheel cache), copies the source files
directly to the installed location and warns. This catches the recurring
issue where pip serves stale Python files despite --no-cache-dir because
the setuptools build directory caches them.

Underworld development team with AI support from Claude Code (https://claude.com/claude-code)
…g average

- Fix Maxwell analytical solution: remove spurious De factor in prefactor.
  Was η γ̇₀ De/(1+De²), correct is η γ̇₀/(1+De²). The bug was invisible
  at De=1 (factor cancels) but caused apparent "amplitude error" at other De.

- Add oscillatory shear benchmark documentation (docs/advanced/benchmarks/)
  with convergence tables and resolution study description.

- Add plot_ve_oscillatory_validation.py: generates validation figures for
  De=1.5 (phase lag, amplitude) with --replot support for saved .npz data.

- Remove running-average history smoothing from VE_Stokes.solve(): the BDF
  constitutive formula couples dt_elastic to the time between evaluations,
  so the exponential moving average cannot compensate for dt << dt_elastic.
  Reverted to simple history shift (direct copy-down).

Underworld development team with AI support from Claude Code (https://claude.com/claude-code)
…osity limiting

Underworld development team with AI support from Claude Code (https://claude.com/claude-code)
Copilot AI review requested due to automatic review settings March 22, 2026 03:20
Copy link
Contributor

Copilot AI left a comment

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 refactors the viscoelastic Stokes (VE_Stokes) stress-history handling to store and advect the computed deviatoric stress explicitly, adds BDF-1/2/3 branching for Maxwell constitutive terms, and introduces a symbolic mesh time coordinate (mesh.t) wired through PETSc’s petsc_t plumbing.

Changes:

  • Replace “advect flux expression” with explicit stress history storage in psi_star[0], plus history shifting after each solve.
  • Add solver.tau as a projected/computed stress (base solver projection) and override for VE_Stokes to return stored history directly.
  • Add mesh.t / petsc_t support and benchmark/validation scripts & docs for oscillatory shear.

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
uw Build-script changes to aggressively clean build artifacts and attempt to detect/correct stale installed .py files.
tests/test_1051_VE_shear_box.py New pytest validation against Maxwell constant-shear analytical solution using stokes.tau.
tests/run_vp_shear_box.py New manual VP shear-box validation script using stokes.tau.
tests/run_vep_shear_box.py New manual VEP shear-box validation script.
tests/run_vep_oscillatory.py New manual oscillatory VEP validation script (includes analytical helper).
tests/run_ve_vep_oscillatory_plot.py New VE vs VEP oscillatory comparison/plot script with analytical init.
tests/run_ve_vep_oscillatory_checkpoint.py New checkpoint writer for VE vs VEP oscillatory runs.
tests/run_ve_shear_validation.py New VE shear-box validation helper script.
tests/run_ve_shear_quick.py New quick VE order-1 shear script.
tests/run_ve_shear_order2_quick.py New quick VE order-2 shear script.
tests/run_ve_oscillatory.py New VE oscillatory validation script.
tests/run_ve_order2_debug.py New debugging script to inspect psi_star evolution.
tests/plot_ve_vep_oscillatory.py New plotting script for VE/VEP oscillatory checkpoints.
tests/plot_ve_oscillatory_validation.py New benchmark plotting script and checkpointing for oscillatory VE.
src/underworld3/utilities/unit_aware_coordinates.py Add TimeSymbol and time-unit patching for unit-aware mesh.t.
src/underworld3/systems/solvers.py VE_Stokes solve sequence refactor: advect_history() + post-solve projection into psi_star[0] + history shift; add VE override tau.
src/underworld3/systems/ddt.py Change effective-order ramp logic and add SemiLagrangian.advect_history() for pure-history advection.
src/underworld3/function/_function.pyx Add TODO note about matrix projection limitations.
src/underworld3/discretisation/discretisation_mesh.py Add mesh.t property and initialize _t as TimeSymbol.
src/underworld3/cython/petsc_generic_snes_solvers.pyx Add base SolverBaseClass.tau projection property and plumb time= into SNES solves via UW_DMSetTime.
src/underworld3/cython/petsc_extras.pxi Add Cython binding for UW_DMSetTime.
src/underworld3/cython/petsc_compat.h Implement UW_DMSetTime() via DM(Set/Get)OutputSequenceNumber.
src/underworld3/constitutive_models.py Implement BDF-1/2/3 branching for VE effective viscosity / E_eff / stress; introduce constitutive effective_order.
docs/advanced/benchmarks/ve-oscillatory-shear.md New benchmark write-up for oscillatory Maxwell shear + timestep study + dt_elastic note.
docs/advanced/benchmarks/index.md Add benchmarks index and toctree entry.

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

Comment on lines +1120 to +1122
# BDF-k requires k completed solves to have k distinct history values.
# With 0 or 1 completed solves → order 1. Order 2 needs ≥2 solves.
return min(self.order, max(1, self._n_solves_completed))
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

Same effective_order off-by-one issue here as in the symbolic DDt: using max(1, _n_solves_completed) delays BDF-2/3 usage by one extra timestep even when distinct history values exist. This can degrade the advertised order of accuracy during startup.

Suggested change
# BDF-k requires k completed solves to have k distinct history values.
# With 0 or 1 completed solves → order 1. Order 2 needs ≥2 solves.
return min(self.order, max(1, self._n_solves_completed))
# Distinct history values = initial condition + completed solves.
# With 0 completed solves → 1 value (order 1); with 1 solve → 2 values (order 2).
return min(self.order, max(1, self._n_solves_completed + 1))

Copilot uses AI. Check for mistakes.
Comment on lines +1363 to +1367
import numpy as np

# Save advected σ* before projection modifies psi_star[0]
_advected_sigma_star = np.copy(self.DFDt.psi_star[0].array[...])

Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

This always copies the full psi_star[0] array even when self.DFDt.order == 1 (where no history shift is performed). For large meshes this extra allocation/copy can be expensive; consider guarding the copy/shift logic with if self.DFDt.order > 1: and only snapshotting when it’s actually needed.

Copilot uses AI. Check for mistakes.
uw
Comment on lines +209 to +213
echo " Fixing: copying source files to installed location..."
for f in "${stale_files[@]}"; do
cp "src/underworld3/$f" "$site_packages/$f"
done
echo -e " ${GREEN}✓${NC} ${#stale_files[@]} file(s) updated"
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

Copying differing .py sources directly into site-packages can hide packaging/install issues and create an environment that doesn’t correspond to any wheel/sdist artifact (harder to reproduce/debug). If the goal is always-up-to-date sources for dev, consider using an editable install (pip install -e .) or bumping the package version/build tag so pip’s wheel caching can’t serve stale files, rather than mutating the installed tree post-install.

Suggested change
echo " Fixing: copying source files to installed location..."
for f in "${stale_files[@]}"; do
cp "src/underworld3/$f" "$site_packages/$f"
done
echo -e " ${GREEN}✓${NC} ${#stale_files[@]} file(s) updated"
echo " Installed package appears out of sync with the source tree."
echo " Please reinstall the 'underworld3' package to refresh installed files."
echo " For development, consider using an editable install: 'pip install -e .'."
echo " Alternatively, bump the package version so pip does not reuse a stale wheel."
echo -e " ${YELLOW}No files were modified automatically by this script.${NC}"

Copilot uses AI. Check for mistakes.
- Bottom panel: stress σ_xy(t) for order 1 and order 2 vs analytical

Maxwell analytical solution including startup transient:
σ_xy(t) = η γ̇₀ De/(1+De²) [sin(ωt) - De cos(ωt) + De exp(-t/t_r)]
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

The docstring’s analytical solution still shows a De factor in the prefactor (η·γ̇0·De/(1+De²)), but maxwell_oscillatory() below uses the corrected η·γ̇0/(1+De²) form. Updating the docstring keeps the benchmark description consistent with the corrected formula elsewhere in the PR.

Suggested change
σ_xy(t) = η γ̇ De/(1+De²) [sin(ωt) - De cos(ωt) + De exp(-t/t_r)]
σ_xy(t) = η γ̇/(1+De²) [sin(ωt) - De cos(ωt) + De exp(-t/t_r)]

Copilot uses AI. Check for mistakes.
Comment on lines +1873 to +1875
# BDF-k requires k completed solves to have k distinct history values.
# With 0 or 1 completed solves → order 1. Order 2 needs ≥2 solves.
return min(self.order, max(1, self._n_solves_completed))
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

Same effective_order off-by-one issue here: BDF-2 should be available after 1 completed solve (with initial condition as the older history). Using _n_solves_completed directly postpones higher-order coefficients longer than necessary and can reduce accuracy.

Suggested change
# BDF-k requires k completed solves to have k distinct history values.
# With 0 or 1 completed solves → order 1. Order 2 needs ≥2 solves.
return min(self.order, max(1, self._n_solves_completed))
# BDF-k requires k distinct history values.
# With the initial condition stored as the oldest history entry,
# we have (_n_solves_completed + 1) history states available.
return min(self.order, max(1, self._n_solves_completed + 1))

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +35
"""Oscillatory VEP shear box — does the yield stress cap the oscillation?

Same setup as the VE oscillatory test but with a yield stress.
The VE amplitude at steady state is η γ̇₀ De/√(1+De²).
If τ_y is below this amplitude, the stress should be clipped.
"""

import time as timer
import numpy as np
import sympy
import underworld3 as uw
from underworld3.function import expression


def maxwell_oscillatory(t, eta, mu, gamma_dot_0, omega):
"""Full analytical σ_xy for oscillatory Maxwell shear (incl. transient)."""
t_r = eta / mu
De = omega * t_r
prefactor = eta * gamma_dot_0 * De / (1.0 + De**2)
return prefactor * (np.sin(omega * t) - De * np.cos(omega * t) + De * np.exp(-t / t_r))


def run_vep_oscillatory(order, n_steps, dt_over_tr, De, tau_y):
"""Run oscillatory VEP shear box."""

ETA, MU, H, W = 1.0, 1.0, 1.0, 2.0
t_r = ETA / MU
omega = De / t_r
V0 = 0.5
gamma_dot_0 = 2.0 * V0 / H
dt = dt_over_tr * t_r

# VE steady-state amplitude
ve_amplitude = ETA * gamma_dot_0 * De / np.sqrt(1 + De**2)

Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

The docstring and ve_amplitude computation include an extra De factor in the steady-state amplitude. That contradicts the corrected analytical solution used elsewhere in this PR (and the new benchmark doc), where the steady-state amplitude is η·γ̇0 / sqrt(1+De^2). Please update the amplitude formula and the prefactor/return expression accordingly so the validation script isn’t checking against a wrong analytic baseline.

Copilot uses AI. Check for mistakes.
"""Full analytical σ_xy for oscillatory Maxwell shear."""
t_r = eta / mu
De = omega * t_r
prefactor = eta * gamma_dot_0 * De / (1.0 + De**2)
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

maxwell_oscillatory() here still uses prefactor = η·γ̇0·De/(1+De²) (and the corresponding De * exp(-t/t_r) term). Elsewhere in this PR the spurious De factor was removed; if this helper is meant to match the corrected Maxwell solution it should use prefactor = η·γ̇0/(1+De²) with the same bracketed terms.

Suggested change
prefactor = eta * gamma_dot_0 * De / (1.0 + De**2)
prefactor = eta * gamma_dot_0 / (1.0 + De**2)

Copilot uses AI. Check for mistakes.
Comment on lines +334 to +336
# BDF-k requires k completed solves to have k distinct history values.
# With 0 or 1 completed solves → order 1. Order 2 needs ≥2 solves.
return min(self.order, max(1, self._n_solves_completed))
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

effective_order appears off by one for BDF startup. After 1 completed solve you already have two distinct history states (t0 and t1), so BDF-2 can be used on the second solve. Returning max(1, _n_solves_completed) delays BDF-2 until the third solve and can reduce the expected convergence order; consider reverting to max(1, _n_solves_completed + 1) (and likewise for BDF-3).

Suggested change
# BDF-k requires k completed solves to have k distinct history values.
# With 0 or 1 completed solves → order 1. Order 2 needs ≥2 solves.
return min(self.order, max(1, self._n_solves_completed))
# After n completed solves, we have n+1 distinct time levels (t0..tn),
# so BDF-k can be used once k ≤ n+1. This corresponds to an effective
# order of min(self.order, max(1, _n_solves_completed + 1)).
return min(self.order, max(1, self._n_solves_completed + 1))

Copilot uses AI. Check for mistakes.
f"analytical = {ana[-1]:.6f}")

diffs = np.diff(num)
assert np.all(diffs > 0), "Stress should be monotonically increasing"
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

This monotonicity assertion is very strict (diffs > 0) and may be flaky due to solver tolerances / roundoff (e.g. tiny negative differences around 1e-12). Consider allowing a small tolerance (e.g. diffs >= -eps) so the test remains robust across platforms and PETSc/KSP configurations.

Suggested change
assert np.all(diffs > 0), "Stress should be monotonically increasing"
eps = 1.0e-10
assert np.all(diffs >= -eps), "Stress should be monotonically increasing"

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +11
σ_xy(t) = η γ̇₀ De/(1+De²) [sin(ωt) - De cos(ωt) + De exp(-t/t_r)]

where De = ω t_r is the Deborah number.

At steady state (t >> t_r), the transient dies out and the stress
oscillates with amplitude η γ̇₀ De/√(1+De²) and phase lag arctan(De).
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

The analytical formula in the module docstring still includes an extra De factor in the prefactor and in the steady-state amplitude. The implementation below uses prefactor = η·γ̇0/(1+De²), so the docstring should be updated to match the corrected Maxwell solution (and avoid confusing users reading this validation script).

Suggested change
σ_xy(t) = η γ̇ De/(1+De²) [sin(ωt) - De cos(ωt) + De exp(-t/t_r)]
where De = ω t_r is the Deborah number.
At steady state (t >> t_r), the transient dies out and the stress
oscillates with amplitude η γ̇ De/√(1+De²) and phase lag arctan(De).
σ_xy(t) = η γ̇/(1+De²) [sin(ωt) - De cos(ωt) + De exp(-t/t_r)]
where De = ω t_r is the Deborah number.
At steady state (t >> t_r), the transient dies out and the stress
oscillates with amplitude η γ̇/√(1+De²) and phase lag arctan(De).

Copilot uses AI. Check for mistakes.
- Remove spurious De factor from Maxwell oscillatory prefactor and
  steady-state amplitude in 5 test/validation scripts (code and
  docstrings). The corrected formula is η γ̇₀/(1+De²), not
  η γ̇₀ De/(1+De²).
- TimeSymbol: change positive=True to nonnegative=True (t=0 is valid)
- test_1051: relax strict monotonicity check (diffs > 0 → >= -1e-10)
  to avoid flaky failures from solver roundoff
- mesh.t docstring: clarify that high-level Python solve() wrappers
  do not yet pass time= through to PETSc

Underworld development team with AI support from Claude Code
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.

2 participants