From eec9a29b6f9ece4dca188bee37cb3dbe0c5a2153 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Tue, 13 Feb 2024 19:06:40 -0600 Subject: [PATCH 01/64] draft 1 off-axis projection and small-particle proj. fix (off-axis based heavily on PR 4712) --- yt/utilities/lib/pixelization_routines.pyx | 183 ++++++++++++------ yt/visualization/fixed_resolution.py | 2 + yt/visualization/plot_window.py | 2 + .../volume_rendering/off_axis_projection.py | 25 ++- 4 files changed, 151 insertions(+), 61 deletions(-) diff --git a/yt/utilities/lib/pixelization_routines.pyx b/yt/utilities/lib/pixelization_routines.pyx index 8cc37ab1d12..711a9fcb3c5 100644 --- a/yt/utilities/lib/pixelization_routines.pyx +++ b/yt/utilities/lib/pixelization_routines.pyx @@ -1123,6 +1123,7 @@ def pixelize_sph_kernel_projection( np.uint8_t[:, :] mask, any_float[:] posx, any_float[:] posy, + any_float[:] posz, any_float[:] hsml, any_float[:] pmass, any_float[:] pdens, @@ -1134,17 +1135,19 @@ def pixelize_sph_kernel_projection( period=None): cdef np.intp_t xsize, ysize - cdef np.float64_t x_min, x_max, y_min, y_max, prefactor_j + cdef np.float64_t x_min, x_max, y_min, y_max, z_min, z_max, prefactor_j cdef np.int64_t xi, yi, x0, x1, y0, y1, xxi, yyi cdef np.float64_t q_ij2, posx_diff, posy_diff, ih_j2 - cdef np.float64_t x, y, dx, dy, idx, idy, h_j2, px, py - cdef np.float64_t period_x = 0, period_y = 0 - cdef int i, j, ii, jj + cdef np.float64_t x, y, dx, dy, idx, idy, h_j2, px, py, pz + cdef np.float64_t period_x = 0, period_y = 0, period_z = 0 + cdef int i, j, ii, jj, kk cdef np.float64_t[:] _weight_field cdef int * xiter cdef int * yiter + cdef int * ziter cdef np.float64_t * xiterv cdef np.float64_t * yiterv + cdef np.float64_t * ziterv if weight_field is not None: _weight_field = weight_field @@ -1152,6 +1155,7 @@ def pixelize_sph_kernel_projection( if period is not None: period_x = period[0] period_y = period[1] + period_z = period[2] # we find the x and y range over which we have pixels and we find how many # pixels we have in each dimension @@ -1160,6 +1164,8 @@ def pixelize_sph_kernel_projection( x_max = bounds[1] y_min = bounds[2] y_max = bounds[3] + z_min = bounds[4] + z_max = bounds[5] dx = (x_max - x_min) / xsize dy = (y_max - y_min) / ysize @@ -1190,10 +1196,12 @@ def pixelize_sph_kernel_projection( local_buff = malloc(sizeof(np.float64_t) * xsize * ysize) xiterv = malloc(sizeof(np.float64_t) * 2) yiterv = malloc(sizeof(np.float64_t) * 2) + ziterv = malloc(sizeof(np.float64_t) * 2) xiter = malloc(sizeof(int) * 2) yiter = malloc(sizeof(int) * 2) - xiter[0] = yiter[0] = 0 - xiterv[0] = yiterv[0] = 0.0 + ziter = malloc(sizeof(int) * 2) + xiter[0] = yiter[0] = ziter[0] = 0 + xiterv[0] = yiterv[0] = ziterv[0] = 0.0 for i in range(xsize * ysize): local_buff[i] = 0.0 @@ -1202,7 +1210,7 @@ def pixelize_sph_kernel_projection( with gil: PyErr_CheckSignals() - xiter[1] = yiter[1] = 999 + xiter[1] = yiter[1] = ziter[1] = 999 if check_period == 1: if posx[j] - hsml[j] < x_min: @@ -1217,60 +1225,77 @@ def pixelize_sph_kernel_projection( elif posy[j] + hsml[j] > y_max: yiter[1] = -1 yiterv[1] = -period_y + if posz[j] - hsml[j] < z_min: + ziter[1] = +1 + ziterv[1] = period_z + elif posz[j] + hsml[j] > z_max: + ziter[1] = -1 + ziterv[1] = -period_z # we set the smoothing length squared with lower limit of the pixel - h_j2 = fmax(hsml[j]*hsml[j], dx*dy) + # Nope! that causes weird grid resolution dependences and increases + # total values when resolution elements have hsml < grid spacing + h_j2 = hsml[j]*hsml[j] ih_j2 = 1.0/h_j2 prefactor_j = pmass[j] / pdens[j] / hsml[j]**2 * quantity_to_smooth[j] if weight_field is not None: prefactor_j *= _weight_field[j] + + # Discussion point: do we want the hsml margin on the z direction? + # it's consistent with Ray and Region selections, I think, + # but does tend to 'tack on' stuff compared to the nominal depth + for kk in range(2): + # discard if z is outside bounds + if ziter[kk] == 999: continue + pz = posz[j] + ziterv[kk] + if (pz + hsml[j] < z_min) or (pz - hsml[j] > z_max): continue + + for ii in range(2): + if xiter[ii] == 999: continue + px = posx[j] + xiterv[ii] + if (px + hsml[j] < x_min) or (px - hsml[j] > x_max): continue + for jj in range(2): + if yiter[jj] == 999: continue + py = posy[j] + yiterv[jj] + if (py + hsml[j] < y_min) or (py - hsml[j] > y_max): continue + + # here we find the pixels which this particle contributes to + x0 = ((px - hsml[j] - x_min)*idx) + x1 = ((px + hsml[j] - x_min)*idx) + x0 = iclip(x0-1, 0, xsize) + x1 = iclip(x1+1, 0, xsize) - for ii in range(2): - if xiter[ii] == 999: continue - px = posx[j] + xiterv[ii] - if (px + hsml[j] < x_min) or (px - hsml[j] > x_max): continue - for jj in range(2): - if yiter[jj] == 999: continue - py = posy[j] + yiterv[jj] - if (py + hsml[j] < y_min) or (py - hsml[j] > y_max): continue - - # here we find the pixels which this particle contributes to - x0 = ((px - hsml[j] - x_min)*idx) - x1 = ((px + hsml[j] - x_min)*idx) - x0 = iclip(x0-1, 0, xsize) - x1 = iclip(x1+1, 0, xsize) - - y0 = ((py - hsml[j] - y_min)*idy) - y1 = ((py + hsml[j] - y_min)*idy) - y0 = iclip(y0-1, 0, ysize) - y1 = iclip(y1+1, 0, ysize) + y0 = ((py - hsml[j] - y_min)*idy) + y1 = ((py + hsml[j] - y_min)*idy) + y0 = iclip(y0-1, 0, ysize) + y1 = iclip(y1+1, 0, ysize) - # found pixels we deposit on, loop through those pixels - for xi in range(x0, x1): - # we use the centre of the pixel to calculate contribution - x = (xi + 0.5) * dx + x_min + # found pixels we deposit on, loop through those pixels + for xi in range(x0, x1): + # we use the centre of the pixel to calculate contribution + x = (xi + 0.5) * dx + x_min - posx_diff = px - x - posx_diff = posx_diff * posx_diff + posx_diff = px - x + posx_diff = posx_diff * posx_diff - if posx_diff > h_j2: continue + if posx_diff > h_j2: continue - for yi in range(y0, y1): - y = (yi + 0.5) * dy + y_min + for yi in range(y0, y1): + y = (yi + 0.5) * dy + y_min - posy_diff = py - y - posy_diff = posy_diff * posy_diff - if posy_diff > h_j2: continue + posy_diff = py - y + posy_diff = posy_diff * posy_diff + if posy_diff > h_j2: continue - q_ij2 = (posx_diff + posy_diff) * ih_j2 - if q_ij2 >= 1: - continue + q_ij2 = (posx_diff + posy_diff) * ih_j2 + if q_ij2 >= 1: + continue - # see equation 32 of the SPLASH paper - # now we just use the kernel projection - local_buff[xi + yi*xsize] += prefactor_j * itab.interpolate(q_ij2) - mask[xi, yi] = 1 + # see equation 32 of the SPLASH paper + # now we just use the kernel projection + local_buff[xi + yi*xsize] += prefactor_j * itab.interpolate(q_ij2) + mask[xi, yi] = 1 with gil: for xxi in range(xsize): @@ -1864,7 +1889,10 @@ def rotate_particle_coord(np.float64_t[:] px, np.float64_t[:] py, np.float64_t[:] pz, center, + bounds, + periodic, width, + depth, normal_vector, north_vector): # We want to do two rotations, one to first rotate our coordinates to have @@ -1886,28 +1914,50 @@ def rotate_particle_coord(np.float64_t[:] px, cdef np.float64_t[:] px_rotated = np.empty(num_particles, dtype="float64") cdef np.float64_t[:] py_rotated = np.empty(num_particles, dtype="float64") + cdef np.float64_t[:] pz_rotated = np.empty(num_particles, dtype="float64") cdef np.float64_t[:] coordinate_matrix = np.empty(3, dtype="float64") cdef np.float64_t[:] rotated_coordinates cdef np.float64_t[:] rotated_center - rotated_center = rotation_matmul( - rotation_matrix, np.array([center[0], center[1], center[2]])) - + #rotated_center = rotation_matmul( + # rotation_matrix, np.array([center[0], center[1], center[2]])) + rotated_center = np.zeros(3, dtype=center.dtype) # set up the rotated bounds - cdef np.float64_t rot_bounds_x0 = rotated_center[0] - width[0] / 2 - cdef np.float64_t rot_bounds_x1 = rotated_center[0] + width[0] / 2 - cdef np.float64_t rot_bounds_y0 = rotated_center[1] - width[1] / 2 - cdef np.float64_t rot_bounds_y1 = rotated_center[1] + width[1] / 2 + cdef np.float64_t rot_bounds_x0 = rotated_center[0] - 0.5 * width[0] + cdef np.float64_t rot_bounds_x1 = rotated_center[0] + 0.5 * width[0] + cdef np.float64_t rot_bounds_y0 = rotated_center[1] - 0.5 * width[1] + cdef np.float64_t rot_bounds_y1 = rotated_center[1] + 0.5 * width[1] + cdef np.float64_t rot_bounds_z0 = rotated_center[2] - 0.5 * depth[2] + cdef np.float64_t rot_bounds_z1 = rotated_center[2] + 0.5 * depth[2] for i in range(num_particles): coordinate_matrix[0] = px[i] coordinate_matrix[1] = py[i] coordinate_matrix[2] = pz[i] + + # centering: + # make sure this also works for centers close to periodic edges + # added consequence: the center is placed at the origin + # (might as well keep it there in these temporary coordinates) + for ax in range(3): + if not periodic[ax]: continue + period = bounds[2 * ax + 1] - bounds[2 * ax] + coordinate_matrix[ax] -= center[ax] + # abs. difference between points in the volume is <= period + if coordinate_matrix[ax] < -0.5 * period: + coordinate_matrix[ax] += period + if coordinate_matrix[ax] > 0.5 * period: + coordinate_matrix[ax] -= period + rotated_coordinates = rotation_matmul( rotation_matrix, coordinate_matrix) px_rotated[i] = rotated_coordinates[0] py_rotated[i] = rotated_coordinates[1] + pz_rotated[i] = rotated_coordinates[2] - return px_rotated, py_rotated, rot_bounds_x0, rot_bounds_x1, rot_bounds_y0, rot_bounds_y1 + return (px_rotated, py_rotated, pz_rotated, + rot_bounds_x0, rot_bounds_x1, + rot_bounds_y0, rot_bounds_y1, + rot_bounds_z0, rot_bounds_z1) @cython.boundscheck(False) @@ -1921,31 +1971,46 @@ def off_axis_projection_SPH(np.float64_t[:] px, bounds, center, width, + periodic, np.float64_t[:] quantity_to_smooth, np.float64_t[:, :] projection_array, np.uint8_t[:, :] mask, normal_vector, north_vector, - weight_field=None): + weight_field=None, + depth=None): + # periodic: periodicity of the data set: # Do nothing in event of a 0 normal vector if np.allclose(normal_vector, 0.): return + if depth is None: + # set to volume diagonal -> won't exclude anything + depth = np.sqrt((bounds[1] - bounds[0])**2 + + (bounds[3] - bounds[2])**2, + + (bounds[5] - bounds[4])**2) - px_rotated, py_rotated, \ + px_rotated, py_rotated, pz_rotated, \ rot_bounds_x0, rot_bounds_x1, \ - rot_bounds_y0, rot_bounds_y1 = rotate_particle_coord(px, py, pz, - center, width, normal_vector, north_vector) + rot_bounds_y0, rot_bounds_y1, \ + rot_bounds_z0, rot_bounds_z1 = rotate_particle_coord(px, py, pz, + center, bounds, + periodic, + width, depth, + normal_vector, + north_vector) pixelize_sph_kernel_projection(projection_array, mask, px_rotated, py_rotated, + pz_rotated, smoothing_lengths, particle_masses, particle_densities, quantity_to_smooth, [rot_bounds_x0, rot_bounds_x1, - rot_bounds_y0, rot_bounds_y1], + rot_bounds_y0, rot_bounds_y1, + rot_bounds_z0, rot_bounds_z1], weight_field=weight_field, check_period=0) diff --git a/yt/visualization/fixed_resolution.py b/yt/visualization/fixed_resolution.py index d2da6a29e10..464e1bf22af 100644 --- a/yt/visualization/fixed_resolution.py +++ b/yt/visualization/fixed_resolution.py @@ -649,6 +649,7 @@ def _generate_image_and_mask(self, item) -> None: no_ghost=dd.no_ghost, interpolated=dd.interpolated, north_vector=dd.north_vector, + depth=dd.depth, method=dd.method, ) if self.data_source.moment == 2: @@ -679,6 +680,7 @@ def _sq_field(field, data, item: FieldKey): no_ghost=dd.no_ghost, interpolated=dd.interpolated, north_vector=dd.north_vector, + depth=dd.depth, method=dd.method, ) buff = compute_stddev_image(buff2, buff) diff --git a/yt/visualization/plot_window.py b/yt/visualization/plot_window.py index 4534d7325c3..89271191709 100644 --- a/yt/visualization/plot_window.py +++ b/yt/visualization/plot_window.py @@ -2273,6 +2273,7 @@ def __init__( le=None, re=None, north_vector=None, + depth=None, method="integrate", data_source=None, *, @@ -2284,6 +2285,7 @@ def __init__( self.axis = None # always true for oblique data objects self.normal_vector = normal_vector self.width = width + self.depth = depth if data_source is None: self.dd = ds.all_data() else: diff --git a/yt/visualization/volume_rendering/off_axis_projection.py b/yt/visualization/volume_rendering/off_axis_projection.py index 392749633e9..217518003c2 100644 --- a/yt/visualization/volume_rendering/off_axis_projection.py +++ b/yt/visualization/volume_rendering/off_axis_projection.py @@ -30,6 +30,7 @@ def off_axis_projection( no_ghost=False, interpolated=False, north_vector=None, + depth=None, num_threads=1, method="integrate", ): @@ -80,6 +81,7 @@ def off_axis_projection( north_vector : optional, array_like, default None A vector that, if specified, restricts the orientation such that the north vector dotted into the image plane points "up". Useful for rotations + depth: float or num_threads: integer, optional, default 1 Use this many OpenMP threads during projection. method : string @@ -145,6 +147,11 @@ def off_axis_projection( center = data_source.ds.arr(center, "code_length") if not hasattr(width, "units"): width = data_source.ds.arr(width, "code_length") + if depth is not None: + if hasattr(depth, "units"): + depth = depth.to("code_length").d + #depth = data_source.ds.arr(depth, "code_length") + if hasattr(data_source.ds, "_sph_ptypes"): if method != "integrate": @@ -202,16 +209,23 @@ def off_axis_projection( # if weight is None: buf = np.zeros((resolution[0], resolution[1]), dtype="float64") mask = np.ones_like(buf, dtype="uint8") - + + # width from fixed_resolution.py is just the size of the domain x_min = center[0] - width[0] / 2 x_max = center[0] + width[0] / 2 y_min = center[1] - width[1] / 2 y_max = center[1] + width[1] / 2 z_min = center[2] - width[2] / 2 z_max = center[2] + width[2] / 2 + bounds = [x_min, x_max, y_min, y_max, z_min, z_max] + periodic = data_source.ds.periodicity + #le = data_source.ds.domain_left_edge.to("code_length").d + #re = data_source.ds.domain_right_edge.to("code_length").d + #x_min, y_min, z_min = le + #x_max, y_max, z_max = re + finfo = data_source.ds.field_info[item] ounits = finfo.output_units - bounds = [x_min, x_max, y_min, y_max, z_min, z_max] if weight is None: for chunk in data_source.chunks([], "io"): @@ -225,11 +239,13 @@ def off_axis_projection( bounds, center.to("code_length").d, width.to("code_length").d, + periodic, chunk[item].in_units(ounits), buf, mask, normal_vector, north, + depth=depth ) # Assure that the path length unit is in the default length units @@ -263,12 +279,14 @@ def off_axis_projection( bounds, center.to("code_length").d, width.to("code_length").d, + periodic, chunk[item].in_units(ounits), buf, mask, normal_vector, north, weight_field=chunk[weight].in_units(wounits), + depth=depth, ) for chunk in data_source.chunks([], "io"): @@ -282,11 +300,13 @@ def off_axis_projection( bounds, center.to("code_length").d, width.to("code_length").d, + periodic, chunk[weight].to(wounits), weight_buff, mask, normal_vector, north, + depth=depth, ) normalization_2d_utility(buf, weight_buff) @@ -300,6 +320,7 @@ def off_axis_projection( "north_vector": north_vector, "normal_vector": normal_vector, "width": width, + "depth": depth, "units": funits, "type": "SPH smoothed projection", } From 56c93d39e45110bb0d11e0566783e8cc2755badb Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Fri, 16 Feb 2024 21:16:37 -0600 Subject: [PATCH 02/64] debugging: axis-aligned SPH projection works again, off-axis runs, but fails output value tests --- .../coordinates/cartesian_coordinates.py | 58 +++++++++++++------ yt/utilities/lib/pixelization_routines.pyx | 11 ++-- yt/visualization/fixed_resolution.py | 1 - yt/visualization/plot_window.py | 11 ++-- .../volume_rendering/off_axis_projection.py | 38 +++++++----- 5 files changed, 76 insertions(+), 43 deletions(-) diff --git a/yt/geometry/coordinates/cartesian_coordinates.py b/yt/geometry/coordinates/cartesian_coordinates.py index 775de8c6a54..7e1fbb11cbe 100644 --- a/yt/geometry/coordinates/cartesian_coordinates.py +++ b/yt/geometry/coordinates/cartesian_coordinates.py @@ -323,11 +323,20 @@ def _ortho_pixelize( # We should be using fcoords field = data_source._determine_fields(field)[0] finfo = data_source.ds.field_info[field] - period = self.period[:2].copy() # dummy here - period[0] = self.period[self.x_axis[dim]] - period[1] = self.period[self.y_axis[dim]] - if hasattr(period, "in_units"): - period = period.in_units("code_length").d + # some coordinate handlers use only projection-plane periods, + # others need all box periods. + period2 = self.period[:2].copy() # dummy here + period2[0] = self.period[self.x_axis[dim]] + period2[1] = self.period[self.y_axis[dim]] + period3 = self.period[:].copy() # dummy here + period3[0] = self.period[self.x_axis[dim]] + period3[1] = self.period[self.y_axis[dim]] + zax = list({0, 1, 2} - {self.x_axis[dim], self.y_axis[dim]}) + period3[2] = self.period[zax] + if hasattr(period2, "in_units"): + period2 = period2.in_units("code_length").d + if hasattr(period3, "in_units"): + period3 = period3.in_units("code_length").d buff = np.full((size[1], size[0]), np.nan, dtype="float64") particle_datasets = (ParticleDataset, StreamParticlesDataset) @@ -349,7 +358,7 @@ def _ortho_pixelize( coord, bounds, int(antialias), - period, + period2, int(periodic), return_mask=True, ) @@ -359,8 +368,13 @@ def _ortho_pixelize( ptype = data_source.ds._sph_ptypes[0] px_name = self.axis_name[self.x_axis[dim]] py_name = self.axis_name[self.y_axis[dim]] + # need z coordinates for depth, + # but name isn't saved in the handler -> use the 'other one' + pz_name = list((set(self.axis_order) - {px_name, py_name}))[0] + ounits = data_source.ds.field_info[field].output_units bnds = data_source.ds.arr(bounds, "code_length").tolist() + if isinstance(data_source, YTParticleProj): weight = data_source.weight_field moment = data_source.moment @@ -369,6 +383,7 @@ def _ortho_pixelize( ya = self.y_axis[dim] # If we're not periodic, we need to clip to the boundary edges # or we get errors about extending off the edge of the region. + # (depth/z range is handled by region setting) if not self.ds.periodicity[xa]: le[xa] = max(bounds[0], self.ds.domain_left_edge[xa]) re[xa] = min(bounds[1], self.ds.domain_right_edge[xa]) @@ -389,6 +404,11 @@ def _ortho_pixelize( data_source=data_source.data_source, ) proj_reg.set_field_parameter("axis", data_source.axis) + # need some z bounds for SPH projection + # -> use source bounds + zax = list({0, 1, 2} - set([xa, ya]))[0] + bnds3 = bnds + [le[zax], re[zax]] + buff = np.zeros(size, dtype="float64") mask_uint8 = np.zeros_like(buff, dtype="uint8") if weight is None: @@ -399,13 +419,14 @@ def _ortho_pixelize( mask_uint8, chunk[ptype, px_name].to("code_length"), chunk[ptype, py_name].to("code_length"), + chunk[ptype, pz_name].to("code_length"), chunk[ptype, "smoothing_length"].to("code_length"), chunk[ptype, "mass"].to("code_mass"), chunk[ptype, "density"].to("code_density"), chunk[field].in_units(ounits), - bnds, + bnds3, check_period=int(periodic), - period=period, + period=period3, ) # We use code length here, but to get the path length right # we need to multiply by the conversion factor between @@ -430,13 +451,14 @@ def _ortho_pixelize( mask_uint8, chunk[ptype, px_name].to("code_length"), chunk[ptype, py_name].to("code_length"), + chunk[ptype, pz_name].to("code_length"), chunk[ptype, "smoothing_length"].to("code_length"), chunk[ptype, "mass"].to("code_mass"), chunk[ptype, "density"].to("code_density"), chunk[field].in_units(ounits), - bnds, + bnds3, check_period=int(periodic), - period=period, + period=period3, weight_field=chunk[weight].in_units(wounits), ) mylog.info( @@ -452,13 +474,14 @@ def _ortho_pixelize( mask_uint8, chunk[ptype, px_name].to("code_length"), chunk[ptype, py_name].to("code_length"), + chunk[ptype, pz_name].to("code_length"), chunk[ptype, "smoothing_length"].to("code_length"), chunk[ptype, "mass"].to("code_mass"), chunk[ptype, "density"].to("code_density"), chunk[weight].in_units(wounits), - bnds, + bnds3, check_period=int(periodic), - period=period, + period=period3, ) normalization_2d_utility(buff, weight_buff) if moment == 2: @@ -471,13 +494,14 @@ def _ortho_pixelize( mask_uint8, chunk[ptype, px_name].to("code_length"), chunk[ptype, py_name].to("code_length"), + chunk[ptype, pz_name].to("code_length"), chunk[ptype, "smoothing_length"].to("code_length"), chunk[ptype, "mass"].to("code_mass"), chunk[ptype, "density"].to("code_density"), chunk[field].in_units(ounits) ** 2, - bnds, + bnds3, check_period=int(periodic), - period=period, + period=period3, weight_field=chunk[weight].in_units(wounits), ) normalization_2d_utility(buff2, weight_buff) @@ -505,7 +529,7 @@ def _ortho_pixelize( chunk[field].in_units(ounits), bnds, check_period=int(periodic), - period=period, + period=period2, ) if normalize: pixelize_sph_kernel_slice( @@ -519,7 +543,7 @@ def _ortho_pixelize( np.ones(chunk[ptype, "density"].shape[0]), bnds, check_period=int(periodic), - period=period, + period=period2, ) if normalize: @@ -608,7 +632,7 @@ def _ortho_pixelize( data_source[field], bounds, int(antialias), - period, + period2, int(periodic), return_mask=True, ) diff --git a/yt/utilities/lib/pixelization_routines.pyx b/yt/utilities/lib/pixelization_routines.pyx index 711a9fcb3c5..52d964d9e21 100644 --- a/yt/utilities/lib/pixelization_routines.pyx +++ b/yt/utilities/lib/pixelization_routines.pyx @@ -1176,7 +1176,6 @@ def pixelize_sph_kernel_projection( if kernel_name not in kernel_tables: kernel_tables[kernel_name] = SPHKernelInterpolationTable(kernel_name) cdef SPHKernelInterpolationTable itab = kernel_tables[kernel_name] - with nogil, parallel(): # loop through every particle # NOTE: this loop can be quite time consuming. However it is easily @@ -1204,7 +1203,7 @@ def pixelize_sph_kernel_projection( xiterv[0] = yiterv[0] = ziterv[0] = 0.0 for i in range(xsize * ysize): local_buff[i] = 0.0 - + for j in prange(0, posx.shape[0], schedule="dynamic"): if j % 100000 == 0: with gil: @@ -1920,14 +1919,14 @@ def rotate_particle_coord(np.float64_t[:] px, cdef np.float64_t[:] rotated_center #rotated_center = rotation_matmul( # rotation_matrix, np.array([center[0], center[1], center[2]])) - rotated_center = np.zeros(3, dtype=center.dtype) + rotated_center = np.zeros((3,), dtype=center.dtype) # set up the rotated bounds cdef np.float64_t rot_bounds_x0 = rotated_center[0] - 0.5 * width[0] cdef np.float64_t rot_bounds_x1 = rotated_center[0] + 0.5 * width[0] cdef np.float64_t rot_bounds_y0 = rotated_center[1] - 0.5 * width[1] cdef np.float64_t rot_bounds_y1 = rotated_center[1] + 0.5 * width[1] - cdef np.float64_t rot_bounds_z0 = rotated_center[2] - 0.5 * depth[2] - cdef np.float64_t rot_bounds_z1 = rotated_center[2] + 0.5 * depth[2] + cdef np.float64_t rot_bounds_z0 = rotated_center[2] - 0.5 * depth + cdef np.float64_t rot_bounds_z1 = rotated_center[2] + 0.5 * depth for i in range(num_particles): coordinate_matrix[0] = px[i] @@ -1986,7 +1985,7 @@ def off_axis_projection_SPH(np.float64_t[:] px, if depth is None: # set to volume diagonal -> won't exclude anything depth = np.sqrt((bounds[1] - bounds[0])**2 - + (bounds[3] - bounds[2])**2, + + (bounds[3] - bounds[2])**2 + (bounds[5] - bounds[4])**2) px_rotated, py_rotated, pz_rotated, \ diff --git a/yt/visualization/fixed_resolution.py b/yt/visualization/fixed_resolution.py index 464e1bf22af..782e734ad3e 100644 --- a/yt/visualization/fixed_resolution.py +++ b/yt/visualization/fixed_resolution.py @@ -634,7 +634,6 @@ def _generate_image_and_mask(self, item) -> None: ( self.bounds[1] - self.bounds[0], self.bounds[3] - self.bounds[2], - self.bounds[5] - self.bounds[4], ) ) buff = off_axis_projection( diff --git a/yt/visualization/plot_window.py b/yt/visualization/plot_window.py index 89271191709..b05a195fd94 100644 --- a/yt/visualization/plot_window.py +++ b/yt/visualization/plot_window.py @@ -86,14 +86,16 @@ def get_oblique_window_parameters(normal, center, width, ds, depth=None): if len(width) == 2: # Transforming to the cutting plane coordinate system - center = (center - ds.domain_left_edge) / ds.domain_width - 0.5 + # the original dimensionless center messes up off-axis + # SPH projections though + center = ((center - ds.domain_left_edge) / ds.domain_width - 0.5)\ + * ds.domain_width (normal, perp1, perp2) = ortho_find(normal) mat = np.transpose(np.column_stack((perp1, perp2, normal))) center = np.dot(mat, center) w = tuple(el.in_units("code_length") for el in width) bounds = tuple(((2 * (i % 2)) - 1) * w[i // 2] / 2 for i in range(len(w) * 2)) - return (bounds, center) @@ -2423,7 +2425,7 @@ def __init__( fields, center="center", width=None, - depth=(1, "1"), + depth=None, axes_unit=None, weight_field=None, max_level=None, @@ -2450,7 +2452,7 @@ def __init__( ) fields = list(iter_fields(fields))[:] oap_width = ds.arr( - (bounds[1] - bounds[0], bounds[3] - bounds[2], bounds[5] - bounds[4]) + (bounds[1] - bounds[0], bounds[3] - bounds[2]) ) OffAxisProj = OffAxisProjectionDummyDataSource( center_rot, @@ -2465,6 +2467,7 @@ def __init__( le=le, re=re, north_vector=north_vector, + depth=depth, method=method, data_source=data_source, moment=moment, diff --git a/yt/visualization/volume_rendering/off_axis_projection.py b/yt/visualization/volume_rendering/off_axis_projection.py index 217518003c2..ac9ba69962a 100644 --- a/yt/visualization/volume_rendering/off_axis_projection.py +++ b/yt/visualization/volume_rendering/off_axis_projection.py @@ -81,7 +81,10 @@ def off_axis_projection( north_vector : optional, array_like, default None A vector that, if specified, restricts the orientation such that the north vector dotted into the image plane points "up". Useful for rotations - depth: float or + depth: float, tuple[float, str], or unyt_array or size 1. + specify the depth of the projection region (size along the + line of sight). If no units are given (unyt_array or second + tuple element), code units are assumed. num_threads: integer, optional, default 1 Use this many OpenMP threads during projection. method : string @@ -148,8 +151,14 @@ def off_axis_projection( if not hasattr(width, "units"): width = data_source.ds.arr(width, "code_length") if depth is not None: + # handle units (intrinsic or as a tuple), + # then convert to code length + # float -> assumed to be in code units + if isinstance(depth, tuple): + depth = data_source.ds.arr(np.array([depth[0]]), depth[1]) if hasattr(depth, "units"): depth = depth.to("code_length").d + #depth = data_source.ds.arr(depth, "code_length") @@ -210,23 +219,22 @@ def off_axis_projection( buf = np.zeros((resolution[0], resolution[1]), dtype="float64") mask = np.ones_like(buf, dtype="uint8") - # width from fixed_resolution.py is just the size of the domain - x_min = center[0] - width[0] / 2 - x_max = center[0] + width[0] / 2 - y_min = center[1] - width[1] / 2 - y_max = center[1] + width[1] / 2 - z_min = center[2] - width[2] / 2 - z_max = center[2] + width[2] / 2 - bounds = [x_min, x_max, y_min, y_max, z_min, z_max] - periodic = data_source.ds.periodicity - #le = data_source.ds.domain_left_edge.to("code_length").d - #re = data_source.ds.domain_right_edge.to("code_length").d - #x_min, y_min, z_min = le - #x_max, y_max, z_max = re + ## width from fixed_resolution.py is just the size of the domain + #x_min = center[0] - width[0] / 2 + #x_max = center[0] + width[0] / 2 + #y_min = center[1] - width[1] / 2 + #y_max = center[1] + width[1] / 2 + #z_min = center[2] - width[2] / 2 + #z_max = center[2] + width[2] / 2 + periodic = data_source.ds.periodicity + le = data_source.ds.domain_left_edge.to("code_length").d + re = data_source.ds.domain_right_edge.to("code_length").d + x_min, y_min, z_min = le + x_max, y_max, z_max = re + bounds = [x_min, x_max, y_min, y_max, z_min, z_max] finfo = data_source.ds.field_info[item] ounits = finfo.output_units - if weight is None: for chunk in data_source.chunks([], "io"): off_axis_projection_SPH( From 44d6d4615706b7c857715dedb5c6fbbe1c883746 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:55:50 -0500 Subject: [PATCH 03/64] partway debugging off-axis SPH-proj. --- yt/utilities/lib/pixelization_routines.pyx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/yt/utilities/lib/pixelization_routines.pyx b/yt/utilities/lib/pixelization_routines.pyx index 52d964d9e21..f8d829e799f 100644 --- a/yt/utilities/lib/pixelization_routines.pyx +++ b/yt/utilities/lib/pixelization_routines.pyx @@ -1927,7 +1927,6 @@ def rotate_particle_coord(np.float64_t[:] px, cdef np.float64_t rot_bounds_y1 = rotated_center[1] + 0.5 * width[1] cdef np.float64_t rot_bounds_z0 = rotated_center[2] - 0.5 * depth cdef np.float64_t rot_bounds_z1 = rotated_center[2] + 0.5 * depth - for i in range(num_particles): coordinate_matrix[0] = px[i] coordinate_matrix[1] = py[i] @@ -1938,9 +1937,10 @@ def rotate_particle_coord(np.float64_t[:] px, # added consequence: the center is placed at the origin # (might as well keep it there in these temporary coordinates) for ax in range(3): + # assumed center is zero even if non-periodic + coordinate_matrix[ax] -= center[ax] if not periodic[ax]: continue period = bounds[2 * ax + 1] - bounds[2 * ax] - coordinate_matrix[ax] -= center[ax] # abs. difference between points in the volume is <= period if coordinate_matrix[ax] < -0.5 * period: coordinate_matrix[ax] += period @@ -1961,6 +1961,7 @@ def rotate_particle_coord(np.float64_t[:] px, @cython.boundscheck(False) @cython.wraparound(False) + def off_axis_projection_SPH(np.float64_t[:] px, np.float64_t[:] py, np.float64_t[:] pz, @@ -1997,7 +1998,9 @@ def off_axis_projection_SPH(np.float64_t[:] px, width, depth, normal_vector, north_vector) - + # check_period=0: assumed to be a small region compared to the box + # size. The rotation already ensures that a center close to a + # periodic edge works out fine. pixelize_sph_kernel_projection(projection_array, mask, px_rotated, From 30d60fac633b32193276bcf881504a999a2321f3 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:55:41 -0500 Subject: [PATCH 04/64] off-axis projections seem to work --- yt/utilities/lib/pixelization_routines.pyx | 13 ++++++++----- yt/visualization/plot_window.py | 22 ++++++++++++++++++++-- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/yt/utilities/lib/pixelization_routines.pyx b/yt/utilities/lib/pixelization_routines.pyx index f8d829e799f..a0fce64c042 100644 --- a/yt/utilities/lib/pixelization_routines.pyx +++ b/yt/utilities/lib/pixelization_routines.pyx @@ -1984,11 +1984,10 @@ def off_axis_projection_SPH(np.float64_t[:] px, if np.allclose(normal_vector, 0.): return if depth is None: - # set to volume diagonal -> won't exclude anything - depth = np.sqrt((bounds[1] - bounds[0])**2 - + (bounds[3] - bounds[2])**2 - + (bounds[5] - bounds[4])**2) - + # set to volume diagonal + margin -> won't exclude anything + depth = 2. * np.sqrt((bounds[1] - bounds[0])**2 + + (bounds[3] - bounds[2])**2 + + (bounds[5] - bounds[4])**2) px_rotated, py_rotated, pz_rotated, \ rot_bounds_x0, rot_bounds_x1, \ rot_bounds_y0, rot_bounds_y1, \ @@ -2001,6 +2000,10 @@ def off_axis_projection_SPH(np.float64_t[:] px, # check_period=0: assumed to be a small region compared to the box # size. The rotation already ensures that a center close to a # periodic edge works out fine. + # since the simple single-coordinate modulo math periodicity + # does not apply to the *rotated* coordinates, the periodicity + # approach implemented for this along-axis projection method + # would fail here pixelize_sph_kernel_projection(projection_array, mask, px_rotated, diff --git a/yt/visualization/plot_window.py b/yt/visualization/plot_window.py index b05a195fd94..7068474f434 100644 --- a/yt/visualization/plot_window.py +++ b/yt/visualization/plot_window.py @@ -13,6 +13,8 @@ from yt._typing import AlphaT from yt.data_objects.image_array import ImageArray from yt.frontends.ytdata.data_structures import YTSpatialPlotDataset +from yt.frontends.sph.data_structures import ParticleDataset +from yt.frontends.stream.data_structures import StreamParticlesDataset from yt.funcs import ( fix_axis, fix_unitary, @@ -2446,16 +2448,32 @@ def __init__( f"off-axis slices are not supported for {ds.geometry!r} geometry\n" f"currently supported geometries: {self._supported_geometries!r}" ) - + # center_rot normalizes the center to (0,0), + # units match bounds + # for SPH data, we want to input the original center + # the cython backend handles centering to this point and + # rotation (bounds, center_rot) = get_oblique_window_parameters( normal, center, width, ds, depth=depth ) + # will probably fail if you try to project an SPH and non-SPH + # field in a single call + # checks for SPH fields copied from the + # _ortho_pixelize method in cartesian_coordinates.py + field = data_source._determine_fields(fields)[0] + finfo = data_source.ds.field_info[field] + particle_datasets = (ParticleDataset, StreamParticlesDataset) + is_sph_field = finfo.is_sph_field + if isinstance(data_source.ds, particle_datasets) and is_sph_field: + center_use = center + else: + center_use = center_rot fields = list(iter_fields(fields))[:] oap_width = ds.arr( (bounds[1] - bounds[0], bounds[3] - bounds[2]) ) OffAxisProj = OffAxisProjectionDummyDataSource( - center_rot, + center_use, ds, normal, oap_width, From 2ff58cd9e3e7de23d11cd85ead4f3da95b946256 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Wed, 3 Jul 2024 15:59:50 -0500 Subject: [PATCH 05/64] first attempt to fix pixelize_sph_kernel_slice --- yt/utilities/lib/pixelization_routines.pyx | 36 ++++++++++++++++------ 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/yt/utilities/lib/pixelization_routines.pyx b/yt/utilities/lib/pixelization_routines.pyx index a0fce64c042..2293dad03df 100644 --- a/yt/utilities/lib/pixelization_routines.pyx +++ b/yt/utilities/lib/pixelization_routines.pyx @@ -1489,11 +1489,13 @@ def interpolate_sph_grid_gather(np.float64_t[:, :, :] buff, def pixelize_sph_kernel_slice( np.float64_t[:, :] buff, np.uint8_t[:, :] mask, - np.float64_t[:] posx, np.float64_t[:] posy, + np.float64_t[:] posx, np.float64_t[:] posy, + np.float64_t[:] posz, np.float64_t[:] hsml, np.float64_t[:] pmass, np.float64_t[:] pdens, np.float64_t[:] quantity_to_smooth, - bounds, kernel_name="cubic", + bounds, np.float64_t slicez, + kernel_name="cubic", int check_period=1, period=None): @@ -1501,10 +1503,10 @@ def pixelize_sph_kernel_slice( cdef np.intp_t xsize, ysize cdef np.float64_t x_min, x_max, y_min, y_max, prefactor_j cdef np.int64_t xi, yi, x0, x1, y0, y1, xxi, yyi - cdef np.float64_t q_ij, posx_diff, posy_diff, ih_j - cdef np.float64_t x, y, dx, dy, idx, idy, h_j2, h_j, px, py + cdef np.float64_t q_ij, posx_diff, posy_diff, posz_diff, ih_j + cdef np.float64_t x, y, dx, dy, idx, idy, h_j2, h_j, px, py, pz cdef int i, j, ii, jj - cdef np.float64_t period_x = 0, period_y = 0 + cdef np.float64_t period_x = 0, period_y = 0, period_z = 0 cdef int * xiter cdef int * yiter cdef np.float64_t * xiterv @@ -1513,6 +1515,7 @@ def pixelize_sph_kernel_slice( if period is not None: period_x = period[0] period_y = period[1] + period_z = period[2] xsize, ysize = buff.shape[0], buff.shape[1] @@ -1560,11 +1563,24 @@ def pixelize_sph_kernel_slice( elif posy[j] + hsml[j] > y_max: yiter[1] = -1 yiterv[1] = -period_y - - h_j2 = fmax(hsml[j]*hsml[j], dx*dy) - h_j = math.sqrt(h_j2) + # z of particle might be < hsml from the slice plane + # but across a periodic boundary + if posz[j] - hsml[j] > slicez: + pz = posz[j] - period_z + elif posz[j] + hsml[j] < slicez: + pz = posz[j] + period_z + else: + pz = posz[j] + + h_j2 = hsml[j] * hsml[j] #fmax(hsml[j]*hsml[j], dx*dy) + h_j = hsml[j] #math.sqrt(h_j2) ih_j = 1.0/h_j + posz_diff = pz - slicez + posz_diff = posz_diff * posz_diff + if posz_diff > h_j2: + continue + prefactor_j = pmass[j] / pdens[j] / hsml[j]**3 prefactor_j *= quantity_to_smooth[j] @@ -1606,7 +1622,9 @@ def pixelize_sph_kernel_slice( continue # see equation 4 of the SPLASH paper - q_ij = math.sqrt(posx_diff + posy_diff) * ih_j + q_ij = math.sqrt(posx_diff + + posy_diff + + posz_diff) * ih_j if q_ij >= 1: continue From 13cd93ad953224bd10219dbf742f205d1472e2de Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Wed, 3 Jul 2024 16:46:48 -0500 Subject: [PATCH 06/64] attempt 1 to fix axis-aligned SPH slices --- yt/geometry/coordinates/cartesian_coordinates.py | 12 +++++++----- yt/utilities/lib/pixelization_routines.pyx | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/yt/geometry/coordinates/cartesian_coordinates.py b/yt/geometry/coordinates/cartesian_coordinates.py index 7e1fbb11cbe..ff9e8b3652e 100644 --- a/yt/geometry/coordinates/cartesian_coordinates.py +++ b/yt/geometry/coordinates/cartesian_coordinates.py @@ -516,20 +516,21 @@ def _ortho_pixelize( mask_uint8 = np.zeros_like(buff, dtype="uint8") if normalize: buff_den = np.zeros(size, dtype="float64") - + for chunk in data_source.chunks([], "io"): pixelize_sph_kernel_slice( buff, mask_uint8, chunk[ptype, px_name].to("code_length"), chunk[ptype, py_name].to("code_length"), + chunk[ptype, pz_name].to("code_length"), chunk[ptype, "smoothing_length"].to("code_length"), chunk[ptype, "mass"].to("code_mass"), chunk[ptype, "density"].to("code_density"), chunk[field].in_units(ounits), - bnds, + bnds, data_source.coord, check_period=int(periodic), - period=period2, + period=period3, ) if normalize: pixelize_sph_kernel_slice( @@ -537,13 +538,14 @@ def _ortho_pixelize( mask_uint8, chunk[ptype, px_name].to("code_length"), chunk[ptype, py_name].to("code_length"), + chunk[ptype, pz_name].to("code_length"), chunk[ptype, "smoothing_length"].to("code_length"), chunk[ptype, "mass"].to("code_mass"), chunk[ptype, "density"].to("code_density"), np.ones(chunk[ptype, "density"].shape[0]), - bnds, + bnds, data_source.coord, check_period=int(periodic), - period=period2, + period=period3, ) if normalize: diff --git a/yt/utilities/lib/pixelization_routines.pyx b/yt/utilities/lib/pixelization_routines.pyx index 2293dad03df..3821c92f537 100644 --- a/yt/utilities/lib/pixelization_routines.pyx +++ b/yt/utilities/lib/pixelization_routines.pyx @@ -1498,7 +1498,8 @@ def pixelize_sph_kernel_slice( kernel_name="cubic", int check_period=1, period=None): - + # bounds are [x0, x1, y0, y1], slicez is the single coordinate + # of the slice along the normal direction. # similar method to pixelize_sph_kernel_projection cdef np.intp_t xsize, ysize cdef np.float64_t x_min, x_max, y_min, y_max, prefactor_j From 9bca940b848e20772d3e910615eea55e344296e4 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Wed, 3 Jul 2024 17:37:39 -0500 Subject: [PATCH 07/64] draft 1 backend SPH cutting plane --- yt/utilities/lib/pixelization_routines.pyx | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/yt/utilities/lib/pixelization_routines.pyx b/yt/utilities/lib/pixelization_routines.pyx index 3821c92f537..94994ef8706 100644 --- a/yt/utilities/lib/pixelization_routines.pyx +++ b/yt/utilities/lib/pixelization_routines.pyx @@ -2038,6 +2038,45 @@ def off_axis_projection_SPH(np.float64_t[:] px, weight_field=weight_field, check_period=0) +# like slice pixelization, but for off-axis planes +def pixelize_sph_kernel_cutting( + np.float64_t[:, :] buff, + np.uint8_t[:, :] mask, + np.float64_t[:] posx, np.float64_t[:] posy, + np.float64_t[:] posz, + np.float64_t[:] hsml, np.float64_t[:] pmass, + np.float64_t[:] pdens, + np.float64_t[:] quantity_to_smooth, + np.float64_t[3] center, np.float64_t[2] widthxy, + np.float64_t[3] normal_vector, np.float64_t[3] north_vector, + boxbounds, periodic, + kernel_name="cubic", + int check_period=1): + + if check_period == 0: + periodic = np.zeros(3, dtype=bool) + + posx_rot, posy_rot, posz_rot, \ + rot_bounds_x0, rot_bounds_x1, \ + rot_bounds_y0, rot_bounds_y1, \ + rot_bounds_z0, rot_bounds_z1 = rotate_particle_coord(posx, posy, posz, + center, boxbounds, + periodic, + widthxy, 0., + normal_vector, + north_vector) + bounds_rot = np.array([rot_bounds_x0, rot_bounds_x1, + rot_bounds_y0, rot_bounds_y1]) + slicez_rot = rot_bounds_z0 + pixelize_sph_kernel_slice(buff, mask, + posx_rot, posy_rot, posz_rot, + hsml, pmass, pdens, quantity_to_smooth, + bounds_rot, slicez_rot, + kernel_name=kernel_name, + check_period=0, + period=None) + + @cython.boundscheck(False) @cython.wraparound(False) From 22985f74ea786efb883c0b16fb5f7d0118cad2b3 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Thu, 4 Jul 2024 14:55:38 -0500 Subject: [PATCH 08/64] passing dataset kernel names to SPH slice/projection backend; work on slice backend call resolve merge conflict with before/after file marking branch: preserve sph_proj_backend changes in cartesian_coordinates.py --- .../coordinates/cartesian_coordinates.py | 125 +++++++++++++++--- yt/utilities/lib/pixelization_routines.pyx | 6 +- yt/visualization/plot_window.py | 4 +- .../volume_rendering/off_axis_projection.py | 11 +- 4 files changed, 121 insertions(+), 25 deletions(-) diff --git a/yt/geometry/coordinates/cartesian_coordinates.py b/yt/geometry/coordinates/cartesian_coordinates.py index ff9e8b3652e..b52b6e36ecd 100644 --- a/yt/geometry/coordinates/cartesian_coordinates.py +++ b/yt/geometry/coordinates/cartesian_coordinates.py @@ -12,6 +12,7 @@ pixelize_element_mesh, pixelize_element_mesh_line, pixelize_off_axis_cartesian, + pixelize_sph_kernel_cutting, pixelize_sph_kernel_projection, pixelize_sph_kernel_slice, ) @@ -374,6 +375,11 @@ def _ortho_pixelize( ounits = data_source.ds.field_info[field].output_units bnds = data_source.ds.arr(bounds, "code_length").tolist() + kernel_name = None + if hasattr(data_source.ds, "kernel_name"): + kernel_name = data_source.ds.kernel_name + if kernel_name is None: + kernel_name = "cubic" if isinstance(data_source, YTParticleProj): weight = data_source.weight_field @@ -427,6 +433,7 @@ def _ortho_pixelize( bnds3, check_period=int(periodic), period=period3, + kernel_name=kernel_name ) # We use code length here, but to get the path length right # we need to multiply by the conversion factor between @@ -460,6 +467,7 @@ def _ortho_pixelize( check_period=int(periodic), period=period3, weight_field=chunk[weight].in_units(wounits), + kernel_name=kernel_name ) mylog.info( "Making a fixed resolution buffer of (%s) %d by %d", @@ -482,6 +490,7 @@ def _ortho_pixelize( bnds3, check_period=int(periodic), period=period3, + kernel_name=kernel_name ) normalization_2d_utility(buff, weight_buff) if moment == 2: @@ -503,6 +512,7 @@ def _ortho_pixelize( check_period=int(periodic), period=period3, weight_field=chunk[weight].in_units(wounits), + kernel_name=kernel_name ) normalization_2d_utility(buff2, weight_buff) buff = compute_stddev_image(buff2, buff) @@ -531,6 +541,7 @@ def _ortho_pixelize( bnds, data_source.coord, check_period=int(periodic), period=period3, + kernel_name=kernel_name ) if normalize: pixelize_sph_kernel_slice( @@ -546,6 +557,7 @@ def _ortho_pixelize( bnds, data_source.coord, check_period=int(periodic), period=period3, + kernel_name=kernel_name ) if normalize: @@ -643,28 +655,99 @@ def _ortho_pixelize( def _oblique_pixelize(self, data_source, field, bounds, size, antialias): from yt.frontends.ytdata.data_structures import YTSpatialPlotDataset + from yt.data_objects.selection_objects.slices import YTSlice + from yt.frontends.sph.data_structures import ParticleDataset + from yt.frontends.stream.data_structures import StreamParticlesDataset - indices = np.argsort(data_source["pdx"])[::-1].astype("int64", copy=False) - buff = np.full((size[1], size[0]), np.nan, dtype="float64") - ftype = "index" - if isinstance(data_source.ds, YTSpatialPlotDataset): - ftype = "gas" - mask = pixelize_off_axis_cartesian( - buff, - data_source[ftype, "x"], - data_source[ftype, "y"], - data_source[ftype, "z"], - data_source["px"], - data_source["py"], - data_source["pdx"], - data_source["pdy"], - data_source["pdz"], - data_source.center, - data_source._inv_mat, - indices, - data_source[field], - bounds, - ) + # Determine what sort of data we're dealing with + # -> what backend to use + # copied from the _ortho_pixelize method + field = data_source._determine_fields(field)[0] + _finfo = data_source.ds.field_info[field] + is_sph_field = _finfo.is_sph_field + particle_datasets = (ParticleDataset, StreamParticlesDataset) + #finfo = self.ds._get_field_info(field) + + # SPH data + # only for slices: a function in off_axis_projection.py + # handles projections + if isinstance(data_source.ds, particle_datasets) and is_sph_field \ + and isinstance(data_source, YTSlice): + normalize = getattr(self.ds, "use_sph_normalization", True) + le, re = data_source.data_source.get_bbox() + boxbounds = np.array([le[0], re[0], le[1], re[1], le[2], re[2]]) + periodic = data_source.ds.coordinates.period.astype(bool).v + ptype = field[0] + if ptype == "gas": + ptype = data_source.ds._sph_ptypes[0] + axorder = data_source.ds.coordinates.axis_order + ounits = data_source.ds.field_info[field].output_units + + buff = np.zeros(size, dtype="float64") + mask_uint8 = np.zeros_like(buff, dtype="uint8") + if normalize: + buff_den = np.zeros(size, dtype="float64") + + for chunk in data_source.chunks([], "io"): + pixelize_sph_kernel_cutting( + buff, mask_uint8, + chunk[ptype, axorder[0]].to("code_length"), + chunk[ptype, axorder[1]].to("code_length"), + chunk[ptype, axorder[2]].to("code_length"), + chunk[ptype, "smoothing_length"].to("code_length"), + chunk[ptype, "mass"].to("code_mass"), + chunk[ptype, "density"].to("code_density"), + chunk[field].in_units(ounits), + center, widthxy, + normal_vector, north_vector, + boxbounds, periodic, + kernel_name="cubic", + check_period=1) + if normalize: + pixelize_sph_kernel_slice( + buff_den, + mask_uint8, + chunk[ptype, px_name].to("code_length"), + chunk[ptype, py_name].to("code_length"), + chunk[ptype, pz_name].to("code_length"), + chunk[ptype, "smoothing_length"].to("code_length"), + chunk[ptype, "mass"].to("code_mass"), + chunk[ptype, "density"].to("code_density"), + np.ones(chunk[ptype, "density"].shape[0]), + bnds, data_source.coord, + check_period=int(periodic), + period=period3, + ) + + if normalize: + normalization_2d_utility(buff, buff_den) + + mask = mask_uint8.astype("bool", copy=False) + + # whatever other data this code could handle before the + # SPH option was added + else: + indices = np.argsort(data_source["pdx"])[::-1].astype(np.int_) + buff = np.full((size[1], size[0]), np.nan, dtype="float64") + ftype = "index" + if isinstance(data_source.ds, YTSpatialPlotDataset): + ftype = "gas" + mask = pixelize_off_axis_cartesian( + buff, + data_source[ftype, "x"], + data_source[ftype, "y"], + data_source[ftype, "z"], + data_source["px"], + data_source["py"], + data_source["pdx"], + data_source["pdy"], + data_source["pdz"], + data_source.center, + data_source._inv_mat, + indices, + data_source[field], + bounds, + ) return buff, mask def convert_from_cartesian(self, coord): diff --git a/yt/utilities/lib/pixelization_routines.pyx b/yt/utilities/lib/pixelization_routines.pyx index 94994ef8706..3ca19527205 100644 --- a/yt/utilities/lib/pixelization_routines.pyx +++ b/yt/utilities/lib/pixelization_routines.pyx @@ -1997,7 +1997,8 @@ def off_axis_projection_SPH(np.float64_t[:] px, normal_vector, north_vector, weight_field=None, - depth=None): + depth=None, + kernel_name="cubic"): # periodic: periodicity of the data set: # Do nothing in event of a 0 normal vector if np.allclose(normal_vector, 0.): @@ -2036,7 +2037,8 @@ def off_axis_projection_SPH(np.float64_t[:] px, rot_bounds_y0, rot_bounds_y1, rot_bounds_z0, rot_bounds_z1], weight_field=weight_field, - check_period=0) + check_period=0, + kernel_name=kernel_name) # like slice pixelization, but for off-axis planes def pixelize_sph_kernel_cutting( diff --git a/yt/visualization/plot_window.py b/yt/visualization/plot_window.py index 7068474f434..9c6143cf65b 100644 --- a/yt/visualization/plot_window.py +++ b/yt/visualization/plot_window.py @@ -2222,7 +2222,9 @@ def __init__( f"off-axis slices are not supported for {ds.geometry!r} geometry\n" f"currently supported geometries: {self._supported_geometries!r}" ) - + # bounds are in cutting plane coordinates, centered on 0: + # [xmin, xmax, ymin, ymax]. Can derive width/height back + # from these. (bounds, center_rot) = get_oblique_window_parameters(normal, center, width, ds) if field_parameters is None: field_parameters = {} diff --git a/yt/visualization/volume_rendering/off_axis_projection.py b/yt/visualization/volume_rendering/off_axis_projection.py index ac9ba69962a..6f741a1004e 100644 --- a/yt/visualization/volume_rendering/off_axis_projection.py +++ b/yt/visualization/volume_rendering/off_axis_projection.py @@ -235,6 +235,12 @@ def off_axis_projection( bounds = [x_min, x_max, y_min, y_max, z_min, z_max] finfo = data_source.ds.field_info[item] ounits = finfo.output_units + kernel_name = None + if hasattr(data_source.ds, "kernel_name"): + kernel_name = data_source.ds.kernel_name + if kernel_name is None: + kernel_name = "cubic" + if weight is None: for chunk in data_source.chunks([], "io"): off_axis_projection_SPH( @@ -253,7 +259,8 @@ def off_axis_projection( mask, normal_vector, north, - depth=depth + depth=depth, + kernel_name=kernel_name, ) # Assure that the path length unit is in the default length units @@ -295,6 +302,7 @@ def off_axis_projection( north, weight_field=chunk[weight].in_units(wounits), depth=depth, + kernel_name=kernel_name, ) for chunk in data_source.chunks([], "io"): @@ -315,6 +323,7 @@ def off_axis_projection( normal_vector, north, depth=depth, + kernel_name=kernel_name, ) normalization_2d_utility(buf, weight_buff) From 6a874fe7f21290d828c85ec841772f90d284963d Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Thu, 4 Jul 2024 15:30:30 -0500 Subject: [PATCH 09/64] draft 1 fixed slices, new off-axis slices SPH --- .../coordinates/cartesian_coordinates.py | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/yt/geometry/coordinates/cartesian_coordinates.py b/yt/geometry/coordinates/cartesian_coordinates.py index b52b6e36ecd..e9d2199ee75 100644 --- a/yt/geometry/coordinates/cartesian_coordinates.py +++ b/yt/geometry/coordinates/cartesian_coordinates.py @@ -682,6 +682,17 @@ def _oblique_pixelize(self, data_source, field, bounds, size, antialias): ptype = data_source.ds._sph_ptypes[0] axorder = data_source.ds.coordinates.axis_order ounits = data_source.ds.field_info[field].output_units + widthxy = np.array(((bounds[1] - bounds[0]).to("code_length"), + (bounds[3] - bounds[2]).to("code_length"))) + kernel_name = None + if hasattr(data_source.ds, "kernel_name"): + kernel_name = data_source.ds.kernel_name + if kernel_name is None: + kernel_name = "cubic" + # data_source should be a YTCuttingPlane object + normal_vector = data_source.normal + north_vector = data_source._y_vec + center = data_source.center.to("code_length") buff = np.zeros(size, dtype="float64") mask_uint8 = np.zeros_like(buff, dtype="uint8") @@ -690,7 +701,8 @@ def _oblique_pixelize(self, data_source, field, bounds, size, antialias): for chunk in data_source.chunks([], "io"): pixelize_sph_kernel_cutting( - buff, mask_uint8, + buff, + mask_uint8, chunk[ptype, axorder[0]].to("code_length"), chunk[ptype, axorder[1]].to("code_length"), chunk[ptype, axorder[2]].to("code_length"), @@ -701,22 +713,25 @@ def _oblique_pixelize(self, data_source, field, bounds, size, antialias): center, widthxy, normal_vector, north_vector, boxbounds, periodic, - kernel_name="cubic", - check_period=1) + kernel_name=kernel_name, + check_period=1, + ) if normalize: - pixelize_sph_kernel_slice( + pixelize_sph_kernel_cutting( buff_den, mask_uint8, - chunk[ptype, px_name].to("code_length"), - chunk[ptype, py_name].to("code_length"), - chunk[ptype, pz_name].to("code_length"), + chunk[ptype, axorder[0]].to("code_length"), + chunk[ptype, axorder[1]].to("code_length"), + chunk[ptype, axorder[2]].to("code_length"), chunk[ptype, "smoothing_length"].to("code_length"), chunk[ptype, "mass"].to("code_mass"), chunk[ptype, "density"].to("code_density"), np.ones(chunk[ptype, "density"].shape[0]), - bnds, data_source.coord, - check_period=int(periodic), - period=period3, + center, widthxy, + normal_vector, north_vector, + boxbounds, periodic, + kernel_name=kernel_name, + check_period=1 ) if normalize: From 8b3ca657d8326e4dc4576f6bb068f7a42f29fc3e Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Fri, 5 Jul 2024 16:41:11 -0500 Subject: [PATCH 10/64] further bugfix attempts --- yt/utilities/lib/pixelization_routines.pyx | 4 ++-- yt/visualization/plot_window.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/yt/utilities/lib/pixelization_routines.pyx b/yt/utilities/lib/pixelization_routines.pyx index 3ca19527205..7afc6fc560f 100644 --- a/yt/utilities/lib/pixelization_routines.pyx +++ b/yt/utilities/lib/pixelization_routines.pyx @@ -2049,8 +2049,8 @@ def pixelize_sph_kernel_cutting( np.float64_t[:] hsml, np.float64_t[:] pmass, np.float64_t[:] pdens, np.float64_t[:] quantity_to_smooth, - np.float64_t[3] center, np.float64_t[2] widthxy, - np.float64_t[3] normal_vector, np.float64_t[3] north_vector, + center, widthxy, + normal_vector, north_vector, boxbounds, periodic, kernel_name="cubic", int check_period=1): diff --git a/yt/visualization/plot_window.py b/yt/visualization/plot_window.py index 9c6143cf65b..47981545817 100644 --- a/yt/visualization/plot_window.py +++ b/yt/visualization/plot_window.py @@ -1595,6 +1595,7 @@ class SlicePlot(NormalPlot): def __new__( # type: ignore cls, ds, normal, fields, *args, **kwargs ) -> Union["AxisAlignedSlicePlot", "OffAxisSlicePlot"]: + print('SlicePlot call cls: ', cls) if cls is SlicePlot: normal = cls.sanitize_normal_vector(ds, normal) if isinstance(normal, str): @@ -1602,6 +1603,8 @@ def __new__( # type: ignore else: cls = OffAxisSlicePlot self = object.__new__(cls) + print('SlicePlot result cls: ', cls) + print('Sliceplot return self: ', self) return self # type: ignore [return-value] @@ -1822,9 +1825,12 @@ def __init__( normal = self.sanitize_normal_vector(ds, normal) # this will handle time series data and controllers axis = fix_axis(normal, ds) + #print('center at SlicePlot init: ', center) + #print('current domain left edge: ', ds.domain_left_edge) (bounds, center, display_center) = get_window_parameters( axis, center, width, ds ) + # print('center after get_window_parameters: ', center) if field_parameters is None: field_parameters = {} From 31aae231cf13374db018892f01dc9297ae1f4f79 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Fri, 5 Jul 2024 18:26:01 -0500 Subject: [PATCH 11/64] debugged SPH slices to run; outcome clearly wrong off-axis --- .../coordinates/cartesian_coordinates.py | 26 +++++++++++-------- yt/visualization/plot_window.py | 2 +- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/yt/geometry/coordinates/cartesian_coordinates.py b/yt/geometry/coordinates/cartesian_coordinates.py index e9d2199ee75..e709cfd55bd 100644 --- a/yt/geometry/coordinates/cartesian_coordinates.py +++ b/yt/geometry/coordinates/cartesian_coordinates.py @@ -655,7 +655,7 @@ def _ortho_pixelize( def _oblique_pixelize(self, data_source, field, bounds, size, antialias): from yt.frontends.ytdata.data_structures import YTSpatialPlotDataset - from yt.data_objects.selection_objects.slices import YTSlice + from yt.data_objects.selection_objects.slices import YTCuttingPlane from yt.frontends.sph.data_structures import ParticleDataset from yt.frontends.stream.data_structures import StreamParticlesDataset @@ -672,9 +672,10 @@ def _oblique_pixelize(self, data_source, field, bounds, size, antialias): # only for slices: a function in off_axis_projection.py # handles projections if isinstance(data_source.ds, particle_datasets) and is_sph_field \ - and isinstance(data_source, YTSlice): + and isinstance(data_source, YTCuttingPlane): normalize = getattr(self.ds, "use_sph_normalization", True) - le, re = data_source.data_source.get_bbox() + le = data_source.ds.domain_left_edge.to("code_length") + re = data_source.ds.domain_right_edge.to("code_length") boxbounds = np.array([le[0], re[0], le[1], re[1], le[2], re[2]]) periodic = data_source.ds.coordinates.period.astype(bool).v ptype = field[0] @@ -682,16 +683,19 @@ def _oblique_pixelize(self, data_source, field, bounds, size, antialias): ptype = data_source.ds._sph_ptypes[0] axorder = data_source.ds.coordinates.axis_order ounits = data_source.ds.field_info[field].output_units - widthxy = np.array(((bounds[1] - bounds[0]).to("code_length"), - (bounds[3] - bounds[2]).to("code_length"))) + # input bounds are in code length units already + widthxy = np.array((bounds[1] - bounds[0], + bounds[3] - bounds[2])) kernel_name = None if hasattr(data_source.ds, "kernel_name"): kernel_name = data_source.ds.kernel_name if kernel_name is None: kernel_name = "cubic" # data_source should be a YTCuttingPlane object - normal_vector = data_source.normal - north_vector = data_source._y_vec + # dimensionless unyt normal/north + # -> numpy array cython can deal with + normal_vector = data_source.normal.v + north_vector = data_source._y_vec.v center = data_source.center.to("code_length") buff = np.zeros(size, dtype="float64") @@ -703,10 +707,10 @@ def _oblique_pixelize(self, data_source, field, bounds, size, antialias): pixelize_sph_kernel_cutting( buff, mask_uint8, - chunk[ptype, axorder[0]].to("code_length"), - chunk[ptype, axorder[1]].to("code_length"), - chunk[ptype, axorder[2]].to("code_length"), - chunk[ptype, "smoothing_length"].to("code_length"), + chunk[ptype, axorder[0]].to("code_length").v, + chunk[ptype, axorder[1]].to("code_length").v, + chunk[ptype, axorder[2]].to("code_length").v, + chunk[ptype, "smoothing_length"].to("code_length").v, chunk[ptype, "mass"].to("code_mass"), chunk[ptype, "density"].to("code_density"), chunk[field].in_units(ounits), diff --git a/yt/visualization/plot_window.py b/yt/visualization/plot_window.py index 47981545817..b2a49538cfc 100644 --- a/yt/visualization/plot_window.py +++ b/yt/visualization/plot_window.py @@ -2230,7 +2230,7 @@ def __init__( ) # bounds are in cutting plane coordinates, centered on 0: # [xmin, xmax, ymin, ymax]. Can derive width/height back - # from these. + # from these. unit is code_length (bounds, center_rot) = get_oblique_window_parameters(normal, center, width, ds) if field_parameters is None: field_parameters = {} From 690d64f847308bea0c50d14afd73db00e14e37ca Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Fri, 5 Jul 2024 19:45:03 -0500 Subject: [PATCH 12/64] partial debug along-axis SPH slices --- yt/geometry/coordinates/cartesian_coordinates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yt/geometry/coordinates/cartesian_coordinates.py b/yt/geometry/coordinates/cartesian_coordinates.py index e709cfd55bd..8c060e7f42d 100644 --- a/yt/geometry/coordinates/cartesian_coordinates.py +++ b/yt/geometry/coordinates/cartesian_coordinates.py @@ -412,7 +412,7 @@ def _ortho_pixelize( proj_reg.set_field_parameter("axis", data_source.axis) # need some z bounds for SPH projection # -> use source bounds - zax = list({0, 1, 2} - set([xa, ya]))[0] + zax = list({0, 1, 2} - {xa, ya})[0] bnds3 = bnds + [le[zax], re[zax]] buff = np.zeros(size, dtype="float64") From 695f1969a70d6baaa90ad944ff63789e19dd1c4e Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Sat, 6 Jul 2024 08:59:26 -0500 Subject: [PATCH 13/64] fix complaint from ruff --- yt/utilities/lib/pixelization_routines.pyx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yt/utilities/lib/pixelization_routines.pyx b/yt/utilities/lib/pixelization_routines.pyx index 7afc6fc560f..62c8c3c9602 100644 --- a/yt/utilities/lib/pixelization_routines.pyx +++ b/yt/utilities/lib/pixelization_routines.pyx @@ -2061,12 +2061,12 @@ def pixelize_sph_kernel_cutting( posx_rot, posy_rot, posz_rot, \ rot_bounds_x0, rot_bounds_x1, \ rot_bounds_y0, rot_bounds_y1, \ - rot_bounds_z0, rot_bounds_z1 = rotate_particle_coord(posx, posy, posz, - center, boxbounds, - periodic, - widthxy, 0., - normal_vector, - north_vector) + rot_bounds_z0, _ = rotate_particle_coord(posx, posy, posz, + center, boxbounds, + periodic, + widthxy, 0., + normal_vector, + north_vector) bounds_rot = np.array([rot_bounds_x0, rot_bounds_x1, rot_bounds_y0, rot_bounds_y1]) slicez_rot = rot_bounds_z0 From 80cb90da112f625684ca4bf2baf11f83a90f64a2 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Wed, 10 Jul 2024 18:48:58 -0500 Subject: [PATCH 14/64] debugging: axis-aligned slices pass tests --- .../coordinates/cartesian_coordinates.py | 47 ++++++++++++------- yt/utilities/lib/pixelization_routines.pyx | 30 ++++++++---- yt/visualization/plot_window.py | 3 -- 3 files changed, 51 insertions(+), 29 deletions(-) diff --git a/yt/geometry/coordinates/cartesian_coordinates.py b/yt/geometry/coordinates/cartesian_coordinates.py index 8c060e7f42d..61835bfe20e 100644 --- a/yt/geometry/coordinates/cartesian_coordinates.py +++ b/yt/geometry/coordinates/cartesian_coordinates.py @@ -169,7 +169,7 @@ def pixelize( bounds, size, antialias=True, - periodic=True, + periodic=None, *, return_mask=False, ): @@ -178,6 +178,18 @@ def pixelize( two-dimensional image plots. Relies on several sampling routines written in cython """ + if periodic is None: + if np.all(data_source.ds.periodicity): + periodic = True + elif not np.any(data_source.ds.periodicity): + periodic = False + else: + msg = ('Pixelization routines in CartesianCoordinate' + 'Handler can currently only deal with datasets ' + 'that are periodic in all or none of their ' + f'dimensions. This dataset {data_source.ds}' + f'had periodicity {data_source.ds.periodicity}') + raise NotImplementedError(msg) index = data_source.ds.index if hasattr(index, "meshes") and not isinstance( index.meshes[0], SemiStructuredMesh @@ -528,17 +540,19 @@ def _ortho_pixelize( buff_den = np.zeros(size, dtype="float64") for chunk in data_source.chunks([], "io"): + hsmlname = "smoothing_length" pixelize_sph_kernel_slice( buff, mask_uint8, - chunk[ptype, px_name].to("code_length"), - chunk[ptype, py_name].to("code_length"), - chunk[ptype, pz_name].to("code_length"), - chunk[ptype, "smoothing_length"].to("code_length"), - chunk[ptype, "mass"].to("code_mass"), - chunk[ptype, "density"].to("code_density"), - chunk[field].in_units(ounits), - bnds, data_source.coord, + chunk[ptype, px_name].to("code_length").v, + chunk[ptype, py_name].to("code_length").v, + chunk[ptype, pz_name].to("code_length").v, + chunk[ptype, hsmlname].to("code_length").v, + chunk[ptype, "mass"].to("code_mass").v, + chunk[ptype, "density"].to("code_density").v, + chunk[field].in_units(ounits).v, + bnds, + data_source.coord.to("code_length").v, check_period=int(periodic), period=period3, kernel_name=kernel_name @@ -547,14 +561,15 @@ def _ortho_pixelize( pixelize_sph_kernel_slice( buff_den, mask_uint8, - chunk[ptype, px_name].to("code_length"), - chunk[ptype, py_name].to("code_length"), - chunk[ptype, pz_name].to("code_length"), - chunk[ptype, "smoothing_length"].to("code_length"), - chunk[ptype, "mass"].to("code_mass"), - chunk[ptype, "density"].to("code_density"), + chunk[ptype, px_name].to("code_length").v, + chunk[ptype, py_name].to("code_length").v, + chunk[ptype, pz_name].to("code_length").v, + chunk[ptype, hsmlname].to("code_length").v, + chunk[ptype, "mass"].to("code_mass").v, + chunk[ptype, "density"].to("code_density").v, np.ones(chunk[ptype, "density"].shape[0]), - bnds, data_source.coord, + bnds, + data_source.coord.to("code_length").v, check_period=int(periodic), period=period3, kernel_name=kernel_name diff --git a/yt/utilities/lib/pixelization_routines.pyx b/yt/utilities/lib/pixelization_routines.pyx index 62c8c3c9602..2de77bf6ee8 100644 --- a/yt/utilities/lib/pixelization_routines.pyx +++ b/yt/utilities/lib/pixelization_routines.pyx @@ -1494,10 +1494,18 @@ def pixelize_sph_kernel_slice( np.float64_t[:] hsml, np.float64_t[:] pmass, np.float64_t[:] pdens, np.float64_t[:] quantity_to_smooth, - bounds, np.float64_t slicez, + bounds, + np.float64_t slicez, kernel_name="cubic", int check_period=1, period=None): + #print("bounds, slicez, kernel_name, check_period, period") + #print(bounds) + #print(slicez) + #print(kernel_name) + #print(check_period) + #print(period) + #print() # bounds are [x0, x1, y0, y1], slicez is the single coordinate # of the slice along the normal direction. # similar method to pixelize_sph_kernel_projection @@ -1531,7 +1539,7 @@ def pixelize_sph_kernel_slice( idy = 1.0/dy kernel = get_kernel_func(kernel_name) - + #print('particle index, ii, jj, px, py, pz') with nogil, parallel(): # NOTE see note in pixelize_sph_kernel_projection local_buff = malloc(sizeof(np.float64_t) * xsize * ysize) @@ -1548,18 +1556,19 @@ def pixelize_sph_kernel_slice( if j % 100000 == 0: with gil: PyErr_CheckSignals() - + #with gil: + # print(j) xiter[1] = yiter[1] = 999 - + pz = posz[j] if check_period == 1: if posx[j] - hsml[j] < x_min: - xiter[1] = +1 + xiter[1] = 1 xiterv[1] = period_x elif posx[j] + hsml[j] > x_max: xiter[1] = -1 xiterv[1] = -period_x if posy[j] - hsml[j] < y_min: - yiter[1] = +1 + yiter[1] = 1 yiterv[1] = period_y elif posy[j] + hsml[j] > y_max: yiter[1] = -1 @@ -1570,8 +1579,6 @@ def pixelize_sph_kernel_slice( pz = posz[j] - period_z elif posz[j] + hsml[j] < slicez: pz = posz[j] + period_z - else: - pz = posz[j] h_j2 = hsml[j] * hsml[j] #fmax(hsml[j]*hsml[j], dx*dy) h_j = hsml[j] #math.sqrt(h_j2) @@ -1603,7 +1610,8 @@ def pixelize_sph_kernel_slice( y1 = ( (py + hsml[j] - y_min) * idy) y0 = iclip(y0-1, 0, ysize) y1 = iclip(y1+1, 0, ysize) - + #with gil: + # print(ii, jj, px, py, pz) # Now we know which pixels to deposit onto for this particle, # so loop over them and add this particle's contribution for xi in range(x0, x1): @@ -1917,7 +1925,7 @@ def rotate_particle_coord(np.float64_t[:] px, # the normal vector be the z-axis (i.e., the viewer's perspective), and then # another rotation to make the north-vector be the y-axis (i.e., north). # Fortunately, total_rotation_matrix = rotation_matrix_1 x rotation_matrix_2 - cdef int num_particles = np.size(px) + cdef np.int64_t num_particles = np.size(px) cdef np.float64_t[:] z_axis = np.array([0., 0., 1.], dtype="float64") cdef np.float64_t[:] y_axis = np.array([0., 1., 0.], dtype="float64") cdef np.float64_t[:, :] normal_rotation_matrix @@ -1936,6 +1944,8 @@ def rotate_particle_coord(np.float64_t[:] px, cdef np.float64_t[:] coordinate_matrix = np.empty(3, dtype="float64") cdef np.float64_t[:] rotated_coordinates cdef np.float64_t[:] rotated_center + cdef np.int64_t i + cdef int ax #rotated_center = rotation_matmul( # rotation_matrix, np.array([center[0], center[1], center[2]])) rotated_center = np.zeros((3,), dtype=center.dtype) diff --git a/yt/visualization/plot_window.py b/yt/visualization/plot_window.py index b2a49538cfc..af603e0d202 100644 --- a/yt/visualization/plot_window.py +++ b/yt/visualization/plot_window.py @@ -1595,7 +1595,6 @@ class SlicePlot(NormalPlot): def __new__( # type: ignore cls, ds, normal, fields, *args, **kwargs ) -> Union["AxisAlignedSlicePlot", "OffAxisSlicePlot"]: - print('SlicePlot call cls: ', cls) if cls is SlicePlot: normal = cls.sanitize_normal_vector(ds, normal) if isinstance(normal, str): @@ -1603,8 +1602,6 @@ def __new__( # type: ignore else: cls = OffAxisSlicePlot self = object.__new__(cls) - print('SlicePlot result cls: ', cls) - print('Sliceplot return self: ', self) return self # type: ignore [return-value] From 1907b2fc2b28e6c58168e7b0f1f297353462caf7 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Wed, 10 Jul 2024 19:16:13 -0500 Subject: [PATCH 15/64] fix a ruff complaint --- yt/visualization/plot_window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yt/visualization/plot_window.py b/yt/visualization/plot_window.py index af603e0d202..6edec738e6d 100644 --- a/yt/visualization/plot_window.py +++ b/yt/visualization/plot_window.py @@ -12,9 +12,9 @@ from yt._maintenance.deprecation import issue_deprecation_warning from yt._typing import AlphaT from yt.data_objects.image_array import ImageArray -from yt.frontends.ytdata.data_structures import YTSpatialPlotDataset from yt.frontends.sph.data_structures import ParticleDataset from yt.frontends.stream.data_structures import StreamParticlesDataset +from yt.frontends.ytdata.data_structures import YTSpatialPlotDataset from yt.funcs import ( fix_axis, fix_unitary, From a80228aab074da91f268f30c99fbfac1e10f388e Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Thu, 11 Jul 2024 18:52:46 -0500 Subject: [PATCH 16/64] on/off-axis SPH slices actually work now --- yt/geometry/coordinates/cartesian_coordinates.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/yt/geometry/coordinates/cartesian_coordinates.py b/yt/geometry/coordinates/cartesian_coordinates.py index 61835bfe20e..3df343fe5a1 100644 --- a/yt/geometry/coordinates/cartesian_coordinates.py +++ b/yt/geometry/coordinates/cartesian_coordinates.py @@ -577,7 +577,7 @@ def _ortho_pixelize( if normalize: normalization_2d_utility(buff, buff_den) - + mask = mask_uint8.astype("bool", copy=False) if smoothing_style == "gather": @@ -692,7 +692,7 @@ def _oblique_pixelize(self, data_source, field, bounds, size, antialias): le = data_source.ds.domain_left_edge.to("code_length") re = data_source.ds.domain_right_edge.to("code_length") boxbounds = np.array([le[0], re[0], le[1], re[1], le[2], re[2]]) - periodic = data_source.ds.coordinates.period.astype(bool).v + periodic = data_source.ds.periodicity ptype = field[0] if ptype == "gas": ptype = data_source.ds._sph_ptypes[0] @@ -757,6 +757,9 @@ def _oblique_pixelize(self, data_source, field, bounds, size, antialias): normalization_2d_utility(buff, buff_den) mask = mask_uint8.astype("bool", copy=False) + # swap axes for image plotting + mask = mask.swapaxes(0, 1) + buff = buff.swapaxes(0, 1) # whatever other data this code could handle before the # SPH option was added From 8e5aca2e97b969cefc2e8abd4067b10d46999f67 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Thu, 11 Jul 2024 22:50:36 -0500 Subject: [PATCH 17/64] hopefully fixed induced grid projection bug; SPH projections ignore periodic arg in pixelize, use dataset periodicity instead --- .../coordinates/cartesian_coordinates.py | 49 +++++++++---------- yt/utilities/lib/pixelization_routines.pyx | 28 +++++++---- 2 files changed, 43 insertions(+), 34 deletions(-) diff --git a/yt/geometry/coordinates/cartesian_coordinates.py b/yt/geometry/coordinates/cartesian_coordinates.py index 3df343fe5a1..038550adda7 100644 --- a/yt/geometry/coordinates/cartesian_coordinates.py +++ b/yt/geometry/coordinates/cartesian_coordinates.py @@ -169,7 +169,7 @@ def pixelize( bounds, size, antialias=True, - periodic=None, + periodic=True, *, return_mask=False, ): @@ -178,18 +178,6 @@ def pixelize( two-dimensional image plots. Relies on several sampling routines written in cython """ - if periodic is None: - if np.all(data_source.ds.periodicity): - periodic = True - elif not np.any(data_source.ds.periodicity): - periodic = False - else: - msg = ('Pixelization routines in CartesianCoordinate' - 'Handler can currently only deal with datasets ' - 'that are periodic in all or none of their ' - f'dimensions. This dataset {data_source.ds}' - f'had periodicity {data_source.ds.periodicity}') - raise NotImplementedError(msg) index = data_source.ds.index if hasattr(index, "meshes") and not isinstance( index.meshes[0], SemiStructuredMesh @@ -376,6 +364,7 @@ def _ortho_pixelize( return_mask=True, ) elif isinstance(data_source.ds, particle_datasets) and is_sph_field: + # SPH handling ptype = field[0] if ptype == "gas": ptype = data_source.ds._sph_ptypes[0] @@ -384,7 +373,20 @@ def _ortho_pixelize( # need z coordinates for depth, # but name isn't saved in the handler -> use the 'other one' pz_name = list((set(self.axis_order) - {px_name, py_name}))[0] - + + # ignore default True periodic argument + # (not actually supplied by a call from + # FixedResolutionBuffer), and use the dataset periodicity + # instead + xa = self.x_axis[dim] + ya = self.y_axis[dim] + #axorder = data_source.ds.coordinates.axis_order + za = list({0, 1, 2} - {xa, ya})[0] + ds_periodic = data_source.ds.periodicity + _periodic = np.array(ds_periodic) + _periodic[0] = ds_periodic[xa] + _periodic[1] = ds_periodic[ya] + _periodic[2] = ds_periodic[za] ounits = data_source.ds.field_info[field].output_units bnds = data_source.ds.arr(bounds, "code_length").tolist() kernel_name = None @@ -393,12 +395,10 @@ def _ortho_pixelize( if kernel_name is None: kernel_name = "cubic" - if isinstance(data_source, YTParticleProj): + if isinstance(data_source, YTParticleProj): # projection weight = data_source.weight_field moment = data_source.moment le, re = data_source.data_source.get_bbox() - xa = self.x_axis[dim] - ya = self.y_axis[dim] # If we're not periodic, we need to clip to the boundary edges # or we get errors about extending off the edge of the region. # (depth/z range is handled by region setting) @@ -424,8 +424,7 @@ def _ortho_pixelize( proj_reg.set_field_parameter("axis", data_source.axis) # need some z bounds for SPH projection # -> use source bounds - zax = list({0, 1, 2} - {xa, ya})[0] - bnds3 = bnds + [le[zax], re[zax]] + bnds3 = bnds + [le[za], re[za]] buff = np.zeros(size, dtype="float64") mask_uint8 = np.zeros_like(buff, dtype="uint8") @@ -443,7 +442,7 @@ def _ortho_pixelize( chunk[ptype, "density"].to("code_density"), chunk[field].in_units(ounits), bnds3, - check_period=int(periodic), + _check_period=_periodic.astype("int"), period=period3, kernel_name=kernel_name ) @@ -476,7 +475,7 @@ def _ortho_pixelize( chunk[ptype, "density"].to("code_density"), chunk[field].in_units(ounits), bnds3, - check_period=int(periodic), + _check_period=_periodic.astype("int"), period=period3, weight_field=chunk[weight].in_units(wounits), kernel_name=kernel_name @@ -500,7 +499,7 @@ def _ortho_pixelize( chunk[ptype, "density"].to("code_density"), chunk[weight].in_units(wounits), bnds3, - check_period=int(periodic), + _check_period=_periodic.astype("int"), period=period3, kernel_name=kernel_name ) @@ -521,7 +520,7 @@ def _ortho_pixelize( chunk[ptype, "density"].to("code_density"), chunk[field].in_units(ounits) ** 2, bnds3, - check_period=int(periodic), + _check_period=_periodic.astype("int"), period=period3, weight_field=chunk[weight].in_units(wounits), kernel_name=kernel_name @@ -553,7 +552,7 @@ def _ortho_pixelize( chunk[field].in_units(ounits).v, bnds, data_source.coord.to("code_length").v, - check_period=int(periodic), + _check_period=_periodic.astype("int"), period=period3, kernel_name=kernel_name ) @@ -570,7 +569,7 @@ def _ortho_pixelize( np.ones(chunk[ptype, "density"].shape[0]), bnds, data_source.coord.to("code_length").v, - check_period=int(periodic), + _check_period=_periodic.astype("int"), period=period3, kernel_name=kernel_name ) diff --git a/yt/utilities/lib/pixelization_routines.pyx b/yt/utilities/lib/pixelization_routines.pyx index 2de77bf6ee8..3a27345004e 100644 --- a/yt/utilities/lib/pixelization_routines.pyx +++ b/yt/utilities/lib/pixelization_routines.pyx @@ -1131,7 +1131,7 @@ def pixelize_sph_kernel_projection( bounds, kernel_name="cubic", weight_field=None, - int check_period=1, + _check_period = (1, 1, 1), period=None): cdef np.intp_t xsize, ysize @@ -1148,6 +1148,7 @@ def pixelize_sph_kernel_projection( cdef np.float64_t * xiterv cdef np.float64_t * yiterv cdef np.float64_t * ziterv + cdef np.int8_t[3] check_period if weight_field is not None: _weight_field = weight_field @@ -1156,7 +1157,8 @@ def pixelize_sph_kernel_projection( period_x = period[0] period_y = period[1] period_z = period[2] - + for i in range(3): + check_period[i] = np.int8(_check_period[i]) # we find the x and y range over which we have pixels and we find how many # pixels we have in each dimension xsize, ysize = buff.shape[0], buff.shape[1] @@ -1211,19 +1213,21 @@ def pixelize_sph_kernel_projection( xiter[1] = yiter[1] = ziter[1] = 999 - if check_period == 1: + if check_period[0] == 1: if posx[j] - hsml[j] < x_min: xiter[1] = +1 xiterv[1] = period_x elif posx[j] + hsml[j] > x_max: xiter[1] = -1 xiterv[1] = -period_x + if check_period[1] == 1: if posy[j] - hsml[j] < y_min: yiter[1] = +1 yiterv[1] = period_y elif posy[j] + hsml[j] > y_max: yiter[1] = -1 yiterv[1] = -period_y + if check_period[2] == 1: if posz[j] - hsml[j] < z_min: ziter[1] = +1 ziterv[1] = period_z @@ -1497,7 +1501,7 @@ def pixelize_sph_kernel_slice( bounds, np.float64_t slicez, kernel_name="cubic", - int check_period=1, + _check_period = (1, 1, 1), period=None): #print("bounds, slicez, kernel_name, check_period, period") #print(bounds) @@ -1520,14 +1524,16 @@ def pixelize_sph_kernel_slice( cdef int * yiter cdef np.float64_t * xiterv cdef np.float64_t * yiterv + cdef np.int8_t[3] check_period if period is not None: period_x = period[0] period_y = period[1] period_z = period[2] - + for i in range(3): + check_period[i] = np.int8(_check_period[i]) + xsize, ysize = buff.shape[0], buff.shape[1] - x_min = bounds[0] x_max = bounds[1] y_min = bounds[2] @@ -1560,19 +1566,21 @@ def pixelize_sph_kernel_slice( # print(j) xiter[1] = yiter[1] = 999 pz = posz[j] - if check_period == 1: + if check_period[0] == 1: if posx[j] - hsml[j] < x_min: xiter[1] = 1 xiterv[1] = period_x elif posx[j] + hsml[j] > x_max: xiter[1] = -1 xiterv[1] = -period_x + if check_period[1] == 1: if posy[j] - hsml[j] < y_min: yiter[1] = 1 yiterv[1] = period_y elif posy[j] + hsml[j] > y_max: yiter[1] = -1 yiterv[1] = -period_y + if check_period[2] == 1: # z of particle might be < hsml from the slice plane # but across a periodic boundary if posz[j] - hsml[j] > slicez: @@ -1933,6 +1941,7 @@ def rotate_particle_coord(np.float64_t[:] px, cdef np.float64_t[:, :] north_rotation_matrix cdef np.float64_t[:, :] rotation_matrix + normal_rotation_matrix = get_rotation_matrix(normal_vector, z_axis) transformed_north_vector = np.matmul(normal_rotation_matrix, north_vector) north_rotation_matrix = get_rotation_matrix(transformed_north_vector, y_axis) @@ -2034,6 +2043,7 @@ def off_axis_projection_SPH(np.float64_t[:] px, # does not apply to the *rotated* coordinates, the periodicity # approach implemented for this along-axis projection method # would fail here + check_period = np.array([0, 0, 0], dtype="int") pixelize_sph_kernel_projection(projection_array, mask, px_rotated, @@ -2047,7 +2057,7 @@ def off_axis_projection_SPH(np.float64_t[:] px, rot_bounds_y0, rot_bounds_y1, rot_bounds_z0, rot_bounds_z1], weight_field=weight_field, - check_period=0, + _check_period=check_period, kernel_name=kernel_name) # like slice pixelization, but for off-axis planes @@ -2085,7 +2095,7 @@ def pixelize_sph_kernel_cutting( hsml, pmass, pdens, quantity_to_smooth, bounds_rot, slicez_rot, kernel_name=kernel_name, - check_period=0, + _check_period=np.zeros(3, dtype="int"), period=None) From 9cc98ba378a2b0251cd79b9e9633af10c5e310f3 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Thu, 11 Jul 2024 23:45:40 -0500 Subject: [PATCH 18/64] partial debug issues from pull request tests --- yt/visualization/plot_window.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/yt/visualization/plot_window.py b/yt/visualization/plot_window.py index 6edec738e6d..636674aba2d 100644 --- a/yt/visualization/plot_window.py +++ b/yt/visualization/plot_window.py @@ -2465,10 +2465,16 @@ def __init__( # field in a single call # checks for SPH fields copied from the # _ortho_pixelize method in cartesian_coordinates.py + + ## data_source might be None here + ## (OffAxisProjectionDummyDataSource gets used later) + if data_source is None: + data_source = ds.all_data() field = data_source._determine_fields(fields)[0] finfo = data_source.ds.field_info[field] - particle_datasets = (ParticleDataset, StreamParticlesDataset) is_sph_field = finfo.is_sph_field + particle_datasets = (ParticleDataset, StreamParticlesDataset) + if isinstance(data_source.ds, particle_datasets) and is_sph_field: center_use = center else: From 429536dd8f30a4e3befa8f28f4fed6d07cfbb35f Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Fri, 12 Jul 2024 17:30:35 -0500 Subject: [PATCH 19/64] attempted fix bounds issues in off_axis_projection calls (non-SPH ds) --- yt/visualization/fixed_resolution.py | 3 ++ yt/visualization/plot_window.py | 34 +++++++++++++------ .../volume_rendering/off_axis_projection.py | 8 +++-- .../volume_rendering/old_camera.py | 3 +- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/yt/visualization/fixed_resolution.py b/yt/visualization/fixed_resolution.py index 782e734ad3e..e19c9c58f00 100644 --- a/yt/visualization/fixed_resolution.py +++ b/yt/visualization/fixed_resolution.py @@ -630,10 +630,13 @@ def _generate_image_and_mask(self, item) -> None: self.buff_size[1], ) dd = self.data_source + # only need the first two for SPH, + # but need the third one for other data formats. width = self.ds.arr( ( self.bounds[1] - self.bounds[0], self.bounds[3] - self.bounds[2], + self.bounds[5] - self.bounds[4], ) ) buff = off_axis_projection( diff --git a/yt/visualization/plot_window.py b/yt/visualization/plot_window.py index 636674aba2d..2503d3b5c19 100644 --- a/yt/visualization/plot_window.py +++ b/yt/visualization/plot_window.py @@ -82,14 +82,15 @@ def get_window_parameters(axis, center, width, ds): return (bounds, center, display_center) -def get_oblique_window_parameters(normal, center, width, ds, depth=None): +def get_oblique_window_parameters(normal, center, width, ds, + depth=None, get3bounds=False): center, display_center = ds.coordinates.sanitize_center(center, axis=None) width = ds.coordinates.sanitize_width(normal, width, depth) if len(width) == 2: # Transforming to the cutting plane coordinate system # the original dimensionless center messes up off-axis - # SPH projections though + # SPH projections though -> don't use this center there center = ((center - ds.domain_left_edge) / ds.domain_width - 0.5)\ * ds.domain_width (normal, perp1, perp2) = ortho_find(normal) @@ -98,6 +99,14 @@ def get_oblique_window_parameters(normal, center, width, ds, depth=None): w = tuple(el.in_units("code_length") for el in width) bounds = tuple(((2 * (i % 2)) - 1) * w[i // 2] / 2 for i in range(len(w) * 2)) + if get3bounds and depth is None: + # off-axis projection, depth not specified + # -> set 'large enough' depth using half the box diagonal + margin + d2 = ds.domain_width[0].in_units("code_length")**2 + d2 += ds.domain_width[1].in_units("code_length")**2 + d2 += ds.domain_width[2].in_units("code_length")**2 + diag = np.sqrt(d2) + bounds = bounds + (-0.51 * diag, 0.51 * diag) return (bounds, center) @@ -2450,16 +2459,20 @@ def __init__( ): if ds.geometry not in self._supported_geometries: raise NotImplementedError( - f"off-axis slices are not supported for {ds.geometry!r} geometry\n" - f"currently supported geometries: {self._supported_geometries!r}" + "off-axis slices are not supported" + f" for {ds.geometry!r} geometry\n" + "currently supported geometries:" + f" {self._supported_geometries!r}" ) # center_rot normalizes the center to (0,0), # units match bounds # for SPH data, we want to input the original center # the cython backend handles centering to this point and - # rotation + # rotation. + # get3bounds gets a depth 0.5 * diagonal + margin in the + # depth=None case. (bounds, center_rot) = get_oblique_window_parameters( - normal, center, width, ds, depth=depth + normal, center, width, ds, depth=depth, get3bounds=True, ) # will probably fail if you try to project an SPH and non-SPH # field in a single call @@ -2480,14 +2493,15 @@ def __init__( else: center_use = center_rot fields = list(iter_fields(fields))[:] - oap_width = ds.arr( - (bounds[1] - bounds[0], bounds[3] - bounds[2]) - ) + #oap_width = ds.arr( + # (bounds[1] - bounds[0], + # bounds[3] - bounds[2]) + #) OffAxisProj = OffAxisProjectionDummyDataSource( center_use, ds, normal, - oap_width, + width, fields, interpolated, weight=weight_field, diff --git a/yt/visualization/volume_rendering/off_axis_projection.py b/yt/visualization/volume_rendering/off_axis_projection.py index 6f741a1004e..5ca35b297cb 100644 --- a/yt/visualization/volume_rendering/off_axis_projection.py +++ b/yt/visualization/volume_rendering/off_axis_projection.py @@ -233,6 +233,8 @@ def off_axis_projection( x_min, y_min, z_min = le x_max, y_max, z_max = re bounds = [x_min, x_max, y_min, y_max, z_min, z_max] + # only need (rotated) x/y widths + _width = (width.to("code_length").d)[:2] finfo = data_source.ds.field_info[item] ounits = finfo.output_units kernel_name = None @@ -252,7 +254,7 @@ def off_axis_projection( chunk[ptype, "smoothing_length"].to("code_length").d, bounds, center.to("code_length").d, - width.to("code_length").d, + _width, periodic, chunk[item].in_units(ounits), buf, @@ -293,7 +295,7 @@ def off_axis_projection( chunk[ptype, "smoothing_length"].to("code_length").d, bounds, center.to("code_length").d, - width.to("code_length").d, + _width, periodic, chunk[item].in_units(ounits), buf, @@ -315,7 +317,7 @@ def off_axis_projection( chunk[ptype, "smoothing_length"].to("code_length").d, bounds, center.to("code_length").d, - width.to("code_length").d, + _width, periodic, chunk[weight].to(wounits), weight_buff, diff --git a/yt/visualization/volume_rendering/old_camera.py b/yt/visualization/volume_rendering/old_camera.py index cca5ff178a0..0ca8489814f 100644 --- a/yt/visualization/volume_rendering/old_camera.py +++ b/yt/visualization/volume_rendering/old_camera.py @@ -2438,7 +2438,8 @@ def _render(self, double_check, num_threads, image, sampler, msg): data_object_registry["stereospherical_camera"] = StereoSphericalCamera - +# replaced in volume_rendering API by the function of the same name in +# yt/visualization/volume_rendering/off_axis_projection def off_axis_projection( ds, center, From a088d5fa3be979eba33f44979f139cfda587bf4c Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Fri, 12 Jul 2024 17:45:25 -0500 Subject: [PATCH 20/64] attempted fix: ParticleImageBuffer counts on rotate_particle_coord function, but I changed the function --- yt/utilities/lib/pixelization_routines.pyx | 58 +++++++++++++++++++++- yt/visualization/fixed_resolution.py | 4 +- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/yt/utilities/lib/pixelization_routines.pyx b/yt/utilities/lib/pixelization_routines.pyx index 3a27345004e..fab75a19d36 100644 --- a/yt/utilities/lib/pixelization_routines.pyx +++ b/yt/utilities/lib/pixelization_routines.pyx @@ -1916,7 +1916,63 @@ def pixelize_element_mesh_line(np.ndarray[np.float64_t, ndim=2] coords, free(field_vals) return arc_length, plot_values +# intended for use in ParticleImageBuffer +@cython.boundscheck(False) +@cython.wraparound(False) +def rotate_particle_coord_pib(np.float64_t[:] px, + np.float64_t[:] py, + np.float64_t[:] pz, + center, + width, + normal_vector, + north_vector): + # We want to do two rotations, one to first rotate our coordinates to have + # the normal vector be the z-axis (i.e., the viewer's perspective), and then + # another rotation to make the north-vector be the y-axis (i.e., north). + # Fortunately, total_rotation_matrix = rotation_matrix_1 x rotation_matrix_2 + cdef int num_particles = np.size(px) + cdef np.float64_t[:] z_axis = np.array([0., 0., 1.], dtype="float64") + cdef np.float64_t[:] y_axis = np.array([0., 1., 0.], dtype="float64") + cdef np.float64_t[:, :] normal_rotation_matrix + cdef np.float64_t[:] transformed_north_vector + cdef np.float64_t[:, :] north_rotation_matrix + cdef np.float64_t[:, :] rotation_matrix + normal_rotation_matrix = get_rotation_matrix(normal_vector, z_axis) + transformed_north_vector = np.matmul(normal_rotation_matrix, north_vector) + north_rotation_matrix = get_rotation_matrix(transformed_north_vector, y_axis) + rotation_matrix = np.matmul(north_rotation_matrix, normal_rotation_matrix) + + cdef np.float64_t[:] px_rotated = np.empty(num_particles, dtype="float64") + cdef np.float64_t[:] py_rotated = np.empty(num_particles, dtype="float64") + cdef np.float64_t[:] coordinate_matrix = np.empty(3, dtype="float64") + cdef np.float64_t[:] rotated_coordinates + cdef np.float64_t[:] rotated_center + rotated_center = rotation_matmul( + rotation_matrix, np.array([center[0], center[1], center[2]])) + + # set up the rotated bounds + cdef np.float64_t rot_bounds_x0 = rotated_center[0] - width[0] / 2 + cdef np.float64_t rot_bounds_x1 = rotated_center[0] + width[0] / 2 + cdef np.float64_t rot_bounds_y0 = rotated_center[1] - width[1] / 2 + cdef np.float64_t rot_bounds_y1 = rotated_center[1] + width[1] / 2 + + for i in range(num_particles): + coordinate_matrix[0] = px[i] + coordinate_matrix[1] = py[i] + coordinate_matrix[2] = pz[i] + rotated_coordinates = rotation_matmul( + rotation_matrix, coordinate_matrix) + px_rotated[i] = rotated_coordinates[0] + py_rotated[i] = rotated_coordinates[1] + + return px_rotated, py_rotated, rot_bounds_x0, rot_bounds_x1, rot_bounds_y0, rot_bounds_y1 + +# version intended for SPH off-axis slices/projections +# includes dealing with periodic boundaries, but also +# shifts particles so center -> origin. +# therefore, don't want to use this in the ParticleImageBuffer, +# which expects differently centered coordinates. @cython.boundscheck(False) @cython.wraparound(False) def rotate_particle_coord(np.float64_t[:] px, @@ -1941,7 +1997,6 @@ def rotate_particle_coord(np.float64_t[:] px, cdef np.float64_t[:, :] north_rotation_matrix cdef np.float64_t[:, :] rotation_matrix - normal_rotation_matrix = get_rotation_matrix(normal_vector, z_axis) transformed_north_vector = np.matmul(normal_rotation_matrix, north_vector) north_rotation_matrix = get_rotation_matrix(transformed_north_vector, y_axis) @@ -1999,7 +2054,6 @@ def rotate_particle_coord(np.float64_t[:] px, @cython.boundscheck(False) @cython.wraparound(False) - def off_axis_projection_SPH(np.float64_t[:] px, np.float64_t[:] py, np.float64_t[:] pz, diff --git a/yt/visualization/fixed_resolution.py b/yt/visualization/fixed_resolution.py index e19c9c58f00..f597393a5dc 100644 --- a/yt/visualization/fixed_resolution.py +++ b/yt/visualization/fixed_resolution.py @@ -17,7 +17,7 @@ ) from yt.utilities.lib.pixelization_routines import ( pixelize_cylinder, - rotate_particle_coord, + rotate_particle_coord_pib, ) from yt.utilities.math_utils import compute_stddev_image from yt.utilities.on_demand_imports import _h5py as h5py @@ -749,7 +749,7 @@ def _generate_image_and_mask(self, item) -> None: if hasattr(w, "to_value"): w = w.to_value("code_length") wd.append(w) - x_data, y_data, *bounds = rotate_particle_coord( + x_data, y_data, *bounds = rotate_particle_coord_pib( dd[ftype, "particle_position_x"].to_value("code_length"), dd[ftype, "particle_position_y"].to_value("code_length"), dd[ftype, "particle_position_z"].to_value("code_length"), From effd35109e62d17e90003f6aa18af74ecf5464fe Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Fri, 12 Jul 2024 17:53:54 -0500 Subject: [PATCH 21/64] add new pz argument to test_off_axis_SPH pixelize_sph_kernel_projection call --- yt/visualization/volume_rendering/tests/test_off_axis_SPH.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yt/visualization/volume_rendering/tests/test_off_axis_SPH.py b/yt/visualization/volume_rendering/tests/test_off_axis_SPH.py index dc5f4ae13a3..57f6f85d522 100644 --- a/yt/visualization/volume_rendering/tests/test_off_axis_SPH.py +++ b/yt/visualization/volume_rendering/tests/test_off_axis_SPH.py @@ -26,6 +26,7 @@ def test_no_rotation(): width = right_edge - left_edge px = ad["all", "particle_position_x"] py = ad["all", "particle_position_y"] + pz = ad["all", "particle_position_y"] hsml = ad["all", "smoothing_length"] quantity_to_smooth = ad["gas", "density"] density = ad["io", "density"] @@ -38,7 +39,7 @@ def test_no_rotation(): ds, center, normal_vector, width, resolution, ("gas", "density") ) pixelize_sph_kernel_projection( - buf2, mask, px, py, hsml, mass, density, quantity_to_smooth, bounds + buf2, mask, px, py, pz, hsml, mass, density, quantity_to_smooth, bounds ) assert_almost_equal(buf1.ndarray_view(), buf2) From 33b2c102947d11058965cd7694cd0d461b1300f5 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Fri, 12 Jul 2024 18:27:55 -0500 Subject: [PATCH 22/64] attempted fix issue with non-coordinate center arg off_axis_projection --- yt/visualization/plot_window.py | 5 ++++- yt/visualization/volume_rendering/off_axis_projection.py | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/yt/visualization/plot_window.py b/yt/visualization/plot_window.py index 2503d3b5c19..2fdc46edc7b 100644 --- a/yt/visualization/plot_window.py +++ b/yt/visualization/plot_window.py @@ -22,6 +22,7 @@ iter_fields, mylog, obj_length, + parse_center_array, validate_moment, ) from yt.geometry.api import Geometry @@ -2489,7 +2490,9 @@ def __init__( particle_datasets = (ParticleDataset, StreamParticlesDataset) if isinstance(data_source.ds, particle_datasets) and is_sph_field: - center_use = center + center_use = center = parse_center_array(center, + ds=data_source.ds, + axis=None) else: center_use = center_rot fields = list(iter_fields(fields))[:] diff --git a/yt/visualization/volume_rendering/off_axis_projection.py b/yt/visualization/volume_rendering/off_axis_projection.py index 5ca35b297cb..44f03ade0cb 100644 --- a/yt/visualization/volume_rendering/off_axis_projection.py +++ b/yt/visualization/volume_rendering/off_axis_projection.py @@ -242,7 +242,6 @@ def off_axis_projection( kernel_name = data_source.ds.kernel_name if kernel_name is None: kernel_name = "cubic" - if weight is None: for chunk in data_source.chunks([], "io"): off_axis_projection_SPH( From 0d13bebcc06ced43134c0b8b7094c03734511624 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Fri, 12 Jul 2024 18:46:56 -0500 Subject: [PATCH 23/64] attempted fix ruff complaint import sorting --- yt/geometry/coordinates/cartesian_coordinates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yt/geometry/coordinates/cartesian_coordinates.py b/yt/geometry/coordinates/cartesian_coordinates.py index 038550adda7..603a052088b 100644 --- a/yt/geometry/coordinates/cartesian_coordinates.py +++ b/yt/geometry/coordinates/cartesian_coordinates.py @@ -668,10 +668,10 @@ def _ortho_pixelize( return buff, mask def _oblique_pixelize(self, data_source, field, bounds, size, antialias): - from yt.frontends.ytdata.data_structures import YTSpatialPlotDataset from yt.data_objects.selection_objects.slices import YTCuttingPlane from yt.frontends.sph.data_structures import ParticleDataset from yt.frontends.stream.data_structures import StreamParticlesDataset + from yt.frontends.ytdata.data_structures import YTSpatialPlotDataset # Determine what sort of data we're dealing with # -> what backend to use From 15fcea4e062ccb9fe0eaf7d8177f0b7d8e7582eb Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Fri, 12 Jul 2024 18:53:44 -0500 Subject: [PATCH 24/64] removed unneeded parentheses --- yt/geometry/coordinates/cartesian_coordinates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yt/geometry/coordinates/cartesian_coordinates.py b/yt/geometry/coordinates/cartesian_coordinates.py index 603a052088b..96f43e0c4d7 100644 --- a/yt/geometry/coordinates/cartesian_coordinates.py +++ b/yt/geometry/coordinates/cartesian_coordinates.py @@ -372,7 +372,7 @@ def _ortho_pixelize( py_name = self.axis_name[self.y_axis[dim]] # need z coordinates for depth, # but name isn't saved in the handler -> use the 'other one' - pz_name = list((set(self.axis_order) - {px_name, py_name}))[0] + pz_name = list(set(self.axis_order) - {px_name, py_name})[0] # ignore default True periodic argument # (not actually supplied by a call from From 5bb4bd7ac2294d2aebb4dd3eebf5c3d71ee69e96 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Tue, 16 Jul 2024 19:35:57 -0500 Subject: [PATCH 25/64] SPH scatter gridding fixes, tests passed --- .../construction_data_containers.py | 6 ++- yt/utilities/lib/pixelization_routines.pyx | 46 +++++++++++++++---- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/yt/data_objects/construction_data_containers.py b/yt/data_objects/construction_data_containers.py index 03c10f92c1c..b6dfd4dd8bd 100644 --- a/yt/data_objects/construction_data_containers.py +++ b/yt/data_objects/construction_data_containers.py @@ -649,6 +649,8 @@ class YTCoveringGrid(YTSelectionContainer3D): level : int The resolution level data to which data will be gridded. Level 0 is the root grid dx for that dataset. + (The grid resolution will be simulation size / 2**level along + each grid axis.) left_edge : array_like The left edge of the region to be extracted. Specify units by supplying a YTArray, otherwise code length units are assumed. @@ -1001,8 +1003,8 @@ def _fill_sph_particles(self, fields): period = self.ds.coordinates.period.copy() if hasattr(period, "in_units"): period = period.in_units("code_length").d - # TODO maybe there is a better way of handling this - is_periodic = int(any(self.ds.periodicity)) + # check periodicity per dimension + is_periodic = self.ds.periodicity if smoothing_style == "scatter": for field in fields: diff --git a/yt/utilities/lib/pixelization_routines.pyx b/yt/utilities/lib/pixelization_routines.pyx index fab75a19d36..5752944f813 100644 --- a/yt/utilities/lib/pixelization_routines.pyx +++ b/yt/utilities/lib/pixelization_routines.pyx @@ -1669,7 +1669,7 @@ def pixelize_sph_kernel_arbitrary_grid(np.float64_t[:, :, :] buff, np.float64_t[:] pdens, np.float64_t[:] quantity_to_smooth, bounds, pbar=None, kernel_name="cubic", - int check_period=1, period=None): + check_period=True, period=None): cdef np.intp_t xsize, ysize, zsize cdef np.float64_t x_min, x_max, y_min, y_max, z_min, z_max, prefactor_j @@ -1685,10 +1685,21 @@ def pixelize_sph_kernel_arbitrary_grid(np.float64_t[:, :, :] buff, cdef np.float64_t xiterv[2] cdef np.float64_t yiterv[2] cdef np.float64_t ziterv[2] + cdef int[3] periodic + xiter[0] = yiter[0] = ziter[0] = 0 xiterv[0] = yiterv[0] = ziterv[0] = 0.0 + if hasattr(check_period, "__len__"): + periodic[0] = int(check_period[0]) + periodic[1] = int(check_period[1]) + periodic[2] = int(check_period[2]) + else: + _cp = int(check_period) + periodic[0] = _cp + periodic[1] = _cp + periodic[2] = _cp if period is not None: period_x = period[0] period_y = period[1] @@ -1710,7 +1721,17 @@ def pixelize_sph_kernel_arbitrary_grid(np.float64_t[:, :, :] buff, idz = 1.0/dz kernel = get_kernel_func(kernel_name) - + + # nogil seems dangerous here, but there are no actual parallel + # sections (e.g., prange instead of range) used here. + # However, for future writers: + # !! the final buff array mutation has no protections against + # !! race conditions (e.g., OpenMP's atomic read/write), and + # !! cython doesn't seem to provide such options. + # (other routines in this file use private variable buffer arrays + # and add everything together at the end, but grid arrays can get + # big fast, and having such a large array in each thread could + # cause memory use issues.) with nogil: # TODO make this parallel without using too much memory for j in range(0, posx.shape[0]): @@ -1719,23 +1740,26 @@ def pixelize_sph_kernel_arbitrary_grid(np.float64_t[:, :, :] buff, if(pbar is not None): pbar.update(50000) PyErr_CheckSignals() + # end with gil xiter[1] = yiter[1] = ziter[1] = 999 xiterv[1] = yiterv[1] = ziterv[1] = 0.0 - if check_period == 1: + if periodic[0] == 1: if posx[j] - hsml[j] < x_min: xiter[1] = +1 xiterv[1] = period_x elif posx[j] + hsml[j] > x_max: xiter[1] = -1 xiterv[1] = -period_x + if periodic[1] == 1: if posy[j] - hsml[j] < y_min: yiter[1] = +1 yiterv[1] = period_y elif posy[j] + hsml[j] > y_max: yiter[1] = -1 yiterv[1] = -period_y + if periodic[2] == 1: if posz[j] - hsml[j] < z_min: ziter[1] = +1 ziterv[1] = period_z @@ -1743,8 +1767,8 @@ def pixelize_sph_kernel_arbitrary_grid(np.float64_t[:, :, :] buff, ziter[1] = -1 ziterv[1] = -period_z - h_j3 = fmax(hsml[j]*hsml[j]*hsml[j], dx*dy*dz) - h_j = math.cbrt(h_j3) + #h_j3 = fmax(hsml[j]*hsml[j]*hsml[j], dx*dy*dz) + h_j = hsml[j] #math.cbrt(h_j3) h_j2 = h_j*h_j ih_j = 1/h_j @@ -1805,11 +1829,17 @@ def pixelize_sph_kernel_arbitrary_grid(np.float64_t[:, :, :] buff, continue # see equation 4 of the SPLASH paper - q_ij = math.sqrt(posx_diff + posy_diff + posz_diff) * ih_j + q_ij = math.sqrt(posx_diff + + posy_diff + + posz_diff) * ih_j if q_ij >= 1: continue - - buff[xi, yi, zi] += prefactor_j * kernel(q_ij) + # shared variable buff should not + # be mutatated in a nogil section + # where different threads may change + # the same array element + buff[xi, yi, zi] += prefactor_j \ + * kernel(q_ij) def pixelize_element_mesh_line(np.ndarray[np.float64_t, ndim=2] coords, From 222500ed66c77c972754462324d328c46e264cdc Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Tue, 16 Jul 2024 20:02:46 -0500 Subject: [PATCH 26/64] minor documentation updates --- doc/source/analyzing/generating_processed_data.rst | 12 ++++++++---- doc/source/visualizing/plots.rst | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/doc/source/analyzing/generating_processed_data.rst b/doc/source/analyzing/generating_processed_data.rst index 84abf87eb72..5f7866b4cab 100644 --- a/doc/source/analyzing/generating_processed_data.rst +++ b/doc/source/analyzing/generating_processed_data.rst @@ -54,7 +54,8 @@ the transformation of a variable mesh of points consisting of positions and sizes into a fixed-size array that appears like an image. This process is that of pixelization, which yt handles transparently internally. You can access this functionality by constructing a -:class:`~yt.visualization.fixed_resolution.FixedResolutionBuffer` and supplying +:class:`~yt.visualization.fixed_resolution.FixedResolutionBuffer` +and supplying to it your :class:`~yt.data_objects.data_containers.YTSelectionContainer2D` object, as well as some information about how you want the final image to look. You can specify both the bounds of the image (in the appropriate x-y plane) and @@ -62,8 +63,8 @@ the resolution of the output image. You can then have yt pixelize any field you like. .. note:: In previous versions of yt, there was a special class of - FixedResolutionBuffer for off-axis slices. This is no longer - necessary. + FixedResolutionBuffer for off-axis slices. This is still used + for off-axis SPH data projections: OffAxisFixedResolutionBuffer. To create :class:`~yt.data_objects.data_containers.YTSelectionContainer2D` objects, you can access them as described in :ref:`data-objects`, specifically the section @@ -99,7 +100,10 @@ this, see :ref:`saving-grid-data-containers`. In the FITS case, there is an option for setting the ``units`` of the coordinate system in the file. If you want to overwrite a file with the same name, set ``clobber=True``. -The :class:`~yt.visualization.fixed_resolution.FixedResolutionBuffer` can even be exported +The :class:`~yt.visualization.fixed_resolution.FixedResolutionBuffer` +(and its +:class:`~yt.visualization.fixed_resolution.OffAxisProjectionFixedResolutionBuffer` +subclass) can even be exported as a 2D dataset itself, which may be operated on in the same way as any other dataset in yt: .. code-block:: python diff --git a/doc/source/visualizing/plots.rst b/doc/source/visualizing/plots.rst index ec7b6109a50..1cc9d3572b1 100644 --- a/doc/source/visualizing/plots.rst +++ b/doc/source/visualizing/plots.rst @@ -293,8 +293,8 @@ argument. Optionally, a ``north_vector`` can be specified to fix the orientation of the image plane. .. note:: Not every data types have support for off-axis slices yet. - Currently, this operation is supported for grid based data with cartesian geometry. - In some cases (like SPH data) an off-axis projection over a thin region might be used instead. + Currently, this operation is supported for grid based and SPH data with cartesian geometry. + In some cases an off-axis projection over a thin region might be used instead. .. _projection-plots: From f5c476ad55ee1df9a16f6d924f92261e18bea7b0 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Wed, 17 Jul 2024 13:44:09 -0500 Subject: [PATCH 27/64] doc updates I forgot to save --- doc/source/quickstart/4)_Data_Objects_and_Time_Series.ipynb | 6 ++++-- doc/source/visualizing/plots.rst | 6 ++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/source/quickstart/4)_Data_Objects_and_Time_Series.ipynb b/doc/source/quickstart/4)_Data_Objects_and_Time_Series.ipynb index 49592f19f18..351719dce78 100644 --- a/doc/source/quickstart/4)_Data_Objects_and_Time_Series.ipynb +++ b/doc/source/quickstart/4)_Data_Objects_and_Time_Series.ipynb @@ -337,6 +337,8 @@ "\n", "There are two different types of covering grids: unsmoothed and smoothed. Smoothed grids will be filled through a cascading interpolation process; they will be filled at level 0, interpolated to level 1, filled at level 1, interpolated to level 2, filled at level 2, etc. This will help to reduce edge effects. Unsmoothed covering grids will not be interpolated, but rather values will be duplicated multiple times.\n", "\n", + "For SPH datasets, the covering grid gives the SPH-interpolated value of a field at each grid cell center. This is done for unsmoothed grids; smoothed grids are not available for SPH data.\n", + "\n", "Here we create an unsmoothed covering grid at level 2, with the left edge at `[0.0, 0.0, 0.0]` and with dimensions equal to those that would cover the entire domain at level 2. We can then ask for the Density field, which will be a 3D array." ] }, @@ -385,13 +387,13 @@ ], "metadata": { "kernelspec": { - "name": "python3", "display_name": "Python 3.9.5 64-bit ('yt-dev': pyenv)", "metadata": { "interpreter": { "hash": "14363bd97bed451d1329fb3e06aa057a9e955a9421c5343dd7530f5497723a41" } - } + }, + "name": "python3" }, "language_info": { "codemirror_mode": { diff --git a/doc/source/visualizing/plots.rst b/doc/source/visualizing/plots.rst index 1cc9d3572b1..72e5aac7580 100644 --- a/doc/source/visualizing/plots.rst +++ b/doc/source/visualizing/plots.rst @@ -433,6 +433,8 @@ by applying the In this use case, the volume renderer casts a set of plane parallel rays, one for each pixel in the image. The data values along each ray are summed, creating the final image buffer. +For SPH datsets, the coordinates are instead simply rotated before the axis-aligned +projection function is applied. .. _off-axis-projection-function: @@ -652,10 +654,6 @@ simply pass ``all`` as the first argument of the field tuple: Additional Notes for Plotting Particle Data ------------------------------------------- -An important caveat when visualizing particle data is that off-axis slice plotting is -not available for any particle data. However, axis-aligned slice plots (as described in -:ref:`slice-plots`) will work. - Since version 4.2.0, off-axis projections ares supported for non-SPH particle data. Previous to that, this operation was only supported for SPH particles. Two historical workaround methods were available for plotting non-SPH particles with off-axis From 7d8b676ae70a1ff3764d36af9beaa09a2d3d97e3 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Wed, 17 Jul 2024 13:44:37 -0500 Subject: [PATCH 28/64] fix ruff comment: not declaring unused h_j3 --- yt/utilities/lib/pixelization_routines.pyx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yt/utilities/lib/pixelization_routines.pyx b/yt/utilities/lib/pixelization_routines.pyx index 5752944f813..7469d492af0 100644 --- a/yt/utilities/lib/pixelization_routines.pyx +++ b/yt/utilities/lib/pixelization_routines.pyx @@ -1675,7 +1675,8 @@ def pixelize_sph_kernel_arbitrary_grid(np.float64_t[:, :, :] buff, cdef np.float64_t x_min, x_max, y_min, y_max, z_min, z_max, prefactor_j cdef np.int64_t xi, yi, zi, x0, x1, y0, y1, z0, z1 cdef np.float64_t q_ij, posx_diff, posy_diff, posz_diff, px, py, pz - cdef np.float64_t x, y, z, dx, dy, dz, idx, idy, idz, h_j3, h_j2, h_j, ih_j + cdef np.float64_t x, y, z, dx, dy, dz, idx, idy, idz, h_j2, h_j, ih_j + # cdef np.float64_t h_j3 cdef int j, ii, jj, kk cdef np.float64_t period_x = 0, period_y = 0, period_z = 0 From 170e9af1c5a7269ef125cadf4e67863495313188 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Wed, 17 Jul 2024 15:33:04 -0500 Subject: [PATCH 29/64] fix test_offaxis_moment failure --- yt/visualization/plot_window.py | 5 ++- .../tests/test_offaxisprojection.py | 34 +++++++++++++++---- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/yt/visualization/plot_window.py b/yt/visualization/plot_window.py index 2fdc46edc7b..68db155ad1b 100644 --- a/yt/visualization/plot_window.py +++ b/yt/visualization/plot_window.py @@ -2490,9 +2490,8 @@ def __init__( particle_datasets = (ParticleDataset, StreamParticlesDataset) if isinstance(data_source.ds, particle_datasets) and is_sph_field: - center_use = center = parse_center_array(center, - ds=data_source.ds, - axis=None) + center_use = parse_center_array(center, ds=data_source.ds, + axis=None) else: center_use = center_rot fields = list(iter_fields(fields))[:] diff --git a/yt/visualization/tests/test_offaxisprojection.py b/yt/visualization/tests/test_offaxisprojection.py index d317e31d8be..e1c0fed5e94 100644 --- a/yt/visualization/tests/test_offaxisprojection.py +++ b/yt/visualization/tests/test_offaxisprojection.py @@ -235,10 +235,30 @@ def _vlos_sq(field, data): moment=2, buff_size=(400, 400), ) - assert_rel_equal( - np.sqrt( - p1.frb["gas", "velocity_los_squared"] - p1.frb["gas", "velocity_los"] ** 2 - ), - p2.frb["gas", "velocity_los"], - 10, - ) + ## this failed because some - **2 values come out + ## marginally < 0, resulting in unmatched NaN values in the + ## first assert_rel_equal argument. The compute_stddev_image + ## function used in OffAxisProjectionPlot checks for and deals + ## with these cases. + #assert_rel_equal( + # np.sqrt( + # p1.frb["gas", "velocity_los_squared"] - p1.frb["gas", "velocity_los"] ** 2 + # ), + # p2.frb["gas", "velocity_los"], + # 10, + #) + p1_expsq = p1.frb["gas", "velocity_los_squared"] + p1_sqexp = p1.frb["gas", "velocity_los"] ** 2 + p1res = np.sqrt(p1_expsq - p1_sqexp) + # set values to zero that have **2 - **2 < 0, but + # the absolute values are much smaller than the smallest + # postive values of **2 and **2 + # (i.e., the difference is pretty much zero) + mindiff = 1e-10 * min(np.min(p1_expsq[p1_expsq > 0]), + np.min(p1_sqexp[p1_sqexp > 0])) + print(mindiff) + setzero = np.logical_and(p1_expsq - p1_sqexp < 0, + p1_expsq - p1_sqexp > -1. * mindiff) + p1res[setzero] = 0. + p2res = p2.frb["gas", "velocity_los"] + assert_rel_equal(p1res, p2res, 10) \ No newline at end of file From 9b91e4fb0ce89f3c81ccccafdc9c149da7aee5d7 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Wed, 17 Jul 2024 15:35:35 -0500 Subject: [PATCH 30/64] fix ruff comment end of file? --- yt/visualization/tests/test_offaxisprojection.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yt/visualization/tests/test_offaxisprojection.py b/yt/visualization/tests/test_offaxisprojection.py index e1c0fed5e94..39299703d00 100644 --- a/yt/visualization/tests/test_offaxisprojection.py +++ b/yt/visualization/tests/test_offaxisprojection.py @@ -261,4 +261,5 @@ def _vlos_sq(field, data): p1_expsq - p1_sqexp > -1. * mindiff) p1res[setzero] = 0. p2res = p2.frb["gas", "velocity_los"] - assert_rel_equal(p1res, p2res, 10) \ No newline at end of file + assert_rel_equal(p1res, p2res, 10) + \ No newline at end of file From 71cc131d242a6ac63fc3c17a1a83168849cb0739 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:09:53 -0500 Subject: [PATCH 31/64] draft 1 adding SPH pixelization tests (copied from separate file) --- .../tests/test_sph_pixelization.py | 618 +++++++++++++++++- yt/testing.py | 298 ++++++++- .../tests/test_offaxisprojection.py | 146 ++++- 3 files changed, 1058 insertions(+), 4 deletions(-) diff --git a/yt/geometry/coordinates/tests/test_sph_pixelization.py b/yt/geometry/coordinates/tests/test_sph_pixelization.py index 6de8b29f777..d42ca6de8f5 100644 --- a/yt/geometry/coordinates/tests/test_sph_pixelization.py +++ b/yt/geometry/coordinates/tests/test_sph_pixelization.py @@ -1,7 +1,24 @@ +import pytest + +import numpy as np +import unyt + import yt -from yt.testing import assert_rel_equal, requires_file +from yt.data_objects.selection_objects.region import YTRegion +from yt.testing import ( + assert_rel_equal, + cubicspline_python, + distancematrix, + integrate_kernel, + fake_random_sph_ds, + fake_sph_flexible_grid_ds, + requires_file) from yt.utilities.math_utils import compute_stddev_image +## off-axis projection tests for SPH data are in +## yt/visualization/tests/test_offaxisprojection.py + + magneticum = "MagneticumCluster/snap_132" mag_kwargs = { @@ -36,3 +53,602 @@ def _vysq(field, data): ) sigy = compute_stddev_image(prj1.frb["gas", "vysq"], prj1.frb["gas", "velocity_y"]) assert_rel_equal(sigy, prj2.frb["gas", "velocity_y"].d, 10) + +def test_sph_projection_basic1(): + ''' + small, uniform grid: expected values for given dl? + pixel centers at 0.5, 1., 1.5, 2., 2.5 + particles at 0.5, 1.5, 2.5 + ''' + bbox = np.array([[0., 3.]] * 3) + ds = fake_sph_flexible_grid_ds(hsml_factor=1.0, nperside=3, + bbox=bbox) + # works, but no depth control (at least without specific filters) + proj = ds.proj(("gas", "density"), 2) + frb = proj.to_frb(width=(2.5, 'cm'), + resolution=(5, 5), + height=(2.5, 'cm'), + center=np.array([1.5, 1.5, 1.5]), + periodic=False) + out = frb.get_image(('gas', 'density')) + + expected_out = np.zeros((5, 5), dtype=np.float64) + dl_1part = integrate_kernel(cubicspline_python, 0., 0.5) + linedens_1part = dl_1part * 1. # unit mass, density + linedens = 3. * linedens_1part + expected_out[::2, ::2] = linedens + + assert_rel_equal(expected_out, out.v, 5) + #return out + +def test_sph_projection_basic2(): + ''' + small, uniform grid: expected values for given dl? + pixel centers at 0.5, 1., 1.5, 2., 2.5 + particles at 0.5, 1.5, 2.5 + but hsml radii are 0.25 -> try non-zero impact parameters, + other pixels are still zero. + ''' + bbox = np.array([[0., 3.]] * 3) + ds = fake_sph_flexible_grid_ds(hsml_factor=0.5, nperside=3, + bbox=bbox) + proj = ds.proj(("gas", "density"), 2) + frb = proj.to_frb(width=(2.5, 'cm'), + resolution=(5, 5), + height=(2.5, 'cm'), + center=np.array([1.375, 1.375, 1.5]), + periodic=False) + out = frb.get_image(('gas', 'density')) + + expected_out = np.zeros((5, 5), dtype=np.float64) + dl_1part = integrate_kernel(cubicspline_python, + np.sqrt(2) * 0.125, + 0.25) + linedens_1part = dl_1part * 1. # unit mass, density + linedens = 3. * linedens_1part + expected_out[::2, ::2] = linedens + + #print(expected_out) + #print(out.v) + assert_rel_equal(expected_out, out.v, 4) + #return out + +def get_dataset_sphrefine(reflevel: int = 1): + ''' + constant density particle grid, + with increasing particle sampling + ''' + lenfact = (1./3.)**(reflevel - 1) + massfact = lenfact**3 + nperside = 3**reflevel + + e1hat = np.array([lenfact, 0, 0]) + e2hat = np.array([0, lenfact, 0]) + e3hat = np.array([0, 0, lenfact]) + hsml_factor = lenfact + bbox = np.array([[0., 3.]] * 3) + offsets = np.ones(3, dtype=np.float64) * 0.5 # in units of ehat + + def refmass(i: int, j: int, k: int) -> float: + return massfact + unitrho = 1. / massfact # want density 1 for decreasing mass + + ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, + nperside=nperside, + periodic=True, + e1hat=e1hat, + e2hat=e2hat, + e3hat=e3hat, + offsets=offsets, + massgenerator=refmass, + unitrho=unitrho, + bbox=bbox, + ) + return ds + +def getdata_test_gridproj2(): + # initial pixel centers at 0.5, 1., 1.5, 2., 2.5 + # particles at 0.5, 1.5, 2.5 + # refine particle grid, check if pixel values remain the + # same in the pixels passing through initial particle centers + outlist = [] + dss = [] + for rl in range(1, 4): + ds = get_dataset_sphrefine(reflevel=rl) + proj = ds.proj(("gas", "density"), 2) + frb = proj.to_frb(width=(2.5, 'cm'), + resolution=(5, 5), + height=(2.5, 'cm'), + center=np.array([1.5, 1.5, 1.5]), + periodic=False) + out = frb.get_image(('gas', 'density')) + outlist.append(out) + dss.append(ds) + return outlist, dss + +def test_sph_gridproj_reseffect1(): + ''' + Comparing same pixel centers with higher particle resolution. + The pixel centers are at x/y coordinates [0.5, 1., 1.5, 2., 2.5] + at the first level, the spacing halves at each level. + Checking the pixels at [0.5, 1.5, 2.5], + which should have the same values at each resolution. + ''' + imgs, _ = getdata_test_gridproj2() + ref = imgs[-1] + for i, img in enumerate(imgs): + assert_rel_equal(img[::img.shape[0] // 2, ::img.shape[1] // 2], + ref[::ref.shape[0] // 2, ::ref.shape[1] // 2], 4) + +def test_sph_gridproj_reseffect2(): + ''' + refine the pixel grid instead of the particle grid + ''' + ds = get_dataset_sphrefine(reflevel=2) + proj = ds.proj(("gas", "density"), 2) + imgs = {} + maxrl = 5 + for rl in range(1, maxrl + 1): + npix = 1 + 2**(rl + 1) + margin = 0.5 - 0.5**(rl + 1) + frb = proj.to_frb(width=(3. - 2. * margin, 'cm'), + resolution=(npix, npix), + height=(3. - 2. * margin, 'cm'), + center=np.array([1.5, 1.5, 1.5]), + periodic=False) + out = frb.get_image(('gas', 'density')) + imgs[rl] = out + ref = imgs[maxrl] + pixspace_ref = 2**(maxrl) + for rl in imgs: + img = imgs[rl] + pixspace = 2**(rl) + #print(f'Grid refinement level {rl}:') + assert_rel_equal(img[::pixspace, ::pixspace], + ref[::pixspace_ref, ::pixspace_ref], 4) + + +@pytest.mark.parametrize("weighted", [True, False]) +@pytest.mark.parametrize("periodic", [True, False]) +@pytest.mark.parametrize("depth", [None, (1., "cm")]) +@pytest.mark.parametrize("shiftcenter", [False, True]) +@pytest.mark.parametrize("axis", [0, 1, 2]) +def test_sph_proj_general_alongaxes(axis: int, + shiftcenter: bool, + depth : float | None, + periodic: bool, + weighted: bool) -> None: + ''' + The previous projection tests were for a specific issue. + Here, we test more functionality of the projections. + We just send lines of sight through pixel centers for convenience. + Particles at [0.5, 1.5, 2.5] (in each coordinate) + smoothing lengths 0.25 + all particles have mass 1., density 1.5, + except the single center particle, with mass 2., density 3. + + Parameters: + ----------- + axis: {0, 1, 2} + projection axis (aligned with sim. axis) + shiftcenter: bool + shift the coordinates to center the projection on. + (The grid is offset to this same center) + depth: float or None + depth of the projection slice + periodic: bool + assume periodic boundary conditions, or not + weighted: bool + make a weighted projection (density-weighted density), or not + + Returns: + -------- + None + ''' + if shiftcenter: + center = unyt.unyt_array(np.array((0.625, 0.625, 0.625)), 'cm') + else: + center = unyt.unyt_array(np.array((1.5, 1.5, 1.5)), 'cm') + bbox = unyt.unyt_array(np.array([[0., 3.], [0., 3.], [0., 3.]]), 'cm') + hsml_factor = 0.5 + unitrho = 1.5 + # test correct centering, particle selection + def makemasses(i, j, k): + if i == j == k == 1: + return 2. + else: + return 1. + # m / rho, factor 1. / hsml**2 is included in the kernel integral + # (density is adjusted, so same for center particle) + prefactor = 1. / unitrho #/ (0.5 * 0.5)**2 + dl_cen = integrate_kernel(cubicspline_python, 0., 0.25) + + # result shouldn't depend explicitly on the center if we re-center + # the data, unless we get cut-offs in the non-periodic case + ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, + periodic=periodic, + offsets=np.full(3, 0.5), + massgenerator=makemasses, + unitrho=unitrho, + bbox=bbox.v, + recenter=center.v) + if depth is None: + source = ds.all_data() + else: + depth = unyt.unyt_quantity(*depth) + le = np.array(ds.domain_left_edge) + re = np.array(ds.domain_right_edge) + le[axis] = center[axis] - 0.5 * depth + re[axis] = center[axis] + 0.5 * depth + cen = 0.5 * (le + re) + reg = YTRegion(center=cen, + left_edge=le, + right_edge=re, + ds=ds) + source = reg + + # we don't actually want a plot, it's just a straightforward, + # common way to get an frb / image array + if weighted: + toweight_field = ("gas", "density") + else: + toweight_field = None + prj = yt.ProjectionPlot(ds, axis, ("gas", "density"), + width=(2.5, "cm"), + weight_field=toweight_field, + buff_size=(5, 5), + center=center, + data_source=source) + img = prj.frb.data[('gas', 'density')] + if weighted: + expected_out = np.zeros((5, 5,), dtype=img.v.dtype) + expected_out[::2, ::2] = unitrho + if depth is None: + ## during shift, particle coords do wrap around edges + #if (not periodic) and shiftcenter: + # # weight 1. for unitrho, 2. for 2. * untrho + # expected_out[2, 2] *= 5. / 3. + #else: + # weight (2 * 1.) for unitrho, (1 * 2.) for 2. * unitrho + expected_out[2, 2] *= 1.5 + else: + # only 2 * unitrho element included + expected_out[2, 2] *= 2. + else: + expected_out = np.zeros((5, 5,), dtype=img.v.dtype) + expected_out[::2, ::2] = dl_cen * prefactor * unitrho + if depth is None: + # 3 particles per l.o.s., including the denser one + expected_out *= 3. + expected_out[2, 2] *= 4. / 3. + else: + # 1 particle per l.o.s., including the denser one + expected_out[2, 2] *= 2. + # grid is shifted to the left -> 'missing' stuff at the left + if (not periodic) and shiftcenter: + expected_out[:1, :] = 0. + expected_out[:, :1] = 0. + #print(axis, shiftcenter, depth, periodic, weighted) + #print(expected_out) + #print(img.v) + assert_rel_equal(expected_out, img.v, 5) + +@pytest.mark.parametrize("periodic", [True, False]) +@pytest.mark.parametrize("shiftcenter", [False, True]) +@pytest.mark.parametrize("zoff", [0., 0.1, 0.5, 1.]) +@pytest.mark.parametrize("axis", [0, 1, 2]) +def test_sph_slice_general_alongaxes(axis: int, + shiftcenter: bool, + periodic: bool, + zoff: float) -> None: + ''' + Particles at [0.5, 1.5, 2.5] (in each coordinate) + smoothing lengths 0.25 + all particles have mass 1., density 1.5, + except the single center particle, with mass 2., density 3. + + Parameters: + ----------- + axis: {0, 1, 2} + projection axis (aligned with sim. axis) + northvector: tuple + y-axis direction in the final plot (direction vector) + shiftcenter: bool + shift the coordinates to center the projection on. + (The grid is offset to this same center) + zoff: float + offset of the slice plane from the SPH particle center plane + periodic: bool + assume periodic boundary conditions, or not + + Returns: + -------- + None + ''' + if shiftcenter: + center = unyt.unyt_array(np.array((0.625, 0.625, 0.625)), 'cm') + else: + center = unyt.unyt_array(np.array((1.5, 1.5, 1.5)), 'cm') + bbox = unyt.unyt_array(np.array([[0., 3.], [0., 3.], [0., 3.]]), 'cm') + hsml_factor = 0.5 + unitrho = 1.5 + # test correct centering, particle selection + def makemasses(i, j, k): + if i == j == k == 1: + return 2. + elif i == j == k == 2: + return 3. + else: + return 1. + + # result shouldn't depend explicitly on the center if we re-center + # the data, unless we get cut-offs in the non-periodic case + ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, + periodic=periodic, + offsets=np.full(3, 0.5), + massgenerator=makemasses, + unitrho=unitrho, + bbox=bbox.v, + recenter=center.v) + ad = ds.all_data() + #print(ad[('gas', 'position')]) + outgridsize = 10 + width = 2.5 + _center = center.to("cm").v.copy() + _center[axis] += zoff + + # we don't actually want a plot, it's just a straightforward, + # common way to get an frb / image array + slc = yt.SlicePlot(ds, axis, ("gas", "density"), + width=(width, "cm"), + buff_size=(outgridsize,) * 2, + center=(_center, "cm")) + img = slc.frb.data[('gas', 'density')] + + # center is same in non-projection coords + if axis == 0: + ci = 1 + else: + ci = 0 + gridcens = _center[ci] - 0.5 * width \ + + 0.5 * width / outgridsize \ + + np.arange(outgridsize) * width / outgridsize + xgrid = np.repeat(gridcens, outgridsize) + ygrid = np.tile(gridcens, outgridsize) + zgrid = np.full(outgridsize**2, _center[axis]) + gridcoords = np.empty((outgridsize**2, 3), dtype=xgrid.dtype) + if axis == 2: + gridcoords[:, 0] = xgrid + gridcoords[:, 1] = ygrid + gridcoords[:, 2] = zgrid + elif axis == 0: + gridcoords[:, 0] = zgrid + gridcoords[:, 1] = xgrid + gridcoords[:, 2] = ygrid + elif axis == 1: + gridcoords[:, 0] = ygrid + gridcoords[:, 1] = zgrid + gridcoords[:, 2] = xgrid + ad = ds.all_data() + sphcoords = np.array([(ad[("gas", "x")]).to("cm"), + (ad[("gas", "y")]).to("cm"), + (ad[("gas", "z")]).to("cm"), + ]).T + print('sphcoords:') + print(sphcoords) + print('gridcoords:') + print(gridcoords) + dists = distancematrix(gridcoords, sphcoords, + periodic=(periodic,)*3, + periods=np.array([3., 3., 3.])) + print('dists <= 1:') + print(dists <= 1) + sml = (ad[("gas", "smoothing_length")]).to("cm") + normkern = cubicspline_python(dists / sml.v[np.newaxis, :]) + sphcontr = normkern / sml[np.newaxis, :]**3 * ad[("gas", "mass")] + contsum = np.sum(sphcontr, axis=1) + sphweights = normkern / sml[np.newaxis, :]**3 \ + * ad[("gas", "mass")] / ad[("gas", "density")] + weights = np.sum(sphweights, axis=1) + expected = contsum / weights + expected = expected.reshape((outgridsize, outgridsize)) + expected[np.isnan(expected)] = 0. # convention in the slices + + print('expected:\n', expected.v) + print('recovered:\n', img.v) + assert_rel_equal(expected.v, img.v, 5) + + + +@pytest.mark.parametrize("periodic", [True, False]) +@pytest.mark.parametrize("shiftcenter", [False, True]) +@pytest.mark.parametrize("northvector", [None, (1.e-4, 1., 0.)]) +@pytest.mark.parametrize("zoff", [0., 0.1, 0.5, 1.]) +def test_sph_slice_general_offaxis( + northvector: tuple[float, float, float] | None, + shiftcenter: bool, + zoff: float, + periodic: bool, + ) -> None: + ''' + Same as the on-axis slices, but we rotate the basis vectors + to test whether roations are handled ok. the rotation is chosen to + be small so that in/exclusion of particles within bboxes, etc. + works out the same way. + Particles at [0.5, 1.5, 2.5] (in each coordinate) + smoothing lengths 0.25 + all particles have mass 1., density 1.5, + except the single center particle, with mass 2., density 3. + + Parameters: + ----------- + northvector: tuple + y-axis direction in the final plot (direction vector) + shiftcenter: bool + shift the coordinates to center the projection on. + (The grid is offset to this same center) + zoff: float + offset of the slice plane from the SPH particle center plane + periodic: bool + assume periodic boundary conditions, or not + + Returns: + -------- + None + ''' + if shiftcenter: + center = np.array((0.625, 0.625, 0.625)) # cm + else: + center = np.array((1.5, 1.5, 1.5)) # cm + bbox = unyt.unyt_array(np.array([[0., 3.], [0., 3.], [0., 3.]]), 'cm') + hsml_factor = 0.5 + unitrho = 1.5 + # test correct centering, particle selection + def makemasses(i, j, k): + if i == j == k == 1: + return 2. + else: + return 1. + # try to make sure dl differences from periodic wrapping are small + epsilon = 1e-4 + projaxis = np.array([epsilon, 0.00, np.sqrt(1. - epsilon**2)]) + e1dir = projaxis / np.sqrt(np.sum(projaxis**2)) + if northvector is None: + e2dir = np.array([0., 1., 0.]) + else: + e2dir = np.asarray(northvector) + e2dir = e2dir - np.sum(e1dir * e2dir) * e2dir # orthonormalize + e2dir /= np.sqrt(np.sum(e2dir**2)) + e3dir = np.cross(e2dir, e1dir) + + outgridsize = 10 + width = 2.5 + _center = center.copy() + _center += zoff * e1dir + + ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, + periodic=periodic, + offsets=np.full(3, 0.5), + massgenerator=makemasses, + unitrho=unitrho, + bbox=bbox.v, + recenter=center, + e1hat=e1dir, e2hat=e2dir, e3hat=e3dir) + + source = ds.all_data() + # couple to dataset -> right unit registry + center = ds.arr(center, 'cm') + print('position:\n', source['gas','position']) + slc = yt.SlicePlot(ds, e1dir, ("gas", "density"), + width=(width, "cm"), + buff_size=(outgridsize,) * 2, + center=(_center, "cm"), + north_vector=e2dir) + img = slc.frb.data[('gas', 'density')] + + # center is same in x/y (e3dir/e2dir) + gridcenx = np.dot(_center, e3dir) - 0.5 * width \ + + 0.5 * width / outgridsize \ + + np.arange(outgridsize) * width / outgridsize + gridceny = np.dot(_center, e2dir) - 0.5 * width \ + + 0.5 * width / outgridsize \ + + np.arange(outgridsize) * width / outgridsize + xgrid = np.repeat(gridcenx, outgridsize) + ygrid = np.tile(gridceny, outgridsize) + zgrid = np.full(outgridsize**2, np.dot(_center, e1dir)) + gridcoords = (xgrid[:, np.newaxis] * e3dir[np.newaxis, :] + + ygrid[:, np.newaxis] * e2dir[np.newaxis, :] + + zgrid[:, np.newaxis] * e1dir[np.newaxis, :]) + print('gridcoords:') + print(gridcoords) + ad = ds.all_data() + sphcoords = np.array([(ad[("gas", "x")]).to("cm"), + (ad[("gas", "y")]).to("cm"), + (ad[("gas", "z")]).to("cm"), + ]).T + dists = distancematrix(gridcoords, sphcoords, + periodic=(periodic,)*3, + periods=np.array([3., 3., 3.])) + sml = (ad[("gas", "smoothing_length")]).to("cm") + normkern = cubicspline_python(dists / sml.v[np.newaxis, :]) + sphcontr = normkern / sml[np.newaxis, :]**3 * ad[("gas", "mass")] + contsum = np.sum(sphcontr, axis=1) + sphweights = normkern / sml[np.newaxis, :]**3 \ + * ad[("gas", "mass")] / ad[("gas", "density")] + weights = np.sum(sphweights, axis=1) + expected = contsum / weights + expected = expected.reshape((outgridsize, outgridsize)) + expected = expected.T # transposed for image plotting + expected[np.isnan(expected)] = 0. # convention in the slices + + #print(axis, shiftcenter, depth, periodic, weighted) + print('expected:\n', expected.v) + print('recovered:\n', img.v) + assert_rel_equal(expected.v, img.v, 4) + +# only axis-aligned; testing YTArbitraryGrid, YTCoveringGrid +@pytest.mark.parametrize("periodic", [True, False, (True, True, False)]) +@pytest.mark.parametrize("wholebox", [True, False]) +def test_sph_grid(periodic: bool | tuple[bool, bool, bool], + wholebox: bool): + bbox = np.array([[-1., 3.], [1., 5.2], [-1., 3.]]) + ds = fake_random_sph_ds(50, bbox, periodic=periodic) + + if not hasattr(periodic, "__len__"): + periodic = (periodic,) * 3 + + if wholebox: + left = bbox[:, 0].copy() + level = 2 + ncells = np.array([2**level] * 3) + print('left: ', left) + print('ncells: ', ncells) + resgrid = ds.covering_grid(level, tuple(left), ncells) + right = bbox[:, 1].copy() + xedges = np.linspace(left[0], right[0], ncells[0] + 1) + yedges = np.linspace(left[1], right[1], ncells[1] + 1) + zedges = np.linspace(left[2], right[2], ncells[2] + 1) + else: + left = np.array([-1., 1.8, -1.]) + right = np.array([2.5, 5.2, 2.5]) + ncells = np.array([3, 4, 4]) + resgrid = ds.arbitrary_grid(left, right, dims=ncells) + xedges = np.linspace(left[0], right[0], ncells[0] + 1) + yedges = np.linspace(left[1], right[1], ncells[1] + 1) + zedges = np.linspace(left[2], right[2], ncells[2] + 1) + res = resgrid["gas", "density"] + xcens = 0.5 * (xedges[:-1] + xedges[1:]) + ycens = 0.5 * (yedges[:-1] + yedges[1:]) + zcens = 0.5 * (zedges[:-1] + zedges[1:]) + + ad = ds.all_data() + sphcoords = np.array([(ad[("gas", "x")]).to("cm"), + (ad[("gas", "y")]).to("cm"), + (ad[("gas", "z")]).to("cm"), + ]).T + gridx, gridy, gridz = np.meshgrid(xcens, ycens, zcens, + indexing='ij') + outshape = gridx.shape + gridx = gridx.flatten() + gridy = gridy.flatten() + gridz = gridz.flatten() + gridcoords = np.array([gridx, gridy, gridz]).T + periods = bbox[:, 1] - bbox[:, 0] + dists = distancematrix(gridcoords, sphcoords, + periodic=periodic, + periods=periods) + sml = (ad[("gas", "smoothing_length")]).to("cm") + normkern = cubicspline_python(dists / sml.v[np.newaxis, :]) + sphcontr = normkern / sml[np.newaxis, :]**3 * ad[("gas", "mass")] + contsum = np.sum(sphcontr, axis=1) + sphweights = normkern / sml[np.newaxis, :]**3 \ + * ad[("gas", "mass")] / ad[("gas", "density")] + weights = np.sum(sphweights, axis=1) + expected = contsum / weights + expected = expected.reshape(outshape) + expected[np.isnan(expected)] = 0. # convention in the slices + + #print(axis, shiftcenter, depth, periodic, weighted) + print('expected:\n', expected.v) + print('recovered:\n', res.v) + assert_rel_equal(expected.v, res.v, 4) diff --git a/yt/testing.py b/yt/testing.py index 2205439ea1f..d519f32c795 100644 --- a/yt/testing.py +++ b/yt/testing.py @@ -9,6 +9,7 @@ from functools import wraps from importlib.util import find_spec from shutil import which +from typing import Callable from unittest import SkipTest import matplotlib @@ -19,10 +20,13 @@ from yt._maintenance.deprecation import issue_deprecation_warning from yt.config import ytcfg +from yt.frontends.stream.data_structures import StreamParticlesDataset from yt.funcs import is_sequence -from yt.loaders import load +from yt.loaders import load, load_particles from yt.units.yt_array import YTArray, YTQuantity + + ANSWER_TEST_TAG = "answer_test" @@ -79,6 +83,101 @@ def assert_rel_equal(a1, a2, decimals, err_msg="", verbose=True): np.array(a1) / np.array(a2), 1.0, decimals, err_msg=err_msg, verbose=verbose ) +# tested: volume integral is 1. +def cubicspline_python(x: float) -> float: + ''' + cubic spline SPH kernel function for testing against more + effiecient cython methods + + Parameters + ---------- + x: + impact parameter / smoothing length [dimenionless] + + Returns + ------- + value of the kernel function + ''' + # C is 8/pi + _c = 8. / np.pi + x = np.asarray(x) + kernel = np.zeros(x.shape, dtype=x.dtype) + half1 = np.where(np.logical_and(x >=0., x <= 0.5)) + kernel[half1] = 1. - 6. * x[half1]**2 * (1. - x[half1]) + half2 = np.where(np.logical_and(x > 0.5, x <= 1.0)) + kernel[half2] = 2. * (1. - x[half2])**3 + return kernel * _c + +def integrate_kernel(kernelfunc: Callable[[float], float], + b: float, hsml: float) -> float: + """ + integrates a kernel function over a line passing entirely + through it + + Parameters: + ----------- + kernelfunc: + the kernel function to integrate + b: + impact parameter + hsml: + smoothing length [same units as impact parameter] + + Returns: + -------- + the integral of the SPH kernel function. + units: 1 / units of b and hsml + """ + pre = 1. / hsml**2 + x = b / hsml + xmax = np.sqrt(1. - x**2) + xmin = -1. * xmax + xe = np.linspace(xmin, xmax, 500) # shape: 500, x.shape + xc = 0.5 * (xe[:-1, ...] + xe[1:, ...]) + dx = np.diff(xe, axis=0) + spv = kernelfunc(np.sqrt(xc**2 + x**2)) + integral = np.sum(spv * dx, axis=0) + return pre * integral + +def distancematrix(pos3_i0: np.ndarray[float], + pos3_i1: np.ndarray[float], + periodic: tuple[bool] = (True,) * 3, + periods: np.ndarray = np.array([0., 0., 0.]), + ) -> np.ndarray[float]: + ''' + Calculates the distances between two arrays of points. + + Parameters: + ---------- + pos3_i0: shape (first number of points, 3) + positions of the first set of points. The second index is + for positions along the different cartesian axes + pos3_i1: shape (second number of points, 3) + as pos3_i0, but for the second set of points + periodic: + are the positions along each axis periodic (True) or not + periods: + the periods along each axis. Ignored if positions in a given + direction are not periodic. + + Returns: + -------- + a 2D-array of distances between postions `pos3_i0` (changes along + index 0) and `pos3_i1` (changes along index 1) + + ''' + d2 = np.zeros((len(pos3_i0), len(pos3_i1)), dtype=pos3_i0.dtype) + for ax in range(3): + # 'center on' pos3_i1 + _d = pos3_i0[:, ax, np.newaxis] - pos3_i1[np.newaxis, :, ax] + if periodic[ax]: + _period = periods[ax] + _d += 0.5 * _period # center on half box size + _d %= _period # wrap coordinate to 0 -- boxsize range + _d -= 0.5 * _period # center back to zero + d2 += _d**2 + return np.sqrt(d2) + def amrspace(extent, levels=7, cells=8): """Creates two numpy arrays representing the left and right bounds of @@ -686,6 +785,203 @@ def fake_sph_grid_ds(hsml_factor=1.0): return load_particles(data=data, length_unit=1.0, bbox=bbox) +def constantmass(i: int, j: int, k: int) -> float: + return 1. + +def fake_sph_flexible_grid_ds( + hsml_factor: float = 1.0, + nperside: int = 3, + periodic: bool = True, + e1hat: np.ndarray[float] = np.array([1, 0, 0]), + e2hat: np.ndarray[float] = np.array([0, 1, 0]), + e3hat: np.ndarray[float] = np.array([0, 0, 1]), + offsets: np.ndarray[float] = 0.5 * np.ones((3,), dtype=np.float64), + massgenerator: Callable[[int, int, int], float] = constantmass, + unitrho: float = 1., + bbox: np.ndarray | None = None, + recenter: np.ndarray | None = None, + ) -> StreamParticlesDataset: + """Returns an in-memory SPH dataset useful for testing + + Parameters: + ----------- + hsml_factor: + all particles have smoothing lengths of `hsml_factor` * 0.5 + nperside: + the dataset will have `nperside`**3 particles, arranged + uniformly on a 3D grid + periodic: + are the positions taken to be periodic? (applies to all + coordinate axes) + e1hat: shape (3,) + the first basis vector defining the 3D grid. If the basis + vectors are not normalized to 1 or not orthogonal, the spacing + or overlap between SPH particles will be affected, but this is + allowed. + e2hat: shape (3,) + the second basis vector defining the 3D grid. (See `e1hat`.) + e3hat: shape (3,) + the third basis vector defining the 3D grid. (See `e1hat`.) + offsets: shape (3,) + the the zero point of the 3D grid along each coordinate axis + massgenerator: + a function assigning a mass to each particle, as a function of + the e[1-3]hat indices, in order + unitrho: + defines the density for a particle with mass 1 ('g'), and the + standard (uniform) grid `hsml_factor`. + bbox: if np.ndarray, shape is (2, 3) + the assumed enclosing volume of the particles. Should enclose + all the coordinate values. If not specified, a bbox is defined + which encloses all coordinates values with a margin. If + `periodic`, the size of the `bbox` along each coordinate is + also the period along that axis. + recenter: + if not `None`, after generating the grid, the positions are + periodically shifted to move the old center to this positions. + Useful for testing periodicity handling. + This shift is relative to the halfway positions of the bbox + edges. + + Returns: + -------- + A `StreamParticlesDataset` object with particle positions, masses, + velocities (zero), smoothing lengths, and densities specified. + Values are in cgs units. + """ + + npart = nperside**3 + + pos = np.empty((npart, 3), dtype=np.float64) + mass = np.empty((npart,), dtype=np.float64) + for i in range(0, nperside): + for j in range(0, nperside): + for k in range(0, nperside): + _pos = (offsets[0] + i) * e1hat \ + + (offsets[1] + j) * e2hat \ + + (offsets[2] + k) * e3hat + ind = nperside**2 * i + nperside * j + k + pos[ind, :] = _pos + mass[ind] = massgenerator(i, j, k) + rho = unitrho * mass + + if bbox is None: + eps = 1e-3 + margin = (1. + eps) * hsml_factor + bbox = np.array([[np.min(pos[:, 0]) - margin, + np.max(pos[:, 0]) + margin], + [np.min(pos[:, 1]) - margin, + np.max(pos[:, 1]) + margin], + [np.min(pos[:, 2]) - margin, + np.max(pos[:, 2]) + margin], + ]) + + if recenter is not None: + periods = bbox[:, 1] - bbox[:, 0] + # old center -> new position + pos += -0.5 * periods[np.newaxis, :] + recenter[np.newaxis, :] + # wrap coordinates -> all in [0, boxsize) range + pos %= periods[np.newaxis, :] + # shift back to original bbox range + pos += (bbox[:, 0])[np.newaxis, :] + if not periodic: + # remove points outside bbox to avoid errors: + okinds = np.ones(len(mass), dtype=bool) + for ax in [0, 1, 2]: + okinds &= pos[:, ax] < bbox[ax, 1] + okinds &= pos[:, ax] >= bbox[ax, 0] + npart = sum(okinds) + else: + okinds = slice(None, None, None) + + data = { + "particle_position_x": (np.copy(pos[okinds, 0]), "cm"), + "particle_position_y": (np.copy(pos[okinds, 1]), "cm"), + "particle_position_z": (np.copy(pos[okinds, 2]), "cm"), + "particle_mass": (mass[okinds], "g"), + "particle_velocity_x": (np.zeros(npart), "cm/s"), + "particle_velocity_y": (np.zeros(npart), "cm/s"), + "particle_velocity_z": (np.zeros(npart), "cm/s"), + "smoothing_length": (np.ones(npart) * 0.5 * hsml_factor, "cm"), + "density": (rho[okinds], "g/cm**3"), + } + + ds = load_particles(data=data, + bbox=bbox, periodicity=(periodic,) * 3, + length_unit=1., mass_unit=1., time_unit=1., + velocity_unit=1.) + ds.kernel_name = 'cubic' + return ds + + +def fake_random_sph_ds(npart: int, bbox: np.ndarray, + periodic: bool | tuple[bool, bool, bool] = True, + massrange: tuple[float, float] = (0.5, 2.), + hsmlrange: tuple[float, float] = (0.5, 2.), + unitrho: float = 1., + ) -> StreamParticlesDataset: + """Returns an in-memory SPH dataset useful for testing + + Parameters: + ----------- + npart: + number of particles to generate + bbox: shape: (3, 2), units: "cm" + the assumed enclosing volume of the particles. Particle + positions are drawn uniformly from these ranges. + periodic: + are the positions taken to be periodic? If a single value, + that value is applied to all axes + massrange: + particle masses are drawn uniformly from this range (unit: "g") + hsmlrange: units: "cm" + particle smoothing lengths are drawn uniformly from this range + unitrho: + defines the density for a particle with mass 1 ("g"), and + smoothing length 1 ("cm"). + + Returns: + -------- + A `StreamParticlesDataset` object with particle positions, masses, + velocities (zero), smoothing lengths, and densities specified. + Values are in cgs units. + """ + + if not hasattr(periodic, "__len__"): + periodic = (periodic, ) * 3 + + posx = np.random.uniform(low=bbox[0][0], high=bbox[0][1], + size=npart) + posy = np.random.uniform(low=bbox[1][0], high=bbox[1][1], + size=npart) + posz = np.random.uniform(low=bbox[2][0], high=bbox[2][1], + size=npart) + mass = np.random.uniform(low=massrange[0], high=massrange[1], + size=npart) + hsml = np.random.uniform(low=hsmlrange[0], high=hsmlrange[1], + size=npart) + dens = mass / hsml**3 * unitrho + + data = { + "particle_position_x": (posx, "cm"), + "particle_position_y": (posy, "cm"), + "particle_position_z": (posz, "cm"), + "particle_mass": (mass, "g"), + "particle_velocity_x": (np.zeros(npart), "cm/s"), + "particle_velocity_y": (np.zeros(npart), "cm/s"), + "particle_velocity_z": (np.zeros(npart), "cm/s"), + "smoothing_length": (hsml, "cm"), + "density": (dens, "g/cm**3"), + } + + ds = load_particles(data=data, + bbox=bbox, periodicity=periodic, + length_unit=1., mass_unit=1., time_unit=1., + velocity_unit=1.) + ds.kernel_name = 'cubic' + return ds + + def construct_octree_mask(prng=RandomState(0x1D3D3D3), refined=None): # noqa B008 # Implementation taken from url: # http://docs.hyperion-rt.org/en/stable/advanced/indepth_oct.html diff --git a/yt/visualization/tests/test_offaxisprojection.py b/yt/visualization/tests/test_offaxisprojection.py index 39299703d00..42b9b5faaa9 100644 --- a/yt/visualization/tests/test_offaxisprojection.py +++ b/yt/visualization/tests/test_offaxisprojection.py @@ -1,19 +1,27 @@ import itertools as it import os +import pytest import shutil import tempfile import unittest import numpy as np from numpy.testing import assert_equal +import unyt from yt.testing import ( assert_fname, assert_rel_equal, + cubicspline_python, + integrate_kernel, fake_octree_ds, fake_random_ds, + fake_sph_flexible_grid_ds, ) -from yt.visualization.api import OffAxisProjectionPlot, OffAxisSlicePlot +from yt.visualization.api import ( + OffAxisProjectionPlot, + OffAxisSlicePlot, + ProjectionPlot) from yt.visualization.image_writer import write_projection from yt.visualization.volume_rendering.api import off_axis_projection @@ -262,4 +270,138 @@ def _vlos_sq(field, data): p1res[setzero] = 0. p2res = p2.frb["gas", "velocity_los"] assert_rel_equal(p1res, p2res, 10) - \ No newline at end of file + + +@pytest.mark.parametrize("weighted", [True, False]) +@pytest.mark.parametrize("periodic", [True, False]) +@pytest.mark.parametrize("depth", [None, (1., "cm"), (0.5, "cm")]) +@pytest.mark.parametrize("shiftcenter", [False, True]) +@pytest.mark.parametrize("northvector", [None, (1.e-4, 1., 0.)]) +def test_sph_proj_general_offaxis( + northvector: tuple[float, float, float] | None, + shiftcenter: bool, + depth: tuple[float, str] | None, + periodic: bool, + weighted: bool) -> None: + ''' + Same as the on-axis projections, but we rotate the basis vectors + to test whether roations are handled ok. the rotation is chosen to + be small so that in/exclusion of particles within bboxes, etc. + works out the same way. + We just send lines of sight through pixel centers for convenience. + Particles at [0.5, 1.5, 2.5] (in each coordinate) + smoothing lengths 0.25 + all particles have mass 1., density 1.5, + except the single center particle, with mass 2., density 3. + + Parameters: + ----------- + northvector: tuple + y-axis direction in the final plot (direction vector) + shiftcenter: bool + shift the coordinates to center the projection on. + (The grid is offset to this same center) + depth: float or None + depth of the projection slice + periodic: bool + assume periodic boundary conditions, or not + weighted: bool + make a weighted projection (density-weighted density), or not + + Returns: + -------- + None + ''' + if shiftcenter: + center = np.array((0.625, 0.625, 0.625)) # cm + else: + center = np.array((1.5, 1.5, 1.5)) # cm + bbox = unyt.unyt_array(np.array([[0., 3.], [0., 3.], [0., 3.]]), 'cm') + hsml_factor = 0.5 + unitrho = 1.5 + # test correct centering, particle selection + def makemasses(i, j, k): + if i == j == k == 1: + return 2. + else: + return 1. + #dl_shift = _integrate_kernel(_cubicspline_python, + # np.sqrt(2) * 0.125, 0.25) + + # result shouldn't depend explicitly on the center if we re-center + # the data, unless we get cut-offs in the non-periodic case + # *almost* the z-axis + # try to make sure dl differences from periodic wrapping are small + epsilon = 1e-4 + projaxis = np.array([epsilon, 0.00, np.sqrt(1. - epsilon**2)]) + e1dir = projaxis / np.sqrt(np.sum(projaxis**2)) + # TODO: figure out other (default) axes for basis vectors here + if northvector is None: + e2dir = np.array([0., 1., 0.]) + else: + e2dir = np.asarray(northvector) + e2dir = e2dir - np.sum(e1dir * e2dir) * e2dir # orthonormalize + e2dir /= np.sqrt(np.sum(e2dir**2)) + e3dir = np.cross(e1dir, e2dir) + + ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, + periodic=periodic, + offsets=np.full(3, 0.5), + massgenerator=makemasses, + unitrho=unitrho, + bbox=bbox.v, + recenter=center, + e1hat=e1dir, e2hat=e2dir, e3hat=e3dir) + + source = ds.all_data() + # couple to dataset -> right unit registry + center = ds.arr(center, 'cm') + #print('position:\n', source['gas','position']) + + # m / rho, factor 1. / hsml**2 is included in the kernel integral + # (density is adjusted, so same for center particle) + prefactor = 1. / unitrho #/ (0.5 * 0.5)**2 + dl_cen = integrate_kernel(cubicspline_python, 0., 0.25) + + if weighted: + toweight_field = ("gas", "density") + else: + toweight_field = None + # we don't actually want a plot, it's just a straightforward, + # common way to get an frb / image array + prj = ProjectionPlot(ds, projaxis, ("gas", "density"), + width=(2.5, "cm"), + weight_field=toweight_field, + buff_size=(5, 5), + center=center, + data_source=source, + north_vector=northvector, + depth=depth) + img = prj.frb.data[('gas', 'density')] + if weighted: + # periodic shifts will modify the (relative) dl values a bit + expected_out = np.zeros((5, 5,), dtype=img.v.dtype) + expected_out[::2, ::2] = unitrho + if depth is None: + expected_out[2, 2] *= 1.5 + else: + # only 2 * unitrho element included + expected_out[2, 2] *= 2. + else: + expected_out = np.zeros((5, 5,), dtype=img.v.dtype) + expected_out[::2, ::2] = dl_cen * prefactor * unitrho + if depth is None: + # 3 particles per l.o.s., including the denser one + expected_out *= 3. + expected_out[2, 2] *= 4. / 3. + else: + # 1 particle per l.o.s., including the denser one + expected_out[2, 2] *= 2. + # grid is shifted to the left -> 'missing' stuff at the left + if (not periodic) and shiftcenter: + expected_out[:1, :] = 0. + expected_out[:, :1] = 0. + #print(axis, shiftcenter, depth, periodic, weighted) + print('expected:\n', expected_out) + print('recovered:\n', img.v) + assert_rel_equal(expected_out, img.v, 4) From 159167bead3fd6a818cd8ab1999fbffaeebd7588 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Wed, 17 Jul 2024 19:10:34 -0500 Subject: [PATCH 32/64] fix ruff issues --- .../tests/test_sph_pixelization.py | 5 ++- yt/testing.py | 33 ++++++++++--------- .../tests/test_offaxisprojection.py | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/yt/geometry/coordinates/tests/test_sph_pixelization.py b/yt/geometry/coordinates/tests/test_sph_pixelization.py index d42ca6de8f5..5c6bd91677d 100644 --- a/yt/geometry/coordinates/tests/test_sph_pixelization.py +++ b/yt/geometry/coordinates/tests/test_sph_pixelization.py @@ -1,6 +1,5 @@ -import pytest - import numpy as np +import pytest import unyt import yt @@ -176,7 +175,7 @@ def test_sph_gridproj_reseffect1(): ''' imgs, _ = getdata_test_gridproj2() ref = imgs[-1] - for i, img in enumerate(imgs): + for img in imgs: assert_rel_equal(img[::img.shape[0] // 2, ::img.shape[1] // 2], ref[::ref.shape[0] // 2, ::ref.shape[1] // 2], 4) diff --git a/yt/testing.py b/yt/testing.py index d519f32c795..14b58ed9506 100644 --- a/yt/testing.py +++ b/yt/testing.py @@ -13,8 +13,8 @@ from unittest import SkipTest import matplotlib -import numpy as np from more_itertools import always_iterable +import numpy as np from numpy.random import RandomState from unyt.exceptions import UnitOperationError @@ -139,10 +139,11 @@ def integrate_kernel(kernelfunc: Callable[[float], float], integral = np.sum(spv * dx, axis=0) return pre * integral +_zeroperiods = np.array([0., 0., 0.]) def distancematrix(pos3_i0: np.ndarray[float], pos3_i1: np.ndarray[float], periodic: tuple[bool] = (True,) * 3, - periods: np.ndarray = np.array([0., 0., 0.]), + periods: np.ndarray = _zeroperiods, ) -> np.ndarray[float]: ''' Calculates the distances between two arrays of points. @@ -788,14 +789,18 @@ def fake_sph_grid_ds(hsml_factor=1.0): def constantmass(i: int, j: int, k: int) -> float: return 1. +_xhat = np.array([1, 0, 0]) +_yhat = np.array([0, 1, 0]) +_zhat = np.array([0, 0, 1]) +_floathalves = 0.5 * np.ones((3,), dtype=np.float64) def fake_sph_flexible_grid_ds( hsml_factor: float = 1.0, nperside: int = 3, periodic: bool = True, - e1hat: np.ndarray[float] = np.array([1, 0, 0]), - e2hat: np.ndarray[float] = np.array([0, 1, 0]), - e3hat: np.ndarray[float] = np.array([0, 0, 1]), - offsets: np.ndarray[float] = 0.5 * np.ones((3,), dtype=np.float64), + e1hat: np.ndarray[float] = _xhat, + e2hat: np.ndarray[float] = _yhat, + e3hat: np.ndarray[float] = _zhat, + offsets: np.ndarray[float] = _floathalves, massgenerator: Callable[[int, int, int], float] = constantmass, unitrho: float = 1., bbox: np.ndarray | None = None, @@ -949,17 +954,13 @@ def fake_random_sph_ds(npart: int, bbox: np.ndarray, if not hasattr(periodic, "__len__"): periodic = (periodic, ) * 3 + gen = np.random.default_rng(seed=0) - posx = np.random.uniform(low=bbox[0][0], high=bbox[0][1], - size=npart) - posy = np.random.uniform(low=bbox[1][0], high=bbox[1][1], - size=npart) - posz = np.random.uniform(low=bbox[2][0], high=bbox[2][1], - size=npart) - mass = np.random.uniform(low=massrange[0], high=massrange[1], - size=npart) - hsml = np.random.uniform(low=hsmlrange[0], high=hsmlrange[1], - size=npart) + posx = gen.uniform(low=bbox[0][0], high=bbox[0][1], size=npart) + posy = gen.uniform(low=bbox[1][0], high=bbox[1][1], size=npart) + posz = gen.uniform(low=bbox[2][0], high=bbox[2][1], size=npart) + mass = gen.uniform(low=massrange[0], high=massrange[1], size=npart) + hsml = gen.uniform(low=hsmlrange[0], high=hsmlrange[1], size=npart) dens = mass / hsml**3 * unitrho data = { diff --git a/yt/visualization/tests/test_offaxisprojection.py b/yt/visualization/tests/test_offaxisprojection.py index 42b9b5faaa9..02f8eb9380c 100644 --- a/yt/visualization/tests/test_offaxisprojection.py +++ b/yt/visualization/tests/test_offaxisprojection.py @@ -1,12 +1,12 @@ import itertools as it import os -import pytest import shutil import tempfile import unittest import numpy as np from numpy.testing import assert_equal +import pytest import unyt from yt.testing import ( From 2f589c95f7373589f6926e8c6a67fc4046d29a3e Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Wed, 17 Jul 2024 19:19:02 -0500 Subject: [PATCH 33/64] order imports correctly? --- yt/geometry/coordinates/tests/test_sph_pixelization.py | 2 +- yt/testing.py | 2 +- yt/visualization/tests/test_offaxisprojection.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/yt/geometry/coordinates/tests/test_sph_pixelization.py b/yt/geometry/coordinates/tests/test_sph_pixelization.py index 5c6bd91677d..89b2d8003a7 100644 --- a/yt/geometry/coordinates/tests/test_sph_pixelization.py +++ b/yt/geometry/coordinates/tests/test_sph_pixelization.py @@ -8,9 +8,9 @@ assert_rel_equal, cubicspline_python, distancematrix, - integrate_kernel, fake_random_sph_ds, fake_sph_flexible_grid_ds, + integrate_kernel, requires_file) from yt.utilities.math_utils import compute_stddev_image diff --git a/yt/testing.py b/yt/testing.py index 14b58ed9506..68b6cc4619f 100644 --- a/yt/testing.py +++ b/yt/testing.py @@ -13,8 +13,8 @@ from unittest import SkipTest import matplotlib -from more_itertools import always_iterable import numpy as np +from more_itertools import always_iterable from numpy.random import RandomState from unyt.exceptions import UnitOperationError diff --git a/yt/visualization/tests/test_offaxisprojection.py b/yt/visualization/tests/test_offaxisprojection.py index 02f8eb9380c..7b7f7e6966d 100644 --- a/yt/visualization/tests/test_offaxisprojection.py +++ b/yt/visualization/tests/test_offaxisprojection.py @@ -5,18 +5,18 @@ import unittest import numpy as np -from numpy.testing import assert_equal import pytest import unyt +from numpy.testing import assert_equal from yt.testing import ( assert_fname, assert_rel_equal, cubicspline_python, - integrate_kernel, fake_octree_ds, fake_random_ds, fake_sph_flexible_grid_ds, + integrate_kernel, ) from yt.visualization.api import ( OffAxisProjectionPlot, From aecc0513ded478f0dfc0fc73519e11e74cf35700 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Thu, 18 Jul 2024 14:21:35 -0500 Subject: [PATCH 34/64] separate out pytest-only tests, add to nose ignore lists --- nose_ignores.txt | 2 + tests/tests.yaml | 2 + .../tests/test_sph_pixelization.py | 451 ----------------- .../tests/test_sph_pixelization_pytestonly.py | 459 ++++++++++++++++++ .../tests/test_offaxisprojection.py | 142 +----- .../test_offaxisprojection_pytestonly.py | 143 ++++++ 6 files changed, 607 insertions(+), 592 deletions(-) create mode 100644 yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py create mode 100644 yt/visualization/tests/test_offaxisprojection_pytestonly.py diff --git a/nose_ignores.txt b/nose_ignores.txt index 115b917d59d..b5529a6eccf 100644 --- a/nose_ignores.txt +++ b/nose_ignores.txt @@ -45,3 +45,5 @@ --ignore-file=test_set_log_level\.py --ignore-file=test_field_parsing\.py --ignore-file=test_disks\.py +--ignore-file=test_offaxisprojection_pytestonly\.py +--ignore-file=test_sph_pixelization_pytestonly\.py diff --git a/tests/tests.yaml b/tests/tests.yaml index d0f12ad1e57..d7de9b9650c 100644 --- a/tests/tests.yaml +++ b/tests/tests.yaml @@ -221,5 +221,7 @@ other_tests: - "--exclude-test=yt.frontends.gdf.tests.test_outputs.TestGDF" - "--exclude-test=yt.frontends.adaptahop.tests.test_outputs" - "--exclude-test=yt.frontends.stream.tests.test_stream_particles.test_stream_non_cartesian_particles" + - "--ignore-file=test_offaxisprojection_pytestonly\\.py" + - "--ignore-file=test_sph_pixelization_pytestonly\\.py" cookbook: - 'doc/source/cookbook/tests/test_cookbook.py' diff --git a/yt/geometry/coordinates/tests/test_sph_pixelization.py b/yt/geometry/coordinates/tests/test_sph_pixelization.py index 89b2d8003a7..da5c9984bff 100644 --- a/yt/geometry/coordinates/tests/test_sph_pixelization.py +++ b/yt/geometry/coordinates/tests/test_sph_pixelization.py @@ -1,14 +1,9 @@ import numpy as np -import pytest -import unyt import yt -from yt.data_objects.selection_objects.region import YTRegion from yt.testing import ( assert_rel_equal, cubicspline_python, - distancematrix, - fake_random_sph_ds, fake_sph_flexible_grid_ds, integrate_kernel, requires_file) @@ -205,449 +200,3 @@ def test_sph_gridproj_reseffect2(): #print(f'Grid refinement level {rl}:') assert_rel_equal(img[::pixspace, ::pixspace], ref[::pixspace_ref, ::pixspace_ref], 4) - - -@pytest.mark.parametrize("weighted", [True, False]) -@pytest.mark.parametrize("periodic", [True, False]) -@pytest.mark.parametrize("depth", [None, (1., "cm")]) -@pytest.mark.parametrize("shiftcenter", [False, True]) -@pytest.mark.parametrize("axis", [0, 1, 2]) -def test_sph_proj_general_alongaxes(axis: int, - shiftcenter: bool, - depth : float | None, - periodic: bool, - weighted: bool) -> None: - ''' - The previous projection tests were for a specific issue. - Here, we test more functionality of the projections. - We just send lines of sight through pixel centers for convenience. - Particles at [0.5, 1.5, 2.5] (in each coordinate) - smoothing lengths 0.25 - all particles have mass 1., density 1.5, - except the single center particle, with mass 2., density 3. - - Parameters: - ----------- - axis: {0, 1, 2} - projection axis (aligned with sim. axis) - shiftcenter: bool - shift the coordinates to center the projection on. - (The grid is offset to this same center) - depth: float or None - depth of the projection slice - periodic: bool - assume periodic boundary conditions, or not - weighted: bool - make a weighted projection (density-weighted density), or not - - Returns: - -------- - None - ''' - if shiftcenter: - center = unyt.unyt_array(np.array((0.625, 0.625, 0.625)), 'cm') - else: - center = unyt.unyt_array(np.array((1.5, 1.5, 1.5)), 'cm') - bbox = unyt.unyt_array(np.array([[0., 3.], [0., 3.], [0., 3.]]), 'cm') - hsml_factor = 0.5 - unitrho = 1.5 - # test correct centering, particle selection - def makemasses(i, j, k): - if i == j == k == 1: - return 2. - else: - return 1. - # m / rho, factor 1. / hsml**2 is included in the kernel integral - # (density is adjusted, so same for center particle) - prefactor = 1. / unitrho #/ (0.5 * 0.5)**2 - dl_cen = integrate_kernel(cubicspline_python, 0., 0.25) - - # result shouldn't depend explicitly on the center if we re-center - # the data, unless we get cut-offs in the non-periodic case - ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, - periodic=periodic, - offsets=np.full(3, 0.5), - massgenerator=makemasses, - unitrho=unitrho, - bbox=bbox.v, - recenter=center.v) - if depth is None: - source = ds.all_data() - else: - depth = unyt.unyt_quantity(*depth) - le = np.array(ds.domain_left_edge) - re = np.array(ds.domain_right_edge) - le[axis] = center[axis] - 0.5 * depth - re[axis] = center[axis] + 0.5 * depth - cen = 0.5 * (le + re) - reg = YTRegion(center=cen, - left_edge=le, - right_edge=re, - ds=ds) - source = reg - - # we don't actually want a plot, it's just a straightforward, - # common way to get an frb / image array - if weighted: - toweight_field = ("gas", "density") - else: - toweight_field = None - prj = yt.ProjectionPlot(ds, axis, ("gas", "density"), - width=(2.5, "cm"), - weight_field=toweight_field, - buff_size=(5, 5), - center=center, - data_source=source) - img = prj.frb.data[('gas', 'density')] - if weighted: - expected_out = np.zeros((5, 5,), dtype=img.v.dtype) - expected_out[::2, ::2] = unitrho - if depth is None: - ## during shift, particle coords do wrap around edges - #if (not periodic) and shiftcenter: - # # weight 1. for unitrho, 2. for 2. * untrho - # expected_out[2, 2] *= 5. / 3. - #else: - # weight (2 * 1.) for unitrho, (1 * 2.) for 2. * unitrho - expected_out[2, 2] *= 1.5 - else: - # only 2 * unitrho element included - expected_out[2, 2] *= 2. - else: - expected_out = np.zeros((5, 5,), dtype=img.v.dtype) - expected_out[::2, ::2] = dl_cen * prefactor * unitrho - if depth is None: - # 3 particles per l.o.s., including the denser one - expected_out *= 3. - expected_out[2, 2] *= 4. / 3. - else: - # 1 particle per l.o.s., including the denser one - expected_out[2, 2] *= 2. - # grid is shifted to the left -> 'missing' stuff at the left - if (not periodic) and shiftcenter: - expected_out[:1, :] = 0. - expected_out[:, :1] = 0. - #print(axis, shiftcenter, depth, periodic, weighted) - #print(expected_out) - #print(img.v) - assert_rel_equal(expected_out, img.v, 5) - -@pytest.mark.parametrize("periodic", [True, False]) -@pytest.mark.parametrize("shiftcenter", [False, True]) -@pytest.mark.parametrize("zoff", [0., 0.1, 0.5, 1.]) -@pytest.mark.parametrize("axis", [0, 1, 2]) -def test_sph_slice_general_alongaxes(axis: int, - shiftcenter: bool, - periodic: bool, - zoff: float) -> None: - ''' - Particles at [0.5, 1.5, 2.5] (in each coordinate) - smoothing lengths 0.25 - all particles have mass 1., density 1.5, - except the single center particle, with mass 2., density 3. - - Parameters: - ----------- - axis: {0, 1, 2} - projection axis (aligned with sim. axis) - northvector: tuple - y-axis direction in the final plot (direction vector) - shiftcenter: bool - shift the coordinates to center the projection on. - (The grid is offset to this same center) - zoff: float - offset of the slice plane from the SPH particle center plane - periodic: bool - assume periodic boundary conditions, or not - - Returns: - -------- - None - ''' - if shiftcenter: - center = unyt.unyt_array(np.array((0.625, 0.625, 0.625)), 'cm') - else: - center = unyt.unyt_array(np.array((1.5, 1.5, 1.5)), 'cm') - bbox = unyt.unyt_array(np.array([[0., 3.], [0., 3.], [0., 3.]]), 'cm') - hsml_factor = 0.5 - unitrho = 1.5 - # test correct centering, particle selection - def makemasses(i, j, k): - if i == j == k == 1: - return 2. - elif i == j == k == 2: - return 3. - else: - return 1. - - # result shouldn't depend explicitly on the center if we re-center - # the data, unless we get cut-offs in the non-periodic case - ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, - periodic=periodic, - offsets=np.full(3, 0.5), - massgenerator=makemasses, - unitrho=unitrho, - bbox=bbox.v, - recenter=center.v) - ad = ds.all_data() - #print(ad[('gas', 'position')]) - outgridsize = 10 - width = 2.5 - _center = center.to("cm").v.copy() - _center[axis] += zoff - - # we don't actually want a plot, it's just a straightforward, - # common way to get an frb / image array - slc = yt.SlicePlot(ds, axis, ("gas", "density"), - width=(width, "cm"), - buff_size=(outgridsize,) * 2, - center=(_center, "cm")) - img = slc.frb.data[('gas', 'density')] - - # center is same in non-projection coords - if axis == 0: - ci = 1 - else: - ci = 0 - gridcens = _center[ci] - 0.5 * width \ - + 0.5 * width / outgridsize \ - + np.arange(outgridsize) * width / outgridsize - xgrid = np.repeat(gridcens, outgridsize) - ygrid = np.tile(gridcens, outgridsize) - zgrid = np.full(outgridsize**2, _center[axis]) - gridcoords = np.empty((outgridsize**2, 3), dtype=xgrid.dtype) - if axis == 2: - gridcoords[:, 0] = xgrid - gridcoords[:, 1] = ygrid - gridcoords[:, 2] = zgrid - elif axis == 0: - gridcoords[:, 0] = zgrid - gridcoords[:, 1] = xgrid - gridcoords[:, 2] = ygrid - elif axis == 1: - gridcoords[:, 0] = ygrid - gridcoords[:, 1] = zgrid - gridcoords[:, 2] = xgrid - ad = ds.all_data() - sphcoords = np.array([(ad[("gas", "x")]).to("cm"), - (ad[("gas", "y")]).to("cm"), - (ad[("gas", "z")]).to("cm"), - ]).T - print('sphcoords:') - print(sphcoords) - print('gridcoords:') - print(gridcoords) - dists = distancematrix(gridcoords, sphcoords, - periodic=(periodic,)*3, - periods=np.array([3., 3., 3.])) - print('dists <= 1:') - print(dists <= 1) - sml = (ad[("gas", "smoothing_length")]).to("cm") - normkern = cubicspline_python(dists / sml.v[np.newaxis, :]) - sphcontr = normkern / sml[np.newaxis, :]**3 * ad[("gas", "mass")] - contsum = np.sum(sphcontr, axis=1) - sphweights = normkern / sml[np.newaxis, :]**3 \ - * ad[("gas", "mass")] / ad[("gas", "density")] - weights = np.sum(sphweights, axis=1) - expected = contsum / weights - expected = expected.reshape((outgridsize, outgridsize)) - expected[np.isnan(expected)] = 0. # convention in the slices - - print('expected:\n', expected.v) - print('recovered:\n', img.v) - assert_rel_equal(expected.v, img.v, 5) - - - -@pytest.mark.parametrize("periodic", [True, False]) -@pytest.mark.parametrize("shiftcenter", [False, True]) -@pytest.mark.parametrize("northvector", [None, (1.e-4, 1., 0.)]) -@pytest.mark.parametrize("zoff", [0., 0.1, 0.5, 1.]) -def test_sph_slice_general_offaxis( - northvector: tuple[float, float, float] | None, - shiftcenter: bool, - zoff: float, - periodic: bool, - ) -> None: - ''' - Same as the on-axis slices, but we rotate the basis vectors - to test whether roations are handled ok. the rotation is chosen to - be small so that in/exclusion of particles within bboxes, etc. - works out the same way. - Particles at [0.5, 1.5, 2.5] (in each coordinate) - smoothing lengths 0.25 - all particles have mass 1., density 1.5, - except the single center particle, with mass 2., density 3. - - Parameters: - ----------- - northvector: tuple - y-axis direction in the final plot (direction vector) - shiftcenter: bool - shift the coordinates to center the projection on. - (The grid is offset to this same center) - zoff: float - offset of the slice plane from the SPH particle center plane - periodic: bool - assume periodic boundary conditions, or not - - Returns: - -------- - None - ''' - if shiftcenter: - center = np.array((0.625, 0.625, 0.625)) # cm - else: - center = np.array((1.5, 1.5, 1.5)) # cm - bbox = unyt.unyt_array(np.array([[0., 3.], [0., 3.], [0., 3.]]), 'cm') - hsml_factor = 0.5 - unitrho = 1.5 - # test correct centering, particle selection - def makemasses(i, j, k): - if i == j == k == 1: - return 2. - else: - return 1. - # try to make sure dl differences from periodic wrapping are small - epsilon = 1e-4 - projaxis = np.array([epsilon, 0.00, np.sqrt(1. - epsilon**2)]) - e1dir = projaxis / np.sqrt(np.sum(projaxis**2)) - if northvector is None: - e2dir = np.array([0., 1., 0.]) - else: - e2dir = np.asarray(northvector) - e2dir = e2dir - np.sum(e1dir * e2dir) * e2dir # orthonormalize - e2dir /= np.sqrt(np.sum(e2dir**2)) - e3dir = np.cross(e2dir, e1dir) - - outgridsize = 10 - width = 2.5 - _center = center.copy() - _center += zoff * e1dir - - ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, - periodic=periodic, - offsets=np.full(3, 0.5), - massgenerator=makemasses, - unitrho=unitrho, - bbox=bbox.v, - recenter=center, - e1hat=e1dir, e2hat=e2dir, e3hat=e3dir) - - source = ds.all_data() - # couple to dataset -> right unit registry - center = ds.arr(center, 'cm') - print('position:\n', source['gas','position']) - slc = yt.SlicePlot(ds, e1dir, ("gas", "density"), - width=(width, "cm"), - buff_size=(outgridsize,) * 2, - center=(_center, "cm"), - north_vector=e2dir) - img = slc.frb.data[('gas', 'density')] - - # center is same in x/y (e3dir/e2dir) - gridcenx = np.dot(_center, e3dir) - 0.5 * width \ - + 0.5 * width / outgridsize \ - + np.arange(outgridsize) * width / outgridsize - gridceny = np.dot(_center, e2dir) - 0.5 * width \ - + 0.5 * width / outgridsize \ - + np.arange(outgridsize) * width / outgridsize - xgrid = np.repeat(gridcenx, outgridsize) - ygrid = np.tile(gridceny, outgridsize) - zgrid = np.full(outgridsize**2, np.dot(_center, e1dir)) - gridcoords = (xgrid[:, np.newaxis] * e3dir[np.newaxis, :] - + ygrid[:, np.newaxis] * e2dir[np.newaxis, :] - + zgrid[:, np.newaxis] * e1dir[np.newaxis, :]) - print('gridcoords:') - print(gridcoords) - ad = ds.all_data() - sphcoords = np.array([(ad[("gas", "x")]).to("cm"), - (ad[("gas", "y")]).to("cm"), - (ad[("gas", "z")]).to("cm"), - ]).T - dists = distancematrix(gridcoords, sphcoords, - periodic=(periodic,)*3, - periods=np.array([3., 3., 3.])) - sml = (ad[("gas", "smoothing_length")]).to("cm") - normkern = cubicspline_python(dists / sml.v[np.newaxis, :]) - sphcontr = normkern / sml[np.newaxis, :]**3 * ad[("gas", "mass")] - contsum = np.sum(sphcontr, axis=1) - sphweights = normkern / sml[np.newaxis, :]**3 \ - * ad[("gas", "mass")] / ad[("gas", "density")] - weights = np.sum(sphweights, axis=1) - expected = contsum / weights - expected = expected.reshape((outgridsize, outgridsize)) - expected = expected.T # transposed for image plotting - expected[np.isnan(expected)] = 0. # convention in the slices - - #print(axis, shiftcenter, depth, periodic, weighted) - print('expected:\n', expected.v) - print('recovered:\n', img.v) - assert_rel_equal(expected.v, img.v, 4) - -# only axis-aligned; testing YTArbitraryGrid, YTCoveringGrid -@pytest.mark.parametrize("periodic", [True, False, (True, True, False)]) -@pytest.mark.parametrize("wholebox", [True, False]) -def test_sph_grid(periodic: bool | tuple[bool, bool, bool], - wholebox: bool): - bbox = np.array([[-1., 3.], [1., 5.2], [-1., 3.]]) - ds = fake_random_sph_ds(50, bbox, periodic=periodic) - - if not hasattr(periodic, "__len__"): - periodic = (periodic,) * 3 - - if wholebox: - left = bbox[:, 0].copy() - level = 2 - ncells = np.array([2**level] * 3) - print('left: ', left) - print('ncells: ', ncells) - resgrid = ds.covering_grid(level, tuple(left), ncells) - right = bbox[:, 1].copy() - xedges = np.linspace(left[0], right[0], ncells[0] + 1) - yedges = np.linspace(left[1], right[1], ncells[1] + 1) - zedges = np.linspace(left[2], right[2], ncells[2] + 1) - else: - left = np.array([-1., 1.8, -1.]) - right = np.array([2.5, 5.2, 2.5]) - ncells = np.array([3, 4, 4]) - resgrid = ds.arbitrary_grid(left, right, dims=ncells) - xedges = np.linspace(left[0], right[0], ncells[0] + 1) - yedges = np.linspace(left[1], right[1], ncells[1] + 1) - zedges = np.linspace(left[2], right[2], ncells[2] + 1) - res = resgrid["gas", "density"] - xcens = 0.5 * (xedges[:-1] + xedges[1:]) - ycens = 0.5 * (yedges[:-1] + yedges[1:]) - zcens = 0.5 * (zedges[:-1] + zedges[1:]) - - ad = ds.all_data() - sphcoords = np.array([(ad[("gas", "x")]).to("cm"), - (ad[("gas", "y")]).to("cm"), - (ad[("gas", "z")]).to("cm"), - ]).T - gridx, gridy, gridz = np.meshgrid(xcens, ycens, zcens, - indexing='ij') - outshape = gridx.shape - gridx = gridx.flatten() - gridy = gridy.flatten() - gridz = gridz.flatten() - gridcoords = np.array([gridx, gridy, gridz]).T - periods = bbox[:, 1] - bbox[:, 0] - dists = distancematrix(gridcoords, sphcoords, - periodic=periodic, - periods=periods) - sml = (ad[("gas", "smoothing_length")]).to("cm") - normkern = cubicspline_python(dists / sml.v[np.newaxis, :]) - sphcontr = normkern / sml[np.newaxis, :]**3 * ad[("gas", "mass")] - contsum = np.sum(sphcontr, axis=1) - sphweights = normkern / sml[np.newaxis, :]**3 \ - * ad[("gas", "mass")] / ad[("gas", "density")] - weights = np.sum(sphweights, axis=1) - expected = contsum / weights - expected = expected.reshape(outshape) - expected[np.isnan(expected)] = 0. # convention in the slices - - #print(axis, shiftcenter, depth, periodic, weighted) - print('expected:\n', expected.v) - print('recovered:\n', res.v) - assert_rel_equal(expected.v, res.v, 4) diff --git a/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py b/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py new file mode 100644 index 00000000000..4d299e2516f --- /dev/null +++ b/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py @@ -0,0 +1,459 @@ +import numpy as np +import pytest +import unyt + +import yt +from yt.data_objects.selection_objects.region import YTRegion +from yt.testing import ( + assert_rel_equal, + cubicspline_python, + distancematrix, + fake_random_sph_ds, + fake_sph_flexible_grid_ds, + integrate_kernel, +) + +@pytest.mark.parametrize("weighted", [True, False]) +@pytest.mark.parametrize("periodic", [True, False]) +@pytest.mark.parametrize("depth", [None, (1., "cm")]) +@pytest.mark.parametrize("shiftcenter", [False, True]) +@pytest.mark.parametrize("axis", [0, 1, 2]) +def test_sph_proj_general_alongaxes(axis: int, + shiftcenter: bool, + depth : float | None, + periodic: bool, + weighted: bool) -> None: + ''' + The previous projection tests were for a specific issue. + Here, we test more functionality of the projections. + We just send lines of sight through pixel centers for convenience. + Particles at [0.5, 1.5, 2.5] (in each coordinate) + smoothing lengths 0.25 + all particles have mass 1., density 1.5, + except the single center particle, with mass 2., density 3. + + Parameters: + ----------- + axis: {0, 1, 2} + projection axis (aligned with sim. axis) + shiftcenter: bool + shift the coordinates to center the projection on. + (The grid is offset to this same center) + depth: float or None + depth of the projection slice + periodic: bool + assume periodic boundary conditions, or not + weighted: bool + make a weighted projection (density-weighted density), or not + + Returns: + -------- + None + ''' + if shiftcenter: + center = unyt.unyt_array(np.array((0.625, 0.625, 0.625)), 'cm') + else: + center = unyt.unyt_array(np.array((1.5, 1.5, 1.5)), 'cm') + bbox = unyt.unyt_array(np.array([[0., 3.], [0., 3.], [0., 3.]]), 'cm') + hsml_factor = 0.5 + unitrho = 1.5 + # test correct centering, particle selection + def makemasses(i, j, k): + if i == j == k == 1: + return 2. + else: + return 1. + # m / rho, factor 1. / hsml**2 is included in the kernel integral + # (density is adjusted, so same for center particle) + prefactor = 1. / unitrho #/ (0.5 * 0.5)**2 + dl_cen = integrate_kernel(cubicspline_python, 0., 0.25) + + # result shouldn't depend explicitly on the center if we re-center + # the data, unless we get cut-offs in the non-periodic case + ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, + periodic=periodic, + offsets=np.full(3, 0.5), + massgenerator=makemasses, + unitrho=unitrho, + bbox=bbox.v, + recenter=center.v) + if depth is None: + source = ds.all_data() + else: + depth = unyt.unyt_quantity(*depth) + le = np.array(ds.domain_left_edge) + re = np.array(ds.domain_right_edge) + le[axis] = center[axis] - 0.5 * depth + re[axis] = center[axis] + 0.5 * depth + cen = 0.5 * (le + re) + reg = YTRegion(center=cen, + left_edge=le, + right_edge=re, + ds=ds) + source = reg + + # we don't actually want a plot, it's just a straightforward, + # common way to get an frb / image array + if weighted: + toweight_field = ("gas", "density") + else: + toweight_field = None + prj = yt.ProjectionPlot(ds, axis, ("gas", "density"), + width=(2.5, "cm"), + weight_field=toweight_field, + buff_size=(5, 5), + center=center, + data_source=source) + img = prj.frb.data[('gas', 'density')] + if weighted: + expected_out = np.zeros((5, 5,), dtype=img.v.dtype) + expected_out[::2, ::2] = unitrho + if depth is None: + ## during shift, particle coords do wrap around edges + #if (not periodic) and shiftcenter: + # # weight 1. for unitrho, 2. for 2. * untrho + # expected_out[2, 2] *= 5. / 3. + #else: + # weight (2 * 1.) for unitrho, (1 * 2.) for 2. * unitrho + expected_out[2, 2] *= 1.5 + else: + # only 2 * unitrho element included + expected_out[2, 2] *= 2. + else: + expected_out = np.zeros((5, 5,), dtype=img.v.dtype) + expected_out[::2, ::2] = dl_cen * prefactor * unitrho + if depth is None: + # 3 particles per l.o.s., including the denser one + expected_out *= 3. + expected_out[2, 2] *= 4. / 3. + else: + # 1 particle per l.o.s., including the denser one + expected_out[2, 2] *= 2. + # grid is shifted to the left -> 'missing' stuff at the left + if (not periodic) and shiftcenter: + expected_out[:1, :] = 0. + expected_out[:, :1] = 0. + #print(axis, shiftcenter, depth, periodic, weighted) + #print(expected_out) + #print(img.v) + assert_rel_equal(expected_out, img.v, 5) + +@pytest.mark.parametrize("periodic", [True, False]) +@pytest.mark.parametrize("shiftcenter", [False, True]) +@pytest.mark.parametrize("zoff", [0., 0.1, 0.5, 1.]) +@pytest.mark.parametrize("axis", [0, 1, 2]) +def test_sph_slice_general_alongaxes(axis: int, + shiftcenter: bool, + periodic: bool, + zoff: float) -> None: + ''' + Particles at [0.5, 1.5, 2.5] (in each coordinate) + smoothing lengths 0.25 + all particles have mass 1., density 1.5, + except the single center particle, with mass 2., density 3. + + Parameters: + ----------- + axis: {0, 1, 2} + projection axis (aligned with sim. axis) + northvector: tuple + y-axis direction in the final plot (direction vector) + shiftcenter: bool + shift the coordinates to center the projection on. + (The grid is offset to this same center) + zoff: float + offset of the slice plane from the SPH particle center plane + periodic: bool + assume periodic boundary conditions, or not + + Returns: + -------- + None + ''' + if shiftcenter: + center = unyt.unyt_array(np.array((0.625, 0.625, 0.625)), 'cm') + else: + center = unyt.unyt_array(np.array((1.5, 1.5, 1.5)), 'cm') + bbox = unyt.unyt_array(np.array([[0., 3.], [0., 3.], [0., 3.]]), 'cm') + hsml_factor = 0.5 + unitrho = 1.5 + # test correct centering, particle selection + def makemasses(i, j, k): + if i == j == k == 1: + return 2. + elif i == j == k == 2: + return 3. + else: + return 1. + + # result shouldn't depend explicitly on the center if we re-center + # the data, unless we get cut-offs in the non-periodic case + ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, + periodic=periodic, + offsets=np.full(3, 0.5), + massgenerator=makemasses, + unitrho=unitrho, + bbox=bbox.v, + recenter=center.v) + ad = ds.all_data() + #print(ad[('gas', 'position')]) + outgridsize = 10 + width = 2.5 + _center = center.to("cm").v.copy() + _center[axis] += zoff + + # we don't actually want a plot, it's just a straightforward, + # common way to get an frb / image array + slc = yt.SlicePlot(ds, axis, ("gas", "density"), + width=(width, "cm"), + buff_size=(outgridsize,) * 2, + center=(_center, "cm")) + img = slc.frb.data[('gas', 'density')] + + # center is same in non-projection coords + if axis == 0: + ci = 1 + else: + ci = 0 + gridcens = _center[ci] - 0.5 * width \ + + 0.5 * width / outgridsize \ + + np.arange(outgridsize) * width / outgridsize + xgrid = np.repeat(gridcens, outgridsize) + ygrid = np.tile(gridcens, outgridsize) + zgrid = np.full(outgridsize**2, _center[axis]) + gridcoords = np.empty((outgridsize**2, 3), dtype=xgrid.dtype) + if axis == 2: + gridcoords[:, 0] = xgrid + gridcoords[:, 1] = ygrid + gridcoords[:, 2] = zgrid + elif axis == 0: + gridcoords[:, 0] = zgrid + gridcoords[:, 1] = xgrid + gridcoords[:, 2] = ygrid + elif axis == 1: + gridcoords[:, 0] = ygrid + gridcoords[:, 1] = zgrid + gridcoords[:, 2] = xgrid + ad = ds.all_data() + sphcoords = np.array([(ad[("gas", "x")]).to("cm"), + (ad[("gas", "y")]).to("cm"), + (ad[("gas", "z")]).to("cm"), + ]).T + print('sphcoords:') + print(sphcoords) + print('gridcoords:') + print(gridcoords) + dists = distancematrix(gridcoords, sphcoords, + periodic=(periodic,)*3, + periods=np.array([3., 3., 3.])) + print('dists <= 1:') + print(dists <= 1) + sml = (ad[("gas", "smoothing_length")]).to("cm") + normkern = cubicspline_python(dists / sml.v[np.newaxis, :]) + sphcontr = normkern / sml[np.newaxis, :]**3 * ad[("gas", "mass")] + contsum = np.sum(sphcontr, axis=1) + sphweights = normkern / sml[np.newaxis, :]**3 \ + * ad[("gas", "mass")] / ad[("gas", "density")] + weights = np.sum(sphweights, axis=1) + expected = contsum / weights + expected = expected.reshape((outgridsize, outgridsize)) + expected[np.isnan(expected)] = 0. # convention in the slices + + print('expected:\n', expected.v) + print('recovered:\n', img.v) + assert_rel_equal(expected.v, img.v, 5) + + + +@pytest.mark.parametrize("periodic", [True, False]) +@pytest.mark.parametrize("shiftcenter", [False, True]) +@pytest.mark.parametrize("northvector", [None, (1.e-4, 1., 0.)]) +@pytest.mark.parametrize("zoff", [0., 0.1, 0.5, 1.]) +def test_sph_slice_general_offaxis( + northvector: tuple[float, float, float] | None, + shiftcenter: bool, + zoff: float, + periodic: bool, + ) -> None: + ''' + Same as the on-axis slices, but we rotate the basis vectors + to test whether roations are handled ok. the rotation is chosen to + be small so that in/exclusion of particles within bboxes, etc. + works out the same way. + Particles at [0.5, 1.5, 2.5] (in each coordinate) + smoothing lengths 0.25 + all particles have mass 1., density 1.5, + except the single center particle, with mass 2., density 3. + + Parameters: + ----------- + northvector: tuple + y-axis direction in the final plot (direction vector) + shiftcenter: bool + shift the coordinates to center the projection on. + (The grid is offset to this same center) + zoff: float + offset of the slice plane from the SPH particle center plane + periodic: bool + assume periodic boundary conditions, or not + + Returns: + -------- + None + ''' + if shiftcenter: + center = np.array((0.625, 0.625, 0.625)) # cm + else: + center = np.array((1.5, 1.5, 1.5)) # cm + bbox = unyt.unyt_array(np.array([[0., 3.], [0., 3.], [0., 3.]]), 'cm') + hsml_factor = 0.5 + unitrho = 1.5 + # test correct centering, particle selection + def makemasses(i, j, k): + if i == j == k == 1: + return 2. + else: + return 1. + # try to make sure dl differences from periodic wrapping are small + epsilon = 1e-4 + projaxis = np.array([epsilon, 0.00, np.sqrt(1. - epsilon**2)]) + e1dir = projaxis / np.sqrt(np.sum(projaxis**2)) + if northvector is None: + e2dir = np.array([0., 1., 0.]) + else: + e2dir = np.asarray(northvector) + e2dir = e2dir - np.sum(e1dir * e2dir) * e2dir # orthonormalize + e2dir /= np.sqrt(np.sum(e2dir**2)) + e3dir = np.cross(e2dir, e1dir) + + outgridsize = 10 + width = 2.5 + _center = center.copy() + _center += zoff * e1dir + + ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, + periodic=periodic, + offsets=np.full(3, 0.5), + massgenerator=makemasses, + unitrho=unitrho, + bbox=bbox.v, + recenter=center, + e1hat=e1dir, e2hat=e2dir, e3hat=e3dir) + + source = ds.all_data() + # couple to dataset -> right unit registry + center = ds.arr(center, 'cm') + print('position:\n', source['gas','position']) + slc = yt.SlicePlot(ds, e1dir, ("gas", "density"), + width=(width, "cm"), + buff_size=(outgridsize,) * 2, + center=(_center, "cm"), + north_vector=e2dir) + img = slc.frb.data[('gas', 'density')] + + # center is same in x/y (e3dir/e2dir) + gridcenx = np.dot(_center, e3dir) - 0.5 * width \ + + 0.5 * width / outgridsize \ + + np.arange(outgridsize) * width / outgridsize + gridceny = np.dot(_center, e2dir) - 0.5 * width \ + + 0.5 * width / outgridsize \ + + np.arange(outgridsize) * width / outgridsize + xgrid = np.repeat(gridcenx, outgridsize) + ygrid = np.tile(gridceny, outgridsize) + zgrid = np.full(outgridsize**2, np.dot(_center, e1dir)) + gridcoords = (xgrid[:, np.newaxis] * e3dir[np.newaxis, :] + + ygrid[:, np.newaxis] * e2dir[np.newaxis, :] + + zgrid[:, np.newaxis] * e1dir[np.newaxis, :]) + print('gridcoords:') + print(gridcoords) + ad = ds.all_data() + sphcoords = np.array([(ad[("gas", "x")]).to("cm"), + (ad[("gas", "y")]).to("cm"), + (ad[("gas", "z")]).to("cm"), + ]).T + dists = distancematrix(gridcoords, sphcoords, + periodic=(periodic,)*3, + periods=np.array([3., 3., 3.])) + sml = (ad[("gas", "smoothing_length")]).to("cm") + normkern = cubicspline_python(dists / sml.v[np.newaxis, :]) + sphcontr = normkern / sml[np.newaxis, :]**3 * ad[("gas", "mass")] + contsum = np.sum(sphcontr, axis=1) + sphweights = normkern / sml[np.newaxis, :]**3 \ + * ad[("gas", "mass")] / ad[("gas", "density")] + weights = np.sum(sphweights, axis=1) + expected = contsum / weights + expected = expected.reshape((outgridsize, outgridsize)) + expected = expected.T # transposed for image plotting + expected[np.isnan(expected)] = 0. # convention in the slices + + #print(axis, shiftcenter, depth, periodic, weighted) + print('expected:\n', expected.v) + print('recovered:\n', img.v) + assert_rel_equal(expected.v, img.v, 4) + +# only axis-aligned; testing YTArbitraryGrid, YTCoveringGrid +@pytest.mark.parametrize("periodic", [True, False, (True, True, False)]) +@pytest.mark.parametrize("wholebox", [True, False]) +def test_sph_grid(periodic: bool | tuple[bool, bool, bool], + wholebox: bool): + bbox = np.array([[-1., 3.], [1., 5.2], [-1., 3.]]) + ds = fake_random_sph_ds(50, bbox, periodic=periodic) + + if not hasattr(periodic, "__len__"): + periodic = (periodic,) * 3 + + if wholebox: + left = bbox[:, 0].copy() + level = 2 + ncells = np.array([2**level] * 3) + print('left: ', left) + print('ncells: ', ncells) + resgrid = ds.covering_grid(level, tuple(left), ncells) + right = bbox[:, 1].copy() + xedges = np.linspace(left[0], right[0], ncells[0] + 1) + yedges = np.linspace(left[1], right[1], ncells[1] + 1) + zedges = np.linspace(left[2], right[2], ncells[2] + 1) + else: + left = np.array([-1., 1.8, -1.]) + right = np.array([2.5, 5.2, 2.5]) + ncells = np.array([3, 4, 4]) + resgrid = ds.arbitrary_grid(left, right, dims=ncells) + xedges = np.linspace(left[0], right[0], ncells[0] + 1) + yedges = np.linspace(left[1], right[1], ncells[1] + 1) + zedges = np.linspace(left[2], right[2], ncells[2] + 1) + res = resgrid["gas", "density"] + xcens = 0.5 * (xedges[:-1] + xedges[1:]) + ycens = 0.5 * (yedges[:-1] + yedges[1:]) + zcens = 0.5 * (zedges[:-1] + zedges[1:]) + + ad = ds.all_data() + sphcoords = np.array([(ad[("gas", "x")]).to("cm"), + (ad[("gas", "y")]).to("cm"), + (ad[("gas", "z")]).to("cm"), + ]).T + gridx, gridy, gridz = np.meshgrid(xcens, ycens, zcens, + indexing='ij') + outshape = gridx.shape + gridx = gridx.flatten() + gridy = gridy.flatten() + gridz = gridz.flatten() + gridcoords = np.array([gridx, gridy, gridz]).T + periods = bbox[:, 1] - bbox[:, 0] + dists = distancematrix(gridcoords, sphcoords, + periodic=periodic, + periods=periods) + sml = (ad[("gas", "smoothing_length")]).to("cm") + normkern = cubicspline_python(dists / sml.v[np.newaxis, :]) + sphcontr = normkern / sml[np.newaxis, :]**3 * ad[("gas", "mass")] + contsum = np.sum(sphcontr, axis=1) + sphweights = normkern / sml[np.newaxis, :]**3 \ + * ad[("gas", "mass")] / ad[("gas", "density")] + weights = np.sum(sphweights, axis=1) + expected = contsum / weights + expected = expected.reshape(outshape) + expected[np.isnan(expected)] = 0. # convention in the slices + + #print(axis, shiftcenter, depth, periodic, weighted) + print('expected:\n', expected.v) + print('recovered:\n', res.v) + assert_rel_equal(expected.v, res.v, 4) diff --git a/yt/visualization/tests/test_offaxisprojection.py b/yt/visualization/tests/test_offaxisprojection.py index 7b7f7e6966d..d67fe4c4c57 100644 --- a/yt/visualization/tests/test_offaxisprojection.py +++ b/yt/visualization/tests/test_offaxisprojection.py @@ -5,23 +5,18 @@ import unittest import numpy as np -import pytest -import unyt from numpy.testing import assert_equal from yt.testing import ( assert_fname, assert_rel_equal, - cubicspline_python, fake_octree_ds, fake_random_ds, - fake_sph_flexible_grid_ds, - integrate_kernel, ) from yt.visualization.api import ( OffAxisProjectionPlot, OffAxisSlicePlot, - ProjectionPlot) +) from yt.visualization.image_writer import write_projection from yt.visualization.volume_rendering.api import off_axis_projection @@ -270,138 +265,3 @@ def _vlos_sq(field, data): p1res[setzero] = 0. p2res = p2.frb["gas", "velocity_los"] assert_rel_equal(p1res, p2res, 10) - - -@pytest.mark.parametrize("weighted", [True, False]) -@pytest.mark.parametrize("periodic", [True, False]) -@pytest.mark.parametrize("depth", [None, (1., "cm"), (0.5, "cm")]) -@pytest.mark.parametrize("shiftcenter", [False, True]) -@pytest.mark.parametrize("northvector", [None, (1.e-4, 1., 0.)]) -def test_sph_proj_general_offaxis( - northvector: tuple[float, float, float] | None, - shiftcenter: bool, - depth: tuple[float, str] | None, - periodic: bool, - weighted: bool) -> None: - ''' - Same as the on-axis projections, but we rotate the basis vectors - to test whether roations are handled ok. the rotation is chosen to - be small so that in/exclusion of particles within bboxes, etc. - works out the same way. - We just send lines of sight through pixel centers for convenience. - Particles at [0.5, 1.5, 2.5] (in each coordinate) - smoothing lengths 0.25 - all particles have mass 1., density 1.5, - except the single center particle, with mass 2., density 3. - - Parameters: - ----------- - northvector: tuple - y-axis direction in the final plot (direction vector) - shiftcenter: bool - shift the coordinates to center the projection on. - (The grid is offset to this same center) - depth: float or None - depth of the projection slice - periodic: bool - assume periodic boundary conditions, or not - weighted: bool - make a weighted projection (density-weighted density), or not - - Returns: - -------- - None - ''' - if shiftcenter: - center = np.array((0.625, 0.625, 0.625)) # cm - else: - center = np.array((1.5, 1.5, 1.5)) # cm - bbox = unyt.unyt_array(np.array([[0., 3.], [0., 3.], [0., 3.]]), 'cm') - hsml_factor = 0.5 - unitrho = 1.5 - # test correct centering, particle selection - def makemasses(i, j, k): - if i == j == k == 1: - return 2. - else: - return 1. - #dl_shift = _integrate_kernel(_cubicspline_python, - # np.sqrt(2) * 0.125, 0.25) - - # result shouldn't depend explicitly on the center if we re-center - # the data, unless we get cut-offs in the non-periodic case - # *almost* the z-axis - # try to make sure dl differences from periodic wrapping are small - epsilon = 1e-4 - projaxis = np.array([epsilon, 0.00, np.sqrt(1. - epsilon**2)]) - e1dir = projaxis / np.sqrt(np.sum(projaxis**2)) - # TODO: figure out other (default) axes for basis vectors here - if northvector is None: - e2dir = np.array([0., 1., 0.]) - else: - e2dir = np.asarray(northvector) - e2dir = e2dir - np.sum(e1dir * e2dir) * e2dir # orthonormalize - e2dir /= np.sqrt(np.sum(e2dir**2)) - e3dir = np.cross(e1dir, e2dir) - - ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, - periodic=periodic, - offsets=np.full(3, 0.5), - massgenerator=makemasses, - unitrho=unitrho, - bbox=bbox.v, - recenter=center, - e1hat=e1dir, e2hat=e2dir, e3hat=e3dir) - - source = ds.all_data() - # couple to dataset -> right unit registry - center = ds.arr(center, 'cm') - #print('position:\n', source['gas','position']) - - # m / rho, factor 1. / hsml**2 is included in the kernel integral - # (density is adjusted, so same for center particle) - prefactor = 1. / unitrho #/ (0.5 * 0.5)**2 - dl_cen = integrate_kernel(cubicspline_python, 0., 0.25) - - if weighted: - toweight_field = ("gas", "density") - else: - toweight_field = None - # we don't actually want a plot, it's just a straightforward, - # common way to get an frb / image array - prj = ProjectionPlot(ds, projaxis, ("gas", "density"), - width=(2.5, "cm"), - weight_field=toweight_field, - buff_size=(5, 5), - center=center, - data_source=source, - north_vector=northvector, - depth=depth) - img = prj.frb.data[('gas', 'density')] - if weighted: - # periodic shifts will modify the (relative) dl values a bit - expected_out = np.zeros((5, 5,), dtype=img.v.dtype) - expected_out[::2, ::2] = unitrho - if depth is None: - expected_out[2, 2] *= 1.5 - else: - # only 2 * unitrho element included - expected_out[2, 2] *= 2. - else: - expected_out = np.zeros((5, 5,), dtype=img.v.dtype) - expected_out[::2, ::2] = dl_cen * prefactor * unitrho - if depth is None: - # 3 particles per l.o.s., including the denser one - expected_out *= 3. - expected_out[2, 2] *= 4. / 3. - else: - # 1 particle per l.o.s., including the denser one - expected_out[2, 2] *= 2. - # grid is shifted to the left -> 'missing' stuff at the left - if (not periodic) and shiftcenter: - expected_out[:1, :] = 0. - expected_out[:, :1] = 0. - #print(axis, shiftcenter, depth, periodic, weighted) - print('expected:\n', expected_out) - print('recovered:\n', img.v) - assert_rel_equal(expected_out, img.v, 4) diff --git a/yt/visualization/tests/test_offaxisprojection_pytestonly.py b/yt/visualization/tests/test_offaxisprojection_pytestonly.py new file mode 100644 index 00000000000..1a252314ba4 --- /dev/null +++ b/yt/visualization/tests/test_offaxisprojection_pytestonly.py @@ -0,0 +1,143 @@ +import numpy as np +import pytest +import unyt + +from yt.testing import ( + assert_rel_equal, + cubicspline_python, + fake_sph_flexible_grid_ds, + integrate_kernel, +) +from yt.visualization.api import ProjectionPlot + +@pytest.mark.parametrize("weighted", [True, False]) +@pytest.mark.parametrize("periodic", [True, False]) +@pytest.mark.parametrize("depth", [None, (1., "cm"), (0.5, "cm")]) +@pytest.mark.parametrize("shiftcenter", [False, True]) +@pytest.mark.parametrize("northvector", [None, (1.e-4, 1., 0.)]) +def test_sph_proj_general_offaxis( + northvector: tuple[float, float, float] | None, + shiftcenter: bool, + depth: tuple[float, str] | None, + periodic: bool, + weighted: bool) -> None: + ''' + Same as the on-axis projections, but we rotate the basis vectors + to test whether roations are handled ok. the rotation is chosen to + be small so that in/exclusion of particles within bboxes, etc. + works out the same way. + We just send lines of sight through pixel centers for convenience. + Particles at [0.5, 1.5, 2.5] (in each coordinate) + smoothing lengths 0.25 + all particles have mass 1., density 1.5, + except the single center particle, with mass 2., density 3. + + Parameters: + ----------- + northvector: tuple + y-axis direction in the final plot (direction vector) + shiftcenter: bool + shift the coordinates to center the projection on. + (The grid is offset to this same center) + depth: float or None + depth of the projection slice + periodic: bool + assume periodic boundary conditions, or not + weighted: bool + make a weighted projection (density-weighted density), or not + + Returns: + -------- + None + ''' + if shiftcenter: + center = np.array((0.625, 0.625, 0.625)) # cm + else: + center = np.array((1.5, 1.5, 1.5)) # cm + bbox = unyt.unyt_array(np.array([[0., 3.], [0., 3.], [0., 3.]]), 'cm') + hsml_factor = 0.5 + unitrho = 1.5 + # test correct centering, particle selection + def makemasses(i, j, k): + if i == j == k == 1: + return 2. + else: + return 1. + + # result shouldn't depend explicitly on the center if we re-center + # the data, unless we get cut-offs in the non-periodic case + # *almost* the z-axis + # try to make sure dl differences from periodic wrapping are small + epsilon = 1e-4 + projaxis = np.array([epsilon, 0.00, np.sqrt(1. - epsilon**2)]) + e1dir = projaxis / np.sqrt(np.sum(projaxis**2)) + # TODO: figure out other (default) axes for basis vectors here + if northvector is None: + e2dir = np.array([0., 1., 0.]) + else: + e2dir = np.asarray(northvector) + e2dir = e2dir - np.sum(e1dir * e2dir) * e2dir # orthonormalize + e2dir /= np.sqrt(np.sum(e2dir**2)) + e3dir = np.cross(e1dir, e2dir) + + ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, + periodic=periodic, + offsets=np.full(3, 0.5), + massgenerator=makemasses, + unitrho=unitrho, + bbox=bbox.v, + recenter=center, + e1hat=e1dir, e2hat=e2dir, e3hat=e3dir) + + source = ds.all_data() + # couple to dataset -> right unit registry + center = ds.arr(center, 'cm') + #print('position:\n', source['gas','position']) + + # m / rho, factor 1. / hsml**2 is included in the kernel integral + # (density is adjusted, so same for center particle) + prefactor = 1. / unitrho #/ (0.5 * 0.5)**2 + dl_cen = integrate_kernel(cubicspline_python, 0., 0.25) + + if weighted: + toweight_field = ("gas", "density") + else: + toweight_field = None + # we don't actually want a plot, it's just a straightforward, + # common way to get an frb / image array + prj = ProjectionPlot(ds, projaxis, ("gas", "density"), + width=(2.5, "cm"), + weight_field=toweight_field, + buff_size=(5, 5), + center=center, + data_source=source, + north_vector=northvector, + depth=depth) + img = prj.frb.data[('gas', 'density')] + if weighted: + # periodic shifts will modify the (relative) dl values a bit + expected_out = np.zeros((5, 5,), dtype=img.v.dtype) + expected_out[::2, ::2] = unitrho + if depth is None: + expected_out[2, 2] *= 1.5 + else: + # only 2 * unitrho element included + expected_out[2, 2] *= 2. + else: + expected_out = np.zeros((5, 5,), dtype=img.v.dtype) + expected_out[::2, ::2] = dl_cen * prefactor * unitrho + if depth is None: + # 3 particles per l.o.s., including the denser one + expected_out *= 3. + expected_out[2, 2] *= 4. / 3. + else: + # 1 particle per l.o.s., including the denser one + expected_out[2, 2] *= 2. + # grid is shifted to the left -> 'missing' stuff at the left + if (not periodic) and shiftcenter: + expected_out[:1, :] = 0. + expected_out[:, :1] = 0. + #print(axis, shiftcenter, depth, periodic, weighted) + print('expected:\n', expected_out) + print('recovered:\n', img.v) + assert_rel_equal(expected_out, img.v, 4) From fa78f5c95d0bf4abeed6c24157ad7d60bf1909b5 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Thu, 18 Jul 2024 14:33:06 -0500 Subject: [PATCH 35/64] remove trailing whitespace --- .../analyzing/generating_processed_data.rst | 4 +- .../coordinates/cartesian_coordinates.py | 28 ++++---- .../tests/test_sph_pixelization.py | 58 ++++++++-------- .../tests/test_sph_pixelization_pytestonly.py | 68 +++++++++---------- yt/testing.py | 52 +++++++------- yt/utilities/lib/pixelization_routines.pyx | 68 +++++++++---------- yt/visualization/plot_window.py | 14 ++-- .../tests/test_offaxisprojection.py | 10 +-- .../test_offaxisprojection_pytestonly.py | 32 ++++----- .../volume_rendering/off_axis_projection.py | 16 ++--- 10 files changed, 175 insertions(+), 175 deletions(-) diff --git a/doc/source/analyzing/generating_processed_data.rst b/doc/source/analyzing/generating_processed_data.rst index 5f7866b4cab..5f748d9e259 100644 --- a/doc/source/analyzing/generating_processed_data.rst +++ b/doc/source/analyzing/generating_processed_data.rst @@ -100,8 +100,8 @@ this, see :ref:`saving-grid-data-containers`. In the FITS case, there is an option for setting the ``units`` of the coordinate system in the file. If you want to overwrite a file with the same name, set ``clobber=True``. -The :class:`~yt.visualization.fixed_resolution.FixedResolutionBuffer` -(and its +The :class:`~yt.visualization.fixed_resolution.FixedResolutionBuffer` +(and its :class:`~yt.visualization.fixed_resolution.OffAxisProjectionFixedResolutionBuffer` subclass) can even be exported as a 2D dataset itself, which may be operated on in the same way as any other dataset in yt: diff --git a/yt/geometry/coordinates/cartesian_coordinates.py b/yt/geometry/coordinates/cartesian_coordinates.py index 96f43e0c4d7..bf471b2b58d 100644 --- a/yt/geometry/coordinates/cartesian_coordinates.py +++ b/yt/geometry/coordinates/cartesian_coordinates.py @@ -370,14 +370,14 @@ def _ortho_pixelize( ptype = data_source.ds._sph_ptypes[0] px_name = self.axis_name[self.x_axis[dim]] py_name = self.axis_name[self.y_axis[dim]] - # need z coordinates for depth, + # need z coordinates for depth, # but name isn't saved in the handler -> use the 'other one' pz_name = list(set(self.axis_order) - {px_name, py_name})[0] # ignore default True periodic argument - # (not actually supplied by a call from + # (not actually supplied by a call from # FixedResolutionBuffer), and use the dataset periodicity - # instead + # instead xa = self.x_axis[dim] ya = self.y_axis[dim] #axorder = data_source.ds.coordinates.axis_order @@ -422,10 +422,10 @@ def _ortho_pixelize( data_source=data_source.data_source, ) proj_reg.set_field_parameter("axis", data_source.axis) - # need some z bounds for SPH projection + # need some z bounds for SPH projection # -> use source bounds bnds3 = bnds + [le[za], re[za]] - + buff = np.zeros(size, dtype="float64") mask_uint8 = np.zeros_like(buff, dtype="uint8") if weight is None: @@ -537,7 +537,7 @@ def _ortho_pixelize( mask_uint8 = np.zeros_like(buff, dtype="uint8") if normalize: buff_den = np.zeros(size, dtype="float64") - + for chunk in data_source.chunks([], "io"): hsmlname = "smoothing_length" pixelize_sph_kernel_slice( @@ -576,7 +576,7 @@ def _ortho_pixelize( if normalize: normalization_2d_utility(buff, buff_den) - + mask = mask_uint8.astype("bool", copy=False) if smoothing_style == "gather": @@ -681,9 +681,9 @@ def _oblique_pixelize(self, data_source, field, bounds, size, antialias): is_sph_field = _finfo.is_sph_field particle_datasets = (ParticleDataset, StreamParticlesDataset) #finfo = self.ds._get_field_info(field) - - # SPH data - # only for slices: a function in off_axis_projection.py + + # SPH data + # only for slices: a function in off_axis_projection.py # handles projections if isinstance(data_source.ds, particle_datasets) and is_sph_field \ and isinstance(data_source, YTCuttingPlane): @@ -706,9 +706,9 @@ def _oblique_pixelize(self, data_source, field, bounds, size, antialias): if kernel_name is None: kernel_name = "cubic" # data_source should be a YTCuttingPlane object - # dimensionless unyt normal/north + # dimensionless unyt normal/north # -> numpy array cython can deal with - normal_vector = data_source.normal.v + normal_vector = data_source.normal.v north_vector = data_source._y_vec.v center = data_source.center.to("code_length") @@ -716,7 +716,7 @@ def _oblique_pixelize(self, data_source, field, bounds, size, antialias): mask_uint8 = np.zeros_like(buff, dtype="uint8") if normalize: buff_den = np.zeros(size, dtype="float64") - + for chunk in data_source.chunks([], "io"): pixelize_sph_kernel_cutting( buff, @@ -759,7 +759,7 @@ def _oblique_pixelize(self, data_source, field, bounds, size, antialias): # swap axes for image plotting mask = mask.swapaxes(0, 1) buff = buff.swapaxes(0, 1) - + # whatever other data this code could handle before the # SPH option was added else: diff --git a/yt/geometry/coordinates/tests/test_sph_pixelization.py b/yt/geometry/coordinates/tests/test_sph_pixelization.py index da5c9984bff..3ae3e9ee3dc 100644 --- a/yt/geometry/coordinates/tests/test_sph_pixelization.py +++ b/yt/geometry/coordinates/tests/test_sph_pixelization.py @@ -2,7 +2,7 @@ import yt from yt.testing import ( - assert_rel_equal, + assert_rel_equal, cubicspline_python, fake_sph_flexible_grid_ds, integrate_kernel, @@ -55,17 +55,17 @@ def test_sph_projection_basic1(): particles at 0.5, 1.5, 2.5 ''' bbox = np.array([[0., 3.]] * 3) - ds = fake_sph_flexible_grid_ds(hsml_factor=1.0, nperside=3, + ds = fake_sph_flexible_grid_ds(hsml_factor=1.0, nperside=3, bbox=bbox) # works, but no depth control (at least without specific filters) - proj = ds.proj(("gas", "density"), 2) - frb = proj.to_frb(width=(2.5, 'cm'), + proj = ds.proj(("gas", "density"), 2) + frb = proj.to_frb(width=(2.5, 'cm'), resolution=(5, 5), height=(2.5, 'cm'), - center=np.array([1.5, 1.5, 1.5]), + center=np.array([1.5, 1.5, 1.5]), periodic=False) out = frb.get_image(('gas', 'density')) - + expected_out = np.zeros((5, 5), dtype=np.float64) dl_1part = integrate_kernel(cubicspline_python, 0., 0.5) linedens_1part = dl_1part * 1. # unit mass, density @@ -84,18 +84,18 @@ def test_sph_projection_basic2(): other pixels are still zero. ''' bbox = np.array([[0., 3.]] * 3) - ds = fake_sph_flexible_grid_ds(hsml_factor=0.5, nperside=3, + ds = fake_sph_flexible_grid_ds(hsml_factor=0.5, nperside=3, bbox=bbox) - proj = ds.proj(("gas", "density"), 2) - frb = proj.to_frb(width=(2.5, 'cm'), + proj = ds.proj(("gas", "density"), 2) + frb = proj.to_frb(width=(2.5, 'cm'), resolution=(5, 5), height=(2.5, 'cm'), - center=np.array([1.375, 1.375, 1.5]), + center=np.array([1.375, 1.375, 1.5]), periodic=False) out = frb.get_image(('gas', 'density')) - + expected_out = np.zeros((5, 5), dtype=np.float64) - dl_1part = integrate_kernel(cubicspline_python, + dl_1part = integrate_kernel(cubicspline_python, np.sqrt(2) * 0.125, 0.25) linedens_1part = dl_1part * 1. # unit mass, density @@ -109,7 +109,7 @@ def test_sph_projection_basic2(): def get_dataset_sphrefine(reflevel: int = 1): ''' - constant density particle grid, + constant density particle grid, with increasing particle sampling ''' lenfact = (1./3.)**(reflevel - 1) @@ -122,42 +122,42 @@ def get_dataset_sphrefine(reflevel: int = 1): hsml_factor = lenfact bbox = np.array([[0., 3.]] * 3) offsets = np.ones(3, dtype=np.float64) * 0.5 # in units of ehat - + def refmass(i: int, j: int, k: int) -> float: return massfact unitrho = 1. / massfact # want density 1 for decreasing mass - ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, + ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=nperside, - periodic=True, + periodic=True, e1hat=e1hat, - e2hat=e2hat, + e2hat=e2hat, e3hat=e3hat, offsets=offsets, massgenerator=refmass, unitrho=unitrho, - bbox=bbox, + bbox=bbox, ) - return ds + return ds def getdata_test_gridproj2(): # initial pixel centers at 0.5, 1., 1.5, 2., 2.5 # particles at 0.5, 1.5, 2.5 - # refine particle grid, check if pixel values remain the + # refine particle grid, check if pixel values remain the # same in the pixels passing through initial particle centers outlist = [] dss = [] for rl in range(1, 4): ds = get_dataset_sphrefine(reflevel=rl) - proj = ds.proj(("gas", "density"), 2) - frb = proj.to_frb(width=(2.5, 'cm'), + proj = ds.proj(("gas", "density"), 2) + frb = proj.to_frb(width=(2.5, 'cm'), resolution=(5, 5), height=(2.5, 'cm'), - center=np.array([1.5, 1.5, 1.5]), + center=np.array([1.5, 1.5, 1.5]), periodic=False) out = frb.get_image(('gas', 'density')) outlist.append(out) - dss.append(ds) + dss.append(ds) return outlist, dss def test_sph_gridproj_reseffect1(): @@ -179,16 +179,16 @@ def test_sph_gridproj_reseffect2(): refine the pixel grid instead of the particle grid ''' ds = get_dataset_sphrefine(reflevel=2) - proj = ds.proj(("gas", "density"), 2) + proj = ds.proj(("gas", "density"), 2) imgs = {} maxrl = 5 for rl in range(1, maxrl + 1): npix = 1 + 2**(rl + 1) margin = 0.5 - 0.5**(rl + 1) - frb = proj.to_frb(width=(3. - 2. * margin, 'cm'), + frb = proj.to_frb(width=(3. - 2. * margin, 'cm'), resolution=(npix, npix), height=(3. - 2. * margin, 'cm'), - center=np.array([1.5, 1.5, 1.5]), + center=np.array([1.5, 1.5, 1.5]), periodic=False) out = frb.get_image(('gas', 'density')) imgs[rl] = out @@ -197,6 +197,6 @@ def test_sph_gridproj_reseffect2(): for rl in imgs: img = imgs[rl] pixspace = 2**(rl) - #print(f'Grid refinement level {rl}:') - assert_rel_equal(img[::pixspace, ::pixspace], + #print(f'Grid refinement level {rl}:') + assert_rel_equal(img[::pixspace, ::pixspace], ref[::pixspace_ref, ::pixspace_ref], 4) diff --git a/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py b/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py index 4d299e2516f..af95fde0345 100644 --- a/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py +++ b/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py @@ -5,7 +5,7 @@ import yt from yt.data_objects.selection_objects.region import YTRegion from yt.testing import ( - assert_rel_equal, + assert_rel_equal, cubicspline_python, distancematrix, fake_random_sph_ds, @@ -24,20 +24,20 @@ def test_sph_proj_general_alongaxes(axis: int, periodic: bool, weighted: bool) -> None: ''' - The previous projection tests were for a specific issue. + The previous projection tests were for a specific issue. Here, we test more functionality of the projections. We just send lines of sight through pixel centers for convenience. Particles at [0.5, 1.5, 2.5] (in each coordinate) smoothing lengths 0.25 - all particles have mass 1., density 1.5, + all particles have mass 1., density 1.5, except the single center particle, with mass 2., density 3. - + Parameters: ----------- axis: {0, 1, 2} projection axis (aligned with sim. axis) shiftcenter: bool - shift the coordinates to center the projection on. + shift the coordinates to center the projection on. (The grid is offset to this same center) depth: float or None depth of the projection slice @@ -60,7 +60,7 @@ def test_sph_proj_general_alongaxes(axis: int, # test correct centering, particle selection def makemasses(i, j, k): if i == j == k == 1: - return 2. + return 2. else: return 1. # m / rho, factor 1. / hsml**2 is included in the kernel integral @@ -69,13 +69,13 @@ def makemasses(i, j, k): dl_cen = integrate_kernel(cubicspline_python, 0., 0.25) # result shouldn't depend explicitly on the center if we re-center - # the data, unless we get cut-offs in the non-periodic case + # the data, unless we get cut-offs in the non-periodic case ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, - periodic=periodic, + periodic=periodic, offsets=np.full(3, 0.5), massgenerator=makemasses, unitrho=unitrho, - bbox=bbox.v, + bbox=bbox.v, recenter=center.v) if depth is None: source = ds.all_data() @@ -92,9 +92,9 @@ def makemasses(i, j, k): ds=ds) source = reg - # we don't actually want a plot, it's just a straightforward, + # we don't actually want a plot, it's just a straightforward, # common way to get an frb / image array - if weighted: + if weighted: toweight_field = ("gas", "density") else: toweight_field = None @@ -108,7 +108,7 @@ def makemasses(i, j, k): if weighted: expected_out = np.zeros((5, 5,), dtype=img.v.dtype) expected_out[::2, ::2] = unitrho - if depth is None: + if depth is None: ## during shift, particle coords do wrap around edges #if (not periodic) and shiftcenter: # # weight 1. for unitrho, 2. for 2. * untrho @@ -130,7 +130,7 @@ def makemasses(i, j, k): # 1 particle per l.o.s., including the denser one expected_out[2, 2] *= 2. # grid is shifted to the left -> 'missing' stuff at the left - if (not periodic) and shiftcenter: + if (not periodic) and shiftcenter: expected_out[:1, :] = 0. expected_out[:, :1] = 0. #print(axis, shiftcenter, depth, periodic, weighted) @@ -149,9 +149,9 @@ def test_sph_slice_general_alongaxes(axis: int, ''' Particles at [0.5, 1.5, 2.5] (in each coordinate) smoothing lengths 0.25 - all particles have mass 1., density 1.5, + all particles have mass 1., density 1.5, except the single center particle, with mass 2., density 3. - + Parameters: ----------- axis: {0, 1, 2} @@ -159,7 +159,7 @@ def test_sph_slice_general_alongaxes(axis: int, northvector: tuple y-axis direction in the final plot (direction vector) shiftcenter: bool - shift the coordinates to center the projection on. + shift the coordinates to center the projection on. (The grid is offset to this same center) zoff: float offset of the slice plane from the SPH particle center plane @@ -180,36 +180,36 @@ def test_sph_slice_general_alongaxes(axis: int, # test correct centering, particle selection def makemasses(i, j, k): if i == j == k == 1: - return 2. + return 2. elif i == j == k == 2: return 3. else: return 1. # result shouldn't depend explicitly on the center if we re-center - # the data, unless we get cut-offs in the non-periodic case + # the data, unless we get cut-offs in the non-periodic case ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, - periodic=periodic, + periodic=periodic, offsets=np.full(3, 0.5), massgenerator=makemasses, unitrho=unitrho, - bbox=bbox.v, + bbox=bbox.v, recenter=center.v) ad = ds.all_data() #print(ad[('gas', 'position')]) outgridsize = 10 width = 2.5 _center = center.to("cm").v.copy() - _center[axis] += zoff + _center[axis] += zoff - # we don't actually want a plot, it's just a straightforward, + # we don't actually want a plot, it's just a straightforward, # common way to get an frb / image array slc = yt.SlicePlot(ds, axis, ("gas", "density"), width=(width, "cm"), buff_size=(outgridsize,) * 2, center=(_center, "cm")) img = slc.frb.data[('gas', 'density')] - + # center is same in non-projection coords if axis == 0: ci = 1 @@ -278,25 +278,25 @@ def test_sph_slice_general_offaxis( ''' Same as the on-axis slices, but we rotate the basis vectors to test whether roations are handled ok. the rotation is chosen to - be small so that in/exclusion of particles within bboxes, etc. + be small so that in/exclusion of particles within bboxes, etc. works out the same way. Particles at [0.5, 1.5, 2.5] (in each coordinate) smoothing lengths 0.25 - all particles have mass 1., density 1.5, + all particles have mass 1., density 1.5, except the single center particle, with mass 2., density 3. - + Parameters: ----------- northvector: tuple y-axis direction in the final plot (direction vector) shiftcenter: bool - shift the coordinates to center the projection on. + shift the coordinates to center the projection on. (The grid is offset to this same center) zoff: float offset of the slice plane from the SPH particle center plane periodic: bool assume periodic boundary conditions, or not - + Returns: -------- None @@ -311,11 +311,11 @@ def test_sph_slice_general_offaxis( # test correct centering, particle selection def makemasses(i, j, k): if i == j == k == 1: - return 2. + return 2. else: return 1. # try to make sure dl differences from periodic wrapping are small - epsilon = 1e-4 + epsilon = 1e-4 projaxis = np.array([epsilon, 0.00, np.sqrt(1. - epsilon**2)]) e1dir = projaxis / np.sqrt(np.sum(projaxis**2)) if northvector is None: @@ -332,11 +332,11 @@ def makemasses(i, j, k): _center += zoff * e1dir ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, - periodic=periodic, + periodic=periodic, offsets=np.full(3, 0.5), massgenerator=makemasses, unitrho=unitrho, - bbox=bbox.v, + bbox=bbox.v, recenter=center, e1hat=e1dir, e2hat=e2dir, e3hat=e3dir) @@ -361,7 +361,7 @@ def makemasses(i, j, k): xgrid = np.repeat(gridcenx, outgridsize) ygrid = np.tile(gridceny, outgridsize) zgrid = np.full(outgridsize**2, np.dot(_center, e1dir)) - gridcoords = (xgrid[:, np.newaxis] * e3dir[np.newaxis, :] + gridcoords = (xgrid[:, np.newaxis] * e3dir[np.newaxis, :] + ygrid[:, np.newaxis] * e2dir[np.newaxis, :] + zgrid[:, np.newaxis] * e1dir[np.newaxis, :]) print('gridcoords:') @@ -425,7 +425,7 @@ def test_sph_grid(periodic: bool | tuple[bool, bool, bool], xcens = 0.5 * (xedges[:-1] + xedges[1:]) ycens = 0.5 * (yedges[:-1] + yedges[1:]) zcens = 0.5 * (zedges[:-1] + zedges[1:]) - + ad = ds.all_data() sphcoords = np.array([(ad[("gas", "x")]).to("cm"), (ad[("gas", "y")]).to("cm"), diff --git a/yt/testing.py b/yt/testing.py index 68b6cc4619f..40b185010e3 100644 --- a/yt/testing.py +++ b/yt/testing.py @@ -86,12 +86,12 @@ def assert_rel_equal(a1, a2, decimals, err_msg="", verbose=True): # tested: volume integral is 1. def cubicspline_python(x: float) -> float: ''' - cubic spline SPH kernel function for testing against more + cubic spline SPH kernel function for testing against more effiecient cython methods Parameters ---------- - x: + x: impact parameter / smoothing length [dimenionless] Returns @@ -113,7 +113,7 @@ def integrate_kernel(kernelfunc: Callable[[float], float], """ integrates a kernel function over a line passing entirely through it - + Parameters: ----------- kernelfunc: @@ -122,10 +122,10 @@ def integrate_kernel(kernelfunc: Callable[[float], float], impact parameter hsml: smoothing length [same units as impact parameter] - + Returns: -------- - the integral of the SPH kernel function. + the integral of the SPH kernel function. units: 1 / units of b and hsml """ pre = 1. / hsml**2 @@ -155,9 +155,9 @@ def distancematrix(pos3_i0: np.ndarray[float], for positions along the different cartesian axes pos3_i1: shape (second number of points, 3) as pos3_i0, but for the second set of points - periodic: + periodic: are the positions along each axis periodic (True) or not - periods: + periods: the periods along each axis. Ignored if positions in a given direction are not periodic. @@ -796,14 +796,14 @@ def constantmass(i: int, j: int, k: int) -> float: def fake_sph_flexible_grid_ds( hsml_factor: float = 1.0, nperside: int = 3, - periodic: bool = True, + periodic: bool = True, e1hat: np.ndarray[float] = _xhat, e2hat: np.ndarray[float] = _yhat, e3hat: np.ndarray[float] = _zhat, offsets: np.ndarray[float] = _floathalves, massgenerator: Callable[[int, int, int], float] = constantmass, unitrho: float = 1., - bbox: np.ndarray | None = None, + bbox: np.ndarray | None = None, recenter: np.ndarray | None = None, ) -> StreamParticlesDataset: """Returns an in-memory SPH dataset useful for testing @@ -818,10 +818,10 @@ def fake_sph_flexible_grid_ds( periodic: are the positions taken to be periodic? (applies to all coordinate axes) - e1hat: shape (3,) + e1hat: shape (3,) the first basis vector defining the 3D grid. If the basis vectors are not normalized to 1 or not orthogonal, the spacing - or overlap between SPH particles will be affected, but this is + or overlap between SPH particles will be affected, but this is allowed. e2hat: shape (3,) the second basis vector defining the 3D grid. (See `e1hat`.) @@ -831,27 +831,27 @@ def fake_sph_flexible_grid_ds( the the zero point of the 3D grid along each coordinate axis massgenerator: a function assigning a mass to each particle, as a function of - the e[1-3]hat indices, in order + the e[1-3]hat indices, in order unitrho: defines the density for a particle with mass 1 ('g'), and the standard (uniform) grid `hsml_factor`. bbox: if np.ndarray, shape is (2, 3) the assumed enclosing volume of the particles. Should enclose all the coordinate values. If not specified, a bbox is defined - which encloses all coordinates values with a margin. If + which encloses all coordinates values with a margin. If `periodic`, the size of the `bbox` along each coordinate is also the period along that axis. recenter: if not `None`, after generating the grid, the positions are - periodically shifted to move the old center to this positions. - Useful for testing periodicity handling. + periodically shifted to move the old center to this positions. + Useful for testing periodicity handling. This shift is relative to the halfway positions of the bbox edges. Returns: -------- A `StreamParticlesDataset` object with particle positions, masses, - velocities (zero), smoothing lengths, and densities specified. + velocities (zero), smoothing lengths, and densities specified. Values are in cgs units. """ @@ -878,15 +878,15 @@ def fake_sph_flexible_grid_ds( [np.min(pos[:, 1]) - margin, np.max(pos[:, 1]) + margin], [np.min(pos[:, 2]) - margin, - np.max(pos[:, 2]) + margin], + np.max(pos[:, 2]) + margin], ]) - + if recenter is not None: periods = bbox[:, 1] - bbox[:, 0] # old center -> new position pos += -0.5 * periods[np.newaxis, :] + recenter[np.newaxis, :] # wrap coordinates -> all in [0, boxsize) range - pos %= periods[np.newaxis, :] + pos %= periods[np.newaxis, :] # shift back to original bbox range pos += (bbox[:, 0])[np.newaxis, :] if not periodic: @@ -910,7 +910,7 @@ def fake_sph_flexible_grid_ds( "smoothing_length": (np.ones(npart) * 0.5 * hsml_factor, "cm"), "density": (rho[okinds], "g/cm**3"), } - + ds = load_particles(data=data, bbox=bbox, periodicity=(periodic,) * 3, length_unit=1., mass_unit=1., time_unit=1., @@ -919,7 +919,7 @@ def fake_sph_flexible_grid_ds( return ds -def fake_random_sph_ds(npart: int, bbox: np.ndarray, +def fake_random_sph_ds(npart: int, bbox: np.ndarray, periodic: bool | tuple[bool, bool, bool] = True, massrange: tuple[float, float] = (0.5, 2.), hsmlrange: tuple[float, float] = (0.5, 2.), @@ -931,24 +931,24 @@ def fake_random_sph_ds(npart: int, bbox: np.ndarray, ----------- npart: number of particles to generate - bbox: shape: (3, 2), units: "cm" + bbox: shape: (3, 2), units: "cm" the assumed enclosing volume of the particles. Particle positions are drawn uniformly from these ranges. periodic: - are the positions taken to be periodic? If a single value, + are the positions taken to be periodic? If a single value, that value is applied to all axes massrange: particle masses are drawn uniformly from this range (unit: "g") hsmlrange: units: "cm" particle smoothing lengths are drawn uniformly from this range unitrho: - defines the density for a particle with mass 1 ("g"), and + defines the density for a particle with mass 1 ("g"), and smoothing length 1 ("cm"). Returns: -------- A `StreamParticlesDataset` object with particle positions, masses, - velocities (zero), smoothing lengths, and densities specified. + velocities (zero), smoothing lengths, and densities specified. Values are in cgs units. """ @@ -974,7 +974,7 @@ def fake_random_sph_ds(npart: int, bbox: np.ndarray, "smoothing_length": (hsml, "cm"), "density": (dens, "g/cm**3"), } - + ds = load_particles(data=data, bbox=bbox, periodicity=periodic, length_unit=1., mass_unit=1., time_unit=1., diff --git a/yt/utilities/lib/pixelization_routines.pyx b/yt/utilities/lib/pixelization_routines.pyx index 7469d492af0..b1f7e620d5d 100644 --- a/yt/utilities/lib/pixelization_routines.pyx +++ b/yt/utilities/lib/pixelization_routines.pyx @@ -1144,7 +1144,7 @@ def pixelize_sph_kernel_projection( cdef np.float64_t[:] _weight_field cdef int * xiter cdef int * yiter - cdef int * ziter + cdef int * ziter cdef np.float64_t * xiterv cdef np.float64_t * yiterv cdef np.float64_t * ziterv @@ -1205,7 +1205,7 @@ def pixelize_sph_kernel_projection( xiterv[0] = yiterv[0] = ziterv[0] = 0.0 for i in range(xsize * ysize): local_buff[i] = 0.0 - + for j in prange(0, posx.shape[0], schedule="dynamic"): if j % 100000 == 0: with gil: @@ -1237,16 +1237,16 @@ def pixelize_sph_kernel_projection( # we set the smoothing length squared with lower limit of the pixel # Nope! that causes weird grid resolution dependences and increases - # total values when resolution elements have hsml < grid spacing + # total values when resolution elements have hsml < grid spacing h_j2 = hsml[j]*hsml[j] ih_j2 = 1.0/h_j2 prefactor_j = pmass[j] / pdens[j] / hsml[j]**2 * quantity_to_smooth[j] if weight_field is not None: prefactor_j *= _weight_field[j] - + # Discussion point: do we want the hsml margin on the z direction? - # it's consistent with Ray and Region selections, I think, + # it's consistent with Ray and Region selections, I think, # but does tend to 'tack on' stuff compared to the nominal depth for kk in range(2): # discard if z is outside bounds @@ -1493,13 +1493,13 @@ def interpolate_sph_grid_gather(np.float64_t[:, :, :] buff, def pixelize_sph_kernel_slice( np.float64_t[:, :] buff, np.uint8_t[:, :] mask, - np.float64_t[:] posx, np.float64_t[:] posy, - np.float64_t[:] posz, + np.float64_t[:] posx, np.float64_t[:] posy, + np.float64_t[:] posz, np.float64_t[:] hsml, np.float64_t[:] pmass, np.float64_t[:] pdens, np.float64_t[:] quantity_to_smooth, bounds, - np.float64_t slicez, + np.float64_t slicez, kernel_name="cubic", _check_period = (1, 1, 1), period=None): @@ -1511,7 +1511,7 @@ def pixelize_sph_kernel_slice( #print(period) #print() # bounds are [x0, x1, y0, y1], slicez is the single coordinate - # of the slice along the normal direction. + # of the slice along the normal direction. # similar method to pixelize_sph_kernel_projection cdef np.intp_t xsize, ysize cdef np.float64_t x_min, x_max, y_min, y_max, prefactor_j @@ -1532,7 +1532,7 @@ def pixelize_sph_kernel_slice( period_z = period[2] for i in range(3): check_period[i] = np.int8(_check_period[i]) - + xsize, ysize = buff.shape[0], buff.shape[1] x_min = bounds[0] x_max = bounds[1] @@ -1639,8 +1639,8 @@ def pixelize_sph_kernel_slice( continue # see equation 4 of the SPLASH paper - q_ij = math.sqrt(posx_diff - + posy_diff + q_ij = math.sqrt(posx_diff + + posy_diff + posz_diff) * ih_j if q_ij >= 1: continue @@ -1675,7 +1675,7 @@ def pixelize_sph_kernel_arbitrary_grid(np.float64_t[:, :, :] buff, cdef np.float64_t x_min, x_max, y_min, y_max, z_min, z_max, prefactor_j cdef np.int64_t xi, yi, zi, x0, x1, y0, y1, z0, z1 cdef np.float64_t q_ij, posx_diff, posy_diff, posz_diff, px, py, pz - cdef np.float64_t x, y, z, dx, dy, dz, idx, idy, idz, h_j2, h_j, ih_j + cdef np.float64_t x, y, z, dx, dy, dz, idx, idy, idz, h_j2, h_j, ih_j # cdef np.float64_t h_j3 cdef int j, ii, jj, kk cdef np.float64_t period_x = 0, period_y = 0, period_z = 0 @@ -1722,16 +1722,16 @@ def pixelize_sph_kernel_arbitrary_grid(np.float64_t[:, :, :] buff, idz = 1.0/dz kernel = get_kernel_func(kernel_name) - - # nogil seems dangerous here, but there are no actual parallel + + # nogil seems dangerous here, but there are no actual parallel # sections (e.g., prange instead of range) used here. # However, for future writers: - # !! the final buff array mutation has no protections against + # !! the final buff array mutation has no protections against # !! race conditions (e.g., OpenMP's atomic read/write), and # !! cython doesn't seem to provide such options. # (other routines in this file use private variable buffer arrays - # and add everything together at the end, but grid arrays can get - # big fast, and having such a large array in each thread could + # and add everything together at the end, but grid arrays can get + # big fast, and having such a large array in each thread could # cause memory use issues.) with nogil: # TODO make this parallel without using too much memory @@ -1830,14 +1830,14 @@ def pixelize_sph_kernel_arbitrary_grid(np.float64_t[:, :, :] buff, continue # see equation 4 of the SPLASH paper - q_ij = math.sqrt(posx_diff + q_ij = math.sqrt(posx_diff + posy_diff + posz_diff) * ih_j if q_ij >= 1: continue # shared variable buff should not # be mutatated in a nogil section - # where different threads may change + # where different threads may change # the same array element buff[xi, yi, zi] += prefactor_j \ * kernel(q_ij) @@ -2000,7 +2000,7 @@ def rotate_particle_coord_pib(np.float64_t[:] px, return px_rotated, py_rotated, rot_bounds_x0, rot_bounds_x1, rot_bounds_y0, rot_bounds_y1 # version intended for SPH off-axis slices/projections -# includes dealing with periodic boundaries, but also +# includes dealing with periodic boundaries, but also # shifts particles so center -> origin. # therefore, don't want to use this in the ParticleImageBuffer, # which expects differently centered coordinates. @@ -2055,14 +2055,14 @@ def rotate_particle_coord(np.float64_t[:] px, coordinate_matrix[0] = px[i] coordinate_matrix[1] = py[i] coordinate_matrix[2] = pz[i] - - # centering: + + # centering: # make sure this also works for centers close to periodic edges # added consequence: the center is placed at the origin # (might as well keep it there in these temporary coordinates) for ax in range(3): # assumed center is zero even if non-periodic - coordinate_matrix[ax] -= center[ax] + coordinate_matrix[ax] -= center[ax] if not periodic[ax]: continue period = bounds[2 * ax + 1] - bounds[2 * ax] # abs. difference between points in the volume is <= period @@ -2077,9 +2077,9 @@ def rotate_particle_coord(np.float64_t[:] px, py_rotated[i] = rotated_coordinates[1] pz_rotated[i] = rotated_coordinates[2] - return (px_rotated, py_rotated, pz_rotated, - rot_bounds_x0, rot_bounds_x1, - rot_bounds_y0, rot_bounds_y1, + return (px_rotated, py_rotated, pz_rotated, + rot_bounds_x0, rot_bounds_x1, + rot_bounds_y0, rot_bounds_y1, rot_bounds_z0, rot_bounds_z1) @@ -2103,7 +2103,7 @@ def off_axis_projection_SPH(np.float64_t[:] px, weight_field=None, depth=None, kernel_name="cubic"): - # periodic: periodicity of the data set: + # periodic: periodicity of the data set: # Do nothing in event of a 0 normal vector if np.allclose(normal_vector, 0.): return @@ -2122,7 +2122,7 @@ def off_axis_projection_SPH(np.float64_t[:] px, normal_vector, north_vector) # check_period=0: assumed to be a small region compared to the box - # size. The rotation already ensures that a center close to a + # size. The rotation already ensures that a center close to a # periodic edge works out fine. # since the simple single-coordinate modulo math periodicity # does not apply to the *rotated* coordinates, the periodicity @@ -2149,8 +2149,8 @@ def off_axis_projection_SPH(np.float64_t[:] px, def pixelize_sph_kernel_cutting( np.float64_t[:, :] buff, np.uint8_t[:, :] mask, - np.float64_t[:] posx, np.float64_t[:] posy, - np.float64_t[:] posz, + np.float64_t[:] posx, np.float64_t[:] posy, + np.float64_t[:] posz, np.float64_t[:] hsml, np.float64_t[:] pmass, np.float64_t[:] pdens, np.float64_t[:] quantity_to_smooth, @@ -2162,7 +2162,7 @@ def pixelize_sph_kernel_cutting( if check_period == 0: periodic = np.zeros(3, dtype=bool) - + posx_rot, posy_rot, posz_rot, \ rot_bounds_x0, rot_bounds_x1, \ rot_bounds_y0, rot_bounds_y1, \ @@ -2172,13 +2172,13 @@ def pixelize_sph_kernel_cutting( widthxy, 0., normal_vector, north_vector) - bounds_rot = np.array([rot_bounds_x0, rot_bounds_x1, + bounds_rot = np.array([rot_bounds_x0, rot_bounds_x1, rot_bounds_y0, rot_bounds_y1]) slicez_rot = rot_bounds_z0 pixelize_sph_kernel_slice(buff, mask, posx_rot, posy_rot, posz_rot, hsml, pmass, pdens, quantity_to_smooth, - bounds_rot, slicez_rot, + bounds_rot, slicez_rot, kernel_name=kernel_name, _check_period=np.zeros(3, dtype="int"), period=None) diff --git a/yt/visualization/plot_window.py b/yt/visualization/plot_window.py index 68db155ad1b..5afd02bd097 100644 --- a/yt/visualization/plot_window.py +++ b/yt/visualization/plot_window.py @@ -90,7 +90,7 @@ def get_oblique_window_parameters(normal, center, width, ds, if len(width) == 2: # Transforming to the cutting plane coordinate system - # the original dimensionless center messes up off-axis + # the original dimensionless center messes up off-axis # SPH projections though -> don't use this center there center = ((center - ds.domain_left_edge) / ds.domain_width - 0.5)\ * ds.domain_width @@ -2469,18 +2469,18 @@ def __init__( # units match bounds # for SPH data, we want to input the original center # the cython backend handles centering to this point and - # rotation. + # rotation. # get3bounds gets a depth 0.5 * diagonal + margin in the - # depth=None case. + # depth=None case. (bounds, center_rot) = get_oblique_window_parameters( normal, center, width, ds, depth=depth, get3bounds=True, ) # will probably fail if you try to project an SPH and non-SPH # field in a single call - # checks for SPH fields copied from the + # checks for SPH fields copied from the # _ortho_pixelize method in cartesian_coordinates.py - - ## data_source might be None here + + ## data_source might be None here ## (OffAxisProjectionDummyDataSource gets used later) if data_source is None: data_source = ds.all_data() @@ -2488,7 +2488,7 @@ def __init__( finfo = data_source.ds.field_info[field] is_sph_field = finfo.is_sph_field particle_datasets = (ParticleDataset, StreamParticlesDataset) - + if isinstance(data_source.ds, particle_datasets) and is_sph_field: center_use = parse_center_array(center, ds=data_source.ds, axis=None) diff --git a/yt/visualization/tests/test_offaxisprojection.py b/yt/visualization/tests/test_offaxisprojection.py index d67fe4c4c57..89a610f2987 100644 --- a/yt/visualization/tests/test_offaxisprojection.py +++ b/yt/visualization/tests/test_offaxisprojection.py @@ -14,7 +14,7 @@ fake_random_ds, ) from yt.visualization.api import ( - OffAxisProjectionPlot, + OffAxisProjectionPlot, OffAxisSlicePlot, ) from yt.visualization.image_writer import write_projection @@ -242,7 +242,7 @@ def _vlos_sq(field, data): ## marginally < 0, resulting in unmatched NaN values in the ## first assert_rel_equal argument. The compute_stddev_image ## function used in OffAxisProjectionPlot checks for and deals - ## with these cases. + ## with these cases. #assert_rel_equal( # np.sqrt( # p1.frb["gas", "velocity_los_squared"] - p1.frb["gas", "velocity_los"] ** 2 @@ -254,10 +254,10 @@ def _vlos_sq(field, data): p1_sqexp = p1.frb["gas", "velocity_los"] ** 2 p1res = np.sqrt(p1_expsq - p1_sqexp) # set values to zero that have **2 - **2 < 0, but - # the absolute values are much smaller than the smallest - # postive values of **2 and **2 + # the absolute values are much smaller than the smallest + # postive values of **2 and **2 # (i.e., the difference is pretty much zero) - mindiff = 1e-10 * min(np.min(p1_expsq[p1_expsq > 0]), + mindiff = 1e-10 * min(np.min(p1_expsq[p1_expsq > 0]), np.min(p1_sqexp[p1_sqexp > 0])) print(mindiff) setzero = np.logical_and(p1_expsq - p1_sqexp < 0, diff --git a/yt/visualization/tests/test_offaxisprojection_pytestonly.py b/yt/visualization/tests/test_offaxisprojection_pytestonly.py index 1a252314ba4..5a1f013313f 100644 --- a/yt/visualization/tests/test_offaxisprojection_pytestonly.py +++ b/yt/visualization/tests/test_offaxisprojection_pytestonly.py @@ -19,25 +19,25 @@ def test_sph_proj_general_offaxis( northvector: tuple[float, float, float] | None, shiftcenter: bool, depth: tuple[float, str] | None, - periodic: bool, + periodic: bool, weighted: bool) -> None: ''' Same as the on-axis projections, but we rotate the basis vectors to test whether roations are handled ok. the rotation is chosen to - be small so that in/exclusion of particles within bboxes, etc. - works out the same way. + be small so that in/exclusion of particles within bboxes, etc. + works out the same way. We just send lines of sight through pixel centers for convenience. Particles at [0.5, 1.5, 2.5] (in each coordinate) smoothing lengths 0.25 - all particles have mass 1., density 1.5, + all particles have mass 1., density 1.5, except the single center particle, with mass 2., density 3. - + Parameters: ----------- northvector: tuple y-axis direction in the final plot (direction vector) shiftcenter: bool - shift the coordinates to center the projection on. + shift the coordinates to center the projection on. (The grid is offset to this same center) depth: float or None depth of the projection slice @@ -45,7 +45,7 @@ def test_sph_proj_general_offaxis( assume periodic boundary conditions, or not weighted: bool make a weighted projection (density-weighted density), or not - + Returns: -------- None @@ -60,15 +60,15 @@ def test_sph_proj_general_offaxis( # test correct centering, particle selection def makemasses(i, j, k): if i == j == k == 1: - return 2. + return 2. else: return 1. # result shouldn't depend explicitly on the center if we re-center - # the data, unless we get cut-offs in the non-periodic case + # the data, unless we get cut-offs in the non-periodic case # *almost* the z-axis # try to make sure dl differences from periodic wrapping are small - epsilon = 1e-4 + epsilon = 1e-4 projaxis = np.array([epsilon, 0.00, np.sqrt(1. - epsilon**2)]) e1dir = projaxis / np.sqrt(np.sum(projaxis**2)) # TODO: figure out other (default) axes for basis vectors here @@ -81,11 +81,11 @@ def makemasses(i, j, k): e3dir = np.cross(e1dir, e2dir) ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, - periodic=periodic, + periodic=periodic, offsets=np.full(3, 0.5), massgenerator=makemasses, unitrho=unitrho, - bbox=bbox.v, + bbox=bbox.v, recenter=center, e1hat=e1dir, e2hat=e2dir, e3hat=e3dir) @@ -99,11 +99,11 @@ def makemasses(i, j, k): prefactor = 1. / unitrho #/ (0.5 * 0.5)**2 dl_cen = integrate_kernel(cubicspline_python, 0., 0.25) - if weighted: + if weighted: toweight_field = ("gas", "density") else: toweight_field = None - # we don't actually want a plot, it's just a straightforward, + # we don't actually want a plot, it's just a straightforward, # common way to get an frb / image array prj = ProjectionPlot(ds, projaxis, ("gas", "density"), width=(2.5, "cm"), @@ -118,7 +118,7 @@ def makemasses(i, j, k): # periodic shifts will modify the (relative) dl values a bit expected_out = np.zeros((5, 5,), dtype=img.v.dtype) expected_out[::2, ::2] = unitrho - if depth is None: + if depth is None: expected_out[2, 2] *= 1.5 else: # only 2 * unitrho element included @@ -134,7 +134,7 @@ def makemasses(i, j, k): # 1 particle per l.o.s., including the denser one expected_out[2, 2] *= 2. # grid is shifted to the left -> 'missing' stuff at the left - if (not periodic) and shiftcenter: + if (not periodic) and shiftcenter: expected_out[:1, :] = 0. expected_out[:, :1] = 0. #print(axis, shiftcenter, depth, periodic, weighted) diff --git a/yt/visualization/volume_rendering/off_axis_projection.py b/yt/visualization/volume_rendering/off_axis_projection.py index 44f03ade0cb..b3f579b9cd8 100644 --- a/yt/visualization/volume_rendering/off_axis_projection.py +++ b/yt/visualization/volume_rendering/off_axis_projection.py @@ -82,8 +82,8 @@ def off_axis_projection( A vector that, if specified, restricts the orientation such that the north vector dotted into the image plane points "up". Useful for rotations depth: float, tuple[float, str], or unyt_array or size 1. - specify the depth of the projection region (size along the - line of sight). If no units are given (unyt_array or second + specify the depth of the projection region (size along the + line of sight). If no units are given (unyt_array or second tuple element), code units are assumed. num_threads: integer, optional, default 1 Use this many OpenMP threads during projection. @@ -151,16 +151,16 @@ def off_axis_projection( if not hasattr(width, "units"): width = data_source.ds.arr(width, "code_length") if depth is not None: - # handle units (intrinsic or as a tuple), + # handle units (intrinsic or as a tuple), # then convert to code length # float -> assumed to be in code units if isinstance(depth, tuple): depth = data_source.ds.arr(np.array([depth[0]]), depth[1]) if hasattr(depth, "units"): depth = depth.to("code_length").d - + #depth = data_source.ds.arr(depth, "code_length") - + if hasattr(data_source.ds, "_sph_ptypes"): if method != "integrate": @@ -218,7 +218,7 @@ def off_axis_projection( # if weight is None: buf = np.zeros((resolution[0], resolution[1]), dtype="float64") mask = np.ones_like(buf, dtype="uint8") - + ## width from fixed_resolution.py is just the size of the domain #x_min = center[0] - width[0] / 2 #x_max = center[0] + width[0] / 2 @@ -226,7 +226,7 @@ def off_axis_projection( #y_max = center[1] + width[1] / 2 #z_min = center[2] - width[2] / 2 #z_max = center[2] + width[2] / 2 - + periodic = data_source.ds.periodicity le = data_source.ds.domain_left_edge.to("code_length").d re = data_source.ds.domain_right_edge.to("code_length").d @@ -234,7 +234,7 @@ def off_axis_projection( x_max, y_max, z_max = re bounds = [x_min, x_max, y_min, y_max, z_min, z_max] # only need (rotated) x/y widths - _width = (width.to("code_length").d)[:2] + _width = (width.to("code_length").d)[:2] finfo = data_source.ds.field_info[item] ounits = finfo.output_units kernel_name = None From 785108afc557361f3bcfd962ce4a32ef766dbadf Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Thu, 18 Jul 2024 15:13:19 -0500 Subject: [PATCH 36/64] add typing option of (array, unit) values --- yt/loaders.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yt/loaders.py b/yt/loaders.py index 54d52e5b83c..aa7abea55f9 100644 --- a/yt/loaders.py +++ b/yt/loaders.py @@ -694,7 +694,7 @@ def load_amr_grids( def load_particles( - data: dict[AnyFieldKey, np.ndarray], + data: dict[AnyFieldKey, Union[np.ndarray, tuple[np.ndarry, str]]], length_unit=None, bbox=None, sim_time=None, From fec520f88fa225691c471a83a57947dc7b5ba00b Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Thu, 18 Jul 2024 15:13:47 -0500 Subject: [PATCH 37/64] attempted change typing to work with python 3.9 --- yt/testing.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/yt/testing.py b/yt/testing.py index 40b185010e3..af4e6998277 100644 --- a/yt/testing.py +++ b/yt/testing.py @@ -9,11 +9,12 @@ from functools import wraps from importlib.util import find_spec from shutil import which -from typing import Callable +from typing import Callable, Union from unittest import SkipTest import matplotlib import numpy as np +import numpy.typing as npt from more_itertools import always_iterable from numpy.random import RandomState from unyt.exceptions import UnitOperationError @@ -84,7 +85,8 @@ def assert_rel_equal(a1, a2, decimals, err_msg="", verbose=True): ) # tested: volume integral is 1. -def cubicspline_python(x: float) -> float: +def cubicspline_python(x: Union[float,npt.ndarray[float]], + ) -> npt.ndarray[float]: ''' cubic spline SPH kernel function for testing against more effiecient cython methods @@ -140,11 +142,11 @@ def integrate_kernel(kernelfunc: Callable[[float], float], return pre * integral _zeroperiods = np.array([0., 0., 0.]) -def distancematrix(pos3_i0: np.ndarray[float], - pos3_i1: np.ndarray[float], - periodic: tuple[bool] = (True,) * 3, - periods: np.ndarray = _zeroperiods, - ) -> np.ndarray[float]: +def distancematrix(pos3_i0: npt.ndarray[float], + pos3_i1: npt.ndarray[float], + periodic: tuple[bool, bool, bool] = (True,) * 3, + periods: npt.ndarray[float] = _zeroperiods, + ) -> npt.ndarray[float]: ''' Calculates the distances between two arrays of points. @@ -797,14 +799,14 @@ def fake_sph_flexible_grid_ds( hsml_factor: float = 1.0, nperside: int = 3, periodic: bool = True, - e1hat: np.ndarray[float] = _xhat, - e2hat: np.ndarray[float] = _yhat, - e3hat: np.ndarray[float] = _zhat, - offsets: np.ndarray[float] = _floathalves, + e1hat: npt.ndarray[float] = _xhat, + e2hat: npt.ndarray[float] = _yhat, + e3hat: npt.ndarray[float] = _zhat, + offsets: npt.ndarray[float] = _floathalves, massgenerator: Callable[[int, int, int], float] = constantmass, unitrho: float = 1., - bbox: np.ndarray | None = None, - recenter: np.ndarray | None = None, + bbox: Union[npt.ndarray[float], None] = None, + recenter: Union[npt.ndarray[float], None] = None, ) -> StreamParticlesDataset: """Returns an in-memory SPH dataset useful for testing @@ -919,8 +921,9 @@ def fake_sph_flexible_grid_ds( return ds -def fake_random_sph_ds(npart: int, bbox: np.ndarray, - periodic: bool | tuple[bool, bool, bool] = True, +def fake_random_sph_ds(npart: int, + bbox: npt.ndarray[float], + periodic: Union[bool, tuple[bool, bool, bool]] = True, massrange: tuple[float, float] = (0.5, 2.), hsmlrange: tuple[float, float] = (0.5, 2.), unitrho: float = 1., From f4dd2cc57e26488a486e410b01665cea6a200f01 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 18 Jul 2024 19:36:15 +0000 Subject: [PATCH 38/64] [pre-commit.ci] auto fixes from pre-commit.com hooks preserve auto fixes in merge for more information, see https://pre-commit.ci --- .../coordinates/cartesian_coordinates.py | 50 ++- .../tests/test_sph_pixelization.py | 173 ++++---- .../tests/test_sph_pixelization_pytestonly.py | 414 ++++++++++-------- yt/testing.py | 156 ++++--- yt/visualization/plot_window.py | 38 +- .../tests/test_offaxisprojection.py | 16 +- .../test_offaxisprojection_pytestonly.py | 122 +++--- .../volume_rendering/off_axis_projection.py | 15 +- .../volume_rendering/old_camera.py | 1 + 9 files changed, 566 insertions(+), 419 deletions(-) diff --git a/yt/geometry/coordinates/cartesian_coordinates.py b/yt/geometry/coordinates/cartesian_coordinates.py index bf471b2b58d..7c68b8b0fea 100644 --- a/yt/geometry/coordinates/cartesian_coordinates.py +++ b/yt/geometry/coordinates/cartesian_coordinates.py @@ -380,7 +380,7 @@ def _ortho_pixelize( # instead xa = self.x_axis[dim] ya = self.y_axis[dim] - #axorder = data_source.ds.coordinates.axis_order + # axorder = data_source.ds.coordinates.axis_order za = list({0, 1, 2} - {xa, ya})[0] ds_periodic = data_source.ds.periodicity _periodic = np.array(ds_periodic) @@ -395,7 +395,7 @@ def _ortho_pixelize( if kernel_name is None: kernel_name = "cubic" - if isinstance(data_source, YTParticleProj): # projection + if isinstance(data_source, YTParticleProj): # projection weight = data_source.weight_field moment = data_source.moment le, re = data_source.data_source.get_bbox() @@ -444,7 +444,7 @@ def _ortho_pixelize( bnds3, _check_period=_periodic.astype("int"), period=period3, - kernel_name=kernel_name + kernel_name=kernel_name, ) # We use code length here, but to get the path length right # we need to multiply by the conversion factor between @@ -478,7 +478,7 @@ def _ortho_pixelize( _check_period=_periodic.astype("int"), period=period3, weight_field=chunk[weight].in_units(wounits), - kernel_name=kernel_name + kernel_name=kernel_name, ) mylog.info( "Making a fixed resolution buffer of (%s) %d by %d", @@ -501,7 +501,7 @@ def _ortho_pixelize( bnds3, _check_period=_periodic.astype("int"), period=period3, - kernel_name=kernel_name + kernel_name=kernel_name, ) normalization_2d_utility(buff, weight_buff) if moment == 2: @@ -523,7 +523,7 @@ def _ortho_pixelize( _check_period=_periodic.astype("int"), period=period3, weight_field=chunk[weight].in_units(wounits), - kernel_name=kernel_name + kernel_name=kernel_name, ) normalization_2d_utility(buff2, weight_buff) buff = compute_stddev_image(buff2, buff) @@ -554,7 +554,7 @@ def _ortho_pixelize( data_source.coord.to("code_length").v, _check_period=_periodic.astype("int"), period=period3, - kernel_name=kernel_name + kernel_name=kernel_name, ) if normalize: pixelize_sph_kernel_slice( @@ -571,7 +571,7 @@ def _ortho_pixelize( data_source.coord.to("code_length").v, _check_period=_periodic.astype("int"), period=period3, - kernel_name=kernel_name + kernel_name=kernel_name, ) if normalize: @@ -680,13 +680,16 @@ def _oblique_pixelize(self, data_source, field, bounds, size, antialias): _finfo = data_source.ds.field_info[field] is_sph_field = _finfo.is_sph_field particle_datasets = (ParticleDataset, StreamParticlesDataset) - #finfo = self.ds._get_field_info(field) + # finfo = self.ds._get_field_info(field) # SPH data # only for slices: a function in off_axis_projection.py # handles projections - if isinstance(data_source.ds, particle_datasets) and is_sph_field \ - and isinstance(data_source, YTCuttingPlane): + if ( + isinstance(data_source.ds, particle_datasets) + and is_sph_field + and isinstance(data_source, YTCuttingPlane) + ): normalize = getattr(self.ds, "use_sph_normalization", True) le = data_source.ds.domain_left_edge.to("code_length") re = data_source.ds.domain_right_edge.to("code_length") @@ -698,8 +701,7 @@ def _oblique_pixelize(self, data_source, field, bounds, size, antialias): axorder = data_source.ds.coordinates.axis_order ounits = data_source.ds.field_info[field].output_units # input bounds are in code length units already - widthxy = np.array((bounds[1] - bounds[0], - bounds[3] - bounds[2])) + widthxy = np.array((bounds[1] - bounds[0], bounds[3] - bounds[2])) kernel_name = None if hasattr(data_source.ds, "kernel_name"): kernel_name = data_source.ds.kernel_name @@ -728,9 +730,12 @@ def _oblique_pixelize(self, data_source, field, bounds, size, antialias): chunk[ptype, "mass"].to("code_mass"), chunk[ptype, "density"].to("code_density"), chunk[field].in_units(ounits), - center, widthxy, - normal_vector, north_vector, - boxbounds, periodic, + center, + widthxy, + normal_vector, + north_vector, + boxbounds, + periodic, kernel_name=kernel_name, check_period=1, ) @@ -745,11 +750,14 @@ def _oblique_pixelize(self, data_source, field, bounds, size, antialias): chunk[ptype, "mass"].to("code_mass"), chunk[ptype, "density"].to("code_density"), np.ones(chunk[ptype, "density"].shape[0]), - center, widthxy, - normal_vector, north_vector, - boxbounds, periodic, + center, + widthxy, + normal_vector, + north_vector, + boxbounds, + periodic, kernel_name=kernel_name, - check_period=1 + check_period=1, ) if normalize: @@ -763,7 +771,7 @@ def _oblique_pixelize(self, data_source, field, bounds, size, antialias): # whatever other data this code could handle before the # SPH option was added else: - indices = np.argsort(data_source["pdx"])[::-1].astype(np.int_) + indices = np.argsort(data_source["pdx"])[::-1].astype("int64", copy=False) buff = np.full((size[1], size[0]), np.nan, dtype="float64") ftype = "index" if isinstance(data_source.ds, YTSpatialPlotDataset): diff --git a/yt/geometry/coordinates/tests/test_sph_pixelization.py b/yt/geometry/coordinates/tests/test_sph_pixelization.py index 3ae3e9ee3dc..1d1c82be5c8 100644 --- a/yt/geometry/coordinates/tests/test_sph_pixelization.py +++ b/yt/geometry/coordinates/tests/test_sph_pixelization.py @@ -6,7 +6,8 @@ cubicspline_python, fake_sph_flexible_grid_ds, integrate_kernel, - requires_file) + requires_file, +) from yt.utilities.math_utils import compute_stddev_image ## off-axis projection tests for SPH data are in @@ -48,71 +49,74 @@ def _vysq(field, data): sigy = compute_stddev_image(prj1.frb["gas", "vysq"], prj1.frb["gas", "velocity_y"]) assert_rel_equal(sigy, prj2.frb["gas", "velocity_y"].d, 10) + def test_sph_projection_basic1(): - ''' + """ small, uniform grid: expected values for given dl? pixel centers at 0.5, 1., 1.5, 2., 2.5 particles at 0.5, 1.5, 2.5 - ''' - bbox = np.array([[0., 3.]] * 3) - ds = fake_sph_flexible_grid_ds(hsml_factor=1.0, nperside=3, - bbox=bbox) + """ + bbox = np.array([[0.0, 3.0]] * 3) + ds = fake_sph_flexible_grid_ds(hsml_factor=1.0, nperside=3, bbox=bbox) # works, but no depth control (at least without specific filters) proj = ds.proj(("gas", "density"), 2) - frb = proj.to_frb(width=(2.5, 'cm'), - resolution=(5, 5), - height=(2.5, 'cm'), - center=np.array([1.5, 1.5, 1.5]), - periodic=False) - out = frb.get_image(('gas', 'density')) + frb = proj.to_frb( + width=(2.5, "cm"), + resolution=(5, 5), + height=(2.5, "cm"), + center=np.array([1.5, 1.5, 1.5]), + periodic=False, + ) + out = frb.get_image(("gas", "density")) expected_out = np.zeros((5, 5), dtype=np.float64) - dl_1part = integrate_kernel(cubicspline_python, 0., 0.5) - linedens_1part = dl_1part * 1. # unit mass, density - linedens = 3. * linedens_1part + dl_1part = integrate_kernel(cubicspline_python, 0.0, 0.5) + linedens_1part = dl_1part * 1.0 # unit mass, density + linedens = 3.0 * linedens_1part expected_out[::2, ::2] = linedens assert_rel_equal(expected_out, out.v, 5) - #return out + # return out + def test_sph_projection_basic2(): - ''' + """ small, uniform grid: expected values for given dl? pixel centers at 0.5, 1., 1.5, 2., 2.5 particles at 0.5, 1.5, 2.5 but hsml radii are 0.25 -> try non-zero impact parameters, other pixels are still zero. - ''' - bbox = np.array([[0., 3.]] * 3) - ds = fake_sph_flexible_grid_ds(hsml_factor=0.5, nperside=3, - bbox=bbox) + """ + bbox = np.array([[0.0, 3.0]] * 3) + ds = fake_sph_flexible_grid_ds(hsml_factor=0.5, nperside=3, bbox=bbox) proj = ds.proj(("gas", "density"), 2) - frb = proj.to_frb(width=(2.5, 'cm'), - resolution=(5, 5), - height=(2.5, 'cm'), - center=np.array([1.375, 1.375, 1.5]), - periodic=False) - out = frb.get_image(('gas', 'density')) + frb = proj.to_frb( + width=(2.5, "cm"), + resolution=(5, 5), + height=(2.5, "cm"), + center=np.array([1.375, 1.375, 1.5]), + periodic=False, + ) + out = frb.get_image(("gas", "density")) expected_out = np.zeros((5, 5), dtype=np.float64) - dl_1part = integrate_kernel(cubicspline_python, - np.sqrt(2) * 0.125, - 0.25) - linedens_1part = dl_1part * 1. # unit mass, density - linedens = 3. * linedens_1part + dl_1part = integrate_kernel(cubicspline_python, np.sqrt(2) * 0.125, 0.25) + linedens_1part = dl_1part * 1.0 # unit mass, density + linedens = 3.0 * linedens_1part expected_out[::2, ::2] = linedens - #print(expected_out) - #print(out.v) + # print(expected_out) + # print(out.v) assert_rel_equal(expected_out, out.v, 4) - #return out + # return out + def get_dataset_sphrefine(reflevel: int = 1): - ''' + """ constant density particle grid, with increasing particle sampling - ''' - lenfact = (1./3.)**(reflevel - 1) + """ + lenfact = (1.0 / 3.0) ** (reflevel - 1) massfact = lenfact**3 nperside = 3**reflevel @@ -120,26 +124,29 @@ def get_dataset_sphrefine(reflevel: int = 1): e2hat = np.array([0, lenfact, 0]) e3hat = np.array([0, 0, lenfact]) hsml_factor = lenfact - bbox = np.array([[0., 3.]] * 3) - offsets = np.ones(3, dtype=np.float64) * 0.5 # in units of ehat + bbox = np.array([[0.0, 3.0]] * 3) + offsets = np.ones(3, dtype=np.float64) * 0.5 # in units of ehat def refmass(i: int, j: int, k: int) -> float: return massfact - unitrho = 1. / massfact # want density 1 for decreasing mass - - ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, - nperside=nperside, - periodic=True, - e1hat=e1hat, - e2hat=e2hat, - e3hat=e3hat, - offsets=offsets, - massgenerator=refmass, - unitrho=unitrho, - bbox=bbox, - ) + + unitrho = 1.0 / massfact # want density 1 for decreasing mass + + ds = fake_sph_flexible_grid_ds( + hsml_factor=hsml_factor, + nperside=nperside, + periodic=True, + e1hat=e1hat, + e2hat=e2hat, + e3hat=e3hat, + offsets=offsets, + massgenerator=refmass, + unitrho=unitrho, + bbox=bbox, + ) return ds + def getdata_test_gridproj2(): # initial pixel centers at 0.5, 1., 1.5, 2., 2.5 # particles at 0.5, 1.5, 2.5 @@ -150,53 +157,63 @@ def getdata_test_gridproj2(): for rl in range(1, 4): ds = get_dataset_sphrefine(reflevel=rl) proj = ds.proj(("gas", "density"), 2) - frb = proj.to_frb(width=(2.5, 'cm'), - resolution=(5, 5), - height=(2.5, 'cm'), - center=np.array([1.5, 1.5, 1.5]), - periodic=False) - out = frb.get_image(('gas', 'density')) + frb = proj.to_frb( + width=(2.5, "cm"), + resolution=(5, 5), + height=(2.5, "cm"), + center=np.array([1.5, 1.5, 1.5]), + periodic=False, + ) + out = frb.get_image(("gas", "density")) outlist.append(out) dss.append(ds) return outlist, dss + def test_sph_gridproj_reseffect1(): - ''' + """ Comparing same pixel centers with higher particle resolution. The pixel centers are at x/y coordinates [0.5, 1., 1.5, 2., 2.5] at the first level, the spacing halves at each level. Checking the pixels at [0.5, 1.5, 2.5], which should have the same values at each resolution. - ''' + """ imgs, _ = getdata_test_gridproj2() ref = imgs[-1] for img in imgs: - assert_rel_equal(img[::img.shape[0] // 2, ::img.shape[1] // 2], - ref[::ref.shape[0] // 2, ::ref.shape[1] // 2], 4) + assert_rel_equal( + img[:: img.shape[0] // 2, :: img.shape[1] // 2], + ref[:: ref.shape[0] // 2, :: ref.shape[1] // 2], + 4, + ) + def test_sph_gridproj_reseffect2(): - ''' + """ refine the pixel grid instead of the particle grid - ''' + """ ds = get_dataset_sphrefine(reflevel=2) proj = ds.proj(("gas", "density"), 2) imgs = {} maxrl = 5 for rl in range(1, maxrl + 1): - npix = 1 + 2**(rl + 1) - margin = 0.5 - 0.5**(rl + 1) - frb = proj.to_frb(width=(3. - 2. * margin, 'cm'), - resolution=(npix, npix), - height=(3. - 2. * margin, 'cm'), - center=np.array([1.5, 1.5, 1.5]), - periodic=False) - out = frb.get_image(('gas', 'density')) + npix = 1 + 2 ** (rl + 1) + margin = 0.5 - 0.5 ** (rl + 1) + frb = proj.to_frb( + width=(3.0 - 2.0 * margin, "cm"), + resolution=(npix, npix), + height=(3.0 - 2.0 * margin, "cm"), + center=np.array([1.5, 1.5, 1.5]), + periodic=False, + ) + out = frb.get_image(("gas", "density")) imgs[rl] = out ref = imgs[maxrl] - pixspace_ref = 2**(maxrl) + pixspace_ref = 2 ** (maxrl) for rl in imgs: img = imgs[rl] - pixspace = 2**(rl) - #print(f'Grid refinement level {rl}:') - assert_rel_equal(img[::pixspace, ::pixspace], - ref[::pixspace_ref, ::pixspace_ref], 4) + pixspace = 2 ** (rl) + # print(f'Grid refinement level {rl}:') + assert_rel_equal( + img[::pixspace, ::pixspace], ref[::pixspace_ref, ::pixspace_ref], 4 + ) diff --git a/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py b/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py index af95fde0345..945286af0c2 100644 --- a/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py +++ b/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py @@ -13,17 +13,16 @@ integrate_kernel, ) + @pytest.mark.parametrize("weighted", [True, False]) @pytest.mark.parametrize("periodic", [True, False]) -@pytest.mark.parametrize("depth", [None, (1., "cm")]) +@pytest.mark.parametrize("depth", [None, (1.0, "cm")]) @pytest.mark.parametrize("shiftcenter", [False, True]) @pytest.mark.parametrize("axis", [0, 1, 2]) -def test_sph_proj_general_alongaxes(axis: int, - shiftcenter: bool, - depth : float | None, - periodic: bool, - weighted: bool) -> None: - ''' +def test_sph_proj_general_alongaxes( + axis: int, shiftcenter: bool, depth: float | None, periodic: bool, weighted: bool +) -> None: + """ The previous projection tests were for a specific issue. Here, we test more functionality of the projections. We just send lines of sight through pixel centers for convenience. @@ -49,34 +48,39 @@ def test_sph_proj_general_alongaxes(axis: int, Returns: -------- None - ''' + """ if shiftcenter: - center = unyt.unyt_array(np.array((0.625, 0.625, 0.625)), 'cm') + center = unyt.unyt_array(np.array((0.625, 0.625, 0.625)), "cm") else: - center = unyt.unyt_array(np.array((1.5, 1.5, 1.5)), 'cm') - bbox = unyt.unyt_array(np.array([[0., 3.], [0., 3.], [0., 3.]]), 'cm') + center = unyt.unyt_array(np.array((1.5, 1.5, 1.5)), "cm") + bbox = unyt.unyt_array(np.array([[0.0, 3.0], [0.0, 3.0], [0.0, 3.0]]), "cm") hsml_factor = 0.5 unitrho = 1.5 + # test correct centering, particle selection def makemasses(i, j, k): if i == j == k == 1: - return 2. + return 2.0 else: - return 1. + return 1.0 + # m / rho, factor 1. / hsml**2 is included in the kernel integral # (density is adjusted, so same for center particle) - prefactor = 1. / unitrho #/ (0.5 * 0.5)**2 - dl_cen = integrate_kernel(cubicspline_python, 0., 0.25) + prefactor = 1.0 / unitrho # / (0.5 * 0.5)**2 + dl_cen = integrate_kernel(cubicspline_python, 0.0, 0.25) # result shouldn't depend explicitly on the center if we re-center # the data, unless we get cut-offs in the non-periodic case - ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, - periodic=periodic, - offsets=np.full(3, 0.5), - massgenerator=makemasses, - unitrho=unitrho, - bbox=bbox.v, - recenter=center.v) + ds = fake_sph_flexible_grid_ds( + hsml_factor=hsml_factor, + nperside=3, + periodic=periodic, + offsets=np.full(3, 0.5), + massgenerator=makemasses, + unitrho=unitrho, + bbox=bbox.v, + recenter=center.v, + ) if depth is None: source = ds.all_data() else: @@ -86,10 +90,7 @@ def makemasses(i, j, k): le[axis] = center[axis] - 0.5 * depth re[axis] = center[axis] + 0.5 * depth cen = 0.5 * (le + re) - reg = YTRegion(center=cen, - left_edge=le, - right_edge=re, - ds=ds) + reg = YTRegion(center=cen, left_edge=le, right_edge=re, ds=ds) source = reg # we don't actually want a plot, it's just a straightforward, @@ -98,55 +99,71 @@ def makemasses(i, j, k): toweight_field = ("gas", "density") else: toweight_field = None - prj = yt.ProjectionPlot(ds, axis, ("gas", "density"), - width=(2.5, "cm"), - weight_field=toweight_field, - buff_size=(5, 5), - center=center, - data_source=source) - img = prj.frb.data[('gas', 'density')] + prj = yt.ProjectionPlot( + ds, + axis, + ("gas", "density"), + width=(2.5, "cm"), + weight_field=toweight_field, + buff_size=(5, 5), + center=center, + data_source=source, + ) + img = prj.frb.data[("gas", "density")] if weighted: - expected_out = np.zeros((5, 5,), dtype=img.v.dtype) + expected_out = np.zeros( + ( + 5, + 5, + ), + dtype=img.v.dtype, + ) expected_out[::2, ::2] = unitrho if depth is None: ## during shift, particle coords do wrap around edges - #if (not periodic) and shiftcenter: + # if (not periodic) and shiftcenter: # # weight 1. for unitrho, 2. for 2. * untrho # expected_out[2, 2] *= 5. / 3. - #else: + # else: # weight (2 * 1.) for unitrho, (1 * 2.) for 2. * unitrho expected_out[2, 2] *= 1.5 else: # only 2 * unitrho element included - expected_out[2, 2] *= 2. + expected_out[2, 2] *= 2.0 else: - expected_out = np.zeros((5, 5,), dtype=img.v.dtype) + expected_out = np.zeros( + ( + 5, + 5, + ), + dtype=img.v.dtype, + ) expected_out[::2, ::2] = dl_cen * prefactor * unitrho if depth is None: # 3 particles per l.o.s., including the denser one - expected_out *= 3. - expected_out[2, 2] *= 4. / 3. + expected_out *= 3.0 + expected_out[2, 2] *= 4.0 / 3.0 else: # 1 particle per l.o.s., including the denser one - expected_out[2, 2] *= 2. + expected_out[2, 2] *= 2.0 # grid is shifted to the left -> 'missing' stuff at the left if (not periodic) and shiftcenter: - expected_out[:1, :] = 0. - expected_out[:, :1] = 0. - #print(axis, shiftcenter, depth, periodic, weighted) - #print(expected_out) - #print(img.v) + expected_out[:1, :] = 0.0 + expected_out[:, :1] = 0.0 + # print(axis, shiftcenter, depth, periodic, weighted) + # print(expected_out) + # print(img.v) assert_rel_equal(expected_out, img.v, 5) + @pytest.mark.parametrize("periodic", [True, False]) @pytest.mark.parametrize("shiftcenter", [False, True]) -@pytest.mark.parametrize("zoff", [0., 0.1, 0.5, 1.]) +@pytest.mark.parametrize("zoff", [0.0, 0.1, 0.5, 1.0]) @pytest.mark.parametrize("axis", [0, 1, 2]) -def test_sph_slice_general_alongaxes(axis: int, - shiftcenter: bool, - periodic: bool, - zoff: float) -> None: - ''' +def test_sph_slice_general_alongaxes( + axis: int, shiftcenter: bool, periodic: bool, zoff: float +) -> None: + """ Particles at [0.5, 1.5, 2.5] (in each coordinate) smoothing lengths 0.25 all particles have mass 1., density 1.5, @@ -169,34 +186,38 @@ def test_sph_slice_general_alongaxes(axis: int, Returns: -------- None - ''' + """ if shiftcenter: - center = unyt.unyt_array(np.array((0.625, 0.625, 0.625)), 'cm') + center = unyt.unyt_array(np.array((0.625, 0.625, 0.625)), "cm") else: - center = unyt.unyt_array(np.array((1.5, 1.5, 1.5)), 'cm') - bbox = unyt.unyt_array(np.array([[0., 3.], [0., 3.], [0., 3.]]), 'cm') + center = unyt.unyt_array(np.array((1.5, 1.5, 1.5)), "cm") + bbox = unyt.unyt_array(np.array([[0.0, 3.0], [0.0, 3.0], [0.0, 3.0]]), "cm") hsml_factor = 0.5 unitrho = 1.5 + # test correct centering, particle selection def makemasses(i, j, k): if i == j == k == 1: - return 2. + return 2.0 elif i == j == k == 2: - return 3. + return 3.0 else: - return 1. + return 1.0 # result shouldn't depend explicitly on the center if we re-center # the data, unless we get cut-offs in the non-periodic case - ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, - periodic=periodic, - offsets=np.full(3, 0.5), - massgenerator=makemasses, - unitrho=unitrho, - bbox=bbox.v, - recenter=center.v) + ds = fake_sph_flexible_grid_ds( + hsml_factor=hsml_factor, + nperside=3, + periodic=periodic, + offsets=np.full(3, 0.5), + massgenerator=makemasses, + unitrho=unitrho, + bbox=bbox.v, + recenter=center.v, + ) ad = ds.all_data() - #print(ad[('gas', 'position')]) + # print(ad[('gas', 'position')]) outgridsize = 10 width = 2.5 _center = center.to("cm").v.copy() @@ -204,20 +225,27 @@ def makemasses(i, j, k): # we don't actually want a plot, it's just a straightforward, # common way to get an frb / image array - slc = yt.SlicePlot(ds, axis, ("gas", "density"), - width=(width, "cm"), - buff_size=(outgridsize,) * 2, - center=(_center, "cm")) - img = slc.frb.data[('gas', 'density')] + slc = yt.SlicePlot( + ds, + axis, + ("gas", "density"), + width=(width, "cm"), + buff_size=(outgridsize,) * 2, + center=(_center, "cm"), + ) + img = slc.frb.data[("gas", "density")] # center is same in non-projection coords if axis == 0: ci = 1 else: ci = 0 - gridcens = _center[ci] - 0.5 * width \ - + 0.5 * width / outgridsize \ - + np.arange(outgridsize) * width / outgridsize + gridcens = ( + _center[ci] + - 0.5 * width + + 0.5 * width / outgridsize + + np.arange(outgridsize) * width / outgridsize + ) xgrid = np.repeat(gridcens, outgridsize) ygrid = np.tile(gridcens, outgridsize) zgrid = np.full(outgridsize**2, _center[axis]) @@ -235,47 +263,56 @@ def makemasses(i, j, k): gridcoords[:, 1] = zgrid gridcoords[:, 2] = xgrid ad = ds.all_data() - sphcoords = np.array([(ad[("gas", "x")]).to("cm"), - (ad[("gas", "y")]).to("cm"), - (ad[("gas", "z")]).to("cm"), - ]).T - print('sphcoords:') + sphcoords = np.array( + [ + (ad[("gas", "x")]).to("cm"), + (ad[("gas", "y")]).to("cm"), + (ad[("gas", "z")]).to("cm"), + ] + ).T + print("sphcoords:") print(sphcoords) - print('gridcoords:') + print("gridcoords:") print(gridcoords) - dists = distancematrix(gridcoords, sphcoords, - periodic=(periodic,)*3, - periods=np.array([3., 3., 3.])) - print('dists <= 1:') + dists = distancematrix( + gridcoords, + sphcoords, + periodic=(periodic,) * 3, + periods=np.array([3.0, 3.0, 3.0]), + ) + print("dists <= 1:") print(dists <= 1) sml = (ad[("gas", "smoothing_length")]).to("cm") normkern = cubicspline_python(dists / sml.v[np.newaxis, :]) - sphcontr = normkern / sml[np.newaxis, :]**3 * ad[("gas", "mass")] + sphcontr = normkern / sml[np.newaxis, :] ** 3 * ad[("gas", "mass")] contsum = np.sum(sphcontr, axis=1) - sphweights = normkern / sml[np.newaxis, :]**3 \ - * ad[("gas", "mass")] / ad[("gas", "density")] + sphweights = ( + normkern + / sml[np.newaxis, :] ** 3 + * ad[("gas", "mass")] + / ad[("gas", "density")] + ) weights = np.sum(sphweights, axis=1) expected = contsum / weights expected = expected.reshape((outgridsize, outgridsize)) - expected[np.isnan(expected)] = 0. # convention in the slices + expected[np.isnan(expected)] = 0.0 # convention in the slices - print('expected:\n', expected.v) - print('recovered:\n', img.v) + print("expected:\n", expected.v) + print("recovered:\n", img.v) assert_rel_equal(expected.v, img.v, 5) - @pytest.mark.parametrize("periodic", [True, False]) @pytest.mark.parametrize("shiftcenter", [False, True]) -@pytest.mark.parametrize("northvector", [None, (1.e-4, 1., 0.)]) -@pytest.mark.parametrize("zoff", [0., 0.1, 0.5, 1.]) +@pytest.mark.parametrize("northvector", [None, (1.0e-4, 1.0, 0.0)]) +@pytest.mark.parametrize("zoff", [0.0, 0.1, 0.5, 1.0]) def test_sph_slice_general_offaxis( - northvector: tuple[float, float, float] | None, - shiftcenter: bool, - zoff: float, - periodic: bool, - ) -> None: - ''' + northvector: tuple[float, float, float] | None, + shiftcenter: bool, + zoff: float, + periodic: bool, +) -> None: + """ Same as the on-axis slices, but we rotate the basis vectors to test whether roations are handled ok. the rotation is chosen to be small so that in/exclusion of particles within bboxes, etc. @@ -300,29 +337,31 @@ def test_sph_slice_general_offaxis( Returns: -------- None - ''' + """ if shiftcenter: - center = np.array((0.625, 0.625, 0.625)) # cm + center = np.array((0.625, 0.625, 0.625)) # cm else: - center = np.array((1.5, 1.5, 1.5)) # cm - bbox = unyt.unyt_array(np.array([[0., 3.], [0., 3.], [0., 3.]]), 'cm') + center = np.array((1.5, 1.5, 1.5)) # cm + bbox = unyt.unyt_array(np.array([[0.0, 3.0], [0.0, 3.0], [0.0, 3.0]]), "cm") hsml_factor = 0.5 unitrho = 1.5 + # test correct centering, particle selection def makemasses(i, j, k): if i == j == k == 1: - return 2. + return 2.0 else: - return 1. + return 1.0 + # try to make sure dl differences from periodic wrapping are small epsilon = 1e-4 - projaxis = np.array([epsilon, 0.00, np.sqrt(1. - epsilon**2)]) + projaxis = np.array([epsilon, 0.00, np.sqrt(1.0 - epsilon**2)]) e1dir = projaxis / np.sqrt(np.sum(projaxis**2)) if northvector is None: - e2dir = np.array([0., 1., 0.]) + e2dir = np.array([0.0, 1.0, 0.0]) else: e2dir = np.asarray(northvector) - e2dir = e2dir - np.sum(e1dir * e2dir) * e2dir # orthonormalize + e2dir = e2dir - np.sum(e1dir * e2dir) * e2dir # orthonormalize e2dir /= np.sqrt(np.sum(e2dir**2)) e3dir = np.cross(e2dir, e1dir) @@ -331,72 +370,99 @@ def makemasses(i, j, k): _center = center.copy() _center += zoff * e1dir - ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, - periodic=periodic, - offsets=np.full(3, 0.5), - massgenerator=makemasses, - unitrho=unitrho, - bbox=bbox.v, - recenter=center, - e1hat=e1dir, e2hat=e2dir, e3hat=e3dir) + ds = fake_sph_flexible_grid_ds( + hsml_factor=hsml_factor, + nperside=3, + periodic=periodic, + offsets=np.full(3, 0.5), + massgenerator=makemasses, + unitrho=unitrho, + bbox=bbox.v, + recenter=center, + e1hat=e1dir, + e2hat=e2dir, + e3hat=e3dir, + ) source = ds.all_data() # couple to dataset -> right unit registry - center = ds.arr(center, 'cm') - print('position:\n', source['gas','position']) - slc = yt.SlicePlot(ds, e1dir, ("gas", "density"), - width=(width, "cm"), - buff_size=(outgridsize,) * 2, - center=(_center, "cm"), - north_vector=e2dir) - img = slc.frb.data[('gas', 'density')] + center = ds.arr(center, "cm") + print("position:\n", source["gas", "position"]) + slc = yt.SlicePlot( + ds, + e1dir, + ("gas", "density"), + width=(width, "cm"), + buff_size=(outgridsize,) * 2, + center=(_center, "cm"), + north_vector=e2dir, + ) + img = slc.frb.data[("gas", "density")] # center is same in x/y (e3dir/e2dir) - gridcenx = np.dot(_center, e3dir) - 0.5 * width \ - + 0.5 * width / outgridsize \ - + np.arange(outgridsize) * width / outgridsize - gridceny = np.dot(_center, e2dir) - 0.5 * width \ - + 0.5 * width / outgridsize \ - + np.arange(outgridsize) * width / outgridsize + gridcenx = ( + np.dot(_center, e3dir) + - 0.5 * width + + 0.5 * width / outgridsize + + np.arange(outgridsize) * width / outgridsize + ) + gridceny = ( + np.dot(_center, e2dir) + - 0.5 * width + + 0.5 * width / outgridsize + + np.arange(outgridsize) * width / outgridsize + ) xgrid = np.repeat(gridcenx, outgridsize) ygrid = np.tile(gridceny, outgridsize) zgrid = np.full(outgridsize**2, np.dot(_center, e1dir)) - gridcoords = (xgrid[:, np.newaxis] * e3dir[np.newaxis, :] - + ygrid[:, np.newaxis] * e2dir[np.newaxis, :] - + zgrid[:, np.newaxis] * e1dir[np.newaxis, :]) - print('gridcoords:') + gridcoords = ( + xgrid[:, np.newaxis] * e3dir[np.newaxis, :] + + ygrid[:, np.newaxis] * e2dir[np.newaxis, :] + + zgrid[:, np.newaxis] * e1dir[np.newaxis, :] + ) + print("gridcoords:") print(gridcoords) ad = ds.all_data() - sphcoords = np.array([(ad[("gas", "x")]).to("cm"), - (ad[("gas", "y")]).to("cm"), - (ad[("gas", "z")]).to("cm"), - ]).T - dists = distancematrix(gridcoords, sphcoords, - periodic=(periodic,)*3, - periods=np.array([3., 3., 3.])) + sphcoords = np.array( + [ + (ad[("gas", "x")]).to("cm"), + (ad[("gas", "y")]).to("cm"), + (ad[("gas", "z")]).to("cm"), + ] + ).T + dists = distancematrix( + gridcoords, + sphcoords, + periodic=(periodic,) * 3, + periods=np.array([3.0, 3.0, 3.0]), + ) sml = (ad[("gas", "smoothing_length")]).to("cm") normkern = cubicspline_python(dists / sml.v[np.newaxis, :]) - sphcontr = normkern / sml[np.newaxis, :]**3 * ad[("gas", "mass")] + sphcontr = normkern / sml[np.newaxis, :] ** 3 * ad[("gas", "mass")] contsum = np.sum(sphcontr, axis=1) - sphweights = normkern / sml[np.newaxis, :]**3 \ - * ad[("gas", "mass")] / ad[("gas", "density")] + sphweights = ( + normkern + / sml[np.newaxis, :] ** 3 + * ad[("gas", "mass")] + / ad[("gas", "density")] + ) weights = np.sum(sphweights, axis=1) expected = contsum / weights expected = expected.reshape((outgridsize, outgridsize)) - expected = expected.T # transposed for image plotting - expected[np.isnan(expected)] = 0. # convention in the slices + expected = expected.T # transposed for image plotting + expected[np.isnan(expected)] = 0.0 # convention in the slices - #print(axis, shiftcenter, depth, periodic, weighted) - print('expected:\n', expected.v) - print('recovered:\n', img.v) + # print(axis, shiftcenter, depth, periodic, weighted) + print("expected:\n", expected.v) + print("recovered:\n", img.v) assert_rel_equal(expected.v, img.v, 4) + # only axis-aligned; testing YTArbitraryGrid, YTCoveringGrid @pytest.mark.parametrize("periodic", [True, False, (True, True, False)]) @pytest.mark.parametrize("wholebox", [True, False]) -def test_sph_grid(periodic: bool | tuple[bool, bool, bool], - wholebox: bool): - bbox = np.array([[-1., 3.], [1., 5.2], [-1., 3.]]) +def test_sph_grid(periodic: bool | tuple[bool, bool, bool], wholebox: bool): + bbox = np.array([[-1.0, 3.0], [1.0, 5.2], [-1.0, 3.0]]) ds = fake_random_sph_ds(50, bbox, periodic=periodic) if not hasattr(periodic, "__len__"): @@ -406,15 +472,15 @@ def test_sph_grid(periodic: bool | tuple[bool, bool, bool], left = bbox[:, 0].copy() level = 2 ncells = np.array([2**level] * 3) - print('left: ', left) - print('ncells: ', ncells) + print("left: ", left) + print("ncells: ", ncells) resgrid = ds.covering_grid(level, tuple(left), ncells) right = bbox[:, 1].copy() xedges = np.linspace(left[0], right[0], ncells[0] + 1) yedges = np.linspace(left[1], right[1], ncells[1] + 1) zedges = np.linspace(left[2], right[2], ncells[2] + 1) else: - left = np.array([-1., 1.8, -1.]) + left = np.array([-1.0, 1.8, -1.0]) right = np.array([2.5, 5.2, 2.5]) ncells = np.array([3, 4, 4]) resgrid = ds.arbitrary_grid(left, right, dims=ncells) @@ -427,33 +493,37 @@ def test_sph_grid(periodic: bool | tuple[bool, bool, bool], zcens = 0.5 * (zedges[:-1] + zedges[1:]) ad = ds.all_data() - sphcoords = np.array([(ad[("gas", "x")]).to("cm"), - (ad[("gas", "y")]).to("cm"), - (ad[("gas", "z")]).to("cm"), - ]).T - gridx, gridy, gridz = np.meshgrid(xcens, ycens, zcens, - indexing='ij') + sphcoords = np.array( + [ + (ad[("gas", "x")]).to("cm"), + (ad[("gas", "y")]).to("cm"), + (ad[("gas", "z")]).to("cm"), + ] + ).T + gridx, gridy, gridz = np.meshgrid(xcens, ycens, zcens, indexing="ij") outshape = gridx.shape gridx = gridx.flatten() gridy = gridy.flatten() gridz = gridz.flatten() gridcoords = np.array([gridx, gridy, gridz]).T periods = bbox[:, 1] - bbox[:, 0] - dists = distancematrix(gridcoords, sphcoords, - periodic=periodic, - periods=periods) + dists = distancematrix(gridcoords, sphcoords, periodic=periodic, periods=periods) sml = (ad[("gas", "smoothing_length")]).to("cm") normkern = cubicspline_python(dists / sml.v[np.newaxis, :]) - sphcontr = normkern / sml[np.newaxis, :]**3 * ad[("gas", "mass")] + sphcontr = normkern / sml[np.newaxis, :] ** 3 * ad[("gas", "mass")] contsum = np.sum(sphcontr, axis=1) - sphweights = normkern / sml[np.newaxis, :]**3 \ - * ad[("gas", "mass")] / ad[("gas", "density")] + sphweights = ( + normkern + / sml[np.newaxis, :] ** 3 + * ad[("gas", "mass")] + / ad[("gas", "density")] + ) weights = np.sum(sphweights, axis=1) expected = contsum / weights expected = expected.reshape(outshape) - expected[np.isnan(expected)] = 0. # convention in the slices + expected[np.isnan(expected)] = 0.0 # convention in the slices - #print(axis, shiftcenter, depth, periodic, weighted) - print('expected:\n', expected.v) - print('recovered:\n', res.v) + # print(axis, shiftcenter, depth, periodic, weighted) + print("expected:\n", expected.v) + print("recovered:\n", res.v) assert_rel_equal(expected.v, res.v, 4) diff --git a/yt/testing.py b/yt/testing.py index af4e6998277..43f4ba3e60a 100644 --- a/yt/testing.py +++ b/yt/testing.py @@ -26,8 +26,6 @@ from yt.loaders import load, load_particles from yt.units.yt_array import YTArray, YTQuantity - - ANSWER_TEST_TAG = "answer_test" @@ -84,10 +82,12 @@ def assert_rel_equal(a1, a2, decimals, err_msg="", verbose=True): np.array(a1) / np.array(a2), 1.0, decimals, err_msg=err_msg, verbose=verbose ) + # tested: volume integral is 1. + def cubicspline_python(x: Union[float,npt.ndarray[float]], ) -> npt.ndarray[float]: - ''' + """ cubic spline SPH kernel function for testing against more effiecient cython methods @@ -99,19 +99,21 @@ def cubicspline_python(x: Union[float,npt.ndarray[float]], Returns ------- value of the kernel function - ''' + """ # C is 8/pi - _c = 8. / np.pi + _c = 8.0 / np.pi x = np.asarray(x) kernel = np.zeros(x.shape, dtype=x.dtype) - half1 = np.where(np.logical_and(x >=0., x <= 0.5)) - kernel[half1] = 1. - 6. * x[half1]**2 * (1. - x[half1]) + half1 = np.where(np.logical_and(x >= 0.0, x <= 0.5)) + kernel[half1] = 1.0 - 6.0 * x[half1] ** 2 * (1.0 - x[half1]) half2 = np.where(np.logical_and(x > 0.5, x <= 1.0)) - kernel[half2] = 2. * (1. - x[half2])**3 + kernel[half2] = 2.0 * (1.0 - x[half2]) ** 3 return kernel * _c -def integrate_kernel(kernelfunc: Callable[[float], float], - b: float, hsml: float) -> float: + +def integrate_kernel( + kernelfunc: Callable[[float], float], b: float, hsml: float +) -> float: """ integrates a kernel function over a line passing entirely through it @@ -130,24 +132,27 @@ def integrate_kernel(kernelfunc: Callable[[float], float], the integral of the SPH kernel function. units: 1 / units of b and hsml """ - pre = 1. / hsml**2 + pre = 1.0 / hsml**2 x = b / hsml - xmax = np.sqrt(1. - x**2) - xmin = -1. * xmax - xe = np.linspace(xmin, xmax, 500) # shape: 500, x.shape + xmax = np.sqrt(1.0 - x**2) + xmin = -1.0 * xmax + xe = np.linspace(xmin, xmax, 500) # shape: 500, x.shape xc = 0.5 * (xe[:-1, ...] + xe[1:, ...]) dx = np.diff(xe, axis=0) spv = kernelfunc(np.sqrt(xc**2 + x**2)) integral = np.sum(spv * dx, axis=0) return pre * integral -_zeroperiods = np.array([0., 0., 0.]) -def distancematrix(pos3_i0: npt.ndarray[float], - pos3_i1: npt.ndarray[float], - periodic: tuple[bool, bool, bool] = (True,) * 3, - periods: npt.ndarray[float] = _zeroperiods, - ) -> npt.ndarray[float]: - ''' +_zeroperiods = np.array([0.0, 0.0, 0.0]) + + +def distancematrix( + pos3_i0: np.ndarray[float], + pos3_i1: np.ndarray[float], + periodic: tuple[bool] = (True,) * 3, + periods: np.ndarray = _zeroperiods, +) -> np.ndarray[float]: + """ Calculates the distances between two arrays of points. Parameters: @@ -168,16 +173,16 @@ def distancematrix(pos3_i0: npt.ndarray[float], a 2D-array of distances between postions `pos3_i0` (changes along index 0) and `pos3_i1` (changes along index 1) - ''' + """ d2 = np.zeros((len(pos3_i0), len(pos3_i1)), dtype=pos3_i0.dtype) for ax in range(3): # 'center on' pos3_i1 _d = pos3_i0[:, ax, np.newaxis] - pos3_i1[np.newaxis, :, ax] if periodic[ax]: _period = periods[ax] - _d += 0.5 * _period # center on half box size - _d %= _period # wrap coordinate to 0 -- boxsize range - _d -= 0.5 * _period # center back to zero + _d += 0.5 * _period # center on half box size + _d %= _period # wrap coordinate to 0 -- boxsize range + _d -= 0.5 * _period # center back to zero d2 += _d**2 return np.sqrt(d2) @@ -789,25 +794,28 @@ def fake_sph_grid_ds(hsml_factor=1.0): def constantmass(i: int, j: int, k: int) -> float: - return 1. + return 1.0 + _xhat = np.array([1, 0, 0]) _yhat = np.array([0, 1, 0]) _zhat = np.array([0, 0, 1]) _floathalves = 0.5 * np.ones((3,), dtype=np.float64) + + def fake_sph_flexible_grid_ds( - hsml_factor: float = 1.0, - nperside: int = 3, - periodic: bool = True, - e1hat: npt.ndarray[float] = _xhat, - e2hat: npt.ndarray[float] = _yhat, - e3hat: npt.ndarray[float] = _zhat, - offsets: npt.ndarray[float] = _floathalves, - massgenerator: Callable[[int, int, int], float] = constantmass, - unitrho: float = 1., - bbox: Union[npt.ndarray[float], None] = None, - recenter: Union[npt.ndarray[float], None] = None, - ) -> StreamParticlesDataset: + hsml_factor: float = 1.0, + nperside: int = 3, + periodic: bool = True, + e1hat: np.ndarray[float] = _xhat, + e2hat: np.ndarray[float] = _yhat, + e3hat: np.ndarray[float] = _zhat, + offsets: np.ndarray[float] = _floathalves, + massgenerator: Callable[[int, int, int], float] = constantmass, + unitrho: float = 1.0, + bbox: np.ndarray | None = None, + recenter: np.ndarray | None = None, +) -> StreamParticlesDataset: """Returns an in-memory SPH dataset useful for testing Parameters: @@ -864,9 +872,11 @@ def fake_sph_flexible_grid_ds( for i in range(0, nperside): for j in range(0, nperside): for k in range(0, nperside): - _pos = (offsets[0] + i) * e1hat \ - + (offsets[1] + j) * e2hat \ - + (offsets[2] + k) * e3hat + _pos = ( + (offsets[0] + i) * e1hat + + (offsets[1] + j) * e2hat + + (offsets[2] + k) * e3hat + ) ind = nperside**2 * i + nperside * j + k pos[ind, :] = _pos mass[ind] = massgenerator(i, j, k) @@ -874,14 +884,14 @@ def fake_sph_flexible_grid_ds( if bbox is None: eps = 1e-3 - margin = (1. + eps) * hsml_factor - bbox = np.array([[np.min(pos[:, 0]) - margin, - np.max(pos[:, 0]) + margin], - [np.min(pos[:, 1]) - margin, - np.max(pos[:, 1]) + margin], - [np.min(pos[:, 2]) - margin, - np.max(pos[:, 2]) + margin], - ]) + margin = (1.0 + eps) * hsml_factor + bbox = np.array( + [ + [np.min(pos[:, 0]) - margin, np.max(pos[:, 0]) + margin], + [np.min(pos[:, 1]) - margin, np.max(pos[:, 1]) + margin], + [np.min(pos[:, 2]) - margin, np.max(pos[:, 2]) + margin], + ] + ) if recenter is not None: periods = bbox[:, 1] - bbox[:, 0] @@ -913,21 +923,26 @@ def fake_sph_flexible_grid_ds( "density": (rho[okinds], "g/cm**3"), } - ds = load_particles(data=data, - bbox=bbox, periodicity=(periodic,) * 3, - length_unit=1., mass_unit=1., time_unit=1., - velocity_unit=1.) - ds.kernel_name = 'cubic' + ds = load_particles( + data=data, + bbox=bbox, + periodicity=(periodic,) * 3, + length_unit=1.0, + mass_unit=1.0, + time_unit=1.0, + velocity_unit=1.0, + ) + ds.kernel_name = "cubic" return ds - -def fake_random_sph_ds(npart: int, - bbox: npt.ndarray[float], - periodic: Union[bool, tuple[bool, bool, bool]] = True, - massrange: tuple[float, float] = (0.5, 2.), - hsmlrange: tuple[float, float] = (0.5, 2.), - unitrho: float = 1., - ) -> StreamParticlesDataset: +def fake_random_sph_ds( + npart: int, + bbox: np.ndarray, + periodic: bool | tuple[bool, bool, bool] = True, + massrange: tuple[float, float] = (0.5, 2.0), + hsmlrange: tuple[float, float] = (0.5, 2.0), + unitrho: float = 1.0, +) -> StreamParticlesDataset: """Returns an in-memory SPH dataset useful for testing Parameters: @@ -956,7 +971,7 @@ def fake_random_sph_ds(npart: int, """ if not hasattr(periodic, "__len__"): - periodic = (periodic, ) * 3 + periodic = (periodic,) * 3 gen = np.random.default_rng(seed=0) posx = gen.uniform(low=bbox[0][0], high=bbox[0][1], size=npart) @@ -978,11 +993,16 @@ def fake_random_sph_ds(npart: int, "density": (dens, "g/cm**3"), } - ds = load_particles(data=data, - bbox=bbox, periodicity=periodic, - length_unit=1., mass_unit=1., time_unit=1., - velocity_unit=1.) - ds.kernel_name = 'cubic' + ds = load_particles( + data=data, + bbox=bbox, + periodicity=periodic, + length_unit=1.0, + mass_unit=1.0, + time_unit=1.0, + velocity_unit=1.0, + ) + ds.kernel_name = "cubic" return ds diff --git a/yt/visualization/plot_window.py b/yt/visualization/plot_window.py index 5afd02bd097..8176065f472 100644 --- a/yt/visualization/plot_window.py +++ b/yt/visualization/plot_window.py @@ -83,8 +83,9 @@ def get_window_parameters(axis, center, width, ds): return (bounds, center, display_center) -def get_oblique_window_parameters(normal, center, width, ds, - depth=None, get3bounds=False): +def get_oblique_window_parameters( + normal, center, width, ds, depth=None, get3bounds=False +): center, display_center = ds.coordinates.sanitize_center(center, axis=None) width = ds.coordinates.sanitize_width(normal, width, depth) @@ -92,8 +93,9 @@ def get_oblique_window_parameters(normal, center, width, ds, # Transforming to the cutting plane coordinate system # the original dimensionless center messes up off-axis # SPH projections though -> don't use this center there - center = ((center - ds.domain_left_edge) / ds.domain_width - 0.5)\ - * ds.domain_width + center = ( + (center - ds.domain_left_edge) / ds.domain_width - 0.5 + ) * ds.domain_width (normal, perp1, perp2) = ortho_find(normal) mat = np.transpose(np.column_stack((perp1, perp2, normal))) center = np.dot(mat, center) @@ -103,9 +105,9 @@ def get_oblique_window_parameters(normal, center, width, ds, if get3bounds and depth is None: # off-axis projection, depth not specified # -> set 'large enough' depth using half the box diagonal + margin - d2 = ds.domain_width[0].in_units("code_length")**2 - d2 += ds.domain_width[1].in_units("code_length")**2 - d2 += ds.domain_width[2].in_units("code_length")**2 + d2 = ds.domain_width[0].in_units("code_length") ** 2 + d2 += ds.domain_width[1].in_units("code_length") ** 2 + d2 += ds.domain_width[2].in_units("code_length") ** 2 diag = np.sqrt(d2) bounds = bounds + (-0.51 * diag, 0.51 * diag) return (bounds, center) @@ -1832,12 +1834,12 @@ def __init__( normal = self.sanitize_normal_vector(ds, normal) # this will handle time series data and controllers axis = fix_axis(normal, ds) - #print('center at SlicePlot init: ', center) - #print('current domain left edge: ', ds.domain_left_edge) + # print('center at SlicePlot init: ', center) + # print('current domain left edge: ', ds.domain_left_edge) (bounds, center, display_center) = get_window_parameters( axis, center, width, ds ) - # print('center after get_window_parameters: ', center) + # print('center after get_window_parameters: ', center) if field_parameters is None: field_parameters = {} @@ -2473,7 +2475,12 @@ def __init__( # get3bounds gets a depth 0.5 * diagonal + margin in the # depth=None case. (bounds, center_rot) = get_oblique_window_parameters( - normal, center, width, ds, depth=depth, get3bounds=True, + normal, + center, + width, + ds, + depth=depth, + get3bounds=True, ) # will probably fail if you try to project an SPH and non-SPH # field in a single call @@ -2489,16 +2496,15 @@ def __init__( is_sph_field = finfo.is_sph_field particle_datasets = (ParticleDataset, StreamParticlesDataset) - if isinstance(data_source.ds, particle_datasets) and is_sph_field: - center_use = parse_center_array(center, ds=data_source.ds, - axis=None) + if isinstance(data_source.ds, particle_datasets) and is_sph_field: + center_use = parse_center_array(center, ds=data_source.ds, axis=None) else: center_use = center_rot fields = list(iter_fields(fields))[:] - #oap_width = ds.arr( + # oap_width = ds.arr( # (bounds[1] - bounds[0], # bounds[3] - bounds[2]) - #) + # ) OffAxisProj = OffAxisProjectionDummyDataSource( center_use, ds, diff --git a/yt/visualization/tests/test_offaxisprojection.py b/yt/visualization/tests/test_offaxisprojection.py index 89a610f2987..34dfbf49aa9 100644 --- a/yt/visualization/tests/test_offaxisprojection.py +++ b/yt/visualization/tests/test_offaxisprojection.py @@ -243,13 +243,13 @@ def _vlos_sq(field, data): ## first assert_rel_equal argument. The compute_stddev_image ## function used in OffAxisProjectionPlot checks for and deals ## with these cases. - #assert_rel_equal( + # assert_rel_equal( # np.sqrt( # p1.frb["gas", "velocity_los_squared"] - p1.frb["gas", "velocity_los"] ** 2 # ), # p2.frb["gas", "velocity_los"], # 10, - #) + # ) p1_expsq = p1.frb["gas", "velocity_los_squared"] p1_sqexp = p1.frb["gas", "velocity_los"] ** 2 p1res = np.sqrt(p1_expsq - p1_sqexp) @@ -257,11 +257,13 @@ def _vlos_sq(field, data): # the absolute values are much smaller than the smallest # postive values of **2 and **2 # (i.e., the difference is pretty much zero) - mindiff = 1e-10 * min(np.min(p1_expsq[p1_expsq > 0]), - np.min(p1_sqexp[p1_sqexp > 0])) + mindiff = 1e-10 * min( + np.min(p1_expsq[p1_expsq > 0]), np.min(p1_sqexp[p1_sqexp > 0]) + ) print(mindiff) - setzero = np.logical_and(p1_expsq - p1_sqexp < 0, - p1_expsq - p1_sqexp > -1. * mindiff) - p1res[setzero] = 0. + setzero = np.logical_and( + p1_expsq - p1_sqexp < 0, p1_expsq - p1_sqexp > -1.0 * mindiff + ) + p1res[setzero] = 0.0 p2res = p2.frb["gas", "velocity_los"] assert_rel_equal(p1res, p2res, 10) diff --git a/yt/visualization/tests/test_offaxisprojection_pytestonly.py b/yt/visualization/tests/test_offaxisprojection_pytestonly.py index 5a1f013313f..67db76db373 100644 --- a/yt/visualization/tests/test_offaxisprojection_pytestonly.py +++ b/yt/visualization/tests/test_offaxisprojection_pytestonly.py @@ -10,18 +10,20 @@ ) from yt.visualization.api import ProjectionPlot + @pytest.mark.parametrize("weighted", [True, False]) @pytest.mark.parametrize("periodic", [True, False]) -@pytest.mark.parametrize("depth", [None, (1., "cm"), (0.5, "cm")]) +@pytest.mark.parametrize("depth", [None, (1.0, "cm"), (0.5, "cm")]) @pytest.mark.parametrize("shiftcenter", [False, True]) -@pytest.mark.parametrize("northvector", [None, (1.e-4, 1., 0.)]) +@pytest.mark.parametrize("northvector", [None, (1.0e-4, 1.0, 0.0)]) def test_sph_proj_general_offaxis( - northvector: tuple[float, float, float] | None, - shiftcenter: bool, - depth: tuple[float, str] | None, - periodic: bool, - weighted: bool) -> None: - ''' + northvector: tuple[float, float, float] | None, + shiftcenter: bool, + depth: tuple[float, str] | None, + periodic: bool, + weighted: bool, +) -> None: + """ Same as the on-axis projections, but we rotate the basis vectors to test whether roations are handled ok. the rotation is chosen to be small so that in/exclusion of particles within bboxes, etc. @@ -49,55 +51,61 @@ def test_sph_proj_general_offaxis( Returns: -------- None - ''' + """ if shiftcenter: - center = np.array((0.625, 0.625, 0.625)) # cm + center = np.array((0.625, 0.625, 0.625)) # cm else: - center = np.array((1.5, 1.5, 1.5)) # cm - bbox = unyt.unyt_array(np.array([[0., 3.], [0., 3.], [0., 3.]]), 'cm') + center = np.array((1.5, 1.5, 1.5)) # cm + bbox = unyt.unyt_array(np.array([[0.0, 3.0], [0.0, 3.0], [0.0, 3.0]]), "cm") hsml_factor = 0.5 unitrho = 1.5 + # test correct centering, particle selection def makemasses(i, j, k): if i == j == k == 1: - return 2. + return 2.0 else: - return 1. + return 1.0 # result shouldn't depend explicitly on the center if we re-center # the data, unless we get cut-offs in the non-periodic case # *almost* the z-axis # try to make sure dl differences from periodic wrapping are small epsilon = 1e-4 - projaxis = np.array([epsilon, 0.00, np.sqrt(1. - epsilon**2)]) + projaxis = np.array([epsilon, 0.00, np.sqrt(1.0 - epsilon**2)]) e1dir = projaxis / np.sqrt(np.sum(projaxis**2)) # TODO: figure out other (default) axes for basis vectors here if northvector is None: - e2dir = np.array([0., 1., 0.]) + e2dir = np.array([0.0, 1.0, 0.0]) else: e2dir = np.asarray(northvector) - e2dir = e2dir - np.sum(e1dir * e2dir) * e2dir # orthonormalize + e2dir = e2dir - np.sum(e1dir * e2dir) * e2dir # orthonormalize e2dir /= np.sqrt(np.sum(e2dir**2)) e3dir = np.cross(e1dir, e2dir) - ds = fake_sph_flexible_grid_ds(hsml_factor=hsml_factor, nperside=3, - periodic=periodic, - offsets=np.full(3, 0.5), - massgenerator=makemasses, - unitrho=unitrho, - bbox=bbox.v, - recenter=center, - e1hat=e1dir, e2hat=e2dir, e3hat=e3dir) + ds = fake_sph_flexible_grid_ds( + hsml_factor=hsml_factor, + nperside=3, + periodic=periodic, + offsets=np.full(3, 0.5), + massgenerator=makemasses, + unitrho=unitrho, + bbox=bbox.v, + recenter=center, + e1hat=e1dir, + e2hat=e2dir, + e3hat=e3dir, + ) source = ds.all_data() # couple to dataset -> right unit registry - center = ds.arr(center, 'cm') - #print('position:\n', source['gas','position']) + center = ds.arr(center, "cm") + # print('position:\n', source['gas','position']) # m / rho, factor 1. / hsml**2 is included in the kernel integral # (density is adjusted, so same for center particle) - prefactor = 1. / unitrho #/ (0.5 * 0.5)**2 - dl_cen = integrate_kernel(cubicspline_python, 0., 0.25) + prefactor = 1.0 / unitrho # / (0.5 * 0.5)**2 + dl_cen = integrate_kernel(cubicspline_python, 0.0, 0.25) if weighted: toweight_field = ("gas", "density") @@ -105,39 +113,55 @@ def makemasses(i, j, k): toweight_field = None # we don't actually want a plot, it's just a straightforward, # common way to get an frb / image array - prj = ProjectionPlot(ds, projaxis, ("gas", "density"), - width=(2.5, "cm"), - weight_field=toweight_field, - buff_size=(5, 5), - center=center, - data_source=source, - north_vector=northvector, - depth=depth) - img = prj.frb.data[('gas', 'density')] + prj = ProjectionPlot( + ds, + projaxis, + ("gas", "density"), + width=(2.5, "cm"), + weight_field=toweight_field, + buff_size=(5, 5), + center=center, + data_source=source, + north_vector=northvector, + depth=depth, + ) + img = prj.frb.data[("gas", "density")] if weighted: # periodic shifts will modify the (relative) dl values a bit - expected_out = np.zeros((5, 5,), dtype=img.v.dtype) + expected_out = np.zeros( + ( + 5, + 5, + ), + dtype=img.v.dtype, + ) expected_out[::2, ::2] = unitrho if depth is None: expected_out[2, 2] *= 1.5 else: # only 2 * unitrho element included - expected_out[2, 2] *= 2. + expected_out[2, 2] *= 2.0 else: - expected_out = np.zeros((5, 5,), dtype=img.v.dtype) + expected_out = np.zeros( + ( + 5, + 5, + ), + dtype=img.v.dtype, + ) expected_out[::2, ::2] = dl_cen * prefactor * unitrho if depth is None: # 3 particles per l.o.s., including the denser one - expected_out *= 3. - expected_out[2, 2] *= 4. / 3. + expected_out *= 3.0 + expected_out[2, 2] *= 4.0 / 3.0 else: # 1 particle per l.o.s., including the denser one - expected_out[2, 2] *= 2. + expected_out[2, 2] *= 2.0 # grid is shifted to the left -> 'missing' stuff at the left if (not periodic) and shiftcenter: - expected_out[:1, :] = 0. - expected_out[:, :1] = 0. - #print(axis, shiftcenter, depth, periodic, weighted) - print('expected:\n', expected_out) - print('recovered:\n', img.v) + expected_out[:1, :] = 0.0 + expected_out[:, :1] = 0.0 + # print(axis, shiftcenter, depth, periodic, weighted) + print("expected:\n", expected_out) + print("recovered:\n", img.v) assert_rel_equal(expected_out, img.v, 4) diff --git a/yt/visualization/volume_rendering/off_axis_projection.py b/yt/visualization/volume_rendering/off_axis_projection.py index b3f579b9cd8..d3765222c90 100644 --- a/yt/visualization/volume_rendering/off_axis_projection.py +++ b/yt/visualization/volume_rendering/off_axis_projection.py @@ -159,8 +159,7 @@ def off_axis_projection( if hasattr(depth, "units"): depth = depth.to("code_length").d - #depth = data_source.ds.arr(depth, "code_length") - + # depth = data_source.ds.arr(depth, "code_length") if hasattr(data_source.ds, "_sph_ptypes"): if method != "integrate": @@ -220,12 +219,12 @@ def off_axis_projection( mask = np.ones_like(buf, dtype="uint8") ## width from fixed_resolution.py is just the size of the domain - #x_min = center[0] - width[0] / 2 - #x_max = center[0] + width[0] / 2 - #y_min = center[1] - width[1] / 2 - #y_max = center[1] + width[1] / 2 - #z_min = center[2] - width[2] / 2 - #z_max = center[2] + width[2] / 2 + # x_min = center[0] - width[0] / 2 + # x_max = center[0] + width[0] / 2 + # y_min = center[1] - width[1] / 2 + # y_max = center[1] + width[1] / 2 + # z_min = center[2] - width[2] / 2 + # z_max = center[2] + width[2] / 2 periodic = data_source.ds.periodicity le = data_source.ds.domain_left_edge.to("code_length").d diff --git a/yt/visualization/volume_rendering/old_camera.py b/yt/visualization/volume_rendering/old_camera.py index 0ca8489814f..fd171dbc80a 100644 --- a/yt/visualization/volume_rendering/old_camera.py +++ b/yt/visualization/volume_rendering/old_camera.py @@ -2438,6 +2438,7 @@ def _render(self, double_check, num_threads, image, sampler, msg): data_object_registry["stereospherical_camera"] = StereoSphericalCamera + # replaced in volume_rendering API by the function of the same name in # yt/visualization/volume_rendering/off_axis_projection def off_axis_projection( From d4e5bfb9728e49211162a079facef04dccd90736 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 18 Jul 2024 20:21:29 +0000 Subject: [PATCH 39/64] [pre-commit.ci] auto fixes from pre-commit.com hooks keep those fixes in merge for more information, see https://pre-commit.ci --- yt/testing.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/yt/testing.py b/yt/testing.py index 43f4ba3e60a..b4d00c78754 100644 --- a/yt/testing.py +++ b/yt/testing.py @@ -84,9 +84,9 @@ def assert_rel_equal(a1, a2, decimals, err_msg="", verbose=True): # tested: volume integral is 1. - -def cubicspline_python(x: Union[float,npt.ndarray[float]], - ) -> npt.ndarray[float]: +def cubicspline_python( + x: Union[float, npt.ndarray[float]], +) -> npt.ndarray[float]: """ cubic spline SPH kernel function for testing against more effiecient cython methods @@ -143,6 +143,7 @@ def integrate_kernel( integral = np.sum(spv * dx, axis=0) return pre * integral + _zeroperiods = np.array([0.0, 0.0, 0.0]) @@ -813,8 +814,8 @@ def fake_sph_flexible_grid_ds( offsets: np.ndarray[float] = _floathalves, massgenerator: Callable[[int, int, int], float] = constantmass, unitrho: float = 1.0, - bbox: np.ndarray | None = None, - recenter: np.ndarray | None = None, + bbox: Union[npt.ndarray[float], None] = None, + recenter: Union[npt.ndarray[float], None] = None, ) -> StreamParticlesDataset: """Returns an in-memory SPH dataset useful for testing @@ -937,8 +938,8 @@ def fake_sph_flexible_grid_ds( def fake_random_sph_ds( npart: int, - bbox: np.ndarray, - periodic: bool | tuple[bool, bool, bool] = True, + bbox: npt.ndarray[float], + periodic: Union[bool, tuple[bool, bool, bool]] = True, massrange: tuple[float, float] = (0.5, 2.0), hsmlrange: tuple[float, float] = (0.5, 2.0), unitrho: float = 1.0, From b4f493587add9a1c53e3ed541a49630c4e4ea08c Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Thu, 18 Jul 2024 15:44:36 -0500 Subject: [PATCH 40/64] attempting to pass type-checking --- .../tests/test_sph_pixelization_pytestonly.py | 20 ++++++++--- yt/loaders.py | 2 +- yt/testing.py | 34 +++++++++---------- .../test_offaxisprojection_pytestonly.py | 6 ++-- 4 files changed, 38 insertions(+), 24 deletions(-) diff --git a/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py b/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py index 945286af0c2..44983c1120f 100644 --- a/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py +++ b/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py @@ -1,3 +1,5 @@ +from typing import Union + import numpy as np import pytest import unyt @@ -20,7 +22,11 @@ @pytest.mark.parametrize("shiftcenter", [False, True]) @pytest.mark.parametrize("axis", [0, 1, 2]) def test_sph_proj_general_alongaxes( - axis: int, shiftcenter: bool, depth: float | None, periodic: bool, weighted: bool + axis: int, + shiftcenter: bool, + depth: Union[float, None], + periodic: bool, + weighted: bool, ) -> None: """ The previous projection tests were for a specific issue. @@ -161,7 +167,10 @@ def makemasses(i, j, k): @pytest.mark.parametrize("zoff", [0.0, 0.1, 0.5, 1.0]) @pytest.mark.parametrize("axis", [0, 1, 2]) def test_sph_slice_general_alongaxes( - axis: int, shiftcenter: bool, periodic: bool, zoff: float + axis: int, + shiftcenter: bool, + periodic: bool, + zoff: float, ) -> None: """ Particles at [0.5, 1.5, 2.5] (in each coordinate) @@ -307,7 +316,7 @@ def makemasses(i, j, k): @pytest.mark.parametrize("northvector", [None, (1.0e-4, 1.0, 0.0)]) @pytest.mark.parametrize("zoff", [0.0, 0.1, 0.5, 1.0]) def test_sph_slice_general_offaxis( - northvector: tuple[float, float, float] | None, + northvector: Union[tuple[float, float, float], None], shiftcenter: bool, zoff: float, periodic: bool, @@ -461,7 +470,10 @@ def makemasses(i, j, k): # only axis-aligned; testing YTArbitraryGrid, YTCoveringGrid @pytest.mark.parametrize("periodic", [True, False, (True, True, False)]) @pytest.mark.parametrize("wholebox", [True, False]) -def test_sph_grid(periodic: bool | tuple[bool, bool, bool], wholebox: bool): +def test_sph_grid( + periodic: Union[bool, tuple[bool, bool, bool]], + wholebox: bool, +) -> None: bbox = np.array([[-1.0, 3.0], [1.0, 5.2], [-1.0, 3.0]]) ds = fake_random_sph_ds(50, bbox, periodic=periodic) diff --git a/yt/loaders.py b/yt/loaders.py index aa7abea55f9..1a19c5583a3 100644 --- a/yt/loaders.py +++ b/yt/loaders.py @@ -694,7 +694,7 @@ def load_amr_grids( def load_particles( - data: dict[AnyFieldKey, Union[np.ndarray, tuple[np.ndarry, str]]], + data: dict[AnyFieldKey, Union[np.ndarray, tuple[np.ndarray, str]]], length_unit=None, bbox=None, sim_time=None, diff --git a/yt/testing.py b/yt/testing.py index b4d00c78754..b809835310b 100644 --- a/yt/testing.py +++ b/yt/testing.py @@ -9,7 +9,7 @@ from functools import wraps from importlib.util import find_spec from shutil import which -from typing import Callable, Union +from typing import Any, Callable, Union from unittest import SkipTest import matplotlib @@ -85,8 +85,8 @@ def assert_rel_equal(a1, a2, decimals, err_msg="", verbose=True): # tested: volume integral is 1. def cubicspline_python( - x: Union[float, npt.ndarray[float]], -) -> npt.ndarray[float]: + x: Union[float, np.ndarray], +) -> np.ndarray: """ cubic spline SPH kernel function for testing against more effiecient cython methods @@ -148,11 +148,11 @@ def integrate_kernel( def distancematrix( - pos3_i0: np.ndarray[float], - pos3_i1: np.ndarray[float], - periodic: tuple[bool] = (True,) * 3, + pos3_i0: np.ndarray, + pos3_i1: np.ndarray, + periodic: tuple[bool, bool, bool] = (True,) * 3, periods: np.ndarray = _zeroperiods, -) -> np.ndarray[float]: +) -> np.ndarray: """ Calculates the distances between two arrays of points. @@ -808,14 +808,14 @@ def fake_sph_flexible_grid_ds( hsml_factor: float = 1.0, nperside: int = 3, periodic: bool = True, - e1hat: np.ndarray[float] = _xhat, - e2hat: np.ndarray[float] = _yhat, - e3hat: np.ndarray[float] = _zhat, - offsets: np.ndarray[float] = _floathalves, + e1hat: np.ndarray = _xhat, + e2hat: np.ndarray = _yhat, + e3hat: np.ndarray = _zhat, + offsets: np.ndarray = _floathalves, massgenerator: Callable[[int, int, int], float] = constantmass, unitrho: float = 1.0, - bbox: Union[npt.ndarray[float], None] = None, - recenter: Union[npt.ndarray[float], None] = None, + bbox: Union[np.ndarray, None] = None, + recenter: Union[np.ndarray, None] = None, ) -> StreamParticlesDataset: """Returns an in-memory SPH dataset useful for testing @@ -913,9 +913,9 @@ def fake_sph_flexible_grid_ds( okinds = slice(None, None, None) data = { - "particle_position_x": (np.copy(pos[okinds, 0]), "cm"), - "particle_position_y": (np.copy(pos[okinds, 1]), "cm"), - "particle_position_z": (np.copy(pos[okinds, 2]), "cm"), + "particle_position_x": (np.copy(pos[:, 0][okinds]), "cm"), + "particle_position_y": (np.copy(pos[:, 1][okinds]), "cm"), + "particle_position_z": (np.copy(pos[:, 2][okinds]), "cm"), "particle_mass": (mass[okinds], "g"), "particle_velocity_x": (np.zeros(npart), "cm/s"), "particle_velocity_y": (np.zeros(npart), "cm/s"), @@ -938,7 +938,7 @@ def fake_sph_flexible_grid_ds( def fake_random_sph_ds( npart: int, - bbox: npt.ndarray[float], + bbox: np.ndarray, periodic: Union[bool, tuple[bool, bool, bool]] = True, massrange: tuple[float, float] = (0.5, 2.0), hsmlrange: tuple[float, float] = (0.5, 2.0), diff --git a/yt/visualization/tests/test_offaxisprojection_pytestonly.py b/yt/visualization/tests/test_offaxisprojection_pytestonly.py index 67db76db373..6c9010b8a50 100644 --- a/yt/visualization/tests/test_offaxisprojection_pytestonly.py +++ b/yt/visualization/tests/test_offaxisprojection_pytestonly.py @@ -1,3 +1,5 @@ +from typing import Union + import numpy as np import pytest import unyt @@ -17,9 +19,9 @@ @pytest.mark.parametrize("shiftcenter", [False, True]) @pytest.mark.parametrize("northvector", [None, (1.0e-4, 1.0, 0.0)]) def test_sph_proj_general_offaxis( - northvector: tuple[float, float, float] | None, + northvector: Union[tuple[float, float, float], None], shiftcenter: bool, - depth: tuple[float, str] | None, + depth: Union[tuple[float, str], None], periodic: bool, weighted: bool, ) -> None: From ebb357cdbada870627eedf600124af75c1f82b28 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Thu, 18 Jul 2024 15:46:17 -0500 Subject: [PATCH 41/64] ruff fix --- yt/testing.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/yt/testing.py b/yt/testing.py index b809835310b..678e769f65e 100644 --- a/yt/testing.py +++ b/yt/testing.py @@ -9,12 +9,11 @@ from functools import wraps from importlib.util import find_spec from shutil import which -from typing import Any, Callable, Union +from typing import Callable, Union from unittest import SkipTest import matplotlib import numpy as np -import numpy.typing as npt from more_itertools import always_iterable from numpy.random import RandomState from unyt.exceptions import UnitOperationError From f09851c3eec132c65f78ef00841a9aae2518d305 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Thu, 18 Jul 2024 21:52:36 -0500 Subject: [PATCH 42/64] more attempted type check fixes --- yt/loaders.py | 4 ++-- yt/testing.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/yt/loaders.py b/yt/loaders.py index 1a19c5583a3..df0b32ce6d6 100644 --- a/yt/loaders.py +++ b/yt/loaders.py @@ -10,7 +10,7 @@ import types import warnings from pathlib import Path -from typing import TYPE_CHECKING, Any, Optional, Union, cast +from typing import TYPE_CHECKING, Any, Mapping, Optional, Union, cast from urllib.parse import urlsplit import numpy as np @@ -694,7 +694,7 @@ def load_amr_grids( def load_particles( - data: dict[AnyFieldKey, Union[np.ndarray, tuple[np.ndarray, str]]], + data: Mapping[AnyFieldKey, Union[np.ndarray, tuple[np.ndarray, str]]], length_unit=None, bbox=None, sim_time=None, diff --git a/yt/testing.py b/yt/testing.py index 678e769f65e..53b4689e65a 100644 --- a/yt/testing.py +++ b/yt/testing.py @@ -909,12 +909,12 @@ def fake_sph_flexible_grid_ds( okinds &= pos[:, ax] >= bbox[ax, 0] npart = sum(okinds) else: - okinds = slice(None, None, None) + okinds: slice = slice(None, None, None) data = { - "particle_position_x": (np.copy(pos[:, 0][okinds]), "cm"), - "particle_position_y": (np.copy(pos[:, 1][okinds]), "cm"), - "particle_position_z": (np.copy(pos[:, 2][okinds]), "cm"), + "particle_position_x": (np.copy(pos[okinds, 0]), "cm"), + "particle_position_y": (np.copy(pos[okinds, 1]), "cm"), + "particle_position_z": (np.copy(pos[okinds, 2]), "cm"), "particle_mass": (mass[okinds], "g"), "particle_velocity_x": (np.zeros(npart), "cm/s"), "particle_velocity_y": (np.zeros(npart), "cm/s"), From 987e50e434b00bbf1e18329f7524a75249f8121e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 19 Jul 2024 03:01:41 +0000 Subject: [PATCH 43/64] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- yt/loaders.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yt/loaders.py b/yt/loaders.py index df0b32ce6d6..32adc89fae6 100644 --- a/yt/loaders.py +++ b/yt/loaders.py @@ -9,8 +9,9 @@ import time import types import warnings +from collections.abc import Mapping from pathlib import Path -from typing import TYPE_CHECKING, Any, Mapping, Optional, Union, cast +from typing import TYPE_CHECKING, Any, Optional, Union, cast from urllib.parse import urlsplit import numpy as np From 422e7161f7988ef2b746e44fc3e2a8463e790695 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Thu, 18 Jul 2024 23:10:21 -0500 Subject: [PATCH 44/64] more attempted type-checking fixes --- yt/loaders.py | 2 +- yt/testing.py | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/yt/loaders.py b/yt/loaders.py index 32adc89fae6..84946495492 100644 --- a/yt/loaders.py +++ b/yt/loaders.py @@ -834,7 +834,7 @@ def parse_unit(unit, dimension): field_units, data, _ = process_data(data) sfh = StreamDictFieldHandler() - pdata: dict[AnyFieldKey, np.ndarray] = {} + pdata: dict[AnyFieldKey, Union[np.ndarray, tuple[np.ndarray, str]]] = {} for key in data.keys(): field: FieldKey if not isinstance(key, tuple): diff --git a/yt/testing.py b/yt/testing.py index 53b4689e65a..869f6959583 100644 --- a/yt/testing.py +++ b/yt/testing.py @@ -6,6 +6,7 @@ import sys import tempfile import unittest +from collections.abc import Mapping from functools import wraps from importlib.util import find_spec from shutil import which @@ -19,6 +20,7 @@ from unyt.exceptions import UnitOperationError from yt._maintenance.deprecation import issue_deprecation_warning +from yt._typing import AnyFieldKey from yt.config import ytcfg from yt.frontends.stream.data_structures import StreamParticlesDataset from yt.funcs import is_sequence @@ -909,18 +911,18 @@ def fake_sph_flexible_grid_ds( okinds &= pos[:, ax] >= bbox[ax, 0] npart = sum(okinds) else: - okinds: slice = slice(None, None, None) + okinds = np.ones((npart,), dtype=bool) - data = { + data: Mapping[AnyFieldKey, tuple[np.ndarray, str]] = { "particle_position_x": (np.copy(pos[okinds, 0]), "cm"), "particle_position_y": (np.copy(pos[okinds, 1]), "cm"), "particle_position_z": (np.copy(pos[okinds, 2]), "cm"), - "particle_mass": (mass[okinds], "g"), + "particle_mass": (np.copy(mass[okinds]), "g"), "particle_velocity_x": (np.zeros(npart), "cm/s"), "particle_velocity_y": (np.zeros(npart), "cm/s"), "particle_velocity_z": (np.zeros(npart), "cm/s"), "smoothing_length": (np.ones(npart) * 0.5 * hsml_factor, "cm"), - "density": (rho[okinds], "g/cm**3"), + "density": (np.copy(rho[okinds]), "g/cm**3"), } ds = load_particles( @@ -981,7 +983,7 @@ def fake_random_sph_ds( hsml = gen.uniform(low=hsmlrange[0], high=hsmlrange[1], size=npart) dens = mass / hsml**3 * unitrho - data = { + data: Mapping[AnyFieldKey, tuple[np.ndarray, str]] = { "particle_position_x": (posx, "cm"), "particle_position_y": (posy, "cm"), "particle_position_z": (posz, "cm"), From 02011d04817a3c8ceda79864f0647eede4f9a102 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Fri, 19 Jul 2024 15:06:39 -0500 Subject: [PATCH 45/64] avoid test failures from divide by zero errors --- .../tests/test_sph_pixelization_pytestonly.py | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py b/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py index 44983c1120f..6010644790b 100644 --- a/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py +++ b/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py @@ -279,18 +279,18 @@ def makemasses(i, j, k): (ad[("gas", "z")]).to("cm"), ] ).T - print("sphcoords:") - print(sphcoords) - print("gridcoords:") - print(gridcoords) + #print("sphcoords:") + #print(sphcoords) + #print("gridcoords:") + #print(gridcoords) dists = distancematrix( gridcoords, sphcoords, periodic=(periodic,) * 3, periods=np.array([3.0, 3.0, 3.0]), ) - print("dists <= 1:") - print(dists <= 1) + #print("dists <= 1:") + #print(dists <= 1) sml = (ad[("gas", "smoothing_length")]).to("cm") normkern = cubicspline_python(dists / sml.v[np.newaxis, :]) sphcontr = normkern / sml[np.newaxis, :] ** 3 * ad[("gas", "mass")] @@ -302,9 +302,11 @@ def makemasses(i, j, k): / ad[("gas", "density")] ) weights = np.sum(sphweights, axis=1) - expected = contsum / weights + nzeromask = np.logical_not(weights == 0) + expected = np.zeros(weights.shape, weights.dtype) + expected[nzeromask] = contsum[nzeromask] / weights[nzeromask] expected = expected.reshape((outgridsize, outgridsize)) - expected[np.isnan(expected)] = 0.0 # convention in the slices + #expected[np.isnan(expected)] = 0.0 # convention in the slices print("expected:\n", expected.v) print("recovered:\n", img.v) @@ -396,7 +398,7 @@ def makemasses(i, j, k): source = ds.all_data() # couple to dataset -> right unit registry center = ds.arr(center, "cm") - print("position:\n", source["gas", "position"]) + #print("position:\n", source["gas", "position"]) slc = yt.SlicePlot( ds, e1dir, @@ -429,8 +431,8 @@ def makemasses(i, j, k): + ygrid[:, np.newaxis] * e2dir[np.newaxis, :] + zgrid[:, np.newaxis] * e1dir[np.newaxis, :] ) - print("gridcoords:") - print(gridcoords) + #print("gridcoords:") + #print(gridcoords) ad = ds.all_data() sphcoords = np.array( [ @@ -456,10 +458,12 @@ def makemasses(i, j, k): / ad[("gas", "density")] ) weights = np.sum(sphweights, axis=1) - expected = contsum / weights + nzeromask = np.logical_not(weights == 0) + expected = np.zeros(weights.shape, weights.dtype) + expected[nzeromask] = contsum[nzeromask] / weights[nzeromask] expected = expected.reshape((outgridsize, outgridsize)) expected = expected.T # transposed for image plotting - expected[np.isnan(expected)] = 0.0 # convention in the slices + # expected[np.isnan(expected)] = 0.0 # convention in the slices # print(axis, shiftcenter, depth, periodic, weighted) print("expected:\n", expected.v) @@ -484,8 +488,8 @@ def test_sph_grid( left = bbox[:, 0].copy() level = 2 ncells = np.array([2**level] * 3) - print("left: ", left) - print("ncells: ", ncells) + #print("left: ", left) + #print("ncells: ", ncells) resgrid = ds.covering_grid(level, tuple(left), ncells) right = bbox[:, 1].copy() xedges = np.linspace(left[0], right[0], ncells[0] + 1) @@ -531,9 +535,11 @@ def test_sph_grid( / ad[("gas", "density")] ) weights = np.sum(sphweights, axis=1) - expected = contsum / weights + nzeromask = np.logical_not(weights == 0) + expected = np.zeros(weights.shape, weights.dtype) + expected[nzeromask] = contsum[nzeromask] / weights[nzeromask] expected = expected.reshape(outshape) - expected[np.isnan(expected)] = 0.0 # convention in the slices + #expected[np.isnan(expected)] = 0.0 # convention in the slices # print(axis, shiftcenter, depth, periodic, weighted) print("expected:\n", expected.v) From be087cee7eca4865315c8727f6c78899eaaea4e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Fri, 19 Jul 2024 10:02:50 +0200 Subject: [PATCH 46/64] BUG: fix a defect in CartesianCoordinateHandler._ortho_pixelize (ValueError: setting an array element with a sequence.) --- yt/geometry/coordinates/cartesian_coordinates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yt/geometry/coordinates/cartesian_coordinates.py b/yt/geometry/coordinates/cartesian_coordinates.py index 7c68b8b0fea..4fed1977c4e 100644 --- a/yt/geometry/coordinates/cartesian_coordinates.py +++ b/yt/geometry/coordinates/cartesian_coordinates.py @@ -332,7 +332,7 @@ def _ortho_pixelize( period3 = self.period[:].copy() # dummy here period3[0] = self.period[self.x_axis[dim]] period3[1] = self.period[self.y_axis[dim]] - zax = list({0, 1, 2} - {self.x_axis[dim], self.y_axis[dim]}) + zax = list({0, 1, 2} - {self.x_axis[dim], self.y_axis[dim]})[0] period3[2] = self.period[zax] if hasattr(period2, "in_units"): period2 = period2.in_units("code_length").d From 374a758e3fe1ce565c1c4be56ac41958782563c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Fri, 19 Jul 2024 11:00:49 +0200 Subject: [PATCH 47/64] BUG: fix a defect in OffAxisProjectionFixedResolutionBuffer._generate_image_and_mask --- yt/visualization/fixed_resolution.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yt/visualization/fixed_resolution.py b/yt/visualization/fixed_resolution.py index f597393a5dc..000c2dba8bb 100644 --- a/yt/visualization/fixed_resolution.py +++ b/yt/visualization/fixed_resolution.py @@ -639,6 +639,7 @@ def _generate_image_and_mask(self, item) -> None: self.bounds[5] - self.bounds[4], ) ) + depth = dd.depth[0] if dd.depth is not None else None buff = off_axis_projection( dd.dd, dd.center, @@ -651,7 +652,7 @@ def _generate_image_and_mask(self, item) -> None: no_ghost=dd.no_ghost, interpolated=dd.interpolated, north_vector=dd.north_vector, - depth=dd.depth, + depth=depth, method=dd.method, ) if self.data_source.moment == 2: From e8a0371fb25804cb45162cdff133c66e35b6373a Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Fri, 19 Jul 2024 15:17:20 -0500 Subject: [PATCH 48/64] ruff fix --- .../coordinates/tests/test_sph_pixelization_pytestonly.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py b/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py index 6010644790b..476a1bc9c7e 100644 --- a/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py +++ b/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py @@ -395,10 +395,10 @@ def makemasses(i, j, k): e3hat=e3dir, ) - source = ds.all_data() + # source = ds.all_data() # couple to dataset -> right unit registry center = ds.arr(center, "cm") - #print("position:\n", source["gas", "position"]) + # print("position:\n", source["gas", "position"]) slc = yt.SlicePlot( ds, e1dir, From 7f129c1bfc0cadc67e6bdcec9fe72c181f505758 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Fri, 19 Jul 2024 15:20:05 -0500 Subject: [PATCH 49/64] avoid 'divide by zero' errors --- yt/visualization/volume_rendering/off_axis_projection.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/yt/visualization/volume_rendering/off_axis_projection.py b/yt/visualization/volume_rendering/off_axis_projection.py index d3765222c90..5c547f61c90 100644 --- a/yt/visualization/volume_rendering/off_axis_projection.py +++ b/yt/visualization/volume_rendering/off_axis_projection.py @@ -525,7 +525,8 @@ def temp_weightfield(field, data): image *= dl else: mask = image[:, :, 1] == 0 - image[:, :, 0] /= image[:, :, 1] + nmask = np.logical_not(mask) + image[:, :, 0][nmask] /= image[:, :, 1][nmask] image[mask] = 0 return image[:, :, 0] From 6f57981a49cb4cfd69d123ccf85206425aa54fff Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 19 Jul 2024 21:02:46 +0000 Subject: [PATCH 50/64] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../tests/test_sph_pixelization_pytestonly.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py b/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py index 476a1bc9c7e..21e4f8d17de 100644 --- a/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py +++ b/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py @@ -279,18 +279,18 @@ def makemasses(i, j, k): (ad[("gas", "z")]).to("cm"), ] ).T - #print("sphcoords:") - #print(sphcoords) - #print("gridcoords:") - #print(gridcoords) + # print("sphcoords:") + # print(sphcoords) + # print("gridcoords:") + # print(gridcoords) dists = distancematrix( gridcoords, sphcoords, periodic=(periodic,) * 3, periods=np.array([3.0, 3.0, 3.0]), ) - #print("dists <= 1:") - #print(dists <= 1) + # print("dists <= 1:") + # print(dists <= 1) sml = (ad[("gas", "smoothing_length")]).to("cm") normkern = cubicspline_python(dists / sml.v[np.newaxis, :]) sphcontr = normkern / sml[np.newaxis, :] ** 3 * ad[("gas", "mass")] @@ -306,7 +306,7 @@ def makemasses(i, j, k): expected = np.zeros(weights.shape, weights.dtype) expected[nzeromask] = contsum[nzeromask] / weights[nzeromask] expected = expected.reshape((outgridsize, outgridsize)) - #expected[np.isnan(expected)] = 0.0 # convention in the slices + # expected[np.isnan(expected)] = 0.0 # convention in the slices print("expected:\n", expected.v) print("recovered:\n", img.v) @@ -431,8 +431,8 @@ def makemasses(i, j, k): + ygrid[:, np.newaxis] * e2dir[np.newaxis, :] + zgrid[:, np.newaxis] * e1dir[np.newaxis, :] ) - #print("gridcoords:") - #print(gridcoords) + # print("gridcoords:") + # print(gridcoords) ad = ds.all_data() sphcoords = np.array( [ @@ -488,8 +488,8 @@ def test_sph_grid( left = bbox[:, 0].copy() level = 2 ncells = np.array([2**level] * 3) - #print("left: ", left) - #print("ncells: ", ncells) + # print("left: ", left) + # print("ncells: ", ncells) resgrid = ds.covering_grid(level, tuple(left), ncells) right = bbox[:, 1].copy() xedges = np.linspace(left[0], right[0], ncells[0] + 1) @@ -539,7 +539,7 @@ def test_sph_grid( expected = np.zeros(weights.shape, weights.dtype) expected[nzeromask] = contsum[nzeromask] / weights[nzeromask] expected = expected.reshape(outshape) - #expected[np.isnan(expected)] = 0.0 # convention in the slices + # expected[np.isnan(expected)] = 0.0 # convention in the slices # print(axis, shiftcenter, depth, periodic, weighted) print("expected:\n", expected.v) From 45574d62753e1dddf067c10c802a7c812edc8e6b Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Fri, 19 Jul 2024 17:16:06 -0500 Subject: [PATCH 51/64] fix .v on numpy array problem --- .../tests/test_sph_pixelization_pytestonly.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py b/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py index 21e4f8d17de..1ec291d5e65 100644 --- a/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py +++ b/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py @@ -308,9 +308,9 @@ def makemasses(i, j, k): expected = expected.reshape((outgridsize, outgridsize)) # expected[np.isnan(expected)] = 0.0 # convention in the slices - print("expected:\n", expected.v) + print("expected:\n", expected) print("recovered:\n", img.v) - assert_rel_equal(expected.v, img.v, 5) + assert_rel_equal(expected, img.v, 5) @pytest.mark.parametrize("periodic", [True, False]) @@ -466,9 +466,9 @@ def makemasses(i, j, k): # expected[np.isnan(expected)] = 0.0 # convention in the slices # print(axis, shiftcenter, depth, periodic, weighted) - print("expected:\n", expected.v) + print("expected:\n", expected) print("recovered:\n", img.v) - assert_rel_equal(expected.v, img.v, 4) + assert_rel_equal(expected, img.v, 4) # only axis-aligned; testing YTArbitraryGrid, YTCoveringGrid @@ -542,6 +542,6 @@ def test_sph_grid( # expected[np.isnan(expected)] = 0.0 # convention in the slices # print(axis, shiftcenter, depth, periodic, weighted) - print("expected:\n", expected.v) + print("expected:\n", expected) print("recovered:\n", res.v) - assert_rel_equal(expected.v, res.v, 4) + assert_rel_equal(expected, res.v, 4) From c653a4f5e97c7ad2cb2c60b0a5653e2434876c67 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Sat, 20 Jul 2024 14:38:19 -0500 Subject: [PATCH 52/64] remove printing in tests ('clogs up' test logs) --- .../tests/test_sph_pixelization_pytestonly.py | 12 ++++++------ .../tests/test_offaxisprojection_pytestonly.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py b/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py index 1ec291d5e65..75c7175e065 100644 --- a/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py +++ b/yt/geometry/coordinates/tests/test_sph_pixelization_pytestonly.py @@ -308,8 +308,8 @@ def makemasses(i, j, k): expected = expected.reshape((outgridsize, outgridsize)) # expected[np.isnan(expected)] = 0.0 # convention in the slices - print("expected:\n", expected) - print("recovered:\n", img.v) + # print("expected:\n", expected) + # print("recovered:\n", img.v) assert_rel_equal(expected, img.v, 5) @@ -466,8 +466,8 @@ def makemasses(i, j, k): # expected[np.isnan(expected)] = 0.0 # convention in the slices # print(axis, shiftcenter, depth, periodic, weighted) - print("expected:\n", expected) - print("recovered:\n", img.v) + # print("expected:\n", expected) + # print("recovered:\n", img.v) assert_rel_equal(expected, img.v, 4) @@ -542,6 +542,6 @@ def test_sph_grid( # expected[np.isnan(expected)] = 0.0 # convention in the slices # print(axis, shiftcenter, depth, periodic, weighted) - print("expected:\n", expected) - print("recovered:\n", res.v) + # print("expected:\n", expected) + # print("recovered:\n", res.v) assert_rel_equal(expected, res.v, 4) diff --git a/yt/visualization/tests/test_offaxisprojection_pytestonly.py b/yt/visualization/tests/test_offaxisprojection_pytestonly.py index 6c9010b8a50..f63ac924c39 100644 --- a/yt/visualization/tests/test_offaxisprojection_pytestonly.py +++ b/yt/visualization/tests/test_offaxisprojection_pytestonly.py @@ -164,6 +164,6 @@ def makemasses(i, j, k): expected_out[:1, :] = 0.0 expected_out[:, :1] = 0.0 # print(axis, shiftcenter, depth, periodic, weighted) - print("expected:\n", expected_out) - print("recovered:\n", img.v) + # print("expected:\n", expected_out) + # print("recovered:\n", img.v) assert_rel_equal(expected_out, img.v, 4) From 92a7127df7b1e07058c66c6bc2f41e6d8371b95e Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Sat, 20 Jul 2024 14:59:38 -0500 Subject: [PATCH 53/64] attempted fix test error from sqrt(< 0) --- yt/visualization/tests/test_offaxisprojection.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/yt/visualization/tests/test_offaxisprojection.py b/yt/visualization/tests/test_offaxisprojection.py index 34dfbf49aa9..f72c4a4e630 100644 --- a/yt/visualization/tests/test_offaxisprojection.py +++ b/yt/visualization/tests/test_offaxisprojection.py @@ -252,7 +252,6 @@ def _vlos_sq(field, data): # ) p1_expsq = p1.frb["gas", "velocity_los_squared"] p1_sqexp = p1.frb["gas", "velocity_los"] ** 2 - p1res = np.sqrt(p1_expsq - p1_sqexp) # set values to zero that have **2 - **2 < 0, but # the absolute values are much smaller than the smallest # postive values of **2 and **2 @@ -260,10 +259,13 @@ def _vlos_sq(field, data): mindiff = 1e-10 * min( np.min(p1_expsq[p1_expsq > 0]), np.min(p1_sqexp[p1_sqexp > 0]) ) - print(mindiff) - setzero = np.logical_and( + # print(mindiff) + safeorbad = np.logical_not(np.logical_and( p1_expsq - p1_sqexp < 0, p1_expsq - p1_sqexp > -1.0 * mindiff - ) - p1res[setzero] = 0.0 + )) + # avoid errors from sqrt(negative) + # sqrt in zeros_like insures correct units + p1res = np.zeros_like(np.sqrt(p1_expsq)) + p1res[safeorbad] = np.sqrt(p1_expsq[safeorbad] - p1_sqexp[safeorbad]) p2res = p2.frb["gas", "velocity_los"] assert_rel_equal(p1res, p2res, 10) From 5587ff320e3df53606d9a4ea62fcc28ddd49659a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 20 Jul 2024 20:09:00 +0000 Subject: [PATCH 54/64] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- yt/visualization/tests/test_offaxisprojection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yt/visualization/tests/test_offaxisprojection.py b/yt/visualization/tests/test_offaxisprojection.py index f72c4a4e630..739e48d7ea6 100644 --- a/yt/visualization/tests/test_offaxisprojection.py +++ b/yt/visualization/tests/test_offaxisprojection.py @@ -260,9 +260,9 @@ def _vlos_sq(field, data): np.min(p1_expsq[p1_expsq > 0]), np.min(p1_sqexp[p1_sqexp > 0]) ) # print(mindiff) - safeorbad = np.logical_not(np.logical_and( - p1_expsq - p1_sqexp < 0, p1_expsq - p1_sqexp > -1.0 * mindiff - )) + safeorbad = np.logical_not( + np.logical_and(p1_expsq - p1_sqexp < 0, p1_expsq - p1_sqexp > -1.0 * mindiff) + ) # avoid errors from sqrt(negative) # sqrt in zeros_like insures correct units p1res = np.zeros_like(np.sqrt(p1_expsq)) From d3bd38a9b46868f1b7ee71db8f89e39d209a5ec6 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Tue, 23 Jul 2024 16:13:09 -0500 Subject: [PATCH 55/64] docstring typo fix --- yt/visualization/volume_rendering/off_axis_projection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yt/visualization/volume_rendering/off_axis_projection.py b/yt/visualization/volume_rendering/off_axis_projection.py index 5c547f61c90..3c2359a3f80 100644 --- a/yt/visualization/volume_rendering/off_axis_projection.py +++ b/yt/visualization/volume_rendering/off_axis_projection.py @@ -81,7 +81,7 @@ def off_axis_projection( north_vector : optional, array_like, default None A vector that, if specified, restricts the orientation such that the north vector dotted into the image plane points "up". Useful for rotations - depth: float, tuple[float, str], or unyt_array or size 1. + depth: float, tuple[float, str], or unyt_array of size 1. specify the depth of the projection region (size along the line of sight). If no units are given (unyt_array or second tuple element), code units are assumed. From 726bdf3fecb4369bd3335ebad78a04499c389400 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Tue, 23 Jul 2024 16:22:45 -0500 Subject: [PATCH 56/64] pass depth kwarg to off_axis_projection in FITSOffAxisProjection --- yt/visualization/fits_image.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/yt/visualization/fits_image.py b/yt/visualization/fits_image.py index 85a098fa30e..c740d91287a 100644 --- a/yt/visualization/fits_image.py +++ b/yt/visualization/fits_image.py @@ -1648,6 +1648,7 @@ def __init__( north_vector=north_vector, method=method, weight=weight_field, + depth=depth, ).swapaxes(0, 1) if moment == 2: @@ -1675,6 +1676,7 @@ def _sq_field(field, data, item: FieldKey): north_vector=north_vector, method=method, weight=weight_field, + depth=depth, ).swapaxes(0, 1) buf[key] = compute_stddev_image(buff2, buf[key]) From 8ea95e30052b50948e0ee6f107cbb4719813116b Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:13:06 -0500 Subject: [PATCH 57/64] now passing dataset kernel name to SPH gridding backend --- yt/data_objects/construction_data_containers.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/yt/data_objects/construction_data_containers.py b/yt/data_objects/construction_data_containers.py index b6dfd4dd8bd..5f0c23ad58b 100644 --- a/yt/data_objects/construction_data_containers.py +++ b/yt/data_objects/construction_data_containers.py @@ -997,6 +997,7 @@ def _fill_sph_particles(self, fields): smoothing_style = getattr(self.ds, "sph_smoothing_style", "scatter") normalize = getattr(self.ds, "use_sph_normalization", True) + kernel_name = getattr(self.ds, "kernel_name", "cubic") bounds, size = self._get_grid_bounds_size() @@ -1038,6 +1039,7 @@ def _fill_sph_particles(self, fields): pbar=pbar, check_period=is_periodic, period=period, + kernel_name=kernel_name, ) if normalize: pixelize_sph_kernel_arbitrary_grid( @@ -1053,6 +1055,7 @@ def _fill_sph_particles(self, fields): pbar=pbar, check_period=is_periodic, period=period, + kernel_name=kernel_name, ) if normalize: From 22cac2ba239df9a4e7fda9f2b1ca120ff709d218 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:13:37 -0500 Subject: [PATCH 58/64] remove hsml margin in line of sight direction for SPH projections --- yt/utilities/lib/pixelization_routines.pyx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/yt/utilities/lib/pixelization_routines.pyx b/yt/utilities/lib/pixelization_routines.pyx index b1f7e620d5d..a207473e9ea 100644 --- a/yt/utilities/lib/pixelization_routines.pyx +++ b/yt/utilities/lib/pixelization_routines.pyx @@ -1252,7 +1252,11 @@ def pixelize_sph_kernel_projection( # discard if z is outside bounds if ziter[kk] == 999: continue pz = posz[j] + ziterv[kk] - if (pz + hsml[j] < z_min) or (pz - hsml[j] > z_max): continue + ## removed hsml 'margin' in the projection direction to avoid + ## double-counting particles near periodic edges + ## and adding extra 'depth' to projections + #if (pz + hsml[j] < z_min) or (pz - hsml[j] > z_max): continue + if (pz < z_min) or (pz > z_max): continue for ii in range(2): if xiter[ii] == 999: continue From 5362365917849024b18248d84c040b59cfec85c6 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Wed, 24 Jul 2024 12:00:42 -0500 Subject: [PATCH 59/64] add prefixes marking old and new test images --- yt/utilities/answer_testing/framework.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yt/utilities/answer_testing/framework.py b/yt/utilities/answer_testing/framework.py index 37358573dd9..ad2db01c4f3 100644 --- a/yt/utilities/answer_testing/framework.py +++ b/yt/utilities/answer_testing/framework.py @@ -776,9 +776,9 @@ def compare(self, new_result, old_result): def dump_images(new_result, old_result, decimals=10): - tmpfd, old_image = tempfile.mkstemp(suffix=".png") + tmpfd, old_image = tempfile.mkstemp(prefix="baseline_", suffix=".png") os.close(tmpfd) - tmpfd, new_image = tempfile.mkstemp(suffix=".png") + tmpfd, new_image = tempfile.mkstemp(prefix="thisPR_", suffix=".png") os.close(tmpfd) image_writer.write_projection(new_result, new_image) image_writer.write_projection(old_result, old_image) From 0580c692ad36035a97fa3561cd34f891ad02b7c3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 24 Jul 2024 18:49:07 +0000 Subject: [PATCH 60/64] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- yt/testing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/yt/testing.py b/yt/testing.py index 869f6959583..a5e6f10d836 100644 --- a/yt/testing.py +++ b/yt/testing.py @@ -937,6 +937,7 @@ def fake_sph_flexible_grid_ds( ds.kernel_name = "cubic" return ds + def fake_random_sph_ds( npart: int, bbox: np.ndarray, From d000cd532adc9ae14341e14500d0d7221c20713f Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Mon, 19 Aug 2024 15:16:32 -0500 Subject: [PATCH 61/64] increase numbers for frontend tests affect by SPH backend change --- tests/tests.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/tests.yaml b/tests/tests.yaml index d7de9b9650c..4f3dc77c95b 100644 --- a/tests/tests.yaml +++ b/tests/tests.yaml @@ -37,7 +37,7 @@ answer_tests: - yt/frontends/amrvac/tests/test_outputs.py:test_riemann_cartesian_175D - yt/frontends/amrvac/tests/test_outputs.py:test_rmi_cartesian_dust_2D - local_arepo_011: # PR 4419 + local_arepo_012: # PR 4419; to 012: PR 4939 - yt/frontends/arepo/tests/test_outputs.py:test_arepo_bullet - yt/frontends/arepo/tests/test_outputs.py:test_arepo_tng59 - yt/frontends/arepo/tests/test_outputs.py:test_arepo_cr @@ -92,7 +92,7 @@ answer_tests: - yt/frontends/flash/tests/test_outputs.py:test_wind_tunnel - yt/frontends/flash/tests/test_outputs.py:test_fid_1to3_b1 - local_gadget_009: # PR 3258 + local_gadget_010: # PR 3258; to 010: PR 4939 - yt/frontends/gadget/tests/test_outputs.py:test_iso_collapse - yt/frontends/gadget/tests/test_outputs.py:test_pid_uniqueness - yt/frontends/gadget/tests/test_outputs.py:test_bigendian_field_access @@ -108,7 +108,7 @@ answer_tests: local_gdf_002: - yt/frontends/gdf/tests/test_outputs_nose.py:test_sedov_tunnel - local_gizmo_008: # PR 2909 + local_gizmo_008: # PR 2909; to 009: PR 4939 - yt/frontends/gizmo/tests/test_outputs.py:test_gizmo_64 local_halos_012: # PR 3325 @@ -118,7 +118,7 @@ answer_tests: - yt/frontends/gadget_fof/tests/test_outputs.py:test_fields_g5 - yt/frontends/gadget_fof/tests/test_outputs.py:test_fields_g42 - local_owls_008: # PR 2909 + local_owls_008: # PR 2909; to 009: PR 4939 - yt/frontends/owls/tests/test_outputs.py:test_snapshot_033 - yt/frontends/owls/tests/test_outputs.py:test_OWLS_particlefilter @@ -130,7 +130,7 @@ answer_tests: - yt/visualization/tests/test_particle_plot.py:test_particle_phase_answers - yt/visualization/tests/test_callbacks.py:test_axis_manipulations - local_tipsy_009: # PR 2909 + local_tipsy_010: # PR 2909; to 010: PR 4939 - yt/frontends/tipsy/tests/test_outputs.py:test_pkdgrav - yt/frontends/tipsy/tests/test_outputs.py:test_gasoline_dmonly - yt/frontends/tipsy/tests/test_outputs.py:test_tipsy_galaxy From f46649424b4dbcb3152611e106c0c2119b9b77fe Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Mon, 19 Aug 2024 15:36:55 -0500 Subject: [PATCH 62/64] comments update --- tests/tests.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/tests.yaml b/tests/tests.yaml index 4f3dc77c95b..8396312e1ce 100644 --- a/tests/tests.yaml +++ b/tests/tests.yaml @@ -37,7 +37,7 @@ answer_tests: - yt/frontends/amrvac/tests/test_outputs.py:test_riemann_cartesian_175D - yt/frontends/amrvac/tests/test_outputs.py:test_rmi_cartesian_dust_2D - local_arepo_012: # PR 4419; to 012: PR 4939 + local_arepo_012: # PR 4939 - yt/frontends/arepo/tests/test_outputs.py:test_arepo_bullet - yt/frontends/arepo/tests/test_outputs.py:test_arepo_tng59 - yt/frontends/arepo/tests/test_outputs.py:test_arepo_cr @@ -92,7 +92,7 @@ answer_tests: - yt/frontends/flash/tests/test_outputs.py:test_wind_tunnel - yt/frontends/flash/tests/test_outputs.py:test_fid_1to3_b1 - local_gadget_010: # PR 3258; to 010: PR 4939 + local_gadget_010: # PR 4939 - yt/frontends/gadget/tests/test_outputs.py:test_iso_collapse - yt/frontends/gadget/tests/test_outputs.py:test_pid_uniqueness - yt/frontends/gadget/tests/test_outputs.py:test_bigendian_field_access @@ -108,7 +108,7 @@ answer_tests: local_gdf_002: - yt/frontends/gdf/tests/test_outputs_nose.py:test_sedov_tunnel - local_gizmo_008: # PR 2909; to 009: PR 4939 + local_gizmo_008: # PR 4939 - yt/frontends/gizmo/tests/test_outputs.py:test_gizmo_64 local_halos_012: # PR 3325 @@ -118,7 +118,7 @@ answer_tests: - yt/frontends/gadget_fof/tests/test_outputs.py:test_fields_g5 - yt/frontends/gadget_fof/tests/test_outputs.py:test_fields_g42 - local_owls_008: # PR 2909; to 009: PR 4939 + local_owls_008: # PR 4939 - yt/frontends/owls/tests/test_outputs.py:test_snapshot_033 - yt/frontends/owls/tests/test_outputs.py:test_OWLS_particlefilter @@ -130,7 +130,7 @@ answer_tests: - yt/visualization/tests/test_particle_plot.py:test_particle_phase_answers - yt/visualization/tests/test_callbacks.py:test_axis_manipulations - local_tipsy_010: # PR 2909; to 010: PR 4939 + local_tipsy_010: # PR 4939 - yt/frontends/tipsy/tests/test_outputs.py:test_pkdgrav - yt/frontends/tipsy/tests/test_outputs.py:test_gasoline_dmonly - yt/frontends/tipsy/tests/test_outputs.py:test_tipsy_galaxy From f8949c24b3688d729dbf210ebe59bd206a46ce5b Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Mon, 19 Aug 2024 19:37:27 -0500 Subject: [PATCH 63/64] actually update gizmo, owls test numbers --- tests/tests.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tests.yaml b/tests/tests.yaml index 8396312e1ce..e6804b57c8c 100644 --- a/tests/tests.yaml +++ b/tests/tests.yaml @@ -108,7 +108,7 @@ answer_tests: local_gdf_002: - yt/frontends/gdf/tests/test_outputs_nose.py:test_sedov_tunnel - local_gizmo_008: # PR 4939 + local_gizmo_009: # PR 4939 - yt/frontends/gizmo/tests/test_outputs.py:test_gizmo_64 local_halos_012: # PR 3325 @@ -118,7 +118,7 @@ answer_tests: - yt/frontends/gadget_fof/tests/test_outputs.py:test_fields_g5 - yt/frontends/gadget_fof/tests/test_outputs.py:test_fields_g42 - local_owls_008: # PR 4939 + local_owls_009: # PR 4939 - yt/frontends/owls/tests/test_outputs.py:test_snapshot_033 - yt/frontends/owls/tests/test_outputs.py:test_OWLS_particlefilter From ac30d156c15903a485f159e78c21849c3f3fcc45 Mon Sep 17 00:00:00 2001 From: nastasha-w <35771073+nastasha-w@users.noreply.github.com> Date: Wed, 21 Aug 2024 17:50:21 -0500 Subject: [PATCH 64/64] bumpy arepo test number: avoid missing files error? --- tests/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.yaml b/tests/tests.yaml index e6804b57c8c..40836b3381b 100644 --- a/tests/tests.yaml +++ b/tests/tests.yaml @@ -37,7 +37,7 @@ answer_tests: - yt/frontends/amrvac/tests/test_outputs.py:test_riemann_cartesian_175D - yt/frontends/amrvac/tests/test_outputs.py:test_rmi_cartesian_dust_2D - local_arepo_012: # PR 4939 + local_arepo_013: # PR 4939 - yt/frontends/arepo/tests/test_outputs.py:test_arepo_bullet - yt/frontends/arepo/tests/test_outputs.py:test_arepo_tng59 - yt/frontends/arepo/tests/test_outputs.py:test_arepo_cr