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

Template initial microgrid subsystem example #569

Merged
merged 34 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
96df04d
pvsubsystem template and code to render
vtnate Jun 29, 2023
dfe568c
test to build pvsubsystem model from template
vtnate Jun 29, 2023
aafbd47
transformer template and code to render
vtnate Jun 30, 2023
97d7b35
hacking in some sample transformer data to the sys-param file
vtnate Jun 30, 2023
e9061f7
new tests for transformer, and simulation tests for previous components
vtnate Jun 30, 2023
26d8c15
add calculation for line power in line and pvsubsystem templates
vtnate Jul 14, 2023
4800f95
skip test_build_pv_subsystem because it is captured in the simulation…
vtnate Jul 14, 2023
ae88af4
add comments to pv_subsystem.mot for future improvements
vtnate Jul 17, 2023
be46295
Merge branch 'develop' into microgrid-subsystem
vtnate Jul 20, 2023
219330e
add todo to get ditto-reader info about transformers into the sys-par…
vtnate Jul 20, 2023
cd76ffc
add code to read transformer in- and out-going voltages from uo sdk
vtnate Jul 24, 2023
d8ef236
fix transformer template and code to use new variable names
vtnate Jul 24, 2023
1b5d82e
update test sys-param file with current variable names
vtnate Jul 24, 2023
3e76695
use .get() method for looking at opendss values
vtnate Jul 24, 2023
1e23031
use .get() method for other transformer properties as well
vtnate Jul 24, 2023
0fccc81
formatting instance file
vtnate Jul 26, 2023
14efb39
remove single 5g test file - district 5g test already exists
vtnate Jul 26, 2023
6929d61
unskip testing district 5g system
vtnate Jul 26, 2023
03eac00
add start/stop/step times to dhc test setup
vtnate Jul 26, 2023
d9a3ce6
re-skip the dhc simulation test
vtnate Jul 26, 2023
3521926
use appropriate cable in pv subsystem model
vtnate Jul 31, 2023
b2fd01f
Merge branch 'develop' into microgrid-subsystem
vtnate Aug 11, 2023
29ab725
Merge branch 'develop' into microgrid-subsystem
vtnate Aug 15, 2023
6706d0a
Merge branch 'develop' into microgrid-subsystem
vtnate Aug 18, 2023
7e17f36
Merge branch 'develop' into microgrid-subsystem
vtnate Aug 21, 2023
89b21e5
capacitor was confused with capacitive load. fixed capacitive load
vtnate Aug 25, 2023
f87389f
use pathlib instead of os.path when adding microgrid to sys-params
vtnate Aug 25, 2023
a6db5b1
tests for capacitive load model
vtnate Aug 25, 2023
db21fb6
`poetry update`
vtnate Aug 25, 2023
259bbd1
gix typo in transformer data for sys-param
vtnate Sep 14, 2023
67ce2bb
Merge branch 'develop' into microgrid-subsystem
vtnate Sep 14, 2023
7f9ead2
potential solution to poetry dependency installation failures
vtnate Sep 14, 2023
56b8ce9
Merge branch 'fix-poetry-ci-failures' into microgrid-subsystem
vtnate Sep 14, 2023
dc759d5
fix district heating and cooling test assert path
vtnate Sep 14, 2023
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 .github/workflows/ci.yml
Expand Up @@ -44,7 +44,9 @@ jobs:
# with:
# poetry-version: "1.5.1"
- name: Install dependencies with Poetry
# poetry install workaround sourced from https://github.com/python-poetry/poetry/issues/7611#issuecomment-1711443539
run: |
poetry self add setuptools
poetry --version
poetry install
- name: Install modelicafmt
Expand Down
Expand Up @@ -6,7 +6,7 @@
use_m_flow_in=true,
use_T_in=false,
{% if 'ghe_parameters' in sys_params.district_system['fifth_generation'] %}
T={{ sys_params.district_system.fifth_generation.ghe_parameters.soil.undisturbed_temp}}+273.15,
T={{ sys_params.district_system.fifth_generation.ghe_parameters.soil.undisturbed_temp }}+273.15,
{% else %}
T={{ sys_params.district_system.fifth_generation.central_heating_plant_parameters.temp_setpoint_hhw }}+273.15,
{% endif %}
Expand Down
@@ -0,0 +1,41 @@
within;
model ACACTransformer
"Isolated transformer template model for GMT level 1 testing"
parameter Modelica.Units.SI.Voltage VHigh = {{data["tx_incoming_voltage"]}} "Rms voltage on side 1 of the transformer (primary side)";
parameter Modelica.Units.SI.Voltage VLow = {{data["tx_outgoing_voltage"]}} "Rms voltage on side 2 of the transformer (secondary side)";
parameter Modelica.Units.SI.ApparentPower VABase = {{data["nominal_capacity"]}} "Nominal power of the transformer";
parameter Real XoverR = {{data["reactance_resistance_ratio"]}} "Ratio between the complex and real components of the impedance (XL/R)";
extends Buildings.Electrical.AC.ThreePhasesBalanced.Conversion.Examples.ACACTransformer(
tra_load(
VHigh=VHigh,
VLow=VLow,
VABase=VABase,
XoverR=XoverR)
);
annotation (
Icon(
coordinateSystem(
preserveAspectRatio=false)),
Diagram(
coordinateSystem(
preserveAspectRatio=false)),
experiment(
StopTime=86400,
Tolerance=1e-06),
Documentation(
info="<html>
<p>This model validates the transformer template model implemented in
<a href=\"Buildings.Electrical.AC.ThreePhasesBalanced.Conversion.Examples.ACACTransformer.mo\">
Buildings.Electrical.AC.ThreePhasesBalanced.Conversion.Examples.ACACTransformer.mot</a>.
</p>
</html>",
revisions="<html>
<ul>
<li>
April 20, 2023 by Zhanwei He:<br/>
First implementation.
</li>
</ul>
</html>"));

end ACACTransformer;
@@ -0,0 +1,24 @@
from pathlib import Path

from geojson_modelica_translator.modelica.simple_gmt_base import SimpleGMTBase


class ACACTransformer(SimpleGMTBase):
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):
transformer_params = self.system_parameters.get_param("$.transformers")
# There can be multiple capacitors so we need to loop over them
for index, transformer in enumerate(transformer_params):
cap_params = {
'nominal_capacity': transformer["nominal_capacity"],
"reactance_resistance_ratio": transformer["reactance_resistance_ratio"],
"tx_incoming_voltage": transformer["tx_incoming_voltage"],
"tx_outgoing_voltage": transformer["tx_outgoing_voltage"],
'model_name': f"Transformer{index}",
}
# render template to final modelica file
self.to_modelica(output_dir=output_dir, model_name='ACACTransformer', param_data=cap_params, iteration=index)
@@ -1,7 +1,8 @@
model AC{{ data["model_name"] }}
"Isolated AC distribution line template model for GMT level 1 testing"
parameter Real safety_factor = 1.2;
parameter Modelica.Units.SI.Length l={{ data["length"] }};
parameter Modelica.Units.SI.Power P_nominal={{ data["ampacity"] }};
parameter Modelica.Units.SI.Power P_nominal={{ data["ampacity"] }}*{{ data["nominal_voltage"] }}*safety_factor;
parameter Modelica.Units.SI.Voltage V_nominal={{ data["nominal_voltage"] }};
parameter {{ data["commercial_line_type"] }} cable;{% raw %}
extends
Expand Down
@@ -1,10 +1,12 @@
within;
model {{ data["model_name"] }}
"Isolated capacitor template model for GMT level 1 testing"
parameter Modelica.Units.SI.Power P_nominal = {{ data["nominal_capacity"] }};
parameter Modelica.Units.SI.Power P_nominal = {{ data["nominal_power_consumption"] }};
parameter Modelica.Units.SI.Voltage V_nominal = {{ data["nominal_voltage"] }};
extends Buildings.Electrical.AC.ThreePhasesBalanced.Loads.Examples.ParallelLoads(
varRC_y(
P_nominal = P_nominal),
P_nominal = P_nominal,
V_nominal=V_nominal),
load.height = 0,
load.duration = 10,
pow.height = 0,
Expand Down
@@ -0,0 +1,34 @@
import logging
from pathlib import Path

from geojson_modelica_translator.modelica.simple_gmt_base import SimpleGMTBase

logger = logging.getLogger(__name__)
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s: %(message)s',
datefmt='%d-%b-%y %H:%M:%S',
)


class Capacitive_load(SimpleGMTBase):
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):
for building_index, building in enumerate(self.system_parameters.get_param("$.buildings")):
capacitive_params = {
'nominal_power_consumption': building["load"]["max_power_kw"],
'nominal_voltage': building["load"]["nominal_voltage"],
'model_name': f"Capacitive{building_index}",
}
# render template to final modelica file
self.to_modelica(
output_dir=output_dir,
model_name='Capacitive',
param_data=capacitive_params,
iteration=building_index
)
# If the sys-param file is missing an entry, it will show up as a jinja2.exceptions.UndefinedError
@@ -0,0 +1,99 @@
within ;
model PVsubsystem
"microgrid template model for GMT level 1 testing"
//grid parameters
parameter Modelica.Units.SI.Frequency f_gri= {{data["electrical_grid"]["frequency"]}};
parameter Modelica.Units.SI.Voltage V_gri = {{data["electrical_grid"]["source_rms_voltage"]}};
parameter Modelica.Units.SI.Angle phiSou_gri = {{data["electrical_grid"]["source_phase_shift"]}};
//PVPanels parameters
parameter Modelica.Units.SI.Area A_PV = {{ data["photovoltaic_panels"][0]["net_surface_area"] }};
parameter Modelica.Units.SI.Voltage V_nominalPV = {{ data["photovoltaic_panels"][0]["nominal_voltage"] }};
parameter Modelica.Units.SI.Angle til_PV = {{ data["photovoltaic_panels"][0]["surface_tilt"] }};
parameter Modelica.Units.SI.Angle azi_PV = {{ data["photovoltaic_panels"][0]["surface_azimuth"] }};
//inductive load parameters
parameter Modelica.Units.SI.Power P_nominalInd=-{{ data["buildings"][0]["load"]["max_power_kw"] }} "The negative sign means this the load consumption";

parameter Real line_safety_factor = 1.2;

//PV line parameters
// FIXME: We should use more than a single entry in the sys-param file. pv_subsystem.py should be more sophisticated
parameter Modelica.Units.SI.Length l_LPV={{ data["distribution_lines"][0]["length"] }};
parameter Modelica.Units.SI.Voltage V_nominal_LPV={{ data["distribution_lines"][0]["nominal_voltage"] }};
// P_nominal is a required parameter of the line. I think the MBL model should use ampacity instead, but se la vie.
parameter Modelica.Units.SI.Voltage P_nominal_LPV={{ data["distribution_lines"][0]["nominal_voltage"] }}*{{ data["distribution_lines"][0]["ampacity"] }}*line_safety_factor;
//inductive load line parameters
parameter Modelica.Units.SI.Length l_Lind={{ data["distribution_lines"][1]["length"] }};
parameter Modelica.Units.SI.Voltage V_nominal_Lind={{ data["distribution_lines"][1]["nominal_voltage"]}};
// P_nominal is a required parameter of the line. I think the MBL model should use ampacity instead, but se la vie.
parameter Modelica.Units.SI.Voltage P_nominal_Lind={{ data["distribution_lines"][1]["nominal_voltage"] }}*{{ data["distribution_lines"][1]["ampacity"] }}*line_safety_factor;


Modelica.Blocks.Sources.Constant load(k=1) "Load consumption"
{% raw %}annotation (Placement(transformation(extent={{124,-34},{100,-10}})));
Buildings.BoundaryConditions.WeatherData.ReaderTMY3
weaDat(computeWetBulbTemperature=false, filNam=
ModelicaServices.ExternalReferences.loadResource(
"modelica://Buildings/Resources/weatherdata/USA_IL_Chicago-OHare.Intl.AP.725300_TMY3.mos"))
annotation (Placement(transformation(extent={{-16,108},{-40,132}})));
Buildings.Electrical.AC.ThreePhasesBalanced.Loads.Inductive InductiveLoad(
mode=Buildings.Electrical.Types.Load.VariableZ_y_input,
P_nominal=P_nominalInd)
annotation (Placement(transformation(extent={{40,-56},{76,-24}})));
Buildings.Electrical.AC.ThreePhasesBalanced.Sources.PVSimpleOriented PV(
A=A_PV,
V_nominal=V_nominalPV,
til=til_PV,
azi=azi_PV) "PV array oriented"
annotation (Placement(transformation(extent={{-78,42},{-40,78}})));
Buildings.Electrical.AC.ThreePhasesBalanced.Sources.Grid grid(
f=f_gri,
V=V_gri,
phiSou=phiSou_gri)
"Electrical grid model"
annotation (Placement(transformation(extent={{-132,2},{-106,26}})));
Buildings.Electrical.AC.ThreePhasesBalanced.Lines.Line line_PV(
l=l_LPV,
P_nominal=P_nominal_LPV,
redeclare Buildings.Electrical.Transmission.MediumVoltageCables.Generic
// TODO: commercialCable should be templatized using the same selection from Lines.py
commercialCable = Buildings.Electrical.Transmission.MediumVoltageCables.Annealed_Al_1000(),
V_nominal=V_nominal_LPV) "line model uses the medium voltage option"
annotation (Placement(transformation(
extent={{10,-10},{-10,10}},
rotation=90,
origin={-80,20})));
Buildings.Electrical.AC.ThreePhasesBalanced.Lines.Line line_ind(
l=l_Lind,
P_nominal=P_nominal_Lind,
redeclare Buildings.Electrical.Transmission.MediumVoltageCables.Generic
// TODO: commercialCable should be templatized using the same selection from Lines.py
commercialCable = Buildings.Electrical.Transmission.MediumVoltageCables.Annealed_Al_1000(),
V_nominal=V_nominal_Lind) "line model uses the medium voltage option"
annotation (Placement(transformation(
extent={{-10,-10},{10,10}},
rotation=0,
origin={10,-40})));
equation
connect(weaDat.weaBus, PV.weaBus) annotation (Line(
points={{-40,120},{-59,120},{-59,76.2}},
color={255,204,51},
thickness=0.5));
connect(load.y, InductiveLoad.y) annotation (Line(points={{98.8,-22},{90,-22},
{90,-40},{76,-40}}, color={0,0,127}));
connect(PV.terminal, line_PV.terminal_n)
annotation (Line(points={{-78,60},{-80,60},{-80,30}}, color={0,120,120}));
connect(line_PV.terminal_p, line_ind.terminal_n) annotation (Line(points={{
-80,10},{-80,-40},{-8.88178e-16,-40}}, color={0,120,120}));
connect(line_ind.terminal_p, InductiveLoad.terminal)
annotation (Line(points={{20,-40},{40,-40}}, color={0,120,120}));
connect(grid.terminal, line_ind.terminal_n) annotation (Line(points={{-119,2},
{-119,-40},{-8.88178e-16,-40}}, color={0,120,120}));
annotation (
Icon(coordinateSystem(preserveAspectRatio=false, extent={{-160,-80},{140,
140}})),
Diagram(coordinateSystem(preserveAspectRatio=false, extent={{-160,-80},{140,
140}})),
uses( Modelica(version="4.0.0"),
ModelicaServices(version="4.0.0"),
Buildings(version="9.1.0")));
{% endraw %}end PVsubsystem;
@@ -0,0 +1,14 @@
from pathlib import Path

from geojson_modelica_translator.modelica.simple_gmt_base import SimpleGMTBase


class PVSubsystem(SimpleGMTBase):
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):
pv_subsystem_params = self.system_parameters.get_param("$")
self.to_modelica(output_dir=output_dir, model_name='PVsubsystem', param_data=pv_subsystem_params)
36 changes: 19 additions & 17 deletions geojson_modelica_translator/system_parameters/system_parameters.py
Expand Up @@ -4,7 +4,6 @@
import json
import logging
import math
import os
from copy import deepcopy
from pathlib import Path
from typing import Union
Expand Down Expand Up @@ -480,8 +479,8 @@ def process_electrical_components(self, scenario_dir: Path):
capacitor banks (todo)
"""
dss_data = {}
opendss_json_file = os.path.join(scenario_dir, 'scenario_report_opendss.json')
if (os.path.exists(opendss_json_file)):
opendss_json_file = Path(scenario_dir) / 'scenario_report_opendss.json'
vtnate marked this conversation as resolved.
Show resolved Hide resolved
if opendss_json_file.exists():
with open(opendss_json_file, "r") as f:
dss_data = json.load(f)

Expand Down Expand Up @@ -555,13 +554,16 @@ def process_electrical_components(self, scenario_dir: Path):
for item in data:
t = {}
t['id'] = item['id']
t['nominal_capacity'] = None
if item['power_distribution']['nominal_capacity']:
t['nominal_capacity'] = item['power_distribution']['nominal_capacity']
t['nominal_capacity'] = item['power_distribution'].get('nominal_capacity', None)
t['reactance_resistance_ratio'] = item['power_distribution'].get('reactance_resistance_ratio', None)
t['tx_incoming_voltage'] = item['power_distribution'].get('tx_incoming_voltage', None)
t['tx_outgoing_voltage'] = item['power_distribution'].get('tx_outgoing_voltage', None)

# Validate transformer input voltage is same as substation output voltage
if t['tx_incoming_voltage'] is not None and t['tx_incoming_voltage'] != self.param_template['substations']['RMS_voltage_low_side']:
raise ValueError(f"Transformer input voltage {t['tx_incoming_voltage']} does not "
f"match substation output voltage {self.param_template['substations']['RMS_voltage_low_side']}")

t['reactance_resistance_ratio'] = None
if item['power_distribution']['reactance_resistance_ratio']:
t['reactance_resistance_ratio'] = item['power_distribution']['reactance_resistance_ratio']
transformers.append(t)

self.param_template['transformers'] = transformers
Expand All @@ -577,7 +579,7 @@ def process_electrical_components(self, scenario_dir: Path):
if match:
# add data
bldg['load'] = {}
# print("Found match for {}: {}".format(bldg['geojson_id'], match[0]['id']))
# print(f"Found match for {bldg['geojson_id']}: {match[0]['id']}")
bldg['load']['nominal_voltage'] = match[0]['power_distribution']['nominal_voltage']
bldg['load']['max_power_kw'] = match[0]['power_distribution']['max_power_kw']
bldg['load']['max_reactive_power_kvar'] = match[0]['power_distribution']['max_reactive_power_kvar']
Expand All @@ -589,9 +591,9 @@ def process_building_microgrid_inputs(self, building, scenario_dir: Path):
:param scenario_dir: Path, location/name of folder with uo_sdk results
:return building, updated building list object
"""
feature_opt_file = os.path.join(
scenario_dir, building['geojson_id'], 'feature_reports', 'feature_optimization.json')
if (os.path.exists(feature_opt_file)):
feature_opt_file = Path(
scenario_dir) / building['geojson_id'] / 'feature_reports' / 'feature_optimization.json'
if feature_opt_file.exists():
with open(feature_opt_file, "r") as f:
reopt_data = json.load(f)

Expand All @@ -618,13 +620,13 @@ def process_microgrid_inputs(self, scenario_dir: Path):
reopt_data = {}
raw_data = {}
# look for REopt scenario_optimization.json file in scenario dir (uo report)
scenario_opt_file = os.path.join(scenario_dir, 'scenario_optimization.json')
if (os.path.exists(scenario_opt_file)):
scenario_opt_file = Path(scenario_dir) / 'scenario_optimization.json'
if scenario_opt_file.exists():
with open(scenario_opt_file, "r") as f:
reopt_data = json.load(f)
# also look for raw REopt report with inputs and xzx for non-uo results
raw_scenario_file = os.path.join(scenario_dir, 'reopt', f'scenario_report_{scenario_dir.name}_reopt_run.json')
if (os.path.exists(raw_scenario_file)):
raw_scenario_file = Path(scenario_dir) / 'reopt' / f'scenario_report_{scenario_dir.name}_reopt_run.json'
if raw_scenario_file.exists():
with open(raw_scenario_file, "r") as f:
raw_data = json.load(f)

Expand Down