Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ RUN apt-get -y update && apt-get install -y python3 git sudo vim python3-pip pyt

# Install the OMF
# Warning: clone might be cached. Consider invalidating manually.
RUN git clone --depth 1 https://github.com/nreca-bts/omf.git && cd omf && sudo python3 install.py && rm -rf /omf/.git
RUN python3 -m pip install --no-cache-dir --upgrade pip && python3 -m pip install --no-cache-dir git+https://github.com/nreca-bts/omf.git && rm -rf /root/.cache/pip /root/.cache/pip/http
# Install a compatible version of numpy<2.0.0
RUN python3 -m pip install --no-cache-dir numpy==1.26.4 && rm -rf /root/.cache/pip /root/.cache/pip/http

Expand Down
5 changes: 5 additions & 0 deletions microgridup.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def main(data, invalidate_cache=True, open_results=False):
}
if 'jsCircuitModel' in data:
inputs['jsCircuitModel'] = data['jsCircuitModel']
inputs['circuitSource'] = 'wizard' if 'jsCircuitModel' in data else 'uploaded'
# - Set up the model directory and environment
# Create initial files.
if not os.path.isdir(absolute_model_directory):
Expand Down Expand Up @@ -802,6 +803,10 @@ def _tests():
'BASE_DSS': '<replace_me>',
'LOAD_CSV': f'{MGU_DIR}/testfiles/lehigh_load.csv',
'QSTS_STEPS': 480,
'LOAD_GROWTH_PERCENT': 0.0,
'LOAD_GROWTH_SPECIFIC': {},
'ADDITIONAL_LOADSHAPE_CSV': None,
'ADDITIONAL_LOADSHAPE_METER': '',
'REOPT_INPUTS': {
'energyCost': 0.12,
'wholesaleCost': 0.034,
Expand Down
47 changes: 24 additions & 23 deletions microgridup_gen_mgs.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,12 +520,12 @@ def get_edge_name(fr, to, omd_list):
# If still not found, raise error.
raise SwitchNotFoundError(f'Selected partitioning method produced invalid results. No valid switch found between {fr} and {to}. Please change partitioning parameter(s).')

def _validate_mg_groups_feeders_and_substations(mg_groups, G, omd, partition_params_json=None):
def _validate_mg_groups_feeders_and_substations(mg_groups, G, omd, partition_params_json=None, strict_feeder_check=True):
'''
Validate that each microgrid group:
- contains nodes that exist in the graph G,
- is contained entirely within one weakly-connected component (all nodes reachable if edge directions are ignored) (within same substation's tree),
- maps to a single parent bus (feeder) where possible (i.e., does not span multiple feeders).
- (wizard circuits only, strict_feeder_check=True) maps to a single parent bus (feeder).
Raises ValueError on failure.
'''
# Map node -> component id
Expand Down Expand Up @@ -562,30 +562,31 @@ def _validate_mg_groups_feeders_and_substations(mg_groups, G, omd, partition_par
if len(comp_ids) > 1:
problems.append(f'{friendly} ({mg_id}) spans multiple substations/trees; nodes: {nodes}')
continue
# 3) Collect parent buses for all nodes that have them
parent_buses = set()
for n in nodes:
ob = omd_by_name.get(n)
if not ob:
continue
parent = ob.get('parent') or ob.get('bus') or ob.get('bus1') or ob.get('parent_bus')
if parent:
parent_buses.add(str(parent).split('.')[0])
# If we found multiple distinct parent buses, group spans feeders -> problem
if len(parent_buses) > 1:
problems.append(f'{friendly} ({mg_id}) spans multiple feeder buses: {sorted(parent_buses)}; nodes: {nodes}')
continue
# If we found no parent buses, ensure at least one member of the group is a bus-type object in omd
if len(parent_buses) == 0:
found_bus_obj = False
if strict_feeder_check:
# 3) Collect parent buses for all nodes that have them
parent_buses = set()
for n in nodes:
ob = omd_by_name.get(n)
if ob and ob.get('object') and ob.get('object').lower() == 'bus':
found_bus_obj = True
break
if not found_bus_obj:
problems.append(f'{friendly} ({mg_id}) has no identifiable parent bus and contains no bus object; nodes: {nodes}')
if not ob:
continue
parent = ob.get('parent') or ob.get('bus') or ob.get('bus1') or ob.get('parent_bus')
if parent:
parent_buses.add(str(parent).split('.')[0])
# If we found multiple distinct parent buses, group spans feeders -> problem
if len(parent_buses) > 1:
problems.append(f'{friendly} ({mg_id}) spans multiple feeder buses: {sorted(parent_buses)}; nodes: {nodes}')
continue
# If we found no parent buses, ensure at least one member of the group is a bus-type object in omd
if len(parent_buses) == 0:
found_bus_obj = False
for n in nodes:
ob = omd_by_name.get(n)
if ob and ob.get('object') and ob.get('object').lower() == 'bus':
found_bus_obj = True
break
if not found_bus_obj:
problems.append(f'{friendly} ({mg_id}) has no identifiable parent bus and contains no bus object; nodes: {nodes}')
continue
if problems:
msg = (
'Invalid microgrid partitioning: each microgrid must be wholly within a single substation/feeder tree and map to a single feeder bus. Problems found: ' + '; '.join(problems)
Expand Down
8 changes: 4 additions & 4 deletions microgridup_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ def run():
# - Format faulted lines
data['FAULTED_LINES'] = data['FAULTED_LINES'].split(',')
# - Create microgrids here and not in microgridup.main because it's easier to format the testing data
data['MICROGRIDS'] = _get_microgrids(data['CRITICAL_LOADS'], data['MG_DEF_METHOD'], data['mgQuantity'], data['BASE_DSS'], data['PARTITION_PARAMS'])
data['MICROGRIDS'] = _get_microgrids(data['CRITICAL_LOADS'], data['MG_DEF_METHOD'], data['mgQuantity'], data['BASE_DSS'], data['PARTITION_PARAMS'], is_wizard_circuit='jsCircuitModel' in data)
# Validate that intended microgrids (from PARTITION_PARAMS.mg_name) actually exist in data['MICROGRIDS']
try:
partition_params = json.loads(request.form.get('PARTITION_PARAMS', '{}'))
Expand Down Expand Up @@ -768,7 +768,7 @@ def _get_reopt_inputs(data):
del data[k]
return reopt_inputs

def _get_microgrids(critical_loads, partition_method, quantity, dss_path, partition_params_json):
def _get_microgrids(critical_loads, partition_method, quantity, dss_path, partition_params_json, is_wizard_circuit=False):
'''
:param critical_loads: a list of critical loads
:type critical_loads: list
Expand Down Expand Up @@ -805,12 +805,12 @@ def _get_microgrids(critical_loads, partition_method, quantity, dss_path, partit
elif partition_method == 'loadGrouping':
algo_params = json.loads(partition_params_json)
mg_groups = form_mg_groups(G, critical_loads, 'loadGrouping', algo_params)
_validate_mg_groups_feeders_and_substations(mg_groups, G, omd, partition_params_json=partition_params_json)
_validate_mg_groups_feeders_and_substations(mg_groups, G, omd, partition_params_json=partition_params_json, strict_feeder_check=is_wizard_circuit)
microgrids = form_microgrids(G, mg_groups, omd, switch_dict=algo_params.get('switch', None), gen_bus_dict=algo_params.get('gen_bus', None), mg_name_dict=algo_params.get('mg_name', None))
elif partition_method == 'manual':
algo_params = json.loads(partition_params_json)
mg_groups = form_mg_groups(G, critical_loads, 'manual', algo_params)
_validate_mg_groups_feeders_and_substations(mg_groups, G, omd, partition_params_json=partition_params_json)
_validate_mg_groups_feeders_and_substations(mg_groups, G, omd, partition_params_json=partition_params_json, strict_feeder_check=is_wizard_circuit)
microgrids = form_microgrids(G, mg_groups, omd, switch_dict=algo_params.get('switch', None), gen_bus_dict=algo_params.get('gen_bus', None), mg_name_dict=algo_params.get('mg_name', None))
elif partition_method == '':
microgrids = json.loads(partition_params_json)
Expand Down
Loading
Loading