diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 00000000..62738de1 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# PR #134 (Apply and enforce Black formatting) +6561c4fc1968ef38bdd6221f985f421fe024c21f diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 1c7213b5..0151406d 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -34,7 +34,7 @@ concurrency: env: # default Python version to use for checks that do not require multiple versions - DEFAULT_PYTHON_VERSION: '3.8' + DEFAULT_PYTHON_VERSION: '3.10' IDAES_CONDA_ENV_NAME_DEV: idaes-examples-dev PYTEST_ADDOPTS: "--color=yes" @@ -44,17 +44,45 @@ defaults: shell: bash -l {0} jobs: + + code-formatting: + name: Check code formatting (Black) + # OS and/or Python version don't make a difference, so we choose ubuntu and 3.10 for performance + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ env.DEFAULT_PYTHON_VERSION }} + - name: Install Black + run: | + pip install --progress-bar off black[jupyter]==24.3.0 + - name: Run Black to verify that the committed code is formatted + run: | + black --check . + + spell-check: + name: Check Spelling + runs-on: ubuntu-latest + steps: + - name: Checkout source + uses: actions/checkout@v3 + - name: Run Spell Checker + uses: crate-ci/typos@master + pytest: # description: Run pytest with dev dependencies name: pytest (py${{ matrix.python-version }}/${{ matrix.os }}) runs-on: ${{ matrix.runner-image }} + needs: [code-formatting, spell-check] strategy: fail-fast: false matrix: python-version: - - '3.8' - '3.9' - '3.10' + - '3.11' + - '3.12' os: - linux - win64 @@ -74,6 +102,9 @@ jobs: uses: ./.github/actions/install with: install-target: -r requirements-dev.txt + - name: Run pytest (repo root) + run: | + pytest -v . - name: Run pytest run: | pwd diff --git a/.gitignore b/.gitignore index 357127f9..413dbf03 100644 --- a/.gitignore +++ b/.gitignore @@ -143,6 +143,7 @@ idaes_examples/notebooks/**/pysmo/*.pickle idaes_examples/notebooks/**/omlt/keras_surrogate/*.pb idaes_examples/notebooks/**/omlt/keras_surrogate/*.pdf idaes_examples/notebooks/**/omlt/*.pdf +idaes_examples/notebooks/docs/power_gen/solid_oxide_cell/soc_dynamic_flowsheet.svg alamo_run.alm idaes_examples/notebooks/docs/tut/sin_data.csv *.pb diff --git a/.readthedocs.yml b/.readthedocs.yml index d0624c56..017e3d60 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -3,11 +3,11 @@ version: 2 build: os: ubuntu-20.04 tools: - python: "3.8" + python: "3.10" jobs: pre_build: # Set conf vars and update the Sphinx configuration (conf.py) - - idaesx conf --execute cache --timeout 600 --sphinx --show + - idaesx conf --execute off --sphinx --show python: install: - requirements: requirements-dev.txt diff --git a/README-developer.md b/README-developer.md index 72c567d2..0ab7bb68 100644 --- a/README-developer.md +++ b/README-developer.md @@ -2,34 +2,16 @@ This file provides details needed by developers to properly create and add new example notebooks to this repository. -**Table of Contents** -* Installation -* Add a new example -* File layout -* Running tests -* Building documentation -* Preprocessing -* Copyright headers -* Notebook names -* How to create an example - * Jupyter Notebook file extensions - * Jupyter Notebook header - * Jupyter Notebook cell tags - * Jupyter notebook metadata -* Packaging - -**Quickstart**, skip to sections: -* [Installation](#installation) -* [Running tests → Integration tests](#integration-tests) -* [Building documentation](#building-documentation) - +**Contents:** -**Quickstart**, skip to sections: * [Installation](#installation) -* [Add a new example](#add-a-new-example) -* [Running tests → Integration tests](#integration-tests) -* [Building documentation](#building-documentation) - +* [How to add a new notebook example](#how-to-add-a-new-notebook-example) +* [File layout](#file-layout) +* [Running tests](#running-tests) +* [Building and preprocessing](#building-and-preprocessing) +* [Notebook names](#notebook-names) +* [Copyright headers](#copyright-headers) +* [Packaging for PyPI](#packaging-for-pypi) ## Installation @@ -37,26 +19,36 @@ Clone the repository from GitHub, set up your Python environment as you usually ```shell # to create a conda environment first: -# conda create -n idaes-examples python=3.10; conda activate idaes-examples +# conda create --yes --name idaes-examples python=3.10 && conda activate idaes-examples pip install -r requirements-dev.txt ``` +Note: if you have IDAES installed in your current environment, it will uninstall it and install the latest version from the main branch on Github. You can run `pip uninstall idaes` and reinstall it from your local repository if you need to test examples against a local branch of IDAES. + The configuration of the installation is stored in `pyproject.toml`. -## Add a new example +## How to add a new notebook example + +Examples are currently all [Jupyter](https://jupyter.org) Notebooks. This section goes through the main steps to creating a new notebook example. For more details about naming and layout, see the reference sections on [File layout](#file-layout) and [Notebook names](#notebook-names), below. In particular, it is recommended to read the section on [Jupyter Notebook cell tags](#jupyter-notebook-cell-tags), found under the Notebook names section; these are used for testing, to handle the "solution" cells in a tutorial, etc. + +First, pick the directory in which to add the notebook. See the [standards][standards] to figure out the parent directory for the notebook -- usually, it's *notebooks/docs*. The examples are organized into sections. This affects how the notebook will appear in the overall navigation of the documentation when it is published. -Note: Below, `notebooks/*` refers to the directory `idaes_examples/notebooks`. +Next, you need to choose whether to put your new notebook in an existing section or to create a new section. -If you want to add an example in an **existing section** of `notebooks/docs`, you can run -`idaesx new` to get a guided terminal-based UI that will create a skeleton of the -new notebook and add it to the table of contents, and also add all the variations of the notebook (see [Notebook Names](#notebook-names)) and, if git is enabled and found, add and commit them to git. -Then you just need to edit your notebook. +- Use existing section: Simply navigate there before the next step. -If you need to create a **new section** in `notebooks/docs` or `notebooks/held`: -- add the appropriate subdirectory, e.g. `notebooks/docs/fantastic` -- add a section into `notebooks/_toc.yml`, imitating an existing entry -- create and populate a `notebooks/docs/fantastic/index.md` file -- now you can add your notebook(s) manually, e.g. `notebooks/docs/fantastic/my_notebook.ipynb`, or use the `idaesx new` command +- Create a new section: in `notebooks/docs` or `notebooks/held`, create a new subdirectory. The directory name *should* be in lowercase with underscores between words. Also create and populate an *index.md* file, e.g., `notebooks/docs/fantastic/index.md` file. This should describe the section and notebooks in it. + +Next, choose to use the *idaesx new* command to create a new blank notebook or to copy/edit an existing notebook yourself. In either case, the notebook filename should be in lowercase with underscores. For example: *my_notebook*. + +- If you are adding your notebook using the `idaesx new` command, you need to just pick a section and name for the notebook. The script will modify the `notebooks/_toc.yml` and create a blank notebook to get started. +- If you are adding your notebook manually, copy or create the notebook file, e.g., `notebooks/docs/fantastic/my_notebook.ipynb`. Make sure you add the *.ipynb* extension. You also need to add a "file" entry in `notebooks/_toc.yml` , as a new item in the list under the appropriate section, with "_doc" appended to the name and without the extension, e.g. `- file: docs/fantastic/my_notebook_doc`. + +If the new notebook needs any data files or images, add these in the same directory as the notebook. Remember to add these to Git as well. + +If your new notebook needs additional Python modules, these should be put under the `mod/` directory in an appropriate place, usually a directory name that matches the notebook's subdirectory under `docs/`. For example, our notebook in `notebooks/docs/fantastic/my_notebook.ipynb` could have an additional module `mod/fantastic/util.py`, which will be imported: `from idaes_examples.mod.fantastic import util`. + +Finally, you will test the new notebook and build it locally before adding, committing, and pushing the new files. See the section on [running tests](#running-tests), below. ## File layout @@ -82,13 +74,15 @@ Which one you run depends in which directory you run tests. If your current directory is the root of the repository: -1. `pytest .`: Runs **Python test modules**, matching the usual patterns (e.g., `*_test.py`). +1. `pytest .`: Runs **Python test modules**, matching the usual patterns (e.g., `test_*.py`). 2. `pytest idaes_examples`: Runs **Jupyter notebook tests.** Due to the presence of a special `conftest.py` file in this directory, Jupyter Notebooks will be preprocessed and then all test notebooks (their filename ending in `_test.ipynb`) will be executed. -### Integration tests +The `-v` or `--verbose` flag can be added to any pytest command so that more information is displayed while the test suite runs. + +### Testing the notebooks -Run integration tests from the top-level (root) directory of the repository. -In the root directory, tests are configured by `pyproject.toml`; see the *tool.pytest.ini_options* section. +To run all registered notebooks, run the following command from the top-level (root) directory of the repository, specifying `idaes_examples` as an argument. +The `pytest.ini` file and `conftest.py` files contained in `idaes_examples` will override the top-level pytest configuration defined in `pyproject.toml` under `[tool.pytest.ini_options]`. ```shell # from the root directory of the repository @@ -99,8 +93,7 @@ pytest idaes_examples To test just one notebook, you need to use the name of the *test* notebook not the source. For example, to test the `compressor.ipynb` notebook (in the `unit_models/operations` subdirectory) -you need to add `-c` and the path to the top-level *pyproject.toml*, which has the pytest configuration, -then use the name of the test notebook: +you need to use the name of the test notebook: ```shell pytest -v idaes_examples/notebooks/docs/unit_models/operations/compressor_test.ipynb @@ -123,7 +116,9 @@ pytest -v idaes_examples -k operations # docs/unit_models/operations/turbine_test.ipynb ``` -## Building documentation +## Building and preprocessing + +### Building documentation **Note:** Building the documentation runs all the notebooks. This is very slow, so is not an operation that a developer should perform during their regular workflow. @@ -139,7 +134,7 @@ idaesx build The output will be in *idaes_examples/nb/_build/html*. As a convenience, you can open that HTML file with the command `idaesx view`. -## Preprocessing notebooks +### Preprocessing notebooks Preprocessing creates separate copies of the Jupyter notebooks that are used for tests, tutorial exercise and solution, and documentation (see [Notebook Names](#notebook-names)). These (derived) notebooks are also committed and saved in Git. @@ -185,27 +180,7 @@ When creating or modifying notebooks, you should always use the version with no Other extensions are automatically generated when running tests, building the documentation, and manually running the preprocessing step. See the table of notebook types for details. -## How to add a new notebook - -There are two main steps to creating a new notebook example. - -1. Add Jupyter Notebook and supporting files - 1. See the [standards][standards] to figure out the parent directory for the notebook -- usually, it's *docs*. - 2. Put the notebook in the appropriate subdirectory. - If you create a new directory for the notebook, the directory name *should* be in lowercase - with underscores between words. For example: '*docs/machine_learning*'. - 3. Notebook filename *should* be in lowercase with underscores and ***must*** end with '.ipynb'. For example: - 'my_example.ipynb'. - 4. Add -- in the same directory as the notebook -- any data files or images it needs. - 5. Additional Python modules should be put in an appropriate place under *idaes_examples/mod*. - Then your notebook can write: `from idaes_examples.mod. import ` -2. Add Jupyter notebook to the Jupyterbook table of contents in *idaes_examples/notebooks/_toc.yml*. - 1. The notebook will be a *section*. If you added a new directory, you will create a new *chapter*, otherwise it will go under an existing one. See [Jupyterbook][jb] documentation for more details. - 2. Refer to the notebook as '*path/to/notebook-name*_doc' (add the '_doc' and drop the '.ipynb' extension). For example: 'machine_learning/my_example_doc'. - 3. If you created a new directory for this notebook, make sure you add an *index.md* file to it. See other *index.md* files for the expected format. -You *should* test the new notebook and build it locally before pushing the new file, i.e., run `pytest` and `idaesx build`. -Note that the cache for the documentation will be updated to contain the new output cells, which will modify files in *idaes_examples/notebooks/nbcache*; these files should also be committed and pushed. ### Jupyter Notebook file extensions @@ -303,7 +278,8 @@ The output will be in `_dev/notebooks/_build/html`. In addition to per-cell tags, the preprocessor also can look at notebook-level metadata. This is currently used for only one purpose: to tell the preprocessor not to generate a 'test' notebook, and thereby to skip the given notebook in the tests. In order to make this happen, either manually edit the notebook source or use the Jupyter notebook "Edit -> Edit Notebook Metadata" menu item to add the following section to the notebook-level metadata: -``` + +```json "idaes": { "skip": ["test"] } @@ -323,22 +299,25 @@ addheader -c addheader.yml All existing notebooks and Python files will be automatically discovered and modified as needed. -## Packaging +## Packaging for PyPI Instructions to package and distribute the examples as idaes-examples in PyPI. Based on the PyPA [packaging projects](https://packaging.python.org/en/latest/tutorials/packaging-projects/) documentation. Install dependencies for packaging into your current (virtual) Python environment: + ```shell -pip install -e .[dev,jb,pkg] +pip install -e .[dev,packaging] ``` Edit the `pyproject.toml` file: - 1. Ensure that you have commented out the line under `[project.optional-dependencies]`, in the `dev` section, + +1. Ensure that you have commented out the line under `[project.optional-dependencies]`, in the `dev` section, that reads `"idaes-pse @ git+https://github.com/IDAES/idaes-pse"`. - 2. Set the release version. You should increment the version for each new release. +2. Set the release version. You should increment the version for each new release. **Build** the distribution: + ```shell > python -m build # Many lines of output later, you should see a message like: @@ -352,6 +331,7 @@ To generate an API token, go to _Settings_ → _API Tokens_, and selecting _A You will paste this token in the commands below. **Upload** to [TestPyPI](https://packaging.python.org/en/latest/guides/using-testpypi/): + ```shell > python -m twine upload --repository testpypi dist/* Uploading distributions to https://test.pypi.org/legacy/ @@ -360,17 +340,20 @@ Enter your password: {{paste token here}} ``` Create a new virtual environment and install the package from test.pypi into it: + ```shell pip install --extra-index-url https://test.pypi.org/simple/ idaes-examples ``` -If the installation succeeds, you should be able to browse the notebooks using the built-in GUI: +If the installation succeeds, you should be able to serve the notebooks: + ```shell -idaesx gui +idaesx serve ``` If it all looks good, you can repeat the **Upload** step with the real [PyPI](pypi.org) (you will need to get an account and token, just as for test.pypi.org, above): + ```shell > python -m twine upload dist/* Uploading distributions to https://upload.pypi.org/legacy/ @@ -388,4 +371,4 @@ Enter your password: {{past token here}} ---- Author: Dan Gunter -Last modified: 13 Mar 2023 +Last modified: 25 Apr 2024 diff --git a/README.md b/README.md index 5cab0de1..2050a9c7 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ Below are basic instructions to install, view, and run the examples. In the source code repository, you may note that there are a number of examples that are not in the documentation. There are two main categories of examples: - - "Docs" examples (under `idaes_examples/notebooks/docs`), which are tested and built into this documentation. - - "Active" examples (under `idaes_examples/notebooks/active`) that are tested but *not* in the documentation. +- "Docs" examples (under `idaes_examples/notebooks/docs`), which are tested and built into this documentation. +- "Active" examples (under `idaes_examples/notebooks/active`) that are tested but *not* in the documentation. There is also a third category of "Held" examples (under `idaes_examples/notebooks/held`), which could in the next release of IDAES in Docs or Active, or could be removed. @@ -30,9 +30,15 @@ These are *not* tested and *not* in the docs, and should generally be ignored by This repository can be installed with *pip*: ```shell -# RECOMMENDED: this will install the IDEAS examples, accessory code, -# plus the Graphical User Interface (GUI) to browse them (see section below) -pip install "idaes-examples[gui]" +# install the IDAES examples with a core set of dependencies +pip install idaes-examples + +# install the IDAES examples with additional dependencies needed to run specific examples, +# e.g. `omlt` for surrogate modeling with OMLT +pip install "idaes-examples[omlt]" + +# install the IDAES examples with dependencies need to build the documentation +pip install "idaes-examples[docs]" ``` We recommend you use a virtual environment tool such as @@ -41,32 +47,27 @@ to install and run the notebooks in an isolated environment. ## Run examples -Use the command +Use the command ``` -idaesx gui +idaesx serve ``` -to get a simple graphical UI that lets you -browse and open notebooks (with Jupyter) for local execution and experimentation. -The GUI will show the description of each notebook and allow selection of tutorial or exercise versions of the notebook, if these exist. +to start a Jupyter server to browser and open the notebooks for local execution and experimentation. Alternately, you may use Jupyter notebook's file browser in the installed notebooks directory, using the `idaesx where` command to find that directory: `jupyter notebook $(idaesx where)`. -Only the source notebooks (ending in '_src.ipynb') are included in the repository. -The `idaesx gui` command will generate the other versions, or you can run preprocessing manually with: `idaesx pre -d "$(idaesx where)\.."`. - +Only the source notebooks (ending in `_src.ipynb`) are included in the repository. +The `idaesx serve` command will generate the other versions, or you can run preprocessing manually with: `idaesx pre -d "$(idaesx where)\.."`. ## Build documentation Run the command `idaesx build` from the repository root to build the [JupyterBook](https://jupyterbook.org) documentation. - *Note: This will take quite a while, as each example must be run first. You may want to step out and enjoy a beverage.* - ---- Author: Dan Gunter -Last modified: 17 Feb 2023 +Last modified: 25 Apr 2024 diff --git a/idaes_examples/archive/data_reconciliation/boiler_data_rec.py b/idaes_examples/archive/data_reconciliation/boiler_data_rec.py index 455c7681..b84fdbeb 100644 --- a/idaes_examples/archive/data_reconciliation/boiler_data_rec.py +++ b/idaes_examples/archive/data_reconciliation/boiler_data_rec.py @@ -56,17 +56,18 @@ from idaes.core.util.model_statistics import degrees_of_freedom import idaes.core.util.scaling as iscale + def deactivate_performance_constraints(m): for blk in m.fs.component_objects(pyo.Block, descend_into=False): try: Uval = value(blk.overall_heat_transfer_coefficient[0]) Dps = value(blk.deltaP_shell[0]) Dpt = value(blk.deltaP_tube[0]) - blk.overall_heat_transfer_coefficient[0].setlb(Uval*0.25) - blk.overall_heat_transfer_coefficient[0].setub(Uval*2.5) - blk.deltaP_shell.setlb(Dps*10) + blk.overall_heat_transfer_coefficient[0].setlb(Uval * 0.25) + blk.overall_heat_transfer_coefficient[0].setub(Uval * 2.5) + blk.deltaP_shell.setlb(Dps * 10) blk.deltaP_shell.setub(0) - blk.deltaP_tube.setlb(Dpt*10) + blk.deltaP_tube.setlb(Dpt * 10) blk.deltaP_tube.setub(0) blk.overall_heat_transfer_coefficient_eqn.deactivate() @@ -98,15 +99,15 @@ def deactivate_performance_constraints(m): blk.gas_emissivity_eqn.deactivate() except AttributeError: pass - print('degrees of freedom = ' + str(degrees_of_freedom(m))) + print("degrees of freedom = " + str(degrees_of_freedom(m))) def boiler_flowsheet(): """ Make the flowsheet object, fix some variables, and solve the problem """ - import idaes.models_extra.power_generation.flowsheets.\ - supercritical_power_plant.boiler_subflowsheet_build as blr + import idaes.models_extra.power_generation.flowsheets.supercritical_power_plant.boiler_subflowsheet_build as blr + # First, we import the boler model from boiler_subflowsheet_build m, solver = blr.main() # initialize boiler flowsheet @@ -123,92 +124,121 @@ def boiler_flowsheet(): # Therefore, in the new model, these variables will change with plant load, # More details regarding this methodology can be found in Zamarripa et al., # FOCAPD 2 page paper included in this directory as FOCAPD_paper.pdf - m.fs.SR = pyo.Var(initialize=1.15, doc='stoichiometric ratio') + m.fs.SR = pyo.Var(initialize=1.15, doc="stoichiometric ratio") m.fs.SR.setlb(1.0) m.fs.SR.setub(1.25) m.fs.SR.fix(1.15) - m.fs.coal_flow = pyo.Var(initialize=54.01, doc='coal flowrate kg.s') - m.fs.FGET = pyo.Var(initialize=1300.00, doc='flue gas exit temperature K') - m.fs.FG_flow = pyo.Var(m.fs.prop_fluegas.component_list, - initialize=2000, - domain=pyo.Reals, doc='component molar flow rate') - init_fg = {"H2O": (8.69/100), "CO2": (14.49/100), - "N2": (0.6999), "O2": (2.47/100), "NO": 0.0006, "SO2": (0.002)} - m.fs.fg_frac = pyo.Var(m.fs.prop_fluegas.component_list, - initialize=init_fg, - doc='flue gas molar fraction') + m.fs.coal_flow = pyo.Var(initialize=54.01, doc="coal flowrate kg.s") + m.fs.FGET = pyo.Var(initialize=1300.00, doc="flue gas exit temperature K") + m.fs.FG_flow = pyo.Var( + m.fs.prop_fluegas.component_list, + initialize=2000, + domain=pyo.Reals, + doc="component molar flow rate", + ) + init_fg = { + "H2O": (8.69 / 100), + "CO2": (14.49 / 100), + "N2": (0.6999), + "O2": (2.47 / 100), + "NO": 0.0006, + "SO2": (0.002), + } + m.fs.fg_frac = pyo.Var( + m.fs.prop_fluegas.component_list, + initialize=init_fg, + doc="flue gas molar fraction", + ) # surrogate models valid for coal flowrate (30 to 70 kg/s) and SR (1-2.5) def fg_mol_frac_rule(b, i): - if i == 'CO2': - return m.fs.fg_frac[i] == 0.23235491508307534735955\ - * m.fs.SR**-0.5 - elif i == 'H2O': - return m.fs.fg_frac[i] == 0.45009703293861530459807E-001 \ - * m.fs.SR - elif i == 'N2': - return m.fs.fg_frac[i] == 1 - (m.fs.fg_frac['CO2'] - + m.fs.fg_frac['H2O'] - + m.fs.fg_frac['NO'] - + m.fs.fg_frac['O2'] - + m.fs.fg_frac['SO2']) - elif i == 'NO': - return m.fs.fg_frac[i] == 0.38148020722713599076938E-005 \ - * m.fs.coal_flow - elif i == 'O2': - return m.fs.fg_frac[i] == 0.60149266916011525155317E-003 \ - * m.fs.coal_flow - elif i == 'SO2': - return m.fs.fg_frac[i] == 0.21991717807021016347323E-004 \ - * m.fs.coal_flow - m.fs.fg_mol_cn = pyo.Constraint(m.fs.prop_fluegas.component_list, - rule=fg_mol_frac_rule) + if i == "CO2": + return m.fs.fg_frac[i] == 0.23235491508307534735955 * m.fs.SR**-0.5 + elif i == "H2O": + return m.fs.fg_frac[i] == 0.45009703293861530459807e-001 * m.fs.SR + elif i == "N2": + return m.fs.fg_frac[i] == 1 - ( + m.fs.fg_frac["CO2"] + + m.fs.fg_frac["H2O"] + + m.fs.fg_frac["NO"] + + m.fs.fg_frac["O2"] + + m.fs.fg_frac["SO2"] + ) + elif i == "NO": + return m.fs.fg_frac[i] == 0.38148020722713599076938e-005 * m.fs.coal_flow + elif i == "O2": + return m.fs.fg_frac[i] == 0.60149266916011525155317e-003 * m.fs.coal_flow + elif i == "SO2": + return m.fs.fg_frac[i] == 0.21991717807021016347323e-004 * m.fs.coal_flow + + m.fs.fg_mol_cn = pyo.Constraint( + m.fs.prop_fluegas.component_list, rule=fg_mol_frac_rule + ) def fg_flow_rule(b, i): - mass_flow = 0.87757407893140026988732 * m.fs.coal_flow \ - - 0.68240933480416252066014E-001 * m.fs.SR + 8.6386912890637752582\ - * m.fs.coal_flow*m.fs.SR - 0.11737247790640543564002E-003 \ - * (m.fs.coal_flow/m.fs.SR)**2 # ~ 28.3876e3 kg/s + mass_flow = ( + 0.87757407893140026988732 * m.fs.coal_flow + - 0.68240933480416252066014e-001 * m.fs.SR + + 8.6386912890637752582 * m.fs.coal_flow * m.fs.SR + - 0.11737247790640543564002e-003 * (m.fs.coal_flow / m.fs.SR) ** 2 + ) # ~ 28.3876e3 kg/s # flow mol component in mol/s = kg/s/kg/mol - return m.fs.FSH.hot_side.properties_in[0].flow_mol_comp[i] == \ - (mass_flow / sum(m.fs.fg_frac[c]*m.fs.prop_fluegas.mw_comp[c] - for c in m.fs.prop_fluegas.component_list))\ + return ( + m.fs.FSH.hot_side.properties_in[0].flow_mol_comp[i] + == ( + mass_flow + / sum( + m.fs.fg_frac[c] * m.fs.prop_fluegas.mw_comp[c] + for c in m.fs.prop_fluegas.component_list + ) + ) * m.fs.fg_frac[i] - m.fs.fg_flow_cn = pyo.Constraint(m.fs.prop_fluegas.component_list, - rule=fg_flow_rule) + ) + + m.fs.fg_flow_cn = pyo.Constraint( + m.fs.prop_fluegas.component_list, rule=fg_flow_rule + ) def fg_temp_rule(b): - return m.fs.FSH.hot_side.properties_in[0].temperature == \ - 59836.381548557488713413 * m.fs.coal_flow**-0.5 \ - + 791.74814907302368283126 * m.fs.coal_flow**0.5 \ - + 0.60200443235342349090899 * m.fs.coal_flow**1.5 \ - + 285.74858049626226375040 * m.fs.SR**3 - 29865.27413896147845662\ - * (m.fs.coal_flow*m.fs.SR)**-.5 - 518.58090213915738786454 \ - * (m.fs.coal_flow*m.fs.SR)**0.5 - 0.86351166781748790735040E-002 \ - * (m.fs.coal_flow*m.fs.SR)**2 - 30141.308801694875000976 \ - * (m.fs.SR/m.fs.coal_flow)**0.5 - 18.109911868067683826666 \ - * m.fs.coal_flow/m.fs.SR + 1017.0807559525446777116 \ - * m.fs.SR/m.fs.coal_flow + return ( + m.fs.FSH.hot_side.properties_in[0].temperature + == 59836.381548557488713413 * m.fs.coal_flow**-0.5 + + 791.74814907302368283126 * m.fs.coal_flow**0.5 + + 0.60200443235342349090899 * m.fs.coal_flow**1.5 + + 285.74858049626226375040 * m.fs.SR**3 + - 29865.27413896147845662 * (m.fs.coal_flow * m.fs.SR) ** -0.5 + - 518.58090213915738786454 * (m.fs.coal_flow * m.fs.SR) ** 0.5 + - 0.86351166781748790735040e-002 * (m.fs.coal_flow * m.fs.SR) ** 2 + - 30141.308801694875000976 * (m.fs.SR / m.fs.coal_flow) ** 0.5 + - 18.109911868067683826666 * m.fs.coal_flow / m.fs.SR + + 1017.0807559525446777116 * m.fs.SR / m.fs.coal_flow + ) + m.fs.fg_temp_cn = pyo.Constraint(rule=fg_temp_rule) def heat_2_PLSH_rule(b): - return m.fs.PlSH.heat_duty[0] == - 42149808.046329699456692 \ - * pyo.log(m.fs.coal_flow) + 13125913.817196270450950 \ - * pyo.exp(m.fs.SR) - 82168941.403612509369850 \ - * m.fs.coal_flow**-.5 - 20751.165176131220505340 \ - * (m.fs.coal_flow*m.fs.SR)**1.5 + 35303843.583323523402214 \ - * (m.fs.coal_flow/m.fs.SR)**0.5 + return ( + m.fs.PlSH.heat_duty[0] + == -42149808.046329699456692 * pyo.log(m.fs.coal_flow) + + 13125913.817196270450950 * pyo.exp(m.fs.SR) + - 82168941.403612509369850 * m.fs.coal_flow**-0.5 + - 20751.165176131220505340 * (m.fs.coal_flow * m.fs.SR) ** 1.5 + + 35303843.583323523402214 * (m.fs.coal_flow / m.fs.SR) ** 0.5 + ) + m.fs.heat_2_PLSH_cn = pyo.Constraint(rule=heat_2_PLSH_rule) def heat_2_ww_rule(b): - heat_total = 15383517.068522246554494 * m.fs.coal_flow \ - - 145195958.70188459753990 * m.fs.SR**0.5 + 91548063.4268338829278\ - * (m.fs.coal_flow*m.fs.SR)**0.5 - 11732787.822234204038978 \ - * m.fs.coal_flow*m.fs.SR - 19639.552666366322227987 \ - * (m.fs.coal_flow/m.fs.SR)**2 - return m.fs.Water_wall.heat_duty[0] == heat_total \ - - m.fs.PlSH.heat_duty[0] + heat_total = ( + 15383517.068522246554494 * m.fs.coal_flow + - 145195958.70188459753990 * m.fs.SR**0.5 + + 91548063.4268338829278 * (m.fs.coal_flow * m.fs.SR) ** 0.5 + - 11732787.822234204038978 * m.fs.coal_flow * m.fs.SR + - 19639.552666366322227987 * (m.fs.coal_flow / m.fs.SR) ** 2 + ) + return m.fs.Water_wall.heat_duty[0] == heat_total - m.fs.PlSH.heat_duty[0] + m.fs.heat_2_ww_cn = pyo.Constraint(rule=heat_2_ww_rule) # unfix variables to build flowsheet connections @@ -217,14 +247,10 @@ def heat_2_ww_rule(b): m.fs.FSH.hot_side.properties_in[:].flow_mol_comp.unfix() m.fs.FSH.hot_side.properties_in[:].temperature.unfix() m.fs.coal_flow.fix(50.15) - print('degrees of freedom = ' + str(degrees_of_freedom(m))) + print("degrees of freedom = " + str(degrees_of_freedom(m))) # initialize surrogate models (better initial point before solve) - calculate_variable_from_constraint( - m.fs.PlSH.heat_duty[0], - m.fs.heat_2_PLSH_cn) - calculate_variable_from_constraint( - m.fs.Water_wall.heat_duty[0], - m.fs.heat_2_ww_cn) + calculate_variable_from_constraint(m.fs.PlSH.heat_duty[0], m.fs.heat_2_PLSH_cn) + calculate_variable_from_constraint(m.fs.Water_wall.heat_duty[0], m.fs.heat_2_ww_cn) # set scaling parameters iscale.calculate_scaling_factors(m) # final solve diff --git a/idaes_examples/archive/data_reconciliation/boiler_flowsheet_recon_src.ipynb b/idaes_examples/archive/data_reconciliation/boiler_flowsheet_recon_src.ipynb index 15a6b0fc..402842e8 100644 --- a/idaes_examples/archive/data_reconciliation/boiler_flowsheet_recon_src.ipynb +++ b/idaes_examples/archive/data_reconciliation/boiler_flowsheet_recon_src.ipynb @@ -614,7 +614,7 @@ "outputs": [], "source": [ "# Add the model references to the tag metadata based on the strings above.\n", - "da.upadate_metadata_model_references(m, df_meta)" + "da.update_metadata_model_references(m, df_meta)" ] }, { diff --git a/idaes_examples/archive/data_reconciliation/econ_parmest_src.ipynb b/idaes_examples/archive/data_reconciliation/econ_parmest_src.ipynb index 1634e721..ead3fe78 100644 --- a/idaes_examples/archive/data_reconciliation/econ_parmest_src.ipynb +++ b/idaes_examples/archive/data_reconciliation/econ_parmest_src.ipynb @@ -636,6 +636,7 @@ " \"FG_2_AIRPH_P\",\n", "}\n", "\n", + "\n", "# Return expressions for the objective\n", "def sse(model, data):\n", " return sum((model.err[i]) ** 2 for i in objective_tags)" diff --git a/idaes_examples/archive/data_reconciliation/econ_recon_src.ipynb b/idaes_examples/archive/data_reconciliation/econ_recon_src.ipynb index 797b63df..503c399f 100644 --- a/idaes_examples/archive/data_reconciliation/econ_recon_src.ipynb +++ b/idaes_examples/archive/data_reconciliation/econ_recon_src.ipynb @@ -118,7 +118,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "It can be useful to visualize the measurement data and estimated uncertainty. The following creates box and whisker plots for each tag based on the data bins. A large number of plots may be created, so to manage them more easily, they are saved as PDFs and merged into a single multi-page PDF document. The deafault file name for the resulting PDF is \"data_plot_book.pdf.\"" + "It can be useful to visualize the measurement data and estimated uncertainty. The following creates box and whisker plots for each tag based on the data bins. A large number of plots may be created, so to manage them more easily, they are saved as PDFs and merged into a single multi-page PDF document. The default file name for the resulting PDF is \"data_plot_book.pdf.\"" ] }, { @@ -384,7 +384,7 @@ "outputs": [], "source": [ "# Add the model references to the tag metadata based on the strings above.\n", - "da.upadate_metadata_model_references(m, df_meta)" + "da.update_metadata_model_references(m, df_meta)" ] }, { @@ -413,7 +413,7 @@ "from idaes.core.util.tags import ModelTagGroup\n", "\n", "# This function creates a dictionary of streams based of streams based on model arcs. The function\n", - "# also takes an addtional set of stream-like objects for add to the stream dictionary. In this case,\n", + "# also takes an additional set of stream-like objects for add to the stream dictionary. In this case,\n", "# this is a single unit and the flowsheet doesn't contain any arcs, so we add the economized inlet and\n", "# outlet ports to the stream dictionary.\n", "stream_dict = ta.arcs_to_stream_dict(\n", @@ -459,7 +459,7 @@ " except KeyError:\n", " pass\n", "\n", - "# Any addtional tags can be added. This is required for tags that cannot be systematically generated\n", + "# Any additional tags can be added. This is required for tags that cannot be systematically generated\n", "# from the model streams.\n", "recon_tags.add(expr=m.fs.econ.heat_duty[0], name=\"ECON_Q\", format_string=\"{:.3f}\")" ] diff --git a/idaes_examples/archive/matopt/bifunctional_surface_design.py b/idaes_examples/archive/matopt/bifunctional_surface_design.py index 7bca435a..8ea5d3ed 100644 --- a/idaes_examples/archive/matopt/bifunctional_surface_design.py +++ b/idaes_examples/archive/matopt/bifunctional_surface_design.py @@ -14,7 +14,7 @@ from idaes.apps.matopt import * from copy import deepcopy -if __name__ == '__main__': +if __name__ == "__main__": IAD = sqrt(2) * 2 Lat = FCCLattice.alignedWith111(IAD) @@ -33,89 +33,115 @@ Canv = Canvas.fromLatticeAndTilingScan(Lat, T) - D = Design(Canv, Atom('Pt')) - D.toPDB('canvas.pdb') - D.toCFG('canvas.cfg', GS=1.0, BBox=S) + D = Design(Canv, Atom("Pt")) + D.toPDB("canvas.pdb") + D.toCFG("canvas.cfg", GS=1.0, BBox=S) MotifCanvas = Canvas() MotifCanvas.addLocation(np.array([0, 0, 0], dtype=float), NNeighbors=12) MotifCanvas.addShell(Lat.getNeighbors) Confs = [[None] * len(MotifCanvas.NeighborhoodIndexes[0]) for _ in range(7)] - iToSetNi = [[3, 4, 5, 6, 7, 8], - [3, 4, 5, 6], - [4, 5, 6, 7], - [5, 6, 7, 8], - [6, 7, 8, 3], - [7, 8, 3, 4], - [8, 3, 4, 5]] - iToSetPt = [[9, 10, 11], - [9, 10, 11], - [9, 10, 11], - [9, 10, 11], - [9, 10, 11], - [9, 10, 11], - [9, 10, 11]] + iToSetNi = [ + [3, 4, 5, 6, 7, 8], + [3, 4, 5, 6], + [4, 5, 6, 7], + [5, 6, 7, 8], + [6, 7, 8, 3], + [7, 8, 3, 4], + [8, 3, 4, 5], + ] + iToSetPt = [ + [9, 10, 11], + [9, 10, 11], + [9, 10, 11], + [9, 10, 11], + [9, 10, 11], + [9, 10, 11], + [9, 10, 11], + ] for iConf, Conf in enumerate(Confs): for i in iToSetNi[iConf]: - Conf[i] = Atom('Ni') + Conf[i] = Atom("Ni") for i in iToSetPt[iConf]: - Conf[i] = Atom('Pt') + Conf[i] = Atom("Pt") TypeAConfs = [0] TypeBConfs = [1, 2, 3, 4, 5, 6] - LocsToFixPt = [i for i in range(len(Canv)) if Canv.Points[i][2] < Lat.FCC111LayerSpacing * 2.5] + LocsToFixPt = [ + i for i in range(len(Canv)) if Canv.Points[i][2] < Lat.FCC111LayerSpacing * 2.5 + ] LocsToExcludePt = [i for i in range(len(Canv)) if i not in LocsToFixPt] - CanvTwoBotLayers = [i for i in range(len(Canv)) if Canv.Points[i][2] < Lat.FCC111LayerSpacing * 1.5] + CanvTwoBotLayers = [ + i for i in range(len(Canv)) if Canv.Points[i][2] < Lat.FCC111LayerSpacing * 1.5 + ] CanvMinusTwoBotLayers = [i for i in range(len(Canv)) if i not in CanvTwoBotLayers] OneLocToFix = [min(LocsToExcludePt)] - TileSizeSquared = nUnitCellsOnEdge ** 2 + TileSizeSquared = nUnitCellsOnEdge**2 CatNorm = TileSizeSquared * 6.0 UndefectedSurfE = 0.129758 maxSurfE = 999 CatWeight = 1.0 - Atoms = [Atom('Ni'), Atom('Pt')] + Atoms = [Atom("Ni"), Atom("Pt")] m = MatOptModel(Canv, Atoms, Confs) - m.Yik.rules.append(FixedTo(1, sites=LocsToFixPt, site_types=[Atom('Pt')])) - m.Yik.rules.append(FixedTo(0, sites=LocsToExcludePt, site_types=[Atom('Pt')])) + m.Yik.rules.append(FixedTo(1, sites=LocsToFixPt, site_types=[Atom("Pt")])) + m.Yik.rules.append(FixedTo(0, sites=LocsToExcludePt, site_types=[Atom("Pt")])) m.Zic.rules.append(FixedTo(1, sites=OneLocToFix, confs=TypeAConfs)) - m.Zic.rules.append(Implies(concs=(m.Yik, EqualTo(1, site_types=[Atom('Ni')])))) + m.Zic.rules.append(Implies(concs=(m.Yik, EqualTo(1, site_types=[Atom("Ni")])))) SumAConfsExpr = SumConfs(m.Zic, confs_to_sum=TypeAConfs) SumBConfsExpr = SumConfs(m.Zic, confs_to_sum=TypeBConfs) - m.addBondsDescriptor('SiteCombinations', binary=True, - rules=ImpliesSiteCombination(Canv, - (SumAConfsExpr, GreaterThan(1)), - (SumBConfsExpr, GreaterThan(1)))) - m.addGlobalDescriptor('Activity', - rules=EqualTo(SumBonds(m.SiteCombinations, coefs=1 / CatNorm))) + m.addBondsDescriptor( + "SiteCombinations", + binary=True, + rules=ImpliesSiteCombination( + Canv, (SumAConfsExpr, GreaterThan(1)), (SumBConfsExpr, GreaterThan(1)) + ), + ) + m.addGlobalDescriptor( + "Activity", rules=EqualTo(SumBonds(m.SiteCombinations, coefs=1 / CatNorm)) + ) - EiVals = [0, -0.04293 * 3 + 0.41492, -0.04293 * 10 + 0.41492, 0.05179 * 11 - 0.62148, 0] + EiVals = [ + 0, + -0.04293 * 3 + 0.41492, + -0.04293 * 10 + 0.41492, + 0.05179 * 11 - 0.62148, + 0, + ] EiBPs = [0, 3, 10, 11, 12] - m.addSitesDescriptor('Ei', - rules=PiecewiseLinear(values=EiVals, - breakpoints=EiBPs, - input_desc=m.Ci), - sites=CanvMinusTwoBotLayers) - m.addGlobalDescriptor('Esurf', - rules=EqualTo(SumSites(m.Ei, coefs=1 / TileSizeSquared, offset=0.101208))) - m.addGlobalDescriptor('Stability', - rules=EqualTo(LinearExpr(m.Esurf, 1 / UndefectedSurfE))) + m.addSitesDescriptor( + "Ei", + rules=PiecewiseLinear(values=EiVals, breakpoints=EiBPs, input_desc=m.Ci), + sites=CanvMinusTwoBotLayers, + ) + m.addGlobalDescriptor( + "Esurf", + rules=EqualTo(SumSites(m.Ei, coefs=1 / TileSizeSquared, offset=0.101208)), + ) + m.addGlobalDescriptor( + "Stability", rules=EqualTo(LinearExpr(m.Esurf, 1 / UndefectedSurfE)) + ) - m.addGlobalDescriptor('ActAndStab', - rules=EqualTo(LinearExpr(descs=[m.Stability, m.Activity], - coefs=[-(1 - CatWeight), CatWeight]))) + m.addGlobalDescriptor( + "ActAndStab", + rules=EqualTo( + LinearExpr( + descs=[m.Stability, m.Activity], coefs=[-(1 - CatWeight), CatWeight] + ) + ), + ) D = None try: D = m.maximize(m.ActAndStab, tilim=360) except: - print('MaOpt can not find usable solver (CPLEX or NEOS-CPLEX)') - if (D is not None): - D.toCFG('result.cfg', BBox=S) + print("MaOpt can not find usable solver (CPLEX or NEOS-CPLEX)") + if D is not None: + D.toCFG("result.cfg", BBox=S) PeriodicD = T.replicateDesign(D, 4) PeriodicS = deepcopy(S) PeriodicS.scale(np.array([4, 4, 1])) - PeriodicD.toCFG('periodic_result.cfg', BBox=PeriodicS) + PeriodicD.toCFG("periodic_result.cfg", BBox=PeriodicS) diff --git a/idaes_examples/archive/matopt/bifunctional_surface_design_src.ipynb b/idaes_examples/archive/matopt/bifunctional_surface_design_src.ipynb index d9a0ce4e..b123df61 100644 --- a/idaes_examples/archive/matopt/bifunctional_surface_design_src.ipynb +++ b/idaes_examples/archive/matopt/bifunctional_surface_design_src.ipynb @@ -43,7 +43,7 @@ "metadata": {}, "source": [ "## Importing Packages\n", - "We start by importing several standard Python modules for convienience. " + "We start by importing several standard Python modules for convenience. " ] }, { @@ -339,7 +339,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Finally, we introduce a single descriptor for the weighted combination of acitivity and stability. \n", + "Finally, we introduce a single descriptor for the weighted combination of activity and stability. \n", "By changing the parameter weighting the catalytic portion of the objective function, we can optimize for a range of designs optimizing stability and activity. " ] }, diff --git a/idaes_examples/archive/matopt/bimetallic_nanocluster_design.py b/idaes_examples/archive/matopt/bimetallic_nanocluster_design.py index 7551a8cc..c71ddf9e 100644 --- a/idaes_examples/archive/matopt/bimetallic_nanocluster_design.py +++ b/idaes_examples/archive/matopt/bimetallic_nanocluster_design.py @@ -14,68 +14,77 @@ from idaes.apps.matopt import * from math import sqrt -if __name__ == '__main__': +if __name__ == "__main__": # === MONOMETALIC OPT === Lat = FCCLattice(IAD=1.0) Canv = Canvas() Canv.addLocation(np.array([0, 0, 0], dtype=float)) Canv.addShells(1, Lat.getNeighbors) - Atoms = [Atom('Cu')] + Atoms = [Atom("Cu")] N = 6 m = MatOptModel(Canv, Atoms) Vals = [sqrt(CN) for CN in range(0, 13)] BPs = [CN for CN in range(0, 13)] - m.addSitesDescriptor('CNRi', bounds=(0, sqrt(12)), integer=False, - rules=PiecewiseLinear(values=Vals, - breakpoints=BPs, - input_desc=m.Ci)) - m.addGlobalDescriptor('Ecoh', rules=EqualTo(SumSites(desc=m.CNRi, - coefs=(1 / (N * sqrt(12)))))) - m.addGlobalDescriptor('Size', bounds=(N, N), - rules=EqualTo(SumSites(desc=m.Yi))) + m.addSitesDescriptor( + "CNRi", + bounds=(0, sqrt(12)), + integer=False, + rules=PiecewiseLinear(values=Vals, breakpoints=BPs, input_desc=m.Ci), + ) + m.addGlobalDescriptor( + "Ecoh", rules=EqualTo(SumSites(desc=m.CNRi, coefs=(1 / (N * sqrt(12))))) + ) + m.addGlobalDescriptor("Size", bounds=(N, N), rules=EqualTo(SumSites(desc=m.Yi))) D = m.maximize(m.Ecoh, tilim=100) # === PROCESSING SOLUTION === Canv = Canvas() for i in range(len(D)): - if (D.Contents[i] is not None): + if D.Contents[i] is not None: Canv.addLocation(D.Canvas.Points[i]) Canv.setNeighborsFromFunc(Lat.getNeighbors) - Atoms = [Atom('Cu'), Atom('Ag')] - CompBounds = {Atom('Cu'): (3, 3), - Atom('Ag'): (3, 3)} + Atoms = [Atom("Cu"), Atom("Ag")] + CompBounds = {Atom("Cu"): (3, 3), Atom("Ag"): (3, 3)} m = MatOptModel(Canv, Atoms) m.Yi.rules.append(FixedTo(1.0)) - GklCoefs = {(Atom('Cu'), Atom('Cu')): 3.520, - (Atom('Cu'), Atom('Ag')): 2.112, - (Atom('Ag'), Atom('Ag')): 2.580, - (Atom('Ag'), Atom('Cu')): 3.612} + GklCoefs = { + (Atom("Cu"), Atom("Cu")): 3.520, + (Atom("Cu"), Atom("Ag")): 2.112, + (Atom("Ag"), Atom("Ag")): 2.580, + (Atom("Ag"), Atom("Cu")): 3.612, + } BEijCoefs = {} for i in range(len(Canv)): CNi = sum(1 for _ in Canv.NeighborhoodIndexes[i] if _ is not None) for j in Canv.NeighborhoodIndexes[i]: - if (j is not None): + if j is not None: CNj = sum(1 for _ in Canv.NeighborhoodIndexes[j] if _ is not None) for k in Atoms: for l in Atoms: - BEijCoefs[i, j, k, l] = GklCoefs[k, l] * 1 / sqrt(CNi) + GklCoefs[l, k] * 1 / sqrt(CNj) - m.addBondsDescriptor('BEij', - rules=EqualTo(SumBondTypes(m.Xijkl, coefs=BEijCoefs)), - symmetric_bonds=True) + BEijCoefs[i, j, k, l] = GklCoefs[k, l] * 1 / sqrt( + CNi + ) + GklCoefs[l, k] * 1 / sqrt(CNj) + m.addBondsDescriptor( + "BEij", + rules=EqualTo(SumBondTypes(m.Xijkl, coefs=BEijCoefs)), + symmetric_bonds=True, + ) - m.addGlobalDescriptor('Ecoh', rules=EqualTo(SumBonds(desc=m.BEij, - coefs=1.0 / (N * sqrt(12))))) - m.addGlobalTypesDescriptor('Composition', bounds=CompBounds, - rules=EqualTo(SumSites(desc=m.Yik))) + m.addGlobalDescriptor( + "Ecoh", rules=EqualTo(SumBonds(desc=m.BEij, coefs=1.0 / (N * sqrt(12)))) + ) + m.addGlobalTypesDescriptor( + "Composition", bounds=CompBounds, rules=EqualTo(SumSites(desc=m.Yik)) + ) D = None try: D = m.maximize(m.Ecoh, tilim=360, trelim=4096) except: - print('MaOpt can not find usable solver (CPLEX or NEOS-CPLEX)') - if (D is not None): - D.toPDB('result.pdb') + print("MaOpt can not find usable solver (CPLEX or NEOS-CPLEX)") + if D is not None: + D.toPDB("result.pdb") diff --git a/idaes_examples/archive/matopt/bimetallic_nanocluster_design_src.ipynb b/idaes_examples/archive/matopt/bimetallic_nanocluster_design_src.ipynb index 643ce34c..a1872731 100644 --- a/idaes_examples/archive/matopt/bimetallic_nanocluster_design_src.ipynb +++ b/idaes_examples/archive/matopt/bimetallic_nanocluster_design_src.ipynb @@ -45,7 +45,7 @@ "source": [ "## Importing Packages\n", "\n", - "We start by importing several standard Python modules for convienience." + "We start by importing several standard Python modules for convenience." ] }, { diff --git a/idaes_examples/archive/matopt/metal_oxide_bulk_design.py b/idaes_examples/archive/matopt/metal_oxide_bulk_design.py index dc44758a..53e43782 100644 --- a/idaes_examples/archive/matopt/metal_oxide_bulk_design.py +++ b/idaes_examples/archive/matopt/metal_oxide_bulk_design.py @@ -15,27 +15,47 @@ from zipfile import ZipFile from idaes.apps.matopt import * -if __name__ == '__main__': +if __name__ == "__main__": A = 4.0 B = 4.0 C = 4.0 Lat = PerovskiteLattice(A, B, C) nUnitCellsOnEdge = 2 - S = RectPrism(nUnitCellsOnEdge * A, - nUnitCellsOnEdge * B, - nUnitCellsOnEdge * C) + S = RectPrism(nUnitCellsOnEdge * A, nUnitCellsOnEdge * B, nUnitCellsOnEdge * C) S.shift(np.array([-0.01, -0.01, -0.01])) T = CubicTiling(S) Canv = Canvas.fromLatticeAndTilingScan(Lat, T) - Atoms = [Atom('Ba'), Atom('Fe'), Atom('In'), Atom('O')] + Atoms = [Atom("Ba"), Atom("Fe"), Atom("In"), Atom("O")] - iDesiredConfs = [394, 395, 396, 397, 398, 399, 400, 401, 68, 69, - 70, 71, 162, 163, 164, 165, 166, 167, 168, 169] + iDesiredConfs = [ + 394, + 395, + 396, + 397, + 398, + 399, + 400, + 401, + 68, + 69, + 70, + 71, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + ] with TemporaryDirectory() as ConfDir: - ZipFile('confs.zip').extractall(ConfDir) - ConfDesigns = loadFromPDBs([str(i) + '.pdb' for i in iDesiredConfs], folder=ConfDir + '/confs/') + ZipFile("confs.zip").extractall(ConfDir) + ConfDesigns = loadFromPDBs( + [str(i) + ".pdb" for i in iDesiredConfs], folder=ConfDir + "/confs/" + ) Confs = [Conf.Contents for Conf in ConfDesigns] Sites = [i for i in range(len(Canv))] @@ -44,36 +64,47 @@ OSites = [i for i in Sites if Lat.isOSite(Canv.Points[i])] pctLocalLB, pctLocalUB = 0, 1 pctGlobalLB, pctGlobalUB = 0.0, 0.3 - LocalBounds = {(i, Atom('In')): (round(pctLocalLB * len(Canv.NeighborhoodIndexes[i])), - round(pctLocalUB * len(Canv.NeighborhoodIndexes[i]))) for i in OSites} + LocalBounds = { + (i, Atom("In")): ( + round(pctLocalLB * len(Canv.NeighborhoodIndexes[i])), + round(pctLocalUB * len(Canv.NeighborhoodIndexes[i])), + ) + for i in OSites + } GlobalLB = round(pctGlobalLB * len(BSites)) GlobalUB = round(pctGlobalUB * len(BSites)) m = MatOptModel(Canv, Atoms, Confs) - m.Yik.rules.append(FixedTo(1, sites=ASites, site_types=[Atom('Ba')])) - m.Yik.rules.append(FixedTo(1, sites=OSites, site_types=[Atom('O')])) - m.Yik.rules.append(FixedTo(0, sites=BSites, site_types=[Atom('Ba'), Atom('O')])) + m.Yik.rules.append(FixedTo(1, sites=ASites, site_types=[Atom("Ba")])) + m.Yik.rules.append(FixedTo(1, sites=OSites, site_types=[Atom("O")])) + m.Yik.rules.append(FixedTo(0, sites=BSites, site_types=[Atom("Ba"), Atom("O")])) m.Yi.rules.append(FixedTo(1, sites=BSites)) - m.addGlobalDescriptor('Activity', - rules=EqualTo(SumSitesAndConfs(m.Zic, coefs=1 / len(OSites), sites_to_sum=OSites))) - m.addGlobalTypesDescriptor('GlobalBudget', bounds=(GlobalLB, GlobalUB), - rules=EqualTo(SumSites(m.Yik, - site_types=[Atom('In')], - sites_to_sum=BSites))) - m.addSitesTypesDescriptor('LocalBudget', bounds=LocalBounds, - rules=EqualTo(SumNeighborSites(m.Yik, - sites=OSites, - site_types=[Atom('In')]))) + m.addGlobalDescriptor( + "Activity", + rules=EqualTo( + SumSitesAndConfs(m.Zic, coefs=1 / len(OSites), sites_to_sum=OSites) + ), + ) + m.addGlobalTypesDescriptor( + "GlobalBudget", + bounds=(GlobalLB, GlobalUB), + rules=EqualTo(SumSites(m.Yik, site_types=[Atom("In")], sites_to_sum=BSites)), + ) + m.addSitesTypesDescriptor( + "LocalBudget", + bounds=LocalBounds, + rules=EqualTo(SumNeighborSites(m.Yik, sites=OSites, site_types=[Atom("In")])), + ) D = None try: D = m.maximize(m.Activity, tilim=360) except: - print('MaOpt can not find usable solver (CPLEX or NEOS-CPLEX)') - if (D is not None): + print("MaOpt can not find usable solver (CPLEX or NEOS-CPLEX)") + if D is not None: for i, c in m.Zic.keys(): - if (m.Zic.values[i, c] > 0.5): - D.setContent(i, Atom('S')) - D.toCFG('result.cfg', BBox=S) + if m.Zic.values[i, c] > 0.5: + D.setContent(i, Atom("S")) + D.toCFG("result.cfg", BBox=S) diff --git a/idaes_examples/archive/matopt/monometallic_nanocluster_design.py b/idaes_examples/archive/matopt/monometallic_nanocluster_design.py index bc668670..4fb38b22 100644 --- a/idaes_examples/archive/matopt/monometallic_nanocluster_design.py +++ b/idaes_examples/archive/matopt/monometallic_nanocluster_design.py @@ -14,33 +14,41 @@ from idaes.apps.matopt import * from math import sqrt -if __name__ == '__main__': +if __name__ == "__main__": Lat = FCCLattice(IAD=2.7704443686888935) Canv = Canvas() Canv.addLocation(np.array([0, 0, 0], dtype=float)) Canv.addShells(2, Lat.getNeighbors) Canv.setNeighborsFromFunc(Lat.getNeighbors) - D = Design(Canv, Atom('Pt')) - D.toPDB('canvas.pdb') + D = Design(Canv, Atom("Pt")) + D.toPDB("canvas.pdb") # NOTE: If canvas.pdb was already created, we could have simply loaded it: # Canv = Canvas.fromPDB('canvas.pdb',Lat=Lat) - Atoms = [Atom('Pt')] + Atoms = [Atom("Pt")] N = 22 m = MatOptModel(Canv, Atoms) - m.addSitesDescriptor('CNRi', bounds=(0, sqrt(12)), integer=False, - rules=PiecewiseLinear(values=[sqrt(CN) for CN in range(13)], - breakpoints=[CN for CN in range(13)], - input_desc=m.Ci, con_type='UB')) - m.addGlobalDescriptor('Ecoh', rules=EqualTo(SumSites(desc=m.CNRi, - coefs=(1.0 / sqrt(12) * 1.0 / N)))) - m.addGlobalDescriptor('Size', bounds=(N, N), rules=EqualTo(SumSites(desc=m.Yi))) + m.addSitesDescriptor( + "CNRi", + bounds=(0, sqrt(12)), + integer=False, + rules=PiecewiseLinear( + values=[sqrt(CN) for CN in range(13)], + breakpoints=[CN for CN in range(13)], + input_desc=m.Ci, + con_type="UB", + ), + ) + m.addGlobalDescriptor( + "Ecoh", rules=EqualTo(SumSites(desc=m.CNRi, coefs=(1.0 / sqrt(12) * 1.0 / N))) + ) + m.addGlobalDescriptor("Size", bounds=(N, N), rules=EqualTo(SumSites(desc=m.Yi))) D = None try: D = m.maximize(m.Ecoh, tilim=100) except: - print('MaOpt can not find usable solver (CPLEX or NEOS-CPLEX)') - if (D is not None): - D.toPDB('result.pdb') + print("MaOpt can not find usable solver (CPLEX or NEOS-CPLEX)") + if D is not None: + D.toPDB("result.pdb") diff --git a/idaes_examples/archive/matopt/monometallic_nanocluster_design_src.ipynb b/idaes_examples/archive/matopt/monometallic_nanocluster_design_src.ipynb index 32b4955b..b4a900c3 100644 --- a/idaes_examples/archive/matopt/monometallic_nanocluster_design_src.ipynb +++ b/idaes_examples/archive/matopt/monometallic_nanocluster_design_src.ipynb @@ -34,10 +34,10 @@ "\n", "In this module, we introduce the **MatOpt** interface for representing material properties and specifying optimization problems. \n", "\n", - "We have designed the interface with severl goals in mind:\n", + "We have designed the interface with several goals in mind:\n", "\n", "1. To **simplify the representation of nanostructured materials,** streamlining the creation of materials optimization problems. \n", - "2. To provide a simple interface so that users **do not need to understand the details of building mathematical optmization models** or the syntax of the Pyomo package. \n", + "2. To provide a simple interface so that users **do not need to understand the details of building mathematical optimization models** or the syntax of the Pyomo package. \n", "3. To **automate many of the common steps of materials optimization,** speeding up the development of new models. \n", "\n", "As an example system, we will consider the minimization of cohesive energy in nanoclusters, recently demonstrated in:\n", @@ -45,7 +45,7 @@ "Isenberg, N. M., et al., \"Identification of Optimally Stable Nanocluster Geometries via Mathematical Optimization and Density Functional Theory,\" *Molecular Systems Design & Engineering* 5 (2020): 232-244. DOI: [10.1039/C9ME00108E](https://pubs.rsc.org/en/content/articlelanding/2020/me/c9me00108e#!divAbstract).\n", "\n", "We seek to identify the geometry that minimizes the cohesive energy of a nanocluster on the face-centered cubic (FCC) lattice. \n", - "As a model for cohesive energy, we use model based on the square-root of coordination number, refered to as the Tomanek model [[1]](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.28.665).\n", + "As a model for cohesive energy, we use model based on the square-root of coordination number, referred to as the Tomanek model [[1]](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.28.665).\n", "In the equation below, we define the normalized cohesive energy, as the normalized contribution of the square root of the coordination number. \n", "\n", "$$\\hat{E}^{\\text{surf}} = \\frac{1}{N \\sqrt{12}} \\displaystyle \\sum_i \\sqrt{CN_i} $$\n", @@ -178,7 +178,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The MatOptModel additionally hold lists of ***MaterialDescriptor*** objects that define the relevant material desriptors. \n", + "The MatOptModel additionally hold lists of ***MaterialDescriptor*** objects that define the relevant material descriptors. \n", "By default, several universal site descriptors are pre-defined in the model. \n", "From these, all other material descriptors can be defined.\n", "\n", @@ -353,7 +353,7 @@ " Default: True\n", " tilim (float): Optional, solver time limit (in seconds).\n", " Default: 3600\n", - " trelim (float): Optional, solver tree memeory limit (in MB).\n", + " trelim (float): Optional, solver tree memory limit (in MB).\n", " Default: None (i.e., Pyomo/CPLEX default)\n", " solver (str): Solver choice. Currently only cplex or neos-cplex are supported\n", " Default: cplex\n", diff --git a/idaes_examples/archive/matopt/nanowire_design_src.ipynb b/idaes_examples/archive/matopt/nanowire_design_src.ipynb index 2111fb33..a1070c8e 100644 --- a/idaes_examples/archive/matopt/nanowire_design_src.ipynb +++ b/idaes_examples/archive/matopt/nanowire_design_src.ipynb @@ -40,7 +40,7 @@ "metadata": {}, "source": [ "## Importing Packages\n", - "We start by importing several standard Python modules for convienience. " + "We start by importing several standard Python modules for convenience. " ] }, { @@ -161,7 +161,7 @@ "source": [ "## Building a Model\n", "\n", - "In this example, we will build a model that maximizes the cohesive energy of the nanowire as an indicator of it's thermal stability. To begin, we start by creating a **MatOptModel** object to hold information about the model. Notice that we use a list of empty atoms as our set of building blocks because we will use a cohesive energy function for semiconductor materials that is indepedent of atom types. Thus, we do not need to specify the types of building blocks in our model." + "In this example, we will build a model that maximizes the cohesive energy of the nanowire as an indicator of it's thermal stability. To begin, we start by creating a **MatOptModel** object to hold information about the model. Notice that we use a list of empty atoms as our set of building blocks because we will use a cohesive energy function for semiconductor materials that is independent of atom types. Thus, we do not need to specify the types of building blocks in our model." ] }, { diff --git a/idaes_examples/archive/matopt/nanowires_design.py b/idaes_examples/archive/matopt/nanowires_design.py index 75801a4e..2fa5323f 100644 --- a/idaes_examples/archive/matopt/nanowires_design.py +++ b/idaes_examples/archive/matopt/nanowires_design.py @@ -15,9 +15,9 @@ from copy import deepcopy -if __name__ == '__main__': +if __name__ == "__main__": IAD = 3.7265 - orientation = '0001' + orientation = "0001" nAtomRadius = 6 nAtomUnitLength = 2 origin = np.zeros(3, dtype=float) @@ -34,7 +34,11 @@ lattice = WurtziteLattice.alignedWith(IAD, orientation) radius = lattice.getShellSpacing(orientation) * (nAtomRadius - 1) - height = lattice.getLayerSpacing(orientation) * lattice.getUniqueLayerCount(orientation) * nAtomUnitLength + height = ( + lattice.getLayerSpacing(orientation) + * lattice.getUniqueLayerCount(orientation) + * nAtomUnitLength + ) shape = Cylinder(origin, radius, height, axisDirection) shape.shift(-0.001 * shape.Vh) # shift downwards so that the seed is in the shape canvas = Canvas.fromLatticeAndShape(lattice, shape) @@ -44,37 +48,67 @@ # lattice.setDesign(design, Atom('In'), Atom('As')) # design.toPDB('canvas.pdb') - CoreLayers = [i for i, p in enumerate(canvas.Points) if p[0] ** 2 + p[1] ** 2 < (coreRatio * radius) ** 2] - CanvasMinusCoreLayers = [i for i, p in enumerate(canvas.Points) if i not in CoreLayers] - NeighborsInside = [[j for j in canvas.NeighborhoodIndexes[i] if ( - j is not None and canvas.Points[j][0] ** 2 + canvas.Points[j][1] ** 2 < - p[0] ** 2 + p[1] ** 2 - DBL_TOL)] for i, p in enumerate(canvas.Points)] + CoreLayers = [ + i + for i, p in enumerate(canvas.Points) + if p[0] ** 2 + p[1] ** 2 < (coreRatio * radius) ** 2 + ] + CanvasMinusCoreLayers = [ + i for i, p in enumerate(canvas.Points) if i not in CoreLayers + ] + NeighborsInside = [ + [ + j + for j in canvas.NeighborhoodIndexes[i] + if ( + j is not None + and canvas.Points[j][0] ** 2 + canvas.Points[j][1] ** 2 + < p[0] ** 2 + p[1] ** 2 - DBL_TOL + ) + ] + for i, p in enumerate(canvas.Points) + ] - m = MatOptModel(canvas, [Atom('')]) + m = MatOptModel(canvas, [Atom("")]) m.Yi.rules.append(FixedTo(1, sites=CoreLayers)) - m.Yi.rules.append(ImpliesNeighbors(concs=(m.Yi, GreaterThan(1)), - sites=CanvasMinusCoreLayers, - neighborhoods=NeighborsInside)) - m.addSitesDescriptor('Vi', bounds=(min(Vals), max(Vals)), - rules=PiecewiseLinear(values=Vals, breakpoints=BPs, input_desc=m.Ci, con_type='UB')) - m.addGlobalDescriptor('Ecoh', rules=EqualTo(SumSites(desc=m.Vi,coefs=(1.0 / sizeUnitLength)))) - m.addGlobalDescriptor('Size', bounds=(sizeUnitLength, sizeUnitLength), rules=EqualTo(SumSites(desc=m.Yi))) + m.Yi.rules.append( + ImpliesNeighbors( + concs=(m.Yi, GreaterThan(1)), + sites=CanvasMinusCoreLayers, + neighborhoods=NeighborsInside, + ) + ) + m.addSitesDescriptor( + "Vi", + bounds=(min(Vals), max(Vals)), + rules=PiecewiseLinear( + values=Vals, breakpoints=BPs, input_desc=m.Ci, con_type="UB" + ), + ) + m.addGlobalDescriptor( + "Ecoh", rules=EqualTo(SumSites(desc=m.Vi, coefs=(1.0 / sizeUnitLength))) + ) + m.addGlobalDescriptor( + "Size", + bounds=(sizeUnitLength, sizeUnitLength), + rules=EqualTo(SumSites(desc=m.Yi)), + ) optimalDesign = None try: - optimalDesign = m.maximize(m.Ecoh, tilim=360, solver='cplex') + optimalDesign = m.maximize(m.Ecoh, tilim=360, solver="cplex") except: - print('MaOpt can not find usable solver (CPLEX or NEOS-CPLEX)') - + print("MaOpt can not find usable solver (CPLEX or NEOS-CPLEX)") + if optimalDesign is not None: for i, p in enumerate(optimalDesign.Canvas.Points): if optimalDesign.Contents[i] is not None: if lattice.isASite(p): - optimalDesign.setContent(i, Atom('In')) + optimalDesign.setContent(i, Atom("In")) elif lattice.isBSite(p): - optimalDesign.setContent(i, Atom('As')) - optimalDesign.toPDB('result.pdb') + optimalDesign.setContent(i, Atom("As")) + optimalDesign.toPDB("result.pdb") periodicDesign = deepcopy(optimalDesign) for k in range(4): for i, p in enumerate(optimalDesign.Canvas.Points): @@ -82,4 +116,4 @@ for k in range(4): for i, p in enumerate(optimalDesign.Canvas.Points): periodicDesign.add(p - (k + 1) * shape.Vh, optimalDesign.Contents[i]) - periodicDesign.toPDB('periodic_result.pdb') + periodicDesign.toPDB("periodic_result.pdb") diff --git a/idaes_examples/archive/matopt/surface_design.py b/idaes_examples/archive/matopt/surface_design.py index 8690bf93..7714525d 100644 --- a/idaes_examples/archive/matopt/surface_design.py +++ b/idaes_examples/archive/matopt/surface_design.py @@ -14,7 +14,7 @@ from idaes.apps.matopt import * from copy import deepcopy -if __name__ == '__main__': +if __name__ == "__main__": IAD = 2.828427 # Angstrom Lat = FCCLattice.alignedWith111(IAD) @@ -33,71 +33,112 @@ Canv = Canvas.fromLatticeAndTilingScan(Lat, T) - D = Design(Canv, Atom('Pt')) - D.toPDB('undefected.pdb') - D.toCFG('undefected.cfg', GS=1.0, BBox=S) + D = Design(Canv, Atom("Pt")) + D.toPDB("undefected.pdb") + D.toCFG("undefected.cfg", GS=1.0, BBox=S) - Atoms = [Atom('Pt')] + Atoms = [Atom("Pt")] TargetGCN = 8.0 CNsurfMin = 3 CNsurfMax = 9 - TileSizeSquared = nAtomsOnEdge ** 2 + TileSizeSquared = nAtomsOnEdge**2 UndefectedSurfE = 0.129758 maxSurfE = 999 CatWeight = 1.0 m = MatOptModel(Canv, Atoms) - CanvTwoBotLayers = [i for i in range(len(Canv)) - if Canv.Points[i][2] < 1.5 * Lat.FCC111LayerSpacing] - CanvMinusTwoBotLayers = [i for i in range(len(Canv)) - if i not in CanvTwoBotLayers] - OneSiteInTopLayer = [min([i for i in range(len(Canv)) - if Canv.Points[i][2] > (nLayers - 1.5) * Lat.FCC111LayerSpacing])] + CanvTwoBotLayers = [ + i for i in range(len(Canv)) if Canv.Points[i][2] < 1.5 * Lat.FCC111LayerSpacing + ] + CanvMinusTwoBotLayers = [i for i in range(len(Canv)) if i not in CanvTwoBotLayers] + OneSiteInTopLayer = [ + min( + [ + i + for i in range(len(Canv)) + if Canv.Points[i][2] > (nLayers - 1.5) * Lat.FCC111LayerSpacing + ] + ) + ] m.Yi.rules.append(FixedTo(1, sites=OneSiteInTopLayer)) m.Yi.rules.append(FixedTo(1, sites=CanvTwoBotLayers)) - NeighborsBelow = [[j for j in Canv.NeighborhoodIndexes[i] - if (j is not None and - Canv.Points[j][2] < Canv.Points[i][2] - DBL_TOL)] - for i in range(len(Canv))] - m.Yi.rules.append(ImpliesNeighbors(concs=(m.Yi, GreaterThan(1)), - sites=CanvMinusTwoBotLayers, - neighborhoods=NeighborsBelow)) - m.addSitesDescriptor('GCNi', bounds=(0, 12), integer=False, - rules=EqualTo(SumNeighborSites(desc=m.Ci, - coefs=1 / 12)), - sites=CanvMinusTwoBotLayers) - m.addSitesDescriptor('IdealSitei', binary=True, - rules=[Implies(concs=(m.Ci, GreaterThan(3))), - Implies(concs=(m.Ci, LessThan(9))), - Implies(concs=(m.GCNi, EqualTo(TargetGCN)))], - sites=CanvMinusTwoBotLayers) - m.addGlobalDescriptor('Activity', rules=EqualTo(SumSites(m.IdealSitei, coefs=1 / TileSizeSquared))) - EiVals = [0, -0.04293 * 3 + 0.41492, -0.04293 * 10 + 0.41492, 0.05179 * 11 - 0.62148, 0] + NeighborsBelow = [ + [ + j + for j in Canv.NeighborhoodIndexes[i] + if (j is not None and Canv.Points[j][2] < Canv.Points[i][2] - DBL_TOL) + ] + for i in range(len(Canv)) + ] + m.Yi.rules.append( + ImpliesNeighbors( + concs=(m.Yi, GreaterThan(1)), + sites=CanvMinusTwoBotLayers, + neighborhoods=NeighborsBelow, + ) + ) + m.addSitesDescriptor( + "GCNi", + bounds=(0, 12), + integer=False, + rules=EqualTo(SumNeighborSites(desc=m.Ci, coefs=1 / 12)), + sites=CanvMinusTwoBotLayers, + ) + m.addSitesDescriptor( + "IdealSitei", + binary=True, + rules=[ + Implies(concs=(m.Ci, GreaterThan(3))), + Implies(concs=(m.Ci, LessThan(9))), + Implies(concs=(m.GCNi, EqualTo(TargetGCN))), + ], + sites=CanvMinusTwoBotLayers, + ) + m.addGlobalDescriptor( + "Activity", rules=EqualTo(SumSites(m.IdealSitei, coefs=1 / TileSizeSquared)) + ) + EiVals = [ + 0, + -0.04293 * 3 + 0.41492, + -0.04293 * 10 + 0.41492, + 0.05179 * 11 - 0.62148, + 0, + ] EiBPs = [0, 3, 10, 11, 12] - m.addSitesDescriptor('Ei', rules=PiecewiseLinear(values=EiVals, - breakpoints=EiBPs, - input_desc=m.Ci), - sites=CanvMinusTwoBotLayers) - m.addGlobalDescriptor('Esurf', rules=EqualTo(SumSites(m.Ei, coefs=1 / TileSizeSquared, offset=0.101208))) - m.addGlobalDescriptor('Stability', - rules=EqualTo(LinearExpr(descs=m.Esurf, - coefs=-1 / UndefectedSurfE, - offset=1))) - m.addGlobalDescriptor('ActAndStab', rules=EqualTo(LinearExpr(descs=[m.Activity, m.Stability], - coefs=[CatWeight, (1 - CatWeight)]))) + m.addSitesDescriptor( + "Ei", + rules=PiecewiseLinear(values=EiVals, breakpoints=EiBPs, input_desc=m.Ci), + sites=CanvMinusTwoBotLayers, + ) + m.addGlobalDescriptor( + "Esurf", + rules=EqualTo(SumSites(m.Ei, coefs=1 / TileSizeSquared, offset=0.101208)), + ) + m.addGlobalDescriptor( + "Stability", + rules=EqualTo(LinearExpr(descs=m.Esurf, coefs=-1 / UndefectedSurfE, offset=1)), + ) + m.addGlobalDescriptor( + "ActAndStab", + rules=EqualTo( + LinearExpr( + descs=[m.Activity, m.Stability], coefs=[CatWeight, (1 - CatWeight)] + ) + ), + ) D = None try: D = m.maximize(m.ActAndStab, tilim=360) except: - print('MaOpt can not find usable solver (CPLEX or NEOS-CPLEX)') - if (D is not None): + print("MaOpt can not find usable solver (CPLEX or NEOS-CPLEX)") + if D is not None: for i in m.IdealSitei.keys(): if m.IdealSitei.values[i] > 0.5: - D.setContent(i, Atom('S')) - D.toPDB('result.pdb') + D.setContent(i, Atom("S")) + D.toPDB("result.pdb") PeriodicD = T.replicateDesign(D, 4) PeriodicS = deepcopy(S) PeriodicS.scale(np.array([4, 4, 1])) - PeriodicD.toCFG('periodic_result.cfg', BBox=PeriodicS) + PeriodicD.toCFG("periodic_result.cfg", BBox=PeriodicS) diff --git a/idaes_examples/archive/matopt/surface_design_src.ipynb b/idaes_examples/archive/matopt/surface_design_src.ipynb index 8923413b..49861f88 100644 --- a/idaes_examples/archive/matopt/surface_design_src.ipynb +++ b/idaes_examples/archive/matopt/surface_design_src.ipynb @@ -42,7 +42,7 @@ "metadata": {}, "source": [ "## Importing Packages\n", - "We start by importing several standard Python modules for convienience. " + "We start by importing several standard Python modules for convenience. " ] }, { @@ -211,7 +211,7 @@ "source": [ "First, we introduce two rules to fix special sites in the design. \n", "We fix the bottom two layers of atoms to exist, creating underlying bulk layers above which we will introduce nanostruced defets.\n", - "We also fix an arbitrary atom in the top layer, breaking symetry of the design space and resulting in easier to solve opitmization problems without actually restricting the designs that can be possibly represented. " + "We also fix an arbitrary atom in the top layer, breaking symmetry of the design space and resulting in easier to solve opitmization problems without actually restricting the designs that can be possibly represented. " ] }, { @@ -241,7 +241,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Next, we introduce constraints thtat require atoms to be placed on top of each other, avoiding hollow pockets below the surface. " + "Next, we introduce constraints that require atoms to be placed on top of each other, avoiding hollow pockets below the surface. " ] }, { @@ -311,7 +311,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Next, we define a simple model for the surface energy of nanostructured slabs as a piecwise linear function of coordination number. " + "Next, we define a simple model for the surface energy of nanostructured slabs as a piecewise linear function of coordination number. " ] }, { @@ -473,7 +473,7 @@ "metadata": {}, "source": [ "## Processing Solutions\n", - "Once the model is solved, we can plot the resulting design. However, it is often useful to label atoms according to some auxilliary information. In this case, we would like to label atoms that consitute ideal reactive sites. We loop over the sites and set the atom to S to highlight the sites that are reactive. Then, we can write the Design object to PDB or CFG files for plotting.\n", + "Once the model is solved, we can plot the resulting design. However, it is often useful to label atoms according to some auxiliary information. In this case, we would like to label atoms that constitute ideal reactive sites. We loop over the sites and set the atom to S to highlight the sites that are reactive. Then, we can write the Design object to PDB or CFG files for plotting.\n", "\n", "Additionally, we can manipulate the resulting design to better see the periodic pattern. Here, we replicate the design four times to see the periodic pattern. " ] diff --git a/idaes_examples/archive/power_gen/ngfc/NGFC_flowsheet_init.json.gz b/idaes_examples/archive/power_gen/ngfc/NGFC_flowsheet_init.json.gz index 422b947f..f3c19160 100644 Binary files a/idaes_examples/archive/power_gen/ngfc/NGFC_flowsheet_init.json.gz and b/idaes_examples/archive/power_gen/ngfc/NGFC_flowsheet_init.json.gz differ diff --git a/idaes_examples/archive/power_gen/ngfc/NGFC_flowsheet_solution.json.gz b/idaes_examples/archive/power_gen/ngfc/NGFC_flowsheet_solution.json.gz index 0bb58cfa..74f0cd47 100644 Binary files a/idaes_examples/archive/power_gen/ngfc/NGFC_flowsheet_solution.json.gz and b/idaes_examples/archive/power_gen/ngfc/NGFC_flowsheet_solution.json.gz differ diff --git a/idaes_examples/archive/power_gen/ngfc/NGFC_results.svg b/idaes_examples/archive/power_gen/ngfc/NGFC_results.svg index 6588363e..e15a7f3c 100644 --- a/idaes_examples/archive/power_gen/ngfc/NGFC_results.svg +++ b/idaes_examples/archive/power_gen/ngfc/NGFC_results.svg @@ -1,7 +1,7 @@ -image/svg+xml - +image/svg+xml + - + @@ -73,431 +73,431 @@ - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + For SVG HRSG - + - + Sheet.1 - + - + Sheet.2 Steam - - - Steam - + + + Steam + Sheet.3 - + - + Sheet.4 - + - + Sheet.5 SOFC Stack - - - SOFC Stack - + + + SOFC Stack + Sheet.6 - + - + Sheet.7 Air - - - Air - + + + Air + Sheet.8 - + - + Sheet.9 Cathode - - - Cathode - + + + Cathode + Sheet.10 Electrolyte - - - Electrolyte - + + + Electrolyte + Sheet.11 Anode - - - Anode - + + + Anode + AC source - - - - + + + + - - - - - + + + + + - + - - - + + + Inverter - + Sheet.14 - + Sheet.15 1 - - - + + + - + Sheet.16 Inverter - - - Inverter - + + + Inverter + Sheet.17 - + - + Sheet.18 - + - + Sheet.19 - + - + Sheet.20 - + - + Sheet.21 - + - + Dynamic connector.122 - + - + Sheet.23 - + - + Dynamic connector.124 - + - + Dynamic connector.125 - + - + Sheet.26 AC - - - AC - + + + AC + Dynamic connector.127 - + - + Sheet.29 Cathode Blower - - - Cathode Blower - + + + Cathode Blower + Sheet.31 Cathode HTX - - - Cathode HTX - + + + Cathode HTX + Sheet.32 Autothermal - - - Autothermal - + + + Autothermal + Dynamic connector.78 - + - + Dynamic connector.134 - + - + Sheet.35 - + - + Dynamic connector.136 - + - + Dynamic connector.138 - + - + Dynamic connector.137 - + - + Dynamic connector.139 - + - + Sheet.40 - + - + Sheet.41 - + - + Sheet.43 - + - + Dynamic connector.144 - + - + Dynamic connector.109 - + - + Dynamic connector.110 - + - + Sheet.50 Preheat - - - Preheat - + + + Preheat + Sheet.51 Autothermal Reformer - - - Autothermal Reformer - + + + Autothermal Reformer + Sheet.52 Anode HTX - - - Anode HTX - + + + Anode HTX + Sheet.53 Combustor - - - Combustor - + + + Combustor + Sheet.54 Natural Gas - - - Natural Gas - + + + Natural Gas + Dynamic connector.155 - + - + Sheet.62 - + - + Sheet.64 - + - + Dynamic connector.167 - + - + Sheet.66 - + - + Sheet.67 Air - - - Air - + + + Air + Sheet.68 Heat Recovery Steam Generator - - - Heat Recovery Steam Generator - + + + Heat Recovery Steam Generator + Sheet.69 Steam Turbine Generator - - - Steam Turbine Generator - + + + Steam Turbine Generator + Dynamic connector.154 - + - + AC source.160 - - - - + + + + - - - - - + + + + + - + Sheet.72 AC - - - AC - + + + AC + Dynamic connector.170 - + - + Dynamic connector.172 - + - + Dynamic connector.191 - + - + Dynamic connector.192 - + - + Dynamic connector.132 - + - + Dynamic connector.193 - + - + Dynamic connector.42 - + - + Dynamic connector.96 - + - + Sheet.98 Pre-Reformer - - - Pre-Reformer + + + Pre-Reformer -621 K137 kPaSYN_INT:P:3,642 mol/sF:979 K137 kPaANODE_RECT:P:4,084 mol/sF:890 K105 kPaCATH_INT:P:34,197 mol/sF:998 K104 kPaCATH_OUTT:P:32,525 mol/sF:998 K104 kPaCATH_HX_HIT:P:16,262 mol/sF:288 K101 kPaAIRT:P:17,934 mol/sF:834 K137 kPaANODE_INT:P:8,041 mol/sF:978 K137 kPaANO_HX_HIT:P:5,084 mol/sF:825 K136 kPaANO_HX_HOT:P:5,084 mol/sF:814 K137 kPaANO_HX_CIT:P:7,727 mol/sF:978 K137 kPaANODE_OUTT:P:9,169 mol/sF:476 K102 kPaCATH_HX_HOT:P:16,262 mol/sF:1,002 K105 kPaCATH_RECT:P:16,262 mol/sF:297 K111 kPaCATH_HX_CIT:P:17,934 mol/sF:787 K105 kPaCATH_HX_COT:P:17,934 mol/sF:348.30.6617.3ROM InputsFuel Inlet Temperature (C):Internal Reformation fraction:Air Inlet Temperature (C):4,000Avg. Curent Density (A/m^2):0.5Air Recirculation fraction:2.1Oxygen to Carbon ratio:Fuel Utilization fraction:Air Utilization fraction:0.80.449559.3ROM OutputsDC Stack Power (MW):0.8668Stack Voltage (V):30.6265.17-673.92Heat Duties (MW)Anode Heat Exchanger:Cathode Heat Exchanger:Anode:48.1Reformer Recuperator:114.57Cathode:422 K206 kPaSTEAM_INT:P:464 mol/sF:310 K203 kPaAIR_INT:P:1,333 mol/sF:747 K206 kPaREF_INT:P:465 mol/sF:288 K3,447 kPaFUEL_INT:P:1,161 mol/sF:1,060 K137 kPaREF_OUTT:P:2,944 mol/sF:1,265 K95 kPaCOMB_OUTT:P:9,545 mol/sF:405 K94 kPaANOD_EXHT:P:9,546 mol/sF:476 K102 kPaCOMB_AIRT:P:4,878 mol/sF:405 K101 kPaCATH_EXHT:P:11,384 mol/sF:476 K102 kPaCATH_HRSG_INT:P:11,384 mol/sF:107.321.110.4Performance SummarySteam Turbine Power (MW):NG Expander Power (MW):Auxiliary Load (MW):542.6AC Stack Power (MW):660.6Net Power (MW):Thermal Input (MW):HHV Efficiency (%):CO2 Emissions (g/kWh):1,056.0291.262.561,012 K3,447 kPaHOT_FUELT:P:1,161 mol/sF: \ No newline at end of file +621 K137 kPaSYN_INT:P:3,642 mol/sF:979 K137 kPaANODE_RECT:P:4,084 mol/sF:890 K105 kPaCATH_INT:P:34,197 mol/sF:998 K104 kPaCATH_OUTT:P:32,525 mol/sF:998 K104 kPaCATH_HX_HIT:P:16,262 mol/sF:288 K101 kPaAIRT:P:17,934 mol/sF:834 K137 kPaANODE_INT:P:8,041 mol/sF:978 K137 kPaANO_HX_HIT:P:5,084 mol/sF:825 K136 kPaANO_HX_HOT:P:5,084 mol/sF:814 K137 kPaANO_HX_CIT:P:7,727 mol/sF:978 K137 kPaANODE_OUTT:P:9,169 mol/sF:476 K102 kPaCATH_HX_HOT:P:16,262 mol/sF:1,002 K105 kPaCATH_RECT:P:16,262 mol/sF:297 K111 kPaCATH_HX_CIT:P:17,934 mol/sF:787 K105 kPaCATH_HX_COT:P:17,934 mol/sF:348.30.6617.3ROM InputsFuel Inlet Temperature (C):Internal Reformation fraction:Air Inlet Temperature (C):4,000Avg. Current Density (A/m^2):0.5Air Recirculation fraction:2.1Oxygen to Carbon ratio:Fuel Utilization fraction:Air Utilization fraction:0.80.449559.3ROM OutputsDC Stack Power (MW):0.8668Stack Voltage (V):30.6265.17-673.92Heat Duties (MW)Anode Heat Exchanger:Cathode Heat Exchanger:Anode:48.1Reformer Recuperator:114.57Cathode:422 K206 kPaSTEAM_INT:P:464 mol/sF:310 K203 kPaAIR_INT:P:1,333 mol/sF:747 K206 kPaREF_INT:P:465 mol/sF:288 K3,447 kPaFUEL_INT:P:1,161 mol/sF:1,060 K137 kPaREF_OUTT:P:2,944 mol/sF:1,265 K95 kPaCOMB_OUTT:P:9,545 mol/sF:405 K94 kPaANOD_EXHT:P:9,546 mol/sF:476 K102 kPaCOMB_AIRT:P:4,878 mol/sF:405 K101 kPaCATH_EXHT:P:11,384 mol/sF:476 K102 kPaCATH_HRSG_INT:P:11,384 mol/sF:107.321.110.4Performance SummarySteam Turbine Power (MW):NG Expander Power (MW):Auxiliary Load (MW):542.6AC Stack Power (MW):660.6Net Power (MW):Thermal Input (MW):HHV Efficiency (%):CO2 Emissions (g/kWh):1,056.0291.262.561,012 K3,447 kPaHOT_FUELT:P:1,161 mol/sF: \ No newline at end of file diff --git a/idaes_examples/archive/power_gen/ngfc/NGFC_results_template.svg b/idaes_examples/archive/power_gen/ngfc/NGFC_results_template.svg index 3e9ebc6f..2079cfd2 100644 --- a/idaes_examples/archive/power_gen/ngfc/NGFC_results_template.svg +++ b/idaes_examples/archive/power_gen/ngfc/NGFC_results_template.svg @@ -3896,7 +3896,7 @@ y="493.27994" x="-444.19608" id="tspan9468-5-5-3" - sodipodi:role="line">Avg. Curent Density (A/m^2):Avg. Current Density (A/m^2):" ] @@ -133,7 +132,7 @@ "\n", "The sequence of ```build_power_island()```, ```scale_flowsheet()```, ```set_power_island_inputs()```, and ```initialize_power_island()``` creates and initializes the unit models in the power island. At this stage, the inlet to the anode side of the power island is a guess of the syngas conditions. Then the solver is called to finalize the solution to the power island. The solution to the reformer section remains unaffected.\n", "\n", - "To combine the two sections ```connect_reformer_to_power_island()``` is called. The funtion unfixes the guess for the inlet to power island and connects the outlet of the reformer section to it. Then the solver is called a third time on the fully connected flowsheet.\n", + "To combine the two sections ```connect_reformer_to_power_island()``` is called. The function unfixes the guess for the inlet to power island and connects the outlet of the reformer section to it. Then the solver is called a third time on the fully connected flowsheet.\n", "\n", "In execution order, the flowsheet builds each of the two subsections, scales model constraints, sets inputs, initializes independently, and connects the two subsections.\n", "\n", @@ -154,11 +153,12 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "import os\n", + "import logging\n", "\n", "# Import Pyomo libraries\n", "import pyomo.environ as pyo\n", @@ -167,14 +167,17 @@ "# Import IDAES core\n", "from idaes.core import FlowsheetBlock\n", "from idaes.core.util import model_serializer as ms\n", + "from idaes.core.util.model_statistics import degrees_of_freedom\n", "\n", "# Import NGFC model components\n", - "from idaes_examples.mod.power_gen import NGFC_flowsheet as NGFC" + "from idaes_examples.mod.power_gen import NGFC_flowsheet as NGFC\n", + "\n", + "import idaes.logger as idaeslog" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -182,14 +185,41 @@ "m = pyo.ConcreteModel(name=\"NGFC no CCS\")\n", "m.fs = FlowsheetBlock(dynamic=False)\n", "\n", - "# create the solver\n", - "solver = pyo.SolverFactory(\"ipopt\")\n", - "solver.options = {\"bound_push\": 1e-16}" + "# create the solvers\n", + "# the SOFC ROM is very large and using the ma97 linear solver significantly reduces the solve time\n", + "solver_ma97 = pyo.SolverFactory(\"ipopt\")\n", + "solver_ma97.options = {\n", + " \"max_iter\": 200,\n", + " \"tol\": 1e-7,\n", + " \"bound_push\": 1e-5,\n", + " \"mu_init\": 1e-2,\n", + " \"linear_solver\": \"ma97\",\n", + " \"nlp_scaling_method\": \"user-scaling\",\n", + "}\n", + "\n", + "# the ma57 linear solver is better for the initial solve before the ROM is built\n", + "solver_ma57 = pyo.SolverFactory(\"ipopt\")\n", + "solver_ma57.options = {\n", + " \"max_iter\": 200,\n", + " \"tol\": 1e-7,\n", + " \"bound_push\": 1e-5,\n", + " \"linear_solver\": \"ma57\",\n", + " \"OF_ma57_automatic_scaling\": \"yes\",\n", + " \"nlp_scaling_method\": \"user-scaling\",\n", + "}\n", + "\n", + "# suppress warnings about missing scaling factors\n", + "scaling_log = idaeslog.getLogger(\"idaes.core.util.scaling\")\n", + "scaling_log.setLevel(idaeslog.ERROR)\n", + "\n", + "# suppress NL writer warnings\n", + "nl_writer_log = logging.getLogger(\"pyomo.repn.plugins.nl_writer\")\n", + "nl_writer_log.setLevel(logging.ERROR)" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 5, "metadata": { "scrolled": true }, @@ -199,178 +229,23 @@ "output_type": "stream", "text": [ "Scaling flowsheet variables\n", - "overwriting mole_frac lower bound, set to 0 to remove warnings\n", - "Scaling flowsheet constraints\n", - "Calculating scaling factors\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2O]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,CH4]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,C2H6]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,C3H8]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,C4H10]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,N2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,O2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,Ar]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2O]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,CH4]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,C2H6]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,C3H8]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,C4H10]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,N2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,O2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,Ar]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2O]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,CH4]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,C2H6]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,C3H8]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,C4H10]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,N2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,O2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,Ar]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2O]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,CH4]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,C2H6]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,C3H8]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,C4H10]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,N2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,O2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,Ar]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2O]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,CH4]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,C2H6]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,C3H8]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,C4H10]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,N2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,O2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.prereformer.control_volume.properties_out[0.0]._material_density_term[Vap,Ar]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,H2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,CO]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,H2O]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,CO2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,CH4]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,N2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,O2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,Ar]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,H2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,CO]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,H2O]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,CO2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,CH4]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,N2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,O2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,Ar]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,H2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,CO]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,H2O]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,CO2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,CH4]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,N2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,O2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,Ar]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,H2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,CO]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,H2O]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,CO2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,CH4]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,N2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,O2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,Ar]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,H2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,CO]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,H2O]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,CO2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,CH4]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,N2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,O2]\n", - "2023-02-13 09:09:03 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.anode.control_volume.properties_out[0.0]._material_density_term[Vap,Ar]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2O]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,CH4]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,C2H6]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,C3H8]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,C4H10]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,N2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,O2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,Ar]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2O]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,CH4]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,C2H6]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,C3H8]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,C4H10]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,N2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,O2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,Ar]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2O]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,CH4]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,C2H6]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,C3H8]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,C4H10]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,N2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,O2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,Ar]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2O]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,CH4]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,C2H6]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,C3H8]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,C4H10]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,N2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,O2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,Ar]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,H2O]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,CO2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,CH4]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,C2H6]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,C3H8]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,C4H10]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,N2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,O2]\n", - "2023-02-13 09:09:04 [WARNING] idaes.core.util.scaling: Missing scaling factor for fs.reformer.control_volume.properties_out[0.0]._material_density_term[Vap,Ar]\n", - "\n", - "Starting ROM initialization\n", - "ROM initialization completed\n", "Loading solved model\n" ] } ], "source": [ - "# initialization takes ~ 5 minutes, full solve takes ~ 1 hour\n", + "# full initialization and solves takes about 2 minutes\n", "reinit = False # switch to True to re-initialize and re-solve\n", "resolve = False # switch to True to re-solve only (for debugging)\n", "\n", "if os.path.exists(\"NGFC_flowsheet_init.json.gz\") and reinit is False:\n", " # already initialized, can build model and load results from json\n", + " NGFC.build_properties(m)\n", " NGFC.build_power_island(m)\n", " NGFC.build_reformer(m)\n", " NGFC.scale_flowsheet(m)\n", " NGFC.connect_reformer_to_power_island(m)\n", - " NGFC.SOFC_ROM_setup(m)\n", + " NGFC.SOFC_ROM_setup(m, init=False)\n", " NGFC.add_SOFC_energy_balance(m)\n", " NGFC.add_result_constraints(m)\n", " if os.path.exists(\"NGFC_flowsheet_solution.json.gz\") and resolve is False:\n", @@ -382,27 +257,13 @@ " # and then serialize solved model results\n", " print(\"Loading initialized model\")\n", " ms.from_json(m, fname=\"NGFC_flowsheet_init.json.gz\")\n", - " # solver and options\n", - " solver = pyo.SolverFactory(\"ipopt\")\n", - " solver.options = {\n", - " \"max_iter\": 50,\n", - " \"tol\": 1e-5,\n", - " \"bound_push\": 1e-8,\n", - " \"linear_solver\": \"ma57\",\n", - " \"ma57_pivtol\": 1e-3,\n", - " \"OF_ma57_automatic_scaling\": \"yes\",\n", - " \"nlp_scaling_method\": \"user-scaling\",\n", - " }\n", - " solve_iteration = 0\n", - " for i in range(1, 10): # keep looping until condition is met\n", - " solve_iteration += 1\n", - " print(\"Solve # \", solve_iteration)\n", - " res = solver.solve(m, tee=True)\n", - " if \"Optimal Solution Found\" in res.solver.message:\n", - " break\n", - " ms.to_json(m, fname=\"NGFC_flowsheet_solution.json.gz\")\n", + "\n", + " res = solver_ma97.solve(m, tee=True)\n", + " if \"Optimal Solution Found\" in res.solver.message:\n", + " ms.to_json(m, fname=\"NGFC_flowsheet_solution.json.gz\")\n", "else:\n", " # need to initialize model, serialize, and try to solve/serialize\n", + " NGFC.build_properties(m)\n", " NGFC.build_power_island(m)\n", " NGFC.build_reformer(m)\n", " NGFC.scale_flowsheet(m)\n", @@ -411,34 +272,23 @@ " NGFC.initialize_power_island(m)\n", " NGFC.initialize_reformer(m)\n", " NGFC.connect_reformer_to_power_island(m)\n", + "\n", + " solver_ma57.solve(m, tee=True)\n", + "\n", " NGFC.SOFC_ROM_setup(m)\n", " NGFC.add_SOFC_energy_balance(m)\n", " NGFC.add_result_constraints(m)\n", + "\n", " ms.to_json(m, fname=\"NGFC_flowsheet_init.json.gz\")\n", - " solver = pyo.SolverFactory(\"ipopt\")\n", - " solver.options = {\n", - " \"max_iter\": 50,\n", - " \"tol\": 1e-5,\n", - " \"bound_push\": 1e-8,\n", - " \"linear_solver\": \"ma57\",\n", - " \"ma57_pivtol\": 1e-3,\n", - " \"OF_ma57_automatic_scaling\": \"yes\",\n", - " \"nlp_scaling_method\": \"user-scaling\",\n", - " }\n", - " solve_iteration = 0\n", - " for i in range(1, 10): # keep looping until condition is met\n", - " solve_iteration += 1\n", - " print(\"Solve # \", solve_iteration)\n", - " res = solver.solve(m, tee=True)\n", - " if \"Optimal Solution Found\" in res.solver.message:\n", - " break\n", "\n", - " ms.to_json(m, fname=\"NGFC_flowsheet_solution.json.gz\")" + " res = solver_ma97.solve(m, tee=True)\n", + " if \"Optimal Solution Found\" in res.solver.message:\n", + " ms.to_json(m, fname=\"NGFC_flowsheet_solution.json.gz\")" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "metadata": { "scrolled": true }, @@ -449,406 +299,12 @@ "text": [ "DOF = 0\n", "\n", - "Solve # 1\n", - "WARNING: model contains export suffix\n", - " 'fs.bypass_rejoin.mixed_state[0.0].scaling_factor' that contains 13\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.bypass_rejoin.bypass_inlet_state[0.0].scaling_factor' that contains 13\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.bypass_rejoin.syngas_inlet_state[0.0].scaling_factor' that contains 13\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.reformer.control_volume.properties_out[0.0].scaling_factor' that\n", - " contains 15 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.reformer.control_volume.properties_in[0.0].scaling_factor' that\n", - " contains 13 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.reformer.control_volume.scaling_factor' that contains 10 component\n", - " keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix 'fs.reformer.scaling_factor' that\n", - " contains 4 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.reformer_mix.mixed_state[0.0].scaling_factor' that contains 13\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.reformer_mix.steam_inlet_state[0.0].scaling_factor' that contains 26\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.reformer_mix.oxygen_inlet_state[0.0].scaling_factor' that contains 13\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.reformer_mix.gas_inlet_state[0.0].scaling_factor' that contains 13\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.intercooler_s2.control_volume.properties_out[0.0].scaling_factor' that\n", - " contains 14 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.intercooler_s2.control_volume.properties_in[0.0].scaling_factor' that\n", - " contains 13 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.intercooler_s2.control_volume.scaling_factor' that contains 1\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.air_compressor_s2.properties_isentropic[0.0].scaling_factor' that\n", - " contains 15 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.air_compressor_s2.control_volume.properties_out[0.0].scaling_factor'\n", - " that contains 14 component keys that are not exported as part of the NL\n", - " file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.air_compressor_s2.control_volume.properties_in[0.0].scaling_factor'\n", - " that contains 15 component keys that are not exported as part of the NL\n", - " file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.intercooler_s1.control_volume.properties_out[0.0].scaling_factor' that\n", - " contains 14 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.intercooler_s1.control_volume.properties_in[0.0].scaling_factor' that\n", - " contains 13 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.intercooler_s1.control_volume.scaling_factor' that contains 1\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.air_compressor_s1.properties_isentropic[0.0].scaling_factor' that\n", - " contains 15 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.air_compressor_s1.control_volume.properties_out[0.0].scaling_factor'\n", - " that contains 14 component keys that are not exported as part of the NL\n", - " file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.air_compressor_s1.control_volume.properties_in[0.0].scaling_factor'\n", - " that contains 28 component keys that are not exported as part of the NL\n", - " file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.reformer_bypass.bypass_outlet_state[0.0].scaling_factor' that contains\n", - " 11 component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.reformer_bypass.reformer_outlet_state[0.0].scaling_factor' that\n", - " contains 11 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.reformer_bypass.mixed_state[0.0].scaling_factor' that contains 11\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.NG_expander.properties_isentropic[0.0].scaling_factor' that contains\n", - " 15 component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.NG_expander.control_volume.properties_out[0.0].scaling_factor' that\n", - " contains 14 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.NG_expander.control_volume.properties_in[0.0].scaling_factor' that\n", - " contains 15 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.reformer_recuperator.cold_side.properties_out[0.0].scaling_factor'\n", - " that contains 13 component keys that are not exported as part of the NL\n", - " file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.reformer_recuperator.cold_side.properties_in[0.0].scaling_factor' that\n", - " contains 27 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.reformer_recuperator.hot_side.properties_out[0.0].scaling_factor' that\n", - " contains 13 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.reformer_recuperator.hot_side.properties_in[0.0].scaling_factor' that\n", - " contains 13 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix 'fs.reformer_recuperator.scaling_factor'\n", - " that contains 2 component keys that are not exported as part of the NL\n", - " file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode_HRSG.control_volume.properties_out[0.0].scaling_factor' that\n", - " contains 11 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode_HRSG.control_volume.properties_in[0.0].scaling_factor' that\n", - " contains 10 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode_HRSG.control_volume.scaling_factor' that contains 1 component\n", - " keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.combustor_expander.properties_isentropic[0.0].scaling_factor' that\n", - " contains 12 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.combustor_expander.control_volume.properties_out[0.0].scaling_factor'\n", - " that contains 10 component keys that are not exported as part of the NL\n", - " file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.combustor_expander.control_volume.properties_in[0.0].scaling_factor'\n", - " that contains 12 component keys that are not exported as part of the NL\n", - " file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.combustor_expander.control_volume.scaling_factor' that contains 1\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.combustor.control_volume.properties_out[0.0].scaling_factor' that\n", - " contains 13 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.combustor.control_volume.properties_in[0.0].scaling_factor' that\n", - " contains 10 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.combustor.control_volume.scaling_factor' that contains 1 component\n", - " keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.combustor_mix.mixed_state[0.0].scaling_factor' that contains 10\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.combustor_mix.cathode_inlet_state[0.0].scaling_factor' that contains\n", - " 10 component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.combustor_mix.anode_inlet_state[0.0].scaling_factor' that contains 10\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_exhaust_translator.properties_out[0.0].scaling_factor' that\n", - " contains 11 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_exhaust_translator.properties_in[0.0].scaling_factor' that\n", - " contains 5 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_HRSG.control_volume.properties_out[0.0].scaling_factor' that\n", - " contains 8 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_HRSG.control_volume.properties_in[0.0].scaling_factor' that\n", - " contains 7 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_HRSG.control_volume.scaling_factor' that contains 1 component\n", - " keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_expander.properties_isentropic[0.0].scaling_factor' that\n", - " contains 9 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_expander.control_volume.properties_out[0.0].scaling_factor'\n", - " that contains 7 component keys that are not exported as part of the NL\n", - " file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_expander.control_volume.properties_in[0.0].scaling_factor'\n", - " that contains 9 component keys that are not exported as part of the NL\n", - " file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_expander.control_volume.scaling_factor' that contains 1\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_exhaust_split.combustor_outlet_state[0.0].scaling_factor' that\n", - " contains 5 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_exhaust_split.exhaust_outlet_state[0.0].scaling_factor' that\n", - " contains 5 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_exhaust_split.mixed_state[0.0].scaling_factor' that contains 5\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_blower.properties_isentropic[0.0].scaling_factor' that\n", - " contains 9 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_blower.control_volume.properties_out[0.0].scaling_factor' that\n", - " contains 8 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_blower.control_volume.properties_in[0.0].scaling_factor' that\n", - " contains 9 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_recycle.recycle_state[0.0].scaling_factor' that contains 5\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_recycle.exhaust_state[0.0].scaling_factor' that contains 5\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_recycle.mixed_state[0.0].scaling_factor' that contains 5\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_heat.control_volume.properties_out[0.0].scaling_factor' that\n", - " contains 8 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_heat.control_volume.properties_in[0.0].scaling_factor' that\n", - " contains 7 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_translator.properties_out[0.0].scaling_factor' that contains\n", - " 16 component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_translator.properties_in[0.0].scaling_factor' that contains 5\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode.ion_outlet_state[0.0].scaling_factor' that contains 5\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode.air_outlet_state[0.0].scaling_factor' that contains 5\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode.mixed_state[0.0].scaling_factor' that contains 5 component\n", - " keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_mix.mixed_state[0.0].scaling_factor' that contains 7 component\n", - " keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_mix.recycle_state[0.0].scaling_factor' that contains 7\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_mix.feed_state[0.0].scaling_factor' that contains 7 component\n", - " keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_hx.cold_side.properties_out[0.0].scaling_factor' that contains\n", - " 8 component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_hx.cold_side.properties_in[0.0].scaling_factor' that contains\n", - " 7 component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_hx.hot_side.properties_out[0.0].scaling_factor' that contains\n", - " 7 component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.cathode_hx.hot_side.properties_in[0.0].scaling_factor' that contains 7\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix 'fs.cathode_hx.hot_side.scaling_factor'\n", - " that contains 1 component keys that are not exported as part of the NL\n", - " file. Skipping.\n", - "WARNING: model contains export suffix 'fs.cathode_hx.scaling_factor' that\n", - " contains 2 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.air_blower.properties_isentropic[0.0].scaling_factor' that contains 9\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.air_blower.control_volume.properties_out[0.0].scaling_factor' that\n", - " contains 8 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.air_blower.control_volume.properties_in[0.0].scaling_factor' that\n", - " contains 16 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.recycle_translator.properties_out[0.0].scaling_factor' that contains\n", - " 14 component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.recycle_translator.properties_in[0.0].scaling_factor' that contains 8\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode_blower.properties_isentropic[0.0].scaling_factor' that contains\n", - " 12 component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode_blower.control_volume.properties_out[0.0].scaling_factor' that\n", - " contains 11 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode_blower.control_volume.properties_in[0.0].scaling_factor' that\n", - " contains 12 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode_recycle.recycle_state[0.0].scaling_factor' that contains 8\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode_recycle.exhaust_state[0.0].scaling_factor' that contains 8\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode_recycle.mixed_state[0.0].scaling_factor' that contains 8\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode.control_volume.properties_out[0.0].scaling_factor' that contains\n", - " 11 component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode.control_volume.properties_in[0.0].scaling_factor' that contains\n", - " 10 component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix 'fs.anode.control_volume.scaling_factor'\n", - " that contains 10 component keys that are not exported as part of the NL\n", - " file. Skipping.\n", - "WARNING: model contains export suffix 'fs.anode.scaling_factor' that contains\n", - " 4 component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.fuel_cell_mix.mixed_state[0.0].scaling_factor' that contains 10\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.fuel_cell_mix.ion_inlet_state[0.0].scaling_factor' that contains 10\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.fuel_cell_mix.fuel_inlet_state[0.0].scaling_factor' that contains 10\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode_translator.properties_out[0.0].scaling_factor' that contains 8\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode_translator.properties_in[0.0].scaling_factor' that contains 11\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.prereformer.control_volume.properties_out[0.0].scaling_factor' that\n", - " contains 13 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.prereformer.control_volume.properties_in[0.0].scaling_factor' that\n", - " contains 13 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.prereformer.control_volume.scaling_factor' that contains 13 component\n", - " keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix 'fs.prereformer.scaling_factor' that\n", - " contains 4 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode_hx.cold_side.properties_out[0.0].scaling_factor' that contains\n", - " 13 component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode_hx.cold_side.properties_in[0.0].scaling_factor' that contains 13\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix 'fs.anode_hx.cold_side.scaling_factor'\n", - " that contains 1 component keys that are not exported as part of the NL\n", - " file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode_hx.hot_side.properties_out[0.0].scaling_factor' that contains 10\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode_hx.hot_side.properties_in[0.0].scaling_factor' that contains 10\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix 'fs.anode_hx.hot_side.scaling_factor'\n", - " that contains 1 component keys that are not exported as part of the NL\n", - " file. Skipping.\n", - "WARNING: model contains export suffix 'fs.anode_hx.scaling_factor' that\n", - " contains 3 component keys that are not exported as part of the NL file.\n", - " Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode_mix.mixed_state[0.0].scaling_factor' that contains 13 component\n", - " keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode_mix.recycle_state[0.0].scaling_factor' that contains 13\n", - " component keys that are not exported as part of the NL file. Skipping.\n", - "WARNING: model contains export suffix\n", - " 'fs.anode_mix.feed_state[0.0].scaling_factor' that contains 13 component\n", - " keys that are not exported as part of the NL file. Skipping.\n", - "Ipopt 3.13.2: max_iter=50\n", - "tol=0.0001\n", - "bound_push=1e-08\n", - "linear_solver=ma57\n", - "ma57_pivtol=0.001\n", + "Ipopt 3.13.2: max_iter=200\n", + "tol=1e-07\n", + "bound_push=1e-05\n", + "mu_init=0.01\n", + "linear_solver=ma97\n", "nlp_scaling_method=user-scaling\n", - "option_file_name=C:\\Users\\dang\\AppData\\Local\\Temp\\tmpohg_fgrp_ipopt.opt\n", - "\n", - "Using option file \"C:\\Users\\dang\\AppData\\Local\\Temp\\tmpohg_fgrp_ipopt.opt\".\n", "\n", "\n", "******************************************************************************\n", @@ -869,7 +325,7 @@ " computation. See http://www.hsl.rl.ac.uk.\n", "******************************************************************************\n", "\n", - "This is Ipopt version 3.13.2, running with linear solver ma57.\n", + "This is Ipopt version 3.13.2, running with linear solver ma97.\n", "\n", "Number of nonzeros in equality constraint Jacobian...: 759386\n", "Number of nonzeros in inequality constraint Jacobian.: 0\n", @@ -886,68 +342,67 @@ " inequality constraints with only upper bounds: 0\n", "\n", "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", - " 0 0.0000000e+00 1.67e+00 1.00e+00 -1.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", - " 1 0.0000000e+00 1.68e+02 4.70e+06 -1.0 3.39e+05 - 3.65e-03 1.00e+00f 1\n", - " 2 0.0000000e+00 1.67e+02 4.69e+06 -1.0 6.13e+05 - 2.87e-01 3.00e-03h 1\n", - " 3 0.0000000e+00 1.49e+02 3.77e+06 -1.0 5.96e+05 - 5.87e-01 2.18e-01H 1\n", - " 4 0.0000000e+00 8.40e+01 2.44e+06 -1.0 3.76e+05 - 9.06e-01 4.37e-01h 1\n", - " 5 0.0000000e+00 8.36e+01 2.32e+06 -1.0 5.96e+04 - 9.94e-01 4.75e-02h 1\n", - " 6 0.0000000e+00 5.67e+01 1.34e+06 -1.0 3.66e+04 - 1.00e+00 5.48e-01H 1\n", - " 7 0.0000000e+00 1.77e+00 4.29e+05 -1.0 1.26e+04 - 1.00e+00 1.00e+00H 1\n", - " 8 0.0000000e+00 4.61e-01 1.58e+05 -1.0 1.40e+03 - 1.00e+00 1.00e+00f 1\n", - " 9 0.0000000e+00 1.70e-01 5.81e+04 -1.0 1.43e+03 - 1.00e+00 1.00e+00h 1\n", + " 0 0.0000000e+00 1.61e+00 1.00e+00 -2.0 0.00e+00 - 0.00e+00 0.00e+00 0\n", + " 1 0.0000000e+00 1.23e+00 9.83e+02 -2.0 2.60e+00 - 3.06e-02 1.00e+00f 1\n", + " 2 0.0000000e+00 6.11e-01 3.99e+03 -2.0 1.59e+00 - 9.60e-01 5.04e-01h 1\n", + " 3 0.0000000e+00 4.71e-01 3.04e+03 -2.0 1.52e+00 - 9.90e-01 2.28e-01h 1\n", + " 4 0.0000000e+00 2.30e-03 6.07e+00 -2.0 9.63e-01 - 1.00e+00 1.00e+00h 1\n", + " 5 0.0000000e+00 4.83e-04 3.06e-01 -2.0 2.58e-01 - 1.00e+00 1.00e+00h 1\n", + " 6 0.0000000e+00 2.19e-05 7.45e-02 -2.0 8.17e-02 - 1.00e+00 1.00e+00h 1\n", + " 7 0.0000000e+00 1.11e-04 5.82e+00 -3.0 6.83e-01 - 1.00e+00 1.00e+00H 1\n", + " 8 0.0000000e+00 9.95e-07 1.11e-02 -3.0 2.92e-02 - 1.00e+00 1.00e+00f 1\n", + " 9 0.0000000e+00 1.27e-06 2.77e-04 -3.0 2.14e-02 - 1.00e+00 1.00e+00h 1\n", "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", - " 10 0.0000000e+00 6.24e-02 2.14e+04 -1.0 1.06e+03 - 1.00e+00 1.00e+00h 1\n", - " 11 0.0000000e+00 2.30e-02 7.86e+03 -1.0 1.00e+00 0.0 5.53e-01 1.00e+00h 1\n", - " 12 0.0000000e+00 1.79e-02 6.12e+03 -1.0 2.54e+03 - 1.00e+00 2.50e-01f 3\n", - " 13 0.0000000e+00 1.68e-02 2.25e+03 -1.0 1.85e+03 - 9.04e-01 1.00e+00h 1\n", - " 14 0.0000000e+00 7.34e-03 8.29e+02 -1.0 1.12e+02 - 1.00e+00 1.00e+00h 1\n", - " 15 0.0000000e+00 5.21e-03 3.05e+02 -1.0 6.04e+02 - 1.00e+00 1.00e+00h 1\n", - " 16 0.0000000e+00 5.21e-03 1.47e+06 -1.0 2.54e+01 2.2 5.27e-06 8.21e-07H 1\n", - " 17 0.0000000e+00 1.38e-03 1.75e+02 -1.0 5.54e-01 2.7 1.00e+00 1.00e+00h 1\n", - " 18 0.0000000e+00 5.44e-04 1.56e+05 -1.0 2.08e+00 2.2 5.35e-01 1.00e+00H 1\n", - " 19 0.0000000e+00 6.97e-04 2.71e+05 -1.0 2.48e-01 2.6 2.43e-02 1.00e+00f 1\n", + " 10 0.0000000e+00 5.48e-07 1.46e+03 -4.5 6.14e-02 - 6.18e-01 1.00e+00H 1\n", + " 11 0.0000000e+00 6.02e-07 5.15e+02 -4.5 7.21e-02 - 6.08e-01 1.00e+00h 1\n", + " 12 0.0000000e+00 5.98e-07 1.18e-05 -4.5 1.04e-01 - 1.00e+00 1.00e+00h 1\n", + " 13 0.0000000e+00 5.29e-07 9.32e+02 -6.8 1.06e-01 - 2.12e-01 1.00e+00h 1\n", + " 14 0.0000000e+00 4.55e-07 1.72e+03 -6.8 1.46e-02 - 3.37e-01 1.00e+00h 1\n", + " 15 0.0000000e+00 4.14e-07 2.33e+03 -6.8 6.91e-03 - 5.64e-02 1.00e+00h 1\n", + " 16 0.0000000e+00 3.45e-07 2.90e+03 -6.8 1.01e-04 - 2.14e-01 1.00e+00h 1\n", + " 17 0.0000000e+00 5.97e-07 3.12e+03 -6.8 5.35e-04 - 1.37e-02 1.00e+00h 1\n", + " 18 0.0000000e+00 5.74e-07 3.25e+03 -6.8 1.60e-03 - 8.54e-02 1.00e+00h 1\n", + " 19 0.0000000e+00 5.30e-07 3.49e+03 -6.8 1.34e-03 - 8.76e-02 1.00e+00h 1\n", "iter objective inf_pr inf_du lg(mu) ||d|| lg(rg) alpha_du alpha_pr ls\n", - " 20 0.0000000e+00 6.97e-04 1.19e+05 -1.0 8.84e-02 2.1 6.98e-01 1.00e+00h 1\n", - " 21 0.0000000e+00 6.86e-04 1.15e+05 -1.0 8.39e+03 - 3.71e-02 1.56e-02f 7\n", - " 22 0.0000000e+00 6.65e-04 7.09e+04 -1.0 8.17e+03 - 3.85e-01 3.12e-02f 6\n", - " 23 0.0000000e+00 6.54e-04 6.97e+04 -1.0 7.73e+03 - 2.01e-02 1.56e-02f 7\n", - " 24 0.0000000e+00 6.49e-04 7.20e+04 -1.0 7.53e+03 - 1.70e-01 7.81e-03f 8\n", - " 25 0.0000000e+00 6.39e-04 6.85e+04 -1.0 7.43e+03 - 7.02e-02 1.56e-02f 7\n", - " 26 0.0000000e+00 2.53e-03 5.60e+04 -1.0 7.23e+03 - 8.68e-01 2.50e-01f 3\n", - " 27 0.0000000e+00 2.39e-04 3.14e+03 -1.0 4.19e+03 - 6.72e-01 1.00e+00H 1\n", - " 28 0.0000000e+00 1.78e-05 1.20e+02 -1.0 6.59e-03 1.7 9.48e-01 1.00e+00h 1\n", + " 20 0.0000000e+00 7.79e-07 3.66e+03 -6.8 1.62e-04 - 1.95e-01 1.00e+00h 1\n", + " 21 0.0000000e+00 9.49e-07 3.26e+02 -6.8 1.01e-04 - 2.10e-01 1.00e+00h 1\n", + " 22 0.0000000e+00 9.34e-08 1.84e+02 -6.8 8.77e-05 - 4.61e-01 1.00e+00h 1\n", + "Cannot recompute multipliers for feasibility problem. Error in eq_mult_calculator\n", "\n", - "Number of Iterations....: 28\n", + "Number of Iterations....: 22\n", "\n", " (scaled) (unscaled)\n", "Objective...............: 0.0000000000000000e+00 0.0000000000000000e+00\n", - "Dual infeasibility......: 0.0000000000000000e+00 0.0000000000000000e+00\n", - "Constraint violation....: 1.7770481779855499e-05 1.7770481779855499e-05\n", + "Dual infeasibility......: 5.3733521937880550e+02 5.3733521937880549e+04\n", + "Constraint violation....: 9.3390433969156556e-08 9.3390433969156556e-08\n", "Complementarity.........: 0.0000000000000000e+00 0.0000000000000000e+00\n", - "Overall NLP error.......: 1.7770481779855499e-05 1.7770481779855499e-05\n", + "Overall NLP error.......: 9.3390433969156556e-08 5.3733521937880549e+04\n", "\n", "\n", - "Number of objective function evaluations = 93\n", - "Number of objective gradient evaluations = 29\n", - "Number of equality constraint evaluations = 93\n", + "Number of objective function evaluations = 27\n", + "Number of objective gradient evaluations = 23\n", + "Number of equality constraint evaluations = 27\n", "Number of inequality constraint evaluations = 0\n", - "Number of equality constraint Jacobian evaluations = 29\n", + "Number of equality constraint Jacobian evaluations = 23\n", "Number of inequality constraint Jacobian evaluations = 0\n", - "Number of Lagrangian Hessian evaluations = 28\n", - "Total CPU secs in IPOPT (w/o function evaluations) = 41.744\n", - "Total CPU secs in NLP function evaluations = 2.465\n", + "Number of Lagrangian Hessian evaluations = 22\n", + "Total CPU secs in IPOPT (w/o function evaluations) = 13.452\n", + "Total CPU secs in NLP function evaluations = 2.432\n", "\n", - "EXIT: Optimal Solution Found.\n" + "EXIT: Optimal Solution Found.\n", + "addfunc: duplicate function cubic_root_l\n", + "addfunc: duplicate function cubic_root_h\n", + "addfunc: duplicate function cubic_root_l_nan\n", + "addfunc: duplicate function cubic_root_h_nan\n", + "addfunc: duplicate function cubic_root_l_ext\n", + "addfunc: duplicate function cubic_root_h_ext\n", + "addfunc: duplicate function cbrt\n", + "addfunc: duplicate function x_over_exp_x_minus_one\n" ] } ], "source": [ "# After the initial solve, setup the flowsheet to be controlled by changing the ROM inputs\n", - "# from solved state, re-solving with new inputs takes ~ 30 minutes\n", - "\n", - "# to ensure overall convergence and discourage local minimia that end in solver loops,\n", - "# we take a similar approach as the flowsheet solve and only calculate 25 iterations at a time\n", "\n", "# current density\n", "m.fs.SOFC.current_density.fix(4000)\n", @@ -982,32 +437,15 @@ "m.fs.SOFC.deltaT_cell.unfix()\n", "m.fs.SOFC.air_util.fix(0.4488)\n", "\n", - "from idaes.core.util.model_statistics import degrees_of_freedom\n", - "\n", "print(\"DOF = \", degrees_of_freedom(m))\n", "print()\n", - "solver = pyo.SolverFactory(\"ipopt\")\n", - "solver.options = {\n", - " \"max_iter\": 50,\n", - " \"tol\": 1e-4,\n", - " \"bound_push\": 1e-8,\n", - " \"linear_solver\": \"ma57\",\n", - " \"ma57_pivtol\": 1e-3,\n", - " \"OF_ma57_automatic_scaling\": \"yes\",\n", - " \"nlp_scaling_method\": \"user-scaling\",\n", - "}\n", - "solve_iteration = 0\n", - "for i in range(1, 10): # keep looping until condition is met\n", - " solve_iteration += 1\n", - " print(\"Solve # \", solve_iteration)\n", - " status = solver.solve(m, tee=True)\n", - " if \"Optimal Solution Found\" in status.solver.message:\n", - " break" + "\n", + "status = solver_ma97.solve(m, tee=True)" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "metadata": { "tags": [ "testing" @@ -1032,9 +470,9 @@ "\n", "import pytest\n", "\n", - "assert pytest.approx(660.608, rel=1e-5) == value(m.fs.net_power)\n", - "assert pytest.approx(0.625588, rel=1e-5) == value(m.fs.HHV_efficiency)\n", - "assert pytest.approx(291.249, rel=1e-5) == value(m.fs.CO2_emissions)\n", + "assert pytest.approx(659.8, rel=1e-1) == value(m.fs.net_power)\n", + "assert pytest.approx(0.6248, rel=1e-4) == value(m.fs.HHV_efficiency)\n", + "assert pytest.approx(291.2, rel=1e-1) == value(m.fs.CO2_emissions)\n", "\n", "print(\"Problem solved successfully\")" ] @@ -1047,16 +485,16 @@ "\n", "The results of the simulation can be viewed in the SVG file below.\n", "\n", - "It can be seen from the figure that the bulk of the power generated by the NGFC plant is from the SOFCs (542.6 MW). The steam turbine and natural gas expander both contribute smaller amounts at 107.3 and 21.1 MW, respectively. The auxiliary load from the recycle blowers and air compressors is only 10.4 MW. The net power of the system is 660.6 MW.\n", + "It can be seen from the figure that the bulk of the power generated by the NGFC plant is from the SOFCs (541.9 MW). The steam turbine and natural gas expander both contribute smaller amounts at 107.1 and 21.1 MW, respectively. The auxiliary load from the recycle blowers and air compressors is only 10.3 MW. The net power of the system is 659.8 MW.\n", "\n", - "The higher heating value of the natural gas feed is 908,839 J/mol. Multiplying by the inlet flowrate of 1,161 mol/s gives a total thermal input of 1056 MW. Based on the thermal input and the net generation the efficiency is 62.56%. The carbon emissions are 291.2 g/kWh.\n", + "The higher heating value of the natural gas feed is 908,839 J/mol. Multiplying by the inlet flowrate of 1,161 mol/s gives a total thermal input of 1056 MW. Based on the thermal input and the net generation the efficiency is 62.48%. The carbon emissions are 291.2 g/kWh.\n", "\n", - "The results also show the closure of the energy balance around the SOFC. The heat duty of the anode is -673.92 MW and the duty of the cathode is 114.57 MW. When added together they produce the DC stack power with an absolute value of 559.3 MW." + "The results also show the closure of the energy balance around the SOFC. The heat duty of the anode is -673.0 MW and the duty of the cathode is 114.3 MW. When added together they produce the DC stack power with an absolute value of 558.7 MW." ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -1070,16 +508,16 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "image/svg+xml": [ - "image/svg+xml\n", - "\t\n", + "image/svg+xml\n", + "\t\n", "\t\t\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\t\n", "\t\t\n", "\t\n", @@ -1151,434 +589,434 @@ "\t\n", "\n", "\t\n", - "\t\t\n", - "\t\t\t\n", + "\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\n", + "\t\n", "\t\n", "\t\t\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\t\n", "\t\t\n", "\t\n", "\t\n", "\t\tFor SVG HRSG\n", - "\t\t\n", + "\t\t\n", "\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.1\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.2\n", "\t\t\tSteam\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tSteam\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tSteam\t\t\n", + "\t\t\n", "\t\t\tSheet.3\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.4\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.5\n", "\t\t\tSOFC Stack\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tSOFC Stack\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tSOFC Stack\t\t\n", + "\t\t\n", "\t\t\tSheet.6\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.7\n", "\t\t\tAir\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tAir\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tAir\t\t\n", + "\t\t\n", "\t\t\tSheet.8\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.9\n", "\t\t\tCathode\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tCathode\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tCathode\t\t\n", + "\t\t\n", "\t\t\tSheet.10\n", "\t\t\tElectrolyte\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tElectrolyte\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tElectrolyte\t\t\n", + "\t\t\n", "\t\t\tSheet.11\n", "\t\t\tAnode\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tAnode\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tAnode\t\t\n", + "\t\t\n", "\t\t\tAC source\n", "\t\t\t\n", - "\t\t\t\t\n", - "\t\t\t\t\n", - "\t\t\t\t\n", - "\t\t\t\t\n", + "\t\t\t\t\n", + "\t\t\t\t\n", + "\t\t\t\t\n", + "\t\t\t\t\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\t\n", - "\t\t\t\t\n", - "\t\t\t\t\n", - "\t\t\t\t\n", + "\t\t\t\t\n", + "\t\t\t\t\n", + "\t\t\t\t\n", "\t\t\t\n", "\t\t\tInverter\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\t\tSheet.14\n", "\t\t\t\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\t\tSheet.15\n", "\t\t\t\t1\n", - "\t\t\t\t\n", - "\t\t\t\t\n", - "\t\t\t\t\n", + "\t\t\t\t\n", + "\t\t\t\t\n", + "\t\t\t\t\n", "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.16\n", "\t\t\tInverter\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tInverter\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tInverter\t\t\n", + "\t\t\n", "\t\t\tSheet.17\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.18\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.19\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.20\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.21\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.122\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.23\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.124\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.125\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.26\n", "\t\t\tAC\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tAC\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tAC\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.127\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.29\n", "\t\t\tCathode Blower\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tCathode Blower\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tCathode Blower\t\t\n", + "\t\t\n", "\t\t\tSheet.31\n", "\t\t\tCathode HTX\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tCathode HTX\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tCathode HTX\t\t\n", + "\t\t\n", "\t\t\tSheet.32\n", "\t\t\tAutothermal\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tAutothermal\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tAutothermal\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.78\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.134\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.35\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.136\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.138\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.137\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.139\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.40\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.41\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.43\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.144\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.109\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.110\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.50\n", "\t\t\tPreheat\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tPreheat\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tPreheat\t\t\n", + "\t\t\n", "\t\t\tSheet.51\n", "\t\t\tAutothermal Reformer\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tAutothermal Reformer\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tAutothermal Reformer\t\t\n", + "\t\t\n", "\t\t\tSheet.52\n", "\t\t\tAnode HTX\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tAnode HTX\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tAnode HTX\t\t\n", + "\t\t\n", "\t\t\tSheet.53\n", "\t\t\tCombustor\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tCombustor\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tCombustor\t\t\n", + "\t\t\n", "\t\t\tSheet.54\n", "\t\t\tNatural Gas\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tNatural Gas\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tNatural Gas\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.155\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.62\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.64\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.167\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.66\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.67\n", "\t\t\tAir\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tAir\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tAir\t\t\n", + "\t\t\n", "\t\t\tSheet.68\n", "\t\t\tHeat Recovery Steam Generator\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tHeat Recovery Steam Generator\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tHeat Recovery Steam Generator\t\t\n", + "\t\t\n", "\t\t\tSheet.69\n", "\t\t\tSteam Turbine Generator\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tSteam Turbine Generator\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tSteam Turbine Generator\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.154\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tAC source.160\n", "\t\t\t\n", - "\t\t\t\t\n", - "\t\t\t\t\n", - "\t\t\t\t\n", - "\t\t\t\t\n", + "\t\t\t\t\n", + "\t\t\t\t\n", + "\t\t\t\t\n", + "\t\t\t\t\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.72\n", "\t\t\tAC\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tAC\t\t\n", - "\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tAC\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.170\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.172\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.191\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.192\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.132\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.193\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.42\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tDynamic connector.96\n", - "\t\t\t\n", + "\t\t\t\n", "\t\t\n", - "\t\t\n", + "\t\t\n", "\t\t\tSheet.98\n", "\t\t\tPre-Reformer\n", "\t\t\t\n", - "\t\t\t\n", - "\t\t\t\n", - "\t\t\tPre-Reformer\t\t\n", + "\t\t\t\n", + "\t\t\t\n", + "\t\t\tPre-Reformer\t\t\n", "\t\n", - "621 K137 kPaSYN_INT:P:3,642 mol/sF:979 K137 kPaANODE_RECT:P:4,084 mol/sF:890 K105 kPaCATH_INT:P:34,197 mol/sF:998 K104 kPaCATH_OUTT:P:32,525 mol/sF:998 K104 kPaCATH_HX_HIT:P:16,262 mol/sF:288 K101 kPaAIRT:P:17,934 mol/sF:834 K137 kPaANODE_INT:P:8,041 mol/sF:978 K137 kPaANO_HX_HIT:P:5,084 mol/sF:825 K136 kPaANO_HX_HOT:P:5,084 mol/sF:814 K137 kPaANO_HX_CIT:P:7,727 mol/sF:978 K137 kPaANODE_OUTT:P:9,169 mol/sF:476 K102 kPaCATH_HX_HOT:P:16,262 mol/sF:1,002 K105 kPaCATH_RECT:P:16,262 mol/sF:297 K111 kPaCATH_HX_CIT:P:17,934 mol/sF:787 K105 kPaCATH_HX_COT:P:17,934 mol/sF:348.30.6617.3ROM InputsFuel Inlet Temperature (C):Internal Reformation fraction:Air Inlet Temperature (C):4,000Avg. Curent Density (A/m^2):0.5Air Recirculation fraction:2.1Oxygen to Carbon ratio:Fuel Utilization fraction:Air Utilization fraction:0.80.449559.3ROM OutputsDC Stack Power (MW):0.8668Stack Voltage (V):30.6265.17-673.92Heat Duties (MW)Anode Heat Exchanger:Cathode Heat Exchanger:Anode:48.1Reformer Recuperator:114.57Cathode:422 K206 kPaSTEAM_INT:P:464 mol/sF:310 K203 kPaAIR_INT:P:1,333 mol/sF:747 K206 kPaREF_INT:P:465 mol/sF:288 K3,447 kPaFUEL_INT:P:1,161 mol/sF:1,060 K137 kPaREF_OUTT:P:2,944 mol/sF:1,265 K95 kPaCOMB_OUTT:P:9,545 mol/sF:405 K94 kPaANOD_EXHT:P:9,546 mol/sF:476 K102 kPaCOMB_AIRT:P:4,878 mol/sF:405 K101 kPaCATH_EXHT:P:11,384 mol/sF:476 K102 kPaCATH_HRSG_INT:P:11,384 mol/sF:107.321.110.4Performance SummarySteam Turbine Power (MW):NG Expander Power (MW):Auxiliary Load (MW):542.6AC Stack Power (MW):660.6Net Power (MW):Thermal Input (MW):HHV Efficiency (%):CO2 Emissions (g/kWh):1,056.0291.262.561,012 K3,447 kPaHOT_FUELT:P:1,161 mol/sF:" + "621 K137 kPaSYN_INT:P:3,642 mol/sF:979 K137 kPaANODE_RECT:P:4,084 mol/sF:890 K105 kPaCATH_INT:P:34,197 mol/sF:998 K104 kPaCATH_OUTT:P:32,525 mol/sF:998 K104 kPaCATH_HX_HIT:P:16,262 mol/sF:288 K101 kPaAIRT:P:17,934 mol/sF:834 K137 kPaANODE_INT:P:8,041 mol/sF:978 K137 kPaANO_HX_HIT:P:5,084 mol/sF:825 K136 kPaANO_HX_HOT:P:5,084 mol/sF:814 K137 kPaANO_HX_CIT:P:7,727 mol/sF:978 K137 kPaANODE_OUTT:P:9,169 mol/sF:476 K102 kPaCATH_HX_HOT:P:16,262 mol/sF:1,002 K105 kPaCATH_RECT:P:16,262 mol/sF:297 K111 kPaCATH_HX_CIT:P:17,934 mol/sF:787 K105 kPaCATH_HX_COT:P:17,934 mol/sF:348.30.6617.3ROM InputsFuel Inlet Temperature (C):Internal Reformation fraction:Air Inlet Temperature (C):4,000Avg. Current Density (A/m^2):0.5Air Recirculation fraction:2.1Oxygen to Carbon ratio:Fuel Utilization fraction:Air Utilization fraction:0.80.449559.3ROM OutputsDC Stack Power (MW):0.8668Stack Voltage (V):30.6265.17-673.92Heat Duties (MW)Anode Heat Exchanger:Cathode Heat Exchanger:Anode:48.1Reformer Recuperator:114.57Cathode:422 K206 kPaSTEAM_INT:P:464 mol/sF:310 K203 kPaAIR_INT:P:1,333 mol/sF:747 K206 kPaREF_INT:P:465 mol/sF:288 K3,447 kPaFUEL_INT:P:1,161 mol/sF:1,060 K137 kPaREF_OUTT:P:2,944 mol/sF:1,265 K95 kPaCOMB_OUTT:P:9,545 mol/sF:405 K94 kPaANOD_EXHT:P:9,546 mol/sF:476 K102 kPaCOMB_AIRT:P:4,878 mol/sF:405 K101 kPaCATH_EXHT:P:11,384 mol/sF:476 K102 kPaCATH_HRSG_INT:P:11,384 mol/sF:107.321.110.4Performance SummarySteam Turbine Power (MW):NG Expander Power (MW):Auxiliary Load (MW):542.6AC Stack Power (MW):660.6Net Power (MW):Thermal Input (MW):HHV Efficiency (%):CO2 Emissions (g/kWh):1,056.0291.262.561,012 K3,447 kPaHOT_FUELT:P:1,161 mol/sF:" ], "text/plain": [ "" @@ -1608,7 +1046,7 @@ "metadata": { "celltoolbar": "Tags", "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -1622,7 +1060,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.12" + "version": "3.7.3" } }, "nbformat": 4, diff --git a/idaes_examples/archive/power_gen/rsofc/cpu.py b/idaes_examples/archive/power_gen/rsofc/cpu.py index c7c6d9fe..9bb504da 100644 --- a/idaes_examples/archive/power_gen/rsofc/cpu.py +++ b/idaes_examples/archive/power_gen/rsofc/cpu.py @@ -67,10 +67,7 @@ import idaes.logger as idaeslog # Import Pyomo environment and network -from pyomo.environ import (Var, - units as pyunits, - Constraint, - assert_optimal_termination) +from pyomo.environ import Var, units as pyunits, Constraint, assert_optimal_termination from pyomo.network import Port import cpu_surrogate_methods as sm @@ -126,15 +123,15 @@ def build(self): self.vent.add(self.vent_mole_frac_comp, "mole_frac_comp") def make_vars(self): - """ This section is for creating all the vars for this model. - There are 1 inlet and 3 outlet streams. - These streams are names as inlet, pureco2, water, vent - For each of these streams, the following variables are defined: - (1) Total mole flow [mol/s]: [stream_name]_flow_mol - (2) Component molar fraction: [stream_name]_mole_frac_comp - (3) Component mole flows [mol/s]: [stream_name]_flow_mol_comp - (4) Temperature [K]: [stream_name]_temperature - (5) Pressure [Pa]: [stream_name]_pressure + """This section is for creating all the vars for this model. + There are 1 inlet and 3 outlet streams. + These streams are names as inlet, pureco2, water, vent + For each of these streams, the following variables are defined: + (1) Total mole flow [mol/s]: [stream_name]_flow_mol + (2) Component molar fraction: [stream_name]_mole_frac_comp + (3) Component mole flows [mol/s]: [stream_name]_flow_mol_comp + (4) Temperature [K]: [stream_name]_temperature + (5) Pressure [Pa]: [stream_name]_pressure """ # units declaration for vars @@ -254,7 +251,7 @@ def make_vars(self): doc="Vent temperature [K]", ) - # Pressue [Pa] + # Pressure [Pa] self.inlet_pressure = Var( self.flowsheet().config.time, initialize=17, @@ -281,9 +278,9 @@ def make_vars(self): ) def add_material_balances(self): - """ This section is for material balance constraints""" + """This section is for material balance constraints""" - # Sum of all componenet mole fractions in a stream equals 1 + # Sum of all components mole fractions in a stream equals 1 @self.Constraint( self.flowsheet().config.time, doc="PureCO2 stream: component mole flow equation", @@ -372,7 +369,7 @@ def component_material_balance_eqn(b, t, c): ) def add_surrogates(self): - """ This section is to add the surrogate models""" + """This section is to add the surrogate models""" # Compressure heat duty @self.Expression( @@ -640,7 +637,7 @@ def initialize(blk, outlvl=idaeslog.NOTSET, solver=None, optarg=None): outlvl : sets output level of initialisation routine optarg : solver options dictionary object (default={'tol': 1e-6}) - solver : str indicating whcih solver to use during + solver : str indicating which solver to use during initialization (default = 'ipopt') Returns: diff --git a/idaes_examples/archive/power_gen/rsofc/rsofc_costing.py b/idaes_examples/archive/power_gen/rsofc/rsofc_costing.py index bcec0996..454f3b61 100644 --- a/idaes_examples/archive/power_gen/rsofc/rsofc_costing.py +++ b/idaes_examples/archive/power_gen/rsofc/rsofc_costing.py @@ -24,22 +24,27 @@ QGESSCosting, QGESSCostingData, ) -from idaes.models.costing.SSLW import( - SSLWCosting, - SSLWCostingData, - HXType) +from idaes.models.costing.SSLW import SSLWCosting, SSLWCostingData, HXType from idaes.core import FlowsheetBlock, UnitModelBlock, UnitModelCostingBlock -def add_total_plant_cost(b, installation_labor=1.25, eng_fee=1.2, - project_contingency=1.15, process_contingency=1.15, - CE_index_units=pyo.units.MUSD_2018): +def add_total_plant_cost( + b, + installation_labor=1.25, + eng_fee=1.2, + project_contingency=1.15, + process_contingency=1.15, + CE_index_units=pyo.units.MUSD_2018, +): @b.costing.Expression() def total_plant_cost(c): - return (pyo.units.convert(c.capital_cost, CE_index_units) * - installation_labor * eng_fee * - (project_contingency + process_contingency)) + return ( + pyo.units.convert(c.capital_cost, CE_index_units) + * installation_labor + * eng_fee + * (project_contingency + process_contingency) + ) def get_rsofc_sofc_capital_cost(fs, CE_index_year): @@ -48,10 +53,13 @@ def get_rsofc_sofc_capital_cost(fs, CE_index_year): # SOEC-only cap costs for water-side equipment and H2 compr. will be added # NGFC_TPC = 752.55 # MM$ - CE_index_units = getattr(pyo.units, "MUSD_"+CE_index_year) # millions of USD in base year + CE_index_units = getattr( + pyo.units, "MUSD_" + CE_index_year + ) # millions of USD in base year - fs.NGFC_TPC = pyo.Var(initialize=752.55, units=CE_index_units, - doc="total plant cost in $MM") + fs.NGFC_TPC = pyo.Var( + initialize=752.55, units=CE_index_units, doc="total plant cost in $MM" + ) # build cost constraints fs.costing.build_process_costs( @@ -69,9 +77,7 @@ def get_rsofc_sofc_capital_cost(fs, CE_index_year): # costing initialization QGESSCostingData.costing_initialization(fs.costing) - calculate_variable_from_constraint( - fs.costing.total_TPC, - fs.costing.total_TPC_eq) + calculate_variable_from_constraint(fs.costing.total_TPC, fs.costing.total_TPC_eq) def get_rsofc_soec_capital_cost(fs, CE_index_year): @@ -81,7 +87,9 @@ def get_rsofc_soec_capital_cost(fs, CE_index_year): # For rsofc_soec mode - O2 and ng preheater costs are captured in NGFC_TPC # Water treatment and CO2 processing capcosts included in NGFC_TPC - CE_index_units = getattr(pyo.units, "MUSD_"+CE_index_year) # millions of USD in base year + CE_index_units = getattr( + pyo.units, "MUSD_" + CE_index_year + ) # millions of USD in base year # U-tube HXs - bhx1, oxygen_preheater, and ng_preheater # costed with IDAES generic heat exchanger correlation @@ -106,16 +114,17 @@ def get_rsofc_soec_capital_cost(fs, CE_index_year): @fs.fuel_recycle_heater.costing.Expression() def total_plant_cost(b): - U = 100 * pyo.units.W / pyo.units.m ** 2 / pyo.units.K + U = 100 * pyo.units.W / pyo.units.m**2 / pyo.units.K DT = 50 * pyo.units.K area = fs.fuel_recycle_heater.heat_duty[0] / U / DT # Add factor of two to pathways cost to account for corrosion-resistant materials for trim heaters return pyo.units.convert( - 2*81.88 * pyo.value( - pyo.units.convert(area, pyo.units.ft**2) - ) * - pyo.units.USD_2018, - CE_index_units) + 2 + * 81.88 + * pyo.value(pyo.units.convert(area, pyo.units.ft**2)) + * pyo.units.USD_2018, + CE_index_units, + ) # adding cost for air_preheater_2 fs.air_preheater_2.costing = UnitModelCostingBlock( @@ -129,11 +138,12 @@ def total_plant_cost(b): @fs.air_preheater_2.costing.Expression() def total_plant_cost(b): return pyo.units.convert( - 2*81.88 * pyo.value( - pyo.units.convert(fs.air_preheater_2.area, pyo.units.ft**2) - ) * - pyo.units.USD_2018, - CE_index_units) + 2 + * 81.88 + * pyo.value(pyo.units.convert(fs.air_preheater_2.area, pyo.units.ft**2)) + * pyo.units.USD_2018, + CE_index_units, + ) # H2 compressor fs.hcmp01.costing = UnitModelCostingBlock( @@ -156,8 +166,7 @@ def total_plant_cost(c): ref_param = 44369 * pyo.units.lb / pyo.units.hr alpha = 0.7 return pyo.units.convert( - 2 * ref_cost * (h2_comp_process_param / ref_param) ** alpha, - CE_index_units + 2 * ref_cost * (h2_comp_process_param / ref_param) ** alpha, CE_index_units ) # build cost constraints @@ -175,9 +184,7 @@ def total_plant_cost(c): # costing initialization QGESSCostingData.costing_initialization(fs.costing) - calculate_variable_from_constraint( - fs.costing.total_TPC, - fs.costing.total_TPC_eq) + calculate_variable_from_constraint(fs.costing.total_TPC, fs.costing.total_TPC_eq) def lock_rsofc_soec_capital_cost(fs): @@ -198,7 +205,9 @@ def lock_rsofc_sofc_capital_cost(fs): def get_rsofc_sofc_fixed_OM_costing( - fs, design_rsofc_netpower=650 * pyo.units.MW, fixed_TPC=None, + fs, + design_rsofc_netpower=650 * pyo.units.MW, + fixed_TPC=None, CE_index_year="2018", ): @@ -208,12 +217,14 @@ def get_rsofc_sofc_fixed_OM_costing( # fixed O&M costs - CE_index_units = getattr(pyo.units, "MUSD_"+CE_index_year) # millions of USD in base year + CE_index_units = getattr( + pyo.units, "MUSD_" + CE_index_year + ) # millions of USD in base year - fs.fixed_TPC = pyo.Var(initialize=fixed_TPC, units=CE_index_units, - doc="total plant cost in $MM") - fs.net_power = pyo.Var(initialize=design_rsofc_netpower, - units=pyo.units.MW) + fs.fixed_TPC = pyo.Var( + initialize=fixed_TPC, units=CE_index_units, doc="total plant cost in $MM" + ) + fs.net_power = pyo.Var(initialize=design_rsofc_netpower, units=pyo.units.MW) # build cost constraints fs.costing.build_process_costs( total_plant_cost=fs.fixed_TPC, @@ -247,17 +258,21 @@ def stack_replacement_cost(costing): def get_rsofc_soec_fixed_OM_costing( - fs, design_rsofc_netpower=650 * pyo.units.MW, fixed_TPC=None, + fs, + design_rsofc_netpower=650 * pyo.units.MW, + fixed_TPC=None, CE_index_year="2018", ): # fixed O&M costs - CE_index_units = getattr(pyo.units, "MUSD_"+CE_index_year) # millions of USD in base year + CE_index_units = getattr( + pyo.units, "MUSD_" + CE_index_year + ) # millions of USD in base year - fs.fixed_TPC = pyo.Var(initialize=fixed_TPC, units=CE_index_units, - doc="total plant cost in $MM") - fs.net_power = pyo.Var(initialize=design_rsofc_netpower, - units=pyo.units.MW) + fs.fixed_TPC = pyo.Var( + initialize=fixed_TPC, units=CE_index_units, doc="total plant cost in $MM" + ) + fs.net_power = pyo.Var(initialize=design_rsofc_netpower, units=pyo.units.MW) # build cost constraints fs.costing.build_process_costs( total_plant_cost=fs.fixed_TPC, @@ -295,7 +310,7 @@ def NG_energy_rate(fs, t): @fs.Expression(fs.time) def water_withdrawal(fs, t): # cm^3/s molar_mass = 18.015 * pyo.units.g / pyo.units.mol - density = 0.997 * pyo.units.g / pyo.units.cm ** 3 + density = 0.997 * pyo.units.g / pyo.units.cm**3 return ( molar_mass / density @@ -483,7 +498,7 @@ def methanation_catalyst_use(fs, t): 3.2 * syngas_flow / 611167 - * pyo.units.m ** 3 + * pyo.units.m**3 / pyo.units.day * pyo.units.hr / pyo.units.lb @@ -505,11 +520,12 @@ def methanation_catalyst_use(fs, t): prices = { "desulfur adsorbent": 6.0297 * pyo.units.USD_2018 / pyo.units.lb, - "methanation catalyst": 601.765 * pyo.units.USD_2018 / pyo.units.m ** 3, + "methanation catalyst": 601.765 * pyo.units.USD_2018 / pyo.units.m**3, } - fs.costing.get_variable_OM_costs(fs, fs.net_power, resources, rates, prices, - CE_index_year=CE_index_year) + fs.costing.get_variable_OM_costs( + fs, fs.net_power, resources, rates, prices, CE_index_year=CE_index_year + ) # initialize variable costs QGESSCostingData.initialize_variable_OM_costs(fs.costing) @@ -518,25 +534,32 @@ def methanation_catalyst_use(fs, t): def display_rsofc_costing(fs): print("Capital cost: ${:.0f}M".format(pyo.value(fs.costing.total_TPC))) print( - "Fixed O&M cost: ${:.1f}M/yr".format( - pyo.value(fs.costing.total_fixed_OM_cost) - ) + "Fixed O&M cost: ${:.1f}M/yr".format(pyo.value(fs.costing.total_fixed_OM_cost)) ) print( "Electricity cost: ${:.2f}/kg H2".format( - pyo.value(fs.costing.variable_operating_costs[0, "electricity"] - * fs.net_power[0] / fs.h2_product_rate_mass[0]) + pyo.value( + fs.costing.variable_operating_costs[0, "electricity"] + * fs.net_power[0] + / fs.h2_product_rate_mass[0] + ) ) ) print( "Fuel cost: ${:.2f}/kg H2".format( - pyo.value(fs.costing.variable_operating_costs[0, "natural_gas"] - * fs.net_power[0] / fs.h2_product_rate_mass[0]) + pyo.value( + fs.costing.variable_operating_costs[0, "natural_gas"] + * fs.net_power[0] + / fs.h2_product_rate_mass[0] + ) ) ) print( "Total variable O&M cost: ${:.2f}/kg H2".format( - pyo.value(fs.costing.total_variable_OM_cost[0] - * fs.net_power[0] / fs.h2_product_rate_mass[0]) + pyo.value( + fs.costing.total_variable_OM_cost[0] + * fs.net_power[0] + / fs.h2_product_rate_mass[0] + ) ) ) diff --git a/idaes_examples/archive/power_gen/rsofc/rsofc_soec_flowsheet.py b/idaes_examples/archive/power_gen/rsofc/rsofc_soec_flowsheet.py index 9cc0e89c..74a68a8a 100644 --- a/idaes_examples/archive/power_gen/rsofc/rsofc_soec_flowsheet.py +++ b/idaes_examples/archive/power_gen/rsofc/rsofc_soec_flowsheet.py @@ -153,7 +153,10 @@ def add_properties(fs): fs.h2_compress_prop.set_default_scaling("mole_frac_phase_comp", 1e2) fs.CO2_H2O_VLE = GenericParameterBlock( - **get_prop(components=air_comp, phases=["Vap", "Liq"],) + **get_prop( + components=air_comp, + phases=["Vap", "Liq"], + ) ) fs.CO2_H2O_VLE.set_default_scaling("mole_frac_comp", 1e2) fs.CO2_H2O_VLE.set_default_scaling("mole_frac_phase_comp", 1e2) @@ -202,7 +205,7 @@ def add_asu(fs): fs.intercooler_s2.outlet.temperature.fix(310.93) # K (100 F) fs.intercooler_s2.deltaP.fix(-3447) # Pa (-0.5 psi) - # air seperation unit + # air separation unit fs.ASU.split_fraction[0, "O2_outlet", "CO2"].fix(1e-10) fs.ASU.split_fraction[0, "O2_outlet", "H2O"].fix(1e-10) fs.ASU.split_fraction[0, "O2_outlet", "N2"].fix(0.0005) @@ -247,7 +250,8 @@ def add_preheater(fs): tube={"property_package": fs.fg_prop}, ) fs.preheat_split = gum.Separator( - property_package=fs.air_prop, outlet_list=["oxygen", "ng"], + property_package=fs.air_prop, + outlet_list=["oxygen", "ng"], ) ########################################################################### @@ -401,7 +405,7 @@ def add_aux_boiler_steam(fs): ) # enthalpy outlet fs.bhx2.outlet.enth_mol.fix( h_bhx2 - ) # K (100 F) # unfix after initalize and spec Q from cmb + ) # K (100 F) # unfix after initialize and spec Q from cmb fs.bhx1.overall_heat_transfer_coefficient.fix(100) fs.bhx1.delta_temperature_out.fix(10) # fix DT for pinch side @@ -512,10 +516,12 @@ def add_soec_unit(fs): fs.soec_stack.number_cells.fix(3.22e6) fs.spltf1 = gum.Separator( - property_package=fs.h2_prop, outlet_list=["out", "recycle"], + property_package=fs.h2_prop, + outlet_list=["out", "recycle"], ) fs.splta1 = gum.Separator( - property_package=fs.air_prop, outlet_list=["out", "recycle"], + property_package=fs.air_prop, + outlet_list=["out", "recycle"], ) ########################################################################### @@ -634,8 +640,10 @@ def add_soec_inlet_mix(fs): inlet_list=["air", "recycle"], momentum_mixing_type=gum.MomentumMixingType.none, ) - fs.fuel_recycle_heater = gum.Heater( # recycle heater for temperature control purposes - has_pressure_change=False, property_package=fs.h2_prop + fs.fuel_recycle_heater = ( + gum.Heater( # recycle heater for temperature control purposes + has_pressure_change=False, property_package=fs.h2_prop + ) ) ########################################################################### @@ -677,15 +685,20 @@ def h_side_mole_frac_expr(b, t, i): } ) - fs.s04 = Arc(source=fs.main_steam_split.h_side_adapt, destination=fs.mxf1.water,) + fs.s04 = Arc( + source=fs.main_steam_split.h_side_adapt, + destination=fs.mxf1.water, + ) ########################################################################### ########################################################################### fs.hr01 = Arc( # h2 rich air from soec recycle stream - source=fs.spltf1.recycle, destination=fs.fuel_recycle_heater.inlet, + source=fs.spltf1.recycle, + destination=fs.fuel_recycle_heater.inlet, ) fs.hr02 = Arc( # h2 rich air from soec recycle stream - source=fs.fuel_recycle_heater.outlet, destination=fs.mxf1.recycle, + source=fs.fuel_recycle_heater.outlet, + destination=fs.mxf1.recycle, ) fs.a03 = Arc(source=fs.air_preheater_2.tube_outlet, destination=fs.mxa1.air) fs.s05 = Arc( @@ -1407,7 +1420,7 @@ def set_guess(fs): fs.preheat_split.inlet, F=7765, T=700, P=1.04e5, comp=comp_guess, fix=True ) - # Set guess for temp, pressure and mole frac conditions to initalize soec + # Set guess for temp, pressure and mole frac conditions to initialize soec fs.soec_stack.fuel_inlet.flow_mol[0].fix(5600) fs.soec_stack.fuel_inlet.temperature[0].fix(1023.15) fs.soec_stack.fuel_inlet.pressure[0].fix(1.01325e5) @@ -1487,7 +1500,8 @@ def initialize_plant(fs, solver): 1.285 ) # Roughly at the thermoneutral point fs.soec_stack.initialize( - current_density_guess=-5000, temperature_guess=1023.15, # mA/cm2 + current_density_guess=-5000, + temperature_guess=1023.15, # mA/cm2 ) iinit.propagate_state(fs.h01) iinit.propagate_state(fs.o01) @@ -1681,7 +1695,11 @@ def add_scaling(fs): for (t, j), c in fs.cmb.control_volume.material_balances.items(): iscale.constraint_scaling_transform(c, 1e-1, overwrite=True) for ( - (t, p, j,), + ( + t, + p, + j, + ), c, ) in fs.cmb.control_volume.rate_reaction_stoichiometry_constraint.items(): iscale.constraint_scaling_transform(c, 1e-1, overwrite=True) @@ -1752,12 +1770,16 @@ def set_missing_scaling_and_bounds(fs): # catch some mole fraction variables that slipped through iscale.set_scaling_factor(fs.CPU_translator.properties_out[0.0].mole_frac_comp, 1e2) - iscale.set_scaling_factor(fs.flash.control_volume.properties_in[0.0].mole_frac_comp, 1e2) - iscale.set_scaling_factor(fs.flash.control_volume.properties_out[0.0].mole_frac_comp, 1e2) + iscale.set_scaling_factor( + fs.flash.control_volume.properties_in[0.0].mole_frac_comp, 1e2 + ) + iscale.set_scaling_factor( + fs.flash.control_volume.properties_out[0.0].mole_frac_comp, 1e2 + ) # correct lower bounds on mole fractions that default to 1e-20 for var in fs.component_data_objects(pyo.Var, descend_into=True): - if '.mole_frac_comp' in var.name and var.lb == 1e-20: + if ".mole_frac_comp" in var.name and var.lb == 1e-20: var.setlb(0) if var.value == 0: var.set_value(1e-10, skip_validation=True) @@ -1912,7 +1934,7 @@ def tags_for_pfd(fs): doc=f"{i}: volumetric flow", expr=s.flow_vol, format_string="{:.3f}", - display_units=pyo.units.m ** 3 / pyo.units.s, + display_units=pyo.units.m**3 / pyo.units.s, ) tag_group[f"{i}_T"] = iutil.ModelTag( doc=f"{i}: temperature", @@ -1964,7 +1986,7 @@ def tags_for_pfd(fs): doc="Average current density of SOEC", expr=fs.soec_stack.solid_oxide_cell.average_current_density[0], format_string="{:.0f}", - display_units=pyo.units.A / pyo.units.m ** 2, + display_units=pyo.units.A / pyo.units.m**2, ) tag_group["h2_product_rate_mass"] = iutil.ModelTag( expr=fs.h2_product_rate_mass[0], @@ -1982,7 +2004,9 @@ def tags_for_pfd(fs): display_units=pyo.units.kg / pyo.units.s, ) tag_group["net_power"] = iutil.ModelTag( - expr=fs.net_power[0], format_string="{:.3f}", display_units=pyo.units.MW, + expr=fs.net_power[0], + format_string="{:.3f}", + display_units=pyo.units.MW, ) tag_group["net_power_per_mass_h2"] = iutil.ModelTag( expr=fs.net_power_per_mass_h2[0], @@ -2321,7 +2345,8 @@ def average_current_density_constraint(b, t): ) m.soec_fs.obj = pyo.Objective( - expr=1e2 * m.soec_fs.costing.total_variable_OM_cost[0] + expr=1e2 + * m.soec_fs.costing.total_variable_OM_cost[0] / m.soec_fs.h2_product_rate_mass[0] ) @@ -2348,7 +2373,9 @@ def results_table_dataframe(result_variables): # Populate results table from results_variables list for var in result_variables: - variable_name.append(var[0].name) # make descriptive Var names and update to var.doc + variable_name.append( + var[0].name + ) # make descriptive Var names and update to var.doc variable_value.append(var[0].value) variable_units.append(pyo.units.get_units(var[0])) @@ -2522,8 +2549,9 @@ def average_current_density_constraint(b, t): ) ) - fs.obj = pyo.Objective(expr=1e2 * fs.costing.total_variable_OM_cost[0] - / fs.h2_product_rate_mass[0]) + fs.obj = pyo.Objective( + expr=1e2 * fs.costing.total_variable_OM_cost[0] / fs.h2_product_rate_mass[0] + ) options = { "max_iter": 150, @@ -2538,8 +2566,7 @@ def average_current_density_constraint(b, t): # Additional tags for variable O&M costs fs.tag_pfd["total_variable_OM_cost"] = iutil.ModelTag( expr=fs.costing.total_variable_OM_cost[0] - / pyo.units.convert(fs.h2_product_rate_mass[0], - pyo.units.kg/pyo.units.a), + / pyo.units.convert(fs.h2_product_rate_mass[0], pyo.units.kg / pyo.units.a), format_string="{:.3f}", display_units=pyo.units.MUSD_2018 / pyo.units.kg, doc="Total variable O&M cost $MM/kg hydrogen", @@ -2547,8 +2574,7 @@ def average_current_density_constraint(b, t): fs.tag_pfd["electricity_variable_OM_costs"] = iutil.ModelTag( expr=fs.costing.variable_operating_costs[0, "electricity"] - / pyo.units.convert(fs.h2_product_rate_mass[0], - pyo.units.kg/pyo.units.a), + / pyo.units.convert(fs.h2_product_rate_mass[0], pyo.units.kg / pyo.units.a), format_string="{:.3f}", display_units=pyo.units.MUSD_2018 / pyo.units.kg, doc="Electricity variable O&M cost $MM/kg hydrogen", @@ -2556,8 +2582,7 @@ def average_current_density_constraint(b, t): fs.tag_pfd["natural_gas_variable_OM_costs"] = iutil.ModelTag( expr=fs.costing.variable_operating_costs[0, "natural_gas"] - / pyo.units.convert(fs.h2_product_rate_mass[0], - pyo.units.kg/pyo.units.a), + / pyo.units.convert(fs.h2_product_rate_mass[0], pyo.units.kg / pyo.units.a), format_string="{:.3f}", display_units=pyo.units.MUSD_2018 / pyo.units.kg, doc="Natural gas variable O&M cost $MM/kg hydrogen", @@ -2565,8 +2590,7 @@ def average_current_density_constraint(b, t): fs.tag_pfd["water_variable_OM_costs"] = iutil.ModelTag( expr=fs.costing.variable_operating_costs[0, "water"] - / pyo.units.convert(fs.h2_product_rate_mass[0], - pyo.units.kg/pyo.units.a), + / pyo.units.convert(fs.h2_product_rate_mass[0], pyo.units.kg / pyo.units.a), format_string="{:.3f}", display_units=pyo.units.MUSD_2018 / pyo.units.kg, doc="Water variable O&M cost $MM/kg hydrogen", @@ -2574,8 +2598,7 @@ def average_current_density_constraint(b, t): fs.tag_pfd["water_treatment_chemicals_OM_costs"] = iutil.ModelTag( expr=fs.costing.variable_operating_costs[0, "water_treatment_chemicals"] - / pyo.units.convert(fs.h2_product_rate_mass[0], - pyo.units.kg/pyo.units.a), + / pyo.units.convert(fs.h2_product_rate_mass[0], pyo.units.kg / pyo.units.a), format_string="{:.3f}", display_units=pyo.units.MUSD_2018 / pyo.units.kg, doc="Water treatment chemicals O&M cost $MM/kg hydrogen", @@ -2732,7 +2755,6 @@ def get_model(m=None, name="SOEC Module"): # uncomment to optimize model # optimize_model(m.soec_fs) - # display_input_tags(m.soec_fs) write_pfd_results(m, "rsofc_soec_results.svg") diff --git a/idaes_examples/archive/power_gen/rsofc/rsofc_soec_mode_PFD.svg b/idaes_examples/archive/power_gen/rsofc/rsofc_soec_mode_PFD.svg index 4cb35a9d..7b7bdd24 100644 --- a/idaes_examples/archive/power_gen/rsofc/rsofc_soec_mode_PFD.svg +++ b/idaes_examples/archive/power_gen/rsofc/rsofc_soec_mode_PFD.svg @@ -753,7 +753,7 @@ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:2.82222px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583" id="tspan104298">compression + id="tspan124110">compression compression + id="tspan124110">compression Nitrogen ASU - Hydrogen purification andcompression + Hydrogen purification andcompression Water_in Water_out1 Water_out2 diff --git a/idaes_examples/archive/power_gen/rsofc/rsofc_soec_src.ipynb b/idaes_examples/archive/power_gen/rsofc/rsofc_soec_src.ipynb index d2d078fe..85c5c931 100644 --- a/idaes_examples/archive/power_gen/rsofc/rsofc_soec_src.ipynb +++ b/idaes_examples/archive/power_gen/rsofc/rsofc_soec_src.ipynb @@ -193,7 +193,7 @@ " Nitrogen\n", " ASU\n", " \n", - " Hydrogen purification andcompression\n", + " Hydrogen purification andcompression\n", " Water_in\n", " Water_out1\n", " Water_out2\n", diff --git a/idaes_examples/archive/power_gen/soec/soec.ipynb b/idaes_examples/archive/power_gen/soec/soec.ipynb index 35aec23e..2e4e7e19 100644 --- a/idaes_examples/archive/power_gen/soec/soec.ipynb +++ b/idaes_examples/archive/power_gen/soec/soec.ipynb @@ -8,7 +8,7 @@ "outputs": [], "source": [ "import os\n", - "from scipy.linalg import svd \n", + "from scipy.linalg import svd\n", "from IPython.core.display import SVG\n", "import pyomo.environ as pyo\n", "import idaes\n", @@ -24,7 +24,7 @@ " initialize_flowsheet_costing,\n", " scale_flowsheet_costing,\n", " get_soec_OM_costing,\n", - " display_soec_costing\n", + " display_soec_costing,\n", ")\n", "import idaes.core.util.model_statistics as mstat\n", "from idaes.models.properties import iapws95\n", @@ -44,10 +44,14 @@ "source": [ "from pyomo.core.expr.current import identify_variables\n", "from pyomo.common.collections import ComponentSet\n", + "\n", + "\n", "def find_active_constraints_containing_variable(var, blk):\n", " con_set = ComponentSet()\n", " CUID = pyo.ComponentUID(var)\n", - " for c in blk.component_data_objects(ctype=pyo.Constraint, active=True, descend_into=True):\n", + " for c in blk.component_data_objects(\n", + " ctype=pyo.Constraint, active=True, descend_into=True\n", + " ):\n", " for v in identify_variables(c.body):\n", " if CUID.matches(v):\n", " con_set.add(c)\n", @@ -214,7 +218,9 @@ } ], "source": [ - "m.fs.initialize_build(outlvl=idaeslog.INFO_LOW)#, load_from=\"soec_standalone_init.json.gz\")\n", + "m.fs.initialize_build(\n", + " outlvl=idaeslog.INFO_LOW\n", + ") # , load_from=\"soec_standalone_init.json.gz\")\n", "# m.fs.initialize_build(outlvl=idaeslog.DEBUG)" ] }, @@ -390,7 +396,9 @@ ], "source": [ "assert dof(m) == 0\n", - "res = solver.solve(m, tee=True, options={\"tol\": 1e-6, \"max_iter\": 300, \"halt_on_ampl_error\":\"no\"})\n", + "res = solver.solve(\n", + " m, tee=True, options={\"tol\": 1e-6, \"max_iter\": 300, \"halt_on_ampl_error\": \"no\"}\n", + ")\n", "assert res.solver.termination_condition == pyo.TerminationCondition.optimal\n", "assert res.solver.status == pyo.SolverStatus.ok" ] @@ -419,122 +427,143 @@ } ], "source": [ - "def set_indexed_variable_bounds(var,bounds):\n", + "def set_indexed_variable_bounds(var, bounds):\n", " for idx, subvar in var.items():\n", " subvar.bounds = bounds\n", + "\n", + "\n", "if True:\n", " m.fs.obj = pyo.Objective(\n", - " expr = (\n", - " m.fs.costing.annual_electricity_cost \n", + " expr=(\n", + " m.fs.costing.annual_electricity_cost\n", " + m.fs.costing.annual_water_cost\n", " + m.fs.costing.total_annualized_cost\n", " + m.fs.costing.annual_fixed_operations_and_maintenance_cost\n", " + m.fs.costing.annual_air_cost\n", " )\n", " )\n", - " \n", + "\n", " m.fs.max_raw_water_withdrawal_eqn.deactivate()\n", " m.fs.feed_heater.max_heat_duty_eqn.deactivate()\n", " m.fs.sweep_heater.max_heat_duty_eqn.deactivate()\n", " m.fs.heat_pump.max_heat_duty_eqn.deactivate()\n", - " \n", + "\n", " m.fs.max_raw_water_withdrawal_ineq.activate()\n", " m.fs.feed_heater.max_heat_duty_ineq.activate()\n", " m.fs.sweep_heater.max_heat_duty_ineq.activate()\n", " m.fs.heat_pump.max_heat_duty_ineq.activate()\n", - " \n", - " for hx in [m.fs.feed_hot_exchanger, m.fs.sweep_hot_exchanger, m.fs.sweep_medium_exchanger,\n", - " m.fs.water_evaporator01, m.fs.water_evaporator02, m.fs.water_evaporator03,\n", - " m.fs.water_evaporator04, m.fs.water_evaporator05, m.fs.water_preheater]:\n", + "\n", + " for hx in [\n", + " m.fs.feed_hot_exchanger,\n", + " m.fs.sweep_hot_exchanger,\n", + " m.fs.sweep_medium_exchanger,\n", + " m.fs.water_evaporator01,\n", + " m.fs.water_evaporator02,\n", + " m.fs.water_evaporator03,\n", + " m.fs.water_evaporator04,\n", + " m.fs.water_evaporator05,\n", + " m.fs.water_preheater,\n", + " ]:\n", " set_indexed_variable_bounds(hx.delta_temperature_in, (0, None))\n", " set_indexed_variable_bounds(hx.delta_temperature_out, (0, None))\n", " hx.area.bounds = (400, None)\n", " hx.area.unfix()\n", - " \n", + "\n", " # I suspect there are several solutions close in price with areas portioned\n", " # differerently between evaporators. Fix these areas to (hopefully) force\n", " # IPOPT to just choose one\n", " m.fs.water_evaporator01.area.fix(2018)\n", " m.fs.water_evaporator04.area.fix(5245)\n", - " \n", + "\n", " @m.fs.Constraint(m.fs.time)\n", - " def water_evaporator02_vapor_out_ineq(b,t):\n", + " def water_evaporator02_vapor_out_ineq(b, t):\n", " try:\n", - " tdew = b.water_evaporator02.shell.properties_out[t].temperature_dew[\"Vap\",\"Liq\"]\n", + " tdew = b.water_evaporator02.shell.properties_out[t].temperature_dew[\n", + " \"Vap\", \"Liq\"\n", + " ]\n", " except KeyError:\n", - " tdew = b.water_evaporator02.shell.properties_out[t].temperature_dew[\"Liq\",\"Vap\"]\n", - " return (\n", - " b.water_evaporator02.shell.properties_out[t].temperature \n", - " >= tdew\n", - " )\n", - "# @m.fs.Constraint(m.fs.time)\n", - "# def water_evaporator03_vapor_out_ineq(b,t):\n", - "# try:\n", - "# tdew = b.water_evaporator03.shell.properties_out[t].temperature_dew[\"Vap\",\"Liq\"]\n", - "# except KeyError:\n", - "# tdew = b.water_evaporator03.shell.properties_out[t].temperature_dew[\"Liq\",\"Vap\"]\n", - "# return (\n", - "# b.water_evaporator03.shell.properties_out[t].temperature \n", - "# >= tdew\n", - "# )\n", + " tdew = b.water_evaporator02.shell.properties_out[t].temperature_dew[\n", + " \"Liq\", \"Vap\"\n", + " ]\n", + " return b.water_evaporator02.shell.properties_out[t].temperature >= tdew\n", + "\n", + " # @m.fs.Constraint(m.fs.time)\n", + " # def water_evaporator03_vapor_out_ineq(b,t):\n", + " # try:\n", + " # tdew = b.water_evaporator03.shell.properties_out[t].temperature_dew[\"Vap\",\"Liq\"]\n", + " # except KeyError:\n", + " # tdew = b.water_evaporator03.shell.properties_out[t].temperature_dew[\"Liq\",\"Vap\"]\n", + " # return (\n", + " # b.water_evaporator03.shell.properties_out[t].temperature\n", + " # >= tdew\n", + " # )\n", " iscale.constraint_scaling_transform(m.fs.water_evaporator02_vapor_out_ineq[0], 1e-2)\n", - "# iscale.constraint_scaling_transform(m.fs.water_evaporator03_vapor_out_ineq[0], 1e-2)\n", - " \n", - " \n", + " # iscale.constraint_scaling_transform(m.fs.water_evaporator03_vapor_out_ineq[0], 1e-2)\n", + "\n", " for cmp in [m.fs.sweep_blower, m.fs.cmp01, m.fs.cmp02, m.fs.cmp03, m.fs.cmp04]:\n", " set_indexed_variable_bounds(cmp.work_mechanical, (0, None))\n", " set_indexed_variable_bounds(m.fs.water_compressor.control_volume.work, (0, None))\n", - " \n", + "\n", " m.fs.h2_mass_production.fix(2)\n", " m.fs.water_preheater.tube_inlet.flow_mol.unfix()\n", " m.fs.soec_single_pass_water_conversion.unfix()\n", " m.fs.feed_recycle_split.split_fraction.unfix()\n", " m.fs.sweep_recycle_split.split_fraction.unfix()\n", " m.fs.sweep_blower.inlet.flow_mol.unfix()\n", - " #m.fs.sweep_blower.control_volume.properties_out[:].pressure.unfix()\n", + " # m.fs.sweep_blower.control_volume.properties_out[:].pressure.unfix()\n", " m.fs.feed_heater.outlet.temperature.unfix()\n", " m.fs.sweep_heater.outlet.temperature.unfix()\n", - " \n", - " \n", - " \n", + "\n", " m.fs.soec_module.potential_cell.unfix()\n", " m.fs.soec_module.number_cells.unfix()\n", " m.fs.costing.electricity_price.fix(71.7)\n", - " \n", - " set_indexed_variable_bounds(m.fs.soec_module.solid_oxide_cell.potential, (1.1,1.6))\n", - " set_indexed_variable_bounds(m.fs.water_split.split_fraction, (0.03,0.98))\n", - " set_indexed_variable_bounds(m.fs.feed_heater.heat_duty,(0,None))\n", - " set_indexed_variable_bounds(m.fs.sweep_heater.heat_duty,(0,None))\n", + "\n", + " set_indexed_variable_bounds(m.fs.soec_module.solid_oxide_cell.potential, (1.1, 1.6))\n", + " set_indexed_variable_bounds(m.fs.water_split.split_fraction, (0.03, 0.98))\n", + " set_indexed_variable_bounds(m.fs.feed_heater.heat_duty, (0, None))\n", + " set_indexed_variable_bounds(m.fs.sweep_heater.heat_duty, (0, None))\n", " set_indexed_variable_bounds(m.fs.heat_pump_hot_terminus.heat_duty, (0, None))\n", - " set_indexed_variable_bounds(m.fs.sweep_blower.inlet.flow_mol,(1000,None))\n", - " m.fs.feed_recycle_split.split_fraction[0,\"recycle\"].bounds = (0.03,0.5)\n", - " m.fs.sweep_recycle_split.split_fraction[0,\"recycle\"].bounds = (0.03,0.5)\n", - " set_indexed_variable_bounds(m.fs.soec_overall_water_conversion, (0.4,0.8))\n", - " set_indexed_variable_bounds(m.fs.soec_module.solid_oxide_cell.temperature_z, (550+273.15,750+273.15))\n", - " set_indexed_variable_bounds(m.fs.soec_module.solid_oxide_cell.fuel_channel.temperature_inlet, (600+273.15,750+273.15))\n", - " set_indexed_variable_bounds(m.fs.soec_module.solid_oxide_cell.oxygen_channel.temperature_inlet, (600+273.15,750+273.15))\n", - " set_indexed_variable_bounds(m.fs.soec_module.solid_oxide_cell.current_density, (-10400,5200))\n", + " set_indexed_variable_bounds(m.fs.sweep_blower.inlet.flow_mol, (1000, None))\n", + " m.fs.feed_recycle_split.split_fraction[0, \"recycle\"].bounds = (0.03, 0.5)\n", + " m.fs.sweep_recycle_split.split_fraction[0, \"recycle\"].bounds = (0.03, 0.5)\n", + " set_indexed_variable_bounds(m.fs.soec_overall_water_conversion, (0.4, 0.8))\n", + " set_indexed_variable_bounds(\n", + " m.fs.soec_module.solid_oxide_cell.temperature_z, (550 + 273.15, 750 + 273.15)\n", + " )\n", + " set_indexed_variable_bounds(\n", + " m.fs.soec_module.solid_oxide_cell.fuel_channel.temperature_inlet,\n", + " (600 + 273.15, 750 + 273.15),\n", + " )\n", + " set_indexed_variable_bounds(\n", + " m.fs.soec_module.solid_oxide_cell.oxygen_channel.temperature_inlet,\n", + " (600 + 273.15, 750 + 273.15),\n", + " )\n", + " set_indexed_variable_bounds(\n", + " m.fs.soec_module.solid_oxide_cell.current_density, (-10400, 5200)\n", + " )\n", " set_indexed_variable_bounds(m.fs.heat_source.inlet.flow_mol[0], (1, None))\n", - " \n", + "\n", " m.fs.feed_heater.max_heat_duty.set_value(8e6)\n", " m.fs.sweep_heater.max_heat_duty.set_value(8e6)\n", " m.fs.feed_heater.max_heat_duty.bounds = (8e6, None)\n", " m.fs.sweep_heater.max_heat_duty.bounds = (8e6, None)\n", - " #m.fs.heat_pump.max_heat_duty.fix(2e7)\n", + " # m.fs.heat_pump.max_heat_duty.fix(2e7)\n", " m.fs.max_raw_water_withdrawal.fix(3000)\n", - " \n", + "\n", " for cmp in [m.fs.cmp01, m.fs.cmp02, m.fs.cmp03, m.fs.cmp04]:\n", " cmp.ratioP.unfix()\n", " set_indexed_variable_bounds(cmp.ratioP, (1, 3))\n", - " set_indexed_variable_bounds(cmp.outlet.temperature, (273.15, 250+273.15))\n", - " \n", + " set_indexed_variable_bounds(cmp.outlet.temperature, (273.15, 250 + 273.15))\n", + "\n", " m.fs._make_temperature_gradient_terms()\n", - " set_indexed_variable_bounds(m.fs.soec_module.solid_oxide_cell.fuel_electrode.dtemperature_dz, (-750, 750))\n", + " set_indexed_variable_bounds(\n", + " m.fs.soec_module.solid_oxide_cell.fuel_electrode.dtemperature_dz, (-750, 750)\n", + " )\n", " m.fs.make_performance_constraints()\n", "# @m.fs.Constraint(m.fs.time)\n", "# def equal_pressures_eqn(b,t):\n", "# return b.soec_module.fuel_inlet.pressure[t] == b.soec_module.oxygen_inlet.pressure[t]\n", - " \n", + "\n", "# iscale.constraint_scaling_transform(m.fs.equal_pressures_eqn[0],1e-5)" ] }, @@ -869,7 +898,9 @@ "source": [ "m.fs.h2_mass_production.fix(5)\n", "jac_unscaled, jac_scaled, nlp = iscale.constraint_autoscale_large_jac(m)\n", - "solver.solve(m, tee=True, options={\"tol\": 3e-8, \"max_iter\": 500, \"halt_on_ampl_error\":\"no\"})" + "solver.solve(\n", + " m, tee=True, options={\"tol\": 3e-8, \"max_iter\": 500, \"halt_on_ampl_error\": \"no\"}\n", + ")" ] }, { @@ -907,7 +938,7 @@ "m.fs.soec_module.number_cells.pprint()\n", "m.fs.soec_module.solid_oxide_cell.current_density.pprint()\n", "print(pyo.value(m.fs.soec_module.solid_oxide_cell.average_current_density[0]))\n", - "print(pyo.value((m.fs.obj)*1e6/(5*m.fs.costing.plant_uptime)))" + "print(pyo.value((m.fs.obj) * 1e6 / (5 * m.fs.costing.plant_uptime)))" ] }, { @@ -1493,7 +1524,7 @@ " \n", " Summary:\n", " \n", - " SOEC Electric Power:SOEC Current:Cell Potential:Number of cells:SOEC Power/H2:H2 Product Rate:Single-pass H2O coversion:Sweep Blower Power:Feed Heater Power:Sweep Heater Power:Heat Pump Power:Heat Pump Water Draw:Compressor Power:Total electric Power:Total Power/H2:\n", + " SOEC Electric Power:SOEC Current:Cell Potential:Number of cells:SOEC Power/H2:H2 Product Rate:Single-pass H2O conversion:Sweep Blower Power:Feed Heater Power:Sweep Heater Power:Heat Pump Power:Heat Pump Water Draw:Compressor Power:Total electric Power:Total Power/H2:\n", " \n", " 638.539 MW\n", " -478.598 MA\n", @@ -2794,35 +2825,60 @@ " m.fs.sweep_hot_exchanger,\n", " m.fs.sweep_medium_exchanger,\n", "]\n", - "heaters = [\n", - " m.fs.feed_heater,\n", - " m.fs.sweep_heater\n", + "heaters = [m.fs.feed_heater, m.fs.sweep_heater]\n", + "flash_vessels = [\n", + " m.fs.product_flash01,\n", + " # m.fs.product_flash02,\n", + " m.fs.product_flash03,\n", + " m.fs.product_flash04,\n", + " m.fs.product_flash05,\n", "]\n", - "flash_vessels = [m.fs.product_flash01,\n", - "# m.fs.product_flash02,\n", - " m.fs.product_flash03,\n", - " m.fs.product_flash04,\n", - " m.fs.product_flash05,\n", - " ]\n", "\n", "\n", - "\n", - "compressors = [getattr(m.fs, f\"cmp0{i}\") for i in range(1,5)]\n", + "compressors = [getattr(m.fs, f\"cmp0{i}\") for i in range(1, 5)]\n", "print(f\"Total CapEx: ${pyo.value(m.fs.costing.total_TPC):.2f}MM\")\n", - "print(f\"Contribution from SOEC: ${pyo.value(m.fs.soec_module.costing.total_plant_cost):.2f}MM\")\n", - "print(f\"Contribution from H2 compressors: ${pyo.value(sum([cmp.costing.total_plant_cost for cmp in compressors])):.2f}MM\")\n", - "print(f\"Contribution from H2O compressor: ${pyo.value(m.fs.water_compressor.costing.total_plant_cost):.2f}MM\")\n", - "print(f\"Contribution from trim heaters: ${pyo.value(sum([heater.costing.total_plant_cost for heater in heaters])):.2f}MM\")\n", - "print(f\"Contribution from gas-gas exchangers: ${pyo.value(sum([hx.costing.total_plant_cost for hx in cross_flow_exchangers])):.2f}MM\")\n", - "print(f\"Contribution from gas-water exchangers: ${pyo.value(sum([hx.costing.total_plant_cost for hx in water_heaters])):.2f}MM\")\n", - "print(f\"Contribution from flash vessels: ${pyo.value(sum([flash.costing.total_plant_cost for flash in flash_vessels])):.2f}MM\")\n", - "print(f\"Contribution from heat pump: ${pyo.value(m.fs.heat_pump.costing.total_plant_cost):.2f}MM\")\n", - "print(f\"Contribution from sweep blower: ${pyo.value(m.fs.sweep_blower.costing.total_plant_cost):.2f}MM\")\n", - "print(f\"Contribution from water feed & treatment systems: ${pyo.value(m.fs.costing.water_systems_cost):.2f}MM\")\n", - "print(f\"Contribution from accessory electric equipment: ${pyo.value(m.fs.costing.accessory_electric_plant_cost):.2f}MM\")\n", - "print(f\"Contribution from instrumentation and controls: ${pyo.value(m.fs.costing.instrumentation_and_control_cost):.2f}MM\")\n", - "print(f\"Contribution from improvements to site: ${pyo.value(m.fs.costing.improvements_to_site_cost):.2f}MM\")\n", - "print(f\"Contribution from buildings and structures: ${pyo.value(m.fs.costing.buildings_and_structures_cost):.2f}MM\")\n", + "print(\n", + " f\"Contribution from SOEC: ${pyo.value(m.fs.soec_module.costing.total_plant_cost):.2f}MM\"\n", + ")\n", + "print(\n", + " f\"Contribution from H2 compressors: ${pyo.value(sum([cmp.costing.total_plant_cost for cmp in compressors])):.2f}MM\"\n", + ")\n", + "print(\n", + " f\"Contribution from H2O compressor: ${pyo.value(m.fs.water_compressor.costing.total_plant_cost):.2f}MM\"\n", + ")\n", + "print(\n", + " f\"Contribution from trim heaters: ${pyo.value(sum([heater.costing.total_plant_cost for heater in heaters])):.2f}MM\"\n", + ")\n", + "print(\n", + " f\"Contribution from gas-gas exchangers: ${pyo.value(sum([hx.costing.total_plant_cost for hx in cross_flow_exchangers])):.2f}MM\"\n", + ")\n", + "print(\n", + " f\"Contribution from gas-water exchangers: ${pyo.value(sum([hx.costing.total_plant_cost for hx in water_heaters])):.2f}MM\"\n", + ")\n", + "print(\n", + " f\"Contribution from flash vessels: ${pyo.value(sum([flash.costing.total_plant_cost for flash in flash_vessels])):.2f}MM\"\n", + ")\n", + "print(\n", + " f\"Contribution from heat pump: ${pyo.value(m.fs.heat_pump.costing.total_plant_cost):.2f}MM\"\n", + ")\n", + "print(\n", + " f\"Contribution from sweep blower: ${pyo.value(m.fs.sweep_blower.costing.total_plant_cost):.2f}MM\"\n", + ")\n", + "print(\n", + " f\"Contribution from water feed & treatment systems: ${pyo.value(m.fs.costing.water_systems_cost):.2f}MM\"\n", + ")\n", + "print(\n", + " f\"Contribution from accessory electric equipment: ${pyo.value(m.fs.costing.accessory_electric_plant_cost):.2f}MM\"\n", + ")\n", + "print(\n", + " f\"Contribution from instrumentation and controls: ${pyo.value(m.fs.costing.instrumentation_and_control_cost):.2f}MM\"\n", + ")\n", + "print(\n", + " f\"Contribution from improvements to site: ${pyo.value(m.fs.costing.improvements_to_site_cost):.2f}MM\"\n", + ")\n", + "print(\n", + " f\"Contribution from buildings and structures: ${pyo.value(m.fs.costing.buildings_and_structures_cost):.2f}MM\"\n", + ")\n", "\n", "print(\"\\n\")\n", "print(\"------------------------------------------\")\n", @@ -2836,22 +2892,40 @@ "print(f\"Annualized TASC (MM$/yr) = {pyo.value(m.fs.costing.total_annualized_cost)}\")\n", "\n", "print(\"Fixed O&M Costs\")\n", - "print(f\"annual_op_labor (MM$/yr) = {pyo.value(m.fs.costing.annual_operating_labor_cost)}\")\n", + "print(\n", + " f\"annual_op_labor (MM$/yr) = {pyo.value(m.fs.costing.annual_operating_labor_cost)}\"\n", + ")\n", "print(f\"maint_labor (MM$/yr) = {pyo.value(m.fs.costing.maintenance_labor_cost)}\")\n", "print(f\"maint_material (MM$/yr) = {pyo.value(m.fs.costing.maintenance_material_cost)}\")\n", "print(f\"admin_labor (MM$/yr) = {pyo.value(m.fs.costing.admin_and_support_labor_cost)}\")\n", - "print(f\"prop_tax_ins (MM$/yr) = {pyo.value(m.fs.costing.property_tax_and_insurance_cost)}\")\n", - "print(f\"soec_replace (MM$/yr) = {pyo.value(m.fs.soec_module.costing.annual_soec_replacement_cost)}\")\n", + "print(\n", + " f\"prop_tax_ins (MM$/yr) = {pyo.value(m.fs.costing.property_tax_and_insurance_cost)}\"\n", + ")\n", + "print(\n", + " f\"soec_replace (MM$/yr) = {pyo.value(m.fs.soec_module.costing.annual_soec_replacement_cost)}\"\n", + ")\n", "# total_fixed = annual_op_labor + maint_labor + maint_material + admin_labor + prop_tax_ins + soec_replace\n", - "print(f\"Annualized Fixed O&M (MM$/year) = {pyo.value(m.fs.costing.annual_fixed_operations_and_maintenance_cost)}\")\n", - "fixed_cost_per_h2 = pyo.value(\n", - " m.fs.costing.total_annualized_cost\n", - " + m.fs.costing.annual_fixed_operations_and_maintenance_cost\n", - ")/24/365.2425/5/60/60*1e6\n", + "print(\n", + " f\"Annualized Fixed O&M (MM$/year) = {pyo.value(m.fs.costing.annual_fixed_operations_and_maintenance_cost)}\"\n", + ")\n", + "fixed_cost_per_h2 = (\n", + " pyo.value(\n", + " m.fs.costing.total_annualized_cost\n", + " + m.fs.costing.annual_fixed_operations_and_maintenance_cost\n", + " )\n", + " / 24\n", + " / 365.2425\n", + " / 5\n", + " / 60\n", + " / 60\n", + " * 1e6\n", + ")\n", "print(f\"Fixed ($/kg h2) = {fixed_cost_per_h2}\")\n", "\n", "print(f\"Annual water cost MM$ {pyo.value(m.fs.costing.annual_water_cost)}\")\n", - "print(f\"water cost $/kg h2 {pyo.value(m.fs.costing.annual_water_cost)/24/365/5/60/60*1e6}\")" + "print(\n", + " f\"water cost $/kg h2 {pyo.value(m.fs.costing.annual_water_cost)/24/365/5/60/60*1e6}\"\n", + ")" ] }, { @@ -2861,8 +2935,10 @@ "metadata": {}, "outputs": [], "source": [ - "assert m.fs.tags_output['total_electric_power_per_h2'].value == pytest.approx(143.951, rel=5e-3)\n", - "assert m.fs.tags_input['water_utilization'].value == pytest.approx(66.667, rel=5e-3)" + "assert m.fs.tags_output[\"total_electric_power_per_h2\"].value == pytest.approx(\n", + " 143.951, rel=5e-3\n", + ")\n", + "assert m.fs.tags_input[\"water_utilization\"].value == pytest.approx(66.667, rel=5e-3)" ] }, { @@ -2885,8 +2961,8 @@ } ], "source": [ - "print(m.fs.tags_output['total_electric_power_per_h2'].value)\n", - "print(m.fs.tags_input['water_utilization'].value)\n", + "print(m.fs.tags_output[\"total_electric_power_per_h2\"].value)\n", + "print(m.fs.tags_input[\"water_utilization\"].value)\n", "m.fs.soec_module.number_cells.pprint()" ] }, @@ -2935,7 +3011,7 @@ "\n", "for heater in heaters:\n", " heater.max_heat_duty.fix()\n", - " \n", + "\n", "for blk in m.fs.block_data_objects():\n", " if hasattr(blk, \"costing\"):\n", " blk.costing.deactivate()\n", @@ -2944,26 +3020,28 @@ "m.fs.costing.activate()\n", "m.fs.costing.total_TPC.fix()\n", "m.fs.costing.total_TPC_eqn.deactivate()\n", - " \n", + "\n", "m.fs.soec_module.number_cells.fix()\n", "m.fs.max_raw_water_withdrawal.fix()\n", "m.fs.heat_pump.max_heat_duty.fix()\n", "\n", "m.fs.obj.deactivate()\n", "m.fs.obj2 = pyo.Objective(\n", - " expr = 0.1*(\n", - " m.fs.costing.annual_electricity_cost \n", - " + m.fs.costing.annual_water_cost\n", - " + m.fs.costing.annual_air_cost\n", - " )\n", + " expr=0.1\n", + " * (\n", + " m.fs.costing.annual_electricity_cost\n", + " + m.fs.costing.annual_water_cost\n", + " + m.fs.costing.annual_air_cost\n", " )\n", + ")\n", "m.fs.obj3 = pyo.Objective(\n", - " expr = 10*(\n", - " m.fs.costing.annual_electricity_cost \n", - " + m.fs.costing.annual_water_cost\n", - " + m.fs.costing.annual_air_cost\n", - " )\n", - " ) \n", + " expr=10\n", + " * (\n", + " m.fs.costing.annual_electricity_cost\n", + " + m.fs.costing.annual_water_cost\n", + " + m.fs.costing.annual_air_cost\n", + " )\n", + ")\n", "m.fs.obj3.deactivate()" ] }, @@ -2981,7 +3059,7 @@ "prod_vec = np.linspace(5, 1, 17)\n", "\n", "if run_samples:\n", - " i=1\n", + " i = 1\n", " df = pd.DataFrame(columns=m.fs.tags_output.table_heading())\n", " for prod in prod_vec:\n", " print(prod)\n", @@ -2989,24 +3067,34 @@ " m.fs.obj2.activate()\n", " m.fs.obj3.deactivate()\n", " m.fs.h2_mass_production.fix(prod)\n", - " res = solver.solve(m, tee=True, options={\"tol\": 1e-6, \"max_iter\": 250, \"halt_on_ampl_error\":\"no\"})\n", - " solved = (res.solver.termination_condition == pyo.TerminationCondition.optimal\n", - " and res.solver.status == pyo.SolverStatus.ok)\n", + " res = solver.solve(\n", + " m,\n", + " tee=True,\n", + " options={\"tol\": 1e-6, \"max_iter\": 250, \"halt_on_ampl_error\": \"no\"},\n", + " )\n", + " solved = (\n", + " res.solver.termination_condition == pyo.TerminationCondition.optimal\n", + " and res.solver.status == pyo.SolverStatus.ok\n", + " )\n", " if not solved:\n", " m.fs.obj2.deactivate()\n", " m.fs.obj3.activate()\n", - " res = solver.solve(m, tee=True, options={\"tol\": 1e-6, \"max_iter\": 250, \"halt_on_ampl_error\":\"no\"})\n", + " res = solver.solve(\n", + " m,\n", + " tee=True,\n", + " options={\"tol\": 1e-6, \"max_iter\": 250, \"halt_on_ampl_error\": \"no\"},\n", + " )\n", " assert res.solver.termination_condition == pyo.TerminationCondition.optimal\n", " assert res.solver.status == pyo.SolverStatus.ok\n", - "# solved = (res.solver.termination_condition == pyo.TerminationCondition.optimal\n", - "# and res.solver.status == pyo.SolverStatus.ok)\n", - "# if not solved:\n", - "# m.fs.obj3.deactivate()\n", - "# m.fs.obj4.activate()\n", - "# res = solver.solve(m, tee=True, options={\"tol\": 1e-6, \"max_iter\": 150, \"halt_on_ampl_error\":\"no\"})\n", + " # solved = (res.solver.termination_condition == pyo.TerminationCondition.optimal\n", + " # and res.solver.status == pyo.SolverStatus.ok)\n", + " # if not solved:\n", + " # m.fs.obj3.deactivate()\n", + " # m.fs.obj4.activate()\n", + " # res = solver.solve(m, tee=True, options={\"tol\": 1e-6, \"max_iter\": 150, \"halt_on_ampl_error\":\"no\"})\n", "\n", " df.loc[i] = m.fs.tags_output.table_row(numeric=True)\n", - " i += 1\n" + " i += 1" ] }, { @@ -3579,7 +3667,7 @@ " \n", " Summary:\n", " \n", - " SOEC Electric Power:SOEC Current:Cell Potential:Number of cells:SOEC Power/H2:H2 Product Rate:Single-pass H2O coversion:Sweep Blower Power:Feed Heater Power:Sweep Heater Power:Heat Pump Power:Heat Pump Water Draw:Compressor Power:Total electric Power:Total Power/H2:\n", + " SOEC Electric Power:SOEC Current:Cell Potential:Number of cells:SOEC Power/H2:H2 Product Rate:Single-pass H2O conversion:Sweep Blower Power:Feed Heater Power:Sweep Heater Power:Heat Pump Power:Heat Pump Water Draw:Compressor Power:Total electric Power:Total Power/H2:\n", " \n", " 638.539 MW\n", " -478.598 MA\n", @@ -3802,11 +3890,13 @@ } ], "source": [ - "for flash in [m.fs.product_flash01, \n", - " #m.fs.product_flash02,\n", - " m.fs.product_flash03,\n", - " m.fs.product_flash04,\n", - " m.fs.product_flash05]:\n", + "for flash in [\n", + " m.fs.product_flash01,\n", + " # m.fs.product_flash02,\n", + " m.fs.product_flash03,\n", + " m.fs.product_flash04,\n", + " m.fs.product_flash05,\n", + "]:\n", " print(flash.name)\n", " print(f\"Diameter: {flash.diameter.value}m\")\n", " print(f\"Height: {flash.length.value}m\")\n", @@ -3821,6 +3911,8 @@ "outputs": [], "source": [ "from idaes.core.util.model_diagnostics import DegeneracyHunter\n", + "\n", + "\n", "def check_scaling(blk):\n", " jac, nlp = iscale.get_jacobian(blk, scaled=True)\n", " # djac = jac.todense()\n", @@ -3828,18 +3920,16 @@ " # for i in iscale.extreme_jacobian_entries(jac=jac, nlp=nlp, large=1E3, small=0):\n", " # print(f\" {i[0]:.2e}, [{i[1]}, {i[2]}]\")\n", " print(\"Badly scaled variables:\")\n", - " for i in iscale.extreme_jacobian_columns(\n", - " jac=jac, nlp=nlp, large=1E3, small=5E-3):\n", + " for i in iscale.extreme_jacobian_columns(jac=jac, nlp=nlp, large=1e3, small=5e-3):\n", " print(f\" {i[0]:.2e}, [{i[1]}]\")\n", " print(\"\\n\\n\" + \"Badly scaled constraints:\")\n", - " for i in iscale.extreme_jacobian_rows(\n", - " jac=jac, nlp=nlp, large=1E3, small=5E-3):\n", + " for i in iscale.extreme_jacobian_rows(jac=jac, nlp=nlp, large=1e3, small=5e-3):\n", " print(f\" {i[0]:.2e}, [{i[1]}]\")\n", - " #print(f\"Jacobian Condition Number: {iscale.jacobian_cond(jac=jac):.2e}\")\n", + " # print(f\"Jacobian Condition Number: {iscale.jacobian_cond(jac=jac):.2e}\")\n", "\n", " if not hasattr(blk, \"obj\"):\n", " blk.obj = pyo.Objective(expr=0)\n", - " dh = DegeneracyHunter(blk, solver=pyo.SolverFactory('cbc'))\n", + " dh = DegeneracyHunter(blk, solver=pyo.SolverFactory(\"cbc\"))\n", " dh.check_rank_equality_constraints(dense=True)\n", " variables = nlp.get_pyomo_variables()\n", " constraints = nlp.get_pyomo_equality_constraints()\n", diff --git a/idaes_examples/archive/power_gen/soec/soec.py b/idaes_examples/archive/power_gen/soec/soec.py index 9b13fa0b..20eaefb1 100644 --- a/idaes_examples/archive/power_gen/soec/soec.py +++ b/idaes_examples/archive/power_gen/soec/soec.py @@ -11,16 +11,21 @@ from idaes.core.util.model_statistics import degrees_of_freedom from idaes.models.properties.modular_properties.base.generic_property import ( GenericParameterBlock, - GenericStateBlockData + GenericStateBlockData, ) import idaes.core.util.scaling as iscale import idaes.core.util.exceptions as idaes_except -from idaes.models_extra.power_generation.properties.natural_gas_PR import get_prop, EosType -from idaes.models_extra.power_generation.unit_models.soc_submodels import SolidOxideModuleSimple +from idaes.models_extra.power_generation.properties.natural_gas_PR import ( + get_prop, + EosType, +) +from idaes.models_extra.power_generation.unit_models.soc_submodels import ( + SolidOxideModuleSimple, +) import idaes.models.unit_models as gum from idaes.models.unit_models.heat_exchanger import ( HeatExchangerFlowPattern, - delta_temperature_lmtd_smooth_callback + delta_temperature_lmtd_smooth_callback, ) import idaes.models_extra.power_generation.unit_models.helm as hum from idaes.models.properties import iapws95 @@ -79,14 +84,19 @@ def _add_properties(self): doc="H2O + H2 gas property parameters", ) self.h2_condensing_prop_params.H2.config.parameter_data["kappa1"] = 0.0 - self.h2_condensing_prop_params.H2O.config \ - .parameter_data["kappa1"] = -0.0665 # p. 312 of (Sandler, 2006) + self.h2_condensing_prop_params.H2O.config.parameter_data["kappa1"] = ( + -0.0665 + ) # p. 312 of (Sandler, 2006) # Use PR-SV for water phase equilibrium sqrt = pyo.sqrt def omega_func(cobj): - return (0.378893 + 1.4897153 * cobj.omega - 0.17131848 * cobj.omega ** 2 - + 0.0196554 * cobj.omega ** 3) + return ( + 0.378893 + + 1.4897153 * cobj.omega + - 0.17131848 * cobj.omega**2 + + 0.0196554 * cobj.omega**3 + ) def alpha_func(T, fw, cobj): TR = T / cobj.temperature_crit @@ -100,8 +110,11 @@ def dalpha_dT_func(T, fw, cobj): kappa1 = cobj.config.parameter_data["kappa1"] kappa = fw + kappa1 * (1 + sqrt(Tr)) * (0.7 - Tr) dkappa_dT = kappa1 * ((0.7 - Tr) / (2 * sqrt(T * Tc)) - (1 + sqrt(Tr)) / Tc) - return 2 * (1 + kappa * (1 - sqrt(Tr))) * ((1 - sqrt(Tr)) * dkappa_dT - - kappa / (2 * sqrt(T * Tc))) + return ( + 2 + * (1 + kappa * (1 - sqrt(Tr))) + * ((1 - sqrt(Tr)) * dkappa_dT - kappa / (2 * sqrt(T * Tc))) + ) def d2alpha_dT2_func(T, fw, cobj): Tc = cobj.temperature_crit @@ -112,11 +125,11 @@ def d2alpha_dT2_func(T, fw, cobj): sqrt_alpha = 1 + kappa * (1 - sqrt(Tr)) dsqrtalpha_dTr = -fw / (2 * sqrt(Tr)) - 1.7 * kappa1 + 2 * kappa1 * Tr - d2sqrtalpha_dTr = 2 * kappa1 + fw / (4 * Tr ** 1.5) + d2sqrtalpha_dTr = 2 * kappa1 + fw / (4 * Tr**1.5) - d2alpha_dTr2 = 2 * dsqrtalpha_dTr ** 2 + 2 * sqrt_alpha * d2sqrtalpha_dTr + d2alpha_dTr2 = 2 * dsqrtalpha_dTr**2 + 2 * sqrt_alpha * d2sqrtalpha_dTr - return d2alpha_dTr2 / Tc ** 2 + return d2alpha_dTr2 / Tc**2 self.h2_condensing_prop_params.PR_func_fw = omega_func self.h2_condensing_prop_params.PR_func_alpha = alpha_func @@ -127,49 +140,50 @@ def d2alpha_dT2_func(T, fw, cobj): **get_prop({"H2"}, {"Vap"}, eos=EosType.PR), doc="Pure H2 gas property parameters", ) - generic_prop_packages = [self.o2_side_prop_params, - self.h2_side_prop_params, - self.h2_pure_prop_params, - self.h2_condensing_prop_params] + generic_prop_packages = [ + self.o2_side_prop_params, + self.h2_side_prop_params, + self.h2_pure_prop_params, + self.h2_condensing_prop_params, + ] for pp in generic_prop_packages: pp.set_default_scaling("enth_mol_phase", 1e-3, index="Vap") pp.set_default_scaling("pressure", 1e-5) pp.set_default_scaling("temperature", 1e-2) - pp.set_default_scaling("flow_mol", 1E-3) - self.h2_condensing_prop_params.set_default_scaling("enth_mol_phase", 1e-3, index="Liq") - _mf_scale = { - "Ar": 100, - "O2": 10, - "N2": 10, - "H2": 10, - "H2O": 100, - "CO2": 1000} + pp.set_default_scaling("flow_mol", 1e-3) + self.h2_condensing_prop_params.set_default_scaling( + "enth_mol_phase", 1e-3, index="Liq" + ) + _mf_scale = {"Ar": 100, "O2": 10, "N2": 10, "H2": 10, "H2O": 100, "CO2": 1000} for comp, s in _mf_scale.items(): self.o2_side_prop_params.set_default_scaling( - "mole_frac_comp", s, index=comp) + "mole_frac_comp", s, index=comp + ) self.o2_side_prop_params.set_default_scaling( "mole_frac_phase_comp", s, index=("Vap", comp) ) self.o2_side_prop_params.set_default_scaling( - "flow_mol_comp", s*1e-3, index=comp) + "flow_mol_comp", s * 1e-3, index=comp + ) self.o2_side_prop_params.set_default_scaling( - "flow_mol_phase_comp", s*1e-3, index=("Vap", comp) + "flow_mol_phase_comp", s * 1e-3, index=("Vap", comp) ) - for par in [self.h2_side_prop_params, - self.h2_condensing_prop_params]: + for par in [self.h2_side_prop_params, self.h2_condensing_prop_params]: for comp in ["H2", "H2O"]: par.set_default_scaling("mole_frac_comp", 10, index=comp) par.set_default_scaling("flow_mol_comp", 1e-2, index=comp) par.set_default_scaling("mole_frac_phase_comp", 10, index=("Vap", comp)) - par.set_default_scaling("flow_mol_phase_comp", 1e-2, index=("Vap", comp)) + par.set_default_scaling( + "flow_mol_phase_comp", 1e-2, index=("Vap", comp) + ) par.set_default_scaling("mole_frac_phase_comp", 1, index=("Liq", "H2O")) par.set_default_scaling("flow_mol_phase_comp", 1e-2, index=("Liq", "H2O")) - - self.h2_pure_prop_params.set_default_scaling("mole_frac_comp", 1, index="H2") - self.h2_pure_prop_params.set_default_scaling("mole_frac_phase_comp", 1, index=("Vap", "H2")) + self.h2_pure_prop_params.set_default_scaling( + "mole_frac_phase_comp", 1, index=("Vap", "H2") + ) def _define_cell_params(self): self.soec_module.number_cells.fix(1087915) @@ -189,8 +203,7 @@ def _define_cell_params(self): soec.fuel_electrode.solid_heat_capacity.fix(595) soec.fuel_electrode.solid_density.fix(7740.0) soec.fuel_electrode.solid_thermal_conductivity.fix(6.23) - soec.fuel_electrode.resistivity_log_preexponential_factor\ - .fix(pyo.log(2.5e-5)) + soec.fuel_electrode.resistivity_log_preexponential_factor.fix(pyo.log(2.5e-5)) soec.fuel_electrode.resistivity_thermal_exponent_dividend.fix(0) soec.oxygen_electrode.length_x.fix(40e-6) @@ -199,20 +212,21 @@ def _define_cell_params(self): soec.oxygen_electrode.solid_heat_capacity.fix(142.3) soec.oxygen_electrode.solid_density.fix(5300) soec.oxygen_electrode.solid_thermal_conductivity.fix(2.0) - soec.oxygen_electrode.resistivity_log_preexponential_factor\ - .fix(pyo.log(7.8125e-05)) + soec.oxygen_electrode.resistivity_log_preexponential_factor.fix( + pyo.log(7.8125e-05) + ) soec.oxygen_electrode.resistivity_thermal_exponent_dividend.fix(0) soec.electrolyte.length_x.fix(10.5e-6) soec.electrolyte.heat_capacity.fix(400) soec.electrolyte.density.fix(6000) soec.electrolyte.thermal_conductivity.fix(2.17) - soec.electrolyte.resistivity_log_preexponential_factor\ - .fix(-9) + soec.electrolyte.resistivity_log_preexponential_factor.fix(-9) soec.electrolyte.resistivity_thermal_exponent_dividend.fix(8988) - soec.fuel_triple_phase_boundary.exchange_current_log_preexponential_factor\ - .fix(22.5) + soec.fuel_triple_phase_boundary.exchange_current_log_preexponential_factor.fix( + 22.5 + ) soec.fuel_triple_phase_boundary.exchange_current_activation_energy.fix(110.8e3) soec.fuel_triple_phase_boundary.activation_potential_alpha1.fix(1 - 0.352184) soec.fuel_triple_phase_boundary.activation_potential_alpha2.fix(0.352184) @@ -220,9 +234,12 @@ def _define_cell_params(self): soec.fuel_triple_phase_boundary.exchange_current_exponent_comp["H2"].fix(0.5) soec.fuel_triple_phase_boundary.exchange_current_exponent_comp["H2O"].fix(0.5) - soec.oxygen_triple_phase_boundary.exchange_current_log_preexponential_factor\ - .fix(25.5) - soec.oxygen_triple_phase_boundary.exchange_current_activation_energy.fix(112.1e3) + soec.oxygen_triple_phase_boundary.exchange_current_log_preexponential_factor.fix( + 25.5 + ) + soec.oxygen_triple_phase_boundary.exchange_current_activation_energy.fix( + 112.1e3 + ) soec.oxygen_triple_phase_boundary.activation_potential_alpha1.fix(1 - 0.497231) soec.oxygen_triple_phase_boundary.activation_potential_alpha2.fix(0.497231) @@ -249,7 +266,13 @@ def _add_units(self): # operating_pressure = 2e5 fuel_comps = ["H2", "H2O"] - fuel_stoich_dict = {"H2": -0.5, "H2O": 0.5, "Vac": 0.5, "O^2-": -0.5, "e^-": 1.0} + fuel_stoich_dict = { + "H2": -0.5, + "H2O": 0.5, + "Vac": 0.5, + "O^2-": -0.5, + "e^-": 1.0, + } fuel_inerts = [] if air_sweep: @@ -261,8 +284,7 @@ def _add_units(self): oxygen_stoich_dict = {"O2": -0.25, "Vac": -0.5, "O^2-": 0.5, "e^-": -1.0} oxygen_inerts = ["H2O"] - self.number_cells = pyo.Var(initialize=60e6, - units=pyo.units.dimensionless) + self.number_cells = pyo.Var(initialize=60e6, units=pyo.units.dimensionless) self.soec_module = SolidOxideModuleSimple( solid_oxide_cell_config={ @@ -288,7 +310,7 @@ def _add_units(self): self.sweep_recycle_split = gum.Separator( doc="Sweep recycle splitter", property_package=self.o2_side_prop_params, - outlet_list=["out", "recycle"] + outlet_list=["out", "recycle"], ) self.feed_recycle_split = gum.Separator( doc="Feed recycle splitter", @@ -299,7 +321,7 @@ def _add_units(self): doc="Sweep recycle mixer", property_package=self.o2_side_prop_params, inlet_list=["feed", "recycle"], - momentum_mixing_type=gum.MomentumMixingType.none + momentum_mixing_type=gum.MomentumMixingType.none, ) @self.sweep_recycle_mix.Constraint(self.time) @@ -310,7 +332,7 @@ def pressure_equality_eqn(b, t): doc="Feed recycle mixer", property_package=self.h2_side_prop_params, inlet_list=["feed", "recycle"], - momentum_mixing_type=gum.MomentumMixingType.none + momentum_mixing_type=gum.MomentumMixingType.none, ) @self.feed_recycle_mix.Constraint(self.time) @@ -318,8 +340,7 @@ def pressure_equality_eqn(b, t): return b.mixed_state[t].pressure == b.feed_state[t].pressure self.sweep_blower = gum.Compressor( - doc="Sweep blower", - property_package=self.o2_side_prop_params + doc="Sweep blower", property_package=self.o2_side_prop_params ) self.sweep_hot_exchanger = gum.HeatExchanger( @@ -354,16 +375,24 @@ def pressure_equality_eqn(b, t): self.feed_heater = gum.Heater(property_package=self.h2_side_prop_params) self.sweep_heater = gum.Heater(property_package=self.o2_side_prop_params) - self.product_flash01 = gum.Flash(property_package=self.h2_condensing_prop_params) + self.product_flash01 = gum.Flash( + property_package=self.h2_condensing_prop_params + ) # Use vapor-only property package for compressors to prevent spurious liquid terms from popping up self.cmp01 = gum.Compressor(property_package=self.h2_side_prop_params) # self.product_flash02 = gum.Flash(property_package=self.h2_condensing_prop_params) self.cmp02 = gum.Compressor(property_package=self.h2_side_prop_params) - self.product_flash03 = gum.Flash(property_package=self.h2_condensing_prop_params) + self.product_flash03 = gum.Flash( + property_package=self.h2_condensing_prop_params + ) self.cmp03 = gum.Compressor(property_package=self.h2_side_prop_params) - self.product_flash04 = gum.Flash(property_package=self.h2_condensing_prop_params) - self.product_flash05 = gum.Flash(property_package=self.h2_condensing_prop_params) + self.product_flash04 = gum.Flash( + property_package=self.h2_condensing_prop_params + ) + self.product_flash05 = gum.Flash( + property_package=self.h2_condensing_prop_params + ) self.product_dryer = gum.Translator( inlet_property_package=self.h2_condensing_prop_params, outlet_property_package=self.h2_pure_prop_params, @@ -371,9 +400,11 @@ def pressure_equality_eqn(b, t): ) self.cmp04 = gum.Compressor( property_package=self.h2_pure_prop_params, - thermodynamic_assumption=gum.pressure_changer.ThermodynamicAssumption.isothermal + thermodynamic_assumption=gum.pressure_changer.ThermodynamicAssumption.isothermal, + ) + self.cmp04.efficiency_isentropic = pyo.Var( + self.time, initialize=1, units=pyo.units.dimensionless ) - self.cmp04.efficiency_isentropic = pyo.Var(self.time, initialize=1, units=pyo.units.dimensionless) self.water_preheater = gum.HeatExchanger( hot_side_name="shell", @@ -385,14 +416,22 @@ def pressure_equality_eqn(b, t): # self.water_preheater = gum.Heater( # default={"property_package":self.steam_prop_params} # ) - self.water_valve = hum.HelmValve(property_package=self.steam_prop_params, phase="Liq") + self.water_valve = hum.HelmValve( + property_package=self.steam_prop_params, phase="Liq" + ) self.water_valve.Cv.fix() self.water_valve.valve_opening.fix() self.water_valve.pressure_flow_equation.deactivate() self.water_split = hum.HelmSplitter( - property_package=self.steam_prop_params, - outlet_list=["outlet1", "outlet2", "outlet3", "outlet4", - "outlet5", "outlet6"], + property_package=self.steam_prop_params, + outlet_list=[ + "outlet1", + "outlet2", + "outlet3", + "outlet4", + "outlet5", + "outlet6", + ], ) self.water_evaporator01 = gum.HeatExchanger( hot_side_name="shell", @@ -429,29 +468,33 @@ def pressure_equality_eqn(b, t): tube={"property_package": self.steam_prop_params}, # delta_temperature_callback=delta_temperature_lmtd_smooth_callback ) - self.heat_pump_hot_terminus = gum.Heater(property_package=self.steam_prop_params) + self.heat_pump_hot_terminus = gum.Heater( + property_package=self.steam_prop_params + ) self.steam_mix = hum.HelmMixer( property_package=self.steam_prop_params, - inlet_list=["inlet1", "inlet2", "inlet3", "inlet4", - "inlet5", "inlet6"], - momentum_mixing_type=gum.MomentumMixingType.none + inlet_list=["inlet1", "inlet2", "inlet3", "inlet4", "inlet5", "inlet6"], + momentum_mixing_type=gum.MomentumMixingType.none, ) + @self.steam_mix.Constraint(self.time) - def pressure_equality_eqn(b,t): + def pressure_equality_eqn(b, t): return b.mixed_state[t].pressure == b.inlet6.pressure[t] - self.water_compressor = hum.HelmIsentropicCompressor(property_package=self.steam_prop_params) + self.water_compressor = hum.HelmIsentropicCompressor( + property_package=self.steam_prop_params + ) self.heat_pump = UnitModelBlock() - self.heat_pump.heat_in = pyo.Var(self.time, initialize=0.5e6, - units=pyo.units.W) - self.heat_pump.heat_out = pyo.Var(self.time, initialize=-1e6, - units=pyo.units.W) - self.heat_pump.work_mechanical = pyo.Var(self.time, initialize=0.5e6, - units=pyo.units.W) - self.heat_pump.coefficient_of_performance = pyo.Var(initialize=2, - units=pyo.units.dimensionless) + self.heat_pump.heat_in = pyo.Var(self.time, initialize=0.5e6, units=pyo.units.W) + self.heat_pump.heat_out = pyo.Var(self.time, initialize=-1e6, units=pyo.units.W) + self.heat_pump.work_mechanical = pyo.Var( + self.time, initialize=0.5e6, units=pyo.units.W + ) + self.heat_pump.coefficient_of_performance = pyo.Var( + initialize=2, units=pyo.units.dimensionless + ) @self.heat_pump.Constraint(self.time) def energy_balance_eqn(b, t): @@ -459,8 +502,9 @@ def energy_balance_eqn(b, t): @self.heat_pump.Constraint(self.time) def performance_eqn(b, t): - return -b.heat_out[t] == (b.work_mechanical[t] - * b.coefficient_of_performance) + return -b.heat_out[t] == ( + b.work_mechanical[t] * b.coefficient_of_performance + ) # Some outside source of water from which the heat pump can harvest heat self.heat_source = gum.Heater(property_package=self.steam_prop_params) @@ -540,7 +584,7 @@ def _add_arcs(self): self.sweep03 = Arc( doc="", source=self.sweep_hot_exchanger.tube_outlet, - destination=self.sweep_recycle_mix.feed + destination=self.sweep_recycle_mix.feed, ) self.hstrm03 = Arc( doc="", @@ -644,31 +688,27 @@ def _add_arcs(self): source=self.water_valve.outlet, destination=self.water_split.inlet, ) - for i, l in zip(range(1, 6), 'abcde'): + for i, l in zip(range(1, 6), "abcde"): src = getattr(self.water_split, f"outlet{i}") evap = getattr(self, f"water_evaporator0{i}") - setattr(self, f"water03{l}", - Arc(source=src, destination=evap.tube_inlet)) + setattr(self, f"water03{l}", Arc(source=src, destination=evap.tube_inlet)) self.water03f = Arc( source=self.water_split.outlet6, - destination=self.heat_pump_hot_terminus.inlet + destination=self.heat_pump_hot_terminus.inlet, ) - for i, l in zip(range(1, 6), 'abcde'): + for i, l in zip(range(1, 6), "abcde"): evap = getattr(self, f"water_evaporator0{i}") dest = getattr(self.steam_mix, f"inlet{i}") - setattr(self, f"water04{l}", - Arc(source=evap.tube_outlet, destination=dest)) + setattr(self, f"water04{l}", Arc(source=evap.tube_outlet, destination=dest)) self.water04f = Arc( - source=self.heat_pump_hot_terminus.outlet, - destination=self.steam_mix.inlet6 + source=self.heat_pump_hot_terminus.outlet, destination=self.steam_mix.inlet6 ) self.water05 = Arc( - source=self.steam_mix.outlet, - destination=self.water_compressor.inlet + source=self.steam_mix.outlet, destination=self.water_compressor.inlet ) self.feed01 = Arc( source=self.water_compressor.outlet, - destination=self.feed_hot_exchanger.tube_inlet + destination=self.feed_hot_exchanger.tube_inlet, ) pyo.TransformationFactory("network.expand_arcs").apply_to(self) @@ -681,15 +721,22 @@ def rule_flow_mol(blk, t): return blk.properties_in[t].flow_mol == blk.properties_out[t].flow_mol def rule_flow_mol_scale_down(blk, t): - return (blk.properties_in[t].flow_mol - == self.number_cells * blk.properties_out[t].flow_mol) + return ( + blk.properties_in[t].flow_mol + == self.number_cells * blk.properties_out[t].flow_mol + ) def rule_flow_mol_scale_up(blk, t): - return (self.number_cells * blk.properties_in[t].flow_mol - == blk.properties_out[t].flow_mol) + return ( + self.number_cells * blk.properties_in[t].flow_mol + == blk.properties_out[t].flow_mol + ) def rule_mole_frac(blk, t, j): - return blk.properties_in[t].mole_frac_comp[j] == blk.properties_out[t].mole_frac_comp[j] + return ( + blk.properties_in[t].mole_frac_comp[j] + == blk.properties_out[t].mole_frac_comp[j] + ) def rule_temperature(blk, t): return blk.properties_in[t].temperature == blk.properties_out[t].temperature @@ -697,27 +744,31 @@ def rule_temperature(blk, t): def rule_pressure(blk, t): return blk.properties_in[t].pressure == blk.properties_out[t].pressure - - self.product_dryer.temperature_eqn = pyo.Constraint(self.time, - rule=rule_temperature) - self.product_dryer.pressure_eqn = pyo.Constraint(self.time, - rule=rule_pressure) + self.product_dryer.temperature_eqn = pyo.Constraint( + self.time, rule=rule_temperature + ) + self.product_dryer.pressure_eqn = pyo.Constraint(self.time, rule=rule_pressure) @self.product_dryer.Constraint(self.time) def flow_mol_eqn(b, t): - return b.properties_in[t].flow_mol_comp["H2"] == b.properties_out[t].flow_mol + return ( + b.properties_in[t].flow_mol_comp["H2"] == b.properties_out[t].flow_mol + ) @self.product_dryer.Constraint(self.time) def mole_frac_comp_eqn(b, t): return 1 == b.properties_out[t].mole_frac_comp["H2"] - self.feed_translator.temperature_eqn = pyo.Constraint(self.time, - rule=rule_temperature) - self.feed_translator.pressure_eqn = pyo.Constraint(self.time, - rule=rule_pressure) + self.feed_translator.temperature_eqn = pyo.Constraint( + self.time, rule=rule_temperature + ) + self.feed_translator.pressure_eqn = pyo.Constraint( + self.time, rule=rule_pressure + ) - self.feed_translator.flow_mol_eqn = pyo.Constraint(self.time, - rule=rule_flow_mol) + self.feed_translator.flow_mol_eqn = pyo.Constraint( + self.time, rule=rule_flow_mol + ) @self.feed_translator.Constraint(self.time) def mole_frac_comp_H2_eqn(b, t): @@ -727,16 +778,18 @@ def mole_frac_comp_H2_eqn(b, t): def mole_frac_comp_H2O_eqn(b, t): return b.properties_out[t].mole_frac_comp["H2O"] == 1 - 1e-19 - self.h2_mass_production = pyo.Var(self.time, initialize=2, - units=pyo.units.kg / pyo.units.s) + self.h2_mass_production = pyo.Var( + self.time, initialize=2, units=pyo.units.kg / pyo.units.s + ) @self.Constraint(self.time) def h2_mass_production_eqn(b, t): return ( - b.h2_mass_production[t] - == 0.002016 * (pyo.units.kg / pyo.units.mol) * - b.feed_recycle_split.out_state[t].flow_mol * - b.feed_recycle_split.out_state[t].mole_frac_comp["H2"] + b.h2_mass_production[t] + == 0.002016 + * (pyo.units.kg / pyo.units.mol) + * b.feed_recycle_split.out_state[t].flow_mol + * b.feed_recycle_split.out_state[t].mole_frac_comp["H2"] ) self.soec_single_pass_water_conversion = pyo.Var(self.time, initialize=0.7) @@ -744,67 +797,87 @@ def h2_mass_production_eqn(b, t): @self.Constraint(self.time) def soec_single_pass_water_conversion_eqn(b, t): return b.soec_single_pass_water_conversion[t] == ( - (b.soec_module.solid_oxide_cell.fuel_channel.flow_mol_comp_outlet[t, "H2"] - - b.soec_module.solid_oxide_cell.fuel_channel.flow_mol_comp_inlet[t, "H2"]) - / b.soec_module.solid_oxide_cell.fuel_channel.flow_mol_comp_inlet[t, "H2O"]) + ( + b.soec_module.solid_oxide_cell.fuel_channel.flow_mol_comp_outlet[ + t, "H2" + ] + - b.soec_module.solid_oxide_cell.fuel_channel.flow_mol_comp_inlet[ + t, "H2" + ] + ) + / b.soec_module.solid_oxide_cell.fuel_channel.flow_mol_comp_inlet[ + t, "H2O" + ] + ) self.soec_overall_water_conversion = pyo.Var(self.time, initialize=0.75) @self.Constraint(self.time) def soec_overall_water_conversion_eqn(b, t): - return (b.soec_overall_water_conversion[t] == - 1 - b.feed_recycle_split.inlet.mole_frac_comp[t, "H2O"]) - + return ( + b.soec_overall_water_conversion[t] + == 1 - b.feed_recycle_split.inlet.mole_frac_comp[t, "H2O"] + ) @self.Expression(self.time) def soec_power_per_h2(b, t): return b.soec_module.electrical_work[t] / b.h2_mass_production[t] def rule_sat_vapor(b, t): - return (b.properties_out[t].enth_mol - == b.properties_out[t].enth_mol_sat_phase["Vap"] + 30) + return ( + b.properties_out[t].enth_mol + == b.properties_out[t].enth_mol_sat_phase["Vap"] + 30 + ) - self.heat_pump_hot_terminus.control_volume.sat_vapor_eqn = pyo.Constraint(self.time, rule=rule_sat_vapor) + self.heat_pump_hot_terminus.control_volume.sat_vapor_eqn = pyo.Constraint( + self.time, rule=rule_sat_vapor + ) - for hx in [self.water_evaporator01, - self.water_evaporator02, - self.water_evaporator03, - self.water_evaporator04, - self.water_evaporator05, - ]: + for hx in [ + self.water_evaporator01, + self.water_evaporator02, + self.water_evaporator03, + self.water_evaporator04, + self.water_evaporator05, + ]: hx.tube.sat_vapor_eqn = pyo.Constraint(self.time, rule=rule_sat_vapor) - self.phase_change_eps = pyo.Param(default=0.1, mutable=True, - units=pyo.units.K) + self.phase_change_eps = pyo.Param(default=0.1, mutable=True, units=pyo.units.K) def rule_dT_in(b, t): return ( - b.delta_temperature_in[t] - == b.hot_side.properties_in[t].temperature - # - smooth_max(b.cold_side.properties_out[t].temperature, - # b.cold_side.properties_out[t].temperature_sat, - # eps = self.phase_change_eps) - - b.cold_side.properties_out[t].temperature_sat + b.delta_temperature_in[t] + == b.hot_side.properties_in[t].temperature + # - smooth_max(b.cold_side.properties_out[t].temperature, + # b.cold_side.properties_out[t].temperature_sat, + # eps = self.phase_change_eps) + - b.cold_side.properties_out[t].temperature_sat ) def rule_dT_out(b, t): return ( - b.delta_temperature_out[t] - == b.hot_side.properties_out[t].temperature - # - smooth_max(b.cold_side.properties_in[t].temperature, - # b.cold_side.properties_in[t].temperature_sat, - # eps = self.phase_change_eps) - - b.cold_side.properties_in[t].temperature_sat + b.delta_temperature_out[t] + == b.hot_side.properties_out[t].temperature + # - smooth_max(b.cold_side.properties_in[t].temperature, + # b.cold_side.properties_in[t].temperature_sat, + # eps = self.phase_change_eps) + - b.cold_side.properties_in[t].temperature_sat ) # Evaporation via heat exchange with a hot gas - for hx in [self.water_evaporator01, self.water_evaporator02, self.water_evaporator05]: + for hx in [ + self.water_evaporator01, + self.water_evaporator02, + self.water_evaporator05, + ]: hx.del_component("delta_temperature_in_equation") hx.del_component("delta_temperature_out_equation") - hx.delta_temperature_in_equation = pyo.Constraint(self.time, - rule=rule_dT_in) - hx.delta_temperature_out_equation = pyo.Constraint(self.time, - rule=rule_dT_out) + hx.delta_temperature_in_equation = pyo.Constraint( + self.time, rule=rule_dT_in + ) + hx.delta_temperature_out_equation = pyo.Constraint( + self.time, rule=rule_dT_out + ) # Evaporation via heat exchange with condensing water in H2 stream for hx in [self.water_evaporator03, self.water_evaporator04]: hx.del_component("delta_temperature_in_equation") @@ -814,38 +887,48 @@ def rule_dT_out(b, t): def delta_temperature_in_equation(b, t): eps = self.phase_change_eps try: - tdew_in = hx.hot_side.properties_in[t].temperature_dew[("Liq", "Vap")] + tdew_in = hx.hot_side.properties_in[t].temperature_dew[ + ("Liq", "Vap") + ] # eps = hx.hot_side.properties_in[t].eps_2_Liq_Vap except KeyError: - tdew_in = hx.hot_side.properties_in[t].temperature_dew[("Vap", "Liq")] + tdew_in = hx.hot_side.properties_in[t].temperature_dew[ + ("Vap", "Liq") + ] # eps = hx.hot_side.properties_in[t].eps_2_Vap_Liq return ( - b.delta_temperature_in[t] - == smooth_min(b.hot_side.properties_in[t].temperature, - tdew_in, eps=eps) - # - smooth_max(b.cold_side.properties_out[t].temperature, - # b.cold_side.properties_out[t].temperature_sat, - # eps=eps) - - b.cold_side.properties_out[t].temperature + b.delta_temperature_in[t] + == smooth_min( + b.hot_side.properties_in[t].temperature, tdew_in, eps=eps + ) + # - smooth_max(b.cold_side.properties_out[t].temperature, + # b.cold_side.properties_out[t].temperature_sat, + # eps=eps) + - b.cold_side.properties_out[t].temperature ) @hx.Constraint(self.time) def delta_temperature_out_equation(b, t): eps = self.phase_change_eps try: - tdew_out = hx.hot_side.properties_out[t].temperature_dew[("Liq", "Vap")] + tdew_out = hx.hot_side.properties_out[t].temperature_dew[ + ("Liq", "Vap") + ] # eps = hx.hot_side.properties_out[t].eps_2_Liq_Vap except KeyError: - tdew_out = hx.hot_side.properties_out[t].temperature_dew[("Vap", "Liq")] + tdew_out = hx.hot_side.properties_out[t].temperature_dew[ + ("Vap", "Liq") + ] # eps = hx.hot_side.properties_out[t].eps_2_Vap_Liq return ( - b.delta_temperature_out[t] - == smooth_min(b.hot_side.properties_out[t].temperature, - tdew_out, eps=eps) - # - smooth_max(b.cold_side.properties_in[t].temperature, - # b.cold_side.properties_in[t].temperature_sat, - # eps=eps) - - b.cold_side.properties_in[t].temperature_sat + b.delta_temperature_out[t] + == smooth_min( + b.hot_side.properties_out[t].temperature, tdew_out, eps=eps + ) + # - smooth_max(b.cold_side.properties_in[t].temperature, + # b.cold_side.properties_in[t].temperature_sat, + # eps=eps) + - b.cold_side.properties_in[t].temperature_sat ) self.water_preheater.del_component("delta_temperature_in_equation") @@ -853,17 +936,16 @@ def delta_temperature_out_equation(b, t): @self.water_preheater.Constraint(self.time) def delta_temperature_in_equation(b, t): - eps = self.phase_change_eps - try: - tdew_in = b.hot_side.properties_in[t].temperature_dew[("Liq", "Vap")] - except KeyError: - tdew_in = b.hot_side.properties_in[t].temperature_dew[("Vap", "Liq")] - return ( - b.delta_temperature_in[t] - == smooth_min(b.hot_side.properties_in[t].temperature, - tdew_in, eps=eps) - - b.cold_side.properties_out[t].temperature - ) + eps = self.phase_change_eps + try: + tdew_in = b.hot_side.properties_in[t].temperature_dew[("Liq", "Vap")] + except KeyError: + tdew_in = b.hot_side.properties_in[t].temperature_dew[("Vap", "Liq")] + return ( + b.delta_temperature_in[t] + == smooth_min(b.hot_side.properties_in[t].temperature, tdew_in, eps=eps) + - b.cold_side.properties_out[t].temperature + ) @self.water_preheater.Constraint(self.time) def delta_temperature_out_equation(b, t): @@ -873,60 +955,80 @@ def delta_temperature_out_equation(b, t): except KeyError: tdew_out = b.hot_side.properties_out[t].temperature_dew[("Vap", "Liq")] return ( - b.delta_temperature_out[t] - == smooth_min(b.hot_side.properties_out[t].temperature, - tdew_out, eps=eps) - - b.cold_side.properties_in[t].temperature + b.delta_temperature_out[t] + == smooth_min( + b.hot_side.properties_out[t].temperature, tdew_out, eps=eps + ) + - b.cold_side.properties_in[t].temperature ) @self.Constraint(self.time) def heat_pump_evaporator_eqn(b, t): - return 0 == (b.heat_pump_hot_terminus.heat_duty[t] - + b.heat_pump.heat_out[t]) + return 0 == ( + b.heat_pump_hot_terminus.heat_duty[t] + b.heat_pump.heat_out[t] + ) self.cmp04.del_component(self.cmp04.work_mechanical) - self.cmp04.work_mechanical = pyo.Var(self.time, units=pyo.units.W, initialize=1e6, bounds=(0, None)) + self.cmp04.work_mechanical = pyo.Var( + self.time, units=pyo.units.W, initialize=1e6, bounds=(0, None) + ) + @self.cmp04.Constraint(self.time) def work_mechanical_eqn(b, t): - return b.work_mechanical[t] == (b.outlet.flow_mol[t] * - (b.control_volume.properties_out[t].gibbs_mol - b.control_volume.properties_in[t].gibbs_mol) - / b.efficiency_isentropic[t]) + return b.work_mechanical[t] == ( + b.outlet.flow_mol[t] + * ( + b.control_volume.properties_out[t].gibbs_mol + - b.control_volume.properties_in[t].gibbs_mol + ) + / b.efficiency_isentropic[t] + ) @self.cmp04.Expression(self.time) def heat_duty(b, t): - return ((b.control_volume.properties_out[t].entr_mol - - b.control_volume.properties_in[t].entr_mol) - * b.outlet.temperature[t] * b.outlet.flow_mol[t] - - (1 - b.efficiency_isentropic[t]) * b.work_mechanical[t]) + return ( + b.control_volume.properties_out[t].entr_mol + - b.control_volume.properties_in[t].entr_mol + ) * b.outlet.temperature[t] * b.outlet.flow_mol[t] - ( + 1 - b.efficiency_isentropic[t] + ) * b.work_mechanical[ + t + ] @self.Constraint(self.time) def heat_pump_refrigeration_eqn(b, t): - return 0 == (b.heat_pump.heat_in[t] + b.heat_source.heat_duty[t] - + b.product_flash05.heat_duty[t] + b.cmp04.heat_duty[t]) + return 0 == ( + b.heat_pump.heat_in[t] + + b.heat_source.heat_duty[t] + + b.product_flash05.heat_duty[t] + + b.cmp04.heat_duty[t] + ) @self.Expression(self.time) def total_compressor_power(b, t): return ( - b.cmp01.work_mechanical[t] + - b.cmp02.work_mechanical[t] + - b.cmp03.work_mechanical[t] + - b.cmp04.work_mechanical[t] + b.cmp01.work_mechanical[t] + + b.cmp02.work_mechanical[t] + + b.cmp03.work_mechanical[t] + + b.cmp04.work_mechanical[t] ) @self.Expression(self.time) def total_electric_power(b, t): return ( - b.soec_module.electrical_work[t] + - # b.sweep_turbine.control_volume.work[t] + - # b.sweep_compressor.control_volume.work[t] + - b.sweep_blower.control_volume.work[t] + - b.sweep_heater.control_volume.heat[t] + - b.feed_heater.control_volume.heat[t] + - # b.water_pump.control_volume.work[t] + - b.water_compressor.control_volume.work[t] + - b.heat_pump.work_mechanical[t] + - b.total_compressor_power[t] + b.soec_module.electrical_work[t] + + + # b.sweep_turbine.control_volume.work[t] + + # b.sweep_compressor.control_volume.work[t] + + b.sweep_blower.control_volume.work[t] + + b.sweep_heater.control_volume.heat[t] + + b.feed_heater.control_volume.heat[t] + + + # b.water_pump.control_volume.work[t] + + b.water_compressor.control_volume.work[t] + + b.heat_pump.work_mechanical[t] + + b.total_compressor_power[t] ) @self.Expression(self.time) @@ -959,11 +1061,17 @@ def scale_indexed_constraint(con, sf): # pp.set_default_scaling("pressure", 1E-5) # pp.set_default_scaling( # "enth_mol_phase", 1e-3, index="Vap") - for blk in [self.sweep_recycle_mix.feed_state, self.sweep_recycle_mix.recycle_state, - self.sweep_recycle_mix.mixed_state, self.feed_recycle_mix.feed_state, - self.feed_recycle_mix.recycle_state, self.feed_recycle_mix.mixed_state, - self.sweep_blower.control_volume.properties_in, self.sweep_blower.control_volume.properties_out, - self.sweep_blower.properties_isentropic]: + for blk in [ + self.sweep_recycle_mix.feed_state, + self.sweep_recycle_mix.recycle_state, + self.sweep_recycle_mix.mixed_state, + self.feed_recycle_mix.feed_state, + self.feed_recycle_mix.recycle_state, + self.feed_recycle_mix.mixed_state, + self.sweep_blower.control_volume.properties_in, + self.sweep_blower.control_volume.properties_out, + self.sweep_blower.properties_isentropic, + ]: for t in self.time: for p in ["Vap"]: iscale.set_scaling_factor(blk[t].enth_mol_phase[p], 1e-4) @@ -977,25 +1085,28 @@ def scale_indexed_constraint(con, sf): for heater in [self.heat_pump_hot_terminus]: ssf(heater.control_volume.heat, 1e-6) for t in self.time: - for blk in [heater.control_volume.properties_in, heater.control_volume.properties_out]: + for blk in [ + heater.control_volume.properties_in, + heater.control_volume.properties_out, + ]: for p in ["Liq", "Vap"]: ssf(blk[t].enth_mol_phase[p], 1e-4) ssf(blk[t].enth_mol_phase[p], 1e-4) if hasattr(heater.control_volume, "sat_vapor_eqn"): cst(heater.control_volume.sat_vapor_eqn[t], 1e-4) - - for hx in [self.feed_hot_exchanger, - # self.feed_medium_exchanger, - self.sweep_hot_exchanger, - self.sweep_medium_exchanger, - self.water_preheater, - self.water_evaporator01, - self.water_evaporator02, - self.water_evaporator03, - self.water_evaporator04, - self.water_evaporator05 - ]: + for hx in [ + self.feed_hot_exchanger, + # self.feed_medium_exchanger, + self.sweep_hot_exchanger, + self.sweep_medium_exchanger, + self.water_preheater, + self.water_evaporator01, + self.water_evaporator02, + self.water_evaporator03, + self.water_evaporator04, + self.water_evaporator05, + ]: ssf(hx.overall_heat_transfer_coefficient, 1e-2) ssf(hx.area, 1e-3) for t in self.time: @@ -1018,28 +1129,34 @@ def scale_indexed_constraint(con, sf): for cmp in [self.cmp01, self.cmp02, self.cmp03]: for t in self.time: ssf(cmp.control_volume.work[t], 1e-6) - for blk in [cmp.control_volume.properties_in, - cmp.control_volume.properties_out, - cmp.properties_isentropic]: + for blk in [ + cmp.control_volume.properties_in, + cmp.control_volume.properties_out, + cmp.properties_isentropic, + ]: for p in ["Vap"]: ssf(blk[t].enth_mol_phase[p], 1e-4) for t in self.time: ssf(self.cmp04.control_volume.properties_in[t].enth_mol_phase["Vap"], 1e-4) ssf(self.cmp04.control_volume.properties_out[t].enth_mol_phase["Vap"], 1e-4) - for cond in [self.product_flash01, - # self.product_flash02, - self.product_flash03, - self.product_flash04, - self.product_flash05]: + for cond in [ + self.product_flash01, + # self.product_flash02, + self.product_flash03, + self.product_flash04, + self.product_flash05, + ]: for t in self.time: ssf(cond.control_volume.heat[t], 1e-6) - for blk in [cond.control_volume.properties_in, cond.control_volume.properties_out]: + for blk in [ + cond.control_volume.properties_in, + cond.control_volume.properties_out, + ]: for p in ["Liq", "Vap"]: ssf(blk[t].enth_mol_phase[p], 1e-4) - for blk in [self.feed_translator, - self.product_dryer]: + for blk in [self.feed_translator, self.product_dryer]: scale_indexed_constraint(blk.temperature_eqn, 1e-2) scale_indexed_constraint(blk.pressure_eqn, 1e-5) scale_indexed_constraint(blk.flow_mol_eqn, 1e-3) @@ -1060,11 +1177,14 @@ def scale_indexed_constraint(con, sf): for t in self.time: iscale.constraint_scaling_transform( - self.sweep_recycle_mix.pressure_equality_eqn[t], 1e-5) + self.sweep_recycle_mix.pressure_equality_eqn[t], 1e-5 + ) iscale.constraint_scaling_transform( - self.feed_recycle_mix.pressure_equality_eqn[t], 1e-5) + self.feed_recycle_mix.pressure_equality_eqn[t], 1e-5 + ) iscale.constraint_scaling_transform( - self.steam_mix.pressure_equality_eqn[t], 1e-5) + self.steam_mix.pressure_equality_eqn[t], 1e-5 + ) ssf(self.soec_module.number_cells, 1e-6) @@ -1085,16 +1205,26 @@ def _set_initial_inputs(self): self.feed_hot_exchanger.tube.properties_in[0].pressure.fix(1.2e5) self.feed_hot_exchanger.tube.properties_in[0].flow_mol.fix(3100) self.feed_hot_exchanger.tube.properties_in[0].enth_mol.fix( - iapws95.htpx(T=489.18 * pyo.units.K, P=1.2e5 * pyo.units.Pa)) + iapws95.htpx(T=489.18 * pyo.units.K, P=1.2e5 * pyo.units.Pa) + ) self._set_gas_port( - self.sweep_blower.inlet, F=5653, T=288.15, P=101000, y=self.sweep_comp) + self.sweep_blower.inlet, F=5653, T=288.15, P=101000, y=self.sweep_comp + ) self._set_gas_port( - self.feed_recycle_mix.feed, F=3100, T=837.80, P=1.2e5, y=self.feed_comp) + self.feed_recycle_mix.feed, F=3100, T=837.80, P=1.2e5, y=self.feed_comp + ) self._set_gas_port( - self.feed_recycle_mix.recycle, F=3100, T=962.17, P=1.2e5, y={"H2": 0.8, "H2O": 0.2}, fix=False) + self.feed_recycle_mix.recycle, + F=3100, + T=962.17, + P=1.2e5, + y={"H2": 0.8, "H2O": 0.2}, + fix=False, + ) self._set_gas_port( - self.sweep_recycle_mix.feed, F=5653, T=887.23, P=1.2e5, y=self.sweep_comp) + self.sweep_recycle_mix.feed, F=5653, T=887.23, P=1.2e5, y=self.sweep_comp + ) recycle_comp = { "O2": 0.35, "H2O": (1 - 0.35) / (1 - 0.2074) * 0.0099, @@ -1103,7 +1233,13 @@ def _set_initial_inputs(self): "Ar": (1 - 0.35) / (1 - 0.2074) * 0.0092, } self._set_gas_port( - self.sweep_recycle_mix.recycle, F=6893, T=983.84, P=1.2e5, y=recycle_comp, fix=False) + self.sweep_recycle_mix.recycle, + F=6893, + T=983.84, + P=1.2e5, + y=recycle_comp, + fix=False, + ) self.soec_module.potential_cell.fix(1.334) # Recycle splits @@ -1120,7 +1256,6 @@ def _set_initial_inputs(self): self.feed_heater.control_volume.properties_out[:].temperature.fix(929.13) self.sweep_heater.control_volume.properties_out[:].temperature.fix(945.41) - # self.h2_precooler.control_volume.properties_out[:].temperature.fix(300) self.cmp01.ratioP.fix(3) self.cmp01.efficiency_isentropic.fix(0.85) @@ -1131,7 +1266,7 @@ def _set_initial_inputs(self): self.cmp03.ratioP.fix(2.5) self.cmp03.efficiency_isentropic.fix(0.85) - #self.cmp04.ratioP.fix(9.0) + # self.cmp04.ratioP.fix(9.0) # self.cmp04.ratioP.fix(9.0**(1/12)) self.cmp04.efficiency_isentropic.fix(0.8) # self.cmp04.cold_gas_temperature.fix(273.15+15) @@ -1167,10 +1302,11 @@ def _set_initial_inputs(self): # self.water_preheater.area.fix(600) for t in self.time: - self.product_flash05.control_volume.properties_out[t] \ - .temperature.fix(273.15 + 15) + self.product_flash05.control_volume.properties_out[t].temperature.fix( + 273.15 + 15 + ) for i in range(1, 6): - if i == 2: # or i == 3: + if i == 2: # or i == 3: continue flash = getattr(self, f"product_flash0{i}") flash.deltaP.fix(0) @@ -1182,30 +1318,32 @@ def _set_initial_inputs(self): water_inlet.pressure.fix(1.013e5) water_inlet.enth_mol.fix( - iapws95.htpx(T=(273.15 + 15) * pyo.units.K, P=1.013e5 * pyo.units.Pa)) + iapws95.htpx(T=(273.15 + 15) * pyo.units.K, P=1.013e5 * pyo.units.Pa) + ) water_inlet.flow_mol.fix(3100) self.water_valve.outlet.pressure.fix(0.4e5) - self.water_compressor.control_volume.properties_out[:].pressure.fix( - 1.2e5) + self.water_compressor.control_volume.properties_out[:].pressure.fix(1.2e5) self.water_compressor.efficiency_isentropic[:].fix(0.75) self.heat_source.inlet.pressure.fix(1.013e5) self.heat_source.inlet.enth_mol.fix( - iapws95.htpx(T=(273.15 + 15) * pyo.units.K, P=1.013e5 * pyo.units.Pa)) + iapws95.htpx(T=(273.15 + 15) * pyo.units.K, P=1.013e5 * pyo.units.Pa) + ) for t in self.time: self.heat_source.inlet.flow_mol[t].value = 10000 self.heat_source.outlet.enth_mol.fix( - iapws95.htpx(T=(273.15 + 5) * pyo.units.K, P=1.013e5 * pyo.units.Pa)) + iapws95.htpx(T=(273.15 + 5) * pyo.units.K, P=1.013e5 * pyo.units.Pa) + ) set_indexed_variable_bounds(self.heat_source.inlet.flow_mol[0], (0, None)) def initialize_build( - self, - outlvl=idaeslog.NOTSET, - solver="ipopt", - optarg=None, - load_from=None, - save_to="soec_standalone_init.json.gz", + self, + outlvl=idaeslog.NOTSET, + solver="ipopt", + optarg=None, + load_from=None, + save_to="soec_standalone_init.json.gz", ): if load_from is not None: @@ -1256,7 +1394,9 @@ def safe_solve(blk): propagate_state(self.sweep01) self.sweep_medium_exchanger.tube_outlet.temperature.fix(360.08) - tube_flags = self.sweep_medium_exchanger.tube.initialize(outlvl=outlvl, solver=solver, optarg=optarg) + tube_flags = self.sweep_medium_exchanger.tube.initialize( + outlvl=outlvl, solver=solver, optarg=optarg + ) safe_solve(self.sweep_medium_exchanger.tube) self.sweep_medium_exchanger.tube_outlet.temperature.unfix() self.sweep_medium_exchanger.tube.properties_in.release_state(tube_flags) @@ -1268,20 +1408,24 @@ def safe_solve(blk): propagate_state(self.sweep03) propagate_state(self.ostrm04) - self.feed_hot_exchanger.initialize(outlvl=outlvl, solver=solver, optarg=optarg, - duty=(40310061, pyo.units.W)) + self.feed_hot_exchanger.initialize( + outlvl=outlvl, solver=solver, optarg=optarg, duty=(40310061, pyo.units.W) + ) propagate_state(self.feed02a) self.feed_translator.initialize(outlvl=outlvl, solver=solver, optarg=optarg) propagate_state(self.feed02b) - shell_flags = self.water_evaporator05.shell.initialize(outlvl=outlvl, solver=solver, optarg=optarg) + shell_flags = self.water_evaporator05.shell.initialize( + outlvl=outlvl, solver=solver, optarg=optarg + ) safe_solve(self.water_evaporator05.shell) self.water_evaporator05.shell.properties_in.release_state(shell_flags) propagate_state(self.ostrm06) - self.sweep_medium_exchanger.initialize(outlvl=outlvl, solver=solver, optarg=optarg, - duty=(8917353, pyo.units.W)) + self.sweep_medium_exchanger.initialize( + outlvl=outlvl, solver=solver, optarg=optarg, duty=(8917353, pyo.units.W) + ) propagate_state(self.sweep02) propagate_state(self.hstrm04) @@ -1295,19 +1439,30 @@ def safe_solve(blk): self.water_evaporator04.shell_outlet.temperature.fix(349.84) self.water_evaporator05.shell_outlet.temperature.fix(349.84) - for hx in [self.water_evaporator01, self.water_evaporator02, - self.water_evaporator03, self.water_evaporator04, - self.water_evaporator05, self.water_preheater]: + for hx in [ + self.water_evaporator01, + self.water_evaporator02, + self.water_evaporator03, + self.water_evaporator04, + self.water_evaporator05, + self.water_preheater, + ]: hx.heat_transfer_equation.deactivate() - shell_flags = self.water_evaporator01.shell.initialize(outlvl=outlvl, solver=solver, optarg=optarg) + shell_flags = self.water_evaporator01.shell.initialize( + outlvl=outlvl, solver=solver, optarg=optarg + ) safe_solve(self.water_evaporator01.shell) self.water_evaporator01.shell.properties_in.release_state(shell_flags) propagate_state(self.hstrm05) - tube_flags = self.water_preheater.tube.initialize(outlvl=outlvl, solver=solver, optarg=optarg) + tube_flags = self.water_preheater.tube.initialize( + outlvl=outlvl, solver=solver, optarg=optarg + ) safe_solve(self.water_preheater.tube) - shell_flags = self.water_preheater.shell.initialize(outlvl=outlvl, solver=solver, optarg=optarg) + shell_flags = self.water_preheater.shell.initialize( + outlvl=outlvl, solver=solver, optarg=optarg + ) safe_solve(self.water_preheater) self.water_preheater.tube.properties_in.release_state(tube_flags) self.water_preheater.shell.properties_in.release_state(shell_flags) @@ -1320,7 +1475,9 @@ def safe_solve(blk): self.cmp01.initialize(outlvl=outlvl, solver=solver, optarg=optarg) propagate_state(self.hstrm08) - shell_flags = self.water_evaporator02.shell.initialize(outlvl=outlvl, solver=solver, optarg=optarg) + shell_flags = self.water_evaporator02.shell.initialize( + outlvl=outlvl, solver=solver, optarg=optarg + ) safe_solve(self.water_evaporator02.shell) self.water_evaporator02.shell.properties_in.release_state(shell_flags) @@ -1330,7 +1487,9 @@ def safe_solve(blk): self.cmp02.initialize(outlvl=outlvl, solver=solver, optarg=optarg) propagate_state(self.hstrm11) - shell_flags = self.water_evaporator03.shell.initialize(outlvl=outlvl, solver=solver, optarg=optarg) + shell_flags = self.water_evaporator03.shell.initialize( + outlvl=outlvl, solver=solver, optarg=optarg + ) safe_solve(self.water_evaporator03.shell) self.water_evaporator03.shell.properties_in.release_state(shell_flags) @@ -1340,7 +1499,9 @@ def safe_solve(blk): self.cmp03.initialize(outlvl=outlvl, solver=solver, optarg=optarg) propagate_state(self.hstrm14) - shell_flags = self.water_evaporator04.shell.initialize(outlvl=outlvl, solver=solver, optarg=optarg) + shell_flags = self.water_evaporator04.shell.initialize( + outlvl=outlvl, solver=solver, optarg=optarg + ) safe_solve(self.water_evaporator04.shell) self.water_evaporator04.shell.properties_in.release_state(shell_flags) @@ -1366,12 +1527,14 @@ def safe_solve(blk): self.water_split.initialize(outlvl=outlvl, solver=solver, optarg=optarg) self.water_split.split_fraction.unfix() - for i, l in zip(range(1, 6), 'abcde'): + for i, l in zip(range(1, 6), "abcde"): arc_in = getattr(self, f"water03{l}") propagate_state(arc_in) evap = getattr(self, f"water_evaporator0{i}") - tube_flags = evap.tube.initialize(outlvl=outlvl, solver=solver, optarg=optarg) + tube_flags = evap.tube.initialize( + outlvl=outlvl, solver=solver, optarg=optarg + ) # import pdb; pdb.set_trace() safe_solve(evap.tube) evap.tube.properties_in.release_state(tube_flags) @@ -1380,7 +1543,9 @@ def safe_solve(blk): propagate_state(arc_out) propagate_state(self.water03f) - self.heat_pump_hot_terminus.initialize(outlvl=outlvl, solver=solver, optarg=optarg) + self.heat_pump_hot_terminus.initialize( + outlvl=outlvl, solver=solver, optarg=optarg + ) self.heat_pump_hot_terminus.inlet.fix() safe_solve(self.heat_pump_hot_terminus) self.heat_pump_hot_terminus.inlet.unfix() @@ -1400,9 +1565,13 @@ def safe_solve(blk): safe_solve(self.heat_source) self.heat_source.inlet.flow_mol.unfix() - for hx in [self.water_evaporator01, self.water_evaporator02, - self.water_evaporator03, self.water_evaporator04, - self.water_evaporator05, ]: + for hx in [ + self.water_evaporator01, + self.water_evaporator02, + self.water_evaporator03, + self.water_evaporator04, + self.water_evaporator05, + ]: hx.heat_transfer_equation.activate() hx.shell_outlet.temperature.unfix() self.water_preheater.heat_transfer_equation.activate() @@ -1418,7 +1587,6 @@ def safe_solve(blk): if save_to is not None: iutil.to_json(self, fname=save_to) - def _add_tags(self): tag_group = iutil.ModelTagGroup() self.tags_streams = tag_group @@ -1455,7 +1623,7 @@ def _add_tags(self): doc=f"{i}: volumetric flow", expr=s.flow_vol, format_string="{:.3f}", - display_units=pyo.units.m ** 3 / pyo.units.s, + display_units=pyo.units.m**3 / pyo.units.s, ) tag_group[f"{i}_P"] = iutil.ModelTag( doc=f"{i}: pressure", @@ -1509,9 +1677,12 @@ def _add_tags(self): ) tag_group["soec_current"] = iutil.ModelTag( doc="SOEC electrical current", - expr=self.soec_module.number_cells * sum(self.soec_module.solid_oxide_cell.current_density[0, iz] - * self.soec_module.solid_oxide_cell.fuel_electrode.xface_area[iz] - for iz in self.soec_module.solid_oxide_cell.iznodes), + expr=self.soec_module.number_cells + * sum( + self.soec_module.solid_oxide_cell.current_density[0, iz] + * self.soec_module.solid_oxide_cell.fuel_electrode.xface_area[iz] + for iz in self.soec_module.solid_oxide_cell.iznodes + ), format_string="{:.3f}", display_units=pyo.units.MA, ) @@ -1529,7 +1700,10 @@ def _add_tags(self): ) tag_group["heat_pump_water_draw"] = iutil.ModelTag( doc="Heat pump water draw", - expr=self.heat_source.inlet.flow_mol[0] * 0.01802 * pyo.units.kg/pyo.units.mol, + expr=self.heat_source.inlet.flow_mol[0] + * 0.01802 + * pyo.units.kg + / pyo.units.mol, format_string="{:.3f}", display_units=pyo.units.kg / pyo.units.s, ) @@ -1558,13 +1732,13 @@ def _add_tags(self): display_units=pyo.units.MW, ) tag_group["total_electric_power"] = iutil.ModelTag( - doc="Total electric power for SOEC and auxilaries", + doc="Total electric power for SOEC and auxiliaries", expr=self.total_electric_power[0], format_string="{:.3f}", display_units=pyo.units.MW, ) tag_group["total_electric_power_per_h2"] = iutil.ModelTag( - doc="Total electric power for SOEC and auxilaries per H2 produced", + doc="Total electric power for SOEC and auxiliaries per H2 produced", expr=self.total_electric_power_per_h2[0], format_string="{:.3f}", display_units=pyo.units.MJ / pyo.units.kg, @@ -1645,41 +1819,62 @@ def _make_temperature_gradient_terms(self): for iz in soec.iznodes: assert abs(soec.zfaces[iz + 1] - soec.zfaces[iz] - dz) < 1e-8 dz = dz * soec.length_z + def finite_difference(expr, t, ix, iz): # Since this is mostly for reference, no need to worry about upwinding or whatever if iz == soec.iznodes.first(): if ix is None: - return (-1.5 * expr[t, iz] + 2 * expr[t, iz + 1] - 0.5 * expr[t, iz + 2]) / dz + return ( + -1.5 * expr[t, iz] + 2 * expr[t, iz + 1] - 0.5 * expr[t, iz + 2] + ) / dz else: - return (-1.5 * expr[t, ix, iz] + 2 * expr[t, ix, iz + 1] - 0.5 * expr[t, ix, iz + 2]) / dz + return ( + -1.5 * expr[t, ix, iz] + + 2 * expr[t, ix, iz + 1] + - 0.5 * expr[t, ix, iz + 2] + ) / dz elif iz == soec.iznodes.last(): if ix is None: - return (1.5 * expr[t, iz] - 2 * expr[t, iz - 1] + 0.5 * expr[t, iz - 2]) / dz + return ( + 1.5 * expr[t, iz] - 2 * expr[t, iz - 1] + 0.5 * expr[t, iz - 2] + ) / dz else: - return (1.5 * expr[t, ix, iz] - 2 * expr[t, ix, iz - 1] + 0.5 * expr[t, ix, iz - 2]) / dz + return ( + 1.5 * expr[t, ix, iz] + - 2 * expr[t, ix, iz - 1] + + 0.5 * expr[t, ix, iz - 2] + ) / dz else: if ix is None: return (0.5 * expr[t, iz + 1] - 0.5 * expr[t, iz - 1]) / dz else: return (0.5 * expr[t, ix, iz + 1] - 0.5 * expr[t, ix, iz - 1]) / dz - soec.dtemperature_z_dz = pyo.Var(self.time, soec.iznodes, initialize=0, units=pyo.units.K / pyo.units.m) + soec.dtemperature_z_dz = pyo.Var( + self.time, soec.iznodes, initialize=0, units=pyo.units.K / pyo.units.m + ) @soec.Constraint(self.time, soec.iznodes) def dtemperature_z_dz_eqn(b, t, iz): - return b.dtemperature_z_dz[t, iz] == finite_difference(b.temperature_z, t, None, iz) + return b.dtemperature_z_dz[t, iz] == finite_difference( + b.temperature_z, t, None, iz + ) soec.fuel_electrode.dtemperature_dz = pyo.Var( self.time, soec.fuel_electrode.ixnodes, soec.fuel_electrode.iznodes, initialize=0, - units=pyo.units.K / pyo.units.m + units=pyo.units.K / pyo.units.m, ) - @soec.fuel_electrode.Constraint(self.time, soec.fuel_electrode.ixnodes, soec.fuel_electrode.iznodes) + @soec.fuel_electrode.Constraint( + self.time, soec.fuel_electrode.ixnodes, soec.fuel_electrode.iznodes + ) def dtemperature_dz_eqn(b, t, ix, iz): - return b.dtemperature_dz[t, ix, iz] == finite_difference(b.temperature, t, ix, iz) + return b.dtemperature_dz[t, ix, iz] == finite_difference( + b.temperature, t, ix, iz + ) vars = [ soec.dtemperature_z_dz, @@ -1707,7 +1902,11 @@ def no_condensation_eqn(b, t): iscale.constraint_scaling_transform(hx.shell.no_condensation_eqn[0], 1e-2) - for hx in [self.water_evaporator03, self.water_evaporator04, self.water_preheater]: + for hx in [ + self.water_evaporator03, + self.water_evaporator04, + self.water_preheater, + ]: for t in self.time: try: eps = hx.hot_side.properties_in[t].eps_2_Liq_Vap @@ -1739,36 +1938,59 @@ def no_condensation_eqn(b, t): @self.Constraint(self.time) def thermal_gradient_eqn_1(b, t): - return (b.soec_module.fuel_outlet.temperature[t] - b.soec_module.fuel_inlet.temperature[t]) <= delta_T_limit + return ( + b.soec_module.fuel_outlet.temperature[t] + - b.soec_module.fuel_inlet.temperature[t] + ) <= delta_T_limit @self.Constraint(self.time) def thermal_gradient_eqn_2(b, t): - return (b.soec_module.fuel_outlet.temperature[t] - b.soec_module.fuel_inlet.temperature[t]) >= -delta_T_limit + return ( + b.soec_module.fuel_outlet.temperature[t] + - b.soec_module.fuel_inlet.temperature[t] + ) >= -delta_T_limit @self.Constraint(self.time) def thermal_gradient_eqn_3(b, t): - return (b.soec_module.oxygen_outlet.temperature[t] - b.soec_module.oxygen_inlet.temperature[t]) <= delta_T_limit + return ( + b.soec_module.oxygen_outlet.temperature[t] + - b.soec_module.oxygen_inlet.temperature[t] + ) <= delta_T_limit @self.Constraint(self.time) def thermal_gradient_eqn_4(b, t): - return (b.soec_module.oxygen_outlet.temperature[t] - b.soec_module.oxygen_inlet.temperature[t]) >= -delta_T_limit + return ( + b.soec_module.oxygen_outlet.temperature[t] + - b.soec_module.oxygen_inlet.temperature[t] + ) >= -delta_T_limit @self.Constraint(self.time) def thermal_gradient_eqn_5(b, t): - return (b.soec_module.fuel_outlet.temperature[t] - b.soec_module.oxygen_inlet.temperature[t]) <= delta_T_limit + return ( + b.soec_module.fuel_outlet.temperature[t] + - b.soec_module.oxygen_inlet.temperature[t] + ) <= delta_T_limit @self.Constraint(self.time) def thermal_gradient_eqn_6(b, t): - return (b.soec_module.fuel_outlet.temperature[t] - b.soec_module.oxygen_inlet.temperature[t]) >= -delta_T_limit + return ( + b.soec_module.fuel_outlet.temperature[t] + - b.soec_module.oxygen_inlet.temperature[t] + ) >= -delta_T_limit @self.Constraint(self.time) def thermal_gradient_eqn_7(b, t): - return (b.soec_module.oxygen_outlet.temperature[t] - b.soec_module.fuel_inlet.temperature[t]) <= delta_T_limit + return ( + b.soec_module.oxygen_outlet.temperature[t] + - b.soec_module.fuel_inlet.temperature[t] + ) <= delta_T_limit @self.Constraint(self.time) def thermal_gradient_eqn_8(b, t): - return (b.soec_module.oxygen_outlet.temperature[t] - b.soec_module.fuel_inlet.temperature[t]) >= -delta_T_limit - + return ( + b.soec_module.oxygen_outlet.temperature[t] + - b.soec_module.fuel_inlet.temperature[t] + ) >= -delta_T_limit for con in [ self.thermal_gradient_eqn_1, @@ -1778,14 +2000,16 @@ def thermal_gradient_eqn_8(b, t): self.thermal_gradient_eqn_5, self.thermal_gradient_eqn_6, self.thermal_gradient_eqn_7, - self.thermal_gradient_eqn_8 - + self.thermal_gradient_eqn_8, ]: iscale.constraint_scaling_transform(con[0], 1e-2) @self.Constraint(self.time) def average_current_density_constraint(b, t): - return self.soec_module.solid_oxide_cell.average_current_density[0] / 1000 >= -8 + return ( + self.soec_module.solid_oxide_cell.average_current_density[0] / 1000 + >= -8 + ) @self.Constraint(self.time) def sweep_min_concentration_eqn(b, t): @@ -1801,10 +2025,13 @@ def min_h2_feed_eqn(b, t): @self.Constraint(self.time) def water_utilization(b, t): - return (b.water_demand[t] - b.water_recycle[t]) <= 10475 * pyo.units.mol / pyo.units.s + return ( + b.water_demand[t] - b.water_recycle[t] + ) <= 10475 * pyo.units.mol / pyo.units.s iscale.constraint_scaling_transform(self.water_utilization[0], 1e-4) + if __name__ == "__main__": import os from scipy.linalg import svd @@ -1822,7 +2049,7 @@ def water_utilization(b, t): initialize_flowsheet_costing, scale_flowsheet_costing, get_soec_OM_costing, - display_soec_costing + display_soec_costing, ) import idaes.core.util.model_statistics as mstat from idaes.models.properties import iapws95 @@ -1835,17 +2062,17 @@ def water_utilization(b, t): from pyomo.core.expr.current import identify_variables from pyomo.common.collections import ComponentSet - def find_active_constraints_containing_variable(var, blk): con_set = ComponentSet() CUID = pyo.ComponentUID(var) - for c in blk.component_data_objects(ctype=pyo.Constraint, active=True, descend_into=True): + for c in blk.component_data_objects( + ctype=pyo.Constraint, active=True, descend_into=True + ): for v in identify_variables(c.body): if CUID.matches(v): con_set.add(c) return con_set - use_idaes_solver_configuration_defaults() idaes.cfg.ipopt.options.nlp_scaling_method = "user-scaling" idaes.cfg.ipopt.options.OF_ma57_automatic_scaling = "yes" @@ -1857,7 +2084,9 @@ def find_active_constraints_containing_variable(var, blk): m.fs = soec_standalone_flowsheet.SoecStandaloneFlowsheet(dynamic=False) iscale.calculate_scaling_factors(m) - m.fs.initialize_build(outlvl=idaeslog.INFO_LOW) # , load_from="soec_standalone_init.json.gz") + m.fs.initialize_build( + outlvl=idaeslog.INFO_LOW + ) # , load_from="soec_standalone_init.json.gz") print(dof(m)) get_solo_soec_capital_costing(m.fs, CE_index_year="2018") @@ -1876,28 +2105,36 @@ def find_active_constraints_containing_variable(var, blk): format_string="{:.2f}", ) - solver.solve(m, tee=True, options={"tol": 1e-6, "max_iter": 300, "halt_on_ampl_error": "no"}) - + solver.solve( + m, tee=True, options={"tol": 1e-6, "max_iter": 300, "halt_on_ampl_error": "no"} + ) def set_indexed_variable_bounds(var, bounds): for idx, subvar in var.items(): subvar.bounds = bounds - if True: m.fs.obj = pyo.Objective( expr=( - m.fs.costing.annual_electricity_cost - + m.fs.costing.annual_water_cost - + m.fs.costing.total_annualized_cost - + m.fs.costing.annual_fixed_operations_and_maintenance_cost - + m.fs.costing.annual_air_cost + m.fs.costing.annual_electricity_cost + + m.fs.costing.annual_water_cost + + m.fs.costing.total_annualized_cost + + m.fs.costing.annual_fixed_operations_and_maintenance_cost + + m.fs.costing.annual_air_cost ) ) - for hx in [m.fs.feed_hot_exchanger, m.fs.sweep_hot_exchanger, m.fs.sweep_medium_exchanger, - m.fs.water_evaporator01, m.fs.water_evaporator02, m.fs.water_evaporator03, - m.fs.water_evaporator04, m.fs.water_evaporator05, m.fs.water_preheater]: + for hx in [ + m.fs.feed_hot_exchanger, + m.fs.sweep_hot_exchanger, + m.fs.sweep_medium_exchanger, + m.fs.water_evaporator01, + m.fs.water_evaporator02, + m.fs.water_evaporator03, + m.fs.water_evaporator04, + m.fs.water_evaporator05, + m.fs.water_preheater, + ]: set_indexed_variable_bounds(hx.delta_temperature_in, (0, None)) set_indexed_variable_bounds(hx.delta_temperature_out, (0, None)) hx.area.bounds = (400, None) @@ -1905,7 +2142,9 @@ def set_indexed_variable_bounds(var, bounds): for cmp in [m.fs.sweep_blower, m.fs.cmp01, m.fs.cmp02, m.fs.cmp03, m.fs.cmp04]: set_indexed_variable_bounds(cmp.work_mechanical, (0, None)) - set_indexed_variable_bounds(m.fs.water_compressor.control_volume.work, (0, None)) + set_indexed_variable_bounds( + m.fs.water_compressor.control_volume.work, (0, None) + ) m.fs.h2_mass_production.fix(2) m.fs.water_preheater.tube_inlet.flow_mol.unfix() @@ -1921,7 +2160,9 @@ def set_indexed_variable_bounds(var, bounds): m.fs.soec_module.number_cells.unfix() m.fs.costing.electricity_price.fix(71.7) - set_indexed_variable_bounds(m.fs.soec_module.solid_oxide_cell.potential, (1.1, 1.6)) + set_indexed_variable_bounds( + m.fs.soec_module.solid_oxide_cell.potential, (1.1, 1.6) + ) set_indexed_variable_bounds(m.fs.water_split.split_fraction, (0.03, 0.98)) set_indexed_variable_bounds(m.fs.feed_heater.heat_duty, (0, None)) set_indexed_variable_bounds(m.fs.sweep_heater.heat_duty, (0, None)) @@ -1930,12 +2171,21 @@ def set_indexed_variable_bounds(var, bounds): m.fs.feed_recycle_split.split_fraction[0, "recycle"].bounds = (0.03, 0.5) m.fs.sweep_recycle_split.split_fraction[0, "recycle"].bounds = (0.03, 0.5) set_indexed_variable_bounds(m.fs.soec_overall_water_conversion, (0.4, 0.8)) - set_indexed_variable_bounds(m.fs.soec_module.solid_oxide_cell.temperature_z, (550 + 273.15, 750 + 273.15)) - set_indexed_variable_bounds(m.fs.soec_module.solid_oxide_cell.fuel_channel.temperature_inlet, - (600 + 273.15, 750 + 273.15)) - set_indexed_variable_bounds(m.fs.soec_module.solid_oxide_cell.oxygen_channel.temperature_inlet, - (600 + 273.15, 750 + 273.15)) - set_indexed_variable_bounds(m.fs.soec_module.solid_oxide_cell.current_density, (-10400, 5200)) + set_indexed_variable_bounds( + m.fs.soec_module.solid_oxide_cell.temperature_z, + (550 + 273.15, 750 + 273.15), + ) + set_indexed_variable_bounds( + m.fs.soec_module.solid_oxide_cell.fuel_channel.temperature_inlet, + (600 + 273.15, 750 + 273.15), + ) + set_indexed_variable_bounds( + m.fs.soec_module.solid_oxide_cell.oxygen_channel.temperature_inlet, + (600 + 273.15, 750 + 273.15), + ) + set_indexed_variable_bounds( + m.fs.soec_module.solid_oxide_cell.current_density, (-10400, 5200) + ) set_indexed_variable_bounds(m.fs.heat_source.inlet.flow_mol[0], (1, None)) m.fs.feed_heater.costing.max_heat_duty.set_value(8e6) @@ -1949,7 +2199,10 @@ def set_indexed_variable_bounds(var, bounds): set_indexed_variable_bounds(cmp.outlet.temperature, (273.15, 250 + 273.15)) m.fs._make_temperature_gradient_terms() - set_indexed_variable_bounds(m.fs.soec_module.solid_oxide_cell.fuel_electrode.dtemperature_dz, (-750, 750)) + set_indexed_variable_bounds( + m.fs.soec_module.solid_oxide_cell.fuel_electrode.dtemperature_dz, + (-750, 750), + ) m.fs.make_performance_constraints() # @m.fs.Constraint(m.fs.time) # def equal_pressures_eqn(b,t): @@ -1961,7 +2214,10 @@ def set_indexed_variable_bounds(var, bounds): # m.fs.average_current_density_constraint.deactivate() m.fs.h2_mass_production.fix(5) jac_unscaled, jac_scaled, nlp = iscale.constraint_autoscale_large_jac(m) - solver.solve(m, tee=True, options={"tol": 3e-8, "max_iter": 500, "halt_on_ampl_error": "yes"}) + solver.solve( + m, tee=True, options={"tol": 3e-8, "max_iter": 500, "halt_on_ampl_error": "yes"} + ) + def check_scaling(blk): jac, nlp = iscale.get_jacobian(blk, scaled=True) @@ -1970,18 +2226,16 @@ def check_scaling(blk): # for i in iscale.extreme_jacobian_entries(jac=jac, nlp=nlp, large=1E3, small=0): # print(f" {i[0]:.2e}, [{i[1]}, {i[2]}]") print("Badly scaled variables:") - for i in iscale.extreme_jacobian_columns( - jac=jac, nlp=nlp, large=1E3, small=5E-3): + for i in iscale.extreme_jacobian_columns(jac=jac, nlp=nlp, large=1e3, small=5e-3): print(f" {i[0]:.2e}, [{i[1]}]") print("\n\n" + "Badly scaled constraints:") - for i in iscale.extreme_jacobian_rows( - jac=jac, nlp=nlp, large=1E3, small=5E-3): + for i in iscale.extreme_jacobian_rows(jac=jac, nlp=nlp, large=1e3, small=5e-3): print(f" {i[0]:.2e}, [{i[1]}]") print(f"Jacobian Condition Number: {iscale.jacobian_cond(jac=jac):.2e}") if not hasattr(blk, "obj"): blk.obj = pyo.Objective(expr=0) - dh = DegeneracyHunter(blk, solver=pyo.SolverFactory('cbc')) + dh = DegeneracyHunter(blk, solver=pyo.SolverFactory("cbc")) dh.check_rank_equality_constraints(dense=True) variables = nlp.get_pyomo_variables() constraints = nlp.get_pyomo_equality_constraints() diff --git a/idaes_examples/archive/power_gen/soec/soec_flowsheet_costing.py b/idaes_examples/archive/power_gen/soec/soec_flowsheet_costing.py index c53f1884..79b6c4e6 100644 --- a/idaes_examples/archive/power_gen/soec/soec_flowsheet_costing.py +++ b/idaes_examples/archive/power_gen/soec/soec_flowsheet_costing.py @@ -17,7 +17,12 @@ from pyomo.environ import units as pyunits from pyomo.common.config import ConfigValue from pyomo.util.calc_var_value import calculate_variable_from_constraint -from idaes.core import UnitModelCostingBlock, UnitModelBlock, FlowsheetCostingBlockData, register_idaes_currency_units +from idaes.core import ( + UnitModelCostingBlock, + UnitModelBlock, + FlowsheetCostingBlockData, + register_idaes_currency_units, +) from idaes.core.util.constants import Constants import idaes.core.util.scaling as iscale from idaes.core.util.math import smooth_max @@ -35,7 +40,7 @@ HXType, VesselMaterial, BlowerMaterial, - CompressorType + CompressorType, ) ssf = iscale.set_scaling_factor @@ -169,6 +174,7 @@ def get_solo_soec_capital_costing(fs, CE_index_year): "material_type": VesselMaterial.LowAlloySteel, }, ) + # Heuristics for flash vessel taken from Analysis, Synthesis, and Design of Chemical Processes by Turton et al. # Fourth edition, copyright 2012. Page 344 @flash.Constraint() @@ -199,23 +205,19 @@ def capacity_heuristic(b): # Have to hack entries into the Helmholtz compressor config dict that the costing method expects to exist fs.water_compressor.config.declare( "thermodynamic_assumption", - ConfigValue( - default=ThermodynamicAssumption.isentropic - ), + ConfigValue(default=ThermodynamicAssumption.isentropic), ) fs.water_compressor.config.declare( "compressor", - ConfigValue( - default=True - ), + ConfigValue(default=True), ) fs.water_compressor.costing = UnitModelCostingBlock( - flowsheet_costing_block=fs.costing, - costing_method=SSLWCostingData.cost_compressor, - costing_method_arguments={ - "compressor_type": CompressorType.Screw, - }, - ) + flowsheet_costing_block=fs.costing, + costing_method=SSLWCostingData.cost_compressor, + costing_method_arguments={ + "compressor_type": CompressorType.Screw, + }, + ) add_total_plant_cost(fs.water_compressor) fs.cmp04.ersatz_costing = pyo.Block() @@ -223,19 +225,19 @@ def capacity_heuristic(b): initialize=1, bounds=(0, 1e4), doc="total plant cost in $MM" ) fs.cmp04.costing = UnitModelCostingBlock( - flowsheet_costing_block=fs.costing, - costing_method=SOFCPathwaysCostingData.cost_centrifugal_hydrogen_compressor, - costing_method_arguments={ - "CE_index_year": CE_index_year, - }, + flowsheet_costing_block=fs.costing, + costing_method=SOFCPathwaysCostingData.cost_centrifugal_hydrogen_compressor, + costing_method_arguments={ + "CE_index_year": CE_index_year, + }, ) fs.heat_pump.costing = UnitModelCostingBlock( - flowsheet_costing_block=fs.costing, - costing_method=SOFCPathwaysCostingData.cost_heat_pump, - costing_method_arguments={ - "CE_index_year": CE_index_year, - }, + flowsheet_costing_block=fs.costing, + costing_method=SOFCPathwaysCostingData.cost_heat_pump, + costing_method_arguments={ + "CE_index_year": CE_index_year, + }, ) ########################################################################## @@ -262,7 +264,7 @@ def feedwater_flow(b, t): "tech": 6, "ccs": "A", "CE_index_year": CE_index_year, - } + }, ) # accounts with raw water withdrawal as the process parameter water_withdrawal_accounts = ["3.2", "3.4", "9.5", "14.6"] @@ -290,16 +292,21 @@ def water_recycle(b, t): def raw_water_withdrawal(b, t): # Molar volume of water = 1.8068e-5 m^3/mol return pyo.units.convert( - 1.8068e-5 * pyo.units.m**3/pyo.units.mol + 1.8068e-5 + * pyo.units.m**3 + / pyo.units.mol * (b.water_demand[t] - b.water_recycle[t]), - pyo.units.gal/pyo.units.min + pyo.units.gal / pyo.units.min, ) - fs.max_raw_water_withdrawal = pyo.Var(units=pyo.units.gal/pyo.units.min, initialize=3000, bounds=(0, None)) + fs.max_raw_water_withdrawal = pyo.Var( + units=pyo.units.gal / pyo.units.min, initialize=3000, bounds=(0, None) + ) @fs.Constraint(fs.time) def max_raw_water_withdrawal_ineq(b, t): return b.raw_water_withdrawal[t] <= b.max_raw_water_withdrawal + @fs.Constraint(fs.time) def max_raw_water_withdrawal_eqn(b, t): return b.raw_water_withdrawal[t] == b.max_raw_water_withdrawal @@ -319,18 +326,20 @@ def max_raw_water_withdrawal_eqn(b, t): "tech": 6, "ccs": "A", "CE_index_year": CE_index_year, - } + }, ) # accounts with process water discharge as the process parameter water_discharge_accounts = ["3.7"] fs.discharge_system = UnitModelBlock() + @fs.Expression(fs.time) def process_water_discharge(b, t): return pyo.units.convert( fs.heat_source.control_volume.properties_out[0].flow_vol, - pyo.units.gal/pyo.units.min) + pyo.units.gal / pyo.units.min, + ) fs.discharge_system.costing = UnitModelCostingBlock( flowsheet_costing_block=fs.costing, @@ -341,7 +350,7 @@ def process_water_discharge(b, t): "tech": 6, "ccs": "A", "CE_index_year": CE_index_year, - } + }, ) # accounts with net power as the process parameter @@ -371,7 +380,7 @@ def process_water_discharge(b, t): "tech": 6, "ccs": "A", "CE_index_year": CE_index_year, - } + }, ) # costing for transformers @@ -387,7 +396,7 @@ def process_water_discharge(b, t): "tech": 6, "ccs": "A", "CE_index_year": CE_index_year, - } + }, ) # accounts with auxiliary load (excluding the SOEC) as the process parameter @@ -420,7 +429,7 @@ def aux_load(b, t): "tech": 6, "ccs": "A", "CE_index_year": CE_index_year, - } + }, ) # adding expressions to group Fossil Energy Baseline costs @@ -551,6 +560,7 @@ def cost_solid_oxide_cell(blk, CE_index_year="2018"): and 1000 mA/cm^2 in SOEC mode. """ CE_index_units = getattr(pyunits, "MUSD_" + CE_index_year) + @blk.Expression() def total_plant_cost(c): extra_installed_area = 1.10 # accounts for cell degradation @@ -567,10 +577,13 @@ def cost_cross_flow_heat_exchanger(blk, CE_index_year="2018"): Cross flow heat exchangers """ CE_index_units = getattr(pyunits, "MUSD_" + CE_index_year) + @blk.Expression() def total_plant_cost(b): return pyunits.convert( - 81.88 * pyunits.USD_2018 / pyo.units.ft**2 + 81.88 + * pyunits.USD_2018 + / pyo.units.ft**2 * pyo.units.convert(b.unit_model.area, pyo.units.ft**2), to_units=CE_index_units, ) @@ -581,7 +594,10 @@ def cost_trim_heater(blk, CE_index_year="2018"): """ CE_index_units = getattr(pyunits, "MUSD_" + CE_index_year) # blk.max_heat_duty = pyo.Param(initialize=8e6, mutable=True, units=pyo.units.W) - blk.unit_model.max_heat_duty = pyo.Var(initialize=9e6, bounds=(0, None), units=pyo.units.W) + blk.unit_model.max_heat_duty = pyo.Var( + initialize=9e6, bounds=(0, None), units=pyo.units.W + ) + @blk.Expression() def total_plant_cost(b): U = 56 * pyo.units.W / pyo.units.m**2 / pyo.units.K @@ -591,26 +607,35 @@ def total_plant_cost(b): # Add factor of two to pathways cost to account for corrosion-resistant materials for trim heaters return pyunits.convert( 2 - * 81.88 * pyunits.USD_2018 / pyo.units.ft**2 + * 81.88 + * pyunits.USD_2018 + / pyo.units.ft**2 * pyo.units.convert(area, pyo.units.ft**2), to_units=CE_index_units, ) + @blk.unit_model.Constraint(blk.flowsheet().time) def max_heat_duty_ineq(b, t): return b.heat_duty[t] <= b.max_heat_duty + @blk.unit_model.Constraint(blk.flowsheet().time) def max_heat_duty_eqn(b, t): return b.heat_duty[t] == b.max_heat_duty iscale.set_scaling_factor(blk.unit_model.max_heat_duty, 1e-6) for t in blk.flowsheet().time: - iscale.constraint_scaling_transform(blk.unit_model.max_heat_duty_ineq[t], 1e-6) - iscale.constraint_scaling_transform(blk.unit_model.max_heat_duty_eqn[t], 1e-6) + iscale.constraint_scaling_transform( + blk.unit_model.max_heat_duty_ineq[t], 1e-6 + ) + iscale.constraint_scaling_transform( + blk.unit_model.max_heat_duty_eqn[t], 1e-6 + ) blk.unit_model.max_heat_duty_ineq.deactivate() def cost_reciprocating_piston_hydrogen_compressor(blk, CE_index_year="2018"): # TODO determine if intercooling is taken into account CE_index_units = getattr(pyunits, "MUSD_" + CE_index_year) + @blk.Expression() def total_plant_cost(b): # This is for one stage. The original was for 2 hence the factor of 0.5 @@ -647,6 +672,7 @@ def cost_centrifugal_hydrogen_compressor(blk, CE_index_year="2018"): """ blk.number_units = pyo.Param(initialize=2, domain=pyo.Integers, mutable=True) CE_index_units = getattr(pyunits, "MUSD_" + CE_index_year) + @blk.Expression() def total_plant_cost(b): ref_cost = ( @@ -668,8 +694,7 @@ def total_plant_cost(b): pyo.units.kg / pyo.units.day, ) return pyunits.convert( - ref_cost * (process_param / ref_param) ** alpha, - to_units=CE_index_units + ref_cost * (process_param / ref_param) ** alpha, to_units=CE_index_units ) def cost_heat_pump(blk, CE_index_year="2018"): @@ -682,7 +707,10 @@ def cost_heat_pump(blk, CE_index_year="2018"): overestimating how much we need to pay for water so hopefully things will balance out """ CE_index_units = getattr(pyunits, "MUSD_" + CE_index_year) - blk.unit_model.max_heat_duty = pyo.Var(initialize=7e6, bounds=(0, None), units=pyo.units.W) + blk.unit_model.max_heat_duty = pyo.Var( + initialize=7e6, bounds=(0, None), units=pyo.units.W + ) + @blk.Expression() def total_plant_cost(b): equipment_cost = ( @@ -700,20 +728,25 @@ def total_plant_cost(b): ) alpha = 1 return pyunits.convert( - ref_cost * (process_param / ref_param) ** alpha, - to_units=CE_index_units + ref_cost * (process_param / ref_param) ** alpha, to_units=CE_index_units ) + @blk.unit_model.Constraint(blk.flowsheet().time) def max_heat_duty_ineq(b, t): return -b.heat_out[t] <= b.max_heat_duty + @blk.unit_model.Constraint(blk.flowsheet().time) def max_heat_duty_eqn(b, t): return -b.heat_out[t] == b.max_heat_duty iscale.set_scaling_factor(blk.unit_model.max_heat_duty, 1e-6) for t in blk.flowsheet().time: - iscale.constraint_scaling_transform(blk.unit_model.max_heat_duty_ineq[t], 1e-6) - iscale.constraint_scaling_transform(blk.unit_model.max_heat_duty_eqn[t], 1e-6) + iscale.constraint_scaling_transform( + blk.unit_model.max_heat_duty_ineq[t], 1e-6 + ) + iscale.constraint_scaling_transform( + blk.unit_model.max_heat_duty_eqn[t], 1e-6 + ) blk.unit_model.max_heat_duty_ineq.deactivate() @@ -723,7 +756,7 @@ def initialize_flowsheet_costing(fs): # fs.product_flash02, fs.product_flash03, fs.product_flash04, - fs.product_flash05 + fs.product_flash05, ] for flash in flash_vessels: flash.diameter.value = pyo.value( @@ -793,6 +826,7 @@ def scale_flowsheet_costing(fs): def get_soec_OM_costing(fs, CE_index_year="2018"): CE_index_units = getattr(pyunits, "MUSD_" + CE_index_year) + # fixed O&M costs @fs.costing.Expression() def annual_operating_labor_cost(b): @@ -803,7 +837,7 @@ def annual_operating_labor_cost(b): * 365.2425 # Average hours in Gregorian year * 1.3 # 30% Labor burden * pyunits.USD_2018, - to_units=CE_index_units + to_units=CE_index_units, ) @fs.costing.Expression() @@ -826,10 +860,9 @@ def property_tax_and_insurance_cost(b): def annual_soec_replacement_cost(b): return pyunits.convert( 4.2765 * fs.soec_module.number_cells * pyunits.USD_2018, - to_units=CE_index_units + to_units=CE_index_units, ) - @fs.costing.Expression() def annual_fixed_operations_and_maintenance_cost(b): return ( @@ -885,7 +918,7 @@ def annual_electricity_cost(b): fs.total_electric_power[0] * b.plant_uptime * pyo.units.convert(b.electricity_price, pyo.units.USD_2018 / pyo.units.J), - to_units=CE_index_units + to_units=CE_index_units, ) @fs.costing.Expression() @@ -897,16 +930,14 @@ def annual_water_cost(b): * pyo.units.kg / pyo.units.mol * (fs.water_demand[0] - fs.water_recycle[0]), - to_units=CE_index_units + to_units=CE_index_units, ) @fs.costing.Expression() def annual_air_cost(b): return pyunits.convert( - b.air_price - * b.plant_uptime - * fs.sweep_blower.inlet.flow_mol[0], - to_units=CE_index_units + b.air_price * b.plant_uptime * fs.sweep_blower.inlet.flow_mol[0], + to_units=CE_index_units, ) diff --git a/idaes_examples/archive/power_gen/soec/soec_standalone_template.svg b/idaes_examples/archive/power_gen/soec/soec_standalone_template.svg index 5eb48e41..23203ac6 100644 --- a/idaes_examples/archive/power_gen/soec/soec_standalone_template.svg +++ b/idaes_examples/archive/power_gen/soec/soec_standalone_template.svg @@ -3684,7 +3684,7 @@ x="11.245358" y="153.43445" style="text-align:end;text-anchor:end;stroke-width:0.264583" - id="tspan1177">Single-pass H2O coversion:Single-pass H2O conversion: