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 NaCl recovery value to crystallizer model #1120

Merged
merged 12 commits into from
Sep 8, 2023
12 changes: 12 additions & 0 deletions watertap/costing/units/crystallizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,15 @@ def build_crystallizer_cost_param_block(blk):
doc="Steam cost, Panagopoulos (2019)",
)

blk.NaCl_recovery_value = pyo.Var(
initialize=0,
units=pyo.units.USD_2018 / pyo.units.kg,
doc="Unit recovery value of NaCl",
)

costing = blk.parent_block()
costing.add_defined_flow("steam", blk.steam_cost)
costing.add_defined_flow("NaCl", blk.NaCl_recovery_value)


def cost_crystallizer(blk, cost_type=CrystallizerCostType.default):
Expand Down Expand Up @@ -135,6 +142,11 @@ def _cost_crystallizer_flows(blk):
"steam",
)

blk.costing_package.cost_flow(
blk.unit_model.solids.flow_mass_phase_comp[0, "Sol", "NaCl"],
"NaCl",
)


@register_costing_parameter_block(
build_rule=build_crystallizer_cost_param_block,
Expand Down
2 changes: 1 addition & 1 deletion watertap/costing/watertap_costing_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ def build_process_costs(self):
)
self.total_operating_cost = pyo.Var(
initialize=1e3,
domain=pyo.NonNegativeReals,
domain=pyo.Reals,
doc="Total operating cost",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is where I changed the domain in the costing package and it creates some different solutions in the testing files below, one of which has a difference larger than 10%. @bknueven @adam-a-a

units=self.base_currency / self.base_period,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def test_flowsheet_NF():
) == pytest.approx(0.7667, rel=1e-3)
assert value(
m.fs.tb_pretrt_to_desal.properties_in[0].flow_mass_phase_comp["Liq", "Ca"]
) == pytest.approx(1.15808e-4, rel=1e-3)
) == pytest.approx(1.1636e-4, rel=1e-3)


@pytest.mark.component
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,24 +72,24 @@ def test_ideal_naocl_chlorination():
def test_ideal_naocl_chlorination_full_block():
model = run_chlorination_block_example(fix_free_chlorine=True)
assert model.fs.ideal_naocl_mixer_unit.dosing_rate.value == pytest.approx(
1.7296113311683092e-09, rel=1e-3
9.505698559578499e-07, rel=1e-3
)
assert model.fs.ideal_naocl_mixer_unit.outlet.flow_mol[0].value == pytest.approx(
25.000025535888078, rel=1e-3
)
assert model.fs.ideal_naocl_mixer_unit.outlet.mole_frac_comp[
0, "OCl_-"
].value == pytest.approx(1.6123004572288052e-07, rel=1e-3)
].value == pytest.approx(5.107133802822922e-07, rel=1e-3)

assert model.fs.ideal_naocl_chlorination_unit.free_chlorine.value == pytest.approx(
2, rel=1e-3
)
assert model.fs.ideal_naocl_chlorination_unit.outlet.mole_frac_comp[
0, "OCl_-"
].value == pytest.approx(5.0027242332010015e-08, rel=1e-3)
].value == pytest.approx(4.5088044031726496e-07, rel=1e-3)
assert model.fs.ideal_naocl_chlorination_unit.outlet.mole_frac_comp[
0, "H_+"
].value == pytest.approx(1.49011416785194e-10, rel=1e-3)
].value == pytest.approx(5.4260427865375997e-11, rel=1e-3)
Comment on lines +75 to +92
Copy link
Contributor

Choose a reason for hiding this comment

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

since these are all close to 0, wonder if they should have absolute tolerances instead of relative tolerances (what we typically want to use for ~0 values).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea good catch, I think that should be the case.



@pytest.mark.component
Expand Down
8 changes: 4 additions & 4 deletions watertap/examples/flowsheets/nf_dspmde/tests/test_nf.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@
def test_main():
m = main()
test_dict = {
"lcow": [m.fs.costing.LCOW, 0.16811587158493219],
"pressure": [m.fs.NF.pump.outlet.pressure[0] / 1e5, 6.56],
"area": [m.fs.NF.nfUnit.area, 285.6900547389303],
"lcow": [m.fs.costing.LCOW, 0.15058960529129017],
"pressure": [m.fs.NF.pump.outlet.pressure[0] / 1e5, 8.13],
"area": [m.fs.NF.nfUnit.area, 423.8956418211484],
"recovery": [
m.fs.NF.nfUnit.recovery_vol_phase[0.0, "Liq"] * 100,
73.47934090302432,
94.99999441324391,
],
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This test has a larger difference in results. @bknueven @adam-a-a

Copy link
Contributor

Choose a reason for hiding this comment

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

@Zhuoran29 can you double check the value of the total_operating_cost here, both before and after the change?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bknueven Sure, before the change total_operating_cost = 2369.18, and after the change total_operating_cost = 2429.43.

Copy link
Contributor

Choose a reason for hiding this comment

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

I was able to do this myself:
current main: total_operating_cost: 2369.1822124750583
With this change: total_operating_cost: 2429.430766595918

It's actually a bit higher, funnily enough ...

Copy link
Contributor

Choose a reason for hiding this comment

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

This change also brings the converged values without bypass much closer to those with by pass:

No Bypass, main:

total_operating_cost: 2369.182212475059
Optimal cost 0.16810666231247273
Optimal NF pressure (Bar) 6.560034522531212
Optimal area (m2) 285.7101510399154
Optimal nf recovery (%) 73.48423398501477
Feed hardness (mg/L as CaCO3) 1016.1545749479138
Product hardness (mg/L as CaCO3) 199.9999928713816
Disposal hardness (mg/L as CaCO3) 3277.997368528139

No Bypass, with this change:

total_operating_cost: 2429.430766595918
Optimal cost 0.15058960529129017
Optimal NF pressure (Bar) 8.12906860029318
Optimal area (m2) 423.89564182114776
Optimal nf recovery (%) 94.99999441324391
Feed hardness (mg/L as CaCO3) 1016.1545749479138
Product hardness (mg/L as CaCO3) 199.9995335609493
Disposal hardness (mg/L as CaCO3) 16523.08212268398

With Bypass with / without this change:

total_operating_cost: 2217.7019485486526
Optimal cost 0.1376886456374725
Optimal NF pressure (Bar) 6.702781481688757
Optimal area (m2) 419.7559334394971
Optimal nf recovery (%) 94.99999897882932
bypass (%) 10.592027708476932
Feed hardness (mg/L as CaCO3) 1016.1240639142685
Product hardness (mg/L as CaCO3) 199.9999851467023
Disposal hardness (mg/L as CaCO3) 18456.17762054043

Comment on lines +23 to 29
Copy link
Contributor

Choose a reason for hiding this comment

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

}
for (model_result, testval) in test_dict.values():
Expand Down
29 changes: 29 additions & 0 deletions watertap/unit_models/tests/test_crystallizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,9 @@ def test_solution2_operatingcost(self, Crystallizer_frame_2):
assert pytest.approx(30666.67, rel=1e-3) == value(
m.fs.costing.aggregate_flow_costs["steam"]
)
assert pytest.approx(0, rel=1e-3) == value(
m.fs.costing.aggregate_flow_costs["NaCl"]
)

@pytest.mark.component
def test_solution2_operatingcost_steampressure(self, Crystallizer_frame_2):
Expand All @@ -589,3 +592,29 @@ def test_solution2_operatingcost_steampressure(self, Crystallizer_frame_2):
assert pytest.approx(21451.91, rel=1e-3) == value(
m.fs.costing.aggregate_flow_costs["steam"]
)
assert pytest.approx(0, abs=1e-6) == value(
m.fs.costing.aggregate_flow_costs["NaCl"]
)

@pytest.mark.component
def test_solution2_operatingcost_NaCl_revenue(self, Crystallizer_frame_2):
m = Crystallizer_frame_2
m.fs.costing.crystallizer.steam_pressure.fix(3)
m.fs.costing.crystallizer.NaCl_recovery_value.fix(-0.07)

results = solver.solve(m)

# Check for optimal solution
assert results.solver.termination_condition == TerminationCondition.optimal
assert results.solver.status == SolverStatus.ok

# Operating cost validation
assert pytest.approx(835.41, rel=1e-3) == value(
m.fs.costing.aggregate_flow_costs["electricity"]
)
assert pytest.approx(30666.67, rel=1e-3) == value(
m.fs.costing.aggregate_flow_costs["steam"]
)
assert pytest.approx(-187858.2, rel=1e-3) == value(
m.fs.costing.aggregate_flow_costs["NaCl"]
)
Loading