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

Add model for controlled distribution loop mass flow rate #594

Merged
merged 3 commits into from Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cspell.json
Expand Up @@ -8,6 +8,8 @@
"cvrmsd",
"dassl",
"dymola",
"endfor",
"endraw",
"energyplus",
"GDHC",
"HVAC",
Expand Down
Expand Up @@ -75,14 +75,15 @@ Sommer T., Sulzer M., Wetter M., Sotnikov A., Mennel S., Stettler C.
and cooling.</i>
Energy, Volume 199, 15 May 2020, 117418.
</p>
</html>", revisions="<html>
</html>", revisions="<html>
<ul>
<li>
April 12, 2023, by Nicholas Long:<br/>
Templatized for direct use in GMT with n-building connectors.<br/>
Changes include: removing dymola run command tied to MBL path, adding
a constant for borehole field mass flow rate (no longer tied to main
distribution loop).
</li>
<li>
February 23, 2021, by Antoine Gautier:<br/>
Refactored with base classes from the <code>DHC</code> package.<br/>
Expand Down
@@ -0,0 +1,115 @@
within {{ data['project_name'] }}.Districts;{% raw %}
model district
"Series connection with variable district water mass flow rate"
extends
PartialSeries(redeclare
Buildings.Experimental.DHC.Loads.Combined.BuildingTimeSeriesWithETS
bui[nBui](final filNam=filNam), datDes(
{% endraw %}nBui={{ data['building_load_files'] | count }},
mPumDis_flow_nominal={{ data['max_flow_rate'] }},
mPla_flow_nominal={{ data['max_flow_rate'] }},
mSto_flow_nominal={{ data['max_flow_rate'] / 10 }},
dp_length_nominal=250,{% raw %}
epsPla=0.935));
parameter String filNam[nBui]={
{% endraw %}{% for building in data['building_load_files'] %}
"{{ building }}"{% if not loop.last %},{% endif %}
{% endfor %}{% raw %}}
"Library paths of the files with thermal loads as time series";
Modelica.Blocks.Sources.Constant masFloDisPla(
k=datDes.mPla_flow_nominal)
"District water flow rate to plant"
annotation (Placement(transformation(extent={{-250,10},{-230,30}})));
Buildings.Controls.OBC.CDL.Continuous.Sources.Constant THotWatSupSet[nBui](
k=fill(63 + 273.15, nBui))
"Hot water supply temperature set point"
annotation (Placement(transformation(extent={{-190,170},{-170,190}})));
Buildings.Controls.OBC.CDL.Continuous.Sources.Constant TColWat[nBui](
k=fill(15 + 273.15, nBui))
"Cold water temperature"
annotation (Placement(transformation(extent={{-160,150},{-140,170}})));
Buildings.Experimental.DHC.Networks.Controls.MainPump conPum(
nMix=nBui,
nSou=2,
TMin=279.15,
TMax=290.15,
use_temperatureShift=false) "Main pump controller"
annotation (Placement(transformation(extent={{-280,-70},{-260,-50}})));
Buildings.Controls.OBC.CDL.Continuous.MultiplyByParameter gai(k=datDes.mPumDis_flow_nominal)
"Scale with nominal mass flow rate"
annotation (Placement(transformation(extent={{-240,-70},{-220,-50}})));
equation
connect(masFloDisPla.y, pla.mPum_flow) annotation (Line(points={{-229,20},{
-184,20},{-184,4.66667},{-161.333,4.66667}},
color={0,0,127}));
connect(THotWatSupSet.y, bui.THotWatSupSet) annotation (Line(points={{-168,
180},{-40,180},{-40,183},{-12,183}}, color={0,0,127}));
connect(TColWat.y, bui.TColWat) annotation (Line(points={{-138,160},{-40,160},
{-40,164},{-8,164},{-8,168}},
color={0,0,127}));
connect(pumDis.m_flow_in, gai.y)
annotation (Line(points={{68,-60},{-218,-60}},
color={0,0,127}));
connect(conPum.y, gai.u)
annotation (Line(points={{-258,-60},{-242,-60}},
color={0,0,127}));
connect(dis.TOut, conPum.TMix) annotation (Line(points={{22,134},{30,134},{30,
120},{-300,120},{-300,-54},{-282,-54}},
color={0,0,127}));
connect(TDisWatRet.T, conPum.TSouIn[1]) annotation (Line(points={{69,0},{60,0},
{60,80},{-304,80},{-304,-61},{-282,-61}},
color={0,0,127}));
connect(TDisWatBorLvg.T, conPum.TSouIn[2]) annotation (Line(points={{-91,-40},
{-290,-40},{-290,-58},{-282,-58},{-282,-59}},
color={0,0,127}));
connect(TDisWatBorLvg.T, conPum.TSouOut[1]) annotation (Line(points={{-91,-40},
{-290,-40},{-290,-67},{-282,-67}}, color={0,0,127}));
connect(TDisWatSup.T, conPum.TSouOut[2]) annotation (Line(points={{-91,20},{-100,
20},{-100,60},{-296,60},{-296,-65},{-282,-65}},
color={0,0,127}));
connect(gai.y, pumSto.m_flow_in) annotation (Line(points={{-218,-60},{-180,-60},
{-180,-68}}, color={0,0,127}));
annotation (
Diagram(
coordinateSystem(preserveAspectRatio=false, extent={{-360,-260},{360,260}})),
experiment(
StopTime=31536000,
Interval=300,
Tolerance=1e-06,
__Dymola_Algorithm="Dassl"),
Documentation(info="<html>
<p>
This model is identical to
<a href=\"Buildings.Experimental.DHC.Examples.Combined.SeriesConstantFlow\">
Buildings.Experimental.DHC.Examples.Combined.SeriesConstantFlow</a>
except for the pipe diameter and the control of the main circulation pump.
Rather than having a constant mass flow rate, the mass flow rate is varied
based on the mixing temperatures after each agent.
If these mixing temperatures are sufficiently far away from the minimum or maximum
allowed loop temperature, then the mass flow rate is reduced to save pump energy.
</p>
</html>", revisions="<html>
<ul>
<li>
November 1, 2023, by Nicholas Long:<br/>
Templatized for direct use in GMT with n-building connectors.<br/>
Changes include: removing dymola run command tied to MBL path, adding
a constant for borehole field mass flow rate (no longer tied to main
distribution loop).
</li>
<li>
February 23, 2021, by Antoine Gautier:<br/>
Refactored with base classes from the <code>DHC</code> package.<br/>
This is for
<a href=\"https://github.com/lbl-srg/modelica-buildings/issues/1769\">
issue 1769</a>.
</li>
<li>
January 12, 2020, by Michael Wetter:<br/>
Added documentation.
</li>
</ul>
</html>"),
__Dymola_experimentSetupOutput);
end district;
{% endraw %}
@@ -0,0 +1,101 @@
import shutil
from pathlib import Path

from modelica_builder.modelica_mos_file import ModelicaMOS
from modelica_builder.package_parser import PackageParser

from geojson_modelica_translator.modelica.simple_gmt_base import SimpleGMTBase
from geojson_modelica_translator.scaffold import Scaffold


class DHC5GWasteHeatAndGHXVariable(SimpleGMTBase):
"""Generates a full Modelica package with the DHC 5G waste heat and GHX model with a controlled variable speed distribution pump."""

def __init__(self, system_parameters):
self.system_parameters = system_parameters
self.template_dir = Path(__file__).parent
super().__init__(self.system_parameters, self.template_dir)

def build_from_template(self, output_dir: Path, project_name: str) -> None:
"""This is a bit past being a simple template as it is exporting an entire scaffolded package
that can be loaded and simulated in Modelica. The scaffold is very specific to DES.

Args:
output_dir (Path): directory to save the package to (without the project name)
project_name (str, optional): The name of the project which is used in the Scaffold object.
"""
template_data = {
'project_name': project_name,
'save_file_name': 'district',
'building_load_files': []
}

# create the directory structure
scaffold = Scaffold(output_dir, project_name=project_name)
scaffold.create(ignore_paths=['Loads', 'Networks', 'Plants', 'Substations'])

# create the root package
package = PackageParser.new_from_template(scaffold.project_path, project_name, order=[])
package.add_model('Districts')

# create the district package with the template_data from above
files_to_copy = []

# 1: grab all of the time series files and place them in the proper location
for building in self.system_parameters.get_param("$.buildings[?load_model=time_series]"):
building_load_file = Path(building['load_model_parameters']['time_series']['filepath'])
files_to_copy.append({
"orig_file": building_load_file,
"geojson_id": building['geojson_id'],
"save_path": f"{scaffold.districts_path.resources_dir}/{building['geojson_id']}",
"save_filename": building_load_file.name
})

# 2: Copy the files to the appropriate location and ensure uniqueness by putting into a unique directory
# (since OpenStudio creates all files with modelica.mos)
total_heating_load = 0
total_cooling_load = 0
total_swh_load = 0
for file_to_copy in files_to_copy:
# create the path if it doesn't exist
Path(file_to_copy['save_path']).mkdir(parents=True, exist_ok=True)
save_filename = f"{file_to_copy['save_path']}/{file_to_copy['save_filename']}"
shutil.copy(file_to_copy['orig_file'], save_filename)

# 3: If the file is an MOS file, and it has the Peak water heating load set to zero, then set it to a minimum value
# Also, store the total heating, cooling, and water loads which will be used for sizing.
mos_file = ModelicaMOS(save_filename)
total_heating_load += mos_file.retrieve_header_variable_value('Peak space heating load', cast_type=float)
total_cooling_load += mos_file.retrieve_header_variable_value('Peak space cooling load', cast_type=float)
peak_water = mos_file.retrieve_header_variable_value('Peak water heating load', cast_type=float)
total_swh_load += peak_water
if peak_water == 0:
peak_heat = mos_file.retrieve_header_variable_value('Peak space heating load', cast_type=float)
peak_swh = max(peak_heat / 10, 5000)

mos_file.replace_header_variable_value('Peak water heating load', peak_swh)
mos_file.save()

# 4: Add the path to the param data with Modelica friendly path names
rel_path_name = f"{project_name}/{scaffold.districts_path.resources_relative_dir}/{file_to_copy['geojson_id']}/{file_to_copy['save_filename']}"
template_data['building_load_files'].append(f"modelica://{rel_path_name}") # type: ignore

# 5: Calculate the mass flow rates (kg/s) for the heating and cooling networks peak load (in Watts)
# (assuming 5C delta T [since 5G] and 4.18 Cp (kJ/kgK)). Add 1.5x the peak for oversizing
delta_t = 5
heating_flow_rate = 1.5 * total_heating_load / (1000 * delta_t * 4.18)
cooling_flow_rate = 1.5 * total_cooling_load / (1000 * delta_t * 4.18)
swh_flow_rate = 1.5 * total_swh_load / (1000 * delta_t * 4.18)

template_data['max_flow_rate'] = round(max(heating_flow_rate, cooling_flow_rate, swh_flow_rate), 3) # type: ignore

# 6: generate the modelica files from the template
self.to_modelica(output_dir=Path(scaffold.districts_path.files_dir),
model_name='DHC_5G_waste_heat_GHX_variable',
param_data=template_data,
save_file_name='district.mo',
generate_package=True,
partial_files={'DHC_5G_partial': 'PartialSeries'})

# 7: save the root package.mo
package.save()
Expand Up @@ -711,12 +711,14 @@ def csv_to_sys_param(self,

:kwargs (optional):
- relative_path: Path, set the paths (time series files, weather file, etc) relate to `relative_path`
- skip_weather_download: Boolean, set to True to not download the weather file, defaults to False
:return None, file created and saved to user-specified location


"""
self.sys_param_filename = sys_param_filename
self.rel_path = kwargs.get('relative_path', None)
skip_weather_download = kwargs.get('skip_weather_download', False)

if model_type == 'time_series':
# TODO: delineate between time_series and time_series_massflow_rate
Expand Down Expand Up @@ -766,13 +768,15 @@ def csv_to_sys_param(self,
building_ids.append(feature['properties']['id'])

# Check if the EPW weatherfile exists, if not, try to download
if not weather_path.exists():
self.download_weatherfile(weather_path.name, weather_path.parent)
if not skip_weather_download:
if not weather_path.exists():
self.download_weatherfile(weather_path.name, weather_path.parent)

# also download the MOS weatherfile -- this is the file that will be set in the sys param file
mos_weather_path = weather_path.with_suffix('.mos')
if not mos_weather_path.exists():
self.download_weatherfile(mos_weather_path.name, mos_weather_path.parent)
if not skip_weather_download:
if not mos_weather_path.exists():
self.download_weatherfile(mos_weather_path.name, mos_weather_path.parent)

# Make sys_param template entries for each feature_id
building_list = []
Expand Down
71 changes: 70 additions & 1 deletion tests/GMT_Lib/test_gmt_lib_des.py
Expand Up @@ -11,6 +11,9 @@
from geojson_modelica_translator.modelica.GMT_Lib.DHC.DHC_5G_waste_heat_GHX import (
DHC5GWasteHeatAndGHX
)
from geojson_modelica_translator.modelica.GMT_Lib.DHC.DHC_5G_waste_heat_GHX_variable import (
DHC5GWasteHeatAndGHXVariable
)
from geojson_modelica_translator.modelica.modelica_runner import ModelicaRunner
from geojson_modelica_translator.system_parameters.system_parameters import (
SystemParameters
Expand Down Expand Up @@ -61,7 +64,7 @@ def test_5G_des_waste_heat_and_ghx(self):
run_path=package_output_dir / package_name,
start_time=0, stop_time=86400)

# assert success is True
assert success is True

@pytest.mark.dymola
def test_5G_des_waste_heat_and_ghx_dymola(self):
Expand Down Expand Up @@ -96,3 +99,69 @@ def test_5G_des_waste_heat_and_ghx_dymola(self):
)

assert success is True

@pytest.mark.simulation
def test_5G_des_waste_heat_and_ghx_variable(self):
# -- Setup
package_output_dir = PARENT_DIR / 'output'
package_name = 'DES_5G_Variable'
if (package_output_dir / package_name).exists():
rmtree(package_output_dir / package_name)
sys_params = SystemParameters(DES_PARAMS)

# -- Act
cpv = DHC5GWasteHeatAndGHXVariable(sys_params)
cpv.build_from_template(package_output_dir, package_name)

# -- Assert
# Did the mofile get created?
assert linecount(package_output_dir / package_name / 'Districts' / 'district.mo') > 20

# Test to make sure that a zero SWH peak is set to a minimum value.
# Otherwise, Modelica will error out.
with open(package_output_dir / package_name / 'Resources' / 'Data' / 'Districts' / '8' / 'B11.mos', 'r') as f:
assert '#Peak water heating load = 7714.5 Watts' in f.read()

# # -- Act - with simulation
runner = ModelicaRunner()
success, _ = runner.run_in_docker(
'compile_and_run', f"{package_name}.Districts.district",
file_to_load=package_output_dir / package_name / 'package.mo',
run_path=package_output_dir / package_name,
start_time=0, stop_time=86400)

assert success is True

@pytest.mark.dymola
def test_5G_des_waste_heat_and_ghx_variable_dymola(self):
# -- Setup
package_output_dir = PARENT_DIR / 'output'
package_name = 'DES_5G_Variable_Dymola'
if (package_output_dir / package_name).exists():
rmtree(package_output_dir / package_name)
sys_params = SystemParameters(DES_PARAMS)

# -- Act
cpv = DHC5GWasteHeatAndGHXVariable(sys_params)
cpv.build_from_template(package_output_dir, package_name)

# -- Assert
# Did the mofile get created?
assert linecount(package_output_dir / package_name / 'Districts' / 'district.mo') > 20

# Test to make sure that a zero SWH peak is set to a minimum value.
# Otherwise, Modelica will error out.
with open(package_output_dir / package_name / 'Resources' / 'Data' / 'Districts' / '8' / 'B11.mos', 'r') as f:
assert '#Peak water heating load = 7714.5 Watts' in f.read()

# # -- Act - with simulation
runner = ModelicaRunner()
success, _ = runner.run_in_dymola(
'simulate', f"{package_name}.Districts.district",
file_to_load=package_output_dir / package_name,
run_path=package_output_dir / package_name,
start_time=0, stop_time=86400, step_size=300,
debug=True
)

assert success is True