Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 30 additions & 40 deletions .github/skills/flow-curve-analysis/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ import rheofit

rheofit.list_models() # ['carreau', 'carreau_carreau', 'herschel_bulkley', ...]
rheofit.model_info("tccc") # equation, params, scorecard params, nested parent
rheofit.demo_source() # path to the bundled demo TRIOS JSON
rheofit.demo_source() # path to the demo TRIOS JSON (from rheodata)

rheofit.print_steps("sample.json") # step table incl. test type (Step 1 of the workflow below)
rheofit.discover_steps("sample.json") # same, as a list of dicts
Expand Down Expand Up @@ -131,36 +131,30 @@ When the skill is first introduced, offer an optional guided demo before asking

Before running anything, ask the user which data source they want to use:

- [ ] **Use bundled demo file** (quick walkthrough, no network needed)
- [ ] **Use demo dataset** (quick walkthrough, no network needed)
- [ ] **Upload local JSON file**
- [ ] **Provide JSON URL**

If the host chat UI does not support clickable checkboxes/buttons, ask for a simple reply:
`demo` / `upload` / `url`.

The demo data ships with the library, as package data:
The demo dataset comes from the [`rheodata`](https://github.com/rheopy/rheodata) package
(a rheofit dependency, so it is always installed and needs no network):

`rheofit/data/structured_shampoo.json` — a structured shampoo, flow curves at two temperatures
`caggioni_pg_carbopol_2pct` — 2% Carbopol Ultrez 21 in propylene glycol, one equilibrium
flow sweep (61 points, 10⁻³–10³ s⁻¹).

A second bundled sample covers the oscillatory views:

`rheofit/data/linear_polymer_HA_solution.json` — a hyaluronic-acid solution with an amplitude
sweep (step 0), a flow sweep (step 1) and a frequency sweep (step 2). Use this one when the user
wants to see frequency/amplitude plotting.

Never resolve either path relative to the user's working directory — use `--demo` on the CLI or
`rheofit.demo_source()` in Python, both of which locate the file inside the installed package.

Online fallback, used only if the bundled file is missing:

`https://pgone.sharepoint.com/:u:/s/i2iAcceleratedPrototypingScale/IQAW80L5GqglQIciUmYPko4CAU-CMKivBmKC78iY4VD0gdc`
Never resolve the demo path relative to the user's working directory — use `--demo` on the CLI or
`rheofit.demo_source()` in Python, which materializes the dataset as a TRIOS JSON in the
system temp dir.

Suggested prompt:

> "If you want, I can run a quick end-to-end demo using the test TRIOS JSON bundled with this skill,
> so you can see the workflow before we analyze your own sample. Shall I run that demo?"
> "If you want, I can run a quick end-to-end demo using an example dataset from the
> rheodata package, so you can see the workflow before we analyze your own sample.
> Shall I run that demo?"

If the user says yes, follow the normal approval flow with `--demo` as the input source and default output mode `png_csv`. Demo artifacts are written to `results/structured_shampoo/` under the current working directory, so the skill folder stays clean.
If the user says yes, follow the normal approval flow with `--demo` as the input source and default output mode `png_csv`. Demo artifacts are written to `results/` under the current working directory, so the skill folder stays clean.

### Demo walkthrough script (recommended)

Expand All @@ -172,26 +166,23 @@ When running the online demo, explain the workflow step-by-step:
4. Propose plan (steps, labels, model, effort, output mode) and wait for approval.
5. Run fit and report results/warnings.

The demo sample is `structured_shampoo` — a structured shampoo with Thixin. Its steps are
`0` (Flow sweep - 1), `1` (Temperature ramp - 2, not fittable) and `2` (Flow sweep - 3).
When asked whether the sample is expected to have a yield stress, the expected answer is:
The demo sample is 2% Carbopol Ultrez 21 in propylene glycol — a jammed microgel, a
soft-particle glass. Its single step is `0` (Flow sweep - 1). When asked whether the
sample is expected to have a yield stress, the expected answer is:

> **Yes** — this is a **structured shampoo** sample with **Thixin**
> (hydrogenated castor oil fibers, internally developed as a structurant),
> so the yield-stress family should be used.
> **Yes** — this is a **Carbopol gel**, so the yield-stress family should be used.

If the user accepts that framing, recommend `tccc` as the demo model and explain why:
If the user accepts that framing, recommend `tc` vs `herschel_bulkley` head-to-head as the
demo and explain why:

- The shampoo base is well represented by a **Carreau–Carreau viscosity background**
(two fixed-exponent relaxation contributions: $n=0$ for the worm-like micelles of the
surfactant base, $n=1/2$ for polymer–surfactant interaction / branched WLM).
- Thixin adds a **structured network with yield behavior**, captured by the `tc` term
($\sigma_y + \sigma_y\sqrt{\dot\gamma/\dot\gamma_c}$).
- `tccc` combines both physics in one model:
yield-stress structure + two-component shear-thinning background.
- Carbopol is a jammed microgel in a *viscous* continuous phase (propylene glycol) —
exactly where the TC (three-component) model earns its keep over Herschel–Bulkley.
- HB has no explicit viscous term, so its exponent drifts with the continuous-phase
viscosity; TC gives each dissipation mechanism its own parameter
($\sigma_y$ + elastoplastic term + $\eta_{bg}$ viscous background).

So for this demo, prefer `tccc` first; only step down the ladder if residuals/identifiability
show the extra terms are not needed.
So for this demo, fit both and compare — the documented result is TC winning ~6× on
RedChi² (6.02e-04 vs 3.65e-03), every TC parameter tightly identified.

When you present the demo plan (before asking for approval), include this workflow preview so the user can follow the steps while waiting for the fit:

Expand Down Expand Up @@ -682,8 +673,8 @@ uv run rheofit "sample.json" --steps 0 --model carreau --labels "25C"
# Structured sample, three steps, Herschel-Bulkley
uv run rheofit "sample.json" --steps 0 2 4 --model herschel_bulkley

# Bundled demo sample (structured shampoo with Thixin), TCCC
uv run rheofit --demo --steps 0 2 --model tccc --labels "Flow sweep 1" "Flow sweep 3"
# Demo dataset (2% Carbopol in propylene glycol), TC
uv run rheofit --demo --steps 0 --model tc
```

Same runs from Python:
Expand All @@ -692,8 +683,7 @@ Same runs from Python:
import rheofit

rheofit.analyze("sample.json", steps=[0, 2], model="tc", labels=["25C", "40C"])
rheofit.analyze(rheofit.demo_source(), steps=[0, 2], model="tccc",
labels=["Flow sweep 1", "Flow sweep 3"])
rheofit.analyze(rheofit.demo_source(), steps=[0], model="tc")
```

---
Expand Down Expand Up @@ -804,7 +794,7 @@ rheofit/
load_step, load_steps, fit, analyze, demo_source,
plot, list_plots, plot_info, get_plot
io.py TRIOS JSON reading, column standardisation, test-type detection,
URL download, demo data resolution
URL download, rheodata-backed demo
models/ one module per model + _fitcore.py (the shared fitting engine)
visualization/ one module per measurement type + _plotcore.py (shared plot primitives)
report.py PNG scorecard, parameter summary, PPTX builder
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ a.outputs # saved PNG / CSV / PPTX paths
```

`analyze(..., output="none")` fits without writing files. Inputs may be a local path or an
HTTP(S) URL. `rheofit.demo_source()` returns the bundled demo flow-curve file.
HTTP(S) URL. `rheofit.demo_source()` materializes the demo dataset (from the `rheodata` package) as a TRIOS JSON in the system temp dir.

It also **plots** oscillatory data 📊 — frequency and amplitude sweeps (visualization only, no
models to fit yet): `rheofit.plot(df)` picks the right view from the step's test type, and reads
Expand Down
14 changes: 7 additions & 7 deletions docs/walkthrough-carbopol-glycerin.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@ that reveals the microgel's own contribution.*

## 🧪 The dataset

`cp05_gly_newsample.json` (attached to [issue #27](https://github.com/rheopy/rheofit/issues/27),
archived here as `walkthrough/cp05_gly_newsample.json`) holds **three equilibrium flow
Dataset `caggioni_carbopol_glycerin_temp` in the [`rheodata`](https://github.com/rheopy/rheodata)
package (`pip install rheopy-rheodata`) — attached to
[issue #27](https://github.com/rheopy/rheofit/issues/27) — holds **three equilibrium flow
curves of Carbopol in glycerin at 20, 30 and 40 °C** — 51 points each, 0.001 to
100 s⁻¹, measured on a Peltier plate with 200 s thermal soaks between steps.

```python
import rheofit
import rheodata, rheofit

rheofit.print_steps("walkthrough/cp05_gly_newsample.json")
dfs = {T: rheofit.load_step("walkthrough/cp05_gly_newsample.json", i)
for i, T in enumerate([40, 30, 20])}
dfs = {T: rheodata.to_rheofit("caggioni_carbopol_glycerin_temp", f"T_{T}")
for T in [40, 30, 20]}
```

````{only} builder_html
```mermaid
flowchart TD
A["cp05_gly_newsample.json<br/>Carbopol in glycerin<br/>flow curves at 20 / 30 / 40 °C"]
A["rheodata: caggioni_carbopol_glycerin_temp<br/>Carbopol in glycerin<br/>flow curves at 20 / 30 / 40 °C"]
A --> B["Fit HB and TC<br/>at each temperature<br/>(thorough, seed 0)"]
B --> C["Head-to-head:<br/>RedChi², parameters"]
B --> D["Track TC parameters<br/>vs T"]
Expand Down
20 changes: 10 additions & 10 deletions docs/walkthrough-carreau-carreau.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ temperature like they mean it.*

## 🧪 The dataset

`aos_2_1.json` (attached to [issue #20](https://github.com/rheopy/rheofit/issues/20), archived here
as `walkthrough/aos_2_1.json`) holds equilibrium flow curves of a mixed surfactant system:
**wormlike micelles (WLM) plus a polymer solution**. Seven flow sweeps at
**18, 20, 22, 24, 26, 28 °C — plus a repeat at 18 °C** — 41 points each from 0.01 to 100 1/s.
The curves carry at least two distinct relaxation times, and that is exactly what makes them
interesting.
Dataset `caggioni_wlm_polymer_temp_series` in the [`rheodata`](https://github.com/rheopy/rheodata)
package (`pip install rheopy-rheodata`) — attached to
[issue #20](https://github.com/rheopy/rheofit/issues/20) — holds equilibrium flow curves of a
mixed surfactant system: **wormlike micelles (WLM) plus a polymer solution**. Seven flow
sweeps at **18, 20, 22, 24, 26, 28 °C — plus a repeat at 18 °C** — 41 points each from
0.01 to 100 1/s. The curves carry at least two distinct relaxation times, and that is
exactly what makes them interesting.

```python
import rheofit
import rheodata, rheofit

rheofit.print_steps("walkthrough/aos_2_1.json")
df18 = rheofit.load_step("walkthrough/aos_2_1.json", 0) # 18 °C
df18 = rheodata.to_rheofit("caggioni_wlm_polymer_temp_series", "T_18") # 18 °C
res = rheofit.fit(df18, "carreau_carreau", effort="thorough", seed=0)
```

Expand Down Expand Up @@ -112,5 +112,5 @@ an exponent of 0.61 that describes neither the micelles nor the polymer. The
microstructure-informed sum fits the physics instead of averaging it, and its parameters
repay the effort by moving with temperature the way real material parameters should.

*Dataset: `walkthrough/aos_2_1.json` (via issue #20). Fits: `rheofit.fit(...,
*Dataset: `rheodata:caggioni_wlm_polymer_temp_series` (via issue #20). Fits: `rheofit.fit(...,
"carreau_carreau", effort="thorough", seed=0)` with rheofit 1.0.2.*
57 changes: 47 additions & 10 deletions docs/walkthrough-carreau.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,37 @@ and with `rheofit` you can check, quantitatively, that they are.*

## 🧪 The dataset

`a_remake_dhr2.json` (attached to [issue #25](https://github.com/rheopy/rheofit/issues/25), archived here
as `walkthrough/a_remake_dhr2.json`) holds three equilibrium experiments on a **high molecular
weight linear polymer in aqueous solution** at **25 °C**:
The measurement session (attached to [issue #25](https://github.com/rheopy/rheofit/issues/25))
ran three equilibrium experiments on a **high molecular weight linear polymer in aqueous
solution** at **25 °C**:

| # | Step | Range | Points |
| # | Experiment | Range | Points |
|---|------|-------|--------|
| 0 | Amplitude sweep | strain 0.1 → 1000 % at ω = 1 rad/s | 41 |
| 1 | Flow sweep | shear rate 1000 → 0.01 s⁻¹ | 51 |
| 2 | Frequency sweep | ω = 100 → 0.1 rad/s at γ = 0.5 % | 31 |

The flow sweep lives in the [`rheodata`](https://github.com/rheopy/rheodata) package as
dataset `caggioni_linear_polymer_flow` (`pip install rheopy-rheodata`):

```python
import rheofit
import rheodata, rheofit

rheofit.print_steps("walkthrough/a_remake_dhr2.json")
flow = rheofit.load_step("walkthrough/a_remake_dhr2.json", 1) # flow curve
amp = rheofit.load_step("walkthrough/a_remake_dhr2.json", 0) # amplitude sweep
freq = rheofit.load_step("walkthrough/a_remake_dhr2.json", 2) # frequency sweep
flow = rheodata.to_rheofit("caggioni_linear_polymer_flow", "linear_polymer")
```

The amplitude and frequency sweeps from the same session are in rheodata too — they
feed the Cox–Merz and Delaware–Rutgers comparisons below:

```python
amp = rheodata.load("caggioni_linear_polymer_amplitude_sweep") # γ₀ = 0.1–1000 % @ ω = 1 rad/s
freq = rheodata.load("caggioni_linear_polymer_frequency_sweep") # ω = 0.1–100 rad/s @ γ₀ = 0.5 %
```

````{only} builder_html
```mermaid
flowchart TD
A["a_remake_dhr2.json<br/>high-MW linear polymer, 25 °C"]
A["high-MW linear polymer, 25 °C<br/>flow curve via rheodata"]
A --> B["Amplitude sweep<br/>γ₀ = 0.1–1000 % @ ω = 1 rad/s"]
A --> C["Flow sweep<br/>γ̇ = 0.01–1000 s⁻¹"]
A --> D["Frequency sweep<br/>ω = 0.1–100 rad/s @ γ = 0.5 %"]
Expand Down Expand Up @@ -89,6 +97,21 @@ linear oscillations. No deep theory demanded it; the data did
Plotting $|\eta^*(\omega)|$ from the frequency sweep directly onto $\eta(\dot{\gamma})$
from the flow curve:

```python
import numpy as np

f = freq.df
w = f["omega_rad/s"].to_numpy()
eta_star = np.hypot(f["Gp_Pa"], f["Gpp_Pa"]) / w # |η*(ω)|

gd = flow["Shear rate / 1/s"].to_numpy()
eta = flow["Stress / Pa"].to_numpy() / gd # η(γ̇)
eta_ss = np.exp(np.interp(np.log(w), np.log(gd), np.log(eta)))

log_rms = np.sqrt(np.mean((np.log(eta_star) - np.log(eta_ss))**2))
print(f"Cox–Merz log-RMS: {log_rms*100:.1f} %") # → 2.3 %
```

![Cox–Merz superposition: complex viscosity from the frequency sweep overlaid on the steady-shear flow curve](walkthrough/fig8_cox_merz.png)

The two curves coincide to within **2.3 %** (log-RMS) over three decades, 0.1–100 s⁻¹.
Expand All @@ -109,6 +132,20 @@ $$\eta(\dot{\gamma}) = \eta'(\gamma_0 \omega)\Big|_{\dot{\gamma}=\gamma_0\omega}
Here the amplitude sweep ran at ω = 1 rad/s, so $\gamma_0\omega$ spans 0.001–10 s⁻¹ —
right across the Carreau plateau and into the thinning regime:

```python
a = amp.df
g0 = a["strain_pct"].to_numpy() / 100 # strain fraction
eta_prime = a["Gpp_Pa"].to_numpy() / 1.0 # η' = G″/ω at ω = 1 rad/s
gdot = g0 * 1.0 # γ̇ = γ₀ω

gd = flow["Shear rate / 1/s"].to_numpy()
eta = flow["Stress / Pa"].to_numpy() / gd # η(γ̇)
overlap = gdot >= gd.min() # stay inside the flow curve's range
eta_ss = np.exp(np.interp(np.log(gdot[overlap]), np.log(gd), np.log(eta)))
log_rms = np.sqrt(np.mean((np.log(eta_prime[overlap]) - np.log(eta_ss))**2))
print(f"Delaware–Rutgers log-RMS: {log_rms*100:.0f} %") # → 9 %
```

![Delaware–Rutgers superposition: dynamic viscosity from the amplitude sweep overlaid on the steady-shear flow curve](walkthrough/fig9_delaware_rutgers.png)

Agreement within **9 %** (log-RMS). The slight shortfall at the highest strain rates is
Expand Down
20 changes: 11 additions & 9 deletions docs/walkthrough.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ code the experts maintain.

A single flow sweep of **2% Carbopol Ultrez 21 dispersed in propylene glycol**
(61 points, shear rates 10⁻³–10³ s⁻¹, 20 °C, concentric-cylinder geometry). The data
ships with the repo: `rheofit/data/pgpol_2pc_ultrez21.xlsx` (original export) and
`rheofit/data/pgpol_2pc_ultrez21.json` (TRIOS-shaped conversion used below).
lives in the [`rheodata`](https://github.com/rheopy/rheodata) package as dataset
`caggioni_pg_carbopol_2pct` (`pip install rheopy-rheodata`) — rheofit no longer bundles
example data, it loads it from rheodata.

Why this system? Carbopol is a jammed microgel — a soft-particle glass — and propylene
glycol is a *viscous* continuous phase. That combination is exactly where the TC
Expand All @@ -56,7 +57,7 @@ gives each dissipation mechanism its own parameter.

The user points at the data file. Behind the scenes, the harness:

1. 📥 Locates the dataset (`rheofit/data/pgpol_2pc_ultrez21.xlsx`)
1. 📥 Locates the dataset (`rheodata` → `caggioni_pg_carbopol_2pct`)
2. ⚙️ Installs the pinned environment (`uv sync` — numpy, scipy, matplotlib, pandas, exact versions from `uv.lock`)
3. 📘 Loads the skill — the agent now knows the models, the fitting contract, and the workflow

Expand Down Expand Up @@ -109,14 +110,15 @@ Once approved, the agent runs both fits — thorough effort, relative-weighted o
(every decade of stress counts equally), Sobol multi-start with a fixed seed. The exact
commands, reproducible anywhere:

```bash
python -m rheofit rheofit/data/pgpol_2pc_ultrez21.json --steps 0 --model tc \
--effort thorough --seed 0
python -m rheofit rheofit/data/pgpol_2pc_ultrez21.json --steps 0 --model herschel_bulkley \
--effort thorough --seed 0
```python
import rheodata, rheofit

flow = rheodata.to_rheofit("caggioni_pg_carbopol_2pct", "carbopol_2pct")
res_tc = rheofit.fit(flow, "tc", effort="thorough", seed=0)
res_hb = rheofit.fit(flow, "herschel_bulkley", effort="thorough", seed=0)
```

Reproducibility isn't a hope, it's a flag: `--seed` makes any run exactly repeatable,
Reproducibility isn't a hope, it's a flag: `seed=0` makes any run exactly repeatable,
`uv.lock` pins the environment, and the skill, the CLI, and the Python API all call the
same `rheofit` functions. *Skill and library are one thing* — the agent cannot drift from
the documented behavior, because it *is* the documented behavior.
Expand Down
Loading
Loading