Add heat exchanger performance and cost components - #418
Conversation
5cf34b2 to
8c1bcb7
Compare
8c1bcb7 to
a693f1c
Compare
| """_summary_ | ||
| This is a very simple placeholder cost model: | ||
|
|
||
| - Reference: 240,000 USD for a 1 MW HX with U ~ 1000 W/m²-K |
There was a problem hiding this comment.
I don't know the source for this function, I would rather use something from a chemical plant design handbook.
From Sinott and Towler (2021):
U-tube shell and tube HX:
Total installed costs = [(a+b*S^n) * f_year * f_install * f_material]
a = 28000
b = 54
n = 1.2
S - heat transfer area in m^2
limits: 10 m^2 <= S <= 1000 m^2
This is in CEPCI index = 532.9 so need to be converted (it's for Jan. 2010)
To get it to 2022 USD f_year = 816/532.9
f_install = 1.61 (default, maybe let the user add their own?)
It also assums simple carbon steel, so we need to account for materials once temperatures are higher
f_material = 1 for carbon steel (should be a default value), if user specifies another more expensive material they should include f_material
For the OpEx I am not sure we want to include it in a cost function, this should be in the general economic calculation (like ProFAST)
| exp_Q = inputs["exp_Q"][0] | ||
|
|
||
| scale_Q = (Q_total_W / Q_ref) ** exp_Q if Q_total_W > 0 else 0.0 | ||
| capex = C_ref * scale_Q |
There was a problem hiding this comment.
Do we want to add installation factor in here, or are those things usually accounted for elsewhere in H2I analysis?
I think in ther steel paper at least the cost functions are for totat installed costs
elenya-grant
left a comment
There was a problem hiding this comment.
Howdy Chris! I'm not sure whether this is ready for a full in-depth review yet but I left a few small comments and did a quick review (I will do another deeper-review once this is not longer a draft). Overall it seems like some very useful and cool functionality! Thanks for the work on this!
Some higher-level notes are:
heat_exchanger_model/hx_shell_tube_steady.py: more inline comments and docstrings would be helpful in the functions defined in here- Could you update the performance model to use the recently introduced
PerformanceModelBaseClass? I'd be happy to help here if needed! - Don't forget to add the performance and cost model(s) to
supported_models.py
| D_o_m: float = field(validator=gt_zero) | ||
| t_wall_m: float = field(validator=gt_zero) | ||
| D_shell_m: float = field(validator=gt_zero) | ||
| cost_year: int = field(validator=gt_zero) |
There was a problem hiding this comment.
why is cost year an input to the performance model config? Doesn't look like its used in this model
There was a problem hiding this comment.
Copied from other code, good catch!
|
|
||
| # Setup OpenMDAO inputs | ||
| self.add_input( | ||
| "Th_in_C", val=self.config.Th_in_C, units="C", desc="Hot fluid inlet temperature" |
There was a problem hiding this comment.
units="degC", "C" is Coulombs in OpenMDAO units
There was a problem hiding this comment.
I wonder if we will ever model Coulombs...
| ) | ||
|
|
||
| # Setup OpenMDAO outputs | ||
| self.add_output("Th_out_C", val=0.0, units="C", desc="Hot fluid outlet temperature") |
There was a problem hiding this comment.
Same note about units being degC instead of C
| "D_shell": self.config.D_shell_m, | ||
| } | ||
|
|
||
| res = hx_shell_tube_steady(params) |
There was a problem hiding this comment.
I kind of feel like whatever logic is done in the hx_shell_tube_steady() function should be replaced in ShellTubeHXPerformanceModel as a method
There was a problem hiding this comment.
I could understand the reasoning there. The reason why I kept it in a separate file is because that method is ~600 lines of code. Plus the model comes with some printing and plotting utilities. I will move them into the class and have you take a look to see if you think it is better or if keeping them separate is preferred.
There was a problem hiding this comment.
Moved! Let me know what you think.
| "Tc_in_C", val=self.config.Tc_in_C, units="C", desc="Cold fluid inlet temperature" | ||
| ) | ||
| self.add_input( | ||
| "m_dot_h_kg_s", |
There was a problem hiding this comment.
I don't think these input and output names should have units in them - because it gets confusing if I did prob.model.get_val("hx.m_dot_h_kg_s", units="kg/h"). I do think units are appropriate for the config input names
| Th_in_C: float = field(validator=gt_zero) | ||
| Tc_in_C: float = field(validator=gt_zero) | ||
| m_dot_h_kg_s: float = field(validator=gt_zero) | ||
| m_dot_c_kg_s: float = field(validator=gt_zero) |
There was a problem hiding this comment.
I understand that the h and c in the variable names mean "hot" and "cold" but I think that it'd be nice to have the "hot" and "cold" within the variable names themselves, something like:
Temp_hot_CorHot_temp_CTemp_cold_CorCold_temp_CHot_mass_flow_rate_kg_s
There was a problem hiding this comment.
I've tried to update all the inputs and outputs to be spelled out.
| cost_year (int): Year for cost estimation. | ||
| """ | ||
|
|
||
| Th_in_C: float = field(validator=gt_zero) |
There was a problem hiding this comment.
Perhaps this relates to PR #480, but I think that this could be generalized to just define the two commodities coming into the heat exchanger. Then in ShellTubeHXPerformanceModel, the inputs would be something like: f"{commodity1}_in_temp", f"{commodity2}_in_temp", f"{commodity1}_in", f"{commodity2}_in".
Is the "hot" thing in just hotter than the "cold" thing in (if so - then the model could see which one is hotter rather than having the user define it). Or is the "hot" thing the thing getting heated?
There was a problem hiding this comment.
I am not sure if it ties to #480, to be honest. I could see the generalization happening as the commodities would be the working fluid and the source fluid, which the temperatures of which would be defined by other components. The "cold" thing (working fluid) is the thing getting heated by the "hot" thing (source fluid). For example, the working fluid could be a feedstock for a food/beverage process, while the source fluid could be water that is heated from an electric boiler.
Can you re-evaluate this with respect to #480 now that that is merged in and see if your thoughts/suggestions have changed at all? If not, I can work to implement the generalization as you suggested.
There was a problem hiding this comment.
I think you're right - I don't think this is related to PR #480 (what a long time ago that was). I haven't re-looked into the code yet, and I understand your definition of the working fluid vs source fluid. But what if the food/beverage process feedstock had more thermal energy than the water - then the working fluid would be the water and the source fluid would be the food/beverage feedstock - right? I'll update this comment in a sec - I'm going to look back at the code real quick.
There was a problem hiding this comment.
Okay - my above comment was more related to the openmdao inputs rather than the config inputs. I guess I was imaging this as almost a transport model instead of a converter. Like - where I could connect the temperature and "out" of two commodity streams to this heat exchanger and that it'd output the temperate after heat has been exchanged. If that's the intended usage - then I think that some of these config inputs would not be necessary because they'd be inputs from upstream techs. Anyway - I think that using this model as a transport model instead is probably out of scope for this PR. So - no changes necessary on this topic!
…into feature/heat_exchanger
445a159 to
f9bfe3a
Compare
kbrunik
left a comment
There was a problem hiding this comment.
Exciting stuff! It was fun getting a chance to dig into it a little. Overall I think it's coming along, I have a few bigger comments particularly regarding the use of a multivariable commodity that I'd love to address before this is ready to come in. Thanks for putting this together though, I think it's really pushing the boudaries of H2I as we know it :)
| ) | ||
|
|
||
| # Setup OpenMDAO inputs | ||
| self.add_input( |
There was a problem hiding this comment.
Hey I saw your discussion with @elenya-grant about PR #480 and I was thinking more about it. H2I currently focuses on passing commodities or physical mass/electrons around a system. @johnjasa added the notion of multivariable commodities where you can add things like temperature and pressure to the commodity. I think to maintain our structure and plan for a larger heat integration effort in the future we should use that paradigm when handling the heat exchanger so your commodities would be your working fluid and your process fluid and those would have associated temperatures and mass flows. Then you would be able to add a commodity that can be more easily tracked in h2i. This converter has a good example of how to add inputs and outputs that way: https://github.com/NatLabRockies/H2Integrate/blob/develop/h2integrate/converters/natural_gas/dummy_gas_components.py
It might not be fully necessary in this PR to move in this direction but I do think in the long run it's going to be easier on the user to appropriately track heat across their system (through things like combiners, splitters and transporters).
| "temp_cold_out", val=0.0, units="degC", desc="Cold fluid outlet temperature" | ||
| ) | ||
| self.add_output("Q_total", val=0.0, units="kW", desc="Total heat transfer rate") | ||
| self.add_output("epsilon", val=0.0, desc="Effectiveness of the heat exchanger") |
There was a problem hiding this comment.
On naming outputs it would be really helpful if the OpenMDAO outputs were more descriptive. Also, I've seen it a few places but be sure to keep the units out of the OpenMDAO name.
| @@ -0,0 +1,916 @@ | |||
| """ | |||
There was a problem hiding this comment.
This file looks duplicative with what's in the body of the ShellTubeHXPerformanceModel, I'd suggest removing this.
|
|
||
| @define(kw_only=True) | ||
| class ShellTubeHXPerformanceModelConfig(BaseConfig): | ||
| """ |
There was a problem hiding this comment.
I would suggest adding an optional argument to be able to plot using the plot_results() function
| # ---------------------------------------------------------------------- | ||
| # 4) Printing summary utility | ||
| # ---------------------------------------------------------------------- | ||
| def print_summary(res, label="Python + CoolProp"): |
There was a problem hiding this comment.
suggest removing or adding an option for a user set in the config to print the summary.
| Tc_out0_max = Th_in - 1e-3 | ||
| Tc_out0_guess = Tc_in + 0.7 * (Th_in - Tc_in) | ||
|
|
||
| def march_segments(Th_in_local, Tc_out0_local): |
There was a problem hiding this comment.
I would suggest adding at least a docstring to this function
| S_gen_seg, | ||
| ) | ||
|
|
||
| def residual_Tc_out(Tc_out0): |
There was a problem hiding this comment.
A docstring would be helpful
| return Tc_at_L - Tc_in | ||
|
|
||
| # Simple bisection | ||
| def find_root_bisect(f, a, b, tol, maxit): |
| # ---------------------------------------------------------------------- | ||
| # 3) Main HX model | ||
| # ---------------------------------------------------------------------- | ||
| def hx_shell_tube_steady(self, params=None): |
There was a problem hiding this comment.
Are there any conditions that would cause this hx model to not work properly and are they captured by the attrs validators or are there some additional errors/warnings that should be added?
|
|
||
| class ShellTubeHXPerformanceModel(om.ExplicitComponent): | ||
| """ | ||
| An OpenMDAO component that wraps the steady shell tube heat exchanger model. |
Add HX performance and cost components
This PR adds a simple heat exchanger model, along with a cost model. This model is meant to be used with other models that are being developed, such as the electric thermal energy storage model #810 and a yet to be opened electric/natural gas boiler model PR. As such, an explicit example using this technology has not yet been added, but will be as part of an integrated example of these components.
This model is meant to accept two fluids, a source fluid (that brings the heat) and a working fluid (that removes the heat). I would appreciate specific review on the implementation of these inputs/outputs and whether they should be changed in some way to reflect commodity streams, or some other form.
Also, review of the organization of the code would be helpful. I originally had the specific heat exchanger functions located in a sub-folder
converters/heat/heat_exchanger_model, but the current version has those functions integrated directly into the model class. So feedback on which method will determine what remains and what gets removed.Docs will be updated once reviewed to be informed by any required changes.
Section 1: Type of Contribution
Section 2: Draft PR Checklist
TODO:
Type of Reviewer Feedback Requested (on Draft PR)
Structural feedback:
heatfolder to theconvertersdirectory.Implementation feedback:
Other feedback:
Section 3: General PR Checklist
docs/files are up-to-date, or added when necessaryCHANGELOG.mdhas been updated to describe the changes made in this PRSection 3: Related Issues
Section 4: Impacted Areas of the Software
Section 4.1: New Files
converters/heat/shell_tube_hx.pyconverters/heat/shell_tube_hx_cost_model.pyconverters/heat/test/test_shell_tube_hx.pyconverters/heat/test/test_shell_tube_hx_cost_model.pySection 4.2: Modified Files
Section 5: Additional Supporting Information
Section 6: Test Results, if applicable
Section 7 (Optional): New Model Checklist
docs/developer_guide/coding_guidelines.mdattrsclass to define theConfigto load in attributes for the modelBaseConfigorCostModelBaseConfiginitialize()method,setup()method,compute()methodCostModelBaseClasssupported_models.pycreate_financial_modelinh2integrate_model.pytest_all_examples.pydocs/user_guide/model_overview.mddocs/section<model_name>.mdis added to the_toc.yml