[yt-svn] commit/yt: 23 new changesets

commits-noreply at bitbucket.org commits-noreply at bitbucket.org
Mon Aug 7 08:11:48 PDT 2017


23 new commits in yt:

https://bitbucket.org/yt_analysis/yt/commits/b3510aeb63cd/
Changeset:   b3510aeb63cd
User:        ngoldbaum
Date:        2017-07-26 20:40:49+00:00
Summary:     Make ParticleProfile handle log-scaled fields correctly
Affected #:  2 files

diff -r 85cb5b40cd70e9772c4f581e8133692d5f73ed9e -r b3510aeb63cd0f0ce506324e89a025d45de25eee yt/data_objects/profiles.py
--- a/yt/data_objects/profiles.py
+++ b/yt/data_objects/profiles.py
@@ -22,7 +22,8 @@
     get_output_filename, \
     ensure_list, \
     iterable, \
-    issue_deprecation_warning
+    issue_deprecation_warning, \
+    mylog
 from yt.units.yt_array import \
     array_like_field, \
     YTQuantity
@@ -661,33 +662,31 @@
     fractional = False
 
     def __init__(self, data_source,
-                 x_field, x_n, x_min, x_max,
-                 y_field, y_n, y_min, y_max,
+                 x_field, x_n, x_min, x_max, x_log,
+                 y_field, y_n, y_min, y_max, y_log,
                  weight_field=None, deposition="ngp"):
 
         x_field = data_source._determine_fields(x_field)[0]
         y_field = data_source._determine_fields(y_field)[0]
 
+        if deposition not in ['ngp', 'cic']:
+            raise NotImplementedError(deposition)
+        elif (x_log or y_log) and deposition != 'ngp':
+            mylog.info('cic deposition is only supported for linear axis '
+                       'scales, falling back to ngp deposition')
+            deposition = 'ngp'
+
+        self.deposition = deposition
+
         # set the log parameters to False (since that doesn't make much sense
         # for deposited data) and also turn off the weight field.
         super(ParticleProfile, self).__init__(data_source,
                                               x_field,
-                                              x_n, x_min, x_max, False,
+                                              x_n, x_min, x_max, x_log,
                                               y_field,
-                                              y_n, y_min, y_max, False,
+                                              y_n, y_min, y_max, y_log,
                                               weight_field=weight_field)
 
-        self.LeftEdge = [self.x_bins[0], self.y_bins[0]]
-        self.dx = (self.x_bins[-1] - self.x_bins[0]) / x_n
-        self.dy = (self.y_bins[-1] - self.y_bins[0]) / y_n
-        self.CellSize = [self.dx, self.dy]
-        self.CellVolume = np.product(self.CellSize)
-        self.GridDimensions = np.array([x_n, y_n], dtype=np.int32)
-        self.known_styles = ["ngp", "cic"]
-        if deposition not in self.known_styles:
-            raise NotImplementedError(deposition)
-        self.deposition = deposition
-
     # Either stick the particle field in the nearest bin,
     # or spread it out using the 2D CIC deposition function
     def _bin_chunk(self, chunk, fields, storage):
@@ -696,42 +695,31 @@
         fdata, wdata, (bf_x, bf_y) = rv
         # make sure everything has the same units before deposition.
         # the units will be scaled to the correct values later.
-        LE = np.array([self.LeftEdge[0].in_units(bf_x.units),
-                       self.LeftEdge[1].in_units(bf_y.units)])
-        cell_size = np.array([self.CellSize[0].in_units(bf_x.units),
-                              self.CellSize[1].in_units(bf_y.units)])
+
+        if self.deposition == "ngp":
+            func = NGPDeposit_2
+        elif self.deposition == "cic":
+            func = CICDeposit_2
+
         for fi, field in enumerate(fields):
-            Np = fdata[:, fi].size
-
-            if self.deposition == "ngp":
-                func = NGPDeposit_2
-            elif self.deposition == 'cic':
-                func = CICDeposit_2
-
             if self.weight_field is None:
                 deposit_vals = fdata[:, fi]
             else:
                 deposit_vals = wdata*fdata[:, fi]
 
-            func(bf_x, bf_y, deposit_vals, Np,
-                 storage.values[:, :, fi],
-                 LE,
-                 self.GridDimensions,
-                 cell_size)
+            func(bf_x, bf_y, deposit_vals, fdata[:, fi].size,
+                 storage.values[:, :, fi], self.x_bins, self.y_bins)
 
             locs = storage.values[:, :, fi] != 0.0
             storage.used[locs] = True
 
             if self.weight_field is not None:
-                func(bf_x, bf_y, wdata, Np,
-                     storage.weight_values,
-                     LE,
-                     self.GridDimensions,
-                     cell_size)
+                func(bf_x, bf_y, wdata, fdata[:, fi].size,
+                     storage.weight_values, self.x_bins, self.y_bins)
             else:
                 storage.weight_values[locs] = 1.0
-            storage.mvalues[locs, fi] = storage.values[locs, fi] \
-                                        / storage.weight_values[locs]
+            storage.mvalues[locs, fi] = \
+                storage.values[locs, fi] / storage.weight_values[locs]
         # We've binned it!
 
 
@@ -950,10 +938,12 @@
         to n.  If the profile is 2D or 3D, a list of values can be given to
         control the summation in each dimension independently.
         Default: False.
-    fractional : If True the profile values are divided by the sum of all
+    fractional : bool
+        If True the profile values are divided by the sum of all
         the profile data such that the profile represents a probability
         distribution function.
-    deposition : Controls the type of deposition used for ParticlePhasePlots.
+    deposition : strings
+        Controls the type of deposition used for ParticlePhasePlots.
         Valid choices are 'ngp' and 'cic'. Default is 'ngp'. This parameter is
         ignored the if the input fields are not of particle type.
 
@@ -983,18 +973,37 @@
         is_pfield.append(wf.particle_type)
         wf = wf.name
 
+    if len(bin_fields) > 1 and isinstance(accumulation, bool):
+        accumulation = [accumulation for _ in range(len(bin_fields))]
+
+    bin_fields = data_source._determine_fields(bin_fields)
+    fields = data_source._determine_fields(fields)
+    units = sanitize_field_tuple_keys(units, data_source)
+    extrema = sanitize_field_tuple_keys(extrema, data_source)
+    logs = sanitize_field_tuple_keys(logs, data_source)
+
     if any(is_pfield) and not all(is_pfield):
         raise YTIllDefinedProfile(
             bin_fields, data_source._determine_fields(fields), wf, is_pfield)
     elif len(bin_fields) == 1:
         cls = Profile1D
     elif len(bin_fields) == 2 and all(is_pfield):
-        # log bin_fields set to False for Particle Profiles.
-        # doesn't make much sense for CIC deposition.
-        # accumulation and fractional set to False as well.
-        logs = {bin_fields[0]: False, bin_fields[1]: False}
-        accumulation = False
-        fractional = False
+        if deposition == 'cic':
+            if logs is not None:
+                if ((bin_fields[0] in logs and logs[bin_fields[0]]) or
+                    (bin_fields[1] in logs and logs[bin_fields[1]])):
+                    raise RuntimeError(
+                        "CIC deposition is only implemented for linear-scaled "
+                        "axes")
+                else:
+                    logs[bin_fields[0]] = False
+                    logs[bin_fields[1]] = False
+            else:
+                logs = {bin_fields[0]: False, bin_fields[1]: False}
+            if any(accumulation) or fractional:
+                raise RuntimeError(
+                    'The accumulation and fractional keyword arguments must be '
+                    'False for CIC deposition')
         cls = ParticleProfile
     elif len(bin_fields) == 2:
         cls = Profile2D
@@ -1002,11 +1011,6 @@
         cls = Profile3D
     else:
         raise NotImplementedError
-    bin_fields = data_source._determine_fields(bin_fields)
-    fields = data_source._determine_fields(fields)
-    units = sanitize_field_tuple_keys(units, data_source)
-    extrema = sanitize_field_tuple_keys(extrema, data_source)
-    logs = sanitize_field_tuple_keys(logs, data_source)
     if weight_field is not None and cls == ParticleProfile:
         weight_field, = data_source._determine_fields([weight_field])
         if not data_source.ds._get_field_info(weight_field).particle_type:
@@ -1053,18 +1057,15 @@
                 field_ex[1] = data_source.ds.quan(field_ex[1][0], field_ex[1][1])
                 field_ex[1] = field_ex[1].in_units(bf_units)
             ex.append(field_ex)
+    args = [data_source]
+    for f, n, (mi, ma), l in zip(bin_fields, n_bins, ex, logs):
+        args += [f, n, mi, ma, l]
+    kwargs = dict(weight_field=weight_field)
     if cls is ParticleProfile:
-        args = [data_source]
-        for f, n, (mi, ma) in zip(bin_fields, n_bins, ex):
-            args += [f, n, mi, ma]
-        obj = cls(*args, weight_field=weight_field, deposition=deposition)
-    else:
-        args = [data_source]
-        for f, n, (mi, ma), l in zip(bin_fields, n_bins, ex, logs):
-            args += [f, n, mi, ma, l]
-        obj = cls(*args, weight_field = weight_field)
-        setattr(obj, "accumulation", accumulation)
-        setattr(obj, "fractional", fractional)
+        kwargs['deposition'] = deposition
+    obj = cls(*args, **kwargs)
+    setattr(obj, "accumulation", accumulation)
+    setattr(obj, "fractional", fractional)
     if fields is not None:
         obj.add_fields([field for field in fields])
     for field in fields:

diff -r 85cb5b40cd70e9772c4f581e8133692d5f73ed9e -r b3510aeb63cd0f0ce506324e89a025d45de25eee yt/utilities/lib/particle_mesh_operations.pyx
--- a/yt/utilities/lib/particle_mesh_operations.pyx
+++ b/yt/utilities/lib/particle_mesh_operations.pyx
@@ -83,88 +83,97 @@
 
 @cython.boundscheck(False)
 @cython.wraparound(False)
-def CICDeposit_2(np.ndarray[np.float64_t, ndim=1] posx,
-                 np.ndarray[np.float64_t, ndim=1] posy,
-                 np.ndarray[np.float64_t, ndim=1] mass,
+ at cython.cdivision(True)
+def CICDeposit_2(np.float64_t[:] posx,
+                 np.float64_t[:] posy,
+                 np.float64_t[:] mass,
                  np.int64_t npositions,
-                 np.ndarray[np.float64_t, ndim=2] field,
-                 np.ndarray[np.float64_t, ndim=1] leftEdge,
-                 np.ndarray[np.int32_t, ndim=1] gridDimension,
-                 np.ndarray[np.float64_t, ndim=1] cellSize):
+                 np.float64_t[:, :] field,
+                 np.float64_t[:] x_bin_edges,
+                 np.float64_t[:] y_bin_edges):
 
     cdef int i1, j1, n
     cdef np.float64_t xpos, ypos
-    cdef np.float64_t edge0, edge1
-    cdef np.float64_t le0, le1
-    cdef np.float64_t dx, dy, dx2, dy2
+    cdef np.float64_t edgex, edgey
+    cdef np.float64_t dx, dy, ddx, ddy, ddx2, ddy2
 
-    edge0 = (<np.float64_t> gridDimension[0]) - 0.5001
-    edge1 = (<np.float64_t> gridDimension[1]) - 0.5001
+    edgex = (<np.float64_t> x_bin_edges.shape[0]) + 0.5001
+    edgey = (<np.float64_t> y_bin_edges.shape[0]) + 0.5001
 
-    le0 = leftEdge[0]
-    le1 = leftEdge[1]
+    # We are always dealing with uniformly spaced bins for CiC
+    dx = x_bin_edges[1] - x_bin_edges[0]
+    dy = y_bin_edges[1] - y_bin_edges[0]
 
     for n in range(npositions):
 
         # Compute the position of the central cell
-        xpos = (posx[n] - le0)/cellSize[0]
-        ypos = (posy[n] - le1)/cellSize[1]
+        xpos = (posx[n] - x_bin_edges[0])/dx
+        ypos = (posy[n] - y_bin_edges[0])/dy
 
-        if (xpos < 0.5001) or (xpos > edge0):
+        if (xpos < -0.5001) or (xpos > edgex):
             continue
-        if (ypos < 0.5001) or (ypos > edge1):
+        if (ypos < -0.5001) or (ypos > edgey):
             continue
 
-        i1  = <int> (xpos + 0.5)
-        j1  = <int> (ypos + 0.5)
+        i1  = <int> (xpos)
+        j1  = <int> (ypos)
 
         # Compute the weights
-        dx = (<np.float64_t> i1) + 0.5 - xpos
-        dy = (<np.float64_t> j1) + 0.5 - ypos
-        dx2 =  1.0 - dx
-        dy2 =  1.0 - dy
+        ddx = (<np.float64_t> i1) + 0.5 - xpos
+        ddy = (<np.float64_t> j1) + 0.5 - ypos
+        ddx2 =  1.0 - ddx
+        ddy2 =  1.0 - ddy
 
         # Deposit onto field
-        field[i1-1,j1-1] += mass[n] * dx  * dy
-        field[i1  ,j1-1] += mass[n] * dx2 * dy
-        field[i1-1,j1  ] += mass[n] * dx  * dy2
-        field[i1  ,j1  ] += mass[n] * dx2 * dy2
+        if i1 > 0 and j1 > 0:
+            field[i1-1,j1-1] += mass[n] * ddx  * ddy
+        if j1 > 0 and i1 < field.shape[0]:
+            field[i1  ,j1-1] += mass[n] * ddx2 * ddy
+        if i1 > 0 and j1 < field.shape[1]:
+            field[i1-1,j1  ] += mass[n] * ddx  * ddy2
+        if i1 < field.shape[0] and j1 < field.shape[1]:
+            field[i1  ,j1  ] += mass[n] * ddx2 * ddy2
 
 @cython.boundscheck(False)
 @cython.wraparound(False)
-def NGPDeposit_2(np.ndarray[np.float64_t, ndim=1] posx,
-                 np.ndarray[np.float64_t, ndim=1] posy,
-                 np.ndarray[np.float64_t, ndim=1] mass,
+ at cython.cdivision(True)
+def NGPDeposit_2(np.float64_t[:] posx,
+                 np.float64_t[:] posy,
+                 np.float64_t[:] mass,
                  np.int64_t npositions,
-                 np.ndarray[np.float64_t, ndim=2] field,
-                 np.ndarray[np.float64_t, ndim=1] leftEdge,
-                 np.ndarray[np.int32_t, ndim=1] gridDimension,
-                 np.ndarray[np.float64_t, ndim=1] cellSize):
+                 np.float64_t[:, :] field,
+                 np.float64_t[:] x_bin_edges,
+                 np.float64_t[:] y_bin_edges):
 
-    cdef int i1, j1, n
+    cdef int i, j, i1, j1, n
     cdef np.float64_t xpos, ypos
     cdef np.float64_t edge0, edge1
     cdef np.float64_t le0, le1
+    cdef np.float64_t[2] x_endpoints
+    cdef np.float64_t[2] y_endpoints
 
-    edge0 = (<np.float64_t> gridDimension[0]) - 0.5001
-    edge1 = (<np.float64_t> gridDimension[1]) - 0.5001
-
-    le0 = leftEdge[0]
-    le1 = leftEdge[1]
+    x_endpoints = (x_bin_edges[0], x_bin_edges[x_bin_edges.shape[0] - 1])
+    y_endpoints = (y_bin_edges[0], y_bin_edges[y_bin_edges.shape[0] - 1])
 
     for n in range(npositions):
 
-        # Compute the position of the central cell
-        xpos = (posx[n] - le0)/cellSize[0]
-        ypos = (posy[n] - le1)/cellSize[1]
+        xpos = posx[n]
+        ypos = posy[n]
 
-        if (xpos < 0.5001) or (xpos > edge0):
+        if (xpos < x_endpoints[0]) or (xpos > x_endpoints[1]):
             continue
-        if (ypos < 0.5001) or (ypos > edge1):
+        if (ypos < y_endpoints[0]) or (ypos > y_endpoints[1]):
             continue
 
-        i1  = <int> (xpos + 0.5)
-        j1  = <int> (ypos + 0.5)
+        for i in range(x_bin_edges.shape[0]):
+            if (xpos >= x_bin_edges[i]) and (xpos < x_bin_edges[i+1]):
+                i1 = i
+                break
+
+        for j in range(y_bin_edges.shape[0]):
+            if (ypos >= y_bin_edges[j]) and (ypos < y_bin_edges[j+1]):
+                j1 = j
+                break
 
         # Deposit onto field
         field[i1,j1] += mass[n]


https://bitbucket.org/yt_analysis/yt/commits/bb7b69e7c5b4/
Changeset:   bb7b69e7c5b4
User:        ngoldbaum
Date:        2017-07-26 20:41:28+00:00
Summary:     Fix corner case for setting field extrema via create_profile
Affected #:  2 files

diff -r b3510aeb63cd0f0ce506324e89a025d45de25eee -r bb7b69e7c5b476cf8cfb683e6a8567dcf740b0de yt/data_objects/profiles.py
--- a/yt/data_objects/profiles.py
+++ b/yt/data_objects/profiles.py
@@ -1044,12 +1044,24 @@
                 field_ex = list(extrema[bin_field[-1]])
             except KeyError:
                 field_ex = list(extrema[bin_field])
+            if isinstance(field_ex[0], tuple):
+                field_ex = [data_source.ds.quan(*f) for f in field_ex]
+            if any([ex is None for ex in field_ex]):
+                ds_extrema = data_source.quantities.extrema(bin_field)
+                for i, exi in enumerate(field_ex):
+                    if exi is None:
+                        field_ex[i] = ds_extrema[i]
             if units is not None and bin_field in units:
-                if isinstance(field_ex[0], tuple):
-                    field_ex = [data_source.ds.quan(*f) for f in field_ex]
-                fe = data_source.ds.arr(field_ex, units[bin_field])
-                fe.convert_to_units(bf_units)
-                field_ex = [fe[0].v, fe[1].v]
+                for i, exi in enumerate(field_ex):
+                    if hasattr(exi, 'units'):
+                        field_ex[i] = exi.to(units[bin_field])
+                    else:
+                        field_ex[i] = data_source.ds.quan(exi, units[bin_field])
+                fe = data_source.ds.arr(field_ex)
+            else:
+                fe = data_source.ds.arr(field_ex)
+            fe.convert_to_units(bf_units)
+            field_ex = [fe[0].v, fe[1].v]
             if iterable(field_ex[0]):
                 field_ex[0] = data_source.ds.quan(field_ex[0][0], field_ex[0][1])
                 field_ex[0] = field_ex[0].in_units(bf_units)

diff -r b3510aeb63cd0f0ce506324e89a025d45de25eee -r bb7b69e7c5b476cf8cfb683e6a8567dcf740b0de yt/data_objects/tests/test_profiles.py
--- a/yt/data_objects/tests/test_profiles.py
+++ b/yt/data_objects/tests/test_profiles.py
@@ -124,6 +124,19 @@
         p3d.add_fields(["ones"])
         assert_equal(p3d["ones"], np.ones((nb,nb,nb)))
 
+        p2d = create_profile(dd, ('gas', 'density'), ('gas', 'temperature'),
+                             weight_field=('gas', 'cell_mass'),
+                             extrema={'density': (None, rma*e2)})
+        assert_equal(p2d.x_bins[0], rmi)
+        assert_equal(p2d.x_bins[-1], rma*e2)
+
+        p2d = create_profile(dd, ('gas', 'density'), ('gas', 'temperature'),
+                             weight_field=('gas', 'cell_mass'),
+                             extrema={'density': (rmi*e2, None)})
+        assert_equal(p2d.x_bins[0], rmi*e2)
+        assert_equal(p2d.x_bins[-1], rma)
+
+
 extrema_s = {'particle_position_x': (0, 1)}
 logs_s = {'particle_position_x': False}
 
@@ -220,3 +233,40 @@
         "particle_velocity_x",
         weight_field=None)
     assert profile['particle_velocity_x'].min() < 0
+    assert profile.x_bins.min() > 0
+    assert profile.y_bins.min() > 0
+
+    profile = yt.create_profile(
+        ad,
+        ["particle_position_x", "particle_position_y"],
+        "particle_velocity_x",
+        logs = {'particle_position_x': False,
+                'particle_position_y': False,
+                'particle_position_z': False},
+        weight_field=None)
+    assert profile['particle_velocity_x'].min() < 0
+    assert profile.x_bins.min() < 0
+    assert profile.y_bins.min() < 0
+
+    # can't use CIC deposition with log-scaled bin fields
+    with assert_raises(RuntimeError):
+        yt.create_profile(
+            ad,
+            ["particle_position_x", "particle_position_y"],
+            "particle_velocity_x",
+            logs = {'particle_position_x': True,
+                    'particle_position_y': False,
+                    'particle_position_z': False},
+            weight_field=None, deposition='cic')
+
+    # can't use CIC deposition with accumulation or fractional
+    with assert_raises(RuntimeError):
+        yt.create_profile(
+            ad,
+            ["particle_position_x", "particle_position_y"],
+            "particle_velocity_x",
+            logs = {'particle_position_x': False,
+                    'particle_position_y': False,
+                    'particle_position_z': False},
+            weight_field=None, deposition='cic',
+            accumulation=True, fractional=True)


https://bitbucket.org/yt_analysis/yt/commits/1594375115c4/
Changeset:   1594375115c4
User:        ngoldbaum
Date:        2017-07-26 20:41:55+00:00
Summary:     Make PhasePlot recompute profiles only as needed. Fixes #1448
Affected #:  3 files

diff -r bb7b69e7c5b476cf8cfb683e6a8567dcf740b0de -r 1594375115c49a0f250f25f9fce1525f9ed6ef4a yt/visualization/plot_container.py
--- a/yt/visualization/plot_container.py
+++ b/yt/visualization/plot_container.py
@@ -85,6 +85,9 @@
         if hasattr(args[0], '_data_valid'):
             if not args[0]._data_valid:
                 args[0]._recreate_frb()
+        if hasattr(args[0], '_profile_valid'):
+            if not args[0]._profile_valid:
+                args[0]._recreate_profile()
         if not args[0]._plot_valid:
             # it is the responsibility of _setup_plots to
             # call args[0].run_callbacks()

diff -r bb7b69e7c5b476cf8cfb683e6a8567dcf740b0de -r 1594375115c49a0f250f25f9fce1525f9ed6ef4a yt/visualization/profile_plotter.py
--- a/yt/visualization/profile_plotter.py
+++ b/yt/visualization/profile_plotter.py
@@ -20,11 +20,11 @@
 from collections import OrderedDict
 import base64
 import os
+from functools import wraps
 
 import matplotlib
 import numpy as np
 
-
 from .base_plot_types import \
     PlotMPL, ImagePlotMPL
 from .plot_container import \
@@ -61,6 +61,14 @@
         canvas_cls = mpl.FigureCanvasAgg
     return canvas_cls
 
+def invalidate_profile(f):
+    @wraps(f)
+    def newfunc(*args, **kwargs):
+        rv = f(*args, **kwargs)
+        args[0]._profile_valid = False
+        return rv
+    return newfunc
+
 class PlotContainerDict(OrderedDict):
     def __missing__(self, key):
         plot = PlotMPL((10, 8), [0.1, 0.1, 0.8, 0.8], None, None)
@@ -729,6 +737,7 @@
     y_log = None
     plot_title = None
     _plot_valid = False
+    _profile_valid = False
     _plot_type = 'Phase'
     _xlim = (None, None)
     _ylim = (None, None)
@@ -766,7 +775,9 @@
         obj._text_xpos = {}
         obj._text_ypos = {}
         obj._text_kwargs = {}
-        obj.profile = profile
+        obj._profile = profile
+        obj._xlim = (None, None)
+        obj._ylim = (None, None)
         super(PhasePlot, obj).__init__(data_source, figure_size, fontsize)
         obj._setup_plots()
         obj._initfinished = True
@@ -828,6 +839,12 @@
         # needed for API compatibility with PlotWindow
         pass
 
+    @property
+    def profile(self):
+        if not self._profile_valid:
+            self._recreate_profile()
+        return self._profile
+
     def _setup_plots(self):
         if self._plot_valid:
             return
@@ -1190,11 +1207,14 @@
             self.y_log = log
             for field in self.profile.field_data:
                 self.z_log[field] = log
+            self._profile_valid = False
         else:
             if field == self.profile.x_field[1]:
                 self.x_log = log
+                self._profile_valid = False
             elif field == self.profile.y_field[1]:
                 self.y_log = log
+                self._profile_valid = False
             elif field in self.profile.field_map:
                 self.z_log[self.profile.field_map[field]] = log
             else:
@@ -1226,6 +1246,7 @@
         return self
 
     @invalidate_plot
+    @invalidate_profile
     def set_xlim(self, xmin=None, xmax=None):
         """Sets the limits of the x bin field
 
@@ -1233,12 +1254,12 @@
         ----------
 
         xmin : float or None
-          The new x minimum.  Defaults to None, which leaves the xmin
-          unchanged.
+          The new x minimum in the current x-axis units.  Defaults to None,
+          which leaves the xmin unchanged.
 
         xmax : float or None
-          The new x maximum.  Defaults to None, which leaves the xmax
-          unchanged.
+          The new x maximum in the current x-axis units.  Defaults to None,
+          which leaves the xmax unchanged.
 
         Examples
         --------
@@ -1253,44 +1274,17 @@
         p = self.profile
         if xmin is None:
             xmin = p.x_bins.min()
+        elif not hasattr(xmin, 'units'):
+            xmin = self.ds.quan(xmin, p.y_bins.units)
         if xmax is None:
             xmax = p.x_bins.max()
-        units = {p.x_field: str(p.x.units),
-                 p.y_field: str(p.y.units)}
-        zunits = dict((field, str(p.field_units[field])) for field in p.field_units)
-        extrema = {p.x_field: ((xmin, str(p.x.units)), (xmax, str(p.x.units))),
-                   p.y_field: ((p.y_bins.min(), str(p.y.units)),
-                               (p.y_bins.max(), str(p.y.units)))}
-        if self.x_log is not None or self.y_log is not None:
-            logs = {}
-        else:
-            logs = None
-        if self.x_log is not None:
-            logs[p.x_field] = self.x_log
-        if self.y_log is not None:
-            logs[p.y_field] = self.y_log
-        deposition = getattr(self.profile, "deposition", None)
-        if deposition is None:
-            additional_kwargs = {'accumulation': p.accumulation,
-                                 'fractional': p.fractional}
-        else:
-            additional_kwargs = {'deposition': p.deposition}
-        self.profile = create_profile(
-            p.data_source,
-            [p.x_field, p.y_field],
-            list(p.field_map.values()),
-            n_bins=[len(p.x_bins)-1, len(p.y_bins)-1],
-            weight_field=p.weight_field,
-            units=units,
-            extrema=extrema,
-            logs=logs,
-            **additional_kwargs)
-        for field in zunits:
-            self.profile.set_field_unit(field, zunits[field])
+        elif not hasattr(xmax, 'units'):
+            xmax = self.ds.quan(xmax, p.y_bins.units)
         self._xlim = (xmin, xmax)
         return self
 
     @invalidate_plot
+    @invalidate_profile
     def set_ylim(self, ymin=None, ymax=None):
         """Sets the plot limits for the y bin field.
 
@@ -1298,12 +1292,12 @@
         ----------
 
         ymin : float or None
-          The new y minimum.  Defaults to None, which leaves the ymin
-          unchanged.
+          The new y minimum in the current y-axis units.  Defaults to None,
+          which leaves the ymin unchanged.
 
         ymax : float or None
-          The new y maximum.  Defaults to None, which leaves the ymax
-          unchanged.
+          The new y maximum in the current y-axis units.  Defaults to None,
+          which leaves the ymax unchanged.
 
         Examples
         --------
@@ -1318,14 +1312,21 @@
         p = self.profile
         if ymin is None:
             ymin = p.y_bins.min()
+        elif not hasattr(ymin, 'units'):
+            ymin = self.ds.quan(ymin, p.y_bins.units)
         if ymax is None:
             ymax = p.y_bins.max()
+        elif not hasattr(ymax, 'units'):
+            ymax = self.ds.quan(ymax, p.y_bins.units)
+        self._ylim = (ymin, ymax)
+        return self
+
+    def _recreate_profile(self):
+        p = self._profile
         units = {p.x_field: str(p.x.units),
                  p.y_field: str(p.y.units)}
         zunits = dict((field, str(p.field_units[field])) for field in p.field_units)
-        extrema = {p.x_field: ((p.x_bins.min(), str(p.x.units)),
-                               (p.x_bins.max(), str(p.x.units))),
-                   p.y_field: ((ymin, str(p.y.units)), (ymax, str(p.y.units)))}
+        extrema = {p.x_field: self._xlim, p.y_field: self._ylim}
         if self.x_log is not None or self.y_log is not None:
             logs = {}
         else:
@@ -1334,13 +1335,11 @@
             logs[p.x_field] = self.x_log
         if self.y_log is not None:
             logs[p.y_field] = self.y_log
-        deposition = getattr(self.profile, "deposition", None)
-        if deposition is None:
-            additional_kwargs = {'accumulation': p.accumulation,
-                                 'fractional': p.fractional}
-        else:
-            additional_kwargs = {'deposition': p.deposition}
-        self.profile = create_profile(
+        deposition = getattr(p, "deposition", None)
+        additional_kwargs = {'accumulation': p.accumulation,
+                             'fractional': p.fractional,
+                             'deposition': deposition}
+        self._profile = create_profile(
             p.data_source,
             [p.x_field, p.y_field],
             list(p.field_map.values()),
@@ -1351,9 +1350,8 @@
             logs=logs,
             **additional_kwargs)
         for field in zunits:
-            self.profile.set_field_unit(field, zunits[field])
-        self._ylim = (ymin, ymax)
-        return self
+            self._profile.set_field_unit(field, zunits[field])
+
 
 
 class PhasePlotMPL(ImagePlotMPL):

diff -r bb7b69e7c5b476cf8cfb683e6a8567dcf740b0de -r 1594375115c49a0f250f25f9fce1525f9ed6ef4a yt/visualization/tests/test_particle_plot.py
--- a/yt/visualization/tests/test_particle_plot.py
+++ b/yt/visualization/tests/test_particle_plot.py
@@ -16,19 +16,28 @@
 import tempfile
 import shutil
 import unittest
+
+import numpy as np
+
 from yt.data_objects.profiles import create_profile
 from yt.visualization.tests.test_plotwindow import \
     assert_fname, WIDTH_SPECS, ATTR_ARGS
+from yt.convenience import load
 from yt.data_objects.particle_filters import add_particle_filter
 from yt.testing import \
-    fake_particle_ds, assert_array_almost_equal
+    fake_particle_ds, \
+    assert_array_almost_equal, \
+    requires_file, \
+    assert_allclose
 from yt.utilities.answer_testing.framework import \
     requires_ds, \
     data_dir_load, \
     PlotWindowAttributeTest, \
     PhasePlotAttributeTest
 from yt.visualization.api import \
-    ParticleProjectionPlot, ParticlePhasePlot
+    ParticlePlot, \
+    ParticleProjectionPlot, \
+    ParticlePhasePlot
 from yt.units.yt_array import YTArray
 
 
@@ -207,6 +216,53 @@
             for fname in TEST_FLNMS:
                 assert assert_fname(p.save(fname)[0])
 
+tgal = 'TipsyGalaxy/galaxy.00300'
+ at requires_file(tgal)
+def test_particle_phase_plot_semantics():
+    ds = load(tgal)
+    ad = ds.all_data()
+    dens_ex = ad.quantities.extrema(('Gas', 'density'))
+    temp_ex = ad.quantities.extrema(('Gas', 'temperature'))
+    plot = ParticlePlot(ds,
+                        ('Gas', 'density'),
+                        ('Gas', 'temperature'),
+                        ('Gas', 'particle_mass'))
+    plot.set_log('density', True)
+    plot.set_log('temperature', True)
+    p = plot.profile
+
+    # bin extrema are field extrema
+    assert dens_ex[0] == p.x_bins[0]
+    assert dens_ex[-1] == p.x_bins[-1]
+    assert temp_ex[0] == p.y_bins[0]
+    assert temp_ex[-1] == p.y_bins[-1]
+
+    # bins are evenly spaced in log space
+    logxbins = np.log10(p.x_bins)
+    dxlogxbins = logxbins[1:] - logxbins[:-1]
+    assert_allclose(dxlogxbins, dxlogxbins[0])
+
+    logybins = np.log10(p.y_bins)
+    dylogybins = logybins[1:] - logybins[:-1]
+    assert_allclose(dylogybins, dylogybins[0])
+
+    plot.set_log('density', False)
+    plot.set_log('temperature', False)
+    p = plot.profile
+
+    # bin extrema are field extrema
+    assert dens_ex[0] == p.x_bins[0]
+    assert dens_ex[-1] == p.x_bins[-1]
+    assert temp_ex[0] == p.y_bins[0]
+    assert temp_ex[-1] == p.y_bins[-1]
+
+    # bins are evenly spaced in log space
+    dxbins = p.x_bins[1:] - p.x_bins[:-1]
+    assert_allclose(dxbins, dxbins[0])
+
+    dybins = p.y_bins[1:] - p.y_bins[:-1]
+    assert_allclose(dybins, dybins[0])
+    import pdb; pdb.set_trace()
 
 class TestParticleProjectionPlotSave(unittest.TestCase):
 


https://bitbucket.org/yt_analysis/yt/commits/d7db3447fbe1/
Changeset:   d7db3447fbe1
User:        ngoldbaum
Date:        2017-07-26 20:55:12+00:00
Summary:     Merge branch 'master' into phase-plot-fixes
Affected #:  10 files

diff -r 1594375115c49a0f250f25f9fce1525f9ed6ef4a -r d7db3447fbe1a15cd511b93c63f7ca257a2d2561 CONTRIBUTING.rst
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -722,7 +722,7 @@
 cleaned up the rest of the yt codebase of the errors and warnings detected by
 the `flake8` tool. Note that this will only trigger a subset of the `full flake8
 error and warning list
-<http://flake8.readthedocs.org/en/latest/warnings.html>`_, since we explicitly
+<http://flake8.readthedocs.io/en/latest/user/error-codes.html>`_, since we explicitly
 blacklist a large number of the full list of rules that are checked by
 ``flake8`` by default.
 

diff -r 1594375115c49a0f250f25f9fce1525f9ed6ef4a -r d7db3447fbe1a15cd511b93c63f7ca257a2d2561 appveyor.yml
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -7,8 +7,8 @@
       PYTHON: "C:\\Miniconda3-x64"
 
   matrix:
+      - PYTHON_VERSION: "2.7"
       - PYTHON_VERSION: "3.6"
-      - PYTHON_VERSION: "2.7"
 
 platform:
     -x64
@@ -21,6 +21,7 @@
 
     # Install the build and runtime dependencies of the project.
     # Create a conda environment
+    - "conda update -q --yes conda"
     - "conda create -q --yes -n test python=%PYTHON_VERSION%"
     - "activate test"
 
@@ -28,7 +29,7 @@
     - "python --version"
 
     # Install specified version of numpy and dependencies
-    - "conda install -q --yes -c conda-forge numpy scipy nose setuptools ipython Cython sympy fastcache h5py matplotlib flake8 mock"
+    - "conda install --yes -c conda-forge numpy scipy nose setuptools ipython Cython sympy fastcache h5py matplotlib flake8 mock"
     - "pip install -e ."
 
 # Not a .NET project
@@ -36,3 +37,9 @@
 
 test_script:
   - "nosetests --nologcapture -sv yt"
+
+# Enable this to be able to login to the build worker. You can use the
+# `remmina` program in Ubuntu, use the login information that the line below
+# prints into the log.
+#on_finish:
+#- ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))

diff -r 1594375115c49a0f250f25f9fce1525f9ed6ef4a -r d7db3447fbe1a15cd511b93c63f7ca257a2d2561 yt/testing.py
--- a/yt/testing.py
+++ b/yt/testing.py
@@ -1019,3 +1019,30 @@
     at = at.value
 
     return assert_allclose(act, des, rt, at, **kwargs)
+
+def assert_fname(fname):
+    """Function that checks file type using libmagic"""
+    if fname is None:
+        return
+
+    with open(fname, 'rb') as fimg:
+        data = fimg.read()
+    image_type = ''
+
+    # see http://www.w3.org/TR/PNG/#5PNG-file-signature
+    if data.startswith(b'\211PNG\r\n\032\n'):
+        image_type = '.png'
+    # see http://www.mathguide.de/info/tools/media-types/image/jpeg
+    elif data.startswith(b'\377\330'):
+        image_type = '.jpeg'
+    elif data.startswith(b'%!PS-Adobe'):
+        data_str = data.decode("utf-8", "ignore")
+        if 'EPSF' in data_str[:data_str.index('\n')]:
+            image_type = '.eps'
+        else:
+            image_type = '.ps'
+    elif data.startswith(b'%PDF'):
+        image_type = '.pdf'
+
+    return image_type == os.path.splitext(fname)[1]
+

diff -r 1594375115c49a0f250f25f9fce1525f9ed6ef4a -r d7db3447fbe1a15cd511b93c63f7ca257a2d2561 yt/visualization/tests/test_callbacks.py
--- a/yt/visualization/tests/test_callbacks.py
+++ b/yt/visualization/tests/test_callbacks.py
@@ -23,9 +23,9 @@
 from yt.testing import \
     fake_amr_ds, \
     fake_tetrahedral_ds, \
-    fake_hexahedral_ds
+    fake_hexahedral_ds, \
+    assert_fname
 import yt.units as u
-from .test_plotwindow import assert_fname
 from yt.utilities.exceptions import \
     YTPlotCallbackError, \
     YTDataTypeUnsupported

diff -r 1594375115c49a0f250f25f9fce1525f9ed6ef4a -r d7db3447fbe1a15cd511b93c63f7ca257a2d2561 yt/visualization/tests/test_particle_plot.py
--- a/yt/visualization/tests/test_particle_plot.py
+++ b/yt/visualization/tests/test_particle_plot.py
@@ -21,14 +21,16 @@
 
 from yt.data_objects.profiles import create_profile
 from yt.visualization.tests.test_plotwindow import \
-    assert_fname, WIDTH_SPECS, ATTR_ARGS
+    WIDTH_SPECS, ATTR_ARGS
 from yt.convenience import load
+    WIDTH_SPECS, ATTR_ARGS
 from yt.data_objects.particle_filters import add_particle_filter
 from yt.testing import \
     fake_particle_ds, \
     assert_array_almost_equal, \
     requires_file, \
-    assert_allclose
+    assert_allclose, \
+    assert_fname
 from yt.utilities.answer_testing.framework import \
     requires_ds, \
     data_dir_load, \

diff -r 1594375115c49a0f250f25f9fce1525f9ed6ef4a -r d7db3447fbe1a15cd511b93c63f7ca257a2d2561 yt/visualization/tests/test_plotwindow.py
--- a/yt/visualization/tests/test_plotwindow.py
+++ b/yt/visualization/tests/test_plotwindow.py
@@ -24,7 +24,7 @@
 
 from yt.testing import \
     fake_random_ds, assert_equal, assert_rel_equal, assert_array_equal, \
-    assert_array_almost_equal, assert_raises
+    assert_array_almost_equal, assert_raises, assert_fname
 from yt.utilities.answer_testing.framework import \
     requires_ds, data_dir_load, PlotWindowAttributeTest
 from yt.utilities.exceptions import \
@@ -43,33 +43,6 @@
     ytcfg["yt", "__withintesting"] = "True"
 
 
-def assert_fname(fname):
-    """Function that checks file type using libmagic"""
-    if fname is None:
-        return
-
-    with open(fname, 'rb') as fimg:
-        data = fimg.read()
-    image_type = ''
-
-    # see http://www.w3.org/TR/PNG/#5PNG-file-signature
-    if data.startswith(b'\211PNG\r\n\032\n'):
-        image_type = '.png'
-    # see http://www.mathguide.de/info/tools/media-types/image/jpeg
-    elif data.startswith(b'\377\330'):
-        image_type = '.jpeg'
-    elif data.startswith(b'%!PS-Adobe'):
-        data_str = data.decode("utf-8", "ignore")
-        if 'EPSF' in data_str[:data_str.index('\n')]:
-            image_type = '.eps'
-        else:
-            image_type = '.ps'
-    elif data.startswith(b'%PDF'):
-        image_type = '.pdf'
-
-    return image_type == os.path.splitext(fname)[1]
-
-
 TEST_FLNMS = [None, 'test', 'test.png', 'test.eps',
               'test.ps', 'test.pdf']
 M7 = "DD0010/moving7_0010"

diff -r 1594375115c49a0f250f25f9fce1525f9ed6ef4a -r d7db3447fbe1a15cd511b93c63f7ca257a2d2561 yt/visualization/tests/test_profile_plots.py
--- a/yt/visualization/tests/test_profile_plots.py
+++ b/yt/visualization/tests/test_profile_plots.py
@@ -21,11 +21,12 @@
 from yt.testing import \
     fake_random_ds, \
     assert_array_almost_equal, \
-    requires_file
+    requires_file, \
+    assert_fname
 from yt.visualization.profile_plotter import \
     ProfilePlot, PhasePlot
 from yt.visualization.tests.test_plotwindow import \
-    assert_fname, TEST_FLNMS
+    TEST_FLNMS
 from yt.utilities.answer_testing.framework import \
     PhasePlotAttributeTest, \
     requires_ds, \

diff -r 1594375115c49a0f250f25f9fce1525f9ed6ef4a -r d7db3447fbe1a15cd511b93c63f7ca257a2d2561 yt/visualization/volume_rendering/scene.py
--- a/yt/visualization/volume_rendering/scene.py
+++ b/yt/visualization/volume_rendering/scene.py
@@ -241,8 +241,10 @@
         Parameters
         ----------
         fname: string, optional
-            If specified, save the rendering as a bitmap to the file "fname".
+            If specified, save the rendering as to the file "fname".
             If unspecified, it creates a default based on the dataset filename.
+            The file format is inferred from the filename's suffix. Supported
+            fomats are png, pdf, eps, and ps.
             Default: None
         sigma_clip: float, optional
             Image values greater than this number times the standard deviation
@@ -302,8 +304,38 @@
         self.render()
 
         mylog.info("Saving render %s", fname)
-        self._last_render.write_png(fname, sigma_clip=sigma_clip)
-
+        # We can render pngs natively but for other formats we defer to
+        # matplotlib.
+        if suffix == '.png':
+            self._last_render.write_png(fname, sigma_clip=sigma_clip)
+        else:
+            from matplotlib.figure import Figure
+            from matplotlib.backends.backend_pdf import \
+                FigureCanvasPdf
+            from matplotlib.backends.backend_ps import \
+                FigureCanvasPS
+            shape = self._last_render.shape
+            fig = Figure((shape[0]/100., shape[1]/100.))
+            if suffix == '.pdf':
+                canvas = FigureCanvasPdf(fig)
+            elif suffix in ('.eps', '.ps'):
+                canvas = FigureCanvasPS(fig)
+            else:
+                raise NotImplementedError(
+                    "Unknown file suffix '{}'".format(suffix))
+            ax = fig.add_axes([0, 0, 1, 1])
+            ax.set_axis_off()
+            out = self._last_render
+            nz = out[:, :, :3][out[:, :, :3].nonzero()]
+            max_val = nz.mean() + sigma_clip * nz.std()
+            alpha = 255 * out[:, :, 3].astype('uint8')
+            out = np.clip(out[:, :, :3] / max_val, 0.0, 1.0) * 255
+            out = np.concatenate(
+                [out.astype('uint8'), alpha[..., None]], axis=-1)
+            # not sure why we need rot90, but this makes the orentation
+            # match the png writer
+            ax.imshow(np.rot90(out), origin='lower')
+            canvas.print_figure(fname, dpi=100)
 
     def save_annotated(self, fname=None, label_fmt=None,
                        text_annotate=None, dpi=100, sigma_clip=None):

diff -r 1594375115c49a0f250f25f9fce1525f9ed6ef4a -r d7db3447fbe1a15cd511b93c63f7ca257a2d2561 yt/visualization/volume_rendering/tests/test_scene.py
--- a/yt/visualization/volume_rendering/tests/test_scene.py
+++ b/yt/visualization/volume_rendering/tests/test_scene.py
@@ -16,7 +16,9 @@
 import os
 import tempfile
 import shutil
-from yt.testing import fake_random_ds
+from yt.testing import \
+    fake_random_ds, \
+    assert_fname
 from yt.visualization.volume_rendering.api import volume_render, VolumeSource
 import numpy as np
 from unittest import TestCase
@@ -78,7 +80,10 @@
         ma_bound = ((ma-mi)*(0.90))+mi
         tf.map_to_colormap(mi_bound, ma_bound,  scale=0.01, colormap='Reds_r')
         sc.render()
-        sc.save('test_scene.png', sigma_clip=6.0)
+        for suffix in ['png', 'eps', 'ps', 'pdf']:
+            fname = 'test_scene.{}'.format(suffix)
+            sc.save(fname, sigma_clip=6.0)
+            assert_fname(fname)
 
         nrot = 2 
         for i in range(nrot):

diff -r 1594375115c49a0f250f25f9fce1525f9ed6ef4a -r d7db3447fbe1a15cd511b93c63f7ca257a2d2561 yt/visualization/volume_rendering/tests/test_vr_cameras.py
--- a/yt/visualization/volume_rendering/tests/test_vr_cameras.py
+++ b/yt/visualization/volume_rendering/tests/test_vr_cameras.py
@@ -18,14 +18,14 @@
 import tempfile
 import shutil
 from yt.testing import \
-    fake_random_ds
+    fake_random_ds, \
+    assert_fname
 import numpy as np
 from yt.visualization.volume_rendering.old_camera import \
     PerspectiveCamera, StereoPairCamera, InteractiveCamera, ProjectionCamera, \
     FisheyeCamera
 from yt.visualization.volume_rendering.api import ColorTransferFunction, \
     ProjectionTransferFunction
-from yt.visualization.tests.test_plotwindow import assert_fname
 from unittest import TestCase
 
 


https://bitbucket.org/yt_analysis/yt/commits/30893016dd68/
Changeset:   30893016dd68
User:        ngoldbaum
Date:        2017-07-26 22:58:23+00:00
Summary:     remove pdb call and bad merge mistake
Affected #:  1 file

diff -r d7db3447fbe1a15cd511b93c63f7ca257a2d2561 -r 30893016dd681b17fd698e885b4ac1d4bd2e8acf yt/visualization/tests/test_particle_plot.py
--- a/yt/visualization/tests/test_particle_plot.py
+++ b/yt/visualization/tests/test_particle_plot.py
@@ -23,7 +23,6 @@
 from yt.visualization.tests.test_plotwindow import \
     WIDTH_SPECS, ATTR_ARGS
 from yt.convenience import load
-    WIDTH_SPECS, ATTR_ARGS
 from yt.data_objects.particle_filters import add_particle_filter
 from yt.testing import \
     fake_particle_ds, \
@@ -264,7 +263,6 @@
 
     dybins = p.y_bins[1:] - p.y_bins[:-1]
     assert_allclose(dybins, dybins[0])
-    import pdb; pdb.set_trace()
 
 class TestParticleProjectionPlotSave(unittest.TestCase):
 


https://bitbucket.org/yt_analysis/yt/commits/342466cf6ae6/
Changeset:   342466cf6ae6
User:        ngoldbaum
Date:        2017-07-26 23:06:10+00:00
Summary:     fix issue with handling of unitless extrema
Affected #:  1 file

diff -r 30893016dd681b17fd698e885b4ac1d4bd2e8acf -r 342466cf6ae6a99a2dc4925ce07ed5cb314af606 yt/data_objects/profiles.py
--- a/yt/data_objects/profiles.py
+++ b/yt/data_objects/profiles.py
@@ -1059,7 +1059,10 @@
                         field_ex[i] = data_source.ds.quan(exi, units[bin_field])
                 fe = data_source.ds.arr(field_ex)
             else:
-                fe = data_source.ds.arr(field_ex)
+                if hasattr(field_ex, 'units'):
+                    fe = field_ex.to(bf_units)
+                else:
+                    fe = data_source.ds.arr(field_ex, bf_units)
             fe.convert_to_units(bf_units)
             field_ex = [fe[0].v, fe[1].v]
             if iterable(field_ex[0]):


https://bitbucket.org/yt_analysis/yt/commits/603f23f50362/
Changeset:   603f23f50362
User:        ngoldbaum
Date:        2017-07-27 00:47:57+00:00
Summary:     fix python2 list comprehension variable leaking
Affected #:  1 file

diff -r 342466cf6ae6a99a2dc4925ce07ed5cb314af606 -r 603f23f50362130e7bb47e0f94d80f06670d8e85 yt/data_objects/profiles.py
--- a/yt/data_objects/profiles.py
+++ b/yt/data_objects/profiles.py
@@ -1046,7 +1046,7 @@
                 field_ex = list(extrema[bin_field])
             if isinstance(field_ex[0], tuple):
                 field_ex = [data_source.ds.quan(*f) for f in field_ex]
-            if any([ex is None for ex in field_ex]):
+            if any([exi is None for exi in field_ex]):
                 ds_extrema = data_source.quantities.extrema(bin_field)
                 for i, exi in enumerate(field_ex):
                     if exi is None:


https://bitbucket.org/yt_analysis/yt/commits/e5d0f49e14c5/
Changeset:   e5d0f49e14c5
User:        ngoldbaum
Date:        2017-07-27 17:49:54+00:00
Summary:     Ensure the weight is the correct shape for non-flat data
Affected #:  1 file

diff -r 603f23f50362130e7bb47e0f94d80f06670d8e85 -r e5d0f49e14c5898fcdc7967e7aef2dc040b972b4 yt/data_objects/profiles.py
--- a/yt/data_objects/profiles.py
+++ b/yt/data_objects/profiles.py
@@ -257,7 +257,7 @@
             units = chunk.ds.field_info[self.weight_field].output_units
             weight_data = chunk[self.weight_field].in_units(units)
         else:
-            weight_data = np.ones(filter.size, dtype="float64")
+            weight_data = np.ones(filter.shape, dtype="float64")
         weight_data = weight_data[filter]
         # So that we can pass these into
         return arr, weight_data, bin_fields


https://bitbucket.org/yt_analysis/yt/commits/0add5b996add/
Changeset:   0add5b996add
User:        ngoldbaum
Date:        2017-07-27 17:51:01+00:00
Summary:     Fix issues with profile data reloaded via the ytdata frontend
Affected #:  1 file

diff -r e5d0f49e14c5898fcdc7967e7aef2dc040b972b4 -r 0add5b996add69fcbf28615c93a824cd86e54b58 yt/data_objects/profiles.py
--- a/yt/data_objects/profiles.py
+++ b/yt/data_objects/profiles.py
@@ -347,7 +347,8 @@
         extra_attrs = {"data_type": "yt_profile",
                        "profile_dimensions": self.size,
                        "weight_field": self.weight_field,
-                       "fractional": self.fractional}
+                       "fractional": self.fractional,
+                       "accumulation": self.accumulation}
         data = {}
         data.update(self.field_data)
         data["weight"] = self.weight
@@ -386,6 +387,7 @@
     def __init__(self, ds):
         ProfileND.__init__(self, ds.data, ds.parameters["weight_field"])
         self.fractional = ds.parameters["fractional"]
+        self.accumulation = ds.parameters["accumulation"]
         exclude_fields = ["used", "weight"]
         for ax in "xyz"[:ds.dimensionality]:
             setattr(self, ax, ds.data[ax])
@@ -1047,7 +1049,13 @@
             if isinstance(field_ex[0], tuple):
                 field_ex = [data_source.ds.quan(*f) for f in field_ex]
             if any([exi is None for exi in field_ex]):
-                ds_extrema = data_source.quantities.extrema(bin_field)
+                try:
+                    ds_extrema = data_source.quantities.extrema(bin_field)
+                except AttributeError:
+                    # ytdata profile datasets don't have data_source.quantities
+                    bf_vals = data_source[bin_field]
+                    ds_extrema = data_source.ds.arr(
+                        [bf_vals.min(), bf_vals.max()])
                 for i, exi in enumerate(field_ex):
                     if exi is None:
                         field_ex[i] = ds_extrema[i]


https://bitbucket.org/yt_analysis/yt/commits/3745090c62a0/
Changeset:   3745090c62a0
User:        ngoldbaum
Date:        2017-07-27 17:51:31+00:00
Summary:     Ensure profiles don't get unnecessarily regenerated
Affected #:  1 file

diff -r 0add5b996add69fcbf28615c93a824cd86e54b58 -r 3745090c62a08f33addc22f71019dd2d4a6db345 yt/visualization/profile_plotter.py
--- a/yt/visualization/profile_plotter.py
+++ b/yt/visualization/profile_plotter.py
@@ -776,6 +776,7 @@
         obj._text_ypos = {}
         obj._text_kwargs = {}
         obj._profile = profile
+        obj._profile_valid = True
         obj._xlim = (None, None)
         obj._ylim = (None, None)
         super(PhasePlot, obj).__init__(data_source, figure_size, fontsize)
@@ -1180,7 +1181,7 @@
         >>> plot.annotate_title("This is a phase plot")
 
         """
-        for f in self.profile.field_data:
+        for f in self._profile.field_data:
             if isinstance(f, tuple):
                 f = f[1]
             self.plot_title[self.data_source._determine_fields(f)[0]] = title
@@ -1202,21 +1203,22 @@
         log : boolean
             Log on/off.
         """
+        p = self._profile
         if field == "all":
             self.x_log = log
             self.y_log = log
-            for field in self.profile.field_data:
+            for field in p.field_data:
                 self.z_log[field] = log
             self._profile_valid = False
         else:
-            if field == self.profile.x_field[1]:
+            if field == p.x_field[1]:
                 self.x_log = log
                 self._profile_valid = False
-            elif field == self.profile.y_field[1]:
+            elif field == p.y_field[1]:
                 self.y_log = log
                 self._profile_valid = False
-            elif field in self.profile.field_map:
-                self.z_log[self.profile.field_map[field]] = log
+            elif field in p.field_map:
+                self.z_log[p.field_map[field]] = log
             else:
                 raise KeyError("Field %s not in phase plot!" % (field))
         return self
@@ -1271,15 +1273,15 @@
         >>> pp.save()
 
         """
-        p = self.profile
+        p = self._profile
         if xmin is None:
             xmin = p.x_bins.min()
         elif not hasattr(xmin, 'units'):
-            xmin = self.ds.quan(xmin, p.y_bins.units)
+            xmin = self.ds.quan(xmin, p.x_bins.units)
         if xmax is None:
             xmax = p.x_bins.max()
         elif not hasattr(xmax, 'units'):
-            xmax = self.ds.quan(xmax, p.y_bins.units)
+            xmax = self.ds.quan(xmax, p.x_bins.units)
         self._xlim = (xmin, xmax)
         return self
 
@@ -1309,7 +1311,7 @@
         >>> pp.save()
 
         """
-        p = self.profile
+        p = self._profile
         if ymin is None:
             ymin = p.y_bins.min()
         elif not hasattr(ymin, 'units'):
@@ -1351,7 +1353,7 @@
             **additional_kwargs)
         for field in zunits:
             self._profile.set_field_unit(field, zunits[field])
-
+        self._profile_valid = True
 
 
 class PhasePlotMPL(ImagePlotMPL):


https://bitbucket.org/yt_analysis/yt/commits/b3f047b7f475/
Changeset:   b3f047b7f475
User:        ngoldbaum
Date:        2017-07-27 17:52:11+00:00
Summary:     make ParticlePlot example look a little nicer by using linear-scaled axes
Affected #:  1 file

diff -r 3745090c62a08f33addc22f71019dd2d4a6db345 -r b3f047b7f4757ecb5c0e98aa688d2a1738cde4e7 doc/source/cookbook/particle_xvz_plot.py
--- a/doc/source/cookbook/particle_xvz_plot.py
+++ b/doc/source/cookbook/particle_xvz_plot.py
@@ -11,5 +11,9 @@
 p.set_unit('particle_velocity_z', 'km/s')
 p.set_unit('particle_mass', 'Msun')
 
+# We want to plot position and velocity in linear scale
+p.set_log('particle_position_x', False)
+p.set_log('particle_velocity_z', False)
+
 # save result
 p.save()


https://bitbucket.org/yt_analysis/yt/commits/b0ba8ad225bc/
Changeset:   b0ba8ad225bc
User:        ngoldbaum
Date:        2017-07-27 18:51:07+00:00
Summary:     revert change to defaults for log-scaling particle fields
Affected #:  2 files

diff -r b3f047b7f4757ecb5c0e98aa688d2a1738cde4e7 -r b0ba8ad225bc6c3bdd1a10da026771998dcd7ef2 doc/source/cookbook/particle_xvz_plot.py
--- a/doc/source/cookbook/particle_xvz_plot.py
+++ b/doc/source/cookbook/particle_xvz_plot.py
@@ -11,9 +11,5 @@
 p.set_unit('particle_velocity_z', 'km/s')
 p.set_unit('particle_mass', 'Msun')
 
-# We want to plot position and velocity in linear scale
-p.set_log('particle_position_x', False)
-p.set_log('particle_velocity_z', False)
-
 # save result
 p.save()

diff -r b3f047b7f4757ecb5c0e98aa688d2a1738cde4e7 -r b0ba8ad225bc6c3bdd1a10da026771998dcd7ef2 yt/data_objects/profiles.py
--- a/yt/data_objects/profiles.py
+++ b/yt/data_objects/profiles.py
@@ -997,15 +997,14 @@
                     raise RuntimeError(
                         "CIC deposition is only implemented for linear-scaled "
                         "axes")
-                else:
-                    logs[bin_fields[0]] = False
-                    logs[bin_fields[1]] = False
             else:
                 logs = {bin_fields[0]: False, bin_fields[1]: False}
             if any(accumulation) or fractional:
                 raise RuntimeError(
                     'The accumulation and fractional keyword arguments must be '
                     'False for CIC deposition')
+        elif logs is None:
+            logs = {bin_fields[0]: False, bin_fields[1]: False}
         cls = ParticleProfile
     elif len(bin_fields) == 2:
         cls = Profile2D


https://bitbucket.org/yt_analysis/yt/commits/1368f835bd61/
Changeset:   1368f835bd61
User:        ngoldbaum
Date:        2017-07-27 19:40:36+00:00
Summary:     update new test to reflect reverted behavior in previous commit
Affected #:  1 file

diff -r b0ba8ad225bc6c3bdd1a10da026771998dcd7ef2 -r 1368f835bd61f96160e2c235ab972098a5ef9a1f yt/data_objects/tests/test_profiles.py
--- a/yt/data_objects/tests/test_profiles.py
+++ b/yt/data_objects/tests/test_profiles.py
@@ -231,6 +231,9 @@
         ad,
         ["particle_position_x", "particle_position_y"],
         "particle_velocity_x",
+        logs = {'particle_position_x': True,
+                'particle_position_y': True,
+                'particle_position_z': True},
         weight_field=None)
     assert profile['particle_velocity_x'].min() < 0
     assert profile.x_bins.min() > 0
@@ -240,9 +243,6 @@
         ad,
         ["particle_position_x", "particle_position_y"],
         "particle_velocity_x",
-        logs = {'particle_position_x': False,
-                'particle_position_y': False,
-                'particle_position_z': False},
         weight_field=None)
     assert profile['particle_velocity_x'].min() < 0
     assert profile.x_bins.min() < 0


https://bitbucket.org/yt_analysis/yt/commits/ba12c9f95a75/
Changeset:   ba12c9f95a75
User:        ngoldbaum
Date:        2017-07-27 20:40:46+00:00
Summary:     pad extrema to avoid excluding data points at edges
Affected #:  1 file

diff -r 1368f835bd61f96160e2c235ab972098a5ef9a1f -r ba12c9f95a75d016f841f10bd290b8e316685416 yt/data_objects/profiles.py
--- a/yt/data_objects/profiles.py
+++ b/yt/data_objects/profiles.py
@@ -1058,6 +1058,9 @@
                 for i, exi in enumerate(field_ex):
                     if exi is None:
                         field_ex[i] = ds_extrema[i]
+                        # pad extrema by epsilon so cells at bin edges are
+                        # not excluded
+                        field_ex[i] -= (-1)**i*np.spacing(field_ex[i])
             if units is not None and bin_field in units:
                 for i, exi in enumerate(field_ex):
                     if hasattr(exi, 'units'):


https://bitbucket.org/yt_analysis/yt/commits/23eb3be665c0/
Changeset:   23eb3be665c0
User:        ngoldbaum
Date:        2017-07-27 21:34:02+00:00
Summary:     include epsilon offsets in tests for bin edges
Affected #:  2 files

diff -r ba12c9f95a75d016f841f10bd290b8e316685416 -r 23eb3be665c0c18ca50432a8cd2b4e824dc41f24 yt/data_objects/tests/test_profiles.py
--- a/yt/data_objects/tests/test_profiles.py
+++ b/yt/data_objects/tests/test_profiles.py
@@ -127,14 +127,14 @@
         p2d = create_profile(dd, ('gas', 'density'), ('gas', 'temperature'),
                              weight_field=('gas', 'cell_mass'),
                              extrema={'density': (None, rma*e2)})
-        assert_equal(p2d.x_bins[0], rmi)
+        assert_equal(p2d.x_bins[0], rmi - np.spacing(rmi))
         assert_equal(p2d.x_bins[-1], rma*e2)
 
         p2d = create_profile(dd, ('gas', 'density'), ('gas', 'temperature'),
                              weight_field=('gas', 'cell_mass'),
                              extrema={'density': (rmi*e2, None)})
         assert_equal(p2d.x_bins[0], rmi*e2)
-        assert_equal(p2d.x_bins[-1], rma)
+        assert_equal(p2d.x_bins[-1], rma + np.spacing(rma))
 
 
 extrema_s = {'particle_position_x': (0, 1)}

diff -r ba12c9f95a75d016f841f10bd290b8e316685416 -r 23eb3be665c0c18ca50432a8cd2b4e824dc41f24 yt/visualization/tests/test_particle_plot.py
--- a/yt/visualization/tests/test_particle_plot.py
+++ b/yt/visualization/tests/test_particle_plot.py
@@ -233,10 +233,10 @@
     p = plot.profile
 
     # bin extrema are field extrema
-    assert dens_ex[0] == p.x_bins[0]
-    assert dens_ex[-1] == p.x_bins[-1]
-    assert temp_ex[0] == p.y_bins[0]
-    assert temp_ex[-1] == p.y_bins[-1]
+    assert dens_ex[0] - np.spacing(dens_ex[0]) == p.x_bins[0]
+    assert dens_ex[-1] + np.spacing(dens_ex[-1]) == p.x_bins[-1]
+    assert temp_ex[0] - np.spacing(temp_ex[0]) == p.y_bins[0]
+    assert temp_ex[-1] + np.spacing(temp_ex[-1]) == p.y_bins[-1]
 
     # bins are evenly spaced in log space
     logxbins = np.log10(p.x_bins)
@@ -252,10 +252,10 @@
     p = plot.profile
 
     # bin extrema are field extrema
-    assert dens_ex[0] == p.x_bins[0]
-    assert dens_ex[-1] == p.x_bins[-1]
-    assert temp_ex[0] == p.y_bins[0]
-    assert temp_ex[-1] == p.y_bins[-1]
+    assert dens_ex[0] - np.spacing(dens_ex[0]) == p.x_bins[0]
+    assert dens_ex[-1] + np.spacing(dens_ex[-1]) == p.x_bins[-1]
+    assert temp_ex[0] - np.spacing(temp_ex[0]) == p.y_bins[0]
+    assert temp_ex[-1] + np.spacing(temp_ex[-1]) == p.y_bins[-1]
 
     # bins are evenly spaced in log space
     dxbins = p.x_bins[1:] - p.x_bins[:-1]


https://bitbucket.org/yt_analysis/yt/commits/478a7b2a55c5/
Changeset:   478a7b2a55c5
User:        ngoldbaum
Date:        2017-07-27 22:32:22+00:00
Summary:     Update plotwindow answers
Affected #:  1 file

diff -r 23eb3be665c0c18ca50432a8cd2b4e824dc41f24 -r 478a7b2a55c55036915a20caeac5761d28537e27 tests/tests.yaml
--- a/tests/tests.yaml
+++ b/tests/tests.yaml
@@ -45,7 +45,7 @@
   local_owls_001:
     - yt/frontends/owls/tests/test_outputs.py
 
-  local_pw_017:
+  local_pw_018:
     - yt/visualization/tests/test_plotwindow.py:test_attributes
     - yt/visualization/tests/test_plotwindow.py:test_attributes_wt
     - yt/visualization/tests/test_profile_plots.py:test_phase_plot_attributes


https://bitbucket.org/yt_analysis/yt/commits/6969c7e705ac/
Changeset:   6969c7e705ac
User:        ngoldbaum
Date:        2017-07-28 21:12:07+00:00
Summary:     Add missing docstring for `window_size` keyword argument
Affected #:  1 file

diff -r 478a7b2a55c55036915a20caeac5761d28537e27 -r 6969c7e705acdd12d2f3d4ed58faf8b37fe4b755 yt/visualization/plot_window.py
--- a/yt/visualization/plot_window.py
+++ b/yt/visualization/plot_window.py
@@ -163,6 +163,9 @@
     window_size : float
         The size of the window on the longest axis (in units of inches),
         including the margins but not the colorbar.
+    window_size : float
+        The size of the window on the longest axis (in units of inches),
+        including the margins but not the colorbar.
     right_handed : boolean
         Whether the implicit east vector for the image generated is set to make a right
         handed coordinate system with a north vector and the normal vector, the
@@ -2060,4 +2063,4 @@
                                 fontsize=fontsize,
                                 field_parameters=field_parameters, 
                                 window_size=window_size, aspect=aspect,
-                                data_source=data_source)
\ No newline at end of file
+                                data_source=data_source)


https://bitbucket.org/yt_analysis/yt/commits/d093f9fcaec7/
Changeset:   d093f9fcaec7
User:        ngoldbaum
Date:        2017-07-28 21:12:41+00:00
Summary:     Add documentation for window_size and aspect arguments to ParticlePhasePlot
Affected #:  1 file

diff -r 6969c7e705acdd12d2f3d4ed58faf8b37fe4b755 -r d093f9fcaec7a0a3d28e73d0c8dcba06423a99f1 yt/visualization/particle_plots.py
--- a/yt/visualization/particle_plots.py
+++ b/yt/visualization/particle_plots.py
@@ -175,6 +175,11 @@
     field_parameters : dictionary
          A dictionary of field parameters than can be accessed by derived
          fields.
+    window_size : float
+        The size of the window on the longest axis (in units of inches),
+        including the margins but not the colorbar.
+    aspect : float
+         The aspect ratio of the plot.  Set to None for 1.
     data_source : YTSelectionContainer Object
          Object to be used for data selection.  Defaults to a region covering
          the entire simulation.


https://bitbucket.org/yt_analysis/yt/commits/b4a2d5770a0f/
Changeset:   b4a2d5770a0f
User:        ngoldbaum
Date:        2017-07-28 21:13:14+00:00
Summary:     Add docstrings for arguments accepted by delegated classes to ParticlePlot.

Fixes #1288.
Affected #:  1 file

diff -r d093f9fcaec7a0a3d28e73d0c8dcba06423a99f1 -r b4a2d5770a0f08a9722df4917bf9a8e89158e283 yt/visualization/particle_plots.py
--- a/yt/visualization/particle_plots.py
+++ b/yt/visualization/particle_plots.py
@@ -347,8 +347,8 @@
                                         figure_size)
 
 
-def ParticlePlot(ds, x_field, y_field, z_fields=None, color='b', *args, **
-                 kwargs):
+def ParticlePlot(ds, x_field, y_field, z_fields=None, color='b', *args,
+                 **kwargs):
     r"""
     A factory function for
     :class:`yt.visualization.particle_plots.ParticleProjectionPlot`
@@ -357,13 +357,15 @@
     plots, the distinction being determined by the fields passed in.
 
     If the x_field and y_field combination corresponds to a valid, right-handed
-    spatial plot, an 'ParticleProjectionPlot` will be returned. This plot
+    spatial plot, an ``ParticleProjectionPlot`` will be returned. This plot
     object can be updated using one of the many helper functions defined in
-    PlotWindow.
+    ``PlotWindow``.
 
     If the x_field and y_field combo do not correspond to a valid
-    'ParticleProjectionPlot`, then a `ParticlePhasePlot`. This object can be
-    modified by its own set of  helper functions defined in PhasePlot.
+    ``ParticleProjectionPlot``, then a ``ParticlePhasePlot``. This object can be
+    modified by its own set of  helper functions defined in PhasePlot. We note
+    below which arguments are only accepted by ``ParticleProjectionPlot`` and
+    which arguments are only accepted by ``ParticlePhasePlot``.
 
     Parameters
     ----------
@@ -386,6 +388,103 @@
          The color that will indicate the particle locations
          on the plot. This argument is ignored if z_fields is
          not None. Default is 'b'.
+    weight_field : string
+         The name of the weighting field.  Set to None for no weight.
+    fontsize : integer
+         The size of the fonts for the axis, colorbar, and tick labels.
+    data_source : YTSelectionContainer Object
+         Object to be used for data selection.  Defaults to a region covering
+         the entire simulation.
+    center : A sequence of floats, a string, or a tuple.
+         The coordinate of the center of the image. If set to 'c', 'center' or
+         left blank, the plot is centered on the middle of the domain. If set to
+         'max' or 'm', the center will be located at the maximum of the
+         ('gas', 'density') field. Centering on the max or min of a specific
+         field is supported by providing a tuple such as ("min","temperature") or
+         ("max","dark_matter_density"). Units can be specified by passing in *center*
+         as a tuple containing a coordinate and string unit name or by passing
+         in a YTArray. If a list or unitless array is supplied, code units are
+         assumed. This argument is only accepted by ``ParticleProjectionPlot``.
+    width : tuple or a float.
+         Width can have four different formats to support windows with variable
+         x and y widths.  They are:
+
+         ==================================     =======================
+         format                                 example
+         ==================================     =======================
+         (float, string)                        (10,'kpc')
+         ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))
+         float                                  0.2
+         (float, float)                         (0.2, 0.3)
+         ==================================     =======================
+
+         For example, (10, 'kpc') requests a plot window that is 10 kiloparsecs
+         wide in the x and y directions, ((10,'kpc'),(15,'kpc')) requests a
+         window that is 10 kiloparsecs wide along the x axis and 15
+         kiloparsecs wide along the y axis.  In the other two examples, code
+         units are assumed, for example (0.2, 0.3) requests a plot that has an
+         x width of 0.2 and a y width of 0.3 in code units.  If units are
+         provided the resulting plot axis labels will use the supplied units.
+         This argument is only accepted by ``ParticleProjectionPlot``.
+    depth : A tuple or a float
+         A tuple containing the depth to project through and the string
+         key of the unit: (width, 'unit').  If set to a float, code units
+         are assumed. Defaults to the entire domain. This argument is only 
+         accepted by ``ParticleProjectionPlot``.
+    axes_unit : A string
+         The name of the unit for the tick labels on the x and y axes.
+         Defaults to None, which automatically picks an appropriate unit.
+         If axes_unit is '1', 'u', or 'unitary', it will not display the
+         units, and only show the axes name.
+    origin : string or length 1, 2, or 3 sequence of strings
+         The location of the origin of the plot coordinate system.  This is
+         represented by '-' separated string or a tuple of strings.  In the
+         first index the y-location is given by 'lower', 'upper', or 'center'.
+         The second index is the x-location, given as 'left', 'right', or
+         'center'.  Finally, the whether the origin is applied in 'domain'
+         space, plot 'window' space or 'native' simulation coordinate system
+         is given. For example, both 'upper-right-domain' and ['upper',
+         'right', 'domain'] both place the origin in the upper right hand
+         corner of domain space. If x or y are not given, a value is inffered.
+         For instance, 'left-domain' corresponds to the lower-left hand corner
+         of the simulation domain, 'center-domain' corresponds to the center
+         of the simulation domain, or 'center-window' for the center of the
+         plot window. Further examples:
+
+         ==================================     ============================
+         format                                 example
+         ==================================     ============================
+         '{space}'                              'domain'
+         '{xloc}-{space}'                       'left-window'
+         '{yloc}-{space}'                       'upper-domain'
+         '{yloc}-{xloc}-{space}'                'lower-right-window'
+         ('{space}',)                           ('window',)
+         ('{xloc}', '{space}')                  ('right', 'domain')
+         ('{yloc}', '{space}')                  ('lower', 'window')
+         ('{yloc}', '{xloc}', '{space}')        ('lower', 'right', 'window')
+         ==================================     ============================
+
+         This argument is only accepted by ``ParticleProjectionPlot``.
+    window_size : float
+         The size of the window on the longest axis (in units of inches),
+         including the margins but not the colorbar. This argument is only 
+         accepted by ``ParticleProjectionPlot``.
+    aspect : float
+         The aspect ratio of the plot.  Set to None for 1. This argument is 
+         only accepted by ``ParticleProjectionPlot``.
+    x_bins : int
+        The number of bins in x field for the mesh. Defaults to 800. This
+        argument is only accepted by ``ParticlePhasePlot``.
+    y_bins : int
+        The number of bins in y field for the mesh. Defaults to 800. This
+        argument is only accepted by ``ParticlePhasePlot``.
+    deposition : str
+        Either 'ngp' or 'cic'. Controls what type of interpolation will be 
+        used to deposit the particle z_fields onto the mesh. Defaults to 'ngp'.
+        This argument is only accepted by ``ParticlePhasePlot``.
+    figure_size : int
+        Size in inches of the image. Defaults to 8 (product an 8x8 inch figure).
+        This argument is only accepted by ``ParticlePhasePlot``.
 
     Examples
     --------


https://bitbucket.org/yt_analysis/yt/commits/72daa15d5dc2/
Changeset:   72daa15d5dc2
User:        ngoldbaum
Date:        2017-07-28 21:14:07+00:00
Summary:     make it possible to pass a custom data source to ParticlePhasePlots produced by ParticlePlot
Affected #:  1 file

diff -r b4a2d5770a0f08a9722df4917bf9a8e89158e283 -r 72daa15d5dc2fec82ef191651988845f4d437388 yt/visualization/particle_plots.py
--- a/yt/visualization/particle_plots.py
+++ b/yt/visualization/particle_plots.py
@@ -498,10 +498,11 @@
     ...                     color='g')
 
     """
-
-    ad = ds.all_data()
-    x_field = ad._determine_fields(x_field)[0]
-    y_field = ad._determine_fields(y_field)[0]
+    dd = kwargs.get('data_source', None)
+    if dd is None:
+        dd = ds.all_data()
+    x_field = dd._determine_fields(x_field)[0]
+    y_field = dd._determine_fields(y_field)[0]
 
     direction = 3
     # try potential axes for a ParticleProjectionPlot:
@@ -523,5 +524,5 @@
     # Does not correspond to any valid PlotWindow-style plot,
     # use ParticlePhasePlot instead
     else:
-        return ParticlePhasePlot(ad, x_field, y_field,
+        return ParticlePhasePlot(dd, x_field, y_field,
                                  z_fields, color, *args, **kwargs)


https://bitbucket.org/yt_analysis/yt/commits/2210f0c62b1c/
Changeset:   2210f0c62b1c
User:        ngoldbaum
Date:        2017-08-03 16:11:18+00:00
Summary:     issue message about ngp fallback at warning level
Affected #:  1 file

diff -r 72daa15d5dc2fec82ef191651988845f4d437388 -r 2210f0c62b1c22856ead6655458ea48dc973c99e yt/data_objects/profiles.py
--- a/yt/data_objects/profiles.py
+++ b/yt/data_objects/profiles.py
@@ -674,8 +674,8 @@
         if deposition not in ['ngp', 'cic']:
             raise NotImplementedError(deposition)
         elif (x_log or y_log) and deposition != 'ngp':
-            mylog.info('cic deposition is only supported for linear axis '
-                       'scales, falling back to ngp deposition')
+            mylog.warning('cic deposition is only supported for linear axis '
+                          'scales, falling back to ngp deposition')
             deposition = 'ngp'
 
         self.deposition = deposition


https://bitbucket.org/yt_analysis/yt/commits/42c37372d0c1/
Changeset:   42c37372d0c1
User:        ngoldbaum
Date:        2017-08-03 21:16:27+00:00
Summary:     fix off-by-one error in CICDeposit_2
Affected #:  1 file

diff -r 2210f0c62b1c22856ead6655458ea48dc973c99e -r 42c37372d0c102962bf34c74836d0471fec81994 yt/utilities/lib/particle_mesh_operations.pyx
--- a/yt/utilities/lib/particle_mesh_operations.pyx
+++ b/yt/utilities/lib/particle_mesh_operations.pyx
@@ -97,8 +97,8 @@
     cdef np.float64_t edgex, edgey
     cdef np.float64_t dx, dy, ddx, ddy, ddx2, ddy2
 
-    edgex = (<np.float64_t> x_bin_edges.shape[0]) + 0.5001
-    edgey = (<np.float64_t> y_bin_edges.shape[0]) + 0.5001
+    edgex = (<np.float64_t> x_bin_edges.shape[0] - 1) + 0.5001
+    edgey = (<np.float64_t> y_bin_edges.shape[0] - 1) + 0.5001
 
     # We are always dealing with uniformly spaced bins for CiC
     dx = x_bin_edges[1] - x_bin_edges[0]


https://bitbucket.org/yt_analysis/yt/commits/399cd9128a70/
Changeset:   399cd9128a70
User:        ngoldbaum
Date:        2017-08-07 15:11:28+00:00
Summary:     Merge pull request #1510 from ngoldbaum/phase-plot-fixes

Phase plot and ParticlePlot fixes and improvements
Affected #:  10 files

diff -r bfd5887812b4b0a8ec76b548e2f4021ba0b3c0eb -r 399cd9128a7011779f161a11b03529289c9a2f51 tests/tests.yaml
--- a/tests/tests.yaml
+++ b/tests/tests.yaml
@@ -45,7 +45,7 @@
   local_owls_001:
     - yt/frontends/owls/tests/test_outputs.py
 
-  local_pw_017:
+  local_pw_018:
     - yt/visualization/tests/test_plotwindow.py:test_attributes
     - yt/visualization/tests/test_plotwindow.py:test_attributes_wt
     - yt/visualization/tests/test_profile_plots.py:test_phase_plot_attributes

diff -r bfd5887812b4b0a8ec76b548e2f4021ba0b3c0eb -r 399cd9128a7011779f161a11b03529289c9a2f51 yt/data_objects/profiles.py
--- a/yt/data_objects/profiles.py
+++ b/yt/data_objects/profiles.py
@@ -22,7 +22,8 @@
     get_output_filename, \
     ensure_list, \
     iterable, \
-    issue_deprecation_warning
+    issue_deprecation_warning, \
+    mylog
 from yt.units.yt_array import \
     array_like_field, \
     YTQuantity
@@ -256,7 +257,7 @@
             units = chunk.ds.field_info[self.weight_field].output_units
             weight_data = chunk[self.weight_field].in_units(units)
         else:
-            weight_data = np.ones(filter.size, dtype="float64")
+            weight_data = np.ones(filter.shape, dtype="float64")
         weight_data = weight_data[filter]
         # So that we can pass these into
         return arr, weight_data, bin_fields
@@ -346,7 +347,8 @@
         extra_attrs = {"data_type": "yt_profile",
                        "profile_dimensions": self.size,
                        "weight_field": self.weight_field,
-                       "fractional": self.fractional}
+                       "fractional": self.fractional,
+                       "accumulation": self.accumulation}
         data = {}
         data.update(self.field_data)
         data["weight"] = self.weight
@@ -385,6 +387,7 @@
     def __init__(self, ds):
         ProfileND.__init__(self, ds.data, ds.parameters["weight_field"])
         self.fractional = ds.parameters["fractional"]
+        self.accumulation = ds.parameters["accumulation"]
         exclude_fields = ["used", "weight"]
         for ax in "xyz"[:ds.dimensionality]:
             setattr(self, ax, ds.data[ax])
@@ -661,33 +664,31 @@
     fractional = False
 
     def __init__(self, data_source,
-                 x_field, x_n, x_min, x_max,
-                 y_field, y_n, y_min, y_max,
+                 x_field, x_n, x_min, x_max, x_log,
+                 y_field, y_n, y_min, y_max, y_log,
                  weight_field=None, deposition="ngp"):
 
         x_field = data_source._determine_fields(x_field)[0]
         y_field = data_source._determine_fields(y_field)[0]
 
+        if deposition not in ['ngp', 'cic']:
+            raise NotImplementedError(deposition)
+        elif (x_log or y_log) and deposition != 'ngp':
+            mylog.warning('cic deposition is only supported for linear axis '
+                          'scales, falling back to ngp deposition')
+            deposition = 'ngp'
+
+        self.deposition = deposition
+
         # set the log parameters to False (since that doesn't make much sense
         # for deposited data) and also turn off the weight field.
         super(ParticleProfile, self).__init__(data_source,
                                               x_field,
-                                              x_n, x_min, x_max, False,
+                                              x_n, x_min, x_max, x_log,
                                               y_field,
-                                              y_n, y_min, y_max, False,
+                                              y_n, y_min, y_max, y_log,
                                               weight_field=weight_field)
 
-        self.LeftEdge = [self.x_bins[0], self.y_bins[0]]
-        self.dx = (self.x_bins[-1] - self.x_bins[0]) / x_n
-        self.dy = (self.y_bins[-1] - self.y_bins[0]) / y_n
-        self.CellSize = [self.dx, self.dy]
-        self.CellVolume = np.product(self.CellSize)
-        self.GridDimensions = np.array([x_n, y_n], dtype=np.int32)
-        self.known_styles = ["ngp", "cic"]
-        if deposition not in self.known_styles:
-            raise NotImplementedError(deposition)
-        self.deposition = deposition
-
     # Either stick the particle field in the nearest bin,
     # or spread it out using the 2D CIC deposition function
     def _bin_chunk(self, chunk, fields, storage):
@@ -696,42 +697,31 @@
         fdata, wdata, (bf_x, bf_y) = rv
         # make sure everything has the same units before deposition.
         # the units will be scaled to the correct values later.
-        LE = np.array([self.LeftEdge[0].in_units(bf_x.units),
-                       self.LeftEdge[1].in_units(bf_y.units)])
-        cell_size = np.array([self.CellSize[0].in_units(bf_x.units),
-                              self.CellSize[1].in_units(bf_y.units)])
+
+        if self.deposition == "ngp":
+            func = NGPDeposit_2
+        elif self.deposition == "cic":
+            func = CICDeposit_2
+
         for fi, field in enumerate(fields):
-            Np = fdata[:, fi].size
-
-            if self.deposition == "ngp":
-                func = NGPDeposit_2
-            elif self.deposition == 'cic':
-                func = CICDeposit_2
-
             if self.weight_field is None:
                 deposit_vals = fdata[:, fi]
             else:
                 deposit_vals = wdata*fdata[:, fi]
 
-            func(bf_x, bf_y, deposit_vals, Np,
-                 storage.values[:, :, fi],
-                 LE,
-                 self.GridDimensions,
-                 cell_size)
+            func(bf_x, bf_y, deposit_vals, fdata[:, fi].size,
+                 storage.values[:, :, fi], self.x_bins, self.y_bins)
 
             locs = storage.values[:, :, fi] != 0.0
             storage.used[locs] = True
 
             if self.weight_field is not None:
-                func(bf_x, bf_y, wdata, Np,
-                     storage.weight_values,
-                     LE,
-                     self.GridDimensions,
-                     cell_size)
+                func(bf_x, bf_y, wdata, fdata[:, fi].size,
+                     storage.weight_values, self.x_bins, self.y_bins)
             else:
                 storage.weight_values[locs] = 1.0
-            storage.mvalues[locs, fi] = storage.values[locs, fi] \
-                                        / storage.weight_values[locs]
+            storage.mvalues[locs, fi] = \
+                storage.values[locs, fi] / storage.weight_values[locs]
         # We've binned it!
 
 
@@ -950,10 +940,12 @@
         to n.  If the profile is 2D or 3D, a list of values can be given to
         control the summation in each dimension independently.
         Default: False.
-    fractional : If True the profile values are divided by the sum of all
+    fractional : bool
+        If True the profile values are divided by the sum of all
         the profile data such that the profile represents a probability
         distribution function.
-    deposition : Controls the type of deposition used for ParticlePhasePlots.
+    deposition : strings
+        Controls the type of deposition used for ParticlePhasePlots.
         Valid choices are 'ngp' and 'cic'. Default is 'ngp'. This parameter is
         ignored the if the input fields are not of particle type.
 
@@ -983,18 +975,36 @@
         is_pfield.append(wf.particle_type)
         wf = wf.name
 
+    if len(bin_fields) > 1 and isinstance(accumulation, bool):
+        accumulation = [accumulation for _ in range(len(bin_fields))]
+
+    bin_fields = data_source._determine_fields(bin_fields)
+    fields = data_source._determine_fields(fields)
+    units = sanitize_field_tuple_keys(units, data_source)
+    extrema = sanitize_field_tuple_keys(extrema, data_source)
+    logs = sanitize_field_tuple_keys(logs, data_source)
+
     if any(is_pfield) and not all(is_pfield):
         raise YTIllDefinedProfile(
             bin_fields, data_source._determine_fields(fields), wf, is_pfield)
     elif len(bin_fields) == 1:
         cls = Profile1D
     elif len(bin_fields) == 2 and all(is_pfield):
-        # log bin_fields set to False for Particle Profiles.
-        # doesn't make much sense for CIC deposition.
-        # accumulation and fractional set to False as well.
-        logs = {bin_fields[0]: False, bin_fields[1]: False}
-        accumulation = False
-        fractional = False
+        if deposition == 'cic':
+            if logs is not None:
+                if ((bin_fields[0] in logs and logs[bin_fields[0]]) or
+                    (bin_fields[1] in logs and logs[bin_fields[1]])):
+                    raise RuntimeError(
+                        "CIC deposition is only implemented for linear-scaled "
+                        "axes")
+            else:
+                logs = {bin_fields[0]: False, bin_fields[1]: False}
+            if any(accumulation) or fractional:
+                raise RuntimeError(
+                    'The accumulation and fractional keyword arguments must be '
+                    'False for CIC deposition')
+        elif logs is None:
+            logs = {bin_fields[0]: False, bin_fields[1]: False}
         cls = ParticleProfile
     elif len(bin_fields) == 2:
         cls = Profile2D
@@ -1002,11 +1012,6 @@
         cls = Profile3D
     else:
         raise NotImplementedError
-    bin_fields = data_source._determine_fields(bin_fields)
-    fields = data_source._determine_fields(fields)
-    units = sanitize_field_tuple_keys(units, data_source)
-    extrema = sanitize_field_tuple_keys(extrema, data_source)
-    logs = sanitize_field_tuple_keys(logs, data_source)
     if weight_field is not None and cls == ParticleProfile:
         weight_field, = data_source._determine_fields([weight_field])
         if not data_source.ds._get_field_info(weight_field).particle_type:
@@ -1040,12 +1045,36 @@
                 field_ex = list(extrema[bin_field[-1]])
             except KeyError:
                 field_ex = list(extrema[bin_field])
+            if isinstance(field_ex[0], tuple):
+                field_ex = [data_source.ds.quan(*f) for f in field_ex]
+            if any([exi is None for exi in field_ex]):
+                try:
+                    ds_extrema = data_source.quantities.extrema(bin_field)
+                except AttributeError:
+                    # ytdata profile datasets don't have data_source.quantities
+                    bf_vals = data_source[bin_field]
+                    ds_extrema = data_source.ds.arr(
+                        [bf_vals.min(), bf_vals.max()])
+                for i, exi in enumerate(field_ex):
+                    if exi is None:
+                        field_ex[i] = ds_extrema[i]
+                        # pad extrema by epsilon so cells at bin edges are
+                        # not excluded
+                        field_ex[i] -= (-1)**i*np.spacing(field_ex[i])
             if units is not None and bin_field in units:
-                if isinstance(field_ex[0], tuple):
-                    field_ex = [data_source.ds.quan(*f) for f in field_ex]
-                fe = data_source.ds.arr(field_ex, units[bin_field])
-                fe.convert_to_units(bf_units)
-                field_ex = [fe[0].v, fe[1].v]
+                for i, exi in enumerate(field_ex):
+                    if hasattr(exi, 'units'):
+                        field_ex[i] = exi.to(units[bin_field])
+                    else:
+                        field_ex[i] = data_source.ds.quan(exi, units[bin_field])
+                fe = data_source.ds.arr(field_ex)
+            else:
+                if hasattr(field_ex, 'units'):
+                    fe = field_ex.to(bf_units)
+                else:
+                    fe = data_source.ds.arr(field_ex, bf_units)
+            fe.convert_to_units(bf_units)
+            field_ex = [fe[0].v, fe[1].v]
             if iterable(field_ex[0]):
                 field_ex[0] = data_source.ds.quan(field_ex[0][0], field_ex[0][1])
                 field_ex[0] = field_ex[0].in_units(bf_units)
@@ -1053,18 +1082,15 @@
                 field_ex[1] = data_source.ds.quan(field_ex[1][0], field_ex[1][1])
                 field_ex[1] = field_ex[1].in_units(bf_units)
             ex.append(field_ex)
+    args = [data_source]
+    for f, n, (mi, ma), l in zip(bin_fields, n_bins, ex, logs):
+        args += [f, n, mi, ma, l]
+    kwargs = dict(weight_field=weight_field)
     if cls is ParticleProfile:
-        args = [data_source]
-        for f, n, (mi, ma) in zip(bin_fields, n_bins, ex):
-            args += [f, n, mi, ma]
-        obj = cls(*args, weight_field=weight_field, deposition=deposition)
-    else:
-        args = [data_source]
-        for f, n, (mi, ma), l in zip(bin_fields, n_bins, ex, logs):
-            args += [f, n, mi, ma, l]
-        obj = cls(*args, weight_field = weight_field)
-        setattr(obj, "accumulation", accumulation)
-        setattr(obj, "fractional", fractional)
+        kwargs['deposition'] = deposition
+    obj = cls(*args, **kwargs)
+    setattr(obj, "accumulation", accumulation)
+    setattr(obj, "fractional", fractional)
     if fields is not None:
         obj.add_fields([field for field in fields])
     for field in fields:

diff -r bfd5887812b4b0a8ec76b548e2f4021ba0b3c0eb -r 399cd9128a7011779f161a11b03529289c9a2f51 yt/data_objects/tests/test_profiles.py
--- a/yt/data_objects/tests/test_profiles.py
+++ b/yt/data_objects/tests/test_profiles.py
@@ -124,6 +124,19 @@
         p3d.add_fields(["ones"])
         assert_equal(p3d["ones"], np.ones((nb,nb,nb)))
 
+        p2d = create_profile(dd, ('gas', 'density'), ('gas', 'temperature'),
+                             weight_field=('gas', 'cell_mass'),
+                             extrema={'density': (None, rma*e2)})
+        assert_equal(p2d.x_bins[0], rmi - np.spacing(rmi))
+        assert_equal(p2d.x_bins[-1], rma*e2)
+
+        p2d = create_profile(dd, ('gas', 'density'), ('gas', 'temperature'),
+                             weight_field=('gas', 'cell_mass'),
+                             extrema={'density': (rmi*e2, None)})
+        assert_equal(p2d.x_bins[0], rmi*e2)
+        assert_equal(p2d.x_bins[-1], rma + np.spacing(rma))
+
+
 extrema_s = {'particle_position_x': (0, 1)}
 logs_s = {'particle_position_x': False}
 
@@ -218,5 +231,42 @@
         ad,
         ["particle_position_x", "particle_position_y"],
         "particle_velocity_x",
+        logs = {'particle_position_x': True,
+                'particle_position_y': True,
+                'particle_position_z': True},
+        weight_field=None)
+    assert profile['particle_velocity_x'].min() < 0
+    assert profile.x_bins.min() > 0
+    assert profile.y_bins.min() > 0
+
+    profile = yt.create_profile(
+        ad,
+        ["particle_position_x", "particle_position_y"],
+        "particle_velocity_x",
         weight_field=None)
     assert profile['particle_velocity_x'].min() < 0
+    assert profile.x_bins.min() < 0
+    assert profile.y_bins.min() < 0
+
+    # can't use CIC deposition with log-scaled bin fields
+    with assert_raises(RuntimeError):
+        yt.create_profile(
+            ad,
+            ["particle_position_x", "particle_position_y"],
+            "particle_velocity_x",
+            logs = {'particle_position_x': True,
+                    'particle_position_y': False,
+                    'particle_position_z': False},
+            weight_field=None, deposition='cic')
+
+    # can't use CIC deposition with accumulation or fractional
+    with assert_raises(RuntimeError):
+        yt.create_profile(
+            ad,
+            ["particle_position_x", "particle_position_y"],
+            "particle_velocity_x",
+            logs = {'particle_position_x': False,
+                    'particle_position_y': False,
+                    'particle_position_z': False},
+            weight_field=None, deposition='cic',
+            accumulation=True, fractional=True)

diff -r bfd5887812b4b0a8ec76b548e2f4021ba0b3c0eb -r 399cd9128a7011779f161a11b03529289c9a2f51 yt/utilities/lib/particle_mesh_operations.pyx
--- a/yt/utilities/lib/particle_mesh_operations.pyx
+++ b/yt/utilities/lib/particle_mesh_operations.pyx
@@ -83,88 +83,97 @@
 
 @cython.boundscheck(False)
 @cython.wraparound(False)
-def CICDeposit_2(np.ndarray[np.float64_t, ndim=1] posx,
-                 np.ndarray[np.float64_t, ndim=1] posy,
-                 np.ndarray[np.float64_t, ndim=1] mass,
+ at cython.cdivision(True)
+def CICDeposit_2(np.float64_t[:] posx,
+                 np.float64_t[:] posy,
+                 np.float64_t[:] mass,
                  np.int64_t npositions,
-                 np.ndarray[np.float64_t, ndim=2] field,
-                 np.ndarray[np.float64_t, ndim=1] leftEdge,
-                 np.ndarray[np.int32_t, ndim=1] gridDimension,
-                 np.ndarray[np.float64_t, ndim=1] cellSize):
+                 np.float64_t[:, :] field,
+                 np.float64_t[:] x_bin_edges,
+                 np.float64_t[:] y_bin_edges):
 
     cdef int i1, j1, n
     cdef np.float64_t xpos, ypos
-    cdef np.float64_t edge0, edge1
-    cdef np.float64_t le0, le1
-    cdef np.float64_t dx, dy, dx2, dy2
+    cdef np.float64_t edgex, edgey
+    cdef np.float64_t dx, dy, ddx, ddy, ddx2, ddy2
 
-    edge0 = (<np.float64_t> gridDimension[0]) - 0.5001
-    edge1 = (<np.float64_t> gridDimension[1]) - 0.5001
+    edgex = (<np.float64_t> x_bin_edges.shape[0] - 1) + 0.5001
+    edgey = (<np.float64_t> y_bin_edges.shape[0] - 1) + 0.5001
 
-    le0 = leftEdge[0]
-    le1 = leftEdge[1]
+    # We are always dealing with uniformly spaced bins for CiC
+    dx = x_bin_edges[1] - x_bin_edges[0]
+    dy = y_bin_edges[1] - y_bin_edges[0]
 
     for n in range(npositions):
 
         # Compute the position of the central cell
-        xpos = (posx[n] - le0)/cellSize[0]
-        ypos = (posy[n] - le1)/cellSize[1]
+        xpos = (posx[n] - x_bin_edges[0])/dx
+        ypos = (posy[n] - y_bin_edges[0])/dy
 
-        if (xpos < 0.5001) or (xpos > edge0):
+        if (xpos < -0.5001) or (xpos > edgex):
             continue
-        if (ypos < 0.5001) or (ypos > edge1):
+        if (ypos < -0.5001) or (ypos > edgey):
             continue
 
-        i1  = <int> (xpos + 0.5)
-        j1  = <int> (ypos + 0.5)
+        i1  = <int> (xpos)
+        j1  = <int> (ypos)
 
         # Compute the weights
-        dx = (<np.float64_t> i1) + 0.5 - xpos
-        dy = (<np.float64_t> j1) + 0.5 - ypos
-        dx2 =  1.0 - dx
-        dy2 =  1.0 - dy
+        ddx = (<np.float64_t> i1) + 0.5 - xpos
+        ddy = (<np.float64_t> j1) + 0.5 - ypos
+        ddx2 =  1.0 - ddx
+        ddy2 =  1.0 - ddy
 
         # Deposit onto field
-        field[i1-1,j1-1] += mass[n] * dx  * dy
-        field[i1  ,j1-1] += mass[n] * dx2 * dy
-        field[i1-1,j1  ] += mass[n] * dx  * dy2
-        field[i1  ,j1  ] += mass[n] * dx2 * dy2
+        if i1 > 0 and j1 > 0:
+            field[i1-1,j1-1] += mass[n] * ddx  * ddy
+        if j1 > 0 and i1 < field.shape[0]:
+            field[i1  ,j1-1] += mass[n] * ddx2 * ddy
+        if i1 > 0 and j1 < field.shape[1]:
+            field[i1-1,j1  ] += mass[n] * ddx  * ddy2
+        if i1 < field.shape[0] and j1 < field.shape[1]:
+            field[i1  ,j1  ] += mass[n] * ddx2 * ddy2
 
 @cython.boundscheck(False)
 @cython.wraparound(False)
-def NGPDeposit_2(np.ndarray[np.float64_t, ndim=1] posx,
-                 np.ndarray[np.float64_t, ndim=1] posy,
-                 np.ndarray[np.float64_t, ndim=1] mass,
+ at cython.cdivision(True)
+def NGPDeposit_2(np.float64_t[:] posx,
+                 np.float64_t[:] posy,
+                 np.float64_t[:] mass,
                  np.int64_t npositions,
-                 np.ndarray[np.float64_t, ndim=2] field,
-                 np.ndarray[np.float64_t, ndim=1] leftEdge,
-                 np.ndarray[np.int32_t, ndim=1] gridDimension,
-                 np.ndarray[np.float64_t, ndim=1] cellSize):
+                 np.float64_t[:, :] field,
+                 np.float64_t[:] x_bin_edges,
+                 np.float64_t[:] y_bin_edges):
 
-    cdef int i1, j1, n
+    cdef int i, j, i1, j1, n
     cdef np.float64_t xpos, ypos
     cdef np.float64_t edge0, edge1
     cdef np.float64_t le0, le1
+    cdef np.float64_t[2] x_endpoints
+    cdef np.float64_t[2] y_endpoints
 
-    edge0 = (<np.float64_t> gridDimension[0]) - 0.5001
-    edge1 = (<np.float64_t> gridDimension[1]) - 0.5001
-
-    le0 = leftEdge[0]
-    le1 = leftEdge[1]
+    x_endpoints = (x_bin_edges[0], x_bin_edges[x_bin_edges.shape[0] - 1])
+    y_endpoints = (y_bin_edges[0], y_bin_edges[y_bin_edges.shape[0] - 1])
 
     for n in range(npositions):
 
-        # Compute the position of the central cell
-        xpos = (posx[n] - le0)/cellSize[0]
-        ypos = (posy[n] - le1)/cellSize[1]
+        xpos = posx[n]
+        ypos = posy[n]
 
-        if (xpos < 0.5001) or (xpos > edge0):
+        if (xpos < x_endpoints[0]) or (xpos > x_endpoints[1]):
             continue
-        if (ypos < 0.5001) or (ypos > edge1):
+        if (ypos < y_endpoints[0]) or (ypos > y_endpoints[1]):
             continue
 
-        i1  = <int> (xpos + 0.5)
-        j1  = <int> (ypos + 0.5)
+        for i in range(x_bin_edges.shape[0]):
+            if (xpos >= x_bin_edges[i]) and (xpos < x_bin_edges[i+1]):
+                i1 = i
+                break
+
+        for j in range(y_bin_edges.shape[0]):
+            if (ypos >= y_bin_edges[j]) and (ypos < y_bin_edges[j+1]):
+                j1 = j
+                break
 
         # Deposit onto field
         field[i1,j1] += mass[n]

diff -r bfd5887812b4b0a8ec76b548e2f4021ba0b3c0eb -r 399cd9128a7011779f161a11b03529289c9a2f51 yt/visualization/particle_plots.py
--- a/yt/visualization/particle_plots.py
+++ b/yt/visualization/particle_plots.py
@@ -175,6 +175,11 @@
     field_parameters : dictionary
          A dictionary of field parameters than can be accessed by derived
          fields.
+    window_size : float
+        The size of the window on the longest axis (in units of inches),
+        including the margins but not the colorbar.
+    aspect : float
+         The aspect ratio of the plot.  Set to None for 1.
     data_source : YTSelectionContainer Object
          Object to be used for data selection.  Defaults to a region covering
          the entire simulation.
@@ -342,8 +347,8 @@
                                         figure_size)
 
 
-def ParticlePlot(ds, x_field, y_field, z_fields=None, color='b', *args, **
-                 kwargs):
+def ParticlePlot(ds, x_field, y_field, z_fields=None, color='b', *args,
+                 **kwargs):
     r"""
     A factory function for
     :class:`yt.visualization.particle_plots.ParticleProjectionPlot`
@@ -352,13 +357,15 @@
     plots, the distinction being determined by the fields passed in.
 
     If the x_field and y_field combination corresponds to a valid, right-handed
-    spatial plot, an 'ParticleProjectionPlot` will be returned. This plot
+    spatial plot, an ``ParticleProjectionPlot`` will be returned. This plot
     object can be updated using one of the many helper functions defined in
-    PlotWindow.
+    ``PlotWindow``.
 
     If the x_field and y_field combo do not correspond to a valid
-    'ParticleProjectionPlot`, then a `ParticlePhasePlot`. This object can be
-    modified by its own set of  helper functions defined in PhasePlot.
+    ``ParticleProjectionPlot``, then a ``ParticlePhasePlot``. This object can be
+    modified by its own set of  helper functions defined in PhasePlot. We note
+    below which arguments are only accepted by ``ParticleProjectionPlot`` and
+    which arguments are only accepted by ``ParticlePhasePlot``.
 
     Parameters
     ----------
@@ -381,6 +388,103 @@
          The color that will indicate the particle locations
          on the plot. This argument is ignored if z_fields is
          not None. Default is 'b'.
+    weight_field : string
+         The name of the weighting field.  Set to None for no weight.
+    fontsize : integer
+         The size of the fonts for the axis, colorbar, and tick labels.
+    data_source : YTSelectionContainer Object
+         Object to be used for data selection.  Defaults to a region covering
+         the entire simulation.
+    center : A sequence of floats, a string, or a tuple.
+         The coordinate of the center of the image. If set to 'c', 'center' or
+         left blank, the plot is centered on the middle of the domain. If set to
+         'max' or 'm', the center will be located at the maximum of the
+         ('gas', 'density') field. Centering on the max or min of a specific
+         field is supported by providing a tuple such as ("min","temperature") or
+         ("max","dark_matter_density"). Units can be specified by passing in *center*
+         as a tuple containing a coordinate and string unit name or by passing
+         in a YTArray. If a list or unitless array is supplied, code units are
+         assumed. This argument is only accepted by ``ParticleProjectionPlot``.
+    width : tuple or a float.
+         Width can have four different formats to support windows with variable
+         x and y widths.  They are:
+
+         ==================================     =======================
+         format                                 example
+         ==================================     =======================
+         (float, string)                        (10,'kpc')
+         ((float, string), (float, string))     ((10,'kpc'),(15,'kpc'))
+         float                                  0.2
+         (float, float)                         (0.2, 0.3)
+         ==================================     =======================
+
+         For example, (10, 'kpc') requests a plot window that is 10 kiloparsecs
+         wide in the x and y directions, ((10,'kpc'),(15,'kpc')) requests a
+         window that is 10 kiloparsecs wide along the x axis and 15
+         kiloparsecs wide along the y axis.  In the other two examples, code
+         units are assumed, for example (0.2, 0.3) requests a plot that has an
+         x width of 0.2 and a y width of 0.3 in code units.  If units are
+         provided the resulting plot axis labels will use the supplied units.
+         This argument is only accepted by ``ParticleProjectionPlot``.
+    depth : A tuple or a float
+         A tuple containing the depth to project through and the string
+         key of the unit: (width, 'unit').  If set to a float, code units
+         are assumed. Defaults to the entire domain. This argument is only 
+         accepted by ``ParticleProjectionPlot``.
+    axes_unit : A string
+         The name of the unit for the tick labels on the x and y axes.
+         Defaults to None, which automatically picks an appropriate unit.
+         If axes_unit is '1', 'u', or 'unitary', it will not display the
+         units, and only show the axes name.
+    origin : string or length 1, 2, or 3 sequence of strings
+         The location of the origin of the plot coordinate system.  This is
+         represented by '-' separated string or a tuple of strings.  In the
+         first index the y-location is given by 'lower', 'upper', or 'center'.
+         The second index is the x-location, given as 'left', 'right', or
+         'center'.  Finally, the whether the origin is applied in 'domain'
+         space, plot 'window' space or 'native' simulation coordinate system
+         is given. For example, both 'upper-right-domain' and ['upper',
+         'right', 'domain'] both place the origin in the upper right hand
+         corner of domain space. If x or y are not given, a value is inffered.
+         For instance, 'left-domain' corresponds to the lower-left hand corner
+         of the simulation domain, 'center-domain' corresponds to the center
+         of the simulation domain, or 'center-window' for the center of the
+         plot window. Further examples:
+
+         ==================================     ============================
+         format                                 example
+         ==================================     ============================
+         '{space}'                              'domain'
+         '{xloc}-{space}'                       'left-window'
+         '{yloc}-{space}'                       'upper-domain'
+         '{yloc}-{xloc}-{space}'                'lower-right-window'
+         ('{space}',)                           ('window',)
+         ('{xloc}', '{space}')                  ('right', 'domain')
+         ('{yloc}', '{space}')                  ('lower', 'window')
+         ('{yloc}', '{xloc}', '{space}')        ('lower', 'right', 'window')
+         ==================================     ============================
+
+         This argument is only accepted by ``ParticleProjectionPlot``.
+    window_size : float
+         The size of the window on the longest axis (in units of inches),
+         including the margins but not the colorbar. This argument is only 
+         accepted by ``ParticleProjectionPlot``.
+    aspect : float
+         The aspect ratio of the plot.  Set to None for 1. This argument is 
+         only accepted by ``ParticleProjectionPlot``.
+    x_bins : int
+        The number of bins in x field for the mesh. Defaults to 800. This
+        argument is only accepted by ``ParticlePhasePlot``.
+    y_bins : int
+        The number of bins in y field for the mesh. Defaults to 800. This
+        argument is only accepted by ``ParticlePhasePlot``.
+    deposition : str
+        Either 'ngp' or 'cic'. Controls what type of interpolation will be 
+        used to deposit the particle z_fields onto the mesh. Defaults to 'ngp'.
+        This argument is only accepted by ``ParticlePhasePlot``.
+    figure_size : int
+        Size in inches of the image. Defaults to 8 (product an 8x8 inch figure).
+        This argument is only accepted by ``ParticlePhasePlot``.
 
     Examples
     --------
@@ -394,10 +498,11 @@
     ...                     color='g')
 
     """
-
-    ad = ds.all_data()
-    x_field = ad._determine_fields(x_field)[0]
-    y_field = ad._determine_fields(y_field)[0]
+    dd = kwargs.get('data_source', None)
+    if dd is None:
+        dd = ds.all_data()
+    x_field = dd._determine_fields(x_field)[0]
+    y_field = dd._determine_fields(y_field)[0]
 
     direction = 3
     # try potential axes for a ParticleProjectionPlot:
@@ -419,5 +524,5 @@
     # Does not correspond to any valid PlotWindow-style plot,
     # use ParticlePhasePlot instead
     else:
-        return ParticlePhasePlot(ad, x_field, y_field,
+        return ParticlePhasePlot(dd, x_field, y_field,
                                  z_fields, color, *args, **kwargs)

diff -r bfd5887812b4b0a8ec76b548e2f4021ba0b3c0eb -r 399cd9128a7011779f161a11b03529289c9a2f51 yt/visualization/plot_container.py
--- a/yt/visualization/plot_container.py
+++ b/yt/visualization/plot_container.py
@@ -85,6 +85,9 @@
         if hasattr(args[0], '_data_valid'):
             if not args[0]._data_valid:
                 args[0]._recreate_frb()
+        if hasattr(args[0], '_profile_valid'):
+            if not args[0]._profile_valid:
+                args[0]._recreate_profile()
         if not args[0]._plot_valid:
             # it is the responsibility of _setup_plots to
             # call args[0].run_callbacks()

diff -r bfd5887812b4b0a8ec76b548e2f4021ba0b3c0eb -r 399cd9128a7011779f161a11b03529289c9a2f51 yt/visualization/plot_window.py
--- a/yt/visualization/plot_window.py
+++ b/yt/visualization/plot_window.py
@@ -168,6 +168,9 @@
     window_size : float
         The size of the window on the longest axis (in units of inches),
         including the margins but not the colorbar.
+    window_size : float
+        The size of the window on the longest axis (in units of inches),
+        including the margins but not the colorbar.
     right_handed : boolean
         Whether the implicit east vector for the image generated is set to make a right
         handed coordinate system with a north vector and the normal vector, the

diff -r bfd5887812b4b0a8ec76b548e2f4021ba0b3c0eb -r 399cd9128a7011779f161a11b03529289c9a2f51 yt/visualization/profile_plotter.py
--- a/yt/visualization/profile_plotter.py
+++ b/yt/visualization/profile_plotter.py
@@ -20,11 +20,11 @@
 from collections import OrderedDict
 import base64
 import os
+from functools import wraps
 
 import matplotlib
 import numpy as np
 
-
 from .base_plot_types import \
     PlotMPL, ImagePlotMPL
 from .plot_container import \
@@ -61,6 +61,14 @@
         canvas_cls = mpl.FigureCanvasAgg
     return canvas_cls
 
+def invalidate_profile(f):
+    @wraps(f)
+    def newfunc(*args, **kwargs):
+        rv = f(*args, **kwargs)
+        args[0]._profile_valid = False
+        return rv
+    return newfunc
+
 class PlotContainerDict(OrderedDict):
     def __missing__(self, key):
         plot = PlotMPL((10, 8), [0.1, 0.1, 0.8, 0.8], None, None)
@@ -729,6 +737,7 @@
     y_log = None
     plot_title = None
     _plot_valid = False
+    _profile_valid = False
     _plot_type = 'Phase'
     _xlim = (None, None)
     _ylim = (None, None)
@@ -766,7 +775,10 @@
         obj._text_xpos = {}
         obj._text_ypos = {}
         obj._text_kwargs = {}
-        obj.profile = profile
+        obj._profile = profile
+        obj._profile_valid = True
+        obj._xlim = (None, None)
+        obj._ylim = (None, None)
         super(PhasePlot, obj).__init__(data_source, figure_size, fontsize)
         obj._setup_plots()
         obj._initfinished = True
@@ -828,6 +840,12 @@
         # needed for API compatibility with PlotWindow
         pass
 
+    @property
+    def profile(self):
+        if not self._profile_valid:
+            self._recreate_profile()
+        return self._profile
+
     def _setup_plots(self):
         if self._plot_valid:
             return
@@ -1163,7 +1181,7 @@
         >>> plot.annotate_title("This is a phase plot")
 
         """
-        for f in self.profile.field_data:
+        for f in self._profile.field_data:
             if isinstance(f, tuple):
                 f = f[1]
             self.plot_title[self.data_source._determine_fields(f)[0]] = title
@@ -1185,18 +1203,22 @@
         log : boolean
             Log on/off.
         """
+        p = self._profile
         if field == "all":
             self.x_log = log
             self.y_log = log
-            for field in self.profile.field_data:
+            for field in p.field_data:
                 self.z_log[field] = log
+            self._profile_valid = False
         else:
-            if field == self.profile.x_field[1]:
+            if field == p.x_field[1]:
                 self.x_log = log
-            elif field == self.profile.y_field[1]:
+                self._profile_valid = False
+            elif field == p.y_field[1]:
                 self.y_log = log
-            elif field in self.profile.field_map:
-                self.z_log[self.profile.field_map[field]] = log
+                self._profile_valid = False
+            elif field in p.field_map:
+                self.z_log[p.field_map[field]] = log
             else:
                 raise KeyError("Field %s not in phase plot!" % (field))
         return self
@@ -1226,6 +1248,7 @@
         return self
 
     @invalidate_plot
+    @invalidate_profile
     def set_xlim(self, xmin=None, xmax=None):
         """Sets the limits of the x bin field
 
@@ -1233,12 +1256,12 @@
         ----------
 
         xmin : float or None
-          The new x minimum.  Defaults to None, which leaves the xmin
-          unchanged.
+          The new x minimum in the current x-axis units.  Defaults to None,
+          which leaves the xmin unchanged.
 
         xmax : float or None
-          The new x maximum.  Defaults to None, which leaves the xmax
-          unchanged.
+          The new x maximum in the current x-axis units.  Defaults to None,
+          which leaves the xmax unchanged.
 
         Examples
         --------
@@ -1250,47 +1273,20 @@
         >>> pp.save()
 
         """
-        p = self.profile
+        p = self._profile
         if xmin is None:
             xmin = p.x_bins.min()
+        elif not hasattr(xmin, 'units'):
+            xmin = self.ds.quan(xmin, p.x_bins.units)
         if xmax is None:
             xmax = p.x_bins.max()
-        units = {p.x_field: str(p.x.units),
-                 p.y_field: str(p.y.units)}
-        zunits = dict((field, str(p.field_units[field])) for field in p.field_units)
-        extrema = {p.x_field: ((xmin, str(p.x.units)), (xmax, str(p.x.units))),
-                   p.y_field: ((p.y_bins.min(), str(p.y.units)),
-                               (p.y_bins.max(), str(p.y.units)))}
-        if self.x_log is not None or self.y_log is not None:
-            logs = {}
-        else:
-            logs = None
-        if self.x_log is not None:
-            logs[p.x_field] = self.x_log
-        if self.y_log is not None:
-            logs[p.y_field] = self.y_log
-        deposition = getattr(self.profile, "deposition", None)
-        if deposition is None:
-            additional_kwargs = {'accumulation': p.accumulation,
-                                 'fractional': p.fractional}
-        else:
-            additional_kwargs = {'deposition': p.deposition}
-        self.profile = create_profile(
-            p.data_source,
-            [p.x_field, p.y_field],
-            list(p.field_map.values()),
-            n_bins=[len(p.x_bins)-1, len(p.y_bins)-1],
-            weight_field=p.weight_field,
-            units=units,
-            extrema=extrema,
-            logs=logs,
-            **additional_kwargs)
-        for field in zunits:
-            self.profile.set_field_unit(field, zunits[field])
+        elif not hasattr(xmax, 'units'):
+            xmax = self.ds.quan(xmax, p.x_bins.units)
         self._xlim = (xmin, xmax)
         return self
 
     @invalidate_plot
+    @invalidate_profile
     def set_ylim(self, ymin=None, ymax=None):
         """Sets the plot limits for the y bin field.
 
@@ -1298,12 +1294,12 @@
         ----------
 
         ymin : float or None
-          The new y minimum.  Defaults to None, which leaves the ymin
-          unchanged.
+          The new y minimum in the current y-axis units.  Defaults to None,
+          which leaves the ymin unchanged.
 
         ymax : float or None
-          The new y maximum.  Defaults to None, which leaves the ymax
-          unchanged.
+          The new y maximum in the current y-axis units.  Defaults to None,
+          which leaves the ymax unchanged.
 
         Examples
         --------
@@ -1315,17 +1311,24 @@
         >>> pp.save()
 
         """
-        p = self.profile
+        p = self._profile
         if ymin is None:
             ymin = p.y_bins.min()
+        elif not hasattr(ymin, 'units'):
+            ymin = self.ds.quan(ymin, p.y_bins.units)
         if ymax is None:
             ymax = p.y_bins.max()
+        elif not hasattr(ymax, 'units'):
+            ymax = self.ds.quan(ymax, p.y_bins.units)
+        self._ylim = (ymin, ymax)
+        return self
+
+    def _recreate_profile(self):
+        p = self._profile
         units = {p.x_field: str(p.x.units),
                  p.y_field: str(p.y.units)}
         zunits = dict((field, str(p.field_units[field])) for field in p.field_units)
-        extrema = {p.x_field: ((p.x_bins.min(), str(p.x.units)),
-                               (p.x_bins.max(), str(p.x.units))),
-                   p.y_field: ((ymin, str(p.y.units)), (ymax, str(p.y.units)))}
+        extrema = {p.x_field: self._xlim, p.y_field: self._ylim}
         if self.x_log is not None or self.y_log is not None:
             logs = {}
         else:
@@ -1334,13 +1337,11 @@
             logs[p.x_field] = self.x_log
         if self.y_log is not None:
             logs[p.y_field] = self.y_log
-        deposition = getattr(self.profile, "deposition", None)
-        if deposition is None:
-            additional_kwargs = {'accumulation': p.accumulation,
-                                 'fractional': p.fractional}
-        else:
-            additional_kwargs = {'deposition': p.deposition}
-        self.profile = create_profile(
+        deposition = getattr(p, "deposition", None)
+        additional_kwargs = {'accumulation': p.accumulation,
+                             'fractional': p.fractional,
+                             'deposition': deposition}
+        self._profile = create_profile(
             p.data_source,
             [p.x_field, p.y_field],
             list(p.field_map.values()),
@@ -1351,9 +1352,8 @@
             logs=logs,
             **additional_kwargs)
         for field in zunits:
-            self.profile.set_field_unit(field, zunits[field])
-        self._ylim = (ymin, ymax)
-        return self
+            self._profile.set_field_unit(field, zunits[field])
+        self._profile_valid = True
 
 
 class PhasePlotMPL(ImagePlotMPL):

diff -r bfd5887812b4b0a8ec76b548e2f4021ba0b3c0eb -r 399cd9128a7011779f161a11b03529289c9a2f51 yt/visualization/tests/test_particle_plot.py
--- a/yt/visualization/tests/test_particle_plot.py
+++ b/yt/visualization/tests/test_particle_plot.py
@@ -16,13 +16,19 @@
 import tempfile
 import shutil
 import unittest
+
+import numpy as np
+
 from yt.data_objects.profiles import create_profile
 from yt.visualization.tests.test_plotwindow import \
     WIDTH_SPECS, ATTR_ARGS
+from yt.convenience import load
 from yt.data_objects.particle_filters import add_particle_filter
 from yt.testing import \
     fake_particle_ds, \
     assert_array_almost_equal, \
+    requires_file, \
+    assert_allclose, \
     assert_fname
 from yt.utilities.answer_testing.framework import \
     requires_ds, \
@@ -30,7 +36,9 @@
     PlotWindowAttributeTest, \
     PhasePlotAttributeTest
 from yt.visualization.api import \
-    ParticleProjectionPlot, ParticlePhasePlot
+    ParticlePlot, \
+    ParticleProjectionPlot, \
+    ParticlePhasePlot
 from yt.units.yt_array import YTArray
 
 
@@ -209,6 +217,52 @@
             for fname in TEST_FLNMS:
                 assert assert_fname(p.save(fname)[0])
 
+tgal = 'TipsyGalaxy/galaxy.00300'
+ at requires_file(tgal)
+def test_particle_phase_plot_semantics():
+    ds = load(tgal)
+    ad = ds.all_data()
+    dens_ex = ad.quantities.extrema(('Gas', 'density'))
+    temp_ex = ad.quantities.extrema(('Gas', 'temperature'))
+    plot = ParticlePlot(ds,
+                        ('Gas', 'density'),
+                        ('Gas', 'temperature'),
+                        ('Gas', 'particle_mass'))
+    plot.set_log('density', True)
+    plot.set_log('temperature', True)
+    p = plot.profile
+
+    # bin extrema are field extrema
+    assert dens_ex[0] - np.spacing(dens_ex[0]) == p.x_bins[0]
+    assert dens_ex[-1] + np.spacing(dens_ex[-1]) == p.x_bins[-1]
+    assert temp_ex[0] - np.spacing(temp_ex[0]) == p.y_bins[0]
+    assert temp_ex[-1] + np.spacing(temp_ex[-1]) == p.y_bins[-1]
+
+    # bins are evenly spaced in log space
+    logxbins = np.log10(p.x_bins)
+    dxlogxbins = logxbins[1:] - logxbins[:-1]
+    assert_allclose(dxlogxbins, dxlogxbins[0])
+
+    logybins = np.log10(p.y_bins)
+    dylogybins = logybins[1:] - logybins[:-1]
+    assert_allclose(dylogybins, dylogybins[0])
+
+    plot.set_log('density', False)
+    plot.set_log('temperature', False)
+    p = plot.profile
+
+    # bin extrema are field extrema
+    assert dens_ex[0] - np.spacing(dens_ex[0]) == p.x_bins[0]
+    assert dens_ex[-1] + np.spacing(dens_ex[-1]) == p.x_bins[-1]
+    assert temp_ex[0] - np.spacing(temp_ex[0]) == p.y_bins[0]
+    assert temp_ex[-1] + np.spacing(temp_ex[-1]) == p.y_bins[-1]
+
+    # bins are evenly spaced in log space
+    dxbins = p.x_bins[1:] - p.x_bins[:-1]
+    assert_allclose(dxbins, dxbins[0])
+
+    dybins = p.y_bins[1:] - p.y_bins[:-1]
+    assert_allclose(dybins, dybins[0])
 
 class TestParticleProjectionPlotSave(unittest.TestCase):

Repository URL: https://bitbucket.org/yt_analysis/yt/

--

This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.



More information about the yt-svn mailing list