Skip to content

Commit

Permalink
Template initial microgrid subsystem example (#569)
Browse files Browse the repository at this point in the history
* pvsubsystem template and code to render

* test to build pvsubsystem model from template

* transformer template and code to render

* hacking in some sample transformer data to the sys-param file

* new tests for transformer, and simulation tests for previous components

* add calculation for line power in line and pvsubsystem templates

* skip test_build_pv_subsystem because it is captured in the simulation test

* add comments to pv_subsystem.mot for future improvements

* add todo to get ditto-reader info about transformers into the sys-param file

* add code to read transformer in- and out-going voltages from uo sdk

* fix transformer template and code to use new variable names

* update test sys-param file with current variable names

* use .get() method for looking at opendss values

* use .get() method for other transformer properties as well

* formatting instance file

* remove single 5g test file - district 5g test already exists

* unskip testing district 5g system

* add start/stop/step times to dhc test setup

* re-skip the dhc simulation test

* use appropriate cable in pv subsystem model

* capacitor was confused with capacitive load. fixed capacitive load

* use pathlib instead of os.path when adding microgrid to sys-params

* tests for capacitive load model

* `poetry update`

* gix typo in transformer data for sys-param

* potential solution to poetry dependency installation failures

* fix district heating and cooling test assert path
  • Loading branch information
vtnate committed Sep 15, 2023
1 parent ad0ba66 commit dd7a461
Show file tree
Hide file tree
Showing 16 changed files with 676 additions and 170 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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'
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

0 comments on commit dd7a461

Please sign in to comment.