riverine.quantitate
===================

.. py:module:: riverine.quantitate

.. autoapi-nested-parse::

   A module for handling "quantitation": measuring concentration of strands,
   and diluting and hydrating to reach a desired concentration.

   The main "easy" functions to use are :func:`hydrate_from_specs` and
   :func:`hydrate_and_measure_conc_and_dilute_from_specs`.

   >>> from riverine.quantitate import hydrate_from_specs, hydrate_and_measure_conc_and_dilute_from_specs
   >>> specs_file = 'path/to/coa.csv'
   >>> target_conc_high = '200 uM'
   >>> target_conc_low = '100 uM'
   >>> hydrate_from_specs(
   ...     filename=specs_file,
   ...     target_conc=target_conc_high,
   ...     strands=['5RF', '3RQ'],
   ... )
   nmoles = 8.9 nmol
   nmoles = 15.7 nmol
   {'5RF': <Quantity(44.5, 'microliter')>,
    '3RQ': <Quantity(78.5, 'microliter')>}
   >>> # now go to the lab and add the above quantities of water/buffer to the dry samples,
   >>> # then measure absorbances, e.g., with a NanoDrop, to populate the dict `absorbances` below
   >>> absorbances = {
   ...     '5RF': [48.46, 48.28],
   ...     '3RQ': [34.36, 34.82],
   ... }
   >>> hydrate_and_measure_conc_and_dilute_from_specs(
   ...     filename=specs_file,
   ...     target_conc_high=target_conc_high,
   ...     target_conc_low=target_conc_low,
   ...     absorbances=absorbances,
   ... )
   {'5RF': (<Quantity(213.931889, 'micromolar')>, <Quantity(48.4210528, 'microliter')>),
    '3RQ': (<Quantity(190.427429, 'micromolar')>, <Quantity(69.176983, 'microliter')>)}

   For convenience in Jupyter notebooks, there are also versions of these functions beginning with ``display_``:
   :func:`display_hydrate_from_specs` and :func:`display_hydrate_and_measure_conc_and_dilute_from_specs`.
   Instead of returning a dictionary, these methods display the result in the Jupyter notebook,
   as nicely-formatted Markdown.



Functions
---------

.. autoapisummary::

   riverine.quantitate.measure_conc_and_dilute
   riverine.quantitate.hydrate_and_measure_conc_and_dilute
   riverine.quantitate.hydrate_and_measure_conc_and_dilute_from_specs
   riverine.quantitate.hydrate_from_specs


Module Contents
---------------

.. 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: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:: hydrate_and_measure_conc_and_dilute_from_specs(filename: str, target_conc_high: float | str | riverine.units.DecimalQuantity, target_conc_low: float | str | riverine.units.DecimalQuantity, absorbances: dict[str, float | Sequence[float]], vols_removed: dict[str, None | float | str | riverine.units.DecimalQuantity] | None = None, enforce_utf8: bool = True) -> dict[str, tuple[riverine.units.DecimalQuantity, riverine.units.DecimalQuantity]]

   Like :func:`hydrate_and_measure_conc_and_dilute`, but works with multiple strands,
   using an IDT spec file to look up nmoles and extinction coefficients.

   The intended usage of this method is to be used in conjunction with the function
   :func:`hydrate_from_specs` as follows.

   >>> from riverine.quantitate import hydrate_from_specs, hydrate_and_measure_conc_and_dilute_from_specs
   >>> specs_file = 'path/to/coa.csv'
   >>> target_conc_high = '200 uM'
   >>> target_conc_low = '100 uM'
   >>> hydrate_from_specs(
   ...     filename=specs_file,
   ...     target_conc=target_conc_high,
   ...     strands=['5RF', '3RQ'],
   ... )
   nmoles = 8.9 nmol
   nmoles = 15.7 nmol
   {'5RF': <Quantity(44.5, 'microliter')>,
    '3RQ': <Quantity(78.5, 'microliter')>}
   >>> # now go to the lab and add the above quantities of water/buffer to the dry samples,
   >>> # then measure absorbances, e.g., with a NanoDrop, to populate the dict `absorbances` below
   >>> absorbances = {
   ...     '5RF': [48.46, 48.28],
   ...     '3RQ': [34.36, 34.82],
   ... }
   >>> hydrate_and_measure_conc_and_dilute_from_specs(
   ...     filename=specs_file,
   ...     target_conc_high=target_conc_high,
   ...     target_conc_low=target_conc_low,
   ...     absorbances=absorbances,
   ... )
   {'5RF': (<Quantity(213.931889, 'micromolar')>, <Quantity(48.4210528, 'microliter')>),
    '3RQ': (<Quantity(190.427429, 'micromolar')>, <Quantity(69.176983, 'microliter')>)}

   Note in particular that we do not need to specify the volume prior to the dilution step,
   since it is calculated based on the volume necessary for the first hydration step to
   reach concentration `target_conc_high`.

   For convenience in Jupyter notebooks, there are also versions of these functions beginning with
   ``display_``:
   :func:`display_hydrate_from_specs` and :func:`display_hydrate_and_measure_conc_and_dilute_from_specs`.
   Instead of returning a dictionary, these methods display the result in the Jupyter notebook,
   as nicely-formatted Markdown.

   :param filename: path to IDT Excel/CSV spreadsheet with specs of strands (e.g., coa.csv)
   :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 absorbances: UV absorbances at 260 nm. Is a dict mapping each strand name to an "absorbance" as defined
                       in the `absobance` parameter of :func:`hydrate_and_measure_conc_and_dilute`.
                       In other words the value to which each strand name maps
                       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 vols_removed: Total volumes removed from `vol` to measure absorbance;
                        is a dict mapping strand names (should be subset of strand names that are keys in `absorbances`).
                        Can be None, or can have strictly fewer strand names than in `absorbances`;
                        defaults are assumed as explained next for any missing strand name key.
                        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 enforce_utf8: If `filename` is a text CSV file and this paramter is True, it enforces that `filename` is valid
                        UTF-8, raising an exception if not. This helps to avoid accidentally dropping Unicode characters
                        such as µ, which would silently convert a volume from µL to L.
                        If do not want to convert the specs file to UTF-8 and you are certain that no important Unicode
                        characters would be dropped, then you can set this parameter to false.

   :returns: * dict mapping each strand name in keys of `absorbances` to a pair (`conc`, `vol_to_add`),
             * where `conc` is the measured concentration according to the absorbance value(s) of that strandm
             * and `vol_to_add` is the volume needed to add to reach concentration `target_conc_low`.


.. py:function:: hydrate_from_specs(filename: str, target_conc: float | str | riverine.units.DecimalQuantity, strands: Sequence[str] | Sequence[int] | None = None, enforce_utf8: bool = True) -> dict[str, riverine.units.DecimalQuantity]

   Indicates how much volume to add to a dry DNA sample to reach a particular concentration,
   given data in an Excel file in the IDT format.

   :param filename: path to IDT Excel/CSV spreadsheet with specs of strands (e.g., coa.csv)
   :param target_conc: target concentration. If float/int, units are µM (micromolar).
   :param strands: strands to hydrate. Can be list of strand names (strings), or list of of ints indicating
                   which rows in the Excel spreadsheet to hydrate
   :param enforce_utf8: If `filename` is a text CSV file and this paramter is True, it enforces that `filename` is valid
                        UTF-8, raising an exception if not. This helps to avoid accidentally dropping Unicode characters
                        such as µ, which would silently convert a volume from µL to L.
                        If do not want to convert the specs file to UTF-8 and you are certain that no important Unicode
                        characters would be dropped, then you can set this parameter to false.

   :returns: * *dict mapping each strand name to an amount of µL (microliters) of water/buffer*
             * to pipette to reach `target_conc` concentration for that strand


