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

Multi-effect crystallizer unit model #121

Merged
merged 92 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
92 commits
Select commit Hold shift + click to select a range
4510b03
save progress
Zhuoran29 Jul 7, 2024
bdd26f5
Merge branch 'main' into crystallizers
kurbansitterley Jul 24, 2024
a468289
run black
Zhuoran29 Jul 24, 2024
de615d7
merge
Zhuoran29 Jul 24, 2024
3084a49
Merge branch 'main' into crystallizers
kurbansitterley Aug 2, 2024
bfb53e6
modify costing
Zhuoran29 Aug 13, 2024
de48abd
Merge branch 'main' of https://github.com/Zhuoran29/watertap-reflo in…
Zhuoran29 Aug 13, 2024
a3195d3
Merge branch 'crystallizers' of https://github.com/Zhuoran29/watertap…
Zhuoran29 Aug 13, 2024
43a3039
change prop package path to reflo
Zhuoran29 Aug 13, 2024
f0a79b6
Merge branch 'main' into crystallizers
kurbansitterley Sep 11, 2024
901aa66
new black
kurbansitterley Sep 11, 2024
7c62034
clean up imports; spelling
kurbansitterley Sep 11, 2024
fe63f64
try to use WaterTAP cryst_prop_pack
kurbansitterley Sep 11, 2024
6179f4a
black
kurbansitterley Sep 11, 2024
d4b39e7
try watertap==1.0.0rc0
kurbansitterley Sep 12, 2024
0a4ffdd
initial crystallizer_effect model
kurbansitterley Sep 12, 2024
e99d461
add steam props
kurbansitterley Sep 12, 2024
da58c8d
clean up; add pressure balance constraints
kurbansitterley Sep 12, 2024
4b7e386
add heat transfer equation
kurbansitterley Sep 12, 2024
8a3ee8b
initial muli_effect_crystallizer model
kurbansitterley Sep 13, 2024
94c738f
black
kurbansitterley Sep 13, 2024
912ddac
add standalone config option and rearrange constraints, remove ports;…
kurbansitterley Sep 13, 2024
6d4a1f7
remove ports from non-standalone cryst effect model, add ports to fir…
kurbansitterley Sep 13, 2024
e18e89b
only make heating steam in standalone
kurbansitterley Sep 13, 2024
2a5a38f
only make heating steam in first effect; checkpoint
kurbansitterley Sep 13, 2024
33e1789
solving with 0 DOF
kurbansitterley Sep 13, 2024
3ebfdb9
initial initialization(); clean up
kurbansitterley Sep 13, 2024
5aae54f
initial costing package
kurbansitterley Sep 14, 2024
2b237e9
MEC costing pkg checkpoint
kurbansitterley Sep 14, 2024
352dde3
add params for costing to effect unit model
kurbansitterley Sep 14, 2024
c74c94a
add total_flow_vol expr; costing attempt
kurbansitterley Sep 14, 2024
409bd16
area -> heat_exchanger_area
kurbansitterley Sep 17, 2024
4e84488
area -> heat_exchanger_area
kurbansitterley Sep 17, 2024
8e40d10
finish changing area var name
kurbansitterley Sep 17, 2024
0a490b1
working initialize_build routine
kurbansitterley Sep 17, 2024
c34d9f3
black
kurbansitterley Sep 17, 2024
135cf9d
costing by volume working; use heating_steam for steam spec energy; c…
kurbansitterley Sep 18, 2024
feb76e3
checkpoint
kurbansitterley Sep 18, 2024
be92ded
clean up imports; add init_args
kurbansitterley Sep 18, 2024
a05cd22
clean up
kurbansitterley Sep 18, 2024
6d28dda
clean up
kurbansitterley Sep 20, 2024
f38d5f8
add test file for multi effect crystallizer
kurbansitterley Sep 20, 2024
3d1fbb7
change units for consistency; clean up
kurbansitterley Sep 20, 2024
b4ce29b
fix for units consistency; clean up
kurbansitterley Sep 20, 2024
bdc74cc
move test file out of ZO folder
kurbansitterley Sep 20, 2024
828fea8
comment test file before removal
kurbansitterley Sep 20, 2024
58ff838
comment MEC fs test before removal
kurbansitterley Sep 20, 2024
8d6b9fe
remove multi effect crystallizer flowsheet and test file
kurbansitterley Sep 25, 2024
a2d4a56
remove cryst prop pack and test
kurbansitterley Sep 25, 2024
3e93607
remove watertap crystallizer and test
kurbansitterley Sep 25, 2024
4dd9b04
remove __main__ func
kurbansitterley Sep 25, 2024
23cbb63
remove __main__ func
kurbansitterley Sep 25, 2024
045b9ab
update initial value and scaling factor for overall_heat_transfer_coeff
kurbansitterley Sep 25, 2024
bb7b679
revert overall_heat_transfer_coefficient sf
kurbansitterley Sep 25, 2024
8c99484
modify to accomodate costing CrystallizerEffect and MulitEffectCrysta…
kurbansitterley Sep 26, 2024
27402c1
add costing method; clean up imports
kurbansitterley Sep 26, 2024
771ea24
add to test for indivdiual effect component capex
kurbansitterley Sep 26, 2024
93ff68c
add constraint for heating steam flow rate
kurbansitterley Sep 26, 2024
97460b4
cost unit not effect
kurbansitterley Sep 26, 2024
75dea2c
add test file for crystallizer_effect
kurbansitterley Sep 26, 2024
c5c3a85
black
kurbansitterley Sep 26, 2024
00124ec
clean up; black!
kurbansitterley Sep 26, 2024
6fe16b9
remove steam prop calculation func from costing pkg
kurbansitterley Sep 27, 2024
4dc24e9
update test files for new model run approach
kurbansitterley Sep 27, 2024
b137a9f
black!
kurbansitterley Sep 27, 2024
1cfc2d2
remove watertap crystallizer costing pkg
kurbansitterley Sep 27, 2024
d71d7f9
remove crystallizer_watertap from init
kurbansitterley Sep 27, 2024
184f4a8
blackblackblackblackblackblackblackblackblackblackblackblackblackblac…
kurbansitterley Sep 27, 2024
78108aa
fix test for windows
kurbansitterley Sep 27, 2024
bd2f3e9
Merge branch 'main' into crystallizers
kurbansitterley Sep 27, 2024
1fe1730
Merge branch 'main' into crystallizers
kurbansitterley Oct 7, 2024
8c83a56
remove commented code
kurbansitterley Oct 7, 2024
b7c8af5
Merge branch 'main' into crystallizers
kurbansitterley Oct 8, 2024
b166843
prop_pure_water vapor flow = 0, liq flow = prop_vapor vapor flow
kurbansitterley Oct 8, 2024
651dba5
add 0D control volume to multi_effect_crystallizer
kurbansitterley Oct 8, 2024
e7ba576
Merge branch 'crystallizers' of https://github.com/zhuoran29/watertap…
kurbansitterley Oct 8, 2024
a4976f2
improve initialization routine
kurbansitterley Oct 8, 2024
e6e4c52
add CV temperature and pressure at outlet
kurbansitterley Oct 8, 2024
189a42c
add overall recovery vol phase
kurbansitterley Oct 8, 2024
4d2bb63
update test to reflect unit model changes
kurbansitterley Oct 8, 2024
34ebf58
add test for 2 effects
kurbansitterley Oct 9, 2024
a84ceea
add ConfigurationError for number_effects=1
kurbansitterley Oct 9, 2024
e0b5b44
add test for single effect to multi effect crystallizer
kurbansitterley Oct 9, 2024
0bdc651
blackification
kurbansitterley Oct 9, 2024
e0743db
remove pure_water port
kurbansitterley Oct 9, 2024
5330387
change number_unused_variables test
kurbansitterley Oct 9, 2024
c383d97
remove commented code
kurbansitterley Oct 9, 2024
8fb814b
black
kurbansitterley Oct 9, 2024
1c72704
Merge branch 'main' into crystallizers
kurbansitterley Oct 9, 2024
672d479
add optimization
Zhuoran29 Oct 10, 2024
4e49490
Merge branch 'main' into crystallizers
kurbansitterley Oct 10, 2024
2ce649e
remove duplicate optimization test; clarify conditions; add opt conse…
kurbansitterley Oct 10, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,381 @@
import pandas as pd
import numpy as np
import pytest
from pyomo.environ import (
ConcreteModel,
TerminationCondition,
SolverStatus,
Objective,
Expression,
maximize,
value,
Set,
Var,
log,
units as pyunits,
)
from pyomo.network import Port
from idaes.core import FlowsheetBlock
import idaes.core.util.scaling as iscale
from pyomo.util.check_units import assert_units_consistent
from watertap_contrib.reflo.unit_models.zero_order.crystallizer_zo_watertap import (
Crystallization,
)
import watertap_contrib.reflo.property_models.cryst_prop_pack as props
from watertap.unit_models.mvc.components.lmtd_chen_callback import (
delta_temperature_chen_callback,
)
from idaes.core.solvers import get_solver
from idaes.core.util.model_statistics import (
degrees_of_freedom,
number_variables,
number_total_constraints,
number_unused_variables,
)
from idaes.models.unit_models import HeatExchanger
from idaes.models.unit_models.heat_exchanger import (
delta_temperature_lmtd_callback,
delta_temperature_underwood_callback,
)
from idaes.core.util.testing import initialization_tester
from idaes.core.util.scaling import (
calculate_scaling_factors,
unscaled_variables_generator,
badly_scaled_var_generator,
)
from idaes.core import UnitModelCostingBlock

from watertap.costing import WaterTAPCosting, CrystallizerCostType

solver = get_solver()


def build_fs_multi_effect_crystallizer(
m=None,
# num_effect=3,
operating_pressure_eff1=0.78, # bar
operating_pressure_eff2=0.25, # bar
operating_pressure_eff3=0.208, # bar
operating_pressure_eff4=0.095, # bar
feed_flow_mass=1, # kg/s
feed_mass_frac_NaCl=0.3,
feed_pressure=101325, # Pa
feed_temperature=273.15 + 20, # K
crystallizer_yield=0.5,
steam_pressure=1.5, # bar (gauge pressure)
):
"""
This flowsheet depicts a 4-effect crystallizer, with brine fed in parallel
to each effect, and the operating pressure is specfied individually.
"""

if m is None:
m = ConcreteModel()

mfs = m.fs = FlowsheetBlock(dynamic=False)
m.fs.props = props.NaClParameterBlock()

# Create 4 effects of crystallizer
eff_1 = m.fs.eff_1 = Crystallization(property_package=m.fs.props)
eff_2 = m.fs.eff_2 = Crystallization(property_package=m.fs.props)
eff_3 = m.fs.eff_3 = Crystallization(property_package=m.fs.props)
eff_4 = m.fs.eff_4 = Crystallization(property_package=m.fs.props)

feed_mass_frac_H2O = 1 - feed_mass_frac_NaCl
eps = 1e-6

eff_1.inlet.flow_mass_phase_comp[0, "Liq", "NaCl"].fix(
feed_flow_mass * feed_mass_frac_NaCl
)
eff_1.inlet.flow_mass_phase_comp[0, "Liq", "H2O"].fix(
feed_flow_mass * feed_mass_frac_H2O
)

for eff in [eff_1, eff_2, eff_3, eff_4]:
# Define feed for all effects
eff.inlet.flow_mass_phase_comp[0, "Sol", "NaCl"].fix(eps)
eff.inlet.flow_mass_phase_comp[0, "Vap", "H2O"].fix(eps)

eff.inlet.pressure[0].fix(feed_pressure)
eff.inlet.temperature[0].fix(feed_temperature)

# Fix growth rate, crystal length and Sounders brown constant to default values
eff.crystal_growth_rate.fix()
eff.souders_brown_constant.fix()
eff.crystal_median_length.fix()

# Fix yield
eff.crystallization_yield["NaCl"].fix(crystallizer_yield)

# Define operating conditions
m.fs.eff_1.pressure_operating.fix(operating_pressure_eff1 * pyunits.bar)
m.fs.eff_2.pressure_operating.fix(operating_pressure_eff2 * pyunits.bar)
m.fs.eff_3.pressure_operating.fix(operating_pressure_eff3 * pyunits.bar)
m.fs.eff_4.pressure_operating.fix(operating_pressure_eff4 * pyunits.bar)

add_heat_exchanger_eff2(m)
add_heat_exchanger_eff3(m)
add_heat_exchanger_eff4(m)
return m


def add_heat_exchanger_eff2(m):
eff_1 = m.fs.eff_1
eff_2 = m.fs.eff_2

eff_2.delta_temperature_in = Var(
eff_2.flowsheet().time,
initialize=35,
bounds=(None, None),
units=pyunits.K,
doc="Temperature differnce at the inlet side",
)
eff_2.delta_temperature_out = Var(
eff_2.flowsheet().time,
initialize=35,
bounds=(None, None),
units=pyunits.K,
doc="Temperature differnce at the outlet side",
)
delta_temperature_chen_callback(eff_2)

eff_2.area = Var(
bounds=(0, None),
initialize=1000.0,
doc="Heat exchange area",
units=pyunits.m**2,
)

eff_2.overall_heat_transfer_coefficient = Var(
eff_2.flowsheet().time,
bounds=(0, None),
initialize=100.0,
doc="Overall heat transfer coefficient",
units=pyunits.W / pyunits.m**2 / pyunits.K,
)

eff_2.overall_heat_transfer_coefficient[0].fix(100)

@m.Constraint(eff_2.flowsheet().time, doc="delta_temperature_in at the 2nd effect")
def delta_temperature_in_eff2(b, t):
return (
b.fs.eff_2.delta_temperature_in[t]
== b.fs.eff_1.properties_vapor[0].temperature
- b.fs.eff_2.temperature_operating
)

@m.Constraint(eff_2.flowsheet().time, doc="delta_temperature_out at the 2nd effect")
def delta_temperature_out_eff2(b, t):
return (
b.fs.eff_2.delta_temperature_out[t]
== b.fs.eff_1.properties_pure_water[0].temperature
- b.fs.eff_2.properties_in[0].temperature
)

@m.Constraint(eff_2.flowsheet().time)
def heat_transfer_equation_eff_2(b, t):
return b.fs.eff_1.energy_flow_superheated_vapor == (
b.fs.eff_2.overall_heat_transfer_coefficient[t]
* b.fs.eff_2.area
* b.fs.eff_2.delta_temperature[0]
)

iscale.set_scaling_factor(eff_2.delta_temperature_in, 1e-1)
iscale.set_scaling_factor(eff_2.delta_temperature_out, 1e-1)
iscale.set_scaling_factor(eff_2.area, 1e-1)
iscale.set_scaling_factor(eff_2.overall_heat_transfer_coefficient, 1e-1)


def add_heat_exchanger_eff3(m):
eff_2 = m.fs.eff_2
eff_3 = m.fs.eff_3

eff_3.delta_temperature_in = Var(
eff_3.flowsheet().time,
initialize=35,
bounds=(None, None),
units=pyunits.K,
doc="Temperature differnce at the inlet side",
)
eff_3.delta_temperature_out = Var(
eff_3.flowsheet().time,
initialize=35,
bounds=(None, None),
units=pyunits.K,
doc="Temperature differnce at the outlet side",
)
delta_temperature_chen_callback(eff_3)

eff_3.area = Var(
bounds=(0, None),
initialize=1000.0,
doc="Heat exchange area",
units=pyunits.m**2,
)

eff_3.overall_heat_transfer_coefficient = Var(
eff_3.flowsheet().time,
bounds=(0, None),
initialize=100.0,
doc="Overall heat transfer coefficient",
units=pyunits.W / pyunits.m**2 / pyunits.K,
)

eff_3.overall_heat_transfer_coefficient[0].fix(100)

@m.Constraint(eff_3.flowsheet().time, doc="delta_temperature_in at the 2nd effect")
def delta_temperature_in_eff3(b, t):
return (
eff_3.delta_temperature_in[t]
== eff_2.properties_vapor[0].temperature - eff_3.temperature_operating
)

@m.Constraint(eff_3.flowsheet().time, doc="delta_temperature_out at the 2nd effect")
def delta_temperature_out_eff3(b, t):
return (
eff_3.delta_temperature_out[t]
== eff_2.properties_pure_water[0].temperature
- eff_3.properties_in[0].temperature
)

@m.Constraint(eff_3.flowsheet().time)
def heat_transfer_equation_eff3(b, t):
return eff_2.energy_flow_superheated_vapor == (
eff_3.overall_heat_transfer_coefficient[t]
* eff_3.area
* eff_3.delta_temperature[0]
)

iscale.set_scaling_factor(eff_3.delta_temperature_in, 1e-1)
iscale.set_scaling_factor(eff_3.delta_temperature_out, 1e-1)
iscale.set_scaling_factor(eff_3.area, 1e-1)
iscale.set_scaling_factor(eff_3.overall_heat_transfer_coefficient, 1e-1)


def add_heat_exchanger_eff4(m):
eff_3 = m.fs.eff_3
eff_4 = m.fs.eff_4

eff_4.delta_temperature_in = Var(
eff_4.flowsheet().time,
initialize=35,
bounds=(None, None),
units=pyunits.K,
doc="Temperature differnce at the inlet side",
)
eff_4.delta_temperature_out = Var(
eff_4.flowsheet().time,
initialize=35,
bounds=(None, None),
units=pyunits.K,
doc="Temperature differnce at the outlet side",
)
delta_temperature_chen_callback(eff_4)

eff_4.area = Var(
bounds=(0, None),
initialize=1000.0,
doc="Heat exchange area",
units=pyunits.m**2,
)

eff_4.overall_heat_transfer_coefficient = Var(
eff_4.flowsheet().time,
bounds=(0, None),
initialize=100.0,
doc="Overall heat transfer coefficient",
units=pyunits.W / pyunits.m**2 / pyunits.K,
)

eff_4.overall_heat_transfer_coefficient[0].fix(100)

@m.Constraint(eff_4.flowsheet().time, doc="delta_temperature_in at the 2nd effect")
def delta_temperature_in_eff4(b, t):
return (
eff_4.delta_temperature_in[t]
== eff_3.properties_vapor[0].temperature - eff_4.temperature_operating
)

@m.Constraint(eff_4.flowsheet().time, doc="delta_temperature_out at the 2nd effect")
def delta_temperature_out_eff4(b, t):
return (
eff_4.delta_temperature_out[t]
== eff_3.properties_pure_water[0].temperature
- eff_4.properties_in[0].temperature
)

@m.Constraint(eff_4.flowsheet().time)
def heat_transfer_equation_eff4(b, t):
return eff_3.energy_flow_superheated_vapor == (
eff_4.overall_heat_transfer_coefficient[t]
* eff_4.area
* eff_4.delta_temperature[0]
)

iscale.set_scaling_factor(eff_4.delta_temperature_in, 1e-1)
iscale.set_scaling_factor(eff_4.delta_temperature_out, 1e-1)
iscale.set_scaling_factor(eff_4.area, 1e-1)
iscale.set_scaling_factor(eff_4.overall_heat_transfer_coefficient, 1e-1)


def multi_effect_crystallizer_initialization(m):
# Set scaling factors
m.fs.props.set_default_scaling("flow_mass_phase_comp", 1e-1, index=("Liq", "H2O"))
m.fs.props.set_default_scaling("flow_mass_phase_comp", 1e-1, index=("Liq", "NaCl"))
m.fs.props.set_default_scaling("flow_mass_phase_comp", 1e-1, index=("Vap", "H2O"))
m.fs.props.set_default_scaling("flow_mass_phase_comp", 1e-1, index=("Sol", "NaCl"))

calculate_scaling_factors(m)

m.fs.eff_1.initialize()
m.fs.eff_2.initialize()
m.fs.eff_3.initialize()
m.fs.eff_4.initialize()

# Unfix dof
brine_salinity = (
m.fs.eff_1.properties_in[0].conc_mass_phase_comp["Liq", "NaCl"].value
)

for eff in [m.fs.eff_2, m.fs.eff_3, m.fs.eff_4]:
eff.inlet.flow_mass_phase_comp[0, "Liq", "NaCl"].unfix()
eff.inlet.flow_mass_phase_comp[0, "Liq", "H2O"].unfix()
eff.properties_in[0].conc_mass_phase_comp["Liq", "NaCl"].fix(brine_salinity)

# Energy is provided from the previous effect
@m.Constraint(doc="Energy supplied to the 2nd effect")
def eqn_energy_from_eff1(b):
return b.fs.eff_2.work_mechanical[0] == b.fs.eff_1.energy_flow_superheated_vapor

@m.Constraint(doc="Energy supplied to the 3rd effect")
def eqn_energy_from_eff2(b):
return b.fs.eff_3.work_mechanical[0] == b.fs.eff_2.energy_flow_superheated_vapor

@m.Constraint(doc="Energy supplied to the 4th effect")
def eqn_energy_from_eff3(b):
return b.fs.eff_4.work_mechanical[0] == b.fs.eff_3.energy_flow_superheated_vapor


if __name__ == "__main__":
m = build_fs_multi_effect_crystallizer(
operating_pressure_eff1=0.45, # bar
operating_pressure_eff2=0.25, # bar
operating_pressure_eff3=0.208, # bar
operating_pressure_eff4=0.095, # bar
feed_flow_mass=1, # kg/s
feed_mass_frac_NaCl=0.3,
feed_pressure=101325, # Pa
feed_temperature=273.15 + 20, # K
crystallizer_yield=0.5,
steam_pressure=1.5, # bar (gauge pressure)
)

multi_effect_crystallizer_initialization(m)

print(degrees_of_freedom(m))
solver = get_solver()

results = solver.solve(m)

assert results.solver.termination_condition == TerminationCondition.optimal
assert results.solver.status == SolverStatus.ok
Loading
Loading