|
| 1 | +--- |
| 2 | +title: Growth rate cuve modelling |
| 3 | +slug: /growth-rate-modelling |
| 4 | +--- |
| 5 | + |
| 6 | + |
| 7 | +## Introduction to our growth-rate model |
| 8 | + |
| 9 | + |
| 10 | +> The study of the growth of bacterial cultures |
| 11 | +does not constitute a specialized subject or branch |
| 12 | +of research: it is the basic method of microbiology.” |
| 13 | +Monitoring and controlling the specific growth rate |
| 14 | +should not constitute a specialized subject or branch |
| 15 | +of research: it should be the basic method of advanced |
| 16 | +microbial bioprocessing. |
| 17 | +> \- Jacques Monod |
| 18 | +
|
| 19 | +The first step to controlling a culture's growth is to measure it. To do this, we need to model the rate of growth. We've seen other attempts at modelling the growth rate, but they all assumed too much: |
| 20 | + |
| 21 | + - Exponential growth model: perhaps truly early on, but certainly not the case near stationary. |
| 22 | + - Logistic growth model: requires an assumption about the max optical density, and assumes symmetric growth around the inflection point. |
| 23 | + - Gompertz models and other models: more parameters, less interpretable, and still make strong assumptions about the trajectory of growth. |
| 24 | + |
| 25 | +Further more, none of these can model a culture in a turbidostat or chemostat mode, where the optical density is dropping quickly during a dilution, but the growth rate should remain constant. |
| 26 | + |
| 27 | +### Re-visiting a growth model |
| 28 | + |
| 29 | +We are often introduced to a growth model by the simple exponential growth model: |
| 30 | + |
| 31 | +$\text{OD}(t) = \exp{\left( \text{gr} t \right)}$ |
| 32 | + |
| 33 | +Plainly put, the culture grows exponentially at rate $\text{gr}$. Like we mentioned above, this might be true for small time-scales, but certainly over the entire lag, log, and then stationary phases, this is not the case. |
| 34 | + |
| 35 | +There's a hint of an interesting idea in the last paragraph though: _"over small time scales"_. What if we cut up the growth curve into many small time intervals, and computed a growth rate for each interval? Then our growth rate can be changing: starts near 0 in the lag phase, can increase to a peak in the log phase, and then drop to 0 again in the stationary phase. We also don't need to assume any parametric form for the growth rate, we can just measure it directly from the data. |
| 36 | + |
| 37 | +Our new formula might look like: |
| 38 | + |
| 39 | + |
| 40 | +$$\text{OD}(t) = \exp{\left(\text{gr}_0 \Delta t + \text{gr}_1 \Delta t + ... \right)}$$ |
| 41 | + |
| 42 | +If we think more about this, and we keep shrinking our time interval towards zero, this is just an integral: |
| 43 | + |
| 44 | +$$ |
| 45 | +\text{nOD}(t) = \exp{ \left( \int_0^t \text{gr}(s)ds \right)} |
| 46 | +$$ |
| 47 | + |
| 48 | +There's still no particular assumption about the shape of the growth rate function, $gr(s)$. For example, consider the following ODs from a batch experiment: |
| 49 | + |
| 50 | + |
| 51 | + |
| 52 | +Using our estimation technique outlined below, we can estimate the growth curve as: |
| 53 | + |
| 54 | + |
| 55 | + |
| 56 | + |
| 57 | +We can see that the growth is very dynamic, and certainly not a single number or a constrained form! |
| 58 | + |
| 59 | + |
| 60 | +### Estimating a non-parametric growth rate |
| 61 | + |
| 62 | +A non-parametric, dynamic growth rate sounds great, but we've replaced a estimation of a single value (or handle of values) to an entire function! This seems expensive! |
| 63 | + |
| 64 | +Luckily, we do the work for you. We will compute $gr(t)$ using a statistical algorithm, **the Kalman filter**. The Kalman filter is an algorithm that estimates the state of a dynamic system from noisy measurements. In our case, the state is the growth rate, and the measurements are the optical density readings. We input optical density observations one at a time, and the Kalman filter updates its estimate of the growth rate based on the new observation. This allows us to track the growth rate in real-time, as the culture grows. |
| 65 | + |
| 66 | +### Online: Using the Pioreactor's software |
| 67 | + |
| 68 | +When you start the "Growth rate" action in your Pioreactor, the action starts reading from the stream of OD readings, and computes the growth-rate for each reading. The growth rate is then displayed in the Pioreactor UI. |
| 69 | + |
| 70 | +### Offline: Using the [growth-rate tool](https://growth.pioreactor.com) |
| 71 | + |
| 72 | +We have implemented our algorithm and estimation in an easy-to-use web app, the [growth-rate tool](https://growth.pioreactor.com). You can use this tool to upload your optical density and (optionally) dosing events data, and it will compute the growth rate for you. |
| 73 | + |
| 74 | +### Backed by our Python library, `grpredict` |
| 75 | + |
| 76 | +If you want more control, you can use our `grpredict` Python library, available with: |
| 77 | +``` |
| 78 | +pip install grpredict |
| 79 | +``` |
| 80 | + |
| 81 | +This is the same library that's used in our Pioreactor software. |
| 82 | + |
| 83 | +### Model parameters |
| 84 | + |
| 85 | + - **baseline samples**: the number of samples at the start of the dataset to use for initial variance calculations, and as the baseline value to normalize the optical densities against (so that the initial nOD is nearly equal to 1.0) |
| 86 | + - **Growth-rate std**: a Kalman-filter specific parameter. Higher values allow more flexibility in the growth rate, lower values smooth the growth rate. |
| 87 | + - **nOD std.**: a Kalman-filter specific parameter. If your dosing large amounts at a time, you may want to increase this. |
| 88 | + - **OD std. factor**: a Kalman-filter specific parameter. If your sample are _very_ noisy, you can increase 2x to dampen the affect of that noise. |
| 89 | + - **Outlier threshold**: a Kalman-filter specific parameter. Large outliers or shifts in OD will likely be taken care of by setting this to 5, but try values between 3 and 5 if you want to handle less large outliers. |
| 90 | + |
| 91 | + |
| 92 | +## Required CSV schemas for growth-rate tool |
| 93 | + |
| 94 | +To upload your optical density and dosing events data to the Pioreactor, you need to prepare CSV files with specific schemas. Below are the required formats for each file, along with common pitfalls to avoid. |
| 95 | + |
| 96 | +:::tip |
| 97 | + |
| 98 | +If using the Pioreactor UI to export datasets, these already have the required schema. |
| 99 | + |
| 100 | +::: |
| 101 | + |
| 102 | +### 1. Optical-Density CSV |
| 103 | + |
| 104 | +| **Column** | **Required?** | **Expected dtype** | **Description** | |
| 105 | +| -------------------------------- | ------------- | ------------------ | ------------------------------------------------------------------------------------------------------------- | |
| 106 | +| `od_reading` | Yes | **float** | Raw or pre-normalized optical-density measurement. | |
| 107 | +| `hours_since_experiment_created` | Yes | **float** | Elapsed hours since the experiment’s “time 0”. Use decimals for sub-hour resolution (e.g. `0.4167` ≈ 25 min). | |
| 108 | + |
| 109 | +:::info |
| 110 | + |
| 111 | +Additional columns (sensor angle, experiment name, etc.) are ignored by the parser, so feel free to keep them—just make sure the two required columns exist and are spelled exactly as above (case-sensitive). |
| 112 | + |
| 113 | +::: |
| 114 | + |
| 115 | +#### Minimal example |
| 116 | + |
| 117 | +```csv |
| 118 | +od_reading,hours_since_experiment_created |
| 119 | +0.1725,0.397 |
| 120 | +0.1735,0.399 |
| 121 | +0.1749,0.402 |
| 122 | +``` |
| 123 | + |
| 124 | +--- |
| 125 | + |
| 126 | +### 2. Dosing-events CSV |
| 127 | + |
| 128 | +| **Column** | **Required?** | **Expected dtype** | **Description** | |
| 129 | +| -------------------------------- | ------------- | --------------------- | --------------------------------------------------------------------------------------------- | |
| 130 | +| `event` | Yes | **category** (string) | Either `add_media`, `add_alt_media`, or `remove_waste`. Other strings are treated as distinct categories. | |
| 131 | +| `volume_change_ml` | Yes | **float** | Signed volume change (mL). Convention: positive for **additions**, negative for **removals**. | |
| 132 | +| `hours_since_experiment_created` | Yes | **float** | Timestamp in **hours**, exactly matching the OD clock. | |
| 133 | + |
| 134 | +#### Minimal example |
| 135 | + |
| 136 | +```csv |
| 137 | +event,volume_change_ml,hours_since_experiment_created |
| 138 | +add_media,1.0,1.533 |
| 139 | +remove_waste,-1.0,1.544 |
| 140 | +remove_waste,-2.0,1.567 |
| 141 | +``` |
| 142 | + |
| 143 | +--- |
| 144 | + |
| 145 | +### Common pitfalls |
| 146 | + |
| 147 | +* **Header spelling matters** – `odReading`, `OD_reading`, or trailing spaces will fail validation. |
| 148 | +* **Mixed delimiters** – Ensure you’re saving with commas; semicolons require a manual find-&-replace. |
| 149 | +* **Units** – `volume_change_ml` must be millilitres; convert L → mL beforehand. |
| 150 | +* **Consistent clock** – Both files must use the same `hours_since_experiment_created` reference, or dilution jumps won’t align. |
| 151 | + |
| 152 | +--- |
| 153 | + |
| 154 | +### Quick checklist before uploading |
| 155 | + |
| 156 | +1. **Open in a plain-text editor** – Verify the first line matches the header tables above. |
| 157 | +2. **Scan for blank lines** – Delete empty rows at the end; they sometimes appear after Excel saves. |
| 158 | +3. **Check decimals** – European locales may export `0,1725`; switch to `.` decimal point. |
| 159 | +4. **Save as UTF-8 CSV** – Most editors do this by default; avoid Excel’s “CSV (Macintosh)” variant. |
| 160 | + |
| 161 | + |
| 162 | + |
| 163 | + |
| 164 | + |
| 165 | + |
0 commit comments