Skip to content

Comments

Zz/cyme improvements#54

Open
tarekelgindy wants to merge 52 commits intomainfrom
zz/cyme_improvements
Open

Zz/cyme improvements#54
tarekelgindy wants to merge 52 commits intomainfrom
zz/cyme_improvements

Conversation

@tarekelgindy
Copy link
Contributor

No description provided.

tarekelgindy and others added 30 commits February 26, 2025 14:01
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds comprehensive CYME reader functionality and improves the OpenDSS writer with name sanitization and bug fixes. The changes enable reading CYME distribution network files and converting them to the internal data model format.

Key Changes:

  • New CYME reader implementation with component and equipment mappers for buses, transformers, loads, capacitors, switches, fuses, reclosers, and branches
  • OpenDSS writer improvements including name sanitization (replacing spaces and dots with underscores) and attribute name corrections (nominal_voltage → rated_voltage)
  • Quantity type updates in OpenDSS reader (Positive* types → regular types for broader compatibility)

Reviewed changes

Copilot reviewed 58 out of 59 changed files in this pull request and generated 48 comments.

Show a summary per file
File Description
tests/test_cyme/test_cyme_reader.py New test file for CYME reader functionality
tests/test_opendss/test_opendss_reader.py Updated export path structure and directory creation
src/ditto/readers/cyme/reader.py Main CYME reader with component parsing and voltage assignment logic
src/ditto/readers/cyme/utils.py Utility functions for reading CYME data files
src/ditto/readers/cyme/components/*.py Component mappers for buses, loads, transformers, branches, capacitors, switches, fuses, reclosers
src/ditto/readers/cyme/equipment/*.py Equipment mappers for conductors, cables, transformers, protective devices
src/ditto/writers/opendss/write.py Updated imports and voltage attribute names
src/ditto/writers/opendss/components/*.py Added name sanitization and rated_voltage usage
src/ditto/writers/opendss/equipment/*.py Added name sanitization and validation guards
src/ditto/readers/opendss/components/*.py Changed Positive* quantity types to regular types
src/ditto/enumerations.py Added RECLOSER file types to OpenDSSFileTypes enum
.gitignore Added .DS_Store exclusion

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +62 to +66
try:
mapping_function = getattr(self, "map_" + field)
mapping_function()
except Exception as e:
print(f"{self.model.label} - {field}")
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The try-except block catches all exceptions without any logging or specific exception handling. This can hide bugs and make debugging difficult. Consider catching specific exceptions or at least logging the error.

Copilot uses AI. Check for mistakes.

def map_conductor_diameter(self):
radius = self.model.conductor_diameter.magnitude / 2
if radius <=0:
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comparison operators should have spaces around them. 'radius <=0' should be 'radius <= 0' for consistency with Python PEP 8 style guidelines.

Copilot uses AI. Check for mistakes.
mapping_function = getattr(self, "map_" + field)
mapping_function()
except Exception as e:
print(f"{self.model.label} - {field}")
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using print statements instead of logger for error messages is inconsistent with the rest of the codebase which uses loguru's logger. This should use logger.error() or logger.warning() instead.

Copilot uses AI. Check for mistakes.
from_bus = None
try:
from_bus = self.system.get_component(component_type=DistributionBus,name=from_bus_name)
except Exception as e:
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 58 out of 59 changed files in this pull request and generated 17 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

# TODO: We're not building equipment for the Capacitors. This means that there's no guarantee that we're addressing all of the attributes in the equipment in a structured way like we are for the component.

def map_in_service(self):
self.opendss_dict["Enabled"] = "Yes" if self.model.in_serivce else "No"
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'in_serivce' to 'in_service'.

Copilot uses AI. Check for mistakes.
separate_substations: bool = True,
separate_feeders: bool = True,
):
output_folder = output_path
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable output_folder is assigned but then reassigned on line 90 without being used. This initial assignment appears redundant and should be removed.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

@tarekelgindy tarekelgindy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looots of comments.
Many are for clarity but a good number of changes should probably be made.

if not export_path.exists():
export_path.mkdir(parents=True, exist_ok=True)

reader = Reader(cyme_folder / cyme_network_name, cyme_folder / cyme_equipment_name, cyme_folder / cyme_load_name, '1')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's have a value for load_model_id ('1') here, instead of providing it directly, since some models might use 0

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tarekelgindy I've tried to address but not exactly sure what this means.


return profile_name
try:
mapping_function = getattr(self, "map_" + field)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with copilot that this should probably be outside of a try-except so that errors are thrown


def map_name(self):
self.opendss_dict["Name"] = self.model.name
self.opendss_dict["Name"] = self.model.name.replace(" ", "_").replace(".", "_")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that this might already be addressed in the existing PR #55 ?
If we merge that first, this can be removed.


altdss_name = "LineCode_ZMatrixCMatrix"
altdss_composition_name = "LineCode"
opendss_file = OpenDSSFileTypes.RECLOSER_CODES_FILE.value
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some differences here between this and PR #55 that will cause a conflict.
There, line 13 is:
opendss_file = OpenDSSFileTypes.SWITCH_CODES_FILE.value
also, model and system are also provided to init function.


def map_name(self):
self.opendss_dict["Name"] = self.model.name
self.opendss_dict["Name"] = self.model.name.replace(" ", "_").replace(".", "_")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be removed since handled in PR #55

return ReactivePower(kvar, 'kilovar')

# Is this included in CYME 9.* ? It was in customer class in previous cyme versions
def map_z_real(self, row):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we always assume a full impedance load?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure we have another option

"MatrixImpedanceFuseEquipmentMapper",
]
)
default_conductor = BareConductorEquipment(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use a rigid set of values like this?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tarekelgindy We can remove this I think. I've never used it. Have you?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed

components = self.system.get_components(component_type)
self._add_components(components)

self._validate_model()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a sensible place to fill missing values using GDM MDC rather than having them be done in-line for many cases.


for vsource in voltage_sources:
vsource.bus.rated_voltage = (
vsource.equipment.sources[0].voltage * 1.732
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

voltage source values are assumed line-ground.
Does this mean that when setting bus voltages we're assuming line-line?
We should have a way to enforce our choice rather than assuming

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're not assuming L-L. It's just being left in L-N unless we have multiple phases in which can it's L-L

if component_type == "DistributionTransformer":
for i, winding in enumerate(obj.equipment.windings):
voltage = winding.rated_voltage
voltage_type = winding.voltage_type
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually... is this where voltage type checking is being done?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this function, (which has since been updated slightly) is responsible for bus voltage assignment and wrangles most of this.

Copy link
Contributor Author

@tarekelgindy tarekelgindy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looots of comments.
Many are for clarity but a good number of changes should probably be made.


if mapper_name in phase_elements:
phases = []
for phase in ["A", "B", "C"]:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tarekelgindy Does this create a 1ph, 2ph, and 3ph version of equipment components where the phasing isn't immediately apparent? It would be more work but I think referencing network and then section to know phasing would be better to avoid extra equipment creation.

# TODO: Understand how this is actually represented
return Distance(0.001,'mm')

def map_insulation_diameter(self, row, equipment_file):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tarekelgindy Should we look to take another pass on these functions?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants