├── __init__.py ├── PharmaPy ├── __init__.py ├── PharmaPy.egg-info │ ├── not-zip-safe │ ├── top_level.txt │ ├── dependency_links.txt │ ├── requires.txt │ ├── PKG-INFO │ └── SOURCES.txt ├── Errors.py ├── general_interpolation.py ├── ProcessControl.py ├── Utilities.py ├── CheckModule.py ├── Gaussians.py ├── jac_module.py ├── LevMarq.py ├── animate_profiles.py ├── ThreePhaseSettler.py ├── Interpolation.py ├── Calibration.py ├── Streams.py └── Extractors.py ├── doc ├── online_docs │ ├── general_features.rst │ ├── simulation_executive.rst │ ├── images │ │ ├── fda_logo.png │ │ ├── purdue_logo.png │ │ ├── FDA_footer_logo.png │ │ ├── PharmaPy_logo.jpeg │ │ └── Purdue_footer_logo.png │ ├── examples │ │ └── index.rst │ ├── docstrings │ │ ├── index.rst │ │ ├── kinetics.rst │ │ ├── phases.rst │ │ └── unit_operations.rst │ ├── publications.rst │ ├── _templates │ │ └── footer.html │ ├── installation.rst │ ├── index.rst │ ├── conf.py │ ├── advanced_usage.rst │ └── references.bib ├── Makefile └── make.bat ├── requirements.txt ├── tests ├── Flowsheet │ └── data │ │ ├── raw_material_cost.csv │ │ └── compound_database.json └── integration │ ├── data │ ├── pfr_test_constructor_kwargs.json │ ├── pfr_test_pure_comp.json │ ├── pfr_test_expected_times.csv │ ├── pfr_test_expected_conc_0.csv │ ├── pfr_test_expected_conc_1.csv │ ├── pfr_test_expected_conc_2.csv │ ├── pfr_test_expected_conc_3.csv │ └── pfr_test_expected_conc_4.csv │ └── reactor_tests.py ├── workshop_data ├── Case_study_3 │ ├── raw_material_cost.csv │ ├── pfd_flowsheet.png │ └── compound_database.json ├── Case_study_1 │ ├── rxn_network.png │ ├── ziegler_data_798.csv │ ├── ziegler_data_773.csv │ ├── ziegler_data_748.csv │ ├── ziegler_data_698.csv │ ├── ziegler_data_723.csv │ ├── ziegler_data_673.csv │ ├── ziegler_data_773K.csv │ ├── ziegler_conc_init.json │ ├── ziegler_data_698K.csv │ ├── ziegler_data_723K.csv │ ├── ziegler_data_748K.csv │ ├── ziegler_data_673K.csv │ ├── compounds_ziegler.json │ ├── physical_property_descriptors.json │ ├── pure_components_ziegler.json │ └── pure_component_database.json └── Case_study_2 │ └── compounds_mom.json ├── data ├── minimum_modeling_objects.json ├── evaporator │ └── props_nitrogen.json └── thermodynamics │ └── unifac_rk_qk.csv ├── setup.py ├── InstallOnWindows.bat ├── InstallOnMac.sh ├── .readthedocs.yaml ├── .gitignore ├── README.md ├── LICENSE.md └── install_instructions.txt /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /PharmaPy/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/online_docs/general_features.rst: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/online_docs/simulation_executive.rst: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /PharmaPy/PharmaPy.egg-info/not-zip-safe: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /PharmaPy/PharmaPy.egg-info/top_level.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /PharmaPy/PharmaPy.egg-info/dependency_links.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /PharmaPy/PharmaPy.egg-info/requires.txt: -------------------------------------------------------------------------------- 1 | pyomo 2 | coverage 3 | numpy 4 | scipy 5 | pandas 6 | assimulo 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy<2 2 | cython<3 3 | scipy<1.14 4 | assimulo==3.4.3 5 | matplotlib 6 | pandas 7 | -------------------------------------------------------------------------------- /tests/Flowsheet/data/raw_material_cost.csv: -------------------------------------------------------------------------------- 1 | compound,cost (USD/kg) 2 | A,10 3 | B,12 4 | C,0 5 | D,0 6 | solvent,2 7 | -------------------------------------------------------------------------------- /doc/online_docs/images/fda_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CryPTSys/PharmaPy/HEAD/doc/online_docs/images/fda_logo.png -------------------------------------------------------------------------------- /workshop_data/Case_study_3/raw_material_cost.csv: -------------------------------------------------------------------------------- 1 | compound,cost (USD/kg) 2 | A,10 3 | B,12 4 | C,0 5 | D,0 6 | solvent,2 7 | -------------------------------------------------------------------------------- /doc/online_docs/images/purdue_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CryPTSys/PharmaPy/HEAD/doc/online_docs/images/purdue_logo.png -------------------------------------------------------------------------------- /doc/online_docs/images/FDA_footer_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CryPTSys/PharmaPy/HEAD/doc/online_docs/images/FDA_footer_logo.png -------------------------------------------------------------------------------- /doc/online_docs/images/PharmaPy_logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CryPTSys/PharmaPy/HEAD/doc/online_docs/images/PharmaPy_logo.jpeg -------------------------------------------------------------------------------- /workshop_data/Case_study_1/rxn_network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CryPTSys/PharmaPy/HEAD/workshop_data/Case_study_1/rxn_network.png -------------------------------------------------------------------------------- /doc/online_docs/images/Purdue_footer_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CryPTSys/PharmaPy/HEAD/doc/online_docs/images/Purdue_footer_logo.png -------------------------------------------------------------------------------- /workshop_data/Case_study_3/pfd_flowsheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CryPTSys/PharmaPy/HEAD/workshop_data/Case_study_3/pfd_flowsheet.png -------------------------------------------------------------------------------- /workshop_data/Case_study_1/ziegler_data_798.csv: -------------------------------------------------------------------------------- 1 | time,CB,CC 2 | 3,25.2,20.9 3 | 3.25,33.1,25.2 4 | 3.5,21.6,17.3 5 | 4,20.9,36.7 6 | 5,4.3,56.8 7 | 7,0,61.8 8 | -------------------------------------------------------------------------------- /workshop_data/Case_study_1/ziegler_data_773.csv: -------------------------------------------------------------------------------- 1 | time,CB,CC 2 | 3,6.5,0 3 | 4,24.4,23 4 | 4.5,26.6,32.4 5 | 5,25.9,37.4 6 | 5.5,17.3,45.3 7 | 6,21.6,45.3 8 | 6.5,1.4,57.5 9 | 10,0,60.4 10 | -------------------------------------------------------------------------------- /doc/online_docs/examples/index.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Examples 3 | =============== 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | param_estimation_solved 9 | PFR_Batch_solved 10 | PharmaPy_guide -------------------------------------------------------------------------------- /doc/online_docs/docstrings/index.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | Documentation 3 | ========================= 4 | .. toctree:: 5 | :maxdepth: 1 6 | 7 | unit_operations.rst 8 | kinetics.rst 9 | phases.rst 10 | 11 | -------------------------------------------------------------------------------- /workshop_data/Case_study_1/ziegler_data_748.csv: -------------------------------------------------------------------------------- 1 | time,CB,CC 2 | 3,0.7,0 3 | 4.5,17.3,2.9 4 | 5,23,17.3 5 | 5.5,24.4,20.9 6 | 6,23,25.9 7 | 6.5,33.1,29.5 8 | 7,31.6,33.8 9 | 8,20.9,45.3 10 | 9,10.1,53.2 11 | 10,4.3,58.2 12 | 12.5,0.7,57.5 13 | 15,0.7,61.1 14 | -------------------------------------------------------------------------------- /workshop_data/Case_study_1/ziegler_data_698.csv: -------------------------------------------------------------------------------- 1 | time,CB,CC 2 | 5,6.5,0 3 | 7,14.4,1.4 4 | 10,18,10.8 5 | 12.5,16.5,14.4 6 | 15,29.5,21.6 7 | 17.5,23.7,30.2 8 | 20,36.7,33.1 9 | 25,27.3,40.3 10 | 30,16.5,47.5 11 | 40,7.2,55.4 12 | 50,3.6,56.8 13 | 60,2.2,59.7 14 | -------------------------------------------------------------------------------- /workshop_data/Case_study_1/ziegler_data_723.csv: -------------------------------------------------------------------------------- 1 | time,CB,CC 2 | 5,8.6,0 3 | 7.5,15.8,2.9 4 | 8,25.9,16.5 5 | 9,25.2,24.4 6 | 10,26.6,29.5 7 | 11,33.8,35.2 8 | 12.5,25.9,39.5 9 | 15,20.1,45.3 10 | 17.5,12.9,43.1 11 | 17.5,9.3,54.6 12 | 20,3.6,59.7 13 | 20,2.2,53.9 14 | -------------------------------------------------------------------------------- /PharmaPy/PharmaPy.egg-info/PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: PharmaPy 3 | Version: 0.0.0 4 | Summary: UNKNOWN 5 | Home-page: UNKNOWN 6 | Author: Daniel Casas-Orozco 7 | Author-email: dcasasor@purdue.edu 8 | License: UNKNOWN 9 | Description: UNKNOWN 10 | Platform: UNKNOWN 11 | -------------------------------------------------------------------------------- /workshop_data/Case_study_1/ziegler_data_673.csv: -------------------------------------------------------------------------------- 1 | time,CB,CC 2 | 5,0,0 3 | 7,2.2,0 4 | 10,11.5,0.7 5 | 15,13.7,7.2 6 | 20,15.1,11.5 7 | 25,17.3,15.8 8 | 30,17.3,20.9 9 | 40,20.1,26.6 10 | 50,20.1,32.4 11 | 60,22.3,38.1 12 | 80,20.9,43.2 13 | 100,11.5,49.6 14 | 120,6.5,51.8 15 | 150,3.6,54.7 16 | -------------------------------------------------------------------------------- /PharmaPy/PharmaPy.egg-info/SOURCES.txt: -------------------------------------------------------------------------------- 1 | README.md 2 | setup.py 3 | source/PharmaPy.egg-info/PKG-INFO 4 | source/PharmaPy.egg-info/SOURCES.txt 5 | source/PharmaPy.egg-info/dependency_links.txt 6 | source/PharmaPy.egg-info/not-zip-safe 7 | source/PharmaPy.egg-info/requires.txt 8 | source/PharmaPy.egg-info/top_level.txt -------------------------------------------------------------------------------- /data/minimum_modeling_objects.json: -------------------------------------------------------------------------------- 1 | {"special": {"Mixer": ["Inlets"], "DynamicCollector": ["Inlet"]}, 2 | "Continuous": ["Phases", "Inlet"], 3 | "Semibatch": ["Phases", "Inlet"], 4 | "Batch": ["Phases"], 5 | "has_kinetics": {"modules": ["Reactors", "Crystallizers"], "classes": []}, 6 | "has_utility": {"modules": ["Reactors"], "classes": ["MSMPR", "Evaporator", "ContinuousEvaporator"]} 7 | } -------------------------------------------------------------------------------- /doc/online_docs/docstrings/kinetics.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | Kinetic classes 3 | ==================== 4 | 5 | Documentation for the class objects creation are presented in this section [Highlighted in blue]. 6 | Documentation for methods called internally and not requiring user definition are ignored. 7 | 8 | .. automodule:: PharmaPy.Kinetics 9 | :members: 10 | :undoc-members: 11 | :show-inheritance: 12 | -------------------------------------------------------------------------------- /PharmaPy/Errors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Jun 13 11:14:31 2022 4 | 5 | @author: dcasasor 6 | """ 7 | 8 | 9 | class PharmaPyValueError(ValueError): 10 | pass 11 | 12 | 13 | class PharmaPyNonImplementedError(NotImplementedError): 14 | pass 15 | 16 | 17 | class PharmaPySpecificationError(AttributeError): 18 | pass 19 | 20 | 21 | class PharmaPyTypeError(TypeError): 22 | pass 23 | 24 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages, Extension 2 | 3 | # Read the content of requirements.txt into a list 4 | with open("requirements.txt", "r") as f: 5 | requirements = f.read().splitlines() 6 | 7 | setup(name='PharmaPy', 8 | version='0.0.1', 9 | packages=find_packages(), 10 | author='Daniel Casas-Orozco', 11 | author_email='dcasasor@purdue.edu', 12 | license='', 13 | url='', 14 | py_modules=["PharmaPy"], 15 | install_requires=requirements) -------------------------------------------------------------------------------- /tests/integration/data/pfr_test_constructor_kwargs.json: -------------------------------------------------------------------------------- 1 | {"inlet": 2 | {"mole_conc": [0.15, 0.15, 0, 0], "temp": 298.15, "tau": 1800, "name_solv": "solv"}, 3 | "phase": 4 | {"mole_conc": [0.0, 0.0, 0, 0], "temp": 298.15, "vol": 0.002, "name_solv": "solv"}, 5 | "kinetics": 6 | {"stoich_matrix": [-1, -1, 1], "k_params": 40, "ea_params": 2e3, "partic_species": ["A", "B", "C"]}, 7 | "utility": 8 | {"temp_in": 300, "mass_flow": 0.1}, 9 | "reactor": 10 | {"num_discr": 50, "diam_in": 0.0254, "isothermal": false}, 11 | "solve_unit": 12 | {"verbose": false} 13 | } -------------------------------------------------------------------------------- /doc/online_docs/publications.rst: -------------------------------------------------------------------------------- 1 | Publications 2 | ========================= 3 | 4 | Main PharmaPy paper 5 | 6 | .. bibliography:: 7 | :list: enumerated 8 | 9 | Casas-Orozco2020 10 | 11 | Application papers 12 | 13 | .. bibliography:: 14 | :list: enumerated 15 | :style: unsrt 16 | 17 | Laky2022 18 | Casas-Orozco2023 19 | Casas-Orozco2023a 20 | 21 | Conference papers 22 | 23 | .. bibliography:: 24 | :list: enumerated 25 | 26 | Casas-Orozco2021 27 | Casas-Orozco2022 28 | Laky2022a 29 | 30 | Book chapters 31 | 32 | .. bibliography:: 33 | :list: enumerated 34 | 35 | Laky2022b 36 | -------------------------------------------------------------------------------- /doc/online_docs/docstrings/phases.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | Phases classes 3 | ==================== 4 | 5 | Documentation for the class objects creation are presented in this section [Highlighted in blue]. 6 | Documentation for methods called internally and not requiring user definition are ignored. 7 | 8 | Single Phases 9 | =============== 10 | 11 | .. automodule:: PharmaPy.Phases 12 | :members: 13 | :undoc-members: 14 | :show-inheritance: 15 | 16 | Mixed Phases 17 | =============== 18 | 19 | .. automodule:: PharmaPy.MixedPhases 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | 24 | -------------------------------------------------------------------------------- /InstallOnWindows.bat: -------------------------------------------------------------------------------- 1 | @echo OFF 2 | echo -------------------------------------------------- 3 | echo Welcome to the PharmaPy installation wizard. 4 | echo -------------------------------------------------- 5 | set /p env_name=Enter environment name: 6 | echo ------------------------------ 7 | echo Creating pandas environment... 8 | echo ------------------------------ 9 | call conda create -n %env_name% python=3.9 --file requirements.txt -c conda-forge 10 | call conda activate %env_name% 11 | echo ---------------------- 12 | echo Installing PharmaPy... 13 | echo ---------------------- 14 | call python setup.py develop 15 | echo Done! 16 | -------------------------------------------------------------------------------- /workshop_data/Case_study_1/ziegler_data_773K.csv: -------------------------------------------------------------------------------- 1 | 0.000000000000000000e+00,6.500000000000000222e-02,0.000000000000000000e+00 2 | 1.000000000000000000e+00,2.439999999999999947e-01,2.300000000000000100e-01 3 | 1.500000000000000000e+00,2.660000000000000142e-01,3.240000000000000102e-01 4 | 2.000000000000000000e+00,2.590000000000000080e-01,3.739999999999999991e-01 5 | 2.500000000000000000e+00,1.730000000000000149e-01,4.529999999999999583e-01 6 | 3.000000000000000000e+00,2.160000000000000253e-01,4.529999999999999583e-01 7 | 3.500000000000000000e+00,1.399999999999999856e-02,5.749999999999999556e-01 8 | 7.000000000000000000e+00,0.000000000000000000e+00,6.039999999999999813e-01 9 | -------------------------------------------------------------------------------- /workshop_data/Case_study_1/ziegler_conc_init.json: -------------------------------------------------------------------------------- 1 | { 2 | "673K": [ 3 | 1.0, 4 | 0.0, 5 | 0.0, 6 | 0.0, 7 | 0.0 8 | ], 9 | "698K": [ 10 | 0.935, 11 | 0.065, 12 | 0.0, 13 | 0.0, 14 | 0.0 15 | ], 16 | "723K": [ 17 | 0.914, 18 | 0.086, 19 | 0.0, 20 | 0.0, 21 | 0.0 22 | ], 23 | "748K": [ 24 | 0.993, 25 | 0.006999999999999999, 26 | 0.0, 27 | 0.0, 28 | 0.0 29 | ], 30 | "773K": [ 31 | 0.935, 32 | 0.065, 33 | 0.0, 34 | 0.0, 35 | 0.0 36 | ] 37 | } -------------------------------------------------------------------------------- /data/evaporator/props_nitrogen.json: -------------------------------------------------------------------------------- 1 | { 2 | "nitrogen": 3 | {"cas": "7727-37-9", 4 | "formula": "N2", 5 | "mw": 28.0134, 6 | "t_crit": 126.14, 7 | "p_crit": 33978000, 8 | "henry_constant": 7e9, 9 | "cp_liq": [2.62519203941781, 2.5182418381688, -3.91727903425312E-02, 2.03011574069022E-04], 10 | "cp_vapor": [28.7167713794797, 7.34582726095694E-03, -4.54759070492041E-05, 1.16406082454094E-07, -1.22458456787057E-10, 5.90449193986651E-14, -1.08748042093876E-17], 11 | "cp_solid": [1600], 12 | "rho_liq": 887.6, 13 | "rho_solid": 887.6, 14 | "p_vap": [8.7419, 264.651, -6.788], 15 | "delta_hvap": 6100, 16 | "tref_hvap": 78 17 | } 18 | } -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /InstallOnMac.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo -------------------------------------------------- 3 | echo Welcome to the PharmaPy installation wizard. 4 | echo -------------------------------------------------- 5 | echo Enter environment name: 6 | read env_name 7 | echo ------------------------------ 8 | echo Creating conda environment... 9 | echo ------------------------------ 10 | conda create -n $env_name python=3.9 --file requirements.txt -c conda-forge 11 | echo ------------------------------ 12 | echo Activating conda environment... 13 | echo ------------------------------ 14 | conda init 15 | conda activate $env_name 16 | echo ---------------------- 17 | echo Installing PharmaPy... 18 | echo ---------------------- 19 | python setup.py develop 20 | -------------------------------------------------------------------------------- /doc/online_docs/_templates/footer.html: -------------------------------------------------------------------------------- 1 | 23 | 24 | -------------------------------------------------------------------------------- /doc/online_docs/docstrings/unit_operations.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | Unit operations 3 | ==================== 4 | 5 | Documentation for the class objects creation are presented in this section [Highlighted in blue]. 6 | Documentation for methods called internally and not requiring user definition are ignored. 7 | 8 | Reactors 9 | =============== 10 | 11 | .. automodule:: PharmaPy.Reactors 12 | :members: 13 | :undoc-members: 14 | :show-inheritance: 15 | 16 | Crystallizers 17 | ============== 18 | 19 | .. automodule:: PharmaPy.Crystallizers 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | 24 | Evaporators 25 | ============== 26 | 27 | .. automodule:: PharmaPy.Evaporators 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.10" 13 | # You can also specify other tool versions: 14 | # nodejs: "19" 15 | # rust: "1.64" 16 | # golang: "1.19" 17 | 18 | python: 19 | install: 20 | - requirements: requirements.txt 21 | system_packages: true 22 | 23 | # Build documentation in the docs/ directory with Sphinx 24 | sphinx: 25 | configuration: doc/online_docs/conf.py 26 | 27 | # If using Sphinx, optionally build your docs in additional formats such as PDF 28 | # formats: 29 | # - pdf 30 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /PharmaPy/general_interpolation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Tue Jun 1 11:37:19 2021 4 | 5 | @author: huri 6 | """ 7 | 8 | import numpy as np 9 | from scipy.interpolate import CubicSpline 10 | 11 | 12 | def define_initial_state(state, z_after, z_before=None, indexed_state=False): 13 | 14 | if indexed_state: # e.g. concentration 15 | if state.ndim == 1: 16 | num_z = len(z_after) 17 | state_interp = np.tile(state, (num_z, 1)) 18 | else: 19 | interp_obj = CubicSpline(z_before, state) 20 | state_interp = interp_obj(z_after) 21 | 22 | else: # e.g. saturation 23 | if isinstance(state, float): 24 | state_interp = state * np.ones_like(z_after) 25 | else: 26 | interp_obj = CubicSpline(z_before, state) 27 | state_interp = interp_obj(z_after) 28 | 29 | return state_interp -------------------------------------------------------------------------------- /workshop_data/Case_study_1/ziegler_data_698K.csv: -------------------------------------------------------------------------------- 1 | 0.000000000000000000e+00,6.500000000000000222e-02,0.000000000000000000e+00 2 | 2.000000000000000000e+00,1.440000000000000169e-01,1.399999999999999856e-02 3 | 5.000000000000000000e+00,1.799999999999999933e-01,1.080000000000000127e-01 4 | 7.500000000000000000e+00,1.650000000000000078e-01,1.440000000000000169e-01 5 | 1.000000000000000000e+01,2.949999999999999845e-01,2.160000000000000253e-01 6 | 1.250000000000000000e+01,2.369999999999999885e-01,3.019999999999999907e-01 7 | 1.500000000000000000e+01,3.670000000000000484e-01,3.310000000000000164e-01 8 | 2.000000000000000000e+01,2.730000000000000204e-01,4.029999999999999694e-01 9 | 2.500000000000000000e+01,1.650000000000000078e-01,4.749999999999999778e-01 10 | 3.500000000000000000e+01,7.200000000000000844e-02,5.539999999999999369e-01 11 | 4.500000000000000000e+01,3.600000000000000422e-02,5.679999999999999494e-01 12 | 5.500000000000000000e+01,2.200000000000000219e-02,5.969999999999999751e-01 13 | -------------------------------------------------------------------------------- /workshop_data/Case_study_1/ziegler_data_723K.csv: -------------------------------------------------------------------------------- 1 | 0.000000000000000000e+00,8.599999999999999312e-02,0.000000000000000000e+00 2 | 2.500000000000000000e+00,1.580000000000000016e-01,2.899999999999999800e-02 3 | 3.000000000000000000e+00,2.590000000000000080e-01,1.650000000000000078e-01 4 | 4.000000000000000000e+00,2.520000000000000018e-01,2.439999999999999947e-01 5 | 5.000000000000000000e+00,2.660000000000000142e-01,2.949999999999999845e-01 6 | 6.000000000000000000e+00,3.379999999999999671e-01,3.520000000000000351e-01 7 | 7.500000000000000000e+00,2.590000000000000080e-01,3.950000000000000178e-01 8 | 1.000000000000000000e+01,2.010000000000000120e-01,4.529999999999999583e-01 9 | 1.250000000000000000e+01,1.290000000000000036e-01,4.309999999999999942e-01 10 | 1.250000000000000000e+01,9.300000000000001321e-02,5.460000000000000409e-01 11 | 1.500000000000000000e+01,3.600000000000000422e-02,5.969999999999999751e-01 12 | 1.500000000000000000e+01,2.200000000000000219e-02,5.390000000000000346e-01 13 | -------------------------------------------------------------------------------- /workshop_data/Case_study_1/ziegler_data_748K.csv: -------------------------------------------------------------------------------- 1 | 0.000000000000000000e+00,6.999999999999999278e-03,0.000000000000000000e+00 2 | 1.500000000000000000e+00,1.730000000000000149e-01,2.899999999999999800e-02 3 | 2.000000000000000000e+00,2.300000000000000100e-01,1.730000000000000149e-01 4 | 2.500000000000000000e+00,2.439999999999999947e-01,2.089999999999999913e-01 5 | 3.000000000000000000e+00,2.300000000000000100e-01,2.590000000000000080e-01 6 | 3.500000000000000000e+00,3.310000000000000164e-01,2.949999999999999845e-01 7 | 4.000000000000000000e+00,3.160000000000000031e-01,3.379999999999999671e-01 8 | 5.000000000000000000e+00,2.089999999999999913e-01,4.529999999999999583e-01 9 | 6.000000000000000000e+00,1.009999999999999926e-01,5.320000000000000284e-01 10 | 7.000000000000000000e+00,4.299999999999999656e-02,5.820000000000000728e-01 11 | 9.500000000000000000e+00,6.999999999999999278e-03,5.749999999999999556e-01 12 | 1.200000000000000000e+01,6.999999999999999278e-03,6.109999999999999876e-01 13 | -------------------------------------------------------------------------------- /workshop_data/Case_study_2/compounds_mom.json: -------------------------------------------------------------------------------- 1 | { 2 | "solute": 3 | {"cas": "109-99-9", 4 | "formula": "C4H8O", 5 | "mw": 72.1057, 6 | "t_crit": 540.2, 7 | "p_crit": 5190, 8 | "cp_liq": [63.393, 0.40257, -0.0012686, 0.0000018275, 0], 9 | "cp_solid": [2000], 10 | "kv_solid": 0.5236, 11 | "rho_solid": 1600, 12 | "rho_liq": 1000, 13 | "p_vap": [4.12118, 1202.942, -46.818], 14 | "mol_vol": 0.0811, 15 | "visc_liq": 1, 16 | "surface_tension": 1e-3, 17 | "normal_state": "liquid" 18 | }, 19 | "solvent": 20 | {"cas": "108-91-8", 21 | "formula": "C6H13N", 22 | "mw": 99.1741, 23 | "t_crit": 614.6, 24 | "cp_liq": [122.21339975625, 0.488130740171043, -1.26377033159788E-03, 1.71423738891454E-06, 0], 25 | "p_vap": [4.06885, 1380.225, -67.285], 26 | "mol_vol": 0.11465, 27 | "rho_liq": 1000, 28 | "rho_solid": 1600, 29 | "cp_solid": [2000], 30 | "visc_liq": 1, 31 | "surface_tension": 1e-3, 32 | "normal_state": "liquid" 33 | } 34 | } -------------------------------------------------------------------------------- /workshop_data/Case_study_1/ziegler_data_673K.csv: -------------------------------------------------------------------------------- 1 | 0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00 2 | 2.000000000000000000e+00,2.200000000000000219e-02,0.000000000000000000e+00 3 | 5.000000000000000000e+00,1.150000000000000050e-01,6.999999999999999278e-03 4 | 1.000000000000000000e+01,1.369999999999999829e-01,7.200000000000000844e-02 5 | 1.500000000000000000e+01,1.509999999999999953e-01,1.150000000000000050e-01 6 | 2.000000000000000000e+01,1.730000000000000149e-01,1.580000000000000016e-01 7 | 2.500000000000000000e+01,1.730000000000000149e-01,2.089999999999999913e-01 8 | 3.500000000000000000e+01,2.010000000000000120e-01,2.660000000000000142e-01 9 | 4.500000000000000000e+01,2.010000000000000120e-01,3.240000000000000102e-01 10 | 5.500000000000000000e+01,2.230000000000000038e-01,3.810000000000000053e-01 11 | 7.500000000000000000e+01,2.089999999999999913e-01,4.320000000000000506e-01 12 | 9.500000000000000000e+01,1.150000000000000050e-01,4.959999999999999964e-01 13 | 1.150000000000000000e+02,6.500000000000000222e-02,5.180000000000000160e-01 14 | 1.450000000000000000e+02,3.600000000000000422e-02,5.470000000000000417e-01 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Sphinx documentation 2 | docs/_build/ 3 | 4 | # PyBuilder 5 | target/ 6 | 7 | # Jupyter Notebook 8 | .ipynb_checkpoints 9 | 10 | # pyenv 11 | *.python-version 12 | *.pyc 13 | 14 | # celery beat schedule file 15 | celerybeat-schedule 16 | 17 | # dotenv 18 | .env 19 | 20 | # virtualenv 21 | .venv/ 22 | venv/ 23 | ENV/ 24 | 25 | # Spyder project settings 26 | .spyderproject 27 | 28 | # Rope project settings 29 | .ropeproject 30 | 31 | # Vim files 32 | *.swp 33 | *.swa 34 | 35 | # Latex auxiliary files 36 | *.aux 37 | *._aux 38 | *.bbl 39 | *.blg 40 | *.fdb_latexmk 41 | *.fls 42 | *.ilg 43 | *.log 44 | *._log 45 | *.nlo 46 | *.nls 47 | *.synctex.gz 48 | *.project.vim 49 | *.run.xml 50 | # *.pdf 51 | *.bib 52 | *.nlg 53 | .~lock* 54 | *.upa 55 | *.upb 56 | *.out 57 | *.backup 58 | *.spl 59 | *.synctex 60 | *.bcf 61 | *.nav 62 | *.snm 63 | *.toc 64 | *.vrb 65 | 66 | # Some output files 67 | *.pdf 68 | *.png 69 | *.wav 70 | *.jpg 71 | *.mp4 72 | 73 | # MS Office files 74 | #*.doc 75 | #*.docx 76 | *.ppt 77 | *.pptx 78 | 79 | # Compressed files 80 | *.zip 81 | *.tar.gz 82 | *.rar 83 | 84 | source/crystallization/ 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PharmaPy 2 | 3 | PharmaPy_logo 4 | 5 | 6 | ![GitHub all releases](https://img.shields.io/github/downloads/CryPTSys/PharmaPy/total) 7 | [![DOI](https://img.shields.io/badge/DOI-10.1016%2Fj.compchemeng.2021.107408-blue)](https://www.sciencedirect.com/science/article/abs/pii/S0098135421001861) 8 | 9 | 10 | PharmaPy is a pythonic library for the analysis of pharmaceutical manufacturing systems. 11 | 12 | It allows to simulate the dynamics of standalone, drug substance unit operations in a variety of operating modes (batch, continuous, semibatch). Also, PharmaPy facilitates setting up and simulating pharmaceutical **flowsheets**, i.e., interconnected unit operations running in one or more operation modes, offering flexibility to simulate end-to-end batch, end-to-end continuous, and hybrid operation schemes (combination of batch and/or continuous and semicontinuous unit operations). 13 | 14 |
15 | 16 | ## Getting started 17 | To install PharmaPy, download and unzip the code from the release section, and then follow the instructions on the `install_instructions.txt` file. 18 | 19 | Read our [documentation](https://pharmapy.readthedocs.io/en/latest/) or chat with the [PharmaPy Simulation Assistant](https://chatgpt.com/g/g-679bb3b5c5188191b26680b147a4f4a2-pharmapy-simulation-assistant) for more information on how to install and how to use PharmaPy. 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /PharmaPy/ProcessControl.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Sep 16 15:06:16 2021 4 | 5 | @author: dcasasor 6 | """ 7 | 8 | 9 | def analyze_controls(di): 10 | 11 | controls = {} 12 | 13 | for key, val in di.items(): 14 | if isinstance(val, dict) and key != 'kwargs': 15 | if 'fun' not in val: 16 | raise KeyError("'%s' dictionary must have a 'fun' field") 17 | elif not callable(val['fun']): 18 | raise TypeError( 19 | "Object passed to the 'fun' field must be a " 20 | "callable with signature fun(time, *args, **kwargs)") 21 | 22 | out = val 23 | else: 24 | if not callable(val): 25 | raise TypeError( 26 | "Object passed to the 'fun' field must be a " 27 | "callable with signature fun(time, *args, **kwargs)") 28 | 29 | out = {'fun': val} 30 | 31 | if 'args' not in out: 32 | out['args'] = () 33 | 34 | if 'kwargs' not in out: 35 | out['kwargs'] = {} 36 | 37 | controls[key] = out 38 | 39 | return controls 40 | 41 | 42 | class DynamicInput: 43 | def __init__(self): 44 | self.controls = {} 45 | self.args_control = {} 46 | 47 | # Attributes assigned from UO instance 48 | self.parent_instance = None 49 | 50 | def add_variable(self, variable_name, function, args_control=None): 51 | self.controls[variable_name] = function 52 | if args_control is None: 53 | args_control = () 54 | 55 | self.args_control[variable_name] = args_control 56 | 57 | def evaluate_inputs(self, time): 58 | controls_out = {} 59 | 60 | for key, fun in self.controls.items(): 61 | args = self.args_control[key] 62 | controls_out[key] = fun(time, *args) 63 | 64 | return controls_out 65 | -------------------------------------------------------------------------------- /workshop_data/Case_study_1/compounds_ziegler.json: -------------------------------------------------------------------------------- 1 | { 2 | "A": 3 | {"mw": 100, 4 | "t_crit": 632, 5 | "cp_liq": [135.215465256606, 0.401510545972073, -1.03569456312656E-03, 1.45506511371582E-06, 0], 6 | "cp_solid": [1600], 7 | "rho_liq": 857, 8 | "rho_solid": 1080.4, 9 | "p_vap": [3.92161, 1411.869, -68.817], 10 | "mol_vol": 0.15897, 11 | "normal_state": "liquid", 12 | "heat_form_std": -256400 13 | }, 14 | "B": 15 | {"mw": 100, 16 | "t_crit": 614.6, 17 | "cp_liq": [173.533941416073, 0.459105851863442, -1.13661931943915E-03, 1.60123505430493E-06, 0], 18 | "cp_solid": [1600], 19 | "rho_liq": 842, 20 | "rho_solid": 1065.4, 21 | "p_vap": [4.06885, 1380.225, -67.285], 22 | "mol_vol": 0.16081, 23 | "normal_state": "liquid", 24 | "heat_form_std": -204880 25 | }, 26 | "C": 27 | {"mw": 100, 28 | "mol_vol": 0, 29 | "cp_solid": [1600], 30 | "cp_liq": [173.533941416073, 0.459105851863442, -1.13661931943915E-03, 1.60123505430493E-06, 0], 31 | "rho_liq": 889.1, 32 | "rho_solid": 1070.1, 33 | "normal_state": "solid", 34 | "heat_form_std": -420000 35 | }, 36 | "D": 37 | {"mw": 100, 38 | "t_crit": 615, 39 | "p_crit": 3550, 40 | "cp_liq": [173.533941416073, 0.459105851863442, -1.13661931943915E-03, 1.60123505430493E-06, 0], 41 | "cp_solid": [1600], 42 | "rho_liq": 867.1, 43 | "rho_solid": 1020.5, 44 | "mol_vol": 0.0811, 45 | "normal_state": "liquid", 46 | "heat_form_std": -415000 47 | }, 48 | "solvent": 49 | {"mw": 72.107, 50 | "t_crit": 540.2, 51 | "p_crit": 3550, 52 | "cp_liq": [63.393, 0.40257, -0.0012686, 0.0000018275, 0], 53 | "cp_solid": [1600], 54 | "rho_liq": 887.6, 55 | "rho_solid": 887.6, 56 | "visc_liq": [-2.786, 476.81, 0.0049173, -0.0000068815], 57 | "p_vap": [9.23027, 1256.68, -40.529], 58 | "delta_hvap": 32300, 59 | "surf_tension": 0.02640, 60 | "tref_hvap": 305 61 | } 62 | } -------------------------------------------------------------------------------- /tests/integration/data/pfr_test_pure_comp.json: -------------------------------------------------------------------------------- 1 | { 2 | "A": 3 | {"cas": "1943-83-5", 4 | "formula": "C3H4ClNO", 5 | "mw": 105.52, 6 | "t_crit": 540.2, 7 | "p_crit": 5190, 8 | "cp_liq": [63.393, 0.40257, -0.0012686, 0.0000018275, 0], 9 | "cp_solid": [1600], 10 | "rho_liq": 1230, 11 | "rho_solid": 1230, 12 | "visc_liq": [-2.786, 476.81, 0.0049173, -0.0000068815], 13 | "p_vap": [9.23027, 1256.68, -40.529], 14 | "mol_vol": 0.0811, 15 | "delta_hvap": 40600, 16 | "tref_hvap": 378, 17 | "surf_tension": 0.02014, 18 | "source": "Knovel-THF" 19 | }, 20 | "B": 21 | {"cas": "108-91-8", 22 | "formula": "C6H13N", 23 | "mw": 99.1741, 24 | "t_crit": 614.6, 25 | "cp_liq": [122.21339975625, 0.488130740171043, -1.26377033159788E-03, 1.71423738891454E-06, 0], 26 | "cp_solid": [1600], 27 | "rho_liq": 864.7, 28 | "rho_solid": 864.7, 29 | "visc_liq": [-4.4924, 1027.2, 0.0061025, -0.0000057699], 30 | "p_vap": [9.4898, 1647.76, -40.184], 31 | "mol_vol": 0.0811, 32 | "delta_hvap": 40600, 33 | "tref_hvap": 378, 34 | "surf_tension": 0.02014, 35 | "source": "Knovel" 36 | }, 37 | "C": 38 | {"formula": "C9H17ClN2O", 39 | "mw": 204.6941, 40 | "t_crit": 658.2, 41 | "p_crit": 18.6e5, 42 | "mol_vol": 0, 43 | "cp_liq": [63.393, 0.40257, -0.0012686, 0.0000018275, 0], 44 | "cp_solid": [1600], 45 | "rho_liq": 889.1, 46 | "rho_solid": 889.1, 47 | "visc_liq": [-2.786, 476.81, 0.0049173, -0.0000068815], 48 | "p_vap": [9.35373, 1807.47, -73.769], 49 | "delta_hvap": 61400, 50 | "tref_hvap": 299, 51 | "surf_tension": 0.02014, 52 | "source": "Knovel-Dodecane-THF" 53 | }, 54 | "solv": 55 | {"cas": "109-99-9", 56 | "formula": "C4H8O", 57 | "mw": 72.107, 58 | "t_crit": 540.2, 59 | "p_crit": 3550, 60 | "cp_liq": [63.393, 0.40257, -0.0012686, 0.0000018275, 0], 61 | "cp_solid": [1600], 62 | "rho_liq": 887.6, 63 | "rho_solid": 887.6, 64 | "visc_liq": [-2.786, 476.81, 0.0049173, -0.0000068815], 65 | "p_vap": [9.23027, 1256.68, -40.529], 66 | "delta_hvap": 32300, 67 | "surf_tension": 0.02640, 68 | "tref_hvap": 305 69 | } 70 | } -------------------------------------------------------------------------------- /PharmaPy/Utilities.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Sun Aug 9 13:52:55 2020 5 | 6 | @author: dcasasor 7 | """ 8 | 9 | from PharmaPy.Connections import get_inputs_new 10 | 11 | 12 | class CoolingWater: 13 | def __init__(self, vol_flow=None, mass_flow=None, temp_in=298.15, 14 | h_conv=1000): 15 | 16 | self.rho = 1000 # kg/m**3 17 | self.cp = 4180 # J/kg/K 18 | self.h_conv = h_conv 19 | 20 | if vol_flow is None and mass_flow is None: 21 | raise RuntimeError("Both 'vol_flow' and 'mass_flow' are None. " 22 | "Specify one of them.") 23 | 24 | self.updateObject(vol_flow, mass_flow, temp_in) 25 | 26 | self.controllable = ('temp_in', 'vol_flow', 'mass_flow') 27 | 28 | # Outputs 29 | self.temp_out = None 30 | self.y_upstream = None 31 | 32 | self._DynamicInlet = None 33 | 34 | @property 35 | def DynamicInlet(self): 36 | return self._DynamicInlet 37 | 38 | @DynamicInlet.setter 39 | def DynamicInlet(self, dynamic_object): 40 | dynamic_object.controllable = self.controllable 41 | dynamic_object.parent_instance = self 42 | 43 | self._DynamicInlet = dynamic_object 44 | 45 | def updateObject(self, vol_flow=None, mass_flow=None, temp_in=None): 46 | if vol_flow is not None: 47 | self.vol_flow = vol_flow 48 | self.mass_flow = vol_flow * self.rho 49 | elif mass_flow is not None: 50 | self.mass_flow = mass_flow 51 | self.vol_flow = mass_flow / self.rho 52 | 53 | if temp_in is not None: 54 | self.temp_in = temp_in 55 | 56 | def evaluate_inputs(self, time): 57 | if self.DynamicInlet is None: 58 | inputs = {} 59 | for attr in self.controllable: 60 | inputs[attr] = getattr(self, attr) 61 | 62 | else: 63 | inputs = self.DynamicInlet.evaluate_inputs(time) 64 | 65 | return inputs 66 | 67 | def get_inputs(self, time): 68 | di = {'Inlet': {'vol_flow': 1, 'temp_in': 1, 'mass_flow': 1}} 69 | inputs = get_inputs_new(time, self, di)['Inlet'] 70 | 71 | return inputs 72 | -------------------------------------------------------------------------------- /workshop_data/Case_study_1/physical_property_descriptors.json: -------------------------------------------------------------------------------- 1 | { 2 | "Formula": {"name": "Formula"}, 3 | "CAS": {"name": "CAS Registry Number"}, 4 | "mw": {"name": "Molecular weight"}, 5 | "p_vap": {"name": "Antoine Equation Parameters", "cols": ["A", "B", "C"], "ref": "Phase change data", "temp_ref": 343.15, "metadata": {"units": "T in K, P in Pa", "model": "log10(P) = A - B / (T + C)"}}, 6 | "temp_crit": {"name": "One dimensional data", "index": "Tc", "ref": "Phase change data", "cols": "Value", "metadata": {"units": "K"}}, 7 | "p_crit": {"name": "One dimensional data", "index": "Pc", "ref": "Phase change data", "cols": "Value", "metadata": {"units": "Pa"}}, 8 | "rho_liq": {"name": "dens_liq", "metadata": {"units": "kg/m^3"}}, 9 | "rho_vap": {"name": "dens_vap", "metadata": {"units": "kg/m^3"}}, 10 | "rho_solid": {"name": "dens_solid", "metadata": {"units": "kg/m^3"}}, 11 | "cp_liq": {"name": "Constant pressure heat capacity of liquid", "ref": "Condensed phase thermochemistry data", "cols": ["Cp,liquid (J/mol*K)"], "temp_ref": 298.15, "metadata": {"units": "J/mol/K", "model": "A + B*T + C*T^2 + ...", "comments": "Pass a list [A, B, C, ..] in the \"value\" field of this dictionary"}}, 12 | "cp_vap": {"name": "Constant pressure heat capacity of gas", "ref": "Gas phase thermochemistry data", "cols": ["Cp,gas (J/mol*K)"], "temp_ref": 343.15, "metadata": {"units": "J/mol/K", "model": "A + B*T + C*T^2 + ...", "comments": "Pass a list [A, B, C, ..] in the \"value\" field of this dictionary"}}, 13 | "cp_solid": {"name": "cp_solid", "metadata": {"units": "J/mol/K", "model": "A + B*T + C*T^2 + ...", "comments": "Pass a list [A, B, C, ..] in the \"value\" field of this dictionary"}}, 14 | "delta_hvap": {"name": "Enthalpy of vaporization", "ref": "Phase change data", "cols": ["vapH (kJ/mol)"], "temp_ref": 343.15, "metadata": {"units": "J/mol", "model": "deltaH_vap(T) = [(T_c - T)/(T_c - T_ref)]**0.38 * deltaH_vap(T_ref)"}}, 15 | "visc_liq": {"name": "visc_liq", "metadata": {"units": "cP", "model": "ln10(visc) = A + B/T + C*T**2", "comments": "Pass a list [A, B, C] for Andrade Equation in the \"value\" field of this dictionary. For constant viscosity, pass log10(visc) as A in a list.", "bibliography": "Poling et al. The properties of Gases and Liquids. 5th Edition"}}, 16 | "surf_tension": {"name": "surf_tension", "metadata": {"units": "N/m"}} 17 | } -------------------------------------------------------------------------------- /PharmaPy/CheckModule.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Tue Mar 7 14:49:31 2023 4 | 5 | @author: dcasasor 6 | """ 7 | 8 | import json 9 | import pathlib 10 | 11 | from PharmaPy.Errors import PharmaPySpecificationError 12 | import warnings 13 | 14 | 15 | root = str(pathlib.Path(__file__).parents[1]) 16 | 17 | 18 | def check_modeling_objects(uo, instance_name=None): 19 | with open(root + '/data/minimum_modeling_objects.json') as fi: 20 | checks = json.load(fi) 21 | 22 | class_name = uo.__class__.__name__ 23 | 24 | if instance_name is None: 25 | instance_name = '' 26 | instance_descr = "a " + class_name + ' instance' 27 | else: 28 | instance_descr = "the '%s' %s instance" % (instance_name, class_name) 29 | 30 | if class_name in checks['special']: 31 | modeling_objs = checks['special'][class_name] 32 | else: 33 | modeling_objs = checks[uo.oper_mode] 34 | 35 | module_name = uo.__module__.split('.')[-1] 36 | 37 | cond_kin = (module_name in checks['has_kinetics']['modules'] or 38 | class_name in checks['has_kinetics']['classes']) and \ 39 | 'Kinetics' not in modeling_objs 40 | 41 | cond_utility = (module_name in checks['has_utility']['modules'] or 42 | class_name in checks['has_utility']['classes']) and \ 43 | 'Utility' not in modeling_objs 44 | 45 | if cond_kin: 46 | modeling_objs.append('Kinetics') 47 | 48 | if cond_utility: 49 | modeling_objs.append('Utility') 50 | 51 | missing_obj = [] 52 | for obj in modeling_objs: 53 | if not hasattr(uo, obj) or getattr(uo, obj) is None: 54 | missing_obj.append(obj) 55 | 56 | if len(missing_obj) > 0: 57 | intro = "The following PharmaPy modeling objects were " \ 58 | "not detected in %s:\n" % instance_descr 59 | 60 | obj_enum = '\t' + ',\n\t'.join(missing_obj) + '.\n\n' 61 | 62 | recommend = "Please create the missing modeling objects listed above" \ 63 | " and then aggregate them one by one to the corresponding unit " \ 64 | "operation instance, e.g. %s.%s = <%sClass>(...)" % ( 65 | instance_name, missing_obj[0], missing_obj[0]) 66 | 67 | message = intro + obj_enum + recommend 68 | 69 | # raise PharmaPySpecificationError(message) 70 | warnings.warn(message) 71 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | Pythonic framework for the digital analysis of pharmaceutical processes: PharmaPy 5 | Copyright (c) 2021, by the software owners: Purdue University. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | 1. Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | 2. Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation and/or 15 | other materials provided with the distribution. 16 | 17 | 3. Neither the name PharmaPy, Purdue University, nor the names of its contributors 18 | may be used to endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 23 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 26 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 27 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 29 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | 33 | You are under no obligation whatsoever to provide any bug fixes, patches, or 34 | upgrades to the features, functionality or performance of the source code 35 | ("Enhancements") to anyone; however, if you choose to make your Enhancements 36 | available either publicly, or directly to Purdue University, without imposing 37 | a separate written license agreement for such Enhancements, then you hereby 38 | grant Purdue Univeristy the following license: a non-exclusive, royalty-free 39 | perpetual license to install, use, modify, prepare derivative works, incorporate 40 | into other computer software, distribute, and sublicense such enhancements or 41 | derivative works thereof, in binary and source code form. 42 | -------------------------------------------------------------------------------- /tests/Flowsheet/data/compound_database.json: -------------------------------------------------------------------------------- 1 | { 2 | "A": 3 | {"mw": 100, 4 | "t_crit": 540.2, 5 | "p_crit": 5190, 6 | "cp_liq": [63.393, 0.40257, -0.0012686, 0.0000018275, 0], 7 | "cp_solid": [1600], 8 | "rho_liq": 1230, 9 | "rho_solid": 1230, 10 | "visc_liq": [-2.786, 476.81, 0.0049173, -0.0000068815], 11 | "p_vap": [9.23027, 1256.68, -40.529], 12 | "mol_vol": 0.0811, 13 | "delta_hvap": 40600, 14 | "tref_hvap": 378, 15 | "surf_tension": 0.02014, 16 | "source": "Knovel-THF" 17 | }, 18 | "B": 19 | {"mw": 50, 20 | "t_crit": 614.6, 21 | "cp_liq": [122.21339975625, 0.488130740171043, -1.26377033159788E-03, 1.71423738891454E-06, 0], 22 | "cp_solid": [1600], 23 | "rho_liq": 864.7, 24 | "rho_solid": 864.7, 25 | "visc_liq": [-4.4924, 1027.2, 0.0061025, -0.0000057699], 26 | "p_vap": [9.4898, 1647.76, -40.184], 27 | "mol_vol": 0.0811, 28 | "delta_hvap": 40600, 29 | "tref_hvap": 378, 30 | "surf_tension": 0.02014, 31 | "source": "Knovel" 32 | }, 33 | "C": 34 | {"mw": 150, 35 | "t_crit": 540.2, 36 | "p_crit": 5190, 37 | "mol_vol": 0.0811, 38 | "cp_liq": [63.393, 0.40257, -0.0012686, 0.0000018275, 0], 39 | "cp_solid": [1600], 40 | "rho_liq": 1200, 41 | "rho_solid": 1200, 42 | "visc_liq": [-0.011485634166334, 0, 0, 0], 43 | "p_vap": [9.23027, 1256.68, -40.529], 44 | "delta_hvap": 61400, 45 | "tref_hvap": 299, 46 | "surf_tension": 0.02014, 47 | "source": "Knovel-Dodecane-THF" 48 | }, 49 | "D": 50 | {"mw": 250, 51 | "t_crit": 615, 52 | "p_crit": 3550, 53 | "cp_liq": [63.393, 0.40257, -0.0012686, 0.0000018275, 0], 54 | "cp_solid": [1600], 55 | "rho_liq": 867.1, 56 | "rho_solid": 867.1, 57 | "visc_liq": [-2.786, 476.81, 0.0049173, -0.0000068815], 58 | "mol_vol": 0.0811, 59 | "p_vap": [9.785, 1610, 0], 60 | "delta_hvap": 30800 , 61 | "tref_hvap": 282, 62 | "surf_tension": 0.02014, 63 | "source": "Knovel-Stephenson-THF" 64 | }, 65 | "solvent": 66 | {"mw": 72.107, 67 | "t_crit": 540.2, 68 | "p_crit": 3550, 69 | "cp_liq": [63.393, 0.40257, -0.0012686, 0.0000018275, 0], 70 | "cp_solid": [1600], 71 | "rho_liq": 887.6, 72 | "rho_solid": 887.6, 73 | "visc_liq": [-0.011485634166334, 0, 0, 0], 74 | "p_vap": [9.23027, 1256.68, -40.529], 75 | "delta_hvap": 32300, 76 | "surf_tension": 0.02640, 77 | "tref_hvap": 305 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /workshop_data/Case_study_3/compound_database.json: -------------------------------------------------------------------------------- 1 | { 2 | "A": 3 | {"mw": 100, 4 | "t_crit": 540.2, 5 | "p_crit": 5190, 6 | "cp_liq": [63.393, 0.40257, -0.0012686, 0.0000018275, 0], 7 | "cp_solid": [1600], 8 | "rho_liq": 1230, 9 | "rho_solid": 1230, 10 | "visc_liq": [-2.786, 476.81, 0.0049173, -0.0000068815], 11 | "p_vap": [9.23027, 1256.68, -40.529], 12 | "mol_vol": 0.0811, 13 | "delta_hvap": 40600, 14 | "tref_hvap": 378, 15 | "surf_tension": 0.02014, 16 | "source": "Knovel-THF" 17 | }, 18 | "B": 19 | {"mw": 50, 20 | "t_crit": 614.6, 21 | "cp_liq": [122.21339975625, 0.488130740171043, -1.26377033159788E-03, 1.71423738891454E-06, 0], 22 | "cp_solid": [1600], 23 | "rho_liq": 864.7, 24 | "rho_solid": 864.7, 25 | "visc_liq": [-4.4924, 1027.2, 0.0061025, -0.0000057699], 26 | "p_vap": [9.4898, 1647.76, -40.184], 27 | "mol_vol": 0.0811, 28 | "delta_hvap": 40600, 29 | "tref_hvap": 378, 30 | "surf_tension": 0.02014, 31 | "source": "Knovel" 32 | }, 33 | "C": 34 | {"mw": 150, 35 | "t_crit": 540.2, 36 | "p_crit": 5190, 37 | "mol_vol": 0.0811, 38 | "cp_liq": [63.393, 0.40257, -0.0012686, 0.0000018275, 0], 39 | "cp_solid": [1600], 40 | "rho_liq": 1200, 41 | "rho_solid": 1200, 42 | "visc_liq": [-0.011485634166334, 0, 0, 0], 43 | "p_vap": [9.23027, 1256.68, -40.529], 44 | "delta_hvap": 61400, 45 | "tref_hvap": 299, 46 | "surf_tension": 0.02014, 47 | "source": "Knovel-Dodecane-THF" 48 | }, 49 | "D": 50 | {"mw": 250, 51 | "t_crit": 615, 52 | "p_crit": 3550, 53 | "cp_liq": [63.393, 0.40257, -0.0012686, 0.0000018275, 0], 54 | "cp_solid": [1600], 55 | "rho_liq": 867.1, 56 | "rho_solid": 867.1, 57 | "visc_liq": [-2.786, 476.81, 0.0049173, -0.0000068815], 58 | "mol_vol": 0.0811, 59 | "p_vap": [9.785, 1610, 0], 60 | "delta_hvap": 30800 , 61 | "tref_hvap": 282, 62 | "surf_tension": 0.02014, 63 | "source": "Knovel-Stephenson-THF" 64 | }, 65 | "solvent": 66 | {"mw": 72.107, 67 | "t_crit": 540.2, 68 | "p_crit": 3550, 69 | "cp_liq": [63.393, 0.40257, -0.0012686, 0.0000018275, 0], 70 | "cp_solid": [1600], 71 | "rho_liq": 887.6, 72 | "rho_solid": 887.6, 73 | "visc_liq": [-0.011485634166334, 0, 0, 0], 74 | "p_vap": [9.23027, 1256.68, -40.529], 75 | "delta_hvap": 32300, 76 | "surf_tension": 0.02640, 77 | "tref_hvap": 305 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /doc/online_docs/installation.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Installation 3 | ============ 4 | 5 | .. There are two ways to install PharmaPy. The first way is for casual users, intending to use the software and not edit or add modules to the software package. The second way is for developers or advanced users who intend to create and incorporate their own models into their work. 6 | 7 | Standard Installation 8 | ===================== 9 | 10 | We recommend using python or anaconda virtual environments to control python packages effectively, including PharmaPy and its dependencies. PharmaPy can be installed in two different ways. For those who want to just use the software in the latest stable version, use the following command: 11 | 12 | .. testcode:: 13 | 14 | pip install pharmapy 15 | 16 | This will download and install PharmaPy to the current python environment. To edit and run code, it is recommended to also install an IDE, or use jupyer notebooks. To install jupyter notebooks and its dependencies, run the following command: 17 | 18 | .. testcode:: 19 | 20 | pip install jupyterlab 21 | 22 | .. 23 | Developer Installation 24 | ====================== 25 | 26 | For installation, we recommend the use of conda environments to control packages dependencies and PharmaPy. A lighweight version of conda (`miniconda`_) is probably a good option for new users of the management system. 27 | 28 | For PharmaPy installation, you must use the source code, which is available in our `Github repository`_. Once the source code is downloaded to the desired location, navigate (:code:`cd`) to the directory which contains the setup.py file. Then, follow the instructions on the :code:`installation_guide.txt`, to setup fresh conda environment and install PharmaPy and its dependencies. 29 | 30 | .. 31 | make sure your conda environment is appropriately installed and activated, then input the following commands for PharmaPy installation: 32 | 1. conda install --file requirements.txt -c conda-forge 33 | 2. python setup.py develop 34 | 35 | .. _Github repository: https://github.com/CryPTSys/PharmaPy/tree/develop 36 | .. _miniconda: https://github.com/CryPTSys/PharmaPy/ 37 | 38 | Once the software is installed, install and/or use your preferred IDE or text editor to construct PharmaPy flowsheets. For instance, on an active conda environment, install the `Spyder IDE`_ by doing :code:`conda -c conda-forge install spyder`, which provides a nice development environment very well suited for scientific computing. 39 | 40 | .. _Spyder IDE: https://github.com/spyder-ide/spyder 41 | 42 | Tutorials in the format of Jupyter notebooks are available for users and developers getting started with PharmaPy. Also, on this site, documentation for all unit operations is available. 43 | -------------------------------------------------------------------------------- /workshop_data/Case_study_1/pure_components_ziegler.json: -------------------------------------------------------------------------------- 1 | { 2 | "A": { 3 | "Formula": "C6H14", 4 | "CAS": "110-54-3", 5 | "mw": 86.1754, 6 | "p_vap": [ 7 | 9.00837661241373, 8 | 1171.53, 9 | -48.784 10 | ], 11 | "rho_liq": 655, 12 | "rho_vap": 0.655, 13 | "rho_solid": 655, 14 | "cp_liq": [ 15 | 265.2 16 | ], 17 | "cp_vap": [ 18 | 143.26 19 | ], 20 | "cp_solid": 1600, 21 | "t_crit": 507.6, 22 | "p_crit": 3060015.0 23 | }, 24 | "B": { 25 | "Formula": "C6H14", 26 | "CAS": "110-54-3", 27 | "mw": 86.1754, 28 | "p_vap": [ 29 | 9.00837661241373, 30 | 1171.53, 31 | -48.784 32 | ], 33 | "rho_liq": 655, 34 | "rho_vap": 0.655, 35 | "rho_solid": 655, 36 | "cp_liq": [ 37 | 265.2 38 | ], 39 | "cp_vap": [ 40 | 143.26 41 | ], 42 | "cp_solid": 1600, 43 | "t_crit": 507.6, 44 | "p_crit": 3060015.0 45 | }, 46 | "C": { 47 | "Formula": "C6H14", 48 | "CAS": "110-54-3", 49 | "mw": 86.1754, 50 | "p_vap": [ 51 | 9.00837661241373, 52 | 1171.53, 53 | -48.784 54 | ], 55 | "rho_liq": 655, 56 | "rho_vap": 0.655, 57 | "rho_solid": 655, 58 | "cp_liq": [ 59 | 265.2 60 | ], 61 | "cp_vap": [ 62 | 143.26 63 | ], 64 | "cp_solid": 1600, 65 | "t_crit": 507.6, 66 | "p_crit": 3060015.0 67 | }, 68 | "D": { 69 | "Formula": "C6H14", 70 | "CAS": "110-54-3", 71 | "mw": 86.1754, 72 | "p_vap": [ 73 | 9.00837661241373, 74 | 1171.53, 75 | -48.784 76 | ], 77 | "rho_liq": 655, 78 | "rho_vap": 0.655, 79 | "rho_solid": 655, 80 | "cp_liq": [ 81 | 265.2 82 | ], 83 | "cp_vap": [ 84 | 143.26 85 | ], 86 | "cp_solid": 1600, 87 | "t_crit": 507.6, 88 | "p_crit": 3060015.0 89 | }, 90 | "solvent": { 91 | "Formula": "C4H8O", 92 | "CAS": "109-99-9", 93 | "mw": 72.1057, 94 | "p_vap": [ 95 | 9.12689661241373, 96 | 1202.942, 97 | -46.818 98 | ], 99 | "rho_liq": 887.6, 100 | "rho_vap": 0.8876, 101 | "rho_solid": 887.6, 102 | "cp_liq": [ 103 | 124.1 104 | ], 105 | "cp_vap": [ 106 | 77.18 107 | ], 108 | "cp_solid": 1600, 109 | "t_crit": 541.0, 110 | "p_crit": 5258767.5 111 | } 112 | } -------------------------------------------------------------------------------- /tests/integration/reactor_tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Apr 18 10:57:33 2022 4 | 5 | @author: dcasasor 6 | """ 7 | 8 | import unittest 9 | import json 10 | from glob import glob 11 | from numpy import genfromtxt, savetxt, allclose, vstack 12 | 13 | from PharmaPy.Reactors import PlugFlowReactor 14 | from PharmaPy.Streams import LiquidStream 15 | from PharmaPy.Phases import LiquidPhase 16 | from PharmaPy.Kinetics import RxnKinetics 17 | from PharmaPy.Utilities import CoolingWater 18 | 19 | 20 | class PlugFlowReactorTests(unittest.TestCase): 21 | """ Class containing tests for the PlugFlowReactor class in PharmaPy 22 | """ 23 | 24 | def setUp(self): 25 | # Data 26 | datapath = 'data/pfr_test_pure_comp.json' 27 | 28 | with open('data/pfr_test_constructor_kwargs.json') as f: 29 | data_objects = json.load(f) 30 | 31 | tau = data_objects['inlet'].pop('tau') 32 | data_objects['inlet']['vol_flow'] = data_objects['phase']['vol'] / tau 33 | 34 | data_objects['kinetics']['k_params'] *= 1/60 35 | data_objects['kinetics']['path'] = datapath 36 | 37 | time_integration = genfromtxt('data/pfr_test_expected_times.csv', 38 | delimiter=',') 39 | 40 | data_objects['solve_unit']['time_grid'] = time_integration 41 | 42 | # PharmaPy objects 43 | inlet = LiquidStream(datapath, **data_objects['inlet']) 44 | phase = LiquidPhase(datapath, **data_objects['phase']) 45 | 46 | kinetics = RxnKinetics(**data_objects['kinetics']) 47 | utility = CoolingWater(**data_objects['utility']) 48 | 49 | self.m = reactor = PlugFlowReactor(**data_objects['reactor']) 50 | 51 | reactor.Inlet = inlet 52 | reactor.Phases = phase 53 | reactor.Kinetics = kinetics 54 | reactor.Utility = utility 55 | 56 | reactor.solve_unit(**data_objects['solve_unit']) 57 | 58 | def test_mole_conc(self): 59 | filenames = glob('data/pfr_test_expected_conc*') 60 | filenames.sort(key=lambda f: int(''.join(filter(str.isdigit, f)))) 61 | 62 | flags = [] 63 | molefracs = self.m.result.mole_conc 64 | zipped = list(zip(*[ar.T for ar in molefracs.values()])) 65 | 66 | for ind, name in enumerate(filenames): 67 | expected_conc = genfromtxt(name, delimiter=',') 68 | 69 | # I'm excluding the solvent in this comparison 70 | 71 | abstol = 1e-4 # TODO: is this determined by the tolerances of assimulo? 72 | 73 | per_vol_elem = vstack(zipped[ind]).T[:, :-1] 74 | flag = allclose(expected_conc, per_vol_elem, atol=abstol) 75 | 76 | flags.append(flag) 77 | 78 | # TODO: maybe print largest deviation 79 | 80 | self.assertTrue(all(flags)) 81 | 82 | def test_temperature(self): 83 | pass 84 | 85 | 86 | if __name__ == '__main__': 87 | unittest.main() 88 | -------------------------------------------------------------------------------- /workshop_data/Case_study_1/pure_component_database.json: -------------------------------------------------------------------------------- 1 | { 2 | "thf": { 3 | "Formula": "C4H8O", 4 | "CAS": "109-99-9", 5 | "mw": 72.1057, 6 | "p_vap": [ 7 | 9.12689661241373, 8 | 1202.942, 9 | -46.818 10 | ], 11 | "Tc": 541.0, 12 | "Pc": 5258767.5, 13 | "rho_liq": null, 14 | "rho_vap": null, 15 | "cp_liq": [ 16 | 124.1 17 | ], 18 | "cp_vap": [ 19 | 77.18 20 | ] 21 | }, 22 | "ethanol": { 23 | "Formula": "C2H6O", 24 | "CAS": "64-17-5", 25 | "mw": 46.0684, 26 | "p_vap": [ 27 | 10.25248661241373, 28 | 1598.673, 29 | -46.424 30 | ], 31 | "Tc": 514.0, 32 | "Pc": 6383475.0, 33 | "rho_liq": null, 34 | "rho_vap": null, 35 | "cp_liq": [ 36 | 112.4 37 | ], 38 | "cp_vap": [ 39 | 65.49 40 | ] 41 | }, 42 | "heptane": { 43 | "Formula": "C7H16", 44 | "CAS": "142-82-5", 45 | "mw": 100.2019, 46 | "p_vap": [ 47 | 9.03403661241373, 48 | 1268.636, 49 | -56.199 50 | ], 51 | "Tc": 540.0, 52 | "Pc": 2776305.0, 53 | "rho_liq": null, 54 | "rho_vap": null, 55 | "cp_liq": [ 56 | 224.64 57 | ], 58 | "cp_vap": [ 59 | 165.98 60 | ] 61 | }, 62 | "dodecane": { 63 | "Formula": "C12H26", 64 | "CAS": "112-40-3", 65 | "mw": 170.3348, 66 | "p_vap": [ 67 | 9.111206612413731, 68 | 1625.928, 69 | -92.839 70 | ], 71 | "Tc": 658.2, 72 | "Pc": 1848167.9999999998, 73 | "rho_liq": null, 74 | "rho_vap": null, 75 | "cp_liq": [ 76 | 376.0 77 | ], 78 | "cp_vap": null 79 | }, 80 | "ethylacetate": { 81 | "Formula": "C4H8O2", 82 | "CAS": "141-78-6", 83 | "mw": 88.1051, 84 | "p_vap": [ 85 | 9.23380661241373, 86 | 1245.702, 87 | -55.189 88 | ], 89 | "Tc": 530.0, 90 | "Pc": 4336710.0, 91 | "rho_liq": null, 92 | "rho_vap": null, 93 | "cp_liq": [ 94 | 168.94 95 | ], 96 | "cp_vap": [ 97 | 125.82 98 | ] 99 | }, 100 | "water": { 101 | "Formula": "H2O", 102 | "CAS": "7732-18-5", 103 | "mw": 18.0153, 104 | "p_vap": [ 105 | 10.08251661241373, 106 | 1659.793, 107 | -45.854 108 | ], 109 | "Tc": 647.0, 110 | "Pc": 22356348.0, 111 | "rho_liq": null, 112 | "rho_vap": null, 113 | "cp_liq": null, 114 | "cp_vap": null 115 | } 116 | } -------------------------------------------------------------------------------- /install_instructions.txt: -------------------------------------------------------------------------------- 1 | # ---------- With installer ---------- 2 | After installing miniconda, do the following: 3 | 4 | Step 1: 5 | WINDOWS: Open an Anaconda PowerShell ("powershell" for short) by clicking on: Start --> Anaconda3 (XX-bit) --> Anaconda PowerShell Prompt (Miniconda3). Right click on it and execute as an administrator 6 | LINUX/MAC: Open a new terminal where the this file is. 7 | 8 | Step 2 (WINDOWS only): Copy (CTRL+C) the path to the installation files (where this file is). This can be done by clicking on the top horizontal bar of the file explorer, which will highlight the path we need to go to. For instance, after copying, my path is C:\Users\dcasasor\OneDrive - purdue.edu\postdoc\PharmaPy_dcasasor 9 | 10 | Step 3 (WINDOWS only): Introduce the following command in the powershell: cd '' and hit Enter. Do not include the <> characters, but do include the quotes. Replace text in between the <> characters with the path you copied in Step 2. Use CTRL+V to avoid transcription errors 11 | 12 | Step 4: After this step, both PharmaPy dependencies and PharmaPy itself should be installed 13 | WINDOWS: Do .\InstallOnWindows.bat and hit Enter. Follow the instructions on screen 14 | LINUX/MAC: To give permissions to the installation file, do chmod +x InstallOnMac.sh. 15 | Then, execute it with source InstallOnMac.sh. Follow the instructions on screen 16 | 17 | Step 5: Activate the new environment by doing: conda activate (exclude <>) 18 | 19 | Step 6: Test PharmaPy by moving to the tests/ directory (cd tests) and then running: python reactor_tests.py 20 | 21 | # ---------- Manual installation ---------- 22 | Step 1: Create a conda environment named (optional) 23 | 24 | conda create --name python=3.9 25 | 26 | Step 1.1: If making a virtual environment 27 | 28 | conda activate 29 | 30 | This activates the environment. You may deactivate the environment using 31 | the command: 32 | 33 | conda deactivate 34 | 35 | All the packages installed will be frozen on this environment, and will be 36 | separate from those installed on your base environment. Using this virtual 37 | environment structure allows us to have only the packages we need to do 38 | the analysis we need to do! If you want to use base, you may continue to step 39 | 2 without doing steps 1 and 1.1. 40 | 41 | When user does not have a write permission to a required path, please direct the path of the directory 42 | 43 | step 1.1.1: Disclose the available environment directories. 44 | 45 | conda config -- show envs_dirs 46 | 47 | step 1.1.2: Create a path 48 | conda create --prefix > 49 | 50 | Step 2: Install the package requirements in requirements.txt 51 | 52 | conda install -c conda-forge --file requirements.txt 53 | 54 | Step 3: Install PharmaPy; navigate to the source directory 55 | 56 | python setup.py develop 57 | 58 | Step 4: Check to see if the package is working by running a test in tests/ 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /doc/online_docs/index.rst: -------------------------------------------------------------------------------- 1 | .. PharmaPy documentation master file, created by 2 | sphinx-quickstart on Fri Feb 11 15:06:38 2022. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to PharmaPy's documentation! 7 | ==================================== 8 | 9 | PharmaPy is an open-source library for the analysis of pharmaceutical manufacturing systems. Some of its features are: 10 | 11 | * Fully dynamic models (ODEs and DAEs) of commonly found unit operations on the drug substance side of pharmaceutical manufacturing 12 | * Start-up modeling, disturbance analysis 13 | * Fully sequential-modular approach: each unit is simulated individually in a pre-defined sequence 14 | * Allows decoupling of continuous/discontinuous sections of a flowsheet 15 | * Flexible modeling for batch/hybrid/continuous flowsheets 16 | * Utilize robust numerical integrators: SUNDIALS through python package Assimulo (ODE/DAE simulation) 17 | * In-house implementation of the Levenberg-Marquardt algorithm for Parameter Estimation 18 | * Simulate flowsheets, estimate kinetic parameters, optimize process conditions with external tools 19 | 20 | How to cite us: 21 | 22 | .. bibliography:: 23 | :list: bullet 24 | 25 | Casas-Orozco2020 26 | 27 | Bibtex entry:: 28 | 29 | @article{Casas-Orozco2020, 30 | author = {Casas-Orozco, Daniel and Laky, Daniel and Wang, Vivian and Abdi, Mesfin and Feng, X. and Wood, E. and Laird, Carl and Reklaitis, Gintaras V. and Nagy, Zoltan K.}, 31 | doi = {10.1016/j.compchemeng.2021.107408}, 32 | issn = {00981354}, 33 | journal = {Comput. Chem. Eng.}, 34 | month = {oct}, 35 | pages = {107408}, 36 | title = {{PharmaPy: An object-oriented tool for the development of hybrid pharmaceutical flowsheets}}, 37 | url = {https://linkinghub.elsevier.com/retrieve/pii/S0098135421001861}, 38 | volume = {153}, 39 | year = {2021} 40 | } 41 | 42 | Our team 43 | ========== 44 | 45 | Developers 46 | ++++++++++ 47 | 48 | * `Daniel Casas-Orozco`_ 49 | * Daniel J. Laky 50 | * Inyoung Hur 51 | * Varun Sundarkumar 52 | * Yash Barhate 53 | * Jung Soo Rhim 54 | * PharmaPy logo by Montgomery Smith 55 | 56 | Purdue University Staff 57 | +++++++++++++++++++++++ 58 | 59 | * Prof. Zoltan Nagy (PI) 60 | * Prof. Gintaras V. Reklaitis (coPI) 61 | 62 | .. _`Daniel Casas-Orozco`: https://github.com/dcasasor-purdue 63 | 64 | Support 65 | ======= 66 | PharmaPy has been made with the collaboration and support from the following institutions: 67 | 68 | * The `CryPTSys Lab`_ at Purdue University 69 | * The U.S. Food and Drug Administration (FDA) 70 | 71 | .. _`CryPTSys Lab`: https://engineering.purdue.edu/CryPTSys/index.html 72 | 73 | 74 | .. toctree:: 75 | :maxdepth: 2 76 | :caption: Contents: 77 | 78 | installation 79 | docstrings/index 80 | examples/index 81 | publications 82 | advanced_usage 83 | 84 | Indices and tables 85 | ================== 86 | 87 | * :ref:`genindex` 88 | * :ref:`modindex` 89 | * :ref:`search` 90 | 91 | -------------------------------------------------------------------------------- /doc/online_docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | sys.path.insert(0, os.path.abspath('../../')) 17 | # sys.path.insert(0, os.path.abspath('sphinxext')) 18 | 19 | 20 | # Setup of mock libraries to override the build fails for C based libraries 21 | 22 | #import mock 23 | 24 | autodoc_mock_imports = ["numpy", "scipy", "matplotlib", "pandas", "autograd", "assimulo", "cyipopt"] 25 | 26 | #MOCK_MODULES = ['numpy', 'scipy', 'matplotlib', 'matplotlib.pyplot', 'scipy.interpolate', 'assimulo'] 27 | #for mod_name in MOCK_MODULES: 28 | # sys.modules[mod_name] = mock.Mock() 29 | 30 | 31 | # -- Project information ----------------------------------------------------- 32 | 33 | project = 'PharmaPy' 34 | copyright = '2023, Purdue University, Daniel Casas-Orozco, Dan Laky, Inyoung Hur' 35 | author = 'Daniel Casas-Orozco, Dan Laky, Inyoung Hur' 36 | 37 | # The full version, including alpha/beta/rc tags 38 | release = '2023' 39 | 40 | 41 | # -- General configuration --------------------------------------------------- 42 | 43 | # Add any Sphinx extension module names here, as strings. They can be 44 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 45 | # ones. 46 | extensions = ['sphinx.ext.doctest', 'sphinx.ext.autodoc', 'sphinx.ext.napoleon', 'nbsphinx', 'sphinxcontrib.bibtex' 47 | ] 48 | 49 | bibtex_bibfiles = ['references.bib'] 50 | 51 | # Add any paths that contain templates here, relative to this directory. 52 | templates_path = ['_templates'] 53 | 54 | # List of patterns, relative to source directory, that match files and 55 | # directories to ignore when looking for source files. 56 | # This pattern also affects html_static_path and html_extra_path. 57 | exclude_patterns = [] 58 | 59 | 60 | # -- Options for HTML output ------------------------------------------------- 61 | 62 | # The theme to use for HTML and HTML Help pages. See the documentation for 63 | # a list of builtin themes. 64 | # 65 | html_theme = 'sphinx_rtd_theme' 66 | html_logo = 'images/PharmaPy_logo.jpeg' 67 | 68 | # Add any paths that contain custom static files (such as style sheets) here, 69 | # relative to this directory. They are copied after the builtin static files, 70 | # so a file named "default.css" will overwrite the builtin "default.css". 71 | html_static_path = ['static','images'] 72 | html_context = { 73 | "footer_logos": { 74 | "row1": [ 75 | { 76 | "alt": "Purdue University Logo", 77 | # "src": "Purdue_footer_logo.png", 78 | "src": "purdue_logo.png", 79 | "href": "https://www.purdue.edu/", 80 | }, 81 | { 82 | "alt": "U.S. Food and Drug Administration Logo", 83 | "src": "fda_logo.png", 84 | "href": "https://www.fda.gov/", 85 | }, 86 | ], 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tests/integration/data/pfr_test_expected_times.csv: -------------------------------------------------------------------------------- 1 | 0.000000000000000000e+00 2 | 4.310787533763738130e-06 3 | 4.311218612517114107e-02 4 | 4.741909395015449502e-01 5 | 9.052696928779186969e-01 6 | 1.336348446254292499e+00 7 | 2.052171030598715085e+00 8 | 2.767993614943137448e+00 9 | 3.879314488144147255e+00 10 | 4.990635361345156618e+00 11 | 6.101956234546165980e+00 12 | 8.177234652972067863e+00 13 | 1.025251307139797063e+01 14 | 1.232779148982387341e+01 15 | 1.547768046722090851e+01 16 | 1.862756944461794362e+01 17 | 2.177745842201498050e+01 18 | 2.492734739941201383e+01 19 | 2.807723637680904716e+01 20 | 3.122712535420608049e+01 21 | 3.437701433160311382e+01 22 | 3.752690330900014715e+01 23 | 4.226264905998146304e+01 24 | 4.699839481096277893e+01 25 | 5.173414056194409483e+01 26 | 5.646988631292541072e+01 27 | 6.120563206390672661e+01 28 | 6.594137781488804251e+01 29 | 7.067712356586935130e+01 30 | 7.541286931685067429e+01 31 | 8.014861506783199729e+01 32 | 8.488436081881332029e+01 33 | 8.962010656979464329e+01 34 | 9.676089375866889952e+01 35 | 1.039016809475431558e+02 36 | 1.110424681364174120e+02 37 | 1.181832553252916682e+02 38 | 1.253240425141659244e+02 39 | 1.324648297030401807e+02 40 | 1.396056168919144227e+02 41 | 1.512238879974088093e+02 42 | 1.628421591029031958e+02 43 | 1.744604302083975824e+02 44 | 1.860787013138919690e+02 45 | 1.976969724193863556e+02 46 | 2.093152435248807421e+02 47 | 2.209335146303751287e+02 48 | 2.325517857358695153e+02 49 | 2.441700568413639019e+02 50 | 2.557883279468582884e+02 51 | 2.735438891179424559e+02 52 | 2.912994502890265949e+02 53 | 3.090550114601107339e+02 54 | 3.268105726311948729e+02 55 | 3.445661338022790119e+02 56 | 3.623216949733631509e+02 57 | 3.800772561444472899e+02 58 | 3.978328173155314289e+02 59 | 4.155883784866155679e+02 60 | 4.333439396576997069e+02 61 | 4.510995008287838459e+02 62 | 4.688550619998679849e+02 63 | 4.866106231709521239e+02 64 | 5.043661843420362629e+02 65 | 5.221217455131204588e+02 66 | 5.398773066842046546e+02 67 | 5.576328678552888505e+02 68 | 5.753884290263730463e+02 69 | 6.024202729676366062e+02 70 | 6.294521169089001660e+02 71 | 6.564839608501637258e+02 72 | 6.835158047914272856e+02 73 | 7.105476487326908455e+02 74 | 7.375794926739544053e+02 75 | 7.646113366152179651e+02 76 | 7.916431805564815249e+02 77 | 8.186750244977450848e+02 78 | 8.457068684390086446e+02 79 | 8.727387123802722044e+02 80 | 8.997705563215357643e+02 81 | 9.268024002627993241e+02 82 | 9.538342442040628839e+02 83 | 9.808660881453264437e+02 84 | 1.007897932086590004e+03 85 | 1.034929776027853450e+03 86 | 1.061961619969117010e+03 87 | 1.088993463910380569e+03 88 | 1.116025307851644129e+03 89 | 1.143057151792907689e+03 90 | 1.170088995734171249e+03 91 | 1.197120839675434809e+03 92 | 1.224152683616698368e+03 93 | 1.251184527557961928e+03 94 | 1.278216371499225488e+03 95 | 1.305248215440489048e+03 96 | 1.332280059381752608e+03 97 | 1.359311903323016168e+03 98 | 1.386343747264279727e+03 99 | 1.413375591205543287e+03 100 | 1.440407435146806847e+03 101 | 1.467439279088070407e+03 102 | 1.494471123029333967e+03 103 | 1.521502966970597527e+03 104 | 1.548534810911861086e+03 105 | 1.575566654853124646e+03 106 | 1.602598498794388206e+03 107 | 1.629630342735651766e+03 108 | 1.656662186676915326e+03 109 | 1.683694030618178886e+03 110 | 1.710725874559442445e+03 111 | 1.737757718500706005e+03 112 | 1.764789562441969565e+03 113 | 1.791821406383233125e+03 114 | 1.818853250324496685e+03 115 | 1.845885094265760245e+03 116 | 1.872916938207023804e+03 117 | 1.899948782148287364e+03 118 | 1.926980626089550924e+03 119 | 1.954012470030814484e+03 120 | 1.994627061889636252e+03 121 | 2.035241653748458020e+03 122 | 2.075856245607279561e+03 123 | 2.116470837466101329e+03 124 | 2.157085429324923098e+03 125 | 2.197700021183744866e+03 126 | 2.238314613042566634e+03 127 | 2.278929204901388403e+03 128 | 2.319543796760210171e+03 129 | 2.360158388619031939e+03 130 | 2.400772980477853707e+03 131 | 2.464271435278984427e+03 132 | 2.527769890080115147e+03 133 | 2.591268344881245866e+03 134 | 2.654766799682376586e+03 135 | 2.718265254483507306e+03 136 | 2.781763709284638026e+03 137 | 2.845262164085768745e+03 138 | 2.908760618886899465e+03 139 | -------------------------------------------------------------------------------- /PharmaPy/Gaussians.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Tue Jun 30 18:21:24 2020 4 | 5 | @author: dcasasor 6 | """ 7 | 8 | import numpy as np 9 | 10 | 11 | def gaussian(x, mu=0, sigma=1, ampl=1): 12 | func = ampl/sigma/np.sqrt(2*np.pi) * np.exp(-0.5*((x - mu) / sigma)**2) 13 | 14 | return func 15 | 16 | 17 | def multiple_gaussian(x, mus, sigmas, ampls, separated=False): 18 | 19 | num_mu = len(mus) 20 | num_sigma = len(sigmas) 21 | num_ampl = len(ampls) 22 | 23 | num_x = len(x) 24 | 25 | equal = num_mu == num_sigma == num_ampl 26 | if not equal: 27 | raise RuntimeError("'mus', 'sigmas' and 'ampls' must have the same " 28 | "number of elements") 29 | 30 | gaussians = np.zeros((num_mu, num_x)) 31 | for ind in range(num_mu): 32 | gaussians[ind] = gaussian(x, mus[ind], sigmas[ind], ampls[ind]) 33 | 34 | gaussians = gaussians.T 35 | 36 | if separated: 37 | return gaussians 38 | else: 39 | return gaussians.sum(axis=1) 40 | 41 | 42 | def gaussian_dparam(x, mu=0, sigma=1, ampl=1): 43 | gauss = gaussian(x, mu, sigma, ampl) 44 | 45 | df_dmu = gauss * (x - mu) / sigma**2 46 | df_dsigma = gauss / sigma * (((x - mu) / sigma)**2 - 1) 47 | df_dampl = gauss / ampl 48 | 49 | return df_dmu, df_dsigma, df_dampl 50 | 51 | 52 | def gaussian_dx(x, mu=0, sigma=1, ampl=1): 53 | gauss = gaussian(x, mu, sigma, ampl) 54 | 55 | df_dx = -gauss * (x - mu) / sigma**2 56 | 57 | return df_dx 58 | 59 | 60 | def gaussian_dxdx(x, mu=0, sigma=1, ampl=1): 61 | gauss = gaussian(x, mu, sigma, ampl) 62 | 63 | df_dxdx = gauss / sigma**2 * (((x - mu) / sigma)**2 - 1) 64 | 65 | return df_dxdx 66 | 67 | 68 | def gauss_dparam_mult(x, mus, sigmas, ampls): 69 | num_mu = len(mus) 70 | num_sigma = len(sigmas) 71 | num_ampl = len(ampls) 72 | 73 | num_x = len(x) 74 | 75 | equal = num_mu == num_sigma == num_ampl 76 | if not equal: 77 | raise RuntimeError("'mus', 'sigmas' and 'ampls' must have the same " 78 | "number of elements") 79 | 80 | dgauss_dmu = np.zeros((num_x, num_mu)) 81 | dgauss_dsigma = np.zeros_like(dgauss_dmu) 82 | dgauss_dampl = np.zeros_like(dgauss_dmu) 83 | 84 | for ind in range(num_mu): 85 | df_dmu, df_dsigma, df_dampl = gaussian_dparam(x, mus[ind], sigmas[ind], 86 | ampls[ind]) 87 | 88 | dgauss_dmu[:, ind] = df_dmu 89 | dgauss_dsigma[:, ind] = df_dsigma 90 | dgauss_dampl[:, ind] = df_dampl 91 | 92 | dgauss_dparam = np.hstack((dgauss_dmu, dgauss_dsigma, dgauss_dampl)) 93 | 94 | return dgauss_dparam 95 | 96 | 97 | def gauss_dx_mult(x, mus, sigmas, ampls): 98 | num_mu = len(mus) 99 | num_sigma = len(sigmas) 100 | num_ampl = len(ampls) 101 | 102 | num_x = len(x) 103 | 104 | equal = num_mu == num_sigma == num_ampl 105 | if not equal: 106 | raise RuntimeError("'mus', 'sigmas' and 'ampls' must have the same " 107 | "number of elements") 108 | 109 | dgauss_dx = np.zeros((num_mu, num_x)) 110 | 111 | for ind in range(num_mu): 112 | dgauss_dx[ind] = gaussian_dx(x, mus[ind], sigmas[ind], ampls[ind]) 113 | 114 | dgauss_dx = dgauss_dx.sum(axis=0) 115 | 116 | return dgauss_dx 117 | 118 | 119 | def gauss_dxdx_mult(x, mus, sigmas, ampls): 120 | num_mu = len(mus) 121 | num_sigma = len(sigmas) 122 | num_ampl = len(ampls) 123 | 124 | num_x = len(x) 125 | 126 | equal = num_mu == num_sigma == num_ampl 127 | if not equal: 128 | raise RuntimeError("'mus', 'sigmas' and 'ampls' must have the same " 129 | "number of elements") 130 | 131 | dgauss_dxdx = np.zeros((num_mu, num_x)) 132 | 133 | for ind in range(num_mu): 134 | dgauss_dxdx[ind] = gaussian_dxdx(x, mus[ind], sigmas[ind], ampls[ind]) 135 | 136 | dgauss_dxdx = dgauss_dxdx.sum(axis=0) 137 | 138 | return dgauss_dxdx 139 | -------------------------------------------------------------------------------- /data/thermodynamics/unifac_rk_qk.csv: -------------------------------------------------------------------------------- 1 | No.,Subgroup Name,Main Group No.,Main Group Name,Rk,Qk 2 | 1,CH3,1,CH2,0.6325,1.0608 3 | 2,CH2,1,CH2,0.6325,0.7081 4 | 3,CH,1,CH2,0.6325,0.3554 5 | 4,C,1,CH2,0.6325,0 6 | 5,CH2=CH,2,C=C,1.2832,1.6016 7 | 6,CH=CH,2,C=C,1.2832,1.2489 8 | 7,CH2=C,2,C=C,1.2832,1.2489 9 | 8,CH=C,2,C=C,1.2832,0.8962 10 | 9,ACH,3,ACH,0.3763,0.4321 11 | 10,AC,3,ACH,0.3763,0.2113 12 | 11,ACCH3,4,ACCH2,0.91,0.949 13 | 12,ACCH2,4,ACCH2,0.91,0.7962 14 | 13,ACCH,4,ACCH2,0.91,0.3769 15 | 14,OH (P),5,OH,1.2302,0.8927 16 | 15,CH3OH,6,CH3OH,0.8585,0.9938 17 | 16,H2O,7,H2O,1.7334,2.4561 18 | 17,ACOH,8,ACOH,1.08,0.975 19 | 18,CH3CO,9,CH2CO,1.7048,1.67 20 | 19,CH2CO,9,CH2CO,1.7048,1.5542 21 | 20,CHO,10,CHO,0.7173,0.771 22 | 21,CH3COO,11,CCOO,1.27,1.6286 23 | 22,CH2COO,11,CCOO,1.27,1.4228 24 | 23,HCOO,12,HCOO,1.9,1.8 25 | 24,CH3O,13,CH2O,1.1434,1.6022 26 | 25,CH2O,13,CH2O,1.1434,1.2495 27 | 26,CHO,13,CH2O,1.1434,0.8968 28 | 27,THF,43,CY-CH2O,1.7023,1.8784 29 | 28,CH3NH2,14,CH2NH2,1.6607,1.6904 30 | 29,CH2NH2,14,CH2NH2,1.6607,1.3377 31 | 30,CHNH2,14,CH2NH2,1.6607,0.985 32 | 31,CH3NH,15,CH2NH,1.368,1.4332 33 | 32,CH2NH,15,CH2NH,1.368,1.0805 34 | 33,CHNH,15,CH2NH,1.368,0.7278 35 | 34,CH3N,16,(C)3N,1.0746,1.176 36 | 35,CH2N,16,(C)3N,1.0746,0.824 37 | 36,ACNH2,17,ACNH2,1.1849,0.8067 38 | 37,AC2H2N,18,PYRIDINE,1.4578,0.9022 39 | 38,AC2HN,18,PYRIDINE,1.2393,0.633 40 | 39,AC2N,18,PYRIDINE,1.0731,0.353 41 | 40,CH3CN,19,CH2CN,1.5575,1.5193 42 | 41,CH2CN,19,CH2CN,1.5575,1.1666 43 | 42,COOH,20,COOH,0.8,0.9215 44 | 43,HCOOH,44,HCOOH,0.8,1.2742 45 | 44,CH2CL,21,CCL,0.9919,1.3654 46 | 45,CHCL,21,CCL,0.9919,1.0127 47 | 46,CCL,21,CCL,0.9919,0.66 48 | 47,CH2CL2,22,CCL2,1.8,2.5 49 | 48,CHCL2,22,CCL2,1.8,2.1473 50 | 49,CCL2,22,CCL2,1.8,1.7946 51 | 50,CHCL3,45,CHCL3,2.45,2.8912 52 | 51,CCL3,23,CCL3,2.65,2.3778 53 | 52,CCL4,24,CCL4,2.618,3.1836 54 | 53,ACCL,25,ACCL,0.5365,0.3177 55 | 54,CH3NO2,26,CNO2,2.644,2.5 56 | 55,CH2NO2,26,CNO2,2.5,2.304 57 | 56,CHNO2,26,CNO2,2.887,2.241 58 | 57,ACNO2,27,ACNO2,0.4656,0.3589 59 | 58,CS2,28,CS2,1.24,1.068 60 | 59,CH3SH,29,CH3SH,1.289,1.762 61 | 60,CH2SH,29,CH3SH,1.535,1.316 62 | 61,FURFURAL,30,FURFURAL,1.299,1.289 63 | 62,DOH,31,DOH,2.088,2.4 64 | 63,I,32,I,1.076,0.9169 65 | 64,BR,33,BR,1.209,1.4 66 | 65,CH=-C,34,C=-C,0.9214,1.3 67 | 66,C=-C,34,C=-C,1.303,1.132 68 | 67,DMSO,35,DMSO,3.6,2.692 69 | 68,ACRY,36,ACRY,1,0.92 70 | 69,CL-(C=C),37,CLCC,0.5229,0.7391 71 | 70,C=C,2,C=C,1.2832,0.4582 72 | 71,ACF,38,ACF,0.8814,0.7269 73 | 72,DMF,39,DMF,2,2.093 74 | 73,HCON(..,39,DMF,2.381,1.522 75 | 74,CF3,40,CF2,1.284,1.266 76 | 75,CF2,40,CF2,1.284,1.098 77 | 76,CF,40,CF2,0.8215,0.5135 78 | 77,COO,41,COO,1.6,0.9 79 | 78,CY-CH2,42,CY-CH2,0.7136,0.8635 80 | 79,CY-CH,42,CY-CH2,0.3479,0.1071 81 | 80,CY-C,42,CY-CH2,0.347,0 82 | 81,OH (S),5,OH,1.063,0.8663 83 | 82,OH (T),5,OH,0.6895,0.8345 84 | 83,CY-CH2O,43,CY-CH2O,1.4046,1.4 85 | 84,TRIOXAN,43,CY-CH2O,1.0413,1.0116 86 | 85,CNH2,14,CH2NH2,1.6607,0.985 87 | 86,NMP,46,CY-CONC,3.981,3.2 88 | 87,NEP,46,CY-CONC,3.7543,2.892 89 | 88,NIPP,46,CY-CONC,3.5268,2.58 90 | 89,NTBP,46,CY-CONC,3.2994,2.352 91 | 91,CONH2,47,CONR,1.4515,1.248 92 | 92,CONHCH3,47,CONR,1.5,1.08 93 | 93,HCONHCH3,49,HCONR,2.4617,2.192 94 | 94,HCONHCH2,49,HCONR,2.4617,1.842 95 | 100,CONHCH2,47,CONR,1.5,1.08 96 | 101,AM(CH3)2,48,CONR2,2.4748,1.9643 97 | 102,AMCH3CH2,48,CONR2,2.2739,1.5754 98 | 103,AM(CH2)2,48,CONR2,2.0767,1.1866 99 | 104,AC2H2S,52,ACS,1.7943,1.34 100 | 105,AC2HS,52,ACS,1.6282,1.06 101 | 106,AC2S,52,ACS,1.4621,0.78 102 | 107,H2COCH,53,EPOXIDES,1.3601,1.8031 103 | 108,COCH,53,EPOXIDES,0.683,0.3418 104 | 109,HCOCH,53,EPOXIDES,0.9104,0.6538 105 | 110,(CH2)2SU,56,SULFONE,2.687,2.12 106 | 111,CH2SUCH,56,SULFONE,2.46,1.808 107 | 112,(CH3)2CB,55,CARBONAT,2.42,2.4976 108 | 113,(CH2)2CB,55,CARBONAT,2.42,2.0018 109 | 114,CH2CH3CB,55,CARBONAT,2.42,2.2497 110 | 119,H2COCH2,53,EPOXIDES,1.063,1.123 111 | 122,CH3S,61,CH2S,1.613,1.368 112 | 123,CH2S,61,CH2S,1.3863,1.06 113 | 124,CHS,61,CH2S,1.1589,0.748 114 | 153,H2COC,53,EPOXIDES,0.9104,0.6538 115 | 178,C3H2N2+,84,IMIDAZOL,1.3662,0.6797 116 | 179,BTI-,85,BTI,5.621,5.9463 117 | 184,C3H3N2+,84,IMIDAZOL,1.843,1.6997 118 | 189,C4H8N+,87,PYRROL,2.7867,2.7723 119 | 195,BF4-,89,BF4,3.9628,0.6214 120 | 196,C5H5N+,90,PYRIDIN,2.1094,2.5106 121 | 197,OTF-,91,OTF,3.371,2.0001 122 | 201,-S-S-,93,-S-S-,1.0678,2.244 123 | 209,SO4,98,SO4,0.9903,3.5249 124 | 210,HSO4,98,SO4,1.5654,3.8076 125 | 211,PF6,99,PF6,3.8183,3.6018 126 | 220,C5H4N+,90,PYRIDIN,2.4873,2.4457 127 | -------------------------------------------------------------------------------- /PharmaPy/jac_module.py: -------------------------------------------------------------------------------- 1 | 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Spyder Editor 5 | 6 | This is a temporary script file. 7 | """ 8 | 9 | import numpy as np 10 | from numpy.linalg import norm 11 | 12 | eps = np.sqrt(np.finfo(float).eps) 13 | 14 | 15 | def dx_jac_x(x, abstol, reltol, eps): 16 | sigma_zero = 1 17 | crit_1 = np.abs(x) * np.sqrt(eps) 18 | 19 | weights = 1 / (reltol * x + abstol) 20 | crit_2 = sigma_zero / weights 21 | 22 | dx = np.maximum(crit_1, crit_2) 23 | 24 | # print(dx) 25 | return dx 26 | 27 | 28 | def dx_jac_p(p, abstol, reltol, eps): 29 | dx = np.abs(p) * np.sqrt(max(reltol, eps)) 30 | return dx 31 | 32 | 33 | def numerical_jac(func, x, args=(), dx=None, abs_tol=None, rel_tol=None, 34 | pick_x=None): 35 | 36 | if dx is None: 37 | dx = np.ones_like(x) * eps 38 | elif callable(dx): 39 | # dx = dx(x, abs_tol, rel_tol, eps) 40 | dx = dx(x) 41 | else: 42 | dx = np.ones_like(x) * dx 43 | 44 | if pick_x is None: 45 | pick_x = np.arange(len(x)) 46 | else: 47 | pick_x = np.atleast_1d(pick_x) 48 | 49 | f_eval = func(x, *args) 50 | f_eval = np.atleast_1d(f_eval) 51 | 52 | num_x = len(pick_x) 53 | num_f = len(f_eval) 54 | 55 | jac = np.zeros((num_f, num_x)) 56 | delx = np.zeros_like(x) 57 | 58 | for idx, i in enumerate(pick_x): 59 | delx[i] = dx[i] 60 | jac[:, idx] = (func(x + delx, *args) - f_eval)/dx[i] 61 | delx[i] = 0 62 | 63 | return jac 64 | 65 | 66 | def numerical_jac_central(func, x, rel_tol, abs_tol, dx=None, args=()): 67 | 68 | if dx is None: 69 | dx = np.ones_like(x) * eps 70 | elif callable(dx): 71 | dx = dx(x, abs_tol, rel_tol, eps) 72 | else: 73 | dx = dx * np.ones_like(x) 74 | 75 | num_x = len(x) 76 | jac = [] 77 | delx = np.zeros_like(x) 78 | 79 | for j in range(num_x): 80 | delx[j] = dx[j] 81 | jac.append((func(x + delx, *args) - func(x - delx, *args)) /2. / dx[j]) 82 | delx[j] = 0 83 | 84 | return np.column_stack(jac) 85 | 86 | 87 | def numerical_jac_data(func, x, args=(), dx=None, pick_x=None): 88 | 89 | if callable(dx): 90 | dx = dx(x) 91 | elif dx is None: 92 | dx = np.ones_like(x) * eps 93 | else: 94 | dx = np.ones_like(x) * dx 95 | 96 | if pick_x is None: 97 | pick_x = np.arange(len(x)) 98 | else: 99 | pick_x = np.atleast_1d(pick_x) 100 | 101 | f_eval = func(x, *args) 102 | num_data = len(f_eval) 103 | num_states = len(pick_x) 104 | 105 | jac = np.zeros((num_data, num_states)) 106 | delx = np.zeros_like(x) 107 | 108 | for idx, i in enumerate(pick_x): 109 | delx[i] = dx[i] 110 | jac[:, idx] = (func(x + delx, *args) - f_eval)/dx[i] 111 | delx[i] = 0 112 | 113 | return jac 114 | 115 | 116 | def numerical_jacv(func, x, v, args=()): 117 | """ Function to evalute the right product J*v. After Hindmarsh and Serban 118 | (2020) (CVODEs 5.1.0 manual, sec. 2.1). 119 | 120 | It is not very accurate calculation (compared with performing J*v directly) 121 | but accurate enough for an iterative linear method such as GMRS 122 | 123 | 124 | Parameters 125 | ---------- 126 | func : TYPE 127 | DESCRIPTION. 128 | x : TYPE 129 | DESCRIPTION. 130 | v : TYPE 131 | DESCRIPTION. 132 | args : TYPE, optional 133 | DESCRIPTION. The default is (). 134 | 135 | Returns 136 | ------- 137 | jac_v : TYPE 138 | DESCRIPTION. 139 | 140 | """ 141 | sig = 1/norm(v) 142 | 143 | f_eval = func(x, *args) 144 | jac_v = (func(x + sig*v, *args) - f_eval) / sig 145 | 146 | return jac_v 147 | 148 | 149 | def jac_fun(x): 150 | x1, x2 = x 151 | dim = len(x) 152 | jac = np.zeros((dim, dim)) 153 | 154 | jac[0, 0] = 2*x1 155 | jac[0, 1] = -3/2*x2**2 156 | jac[1, 0] = 1 157 | jac[1, 1] = 1/2/np.sqrt(x2) 158 | 159 | return jac 160 | 161 | 162 | if __name__ == '__main__': 163 | from autograd import jacobian, make_jvp 164 | from jax import jvp 165 | 166 | # Autograd fns 167 | jac_ad = jacobian(fun) 168 | jacv_ad = make_jvp(fun) 169 | 170 | 171 | # Nominal x 172 | x_test = np.array([1., 2.]) 173 | 174 | # Evaluate jacs 175 | jacfun_eval = jac_fun(x_test) 176 | # jacauto_eval = jac_ad(x_test) 177 | jacnum_eval = numerical_jac(fun, x_test) 178 | 179 | # Evaluate J*v 180 | v_test = np.array([0.5, 0.5]) 181 | jacv_analytic = np.dot(jacfun_eval, v_test) 182 | jacv_numeric = numerical_jacv(fun, x_test, v_test) 183 | _, jacv_autograd = jacv_ad(x_test)(v_test) 184 | # _, jacv_jax = jvp(fun, (x_test,), (v_test,)) 185 | -------------------------------------------------------------------------------- /doc/online_docs/advanced_usage.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | Advanced features 3 | ==================== 4 | 5 | State events 6 | ============ 7 | 8 | State events are passed as Python dictionaries. Simple state events based directly on model states (i.e. :math:`f(y) = y`) can be passed as shown for the following structure. For detecting a given temperature, one would do: 9 | 10 | .. testcode:: 11 | 12 | my_state_event = {'state_name': 'temperature', 'value': 400} 13 | 14 | If a given state is not scalar but has to be indexed, e.g. concentration for a given component, the dictionary also needs to have the :code:`state_idx` field. For example, if the simulation is to be stopped when the concentration the first component for a reactor model reaches a reference value, the dictionary describing the state event would be: 15 | 16 | .. testcode:: 17 | 18 | my_state_event = {'state_name': 'mole_conc', 'state_idx': 0, 'value': 0.2} 19 | 20 | More advanced usage of state events is allowed by passing a callable directly to PharmaPy. This callable will be able to make full use of all the instantaneous state and derivative information, which is passed by PharmaPy at each integration step. In this case, the function is passed using a dictionary with the keyword :code:`callable`: 21 | 22 | .. testcode:: 23 | 24 | my_state_event = {'callable': my_function} 25 | 26 | where the passed callable function must have the signature :code:`my_function(time, states, sdot, **kwargs)` and must return a scalar whose sign changes only when the event is detected. The passed :code:`state` and :code:`sdot` arguments will be dictionaries that have the names of the states as keys. Any keyword arguments can be optionally specified in the state event dictionary, e.g.: 27 | 28 | .. testcode:: 29 | 30 | my_state_event = {'callable': my_function, 'kwargs': {...}} 31 | 32 | An example of a callable passed as a state event is when solubility wants to be monitored. A callable could have the following form: 33 | 34 | .. testcode:: 35 | 36 | def my_callable(time, y, ydot, a, b, c): 37 | solubility = a + b * y['temp'] + c * y['temp']**2 # a, b, and c are solubility constants 38 | event = solubility - y['mass_conc'][1] # Let's say it is a binary system where the first component is the solvent and the second one is the API 39 | 40 | return event 41 | 42 | In this case, the returned :code:`event` variable will be positive until the solubility limit is reached. When that happens, its sign change will be detected by PharmaPy and the integration will be interruped. 43 | 44 | Interpolators 45 | =============== 46 | 47 | Input trajectories can be specified via interpolators. To this purpose, PharmaPy contains a Lagrange polynomial represention that describes a time-dependent input :math:`u(t)` as: 48 | 49 | .. math:: 50 | 51 | u(t) = \sum_{i = 1}^{ord} u_{i, k} \ell_i^{(ord)} (\tau^{(k)}), \quad t \in [t_{k - 1}, t_k], \ k \in \{1, \ldots, n_{interv}\}, 52 | 53 | for a set of user-provided points :math:`u_{i, k}, \ i \in \{1, \ldots, ord\}` within the interval :math:`k`. :math:`\tau` is a normalized time variable given by: 54 | 55 | .. math:: 56 | \tau = \frac{t - t_{k - 1}}{t_{k} - t_{k - 1}}, \quad t \in [t_{k - 1}, t_k] 57 | 58 | and :math:`\ell^{(ord)}` are Lagrange interpolation polynomials given by: 59 | 60 | .. math:: 61 | \ell_{i}^{(ord)} = 62 | \begin{cases} 63 | 1, & ord = 1, \\ 64 | \prod_{j = 1, j \neq i}^{ord} \frac{\tau - \tau_j}{\tau_i - \tau_j} & ord \geq 2, 65 | \end{cases} 66 | 67 | where :math:`ord` represents the order of the interpolation (1 for piecewise constant, 2 for piecewise linear, etc). 68 | 69 | For example, if a given flowrate wants to be specified as a piecewise constant function, a PharmaPy interpolator can be specified as: 70 | 71 | .. testcode:: 72 | 73 | from PharmaPy.Interpolation import PiecewiseLagrange 74 | 75 | time_hor = 3600 # total time [s] 76 | flowrates = [0.1, 0.2, 0.1, 0.6] # kg/s 77 | interpolator = PiecewiseLagrange(time_hor, flowrates, order=1) 78 | 79 | In this particular case, the horizon time will be split into equally sized, 15-min (900 s) bins with their corresponding four specified flows as specified in the :code:`flowrates` variable. User-defined time marks can also be passed as a list or NumPy array by using the :code:`time_k` argument of the :code:`PiecewiseLagrange` interpolator, which needs to be of size :code:`n_y + 1`, where :code:`n_y` is the vector of interpolated values (:code:`flowrates` in this example). 80 | 81 | Piecewise linear interpolators can also be used. In this case, the passed known values must be arranged into a numpy 2-D array, and the interpolation order will be 2. For example, a linear piecewise temperature profile would be constructed as: 82 | 83 | .. testcode:: 84 | 85 | from PharmaPy.Interpolation import PiecewiseLagrange 86 | 87 | time_hor = 3600 # total time [s] 88 | temperatures = np.array([[360, 345], 89 | [345, 330], 90 | [330, 318], 91 | [318, 295]]) # K 92 | interpolator = PiecewiseLagrange(time_hor, temperatures, order=2) 93 | 94 | Note that the values on the second column always match the value of the first column in the next raw, for continuity purposes. Higher orders will follow the same structure, where each row will represent a subinterval and the number of columns will dictate the interpolation order, which must be passed using the :code:`order` argument. 95 | 96 | -------------------------------------------------------------------------------- /PharmaPy/LevMarq.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Fri Nov 8 20:23:12 2019 5 | 6 | @author: dcasasor 7 | """ 8 | 9 | from numpy import ones_like, inner, diag, asarray, maximum 10 | from numpy.linalg import solve, norm, inv 11 | 12 | 13 | lm_header = [ 14 | '{:<40}'.format('-'*60), 15 | "{:<7} {:<10} {:<10} {:<10} {:<10}".format('eval', 'fun_val', '||step||', 'gradient', 'dampening_factor'), 16 | '{:<40}'.format('-'*60)] 17 | 18 | 19 | def levenberg_marquardt(x, func, deriv, fletcher_modif=False, max_fun_eval=100, 20 | eps_1=1e-8, eps_2=1e-8, tol_fun=1e-12, 21 | full_output=False, mu=None, d_diag=None, 22 | args=(), verbose=False): 23 | """ 24 | Optimize function using the Levenberg-Marquardt algorithm 25 | 26 | Parameters 27 | ---------- 28 | x : array-like 29 | 1D array containing the decision variables. 30 | func : callable 31 | function with signature func(x, *args), which returns a n_data-sized 32 | vector of residuals (x_data - x_model). 33 | deriv : callable 34 | function that calculates the derivative of func with respect to x. 35 | It has the same signature as func, and returns a n_data x n_x matrix 36 | containing the jacobian of func 37 | fletcher_modif : bool, optional 38 | DESCRIPTION. The default is False. 39 | max_fun_eval : int, optional 40 | maximum number of function evaluations. The default is 100. 41 | eps_1 : float, optional 42 | stoping criteria for the gradient. The default is 1e-8. 43 | eps_2 : float, optional 44 | stopping criterion for step size. The default is 1e-8. 45 | tol_fun : float, optional 46 | stoping criterion for the objective function . The default is 1e-12. 47 | full_output : bool, optional 48 | If true, the returned result includes optimum x, covariance of x 49 | and a dictionary with details about the optimization. 50 | The default is False. 51 | lambd_zero : float, optional 52 | multiplier for the first value of mu. The default is 1e-2. 53 | args : tuple, optional 54 | additional arguments to pass to func and deriv callables. 55 | The default is (). 56 | verbose : bool, optional 57 | If True, a table with the current value of the objective, gradient 58 | and step size is shown as the optimization proceeds. 59 | The default is False. 60 | 61 | Returns 62 | ------- 63 | TYPE 64 | DESCRIPTION. 65 | 66 | """ 67 | 68 | nu = 2 69 | beta = nu 70 | x = asarray(x) 71 | 72 | num_iter = 0 73 | 74 | fun = func(x, *args) 75 | jac = deriv(x, *args) 76 | 77 | a_matrix = inner(jac, jac) # Hessian approximation 78 | b_vector = inner(jac, fun) # gradient 79 | 80 | if d_diag is None: 81 | if fletcher_modif: 82 | d_scaling = norm(jac, axis=1) 83 | d_scaling = max(d_scaling) * ones_like(x) 84 | else: 85 | d_scaling = ones_like(x) 86 | else: 87 | d_scaling = d_diag 88 | 89 | if mu is None: 90 | mu = 1e-2 * max(diag(a_matrix)) # after Nielsen (1999) 91 | 92 | num_feval = 0 93 | 94 | if verbose: 95 | print('\n'.join(lm_header)) 96 | print("{:<7} {:<10.3e} {:<10} {:<10.3e} {:<10.3e}".format( 97 | num_feval, norm(fun)**2, '---', norm(b_vector), mu)) 98 | 99 | while num_feval < max_fun_eval: 100 | d_diag = diag(d_scaling) 101 | lm_step = solve(a_matrix + mu * inner(d_diag.T, d_diag), -b_vector) 102 | 103 | if norm(lm_step) < eps_2 * norm(x): 104 | reason = 'Small step' 105 | break 106 | 107 | x_new = x + lm_step 108 | fun_new = func(x_new, *args) 109 | jac_new = deriv(x_new, *args) 110 | 111 | num_feval += 1 112 | 113 | sq_old = norm(fun)**2 114 | sq_new = norm(fun_new)**2 115 | rho = (sq_old - sq_new) / (inner(lm_step, mu * lm_step - b_vector)) 116 | 117 | if rho > 0: # update x if function is decreased 118 | x = x_new 119 | fun = fun_new 120 | jac = jac_new 121 | 122 | a_matrix = inner(jac, jac) # Hessian approximation 123 | b_vector = inner(jac, fun) 124 | 125 | if fletcher_modif: 126 | d_scaling = maximum(norm(jac, axis=1), d_scaling) 127 | 128 | num_iter += 1 129 | 130 | if norm(b_vector) < eps_1: 131 | reason = 'Small gradient' 132 | break 133 | 134 | mu = mu * max(1/3, 1 - (beta - 1) * (2*rho - 1)**3) # After Nielsen 135 | nu = 2 136 | else: 137 | mu = mu * nu # decrease step size 138 | nu = 2 * nu 139 | 140 | beta = nu 141 | 142 | if verbose: 143 | if num_feval % 50 == 0: 144 | print('\n'.join(lm_header)) 145 | 146 | print("{:<7} {:<10.3e} {:<10.3e} {:<10.3e} {:<10.3e}".format( 147 | num_feval, sq_new, norm(lm_step), norm(b_vector), mu)) 148 | 149 | if norm(fun_new) < tol_fun: 150 | break 151 | reason = 'Value of the objective function lower than tolerance' 152 | 153 | else: 154 | reason = 'Maximum iterations exceeded' 155 | 156 | covar_x = inv(a_matrix) 157 | 158 | if verbose: 159 | print('{:<40}'.format('-'*60)) 160 | print() 161 | 162 | if full_output: 163 | lm_par = {'d_diag': d_scaling, 'mu': mu} 164 | if max_fun_eval == 0: 165 | output_dict = {'x': x, 'fun': fun, 'jac': jac, 166 | 'num_iter': num_iter, 'num_fun_eval': num_feval, 167 | 'lm_params': lm_par} 168 | else: 169 | output_dict = {'x': x, 'fun': fun, 'jac': jac, 170 | 'norm_step': norm(lm_step), 171 | 'stop_criterion': reason, 'num_iter': num_iter, 172 | 'num_fun_eval': num_feval, 'lm_params': lm_par} 173 | 174 | return x, covar_x, output_dict 175 | else: 176 | return x 177 | -------------------------------------------------------------------------------- /doc/online_docs/references.bib: -------------------------------------------------------------------------------- 1 | @article{Casas-Orozco2020, 2 | address = {Paper 235e}, 3 | author = {Casas-Orozco, Daniel and Laky, Daniel and Wang, Vivian and Abdi, Mesfin and Feng, X. and Wood, E. and Laird, Carl and Reklaitis, Gintaras V. and Nagy, Zoltan K.}, 4 | doi = {10.1016/j.compchemeng.2021.107408}, 5 | file = {:C$\backslash$:/Users/dcasasor/AppData/Local/Mendeley Ltd./Mendeley Desktop/Downloaded/Casas-Orozco et al. - 2021 - PharmaPy An object-oriented tool for the development of hybrid pharmaceutical flowsheets.pdf:pdf}, 6 | issn = {00981354}, 7 | journal = {Computers {\&} Chemical Engineering}, 8 | mendeley-groups = {postdoc/process,postdoc/CHE55400}, 9 | month = {oct}, 10 | pages = {107408}, 11 | title = {{PharmaPy: An object-oriented tool for the development of hybrid pharmaceutical flowsheets}}, 12 | url = {https://linkinghub.elsevier.com/retrieve/pii/S0098135421001861}, 13 | volume = {153}, 14 | year = {2021} 15 | } 16 | @article{Laky2022, 17 | author = {Laky, Daniel Joseph and Casas-Orozco, Daniel and Laird, Carl D. and Reklaitis, Gintaras V. and Nagy, Zoltan K.}, 18 | doi = {10.1021/acs.iecr.2c01636}, 19 | file = {:C$\backslash$:/Users/dcasasor/AppData/Local/Mendeley Ltd./Mendeley Desktop/Downloaded/Laky et al. - 2022 - Simulation-optimization framework for the digital design of pharmaceutical processes using Pyomo and PharmaPy.pdf:pdf}, 20 | issn = {0888-5885}, 21 | journal = {Industrial {\&} Engineering Chemistry Research}, 22 | month = {oct}, 23 | pages = {16128--16140}, 24 | title = {{Simulation–Optimization Framework for the Digital Design of Pharmaceutical Processes Using Pyomo and PharmaPy}}, 25 | url = {https://pubs.acs.org/doi/10.1021/acs.iecr.2c01636}, 26 | volume = {61}, 27 | year = {2022} 28 | } 29 | @article{Casas-Orozco2023, 30 | author = {Casas-Orozco, Daniel and Laky, Daniel Joseph and Mackey, Jaron and Reklaitis, Gintaras V. and Nagy, Zoltan K.}, 31 | file = {:C$\backslash$:/Users/dcasasor/AppData/Local/Mendeley Ltd./Mendeley Desktop/Downloaded/Casas-Orozco et al. - 2023 - Reaction kinetics determination and uncertainty analysis for the synthesis of the cancer drug lomustine.pdf:pdf}, 32 | journal = {Chemical Engineering Science}, 33 | pages = {1--13}, 34 | title = {{Reaction kinetics determination and uncertainty analysis for the synthesis of the cancer drug lomustine}}, 35 | year = {2023} 36 | } 37 | @article{Casas-Orozco2023a, 38 | author = {Casas-Orozco, Daniel and Laky, Daniel Joseph and Wang, Vivian and Abdi, Mesfin and Feng, X. and Wood, Erin and Reklaitis, Gintaras V. and Nagy, Zoltan K.}, 39 | journal = {AIChE Journal}, 40 | title = {{Techno-economic analysis of dynamic, end-to-end optimal pharmaceutical campaign manufacturing using PharmaPy}}, 41 | year = {2023} 42 | } 43 | @incollection{Casas-Orozco2021, 44 | address = {Amsterdam}, 45 | author = {Casas-Orozco, Daniel and Laky, Daniel Joseph and Wang, Vivian and Abdi, M. and Feng, X. and Wood, E. and Reklaitis, Gintaras V. and Laird, Carl D. and Nagy, Zoltan K.}, 46 | booktitle = {31st European Symposium on Computer Aided Process Engineering}, 47 | doi = {10.1016/B978-0-323-85159-6.50355-9}, 48 | editor = {Turkay, Metin and Gani, Rafiqul}, 49 | file = {:C$\backslash$:/Users/dcasasor/AppData/Local/Mendeley Ltd./Mendeley Desktop/Downloaded/Casas-Orozco et al. - 2021 - Application of PharmaPy in the digital design of the manufacturing process of an active pharmaceutical ingr.pdf:pdf}, 50 | isbn = {978-0-323-98325-9}, 51 | pages = {333--339}, 52 | publisher = {Elsevier}, 53 | title = {{Application of PharmaPy in the digital design of the manufacturing process of an active pharmaceutical ingredient}}, 54 | year = {2021} 55 | } 56 | @inproceedings{Casas-Orozco2022, 57 | author = {Casas-Orozco, Daniel and Mackey, Jaron and Akturk, Ilke and Reklaitis, Gintaras V. and Nagy, Zoltan K.}, 58 | booktitle = {33rd European Symposium on Computer Aided Process Engineering}, 59 | title = {{Extended Multiple-Curve Resolution framework for the calibration of first-principles models}}, 60 | year = {2022} 61 | } 62 | @inproceedings{Laky2022a, 63 | address = {Kyoto, Japan}, 64 | author = {Laky, Daniel Joseph and Casas-Orozco, Daniel and Rossi, Francesco and Reklaitis, Gintaras V. and Nagy, Zoltan K.}, 65 | booktitle = {14th International Symposium on Process Systems Engineering – PSE 2021+}, 66 | doi = {10.1016/B978-0-323-85159-6.50355-9}, 67 | file = {:C$\backslash$:/Users/dcasasor/AppData/Local/Mendeley Ltd./Mendeley Desktop/Downloaded/Laky et al. - 2022 - Determination of probabilistic design spaces in the hybrid manufacture of an active pharmaceutical ingredient using.pdf:pdf}, 68 | pages = {2131--2136}, 69 | title = {{Determination of probabilistic design spaces in the hybrid manufacture of an active pharmaceutical ingredient using the Python-based framework PharmaPy}}, 70 | year = {2022} 71 | } 72 | @incollection{Laky2022b, 73 | abstract = {Over the past two decades, pharmaceutical manufacturing has seen significant modernization due to the digitalization of manufacturing as more manufacturers adopt Industry 4.0 standards. The FDA launched the quality-by-design and quality-by-control initiatives as steps to ensure this digitalization in pharmaceutical manufacturing may proceed alongside regulatory guidelines for the transition from traditional batchwise manufacturing to end-to-end continuous or hybrid batch-continuous manufacturing schemes. Adoption of a process digital twin is a pivotal step in providing quantitative justification for updating or designing new processing routes for pharmaceutical materials, especially when implementing online process control. This article presents a framework for the integrated design of continuous and hybrid systems for the synthesis-crystallization and filtration-drying steps of paracetamol using digital twins to simulate process operation and control.}, 74 | author = {Laky, Daniel J. and Casas-Orozco, Daniel and Destro, Francesco and Barolo, Massimiliano and Reklaitis, Gintaras V. and Nagy, Zoltan K.}, 75 | booktitle = {Springer Optimization and Its Applications}, 76 | doi = {10.1007/978-3-030-90924-6_10}, 77 | file = {:C$\backslash$:/Users/dcasasor/AppData/Local/Mendeley Ltd./Mendeley Desktop/Downloaded/Laky et al. - 2022 - Integrated Synthesis, Crystallization, Filtration, and Drying of Active Pharmaceutical Ingredients A Model-Based Di.pdf:pdf}, 78 | isbn = {9783030909246}, 79 | issn = {19316836}, 80 | keywords = {Continuous manufacturing,Digitalization,Industry 4.0,Process control,Process design,Quality-by-control,Quality-by-design}, 81 | pages = {253--287}, 82 | title = {{Integrated Synthesis, Crystallization, Filtration, and Drying of Active Pharmaceutical Ingredients: A Model-Based Digital Design Framework for Process Optimization and Control}}, 83 | url = {https://link.springer.com/10.1007/978-3-030-90924-6{\_}10}, 84 | volume = {189}, 85 | year = {2022} 86 | } 87 | 88 | -------------------------------------------------------------------------------- /PharmaPy/animate_profiles.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Thu Oct 15 15:01:59 2020 5 | 6 | @author: dcasasor 7 | """ 8 | 9 | import matplotlib.pyplot as plt 10 | # from matplotlib.animation import FuncAnimation, FFMpegWriter 11 | from matplotlib.animation import FuncAnimation 12 | import numpy as np 13 | 14 | 15 | def check_inputs(states, sep_states, width=4): 16 | if isinstance(states, list) or isinstance(states, tuple): 17 | list_states = [] 18 | for state in states: 19 | if state.ndim == 1: 20 | list_states.append(np.atleast_2d(state).T) 21 | else: 22 | list_states.append(state) 23 | 24 | states = list_states 25 | 26 | else: 27 | 28 | if states.ndim == 1: 29 | states = [np.atleast_2d(states).T] 30 | else: 31 | states = [states] 32 | 33 | if sep_states: 34 | fig, axes = plt.subplots(len(states), 1, 35 | figsize=(width, width/1.6*len(states))) 36 | axes = np.atleast_1d(axes) 37 | else: 38 | fig, axes = plt.subplots() 39 | axes = [axes] 40 | 41 | return states, fig, axes 42 | 43 | 44 | def anim_func(time, states, name_file=None, legend=None, names_y=None, 45 | title=None): 46 | """ 47 | 48 | 49 | Parameters 50 | ---------- 51 | time : array-like 52 | array containing the simulation time values 53 | states : list of array-like, array-like 54 | states to be animated. To plot several states, pass an array of each 55 | state in a list or tuple. Otherwise, pass the array corresponding to 56 | the state of interest 57 | name_file : str, optional 58 | Name of the video file. The default is None. 59 | legend : tuple of str, optional 60 | The default is None. 61 | names_y : tuple of str, optional 62 | Names of the states to be animated. The default is None. 63 | title : str, optional 64 | Title of the plot. The default is None. 65 | 66 | Returns 67 | ------- 68 | animation : TYPE 69 | DESCRIPTION. 70 | fig : TYPE 71 | DESCRIPTION. 72 | axes : TYPE 73 | DESCRIPTION. 74 | 75 | """ 76 | 77 | states, fig, axes = check_inputs(states, True) 78 | 79 | if legend is None: 80 | legend = [None] * len(states) 81 | else: 82 | legend = legend 83 | 84 | if name_file is None: 85 | name_file = 'anim' 86 | 87 | fig.suptitle(title) 88 | 89 | all_lines = [] 90 | time_diff = time[-1] - time[0] 91 | xlim = (time[0] - time_diff*0.03, time[-1] + time_diff*0.03) 92 | for idx_state, state in enumerate(states): 93 | state_range = state.max() - state.min() 94 | ylim = (state.min() - state_range*0.03, 95 | state.max() + state_range*0.03) 96 | 97 | axes[idx_state].set_xlim(xlim) 98 | axes[idx_state].set_ylim(ylim) 99 | 100 | if names_y is not None: 101 | axes[idx_state].set_ylabel(names_y[idx_state]) 102 | 103 | lines = [] 104 | 105 | num_lines = state.shape[1] 106 | for i in range(num_lines): 107 | line_obj = axes[idx_state].plot([], [], lw=1.5)[0] 108 | lines.append(line_obj) 109 | 110 | all_lines.append(lines) 111 | 112 | time_tag = axes[0].text( 113 | 1, 1.04, '$time = {:.1f}$ s'.format(time[0]), 114 | horizontalalignment='right', 115 | transform=axes[0].transAxes) 116 | 117 | axes[-1].set_xlabel('time (s)') 118 | 119 | def fun_anim(ind): 120 | for idx_state, state in enumerate(states): 121 | time_plot = time[:ind] 122 | states_plot = state[:ind] 123 | lines_state = all_lines[idx_state] 124 | 125 | for idx, col in enumerate(states_plot.T): # index for each state 126 | lines_state[idx].set_data(time_plot, col) 127 | 128 | if legend[idx_state] is not None: 129 | lines_state[idx].set_label(legend[idx_state][idx]) 130 | 131 | time_tag.set_text('$time = {:.1f}$ s'.format(time[ind])) 132 | 133 | if legend[idx_state] is not None: 134 | axes[idx_state].legend(loc='best') 135 | 136 | fig.tight_layout() 137 | 138 | animation = FuncAnimation(fig, fun_anim, 139 | frames=range(1, len(time)), repeat=True) 140 | 141 | writer = 'ffmpeg' 142 | suff = '.mp4' 143 | 144 | # writer = 'imagemagick' 145 | # suff = '.avi' 146 | 147 | animation.save(name_file + suff, writer=writer) 148 | 149 | return animation, fig, axes 150 | 151 | 152 | def anim_multidim(time, indep_vble, data, filename=None, step_data=1, 153 | title=None, xlabel=None, ylabel=None, invert_x=False, 154 | time_unit=None, legend=None): 155 | """ 156 | Animate states that depend on time and position. This typically arises when 157 | analyzing the outputs of a PDE model 158 | 159 | Parameters 160 | ---------- 161 | time : array-like 162 | Time array from the simulator. 163 | indep_vble : numpy array 164 | array with the values of the spatial coordinate. 165 | data : numpy array 166 | independent variable to plot, with time advancing with rows, and space 167 | advancing with the columns.. 168 | filename : str, optional 169 | Name of the output video file. The default is None. 170 | step_data : int, optional 171 | 'data' array will be used as data[::step_data]. The default is 1. 172 | title : str, optional 173 | Figure title. The default is None. 174 | xlabel : str, optional 175 | DESCRIPTION. The default is None. 176 | ylabel : str, optional 177 | DESCRIPTION. The default is None. 178 | time_unit : str, optional 179 | If None, time unit is 's' (seconds). The default is None. 180 | legend : list of str, optional 181 | list of legends. The default is None. 182 | 183 | Returns 184 | ------- 185 | TYPE 186 | DESCRIPTION. 187 | 188 | """ 189 | 190 | if filename is None: 191 | filename = 'anim' 192 | 193 | if time_unit is None: 194 | time_unit = 's' 195 | 196 | data, fig_anim, (ax_anim,) = check_inputs(data, sep_states=False) 197 | 198 | indep_diff = np.ptp(indep_vble) 199 | ax_anim.set_xlim(indep_vble.min() - 0.03*indep_diff, 200 | indep_vble.max() + 0.03*indep_diff) 201 | fig_anim.suptitle(title) 202 | 203 | fig_anim.subplots_adjust(left=0, bottom=0, right=1, top=1, 204 | wspace=None, hspace=None) 205 | 206 | data_min = np.vstack(data).min() 207 | data_max = np.vstack(data).max() 208 | 209 | data_diff = data_max - data_min 210 | 211 | ax_anim.set_ylim(data_min - 0.03*data_diff, data_max + data_diff*0.03) 212 | 213 | ax_anim.set_xlabel(xlabel) 214 | ax_anim.set_ylabel(ylabel) 215 | 216 | def func_data(ind): 217 | data_merged = [] 218 | for idx_data, array in enumerate(data): 219 | data_merged.append(array[ind]) 220 | 221 | if legend is not None: 222 | ax_anim.legend(legend) 223 | 224 | data_merged = np.column_stack(data_merged) 225 | return data_merged 226 | 227 | lines = ax_anim.plot(indep_vble, func_data(0)) 228 | if invert_x: 229 | ax_anim.set_xlim(ax_anim.get_xlim()[::-1]) 230 | 231 | time_tag = ax_anim.text( 232 | 1, 1.04, '$time = {:.1f}$ {}'.format(time[0], time_unit), 233 | horizontalalignment='right', 234 | transform=ax_anim.transAxes) 235 | 236 | def func_anim(ind): 237 | f_vals = func_data(ind) 238 | for idx_line, line in enumerate(lines): 239 | line.set_ydata(f_vals[:, idx_line]) 240 | 241 | # ax_anim.legend() 242 | fig_anim.tight_layout() 243 | 244 | time_tag.set_text('$t = {:.1f}$ {}'.format(time[ind], time_unit)) 245 | 246 | frames = np.arange(0, len(time), step_data) 247 | animation = FuncAnimation(fig_anim, func_anim, frames=frames, 248 | repeat=True) 249 | 250 | writer = 'ffmpeg' 251 | suff = '.mp4' 252 | 253 | animation.save(filename + suff, writer=writer) 254 | 255 | return animation, fig_anim, ax_anim 256 | -------------------------------------------------------------------------------- /PharmaPy/ThreePhaseSettler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Mar 13 10:51:16 2023 4 | 5 | @author: vsundark 6 | """ 7 | 8 | import numpy as np 9 | from assimulo.problem import Implicit_Problem 10 | from PharmaPy.Phases import classify_phases 11 | from PharmaPy.Connections import get_inputs_new 12 | from PharmaPy.Streams import LiquidStream, SolidStream 13 | from PharmaPy.MixedPhases import Slurry, SlurryStream 14 | from PharmaPy.Results import DynamicResult 15 | from PharmaPy.Plotting import plot_distrib 16 | 17 | from assimulo.solvers import IDA 18 | 19 | import scipy.optimize 20 | import scipy.sparse 21 | 22 | # from itertools import cycle 23 | from matplotlib.ticker import AutoMinorLocator, MaxNLocator 24 | 25 | class ThreePhaseSettler: 26 | def __init__(self, d_impeller, d_vessel, 27 | target_compound, agit_rate=1000/60): 28 | 29 | self.d_impeller = d_impeller 30 | self.d_vessel = d_vessel 31 | self.target_compound = target_compound 32 | self.agit_rate = agit_rate 33 | 34 | self.outputs = None 35 | self.oper_mode = 'Continuous' 36 | 37 | def nomenclature(self): 38 | self.name_states = ['solid_conc'] 39 | self.states_di = {'solid_conc': 40 | {'dim': 1, 'units': 'kg API/kg solvent'}} 41 | self.states_dict = {'Inlet': self.states_di} 42 | self.fstates_di = {} 43 | 44 | self.names_states_in = ['solid_conc'] 45 | 46 | @property 47 | def Phases(self): 48 | return self._Phases 49 | 50 | @Phases.setter 51 | def Phases(self, phases): 52 | if not isinstance(phases, (list, tuple)): 53 | phases = [phases] 54 | 55 | self._Phases = phases 56 | classify_phases(self) 57 | 58 | if self.Liquid_1.getDensity() > self.Liquid_2.getDensity(): 59 | self.BotLiq = self.Liquid_1 60 | self.TopLiq = self.Liquid_2 61 | else: 62 | self.BotLiq = self.Liquid_2 63 | self.TopLiq = self.Liquid_1 64 | path = self.Inlet_bot_phase.path_data 65 | self.TopPhase = Slurry(path) 66 | self.TopPhase.Phases = [self.TopLiq, self.Solid_1] 67 | self.BotPhase = Slurry(path) 68 | self.BotPhase.Phases = [self.BotLiq, self.Solid_2] 69 | self.holdup_top = self.TopLiq.vol 70 | self.holdup_bot = self.BotLiq.vol 71 | 72 | self.nomenclature() 73 | 74 | @property 75 | def Inlet(self): 76 | return self._Inlet 77 | 78 | @Inlet.setter 79 | def Inlet(self, Inlet): 80 | self._Inlet = Inlet 81 | self.name_species = self.Inlet.Liquid_1.name_species 82 | self.index = self.name_species.index(self.target_compound) 83 | 84 | @property 85 | def Inlet_bot_phase(self): 86 | return self._Inlet_bot_phase 87 | 88 | @Inlet_bot_phase.setter 89 | def Inlet_bot_phase(self, inlet_bot_phase): 90 | self._Inlet_bot_phase = inlet_bot_phase 91 | 92 | def flatten_states(self): 93 | pass 94 | 95 | def get_input_feed(self, time): 96 | inputs = get_inputs_new(time, self.Inlet, self.states_dict) 97 | return inputs 98 | 99 | def get_input_bot_phase(self, time): 100 | inputs = get_inputs_new(time, self.Inlet_bot_phase, self.states_dict) 101 | return inputs 102 | 103 | def unit_model(self, time, states, d_states): 104 | material = self.material_balances(time, states) 105 | material = material - d_states 106 | return material 107 | 108 | def design_space_check(self,): 109 | 110 | # Calculate mean diameter 111 | distrib = self.Inlet.Solid_1.distrib 112 | x_distrib = self.Inlet.Solid_1.x_distrib 113 | perc_vol_basis = np.percentile(distrib*x_distrib**3*1e-9, 50, 114 | method='closest_observation') #distrib**3 to get volume basis, 1e-9 to convert to m3 and reduce number magnitude 115 | D50_index = np.where(distrib*x_distrib**3*1e-9==perc_vol_basis)[0][0] 116 | D50 = x_distrib[D50_index] 117 | 118 | d_particle = D50*1e-6 119 | 120 | # d_particle = np.dot(self.Inlet.Solid_1.distrib/sum(self.Inlet.Solid_1.distrib), 121 | # self.Inlet.Solid_1.x_distrib/1e6) 122 | viscosity_top = self.TopPhase.Liquid_1.getViscosity() 123 | density_top = self.TopPhase.Liquid_1.getDensity() 124 | density_particle = self.TopPhase.Solid_1.getDensity() 125 | flowrate_feed = self.Inlet.Liquid_1.vol_flow 126 | gamma_top = self.TopPhase.Liquid_1.getSurfTension() 127 | g = 9.81 # m/s2 128 | 129 | # 1st criterion 130 | vt_top = g*d_particle**2*(density_particle-density_top)/(18*viscosity_top) 131 | tau_top = self.holdup_top/flowrate_feed 132 | height_toplayer = self.holdup_top/(np.pi*self.d_vessel**2/4) 133 | vt_crit = 10* height_toplayer/tau_top 134 | crit1 = vt_top > vt_crit 135 | 136 | viscosity_bot = self.BotPhase.Liquid_1.getViscosity() 137 | density_bot = self.TopPhase.Liquid_1.getDensity() 138 | flowrate_bot = self.Inlet_bot_phase.vol_flow 139 | gamma_bot = self.BotPhase.Liquid_1.getSurfTension() 140 | gamma_int = np.abs(gamma_bot-gamma_top) 141 | 142 | d_impeller = self.d_impeller 143 | agit_rate = self.agit_rate 144 | 145 | # 2nd criterion 146 | Bo = (density_particle-density_bot)*g*d_particle**2/gamma_int 147 | Ea = np.pi*d_particle**2/4*gamma_int 148 | Ek = 0.5*(density_particle*np.pi*d_particle**3/6)*(d_impeller/2*(agit_rate/2*np.pi)) 149 | crit2 = Bo>1 or Ek>Ea 150 | 151 | def N_cr_cal(N): 152 | N = np.abs(N) 153 | if len(N): 154 | N=N[-1] 155 | Re = density_bot*N*d_impeller**2/viscosity_bot 156 | Fr = N**2*d_impeller/g 157 | zeta = 0.85 158 | return zeta - d_impeller*Fr*(12.9*(1-Re**-0.11*zeta**0.17)-4.27) 159 | 160 | # 3rd criterion 161 | N_cr = scipy.optimize.fsolve(N_cr_cal, x0=agit_rate)[0] 162 | N_js = (234*(d_impeller)**(-2/3)*(d_particle)**(1/3) 163 | *((density_particle-density_bot)/density_bot)**(2/3) 164 | *(viscosity_bot*1000/(density_bot/1000))**(-1/9)/60) 165 | crit3 = agit_rateN_js 166 | 167 | # Test 168 | if crit1 and crit2 and crit3: 169 | print('TPS: operating point is in design space') 170 | else: 171 | print('TPS: operating point is not in design space') 172 | 173 | return 174 | 175 | def material_balances(self, time, solid_conc): 176 | density_top = self.TopPhase.Liquid_1.getDensity() 177 | flowrate_feed = self.Inlet.Liquid_1.vol_flow 178 | density_bot = self.BotPhase.Liquid_1.getDensity() 179 | flowrate_bot = self.Inlet_bot_phase.vol_flow 180 | 181 | input_feed = self.get_input_feed(time)['Inlet'] 182 | solid_conc_feed = input_feed['solid_conc'] 183 | if isinstance(solid_conc_feed, (list, np.ndarray)): 184 | solid_conc_feed = solid_conc_feed[solid_conc_feed!=0][0] 185 | 186 | d_solid_conc_dt = ((1/(self.holdup_bot * density_bot)) 187 | *(solid_conc_feed*flowrate_feed*density_top 188 | -solid_conc*flowrate_bot*density_bot)) 189 | 190 | return d_solid_conc_dt 191 | 192 | def energy_balances(self, time, temp, mole_frac): 193 | pass 194 | return 195 | 196 | def solve_unit(self, runtime=None, t0=0, verbose=True): 197 | # Check if operating point is in design space 198 | self.design_space_check() 199 | 200 | init_states = (self.BotPhase.Solid_1.mass 201 | / (self.BotPhase.Liquid_1.mass + self.BotPhase.Solid_1.mass)) 202 | init_derivative = self.material_balances(time=0, 203 | solid_conc=init_states) 204 | 205 | problem = Implicit_Problem(self.unit_model, init_states, 206 | init_derivative, t0) 207 | solver = IDA(problem) 208 | 209 | if not verbose: 210 | solver.verbosity = 50 211 | 212 | time, states, d_states = solver.simulate(runtime) 213 | self.retrieve_results(time, states) 214 | return time, states, d_states 215 | 216 | def retrieve_results(self, time, states): 217 | time = np.asarray(time) 218 | self.timeProf = time 219 | 220 | indexes = {key: self.states_di[key].get('index', None) 221 | for key in self.name_states} 222 | 223 | dp = {'time': time, 'solid_conc': states} 224 | self.result = DynamicResult(di_states=self.states_di, di_fstates=None,**dp) 225 | self.outputs = dp 226 | 227 | # Outlet stream 228 | path = self.Inlet_bot_phase.path_data 229 | solid_conc = self.result.solid_conc[-1][-1] 230 | solid_mass_flow = solid_conc*self.Inlet_bot_phase.vol_flow*self.Inlet_bot_phase.getDensity() 231 | 232 | OutletLiq = LiquidStream( 233 | path, mass_frac=self.Inlet_bot_phase.mass_frac, vol_flow=self.Inlet_bot_phase.vol_flow) 234 | OutletSolid = SolidStream( 235 | path, mass_flow=solid_mass_flow, mass_frac=self.Inlet.Solid_1.mass_frac, 236 | temp=self.Inlet.Solid_1.temp, distrib=self.Inlet.Solid_1.distrib, x_distrib=self.Inlet.Solid_1.x_distrib) 237 | self.Outlet = SlurryStream(path) 238 | self.Outlet.Phases = [OutletLiq, OutletSolid] 239 | -------------------------------------------------------------------------------- /PharmaPy/Interpolation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Tue Apr 14 22:06:49 2020 5 | 6 | @author: casas100 7 | """ 8 | 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | from scipy.interpolate import CubicSpline 12 | from scipy.special import comb 13 | 14 | 15 | def local_newton_interpolation(time, t_data, y_data, num_points=3): 16 | idx_time = np.argmin(abs(time - t_data)) 17 | 18 | idx_lower = max(0, idx_time - 1) 19 | idx_upper = min(len(t_data) - 1, idx_lower + num_points) 20 | 21 | t_interp = t_data[idx_lower:idx_upper] 22 | y_interp = y_data[idx_lower:idx_upper] 23 | 24 | # Newton interpolation 25 | interp = NewtonInterpolation(t_interp, y_interp) 26 | y_target = interp.evalPolynomial(time) 27 | 28 | return y_target 29 | 30 | 31 | class NewtonInterpolation: 32 | def __init__(self, x_data, y_data): 33 | 34 | self.x_data = x_data 35 | self.y_data = y_data 36 | 37 | self.coeff = self.__getCoefficients() 38 | 39 | def __getCoefficients(self): 40 | """ 41 | x: list or np array contanining x data points 42 | y: list or np array contanining y data points 43 | """ 44 | 45 | n = len(self.x_data) 46 | x = self.x_data 47 | 48 | a = np.copy(self.y_data) 49 | 50 | if a.ndim == 1: 51 | for k in range(1, n): 52 | a[k:n] = (a[k:n] - a[k - 1]) / (x[k:n] - x[k - 1]) 53 | 54 | else: 55 | for k in range(1, n): 56 | coeff = (a[k:n] - a[k - 1]).T / (x[k:n] - x[k - 1]) 57 | a[k:n] = coeff.T 58 | 59 | return a 60 | 61 | def evalPolynomial(self, x): 62 | """ 63 | x_data: data points at x 64 | y_data: data points at y 65 | x: evaluation point(s) 66 | """ 67 | 68 | a = self.coeff 69 | n = len(self.x_data) - 1 # Degree of polynomial 70 | p = a[n] 71 | 72 | if isinstance(x, float) or isinstance(x, int): 73 | x_eval = x 74 | elif a.ndim == 1: 75 | x_eval = x 76 | else: 77 | x_eval = x[..., np.newaxis] 78 | 79 | for k in range(1, n + 1): 80 | p = a[n - k] + (x_eval - self.x_data[n - k])*p 81 | 82 | return p 83 | 84 | 85 | def smoothstep(x, x_min=0, x_max=1, N=1): 86 | x = np.clip((x - x_min) / (x_max - x_min), 0, 1) 87 | 88 | result = 0 89 | for n in range(0, N + 1): 90 | result += comb(N + n, n) * comb(2 * N + 1, N - n) * (-x)**n 91 | 92 | result *= x ** (N + 1) 93 | 94 | return result 95 | 96 | 97 | # class SplineInterpolation: 98 | # def __init__(self, x_data, y_data): 99 | 100 | # self.Spline = CubicSpline(x_data, y_data) 101 | 102 | # def evalSpline(self, x): 103 | 104 | # y_interp = self.Spline(x) 105 | 106 | # return y_interp 107 | 108 | 109 | class PiecewiseLagrange: 110 | 111 | def __init__(self, time_final, y_vals, order=2, time_k=None, time_zero=0): 112 | """ Create a piecewise Lagrange interpolation object 113 | 114 | Parameters 115 | ---------- 116 | time_final : float 117 | final time 118 | y_vals : numpy array 119 | 2D array with dimensions num_intervals x order. Each row contains 120 | the values of the function at the collocation point within a given 121 | finite element 122 | order : int 123 | order of the Lagrange polynomial (1 for piecewise constant, 124 | 2 for linear...) 125 | time_k : array-like (optional) 126 | If None, the time horizon is divided in num_intervals equally 127 | spaced intervals. Otherwise, time_k represents the limits of the 128 | finite elements. 129 | time_zero : float (optional) 130 | initial time (default is zero) 131 | 132 | 133 | The interpolation algorithm is based on the work of Vassiliadis et al 134 | (Ind. Eng. Chem. Res., 1994, 33, 2111-2122) 135 | 136 | """ 137 | 138 | y_vals = np.atleast_1d(y_vals) 139 | 140 | if y_vals.ndim == 1: 141 | y_vals = y_vals.reshape(-1, order) 142 | 143 | if time_k is None: 144 | num_interv = len(y_vals) 145 | time_k = np.linspace(time_zero, time_final, num_interv + 1) 146 | else: 147 | num_interv = len(time_k) - 1 148 | time_k = np.asarray(time_k) 149 | 150 | if len(y_vals) != num_interv: 151 | raise ValueError("The number of rows in 'y_vals' must match " 152 | "the number of intervals given by the length " 153 | "of 'time_k'") 154 | 155 | delta_t = np.diff(time_k) 156 | if (delta_t < 0).any(): 157 | raise ValueError("Values in 'time_k' must be strictly increasing.") 158 | equal = np.isclose(delta_t[1:], delta_t[:-1]).all() 159 | self.equal_dt = equal 160 | 161 | self.time_k = time_k 162 | 163 | if equal: 164 | self.dt = delta_t[0] 165 | else: 166 | self.dt = delta_t 167 | 168 | self.order = order 169 | 170 | self.num_interv = num_interv 171 | self.y_vals = y_vals 172 | 173 | def evaluate_poly(self, time_eval, y_init=None): 174 | if y_init is not None: 175 | self.y_vals[0, 0] = y_init 176 | 177 | time_k = self.time_k 178 | 179 | if self.equal_dt: 180 | k = np.ceil((time_eval - time_k[0]) / self.dt).astype(int) 181 | k = np.maximum(1, k) 182 | else: 183 | k = np.searchsorted(self.time_k, time_eval, side='right') 184 | k = np.minimum(self.num_interv, k) 185 | 186 | k = np.clip(k, 1, self.num_interv) 187 | 188 | # ---------- Time normalization 189 | tau_k = (time_eval - time_k[k - 1]) / (time_k[k] - time_k[k - 1]) 190 | tau_k = tau_k[..., np.newaxis] 191 | 192 | # ---------- Build Lagrange polynomials 193 | # Intermediate collocation points 194 | colloc = np.linspace(0, 1, self.order) 195 | 196 | # Lagrange polynomials for k = 1, ..., K (all intervals) 197 | i_set = set(range(self.order)) 198 | 199 | if isinstance(time_eval, np.ndarray): 200 | ntimes = len(time_eval) 201 | 202 | poly = np.zeros((ntimes, self.order)) 203 | 204 | for i in i_set: 205 | i_pr = list(i_set.difference([i])) 206 | poly_indiv = (tau_k - colloc[i_pr]) / (colloc[i] - colloc[i_pr]) 207 | 208 | poly[:, i] = poly_indiv.prod(axis=1) 209 | 210 | u_time = np.zeros_like(time_eval, dtype=float) 211 | 212 | for ind in np.unique(k): 213 | row_map = k == ind 214 | poly_k = poly[row_map] 215 | u_time[row_map] = np.dot(poly_k, 216 | self.y_vals[ind - 1]).flatten() 217 | 218 | else: 219 | poly = np.zeros(self.order) 220 | for i in i_set: 221 | i_pr = list(i_set.difference([i])) 222 | poly_indiv = (tau_k - colloc[i_pr]) / (colloc[i] - colloc[i_pr]) 223 | 224 | poly[i] = poly_indiv.prod() 225 | 226 | u_time = np.dot(poly, self.y_vals[k - 1]) 227 | 228 | return u_time 229 | 230 | 231 | if __name__ == '__main__': 232 | from scipy.interpolate import interp1d 233 | 234 | case = 2 235 | 236 | if case == 1: 237 | 238 | x_discr = np.arange(0, 5) 239 | y_discr = np.sin(x_discr) 240 | 241 | x_interp = [0.5] 242 | interp = NewtonInterpolation(x_discr, y_discr) 243 | y_interp = interp.evalPolynomial(x_interp) 244 | 245 | plots = False 246 | 247 | if plots: 248 | x_c = np.linspace(0, 5) 249 | y_c = np.sin(x_c) 250 | 251 | plt.plot(x_discr, y_discr, 'o', mfc='None') 252 | plt.plot(x_c, y_c) 253 | 254 | plt.plot(x_interp, interp, 's', mfc='None') 255 | 256 | elif case == 2: # Saturation example 257 | sat = np.array( 258 | [0.2322061, 0.24866445, 0.25621458, 0.26201002, 0.26689287, 259 | 0.27117952, 0.27503542, 0.27856042, 0.28181078, 0.28464856]) 260 | sat_two = 1.2 * sat + 0.05 261 | 262 | sat = np.column_stack((sat, sat_two)) 263 | num_nodes = len(sat) 264 | step_grid = 0.06 / num_nodes 265 | z_sat = np.linspace(step_grid/2, 0.06 - step_grid/2, num_nodes) 266 | 267 | z_bounds = z_sat[[0, -1]] 268 | num_interp = 15 269 | nodes_interp = np.linspace(*z_bounds, num_interp) 270 | 271 | nodes_cont = np.linspace(*z_bounds, 100) 272 | 273 | scipy = False 274 | if scipy: 275 | sat_input_fit = interp1d(z_sat, sat) 276 | sat_interp_scipy = sat_input_fit(nodes_interp) 277 | sat_interp = sat_interp_scipy 278 | sat_cont = sat_input_fit(nodes_cont) 279 | else: 280 | interp = NewtonInterpolation(z_sat, sat) 281 | sat_interp_pharmapy = interp.evalPolynomial(nodes_interp) 282 | sat_interp = sat_interp_pharmapy 283 | 284 | sat_cont = interp.evalPolynomial(nodes_cont) 285 | 286 | plot = True 287 | if plot: 288 | fig, axis = plt.subplots() 289 | axis.plot(z_sat, sat, 'o', mfc='None') 290 | axis.plot(nodes_interp, sat_interp, 's', mfc='None') 291 | axis.plot(nodes_cont, sat_cont) 292 | axis.legend(('data', 'model')) 293 | -------------------------------------------------------------------------------- /PharmaPy/Calibration.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Tue Nov 3 11:29:05 2020 5 | 6 | @author: dcasasor 7 | """ 8 | 9 | import numpy as np 10 | import itertools 11 | import matplotlib.pyplot as plt 12 | from matplotlib.ticker import AutoMinorLocator 13 | 14 | 15 | class PCR_calibration: 16 | def __init__(self, data, num_comp=None, standardize=True, snv=False, 17 | y_name=None, y_suffixes=None): 18 | 19 | self.data = data 20 | self.standardize = standardize 21 | self.snv = snv 22 | 23 | data_mean = data.mean(axis=0) 24 | data_std = data.std(axis=0) 25 | 26 | if snv: 27 | self.data_centered = self.__center_data(data) 28 | else: 29 | self.data_centered = self.__center_data(data, data_mean, data_std) 30 | # self.data_centered = self.__center_data(data, None, None) 31 | 32 | self.data_mean = data_mean 33 | self.data_std = data_std 34 | 35 | (self.projections, self.explained_variance, 36 | self.svd_dict) = self.__get_projections() 37 | 38 | if num_comp is None: 39 | self.num_comp = len(self.svd_dict['sv']) 40 | else: 41 | self.num_comp = num_comp 42 | 43 | if y_name is None: 44 | y_name = 'y_' 45 | 46 | self.y_name = y_name 47 | self.y_suffixes = y_suffixes 48 | 49 | def __center_data(self, data=None, mean=None, std=None): 50 | if mean is None and std is None: 51 | mean = data.mean(axis=0) 52 | std = data.std(axis=0) 53 | 54 | data_centered = data - mean 55 | 56 | if self.snv or self.standardize: 57 | data_centered *= 1 / std 58 | 59 | return data_centered 60 | 61 | def __get_projections(self, data=None, n_comp=None): 62 | 63 | if data is None: 64 | data = self.data_centered 65 | 66 | # Perform SVD 67 | u_m, sv, v_nt = np.linalg.svd(data) 68 | v_n = v_nt.T 69 | 70 | # Percent of explained variance 71 | explained_var = sv**2 / (sv**2).sum() * 100 72 | 73 | # Store SVD in a dict 74 | v_trunc = v_nt[:len(sv)].T 75 | 76 | svd_dict = {'U': u_m, 'sv': sv, 'V': v_n, 'V_trunc': v_trunc} 77 | 78 | # Projections 79 | projections = np.dot(data, v_n) 80 | 81 | if n_comp is not None: 82 | projections = projections[:, :n_comp] 83 | 84 | return projections, explained_var, svd_dict 85 | 86 | def plot_projections(self, fig_size=None, num_comp=None): 87 | 88 | if num_comp is None: 89 | num_comp = self.num_comp 90 | 91 | comb = itertools.combinations(range(num_comp), 2) 92 | 93 | combs = [item for item in comb] 94 | num_plots = len(combs) 95 | 96 | if num_plots == 1: 97 | fig, axes = plt.subplots(figsize=fig_size) 98 | axes = np.atleast_1d(axes) 99 | else: 100 | ncols = 2 101 | nrows = num_plots // ncols + num_plots % ncols 102 | 103 | fig, axes = plt.subplots(nrows, ncols, figsize=fig_size) 104 | 105 | axes_flat = axes.flatten() 106 | num_axes = len(axes_flat) 107 | for ind in range(num_plots): 108 | axis = axes_flat[ind] 109 | pc_one, pc_two = combs[ind] 110 | 111 | my_map = plt.get_cmap('Reds') 112 | data_plot = self.projections[:, combs[ind]].T 113 | axis.scatter(data_plot[0], data_plot[1], 114 | # 'o', mfc='None', 115 | s=30/(num_axes/2), c=range(data_plot.shape[1]), 116 | cmap=my_map, 117 | marker='o', edgecolor='k') 118 | 119 | axis.text(0.5, -0.08, 'PC%i' % (pc_one + 1), 120 | transform=axis.transAxes, ha='center') 121 | 122 | axis.text(-0.08, 0.5, 'PC%i' % (pc_two + 1), rotation=90, 123 | transform=axis.transAxes, va='center') 124 | 125 | axis.spines['bottom'].set_position('zero') 126 | axis.spines['left'].set_position('zero') 127 | 128 | axis.spines['top'].set_visible(False) 129 | axis.spines['right'].set_visible(False) 130 | 131 | if num_plots < num_axes: 132 | fig.delaxes(axes_flat[-1]) 133 | 134 | fig.tight_layout() 135 | 136 | return fig, axes 137 | 138 | def get_regression(self, y_data, num_comp=None, update_instance=True): 139 | 140 | if self.y_suffixes is None: 141 | self.y_suffixes = ['%i' % num for num in range(1, len(y_data))] 142 | 143 | self.y_labels = [r'$' + self.y_name + ('{%s}' % suffix) + '$' 144 | for suffix in self.y_suffixes] 145 | 146 | if num_comp is None: 147 | num_comp = self.num_comp 148 | 149 | # scores = self.projections[:, :num_comp] 150 | scores = self.projections[:, :num_comp] 151 | 152 | scores_inv = np.linalg.pinv(scores) # pseudoinverse of scores 153 | 154 | # Regression coefficients w.r.t. principal components 155 | if y_data.ndim == 1: 156 | y_data = y_data[..., np.newaxis] 157 | 158 | y_means = y_data.mean(axis=0) 159 | y_center = y_data - y_means 160 | q_coeff = np.dot(scores_inv, y_center) 161 | 162 | regression_coeff = q_coeff 163 | 164 | self.y_data = y_data 165 | self.y_center = y_center 166 | self.y_means = y_means 167 | 168 | if update_instance: 169 | self.regression_coeff = regression_coeff 170 | self.num_comp = num_comp 171 | 172 | y_pred = self.predict(self.data) 173 | residuals = y_data - y_pred 174 | self.residuals = residuals 175 | 176 | # # Regression coefficients w.r.t. original X 177 | # regression_coeff = np.dot(self.svd_dict['V'][:, :num_comp], 178 | # q_coeff) 179 | 180 | 181 | # if update_instance: 182 | # self.regression_coeff = regression_coeff 183 | 184 | # y_pred = self.predict(self.data) 185 | # residuals = y_data - y_pred 186 | # self.residuals = residuals 187 | 188 | return regression_coeff 189 | 190 | def predict(self, inputs, num_comp=None, regression_coeff=None, 191 | full_output=False): 192 | inputs = np.atleast_2d(inputs) 193 | 194 | if self.snv: 195 | inputs_centered = self.__center_data(inputs) 196 | else: 197 | inputs_centered = self.__center_data(inputs) 198 | 199 | if regression_coeff is None: 200 | coeff = self.regression_coeff 201 | else: 202 | coeff = regression_coeff 203 | 204 | if num_comp is None: 205 | num_comp = self.num_comp 206 | 207 | p_matrix = self.svd_dict['V'][:, :num_comp] 208 | new_projections = np.dot(inputs_centered, p_matrix) 209 | 210 | resid_x = inputs_centered[0] - np.dot(new_projections[0], 211 | p_matrix.T) 212 | 213 | SPE_x = np.dot(resid_x, resid_x) 214 | print(SPE_x) 215 | 216 | # new_projections, _, di = self.__get_projections(inputs_centered, 217 | # num_comp) 218 | 219 | 220 | response = np.dot(new_projections, coeff) + self.y_means 221 | 222 | num_data = self.y_data.size 223 | 224 | if full_output: 225 | resid = response - self.y_data 226 | mse = 1 / num_data * np.dot(resid.T, resid) 227 | info_out = {'x_projected': new_projections, 'y_pred': response, 228 | 'MSE': mse} 229 | 230 | return info_out 231 | else: 232 | 233 | return response 234 | 235 | def evaluate_mse(self, num_comp=None): 236 | 237 | if num_comp is None: 238 | pc_counter = range(len(self.svd_dict['sv'])) 239 | else: 240 | pc_counter = range(num_comp) 241 | 242 | mse = [] 243 | residuals = [] 244 | 245 | n_data = np.prod(self.y_data.shape) 246 | for n_component in pc_counter: 247 | coeff = self.get_regression(self.y_data, n_component + 1, 248 | update_instance=False) 249 | 250 | pred = self.predict(self.data, regression_coeff=coeff, 251 | num_comp=n_component + 1) 252 | 253 | resid = self.y_data - pred 254 | 255 | mse_val = 1 / n_data * (resid**2).sum() 256 | 257 | mse.append(mse_val) 258 | residuals.append(resid) 259 | 260 | return mse, residuals 261 | 262 | def plot_parity(self, figsize=None): 263 | if figsize is None: 264 | figsize = (4, 3.5) 265 | fig, axis = plt.subplots(figsize=figsize) 266 | 267 | markers = ['o', 's', 'd', '*'] 268 | 269 | y_pred = self.y_data - self.residuals 270 | 271 | minim = (np.minimum(self.y_data, y_pred)).min() 272 | maxim = (np.maximum(self.y_data, y_pred)).max() 273 | 274 | range_vals = maxim - minim 275 | 276 | left_bottom = [minim - range_vals*0.01]*2 277 | right_top = [maxim + range_vals*0.01]*2 278 | 279 | axis.plot(*zip(left_bottom, right_top), '--k', alpha=0.5) 280 | 281 | for ind in range(self.y_data.shape[1]): 282 | axis.plot(self.y_data[:, ind], y_pred[:, ind], 283 | marker=markers[ind], mfc='None', ls='', 284 | label=self.y_labels[ind]) 285 | 286 | axis.legend() 287 | 288 | axis.set_xlabel('$%s{data}$' % self.y_name) 289 | axis.set_ylabel('$%s{model}$' % self.y_name) 290 | 291 | axis.xaxis.set_minor_locator(AutoMinorLocator(2)) 292 | axis.yaxis.set_minor_locator(AutoMinorLocator(2)) 293 | 294 | axis.text(1, 1.04, 'num_components = %i' % self.num_comp, 295 | transform=axis.transAxes, ha='right') 296 | 297 | return fig, axis 298 | 299 | def cross_validation(self, num_groups=10): 300 | perm = np.random.permutation(self.data.shape[0]) 301 | -------------------------------------------------------------------------------- /PharmaPy/Streams.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed May 27 10:12:13 2020 4 | 5 | @author: dcasasor 6 | """ 7 | 8 | from PharmaPy.Phases import LiquidPhase, SolidPhase, VaporPhase, classify_phases 9 | from PharmaPy.Interpolation import NewtonInterpolation 10 | from PharmaPy.Results import DynamicResult 11 | 12 | from scipy.interpolate import CubicSpline 13 | import numpy as np 14 | 15 | 16 | def Interpolation(t_data, y_data, time, newton=True, num_points=3): 17 | idx_time = np.argmin(abs(time - t_data)) 18 | 19 | idx_lower = max(0, idx_time - 1) 20 | idx_upper = min(len(t_data) - 1, idx_lower + num_points) 21 | 22 | t_interp = t_data[idx_lower:idx_upper] 23 | y_interp = y_data[idx_lower:idx_upper] 24 | 25 | # Newton interpolation (quadratic, three points) 26 | interp = NewtonInterpolation(t_interp, y_interp) 27 | y_target = interp.evalPolynomial(time) 28 | 29 | return y_target 30 | 31 | 32 | class BatchToFlowConnector: 33 | def __init__(self, cycle_time, flow_mult=1): 34 | self.flow_mult = flow_mult 35 | self.cycle_time = cycle_time 36 | 37 | self._Phases = None 38 | self._Inlet = None 39 | self.oper_mode = 'Batch' 40 | 41 | self.is_continuous = True 42 | 43 | @property 44 | def Phases(self): 45 | return self._Phases 46 | 47 | @Phases.setter 48 | def Phases(self, phases): 49 | self.has_solids = False 50 | 51 | if isinstance(phases, (list, tuple)): 52 | self._Phases = phases 53 | elif 'LiquidPhase' in phases.__class__.__name__: 54 | self._Phases = [phases] 55 | elif 'Slurry' in phases.__class__.__name__: 56 | self._Phases = phases.Phases 57 | self.has_solids = True 58 | 59 | classify_phases(self) 60 | self.nomenclature() 61 | 62 | def nomenclature(self): 63 | comp = self.Liquid_1.name_species 64 | self.states_di = { 65 | 'mass_frac': {'dim': len(comp), 'index': comp}, 66 | 'mass_flow': {'dim': 1, 'units': 'kg/s'}, 67 | 'temp': {'dim': 1, 'units': 'K'}} 68 | 69 | self.names_states_out = ('temp', 'pres', 'mass_frac', 'mass_flow') 70 | 71 | def flatten_states(self): 72 | pass 73 | 74 | def solve_unit(self): 75 | self.retrieve_results() 76 | 77 | def retrieve_results(self): 78 | 79 | fields = ('temp', 'pres', 'mass_frac', 'path_data') 80 | 81 | # if self.cycle_time is None: 82 | # self.cycle_time = self.Phases[0].time_upstream 83 | 84 | if self.has_solids: 85 | pass # TODO: add this if continuous downstream solid processing is made available 86 | else: 87 | kw_phase = {key: getattr(self.Liquid_1, key) for key in fields} 88 | 89 | kw_phase['path_thermo'] = kw_phase.pop('path_data') 90 | mass_flow = self.Liquid_1.mass / self.cycle_time * self.flow_mult 91 | outlet = LiquidStream(**kw_phase, mass_flow=mass_flow) 92 | 93 | kw_phase.pop('path_thermo') 94 | kw_phase['mass_flow'] = mass_flow 95 | 96 | # kw_phase['time'] = [self.cycle_time] 97 | kw_phase['time'] = None 98 | 99 | self.result = DynamicResult(self.states_di, **kw_phase) 100 | 101 | self.Outlet = outlet 102 | self.outputs = kw_phase 103 | 104 | 105 | class LiquidStream(LiquidPhase): 106 | def __init__(self, path_thermo=None, temp=298.15, pres=101325, 107 | mass_flow=0, vol_flow=0, mole_flow=0, 108 | controls=None, args_control=None, 109 | mass_frac=None, mole_frac=None, mass_conc=None, mole_conc=None, 110 | name_solv=None, num_interpolation_points=3, 111 | verbose=True, check_input=True): 112 | 113 | super().__init__(path_thermo, temp, pres, 114 | mass=mass_flow, vol=vol_flow, moles=mole_flow, 115 | mass_frac=mass_frac, mole_frac=mole_frac, 116 | mass_conc=mass_conc, mole_conc=mole_conc, 117 | name_solv=name_solv, 118 | verbose=verbose, check_input=check_input) 119 | 120 | self.mass_flow = self.mass 121 | self.vol_flow = self.vol 122 | self.mole_flow = self.moles 123 | 124 | self._DynamicInlet = None 125 | self.controllable = ('mass_flow', 'mole_flow', 'vol_flow', 'temp') 126 | 127 | # del self.mass 128 | # del self.vol 129 | # del self.moles 130 | 131 | # Outputs from upstream UO 132 | self.y_upstream = None 133 | self.time_upstream = None 134 | self.bipartite = None 135 | 136 | # Controls 137 | if controls is None: 138 | controls = {} 139 | else: 140 | if args_control is None: 141 | args_control = {key: () for key in controls.keys()} 142 | 143 | update_dict = {} 144 | for key, fun in controls.items(): 145 | update_dict[key] = fun(0, *args_control[key]) 146 | 147 | self.updatePhase(**update_dict) 148 | 149 | self.controls = controls 150 | self.args_control = args_control 151 | 152 | self.num_interpolation_points = num_interpolation_points 153 | 154 | @property 155 | def DynamicInlet(self): 156 | return self._DynamicInlet 157 | 158 | @DynamicInlet.setter 159 | def DynamicInlet(self, dynamic_object): 160 | dynamic_object.controllable = self.controllable 161 | dynamic_object.parent_instance = self 162 | 163 | self._DynamicInlet = dynamic_object 164 | 165 | def InterpolateInputs(self, time): 166 | if isinstance(time, (float, int)): 167 | # Assume steady state for extrapolation 168 | time = min(time, self.time_upstream[-1]) 169 | 170 | y_interpol = Interpolation(self.time_upstream, self.y_inlet, 171 | time, 172 | num_points=self.num_interpolation_points) 173 | else: 174 | interpol = CubicSpline(self.time_upstream, self.y_inlet) 175 | flags_interpol = time > self.time_upstream[-1] 176 | 177 | if any(flags_interpol): 178 | time_interpol = time[~flags_interpol] 179 | y_interp = interpol(time_interpol) 180 | 181 | y_extrapol = np.tile(y_interp[-1], 182 | (sum(flags_interpol), 1)) 183 | y_interpol = np.vstack((y_interp, y_extrapol)) 184 | else: 185 | y_interpol = interpol(time) 186 | 187 | return y_interpol 188 | 189 | def updatePhase(self, concentr=None, mass_conc=None, 190 | mass_frac=None, mole_frac=None, 191 | vol_flow=None, mass_flow=None, mole_flow=None): 192 | 193 | if vol_flow is None: 194 | vol_flow = self.vol_flow 195 | 196 | if mass_flow is None: 197 | mass_flow = self.mass_flow 198 | 199 | if mole_flow is None: 200 | mole_flow = self.mole_flow 201 | 202 | super().updatePhase(concentr, mass_conc, mass_frac, mole_frac, 203 | vol_flow, mass_flow, mole_flow) 204 | 205 | self.mass_flow = self.mass 206 | self.vol_flow = self.vol 207 | self.mole_flow = self.moles 208 | 209 | del self.mass 210 | del self.vol 211 | del self.moles 212 | 213 | def evaluate_inputs(self, time): 214 | if self.DynamicInlet is None: 215 | inputs = {} 216 | for attr in self.controllable: 217 | inputs[attr] = getattr(self, attr) 218 | 219 | else: 220 | inputs = self.DynamicInlet.evaluate_inputs(time) 221 | 222 | return inputs 223 | 224 | 225 | class SolidStream(SolidPhase): 226 | def __init__(self, path_thermo=None, temp=298.15, pres=101325, 227 | mass_flow=0, mass_frac=None, 228 | distrib=None, x_distrib=None, kv=1): 229 | 230 | super().__init__(path_thermo, temp, pres=pres, 231 | mass=mass_flow, mass_frac=mass_frac, 232 | # moments=moments, 233 | distrib=distrib, x_distrib=x_distrib, kv=kv) 234 | 235 | self.mass_flow = self.mass 236 | # self.vol_flow = self.vol 237 | self.mole_flow = self.moles 238 | 239 | # del self.mass 240 | # # del self.vol 241 | # del self.moles 242 | 243 | 244 | class VaporStream(VaporPhase): 245 | def __init__(self, path_thermo=None, temp=298.15, pres=101325, 246 | mass_flow=0, vol_flow=0, mole_flow=0, 247 | mass_frac=None, mole_frac=None, mole_conc=None, 248 | check_input=True, verbose=True): 249 | 250 | super().__init__(path_thermo, temp, pres, 251 | mass=mass_flow, vol=vol_flow, moles=mole_flow, 252 | mass_frac=mass_frac, mole_frac=mole_frac, 253 | mole_conc=mole_conc, check_input=check_input, 254 | verbose=verbose) 255 | 256 | self.mass_flow = self.mass 257 | self.vol_flow = self.vol 258 | self.mole_flow = self.moles 259 | 260 | self.controllable = ('mass_flow', 'mole_flow', 'vol_flow', 'temp') 261 | 262 | self._DynamicInlet = None 263 | 264 | # del self.mass 265 | # del self.vol 266 | # del self.moles 267 | 268 | @property 269 | def DynamicInlet(self): 270 | return self._DynamicInlet 271 | 272 | @DynamicInlet.setter 273 | def DynamicInlet(self, dynamic_object): 274 | dynamic_object.controllable = self.controllable 275 | dynamic_object.parent_instance = self 276 | 277 | self._DynamicInlet = dynamic_object 278 | 279 | def evaluate_inputs(self, time): 280 | if self.DynamicInlet is None: 281 | inputs = {} 282 | for attr in self.controllable: 283 | inputs[attr] = getattr(self, attr) 284 | 285 | else: 286 | inputs = self.DynamicInlet.evaluate_inputs(time) 287 | 288 | return inputs 289 | 290 | 291 | if __name__ == '__main__': 292 | path = '../../data/evaporator/compounds_evap.json' 293 | stream_liq = LiquidStream(path) 294 | -------------------------------------------------------------------------------- /PharmaPy/Extractors.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Jun 29 14:55:22 2020 4 | 5 | @author: dcasasor 6 | """ 7 | 8 | import numpy as np 9 | from scipy.optimize import newton 10 | 11 | from PharmaPy.Commons import mid_fn 12 | from PharmaPy.Phases import LiquidPhase 13 | from PharmaPy.Streams import LiquidStream 14 | from PharmaPy.Connections import get_inputs_new 15 | 16 | from PharmaPy.Results import DynamicResult 17 | 18 | 19 | def material_setter(instance, oper_mode): 20 | name_comp = instance.name_species 21 | num_comp = len(name_comp) 22 | states_di = { 23 | 'x_heavy': {'dim': num_comp, 'type': 'alg', 'index': name_comp}, 24 | 'x_light': {'dim': num_comp, 'type': 'alg', 'index': name_comp}, 25 | 'moles_heavy': {'dim': 1, 'type': 'alg'}, 26 | 'moles_light': {'dim': 1, 'type': 'alg'}} 27 | 28 | if oper_mode == 'continuous': 29 | in_flow = instance.mole_flow 30 | else: 31 | in_flow = instance.moles 32 | 33 | out = {'in_flow': in_flow, 'temp': instance.temp, 'pres': instance.pres, 34 | 'num_comp': num_comp, 'states_di': states_di} 35 | 36 | return out 37 | 38 | 39 | class ContinuousExtractor: 40 | def __init__(self, k_fun=None, gamma_method='UNIQUAC'): 41 | 42 | self._Inlet = None 43 | 44 | self.k_fun = k_fun 45 | 46 | self.gamma_method = gamma_method 47 | 48 | self.oper_mode = 'Batch' 49 | 50 | @property 51 | def Inlet(self): 52 | return self._Inlet 53 | 54 | @Inlet.setter 55 | def Inlet(self, instance): 56 | fields = material_setter(instance, oper_mode=self.oper_mode) 57 | self._Inlet = instance 58 | 59 | self.matter = self._Inlet 60 | 61 | for key, val in fields.items(): 62 | setattr(self, key, val) 63 | 64 | def get_inputs(self): 65 | pass 66 | 67 | def material_eqn_based(self, extr, raff, x_extr, x_raff, z_i): 68 | 69 | gamma_extr = self.matter.getActivityCoeff(method=self.gamma_method, 70 | mole_frac=x_extr, 71 | temp=self.temp) 72 | 73 | gamma_raff = self.matter.getActivityCoeff(method=self.gamma_method, 74 | mole_frac=x_raff, 75 | temp=self.temp) 76 | 77 | global_bce = 1 - extr - raff 78 | comp_bces = z_i - extr*x_extr - raff*x_raff 79 | equilibria = x_extr * gamma_extr - x_raff * gamma_raff 80 | 81 | diff_frac = np.sum(x_extr - x_raff) 82 | args_mid = np.array([raff, diff_frac, raff - 1]) 83 | vap_flow = mid_fn(args_mid) 84 | 85 | balance = np.concatenate( 86 | (np.array([global_bce]), 87 | comp_bces, equilibria, 88 | np.array([vap_flow])) 89 | ) 90 | 91 | return balance 92 | 93 | def material_balance(self, phi_seed, x_1_seed, x_2_seed, z_i, temp): 94 | 95 | def get_ki(x1, x2, temp): 96 | gamma_1 = self.matter.getActivityCoeff(method=self.gamma_method, 97 | mole_frac=x1, 98 | temp=temp) 99 | 100 | gamma_2 = self.matter.getActivityCoeff(method=self.gamma_method, 101 | mole_frac=x2, 102 | temp=temp) 103 | 104 | k_i = gamma_1 / gamma_2 105 | 106 | return k_i 107 | 108 | if self.k_fun is None: 109 | k_fun = get_ki 110 | else: 111 | k_fun = self.k_fun 112 | 113 | def func_phi(phi, k_i): 114 | f_phi = z_i * (1 - k_i) / (1 + phi*(k_i - 1)) 115 | 116 | return f_phi.sum() 117 | 118 | def deriv_phi(phi, k_i): 119 | deriv = z_i * (1 - k_i)**2 / (1 + phi*(k_i - 1))**2 120 | 121 | return deriv.sum() 122 | 123 | error = 1 124 | 125 | tol = self.solver_options.get('tol', 1e-4) 126 | max_iter = self.solver_options.get('max_iter', 100) 127 | 128 | count = 0 129 | 130 | while error > tol and count < max_iter: 131 | k_i = k_fun(x_1_seed, x_2_seed, self.temp) 132 | phi_k = newton(func_phi, phi_seed, args=(k_i, ), fprime=deriv_phi) 133 | 134 | x_1_k = z_i / (1 + phi_k*(k_i - 1)) 135 | x_2_k = x_1_k * k_i 136 | 137 | # Normalize x's 138 | x_1_k *= 1 / x_1_k.sum() 139 | x_2_k *= 1 / x_2_k.sum() 140 | 141 | x_k = np.concatenate((x_1_k, x_2_k)) 142 | x_km1 = np.concatenate((x_1_seed, x_2_seed)) 143 | 144 | error = np.linalg.norm(x_k - x_km1) 145 | 146 | # Update 147 | x_1_seed = x_1_k 148 | x_2_seed = x_2_k 149 | phi_seed = phi_k 150 | 151 | count += 1 152 | 153 | # Retrieve results 154 | phi_conv = phi_seed 155 | x1_conv = x_1_seed 156 | x2_conv = x_2_seed 157 | 158 | info = {'error': error, 'num_iter': count} 159 | 160 | return phi_conv, x1_conv, x2_conv, info 161 | 162 | def energy_balance(self, liq, vap, x_i, y_i, z_i): 163 | liq_flow = liq * self.in_flow # mol/s 164 | vap_flow = vap * self.in_flow # mol/s 165 | 166 | tref = self.temp 167 | 168 | h_liq = 0 169 | h_vap = self.VaporOut.getEnthalpy(self.temp, temp_ref=tref, 170 | mole_frac=y_i, basis='mole') 171 | 172 | h_in = self.matter.getEnthalpy(temp_ref=tref, basis='mole') 173 | 174 | heat_duty = liq_flow * h_liq + vap_flow * h_vap - self.in_flow * h_in 175 | 176 | return heat_duty # W 177 | 178 | def unit_model(self, phi, x1, x2, temp, material=True): 179 | z_i = self.matter.mole_frac 180 | 181 | if material: 182 | balance = self.material_balance(phi, x1, x2, z_i, temp) 183 | else: 184 | balance = self.energy_balance(phi, x1, x2, z_i, temp) 185 | 186 | return balance 187 | 188 | def flatten_states(self): 189 | pass 190 | 191 | def solve_unit(self, solver_options=None): 192 | 193 | if solver_options is None: 194 | solver_options = {} 195 | 196 | self.solver_options = solver_options 197 | 198 | # Set seeds 199 | mol_z = self.in_flow * self.matter.mole_frac 200 | 201 | distr_seed = np.random.random(self.num_comp) 202 | distr_seed = distr_seed / distr_seed.sum() 203 | 204 | liq1_seed = mol_z * distr_seed 205 | liq2_seed = mol_z * (1 - distr_seed) 206 | 207 | x1_seed = liq1_seed / liq1_seed.sum() 208 | x2_seed = liq2_seed / liq2_seed.sum() 209 | 210 | phi_seed = liq2_seed.sum() / self.in_flow 211 | 212 | # Solve material balance 213 | solution = self.unit_model(phi_seed, x1_seed, x2_seed, self.temp) 214 | 215 | # # Energy balance 216 | # heat_bce = self.unit_model(solution, material=False) 217 | 218 | self.retrieve_results(solution) 219 | 220 | return solution 221 | 222 | def retrieve_results(self, solution): 223 | phase_part = solution[0] 224 | self.info_solver = solution[-1] 225 | 226 | if phase_part > 1 or phase_part < 0: 227 | phase_part = 1 228 | 229 | print('\nNo phases in equilibrium present at the specified ' 230 | 'conditions', end='\n') 231 | 232 | xa_liq = self.matter.mole_frac 233 | xb_liq = xa_liq 234 | 235 | else: 236 | xa_liq = solution[1] 237 | xb_liq = solution[2] 238 | 239 | liquid_b = phase_part * self.in_flow 240 | liquid_a = self.in_flow - liquid_b 241 | 242 | path = self.matter.path_data 243 | 244 | # Store in objects 245 | if self.oper_mode == 'continuous': 246 | Liquid_a = LiquidStream(path, mole_frac=xa_liq, 247 | mole_flow=liquid_a, 248 | temp=self.temp, pres=self.pres) 249 | Liquid_b = LiquidStream(path, mole_frac=xb_liq, 250 | mole_flow=liquid_b, 251 | temp=self.temp, pres=self.pres) 252 | else: 253 | Liquid_a = LiquidPhase(path, mole_frac=xa_liq, 254 | moles=liquid_a, 255 | temp=self.temp, pres=self.pres) 256 | Liquid_b = LiquidPhase(path, mole_frac=xb_liq, 257 | moles=liquid_b, 258 | temp=self.temp, pres=self.pres) 259 | 260 | dens_a = Liquid_a.getDensity(basis='mole') 261 | dens_b = Liquid_b.getDensity(basis='mole') 262 | 263 | if phase_part > 0 and phase_part < 1: 264 | if dens_a > dens_b: 265 | self.Liquid_2 = Liquid_a 266 | self.Liquid_3 = Liquid_b 267 | else: 268 | self.Liquid_2 = Liquid_b 269 | self.Liquid_3 = Liquid_a 270 | 271 | else: 272 | self.Liquid_2 = Liquid_b 273 | self.Liquid_3 = Liquid_a 274 | 275 | # Result object 276 | if dens_a > dens_b: 277 | di = {'x_light': xb_liq, 'x_heavy': xa_liq, 278 | 'mol_heavy': liquid_a, 'mol_light': liquid_b, 279 | 'rho_light': dens_b, 'rho_heavy': dens_a} 280 | else: 281 | di = {'x_light': xa_liq, 'x_heavy': xb_liq, 282 | 'mol_heavy': liquid_b, 'mol_light': liquid_a, 283 | 'rho_light': dens_a, 'rho_heavy': dens_b} 284 | 285 | self.result = DynamicResult(self.states_di, **di) 286 | 287 | 288 | class BatchExtractor(ContinuousExtractor): 289 | def __init__(self, k_fun=None, gamma_method='UNIQUAC'): 290 | super().__init__(k_fun, gamma_method) 291 | 292 | self._Phases = None 293 | 294 | @property 295 | def Phases(self): 296 | return self._Phases 297 | 298 | @Phases.setter 299 | def Phases(self, instance): 300 | fields = material_setter(instance, oper_mode=self.oper_mode) 301 | self._Phases = instance 302 | self.matter = self._Phases 303 | 304 | for key, val in fields.items(): 305 | setattr(self, key, val) 306 | -------------------------------------------------------------------------------- /tests/integration/data/pfr_test_expected_conc_0.csv: -------------------------------------------------------------------------------- 1 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 2 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 3 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 4 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 5 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 6 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 7 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 8 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 9 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 10 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 11 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 12 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 13 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 14 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 15 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 16 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 17 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 18 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 19 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 20 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 21 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 22 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 23 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 24 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 25 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 26 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 27 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 28 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 29 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 30 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 31 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 32 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 33 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 34 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 35 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 36 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 37 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 38 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 39 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 40 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 41 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 42 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 43 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 44 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 45 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 46 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 47 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 48 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 49 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 50 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 51 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 52 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 53 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 54 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 55 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 56 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 57 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 58 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 59 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 60 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 61 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 62 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 63 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 64 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 65 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 66 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 67 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 68 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 69 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 70 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 71 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 72 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 73 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 74 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 75 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 76 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 77 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 78 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 79 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 80 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 81 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 82 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 83 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 84 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 85 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 86 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 87 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 88 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 89 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 90 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 91 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 92 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 93 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 94 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 95 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 96 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 97 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 98 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 99 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 100 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 101 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 102 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 103 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 104 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 105 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 106 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 107 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 108 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 109 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 110 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 111 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 112 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 113 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 114 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 115 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 116 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 117 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 118 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 119 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 120 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 121 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 122 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 123 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 124 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 125 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 126 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 127 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 128 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 129 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 130 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 131 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 132 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 133 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 134 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 135 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 136 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 137 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 138 | 1.499999999999999944e-01,1.499999999999999944e-01,0.000000000000000000e+00 139 | -------------------------------------------------------------------------------- /tests/integration/data/pfr_test_expected_conc_1.csv: -------------------------------------------------------------------------------- 1 | 0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00 2 | 1.796161257321779881e-08,1.796161257321779881e-08,4.137631176873394555e-22 3 | 1.794190087808756825e-04,1.794190087808756825e-04,4.130535811377609893e-10 4 | 1.958964696985089973e-03,1.958964696985089973e-03,3.305349659674397860e-07 5 | 3.718740527276052140e-03,3.718740527276052140e-03,1.610765398075487946e-06 6 | 5.456673249431875754e-03,5.456673249431875754e-03,4.546858411753379530e-06 7 | 8.291353830715475146e-03,8.291353830715475146e-03,1.543231657828195803e-05 8 | 1.106063819453799620e-02,1.106063819453799793e-02,3.596922741255649915e-05 9 | 1.522645521725971701e-02,1.522645521725971875e-02,9.309788618305074885e-05 10 | 1.922580950486649926e-02,1.922580950486650272e-02,1.881067043183815620e-04 11 | 2.305497203558105196e-02,2.305497203558105543e-02,3.286247230504220698e-04 12 | 2.974488103662309077e-02,2.974488103662309771e-02,7.311570046608922976e-04 13 | 3.583373505716999641e-02,3.583373505717000335e-02,1.337272048156626283e-03 14 | 4.133247328019899053e-02,4.133247328019899747e-02,2.158464169542587564e-03 15 | 4.860608591326955746e-02,4.860608591326957134e-02,3.807339768042361777e-03 16 | 5.468778804891158418e-02,5.468778804891159806e-02,5.900753669179338579e-03 17 | 5.971350642427760563e-02,5.971350642427761951e-02,8.365664113773601737e-03 18 | 6.382881027165523968e-02,6.382881027165525356e-02,1.111368517603245759e-02 19 | 6.717407782642449698e-02,6.717407782642451086e-02,1.405672185987040176e-02 20 | 6.987676197901790398e-02,6.987676197901791786e-02,1.711542339815856975e-02 21 | 7.204901079822088883e-02,7.204901079822090271e-02,2.022185668840827122e-02 22 | 7.378752625623372086e-02,7.378752625623373473e-02,2.331981121927446249e-02 23 | 7.576166471489245713e-02,7.576166471489247101e-02,2.786556725605003268e-02 24 | 7.715984158624301925e-02,7.715984158624303313e-02,3.218382338003979160e-02 25 | 7.814599552873781330e-02,7.814599552873782717e-02,3.620951828792758759e-02 26 | 7.883911881881271178e-02,7.883911881881271178e-02,3.991038663309677548e-02 27 | 7.932493161495941802e-02,7.932493161495943190e-02,4.327686015229152000e-02 28 | 7.966479361117898006e-02,7.966479361117899394e-02,4.631439276828475116e-02 29 | 7.990234233715701184e-02,7.990234233715701184e-02,4.903782389149943799e-02 30 | 8.006836553442864268e-02,8.006836553442865656e-02,5.146771281291091793e-02 31 | 8.018427166287632468e-02,8.018427166287635244e-02,5.362771806424528054e-02 32 | 8.026506907763586440e-02,8.026506907763587828e-02,5.554231438008078864e-02 33 | 8.032136062655680087e-02,8.032136062655681474e-02,5.723548027165812485e-02 34 | 8.037581851762255758e-02,8.037581851762257146e-02,5.942005346573083630e-02 35 | 8.040744538184026180e-02,8.040744538184027568e-02,6.122477044326161966e-02 36 | 8.042546905230049670e-02,8.042546905230051058e-02,6.271252525532537703e-02 37 | 8.043557122322143871e-02,8.043557122322145259e-02,6.393697031215470061e-02 38 | 8.044128414963210016e-02,8.044128414963211404e-02,6.494362354240106050e-02 39 | 8.044473070733360043e-02,8.044473070733362818e-02,6.577054477767349527e-02 40 | 8.044692337185724296e-02,8.044692337185727071e-02,6.644938424821783540e-02 41 | 8.044889595649561131e-02,8.044889595649563907e-02,6.730350840130089329e-02 42 | 8.044962516210416026e-02,8.044962516210417414e-02,6.792242243367045684e-02 43 | 8.044982437050016144e-02,8.044982437050017532e-02,6.837079879032378904e-02 44 | 8.045002019088524914e-02,8.045002019088527689e-02,6.869558531015536318e-02 45 | 8.045023557847444384e-02,8.045023557847447160e-02,6.893090729500424285e-02 46 | 8.045026518837090912e-02,8.045026518837092300e-02,6.910150171098244920e-02 47 | 8.045015955264493701e-02,8.045015955264495089e-02,6.922513231502006037e-02 48 | 8.045001282697973599e-02,8.045001282697973599e-02,6.931474851709362728e-02 49 | 8.045000376934842434e-02,8.045000376934842434e-02,6.937960051179760390e-02 50 | 8.045007993506991806e-02,8.045007993506993194e-02,6.942651951506005614e-02 51 | 8.045012023681602509e-02,8.045012023681603897e-02,6.947440278220895493e-02 52 | 8.045023328387250483e-02,8.045023328387251871e-02,6.950330912335442313e-02 53 | 8.045037530686617588e-02,8.045037530686618976e-02,6.952084682105401370e-02 54 | 8.045035540515468875e-02,8.045035540515470263e-02,6.953188020872425046e-02 55 | 8.045016601873952733e-02,8.045016601873954121e-02,6.953899822862573410e-02 56 | 8.045012053247352357e-02,8.045012053247353745e-02,6.954328417271361806e-02 57 | 8.045027922586019120e-02,8.045027922586021896e-02,6.954563936317911810e-02 58 | 8.045035776565120922e-02,8.045035776565122310e-02,6.954707325670847484e-02 59 | 8.045028660525356534e-02,8.045028660525357922e-02,6.954811091941141832e-02 60 | 8.045020418700225828e-02,8.045020418700227216e-02,6.954882630448087910e-02 61 | 8.045018935281342265e-02,8.045018935281343653e-02,6.954923199546794721e-02 62 | 8.045021870915931850e-02,8.045021870915934625e-02,6.954942323128965320e-02 63 | 8.045023149827208730e-02,8.045023149827211506e-02,6.954953528637664029e-02 64 | 8.045021449702656480e-02,8.045021449702657867e-02,6.954963499399259563e-02 65 | 8.045019486448500012e-02,8.045019486448500012e-02,6.954971533848929022e-02 66 | 8.045019585694948572e-02,8.045019585694948572e-02,6.954975403072358175e-02 67 | 8.045020824809899496e-02,8.045020824809899496e-02,6.954976197189752241e-02 68 | 8.045022689234418434e-02,8.045022689234419822e-02,6.954975287779117943e-02 69 | 8.045022311792678071e-02,8.045022311792679459e-02,6.954976195125728289e-02 70 | 8.045020280963528780e-02,8.045020280963530168e-02,6.954978568394112881e-02 71 | 8.045020421667374577e-02,8.045020421667374577e-02,6.954978896549937617e-02 72 | 8.045021942828553541e-02,8.045021942828554928e-02,6.954977788074256928e-02 73 | 8.045021123051858403e-02,8.045021123051861178e-02,6.954978773087887312e-02 74 | 8.045019488472637814e-02,8.045019488472640590e-02,6.954980409867833613e-02 75 | 8.045020029637640901e-02,8.045020029637643677e-02,6.954979876579146791e-02 76 | 8.045021255932596760e-02,8.045021255932598148e-02,6.954978708393443065e-02 77 | 8.045021171238393720e-02,8.045021171238395108e-02,6.954978845066486781e-02 78 | 8.045020453065715449e-02,8.045020453065718224e-02,6.954979566064348195e-02 79 | 8.045020449162493259e-02,8.045020449162496035e-02,6.954979546840510396e-02 80 | 8.045020853910336833e-02,8.045020853910339609e-02,6.954979132297794964e-02 81 | 8.045021193244496216e-02,8.045021193244497604e-02,6.954978802629538381e-02 82 | 8.045020868851478213e-02,8.045020868851479601e-02,6.954979140849101849e-02 83 | 8.045020613490308503e-02,8.045020613490311279e-02,6.954979397586549816e-02 84 | 8.045020652144128925e-02,8.045020652144130313e-02,6.954979351047732572e-02 85 | 8.045020903338108431e-02,8.045020903338109819e-02,6.954979092705254606e-02 86 | 8.045020853137972716e-02,8.045020853137974104e-02,6.954979143183089019e-02 87 | 8.045020724841001059e-02,8.045020724841002446e-02,6.954979275346091783e-02 88 | 8.045020593440227463e-02,8.045020593440228851e-02,6.954979407928552071e-02 89 | 8.045020611973888180e-02,8.045020611973888180e-02,6.954979387791619672e-02 90 | 8.045020671687928837e-02,8.045020671687930225e-02,6.954979327628156571e-02 91 | 8.045020731221996790e-02,8.045020731221999566e-02,6.954979268768983480e-02 92 | 8.045020688135151588e-02,8.045020688135155751e-02,6.954979312388526180e-02 93 | 8.045020655283384448e-02,8.045020655283387223e-02,6.954979345030055937e-02 94 | 8.045020649471633367e-02,8.045020649471634755e-02,6.954979350469006616e-02 95 | 8.045020673563757496e-02,8.045020673563760272e-02,6.954979326408954055e-02 96 | 8.045020676956711470e-02,8.045020676956714245e-02,6.954979323220120135e-02 97 | 8.045020666931319842e-02,8.045020666931321229e-02,6.954979333186769863e-02 98 | 8.045020660521415290e-02,8.045020660521416678e-02,6.954979339389164017e-02 99 | 8.045020665787507019e-02,8.045020665787507019e-02,6.954979334104186006e-02 100 | 8.045020670874401814e-02,8.045020670874403201e-02,6.954979329156522894e-02 101 | 8.045020668783998963e-02,8.045020668784001738e-02,6.954979331325152059e-02 102 | 8.045020664672584976e-02,8.045020664672586364e-02,6.954979335384785244e-02 103 | 8.045020664371410613e-02,8.045020664371412000e-02,6.954979335603736490e-02 104 | 8.045020667211442988e-02,8.045020667211444376e-02,6.954979332749162968e-02 105 | 8.045020668748362191e-02,8.045020668748364967e-02,6.954979331248135888e-02 106 | 8.045020667634064648e-02,8.045020667634066036e-02,6.954979332383613710e-02 107 | 8.045020666624271011e-02,8.045020666624272399e-02,6.954979333381697271e-02 108 | 8.045020666988439428e-02,8.045020666988440816e-02,6.954979332999146335e-02 109 | 8.045020667791118185e-02,8.045020667791119573e-02,6.954979332196330188e-02 110 | 8.045020667806310199e-02,8.045020667806310199e-02,6.954979332193100827e-02 111 | 8.045020667255411984e-02,8.045020667255413371e-02,6.954979332748897902e-02 112 | 8.045020666970380263e-02,8.045020666970381651e-02,6.954979333029351340e-02 113 | 8.045020667159191730e-02,8.045020667159191730e-02,6.954979332836670747e-02 114 | 8.045020667380764490e-02,8.045020667380765877e-02,6.954979332617246268e-02 115 | 8.045020667351177046e-02,8.045020667351178434e-02,6.954979332650303159e-02 116 | 8.045020667221371158e-02,8.045020667221372546e-02,6.954979332780435175e-02 117 | 8.045020667210994736e-02,8.045020667210996124e-02,6.954979332788978341e-02 118 | 8.045020667274635495e-02,8.045020667274636883e-02,6.954979332724085805e-02 119 | 8.045020667303670603e-02,8.045020667303671991e-02,6.954979332695512828e-02 120 | 8.045020667258830083e-02,8.045020667258831470e-02,6.954979332742128317e-02 121 | 8.045020667232599676e-02,8.045020667232601064e-02,6.954979332768682077e-02 122 | 8.045020667264561609e-02,8.045020667264564385e-02,6.954979332735670983e-02 123 | 8.045020667271743364e-02,8.045020667271746140e-02,6.954979332727766195e-02 124 | 8.045020667255839419e-02,8.045020667255840807e-02,6.954979332743568832e-02 125 | 8.045020667243447943e-02,8.045020667243449330e-02,6.954979332756336396e-02 126 | 8.045020667237551271e-02,8.045020667237551271e-02,6.954979332762512012e-02 127 | 8.045020667247083923e-02,8.045020667247085311e-02,6.954979332753107035e-02 128 | 8.045020667250085689e-02,8.045020667250087076e-02,6.954979332750013676e-02 129 | 8.045020667249258572e-02,8.045020667249261348e-02,6.954979332750743648e-02 130 | 8.045020667245864066e-02,8.045020667245865453e-02,6.954979332754081256e-02 131 | 8.045020667246838286e-02,8.045020667246838286e-02,6.954979332753159771e-02 132 | 8.045020667251641389e-02,8.045020667251641389e-02,6.954979332748380261e-02 133 | 8.045020667250335489e-02,8.045020667250335489e-02,6.954979332749657017e-02 134 | 8.045020667244535961e-02,8.045020667244537349e-02,6.954979332755445443e-02 135 | 8.045020667245254831e-02,8.045020667245257606e-02,6.954979332754711308e-02 136 | 8.045020667249822011e-02,8.045020667249823398e-02,6.954979332750162169e-02 137 | 8.045020667248868607e-02,8.045020667248868607e-02,6.954979332751125287e-02 138 | 8.045020667246935431e-02,8.045020667246935431e-02,6.954979332753059851e-02 139 | -------------------------------------------------------------------------------- /tests/integration/data/pfr_test_expected_conc_2.csv: -------------------------------------------------------------------------------- 1 | 0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00 2 | 2.150797356626486673e-15,2.150797356626486673e-15,6.165184660010564529e-38 3 | 2.146869947153858690e-07,2.146869947153858690e-07,4.951444783905835052e-21 4 | 1.648170263190818535e-05,1.648170263190818535e-05,3.627659674962693960e-11 5 | 5.120311620592983174e-05,5.120311620592983174e-05,8.010683514052592339e-09 6 | 1.055058581215089370e-04,1.055058581215089370e-04,4.185460518177007729e-08 7 | 2.387600966759554873e-04,2.387600966759554873e-04,2.340924293279829599e-07 8 | 4.241795606792164394e-04,4.241795606792164394e-04,7.543542145955334747e-07 9 | 8.104173860330561238e-04,8.104173860330561238e-04,2.738417351621172306e-06 10 | 1.309228949521144608e-03,1.309228949521144608e-03,7.200091210686980973e-06 11 | 1.911784814402451866e-03,1.911784814402452083e-03,1.560807268689510453e-05 12 | 3.283976862334551505e-03,3.283976862334551938e-03,4.829535006539933424e-05 13 | 4.931290548035972138e-03,4.931290548035973005e-03,1.135660125728211648e-04 14 | 6.800294699310774349e-03,6.800294699310774349e-03,2.255499137723635124e-04 15 | 9.949190628732207040e-03,9.949190628732205305e-03,5.180674222482293135e-04 16 | 1.333316891704550144e-02,1.333316891704550144e-02,1.002059565424348207e-03 17 | 1.681345606685368671e-02,1.681345606685369018e-02,1.720013510257237125e-03 18 | 2.027740368930267337e-02,2.027740368930268031e-02,2.702458723019468111e-03 19 | 2.363743988680742925e-02,2.363743988680743618e-02,3.966531970929272448e-03 20 | 2.682840552253877228e-02,2.682840552253878269e-02,5.516584771156598371e-03 21 | 2.980564097732552389e-02,2.980564097732553430e-02,7.345106727927813196e-03 22 | 3.254256931904177946e-02,3.254256931904179334e-02,9.434453109872089488e-03 23 | 3.618052153528469178e-02,3.618052153528470566e-02,1.301091831691380575e-02 24 | 3.925274880104412389e-02,3.925274880104414471e-02,1.701521841115391509e-02 25 | 4.180046653955537400e-02,4.180046653955538788e-02,2.133096065744737729e-02 26 | 4.388268453686648696e-02,4.388268453686650084e-02,2.584450378092311221e-02 27 | 4.556384378657805240e-02,4.556384378657806627e-02,3.045315648870712544e-02 28 | 4.690735644078451033e-02,4.690735644078453115e-02,3.506860693296233455e-02 29 | 4.797179600155626611e-02,4.797179600155628693e-02,3.961835296582066901e-02 30 | 4.880943785044881272e-02,4.880943785044883354e-02,4.404451767867348377e-02 31 | 4.946523556965912172e-02,4.946523556965914947e-02,4.830312362916733965e-02 32 | 4.997648033073875257e-02,4.997648033073878032e-02,5.236299843541808441e-02 33 | 5.037355094733542149e-02,5.037355094733545618e-02,5.620371451508936883e-02 34 | 5.080948490215578955e-02,5.080948490215582425e-02,6.155594252299216584e-02 35 | 5.110439433636392026e-02,5.110439433636394801e-02,6.637180980937107400e-02 36 | 5.130243787277018186e-02,5.130243787277020961e-02,7.066401757231691172e-02 37 | 5.143459304566373735e-02,5.143459304566377205e-02,7.445958654824910705e-02 38 | 5.152270388867173645e-02,5.152270388867177114e-02,7.779342455617249685e-02 39 | 5.158146521029145087e-02,5.158146521029148557e-02,8.070550075865318140e-02 40 | 5.162064459005134831e-02,5.162064459005137607e-02,8.323765629689568180e-02 41 | 5.165797449600764008e-02,5.165797449600767477e-02,8.665177968265451447e-02 42 | 5.167636217421583272e-02,5.167636217421586742e-02,8.933339148077587555e-02 43 | 5.168574490824431877e-02,5.168574490824434653e-02,9.142412834372524999e-02 44 | 5.169132747298679775e-02,5.169132747298682551e-02,9.304375392088429231e-02 45 | 5.169476319969831118e-02,5.169476319969834588e-02,9.429277795557963748e-02 46 | 5.169644006552862742e-02,5.169644006552866211e-02,9.525311390741396378e-02 47 | 5.169705449909409167e-02,5.169705449909412637e-02,9.598917058849029682e-02 48 | 5.169724378826458244e-02,5.169724378826461714e-02,9.655144213438149325e-02 49 | 5.169745859733459320e-02,5.169745859733461402e-02,9.697938282128541754e-02 50 | 5.169775277372318928e-02,5.169775277372321010e-02,9.730420076694112907e-02 51 | 5.169820897925317404e-02,5.169820897925319486e-02,9.765469627897052640e-02 52 | 5.169828543122455961e-02,5.169828543122458736e-02,9.788281459565016662e-02 53 | 5.169809805469687686e-02,5.169809805469689074e-02,9.803106687802258523e-02 54 | 5.169798387412502527e-02,5.169798387412503915e-02,9.812742252557514477e-02 55 | 5.169807100511079889e-02,5.169807100511081277e-02,9.818987108462444435e-02 56 | 5.169806207481583704e-02,5.169806207481585786e-02,9.823022383849179529e-02 57 | 5.169786335070490624e-02,5.169786335070492705e-02,9.825621324772088050e-02 58 | 5.169779135181338386e-02,5.169779135181340468e-02,9.827275837833883421e-02 59 | 5.169788756388635914e-02,5.169788756388638690e-02,9.828325612639284004e-02 60 | 5.169797294463220061e-02,5.169797294463222836e-02,9.829000922949736319e-02 61 | 5.169797718248989038e-02,5.169797718248991814e-02,9.829438276982696565e-02 62 | 5.169794654283644403e-02,5.169794654283647178e-02,9.829718417419947007e-02 63 | 5.169794546550195158e-02,5.169794546550198627e-02,9.829893923899309105e-02 64 | 5.169797254855106050e-02,5.169797254855110213e-02,9.830003731693486013e-02 65 | 5.169799074894010360e-02,5.169799074894014523e-02,9.830074637680688565e-02 66 | 5.169798186933630829e-02,5.169798186933634992e-02,9.830122002735119935e-02 67 | 5.169796577251251773e-02,5.169796577251255243e-02,9.830152759798602147e-02 68 | 5.169794620638697852e-02,5.169794620638700627e-02,9.830173295369966369e-02 69 | 5.169795596503377194e-02,5.169795596503379970e-02,9.830187818225445684e-02 70 | 5.169797522632735287e-02,5.169797522632738063e-02,9.830192461645624602e-02 71 | 5.169796861193668575e-02,5.169796861193671350e-02,9.830196517589272209e-02 72 | 5.169795662377133644e-02,5.169795662377135032e-02,9.830200785360999893e-02 73 | 5.169797310703394544e-02,5.169797310703395932e-02,9.830201435036230173e-02 74 | 5.169798889877604692e-02,5.169798889877607467e-02,9.830200638454389406e-02 75 | 5.169797582731995578e-02,5.169797582731999047e-02,9.830201831663874790e-02 76 | 5.169796163609074319e-02,5.169796163609077788e-02,9.830203215023335106e-02 77 | 5.169796789559662209e-02,5.169796789559664985e-02,9.830202914116864532e-02 78 | 5.169797748305150070e-02,5.169797748305152846e-02,9.830202277770459796e-02 79 | 5.169797431284225975e-02,5.169797431284228750e-02,9.830202642933512369e-02 80 | 5.169796838601102562e-02,5.169796838601106032e-02,9.830203120792398164e-02 81 | 5.169796558121447089e-02,5.169796558121451252e-02,9.830203317204641578e-02 82 | 5.169797212152554833e-02,5.169797212152558302e-02,9.830202707715850197e-02 83 | 5.169797457541362717e-02,5.169797457541365493e-02,9.830202550151634355e-02 84 | 5.169797220092773454e-02,5.169797220092776230e-02,9.830202819967201466e-02 85 | 5.169796739429073640e-02,5.169796739429076415e-02,9.830203272333537523e-02 86 | 5.169796960315727052e-02,5.169796960315729828e-02,9.830203015181462889e-02 87 | 5.169797210174204710e-02,5.169797210174207486e-02,9.830202763757478479e-02 88 | 5.169797406479138130e-02,5.169797406479140905e-02,9.830202586603886683e-02 89 | 5.169797330976536737e-02,5.169797330976538818e-02,9.830202674795071593e-02 90 | 5.169797232726629221e-02,5.169797232726631303e-02,9.830202771815320373e-02 91 | 5.169797137729332387e-02,5.169797137729335162e-02,9.830202859428804896e-02 92 | 5.169797226729058726e-02,5.169797226729062195e-02,9.830202767511145301e-02 93 | 5.169797275238373346e-02,5.169797275238376816e-02,9.830202722267369908e-02 94 | 5.169797279765393772e-02,5.169797279765397241e-02,9.830202720846208109e-02 95 | 5.169797238535480538e-02,5.169797238535484701e-02,9.830202761573525461e-02 96 | 5.169797239750328899e-02,5.169797239750332368e-02,9.830202758947259589e-02 97 | 5.169797258790698180e-02,5.169797258790700956e-02,9.830202740482570090e-02 98 | 5.169797266203425556e-02,5.169797266203428332e-02,9.830202734332060821e-02 99 | 5.169797253919283797e-02,5.169797253919286573e-02,9.830202746569383088e-02 100 | 5.169797245955106563e-02,5.169797245955108644e-02,9.830202753752895206e-02 101 | 5.169797251556854795e-02,5.169797251556856182e-02,9.830202747866256807e-02 102 | 5.169797259448440790e-02,5.169797259448442872e-02,9.830202740353335966e-02 103 | 5.169797258834053083e-02,5.169797258834055859e-02,9.830202741401453115e-02 104 | 5.169797253086069050e-02,5.169797253086072519e-02,9.830202747168274857e-02 105 | 5.169797250521138143e-02,5.169797250521140919e-02,9.830202749501749937e-02 106 | 5.169797252928801101e-02,5.169797252928804571e-02,9.830202746980923334e-02 107 | 5.169797254839308964e-02,5.169797254839312434e-02,9.830202745143505616e-02 108 | 5.169797254094599809e-02,5.169797254094603278e-02,9.830202745988673996e-02 109 | 5.169797252562646078e-02,5.169797252562649548e-02,9.830202747514159289e-02 110 | 5.169797252603471754e-02,5.169797252603474530e-02,9.830202747400343388e-02 111 | 5.169797253681576027e-02,5.169797253681578109e-02,9.830202746289766480e-02 112 | 5.169797254222913835e-02,5.169797254222915917e-02,9.830202745771625394e-02 113 | 5.169797253832589257e-02,5.169797253832591338e-02,9.830202746187420570e-02 114 | 5.169797253399199127e-02,5.169797253399201209e-02,9.830202746614402631e-02 115 | 5.169797253456979297e-02,5.169797253456982072e-02,9.830202746537540504e-02 116 | 5.169797253714178420e-02,5.169797253714181196e-02,9.830202746275519543e-02 117 | 5.169797253742243470e-02,5.169797253742246246e-02,9.830202746256379298e-02 118 | 5.169797253620461025e-02,5.169797253620463801e-02,9.830202746386765278e-02 119 | 5.169797253558369721e-02,5.169797253558372496e-02,9.830202746447455620e-02 120 | 5.169797253640571327e-02,5.169797253640574103e-02,9.830202746356284105e-02 121 | 5.169797253699259798e-02,5.169797253699262574e-02,9.830202746296348715e-02 122 | 5.169797253641698204e-02,5.169797253641700285e-02,9.830202746359499588e-02 123 | 5.169797253622709227e-02,5.169797253622712002e-02,9.830202746381265511e-02 124 | 5.169797253648652363e-02,5.169797253648655833e-02,9.830202746354836651e-02 125 | 5.169797253673962673e-02,5.169797253673966142e-02,9.830202746326831276e-02 126 | 5.169797253687627436e-02,5.169797253687630906e-02,9.830202746311826612e-02 127 | 5.169797253672572812e-02,5.169797253672574894e-02,9.830202746326208163e-02 128 | 5.169797253665611020e-02,5.169797253665613795e-02,9.830202746333879804e-02 129 | 5.169797253665658204e-02,5.169797253665660980e-02,9.830202746334353037e-02 130 | 5.169797253670745801e-02,5.169797253670748577e-02,9.830202746329655406e-02 131 | 5.169797253670700699e-02,5.169797253670703474e-02,9.830202746329226582e-02 132 | 5.169797253661755077e-02,5.169797253661756464e-02,9.830202746338086162e-02 133 | 5.169797253664071279e-02,5.169797253664072667e-02,9.830202746336058617e-02 134 | 5.169797253676122750e-02,5.169797253676124832e-02,9.830202746324004370e-02 135 | 5.169797253673875936e-02,5.169797253673878712e-02,9.830202746326331675e-02 136 | 5.169797253664180220e-02,5.169797253664182995e-02,9.830202746335874042e-02 137 | 5.169797253665861514e-02,5.169797253665863596e-02,9.830202746334172625e-02 138 | 5.169797253670061626e-02,5.169797253670063014e-02,9.830202746329980146e-02 139 | -------------------------------------------------------------------------------- /tests/integration/data/pfr_test_expected_conc_3.csv: -------------------------------------------------------------------------------- 1 | 0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00 2 | -6.955592949755513652e-38,-6.323266317959555397e-38,6.165184660010564529e-38 3 | 2.570750150819888092e-14,2.570750150819888092e-14,6.165872510670509631e-34 4 | 1.861039464610360555e-08,1.861039464610360555e-08,4.292260004647343808e-22 5 | 3.941931655298744347e-07,3.941931655298744347e-07,8.580768437730951317e-13 6 | 1.341033422378876207e-06,1.341033422378876207e-06,1.899279436965965742e-10 7 | 4.862104905179665753e-06,4.862104905179665753e-06,2.286839908771737445e-09 8 | 1.152707592626091776e-05,1.152707592626091776e-05,1.050183251694949477e-08 9 | 3.021114710097482276e-05,3.021114710097482276e-05,5.421630114301706015e-08 10 | 6.157331323137608676e-05,6.157331323137608676e-05,1.880004878185619289e-07 11 | 1.085148181101918942e-04,1.085148181101919078e-04,5.146332181676127027e-07 12 | 2.463948305944396754e-04,2.463948305944397297e-04,2.207530930019776744e-06 13 | 4.609372255012918152e-04,4.609372255012919237e-04,6.656801111438003124e-06 14 | 7.620492018108524206e-04,7.620492018108525291e-04,1.597846925253783654e-05 15 | 1.396639362750398596e-03,1.396639362750398596e-03,4.633359272360739682e-05 16 | 2.251429355052944614e-03,2.251429355052944614e-03,1.083699056113883756e-04 17 | 3.319999345553622735e-03,3.319999345553623603e-03,2.192982363246770178e-04 18 | 4.584024576606699798e-03,4.584024576606701533e-03,3.992090389789896112e-04 19 | 6.017345354197751323e-03,6.017345354197753057e-03,6.694436720035931059e-04 20 | 7.588939416769053571e-03,7.588939416769055306e-03,1.051374417510879487e-03 21 | 9.265074953506259939e-03,9.265074953506261674e-03,1.565320372851589266e-03 22 | 1.101142298989924058e-02,1.101142298989924405e-02,2.229313722267113433e-03 23 | 1.369808000771225755e-02,1.369808000771226275e-02,3.540716177611403625e-03 24 | 1.636893520700292634e-02,1.636893520700293675e-02,5.257047138266409542e-03 25 | 1.894095087609680081e-02,1.894095087609681122e-02,7.390929126946070050e-03 26 | 2.135354319268049916e-02,2.135354319268051304e-02,9.933452516000244870e-03 27 | 2.356770704403700692e-02,2.356770704403702427e-02,1.285737086841733275e-02 28 | 2.556261048856033405e-02,2.556261048856035140e-02,1.612171106537010901e-02 29 | 2.733216535545555589e-02,2.733216535545557324e-02,1.967601838466455366e-02 30 | 2.888039249531496044e-02,2.888039249531498126e-02,2.346545435919334191e-02 31 | 3.021890647750436268e-02,3.021890647750438696e-02,2.743344683030775680e-02 32 | 3.136448171309422911e-02,3.136448171309424993e-02,3.152426399615700786e-02 33 | 3.233653950912897257e-02,3.233653950912898645e-02,3.568542101830331215e-02 34 | 3.351859081054527068e-02,3.351859081054529149e-02,4.198869864199106933e-02 35 | 3.441878775722916756e-02,3.441878775722918837e-02,4.820446731927351125e-02 36 | 3.509720432165793630e-02,3.509720432165795712e-02,5.422274885751704465e-02 37 | 3.560373280414506209e-02,3.560373280414508290e-02,5.996306390776728890e-02 38 | 3.597872357265065324e-02,3.597872357265068100e-02,6.536981629189708709e-02 39 | 3.625435384415656020e-02,3.625435384415659490e-02,7.040836290543822651e-02 40 | 3.645569089615968011e-02,3.645569089615971481e-02,7.506169522197134181e-02 41 | 3.667265393176760008e-02,3.667265393176764171e-02,8.180340723712796469e-02 42 | 3.680076178334316245e-02,3.680076178334319714e-02,8.755388745287129293e-02 43 | 3.687555084661404275e-02,3.687555084661407745e-02,9.239090030804888998e-02 44 | 3.691887697301054344e-02,3.691887697301057814e-02,9.641237558090404169e-02 45 | 3.694431760336635423e-02,3.694431760336638892e-02,9.972250307661803537e-02 46 | 3.695959868361255174e-02,3.695959868361258643e-02,1.024241551124659977e-01 47 | 3.696858551839097423e-02,3.696858551839100893e-02,1.046138489077882150e-01 48 | 3.697381681794559977e-02,3.697381681794563446e-02,1.063775406050477823e-01 49 | 3.697675920677795380e-02,3.697675920677798850e-02,1.077903848087506805e-01 50 | 3.697835959018481305e-02,3.697835959018484775e-02,1.089165730158643264e-01 51 | 3.697952138629004293e-02,3.697952138629007762e-02,1.102041597857550820e-01 52 | 3.698002248642354856e-02,3.698002248642358325e-02,1.111000460683767843e-01 53 | 3.698020868795218880e-02,3.698020868795222349e-02,1.117179291101704081e-01 54 | 3.698031861821519245e-02,3.698031861821522021e-02,1.121407489637329391e-01 55 | 3.698045491245183225e-02,3.698045491245185307e-02,1.124285365437372591e-01 56 | 3.698058903024763200e-02,3.698058903024765282e-02,1.126237553961918908e-01 57 | 3.698067204627489279e-02,3.698067204627491361e-02,1.127555873316463259e-01 58 | 3.698059527562534005e-02,3.698059527562536780e-02,1.128441835022884338e-01 59 | 3.698048299373030839e-02,3.698048299373034309e-02,1.129033488731431417e-01 60 | 3.698048420685601939e-02,3.698048420685605409e-02,1.129426278469131267e-01 61 | 3.698054595652768006e-02,3.698054595652771476e-02,1.129686834551204000e-01 62 | 3.698056627389425394e-02,3.698056627389428863e-02,1.129860202416235054e-01 63 | 3.698052598851211958e-02,3.698052598851216122e-02,1.129975544690139805e-01 64 | 3.698048588688481603e-02,3.698048588688486460e-02,1.130051547254916910e-01 65 | 3.698048838313744424e-02,3.698048838313748587e-02,1.130100994673504250e-01 66 | 3.698051761053176151e-02,3.698051761053181008e-02,1.130133126286374801e-01 67 | 3.698053322997802567e-02,3.698053322997806730e-02,1.130154346753337036e-01 68 | 3.698053472988729190e-02,3.698053472988733353e-02,1.130168482547175002e-01 69 | 3.698050936742549449e-02,3.698050936742552919e-02,1.130181371652292516e-01 70 | 3.698051417815879233e-02,3.698051417815882702e-02,1.130187513080436745e-01 71 | 3.698053749915995725e-02,3.698053749915998500e-02,1.130190352537916165e-01 72 | 3.698052706743441137e-02,3.698052706743443913e-02,1.130192321109442788e-01 73 | 3.698049614662161921e-02,3.698049614662164003e-02,1.130193869094741760e-01 74 | 3.698049758741304982e-02,3.698049758741307064e-02,1.130194499308295603e-01 75 | 3.698052411585645016e-02,3.698052411585648486e-02,1.130194437274456148e-01 76 | 3.698053158626795484e-02,3.698053158626799647e-02,1.130194415852544265e-01 77 | 3.698051422890568513e-02,3.698051422890572676e-02,1.130194675993992370e-01 78 | 3.698050573078349307e-02,3.698050573078353470e-02,1.130194873973822400e-01 79 | 3.698051527425354201e-02,3.698051527425357671e-02,1.130194842497436597e-01 80 | 3.698052144433482125e-02,3.698052144433486288e-02,1.130194780384072839e-01 81 | 3.698052018632017046e-02,3.698052018632021209e-02,1.130194771892619743e-01 82 | 3.698050997054933009e-02,3.698050997054937172e-02,1.130194868101434130e-01 83 | 3.698050995141075742e-02,3.698050995141079905e-02,1.130194884698129260e-01 84 | 3.698051579113764697e-02,3.698051579113769555e-02,1.130194843197641352e-01 85 | 3.698052310114546826e-02,3.698052310114551683e-02,1.130194775710702260e-01 86 | 3.698051798504247561e-02,3.698051798504251725e-02,1.130194821736418831e-01 87 | 3.698051435453538621e-02,3.698051435453542785e-02,1.130194853034141855e-01 88 | 3.698051220503675851e-02,3.698051220503680014e-02,1.130194875233933027e-01 89 | 3.698051382142388682e-02,3.698051382142392846e-02,1.130194862348881529e-01 90 | 3.698051501131451035e-02,3.698051501131454505e-02,1.130194851094942637e-01 91 | 3.698051623115037462e-02,3.698051623115040931e-02,1.130194837680992825e-01 92 | 3.698051482891610842e-02,3.698051482891614311e-02,1.130194850869331025e-01 93 | 3.698051424285380556e-02,3.698051424285384026e-02,1.130194857047325252e-01 94 | 3.698051420253743726e-02,3.698051420253748583e-02,1.130194857865537972e-01 95 | 3.698051473005543877e-02,3.698051473005549428e-02,1.130194852370288161e-01 96 | 3.698051463045638754e-02,3.698051463045643611e-02,1.130194853155685325e-01 97 | 3.698051435728336167e-02,3.698051435728340330e-02,1.130194856295926448e-01 98 | 3.698051430337581658e-02,3.698051430337585821e-02,1.130194857272931452e-01 99 | 3.698051450556994607e-02,3.698051450556998770e-02,1.130194855069636128e-01 100 | 3.698051460400563623e-02,3.698051460400567786e-02,1.130194853713340913e-01 101 | 3.698051450871212009e-02,3.698051450871215479e-02,1.130194854610572924e-01 102 | 3.698051439272528190e-02,3.698051439272530966e-02,1.130194855982989272e-01 103 | 3.698051441057478850e-02,3.698051441057481625e-02,1.130194855966271394e-01 104 | 3.698051449505770916e-02,3.698051449505773691e-02,1.130194855098266699e-01 105 | 3.698051453056543786e-02,3.698051453056547255e-02,1.130194854667738308e-01 106 | 3.698051449557170078e-02,3.698051449557173548e-02,1.130194855010986377e-01 107 | 3.698051446747335075e-02,3.698051446747339238e-02,1.130194855333765958e-01 108 | 3.698051447597910241e-02,3.698051447597915098e-02,1.130194855271499793e-01 109 | 3.698051449756833975e-02,3.698051449756838832e-02,1.130194855039827057e-01 110 | 3.698051449792619932e-02,3.698051449792624790e-02,1.130194855013573196e-01 111 | 3.698051448352652343e-02,3.698051448352656506e-02,1.130194855158255934e-01 112 | 3.698051447536039593e-02,3.698051447536043063e-02,1.130194855252475705e-01 113 | 3.698051448035525463e-02,3.698051448035528238e-02,1.130194855204438298e-01 114 | 3.698051448659168267e-02,3.698051448659171042e-02,1.130194855133786341e-01 115 | 3.698051448636628658e-02,3.698051448636631433e-02,1.130194855131722714e-01 116 | 3.698051448284274401e-02,3.698051448284277176e-02,1.130194855169974477e-01 117 | 3.698051448204953129e-02,3.698051448204955904e-02,1.130194855181544805e-01 118 | 3.698051448342880299e-02,3.698051448342883768e-02,1.130194855167628437e-01 119 | 3.698051448445577316e-02,3.698051448445581479e-02,1.130194855155382261e-01 120 | 3.698051448368863681e-02,3.698051448368867844e-02,1.130194855160192718e-01 121 | 3.698051448272648978e-02,3.698051448272653141e-02,1.130194855169706220e-01 122 | 3.698051448325531676e-02,3.698051448325536533e-02,1.130194855166220397e-01 123 | 3.698051448358998655e-02,3.698051448359003512e-02,1.130194855164414619e-01 124 | 3.698051448337457553e-02,3.698051448337461716e-02,1.130194855167016704e-01 125 | 3.698051448309391809e-02,3.698051448309395972e-02,1.130194855169491669e-01 126 | 3.698051448285444992e-02,3.698051448285449155e-02,1.130194855171340468e-01 127 | 3.698051448303973920e-02,3.698051448303978084e-02,1.130194855169393831e-01 128 | 3.698051448312017486e-02,3.698051448312020956e-02,1.130194855168642487e-01 129 | 3.698051448314398221e-02,3.698051448314401690e-02,1.130194855168565743e-01 130 | 3.698051448305549049e-02,3.698051448305552519e-02,1.130194855169435464e-01 131 | 3.698051448308785349e-02,3.698051448308789513e-02,1.130194855169143198e-01 132 | 3.698051448321752754e-02,3.698051448321756918e-02,1.130194855167808987e-01 133 | 3.698051448316568707e-02,3.698051448316572176e-02,1.130194855168297347e-01 134 | 3.698051448299145144e-02,3.698051448299147920e-02,1.130194855170090357e-01 135 | 3.698051448301336447e-02,3.698051448301339222e-02,1.130194855169869145e-01 136 | 3.698051448315280848e-02,3.698051448315285011e-02,1.130194855168484558e-01 137 | 3.698051448313444123e-02,3.698051448313447592e-02,1.130194855168643181e-01 138 | 3.698051448307541900e-02,3.698051448307545369e-02,1.130194855169226048e-01 139 | -------------------------------------------------------------------------------- /tests/integration/data/pfr_test_expected_conc_4.csv: -------------------------------------------------------------------------------- 1 | 0.000000000000000000e+00,0.000000000000000000e+00,0.000000000000000000e+00 2 | -6.955592949755513652e-38,-6.323266317959555397e-38,6.165184660010564529e-38 3 | -6.956368986397503571e-34,-6.323971805815910472e-34,6.165872510670509631e-34 4 | 2.228471689163654591e-15,2.228471689163654591e-15,6.782312443728431500e-33 5 | 4.400707628482764315e-10,4.400707628482764315e-10,1.014968560716798393e-23 6 | 9.471706226482352114e-09,9.471706226482352114e-09,2.030280370833395201e-14 7 | 6.730523625347789366e-08,6.730523625347789366e-08,1.203479417193732849e-11 8 | 2.246730578726056506e-07,2.246730578726056506e-07,9.780754510116161118e-11 9 | 8.189855815478634657e-07,8.189855815478634657e-07,7.583402133107492434e-10 10 | 2.146791882163976598e-06,2.146791882163976598e-06,3.616852088893403132e-09 11 | 4.629098536302644081e-06,4.629098536302644081e-06,1.291864539778664256e-08 12 | 1.414688820118353076e-05,1.414688820118353245e-05,8.001537402213171253e-08 13 | 3.300256866381394796e-05,3.300256866381396151e-05,3.184001445202433864e-07 14 | 6.518430830843457318e-05,6.518430830843460028e-05,9.296058849954257302e-07 15 | 1.490631831938132878e-04,1.490631831938133149e-04,3.391655710611350397e-06 16 | 2.882319800621404508e-04,2.882319800621405050e-04,9.488312705259552168e-06 17 | 4.964916089373630449e-04,4.964916089373630449e-04,2.223741181299273783e-05 18 | 7.850664900212416366e-04,7.850664900212416366e-04,4.620152257841929241e-05 19 | 1.162119597491106373e-03,1.162119597491106590e-03,8.747218695602634918e-05 20 | 1.632761972788608499e-03,1.632761972788608932e-03,1.534842829820856364e-04 21 | 2.198820892079491208e-03,2.198820892079491642e-03,2.529782008926682803e-04 22 | 2.858732060526131529e-03,2.858732060526132396e-03,3.958887562319546630e-04 23 | 4.016548788763014965e-03,4.016548788763016700e-03,7.162953415365842097e-04 24 | 5.349748533632553600e-03,5.349748533632556202e-03,1.197238354659626311e-03 25 | 6.822646967084011749e-03,6.822646967084015218e-03,1.876591189087156025e-03 26 | 8.393855363761746832e-03,8.393855363761752036e-03,2.788912361412401904e-03 27 | 1.002064442140642234e-02,1.002064442140642928e-02,3.962536184489454973e-03 28 | 1.166232200883831303e-02,1.166232200883831997e-02,5.417661038925195363e-03 29 | 1.328255240834919776e-02,1.328255240834920643e-02,7.165235972082864557e-03 30 | 1.485090176396348070e-02,1.485090176396348938e-02,9.207021585552196269e-03 31 | 1.634306140737154175e-02,1.634306140737155216e-02,1.153640736977544663e-02 32 | 1.774118549342228179e-02,1.774118549342229220e-02,1.413888624089758121e-02 33 | 1.903368329088252556e-02,1.903368329088253944e-02,1.699318089056139555e-02 34 | 2.077011923861549897e-02,2.077011923861551632e-02,2.171437197917923390e-02 35 | 2.225032876532545556e-02,2.225032876532547291e-02,2.684348221067966306e-02 36 | 2.348844576129220305e-02,2.348844576129222039e-02,3.226724451759666046e-02 37 | 2.450775522297884285e-02,2.450775522297886366e-02,3.787427868696412875e-02 38 | 2.533507939822766952e-02,2.533507939822769034e-02,4.356189395758389149e-02 39 | 2.599867720872983332e-02,2.599867720872985760e-02,4.923784364907830141e-02 40 | 2.652591643923719530e-02,2.652591643923722306e-02,5.482270742508465328e-02 41 | 2.715506955808369774e-02,2.715506955808373243e-02,6.355166349913132784e-02 42 | 2.757536679836657123e-02,2.757536679836660592e-02,7.166768361139176646e-02 43 | 2.785114136716223057e-02,2.785114136716226874e-02,7.905199782358042493e-02 44 | 2.802867682165355659e-02,2.802867682165359822e-02,8.565129301157894948e-02 45 | 2.814162993770704183e-02,2.814162993770708346e-02,9.145907671475328460e-02 46 | 2.821365249141757556e-02,2.821365249141761719e-02,9.650251857009817247e-02 47 | 2.825987471450728053e-02,2.825987471450732563e-02,1.008325727951772943e-01 48 | 2.828924211109454592e-02,2.828924211109458756e-02,1.045142592055526010e-01 49 | 2.830744441116627569e-02,2.830744441116631732e-02,1.076184980832609328e-01 50 | 2.831862676175049207e-02,2.831862676175053370e-02,1.102158821746081485e-01 51 | 2.832780200755927180e-02,2.832780200755931344e-02,1.133598296328491156e-01 52 | 2.833197689469857394e-02,2.833197689469861558e-02,1.156974792961424359e-01 53 | 2.833421231506406263e-02,2.833421231506410773e-02,1.174125004322811305e-01 54 | 2.833576659418328078e-02,2.833576659418332241e-02,1.186553737005422576e-01 55 | 2.833670540799115845e-02,2.833670540799120008e-02,1.195477598987154388e-01 56 | 2.833705531157401300e-02,2.833705531157405463e-02,1.201843395533998271e-01 57 | 2.833716166481034873e-02,2.833716166481038343e-02,1.206355275723220433e-01 58 | 2.833730390267619192e-02,2.833730390267621968e-02,1.209527767945846322e-01 59 | 2.833747833642108818e-02,2.833747833642111594e-02,1.211740989447480377e-01 60 | 2.833753078583554560e-02,2.833753078583557683e-02,1.213276522556781395e-01 61 | 2.833749440898133617e-02,2.833749440898137434e-02,1.214338003360778129e-01 62 | 2.833746715795055701e-02,2.833746715795059865e-02,1.215069140433496370e-01 63 | 2.833749307849509763e-02,2.833749307849513926e-02,1.215570268141166060e-01 64 | 2.833752726254695764e-02,2.833752726254699927e-02,1.215911983537333596e-01 65 | 2.833753088082628310e-02,2.833753088082632821e-02,1.216144109861667238e-01 66 | 2.833751006259400268e-02,2.833751006259405472e-02,1.216301482056167255e-01 67 | 2.833749754299332013e-02,2.833749754299337564e-02,1.216407933869101399e-01 68 | 2.833749951335249456e-02,2.833749951335254660e-02,1.216479799514872534e-01 69 | 2.833751704430369570e-02,2.833751704430374080e-02,1.216546286722123005e-01 70 | 2.833749347821995501e-02,2.833749347821999665e-02,1.216581994995937915e-01 71 | 2.833747247900960872e-02,2.833747247900964689e-02,1.216601100713556316e-01 72 | 2.833750978195194101e-02,2.833750978195198264e-02,1.216611425257271839e-01 73 | 2.833754411068102608e-02,2.833754411068106077e-02,1.216617393937644981e-01 74 | 2.833751912337407336e-02,2.833751912337410458e-02,1.216621087656154787e-01 75 | 2.833748700937398224e-02,2.833748700937401346e-02,1.216623037615510716e-01 76 | 2.833749472189594706e-02,2.833749472189598523e-02,1.216623735037212772e-01 77 | 2.833752086441945414e-02,2.833752086441950271e-02,1.216623983312501850e-01 78 | 2.833752173350964632e-02,2.833752173350970183e-02,1.216624367862603057e-01 79 | 2.833750508094315390e-02,2.833750508094320247e-02,1.216624768463275413e-01 80 | 2.833750240997071415e-02,2.833750240997075925e-02,1.216624878520722042e-01 81 | 2.833751094986215399e-02,2.833751094986219909e-02,1.216624809032538085e-01 82 | 2.833752314370259831e-02,2.833752314370265035e-02,1.216624701789604662e-01 83 | 2.833751777755263990e-02,2.833751777755269194e-02,1.216624781573304309e-01 84 | 2.833750776046251724e-02,2.833750776046256928e-02,1.216624905743951268e-01 85 | 2.833749934072389218e-02,2.833749934072393728e-02,1.216624997909604988e-01 86 | 2.833750865585367082e-02,2.833750865585371939e-02,1.216624908278141071e-01 87 | 2.833751280231593464e-02,2.833751280231598321e-02,1.216624868398858278e-01 88 | 2.833751429679689723e-02,2.833751429679694234e-02,1.216624854716755050e-01 89 | 2.833751160551346154e-02,2.833751160551350318e-02,1.216624882067426916e-01 90 | 2.833751055066919058e-02,2.833751055066922875e-02,1.216624891974655842e-01 91 | 2.833750916396796798e-02,2.833750916396801309e-02,1.216624905805316487e-01 92 | 2.833751096737269909e-02,2.833751096737274419e-02,1.216624889865502573e-01 93 | 2.833751141324071521e-02,2.833751141324075684e-02,1.216624886888655055e-01 94 | 2.833751145542211594e-02,2.833751145542216104e-02,1.216624885479710982e-01 95 | 2.833751092096536278e-02,2.833751092096541829e-02,1.216624889264350251e-01 96 | 2.833751116600751602e-02,2.833751116600757153e-02,1.216624886963744434e-01 97 | 2.833751147325162811e-02,2.833751147325168016e-02,1.216624885322891703e-01 98 | 2.833751145336533064e-02,2.833751145336537922e-02,1.216624886045009762e-01 99 | 2.833751117658737245e-02,2.833751117658742102e-02,1.216624887927991172e-01 100 | 2.833751108613920960e-02,2.833751108613925818e-02,1.216624888225212170e-01 101 | 2.833751122275759982e-02,2.833751122275764492e-02,1.216624887211270878e-01 102 | 2.833751135901490734e-02,2.833751135901494897e-02,1.216624886411473427e-01 103 | 2.833751131819866806e-02,2.833751131819870969e-02,1.216624886911681636e-01 104 | 2.833751121275967372e-02,2.833751121275971188e-02,1.216624887743006089e-01 105 | 2.833751117668870806e-02,2.833751117668874275e-02,1.216624888059290033e-01 106 | 2.833751122312528833e-02,2.833751122312532650e-02,1.216624887758667589e-01 107 | 2.833751125625143599e-02,2.833751125625147763e-02,1.216624887537031963e-01 108 | 2.833751124511513231e-02,2.833751124511517741e-02,1.216624887598513755e-01 109 | 2.833751121908245976e-02,2.833751121908250833e-02,1.216624887777751768e-01 110 | 2.833751121993455246e-02,2.833751121993460798e-02,1.216624887763413099e-01 111 | 2.833751123755851031e-02,2.833751123755856582e-02,1.216624887630666368e-01 112 | 2.833751124736657787e-02,2.833751124736662644e-02,1.216624887549048600e-01 113 | 2.833751124093203400e-02,2.833751124093207910e-02,1.216624887588252518e-01 114 | 2.833751123335318325e-02,2.833751123335322489e-02,1.216624887646607367e-01 115 | 2.833751123359109711e-02,2.833751123359113527e-02,1.216624887657923315e-01 116 | 2.833751123795240703e-02,2.833751123795244173e-02,1.216624887632633545e-01 117 | 2.833751123908032771e-02,2.833751123908036587e-02,1.216624887621059886e-01 118 | 2.833751123750899784e-02,2.833751123750903947e-02,1.216624887623675155e-01 119 | 2.833751123612993084e-02,2.833751123612996900e-02,1.216624887632041518e-01 120 | 2.833751123691217316e-02,2.833751123691221480e-02,1.216624887625969292e-01 121 | 2.833751123826525747e-02,2.833751123826530258e-02,1.216624887614129180e-01 122 | 2.833751123774540942e-02,2.833751123774545799e-02,1.216624887618386747e-01 123 | 2.833751123717634726e-02,2.833751123717639583e-02,1.216624887624459250e-01 124 | 2.833751123730651744e-02,2.833751123730656948e-02,1.216624887624420392e-01 125 | 2.833751123766854382e-02,2.833751123766859586e-02,1.216624887622769768e-01 126 | 2.833751123806722838e-02,2.833751123806727695e-02,1.216624887619570522e-01 127 | 2.833751123788132500e-02,2.833751123788137358e-02,1.216624887621769319e-01 128 | 2.833751123777166273e-02,2.833751123777170783e-02,1.216624887622503731e-01 129 | 2.833751123769673655e-02,2.833751123769678165e-02,1.216624887622939216e-01 130 | 2.833751123781724432e-02,2.833751123781728942e-02,1.216624887621649692e-01 131 | 2.833751123773882788e-02,2.833751123773887298e-02,1.216624887622631129e-01 132 | 2.833751123756077586e-02,2.833751123756082443e-02,1.216624887624419837e-01 133 | 2.833751123763234361e-02,2.833751123763238872e-02,1.216624887623563162e-01 134 | 2.833751123787110054e-02,2.833751123787113524e-02,1.216624887621162027e-01 135 | 2.833751123786493534e-02,2.833751123786496309e-02,1.216624887621236828e-01 136 | 2.833751123771186681e-02,2.833751123771189803e-02,1.216624887622875240e-01 137 | 2.833751123771612729e-02,2.833751123771616892e-02,1.216624887622853590e-01 138 | 2.833751123778210576e-02,2.833751123778215086e-02,1.216624887622171775e-01 139 | --------------------------------------------------------------------------------