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

add steam heater and condenser #1358

Merged
merged 31 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
fcdd657
add steam heater
ElmiraShamlou Apr 12, 2024
ada04b6
modification
ElmiraShamlou Apr 12, 2024
673ebb8
add steam flow cost to heat exchanger costing method
ElmiraShamlou Apr 15, 2024
41a2518
renames files, updates tests
ElmiraShamlou Apr 16, 2024
5a40980
removes files with old names
ElmiraShamlou Apr 16, 2024
e503723
Merge branch 'main' into steam-heater
ElmiraShamlou Apr 16, 2024
f102095
add docs, revise initialization, revise tests
ElmiraShamlou Apr 16, 2024
c2caa2e
Merge branch 'steam-heater' of https://github.com/ElmiraShamlou/water…
ElmiraShamlou Apr 16, 2024
1a6bba6
add config option for outlet saturation pressure
ElmiraShamlou Apr 16, 2024
515a540
modifies config options
ElmiraShamlou Apr 16, 2024
2928af4
code linting
ElmiraShamlou Apr 16, 2024
8ae8fe9
update test
ElmiraShamlou Apr 16, 2024
2d97f6a
update tests
ElmiraShamlou Apr 16, 2024
ca962d8
add iter limit
ElmiraShamlou Apr 17, 2024
36e4a1f
applies unit test harness
ElmiraShamlou Apr 17, 2024
62ef175
fixes pressure
ElmiraShamlou Apr 18, 2024
96347b9
Merge branch 'main' into steam-heater
ElmiraShamlou Apr 22, 2024
8a56c8a
watertap get_solver
ElmiraShamlou Apr 22, 2024
2d6d274
Merge branch 'main' into steam-heater
adam-a-a May 15, 2024
5c42ba6
Merge branch 'main' into steam-heater
ElmiraShamlou Jun 6, 2024
6c26717
Merge branch 'main' into steam-heater
ElmiraShamlou Jun 25, 2024
97ea972
update model to include condenser
ElmiraShamlou Jun 27, 2024
9f057b5
add test for the condenser cooling water flow estimation
ElmiraShamlou Jul 1, 2024
9e0390d
Merge branch 'main' into steam-heater
ElmiraShamlou Jul 1, 2024
6608eeb
add conservation check
ElmiraShamlou Jul 1, 2024
a7c890b
add steam cost to the hx doc
ElmiraShamlou Jul 1, 2024
d075185
add steam cost to hx doc
ElmiraShamlou Jul 1, 2024
ca9c656
Merge branch 'main' into steam-heater
ElmiraShamlou Jul 1, 2024
bf9c705
Merge branch 'main' into steam-heater
ElmiraShamlou Jul 1, 2024
1bbde8c
Merge branch 'main' into steam-heater
ElmiraShamlou Jul 2, 2024
b63d120
modifies hx doc and test
ElmiraShamlou Jul 2, 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
19 changes: 18 additions & 1 deletion watertap/costing/unit_models/heat_exchanger.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,20 @@ def build_heat_exchanger_cost_param_block(blk):
units=pyo.units.dimensionless,
)

blk.steam_cost = pyo.Var(
initialize=0.008,
units=pyo.units.USD_2018 / (pyo.units.kg),
doc="steam cost per kg",
)
MarcusHolly marked this conversation as resolved.
Show resolved Hide resolved

blk.parent_block().register_flow_type("steam", blk.steam_cost)


@register_costing_parameter_block(
build_rule=build_heat_exchanger_cost_param_block,
parameter_block_name="heat_exchanger",
)
def cost_heat_exchanger(blk):
def cost_heat_exchanger(blk, cost_steam_flow=False):
"""
Heat Exchanger Costing Method
Expand All @@ -54,3 +62,12 @@ def cost_heat_exchanger(blk):
to_units=blk.costing_package.base_currency,
)
)

if cost_steam_flow:
blk.costing_package.cost_flow(
pyo.units.convert(
(blk.unit_model.hot_side_inlet.flow_mass_phase_comp[0, "Vap", "H2O"]),
to_units=pyo.units.kg / pyo.units.s,
),
"steam",
)
116 changes: 116 additions & 0 deletions watertap/unit_models/steam_heater.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#################################################################################
# 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/"
#################################################################################

from pyomo.environ import Var

from idaes.core import (
declare_process_block_class,
)
from idaes.models.unit_models.heat_exchanger import HeatExchangerData
from idaes.core.solvers import get_solver
from idaes.core.util.model_statistics import degrees_of_freedom
import idaes.logger as idaeslog


_log = idaeslog.getLogger(__name__)


__author__ = "Elmira Shamlou"

ElmiraShamlou marked this conversation as resolved.
Show resolved Hide resolved

@declare_process_block_class(
"FWHCondensing0D",
doc="""Feedwater Heater Condensing Section
The feedwater heater condensing section model is a normal 0D heat exchanger
model with an added constraint to calculate the steam flow such that the outlet
of shell is a saturated liquid.""",
)
class FWHCondensing0DData(HeatExchangerData):
ElmiraShamlou marked this conversation as resolved.
Show resolved Hide resolved
config = HeatExchangerData.CONFIG()

def build(self):
super().build()

@self.Constraint(
self.flowsheet().time,
self.hot_side.config.property_package.component_list,
doc="Mass balance",
)
def outlet_liquid_mass_balance(b, t, j):
b.hot_side.properties_out[t].flow_mass_phase_comp["Vap", j].fix(0)
return (
b.hot_side.properties_in[t].flow_mass_phase_comp["Vap", j]
+ b.hot_side.properties_in[t].flow_mass_phase_comp["Liq", j]
== b.hot_side.properties_out[t].flow_mass_phase_comp["Liq", j]
)

units_meta = (
self.config.hot_side.property_package.get_metadata().get_derived_units
)

self.pressure_diff = Var(
ElmiraShamlou marked this conversation as resolved.
Show resolved Hide resolved
self.flowsheet().time, initialize=0, units=units_meta("pressure")
)

self.pressure_diff.fix()
ElmiraShamlou marked this conversation as resolved.
Show resolved Hide resolved

@self.Constraint(self.flowsheet().time, doc="Saturation pressure constraint")
def outlet_pressure_sat(b, t):
return (
b.hot_side.properties_out[t].pressure + b.pressure_diff[t]
== b.hot_side.properties_out[t].pressure_sat
)

def initialize_build(self, *args, **kwargs):
"""
Use the regular heat exchanger initialization, with the extraction rate
constraint deactivated; then it activates the constraint and calculates
ElmiraShamlou marked this conversation as resolved.
Show resolved Hide resolved
a steam inlet flow rate.
"""
solver = kwargs.get("solver", None)
optarg = kwargs.get("oparg", {})
outlvl = kwargs.get("outlvl", idaeslog.NOTSET)
init_log = idaeslog.getInitLogger(self.name, outlvl, tag="unit")
solve_log = idaeslog.getSolveLogger(self.name, outlvl, tag="unit")

self.outlet_liquid_mass_balance.deactivate()
self.outlet_pressure_sat.deactivate()
self.hot_side_inlet.fix()
self.cold_side_inlet.fix()
self.hot_side_outlet.unfix()
self.cold_side_outlet.unfix()
self.cold_side_outlet.flow_mass_phase_comp[0, "Vap", "H2O"].fix()

# Do regular heat exchanger initialization
super().initialize_build(*args, **kwargs)
self.outlet_liquid_mass_balance.activate()
self.outlet_pressure_sat.activate()

for j in self.hot_side.config.property_package.component_list:
self.hot_side_inlet.flow_mass_phase_comp[0, "Vap", j].unfix()

if degrees_of_freedom(self) != 0:
raise Exception(
f"{self.name} degrees of freedom were not 0 at the beginning "
f"of initialization. DoF = {degrees_of_freedom(self)}"
)

# Create solver
opt = get_solver(solver, optarg)

with idaeslog.solver_log(solve_log, idaeslog.DEBUG) as slc:
res = opt.solve(self, tee=slc.tee)
init_log.info(
"Initialization Complete (w/ extraction calc): {}".format(
idaeslog.condition(res)
)
)
75 changes: 75 additions & 0 deletions watertap/unit_models/tests/test_steam_heater.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#################################################################################
# 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 pytest
from pyomo.environ import (
ConcreteModel,
value,
)

from idaes.core import FlowsheetBlock
import watertap.property_models.water_prop_pack as props_w
from watertap.unit_models.steam_heater import FWHCondensing0D
from idaes.core.util.model_statistics import degrees_of_freedom
from idaes.core.solvers import get_solver
from watertap.unit_models.mvc.components.lmtd_chen_callback import (
delta_temperature_chen_callback,
)
from idaes.models.unit_models.heat_exchanger import (
HeatExchangerFlowPattern,
)

# Set up solver
solver = get_solver()


@pytest.mark.unit
def test_unit_model():
m = ConcreteModel()
m.fs = FlowsheetBlock(dynamic=False)
m.fs.default_property_package = props_w.WaterParameterBlock()

m.fs.unit = FWHCondensing0D(
hot_side_name="hot",
cold_side_name="cold",
hot={
"property_package": m.fs.default_property_package,
"has_pressure_change": False,
},
cold={
"property_package": m.fs.default_property_package,
"has_pressure_change": False,
},
delta_temperature_callback=delta_temperature_chen_callback,
flow_pattern=HeatExchangerFlowPattern.countercurrent,
)

m.fs.unit.hot_side_inlet.flow_mass_phase_comp[0, "Vap", "H2O"].set_value(6)
m.fs.unit.hot_side_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix(0)
m.fs.unit.hot_side_inlet.temperature.fix(273.15 + 140)
m.fs.unit.cold_side_inlet.pressure.fix(101325)
m.fs.unit.cold_side_inlet.temperature.fix(273.15 + 70)
m.fs.unit.cold_side_inlet.flow_mass_phase_comp[0, "Vap", "H2O"].fix(0)
m.fs.unit.cold_side_outlet.flow_mass_phase_comp[0, "Vap", "H2O"].fix(0)
adam-a-a marked this conversation as resolved.
Show resolved Hide resolved
m.fs.unit.cold_side_inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix(6)
m.fs.unit.area.fix(50)
m.fs.unit.overall_heat_transfer_coefficient.fix(2e3)

assert degrees_of_freedom(m) == 0

m.fs.unit.initialize()

# assert degrees_of_freedom(m) == 0
ElmiraShamlou marked this conversation as resolved.
Show resolved Hide resolved

assert pytest.approx(0.687041189050, rel=1e-5) == value(
m.fs.unit.hot_side_inlet.flow_mass_phase_comp[0, "Vap", "H2O"]
)
Loading