docs: def to_mask(obj, m=None, indices=None):
      '''
      to_mask(obj, m) yields the set of indices from the given vertex-set or itable object obj that
        correspond to the given mask m.
      to_mask((obj, m)) is equivalent to to_mask(obj, m).
      
      The mask m may take any of the following forms:
         * a list of vertex indices
         * a boolean array (one value per vertex)
         * a property name, which can be cast to a boolean array
         * a tuple (property, value) where property is a list of values, one per vertex, and value
           is the value that must match in order for a vertex to be included (this is basically
           equivalent to the mask (property == value); note that property may also be a property
           name
         * a tuple (property, min, max), which specifies that the property must be between min and
           max for a vertex to be included (min < p <= max)
         * a tuple (property, (val1, val2...)), which specifies that the property must be any of
           the values in (val1, val2...) for a vertex to be included
         * None, indicating that all labels should be returned
         * a dict/mapping with one item whose key is either 'and', or 'or' and whose value is a list,
           each of whose elements matches one of the above.
      
      Note that the optional argument indices (default: False) may be set to true to yield the
      vertex indices instead of the vertex labels. If obj is not a VertexSet object, then this
      option is ignored.
      '''
      if not pimms.is_map(obj) and pimms.is_vector(obj) and len(obj) < 4 and m is None:
          if   len(obj) == 1: obj = obj[0]
          elif len(obj) == 2: (obj, m) = obj
          else:
              (obj, m, q) = obj
              if indices is None:
                  if pimms.is_map(q): indices = q.get('indices', False)
                  else: indices = q
      if indices is None: indices = False
      if is_vset(obj):
          lbls = obj.labels
          idcs = obj.indices
          obj = obj.properties
      else:
          obj = pimms.itable(obj)
          lbls = np.arange(0, obj.row_count, 1, dtype=np.int)
          idcs = lbls
      if m is None: return idcs if indices else lbls
      if is_tuple(m):
          if len(m) == 0: return np.asarray([], dtype=np.int)
          p = to_property(obj, m[0])
          if len(m) == 2 and hasattr(m[1], '__iter__'):
              m = reduce(lambda q,u: np.logical_or(q, p == u), m[1], np.zeros(len(p), dtype=np.bool))
          elif len(m) == 2:
              m = (p == m[1])
          elif len(m) == 3:
              m = np.logical_and(m[1] < p, p <= m[2])
      elif pimms.is_str(m):
          m = np.asarray(obj[m], dtype=np.bool)
      elif pimms.is_map(m):
          if len(m) != 1: raise ValueError('Dicts used as masks must contain 1 item')
          (k,v) = next(six.iteritems(m))
          if not hasattr(v, '__iter__'): raise ValueError('Value of dict-mask must be an iterator')
          if not pimms.is_str(k): raise ValueError('Key of dict-mask must be "or", or "and"')
          v = [to_mask(obj, u, indices=indices) for u in v]
          if   k in ('and', 'intersect', 'intersection', '^', '&', '&&'):
              return reduce(np.intersect1d, v)
          elif k in ('or',  'union', 'v', '|' '||'):
              return reduce(np.union1d, v)
      # at this point, m should be a boolean array or a list of indices
      return idcs[m] if indices else lbls[m]