Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added bipolar membrane unit model and costing #1500

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2e5e903
Added bipolar membrane unit model and costing
johnson12742 Oct 2, 2024
1b65114
Minor corrections based on autocheck
johnson12742 Oct 2, 2024
3e85b9f
Minor corrections based on autocheck v1.1
johnson12742 Oct 2, 2024
1e3471d
Minor corrections based on autocheck v1.2
johnson12742 Oct 2, 2024
ecb5db1
Minor corrections based on autocheck v1.3
johnson12742 Oct 2, 2024
ff6a753
Minor corrections based on autocheck v1.4
johnson12742 Oct 3, 2024
a833442
Name change: Acidate/Basate to Acidic/Basic
johnson12742 Oct 10, 2024
2c2ca6d
Updates to (mainly) documentation based on first set of reviews. + Co…
johnson12742 Oct 30, 2024
ecaeaa0
Updates to (mainly) documentation based on first set of reviews. + Co…
johnson12742 Oct 30, 2024
895599a
Updates to (mainly) documentation based on first set of reviews. + Co…
johnson12742 Oct 31, 2024
a56bb73
Updates to (mainly) documentation based on first set of reviews. + Co…
johnson12742 Oct 31, 2024
981da41
Updates to (mainly) documentation based on second set of reviews. Cle…
johnson12742 Nov 2, 2024
c09399b
Updates to (mainly) documentation based on second set of reviews. Cle…
johnson12742 Nov 2, 2024
8b043e3
Merge branch 'main' into biploar_membrane
lbianchi-lbl Nov 5, 2024
d0b0449
Change pytest to check for numerical accuracy instead of directly aga…
johnson12742 Nov 9, 2024
5b60c59
Merge remote-tracking branch 'origin/biploar_membrane' into biploar_m…
johnson12742 Nov 9, 2024
7bed754
fix typos
johnson12742 Nov 12, 2024
0e7c3ae
fix typos
johnson12742 Nov 12, 2024
e4ad509
Corrections to the documentation
johnson12742 Nov 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/_static/unit_models/BPEDdiagram.png
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the figure and its surrounding text: I suspect you are only showing a segment of a more complete picture- should there be regular aem on the left side and cem on the right side? So that'll give another two channels into which NaCl is fed? The current picture indicates the charge isn't balanced . It's likely you are not modeling a cell involving a NaCl solution feed- in either case, the current picture and the text aren't clear about what is being simulated. You may refer to Fig 2 in "X. Tongwen / Resources, ConserTation and Recycling 37 (2002) 1–22" - I suppose you are modeling of one of the two scenarios in this figure.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an important point/distinction. The bipolar membrane by itself cannot support production of acids and bases. It needs AEM and CEM in parallel. I have now made it explicitly clear in the documentation that these features are not in this unit model.
I do have this unit mode (that has BPEM + ED). It needs to be cleaned up before I push it (and all the results I have shown in meetings use the BPEM + ED unit model).

My reasoning to push a standalone bipolar membrane unit model is if someone in the future needs just the bipolar part. I am open to counter arguments.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not commenting on which basic unit should be modeled, but I am unclear about the focus of the current version due to several issues:

  1. You stated that this model allows "direct comparisons with works by Mareev et al. (2020) and Melnikov (2022)," which primarily develop Nernst-Planck (N-P) models for simulating potential profiles across bipolar membranes (BPM). However, this version does not establish such a simulation, and the basis for direct comparison is unclear. The test file also references data from a third work-work-Wilhelm et al. (2002)- and the cited data is used as the model input rather than output verification.
  2. The inclusion or absence of adjacent AEM or CEM layers next to the BPM significantly impacts the model. Melnikov (2022) does not consider such adjacent layers, leading to salt ion transport characteristics (e.g., K⁺ moving in the same direction as OH⁻) that differ from scenarios with adjacent AEM/CEM layers, where counter-ion transport would be supplemented from the other side facing the BPM. Mareev et al. (2020), as well as your diagram, suggest the presence of adjacent layers, as salt counter-ions are supplemented from the opposite side of the BPM. Also, clarifying the charge identity of the BPM layers could be very helpful.
  3. Questions about mass balance for all components remain unanswered and are essential for understanding the model. If the goal is not solving N-P PDEs to establish electric field profiles but rather to account for mass transfer and balances, clear definitions of reaction terms are crucial.

Overall, I recommend clarifying the electrochemical cell scheme and explicitly defining the scope of the model. Additionally, validation against literature data in the test file would strengthen the results.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
364 changes: 364 additions & 0 deletions docs/technical_reference/unit_models/bipolar_electrodialysis_0D.rst

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/technical_reference/unit_models/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Unit Models

anaerobic_digester
aeration_tank
bipolar_electrodialysis_0D
boron_removal
clarifier
coag_floc_model
Expand Down
151 changes: 151 additions & 0 deletions watertap/costing/unit_models/bipolar_electrodialysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#################################################################################
# WaterTAP Copyright (c) 2020-2024, The Regents of the University of California,
# through Lawrence Berkeley National Laboratory, Oak Ridge National Laboratory,
# National Renewable Energy Laboratory, and National Energy Technology
# Laboratory (subject to receipt of any required approvals from the U.S. Dept.
# of Energy). All rights reserved.
#
# Please see the files COPYRIGHT.md and LICENSE.md for full copyright and license
# information, respectively. These files are also available online at the URL
# "https://github.com/watertap-org/watertap/"
#################################################################################

import pyomo.environ as pyo
from watertap.costing.util import (
register_costing_parameter_block,
cost_rectifier,
make_capital_cost_var,
make_fixed_operating_cost_var,
)


def build_bipolar_electrodialysis_cost_param_block(blk):
# The following costing itemization and values are referenced to "Desalination 452 (2019) 265–278"
blk.membrane_capital_cost = pyo.Var(
initialize=160,
doc="Membrane and capital costs in [US$/m^2-membrane-area]",
units=pyo.units.USD_2018 / (pyo.units.meter**2),
)

blk.factor_membrane_replacement = pyo.Var(
initialize=0.2,
doc="Membrane and equipment (other stack components) housing replacement factor, equal to 1/lifetime.",
units=pyo.units.year**-1,
)

blk.stack_electrode_capital_cost = pyo.Var(
initialize=2100,
doc="Electrode cost in [US$/m^2-electrode-area] ",
units=pyo.units.USD_2018 / (pyo.units.meter**2),
)

blk.factor_stack_electrode_replacement = pyo.Var(
initialize=0.2,
doc="Stack and electrode replacement factor, equal to 1/lifetime.",
units=pyo.units.year**-1,
)


@register_costing_parameter_block(
build_rule=build_bipolar_electrodialysis_cost_param_block,
parameter_block_name="bipolar_electrodialysis",
)
def cost_bipolar_electrodialysis(
blk,
cost_electricity_flow=True,
has_rectifier=False,
):
"""
Function for costing the bipolar electrodialysis unit

Args:
cost_electricity_flow (:obj:`bool`, optional): Option for including the
costing of electricity. Defaults to True.
has_rectifier (:obj:`bool`, optional): Option for including a rectifier.
Defaults to False.
"""
t0 = blk.flowsheet().time.first()

# Changed this to grab power from performance table which is identified
# by same key regardless of whether the Electrodialysis unit is 0D or 1D
if cost_electricity_flow:
if not has_rectifier:
blk.costing_package.cost_flow(
pyo.units.convert(
blk.unit_model.get_power_electrical(t0),
to_units=pyo.units.kW,
),
"electricity",
)
else:
power = blk.unit_model.get_power_electrical(blk.flowsheet().time.first())
cost_rectifier(blk, power=power, ac_dc_conversion_efficiency=0.9)

cost_bipolar_electrodialysis_stack(blk)


def cost_bipolar_electrodialysis_stack(blk):
"""
Generic function for costing the stack in an electrodialysis unit.
Assumes the unit_model has a `cell_num`, `cell_width`, and `cell_length`
set of variables used to size the total membrane area.

"""
make_capital_cost_var(blk)
make_fixed_operating_cost_var(blk)
blk.costing_package.add_cost_factor(blk, "TIC")
if blk.find_component("capital_cost_rectifier") is not None:
blk.capital_cost_constraint = pyo.Constraint(
expr=blk.capital_cost
== blk.cost_factor
* (
pyo.units.convert(
blk.costing_package.bipolar_electrodialysis.membrane_capital_cost
* (
2
* blk.unit_model.cell_num
* blk.unit_model.cell_width
* blk.unit_model.cell_length
)
+ blk.costing_package.bipolar_electrodialysis.stack_electrode_capital_cost
* (2 * blk.unit_model.cell_width * blk.unit_model.cell_length),
to_units=blk.costing_package.base_currency,
)
+ blk.capital_cost_rectifier
)
)
else:
blk.capital_cost_constraint = pyo.Constraint(
expr=blk.capital_cost
== blk.cost_factor
* pyo.units.convert(
blk.costing_package.bipolar_electrodialysis.membrane_capital_cost
* (
2
* blk.unit_model.cell_num
* blk.unit_model.cell_width
* blk.unit_model.cell_length
)
+ blk.costing_package.bipolar_electrodialysis.stack_electrode_capital_cost
* (2 * blk.unit_model.cell_width * blk.unit_model.cell_length),
to_units=blk.costing_package.base_currency,
)
)
blk.fixed_operating_cost_constraint = pyo.Constraint(
expr=blk.fixed_operating_cost
== pyo.units.convert(
blk.costing_package.bipolar_electrodialysis.factor_membrane_replacement
* blk.costing_package.bipolar_electrodialysis.membrane_capital_cost
* (
2
* blk.unit_model.cell_num
* blk.unit_model.cell_width
* blk.unit_model.cell_length
)
+ blk.costing_package.bipolar_electrodialysis.factor_stack_electrode_replacement
* blk.costing_package.bipolar_electrodialysis.stack_electrode_capital_cost
* (2 * blk.unit_model.cell_width * blk.unit_model.cell_length),
to_units=blk.costing_package.base_currency
/ blk.costing_package.base_period,
)
)
Loading
Loading