Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Adds "Min" Method to ProjectionPlot #3940

Merged
merged 32 commits into from
May 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
898b5eb
Projection 'min' method (first commit, doesn't work yet). Address Iss…
May 25, 2022
c600581
added mention of the new projection method in 'simple_plots.rst' in t…
rjfarber May 25, 2022
f6d0d3c
switched back to the standard file to load in the cookbook script
rjfarber May 25, 2022
68f29a9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 25, 2022
b64d182
changed 'in' to '==' and separated save from instantiation in cookboo…
rjfarber May 26, 2022
5931042
added deprecation warning for 'mip' method value and removed usage of…
rjfarber May 26, 2022
1b7851f
corrected the deprecation warning to actually have the 'if' statement…
May 26, 2022
e5aa195
Update doc/source/cookbook/simple_projection_methods.py
rjfarber May 26, 2022
7ca0939
added max,min to docstring in plot_window.py; note I'll be doing a lo…
rjfarber May 26, 2022
9090462
just ran pre-commit (had to reinstall)
rjfarber May 26, 2022
3301312
if/elif fixed (again...)
rjfarber May 26, 2022
e515118
added min,max to test_plotwindow. Left mip in...
rjfarber May 26, 2022
b67ea67
changed mip to max in data_containers max method, both docstring and …
rjfarber May 26, 2022
f1852d3
replaced 'mip' with 'max' in test_projection.py
rjfarber May 26, 2022
7bbeef9
added 'min' tests and replaced 'mip' with 'max'
rjfarber May 26, 2022
1e0bc9c
changed 'mip' to 'max' in test_minimal_representation.py
rjfarber May 26, 2022
7f39367
replace 'mip' with 'max' in cookbook/image_resolution.py
rjfarber May 26, 2022
d60be43
added msg to NotImplementedError
rjfarber May 26, 2022
e2bf6d9
added not implemented error msg
rjfarber May 26, 2022
88face7
incorporating black-jupyter fixes
rjfarber May 26, 2022
dadf8fe
not implemented error msg added
rjfarber May 26, 2022
b6b1d19
added msg to not implemented error...not as sure about this one
rjfarber May 26, 2022
893d9f2
precommit fixes
rjfarber May 26, 2022
02a667d
removed msgs from not implemented errors in yt/visualization scripts
rjfarber May 26, 2022
d489942
Update yt/visualization/tests/test_plotwindow.py
rjfarber May 27, 2022
e848ae2
Update yt/data_objects/data_containers.py
rjfarber May 27, 2022
21344cb
Update yt/data_objects/tests/test_projection.py
rjfarber May 27, 2022
06ecc8b
added assert in test_projection
rjfarber May 27, 2022
265c9a8
added min implementation
rjfarber May 27, 2022
9d6dabb
fix broken test
neutrinoceros May 27, 2022
0060e05
DEPR: add missing deprecation warning
neutrinoceros May 27, 2022
0eec914
cleanup
neutrinoceros May 27, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/source/cookbook/image_resolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

# Create projections of the density (max value in each resolution element in the image):
prj = yt.ProjectionPlot(
ds, "x", ("gas", "density"), method="mip", center="max", width=(100, "kpc")
ds, "x", ("gas", "density"), method="max", center="max", width=(100, "kpc")
)

# nicen up the plot by using a better interpolation:
Expand Down
8 changes: 8 additions & 0 deletions doc/source/cookbook/simple_plots.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ See :ref:`projection-plots` for more information.

.. yt_cookbook:: simple_projection_weighted.py

Simple Projections (Methods)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

And here we illustrate different methods for projection plots (integrate,
minimum, maximum).

.. yt_cookbook:: simple_projection_methods.py

Simple Phase Plots
~~~~~~~~~~~~~~~~~~

Expand Down
11 changes: 11 additions & 0 deletions doc/source/cookbook/simple_projection_methods.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import yt

# Load the dataset.
ds = yt.load("GalaxyClusterMerger/fiducial_1to3_b0.273d_hdf5_plt_cnt_0175")
neutrinoceros marked this conversation as resolved.
Show resolved Hide resolved

# Create projections of temperature (with different methods)


for method in ["integrate", "min", "max"]:
proj = yt.ProjectionPlot(ds, "x", ("gas", "temperature"), method=method)
proj.save(f"projection_method_{method}.png")
25 changes: 20 additions & 5 deletions yt/data_objects/construction_data_containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,14 +185,24 @@ def __init__(
removal="4.2",
)
method = style
if method == "mip":
issue_deprecation_warning(
"The 'mip' method value is a deprecated alias for 'max'. "
"Please use max directly.",
since="4.1",
stacklevel=4,
)
method = "max"
if method == "sum":
self.method = "integrate"
self._sum_only = True
else:
self.method = method
self._sum_only = False
if self.method == "mip":
if self.method in ["max", "mip"]:
self.func = np.max
elif self.method == "min":
self.func = np.min
elif self.method == "integrate":
self.func = np.sum # for the future
else:
Expand Down Expand Up @@ -254,9 +264,12 @@ def get_data(self, fields=None):
projected_units = self.comm.mpi_bcast(self._projected_units)
self._projected_units = projected_units
# Note that this will briefly double RAM usage
if self.method == "mip":
if self.method in ["max", "mip"]:
merge_style = -1
op = "max"
elif self.method == "min":
merge_style = -2
op = "min"
elif self.method == "integrate":
merge_style = 1
op = "sum"
Expand Down Expand Up @@ -348,7 +361,7 @@ def _initialize_projected_units(self, fields, chunk):
# for future field accesses.
finfo.units = str(chunk[field].units)
field_unit = Unit(finfo.output_units, registry=self.ds.unit_registry)
if self.method == "mip" or self._sum_only:
if self.method in ("min", "max") or self._sum_only:
path_length_unit = Unit(registry=self.ds.unit_registry)
else:
ax_name = self.ds.coordinates.axis_name[self.axis]
Expand Down Expand Up @@ -447,7 +460,9 @@ class YTQuadTreeProj(YTProj):
method : string, optional
The method of projection to be performed.
"integrate" : integration along the axis
"mip" : maximum intensity projection
"mip" : maximum intensity projection (deprecated)
"max" : maximum intensity projection
"min" : minimum intensity projection
"sum" : same as "integrate", except that we don't multiply by the path length
WARNING: The "sum" option should only be used for uniform resolution grid
datasets, as other datasets may result in unphysical images.
Expand Down Expand Up @@ -557,7 +572,7 @@ def _handle_chunk(self, chunk, fields, tree):
chunk.ires.size,
get_memory_usage() / 1024.0,
)
if self.method == "mip" or self._sum_only:
if self.method in ("min", "max") or self._sum_only:
dl = self.ds.quan(1.0, "")
else:
ax_name = self.ds.coordinates.axis_name[self.axis]
Expand Down
7 changes: 3 additions & 4 deletions yt/data_objects/data_containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -980,7 +980,7 @@ def max(self, field, axis=None):

This will, in a parallel-aware fashion, compute the maximum of the
given field. Supplying an axis will result in a return value of a
YTProjection, with method 'mip' for maximum intensity. If the max has
YTProjection, with method 'max' for maximum intensity. If the max has
already been requested, it will use the cached extrema value.

Parameters
Expand All @@ -1006,8 +1006,7 @@ def max(self, field, axis=None):
return rv[0]
return rv
elif axis in self.ds.coordinates.axis_name:
r = self.ds.proj(field, axis, data_source=self, method="mip")
return r
return self.ds.proj(field, axis, data_source=self, method="max")
else:
raise NotImplementedError(f"Unknown axis {axis}")

Expand Down Expand Up @@ -1040,7 +1039,7 @@ def min(self, field, axis=None):
return rv[0]
return rv
elif axis in self.ds.coordinates.axis_name:
raise NotImplementedError("Minimum intensity projection not implemented.")
return self.ds.proj(field, axis, data_source=self, method="min")
else:
raise NotImplementedError(f"Unknown axis {axis}")

Expand Down
12 changes: 10 additions & 2 deletions yt/data_objects/tests/test_numpy_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,19 @@ def test_min_max():
)

p = ad.max(("gas", "density"), axis=1)
p1 = ds.proj(("gas", "density"), 1, data_source=ad, method="mip")
p1 = ds.proj(("gas", "density"), 1, data_source=ad, method="max")
assert_equal(p[("gas", "density")], p1[("gas", "density")])

p = ad.min(("gas", "density"), axis=1)
p1 = ds.proj(("gas", "density"), 1, data_source=ad, method="min")
neutrinoceros marked this conversation as resolved.
Show resolved Hide resolved
assert_equal(p[("gas", "density")], p1[("gas", "density")])

p = ad.max(("gas", "density"), axis="y")
p1 = ds.proj(("gas", "density"), 1, data_source=ad, method="mip")
p1 = ds.proj(("gas", "density"), 1, data_source=ad, method="max")
assert_equal(p[("gas", "density")], p1[("gas", "density")])

p = ad.min(("gas", "density"), axis="y")
p1 = ds.proj(("gas", "density"), 1, data_source=ad, method="min")
assert_equal(p[("gas", "density")], p1[("gas", "density")])

# Test that we can get multiple in a single pass
Expand Down
13 changes: 11 additions & 2 deletions yt/data_objects/tests/test_projection.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,17 @@ def test_projection(pf):

def test_max_level():
ds = fake_amr_ds(fields=[("gas", "density")], units=["mp/cm**3"])
proj = ds.proj(("gas", "density"), 2, method="mip", max_level=2)
proj = ds.proj(("gas", "density"), 2, method="max", max_level=2)
assert proj[("index", "grid_level")].max() == 2

proj = ds.proj(("gas", "density"), 2, method="mip")
proj = ds.proj(("gas", "density"), 2, method="max")
assert proj[("index", "grid_level")].max() == ds.index.max_level


def test_min_level():
ds = fake_amr_ds(fields=[("gas", "density")], units=["mp/cm**3"])
proj = ds.proj(("gas", "density"), 2, method="min")
assert proj[("index", "grid_level")].min() == 0

proj = ds.proj(("gas", "density"), 2, method="max")
assert proj[("index", "grid_level")].min() == ds.index.min_level
4 changes: 3 additions & 1 deletion yt/frontends/gadget_fof/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ def __init__(self, ds):
self.offset_fields = set()

def _read_fluid_selection(self, chunks, selector, fields, size):
raise NotImplementedError
raise NotImplementedError(
"IOHandlerGadgetFOFHDF5 _read_fluid_selection not implemented yet"
)

def _read_particle_coords(self, chunks, ptf):
# This will read chunks and yield the results.
Expand Down
34 changes: 29 additions & 5 deletions yt/utilities/lib/quad_tree.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ cimport numpy as np
from cython.operator cimport dereference as deref, preincrement as inc
from libc.stdlib cimport abs, free, malloc

from yt.utilities.lib.fp_utils cimport fmax
from yt.utilities.lib.fp_utils cimport fmax, fmin

from yt.utilities.exceptions import YTIntDomainOverflow

Expand Down Expand Up @@ -51,6 +51,19 @@ cdef void QTN_max_value(QuadTreeNode *self,
self.val[i] = fmax(val[i], self.val[i])
self.weight_val = 1.0

cdef void QTN_min_value(QuadTreeNode *self,
np.float64_t *val, np.float64_t weight_val,
int nvals):
cdef int i
#cdef np.float64_t *big_num = 1.0
#big_num = 1.0 #1e10
for i in range(nvals):
if self.val[i] == 0:
self.val[i] = 1e50
# end if
self.val[i] = fmin(val[i], self.val[i])
self.weight_val = 1.0

cdef void QTN_refine(QuadTreeNode *self, int nvals):
cdef int i, j
cdef np.int64_t npos[2]
Expand Down Expand Up @@ -108,10 +121,13 @@ cdef class QuadTree:
int nvals, bounds, method = "integrate"):
if method == "integrate":
self.combine = QTN_add_value
elif method == "mip":
elif method == "mip" or \
method == "max":
self.combine = QTN_max_value
elif method == "min":
self.combine = QTN_min_value
else:
raise NotImplementedError
raise NotImplementedError(f"Unknown projection method {self.method!r}")
self.merged = 1
self.max_level = 0
cdef int i, j
Expand Down Expand Up @@ -202,8 +218,10 @@ cdef class QuadTree:
np.ndarray[np.float64_t, ndim=2] values,
np.ndarray[np.float64_t, ndim=1] wval,
method):
if method == "mip" or method == -1:
if method == "mip" or method == "max" or method == -1:
self.merged = -1
if method == "min" or method == -2:
self.merged = -2
elif method == "integrate" or method == 1:
self.merged = 1
cdef int curpos = 0
Expand Down Expand Up @@ -406,6 +424,9 @@ cdef class QuadTree:
if self.merged == -1:
for i in range(self.nvals):
vdata[self.nvals * curpos + i] = fmax(node.val[i], vtoadd[i])
elif self.merged == -2:
for i in range(self.nvals):
vdata[self.nvals * curpos + i] = fmin(node.val[i], vtoadd[i])
wdata[curpos] = 1.0
else:
for i in range(self.nvals):
Expand All @@ -424,7 +445,7 @@ cdef class QuadTree:
vorig[i] = vtoadd[i]
vtoadd[i] += node.val[i]
wtoadd += node.weight_val
elif self.merged == -1:
elif self.merged == -1 or self.merged == -2:
for i in range(self.nvals):
vtoadd[i] = node.val[i]
for i in range(2):
Expand Down Expand Up @@ -567,6 +588,9 @@ def merge_quadtrees(QuadTree qt1, QuadTree qt2, method = 1):
elif method == -1:
qt1.merged = -1
func = QTN_max_value
elif method == -2:
qt1.merged = -2
func = QTN_min_value
else:
raise NotImplementedError
if qt1.merged != 0 or qt2.merged != 0:
Expand Down
2 changes: 1 addition & 1 deletion yt/utilities/tests/test_minimal_representation.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def test_store():
assert_equal(proj2[field], proj2_c[field])

def fail_for_different_method():
proj2_c = ds.proj(field, "z", data_source=sp, method="mip")
proj2_c = ds.proj(field, "z", data_source=sp, method="max")
assert_equal(proj2[field], proj2_c[field])

# A note here: a unyt.exceptions.UnitOperationError is raised
Expand Down
10 changes: 9 additions & 1 deletion yt/visualization/plot_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -2204,7 +2204,8 @@ class AxisAlignedProjectionPlot(ProjectionPlot, PWViewerMPL):
"integrate" with a weight_field specified : weight the requested
field by the weighting field and integrate along the line of sight.

"mip" : pick out the maximum value of the field in the line of sight.
"max" : pick out the maximum value of the field in the line of sight.
"min" : pick out the minimum value of the field in the line of sight.

"sum" : This method is the same as integrate, except that it does not
multiply by a path length when performing the integration, and is
Expand Down Expand Up @@ -2262,6 +2263,13 @@ def __init__(
*,
axis=None,
):
if method == "mip":
issue_deprecation_warning(
"'mip' method is a deprecated alias for 'max'. "
"Please use method='max' directly.",
since="4.1.0",
)
method = "max"
# TODO: in yt 4.2, remove default values for normal and fields, drop axis kwarg
normal = self._validate_init_args(normal=normal, fields=fields, axis=axis)
normal = self.sanitize_normal_vector(ds, normal)
Expand Down
2 changes: 1 addition & 1 deletion yt/visualization/tests/test_plotwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def setup():
("gas", "density"),
)

PROJECTION_METHODS = ("integrate", "sum", "mip")
PROJECTION_METHODS = ("integrate", "sum", "min", "max")

BUFF_SIZES = [(800, 800), (1600, 1600), (1254, 1254), (800, 600)]

Expand Down
4 changes: 3 additions & 1 deletion yt/visualization/volume_rendering/transfer_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -935,7 +935,9 @@ class ProjectionTransferFunction(MultiVariateTransferFunction):

def __init__(self, x_bounds=(-1e60, 1e60), n_fields=1):
if n_fields > 3:
raise NotImplementedError
raise NotImplementedError(
f"supplied ${n_fields} but n_fields > 3 not implemented."
)
MultiVariateTransferFunction.__init__(self)
# Strip units off of x_bounds, if any
x_bounds = [np.float64(xb) for xb in x_bounds]
Expand Down