-
Notifications
You must be signed in to change notification settings - Fork 57
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
Extended BSM2 Flowsheet Costing #1405
Changes from 3 commits
153c3c2
5773dda
f1e4d60
8ada67b
4fcb5e8
bee9985
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ | |
|
||
from idaes.core import ( | ||
FlowsheetBlock, | ||
UnitModelCostingBlock, | ||
) | ||
from idaes.models.unit_models import ( | ||
CSTR, | ||
|
@@ -74,7 +75,14 @@ | |
Thickener, | ||
ActivatedSludgeModelType as thickener_type, | ||
) | ||
from watertap.core.util.initialization import check_solve | ||
|
||
from watertap.core.util.initialization import check_solve, assert_degrees_of_freedom | ||
from watertap.costing import WaterTAPCosting | ||
from watertap.costing.unit_models.clarifier import ( | ||
cost_circular_clarifier, | ||
cost_primary_clarifier, | ||
) | ||
|
||
|
||
# Set up logger | ||
_log = idaeslog.getLogger(__name__) | ||
|
@@ -116,6 +124,16 @@ def main(bio_P=True): | |
fail_flag=True, | ||
) | ||
|
||
add_costing(m) | ||
m.fs.costing.initialize() | ||
|
||
assert_degrees_of_freedom(m, 0) | ||
|
||
results = solve(m) | ||
pyo.assert_optimal_termination(results) | ||
|
||
display_costing(m) | ||
|
||
return m, results | ||
|
||
|
||
|
@@ -471,6 +489,8 @@ def set_operating_conditions(m): | |
m.fs.CL2.split_fraction[0, "effluent", "X_PP"].fix(0.00187) | ||
m.fs.CL2.split_fraction[0, "effluent", "X_S"].fix(0.00187) | ||
|
||
m.fs.CL2.surface_area.fix(1500 * pyo.units.m**2) | ||
|
||
# Sludge purge separator | ||
m.fs.SP2.split_fraction[:, "recycle"].fix(0.985) | ||
|
||
|
@@ -658,6 +678,184 @@ def solve(m, solver=None): | |
return results | ||
|
||
|
||
def add_costing(m): | ||
m.fs.costing = WaterTAPCosting() | ||
m.fs.costing.base_currency = pyo.units.USD_2020 | ||
|
||
# Costing Blocks | ||
m.fs.R1.costing = UnitModelCostingBlock(flowsheet_costing_block=m.fs.costing) | ||
m.fs.R2.costing = UnitModelCostingBlock(flowsheet_costing_block=m.fs.costing) | ||
m.fs.R3.costing = UnitModelCostingBlock(flowsheet_costing_block=m.fs.costing) | ||
m.fs.R4.costing = UnitModelCostingBlock(flowsheet_costing_block=m.fs.costing) | ||
m.fs.R5.costing = UnitModelCostingBlock(flowsheet_costing_block=m.fs.costing) | ||
m.fs.R6.costing = UnitModelCostingBlock(flowsheet_costing_block=m.fs.costing) | ||
m.fs.R7.costing = UnitModelCostingBlock(flowsheet_costing_block=m.fs.costing) | ||
m.fs.CL.costing = UnitModelCostingBlock( | ||
flowsheet_costing_block=m.fs.costing, | ||
costing_method=cost_primary_clarifier, | ||
) | ||
|
||
m.fs.CL2.costing = UnitModelCostingBlock( | ||
flowsheet_costing_block=m.fs.costing, | ||
costing_method=cost_circular_clarifier, | ||
) | ||
|
||
m.fs.AD.costing = UnitModelCostingBlock(flowsheet_costing_block=m.fs.costing) | ||
m.fs.dewater.costing = UnitModelCostingBlock(flowsheet_costing_block=m.fs.costing) | ||
m.fs.thickener.costing = UnitModelCostingBlock(flowsheet_costing_block=m.fs.costing) | ||
|
||
# TODO: Leaving out mixer costs; consider including later | ||
|
||
# process costing and add system level metrics | ||
m.fs.costing.cost_process() | ||
m.fs.costing.add_electricity_intensity(m.fs.FeedWater.properties[0].flow_vol) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why add electricity intensity and specific energy consumption? They should be the same so you can probably remove this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was pretty much a copy+paste from the conventional BSM2 costing. I'll remove electricity intensity from both. |
||
m.fs.costing.add_annual_water_production(m.fs.Treated.properties[0].flow_vol) | ||
m.fs.costing.add_LCOW(m.fs.FeedWater.properties[0].flow_vol) | ||
m.fs.costing.add_specific_energy_consumption(m.fs.FeedWater.properties[0].flow_vol) | ||
|
||
m.fs.objective = pyo.Objective(expr=m.fs.costing.LCOW) | ||
iscale.set_scaling_factor(m.fs.costing.LCOW, 1e3) | ||
iscale.set_scaling_factor(m.fs.costing.total_capital_cost, 1e-7) | ||
iscale.set_scaling_factor(m.fs.costing.total_capital_cost, 1e-5) | ||
|
||
iscale.calculate_scaling_factors(m.fs) | ||
|
||
|
||
def display_costing(m): | ||
print("Levelized cost of water: %.2f $/m3" % pyo.value(m.fs.costing.LCOW)) | ||
|
||
print( | ||
"Total operating cost: %.2f $/yr" % pyo.value(m.fs.costing.total_operating_cost) | ||
) | ||
print("Total capital cost: %.2f $" % pyo.value(m.fs.costing.total_capital_cost)) | ||
|
||
print( | ||
"Total annualized cost: %.2f $/yr" | ||
% pyo.value(m.fs.costing.total_annualized_cost) | ||
) | ||
print( | ||
"Specific energy consumption with respect to influent flowrate: %.1f kWh/m3" | ||
% pyo.value(m.fs.costing.specific_energy_consumption) | ||
) | ||
|
||
print( | ||
"electricity consumption R5", | ||
pyo.value(m.fs.R5.electricity_consumption[0]), | ||
pyo.units.get_units(m.fs.R5.electricity_consumption[0]), | ||
) | ||
print( | ||
"electricity consumption R6", | ||
pyo.value(m.fs.R6.electricity_consumption[0]), | ||
pyo.units.get_units(m.fs.R6.electricity_consumption[0]), | ||
) | ||
print( | ||
"electricity consumption R7", | ||
pyo.value(m.fs.R7.electricity_consumption[0]), | ||
pyo.units.get_units(m.fs.R7.electricity_consumption[0]), | ||
) | ||
print( | ||
"electricity consumption primary clarifier", | ||
pyo.value(m.fs.CL.electricity_consumption[0]), | ||
pyo.units.get_units(m.fs.CL.electricity_consumption[0]), | ||
) | ||
print( | ||
"electricity consumption secondary clarifier", | ||
pyo.value(m.fs.CL2.electricity_consumption[0]), | ||
pyo.units.get_units(m.fs.CL2.electricity_consumption[0]), | ||
) | ||
print( | ||
"electricity consumption AD", | ||
pyo.value(m.fs.AD.electricity_consumption[0]), | ||
pyo.units.get_units(m.fs.AD.electricity_consumption[0]), | ||
) | ||
print( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like a chunk of these printouts aren't technically under the "costing" category. Maybe you can consider moving those under a separate display function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, I'll address your comments for the conventional and extended BSM2 flowsheets |
||
"electricity consumption dewatering Unit", | ||
pyo.value(m.fs.dewater.electricity_consumption[0]), | ||
pyo.units.get_units(m.fs.dewater.electricity_consumption[0]), | ||
) | ||
print( | ||
"electricity consumption thickening Unit", | ||
pyo.value(m.fs.thickener.electricity_consumption[0]), | ||
pyo.units.get_units(m.fs.thickener.electricity_consumption[0]), | ||
) | ||
print( | ||
"Influent flow", | ||
pyo.value(m.fs.FeedWater.flow_vol[0]), | ||
pyo.units.get_units(m.fs.FeedWater.flow_vol[0]), | ||
) | ||
print( | ||
"flow into R3", | ||
pyo.value(m.fs.R3.control_volume.properties_in[0].flow_vol), | ||
pyo.units.get_units(m.fs.R3.control_volume.properties_in[0].flow_vol), | ||
) | ||
print( | ||
"flow into RADM", | ||
pyo.value(m.fs.AD.liquid_phase.properties_in[0].flow_vol), | ||
pyo.units.get_units(m.fs.AD.liquid_phase.properties_in[0].flow_vol), | ||
) | ||
|
||
print( | ||
"capital cost R1", | ||
pyo.value(m.fs.R1.costing.capital_cost), | ||
pyo.units.get_units(m.fs.R1.costing.capital_cost), | ||
) | ||
print( | ||
"capital cost R2", | ||
pyo.value(m.fs.R2.costing.capital_cost), | ||
pyo.units.get_units(m.fs.R2.costing.capital_cost), | ||
) | ||
print( | ||
"capital cost R3", | ||
pyo.value(m.fs.R3.costing.capital_cost), | ||
pyo.units.get_units(m.fs.R3.costing.capital_cost), | ||
) | ||
print( | ||
"capital cost R4", | ||
pyo.value(m.fs.R4.costing.capital_cost), | ||
pyo.units.get_units(m.fs.R4.costing.capital_cost), | ||
) | ||
print( | ||
"capital cost R5", | ||
pyo.value(m.fs.R5.costing.capital_cost), | ||
pyo.units.get_units(m.fs.R5.costing.capital_cost), | ||
) | ||
print( | ||
"capital cost R6", | ||
pyo.value(m.fs.R6.costing.capital_cost), | ||
pyo.units.get_units(m.fs.R6.costing.capital_cost), | ||
) | ||
print( | ||
"capital cost R7", | ||
pyo.value(m.fs.R7.costing.capital_cost), | ||
pyo.units.get_units(m.fs.R7.costing.capital_cost), | ||
) | ||
print( | ||
"capital cost primary clarifier", | ||
pyo.value(m.fs.CL.costing.capital_cost), | ||
pyo.units.get_units(m.fs.CL.costing.capital_cost), | ||
) | ||
print( | ||
"capital cost secondary clarifier", | ||
pyo.value(m.fs.CL2.costing.capital_cost), | ||
pyo.units.get_units(m.fs.CL2.costing.capital_cost), | ||
) | ||
print( | ||
"capital cost AD", | ||
pyo.value(m.fs.AD.costing.capital_cost), | ||
pyo.units.get_units(m.fs.AD.costing.capital_cost), | ||
) | ||
print( | ||
"capital cost dewatering Unit", | ||
pyo.value(m.fs.dewater.costing.capital_cost), | ||
pyo.units.get_units(m.fs.dewater.costing.capital_cost), | ||
) | ||
print( | ||
"capital cost thickener unit", | ||
pyo.value(m.fs.thickener.costing.capital_cost), | ||
pyo.units.get_units(m.fs.thickener.costing.capital_cost), | ||
) | ||
|
||
|
||
if __name__ == "__main__": | ||
# This method builds and runs a steady state activated sludge flowsheet. | ||
m, results = main() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this what we use on the conventional BSM2? I think we should avoid 2020 as the base currency because of the pandemic's impact.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Conventional also used 2020... should I switch both to 2018 or 2019?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. I'm not sure that we've selected a standard year that we want to go by. I think I can recall using 2018 for a few of the IEDO models previously. I suppose we can put a pin it this for now if we'd rather settle on a year.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But perhaps the best route is to go with the latest year that we have the CEPCI index for
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The latest year in IDAES is 2022, but it appears that hasn't made it's way into WaterTAP yet. I tried 2021 as well, but the solver failed to return an optimal solution... not sure why that would happen.