User’s guide#

Concepts#

Riverine is a small library to make it easier to organize complicated, hierarchical mixes.

Riverine organizes the mixing process into several concepts:

  • A Component is something that goes into a mix, and has a source concentration. It may be a generic component, a strand with a sequence, or a mix. It may also contain information about

  • An Action describes how a component or set of components is to be added to a mix. It may specify that each component be added to get a target concentration in the mix, for example, or that a fixed volume of each component be added. For example, the FixedConcentration action adds a component (or several components) to a mix at a fixed desired concentration, while FixedVolume adds components at fixed volumes.

  • A Mix is a collection of Actions, each covering some Components. It may have a fixed volume, or that may be determined by the components. It may also have a fixed effective concentration (for use as a component), or that may be determined by a particular component.

  • A Reference is an object that has information about component concentrations, sequences, and locations.

  • An Experiment collects many mixes/components. These are not necessary, but allow saving and loading to and from files, tracking of produced and used concentrations, and referencing of other components and mixes by name.

Physical units are used extensively in riverine. Internally, the library uses pint to handle units, and the decimal library to handle numbers, to avoid floating point inaccuracies. While using the library, units can be specifid flexibly as strings, which will be processed as the correct quantity, for example “50 nM”, or “10 µL”, or “5 uM”. If you need to do calculations, quantities can be created using Q_, for example, Q_(10, “µL”), or Q_(“55.3”, “nM”), and these values can be used with normal arithmetic operations.

ref = Reference.compile(["idt-spec-sheet-1.xlsx", ("idt-order-2.xlsx", "100 µM")])

mg = Component("10x Mg in TE buffer", "125 mM")

strands = [Strand(f"strand_{i}") for i in range(0, 10)]

strand_individual = Strand("extra_strand", sequence="AGATTAGCTCC").with_reference(ref)

mix1 = Mix(
   [
      MultiFixedConcentration(strands, "1 µM"),
      FixedConcentration(strand_individual, "2 µM"),
      FixedConcentration(mg, "125 mM")
   ],
   name = "mix_with_concentrations",
   fixed_total_volume = "100 µL"
)

mix2 = Mix(
   [
      MultiFixedVolume(strands, "5 µL", equal_conc="min_volume")
   ],
   name = "mix_with_volumes"
)

Components#

A component is something that is added to a mix. It must have a name, and generally includes some other information, such as concentration, a tube or plate and well location, or a DNA sequence. A Mix is also, itself, a component that can be reused in other mixes.

Components are usually created by using their classes. For simple systems, they can be be specified directly, for example,

strand = Strand("H_N_5", "100 uM", "GGAGTCCATTCG", plate="plate1", well="A3")

For more complex systems, information will often be in a reference file. In that case, partially specified components can be used, and the details filled in with the reference. .with_reference(ref) will search for a matching component in the reference ref, and return a component with details added. For example:

strand = Strand("H_N_5").with_reference(ref)

Adding details beyond a name may be useful, in order to ensure that the details in the reference match, or to find a particular item in the reference, for example, specifying that you’d like a component in a particular plate, or at a particular concentration, if you have multiple copies of it.

strand = Strand("H_N_5", plate="plate2")

In order to be used in a mix, a component should have at least a name and concentration. However, these can be omitted when adding them to a mix, and added later (for example, Mix.with_reference will add reference details recursively to components in a mix), or may be calculated automatically (a Mix will often have a “mix concentration” calculated based on its components).

Component(name, concentration, volume, *, ...)

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

Strand(name, concentration, volume, ...)

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

Mix(*args, **kwargs)

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

Note

Components implement the AbstractComponent class.

Actions#

Actions describe how a component, or set of components, should be added to a mix. An action can be seen as a collection of related pipetting steps.

There are two main actions. FixedConcentration is useful when you’d like to add a fixed concentration of each component to a mix, but you don’t care about the volume being transferred, beyond potentially wanting to ensure that it is above a certain minimum. FixedVolume is useful when you’d like to add a fixed volume of each component. There is also EqualConcentration, when you’d like to transfer some volume of a number of components so the same concentration of each is added, and ToConcentration, which adds (non-mix) components so that, taking into account everything else in the mix recursively, those components will each be at a particular concentration.

FixedVolume(*args, **kwargs)

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

FixedConcentration(components, ...)

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

EqualConcentration(*args, **kwargs)

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

ToConcentration(components, ...)

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

Note

Previous versions of riverine only supported single components for FixedVolume and FixedConcentration, and included separate MultiFixedVolume and MultiFixedConcentration classes for multiple components. The two Multi class names are currently kept for compatibility reasons, as aliases of FixedVolume and FixedConcentration. FixedVolume originally implemented the features of EqualConcentration, but this was confusing: the equal_conc parameter to FixedVolume is deprecated, but will try to create a corresponding EqualConcentration instance. Note that FixedVolume originally by default gave an error if destination concentrations were different; it no longer does so, and simply transfers fixed volumes.

Actions must implement the AbstractAction class. For convenience, there’s the ActionWithComponents class, which is generally applicable for actions that act on a list of components.

Mixes#

The Mix class represents a mix, which is defined as a set of actions, a name, and potentially some other information, like fixed total volume, a minimum desired transfer volume, or a way to define the mix’s concentration.

Mix(*args, **kwargs)

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

Mix.with_reference(reference, *[, inplace])

The easiest way to display mixes is through Jupyter notebooks. Simply displaying (or using display) a mix will output a formatted table. Additionally, there are several methods:

Mix.display_instructions([plate_type, ...])

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

Mix.instructions(*[, plate_type, ...])

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

Mix.plate_maps([plate_type, validate, ...])

Similar to table(), but indicates only the strands to mix from each plate, in the form of a PlateMap.

Mix.tubes_markdown([tablefmt])

Many methods can accept a tablefmt parameter, based on the tabulate package, which chooses what format the output should be. The useful tablefmt values include “pipe” (a pandoc-compatible Markdown table), “github” (a github-and-pandoc-compatible Markdown table), “unsafehtml” (an HTML table), “latex”, and “orgtbl”.

Mixes can also provide information on the individual components they contain, calculated recursively if there are multiple levels of mixes involved:

Mix.all_components()

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

References#

While riverine can be used while specifying all component information directly, it is often more useful to collect that information from other sources.

Reference([df])

Reference.compile(files[, round])

Compile reference information.

Reference.update(files[, round])

Update reference information.

Reference.from_csv(filename_or_file)

Load reference information from a CSV file.

Reference.to_csv(filename)

With this reference, components, actions, and mixes can be updated with the information in them:

Mix.with_reference(reference, *[, inplace])

AbstractAction.with_reference(reference, *)

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

Component.with_reference(reference[, inplace])

Experiments#

Experiments hold mixes, and potentially concentrations, to be referred to later and tracked as a group.

Mixes can be added to an experiment using the Experiment.add_mix method, or by using experiment[mix_name] = Mix(…). In the latter case, the name in the Mix does not need to be set: it will be set to mix_name when it is added to the experiment. When mixes are added, components that are string references are resolved to components with the same names in the experiment. This is obviously not possible if the mix being referred to is only added later: in this case, you can use the Experiment.resolve_components method to resolve the references.

A useful feature of an Experiment is that the consumed and produced volumes of mixes, and the consumed volumes of other components, can be tracked across all of the mixes involved:

Experiment.check_volumes([showall, display, ...])

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

Experiment.consumed_and_produced_volumes()

Experiments also allow groups of mixes to be saved and loaded to JSON files. When written, any mixes used in other mixes are replaced with references to those mixes, so that when the file is loaded, the mixes are linked back together, and changes to one mix in the experiment will propagate to all the mixes that use it:

Experiment.save(filename_or_stream)

Save an experiment to a JSON-formatted file.

Experiment.load(filename_or_stream)

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