Repository navigation
Conversation
- DataCenterPerformanceModel/DataCenterCostModel: compute load model using electrical efficiency and cooling load ratio - DataCenterPUEWUEPerformanceModel/DataCenterPUEWUECostModel: PUE/WUE-based model with IECC climate-zone lookup (Lei & Masanet 2022) via eeweather - Example 38_data_center, unit and example tests, docs page - Add eeweather dependency
johnjasa
left a comment
There was a problem hiding this comment.
Thanks for this, Chris! Love the docs and references to sources. Glad to get data center modeling in H2I! Submitting a partial review with some blocking changes now, will come back to this later on.
| load profile. An example that runs both model pairs with an hourly AI training workload | ||
| is provided in `examples/38_data_center/`. |
There was a problem hiding this comment.
Could you please add where the training workload profile came from? Given all the questions we get!
| "electricity_in", | ||
| val=0.0, | ||
| shape=n_timesteps, | ||
| units="MW/h", |
There was a problem hiding this comment.
The units are incorrect here; they should be commodity_rate_units; anything upstream that would be connected to this would fail with a units mismatch
| outputs["unmet_electricity_demand_out"] = total_electricity_demand - electricity_used | ||
| outputs["water_consumed"] = water_consumed | ||
| outputs["compute_load_out"] = compute_load_demand | ||
| outputs["total_compute_load_produced"] = np.sum(compute_load_demand) * (self.dt / 3600) | ||
| outputs["capacity_factor"] = outputs["total_compute_load_produced"].sum() / max_production | ||
| outputs["annual_compute_load_produced"] = outputs["total_compute_load_produced"] * ( | ||
| 1 / self.fraction_of_year_simulated | ||
| ) | ||
| outputs["rated_compute_load_production"] = inputs["system_capacity"] |
There was a problem hiding this comment.
These output names don't match the other model's; for interchangability, could you please use the same names for both models? Unless there's a meaningful difference between the outputs.
| Simple model | PUE/WUE model |
|---|---|
compute_load_demand |
compute_it_workload |
demand_profile |
compute_it_workload_profile |
unmet_electricity_demand_out |
unmet_electricity_demand |
| none | unmet_water_demand |
| if warnings: | ||
| self.logger.warning( | ||
| "Warnings encountered when selecting weather station for location (%s, %s): %s", | ||
| latitude, | ||
| longitude, | ||
| warnings, | ||
| ) |
There was a problem hiding this comment.
There's no logger attr. If this is reached, it fails on an AttributeError. I'd suggest raising a UserWarning
There was a problem hiding this comment.
Old habits die hard...
| opex = fixed_om + variable_om | ||
|
|
||
| outputs["CapEx"] = capex | ||
| outputs["OpEx"] = opex |
There was a problem hiding this comment.
Could you please break out the variable OpEx into VarOpEx? The GenericConverterCost is a good example of this
| climate_zone: str = field(default=None) | ||
| efficiency_level: str = field(default="efficient") | ||
| cooling_configuration: int = field(default=None) | ||
| optimize_for: str = field(default="pue") | ||
| size_sqft: float = field(default=None) |
There was a problem hiding this comment.
Could you please add a list of allowable inputs here? Each one of these could accept e.g. optimize_for: "babadook" and it'd pass initialization without a clear error
| 3600, | ||
| 3600, | ||
| ) # (min, max) time step lengths (in seconds) compatible with this model | ||
|
|
There was a problem hiding this comment.
please add a _control_classifier for each performance model. I think "fixed" is the right option but it might be another one.
kbrunik
left a comment
There was a problem hiding this comment.
Thanks for adding this PR and all of the details on how to use it, this is going to be a great addition to H2I! The doc page looks great!
I have a bigger concern about making sure the model is correctly limited by the feedstocks available per timestep for both of the data center models that I think it would be good to take a closer look at.
To respond to your reviewer feedback questions:
- I think the PUE/WUE file looks good where it's at, easy to see how it's related.
- I think you should remove the rates from the cost model and allow the feedstocks to handle the costs. It will help add more clarity since in the current model they have to be lumped into a single VarOpex for the data center.
| \dot{V}_{\text{water}} = E_{\text{IT}} \times \text{WUE} | ||
| ``` | ||
|
|
||
| where PUE is the ratio of total facility power to IT equipment power, and WUE is the water |
There was a problem hiding this comment.
Can you add the acronym definitions for PUE and WUE?
| """ | ||
|
|
||
| system_capacity_mw: float = field(validator=validators.gt(0)) | ||
| compute_electrical_efficiency: float = field(validator=validators.gt(0)) |
There was a problem hiding this comment.
| compute_electrical_efficiency: float = field(validator=validators.gt(0)) | |
| compute_electrical_efficiency: float = field(validator=(validators.gt(0), validators.le(1))) |
Then you don't get folks trying to input percentages
| compute_electrical_efficiency: float = field(validator=validators.gt(0)) | ||
| cooling_load_ratio: float = field(validator=validators.ge(0)) | ||
| water_use_gal_per_mwh: float = field(validator=validators.ge(0)) | ||
| demand_profile: int | float | list = field() |
There was a problem hiding this comment.
I'd change this to demand_profile_mw since the units are preset in the initialize, or you could allow the user to specify the commodity_rate_units in the config and then leave it as demand_profile which is more common across the code base (see Demand Base Class).
And could you add this attribute to the docstring?
| compute load (0 < efficiency <= 1). | ||
| cooling_load_ratio (float): Ratio of cooling load to compute load. | ||
| water_use_gal_per_mwh (float): Water usage per MWh of compute load. | ||
| electricity_in (float array): Electricity input profile in MW/h. |
There was a problem hiding this comment.
| electricity_in (float array): Electricity input profile in MW/h. | |
| electricity_in (float array): Electricity input profile in MW. |
| outputs: OpenMDAO outputs object for compute_load_out, water_consumed, | ||
| and unmet_electricity_demand. | ||
| """ | ||
| # system_capacity = self.config.system_capacity_mw # plant capacity in MW |
There was a problem hiding this comment.
delete commented out line
| if not matching_rows: | ||
| raise ValueError( | ||
| f"No PUE/WUE data found for climate zone '{climate_zone}' and efficiency " | ||
| f"'{efficiency}'. Change the climate zone or efficiency level, or supply " | ||
| f"your PUE and WUE values directly." | ||
| ) | ||
|
|
||
| if cooling_configuration is not None: | ||
| if cooling_configuration not in self.config.COOLING_CONFIGURATIONS: | ||
| raise ValueError( | ||
| f"cooling_configuration must be an integer from 1 to 12, " | ||
| f"got '{cooling_configuration}'." | ||
| ) |
There was a problem hiding this comment.
You could remove these errors too if you use the validators above
| valid_cases = self.config.SIZE_COOLING_CONFIGURATIONS[size_category] | ||
| matching_rows = [r for r in matching_rows if r[0] in valid_cases] | ||
| if not matching_rows: | ||
| raise ValueError( |
There was a problem hiding this comment.
This error could also be removed with use of the validators above
| USD/(MW*year). | ||
| """ | ||
|
|
||
| electricity_rate: float = field(validator=validators.ge(0)) |
There was a problem hiding this comment.
I'd suggest using our feedstocks as is and remove the water and electricity rates from this model. I think you end up with a slightly obfuscated picture when you combine the water electricity into a joint varopex and you can see the impact of water/electricity rate changes more clearly if you use the feedstock models.
| "system_capacity", | ||
| val=self.config.system_capacity_mw, | ||
| units="MW", | ||
| desc="IT equipment rated capacity in MW", |
There was a problem hiding this comment.
| desc="IT equipment rated capacity in MW", | |
| desc="IT equipment rated capacity", |
| self.add_input( | ||
| "capex_per_mw", | ||
| val=self.config.capex_per_mw, | ||
| units="USD/MW", | ||
| desc="Capital cost per MW of IT equipment capacity", | ||
| ) |
There was a problem hiding this comment.
| self.add_input( | |
| "capex_per_mw", | |
| val=self.config.capex_per_mw, | |
| units="USD/MW", | |
| desc="Capital cost per MW of IT equipment capacity", | |
| ) | |
| self.add_input( | |
| "capex", | |
| val=self.config.capex_per_mw, | |
| units="USD/MW", | |
| desc="Capital cost of IT equipment capacity", | |
| ) |
Add data center performance and cost models
This PR adds two data center model pairs in
h2integrate/converters/data_center/. Both produce thecompute_loadcommodity (MW) and calculate the electricity and water a data center needs to serve a compute load profile.DataCenterPerformanceModelDataCenterCostModelDataCenterPUEWUEPerformanceModelDataCenterPUEWUECostModelThe PUE/WUE model takes PUE and WUE in one of three ways:
pueandwue.climate_zone. PUE/WUE are then looked up from the climate- and technology-specific estimates of Lei and Masanet (2022) and Lei, Shehabi, and Masanet (2025). The table covers 12 cooling configurations (see below), each with an efficient (5th-percentile) and an inefficient (95th-percentile) value.latitude/longitudeusingeeweather.If
cooling_configurationisn't set, the model picks the configuration with the lowest PUE, or the lowest WUE whenoptimize_for: wue. The choice can be limited by size category usingsize_sqft.Example
examples/38_data_center/runs both model pairs: a grid-connected data center with an hourly AI training workload (compute_load_profile.yaml).Table 1: Cooling configurations available in data center model
References:
Note: GitHub Copilot was used in the development of this PR.
Section 1: Type of Contribution
Section 2: Draft PR Checklist
TODO:
PR XYZplaceholder inCHANGELOG.mdwith this PR's numberType of Reviewer Feedback Requested (on Draft PR)
Structural feedback: Should the PUE/WUE lookup data (
pue_wue_data/pue_wue_data.csv) and theeeweatherclimate-zone lookup live in the converter folder, or somewhere shared?Other feedback: The PUE/WUE cost model has
electricity_rateandwater_rate. When electricity and water are bought through separate techs (grid_buy,water_feedstock), those rates count the same costs a second time. The example sets them to 0. Should the rates stay?Section 3: General PR Checklist
docs/files are up-to-date, or added when necessaryCHANGELOG.md"A complete thought. [PR XYZ]((https://github.com/NatLabRockies/H2Integrate/pull/XYZ)", where
XYZshould be replaced with the actual number.Section 4: Related Issues
N/A
Section 5: Impacted Areas of the Software
Section 5.1: New Files
h2integrate/converters/data_center/__init__.pyh2integrate/converters/data_center/data_center.pyDataCenterPerformanceModelConfig,DataCenterPerformanceModel: compute load capped at capacity; electricity demand from compute efficiency and cooling load ratio; water demand per MWh.DataCenterCostModelConfig,DataCenterCostModel: CapEx, fixed OpEx, and variable OpEx per MWh.DataCenterPUEWUEPerformanceModelConfig,DataCenterPUEWUEPerformanceModel: facility power and water from PUE and WUE.determine_iecc_climate_zone: infers the IECC climate zone from the site lat/lon usingeeweather.determine_pue_wue_by_climate_zone,_classify_size: look up PUE/WUE by climate zone, cooling configuration, efficiency level, and size category.DataCenterPUEWUECostModelConfig,DataCenterPUEWUECostModel: CapEx, fixed OpEx, and electricity and water costs.h2integrate/converters/data_center/pue_wue_data/pue_wue_data.csvh2integrate/converters/data_center/test/__init__.py,h2integrate/converters/data_center/test/test_data_center_models.pyexamples/38_data_center/(run_data_center.py,driver_config.yaml,plant_config.yaml,plant_config_pue_wue.yaml,tech_config.yaml,tech_config_pue_wue.yaml,data_center.yaml,data_center_pue_wue.yaml,compute_load_profile.yaml)Section 5.2: Modified Files
examples/test/test_all_examples.pytest_data_center_example: runs both configurations in example 38 and checks key outputs.pyproject.tomleeweatherdependency, which the climate-zone lookup needs.Section 6: Additional Supporting Information
eeweatheris only used when PUE/WUE andclimate_zoneare both omitted and the climate zone is inferred from the site lat/lon.electricity_rateandwater_rateare set to 0 because electricity and water are bought throughgrid_buyandwater_feedstock. The docs page explains this.Section 7: Test Results, if applicable
Example 38 LCOC: $190.79 (simple model) and $169.45 (PUE/WUE model).
Section 8 (Optional): New Model Checklist
docs/developer_guide/coding_guidelines.md{ModelName}Config, where{ModelName}is the complete model class name registered insupported_models.pyattrsclass to define theConfigto load in attributes for the modelBaseConfigorCostModelBaseConfiginitialize()method,setup()method,compute()methodPerformanceModelBaseClassandCostModelBaseClass__init__.pyfile to ensure it is properly imported and used insupported_models.pysupported_models.pytest_all_examples.pydocs/section_toc.ymldocs/_static/class_hierarchy.htmlby doing one of the below options:docsfolder and runsh build_book.shgenerate_class_hierarchy.pyto update the class hierarchy diagram indocs/developer_guide/class_structure.md. You may need to delete the_autosummaryfolder before doing this.