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
20 changes: 8 additions & 12 deletions src/underworld3/adaptivity.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,9 @@ def create_metric(
# Create metric MeshVariable
metric = uw.discretisation.MeshVariable(name, mesh, 1, degree=1)

with mesh.access(metric):
# Convert to metric tensor: M = 1/h² × I (isotropic)
# This is dimension-independent: same formula for 2D and 3D
metric.data[:, 0] = 1.0 / (h_values ** 2)
# Convert to metric tensor: M = 1/h² × I (isotropic)
# This is dimension-independent: same formula for 2D and 3D
metric.data[:, 0] = 1.0 / (h_values ** 2)

return metric

Expand Down Expand Up @@ -402,8 +401,7 @@ def metric_from_field(
)

# Get indicator values
with mesh.access(indicator):
ind_values = indicator.data[:, 0].copy()
ind_values = indicator.data[:, 0].copy()

# Handle indicator bounds
if indicator_min is None:
Expand Down Expand Up @@ -647,10 +645,9 @@ def mesh2mesh_swarm(mesh0, mesh1, swarm0, swarmVarList, proxy=True, verbose=Fals
if the returned swarm is ephemeral
"""

with swarm0.access():
swarm_data = swarm0._particle_coordinates.data.copy()
for swarmVar in swarmVarList:
swarm_data = np.hstack((swarm_data, np.ascontiguousarray(swarmVar.data.astype(float))))
swarm_data = swarm0._particle_coordinates.data.copy()
for swarmVar in swarmVarList:
swarm_data = np.hstack((swarm_data, np.ascontiguousarray(swarmVar.data.astype(float))))

s_coords0 = np.ascontiguousarray(swarm_data[:, 0 : mesh0.dim])

Expand Down Expand Up @@ -891,8 +888,7 @@ def mesh2mesh_meshVariable(meshVar0, meshVar1, verbose=False):

# print(f"Map data to swarm (rbf) - points = {tmp_swarm.dm.getSize()}", flush=True)

with tmp_swarm.access(tmp_varS):
tmp_varS.data[...] = meshVar0.rbf_interpolate(tmp_swarm._particle_coordinates.data)
tmp_varS.data[...] = meshVar0.rbf_interpolate(tmp_swarm._particle_coordinates.data)

# print(f"Distribute swarm", flush=True)

Expand Down
5 changes: 3 additions & 2 deletions src/underworld3/coordinates.py
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,8 @@ def from_cartesian(self, x, y, z):
Examples
--------
>>> # Convert mesh points to geographic for comparison with data
>>> x, y, z = mesh.data[:, 0], mesh.data[:, 1], mesh.data[:, 2]
>>> coords = mesh.X.coords
>>> x, y, z = coords[:, 0], coords[:, 1], coords[:, 2]
>>> lon, lat, depth = mesh.geo.from_cartesian(x, y, z)
"""
# Nondimensionalise ellipsoid for numeric coordinate conversion
Expand Down Expand Up @@ -834,7 +835,7 @@ def points_from_cartesian(self, points_xyz):
Examples
--------
>>> # Export mesh coordinates to geographic
>>> mesh_xyz = mesh.data # or mesh.CoordinateSystem.coords
>>> mesh_xyz = mesh.X.coords
>>> mesh_llz = mesh.geo.points_from_cartesian(mesh_xyz)
"""
import numpy as np
Expand Down
5 changes: 2 additions & 3 deletions src/underworld3/discretisation/discretisation_mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -2984,9 +2984,8 @@ def update_lvec(self, swarm_sync=True):
# traverse subdms, taking user generated data in the subdm
# local vec, pushing it into a global sub vec
for var, subiset, subdm in zip(self.vars.values(), isets, dms):
# Use access pattern to ensure vector is available
with self.access(var):
lvec = var.vec
# var.vec lazily creates the PETSc local vector on first access
lvec = var.vec
subvec = a_global.getSubVector(subiset)
subdm.localToGlobal(lvec, subvec, addv=False)
a_global.restoreSubVector(subiset, subvec)
Expand Down
3 changes: 1 addition & 2 deletions src/underworld3/function/_function.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -892,8 +892,7 @@ def _clement_to_work_variable(expr, mesh, derivfns):
result = np.full(n_nodes, result[0])

# Store in work variable
with mesh.access(work_var):
work_var.data[:, 0] = result.flatten()
work_var.data[:, 0] = result.flatten()

return work_var

Expand Down
2 changes: 1 addition & 1 deletion src/underworld3/function/functions_unit_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def _evaluate_impl(
--------
>>> # Works with both dimensional and non-dimensional coords
>>> result = uw.function.evaluate(T.sym, T.coords) # dimensional coords
>>> result = uw.function.evaluate(T.sym, mesh.data[:, :2]) # non-dimensional
>>> result = uw.function.evaluate(T.sym, mesh.X.coords[:, :2]) # non-dimensional
>>> if hasattr(result, 'to'):
... result_K = result.to('K') # Unit conversion
"""
Expand Down
3 changes: 2 additions & 1 deletion src/underworld3/meshing/surfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -2122,7 +2122,8 @@ def transfer_normals(

Args:
mesh: The mesh to transfer normals to
coords: Optional coordinates to query. If None, uses mesh.data
coords: Optional coordinates to query. If None, uses the mesh's
own vertex coordinates (model space)
normal_var: Optional existing MeshVariable
variable_name: Name for new variable

Expand Down
172 changes: 13 additions & 159 deletions src/underworld3/swarm.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,6 @@ def __init__(
# add to swarms dict

self.swarm._vars[self.clean_name] = self
self._is_accessed = False

# Initialize proxy flags first before creating proxy variable
self._updating_proxy = False # Flag to prevent recursive proxy updates
Expand Down Expand Up @@ -1230,19 +1229,18 @@ def _rbf_reduce_to_meshVar(self, meshVar, verbose=False):
# Use cached KDTree for interpolation (avoids redundant index construction)
kd = meshVar._get_kdtree()

with self.swarm.access():
d, n = kd.query(self.swarm.data, k=1, sqr_dists=False) # need actual distances
d, n = kd.query(self.swarm.data, k=1, sqr_dists=False) # need actual distances

node_values = np.zeros((meshVar.coords.shape[0], self.num_components))
w = np.zeros(meshVar.coords.shape[0])
node_values = np.zeros((meshVar.coords.shape[0], self.num_components))
w = np.zeros(meshVar.coords.shape[0])

if not self._nn_proxy:
for i in range(self.local_size):
# if b[i]:
node_values[n[i], :] += self.data[i, :] / (1.0e-24 + d[i])
w[n[i]] += 1.0 / (1.0e-24 + d[i])
if not self._nn_proxy:
for i in range(self.local_size):
# if b[i]:
node_values[n[i], :] += self.data[i, :] / (1.0e-24 + d[i])
w[n[i]] += 1.0 / (1.0e-24 + d[i])

node_values[np.where(w > 0.0)[0], :] /= w[np.where(w > 0.0)[0]].reshape(-1, 1)
node_values[np.where(w > 0.0)[0], :] /= w[np.where(w > 0.0)[0]].reshape(-1, 1)

# 2 - set NN vals on mesh var where w == 0.0

Expand Down Expand Up @@ -4594,147 +4592,6 @@ def apply_snapshot_payload(self, payload: dict) -> None:
# that garbage (exposed by the SWARM-01 invalidation fix).
var.data[...] = saved

def _legacy_access(self, *writeable_vars: SwarmVariable):
"""
This context manager makes the underlying swarm variables data available to
the user. The data should be accessed via the variables `data` handle.

As default, all data is read-only. To enable writeable data, the user should
specify which variable they wish to modify.

At the conclusion of the users context managed block, numerous further operations
will be automatically executed. This includes swarm parallel migration routines
where the swarm's `particle_coordinates` variable has been modified. The swarm
variable proxy mesh variables will also be updated for modifed swarm variables.

Parameters
----------
writeable_vars
The variables for which data write access is required.

Example
-------

>>> import underworld3 as uw
>>> someMesh = uw.discretisation.FeMesh_Cartesian()
>>> with someMesh._deform_mesh():
... someMesh.data[0] = [0.1,0.1]
>>> someMesh.data[0]
array([ 0.1, 0.1])
"""
import time

uw.timing._incrementDepth()
stime = time.time()

deaccess_list = []
for var in self._vars.values():
# if already accessed within higher level context manager, continue.
if var._is_accessed == True:
continue
# set flag so variable status can be known elsewhere
var._is_accessed = True
# add to de-access list to rewind this later
deaccess_list.append(var)
# grab numpy object, setting read only if necessary
var._data = self.dm.getField(var.clean_name).reshape((-1, var.num_components))
assert var._data is not None
if var not in writeable_vars:
var._old_data_flag = var._data.flags.writeable
var._data.flags.writeable = False
else:
# increment variable state
var._increment()

# make *view* for each var component
if var._proxy:
for i in range(0, var.shape[0]):
for j in range(0, var.shape[1]):
var._data_container[i, j] = var._data_container[i, j]._replace(
data=var._data[:, var._data_layout(i, j)],
)

# if particles moving, update swarm state
if self._particle_coordinates in writeable_vars:
self._increment()

# Create a class which specifies the required context
# manager hooks (`__enter__`, `__exit__`).
class exit_manager:
def __init__(self, swarm):
self.em_swarm = swarm

def __enter__(self):

pass

def __exit__(self, *args):

for var in self.em_swarm.vars.values():
# only de-access variables we have set access for.
if var not in deaccess_list:
continue
# set this back, although possibly not required.
if var not in writeable_vars:
var._data.flags.writeable = var._old_data_flag
var._data = None
self.em_swarm.dm.restoreField(var.clean_name)
var._is_accessed = False
# do particle migration if coords changes

if self.em_swarm._particle_coordinates in writeable_vars:
# let's use the mesh index to update the particles owning cells.
# note that the `petsc4py` interface is more convenient here as the
# `SwarmVariable.data` interface is controlled by the context manager
# that we are currently within, and it is therefore too easy to
# get things wrong that way.
#
#

# if uw.mpi.size > 1:
# coords = self.em_swarm.dm.getField("DMSwarmPIC_coor").reshape(
# (-1, self.em_swarm.dim)
# )

# self.em_swarm.dm.restoreField("DMSwarmPIC_coor")

# ## We'll need to identify the new processes here and update the particle rank value accordingly
#

# Even if only on one process, migrate needs to be called to remove particles that are
# not in the domain.

self.em_swarm.migrate(
remove_sent_points=True,
delete_lost_points=self.em_swarm._clip_to_mesh,
)

# void these things too
self.em_swarm._index = None
self.em_swarm._nnmapdict = {}

# do var updates
for var in self.em_swarm.vars.values():
# if swarm migrated, update all.
# if var updated, update var.
if (self.em_swarm._particle_coordinates in writeable_vars) or (
var in writeable_vars
):
var._update()

if var._proxy:
for i in range(0, var.shape[0]):
for j in range(0, var.shape[1]):
# var._data_ij[i, j] = None
var._data_container[i, j] = var._data_container[i, j]._replace(
data=f"SwarmVariable[...].data is only available within mesh.access() context",
)

uw.timing._decrementDepth()
uw.timing.log_result(time.time() - stime, "Swarm.access", 1)

return exit_manager(self)

def access(self, *writeable_vars: SwarmVariable):
"""
Dummy access manager that provides deferred sync for backward compatibility.
Expand Down Expand Up @@ -5176,9 +5033,8 @@ def __init__(

nswarm.dm.migrate(remove_sent_points=True)

with nswarm.access(nX0, nI0):
nX0.data[:, :] = coords
nI0.data[:, 0] = range(0, coords.shape[0])
nX0.data[:, :] = coords
nI0.data[:, 0] = range(0, coords.shape[0])

self._nswarm = nswarm
self._nX0 = nX0
Expand All @@ -5199,11 +5055,9 @@ def advection(
step_limit=True,
):

with self.access(self._X0):
self._X0.data[...] = self._nX0.data[...]
self._X0.data[...] = self._nX0.data[...]

with self.access(self._nR0):
self._nR0.data[...] = uw.mpi.rank
self._nR0.data[...] = uw.mpi.rank

super().advection(
V_fn,
Expand Down
6 changes: 3 additions & 3 deletions src/underworld3/utilities/nd_array_callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,9 +421,9 @@ def delay_callbacks_global(context_info=None):

Example
-------
>>> with NDArray_With_Callback.delay_callbacks_global("mesh update"):
... mesh.data[0] = new_pos
... swarm.data += displacement
>>> with NDArray_With_Callback.delay_callbacks_global("field update"):
... temperature.array[...] = new_T
... material.array[...] = new_material
# All callbacks from all arrays fire here
"""

Expand Down
Loading