diff --git a/src/underworld3/adaptivity.py b/src/underworld3/adaptivity.py index 5442960d0..c22a1fb8b 100644 --- a/src/underworld3/adaptivity.py +++ b/src/underworld3/adaptivity.py @@ -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 @@ -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: @@ -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]) @@ -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) diff --git a/src/underworld3/coordinates.py b/src/underworld3/coordinates.py index 3f50b90b1..a8a0b71a7 100644 --- a/src/underworld3/coordinates.py +++ b/src/underworld3/coordinates.py @@ -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 @@ -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 diff --git a/src/underworld3/discretisation/discretisation_mesh.py b/src/underworld3/discretisation/discretisation_mesh.py index 85a03d03a..8859464a4 100644 --- a/src/underworld3/discretisation/discretisation_mesh.py +++ b/src/underworld3/discretisation/discretisation_mesh.py @@ -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) diff --git a/src/underworld3/function/_function.pyx b/src/underworld3/function/_function.pyx index d07eb41eb..c1d1bce3e 100644 --- a/src/underworld3/function/_function.pyx +++ b/src/underworld3/function/_function.pyx @@ -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 diff --git a/src/underworld3/function/functions_unit_system.py b/src/underworld3/function/functions_unit_system.py index b567081da..4d7c0a31a 100644 --- a/src/underworld3/function/functions_unit_system.py +++ b/src/underworld3/function/functions_unit_system.py @@ -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 """ diff --git a/src/underworld3/meshing/surfaces.py b/src/underworld3/meshing/surfaces.py index 21191979b..ed3511f80 100644 --- a/src/underworld3/meshing/surfaces.py +++ b/src/underworld3/meshing/surfaces.py @@ -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 diff --git a/src/underworld3/swarm.py b/src/underworld3/swarm.py index c648f01de..f60921bb6 100644 --- a/src/underworld3/swarm.py +++ b/src/underworld3/swarm.py @@ -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 @@ -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 @@ -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. @@ -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 @@ -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, diff --git a/src/underworld3/utilities/nd_array_callback.py b/src/underworld3/utilities/nd_array_callback.py index 5ce1b0867..d0bafe87e 100644 --- a/src/underworld3/utilities/nd_array_callback.py +++ b/src/underworld3/utilities/nd_array_callback.py @@ -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 """