Question-and-Answer Resource for the Building Energy Modeling Community
Get started with the Help page
Ask Your Question
1

Creation of a Water-to-Water Heat Pump in OpenStudio

asked 2025-05-27 15:06:22 -0500

annieliesemari's avatar

updated 2025-05-28 09:08:48 -0500

Hello all!

I am trying to add a water-to-water heat pump using a measure to OpenStudio files. I have not been able to find one that exists already, so I somewhat started from scratch.

The file deletes the current HVAC system, then adds a cooling heat pump, heating heat pump, hot water loop, chilled water loop, a source loop, and then connects fan coils to the zones.

I kept receiving errors about missing control names when reaching the EnergyPlus part of the simulation - "* Severe * <root>[PlantEquipmentOperationSchemes][Source Loop Operation Schemes] - Missing required property 'control_scheme_1_name'." So I tried to fix that and then I got errors on how I was defining loops in OpenStudio.

I've attached my code here. Any idea on what I might be doing wrong? I'm running OpenStudio 3.9, the OS 1.9.0rc-1 application, and EnergyPlus 24-2.

class AddReversibleWWHPSystem < OpenStudio::Measure::ModelMeasure

def name return 'Add Reversible WWHP System and Remove Existing HVAC' end

def description return 'Removes existing HVAC and adds a reversible water-to-water heat pump system with hot, chilled, and shared source loops.' end

def modeler_description return 'Installs heating and cooling WWHPs on separate loops sharing a source loop and serves all zones with four-pipe fan coils.' end

def arguments(model) return OpenStudio::Measure::OSArgumentVector.new end

def run(model, runner, user_arguments) super(model, runner, user_arguments)

runner.registerInitialCondition("Model has #{model.getThermalZones.size} thermal zones.")

# Remove existing HVAC
model.getAirLoopHVACs.each(&:remove)
model.getPlantLoops.each(&:remove)
model.getZoneHVACComponents.each(&:remove)
runner.registerInfo('Removed all existing HVAC systems.')

# -------------------------------
# Hot Water Loop
# -------------------------------
heating_loop = OpenStudio::Model::PlantLoop.new(model)
heating_loop.setName('Hot Water Loop')
hw_inlet = heating_loop.supplyInletNode
hw_outlet = heating_loop.supplyOutletNode

hw_sched = OpenStudio::Model::ScheduleRuleset.new(model)
hw_sched.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 60.0)
hw_spm = OpenStudio::Model::SetpointManagerScheduled.new(model, hw_sched)
hw_spm.addToNode(hw_outlet)

# -------------------------------
# Chilled Water Loop
# -------------------------------
cooling_loop = OpenStudio::Model::PlantLoop.new(model)
cooling_loop.setName('Chilled Water Loop')
cw_inlet = cooling_loop.supplyInletNode
cw_outlet = cooling_loop.supplyOutletNode

cw_sched = OpenStudio::Model::ScheduleRuleset.new(model)
cw_sched.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 7.0)
cw_spm = OpenStudio::Model::SetpointManagerScheduled.new(model, cw_sched)
cw_spm.addToNode(cw_outlet)

# -------------------------------
# Source Loop
# -------------------------------
source_loop = OpenStudio::Model::PlantLoop.new(model)
source_loop.setName('Source Loop')
src_inlet = source_loop.supplyInletNode
src_outlet = source_loop.supplyOutletNode

src_sched = OpenStudio::Model::ScheduleRuleset.new(model)
src_sched.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), 15.0)
src_spm = OpenStudio::Model::SetpointManagerScheduled.new(model, src_sched)
src_spm.addToNode(src_outlet)

# -------------------------------
# Heat Pumps
# -------------------------------
heating_hp = OpenStudio::Model::HeatPumpWaterToWaterEquationFitHeating.new(model)
heating_hp.setName('WWHP Heating')
heating_hp.addToNode(hw_inlet)

cooling_hp = OpenStudio::Model::HeatPumpWaterToWaterEquationFitCooling.new(model)
cooling_hp.setName('WWHP Cooling')
cooling_hp.addToNode(cw_inlet)

# -------------------------------
# Add to Loops
# -------------------------------
source_loop.addDemandBranchForComponent(heating_hp)
source_loop.addDemandBranchForComponent(cooling_hp)

heating_loop.addSupplyBranchForComponent(heating_hp)
cooling_loop.addSupplyBranchForComponent(cooling_hp)

# -------------------------------
# Fan Coil Units in Zones
# -------------------------------
model.getThermalZones.each do |zone|
  sched = model.alwaysOnDiscreteSchedule
  fan = OpenStudio::Model::FanOnOff.new(model, sched)
  cc = OpenStudio::Model::CoilCoolingWater.new(model)
  hc = OpenStudio::Model::CoilHeatingWater.new(model)

  fcu = OpenStudio::Model::ZoneHVACFourPipeFanCoil.new(model, sched, fan, cc, hc)
  fcu.setName("Fan Coil - #{zone.name}")
  fcu.addToThermalZone(zone)

  cooling_loop.addDemandBranchForComponent(cc)
  heating_loop.addDemandBranchForComponent ...
(more)
edit retag flag offensive close merge delete

Comments

Also, would it be easier to just go into the osm, delete the current HVAC, and then add in WSHPs? I just have 123 zones in my building, so I'm not sure how feasible that would actually be. I would like to treat it like one heating and one cooling HP serves the whole building.

annieliesemari's avatar annieliesemari  ( 2025-05-27 15:58:03 -0500 )edit

1 Answer

Sort by ยป oldest newest most voted
2

answered 2025-05-28 03:05:47 -0500

TL;DR: You don't have to define your own PlantEquipmentOperation schemes. The issue is that you do not have any supply equipment on the source loop...

You also have a variety of other problems.

  1. Remove heating_hp.addToNode(hw_inlet) and cooling_hp.addToNode(cw_inlet), you are already calling

    heating_loop.addSupplyBranchForComponent(heating_hp)
    cooling_loop.addSupplyBranchForComponent(cooling_hp)
    
  2. You are missing setting the SizingPlant parameters such as design loop temperature, design temperature difference, and loop type.

  3. You are missing a pump on all loops.

  4. You probably want to set both HeatPumpWaterToWaterEquationFit Heating/Cooling as being Companions to each other. And you probably want to set some parameters on them (COP, might end up needing to hardsize some stuff)


Working example

Here is a working example that shows the API, I repurposed this: https://github.com/NREL/OpenStudio-re...

###############################################################################
#                       M A K E    S O M E    L O O P S                       #
###############################################################################

############### HEATING / COOLING (LOAD) LOOPS  ###############

hw_loop = OpenStudio::Model::PlantLoop.new(model)
hw_loop.setName('Hot Water Loop Air Source')
hw_loop.setMinimumLoopTemperature(10)
hw_temp_f = 140
hw_delta_t_r = 20 # 20F delta-T
hw_temp_c = OpenStudio.convert(hw_temp_f, 'F', 'C').get
hw_delta_t_k = OpenStudio.convert(hw_delta_t_r, 'R', 'K').get
hw_temp_sch = OpenStudio::Model::ScheduleRuleset.new(model)
hw_temp_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), hw_temp_c)
hw_stpt_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, hw_temp_sch)
hw_stpt_manager.addToNode(hw_loop.supplyOutletNode)
sizing_plant = hw_loop.sizingPlant
sizing_plant.setLoopType('Heating')
sizing_plant.setDesignLoopExitTemperature(hw_temp_c)
sizing_plant.setLoopDesignTemperatureDifference(hw_delta_t_k)
# Pump
hw_pump = OpenStudio::Model::PumpVariableSpeed.new(model)
hw_pump_head_ft_h2o = 60.0
hw_pump_head_press_pa = OpenStudio.convert(hw_pump_head_ft_h2o, 'ftH_{2}O', 'Pa').get
hw_pump.setRatedPumpHead(hw_pump_head_press_pa)
hw_pump.setPumpControlType('Intermittent')
hw_pump.addToNode(hw_loop.supplyInletNode)

chw_loop = OpenStudio::Model::PlantLoop.new(model)
chw_loop.setName('Chilled Water Loop Air Source')
chw_loop.setMaximumLoopTemperature(98)
chw_loop.setMinimumLoopTemperature(1)
chw_temp_f = 44
chw_delta_t_r = 10.1 # 10.1F delta-T
chw_temp_c = OpenStudio.convert(chw_temp_f, 'F', 'C').get
chw_delta_t_k = OpenStudio.convert(chw_delta_t_r, 'R', 'K').get
chw_temp_sch = OpenStudio::Model::ScheduleRuleset.new(model)
chw_temp_sch.defaultDaySchedule.addValue(OpenStudio::Time.new(0, 24, 0, 0), chw_temp_c)
chw_stpt_manager = OpenStudio::Model::SetpointManagerScheduled.new(model, chw_temp_sch)
chw_stpt_manager.addToNode(chw_loop.supplyOutletNode)
sizing_plant = chw_loop.sizingPlant
sizing_plant.setLoopType('Cooling')
sizing_plant.setDesignLoopExitTemperature(chw_temp_c)
sizing_plant.setLoopDesignTemperatureDifference(chw_delta_t_k)
# Pump
pri_chw_pump = OpenStudio::Model::HeaderedPumpsConstantSpeed.new(model)
pri_chw_pump.setName('Chilled Water Loop Primary Pump Air Source')
pri_chw_pump_head_ft_h2o = 15
pri_chw_pump_head_press_pa = OpenStudio.convert(pri_chw_pump_head_ft_h2o, 'ftH_{2}O', 'Pa').get
pri_chw_pump.setRatedPumpHead(pri_chw_pump_head_press_pa)
pri_chw_pump.setMotorEfficiency(0.9)
pri_chw_pump.setPumpControlType('Intermittent')
pri_chw_pump.addToNode(chw_loop.supplyInletNode)

############   C O N D E N S E R    S I D E  ##################

cw_loop = OpenStudio::Model::PlantLoop.new(model)
cw_loop.setName('Condenser Water Loop Water Source')
cw_loop.setMaximumLoopTemperature(100)
cw_loop.setMinimumLoopTemperature(3)
cw_loop.setPlantLoopVolume(1.0)
cw_temp_sizing_f = 102 # CW sized to deliver 102F
cw_delta_t_r = 10 # 10F delta-T
cw_temp_sizing_c = OpenStudio.convert(cw_temp_sizing_f, 'F', 'C').get
cw_delta_t_k = OpenStudio.convert(cw_delta_t_r, 'R', 'K').get

sizing_plant = cw_loop.sizingPlant
sizing_plant.setLoopType('Condenser')
sizing_plant.setDesignLoopExitTemperature(cw_temp_sizing_c)
sizing_plant.setLoopDesignTemperatureDifference(cw_delta_t_k)

cw_pump = OpenStudio::Model::PumpVariableSpeed.new(model)
cw_pump.setName('Condenser Water Loop Pump Water Source')
cw_pump_head_ft_h2o = 60.0
cw_pump_head_press_pa = OpenStudio.convert(cw_pump_head_ft_h2o, 'ftH_{2}O', 'Pa').get
cw_pump.setRatedPumpHead(cw_pump_head_press_pa)
cw_pump.addToNode(cw_loop.supplyInletNode)

groundHX = OpenStudio::Model::GroundHeatExchangerVertical.new(model)
# THe default isn't the same as the E+ example file (and ...
(more)
edit flag offensive delete link more

Comments

Thank you so much for this! One more question with this version. Since I'm working with a district energy system and using a central plant, what can I change in the ground heat exchanger? I'm assuming this is a geothermal vertical borehole field, which my model does not have.

annieliesemari's avatar annieliesemari  ( 2025-06-02 12:05:58 -0500 )edit
1

Look at the DistrictCooling / DistrictHeating objects

Julien Marrec's avatar Julien Marrec  ( 2025-06-03 04:06:23 -0500 )edit

Your Answer

Please start posting anonymously - your entry will be published after you log in or create a new account.

Add Answer

Training Workshops

Question Tools

1 follower

Stats

Asked: 2025-05-27 15:06:22 -0500

Seen: 136 times

Last updated: May 28