Conversation
…ch objects in CYME
…sed since spacings not provided in CYME
…ting zero length lines to be small instead
…s in different versions of cyme
…fferent versions of cyme
…alidation in gdm first...
There was a problem hiding this comment.
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.
| try: | ||
| mapping_function = getattr(self, "map_" + field) | ||
| mapping_function() | ||
| except Exception as e: | ||
| print(f"{self.model.label} - {field}") |
There was a problem hiding this comment.
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.
|
|
||
| def map_conductor_diameter(self): | ||
| radius = self.model.conductor_diameter.magnitude / 2 | ||
| if radius <=0: |
There was a problem hiding this comment.
The comparison operators should have spaces around them. 'radius <=0' should be 'radius <= 0' for consistency with Python PEP 8 style guidelines.
src/ditto/readers/cyme/equipment/distribution_transformer_equipment.py
Outdated
Show resolved
Hide resolved
| mapping_function = getattr(self, "map_" + field) | ||
| mapping_function() | ||
| except Exception as e: | ||
| print(f"{self.model.label} - {field}") |
There was a problem hiding this comment.
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.
| from_bus = None | ||
| try: | ||
| from_bus = self.system.get_component(component_type=DistributionBus,name=from_bus_name) | ||
| except Exception as e: |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
Corrected spelling of 'in_serivce' to 'in_service'.
| separate_substations: bool = True, | ||
| separate_feeders: bool = True, | ||
| ): | ||
| output_folder = output_path |
There was a problem hiding this comment.
Variable output_folder is assigned but then reassigned on line 90 without being used. This initial assignment appears redundant and should be removed.
tarekelgindy
left a comment
There was a problem hiding this comment.
looots of comments.
Many are for clarity but a good number of changes should probably be made.
tests/test_cyme/test_cyme_reader.py
Outdated
| 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') |
There was a problem hiding this comment.
Let's have a value for load_model_id ('1') here, instead of providing it directly, since some models might use 0
There was a problem hiding this comment.
@tarekelgindy I've tried to address but not exactly sure what this means.
|
|
||
| return profile_name | ||
| try: | ||
| mapping_function = getattr(self, "map_" + field) |
There was a problem hiding this comment.
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(".", "_") |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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(".", "_") |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
Should we always assume a full impedance load?
src/ditto/readers/cyme/reader.py
Outdated
| "MatrixImpedanceFuseEquipmentMapper", | ||
| ] | ||
| ) | ||
| default_conductor = BareConductorEquipment( |
There was a problem hiding this comment.
Should we use a rigid set of values like this?
There was a problem hiding this comment.
@tarekelgindy We can remove this I think. I've never used it. Have you?
| components = self.system.get_components(component_type) | ||
| self._add_components(components) | ||
|
|
||
| self._validate_model() |
There was a problem hiding this comment.
This seems like a sensible place to fill missing values using GDM MDC rather than having them be done in-line for many cases.
src/ditto/readers/cyme/reader.py
Outdated
|
|
||
| for vsource in voltage_sources: | ||
| vsource.bus.rated_voltage = ( | ||
| vsource.equipment.sources[0].voltage * 1.732 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Actually... is this where voltage type checking is being done?
There was a problem hiding this comment.
Yes, this function, (which has since been updated slightly) is responsible for bus voltage assignment and wrangles most of this.
tarekelgindy
left a comment
There was a problem hiding this comment.
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"]: |
There was a problem hiding this comment.
@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): |
There was a problem hiding this comment.
@tarekelgindy Should we look to take another pass on these functions?
No description provided.