Repository: yt-project/yt

Issue:
YTBoundsDefinitionError when donig CIC projection
### Bug report

**Bug summary**

I get a `YTBoundsDefinitionError` when doing a CIC projection of a RAMSES dataset. My data source is a sphere.

**Code for reproduction**

```python
import yt

ds = yt.load('output_00080/info_00080.txt')

# Get a sphere centred on the main halo
hpos = ds.arr([0.5215110772898429, 0.5215110772898429, 0.5215110772898429], 'code_length')
hrvir = ds.quan(0.042307235300540924, 'Mpc')

sp = ds.sphere(hpos, hrvir*10)
dx_min = sp['dx'].min()
print(sp['x'].ptp() / dx_min, sp['y'].ptp() / dx_min, sp['z'].ptp() / dx_min, sp['x'])

# Plot DM quantities
kwa = dict(data_source=sp, center=hpos, width=hrvir*2)
p = yt.ProjectionPlot(ds, 'x', 'io_cic', **kwa)
p.set_cmap('io_cic', 'bone')
p.save()
```

Two comments:
* If I replace `hrvir*10` by `hrvir*20`, I still get one cell in the region, I don't have the bug anymore
* On another dataset, I have ~900 cells in each dimension, yet I still get the issue

**Actual outcome**

```
yt : [INFO     ] 2019-01-09 18:50:26,601 Adding particle_type: DM
yt : [INFO     ] 2019-01-09 18:50:26,616 Adding particle_type: star
yt : [INFO     ] 2019-01-09 18:50:26,630 Adding particle_type: cloud
yt : [INFO     ] 2019-01-09 18:50:26,644 Adding particle_type: dust
yt : [INFO     ] 2019-01-09 18:50:26,658 Adding particle_type: star_tracer
yt : [INFO     ] 2019-01-09 18:50:26,672 Adding particle_type: cloud_tracer
yt : [INFO     ] 2019-01-09 18:50:26,687 Adding particle_type: dust_tracer
yt : [INFO     ] 2019-01-09 18:50:26,702 Adding particle_type: gas_tracer
Traceback (most recent call last):
  File "/home/cadiou/codes/yt/yt/data_objects/data_containers.py", line 308, in _generate_fluid_field
    finfo.check_available(gen_obj)
  File "/home/cadiou/codes/yt/yt/fields/derived_field.py", line 204, in check_available
    validator(data)
  File "/home/cadiou/codes/yt/yt/fields/derived_field.py", line 470, in __call__
    raise NeedsGridType(self.ghost_zones,self.fields)
yt.fields.field_exceptions.NeedsGridType: (0, None)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "analyse.py", line 20, in <module>
    p = yt.ProjectionPlot(ds, 'x', 'DM_cic', **kwa)
  File "HOME/codes/yt/yt/visualization/plot_window.py", line 1591, in __init__
    max_level=max_level)
  File "HOME/codes/yt/yt/data_objects/construction_data_containers.py", line 270, in __init__
    self.get_data(field)
  File "HOME/codes/yt/yt/data_objects/construction_data_containers.py", line 344, in get_data
    self._handle_chunk(chunk, fields, tree)
  File "HOME/codes/yt/yt/data_objects/construction_data_containers.py", line 450, in _handle_chunk
    d = chunk[field] * dl
  File "HOME/codes/yt/yt/data_objects/data_containers.py", line 255, in __getitem__
    self.get_data(f)
  File "HOME/codes/yt/yt/data_objects/data_containers.py", line 1382, in get_data
    self._generate_fields(fields_to_generate)
  File "HOME/codes/yt/yt/data_objects/data_containers.py", line 1402, in _generate_fields
    fd = self._generate_field(field)
  File "HOME/codes/yt/yt/data_objects/data_containers.py", line 292, in _generate_field
    tr = self._generate_fluid_field(field)
  File "HOME//codes/yt/yt/data_objects/data_containers.py", line 310, in _generate_fluid_field
    rv = self._generate_spatial_fluid(field, ngt_exception.ghost_zones)
  File "HOME//codes/yt/yt/data_objects/data_containers.py", line 330, in _generate_spatial_fluid
    ind += o.select(self.selector, self[field], rv, ind)
  File "HOME//codes/yt/yt/data_objects/data_containers.py", line 255, in __getitem__
    self.get_data(f)
  File "HOME//codes/yt/yt/data_objects/data_containers.py", line 1382, in get_data
    self._generate_fields(fields_to_generate)
  File "HOME//codes/yt/yt/data_objects/data_containers.py", line 1402, in _generate_fields
    fd = self._generate_field(field)
  File "HOME//codes/yt/yt/data_objects/data_containers.py", line 292, in _generate_field
    tr = self._generate_fluid_field(field)
  File "HOME//codes/yt/yt/data_objects/data_containers.py", line 312, in _generate_fluid_field
    rv = finfo(gen_obj)
  File "HOME//codes/yt/yt/fields/derived_field.py", line 256, in __call__
    dd = self._function(self, data)
  File "HOME//codes/yt/yt/fields/particle_fields.py", line 149, in particle_cic
    d = data.deposit(pos, [data[ptype, mass_name]], method = "cic")
  File "HOME//codes/yt/yt/data_objects/octree_subset.py", line 184, in deposit
    op.initialize()
  File "yt/geometry/particle_deposit.pyx", line 345, in yt.geometry.particle_deposit.CICDeposit.initialize
yt.utilities.exceptions.YTBoundsDefinitionError: This operation has encountered a bounds error: CIC requires minimum of 2 zones in all spatial dimensions. Specified bounds are '(2, 2, 2, 1)'.
```

**Version Information**
<!--Please specify your platform and versions of the relevant libraries you are
using:-->
  * Operating System: Linux
  * Python Version: 3.6.7
  * yt version: 3.6dev0
  * Other Libraries (if applicable): 


Discussion:
Taking a look at this, thank you for the detailed report.
I have a workaround I think, I just want to make sure it's always safe before making a PR:

```diff
diff --git a/yt/geometry/particle_deposit.pyx b/yt/geometry/particle_deposit.pyx
index bb39baf5b..82b416b39 100644
--- a/yt/geometry/particle_deposit.pyx
+++ b/yt/geometry/particle_deposit.pyx
@@ -340,11 +340,11 @@ cdef class CICDeposit(ParticleDepositOperation):
     cdef np.float64_t[:,:,:,:] field
     cdef public object ofield
     def initialize(self):
-        if not all(_ > 1 for _ in self.nvals):
+        if not all(_ > 1 for _ in self.nvals[:-1]):
             from yt.utilities.exceptions import YTBoundsDefinitionError
             raise YTBoundsDefinitionError(
                 "CIC requires minimum of 2 zones in all spatial dimensions.",
-                self.nvals)
+                self.nvals[:-1])
         self.field = append_axes(
             np.zeros(self.nvals, dtype="float64", order='F'), 4)
```
I can confirm this solves my issue.

Resolving patch:
diff --git a/yt/data_objects/construction_data_containers.py b/yt/data_objects/construction_data_containers.py
--- a/yt/data_objects/construction_data_containers.py
+++ b/yt/data_objects/construction_data_containers.py
@@ -750,15 +750,20 @@ def deposit(self, positions, fields = None, method = None,
         cls = getattr(particle_deposit, "deposit_%s" % method, None)
         if cls is None:
             raise YTParticleDepositionNotImplemented(method)
-        # We allocate number of zones, not number of octs. Everything inside
-        # this is fortran ordered because of the ordering in the octree deposit
-        # routines, so we reverse it here to match the convention there
-        op = cls(tuple(self.ActiveDimensions)[::-1], kernel_name)
+        # We allocate number of zones, not number of octs. Everything
+        # inside this is Fortran ordered because of the ordering in the
+        # octree deposit routines, so we reverse it here to match the
+        # convention there
+        nvals = tuple(self.ActiveDimensions[::-1])
+        # append a dummy dimension because we are only depositing onto
+        # one grid
+        op = cls(nvals + (1,), kernel_name)
         op.initialize()
         op.process_grid(self, positions, fields)
-        vals = op.finalize()
         # Fortran-ordered, so transpose.
-        return vals.transpose()
+        vals = op.finalize().transpose()
+        # squeeze dummy dimension we appended above
+        return np.squeeze(vals, axis=0)
 
     def write_to_gdf(self, gdf_path, fields, nprocs=1, field_units=None,
                      **kwargs):
diff --git a/yt/data_objects/grid_patch.py b/yt/data_objects/grid_patch.py
--- a/yt/data_objects/grid_patch.py
+++ b/yt/data_objects/grid_patch.py
@@ -370,16 +370,22 @@ def deposit(self, positions, fields = None, method = None,
         cls = getattr(particle_deposit, "deposit_%s" % method, None)
         if cls is None:
             raise YTParticleDepositionNotImplemented(method)
-        # We allocate number of zones, not number of octs. Everything inside
-        # this is Fortran ordered because of the ordering in the octree deposit
-        # routines, so we reverse it here to match the convention there
-        op = cls(tuple(self.ActiveDimensions[::-1]), kernel_name)
+        # We allocate number of zones, not number of octs. Everything
+        # inside this is Fortran ordered because of the ordering in the
+        # octree deposit routines, so we reverse it here to match the
+        # convention there
+        nvals = tuple(self.ActiveDimensions[::-1])
+        # append a dummy dimension because we are only depositing onto
+        # one grid
+        op = cls(nvals + (1,), kernel_name)
         op.initialize()
         op.process_grid(self, positions, fields)
         vals = op.finalize()
         if vals is None: return
         # Fortran-ordered, so transpose.
-        return vals.transpose()
+        vals = vals.transpose()
+        # squeeze dummy dimension we appended above
+        return np.squeeze(vals, axis=0)
 
     def select_blocks(self, selector):
         mask = self._get_selector_mask(selector)


Test patch:
diff --git a/yt/geometry/tests/test_particle_deposit.py b/yt/geometry/tests/test_particle_deposit.py
--- a/yt/geometry/tests/test_particle_deposit.py
+++ b/yt/geometry/tests/test_particle_deposit.py
@@ -1,8 +1,11 @@
 from yt.utilities.exceptions import \
     YTBoundsDefinitionError
 
+from yt.convenience import \
+    load
 from yt.testing import \
-    fake_random_ds
+    fake_random_ds, \
+    requires_file
 from numpy.testing import \
     assert_raises
 
@@ -12,3 +15,16 @@ def test_cic_deposit():
             dims=[1, 800, 800])
     f = ("deposit", "all_cic")
     assert_raises(YTBoundsDefinitionError, my_reg.__getitem__, f)
+
+RAMSES = 'output_00080/info_00080.txt'
+
+@requires_file(RAMSES)
+def test_one_zone_octree_deposit():
+    ds = load(RAMSES)
+
+    # Get a sphere centred on the main halo
+    hpos = ds.arr([0.5215110772898429, 0.5215110772898429, 0.5215110772898429], 'code_length')
+    hrvir = ds.quan(0.042307235300540924, 'Mpc')
+
+    sp = ds.sphere(hpos, hrvir*10)
+    assert sp['deposit', 'io_cic'].shape == (1,)