Esempio n. 1
0
    def __init__(
        self,

        # Mandatory parameters.
        x: SequenceTypes,
        y: SequenceTypes,
    ) -> None:
        '''
        Initialize this vector field.

        Parameters
        ----------
        x : ndarray
            Two-dimensional sequence whose:
            * First dimension indexes one or more time steps of this simulation.
            * Second dimension indexes each X component of each vector in this
              vector field for this time step.
        y : ndarray
            Two-dimensional sequence whose:
            * First dimension indexes one or more time steps of this simulation.
            * Second dimension indexes each Y component of each vector in this
              vector field for this time step.
        '''

        # Classify the passed sequences as Numpy arrays for efficiency.
        self._x = nparray.from_iterable(x)
        self._y = nparray.from_iterable(y)
Esempio n. 2
0
    def voltage_polarity(self) -> VectorFieldCellsCache:
        '''
        Vector field cache of all cellular voltage polarities over all time
        steps of the current simulation phase, originally spatially situated at
        cell membrane midpoints.
        '''

        # Two-dimensional Numpy arrays of all transmembrane voltages (Vmem) and
        # Vmem averages across all cell membranes over all time steps.
        vm_time = nparray.from_iterable(self._phase.sim.vm_time)
        vm_ave_time = nparray.from_iterable(self._phase.sim.vm_ave_time)

        # Two-dimensional Numpy array of all transmembrane voltage polarity
        # vector magnitudes whose:
        #
        # * First dimension indexes each time step.
        # * Second dimension indexes each cell membrane such that each element
        #   is the magnitude of the polarity of the transmembrane voltage across
        #   that membrane for this time step, where this magnitude is defined as
        #   the difference between:
        #   * The transmembrane voltage across that membrane for this time step.
        #   * The average transmembrane voltage across all membranes of the cell
        #     containing that membrane for this time step.
        polarity_membranes_midpoint_magnitudes = (
            vm_time - vm_ave_time[:, self._phase.cells.mem_to_cells])

        # Two-dimensional Numpy arrays of the X and Y components of all Vmem
        # polarity vectors, spatially situated at cell membrane midpoints.
        polarity_membranes_midpoint_x = (
            polarity_membranes_midpoint_magnitudes *
            self._phase.cells.membranes_normal_unit_x)
        polarity_membranes_midpoint_y = (
            polarity_membranes_midpoint_magnitudes *
            self._phase.cells.membranes_normal_unit_y)

        # Two-dimensional Numpy arrays of the X and Y components of all Vmem
        # polarity vectors, spatially situated at cell centres.
        polarity_cells_centre_x = (
            self._phase.cells.map_membranes_midpoint_to_cells_centre(
                polarity_membranes_midpoint_x * self._phase.cells.mem_sa) /
            self._phase.cells.cell_sa)
        polarity_cells_centre_y = (
            self._phase.cells.map_membranes_midpoint_to_cells_centre(
                polarity_membranes_midpoint_y * self._phase.cells.mem_sa) /
            self._phase.cells.cell_sa)

        # Create, return, and cache this vector field.
        return VectorFieldCellsCache(
            x=VectorCellsCache(phase=self._phase,
                               times_cells_centre=polarity_cells_centre_x),
            y=VectorCellsCache(phase=self._phase,
                               times_cells_centre=polarity_cells_centre_y))
Esempio n. 3
0
def is_cyclic_quad(
    A: SequenceTypes,
    B: SequenceTypes,
    C: SequenceTypes,
    D: SequenceTypes,
) -> bool:
    '''
    ``True`` only if the quadrilateral represented by the passed four vertices
    is **cyclic** (i.e., counterclockwise-oriented).

    Parameters
    ----------
    A, B, C, D : SequenceTypes
        Four vertices of the quadrilateral to be tested, assumed to be oriented
        counterclockwise.

    Returns
    ----------
    bool
        ``True`` only if this quadrilateral is cyclic.
    '''

    # Avoid circular import dependencies.
    from betse.lib.numpy import nparray, npscalar

    # Coerce these sequences to Numpy arrays for efficiency.
    A = nparray.from_iterable(A)
    B = nparray.from_iterable(B)
    C = nparray.from_iterable(C)
    D = nparray.from_iterable(D)

    # Lengths of the four edges constructed from these vertices.
    a = np.linalg.norm(B - A)
    b = np.linalg.norm(C - B)
    c = np.linalg.norm(D - C)
    d = np.linalg.norm(A - D)

    # Calculate the length of the two diagonals.
    e = np.sqrt(((a * c + b * d) * (a * d + b * c)) / (a * b + c * d))
    f = np.sqrt(((a * c + b * d) * (a * b + c * d)) / (a * d + b * c))

    # For a cyclic quad, the product between the two diagonals equals
    # the product between the two adjacent sides.
    lhs = np.round(e * f, 15)
    rhs = np.round(a * c + b * d, 15)

    # Non-standard Numpy-specific boolean encapsulating this truth value.
    test_bool = lhs == rhs

    # Coerce this into a standard Numpy-agnostic boolean for safety.
    return npscalar.to_python(test_bool)
Esempio n. 4
0
    def _get_cell_times_vmems(self, phase: SimPhase) -> NumpyArrayType:
        '''
        One-dimensional Numpy array of all transmembrane voltages for each
        sampled time step spatially situated at the centre of the single cell
        indexed by the ``plot cell index`` entry specified by the passed
        simulation phase.

        Parameters
        ----------
        phase : SimPhase
            Current simulation phase.
        '''

        # 0-based index of the cell to serialize time data for.
        cell_index = phase.p.visual.single_cell_index

        if phase.p.is_ecm:
            cell_times_vmems = []
            for vm_at_mem in phase.sim.vm_time:
                vm_t = mathunit.upscale_units_milli(
                    cell_ave(phase.cells, vm_at_mem)[cell_index])
                cell_times_vmems.append(vm_t)
        else:
            cell_times_vmems = mathunit.upscale_units_milli(phase.sim.vm_time)

        return nparray.from_iterable(cell_times_vmems)
Esempio n. 5
0
    def pick_cells_and_mems(
        self,
        cells: 'betse.science.cells.Cells',
        p:     'betse.science.parameters.Parameters',
    ) -> tuple:
        '''
        2-tuple ``(cells_index, mems_index)`` of one-dimensional Numpy arrays
        of the indices of both all cells *and* cell membranes in the passed
        cell cluster selected by this tissue picker, ignoring extracellular
        spaces.

        By default, this method returns the array returned by the subclass
        implementation of the abstract :meth:`pick_cells` method, mapped from
        cell to cell membrane indices.

        Parameters
        ----------
        cells : Cells
            Current cell cluster.
        p : Parameters
            Current simulation configuration.

        Returns
        ----------
        (ndarray, ndarray)
            2-tuple ``(cells_index, mems_index)``, where:
            * ``cells_index`` is the one-dimensional Numpy array of the indices
              of all subclass-selected cells.
            * ``mems_index`` is the one-dimensional Numpy array of the indices
              of all subclass-selected cell membranes.
        '''

        # One-dimensional Numpy array of the indices of subclass-selected cells.
        cells_index = self.pick_cells(cells, p)

        #FIXME: Ideally, this array would be trivially defined as follows:
        #    mems_index = cells.cell_to_mems[cells_index].flatten()
        #Unfortunately, the two-dimensional Numpy array "cells.cell_to_mems"
        #actually appears to be a one-dimensional array of lists -- which is a
        #bit bizarro-world. To compaund matters, this array's "dtype" is
        #"object" (presumably, because it contains Python lists) rather than
        #the "dtype" of "int" that one might expect. Since this is the case,
        #it's infeasible to even coerce the temporary one-dimensional Numpy
        #array "cells.cell_to_mems[cells_index]" into a two-dimensional array
        #of ints by calling ndarray.astype(int). Frankly, it's all a bit beyond
        #me; until this core issue is resolved, however, the current inelegant
        #and inefficient approach remains.

        # One-dimensional Numpy array of the indices of subclass-selected cell
        # membranes mapped from the array of cell indices.
        # logs.log_debug('cells_index: %r', cells_index)
        mems_index = cells.cell_to_mems[cells_index]
        mems_index, _, _ = toolbox.flatten(mems_index)
        mems_index = nparray.from_iterable(mems_index)

        # Return these arrays.
        return cells_index, mems_index
Esempio n. 6
0
def _upscale_data_in_units(
        data: NumericOrIterableTypes,
        factor: NumericSimpleTypes) -> (NumericOrIterableTypes):
    '''
    Upscale the contents of the passed object by the passed multiplier,
    typically under the assumption that these contents are denominated in the
    reciprocal units of this multiplier (i.e., ``10**6`` for micrometers).

    Parameters
    ----------
    data : NumericOrIterableTypes
        Number or sequence to be upscaled.
    factor : NumericSimpleTypes
        Reciprocal of the units this object is denominated in.

    Returns
    ----------
    NumericOrIterableTypes
        Either:

        * If this data is numeric, this number upscaled by this multiplier.
        * If this data is sequential, this sequence converted into a Numpy
          array whose elements are upscaled by this multiplier.

    See Also
    ----------
    :func:`upscale_units_milli`
        Further details.
    '''

    # If the passed object is numeric, return this number upscaled.
    if types.is_numeric(data):
        return factor * data
    # Else, this object is a sequence. Return this sequence converted into a
    # Numpy array and then upscaled.
    else:
        return factor * nparray.from_iterable(data)
Esempio n. 7
0
def orient_counterclockwise(polygon: SequenceTypes) -> SequenceTypes:
    '''
    Positively reorient the passed polygon, returning a copy of this polygon
    whose vertices are **positively oriented** (i.e., sorted in
    counter-clockwise order).

    Parameters
    ----------
    polygon : SequenceTypes
        Two-dimensional sequence of all points defining the possibly non-convex
        two-dimensional polygon to be positively oriented such that:

        * The first dimension indexes each such point (in arbitrary order).
        * The second dimension indexes each coordinate of this point such that:

          * The first item is the X coordinate of this point.
          * The second item is the Y coordinate of this point.

        Note that this function expects the type of this sequence to define an
        ``__init__()`` method accepting a passed iterable as its first and only
        positional argument. Unsurprisingly, all builtin sequences (e.g.,
        :class:`tuple`, :class:`list`) *and* :mod:`numpy` sequences (e.g.,
        :class:`numpy.ndarray`) satisfy this requirement.

    Returns
    ----------
    SequenceTypes
        Copy of this polygon positively oriented. The type of this sequence is
        the same as that of the original polygon.
    '''

    # Avoid circular import dependencies.
    from betse.lib.numpy import nparray

    # If this sequence is *NOT* a polygon, raise an exception.
    die_unless_polygon(polygon)

    # Numpy array corresponding to this sequence. While polygon reorientation
    # is feasible in pure-Python, the Numpy-based approach is significantly
    # more efficient as the number of polygon edges increases.
    poly_verts = nparray.from_iterable(polygon)

    # Centre point of this polygon,
    poly_centre = poly_verts.mean(axis=0)

    # One-dimensional Numpy array indexing each vertex of this polygon such
    # that each element is the angle in radians between the positive X-axis and
    # that vertex, derived according to the classic mnemonic SOHCAHTOA:
    #
    #              opposite             (opposite)
    #              --------             (--------)
    #     tan(θ) = adjacent  -->  arctan(adjacent) = θ
    #
    # To sort vertices in a counter-clockwise rotation around the centre point
    # of this polygon rather than around the origin (i.e., the point (0, 0)),
    # each vertex is translated from the origin to this centre point *BEFORE*
    # obtaining this vertex's angle. Assuming standard orientation for a right
    # triangle sitting at the centre point of this polygon:
    #
    # * The opposite edge is the Y coordinate of the current vertex translated
    #   from the origin onto the centre point.
    # * The adjacent edge is the X coordinate of the current vertex translated
    #   from the origin onto the centre point.
    #
    # For safety, the np.arctan2() function intended exactly for this purpose
    # rather than the np.arctan() function intended for general-purpose
    # calculation is called. For further details, see:
    #     https://en.wikipedia.org/wiki/Atan2
    poly_angles = np.arctan2(poly_verts[:, 1] - poly_centre[1],
                             poly_verts[:, 0] - poly_centre[0])

    # One-dimensional Numpy array indexing the index of each vertex of this
    # polygon, sorted in counter-clockwise order.
    poly_verts_sorted_index = poly_angles.argsort()

    # Two-dimensional Numpy array of the polygon to be returned, containing all
    # vertices sorted in this order.
    poly_verts_sorted = poly_verts[poly_verts_sorted_index]

    # Return a sequence of the same type as the passed polygon.
    return nparray.to_iterable(array=poly_verts_sorted, cls=type(polygon))
Esempio n. 8
0
    def export_cell_series(self, phase: SimPhase,
                           conf: SimConfExportCSV) -> None:
        '''
        Save a plaintext file in comma-separated value (CSV) format containing
        several cell-specific time series (e.g., ion concentrations, membrane
        voltages, voltage-gated ion channel pump rates) for the single cell
        indexed ``plot cell index`` in the current simulation configuration.
        '''

        # 0-based index of the cell to serialize time data for.
        cell_index = phase.p.visual.single_cell_index

        # Sequence of key-value pairs containing all simulation data to be
        # exported for this cell, suitable for passing to the
        # OrderedArgsDict.__init__() method calleb below.
        csv_column_name_values = []

        # One-dimensional Numpy array of null data of the required length,
        # suitable for use as CSV column data for columns whose corresponding
        # simulation feature (e.g., deformations) is disabled.
        column_data_empty = np.zeros(len(phase.sim.time))

        # ................{ TIME STEPS                      }..................
        csv_column_name_values.extend(('time_s', phase.sim.time))

        # ................{ VMEM                            }..................
        csv_column_name_values.extend(
            ('Vmem_mV', self._get_cell_times_vmems(phase)))

        # ................{ VMEM ~ goldman                  }..................
        if phase.p.GHK_calc:
            vm_goldman = mathunit.upscale_units_milli([
                vm_GHK_time_cells[cell_index]
                for vm_GHK_time_cells in phase.sim.vm_GHK_time
            ])
        else:
            vm_goldman = column_data_empty

        csv_column_name_values.extend(('Goldman_Vmem_mV', vm_goldman))

        # ................{ Na K PUMP RATE                  }..................
        if phase.p.is_ecm:
            pump_rate = [
                pump_array[phase.cells.cell_to_mems[cell_index][0]]
                for pump_array in phase.sim.rate_NaKATP_time
            ]
        else:
            pump_rate = [
                pump_array[cell_index]
                for pump_array in phase.sim.rate_NaKATP_time
            ]

        csv_column_name_values.extend(('NaK-ATPase_Rate_mol/m2s', pump_rate))

        # ................{ ION CONCENTRATIONS              }..................
        # Create the header starting with cell concentrations.
        for i in range(len(phase.sim.ionlabel)):
            csv_column_name = 'cell_{}_mmol/L'.format(phase.sim.ionlabel[i])
            cc_m = [arr[i][cell_index] for arr in phase.sim.cc_time]
            csv_column_name_values.extend((csv_column_name, cc_m))

        # ................{ MEMBRANE PERMEABILITIES         }..................
        # Create the header starting with membrane permeabilities.
        for i in range(len(phase.sim.ionlabel)):
            if phase.p.is_ecm:
                dd_m = [
                    arr[i][phase.cells.cell_to_mems[cell_index][0]]
                    for arr in phase.sim.dd_time
                ]
            else:
                dd_m = [arr[i][cell_index] for arr in phase.sim.dd_time]

            csv_column_name = 'Dm_{}_m2/s'.format(phase.sim.ionlabel[i])
            csv_column_name_values.extend((csv_column_name, dd_m))

        # ................{ TRANSMEMBRANE CURRENTS          }..................
        if phase.p.is_ecm:
            Imem = [
                memArray[phase.cells.cell_to_mems[cell_index][0]]
                for memArray in phase.sim.I_mem_time
            ]
        else:
            Imem = [memArray[cell_index] for memArray in phase.sim.I_mem_time]

        csv_column_name_values.extend(('I_A/m2', Imem))

        # ................{ HYDROSTATIC PRESSURE            }..................
        p_hydro = [arr[cell_index] for arr in phase.sim.P_cells_time]
        csv_column_name_values.extend(('HydroP_Pa', p_hydro))

        # ................{ OSMOTIC PRESSURE                }..................
        if phase.p.deform_osmo:
            p_osmo = [arr[cell_index] for arr in phase.sim.osmo_P_delta_time]
        else:
            p_osmo = column_data_empty

        csv_column_name_values.extend(('OsmoP_Pa', p_osmo))

        # ................{ DEFORMATION                     }..................
        if (phase.p.deformation and phase.kind is SimPhaseKind.SIM):
            # Extract time-series deformation data for the plot cell:
            dx = nparray.from_iterable(
                [arr[cell_index] for arr in phase.sim.dx_cell_time])
            dy = nparray.from_iterable(
                [arr[cell_index] for arr in phase.sim.dy_cell_time])

            # Get the total magnitude.
            disp = mathunit.upscale_coordinates(np.sqrt(dx**2 + dy**2))
        else:
            disp = column_data_empty

        csv_column_name_values.extend(('Displacement_um', disp))

        # ................{ CSV EXPORT                      }..................
        # Ordered dictionary mapping from CSV column names to data arrays.
        csv_column_name_to_values = OrderedArgsDict(*csv_column_name_values)

        # Export this data to this CSV file.
        npcsv.write_csv(
            filename=self._get_csv_filename(
                phase=phase, basename_sans_filetype='ExportedData'),
            column_name_to_values=csv_column_name_to_values,
        )
Esempio n. 9
0
def to_iterable(
    # Mandatory parameters.
    iterable: IterableTypes,

    # Optional parameters.
    cls: ClassOrNoneTypes = None,
    item_cls: ClassOrNoneTypes = None,
) -> IterableTypes:
    '''
    Convert the passed input iterable into an output iterable of the passed
    iterable type and/or convert each item of this input iterable into an item
    of this output iterable of the passed item type.

    Specifically, if this iterable type is:

    * Either ``None`` *or* of the same type as the passed iterable type, then:

      * If this item type is non-``None``, this input iterable is converted
        into an instance of the same iterable type whose items are converted
        into instances of this item type.
      * Else, this input iterable is returned as is (i.e., unmodified).

    * A non-Numpy iterable (e.g., :class:`list`) and the passed iterable type
      is that of:

      * Another non-Numpy iterable (e.g., :class:`tuple`), then:

        * If this item type is non-``None``, this input iterable is converted
          into an instance of this iterable type whose items are converted into
          instances of this item type.
        * Else, this input iterable is merely converted into an instance of
          this iterable type.

        In either case, this output iterable's ``__init__`` method is required
        to accept this input iterable as a single positional argument.

      * A Numpy array, then:

        * If this item type is non-``None``, an exception is raised. Numpy
          scalar types map poorly to Python scalar types.
        * Else, this input iterable is converted to a Numpy array via the
          :func:`betse.lib.numpy.nparray.from_iterable` function.

    * A Numpy array, then:

      * If this item type is non-``None``, an exception is raised. Numpy
        scalar types map poorly to Python scalar types.
      * Else, this input iterable is converted to a Numpy array via the
        :func:`betse.lib.numpy.nparray.from_iterable` function.

    Parameters
    ----------
    iterable: IterableTypes
        Input iterable to be converted.
    cls : ClassOrNoneTypes
        Type of the output iterable to convert this input iterable into.
        Defaults to ``None``, in which case the same type as that of this input
        iterable is defaulted to.
    item_cls : ClassOrNoneTypes
        Type to convert each item of the output iterable into. Defaults to
        ``None``, in which case these items are preserved as is.

    Returns
    ----------
    IterableTypes
        Output iterable converted from this input iterable.

    Raises
    ----------
    BetseIterableException
        If converting either to or from a Numpy array *and* this item type is
        non-``None``.
    '''

    # Avoid importing third-party packages at the top level, for safety.
    from betse.lib.numpy import nparray
    from betse.util.type.cls import classes
    from betse.util.type.iterable import generators
    from numpy import ndarray

    # Type of the input iterable.
    iterable_src_cls = type(iterable)

    # Type of the output iterable.
    iterable_trg_cls = cls

    # If the caller requested no explicit type conversion...
    if iterable_trg_cls is None:
        # If the input iterable is a generator, default the type of the output
        # iterable to the optimally space- and time-efficient iterable: tuple.
        # Why? Because generators *CANNOT* be explicitly instantiated.
        if generators.is_generator(iterable):
            iterable_trg_cls = tuple
        # Else, the input iterable is *NOT* a generator and hence is assumed to
        # be of a standard type that *CAN* be explicitly instantiated. In this
        # case, default the type of the output iterable to this type.
        else:
            iterable_trg_cls = iterable_src_cls

    # if cls is not None else iterable_src_cls

    # If the input and output iterables are of the same type *AND* no item
    # conversion was requested, efficiently reduce to a noop.
    if iterable_src_cls is iterable_trg_cls and item_cls is None:
        return iterable

    # Else if the input iterable is a Numpy array...
    if iterable_src_cls is ndarray:
        # If the caller requested item conversion, raise an exception.
        if item_cls is not None:
            raise BetseIterableException(
                'Numpy array not convertible to item class "{}".'.format(
                    classes.get_name_unqualified(item_cls)))

        # Defer to logic elsewhere.
        return nparray.to_iterable(array=iterable, cls=iterable_trg_cls)

    # Else if the output iterable is a Numpy array, defer to logic elsewhere.
    if iterable_trg_cls is ndarray:
        # If the caller requested item conversion, raise an exception.
        if item_cls is not None:
            raise BetseIterableException(
                'Numpy array not convertible to item class "{}".'.format(
                    classes.get_name_unqualified(item_cls)))

        return nparray.from_iterable(iterable)

    # Else, neither the input or output iterables are Numpy arrays. In this
    # case, return an output iterable of the desired type containing the items
    # of this input iterable either...
    return (
        # Unmodified if no item conversion was requested *OR*...
        iterable_trg_cls(iterable) if item_cls is None else
        # Converted to this item type otherwise.
        iterable_trg_cls(item_cls(item) for item in iterable))
Esempio n. 10
0
    def __init__(self,
                 times_cells_centre: IterableOrNoneTypes = None,
                 times_grids_centre: IterableOrNoneTypes = None,
                 times_membranes_midpoint: IterableOrNoneTypes = None,
                 **kwargs) -> None:
        '''
        Initialize this cache.

        Parameters
        ----------
        times_cells_centre : optional[IterableTypes]
            Two-dimensional iterable of all cell data for a single cell
            membrane-specific modelled variable (e.g., cell electric field
            magnitude) for all simulation time steps, whose:

            #. First dimension indexes each sampled time step.
            #. Second dimension indexes each cell, such that each element is
               arbitrary cell data spatially situated at the centre of this cell
               for this time step.

            Defaults to ``None``, in which case at least one of the
            ``times_grids_centre`` and ``times_membranes_midpoint`` parameters
            must be non-``None``.
        times_grids_centre : optional[IterableTypes]
            Two-dimensional iterable of all grid data for a single
            intra- and/or extracellular modelled variable (e.g., total current
            density) for all simulation time steps, whose:

            #. First dimension indexes each sampled time step.
            #. Second dimension indexes each grid space (in either dimension),
               such that each element is arbitrary grid data spatially
               situated at the centre of this grid space for this time step.

            Defaults to ``None``, in which case at least one of the
            ``times_cells_centre`` and ``times_membranes_midpoint`` parameters
            must be non-``None``.
        times_membranes_midpoint : optional[IterableTypes]
            Two-dimensional iterable of all cell membrane data for a single
            cell membrane-specific modelled variable (e.g., cell membrane
            voltage) for all simulation time steps, whose:

            #. First dimension indexes each sampled time step.
            #. Second dimension indexes each cell membrane, such that each
               element is arbitrary cell membrane data spatially situated at
               the midpoint of this membrane for this time step.

            Defaults to ``None``, in which case at least one of the
            ``times_cells_centre`` and ``times_grids_centre`` parameters must
            be non-``None``.

        All remaining keyword arguments are passed as is to the superclass
        :meth:`SimPhaseCacheABC.__init__` method.

        Raises
        ----------
        BetseSimVectorException
            If exactly one of the ``times_cells_centre``,
            ``times_grids_centre``, and ``times_membranes_midpoint`` parameters
            is *not* passed.
        BetseSequenceException
            If any of the ``times_cells_centre``, ``times_grids_centre``, and
            ``times_membranes_midpoint`` parameters that are passed are empty
            (i.e., sequences of length 0).
        '''

        # Initialize our superclass.
        super().__init__(**kwargs)

        # If no iterable was passed, raise an exception.
        if (times_cells_centre is None and times_grids_centre is None
                and times_membranes_midpoint is None):
            raise BetseSimVectorException(
                'Parameters "times_cells_centre", "times_grids_centre", and '
                '"times_membranes_midpoint" not passed.')

        # Convert each passed iterable into a Numpy array for efficiency,
        # raising an exception if any such iterable is empty.
        if times_cells_centre is not None:
            times_cells_centre = nparray.from_iterable(times_cells_centre)
            sequences.die_if_empty(
                sequence=times_cells_centre,
                exception_message='Sequence "times_cells_centre" empty.')
        if times_grids_centre is not None:
            times_grids_centre = nparray.from_iterable(times_grids_centre)
            sequences.die_if_empty(
                sequence=times_grids_centre,
                exception_message='Sequence "times_grids_centre" empty.')
        if times_membranes_midpoint is not None:
            times_membranes_midpoint = nparray.from_iterable(
                times_membranes_midpoint)
            sequences.die_if_empty(
                sequence=times_membranes_midpoint,
                exception_message='Sequence "times_membranes_midpoint" empty.')

        # Classify all passed parameters.
        self._times_cells_centre = times_cells_centre
        self._times_grids_centre = times_grids_centre
        self._times_membranes_midpoint = times_membranes_midpoint