-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathexample4.py
130 lines (106 loc) · 5.53 KB
/
example4.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
===============================================================================
SFPPy Example: fitting an arbirary combination of parameters D[i] and k[i]
from experiments using layerLinks and dynamic simulations.
===============================================================================
Example 4: Sensitivity Analysis and Kinetic Data Fitting
---------------------------------------------------------
# **Example4.py: Estimation of D and k from Concentration Kinetics in Food Simulants**
## **Overview**
This script illustrates how **diffusivity (D)** and **partition coefficients (k)** can be estimated from **experimental concentration kinetics** in **food simulants**. The method relies on **layerLink objects**, which allow adjusting simulation parameters dynamically.
The workflow involves:
1. **Define a monolayer material (P) and a food simulant (F).**
2. **Enforce a reference partition coefficient (kF = 1) for F**, allowing kP to be interpreted as the **partition coefficient between F and P**.
3. **Perform a migration simulation** to establish a baseline.
4. **Generate pseudo-experimental data** with controlled noise levels.
5. **Evaluate the squared error (`d2 = R - E`)** between simulation (`R`) and experiment (`E`).
6. **Conduct a sensitivity analysis** by varying D and k to assess their impact.
7. **Optimize D and k simultaneously** to recover their original values.
8. **Analyze the goodness of fit and risk of overfitting.**
## **Key Concepts**
- **`layerLink` for parameter linking:** Enables modifying **D** and **k** externally without modifying the base layer object.
- **Migration simulation (`F.migration(P)`)**: Computes mass transfer from P into F.
- **Pseudo-experimental data (`R.pseudoexperiment()`)**: Creates synthetic datasets for validation.
- **Fitting (`R.fit(E)`)**: Automatically adjusts **D** and **k** to best match experimental data.
## **Expected Outcomes**
- **Validation of layerLink objects** for parameter optimization.
- **Comparison of simulated vs. experimental data** to evaluate fit quality.
- **Determination of optimal D and k values** for given experimental conditions.
--------------------------------------------------------
Summary
--------------------------------------------------------
This script demonstrates **an advanced approach for parameter estimation** in food safety analysis.
@project: SFPPy - SafeFoodPackaging Portal in Python initiative
@author: INRAE\\olivier.vitrac@agroparistech.fr
@licence: MIT
"""
# %% Import Dependencies
from patankar.layer import layer, layerLink
from patankar.food import foodlayer
# %% Define a Monolayer Material (P)
P = layer(
l=(100, "um"),
D=(1e-10, "cm**2/s"), # Diffusivity in cm²/s (internally converted to SI)
C0=1000, # Initial concentration (arbitrary units)
k=0.1 # Partition coefficient
)
# %% Define a Generic Contact Medium (F)
F = foodlayer(
contacttime=(10, "days"),
volume=(1, "L"),
surfacearea=(6, "dm**2"),
h=(1e-6, "m/s"), # Mass transfer coefficient
CF0=0, # Initial concentration in the food
k=1 # Fixed kF, allowing kP to represent kF/P
)
# %% Create and Attach `layerLink` Objects for D and k
Dreference, kreference = P.D, P.k # Store original values for comparison
D = layerLink("D", indices=0, values=Dreference) # Create link for D
k = layerLink("k", indices=0, values=kreference) # Create link for k
P.Dlink = D # Attach links to P
P.klink = k
# %% Run Initial Migration Simulation
R = F.migration(P) # Perform mass transfer simulation
R.plotCF() # Plot the reference concentration profile in food
# %% Generate Pseudo-Experimental Data
E = R.pseudoexperiment(npoints=30, std_relative=0.01) # Simulated experimental dataset
E.plotCF()
# %% Compute Initial Squared Distance (d2)
d2 = R - E # Compute squared error function
d2_original = d2() # Evaluate initial deviation from experiment
print(f"Initial squared distance (E-R)**2: {d2_original}")
# %% Sensitivity Analysis: Explore the Impact of D and k Variations
R.comparison.update(0, label="**Reference**", color="black")
R.comparison.plotCF()
# Set iteration count and color mapping
niterations = 10
cmap = R.comparison.jet(niterations)
# Vary D and k systematically
for i in range(1, niterations + 1):
D[0] = D[0] / 1.1 # Decrease D by 10%
k[0] = k[0] * 1.1 # Increase k by 10%
# Format LaTeX labels for D and k
name = f"{P.Dlatex()[0]}, {P.klatex()[0]}"
# Rerun simulation with updated parameters
R.rerun(name=name, color=cmap[i-1])
# Compute new squared distance
d2new = d2()
print(f"[{i}/{niterations}]: Distance variation = {100 * d2new / d2_original - 100:.2f}%")
# Add pseudo-experimental data to comparison
R.comparison.add(E, label="Pseudo Experiment", discrete=True)
R.comparison.plotCF() # Final visualization
# %% Optimize D and k to Recover the Original Values
d2beforeOptim = d2() # Distance before fitting
resfit = R.fit(E) # Perform parameter fitting
d2afterOptim = d2() # Distance after fitting
# Print optimization results
print(f"BEFORE OPTIMIZATION: Distance (E-R)**2 = {d2beforeOptim}")
print(f"AFTER OPTIMIZATION: Distance (E-R)**2 = {d2afterOptim}")
overfitting_flag = "[Overfitting detected]" if d2afterOptim < d2_original else ""
print(f"Variation with original = {100 * d2afterOptim / d2_original - 100:.4g}% {overfitting_flag}\n")
print(f"Fitted D = {D.values} [m²/s] vs. Original D = {Dreference} [m²/s]")
print(f"Fitted k = {k.values} [a.u.] vs. Original k = {kreference} [a.u.]")
# Plot optimized results
R.comparison.plotCF()