rnamelt 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rnamelt-0.1.0/.gitignore +1 -0
- rnamelt-0.1.0/LICENSE +21 -0
- rnamelt-0.1.0/PKG-INFO +258 -0
- rnamelt-0.1.0/README.md +240 -0
- rnamelt-0.1.0/pyproject.toml +41 -0
- rnamelt-0.1.0/rnamelt/__init__.py +54 -0
- rnamelt-0.1.0/rnamelt/__main__.py +271 -0
- rnamelt-0.1.0/rnamelt/analysis_melting.py +501 -0
- rnamelt-0.1.0/rnamelt/api.py +417 -0
- rnamelt-0.1.0/rnamelt/cleaning.py +41 -0
- rnamelt-0.1.0/rnamelt/constants.py +18 -0
- rnamelt-0.1.0/rnamelt/functions.py +157 -0
- rnamelt-0.1.0/rnamelt/methods.py +489 -0
- rnamelt-0.1.0/rnamelt/plots.py +420 -0
- rnamelt-0.1.0/rnamelt/results.py +655 -0
- rnamelt-0.1.0/rnamelt/utils.py +32 -0
rnamelt-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__pycache__
|
rnamelt-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tspich
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
rnamelt-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rnamelt
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Two-state thermodynamic analysis (ΔH, ΔS, ΔG, Tm) of RNA melting curves
|
|
5
|
+
Project-URL: Homepage, https://github.com/tspich/RNAmelt
|
|
6
|
+
Project-URL: Issues, https://github.com/tspich/RNAmelt/issues
|
|
7
|
+
Author: tspich, RaumZeit
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: melting curve,rna,thermodynamics
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Requires-Dist: numpy>=1.23
|
|
13
|
+
Requires-Dist: pandas>=1.5
|
|
14
|
+
Requires-Dist: scipy>=1.10
|
|
15
|
+
Provides-Extra: plots
|
|
16
|
+
Requires-Dist: matplotlib>=3.5; extra == 'plots'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# rnamelt
|
|
20
|
+
|
|
21
|
+
[](https://pypi.org/project/rnamelt/)
|
|
22
|
+
[](https://pypi.org/project/rnamelt/)
|
|
23
|
+
[](https://github.com/tspich/RNAmelt/blob/main/LICENSE)
|
|
24
|
+
|
|
25
|
+
Two-state thermodynamic analysis (ΔH, ΔS, ΔG, Tm) of UV-absorbance and
|
|
26
|
+
fluorescence RNA / DNA melting curves. One small, dependency-light Python
|
|
27
|
+
package that works as a library, a CLI, and a browser app.
|
|
28
|
+
|
|
29
|
+
- **Library** — `from rnamelt import MeltAnalysis`, typed result objects,
|
|
30
|
+
optional `matplotlib` plotting.
|
|
31
|
+
- **CLI** — `rnamelt FILE.csv [...]` for batch / scripted runs.
|
|
32
|
+
- **Browser** — the same Python pipeline runs client-side via Pyodide.
|
|
33
|
+
Live demo: <https://tspich.github.io/RNAmelt/>.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install rnamelt # base — numpy, pandas, scipy
|
|
39
|
+
pip install 'rnamelt[plots]' # adds matplotlib for .plot() helpers
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Requires Python ≥ 3.10.
|
|
43
|
+
|
|
44
|
+
## Quickstart
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from rnamelt import MeltAnalysis
|
|
48
|
+
|
|
49
|
+
m = MeltAnalysis.from_csv("melt.csv", struct_type="heterodimer")
|
|
50
|
+
r = m.single("sample_1", oligo=0.5) # oligo in µM
|
|
51
|
+
print(r.Tm_fit, r.fit.dH, r.fit.dG)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
rnamelt melt.csv --column sample_1 --oligo 0.5 --struct-type heterodimer
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## CSV input format
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
temperature, sample_1, sample_2, ...
|
|
62
|
+
20.0, 0.412, 0.388, ...
|
|
63
|
+
20.5, 0.415, 0.391, ...
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- Column 0: temperature in °C (any header name).
|
|
67
|
+
- Columns 1+: absorbance or fluorescence (any header name).
|
|
68
|
+
- Rows entirely NaN, or with a missing temperature, are dropped.
|
|
69
|
+
|
|
70
|
+
## CLI
|
|
71
|
+
|
|
72
|
+
Same orchestrator as the library. JSON on stdout; pass `--csv-out` for a
|
|
73
|
+
results CSV in the same format the browser produces.
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# van't Hoff + full fit on every signal column
|
|
77
|
+
rnamelt melt.csv --csv-out batch.csv
|
|
78
|
+
|
|
79
|
+
# single column
|
|
80
|
+
rnamelt melt.csv --column sample_1 --struct-type heterodimer \
|
|
81
|
+
--oligo 5.0 --T-low 20 --T-high 90 --csv-out single.csv
|
|
82
|
+
|
|
83
|
+
# shared-ΔH joint fit across columns
|
|
84
|
+
rnamelt melt.csv --column __multi__ --struct-type heterodimer \
|
|
85
|
+
--oligo-multi sample_1=0.5 --oligo-multi sample_2=5.0
|
|
86
|
+
|
|
87
|
+
# concentration-series van't Hoff (1/Tm vs ln(C_T/f))
|
|
88
|
+
rnamelt melt.csv --column __concentration__ --struct-type heterodimer \
|
|
89
|
+
--oligo-multi sample_1=0.5 --oligo-multi sample_2=5.0 --oligo-multi sample_3=50 \
|
|
90
|
+
--csv-out conc.csv
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`rnamelt --help` lists every flag, including three solver-tuning groups
|
|
94
|
+
(`scipy.optimize.least_squares` overrides, van't Hoff linearisation,
|
|
95
|
+
full-fit initial guesses). Exit codes: `0` success, `1` analysis error,
|
|
96
|
+
`2` bad input.
|
|
97
|
+
|
|
98
|
+
## Python API
|
|
99
|
+
|
|
100
|
+
The recommended surface is `MeltAnalysis`. It holds the DataFrame and
|
|
101
|
+
every configuration knob (struct type, salt, transition window, baseline
|
|
102
|
+
offsets, solver / vH / fit-init overrides) and exposes the three modes as
|
|
103
|
+
methods returning typed result objects from `rnamelt.results`.
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from rnamelt import MeltAnalysis, FitFailed
|
|
107
|
+
|
|
108
|
+
m = MeltAnalysis.from_csv(
|
|
109
|
+
"melt.csv",
|
|
110
|
+
struct_type="heterodimer", # "heterodimer" | "homodimer" | "monomer"
|
|
111
|
+
salt=150.0, # NaCl (mM) — metadata only
|
|
112
|
+
T_low=None, T_high=None, # transition window (°C); None = full range
|
|
113
|
+
bl_lower_offset=10.0, # folded baseline span above T_low (°C)
|
|
114
|
+
bl_upper_offset=10.0, # unfolded baseline span below T_high (°C)
|
|
115
|
+
)
|
|
116
|
+
m.signal_columns # ['sample_1', 'sample_2', ...]
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Three modes
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
# Single column — returns SingleResult; numpy arrays live on the object
|
|
123
|
+
r = m.single("sample_1", oligo=0.5)
|
|
124
|
+
r.Tm_raw, r.vh.dH, r.fit.Tm, r.fit.dG
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
r.fit.require() # raises FitFailed if not r.fit.ok
|
|
128
|
+
except FitFailed as e:
|
|
129
|
+
print(e)
|
|
130
|
+
|
|
131
|
+
# Shared-ΔH joint fit across columns
|
|
132
|
+
multi = m.multi({"sample_1": 0.5, "sample_2": 5.0, "sample_3": 50.0})
|
|
133
|
+
multi.dH, multi.dS
|
|
134
|
+
for c in multi.columns:
|
|
135
|
+
print(c.name, c.Tm_fit, c.oligoC)
|
|
136
|
+
|
|
137
|
+
# Concentration-series van't Hoff — three regressions (raw / vH / full fit)
|
|
138
|
+
conc = m.concentration({"sample_1": 0.5, "sample_2": 5.0, "sample_3": 50.0})
|
|
139
|
+
for s in (conc.series_raw, conc.series_vh, conc.series_fit):
|
|
140
|
+
if s.ok:
|
|
141
|
+
print(s.dH, s.dG_37, s.r_squared)
|
|
142
|
+
|
|
143
|
+
# Every column at once
|
|
144
|
+
batch = m.single_all(oligo=0.5) # dict[str, SingleResult]
|
|
145
|
+
|
|
146
|
+
# Export forms (same shape the browser download produces)
|
|
147
|
+
r.to_dict(); r.to_dataframe(); r.to_csv("out.csv")
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### From raw arrays — no CSV
|
|
151
|
+
|
|
152
|
+
`signals` accepts a 1D array, a `{name: array}` mapping, or a 2D array
|
|
153
|
+
(one column per signal, with optional `names=`):
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
import numpy as np
|
|
157
|
+
from rnamelt import MeltAnalysis
|
|
158
|
+
|
|
159
|
+
T = np.linspace(20, 90, 71)
|
|
160
|
+
|
|
161
|
+
m = MeltAnalysis.from_arrays(T, signal_F4) # 1D
|
|
162
|
+
m = MeltAnalysis.from_arrays(T, {"F4": sigF4, "F5": sigF5}) # mapping
|
|
163
|
+
m = MeltAnalysis.from_arrays(T, arr2d, names=["A", "B", "C"]) # 2D
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Plotting (optional)
|
|
167
|
+
|
|
168
|
+
`pip install 'rnamelt[plots]'`. Each result class has a `.plot()` method
|
|
169
|
+
that lazy-imports `rnamelt.plots`, so matplotlib never enters the
|
|
170
|
+
browser/Pyodide bundle.
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
fig, axes = m.single("F4").plot(figsize=(15, 5)) # raw / vH / full fit
|
|
174
|
+
fig, ax = m.multi(oligo_multi).plot()
|
|
175
|
+
fig, axes = m.concentration(oligo_multi).plot()
|
|
176
|
+
|
|
177
|
+
# Convenience shortcut on the analyzer
|
|
178
|
+
fig, axes, result = m.plot("F4")
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Functional helpers (back-compat)
|
|
182
|
+
|
|
183
|
+
Thin wrappers around the same orchestrator that take a cleaned
|
|
184
|
+
`pandas.DataFrame` and return the legacy dict directly:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
import pandas as pd
|
|
188
|
+
from rnamelt import (
|
|
189
|
+
analyze_single, analyze_multi, analyze_concentration, analyze_csv,
|
|
190
|
+
)
|
|
191
|
+
from rnamelt.cleaning import clean
|
|
192
|
+
|
|
193
|
+
df = clean(pd.read_csv("melt.csv"))
|
|
194
|
+
analyze_single(df, "sample_1", struct_type="heterodimer", oligo=0.5)
|
|
195
|
+
analyze_multi(df, {"sample_1": 0.5, "sample_2": 5.0})
|
|
196
|
+
analyze_concentration(df, {"sample_1": 0.5, "sample_2": 5.0})
|
|
197
|
+
|
|
198
|
+
# read_csv + clean + dispatch in one call
|
|
199
|
+
analyze_csv("melt.csv", mode="single", column="sample_1", oligo=0.5)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Analysis modes
|
|
203
|
+
|
|
204
|
+
All three modes go through `rnamelt.analysis_melting.run(df, params)`;
|
|
205
|
+
the `column` param selects the mode.
|
|
206
|
+
|
|
207
|
+
| `column` value | Mode | What it does |
|
|
208
|
+
|------------------------|-------------------------------------|--------------|
|
|
209
|
+
| any signal-column name | **Single column** | Tm by baseline intersection, van't Hoff linearisation, and a full two-state nonlinear fit. Returns ΔH / ΔS / ΔG / Tm from each method. |
|
|
210
|
+
| `__multi__` | **Shared-ΔH joint fit** | Fit every column with common ΔH, ΔS but independent baselines. Each column carries its own concentration. |
|
|
211
|
+
| `__concentration__` | **Concentration-series van't Hoff** | Three Tm values per column (raw / vH / full-fit) → `1/Tm = (R/ΔH)·ln(C_T/f) + ΔS/ΔH` regression for each. `f = 1` (homodimer) or `f = 4` (heterodimer). |
|
|
212
|
+
|
|
213
|
+
## Two-state model
|
|
214
|
+
|
|
215
|
+
Observed signal is a linear combination of folded and unfolded baselines,
|
|
216
|
+
weighted by the fraction unfolded θ(T):
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
A(T) = (m_F·T + b_F)·(1 − θ) + (m_U·T + b_U)·θ
|
|
220
|
+
K(T) = exp(−ΔG / RT), ΔG = ΔH − T·ΔS
|
|
221
|
+
θ = K / (1 + K) (monomer)
|
|
222
|
+
θ = (√(1 + 8·c₀·K) − 1) / (4·c₀·K) (dimer)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Units: ΔH in kcal/mol, ΔS in kcal/(mol·K), T in K
|
|
226
|
+
(`T_K = T_C − T0`, `T0 = −273.15`).
|
|
227
|
+
|
|
228
|
+
## Browser app
|
|
229
|
+
|
|
230
|
+
The same Python package runs client-side via [Pyodide](https://pyodide.org/);
|
|
231
|
+
no server, no upload, no install. Drop a CSV onto
|
|
232
|
+
<https://tspich.github.io/RNAmelt/> to try it. The source for the page is
|
|
233
|
+
`index.html` in the [repository](https://github.com/tspich/RNAmelt).
|
|
234
|
+
|
|
235
|
+
## Development
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
git clone https://github.com/tspich/RNAmelt
|
|
239
|
+
cd RNAmelt
|
|
240
|
+
pip install -e '.[plots]'
|
|
241
|
+
|
|
242
|
+
pytest tests/ # or: python -m unittest discover tests
|
|
243
|
+
python -m build # sdist + wheel into dist/
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Module layering is strict: `functions` → `methods` → `analysis_melting` →
|
|
247
|
+
`api` → `__main__` / browser. `functions` stays stateless. `plots` is
|
|
248
|
+
lazy-imported so the browser bundle never pulls matplotlib.
|
|
249
|
+
|
|
250
|
+
## License
|
|
251
|
+
|
|
252
|
+
[MIT](LICENSE).
|
|
253
|
+
|
|
254
|
+
## Links
|
|
255
|
+
|
|
256
|
+
- Source: <https://github.com/tspich/RNAmelt>
|
|
257
|
+
- Issues: <https://github.com/tspich/RNAmelt/issues>
|
|
258
|
+
- Live demo: <https://tspich.github.io/RNAmelt/>
|
rnamelt-0.1.0/README.md
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# rnamelt
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/rnamelt/)
|
|
4
|
+
[](https://pypi.org/project/rnamelt/)
|
|
5
|
+
[](https://github.com/tspich/RNAmelt/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
Two-state thermodynamic analysis (ΔH, ΔS, ΔG, Tm) of UV-absorbance and
|
|
8
|
+
fluorescence RNA / DNA melting curves. One small, dependency-light Python
|
|
9
|
+
package that works as a library, a CLI, and a browser app.
|
|
10
|
+
|
|
11
|
+
- **Library** — `from rnamelt import MeltAnalysis`, typed result objects,
|
|
12
|
+
optional `matplotlib` plotting.
|
|
13
|
+
- **CLI** — `rnamelt FILE.csv [...]` for batch / scripted runs.
|
|
14
|
+
- **Browser** — the same Python pipeline runs client-side via Pyodide.
|
|
15
|
+
Live demo: <https://tspich.github.io/RNAmelt/>.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install rnamelt # base — numpy, pandas, scipy
|
|
21
|
+
pip install 'rnamelt[plots]' # adds matplotlib for .plot() helpers
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Requires Python ≥ 3.10.
|
|
25
|
+
|
|
26
|
+
## Quickstart
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from rnamelt import MeltAnalysis
|
|
30
|
+
|
|
31
|
+
m = MeltAnalysis.from_csv("melt.csv", struct_type="heterodimer")
|
|
32
|
+
r = m.single("sample_1", oligo=0.5) # oligo in µM
|
|
33
|
+
print(r.Tm_fit, r.fit.dH, r.fit.dG)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
rnamelt melt.csv --column sample_1 --oligo 0.5 --struct-type heterodimer
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## CSV input format
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
temperature, sample_1, sample_2, ...
|
|
44
|
+
20.0, 0.412, 0.388, ...
|
|
45
|
+
20.5, 0.415, 0.391, ...
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- Column 0: temperature in °C (any header name).
|
|
49
|
+
- Columns 1+: absorbance or fluorescence (any header name).
|
|
50
|
+
- Rows entirely NaN, or with a missing temperature, are dropped.
|
|
51
|
+
|
|
52
|
+
## CLI
|
|
53
|
+
|
|
54
|
+
Same orchestrator as the library. JSON on stdout; pass `--csv-out` for a
|
|
55
|
+
results CSV in the same format the browser produces.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# van't Hoff + full fit on every signal column
|
|
59
|
+
rnamelt melt.csv --csv-out batch.csv
|
|
60
|
+
|
|
61
|
+
# single column
|
|
62
|
+
rnamelt melt.csv --column sample_1 --struct-type heterodimer \
|
|
63
|
+
--oligo 5.0 --T-low 20 --T-high 90 --csv-out single.csv
|
|
64
|
+
|
|
65
|
+
# shared-ΔH joint fit across columns
|
|
66
|
+
rnamelt melt.csv --column __multi__ --struct-type heterodimer \
|
|
67
|
+
--oligo-multi sample_1=0.5 --oligo-multi sample_2=5.0
|
|
68
|
+
|
|
69
|
+
# concentration-series van't Hoff (1/Tm vs ln(C_T/f))
|
|
70
|
+
rnamelt melt.csv --column __concentration__ --struct-type heterodimer \
|
|
71
|
+
--oligo-multi sample_1=0.5 --oligo-multi sample_2=5.0 --oligo-multi sample_3=50 \
|
|
72
|
+
--csv-out conc.csv
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`rnamelt --help` lists every flag, including three solver-tuning groups
|
|
76
|
+
(`scipy.optimize.least_squares` overrides, van't Hoff linearisation,
|
|
77
|
+
full-fit initial guesses). Exit codes: `0` success, `1` analysis error,
|
|
78
|
+
`2` bad input.
|
|
79
|
+
|
|
80
|
+
## Python API
|
|
81
|
+
|
|
82
|
+
The recommended surface is `MeltAnalysis`. It holds the DataFrame and
|
|
83
|
+
every configuration knob (struct type, salt, transition window, baseline
|
|
84
|
+
offsets, solver / vH / fit-init overrides) and exposes the three modes as
|
|
85
|
+
methods returning typed result objects from `rnamelt.results`.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from rnamelt import MeltAnalysis, FitFailed
|
|
89
|
+
|
|
90
|
+
m = MeltAnalysis.from_csv(
|
|
91
|
+
"melt.csv",
|
|
92
|
+
struct_type="heterodimer", # "heterodimer" | "homodimer" | "monomer"
|
|
93
|
+
salt=150.0, # NaCl (mM) — metadata only
|
|
94
|
+
T_low=None, T_high=None, # transition window (°C); None = full range
|
|
95
|
+
bl_lower_offset=10.0, # folded baseline span above T_low (°C)
|
|
96
|
+
bl_upper_offset=10.0, # unfolded baseline span below T_high (°C)
|
|
97
|
+
)
|
|
98
|
+
m.signal_columns # ['sample_1', 'sample_2', ...]
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Three modes
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
# Single column — returns SingleResult; numpy arrays live on the object
|
|
105
|
+
r = m.single("sample_1", oligo=0.5)
|
|
106
|
+
r.Tm_raw, r.vh.dH, r.fit.Tm, r.fit.dG
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
r.fit.require() # raises FitFailed if not r.fit.ok
|
|
110
|
+
except FitFailed as e:
|
|
111
|
+
print(e)
|
|
112
|
+
|
|
113
|
+
# Shared-ΔH joint fit across columns
|
|
114
|
+
multi = m.multi({"sample_1": 0.5, "sample_2": 5.0, "sample_3": 50.0})
|
|
115
|
+
multi.dH, multi.dS
|
|
116
|
+
for c in multi.columns:
|
|
117
|
+
print(c.name, c.Tm_fit, c.oligoC)
|
|
118
|
+
|
|
119
|
+
# Concentration-series van't Hoff — three regressions (raw / vH / full fit)
|
|
120
|
+
conc = m.concentration({"sample_1": 0.5, "sample_2": 5.0, "sample_3": 50.0})
|
|
121
|
+
for s in (conc.series_raw, conc.series_vh, conc.series_fit):
|
|
122
|
+
if s.ok:
|
|
123
|
+
print(s.dH, s.dG_37, s.r_squared)
|
|
124
|
+
|
|
125
|
+
# Every column at once
|
|
126
|
+
batch = m.single_all(oligo=0.5) # dict[str, SingleResult]
|
|
127
|
+
|
|
128
|
+
# Export forms (same shape the browser download produces)
|
|
129
|
+
r.to_dict(); r.to_dataframe(); r.to_csv("out.csv")
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### From raw arrays — no CSV
|
|
133
|
+
|
|
134
|
+
`signals` accepts a 1D array, a `{name: array}` mapping, or a 2D array
|
|
135
|
+
(one column per signal, with optional `names=`):
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
import numpy as np
|
|
139
|
+
from rnamelt import MeltAnalysis
|
|
140
|
+
|
|
141
|
+
T = np.linspace(20, 90, 71)
|
|
142
|
+
|
|
143
|
+
m = MeltAnalysis.from_arrays(T, signal_F4) # 1D
|
|
144
|
+
m = MeltAnalysis.from_arrays(T, {"F4": sigF4, "F5": sigF5}) # mapping
|
|
145
|
+
m = MeltAnalysis.from_arrays(T, arr2d, names=["A", "B", "C"]) # 2D
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Plotting (optional)
|
|
149
|
+
|
|
150
|
+
`pip install 'rnamelt[plots]'`. Each result class has a `.plot()` method
|
|
151
|
+
that lazy-imports `rnamelt.plots`, so matplotlib never enters the
|
|
152
|
+
browser/Pyodide bundle.
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
fig, axes = m.single("F4").plot(figsize=(15, 5)) # raw / vH / full fit
|
|
156
|
+
fig, ax = m.multi(oligo_multi).plot()
|
|
157
|
+
fig, axes = m.concentration(oligo_multi).plot()
|
|
158
|
+
|
|
159
|
+
# Convenience shortcut on the analyzer
|
|
160
|
+
fig, axes, result = m.plot("F4")
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Functional helpers (back-compat)
|
|
164
|
+
|
|
165
|
+
Thin wrappers around the same orchestrator that take a cleaned
|
|
166
|
+
`pandas.DataFrame` and return the legacy dict directly:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
import pandas as pd
|
|
170
|
+
from rnamelt import (
|
|
171
|
+
analyze_single, analyze_multi, analyze_concentration, analyze_csv,
|
|
172
|
+
)
|
|
173
|
+
from rnamelt.cleaning import clean
|
|
174
|
+
|
|
175
|
+
df = clean(pd.read_csv("melt.csv"))
|
|
176
|
+
analyze_single(df, "sample_1", struct_type="heterodimer", oligo=0.5)
|
|
177
|
+
analyze_multi(df, {"sample_1": 0.5, "sample_2": 5.0})
|
|
178
|
+
analyze_concentration(df, {"sample_1": 0.5, "sample_2": 5.0})
|
|
179
|
+
|
|
180
|
+
# read_csv + clean + dispatch in one call
|
|
181
|
+
analyze_csv("melt.csv", mode="single", column="sample_1", oligo=0.5)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Analysis modes
|
|
185
|
+
|
|
186
|
+
All three modes go through `rnamelt.analysis_melting.run(df, params)`;
|
|
187
|
+
the `column` param selects the mode.
|
|
188
|
+
|
|
189
|
+
| `column` value | Mode | What it does |
|
|
190
|
+
|------------------------|-------------------------------------|--------------|
|
|
191
|
+
| any signal-column name | **Single column** | Tm by baseline intersection, van't Hoff linearisation, and a full two-state nonlinear fit. Returns ΔH / ΔS / ΔG / Tm from each method. |
|
|
192
|
+
| `__multi__` | **Shared-ΔH joint fit** | Fit every column with common ΔH, ΔS but independent baselines. Each column carries its own concentration. |
|
|
193
|
+
| `__concentration__` | **Concentration-series van't Hoff** | Three Tm values per column (raw / vH / full-fit) → `1/Tm = (R/ΔH)·ln(C_T/f) + ΔS/ΔH` regression for each. `f = 1` (homodimer) or `f = 4` (heterodimer). |
|
|
194
|
+
|
|
195
|
+
## Two-state model
|
|
196
|
+
|
|
197
|
+
Observed signal is a linear combination of folded and unfolded baselines,
|
|
198
|
+
weighted by the fraction unfolded θ(T):
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
A(T) = (m_F·T + b_F)·(1 − θ) + (m_U·T + b_U)·θ
|
|
202
|
+
K(T) = exp(−ΔG / RT), ΔG = ΔH − T·ΔS
|
|
203
|
+
θ = K / (1 + K) (monomer)
|
|
204
|
+
θ = (√(1 + 8·c₀·K) − 1) / (4·c₀·K) (dimer)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Units: ΔH in kcal/mol, ΔS in kcal/(mol·K), T in K
|
|
208
|
+
(`T_K = T_C − T0`, `T0 = −273.15`).
|
|
209
|
+
|
|
210
|
+
## Browser app
|
|
211
|
+
|
|
212
|
+
The same Python package runs client-side via [Pyodide](https://pyodide.org/);
|
|
213
|
+
no server, no upload, no install. Drop a CSV onto
|
|
214
|
+
<https://tspich.github.io/RNAmelt/> to try it. The source for the page is
|
|
215
|
+
`index.html` in the [repository](https://github.com/tspich/RNAmelt).
|
|
216
|
+
|
|
217
|
+
## Development
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
git clone https://github.com/tspich/RNAmelt
|
|
221
|
+
cd RNAmelt
|
|
222
|
+
pip install -e '.[plots]'
|
|
223
|
+
|
|
224
|
+
pytest tests/ # or: python -m unittest discover tests
|
|
225
|
+
python -m build # sdist + wheel into dist/
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Module layering is strict: `functions` → `methods` → `analysis_melting` →
|
|
229
|
+
`api` → `__main__` / browser. `functions` stays stateless. `plots` is
|
|
230
|
+
lazy-imported so the browser bundle never pulls matplotlib.
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
[MIT](LICENSE).
|
|
235
|
+
|
|
236
|
+
## Links
|
|
237
|
+
|
|
238
|
+
- Source: <https://github.com/tspich/RNAmelt>
|
|
239
|
+
- Issues: <https://github.com/tspich/RNAmelt/issues>
|
|
240
|
+
- Live demo: <https://tspich.github.io/RNAmelt/>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "rnamelt"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Two-state thermodynamic analysis (ΔH, ΔS, ΔG, Tm) of RNA melting curves"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "tspich" },
|
|
14
|
+
{ name = "RaumZeit" },
|
|
15
|
+
]
|
|
16
|
+
keywords = ["rna", "melting curve", "thermodynamics"]
|
|
17
|
+
dependencies = [
|
|
18
|
+
"numpy>=1.23",
|
|
19
|
+
"pandas>=1.5",
|
|
20
|
+
"scipy>=1.10",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
plots = ["matplotlib>=3.5"]
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
rnamelt = "rnamelt.__main__:main"
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/tspich/RNAmelt"
|
|
31
|
+
Issues = "https://github.com/tspich/RNAmelt/issues"
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build.targets.wheel]
|
|
34
|
+
packages = ["rnamelt"]
|
|
35
|
+
|
|
36
|
+
[tool.hatch.build.targets.sdist]
|
|
37
|
+
include = [
|
|
38
|
+
"rnamelt",
|
|
39
|
+
"README.md",
|
|
40
|
+
"pyproject.toml",
|
|
41
|
+
]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""rnamelt — RNA / DNA melting-curve thermodynamic analysis.
|
|
2
|
+
|
|
3
|
+
Public API:
|
|
4
|
+
MeltAnalysis — class façade
|
|
5
|
+
analyze_single, analyze_multi, analyze_concentration — DataFrame in
|
|
6
|
+
analyze_csv — path in
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from rnamelt.api import (
|
|
10
|
+
MeltAnalysis,
|
|
11
|
+
analyze_concentration,
|
|
12
|
+
analyze_csv,
|
|
13
|
+
analyze_multi,
|
|
14
|
+
analyze_single,
|
|
15
|
+
)
|
|
16
|
+
from rnamelt.methods import FIT_INIT_DEFAULTS, SOLVER_DEFAULTS, VH_DEFAULTS
|
|
17
|
+
from rnamelt.results import (
|
|
18
|
+
BatchResult,
|
|
19
|
+
ConcentrationCurve,
|
|
20
|
+
ConcentrationResult,
|
|
21
|
+
ConcentrationSeries,
|
|
22
|
+
FitFailed,
|
|
23
|
+
FullFit,
|
|
24
|
+
MultiColumnResult,
|
|
25
|
+
MultiResult,
|
|
26
|
+
SingleResult,
|
|
27
|
+
VanHoffFit,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
ANALYSES = [
|
|
31
|
+
"rnamelt.analysis_melting",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"MeltAnalysis",
|
|
36
|
+
"analyze_single",
|
|
37
|
+
"analyze_multi",
|
|
38
|
+
"analyze_concentration",
|
|
39
|
+
"analyze_csv",
|
|
40
|
+
"SingleResult",
|
|
41
|
+
"MultiResult",
|
|
42
|
+
"MultiColumnResult",
|
|
43
|
+
"ConcentrationResult",
|
|
44
|
+
"ConcentrationCurve",
|
|
45
|
+
"ConcentrationSeries",
|
|
46
|
+
"BatchResult",
|
|
47
|
+
"VanHoffFit",
|
|
48
|
+
"FullFit",
|
|
49
|
+
"FitFailed",
|
|
50
|
+
"SOLVER_DEFAULTS",
|
|
51
|
+
"VH_DEFAULTS",
|
|
52
|
+
"FIT_INIT_DEFAULTS",
|
|
53
|
+
"ANALYSES",
|
|
54
|
+
]
|