def try_one_half_t_regular(model, extent_kji=(2, 2, 2), dxyz=(1.0, 1.0, 1.0), perm_kji=(1.0, 1.0, 1.0), ntg=1.0, darcy_constant=1.0, rotate=None, dip=None): ones = np.ones(extent_kji) grid = grr.RegularGrid(model, extent_kji=extent_kji, dxyz=dxyz) if dip is not None: # dip positive x axis downwards r_matrix = vec.rotation_matrix_3d_axial(1, dip) p = grid.points_ref(masked=False) p[:] = vec.rotate_array(r_matrix, p) if rotate is not None: # rotate anticlockwise in xy plane (viewed from above) r_matrix = vec.rotation_matrix_3d_axial(2, rotate) p = grid.points_ref(masked=False) p[:] = vec.rotate_array(r_matrix, p) half_t = rqtr.half_cell_t(grid, perm_k=perm_kji[0] * ones, perm_j=perm_kji[1] * ones, perm_i=perm_kji[2] * ones, ntg=ntg * ones, darcy_constant=darcy_constant) expected = 2.0 * darcy_constant * np.array( (perm_kji[0] * dxyz[0] * dxyz[1] / dxyz[2], ntg * perm_kji[1] * dxyz[0] * dxyz[2] / dxyz[1], ntg * perm_kji[2] * dxyz[1] * dxyz[2] / dxyz[0])) assert np.all(np.isclose(half_t, expected.reshape(1, 1, 1, 3)))
def local_to_global_array(self, xyz: np.ndarray, global_z_inc_down: bool = True): """Convert in situ a numpy array of xyz points from this coordinate reference system to the parent one.""" if self.rotated: a = vec.rotate_array(self.reverse_rotation_matrix, xyz) xyz[:] = a if self.x_offset != 0.0: xyz[..., 0] += self.x_offset if self.y_offset != 0.0: xyz[..., 1] += self.y_offset if self.z_offset != 0.0: xyz[..., 2] += self.z_offset if global_z_inc_down != self.z_inc_down: z = np.negative(xyz[..., 2]) xyz[..., 2] = z
def best_angles(points, mid_x, mid_y, steps, d_theta): best_range = None best_x_rotation = None best_y_rotation = None half_steps = float(steps - 1) / 2.0 for xi in range(steps): x_degrees = mid_x + (float(xi) - half_steps) * d_theta for yi in range(steps): y_degrees = mid_y + (float(yi) - half_steps) * d_theta rotation_m = vec.rotation_3d_matrix( (x_degrees, 0.0, y_degrees)) p = points.copy() rotated_p = vec.rotate_array(rotation_m, p) z_r = z_range(rotated_p) if best_range is None or z_r < best_range: best_range = z_r best_x_rotation = x_degrees best_y_rotation = y_degrees return (best_x_rotation, best_y_rotation)
def _global_to_local_crs(grid, a, crs_uuid=None, global_xy_units=None, global_z_units=None, global_z_increasing_downward=None): """Converts array of points in situ from global coordinate system to established local one.""" if crs_uuid is None: crs_uuid = grid.crs_uuid if crs_uuid is None: return a flat_a = a.reshape(( -1, 3)) # flattened view of array a as vector of (x, y, z) points, in situ crs = rqc.Crs(grid.model, uuid=crs_uuid) if global_xy_units is not None: bwam.convert_lengths(flat_a[:, 0], global_xy_units, crs.xy_units) # x bwam.convert_lengths(flat_a[:, 1], global_xy_units, crs.xy_units) # y if global_z_units is not None: bwam.convert_lengths(flat_a[:, 2], global_z_units, crs.z_units) # z # This code assumes x, y, z offsets are in local crs units flat_a[:, 0] -= crs.x_offset flat_a[:, 1] -= crs.y_offset flat_a[:, 2] -= crs.z_offset # note: here negation is made in local crs; if z_offset is not zero, this might not be what is intended if global_z_increasing_downward is not None: if global_z_increasing_downward != crs.z_inc_down: flat_a[:, 2] = np.negative(flat_a[:, 2]) if crs.rotated: flat_a[:] = vec.rotate_array(crs.reverse_rotation_matrix, flat_a) return flat_a.reshape(a.shape)
def reorient(points, rough=True, max_dip=None): """Returns a reoriented copy of a set of points, such that z axis is approximate normal to average plane of points. arguments: points (numpy float array of shape (..., 3)): the points to be reoriented rough (bool, default True): if True, the resulting orientation will be within around 10 degrees of the optimum; if False, that reduces to around 2.5 degrees of the optimum max_dip (float, optional): if present, the reorientation of perspective off vertical is limited to this angle in degrees returns: numpy float array of the same shape as points, numpy xyz vector, numpy 3x3 matrix; the array being a copy of points rotated in 3D space to minimise the z range; the vector is a normal vector to the original points; the matrix is rotation matrix used to transform the original points to the reoriented points notes: the original points array is not modified by this function; the function may typically be called prior to the Delauney triangulation, which uses an xy projection to determine the triangulation """ def z_range(p): return np.nanmax(p[..., 2]) - np.nanmin(p[..., 2]) def best_angles(points, mid_x, mid_y, steps, d_theta): best_range = None best_x_rotation = None best_y_rotation = None half_steps = float(steps - 1) / 2.0 for xi in range(steps): x_degrees = mid_x + (float(xi) - half_steps) * d_theta for yi in range(steps): y_degrees = mid_y + (float(yi) - half_steps) * d_theta rotation_m = vec.rotation_3d_matrix( (x_degrees, 0.0, y_degrees)) p = points.copy() rotated_p = vec.rotate_array(rotation_m, p) z_r = z_range(rotated_p) if best_range is None or z_r < best_range: best_range = z_r best_x_rotation = x_degrees best_y_rotation = y_degrees return (best_x_rotation, best_y_rotation) assert points.ndim >= 2 and points.shape[-1] == 3 # coarse iteration trying a few different angles best_x_rotation, best_y_rotation = best_angles(points, 0.0, 0.0, 7, 30.0) # finer iteration searching around the best coarse rotation best_x_rotation, best_y_rotation = best_angles(points, best_x_rotation, best_y_rotation, 5, 10.0) if not rough: # finer iteration searching around the best coarse rotation best_x_rotation, best_y_rotation = best_angles(points, best_x_rotation, best_y_rotation, 7, 2.5) rotation_m = vec.rotation_3d_matrix( (best_x_rotation, 0.0, best_y_rotation)) reverse_m = vec.reverse_rotation_3d_matrix( (best_x_rotation, 0.0, best_y_rotation)) # just the transpose of abpve! if max_dip is not None: v = vec.rotate_vector(reverse_m, np.array((0.0, 0.0, 1.0))) incl = vec.inclination(v) if incl > max_dip: azi = vec.azimuth(v) rotation_m = vec.tilt_3d_matrix( azi, max_dip ) # TODO: check whether any reverse direction errors here reverse_m = rotation_m.T p = points.copy() return vec.rotate_array(rotation_m, p), vec.rotate_vector(reverse_m, np.array((0.0, 0.0, 1.0))), rotation_m
def set_from_point_set(self, point_set, convexity_parameter=5.0, reorient=False, reorient_max_dip=None, extend_with_flange=False, flange_point_count=11, flange_radial_factor=10.0, make_clockwise=False): """Populate this (empty) Surface object with a Delaunay triangulation of points in a PointSet object. arguments: point_set (PointSet): the set of points to be triangulated to form a surface convexity_parameter (float, default 5.0): controls how likely the resulting triangulation is to be convex; reduce to 1.0 to allow slightly more concavities; increase to 100.0 or more for very little chance of even a slight concavity reorient (bool, default False): if True, a copy of the points is made and reoriented to minimise the z range (ie. z axis is approximate normal to plane of points), to enhace the triangulation reorient_max_dip (float, optional): if present, the reorientation of perspective off vertical is limited to this angle in degrees extend_with_flange (bool, default False): if True, a ring of points is added around the outside of the points before the triangulation, effectively extending the surface with a flange flange_point_count (int, default 11): the number of points to generate in the flange ring; ignored if extend_with_flange is False flange_radial_factor (float, default 10.0): distance of flange points from centre of points, as a factor of the maximum radial distance of the points themselves; ignored if extend_with_flange is False make_clockwise (bool, default False): if True, the returned triangles will all be clockwise when viewed in the direction -ve to +ve z axis; if reorient is also True, the clockwise aspect is enforced in the reoriented space returns: if extend_with_flange is True, numpy bool array with a value per triange indicating flange trianges; if extent_with_flange is False, None note: if extend_with_flange is True, then a boolean array is created for the surface, with a value per triangle, set to False (zero) for non-flange triangles and True (one) for flange triangles; this array is suitable for adding as a property for the surface, with indexable element 'faces' """ p = point_set.full_array_ref() if reorient: p_xy, self.normal_vector, reorient_matrix = triangulate.reorient( p, max_dip=reorient_max_dip) else: p_xy = p if extend_with_flange: flange_points = triangulate.surrounding_xy_ring( p_xy, flange_point_count, flange_radial_factor) p_xy_e = np.concatenate((p_xy, flange_points), axis=0) if reorient: # reorient back extenstion points into original p space flange_points_reverse_oriented = vec.rotate_array( reorient_matrix.T, flange_points) p_e = np.concatenate((p, flange_points_reverse_oriented), axis=0) else: p_e = p_xy_e else: p_xy_e = p_xy p_e = p log.debug('number of points going into dt: ' + str(len(p_xy_e))) success = False try: t = triangulate.dt(p_xy_e[:, :2], container_size_factor=convexity_parameter, algorithm="scipy") success = True except AssertionError: pass if not success: log.warning( 'triangulation failed, trying again with tiny perturbation of points' ) p_xy_e[:, :2] += (np.random.random((len(p_xy_e), 2)) - 0.5) * 0.001 t = triangulate.dt(p_xy_e[:, :2], container_size_factor=convexity_parameter * 1.1) log.debug('number of triangles: ' + str(len(t))) if make_clockwise: triangulate.make_all_clockwise_xy(t, p_e) # modifies t in situ self.crs_uuid = point_set.crs_uuid self.set_from_triangles_and_points(t, p_e) if extend_with_flange: flange_array = np.zeros(len(t), dtype=bool) flange_array[:] = np.where(np.any(t >= len(p), axis=1), True, False) return flange_array return None