From 45ce1ec1d063c1fc097a59c301449cf65650a5ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Mon, 18 Mar 2024 16:40:00 +0100 Subject: [PATCH 01/43] Add autodetect_cadet functionality from CADET-Process --- cadet/cadet.py | 132 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/cadet/cadet.py b/cadet/cadet.py index 86322d0..c9d13c2 100644 --- a/cadet/cadet.py +++ b/cadet/cadet.py @@ -186,6 +186,7 @@ def cadet_runner(self): if hasattr(self, "_cadet_runner_class") and self._cadet_runner_class is not None: return self._cadet_runner_class self.autodetect_cadet() + return self._cadet_runner @property def cadet_path(self): @@ -209,6 +210,137 @@ def cadet_path(self, value): def cadet_path(self): del self._cadet_runner + def autodetect_cadet(self): + """ + Autodetect installation CADET based on operating system and API usage. + + Returns + ------- + cadet_root : Path + Installation path of the CADET program. + """ + executable = 'cadet-cli' + if platform.system() == 'Windows': + executable += '.exe' + + # Searching for the executable in system path + path = shutil.which(executable) + + if path is None: + raise FileNotFoundError( + "Could not autodetect CADET installation. Please provide path." + ) + + cli_path = Path(path) + + cadet_root = None + if cli_path is not None: + cadet_root = cli_path.parent.parent + self.install_path = cadet_root + + return cadet_root + + @property + def install_path(self): + """str: Path to the installation of CADET. + + This can either be the root directory of the installation or the path to the + executable file 'cadet-cli'. If a file path is provided, the root directory will + be inferred. + + Raises + ------ + FileNotFoundError + If CADET cannot be found at the specified path. + + Warnings + -------- + If the specified install_path is not the root of the CADET installation, it will + be inferred from the file path. + + See Also + -------- + check_cadet + """ + return self._install_path + + @install_path.setter + def install_path(self, install_path): + """ + Set the installation path of CADET. + + Parameters + ---------- + install_path : str or Path + Path to the root of the CADET installation. + It should either be the root directory of the installation or the path + to the executable file 'cadet-cli'. + If a file path is provided, the root directory will be inferred. + """ + if install_path is None: + self._install_path = None + self.cadet_cli_path = None + self.cadet_dll_path = None + self.cadet_create_lwe_path = None + return + + install_path = Path(install_path) + + if install_path.is_file(): + cadet_root = install_path.parent.parent + warnings.warn( + "The specified install_path is not the root of the CADET installation. " + "It has been inferred from the file path." + ) + else: + cadet_root = install_path + + self._install_path = cadet_root + + cli_executable = 'cadet-cli' + lwe_executable = 'createLWE' + + if platform.system() == 'Windows': + cli_executable += '.exe' + lwe_executable += '.exe' + + cadet_cli_path = cadet_root / 'bin' / cli_executable + if cadet_cli_path.is_file(): + self.cadet_cli_path = cadet_cli_path + self.cadet_path = cadet_cli_path + else: + raise FileNotFoundError( + "CADET could not be found. Please check the path" + ) + + cadet_create_lwe_path = cadet_root / 'bin' / lwe_executable + if cadet_create_lwe_path.is_file(): + self.cadet_create_lwe_path = cadet_create_lwe_path.as_posix() + + if platform.system() == 'Windows': + dll_path = cadet_root / 'bin' / 'cadet.dll' + dll_debug_path = cadet_root / 'bin' / 'cadet_d.dll' + else: + dll_path = cadet_root / 'lib' / 'lib_cadet.so' + dll_debug_path = cadet_root / 'lib' / 'lib_cadet_d.so' + + # Look for debug dll if dll is not found. + if not dll_path.is_file() and dll_debug_path.is_file(): + dll_path = dll_debug_path + + # Look for debug dll if dll is not found. + if dll_path.is_file(): + self.cadet_dll_path = dll_path.as_posix() + + if platform.system() != 'Windows': + try: + cadet_lib_path = cadet_root / 'lib' + if cadet_lib_path.as_posix() not in os.environ['LD_LIBRARY_PATH']: + os.environ['LD_LIBRARY_PATH'] += \ + os.pathsep + cadet_lib_path.as_posix() + except KeyError: + os.environ['LD_LIBRARY_PATH'] = cadet_lib_path.as_posix() + def transform(self, x): return str.upper(x) From c71e7057719ada184609f47fcfdde4cd4ce5f308 Mon Sep 17 00:00:00 2001 From: "r.jaepel" Date: Fri, 22 Mar 2024 12:33:40 +0100 Subject: [PATCH 02/43] clean up .gitignore --- .gitignore | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index baf0fe2..c5426f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,12 @@ -################################################################################ -# This .gitignore file was automatically created by Microsoft(R) Visual Studio. -################################################################################ - + /.vs *.h5 +*.csv /__pycache__ -/fix_h5.py -/HICWang.py -/Pilot_Bypass_noBT_noC.py -/Test.py -/test_lwe.py -/comp2_1.csv -/comp2_2.csv -/comp2_comb.csv -/comp2_fraction.csv -/comp_1.csv -/ron.py /CADET.egg-info /dist /build/lib/cadet /cadet/__pycache__ /CADET_Python.egg-info +.idea +*tmp* \ No newline at end of file From a4bf77b57f2c62b56ea1eb0e42258a3f984edb2b Mon Sep 17 00:00:00 2001 From: "r.jaepel" Date: Fri, 22 Mar 2024 12:34:13 +0100 Subject: [PATCH 03/43] Update test suite fixup! Update test suite --- .github/workflows/pipeline.yml | 39 +++++- setup.cfg | 6 +- setup.py | 7 ++ tests/__init__.py | 0 {examples => tests}/common.py | 6 +- .../test_MSSMA_2comp.py | 71 ++++++----- examples/SMB.py => tests/test_SMB.py | 118 ++++++++++-------- tests/test_autodetection.py | 6 + tests/test_duplicate_keys.py | 3 +- {examples => tests}/test_lwe.py | 68 +++++----- 10 files changed, 186 insertions(+), 138 deletions(-) create mode 100644 tests/__init__.py rename {examples => tests}/common.py (90%) rename examples/MSSMA_2comp.py => tests/test_MSSMA_2comp.py (83%) rename examples/SMB.py => tests/test_SMB.py (72%) create mode 100644 tests/test_autodetection.py rename {examples => tests}/test_lwe.py (78%) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index f1adfcc..190a984 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -10,23 +10,50 @@ on: jobs: test-job: runs-on: ubuntu-latest + + defaults: + run: + shell: bash -l {0} + strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + + - name: Get Date + id: get-date + run: echo "today=$(/bin/date -u '+%Y%m%d')" >> $GITHUB_OUTPUT + shell: bash + + - name: Setup Conda Environment + uses: conda-incubator/setup-miniconda@v3 with: - python-version: ${{ matrix.python-version }} + miniforge-variant: Mambaforge + use-mamba: true + activate-environment: cadet-python + channels: conda-forge, + + - name: Cache conda + uses: actions/cache@v3 + env: + # Increase this value to reset cache if environment.yml has not changed + CACHE_NUMBER: 0 + with: + path: ${{ env.CONDA }}/envs + key: python_${{ matrix.python-version }}-${{ steps.get-date.outputs.today }}-${{ env.CACHE_NUMBER }} + - name: Test Wheel install and import run: | + mamba install python==${{ matrix.python-version }} pip install wheel python setup.py bdist_wheel cd dist pip install CADET_Python*.whl python -c "import cadet; print(cadet.__version__)" + cd .. - name: Test with pytest run: | - pip install pytest - pytest tests --rootdir=tests + pip install .[testing] + mamba install cadet -c conda-forge + pytest tests --rootdir=tests -m "not slow" diff --git a/setup.cfg b/setup.cfg index 9d5f797..23d72ac 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,7 @@ # Inside of setup.cfg [metadata] -description-file = README.md \ No newline at end of file +description-file = README.md + +[tool:pytest] +markers = + slow: marks tests as slow (deselect with '-m "not slow"') diff --git a/setup.py b/setup.py index 4d11f1d..54cf7e2 100644 --- a/setup.py +++ b/setup.py @@ -24,5 +24,12 @@ "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", ], + extras_require={ + "testing": [ + "pytest", + "matplotlib", + "pandas", + ], + }, python_requires='>=3.7', ) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/common.py b/tests/common.py similarity index 90% rename from examples/common.py rename to tests/common.py index 0caccaa..db6a9fd 100644 --- a/examples/common.py +++ b/tests/common.py @@ -7,11 +7,9 @@ root.input.model.solver.gs_type = 1 root.input.model.solver.max_krylov = 0 root.input.model.solver.max_restarts = 10 -root.input.model.solver.schur_safety = 1e-8 +root.input.model.solver.schur_safety = 1e-8 -#CADET 3.1 and CADET-dev flags are in here so that it works with both -#CADET-dev removed column from the name on the inputs and outputs since for many -#operations it no longer makes sense +# CADET 3.1 and CADET-dev flags are in here so that it works with both root.input['return'].write_solution_times = 1 root.input['return'].split_components_data = 1 root.input['return'].unit_000.write_sens_bulk = 0 diff --git a/examples/MSSMA_2comp.py b/tests/test_MSSMA_2comp.py similarity index 83% rename from examples/MSSMA_2comp.py rename to tests/test_MSSMA_2comp.py index ab3f5fa..06f6713 100644 --- a/examples/MSSMA_2comp.py +++ b/tests/test_MSSMA_2comp.py @@ -1,31 +1,17 @@ -#!/usr/bin/env python3.6 +import os -# Everything in here is based on CADET3.pdf in the same directory -# - -# Basic Python CADET file based interface compatible with CADET 3.0 and 3.1 -# Some additional fields have been added so that the generated simulations will also -# work in 3.1 and where those differences are I have noted them. -# This whole file follows the CADET pdf documentation. I have broken the system up into many -# functions in order to make it simpler and to make code reuse easier. - -# Normally the main function is placed at the bottom of the file but I have placed it at the top so that -# This interface is more like a tutorial and can be read from the top down and any given function -# can be drilled into to learn more about it. - -import numpy - -#use to render results import matplotlib.pyplot as plt +import numpy +import pandas +import pytest from cadet import Cadet -Cadet.cadet_path = "C:/Users/kosh_000/cadet_build/CADET-dev/cadet3.1-win7-x64/bin/cadet-cli.exe" +from tests import common -import common -import pandas def gen_fraction_times(start, stop, bins): - return numpy.linspace(start, stop, bins+1, endpoint=True) + return numpy.linspace(start, stop, bins + 1, endpoint=True) + def gen_fractions(fraction_times, sim): nComp = sim.root.input.model.unit_000.ncomp @@ -36,24 +22,29 @@ def gen_fractions(fraction_times, sim): for idx, (start, stop) in enumerate(zip(fraction_times[:-1], fraction_times[1:])): selected = (times >= start) & (times <= stop) - temp = {'Start':start, 'Stop':stop} + temp = {'Start': start, 'Stop': stop} for comp in range(nComp): local_times = times[selected] local_values = sim.root.output.solution.unit_001["solution_outlet_comp_%03d" % comp][selected] - + temp[str(comp)] = numpy.trapz(local_values, local_times) / (stop - start) df = df.append(temp, ignore_index=True) return df + def main(): + if not os.path.exists("tmp"): + os.makedirs("tmp") simulation = Cadet(common.common.root) - simulation.filename = "MSSMA_2comp.h5" + simulation.filename = "tmp/MSSMA_2comp.h5" createSimulation(simulation) simulation.save() data = simulation.run() print(data) simulation.load() plotSimulation(simulation) + return simulation + def createSimulation(simulation): root = simulation.root @@ -97,18 +88,18 @@ def createSimulation(simulation): root.input.model.unit_001.par_surfdiffusion = [0.0, 0.0, 0.0, 0.0, 0.0] root.input.model.unit_001.unit_type = 'GENERAL_RATE_MODEL' root.input.model.unit_001.velocity = 0.001 - + root.input.model.unit_001.adsorption.is_kinetic = 1 root.input.model.unit_001.adsorption.mssma_ka = [0.0, 1E11, 8E6, 1E11, 8E6] root.input.model.unit_001.adsorption.mssma_kd = [0.0, 6E11, 2E16, 6E11, 2E16] root.input.model.unit_001.adsorption.mssma_lambda = 225 root.input.model.unit_001.adsorption.mssma_nu = [0.0, 10, 25, 20, 50] - root.input.model.unit_001.adsorption.mssma_sigma = [0.0, 48, 66, 48*2, 66*2] + root.input.model.unit_001.adsorption.mssma_sigma = [0.0, 48, 66, 48 * 2, 66 * 2] root.input.model.unit_001.adsorption.mssma_refq = 225 root.input.model.unit_001.adsorption.mssma_refc0 = 520.0 root.input.model.unit_001.adsorption.mssma_rates = [0.0, 0.0, 1e20, 10, 0.0, 0.0, 1e20, 10, 0.0] - root.input.model.unit_001.discretization.nbound = [1, 2,2] + root.input.model.unit_001.discretization.nbound = [1, 2, 2] root.input.model.unit_001.discretization.ncol = 50 root.input.model.unit_001.discretization.npar = 5 @@ -122,12 +113,14 @@ def createSimulation(simulation): root.input.solver.consistent_init_mode = 3 + def plotSimulation(simulation): f, (ax1, ax2) = plt.subplots(1, 2, figsize=[16, 8]) plotInlet(ax1, simulation) plotOutlet(ax2, simulation) f.tight_layout() - plt.show() + plt.savefig("tmp/MSSMA.png") + def plotInlet(axis, simulation): solution_times = simulation.root.output.solution.solution_times @@ -139,7 +132,7 @@ def plotInlet(axis, simulation): axis.set_title("Inlet") axis.plot(solution_times, inlet_salt, 'b-', label="Salt") axis.set_xlabel('time (s)') - + # Make the y-axis label, ticks and tick labels match the line color. axis.set_ylabel('mMol Salt', color='b') axis.tick_params('y', colors='b') @@ -150,7 +143,6 @@ def plotInlet(axis, simulation): axis2.set_ylabel('mMol Protein', color='r') axis2.tick_params('y', colors='r') - lines, labels = axis.get_legend_handles_labels() lines2, labels2 = axis2.get_legend_handles_labels() axis2.legend(lines + lines2, labels + labels2, loc=0) @@ -164,22 +156,22 @@ def plotOutlet(axis, simulation): outlet_p2 = simulation.root.output.solution.unit_002.solution_outlet_comp_002 data = numpy.vstack([solution_times, outlet_p1]).transpose() - numpy.savetxt('comp2_1.csv', data, delimiter=',') + numpy.savetxt('tmp/comp2_1.csv', data, delimiter=',') data = numpy.vstack([solution_times, outlet_p2]).transpose() - numpy.savetxt('comp2_2.csv', data, delimiter=',') + numpy.savetxt('tmp/comp2_2.csv', data, delimiter=',') data = numpy.vstack([solution_times, outlet_p1 + outlet_p2]).transpose() - numpy.savetxt('comp2_comb.csv', data, delimiter=',') + numpy.savetxt('tmp/comp2_comb.csv', data, delimiter=',') fraction_times = gen_fraction_times(6000, 14000, 10) df = gen_fractions(fraction_times, simulation) - df.to_csv("comp2_fraction.csv", columns=('Start', 'Stop', '1'), index=False) + df.to_csv("tmp/comp2_fraction.csv", columns=('Start', 'Stop', '1'), index=False) axis.set_title("Output") axis.plot(solution_times, outlet_salt, 'b-', label="Salt") axis.set_xlabel('time (s)') - + # Make the y-axis label, ticks and tick labels match the line color. axis.set_ylabel('mMol Salt', color='b') axis.tick_params('y', colors='b') @@ -190,13 +182,20 @@ def plotOutlet(axis, simulation): axis2.set_ylabel('mMol Protein', color='r') axis2.tick_params('y', colors='r') - lines, labels = axis.get_legend_handles_labels() lines2, labels2 = axis2.get_legend_handles_labels() axis2.legend(lines + lines2, labels + labels2, loc=0) +@pytest.mark.slow +def test_MSSMA_2comp(): + sim = main() + assert isinstance(sim.root.output.solution.unit_002.solution_outlet_comp_001, numpy.ndarray) + assert isinstance(sim.root.output.solution.unit_002.solution_outlet_comp_000, numpy.ndarray) + + if __name__ == "__main__": import sys + print(sys.version) main() diff --git a/examples/SMB.py b/tests/test_SMB.py similarity index 72% rename from examples/SMB.py rename to tests/test_SMB.py index df13e12..07a7e22 100644 --- a/examples/SMB.py +++ b/tests/test_SMB.py @@ -1,65 +1,67 @@ -#!/usr/bin/env python3.6 +# Basic 4-zone SMB setup -#Basic 4-zone SMB setup - -import numpy as np import math +import os.path -from cadet import Cadet -#Cadet.cadet_path = "C:/Users/kosh_000/cadet_build/CADET-dev/MS_SMKL_RELEASE/bin/cadet-cli.exe" -Cadet.cadet_path = "C:/Users/kosh_000/cadet_build/CADET/VCPKG/bin/cadet-cli.exe" - -#use to render results import matplotlib.pyplot as plt +import numpy +import pytest + +from cadet import Cadet -#number of columns in a cycle +# number of columns in a cycle cycle_size = 8 -#number of cycles +# number of cycles cycles = 4 -#number of times flows have to be expanded for a 4-zone model -repeat_size = int(cycle_size/4) +# number of times flows have to be expanded for a 4-zone model +repeat_size = int(cycle_size / 4) + def gen_connections(units, cycle_size, size, step, flows, flows_static): temp = [] - connections = list(zip(units, np.roll(units,-1), flows)) - io = np.roll(units, step)[[0, size*2, size-1, size*3-1]] + connections = list(zip(units, numpy.roll(units, -1), flows)) + io = numpy.roll(units, step)[[0, size * 2, size - 1, size * 3 - 1]] ios = list(zip([0, 1, 2, 3], io)) for connection in connections: temp.append([connection[0], connection[1], -1, -1, connection[2]]) - #inputs + # inputs idx = 0 for io in ios[:2]: temp.append([io[0], io[1], -1, -1, flows_static[idx]]) - idx+=1; - #outputs + idx += 1 + # outputs for io in ios[2:]: temp.append([io[1], io[0], -1, -1, flows_static[idx]]) - idx+=1; - return np.array(temp) + idx += 1 + return numpy.array(temp) + def expand_flow(seq, inlet1, inlet2, number): "expand the flows for smb, this is more complex since we need link values" temp = [] - temp.extend([seq[3] + inlet1] * (number-1)) + temp.extend([seq[3] + inlet1] * (number - 1)) temp.append(seq[0]) - temp.extend([seq[0]] * (number-1)) + temp.extend([seq[0]] * (number - 1)) temp.append(seq[1]) - temp.extend([seq[1] + inlet2] * (number-1)) + temp.extend([seq[1] + inlet2] * (number - 1)) temp.append(seq[2]) - temp.extend([seq[2]] * (number-1)) + temp.extend([seq[2]] * (number - 1)) temp.append(seq[3]) return temp + def main(): + if not os.path.exists("tmp"): + os.makedirs("tmp") smb = Cadet() - smb.filename = 'F:/temp/SMB.h5' + smb.filename = 'tmp/SMB.h5' createSimulation(smb) print("Simulated Created") smb.save() @@ -67,6 +69,8 @@ def main(): smb.load() print("Simulation Run") plotSimulation(smb) + return smb + def createSimulation(simulation): simulation.root.input.model.nunits = 4 + cycle_size @@ -76,27 +80,31 @@ def createSimulation(simulation): simulation.root.input.model.solver.max_restarts = 0 simulation.root.input.model.solver.schur_safety = 1e-8 - - #setup connections + # setup connections simulation.root.input.model.connections.nswitches = cycle_size - units = range(4, 4+cycle_size) + units = range(4, 4 + cycle_size) flows = expand_flow([7.66E-07, 7.66E-07, 8.08E-07, 8.08E-07], 0.98e-7, 1.96e-07, repeat_size) - flows_static = np.array([0.98e-7, 1.96e-7, 1.4e-7, 1.54e-7]) + flows_static = numpy.array([0.98e-7, 1.96e-7, 1.4e-7, 1.54e-7]) for i in range(cycle_size): simulation.root.input.model.connections["switch_%03d" % i].section = i - simulation.root.input.model.connections["switch_%03d" % i].connections = gen_connections(units, cycle_size, repeat_size, -i, np.array(list(np.roll(flows, i))), flows_static ) - - #setup inlets + simulation.root.input.model.connections["switch_%03d" % i].connections = gen_connections(units, cycle_size, + repeat_size, -i, + numpy.array(list( + numpy.roll(flows, + i))), + flows_static) + + # setup inlets simulation.root.input.model.unit_000.inlet_type = 'PIECEWISE_CUBIC_POLY' simulation.root.input.model.unit_000.ncomp = 2 simulation.root.input.model.unit_000.unit_type = 'INLET' for i in range(cycle_size): - #section - simulation.root.input.model.unit_000["sec_%03d" % i].const_coeff = [0.55/180.16, 0.55/180.16] + # section + simulation.root.input.model.unit_000["sec_%03d" % i].const_coeff = [0.55 / 180.16, 0.55 / 180.16] simulation.root.input.model.unit_000["sec_%03d" % i].lin_coeff = [0.0, 0.0] simulation.root.input.model.unit_000["sec_%03d" % i].quad_coeff = [0.0, 0.0] simulation.root.input.model.unit_000["sec_%03d" % i].cube_coeff = [0.0, 0.0] @@ -106,23 +114,22 @@ def createSimulation(simulation): simulation.root.input.model.unit_001.unit_type = 'INLET' for i in range(cycle_size): - #section + # section simulation.root.input.model.unit_001["sec_%03d" % i].const_coeff = [0.0, 0.0] simulation.root.input.model.unit_001["sec_%03d" % i].lin_coeff = [0.0, 0.0] simulation.root.input.model.unit_001["sec_%03d" % i].quad_coeff = [0.0, 0.0] simulation.root.input.model.unit_001["sec_%03d" % i].cube_coeff = [0.0, 0.0] - #create columns + # create columns for unit in range(4, 4 + cycle_size): - simulation.root.input.model["unit_%03d" % unit].unit_type = 'GENERAL_RATE_MODEL' col = simulation.root.input.model["unit_%03d" % unit] col.ncomp = 2 - col.cross_section_area = math.pi * (0.02**2)/4.0 + col.cross_section_area = math.pi * (0.02 ** 2) / 4.0 col.col_dispersion = 3.8148e-20 - col.col_length = 0.25/repeat_size + col.col_length = 0.25 / repeat_size col.col_porosity = 0.83 col.init_c = [0.0, 0.0] col.init_q = [0.0, 0.0] @@ -154,14 +161,14 @@ def createSimulation(simulation): col.discretization.weno.weno_eps = 1e-12 col.discretization.weno.weno_order = 3 - #create outlets + # create outlets simulation.root.input.model.unit_002.ncomp = 2 simulation.root.input.model.unit_002.unit_type = 'OUTLET' simulation.root.input.model.unit_003.ncomp = 2 simulation.root.input.model.unit_003.unit_type = 'OUTLET' - #create output information + # create output information simulation.root.input['return'].write_solution_times = 1 @@ -192,12 +199,13 @@ def createSimulation(simulation): ret.unit_003.write_solution_column_outlet = 1 ret.unit_003.write_solution_flux = 0 ret.unit_003.write_solution_particle = 0 - + simulation.root.input.solver.nthreads = 0 - simulation.root.input.solver.user_solution_times = np.linspace(0, cycles*180*4, 1000*cycle_size*cycles) - simulation.root.input.solver.sections.nsec = cycle_size*cycles - simulation.root.input.solver.sections.section_continuity = [0] * (cycle_size*cycles -1) - simulation.root.input.solver.sections.section_times = [float(i) * 180*4.0/cycle_size for i in range(cycle_size*cycles+1)] + simulation.root.input.solver.user_solution_times = numpy.linspace(0, cycles * 180 * 4, 1000 * cycle_size * cycles) + simulation.root.input.solver.sections.nsec = cycle_size * cycles + simulation.root.input.solver.sections.section_continuity = [0] * (cycle_size * cycles - 1) + simulation.root.input.solver.sections.section_times = [float(i) * 180 * 4.0 / cycle_size for i in + range(cycle_size * cycles + 1)] simulation.root.input.solver.time_integrator.abstol = 1e-10 simulation.root.input.solver.time_integrator.algtol = 1e-10 @@ -215,8 +223,7 @@ def plotSimulation(simulation): r_0 = simulation.root.output.solution.unit_003.solution_outlet_comp_000 r_1 = simulation.root.output.solution.unit_003.solution_outlet_comp_001 - - fig = plt.figure(figsize=[10, 2*10]) + fig = plt.figure(figsize=[10, 2 * 10]) graph = fig.add_subplot(2, 1, 1) graph.set_title("Extract") @@ -224,17 +231,28 @@ def plotSimulation(simulation): graph.plot(solution_times, e_0, 'r', label='1') graph.plot(solution_times, e_1, 'g', label='2') graph.legend() - + graph = fig.add_subplot(2, 1, 2) graph.set_title("Raffinate") graph.plot(solution_times, r_0, 'r', label='1') graph.plot(solution_times, r_1, 'g', label='2') graph.legend() - plt.show() + plt.savefig("tmp/SMB.png") + + +@pytest.mark.slow +def test_SMB(): + from datetime import datetime + start = datetime.now() + sim = main() + print(datetime.now() - start) + assert isinstance(sim.root.output.solution.unit_003.solution_outlet_comp_001, numpy.ndarray) + assert isinstance(sim.root.output.solution.unit_003.solution_outlet_comp_000, numpy.ndarray) if __name__ == "__main__": import sys + print(sys.version) main() diff --git a/tests/test_autodetection.py b/tests/test_autodetection.py new file mode 100644 index 0000000..8401049 --- /dev/null +++ b/tests/test_autodetection.py @@ -0,0 +1,6 @@ +from cadet import Cadet + + +def test_autodetection(): + sim = Cadet() + assert sim.cadet_runner is not None diff --git a/tests/test_duplicate_keys.py b/tests/test_duplicate_keys.py index b148b87..53ed45e 100644 --- a/tests/test_duplicate_keys.py +++ b/tests/test_duplicate_keys.py @@ -1,6 +1,7 @@ -import pytest import tempfile +import pytest + from cadet import Cadet diff --git a/examples/test_lwe.py b/tests/test_lwe.py similarity index 78% rename from examples/test_lwe.py rename to tests/test_lwe.py index e134cf5..bd0acad 100644 --- a/examples/test_lwe.py +++ b/tests/test_lwe.py @@ -1,27 +1,13 @@ -#!/usr/bin/env python3.6 - -# Everything in here is based on CADET3.pdf in the same directory -# - -# Basic Python CADET file based interface compatible with CADET 3.0 and 3.1 -# Some additional fields have been added so that the generated simulations will also -# work in 3.1 and where those differences are I have noted them. -# This whole file follows the CADET pdf documentation. I have broken the system up into many -# functions in order to make it simpler and to make code reuse easier. - -# Normally the main function is placed at the bottom of the file but I have placed it at the top so that +# Normally the main function is placed at the bottom of the file, but I have placed it at the top so that # This interface is more like a tutorial and can be read from the top down and any given function # can be drilled into to learn more about it. +import os -#use to render results import matplotlib.pyplot as plt - import numpy from cadet import Cadet -import common - -Cadet.cadet_path = "C:/Users/kosh_000/cadet_build/CADET-dev/MS_SMKL_RELEASE/bin/cadet-cli.exe" +from tests import common # Helper functions that make it easier to set the values in the HDF5 file @@ -29,14 +15,18 @@ # below match those types. def main(): + if not os.path.exists("tmp"): + os.makedirs("tmp") simulation = Cadet(common.common.root) - simulation.filename = "f:/temp/LWE.h5" + simulation.filename = "tmp/LWE.h5" createSimulation(simulation) simulation.save() simulation.run() simulation.load() plotSimulation(simulation) + return simulation + def createSimulation(simulation): root = simulation.root @@ -81,11 +71,10 @@ def createSimulation(simulation): root.input.model.unit_001.par_surfdiffusion = [0.0, 0.0] root.input.model.unit_001.unit_type = 'GENERAL_RATE_MODEL' - #root.input.model.unit_001.velocity = 1 - #root.input.model.unit_001.cross_section_area = 4700.352526439483 + # root.input.model.unit_001.velocity = 1 + # root.input.model.unit_001.cross_section_area = 4700.352526439483 root.input.model.unit_001.velocity = 5.75e-4 - root.input.model.unit_001.adsorption.is_kinetic = 0 root.input.model.unit_001.adsorption.sma_ka = [0.0, 35.5] root.input.model.unit_001.adsorption.sma_kd = [0.0, 1000.0] @@ -104,38 +93,39 @@ def createSimulation(simulation): root.input.solver.sections.nsec = 3 root.input.solver.sections.section_continuity = [0, 0] root.input.solver.sections.section_times = [0.0, 10.0, 90.0, 1500.0] - + + def plotSimulation(simulation): f, (ax1, ax2) = plt.subplots(1, 2, figsize=[16, 8]) plotInlet(ax1, simulation) plotOutlet(ax2, simulation) f.tight_layout() - plt.show() + plt.savefig("tmp/lwe.png") + def plotInlet(axis, simulation): solution_times = simulation.root.output.solution.solution_times inlet_salt = simulation.root.output.solution.unit_000.solution_inlet_comp_000 inlet_p1 = simulation.root.output.solution.unit_000.solution_inlet_comp_001 - #inlet_p2 = simulation.root.output.solution.unit_000.solution_inlet_comp_002 - #inlet_p3 = simulation.root.output.solution.unit_000.solution_inlet_comp_003 + # inlet_p2 = simulation.root.output.solution.unit_000.solution_inlet_comp_002 + # inlet_p3 = simulation.root.output.solution.unit_000.solution_inlet_comp_003 axis.set_title("Inlet") axis.plot(solution_times, inlet_salt, 'b-', label="Salt") axis.set_xlabel('time (s)') - + # Make the y-axis label, ticks and tick labels match the line color. axis.set_ylabel('mMol Salt', color='b') axis.tick_params('y', colors='b') axis2 = axis.twinx() axis2.plot(solution_times, inlet_p1, 'r-', label="P1") - #axis2.plot(solution_times, inlet_p2, 'g-', label="P2") - #axis2.plot(solution_times, inlet_p3, 'k-', label="P3") + # axis2.plot(solution_times, inlet_p2, 'g-', label="P2") + # axis2.plot(solution_times, inlet_p3, 'k-', label="P3") axis2.set_ylabel('mMol Protein', color='r') axis2.tick_params('y', colors='r') - lines, labels = axis.get_legend_handles_labels() lines2, labels2 = axis2.get_legend_handles_labels() axis2.legend(lines + lines2, labels + labels2, loc=0) @@ -146,32 +136,30 @@ def plotOutlet(axis, simulation): outlet_salt = simulation.root.output.solution.unit_002.solution_outlet_comp_000 outlet_p1 = simulation.root.output.solution.unit_002.solution_outlet_comp_001 - #outlet_p2 = simulation.root.output.solution.unit_002.solution_outlet_comp_002 - #outlet_p3 = simulation.root.output.solution.unit_002.solution_outlet_comp_003 + # outlet_p2 = simulation.root.output.solution.unit_002.solution_outlet_comp_002 + # outlet_p3 = simulation.root.output.solution.unit_002.solution_outlet_comp_003 axis.set_title("Output") axis.plot(solution_times, outlet_salt, 'b-', label="Salt") axis.set_xlabel('time (s)') - + # Make the y-axis label, ticks and tick labels match the line color. axis.set_ylabel('mMol Salt', color='b') axis.tick_params('y', colors='b') axis2 = axis.twinx() axis2.plot(solution_times, outlet_p1, 'r-', label="P1") - #axis2.plot(solution_times, outlet_p2, 'g-', label="P2") - #axis2.plot(solution_times, outlet_p3, 'k-', label="P3") + # axis2.plot(solution_times, outlet_p2, 'g-', label="P2") + # axis2.plot(solution_times, outlet_p3, 'k-', label="P3") axis2.set_ylabel('mMol Protein', color='r') axis2.tick_params('y', colors='r') - lines, labels = axis.get_legend_handles_labels() lines2, labels2 = axis2.get_legend_handles_labels() axis2.legend(lines + lines2, labels + labels2, loc=0) -if __name__ == "__main__": - import sys - print(sys.version) - main() - +def test_lwe(): + sim = main() + assert isinstance(sim.root.output.solution.unit_002.solution_outlet_comp_001, numpy.ndarray) + assert isinstance(sim.root.output.solution.unit_002.solution_outlet_comp_000, numpy.ndarray) From 52e0c6c83848135be1aa3b0d0fdfc52b47f519ee Mon Sep 17 00:00:00 2001 From: "r.jaepel" Date: Fri, 22 Mar 2024 12:34:23 +0100 Subject: [PATCH 04/43] Clean up repository --- examples/CSTR.py | 483 --------------------- examples/Cadet_Python.py | 266 ------------ examples/Cadet_Python_Scoop.py | 361 --------------- examples/HICWang.py | 172 -------- examples/MSSMA.py | 166 ------- examples/MSSMA2.py | 323 -------------- examples/Pilot_Bypass_noBT_noC.py | 198 --------- examples/Test.py | 182 -------- examples/__pycache__/common.cpython-37.pyc | Bin 1589 -> 0 bytes examples/cadet_json.py | 29 -- examples/cadet_library.py | 291 ------------- examples/dll_test.py | 40 -- examples/fix_h5.py | 10 - examples/library_test.py | 11 - examples/ron.py | 176 -------- 15 files changed, 2708 deletions(-) delete mode 100644 examples/CSTR.py delete mode 100644 examples/Cadet_Python.py delete mode 100644 examples/Cadet_Python_Scoop.py delete mode 100644 examples/HICWang.py delete mode 100644 examples/MSSMA.py delete mode 100644 examples/MSSMA2.py delete mode 100644 examples/Pilot_Bypass_noBT_noC.py delete mode 100644 examples/Test.py delete mode 100644 examples/__pycache__/common.cpython-37.pyc delete mode 100644 examples/cadet_json.py delete mode 100644 examples/cadet_library.py delete mode 100644 examples/dll_test.py delete mode 100644 examples/fix_h5.py delete mode 100644 examples/library_test.py delete mode 100644 examples/ron.py diff --git a/examples/CSTR.py b/examples/CSTR.py deleted file mode 100644 index 9b35d96..0000000 --- a/examples/CSTR.py +++ /dev/null @@ -1,483 +0,0 @@ -#!/usr/bin/env python3.6 - -# Everything in here is based on CADET3.pdf in the same directory -# - -# Basic Python CADET file based interface compatible with CADET 3.0 and 3.1 -# Some additional fields have been added so that the generated simulations will also -# work in 3.1 and where those differences are I have noted them. -# This whole file follows the CADET pdf documentation. I have broken the system up into many -# functions in order to make it simpler and to make code reuse easier. - -# Normally the main function is placed at the bottom of the file but I have placed it at the top so that -# This interface is more like a tutorial and can be read from the top down and any given function -# can be drilled into to learn more about it. - -# The core part of python with CADET is HDF5 and numpy -import h5py -import numpy as np - -#used to run CADET -import subprocess -import io - -#use to render results -import matplotlib.pyplot as plt - -import types - -#location of the cadet binary -cadet_location = "C:/Users/kosh_000/cadet_build/CADET-dev/MS_SMKL_RELEASE/bin/cadet-cli.exe" - -# Helper functions that make it easier to set the values in the HDF5 file -# In the CADET pdf file each value in the hdf5 file has a type. The functions -# below match those types. - -def set_int(node, nameH5, value): - "set one or more integers in the hdf5 file" - data = np.array(value, dtype="i4") - if node.get(nameH5, None) is not None: - del node[nameH5] - node.create_dataset(nameH5, data=data, maxshape=tuple(None for i in range(data.ndim)), fillvalue=[0]) - -def set_double(node, nameH5, value): - "set one or more doubles in the hdf5 file" - data = np.array(value, dtype="f8") - if node.get(nameH5, None) is not None: - del node[nameH5] - node.create_dataset(nameH5, data=data, maxshape=tuple(None for i in range(data.ndim)), fillvalue=[0]) - -def set_string(node, nameH5, value): - "set a string value in the hdf5 file" - if isinstance(value, list): - dtype = 'S' + str(len(value[0])+1) - else: - dtype = 'S' + str(len(value)+1) - data = np.array(value, dtype=dtype) - if node.get(nameH5, None) is not None: - del node[nameH5] - node.create_dataset(nameH5, data=data) - - -def main(): - obj = types.SimpleNamespace() - obj.filename = "CSTR_V1_C1.h5" - obj.volume = 1 - obj.conc = 1 - obj.linear = 0 - obj.flowIn = 1 - obj.flowOut = 1 - obj.porosity = 1 - createSimulation(obj) - runSimulation(obj) - plotSimulation(obj) - - obj.filename = "CSTR_V1_C2.h5" - obj.volume = 1 - obj.conc = 2 - obj.flowIn = 1 - obj.flowOut = 1 - createSimulation(obj) - runSimulation(obj) - plotSimulation(obj) - - obj.filename = "CSTR_V1_C2_F2_F1.h5" - obj.volume = 1 - obj.conc = 2 - obj.flowIn = 2 - obj.flowOut = 1 - createSimulation(obj) - runSimulation(obj) - plotSimulation(obj) - - obj.filename = "CSTR_V10_C1_F2_F1.h5" - obj.volume = 10 - obj.conc = 1 - obj.flowIn = 2 - obj.flowOut = 1 - createSimulation(obj) - runSimulation(obj) - plotSimulation(obj) - - obj.filename = "CSTR_V10_C2.h5" - obj.volume = 10 - obj.conc = 2 - obj.flowIn = 1 - obj.flowOut = 1 - createSimulation(obj) - runSimulation(obj) - plotSimulation(obj) - - obj.filename = "CSTR_V100_C1_F2_F2.h5" - obj.volume = 100 - obj.conc = 1 - obj.flowIn = 2 - obj.flowOut = 2 - createSimulation(obj) - runSimulation(obj) - plotSimulation(obj) - - obj.filename = "CSTR_V100_C2.h5" - obj.volume = 100 - obj.conc = 2 - obj.flowIn = 1 - obj.flowOut = 1 - createSimulation(obj) - runSimulation(obj) - plotSimulation(obj) - - obj.filename = "CSTR_V100_C2_F2_F1.h5" - obj.volume = 100 - obj.conc = 2 - obj.flowIn = 2 - obj.flowOut = 1 - createSimulation(obj) - runSimulation(obj) - plotSimulation(obj) - - obj.filename = "CSTR_sens.h5" - obj.porosity = 0.5 - obj.volume = 1 - obj.conc = 2 - obj.linear = 0.01 - obj.flowIn = 2 - obj.flowOut = 1 - obj.filter = 0.01 - createSimulation(obj) - runSimulation(obj) - plotSimulation(obj) - - obj.filename = "CSTR_con_down.h5" - obj.porosity = 0.5 - obj.volume = 1 - obj.conc = 2 - 1e-6 - obj.linear = 0.01 - obj.flowIn = 2 - obj.flowOut = 1 - obj.filter = 0.01 - createSimulation(obj) - runSimulation(obj) - plotSimulation(obj) - - obj.filename = "CSTR_con_up.h5" - obj.porosity = 0.5 - obj.volume = 1 - obj.conc = 2 + 1e-6 - obj.linear = 0.01 - obj.flowIn = 2 - obj.flowOut = 1 - obj.filter = 0.01 - createSimulation(obj) - runSimulation(obj) - plotSimulation(obj) - - obj.filename = "CSTR_lin_down.h5" - obj.porosity = 0.5 - obj.volume = 1 - obj.conc = 2 - obj.linear = 0.01 - 1e-6 - obj.flowIn = 2 - obj.flowOut = 1 - obj.filter = 0.01 - createSimulation(obj) - runSimulation(obj) - plotSimulation(obj) - - obj.filename = "CSTR_lin_up.h5" - obj.porosity = 0.5 - obj.volume = 1 - obj.conc = 2 - obj.linear = 0.01 + 1e-6 - obj.flowIn = 2 - obj.flowOut = 1 - obj.filter = 0.01 - createSimulation(obj) - runSimulation(obj) - plotSimulation(obj) - - obj.filename = "CSTR_por_up.h5" - obj.volume = 1 - obj.conc = 2 - obj.linear = 0.01 - obj.porosity = 0.5 + 1e-6 - obj.flowIn = 2 - obj.flowOut = 1 - obj.filter = 0.01 - createSimulation(obj) - runSimulation(obj) - plotSimulation(obj) - - obj.filename = "CSTR_por_down.h5" - obj.volume = 1 - obj.conc = 2 - obj.linear = 0.01 - obj.porosity = 0.5 - 1e-6 - obj.flowIn = 2 - obj.flowOut = 1 - obj.filter = 0.01 - createSimulation(obj) - runSimulation(obj) - plotSimulation(obj) - - obj.filename = "CSTR_filter_up.h5" - obj.volume = 1 - obj.conc = 2 - obj.linear = 0.01 - obj.porosity = 0.5 - obj.flowIn = 2 - obj.flowOut = 1 - obj.filter = 0.01 + 1e-6 - createSimulation(obj) - runSimulation(obj) - plotSimulation(obj) - - obj.filename = "CSTR_filter_down.h5" - obj.volume = 1 - obj.conc = 2 - obj.linear = 0.01 - obj.porosity = 0.5 - obj.flowIn = 2 - obj.flowOut = 1 - obj.filter = 0.01 - 1e-6 - createSimulation(obj) - runSimulation(obj) - plotSimulation(obj) - -def createSimulation(obj): - with h5py.File(obj.filename, 'w') as f: - createInput(f, obj) - -def createInput(f, obj): - input = f.create_group("input") - createModel(input, obj) - createReturn(input) - createSolver(input) - createSensitivity(input) - -def createModel(input, obj): - model = input.create_group("model") - set_int(model, 'NUNITS', 3) - - # Part of CADET 3.1 - solver = model.create_group('solver') - set_int(solver, 'GS_TYPE', 1) - set_int(solver, 'MAX_KRYLOV', 0) - set_int(solver, 'MAX_RESTARTS', 0) - set_double(solver, 'SCHUR_SAFETY', 1.0e-8) - - createConnections(model, obj) - createInlet(model, obj) - createColumn(model, obj) - createOutlet(model) - -def createConnections(model, obj): - connections = model.create_group("connections") - set_int(connections, 'NSWITCHES', 1) - createSwitch(connections, obj) - -def createSwitch(connections, obj): - """Create a switch in the system. In 3.0 these are very limited but in 3.1 they allow arbitrary connections between unit operations. - The format is a set of 4 numbers (Source Unit Operation ID, Destination Unit Operation ID, Source Component ID, Destination Component ID. If the - Source and Destination Component IDs are set to -1 that means connect all to all and this is the most common setup.""" - - # CADET uses 0 based numbers and 3 digits to identify anything there are multiples of like unit operations, sections, switches etc - switch_000 = connections.create_group("switch_000") - - #Connect all of Inlet [0] to Column [1] and Column [1] to Outlet [2] - set_int(switch_000, 'CONNECTIONS', [0, 1, -1, -1, obj.flowIn, - 1, 2, -1, -1, obj.flowOut]) - set_int(switch_000, 'SECTION', 0) - -def createInlet(model, obj): - inlet = model.create_group("unit_000") - - set_string(inlet, 'INLET_TYPE', 'PIECEWISE_CUBIC_POLY') - set_int(inlet, 'NCOMP', 1) - set_string(inlet, 'UNIT_TYPE', 'INLET') - - sec = inlet.create_group('sec_000') - - set_double(sec, 'CONST_COEFF', [obj.conc,]) - set_double(sec, 'LIN_COEFF', [obj.linear,]) - set_double(sec, 'QUAD_COEFF', [0.0,]) - set_double(sec, 'CUBE_COEFF', [0.0,]) - -def createColumn(model, obj): - column = model.create_group('unit_001') - - set_double(column, 'INIT_C', [1.0,]) - set_double(column, 'INIT_VOLUME', obj.volume) - set_double(column, 'FLOWRATE_FILTER', [0.0,]) - set_int(column, 'NCOMP', 1) - set_string(column, 'UNIT_TYPE', 'CSTR') - -def createOutlet(model): - outlet = model.create_group('unit_002') - set_int(outlet, 'NCOMP', 1) - set_string(outlet, 'UNIT_TYPE', 'OUTLET') - -def createReturn(input): - ret = input.create_group('return') - - set_int(ret, 'WRITE_SOLUTION_TIMES', 1) - - createColumnOutput(ret) - -def createColumnOutput(ret): - column = ret.create_group('unit_001') - - set_int(column, 'WRITE_SENS_BULK', 0) - set_int(column, 'WRITE_SENS_INLET', 1) - set_int(column, 'WRITE_SENS_OUTLET', 1) - set_int(column, 'WRITE_SENS_FLUX', 0) - set_int(column, 'WRITE_SENS_PARTICLE', 0) - - set_int(column, 'WRITE_SOLUTION_BULK', 0) - set_int(column, 'WRITE_SOLUTION_INLET', 1) - set_int(column, 'WRITE_SOLUTION_OUTLET', 1) - set_int(column, 'WRITE_SOLUTION_FLUX', 0) - set_int(column, 'WRITE_SOLUTION_PARTICLE', 0) - - - column = ret.create_group('unit_002') - - set_int(column, 'WRITE_SENS_BULK', 0) - set_int(column, 'WRITE_SENS_INLET', 1) - set_int(column, 'WRITE_SENS_OUTLET', 1) - set_int(column, 'WRITE_SENS_FLUX', 0) - set_int(column, 'WRITE_SENS_PARTICLE', 0) - - set_int(column, 'WRITE_SOLUTION_BULK', 0) - set_int(column, 'WRITE_SOLUTION_INLET', 1) - set_int(column, 'WRITE_SOLUTION_OUTLET', 1) - set_int(column, 'WRITE_SOLUTION_FLUX', 0) - set_int(column, 'WRITE_SOLUTION_PARTICLE', 0) - -def createSolver(input): - solver = input.create_group("solver") - set_int(solver, 'NTHREADS', 1) - set_double(solver, 'USER_SOLUTION_TIMES', range(0, 101)) - set_int(solver, 'CONSISTENT_INIT_MODE', 1) - - createSections(solver) - createTimeIntegrator(solver) - -def createSections(solver): - sections = solver.create_group("sections") - set_int(sections, 'NSEC', 1) - set_int(sections, 'SECTION_CONTINUITY', [0,0]) - set_double(sections, 'SECTION_TIMES', [0, 100.0]) - -def createTimeIntegrator(solver): - time_integrator = solver.create_group("time_integrator") - set_double(time_integrator, 'ABSTOL', 1e-8) - set_double(time_integrator, 'ALGTOL', 1e-12) - set_double(time_integrator, 'INIT_STEP_SIZE', 1e-6) - set_int(time_integrator, 'MAX_STEPS', 10000) - set_double(time_integrator, 'RELTOL', 1e-6) - -def createSensitivity(input): - sensitivity = input.create_group('sensitivity') - set_int(sensitivity, 'NSENS', 4) - set_string(sensitivity, 'SENS_METHOD', 'ad1') - - param = sensitivity.create_group('param_000') - set_int(param, 'SENS_UNIT', 0) - set_string(param, 'SENS_NAME', 'CONST_COEFF') - set_int(param, 'SENS_COMP', 0) - set_int(param, 'SENS_REACTION', -1) - set_int(param, 'SENS_BOUNDPHASE', -1) - set_int(param, 'SENS_SECTION', 0) - - set_double(param, 'SENS_ABSTOL', 1e-6) - set_double(param, 'SENS_FACTOR', 1) - - param = sensitivity.create_group('param_001') - set_int(param, 'SENS_UNIT', 0) - set_string(param, 'SENS_NAME', 'LIN_COEFF') - set_int(param, 'SENS_COMP', 0) - set_int(param, 'SENS_REACTION', -1) - set_int(param, 'SENS_BOUNDPHASE', -1) - set_int(param, 'SENS_SECTION', 0) - - set_double(param, 'SENS_ABSTOL', 1e-6) - set_double(param, 'SENS_FACTOR', 1) - - param = sensitivity.create_group('param_002') - set_int(param, 'SENS_UNIT', 1) - set_string(param, 'SENS_NAME', 'POROSITY') - set_int(param, 'SENS_COMP', -1) - set_int(param, 'SENS_REACTION', -1) - set_int(param, 'SENS_BOUNDPHASE', -1) - set_int(param, 'SENS_SECTION', -1) - - set_double(param, 'SENS_ABSTOL', 1e-6) - set_double(param, 'SENS_FACTOR', 1) - - param = sensitivity.create_group('param_003') - set_int(param, 'SENS_UNIT', 1) - set_string(param, 'SENS_NAME', 'FLOWRATE_FILTER') - set_int(param, 'SENS_COMP', -1) - set_int(param, 'SENS_REACTION', -1) - set_int(param, 'SENS_BOUNDPHASE', -1) - set_int(param, 'SENS_SECTION', -1) - - set_double(param, 'SENS_ABSTOL', 1e-6) - set_double(param, 'SENS_FACTOR', 1) - -def runSimulation(obj): - proc = subprocess.Popen([cadet_location, obj.filename], bufsize=0, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = proc.communicate() - proc.wait() - print("CADET Output") - print(stdout) - print("CADET Errors") - print(stderr) - -def plotSimulation(obj): - with h5py.File(obj.filename, 'r') as h5: - f, (ax1, ax2) = plt.subplots(1, 2, figsize=[16, 8]) - plotInlet(ax1, h5) - plotOutlet(ax2, h5) - f.tight_layout() - plt.show() - -def plotInlet(axis, h5): - solution_times = np.array(h5['/output/solution/SOLUTION_TIMES'].value) - - inlet = np.array(h5['/output/solution/unit_001/SOLUTION_INLET_COMP_000'].value) - - - axis.set_title("Inlet") - axis.plot(solution_times, inlet, 'b-', label="Product") - axis.set_xlabel('time (s)') - - # Make the y-axis label, ticks and tick labels match the line color. - axis.set_ylabel('mMol', color='b') - axis.tick_params('y', colors='b') - - lines, labels = axis.get_legend_handles_labels() - axis.legend(lines, labels, loc=0) - - -def plotOutlet(axis, h5): - solution_times = np.array(h5['/output/solution/SOLUTION_TIMES'].value) - - outlet = np.array(h5['/output/solution/unit_001/SOLUTION_OUTLET_COMP_000'].value) - - axis.set_title("Output") - axis.plot(solution_times, outlet, 'b-', label="Product") - axis.set_xlabel('time (s)') - - # Make the y-axis label, ticks and tick labels match the line color. - axis.set_ylabel('mMol', color='b') - axis.tick_params('y', colors='b') - - lines, labels = axis.get_legend_handles_labels() - axis.legend(lines, labels, loc=0) - - -if __name__ == "__main__": - import sys - print(sys.version) - main() diff --git a/examples/Cadet_Python.py b/examples/Cadet_Python.py deleted file mode 100644 index 5559b91..0000000 --- a/examples/Cadet_Python.py +++ /dev/null @@ -1,266 +0,0 @@ -#!/usr/bin/env python3.6 - -# Everything in here is based on CADET3.pdf in the same directory -# - -# Basic Python CADET file based interface compatible with CADET 3.0 and 3.1 -# Some additional fields have been added so that the generated simulations will also -# work in 3.1 and where those differences are I have noted them. -# This whole file follows the CADET pdf documentation. I have broken the system up into many -# functions in order to make it simpler and to make code reuse easier. - -# Normally the main function is placed at the bottom of the file but I have placed it at the top so that -# This interface is more like a tutorial and can be read from the top down and any given function -# can be drilled into to learn more about it. - -#use to render results -import matplotlib.pyplot as plt - -import numpy - -from cadet import Cadet -import common - -Cadet.cadet_path = "C:/Users/kosh_000/cadet_build/CADET-dev/MS_SMKL_RELEASE/bin/cadet-cli.exe" - - -# Helper functions that make it easier to set the values in the HDF5 file -# In the CADET pdf file each value in the hdf5 file has a type. The functions -# below match those types. - -def main(): - simulation = Cadet(common.common.root) - simulation.filename = "f:/temp/LWE.h5" - createSimulation(simulation) - simulation.save() - simulation.run() - simulation.load() - - #read sensitivity data - - #sensitivity of protein 1 to nu 1 - #s1 = simulation.root.output.sensitivity.param_000.unit_001.sens_column_outlet_comp_001 - - #sensitivity of protein 2 to nu 1 - #s2 = simulation.root.output.sensitivity.param_000.unit_001.sens_column_outlet_comp_002 - - #sensitivity of protein 1 to nu 2 - #s3 = simulation.root.output.sensitivity.param_001.unit_001.sens_column_outlet_comp_001 - - #sensitivity of protein 1 to sigma 1 - #s4 = simulation.root.output.sensitivity.param_002.unit_001.sens_column_outlet_comp_001 - - #sensitivity of protein 1 to lambda - #s5 = simulation.root.output.sensitivity.param_003.unit_001.sens_column_outlet_comp_001 - - #Sensitvity of first species to loads of all species (except salt) - #s6 = simulation.root.output.sensitivity.param_004.unit_001.sens_column_outlet_comp_001 - - #Sensitvity of first species to velocity - #s7 = simulation.root.output.sensitivity.param_005.unit_001.sens_column_outlet_comp_001 - - - - plotSimulation(simulation) - -def createSimulation(simulation): - root = simulation.root - - root.input.model.nunits = 3 - root.input.model.unit_001.discretization.use_analytic_jacobian = 1 - - root.input.model.connections.nswitches = 1 - root.input.model.connections.switch_000.section = 0 - root.input.model.connections.switch_000.connections = [0, 1, -1, -1, 1.0, - 1, 2, -1, -1, 1.0] - root.input.model.unit_000.inlet_type = 'PIECEWISE_CUBIC_POLY' - root.input.model.unit_000.unit_type = 'INLET' - root.input.model.unit_000.ncomp = 4 - - root.input.model.unit_000.sec_000.const_coeff = [50.0, 1.0, 1.0, 1.0] - root.input.model.unit_000.sec_000.lin_coeff = [0.0, 0.0, 0.0, 0.0] - root.input.model.unit_000.sec_000.quad_coeff = [0.0, 0.0, 0.0, 0.0] - root.input.model.unit_000.sec_000.cube_coeff = [0.0, 0.0, 0.0, 0.0] - - root.input.model.unit_000.sec_001.const_coeff = [50.0, 0.0, 0.0, 0.0] - root.input.model.unit_000.sec_001.lin_coeff = [0.0, 0.0, 0.0, 0.0] - root.input.model.unit_000.sec_001.quad_coeff = [0.0, 0.0, 0.0, 0.0] - root.input.model.unit_000.sec_001.cube_coeff = [0.0, 0.0, 0.0, 0.0] - - root.input.model.unit_000.sec_002.const_coeff = [100.0, 0.0, 0.0, 0.0] - root.input.model.unit_000.sec_002.lin_coeff = [0.2, 0.0, 0.0, 0.0] - root.input.model.unit_000.sec_002.quad_coeff = [0.0, 0.0, 0.0, 0.0] - root.input.model.unit_000.sec_002.cube_coeff = [0.0, 0.0, 0.0, 0.0] - - root.input.model.unit_001.adsorption_model = 'STERIC_MASS_ACTION' - root.input.model.unit_001.col_dispersion = 5.75e-8 - root.input.model.unit_001.col_length = 0.014 - root.input.model.unit_001.col_porosity = 0.37 - root.input.model.unit_001.film_diffusion = [6.9e-6, 6.9e-6, 6.9e-6, 6.9e-6] - root.input.model.unit_001.init_c = [50.0, 0.0, 0.0, 0.0] - root.input.model.unit_001.init_q = [1200.0, 0.0, 0.0, 0.0] - root.input.model.unit_001.ncomp = 4 - root.input.model.unit_001.par_diffusion = [7e-9, 6.07e-9, 6.07e-9, 6.07e-9] - root.input.model.unit_001.par_porosity = 0.75 - root.input.model.unit_001.par_radius = 4.5e-5 - root.input.model.unit_001.par_surfdiffusion = [0.0, 0.0, 0.0, 0.0] - root.input.model.unit_001.unit_type = 'LUMPED_RATE_MODEL_WITH_PORES' - - #root.input.model.unit_001.velocity = 1 - #root.input.model.unit_001.cross_section_area = 4700.352526439483 - root.input.model.unit_001.velocity = 5.75e-4 - - - root.input.model.unit_001.adsorption.is_kinetic = 0 - root.input.model.unit_001.adsorption.sma_ka = [0.0, 35.5, 1.59, 7.7] - root.input.model.unit_001.adsorption.sma_kd = [0.0, 1000.0, 1000.0, 1000.0] - root.input.model.unit_001.adsorption.sma_lambda = 1200.0 - root.input.model.unit_001.adsorption.sma_nu = [0.0, 4.7, 5.29, 3.7] - root.input.model.unit_001.adsorption.sma_sigma = [0.0, 11.83, 10.6, 10.0] - - root.input.model.unit_001.discretization.nbound = [1, 1, 1, 1] - root.input.model.unit_001.discretization.ncol = 100 - root.input.model.unit_001.discretization.npar = 5 - - root.input.model.unit_002.ncomp = 4 - root.input.model.unit_002.unit_type = 'OUTLET' - - root.input.solver.user_solution_times = numpy.linspace(0, 1500, 1500) - root.input.solver.sections.nsec = 3 - root.input.solver.sections.section_continuity = [0, 0] - root.input.solver.sections.section_times = [0.0, 10.0, 90.0, 1500.0] - - #sensitivities - root.input.sensitivity.nsens = 0 - root.input.sensitivity.sens_method = 'AD1' - - #nu1 (sensitivity to protein 1 nu) - #root.input.sensitivity.param_000.sens_abstol = 1e-8 - #root.input.sensitivity.param_000.sens_boundphase = 0 - #root.input.sensitivity.param_000.sens_comp = 1 - #root.input.sensitivity.param_000.sens_factor = 1.0 - #root.input.sensitivity.param_000.sens_name = "SMA_NU" - #root.input.sensitivity.param_000.sens_reaction = -1 - #root.input.sensitivity.param_000.sens_section = -1 - #root.input.sensitivity.param_000.sens_unit = 1 - - #nu2 - #root.input.sensitivity.param_001.sens_abstol = 1e-8 - #root.input.sensitivity.param_001.sens_boundphase = 0 - #root.input.sensitivity.param_001.sens_comp = 2 - #root.input.sensitivity.param_001.sens_factor = 1.0 - #root.input.sensitivity.param_001.sens_name = "SMA_NU" - #root.input.sensitivity.param_001.sens_reaction = -1 - #root.input.sensitivity.param_001.sens_section = -1 - #root.input.sensitivity.param_001.sens_unit = 1 - - #sigma1 - #root.input.sensitivity.param_002.sens_abstol = 1e-8 - #root.input.sensitivity.param_002.sens_boundphase = 0 - #root.input.sensitivity.param_002.sens_comp = 1 - #root.input.sensitivity.param_002.sens_factor = 1.0 - #root.input.sensitivity.param_002.sens_name = "SMA_SIGMA" - #root.input.sensitivity.param_002.sens_reaction = -1 - #root.input.sensitivity.param_002.sens_section = -1 - #root.input.sensitivity.param_002.sens_unit = 1 - - #lambda - #root.input.sensitivity.param_003.sens_abstol = 1e-8 - #root.input.sensitivity.param_003.sens_boundphase = -1 - #root.input.sensitivity.param_003.sens_comp = -1 - #root.input.sensitivity.param_003.sens_factor = 1.0 - #root.input.sensitivity.param_003.sens_name = "SMA_LAMBDA" - #root.input.sensitivity.param_003.sens_reaction = -1 - #root.input.sensitivity.param_003.sens_section = -1 - #root.input.sensitivity.param_003.sens_unit = 1 - - #Sensitvity of first species to loads of all species (except salt) - #root.input.sensitivity.param_004.sens_abstol = [1e-8, 1e-8, 1e-8] - #root.input.sensitivity.param_004.sens_boundphase = [-1, -1, -1] - #root.input.sensitivity.param_004.sens_comp = [1, 2, 3] - #root.input.sensitivity.param_004.sens_factor = [1.0, 1.0, 1.0] - #root.input.sensitivity.param_004.sens_name = ["CONST_COEFF", "CONST_COEFF", "CONST_COEFF"] - #root.input.sensitivity.param_004.sens_reaction = [-1, -1, -1] - #root.input.sensitivity.param_004.sens_section = [0, 0, 0] - #root.input.sensitivity.param_004.sens_unit = [0, 0, 0] - - #Sensitvity of first species to velocity - #root.input.sensitivity.param_005.sens_abstol = 1e-8 - #root.input.sensitivity.param_005.sens_boundphase = -1 - #root.input.sensitivity.param_005.sens_comp = -1 - #root.input.sensitivity.param_005.sens_factor = 1.0 - #root.input.sensitivity.param_005.sens_name = "VELOCITY" - #root.input.sensitivity.param_005.sens_reaction = -1 - #root.input.sensitivity.param_005.sens_section = -1 - #root.input.sensitivity.param_005.sens_unit = 1 - - -def plotSimulation(simulation): - f, (ax1, ax2) = plt.subplots(1, 2, figsize=[16, 8]) - plotInlet(ax1, simulation) - plotOutlet(ax2, simulation) - f.tight_layout() - plt.show() - -def plotInlet(axis, simulation): - solution_times = simulation.root.output.solution.solution_times - - inlet_salt = simulation.root.output.solution.unit_000.solution_inlet_comp_000 - inlet_p1 = simulation.root.output.solution.unit_000.solution_inlet_comp_001 - inlet_p2 = simulation.root.output.solution.unit_000.solution_inlet_comp_002 - inlet_p3 = simulation.root.output.solution.unit_000.solution_inlet_comp_003 - - axis.set_title("Inlet") - axis.plot(solution_times, inlet_salt, 'b-', label="Salt") - axis.set_xlabel('time (s)') - - # Make the y-axis label, ticks and tick labels match the line color. - axis.set_ylabel('mMol Salt', color='b') - axis.tick_params('y', colors='b') - - axis2 = axis.twinx() - axis2.plot(solution_times, inlet_p1, 'r-', label="P1") - axis2.plot(solution_times, inlet_p2, 'g-', label="P2") - axis2.plot(solution_times, inlet_p3, 'k-', label="P3") - axis2.set_ylabel('mMol Protein', color='r') - axis2.tick_params('y', colors='r') - - - lines, labels = axis.get_legend_handles_labels() - lines2, labels2 = axis2.get_legend_handles_labels() - axis2.legend(lines + lines2, labels + labels2, loc=0) - - -def plotOutlet(axis, simulation): - solution_times = simulation.root.output.solution.solution_times - - outlet_salt = simulation.root.output.solution.unit_002.solution_outlet_comp_000 - outlet_p1 = simulation.root.output.solution.unit_002.solution_outlet_comp_001 - outlet_p2 = simulation.root.output.solution.unit_002.solution_outlet_comp_002 - outlet_p3 = simulation.root.output.solution.unit_002.solution_outlet_comp_003 - - axis.set_title("Output") - axis.plot(solution_times, outlet_salt, 'b-', label="Salt") - axis.set_xlabel('time (s)') - - # Make the y-axis label, ticks and tick labels match the line color. - axis.set_ylabel('mMol Salt', color='b') - axis.tick_params('y', colors='b') - - axis2 = axis.twinx() - axis2.plot(solution_times, outlet_p1, 'r-', label="P1") - axis2.plot(solution_times, outlet_p2, 'g-', label="P2") - axis2.plot(solution_times, outlet_p3, 'k-', label="P3") - axis2.set_ylabel('mMol Protein', color='r') - axis2.tick_params('y', colors='r') - - - lines, labels = axis.get_legend_handles_labels() - lines2, labels2 = axis2.get_legend_handles_labels() - axis2.legend(lines + lines2, labels + labels2, loc=0) - - -if __name__ == "__main__": - import sys - print(sys.version) - main() diff --git a/examples/Cadet_Python_Scoop.py b/examples/Cadet_Python_Scoop.py deleted file mode 100644 index 7c68007..0000000 --- a/examples/Cadet_Python_Scoop.py +++ /dev/null @@ -1,361 +0,0 @@ -#!/usr/bin/env python3.6 - -# Everything in here is based on CADET3.pdf in the same directory -# - -# Basic Python CADET file based interface compatible with CADET 3.0 and 3.1 -# Some additional fields have been added so that the generated simulations will also -# work in 3.1 and where those differences are I have noted them. -# This whole file follows the CADET pdf documentation. I have broken the system up into many -# functions in order to make it simpler and to make code reuse easier. - -# Normally the main function is placed at the bottom of the file but I have placed it at the top so that -# This interface is more like a tutorial and can be read from the top down and any given function -# can be drilled into to learn more about it. - -# The core part of python with CADET is HDF5 and numpy -import h5py -import numpy as np - -#used to run CADET -import subprocess -import io - -import matplotlib -matplotlib.use('Agg') -import matplotlib.pyplot as plt - -#parallelization -from scoop import futures - -import itertools -import hashlib - -#location of the cadet binary -cadet_location = "C:\\Users\\kosh_000\\cadet_build\\cadet_3\\cadet\\MS_SMKL_RELEASE\\bin\\cadet-cli.exe" - -# Helper functions that make it easier to set the values in the HDF5 file -# In the CADET pdf file each value in the hdf5 file has a type. The functions -# below match those types. - -def set_int(node, nameH5, value): - "set one or more integers in the hdf5 file" - data = np.array(value, dtype="i4") - if node.get(nameH5, None) is not None: - del node[nameH5] - node.create_dataset(nameH5, data=data, maxshape=tuple(None for i in range(data.ndim)), fillvalue=[0]) - -def set_double(node, nameH5, value): - "set one or more doubles in the hdf5 file" - data = np.array(value, dtype="f8") - if node.get(nameH5, None) is not None: - del node[nameH5] - node.create_dataset(nameH5, data=data, maxshape=tuple(None for i in range(data.ndim)), fillvalue=[0]) - -def set_string(node, nameH5, value): - "set a string value in the hdf5 file" - if isinstance(value, list): - dtype = 'S' + str(len(value[0])+1) - else: - dtype = 'S' + str(len(value)+1) - data = np.array(value, dtype=dtype) - if node.get(nameH5, None) is not None: - del node[nameH5] - node.create_dataset(nameH5, data=data) - - -def main(args): - ka, nu, sigma = args - #filename = "%.2g_%.2g_%.2g.h5" % (ka, nu, sigma) - - filename = hashlib.md5(str(args).encode('utf-8','ignore')).hexdigest() + '.h5' - - createSimulation(filename, ka, nu, sigma) - runSimulation(filename) - plotSimulation(filename) - return "Completed Ka: %s Nu: %s Sigma: %s" % (ka, nu, sigma) - -def createSimulation(filename, ka, nu, sigma): - with h5py.File(filename, 'w') as f: - createInput(f, ka, nu, sigma) - -def createInput(f, ka, nu, sigma): - input = f.create_group("input") - createModel(input, ka, nu, sigma) - createReturn(input) - createSolver(input) - -def createModel(input, ka, nu, sigma): - model = input.create_group("model") - set_int(model, 'NUNITS', 3) - - # Part of CADET 3.1 - set_int(model, 'GS_TYPE', 1) - set_int(model, 'MAX_KRYLOV', 0) - set_int(model, 'MAX_RESTARTS', 0) - set_double(model, 'SCHUR_SAFETY', 1.0e-8) - - createConnections(model) - createInlet(model) - createColumn(model, ka, nu, sigma) - createOutlet(model) - -def createConnections(model): - connections = model.create_group("connections") - set_int(connections, 'NSWITCHES', 1) - createSwitch(connections) - -def createSwitch(connections): - """Create a switch in the system. In 3.0 these are very limited but in 3.1 they allow arbitrary connections between unit operations. - The format is a set of 4 numbers (Source Unit Operation ID, Destination Unit Operation ID, Source Component ID, Destination Component ID. If the - Source and Destination Component IDs are set to -1 that means connect all to all and this is the most common setup.""" - - # CADET uses 0 based numbers and 3 digits to identify anything there are multiples of like unit operations, sections, switches etc - switch_000 = connections.create_group("switch_000") - - #Connect all of Inlet [0] to Column [1] and Column [1] to Outlet [2] - set_int(switch_000, 'CONNECTIONS', [0, 1, -1, -1, 1, 2, -1, -1]) - set_int(switch_000, 'SECTION', 0) - -def createInlet(model): - inlet = model.create_group("unit_000") - - set_string(inlet, 'INLET_TYPE', 'PIECEWISE_CUBIC_POLY') - set_int(inlet, 'NCOMP', 4) - set_string(inlet, 'UNIT_TYPE', 'INLET') - - # CADET 3.1 - set_double(inlet, 'FLOW', 1.0) - - createLoad(inlet) - createWash(inlet) - createElute(inlet) - -def createLoad(inlet): - sec = inlet.create_group('sec_000') - - set_double(sec, 'CONST_COEFF', [50.0, 1.0, 1.0, 1.0]) - set_double(sec, 'LIN_COEFF', [0.0, 0.0, 0.0, 0.0]) - set_double(sec, 'QUAD_COEFF', [0.0, 0.0, 0.0, 0.0]) - set_double(sec, 'CUBE_COEFF', [0.0, 0.0, 0.0, 0.0]) - -def createWash(inlet): - sec = inlet.create_group('sec_001') - - set_double(sec, 'CONST_COEFF', [50.0, 0.0, 0.0, 0.0]) - set_double(sec, 'LIN_COEFF', [0.0, 0.0, 0.0, 0.0]) - set_double(sec, 'QUAD_COEFF', [0.0, 0.0, 0.0, 0.0]) - set_double(sec, 'CUBE_COEFF', [0.0, 0.0, 0.0, 0.0]) - -def createElute(inlet): - sec = inlet.create_group('sec_002') - - set_double(sec, 'CONST_COEFF', [100.0, 0.0, 0.0, 0.0]) - set_double(sec, 'LIN_COEFF', [0.2, 0.0, 0.0, 0.0]) - set_double(sec, 'QUAD_COEFF', [0.0, 0.0, 0.0, 0.0]) - set_double(sec, 'CUBE_COEFF', [0.0, 0.0, 0.0, 0.0]) - -def createColumn(model, ka, nu, sigma): - column = model.create_group('unit_001') - - set_string(column, 'ADSORPTION_MODEL', 'STERIC_MASS_ACTION') - set_double(column, 'COL_DISPERSION', 5.75e-8) - set_double(column, 'COL_LENGTH', 0.014) - set_double(column, 'COL_POROSITY', 0.37) - set_double(column, 'FILM_DIFFUSION', [6.9e-6, 6.9e-6, 6.9e-6, 6.9e-6]) - set_double(column, 'INIT_C', [50.0, 0.0, 0.0, 0.0]) - set_double(column, 'INIT_Q', [1200.0, 0.0, 0.0, 0.0]) - set_int(column, 'NCOMP', 4) - set_double(column, 'PAR_DIFFUSION', [7e-10, 6.07e-11, 6.07e-11, 6.07e-11]) - set_double(column, 'PAR_POROSITY', 0.75) - set_double(column, 'PAR_RADIUS', 4.5e-5) - set_double(column, 'PAR_SURFDIFFUSION', [0.0, 0.0, 0.0, 0.0]) - set_string(column, 'UNIT_TYPE', 'GENERAL_RATE_MODEL') - set_double(column, 'VELOCITY', 5.75e-4) - - # CADET 3.1 - set_double(column, 'FLOW', 1.0) - - createAdsorption(column, ka, nu, sigma) - createDiscretization(column) - -def createAdsorption(column, ka, nu, sigma): - ads = column.create_group('adsorption') - - set_int(ads, 'IS_KINETIC', 0) - set_double(ads, 'SMA_KA', [0.0, ka, 1.59, 7.7]) - set_double(ads, 'SMA_KD', [0, 1000, 1000, 1000]) - set_double(ads, 'SMA_LAMBDA', 1200) - set_double(ads, 'SMA_NU', [0.0, nu, 5.29, 3.7]) - set_double(ads, 'SMA_SIGMA', [0, sigma, 10.6, 10.0]) - -def createDiscretization(column): - disc = column.create_group('discretization') - - set_int(disc, 'GS_TYPE', 1) - set_int(disc, 'MAX_KRYLOV', 0) - set_int(disc, 'MAX_RESTARTS', 10) - set_int(disc, 'NBOUND', [1, 1, 1, 1]) - set_int(disc, 'NCOL', 50) - set_int(disc, 'NPAR', 10) - set_string(disc, 'PAR_DISC_TYPE', 'EQUIDISTANT_PAR') - set_double(disc, 'SCHUR_SAFETY', 1.0e-8) - set_int(disc, 'USE_ANALYTIC_JACOBIAN', 1) - - createWENO(disc) - -def createWENO(disc): - weno = disc.create_group("weno") - set_int(weno, 'BOUNDARY_MODEL', 0) - set_double(weno, 'WENO_EPS', 1e-10) - set_int(weno, 'WENO_ORDER', 3) - -def createOutlet(model): - outlet = model.create_group('unit_002') - set_int(outlet, 'NCOMP', 4) - set_string(outlet, 'UNIT_TYPE', 'OUTLET') - - # CADET 3.1 - set_double(outlet, 'FLOW', 1.0) - -def createReturn(input): - ret = input.create_group('return') - - set_int(ret, 'WRITE_SOLUTION_TIMES', 1) - - createColumnOutput(ret) - -def createColumnOutput(ret): - column = ret.create_group('unit_001') - - set_int(column, 'WRITE_SENS_COLUMN', 0) - set_int(column, 'WRITE_SENS_COLUMN_INLET', 0) - set_int(column, 'WRITE_SENS_COLUMN_OUTLET', 0) - set_int(column, 'WRITE_SENS_FLUX', 0) - set_int(column, 'WRITE_SENS_PARTICLE', 0) - - set_int(column, 'WRITE_SOLUTION_COLUMN', 0) - set_int(column, 'WRITE_SOLUTION_COLUMN_INLET', 1) - set_int(column, 'WRITE_SOLUTION_COLUMN_OUTLET', 1) - set_int(column, 'WRITE_SOLUTION_FLUX', 0) - set_int(column, 'WRITE_SOLUTION_PARTICLE', 0) - -def createSolver(input): - solver = input.create_group("solver") - set_int(solver, 'NTHREADS', 1) - set_double(solver, 'USER_SOLUTION_TIMES', range(0, 1500+1)) - - createSections(solver) - createTimeIntegrator(solver) - -def createSections(solver): - sections = solver.create_group("sections") - set_int(sections, 'NSEC', 3) - set_int(sections, 'SECTION_CONTINUITY', [0,0]) - set_double(sections, 'SECTION_TIMES', [0, 10, 90, 1500]) - -def createTimeIntegrator(solver): - time_integrator = solver.create_group("time_integrator") - set_double(time_integrator, 'ABSTOL', 1e-8) - set_double(time_integrator, 'ALGTOL', 1e-12) - set_double(time_integrator, 'INIT_STEP_SIZE', 1e-6) - set_int(time_integrator, 'MAX_STEPS', 10000) - set_double(time_integrator, 'RELTOL', 1e-6) - -def runSimulation(filename): - proc = subprocess.Popen([cadet_location, filename], bufsize=0, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = proc.communicate() - proc.wait() - print("CADET Output") - print(stdout) - print("CADET Errors") - print(stderr) - -def plotSimulation(filename): - with h5py.File(filename, 'r') as h5: - fig = plt.figure(figsize=[20, 10]) - - ax1 = fig.add_subplot(1, 2, 1) - ax2 = fig.add_subplot(1, 2, 2) - - #f, (ax1, ax2) = fig.subplots(1, 2, figsize=[16, 8]) - plotInlet(ax1, h5) - plotOutlet(ax2, h5) - fig.tight_layout() - #plt.show() - plt.savefig(filename.replace('h5', 'png'), dpi=100) - plt.close() - -def plotInlet(axis, h5): - solution_times = np.array(h5['/output/solution/SOLUTION_TIMES'].value) - - inlet_salt = np.array(h5['/output/solution/unit_001/SOLUTION_COLUMN_INLET_COMP_000'].value) - inlet_p1 = np.array(h5['/output/solution/unit_001/SOLUTION_COLUMN_INLET_COMP_001'].value) - inlet_p2 = np.array(h5['/output/solution/unit_001/SOLUTION_COLUMN_INLET_COMP_002'].value) - inlet_p3 = np.array(h5['/output/solution/unit_001/SOLUTION_COLUMN_INLET_COMP_003'].value) - - - axis.set_title("Inlet") - axis.plot(solution_times, inlet_salt, 'b-', label="Salt") - axis.set_xlabel('time (s)') - - # Make the y-axis label, ticks and tick labels match the line color. - axis.set_ylabel('mMol Salt', color='b') - axis.tick_params('y', colors='b') - - axis2 = axis.twinx() - axis2.plot(solution_times, inlet_p1, 'r-', label="P1") - axis2.plot(solution_times, inlet_p2, 'g-', label="P2") - axis2.plot(solution_times, inlet_p3, 'k-', label="P3") - axis2.set_ylabel('mMol Protein', color='r') - axis2.tick_params('y', colors='r') - - - lines, labels = axis.get_legend_handles_labels() - lines2, labels2 = axis2.get_legend_handles_labels() - axis2.legend(lines + lines2, labels + labels2, loc=0) - - -def plotOutlet(axis, h5): - solution_times = np.array(h5['/output/solution/SOLUTION_TIMES'].value) - - outlet_salt = np.array(h5['/output/solution/unit_001/SOLUTION_COLUMN_OUTLET_COMP_000'].value) - outlet_p1 = np.array(h5['/output/solution/unit_001/SOLUTION_COLUMN_OUTLET_COMP_001'].value) - outlet_p2 = np.array(h5['/output/solution/unit_001/SOLUTION_COLUMN_OUTLET_COMP_002'].value) - outlet_p3 = np.array(h5['/output/solution/unit_001/SOLUTION_COLUMN_OUTLET_COMP_003'].value) - - axis.set_title("Output") - axis.plot(solution_times, outlet_salt, 'b-', label="Salt") - axis.set_xlabel('time (s)') - - # Make the y-axis label, ticks and tick labels match the line color. - axis.set_ylabel('mMol Salt', color='b') - axis.tick_params('y', colors='b') - - axis2 = axis.twinx() - axis2.plot(solution_times, outlet_p1, 'r-', label="P1") - axis2.plot(solution_times, outlet_p2, 'g-', label="P2") - axis2.plot(solution_times, outlet_p3, 'k-', label="P3") - axis2.set_ylabel('mMol Protein', color='r') - axis2.tick_params('y', colors='r') - - - lines, labels = axis.get_legend_handles_labels() - lines2, labels2 = axis2.get_legend_handles_labels() - axis2.legend(lines + lines2, labels + labels2, loc=0) - - -if __name__ == "__main__": - import sys - print(sys.version) - - ka = [30, 35, 40] - nu = [4.6, 4.7, 4.8] - sigma = [11.7, 11.8, 11.9] - - settings = itertools.product(ka, nu, sigma) - - results = futures.map(main, settings) - - for result in results: - print(result) diff --git a/examples/HICWang.py b/examples/HICWang.py deleted file mode 100644 index 53bce64..0000000 --- a/examples/HICWang.py +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env python3.6 - -# Everything in here is based on CADET3.pdf in the same directory -# - -# Basic Python CADET file based interface compatible with CADET 3.0 and 3.1 -# Some additional fields have been added so that the generated simulations will also -# work in 3.1 and where those differences are I have noted them. -# This whole file follows the CADET pdf documentation. I have broken the system up into many -# functions in order to make it simpler and to make code reuse easier. - -# Normally the main function is placed at the bottom of the file but I have placed it at the top so that -# This interface is more like a tutorial and can be read from the top down and any given function -# can be drilled into to learn more about it. - -#use to render results -import matplotlib.pyplot as plt - -import numpy - -from cadet import Cadet -import common - -#Cadet.cadet_path = r"C:\Users\kosh_000\cadet_build\CADET-fran\MS_SMKL_DEBUG\bin\cadet-cli.exe" -Cadet.cadet_path = r"C:\Users\kosh_000\cadet_build\CADET-fran\MS_SMKL_RELEASE\bin\cadet-cli.exe" - - -# Helper functions that make it easier to set the values in the HDF5 file -# In the CADET pdf file each value in the hdf5 file has a type. The functions -# below match those types. - -def main(): - simulation = Cadet(common.common.root) - simulation.filename = "F:/temp/HICWang.h5" - createSimulation(simulation) - simulation.save() - simulation.run() - simulation.load() - - plotSimulation(simulation) - -def createSimulation(simulation): - root = simulation.root - - root.input.model.nunits = 3 - - root.input.solver.time_integrator.abstol = 1e-6 - root.input.solver.time_integrator.reltol = 0.0 - - root.input.model.connections.nswitches = 1 - root.input.model.connections.switch_000.section = 0 - root.input.model.connections.switch_000.connections = [0, 1, -1, -1, 1.0, - 1, 2, -1, -1, 1.0] - root.input.model.unit_000.inlet_type = 'PIECEWISE_CUBIC_POLY' - root.input.model.unit_000.unit_type = 'INLET' - root.input.model.unit_000.ncomp = 2 - - root.input.model.unit_000.sec_000.const_coeff = [4000.0, 1.0] - root.input.model.unit_000.sec_000.lin_coeff = [0.0, 0.0] - root.input.model.unit_000.sec_000.quad_coeff = [0.0, 0.0] - root.input.model.unit_000.sec_000.cube_coeff = [0.0, 0.0] - - root.input.model.unit_000.sec_001.const_coeff = [4000.0, 0.0] - root.input.model.unit_000.sec_001.lin_coeff = [-4000/15000, 0.0] - root.input.model.unit_000.sec_001.quad_coeff = [0.0, 0.0] - root.input.model.unit_000.sec_001.cube_coeff = [0.0, 0.0] - - root.input.model.unit_001.adsorption_model = 'HICWANG' - root.input.model.unit_001.col_dispersion = 5.6e-8 - root.input.model.unit_001.col_length = 0.014 - root.input.model.unit_001.col_porosity = 0.4 - root.input.model.unit_001.film_diffusion = [1.4e-7, 1.4e-7] - root.input.model.unit_001.init_c = [4000.0, 0.0] - root.input.model.unit_001.init_q = [0.0] - root.input.model.unit_001.ncomp = 2 - root.input.model.unit_001.par_diffusion = [4e-12, 4e-12] - root.input.model.unit_001.par_porosity = 0.98 - root.input.model.unit_001.par_radius = 4.5e-5 - root.input.model.unit_001.par_surfdiffusion = [0.0] - root.input.model.unit_001.unit_type = 'GENERAL_RATE_MODEL' - - #root.input.model.unit_001.velocity = 1 - #root.input.model.unit_001.cross_section_area = 4700.352526439483 - root.input.model.unit_001.velocity = 60/(3600*100) - - - root.input.model.unit_001.adsorption.is_kinetic = 1 - root.input.model.unit_001.adsorption.hicwang_kkin = [1e2, 1.0/0.16] - root.input.model.unit_001.adsorption.hicwang_keq = [0.0, 34] - root.input.model.unit_001.adsorption.hicwang_nu = [1.0, 9.5] - root.input.model.unit_001.adsorption.hicwang_qmax = [1.0, 1.3e-2*1000] - root.input.model.unit_001.adsorption.hicwang_beta0 = 3.6e-2 - root.input.model.unit_001.adsorption.hicwang_beta1 = 1.0/1e3 - - root.input.model.unit_001.discretization.nbound = [0, 1] - root.input.model.unit_001.discretization.ncol = 3 - root.input.model.unit_001.discretization.npar = 1 - root.input.model.unit_001.discretization.use_analytic_jacobian = 0 - - - root.input.model.unit_002.ncomp = 2 - root.input.model.unit_002.unit_type = 'OUTLET' - - root.input.solver.user_solution_times = numpy.linspace(0, 15000, 15000) - root.input.solver.sections.nsec = 2 - root.input.solver.sections.section_continuity = [0] - root.input.solver.sections.section_times = [0.0, 10.0, 15000.0] - - root.input.solver.time_integrator.init_step_size = 1e-2 - -def plotSimulation(simulation): - f, (ax1, ax2) = plt.subplots(1, 2, figsize=[16, 8]) - plotInlet(ax1, simulation) - plotOutlet(ax2, simulation) - f.tight_layout() - plt.show() - -def plotInlet(axis, simulation): - solution_times = simulation.root.output.solution.solution_times - - inlet_salt = simulation.root.output.solution.unit_000.solution_column_inlet_comp_000 - inlet_p1 = simulation.root.output.solution.unit_000.solution_column_inlet_comp_001 - - axis.set_title("Inlet") - axis.plot(solution_times, inlet_salt, 'b-', label="Salt") - axis.set_xlabel('time (s)') - - # Make the y-axis label, ticks and tick labels match the line color. - axis.set_ylabel('mMol Salt', color='b') - axis.tick_params('y', colors='b') - - axis2 = axis.twinx() - axis2.plot(solution_times, inlet_p1, 'r-', label="P1") - axis2.set_ylabel('mMol Protein', color='r') - axis2.tick_params('y', colors='r') - - - lines, labels = axis.get_legend_handles_labels() - lines2, labels2 = axis2.get_legend_handles_labels() - axis2.legend(lines + lines2, labels + labels2, loc=0) - - -def plotOutlet(axis, simulation): - solution_times = simulation.root.output.solution.solution_times - - outlet_salt = simulation.root.output.solution.unit_002.solution_column_outlet_comp_000 - outlet_p1 = simulation.root.output.solution.unit_002.solution_column_outlet_comp_001 - - axis.set_title("Output") - axis.plot(solution_times, outlet_salt, 'b-', label="Salt") - axis.set_xlabel('time (s)') - - # Make the y-axis label, ticks and tick labels match the line color. - axis.set_ylabel('mMol Salt', color='b') - axis.tick_params('y', colors='b') - - axis2 = axis.twinx() - axis2.plot(solution_times, outlet_p1, 'r-', label="P1") - axis2.set_ylabel('mMol Protein', color='r') - axis2.tick_params('y', colors='r') - - - lines, labels = axis.get_legend_handles_labels() - lines2, labels2 = axis2.get_legend_handles_labels() - axis2.legend(lines + lines2, labels + labels2, loc=0) - - -if __name__ == "__main__": - import sys - print(sys.version) - main() - diff --git a/examples/MSSMA.py b/examples/MSSMA.py deleted file mode 100644 index a6ce018..0000000 --- a/examples/MSSMA.py +++ /dev/null @@ -1,166 +0,0 @@ -#!/usr/bin/env python3.6 - -# Everything in here is based on CADET3.pdf in the same directory -# - -# Basic Python CADET file based interface compatible with CADET 3.0 and 3.1 -# Some additional fields have been added so that the generated simulations will also -# work in 3.1 and where those differences are I have noted them. -# This whole file follows the CADET pdf documentation. I have broken the system up into many -# functions in order to make it simpler and to make code reuse easier. - -# Normally the main function is placed at the bottom of the file but I have placed it at the top so that -# This interface is more like a tutorial and can be read from the top down and any given function -# can be drilled into to learn more about it. - -import numpy - -#use to render results -import matplotlib.pyplot as plt - -from cadet import Cadet -Cadet.cadet_path = "C:/Users/kosh_000/cadet_build/CADET-dev/cadet3.1-win7-x64/bin/cadet-cli.exe" - -import common - -def main(): - simulation = Cadet(common.common.root) - simulation.filename = "MSSMA.h5" - createSimulation(simulation) - simulation.save() - simulation.run() - simulation.load() - plotSimulation(simulation) - -def createSimulation(simulation): - root = simulation.root - - root.input.model.nunits = 3 - - root.input.model.connections.nswitches = 1 - root.input.model.connections.switch_000.section = 0 - root.input.model.connections.switch_000.connections = [0, 1, -1, -1, 1.0, - 1, 2, -1, -1, 1.0] - root.input.model.unit_000.inlet_type = 'PIECEWISE_CUBIC_POLY' - root.input.model.unit_000.unit_type = 'INLET' - root.input.model.unit_000.ncomp = 2 - - root.input.model.unit_000.sec_000.const_coeff = [100.0, 0.15] - root.input.model.unit_000.sec_000.lin_coeff = [0.0, 0.0] - root.input.model.unit_000.sec_000.quad_coeff = [0.0, 0.0] - root.input.model.unit_000.sec_000.cube_coeff = [0.0, 0.0] - - root.input.model.unit_000.sec_001.const_coeff = [75, 0.0] - root.input.model.unit_000.sec_001.lin_coeff = [0.0, 0.0] - root.input.model.unit_000.sec_001.quad_coeff = [0.0, 0.0] - root.input.model.unit_000.sec_001.cube_coeff = [0.0, 0.0] - - root.input.model.unit_000.sec_002.const_coeff = [75, 0.0] - root.input.model.unit_000.sec_002.lin_coeff = [0.05, 0.0] - root.input.model.unit_000.sec_002.quad_coeff = [0.0, 0.0] - root.input.model.unit_000.sec_002.cube_coeff = [0.0, 0.0] - - root.input.model.unit_001.adsorption_model = 'MULTISTATE_STERIC_MASS_ACTION' - root.input.model.unit_001.col_dispersion = 1.5E-7 - root.input.model.unit_001.col_length = 0.25 - root.input.model.unit_001.col_porosity = 0.3 - root.input.model.unit_001.film_diffusion = [2.14E-4, 2.1e-5] - root.input.model.unit_001.init_c = [75, 0.0] - root.input.model.unit_001.init_q = [225, 0.0, 0.0] - root.input.model.unit_001.ncomp = 2 - root.input.model.unit_001.par_diffusion = [4.08E-10, 9.0E-12] - root.input.model.unit_001.par_porosity = 0.4 - root.input.model.unit_001.par_radius = 3.25E-5 - root.input.model.unit_001.par_surfdiffusion = [0.0, 0.0, 0.0] - root.input.model.unit_001.unit_type = 'GENERAL_RATE_MODEL' - root.input.model.unit_001.velocity = 0.001 - - root.input.model.unit_001.adsorption.is_kinetic = 1 - root.input.model.unit_001.adsorption.mssma_ka = [0.0, 1E11, 8E6] - root.input.model.unit_001.adsorption.mssma_kd = [0.0, 6E11, 2E16] - root.input.model.unit_001.adsorption.mssma_lambda = 225 - root.input.model.unit_001.adsorption.mssma_nu = [0.0, 10, 25] - root.input.model.unit_001.adsorption.mssma_sigma = [0.0, 48, 66] - root.input.model.unit_001.adsorption.mssma_refq = 225 - root.input.model.unit_001.adsorption.mssma_refc0 = 520.0 - root.input.model.unit_001.adsorption.mssma_rates = [0.0, 0.0, 1e20, 10, 0.0] - - root.input.model.unit_001.discretization.nbound = [1, 2] - root.input.model.unit_001.discretization.ncol = 50 - root.input.model.unit_001.discretization.npar = 5 - - root.input.model.unit_002.ncomp = 2 - root.input.model.unit_002.unit_type = 'OUTLET' - - root.input.solver.user_solution_times = numpy.linspace(0, 15000, 1000) - root.input.solver.sections.nsec = 3 - root.input.solver.sections.section_continuity = [0, 0] - root.input.solver.sections.section_times = [0.0, 4500, 6100, 15000] - - root.input.solver.consistent_init_mode = 3 - -def plotSimulation(simulation): - f, (ax1, ax2) = plt.subplots(1, 2, figsize=[16, 8]) - plotInlet(ax1, simulation) - plotOutlet(ax2, simulation) - f.tight_layout() - plt.show() - -def plotInlet(axis, simulation): - solution_times = simulation.root.output.solution.solution_times - - inlet_salt = simulation.root.output.solution.unit_000.solution_inlet_comp_000 - inlet_p1 = simulation.root.output.solution.unit_000.solution_inlet_comp_001 - - axis.set_title("Inlet") - axis.plot(solution_times, inlet_salt, 'b-', label="Salt") - axis.set_xlabel('time (s)') - - # Make the y-axis label, ticks and tick labels match the line color. - axis.set_ylabel('mMol Salt', color='b') - axis.tick_params('y', colors='b') - - axis2 = axis.twinx() - axis2.plot(solution_times, inlet_p1, 'r-', label="P1") - axis2.set_ylabel('mMol Protein', color='r') - axis2.tick_params('y', colors='r') - - - lines, labels = axis.get_legend_handles_labels() - lines2, labels2 = axis2.get_legend_handles_labels() - axis2.legend(lines + lines2, labels + labels2, loc=0) - - -def plotOutlet(axis, simulation): - solution_times = simulation.root.output.solution.solution_times - - outlet_salt = simulation.root.output.solution.unit_002.solution_outlet_comp_000 - outlet_p1 = simulation.root.output.solution.unit_002.solution_outlet_comp_001 - - data = numpy.vstack([solution_times, outlet_p1]).transpose() - numpy.savetxt('comp_1.csv', data, delimiter=',') - - - axis.set_title("Output") - axis.plot(solution_times, outlet_salt, 'b-', label="Salt") - axis.set_xlabel('time (s)') - - # Make the y-axis label, ticks and tick labels match the line color. - axis.set_ylabel('mMol Salt', color='b') - axis.tick_params('y', colors='b') - - axis2 = axis.twinx() - axis2.plot(solution_times, outlet_p1, 'r-', label="P1") - axis2.set_ylabel('mMol Protein', color='r') - axis2.tick_params('y', colors='r') - - - lines, labels = axis.get_legend_handles_labels() - lines2, labels2 = axis2.get_legend_handles_labels() - axis2.legend(lines + lines2, labels + labels2, loc=0) - - -if __name__ == "__main__": - import sys - print(sys.version) - main() diff --git a/examples/MSSMA2.py b/examples/MSSMA2.py deleted file mode 100644 index 36d633a..0000000 --- a/examples/MSSMA2.py +++ /dev/null @@ -1,323 +0,0 @@ -#!/usr/bin/env python3.6 - -# Everything in here is based on CADET_31.pdf in the same directory -# - -#Designed for CADET 3.1 - -# This whole file follows the CADET pdf documentation. I have broken the system up into many -# functions in order to make it simpler and to make code reuse easier. - -# Normally the main function is placed at the bottom of the file but I have placed it at the top so that -# This interface is more like a tutorial and can be read from the top down and any given function -# can be drilled into to learn more about it. - -# The core part of python with CADET is HDF5 and numpy -import h5py -import numpy as np - -#used to run CADET -import subprocess -import io - -#use to render results -import matplotlib.pyplot as plt - -#location of the cadet binary -cadet_location = "C:/Users/kosh_000/cadet_build/CADET/MS_SMKL_RELEASE/bin/cadet-cli.exe" - -# Helper functions that make it easier to set the values in the HDF5 file -# In the CADET pdf file each value in the hdf5 file has a type. The functions -# below match those types. - -def set_int(node, nameH5, value): - "set one or more integers in the hdf5 file" - data = np.array(value, dtype="i4") - if node.get(nameH5, None) is not None: - del node[nameH5] - node.create_dataset(nameH5, data=data, maxshape=tuple(None for i in range(data.ndim)), fillvalue=[0]) - -def set_double(node, nameH5, value): - "set one or more doubles in the hdf5 file" - data = np.array(value, dtype="f8") - if node.get(nameH5, None) is not None: - del node[nameH5] - node.create_dataset(nameH5, data=data, maxshape=tuple(None for i in range(data.ndim)), fillvalue=[0]) - -def set_string(node, nameH5, value): - "set a string value in the hdf5 file" - if isinstance(value, list): - dtype = 'S' + str(len(value[0])+1) - else: - dtype = 'S' + str(len(value)+1) - data = np.array(value, dtype=dtype) - if node.get(nameH5, None) is not None: - del node[nameH5] - node.create_dataset(nameH5, data=data) - - -def main(): - filename = "MSSMA2.h5" - createSimulation(filename) - runSimulation(filename) - plotSimulation(filename) - -def createSimulation(filename): - with h5py.File(filename, 'w') as f: - createInput(f) - -def createInput(f): - input = f.create_group("input") - createModel(input) - createReturn(input) - createSolver(input) - -def createModel(input): - model = input.create_group("model") - set_int(model, 'NUNITS', 3) - - solver = model.create_group('solver') - set_int(solver, 'GS_TYPE', 1) - set_int(solver, 'MAX_KRYLOV', 0) - set_int(solver, 'MAX_RESTARTS', 10) - set_double(solver, 'SCHUR_SAFETY', 1e-8) - - createConnections(model) - createInlet(model) - createColumn(model) - createOutlet(model) - -def createConnections(model): - connections = model.create_group("connections") - set_int(connections, 'NSWITCHES', 1) - createSwitch(connections) - -def createSwitch(connections): - """Create a switch in the system. In 3.1 they allow arbitrary connections between unit operations. - The format is a set of 5 numbers (Source Unit Operation ID, Destination Unit Operation ID, Source Component ID, Destination Component ID and Volumetric flow rate (m^3/s). If the - Source and Destination Component IDs are set to -1 that means connect all to all and this is the most common setup.""" - - # CADET uses 0 based numbers and 3 digits to identify anything there are multiples of like unit operations, sections, switches etc - switch_000 = connections.create_group("switch_000") - - #Connect all of Inlet [0] to Column [1] and Column [1] to Outlet [2] - set_int(switch_000, 'CONNECTIONS', [0, 1, -1, -1, 1.0, - 1, 2, -1, -1, 1.0]) - set_int(switch_000, 'SECTION', 0) - -def createInlet(model): - inlet = model.create_group("unit_000") - - set_string(inlet, 'INLET_TYPE', 'PIECEWISE_CUBIC_POLY') - set_int(inlet, 'NCOMP', 3) - set_string(inlet, 'UNIT_TYPE', 'INLET') - - createLoad(inlet) - createWash(inlet) - createElute(inlet) - -def createLoad(inlet): - sec = inlet.create_group('sec_000') - - set_double(sec, 'CONST_COEFF', [92.0, 0.10631294584377825, 0.10631294584377825/2]) - set_double(sec, 'LIN_COEFF', [0.0, 0.0, 0.0]) - set_double(sec, 'QUAD_COEFF', [0.0, 0.0, 0.0]) - set_double(sec, 'CUBE_COEFF', [0.0, 0.0, 0.0]) - -def createWash(inlet): - sec = inlet.create_group('sec_001') - - set_double(sec, 'CONST_COEFF', [69.97439960989882, 0.0, 0.0]) - set_double(sec, 'LIN_COEFF', [0.0, 0.0, 0.0]) - set_double(sec, 'QUAD_COEFF', [0.0, 0.0, 0.0]) - set_double(sec, 'CUBE_COEFF', [0.0, 0.0, 0.0]) - -def createElute(inlet): - sec = inlet.create_group('sec_002') - - set_double(sec, 'CONST_COEFF', [69.97439960989882, 0.0, 0.0]) - set_double(sec, 'LIN_COEFF', [0.053, 0.0, 0.0]) - set_double(sec, 'QUAD_COEFF', [0.0, 0.0, 0.0]) - set_double(sec, 'CUBE_COEFF', [0.0, 0.0, 0.0]) - -def createColumn(model): - column = model.create_group('unit_001') - - set_string(column, 'ADSORPTION_MODEL', 'MULTISTATE_STERIC_MASS_ACTION') - set_double(column, 'COL_DISPERSION', 1.5E-7) - set_double(column, 'COL_LENGTH', 0.215) - set_double(column, 'COL_POROSITY', 0.33999999999999997) - set_double(column, 'FILM_DIFFUSION', [2.14E-4, 2.1e-5, 2.1e-5]) - set_double(column, 'INIT_C', [69.9743996098988, 0.0, 0.0]) - set_double(column, 'INIT_Q', [223.547, 0.0, 0.0, 0.0, 0.0]) - set_int(column, 'NCOMP', 3) - set_double(column, 'PAR_DIFFUSION', [4.08E-10, 9.0E-12, 9.0e-12]) - set_double(column, 'PAR_POROSITY', 0.39) - set_double(column, 'PAR_RADIUS', 3.25E-5) - set_double(column, 'PAR_SURFDIFFUSION', [0.0, 0.0, 0.0, 0.0, 0.0]) - set_string(column, 'UNIT_TYPE', 'GENERAL_RATE_MODEL') - set_double(column, 'VELOCITY', 0.0011437908496732027) - - createAdsorption(column) - createDiscretization(column) - -def createAdsorption(column): - ads = column.create_group('adsorption') - - set_int(ads, 'IS_KINETIC', 1) - set_double(ads, 'MSSMA_KA', [0.0, 1.0652004307518004E31, 7.724553149425915E26, 1.969122487513422E30, 1.1177522067458229E27]) - set_double(ads, 'MSSMA_KD', [0.0, 5.88452172578919E31, 1.955092026422206E36, 9.923200169245614E32, 8.083909678639826E38]) - set_double(ads, 'MSSMA_LAMBDA', 223.547) - set_double(ads, 'MSSMA_NU', [0.0, 9.618977853171593, 24.75290977103934, 6.058214688510013, 20.20231695871297]) - set_double(ads, 'MSSMA_SIGMA', [0.0, 47.82861669713074, 65.93967947378826, 40.22617141805257, 63.71221340773053 -]) - set_double(ads, 'MSSMA_REFQ', 223.547) - set_double(ads, 'MSSMA_REFC0', 520.0) - set_double(ads, 'MSSMA_RATES', [0.0, 0.0, 9.39710359947847E39, 9.503195767335168, 0.0, 0.0, 5.571477811298548E30, 82427.80960452619, 0.0]) - -def createDiscretization(column): - disc = column.create_group('discretization') - - set_int(disc, 'GS_TYPE', 1) - set_int(disc, 'MAX_KRYLOV', 0) - set_int(disc, 'MAX_RESTARTS', 0) - set_int(disc, 'NBOUND', [1, 2, 2]) - set_int(disc, 'NCOL', 100) - set_int(disc, 'NPAR', 5) - set_string(disc, 'PAR_DISC_TYPE', 'EQUIDISTANT_PAR') - set_double(disc, 'SCHUR_SAFETY', 1.0e-8) - set_int(disc, 'USE_ANALYTIC_JACOBIAN', 1) - - createWENO(disc) - -def createWENO(disc): - weno = disc.create_group("weno") - set_int(weno, 'BOUNDARY_MODEL', 0) - set_double(weno, 'WENO_EPS', 1e-10) - set_int(weno, 'WENO_ORDER', 3) - -def createOutlet(model): - outlet = model.create_group('unit_002') - set_int(outlet, 'NCOMP', 3) - set_string(outlet, 'UNIT_TYPE', 'OUTLET') - -def createReturn(input): - ret = input.create_group('return') - - set_int(ret, 'WRITE_SOLUTION_TIMES', 1) - - createColumnOutput(ret) - -def createColumnOutput(ret): - column = ret.create_group('unit_001') - - set_int(column, 'WRITE_SENS_COLUMN', 0) - set_int(column, 'WRITE_SENS_COLUMN_INLET', 0) - set_int(column, 'WRITE_SENS_COLUMN_OUTLET', 0) - set_int(column, 'WRITE_SENS_FLUX', 0) - set_int(column, 'WRITE_SENS_PARTICLE', 0) - - set_int(column, 'WRITE_SOLUTION_COLUMN', 0) - set_int(column, 'WRITE_SOLUTION_COLUMN_INLET', 1) - set_int(column, 'WRITE_SOLUTION_COLUMN_OUTLET', 1) - set_int(column, 'WRITE_SOLUTION_FLUX', 0) - set_int(column, 'WRITE_SOLUTION_PARTICLE', 0) - -def createSolver(input): - solver = input.create_group("solver") - set_int(solver, 'NTHREADS', 0) - set_int(solver, 'CONSISTENT_INIT_MODE', 3) - set_double(solver, 'USER_SOLUTION_TIMES', np.linspace(0, 14731.2, 1000)) - - createSections(solver) - createTimeIntegrator(solver) - -def createSections(solver): - sections = solver.create_group("sections") - set_int(sections, 'NSEC', 3) - set_int(sections, 'SECTION_CONTINUITY', [0,0]) - set_double(sections, 'SECTION_TIMES', [0.0, 4445.422740524782, 6103.9941690962105, 14731.2]) - -def createTimeIntegrator(solver): - time_integrator = solver.create_group("time_integrator") - set_double(time_integrator, 'ABSTOL', 1e-10) - set_double(time_integrator, 'ALGTOL', 1e-10) - set_double(time_integrator, 'INIT_STEP_SIZE', 1e-8) - set_int(time_integrator, 'MAX_STEPS', 10000) - set_double(time_integrator, 'RELTOL', 1e-6) - -def runSimulation(filename): - proc = subprocess.Popen([cadet_location, filename], bufsize=0, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = proc.communicate() - proc.wait() - print("CADET Output") - print(stdout) - print("CADET Errors") - print(stderr) - -def plotSimulation(filename): - with h5py.File(filename, 'r') as h5: - f, (ax1, ax2) = plt.subplots(1, 2, figsize=[16, 8]) - plotInlet(ax1, h5) - plotOutlet(ax2, h5) - f.tight_layout() - plt.show() - -def plotInlet(axis, h5): - solution_times = np.array(h5['/output/solution/SOLUTION_TIMES'].value) - - inlet_salt = np.array(h5['/output/solution/unit_001/SOLUTION_COLUMN_INLET_COMP_000'].value) - inlet_p1 = np.array(h5['/output/solution/unit_001/SOLUTION_COLUMN_INLET_COMP_001'].value) - inlet_p2 = np.array(h5['/output/solution/unit_001/SOLUTION_COLUMN_INLET_COMP_002'].value) - - axis.set_title("Inlet") - axis.plot(solution_times, inlet_salt, 'b-', label="Salt") - axis.set_xlabel('time (s)') - - # Make the y-axis label, ticks and tick labels match the line color. - axis.set_ylabel('mMol Salt', color='b') - axis.tick_params('y', colors='b') - - axis2 = axis.twinx() - axis2.plot(solution_times, inlet_p1, 'r-', label="P1") - axis2.plot(solution_times, inlet_p2, 'r-', label="P2") - axis2.set_ylabel('mMol Protein', color='r') - axis2.tick_params('y', colors='r') - - - lines, labels = axis.get_legend_handles_labels() - lines2, labels2 = axis2.get_legend_handles_labels() - axis2.legend(lines + lines2, labels + labels2, loc=0) - - -def plotOutlet(axis, h5): - solution_times = np.array(h5['/output/solution/SOLUTION_TIMES'].value) - - outlet_salt = np.array(h5['/output/solution/unit_001/SOLUTION_COLUMN_OUTLET_COMP_000'].value) - outlet_p1 = np.array(h5['/output/solution/unit_001/SOLUTION_COLUMN_OUTLET_COMP_001'].value) - outlet_p2 = np.array(h5['/output/solution/unit_001/SOLUTION_COLUMN_OUTLET_COMP_002'].value) - - axis.set_title("Output") - axis.plot(solution_times, outlet_salt, 'b-', label="Salt") - axis.set_xlabel('time (s)') - - # Make the y-axis label, ticks and tick labels match the line color. - axis.set_ylabel('mMol Salt', color='b') - axis.tick_params('y', colors='b') - - axis2 = axis.twinx() - axis2.plot(solution_times, outlet_p1, 'r-', label="P1") - axis2.plot(solution_times, outlet_p2, 'r-', label="P2") - axis2.set_ylabel('mMol Protein', color='r') - axis2.tick_params('y', colors='r') - - - lines, labels = axis.get_legend_handles_labels() - lines2, labels2 = axis2.get_legend_handles_labels() - axis2.legend(lines + lines2, labels + labels2, loc=0) - - -if __name__ == "__main__": - import sys - print(sys.version) - main() diff --git a/examples/Pilot_Bypass_noBT_noC.py b/examples/Pilot_Bypass_noBT_noC.py deleted file mode 100644 index 1a053b8..0000000 --- a/examples/Pilot_Bypass_noBT_noC.py +++ /dev/null @@ -1,198 +0,0 @@ - -import matplotlib.pyplot as plt - -import numpy - -import pandas - -from cadet import Cadet -Cadet.cadet_path = "C:/Users/kosh_000/cadet_build/CADET-dev/MS_SMKL_RELEASE/bin/cadet-cli.exe" - -common = Cadet() -root = common.root - -root.input.model.unit_001.discretization.par_disc_type = 'EQUIDISTANT_PAR' -root.input.model.unit_001.discretization.schur_safety = 1.0e-8 -root.input.model.unit_001.discretization.use_analytic_jacobian = 1 -root.input.model.unit_001.discretization.weno.boundary_model = 0 -root.input.model.unit_001.discretization.weno.weno_eps = 1e-10 -root.input.model.unit_001.discretization.weno.weno_order = 3 -root.input.model.unit_001.discretization.gs_type = 1 -root.input.model.unit_001.discretization.max_krylov = 0 -root.input.model.unit_001.discretization.max_restarts = 10 - -root.input.solver.time_integrator.abstol = 1e-8 -root.input.solver.time_integrator.algtol = 1e-12 -root.input.solver.time_integrator.init_step_size = 1e-6 -root.input.solver.time_integrator.max_steps = 1000000 -root.input.solver.time_integrator.reltol = 1e-6 - -root.input.model.solver.gs_type = 1 -root.input.model.solver.max_krylov = 0 -root.input.model.solver.max_restarts = 10 -root.input.model.solver.schur_safety = 1e-8 - -#CADET 3.1 and CADET-dev flags are in here so that it works with both -#CADET-dev removed column from the name on the inputs and outputs since for many -#operations it no longer makes sense -root.input['return'].write_solution_times = 1 -root.input['return'].split_components_data = 1 -root.input['return'].unit_000.write_sens_bulk = 0 -root.input['return'].unit_000.write_sens_flux = 0 -root.input['return'].unit_000.write_sens_inlet = 0 -root.input['return'].unit_000.write_sens_outlet = 0 -root.input['return'].unit_000.write_sens_particle = 0 -root.input['return'].unit_000.write_solution_bulk = 0 -root.input['return'].unit_000.write_solution_flux = 0 -root.input['return'].unit_000.write_solution_inlet = 1 -root.input['return'].unit_000.write_solution_outlet = 1 -root.input['return'].unit_000.write_solution_particle = 0 -root.input['return'].unit_000.write_sens_column = 0 -root.input['return'].unit_000.write_sens_column_inlet = 0 -root.input['return'].unit_000.write_sens_column_outlet = 0 -root.input['return'].unit_000.write_solution_column = 0 -root.input['return'].unit_000.write_solution_column_inlet = 1 -root.input['return'].unit_000.write_solution_column_outlet = 1 - -root.input['return'].unit_001 = root.input['return'].unit_000 -root.input['return'].unit_002 = root.input['return'].unit_000 -root.input['return'].unit_003 = root.input['return'].unit_000 - -root.input.solver.nthreads = 1 - - -cstr = Cadet() -root = cstr.root - -root.input.model.unit_002.unit_type = 'CSTR' -root.input.model.unit_002.ncomp = 1 -root.input.model.unit_002.nbound =[0] -root.input.model.unit_002.init_c = [0] -root.input.model.unit_002.porosity = 1.0 -root.input.model.unit_002.init_volume = 5e-6 -root.input.model.unit_002.flowrate_filter = 0.0 - -dpfr = Cadet() -root = dpfr.root - -root.input.model.unit_001.unit_type = 'LUMPED_RATE_MODEL_WITHOUT_PORES' -root.input.model.unit_001.ncomp = 1 -root.input.model.unit_001.adsorption_model= 'NONE' -root.input.model.unit_001.init_c = [0] -root.input.model.unit_001.init_q = [] -root.input.model.unit_001.col_dispersion = 4e-8 -root.input.model.unit_001.col_length = 1.0 -root.input.model.unit_001.total_porosity = 1.0 -root.input.model.unit_001.velocity = 1.0 -root.input.model.unit_001.cross_section_area = ((3.75e-5)**2)*numpy.pi - -root.input.model.unit_001.discretization.nbound = [0] -root.input.model.unit_001.discretization.ncol = 50 -root.input.model.unit_001.discretization.use_analytic_jacobian = 1 -root.input.model.unit_001.discretization.reconstruction = 'WENO' - - -io = Cadet() -root = io.root - -root.input.model.unit_000.inlet_type = 'PIECEWISE_CUBIC_POLY' -root.input.model.unit_000.unit_type = 'INLET' -root.input.model.unit_000.ncomp = 1 - -root.input.model.unit_000.sec_000.const_coeff = [5.0] -root.input.model.unit_000.sec_000.lin_coeff = [0] -root.input.model.unit_000.sec_000.quad_coeff = [0] -root.input.model.unit_000.sec_000.cube_coeff = [0] - -root.input.model.unit_000.sec_001.const_coeff = [1000.0] -root.input.model.unit_000.sec_001.lin_coeff = [0] -root.input.model.unit_000.sec_001.quad_coeff = [0] -root.input.model.unit_000.sec_001.cube_coeff = [0] - -root.input.model.unit_000.sec_002.const_coeff = [0] -root.input.model.unit_000.sec_002.lin_coeff = [0] -root.input.model.unit_000.sec_002.quad_coeff = [0] -root.input.model.unit_000.sec_002.cube_coeff = [0] - -root.input.model.unit_003.unit_type = 'OUTLET' -root.input.model.unit_003.ncomp = 1 - -connectivity = Cadet() -root = connectivity.root - -root.input.model.nunits = 4 - -root.input.model.connections.nswitches = 1 -root.input.model.connections.switch_000.section = 0 -root.input.model.connections.switch_000.connections = [0, 2, -1, -1, (100/(60*1e6)), - 2, 1, -1, -1, (100/(60*1e6)), - 1, 3, -1, -1, (100/(60*1e6))] - -root.input.solver.user_solution_times = numpy.linspace(0, 153.87, 1000) -root.input.solver.sections.nsec = 3 -root.input.solver.sections.section_continuity = [0] -root.input.solver.sections.section_times = [0.0, 15.006, 100.0, 153.87] - - -def main(): - sim = Cadet(common.root, dpfr.root, io.root, cstr.root, connectivity.root) - sim.filename = r"F:\jurgen\Pilot_test.h5" - #createSimulation(sim) - sim.save() - sim.run() - sim.load() - plotSimulation(sim) - - #sim = Cadet(common.root, cstr.root, io.root, connectivity.root) - #sim.root.input['return'].unit_001.write_solution_volume = 1 - #sim.root.input.model.connections.switch_000.connections = [0, 1, -1, -1, 1.5, - # 1, 2, -1, -1, 1.0] - #sim.filename = r"C:\Users\Kohl\Desktop\Cadet\test_file_cstr.h5" - #sim.save() - #sim.run() - #sim.load() - #plotSimulation(sim) - #plotVolume(sim) - - writer = pandas.ExcelWriter(r'F:\jurgen\test_file_cstr.xlsx') - - inputs = pandas.DataFrame.from_items([('Time', sim.root.output.solution.solution_times), ('Concentration', sim.root.output.solution.unit_000.solution_inlet_comp_000)]) - outputs = pandas.DataFrame.from_items([('Time', sim.root.output.solution.solution_times), ('Concentration', sim.root.output.solution.unit_002.solution_outlet_comp_000)]) - #volumes = pandas.DataFrame.from_items([('Time', sim.root.output.solution.solution_times), ('Volume', numpy.squeeze(sim.root.output.solution.unit_001.solution_volume))]) - - inputs.to_excel(writer, 'Input', index=False) - outputs.to_excel(writer, 'Output', index=False) - #volumes.to_excel(writer, 'Volume', index=False) - - writer.save() - - -def plotSimulation(sim): - plt.plot(sim.root.output.solution.solution_times, sim.root.output.solution.unit_002.solution_outlet_comp_000) - plt.show() - -def plotVolume(sim): - plt.plot(sim.root.output.solution.solution_times, sim.root.output.solution.unit_001.solution_volume) - plt.show() - -def createSimulation(sim): - root = sim.root - - #SMA Model - root.input.model.unit_001.adsorption_model= 'STERIC_MASS_ACTION' - root.input.model.unit_001.adsorption.is_kinetic = 1 - root.input.model.unit_001.adsorption.sma_ka = [0,35.5] - root.input.model.unit_001.adsorption.sma_kd = [0,1000.0] - root.input.model.unit_001.adsorption.sma_lambda = 800.0 - root.input.model.unit_001.adsorption.sma_nu = [1,7.0] - root.input.model.unit_001.adsorption.sma_sigma = [0,10.0] - - #Linear isotherm - # root.input.model.unit_001.adsorption_model = 'LINEAR' - - #root.input.model.unit_001.adsorption.is_kinetic = 1 - #root.input.model.unit_001.adsorption.lin_ka = [5e-3] - #root.input.model.unit_001.adsorption.lin_kd = [1e-3] - -if __name__ == "__main__": - main() diff --git a/examples/Test.py b/examples/Test.py deleted file mode 100644 index 01583f4..0000000 --- a/examples/Test.py +++ /dev/null @@ -1,182 +0,0 @@ -import matplotlib.pyplot as plt - -import numpy - -from cadet import Cadet -Cadet.cadet_path = "C:/Users/kosh_000/cadet_build/CADET-dev/MS_SMKL_RELEASE/bin/cadet-cli.exe" - -import pandas - -common = Cadet() -root = common.root - -root.input.model.unit_001.discretization.par_disc_type = 'EQUIDISTANT_PAR' -root.input.model.unit_001.discretization.schur_safety = 1.0e-8 -root.input.model.unit_001.discretization.use_analytic_jacobian = 1 -root.input.model.unit_001.discretization.weno.boundary_model = 0 -root.input.model.unit_001.discretization.weno.weno_eps = 1e-10 -root.input.model.unit_001.discretization.weno.weno_order = 3 -root.input.model.unit_001.discretization.gs_type = 1 -root.input.model.unit_001.discretization.max_krylov = 0 -root.input.model.unit_001.discretization.max_restarts = 10 - -root.input.solver.time_integrator.abstol = 1e-8 -root.input.solver.time_integrator.algtol = 1e-12 -root.input.solver.time_integrator.init_step_size = 1e-6 -root.input.solver.time_integrator.max_steps = 1000000 -root.input.solver.time_integrator.reltol = 1e-6 - -root.input.model.solver.gs_type = 1 -root.input.model.solver.max_krylov = 0 -root.input.model.solver.max_restarts = 10 -root.input.model.solver.schur_safety = 1e-8 - -#CADET 3.1 and CADET-dev flags are in here so that it works with both -#CADET-dev removed column from the name on the inputs and outputs since for many -#operations it no longer makes sense -root.input['return'].write_solution_times = 1 -root.input['return'].split_components_data = 1 -root.input['return'].unit_000.write_sens_bulk = 0 -root.input['return'].unit_000.write_sens_flux = 0 -root.input['return'].unit_000.write_sens_inlet = 0 -root.input['return'].unit_000.write_sens_outlet = 0 -root.input['return'].unit_000.write_sens_particle = 0 -root.input['return'].unit_000.write_solution_bulk = 0 -root.input['return'].unit_000.write_solution_flux = 0 -root.input['return'].unit_000.write_solution_inlet = 1 -root.input['return'].unit_000.write_solution_outlet = 1 -root.input['return'].unit_000.write_solution_particle = 0 -root.input['return'].unit_000.write_sens_column = 0 -root.input['return'].unit_000.write_sens_column_inlet = 0 -root.input['return'].unit_000.write_sens_column_outlet = 0 -root.input['return'].unit_000.write_solution_column = 0 -root.input['return'].unit_000.write_solution_column_inlet = 1 -root.input['return'].unit_000.write_solution_column_outlet = 1 - -root.input['return'].unit_001 = root.input['return'].unit_000 -root.input['return'].unit_002 = root.input['return'].unit_000 - -root.input.solver.nthreads = 1 - -column_setup = Cadet() -root = column_setup.root - -root.input.model.unit_001.unit_type = 'GENERAL_RATE_MODEL' -root.input.model.unit_001.col_dispersion = 5.75e-8 -root.input.model.unit_001.col_length = 0.014 -root.input.model.unit_001.col_porosity = 0.37 -root.input.model.unit_001.film_diffusion = [6.9e-6] -root.input.model.unit_001.init_c = [0.0] -root.input.model.unit_001.init_q = [0.0] -root.input.model.unit_001.ncomp = 1 -root.input.model.unit_001.par_diffusion = [7e-10] -root.input.model.unit_001.par_porosity = 0.75 -root.input.model.unit_001.par_radius = 4.5e-5 -root.input.model.unit_001.par_surfdiffusion = [0.0] -root.input.model.unit_001.velocity = 1 -root.input.model.unit_001.cross_section_area = 4700.352526439483 - -root.input.model.unit_001.discretization.nbound = [1] -root.input.model.unit_001.discretization.ncol = 50 -root.input.model.unit_001.discretization.npar = 4 - -cstr = Cadet() -root = cstr.root - -root.input.model.unit_001.unit_type = 'CSTR' -root.input.model.unit_001.ncomp = 1 -root.input.model.unit_001.nbound = 0 -root.input.model.unit_001.init_c = [1.0] -root.input.model.unit_001.porosity = 1.0 -root.input.model.unit_001.init_volume = 10.0 -root.input.model.unit_001.flowrate_filter = 0.0 - -io = Cadet() -root = io.root - -root.input.model.unit_000.inlet_type = 'PIECEWISE_CUBIC_POLY' -root.input.model.unit_000.unit_type = 'INLET' -root.input.model.unit_000.ncomp = 1 - -root.input.model.unit_000.sec_000.const_coeff = [1.0] -root.input.model.unit_000.sec_000.lin_coeff = [0.0] -root.input.model.unit_000.sec_000.quad_coeff = [0.0] -root.input.model.unit_000.sec_000.cube_coeff = [0.0] - -root.input.model.unit_000.sec_001.const_coeff = [0.0] -root.input.model.unit_000.sec_001.lin_coeff = [0.0] -root.input.model.unit_000.sec_001.quad_coeff = [0.0] -root.input.model.unit_000.sec_001.cube_coeff = [0.0] - -root.input.model.unit_002.unit_type = 'OUTLET' -root.input.model.unit_002.ncomp = 1 - -connectivity = Cadet() -root = connectivity.root - -root.input.model.nunits = 3 - -root.input.model.connections.nswitches = 1 -root.input.model.connections.switch_000.section = 0 -root.input.model.connections.switch_000.connections = [0, 1, -1, -1, 1.0, - 1, 2, -1, -1, 1.0] - -root.input.solver.user_solution_times = numpy.linspace(0, 500, 501) -root.input.solver.sections.nsec = 2 -root.input.solver.sections.section_continuity = [0] -root.input.solver.sections.section_times = [0.0, 100.0, 500.0] - - -def main(): - #sim = Cadet(common.root, column_setup.root, io.root, connectivity.root) - #sim.filename = "F:/temp/test_file.h5" - #createSimulation(sim) - #sim.save() - #sim.run() - #sim.load() - #plotSimulation(sim) - - sim = Cadet(common.root, cstr.root, io.root, connectivity.root) - sim.root.input['return'].unit_001.write_solution_volume = 1 - sim.root.input.model.connections.switch_000.connections = [0, 1, -1, -1, 1.5, - 1, 2, -1, -1, 1.0] - sim.filename = "F:/temp/test_file_cstr.h5" - sim.save() - sim.run() - sim.load() - plotSimulation(sim) - plotVolume(sim) - - writer = pandas.ExcelWriter('F:/temp/test_file_cstr.xlsx') - - inputs = pandas.DataFrame.from_items([('Time', sim.root.output.solution.solution_times), ('Concentration', sim.root.output.solution.unit_002.solution_inlet_comp_000)]) - outputs = pandas.DataFrame.from_items([('Time', sim.root.output.solution.solution_times), ('Concentration', sim.root.output.solution.unit_002.solution_outlet_comp_000)]) - volumes = pandas.DataFrame.from_items([('Time', sim.root.output.solution.solution_times), ('Volume', numpy.squeeze(sim.root.output.solution.unit_001.solution_volume))]) - - inputs.to_excel(writer, 'Input', index=False) - outputs.to_excel(writer, 'Output', index=False) - volumes.to_excel(writer, 'Volume', index=False) - - writer.save() - - -def plotSimulation(sim): - plt.plot(sim.root.output.solution.solution_times, sim.root.output.solution.unit_002.solution_outlet_comp_000) - plt.show() - -def plotVolume(sim): - plt.plot(sim.root.output.solution.solution_times, sim.root.output.solution.unit_001.solution_volume) - plt.show() - -def createSimulation(sim): - root = sim.root - - - root.input.model.unit_001.adsorption_model = 'LINEAR' - - root.input.model.unit_001.adsorption.is_kinetic = 1 - root.input.model.unit_001.adsorption.lin_ka = [5e-3] - root.input.model.unit_001.adsorption.lin_kd = [1e-3] - -if __name__ == "__main__": - main() diff --git a/examples/__pycache__/common.cpython-37.pyc b/examples/__pycache__/common.cpython-37.pyc deleted file mode 100644 index 186ce56374baa6b80c6f5fd80a07353386124750..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1589 zcmaJ=%TE+B9G=-_huvi#JOu>t{Xm7F2M9b=a0=hS}-Zc7P>bj92w) zym|FzOgtH{{tL$OV4^2XJezp3oflhToMzka`}Ox~`|U!xT-3z(u=Wn@7c}jMB*~W* zU{3tly^N+I4e5^7)Pb&RSbwU449?hDkVP4swL9#bZNN^Xr8tkWDK4On6c;0ZlpEGy zR~s%xvC?jrVMSrp6F;%nt*{=&vsYn#3hP(m1{8Ki@f=jxkm5P4u(OKih_W}TuygVT zqSGY^jwQ%0CkUQLhQ#7N$LtI6VtR9u#djC)U5aW)%)Sh-pw5#>!LOpc!mmaB#L*{j zB{QGAmSlxElVM+nH&7wWBJnsXruZi6O7X2W|J!j_2An{p4Q;%9EY&ow2f0NPVScO& zb}S{=n0_|({Y7heHZUmWK6OEL`N7u8(#po>{OTrLn_q8y`1twDx5r;+j-?PAJwGR& zyn6X)rt$pO``5oey`8n@7OL9n_+*f)%hXheR11tcX*P))bWlQgkh5IR7fX{M>;wiQ z&OWA~v%w&5c^DL%<{{jpEr;xblEf4W+I!hLQL7jmnK z85m%mV{s@7dc?&pXAqg(4D!A!tW#4{pc;o^m%(k{*;5QVj(-Tc+dM4S!5s9oS%}Z2 zsjtoCiSDergHvPDQkXySOpIZT)6GY92I8cNYg}valsT=}-d9}&HC>SS%Hu^Q808*n zYdGGB)1R&yXBs(GRmnE`@69M#2y315D|)mf~ryh; Date: Tue, 23 Apr 2024 15:32:13 +0200 Subject: [PATCH 05/43] Update docstrings --- cadet/cadet.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/cadet/cadet.py b/cadet/cadet.py index c9d13c2..a4257fe 100644 --- a/cadet/cadet.py +++ b/cadet/cadet.py @@ -127,6 +127,10 @@ def is_dll(value): class CadetMeta(type): + """ + A meta class for the CADET interface. This allows calls to Cadet.cadet_path = "..." to set + the cadet_path for all subsequent Cadet() instances. + """ _cadet_runner_class = None _is_file_class = None @@ -141,10 +145,6 @@ def cadet_path(cls): @cadet_path.setter def cadet_path(cls, value): - def __init__(cls): - cls._cadet_runner_class = None - cls._is_file_class = True - if cls._cadet_runner_class is not None and cls._cadet_runner_class.cadet_path != value: del cls._cadet_runner_class @@ -165,12 +165,17 @@ class Cadet(H5, metaclass=CadetMeta): def __init__(self, *data): super().__init__(*data) self._cadet_runner = None - self.return_information = None - self._is_file = None + + self._is_file = None # Is CLI or DLL + # self.cadet_path # from Bill, declared in meta class -> path to CLI-file or DLL-file + + self.install_path = None # from Jo -> root of the CADET installation. + + self.cadet_cli_path = None self.cadet_dll_path = None self.cadet_create_lwe_path = None - self.cadet_cli_path = None - self._install_path = None + + self.return_information = None @property def is_file(self): From da30fdff49819a5fc84d97f4e10242726425e6f1 Mon Sep 17 00:00:00 2001 From: Samuel Leweke Date: Fri, 16 Jun 2023 12:29:23 +0200 Subject: [PATCH 06/43] Simplify ctypes API calls and add type hints --- cadet/cadet_dll.py | 154 ++++++++++++++++++++++++++++----------------- 1 file changed, 96 insertions(+), 58 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index 88920c5..1d9de13 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -14,17 +14,32 @@ def log_handler(file, func, line, level, level_name, message): point_int = ctypes.POINTER(ctypes.c_int) +CadetDriver = ctypes.c_void_p -def null(*args): +# Values of cdtResult +_CDT_OK = 0 +_CDT_DATA_NOT_STORED = -3 + +def _no_log_output(*args): pass if 0: log_print = print else: - log_print = null + log_print = _no_log_output class CADETAPIV010000_DATA(): + """ + Definition of CADET-CAPI v1.0 + + _data_ : dict with signatures of exported API functions. (See CADET/include/cadet/cadet.h) + lookup_prototype : ctypes for common parameters + lookup_output_argument_type : ctypes for API output parameters (e.g., double* time or double** data) + + """ + + # Order is important, has to match the cdtAPIv010000 struct of the C-API _data_ = {} _data_['createDriver'] = ('drv',) _data_['deleteDriver'] = (None, 'drv') @@ -59,9 +74,11 @@ class CADETAPIV010000_DATA(): _data_['getSensitivityDerivativeFlux'] = ('return', 'drv', 'unitOpId', 'idx', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp') _data_['getSensitivityDerivativeVolume'] = ('return', 'drv', 'unitOpId', 'idx', 'time', 'data', 'nTime') + + lookup_prototype = { 'return': c_cadet_result, - 'drv': ctypes.c_void_p, + 'drv': CadetDriver, 'unitOpId': ctypes.c_int, 'idx': ctypes.c_int, 'parType': ctypes.c_int, @@ -79,7 +96,7 @@ class CADETAPIV010000_DATA(): 'parameterProvider': ctypes.POINTER(cadet_dll_parameterprovider.PARAMETERPROVIDER) } - lookup_call = { + lookup_output_argument_type = { 'time': ctypes.POINTER(ctypes.c_double), 'nTime': ctypes.c_int, 'data': ctypes.POINTER(ctypes.c_double), @@ -93,7 +110,8 @@ class CADETAPIV010000_DATA(): } -def setup_api(): +def _setup_api(): + """list: Tuples with function names and ctype functions""" _fields_ = [] for key, value in CADETAPIV010000_DATA._data_.items(): @@ -104,140 +122,154 @@ def setup_api(): class CADETAPIV010000(ctypes.Structure): - _fields_ = setup_api() + """Mimic cdtAPIv010000 struct of CADET C-API in ctypes""" + _fields_ = _setup_api() -def null(obj): - "do nothing" - return obj class SimulationResult: - def __init__(self, api, driver): + def __init__(self, api: CADETAPIV010000, driver: CadetDriver): self._api = api self._driver = driver - def load_data(self, unit, get_solution, get_solution_str, idx=None, parType=None, own_data=True): - vars = {} - wrappers = {} + def load_data(self, unit: int, get_solution, get_solution_str: str, idx: int = None, parType: int = None, own_data: bool = True): + + # TODO: Check if get_solution = self._api.__getattr__(get_solution_str) works + + # Collect actual values + call_args = [] + call_outputs = {} + + # Construct API call function arguments for key in CADETAPIV010000_DATA._data_[get_solution_str]: if key == 'return': + # Skip, this is the actual return value of the API function continue elif key == 'drv': - vars['drv'] = self._driver - wrappers[key] = null + call_args.append(self._driver) elif key == 'unitOpId': - vars['unitOpId'] = unit - wrappers[key] = null + call_args.append(unit) elif key == 'idx': - vars['idx'] = idx - wrappers[key] = null + call_args.append(idx) elif key == 'parType': - vars['parType'] = parType - wrappers[key] = null + call_args.append(parType) else: - vars[key] = CADETAPIV010000_DATA.lookup_call[key]() - wrappers[key] = ctypes.byref + _obj = CADETAPIV010000_DATA.lookup_output_argument_type[key]() + call_outputs[key] = _obj + call_args.append(ctypes.byref(_obj)) - result = get_solution(*tuple(wrappers[var_key](var_value) for var_key, var_value in vars.items())) + result = get_solution(*call_args) + + if result == _CDT_DATA_NOT_STORED: + # Call successful, but data is not available + return None, None, None + elif result != _CDT_OK: + # Something else failed + return None, None, None shape = [] dims = [] - dimensions = ['nTime', 'nPort', 'nParShells', 'nAxialCells', 'nRadialCells', 'nComp', 'nBound'] + + # Ordering of multi-dimensional arrays, all possible dimensions: + # Example: Outlet [nTime, nPort, nComp] + # Bulk [nTime, nRadialCells, nAxialCells, nComp] if 2D model + # Bulk [nTime, nAxialCells, nComp] if 1D model + dimensions = ['nTime', 'nPort', 'nRadialCells', 'nAxialCells', 'nParShells', 'nComp', 'nBound'] for dim in dimensions: - if dim in vars and vars[dim].value: - shape.append(vars[dim].value) + if dim in call_outputs and call_outputs[dim].value: + shape.append(call_outputs[dim].value) dims.append(dim) - data = numpy.ctypeslib.as_array(vars['data'], shape=shape) - time = numpy.ctypeslib.as_array(vars['time'], shape=(vars['nTime'].value, )) + data = numpy.ctypeslib.as_array(call_outputs['data'], shape=shape) + time = numpy.ctypeslib.as_array(call_outputs['time'], shape=(call_outputs['nTime'].value, )) if own_data: return time.copy(), data.copy(), dims else: return time, data, dims - def inlet(self, unit, own_data=True): + def inlet(self, unit: int, own_data=True): return self.load_data(unit, self._api.getSolutionInlet, 'getSolutionInlet', own_data=own_data) - def outlet(self, unit, own_data=True): + def outlet(self, unit: int, own_data=True): return self.load_data(unit, self._api.getSolutionOutlet, 'getSolutionOutlet', own_data=own_data) - def bulk(self, unit, own_data=True): + def bulk(self, unit: int, own_data=True): return self.load_data(unit, self._api.getSolutionBulk, 'getSolutionBulk', own_data=own_data) - def particle(self, unit, parType, own_data=True): + def particle(self, unit: int, parType, own_data=True): return self.load_data(unit, self._api.getSolutionBulk, 'getSolutionBulk', own_data=own_data) - def solid(self, unit, parType, own_data=True): + def solid(self, unit: int, parType, own_data=True): return self.load_data(unit, self._api.getSolutionSolid, 'getSolutionSolid', own_data=own_data) - def flux(self, unit, own_data=True): + def flux(self, unit: int, own_data=True): return self.load_data(unit, self._api.getSolutionFlux, 'getSolutionFlux', own_data=own_data) - def volume(self, unit, own_data=True): + def volume(self, unit: int, own_data=True): return self.load_data(unit, self._api.getSolutionVolume, 'getSolutionVolume', own_data=own_data) - def derivativeInlet(self, unit, own_data=True): + def derivativeInlet(self, unit: int, own_data=True): return self.load_data(unit, self._api.getSolutionDerivativeInlet, 'getSolutionDerivativeInlet', own_data=own_data) - def derivativeOutlet(self, unit, own_data=True): + def derivativeOutlet(self, unit: int, own_data=True): return self.load_data(unit, self._api.getSolutionDerivativeOutlet, 'getSolutionDerivativeOutlet', own_data=own_data) - def derivativeBulk(self, unit, own_data=True): + def derivativeBulk(self, unit: int, own_data=True): return self.load_data(unit, self._api.getSolutionDerivativeBulk, 'getSolutionDerivativeBulk', own_data=own_data) - def derivativeParticle(self, unit, parType, own_data=True): + def derivativeParticle(self, unit: int, parType: int, own_data=True): return self.load_data(unit, self._api.getSolutionDerivativeParticle, 'getSolutionDerivativeParticle', own_data=own_data) - def derivativeSolid(self, unit, parType, own_data=True): + def derivativeSolid(self, unit: int, parType: int, own_data=True): return self.load_data(unit, self._api.getSolutionDerivativeSolid, 'getSolutionDerivativeSolid', own_data=own_data) - def derivativeFlux(self, unit, own_data=True): + def derivativeFlux(self, unit: int, own_data=True): return self.load_data(unit, self._api.getSolutionDerivativeFlux, 'getSolutionDerivativeFlux', own_data=own_data) - def derivativeVolume(self, unit, own_data=True): + def derivativeVolume(self, unit: int, own_data=True): return self.load_data(unit, self._api.getSolutionDerivativeVolume, 'getSolutionDerivativeVolume', own_data=own_data) - def sensitivityInlet(self, unit, idx, own_data=True): + def sensitivityInlet(self, unit: int, idx, own_data=True): return self.load_data(unit, self._api.getSensitivityInlet, 'getSensitivityInlet', idx=idx, own_data=own_data) - def sensitivityOutlet(self, unit, idx, own_data=True): + def sensitivityOutlet(self, unit: int, idx, own_data=True): return self.load_data(unit, self._api.getSensitivityOutlet, 'getSensitivityOutlet', idx=idx, own_data=own_data) - def sensitivityBulk(self, unit, idx, own_data=True): + def sensitivityBulk(self, unit: int, idx, own_data=True): return self.load_data(unit, self._api.getSensitivityBulk, 'getSensitivityBulk', idx=idx, own_data=own_data) - def sensitivityParticle(self, unit, idx, parType, own_data=True): + def sensitivityParticle(self, unit: int, idx, parType: int, own_data=True): return self.load_data(unit, self._api.getSensitivityParticle, 'getSensitivityParticle', idx=idx, parType=parType, own_data=own_data) - def sensitivitySolid(self, unit, idx, parType, own_data=True): + def sensitivitySolid(self, unit: int, idx, parType, own_data=True): return self.load_data(unit, self._api.getSensitivitySolid, 'getSensitivitySolid', idx=idx, parType=parType, own_data=own_data) - def sensitivityFlux(self, unit, idx, own_data=True): + def sensitivityFlux(self, unit: int, idx, own_data=True): return self.load_data(unit, self._api.getSensitivityFlux, 'getSensitivityFlux', idx=idx, own_data=own_data) - def sensitivityVolume(self, unit, idx, own_data=True): + def sensitivityVolume(self, unit: int, idx, own_data=True): return self.load_data(unit, self._api.getSensitivityVolume, 'getSensitivityVolume', idx=idx, own_data=own_data) - def sensitivityDerivativeInlet(self, unit, idx, own_data=True): + def sensitivityDerivativeInlet(self, unit: int, idx, own_data=True): return self.load_data(unit, self._api.getSensitivityDerivativeInlet, 'getSensitivityDerivativeInlet', idx=idx, own_data=own_data) - def sensitivityDerivativeOutlet(self, unit, idx, own_data=True): + def sensitivityDerivativeOutlet(self, unit: int, idx, own_data=True): return self.load_data(unit, self._api.getSensitivityDerivativeOutlet, 'getSensitivityDerivativeOutlet', idx=idx, own_data=own_data) - def sensitivityDerivativeBulk(self, unit, idx, own_data=True): + def sensitivityDerivativeBulk(self, unit: int, idx, own_data=True): return self.load_data(unit, self._api.getSensitivityDerivativeBulk, 'getSensitivityDerivativeBulk', idx=idx, own_data=own_data) - def sensitivityDerivativeParticle(self, unit, idx, parType, own_data=True): + def sensitivityDerivativeParticle(self, unit: int, idx, parType, own_data=True): return self.load_data(unit, self._api.getSensitivityDerivativeParticle, 'getSensitivityDerivativeParticle', idx=idx, parType=parType, own_data=own_data) - def sensitivityDerivativeSolid(self, unit, idx, parType, own_data=True): + def sensitivityDerivativeSolid(self, unit: int, idx, parType, own_data=True): return self.load_data(unit, self._api.getSensitivityDerivativeSolid, 'getSensitivityDerivativeSolid', idx=idx, parType=parType, own_data=own_data) - def sensitivityDerivativeFlux(self, unit, idx, own_data=True): + def sensitivityDerivativeFlux(self, unit: int, idx, own_data=True): return self.load_data(unit, self._api.getSensitivityDerivativeFlux, 'getSensitivityDerivativeFlux', idx=idx, own_data=own_data) - def sensitivityDerivativeVolume(self, unit, idx, own_data=True): + def sensitivityDerivativeVolume(self, unit: int, idx, own_data=True): return self.load_data(unit, self._api.getSensitivityDerivativeVolume, 'getSensitivityDerivativeVolume', idx=idx, own_data=own_data) @@ -307,11 +339,13 @@ def __del__(self): self._api.deleteDriver(self._driver) - def run(self, filename = None, simulation=None, timeout = None, check=None): + def run(self, filename=None, simulation=None, timeout=None, check=None): pp = cadet_dll_parameterprovider.PARAMETERPROVIDER(simulation) self._api.runSimulation(self._driver, ctypes.byref(pp)) self.res = SimulationResult(self._api, self._driver) + + # TODO: Return if simulation was successful or crashed return self.res def load_solution(self, sim, solution_fun, solution_str): @@ -329,6 +363,8 @@ def load_solution(self, sim, solution_fun, solution_str): unit = int(key[-3:]) t, out, dims = solution_fun(unit) + # Check if t is None -> Error + if not len(solution.solution_times): solution.solution_times = t @@ -344,6 +380,8 @@ def load_solution_io(self, sim, solution_fun, solution_str): unit = int(key[-3:]) t, out, dims = solution_fun(unit) + # Check if t is None -> Error + if not len(solution.solution_times): solution.solution_times = t From cdd65ba0beb66c235b7f12e43e011cfc510cac2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Tue, 20 Jun 2023 11:27:21 +0200 Subject: [PATCH 07/43] Use int for dtype (instead of numpy.int) --- cadet/cadet_dll_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadet/cadet_dll_utils.py b/cadet/cadet_dll_utils.py index d4b4045..3deb887 100644 --- a/cadet/cadet_dll_utils.py +++ b/cadet/cadet_dll_utils.py @@ -120,7 +120,7 @@ def param_provider_get_int_array(reader, name, n_elem, val): o = c[n] if isinstance(o, list): o = numpy.ascontiguousarray(o) - if (not isinstance(o, numpy.ndarray)) or (o.dtype != numpy.int) or (not o.flags.c_contiguous): + if (not isinstance(o, numpy.ndarray)) or (o.dtype != int) or (not o.flags.c_contiguous): return -1 n_elem[0] = ctypes.c_int(o.size) From 0e1d630b5e7a688de379f3c0cf794bc8344031e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 16 Jun 2023 15:57:49 +0200 Subject: [PATCH 08/43] WIP: Update methods --- cadet/cadet_dll.py | 867 ++++++++++++++++++++++++++++++--------------- 1 file changed, 585 insertions(+), 282 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index 1d9de13..c759086 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -1,13 +1,13 @@ - -from cmath import sin import ctypes import numpy import addict import cadet.cadet_dll_parameterprovider as cadet_dll_parameterprovider + def log_handler(file, func, line, level, level_name, message): log_print('{} ({}:{:d}) {}'.format(level_name.decode('utf-8') , func.decode('utf-8') , line, message.decode('utf-8') )) + c_cadet_result = ctypes.c_int array_double = ctypes.POINTER(ctypes.POINTER(ctypes.c_double)) @@ -20,9 +20,11 @@ def log_handler(file, func, line, level, level_name, message): _CDT_OK = 0 _CDT_DATA_NOT_STORED = -3 + def _no_log_output(*args): pass + if 0: log_print = print else: @@ -33,54 +35,66 @@ class CADETAPIV010000_DATA(): """ Definition of CADET-CAPI v1.0 - _data_ : dict with signatures of exported API functions. (See CADET/include/cadet/cadet.h) + signatures : dict with signatures of exported API functions. (See CADET/include/cadet/cadet.h) lookup_prototype : ctypes for common parameters lookup_output_argument_type : ctypes for API output parameters (e.g., double* time or double** data) - + """ # Order is important, has to match the cdtAPIv010000 struct of the C-API - _data_ = {} - _data_['createDriver'] = ('drv',) - _data_['deleteDriver'] = (None, 'drv') - _data_['runSimulation'] = ('return', 'drv', 'parameterProvider') - _data_['getNParTypes'] = ('return', 'drv', 'unitOpId', 'nParTypes') - _data_['getSolutionInlet'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nPort', 'nComp') - _data_['getSolutionOutlet'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nPort', 'nComp') - _data_['getSolutionBulk'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp') - _data_['getSolutionParticle'] = ('return', 'drv', 'unitOpId', 'parType', 'time', 'data', 'nTime', 'nParShells', 'nAxialCells', 'nRadialCells', 'nComp') - _data_['getSolutionSolid'] = ('return', 'drv', 'unitOpId', 'parType', 'time', 'data', 'nTime', 'nParShells', 'nAxialCells', 'nRadialCells', 'nBound') - _data_['getSolutionFlux'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp') - _data_['getSolutionVolume'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime') - _data_['getSolutionDerivativeInlet'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nPort', 'nComp') - _data_['getSolutionDerivativeOutlet'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nPort', 'nComp') - _data_['getSolutionDerivativeBulk'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp') - _data_['getSolutionDerivativeParticle'] = ('return', 'drv', 'unitOpId', 'parType', 'time', 'data', 'nTime', 'nParShells', 'nAxialCells', 'nRadialCells', 'nComp') - _data_['getSolutionDerivativeSolid'] = ('return', 'drv', 'unitOpId', 'parType', 'time', 'data', 'nTime', 'nParShells', 'nAxialCells', 'nRadialCells', 'nBound') - _data_['getSolutionDerivativeFlux'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp') - _data_['getSolutionDerivativeVolume'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime') - _data_['getSensitivityInlet'] = ('return', 'drv', 'unitOpId', 'idx', 'time', 'data', 'nTime', 'nPort', 'nComp') - _data_['getSensitivityOutlet'] = ('return', 'drv', 'unitOpId', 'idx', 'time', 'data', 'nTime', 'nPort', 'nComp') - _data_['getSensitivityBulk'] = ('return', 'drv', 'unitOpId', 'idx', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp') - _data_['getSensitivityParticle'] = ('return', 'drv', 'unitOpId', 'idx', 'parType', 'time', 'data', 'nTime', 'nParShells', 'nAxialCells', 'nRadialCells', 'nComp') - _data_['getSensitivitySolid'] = ('return', 'drv', 'unitOpId', 'idx', 'parType', 'time', 'data', 'nTime', 'nParShells', 'nAxialCells', 'nRadialCells', 'nBound') - _data_['getSensitivityFlux'] = ('return', 'drv', 'unitOpId', 'idx', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp') - _data_['getSensitivityVolume'] = ('return', 'drv', 'unitOpId', 'idx', 'time', 'data', 'nTime') - _data_['getSensitivityDerivativeInlet'] = ('return', 'drv', 'unitOpId', 'idx', 'time', 'data', 'nTime', 'nPort', 'nComp') - _data_['getSensitivityDerivativeOutlet'] = ('return', 'drv', 'unitOpId', 'idx', 'time', 'data', 'nTime', 'nPort', 'nComp') - _data_['getSensitivityDerivativeBulk'] = ('return', 'drv', 'unitOpId', 'idx', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp') - _data_['getSensitivityDerivativeParticle'] = ('return', 'drv', 'unitOpId', 'idx', 'parType', 'time', 'data', 'nTime', 'nParShells', 'nAxialCells', 'nRadialCells', 'nComp') - _data_['getSensitivityDerivativeSolid'] = ('return', 'drv', 'unitOpId', 'idx', 'parType', 'time', 'data', 'nTime', 'nParShells', 'nAxialCells', 'nRadialCells', 'nBound') - _data_['getSensitivityDerivativeFlux'] = ('return', 'drv', 'unitOpId', 'idx', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp') - _data_['getSensitivityDerivativeVolume'] = ('return', 'drv', 'unitOpId', 'idx', 'time', 'data', 'nTime') - + signatures = {} + signatures['createDriver'] = ('drv',) + signatures['deleteDriver'] = (None, 'drv') + signatures['runSimulation'] = ('return', 'drv', 'parameterProvider') + signatures['getNParTypes'] = ('return', 'drv', 'unitOpId', 'nParTypes') + + signatures['getSolutionInlet'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nPort', 'nComp') + signatures['getSolutionOutlet'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nPort', 'nComp') + signatures['getSolutionBulk'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp') + signatures['getSolutionParticle'] = ('return', 'drv', 'unitOpId', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nComp') + signatures['getSolutionSolid'] = ('return', 'drv', 'unitOpId', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nBound') + signatures['getSolutionFlux'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParTypes', 'nComp') + signatures['getSolutionVolume'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime') + + signatures['getSolutionDerivativeInlet'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nPort', 'nComp') + signatures['getSolutionDerivativeOutlet'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nPort', 'nComp') + signatures['getSolutionDerivativeBulk'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp') + signatures['getSolutionDerivativeParticle'] = ('return', 'drv', 'unitOpId', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nComp') + signatures['getSolutionDerivativeSolid'] = ('return', 'drv', 'unitOpId', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nBound') + signatures['getSolutionDerivativeFlux'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParTypes', 'nComp') + signatures['getSolutionDerivativeVolume'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime') + + signatures['getSensitivityInlet'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nPort', 'nComp') + signatures['getSensitivityOutlet'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nPort', 'nComp') + signatures['getSensitivityBulk'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp') + signatures['getSensitivityParticle'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nComp') + signatures['getSensitivitySolid'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nBound') + signatures['getSensitivityFlux'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParTypes', 'nComp') + signatures['getSensitivityVolume'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime') + + signatures['getSensitivityDerivativeInlet'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nPort', 'nComp') + signatures['getSensitivityDerivativeOutlet'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nPort', 'nComp') + signatures['getSensitivityDerivativeBulk'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nComp') + signatures['getSensitivityDerivativeParticle'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nComp') + signatures['getSensitivityDerivativeSolid'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'parType', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParShells', 'nBound') + signatures['getSensitivityDerivativeFlux'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime', 'nAxialCells', 'nRadialCells', 'nParTypes', 'nComp') + signatures['getSensitivityDerivativeVolume'] = ('return', 'drv', 'unitOpId', 'sensIdx', 'time', 'data', 'nTime') + + signatures['getLastState'] = ('return', 'drv', 'state', 'nStates') + signatures['getLastStateTimeDerivative'] = ('return', 'drv', 'state', 'nStates') + signatures['getLastUnitState'] = ('return', 'drv', 'unitOpId', 'state', 'nStates') + signatures['getLastUnitStateTimeDerivative'] = ('return', 'drv', 'unitOpId', 'state', 'nStates') + signatures['getLastSensitivityState'] = ('return', 'drv', 'sensIdx', 'state', 'nStates') + signatures['getLastSensitivityStateTimeDerivative'] = ('return', 'drv', 'sensIdx', 'state', 'nStates') + signatures['getLastSensitivityUnitState'] = ('return', 'drv', 'sensIdx', 'unitOpId', 'state', 'nStates') + signatures['getLastSensitivityUnitStateTimeDerivative'] = ('return', 'drv', 'sensIdx', 'unitOpId', 'state', 'nStates') lookup_prototype = { 'return': c_cadet_result, 'drv': CadetDriver, 'unitOpId': ctypes.c_int, - 'idx': ctypes.c_int, + 'sensIdx': ctypes.c_int, 'parType': ctypes.c_int, 'time': array_double, 'data': array_double, @@ -92,6 +106,8 @@ class CADETAPIV010000_DATA(): 'nParShells': point_int, 'nComp': point_int, 'nBound': point_int, + 'state': array_double, + 'nStates': point_int, None: None, 'parameterProvider': ctypes.POINTER(cadet_dll_parameterprovider.PARAMETERPROVIDER) } @@ -107,6 +123,8 @@ class CADETAPIV010000_DATA(): 'nParShells': ctypes.c_int, 'nComp': ctypes.c_int, 'nBound': ctypes.c_int, + 'state': ctypes.POINTER(ctypes.c_double), + 'nStates': ctypes.c_int, } @@ -114,7 +132,7 @@ def _setup_api(): """list: Tuples with function names and ctype functions""" _fields_ = [] - for key, value in CADETAPIV010000_DATA._data_.items(): + for key, value in CADETAPIV010000_DATA.signatures.items(): args = tuple(CADETAPIV010000_DATA.lookup_prototype[key] for key in value) _fields_.append( (key, ctypes.CFUNCTYPE(*args)) ) @@ -132,25 +150,30 @@ def __init__(self, api: CADETAPIV010000, driver: CadetDriver): self._api = api self._driver = driver - def load_data(self, unit: int, get_solution, get_solution_str: str, idx: int = None, parType: int = None, own_data: bool = True): + def load_data( + self, + get_solution_str: str, + unitOpId: int | None, + sensIdx: int = None, + parType: int = None): - # TODO: Check if get_solution = self._api.__getattr__(get_solution_str) works + get_solution = getattr(self._api, get_solution_str) # Collect actual values call_args = [] call_outputs = {} # Construct API call function arguments - for key in CADETAPIV010000_DATA._data_[get_solution_str]: + for key in CADETAPIV010000_DATA.signatures[get_solution_str]: if key == 'return': # Skip, this is the actual return value of the API function continue elif key == 'drv': call_args.append(self._driver) - elif key == 'unitOpId': - call_args.append(unit) - elif key == 'idx': - call_args.append(idx) + elif key == 'unitOpId' and unitOpId is not None: + call_args.append(unitOpId) + elif key == 'sensIdx': + call_args.append(sensIdx) elif key == 'parType': call_args.append(parType) else: @@ -160,12 +183,88 @@ def load_data(self, unit: int, get_solution, get_solution_str: str, idx: int = N result = get_solution(*call_args) + return result, call_outputs + + def process_data( + self, + result, + call_outputs, + own_data: bool = True): + if result == _CDT_DATA_NOT_STORED: # Call successful, but data is not available return None, None, None elif result != _CDT_OK: # Something else failed + raise Exception("Error reading data.") + + shape = [] + dims = [] + + # Ordering of multi-dimensional arrays, all possible dimensions: + # Example: Outlet [nTime, nPort, nComp] + # Bulk [nTime, nRadialCells, nAxialCells, nComp] if 2D model + # Bulk [nTime, nAxialCells, nComp] if 1D model + dimensions = ['nTime', 'nPort', 'nRadialCells', 'nAxialCells', 'nParShells', 'nComp', 'nBound'] + for dim in dimensions: + if dim in call_outputs and call_outputs[dim].value: + shape.append(call_outputs[dim].value) + dims.append(dim) + + data = numpy.ctypeslib.as_array(call_outputs['data'], shape=shape) + time = numpy.ctypeslib.as_array(call_outputs['time'], shape=(call_outputs['nTime'].value, )) + + if own_data: + return time.copy(), data.copy(), dims + else: + return time, data, dims + + def load_and_process(self, *args, own_data=True, **kwargs): + result, call_outputs = self.load_data(*args, **kwargs) + time, data, dims = self.process_data(result, call_outputs, own_data) + + return time, data, dims + + def load_data_old( + self, + get_solution_str: str, + unitOpId: int | None, + sensIdx: int = None, + parType: int = None, + own_data: bool = True): + + get_solution = getattr(self._api, get_solution_str) + + # Collect actual values + call_args = [] + call_outputs = {} + + # Construct API call function arguments + for key in CADETAPIV010000_DATA.signatures[get_solution_str]: + if key == 'return': + # Skip, this is the actual return value of the API function + continue + elif key == 'drv': + call_args.append(self._driver) + elif key == 'unitOpId' and unitOpId is not None: + call_args.append(unitOpId) + elif key == 'sensIdx': + call_args.append(sensIdx) + elif key == 'parType': + call_args.append(parType) + else: + _obj = CADETAPIV010000_DATA.lookup_output_argument_type[key]() + call_outputs[key] = _obj + call_args.append(ctypes.byref(_obj)) + + result = get_solution(*call_args) + + if result == _CDT_DATA_NOT_STORED: + # Call successful, but data is not available return None, None, None + elif result != _CDT_OK: + # Something else failed + raise Exception("Error reading data.") shape = [] dims = [] @@ -188,89 +287,283 @@ def load_data(self, unit: int, get_solution, get_solution_str: str, idx: int = N else: return time, data, dims - def inlet(self, unit: int, own_data=True): - return self.load_data(unit, self._api.getSolutionInlet, 'getSolutionInlet', own_data=own_data) + def npartypes(self, unitOpId: int, own_data=True): + result, call_outputs = self.load_data( + 'getNParTypes', + unitOpId, + ) - def outlet(self, unit: int, own_data=True): - return self.load_data(unit, self._api.getSolutionOutlet, 'getSolutionOutlet', own_data=own_data) + return int(numpy.ctypeslib.as_array(call_outputs['nParTypes'])) - def bulk(self, unit: int, own_data=True): - return self.load_data(unit, self._api.getSolutionBulk, 'getSolutionBulk', own_data=own_data) + def solution_inlet(self, unitOpId: int, own_data=True): + return self.load_and_process( + 'getSolutionInlet', + unitOpId, + own_data=own_data + ) - def particle(self, unit: int, parType, own_data=True): - return self.load_data(unit, self._api.getSolutionBulk, 'getSolutionBulk', own_data=own_data) + def solution_outlet(self, unitOpId: int, own_data=True): + return self.load_and_process( + 'getSolutionOutlet', + unitOpId, + own_data=own_data + ) - def solid(self, unit: int, parType, own_data=True): - return self.load_data(unit, self._api.getSolutionSolid, 'getSolutionSolid', own_data=own_data) + def solution_bulk(self, unitOpId: int, own_data=True): + return self.load_and_process( + 'getSolutionBulk', + unitOpId, + own_data=own_data + ) - def flux(self, unit: int, own_data=True): - return self.load_data(unit, self._api.getSolutionFlux, 'getSolutionFlux', own_data=own_data) + def solution_particle(self, unitOpId: int, parType, own_data=True): + return self.load_and_process( + 'getSolutionParticle', + unitOpId, + parType=parType, + own_data=own_data + ) - def volume(self, unit: int, own_data=True): - return self.load_data(unit, self._api.getSolutionVolume, 'getSolutionVolume', own_data=own_data) + def solution_solid(self, unitOpId: int, parType, own_data=True): + return self.load_and_process( + 'getSolutionSolid', + unitOpId, + parType=parType, + own_data=own_data + ) - def derivativeInlet(self, unit: int, own_data=True): - return self.load_data(unit, self._api.getSolutionDerivativeInlet, 'getSolutionDerivativeInlet', own_data=own_data) + def solution_flux(self, unitOpId: int, own_data=True): + return self.load_and_process( + 'getSolutionFlux', + unitOpId, + own_data=own_data + ) - def derivativeOutlet(self, unit: int, own_data=True): - return self.load_data(unit, self._api.getSolutionDerivativeOutlet, 'getSolutionDerivativeOutlet', own_data=own_data) + def solution_volume(self, unitOpId: int, own_data=True): + return self.load_and_process( + 'getSolutionVolume', + unitOpId, + own_data=own_data + ) - def derivativeBulk(self, unit: int, own_data=True): - return self.load_data(unit, self._api.getSolutionDerivativeBulk, 'getSolutionDerivativeBulk', own_data=own_data) + def soldot_inlet(self, unitOpId: int, own_data=True): + return self.load_and_process( + 'getSolutionDerivativeInlet', + unitOpId, + own_data=own_data + ) - def derivativeParticle(self, unit: int, parType: int, own_data=True): - return self.load_data(unit, self._api.getSolutionDerivativeParticle, 'getSolutionDerivativeParticle', own_data=own_data) + def soldot_outlet(self, unitOpId: int, own_data=True): + return self.load_and_process( + 'getSolutionDerivativeOutlet', + unitOpId, + own_data=own_data + ) - def derivativeSolid(self, unit: int, parType: int, own_data=True): - return self.load_data(unit, self._api.getSolutionDerivativeSolid, 'getSolutionDerivativeSolid', own_data=own_data) + def soldot_bulk(self, unitOpId: int, own_data=True): + return self.load_and_process( + 'getSolutionDerivativeBulk', + unitOpId, + own_data=own_data + ) + + def soldot_particle(self, unitOpId: int, parType: int, own_data=True): + return self.load_and_process( + 'getSolutionDerivativeParticle', + unitOpId, + parType=parType, + own_data=own_data + ) + + def soldot_solid(self, unitOpId: int, parType: int, own_data=True): + return self.load_and_process( + 'getSolutionDerivativeSolid', + unitOpId, + parType=parType, + own_data=own_data + ) + + def soldot_flux(self, unitOpId: int, own_data=True): + return self.load_and_process( + 'getSolutionDerivativeFlux', + unitOpId, + own_data=own_data + ) + + def soldot_volume(self, unitOpId: int, own_data=True): + return self.load_and_process( + 'getSolutionDerivativeVolume', + unitOpId, + own_data=own_data + ) + + def sens_inlet(self, unitOpId: int, sensIdx, own_data=True): + return self.load_and_process( + 'getSensitivityInlet', + unitOpId, + sensIdx=sensIdx, + own_data=own_data + ) + + def sens_outlet(self, unitOpId: int, sensIdx, own_data=True): + return self.load_and_process( + 'getSensitivityOutlet', + unitOpId, + sensIdx=sensIdx, + own_data=own_data + ) + + def sens_bulk(self, unitOpId: int, sensIdx, own_data=True): + return self.load_and_process( + 'getSensitivityBulk', + unitOpId, + sensIdx=sensIdx, + own_data=own_data + ) + + def sens_particle(self, unitOpId: int, sensIdx, parType: int, own_data=True): + return self.load_and_process( + 'getSensitivityParticle', + unitOpId, + sensIdx=sensIdx, + parType=parType, own_data=own_data + ) - def derivativeFlux(self, unit: int, own_data=True): - return self.load_data(unit, self._api.getSolutionDerivativeFlux, 'getSolutionDerivativeFlux', own_data=own_data) + def sens_solid(self, unitOpId: int, sensIdx, parType, own_data=True): + return self.load_and_process( + 'getSensitivitySolid', + unitOpId, + sensIdx=sensIdx, + parType=parType, own_data=own_data + ) - def derivativeVolume(self, unit: int, own_data=True): - return self.load_data(unit, self._api.getSolutionDerivativeVolume, 'getSolutionDerivativeVolume', own_data=own_data) + def sens_flux(self, unitOpId: int, sensIdx, own_data=True): + return self.load_and_process( + 'getSensitivityFlux', + unitOpId, + sensIdx=sensIdx, + own_data=own_data + ) - def sensitivityInlet(self, unit: int, idx, own_data=True): - return self.load_data(unit, self._api.getSensitivityInlet, 'getSensitivityInlet', idx=idx, own_data=own_data) + def sens_volume(self, unitOpId: int, sensIdx, own_data=True): + return self.load_and_process( + 'getSensitivityVolume', + unitOpId, + sensIdx=sensIdx, + own_data=own_data + ) - def sensitivityOutlet(self, unit: int, idx, own_data=True): - return self.load_data(unit, self._api.getSensitivityOutlet, 'getSensitivityOutlet', idx=idx, own_data=own_data) + def sensdot_inlet(self, unitOpId: int, sensIdx, own_data=True): + return self.load_and_process( + 'getSensitivityDerivativeInlet', + unitOpId, + sensIdx=sensIdx, + own_data=own_data + ) - def sensitivityBulk(self, unit: int, idx, own_data=True): - return self.load_data(unit, self._api.getSensitivityBulk, 'getSensitivityBulk', idx=idx, own_data=own_data) + def sensdot_outlet(self, unitOpId: int, sensIdx, own_data=True): + return self.load_and_process( + 'getSensitivityDerivativeOutlet', + unitOpId, + sensIdx=sensIdx, + own_data=own_data + ) - def sensitivityParticle(self, unit: int, idx, parType: int, own_data=True): - return self.load_data(unit, self._api.getSensitivityParticle, 'getSensitivityParticle', idx=idx, parType=parType, own_data=own_data) + def sensdot_bulk(self, unitOpId: int, sensIdx, own_data=True): + return self.load_and_process( + 'getSensitivityDerivativeBulk', + unitOpId, + sensIdx=sensIdx, + own_data=own_data + ) - def sensitivitySolid(self, unit: int, idx, parType, own_data=True): - return self.load_data(unit, self._api.getSensitivitySolid, 'getSensitivitySolid', idx=idx, parType=parType, own_data=own_data) + def sensdot_particle(self, unitOpId: int, sensIdx, parType, own_data=True): + return self.load_and_process( + 'getSensitivityDerivativeParticle', + unitOpId, + sensIdx=sensIdx, + parType=parType, own_data=own_data + ) - def sensitivityFlux(self, unit: int, idx, own_data=True): - return self.load_data(unit, self._api.getSensitivityFlux, 'getSensitivityFlux', idx=idx, own_data=own_data) + def sensdot_solid(self, unitOpId: int, sensIdx, parType, own_data=True): + return self.load_and_process( + 'getSensitivityDerivativeSolid', + unitOpId, + sensIdx=sensIdx, + parType=parType, own_data=own_data + ) - def sensitivityVolume(self, unit: int, idx, own_data=True): - return self.load_data(unit, self._api.getSensitivityVolume, 'getSensitivityVolume', idx=idx, own_data=own_data) + def sensdot_flux(self, unitOpId: int, sensIdx, own_data=True): + return self.load_and_process( + 'getSensitivityDerivativeFlux', + unitOpId, + sensIdx=sensIdx, + own_data=own_data + ) - def sensitivityDerivativeInlet(self, unit: int, idx, own_data=True): - return self.load_data(unit, self._api.getSensitivityDerivativeInlet, 'getSensitivityDerivativeInlet', idx=idx, own_data=own_data) + def sensdot_volume(self, unitOpId: int, sensIdx, own_data=True): + return self.load_and_process( + 'getSensitivityDerivativeVolume', + unitOpId, + sensIdx=sensIdx, + own_data=own_data + ) - def sensitivityDerivativeOutlet(self, unit: int, idx, own_data=True): - return self.load_data(unit, self._api.getSensitivityDerivativeOutlet, 'getSensitivityDerivativeOutlet', idx=idx, own_data=own_data) + def last_state_y(self, own_data=True): + return self.load_and_process( + 'getLastState', + own_data=own_data + ) - def sensitivityDerivativeBulk(self, unit: int, idx, own_data=True): - return self.load_data(unit, self._api.getSensitivityDerivativeBulk, 'getSensitivityDerivativeBulk', idx=idx, own_data=own_data) + def last_state_ydot(self, sensIdx, own_data=True): + return self.load_and_process( + 'getLastStateDerivative', + own_data=own_data + ) - def sensitivityDerivativeParticle(self, unit: int, idx, parType, own_data=True): - return self.load_data(unit, self._api.getSensitivityDerivativeParticle, 'getSensitivityDerivativeParticle', idx=idx, parType=parType, own_data=own_data) + def last_state_y_unit(self, unitOpId: int, own_data=True): + return self.load_and_process( + 'getLastUnitState', + unitOpId, + own_data=own_data + ) - def sensitivityDerivativeSolid(self, unit: int, idx, parType, own_data=True): - return self.load_data(unit, self._api.getSensitivityDerivativeSolid, 'getSensitivityDerivativeSolid', idx=idx, parType=parType, own_data=own_data) + def last_state_ydot_unit(self, unitOpId: int, own_data=True): + return self.load_and_process( + 'getLastUnitStateTimeDerivative', + unitOpId, + own_data=own_data + ) - def sensitivityDerivativeFlux(self, unit: int, idx, own_data=True): - return self.load_data(unit, self._api.getSensitivityDerivativeFlux, 'getSensitivityDerivativeFlux', idx=idx, own_data=own_data) + def last_state_sens(self, sensIdx, own_data=True): + return self.load_and_process( + 'getLastSensitivityState', + sensIdx=sensIdx, + own_data=own_data + ) - def sensitivityDerivativeVolume(self, unit: int, idx, own_data=True): - return self.load_data(unit, self._api.getSensitivityDerivativeVolume, 'getSensitivityDerivativeVolume', idx=idx, own_data=own_data) + def last_state_sensdot(self, sensIdx, own_data=True): + return self.load_and_process( + 'getLastSensitivityStateTimeDerivative', + sensIdx=sensIdx, + own_data=own_data + ) + + def last_state_sens_unit(self, unitOpId: int, sensIdx, own_data=True): + return self.load_and_process( + 'getLastSensitivityUnitState', + unitOpId, + sensIdx=sensIdx, + own_data=own_data + ) + + def last_state_sensdot_unit(self, unitOpId: int, sensIdx, own_data=True): + return self.load_and_process( + 'getLastSensitivityUnitStateTimeDerivative', + unitOpId, + sensIdx=sensIdx, + own_data=own_data + ) class CadetDLL: @@ -338,7 +631,6 @@ def __del__(self): log_print('deleteDriver()') self._api.deleteDriver(self._driver) - def run(self, filename=None, simulation=None, timeout=None, check=None): pp = cadet_dll_parameterprovider.PARAMETERPROVIDER(simulation) @@ -348,201 +640,212 @@ def run(self, filename=None, simulation=None, timeout=None, check=None): # TODO: Return if simulation was successful or crashed return self.res - def load_solution(self, sim, solution_fun, solution_str): - # - [ ] Split Ports (incl `SINGLE_AS_MULTI_PORT`) - # - [ ] Split Partype (Particle + Solid) - # - [ ] Coordinates? - # - [ ] Sensitivities / IDs - # - [ ] LAST_STATE_Y / LAST_STATE_YDOT - # - [ ] LAST_STATE_SENSY_XXX / LAST_STATE_SENSYDOT_XXX + def load_results(self, sim): + # TODO: solution time + # sim.root.output.solution.solution_time = ??? + self.load_coordinates(sim) + # TODO: Crashes when simulation includes sensitivities + self.load_solution(sim) + self.load_sensitivity(sim) + self.load_state(sim) + + def load_coordinates(self, sim): solution = addict.Dict() - if self.res is not None: - for key, value in sim.root.input['return'].items(): - if key.startswith('unit'): - if value[f'write_{solution_str}']: - unit = int(key[-3:]) - t, out, dims = solution_fun(unit) + for unit in range(sim.root.input.model.nunits): + unit_index = self._get_index_string('unit', unit) + if 'write_coordinates' in sim.root.input['return'][unit_index].keys(): + # TODO: Missing CAPI call + pass - # Check if t is None -> Error - - if not len(solution.solution_times): - solution.solution_times = t - - solution[key][solution_str] = out return solution - def load_solution_io(self, sim, solution_fun, solution_str): + def load_solution(self, sim): solution = addict.Dict() - if self.res is not None: - for key,value in sim.root.input['return'].items(): - if key.startswith('unit'): - if value[f'write_{solution_str}']: - unit = int(key[-3:]) - t, out, dims = solution_fun(unit) - - # Check if t is None -> Error - - if not len(solution.solution_times): - solution.solution_times = t - - split_components_data = value.get('split_components_data', 1) - split_ports_data = value.get('split_ports_data', 1) - single_as_multi_port = value.get('single_as_multi_port', 0) - - nComp = dims.index('nComp') - try: - nPorts = dims.index('nPorts') - except ValueError: - nPorts = None - - if split_components_data: - if split_ports_data: - if nPorts is None: - if single_as_multi_port: - for comp in range(out.shape[nComp]): - comp_out = numpy.squeeze(out[..., comp]) - solution[key][f'{solution_str}_port_000_comp_{comp:03d}'] = comp_out - else: - for comp in range(out.shape[nComp]): - comp_out = numpy.squeeze(out[..., comp]) - solution[key][f'{solution_str}_comp_{comp:03d}'] = comp_out - else: - for port in range(out.shape[nPorts]): - for comp in range(out.shape[nComp]): - comp_out = numpy.squeeze(out[..., port, comp]) - solution[key][f'{solution_str}_port_{port:03d}_comp_{comp:03d}'] = comp_out - else: - for comp in range(out.shape[nComp]): - comp_out = numpy.squeeze(out[...,comp]) - solution[key][f'{solution_str}_comp_{comp:03d}'] = comp_out - else: - if split_ports_data: - if nPorts is None: - if single_as_multi_port: - solution[key][f'{solution_str}_port_000'] = out - else: - solution[key][solution_str] = out - else: - for port in range(out.shape[nPorts]): - port_out = numpy.squeeze(out[..., port, :]) - solution[key][f'{solution_str}_port_{port:03d}'] = port_out - else: - solution[key][solution_str] = out - return solution - - def load_inlet(self, sim): - return self.load_solution_io(sim, self.res.inlet, 'solution_inlet') - - def load_outlet(self, sim): - return self.load_solution_io(sim, self.res.outlet, 'solution_outlet') - - def load_bulk(self, sim): - return self.load_solution(sim, self.res.bulk, 'solution_bulk') - - def load_particle(self, sim): - return self.load_solution(sim, self.res.particle, 'solution_particle') - - def load_solid(self, sim): - return self.load_solution(sim, self.res.solid, 'solution_solid') - - def load_flux(self, sim): - return self.load_solution(sim, self.res.flux, 'solution_flux') - - def load_flux(self, sim): - return self.load_solution(sim, self.res.flux, 'solution_flux') - - def load_volume(self, sim): - return self.load_solution(sim, self.res.volume, 'solution_volume') - - def load_derivative_inlet(self, sim): - return self.load_solution_io(sim, self.res.derivativeInlet, 'soldot_inlet') - - def load_derivative_outlet(self, sim): - return self.load_solution_io(sim, self.res.derivativeOutlet, 'soldot_outlet') - - def load_derivative_bulk(self, sim): - return self.load_solution(sim, self.res.derivativeBulk, 'soldot_bulk') - - def load_derivative_particle(self, sim): - return self.load_solution(sim, self.res.derivativeParticle, 'soldot_particle') - - def load_derivative_solid(self, sim): - return self.load_solution(sim, self.res.derivativeSolid, 'soldot_solid') - - def load_derivative_flux(self, sim): - return self.load_solution(sim, self.res.derivativeFlux, 'soldot_flux') + for unit in range(sim.root.input.model.nunits): + unit_index = self._get_index_string('unit', unit) + unit_solution = addict.Dict() + + unit_solution.update(self._load_solution_io(sim, unit, 'solution_inlet')) + unit_solution.update(self._load_solution_io(sim, unit, 'solution_outlet')) + unit_solution.update(self._load_solution_trivial(sim, unit, 'solution_bulk')) + unit_solution.update(self._load_solution_particle(sim, unit, 'solution_particle')) + unit_solution.update(self._load_solution_particle(sim, unit, 'solution_solid')) + unit_solution.update(self._load_solution_trivial(sim, unit, 'solution_flux')) + unit_solution.update(self._load_solution_trivial(sim, unit, 'solution_volume')) + + unit_solution.update(self._load_solution_io(sim, unit, 'soldot_inlet')) + unit_solution.update(self._load_solution_io(sim, unit, 'soldot_outlet')) + unit_solution.update(self._load_solution_trivial(sim, unit, 'soldot_bulk')) + unit_solution.update(self._load_solution_particle(sim, unit, 'soldot_particle')) + unit_solution.update(self._load_solution_particle(sim, unit, 'soldot_solid')) + unit_solution.update(self._load_solution_trivial(sim, unit, 'soldot_flux')) + unit_solution.update(self._load_solution_trivial(sim, unit, 'soldot_volume')) + + solution[unit_index].update(unit_solution) - def load_derivative_volume(self, sim): - return self.load_solution(sim, self.res.derivativeVolume, 'soldot_volume') - - def load_sensitivity_inlet(self, sim): - return self.load_solution_io(sim, self.res.sensitivityInlet, 'sens_inlet') + return solution - def load_sensitivity_outlet(self, sim): - return self.load_solution_io(sim, self.res.sensitivityOutlet, 'sens_outlet') + def load_sensitivity(self, sim): + sensitivity = addict.Dict() + nsens = sim.root.input.sensitivity.get('nsens', 0) + for sens in range(nsens): + sens_index = self._get_index_string('param', sens) + for unit in range(sim.root.input.model.nunits): + unit_sensitivity = addict.Dict() + + unit_sensitivity[sens_index].update( + self._load_solution_io(sim, unit, 'sens_inlet', sens) + ) + unit_sensitivity[sens_index].update( + self._load_solution_io(sim, unit, 'sens_outlet', sens) + ) + unit_sensitivity[sens_index].update( + self._load_solution_trivial(sim, unit, 'sens_bulk', sens) + ) + unit_sensitivity[sens_index].update( + self._load_solution_particle(sim, unit, 'sens_particle', sens) + ) + unit_sensitivity[sens_index].update( + self._load_solution_particle(sim, unit, 'sens_solid', sens) + ) + unit_sensitivity[sens_index].update( + self._load_solution_trivial(sim, unit, 'sens_flux', sens) + ) + unit_sensitivity[sens_index].update( + self._load_solution_trivial(sim, unit, 'sens_volume', sens) + ) + + unit_sensitivity[sens_index].update( + self._load_solution_io(sim, unit, 'sensdot_inlet', sens) + ) + unit_sensitivity[sens_index].update( + self._load_solution_io(sim, unit, 'sensdot_outlet', sens) + ) + unit_sensitivity[sens_index].update( + self._load_solution_trivial(sim, unit, 'sensdot_bulk', sens) + ) + unit_sensitivity[sens_index].update( + self._load_solution_particle(sim, unit, 'sensdot_particle', sens) + ) + unit_sensitivity[sens_index].update( + self._load_solution_particle(sim, unit, 'sensdot_solid', sens) + ) + unit_sensitivity[sens_index].update( + self._load_solution_trivial(sim, unit, 'sensdot_flux', sens) + ) + unit_sensitivity[sens_index].update( + self._load_solution_trivial(sim, unit, 'sensdot_volume', sens) + ) + + sensitivity[sens_index].update(unit_sensitivity) + + def _checks_if_write_is_true(func): + def wrapper(self, sim, unitOpId, solution_str, *args, **kwargs): + unit_index = self._get_index_string('unit', unitOpId) + solution_recorder = sim.root.input['return'][unit_index].keys() + if f'write_{solution_str}' not in solution_recorder: + return {} + + return func(self, sim, unitOpId, solution_str, *args, **kwargs) + + return wrapper + + @_checks_if_write_is_true + def _load_solution_trivial(self, sim, unitOpId, solution_str, sensIdx=None): + solution = addict.Dict() + solution_fun = getattr(self.res, solution_str) + if sensIdx is None: + t, out, dims = solution_fun(unitOpId) + else: + t, out, dims = solution_fun(unitOpId, sensIdx) - def load_sensitivity_bulk(self, sim): - return self.load_solution(sim, self.res.sensitivityBulk, 'sens_bulk') + solution[solution_str] = out - def load_sensitivity_particle(self, sim): - return self.load_solution(sim, self.res.sensitivityParticle, 'sens_particle') + return solution - def load_sensitivity_solid(self, sim): - return self.load_solution(sim, self.res.sensitivitySolid, 'sens_solid') + @_checks_if_write_is_true + def _load_solution_io(self, sim, unitOpId, solution_str, sensIdx=None): + solution = addict.Dict() + solution_fun = getattr(self.res, solution_str) - def load_sensitivity_flux(self, sim): - return self.load_solution(sim, self.res.sensitivityFlux, 'sens_flux') + if sensIdx is None: + t, out, dims = solution_fun(unitOpId) + else: + t, out, dims = solution_fun(unitOpId, sensIdx) + + split_components_data = sim.root.input['return'].get('split_components_data', 1) + split_ports_data = sim.root.input['return'].get('split_ports_data', 1) + single_as_multi_port = sim.root.input['return'].get('single_as_multi_port', 0) + + nComp = dims.index('nComp') + try: + nPorts = dims.index('nPorts') + except ValueError: + nPorts = None + + if split_components_data: + if split_ports_data: + if nPorts is None: + if single_as_multi_port: + for comp in range(out.shape[nComp]): + comp_out = numpy.squeeze(out[..., comp]) + solution[f'{solution_str}_port_000_comp_{comp:03d}'] = comp_out + else: + for comp in range(out.shape[nComp]): + comp_out = numpy.squeeze(out[..., comp]) + solution[f'{solution_str}_comp_{comp:03d}'] = comp_out + else: + for port in range(out.shape[nPorts]): + for comp in range(out.shape[nComp]): + comp_out = numpy.squeeze(out[..., port, comp]) + solution[f'{solution_str}_port_{port:03d}_comp_{comp:03d}'] = comp_out + else: + for comp in range(out.shape[nComp]): + comp_out = numpy.squeeze(out[..., comp]) + solution[f'{solution_str}_comp_{comp:03d}'] = comp_out + else: + if split_ports_data: + if nPorts is None: + if single_as_multi_port: + solution[f'{solution_str}_port_000'] = out + else: + solution[solution_str] = out + else: + for port in range(out.shape[nPorts]): + port_out = numpy.squeeze(out[..., port, :]) + solution[f'{solution_str}_port_{port:03d}'] = port_out + else: + solution[solution_str] = out - def load_sensitivity_volume(self, sim): - return self.load_solution(sim, self.res.sensitivityVolume, 'sens_volume') + return solution - def load_sensitivity_derivative_inlet(self, sim): - return self.load_solution_io(sim, self.res.sensitivityDerivativeInlet, 'sensdot_inlet') + @_checks_if_write_is_true + def _load_solution_particle(self, sim, unitOpId, solution_str, sensIdx=None): + solution = addict.Dict() + solution_fun = getattr(self.res, solution_str) - def load_sensitivity_derivative_outlet(self, sim): - return self.load_solution_io(sim, self.res.sensitivityDerivativeOutlet, 'sensdot_outlet') + unit_index = self._get_index_string('unit', unitOpId) - def load_sensitivity_derivative_bulk(self, sim): - return self.load_solution(sim, self.res.sensitivityDerivativeBulk, 'sensdot_bulk') + npartype = self.res.npartypes(unitOpId) - def load_sensitivity_derivative_particle(self, sim): - return self.load_solution(sim, self.res.sensitivityDerivativeParticle, 'sensdot_particle') + for partype in range(npartype): + if sensIdx is None: + t, out, dims = solution_fun(unitOpId, partype) + else: + t, out, dims = solution_fun(unitOpId, sensIdx, partype) - def load_sensitivity_derivative_solid(self, sim): - return self.load_solution(sim, self.res.sensitivityDerivativeSolid, 'sensdot_solid') + if npartype == 1: + solution[solution_str] = out + else: + par_index = self._get_index_string('partype', partype) + solution[f'{solution_str}_{par_index}'] = out - def load_sensitivity_derivative_flux(self, sim): - return self.load_solution(sim, self.res.sensitivityDerivativeFlux, 'sensdot_flux') + return solution - def load_sensitivity_derivative_volume(self, sim): - return self.load_solution(sim, self.res.sensitivityDerivativeVolume, 'sensdot_volume') + def load_state(self, sim): + # TODO + pass - def load_results(self, sim): - sim.root.output.solution.update(self.load_inlet(sim)) - sim.root.output.solution.update(self.load_outlet(sim)) - sim.root.output.solution.update(self.load_bulk(sim)) - sim.root.output.solution.update(self.load_particle(sim)) - sim.root.output.solution.update(self.load_solid(sim)) - sim.root.output.solution.update(self.load_flux(sim)) - sim.root.output.solution.update(self.load_volume(sim)) - sim.root.output.solution.update(self.load_derivative_inlet(sim)) - sim.root.output.solution.update(self.load_derivative_outlet(sim)) - sim.root.output.solution.update(self.load_derivative_bulk(sim)) - sim.root.output.solution.update(self.load_derivative_particle(sim)) - sim.root.output.solution.update(self.load_derivative_solid(sim)) - sim.root.output.solution.update(self.load_derivative_flux(sim)) - sim.root.output.solution.update(self.load_derivative_volume(sim)) - #sim.root.output.solution.update(self.load_sensitivity_inlet(sim)) - #sim.root.output.solution.update(self.load_sensitivity_outlet(sim)) - #sim.root.output.solution.update(self.load_sensitivity_bulk(sim)) - #sim.root.output.solution.update(self.load_sensitivity_particle(sim)) - #sim.root.output.solution.update(self.load_sensitivity_solid(sim)) - #sim.root.output.solution.update(self.load_sensitivity_flux(sim)) - #sim.root.output.solution.update(self.load_sensitivity_volume(sim)) - #sim.root.output.solution.update(self.load_sensitivity_derivative_inlet(sim)) - #sim.root.output.solution.update(self.load_sensitivity_derivative_outlet(sim)) - #sim.root.output.solution.update(self.load_sensitivity_derivative_bulk(sim)) - #sim.root.output.solution.update(self.load_sensitivity_derivative_particle(sim)) - #sim.root.output.solution.update(self.load_sensitivity_derivative_solid(sim)) - #sim.root.output.solution.update(self.load_sensitivity_derivative_flux(sim)) - #sim.root.output.solution.update(self.load_sensitivity_derivative_volume(sim)) + @staticmethod + def _get_index_string(prefix, index): + return f'{prefix}_{index:03d}' From 07dc0e97a09eeddc390781130c3418bec9c73520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Tue, 20 Jun 2023 11:33:15 +0200 Subject: [PATCH 09/43] Update tests --- examples/dll_test.py | 236 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 examples/dll_test.py diff --git a/examples/dll_test.py b/examples/dll_test.py new file mode 100644 index 0000000..77dfb62 --- /dev/null +++ b/examples/dll_test.py @@ -0,0 +1,236 @@ +""" +On Windows: $CADET_ROOT/bin/cadet.dll +On Linux: + release: $CADET_ROOT/lib/libcadet.so + debug: $CADET_ROOT/lib/libcadet_d.so +""" +import copy +import os +from pathlib import Path +import platform +import subprocess +import sys +sys.path.insert(0, '../') +import tempfile + +from cadet import Cadet + +# cadet_root = Path(r"C:\Users\kosh\cadet_build\CADET\VCPKG_42_dll\bin\cadet.dll") +cadet_root = Path('/home/jo/code/CADET/install/capi/') + +cadet_dll_path = cadet_root / 'lib' / 'libcadet_d.so' +cadet_cli_path = cadet_root / 'bin' / 'cadet-cli' + +# %% + +def setup_template_model( + cadet_root, + model='GENERAL_RATE_MODEL', + n_partypes=1, + include_sensitivity=False, + file_name='LWE.h5', + ): + + executable = 'createLWE' + if platform.system() == 'Windows': + executable += '.exe' + + create_lwe_path = Path(cadet_root) / 'bin' / executable + + args =[ + create_lwe_path.as_posix(), + f'--out {file_name}', + f'--unit {model}', + ] + + if include_sensitivity: + args.append('--sens COL_POROSITY/-1/-1/-1/-1/-1/-1/0') + + ret = subprocess.run( + args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd='./' + ) + + if ret.returncode != 0: + if ret.stdout: + print('Output', ret.stdout.decode('utf-8')) + if ret.stderr: + print('Errors', ret.stderr.decode('utf-8')) + raise Exception( + "Failure: Creation of test simulation ran into problems" + ) + + template = Cadet() + template.filename = file_name + template.load() + + return template + + +def setup_solution_recorder( + template, + split_components=0, + split_ports=0, + single_as_multi_port=0, + ): + + template = copy.deepcopy(template) + + template.root.input['return'].write_solution_last = 1 + template.root.input['return'].write_sens_last = 1 + + template.root.input['return'].split_components_data = split_components + template.root.input['return'].split_ports_data = split_ports + template.root.input['return'].single_as_multi_port = single_as_multi_port + + template.root.input['return'].unit_000.write_coordinates = 1 + + template.root.input['return'].unit_000.write_solution_inlet = 1 + template.root.input['return'].unit_000.write_solution_outlet = 1 + template.root.input['return'].unit_000.write_solution_bulk = 1 + template.root.input['return'].unit_000.write_solution_particle = 1 + template.root.input['return'].unit_000.write_solution_solid = 1 + template.root.input['return'].unit_000.write_solution_flux = 1 + template.root.input['return'].unit_000.write_solution_volume = 1 + + template.root.input['return'].unit_000.write_soldot_inlet = 1 + template.root.input['return'].unit_000.write_soldot_outlet = 1 + template.root.input['return'].unit_000.write_soldot_bulk = 1 + template.root.input['return'].unit_000.write_soldot_particle = 1 + template.root.input['return'].unit_000.write_soldot_solid = 1 + template.root.input['return'].unit_000.write_soldot_flux = 1 + template.root.input['return'].unit_000.write_soldot_volume = 1 + + template.root.input['return'].unit_000.write_sens_inlet = 1 + template.root.input['return'].unit_000.write_sens_outlet = 1 + template.root.input['return'].unit_000.write_sens_bulk = 1 + template.root.input['return'].unit_000.write_sens_particle = 1 + template.root.input['return'].unit_000.write_sens_solid = 1 + template.root.input['return'].unit_000.write_sens_flux = 1 + template.root.input['return'].unit_000.write_sens_volume = 1 + + template.root.input['return'].unit_000.write_sensdot_inlet = 1 + template.root.input['return'].unit_000.write_sensdot_outlet = 1 + template.root.input['return'].unit_000.write_sensdot_bulk = 1 + template.root.input['return'].unit_000.write_sensdot_particle = 1 + template.root.input['return'].unit_000.write_sensdot_solid = 1 + template.root.input['return'].unit_000.write_sensdot_flux = 1 + template.root.input['return'].unit_000.write_sensdot_volume = 1 + + template.root.input['return'].unit_000.write_solution_last_unit = 1 + template.root.input['return'].unit_000.write_soldot_last_unit = 1 + + # LAST_STATE_Y + # LAST_STATE_YDOT + # LAST_STATE_SENSYDOT_??? + # LAST_STATE_SENSY_??? + + for unit in range(template.root.input.model['nunits']): + template.root.input['return']['unit_{0:03d}'.format(unit)] = template.root.input['return'].unit_000 + + if template.filename is not None: + template.save() + + return template + + +# %% +dll = True + +if dll: + cadet_path = cadet_dll_path +else: + cadet_path = cadet_cli_path + +Cadet.cadet_path = cadet_path.as_posix() + +# %% Test standard GRM +model = 'GENERAL_RATE_MODEL' + +template = setup_template_model( + cadet_root, + model, +) + +# Don't split anything +sim = setup_solution_recorder( + template, + split_components=0, + split_ports=0, + single_as_multi_port=0, +) + +sim.run_load() + +# Split everything +sim = setup_solution_recorder( + template, + split_components=1, + split_ports=1, + single_as_multi_port=1, +) + +sim.run_load() + +# %% Test sensitivities +model = 'GENERAL_RATE_MODEL' + +template = setup_template_model( + cadet_root, + model, + include_sensitivity=True, +) + +# Don't split anything +sim = setup_solution_recorder( + template, + split_components=0, + split_ports=0, + single_as_multi_port=0, +) + +sim.run_load() + +# Split everything +sim = setup_solution_recorder( + template, + split_components=1, + split_ports=1, + single_as_multi_port=1, +) + +sim.run_load() + + +# %% Test ports +model = 'GENERAL_RATE_MODEL_2D' + +template = setup_template_model(cadet_root, model) +setup_solution_recorder( + template, + split_components=0, + split_ports=0, + single_as_multi_port=0, +) + +sim.run_load() + +# Only split ports for 2D Model +setup_solution_recorder( + template, + split_components=0, + split_ports=1, + single_as_multi_port=0, +) + +# Split everything +setup_solution_recorder( + template, + split_components=1, + split_ports=1, + single_as_multi_port=1, +) + +sim.run_load() From 40bbe700b149ad02b9a184f2ffdcf7fac1d93a8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Tue, 20 Jun 2023 11:33:23 +0200 Subject: [PATCH 10/43] Formatting --- cadet/cadet.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cadet/cadet.py b/cadet/cadet.py index a4257fe..dedebde 100644 --- a/cadet/cadet.py +++ b/cadet/cadet.py @@ -363,7 +363,12 @@ def run(self, timeout=None, check=None): return data def run_load(self, timeout=None, check=None, clear=True): - data = self.cadet_runner.run(simulation=self.root.input, filename=self.filename, timeout=timeout, check=check) + data = self.cadet_runner.run( + simulation=self.root.input, + filename=self.filename, + timeout=timeout, + check=check + ) # self.return_information = data self.load_results() if clear: From 59053cd3529b2fcc206a27985f6017573368da75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 30 Jun 2023 12:09:36 +0200 Subject: [PATCH 11/43] Improve reading string arrays --- cadet/cadet_dll_utils.py | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/cadet/cadet_dll_utils.py b/cadet/cadet_dll_utils.py index 3deb887..b2c8a99 100644 --- a/cadet/cadet_dll_utils.py +++ b/cadet/cadet_dll_utils.py @@ -189,25 +189,20 @@ def param_provider_get_bool_array_item(reader, name, index, val): def param_provider_get_string_array_item(reader, name, index, val): - n = name.decode('utf-8') - c = reader.current() + name = name.decode('utf-8') + current_reader = reader.current() - if n in c: - o = c[n] - if index == 0: - if hasattr(o, 'encode'): - bytes_val = o.encode('utf-8') - elif hasattr(o, 'decode'): - bytes_val = o + if name in current_reader: + str_value = current_reader[name] + if isinstance(str_value, bytes): + bytes_val = str_value else: - if hasattr(o[index], 'encode'): - bytes_val = o[index].encode('utf-8') - elif hasattr(o[index], 'decode'): - bytes_val = o[index] + bytes_val = str_value[index] reader.buffer = bytes_val val[0] = ctypes.cast(reader.buffer, ctypes.c_char_p) - log_print('GET array [string] ({}) {}: {}'.format(index, n, reader.buffer.decode('utf-8'))) + log_print(f"GET array [string] ({index}) {name}: {reader.buffer.decode('utf-8')}") + return 0 return -1 @@ -264,7 +259,7 @@ def param_provider_num_elements(reader, name): def param_provider_push_scope(reader, name): n = name.decode('utf-8') - + if reader.push_scope(n): return 0 else: @@ -273,4 +268,4 @@ def param_provider_push_scope(reader, name): def param_provider_pop_scope(reader): reader.pop_scope() - return 0 \ No newline at end of file + return 0 From 9fa63d0a52dde4a1cb8ce16e4fe0ec61ff17bb4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 30 Jun 2023 12:09:49 +0200 Subject: [PATCH 12/43] Update reading results --- cadet/cadet_dll.py | 100 ++++++++++++++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 34 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index c759086..d64d95f 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -183,21 +183,20 @@ def load_data( result = get_solution(*call_args) - return result, call_outputs - - def process_data( - self, - result, - call_outputs, - own_data: bool = True): - if result == _CDT_DATA_NOT_STORED: # Call successful, but data is not available - return None, None, None + return None elif result != _CDT_OK: # Something else failed raise Exception("Error reading data.") + return call_outputs + + def process_data( + self, + call_outputs, + own_data: bool = True): + shape = [] dims = [] @@ -220,8 +219,12 @@ def process_data( return time, data, dims def load_and_process(self, *args, own_data=True, **kwargs): - result, call_outputs = self.load_data(*args, **kwargs) - time, data, dims = self.process_data(result, call_outputs, own_data) + call_outputs = self.load_data(*args, **kwargs) + + if call_outputs is None: + return None, None, None + + time, data, dims = self.process_data(call_outputs, own_data) return time, data, dims @@ -288,7 +291,7 @@ def load_data_old( return time, data, dims def npartypes(self, unitOpId: int, own_data=True): - result, call_outputs = self.load_data( + call_outputs = self.load_data( 'getNParTypes', unitOpId, ) @@ -650,14 +653,14 @@ def load_results(self, sim): self.load_state(sim) def load_coordinates(self, sim): - solution = addict.Dict() + coordinates = addict.Dict() for unit in range(sim.root.input.model.nunits): unit_index = self._get_index_string('unit', unit) if 'write_coordinates' in sim.root.input['return'][unit_index].keys(): # TODO: Missing CAPI call pass - return solution + sim.root.output.coordinates = coordinates def load_solution(self, sim): solution = addict.Dict() @@ -681,7 +684,11 @@ def load_solution(self, sim): unit_solution.update(self._load_solution_trivial(sim, unit, 'soldot_flux')) unit_solution.update(self._load_solution_trivial(sim, unit, 'soldot_volume')) - solution[unit_index].update(unit_solution) + if len(unit_solution) > 1: + solution[unit_index].update(unit_solution) + + if len(unit_solution) > 1: + sim.root.output.solution = solution return solution @@ -739,6 +746,8 @@ def load_sensitivity(self, sim): sensitivity[sens_index].update(unit_sensitivity) + sim.root.output.sensitivity = sensitivity + def _checks_if_write_is_true(func): def wrapper(self, sim, unitOpId, solution_str, *args, **kwargs): unit_index = self._get_index_string('unit', unitOpId) @@ -746,32 +755,46 @@ def wrapper(self, sim, unitOpId, solution_str, *args, **kwargs): if f'write_{solution_str}' not in solution_recorder: return {} - return func(self, sim, unitOpId, solution_str, *args, **kwargs) + solution = func(self, sim, unitOpId, solution_str, *args, **kwargs) + + if solution is None: + return {} + + return solution + + return wrapper + + def _loads_data(func): + def wrapper(self, sim, unitOpId, solution_str, sensIdx=None, *args, **kwargs): + solution_fun = getattr(self.res, solution_str) + if sensIdx is None: + data = solution_fun(unitOpId) + else: + data = solution_fun(unitOpId, sensIdx) + + if data is None: + return + + solution = func(self, sim, data, unitOpId, solution_str, *args, **kwargs) + + return solution return wrapper @_checks_if_write_is_true - def _load_solution_trivial(self, sim, unitOpId, solution_str, sensIdx=None): + @_loads_data + def _load_solution_trivial(self, sim, data, unitOpId, solution_str, sensIdx=None): solution = addict.Dict() - solution_fun = getattr(self.res, solution_str) - if sensIdx is None: - t, out, dims = solution_fun(unitOpId) - else: - t, out, dims = solution_fun(unitOpId, sensIdx) - + t, out, dims = data solution[solution_str] = out return solution @_checks_if_write_is_true - def _load_solution_io(self, sim, unitOpId, solution_str, sensIdx=None): + @_loads_data + def _load_solution_io(self, sim, data, unitOpId, solution_str, sensIdx=None): solution = addict.Dict() - solution_fun = getattr(self.res, solution_str) - - if sensIdx is None: - t, out, dims = solution_fun(unitOpId) - else: - t, out, dims = solution_fun(unitOpId, sensIdx) + t, out, dims = data split_components_data = sim.root.input['return'].get('split_components_data', 1) split_ports_data = sim.root.input['return'].get('split_ports_data', 1) @@ -824,15 +847,18 @@ def _load_solution_particle(self, sim, unitOpId, solution_str, sensIdx=None): solution = addict.Dict() solution_fun = getattr(self.res, solution_str) - unit_index = self._get_index_string('unit', unitOpId) - npartype = self.res.npartypes(unitOpId) for partype in range(npartype): if sensIdx is None: - t, out, dims = solution_fun(unitOpId, partype) + data = solution_fun(unitOpId, partype) else: - t, out, dims = solution_fun(unitOpId, sensIdx, partype) + data = solution_fun(unitOpId, sensIdx, partype) + + if data is None: + continue + + t, out, dims = data if npartype == 1: solution[solution_str] = out @@ -840,8 +866,14 @@ def _load_solution_particle(self, sim, unitOpId, solution_str, sensIdx=None): par_index = self._get_index_string('partype', partype) solution[f'{solution_str}_{par_index}'] = out + if len(solution) == 0: + return + return solution + def _load_particle_type(self, sim, data, unitOpId, solution_str, sensIdx=None): + pass + def load_state(self, sim): # TODO pass From 61d183cf57acaa18565876a3ffde28c30117ddcb Mon Sep 17 00:00:00 2001 From: Samuel Leweke Date: Fri, 27 Oct 2023 14:04:07 +0200 Subject: [PATCH 13/43] Support last states, coordinates, solution times in C API interface --- cadet/cadet_dll.py | 283 +++++++++++++++++++++++++++++++-------------- 1 file changed, 195 insertions(+), 88 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index d64d95f..cb2b2c8 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -18,6 +18,7 @@ def log_handler(file, func, line, level, level_name, message): # Values of cdtResult _CDT_OK = 0 +_CDT_ERROR_INVALID_INPUTS = -2 _CDT_DATA_NOT_STORED = -3 @@ -46,7 +47,8 @@ class CADETAPIV010000_DATA(): signatures['createDriver'] = ('drv',) signatures['deleteDriver'] = (None, 'drv') signatures['runSimulation'] = ('return', 'drv', 'parameterProvider') - signatures['getNParTypes'] = ('return', 'drv', 'unitOpId', 'nParTypes') + signatures['getNumParTypes'] = ('return', 'drv', 'unitOpId', 'nParTypes') + signatures['getNumSensitivities'] = ('return', 'drv', 'nSens') signatures['getSolutionInlet'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nPort', 'nComp') signatures['getSolutionOutlet'] = ('return', 'drv', 'unitOpId', 'time', 'data', 'nTime', 'nPort', 'nComp') @@ -89,6 +91,11 @@ class CADETAPIV010000_DATA(): signatures['getLastSensitivityUnitState'] = ('return', 'drv', 'sensIdx', 'unitOpId', 'state', 'nStates') signatures['getLastSensitivityUnitStateTimeDerivative'] = ('return', 'drv', 'sensIdx', 'unitOpId', 'state', 'nStates') + signatures['getPrimaryCoordinates'] = ('return', 'drv', 'unitOpId', 'coords', 'nCoords') + signatures['getSecondaryCoordinates'] = ('return', 'drv', 'unitOpId', 'coords', 'nCoords') + signatures['getParticleCoordinates'] = ('return', 'drv', 'unitOpId', 'parType', 'coords', 'nCoords') + + signatures['getSolutionTimes'] = ('return', 'drv', 'time', 'nTime') lookup_prototype = { 'return': c_cadet_result, @@ -98,18 +105,21 @@ class CADETAPIV010000_DATA(): 'parType': ctypes.c_int, 'time': array_double, 'data': array_double, + 'coords': array_double, 'nTime': point_int, + 'nCoords': point_int, 'nPort': point_int, 'nAxialCells': point_int, 'nRadialCells': point_int, 'nParTypes': point_int, + 'nSens': point_int, 'nParShells': point_int, 'nComp': point_int, 'nBound': point_int, 'state': array_double, 'nStates': point_int, None: None, - 'parameterProvider': ctypes.POINTER(cadet_dll_parameterprovider.PARAMETERPROVIDER) + 'parameterProvider': ctypes.POINTER(cadet_dll_parameterprovider.PARAMETERPROVIDER), } lookup_output_argument_type = { @@ -120,11 +130,14 @@ class CADETAPIV010000_DATA(): 'nAxialCells': ctypes.c_int, 'nRadialCells': ctypes.c_int, 'nParTypes': ctypes.c_int, + 'nSens': ctypes.c_int, 'nParShells': ctypes.c_int, 'nComp': ctypes.c_int, 'nBound': ctypes.c_int, 'state': ctypes.POINTER(ctypes.c_double), 'nStates': ctypes.c_int, + 'coords': ctypes.POINTER(ctypes.c_double), + 'nCoords': ctypes.c_int, } @@ -150,10 +163,10 @@ def __init__(self, api: CADETAPIV010000, driver: CadetDriver): self._api = api self._driver = driver - def load_data( + def _load_data( self, get_solution_str: str, - unitOpId: int | None, + unitOpId: int, sensIdx: int = None, parType: int = None): @@ -170,7 +183,7 @@ def load_data( continue elif key == 'drv': call_args.append(self._driver) - elif key == 'unitOpId' and unitOpId is not None: + elif key == 'unitOpId': call_args.append(unitOpId) elif key == 'sensIdx': call_args.append(sensIdx) @@ -186,13 +199,27 @@ def load_data( if result == _CDT_DATA_NOT_STORED: # Call successful, but data is not available return None + elif result == _CDT_ERROR_INVALID_INPUTS: + raise ValueError("Error reading data: Invalid call arguments") elif result != _CDT_OK: # Something else failed raise Exception("Error reading data.") return call_outputs - def process_data( + def _process_array( + self, + call_outputs, + data_key: str, + len_key: str, + own_data: bool = True): + + data = numpy.ctypeslib.as_array(call_outputs[data_key], shape=(call_outputs[len_key].value, )) + if own_data: + return data.copy() + return data + + def _process_data( self, call_outputs, own_data: bool = True): @@ -210,24 +237,35 @@ def process_data( shape.append(call_outputs[dim].value) dims.append(dim) - data = numpy.ctypeslib.as_array(call_outputs['data'], shape=shape) - time = numpy.ctypeslib.as_array(call_outputs['time'], shape=(call_outputs['nTime'].value, )) + if 'data' in call_outputs: + data = numpy.ctypeslib.as_array(call_outputs['data'], shape=shape) + if own_data: + data = data.copy() - if own_data: - return time.copy(), data.copy(), dims - else: - return time, data, dims + if 'time' in call_outputs: + time = numpy.ctypeslib.as_array(call_outputs['time'], shape=(call_outputs['nTime'].value, )) + if own_data: + time = time.copy() + + return time, data, dims - def load_and_process(self, *args, own_data=True, **kwargs): - call_outputs = self.load_data(*args, **kwargs) + def _load_and_process(self, *args, own_data=True, **kwargs): + call_outputs = self._load_data(*args, **kwargs) if call_outputs is None: return None, None, None - time, data, dims = self.process_data(call_outputs, own_data) - + time, data, dims = self._process_data(call_outputs, own_data) return time, data, dims + def _load_and_process_array(self, data_key: str, len_key: str, *args, own_data=True, **kwargs): + call_outputs = self._load_data(*args, **kwargs) + + if call_outputs is None: + return None + + return self._process_array(call_outputs, data_key, len_key, own_data) + def load_data_old( self, get_solution_str: str, @@ -290,45 +328,53 @@ def load_data_old( else: return time, data, dims - def npartypes(self, unitOpId: int, own_data=True): - call_outputs = self.load_data( - 'getNParTypes', + def npartypes(self, unitOpId: int): + call_outputs = self._load_data( + 'getNumParTypes', + unitOpId, + ) + + return int(call_outputs['nParTypes'].value) + + def nsensitivities(self, unitOpId: int): + call_outputs = self._load_data( + 'getNumSensitivities', unitOpId, ) - return int(numpy.ctypeslib.as_array(call_outputs['nParTypes'])) + return int(call_outputs['nSens'].value) def solution_inlet(self, unitOpId: int, own_data=True): - return self.load_and_process( + return self._load_and_process( 'getSolutionInlet', unitOpId, own_data=own_data ) def solution_outlet(self, unitOpId: int, own_data=True): - return self.load_and_process( + return self._load_and_process( 'getSolutionOutlet', unitOpId, own_data=own_data ) def solution_bulk(self, unitOpId: int, own_data=True): - return self.load_and_process( + return self._load_and_process( 'getSolutionBulk', unitOpId, own_data=own_data ) - def solution_particle(self, unitOpId: int, parType, own_data=True): - return self.load_and_process( + def solution_particle(self, unitOpId: int, parType: int, own_data=True): + return self._load_and_process( 'getSolutionParticle', unitOpId, parType=parType, own_data=own_data ) - def solution_solid(self, unitOpId: int, parType, own_data=True): - return self.load_and_process( + def solution_solid(self, unitOpId: int, parType: int, own_data=True): + return self._load_and_process( 'getSolutionSolid', unitOpId, parType=parType, @@ -336,42 +382,42 @@ def solution_solid(self, unitOpId: int, parType, own_data=True): ) def solution_flux(self, unitOpId: int, own_data=True): - return self.load_and_process( + return self._load_and_process( 'getSolutionFlux', unitOpId, own_data=own_data ) def solution_volume(self, unitOpId: int, own_data=True): - return self.load_and_process( + return self._load_and_process( 'getSolutionVolume', unitOpId, own_data=own_data ) def soldot_inlet(self, unitOpId: int, own_data=True): - return self.load_and_process( + return self._load_and_process( 'getSolutionDerivativeInlet', unitOpId, own_data=own_data ) def soldot_outlet(self, unitOpId: int, own_data=True): - return self.load_and_process( + return self._load_and_process( 'getSolutionDerivativeOutlet', unitOpId, own_data=own_data ) def soldot_bulk(self, unitOpId: int, own_data=True): - return self.load_and_process( + return self._load_and_process( 'getSolutionDerivativeBulk', unitOpId, own_data=own_data ) def soldot_particle(self, unitOpId: int, parType: int, own_data=True): - return self.load_and_process( + return self._load_and_process( 'getSolutionDerivativeParticle', unitOpId, parType=parType, @@ -379,7 +425,7 @@ def soldot_particle(self, unitOpId: int, parType: int, own_data=True): ) def soldot_solid(self, unitOpId: int, parType: int, own_data=True): - return self.load_and_process( + return self._load_and_process( 'getSolutionDerivativeSolid', unitOpId, parType=parType, @@ -387,125 +433,125 @@ def soldot_solid(self, unitOpId: int, parType: int, own_data=True): ) def soldot_flux(self, unitOpId: int, own_data=True): - return self.load_and_process( + return self._load_and_process( 'getSolutionDerivativeFlux', unitOpId, own_data=own_data ) def soldot_volume(self, unitOpId: int, own_data=True): - return self.load_and_process( + return self._load_and_process( 'getSolutionDerivativeVolume', unitOpId, own_data=own_data ) - def sens_inlet(self, unitOpId: int, sensIdx, own_data=True): - return self.load_and_process( + def sens_inlet(self, unitOpId: int, sensIdx: int, own_data=True): + return self._load_and_process( 'getSensitivityInlet', unitOpId, sensIdx=sensIdx, own_data=own_data ) - def sens_outlet(self, unitOpId: int, sensIdx, own_data=True): - return self.load_and_process( + def sens_outlet(self, unitOpId: int, sensIdx: int, own_data=True): + return self._load_and_process( 'getSensitivityOutlet', unitOpId, sensIdx=sensIdx, own_data=own_data ) - def sens_bulk(self, unitOpId: int, sensIdx, own_data=True): - return self.load_and_process( + def sens_bulk(self, unitOpId: int, sensIdx: int, own_data=True): + return self._load_and_process( 'getSensitivityBulk', unitOpId, sensIdx=sensIdx, own_data=own_data ) - def sens_particle(self, unitOpId: int, sensIdx, parType: int, own_data=True): - return self.load_and_process( + def sens_particle(self, unitOpId: int, sensIdx: int, parType: int, own_data=True): + return self._load_and_process( 'getSensitivityParticle', unitOpId, sensIdx=sensIdx, parType=parType, own_data=own_data ) - def sens_solid(self, unitOpId: int, sensIdx, parType, own_data=True): - return self.load_and_process( + def sens_solid(self, unitOpId: int, sensIdx: int, parType: int, own_data=True): + return self._load_and_process( 'getSensitivitySolid', unitOpId, sensIdx=sensIdx, parType=parType, own_data=own_data ) - def sens_flux(self, unitOpId: int, sensIdx, own_data=True): - return self.load_and_process( + def sens_flux(self, unitOpId: int, sensIdx: int, own_data=True): + return self._load_and_process( 'getSensitivityFlux', unitOpId, sensIdx=sensIdx, own_data=own_data ) - def sens_volume(self, unitOpId: int, sensIdx, own_data=True): - return self.load_and_process( + def sens_volume(self, unitOpId: int, sensIdx: int, own_data=True): + return self._load_and_process( 'getSensitivityVolume', unitOpId, sensIdx=sensIdx, own_data=own_data ) - def sensdot_inlet(self, unitOpId: int, sensIdx, own_data=True): - return self.load_and_process( + def sensdot_inlet(self, unitOpId: int, sensIdx: int, own_data=True): + return self._load_and_process( 'getSensitivityDerivativeInlet', unitOpId, sensIdx=sensIdx, own_data=own_data ) - def sensdot_outlet(self, unitOpId: int, sensIdx, own_data=True): - return self.load_and_process( + def sensdot_outlet(self, unitOpId: int, sensIdx: int, own_data=True): + return self._load_and_process( 'getSensitivityDerivativeOutlet', unitOpId, sensIdx=sensIdx, own_data=own_data ) - def sensdot_bulk(self, unitOpId: int, sensIdx, own_data=True): - return self.load_and_process( + def sensdot_bulk(self, unitOpId: int, sensIdx: int, own_data=True): + return self._load_and_process( 'getSensitivityDerivativeBulk', unitOpId, sensIdx=sensIdx, own_data=own_data ) - def sensdot_particle(self, unitOpId: int, sensIdx, parType, own_data=True): - return self.load_and_process( + def sensdot_particle(self, unitOpId: int, sensIdx: int, parType: int, own_data=True): + return self._load_and_process( 'getSensitivityDerivativeParticle', unitOpId, sensIdx=sensIdx, parType=parType, own_data=own_data ) - def sensdot_solid(self, unitOpId: int, sensIdx, parType, own_data=True): - return self.load_and_process( + def sensdot_solid(self, unitOpId: int, sensIdx: int, parType: int, own_data=True): + return self._load_and_process( 'getSensitivityDerivativeSolid', unitOpId, sensIdx=sensIdx, parType=parType, own_data=own_data ) - def sensdot_flux(self, unitOpId: int, sensIdx, own_data=True): - return self.load_and_process( + def sensdot_flux(self, unitOpId: int, sensIdx: int, own_data=True): + return self._load_and_process( 'getSensitivityDerivativeFlux', unitOpId, sensIdx=sensIdx, own_data=own_data ) - def sensdot_volume(self, unitOpId: int, sensIdx, own_data=True): - return self.load_and_process( + def sensdot_volume(self, unitOpId: int, sensIdx: int, own_data=True): + return self._load_and_process( 'getSensitivityDerivativeVolume', unitOpId, sensIdx=sensIdx, @@ -513,61 +559,98 @@ def sensdot_volume(self, unitOpId: int, sensIdx, own_data=True): ) def last_state_y(self, own_data=True): - return self.load_and_process( + return self._load_and_process( 'getLastState', own_data=own_data ) - def last_state_ydot(self, sensIdx, own_data=True): - return self.load_and_process( + def last_state_ydot(self, own_data=True): + return self._load_and_process( 'getLastStateDerivative', own_data=own_data ) def last_state_y_unit(self, unitOpId: int, own_data=True): - return self.load_and_process( + return self._load_and_process( 'getLastUnitState', unitOpId, own_data=own_data ) def last_state_ydot_unit(self, unitOpId: int, own_data=True): - return self.load_and_process( + return self._load_and_process( 'getLastUnitStateTimeDerivative', unitOpId, own_data=own_data ) - def last_state_sens(self, sensIdx, own_data=True): - return self.load_and_process( + def last_state_sens(self, sensIdx: int, own_data=True): + return self._load_and_process( 'getLastSensitivityState', sensIdx=sensIdx, own_data=own_data ) - def last_state_sensdot(self, sensIdx, own_data=True): - return self.load_and_process( + def last_state_sensdot(self, sensIdx: int, own_data=True): + return self._load_and_process( 'getLastSensitivityStateTimeDerivative', sensIdx=sensIdx, own_data=own_data ) - def last_state_sens_unit(self, unitOpId: int, sensIdx, own_data=True): - return self.load_and_process( + def last_state_sens_unit(self, unitOpId: int, sensIdx: int, own_data=True): + return self._load_and_process( 'getLastSensitivityUnitState', unitOpId, sensIdx=sensIdx, own_data=own_data ) - def last_state_sensdot_unit(self, unitOpId: int, sensIdx, own_data=True): - return self.load_and_process( + def last_state_sensdot_unit(self, unitOpId: int, sensIdx: int, own_data=True): + return self._load_and_process( 'getLastSensitivityUnitStateTimeDerivative', unitOpId, sensIdx=sensIdx, own_data=own_data ) + def primary_coordinates(self, unitOpId: int, own_data=True): + return self._load_and_process_array( + 'coords', + 'nCoords', + 'getPrimaryCoordinates', + unitOpId, + own_data=own_data + ) + + def secondary_coordinates(self, unitOpId: int, own_data=True): + return self._load_and_process_array( + 'coords', + 'nCoords', + 'getSecondaryCoordinates', + unitOpId, + own_data=own_data + ) + + def particle_coordinates(self, unitOpId: int, parType: int, own_data=True): + return self._load_and_process_array( + 'coords', + 'nCoords', + 'getParticleCoordinates', + unitOpId, + parType=parType, + own_data=own_data + ) + + def solution_times(self, own_data=True): + return self._load_and_process_array( + 'time', + 'nTime', + 'getSolutionTimes', + None, + own_data=own_data + ) + class CadetDLL: @@ -623,6 +706,7 @@ def __init__(self, dll_path): cdtGetAPIv010000(ctypes.byref(self._api)) self._driver = self._api.createDriver() + self.res = None def clear(self): if hasattr(self, "res"): @@ -644,21 +728,38 @@ def run(self, filename=None, simulation=None, timeout=None, check=None): return self.res def load_results(self, sim): - # TODO: solution time - # sim.root.output.solution.solution_time = ??? + if self.res is None: + return + + self.load_solution_times(sim) self.load_coordinates(sim) # TODO: Crashes when simulation includes sensitivities self.load_solution(sim) self.load_sensitivity(sim) self.load_state(sim) + def load_solution_times(self, sim): + if 'write_solution_times' in sim.root.input['return']: + sim.root.output.solution.solution_times = self.res.solution_times() + def load_coordinates(self, sim): coordinates = addict.Dict() for unit in range(sim.root.input.model.nunits): unit_index = self._get_index_string('unit', unit) if 'write_coordinates' in sim.root.input['return'][unit_index].keys(): - # TODO: Missing CAPI call - pass + pc = self.res.primary_coordinates(unit) + if pc: + coordinates[unit_index]['axial_coordinates'] = pc + + sc = self.res.secondary_coordinates(unit) + if sc: + coordinates[unit_index]['radial_coordinates'] = sc + + num_par_types = self.res.npartypes(unit) + for pt in range(num_par_types): + par_coords = self.res.particle_coordinates(unit, pt) + if par_coords: + coordinates[unit_index][self._get_index_string('particle_coordinates', pt)] = par_coords sim.root.output.coordinates = coordinates @@ -751,8 +852,8 @@ def load_sensitivity(self, sim): def _checks_if_write_is_true(func): def wrapper(self, sim, unitOpId, solution_str, *args, **kwargs): unit_index = self._get_index_string('unit', unitOpId) - solution_recorder = sim.root.input['return'][unit_index].keys() - if f'write_{solution_str}' not in solution_recorder: + unit_return_config = sim.root.input['return'][unit_index].keys() + if f'write_{solution_str}' not in unit_return_config: return {} solution = func(self, sim, unitOpId, solution_str, *args, **kwargs) @@ -785,7 +886,7 @@ def wrapper(self, sim, unitOpId, solution_str, sensIdx=None, *args, **kwargs): @_loads_data def _load_solution_trivial(self, sim, data, unitOpId, solution_str, sensIdx=None): solution = addict.Dict() - t, out, dims = data + _, out, _ = data solution[solution_str] = out return solution @@ -794,7 +895,7 @@ def _load_solution_trivial(self, sim, data, unitOpId, solution_str, sensIdx=None @_loads_data def _load_solution_io(self, sim, data, unitOpId, solution_str, sensIdx=None): solution = addict.Dict() - t, out, dims = data + _, out, dims = data split_components_data = sim.root.input['return'].get('split_components_data', 1) split_ports_data = sim.root.input['return'].get('split_ports_data', 1) @@ -858,7 +959,7 @@ def _load_solution_particle(self, sim, unitOpId, solution_str, sensIdx=None): if data is None: continue - t, out, dims = data + _, out, dims = data if npartype == 1: solution[solution_str] = out @@ -875,8 +976,14 @@ def _load_particle_type(self, sim, data, unitOpId, solution_str, sensIdx=None): pass def load_state(self, sim): - # TODO - pass + if 'write_solution_last' in sim.root.input['return']: + sim.root.output['last_state_y'] = self.res.lastState() + sim.root.output['last_state_ydot'] = self.res.lastStateDerivative() + + if 'write_sens_last' in sim.root.input['return']: + for idx in range(self.res.num_sensitivities()): + sim.root.output[self._get_index_string('last_state_sensy', idx)] = self.res.lastSensitivityState(idx) + sim.root.output[self._get_index_string('last_state_sensydot', idx)] = self.res.lastSensitivityStateDerivative(idx) @staticmethod def _get_index_string(prefix, index): From 2ffc6b5e67cf88fea3744a439bded03b4115409b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 09:15:31 +0100 Subject: [PATCH 14/43] Remove old load implementation --- cadet/cadet_dll.py | 62 ---------------------------------------------- 1 file changed, 62 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index cb2b2c8..a99276e 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -266,68 +266,6 @@ def _load_and_process_array(self, data_key: str, len_key: str, *args, own_data=T return self._process_array(call_outputs, data_key, len_key, own_data) - def load_data_old( - self, - get_solution_str: str, - unitOpId: int | None, - sensIdx: int = None, - parType: int = None, - own_data: bool = True): - - get_solution = getattr(self._api, get_solution_str) - - # Collect actual values - call_args = [] - call_outputs = {} - - # Construct API call function arguments - for key in CADETAPIV010000_DATA.signatures[get_solution_str]: - if key == 'return': - # Skip, this is the actual return value of the API function - continue - elif key == 'drv': - call_args.append(self._driver) - elif key == 'unitOpId' and unitOpId is not None: - call_args.append(unitOpId) - elif key == 'sensIdx': - call_args.append(sensIdx) - elif key == 'parType': - call_args.append(parType) - else: - _obj = CADETAPIV010000_DATA.lookup_output_argument_type[key]() - call_outputs[key] = _obj - call_args.append(ctypes.byref(_obj)) - - result = get_solution(*call_args) - - if result == _CDT_DATA_NOT_STORED: - # Call successful, but data is not available - return None, None, None - elif result != _CDT_OK: - # Something else failed - raise Exception("Error reading data.") - - shape = [] - dims = [] - - # Ordering of multi-dimensional arrays, all possible dimensions: - # Example: Outlet [nTime, nPort, nComp] - # Bulk [nTime, nRadialCells, nAxialCells, nComp] if 2D model - # Bulk [nTime, nAxialCells, nComp] if 1D model - dimensions = ['nTime', 'nPort', 'nRadialCells', 'nAxialCells', 'nParShells', 'nComp', 'nBound'] - for dim in dimensions: - if dim in call_outputs and call_outputs[dim].value: - shape.append(call_outputs[dim].value) - dims.append(dim) - - data = numpy.ctypeslib.as_array(call_outputs['data'], shape=shape) - time = numpy.ctypeslib.as_array(call_outputs['time'], shape=(call_outputs['nTime'].value, )) - - if own_data: - return time.copy(), data.copy(), dims - else: - return time, data, dims - def npartypes(self, unitOpId: int): call_outputs = self._load_data( 'getNumParTypes', From c0abeedd52b6d8036dfdbbe648d28092ebfdf702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Thu, 28 Mar 2024 10:44:54 +0100 Subject: [PATCH 15/43] Add option to setup template with multiple particle types --- examples/dll_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/dll_test.py b/examples/dll_test.py index 77dfb62..26ba11f 100644 --- a/examples/dll_test.py +++ b/examples/dll_test.py @@ -41,6 +41,7 @@ def setup_template_model( create_lwe_path.as_posix(), f'--out {file_name}', f'--unit {model}', + f'--parTypes {n_partypes}', ] if include_sensitivity: From 04f032fd8f5c3497d0267bf1445148e4112afe1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 08:37:47 +0100 Subject: [PATCH 16/43] Explicitly check if coordinates are not None --- cadet/cadet_dll.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index a99276e..f4d7ab2 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -686,17 +686,17 @@ def load_coordinates(self, sim): unit_index = self._get_index_string('unit', unit) if 'write_coordinates' in sim.root.input['return'][unit_index].keys(): pc = self.res.primary_coordinates(unit) - if pc: + if pc is not None: coordinates[unit_index]['axial_coordinates'] = pc sc = self.res.secondary_coordinates(unit) - if sc: + if sc is not None: coordinates[unit_index]['radial_coordinates'] = sc num_par_types = self.res.npartypes(unit) for pt in range(num_par_types): par_coords = self.res.particle_coordinates(unit, pt) - if par_coords: + if par_coords is not None: coordinates[unit_index][self._get_index_string('particle_coordinates', pt)] = par_coords sim.root.output.coordinates = coordinates From dfdebb948fa46821e5c3730710d7ac3150b5f047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 08:37:25 +0100 Subject: [PATCH 17/43] Return None if length of array is 0 --- cadet/cadet_dll.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index f4d7ab2..04981d4 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -214,7 +214,11 @@ def _process_array( len_key: str, own_data: bool = True): - data = numpy.ctypeslib.as_array(call_outputs[data_key], shape=(call_outputs[len_key].value, )) + array_length = call_outputs[len_key].value + if array_length == 0: + return + + data = numpy.ctypeslib.as_array(call_outputs[data_key], shape=(array_length, )) if own_data: return data.copy() return data From 6a40c178ecc271471d1ab9c6d071622167544dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 08:42:23 +0100 Subject: [PATCH 18/43] Formatting --- cadet/cadet_dll.py | 188 +++++++++++++++++++++++---------------------- 1 file changed, 95 insertions(+), 93 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index 04981d4..5a28d4a 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -7,34 +7,31 @@ def log_handler(file, func, line, level, level_name, message): log_print('{} ({}:{:d}) {}'.format(level_name.decode('utf-8') , func.decode('utf-8') , line, message.decode('utf-8') )) +def _no_log_output(*args): + pass -c_cadet_result = ctypes.c_int +if 0: + log_print = print +else: + log_print = _no_log_output +# Some common types +CadetDriver = ctypes.c_void_p array_double = ctypes.POINTER(ctypes.POINTER(ctypes.c_double)) - - point_int = ctypes.POINTER(ctypes.c_int) -CadetDriver = ctypes.c_void_p # Values of cdtResult +# TODO: Convert to lookup table to improve error messages below. +c_cadet_result = ctypes.c_int _CDT_OK = 0 +_CDT_ERROR = -1 _CDT_ERROR_INVALID_INPUTS = -2 _CDT_DATA_NOT_STORED = -3 -def _no_log_output(*args): - pass - - -if 0: - log_print = print -else: - log_print = _no_log_output - - class CADETAPIV010000_DATA(): """ - Definition of CADET-CAPI v1.0 + Definition of CADET-C-API v1.0 signatures : dict with signatures of exported API functions. (See CADET/include/cadet/cadet.h) lookup_prototype : ctypes for common parameters @@ -47,6 +44,7 @@ class CADETAPIV010000_DATA(): signatures['createDriver'] = ('drv',) signatures['deleteDriver'] = (None, 'drv') signatures['runSimulation'] = ('return', 'drv', 'parameterProvider') + signatures['getNumParTypes'] = ('return', 'drv', 'unitOpId', 'nParTypes') signatures['getNumSensitivities'] = ('return', 'drv', 'nSens') @@ -166,7 +164,7 @@ def __init__(self, api: CADETAPIV010000, driver: CadetDriver): def _load_data( self, get_solution_str: str, - unitOpId: int, + unitOpId: int | None = None, sensIdx: int = None, parType: int = None): @@ -183,7 +181,7 @@ def _load_data( continue elif key == 'drv': call_args.append(self._driver) - elif key == 'unitOpId': + elif key == 'unitOpId' and unitOpId is not None: call_args.append(unitOpId) elif key == 'sensIdx': call_args.append(sensIdx) @@ -198,7 +196,7 @@ def _load_data( if result == _CDT_DATA_NOT_STORED: # Call successful, but data is not available - return None + return elif result == _CDT_ERROR_INVALID_INPUTS: raise ValueError("Error reading data: Invalid call arguments") elif result != _CDT_OK: @@ -273,7 +271,7 @@ def _load_and_process_array(self, data_key: str, len_key: str, *args, own_data=T def npartypes(self, unitOpId: int): call_outputs = self._load_data( 'getNumParTypes', - unitOpId, + unitOpId=unitOpId, ) return int(call_outputs['nParTypes'].value) @@ -281,7 +279,7 @@ def npartypes(self, unitOpId: int): def nsensitivities(self, unitOpId: int): call_outputs = self._load_data( 'getNumSensitivities', - unitOpId, + unitOpId=unitOpId, ) return int(call_outputs['nSens'].value) @@ -289,167 +287,169 @@ def nsensitivities(self, unitOpId: int): def solution_inlet(self, unitOpId: int, own_data=True): return self._load_and_process( 'getSolutionInlet', - unitOpId, - own_data=own_data + unitOpId=unitOpId, + own_data=own_data, ) def solution_outlet(self, unitOpId: int, own_data=True): return self._load_and_process( 'getSolutionOutlet', - unitOpId, - own_data=own_data + unitOpId=unitOpId, + own_data=own_data, ) def solution_bulk(self, unitOpId: int, own_data=True): return self._load_and_process( 'getSolutionBulk', - unitOpId, - own_data=own_data + unitOpId=unitOpId, + own_data=own_data, ) def solution_particle(self, unitOpId: int, parType: int, own_data=True): return self._load_and_process( 'getSolutionParticle', - unitOpId, + unitOpId=unitOpId, parType=parType, - own_data=own_data + own_data=own_data, ) def solution_solid(self, unitOpId: int, parType: int, own_data=True): return self._load_and_process( 'getSolutionSolid', - unitOpId, + unitOpId=unitOpId, parType=parType, - own_data=own_data + own_data=own_data, ) def solution_flux(self, unitOpId: int, own_data=True): return self._load_and_process( 'getSolutionFlux', - unitOpId, - own_data=own_data + unitOpId=unitOpId, + own_data=own_data, ) def solution_volume(self, unitOpId: int, own_data=True): return self._load_and_process( 'getSolutionVolume', - unitOpId, - own_data=own_data + unitOpId=unitOpId, + own_data=own_data, ) def soldot_inlet(self, unitOpId: int, own_data=True): return self._load_and_process( 'getSolutionDerivativeInlet', - unitOpId, - own_data=own_data + unitOpId=unitOpId, + own_data=own_data, ) def soldot_outlet(self, unitOpId: int, own_data=True): return self._load_and_process( 'getSolutionDerivativeOutlet', - unitOpId, - own_data=own_data + unitOpId=unitOpId, + own_data=own_data, ) def soldot_bulk(self, unitOpId: int, own_data=True): return self._load_and_process( 'getSolutionDerivativeBulk', - unitOpId, - own_data=own_data + unitOpId=unitOpId, + own_data=own_data, ) def soldot_particle(self, unitOpId: int, parType: int, own_data=True): return self._load_and_process( 'getSolutionDerivativeParticle', - unitOpId, + unitOpId=unitOpId, parType=parType, - own_data=own_data + own_data=own_data, ) def soldot_solid(self, unitOpId: int, parType: int, own_data=True): return self._load_and_process( 'getSolutionDerivativeSolid', - unitOpId, + unitOpId=unitOpId, parType=parType, - own_data=own_data + own_data=own_data, ) def soldot_flux(self, unitOpId: int, own_data=True): return self._load_and_process( 'getSolutionDerivativeFlux', - unitOpId, - own_data=own_data + unitOpId=unitOpId, + own_data=own_data, ) def soldot_volume(self, unitOpId: int, own_data=True): return self._load_and_process( 'getSolutionDerivativeVolume', - unitOpId, - own_data=own_data + unitOpId=unitOpId, + own_data=own_data, ) def sens_inlet(self, unitOpId: int, sensIdx: int, own_data=True): return self._load_and_process( 'getSensitivityInlet', - unitOpId, + unitOpId=unitOpId, sensIdx=sensIdx, - own_data=own_data + own_data=own_data, ) def sens_outlet(self, unitOpId: int, sensIdx: int, own_data=True): return self._load_and_process( 'getSensitivityOutlet', - unitOpId, + unitOpId=unitOpId, sensIdx=sensIdx, - own_data=own_data + own_data=own_data, ) def sens_bulk(self, unitOpId: int, sensIdx: int, own_data=True): return self._load_and_process( 'getSensitivityBulk', - unitOpId, + unitOpId=unitOpId, sensIdx=sensIdx, - own_data=own_data + own_data=own_data, ) def sens_particle(self, unitOpId: int, sensIdx: int, parType: int, own_data=True): return self._load_and_process( 'getSensitivityParticle', - unitOpId, + unitOpId=unitOpId, sensIdx=sensIdx, - parType=parType, own_data=own_data + parType=parType, + own_data=own_data, ) def sens_solid(self, unitOpId: int, sensIdx: int, parType: int, own_data=True): return self._load_and_process( 'getSensitivitySolid', - unitOpId, + unitOpId=unitOpId, sensIdx=sensIdx, - parType=parType, own_data=own_data + parType=parType, + own_data=own_data, ) def sens_flux(self, unitOpId: int, sensIdx: int, own_data=True): return self._load_and_process( 'getSensitivityFlux', - unitOpId, + unitOpId=unitOpId, sensIdx=sensIdx, - own_data=own_data + own_data=own_data, ) def sens_volume(self, unitOpId: int, sensIdx: int, own_data=True): return self._load_and_process( 'getSensitivityVolume', - unitOpId, + unitOpId=unitOpId, sensIdx=sensIdx, - own_data=own_data + own_data=own_data, ) def sensdot_inlet(self, unitOpId: int, sensIdx: int, own_data=True): return self._load_and_process( 'getSensitivityDerivativeInlet', - unitOpId, + unitOpId=unitOpId, sensIdx=sensIdx, - own_data=own_data + own_data=own_data, ) def sensdot_outlet(self, unitOpId: int, sensIdx: int, own_data=True): @@ -457,103 +457,105 @@ def sensdot_outlet(self, unitOpId: int, sensIdx: int, own_data=True): 'getSensitivityDerivativeOutlet', unitOpId, sensIdx=sensIdx, - own_data=own_data + own_data=own_data, ) def sensdot_bulk(self, unitOpId: int, sensIdx: int, own_data=True): return self._load_and_process( 'getSensitivityDerivativeBulk', - unitOpId, + unitOpId=unitOpId, sensIdx=sensIdx, - own_data=own_data + own_data=own_data, ) def sensdot_particle(self, unitOpId: int, sensIdx: int, parType: int, own_data=True): return self._load_and_process( 'getSensitivityDerivativeParticle', - unitOpId, + unitOpId=unitOpId, sensIdx=sensIdx, - parType=parType, own_data=own_data + parType=parType, + own_data=own_data, ) def sensdot_solid(self, unitOpId: int, sensIdx: int, parType: int, own_data=True): return self._load_and_process( 'getSensitivityDerivativeSolid', - unitOpId, + unitOpId=unitOpId, sensIdx=sensIdx, - parType=parType, own_data=own_data + parType=parType, + own_data=own_data, ) def sensdot_flux(self, unitOpId: int, sensIdx: int, own_data=True): return self._load_and_process( 'getSensitivityDerivativeFlux', - unitOpId, + unitOpId=unitOpId, sensIdx=sensIdx, - own_data=own_data + own_data=own_data, ) def sensdot_volume(self, unitOpId: int, sensIdx: int, own_data=True): return self._load_and_process( 'getSensitivityDerivativeVolume', - unitOpId, + unitOpId=unitOpId, sensIdx=sensIdx, - own_data=own_data + own_data=own_data, ) def last_state_y(self, own_data=True): return self._load_and_process( 'getLastState', - own_data=own_data + own_data=own_data, ) def last_state_ydot(self, own_data=True): return self._load_and_process( 'getLastStateDerivative', - own_data=own_data + own_data=own_data, ) def last_state_y_unit(self, unitOpId: int, own_data=True): return self._load_and_process( 'getLastUnitState', - unitOpId, - own_data=own_data + unitOpId=unitOpId, + own_data=own_data, ) def last_state_ydot_unit(self, unitOpId: int, own_data=True): return self._load_and_process( 'getLastUnitStateTimeDerivative', - unitOpId, - own_data=own_data + unitOpId=unitOpId, + own_data=own_data, ) def last_state_sens(self, sensIdx: int, own_data=True): return self._load_and_process( 'getLastSensitivityState', sensIdx=sensIdx, - own_data=own_data + own_data=own_data, ) def last_state_sensdot(self, sensIdx: int, own_data=True): return self._load_and_process( 'getLastSensitivityStateTimeDerivative', sensIdx=sensIdx, - own_data=own_data + own_data=own_data, ) def last_state_sens_unit(self, unitOpId: int, sensIdx: int, own_data=True): return self._load_and_process( 'getLastSensitivityUnitState', - unitOpId, + unitOpId=unitOpId, sensIdx=sensIdx, - own_data=own_data + own_data=own_data, ) def last_state_sensdot_unit(self, unitOpId: int, sensIdx: int, own_data=True): return self._load_and_process( 'getLastSensitivityUnitStateTimeDerivative', - unitOpId, + unitOpId=unitOpId, sensIdx=sensIdx, - own_data=own_data + own_data=own_data, ) def primary_coordinates(self, unitOpId: int, own_data=True): @@ -561,8 +563,8 @@ def primary_coordinates(self, unitOpId: int, own_data=True): 'coords', 'nCoords', 'getPrimaryCoordinates', - unitOpId, - own_data=own_data + unitOpId=unitOpId, + own_data=own_data, ) def secondary_coordinates(self, unitOpId: int, own_data=True): @@ -570,8 +572,8 @@ def secondary_coordinates(self, unitOpId: int, own_data=True): 'coords', 'nCoords', 'getSecondaryCoordinates', - unitOpId, - own_data=own_data + unitOpId=unitOpId, + own_data=own_data, ) def particle_coordinates(self, unitOpId: int, parType: int, own_data=True): @@ -579,9 +581,9 @@ def particle_coordinates(self, unitOpId: int, parType: int, own_data=True): 'coords', 'nCoords', 'getParticleCoordinates', - unitOpId, + unitOpId=unitOpId, parType=parType, - own_data=own_data + own_data=own_data, ) def solution_times(self, own_data=True): From 80ca262b6d51905ebb49c20af8a37511c53811f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 09:20:39 +0100 Subject: [PATCH 19/43] Return if len of shape is 0 --- cadet/cadet_dll.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index 5a28d4a..5b5f1c2 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -239,6 +239,9 @@ def _process_data( shape.append(call_outputs[dim].value) dims.append(dim) + if len(shape) == 0: + return + if 'data' in call_outputs: data = numpy.ctypeslib.as_array(call_outputs['data'], shape=shape) if own_data: From b85c329c295d89c0e003a6c3b8d034e4dd610cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 09:21:48 +0100 Subject: [PATCH 20/43] Formatting --- cadet/cadet_dll.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index 5b5f1c2..72bad7a 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -248,7 +248,8 @@ def _process_data( data = data.copy() if 'time' in call_outputs: - time = numpy.ctypeslib.as_array(call_outputs['time'], shape=(call_outputs['nTime'].value, )) + n_time = call_outputs['nTime'].value + time = numpy.ctypeslib.as_array(call_outputs['time'], shape=(n_time, )) if own_data: time = time.copy() @@ -594,7 +595,6 @@ def solution_times(self, own_data=True): 'time', 'nTime', 'getSolutionTimes', - None, own_data=own_data ) From 659d8b26b6c48dac573710e565b99d407e778a2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 09:29:46 +0100 Subject: [PATCH 21/43] Return processed results --- cadet/cadet_dll.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index 72bad7a..78b454e 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -259,10 +259,10 @@ def _load_and_process(self, *args, own_data=True, **kwargs): call_outputs = self._load_data(*args, **kwargs) if call_outputs is None: - return None, None, None + return - time, data, dims = self._process_data(call_outputs, own_data) - return time, data, dims + processed_results = self._process_data(call_outputs, own_data) + return processed_results def _load_and_process_array(self, data_key: str, len_key: str, *args, own_data=True, **kwargs): call_outputs = self._load_data(*args, **kwargs) From 035dde22654846d3e14da44abca06e31ec289397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 09:41:33 +0100 Subject: [PATCH 22/43] Remove unit from sensitivities call --- cadet/cadet_dll.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index 78b454e..dd2daab 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -280,10 +280,9 @@ def npartypes(self, unitOpId: int): return int(call_outputs['nParTypes'].value) - def nsensitivities(self, unitOpId: int): + def nsensitivities(self): call_outputs = self._load_data( 'getNumSensitivities', - unitOpId=unitOpId, ) return int(call_outputs['nSens'].value) From 7ded74a035f294d9c3f03abc07bd76762991a4ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 09:41:39 +0100 Subject: [PATCH 23/43] Update state variable names --- cadet/cadet_dll.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index dd2daab..8c66530 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -513,7 +513,7 @@ def last_state_y(self, own_data=True): def last_state_ydot(self, own_data=True): return self._load_and_process( - 'getLastStateDerivative', + 'getLastStateTimeDerivative', own_data=own_data, ) @@ -923,13 +923,15 @@ def _load_particle_type(self, sim, data, unitOpId, solution_str, sensIdx=None): def load_state(self, sim): if 'write_solution_last' in sim.root.input['return']: - sim.root.output['last_state_y'] = self.res.lastState() - sim.root.output['last_state_ydot'] = self.res.lastStateDerivative() + sim.root.output['last_state_y'] = self.res.last_state_y() + sim.root.output['last_state_ydot'] = self.res.last_state_ydot() if 'write_sens_last' in sim.root.input['return']: - for idx in range(self.res.num_sensitivities()): - sim.root.output[self._get_index_string('last_state_sensy', idx)] = self.res.lastSensitivityState(idx) - sim.root.output[self._get_index_string('last_state_sensydot', idx)] = self.res.lastSensitivityStateDerivative(idx) + for idx in range(self.res.nsensitivities()): + idx_str_y = self._get_index_string('last_state_sensy', idx) + sim.root.output[idx_str_y] = self.res.last_state_sens(idx) + idx_str_ydot = self._get_index_string('last_state_sensydot', idx) + sim.root.output[idx_str_ydot] = self.res.last_state_sensdot(idx) @staticmethod def _get_index_string(prefix, index): From 09c810b333ee0b632d1debbe9a93020b95a9cf23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 09:45:41 +0100 Subject: [PATCH 24/43] Add TODOs --- cadet/cadet.py | 2 ++ cadet/cadet_dll.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cadet/cadet.py b/cadet/cadet.py index dedebde..4cb570d 100644 --- a/cadet/cadet.py +++ b/cadet/cadet.py @@ -359,6 +359,7 @@ def load_results(self): def run(self, timeout=None, check=None): data = self.cadet_runner.run(simulation=self.root.input, filename=self.filename, timeout=timeout, check=check) + # TODO: Why is this commented out? # self.return_information = data return data @@ -369,6 +370,7 @@ def run_load(self, timeout=None, check=None, clear=True): timeout=timeout, check=check ) + # TODO: Why is this commented out? # self.return_information = data self.load_results() if clear: diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index 8c66530..9210153 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -679,7 +679,6 @@ def load_results(self, sim): self.load_solution_times(sim) self.load_coordinates(sim) - # TODO: Crashes when simulation includes sensitivities self.load_solution(sim) self.load_sensitivity(sim) self.load_state(sim) @@ -690,6 +689,7 @@ def load_solution_times(self, sim): def load_coordinates(self, sim): coordinates = addict.Dict() + # TODO: Use n_units from API? for unit in range(sim.root.input.model.nunits): unit_index = self._get_index_string('unit', unit) if 'write_coordinates' in sim.root.input['return'][unit_index].keys(): @@ -711,6 +711,7 @@ def load_coordinates(self, sim): def load_solution(self, sim): solution = addict.Dict() + # TODO: Use n_units from API? for unit in range(sim.root.input.model.nunits): unit_index = self._get_index_string('unit', unit) unit_solution = addict.Dict() From bb95067014cafa6fca148c1b87876bafef968a2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 09:52:18 +0100 Subject: [PATCH 25/43] Require Python >3.10 Due to the use of type hints, we now require Python >=3.10 --- .github/workflows/pipeline.yml | 2 +- setup.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 190a984..484092c 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -17,7 +17,7 @@ jobs: strategy: matrix: - python-version: ["3.9", "3.10", "3.11"] + python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 diff --git a/setup.py b/setup.py index 54cf7e2..0c8cf02 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ with open("README.md", "r") as fh: long_description = fh.read() - + setuptools.setup( name="CADET-Python", version="0.14.1", @@ -24,12 +24,12 @@ "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", ], + python_requires='>=3.10', extras_require={ "testing": [ "pytest", "matplotlib", "pandas", ], - }, - python_requires='>=3.7', -) + } +) From 56b7f5be57fedf905806d956e55d06846a42bff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 10:02:22 +0100 Subject: [PATCH 26/43] Update pipeline --- .github/workflows/pipeline.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 484092c..0b78e41 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -19,7 +19,7 @@ jobs: matrix: python-version: ["3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Get Date id: get-date @@ -43,15 +43,20 @@ jobs: path: ${{ env.CONDA }}/envs key: python_${{ matrix.python-version }}-${{ steps.get-date.outputs.today }}-${{ env.CACHE_NUMBER }} - - name: Test Wheel install and import + - name: Set up mamba run: | mamba install python==${{ matrix.python-version }} - pip install wheel - python setup.py bdist_wheel - cd dist - pip install CADET_Python*.whl + - name: Install pypa/build + run: | + python -m pip install build --user + + - name: Build binary wheel and source tarball + run: | + python -m build --sdist --wheel --outdir dist/ . + + - name: Test Wheel install and import + run: | python -c "import cadet; print(cadet.__version__)" - cd .. - name: Test with pytest run: | pip install .[testing] From b12624c23181a9495492f7dfc4dedd0898a4eef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 11:55:24 +0100 Subject: [PATCH 27/43] Add docstrings --- cadet/cadet_dll.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index 9210153..a1cccde 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -685,9 +685,11 @@ def load_results(self, sim): def load_solution_times(self, sim): if 'write_solution_times' in sim.root.input['return']: + """Load solution_times from simulation results.""" sim.root.output.solution.solution_times = self.res.solution_times() def load_coordinates(self, sim): + """Load coordinates data from simulation results.""" coordinates = addict.Dict() # TODO: Use n_units from API? for unit in range(sim.root.input.model.nunits): @@ -710,6 +712,7 @@ def load_coordinates(self, sim): sim.root.output.coordinates = coordinates def load_solution(self, sim): + """Load solution data from simulation results.""" solution = addict.Dict() # TODO: Use n_units from API? for unit in range(sim.root.input.model.nunits): @@ -741,6 +744,7 @@ def load_solution(self, sim): return solution def load_sensitivity(self, sim): + """Load sensitivity data from simulation results.""" sensitivity = addict.Dict() nsens = sim.root.input.sensitivity.get('nsens', 0) for sens in range(nsens): @@ -797,6 +801,7 @@ def load_sensitivity(self, sim): sim.root.output.sensitivity = sensitivity def _checks_if_write_is_true(func): + """Decorator to check if unit operation solution should be written out.""" def wrapper(self, sim, unitOpId, solution_str, *args, **kwargs): unit_index = self._get_index_string('unit', unitOpId) unit_return_config = sim.root.input['return'][unit_index].keys() @@ -813,6 +818,7 @@ def wrapper(self, sim, unitOpId, solution_str, *args, **kwargs): return wrapper def _loads_data(func): + """Decorator to load data from simulation results before processing further.""" def wrapper(self, sim, unitOpId, solution_str, sensIdx=None, *args, **kwargs): solution_fun = getattr(self.res, solution_str) if sensIdx is None: @@ -936,4 +942,5 @@ def load_state(self, sim): @staticmethod def _get_index_string(prefix, index): + """Helper method to get string indices (e.g. (unit, 0) -> 'unit_000').""" return f'{prefix}_{index:03d}' From 11bcf92ebcfab5525604a7d89d13c2317f6c9abc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 11:55:34 +0100 Subject: [PATCH 28/43] Check if write flag is False --- cadet/cadet_dll.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index a1cccde..038e8d6 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -684,8 +684,9 @@ def load_results(self, sim): self.load_state(sim) def load_solution_times(self, sim): - if 'write_solution_times' in sim.root.input['return']: """Load solution_times from simulation results.""" + write_solution_times = sim.root.input['return'].get('write_solution_times', 0) + if write_solution_times: sim.root.output.solution.solution_times = self.res.solution_times() def load_coordinates(self, sim): @@ -694,7 +695,8 @@ def load_coordinates(self, sim): # TODO: Use n_units from API? for unit in range(sim.root.input.model.nunits): unit_index = self._get_index_string('unit', unit) - if 'write_coordinates' in sim.root.input['return'][unit_index].keys(): + write_coordinates = sim.root.input['return'][unit_index].get('write_coordinates', 0) + if write_coordinates: pc = self.res.primary_coordinates(unit) if pc is not None: coordinates[unit_index]['axial_coordinates'] = pc @@ -804,8 +806,10 @@ def _checks_if_write_is_true(func): """Decorator to check if unit operation solution should be written out.""" def wrapper(self, sim, unitOpId, solution_str, *args, **kwargs): unit_index = self._get_index_string('unit', unitOpId) - unit_return_config = sim.root.input['return'][unit_index].keys() - if f'write_{solution_str}' not in unit_return_config: + + write = sim.root.input['return'][unit_index].get(f'write_{solution_str}', 0) + + if not write: return {} solution = func(self, sim, unitOpId, solution_str, *args, **kwargs) From f8ca4b8002fe3767f1a5a53a88ce6b7ede6fe699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 11:56:26 +0100 Subject: [PATCH 29/43] Only update if keys are present --- cadet/cadet_dll.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index 038e8d6..672f605 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -711,7 +711,8 @@ def load_coordinates(self, sim): if par_coords is not None: coordinates[unit_index][self._get_index_string('particle_coordinates', pt)] = par_coords - sim.root.output.coordinates = coordinates + if len(coordinates) > 0: + sim.root.output.coordinates = coordinates def load_solution(self, sim): """Load solution data from simulation results.""" @@ -741,7 +742,7 @@ def load_solution(self, sim): solution[unit_index].update(unit_solution) if len(unit_solution) > 1: - sim.root.output.solution = solution + sim.root.output.solution.update(solution) return solution @@ -797,10 +798,12 @@ def load_sensitivity(self, sim): unit_sensitivity[sens_index].update( self._load_solution_trivial(sim, unit, 'sensdot_volume', sens) ) + if len(unit_sensitivity) > 0: + sensitivity[sens_index].update(unit_sensitivity) - sensitivity[sens_index].update(unit_sensitivity) + if len(sensitivity) > 0: + sim.root.output.sensitivity = sensitivity - sim.root.output.sensitivity = sensitivity def _checks_if_write_is_true(func): """Decorator to check if unit operation solution should be written out.""" From 970549d438b7e9ad732132b164beca65cb44140a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 11:57:41 +0100 Subject: [PATCH 30/43] Formatting --- cadet/cadet_dll.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index 672f605..5af0bc2 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -709,7 +709,8 @@ def load_coordinates(self, sim): for pt in range(num_par_types): par_coords = self.res.particle_coordinates(unit, pt) if par_coords is not None: - coordinates[unit_index][self._get_index_string('particle_coordinates', pt)] = par_coords + par_idx = self._get_index_string('particle_coordinates', pt) + coordinates[unit_index][par_idx] = par_coords if len(coordinates) > 0: sim.root.output.coordinates = coordinates @@ -804,6 +805,20 @@ def load_sensitivity(self, sim): if len(sensitivity) > 0: sim.root.output.sensitivity = sensitivity + def load_state(self, sim): + """Load last state from simulation results.""" + write_solution_last = sim.root.input['return'].get('write_solution_last', 0) + if write_solution_last: + sim.root.output['last_state_y'] = self.res.last_state_y() + sim.root.output['last_state_ydot'] = self.res.last_state_ydot() + + write_sens_last = sim.root.input['return'].get('write_sens_last', 0) + if write_sens_last: + for idx in range(self.res.nsensitivities()): + idx_str_y = self._get_index_string('last_state_sensy', idx) + sim.root.output[idx_str_y] = self.res.last_state_sens(idx) + idx_str_ydot = self._get_index_string('last_state_sensydot', idx) + sim.root.output[idx_str_ydot] = self.res.last_state_sensdot(idx) def _checks_if_write_is_true(func): """Decorator to check if unit operation solution should be written out.""" @@ -935,18 +950,6 @@ def _load_solution_particle(self, sim, unitOpId, solution_str, sensIdx=None): def _load_particle_type(self, sim, data, unitOpId, solution_str, sensIdx=None): pass - def load_state(self, sim): - if 'write_solution_last' in sim.root.input['return']: - sim.root.output['last_state_y'] = self.res.last_state_y() - sim.root.output['last_state_ydot'] = self.res.last_state_ydot() - - if 'write_sens_last' in sim.root.input['return']: - for idx in range(self.res.nsensitivities()): - idx_str_y = self._get_index_string('last_state_sensy', idx) - sim.root.output[idx_str_y] = self.res.last_state_sens(idx) - idx_str_ydot = self._get_index_string('last_state_sensydot', idx) - sim.root.output[idx_str_ydot] = self.res.last_state_sensdot(idx) - @staticmethod def _get_index_string(prefix, index): """Helper method to get string indices (e.g. (unit, 0) -> 'unit_000').""" From 903cd70a3a8bfcfaa8c3e26dd3bebee53f537d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 12:00:38 +0100 Subject: [PATCH 31/43] Remove old code --- cadet/cadet_dll.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index 5af0bc2..c78b42b 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -947,9 +947,6 @@ def _load_solution_particle(self, sim, unitOpId, solution_str, sensIdx=None): return solution - def _load_particle_type(self, sim, data, unitOpId, solution_str, sensIdx=None): - pass - @staticmethod def _get_index_string(prefix, index): """Helper method to get string indices (e.g. (unit, 0) -> 'unit_000').""" From be5fab8e858bed5bcf304f0dc58630dad57d6f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 14:27:37 +0100 Subject: [PATCH 32/43] Fix loading state --- cadet/cadet_dll.py | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index c78b42b..d9651cc 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -506,40 +506,52 @@ def sensdot_volume(self, unitOpId: int, sensIdx: int, own_data=True): ) def last_state_y(self, own_data=True): - return self._load_and_process( + return self._load_and_process_array( + 'state', + 'nStates', 'getLastState', own_data=own_data, ) def last_state_ydot(self, own_data=True): - return self._load_and_process( + return self._load_and_process_array( + 'state', + 'nStates', 'getLastStateTimeDerivative', own_data=own_data, ) def last_state_y_unit(self, unitOpId: int, own_data=True): - return self._load_and_process( + return self._load_and_process_array( + 'state', + 'nStates', 'getLastUnitState', unitOpId=unitOpId, own_data=own_data, ) def last_state_ydot_unit(self, unitOpId: int, own_data=True): - return self._load_and_process( + return self._load_and_process_array( + 'state', + 'nStates', 'getLastUnitStateTimeDerivative', unitOpId=unitOpId, own_data=own_data, ) def last_state_sens(self, sensIdx: int, own_data=True): - return self._load_and_process( + return self._load_and_process_array( + 'state', + 'nStates', 'getLastSensitivityState', sensIdx=sensIdx, own_data=own_data, ) def last_state_sensdot(self, sensIdx: int, own_data=True): - return self._load_and_process( + return self._load_and_process_array( + 'state', + 'nStates', 'getLastSensitivityStateTimeDerivative', sensIdx=sensIdx, own_data=own_data, @@ -807,11 +819,13 @@ def load_sensitivity(self, sim): def load_state(self, sim): """Load last state from simulation results.""" + # System state write_solution_last = sim.root.input['return'].get('write_solution_last', 0) if write_solution_last: sim.root.output['last_state_y'] = self.res.last_state_y() sim.root.output['last_state_ydot'] = self.res.last_state_ydot() + # System sensitivities write_sens_last = sim.root.input['return'].get('write_sens_last', 0) if write_sens_last: for idx in range(self.res.nsensitivities()): @@ -820,6 +834,18 @@ def load_state(self, sim): idx_str_ydot = self._get_index_string('last_state_sensydot', idx) sim.root.output[idx_str_ydot] = self.res.last_state_sensdot(idx) + + # TODO: Use n_units from API? + solution = sim.root.output.solution + for unit in range(sim.root.input.model.nunits): + unit_index = self._get_index_string('unit', unit) + write_solution_last = sim.root.input['return'][unit_index].get('write_solution_last_unit', 0) + if write_solution_last: + solution_last_unit = self.res.last_state_y_unit(unit) + solution[unit_index]['last_state_y'] = solution_last_unit + soldot_last_unit = self.res.last_state_ydot_unit(unit) + solution[unit_index]['last_state_ydot'] = soldot_last_unit + def _checks_if_write_is_true(func): """Decorator to check if unit operation solution should be written out.""" def wrapper(self, sim, unitOpId, solution_str, *args, **kwargs): From 411fd4a7bda6a4c7049ace04784f990615478cf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 14:27:56 +0100 Subject: [PATCH 33/43] Fix loading sensitivities --- cadet/cadet_dll.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index d9651cc..ba466ae 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -763,56 +763,60 @@ def load_sensitivity(self, sim): """Load sensitivity data from simulation results.""" sensitivity = addict.Dict() nsens = sim.root.input.sensitivity.get('nsens', 0) + for sens in range(nsens): sens_index = self._get_index_string('param', sens) + for unit in range(sim.root.input.model.nunits): unit_sensitivity = addict.Dict() + unit_index = self._get_index_string('unit', unit) - unit_sensitivity[sens_index].update( + unit_sensitivity.update( self._load_solution_io(sim, unit, 'sens_inlet', sens) ) - unit_sensitivity[sens_index].update( + unit_sensitivity.update( self._load_solution_io(sim, unit, 'sens_outlet', sens) ) - unit_sensitivity[sens_index].update( + unit_sensitivity.update( self._load_solution_trivial(sim, unit, 'sens_bulk', sens) ) - unit_sensitivity[sens_index].update( + unit_sensitivity.update( self._load_solution_particle(sim, unit, 'sens_particle', sens) ) - unit_sensitivity[sens_index].update( + unit_sensitivity.update( self._load_solution_particle(sim, unit, 'sens_solid', sens) ) - unit_sensitivity[sens_index].update( + unit_sensitivity.update( self._load_solution_trivial(sim, unit, 'sens_flux', sens) ) - unit_sensitivity[sens_index].update( + unit_sensitivity.update( self._load_solution_trivial(sim, unit, 'sens_volume', sens) ) - unit_sensitivity[sens_index].update( + unit_sensitivity.update( self._load_solution_io(sim, unit, 'sensdot_inlet', sens) ) - unit_sensitivity[sens_index].update( + unit_sensitivity.update( self._load_solution_io(sim, unit, 'sensdot_outlet', sens) ) - unit_sensitivity[sens_index].update( + unit_sensitivity.update( self._load_solution_trivial(sim, unit, 'sensdot_bulk', sens) ) - unit_sensitivity[sens_index].update( + unit_sensitivity.update( self._load_solution_particle(sim, unit, 'sensdot_particle', sens) ) - unit_sensitivity[sens_index].update( + unit_sensitivity.update( self._load_solution_particle(sim, unit, 'sensdot_solid', sens) ) - unit_sensitivity[sens_index].update( + unit_sensitivity.update( self._load_solution_trivial(sim, unit, 'sensdot_flux', sens) ) - unit_sensitivity[sens_index].update( + unit_sensitivity.update( self._load_solution_trivial(sim, unit, 'sensdot_volume', sens) ) - if len(unit_sensitivity) > 0: - sensitivity[sens_index].update(unit_sensitivity) + + if len(unit_sensitivity) > 0: + sensitivity[sens_index][unit_index] = unit_sensitivity if len(sensitivity) > 0: sim.root.output.sensitivity = sensitivity From 94669df78a7497a2d88ce261b9c9f444c369c401 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 14:28:51 +0100 Subject: [PATCH 34/43] Fix splitting ports --- cadet/cadet_dll.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index ba466ae..0fb0870 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -906,45 +906,51 @@ def _load_solution_io(self, sim, data, unitOpId, solution_str, sensIdx=None): split_ports_data = sim.root.input['return'].get('split_ports_data', 1) single_as_multi_port = sim.root.input['return'].get('single_as_multi_port', 0) - nComp = dims.index('nComp') + nComp_idx = dims.index('nComp') + nComp = out.shape[nComp_idx] try: - nPorts = dims.index('nPorts') + nPort_idx = dims.index('nPort') + nPort = out.shape[nPort_idx] except ValueError: - nPorts = None + nPort_idx = None + nPort = 1 if split_components_data: if split_ports_data: - if nPorts is None: + if nPort == 1: if single_as_multi_port: - for comp in range(out.shape[nComp]): + for comp in range(nComp): comp_out = numpy.squeeze(out[..., comp]) solution[f'{solution_str}_port_000_comp_{comp:03d}'] = comp_out else: - for comp in range(out.shape[nComp]): + for comp in range(nComp): comp_out = numpy.squeeze(out[..., comp]) solution[f'{solution_str}_comp_{comp:03d}'] = comp_out else: - for port in range(out.shape[nPorts]): - for comp in range(out.shape[nComp]): + for port in range(nPort): + for comp in range(nComp): comp_out = numpy.squeeze(out[..., port, comp]) solution[f'{solution_str}_port_{port:03d}_comp_{comp:03d}'] = comp_out else: - for comp in range(out.shape[nComp]): + for comp in range(nComp): comp_out = numpy.squeeze(out[..., comp]) solution[f'{solution_str}_comp_{comp:03d}'] = comp_out else: if split_ports_data: - if nPorts is None: + if nPort == 1: if single_as_multi_port: solution[f'{solution_str}_port_000'] = out else: - solution[solution_str] = out + solution[solution_str] = numpy.squeeze(out[..., 0, :]) else: - for port in range(out.shape[nPorts]): + for port in range(nPort): port_out = numpy.squeeze(out[..., port, :]) solution[f'{solution_str}_port_{port:03d}'] = port_out else: - solution[solution_str] = out + if nPort == 1 and nPort_idx is not None: + solution[solution_str] = numpy.squeeze(out[..., 0, :]) + else: + solution[solution_str] = out return solution From 8d3bb5d3704834f614caf9a65edc85fd459e22b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 14:29:18 +0100 Subject: [PATCH 35/43] Add DLL test --- examples/dll_test.py | 237 ------------- tests/__init__.py | 2 + tests/test_dll.py | 789 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 791 insertions(+), 237 deletions(-) delete mode 100644 examples/dll_test.py create mode 100644 tests/test_dll.py diff --git a/examples/dll_test.py b/examples/dll_test.py deleted file mode 100644 index 26ba11f..0000000 --- a/examples/dll_test.py +++ /dev/null @@ -1,237 +0,0 @@ -""" -On Windows: $CADET_ROOT/bin/cadet.dll -On Linux: - release: $CADET_ROOT/lib/libcadet.so - debug: $CADET_ROOT/lib/libcadet_d.so -""" -import copy -import os -from pathlib import Path -import platform -import subprocess -import sys -sys.path.insert(0, '../') -import tempfile - -from cadet import Cadet - -# cadet_root = Path(r"C:\Users\kosh\cadet_build\CADET\VCPKG_42_dll\bin\cadet.dll") -cadet_root = Path('/home/jo/code/CADET/install/capi/') - -cadet_dll_path = cadet_root / 'lib' / 'libcadet_d.so' -cadet_cli_path = cadet_root / 'bin' / 'cadet-cli' - -# %% - -def setup_template_model( - cadet_root, - model='GENERAL_RATE_MODEL', - n_partypes=1, - include_sensitivity=False, - file_name='LWE.h5', - ): - - executable = 'createLWE' - if platform.system() == 'Windows': - executable += '.exe' - - create_lwe_path = Path(cadet_root) / 'bin' / executable - - args =[ - create_lwe_path.as_posix(), - f'--out {file_name}', - f'--unit {model}', - f'--parTypes {n_partypes}', - ] - - if include_sensitivity: - args.append('--sens COL_POROSITY/-1/-1/-1/-1/-1/-1/0') - - ret = subprocess.run( - args, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - cwd='./' - ) - - if ret.returncode != 0: - if ret.stdout: - print('Output', ret.stdout.decode('utf-8')) - if ret.stderr: - print('Errors', ret.stderr.decode('utf-8')) - raise Exception( - "Failure: Creation of test simulation ran into problems" - ) - - template = Cadet() - template.filename = file_name - template.load() - - return template - - -def setup_solution_recorder( - template, - split_components=0, - split_ports=0, - single_as_multi_port=0, - ): - - template = copy.deepcopy(template) - - template.root.input['return'].write_solution_last = 1 - template.root.input['return'].write_sens_last = 1 - - template.root.input['return'].split_components_data = split_components - template.root.input['return'].split_ports_data = split_ports - template.root.input['return'].single_as_multi_port = single_as_multi_port - - template.root.input['return'].unit_000.write_coordinates = 1 - - template.root.input['return'].unit_000.write_solution_inlet = 1 - template.root.input['return'].unit_000.write_solution_outlet = 1 - template.root.input['return'].unit_000.write_solution_bulk = 1 - template.root.input['return'].unit_000.write_solution_particle = 1 - template.root.input['return'].unit_000.write_solution_solid = 1 - template.root.input['return'].unit_000.write_solution_flux = 1 - template.root.input['return'].unit_000.write_solution_volume = 1 - - template.root.input['return'].unit_000.write_soldot_inlet = 1 - template.root.input['return'].unit_000.write_soldot_outlet = 1 - template.root.input['return'].unit_000.write_soldot_bulk = 1 - template.root.input['return'].unit_000.write_soldot_particle = 1 - template.root.input['return'].unit_000.write_soldot_solid = 1 - template.root.input['return'].unit_000.write_soldot_flux = 1 - template.root.input['return'].unit_000.write_soldot_volume = 1 - - template.root.input['return'].unit_000.write_sens_inlet = 1 - template.root.input['return'].unit_000.write_sens_outlet = 1 - template.root.input['return'].unit_000.write_sens_bulk = 1 - template.root.input['return'].unit_000.write_sens_particle = 1 - template.root.input['return'].unit_000.write_sens_solid = 1 - template.root.input['return'].unit_000.write_sens_flux = 1 - template.root.input['return'].unit_000.write_sens_volume = 1 - - template.root.input['return'].unit_000.write_sensdot_inlet = 1 - template.root.input['return'].unit_000.write_sensdot_outlet = 1 - template.root.input['return'].unit_000.write_sensdot_bulk = 1 - template.root.input['return'].unit_000.write_sensdot_particle = 1 - template.root.input['return'].unit_000.write_sensdot_solid = 1 - template.root.input['return'].unit_000.write_sensdot_flux = 1 - template.root.input['return'].unit_000.write_sensdot_volume = 1 - - template.root.input['return'].unit_000.write_solution_last_unit = 1 - template.root.input['return'].unit_000.write_soldot_last_unit = 1 - - # LAST_STATE_Y - # LAST_STATE_YDOT - # LAST_STATE_SENSYDOT_??? - # LAST_STATE_SENSY_??? - - for unit in range(template.root.input.model['nunits']): - template.root.input['return']['unit_{0:03d}'.format(unit)] = template.root.input['return'].unit_000 - - if template.filename is not None: - template.save() - - return template - - -# %% -dll = True - -if dll: - cadet_path = cadet_dll_path -else: - cadet_path = cadet_cli_path - -Cadet.cadet_path = cadet_path.as_posix() - -# %% Test standard GRM -model = 'GENERAL_RATE_MODEL' - -template = setup_template_model( - cadet_root, - model, -) - -# Don't split anything -sim = setup_solution_recorder( - template, - split_components=0, - split_ports=0, - single_as_multi_port=0, -) - -sim.run_load() - -# Split everything -sim = setup_solution_recorder( - template, - split_components=1, - split_ports=1, - single_as_multi_port=1, -) - -sim.run_load() - -# %% Test sensitivities -model = 'GENERAL_RATE_MODEL' - -template = setup_template_model( - cadet_root, - model, - include_sensitivity=True, -) - -# Don't split anything -sim = setup_solution_recorder( - template, - split_components=0, - split_ports=0, - single_as_multi_port=0, -) - -sim.run_load() - -# Split everything -sim = setup_solution_recorder( - template, - split_components=1, - split_ports=1, - single_as_multi_port=1, -) - -sim.run_load() - - -# %% Test ports -model = 'GENERAL_RATE_MODEL_2D' - -template = setup_template_model(cadet_root, model) -setup_solution_recorder( - template, - split_components=0, - split_ports=0, - single_as_multi_port=0, -) - -sim.run_load() - -# Only split ports for 2D Model -setup_solution_recorder( - template, - split_components=0, - split_ports=1, - single_as_multi_port=0, -) - -# Split everything -setup_solution_recorder( - template, - split_components=1, - split_ports=1, - single_as_multi_port=1, -) - -sim.run_load() diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..633f866 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- + diff --git a/tests/test_dll.py b/tests/test_dll.py new file mode 100644 index 0000000..36dd868 --- /dev/null +++ b/tests/test_dll.py @@ -0,0 +1,789 @@ +import copy +import os +from pathlib import Path +import platform +import subprocess +import sys + +from addict import Dict +import pytest + +from cadet import Cadet + + +# %% Utility methods + +# TODO: Remove once #5 is merged +cadet_root = Path('/home/jo/code/CADET/install/capi/') + +def setup_model( + cadet_root, + use_dll=True, + model='GENERAL_RATE_MODEL', + n_partypes=1, + include_sensitivity=False, + file_name='LWE.h5', + ): + """ + Set up and initialize a CADET model template. + + This function prepares a CADET model template by invoking the `createLWE` executable + with specified parameters. It supports the configuration of the model type, number + of particle types, inclusion of sensitivity analysis, and the name of the output + file. Depending on the operating system, it adjusts the executable name accordingly. + After creating the model, it initializes a Cadet instance with the specified or + default CADET binary and the created model file. + + Parameters + ---------- + cadet_root : str or Path + The root directory where the CADET software is located. + use_dll : bool, optional + If True, use the in-memory interface for CADET. Otherwise, use the CLI. + The default is True. + model : str, optional + The model type to set up. The default is 'GENERAL_RATE_MODEL'. + n_partypes : int, optional + The number of particle types. The default is 1. + include_sensitivity : bool, optional + If True, included parameter sensitivities in template. The default is False. + file_name : str, optional + The name of the file to which the CADET model is written. + The default is 'LWE.h5'. + + Returns + ------- + Cadet + An initialized Cadet instance with the model loaded. + + Raises + ------ + Exception + If the creation of the test simulation encounters problems, + detailed in the subprocess's stdout and stderr. + FileNotFoundError + If the CADET executable or DLL file cannot be found at the specified paths. + + Notes + ----- + The function assumes the presence of `createLWE` executable within the `bin` + directory of the `cadet_root` path. The sensitivity analysis, if included, is + configured for column porosity. + + See Also + -------- + Cadet : The class representing a CADET simulation model. + + Examples + -------- + >>> cadet_model = setup_model( + '/path/to/cadet', + use_dll=False, + model='GENERAL_RATE_MODEL', + n_partypes=2, + include_sensitivity=True, + file_name='my_model.h5' + ) + This example sets up a GENERAL_RATE_MODEL with 2 particle types, includes + sensitivity analysis, and writes the model to 'my_model.h5', using the command-line + interface. + """ + executable = 'createLWE' + if platform.system() == 'Windows': + executable += '.exe' + + create_lwe_path = Path(cadet_root) / 'bin' / executable + + args =[ + create_lwe_path.as_posix(), + f'--out {file_name}', + f'--unit {model}', + f'--parTypes {n_partypes}', + ] + + if include_sensitivity: + args.append('--sens COL_POROSITY/-1/-1/-1/-1/-1/-1/0') + + ret = subprocess.run( + args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + cwd='./' + ) + + if ret.returncode != 0: + if ret.stdout: + print('Output', ret.stdout.decode('utf-8')) + if ret.stderr: + print('Errors', ret.stderr.decode('utf-8')) + raise Exception( + "Failure: Creation of test simulation ran into problems" + ) + + model = Cadet() + # TODO: This should be simplified once #14 is merged. + if use_dll: + cadet_path = cadet_root / 'lib' / 'libcadet.so' + if not cadet_path.exists(): + cadet_path = cadet_root / 'lib' / 'libcadet_d.so' + else: + cadet_path = cadet_root / 'bin' / 'cadet-cli' + + if not cadet_path.exists(): + raise FileNotFoundError("Could not find CADET at: {cadet_root}") + + model.cadet_path = cadet_path + model.filename = file_name + model.load() + + return model + + +def setup_solution_recorder( + model, + split_components=0, + split_ports=0, + single_as_multi_port=0, + ): + """ + Configure the solution recorder for the simulation. + + This function adjusts the model's settings to specify what simulation data should + be recorded, including solutions at various points (inlet, outlet, bulk, etc.), + sensitivities, and their derivatives. It allows for the configuration of how + components and ports are treated in the output data, potentially splitting them for + individual analysis or aggregating them for a more holistic view. + + Parameters + ---------- + model : Cadet + The model instance to be configured for solution recording. + split_components : int, optional + If 1, split component data in the output. The default is 0. + split_ports : int, optional + If 1, split port data in the output. The default is 0. + single_as_multi_port : int, optional + If 1, treat single ports as multiple ports in the output. The default is 0. + + Examples + -------- + >>> model = Cadet() + >>> setup_solution_recorder(model, split_components=1, split_ports=1, single_as_multi_port=1) + This example demonstrates configuring a Cadet model instance for detailed solution recording, with component and port data split, and single ports treated as multiple ports. + + """ + + model.root.input['return'].write_solution_times = 1 + model.root.input['return'].write_solution_last = 1 + model.root.input['return'].write_sens_last = 1 + + model.root.input['return'].split_components_data = split_components + model.root.input['return'].split_ports_data = split_ports + model.root.input['return'].single_as_multi_port = single_as_multi_port + + model.root.input['return'].unit_000.write_coordinates = 1 + + model.root.input['return'].unit_000.write_solution_inlet = 1 + model.root.input['return'].unit_000.write_solution_outlet = 1 + model.root.input['return'].unit_000.write_solution_bulk = 1 + model.root.input['return'].unit_000.write_solution_particle = 1 + model.root.input['return'].unit_000.write_solution_solid = 1 + model.root.input['return'].unit_000.write_solution_flux = 1 + model.root.input['return'].unit_000.write_solution_volume = 1 + + model.root.input['return'].unit_000.write_soldot_inlet = 1 + model.root.input['return'].unit_000.write_soldot_outlet = 1 + model.root.input['return'].unit_000.write_soldot_bulk = 1 + model.root.input['return'].unit_000.write_soldot_particle = 1 + model.root.input['return'].unit_000.write_soldot_solid = 1 + model.root.input['return'].unit_000.write_soldot_flux = 1 + model.root.input['return'].unit_000.write_soldot_volume = 1 + + model.root.input['return'].unit_000.write_sens_inlet = 1 + model.root.input['return'].unit_000.write_sens_outlet = 1 + model.root.input['return'].unit_000.write_sens_bulk = 1 + model.root.input['return'].unit_000.write_sens_particle = 1 + model.root.input['return'].unit_000.write_sens_solid = 1 + model.root.input['return'].unit_000.write_sens_flux = 1 + model.root.input['return'].unit_000.write_sens_volume = 1 + + model.root.input['return'].unit_000.write_sensdot_inlet = 1 + model.root.input['return'].unit_000.write_sensdot_outlet = 1 + model.root.input['return'].unit_000.write_sensdot_bulk = 1 + model.root.input['return'].unit_000.write_sensdot_particle = 1 + model.root.input['return'].unit_000.write_sensdot_solid = 1 + model.root.input['return'].unit_000.write_sensdot_flux = 1 + model.root.input['return'].unit_000.write_sensdot_volume = 1 + + model.root.input['return'].unit_000.write_solution_last_unit = 1 + model.root.input['return'].unit_000.write_soldot_last_unit = 1 + + # LAST_STATE_Y + # LAST_STATE_YDOT + # LAST_STATE_SENSYDOT_??? + # LAST_STATE_SENSY_??? + + for unit in range(model.root.input.model['nunits']): + model.root.input['return']['unit_{0:03d}'.format(unit)] = model.root.input['return'].unit_000 + + if model.filename is not None: + model.save() + + +def run_simulation_with_options(use_dll, model_options, solution_recorder_options): + """Run a simulation with specified options for the model and solution recorder. + + Initializes and configures a simulation model with given options, sets up the + solution recording parameters, and executes the simulation. This function leverages + `setup_model` to create and initialize the model and `setup_solution_recorder` to + configure how the simulation results should be recorded based on the specified + options. + + Parameters + ---------- + use_dll : bool, optional + If True, use the in-memory interface for CADET. Otherwise, use the CLI. + The default is True. + model_options : dict + A dictionary of options to pass to `setup_model` for initializing the model. + Keys should match the parameter names of `setup_model`, excluding `use_dll`. + solution_recorder_options : dict + A dictionary of options to pass to `setup_solution_recorder` for configuring the + solution recorder. Keys should match the parameter names of + `setup_solution_recorder`. + + Returns + ------- + Cadet + An instance of the Cadet class with the model simulated and loaded. + + Examples + -------- + >>> use_dll = True + >>> model_options = { + ... 'model': 'GENERAL_RATE_MODEL', + ... 'n_partypes': 2, + ... 'include_sensitivity': True, + ... 'file_name': 'model_output.h5' + ... } + >>> solution_recorder_options = { + ... 'split_components': 1, + ... 'split_ports': 1, + ... 'single_as_multi_port': True + ... } + >>> model = run_simulation_with_options(use_dll, model_options, solution_recorder_options) + This example configures and runs a GENERAL_RATE_MODEL with sensitivity analysis and two particle types, records the solution with specific options, and loads the simulation results for further analysis. + """ + model = setup_model(cadet_root, use_dll, **model_options) + setup_solution_recorder(model, **solution_recorder_options) + + model.run_load() + + return model + + +# %% Model templates +grm_template = { + 'model': 'GENERAL_RATE_MODEL', + 'n_partypes': 1, + 'include_sensitivity': False, +} + +grm_template_sens = { + 'model': 'GENERAL_RATE_MODEL', + 'n_partypes': 1, + 'include_sensitivity': True, +} + +grm_template_partypes = { + 'model': 'GENERAL_RATE_MODEL', + 'n_partypes': 2, + 'include_sensitivity': False, +} + +_2dgrm_template = { + 'model': 'GENERAL_RATE_MODEL_2D', + 'n_partypes': 1, + 'include_sensitivity': False, +} + + +# %% Solution recorder templates + +no_split_options = { + 'split_components': 0, + 'split_ports': 0, + 'single_as_multi_port': 0, +} + +split_ports_options = { + 'split_components': 0, + 'split_ports': 1, + 'single_as_multi_port': 0, +} + +split_all_options = { + 'split_components': 1, + 'split_ports': 1, + 'single_as_multi_port': 1, +} + +# %% Test cases + +class Case(): + def __init__(self, name, model_options, solution_recorder_options, expected_results): + self.name = name + self.model_options = model_options + self.solution_recorder_options = solution_recorder_options + self.expected_results = expected_results + + def __str__(self): + return self.name + + def __repr__(self): + return \ + f"Case('{self.name}', {self.model_options}, " \ + f"{self.solution_recorder_options}, {self.expected_results})" + +# %% GRM + +grm = Case( + name='grm', + model_options=grm_template, + solution_recorder_options=no_split_options, + expected_results={ + 'last_state_y': (412,), + 'last_state_ydot': (412,), + 'coordinates_unit_000': { + 'axial_coordinates': (10,), + 'particle_coordinates_000': (4,), + }, + 'solution_unit_000': { + 'last_state_y': (404,), + 'last_state_ydot': (404,), + 'soldot_bulk': (1501, 10, 4), + 'soldot_flux': (1501, 1, 10, 4), + 'soldot_inlet': (1501, 4), + 'soldot_outlet': (1501, 4), + 'soldot_particle': (1501, 10, 4, 4), + 'soldot_solid': (1501, 10, 4, 4), + 'solution_bulk': (1501, 10, 4), + 'solution_flux': (1501, 1, 10, 4), + 'solution_inlet': (1501, 4), + 'solution_outlet': (1501, 4), + 'solution_particle': (1501, 10, 4, 4), + 'solution_solid': (1501, 10, 4, 4), + }, + 'solution_unit_001': { + 'last_state_y': (4,), + 'last_state_ydot': (4,), + 'soldot_inlet': (1501, 4), + 'soldot_outlet': (1501, 4), + 'solution_inlet': (1501, 4), + 'solution_outlet': (1501, 4), + }, + }, +) + +# %% GRM Split + +grm_split = Case( + name='grm_split', + model_options=grm_template, + solution_recorder_options=split_all_options, + expected_results={ + 'last_state_y': (412,), + 'last_state_ydot': (412,), + 'coordinates_unit_000': { + 'axial_coordinates': (10,), + 'particle_coordinates_000': (4,), + }, + 'solution_unit_000': { + 'last_state_y': (404,), + 'last_state_ydot': (404,), + 'soldot_bulk': (1501, 10, 4), + 'soldot_flux': (1501, 1, 10, 4), + 'soldot_inlet_port_000_comp_000': (1501,), + 'soldot_inlet_port_000_comp_001': (1501,), + 'soldot_inlet_port_000_comp_002': (1501,), + 'soldot_inlet_port_000_comp_003': (1501,), + 'soldot_outlet_port_000_comp_000': (1501,), + 'soldot_outlet_port_000_comp_001': (1501,), + 'soldot_outlet_port_000_comp_002': (1501,), + 'soldot_outlet_port_000_comp_003': (1501,), + 'soldot_particle': (1501, 10, 4, 4), + 'soldot_solid': (1501, 10, 4, 4), + 'solution_bulk': (1501, 10, 4), + 'solution_flux': (1501, 1, 10, 4), + 'solution_inlet_port_000_comp_000': (1501,), + 'solution_inlet_port_000_comp_001': (1501,), + 'solution_inlet_port_000_comp_002': (1501,), + 'solution_inlet_port_000_comp_003': (1501,), + 'solution_outlet_port_000_comp_000': (1501,), + 'solution_outlet_port_000_comp_001': (1501,), + 'solution_outlet_port_000_comp_002': (1501,), + 'solution_outlet_port_000_comp_003': (1501,), + 'solution_particle': (1501, 10, 4, 4), + 'solution_solid': (1501, 10, 4, 4), + }, + 'solution_unit_001': { + 'last_state_y': (4,), + 'last_state_ydot': (4,), + 'soldot_inlet_port_000_comp_000': (1501,), + 'soldot_inlet_port_000_comp_001': (1501,), + 'soldot_inlet_port_000_comp_002': (1501,), + 'soldot_inlet_port_000_comp_003': (1501,), + 'soldot_outlet_port_000_comp_000': (1501,), + 'soldot_outlet_port_000_comp_001': (1501,), + 'soldot_outlet_port_000_comp_002': (1501,), + 'soldot_outlet_port_000_comp_003': (1501,), + 'solution_inlet_port_000_comp_000': (1501,), + 'solution_inlet_port_000_comp_001': (1501,), + 'solution_inlet_port_000_comp_002': (1501,), + 'solution_inlet_port_000_comp_003': (1501,), + 'solution_outlet_port_000_comp_000': (1501,), + 'solution_outlet_port_000_comp_001': (1501,), + 'solution_outlet_port_000_comp_002': (1501,), + 'solution_outlet_port_000_comp_003': (1501,), + }, + }, +) + +# %% GRM Sens + +grm_sens = Case( + name='grm_sens', + model_options=grm_template_sens, + solution_recorder_options=no_split_options, + expected_results={ + 'last_state_y': (412,), + 'last_state_ydot': (412,), + 'coordinates_unit_000': { + 'axial_coordinates': (10,), + 'particle_coordinates_000': (4,), + }, + 'solution_unit_000': { + 'last_state_y': (404,), + 'last_state_ydot': (404,), + 'soldot_bulk': (1501, 10, 4), + 'soldot_flux': (1501, 1, 10, 4), + 'soldot_inlet': (1501, 4), + 'soldot_outlet': (1501, 4), + 'soldot_particle': (1501, 10, 4, 4), + 'soldot_solid': (1501, 10, 4, 4), + 'solution_bulk': (1501, 10, 4), + 'solution_flux': (1501, 1, 10, 4), + 'solution_inlet': (1501, 4), + 'solution_outlet': (1501, 4), + 'solution_particle': (1501, 10, 4, 4), + 'solution_solid': (1501, 10, 4, 4), + }, + 'solution_unit_001': { + 'last_state_y': (4,), + 'last_state_ydot': (4,), + 'soldot_inlet': (1501, 4), + 'soldot_outlet': (1501, 4), + 'solution_inlet': (1501, 4), + 'solution_outlet': (1501, 4), + }, + 'sens_param_000_unit_000': { + 'sensdot_bulk': (1501, 10, 4), + 'sensdot_flux': (1501, 1, 10, 4), + 'sensdot_inlet': (1501, 4), + 'sensdot_outlet': (1501, 4), + 'sensdot_particle': (1501, 10, 4, 4), + 'sensdot_solid': (1501, 10, 4, 4), + 'sens_bulk': (1501, 10, 4), + 'sens_flux': (1501, 1, 10, 4), + 'sens_inlet': (1501, 4), + 'sens_outlet': (1501, 4), + 'sens_particle': (1501, 10, 4, 4), + 'sens_solid': (1501, 10, 4, 4), + }, + 'sens_param_000_unit_001': { + 'sensdot_inlet': (1501, 4), + 'sensdot_outlet': (1501, 4), + 'sens_inlet': (1501, 4), + 'sens_outlet': (1501, 4), + }, + }, +) + +# %% GRM ParTypes + +grm_par_types = Case( + name='grm_par_types', + model_options=grm_template_partypes, + solution_recorder_options=no_split_options, + expected_results={ + 'last_state_y': (772,), + 'last_state_ydot': (772,), + 'coordinates_unit_000': { + 'axial_coordinates': (10,), + 'particle_coordinates_000': (4,), + 'particle_coordinates_001': (4,), + }, + 'solution_unit_000': { + 'last_state_y': (764,), + 'last_state_ydot': (764,), + 'soldot_bulk': (1501, 10, 4), + 'soldot_flux': (1501, 2, 10, 4), + 'soldot_inlet': (1501, 4), + 'soldot_outlet': (1501, 4), + 'soldot_particle_partype_000': (1501, 10, 4, 4), + 'soldot_particle_partype_001': (1501, 10, 4, 4), + 'soldot_solid_partype_000': (1501, 10, 4, 4), + 'soldot_solid_partype_001': (1501, 10, 4, 4), + 'solution_bulk': (1501, 10, 4), + 'solution_flux': (1501, 2, 10, 4), + 'solution_inlet': (1501, 4), + 'solution_outlet': (1501, 4), + 'solution_particle_partype_000': (1501, 10, 4, 4), + 'solution_particle_partype_001': (1501, 10, 4, 4), + 'solution_solid_partype_000': (1501, 10, 4, 4), + 'solution_solid_partype_001': (1501, 10, 4, 4), + }, + 'solution_unit_001': { + 'last_state_y': (4,), + 'last_state_ydot': (4,), + 'soldot_inlet': (1501, 4), + 'soldot_outlet': (1501, 4), + 'solution_inlet': (1501, 4), + 'solution_outlet': (1501, 4), + }, + }, +) + +# %% 2D GRM +_2dgrm = Case( + name='_2dgrm', + model_options=_2dgrm_template, + solution_recorder_options=no_split_options, + expected_results={ + 'last_state_y': (1228,), + 'last_state_ydot': (1228,), + 'coordinates_unit_000': { + 'axial_coordinates': (10,), + 'particle_coordinates_000': (4,), + 'radial_coordinates': (3,), + }, + 'solution_unit_000': { + 'last_state_y': (1212,), + 'last_state_ydot': (1212,), + 'soldot_bulk': (1501, 10, 3, 4), + 'soldot_flux': (1501, 1, 10, 3, 4), + 'soldot_inlet': (1501, 3, 4), + 'soldot_outlet': (1501, 3, 4), + 'soldot_particle': (1501, 10, 3, 4, 4), + 'soldot_solid': (1501, 10, 3, 4, 4), + 'solution_bulk': (1501, 10, 3, 4), + 'solution_flux': (1501, 1, 10, 3, 4), + 'solution_inlet': (1501, 3, 4), + 'solution_outlet': (1501, 3, 4), + 'solution_particle': (1501, 10, 3, 4, 4), + 'solution_solid': (1501, 10, 3, 4, 4), + }, + 'solution_unit_001': { + 'last_state_y': (4,), + 'last_state_ydot': (4,), + 'soldot_inlet': (1501, 4), + 'soldot_outlet': (1501, 4), + 'solution_inlet': (1501, 4), + 'solution_outlet': (1501, 4), + }, + }, +) + +# %% 2D GRM Split Ports (single_as_multi_port=False) + +_2dgrm_split_ports = Case( + name='_2dgrm_split_ports', + model_options=_2dgrm_template, + solution_recorder_options=split_ports_options, + expected_results={ + 'last_state_y': (1228,), + 'last_state_ydot': (1228,), + 'coordinates_unit_000': { + 'axial_coordinates': (10,), + 'particle_coordinates_000': (4,), + 'radial_coordinates': (3,), + }, + 'solution_unit_000': { + 'last_state_y': (1212,), + 'last_state_ydot': (1212,), + 'soldot_bulk': (1501, 10, 3, 4), + 'soldot_flux': (1501, 1, 10, 3, 4), + 'soldot_inlet_port_000': (1501, 4), + 'soldot_inlet_port_001': (1501, 4), + 'soldot_inlet_port_002': (1501, 4), + 'soldot_outlet_port_000': (1501, 4), + 'soldot_outlet_port_001': (1501, 4), + 'soldot_outlet_port_002': (1501, 4), + 'soldot_particle': (1501, 10, 3, 4, 4), + 'soldot_solid': (1501, 10, 3, 4, 4), + 'solution_bulk': (1501, 10, 3, 4), + 'solution_flux': (1501, 1, 10, 3, 4), + 'solution_inlet_port_000': (1501, 4), + 'solution_inlet_port_001': (1501, 4), + 'solution_inlet_port_002': (1501, 4), + 'solution_outlet_port_000': (1501, 4), + 'solution_outlet_port_001': (1501, 4), + 'solution_outlet_port_002': (1501, 4), + 'solution_particle': (1501, 10, 3, 4, 4), + 'solution_solid': (1501, 10, 3, 4, 4), + }, + 'solution_unit_001': { + 'last_state_y': (4,), + 'last_state_ydot': (4,), + 'soldot_inlet': (1501, 4), + 'soldot_outlet': (1501, 4), + 'solution_inlet': (1501, 4), + 'solution_outlet': (1501, 4), + }, + }, +) + +# %% 2D GRM Split All + +_2dgrm_split_all = Case( + name='_2dgrm_split_all', + model_options=_2dgrm_template, + solution_recorder_options=split_all_options, + expected_results={ + 'last_state_y': (1228,), + 'last_state_ydot': (1228,), + 'coordinates_unit_000': { + 'axial_coordinates': (10,), + 'particle_coordinates_000': (4,), + 'radial_coordinates': (3,), + }, + 'solution_unit_000': { + 'last_state_y': (1212,), + 'last_state_ydot': (1212,), + 'soldot_bulk': (1501, 10, 3, 4), + 'soldot_flux': (1501, 1, 10, 3, 4), + 'soldot_inlet_port_000_comp_000': (1501,), + 'soldot_inlet_port_000_comp_001': (1501,), + 'soldot_inlet_port_000_comp_002': (1501,), + 'soldot_inlet_port_000_comp_003': (1501,), + 'soldot_inlet_port_001_comp_000': (1501,), + 'soldot_inlet_port_001_comp_001': (1501,), + 'soldot_inlet_port_001_comp_002': (1501,), + 'soldot_inlet_port_001_comp_003': (1501,), + 'soldot_inlet_port_002_comp_000': (1501,), + 'soldot_inlet_port_002_comp_001': (1501,), + 'soldot_inlet_port_002_comp_002': (1501,), + 'soldot_inlet_port_002_comp_003': (1501,), + 'soldot_outlet_port_000_comp_000': (1501,), + 'soldot_outlet_port_000_comp_001': (1501,), + 'soldot_outlet_port_000_comp_002': (1501,), + 'soldot_outlet_port_000_comp_003': (1501,), + 'soldot_outlet_port_001_comp_000': (1501,), + 'soldot_outlet_port_001_comp_001': (1501,), + 'soldot_outlet_port_001_comp_002': (1501,), + 'soldot_outlet_port_001_comp_003': (1501,), + 'soldot_outlet_port_002_comp_000': (1501,), + 'soldot_outlet_port_002_comp_001': (1501,), + 'soldot_outlet_port_002_comp_002': (1501,), + 'soldot_outlet_port_002_comp_003': (1501,), + 'soldot_particle': (1501, 10, 3, 4, 4), + 'soldot_solid': (1501, 10, 3, 4, 4), + 'solution_bulk': (1501, 10, 3, 4), + 'solution_flux': (1501, 1, 10, 3, 4), + 'solution_inlet_port_000_comp_000': (1501,), + 'solution_inlet_port_000_comp_001': (1501,), + 'solution_inlet_port_000_comp_002': (1501,), + 'solution_inlet_port_000_comp_003': (1501,), + 'solution_inlet_port_001_comp_000': (1501,), + 'solution_inlet_port_001_comp_001': (1501,), + 'solution_inlet_port_001_comp_002': (1501,), + 'solution_inlet_port_001_comp_003': (1501,), + 'solution_inlet_port_002_comp_000': (1501,), + 'solution_inlet_port_002_comp_001': (1501,), + 'solution_inlet_port_002_comp_002': (1501,), + 'solution_inlet_port_002_comp_003': (1501,), + 'solution_outlet_port_000_comp_000': (1501,), + 'solution_outlet_port_000_comp_001': (1501,), + 'solution_outlet_port_000_comp_002': (1501,), + 'solution_outlet_port_000_comp_003': (1501,), + 'solution_outlet_port_001_comp_000': (1501,), + 'solution_outlet_port_001_comp_001': (1501,), + 'solution_outlet_port_001_comp_002': (1501,), + 'solution_outlet_port_001_comp_003': (1501,), + 'solution_outlet_port_002_comp_000': (1501,), + 'solution_outlet_port_002_comp_001': (1501,), + 'solution_outlet_port_002_comp_002': (1501,), + 'solution_outlet_port_002_comp_003': (1501,), + 'solution_particle': (1501, 10, 3, 4, 4), + 'solution_solid': (1501, 10, 3, 4, 4), + }, + 'solution_unit_001': { + 'last_state_y': (4,), + 'last_state_ydot': (4,), + 'soldot_inlet_port_000_comp_000': (1501,), + 'soldot_inlet_port_000_comp_001': (1501,), + 'soldot_inlet_port_000_comp_002': (1501,), + 'soldot_inlet_port_000_comp_003': (1501,), + 'soldot_outlet_port_000_comp_000': (1501,), + 'soldot_outlet_port_000_comp_001': (1501,), + 'soldot_outlet_port_000_comp_002': (1501,), + 'soldot_outlet_port_000_comp_003': (1501,), + 'solution_inlet_port_000_comp_000': (1501,), + 'solution_inlet_port_000_comp_001': (1501,), + 'solution_inlet_port_000_comp_002': (1501,), + 'solution_inlet_port_000_comp_003': (1501,), + 'solution_outlet_port_000_comp_000': (1501,), + 'solution_outlet_port_000_comp_001': (1501,), + 'solution_outlet_port_000_comp_002': (1501,), + 'solution_outlet_port_000_comp_003': (1501,), + }, + }, +) + +# %% Actual tests + +use_dll = [False, True] +test_cases = [ + grm, + grm_split, + grm_sens, + grm_par_types, + _2dgrm, + _2dgrm_split_ports, + _2dgrm_split_all +] + +@pytest.mark.parametrize("use_dll", use_dll) +@pytest.mark.parametrize("test_case", test_cases) +def test_simulator_options(use_dll, test_case): + model_options = test_case.model_options + solution_recorder_options = test_case.solution_recorder_options + expected_results = test_case.expected_results + + model = run_simulation_with_options( + use_dll, model_options, solution_recorder_options + ) + + assert model.root.output.last_state_y.shape == expected_results['last_state_y'] + assert model.root.output.last_state_ydot.shape == expected_results['last_state_ydot'] + + for key, value in expected_results['coordinates_unit_000'].items(): + assert model.root.output.coordinates.unit_000[key].shape == value + + for key, value in expected_results['solution_unit_000'].items(): + assert model.root.output.solution.unit_000[key].shape == value + + for key, value in expected_results['solution_unit_001'].items(): + assert model.root.output.solution.unit_001[key].shape == value + + if model_options['include_sensitivity']: + for key, value in expected_results['sens_param_000_unit_000'].items(): + assert model.root.output.sensitivity.param_000.unit_000[key].shape == value + + if model_options['include_sensitivity']: + for key, value in expected_results['sens_param_000_unit_001'].items(): + assert model.root.output.sensitivity.param_000.unit_001[key].shape == value + + +if __name__ == "__main__": + pytest.main(["test_dll.py"]) From d5a7eeb1913e17bba59a8493619f148c9a077f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 20:24:29 +0100 Subject: [PATCH 36/43] Fix loading bulk --- cadet/cadet_dll.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index 0fb0870..a228102 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -231,9 +231,9 @@ def _process_data( # Ordering of multi-dimensional arrays, all possible dimensions: # Example: Outlet [nTime, nPort, nComp] - # Bulk [nTime, nRadialCells, nAxialCells, nComp] if 2D model + # Bulk [nTime, nAxialCells, nRadialCells, nComp] if 2D model # Bulk [nTime, nAxialCells, nComp] if 1D model - dimensions = ['nTime', 'nPort', 'nRadialCells', 'nAxialCells', 'nParShells', 'nComp', 'nBound'] + dimensions = ['nTime', 'nPort', 'nAxialCells', 'nRadialCells', 'nParShells', 'nComp', 'nBound'] for dim in dimensions: if dim in call_outputs and call_outputs[dim].value: shape.append(call_outputs[dim].value) From 51f1bc828d5032b20607d5b4f55e998a0af09791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 21:48:16 +0100 Subject: [PATCH 37/43] Fix loading flux --- cadet/cadet_dll.py | 11 ++++++++++- tests/test_dll.py | 32 ++++++++++++++++---------------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index a228102..630ec22 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -233,7 +233,16 @@ def _process_data( # Example: Outlet [nTime, nPort, nComp] # Bulk [nTime, nAxialCells, nRadialCells, nComp] if 2D model # Bulk [nTime, nAxialCells, nComp] if 1D model - dimensions = ['nTime', 'nPort', 'nAxialCells', 'nRadialCells', 'nParShells', 'nComp', 'nBound'] + dimensions = [ + 'nTime', + 'nPort', + 'nAxialCells', + 'nRadialCells', + 'nParTypes', + 'nParShells', + 'nComp', + 'nBound', + ] for dim in dimensions: if dim in call_outputs and call_outputs[dim].value: shape.append(call_outputs[dim].value) diff --git a/tests/test_dll.py b/tests/test_dll.py index 36dd868..e06985d 100644 --- a/tests/test_dll.py +++ b/tests/test_dll.py @@ -362,13 +362,13 @@ def __repr__(self): 'last_state_y': (404,), 'last_state_ydot': (404,), 'soldot_bulk': (1501, 10, 4), - 'soldot_flux': (1501, 1, 10, 4), + 'soldot_flux': (1501, 10, 1, 4), 'soldot_inlet': (1501, 4), 'soldot_outlet': (1501, 4), 'soldot_particle': (1501, 10, 4, 4), 'soldot_solid': (1501, 10, 4, 4), 'solution_bulk': (1501, 10, 4), - 'solution_flux': (1501, 1, 10, 4), + 'solution_flux': (1501, 10, 1, 4), 'solution_inlet': (1501, 4), 'solution_outlet': (1501, 4), 'solution_particle': (1501, 10, 4, 4), @@ -402,7 +402,7 @@ def __repr__(self): 'last_state_y': (404,), 'last_state_ydot': (404,), 'soldot_bulk': (1501, 10, 4), - 'soldot_flux': (1501, 1, 10, 4), + 'soldot_flux': (1501, 10, 1, 4), 'soldot_inlet_port_000_comp_000': (1501,), 'soldot_inlet_port_000_comp_001': (1501,), 'soldot_inlet_port_000_comp_002': (1501,), @@ -414,7 +414,7 @@ def __repr__(self): 'soldot_particle': (1501, 10, 4, 4), 'soldot_solid': (1501, 10, 4, 4), 'solution_bulk': (1501, 10, 4), - 'solution_flux': (1501, 1, 10, 4), + 'solution_flux': (1501, 10, 1, 4), 'solution_inlet_port_000_comp_000': (1501,), 'solution_inlet_port_000_comp_001': (1501,), 'solution_inlet_port_000_comp_002': (1501,), @@ -466,13 +466,13 @@ def __repr__(self): 'last_state_y': (404,), 'last_state_ydot': (404,), 'soldot_bulk': (1501, 10, 4), - 'soldot_flux': (1501, 1, 10, 4), + 'soldot_flux': (1501, 10, 1, 4), 'soldot_inlet': (1501, 4), 'soldot_outlet': (1501, 4), 'soldot_particle': (1501, 10, 4, 4), 'soldot_solid': (1501, 10, 4, 4), 'solution_bulk': (1501, 10, 4), - 'solution_flux': (1501, 1, 10, 4), + 'solution_flux': (1501, 10, 1, 4), 'solution_inlet': (1501, 4), 'solution_outlet': (1501, 4), 'solution_particle': (1501, 10, 4, 4), @@ -488,13 +488,13 @@ def __repr__(self): }, 'sens_param_000_unit_000': { 'sensdot_bulk': (1501, 10, 4), - 'sensdot_flux': (1501, 1, 10, 4), + 'sensdot_flux': (1501, 10, 1, 4), 'sensdot_inlet': (1501, 4), 'sensdot_outlet': (1501, 4), 'sensdot_particle': (1501, 10, 4, 4), 'sensdot_solid': (1501, 10, 4, 4), 'sens_bulk': (1501, 10, 4), - 'sens_flux': (1501, 1, 10, 4), + 'sens_flux': (1501, 10, 1, 4), 'sens_inlet': (1501, 4), 'sens_outlet': (1501, 4), 'sens_particle': (1501, 10, 4, 4), @@ -527,7 +527,7 @@ def __repr__(self): 'last_state_y': (764,), 'last_state_ydot': (764,), 'soldot_bulk': (1501, 10, 4), - 'soldot_flux': (1501, 2, 10, 4), + 'soldot_flux': (1501, 10, 2, 4), 'soldot_inlet': (1501, 4), 'soldot_outlet': (1501, 4), 'soldot_particle_partype_000': (1501, 10, 4, 4), @@ -535,7 +535,7 @@ def __repr__(self): 'soldot_solid_partype_000': (1501, 10, 4, 4), 'soldot_solid_partype_001': (1501, 10, 4, 4), 'solution_bulk': (1501, 10, 4), - 'solution_flux': (1501, 2, 10, 4), + 'solution_flux': (1501, 10, 2, 4), 'solution_inlet': (1501, 4), 'solution_outlet': (1501, 4), 'solution_particle_partype_000': (1501, 10, 4, 4), @@ -571,13 +571,13 @@ def __repr__(self): 'last_state_y': (1212,), 'last_state_ydot': (1212,), 'soldot_bulk': (1501, 10, 3, 4), - 'soldot_flux': (1501, 1, 10, 3, 4), + 'soldot_flux': (1501, 10, 3, 1, 4), 'soldot_inlet': (1501, 3, 4), 'soldot_outlet': (1501, 3, 4), 'soldot_particle': (1501, 10, 3, 4, 4), 'soldot_solid': (1501, 10, 3, 4, 4), 'solution_bulk': (1501, 10, 3, 4), - 'solution_flux': (1501, 1, 10, 3, 4), + 'solution_flux': (1501, 10, 3, 1, 4), 'solution_inlet': (1501, 3, 4), 'solution_outlet': (1501, 3, 4), 'solution_particle': (1501, 10, 3, 4, 4), @@ -612,7 +612,7 @@ def __repr__(self): 'last_state_y': (1212,), 'last_state_ydot': (1212,), 'soldot_bulk': (1501, 10, 3, 4), - 'soldot_flux': (1501, 1, 10, 3, 4), + 'soldot_flux': (1501, 10, 3, 1, 4), 'soldot_inlet_port_000': (1501, 4), 'soldot_inlet_port_001': (1501, 4), 'soldot_inlet_port_002': (1501, 4), @@ -622,7 +622,7 @@ def __repr__(self): 'soldot_particle': (1501, 10, 3, 4, 4), 'soldot_solid': (1501, 10, 3, 4, 4), 'solution_bulk': (1501, 10, 3, 4), - 'solution_flux': (1501, 1, 10, 3, 4), + 'solution_flux': (1501, 10, 3, 1, 4), 'solution_inlet_port_000': (1501, 4), 'solution_inlet_port_001': (1501, 4), 'solution_inlet_port_002': (1501, 4), @@ -661,7 +661,7 @@ def __repr__(self): 'last_state_y': (1212,), 'last_state_ydot': (1212,), 'soldot_bulk': (1501, 10, 3, 4), - 'soldot_flux': (1501, 1, 10, 3, 4), + 'soldot_flux': (1501, 10, 3, 1, 4), 'soldot_inlet_port_000_comp_000': (1501,), 'soldot_inlet_port_000_comp_001': (1501,), 'soldot_inlet_port_000_comp_002': (1501,), @@ -689,7 +689,7 @@ def __repr__(self): 'soldot_particle': (1501, 10, 3, 4, 4), 'soldot_solid': (1501, 10, 3, 4, 4), 'solution_bulk': (1501, 10, 3, 4), - 'solution_flux': (1501, 1, 10, 3, 4), + 'solution_flux': (1501, 10, 3, 1, 4), 'solution_inlet_port_000_comp_000': (1501,), 'solution_inlet_port_000_comp_001': (1501,), 'solution_inlet_port_000_comp_002': (1501,), From 627a40a4ad0d4c7b74a6d32ad6e1cab4c1758c3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 22:24:07 +0100 Subject: [PATCH 38/43] Add test for solution times --- tests/test_dll.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_dll.py b/tests/test_dll.py index e06985d..61b76c4 100644 --- a/tests/test_dll.py +++ b/tests/test_dll.py @@ -358,6 +358,7 @@ def __repr__(self): 'axial_coordinates': (10,), 'particle_coordinates_000': (4,), }, + 'solution_times': (1501,), 'solution_unit_000': { 'last_state_y': (404,), 'last_state_ydot': (404,), @@ -398,6 +399,7 @@ def __repr__(self): 'axial_coordinates': (10,), 'particle_coordinates_000': (4,), }, + 'solution_times': (1501,), 'solution_unit_000': { 'last_state_y': (404,), 'last_state_ydot': (404,), @@ -462,6 +464,7 @@ def __repr__(self): 'axial_coordinates': (10,), 'particle_coordinates_000': (4,), }, + 'solution_times': (1501,), 'solution_unit_000': { 'last_state_y': (404,), 'last_state_ydot': (404,), @@ -523,6 +526,7 @@ def __repr__(self): 'particle_coordinates_000': (4,), 'particle_coordinates_001': (4,), }, + 'solution_times': (1501,), 'solution_unit_000': { 'last_state_y': (764,), 'last_state_ydot': (764,), @@ -567,6 +571,7 @@ def __repr__(self): 'particle_coordinates_000': (4,), 'radial_coordinates': (3,), }, + 'solution_times': (1501,), 'solution_unit_000': { 'last_state_y': (1212,), 'last_state_ydot': (1212,), @@ -608,6 +613,7 @@ def __repr__(self): 'particle_coordinates_000': (4,), 'radial_coordinates': (3,), }, + 'solution_times': (1501,), 'solution_unit_000': { 'last_state_y': (1212,), 'last_state_ydot': (1212,), @@ -657,6 +663,7 @@ def __repr__(self): 'particle_coordinates_000': (4,), 'radial_coordinates': (3,), }, + 'solution_times': (1501,), 'solution_unit_000': { 'last_state_y': (1212,), 'last_state_ydot': (1212,), @@ -770,6 +777,8 @@ def test_simulator_options(use_dll, test_case): for key, value in expected_results['coordinates_unit_000'].items(): assert model.root.output.coordinates.unit_000[key].shape == value + assert model.root.output.solution.solution_times.shape == expected_results['solution_times'] + for key, value in expected_results['solution_unit_000'].items(): assert model.root.output.solution.unit_000[key].shape == value From 495676c5375f9203d2be472d995634166b8d36be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 22:34:29 +0100 Subject: [PATCH 39/43] Remove singleton particle shell dimension --- cadet/cadet_dll.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index 630ec22..3e18bc0 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -252,6 +252,11 @@ def _process_data( return if 'data' in call_outputs: + if 'nParShells' in dims: + nParShells = call_outputs['nParShells'].value + if nParShells == 1: + shape.pop(dims.index('nParShells')) + data = numpy.ctypeslib.as_array(call_outputs['data'], shape=shape) if own_data: data = data.copy() From fd50d9bac9ebac72b8bc20b44a8de7daf1fe8358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Fri, 29 Mar 2024 22:35:16 +0100 Subject: [PATCH 40/43] Add tests for lrm(p) --- tests/test_dll.py | 94 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/tests/test_dll.py b/tests/test_dll.py index 61b76c4..a905a92 100644 --- a/tests/test_dll.py +++ b/tests/test_dll.py @@ -13,7 +13,7 @@ # %% Utility methods -# TODO: Remove once #5 is merged +# TODO: Remove once #14 is merged cadet_root = Path('/home/jo/code/CADET/install/capi/') def setup_model( @@ -283,6 +283,19 @@ def run_simulation_with_options(use_dll, model_options, solution_recorder_option # %% Model templates + +lrm_template = { + 'model': 'LUMPED_RATE_MODEL_WITHOUT_PORES', + 'n_partypes': 1, + 'include_sensitivity': False, +} + +lrmp_template = { + 'model': 'LUMPED_RATE_MODEL_WITH_PORES', + 'n_partypes': 1, + 'include_sensitivity': False, +} + grm_template = { 'model': 'GENERAL_RATE_MODEL', 'n_partypes': 1, @@ -345,6 +358,83 @@ def __repr__(self): f"Case('{self.name}', {self.model_options}, " \ f"{self.solution_recorder_options}, {self.expected_results})" +# %% LRM + +lrm = Case( + name='lrm', + model_options=lrm_template, + solution_recorder_options=no_split_options, + expected_results={ + 'last_state_y': (92,), + 'last_state_ydot': (92,), + 'coordinates_unit_000': { + 'axial_coordinates': (10,), + }, + 'solution_times': (1501,), + 'solution_unit_000': { + 'solution_inlet': (1501, 4), + 'solution_outlet': (1501, 4), + 'solution_bulk': (1501, 10, 4), + 'solution_solid': (1501, 10, 4), + 'soldot_inlet': (1501, 4), + 'soldot_outlet': (1501, 4), + 'soldot_bulk': (1501, 10, 4), + 'soldot_solid': (1501, 10, 4), + 'last_state_y': (84,), + 'last_state_ydot': (84,), + }, + 'solution_unit_001': { + 'solution_inlet': (1501, 4), + 'solution_outlet': (1501, 4), + 'soldot_inlet': (1501, 4), + 'soldot_outlet': (1501, 4), + 'last_state_y': (4,), + 'last_state_ydot': (4,), + }, + }, +) + +# %% LRMP + +lrmp = Case( + name='lrmp', + model_options=lrmp_template, + solution_recorder_options=no_split_options, + expected_results={ + 'last_state_y': (172,), + 'last_state_ydot': (172,), + 'coordinates_unit_000': { + 'axial_coordinates': (10,), + 'particle_coordinates_000': (1,), + }, + 'solution_times': (1501,), + 'solution_unit_000': { + 'last_state_y': (164,), + 'last_state_ydot': (164,), + 'soldot_bulk': (1501, 10, 4), + 'soldot_flux': (1501, 10, 1, 4), + 'soldot_inlet': (1501, 4), + 'soldot_outlet': (1501, 4), + 'soldot_particle': (1501, 10, 4), + 'soldot_solid': (1501, 10, 4), + 'solution_bulk': (1501, 10, 4), + 'solution_flux': (1501, 10, 1, 4), + 'solution_inlet': (1501, 4), + 'solution_outlet': (1501, 4), + 'solution_particle': (1501, 10, 4), + 'solution_solid': (1501, 10, 4), + }, + 'solution_unit_001': { + 'last_state_y': (4,), + 'last_state_ydot': (4,), + 'soldot_inlet': (1501, 4), + 'soldot_outlet': (1501, 4), + 'solution_inlet': (1501, 4), + 'solution_outlet': (1501, 4), + }, + }, +) + # %% GRM grm = Case( @@ -751,6 +841,8 @@ def __repr__(self): use_dll = [False, True] test_cases = [ + lrm, + lrmp, grm, grm_split, grm_sens, From 98f5840ad07bb7fa02def56180ae7e93145dd613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Thu, 18 Apr 2024 08:48:34 +0200 Subject: [PATCH 41/43] Add tests for Cstr --- tests/test_dll.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/test_dll.py b/tests/test_dll.py index a905a92..0042886 100644 --- a/tests/test_dll.py +++ b/tests/test_dll.py @@ -284,6 +284,12 @@ def run_simulation_with_options(use_dll, model_options, solution_recorder_option # %% Model templates +cstr_template = { + 'model': 'CSTR', + 'n_partypes': 1, + 'include_sensitivity': False, +} + lrm_template = { 'model': 'LUMPED_RATE_MODEL_WITHOUT_PORES', 'n_partypes': 1, @@ -358,6 +364,45 @@ def __repr__(self): f"Case('{self.name}', {self.model_options}, " \ f"{self.solution_recorder_options}, {self.expected_results})" +# %% CSTR + +cstr = Case( + name='cstr', + model_options=cstr_template, + solution_recorder_options=no_split_options, + expected_results={ + 'last_state_y': (21,), + 'last_state_ydot': (21,), + 'coordinates_unit_000': { + 'axial_coordinates': (1,), + 'particle_coordinates_000': (1,), + }, + 'solution_times': (1501,), + 'solution_unit_000': { + 'solution_inlet': (1501, 4), + 'solution_outlet': (1501, 4), + 'solution_bulk': (1501, 4), + 'solution_solid': (1501, 4), + 'solution_volume': (1501, 1), + 'soldot_inlet': (1501, 4), + 'soldot_outlet': (1501, 4), + 'soldot_bulk': (1501, 4), + 'soldot_solid': (1501, 4), + 'soldot_volume': (1501, 1), + 'last_state_y': (13,), + 'last_state_ydot': (13,), + }, + 'solution_unit_001': { + 'solution_inlet': (1501, 4), + 'solution_outlet': (1501, 4), + 'soldot_inlet': (1501, 4), + 'soldot_outlet': (1501, 4), + 'last_state_y': (4,), + 'last_state_ydot': (4,), + }, + }, +) + # %% LRM lrm = Case( @@ -841,6 +886,7 @@ def __repr__(self): use_dll = [False, True] test_cases = [ + cstr, lrm, lrmp, grm, From fcf79e7374f3d558d4d7f6cb540d20f70d1aca64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Thu, 18 Apr 2024 09:52:20 +0200 Subject: [PATCH 42/43] Formatting --- tests/test_dll.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_dll.py b/tests/test_dll.py index 0042886..ff992dc 100644 --- a/tests/test_dll.py +++ b/tests/test_dll.py @@ -94,7 +94,7 @@ def setup_model( create_lwe_path = Path(cadet_root) / 'bin' / executable - args =[ + args = [ create_lwe_path.as_posix(), f'--out {file_name}', f'--unit {model}', From 466cc4bea4285864b19b49eb0168ce36f7069dc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Schm=C3=B6lder?= Date: Sat, 20 Apr 2024 09:24:44 +0200 Subject: [PATCH 43/43] Update ordering of multi-dimensional data Fixes issue with flux --- cadet/cadet_dll.py | 13 +++++++------ tests/test_dll.py | 36 ++++++++++++++++++------------------ 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/cadet/cadet_dll.py b/cadet/cadet_dll.py index 3e18bc0..ae1c36c 100644 --- a/cadet/cadet_dll.py +++ b/cadet/cadet_dll.py @@ -230,15 +230,16 @@ def _process_data( dims = [] # Ordering of multi-dimensional arrays, all possible dimensions: - # Example: Outlet [nTime, nPort, nComp] - # Bulk [nTime, nAxialCells, nRadialCells, nComp] if 2D model - # Bulk [nTime, nAxialCells, nComp] if 1D model + # bulk: 'nTime', ('nAxialCells',) ('nRadialCells' / 'nPorts',) 'nComp' + # particle_liquid: 'nTime', ('nParTypes',) ('nAxialCells',) ('nRadialCells' / 'nPorts',) ('nParShells',) 'nComp' + # particle_solid: 'nTime', ('nParTypes',) ('nAxialCells',) ('nRadialCells' / 'nPorts',) ('nParShells',) 'nComp', 'nBound' + # flux: 'nTime', ('nParTypes',) ('nAxialCells',) ('nRadialCells' / 'nPorts',) 'nComp' dimensions = [ 'nTime', - 'nPort', + 'nParTypes', 'nAxialCells', + 'nPort', 'nRadialCells', - 'nParTypes', 'nParShells', 'nComp', 'nBound', @@ -252,7 +253,7 @@ def _process_data( return if 'data' in call_outputs: - if 'nParShells' in dims: + if 'nParShells' in dims: nParShells = call_outputs['nParShells'].value if nParShells == 1: shape.pop(dims.index('nParShells')) diff --git a/tests/test_dll.py b/tests/test_dll.py index ff992dc..8b689e3 100644 --- a/tests/test_dll.py +++ b/tests/test_dll.py @@ -457,13 +457,13 @@ def __repr__(self): 'last_state_y': (164,), 'last_state_ydot': (164,), 'soldot_bulk': (1501, 10, 4), - 'soldot_flux': (1501, 10, 1, 4), + 'soldot_flux': (1501, 1, 10, 4), 'soldot_inlet': (1501, 4), 'soldot_outlet': (1501, 4), 'soldot_particle': (1501, 10, 4), 'soldot_solid': (1501, 10, 4), 'solution_bulk': (1501, 10, 4), - 'solution_flux': (1501, 10, 1, 4), + 'solution_flux': (1501, 1, 10, 4), 'solution_inlet': (1501, 4), 'solution_outlet': (1501, 4), 'solution_particle': (1501, 10, 4), @@ -498,13 +498,13 @@ def __repr__(self): 'last_state_y': (404,), 'last_state_ydot': (404,), 'soldot_bulk': (1501, 10, 4), - 'soldot_flux': (1501, 10, 1, 4), + 'soldot_flux': (1501, 1, 10, 4), 'soldot_inlet': (1501, 4), 'soldot_outlet': (1501, 4), 'soldot_particle': (1501, 10, 4, 4), 'soldot_solid': (1501, 10, 4, 4), 'solution_bulk': (1501, 10, 4), - 'solution_flux': (1501, 10, 1, 4), + 'solution_flux': (1501, 1, 10, 4), 'solution_inlet': (1501, 4), 'solution_outlet': (1501, 4), 'solution_particle': (1501, 10, 4, 4), @@ -539,7 +539,7 @@ def __repr__(self): 'last_state_y': (404,), 'last_state_ydot': (404,), 'soldot_bulk': (1501, 10, 4), - 'soldot_flux': (1501, 10, 1, 4), + 'soldot_flux': (1501, 1, 10, 4), 'soldot_inlet_port_000_comp_000': (1501,), 'soldot_inlet_port_000_comp_001': (1501,), 'soldot_inlet_port_000_comp_002': (1501,), @@ -551,7 +551,7 @@ def __repr__(self): 'soldot_particle': (1501, 10, 4, 4), 'soldot_solid': (1501, 10, 4, 4), 'solution_bulk': (1501, 10, 4), - 'solution_flux': (1501, 10, 1, 4), + 'solution_flux': (1501, 1, 10, 4), 'solution_inlet_port_000_comp_000': (1501,), 'solution_inlet_port_000_comp_001': (1501,), 'solution_inlet_port_000_comp_002': (1501,), @@ -604,13 +604,13 @@ def __repr__(self): 'last_state_y': (404,), 'last_state_ydot': (404,), 'soldot_bulk': (1501, 10, 4), - 'soldot_flux': (1501, 10, 1, 4), + 'soldot_flux': (1501, 1, 10, 4), 'soldot_inlet': (1501, 4), 'soldot_outlet': (1501, 4), 'soldot_particle': (1501, 10, 4, 4), 'soldot_solid': (1501, 10, 4, 4), 'solution_bulk': (1501, 10, 4), - 'solution_flux': (1501, 10, 1, 4), + 'solution_flux': (1501, 1, 10, 4), 'solution_inlet': (1501, 4), 'solution_outlet': (1501, 4), 'solution_particle': (1501, 10, 4, 4), @@ -626,13 +626,13 @@ def __repr__(self): }, 'sens_param_000_unit_000': { 'sensdot_bulk': (1501, 10, 4), - 'sensdot_flux': (1501, 10, 1, 4), + 'sensdot_flux': (1501, 1, 10, 4), 'sensdot_inlet': (1501, 4), 'sensdot_outlet': (1501, 4), 'sensdot_particle': (1501, 10, 4, 4), 'sensdot_solid': (1501, 10, 4, 4), 'sens_bulk': (1501, 10, 4), - 'sens_flux': (1501, 10, 1, 4), + 'sens_flux': (1501, 1, 10, 4), 'sens_inlet': (1501, 4), 'sens_outlet': (1501, 4), 'sens_particle': (1501, 10, 4, 4), @@ -666,7 +666,7 @@ def __repr__(self): 'last_state_y': (764,), 'last_state_ydot': (764,), 'soldot_bulk': (1501, 10, 4), - 'soldot_flux': (1501, 10, 2, 4), + 'soldot_flux': (1501, 2, 10, 4), 'soldot_inlet': (1501, 4), 'soldot_outlet': (1501, 4), 'soldot_particle_partype_000': (1501, 10, 4, 4), @@ -674,7 +674,7 @@ def __repr__(self): 'soldot_solid_partype_000': (1501, 10, 4, 4), 'soldot_solid_partype_001': (1501, 10, 4, 4), 'solution_bulk': (1501, 10, 4), - 'solution_flux': (1501, 10, 2, 4), + 'solution_flux': (1501, 2, 10, 4), 'solution_inlet': (1501, 4), 'solution_outlet': (1501, 4), 'solution_particle_partype_000': (1501, 10, 4, 4), @@ -711,13 +711,13 @@ def __repr__(self): 'last_state_y': (1212,), 'last_state_ydot': (1212,), 'soldot_bulk': (1501, 10, 3, 4), - 'soldot_flux': (1501, 10, 3, 1, 4), + 'soldot_flux': (1501, 1, 10, 3, 4), 'soldot_inlet': (1501, 3, 4), 'soldot_outlet': (1501, 3, 4), 'soldot_particle': (1501, 10, 3, 4, 4), 'soldot_solid': (1501, 10, 3, 4, 4), 'solution_bulk': (1501, 10, 3, 4), - 'solution_flux': (1501, 10, 3, 1, 4), + 'solution_flux': (1501, 1, 10, 3, 4), 'solution_inlet': (1501, 3, 4), 'solution_outlet': (1501, 3, 4), 'solution_particle': (1501, 10, 3, 4, 4), @@ -753,7 +753,7 @@ def __repr__(self): 'last_state_y': (1212,), 'last_state_ydot': (1212,), 'soldot_bulk': (1501, 10, 3, 4), - 'soldot_flux': (1501, 10, 3, 1, 4), + 'soldot_flux': (1501, 1, 10, 3, 4), 'soldot_inlet_port_000': (1501, 4), 'soldot_inlet_port_001': (1501, 4), 'soldot_inlet_port_002': (1501, 4), @@ -763,7 +763,7 @@ def __repr__(self): 'soldot_particle': (1501, 10, 3, 4, 4), 'soldot_solid': (1501, 10, 3, 4, 4), 'solution_bulk': (1501, 10, 3, 4), - 'solution_flux': (1501, 10, 3, 1, 4), + 'solution_flux': (1501, 1, 10, 3, 4), 'solution_inlet_port_000': (1501, 4), 'solution_inlet_port_001': (1501, 4), 'solution_inlet_port_002': (1501, 4), @@ -803,7 +803,7 @@ def __repr__(self): 'last_state_y': (1212,), 'last_state_ydot': (1212,), 'soldot_bulk': (1501, 10, 3, 4), - 'soldot_flux': (1501, 10, 3, 1, 4), + 'soldot_flux': (1501, 1, 10, 3, 4), 'soldot_inlet_port_000_comp_000': (1501,), 'soldot_inlet_port_000_comp_001': (1501,), 'soldot_inlet_port_000_comp_002': (1501,), @@ -831,7 +831,7 @@ def __repr__(self): 'soldot_particle': (1501, 10, 3, 4, 4), 'soldot_solid': (1501, 10, 3, 4, 4), 'solution_bulk': (1501, 10, 3, 4), - 'solution_flux': (1501, 10, 3, 1, 4), + 'solution_flux': (1501, 1, 10, 3, 4), 'solution_inlet_port_000_comp_000': (1501,), 'solution_inlet_port_000_comp_001': (1501,), 'solution_inlet_port_000_comp_002': (1501,),