Stocks
Creating Stocks from Tables
Pourfecto represents source and target materials as JLIMS.Stock objects. In most workflows, users do not need to construct Stocks manually. Instead, stocks can be created from tabular data using df_to_stock.
This is useful when reading stocks from CSV files, spreadsheets, notebooks, or user-facing forms.
The main stock conversion functions are:
Pourfecto.df_to_stock — Function
df_to_stock(df::DataFrame, units::DataFrame; kwargs...) -> Vector{Stock}Parse a pair of DataFrame representations into a vector of Stocks.
This is a format-dispatching wrapper that supports two encodings:
- "vc" (volume/concentration): tables include a
"volume"column (in bothdfandunits) and are parsed viavc_to_stock(df, units; kwargs...). - "q" (quantity): tables omit
"volume"and are parsed viaq_to_stock(df, units; kwargs...).
The function auto-detects the encoding by checking for the presence of the "volume" column in both df and units.
Arguments
df::DataFrame: Main stock table (one row per stock; columns depend on the chosen format).units::DataFrame: Units/metadata table aligned withdf(e.g., unit strings for numeric columns).
See also
Pourfecto.stock_to_df — Function
stock_to_df(stocks::Vector{<:JLIMS.Stock}, format::AbstractString = "vc"; kwargs...) -> (DataFrame, DataFrame)Convert a vector of Stocks into a pair of dataframes (df, units) suitable for serialization.
Two formats are supported:
format = "vc": volume/concentration representation (stock_to_vc).format = "q": quantity representation (stock_to_q).
Arguments
stocks::Vector{<:JLIMS.Stock}: Stocks to convert.format::AbstractString = "vc": Output encoding; must be one of["vc", "q"].
See also
A stock table is represented by two DataFrames:
df: the main stock dataunits: the units associated with the values indf
stocks = df_to_stock(df, units)The reverse operation is:
df, units = stock_to_df(stocks)Supported stock table formats
Pourfecto supports two stock table encodings:
| Format | Description |
|---|---|
"vc" | Volume/Concentration format |
"q" | Quantity format |
The parser df_to_stock automatically detects which format is being used.
If both df and units contain a "volume" column, Pourfecto treats the table as a volume/concentration table.
Otherwise, Pourfecto treats the table as a quantity table.
Volume/concentration format
The volume/concentration format is useful when each row represents a stock with a total volume and one or more reagent concentrations.
Conceptually:
| volume | reagent_a | reagent_b |
|---|---|---|
| 1000 | 10 | 5 |
| 500 | 20 | 0 |
with a corresponding units table:
| volume | reagent_a | reagent_b |
|---|---|---|
| µL | mM | mM |
Example:
using DataFrames
using Unitful
using Pourfecto
df = DataFrame(
volume = [1000, 500],
sodium_chloride = [10, 20],
dye = [5, 0],
)
units = DataFrame(
volume = ["µL"],
sodium_chloride = ["mM"],
dye = ["mM"],
)
stocks = df_to_stock(df, units)Because both tables contain a "volume" column, Pourfecto parses this as a "vc" table.
Quantity format
The quantity format is useful when each row represents a stock directly by the amount of each reagent it contains.
Conceptually:
| water | sodium_chloride |
|---|---|
| 1000 | 10 |
| 500 | 5 |
with a corresponding units table:
| water | sodium_chloride |
|---|---|
| µL | mg |
Example:
using DataFrames
using Pourfecto
df = DataFrame(
water = [1000, 500],
sodium_chloride = [10, 5],
)
units = DataFrame(
water = ["µL"],
sodium_chloride = ["mg"],
)
stocks = df_to_stock(df, units)Because these tables do not contain a "volume" column, Pourfecto parses this as a "q" table.
Automatic reagent creation
When parsing stock tables, reagent names are usually taken from the column names.
Pourfecto can turn those reagent names into JLIMS.Chemical objects automatically. Registered JLIMS chemicals are used when available. Unknown reagents are created on the fly with missing chemical properties.
For example, a column named:
:sodium_chlorideor
:dyecan be interpreted as a reagent name.
If the reagent is not registered, Pourfecto will warn and create a generic chemical object.
Unknown reagents can still be used for planning and scheduling. However, calculations that require molecular weight or density may require fully registered JLIMS chemicals.
See also: string_to_reagent, reagent_to_string
Converting stocks back to dataframes
Use stock_to_df to export stocks back into tabular form.
df, units = stock_to_df(stocks)By default, this uses the "vc" format:
df, units = stock_to_df(stocks, "vc")To request quantity format:
df, units = stock_to_df(stocks, "q")Creating Stocks Manually
Stocks can also be created manually with convenient arithmetic syntax from JLIMS.
This is useful in notebooks, tests, examples, and small workflows where writing a dataframe would be unnecessary.
JLIMS overloads the * operator so that quantities and chemicals can be combined directly:
using JLIMS
using Pourfecto
using Unitful
sodium_chloride = string_to_reagent("sodium_chloride",Solid)
water = string_to_reagent("water", Liquid)
10u"mg" * sodium_chloride
1u"mL" * water Depending on the chemical type and unit, these expressions create solid stocks Mixtures or liquid stocks Solutions
In general:
mass * Solidcreates a Mixtureamount * Solidcreates a Mixturevolume * Liquidcreates a Solution
Combining stocks
Stocks can be combined using +.
For example:
stock = 900u"µL" * chem"water" + 100u"µL" * chem"ethanol" # assumes "water" and "ethanol" are pre-registered chemicals This creates a stock containing both water and ethanol.
A more complex example might include both solids and liquids:
buffer = 1u"mL" * chem"water" + 10u"mg" * chem"sodium_chloride" # assumes "water" and "sodium_chloride" are pre-registered chemicals Scaling stocks
Stocks can be multiplied by scalar values.
stock2 = 2 * stockThis returns a new stock with all chemical quantities scaled by the given factor.
Scalar multiplication works in either order:
stock2 = 2 * stock
stock3 = stock * 2Stocks can also be divided by scalars:
half_stock = stock / 2Rescaling a stock to a target quantity
A stock can be scaled to a target total quantity using:
target_quantity * stockFor example, if stock represents a liquid mixture, you can scale it to a final volume:
small_stock = 100u"µL" * stockor:
large_stock = 10u"mL" * stockThe target quantity must be dimensionally compatible with the stock’s total quantity. For example, a liquid stock can be scaled to a volume such as 100u"µL", but not to an incompatible unit.
Example: Adding multiple chemicals in a For Loop
using JLIMS
using Unitful
chem_names = ["A","B","C","D"]
chem_masses = [1,2,3,4]
stock = 1u"mL" * string_to_reagent("water",Liquid)
for i in eachindex(chem_names)
stock += (chem_masses[i] *u"mg") * string_to_reagent(chem_names[i],Solid)
end
This creates a stock containing:
- 1 mL water
- 1 mg A
- 2 mg B
- 3 mg C
- 4 mg D
Operator summary
| Expression | Meaning |
|---|---|
amount * solid | Create a Mixture from a molar quantity of a solid |
mass * solid | Create a Mixture from a mass of a solid |
volume * liquid | Create a Solution from a volume of a liquid |
num * stock | Scale all stock components by num |
stock * num | Same as num * stock |
stock / num | Divide all stock components by num |
quantity * stock | Rescale stock to a target total quantity |
stock * quantity | Same as quantity * stock |