riverine
========

.. py:module:: riverine


Submodules
----------

.. toctree::
   :maxdepth: 1

   /autoapi/riverine/_version/index
   /autoapi/riverine/abbreviated/index
   /autoapi/riverine/actions/index
   /autoapi/riverine/components/index
   /autoapi/riverine/dictstructure/index
   /autoapi/riverine/echo/index
   /autoapi/riverine/experiments/index
   /autoapi/riverine/locations/index
   /autoapi/riverine/logging/index
   /autoapi/riverine/mixes/index
   /autoapi/riverine/printing/index
   /autoapi/riverine/quantitate/index
   /autoapi/riverine/references/index
   /autoapi/riverine/solver/index
   /autoapi/riverine/units/index
   /autoapi/riverine/util/index


Attributes
----------

.. autoapisummary::

   riverine.MultiFixedConcentration
   riverine.MultiFixedVolume
   riverine.DNAN
   riverine.nM
   riverine.uL
   riverine.uM
   riverine.ureg
   riverine.html_with_borders_tablefmt


Exceptions
----------

.. autoapisummary::

   riverine.VolumeError


Classes
-------

.. autoapisummary::

   riverine.AbstractAction
   riverine.EqualConcentration
   riverine.FixedConcentration
   riverine.FixedVolume
   riverine.ToConcentration
   riverine.FillToVolume
   riverine.AbstractComponent
   riverine.Component
   riverine.Strand
   riverine.Experiment
   riverine.WellPos
   riverine.Mix
   riverine.MixLine
   riverine.Reference


Functions
---------

.. autoapisummary::

   riverine.master_mix
   riverine.split_mix
   riverine.hydrate_and_measure_conc_and_dilute
   riverine.measure_conc_and_dilute
   riverine.load_reference
   riverine.Q_


Package Contents
----------------

.. py:class:: AbstractAction

   Abstract class defining an action in a mix recipe.


   .. py:property:: name
      :type: str

      :abstractmethod:



   .. py:method:: mix_volume_effect(_cache_key=None) -> (MixVolumeDep, riverine.units.DecimalQuantity)
      :abstractmethod:


      The effect of the action on the mix volume.

      :returns: * *MixVolumeDep* -- How the mix volume affects the action.
                * *DecimalQuantity* -- If MixVolumeDep is DETERMINES, the total mix volume that the action causes.
                  If MixVolumeDep is DEPENDS, NAN.
                  If MixVolumeDep is INDEPENDENT, the total volume that the action adds.



   .. py:method:: _get_name(_cache_key=None) -> str


   .. py:method:: tx_volume(mix_vol: riverine.units.DecimalQuantity = NAN_VOL, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None) -> riverine.units.DecimalQuantity

      The total volume transferred by the action to the sample.  May depend on the total mix volume.

      :param mix_vol: The mix volume.  Does not accept strings.



   .. py:method:: _mixlines(tablefmt: str | riverine.printing.TableFormat, mix_vol: riverine.units.DecimalQuantity, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None) -> riverine.units.Sequence[riverine.printing.MixLine]
      :abstractmethod:



   .. py:method:: all_components(mix_vol: riverine.units.DecimalQuantity, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None) -> pandas.DataFrame
      :abstractmethod:


      A dataframe containing all base components added by the action.

      :param mix_vol: The mix volume.  Does not accept strings.



   .. py:method:: with_experiment(experiment: riverine.experiments.Experiment, *, inplace: bool = True) -> T
      :abstractmethod:


      Returns a copy of the action updated from a experiment dataframe.



   .. py:method:: with_reference(reference: riverine.references.Reference, *, inplace: bool = False) -> T
      :abstractmethod:


      Returns a copy of the action updated from a reference dataframe.



   .. py:method:: dest_concentration(mix_vol: riverine.units.DecimalQuantity, actions: riverine.units.Sequence[AbstractAction] = (), cache_key=None) -> riverine.units.DecimalQuantity

      The destination concentration added to the mix by the action.

      :raises ValueError: There is no good definition for a single destination concentration
          (the action may add multiple components).



   .. py:method:: dest_concentrations(mix_vol: riverine.units.DecimalQuantity, actions: riverine.units.Sequence[AbstractAction] = (), cache_key=None) -> riverine.units.Sequence[riverine.units.DecimalQuantity]


   .. py:property:: components
      :type: list[riverine.components.AbstractComponent]

      :abstractmethod:



   .. py:property:: source_concentrations
      :type: list[riverine.units.DecimalQuantity]



   .. py:method:: _get_source_concentrations(_cache_key=None) -> list[riverine.units.DecimalQuantity]
      :abstractmethod:



   .. py:method:: each_volumes(mix_volume: riverine.units.DecimalQuantity, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None) -> list[riverine.units.DecimalQuantity]
      :abstractmethod:



   .. py:method:: _structure(d: dict[str, Any], experiment: riverine.experiments.Experiment) -> AbstractAction
      :classmethod:

      :abstractmethod:



   .. py:method:: _unstructure(experiment: riverine.experiments.Experiment | None) -> dict[str, Any]
      :abstractmethod:



.. py:class:: EqualConcentration(components: riverine.units.Sequence[riverine.components.AbstractComponent | str] | riverine.components.AbstractComponent | str, fixed_volume: str | riverine.units.DecimalQuantity, set_name: str | None = None, compact_display: bool = True, method: Literal['max_volume', 'min_volume', 'check'] | tuple[Literal['max_fill'], str] = 'min_volume', equal_conc: bool | str | None = None)

   Bases: :py:obj:`FixedVolume`


   An action adding an equal concentration of each component, without setting that concentration.

   Depending on the setting of
   `equal_conc`, it may require that the concentrations all be equal to begin with, or may treat the fixed
   transfer volume as the volume as the minimum or maximum volume to transfer, adjusting volumes of each
   strand to make this work and have them at equal destination concentrations.

   :param components: A list of :ref:`Components`.
   :param fixed_volume: A fixed volume for the action.  Input can be a string (eg, "5 µL") or a pint Quantity.  The interpretation
                        of this depends on equal_conc.
   :param set_name: The name of the mix.  If not set, name is based on components.
   :param compact_display: If True (default), the action tries to display compactly in mix recipes.  If False, it displays
                           each component as a separate line.
   :param method: If `"check"`, the action still transfers the same volume of each component, but will
                  raise a `ValueError` if this will not result in every component having the same concentration added
                  (ie, if they have different source concentrations).  If `"min_volume"`, the action will transfer *at least*
                  `fixed_volume` of each component, but will transfer more for components with lower source concentration,
                  so that the destination concentrations are all equal (but not fixed to a specific value).  If `"max_volume"`,
                  the action instead transfers *at most* `fixed_volume` of each component, tranferring less for higher
                  source concentration components.  If ('max_fill', buffer_name), the fixed volume is the maximum, while for
                  every component that is added at a lower volume, a corresponding volume of buffer is added to bring the total
                  volume of the two up to the fixed volume.
   :param >>> components = [:
   :param ...     Component("c1":
   :param "200 nM"):
   :param :
   :param ...     Component("c2":
   :param "200 nM"):
   :param :
   :param ...     Component("c3":
   :param "200 nM"):
   :param :
   :param ...     Component("c4":
   :param "100 nM"):
   :param ... ]:
   :param >>> print(Mix([EqualConcentration(components:
   :param "5 uL":
   :param method="min_volume")]:
   :param name="example")):
   :param Table:
   :type Table: Mix: example, Conc: 40.00 nM, Total Vol: 25.00 µl
   :param <BLANKLINE>:
   :param | Comp       | Src []    | Dest []   | #   | Ea Tx Vol   | Tot Tx Vol   | Loc   | Note   |:
   :param |:
   :type |: -----------|:----------|:----------|:----|:------------|:-------------|:------|:-------|
   :param | c1:
   :param c2:
   :param c3 | 200.00 nM | 40.00 nM  | 3   | 5.00 µl     | 15.00 µl     |       |        |:
   :param | c4         | 100.00 nM | 40.00 nM  | 1   | 10.00 µl    | 10.00 µl     |       |        |:
   :param >>> print(Mix([EqualConcentration(components:
   :param "5 uL":
   :param method="max_volume")]:
   :param name="example")):
   :param Table:
   :type Table: Mix: example, Conc: 40.00 nM, Total Vol: 12.50 µl
   :param <BLANKLINE>:
   :param | Comp       | Src []    | Dest []   | #   | Ea Tx Vol   | Tot Tx Vol   | Loc   | Note   |:
   :param |:
   :type |: -----------|:----------|:----------|:----|:------------|:-------------|:------|:-------|
   :param | c1:
   :param c2:
   :param c3 | 200.00 nM | 40.00 nM  | 3   | 2.50 µl     | 7.50 µl      |       |        |:
   :param | c4         | 100.00 nM | 40.00 nM  | 1   | 5.00 µl     | 5.00 µl      |       |        |:


   .. py:attribute:: __hash__


   .. py:attribute:: method
      :type:  Literal['max_volume', 'min_volume', 'check'] | tuple[Literal['max_fill'], str]
      :value: 'min_volume'



   .. py:property:: source_concentrations
      :type: list[riverine.units.DecimalQuantity]



   .. py:method:: _get_source_concentrations(_cache_key=None) -> list[riverine.units.DecimalQuantity]


   .. py:method:: each_volumes(mix_volume: riverine.units.DecimalQuantity = NAN_VOL, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None) -> list[riverine.units.DecimalQuantity]


   .. py:method:: tx_volume(mix_vol: riverine.units.DecimalQuantity = NAN_VOL, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None) -> riverine.units.DecimalQuantity

      The total volume transferred by the action to the sample.  May depend on the total mix volume.

      :param mix_vol: The mix volume.  Does not accept strings.



   .. py:method:: _mixlines(tablefmt: str | riverine.printing.TableFormat, mix_vol: riverine.units.DecimalQuantity, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None) -> list[riverine.printing.MixLine]


   .. py:method:: mix_volume_effect(_cache_key=None) -> (MixVolumeDep, riverine.units.DecimalQuantity)

      The effect of the action on the mix volume.

      :returns: * *MixVolumeDep* -- How the mix volume affects the action.
                * *DecimalQuantity* -- If MixVolumeDep is DETERMINES, the total mix volume that the action causes.
                  If MixVolumeDep is DEPENDS, NAN.
                  If MixVolumeDep is INDEPENDENT, the total volume that the action adds.



.. py:class:: FixedConcentration

   Bases: :py:obj:`ActionWithComponents`


   An action adding one or multiple components, with a set destination concentration per component (adjusting volumes).

   FixedConcentration adds a selection of components, with a specified destination concentration.

   :param components: A list of :ref:`Components`.
   :param fixed_concentration: A fixed concentration for the action.  Input can be a string (eg, "50 nM") or a pint Quantity.
   :param set_name: The name of the mix.  If not set, name is based on components.
   :param compact_display: If True (default), the action tries to display compactly in mix recipes.  If False, it displays
                           each component as a separate line.
   :param min_volume: Specifies a minimum volume that must be transferred per component.  Currently, this is for
                      validation only: it will cause a VolumeError to be raised if a volume is too low.

   :raises VolumeError: One of the volumes to transfer is less than the specified min_volume.

   .. rubric:: Examples

   >>> from riverine.mixes import *
   >>> components = [
   ...     Component("c1", "200 nM"),
   ...     Component("c2", "200 nM"),
   ...     Component("c3", "200 nM"),
   ...     Component("c4", "100 nM")
   ... ]

   >>> print(Mix([FixedConcentration(components, "20 nM")], name="example", fixed_total_volume="25 uL"))
   Table: Mix: example, Conc: 40.00 nM, Total Vol: 25.00 µl
   <BLANKLINE>
   | Comp       | Src []    | Dest []   | #   | Ea Tx Vol   | Tot Tx Vol   | Loc   | Note   |
   |:-----------|:----------|:----------|:----|:------------|:-------------|:------|:-------|
   | c1, c2, c3 | 200.00 nM | 20.00 nM  | 3   | 2.50 µl     | 7.50 µl      |       |        |
   | c4         | 100.00 nM | 20.00 nM  |     | 5.00 µl     | 5.00 µl      |       |        |
   | Buffer     |           |           |     |             | 12.50 µl     |       |        |
   | *Total:*   |           | 40.00 nM  |     |             | 25.00 µl     |       |        |


   .. py:attribute:: fixed_concentration
      :type:  riverine.units.DecimalQuantity


   .. py:attribute:: set_name
      :type:  str | None
      :value: None



   .. py:attribute:: compact_display
      :type:  bool
      :value: True



   .. py:attribute:: min_volume
      :type:  riverine.units.DecimalQuantity


   .. py:method:: dest_concentrations(mix_vol: riverine.units.DecimalQuantity = NAN_VOL, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None) -> list[riverine.units.DecimalQuantity]


   .. py:method:: each_volumes(mix_volume: riverine.units.DecimalQuantity = NAN_VOL, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None) -> list[riverine.units.DecimalQuantity]


   .. py:method:: _mixlines(tablefmt: str | riverine.printing.TableFormat, mix_vol: riverine.units.DecimalQuantity, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None) -> list[riverine.printing.MixLine]


   .. py:property:: name
      :type: str



   .. py:method:: mix_volume_effect(_cache_key=None) -> (MixVolumeDep, riverine.units.DecimalQuantity)

      The effect of the action on the mix volume.

      :returns: * *MixVolumeDep* -- How the mix volume affects the action.
                * *DecimalQuantity* -- If MixVolumeDep is DETERMINES, the total mix volume that the action causes.
                  If MixVolumeDep is DEPENDS, NAN.
                  If MixVolumeDep is INDEPENDENT, the total volume that the action adds.



.. py:class:: FixedVolume

   Bases: :py:obj:`ActionWithComponents`


   An action adding one or multiple components, with a set transfer volume.

   :param components: A list of :ref:`Components`.
   :param fixed_volume: A fixed volume for the action.  Input can be a string (eg, "5 µL") or a pint Quantity.  The interpretation
                        of this depends on equal_conc.
   :param set_name: The name of the mix.  If not set, name is based on components.
   :param compact_display: If True (default), the action tries to display compactly in mix recipes.  If False, it displays
                           each component as a separate line.

   .. rubric:: Examples

   >>> from riverine.mixes import *
   >>> components = [
   ...     Component("c1", "200 nM"),
   ...     Component("c2", "200 nM"),
   ...     Component("c3", "200 nM"),
   ... ]

   >>> print(Mix([FixedVolume(components, "5 uL")], name="example"))
   Table: Mix: example, Conc: 66.67 nM, Total Vol: 15.00 µl
   <BLANKLINE>
   | Comp       | Src []    | Dest []   |   # | Ea Tx Vol   | Tot Tx Vol   | Loc   | Note   |
   |:-----------|:----------|:----------|----:|:------------|:-------------|:------|:-------|
   | c1, c2, c3 | 200.00 nM | 66.67 nM  |   3 | 5.00 µl     | 15.00 µl     |       |        |


   .. py:attribute:: fixed_volume
      :type:  riverine.units.DecimalQuantity


   .. py:attribute:: set_name
      :type:  str | None
      :value: None



   .. py:attribute:: compact_display
      :type:  bool
      :value: True



   .. py:method:: dest_concentrations(mix_vol: riverine.units.DecimalQuantity = NAN_VOL, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None) -> list[riverine.units.DecimalQuantity]


   .. py:method:: each_volumes(mix_volume: riverine.units.DecimalQuantity = NAN_VOL, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None) -> list[riverine.units.DecimalQuantity]


   .. py:property:: name
      :type: str



   .. py:method:: _get_name(_cache_key=None) -> str


   .. py:method:: _mixlines(tablefmt: str | riverine.printing.TableFormat, mix_vol: riverine.units.DecimalQuantity, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None) -> list[riverine.printing.MixLine]


   .. py:method:: mix_volume_effect(_cache_key=None) -> (MixVolumeDep, riverine.units.DecimalQuantity)

      The effect of the action on the mix volume.

      :returns: * *MixVolumeDep* -- How the mix volume affects the action.
                * *DecimalQuantity* -- If MixVolumeDep is DETERMINES, the total mix volume that the action causes.
                  If MixVolumeDep is DEPENDS, NAN.
                  If MixVolumeDep is INDEPENDENT, the total volume that the action adds.



.. py:data:: MultiFixedConcentration

.. py:data:: MultiFixedVolume

.. py:class:: ToConcentration

   Bases: :py:obj:`ActionWithComponents`


   Add an amount of (non-mix) components to result in a fixed total concentration of each in the mix.

   An action adding an amount of components such that the concentration of each component in the mix will
   be at some target concentration.  Unlike FixedConcentration, which *adds* a certain concentration, this
   takes into account other contents of the mix, and only adds enough to reach a particular final
   concentration.


   .. py:attribute:: fixed_concentration
      :type:  riverine.units.DecimalQuantity


   .. py:attribute:: compact_display
      :type:  bool
      :value: True



   .. py:attribute:: min_volume
      :type:  riverine.units.DecimalQuantity


   .. py:method:: _othercomps(mix_vol: riverine.units.DecimalQuantity, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None)


   .. py:method:: dest_concentrations(mix_vol: riverine.units.DecimalQuantity, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None) -> riverine.units.Sequence[riverine.units.DecimalQuantity]


   .. py:method:: each_volumes(mix_volume: riverine.units.DecimalQuantity = NAN_VOL, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None) -> list[riverine.units.DecimalQuantity]


   .. py:method:: _mixlines(tablefmt: str | riverine.printing.TableFormat, mix_vol: riverine.units.DecimalQuantity, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None) -> list[riverine.printing.MixLine]


   .. py:method:: mix_volume_effect(_cache_key=None) -> (MixVolumeDep, riverine.units.DecimalQuantity)

      The effect of the action on the mix volume.

      :returns: * *MixVolumeDep* -- How the mix volume affects the action.
                * *DecimalQuantity* -- If MixVolumeDep is DETERMINES, the total mix volume that the action causes.
                  If MixVolumeDep is DEPENDS, NAN.
                  If MixVolumeDep is INDEPENDENT, the total volume that the action adds.



.. py:class:: FillToVolume

   Bases: :py:obj:`ActionWithComponents`


   Abstract class defining an action in a mix recipe.


   .. py:attribute:: target_total_volume
      :type:  riverine.units.DecimalQuantity


   .. py:method:: dest_concentrations(mix_vol: riverine.units.DecimalQuantity = NAN_VOL, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None) -> list[riverine.units.DecimalQuantity]


   .. py:method:: each_volumes(mix_volume: riverine.units.DecimalQuantity = NAN_VOL, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None) -> list[riverine.units.DecimalQuantity]


   .. py:method:: _mixlines(tablefmt: str | riverine.printing.TableFormat, mix_vol: riverine.units.DecimalQuantity, actions: riverine.units.Sequence[AbstractAction] = (), _cache_key=None) -> list[riverine.printing.MixLine]


   .. py:method:: mix_volume_effect(_cache_key=None) -> (MixVolumeDep, riverine.units.DecimalQuantity)

      The effect of the action on the mix volume.

      :returns: * *MixVolumeDep* -- How the mix volume affects the action.
                * *DecimalQuantity* -- If MixVolumeDep is DETERMINES, the total mix volume that the action causes.
                  If MixVolumeDep is DEPENDS, NAN.
                  If MixVolumeDep is INDEPENDENT, the total volume that the action adds.



.. py:class:: AbstractComponent

   Bases: :py:obj:`abc.ABC`


   Abstract class for a component in a mix.  Custom components that don't inherit from
   a concrete class should inherit from this class and implement the methods here.


   .. py:property:: name
      :type: str

      :abstractmethod:


      Name of the component.


   .. py:property:: location
      :type: tuple[str, riverine.locations.WellPos | None]



   .. py:property:: plate
      :type: str | None



   .. py:property:: is_mix
      :type: bool



   .. py:property:: well
      :type: riverine.locations.WellPos | None



   .. py:property:: _well_list
      :type: list[riverine.locations.WellPos]



   .. py:property:: volume
      :type: riverine.units.DecimalQuantity



   .. py:property:: concentration
      :type: riverine.units.DecimalQuantity

      :abstractmethod:


      (Source) concentration of the component as a pint Quantity.  NaN if undefined.


   .. py:method:: all_components() -> pandas.DataFrame
      :abstractmethod:


      A dataframe of all components.



   .. py:method:: with_reference(reference: riverine.references.Reference, *, inplace: bool = False) -> T
      :abstractmethod:



   .. py:method:: with_experiment(experiment: riverine.experiments.Experiment, *, inplace: bool = True) -> AbstractComponent
      :abstractmethod:



   .. py:method:: _structure(d: dict[str, Any], experiment: riverine.experiments.Experiment | None = None) -> AbstractComponent
      :classmethod:

      :abstractmethod:



   .. py:method:: _unstructure(experiment: riverine.experiments.Experiment | None = None) -> dict[str, Any]
      :abstractmethod:



   .. py:method:: printed_name(tablefmt: str | riverine.printing.TableFormat) -> str


   .. py:method:: _update_volumes(consumed_volumes: dict[str, riverine.units.DecimalQuantity] | None = None, made_volumes: dict[str, riverine.units.DecimalQuantity] | None = None, _cache_key=None) -> Tuple[dict[str, riverine.units.DecimalQuantity], dict[str, riverine.units.DecimalQuantity]]

      Given a



.. py:class:: Component

   Bases: :py:obj:`AbstractComponent`


   A single named component, potentially with a concentration and location.

   Location is stored as a `plate` and `well` property. `plate` is



   .. py:attribute:: name
      :type:  str

      Name of the component.


   .. py:method:: _get_name(_cache_key=None) -> str


   .. py:attribute:: concentration
      :type:  riverine.units.DecimalQuantity

      (Source) concentration of the component as a pint Quantity.  NaN if undefined.


   .. py:method:: _get_concentration(_cache_key=None) -> riverine.units.DecimalQuantity


   .. py:attribute:: plate
      :type:  str | None


   .. py:attribute:: well
      :type:  riverine.locations.WellPos | None


   .. py:attribute:: volume
      :type:  riverine.units.DecimalQuantity


   .. py:property:: location
      :type: tuple[str | None, riverine.locations.WellPos | None]



   .. py:method:: all_components_polars(_cache_key=None) -> polars.DataFrame


   .. py:method:: all_components() -> pandas.DataFrame

      A dataframe of all components.



   .. py:method:: _unstructure(experiment: riverine.experiments.Experiment | None = None) -> dict[str, Any]


   .. py:method:: _structure(d: dict[str, Any], experiment: riverine.experiments.Experiment | None = None) -> Component
      :classmethod:



   .. py:method:: with_experiment(experiment: riverine.experiments.Experiment, inplace: bool = True) -> AbstractComponent


   .. py:method:: with_reference(reference: riverine.references.Reference, inplace: bool = False) -> Component


.. py:class:: Strand

   Bases: :py:obj:`Component`


   A single named strand, potentially with a concentration, location and sequence.


   .. py:attribute:: sequence
      :type:  str | None
      :value: None



   .. py:method:: with_reference(reference: riverine.references.Reference, inplace: bool = False) -> Strand


.. py:class:: Experiment

   A class collecting many related mixes and components, allowing methods to be run that consider all of them
   together.

   Components can be referenced, and set, by name with [], and can be iterated through.


   .. py:attribute:: __hash__


   .. py:attribute:: components
      :type:  dict[str, riverine.components.AbstractComponent]


   .. py:attribute:: volume_checks
      :type:  bool
      :value: True



   .. py:attribute:: reference
      :type:  riverine.references.Reference | None


   .. py:attribute:: locations
      :type:  LocationDict


   .. py:method:: generate_picklist(_cache_key=None) -> Experiment.generate_picklist.PickList


   .. py:method:: add(component: riverine.components.AbstractComponent, *, check_volumes: bool | None = None, apply_reference: bool = True, check_existing: bool | Literal['equal'] = 'equal') -> Experiment


   .. py:method:: add_mix(mix_or_actions: riverine.mixes.Mix | Sequence[riverine.actions.AbstractAction] | riverine.actions.AbstractAction, name: str = '', test_tube_name: str | None = None, *, fixed_total_volume: riverine.units.DecimalQuantity | str | None = None, fixed_concentration: str | riverine.units.DecimalQuantity | None = None, buffer_name: str = 'Buffer', min_volume: riverine.units.DecimalQuantity | str = Q_('0.5', uL), check_volumes: bool | None = None, apply_reference: bool = True, check_existing: bool | Literal['equal'] = 'equal') -> Experiment

      Add a mix to the experiment, either as a Mix object, or by creating a new Mix.

      Either the first argument should be a Mix, or arguments should be passed as for
      initializing a Mix.

      If check_volumes is True (by default), the mix will be added to the experiment, and
      volumes checked.  If the mix causes a volume usage problem, it will not be added to
      the Experiment, and a VolumeError will be raised.

      If check_existing is True (by default), then a exception is raised if the experiment
      already contains a mix with the name `name`. Otherwise, the existing mix is replaced
      with the new mix.



   .. py:method:: __setitem__(name: str, mix: riverine.components.AbstractComponent) -> None


   .. py:method:: get(key: str, default=None)


   .. py:method:: __getitem__(name: str) -> riverine.components.AbstractComponent


   .. py:method:: __delitem__(name: str) -> None


   .. py:method:: __contains__(name: str) -> bool


   .. py:method:: remove_mix(name: str) -> None

      Remove a mix from the experiment, referenced by name,



   .. py:method:: remove(name: str) -> None

      Remove a mix from the experiment, referenced by name,



   .. py:method:: __len__() -> int


   .. py:method:: __iter__() -> Iterator[riverine.components.AbstractComponent]


   .. py:method:: consumed_and_produced_volumes() -> Mapping[str, Tuple[riverine.units.DecimalQuantity, riverine.units.DecimalQuantity]]


   .. py:method:: check_volumes(showall: bool = False, display: bool = True, raise_error: bool = False) -> str | None

      Check to ensure that consumed volumes are less than made volumes.



   .. py:method:: _unstructure() -> dict[str, Any]

      Create a dict representation of the Experiment.



   .. py:method:: _structure(d: dict[str, Any]) -> Experiment
      :classmethod:


      Create an Experiment from a dict representation.



   .. py:method:: load(filename_or_stream: str | os.PathLike | TextIO) -> Experiment
      :classmethod:


      Load an experiment from a JSON-formatted file created by Experiment.save.



   .. py:method:: resolve_components() -> None

      Resolve string/blank-component components in mixes, searching through the mixes
      in the experiment.  FIXME Add used mixes to the experiment if they are not already there.



   .. py:method:: save(filename_or_stream: str | os.PathLike | TextIO) -> None

      Save an experiment to a JSON-formatted file.

      Tries to store each component/mix only once, with other mixes referencing those components.



   .. py:method:: use_reference(reference: riverine.references.Reference) -> Experiment

      Apply a Reference, in place, to all components in the Experiment.



.. py:class:: WellPos(ref_or_row: int, col: int, /, *, platesize: Literal[96, 384] = 384)
              WellPos(ref_or_row: str, col: None = None, /, *, platesize: Literal[96, 384] = 384)

   A Well reference, allowing movement in various directions and bounds checking.

   This uses 1-indexed row and col, in order to match usual practice.  It can take either
   a standard well reference as a string, or two integers for the row and column.


   .. py:attribute:: row
      :type:  int


   .. py:attribute:: col
      :type:  int


   .. py:attribute:: platesize
      :type:  Literal[96, 384]
      :value: 384



   .. py:method:: _validate_row(v: int) -> None


   .. py:method:: _validate_col(v: int) -> None


   .. py:method:: __str__() -> str


   .. py:method:: __repr__() -> str


   .. py:method:: __eq__(other: object) -> bool


   .. py:method:: key_byrow() -> tuple[int, int]

      Get a tuple (row, col) key that can be used for ordering by row.



   .. py:method:: key_bycol() -> tuple[int, int]

      Get a tuple (col, row) key that can be used for ordering by column.



   .. py:method:: next_byrow() -> WellPos

      Get the next well, moving right along rows, then down.



   .. py:method:: next_bycol() -> WellPos

      Get the next well, moving down along columns, and then to the right.



   .. py:method:: is_last() -> bool

      :return:
          whether WellPos is the last well on this type of plate



   .. py:method:: advance(order: Literal['row', 'col'] = 'col') -> WellPos

      Advances to the "next" well position. Default is column-major order, i.e.,
      A1, B1, C1, D1, E1, F1, G1, H1, A2, B2, ...
      To switch to row-major order, select `order` as `'row'`, i.e.,
      A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, B1, B2, ...

      :return:
          new WellPos representing the next well position



.. py:class:: Mix(*args, **kwargs)

   Bases: :py:obj:`riverine.components.AbstractComponent`


   Class denoting a Mix, a collection of source components mixed to
   some volume or concentration.


   .. py:attribute:: __hash__


   .. py:attribute:: actions
      :type:  riverine.units.Sequence[riverine.actions.AbstractAction]


   .. py:attribute:: name
      :type:  str
      :value: ''


      Name of the component.


   .. py:attribute:: test_tube_name
      :type:  str | None

      A short name, eg, for labelling a test tube.


   .. py:attribute:: fixed_concentration
      :type:  str | riverine.units.DecimalQuantity | None


   .. py:attribute:: reference
      :type:  riverine.references.Reference | None
      :value: None



   .. py:attribute:: min_volume
      :type:  riverine.units.DecimalQuantity


   .. py:attribute:: plate
      :type:  str | None


   .. py:attribute:: well
      :type:  riverine.locations.WellPos | None


   .. py:property:: is_mix
      :type: bool



   .. py:property:: fixed_total_volume
      :type: riverine.units.DecimalQuantity



   .. py:property:: buffer_name
      :type: str



   .. py:method:: __eq__(other: object) -> bool


   .. py:method:: __attrs_post_init__() -> None


   .. py:method:: printed_name(tablefmt: str | tabulate.TableFormat) -> str


   .. py:property:: concentration
      :type: riverine.units.DecimalQuantity


      Effective concentration of the mix.  Calculated in order:

      1. If the mix has a fixed concentration, then that concentration.
      2. If `fixed_concentration` is a string, then the final concentration of
         the component with that name.
      3. If `fixed_concentration` is none, then the final concentration of the first
         mix component.


   .. py:method:: _get_concentration(_cache_key=None) -> riverine.units.DecimalQuantity


   .. py:property:: total_volume
      :type: riverine.units.DecimalQuantity


      Total volume of the mix.  If the mix has a fixed total volume, then that,
      otherwise, the sum of the transfer volumes of each component.


   .. py:method:: _get_total_volume(_cache_key=None) -> riverine.units.DecimalQuantity


   .. py:property:: buffer_volume
      :type: riverine.units.Quantity


      The volume of buffer to be added to the mix, in addition to the components.


   .. py:method:: _get_buffer_volume(_cache_key=None) -> riverine.units.Quantity


   .. py:method:: table(tablefmt: tabulate.TableFormat | str = 'pipe', raise_failed_validation: bool = False, stralign='default', missingval='', showindex='default', disable_numparse=False, colalign=None, _cache_key=None) -> str

      Generate a table describing the mix.

      :param tablefmt: The output format for the table.
      :param validate: Ensure volumes make sense.



   .. py:method:: mixlines(tablefmt: str | tabulate.TableFormat = 'pipe', _cache_key=None) -> list[riverine.printing.MixLine]


   .. py:method:: has_fixed_concentration_action() -> bool


   .. py:method:: has_fixed_total_volume() -> bool


   .. py:method:: validate(tablefmt: str | tabulate.TableFormat | None = None, mixlines: riverine.units.Sequence[riverine.printing.MixLine] | None = None, raise_errors: bool = False, _cache_key=None) -> list[riverine.units.VolumeError]


   .. py:method:: all_components_polars(_cache_key=None) -> polars.DataFrame


   .. py:method:: all_components() -> pandas.DataFrame

      Return a Series of all component names, and their concentrations (as pint nM).



   .. py:method:: _repr_markdown_() -> str


   .. py:method:: _repr_html_() -> str


   .. py:method:: infoline(_cache_key=None) -> str


   .. py:method:: __repr__() -> str


   .. py:method:: __str__() -> str


   .. py:method:: with_experiment(experiment: riverine.experiments.Experiment, *, inplace: bool = True) -> Mix


   .. py:method:: with_reference(reference: riverine.references.Reference, *, inplace: bool = True) -> Mix


   .. py:property:: location
      :type: tuple[str, riverine.locations.WellPos | None]



   .. py:method:: vol_to_tube_names(tablefmt: str | tabulate.TableFormat = 'pipe', validate: bool = True) -> dict[riverine.units.DecimalQuantity, list[str]]

      :return:
           dict mapping a volume `vol` to a list of names of strands in this mix that should be pipetted
           with volume `vol`



   .. py:method:: _tube_map_from_mixline(mixline: riverine.printing.MixLine) -> str


   .. py:method:: tubes_markdown(tablefmt: str | tabulate.TableFormat = 'pipe') -> str

      :param tablefmt: table format (see :meth:`PlateMap.to_table` for description)

      :returns: * a Markdown (or other format according to `tablefmt`)
                * *string indicating which strands in test tubes to pipette, grouped by the volume*
                * *of each*



   .. py:method:: display_instructions(plate_type: riverine.locations.PlateType = PlateType.wells96, raise_failed_validation: bool = False, combine_plate_actions: bool = True, well_marker: None | str | Callable[[str], str] = None, title_level: Literal[1, 2, 3, 4, 5, 6] = 3, warn_unsupported_title_format: bool = True, tablefmt: str | tabulate.TableFormat = 'unsafehtml', include_plate_maps: bool = True) -> None

      Displays in a Jupyter notebook the result of calling :meth:`Mix.instructions()`.

      :param plate_type: 96-well or 384-well plate; default is 96-well.
      :param raise_failed_validation: If validation fails (volumes don't make sense), raise an exception.
      :param combine_plate_actions: If True, then if multiple actions in the Mix take the same volume from the same plate,
                                    they will be combined into a single :class:`PlateMap`.
      :param well_marker: By default the strand's name is put in the relevant plate entry. If `well_marker` is specified
                          and is a string, then that string is put into every well with a strand in the plate map instead.
                          This is useful for printing plate maps that just put,
                          for instance, an `'X'` in the well to pipette (e.g., specify ``well_marker='X'``),
                          e.g., for experimental mixes that use only some strands in the plate.
                          To enable the string to depend on the well position
                          (instead of being the same string in every well), `well_marker` can also be a function
                          that takes as input a string representing the well (such as ``"B3"`` or ``"E11"``),
                          and outputs a string. For example, giving the identity function
                          ``mix.to_table(well_marker=lambda x: x)`` puts the well address itself in the well.
      :param title_level: The "title" is the first line of the returned string, which contains the plate's name
                          and volume to pipette. The `title_level` controls the size, with 1 being the largest size,
                          (header level 1, e.g., # title in Markdown or <h1>title</h1> in HTML).
      :param warn_unsupported_title_format: If True, prints a warning if `tablefmt` is a currently unsupported option for the title.
                                            The currently supported formats for the title are 'github', 'html', 'unsafehtml', 'rst',
                                            'latex', 'latex_raw', 'latex_booktabs', "latex_longtable". If `tablefmt` is another valid
                                            option, then the title will be the Markdown format, i.e., same as for `tablefmt` = 'github'.
      :param tablefmt: By default set to `'github'` to create a Markdown table. For other options see
                       https://github.com/astanin/python-tabulate#readme
      :param include_plate_maps: If True, include plate maps as part of displayed instructions, otherwise only include the
                                 more compact mixing table (which is always displayed regardless of this parameter).

      :returns: * pipetting instructions in the form of strings combining results of :meth:`Mix.table` and
                * :meth:`Mix.plate_maps`



   .. py:method:: generate_picklist(experiment: riverine.experiments.Experiment | None, _cache_key=None) -> Mix.generate_picklist.PickList | None

      :param experiment: experiment to use for generating picklist

      :rtype: picklist for the mix



   .. py:method:: instructions(*, plate_type: riverine.locations.PlateType = PlateType.wells96, raise_failed_validation: bool = False, combine_plate_actions: bool = True, well_marker: None | str | Callable[[str], str] = None, title_level: Literal[1, 2, 3, 4, 5, 6] = 3, warn_unsupported_title_format: bool = True, tablefmt: str | tabulate.TableFormat = 'pipe', include_plate_maps: bool = True) -> str

      Returns string combiniing the string results of calling :meth:`Mix.table` and
      :meth:`Mix.plate_maps` (then calling :meth:`PlateMap.to_table` on each :class:`PlateMap`).

      :param plate_type: 96-well or 384-well plate; default is 96-well.

      raise_failed_validation:
          If validation fails (volumes don't make sense), raise an exception.

      combine_plate_actions:
          If True, then if multiple actions in the Mix take the same volume from the same plate,
          they will be combined into a single :class:`PlateMap`.

      well_marker:
          By default the strand's name is put in the relevant plate entry. If `well_marker` is specified
          and is a string, then that string is put into every well with a strand in the plate map instead.
          This is useful for printing plate maps that just put,
          for instance, an `'X'` in the well to pipette (e.g., specify ``well_marker='X'``),
          e.g., for experimental mixes that use only some strands in the plate.
          To enable the string to depend on the well position
          (instead of being the same string in every well), `well_marker` can also be a function
          that takes as input a string representing the well (such as ``"B3"`` or ``"E11"``),
          and outputs a string. For example, giving the identity function
          ``mix.to_table(well_marker=lambda x: x)`` puts the well address itself in the well.

      title_level:
          The "title" is the first line of the returned string, which contains the plate's name
          and volume to pipette. The `title_level` controls the size, with 1 being the largest size,
          (header level 1, e.g., # title in Markdown or <h1>title</h1> in HTML).

      warn_unsupported_title_format:
          If True, prints a warning if `tablefmt` is a currently unsupported option for the title.
          The currently supported formats for the title are 'github', 'html', 'unsafehtml', 'rst',
          'latex', 'latex_raw', 'latex_booktabs', "latex_longtable". If `tablefmt` is another valid
          option, then the title will be the Markdown format, i.e., same as for `tablefmt` = 'github'.

      tablefmt:
          By default set to `'github'` to create a Markdown table. For other options see
          https://github.com/astanin/python-tabulate#readme

      include_plate_maps:
          If True, include plate maps as part of displayed instructions, otherwise only include the
          more compact mixing table (which is always displayed regardless of this parameter).

      :returns: * pipetting instructions in the form of strings combining results of :meth:`Mix.table` and
                * :meth:`Mix.plate_maps`



   .. py:method:: plate_maps(plate_type: riverine.locations.PlateType = PlateType.wells96, validate: bool = True, combine_plate_actions: bool = True) -> list[PlateMap]

      Similar to :meth:`table`, but indicates only the strands to mix from each plate,
      in the form of a :class:`PlateMap`.

      NOTE: this ignores any strands in the :class:`Mix` that are in test tubes. To get a list of strand
      names in test tubes, call :meth:`Mix.vol_to_tube_names` or :meth:`Mix.tubes_markdown`.

      By calling :meth:`PlateMap.to_markdown` on each plate map,
      one can create a Markdown representation of each plate map, for example,

      .. code-block::

          plate 1, 5 uL each
          |     | 1    | 2      | 3      | 4    | 5        | 6   | 7   | 8   | 9   | 10   | 11   | 12   |
          |-----|------|--------|--------|------|----------|-----|-----|-----|-----|------|------|------|
          | A   | mon0 | mon0_F |        | adp0 |          |     |     |     |     |      |      |      |
          | B   | mon1 | mon1_Q | mon1_F | adp1 | adp_sst1 |     |     |     |     |      |      |      |
          | C   | mon2 | mon2_F | mon2_Q | adp2 | adp_sst2 |     |     |     |     |      |      |      |
          | D   | mon3 | mon3_Q | mon3_F | adp3 | adp_sst3 |     |     |     |     |      |      |      |
          | E   | mon4 |        | mon4_Q | adp4 | adp_sst4 |     |     |     |     |      |      |      |
          | F   |      |        |        | adp5 |          |     |     |     |     |      |      |      |
          | G   |      |        |        |      |          |     |     |     |     |      |      |      |
          | H   |      |        |        |      |          |     |     |     |     |      |      |      |

      or, with the `well_marker` parameter of :meth:`PlateMap.to_markdown` set to ``'X'``, for instance
      (in case you don't need to see the strand names and just want to see which wells are marked):

      .. code-block::

          plate 1, 5 uL each
          |     | 1   | 2   | 3   | 4   | 5   | 6   | 7   | 8   | 9   | 10   | 11   | 12   |
          |-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|------|------|------|
          | A   | *   | *   |     | *   |     |     |     |     |     |      |      |      |
          | B   | *   | *   | *   | *   | *   |     |     |     |     |      |      |      |
          | C   | *   | *   | *   | *   | *   |     |     |     |     |      |      |      |
          | D   | *   | *   | *   | *   | *   |     |     |     |     |      |      |      |
          | E   | *   |     | *   | *   | *   |     |     |     |     |      |      |      |
          | F   |     |     |     | *   |     |     |     |     |     |      |      |      |
          | G   |     |     |     |     |     |     |     |     |     |      |      |      |
          | H   |     |     |     |     |     |     |     |     |     |      |      |      |

      :param plate_type: 96-well or 384-well plate; default is 96-well.
      :param validate: Ensure volumes make sense.
      :param combine_plate_actions: If True, then if multiple actions in the Mix take the same volume from the same plate,
                                    they will be combined into a single :class:`PlateMap`.

      :rtype: A list of all plate maps.



   .. py:method:: _plate_map_from_mixline(mixline: riverine.printing.MixLine, plate_type: riverine.locations.PlateType, existing_plate_map: PlateMap | None) -> PlateMap


   .. py:method:: _update_volumes(consumed_volumes: dict[str, riverine.units.Quantity] | None = None, made_volumes: dict[str, riverine.units.Quantity] | None = None, _cache_key=None) -> Tuple[dict[str, riverine.units.Quantity], dict[str, riverine.units.Quantity]]

      Given a



   .. py:method:: _unstructure(experiment: riverine.experiments.Experiment | None = None) -> dict[str, Any]


   .. py:method:: _structure(d: dict[str, Any], experiment: riverine.experiments.Experiment | None = None) -> Mix
      :classmethod:



.. py:class:: MixLine

   Class for handling a line of a (processed) mix recipe.

   Each line should represent a single step, or series of similar steps (same volume per substep)
   in the mixing process.

   :param names: A list of component names.  For a single step, use [name].
   :param source_conc: The source concentration; may not be provided (will be left blank), or be a descriptive string.
   :param dest_conc: The destination/target concentration; may not be provided (will be left blank), or be a descriptive string.
   :param total_tx_vol: The total volume added to the mix by the step.  If zero, the amount will still be included in tables.
                        If None, the amount will be blank.  If provided, and the line is not fake, the value must be correct
                        and interpretable for calculations involving the mix.
   :param number: The number of components added / subste
   :param each_tx_vol: The volume per component / substep.  May be omitted, or a descriptive string.
   :param plate: The plate name for the mix, a descriptive string for location / source type (eg, "tube") or None (omitted).
                 A single MixLine, at present, should not involve multiple plates.
   :param wells: A list of wells for the components in a plate.  If the components are not in a plate, this must be an
                 empty list.  This *does not* parse strings; wells must be provided as WellPos instances.
   :param note: A note to add for the line
   :param fake: Denotes that the line is not a real step, eg, for a summary/total information line.  The line
                will be distinguished in some way in tables (eg, italics) and will not be included in calculations.


   .. py:attribute:: names
      :type:  list[str]


   .. py:attribute:: source_conc
      :type:  riverine.units.DecimalQuantity | str | None
      :value: None



   .. py:attribute:: dest_conc
      :type:  riverine.units.DecimalQuantity | str | None
      :value: None



   .. py:attribute:: total_tx_vol
      :type:  riverine.units.DecimalQuantity


   .. py:attribute:: number
      :type:  int
      :value: 1



   .. py:attribute:: each_tx_vol
      :type:  riverine.units.DecimalQuantity


   .. py:attribute:: plate
      :type:  str
      :value: ''



   .. py:attribute:: wells
      :type:  list[riverine.locations.WellPos]


   .. py:attribute:: note
      :type:  str | None
      :value: None



   .. py:attribute:: fake
      :type:  bool
      :value: False



   .. py:method:: __attrs_post_init__()


   .. py:method:: _check_wells(_: str, v: Any) -> None


   .. py:method:: _check_names(_: str, v: Any) -> None


   .. py:method:: location(tablefmt: str | tabulate.TableFormat = 'pipe', split: bool = True) -> tuple[str | list[str], list[int]]

      A formatted string (according to `tablefmt`) for the location of the component/components.



   .. py:method:: toline(incea: bool, tablefmt: str | tabulate.TableFormat = 'pipe') -> riverine.units.Sequence[str]


.. py:function:: master_mix(mixes: Iterable[Mix], name: str = 'master mix', excess: float | riverine.units.Decimal = Decimal(0.05), exclude_shared_components: Iterable[str | riverine.components.Component] = ()) -> tuple[Mix, list[Mix]]

   Create a "master mix" useful for saving pipetting steps when creating :any:`Mix`'s in `mixes`
   by grouping components shared among each :any:`Mix`'s in `mixes` into a single large master mix
   from which the shared components can be pipetted to create the downstream mixes.

   Components are considered "shared" if they appear in *all* :any:`Mix`'s in `mixes`.

   To ensure sufficient volume for the last mix when the number of mixes is large (due to slight pipetting
   error from the master mix adding up over many steps), the parameter `excess`
   can be used to control how much of a slight excess of necessary volume is included in the master mix.

   Shared Components may be excluded from the master mix by putting them or their names in the parameter
   `exclude_shared_components`.

   Example:

   .. code-block:: python

       # staple mix to be shared in all mixes
       staples = [Strand(f"stap{i}", concentration="1uM") for i in range(5)]
       staple_mix = Mix(
           actions=[FixedConcentration(components=staples, fixed_concentration="100 nM")],
           name="staple mix",
       )

       # "adapter" mixes that are different between mixes
       num_variants = 3
       adapter_mixes = {}
       for adp_idx in range(num_variants):
           adapters = [Strand(f'adp_{adp_idx}_{i}', concentration="1uM") for i in range(5)]
           adapter_mix = Mix(
               actions=[FixedConcentration(components=adapters, fixed_concentration="50 nM")],
               name=f"adapters {adp_idx} mix",
           )
           adapter_mixes[adp_idx] = adapter_mix

       m13 = Strand("m13 100nM", concentration="100 nM")
       mixes = [Mix(
           actions=[
               FixedConcentration(components=[m13], fixed_concentration=f"1 nM"),
               FixedConcentration(components=[staple_mix], fixed_concentration=f"10 nM"),
               FixedConcentration(components=[adapter_mixes[adp_idx]], fixed_concentration=f"10 nM"),
           ],
           name="mm",
           fixed_total_volume=f"100 uL",
       ) for adp_idx, adapter_mix in adapter_mixes.items()]
       mm, final_mixes = master_mix(mixes=mixes, name='origami master mix', excess=0.1)

       print(mm.instructions())
       for mix in final_mixes:
           print(mix.instructions())

   This should print the following. Note that only 63 uL of master mix are strictly required, but
   the total master mix volume is 10% higher (69.3 uL) due to the parameter `excess` = 0.1.

   .. code-block::

       ## Mix "origami master mix":
       | Component   | [Src]     | [Dest]     | #   | Ea Tx Vol   | Tot Tx Vol   | Location  | Note  |
       |:------------|:----------|:-----------|:----|:------------|:-------------|:----------|:------|
       | staple mix  | 100.00 nM | 47.62 nM   |     | 33.00 µl    | 33.00 µl     |           |       |
       | m13 100nM   | 100.00 nM | 4.76 nM    |     | 3.30 µl     | 3.30 µl      |           |       |
       | 10x buffer  | 100.00 mM | 47.62 mM   |     | 33.00 µl    | 33.00 µl     |           |       |
       | Buffer      |           |            |     | 0.00 µl     | 0.00 µl      |           |       |
       | *Total:*    |           | *47.62 nM* | *4* |             | *69.30 µl*   |           |       |

       Aliquot 21.00 µl from this mix into 3 different test tubes.

       ## Mix "mix0":
       | Component          | [Src]     | [Dest]     | #   | Ea Tx Vol   | Tot Tx Vol   | Location  | Note  |
       |:-------------------|:----------|:-----------|:----|:------------|:-------------|:----------|:------|
       | origami master mix | 47.62 nM  | 10.00 nM   |     | 21.00 µl    | 21.00 µl     |           |       |
       | Mg++               | 125.00 mM | 12.50 mM   |     | 10.00 µl    | 10.00 µl     |           |       |
       | adapters 0 mix     | 50.00 nM  | 20.00 nM   |     | 40.00 µl    | 40.00 µl     |           |       |
       | Buffer             |           |            |     | 29.00 µl    | 29.00 µl     |           |       |
       | *Total:*           |           | *10.00 nM* | *4* |             | *100.00 µl*  |           |       |

       ## Mix "mix1":
       | Component          | [Src]     | [Dest]     | #   | Ea Tx Vol   | Tot Tx Vol   | Location  | Note  |
       |:-------------------|:----------|:-----------|:----|:------------|:-------------|:----------|:------|
       | origami master mix | 47.62 nM  | 10.00 nM   |     | 21.00 µl    | 21.00 µl     |           |       |
       | Mg++               | 125.00 mM | 12.50 mM   |     | 10.00 µl    | 10.00 µl     |           |       |
       | adapters 1 mix     | 55.00 nM  | 20.00 nM   |     | 36.36 µl    | 36.36 µl     |           |       |
       | Buffer             |           |            |     | 32.64 µl    | 32.64 µl     |           |       |
       | *Total:*           |           | *10.00 nM* | *4* |             | *100.00 µl*  |           |       |

       ## Mix "mix2":
       | Component          | [Src]     | [Dest]     | #   | Ea Tx Vol   | Tot Tx Vol   | Location  | Note  |
       |:-------------------|:----------|:-----------|:----|:------------|:-------------|:----------|:------|
       | origami master mix | 47.62 nM  | 10.00 nM   |     | 21.00 µl    | 21.00 µl     |           |       |
       | Mg++               | 125.00 mM | 12.50 mM   |     | 10.00 µl    | 10.00 µl     |           |       |
       | adapters 2 mix     | 60.00 nM  | 20.00 nM   |     | 33.33 µl    | 33.33 µl     |           |       |
       | Buffer             |           |            |     | 35.67 µl    | 35.67 µl     |           |       |
       | *Total:*           |           | *10.00 nM* | *4* |             | *100.00 µl*  |           |       |


   :param mixes: the list of :any:`Mix`'s of which to calculate a shared master mix
   :param name: name of the master mix
   :param excess: fraction of "excess" volume to include in master mix to ensure sufficient volume in all downstream
                  mixes; see parameter `excess` of :func:`split_mix` for explanation
   :param exclude_shared_components: names of shared components (or Components themselves) to exclude from master mix;
                                     raises exception if any element of `exclude_shared_components` is not shared by all :any:`Mix`'s
                                     in the parameter `mixes`

   :returns: * pair `(master_mix, final_mixes)`, where `master_mix` is the master mix to use in
             * downstream `final_mixes`. Length of `final_mixes` is the same as parameter `mixes`, and
             * they use the same names, but each :any:`Mix` in `final_mixes` will be created by a single
             * pipetting step from `master_mix` rather than individual pipetting steps for each shared component.


.. py:function:: split_mix(mix: Mix, num_tubes: int | None = None, names: Iterable[str] | None = None, excess: float | riverine.units.Decimal = Decimal(0.05)) -> Mix

   A "split mix" is a :any:`Mix` that involves creating a large volume mix and splitting it into several
   test tubes with identical contents. The advantage of specifying a split mix is that one can give
   the desired volumes/concentrations in the individual test tubes (post splitting) and the number of
   test tubes, and the correct amounts in the larger mix will automatically be calculated.

   The :meth:`Mix.instructions` method of a split mix includes the additional instruction at the end
   to aliquot from the larger mix.

   :param mix: The :any:`Mix` object describing what each
               individual smaller test tube should contain after the split.
   :param num_tubes: The number of test tubes into which to split the large mix. Should not be specified if `names`
                     is specified; in that case `num_tubes` is assumed to be the number of strings in `names`.
   :param excess: A fraction (between 0 and 1) indicating how much extra of the large mix to make. This is useful
                  when `num_tubes` is large, since the aliquots prior to the last test tube may take a small amount
                  of extra volume, resulting in the final test tube receiving significantly less volume if the
                  large mix contained only just enough total volume.

                  For example, if the total volume is 100 uL and `num_tubes` is 20, then each aliquot
                  from the large mix to test tubes would be 100/20 = 5 uL. But if due to pipetting imprecision 5.05 uL
                  is actually taken, then the first 19 aliquots will total to 19*5.05 = 95.95 uL, so there will only be
                  100 - 95.95 = 4.05 uL left for the last test tube. But by setting `excess` to 0.05,
                  then to make 20 test tubes of 5 uL each, we would have 5*20*1.05 = 105 uL total, and in this case
                  even assuming pipetting error resulting in taking 95.95 uL for the first 19 samples, there is still
                  105 - 95.95 = 9.05 uL left, more than enough for the 20'th test tube.

                  Note: using `excess` > 0 means than the test tube with the large mix should *not* be
                  reused as one of the final test tubes, since it will have too much volume at the end.
   :param names: Names of smaller individual test tubes (will be printed in instructions).

   :returns: * A "large" mix, from which `num_tubes` aliquots can be made to create each of the identical
             * *"small" mixes.*


.. py:function:: hydrate_and_measure_conc_and_dilute(nmol: float | str | riverine.units.DecimalQuantity, target_conc_high: float | str | riverine.units.DecimalQuantity, target_conc_low: float | str | riverine.units.DecimalQuantity, absorbance: float | Sequence[float], ext_coef: float, vol_removed: None | float | str | riverine.units.DecimalQuantity = None) -> tuple[riverine.units.DecimalQuantity, riverine.units.DecimalQuantity]

   Assuming :func:`hydrate` is called with parameters `nmol` and `target_conc_high` to give initial
   volumes to add to a dry sample to reach a "high" concentration `target_conc_high`,
   and assuming absorbances are then measured,
   calculates subsequent dilution volumes to reach "low" concentration `target_conc_low`,
   and also actual "start" concentration (i.e., actual concentration after adding initial hydration
   that targeted `target_conc_high`, according to `absorbance`).

   This is on the assumption that the first hydration step could result in a concentration below
   `target_conc_high`, so `target_conc_high` should be chosen sufficiently larger than
   `target_conc_low` so that the actual measured concentration after the first step is
   likely to be above `target_conc_low`, so that it is possible to reach concentration
   `target_conc_low` with a subsequent dilution step. (As opposed to requiring a vacufuge to
   concentrate the sample higher).

   :param nmol: number of nmol (nanomoles) of dry product.
   :param target_conc_high: target concentration for initial hydration. Should be higher than `target_conc_low`,
   :param target_conc_low: the "real" target concentration that we will try to hit after the second
                           addition of water/buffer.
   :param absorbance: UV absorbance at 260 nm. Can either be a single float/int or a nonempty sequence of floats/ints
                      representing repeated measurements; if the latter then an average is taken.
   :param ext_coef: Extinction coefficient in L/mol*cm.
   :param vol_removed: Total volume removed from `vol` to measure absorbance.
                       For example, if two samples were taken, one at 1 µL and one at 1.5 µL, then set
                       `vol_removed` = 2.5 µL.
                       If not specified, it is assumed that each sample is 1 µL, and that the total number of samples
                       taken is the number of entries in `absorbance`.
                       If `absorbance` is a single volume (e.g., ``float``, ``int``, ``str``, ``DecimalQuantity``),
                       then it is assumed the number of samples is 1 (i.e., `vol_removed` = 1 µL),
                       otherwise if `absorbance` is a list, then the length of the list is assumed to be the
                       number of samples taken, each at 1 µL.
   :param : The pair (current concentration of DNA sample, volume to add to reach `target_conc`)
   :type : return:


.. py:function:: measure_conc_and_dilute(absorbance: float | Sequence[float], ext_coef: float, target_conc: float | str | riverine.units.DecimalQuantity, vol: float | str | riverine.units.DecimalQuantity, vol_removed: None | float | str | riverine.units.DecimalQuantity = None) -> tuple[riverine.units.DecimalQuantity, riverine.units.DecimalQuantity]

   Calculates concentration of DNA sample given an absorbance reading on a NanoDrop machine,
   then calculates the amount of buffer/water that must be added to dilute it to a target concentration.

   :param absorbance: UV absorbance at 260 nm. Can either be a single float/int or a nonempty sequence of floats/ints
                      representing repeated measurements; if the latter then an average is taken.
   :param ext_coef: Extinction coefficient in L/mol*cm.
   :param target_conc: target concentration. If float/int, units are µM (micromolar).
   :param vol: current volume of sample. If float/int, units are µL (microliters)
               NOTE: This is the volume *before* samples are taken to measure absorbance.
               It is assumed that each sample taken to measure absorbance is 1 µL.
               If that is not the case, then set the parameter `vol_removed` to the total volume removed.
   :param vol_removed: Total volume removed from `vol` to measure absorbance.
                       For example, if two samples were taken, one at 1 µL and one at 1.5 µL, then set
                       `vol_removed` = 2.5 µL.
                       If not specified, it is assumed that each sample is 1 µL, and that the total number of samples
                       taken is the number of entries in `absorbance`.
                       If `absorbance` is a single volume (e.g., ``float``, ``int``, ``str``, ``DecimalQuantity``),
                       then it is assumed the number of samples is 1 (i.e., `vol_removed` = 1 µL),
                       otherwise if `absorbance` is a list, then the length of the list is assumed to be the
                       number of samples taken, each at 1 µL.

   :rtype: The pair (current concentration of DNA sample, volume to add to reach `target_conc`)


.. py:class:: Reference

   .. py:attribute:: df
      :type:  pandas.DataFrame


   .. py:property:: loc
      :type: pandas.core.indexing._LocIndexer



   .. py:method:: __getitem__(key: Any) -> Any


   .. py:method:: __eq__(other: object) -> bool


   .. py:method:: __len__() -> int


   .. py:method:: plate_map(name: str, plate_type: riverine.locations.PlateType = PlateType.wells96) -> riverine.mixes.PlateMap

      Return a :class:`PlateMap` for a given plate name in the Reference.

      :param name: Name of plate to make a :class:`PlateMap` for.
      :param plate_type: Either :data:`PlateType.wells96` or :data:`PlateType.wells384`;
                         default is :data:`PlateType.wells96`.

      :returns: * a :class:`PlateMap` consisting of all strands in this Reference object from plate named
                * `name`. Currently always makes a 96-well plate.

      :raises ValueError:: If `name` is not the name of a plate in the reference.



   .. py:method:: search(name: str | None = None, plate: str | None = None, well: str | riverine.locations.WellPos | None = None, concentration: str | riverine.units.DecimalQuantity | None = None, sequence: str | None = None) -> Reference


   .. py:method:: get_concentration(name: str | None = None, plate: str | None = None, well: str | riverine.locations.WellPos | None = None, concentration: str | riverine.units.DecimalQuantity | None = None, sequence: str | None = None) -> riverine.units.DecimalQuantity


   .. py:method:: from_csv(filename_or_file: str | TextIO | os.PathLike[str]) -> Reference
      :classmethod:


      Load reference information from a CSV file.

      The reference information loaded by this function should be compiled manually, fitting the :ref:`mix reference` format, or
      be loaded with :func:`compile_reference` or :func:`update_reference`.



   .. py:method:: to_csv(filename: str | os.PathLike[str]) -> None


   .. py:method:: update(files: Sequence[RefFile] | RefFile, round: int = -1) -> Reference

      Update reference information.

      This updates an existing reference dataframe with new files, with the same methods as :func:`compile_reference`.



   .. py:method:: compile(files: Sequence[RefFile] | RefFile, round: int = -1) -> Reference
      :classmethod:


      Compile reference information.

      This loads information from the following sources:

      - An IDT plate order spreadsheet.  This does not include concentration.  To add concentration information, list it as a tuple of
      :code:`(file, concentration)`.
      - An IDT bulk order entry text file.
      - An IDT plate spec sheet.



.. py:function:: load_reference(filename_or_file: str | TextIO) -> Reference

.. py:data:: DNAN

.. py:function:: Q_(qty: int | str | decimal.Decimal | float, unit: str | pint.Unit | pint.facets.plain.PlainUnit | pint.Quantity | None = None) -> DecimalQuantity

   Convenient constructor for units, eg, :code:`Q_(5.0, 'nM')`.  Ensures that the quantity is a Decimal.


.. py:exception:: VolumeError

   Bases: :py:obj:`ValueError`


   Inappropriate argument value (of correct type).


.. py:data:: nM

.. py:data:: uL

.. py:data:: uM

.. py:data:: ureg

.. py:data:: html_with_borders_tablefmt

   Pass this as the parameter `tablefmt` in any method that accepts that parameter to have the table
   be an HTML table with borders around each cell.

