Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
b3aa6b2
adding initial take at bus
tarekelgindy Feb 26, 2025
011f6d5
add distribution capacitor equipment and component
tengis-nrl Mar 3, 2025
abdc5ca
adding testing with data not included in repo
tarekelgindy Mar 3, 2025
bb66583
adding general reading functionality for cyme
tarekelgindy Mar 3, 2025
61ca60b
moving distribution bus to components and fixing bugs
tarekelgindy Mar 3, 2025
37e8148
merging
tarekelgindy Mar 4, 2025
8e556b1
fixing indent problem
tarekelgindy Mar 5, 2025
a86dccd
Create distribution capacitor equipment and component, add it to the …
tengis-nrl Mar 7, 2025
4cd65e3
Implement distribution loads
tengis-nrl Mar 10, 2025
a2f7351
untested changes to read from load file not network
tarekelgindy Mar 25, 2025
7e257e4
fixing breaking changes from GDM updates in opendss writer
tarekelgindy May 6, 2025
83847ce
fixing breaking changes from GDM updates in cyme reader
tarekelgindy May 6, 2025
97a400d
Current chagnes for cyme reader. Needs fixes
tarekelgindy Jul 2, 2025
5c931b5
Removing Positive Quantities
zzink-nrel Sep 11, 2025
9ac42a6
complexity, byphase line comprehension, feeder, random fixes
zzink-nrel Sep 11, 2025
7d45ab2
transformer byphase, feeder mapping, switches
zzink-nrel Sep 16, 2025
e04f900
fuse and recloser additions
zzink-nrel Sep 16, 2025
a74227e
feeder construction, other stuff
zzink-nrel Sep 30, 2025
0069b09
neutral phasing, matrix defaults
zzink-nrel Oct 6, 2025
4bc784b
length unit fixes
zzink-nrel Oct 14, 2025
6bdf1f2
adding functionality for cables to be read into matrix_impedance_bran…
tarekelgindy Oct 16, 2025
b4b38ef
adding functionality for concentric neutral cables to be added. Not u…
tarekelgindy Oct 16, 2025
a3b3eac
small update from nominal voltage to rated voltage
tarekelgindy Oct 16, 2025
cca3dfd
adding concentric neutral cable writing capability in opendss
tarekelgindy Oct 16, 2025
1e8b830
cyme reader to also export to geojson and include loads
tarekelgindy Oct 16, 2025
70e9b98
merging tarek and zephyr changes
tarekelgindy Oct 16, 2025
8679948
not checking for phase mismatches for the moment...
tarekelgindy Oct 16, 2025
80144d7
minor fixes to cyme reader
tarekelgindy Oct 17, 2025
5b8cf3e
removing spaces in opendss outputs
tarekelgindy Oct 17, 2025
91c8500
opendss fixes for changing nominal voltage to rated voltage
tarekelgindy Oct 17, 2025
2e02003
adding reclosers to opendss writer
tarekelgindy Oct 17, 2025
8cd05fb
substation parsing, reconcile with tarek's changes
zzink-nrel Oct 20, 2025
707668d
three winding transformers, voltage assignment rework, winding fixes
zzink-nrel Oct 22, 2025
c5e0499
ensuring unique linecodes per phase
tarekelgindy Nov 5, 2025
c97b84e
fixes for missing loads
tarekelgindy Nov 5, 2025
3095da2
updating reader for unique linecodes
tarekelgindy Nov 5, 2025
576b7db
removing . values from opendss outputs
tarekelgindy Nov 5, 2025
eaf6a7d
removing extra addition of equipment. This is done when parsing the e…
tarekelgindy Nov 5, 2025
68eeea2
updating names due to issues with non-uniqueness in opendss. Also set…
tarekelgindy Nov 5, 2025
f40f192
updates to components to fix bugs with capacitors and voltages source…
tarekelgindy Nov 25, 2025
5b4334f
updates to equipment to fix bugs with transformers and branches in di…
tarekelgindy Nov 25, 2025
005a6c1
setting units for matrix impedance branch. TODO: check unit types
tarekelgindy Nov 25, 2025
31cf0f6
updates to equipment to fix bugs with branches in different versions …
tarekelgindy Nov 25, 2025
78170a0
adding considerations for sizes which are zero. Should be caught by v…
tarekelgindy Nov 25, 2025
06f9302
adding a default load number
tarekelgindy Nov 25, 2025
f83605a
organizing opendss data to be in a folder
tarekelgindy Nov 25, 2025
ffd117e
ability to truncate model to certain subs,feeders
zzink-nrel Jan 7, 2026
b9702ba
linting, removing split phase for now
zzink-nrel Jan 16, 2026
9e5a8db
bug fix
zzink-nrel Jan 16, 2026
708a0c0
aligning values with GDM definitions
zzink-nrel Feb 10, 2026
6a79684
read and assign voltage complexity
zzink-nrel Feb 21, 2026
8e3489c
ruff and comment fixes for CYME Reader
zzink-nrel Feb 24, 2026
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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.DS_Store

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
Expand Down
5 changes: 4 additions & 1 deletion src/ditto/enumerations.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ class OpenDSSFileTypes(str, Enum):
COORDINATE_FILE = "BusCoords.dss"
TRANSFORMERS_FILE = "Transformers.dss"
CAPACITORS_FILE = "Capacitors.dss"
WIRES_FILE = "WireData.dss"
CABLES_FILE = "CableData.dss"
LINECODES_FILE = "LineCodes.dss"
LINES_FILE = "Lines.dss"
LOADS_FILE = "Loads.dss"
WIRES_FILE = "WireData.dss"
LINE_GEOMETRIES_FILE = "LineGeometry.dss"
SWITCH_CODES_FILE = "SwitchCodes.dss"
SWITCH_FILE = "Switches.dss"
FUSE_CODES_FILE = "FuseCodes.dss"
FUSE_FILE = "Fuses.dss"
RECLOSER_CODES_FILE = "RecloserCodes.dss"
RECLOSER_FILE = "Reclosers.dss"
46 changes: 46 additions & 0 deletions src/ditto/readers/cyme/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from ditto.readers.cyme.components.distribution_bus import DistributionBusMapper
from ditto.readers.cyme.components.distribution_capacitor import DistributionCapacitorMapper
from ditto.readers.cyme.components.distribution_load import DistributionLoadMapper
from ditto.readers.cyme.equipment.geometry_branch_equipment import BareConductorEquipmentMapper
from ditto.readers.cyme.equipment.geometry_branch_equipment import GeometryBranchEquipmentMapper
from ditto.readers.cyme.equipment.matrix_impedance_branch_equipment import (
MatrixImpedanceBranchEquipmentMapper,
)
from ditto.readers.cyme.equipment.geometry_branch_equipment import (
GeometryBranchByPhaseEquipmentMapper,
)
from ditto.readers.cyme.components.geometry_branch import GeometryBranchMapper
from ditto.readers.cyme.equipment.distribution_transformer_equipment import (
DistributionTransformerEquipmentMapper,
)
from ditto.readers.cyme.equipment.distribution_transformer_equipment import WindingEquipmentMapper
from ditto.readers.cyme.equipment.distribution_transformer_three_winding_equipment import (
DistributionTransformerThreeWindingEquipmentMapper,
)
from ditto.readers.cyme.equipment.distribution_transformer_three_winding_equipment import (
ThreeWindingEquipmentMapper,
)
from ditto.readers.cyme.components.distribution_transformer import (
DistributionTransformerByPhaseMapper,
DistributionTransformerMapper,
DistributionTransformerThreeWindingMapper,
)
from ditto.readers.cyme.components.matrix_impedance_switch import MatrixImpedanceSwitchMapper
from ditto.readers.cyme.equipment.matrix_impedance_switch_equipment import (
MatrixImpedanceSwitchEquipmentMapper,
)
from ditto.readers.cyme.components.matrix_impedance_fuse import MatrixImpedanceFuseMapper
from ditto.readers.cyme.equipment.matrix_impedance_fuse_equipment import (
MatrixImpedanceFuseEquipmentMapper,
)
from ditto.readers.cyme.components.matrix_impedance_recloser import MatrixImpedanceRecloserMapper
from ditto.readers.cyme.equipment.matrix_impedance_recloser_equipment import (
MatrixImpedanceRecloserEquipmentMapper,
)
from ditto.readers.cyme.components.matrix_impedance_branch import MatrixImpedanceBranchMapper
from ditto.readers.cyme.components.distribution_voltage_source import (
DistributionVoltageSourceMapper,
)
from ditto.readers.cyme.equipment.phase_voltagesource_equipment import (
PhaseVoltageSourceEquipmentMapper,
)
83 changes: 83 additions & 0 deletions src/ditto/readers/cyme/components/distribution_bus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from infrasys.location import Location
from gdm.distribution.components.distribution_bus import DistributionBus
from gdm.distribution.enums import VoltageTypes, Phase
from gdm.quantities import Voltage
from ditto.readers.cyme.cyme_mapper import CymeMapper


class DistributionBusMapper(CymeMapper):
def __init__(self, cyme_model):
super().__init__(cyme_model)

cyme_file = "Network"
cyme_section = "NODE"

def parse(
self, row, from_node_sections, to_node_sections, node_feeder_map, node_substation_map
):
name = self.map_name(row)
feeder = node_feeder_map.get(name, None)
substation = node_substation_map.get(name, None)

coordinate = self.map_coordinate(row)
phases = self.map_phases(row, from_node_sections, to_node_sections)
rated_voltage = self.map_rated_voltage(row)
voltage_limits = self.map_voltagelimits(row)
voltage_type = self.map_voltage_type(row)
return DistributionBus.model_construct(
name=name,
coordinate=coordinate,
rated_voltage=rated_voltage,
feeder=feeder,
substation=substation,
phases=phases,
voltagelimits=voltage_limits,
voltage_type=voltage_type,
)

def map_name(self, row):
name = row["NodeID"]
return name

def map_coordinate(self, row):
x_key = "CoordX" if "CoordX" in row and row["CoordX"] != "" else "CoordX1"
y_key = "CoordY" if "CoordY" in row and row["CoordY"] != "" else "CoordY1"
# CRS is not provided in the Cyme data
return Location(x=float(row[x_key]), y=float(row[y_key]), crs=None)

def map_rated_voltage(self, row):
# Placehoder voltage until assign_bus_voltages assigns voltages based on network traversal and transformer ratings
return Voltage(float(12.47), "kilovolts")

def map_phases(self, row, from_node_sections, to_node_sections):
node_id = row["NodeID"]
all_phases = set()
if node_id in from_node_sections:
for section in from_node_sections[node_id]:
phases = section["Phase"]
for phase in phases:
all_phases.add(phase)
if node_id in to_node_sections:
for section in to_node_sections[node_id]:
phases = section["Phase"]
for phase in phases:
all_phases.add(phase)

phase_map = {"A": Phase.A, "B": Phase.B, "C": Phase.C, "N": Phase.N}
return [phase_map[p] for p in sorted(all_phases) if p in phase_map]

def map_voltagelimits(self, row):
low_voltage = None
high_voltage = None
if row["LowVoltageLimit"] != "":
low_voltage = Voltage(row["LowVoltageLimit"], "kilovolts")
if row["HighVoltageLimit"] != "":
high_voltage = Voltage(row["HighVoltageLimit"], "kilovolts")
if low_voltage is not None and high_voltage is not None:
return [low_voltage, high_voltage]
else:
return []

def map_voltage_type(self, row):
# Defined later in assigne_bus_voltages based on network traversal and transformer ratings
return VoltageTypes.LINE_TO_LINE
89 changes: 89 additions & 0 deletions src/ditto/readers/cyme/components/distribution_capacitor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from ditto.readers.cyme.cyme_mapper import CymeMapper
from ditto.readers.cyme.equipment.capacitor_equipment import CapacitorEquipmentMapper
from gdm.distribution.components.distribution_bus import DistributionBus
from gdm.distribution.components.distribution_capacitor import DistributionCapacitor
from gdm.distribution.enums import Phase
from loguru import logger


class DistributionCapacitorMapper(CymeMapper):
def __init__(self, system):
super().__init__(system)

cyme_file = "Network"
cyme_section = "SHUNT CAPACITOR SETTING"

def parse(self, row, section_id_sections, equipment_data):
name = self.map_name(row)
bus = self.map_bus(row, section_id_sections)
phases = self.map_phases(row, section_id_sections)
controllers = self.map_controllers(row)
equipment = self.map_equipment(row, equipment_data)
in_service = self.map_in_service(row)
return DistributionCapacitor.model_construct(
name=name,
bus=bus,
phases=phases,
controllers=controllers,
equipment=equipment,
in_service=in_service,
)

def map_name(self, row):
return row["DeviceNumber"]

def map_phases(self, row, section_id_sections):
phases = []
section_id = row["SectionID"]
section = section_id_sections[section_id]
section_phases = section["Phase"]
if "FixedKVARA" in row and row["FixedKVARA"] or "A" in section_phases:
phases.append(Phase.A)
if "FixedKVARB" in row and row["FixedKVARB"] or "B" in section_phases:
phases.append(Phase.B)
if "FixedKVARC" in row and row["FixedKVARC"] or "C" in section_phases:
phases.append(Phase.C)
if phases == []:
raise ValueError(
f"Could not determine phases for capacitor {row['DeviceNumber']} on section {section_id} with section phases {section_phases}"
)
return phases

def map_bus(self, row, section_id_sections):
section_id = row["SectionID"]
section = section_id_sections[section_id]
from_bus_name = section["FromNodeID"]
to_bus_name = section["ToNodeID"]
to_bus = None
from_bus = None

from_bus = self.system.get_component(component_type=DistributionBus, name=from_bus_name)

to_bus = self.system.get_component(component_type=DistributionBus, name=to_bus_name)

if from_bus is None:
if to_bus is None:
logger.warning(f"Capacitor {section_id} has no bus")
return None
return to_bus
return from_bus

def map_controllers(self, row):
return []

def map_equipment(self, row, equipment_data):
mapper = CapacitorEquipmentMapper(self.system)
capacitor_id = row["ShuntCapacitorID"]
if capacitor_id not in equipment_data.index:
logger.warning(
f"Capacitor {row['DeviceNumber']} references capacitor equipment {capacitor_id} which is not defined in the equipment data. Assigning default capacitor equipment."
)
capacitor_id = "DEFAULT"
equipment_row = equipment_data.loc[capacitor_id]
if not equipment_row.empty:
equipment = mapper.parse(equipment_row, connection=row["Connection"])
return equipment
return None

def map_in_service(self, row):
return True if int(row["ConnectionStatus"]) == 0 else False
90 changes: 90 additions & 0 deletions src/ditto/readers/cyme/components/distribution_load.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from ditto.readers.cyme.cyme_mapper import CymeMapper
from ditto.readers.cyme.equipment.load_equipment import LoadEquipmentMapper
from gdm.distribution.components.distribution_bus import DistributionBus
from gdm.distribution.components.distribution_load import DistributionLoad
from gdm.distribution.enums import Phase
from loguru import logger


class DistributionLoadMapper(CymeMapper):
def __init__(self, system):
super().__init__(system)

cyme_file = "Load"
cyme_section = "CUSTOMER LOADS"

def parse(self, row, section_id_sections, equipment_file, load_record):
name = self.map_name(row)

bus = self.map_bus(row, section_id_sections)
phases = self.map_phases(row)
equipment = self.map_equipment(row, equipment_file)
if equipment is None:
return None

if load_record.get(name) is not None:
# Combines powers from multiple customer IDs to their spot loads.
# Individual customer loads are not supported.

existing_load = load_record.get(name)
existing_load.equipment.phase_loads[0].real_power += equipment.phase_loads[
0
].real_power
existing_load.equipment.phase_loads[0].reactive_power += equipment.phase_loads[
0
].reactive_power
return None

if len(phases) == 0:
logger.warning(f"Load {name} has no phase values. Skipping...")
return None

load = DistributionLoad.model_construct(
name=name, bus=bus, phases=phases, equipment=equipment
)
load_record[name] = load
return load

def map_name(self, row):
load_phase = row["LoadPhase"]
return row["DeviceNumber"] + "_" + str(load_phase)

def map_bus(self, row, section_id_sections):
section_id = row["SectionID"]
section = section_id_sections[section_id]
from_bus_name = section["FromNodeID"]
to_bus_name = section["ToNodeID"]
to_bus = None
from_bus = None

from_bus = self.system.get_component(component_type=DistributionBus, name=from_bus_name)

to_bus = self.system.get_component(component_type=DistributionBus, name=to_bus_name)

if from_bus is None:
if to_bus is None:
logger.warning(f"Load {section_id} has no bus")
return None
return to_bus
return from_bus

def map_phases(self, row):
phases = []
if row["LoadPhase"] is not None:
phase = row["LoadPhase"]
if phase == "A":
phases.append(Phase.A)
elif phase == "B":
phases.append(Phase.B)
elif phase == "C":
phases.append(Phase.C)
return phases

def map_equipment(self, row, equipment_file):
mapper = LoadEquipmentMapper(self.system)
equipment_row = equipment_file.loc[row["DeviceNumber"]]
if equipment_row is not None:
equipment = mapper.parse(equipment_row, row)
return equipment

return None
Loading