Skip to content

Add data center models - #929

Open
bayc wants to merge 11 commits into
NatLabRockies:developfrom
bayc:feature/data_center
Open

bayc wants to merge 11 commits into
NatLabRockies:developfrom
bayc:feature/data_center

Conversation

@bayc

@bayc bayc commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator

Add data center performance and cost models

This PR adds two data center model pairs in h2integrate/converters/data_center/. Both produce the compute_load commodity (MW) and calculate the electricity and water a data center needs to serve a compute load profile.

Model Type Description
DataCenterPerformanceModel Performance Electricity demand from a compute electrical efficiency and a cooling load ratio
DataCenterCostModel Cost CapEx, fixed OpEx, and variable OpEx per MWh of compute load
DataCenterPUEWUEPerformanceModel Performance Electricity and water demand from Power Usage Effectiveness (PUE) and Water Usage Effectiveness (WUE)
DataCenterPUEWUECostModel Cost CapEx, fixed OpEx, and electricity and water costs

The PUE/WUE model takes PUE and WUE in one of three ways:

  1. Directly: set pue and wue.
  2. By IECC climate zone: set 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.
  3. From the site location: the climate zone is inferred from the plant latitude/longitude using eeweather.

If cooling_configuration isn't set, the model picks the configuration with the lowest PUE, or the lowest WUE when optimize_for: wue. The choice can be limited by size category using size_sqft.

data_center:
  performance_model:
    model: DataCenterPUEWUEPerformanceModel
  cost_model:
    model: DataCenterPUEWUECostModel
  model_inputs:
    shared_parameters:
      system_capacity_mw: 80.
    performance_parameters:
      compute_it_workload_profile: 60.0
      cooling_configuration: 1
      efficiency_level: efficient
    cost_parameters:
      cost_year: 2026
      capex_per_mw: 4.0625e+6
      fixed_opex_per_mw_per_year: 1.5e+5
      electricity_rate: 0.0
      water_rate: 0.0

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

Case Size category Cooling configuration
1 Large-scale Airside economizer + adiabatic cooling + (water-cooled chiller)
2 Large-scale Waterside economizer + (water-cooled chiller)
3 Midsize Airside economizer + (water-cooled chiller)
4 Midsize Waterside economizer + (water-cooled chiller)
5 Midsize Water-cooled chiller
6 Midsize Airside economizer + (air-cooled chiller)
7 Midsize Air-cooled chiller
8 Small Water-cooled chiller
9 Small Air-cooled chiller
10 Small Direct expansion system
11 Large-scale or midsize Direct-to-chip liquid loop + dry cooling + adiabatic cooling + (air-cooled chiller)
12 Large-scale or midsize Direct-to-chip liquid loop + waterside economizer + (water-cooled chiller)

References:

  • Lei, N., & Masanet, E. (2022). Climate-and technology-specific PUE and WUE estimations for US data centers using a hybrid statistical and thermodynamics-based approach. Resources, Conservation and Recycling, 182, 106323.
  • Lei, N., Lu, J., Shehabi, A., & Masanet, E. (2025). The water use of data center workloads: A review and assessment of key determinants. Resources, Conservation and Recycling, 219, 108310.

Note: GitHub Copilot was used in the development of this PR.

Section 1: Type of Contribution

  • Feature Enhancement
    • Framework
    • New Model
    • Updated Model
    • Tools/Utilities
    • Other (please describe):
  • Bug Fix
  • Documentation Update
  • CI Changes
  • Other (please describe):

Section 2: Draft PR Checklist

  • Open draft PR
  • Describe the feature that will be added
  • Fill out TODO list steps
  • Describe requested feedback from reviewers on draft PR
  • Complete Section 8: New Model Checklist (if applicable)

TODO:

  • Replace the PR XYZ placeholder in CHANGELOG.md with this PR's number
  • Rebuild the docs and regenerate the class hierarchy diagram

Type of Reviewer Feedback Requested (on Draft PR)

Structural feedback: Should the PUE/WUE lookup data (pue_wue_data/pue_wue_data.csv) and the eeweather climate-zone lookup live in the converter folder, or somewhere shared?

Other feedback: The PUE/WUE cost model has electricity_rate and water_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

  • PR description thoroughly describes the new feature, bug fix, etc.
  • Added tests for new functionality or bug fixes
  • Tests pass (If not, and this is expected, please elaborate in the Section 6: Test Results)
  • Documentation
    • Docstrings are up-to-date
    • Related docs/ files are up-to-date, or added when necessary
    • Documentation has been rebuilt successfully
    • Examples have been updated (if applicable)
  • CHANGELOG.md
    • At least one complete sentence has been provided to describe the changes made in this PR
    • After the above, a hyperlink has been provided to the PR using the following format:
      "A complete thought. [PR XYZ]((https://github.com/NatLabRockies/H2Integrate/pull/XYZ)", where
      XYZ should 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__.py
    • Exports the four data center models.
  • h2integrate/converters/data_center/data_center.py
    • DataCenterPerformanceModelConfig, 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 using eeweather.
      • 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.csv
    • PUE/WUE estimates by climate zone and cooling configuration (Lei and Masanet, 2022).
  • h2integrate/converters/data_center/test/__init__.py, h2integrate/converters/data_center/test/test_data_center_models.py
    • Unit tests for both model pairs: performance, cost, climate-zone lookup, lat/lon lookup, and the error when site data is missing.
  • examples/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)
    • Grid-connected data center example that runs both model pairs with an hourly compute load profile.

Section 5.2: Modified Files

  • examples/test/test_all_examples.py
    • test_data_center_example: runs both configurations in example 38 and checks key outputs.
  • pyproject.toml
    • Adds the eeweather dependency, which the climate-zone lookup needs.

Section 6: Additional Supporting Information

  • New dependency: eeweather is only used when PUE/WUE and climate_zone are both omitted and the climate zone is inferred from the site lat/lon.
  • Double counting: in example 38, electricity_rate and water_rate are set to 0 because electricity and water are bought through grid_buy and water_feedstock. The docs page explains this.
  • Follow-on PR: a follow-up PR will add data center waste heat recovery, a heat pump model, and a district heating demand model.

Section 7: Test Results, if applicable

pytest h2integrate/converters/data_center/test examples/test/test_all_examples.py::test_data_center_example
10 passed

Example 38 LCOC: $190.79 (simple model) and $169.45 (PUE/WUE model).

Section 8 (Optional): New Model Checklist

  • Model Structure:
    • Follows established naming conventions outlined in docs/developer_guide/coding_guidelines.md
    • Configuration class is named {ModelName}Config, where {ModelName} is the complete model class name registered in supported_models.py
    • Used attrs class to define the Config to load in attributes for the model
      • If applicable: inherit from BaseConfig or CostModelBaseConfig
    • Added: initialize() method, setup() method, compute() method
      • If applicable: inherit from PerformanceModelBaseClass and CostModelBaseClass
  • Integration: Model has been properly integrated into H2Integrate
    • Add the new model to the appropriate __init__.py file to ensure it is properly imported and used in supported_models.py
    • Added to supported_models.py
  • Tests: Unit tests have been added for the new model
    • Pytest-style unit tests
    • Unit tests are in a "test" folder within the folder a new model was added to
    • If applicable, add integration tests
  • Example: If applicable, a working example demonstrating the new model has been created
    • Input file comments
    • Run file comments
    • Example has been tested and runs successfully in test_all_examples.py
  • Documentation:
    • Write docstrings using the Google style
    • Model documentation page added to the appropriate docs/ section
      • Path to model documentation page is added to the _toc.yml
    • Update the class hierarchy diagram docs/_static/class_hierarchy.html by doing one of the below options:
      • Navigate to docs folder and run sh build_book.sh
      • Run generate_class_hierarchy.py to update the class hierarchy diagram in docs/developer_guide/class_structure.md. You may need to delete the _autosummary folder before doing this.

bayc added 5 commits October 7, 2026 08:04
- 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
@bayc bayc self-assigned this Oct 13, 2026
@bayc bayc added the enhancement New feature or request label Oct 13, 2026
@bayc
bayc requested review from johnjasa and kbrunik October 13, 2026 19:26

@johnjasa johnjasa left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Comment thread docs/technology_models/data_center.md Outdated
Comment on lines +6 to +7
load profile. An example that runs both model pairs with an hourly AI training workload
is provided in `examples/38_data_center/`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could you please add where the training workload profile came from? Given all the questions we get!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done!

"electricity_in",
val=0.0,
shape=n_timesteps,
units="MW/h",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed!

Comment on lines +183 to +191
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"]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Normalized!

Comment on lines +660 to +666
if warnings:
self.logger.warning(
"Warnings encountered when selecting weather station for location (%s, %s): %s",
latitude,
longitude,
warnings,
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There's no logger attr. If this is reached, it fails on an AttributeError. I'd suggest raising a UserWarning

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Old habits die hard...

Comment on lines +952 to +955
opex = fixed_om + variable_om

outputs["CapEx"] = capex
outputs["OpEx"] = opex

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could you please break out the variable OpEx into VarOpEx? The GenericConverterCost is a good example of this

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done!

Comment on lines +350 to +354
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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

please add a _control_classifier for each performance model. I think "fixed" is the right option but it might be another one.

@kbrunik kbrunik left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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:

  1. I think the PUE/WUE file looks good where it's at, easy to see how it's related.
  2. 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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
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()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

delete commented out line

Comment on lines +753 to +765
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}'."
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This error could also be removed with use of the validators above

USD/(MW*year).
"""

electricity_rate: float = field(validator=validators.ge(0))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
desc="IT equipment rated capacity in MW",
desc="IT equipment rated capacity",

Comment on lines +892 to +897
self.add_input(
"capex_per_mw",
val=self.config.capex_per_mw,
units="USD/MW",
desc="Capital cost per MW of IT equipment capacity",
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
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",
)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants