22from sparging .config import ureg
33from typing import TYPE_CHECKING
44import numpy as np
5+ import warnings
6+ from scipy .optimize import curve_fit
57
68if TYPE_CHECKING :
79 import pint
810 from sparging .model import SimulationResults
911 import matplotlib .pyplot as plt
1012
1113
12- def indices_from_times (sim_output : SimulationResults , times_ref : list [pint .Quantity ]):
13- i_matching = [np .argmin (np .abs (sim_output .times - t )) for t in times_ref ]
14- times_matching = [sim_output .times [i ] for i in i_matching ]
15-
16- assert len (i_matching ) == len (times_ref )
17- return i_matching , times_matching
14+ def idx_from_t (times : list [pint .Quantity ], timestamp : pint .Quantity ) -> pint .Quantity :
15+ idx = np .argmin (np .abs (times - timestamp ))
16+ time = times [idx ].to (timestamp .units )
17+ if not np .isclose (times [idx ], timestamp ):
18+ warnings .warn (
19+ f"Requested time { timestamp } does not exactly match discrete times. Using closest time { time } for index calculation."
20+ )
21+ return idx
1822
1923
2024def plot_profile (
@@ -25,26 +29,76 @@ def plot_profile(
2529 colors : list = None ,
2630 ** kwargs ,
2731):
28- i_to_plot , _ = indices_from_times (sim_output , times )
32+ idx_to_plot = [ idx_from_t (sim_output . times , t ) for t in times ]
2933 y_to_plot = getattr (sim_output , var_name )
30- for j , idx in enumerate (i_to_plot ):
34+ for j , idx in enumerate (idx_to_plot ):
3135 ax .plot (
3236 sim_output .x_ct ,
3337 y_to_plot [idx ],
3438 label = f"t = { sim_output .times [idx ]:.2f} " ,
3539 c = colors [j ] if colors is not None else None ,
3640 ** kwargs ,
3741 )
38- ax .legend ()
39- ax .set_xlabel (r"z [m]" )
40- ax .set_ylabel (var_name ) # TODO leverage pint for units
41- ax .set_title (var_name + " profile" )
42- ax .grid ()
43-
44-
45- def plot_signal (sim_output : SimulationResults , ax : plt .Axes , var_name : str , ** kwargs ):
46- ax .plot (sim_output .times , getattr (sim_output , var_name ), ** kwargs )
47- # ax.set_xlabel(r"t [s]")
48- # ax.set_ylabel(var_name) # TODO leverage pint for units
49- # ax.set_title(var_name + " profile")
50- # ax.grid()
42+
43+
44+ # TODO remove
45+ # def plot_signal(sim_output: SimulationResults, ax: plt.Axes, var_name: str, **kwargs):
46+ # ax.plot(sim_output.times, getattr(sim_output, var_name), **kwargs)
47+ # # ax.set_xlabel(r"t [s]")
48+ # # ax.set_ylabel(var_name) # TODO leverage pint for units
49+ # # ax.set_title(var_name + " profile")
50+ # # ax.grid()
51+
52+
53+ def get_residual_fraction (
54+ T2_inventories : np .ndarray [pint .Quantity ],
55+ times : np .ndarray [pint .Quantity ],
56+ t_0 : pint .Quantity ,
57+ t_end : pint .Quantity ,
58+ ) -> pint .Quantity :
59+ i_0 = idx_from_t (times , t_0 )
60+ i_end = idx_from_t (times , t_end )
61+ return T2_inventories [i_end ] / T2_inventories [i_0 ]
62+
63+
64+ def fit_exp (
65+ vec : np .ndarray [pint .Quantity ],
66+ times : np .ndarray [pint .Quantity ],
67+ t_0 : pint .Quantity ,
68+ t_end : pint .Quantity ,
69+ phase : str ,
70+ ): # TODO add warning if error or std is too large
71+ """
72+ - phase = 'decay' or 'rampup'
73+ """
74+
75+ def fitting_func (t , tau , n0 ):
76+ match phase :
77+ case "decay" :
78+ return n0 * np .exp (- t / tau )
79+ case "rampup" :
80+ return n0 * (1 - np .exp (- t / tau ))
81+ case _:
82+ raise ValueError ("Invalid phase. Must be 'decay' or 'rampup'." )
83+
84+ idx_0 = idx_from_t (times , t_0 )
85+ idx_end = idx_from_t (times , t_end )
86+ t_0 = times [idx_0 ]
87+ t_end = times [idx_end ]
88+ print (
89+ f"Fitting from t={ t_0 .to ('hour' )} to t={ t_end .to ('hour' )} (indices { idx_0 } to { idx_end } )"
90+ )
91+ tau_guess = 10000 * ureg .s
92+ n0_guess = vec [idx_0 ] if phase == "decay" else vec [idx_end ]
93+
94+ # wrapped_fitting_func = ureg.wraps(vec.units, (None, vec.units, "s"))(fitting_func)
95+
96+ popt , pcov = curve_fit (
97+ fitting_func ,
98+ (times [idx_0 : idx_end + 1 ] - t_0 ).to ("s" ).magnitude ,
99+ vec [idx_0 : idx_end + 1 ].magnitude ,
100+ p0 = [tau_guess .to ("s" ).magnitude , n0_guess .magnitude ],
101+ )
102+ print (f"std:{ np .sqrt (np .diag (pcov ))} " )
103+
104+ return (popt [0 ] * ureg .s , popt [1 ] * vec .units ), pcov
0 commit comments