From 4b8f74bcaa608c69d10b570c5862d37fe7bbcc65 Mon Sep 17 00:00:00 2001 From: Jiri Zbytovsky Date: Sun, 22 Sep 2024 20:26:26 +0200 Subject: [PATCH] Rename Fit variables consistently, cleanup --- appPy/cli/fit.py | 2 +- appPy/src/fit/Fit.py | 18 ++---- appPy/src/fit/Fit_VLE.py | 15 +++-- appPy/src/fit/Fit_Vapor.py | 57 ++++++++++--------- appPy/src/plot/Fit_Vapor_plot.py | 10 ++-- .../actions/FitVLE/FitVLEResultsDialog.tsx | 2 +- .../FitVapor/FitVaporResultsDialog.tsx | 12 ++-- appUI/src/adapters/api/types/fitTypes.ts | 33 ++++++----- dev/notes_VLizard.txt | 1 - docs/appPy.md | 6 +- http/fit.http | 5 +- 11 files changed, 78 insertions(+), 83 deletions(-) delete mode 100644 dev/notes_VLizard.txt diff --git a/appPy/cli/fit.py b/appPy/cli/fit.py index 8a46442..4e0cf5f 100644 --- a/appPy/cli/fit.py +++ b/appPy/cli/fit.py @@ -17,7 +17,7 @@ @click.command() @click.argument('compound1') @click.argument('compound2') -@click.option('-m', '--model', help=f'Which model to fit, enter one of: {model_list}') +@click.argument('model') @click.option('-d', '--datasets', help='Comma-separated exact dataset names, otherwise do all datasets of the system') @click.option('-p', '--params', help='Comma-separated initial parameters, will ignore params saved in file') @click.option('-c', '--consts', help='Comma-separated names of parameters to be kept constant') diff --git a/appPy/src/fit/Fit.py b/appPy/src/fit/Fit.py index b44a3fb..2d66e0c 100644 --- a/appPy/src/fit/Fit.py +++ b/appPy/src/fit/Fit.py @@ -15,24 +15,16 @@ def __init__(self, supported_models, model_name, params0, const_param_names=None const_param_names (list of str): names of parameter names to be kept constant during optimization. """ super().__init__() - self.keys_to_serialize = [ - 'is_optimized', 'nparams', 'nparams0', 'RMS_init', 'RMS_final', 'AAD_init', 'AAD_final' - ] + self.keys_to_serialize = ['nparams0', 'RMS0', 'AAD0'] self.supported_models = supported_models - self.is_optimized = False # whether optimization has been performed self.model = get_model_by_name(supported_models, model_name) self.params0, self.nparams0 = self.__parse_params0(params0) # initial params as np.array, and as dict - self.params = self.params0 # optimization result params as np.array - self.nparams = self.nparams0 self.const_param_names = self.__parse_const_param_names(const_param_names) - # initial and final objective function values - self.RMS_init = None - self.AAD_init = None - self.RMS_final = None - self.AAD_final = None + # initial objective function values + self.RMS0 = self.AAD0 = None def set_named_params(self, params): """Compose vector of ordered params into an ordered dict of named params.""" @@ -44,12 +36,12 @@ def __parse_params0(self, params0): params0: either ordered list of float, or named params as ordered key:value dict """ model = self.model - if not params0: return model.params0, dict(zip(model.param_names, model.params0)) + if not params0: return model.params0, self.set_named_params(model.params0) if isinstance(params0, dict): nparams0 = params0 params0 = [params0[key] for key in model.param_names] else: - nparams0 = dict(zip(model.param_names, params0)) + nparams0 = self.set_named_params(params0) if len(params0) != model.n_params: raise AppException(f'{model.display_name} model expects {model.n_params} parameters, got {len(params0)}!') return params0, nparams0 diff --git a/appPy/src/fit/Fit_VLE.py b/appPy/src/fit/Fit_VLE.py index 869439d..ded895f 100644 --- a/appPy/src/fit/Fit_VLE.py +++ b/appPy/src/fit/Fit_VLE.py @@ -27,7 +27,7 @@ def __init__(self, compound1, compound2, model_name, datasets, params0, const_pa const_param_names (list of str): names of parameters to be kept constant during optimization. """ super().__init__(supported_models, model_name, params0, const_param_names) - self.keys_to_serialize.extend(['tabulated_datasets']) + self.keys_to_serialize.extend(['is_optimized', 'tabulated_datasets', 'nparams', 'RMS_final', 'AAD_final']) self.compound1 = compound1 self.compound2 = compound2 @@ -43,8 +43,13 @@ def __init__(self, compound1, compound2, model_name, datasets, params0, const_pa self.gamma_1 = squash(self.dataset_VLEs, 'gamma_1') self.gamma_2 = squash(self.dataset_VLEs, 'gamma_2') - self.RMS_init = RMS(self.get_residual(self.params0)) - self.AAD_init = AAD(self.get_residual(self.params0)) + self.params = self.params0 # optimization result params as np.array + self.nparams = self.nparams0 + + self.is_optimized = False # whether optimization has been performed + self.RMS_final = self.AAD_final = None + self.RMS0 = RMS(self.get_residual(self.params0)) + self.AAD0 = AAD(self.get_residual(self.params0)) def __check_model_suitability(self): """Check if the model is appropriate for given datasets.""" @@ -88,8 +93,8 @@ def report(self): echo(f' {name} = {value:.4g}') echo('') - echo(f'Initial RMS = {self.RMS_init:.3g}') - echo(f'Initial AAD = {self.AAD_init:.3g}') + echo(f'Initial RMS = {self.RMS0:.3g}') + echo(f'Initial AAD = {self.AAD0:.3g}') if self.is_optimized: echo(f'Final RMS = {self.RMS_final:.3g}') echo(f'Final AAD = {self.AAD_final:.3g}') diff --git a/appPy/src/fit/Fit_Vapor.py b/appPy/src/fit/Fit_Vapor.py index 80ea4a0..b7be142 100644 --- a/appPy/src/fit/Fit_Vapor.py +++ b/appPy/src/fit/Fit_Vapor.py @@ -20,11 +20,12 @@ def __init__(self, compound, model_name, p_data, T_data, params0): p_data (list of float): vector of pressure data. T_data (list of float): vector of temperature data. params0 (list of float): initial estimate of model parameters (ordered). - skip_T_p_optimization (bool): if True, only p residuals will be optimized. """ super().__init__(supported_models, model_name, params0) - self.keys_to_serialize.extend( - ['is_T_p_optimized', 'odr_messages', 'RMS_inter', 'AAD_inter', 'nparams_inter', 'T_min', 'T_max']) + self.keys_to_serialize.extend([ + 'is_p_optimized', 'is_T_p_optimized', 'odr_messages', 'nparams_p', 'nparams_T_p', 'RMS_p', 'AAD_p', + 'RMS_T_p', 'AAD_T_p', 'T_min', 'T_max' + ]) self.compound = compound self.p_data = np.array(p_data) self.T_data = np.array(T_data) @@ -32,15 +33,15 @@ def __init__(self, compound, model_name, p_data, T_data, params0): self.T_min = np.min(T_data) self.T_max = np.max(T_data) - self.T_tab = self.p_tab_inter = self.p_tab_final = None - self.nparams_inter = self.params_inter = None - self.RMS_inter = self.AAD_inter = None - self.is_T_p_optimized = False # in case of Fit_Vapor, Fit.is_optimized means is_p_optimized + self.nparams_p = self.params_p = self.params_T_p = self.nparams_T_p = None + self.RMS_p = self.AAD_p = self.RMS_T_p = self.AAD_T_p = None + self.T_tab = self.p_tab_p = self.p_tab_T_p = None + self.is_p_optimized = self.is_T_p_optimized = False self.odr_messages = [] # scipy.ODR has an unusual output format, as array of messages, but no status # even for ODR, only p residuals are calculated for simplicity, so they may actually increase after ODR - self.RMS_init = RMS(self.get_p_residuals(self.params0)) - self.AAD_init = AAD(self.get_p_residuals(self.params0)) + self.RMS0 = RMS(self.get_p_residuals(self.params0)) + self.AAD0 = AAD(self.get_p_residuals(self.params0)) def get_p_residuals(self, params): """With given params, calculate residuals only of the dependent variable (p).""" @@ -55,15 +56,15 @@ def optimize_p(self): if result.status <= 0: self.warn(f'p-optimization failed! Status {result.status}: {result.message}') return - self.is_optimized = True - self.params_inter = merge_params(result.x) - self.nparams_inter = self.set_named_params(self.params_inter) - self.RMS_inter = RMS(self.get_p_residuals(self.params_inter)) - self.AAD_inter = AAD(self.get_p_residuals(self.params_inter)) + self.is_p_optimized = True + self.params_p = merge_params(result.x) + self.nparams_p = self.set_named_params(self.params_p) + self.RMS_p = RMS(self.get_p_residuals(self.params_p)) + self.AAD_p = AAD(self.get_p_residuals(self.params_p)) def optimize_T_p(self): """Optimize the model params, considering errors of both dependent (p) & independent (T) variables.""" - full_params0 = self.params_inter if self.is_optimized else self.params # use intermediate params if available + full_params0 = self.params_p if self.is_p_optimized else self.params0 # use intermediate params if available fun2wrap = lambda params, x: self.model.fun(x, *params) # swap the order of params and x var_params0, wrapped_fun, merge_params = const_param_wrappers(fun2wrap, full_params0, self.const_param_names, self.model.param_names) @@ -76,19 +77,19 @@ def optimize_T_p(self): self.warn(f'T,p-optimization failed with error: {e}') return self.is_T_p_optimized = True - self.params = merge_params(odr_result.beta) - self.nparams = self.set_named_params(self.params) - self.RMS_final = RMS(self.get_p_residuals(self.params)) - self.AAD_final = AAD(self.get_p_residuals(self.params)) + self.params_T_p = merge_params(odr_result.beta) + self.nparams_T_p = self.set_named_params(self.params_T_p) + self.RMS_T_p = RMS(self.get_p_residuals(self.params_T_p)) + self.AAD_T_p = AAD(self.get_p_residuals(self.params_T_p)) self.odr_messages = odr_result.stopreason self.odr_messages = self.odr_messages if isinstance(self.odr_messages, list) else [self.odr_messages] def tabulate(self): self.T_tab = np.linspace(self.T_min, self.T_max, cst.x_points_smooth_plot) - if self.is_optimized: - self.p_tab_inter = self.model.fun(self.T_tab, *self.params_inter) + if self.is_p_optimized: + self.p_tab_p = self.model.fun(self.T_tab, *self.params_p) if self.is_T_p_optimized: - self.p_tab_final = self.model.fun(self.T_tab, *self.params) + self.p_tab_T_p = self.model.fun(self.T_tab, *self.params_T_p) def report(self): underline_echo(self.get_title()) @@ -98,14 +99,14 @@ def report(self): echo('') echo('Optimized parameters as intermediate & final value:') - for (name, p_inter, p_final) in zip(self.model.param_names, self.params_inter, self.params): - echo(f' {name} = {p_inter:7.4g} {p_final:9.4g}') + for (name, p_p, p_T_p) in zip(self.model.param_names, self.params_p, self.params_T_p): + echo(f' {name} = {p_p:7.4g} {p_T_p:9.4g}') echo('') - echo(f'Initial RMS = {self.RMS_init:.3g}') - echo(f'Initial AAD = {self.AAD_init:.3g}') - echo(f'Final RMS = {self.RMS_final:.3g}') - echo(f'Final AAD = {self.AAD_final:.3g}') + echo(f'Initial RMS = {self.RMS0:.3g}') + echo(f'Initial AAD = {self.AAD0:.3g}') + echo(f'Final RMS = {self.RMS_T_p:.3g}') + echo(f'Final AAD = {self.AAD_T_p:.3g}') def get_title(self): return f'Regression of {self.model.display_name} on {self.compound}' diff --git a/appPy/src/plot/Fit_Vapor_plot.py b/appPy/src/plot/Fit_Vapor_plot.py index 68ee10a..fdde521 100644 --- a/appPy/src/plot/Fit_Vapor_plot.py +++ b/appPy/src/plot/Fit_Vapor_plot.py @@ -20,13 +20,13 @@ def decorate(self): def plot_p(self, mode): """Overlay p-fitted model over vapor pressure data.""" - if not self.is_optimized: return None + if not self.is_p_optimized: return None init_plot(mode) self.draw_data() T_tab_disp = convert_T(self.T_tab) - p_tab_inter_disp = convert_p(self.p_tab_inter) - plt.plot(T_tab_disp, p_tab_inter_disp, '-k', label='p-fitted model') + p_tab_p_disp = convert_p(self.p_tab_p) + plt.plot(T_tab_disp, p_tab_p_disp, '-k', label='p-fitted model') self.decorate() return finish_plot(mode) @@ -38,8 +38,8 @@ def plot_T_p(self, mode): self.draw_data() T_tab_disp = convert_T(self.T_tab) - p_tab_final_disp = convert_p(self.p_tab_final) - plt.plot(T_tab_disp, p_tab_final_disp, '-k', label='T,p-fitted model') + p_tab_T_p_disp = convert_p(self.p_tab_T_p) + plt.plot(T_tab_disp, p_tab_T_p_disp, '-k', label='T,p-fitted model') self.decorate() return finish_plot(mode) diff --git a/appUI/src/actions/FitVLE/FitVLEResultsDialog.tsx b/appUI/src/actions/FitVLE/FitVLEResultsDialog.tsx index 2f3f93f..081ddf6 100644 --- a/appUI/src/actions/FitVLE/FitVLEResultsDialog.tsx +++ b/appUI/src/actions/FitVLE/FitVLEResultsDialog.tsx @@ -50,7 +50,7 @@ export const FitVLEResultsDialog: FC = ({ open, handleClo }, [data]); const metricsSpreadsheetData = useMemo(() => { - const rows = [[data.RMS_init, data.AAD_init]]; + const rows = [[data.RMS0, data.AAD0]]; if (optimized) rows.push([data.RMS_final!, data.AAD_final!]); return makeReadOnly(spreadsheetToSigDgts(matrixToSpreadsheetData(rows), sigDgtsMetrics)); }, [data]); diff --git a/appUI/src/actions/FitVapor/FitVaporResultsDialog.tsx b/appUI/src/actions/FitVapor/FitVaporResultsDialog.tsx index db5c273..899ee89 100644 --- a/appUI/src/actions/FitVapor/FitVaporResultsDialog.tsx +++ b/appUI/src/actions/FitVapor/FitVaporResultsDialog.tsx @@ -34,12 +34,12 @@ export const FitVaporResultsDialog: FC = ({ }) => { const [infoOpen, setInfoOpen] = useState(false); - const optimizedP = data.is_optimized; + const optimizedP = data.is_p_optimized; const optimizedTP = data.is_T_p_optimized; const paramNames = useMemo(() => fromNamedParams(data.nparams0)[0], [data]); const params0 = fromNamedParams(data.nparams0)[1]; - const paramsP = fromNamedParams(data.nparams_inter)[1]; - const paramsTP = fromNamedParams(data.nparams)[1]; + const paramsP = fromNamedParams(data.nparams_p)[1]; + const paramsTP = fromNamedParams(data.nparams_T_p)[1]; const rowLabels = useMemo(() => { const rows = ['initial']; @@ -49,9 +49,9 @@ export const FitVaporResultsDialog: FC = ({ }, [data]); const metricsSpreadsheetData = useMemo(() => { - const rows = [[data.RMS_init, data.AAD_init]]; - if (optimizedP) rows.push([data.RMS_inter!, data.AAD_inter!]); - if (optimizedTP) rows.push([data.RMS_final!, data.AAD_final!]); + const rows = [[data.RMS0, data.AAD0]]; + if (optimizedP) rows.push([data.RMS_p!, data.AAD_p!]); + if (optimizedTP) rows.push([data.RMS_T_p!, data.AAD_T_p!]); return makeReadOnly(spreadsheetToSigDgts(matrixToSpreadsheetData(rows), sigDgtsMetrics)); }, [data]); diff --git a/appUI/src/adapters/api/types/fitTypes.ts b/appUI/src/adapters/api/types/fitTypes.ts index 407fb58..ad98b1c 100644 --- a/appUI/src/adapters/api/types/fitTypes.ts +++ b/appUI/src/adapters/api/types/fitTypes.ts @@ -44,18 +44,17 @@ export type PlottedDataset = AnalysisResult & { gamma_plot: string; }; -type FitMetrics = AnalysisResult & { +export type FitAnalysisResponse = AnalysisResult & { is_optimized: boolean; - RMS_init: number; - RMS_final: number | null; - AAD_init: number; - AAD_final: number | null; -}; + tabulated_datasets: PlottedDataset[]; -export type FitAnalysisResponse = FitMetrics & { nparams0: NamedParams; nparams: NamedParams; - tabulated_datasets: PlottedDataset[]; + + RMS0: number; + RMS_final: number | null; + AAD0: number; + AAD_final: number | null; }; export type FitTabulateRequest = SystemIdentifier & { model_name: string; p: number }; @@ -77,7 +76,7 @@ export type VaporFitRequest = CompoundIdentifier & { }; export type VaporFitResponse = AnalysisResult & { - is_optimized: boolean; + is_p_optimized: boolean; is_T_p_optimized: boolean; odr_messages: string[]; T_min: number; @@ -86,13 +85,13 @@ export type VaporFitResponse = AnalysisResult & { plot_T_p?: string; nparams0: NamedParams; - nparams_inter?: NamedParams; - nparams?: NamedParams; + nparams_p?: NamedParams; + nparams_T_p?: NamedParams; - RMS_init: number; - RMS_inter: number | null; - RMS_final: number | null; - AAD_init: number; - AAD_inter: number | null; - AAD_final: number | null; + RMS0: number; + RMS_p: number | null; + RMS_T_p: number | null; + AAD0: number; + AAD_p: number | null; + AAD_T_p: number | null; }; diff --git a/dev/notes_VLizard.txt b/dev/notes_VLizard.txt deleted file mode 100644 index 89f4297..0000000 --- a/dev/notes_VLizard.txt +++ /dev/null @@ -1 +0,0 @@ -rename Fit output variables to be more consistent diff --git a/docs/appPy.md b/docs/appPy.md index 936f35f..ab2f447 100644 --- a/docs/appPy.md +++ b/docs/appPy.md @@ -55,11 +55,11 @@ pipenv run cli\gamma CHF CHOL -d 25kPa --plot pipenv run cli\rk CHF CHOL -d 25kPa,40kPa --plot pipenv run cli\herington CHF CHOL -d 25kPa,40kPa pipenv run cli\fredenslund CHF CHOL -d 25kPa --ge --res -pipenv run cli\fit CPF CPOL -m vanLaar -d 25kPa --txy --persist +pipenv run cli\fit CPF CPOL vanLaar -d 25kPa --txy --persist pipenv run cli\tabulate CPOL CPF vanLaar 33 --txy pipenv run cli\vn CPOL CPF vanLaar -d 25kPa --plot -pipenv run cli\fit CHOL CHF -d 10kPa,25kPa,40kPa --xy --txy --gamma -c c_12 -pipenv run cli\fit EtOH H2O -m UNIQUAC --skip -d Kamihama2012,Voutsas2011 --xy --gamma -p 1.972,1.4,2.10547,0.92,2.0046,-2.4936,-728.9705,756.9477 +pipenv run cli\fit CHOL CHF NRTL -d 10kPa,25kPa,40kPa --xy --txy --gamma -c c_12 +pipenv run cli\fit EtOH H2O UNIQUAC --skip -d Kamihama2012,Voutsas2011 --xy --gamma -p 1.972,1.4,2.10547,0.92,2.0046,-2.4936,-728.9705,756.9477 ``` See [appPy/cli](../appPy/cli), where filenames correspond to commands; calling with `--help` will instruct you further. diff --git a/http/fit.http b/http/fit.http index 917e734..1f7e4da 100644 --- a/http/fit.http +++ b/http/fit.http @@ -7,7 +7,7 @@ Accept: application/json POST http://localhost:37137/fit/vle Content-Type: application/json -{"compound1": "EtOH", "compound2": "H2O", "model_name": "vanLaar", "datasets": ["1939vac"], +{"compound1": "EtOH", "compound2": "H2O", "model_name": "vanLaar", "datasets": ["Voutsas2011"], "skip_optimization": false, "nparams0": {"A_12": 0.4, "A_21": 0.1}} ### @@ -35,8 +35,7 @@ Content-Type: application/json "model_name": "Wagner", "p_data": [9.98, 11.52, 14.39, 25.01, 25.95, 39.91, 40.05, 52.5, 60.41, 78.21, 89.63, 100.05], "T_data": [363.95, 367.65, 373.55, 389.25, 390.25, 403.35, 403.55, 412.45, 417.15, 426.25, 431.25, 435.35], - "nparams0": {"A": -7.898313, "B": 1.997515, "C": -2.448405, "D": -1.880206, "T_c": 647, "p_c": 22071}, - "skip_T_p_optimization": false + "nparams0": {"A": -7.898313, "B": 1.997515, "C": -2.448405, "D": -1.880206, "T_c": 647, "p_c": 22071} } ###