From 7a4a7278d9592ea288f4b7db00f5f44d9030e1c1 Mon Sep 17 00:00:00 2001 From: titusquah <46580668+titusquah@users.noreply.github.com> Date: Wed, 7 Apr 2021 11:31:53 -0600 Subject: [PATCH] Revert "rename for rtd" This reverts commit 5ce76bb867072c1d4f53533a114e4f271b7573ea. --- docs/_build/doctrees/environment.pickle | Bin 90809 -> 154128 bytes docs/_build/doctrees/guide/about.doctree | Bin 10610 -> 0 bytes docs/_build/doctrees/guide/install.doctree | Bin 5473 -> 5675 bytes docs/_build/doctrees/guide/quickstart.doctree | Bin 7713 -> 7735 bytes docs/_build/doctrees/index.doctree | Bin 6242 -> 6297 bytes docs/_build/doctrees/modules/LLEPE.doctree | Bin 200180 -> 0 bytes docs/_build/doctrees/modules/reeps.doctree | Bin 200180 -> 201626 bytes docs/_build/html/.buildinfo | 2 +- docs/_build/html/_modules/index.html | 6 +- docs/_build/html/_modules/llepe/llepe.html | 124 +- docs/_build/html/_modules/reeps/reeps.html | 1735 +++++++++++++++++ docs/_build/html/_sources/guide/about.rst.txt | 61 - .../html/_sources/guide/install.rst.txt | 6 +- docs/_build/html/_sources/index.rst.txt | 12 +- .../html/_sources/modules/LLEPE.rst.txt | 15 - .../html/_sources/modules/reeps.rst.txt | 2 +- docs/_build/html/_static/alabaster.css | 701 +++++++ docs/_build/html/_static/classic.css | 266 +++ docs/_build/html/_static/custom.css | 1 + docs/_build/html/_static/default.css | 1 + docs/_build/html/_static/sidebar.js | 159 ++ docs/_build/html/genindex.html | 103 +- docs/_build/html/guide/about.html | 272 --- docs/_build/html/guide/install.html | 14 +- docs/_build/html/guide/quickstart.html | 2 +- docs/_build/html/index.html | 20 +- docs/_build/html/modules/LLEPE.html | 1123 ----------- docs/_build/html/modules/reeps.html | 31 +- docs/_build/html/objects.inv | Bin 723 -> 705 bytes docs/_build/html/py-modindex.html | 7 +- docs/_build/html/search.html | 5 +- docs/_build/html/searchindex.js | 2 +- docs/_source/conf.py | 3 - docs/_source/guide/about.rst | 61 - docs/_source/guide/install.rst | 6 +- docs/_source/index.rst | 3 +- docs/_source/modules/{LLEPE.rst => reeps.rst} | 2 +- 37 files changed, 3029 insertions(+), 1716 deletions(-) delete mode 100644 docs/_build/doctrees/guide/about.doctree delete mode 100644 docs/_build/doctrees/modules/LLEPE.doctree create mode 100644 docs/_build/html/_modules/reeps/reeps.html delete mode 100644 docs/_build/html/_sources/guide/about.rst.txt delete mode 100644 docs/_build/html/_sources/modules/LLEPE.rst.txt create mode 100644 docs/_build/html/_static/alabaster.css create mode 100644 docs/_build/html/_static/classic.css create mode 100644 docs/_build/html/_static/custom.css create mode 100644 docs/_build/html/_static/default.css create mode 100644 docs/_build/html/_static/sidebar.js delete mode 100644 docs/_build/html/guide/about.html delete mode 100644 docs/_build/html/modules/LLEPE.html delete mode 100644 docs/_source/guide/about.rst rename docs/_source/modules/{LLEPE.rst => reeps.rst} (74%) diff --git a/docs/_build/doctrees/environment.pickle b/docs/_build/doctrees/environment.pickle index 9aea3280c93fb7a0b7eb137cdb5ac630c3a252ad..bfa0d3321c32cd0778959fdcdf12898c76afd848 100644 GIT binary patch delta 19531 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 delta 14660 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 EzvBrniU0rr diff --git a/docs/_build/doctrees/guide/install.doctree b/docs/_build/doctrees/guide/install.doctree index 88d39c3469c9d7ff2f47d537affc277017ada63d..46eb899ccc86a154a7585cc54f1bb95ea3a8edf7 100644 GIT binary patch delta 530 zcmaE;wOWU@fn}7MEn^CYEI8=WRA+ zbY!g$EXXWS$jmD)NzBPn0IJr`C@Co@w$j(HfN0fAPR%LSOU%pBOV2OUFD}i^1sY;x zU}T_=%}hO@rYRZhu~RbidWbSvlW3zizhdiTR7woVS4hs!D=ErMDlJJ>D9Kj{E=f$v zN!4{qEKbeI%u6kv+{NKRoHEDBTAaS=%|ItBB`JnnxvPUpR4ca z=cDWF>gpQos1H`A2b7zV!5%v$L$3#{SQ?Mw%_i)fjN)M-`3lMTc_l@eNu?#J3MKgp zV3Q`_=kUNI=Qz2G)7QukXu3jjPJUjhLPkkRL9vy-J|6o(j+<=9rAnSQxy>)QoVfv8 C#a?>= diff --git a/docs/_build/doctrees/guide/quickstart.doctree b/docs/_build/doctrees/guide/quickstart.doctree index bfd9bfb6ea8a65dc234ce85ff04682db24f3334d..3f643f70d6da74f401843f3b1a739d244de72271 100644 GIT binary patch delta 51 zcmZ2zv)zWJfn}=BM3(=Id6QWfjil2vOL7vEVhR$A5_3~aQj2s`i%T+d6H7Al^EO*E HK9d0e+MyEd delta 29 lcmdmPv(Sd6fn}=LM3(=I5tCUMjaYqrTmxJ;2QogB0RWD#2{`}& diff --git a/docs/_build/doctrees/index.doctree b/docs/_build/doctrees/index.doctree index 9d5c4ce7adc10fac9e03198205555b350e7dfd7a..9a0fc6aead7248f63ecd5e18fee826c6c1b3294e 100644 GIT binary patch delta 1200 zcmchVO=uHQ5XbwHtZC9DR#Hu=)jYKtD``pFL$N3dBBGUwSP;RYo6XbNHrb84Z?zFh zEodPSlsa3rD0uKDf*-kBC=^6Jt3|8$jfyAn<0!se+p4H1!NblnGyk3a{pYS$@}Xl`)4HYF{H|ys%+NHnv9QX!jC2w>T?*`C zvY`MoOdISXj27Wc5zcm2z_}uf3E_Dt|2!)O3*nNLmSNF=y&7tTpiVp&aeZI~CWCi~ zAHM{5OSgsU7LqWwf#3NNep5U`8Dk2HhiC7R{@+_CD(xhJ&*wK&NlYqL=JbrJWmv!Mn4hItDWowWjh~i6 zl9NQgC%!*(eT^g%TYl%7EB)Zwg4^d)vg0?d?Jn0Ihij?JHP3WU>ldn30%Yqfm6L3q zp_1HfVLTYa3)RcQ_kP;xWchCOQBT!n$7_TchHl`3XtbP;4tv`tV7&LZX!H(=V3h)z fb4Lq#X1aTw=yID~7SuXx9)~rq4kpX-*e3D~k!P8B delta 1264 zcmb_cT}TvB6!yA*uDh!nW@{PkE&N;2)ugb@2W?v-c3Z5id3a z^?jpx*gVlWZazNrC>#!U1Y2;JKdJDs`lS~RG9d8X60an22Pbh!k|q)k%4t5yrBhO2 zAk{Y@@o`Awnt%^CH8wTY;llyod$@QCw@dMqD3UrH4hQ_WT@bKAm9a!*BAp^Jf8jwQ z5KbmBJfw&*Ryhp>CsQ%LmuIov(LGq;dx$6#KaLWD!}|ikNHmChQxaoI1jk64;|1BT zfvrn}Jk@-N^rtndsjGG~^ndNF+Kpp{9qP@WMEmK({3eSr6lyhjk(pMR+A?^?)JpG| zN^UNd8K4eT*u-rSgZ4Eu)GNU)TfVHn!>?I&D zEpZ}BH@v2kE?>0=<>eV-K4ujpa-q-5A-dq&;_))>?}lq&k&Zbx>w*#+>D%IFZLT=S68`J!? zdx-=8un;jU9MvssGAv}vtTa%?B!_YabhI~Xz+yC-K6dYLy;w1hxoNcKbUvDzK3j6j u)I19_i4i6wab8!W+C}}BG|%k9v_b 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

`<4ixH*UNPCmS=S; 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 BcM<>q diff --git a/docs/_build/doctrees/modules/reeps.doctree b/docs/_build/doctrees/modules/reeps.doctree index 87fd4d163347f4d7a29f0733b30bcde9983eb6f4..e316e1aa4f877e05999fba41c7197e7da57e8dce 100644 GIT binary patch delta 15164 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 delta 13628 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 diff --git a/docs/_build/html/.buildinfo b/docs/_build/html/.buildinfo index 51dc31b..d0738d2 100644 --- a/docs/_build/html/.buildinfo +++ b/docs/_build/html/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: fdde7da710e99305b2f90b2ffb8f91ce +config: 09c8b5742a1f96cda53a1848d8ac9ec1 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_build/html/_modules/index.html b/docs/_build/html/_modules/index.html index d96106f..ca853b9 100644 --- a/docs/_build/html/_modules/index.html +++ b/docs/_build/html/_modules/index.html @@ -85,11 +85,10 @@

Searchers

@@ -152,6 +151,7 @@

All modules for which code is available

@@ -164,7 +164,7 @@

- © Copyright 2020, UChicago Argonne, LLC. + © Copyright 2020, Titus Quah, Nwike Iloeje

diff --git a/docs/_build/html/_modules/llepe/llepe.html b/docs/_build/html/_modules/llepe/llepe.html index 13e2620..bf78f9b 100644 --- a/docs/_build/html/_modules/llepe/llepe.html +++ b/docs/_build/html/_modules/llepe/llepe.html @@ -85,11 +85,10 @@

Searchers

@@ -170,7 +169,7 @@ from .utils import set_size -
[docs]class LLEPE: +
[docs]class LLEPE: r""" Liquid-Liquid Extraction Parameter estimator @@ -181,7 +180,7 @@ must be the same order as they appear in the xml, complex_names and extracted_species_ion_names. - For example, say in exp_data, ES_1 is Nd ES_2 is Pr, + For example, say in exp_csv_filename's csv, ES_1 is Nd ES_2 is Pr, and .. code-block:: python @@ -192,7 +191,7 @@ Then: - The exp_data column ordering must be (names do not matter): + The csvs column ordering must be: [h_i, h_eq, z_i, z_eq, Nd_aq_i, Nd_aq_eq, Nd_d_eq, Pr_aq_i, Pr_aq_eq, Pr_d_eq] @@ -527,8 +526,7 @@ self._predicted_dict = None self.update_predicted_dict() - # TODO: move scipy_minimize to optimizers -
[docs] @staticmethod +
[docs] @staticmethod def scipy_minimize(objective, x_guess, optimizer_kwargs=None): """ The default optimizer for LLEPE @@ -562,8 +560,7 @@ est_parameters = res.x return est_parameters, res.fun
- # TODO: move log_mean_squared_error to objectives -
[docs] def log_mean_squared_error(self, predicted_dict, meas_df): +
[docs] def log_mean_squared_error(self, predicted_dict, meas_df): """Default objective function for LLEPE Returns the log mean squared error of @@ -587,14 +584,14 @@ obj = np.sum(log_diff) return obj
-
[docs] def get_exp_df(self) -> pd.DataFrame: +
[docs] def get_exp_df(self) -> pd.DataFrame: """Returns the experimental DataFrame :return: (pd.DataFrame) Experimental data """ return self._exp_df
-
[docs] def set_exp_df(self, exp_data): +
[docs] def set_exp_df(self, exp_data): """Changes the experimental DataFrame to input exp_csv_filename data and renames columns to internal LLEPE names @@ -635,7 +632,7 @@ self.update_predicted_dict() return None
-
[docs] def get_phases(self) -> list: +
[docs] def get_phases(self) -> list: """ Returns the list of Cantera solutions @@ -643,7 +640,7 @@ """ return self._phases
-
[docs] def set_phases(self, phases_xml_filename, phase_names): +
[docs] def set_phases(self, phases_xml_filename, phase_names): """Change list of Cantera solutions by inputting new xml file name and phase names @@ -676,7 +673,7 @@ self.update_predicted_dict() return None
-
[docs] def get_opt_dict(self) -> dict: +
[docs] def get_opt_dict(self) -> dict: """ Returns the dictionary containing optimization information @@ -685,7 +682,7 @@ """ return self._opt_dict
-
[docs] def set_opt_dict(self, opt_dict): +
[docs] def set_opt_dict(self, opt_dict): """ Change the dictionary to input opt_dict. @@ -701,14 +698,14 @@ self._opt_dict = opt_dict return None
-
[docs] def get_aq_solvent_name(self) -> str: +
[docs] def get_aq_solvent_name(self) -> str: """Returns aq_solvent_name :return: aq_solvent_name: (str) name of aqueous solvent in xml file """ return self._aq_solvent_name
-
[docs] def set_aq_solvent_name(self, aq_solvent_name): +
[docs] def set_aq_solvent_name(self, aq_solvent_name): """ Change aq_solvent_name to input aq_solvent_name :param aq_solvent_name: (str) name of aqueous solvent in xml file @@ -716,14 +713,14 @@ self._aq_solvent_name = aq_solvent_name return None
-
[docs] def get_extractant_name(self) -> str: +
[docs] def get_extractant_name(self) -> str: """Returns extractant name :return: extractant_name: (str) name of extractant in xml file """ return self._extractant_name
-
[docs] def set_extractant_name(self, extractant_name): +
[docs] def set_extractant_name(self, extractant_name): """ Change extractant_name to input extractant_name :param extractant_name: (str) name of extractant in xml file @@ -731,13 +728,13 @@ self._extractant_name = extractant_name return None
-
[docs] def get_diluant_name(self) -> str: +
[docs] def get_diluant_name(self) -> str: """ Returns diluant name :return: diluant_name: (str) name of diluant in xml file """ return self._diluant_name
-
[docs] def set_diluant_name(self, diluant_name): +
[docs] def set_diluant_name(self, diluant_name): """ Change diluant_name to input diluant_name @@ -746,14 +743,14 @@ self._diluant_name = diluant_name return None
-
[docs] def get_complex_names(self) -> list: +
[docs] def get_complex_names(self) -> list: """Returns list of complex names :return: complex_names: (list) names of complexes in xml file. """ return self._complex_names
-
[docs] def set_complex_names(self, complex_names): +
[docs] def set_complex_names(self, complex_names): """Change complex names list to input complex_names :param complex_names: (list) names of complexes in xml file. @@ -761,7 +758,7 @@ self._complex_names = complex_names return None
-
[docs] def get_extracted_species_ion_names(self) -> list: +
[docs] def get_extracted_species_ion_names(self) -> list: """Returns list of extracted species ion names :return: extracted_species_ion_names: (list) names of @@ -769,7 +766,7 @@ """ return self._extracted_species_ion_names
-
[docs] def set_extracted_species_ion_names(self, extracted_species_ion_names): +
[docs] def set_extracted_species_ion_names(self, extracted_species_ion_names): """Change list of extracted species ion names to input extracted_species_ion_names @@ -779,7 +776,7 @@ self._extracted_species_ion_names = extracted_species_ion_names return None
-
[docs] def get_extracted_species_list(self) -> list: +
[docs] def get_extracted_species_list(self) -> list: """Returns list of extracted species names :return: extracted_species_list: (list) names of extracted species in @@ -787,7 +784,7 @@ """ return self._extracted_species_list
-
[docs] def set_extracted_species_list(self, extracted_species_list): +
[docs] def set_extracted_species_list(self, extracted_species_list): """Change list of extracted species ion names to input extracted_species_ion_names @@ -797,14 +794,14 @@ self._extracted_species_list = extracted_species_list return None
-
[docs] def get_aq_solvent_rho(self) -> str: +
[docs] def get_aq_solvent_rho(self) -> str: """Returns aqueous solvent density (g/L) :return: aq_solvent_rho: (float) density of aqueous solvent """ return self._aq_solvent_rho
-
[docs] def set_aq_solvent_rho(self, aq_solvent_rho): +
[docs] def set_aq_solvent_rho(self, aq_solvent_rho): """Changes aqueous solvent density (g/L) to input aq_solvent_rho :param aq_solvent_rho: (float) density of aqueous solvent @@ -812,14 +809,14 @@ self._aq_solvent_rho = aq_solvent_rho return None
-
[docs] def get_extractant_rho(self) -> str: +
[docs] def get_extractant_rho(self) -> str: """Returns extractant density (g/L) :return: extractant_rho: (float) density of extractant """ return self._extractant_rho
-
[docs] def set_extractant_rho(self, extractant_rho): +
[docs] def set_extractant_rho(self, extractant_rho): """Changes extractant density (g/L) to input extractant_rho :param extractant_rho: (float) density of extractant @@ -827,14 +824,14 @@ self._extractant_rho = extractant_rho return None
-
[docs] def get_diluant_rho(self) -> str: +
[docs] def get_diluant_rho(self) -> str: """Returns diluant density (g/L) :return: diluant_rho: (float) density of diluant """ return self._diluant_rho
-
[docs] def set_diluant_rho(self, diluant_rho): +
[docs] def set_diluant_rho(self, diluant_rho): """Changes diluant density (g/L) to input diluant_rho :param diluant_rho: (float) density of diluant @@ -842,12 +839,7 @@ self._diluant_rho = diluant_rho return None
- # TODO: Change input DataFrame structure to contain information about - # other species like NaCl - # TODO: Change DataFrame structure to contain info about O/A ratio - # TODO: Generalize code to more than just org and aq phase (3+ phases) - # TODO: Handle multiple electrolytes ie. NO3- with Cl- -
[docs] def set_in_moles(self, feed_vol): +
[docs] def set_in_moles(self, feed_vol): """Function that initializes mole fractions to input feed_vol This function is called at initialization @@ -930,7 +922,7 @@ for extracted_species in extracted_species_list]) extracted_species_charge_sum = np.sum( extracted_species_charges * extracted_species_moles) - anion_moles = extracted_species_charge_sum + h_plus_moles + chlorine_moles = extracted_species_charge_sum + h_plus_moles extractant_moles = feed_vol * row['z_i'] extractant_vol = extractant_moles * extractant_mw / extractant_rho diluant_vol = feed_vol - extractant_vol @@ -940,7 +932,7 @@ species_moles_aq = [aq_phase_solvent_moles, h_plus_moles, hydroxide_ions, - anion_moles] + chlorine_moles] species_moles_aq.extend(list(extracted_species_moles)) species_moles_org = [extractant_moles, diluant_moles] species_moles_org.extend(list(complex_moles)) @@ -954,7 +946,7 @@ self.update_predicted_dict() return None
-
[docs] def get_in_moles(self) -> pd.DataFrame: +
[docs] def get_in_moles(self) -> pd.DataFrame: """Returns the in_moles DataFrame which contains the initial mole fractions of each species for each experiment @@ -962,7 +954,7 @@ """ return self._in_moles
-
[docs] def set_objective_function(self, objective_function): +
[docs] def set_objective_function(self, objective_function): """Change objective function to input objective_function. See class docstring on "objective_function" for instructions @@ -987,7 +979,7 @@ self._objective_function = objective_function return None
-
[docs] def get_objective_function(self): +
[docs] def get_objective_function(self): """Returns objective function :return: objective_function: (func) Objective function to quantify @@ -995,7 +987,7 @@ """ return self._objective_function
-
[docs] def set_optimizer(self, optimizer): +
[docs] def set_optimizer(self, optimizer): """Change optimizer function to input optimizer. See class docstring on "optimizer" for instructions @@ -1020,7 +1012,7 @@ self._optimizer = optimizer return None
-
[docs] def get_optimizer(self): +
[docs] def get_optimizer(self): """Returns objective function :return: optimizer: (func) Optimizer function to minimize objective @@ -1028,7 +1020,7 @@ """ return self._optimizer
-
[docs] def get_temp_xml_file_path(self): +
[docs] def get_temp_xml_file_path(self): """Returns path to temporary xml file. This xml file is a duplicate of the phases_xml_file name and is @@ -1039,7 +1031,7 @@ """ return self._temp_xml_file_path
-
[docs] def set_temp_xml_file_path(self, temp_xml_file_path): +
[docs] def set_temp_xml_file_path(self, temp_xml_file_path): """Changes temporary xml file path to input temp_xml_file_path. This xml file is a duplicate of the phases_xml_file name and is @@ -1051,7 +1043,7 @@ self._temp_xml_file_path = temp_xml_file_path return None
-
[docs] def get_dependant_params_dict(self): +
[docs] def get_dependant_params_dict(self): """ Returns the dependant_params_dict @@ -1060,7 +1052,7 @@ """ return self._dependant_params_dict
-
[docs] def set_dependant_params_dict(self, dependant_params_dict): +
[docs] def set_dependant_params_dict(self, dependant_params_dict): """ Sets the dependant_params_dict @@ -1070,7 +1062,7 @@ self._dependant_params_dict = dependant_params_dict return None
-
[docs] def get_custom_objects_dict(self): +
[docs] def get_custom_objects_dict(self): """ Returns the custom_objects_dict @@ -1079,7 +1071,7 @@ """ return self._custom_objects_dict
-
[docs] def set_custom_objects_dict(self, custom_objects_dict): +
[docs] def set_custom_objects_dict(self, custom_objects_dict): """ Sets the custom_objects_dict @@ -1089,11 +1081,7 @@ self._custom_objects_dict = custom_objects_dict return None
- # TODO: Change DataFrame strucutre to contain info whether to set - # equilibrium pH to measured value. Will be useful for saponification - # TODO: Find way to add saponification to model. - # Maybe use fsolve to match experimental equilibrium pH -
[docs] def update_predicted_dict(self, +
[docs] def update_predicted_dict(self, phases_xml_filename=None, phase_names=None): """Function that computes the predicted equilibrium concentrations @@ -1160,7 +1148,7 @@ self._predicted_dict = predicted_dict return None
-
[docs] def get_predicted_dict(self): +
[docs] def get_predicted_dict(self): """Returns predicted dictionary of species concentrations that xml parameters predicts given current in_moles @@ -1224,7 +1212,7 @@ objective_values = objective_values[0] return objective_values -
[docs] def fit(self, +
[docs] def fit(self, objective_function=None, optimizer=None, objective_kwargs=None, @@ -1267,7 +1255,7 @@ return opt_dict, obj_value
-
[docs] def update_xml(self, +
[docs] def update_xml(self, info_dict, phases_xml_filename=None, dependant_params_dict=None): @@ -1374,7 +1362,7 @@ self.set_phases(self._phases_xml_filename, self._phase_names) return None
-
[docs] def update_custom_objects_dict(self, info_dict): +
[docs] def update_custom_objects_dict(self, info_dict): """ updates internal custom_objects_dict with info_dict @@ -1399,7 +1387,7 @@ self._custom_objects_dict = custom_objects_dict return None
-
[docs] def parity_plot(self, +
[docs] def parity_plot(self, compared_value=None, c_data=None, c_label=None, @@ -1508,9 +1496,7 @@ filtered_meas = filtered_data['meas'] filtered_pred = filtered_data['pred'] if len(filtered_pred) != 0: - ax.scatter(filtered_meas, - filtered_pred, - label=label) + ax.scatter(filtered_meas, filtered_pred, label=label) if legend: ax.legend(loc='best') @@ -1545,7 +1531,7 @@ plt.savefig(save_path, bbox_inches='tight') return fig, ax
-
[docs] def r_squared(self, compared_value=None): +
[docs] def r_squared(self, compared_value=None): """r-squared value comparing measured and predicted compared value Closer to 1, the better the model's predictions. @@ -1585,7 +1571,7 @@ r_2 = (1 - num / den) return r_2
-
[docs] @staticmethod +
[docs] @staticmethod def plot_3d_data(x_data, y_data, z_data, @@ -1595,8 +1581,6 @@ z_label=None, c_label=None): """ - THis is for plotting 3d scatter plots. - We suggest use matplotlib's ax.scatter to make 3d plots. :param x_data: (list) list of data for x axis :param y_data: (list) list of data for y axis @@ -1647,7 +1631,7 @@

- © Copyright 2020, UChicago Argonne, LLC. + © Copyright 2020, Titus Quah, Nwike Iloeje

diff --git a/docs/_build/html/_modules/reeps/reeps.html b/docs/_build/html/_modules/reeps/reeps.html new file mode 100644 index 0000000..bb9a06e --- /dev/null +++ b/docs/_build/html/_modules/reeps/reeps.html @@ -0,0 +1,1735 @@ + + + + + + + + + + + reeps.reeps — LLEPE 1.0.0 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ +

Source code for reeps.reeps

+from datetime import datetime
+import cantera as ct
+import pandas as pd
+import numpy as np
+from scipy.optimize import minimize
+# noinspection PyPep8Naming
+import xml.etree.ElementTree as ET
+import seaborn as sns
+import matplotlib.pyplot as plt
+import shutil
+import copy
+from inspect import signature
+import os
+import re
+import pkg_resources
+from .utils import set_size
+
+sns.set()
+sns.set(font_scale=1.6)
+
+
+
[docs]class REEPS: + r""" + Rare earth elements (REE or RE) Takes in experimental data + Returns parameters for GEM + + .. note:: + + The order in which the REEs appear in the csv file must be the same + order as they appear in the xml, complex_names and + rare_earth_ion_names. + + For example, say in exp_csv_filename's csv, RE_1 is Nd RE_2 is Pr, + and + + .. code-block:: python + + aq_solvent_name = 'H2O(L)' + extractant_name = '(HA)2(org)' + diluent_name = 'dodecane' + + Then: + + The csvs column ordering must be: + + [h_i, h_eq, z_i, z_eq, Nd_aq_i, Nd_aq_eq, Nd_d_eq, + Pr_aq_i, Pr_aq_eq, Pr_d_eq] + + The aqueous speciesArray must be + "H2O(L) H+ OH- Cl- Nd+++ Pr+++" + + The organic speciesArray must be + "(HA)2(org) dodecane Nd(H(A)2)3(org) Pr(H(A)2)3(org)" + + .. code-block:: python + + complex_names = ['Nd(H(A)2)3(org)', 'Pr(H(A)2)3(org)'] + rare_earth_ion_names = ['Nd+++', 'Pr+++'] + + + :param exp_data: (str) csv file name with experimental data + + In the .csv file, the rows are different experiments and + columns are the measured quantities. + + The ordering of the columns needs to be: + + [h_i, h_eq, z_i, z_eq, + {RE_1}_aq_i, {RE_1}_aq_eq, {RE_1}_d_eq, + {RE_2}_aq_i, {RE_2}_aq_eq, {RE_2}_d_eq,... + {RE_N}_aq_i, {RE_N}_aq_eq, {RE_N}_d_eq] + + Naming does not matter, just the order. + + Where {RE_1}-{RE_N} are the rare earth element names of interest + i.e. Nd, Pr, La, etc. + + Below is an explanation of the columns. + + +-------+------------+------------------------------------------+ + | Index | Column | Meaning | + +=======+============+==========================================+ + | 0 | h_i | Initial Concentration of | + | | | H+ ions (mol/L) | + +-------+------------+------------------------------------------+ + | 1 | h_eq | Equilibrium concentration of | + | | | H+ ions (mol/L) | + +-------+------------+------------------------------------------+ + | 2 | z_i | Initial concentration of | + | | | extractant (mol/L) | + +-------+------------+------------------------------------------+ + | 3 | z_eq | Equilibrium concentration of | + | | | extractant (mol/L) | + +-------+------------+------------------------------------------+ + | 4 | {RE}_aq_i | Initial concentration of RE ions (mol/L) | + +-------+------------+------------------------------------------+ + | 5 | {RE}_aq_eq | Equilibrium concentration of RE ions | + | | | in aqueous phase (mol/L) | + +-------+------------+------------------------------------------+ + | 6 | {RE}_d_eq | Equilibrium Ratio between amount of | + | | | RE atoms in organic to aqueous | + +-------+------------+------------------------------------------+ + :param phases_xml_filename: (str) xml file with parameters + for equilibrium calc + + Would recommend copying and modifying xmls located in data/xmls + or in Cantera's "data" folder + + speciesArray fields need specific ordering. + + In aqueous phase: aq_solvent_name, H+, OH-, Cl-, RE_1, RE_2, ..., RE_N + + For aqueous phase, RE_1-RE_N represent RE ion names i.e. Nd+++, Pr+++ + + In organic phase : extractant_name, diluant_name, RE_1, RE_2, ..., RE_N + + For organic phase, RE_1-RE_N represent RE complex names + i.e. Nd(H(A)2)3(org), Pr(H(A)2)3(org) + + :param phase_names: (list) names of phases in xml file + + Found in the xml file under <phase ... id={phase_name}> + + :param aq_solvent_name: (str) name of aqueous solvent in xml file + :param extractant_name: (str) name of extractant in xml file + :param diluant_name: (str) name of diluant in xml file + :param complex_names: (list) names of complexes in xml file. + + Ensure the ordering is correct + :param rare_earth_ion_names: (list) names of rare earth ions in xml file + + Ensure the ordering is correct + :param re_species_list: (list) names of rare earth elements. + + If ``None``, re_species_list will be rare_earth_ion_names without '+' + i.e. 'Nd+++'->'Nd' + + Ensure the ordering is correct + :param aq_solvent_rho: (float) density of solvent (g/L) + + If ``None``, molar volume/molecular weight is used from xml + :param extractant_rho: (float) density of extractant (g/L) + + If ``None``, molar volume/molecular weight is used from xml + :param diluant_rho: (float) density of diluant (g/L) + + If ``None``, molar volume/molecular weight is used from xml + :param opt_dict: (dict) dictionary containing info about which + species parameters are updated to fit model to experimental data + + Should have the format as below. Dictionary keys under user defined + parameter name must be named as shown below ('upper_element_name', + 'upper_attrib_name', etc.). 'attrib_name's and 'attrib_value's can + be None. {} denotes areas for user to fill in. + + .. code-block:: python + + opt_dict = {"{user_defined_name_for_parameter_1}": + {'upper_element_name': {param_upper_element}, + 'upper_attrib_name': {param_upper_attrib_name}, + 'upper_attrib_value': {param_upper_attrib_value}, + 'lower_element_name': {param_lower_element}, + 'lower_attrib_name': {param_lower_attrib_name}, + 'lower_attrib_value': {param_lower_attrib_value}, + 'input_format': {str format to input input_value} + 'input_value': {guess_value}}, + "{user_defined_name_for_parameter_2}": + ... + ... + } + :param objective_function: (function or str) function to compute objective + + By default, the objective function is log mean squared error + of distribution ratio + + .. code-block:: python + + np.sum((np.log10(d_pred)-np.log10(d_meas))^2) + + Function needs to take inputs: + + .. code-block:: python + + objective_function(predicted_dict, measured_df, kwargs) + + ``kwargs`` is optional + + Function needs to return: (float) value computed by objective function + + Below is the guide for referencing predicted values + + +---------------------------+--------------------------------+ + | To access | Use | + +===========================+================================+ + | hydrogen ion conc in aq | predicted_dict['h_eq'] | + +---------------------------+--------------------------------+ + | extractant conc in org | predicted_dict['z_eq'] | + +---------------------------+--------------------------------+ + | RE ion eq conc in aq | predicted_dict['{RE}_aq_eq'] | + +---------------------------+--------------------------------+ + | RE complex eq conc in org | predicted_dict['{RE}_org_eq'] | + +---------------------------+--------------------------------+ + | RE distribution ratio | predicted_dict['{RE}_d_eq'] | + +---------------------------+--------------------------------+ + + Replace "{RE}" with rare earth element i.e. Nd, La, etc. + + For measured values, use the same names, but + replace ``predicted_dict`` with ``measured_df`` + :param optimizer: (function or str) function to perform optimization + + .. note:: + + The optimized variables are not directly the species parameters, + but instead are first multiplied by the initial guess before + sending becoming the species parameters. + + For example, say + + .. code-block:: python + + opt_dict = {'Nd(H(A)2)3(org):'h0':-4.7e6} + + If the bounds on h0 need to be [-4.7e7,-4.7e5], then + divide the bounds by the guess and get + + .. code-block:: python + + "bounds": [(1e-1, 1e1)] + + By default, the optimizer is scipy's optimize function with + + .. code-block:: python + + default_kwargs= {"method": 'SLSQP', + "bounds": [(1e-1, 1e1)] * len(x_guess), + "constraints": (), + "options": {'disp': True, + 'maxiter': 1000, + 'ftol': 1e-6}} + + Function needs to take inputs: + ``optimizer(objective_function, x_guess, kwargs)`` + + ``kwargs`` is optional + + Function needs to return: ((np.ndarray, float)) Optimized parameters, + objective_function value + + :param temp_xml_file_path: (str) path to temporary xml file. + + This xml file is a duplicate of the phases_xml_file name and is + modified during the optimization process to avoid changing the original + xml file + + default is local temp folder + + :param dependant_params_dict: (dict) dictionary containing information + about parameters dependant on opt_dict + """ + + def __init__(self, + exp_data, + phases_xml_filename, + phase_names, + aq_solvent_name, + extractant_name, + diluant_name, + complex_names, + rare_earth_ion_names, + re_species_list=None, + aq_solvent_rho=None, + extractant_rho=None, + diluant_rho=None, + opt_dict=None, + objective_function='Log-MSE', + optimizer='SLSQP', + temp_xml_file_path=None, + dependant_params_dict=None, + ): + self._built_in_obj_list = ['Log-MSE'] + self._built_in_opt_list = ['SLSQP'] + self._exp_data = exp_data + self._phases_xml_filename = phases_xml_filename + self._opt_dict = opt_dict + self._phase_names = phase_names + self._aq_solvent_name = aq_solvent_name + self._extractant_name = extractant_name + self._diluant_name = diluant_name + self._complex_names = complex_names + self._rare_earth_ion_names = rare_earth_ion_names + self._aq_solvent_rho = aq_solvent_rho + self._extractant_rho = extractant_rho + self._diluant_rho = diluant_rho + self._objective_function = None + self.set_objective_function(objective_function) + self._optimizer = None + self._re_species_list = re_species_list + self.set_optimizer(optimizer) + if temp_xml_file_path is None: + temp_xml_file_path = r'{0}/temp.xml'.format(os.getenv('TEMP')) + self._temp_xml_file_path = temp_xml_file_path + self._dependant_params_dict = dependant_params_dict + # Try and except for adding package data to path. + # This only works for sdist, not bdist + # If bdist is needed, research "manifest.in" python setup files + try: + shutil.copyfile(self._phases_xml_filename, + self._temp_xml_file_path) + self._phases = ct.import_phases(self._phases_xml_filename, + phase_names) + except FileNotFoundError: + self._phases_xml_filename = \ + pkg_resources.resource_filename('reeps', + r'..\data\xmls\{0}'.format( + phases_xml_filename)) + shutil.copyfile(self._phases_xml_filename, + self._temp_xml_file_path) + self._phases = ct.import_phases(self._phases_xml_filename, + phase_names) + if isinstance(self._exp_data, str): + try: + self._exp_df = pd.read_csv(self._exp_data) + except FileNotFoundError: + self._exp_data = pkg_resources.resource_filename( + 'reeps', r'..\data\csvs\{0}'.format(self._exp_data)) + self._exp_df = pd.read_csv(self._exp_data) + else: + self._exp_df = self._exp_data.copy() + + self._exp_df_columns = ['h_i', 'h_eq', 'z_i', 'z_eq'] + if self._re_species_list is None: + self._re_species_list = [] + for name in self._rare_earth_ion_names: + species = name.replace('+', '') + self._re_species_list.append(species) + for species in self._re_species_list: + self._exp_df_columns.append('{0}_aq_i'.format(species)) + self._exp_df_columns.append('{0}_aq_eq'.format(species)) + self._exp_df_columns.append('{0}_d_eq'.format(species)) + + self._exp_df.columns = self._exp_df_columns + for species in self._re_species_list: + self._exp_df['{0}_org_eq'.format(species)] = \ + self._exp_df['{0}_aq_eq'.format(species)] \ + * self._exp_df['{0}_d_eq'.format(species)] + + self._in_moles = None + + self._aq_ind = None + self._org_ind = None + self._re_charges = None + + self.set_in_moles(feed_vol=1) + self._predicted_dict = None + self.update_predicted_dict() + +
[docs] @staticmethod + def scipy_minimize(objective, x_guess, optimizer_kwargs=None): + """ The default optimizer for REEPS + + Uses scipy.minimize + + By default, options are + + .. code-block:: python + + default_kwargs= {"method": 'SLSQP', + "bounds": [(1e-1, 1e1)]*len(x_guess), + "constraints": (), + "options": {'disp': True, + 'maxiter': 1000, + 'ftol': 1e-6}} + + :param objective: (func) the objective function + :param x_guess: (np.ndarray) the initial guess (always 1) + :param optimizer_kwargs: (dict) dictionary of options for minimize + :returns: ((np.ndarray, float)) Optimized parameters, + objective_function value + """ + if optimizer_kwargs is None: + optimizer_kwargs = {"method": 'SLSQP', + "bounds": [(1e-1, 1e1)] * len(x_guess), + "constraints": (), + "options": {'disp': True, + 'maxiter': 1000, + 'ftol': 1e-6}} + res = minimize(objective, x_guess, **optimizer_kwargs) + est_parameters = res.x + return est_parameters, res.fun
+ +
[docs] def log_mean_squared_error(self, predicted_dict, meas_df): + """Default objective function for REEPS + + Returns the log mean squared error of + predicted distribution ratios (d=n_org/n_aq) + to measured d. + + np.sum((np.log10(d_pred)-np.log10(d_meas))\**2) + + :param predicted_dict: (dict) contains predicted data + :param meas_df: (pd.DataFrame) contains experimental data + :return: (float) log mean squared error between predicted and measured + """ + meas = np.concatenate([meas_df['{0}_d_eq'.format(species)].values + for species in self._re_species_list]) + pred = np.concatenate([ + predicted_dict['{0}_d_eq'.format(species)] + for species in self._re_species_list]) + log_pred = np.log10(pred) + log_meas = np.log10(meas) + log_diff = (log_pred - log_meas) ** 2 + obj = np.sum(log_diff) + return obj
+ +
[docs] def get_exp_df(self) -> pd.DataFrame: + """Returns the experimental DataFrame + + :return: (pd.DataFrame) Experimental data + """ + return self._exp_df
+ +
[docs] def set_exp_df(self, exp_data): + """Changes the experimental DataFrame to input exp_csv_filename data + and renames columns to internal REEPS names + + + h_i, h_eq, z_i, z_eq, {RE}_aq_i, {RE}_aq_eq, {RE}_d + + See class docstring on "exp_csv_filename" for further explanations. + + :param exp_data: (str or pd.DataFrame) + file name/path or DataFrame for experimental data csv + """ + self._exp_data = exp_data + if isinstance(self._exp_data, str): + try: + self._exp_df = pd.read_csv(self._exp_data) + except FileNotFoundError: + self._exp_data = pkg_resources.resource_filename( + 'reeps', r'..\data\csvs\{0}'.format(self._exp_data)) + self._exp_df = pd.read_csv(self._exp_data) + else: + self._exp_df = exp_data.copy() + self._exp_df_columns = ['h_i', 'h_eq', 'z_i', 'z_eq'] + if self._re_species_list is None: + self._re_species_list = [] + for name in self._rare_earth_ion_names: + species = name.replace('+', '') + self._re_species_list.append(species) + for species in self._re_species_list: + self._exp_df_columns.append('{0}_aq_i'.format(species)) + self._exp_df_columns.append('{0}_aq_eq'.format(species)) + self._exp_df_columns.append('{0}_d_eq'.format(species)) + self._exp_df.columns = self._exp_df_columns + for species in self._re_species_list: + self._exp_df['{0}_org_eq'.format(species)] = \ + self._exp_df['{0}_aq_eq'.format(species)] \ + * self._exp_df['{0}_d_eq'.format(species)] + self.set_in_moles(feed_vol=1) + self.update_predicted_dict() + return None
+ +
[docs] def get_phases(self) -> list: + """ + Returns the list of Cantera solutions + + :return: (list) list of Cantera solutions/phases + """ + return self._phases
+ +
[docs] def set_phases(self, phases_xml_filename, phase_names): + """Change list of Cantera solutions by inputting + new xml file name and phase names + + Also runs set_in_moles to set feed volume to 1 L + + :param phases_xml_filename: (str) xml file with parameters + for equilibrium calc + :param phase_names: (list) names of phases in xml file + """ + self._phases_xml_filename = phases_xml_filename + self._phase_names = phase_names + # Try and except for adding package data to path. + # This only works for sdist, not bdist + # If bdist is needed, research "manifest.in" python setup files + try: + shutil.copyfile(self._phases_xml_filename, + self._temp_xml_file_path) + self._phases = ct.import_phases(self._phases_xml_filename, + phase_names) + except FileNotFoundError: + self._phases_xml_filename = \ + pkg_resources.resource_filename('reeps', + r'..\data\xmls\{0}'.format( + phases_xml_filename)) + shutil.copyfile(self._phases_xml_filename, + self._temp_xml_file_path) + self._phases = ct.import_phases(self._phases_xml_filename, + phase_names) + self.set_in_moles(feed_vol=1) + self.update_predicted_dict() + return None
+ +
[docs] def get_opt_dict(self) -> dict: + """ + Returns the dictionary containing optimization information + + :return: (dict) dictionary containing info about which + species parameters are updated to fit model to experimental data + """ + return self._opt_dict
+ +
[docs] def set_opt_dict(self, opt_dict): + """ + Change the dictionary to input opt_dict. + + opt_dict specifies species parameters to be updated to + fit model to data + + See class docstring on "opt_dict" for more information. + + :param opt_dict: (dict) dictionary containing info about which + species parameters are updated to fit model to experimental data + """ + + self._opt_dict = opt_dict + return None
+ +
[docs] def get_aq_solvent_name(self) -> str: + """Returns aq_solvent_name + + :return: aq_solvent_name: (str) name of aqueous solvent in xml file + """ + return self._aq_solvent_name
+ +
[docs] def set_aq_solvent_name(self, aq_solvent_name): + """ Change aq_solvent_name to input aq_solvent_name + + :param aq_solvent_name: (str) name of aqueous solvent in xml file + """ + self._aq_solvent_name = aq_solvent_name + return None
+ +
[docs] def get_extractant_name(self) -> str: + """Returns extractant name + + :return: extractant_name: (str) name of extractant in xml file + """ + return self._extractant_name
+ +
[docs] def set_extractant_name(self, extractant_name): + """ + Change extractant_name to input extractant_name + :param extractant_name: (str) name of extractant in xml file + """ + self._extractant_name = extractant_name + return None
+ +
[docs] def get_diluant_name(self) -> str: + """ Returns diluant name + :return: diluant_name: (str) name of diluant in xml file + """ + return self._diluant_name
+ +
[docs] def set_diluant_name(self, diluant_name): + """ + Change diluant_name to input diluant_name + + :param diluant_name: (str) name of diluant in xml file + """ + self._diluant_name = diluant_name + return None
+ +
[docs] def get_complex_names(self) -> list: + """Returns list of complex names + + :return: complex_names: (list) names of complexes in xml file. + """ + return self._complex_names
+ +
[docs] def set_complex_names(self, complex_names): + """Change complex names list to input complex_names + + :param complex_names: (list) names of complexes in xml file. + """ + self._complex_names = complex_names + return None
+ +
[docs] def get_rare_earth_ion_names(self) -> list: + """Returns list of rare earth ion names + + :return: rare_earth_ion_names: (list) names of rare earth ions in + xml file + """ + return self._rare_earth_ion_names
+ +
[docs] def set_rare_earth_ion_names(self, rare_earth_ion_names): + """Change list of rare earth ion names to input + rare_earth_ion_names + + :param rare_earth_ion_names: (list) names of rare earth ions in + xml file + """ + self._rare_earth_ion_names = rare_earth_ion_names + return None
+ +
[docs] def get_re_species_list(self) -> list: + """Returns list of rare earth element names + + :return: re_species_list: (list) names of rare earth elements in + xml file + """ + return self._re_species_list
+ +
[docs] def set_re_species_list(self, re_species_list): + """Change list of rare earth ion names to input + rare_earth_ion_names + + :param re_species_list: (list) names of rare earth elements in + xml file + """ + self._re_species_list = re_species_list + return None
+ +
[docs] def get_aq_solvent_rho(self) -> str: + """Returns aqueous solvent density (g/L) + + :return: aq_solvent_rho: (float) density of aqueous solvent + """ + return self._aq_solvent_rho
+ +
[docs] def set_aq_solvent_rho(self, aq_solvent_rho): + """Changes aqueous solvent density (g/L) to input aq_solvent_rho + + :param aq_solvent_rho: (float) density of aqueous solvent + """ + self._aq_solvent_rho = aq_solvent_rho + return None
+ +
[docs] def get_extractant_rho(self) -> str: + """Returns extractant density (g/L) + + :return: extractant_rho: (float) density of extractant + """ + return self._extractant_rho
+ +
[docs] def set_extractant_rho(self, extractant_rho): + """Changes extractant density (g/L) to input extractant_rho + + :param extractant_rho: (float) density of extractant + """ + self._extractant_rho = extractant_rho + return None
+ +
[docs] def get_diluant_rho(self) -> str: + """Returns diluant density (g/L) + + :return: diluant_rho: (float) density of diluant + """ + return self._diluant_rho
+ +
[docs] def set_diluant_rho(self, diluant_rho): + """Changes diluant density (g/L) to input diluant_rho + + :param diluant_rho: (float) density of diluant + """ + self._diluant_rho = diluant_rho + return None
+ +
[docs] def set_in_moles(self, feed_vol): + """Function that initializes mole fractions to input feed_vol + + This function is called at initialization + + Sets in_moles to a pd.DataFrame containing initial mole fractions + + Columns for species and rows for different experiments + + This function also calls update_predicted_dict + + :param feed_vol: (float) feed volume of mixture (L) + """ + phases_copy = self._phases.copy() + exp_df = self._exp_df.copy() + solvent_name = self._aq_solvent_name + extractant_name = self._extractant_name + diluant_name = self._diluant_name + solvent_rho = self._aq_solvent_rho + extractant_rho = self._extractant_rho + diluant_rho = self._diluant_rho + re_names = self._rare_earth_ion_names + re_species_list = self._re_species_list + + mixed = ct.Mixture(phases_copy) + aq_ind = None + solvent_ind = None + for ind, phase in enumerate(phases_copy): + if solvent_name in phase.species_names: + aq_ind = ind + solvent_ind = phase.species_names.index(solvent_name) + if aq_ind is None: + raise Exception('Solvent "{0}" not found \ + in xml file'.format(solvent_name)) + + if aq_ind == 0: + org_ind = 1 + else: + org_ind = 0 + self._aq_ind = aq_ind + self._org_ind = org_ind + extractant_ind = phases_copy[org_ind].species_names.index( + extractant_name) + diluant_ind = phases_copy[org_ind].species_names.index(diluant_name) + + re_ind_list = [phases_copy[aq_ind].species_names.index(re_name) + for re_name in re_names] + re_charges = np.array([phases_copy[aq_ind].species(re_ind).charge + for re_ind in re_ind_list]) + self._re_charges = re_charges + + mix_aq = mixed.phase(aq_ind) + mix_org = mixed.phase(org_ind) + solvent_mw = mix_aq.molecular_weights[solvent_ind] # g/mol + extractant_mw = mix_org.molecular_weights[extractant_ind] + diluant_mw = mix_org.molecular_weights[diluant_ind] + if solvent_rho is None: + solvent_rho = mix_aq(aq_ind).partial_molar_volumes[ + solvent_ind] / solvent_mw * 1e6 # g/L + self._aq_solvent_rho = solvent_rho + if extractant_rho is None: + extractant_rho = mix_org(org_ind).partial_molar_volumes[ + extractant_ind] / extractant_mw * 1e6 + self._extractant_rho = extractant_rho + if diluant_rho is None: + diluant_rho = mix_org(org_ind).partial_molar_volumes[ + extractant_ind] / extractant_mw * 1e6 + self._diluant_rho = diluant_rho + + in_moles_data = [] + aq_phase_solvent_moles = feed_vol * solvent_rho / solvent_mw + for index, row in exp_df.iterrows(): + h_plus_moles = feed_vol * row['h_i'] + hydroxide_ions = 0 + rare_earth_moles = np.array([feed_vol * row[ + '{0}_aq_i'.format(re_species)] + for re_species in re_species_list]) + re_charge_sum = np.sum(re_charges * rare_earth_moles) + chlorine_moles = re_charge_sum + h_plus_moles + extractant_moles = feed_vol * row['z_i'] + extractant_vol = extractant_moles * extractant_mw / extractant_rho + diluant_vol = feed_vol - extractant_vol + diluant_moles = diluant_vol * diluant_rho / diluant_mw + complex_moles = np.zeros(len(re_species_list)) + + species_moles_aq = [aq_phase_solvent_moles, + h_plus_moles, + hydroxide_ions, + chlorine_moles] + species_moles_aq.extend(list(rare_earth_moles)) + species_moles_org = [extractant_moles, diluant_moles] + species_moles_org.extend(list(complex_moles)) + if aq_ind == 0: + species_moles = species_moles_aq + species_moles_org + else: + species_moles = species_moles_org + species_moles_aq + in_moles_data.append(species_moles) + self._in_moles = pd.DataFrame( + in_moles_data, columns=mixed.species_names) + self.update_predicted_dict() + return None
+ +
[docs] def get_in_moles(self) -> pd.DataFrame: + """Returns the in_moles DataFrame which contains the initial mole + fractions of each species for each experiment + + :return: in_moles: (pd.DataFrame) DataFrame with initial mole fractions + """ + return self._in_moles
+ +
[docs] def set_objective_function(self, objective_function): + """Change objective function to input objective_function. + + See class docstring on "objective_function" for instructions + + :param objective_function: (func) Objective function to quantify + error between model and experimental data + """ + if not callable(objective_function) \ + and objective_function not in self._built_in_obj_list: + raise Exception( + "objective_function must be a function " + "or in this strings list: {0}".format( + self._built_in_obj_list)) + if callable(objective_function): + if len(signature(objective_function).parameters) < 2: + raise Exception( + "objective_function must be a function " + "with at least 3 arguments:" + " f(predicted_dict, experimental_df, kwargs)") + if objective_function == 'Log-MSE': + objective_function = self.log_mean_squared_error + self._objective_function = objective_function + return None
+ +
[docs] def get_objective_function(self): + """Returns objective function + + :return: objective_function: (func) Objective function to quantify + error between model and experimental data + """ + return self._objective_function
+ +
[docs] def set_optimizer(self, optimizer): + """Change optimizer function to input optimizer. + + See class docstring on "optimizer" for instructions + + :param optimizer: (func) Optimizer function to minimize objective + function + """ + if not callable(optimizer) \ + and optimizer not in self._built_in_opt_list: + raise Exception( + "optimizer must be a function " + "or in this strings list: {0}".format( + self._built_in_opt_list)) + if callable(optimizer): + if len(signature(optimizer).parameters) < 2: + raise Exception( + "optimizer must be a function " + "with at least 2 arguments: " + "f(objective_func,x_guess, kwargs)") + if optimizer == 'SLSQP': + optimizer = self.scipy_minimize + self._optimizer = optimizer + return None
+ +
[docs] def get_optimizer(self): + """Returns objective function + + :return: optimizer: (func) Optimizer function to minimize objective + function + """ + return self._optimizer
+ +
[docs] def get_temp_xml_file_path(self): + """Returns path to temporary xml file. + + This xml file is a duplicate of the phases_xml_file name and is + modified during the optimization process to avoid changing the original + xml file. + + :return: temp_xml_file_path: (str) path to temporary xml file. + """ + return self._temp_xml_file_path
+ +
[docs] def set_temp_xml_file_path(self, temp_xml_file_path): + """Changes temporary xml file path to input temp_xml_file_path. + + This xml file is a duplicate of the phases_xml_file name and is + modified during the optimization process to avoid changing the original + xml file. + + :param temp_xml_file_path: (str) path to temporary xml file. + """ + self._temp_xml_file_path = temp_xml_file_path + return None
+ +
[docs] def get_dependant_params_dict(self): + """ + Returns the dependant_params_dict + :return: dependant_params_dict: (dict) dictionary containing + information about parameters dependant on opt_dict + """ + return self._dependant_params_dict
+ +
[docs] def set_dependant_params_dict(self, dependant_params_dict): + """ + Sets the dependant_params_dict + :param dependant_params_dict: (dict) dictionary containing information + about parameters dependant on opt_dict + """ + self._dependant_params_dict = dependant_params_dict + return None
+ +
[docs] def update_predicted_dict(self, + phases_xml_filename=None, + phase_names=None): + """Function that computes the predicted equilibrium concentrations + the fed phases_xml_filename parameters predicts given the initial + mole fractions set by in_moles() + + :param phases_xml_filename: (str)xml file with parameters + for equilibrium calc. If ``None``, the + current phases_xml_filename is used. + :param phase_names: (list) names of phases in xml file. + If ``None``, the current phases_names is used. + """ + if phases_xml_filename is None: + phases_xml_filename = self._phases_xml_filename + if phase_names is None: + phase_names = self._phase_names + aq_ind = self._aq_ind + org_ind = self._org_ind + complex_names = self._complex_names + extractant_name = self._extractant_name + rare_earth_ion_names = self._rare_earth_ion_names + in_moles = self._in_moles + re_species_list = self._re_species_list + + phases_copy = ct.import_phases(phases_xml_filename, phase_names) + mix = ct.Mixture(phases_copy) + key_names = ['h_eq', 'z_eq'] + for re_species in re_species_list: + key_names.append('{0}_aq_eq'.format(re_species)) + key_names.append('{0}_org_eq'.format(re_species)) + key_names.append('{0}_d_eq'.format(re_species)) + + predicted_dict = {'{0}'.format(key_name): [] + for key_name in key_names} + + for row in in_moles.values: + mix.species_moles = row + mix.equilibrate('TP', log_level=0) + re_org_array = np.array([mix.species_moles[mix.species_index( + org_ind, complex_name)] for complex_name in complex_names]) + re_aq_array = np.array([mix.species_moles[mix.species_index( + aq_ind, re_ion_name)] for re_ion_name in rare_earth_ion_names]) + d_array = re_org_array / re_aq_array + hydrogen_ions = mix.species_moles[mix.species_index(aq_ind, 'H+')] + extractant = mix.species_moles[mix.species_index( + org_ind, extractant_name)] + for index, re_species in enumerate(re_species_list): + predicted_dict['{0}_aq_eq'.format( + re_species)].append(re_aq_array[index]) + predicted_dict['{0}_org_eq'.format( + re_species)].append(re_org_array[index]) + predicted_dict['{0}_d_eq'.format( + re_species)].append(d_array[index]) + predicted_dict['h_eq'].append(hydrogen_ions) + predicted_dict['z_eq'].append(extractant) + for key, value in predicted_dict.items(): + predicted_dict[key] = np.array(value) + self._predicted_dict = predicted_dict + return None
+ +
[docs] def get_predicted_dict(self): + """Returns predicted dictionary of species concentrations + that xml parameters predicts given current in_moles + + :return: predicted_dict: (dict) dictionary of species concentrations + """ + return self._predicted_dict
+ + def _internal_objective(self, x, kwargs=None): + """ + Internal objective function. Uses objective function to compute value + If the optimizer requires vectorized variables ie pso, this function + takes care of it + + :param x: (list) thermo properties varied to minimize objective func + :param kwargs: (list) arguments for objective_function + """ + temp_xml_file_path = self._temp_xml_file_path + exp_df = self._exp_df + objective_function = self._objective_function + opt_dict = copy.deepcopy(self._opt_dict) + dep_dict = copy.deepcopy(self._dependant_params_dict) + x = np.array(x) + + if len(x.shape) == 1: + xs = np.array([x]) + vectorized_x = False + else: + vectorized_x = True + xs = x + objective_values = [] + for x in xs: + i = 0 + for species_name in opt_dict.keys(): + for thermo_prop in opt_dict[species_name].keys(): + if not np.isnan( + x[i]): # if nan, do not update xml with nan + opt_dict[species_name][thermo_prop] *= x[i] + i += 1 + + self.update_xml(opt_dict, + temp_xml_file_path, + dependant_params_dict=dep_dict) + + self.update_predicted_dict(temp_xml_file_path) + predicted_dict = self.get_predicted_dict() + self.update_predicted_dict() + + if kwargs is None: + # noinspection PyCallingNonCallable + obj = objective_function(predicted_dict, exp_df) + else: + # noinspection PyCallingNonCallable + obj = objective_function(predicted_dict, exp_df, **kwargs) + objective_values.append(obj) + if vectorized_x: + objective_values = np.array(objective_values) + else: + objective_values = objective_values[0] + return objective_values + +
[docs] def fit(self, + objective_function=None, + optimizer=None, + objective_kwargs=None, + optimizer_kwargs=None) -> tuple: + """Fits experimental to modeled data by minimizing objective function + with optimizer. Returns dictionary with opt_dict structure + + :param objective_function: (function) function to compute objective + If 'None', last set objective or default function is used + :param optimizer: (function) function to perform optimization + If 'None', last set optimizer or default is used + :param optimizer_kwargs: (dict) optional arguments for optimizer + :param objective_kwargs: (dict) optional arguments + for objective function + :returns tuple: (opt_dict (dict), opt_value (float)) + optimized opt_dict: Has identical structure as opt_dict + """ + if objective_function is not None: + self.set_objective_function(objective_function) + if optimizer is not None: + self.set_optimizer(optimizer) + + def objective(x): + return self._internal_objective(x, objective_kwargs) + + optimizer = self._optimizer + opt_dict = copy.deepcopy(self._opt_dict) + i = 0 + for species_name in opt_dict.keys(): + for _ in opt_dict[species_name].keys(): + i += 1 + x_guess = np.ones(i) + + if optimizer_kwargs is None: + # noinspection PyCallingNonCallable + est_parameters, obj_value = optimizer(objective, x_guess) + else: + # noinspection PyCallingNonCallable + est_parameters, obj_value = optimizer(objective, + x_guess, + optimizer_kwargs) + + i = 0 + for species_name in opt_dict.keys(): + for thermo_prop in opt_dict[species_name].keys(): + opt_dict[species_name][thermo_prop] *= est_parameters[i] + i += 1 + return opt_dict, obj_value
+ +
[docs] def update_xml(self, + info_dict, + phases_xml_filename=None, + dependant_params_dict=None): + """updates xml file with info_dict + + :param info_dict: (dict) info in {species_names:{thermo_prop:val}} + Requires an identical structure to opt_dict + :param phases_xml_filename: (str) xml filename if editing other xml + If ``None``, the current xml will be modified and the internal + Cantera phases will be refreshed to the new values. + :param dependant_params_dict: (dict) dictionary containing information + about parameters dependant on info_dict + """ + if phases_xml_filename is None: + phases_xml_filename = self._phases_xml_filename + new_dict = copy.deepcopy(info_dict) + dep_dict = dependant_params_dict + if dep_dict is not None: + for species_name in dep_dict.keys(): + for thermo_prop in dep_dict[species_name]: + mod_func = \ + dep_dict[species_name][thermo_prop]['function'] + mod_kwargs = \ + dep_dict[species_name][thermo_prop]['kwargs'] + ind_vars = \ + dep_dict[species_name][thermo_prop]['ind_vars'] + ind_vals = [new_dict[ind_var[0]][ind_var[1]] + for ind_var in ind_vars] + + new_dict[species_name] = {} + new_dict[species_name][thermo_prop] = {} + new_dict[species_name][thermo_prop] = \ + mod_func(ind_vals, **mod_kwargs) + # print(mod_func(ind_vals, **mod_kwargs)) + # print(new_dict) + + tree = ET.parse(phases_xml_filename) + root = tree.getroot() + # Update xml file + for species_name in new_dict.keys(): + for thermo_prop in new_dict[species_name].keys(): + for species in root.iter('species'): + if species.attrib['name'] == species_name: + for changed_prop in species.iter(thermo_prop): + changed_prop.text = str( + new_dict[species_name][thermo_prop]) + now = datetime.now() + changed_prop.set('updated', + 'Updated at {0}:{1} {2}-{3}-{4}' + .format(now.hour, now.minute, + now.month, now.day, + now.year)) + + tree.write(phases_xml_filename) + if phases_xml_filename == self._phases_xml_filename: + self.set_phases(self._phases_xml_filename, self._phase_names) + return None
+ + def _internal_objective_ver2(self, x, kwargs=None): + """ + ver2 generalizes to handle accessing parameters. ver1 assumes species + parameter is modified. ver2 assumes parameter is accessed by going + through two levels: upper and lower + Internal objective function. Uses objective function to compute value + If the optimizer requires vectorized variables ie pso, this function + takes care of it + + :param x: (list) thermo properties varied to minimize objective func + :param kwargs: (list) arguments for objective_function + """ + temp_xml_file_path = self._temp_xml_file_path + exp_df = self._exp_df + objective_function = self._objective_function + opt_dict = copy.deepcopy(self._opt_dict) + dep_dict = copy.deepcopy(self._dependant_params_dict) + x = np.array(x) + + if len(x.shape) == 1: + xs = np.array([x]) + vectorized_x = False + else: + vectorized_x = True + xs = x + objective_values = [] + for x in xs: + for ind, param_name in enumerate(opt_dict.keys()): + if not np.isnan( + x[ind]): # if nan, do not update xml with nan + opt_dict[param_name]['input_value'] *= x[ind] + + self.update_xml_ver2(opt_dict, + temp_xml_file_path, + dependant_params_dict=dep_dict) + + self.update_predicted_dict(temp_xml_file_path) + predicted_dict = self.get_predicted_dict() + self.update_predicted_dict() + + if kwargs is None: + # noinspection PyCallingNonCallable + obj = objective_function(predicted_dict, exp_df) + else: + # noinspection PyCallingNonCallable + obj = objective_function(predicted_dict, exp_df, **kwargs) + objective_values.append(obj) + if vectorized_x: + objective_values = np.array(objective_values) + else: + objective_values = objective_values[0] + return objective_values + +
[docs] def fit_ver2(self, + objective_function=None, + optimizer=None, + objective_kwargs=None, + optimizer_kwargs=None) -> tuple: + """Fits experimental to modeled data by minimizing objective function + with optimizer. Returns dictionary with opt_dict structure + + :param objective_function: (function) function to compute objective + If 'None', last set objective or default function is used + :param optimizer: (function) function to perform optimization + If 'None', last set optimizer or default is used + :param optimizer_kwargs: (dict) optional arguments for optimizer + :param objective_kwargs: (dict) optional arguments + for objective function + :returns tuple: (opt_dict (dict), opt_value (float)) + optimized opt_dict: Has identical structure as opt_dict + """ + if objective_function is not None: + self.set_objective_function(objective_function) + if optimizer is not None: + self.set_optimizer(optimizer) + + def objective(x): + return self._internal_objective_ver2(x, objective_kwargs) + + optimizer = self._optimizer + opt_dict = copy.deepcopy(self._opt_dict) + x_guess = np.ones(len(list(opt_dict.keys()))) + + if optimizer_kwargs is None: + # noinspection PyCallingNonCallable + est_parameters, obj_value = optimizer(objective, x_guess) + else: + # noinspection PyCallingNonCallable + est_parameters, obj_value = optimizer(objective, + x_guess, + optimizer_kwargs) + for ind, param_name in enumerate(opt_dict.keys()): + opt_dict[param_name]['input_value'] *= est_parameters[ind] + + return opt_dict, obj_value
+ +
[docs] def update_xml_ver2(self, + info_dict, + phases_xml_filename=None, + dependant_params_dict=None): + """updates xml file with info_dict + + :param info_dict: (dict) info in {species_names:{thermo_prop:val}} + Requires an identical structure to opt_dict + :param phases_xml_filename: (str) xml filename if editing other xml + If ``None``, the current xml will be modified and the internal + Cantera phases will be refreshed to the new values. + :param dependant_params_dict: (dict) dictionary containing information + about parameters dependant on info_dict + """ + if phases_xml_filename is None: + phases_xml_filename = self._phases_xml_filename + new_dict = copy.deepcopy(info_dict) + dep_dict = dependant_params_dict + + if dep_dict is not None: + new_dict.update(dep_dict) + for param_name in dep_dict.keys(): + mod_func = \ + dep_dict[param_name]['function'] + mod_kwargs = \ + dep_dict[param_name]['kwargs'] + if isinstance(dep_dict[param_name]['independent_params'], str): + ind_param_names = [dep_dict[ + param_name]['independent_params']] + else: + ind_param_names = \ + dep_dict[param_name]['independent_params'] + ind_vals = [new_dict[ind_param_name]['input_value'] + for ind_param_name in ind_param_names] + if mod_kwargs is None: + new_dict[param_name]['input_value'] = mod_func(ind_vals) + else: + new_dict[param_name]['input_value'] = \ + mod_func(ind_vals, + **mod_kwargs) + tree = ET.parse(phases_xml_filename) + root = tree.getroot() + # Update xml file + for key in list(new_dict.keys()): + d = new_dict[key] + now = datetime.now() + if (d['upper_attrib_name'] is not None + and d['lower_attrib_name'] is not None): + for child1 in root.iter(d['upper_element_name']): + if (child1.attrib[d['upper_attrib_name']] + == d['upper_attrib_value']): + for child2 in child1.iter(d['lower_element_name']): + if (child1.attrib[d['lower_attrib_name']] + == d['lower_attrib_value']): + child2.text = d['input_format'].format( + d['input_value']) + child2.set('updated', + 'Updated at {0}:{1} {2}-{3}-{4}' + .format(now.hour, now.minute, + now.month, now.day, + now.year)) + elif (d['upper_attrib_name'] is None + and d['lower_attrib_name'] is not None): + for child1 in root.iter(d['upper_element_name']): + for child2 in child1.iter(d['lower_element_name']): + if (child1.attrib[d['lower_attrib_name']] + == d['lower_attrib_value']): + child2.text = d['input_format'].format( + d['input_value']) + child2.set('updated', + 'Updated at {0}:{1} {2}-{3}-{4}' + .format(now.hour, now.minute, + now.month, now.day, + now.year)) + elif (d['upper_attrib_name'] is not None + and d['lower_attrib_name'] is None): + for child1 in root.iter(d['upper_element_name']): + if (child1.attrib[d['upper_attrib_name']] + == d['upper_attrib_value']): + for child2 in child1.iter(d['lower_element_name']): + child2.text = d['input_format'].format( + d['input_value']) + child2.set('updated', + 'Updated at {0}:{1} {2}-{3}-{4}' + .format(now.hour, now.minute, + now.month, now.day, + now.year)) + else: + for child1 in root.iter(d['upper_element_name']): + for child2 in child1.iter(d['lower_element_name']): + child2.text = d['input_format'].format( + d['input_value']) + child2.set('updated', 'Updated at {0}:{1} {2}-{3}-{4}' + .format(now.hour, now.minute, + now.month, now.day, + now.year)) + + tree.write(phases_xml_filename) + if phases_xml_filename == self._phases_xml_filename: + self.set_phases(self._phases_xml_filename, self._phase_names) + return None
+ +
[docs] def parity_plot(self, + compared_value=None, + c_data=None, + c_label=None, + plot_title=None, + save_path=None, + print_r_squared=False, + data_labels=None, + legend=True): + """ + Parity plot between measured and predicted compared_value. + Default compared value is {RE_1}_aq_eq + + :param compared_value: (str) Quantity to compare predicted and + experimental data. Can be any column containing "eq" in exp_df i.e. + h_eq, z_eq, {RE}_d_eq, etc. + :param plot_title: (str or boolean) + + If None (default): Plot title will be generated from compared_value + Recommend to just explore. If h_eq, plot_title is + "H^+ eq conc". + + If str: Plot title will be plot_title string + + If "False": No plot title + :param c_data: (list or np.ndarray) data for color axis + :param c_label: (str) label for color axis + :param save_path: (str) save path for parity plot + :param print_r_squared: (boolean) To plot or not to plot r-squared + value. Prints 2 places past decimal + :param data_labels: labels for the data such as paper's name where + experiment is pulled from. + :param legend: whether to display legend for data_labels. Has no + effect if data_labels is None + :return fig, ax: returns the figure and axes objects + """ + exp_df = self.get_exp_df() + predicted_dict = self.get_predicted_dict() + re_species_list = self._re_species_list + extractant_name = self.get_extractant_name() + re_charges = self._re_charges + if compared_value is None: + compared_value = '{0}_aq_eq'.format(re_species_list[0]) + pred = pd.DataFrame(predicted_dict)[compared_value].fillna(0).values + meas = exp_df[compared_value].fillna(0).values + name_breakdown = re.findall('[^_\W]+', compared_value) + compared_species = name_breakdown[0] + if compared_species == 'h': + feed_molarity = exp_df['h_i'].fillna(0).values + elif compared_species == 'z': + feed_molarity = exp_df['z_i'].fillna(0).values + else: + feed_molarity = exp_df[ + '{0}_aq_i'.format(compared_species)].fillna(0).values + if isinstance(data_labels, list): + combined_df = pd.DataFrame({'pred': pred, + 'meas': meas, + 'label': data_labels, + 'feed_molarity': feed_molarity}) + elif isinstance(c_data, str): + combined_df = pd.DataFrame({'pred': pred, + 'meas': meas, + c_data: exp_df[c_data].values, + 'feed_molarity': feed_molarity}) + else: + combined_df = pd.DataFrame({'pred': pred, + 'meas': meas, + 'feed_molarity': feed_molarity}) + + combined_df = combined_df[(combined_df['feed_molarity'] != 0)] + meas = combined_df['meas'].values + pred = combined_df['pred'].values + + min_data = np.min([pred, meas]) + max_data = np.max([pred, meas]) + min_max_data = np.array([min_data, max_data]) + + if compared_species == 'h': + default_title = '$H^+$ eq. conc. (mol/L)' + elif compared_species == 'z': + default_title = '{0} eq. conc. (mol/L)'.format(extractant_name) + else: + phase = name_breakdown[1] + if phase == 'aq': + re_charge = re_charges[re_species_list.index(compared_species)] + default_title = '$%s^{%d+}$ eq. conc. (mol/L)' \ + % (compared_species, re_charge) + elif phase == 'd': + default_title = '{0} distribution ratio'.format( + compared_species) + else: + default_title = '{0} complex eq. conc. (mol/L)'.format( + compared_species) + fig, ax = plt.subplots() + if isinstance(data_labels, list): + unique_labels = list(set(data_labels)) + for label in unique_labels: + filtered_data = combined_df[combined_df['label'] == label] + filtered_meas = filtered_data['meas'] + filtered_pred = filtered_data['pred'] + ax.scatter(filtered_meas, filtered_pred, label=label) + if legend: + ax.legend(loc='best') + + elif c_data is not None: + if isinstance(c_data, str): + c_data = combined_df[c_data].values + p1 = ax.scatter(meas, pred, c=c_data, alpha=1, cmap='viridis') + c_bar = fig.colorbar(p1, format='%.2f') + if c_label is not None: + c_bar.set_label(c_label, rotation=270, labelpad=20) + else: + sns.scatterplot(meas, pred, color="r", + legend=False) + ax.plot(min_max_data, min_max_data, color="b", label="") + + if print_r_squared: + ax.text(min_max_data[0], + min_max_data[1] * 0.9, + '$R^2$={0:.2f}'.format(self.r_squared(compared_value))) + # plt.legend(loc='lower right') + # else: + # plt.legend() + + ax.set(xlabel='Measured', ylabel='Predicted') + if plot_title is None: + ax.set_title(default_title) + elif isinstance(plot_title, str): + ax.set_title(plot_title) + set_size(8, 6) + plt.tight_layout() + plt.show() + if save_path is not None: + plt.savefig(save_path, bbox_inches='tight') + return fig, ax
+ +
[docs] def r_squared(self, compared_value=None): + """r-squared value comparing measured and predicted compared value + + Closer to 1, the better the model's predictions. + + :param compared_value: (str) Quantity to compare predicted and + experimental data. Can be any column containing "eq" in exp_df i.e. + h_eq, z_eq, {RE}_d_eq, etc. default is {RE}_aq_eq + """ + exp_df = self.get_exp_df() + predicted_dict = self.get_predicted_dict() + re_species_list = self._re_species_list + if compared_value is None: + compared_value = '{0}_aq_eq'.format(re_species_list[0]) + pred = pd.DataFrame(predicted_dict)[compared_value].fillna(0).values + predicted_y = np.array(pred) + actual_y = exp_df[compared_value].fillna(0).values + name_breakdown = re.findall('[^_\W]+', compared_value) + compared_species = name_breakdown[0] + if compared_species == 'h': + feed_molarity = exp_df['h_i'].fillna(0).values + elif compared_species == 'z': + feed_molarity = exp_df['z_i'].fillna(0).values + else: + feed_molarity = exp_df[ + '{0}_aq_i'.format(compared_species)].fillna(0).values + combined_df = pd.DataFrame({'pred': predicted_y, + 'meas': actual_y, + 'in_moles': feed_molarity}) + combined_df = combined_df[(combined_df['in_moles'] != 0)] + actual_y = combined_df['meas'].values + predicted_y = combined_df['pred'].values + num = sum((actual_y - predicted_y) ** 2) + den = sum((actual_y - np.mean(actual_y)) ** 2) + if den == 0: + r_2 = 0 + else: + r_2 = (1 - num / den) + return r_2
+ +
[docs] @staticmethod + def plot_3d_data(x_data, + y_data, + z_data, + c_data=None, + x_label=None, + y_label=None, + z_label=None, + c_label=None): + """ + + :param x_data: (list) list of data for x axis + :param y_data: (list) list of data for y axis + :param z_data: (list) list of data for z axis + :param c_data: (list) list of data for color axis + :param x_label: (str) label for x axis + :param y_label: (str) label for y axis + :param z_label: (str) label for z axis + :param c_label: (str) label for color axis + :return: + """ + + fig = plt.figure() + ax = fig.add_subplot(111, projection='3d') + if c_data is None: + ax.plot(x_data, y_data, z_data, 'o') + else: + p1 = ax.scatter(x_data, + y_data, + z_data, 'o', c=c_data, + cmap='viridis', alpha=1) + c_bar = fig.colorbar(p1) + if c_label is not None: + c_bar.set_label(c_label, rotation=270, labelpad=20) + if x_label is None: + ax.set_xlabel('x', labelpad=15) + else: + ax.set_xlabel(x_label, labelpad=15) + if y_label is None: + ax.set_ylabel('y', labelpad=15) + else: + ax.set_ylabel(y_label, labelpad=15) + if z_label is None: + ax.set_zlabel('z', labelpad=15) + else: + ax.set_zlabel(z_label, labelpad=15) + plt.show() + return fig, ax
+
+ +
+ +
+
+ + +
+ +
+

+ © Copyright 2020, Titus Quah, Nwike Iloeje + +

+
+ Built with Sphinx using a theme provided by Read the Docs. + +
+ +
+
+ +
+ +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/_sources/guide/about.rst.txt b/docs/_build/html/_sources/guide/about.rst.txt deleted file mode 100644 index dbd1116..0000000 --- a/docs/_build/html/_sources/guide/about.rst.txt +++ /dev/null @@ -1,61 +0,0 @@ -.. _about: - -************ -About -************ - -Authors -============= -Titus Quah, University of Utah, - -Nwike Iloeje, Argonne National Laboratory, - -Acknowledgements -================ -Based upon work supported by funding from Argonne National Laboratory provided by the U.S. Department of Energy, Office of Energy Efficiency and Renewable Energy (EERE), under contract DE-AC02-06CH11357 - -References -========== - -If you use LLEPE in your research , we kindly request that you cite the package as follows: - -T. Quah and C. O. Iloeje, “Liquid--Liquid Extraction Thermodynamic Parameter Estimator (LLEPE) for Multicomponent Separation Systems,” in Materials Processing Fundamentals 2021, 2021, pp. 107–120, doi: https://doi.org/10.1007/978-3-030-65253-1_9. - -License -======= - -:: - - Copyright © 2020, UChicago Argonne, LLC - All Rights Reserved - Software Name: LLEPE - By: Argonne National Laboratory, University of Utah - OPEN SOURCE LICENSE - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - 3. Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - - - ****************************************************************************************************** - DISCLAIMER - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, - OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY - OF SUCH DAMAGE. - *************************************************************************************************** diff --git a/docs/_build/html/_sources/guide/install.rst.txt b/docs/_build/html/_sources/guide/install.rst.txt index e7306d7..ce1a1f9 100644 --- a/docs/_build/html/_sources/guide/install.rst.txt +++ b/docs/_build/html/_sources/guide/install.rst.txt @@ -24,17 +24,17 @@ To install the latest master version: .. code-block:: bash - pip install git+https://github.com/ANL-CEEESA/LLEPE.git + pip install git+https://xgitlab.cels.anl.gov/summer-2020/parameter-estimation.git Development version =================== -To contribute to LLEPE, with support for running tests and building the documentation. +To contribute to Stable-Baselines, with support for running tests and building the documentation. .. code-block:: bash - git clone https://github.com/ANL-CEEESA/LLEPE.git + git clone https://xgitlab.cels.anl.gov/summer-2020/parameter-estimation.git && cd parameter-estimation pip install -e .[docs,tests] diff --git a/docs/_build/html/_sources/index.rst.txt b/docs/_build/html/_sources/index.rst.txt index a0c905b..9a973a9 100644 --- a/docs/_build/html/_sources/index.rst.txt +++ b/docs/_build/html/_sources/index.rst.txt @@ -1,8 +1,9 @@ -.. LLEPE: Liquid-Liquid Equilibrium Parameter Estimator - Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. - Released under the modified BSD license. See LICENSE for more details. +.. reeps documentation master file, created by Titus Quah + sphinx-quickstart on Tue Jun 9 10:13:23 2020. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. -Welcome to LLEPE's docs! - the Liquid-Liquid Extraction Parameter Estimator +Welcome to llepe's docs! - the Liquid-Liquid Extraction Parameter Estimator =========================================================================== LLEPE is a package for thermodynamic parameter estimation for liquid-liquid extraction modeling @@ -18,13 +19,12 @@ Error between predicted and experimental data is then minimized. guide/install guide/quickstart - guide/about .. toctree:: :maxdepth: 1 :caption: Searchers - modules/LLEPE + modules/reeps diff --git a/docs/_build/html/_sources/modules/LLEPE.rst.txt b/docs/_build/html/_sources/modules/LLEPE.rst.txt deleted file mode 100644 index 41daa9d..0000000 --- a/docs/_build/html/_sources/modules/LLEPE.rst.txt +++ /dev/null @@ -1,15 +0,0 @@ -.. _LLEPE: - -.. automodule:: llepe - -LLEPE -===== - - - -Parameters ----------- - -.. autoclass:: LLEPE - :members: - :inherited-members: \ No newline at end of file diff --git a/docs/_build/html/_sources/modules/reeps.rst.txt b/docs/_build/html/_sources/modules/reeps.rst.txt index 41daa9d..bba5c13 100644 --- a/docs/_build/html/_sources/modules/reeps.rst.txt +++ b/docs/_build/html/_sources/modules/reeps.rst.txt @@ -1,4 +1,4 @@ -.. _LLEPE: +.. _reeps: .. automodule:: llepe diff --git a/docs/_build/html/_static/alabaster.css b/docs/_build/html/_static/alabaster.css new file mode 100644 index 0000000..0eddaeb --- /dev/null +++ b/docs/_build/html/_static/alabaster.css @@ -0,0 +1,701 @@ +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: Georgia, serif; + font-size: 17px; + background-color: #fff; + color: #000; + margin: 0; + padding: 0; +} + + +div.document { + width: 940px; + margin: 30px auto 0 auto; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 220px; +} + +div.sphinxsidebar { + width: 220px; + font-size: 14px; + line-height: 1.5; +} + +hr { + border: 1px solid #B1B4B6; +} + +div.body { + background-color: #fff; + color: #3E4349; + padding: 0 30px 0 30px; +} + +div.body > .section { + text-align: left; +} + +div.footer { + width: 940px; + margin: 20px auto 30px auto; + font-size: 14px; + color: #888; + text-align: right; +} + +div.footer a { + color: #888; +} + +p.caption { + font-family: inherit; + font-size: inherit; +} + + +div.relations { + display: none; +} + + +div.sphinxsidebar a { + color: #444; + text-decoration: none; + border-bottom: 1px dotted #999; +} + +div.sphinxsidebar a:hover { + border-bottom: 1px solid #999; +} + +div.sphinxsidebarwrapper { + padding: 18px 10px; +} + +div.sphinxsidebarwrapper p.logo { + padding: 0; + margin: -10px 0 0 0px; + text-align: center; +} + +div.sphinxsidebarwrapper h1.logo { + margin-top: -10px; + text-align: center; + margin-bottom: 5px; + text-align: left; +} + +div.sphinxsidebarwrapper h1.logo-name { + margin-top: 0px; +} + +div.sphinxsidebarwrapper p.blurb { + margin-top: 0; + font-style: normal; +} + +div.sphinxsidebar h3, +div.sphinxsidebar h4 { + font-family: Georgia, serif; + color: #444; + font-size: 24px; + font-weight: normal; + margin: 0 0 5px 0; + padding: 0; +} + +div.sphinxsidebar h4 { + font-size: 20px; +} + +div.sphinxsidebar h3 a { + color: #444; +} + +div.sphinxsidebar p.logo a, +div.sphinxsidebar h3 a, +div.sphinxsidebar p.logo a:hover, +div.sphinxsidebar h3 a:hover { + border: none; +} + +div.sphinxsidebar p { + color: #555; + margin: 10px 0; +} + +div.sphinxsidebar ul { + margin: 10px 0; + padding: 0; + color: #000; +} + +div.sphinxsidebar ul li.toctree-l1 > a { + font-size: 120%; +} + +div.sphinxsidebar ul li.toctree-l2 > a { + font-size: 110%; +} + +div.sphinxsidebar input { + border: 1px solid #CCC; + font-family: Georgia, serif; + font-size: 1em; +} + +div.sphinxsidebar hr { + border: none; + height: 1px; + color: #AAA; + background: #AAA; + + text-align: left; + margin-left: 0; + width: 50%; +} + +div.sphinxsidebar .badge { + border-bottom: none; +} + +div.sphinxsidebar .badge:hover { + border-bottom: none; +} + +/* To address an issue with donation coming after search */ +div.sphinxsidebar h3.donation { + margin-top: 10px; +} + +/* -- body styles ----------------------------------------------------------- */ + +a { + color: #004B6B; + text-decoration: underline; +} + +a:hover { + color: #6D4100; + text-decoration: underline; +} + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: Georgia, serif; + font-weight: normal; + margin: 30px 0px 10px 0px; + padding: 0; +} + +div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } +div.body h2 { font-size: 180%; } +div.body h3 { font-size: 150%; } +div.body h4 { font-size: 130%; } +div.body h5 { font-size: 100%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #DDD; + padding: 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + color: #444; + background: #EAEAEA; +} + +div.body p, div.body dd, div.body li { + line-height: 1.4em; +} + +div.admonition { + margin: 20px 0px; + padding: 10px 30px; + background-color: #EEE; + border: 1px solid #CCC; +} + +div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { + background-color: #FBFBFB; + border-bottom: 1px solid #fafafa; +} + +div.admonition p.admonition-title { + font-family: Georgia, serif; + font-weight: normal; + font-size: 24px; + margin: 0 0 10px 0; + padding: 0; + line-height: 1; +} + +div.admonition p.last { + margin-bottom: 0; +} + +div.highlight { + background-color: #fff; +} + +dt:target, .highlight { + background: #FAF3E8; +} + +div.warning { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.danger { + background-color: #FCC; + border: 1px solid #FAA; + -moz-box-shadow: 2px 2px 4px #D52C2C; + -webkit-box-shadow: 2px 2px 4px #D52C2C; + box-shadow: 2px 2px 4px #D52C2C; +} + +div.error { + background-color: #FCC; + border: 1px solid #FAA; + -moz-box-shadow: 2px 2px 4px #D52C2C; + -webkit-box-shadow: 2px 2px 4px #D52C2C; + box-shadow: 2px 2px 4px #D52C2C; +} + +div.caution { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.attention { + background-color: #FCC; + border: 1px solid #FAA; +} + +div.important { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.note { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.tip { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.hint { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.seealso { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.topic { + background-color: #EEE; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre, tt, code { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; + font-size: 0.9em; +} + +.hll { + background-color: #FFC; + margin: 0 -12px; + padding: 0 12px; + display: block; +} + +img.screenshot { +} + +tt.descname, tt.descclassname, code.descname, code.descclassname { + font-size: 0.95em; +} + +tt.descname, code.descname { + padding-right: 0.08em; +} + +img.screenshot { + -moz-box-shadow: 2px 2px 4px #EEE; + -webkit-box-shadow: 2px 2px 4px #EEE; + box-shadow: 2px 2px 4px #EEE; +} + +table.docutils { + border: 1px solid #888; + -moz-box-shadow: 2px 2px 4px #EEE; + -webkit-box-shadow: 2px 2px 4px #EEE; + box-shadow: 2px 2px 4px #EEE; +} + +table.docutils td, table.docutils th { + border: 1px solid #888; + padding: 0.25em 0.7em; +} + +table.field-list, table.footnote { + border: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} + +table.footnote { + margin: 15px 0; + width: 100%; + border: 1px solid #EEE; + background: #FDFDFD; + font-size: 0.9em; +} + +table.footnote + table.footnote { + margin-top: -15px; + border-top: none; +} + +table.field-list th { + padding: 0 0.8em 0 0; +} + +table.field-list td { + padding: 0; +} + +table.field-list p { + margin-bottom: 0.8em; +} + +/* Cloned from + * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68 + */ +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +table.footnote td.label { + width: .1px; + padding: 0.3em 0 0.3em 0.5em; +} + +table.footnote td { + padding: 0.3em 0.5em; +} + +dl { + margin: 0; + padding: 0; +} + +dl dd { + margin-left: 30px; +} + +blockquote { + margin: 0 0 0 30px; + padding: 0; +} + +ul, ol { + /* Matches the 30px from the narrow-screen "li > ul" selector below */ + margin: 10px 0 10px 30px; + padding: 0; +} + +pre { + background: #EEE; + padding: 7px 30px; + margin: 15px 0px; + line-height: 1.3em; +} + +div.viewcode-block:target { + background: #ffd; +} + +dl pre, blockquote pre, li pre { + margin-left: 0; + padding-left: 30px; +} + +tt, code { + background-color: #ecf0f3; + color: #222; + /* padding: 1px 2px; */ +} + +tt.xref, code.xref, a tt { + background-color: #FBFBFB; + border-bottom: 1px solid #fff; +} + +a.reference { + text-decoration: none; + border-bottom: 1px dotted #004B6B; +} + +/* Don't put an underline on images */ +a.image-reference, a.image-reference:hover { + border-bottom: none; +} + +a.reference:hover { + border-bottom: 1px solid #6D4100; +} + +a.footnote-reference { + text-decoration: none; + font-size: 0.7em; + vertical-align: top; + border-bottom: 1px dotted #004B6B; +} + +a.footnote-reference:hover { + border-bottom: 1px solid #6D4100; +} + +a:hover tt, a:hover code { + background: #EEE; +} + + +@media screen and (max-width: 870px) { + + div.sphinxsidebar { + display: none; + } + + div.document { + width: 100%; + + } + + div.documentwrapper { + margin-left: 0; + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + } + + div.bodywrapper { + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + margin-left: 0; + } + + ul { + margin-left: 0; + } + + li > ul { + /* Matches the 30px from the "ul, ol" selector above */ + margin-left: 30px; + } + + .document { + width: auto; + } + + .footer { + width: auto; + } + + .bodywrapper { + margin: 0; + } + + .footer { + width: auto; + } + + .github { + display: none; + } + + + +} + + + +@media screen and (max-width: 875px) { + + body { + margin: 0; + padding: 20px 30px; + } + + div.documentwrapper { + float: none; + background: #fff; + } + + div.sphinxsidebar { + display: block; + float: none; + width: 102.5%; + margin: 50px -30px -20px -30px; + padding: 10px 20px; + background: #333; + color: #FFF; + } + + div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, + div.sphinxsidebar h3 a { + color: #fff; + } + + div.sphinxsidebar a { + color: #AAA; + } + + div.sphinxsidebar p.logo { + display: none; + } + + div.document { + width: 100%; + margin: 0; + } + + div.footer { + display: none; + } + + div.bodywrapper { + margin: 0; + } + + div.body { + min-height: 0; + padding: 0; + } + + .rtd_doc_footer { + display: none; + } + + .document { + width: auto; + } + + .footer { + width: auto; + } + + .footer { + width: auto; + } + + .github { + display: none; + } +} + + +/* misc. */ + +.revsys-inline { + display: none!important; +} + +/* Make nested-list/multi-paragraph items look better in Releases changelog + * pages. Without this, docutils' magical list fuckery causes inconsistent + * formatting between different release sub-lists. + */ +div#changelog > div.section > ul > li > p:only-child { + margin-bottom: 0; +} + +/* Hide fugly table cell borders in ..bibliography:: directive output */ +table.docutils.citation, table.docutils.citation td, table.docutils.citation th { + border: none; + /* Below needed in some edge cases; if not applied, bottom shadows appear */ + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} + + +/* relbar */ + +.related { + line-height: 30px; + width: 100%; + font-size: 0.9rem; +} + +.related.top { + border-bottom: 1px solid #EEE; + margin-bottom: 20px; +} + +.related.bottom { + border-top: 1px solid #EEE; +} + +.related ul { + padding: 0; + margin: 0; + list-style: none; +} + +.related li { + display: inline; +} + +nav#rellinks { + float: right; +} + +nav#rellinks li+li:before { + content: "|"; +} + +nav#breadcrumbs li+li:before { + content: "\00BB"; +} + +/* Hide certain items when printing */ +@media print { + div.related { + display: none; + } +} \ No newline at end of file diff --git a/docs/_build/html/_static/classic.css b/docs/_build/html/_static/classic.css new file mode 100644 index 0000000..b4fd80f --- /dev/null +++ b/docs/_build/html/_static/classic.css @@ -0,0 +1,266 @@ +/* + * classic.css_t + * ~~~~~~~~~~~~~ + * + * Sphinx stylesheet -- classic theme. + * + * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +html { + /* CSS hack for macOS's scrollbar (see #1125) */ + background-color: #FFFFFF; +} + +body { + font-family: sans-serif; + font-size: 100%; + background-color: #11303d; + color: #000; + margin: 0; + padding: 0; +} + +div.document { + background-color: #1c4e63; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 230px; +} + +div.body { + background-color: #ffffff; + color: #000000; + padding: 0 20px 30px 20px; +} + +div.footer { + color: #ffffff; + width: 100%; + padding: 9px 0 9px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #ffffff; + text-decoration: underline; +} + +div.related { + background-color: #133f52; + line-height: 30px; + color: #ffffff; +} + +div.related a { + color: #ffffff; +} + +div.sphinxsidebar { +} + +div.sphinxsidebar h3 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.4em; + font-weight: normal; + margin: 0; + padding: 0; +} + +div.sphinxsidebar h3 a { + color: #ffffff; +} + +div.sphinxsidebar h4 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.3em; + font-weight: normal; + margin: 5px 0 0 0; + padding: 0; +} + +div.sphinxsidebar p { + color: #ffffff; +} + +div.sphinxsidebar p.topless { + margin: 5px 10px 10px 10px; +} + +div.sphinxsidebar ul { + margin: 10px; + padding: 0; + color: #ffffff; +} + +div.sphinxsidebar a { + color: #98dbcc; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + + + +/* -- hyperlink styles ------------------------------------------------------ */ + +a { + color: #355f7c; + text-decoration: none; +} + +a:visited { + color: #355f7c; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + + + +/* -- body styles ----------------------------------------------------------- */ + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: 'Trebuchet MS', sans-serif; + background-color: #f2f2f2; + font-weight: normal; + color: #20435c; + border-bottom: 1px solid #ccc; + margin: 20px -20px 10px -20px; + padding: 3px 0 3px 10px; +} + +div.body h1 { margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 160%; } +div.body h3 { font-size: 140%; } +div.body h4 { font-size: 120%; } +div.body h5 { font-size: 110%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + text-align: justify; + line-height: 130%; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.admonition p { + margin-bottom: 5px; +} + +div.admonition pre { + margin-bottom: 5px; +} + +div.admonition ul, div.admonition ol { + margin-bottom: 5px; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +div.topic { + background-color: #eee; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #f66; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 5px; + background-color: #eeffcc; + color: #333333; + line-height: 120%; + border: 1px solid #ac9; + border-left: none; + border-right: none; +} + +code { + background-color: #ecf0f3; + padding: 0 1px 0 1px; + font-size: 0.95em; +} + +th, dl.field-list > dt { + background-color: #ede; +} + +.warning code { + background: #efc2c2; +} + +.note code { + background: #d6d6d6; +} + +.viewcode-back { + font-family: sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} + +div.code-block-caption { + color: #efefef; + background-color: #1c4e63; +} \ No newline at end of file diff --git a/docs/_build/html/_static/custom.css b/docs/_build/html/_static/custom.css new file mode 100644 index 0000000..2a924f1 --- /dev/null +++ b/docs/_build/html/_static/custom.css @@ -0,0 +1 @@ +/* This file intentionally left blank. */ diff --git a/docs/_build/html/_static/default.css b/docs/_build/html/_static/default.css new file mode 100644 index 0000000..81b9363 --- /dev/null +++ b/docs/_build/html/_static/default.css @@ -0,0 +1 @@ +@import url("classic.css"); diff --git a/docs/_build/html/_static/sidebar.js b/docs/_build/html/_static/sidebar.js new file mode 100644 index 0000000..657f8be --- /dev/null +++ b/docs/_build/html/_static/sidebar.js @@ -0,0 +1,159 @@ +/* + * sidebar.js + * ~~~~~~~~~~ + * + * This script makes the Sphinx sidebar collapsible. + * + * .sphinxsidebar contains .sphinxsidebarwrapper. This script adds + * in .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton + * used to collapse and expand the sidebar. + * + * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden + * and the width of the sidebar and the margin-left of the document + * are decreased. When the sidebar is expanded the opposite happens. + * This script saves a per-browser/per-session cookie used to + * remember the position of the sidebar among the pages. + * Once the browser is closed the cookie is deleted and the position + * reset to the default (expanded). + * + * :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +$(function() { + + + + + + + + + // global elements used by the functions. + // the 'sidebarbutton' element is defined as global after its + // creation, in the add_sidebar_button function + var bodywrapper = $('.bodywrapper'); + var sidebar = $('.sphinxsidebar'); + var sidebarwrapper = $('.sphinxsidebarwrapper'); + + // for some reason, the document has no sidebar; do not run into errors + if (!sidebar.length) return; + + // original margin-left of the bodywrapper and width of the sidebar + // with the sidebar expanded + var bw_margin_expanded = bodywrapper.css('margin-left'); + var ssb_width_expanded = sidebar.width(); + + // margin-left of the bodywrapper and width of the sidebar + // with the sidebar collapsed + var bw_margin_collapsed = '.8em'; + var ssb_width_collapsed = '.8em'; + + // colors used by the current theme + var dark_color = $('.related').css('background-color'); + var light_color = $('.document').css('background-color'); + + function sidebar_is_collapsed() { + return sidebarwrapper.is(':not(:visible)'); + } + + function toggle_sidebar() { + if (sidebar_is_collapsed()) + expand_sidebar(); + else + collapse_sidebar(); + } + + function collapse_sidebar() { + sidebarwrapper.hide(); + sidebar.css('width', ssb_width_collapsed); + bodywrapper.css('margin-left', bw_margin_collapsed); + sidebarbutton.css({ + 'margin-left': '0', + 'height': bodywrapper.height() + }); + sidebarbutton.find('span').text('»'); + sidebarbutton.attr('title', _('Expand sidebar')); + document.cookie = 'sidebar=collapsed'; + } + + function expand_sidebar() { + bodywrapper.css('margin-left', bw_margin_expanded); + sidebar.css('width', ssb_width_expanded); + sidebarwrapper.show(); + sidebarbutton.css({ + 'margin-left': ssb_width_expanded-12, + 'height': bodywrapper.height() + }); + sidebarbutton.find('span').text('«'); + sidebarbutton.attr('title', _('Collapse sidebar')); + document.cookie = 'sidebar=expanded'; + } + + function add_sidebar_button() { + sidebarwrapper.css({ + 'float': 'left', + 'margin-right': '0', + 'width': ssb_width_expanded - 28 + }); + // create the button + sidebar.append( + '
«
' + ); + var sidebarbutton = $('#sidebarbutton'); + light_color = sidebarbutton.css('background-color'); + // find the height of the viewport to center the '<<' in the page + var viewport_height; + if (window.innerHeight) + viewport_height = window.innerHeight; + else + viewport_height = $(window).height(); + sidebarbutton.find('span').css({ + 'display': 'block', + 'margin-top': (viewport_height - sidebar.position().top - 20) / 2 + }); + + sidebarbutton.click(toggle_sidebar); + sidebarbutton.attr('title', _('Collapse sidebar')); + sidebarbutton.css({ + 'color': '#FFFFFF', + 'border-left': '1px solid ' + dark_color, + 'font-size': '1.2em', + 'cursor': 'pointer', + 'height': bodywrapper.height(), + 'padding-top': '1px', + 'margin-left': ssb_width_expanded - 12 + }); + + sidebarbutton.hover( + function () { + $(this).css('background-color', dark_color); + }, + function () { + $(this).css('background-color', light_color); + } + ); + } + + function set_position_from_cookie() { + if (!document.cookie) + return; + var items = document.cookie.split(';'); + for(var k=0; k
  • Installation
  • Getting Started
  • -
  • About
  • Searchers

    @@ -170,7 +169,7 @@

    F

    @@ -178,45 +177,45 @@

    G

    @@ -228,14 +227,14 @@ llepe @@ -247,7 +246,7 @@ module @@ -256,11 +255,11 @@

    P

    @@ -268,7 +267,7 @@

    R

    @@ -276,45 +275,45 @@

    S

    @@ -322,13 +321,13 @@

    U

    @@ -345,7 +344,7 @@

    - © Copyright 2020, UChicago Argonne, LLC. + © Copyright 2020, Titus Quah, Nwike Iloeje

    diff --git a/docs/_build/html/guide/about.html b/docs/_build/html/guide/about.html deleted file mode 100644 index 67be0bd..0000000 --- a/docs/_build/html/guide/about.html +++ /dev/null @@ -1,272 +0,0 @@ - - - - - - - - - - - About — LLEPE 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - -
    - - - - - -
    - -
    - - - - - - - - - - - - - - - - - -
    - - - - -
    -
    -
    -
    - -
    -

    About

    -
    -

    Authors

    -

    Titus Quah, University of Utah, <titus{dot}quah{at}gmail{dot}com>

    -

    Nwike Iloeje, Argonne National Laboratory, <ciloeje{at}anl{dot}gov>

    -
    -
    -

    Acknowledgements

    -

    Based upon work supported by funding from Argonne National Laboratory provided by the U.S. Department of Energy, Office of Energy Efficiency and Renewable Energy (EERE), under contract DE-AC02-06CH11357

    -
    -
    -

    References

    -

    If you use LLEPE in your research , we kindly request that you cite the package as follows:

    -
      -
    1. Quah and C. O. Iloeje, “Liquid--Liquid Extraction Thermodynamic Parameter Estimator (LLEPE) for Multicomponent Separation Systems,” in Materials Processing Fundamentals 2021, 2021, pp. 107–120, doi: https://doi.org/10.1007/978-3-030-65253-1_9.

    2. -
    -
    -
    -

    License

    -
    Copyright © 2020, UChicago Argonne, LLC
    -All Rights Reserved
    - Software Name: LLEPE
    -By: Argonne National Laboratory, University of Utah
    -OPEN SOURCE LICENSE
    -
    -Redistribution and use in source and binary forms, with or without
    -modification, are permitted provided that the following conditions are met:
    -
    -1. Redistributions of source code must retain the above copyright notice,
    -   this list of conditions and the following disclaimer.
    -2. Redistributions in binary form must reproduce the above copyright notice,
    -   this list of conditions and the following disclaimer in the documentation
    -   and/or other materials provided with the distribution.
    -3. Neither the name of the copyright holder nor the names of its contributors
    -   may be used to endorse or promote products derived from this software without
    -   specific prior written permission.
    -
    -
    -******************************************************************************************************
    -DISCLAIMER
    -
    -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    -IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
    -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
    -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
    -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
    -OF SUCH DAMAGE.
    -***************************************************************************************************
    -
    -
    -
    -
    - - -
    - -
    - - -
    -
    - -
    - -
    - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/guide/install.html b/docs/_build/html/guide/install.html index 17e566e..f28dddc 100644 --- a/docs/_build/html/guide/install.html +++ b/docs/_build/html/guide/install.html @@ -36,7 +36,7 @@ - + @@ -97,7 +97,7 @@

    Searchers

    @@ -175,14 +175,14 @@

    Bleeding-edge version

    To install the latest master version:

    -
    pip install git+https://github.com/ANL-CEEESA/LLEPE.git
    +
    pip install git+https://xgitlab.cels.anl.gov/summer-2020/parameter-estimation.git
     

    Development version

    -

    To contribute to LLEPE, with support for running tests and building the documentation.

    -
    git clone https://github.com/ANL-CEEESA/LLEPE.git
    +

    To contribute to Stable-Baselines, with support for running tests and building the documentation.

    +
    git clone https://xgitlab.cels.anl.gov/summer-2020/parameter-estimation.git && cd parameter-estimation
     pip install -e .[docs,tests]
     
    @@ -204,7 +204,7 @@ pip install -e .[docs,tests] - +
    @@ -213,7 +213,7 @@ pip install -e .[docs,tests]

    - © Copyright 2020, UChicago Argonne, LLC. + © Copyright 2020, Titus Quah, Nwike Iloeje

    diff --git a/docs/_build/html/guide/quickstart.html b/docs/_build/html/guide/quickstart.html index 72b8974..948381e 100644 --- a/docs/_build/html/guide/quickstart.html +++ b/docs/_build/html/guide/quickstart.html @@ -218,7 +218,7 @@ aqueous phase.

    - © Copyright 2020, UChicago Argonne, LLC. + © Copyright 2020, Titus Quah, Nwike Iloeje

    diff --git a/docs/_build/html/index.html b/docs/_build/html/index.html index 9cee7a4..7f6af8e 100644 --- a/docs/_build/html/index.html +++ b/docs/_build/html/index.html @@ -8,7 +8,7 @@ - Welcome to LLEPE's docs! - the Liquid-Liquid Extraction Parameter Estimator — LLEPE 1.0.0 documentation + Welcome to llepe's docs! - the Liquid-Liquid Extraction Parameter Estimator — LLEPE 1.0.0 documentation @@ -86,11 +86,10 @@

    Searchers

    @@ -136,7 +135,7 @@
  • Docs »
  • -
  • Welcome to LLEPE's docs! - the Liquid-Liquid Extraction Parameter Estimator
  • +
  • Welcome to llepe's docs! - the Liquid-Liquid Extraction Parameter Estimator
  • @@ -156,7 +155,7 @@
    -

    Welcome to LLEPE's docs! - the Liquid-Liquid Extraction Parameter Estimator

    +

    Welcome to llepe's docs! - the Liquid-Liquid Extraction Parameter Estimator

    LLEPE is a package for thermodynamic parameter estimation for liquid-liquid extraction modeling

    LLEPE takes experimental data in a csv and system data in a xml.

    The package then uses Cantera, another python package, to simulate equilibrium.

    @@ -173,19 +172,12 @@
  • Getting Started
  • -
  • About -
  • Searchers

    @@ -216,7 +208,7 @@

    - © Copyright 2020, UChicago Argonne, LLC. + © Copyright 2020, Titus Quah, Nwike Iloeje

    diff --git a/docs/_build/html/modules/LLEPE.html b/docs/_build/html/modules/LLEPE.html deleted file mode 100644 index 4f1ff3b..0000000 --- a/docs/_build/html/modules/LLEPE.html +++ /dev/null @@ -1,1123 +0,0 @@ - - - - - - - - - - - LLEPE — LLEPE 1.0.0 documentation - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - -
    - - - - - -
    - -
    - - - - - - - - - - - - - - - - - -
    - - - - -
    -
    -
    -
    - -
    -

    LLEPE

    -
    -

    Parameters

    -
    -
    -class llepe.LLEPE(exp_data, phases_xml_filename, phase_names, aq_solvent_name, extractant_name, diluant_name, complex_names, extracted_species_ion_names, extracted_species_list=None, aq_solvent_rho=None, extractant_rho=None, diluant_rho=None, opt_dict=None, objective_function='Log-MSE', optimizer='scipy_minimize', temp_xml_file_path=None, dependant_params_dict=None, custom_objects_dict=None)[source]
    -

    Liquid-Liquid Extraction Parameter estimator

    -
    -

    Note

    -

    The order in which the extracted species (ES) appear in the csv file -must be the same order as they appear in the xml, complex_names and -extracted_species_ion_names.

    -

    For example, say in exp_data, ES_1 is Nd ES_2 is Pr, -and

    -
    aq_solvent_name = 'H2O(L)'
    -extractant_name = '(HA)2(org)'
    -diluent_name = 'dodecane'
    -
    -
    -

    Then:

    -

    The exp_data column ordering must be (names do not matter):

    -

    [h_i, h_eq, z_i, z_eq, Nd_aq_i, Nd_aq_eq, Nd_d_eq, -Pr_aq_i, Pr_aq_eq, Pr_d_eq]

    -

    The aqueous speciesArray must be -"H2O(L) H+ OH- Cl- Nd+++ Pr+++"

    -

    The organic speciesArray must be -"(HA)2(org) dodecane Nd(H(A)2)3(org) Pr(H(A)2)3(org)"

    -
    complex_names = ['Nd(H(A)2)3(org)', 'Pr(H(A)2)3(org)']
    -extracted_species_ion_names = ['Nd+++', 'Pr+++']
    -
    -
    -
    -
    -
    Parameters
    -
      -
    • exp_data --

      (str or pd.DataFrame) csv file name -or DataFrame with experimental data

      -

      In the .csv file, the rows are different experiments and -columns are the measured quantities.

      -

      The ordering of the columns needs to be:

      -

      [h_i, h_eq, z_i, z_eq, -{ES_1}_aq_i, {ES_1}_aq_eq, {ES_1}_d_eq, -{ES_2}_aq_i, {ES_2}_aq_eq, {ES_2}_d_eq,... -{ES_N}_aq_i, {ES_N}_aq_eq, {ES_N}_d_eq]

      -

      Naming does not matter, just the order.

      -

      Where {ES_1}-{ES_N} are the extracted species names of interest -i.e. Nd, Pr, La, etc.

      -

      Below is an explanation of the columns.

      - ----- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

      Index

      Column

      Meaning

      0

      h_i

      Initial Concentration of -H+ ions (mol/L)

      1

      h_eq

      Equilibrium concentration of -H+ ions (mol/L)

      2

      z_i

      Initial concentration of -extractant (mol/L)

      3

      z_eq

      Equilibrium concentration of -extractant (mol/L)

      4

      {ES}_aq_i

      Initial concentration of ES ions (mol/L)

      5

      {ES}_aq_eq

      Equilibrium concentration of ES ions -in aqueous phase (mol/L)

      6

      {ES}_d_eq

      Equilibrium Ratio between amount of -ES atoms in organic to aqueous

      -

    • -
    • phases_xml_filename --

      (str) xml file with parameters -for equilibrium calc

      -

      Would recommend copying and modifying xmls located in data/xmls -or in Cantera's "data" folder

      -

      speciesArray fields need specific ordering.

      -

      In aqueous phase: aq_solvent_name, H+, OH-, Cl-, ES_1, ES_2, ..., ES_N

      -

      (ES_1-ES_N) represent ES ion names i.e. Nd+++, Pr+++

      -

      In organic phase : extractant_name, diluant_name, ES_1, ES_2, ..., ES_N

      -

      (ES_1-ES_N) represent ES complex names -i.e. Nd(H(A)2)3(org), Pr(H(A)2)3(org)

      -

    • -
    • phase_names --

      (list) names of phases in xml file

      -

      Found in the xml file under <phase ... id={phase_name}>

      -

    • -
    • aq_solvent_name -- (str) name of aqueous solvent in xml file

    • -
    • extractant_name -- (str) name of extractant in xml file

    • -
    • diluant_name -- (str) name of diluant in xml file

    • -
    • complex_names -- (list) names of complexes in xml file.

    • -
    • extracted_species_ion_names -- (list) names of extracted species ions -in xml file

    • -
    • extracted_species_list --

      (list) names of extracted species elements.

      -

      If None, extracted_species_list will be extracted_species_ion_names -without '+' i.e. 'Nd+++'->'Nd'

      -

    • -
    • aq_solvent_rho --

      (float) density of solvent (g/L)

      -

      If None, molar volume/molecular weight is used from xml

      -

    • -
    • extractant_rho --

      (float) density of extractant (g/L)

      -

      If None, molar volume/molecular weight is used from xml

      -

    • -
    • diluant_rho --

      (float) density of diluant (g/L)

      -

      If None, molar volume/molecular weight is used from xml

      -

    • -
    • opt_dict --

      (dict) dictionary containing info about which -species parameters are updated to fit model to experimental data

      -

      Should have the format as below. Dictionary keys under user defined -parameter name must be named as shown below ('upper_element_name', -'upper_attrib_name', etc.). 'attrib_name's and 'attrib_value's can -be None. {} denotes areas for user to fill in.

      -
      opt_dict = {"{user_defined_name_for_parameter_1}":
      -                {'upper_element_name': {param_upper_element},
      -                'upper_attrib_name': {param_upper_attrib_name},
      -                'upper_attrib_value': {param_upper_attrib_value},
      -                'lower_element_name': {param_lower_element},
      -                'lower_attrib_name': {param_lower_attrib_name},
      -                'lower_attrib_value': {param_lower_attrib_value},
      -                'input_format': {str format to input input_value}
      -                'input_value': {guess_value}},
      -            "{user_defined_name_for_parameter_2}":
      -                            ...
      -            ...
      -            }
      -
      -
      -

      See example files for more examples.

      -

    • -
    • objective_function --

      (function or str) function to compute objective

      -

      By default, the objective function is log mean squared error -of distribution ratio

      -
      np.sum((np.log10(d_pred)-np.log10(d_meas))^2)
      -
      -
      -

      Function needs to take inputs:

      -
      objective_function(predicted_dict, measured_df, kwargs)
      -
      -
      -

      kwargs is optional

      -

      Function needs to return: (float) value computed by objective function

      -

      Below is the guide for referencing predicted values

      - ---- - - - - - - - - - - - - - - - - - - - - - - -

      To access

      Use

      hydrogen ion conc in aq

      predicted_dict['h_eq']

      extractant conc in org

      predicted_dict['z_eq']

      ES ion eq conc in aq

      predicted_dict['{ES}_aq_eq']

      ES complex eq conc in org

      predicted_dict['{ES}_org_eq']

      ES distribution ratio

      predicted_dict['{ES}_d_eq']

      -

      Replace "{ES}" with extracted species element i.e. Nd, La, etc.

      -

      For measured values, use the same names, but -replace predicted_dict with measured_df

      -

    • -
    • optimizer --

      (function or str) function to perform optimization

      -
      -

      Note

      -

      The optimized variables are not directly the species parameters, -but instead are first multiplied by the initial guess before -sending becoming the species parameters.

      -

      For example, say

      -
      opt_dict = {'Nd(H(A)2)3(org):'h0':-4.7e6}
      -
      -
      -

      If the bounds on h0 need to be [-4.7e7,-4.7e5], then -divide the bounds by the guess and get

      -
      "bounds": [(1e-1, 1e1)]
      -
      -
      -
      -

      By default, the optimizer is scipy's optimize function with

      -
      default_kwargs= {"method": 'SLSQP',
      -                 "bounds": [(1e-1, 1e1)] * len(x_guess),
      -                 "constraints": (),
      -                 "options": {'disp': True,
      -                             'maxiter': 1000,
      -                             'ftol': 1e-6}}
      -
      -
      -

      Function needs to take inputs: -optimizer(objective_function, x_guess, kwargs)

      -

      kwargs is optional

      -
      -
      Function needs to return: ((np.ndarray, float)) Optimized parameters,

      objective_function value

      -
      -
      -

    • -
    • temp_xml_file_path --

      (str) path to temporary xml file.

      -

      This xml file is a duplicate of the phases_xml_file name and is -modified during the optimization process to avoid changing the original -xml file

      -

      default is local temp folder

      -

    • -
    • dependant_params_dict --

      (dict) dictionary containing information -about parameters dependant on opt_dict. Has a similar structure to -opt_dict except instead of input values, it has 3 other fields: -'function', 'kwargs', and 'independent_params.

      -

      'function' is a function of the form

      -

      function(independent_param__value_list, custom_objects_dict, -**kwargs)

      -

      'kwargs' are the extra arguments to pass to function

      -

      'independent_params' is a list of parameter names in opt_dict that the -dependent_param is a function of.

      -

      'custom_objects_dict' is for accessing the estimator's internal -custom_objects_dict and must be included in the arguments, even if the -custom_objects_dict is not set and is None.

      -

      See example code for usage.

      -

    • -
    • custom_objects_dict -- (dict) dictionary containing custom objects -format: {<object_name_string>: <object>,...}

    • -
    -
    -
    -
    -
    -fit(objective_function=None, optimizer=None, objective_kwargs=None, optimizer_kwargs=None) → tuple[source]
    -

    Fits experimental to modeled data by minimizing objective function -with optimizer. Returns dictionary with opt_dict structure

    -
    -
    Parameters
    -
      -
    • objective_function -- (function) function to compute objective -If 'None', last set objective or default function is used

    • -
    • optimizer -- (function) function to perform optimization -If 'None', last set optimizer or default is used

    • -
    • optimizer_kwargs -- (dict) optional arguments for optimizer

    • -
    • objective_kwargs -- (dict) optional arguments -for objective function

    • -
    -
    -
    Returns tuple
    -

    (opt_dict (dict), opt_value (float)) -optimized opt_dict: Has identical structure as opt_dict

    -
    -
    -
    - -
    -
    -get_aq_solvent_name() → str[source]
    -

    Returns aq_solvent_name

    -
    -
    Returns
    -

    aq_solvent_name: (str) name of aqueous solvent in xml file

    -
    -
    -
    - -
    -
    -get_aq_solvent_rho() → str[source]
    -

    Returns aqueous solvent density (g/L)

    -
    -
    Returns
    -

    aq_solvent_rho: (float) density of aqueous solvent

    -
    -
    -
    - -
    -
    -get_complex_names() → list[source]
    -

    Returns list of complex names

    -
    -
    Returns
    -

    complex_names: (list) names of complexes in xml file.

    -
    -
    -
    - -
    -
    -get_custom_objects_dict()[source]
    -

    Returns the custom_objects_dict

    -
    -
    Returns
    -

    custom_objects_dict: (dict) dictionary containing -information about custom objects from user

    -
    -
    -
    - -
    -
    -get_dependant_params_dict()[source]
    -

    Returns the dependant_params_dict

    -
    -
    Returns
    -

    dependant_params_dict: (dict) dictionary containing -information about parameters dependant on opt_dict

    -
    -
    -
    - -
    -
    -get_diluant_name() → str[source]
    -

    Returns diluant name -:return: diluant_name: (str) name of diluant in xml file

    -
    - -
    -
    -get_diluant_rho() → str[source]
    -

    Returns diluant density (g/L)

    -
    -
    Returns
    -

    diluant_rho: (float) density of diluant

    -
    -
    -
    - -
    -
    -get_exp_df() → pandas.core.frame.DataFrame[source]
    -

    Returns the experimental DataFrame

    -
    -
    Returns
    -

    (pd.DataFrame) Experimental data

    -
    -
    -
    - -
    -
    -get_extractant_name() → str[source]
    -

    Returns extractant name

    -
    -
    Returns
    -

    extractant_name: (str) name of extractant in xml file

    -
    -
    -
    - -
    -
    -get_extractant_rho() → str[source]
    -

    Returns extractant density (g/L)

    -
    -
    Returns
    -

    extractant_rho: (float) density of extractant

    -
    -
    -
    - -
    -
    -get_extracted_species_ion_names() → list[source]
    -

    Returns list of extracted species ion names

    -
    -
    Returns
    -

    extracted_species_ion_names: (list) names of -extracted species ions in xml file

    -
    -
    -
    - -
    -
    -get_extracted_species_list() → list[source]
    -

    Returns list of extracted species names

    -
    -
    Returns
    -

    extracted_species_list: (list) names of extracted species in -xml file

    -
    -
    -
    - -
    -
    -get_in_moles() → pandas.core.frame.DataFrame[source]
    -

    Returns the in_moles DataFrame which contains the initial mole -fractions of each species for each experiment

    -
    -
    Returns
    -

    in_moles: (pd.DataFrame) DataFrame with initial mole fractions

    -
    -
    -
    - -
    -
    -get_objective_function()[source]
    -

    Returns objective function

    -
    -
    Returns
    -

    objective_function: (func) Objective function to quantify -error between model and experimental data

    -
    -
    -
    - -
    -
    -get_opt_dict() → dict[source]
    -

    Returns the dictionary containing optimization information

    -
    -
    Returns
    -

    (dict) dictionary containing info about which -species parameters are updated to fit model to experimental data

    -
    -
    -
    - -
    -
    -get_optimizer()[source]
    -

    Returns objective function

    -
    -
    Returns
    -

    optimizer: (func) Optimizer function to minimize objective -function

    -
    -
    -
    - -
    -
    -get_phases() → list[source]
    -

    Returns the list of Cantera solutions

    -
    -
    Returns
    -

    (list) list of Cantera solutions/phases

    -
    -
    -
    - -
    -
    -get_predicted_dict()[source]
    -

    Returns predicted dictionary of species concentrations -that xml parameters predicts given current in_moles

    -
    -
    Returns
    -

    predicted_dict: (dict) dictionary of species concentrations

    -
    -
    -
    - -
    -
    -get_temp_xml_file_path()[source]
    -

    Returns path to temporary xml file.

    -

    This xml file is a duplicate of the phases_xml_file name and is -modified during the optimization process to avoid changing the original -xml file.

    -
    -
    Returns
    -

    temp_xml_file_path: (str) path to temporary xml file.

    -
    -
    -
    - -
    -
    -log_mean_squared_error(predicted_dict, meas_df)[source]
    -

    Default objective function for LLEPE

    -

    Returns the log mean squared error of -predicted distribution ratios (d=n_org/n_aq) -to measured d.

    -

    np.sum((np.log10(d_pred)-np.log10(d_meas))**2)

    -
    -
    Parameters
    -
      -
    • predicted_dict -- (dict) contains predicted data

    • -
    • meas_df -- (pd.DataFrame) contains experimental data

    • -
    -
    -
    Returns
    -

    (float) log mean squared error between predicted and measured

    -
    -
    -
    - -
    -
    -parity_plot(compared_value=None, c_data=None, c_label=None, plot_title=None, save_path=None, print_r_squared=False, data_labels=None, legend=True)[source]
    -

    Parity plot between measured and predicted compared_value. -Default compared value is {ES_1}_aq_eq

    -
    -
    Parameters
    -
      -
    • compared_value -- (str) Quantity to compare predicted and -experimental data. Can be any column containing "eq" in exp_df i.e. -h_eq, z_eq, {ES}_d_eq, etc.

    • -
    • plot_title --

      (str or boolean)

      -
      -
      If None (default): Plot title will be generated from compared_value

      Recommend to just explore. If h_eq, plot_title is -"H^+ eq conc".

      -
      -
      -

      If str: Plot title will be plot_title string

      -

      If "False": No plot title

      -

    • -
    • c_data -- (list or np.ndarray) data for color axis

    • -
    • c_label -- (str) label for color axis

    • -
    • save_path -- (str) save path for parity plot

    • -
    • print_r_squared -- (boolean) To plot or not to plot r-squared -value. Prints 2 places past decimal

    • -
    • data_labels -- labels for the data such as paper's name where -experiment is pulled from.

    • -
    • legend -- whether to display legend for data_labels. Has no -effect if data_labels is None

    • -
    -
    -
    Return fig, ax
    -

    returns the figure and axes objects

    -
    -
    -
    - -
    -
    -static plot_3d_data(x_data, y_data, z_data, c_data=None, x_label=None, y_label=None, z_label=None, c_label=None)[source]
    -

    THis is for plotting 3d scatter plots. -We suggest use matplotlib's ax.scatter to make 3d plots.

    -
    -
    Parameters
    -
      -
    • x_data -- (list) list of data for x axis

    • -
    • y_data -- (list) list of data for y axis

    • -
    • z_data -- (list) list of data for z axis

    • -
    • c_data -- (list) list of data for color axis

    • -
    • x_label -- (str) label for x axis

    • -
    • y_label -- (str) label for y axis

    • -
    • z_label -- (str) label for z axis

    • -
    • c_label -- (str) label for color axis

    • -
    -
    -
    Returns
    -

    -
    -
    -
    - -
    -
    -r_squared(compared_value=None)[source]
    -

    r-squared value comparing measured and predicted compared value

    -

    Closer to 1, the better the model's predictions.

    -
    -
    Parameters
    -

    compared_value -- (str) Quantity to compare predicted and -experimental data. Can be any column containing "eq" in exp_df i.e. -h_eq, z_eq, {ES}_d_eq, etc. default is {ES}_aq_eq

    -
    -
    -
    - -
    -
    -static scipy_minimize(objective, x_guess, optimizer_kwargs=None)[source]
    -

    The default optimizer for LLEPE

    -

    Uses scipy.minimize

    -

    By default, options are

    -
    default_kwargs= {"method": 'SLSQP',
    -                "bounds": [(1e-1, 1e1)]*len(x_guess),
    -                "constraints": (),
    -                "options": {'disp': True,
    -                            'maxiter': 1000,
    -                            'ftol': 1e-6}}
    -
    -
    -
    -
    Parameters
    -
      -
    • objective -- (func) the objective function

    • -
    • x_guess -- (np.ndarray) the initial guess (always 1)

    • -
    • optimizer_kwargs -- (dict) dictionary of options for minimize

    • -
    -
    -
    Returns
    -

    ((np.ndarray, float)) Optimized parameters, -objective_function value

    -
    -
    -
    - -
    -
    -set_aq_solvent_name(aq_solvent_name)[source]
    -

    Change aq_solvent_name to input aq_solvent_name

    -
    -
    Parameters
    -

    aq_solvent_name -- (str) name of aqueous solvent in xml file

    -
    -
    -
    - -
    -
    -set_aq_solvent_rho(aq_solvent_rho)[source]
    -

    Changes aqueous solvent density (g/L) to input aq_solvent_rho

    -
    -
    Parameters
    -

    aq_solvent_rho -- (float) density of aqueous solvent

    -
    -
    -
    - -
    -
    -set_complex_names(complex_names)[source]
    -

    Change complex names list to input complex_names

    -
    -
    Parameters
    -

    complex_names -- (list) names of complexes in xml file.

    -
    -
    -
    - -
    -
    -set_custom_objects_dict(custom_objects_dict)[source]
    -

    Sets the custom_objects_dict

    -
    -
    Parameters
    -

    custom_objects_dict -- (dict) dictionary containing information -about about custom objects from user

    -
    -
    -
    - -
    -
    -set_dependant_params_dict(dependant_params_dict)[source]
    -

    Sets the dependant_params_dict

    -
    -
    Parameters
    -

    dependant_params_dict -- (dict) dictionary containing information -about parameters dependant on opt_dict

    -
    -
    -
    - -
    -
    -set_diluant_name(diluant_name)[source]
    -

    Change diluant_name to input diluant_name

    -
    -
    Parameters
    -

    diluant_name -- (str) name of diluant in xml file

    -
    -
    -
    - -
    -
    -set_diluant_rho(diluant_rho)[source]
    -

    Changes diluant density (g/L) to input diluant_rho

    -
    -
    Parameters
    -

    diluant_rho -- (float) density of diluant

    -
    -
    -
    - -
    -
    -set_exp_df(exp_data)[source]
    -

    Changes the experimental DataFrame to input exp_csv_filename data -and renames columns to internal LLEPE names

    -

    h_i, h_eq, z_i, z_eq, {ES}_aq_i, {ES}_aq_eq, {ES}_d

    -

    See class docstring on "exp_csv_filename" for further explanations.

    -
    -
    Parameters
    -

    exp_data -- (str or pd.DataFrame) -file name/path or DataFrame for experimental data csv

    -
    -
    -
    - -
    -
    -set_extractant_name(extractant_name)[source]
    -

    Change extractant_name to input extractant_name -:param extractant_name: (str) name of extractant in xml file

    -
    - -
    -
    -set_extractant_rho(extractant_rho)[source]
    -

    Changes extractant density (g/L) to input extractant_rho

    -
    -
    Parameters
    -

    extractant_rho -- (float) density of extractant

    -
    -
    -
    - -
    -
    -set_extracted_species_ion_names(extracted_species_ion_names)[source]
    -
    -
    Change list of extracted species ion names to input

    extracted_species_ion_names

    -
    -
    -
    -
    Parameters
    -

    extracted_species_ion_names -- (list) names of extracted species -ions in xml file

    -
    -
    -
    - -
    -
    -set_extracted_species_list(extracted_species_list)[source]
    -
    -
    Change list of extracted species ion names to input

    extracted_species_ion_names

    -
    -
    -
    -
    Parameters
    -

    extracted_species_list -- (list) names of extracted species in -xml file

    -
    -
    -
    - -
    -
    -set_in_moles(feed_vol)[source]
    -

    Function that initializes mole fractions to input feed_vol

    -

    This function is called at initialization

    -

    Sets in_moles to a pd.DataFrame containing initial mole fractions

    -

    Columns for species and rows for different experiments

    -

    This function also calls update_predicted_dict

    -
    -
    Parameters
    -

    feed_vol -- (float) feed volume of mixture (L)

    -
    -
    -
    - -
    -
    -set_objective_function(objective_function)[source]
    -

    Change objective function to input objective_function.

    -
    -

    See class docstring on "objective_function" for instructions

    -
    -
    -
    Parameters
    -

    objective_function -- (func) Objective function to quantify -error between model and experimental data

    -
    -
    -
    - -
    -
    -set_opt_dict(opt_dict)[source]
    -

    Change the dictionary to input opt_dict.

    -

    opt_dict specifies species parameters to be updated to -fit model to data

    -

    See class docstring on "opt_dict" for more information.

    -
    -
    Parameters
    -

    opt_dict -- (dict) dictionary containing info about which -species parameters are updated to fit model to experimental data

    -
    -
    -
    - -
    -
    -set_optimizer(optimizer)[source]
    -

    Change optimizer function to input optimizer.

    -
    -

    See class docstring on "optimizer" for instructions

    -
    -
    -
    Parameters
    -

    optimizer -- (func) Optimizer function to minimize objective -function

    -
    -
    -
    - -
    -
    -set_phases(phases_xml_filename, phase_names)[source]
    -

    Change list of Cantera solutions by inputting -new xml file name and phase names

    -

    Also runs set_in_moles to set feed volume to 1 L

    -
    -
    Parameters
    -
      -
    • phases_xml_filename -- (str) xml file with parameters -for equilibrium calc

    • -
    • phase_names -- (list) names of phases in xml file

    • -
    -
    -
    -
    - -
    -
    -set_temp_xml_file_path(temp_xml_file_path)[source]
    -

    Changes temporary xml file path to input temp_xml_file_path.

    -

    This xml file is a duplicate of the phases_xml_file name and is -modified during the optimization process to avoid changing the original -xml file.

    -
    -
    Parameters
    -

    temp_xml_file_path -- (str) path to temporary xml file.

    -
    -
    -
    - -
    -
    -update_custom_objects_dict(info_dict)[source]
    -

    updates internal custom_objects_dict with info_dict

    -
    -
    Parameters
    -

    info_dict -- Requires an identical structure to opt_dict -Ignores items with keys containing "custom_object_name"

    -
    -
    Returns
    -

    None

    -
    -
    -
    - -
    -
    -update_predicted_dict(phases_xml_filename=None, phase_names=None)[source]
    -

    Function that computes the predicted equilibrium concentrations -the fed phases_xml_filename parameters predicts given the initial -mole fractions set by in_moles()

    -
    -
    Parameters
    -
      -
    • phases_xml_filename -- (str)xml file with parameters -for equilibrium calc. If None, the -current phases_xml_filename is used.

    • -
    • phase_names -- (list) names of phases in xml file. -If None, the current phases_names is used.

    • -
    -
    -
    -
    - -
    -
    -update_xml(info_dict, phases_xml_filename=None, dependant_params_dict=None)[source]
    -

    updates xml file with info_dict

    -
    -
    Parameters
    -
      -
    • info_dict -- (dict) Requires an identical structure to opt_dict -Ignores items with keys containing "custom_object_name"

    • -
    • phases_xml_filename -- (str) xml filename if editing other xml -If None, the current xml will be modified and the internal -Cantera phases will be refreshed to the new values.

    • -
    • dependant_params_dict -- (dict) dictionary containing information -about parameters dependant on info_dict

    • -
    -
    -
    -
    - -
    - -
    -
    - - -
    - -
    - - -
    -
    - -
    - -
    - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/_build/html/modules/reeps.html b/docs/_build/html/modules/reeps.html index 8f4ad8e..9275abb 100644 --- a/docs/_build/html/modules/reeps.html +++ b/docs/_build/html/modules/reeps.html @@ -34,7 +34,8 @@ - + + @@ -86,6 +87,13 @@
  • Installation
  • Getting Started
  • +

    Searchers

    + @@ -149,8 +157,8 @@
    -
    -

    LLEPE

    +
    +

    LLEPE

    Parameters

    @@ -162,7 +170,7 @@

    The order in which the extracted species (ES) appear in the csv file must be the same order as they appear in the xml, complex_names and extracted_species_ion_names.

    -

    For example, say in exp_data, ES_1 is Nd ES_2 is Pr, +

    For example, say in exp_csv_filename's csv, ES_1 is Nd ES_2 is Pr, and

    aq_solvent_name = 'H2O(L)'
     extractant_name = '(HA)2(org)'
    @@ -170,7 +178,7 @@ and

    Then:

    -

    The exp_data column ordering must be (names do not matter):

    +

    The csvs column ordering must be:

    [h_i, h_eq, z_i, z_eq, Nd_aq_i, Nd_aq_eq, Nd_d_eq, Pr_aq_i, Pr_aq_eq, Pr_d_eq]

    The aqueous speciesArray must be @@ -710,9 +718,7 @@ effect if data_labels is None

    static plot_3d_data(x_data, y_data, z_data, c_data=None, x_label=None, y_label=None, z_label=None, c_label=None)[source]
    -

    THis is for plotting 3d scatter plots. -We suggest use matplotlib's ax.scatter to make 3d plots.

    -
    +
    Parameters
    • x_data -- (list) list of data for x axis

    • @@ -1071,12 +1077,19 @@ about parameters dependant on info_dict