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

adding LCOW per unit and per flow #1398

Open
wants to merge 14 commits into
base: main
Choose a base branch
from

Conversation

bknueven
Copy link
Contributor

@bknueven bknueven commented May 20, 2024

Fixes #1374

Summary/Motivation:

Add named Expressions to provide more breakdowns in aggregate costs.

Changes proposed in this PR:

  • Add breakdown of LCOW
  • Add breakdown for specific energy consumption

LCOW Aggregate Breakdown (LSRRO flowsheet):

In [2]: m.fs.costing.LCOW_aggregate_direct_capex.display()
LCOW_aggregate_direct_capex : Size=3
    Key                  : Value
    EnergyRecoveryDevice : 0.022400579945895168
                    Pump :  0.36255239471102896
        ReverseOsmosis1D :   0.0812137633968554

In [3]: m.fs.costing.LCOW_aggregate_indirect_capex.display()
LCOW_aggregate_indirect_capex : Size=3
    Key                  : Value
    EnergyRecoveryDevice : 0.022400579945895168
                    Pump :  0.36255239471102896
        ReverseOsmosis1D :   0.0812137633968554

In [4]: m.fs.costing.LCOW_aggregate_fixed_opex.display()
LCOW_aggregate_fixed_opex : Size=3
    Key                  : Value
    EnergyRecoveryDevice :  0.0134403479675371
                    Pump : 0.21753143682661735
        ReverseOsmosis1D : 0.17054890313339632

In [5]: m.fs.costing.LCOW_aggregate_variable_opex.display()
LCOW_aggregate_variable_opex : Size=4
    Key                  : Value
    EnergyRecoveryDevice : -0.1619910020385477
                    Pump :  0.7814377745087072
        ReverseOsmosis1D :                 0.0
             electricity :  0.6194467724701596

LCOW Component Breakdown (LSRRO flowsheet):

In [6]: m.fs.costing.LCOW_component_direct_capex.display()
LCOW_component_direct_capex : Size=10
    Key                         : Value
             fs.BoosterPumps[2] :   0.0740629417841365
             fs.BoosterPumps[3] : 0.015031035438535014
    fs.EnergyRecoveryDevices[1] : 0.015590709925634874
    fs.EnergyRecoveryDevices[3] : 0.006809870020260295
             fs.PrimaryPumps[1] :  0.11420386829667144
             fs.PrimaryPumps[2] :  0.10039251522406238
             fs.PrimaryPumps[3] : 0.058862033967623587
                  fs.ROUnits[1] : 0.017956904423638016
                  fs.ROUnits[2] :   0.0443723945092224
                  fs.ROUnits[3] : 0.018884464463994978

In [7]: m.fs.costing.LCOW_component_indirect_capex.display()
LCOW_component_indirect_capex : Size=10
    Key                         : Value
             fs.BoosterPumps[2] :   0.0740629417841365
             fs.BoosterPumps[3] : 0.015031035438535014
    fs.EnergyRecoveryDevices[1] : 0.015590709925634874
    fs.EnergyRecoveryDevices[3] : 0.006809870020260295
             fs.PrimaryPumps[1] :  0.11420386829667144
             fs.PrimaryPumps[2] :  0.10039251522406238
             fs.PrimaryPumps[3] : 0.058862033967623587
                  fs.ROUnits[1] : 0.017956904423638016
                  fs.ROUnits[2] :   0.0443723945092224
                  fs.ROUnits[3] : 0.018884464463994978

In [8]: m.fs.costing.LCOW_component_fixed_opex.display()
LCOW_component_fixed_opex : Size=10
    Key                         : Value
             fs.BoosterPumps[2] :  0.04443776507048189
             fs.BoosterPumps[3] : 0.009018621263121007
    fs.EnergyRecoveryDevices[1] : 0.009354425955380923
    fs.EnergyRecoveryDevices[3] : 0.004085922012156177
             fs.PrimaryPumps[1] :  0.06852232097800286
             fs.PrimaryPumps[2] : 0.060235509134437426
             fs.PrimaryPumps[3] :  0.03531722038057415
                  fs.ROUnits[1] :  0.03770949928963983
                  fs.ROUnits[2] :  0.09318202846936705
                  fs.ROUnits[3] : 0.039657375374389445

In [9]: m.fs.costing.LCOW_component_variable_opex.display()
LCOW_component_variable_opex : Size=10
    Key                         : Value
             fs.BoosterPumps[2] :     0.2824198320478189
             fs.BoosterPumps[3] :   0.057104190081736304
    fs.EnergyRecoveryDevices[1] :   -0.06369812552998685
    fs.EnergyRecoveryDevices[3] :   -0.09829287650856083
             fs.PrimaryPumps[1] :     0.4354868511375486
             fs.PrimaryPumps[2] : 2.2727273122190515e-09
             fs.PrimaryPumps[3] :   0.006426898968876186
                  fs.ROUnits[1] :                    0.0
                  fs.ROUnits[2] :                    0.0
                  fs.ROUnits[3] :                    0.0

SEC Breakdown (LSRRO Flowsheet):

In [2]: m.fs.costing.specific_energy_consumption_feed_component.display()
specific_energy_consumption_feed_component : Size=7
    Key                         : Value
             fs.BoosterPumps[2] :      2.017284514627277
             fs.BoosterPumps[3] :    0.40788707201240215
    fs.EnergyRecoveryDevices[1] :   -0.45498661092847736
    fs.EnergyRecoveryDevices[3] :    -0.7020919750611486
             fs.PrimaryPumps[1] :     3.1106203652682036
             fs.PrimaryPumps[2] : 1.6233766515850364e-08
             fs.PrimaryPumps[3] :   0.045906421206258465

In [3]: m.fs.costing.specific_energy_consumption_component.display()
specific_energy_consumption_component : Size=7
    Key                         : Value
             fs.BoosterPumps[2] :     4.034569029254554
             fs.BoosterPumps[3] :    0.8157741440248043
    fs.EnergyRecoveryDevices[1] :   -0.9099732218569547
    fs.EnergyRecoveryDevices[3] :   -1.4041839501222972
             fs.PrimaryPumps[1] :     6.221240730536407
             fs.PrimaryPumps[2] : 3.246753303170073e-08
             fs.PrimaryPumps[3] :   0.09181284241251693

Legal Acknowledgement

By contributing to this software project, I agree to the following terms and conditions for my contribution:

  1. I agree my contributions are submitted under the license terms described in the LICENSE.txt file at the top level of this directory.
  2. I represent I am authorized to make the contributions and grant the license. If my employer has rights to intellectual property that includes these contributions, I represent that I have received permission to make contributions and grant the required license on behalf of that employer.

Copy link
Contributor

@adam-a-a adam-a-a left a comment

Choose a reason for hiding this comment

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

Being able to grab the LCOW breakdown quickly without (or with minimal) manual intervention will be very useful. I have a couple of comments to consider.

if hasattr(u, "capital_cost"):
capital_cost = pyo.units.convert(u.capital_cost, to_units=c_units)
# total capital costs w/ recovery factor
numerator += self.capital_recovery_factor * (
Copy link
Contributor

Choose a reason for hiding this comment

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

Rather than add the capex and opex components to the numerator (which could be one level of aggregation), the more typical/informative convention for LCOW breakdown is to separately report the capex and opex components separately for each unit. I think summing the fixed and variable opex would be OK.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What should the names be?

We can add unlimited expressions will little additional cost. I.e., we could do
LCOW_unit_total[ unit_name ], LCOW_unit_capex[ unit_name ], LCOW_unit_opex[ unit_name ]

Copy link
Contributor

Choose a reason for hiding this comment

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

That's a good question. Maybe something like

LCOW_capex[unit_name_or_indirect_capex_component]

LCOW_opex[unitname_or_flow_name]

So LCOW_opex["electricity"] or LCOW_opex["reverse_osmosis"] would work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm worried about name clashes if I put units and flows in the same object -- there are other checks to make sure unit names and flow names don't clash with each other. It would probably be best to keep them separate.

Note that flows, by definition, will only have an opex anyways.

pyo.Any,
doc=f"Levelized Cost of Water per flow based on flow {flow_rate.name}",
)
self.add_component(name + "_per_flow", flow_lcows)
Copy link
Contributor

Choose a reason for hiding this comment

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

Personally, I think "per_flow" can be a little confusing to users with water treatment background, though I see how it ties to the jargon of the idaes costing framework. Ideally, we'd have something like "LCOW_comp_electricity" or "LCOW_comp["electricity"]", for example.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess nothing else is indexed for the flow components, so LCOW_comp_electricity seems okay to me.

Copy link
Contributor

Choose a reason for hiding this comment

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

To my eyes/ears familiar with IDAES naming conventions, comp in the name indicates a chemical (dissolved or otherwise) component... "per stream"?

Copy link
Contributor

Choose a reason for hiding this comment

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

oops missed the outdated tag on this comment

for f in flows:
# part of total_variable_operating_cost
flow_cost = pyo.units.convert(f * cost_var, to_units=c_units / t_units)
flow_lcows[str(f)] = (flow_cost * self.utilization_factor) / denominator
Copy link
Contributor

Choose a reason for hiding this comment

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

I suppose we should be aware of a potential edge case--where the primary treatment segment of a plant may be turned off, but there is still some minimum load for parts of the plant that stay on. Up until now, we've assumed that there is absolutely no electricity usage during shutdown when in fact, while we may not be producing treated water at a given time, we can still be, e.g., pretreating water with units that must stay on even when the primary treatment step shuts down.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They'll get an error when evaluating this (and the other expressions) then. Not sure there's much to be done about it.

# part of total_variable_operating_cost
flow_cost = pyo.units.convert(f * cost_var, to_units=c_units / t_units)
flow_lcows[str(f)] = (flow_cost * self.utilization_factor) / denominator

Copy link
Contributor

Choose a reason for hiding this comment

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

Just want to add a note that we should eventually perform a similar unit breakdown for SEC.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes -- once we settle on exactly what should be reported I will use the code here to do it for every "per flow" aggregate.

Copy link

codecov bot commented May 20, 2024

Codecov Report

Attention: Patch coverage is 94.28571% with 2 lines in your changes are missing coverage. Please review.

Project coverage is 93.92%. Comparing base (f48b771) to head (0a6f06c).
Report is 1 commits behind head on main.

Files Patch % Lines
watertap/costing/watertap_costing_package.py 94.28% 2 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1398   +/-   ##
=======================================
  Coverage   93.92%   93.92%           
=======================================
  Files         335      335           
  Lines       35620    35655   +35     
=======================================
+ Hits        33456    33489   +33     
- Misses       2164     2166    +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@kurbansitterley
Copy link
Contributor

I'll just echo @adam-a-a's comments in general for this and am less concerned with the naming convention. I assume you could provide any flow expression you want (not clear if this was answered above)? e.g., LCOW_component_electricity for both the effluent flow rate of the entire system and the influent of the unit itself (two separate values/metrics)

@ksbeattie ksbeattie added the Priority:Normal Normal Priority Issue or PR label May 23, 2024
@ksbeattie ksbeattie added Priority:High High Priority Issue or PR and removed Priority:Normal Normal Priority Issue or PR labels Jul 11, 2024
@ksbeattie ksbeattie added the IDAES label Aug 1, 2024
@ksbeattie
Copy link
Contributor

@bknueven wants to test this again the latest idaes-pse

@bknueven bknueven marked this pull request as ready for review September 12, 2024 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
IDAES Priority:High High Priority Issue or PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CostingPackage: LCOW / SEC breakdown by unit model
4 participants