DiTTo#

DiTTo is the Distribution Transformation Tool. It is an open source tool to convert and modify electrical distribution system models. DiTTo implements a many-to-one-to-many parsing framework, making it modular and robust. The reader modules parse data files of distribution system format (e.g. OpenDSS) and create an object for each electrical component. These objects are stored in a GDM DistributionSystem instance. The writer modules are then used to export the data stored in memory to a selected output distribution system format (e.g. OpenDSS) which are written to disk.

For model conversion. We will need to install DiTto. To do that, use the command below

pip install NREL-ditto opendssdirect.py 
Requirement already satisfied: NREL-ditto in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (0.0.9)
Requirement already satisfied: opendssdirect.py in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (0.9.4)
Requirement already satisfied: grid-data-models==2.0.1 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from NREL-ditto) (2.0.1)
Requirement already satisfied: nrel-altdss-schema==0.0.1 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from NREL-ditto) (0.0.1)
Requirement already satisfied: rdflib in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from NREL-ditto) (7.1.4)
Requirement already satisfied: click~=8.1.7 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from grid-data-models==2.0.1->NREL-ditto) (8.1.8)
Requirement already satisfied: geopandas in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from grid-data-models==2.0.1->NREL-ditto) (1.0.1)
Requirement already satisfied: importlib-metadata in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from grid-data-models==2.0.1->NREL-ditto) (8.6.1)
Requirement already satisfied: infrasys~=0.4.0 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from grid-data-models==2.0.1->NREL-ditto) (0.4.0)
Requirement already satisfied: networkx in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from grid-data-models==2.0.1->NREL-ditto) (3.4.2)
Requirement already satisfied: pandas~=2.2.3 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from grid-data-models==2.0.1->NREL-ditto) (2.2.3)
Requirement already satisfied: plotly in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from grid-data-models==2.0.1->NREL-ditto) (6.0.1)
Requirement already satisfied: pydantic~=2.10.6 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from grid-data-models==2.0.1->NREL-ditto) (2.10.6)
Requirement already satisfied: dss-python==0.15.7 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from opendssdirect.py) (0.15.7)
Requirement already satisfied: dss-python-backend==0.14.5 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from dss-python==0.15.7->opendssdirect.py) (0.14.5)
Requirement already satisfied: numpy>=1.21.0 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from dss-python==0.15.7->opendssdirect.py) (2.2.5)
Requirement already satisfied: typing-extensions<5,>=4.5 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from dss-python==0.15.7->opendssdirect.py) (4.13.2)
Requirement already satisfied: cffi>=1.11.2 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from dss-python-backend==0.14.5->dss-python==0.15.7->opendssdirect.py) (1.17.1)
Requirement already satisfied: pyparsing<4,>=2.1.0 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from rdflib->NREL-ditto) (3.2.3)
Requirement already satisfied: loguru~=0.7.2 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from infrasys~=0.4.0->grid-data-models==2.0.1->NREL-ditto) (0.7.3)
Requirement already satisfied: pint~=0.23 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from infrasys~=0.4.0->grid-data-models==2.0.1->NREL-ditto) (0.24.4)
Requirement already satisfied: pyarrow~=19.0 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from infrasys~=0.4.0->grid-data-models==2.0.1->NREL-ditto) (19.0.1)
Requirement already satisfied: rich~=13.7.1 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from infrasys~=0.4.0->grid-data-models==2.0.1->NREL-ditto) (13.7.1)
Requirement already satisfied: python-dateutil>=2.8.2 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from pandas~=2.2.3->grid-data-models==2.0.1->NREL-ditto) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from pandas~=2.2.3->grid-data-models==2.0.1->NREL-ditto) (2025.2)
Requirement already satisfied: tzdata>=2022.7 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from pandas~=2.2.3->grid-data-models==2.0.1->NREL-ditto) (2025.2)
Requirement already satisfied: annotated-types>=0.6.0 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from pydantic~=2.10.6->grid-data-models==2.0.1->NREL-ditto) (0.7.0)
Requirement already satisfied: pydantic-core==2.27.2 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from pydantic~=2.10.6->grid-data-models==2.0.1->NREL-ditto) (2.27.2)
Requirement already satisfied: pyogrio>=0.7.2 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from geopandas->grid-data-models==2.0.1->NREL-ditto) (0.10.0)
Requirement already satisfied: packaging in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from geopandas->grid-data-models==2.0.1->NREL-ditto) (25.0)
Requirement already satisfied: pyproj>=3.3.0 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from geopandas->grid-data-models==2.0.1->NREL-ditto) (3.7.1)
Requirement already satisfied: shapely>=2.0.0 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from geopandas->grid-data-models==2.0.1->NREL-ditto) (2.1.0)
Requirement already satisfied: zipp>=3.20 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from importlib-metadata->grid-data-models==2.0.1->NREL-ditto) (3.21.0)
Requirement already satisfied: narwhals>=1.15.1 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from plotly->grid-data-models==2.0.1->NREL-ditto) (1.36.0)
Requirement already satisfied: pycparser in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from cffi>=1.11.2->dss-python-backend==0.14.5->dss-python==0.15.7->opendssdirect.py) (2.22)
Requirement already satisfied: platformdirs>=2.1.0 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from pint~=0.23->infrasys~=0.4.0->grid-data-models==2.0.1->NREL-ditto) (4.3.7)
Requirement already satisfied: flexcache>=0.3 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from pint~=0.23->infrasys~=0.4.0->grid-data-models==2.0.1->NREL-ditto) (0.3)
Requirement already satisfied: flexparser>=0.4 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from pint~=0.23->infrasys~=0.4.0->grid-data-models==2.0.1->NREL-ditto) (0.4)
Requirement already satisfied: certifi in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from pyogrio>=0.7.2->geopandas->grid-data-models==2.0.1->NREL-ditto) (2025.4.26)
Requirement already satisfied: six>=1.5 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from python-dateutil>=2.8.2->pandas~=2.2.3->grid-data-models==2.0.1->NREL-ditto) (1.17.0)
Requirement already satisfied: markdown-it-py>=2.2.0 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from rich~=13.7.1->infrasys~=0.4.0->grid-data-models==2.0.1->NREL-ditto) (3.0.0)
Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from rich~=13.7.1->infrasys~=0.4.0->grid-data-models==2.0.1->NREL-ditto) (2.19.1)
Requirement already satisfied: mdurl~=0.1 in /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages (from markdown-it-py>=2.2.0->rich~=13.7.1->infrasys~=0.4.0->grid-data-models==2.0.1->NREL-ditto) (0.1.2)
Note: you may need to restart the kernel to use updated packages.

We start by loading a sample GDM system using the gdmloader.

from gdm.distribution import DistributionSystem
from gdmloader.constants import GCS_CASE_SOURCE
from gdmloader.source import SystemLoader

gdm_loader = SystemLoader()
gdm_loader.add_source(GCS_CASE_SOURCE)

distribution_system: DistributionSystem = gdm_loader.load_dataset(
    source_name=GCS_CASE_SOURCE.name, 
    system_type=DistributionSystem, 
    dataset_name="p5r",
)
distribution_system.name = "P5R"
/opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py:502: UserWarning: Ellipsis is not a Python type (it may be an instance of an object), Pydantic will allow any object with no validation since we cannot even enforce that the input is an instance of the given type. To get rid of this error wrap the type with `pydantic.SkipValidation`.
  warn(
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[2], line 2
      1 from gdm.distribution import DistributionSystem
----> 2 from gdmloader.constants import GCS_CASE_SOURCE
      3 from gdmloader.source import SystemLoader
      5 gdm_loader = SystemLoader()

File /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages/gdmloader/constants.py:13
      3 import fsspec
      5 GDM_CASE_SOURCE = SourceModel(
      6     fs=fsspec.filesystem("github", org="NREL-Distribution-Suites", repo="gdm-cases", branch="main"),
      7     name="gdm-cases",
      8     url="https://github.com/NREL-Distribution-Suites/gdm-cases",
      9     folder="data",
     10 )
     12 GCS_CASE_SOURCE = SourceModel(
---> 13     fs=fsspec.filesystem("gcs"),
     14     name="gdm_data",
     15     url="https://storage.googleapis.com/gdm_data",
     16     folder="data",
     17 )

File /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages/fsspec/registry.py:310, in filesystem(protocol, **storage_options)
    303     warnings.warn(
    304         "The 'arrow_hdfs' protocol has been deprecated and will be "
    305         "removed in the future. Specify it as 'hdfs'.",
    306         DeprecationWarning,
    307     )
    309 cls = get_filesystem_class(protocol)
--> 310 return cls(**storage_options)

File /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages/fsspec/spec.py:81, in _Cached.__call__(cls, *args, **kwargs)
     79     return cls._cache[token]
     80 else:
---> 81     obj = super().__call__(*args, **kwargs)
     82     # Setting _fs_token here causes some static linters to complain.
     83     obj._fs_token_ = token

File /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages/gcsfs/core.py:333, in GCSFileSystem.__init__(self, project, access, token, block_size, consistency, cache_timeout, secure_serialize, check_connection, requests_timeout, requester_pays, asynchronous, session_kwargs, loop, timeout, endpoint_url, default_location, version_aware, **kwargs)
    327 if check_connection:
    328     warnings.warn(
    329         "The `check_connection` argument is deprecated and will be removed in a future release.",
    330         DeprecationWarning,
    331     )
--> 333 self.credentials = GoogleCredentials(project, access, token)

File /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages/gcsfs/credentials.py:53, in GoogleCredentials.__init__(self, project, access, token, check_credentials)
     51 self.lock = threading.Lock()
     52 self.token = token
---> 53 self.connect(method=token)
     55 if check_credentials:
     56     warnings.warn(
     57         "The `check_credentials` argument is deprecated and will be removed in a future release.",
     58         DeprecationWarning,
     59     )

File /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages/gcsfs/credentials.py:262, in GoogleCredentials.connect(self, method)
    260 for meth in ["google_default", "cache", "cloud", "anon"]:
    261     try:
--> 262         self.connect(method=meth)
    263         logger.debug("Connected with method %s", meth)
    264         break

File /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages/gcsfs/credentials.py:279, in GoogleCredentials.connect(self, method)
    277         raise RuntimeError("All connection methods have failed!")
    278 else:
--> 279     self.__getattribute__("_connect_" + method)()
    280     self.method = method

File /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages/gcsfs/credentials.py:100, in GoogleCredentials._connect_cloud(self)
     98     with requests.Session() as session:
     99         req = Request(session)
--> 100         self.credentials.refresh(req)
    101 except gauth.exceptions.RefreshError as error:
    102     raise ValueError("Invalid gcloud credentials") from error

File /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages/google/auth/compute_engine/credentials.py:126, in Credentials.refresh(self, request)
    124 scopes = self._scopes if self._scopes is not None else self._default_scopes
    125 try:
--> 126     self._retrieve_info(request)
    127     self.token, self.expiry = _metadata.get_service_account_token(
    128         request, service_account=self._service_account_email, scopes=scopes
    129     )
    130 except exceptions.TransportError as caught_exc:

File /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages/google/auth/compute_engine/credentials.py:99, in Credentials._retrieve_info(self, request)
     90 def _retrieve_info(self, request):
     91     """Retrieve information about the service account.
     92 
     93     Updates the scopes and retrieves the full service account email.
   (...)     97             HTTP requests.
     98     """
---> 99     info = _metadata.get_service_account_info(
    100         request, service_account=self._service_account_email
    101     )
    103     self._service_account_email = info["email"]
    105     # Don't override scopes requested by the user.

File /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages/google/auth/compute_engine/_metadata.py:342, in get_service_account_info(request, service_account)
    339 path = "instance/service-accounts/{0}/".format(service_account)
    340 # See https://cloud.google.com/compute/docs/metadata#aggcontents
    341 # for more on the use of 'recursive'.
--> 342 return get(request, path, params={"recursive": "true"})

File /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages/google/auth/compute_engine/_metadata.py:207, in get(request, path, root, params, recursive, retry_count, headers, return_none_for_not_found_error, timeout)
    205 backoff = ExponentialBackoff(total_attempts=retry_count)
    206 failure_reason = None
--> 207 for attempt in backoff:
    208     try:
    209         response = request(
    210             url=url, method="GET", headers=headers_to_use, timeout=timeout
    211         )

File /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages/google/auth/_exponential_backoff.py:133, in ExponentialBackoff.__next__(self)
    129     return self._backoff_count
    131 jitter = self._calculate_jitter()
--> 133 time.sleep(jitter)
    135 self._current_wait_in_seconds *= self._multiplier
    136 return self._backoff_count

KeyboardInterrupt: 

Once installed, a GDM model can be loaded into memory and converted to any of the supported formats using one of ditto writers

from pathlib import Path
from ditto.writers.opendss.write import Writer


writer = Writer(distribution_system)
writer.write(output_path=Path("."), separate_substations=False, separate_feeders=False)
2025-05-02 11:06:28.929 | WARNING  | ditto.writers.opendss.write:write:80 - Mapper VoltageLimitSetMapper not found. Skipping
2025-05-02 11:06:28.929 | WARNING  | ditto.writers.opendss.write:write:80 - Mapper LocationMapper not found. Skipping
2025-05-02 11:06:28.929 | WARNING  | ditto.writers.opendss.write:write:80 - Mapper PhaseVoltageSourceEquipmentMapper not found. Skipping
2025-05-02 11:06:28.930 | WARNING  | ditto.writers.opendss.write:write:80 - Mapper PhaseLoadEquipmentMapper not found. Skipping
2025-05-02 11:06:28.930 | WARNING  | ditto.writers.opendss.write:write:80 - Mapper InverterEquipmentMapper not found. Skipping
2025-05-02 11:06:28.930 | WARNING  | ditto.writers.opendss.write:write:80 - Mapper SolarEquipmentMapper not found. Skipping
2025-05-02 11:06:28.930 | WARNING  | ditto.writers.opendss.write:write:80 - Mapper WindingEquipmentMapper not found. Skipping
2025-05-02 11:06:28.931 | DEBUG    | ditto.writers.opendss.write:write:83 - Mapping components in MatrixImpedanceSwitchEquipmentMapper...
2025-05-02 11:06:28.931 | DEBUG    | ditto.writers.opendss.write:write:83 - Mapping components in MatrixImpedanceBranchEquipmentMapper...
2025-05-02 11:06:28.931 | DEBUG    | ditto.writers.opendss.write:write:83 - Mapping components in DistributionBusMapper...
2025-05-02 11:06:28.935 | WARNING  | ditto.writers.opendss.write:write:80 - Mapper VoltageSourceEquipmentMapper not found. Skipping
2025-05-02 11:06:28.935 | WARNING  | ditto.writers.opendss.write:write:80 - Mapper LoadEquipmentMapper not found. Skipping
2025-05-02 11:06:28.935 | DEBUG    | ditto.writers.opendss.write:write:83 - Mapping components in DistributionTransformerEquipmentMapper...
2025-05-02 11:06:28.935 | DEBUG    | ditto.writers.opendss.write:write:83 - Mapping components in DistributionVoltageSourceMapper...
2025-05-02 11:06:28.936 | WARNING  | ditto.writers.opendss.write:write:109 - Equipment Mapper VoltageSourceEquipmentMapper not found. Skipping
2025-05-02 11:06:28.936 | DEBUG    | ditto.writers.opendss.write:write:83 - Mapping components in DistributionLoadMapper...
2025-05-02 11:06:28.937 | WARNING  | ditto.writers.opendss.write:write:109 - Equipment Mapper LoadEquipmentMapper not found. Skipping
2025-05-02 11:06:28.939 | WARNING  | ditto.writers.opendss.write:write:109 - Equipment Mapper LoadEquipmentMapper not found. Skipping
2025-05-02 11:06:28.939 | WARNING  | ditto.writers.opendss.write:write:80 - Mapper DistributionSolarMapper not found. Skipping
2025-05-02 11:06:28.939 | DEBUG    | ditto.writers.opendss.write:write:83 - Mapping components in DistributionTransformerMapper...
2025-05-02 11:06:28.941 | DEBUG    | ditto.writers.opendss.write:write:83 - Mapping components in MatrixImpedanceBranchMapper...
2025-05-02 11:06:28.949 | DEBUG    | ditto.writers.opendss.write:write:83 - Mapping components in MatrixImpedanceSwitchMapper...

In this example, the OpenDSS model contains intentional errors such as missing bus definitions, incorrect syntax for loads, and incorrect capacitor connections. The Python script uses GDM’s GDMReader to attempt to read the model, which should identify and report these issues.

from ditto.readers.opendss.reader import Reader
from opendssdirect import dss

dss("""
    clear
    new Circuit.test bus1=bus_1.1 BasekV=7.2 pu=1.03 Angle=0.0 Phases=1 Z1=[1e-05, 1e-05] Z0=[1e-05, 1e-05]
    new line.line_1  bus1=bus_1.1 bus2=bus_2.2 phases=1
    solve
""")
dss.Circuit.Save("test_circuit", options=0)

reader = Reader("test_circuit/Master.dss")
gdm_system = reader.get_system()
2025-05-02 11:06:29.176 | DEBUG    | ditto.readers.opendss.reader:_read:61 - Loading OpenDSS model.
2025-05-02 11:06:29.180 | DEBUG    | ditto.readers.opendss.reader:_read:70 - Model loaded from test_circuit/Master.dss.
2025-05-02 11:06:29.181 | DEBUG    | ditto.readers.opendss.components.buses:get_buses:22 - parsing bus components...
2025-05-02 11:06:29.182 | DEBUG    | ditto.readers.opendss.components.sources:get_voltage_sources:88 - parsing voltage sources components...
2025-05-02 11:06:29.183 | DEBUG    | ditto.readers.opendss.components.loadshapes:build_profiles:103 - parsing timeseries components...
2025-05-02 11:06:29.183 | DEBUG    | ditto.readers.opendss.components.capacitors:get_capacitors:89 - parsing capacitor components...
2025-05-02 11:06:29.184 | DEBUG    | ditto.readers.opendss.components.loads:get_loads:95 - parsing load components...
2025-05-02 11:06:29.184 | DEBUG    | ditto.readers.opendss.components.pv_systems:get_pvsystems:83 - parsing pvsystem components...
2025-05-02 11:06:29.185 | DEBUG    | ditto.readers.opendss.components.transformers:get_transformer_equipments:149 - parsing transformer equipment...
2025-05-02 11:06:29.185 | DEBUG    | ditto.readers.opendss.components.transformers:get_transformers:182 - parsing transformer components...
2025-05-02 11:06:29.185 | DEBUG    | ditto.readers.opendss.components.conductors:get_conductors_equipment:16 - parsing conductor components...
2025-05-02 11:06:29.186 | DEBUG    | ditto.readers.opendss.components.cables:get_cables_equipment:22 - parsing cable components...
2025-05-02 11:06:29.186 | DEBUG    | ditto.readers.opendss.components.branches:get_matrix_branch_equipments:195 - parsing matrix branch equipment...
2025-05-02 11:06:29.189 | DEBUG    | ditto.readers.opendss.components.branches:get_geometry_branch_equipments:67 - parsing geometry branch equipment...
2025-05-02 11:06:29.189 | DEBUG    | ditto.readers.opendss.components.branches:get_branches:263 - parsing branch components...
2025-05-02 11:06:29.190 | DEBUG    | ditto.readers.opendss.components.branches:get_branches:269 - building line Line.line_1...
---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
Cell In[3], line 12
      4 dss("""
      5     clear
      6     new Circuit.test bus1=bus_1.1 BasekV=7.2 pu=1.03 Angle=0.0 Phases=1 Z1=[1e-05, 1e-05] Z0=[1e-05, 1e-05]
      7     new line.line_1  bus1=bus_1.1 bus2=bus_2.2 phases=1
      8     solve
      9 """)
     10 dss.Circuit.Save("test_circuit", options=0)
---> 12 reader = Reader("test_circuit/Master.dss")
     13 gdm_system = reader.get_system()

File ~/Documents/GitHub/ditto/src/ditto/readers/opendss/reader.py:47, in Reader.__init__(self, Opendss_master_file, crs)
     45 self.Opendss_master_file = Path(Opendss_master_file)
     46 self.crs = crs
---> 47 self._read()

File ~/Documents/GitHub/ditto/src/ditto/readers/opendss/reader.py:99, in Reader._read(self)
     95 geometry_branch_equipment_catalog, mapped_geometry = get_geometry_branch_equipments(
     96     self.system
     97 )
     98 self._add_components(geometry_branch_equipment_catalog.values())
---> 99 branches = get_branches(
    100     self.system,
    101     mapped_geometry,
    102     geometry_branch_equipment_catalog,
    103     matrix_branch_equipments_catalog,
    104     thermal_limit_catalog,
    105 )
    106 self._add_components(branches)
    108 logger.debug("parsing complete...")

File ~/Documents/GitHub/ditto/src/ditto/readers/opendss/components/branches.py:344, in get_branches(system, mapping, geometry_branch_equipment_catalog, matrix_branch_equipments_catalog, thermal_limit_catalog)
    339     if model_class in [MatrixImpedanceSwitch, MatrixImpedanceFuse]:
    340         model_dict["is_closed"] = [
    341             not (odd.CktElement.IsOpen(1, node + 1) or odd.CktElement.IsOpen(2, node + 1))
    342             for node in range(len(nodes))
    343         ]
--> 344     matrix_branch = model_class(**model_dict)
    345     branches.append(matrix_branch)
    346 flag = odd.Lines.Next()

File /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages/pydantic/main.py:214, in BaseModel.__init__(self, **data)
    212 # `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks
    213 __tracebackhide__ = True
--> 214 validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
    215 if self is not validated_self:
    216     warnings.warn(
    217         'A custom validator is returning a value other than `self`.\n'
    218         "Returning anything other than `self` from a top level model validator isn't supported when validating via `__init__`.\n"
    219         'See the `model_validator` docs (https://docs.pydantic.dev/latest/concepts/validators/#model-validators) for more details.',
    220         stacklevel=2,
    221     )

ValidationError: 1 validation error for MatrixImpedanceBranch
  Value error, Conductor phase (phase=<Phase.A: 'A'>) does not match bus phases (bus.phases=[<Phase.B: 'B'>]) [type=value_error, input_value={'name': 'line_1', 'buses...tity(400.0, 'ampere')>)}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.10/v/value_error
from ditto.readers.opendss.reader import Reader
from opendssdirect import dss

dss("""
    clear
    new Circuit.test bus1=bus_1.1 BasekV=7.2 pu=1.03 Angle=0.0 Phases=1 Z1=[1e-05, 1e-05] Z0=[1e-05, 1e-05]
    new line.line_1  bus1=bus_1.1 bus2=bus_2.1 phases=1 length=-10
    solve
""")
dss.Circuit.Save("test_circuit", options=0)

reader = Reader("test_circuit/Master.dss")
gdm_system = reader.get_system()
2025-05-02 11:07:47.071 | DEBUG    | ditto.readers.opendss.reader:_read:61 - Loading OpenDSS model.
2025-05-02 11:07:47.075 | DEBUG    | ditto.readers.opendss.reader:_read:70 - Model loaded from test_circuit/Master.dss.
2025-05-02 11:07:47.076 | DEBUG    | ditto.readers.opendss.components.buses:get_buses:22 - parsing bus components...
2025-05-02 11:07:47.077 | DEBUG    | ditto.readers.opendss.components.sources:get_voltage_sources:88 - parsing voltage sources components...
2025-05-02 11:07:47.078 | DEBUG    | ditto.readers.opendss.components.loadshapes:build_profiles:103 - parsing timeseries components...
2025-05-02 11:07:47.078 | DEBUG    | ditto.readers.opendss.components.capacitors:get_capacitors:89 - parsing capacitor components...
2025-05-02 11:07:47.078 | DEBUG    | ditto.readers.opendss.components.loads:get_loads:95 - parsing load components...
2025-05-02 11:07:47.079 | DEBUG    | ditto.readers.opendss.components.pv_systems:get_pvsystems:83 - parsing pvsystem components...
2025-05-02 11:07:47.079 | DEBUG    | ditto.readers.opendss.components.transformers:get_transformer_equipments:149 - parsing transformer equipment...
2025-05-02 11:07:47.079 | DEBUG    | ditto.readers.opendss.components.transformers:get_transformers:182 - parsing transformer components...
2025-05-02 11:07:47.079 | DEBUG    | ditto.readers.opendss.components.conductors:get_conductors_equipment:16 - parsing conductor components...
2025-05-02 11:07:47.083 | DEBUG    | ditto.readers.opendss.components.cables:get_cables_equipment:22 - parsing cable components...
2025-05-02 11:07:47.084 | DEBUG    | ditto.readers.opendss.components.branches:get_matrix_branch_equipments:195 - parsing matrix branch equipment...
2025-05-02 11:07:47.090 | DEBUG    | ditto.readers.opendss.components.branches:get_geometry_branch_equipments:67 - parsing geometry branch equipment...
2025-05-02 11:07:47.091 | DEBUG    | ditto.readers.opendss.components.branches:get_branches:263 - parsing branch components...
2025-05-02 11:07:47.093 | DEBUG    | ditto.readers.opendss.components.branches:get_branches:269 - building line Line.line_1...
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
Cell In[4], line 12
      4 dss("""
      5     clear
      6     new Circuit.test bus1=bus_1.1 BasekV=7.2 pu=1.03 Angle=0.0 Phases=1 Z1=[1e-05, 1e-05] Z0=[1e-05, 1e-05]
      7     new line.line_1  bus1=bus_1.1 bus2=bus_2.1 phases=1 length=-10
      8     solve
      9 """)
     10 dss.Circuit.Save("test_circuit", options=0)
---> 12 reader = Reader("test_circuit/Master.dss")
     13 gdm_system = reader.get_system()

File ~/Documents/GitHub/ditto/src/ditto/readers/opendss/reader.py:47, in Reader.__init__(self, Opendss_master_file, crs)
     45 self.Opendss_master_file = Path(Opendss_master_file)
     46 self.crs = crs
---> 47 self._read()

File ~/Documents/GitHub/ditto/src/ditto/readers/opendss/reader.py:99, in Reader._read(self)
     95 geometry_branch_equipment_catalog, mapped_geometry = get_geometry_branch_equipments(
     96     self.system
     97 )
     98 self._add_components(geometry_branch_equipment_catalog.values())
---> 99 branches = get_branches(
    100     self.system,
    101     mapped_geometry,
    102     geometry_branch_equipment_catalog,
    103     matrix_branch_equipments_catalog,
    104     thermal_limit_catalog,
    105 )
    106 self._add_components(branches)
    108 logger.debug("parsing complete...")

File ~/Documents/GitHub/ditto/src/ditto/readers/opendss/components/branches.py:335, in get_branches(system, mapping, geometry_branch_equipment_catalog, matrix_branch_equipments_catalog, thermal_limit_catalog)
    318 equipment = _build_matrix_branch(
    319     MatrixBranchTypes.LINE.value,
    320     matrix_branch_equipments_catalog,
   (...)    324     recloser,
    325 )
    326 equipment = get_equipment_from_catalog(
    327     equipment, matrix_branch_equipments_catalog, equipment_class.__name__
    328 )
    329 model_dict = {
    330     "name": odd.Lines.Name().lower(),
    331     "buses": [
    332         system.get_component(DistributionBus, bus1),
    333         system.get_component(DistributionBus, bus2),
    334     ],
--> 335     "length": PositiveDistance(odd.Lines.Length(), UNIT_MAPPER[odd.Lines.Units()]),
    336     "phases": [PHASE_MAPPER[node] for node in nodes],
    337     "equipment": equipment,
    338 }
    339 if model_class in [MatrixImpedanceSwitch, MatrixImpedanceFuse]:
    340     model_dict["is_closed"] = [
    341         not (odd.CktElement.IsOpen(1, node + 1) or odd.CktElement.IsOpen(2, node + 1))
    342         for node in range(len(nodes))
    343     ]

File ~/Documents/GitHub/grid-data-models/src/gdm/quantities.py:180, in PositiveDistance.__init__(self, value, units, **kwargs)
    179 def __init__(self, value, units, **kwargs):
--> 180     assert all(
    181         np.array(value).flatten() >= 0
    182     ), f"Distance ({value}, {units}) must be positive."

AssertionError: Distance (-10.0, m) must be positive.
from ditto.readers.opendss.reader import Reader
from opendssdirect import dss

dss("""
clear
new Circuit.test bus1=bus_1.1 BasekV=7.2 pu=1.03 Angle=0.0 Phases=1 Z1=[1e-05, 1e-05] Z0=[1e-05, 1e-05]
new line.line_1  bus1=bus_1.1 bus2=bus_2.1 phases=1
new load.load_1  bus=bus_2.1.2.3 phases=1
solve
""")
dss.Circuit.Save("test_circuit", options=0)

reader = Reader("test_circuit/Master.dss")
/opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages/pydantic/_internal/_generate_schema.py:502: UserWarning: Ellipsis is not a Python type (it may be an instance of an object), Pydantic will allow any object with no validation since we cannot even enforce that the input is an instance of the given type. To get rid of this error wrap the type with `pydantic.SkipValidation`.
  warn(
2025-05-01 15:23:51.732 | DEBUG    | ditto.readers.opendss.reader:_read:61 - Loading OpenDSS model.
2025-05-01 15:23:51.737 | DEBUG    | ditto.readers.opendss.reader:_read:70 - Model loaded from test_circuit/Master.dss.
2025-05-01 15:23:51.738 | DEBUG    | ditto.readers.opendss.components.buses:get_buses:22 - parsing bus components...
2025-05-01 15:23:51.739 | DEBUG    | ditto.readers.opendss.components.sources:get_voltage_sources:88 - parsing voltage sources components...
2025-05-01 15:23:51.740 | DEBUG    | ditto.readers.opendss.components.loadshapes:build_profiles:103 - parsing timeseries components...
2025-05-01 15:23:51.741 | DEBUG    | ditto.readers.opendss.components.capacitors:get_capacitors:89 - parsing capacitor components...
2025-05-01 15:23:51.741 | DEBUG    | ditto.readers.opendss.components.loads:get_loads:95 - parsing load components...
2025-05-01 15:23:51.741 | DEBUG    | ditto.readers.opendss.components.loadshapes:build_profiles:103 - parsing timeseries components...
---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
Cell In[1], line 13
      4 dss("""
      5 clear
      6 new Circuit.test bus1=bus_1.1 BasekV=7.2 pu=1.03 Angle=0.0 Phases=1 Z1=[1e-05, 1e-05] Z0=[1e-05, 1e-05]
   (...)      9 solve
     10 """)
     11 dss.Circuit.Save("test_circuit", options=0)
---> 13 reader = Reader("test_circuit/Master.dss")

File ~/Documents/GitHub/ditto/src/ditto/readers/opendss/reader.py:47, in Reader.__init__(self, Opendss_master_file, crs)
     45 self.Opendss_master_file = Path(Opendss_master_file)
     46 self.crs = crs
---> 47 self._read()

File ~/Documents/GitHub/ditto/src/ditto/readers/opendss/reader.py:77, in Reader._read(self)
     75 self._add_components(get_voltage_sources(self.system))
     76 self._add_components(get_capacitors(self.system))
---> 77 self._add_components(get_loads(self.system))
     78 self._add_components(get_pvsystems(self.system))
     79 (
     80     distribution_transformer_equipment_catalog,
     81     winding_equipment_catalog,
     82 ) = get_transformer_equipments(self.system)

File ~/Documents/GitHub/ditto/src/ditto/readers/opendss/components/loads.py:106, in get_loads(system)
    104 profile_names = [odd.Loads.Daily(), odd.Loads.Yearly(), odd.Loads.Duty()]
    105 profiles = build_profiles(profile_names, ObjectsWithProfile.LOAD, profile_catalog)
--> 106 distribution_load = DistributionLoad(
    107     name=load_name,
    108     bus=system.get_component(DistributionBus, bus1),
    109     phases=[PHASE_MAPPER[el] for el in nodes],
    110     equipment=LoadEquipment,
    111 )
    112 for profile_name in profile_names:
    113     if profile_name in profiles:

File /opt/homebrew/Caskroom/miniconda/base/envs/gdm2/lib/python3.12/site-packages/pydantic/main.py:214, in BaseModel.__init__(self, **data)
    212 # `__tracebackhide__` tells pytest and some other tools to omit this function from tracebacks
    213 __tracebackhide__ = True
--> 214 validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
    215 if self is not validated_self:
    216     warnings.warn(
    217         'A custom validator is returning a value other than `self`.\n'
    218         "Returning anything other than `self` from a top level model validator isn't supported when validating via `__init__`.\n"
    219         'See the `model_validator` docs (https://docs.pydantic.dev/latest/concepts/validators/#model-validators) for more details.',
    220         stacklevel=2,
    221     )

ValidationError: 1 validation error for DistributionLoad
  Value error, Loads phases self.phases=[<Phase.A: 'A'>, <Phase.B: 'B'>, <Phase.C: 'C'>] must be subset of bus phases. [<Phase.A: 'A'>, <Phase.B: 'B'>] [type=value_error, input_value={'name': 'load_1', 'bus':...tionType.STAR: 'STAR'>)}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.10/v/value_error

Validation Summary Via Monkey Patching#

from pydantic import ValidationError
from rich.console import Console
from infrasys import Component
from rich.table import Table

validation_errors = []
_original_init = Component.__init__
def patched_init(self, *args, **kwargs):
    try:
        _original_init(self, *args, **kwargs)
    except ValidationError as e:
        for error in e.errors():   
            validation_errors.append([
                kwargs['name'],
                self.__class__.__name__,
                error['loc'] if error['loc'] else "On model validation",
                error['type'],
                error['msg']
            ]) 


Component.__init__ = patched_init

from ditto.readers.opendss.reader import Reader
from opendssdirect import dss

dss("""
    clear
    new Circuit.test bus1=bus_1.1 BasekV=7.2 pu=1.03 Angle=0.0 Phases=1 Z1=[1e-05, 1e-05] Z0=[1e-05, 1e-05]
    new line.line_1  bus1=bus_1.1 bus2=bus_2.2 phases=3
    solve
""")
dss.Circuit.Save("test_circuit", options=0)

reader = Reader("test_circuit/Master.dss")

if validation_errors:
    error_table = Table(title="Validation warning summary")
    error_table.add_column("Model", justify="right", style="cyan", no_wrap=True)
    error_table.add_column("Type", style="green")
    error_table.add_column("Field", justify="right", style="bright_magenta")
    error_table.add_column("Error", style="bright_red")
    error_table.add_column("Message", justify="right", style="turquoise2")

    for row in validation_errors:
        error_table.add_row(*row)
    
    console = Console()
    console.print(error_table)

    raise Exception(f"Validations errors occured when running the script. See the table above")
2025-05-02 11:07:58.868 | DEBUG    | ditto.readers.opendss.reader:_read:61 - Loading OpenDSS model.
2025-05-02 11:07:58.873 | DEBUG    | ditto.readers.opendss.reader:_read:70 - Model loaded from test_circuit/Master.dss.
2025-05-02 11:07:58.873 | DEBUG    | ditto.readers.opendss.components.buses:get_buses:22 - parsing bus components...
2025-05-02 11:07:58.875 | DEBUG    | ditto.readers.opendss.components.sources:get_voltage_sources:88 - parsing voltage sources components...
2025-05-02 11:07:58.875 | DEBUG    | ditto.readers.opendss.components.loadshapes:build_profiles:103 - parsing timeseries components...
2025-05-02 11:07:58.877 | DEBUG    | ditto.readers.opendss.components.capacitors:get_capacitors:89 - parsing capacitor components...
2025-05-02 11:07:58.877 | DEBUG    | ditto.readers.opendss.components.loads:get_loads:95 - parsing load components...
2025-05-02 11:07:58.877 | DEBUG    | ditto.readers.opendss.components.pv_systems:get_pvsystems:83 - parsing pvsystem components...
2025-05-02 11:07:58.877 | DEBUG    | ditto.readers.opendss.components.transformers:get_transformer_equipments:149 - parsing transformer equipment...
2025-05-02 11:07:58.877 | DEBUG    | ditto.readers.opendss.components.transformers:get_transformers:182 - parsing transformer components...
2025-05-02 11:07:58.877 | DEBUG    | ditto.readers.opendss.components.conductors:get_conductors_equipment:16 - parsing conductor components...
2025-05-02 11:07:58.878 | DEBUG    | ditto.readers.opendss.components.cables:get_cables_equipment:22 - parsing cable components...
2025-05-02 11:07:58.878 | DEBUG    | ditto.readers.opendss.components.branches:get_matrix_branch_equipments:195 - parsing matrix branch equipment...
2025-05-02 11:07:58.886 | DEBUG    | ditto.readers.opendss.components.branches:get_geometry_branch_equipments:67 - parsing geometry branch equipment...
2025-05-02 11:07:58.886 | DEBUG    | ditto.readers.opendss.components.branches:get_branches:263 - parsing branch components...
2025-05-02 11:07:58.891 | DEBUG    | ditto.readers.opendss.components.branches:get_branches:269 - building line Line.line_1...
2025-05-02 11:07:58.893 | DEBUG    | ditto.readers.opendss.reader:_read:108 - parsing complete...
System                          
┏━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓
┃ Property              Value ┃
┡━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩
│ System name          │       │
│ Data format version  │ 2.0.0 │
│ Components attached  │    10 │
│ Time Series attached │     0 │
│ Description          │       │
└──────────────────────┴───────┘
Component Information                     
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┓
┃ Type                            Count ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━┩
│ DistributionBus                │     2 │
│ DistributionVoltageSource      │     1 │
│ Location                       │     1 │
│ MatrixImpedanceBranch          │     1 │
│ MatrixImpedanceBranchEquipment │     1 │
│ PhaseVoltageSourceEquipment    │     1 │
│ VoltageLimitSet                │     2 │
│ VoltageSourceEquipment         │     1 │
└────────────────────────────────┴───────┘
2025-05-02 11:07:58.900 | DEBUG    | ditto.readers.opendss.reader:_read:109 - 
None
2025-05-02 11:07:58.901 | DEBUG    | ditto.readers.opendss.reader:_read:110 - Building graph...
2025-05-02 11:07:58.901 | DEBUG    | ditto.readers.opendss.reader:_read:112 - Graph with 2 nodes and 1 edges
2025-05-02 11:07:58.902 | DEBUG    | ditto.readers.opendss.reader:_read:113 - Graph build complete...
2025-05-02 11:07:58.902 | DEBUG    | ditto.readers.opendss.reader:_read:114 - Updating graph to fix split phase representation...
2025-05-02 11:07:58.903 | DEBUG    | ditto.readers.opendss.reader:_read:116 - System update complete...
                                            Validation warning summary                                             
┏━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃  Model  Type                                 Field  Error                                           Message ┃
┡━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ line_1  MatrixImpedanceBranch  On model validation  value_error                Value error, Conductor phase │
│                                                                   (phase=<Phase.A: 'A'>) does not match bus │
│                                                                         phases (bus.phases=[<Phase.B: 'B'>, │
│                                                                                            <Phase.C: 'C'>]) │
└────────┴───────────────────────┴─────────────────────┴─────────────┴────────────────────────────────────────────┘
---------------------------------------------------------------------------
Exception                                 Traceback (most recent call last)
Cell In[5], line 51
     48 console = Console()
     49 console.print(error_table)
---> 51 raise Exception(f"Validations errors occured when running the script. See the table above")

Exception: Validations errors occured when running the script. See the table above