|
| 1 | +============ |
| 2 | +Architecture |
| 3 | +============ |
| 4 | + |
| 5 | +lazyarray is a single-module package: all the code lives in |
| 6 | +``lazyarray.py`` (around 600 lines), and the test suite lives in the |
| 7 | +``test/`` directory. The Sphinx documentation source is in ``doc/``. |
| 8 | + |
| 9 | +The public surface is the :class:`larray` class together with the |
| 10 | +``partial_shape`` helper. Everything else in ``lazyarray.py`` is internal. |
| 11 | + |
| 12 | +The core data structure |
| 13 | +======================= |
| 14 | + |
| 15 | +An :class:`larray` instance stores three pieces of state: |
| 16 | + |
| 17 | +* ``base_value`` — the underlying source of element values. This may be a |
| 18 | + number, a NumPy array, a SciPy sparse matrix, a sequence, an iterator, |
| 19 | + a generator, or a callable of the form ``f(i)`` or ``f(i, j)``. |
| 20 | +* ``operations`` — an ordered list of ``(callable, operand)`` pairs that |
| 21 | + describes the queue of arithmetic operations to apply to the base value. |
| 22 | +* ``_shape`` and ``_dtype`` — metadata used to validate broadcasting and |
| 23 | + to short-circuit element-wise evaluation. |
| 24 | + |
| 25 | +Arithmetic on an :class:`larray` does not change the base value. Instead, |
| 26 | +each operator (``__add__``, ``__mul__``, etc.) is built by the |
| 27 | +``lazy_operation`` factory and appends an entry to ``operations`` on a |
| 28 | +deep-copy of the array. The actual computation is deferred. |
| 29 | + |
| 30 | +Evaluation |
| 31 | +========== |
| 32 | + |
| 33 | +Element values are computed only when the array is indexed or its |
| 34 | +``evaluate()`` method is called. The flow is: |
| 35 | + |
| 36 | +1. The address (a slice, integer, tuple, or boolean mask) is normalised |
| 37 | + via ``full_address`` and the resulting sub-array shape is derived |
| 38 | + via ``partial_shape``. |
| 39 | +2. The relevant subset of the base value is materialised — by calling |
| 40 | + the function on the requested indices, advancing the iterator, |
| 41 | + slicing the array, or returning the scalar. |
| 42 | +3. The queued operations are applied in order to that subset. |
| 43 | + |
| 44 | +This means that, even for very large logical arrays, only the elements |
| 45 | +that are actually requested are ever computed. This is the property that |
| 46 | +makes lazyarray useful in MPI-parallel simulations, where each process |
| 47 | +typically needs only a slice of a large parameter array. |
| 48 | + |
| 49 | +Decorators |
| 50 | +========== |
| 51 | + |
| 52 | +A handful of small decorators capture cross-cutting concerns: |
| 53 | + |
| 54 | +* ``check_shape`` ensures that an operand has a shape compatible with |
| 55 | + the array; |
| 56 | +* ``requires_shape`` raises if the array's shape is not yet defined; |
| 57 | +* ``lazy_operation`` / ``lazy_inplace_operation`` / ``lazy_unary_operation`` |
| 58 | + generate the magic methods used for arithmetic. |
| 59 | + |
| 60 | +Extension points |
| 61 | +================ |
| 62 | + |
| 63 | +Two extension points are provided for users who need to plug in their own |
| 64 | +value types: |
| 65 | + |
| 66 | +* a base value object that implements a method ``lazily_evaluate`` will |
| 67 | + have that method called during evaluation (used, for example, to |
| 68 | + integrate Brian quantities); |
| 69 | +* an object that is ``Sized`` but should nonetheless be treated as a |
| 70 | + single element can declare this by setting an attribute |
| 71 | + ``is_lazyarray_scalar = True``. |
0 commit comments