Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/developer/UW3_STYLE_CHARTER.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ These settle the June-2026 drift (see `docs/reviews/2026-07/API-CONSISTENCY-REVI

| Topic | Rule |
|---|---|
| BC argument order | `add_<kind>_bc(value, boundary, ...)` — value first, matching the original trio, the published examples/benchmarks, and previous major versions (maintainer decision 2026-07-04). The newer boundary-first methods (`add_nitsche_bc`, `add_rotated_freeslip_bc`, `add_constraint_bc`) migrate to it; their current signatures are shimmed. |
| BC value parameter | ONE name across all BC methods ⟨pending maintainer confirmation: `conds` (implied by the original-order decision) vs `value`⟩. Until decided, do not introduce a third spelling. |
| BC argument order | `add_<kind>_bc(conds, boundary, ...)` — value first, matching the original trio, the published examples/benchmarks, and previous major versions (maintainer decision 2026-07-04). The newer boundary-first methods (`add_nitsche_bc`, `add_rotated_freeslip_bc`, `add_constraint_bc`) migrate to it; their current signatures are shimmed. |
| BC value parameter | ONE name across all BC methods: **`conds`** (maintainer decision 2026-07-06, recorded in `docs/reviews/2026-07/REMEDIATION-WORKLIST.md`). Other spellings (`g`, `value`, ...) exist only as deprecated keyword aliases where they existed before the decision; never introduce a new spelling. |
| Direction selection | Component masking via `None`/`sympy.oo` entries in the value vector; `direction=` only for a scalar constraint along a vector (defaults to outward normal); `normal=` strictly overrides the geometric surface-normal source. `components=` is deprecated — never in new code. |
| Solver capabilities | Anything that configures or reads one solver is a METHOD on that solver, lazily importing its `utilities/*` implementation (the `boundary_flux` pattern) — never a free function as the documented entry point. |
| Namespaces | Every user-facing module is exported from its subpackage `__init__`/`__all__` in the PR that creates it. No deep-import-only features. |
Expand Down
4 changes: 3 additions & 1 deletion src/underworld3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,9 @@ def view():
from .parameters import ParameterRegistry, ParameterType
from .materials import MaterialRegistry, MaterialProperty
from .constitutive_models import MultiMaterialConstitutiveModel
from .function import quantity, expression, with_units, expand, unwrap
# uw.quantity is THE quantity factory (returns UWQuantity, exposed alongside
# for isinstance checks); uw.create_quantity is deprecated (see units.py).
from .function import quantity, UWQuantity, expression, with_units, expand, unwrap
from .coordinates import uwdiff # Differentiation helper for UWCoordinates
from .utilities import retention_curves

Expand Down
316 changes: 269 additions & 47 deletions src/underworld3/cython/petsc_generic_snes_solvers.pyx

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions src/underworld3/meshing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@
ADAPT_STRATEGIES,
)

from .bounding_surface import (
BoundingSurface,
register_radial_surfaces,
register_plane_surfaces,
register_box_face_surfaces,
)

# Make all functions available at module level for backward compatibility
__all__ = [
# Cartesian meshes
Expand Down Expand Up @@ -108,4 +115,9 @@
"mesh_metric_mismatch",
"follow_metric",
"ADAPT_STRATEGIES",
# Bounding surfaces (tangent-slip providers for deforming boundaries)
"BoundingSurface",
"register_radial_surfaces",
"register_plane_surfaces",
"register_box_face_surfaces",
]
25 changes: 17 additions & 8 deletions src/underworld3/meshing/smoothing.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,8 +504,9 @@ def _edge_pairs(dm):


def _winslow_spring(mesh, metric, pinned_labels, verbose,
n_sweeps=300, relax=None, step_frac=None,
boundary_slip=False, shape_w=1.0, size_w=8.0):
max_cg_iters=300,
boundary_slip=False, shape_w=1.0, size_w=8.0,
n_sweeps=None):
r"""Metric-driven mesh grading by elastic-spring equilibrium.

Every mesh edge is a linear spring whose *rest length* is set
Expand Down Expand Up @@ -540,12 +541,20 @@ def _winslow_spring(mesh, metric, pinned_labels, verbose,
the mean edge length ⇒ only a benign mild regularisation toward
uniform spacing (no grading change).

``n_sweeps`` caps the CG iterations (CG converges far faster
than the old Jacobi sweep budget). ``relax`` / ``step_frac`` are
unused on the equilibrium path (the CG line search controls the
step and the inversion guard) and are kept only for signature
stability. ``n_iters`` / ``alpha`` do not apply.
``max_cg_iters`` caps the CG iterations (CG converges far faster
than the old Jacobi sweep budget); ``n_sweeps`` is its deprecated
former name, accepted for one cycle with a DeprecationWarning.
The old ``relax`` / ``step_frac`` parameters were unused on the
equilibrium path (the CG line search controls the step and the
inversion guard) and have been removed. ``n_iters`` / ``alpha``
do not apply.
"""
if n_sweeps is not None:
warnings.warn(
"the 'n_sweeps' argument is renamed; use max_cg_iters= "
"(it caps the nonlinear-CG iterations, not Jacobi sweeps)",
DeprecationWarning, stacklevel=2)
max_cg_iters = n_sweeps
pinned_labels = tuple(pinned_labels)
dm = mesh.dm
pStart, pEnd = dm.getDepthStratum(0)
Expand Down Expand Up @@ -738,7 +747,7 @@ def _energy_grad(X):
dmax = uw.mpi.comm.allreduce(dmax, op=_MPI.MAX)
t0 = 0.5 * L0_mean / dmax
c_arm = 1.0e-4
max_iter = int(n_sweeps)
max_iter = int(max_cg_iters)
for it in range(max_iter):
gnorm = _allsum((G * G).sum()) ** 0.5
if gnorm <= 1.0e-8 * g0:
Expand Down
71 changes: 33 additions & 38 deletions src/underworld3/swarm.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ def _create_variable_array(self, initial_data=None):
Array object with callback for automatic PETSc synchronization
"""
if initial_data is None:
initial_data = self.unpack_uw_data_from_petsc(squeeze=False, sync=True)
initial_data = self.unpack_uw_data_from_petsc(squeeze=False)

# Create NDArray_With_Callback (following swarm._points pattern)
array_obj = uw.utilities.NDArray_With_Callback(
Expand Down Expand Up @@ -432,7 +432,7 @@ def variable_update_callback(array, change_context):
return

# Persist changes to PETSc (like swarm callback updates coordinates)
var.pack_uw_data_to_petsc(array, sync=True)
var.pack_uw_data_to_petsc(array)

# Register the callback (following swarm.points pattern)
array_obj.add_callback(variable_update_callback)
Expand All @@ -457,7 +457,7 @@ def _create_canonical_data_array(self, initial_data=None):
"""
if initial_data is None:
# Use unpack_raw to get flat format (-1, num_components)
initial_data = self.unpack_raw_data_from_petsc(squeeze=False, sync=True)
initial_data = self.unpack_raw_data_from_petsc(squeeze=False)

# Handle case where unpack returns None (swarm not initialized)
if initial_data is None:
Expand Down Expand Up @@ -504,7 +504,7 @@ def canonical_data_callback(array, change_context):
canonical_array = canonical_array.reshape(-1, self.num_components)

# STEP 1: Sync to PETSc using established method with correct shape
self.pack_raw_data_to_petsc(canonical_array, sync=True)
self.pack_raw_data_to_petsc(canonical_array)

# Coordinate writes may strand particles on the wrong rank. Mark
# the swarm for DEFERRED migration — migrate() itself is
Expand Down Expand Up @@ -1271,7 +1271,18 @@ def _rbf_reduce_to_meshVar(self, meshVar, verbose=False):

# return

def pack_uw_data_to_petsc(self, data_array, sync=True):
@staticmethod
def _warn_deprecated_sync(sync):
"""One-cycle keyword shim for the removed no-op ``sync=`` argument on
the four pack/unpack methods (it never had an effect)."""
if sync is not None:
import warnings
warnings.warn(
"the 'sync' argument never had an effect and is deprecated; "
"remove it",
DeprecationWarning, stacklevel=3)

def pack_uw_data_to_petsc(self, data_array, sync=None):
"""
Enhanced pack method that directly accesses PETSc field without access() context.
Designed for the new swarmVariable.array interface.
Expand All @@ -1280,9 +1291,10 @@ def pack_uw_data_to_petsc(self, data_array, sync=True):
----------
data_array : numpy.ndarray
Array data to pack into PETSc field
sync : bool
Whether to sync parallel operations (default True)
sync : deprecated
Never had an effect; deprecated (one DeprecationWarning if passed).
"""
self._warn_deprecated_sync(sync)
shape = self.shape
data_array_3d = data_array.reshape(-1, *self.shape)

Expand All @@ -1302,11 +1314,6 @@ def pack_uw_data_to_petsc(self, data_array, sync=True):
# Update the proxy mesh variable if one exists (for integral calculations)
self._update()

# Sync parallel operations if requested
if sync:
# TODO: Add parallel sync logic here if needed
pass

finally:
# Always restore the field
self.swarm.dm.restoreField(self.clean_name)
Expand Down Expand Up @@ -1334,7 +1341,7 @@ def pack_uw_data_to_petsc(self, data_array, sync=True):
# else:
# return data_array_3d

def unpack_uw_data_from_petsc(self, squeeze=True, sync=True):
def unpack_uw_data_from_petsc(self, squeeze=True, sync=None):
"""
Enhanced unpack method that directly accesses PETSc field without access() context.
Designed for the new swarmVariable.array interface.
Expand All @@ -1343,20 +1350,16 @@ def unpack_uw_data_from_petsc(self, squeeze=True, sync=True):
----------
squeeze : bool
Whether to squeeze singleton dimensions (default True)
sync : bool
Whether to sync parallel operations (default True)
sync : deprecated
Never had an effect; deprecated (one DeprecationWarning if passed).
"""
self._warn_deprecated_sync(sync)
shape = self.shape

# Direct PETSc field access without context manager
petsc_data = self.swarm.dm.getField(self.clean_name).reshape((-1, self.num_components))

try:
# Sync parallel operations if requested
if sync:
# TODO: Add parallel sync logic here if needed
pass

# Unpack data using same layout as original method
points = petsc_data.shape[0]
data_array_3d = np.empty(shape=(points, *shape), dtype=petsc_data.dtype)
Expand All @@ -1375,7 +1378,7 @@ def unpack_uw_data_from_petsc(self, squeeze=True, sync=True):
else:
return data_array_3d

def pack_raw_data_to_petsc(self, data_array, sync=True):
def pack_raw_data_to_petsc(self, data_array, sync=None):
"""
Pack data array to PETSc using traditional data shape (-1, num_components).
Direct PETSc access without access() context for backward compatibility.
Expand All @@ -1384,9 +1387,10 @@ def pack_raw_data_to_petsc(self, data_array, sync=True):
----------
data_array : numpy.ndarray
Array data in traditional flat format (-1, num_components)
sync : bool
Whether to sync parallel operations (default True)
sync : deprecated
Never had an effect; deprecated (one DeprecationWarning if passed).
"""
self._warn_deprecated_sync(sync)
import numpy as np

# Convert to expected shape: (-1, num_components)
Expand All @@ -1409,18 +1413,13 @@ def pack_raw_data_to_petsc(self, data_array, sync=True):
# Update the proxy mesh variable if one exists (for integral calculations)
self._update()

# Sync parallel operations if requested
if sync:
# TODO: Add parallel sync logic here if needed
pass

finally:
# Always restore the field
self.swarm.dm.restoreField(self.clean_name)

return

def unpack_raw_data_from_petsc(self, squeeze=True, sync=True):
def unpack_raw_data_from_petsc(self, squeeze=True, sync=None):
"""
Unpack data from PETSc in traditional data shape (-1, num_components).
Direct PETSc access without access() context for backward compatibility.
Expand All @@ -1429,14 +1428,15 @@ def unpack_raw_data_from_petsc(self, squeeze=True, sync=True):
----------
squeeze : bool
Whether to remove singleton dimensions (default True)
sync : bool
Whether to sync parallel operations (default True)
sync : deprecated
Never had an effect; deprecated (one DeprecationWarning if passed).

Returns
-------
numpy.ndarray
Array data in traditional flat format (-1, num_components)
"""
self._warn_deprecated_sync(sync)
import numpy as np

# Check if swarm has any particles before accessing field
Expand All @@ -1458,11 +1458,6 @@ def unpack_raw_data_from_petsc(self, squeeze=True, sync=True):
# Return data in traditional flat format
result = petsc_data.copy()

# Sync parallel operations if requested
if sync:
# TODO: Add parallel sync logic here if needed
pass

finally:
# Always restore the field
self.swarm.dm.restoreField(self.clean_name)
Expand Down Expand Up @@ -1522,7 +1517,7 @@ def rbf_interpolate(self, new_coords, verbose=False, nnn=None):
import numpy as np

# Get data directly from PETSc to avoid circular callback dependencies
raw_data = self.unpack_raw_data_from_petsc(squeeze=False, sync=False)
raw_data = self.unpack_raw_data_from_petsc(squeeze=False)
data_size = raw_data.shape

# What to do if there are no particles: never SILENTLY return zeros
Expand Down Expand Up @@ -2897,7 +2892,7 @@ def _flush_pending_petsc_sync(self):
f"DMSwarm holds {self.dm.getLocalSize()} particles. The "
"particle layout changed while migration was disabled."
)
var.pack_raw_data_to_petsc(arr, sync=True)
var.pack_raw_data_to_petsc(arr)
if self._coord_var is var:
self._needs_migration = True
if hasattr(var, "_on_data_changed"):
Expand Down
Loading
Loading