Repository: yt-project/yt

Issue:
Unable to filter a dataset using a derived field
Originally reported by: **Danielle Sponseller (Bitbucket: [DSponseller](https://bitbucket.org/DSponseller), GitHub: Unknown)**

----------------------------------------

When defining a particle filter, a derived field cannot be used to determine which particles to filter out. The example below shows a field defined to be equivalent to the "particle_position_x" field. When using "particle_position_x' in the filter definition, the filter works as expected, however when the derived field is used an error is returned saying that the field cannot be found.


```
#!python

import yt

@yt.particle_filter(requires=["myField"], filtered_type='all')
def small_x(pfilter, data):
    filter = data[(pfilter.filtered_type, "myField")] < 0
    return filter

def myField(field, data):
    return data["particle_position_x"]

snap = 'GadgetDiskGalaxy/snapshot_200.hdf5'

ds = yt.load(snap)
ds.add_particle_filter('small_x')
ds.add_field("myField", function=myField, units="kpc", particle_type=True)

plot = yt.ParticlePlot(
    ds,
    ('small_x', 'particle_position_x'),
    ('small_x', 'particle_position_y'),
    ('small_x', 'myField'),
    width=(2000, 'kpc'))

plot.save("Plot")

```

Running this script produces the following error:

```
Traceback (most recent call last):
  File "test.py", line 22, in <module>
    width=(2000, 'kpc'))
  File "/Users/goldbaum/Documents/yt-hg/yt/visualization/particle_plots.py", line 399, in ParticlePlot
    x_field = ad._determine_fields(x_field)[0]
  File "/Users/goldbaum/Documents/yt-hg/yt/data_objects/data_containers.py", line 1088, in _determine_fields
    finfo = self.ds._get_field_info(ftype, fname)
  File "/Users/goldbaum/Documents/yt-hg/yt/data_objects/static_output.py", line 666, in _get_field_info
    raise YTFieldNotFound((ftype, fname), self)
yt.utilities.exceptions.YTFieldNotFound: Could not find field '('small_x', 'particle_position_x')' in snapshot_200
```

----------------------------------------
- Bitbucket: https://bitbucket.org/yt_analysis/yt/issue/1278



Discussion:
*Original comment by* **Kacper Kowalik (Bitbucket: [xarthisius](https://bitbucket.org/xarthisius), GitHub: [xarthisius](https://github.com/xarthisius))**:

----------------------------------------

FTR, changing order and field name in example snippet makes it work:

```
#!python

ds = yt.load(snap)
ds.add_field(('all', 'myField'), function=myField, units="kpc", particle_type=True)
ds.add_particle_filter('small_x')
```

I guess field type must match index particle type, and filter can be added only if field was already defined.

*Original comment by* **Nathan Goldbaum (Bitbucket: [ngoldbaum](https://bitbucket.org/ngoldbaum), GitHub: [ngoldbaum](https://github.com/ngoldbaum))**:

----------------------------------------

Thanks for the detailed report! I'll look into this closer later today.

*Original comment by* **Nathan Goldbaum (Bitbucket: [ngoldbaum](https://bitbucket.org/ngoldbaum), GitHub: [ngoldbaum](https://github.com/ngoldbaum))**:

----------------------------------------

Making script runnable, adding error message

We should add some validation or error checking so that the error message is better.

Resolving patch:
diff --git a/yt/data_objects/particle_filters.py b/yt/data_objects/particle_filters.py
--- a/yt/data_objects/particle_filters.py
+++ b/yt/data_objects/particle_filters.py
@@ -69,6 +69,10 @@ def available(self, field_list):
         # fields are implicitly "all" or something.
         return all((self.filtered_type, field) in field_list for field in self.requires)
 
+    def missing(self, field_list):
+        return list((self.filtered_type, field) for field in self.requires if
+                    (self.filtered_type, field) not in field_list)
+
     def wrap_func(self, field_name, old_fi):
         new_fi = copy.copy(old_fi)
         new_fi.name = (self.name, field_name[1])
diff --git a/yt/data_objects/static_output.py b/yt/data_objects/static_output.py
--- a/yt/data_objects/static_output.py
+++ b/yt/data_objects/static_output.py
@@ -37,7 +37,8 @@
 from yt.utilities.exceptions import \
     YTObjectNotImplemented, \
     YTFieldNotFound, \
-    YTGeometryNotSupported
+    YTGeometryNotSupported, \
+    YTIllDefinedParticleFilter
 from yt.utilities.parallel_tools.parallel_analysis_interface import \
     parallel_root_only
 from yt.utilities.parameter_file_storage import \
@@ -675,7 +676,8 @@ def add_particle_filter(self, filter):
 
     def _setup_filtered_type(self, filter):
         if not filter.available(self.derived_field_list):
-            return False
+            raise YTIllDefinedParticleFilter(
+                filter, filter.missing(self.derived_field_list))
         fi = self.field_info
         fd = self.field_dependencies
         available = False
diff --git a/yt/utilities/exceptions.py b/yt/utilities/exceptions.py
--- a/yt/utilities/exceptions.py
+++ b/yt/utilities/exceptions.py
@@ -392,6 +392,17 @@ def __str__(self):
         return "Filter '%s' ill-defined.  Applied to shape %s but is shape %s." % (
             self.filter, self.s1, self.s2)
 
+class YTIllDefinedParticleFilter(YTException):
+    def __init__(self, filter, missing):
+        self.filter = filter
+        self.missing = missing
+
+    def __str__(self):
+        msg = ("\nThe fields\n\t{},\nrequired by the \"{}\" particle filter, "
+               "are not defined for this dataset.")
+        f = self.filter
+        return msg.format("\n".join([str(m) for m in self.missing]), f.name)
+
 class YTIllDefinedBounds(YTException):
     def __init__(self, lb, ub):
         self.lb = lb


Test patch: