├── docs ├── history.md ├── img │ └── Test4Node.jpg ├── CYME │ └── Mapping_CYME.xlsx ├── GridLAB-D │ └── Mapping.xlsx ├── notes.md ├── DEW │ └── DEW_Ditto_Mapping.xlsx ├── OpenDSS │ └── Mapping_OpenDSS.xlsx ├── ePHASORsim │ ├── mapping_bus.docx │ ├── mapping_line.docx │ ├── mapping_load.docx │ ├── mapping_transformer.docx │ ├── mapping_voltage_regulator.docx │ └── mapping_shunt_device_capacitor.docx ├── ODO_tool │ ├── ODO_INPUT_updated.xlsx │ └── Master_Input_List_final.xlsx ├── notebooks │ └── voltage_difference_epri_j1.pdf ├── developer-guide │ ├── index.md │ ├── style.md │ ├── docs.md │ └── install.md ├── authors.md ├── modifications.md ├── plugin.md ├── Makefile ├── index.md ├── installation.md ├── usage.md └── readme.md ├── ditto ├── formats │ ├── __init__.py │ └── gridlabd │ │ ├── __init__.py │ │ ├── base.py │ │ └── gridlabd.py ├── metrics │ └── __init__.py ├── modify │ └── __init__.py ├── network │ └── __init__.py ├── readers │ ├── __init__.py │ ├── csv │ │ ├── __init__.py │ │ ├── test_input.csv │ │ ├── adjust_load.py │ │ └── test.py │ ├── dew │ │ ├── __init__.py │ │ ├── Readme_dewditto.docx │ │ ├── DataBase │ │ │ ├── DataBase.xlsx │ │ │ └── ~$DataBase.xlsx │ │ └── default_configuration.json │ ├── json │ │ ├── __init__.py │ │ └── default_configuration.json │ ├── gridlabd │ │ ├── default_configuration.json │ │ └── __init__.py │ ├── windmil_ascii │ │ ├── default_configuration.json │ │ ├── __init__.py │ │ └── test.py │ ├── windmil │ │ ├── default_configuration.json │ │ ├── test.py │ │ └── __init__.py │ ├── synergi │ │ ├── default_configuration.json │ │ ├── __init__.py │ │ ├── utils.py │ │ └── db_parser.py │ ├── cyme │ │ ├── default_configuration.json │ │ └── __init__.py │ ├── opendss │ │ ├── default_configuration.json │ │ └── __init__.py │ └── demo │ │ └── __init__.py ├── writers │ ├── __init__.py │ ├── demo │ │ └── __init__.py │ ├── json │ │ └── __init__.py │ ├── gridlabd │ │ └── __init__.py │ ├── ephasor │ │ └── __init__.py │ ├── odo_tool │ │ └── __init__.py │ ├── cyme │ │ └── __init__.py │ └── opendss │ │ └── __init__.py ├── consistency │ ├── __init__.py │ ├── check_loops.py │ └── check_loads_connected.py ├── default_values │ ├── __init__.py │ ├── default_values_json.py │ └── opendss_default_values.json ├── version.py ├── __init__.py ├── helpers.py ├── models │ ├── __init__.py │ ├── position.py │ ├── phase_storage.py │ ├── phase_winding.py │ ├── load_layer.py │ ├── meter.py │ ├── phase_capacitor.py │ ├── weather_layer.py │ ├── phase_reactor.py │ ├── timeseries.py │ ├── generator.py │ ├── winding.py │ ├── feeder_metadata.py │ └── reactor.py ├── compat.py ├── reader_validation │ └── validation.py ├── dittolayers.py ├── utils.py ├── core.py └── metric_computer.py ├── tests ├── readers │ ├── __init__.py │ ├── cyme │ │ ├── __init__.py │ │ ├── test_phase_mapping.py │ │ ├── test_capacitor_connection_mapping.py │ │ ├── test_connection_configuration_mapping.py │ │ ├── test_transformer_connection_mapping.py │ │ └── test_load_value_mapping.py │ ├── synergi │ │ ├── __init__.py │ │ └── test_unit_converter.py │ └── opendss │ │ ├── Powersource │ │ ├── buscoord.dss │ │ ├── test_powersource.dss │ │ └── test_powersource.py │ │ ├── Nodes │ │ ├── buscoord.dss │ │ ├── test_nodes.dss │ │ └── test_nodes.py │ │ ├── Generators │ │ ├── test_generators.dss │ │ └── test_generators.py │ │ ├── Lines │ │ ├── test_concentricneutral.dss │ │ ├── test_fuses.dss │ │ ├── test_linegeometries.dss │ │ ├── test_switches.dss │ │ ├── test_line_connectivity.dss │ │ └── test_line_length.dss │ │ ├── Loads │ │ ├── test_loads.dss │ │ ├── test_load_p_and_q.dss │ │ ├── test_loads.py │ │ └── test_load_p_and_q.py │ │ ├── Capacitors │ │ ├── test_capacitor_kvar.dss │ │ ├── test_capacitor_kvar.py │ │ └── test_capacitor_connectivity.dss │ │ └── Attributes.json ├── writers │ ├── __init__.py │ └── opendss │ │ ├── Lines │ │ └── test_lines_write.py │ │ └── capacitors │ │ └── test_capacitor_writing.py ├── data │ ├── small_cases │ │ ├── opendss │ │ │ ├── storage_test │ │ │ │ ├── buscoord.dss │ │ │ │ └── master.dss │ │ │ ├── ieee_13node │ │ │ │ └── buscoord.dss │ │ │ └── ieee_4node │ │ │ │ └── master.dss │ │ ├── demo │ │ │ └── demo.txt │ │ ├── opendss_broken │ │ │ ├── ieee_13node │ │ │ │ └── buscoord.dss │ │ │ ├── ieee_13node_loop │ │ │ │ └── buscoord.dss │ │ │ ├── ieee_13node_phases_off │ │ │ │ └── buscoord.dss │ │ │ └── ieee_13node_loads_disconnected │ │ │ │ └── buscoord.dss │ │ └── gridlabd │ │ │ └── ieee_4node │ │ │ └── node.glm │ ├── big_cases │ │ └── opendss │ │ │ ├── epri_j1 │ │ │ ├── LoadShapes_mod.dss │ │ │ ├── substation.dss │ │ │ ├── master.dss │ │ │ ├── capacitors.dss │ │ │ └── monitors.dss │ │ │ └── ieee_8500node │ │ │ ├── Feeder_Loads.dss │ │ │ ├── CloudTransient.dss │ │ │ ├── P174_Run_Voltage_Profile.DSS │ │ │ ├── Run_RecloserSiting.DSS │ │ │ ├── Generators.dss │ │ │ ├── Triplex_Linecodes.dss │ │ │ ├── Master-unbal.dss │ │ │ ├── master.dss │ │ │ ├── Capacitors.dss │ │ │ ├── P174_Run_360kW_PV.DSS │ │ │ ├── Transformers.dss │ │ │ ├── Run_8500Node.dss │ │ │ ├── Run_8500Node_Unbal.dss │ │ │ ├── CapControls.DSS │ │ │ ├── Fuses.DSS │ │ │ └── Regulators.dss │ └── ditto-validation │ │ ├── opendss │ │ ├── disabled_objects │ │ │ └── buscoord.dss │ │ └── linegeometries │ │ │ └── Master.dss │ │ └── cyme │ │ ├── switches │ │ ├── equipment.txt │ │ ├── load.txt │ │ └── network.txt │ │ ├── network_protectors │ │ ├── equipment.txt │ │ └── load.txt │ │ └── breakers │ │ ├── equipment.txt │ │ └── load.txt ├── read_dss_13node.json ├── default_values │ ├── test_default_values.dss │ ├── test_default_values.json │ ├── test_remove_opendss_default_values.py │ └── test_default_values.py ├── test_session.py ├── test_demo_to_gridlabd.py ├── test_gridlabd_to_ephasor.py ├── test_cyme_to_ephasor.py ├── persistence │ ├── update_json.py │ └── test_persistence.py ├── test_cyme_writer.py ├── test_cyme_to_gridlabd.py ├── test_opendss_to_gridlabd.py ├── test_opendss_to_cyme.py ├── test_opendss_to_ephasor.py ├── test_cyme_to_opendss.py ├── test_reader.py └── test_metric_extraction.py ├── mkdocs.yml ├── Manifest.in ├── Makefile ├── examples ├── ieee_13node_opendss_input.json ├── epri_j1_opendss_to_cyme.json ├── cli │ └── config.json ├── ieee_8500_opendss_to_cyme.json ├── ieee_123node_cyme_to_opendss.json ├── README.md ├── ieee_123node_cyme_to_opendss.py ├── ieee_123node_gridlabd_to_opendss.py ├── epri_j1_opendss_to_cyme.py └── ieee_8500_opendss_to_cyme.py ├── .pre-commit-config.yaml ├── Dockerfile ├── .github └── workflows │ ├── docs.yml │ ├── ci.yml │ └── publish-to-pypi.yml ├── LICENSE ├── scripts ├── clean_logs.py └── compare.py └── .gitignore /docs/history.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ditto/formats/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ditto/metrics/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ditto/modify/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ditto/network/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ditto/readers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ditto/writers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/readers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/writers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ditto/consistency/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ditto/default_values/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ditto/readers/csv/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ditto/readers/dew/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/readers/cyme/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/readers/synergi/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: ditto 2 | theme: readthedocs 3 | -------------------------------------------------------------------------------- /tests/data/small_cases/opendss/storage_test/buscoord.dss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ditto/formats/gridlabd/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /tests/readers/opendss/Powersource/buscoord.dss: -------------------------------------------------------------------------------- 1 | sourcebus,200,400 2 | -------------------------------------------------------------------------------- /ditto/readers/json/__init__.py: -------------------------------------------------------------------------------- 1 | from .read import Reader as JsonReader 2 | -------------------------------------------------------------------------------- /ditto/version.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __version__ = "0.2.4" 3 | -------------------------------------------------------------------------------- /ditto/writers/demo/__init__.py: -------------------------------------------------------------------------------- 1 | from .write import Writer as DemoWriter 2 | -------------------------------------------------------------------------------- /ditto/writers/json/__init__.py: -------------------------------------------------------------------------------- 1 | from .write import Writer as JsonWriter 2 | -------------------------------------------------------------------------------- /ditto/writers/gridlabd/__init__.py: -------------------------------------------------------------------------------- 1 | from .write import Writer as GridLABDWriter 2 | -------------------------------------------------------------------------------- /ditto/readers/json/default_configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "input_file": null 3 | } 4 | -------------------------------------------------------------------------------- /docs/img/Test4Node.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/ditto/HEAD/docs/img/Test4Node.jpg -------------------------------------------------------------------------------- /ditto/readers/gridlabd/default_configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "input_file": "input.glm" 3 | } 4 | -------------------------------------------------------------------------------- /ditto/readers/windmil_ascii/default_configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "network_folder": null 3 | } 4 | -------------------------------------------------------------------------------- /docs/CYME/Mapping_CYME.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/ditto/HEAD/docs/CYME/Mapping_CYME.xlsx -------------------------------------------------------------------------------- /docs/GridLAB-D/Mapping.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/ditto/HEAD/docs/GridLAB-D/Mapping.xlsx -------------------------------------------------------------------------------- /docs/notes.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | 4 | ## Tips to use DiTTo 5 | 6 | 7 | ### Developing DiTTo 8 | 9 | -------------------------------------------------------------------------------- /docs/DEW/DEW_Ditto_Mapping.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/ditto/HEAD/docs/DEW/DEW_Ditto_Mapping.xlsx -------------------------------------------------------------------------------- /Manifest.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | recursive-include ditto/formats/gridlabd/schema.json 4 | -------------------------------------------------------------------------------- /docs/OpenDSS/Mapping_OpenDSS.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/ditto/HEAD/docs/OpenDSS/Mapping_OpenDSS.xlsx -------------------------------------------------------------------------------- /docs/ePHASORsim/mapping_bus.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/ditto/HEAD/docs/ePHASORsim/mapping_bus.docx -------------------------------------------------------------------------------- /docs/ePHASORsim/mapping_line.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/ditto/HEAD/docs/ePHASORsim/mapping_line.docx -------------------------------------------------------------------------------- /docs/ePHASORsim/mapping_load.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/ditto/HEAD/docs/ePHASORsim/mapping_load.docx -------------------------------------------------------------------------------- /docs/ODO_tool/ODO_INPUT_updated.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/ditto/HEAD/docs/ODO_tool/ODO_INPUT_updated.xlsx -------------------------------------------------------------------------------- /ditto/readers/dew/Readme_dewditto.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/ditto/HEAD/ditto/readers/dew/Readme_dewditto.docx -------------------------------------------------------------------------------- /ditto/readers/windmil/default_configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "network_folder": null, 3 | "library_folder": null 4 | } 5 | -------------------------------------------------------------------------------- /ditto/readers/dew/DataBase/DataBase.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/ditto/HEAD/ditto/readers/dew/DataBase/DataBase.xlsx -------------------------------------------------------------------------------- /docs/ePHASORsim/mapping_transformer.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/ditto/HEAD/docs/ePHASORsim/mapping_transformer.docx -------------------------------------------------------------------------------- /ditto/readers/synergi/default_configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "input_file": "input.mdb", 3 | "warehouse": "warehouse.mbd" 4 | } 5 | -------------------------------------------------------------------------------- /docs/ODO_tool/Master_Input_List_final.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/ditto/HEAD/docs/ODO_tool/Master_Input_List_final.xlsx -------------------------------------------------------------------------------- /tests/readers/opendss/Nodes/buscoord.dss: -------------------------------------------------------------------------------- 1 | bus1,300,400 2 | sourcebus,1674346.56814483,12272927.0644858 3 | b1,1578139, 14291312 4 | -------------------------------------------------------------------------------- /docs/ePHASORsim/mapping_voltage_regulator.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/ditto/HEAD/docs/ePHASORsim/mapping_voltage_regulator.docx -------------------------------------------------------------------------------- /docs/notebooks/voltage_difference_epri_j1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/ditto/HEAD/docs/notebooks/voltage_difference_epri_j1.pdf -------------------------------------------------------------------------------- /tests/data/small_cases/demo/demo.txt: -------------------------------------------------------------------------------- 1 | Node n1 2 | Node n2 3 | Node n3 4 | Node n4 5 | Line l1 n1 n2 6 | Line l2 n2 n3 7 | Line l3 n2 n4 8 | -------------------------------------------------------------------------------- /ditto/readers/dew/default_configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "input_file_path": "input_file.dew", 3 | "database_path": "database.xlsx" 4 | } 5 | -------------------------------------------------------------------------------- /docs/ePHASORsim/mapping_shunt_device_capacitor.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NREL/ditto/HEAD/docs/ePHASORsim/mapping_shunt_device_capacitor.docx -------------------------------------------------------------------------------- /ditto/readers/csv/test_input.csv: -------------------------------------------------------------------------------- 1 | Load.name,Load.nominal_voltage,Load.phase_loads[0].p,Load.phase_loads[0].q,Load.phase_loads[2].p 2 | load1,240,5,1,4 3 | load2,240,4,2,3 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: clean test 2 | 3 | clean: 4 | find . | grep -E "\(__pycache__|\.pyc|\.pyo$\)" | xargs rm -rf 5 | 6 | test: 7 | PYTHONPATH=. py.test -vv --cov=ditto tests 8 | -------------------------------------------------------------------------------- /examples/ieee_13node_opendss_input.json: -------------------------------------------------------------------------------- 1 | {"master_file":"../tests/data/opendss/ieee_13node/master.dss", 2 | "buscoordinates_file":"../tests/data/opendss/ieee_13node/buscoord.dss" 3 | } 4 | -------------------------------------------------------------------------------- /docs/developer-guide/index.md: -------------------------------------------------------------------------------- 1 | Developer Guide 2 | =============== 3 | 4 | 5 | ```eval_rst 6 | .. toctree:: 7 | :maxdepth: 1 8 | 9 | install 10 | style 11 | docs 12 | ``` 13 | -------------------------------------------------------------------------------- /tests/read_dss_13node.json: -------------------------------------------------------------------------------- 1 | {"master_file": "./tests/data/small_cases/opendss/ieee_13node/master.dss", 2 | "buscoordinates_file": "./tests/data/small_cases/opendss/ieee_13node/buscoord.dss"} 3 | -------------------------------------------------------------------------------- /examples/epri_j1_opendss_to_cyme.json: -------------------------------------------------------------------------------- 1 | {"master_file": "../tests/data/big_cases/opendss/epri_j1/master.dss", 2 | "buscoordinates_file": "../tests/data/big_cases/opendss/epri_j1/busccords.dss" 3 | } 4 | -------------------------------------------------------------------------------- /tests/readers/opendss/Powersource/test_powersource.dss: -------------------------------------------------------------------------------- 1 | Clear 2 | 3 | New Circuit.P4U bus1=sourcebus pu=0.99 basekV=230.0 R1=1.1208 X1=3.5169 R0=1.1208 X0=3.5169 phases=3 baseMVA=150 4 | 5 | Solve 6 | -------------------------------------------------------------------------------- /ditto/readers/dew/DataBase/~$DataBase.xlsx: -------------------------------------------------------------------------------- 1 | NREL NREL -------------------------------------------------------------------------------- /examples/cli/config.json: -------------------------------------------------------------------------------- 1 | {"data_folder_path": "./tests/data/cyme/ieee_4node/", 2 | "equipment_filename": "equipment.txt", 3 | "network_filename": "network.txt", 4 | "load_filename": "load.txt" 5 | } 6 | -------------------------------------------------------------------------------- /ditto/readers/cyme/default_configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "data_folder_path": ".", 3 | "network_filename": "network.txt", 4 | "equipment_filename": "equipment.txt", 5 | "load_filename": "load.txt" 6 | } 7 | -------------------------------------------------------------------------------- /ditto/readers/cyme/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .read import Reader as CymeReader 4 | 5 | __author__ = """Nicolas Gensollen""" 6 | __email__ = "nicolas.gensollen@nrel.gov" 7 | __version__ = "0.1.0" 8 | -------------------------------------------------------------------------------- /examples/ieee_8500_opendss_to_cyme.json: -------------------------------------------------------------------------------- 1 | {"master_file": "../tests/data/big_cases/opendss/ieee_8500node/Master-unbal.dss", 2 | "buscoordinates_file": "../tests/data/big_cases/opendss/ieee_8500node/buscoord.dss" 3 | } 4 | -------------------------------------------------------------------------------- /examples/ieee_123node_cyme_to_opendss.json: -------------------------------------------------------------------------------- 1 | {"data_folder_path": "../tests/data/big_cases/cyme/ieee_123node", 2 | "network_filename": "network.txt", 3 | "equipment_filename": "equipment.txt", 4 | "load_filename": "load.txt" 5 | } 6 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/epri_j1/LoadShapes_mod.dss: -------------------------------------------------------------------------------- 1 | new loadshape.Commercial npts=1, sinterval=1, mult=(1) 2 | new loadshape.Other npts=1, sinterval=1, mult=(1) 3 | new loadshape.AggLoadProfile npts=1, sinterval=1, mult=(1) 4 | -------------------------------------------------------------------------------- /tests/readers/opendss/Generators/test_generators.dss: -------------------------------------------------------------------------------- 1 | Clear 2 | 3 | new circuit.loadtest basekV=1 phases=3 pu=1.0 bus1=src 4 | 5 | new generator.gen bus1=powerbus1 kv=2 kW=1 pf=0.95 phases=3 conn=delta Model=3 vminpu=0.0 vmaxpu=1.2 6 | -------------------------------------------------------------------------------- /ditto/writers/ephasor/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function 4 | from builtins import super, range, zip, round, map 5 | 6 | from .write import Writer as EphasorWriter 7 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/ieee_8500node/Feeder_Loads.dss: -------------------------------------------------------------------------------- 1 | 2 | New Load.Other_Feeders bus1=_CANE_CREEK_DS_LSB.1.2.3 phases=3 kv=12.47 xfkVA=15000 pf=0.95 Allocationfactor=1 model=4 CVRwatts=0.8 CVRvars=3 Yearly=Load_Res status=variable conn=wye 3 | -------------------------------------------------------------------------------- /ditto/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import print_function, division, absolute_import 3 | 4 | __author__ = """Tarek Elgindy""" 5 | __email__ = "tarek.elgindy@nrel.gov" 6 | __version__ = "0.1.0" 7 | 8 | from .store import Store 9 | -------------------------------------------------------------------------------- /tests/readers/opendss/Lines/test_concentricneutral.dss: -------------------------------------------------------------------------------- 1 | New CNDATA.cndata1 k=13 GmrStrand=2 DiaStrand=0.064 Rstrand=2.816666667 epsR=2.3 2 | ~ InsLayer=0.220 DiaIns=1.06 DiaCable=1.16 Rac=0.076705 GMRac=0.20568 diam=0.573 3 | ~ Runits=kft Radunits=in GMRunits=in 4 | -------------------------------------------------------------------------------- /ditto/readers/opendss/default_configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "master_file": "master.dss", 3 | "buscoordinates_file": "buscoord.dss", 4 | "coordinates_delimiter": "," , 5 | "default_values_file": null, 6 | "remove_opendss_default_values_flag": false 7 | } 8 | -------------------------------------------------------------------------------- /tests/readers/opendss/Loads/test_loads.dss: -------------------------------------------------------------------------------- 1 | Clear 2 | 3 | new circuit.loadtest basekV=1 phases=1 pu=1.0 bus1=src 4 | 5 | new load.zipv bus1=load.1 kW=1 pf=0.88 phases=1 kV=1 model=8 vminpu=0.0 vmaxpu=1.2 6 | ~ zipv=(0.855,-0.9855,1.1305,2.559,-2.963,1.404,0.87) 7 | 8 | -------------------------------------------------------------------------------- /docs/authors.md: -------------------------------------------------------------------------------- 1 | # Credits 2 | 3 | Development Team 4 | ---------------- 5 | 6 | * Tarek Elgindy 7 | * Nicolas Gensollen 8 | * Bryan Palmintier 9 | * Dheepak Krishnamurthy 10 | * Elaine Hale 11 | * Michael Rossol 12 | * Jeff Simpson 13 | 14 | 15 | -------------------------------------------------------------------------------- /ditto/readers/windmil/test.py: -------------------------------------------------------------------------------- 1 | from ditto.readers.windmil.read import Reader 2 | from ditto.writers.opendss.write import Writer 3 | from ditto.store import Store 4 | 5 | m=Store() 6 | windmil_reader=Reader() 7 | windmil_reader.parse(m) 8 | OpenDSS_writer=Writer() 9 | OpenDSS_writer.write(m) 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/data/small_cases/opendss/ieee_13node/buscoord.dss: -------------------------------------------------------------------------------- 1 | SourceBus,200,400 2 | 650,200,350 3 | RG60,200,300 4 | 646,0,250 5 | 645,100,250 6 | 632,200,250 7 | 633,350,250 8 | 634,400,250 9 | 670,200,200 10 | 611,0,100 11 | 684,100,100 12 | 671,200,100 13 | 692,250,100 14 | 675,400,100 15 | 652,100,0 16 | 680,200,0 17 | -------------------------------------------------------------------------------- /ditto/readers/demo/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function 4 | from builtins import super, range, zip, round, map 5 | from .read import Reader as DemoReader 6 | 7 | __author__ = """Tarek Elgindy""" 8 | __email__ = "tarek.elgindy@nrel.gov" 9 | __version__ = "0.1.0" 10 | -------------------------------------------------------------------------------- /tests/data/ditto-validation/opendss/disabled_objects/buscoord.dss: -------------------------------------------------------------------------------- 1 | SourceBus,200,400 2 | 650,200,350 3 | RG60,200,300 4 | 646,0,250 5 | 645,100,250 6 | 632,200,250 7 | 633,350,250 8 | 634,400,250 9 | 670,200,200 10 | 611,0,100 11 | 684,100,100 12 | 671,200,100 13 | 692,250,100 14 | 675,400,100 15 | 652,100,0 16 | 680,200,0 17 | -------------------------------------------------------------------------------- /tests/data/small_cases/opendss_broken/ieee_13node/buscoord.dss: -------------------------------------------------------------------------------- 1 | SourceBus,200,400 2 | 650,200,350 3 | RG60,200,300 4 | 646,0,250 5 | 645,100,250 6 | 632,200,250 7 | 633,350,250 8 | 634,400,250 9 | 670,200,200 10 | 611,0,100 11 | 684,100,100 12 | 671,200,100 13 | 692,250,100 14 | 675,400,100 15 | 652,100,0 16 | 680,200,0 17 | -------------------------------------------------------------------------------- /ditto/readers/gridlabd/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function 4 | from builtins import super, range, zip, round, map 5 | from .read import Reader as GridLABDReader 6 | 7 | __author__ = """Tarek Elgindy""" 8 | __email__ = "tarek.elgindy@nrel.gov" 9 | __version__ = "0.1.0" 10 | -------------------------------------------------------------------------------- /ditto/readers/windmil/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function 4 | from builtins import super, range, zip, round, map 5 | from .read import Reader as DemoReader 6 | 7 | __author__ = """Tarek Elgindy""" 8 | __email__ = 'tarek.elgindy@nrel.gov' 9 | __version__ = '0.1.0' 10 | 11 | -------------------------------------------------------------------------------- /ditto/writers/odo_tool/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function 4 | from builtins import super, range, zip, round, map 5 | 6 | from .write import Writer as ODO_writer 7 | 8 | __author__ = """Rishabh Jain""" 9 | __email__ = "rishabh.jain@nrel.gov" 10 | __version__ = "0.4.0" 11 | -------------------------------------------------------------------------------- /tests/data/small_cases/opendss_broken/ieee_13node_loop/buscoord.dss: -------------------------------------------------------------------------------- 1 | SourceBus,200,400 2 | 650,200,350 3 | RG60,200,300 4 | 646,0,250 5 | 645,100,250 6 | 632,200,250 7 | 633,350,250 8 | 634,400,250 9 | 670,200,200 10 | 611,0,100 11 | 684,100,100 12 | 671,200,100 13 | 692,250,100 14 | 675,400,100 15 | 652,100,0 16 | 680,200,0 17 | -------------------------------------------------------------------------------- /ditto/readers/synergi/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function 4 | from builtins import super, range, zip, round, map 5 | 6 | from .read import Reader as SynergiReader 7 | 8 | from .utils import download_mdbtools, URL 9 | 10 | download_mdbtools(URL) # downloads MDB binaries 11 | -------------------------------------------------------------------------------- /tests/data/small_cases/opendss_broken/ieee_13node_phases_off/buscoord.dss: -------------------------------------------------------------------------------- 1 | SourceBus,200,400 2 | 650,200,350 3 | RG60,200,300 4 | 646,0,250 5 | 645,100,250 6 | 632,200,250 7 | 633,350,250 8 | 634,400,250 9 | 670,200,200 10 | 611,0,100 11 | 684,100,100 12 | 671,200,100 13 | 692,250,100 14 | 675,400,100 15 | 652,100,0 16 | 680,200,0 17 | -------------------------------------------------------------------------------- /ditto/helpers.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from builtins import super, range, zip, round, map 3 | 4 | 5 | def model_function(model_class, doc=""): 6 | 7 | def func(self, *args, **kwargs): 8 | 9 | m = model_class(model=self, *args, **kwargs) 10 | 11 | return m 12 | 13 | return func 14 | -------------------------------------------------------------------------------- /ditto/readers/windmil_ascii/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function 4 | from builtins import super, range, zip, round, map 5 | from .read import Reader as DemoReader 6 | 7 | __author__ = """Tarek Elgindy""" 8 | __email__ = 'tarek.elgindy@nrel.gov' 9 | __version__ = '0.1.0' 10 | 11 | -------------------------------------------------------------------------------- /ditto/writers/cyme/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function 4 | from builtins import super, range, zip, round, map 5 | 6 | from .write import Writer as CymeWriter 7 | 8 | __author__ = """Nicolas Gensollen""" 9 | __email__ = "nicolas.gensollen@nrel.gov" 10 | __version__ = "0.1.0" 11 | -------------------------------------------------------------------------------- /tests/data/small_cases/opendss_broken/ieee_13node_loads_disconnected/buscoord.dss: -------------------------------------------------------------------------------- 1 | SourceBus,200,400 2 | 650,200,350 3 | RG60,200,300 4 | 646,0,250 5 | 645,100,250 6 | 632,200,250 7 | 633,350,250 8 | 634,400,250 9 | 670,200,200 10 | 611,0,100 11 | 684,100,100 12 | 671,200,100 13 | 692,250,100 14 | 675,400,100 15 | 652,100,0 16 | 680,200,0 17 | -------------------------------------------------------------------------------- /ditto/readers/opendss/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function 4 | from builtins import super, range, zip, round, map 5 | 6 | from .read import Reader as OpenDSSReader 7 | 8 | __author__ = """Nicolas Gensollen""" 9 | __email__ = "nicolas.gensollen@nrel.gov" 10 | __version__ = "0.1.0" 11 | -------------------------------------------------------------------------------- /ditto/writers/opendss/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from __future__ import absolute_import, division, print_function 4 | from builtins import super, range, zip, round, map 5 | 6 | from .write import Writer as OpenDSSWriter 7 | 8 | __author__ = """Nicolas Gensollen""" 9 | __email__ = "nicolas.gensollen@nrel.gov" 10 | __version__ = "0.1.0" 11 | -------------------------------------------------------------------------------- /ditto/models/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from builtins import super, range, zip, round, map 3 | 4 | from .winding import Winding 5 | from .powertransformer import PowerTransformer 6 | from .line import Line 7 | from .wire import Wire 8 | from .regulator import Regulator 9 | from .load import Load 10 | from .phase_load import PhaseLoad 11 | -------------------------------------------------------------------------------- /tests/readers/opendss/Capacitors/test_capacitor_kvar.dss: -------------------------------------------------------------------------------- 1 | Clear 2 | 3 | New circuit.test_capacitor_kvar basekv=4.16 pu=1.01 phases=3 bus1=sourcebus 4 | 5 | New Capacitor.Cap1 Bus1=bus1 phases=3 kVAR=600 kV=4.16 6 | New Capacitor.Cap2 Bus1=bus2.3 phases=1 kVAR=100 kV=2.4 7 | New Capacitor.Cap3 Bus1=bus3.1 phases=1 kVAR=200.37 kV=2.4 8 | 9 | Set Voltagebases=[4.16, 2.4] 10 | Calcvoltagebases 11 | Solve -------------------------------------------------------------------------------- /docs/modifications.md: -------------------------------------------------------------------------------- 1 | # Modifications documentation 2 | 3 | DiTTo has the capability of modifying models once parsed into the core representation. 4 | 5 | ## Overview 6 | 7 | Explain here the typical workflow to apply modifications. 8 | 9 | ## List of examples 10 | 11 | Here is a list of usual modifications. 12 | 13 | ### Set the nominal voltages 14 | 15 | TODO 16 | 17 | ### TODO 18 | 19 | ### TODO... 20 | -------------------------------------------------------------------------------- /docs/plugin.md: -------------------------------------------------------------------------------- 1 | ============ 2 | Plugin 3 | ============ 4 | 5 | 6 | Reader 7 | -------------- 8 | 9 | This section describes the API for the reader plugin. 10 | A reader plugin should parse an input file and set attributes on the instance of the Model object. 11 | The Model supports adding objects such as ConnectivityNodes, ACLineSegment, etc. 12 | These objects are stored in a dictionary 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/ieee_8500node/CloudTransient.dss: -------------------------------------------------------------------------------- 1 | New Loadshape.Ramp 300 interval=(1 3600 /) mult=(file=solarramp.csv) 2 | 3 | New generator.solar bus1=450 kw=2000 kV=4.16 pf=1 duty=ramp model=1 4 | 5 | 6 | New monitor.M1 line.l99 1 7 | set mode=duty 8 | set stepsize=1s number=300 9 | 10 | solve ! Solves 300 steps each invocation 11 | 12 | Plot monitor object= m1 channels=(1, 3, 5) bases=[2400 2400 2400] 13 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v1.2.3 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: check-ast 7 | - id: check-merge-conflict 8 | - repo: https://github.com/ambv/black 9 | rev: stable 10 | hooks: 11 | - id: black 12 | name: black 13 | language: system 14 | entry: python -m black 15 | types: [python] 16 | args: [--line-length=88, --safe] 17 | -------------------------------------------------------------------------------- /ditto/default_values/default_values_json.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | class Default_Values(object): 5 | """ 6 | Class for parsing default values. 7 | """ 8 | 9 | register_names = [] 10 | 11 | def __init__(self, default_values_file, **kwargs): 12 | 13 | self.default_values_file = default_values_file 14 | 15 | def parse(self): 16 | 17 | with open(self.default_values_file) as f: 18 | values_dict = json.load(f) 19 | return values_dict 20 | -------------------------------------------------------------------------------- /ditto/readers/windmil_ascii/test.py: -------------------------------------------------------------------------------- 1 | from ditto.readers.windmil_ascii.read import Reader 2 | from ditto.writers.opendss.write import Writer 3 | from ditto.store import Store 4 | 5 | m=Store() 6 | 7 | Path = { 8 | 'network_folder' : r'C:\Users\alatif\Desktop\Test-Exports\ASCII\Basalt' 9 | } 10 | 11 | windmil_reader=Reader(**Path) 12 | windmil_reader.parse(m) 13 | OpenDSS_writer=Writer(output_path = r'C:\Users\alatif\Desktop\Test-Exports\OpenDSS\Basalt') 14 | OpenDSS_writer.write(m) 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/developer-guide/style.md: -------------------------------------------------------------------------------- 1 | Style Guide 2 | =========== 3 | 4 | The goal of the style guide is to describe in detail naming conventions 5 | for developing DiTTo. 6 | 7 | Naming Conventions 8 | ------------------ 9 | 10 | 1) All functions and methods should be lower case with use of underscores for clarity 11 | 12 | ```python 13 | def parse_data(): pass 14 | ``` 15 | 16 | 2) All classes should be `PascalCase` 17 | 18 | ```python 19 | class GridLABDReader(AbstractReader): 20 | pass 21 | ``` 22 | 23 | -------------------------------------------------------------------------------- /ditto/models/position.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from builtins import super, range, zip, round, map 3 | 4 | from .base import DiTToHasTraits, Float, Unicode, Any, Int, List, observe, Instance 5 | 6 | 7 | class Position(DiTToHasTraits): 8 | long = Float(help="""Decimal Longitude""") 9 | lat = Float(help="""Decimal Latitude""") 10 | elevation = Float(help="""Decimal elevation (meters)""") 11 | 12 | def build(self, model): 13 | self._model = model 14 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/ieee_8500node/P174_Run_Voltage_Profile.DSS: -------------------------------------------------------------------------------- 1 | // Script for EPRI Report 1020157 2 | // This script will produce voltage profiles 3 | 4 | Compile (Master-unbal.dss) ! unbalanced load master 5 | 6 | New Energymeter.m1 Line.ln5815900-1 1 7 | 8 | Set Maxiterations=20 ! Sometimes the solution takes more than the default 15 iterations 9 | 10 | Solve 11 | 12 | Plot Profile phases=primary 13 | 14 | // Drop the load to 40% 15 | set loadmult=0.4 maxcontrol=30 16 | solve 17 | 18 | Plot Profile phases=primary 19 | -------------------------------------------------------------------------------- /tests/readers/cyme/test_phase_mapping.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ditto.readers.cyme.read import Reader 4 | 5 | 6 | @pytest.mark.parametrize('cyme_value, expected', [ 7 | (0, [None]), 8 | (1, ['A']), 9 | (2, ['B']), 10 | (3, ['C']), 11 | (4, ['A', 'B']), 12 | (5, ['A', 'C']), 13 | (6, ['B', 'C']), 14 | (7, ['A', 'B', 'C']), 15 | ('ABC', ['A', 'B', 'C']), 16 | ]) 17 | def test_phase_mapping(cyme_value, expected): 18 | reader = Reader() 19 | actual = reader.phase_mapping(cyme_value) 20 | assert actual == expected 21 | -------------------------------------------------------------------------------- /ditto/readers/csv/adjust_load.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from builtins import super, range, zip, round, map 3 | 4 | import pandas as pd 5 | 6 | df = pd.read_csv("rnm_load.csv") 7 | df["Load.phase_loads[0].phase"] = "" 8 | df["Load.phase_loads[1].phase"] = "" 9 | df["Load.phase_loads[2].phase"] = "" 10 | df.loc[df.phases == 3, "Load.phase_loads[0].phase"] = "A" 11 | df.loc[df.phases == 3, "Load.phase_loads[1].phase"] = "B" 12 | df.loc[df.phases == 3, "Load.phase_loads[2].phase"] = "C" 13 | df = df.drop("phases", 1) 14 | df.to_csv("rnm_load_updated.csv", index=False) 15 | -------------------------------------------------------------------------------- /tests/data/ditto-validation/cyme/switches/equipment.txt: -------------------------------------------------------------------------------- 1 | [GENERAL] 2 | DATE=June 04, 2018 at 12:08:21 3 | CYME_VERSION=8.00 4 | CYMDIST_REVISION=8 5 | 6 | [SI] 7 | 8 | [SWITCH] 9 | FORMAT_SWITCH=ID,Amps,Amps_1,Amps_2,Amps_3,Amps_4,KVLL,Reversible,FailRate,TmpFailRate,MajorRepairTime,MinorRepairTime,MajorFailureProportion,StuckProbability,SwitchTime,SymbolOpenID,SymbolCloseID,SinglePhaseLocking,RemoteControlled,Automated,Comments 10 | DEFAULT,100.000000,100.000000,100.000000,100.000000,100.000000,25.000000,0,,,,,,,,0,0,0,0,0, 11 | SWITCH1,300.000000,300.000000,300.000000,300.000000,300.000000,50.000000,0,,,,,,,,0,0,0,0,0, 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build ditto 2 | # docker build . -t ditto 3 | 4 | # Run ditto 5 | # docker run --rm -ti ditto --help 6 | 7 | # https://hub.docker.com/_/python 8 | FROM python:3.6-slim 9 | 10 | # Install latest version of ditto 11 | COPY ./ $HOME/ditto 12 | WORKDIR $HOME/ditto 13 | 14 | # Install ditto dependencies 15 | RUN python -m pip install --upgrade pip && \ 16 | pip install -e .[all] && \ 17 | pip install pytest 18 | 19 | # Validate install 20 | RUN pytest -sv 21 | RUN ditto --help 22 | 23 | # By using an ENTRYPOINT, all of the arguments to docker 24 | # run following the image name are passed as arguments 25 | ENTRYPOINT [ "ditto" ] -------------------------------------------------------------------------------- /tests/readers/opendss/Loads/test_load_p_and_q.dss: -------------------------------------------------------------------------------- 1 | clear 2 | 3 | New circuit.test_load_p_and_q basekV=4.16 phases=3 4 | 5 | New Load.load1 phases=3 bus1=b1 conn=wye kV=4.16 kW=5400 Kvar=4285 model=1 6 | New Load.load2 phases=3 bus1=b2 conn=wye kV=4.16 kW=3466 pf=0.9 model=1 7 | New Load.load3 phases=2 bus1=b3.1.3 conn=wye kV=4.16 kW=1600 Kvar=980 model=1 8 | New Load.load4 phases=2 bus1=b2.1.2 conn=wye kV=4.16 kW=1555 pf=0.95 model=1 9 | New Load.load5 phases=1 bus1=b3.3 conn=wye kV=4.16 kW=650 Kvar=500.5 model=1 10 | New Load.load6 phases=1 bus1=b2.1 conn=wye kV=4.16 kW=623.21 pf=0.85 model=1 11 | 12 | Set Voltagebases=[4.16] 13 | Calcvoltagebases 14 | Solve 15 | -------------------------------------------------------------------------------- /ditto/models/phase_storage.py: -------------------------------------------------------------------------------- 1 | from .base import DiTToHasTraits, Float, Unicode, Any, Int, List, observe, Instance 2 | 3 | from .position import Position 4 | 5 | 6 | class PhaseStorage(DiTToHasTraits): 7 | 8 | phase = Unicode(help="""The phases the device is on.""", default_value=None) 9 | p = Float( 10 | help="""Present Watt value (positive denotes power coming out, and negative is charging). In watts.""", 11 | default_value=None, 12 | ) 13 | q = Float(help="""Present var value. In vars.""", default_value=None) 14 | 15 | def build(self, model): 16 | """ 17 | TODO... 18 | """ 19 | self._model = model 20 | -------------------------------------------------------------------------------- /tests/data/small_cases/opendss/storage_test/master.dss: -------------------------------------------------------------------------------- 1 | !Dummy storage test 2 | !If anyone has a real OpenDSS circuit with storage, feel free to upload it… 3 | 4 | Clear 5 | 6 | New Circuit.Name Bus1=97 pu=1.0 basekV=12.47 7 | 8 | ! Defining the storage unit at bus 98 9 | New Storage.N98 Bus1=98 kV=4.16 kWRated=75 kWhRated=150 kWhStored=150 10 | ~ State=IDLING !(this is the default) 11 | ! Now set it to discharge at 25% rate 12 | Storage.n98.state=Dischar %discharge=25 13 | FormEdit "storage.n98" ! this will confirm the property changes 14 | Solve 15 | Visualize powers storage.n98 ! now you can see the result 16 | Storage.n98.state=charging kwhstored=100 %charge=50 17 | Solve 18 | -------------------------------------------------------------------------------- /tests/data/ditto-validation/cyme/network_protectors/equipment.txt: -------------------------------------------------------------------------------- 1 | [GENERAL] 2 | DATE=June 04, 2018 at 12:08:21 3 | CYME_VERSION=8.00 4 | CYMDIST_REVISION=8 5 | 6 | [SI] 7 | 8 | [NETWORKPROTECTOR] 9 | FORMAT_NETWORKPROTECTOR=ID,Amps,Amps_1,Amps_2,Amps_3,Amps_4,KVLL,Reversible,InterruptingRating,FailRate,TmpFailRate,MajorRepairTime,MinorRepairTime,MajorFailureProportion,StuckProbability,SwitchTime,SymbolOpenID,SymbolCloseID,SinglePhaseLocking,Favorite,Flags,Comments 10 | DEFAULT,100.000000,100.000000,100.000000,100.000000,100.000000,25.000000,1,600.000000,,,,,,,,0,0,0,0,0, 11 | NETWORK_PROTECTOR,100.000000,100.000000,100.000000,100.000000,100.000000,25.000000,1,600.000000,,,,,,,,0,0,0,0,0, 12 | -------------------------------------------------------------------------------- /docs/developer-guide/docs.md: -------------------------------------------------------------------------------- 1 | Generating Documentation 2 | ======================== 3 | 4 | The documentation requires Pandoc to convert from Markdown to RST. 5 | 6 | You will need the following Python packages. 7 | 8 | ``` {.sourceCode .bash} 9 | pip install sphinx 10 | pip install ghp-import 11 | pip install sphinx_rtd_theme 12 | pip install nbsphinx 13 | pip install sphinxcontrib-pandoc-markdown 14 | ``` 15 | 16 | If you don't have Pandoc, you can install it using `conda`. 17 | 18 | ``` 19 | conda install pandoc 20 | ``` 21 | 22 | If you are unable to install `pandoc`, you may be able to generate some of the documentation if you install the following. 23 | 24 | ``` 25 | pip install recommonmark 26 | ``` 27 | 28 | -------------------------------------------------------------------------------- /tests/readers/cyme/test_capacitor_connection_mapping.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ditto.readers.cyme.read import Reader 4 | 5 | 6 | @pytest.mark.parametrize('cyme_value, expected', [ 7 | (0, 'Y'), 8 | (1, 'Y'), 9 | (2, 'D'), 10 | ('0', 'Y'), 11 | ('1', 'Y'), 12 | ('2', 'D'), 13 | (3, 3), # Non conversion case 14 | ]) 15 | def test_cap_connection_mapping(cyme_value, expected): 16 | reader = Reader() 17 | actual = reader.capacitors_connection_mapping(cyme_value) 18 | assert actual == expected 19 | 20 | 21 | def test_cap_connection_mapping_invalid_type(): 22 | reader = Reader() 23 | 24 | with pytest.raises(ValueError): 25 | reader.capacitors_connection_mapping(0.0) 26 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/ieee_8500node/Run_RecloserSiting.DSS: -------------------------------------------------------------------------------- 1 | Compile (master.dss) 2 | 3 | New Energymeter.m1 Line.ln5815900-1 1 4 | 5 | // Define a relay at the substation with default TCC Curves and high pickup values to get by solution 6 | // without tripping 7 | New Relay.SubBreaker Line.ln5815900-1 1 PhaseCurve=D GroundCurve=A PhaseTrip=600 GroundTrip=600 8 | 9 | Set Maxiterations=20 ! Sometimes the solution takes more than the default 15 iterations 10 | 11 | Solve 12 | 13 | // Set the triplex failure rate to zero so it does not affect the relcalc 14 | Redirect TplxFaultrate.dss ! set to zero 15 | 16 | // Stick some fuses on 17 | Redirect Fuses.DSS 18 | interpolate ! to get rid of gaps in circuit plotting 19 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/epri_j1/substation.dss: -------------------------------------------------------------------------------- 1 | New Circuit.J1 pu=0.97 r1=1.376 x1=14.257 r0=0.001 x0=8.203 bus1=S basekv=69 2 | 3 | New Transformer.SubXfmr phases=3 windings=2 buses=(S,LS_Bus) conns=(wye,wye) 4 | ~ kvs=(68.8,13.09) kvas=(16000,16000) numtaps=16 5 | ~ xhl=11.63 wdg=1 %r=0.596 wdg=2 %r=0.596 6 | ~ ppm_antifloat=5 7 | 8 | New regcontrol.SubXfmr transformer=SubXfmr vreg=124 winding=2 band=2 PTratio=60 Delay=90 tapdelay=30 enabled=True 9 | 10 | New line.temp_sub bus1=LS_Bus bus2=FeederHead switch=yes enabled=yes phases=3 normamps=99999 emergamps=99999 11 | 12 | New Load.Aggregate_Load phases=3 bus=LS_Bus.1.2.3 kV=12.47 kW=5000 pf=-0.98 status=variable mode=4 CVRwatts=0.8 CVRvars=3 class=1 numcust=1 yearly=AggLoadProfile -------------------------------------------------------------------------------- /tests/default_values/test_default_values.dss: -------------------------------------------------------------------------------- 1 | Clear 2 | 3 | New circuit.test_line_connectivity basekv=4.16 pu=1.01 phases=3 bus1=sourcebus 4 | 5 | ! Line1 connects sourcebus to bus1 and should have 4 wires: A, B, C, and N 6 | New Line.line1 Bus1=sourcebus Bus2=bus1 phases=3 Length=100 units=m 7 | 8 | ! Capacitor Cap1 should be a three phase capacitor (3 PhaseCapacitor objects) connected to bus1 9 | New Capacitor.Cap1 Bus1=bus1 phases=3 kVAR=600 kV=4.16 10 | 11 | New Transformer.Reg1 phases=1 XHL=0.01 kVAs=[1666 1666] 12 | ~ Buses=[650.1 RG60.1] kVs=[2.4 2.4] %LoadLoss=0.01 13 | new regcontrol.Reg1 transformer=Reg1 winding=2 vreg=122 band=2 ptratio=20 ctprim=700 R=3 X=9 vlimit=0 14 | 15 | New load.load1 bus1=load.1 kW=1 pf=0.88 phases=1 kV=1 vminpu=0.0 vmaxpu=1.2 16 | -------------------------------------------------------------------------------- /tests/readers/opendss/Nodes/test_nodes.dss: -------------------------------------------------------------------------------- 1 | Clear 2 | 3 | New Circuit.P4U bus1=sourcebus pu=0.99 basekV=12.47 R1=1.1208 X1=3.5169 R0=1.1208 X0=3.5169 4 | 5 | ! Line1 connects sourcebus to bus1 and should have 4 wires: A, B, C, and N 6 | New Line.line1 Bus1=sourcebus.2.3 Bus2=bus1.2.3 phases=2 Length=100 units=m 7 | 8 | ! Delta-Wye substation transformer from IEEE 8500 test system 9 | 10 | New Transformer.substation phases=3 windings=2 XHL=(8 1000 /) 11 | ~ wdg=1 bus=sourcebus conn=delta kv=115 kva=5000 %r=(.5 1000 /) XHT=4 12 | ~ wdg=2 bus=bus1 conn=wye kv=4.16 kva=5000 %r=(.5 1000 /) XLT=4 13 | 14 | ! Load 15 | New Load.load1 phases=3 bus1=b1 conn=wye kV=4.16 kW=5400 Kvar=4285 model=1 16 | 17 | Set Voltagebases=[12.47] 18 | Calcvoltagebases 19 | Solve 20 | Buscoords buscoord.dss 21 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | docs: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Setup Python 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: '3.8' 17 | - name: Install dependencies 18 | run: | 19 | python -m pip install -U pip 20 | pip install mkdocs mkdocs-material pygments pymdown-extensions 21 | - run: mkdocs build 22 | - name: Github Pages Deploy 23 | uses: peaceiris/actions-gh-pages@v3 24 | with: 25 | personal_token: ${{ secrets.GITHUB_TOKEN }} 26 | publish_branch: gh-pages 27 | publish_dir: ./site 28 | -------------------------------------------------------------------------------- /ditto/readers/csv/test.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from builtins import super, range, zip, round, map 3 | 4 | import logging 5 | from read import Reader 6 | from ditto.store import Store 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | m = Store() 11 | reader = Reader() 12 | reader.parse(m, "test_input.csv") 13 | for i in m.models: 14 | logger.debug(i) 15 | 16 | for obj_name in m.model_names: 17 | logger.debug(obj_name) 18 | 19 | for i in m.model_names["load1"].traits(): 20 | # logger.debug(i,type(m.model_names['load1'].traits()[i])) 21 | class_name = ( 22 | str(type(m.model_names["load1"].traits()[i])).strip("<>'").split(".")[-1] 23 | ) 24 | if class_name == "List": 25 | logger.debug(m.model_names["load1"].traits()[i]._trait.klass) 26 | -------------------------------------------------------------------------------- /tests/data/ditto-validation/opendss/linegeometries/Master.dss: -------------------------------------------------------------------------------- 1 | Clear 2 | 3 | New Circuit.test_circuit 4 | 5 | New Wiredata.ACSR336 GMR=0.0255000 DIAM=0.7410000 RAC=0.3060000 NormAmps=530.0000 Runits=mi radunits=in gmrunits=ft 6 | New Wiredata.ACSR1/0 GMR=0.0044600 DIAM=0.3980000 RAC=1.120000 NormAmps=230.0000 Runits=mi radunits=in gmrunits=ft 7 | 8 | 9 | New Linegeometry.HC2_336_1neut_0Mess nconds=4 nphases=3 10 | ~ cond=1 Wire=ACSR336 x=-1.2909 h=13.716 units=m 11 | ~ cond=2 Wire=ACSR336 x=-0.1530096 h=4.1806368 units=ft 12 | ~ cond=3 Wire=ACSR336 x=0.5737 h=13.716 units=m 13 | ~ cond=4 Wire= ACSR1/0 x=0 h=14.648 ! units=m ! neutral 14 | 15 | New Line.Line1 Bus1=bus1.1.2.3 Bus2=bus2.1.2.3 16 | ~ Geometry= HC2_336_1neut_0Mess 17 | ~ Length=300 units=ft 18 | 19 | Set Voltagebases=[4.8,34.5,115.0] 20 | Calcvoltagebases 21 | Solve -------------------------------------------------------------------------------- /ditto/compat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | ditto.core.compat 4 | 5 | This module handles compatibility issues between Python 2 and Python 3. 6 | """ 7 | 8 | from __future__ import absolute_import, division, print_function 9 | from builtins import super, range, zip, round, map 10 | import types 11 | import sys 12 | 13 | # Syntax sugar. 14 | _ver = sys.version_info 15 | 16 | #: Python 2.x? 17 | is_py2 = _ver[0] == 2 18 | 19 | #: Python 3.x? 20 | is_py3 = _ver[0] == 3 21 | 22 | if is_py2: 23 | 24 | def ModuleType(m): 25 | return types.ModuleType(m.encode("utf-8")) 26 | 27 | 28 | else: 29 | 30 | def ModuleType(m): 31 | return types.ModuleType(m) 32 | 33 | 34 | def common_str(string): 35 | if not sys.version_info >= (3, 0): 36 | return string.encode("utf-8") 37 | else: 38 | return string 39 | -------------------------------------------------------------------------------- /ditto/reader_validation/validation.py: -------------------------------------------------------------------------------- 1 | from create_compare import create_output_dir, create_dict 2 | from create_excel import write_to_excel 3 | from create_plots import plots 4 | import os 5 | 6 | # Path to the directory which contains test cases 7 | current_dir = os.path.realpath(os.path.dirname(__file__)) 8 | small_tests_dir = os.path.join(current_dir, "../../tests/data/small_cases") 9 | 10 | # Create the output directory 11 | output_dir = create_output_dir(small_tests_dir) 12 | 13 | # Create a dictionary of all the readers outputs (Each Node will have R0, X0, R1, X1 values) from the output saved in output_dir 14 | comp_values = create_dict(output_dir) 15 | 16 | # Writing the output to excel as output.xlsx in the current directory 17 | write_to_excel(comp_values) 18 | 19 | # Plotting the sequence impedance values of all readers 20 | plots(comp_values) 21 | -------------------------------------------------------------------------------- /docs/developer-guide/install.md: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | To develop `DiTTo`, you will need to follow the development instructions: 5 | 6 | 7 | ```bash 8 | git clone https://github.com/NREL/ditto 9 | pip install -e ".[dev]" 10 | ``` 11 | 12 | In addition to all the dependencies, this also installs the requirements for running tests. 13 | Also, this installs auto code formatters, checks for trailing whitespace, checks for valid AST and checks for merge conflict markers. 14 | The pre-commit hooks will not let you commit code that fails these checks. 15 | Your code will automatically be formatted when you attempt to make a commit. 16 | These auto formatted changes need to be explicitly added to the staging area in order for the commit to be created. 17 | If you don't want to run the pre-commit hooks, you can make a commit using the `--no-verify` flag. 18 | 19 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/ieee_8500node/Generators.dss: -------------------------------------------------------------------------------- 1 | // Example 480-V 1-MW generator with a Yg-Delta transformer with a reactor in the neutral 2 | 3 | 4 | // 12.47/480V Transformer and neutral reactor definition. 5 | New "Transformer.G_m1125934" XHL=5.75 kVA=1000 Conns=[wye, Delta] 6 | ~ wdg=1 bus=190-7361.1.2.3.4 kV=12.47 7 | ~ wdg=2 bus=G_190-7361 kV=0.48 8 | New Reactor.G_m1125934 Phases=1 Bus1=190-7361.4 R=0.001 X=0 !Neutral Reactor/Resistor 9 | 10 | // Generator definition 11 | New "Generator.G_m1125934" Bus1=G_190-7361 kW=1000 PF=1 kVA=1000 kV=0.48 Xdp=0.27 Xdpp=0.2 H=2 12 | ~ Conn=Delta ! use the interconnection transformer to achieve wye for direct connect 13 | 14 | New "Monitor.G_m1125934" Element="Transformer.G_m1125934" Terminal=1 Mode=0 15 | New "Monitor.G_m1125934_gen" Element="Generator.G_m1125934" Terminal=1 Mode=3 16 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/ieee_8500node/Triplex_Linecodes.dss: -------------------------------------------------------------------------------- 1 | ! Linecodes for secondary Triplex 2 | ! These linecodes are defined as a 2-phase line. The neutral conductor 3 | ! is assumed grounded at both ends and reduced out by Kron reduction 4 | 5 | New Linecode.750_Triplex nphases=2 units=kft ! ohms per 1000 ft 6 | ~ rmatrix=[ 0.04974733 0.02342157 | 0.02342157 0.04974733 ] 7 | ~ xmatrix=[ 0.02782436 0.00669472 | 0.00669472 0.02782436 ] 8 | ~ cmatrix=[ 3.00000000 -2.40000000 | -2.40000000 3.00000000 ] 9 | ~ NormAmps=580 {580 1.25 *} 10 | 11 | 12 | New Linecode.4/0Triplex nphases=2 units=kft !ohms per 1000 ft 13 | ~ rmatrix=[ 0.40995115 0.11809509 | 0.11809509 0.40995115 ] 14 | ~ xmatrix=[ 0.16681819 0.12759250 | 0.12759250 0.16681819 ] 15 | ~ cmatrix=[ 3.00000000 -2.40000000 | -2.40000000 3.00000000 ] 16 | ~ Normamps=156 {156 1.25 *} 17 | -------------------------------------------------------------------------------- /tests/test_session.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains items that pertain to the entire test session. 3 | """ 4 | 5 | import os 6 | import shutil 7 | 8 | import pytest as pt 9 | 10 | STARTUP = True 11 | 12 | @pt.mark.skip() 13 | def manage_outdir(request): 14 | """ 15 | At the beginning of the session, creates the test outdir. If tests.clean_up, 16 | deletes this folder after the tests have finished running. 17 | 18 | Arguments 19 | - request contains the pytest session, including collected tests 20 | """ 21 | global STARTUP 22 | if STARTUP and os.path.exists(outdir): 23 | # create clean space for running tests 24 | shutil.rmtree(outdir) 25 | STARTUP = False 26 | os.mkdir(outdir) 27 | def finalize_outdir(): 28 | if os.path.exists(outdir) and clean_up: 29 | shutil.rmtree(outdir) 30 | request.addfinalizer(finalize_outdir) 31 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/ieee_8500node/Master-unbal.dss: -------------------------------------------------------------------------------- 1 | // Master file for 8500-Node IEEE Test Feeder Case 2 | // Unbalanced Load Case 3 | 4 | Clear 5 | 6 | New Circuit.IEEE8500u 7 | 8 | ! Make the source stiff with small impedance 9 | ~ pu=1.05 r1=0 x1=0.001 r0=0 x0=0.001 10 | 11 | Redirect LineCodes2.dss 12 | Redirect Triplex_Linecodes.dss 13 | 14 | Redirect Lines.dss 15 | Redirect Transformers.dss 16 | //Redirect LoadXfmrs.dss ! Load Transformers 17 | Redirect LoadXfmrCodes.dss ! Referencing XfmrCodes 18 | Redirect Triplex_Lines.dss 19 | Redirect UnbalancedLoads.dss 20 | Redirect Capacitors.dss 21 | Redirect CapControls.dss 22 | Redirect Regulators.dss 23 | 24 | ! Let DSS estimate the voltage bases 25 | Set voltagebases=[115, 12.47, 0.48, 0.208] 26 | Calcvoltagebases ! This also establishes the bus list 27 | 28 | ! Load in bus coordintes now that bus list is established 29 | Buscoords buscoord.dss 30 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/ieee_8500node/master.dss: -------------------------------------------------------------------------------- 1 | // Master file for 8500-Node IEEE Test Feeder Case 2 | // Balanced Load Case 3 | 4 | Clear 5 | 6 | New Circuit.IEEE8500 7 | 8 | ! Make the source stiff with small impedance 9 | ~ pu=1.05 r1=0 x1=0.001 r0=0 x0=0.001 10 | 11 | Redirect LineCodes2.dss 12 | Redirect Triplex_Linecodes.dss 13 | 14 | Redirect Lines.dss 15 | Redirect Transformers.dss 16 | //Redirect LoadXfmrs.dss ! Load Transformers 17 | Redirect LoadXfmrCodes.dss ! Referencing XfmrCodes 18 | Redirect Triplex_Lines.dss 19 | Redirect Loads.dss ! Balanced Loads 20 | Redirect Capacitors.dss 21 | Redirect CapControls.dss 22 | Redirect Regulators.dss 23 | 24 | ! Let DSS estimate the voltage bases 25 | Set voltagebases=[115, 12.47, 0.48, 0.208] 26 | Calcvoltagebases ! This also establishes the bus list 27 | 28 | ! Load in bus coordintes now that bus list is established 29 | Buscoords Buscoords.dss 30 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/epri_j1/master.dss: -------------------------------------------------------------------------------- 1 | !EPRI Feeder J1, OpenDSS 2 | ! This Circuit model is provided to the public by EPRI (Electric Power Research Institute) as part of the Distributed Renewables Research Program (P174). 3 | ! Please feel free to use this circuit model for further research/study. 4 | ! For reference purposes, please use: EPRI Feeder J1, Distributed PV (DPV) Monitoring and Feeder Analysis, dpv.epri.com, 2013 5 | Clear 6 | 7 | Redirect LoadShapes_mod.dss 8 | Redirect substation.dss 9 | Redirect linecodes.dss 10 | Redirect lines.dss 11 | Redirect transformers.dss 12 | Redirect LoadsInd.dss 13 | Redirect services.dss 14 | Redirect capacitors.dss 15 | Redirect regulators.dss 16 | ! Redirect ExistingPV.dss 17 | 18 | Buscoords buscoords.dss 19 | 20 | New EnergyMeter.Main Line.temp_sub 1 21 | 22 | set maxiter=100 23 | set maxcontroliter=100 24 | 25 | Set voltagebases=[69, 12.47, 7.2, 0.480, 0.416] 26 | Calcv 27 | Solve 28 | 29 | redirect monitors.dss 30 | 31 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = DiTTo 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | BRANCH := $(shell git rev-parse --abbrev-ref HEAD) 11 | 12 | # Put it first so that "make" without argument is like "make help". 13 | help: 14 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 15 | 16 | .PHONY: help Makefile 17 | 18 | # Catch-all target: route all unknown targets to Sphinx using the new 19 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 20 | %: Makefile 21 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 22 | 23 | github: html 24 | -git branch -D gh-pages 25 | -git push origin --delete gh-pages 26 | ghp-import -n -b gh-pages -m "Update documentation" ./_build/html 27 | git checkout gh-pages 28 | git push --set-upstream origin gh-pages 29 | git checkout ${BRANCH} 30 | 31 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/ieee_8500node/Capacitors.dss: -------------------------------------------------------------------------------- 1 | ! Capacitor Definitions 2 | ! Capacitors are controlled single phase, so must be defined as single phase capacitors 3 | 4 | New Capacitor.CAPBank2A Bus1=R20185.1 kv=7.2 kvar=300 phases=1 conn=wye 5 | New Capacitor.CAPBank2B Bus1=R20185.2 kv=7.2 kvar=300 phases=1 conn=wye 6 | New Capacitor.CAPBank2C Bus1=R20185.3 kv=7.2 kvar=300 phases=1 conn=wye 7 | 8 | New Capacitor.CAPBank1A Bus1=R42247.1 kv=7.2 kvar=300 phases=1 conn=wye 9 | New Capacitor.CAPBank1B Bus1=R42247.2 kv=7.2 kvar=300 phases=1 conn=wye 10 | New Capacitor.CAPBank1C Bus1=R42247.3 kv=7.2 kvar=300 phases=1 conn=wye 11 | 12 | New Capacitor.CAPBank0A Bus1=R42246.1 kv=7.2 kvar=400 phases=1 conn=wye 13 | New Capacitor.CAPBank0B Bus1=R42246.2 kv=7.2 kvar=400 phases=1 conn=wye 14 | New Capacitor.CAPBank0C Bus1=R42246.3 kv=7.2 kvar=400 phases=1 conn=wye 15 | 16 | ! This is a 3-phase capacitor bank 17 | New Capacitor.CAPBank3 Bus1=R18242 kv=12.47112 kvar=900 conn=wye 18 | -------------------------------------------------------------------------------- /tests/readers/opendss/Lines/test_fuses.dss: -------------------------------------------------------------------------------- 1 | Clear 2 | 3 | New circuit.test_fuses basekv=12.47 pu=1.01 phases=3 bus1=sourcebus 4 | 5 | New Line.origin Units=km Length=0.001 bus1=sourcebus bus2=node1 phases=3 6 | 7 | New Line.line1 Units=km Length=0.001 bus1=node1.1.2.3 bus2=node2.1.2.3 switch=n enabled=y phases=3 Normamps=3000 EmergAmps=4000 8 | New Fuse.Fuse_1 monitoredobj=Line.line1 enabled=y 9 | 10 | New Line.line2 Units=km Length=0.001 bus1=node1.1 bus2=node3.1 switch=n enabled=y phases=1 Normamps=3000 EmergAmps=4000 11 | New Fuse.Fuse_2 monitoredobj=Line.line2 enabled=y 12 | 13 | New Line.line3 Units=km Length=0.001 bus1=node1.3 bus2=node4.3 switch=n enabled=y phases=1 Normamps=3000 EmergAmps=4000 14 | New Fuse.Fuse_3 monitoredobj=Line.line3 enabled=y 15 | 16 | New Line.line4 Units=km Length=0.001 bus1=node1.2.3 bus2=node4.2.3 switch=n enabled=y phases=2 Normamps=3000 EmergAmps=4000 17 | New Fuse.Fuse_4 monitoredobj=Line.line4 enabled=True 18 | 19 | Set Voltagebases=[12.47] 20 | Calcvoltagebases 21 | Solve 22 | -------------------------------------------------------------------------------- /ditto/models/phase_winding.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from builtins import super, range, zip, round, map 3 | 4 | from .base import DiTToHasTraits, Float, Unicode, Any, Int, List, observe, Instance 5 | 6 | from .position import Position 7 | 8 | 9 | class PhaseWinding(DiTToHasTraits): 10 | tap_position = Float( 11 | help="""The initial tap position of the phase on the winding. It should be in the range [lowstep,highstep] provided by the transformer or regulator""", 12 | default_value=None, 13 | ) 14 | phase = Unicode( 15 | help="""The phase for this componant of the winding (A,B,C, N,s1,s2)""", 16 | default_value=None, 17 | ) 18 | compensator_r = Float( 19 | help="""The compensator resistance value for the phase""", default_value=None 20 | ) 21 | compensator_x = Float( 22 | help="""The compensator reactance value for the phase""", default_value=None 23 | ) 24 | 25 | def build(self, model): 26 | self._model = model 27 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/epri_j1/capacitors.dss: -------------------------------------------------------------------------------- 1 | New Capacitor.B4909-1 bus=B4909 kV=12.47 kvar=900 conn=wye 2 | New Capacitor.B18944 bus=B18941 kV=12.47 kvar=1200 conn=wye 3 | New Capacitor.B4862-1 bus=B4862 kV=12.47 kvar=600 conn=wye 4 | New Capacitor.B4829-1 bus=B4829 kV=12.47 kvar=600 conn=wye 5 | New Capacitor.B4877-1 bus=B4877 kV=12.47 kvar=600 conn=wye 6 | 7 | New Capcontrol.B4909-1 Capacitor=B4909-1 element=Line.OH_B4904 terminal=1 Delay=30 8 | ~ type=volt ON=120.5 OFF=125 PTphase=2 PTratio=60 9 | New Capcontrol.B18944 Capacitor=B18944 element=Line.OH_B18944 terminal=1 Delay=31 10 | ~ type=volt ON=118 OFF=124 PTphase=1 PTratio=60 11 | !New Capcontrol.B4862-1 Capacitor=B4862-1 element=Line.OH_B4861 terminal=1 Delay=17 12 | !~ type=volt ON=120.9 OFF=123.5 PTphase=2 PTratio=63.5 13 | !New Capcontrol.B4829-1 Capacitor=B4829-1 element=Line.OH_B4832 terminal=1 Delay=18 14 | !~ type=volt ON=120.9 OFF=123.5 PTphase=2 PTratio=63.5 15 | New Capcontrol.B4877-1 Capacitor=B4877-1 element=Line.OH_B4873 terminal=1 Delay=32 16 | ~ type=volt ON=121 OFF=125 PTphase=2 PTratio=60 -------------------------------------------------------------------------------- /ditto/models/load_layer.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from builtins import super, range, zip, round, map 3 | 4 | from .base import DiTToHasTraits, Float, Unicode, Any, Int, List, observe 5 | 6 | from .position import Position 7 | 8 | 9 | class LoadLayer(DiTToHasTraits): 10 | 11 | name = Unicode(help="""Name of the load object""") 12 | interval = Integer( 13 | help="""The time resolution (in seconds) for the measured data""" 14 | ) 15 | current = Any(help="""The input data for the ZIP current measurements""") 16 | impedance = Any(help="""The input data for the ZIP imedance measurements""") 17 | power = Any(help="""The input data for the ZIP power measurements""") 18 | positions = List( 19 | Instance(Position), 20 | help="""This parameter is a list of positional points describing the load data. The positions are objects containing elements of long, lat and elevation (See Position object documentation).""", 21 | ) 22 | 23 | def build(self, model): 24 | self._model = model 25 | -------------------------------------------------------------------------------- /tests/readers/cyme/test_connection_configuration_mapping.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from ditto.readers.cyme.read import Reader 3 | from itertools import chain 4 | 5 | @pytest.mark.parametrize('value, expected_configuration', list(chain( 6 | [(el, 'Y') for el in (0, "0", "Yg", "yg")], 7 | [(el, 'Y') for el in (1, "1", "Y", "y")], 8 | [(el, 'D') for el in (2, "2", "Delta", "delta")], 9 | [(el, 'D') for el in (3, "3", "Open Delta", "open delta")], 10 | [(el, 'D') for el in (4, "4", "Closed Delta", "closed delta")], 11 | [(el, 'Z') for el in (5, "5", "Zg", "zg")], 12 | ))) 13 | def test_connection_configuration_mapping(value, expected_configuration): 14 | configuration = Reader().connection_configuration_mapping(value) 15 | assert configuration == expected_configuration 16 | 17 | 18 | @pytest.mark.parametrize('value', [ 19 | 6, "6", 'ct', 20 | 7, "7", 'dg', 21 | ]) 22 | def test_unmapped_inputs_to_connection_configuration_mapping(value): 23 | with pytest.raises(NotImplementedError): 24 | Reader().connection_configuration_mapping(value) 25 | 26 | -------------------------------------------------------------------------------- /ditto/models/meter.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from builtins import super, range, zip, round, map 3 | 4 | from .base import DiTToHasTraits, Float, Unicode, Any, Int, List, observe, Instance 5 | 6 | from .position import Position 7 | 8 | 9 | class Meter(DiTToHasTraits): 10 | 11 | name = Unicode(help="""Name of the meter object""") 12 | nominal_voltage = Float(help="""The nominal voltage of the meter""") 13 | positions = List( 14 | Instance(Position), 15 | help="""This parameter is a list of positional points describing the line. The positions are objects containing elements of long, lat and elevation (See Position object documentation). The points can be used to map the position of the line. """, 16 | ) 17 | 18 | # NOT YET CIM COMPATIBLE 19 | phases = List( 20 | Instance(Unicode), 21 | help="""This parameter is a list of all the phases at the node. The Phases are Strings of 'A', 'B', 'C', 'N', 's1' or 's2' (for secondaries).""", 22 | ) 23 | 24 | def build(self, model): 25 | self._model = model 26 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | DiTTo documentation! 2 | ================================= 3 | 4 | [![image](https://travis-ci.org/NREL/ditto.svg?branch=master)](https://travis-ci.org/NREL/ditto) 5 | [![image](https://badges.gitter.im/NREL/ditto.png)](https://gitter.im/NREL/ditto) 6 | [![image](https://img.shields.io/badge/docs-ready-blue.svg)](https://nrel.github.io/ditto) 7 | 8 | DiTTo is a _Distribution Transformation Tool_ that aims at providing an open source framework to convert various distribution systems modeling formats. 9 | DiTTo implements a _many-to-one-to-many_ parsing framework which makes it modular and robust. 10 | Readers and writers are then implemented to perform the translation from a given format to the core representation, or the other way around. 11 | 12 | - [GitHub](https://github.com/NREL/ditto) 13 | - [Gitter](https://gitter.im/NREL/ditto) 14 | - [Documentation](https://nrel.github.io/ditto) 15 | 16 | ```eval_rst 17 | .. toctree:: 18 | :maxdepth: 1 19 | 20 | readme 21 | installation 22 | usage 23 | cli-examples 24 | notes 25 | contributing 26 | developer-guide/index 27 | authors 28 | ``` 29 | 30 | 31 | -------------------------------------------------------------------------------- /ditto/models/phase_capacitor.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from builtins import super, range, zip, round, map 3 | 4 | from .base import DiTToHasTraits, Float, Unicode, Any, Int, List, observe, Instance 5 | 6 | from .position import Position 7 | 8 | 9 | class PhaseCapacitor(DiTToHasTraits): 10 | 11 | phase = Unicode( 12 | help="""The phase (A, B, C, N, s1, s2) of the capacitor section""", 13 | default_value=None, 14 | ) 15 | var = Float(help="""The var rating of the capacitor phase""", default_value=None) 16 | switch = Int( 17 | help="""A boolean value to denote whether or not the capacitor is switched in. 1 means it's switched in, 0 means that it's not""", 18 | default_value=None, 19 | ) 20 | sections = Int( 21 | help="""The maximum number of sections connected to this phase""", 22 | default_value=None, 23 | ) 24 | normalsections = Int( 25 | help="""The normal number of sections connected to this phase""", 26 | default_value=None, 27 | ) 28 | 29 | def build(self, model): 30 | self._model = model 31 | -------------------------------------------------------------------------------- /ditto/models/weather_layer.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from builtins import super, range, zip, round, map 3 | 4 | from .base import DiTToHasTraits, Float, Unicode, Any, Int, List, observe 5 | 6 | from .position import Position 7 | 8 | 9 | class WeatherLayer(DiTToHasTraits): 10 | 11 | name = Unicode(help="""Name of the weather object""") 12 | interval = Integer( 13 | help="""The time resolution (in seconds) for the measured data""" 14 | ) 15 | ghi = Any(help="""The input data for global horizontal irradiance""") 16 | temperature = Any(help="""The input data for temperature""") 17 | relative_humitidy = Any(help="""The input data for relative humidity""") 18 | surface_windspeed = Any(help="""The input data for surface windspeed""") 19 | positions = List( 20 | Instance(Position), 21 | help="""This parameter is a list of positional points describing the weather data. The positions are objects containing elements of long, lat and elevation (See Position object documentation).""", 22 | ) 23 | 24 | def build(self, model): 25 | self._model = model 26 | -------------------------------------------------------------------------------- /ditto/dittolayers.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module facilitates the creation of DiTTo layerstacks (https://github.com/Smart-DS/layerstack). 3 | """ 4 | 5 | from __future__ import absolute_import, division, print_function 6 | from builtins import super, range, zip, round, map 7 | 8 | import logging 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | from layerstack.layer import ModelLayerBase 13 | 14 | 15 | class DiTToLayerBase(ModelLayerBase): 16 | 17 | @classmethod 18 | def _check_model_type(cls, model): 19 | # Check to make sure model is of the proper type 20 | pass 21 | 22 | @classmethod 23 | def _load_model(cls, model_path): 24 | # Method to load model 25 | logger.error( 26 | "DiTTo models cannot be loaded. Start your stack with a load-model layer." 27 | ) 28 | raise ("DiTTo models cannot be loaded.") 29 | 30 | @classmethod 31 | def _save_model(cls, model_path): 32 | # Method to save model 33 | logger.error( 34 | "DiTTo models cannot be saved. End your stack with a save-model layer." 35 | ) 36 | raise ("DiTTo models cannot be saved.") 37 | -------------------------------------------------------------------------------- /tests/readers/opendss/Lines/test_linegeometries.dss: -------------------------------------------------------------------------------- 1 | Clear 2 | 3 | New Circuit.test_circuit 4 | 5 | Redirect test_concentricneutral.dss 6 | 7 | New Wiredata.wire1 GMR=0.0255000 DIAM=0.7410000 RAC=0.3060000 NormAmps=530.0000 Runits=mi radunits=in gmrunits=ft 8 | New Wiredata.wire2 GMR=0.0044600 DIAM=0.3980000 RAC=1.120000 NormAmps=230.0000 Runits=mi radunits=in gmrunits=ft 9 | 10 | New Linegeometry.geometry_1 nconds=4 nphases=3 11 | ~ cond=1 Wire=wire1 x=-1.2909 h=13.716 units=m 12 | ~ cond=2 Wire=wire1 x=-0.1530096 h=4.1806368 units=ft 13 | ~ cond=3 Wire=wire1 x=0.5737 h=13.716 units=m 14 | ~ cond=4 Wire=wire2 x=0 h=14.648 ! units=m ! neutral 15 | 16 | New LineGeometry.geometry_2 nconds=3 nphases=3 units=ft 17 | ~ cond=1 cncable=cndata1 x=-0.5 h= -4 18 | ~ cond=2 cncable=cndata1 x=0 h= -4 19 | ~ cond=3 cncable=cndata1 x=0.5 h= -4 20 | 21 | New Line.Line1 Bus1=bus1.1.2.3 Bus2=bus2.1.2.3 22 | ~ Geometry= geometry_1 23 | ~ Length=300 units=ft EarthModel=FULLCARSON 24 | 25 | New Line.Line2 Bus1=bus2.1.2.3 Bus2=bus3.1.2.3 26 | ~ Geometry= geometry_2 27 | ~ Length=1 units=mi EarthModel=FULLCARSON 28 | 29 | 30 | Set Voltagebases=[4.8,34.5,115.0] 31 | Calcvoltagebases 32 | Solve 33 | -------------------------------------------------------------------------------- /tests/data/ditto-validation/cyme/breakers/equipment.txt: -------------------------------------------------------------------------------- 1 | [GENERAL] 2 | DATE=June 04, 2018 at 12:08:21 3 | CYME_VERSION=8.00 4 | CYMDIST_REVISION=8 5 | 6 | [SI] 7 | 8 | [BREAKER] 9 | FORMAT_BREAKER=ID,Amps,Amps_1,Amps_2,Amps_3,Amps_4,KVLL,Reversible,InterruptingRating,FailRate,TmpFailRate,MajorRepairTime,MinorRepairTime,MajorFailureProportion,StuckProbability,SwitchTime,SymbolOpenID,SymbolCloseID,SinglePhaseLocking,SinglePhaseTripping,RemoteControlled,Automated,Standard,Manufacturer,Model,ANSIMaxRatedVoltage,ANSIRatedRangeKFactor,ANSIMaxSymetricalRMS,ANSIClosingLatchingRMS,ANSIClosingLatchingCrest,IECMakingCurrent,InterruptingTime,Favorite,Flags,Comments 10 | BREAKER_1,100.000000,100.000000,100.000000,100.000000,100.000000,13.800000,1,600.000000,,,,,,,,0,0,0,0,0,0,0,,,4.760000,1.240000,36.000000,58.000000,97.000000,79.000000,50.000000,0,0, 11 | BREAKER_2,300.000000,300.000000,300.000000,300.000000,300.000000,13.800000,1,800.000000,,,,,,,,0,0,0,0,0,0,0,,,4.760000,1.240000,36.000000,58.000000,97.000000,79.000000,50.000000,0,0, 12 | DEFAULT,1200.000000,100.000000,100.000000,100.000000,100.000000,4.160000,1,29000.000000,,,,,,,,0,0,0,0,0,0,0,,,4.760000,1.240000,36.000000,58.000000,97.000000,79.000000,50.000000,0,0, 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | os: 11 | - ubuntu-latest 12 | - windows-latest 13 | python-version: ['3.8', '3.12'] 14 | 15 | runs-on: ${{ matrix.os }} 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Set up Python 19 | uses: actions/setup-python@v4 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install -U pip 25 | pip install ".[all,test]" 26 | - name: run pytests 27 | run: | 28 | pip install pytest pytest-ordering pytest-cov 29 | pytest -vvv 30 | - name: Generate coverage report 31 | run: | 32 | pytest --cov=./ --cov-report=xml:unit.coverage.xml 33 | - name: Upload unit test coverage to Codecov 34 | uses: codecov/codecov-action@v1.0.7 35 | with: 36 | token: ${{ secrets.CODECOV_TOKEN }} 37 | file: ./unit.coverage.xml 38 | flags: unit 39 | env_vars: PYTHON 40 | name: codecov-unit 41 | fail_ci_if_error: false 42 | -------------------------------------------------------------------------------- /ditto/formats/gridlabd/base.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from builtins import super, range, zip, round, map 3 | 4 | 5 | class GridLABDBase(object): 6 | 7 | _properties = [] 8 | 9 | def __init__(self, *args, **kwargs): 10 | 11 | for p in self._build_properties(): 12 | self._properties = self._properties + p 13 | 14 | for k, v in kwargs.items(): 15 | self[k] = v 16 | 17 | def _build_properties(self): 18 | for c in self.__class__.mro(): 19 | if c != self.__class__ and c != object: 20 | yield c._properties 21 | 22 | def __getitem__(self, k): 23 | try: 24 | return getattr(self, "_{}".format(k)) 25 | except AttributeError as e: 26 | raise AttributeError( 27 | "{} not found in {}".format(k, self.__class__.__name__) 28 | ) 29 | 30 | def __setitem__(self, k, v): 31 | if k not in [p["name"] for p in self._properties]: 32 | raise AttributeError( 33 | "Unable to set {} with {} on {}".format(k, v, self.__class__.__name__) 34 | ) 35 | return setattr(self, "_{}".format(k), v) 36 | -------------------------------------------------------------------------------- /tests/data/ditto-validation/cyme/breakers/load.txt: -------------------------------------------------------------------------------- 1 | [GENERAL] 2 | DATE=June 04, 2018 at 12:08:21 3 | CYME_VERSION=8.00 4 | CYMDIST_REVISION=8 5 | 6 | [SI] 7 | 8 | [CUSTOMER CLASS] 9 | FORMAT_CUSTOMERCLASS=ID,DCCustomerType,Description,Color,ConstantPower,ConstantCurrent,ConstantImpedance,UtilizationFactor,PowerFactor,MeteredLoads,NonMeteredLoads,LoadFactor,FrequencySensitivityP,FrequencySensitivityQ,EnableHarmonic,HarmonicCurrentSourceInPercent,IsExponentialModel,ConstantImpedanceZP,ConstantImpedanceZQ,ConstantCurrentIP,ConstantCurrentIQ,ConstantPowerPP,ConstantPowerPQ,ExponentialModelP,ExponentialModelQ,LoadFlowVoltagePercentOfNominal,LossesPerCustomer,ConnectedCenterTap1N,ConnectedCenterTap2N,AdjustmentSettings,PowerCurveModel,PowerCurveModelId 10 | Commercial, 11 | 12 | [LOADS] 13 | FORMAT_LOADS=SectionID,DeviceNumber,DeviceStage,Flags,LoadType,Connection,Location 14 | 15 | [CUSTOMER LOADS] 16 | FORMAT_CUSTOMERLOADS=SectionID,DeviceNumber,LoadType,CustomerNumber,CustomerType,ConnectionStatus,LockDuringLoadAllocation,Year,LoadModelID,NormalPriority,EmergencyPriority,ValueType,LoadPhase,Value1,Value2,ConnectedKVA,KWH,NumberOfCustomer,CenterTapPercent,CenterTapPercent2,LoadValue1N1,LoadValue1N2,LoadValue2N1,LoadValue2N2 17 | 18 | -------------------------------------------------------------------------------- /tests/data/ditto-validation/cyme/switches/load.txt: -------------------------------------------------------------------------------- 1 | [GENERAL] 2 | DATE=June 04, 2018 at 12:08:21 3 | CYME_VERSION=8.00 4 | CYMDIST_REVISION=8 5 | 6 | [SI] 7 | 8 | [CUSTOMER CLASS] 9 | FORMAT_CUSTOMERCLASS=ID,DCCustomerType,Description,Color,ConstantPower,ConstantCurrent,ConstantImpedance,UtilizationFactor,PowerFactor,MeteredLoads,NonMeteredLoads,LoadFactor,FrequencySensitivityP,FrequencySensitivityQ,EnableHarmonic,HarmonicCurrentSourceInPercent,IsExponentialModel,ConstantImpedanceZP,ConstantImpedanceZQ,ConstantCurrentIP,ConstantCurrentIQ,ConstantPowerPP,ConstantPowerPQ,ExponentialModelP,ExponentialModelQ,LoadFlowVoltagePercentOfNominal,LossesPerCustomer,ConnectedCenterTap1N,ConnectedCenterTap2N,AdjustmentSettings,PowerCurveModel,PowerCurveModelId 10 | Commercial, 11 | 12 | [LOADS] 13 | FORMAT_LOADS=SectionID,DeviceNumber,DeviceStage,Flags,LoadType,Connection,Location 14 | 15 | [CUSTOMER LOADS] 16 | FORMAT_CUSTOMERLOADS=SectionID,DeviceNumber,LoadType,CustomerNumber,CustomerType,ConnectionStatus,LockDuringLoadAllocation,Year,LoadModelID,NormalPriority,EmergencyPriority,ValueType,LoadPhase,Value1,Value2,ConnectedKVA,KWH,NumberOfCustomer,CenterTapPercent,CenterTapPercent2,LoadValue1N1,LoadValue1N2,LoadValue2N1,LoadValue2N2 17 | 18 | -------------------------------------------------------------------------------- /tests/data/ditto-validation/cyme/network_protectors/load.txt: -------------------------------------------------------------------------------- 1 | [GENERAL] 2 | DATE=June 04, 2018 at 12:08:21 3 | CYME_VERSION=8.00 4 | CYMDIST_REVISION=8 5 | 6 | [SI] 7 | 8 | [CUSTOMER CLASS] 9 | FORMAT_CUSTOMERCLASS=ID,DCCustomerType,Description,Color,ConstantPower,ConstantCurrent,ConstantImpedance,UtilizationFactor,PowerFactor,MeteredLoads,NonMeteredLoads,LoadFactor,FrequencySensitivityP,FrequencySensitivityQ,EnableHarmonic,HarmonicCurrentSourceInPercent,IsExponentialModel,ConstantImpedanceZP,ConstantImpedanceZQ,ConstantCurrentIP,ConstantCurrentIQ,ConstantPowerPP,ConstantPowerPQ,ExponentialModelP,ExponentialModelQ,LoadFlowVoltagePercentOfNominal,LossesPerCustomer,ConnectedCenterTap1N,ConnectedCenterTap2N,AdjustmentSettings,PowerCurveModel,PowerCurveModelId 10 | Commercial, 11 | 12 | [LOADS] 13 | FORMAT_LOADS=SectionID,DeviceNumber,DeviceStage,Flags,LoadType,Connection,Location 14 | 15 | [CUSTOMER LOADS] 16 | FORMAT_CUSTOMERLOADS=SectionID,DeviceNumber,LoadType,CustomerNumber,CustomerType,ConnectionStatus,LockDuringLoadAllocation,Year,LoadModelID,NormalPriority,EmergencyPriority,ValueType,LoadPhase,Value1,Value2,ConnectedKVA,KWH,NumberOfCustomer,CenterTapPercent,CenterTapPercent2,LoadValue1N1,LoadValue1N2,LoadValue2N1,LoadValue2N2 17 | 18 | -------------------------------------------------------------------------------- /tests/readers/opendss/Capacitors/test_capacitor_kvar.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | test_capacitor_kvar.py 5 | ---------------------------------- 6 | 7 | Tests for parsing kvar values of Capacitors from OpenDSS into DiTTo. 8 | """ 9 | 10 | import os 11 | import math 12 | import pytest 13 | import numpy as np 14 | 15 | from ditto.store import Store 16 | from ditto.readers.opendss.read import Reader 17 | 18 | current_directory = os.path.realpath(os.path.dirname(__file__)) 19 | 20 | 21 | def test_capacitor_kvar(): 22 | m = Store() 23 | r = Reader(master_file=os.path.join(current_directory, "test_capacitor_kvar.dss")) 24 | r.parse(m) 25 | m.set_names() 26 | 27 | assert len(m["cap1"].phase_capacitors) == 3 # Cap1 is a three phase capacitor 28 | assert sum( 29 | [phase_capacitor.var for phase_capacitor in m["cap1"].phase_capacitors] 30 | ) == pytest.approx(600 * 10 ** 3, 0.0001) 31 | 32 | assert len(m["cap2"].phase_capacitors) == 1 # Cap2 is a one phase capacitor 33 | assert m["cap2"].phase_capacitors[0].var == 100 * 10 ** 3 34 | 35 | assert len(m["cap3"].phase_capacitors) == 1 # Cap3 is a one phase capacitor 36 | assert m["cap3"].phase_capacitors[0].var == 200.37 * 10 ** 3 37 | -------------------------------------------------------------------------------- /tests/test_demo_to_gridlabd.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | test_gridlabd_to_ephasor 4 | ---------------------------------- 5 | 6 | Tests for Demo --> Gridlab-D conversion 7 | """ 8 | import six 9 | 10 | if six.PY2: 11 | from backports import tempfile 12 | else: 13 | import tempfile 14 | 15 | import pytest as pt 16 | import os 17 | 18 | current_directory = os.path.realpath(os.path.dirname(__file__)) 19 | def test_demo_to_gridlabd(): 20 | from ditto.readers.demo.read import Reader 21 | from ditto.store import Store 22 | from ditto.writers.gridlabd.write import Writer 23 | 24 | demo_models=[f for f in os.listdir(os.path.join(current_directory,'data/small_cases/demo/')) if not f.startswith('.')] 25 | for model in demo_models: 26 | m = Store() 27 | r = Reader(input_file=os.path.join(current_directory,'data/small_cases/demo',model)) 28 | r.parse(m) 29 | m.set_names() 30 | print('>Demo model {model} read...'.format(model=os.path.join(current_directory,'data/small_cases/demo',model))) 31 | output_path = tempfile.TemporaryDirectory() 32 | w = Writer(output_path=output_path.name, log_path=output_path) 33 | w.write(m) 34 | print('>...and written to Gridlab-D.\n') 35 | -------------------------------------------------------------------------------- /tests/test_gridlabd_to_ephasor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | test_gridlabd_to_ephasor 4 | ---------------------------------- 5 | 6 | Tests for GridlabD --> Ephasor conversion 7 | """ 8 | import six 9 | 10 | if six.PY2: 11 | from backports import tempfile 12 | else: 13 | import tempfile 14 | 15 | import pytest as pt 16 | import os 17 | 18 | current_directory = os.path.realpath(os.path.dirname(__file__)) 19 | 20 | @pt.mark.skip() #currently not running... 21 | def test_gridlabd_to_ephasor(): 22 | from ditto.readers.gridlabd.read import Reader 23 | from ditto.store import Store 24 | from ditto.writers.ephasor.write import Writer 25 | 26 | gridlabd_models=[f for f in os.listdir(os.path.join(current_directory,'data/small_cases/gridlabd/')) if not f.startswith('.')] 27 | for model in gridlabd_models: 28 | m = Store() 29 | r = Reader() 30 | r.parse(m) 31 | m.set_names() 32 | #TODO: Log properly 33 | print('>Gridlab-D model {model} read...'.format(model=model)) 34 | output_path = tempfile.TemporaryDirectory() 35 | w = Writer(output_path=output_path.name, log_path=output_path) 36 | w.write(m) 37 | #TODO: Log properly 38 | print('>...and written to Ephasorsim.\n') 39 | -------------------------------------------------------------------------------- /tests/test_cyme_to_ephasor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | test_cyme_to_ephasor 5 | ---------------------------------- 6 | 7 | Tests for Cyme --> Ephasor conversion 8 | """ 9 | import os 10 | import six 11 | 12 | if six.PY2: 13 | from backports import tempfile 14 | else: 15 | import tempfile 16 | 17 | import pytest as pt 18 | 19 | current_directory = os.path.realpath(os.path.dirname(__file__)) 20 | 21 | def test_cyme_to_ephasor(): 22 | ''' 23 | Test the Cyme to Ephasor conversion. 24 | ''' 25 | from ditto.store import Store 26 | from ditto.readers.cyme.read import Reader 27 | from ditto.writers.ephasor.write import Writer 28 | 29 | cyme_models=[f for f in os.listdir(os.path.join(current_directory, 'data/small_cases/cyme/')) if not f.startswith('.')] 30 | for model in cyme_models: 31 | m = Store() 32 | r = Reader(data_folder_path=os.path.join(current_directory, 'data/small_cases/cyme',model)) 33 | r.parse(m) 34 | #TODO: Log properly 35 | print('>Cyme model {model} red...'.format(model=model)) 36 | t = tempfile.TemporaryDirectory() 37 | w = Writer(output_path=t.name) 38 | w.write(m) 39 | #TODO: Log properly 40 | print('>...and written to Ephasor.\n') 41 | -------------------------------------------------------------------------------- /tests/persistence/update_json.py: -------------------------------------------------------------------------------- 1 | from ditto.store import Store 2 | from ditto.writers.json.write import Writer 3 | from ditto.readers.cyme.read import Reader as Reader_cyme 4 | from ditto.readers.opendss.read import Reader as Reader_opendss 5 | import os 6 | 7 | def update_persistence_jsons(): 8 | test_list = os.walk('data') 9 | for (dirpath, dirname, files) in test_list: 10 | if files !=[]: 11 | reader_type = dirpath.split('\\')[2] 12 | m = Store() 13 | if reader_type == 'opendss': 14 | reader = Reader_opendss(master_file = os.path.join('..',dirpath,'master.dss'), buscoordinates_file = os.path.join('..',dirpath,'buscoord.dss')) 15 | elif reader_type == 'cyme': 16 | reader = Reader_cyme(data_folder_path=os.path.join('..',dirpath),load_filename="load.txt", network_filename="network.txt", equipment_filename="equipment.txt") 17 | else: 18 | #Update with other tests if they get added to the persistence tests 19 | continue 20 | reader.parse(m) 21 | m.set_names() 22 | print("Writing "+dirpath) 23 | w = Writer(output_path=dirpath, log_path=dirpath) 24 | w.write(m) 25 | 26 | if __name__ == '__main__': 27 | update_persistence_jsons() 28 | -------------------------------------------------------------------------------- /tests/readers/opendss/Lines/test_switches.dss: -------------------------------------------------------------------------------- 1 | Clear 2 | 3 | New circuit.test_switches basekv=12.47 pu=1.01 phases=3 bus1=sourcebus 4 | 5 | New Line.origin Units=km Length=0.001 bus1=sourcebus bus2=node1 phases=3 6 | 7 | New Line.switch1 Units=km Length=0.001 bus1=node1 bus2=node2 switch=y enabled=y phases=3 Normamps=3000 EmergAmps=4000 8 | 9 | New Line.switch2 Units=km Length=0.001 bus1=node1 bus2=node3 switch=y enabled=n phases=3 Normamps=3000 EmergAmps=4000 10 | 11 | New Line.switch3 Units=km Length=0.001 bus1=node1.1.2.3 bus2=node4.1.2.3 switch=y enabled=n phases=3 Normamps=3000 EmergAmps=4000 12 | 13 | New Line.switch4 Units=km Length=0.001 bus1=node1.1.2.3 bus2=node5.1.2.3 switch=y enabled=y phases=3 Normamps=3000 EmergAmps=4000 14 | 15 | New Line.switch5 Units=km Length=0.001 bus1=node1.1 bus2=node6.1 switch=y enabled=y phases=1 Normamps=3000 EmergAmps=4000 16 | 17 | New Line.switch6 Units=km Length=0.001 bus1=node1.3 bus2=node7.3 switch=y enabled=n phases=1 Normamps=3000 EmergAmps=4000 18 | 19 | New Line.switch7 Units=km Length=0.001 bus1=node1.2.3 bus2=node8.2.3 switch=y enabled=n phases=2 Normamps=3000 EmergAmps=4000 20 | 21 | New Line.switch8 Units=km Length=0.001 bus1=node1.1.2 bus2=node9.1.2 switch=y enabled=y phases=2 Normamps=3000 EmergAmps=4000 22 | 23 | Set Voltagebases=[12.47] 24 | Calcvoltagebases 25 | Solve 26 | -------------------------------------------------------------------------------- /.github/workflows/publish-to-pypi.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python 🐍 distributions 📦 to TestPyPI and PyPI 2 | 3 | on: push 4 | 5 | jobs: 6 | build-n-publish: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@master 11 | - name: Set up Python 3.7 12 | uses: actions/setup-python@v1 13 | with: 14 | python-version: 3.7 15 | - name: Install dependencies—pandoc 16 | run: | 17 | # pandoc 18 | downloadUrl="https://github.com/jgm/pandoc/releases/download/2.19.2/pandoc-2.19.2-1-amd64.deb" 19 | wget --quiet "$downloadUrl" 20 | sudo dpkg -i "${downloadUrl##*/}" 21 | - name: Install pypandoc 22 | run: python -m pip install pypandoc 23 | - name: Build a binary wheel and a source tarball 24 | run: | 25 | python -m pip install wheel 26 | python setup.py sdist bdist_wheel 27 | - name: Publish distribution 📦 to Test PyPI 28 | uses: pypa/gh-action-pypi-publish@master 29 | with: 30 | password: ${{ secrets.TEST_PYPI_API_TOKEN }} 31 | repository_url: https://test.pypi.org/legacy/ 32 | skip_existing: true 33 | - name: Publish distribution 📦 to PyPI 34 | if: startsWith(github.ref, 'refs/tags') 35 | uses: pypa/gh-action-pypi-publish@master 36 | with: 37 | password: ${{ secrets.PYPI_API_TOKEN }} 38 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | **Stable release** 4 | 5 | To install the latest version of ditto from master for development, run this command in your terminal 6 | after changing your directory to the folder where you'd like to set up the development: 7 | 8 | ```bash 9 | $ pip install git+https://github.com/NREL/ditto.git@master#egg=ditto[all] 10 | ``` 11 | 12 | **From sources** 13 | 14 | The sources for ditto can be downloaded from the [Github repo](https://github.com/NREL/ditto). 15 | 16 | You can either clone the public repository: 17 | 18 | ```bash 19 | $ git clone git://github.com/NREL/ditto 20 | ``` 21 | 22 | Or download the [tarball](https://github.com/NREL/ditto/tarball/master): 23 | 24 | ```bash 25 | $ curl -OL https://github.com/NREL/ditto/tarball/master 26 | ``` 27 | 28 | Once you have a copy of the source, you can install it with: 29 | 30 | ```bash 31 | $ pip install -e .[all] 32 | ``` 33 | 34 | See the developer guide for instructions on how to add features to ditto. 35 | 36 | **Install specific packages alone** 37 | 38 | If you want specific packages alone, you will be able to do the following: 39 | 40 | ```bash 41 | $ pip install git+https://github.com/NREL/ditto.git@master#egg=ditto 42 | $ ditto list --readers 43 | $ ditto list --writers 44 | $ pip install git+https://github.com/NREL/ditto.git@master#egg=ditto[opendss,gridlabd] 45 | ``` 46 | -------------------------------------------------------------------------------- /ditto/models/phase_reactor.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from builtins import super, range, zip, round, map 3 | 4 | from .base import ( 5 | DiTToHasTraits, 6 | Float, 7 | Complex, 8 | Unicode, 9 | Any, 10 | Int, 11 | List, 12 | observe, 13 | Instance, 14 | ) 15 | 16 | from .position import Position 17 | from .wire import Wire 18 | 19 | 20 | class PhaseReactor(DiTToHasTraits): 21 | """ 22 | TODO 23 | """ 24 | phase = Unicode( 25 | help="""The phase (A, B, C, N, s1, s2) of the phase reactor""", 26 | default_value=None, 27 | ) 28 | ampacity = Float( 29 | help="""The ampacity rating for the phase reactor under nomal conditions""", 30 | default_value=None, 31 | ) 32 | emergency_ampacity = Float( 33 | help="""The ampacity rating for the phase reactor under emergency conditions""", 34 | default_value=None, 35 | ) 36 | rated_power = Float( 37 | help="""The rated power of the phase reactor""", default_value=None 38 | ) 39 | resistance = Float( 40 | help="""The total resistance of the phase reactor.""", default_value=None 41 | ) 42 | reactance = Float( 43 | help="""The total reactance of the phase reactor.""", default_value=None 44 | ) 45 | 46 | def build(self, model): 47 | self._model = model 48 | -------------------------------------------------------------------------------- /tests/readers/opendss/Lines/test_line_connectivity.dss: -------------------------------------------------------------------------------- 1 | Clear 2 | 3 | New circuit.test_line_connectivity basekv=4.16 pu=1.01 phases=3 bus1=sourcebus 4 | 5 | ! Line1 connects sourcebus to bus1 and should have 4 wires: A, B, C, and N 6 | New Line.line1 Bus1=sourcebus Bus2=bus1 phases=3 Length=100 units=m 7 | 8 | ! Line2 connects bus1 to bus2 and should have 4 wires: A, B, C, and N 9 | New Line.line2 Bus1=bus1.1.2.3 Bus2=bus2.1.2.3 phases=3 Length=200 units=m 10 | 11 | ! Line3 connects bus2 to bus3 and should have 3 wires: A, B, and N 12 | New Line.line3 Bus1=bus2.1.2 Bus2=bus3.1.2 phases=2 Length=50 units=m 13 | 14 | ! Line4 connects bus3 to bus4 and should have 2 wires: B, and N 15 | New Line.line4 Bus1=bus3.2 Bus2=bus4.2 phases=1 Length=25 units=m 16 | 17 | ! Line5 connects bus1 to bus5 and should have 3 wires: A, C, and N 18 | New Line.line5 Bus1=bus1.1.3 Bus2=bus5.1.3 phases=2 Length=1500 units=ft 19 | 20 | ! Line6 connects bus4 to bus6 and should have 3 wires: B, C, and N 21 | ! This line is obviously wrong since it is downstream of a one phase 22 | ! line. However, DiTTo cannot know this when reading. 23 | New Line.line6 Bus1=bus4.2.3 Bus2=bus6.2.3 phases=2 Length=110 units=m 24 | 25 | ! Line7 should raise some error in DiTTo since it only supports 1, 2, 3, and 0. 26 | New Line.line7 Bus1=bus1.2.3 Bus2=bus2.2.3 phases=2 Length=100 units=m 27 | 28 | Set Voltagebases=[4.16] 29 | Calcvoltagebases 30 | Solve 31 | -------------------------------------------------------------------------------- /tests/test_cyme_writer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | test_cyme_writer 5 | ---------------------------------- 6 | 7 | Tests for cyme writer 8 | """ 9 | import os 10 | import pytest as pt 11 | from ditto.store import Store 12 | from ditto.writers.cyme.write import Writer 13 | 14 | current_directory = os.path.realpath(os.path.dirname(__file__)) 15 | 16 | 17 | def test_center_tap_load_writing(): 18 | """ 19 | Tests the writing of center tap loads. 20 | """ 21 | from ditto.models.load import Load 22 | from ditto.models.phase_load import PhaseLoad 23 | 24 | m = Store() 25 | l = Load(m) 26 | l.name = "load1" 27 | l.is_center_tap = True 28 | l.center_tap_perct_1_N = 0.5 29 | l.center_tap_perct_N_2 = 0.5 30 | l.center_tap_perct_1_2 = 0 31 | 32 | pl = PhaseLoad(m) 33 | pl.p = 10 34 | pl.q = 8 35 | pl.phase = "C" 36 | l.phase_loads.append(pl) 37 | 38 | w = Writer(output_path=current_directory) 39 | w.write(m) 40 | 41 | with open(os.path.join(current_directory, "loads.txt"), "r") as fp: 42 | lines = fp.readlines() 43 | 44 | assert lines[-1] == ",SPOT,0,C,,,0,PQ,50.0,50.0,0.005,0.004,0.005,0.004,0\n" 45 | 46 | # Cleaning 47 | os.remove(os.path.join(current_directory, "loads.txt")) 48 | os.remove(os.path.join(current_directory, "equipment.txt")) 49 | os.remove(os.path.join(current_directory, "network.txt")) 50 | -------------------------------------------------------------------------------- /tests/default_values/test_default_values.json: -------------------------------------------------------------------------------- 1 | { 2 | "Line":{ 3 | "faultrate":0.2, 4 | "rmatrix":[ 5 | [ 6 | 0.00113148, 7 | 0.000142066 8 | ], 9 | [ 10 | 0.000142066, 11 | 0.00113362 12 | ] 13 | ], 14 | "xmatrix":[ 15 | [ 16 | 0.000884886, 17 | 0.000366115 18 | ], 19 | [ 20 | 0.000366115, 21 | 0.000882239 22 | ] 23 | ], 24 | "cmatrix":[ 25 | [ 26 | 0.00733718, 27 | -0.00239809 28 | ], 29 | [ 30 | -0.00239809, 31 | 0.00733718 32 | ] 33 | ] 34 | }, 35 | "Wire":{ 36 | "ampacity":200.0, 37 | "emergency_ampacity":400.0 38 | }, 39 | "Capacitor":{ 40 | "connection_type":"Y", 41 | "low":114, 42 | "high":125, 43 | "delay":10, 44 | "pt_ratio":50, 45 | "ct_ratio":50, 46 | "pt_phase":"A" 47 | }, 48 | "Transformer":{ 49 | "reactances":[ 50 | 6 51 | ] 52 | }, 53 | "Regulator":{ 54 | "ct_prim":300, 55 | "delay":16, 56 | "highstep":15, 57 | "pt_ratio":60, 58 | "bandwidth":3, 59 | "bandcenter":130 60 | }, 61 | "Load":{ 62 | "connection_type":"Y", 63 | "vmin":0.95, 64 | "vmax":1.05 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/readers/opendss/Generators/test_generators.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | test_generators.py 5 | ---------------------------------- 6 | 7 | Tests for parsing all values of Generators from OpenDSS into DiTTo. 8 | """ 9 | 10 | import os 11 | import math 12 | import pytest 13 | import numpy as np 14 | 15 | from ditto.store import Store 16 | from ditto.readers.opendss.read import Reader 17 | from ditto.default_values.default_values_json import Default_Values 18 | 19 | current_directory = os.path.realpath(os.path.dirname(__file__)) 20 | 21 | 22 | def test_generators(): 23 | m = Store() 24 | r = Reader(master_file=os.path.join(current_directory, "test_generators.dss")) 25 | r.parse(m) 26 | m.set_names() 27 | 28 | # Reading OpenDSS default values 29 | d_v = Default_Values( 30 | os.path.join( 31 | current_directory, 32 | "../../../../ditto/default_values/opendss_default_values.json", 33 | ) 34 | ) 35 | 36 | 37 | assert m["gen"].name == "gen" 38 | assert m["gen"].connecting_element == "powerbus1" 39 | assert m["gen"].forced_on == "No" 40 | assert m["gen"].power_factor == 0.95 41 | assert m["gen"].rated_power == 1.2 * 10 ** 3 42 | assert m["gen"].v_min_pu == 0.0 43 | assert m["gen"].v_max_pu == 1.2 44 | assert m["gen"].nominal_voltage == 2 * 10 ** 3 45 | assert m["gen"].feeder_name == "src_src" 46 | assert m["gen"].model == 3 47 | 48 | test_generators() -------------------------------------------------------------------------------- /ditto/utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function, unicode_literals 2 | from builtins import bytes, str, open, super, range, zip, round, input, int, pow 3 | 4 | from json import JSONEncoder 5 | import logging 6 | 7 | 8 | class DittoEncoder(JSONEncoder): 9 | 10 | def default(self, obj): 11 | return {k: getattr(obj, k) for k in obj._attrs} 12 | 13 | 14 | def combine(*dicts): 15 | """Given multiple dicts, merge them into a new dict as a shallow copy.""" 16 | super_dict = {key: val for d in dicts for key, val in d.items()} 17 | return super_dict 18 | 19 | 20 | def start_console_log( 21 | log_level=logging.WARN, 22 | log_format="%(asctime)s|%(levelname)s|%(name)s|\n %(message)s", 23 | ): 24 | console_handler = logging.StreamHandler() 25 | console_handler.setLevel(log_level) 26 | logformat = logging.Formatter(log_format) 27 | console_handler.setFormatter(logformat) 28 | logging.getLogger("ditto").setLevel(log_level) 29 | logging.getLogger("ditto").addHandler(console_handler) 30 | 31 | 32 | def start_file_log( 33 | filename, 34 | log_level=logging.WARN, 35 | log_format="%(asctime)s|%(levelname)s|%(name)s|\n %(message)s", 36 | ): 37 | file_handler = logging.FileHandler(filename=filename) 38 | file_handler.setLevel(log_level) 39 | logformat = logging.Formatter(log_format) 40 | file_handler.setFormatter(logformat) 41 | logging.getLogger("ditto").setLevel(log_level) 42 | logging.getLogger("ditto").addHandler(file_handler) 43 | -------------------------------------------------------------------------------- /tests/readers/opendss/Lines/test_line_length.dss: -------------------------------------------------------------------------------- 1 | Clear 2 | 3 | New circuit.test_line_length basekv=4.16 pu=1.01 phases=3 bus1=sourcebus 4 | 5 | ! Line 1 is a 100 meters 3 phase line 6 | New Line.line1 Bus1=sourcebus Bus2=bus1 phases=3 Length=100 units=m 7 | 8 | ! Line 2 is a 83.47 kilo-feet 3 phase line 9 | New Line.line2 Bus1=bus1.1.2.3 Bus2=bus2.1.2.3 phases=3 Length=83.47 units=kft 10 | 11 | ! Line 3 is a 200 feet 2 phases line 12 | New Line.line3 Bus1=bus2.1.3 Bus2=bus3.1.3 phases=2 Length=200 units=ft 13 | 14 | ! Line 4 is a 1.01 miles 1 phase line 15 | New Line.line4 Bus1=bus2.2 Bus2=bus4.2 phases=1 Length=1.01 units=mi 16 | 17 | ! Line 5 is a 2040.12 centimeters 3 phase line 18 | New Line.line5 Bus1=bus2 Bus2=bus5 phases=3 Length=2040.12 units=cm 19 | 20 | ! Line 6 is a 1666.87 inches 1 phase line 21 | New Line.line6 Bus1=bus2.1 Bus2=bus6.1 phases=1 Length=1666.87 units=in 22 | 23 | ! Line 7 uses like to have the same characteristics as line 1 24 | ! NOTE: DOES NOT WORK IN OPENDSSDIRECT. UNDER INVESTIGATION... 25 | ! IGNORE Line7 and Line8 for now... 26 | ! New Line.line7 Bus1=bus2 Bus2=Bus7 like=line1 27 | 28 | ! Line 8 uses like to have the same characteristics as line 1 29 | ! New Line.line8 Bus1=bus7 Bus2=Bus8 like=line1 Length=300 30 | 31 | New Line.line9 bus1=bus2 bus2=bus9 length=1.01 units=mi faultrate=0 32 | New Line.line10 bus1=bus2 bus2=bus10 length=1.01 units=mi faultrate=0.1 33 | New Line.line11 bus1=bus2 bus2=bus11 length=1.01 units=mi faultrate=1.0 34 | 35 | Set Voltagebases=[4.16] 36 | Calcvoltagebases 37 | Solve 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Alliance for Sustainable Energy LLC, All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | 9 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 10 | 11 | * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | -------------------------------------------------------------------------------- /tests/test_cyme_to_gridlabd.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | test_cyme_to_gridlabd 5 | ---------------------------------- 6 | 7 | Tests for Cyme --> Gridlabd conversion 8 | """ 9 | import os 10 | 11 | import six 12 | 13 | if six.PY2: 14 | from backports import tempfile 15 | else: 16 | import tempfile 17 | import pytest as pt 18 | 19 | current_directory = os.path.realpath(os.path.dirname(__file__)) 20 | 21 | def test_cyme_to_gridlabd(): 22 | ''' 23 | Test the Cyme to GridlabD conversion. 24 | ''' 25 | from ditto.store import Store 26 | from ditto.readers.cyme.read import Reader 27 | from ditto.writers.gridlabd.write import Writer 28 | 29 | cyme_models=[f for f in os.listdir(os.path.join(current_directory, './data/small_cases/cyme/')) if not f.startswith('.')] 30 | for model in cyme_models: 31 | m = Store() 32 | r = Reader(data_folder_path=os.path.join(current_directory, './data/small_cases/cyme',model)) 33 | r.parse(m) 34 | #TODO: Log properly 35 | print('>Cyme model {model} read...'.format(model=model)) 36 | t = tempfile.TemporaryDirectory() 37 | w = Writer(output_path=t.name) 38 | w.write(m) 39 | #TODO: Log properly 40 | print('>...and written to GridLabD.\n') 41 | 42 | for i in os.listdir(os.path.join(current_directory, "./")): 43 | if i.endswith(".glm"): 44 | try: 45 | os.remove(os.path.join(os.path.join(current_directory, "./"), i)) 46 | except: 47 | pass 48 | -------------------------------------------------------------------------------- /ditto/formats/gridlabd/gridlabd.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from builtins import super, range, zip, round, map 3 | 4 | import os 5 | import json 6 | from collections import OrderedDict 7 | import networkx as nx 8 | from .base import GridLABDBase 9 | from ditto.compat import common_str 10 | 11 | 12 | def __create(): 13 | dir_path = os.path.dirname(os.path.realpath(__file__)) 14 | 15 | with open(os.path.join(dir_path, "schema.json")) as f: 16 | string = f.read() 17 | 18 | schema = json.loads(string) 19 | klasses = OrderedDict() 20 | 21 | G = nx.DiGraph() 22 | for i in schema["objects"]: 23 | parent = schema["objects"][i]["parent"] 24 | if parent is not None: 25 | G.add_edge(parent, i) 26 | else: 27 | G.add_node(i) 28 | 29 | def generate_class(klass, properties, parent=None): 30 | if parent is None: 31 | parent = GridLABDBase 32 | 33 | return type( 34 | common_str(klass), (parent,), dict(_properties=properties["properties"]) 35 | ) 36 | 37 | for klass in nx.topological_sort(G): 38 | properties = schema["objects"][klass] 39 | 40 | if properties["parent"] is not None: 41 | parent = klasses[properties["parent"]] 42 | else: 43 | parent = None 44 | 45 | c = generate_class(klass, properties, parent=parent) 46 | klasses[klass] = c 47 | 48 | for k, c in klasses.items(): 49 | globals()[k] = c 50 | 51 | 52 | __create() 53 | -------------------------------------------------------------------------------- /tests/test_opendss_to_gridlabd.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | test_opendss_to_gridlabd 5 | ---------------------------------- 6 | 7 | Tests for OpenDSS --> GridlabD conversion 8 | """ 9 | import os 10 | import six 11 | 12 | if six.PY2: 13 | from backports import tempfile 14 | else: 15 | import tempfile 16 | import pytest as pt 17 | 18 | current_directory = os.path.realpath(os.path.dirname(__file__)) 19 | 20 | #@pt.mark.skip("Segfault occurs") 21 | def test_opendss_to_gridlabd(): 22 | ''' 23 | Test the OpenDSS to GridlabD conversion. 24 | ''' 25 | from ditto.readers.opendss.read import Reader 26 | from ditto.store import Store 27 | from ditto.writers.gridlabd.write import Writer 28 | 29 | opendss_models=[f for f in os.listdir(os.path.join(current_directory,'data/small_cases/opendss/')) if not f.startswith('.')] 30 | for model in opendss_models: 31 | m = Store() 32 | r = Reader( 33 | master_file=os.path.join(current_directory, 'data/small_cases/opendss/{model}/master.dss'.format(model=model)), 34 | buscoordinates_file=os.path.join(current_directory, 'data/small_cases/opendss/{model}/buscoord.dss'.format(model=model)) 35 | ) 36 | r.parse(m) 37 | m.set_names() 38 | #TODO: Log properly 39 | print('>OpenDSS model {model} red...'.format(model=model)) 40 | t = tempfile.TemporaryDirectory() 41 | w = Writer(output_path=t.name) 42 | w.write(m) 43 | #TODO: Log properly 44 | print('>...and written to GridLabD.\n') 45 | -------------------------------------------------------------------------------- /tests/test_opendss_to_cyme.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | test_opendss_to_cyme 5 | ---------------------------------- 6 | 7 | Tests for OpenDSS --> Cyme conversion 8 | """ 9 | import six 10 | 11 | if six.PY2: 12 | from backports import tempfile 13 | else: 14 | import tempfile 15 | 16 | import os 17 | import pytest as pt 18 | 19 | current_directory = os.path.realpath(os.path.dirname(__file__)) 20 | 21 | #@pt.mark.skip("Segfault occurs") 22 | def test_opendss_to_cyme(): 23 | ''' 24 | Test the OpenDSS to Cyme conversion. 25 | ''' 26 | from ditto.readers.opendss.read import Reader 27 | from ditto.store import Store 28 | from ditto.writers.cyme.write import Writer 29 | 30 | opendss_models=[f for f in os.listdir(os.path.join(current_directory,'data/small_cases/opendss/')) if not f.startswith('.')] 31 | for model in opendss_models: 32 | m = Store() 33 | r = Reader( 34 | master_file=os.path.join(current_directory, 'data/small_cases/opendss/{model}/master.dss'.format(model=model)), 35 | buscoordinates_file=os.path.join(current_directory, 'data/small_cases/opendss/{model}/buscoord.dss'.format(model=model)) 36 | ) 37 | r.parse(m) 38 | m.set_names() 39 | #TODO: Log properly 40 | print('>OpenDSS model {model} read...'.format(model=model)) 41 | output_path = tempfile.TemporaryDirectory() 42 | w = Writer(output_path=output_path.name, log_path=output_path) 43 | w.write(m) 44 | #TODO: Log properly 45 | print('>...and written to CYME.\n') 46 | -------------------------------------------------------------------------------- /tests/readers/cyme/test_transformer_connection_mapping.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ditto.readers.cyme.read import Reader 4 | 5 | 6 | @pytest.mark.parametrize('cyme_value, winding, expected', [ 7 | (0, 0, 'Y'), 8 | (0, 1, 'Y'), 9 | (1, 0, 'D'), 10 | (1, 1, 'Y'), 11 | (2, 0, 'Y'), 12 | (2, 1, 'D'), 13 | (3, 0, 'Y'), 14 | (3, 1, 'Y'), 15 | (4, 0, 'D'), 16 | (4, 1, 'D'), 17 | (5, 0, 'D'), 18 | (5, 1, 'D'), 19 | (6, 0, 'Y'), 20 | (6, 1, 'D'), 21 | (7, 0, 'D'), 22 | (7, 1, 'Y'), 23 | (8, 0, 'Y'), 24 | (8, 1, 'D'), 25 | (9, 0, 'Y'), 26 | (9, 1, 'Y'), 27 | (10, 0, 'Y'), 28 | (10, 1, 'Y'), 29 | (11, 0, 'Y'), 30 | (11, 1, 'Z'), 31 | (12, 0, 'D'), 32 | (11, 1, 'Z'), 33 | ]) 34 | def test_transformer_connection_mapping(cyme_value, winding, expected): 35 | reader = Reader() 36 | actual = reader.transformer_connection_configuration_mapping( 37 | cyme_value, winding 38 | ) 39 | assert actual == expected 40 | 41 | # Function also takes strings 42 | actual = reader.transformer_connection_configuration_mapping( 43 | str(cyme_value), winding 44 | ) 45 | assert actual == expected 46 | 47 | 48 | def test_transformer_connection_mapping_invalid_type(): 49 | reader = Reader() 50 | 51 | with pytest.raises(ValueError): 52 | reader.transformer_connection_configuration_mapping(0.0, 0) 53 | 54 | 55 | def test_transformer_connection_mapping_invalid_winding(): 56 | reader = Reader() 57 | 58 | with pytest.raises(ValueError): 59 | reader.transformer_connection_configuration_mapping(0.0, 2) 60 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/ieee_8500node/P174_Run_360kW_PV.DSS: -------------------------------------------------------------------------------- 1 | // Script for EPRI Report 1020157 2 | // This script corresponds to the simulation in Figure 7-7 3 | 4 | Compile (Master-unbal.dss) ! unbalanced load master 5 | 6 | New Energymeter.m1 Line.ln5815900-1 1 7 | 8 | Set Maxiterations=20 ! Sometimes the solution takes more than the default 15 iterations 9 | 10 | Solve 11 | 12 | // Drop the load to 40% 13 | set loadmult=0.4 maxcontrol=30 14 | solve 15 | 16 | // put 360 kW generator at m1026866 17 | new generator.G1 Bus1=m1026866 kV=12.47 kW=360 pf=1.0 18 | solve 19 | 20 | // Solar PV loadshape 21 | New Loadshape.PVCurve interval=(1.0 3600 /) npts=3000 mult=(File="Normalized-1s-2900-pts.CSV") 22 | generator.g1.duty=PVcurve ! assign curve to generator 23 | New Monitor.G1 Generator.G1 1 ! monitor generator output 24 | 25 | solve 26 | 27 | // restrict maxtapchange to 1 tap to better model regulator behavior 28 | regcontrol.feeder_rega.maxtapchange=1 Delay=30 29 | regcontrol.feeder_regb.maxtapchange=1 Delay=30 30 | regcontrol.feeder_regc.maxtapchange=1 Delay=30 31 | regcontrol.vreg2_a.maxtapchange=1 Delay=60 32 | regcontrol.vreg2_b.maxtapchange=1 Delay=60 33 | regcontrol.vreg2_c.maxtapchange=1 Delay=60 34 | regcontrol.vreg3_a.maxtapchange=1 Delay=45 35 | regcontrol.vreg3_b.maxtapchange=1 Delay=45 36 | regcontrol.vreg3_c.maxtapchange=1 Delay=45 37 | regcontrol.vreg4_a.maxtapchange=1 Delay=45 38 | regcontrol.vreg4_b.maxtapchange=1 Delay=45 39 | regcontrol.vreg4_c.maxtapchange=1 Delay=45 40 | 41 | // solve 2900 points 42 | solve mode=duty number=2900 stepsize=1 43 | 44 | plot monitor object=g1 channels=[1 3 5] bases=[7200 7200 7200] 45 | -------------------------------------------------------------------------------- /tests/test_opendss_to_ephasor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | test_opendss_to_ephasor 5 | ---------------------------------- 6 | 7 | Tests for OpenDSS --> Ephasor conversion 8 | """ 9 | import six 10 | 11 | if six.PY2: 12 | from backports import tempfile 13 | else: 14 | import tempfile 15 | 16 | import os 17 | import pytest as pt 18 | 19 | current_directory = os.path.realpath(os.path.dirname(__file__)) 20 | 21 | @pt.mark.skip("Segfault occurs") 22 | def test_opendss_to_ephasor(): 23 | ''' 24 | Test the OpenDSS to Ephasor conversion. 25 | ''' 26 | from ditto.readers.opendss.read import Reader 27 | from ditto.store import Store 28 | from ditto.writers.ephasor.write import Writer 29 | 30 | opendss_models=[f for f in os.listdir(os.path.join(current_directory, 'data/small_cases/opendss/')) if not f.startswith('.')] 31 | for model in opendss_models: 32 | m = Store() 33 | r = Reader( 34 | master_file=os.path.join(current_directory, 'data/small_cases/opendss/{model}/master.dss'.format(model=model)), 35 | buscoordinates_file=os.path.join(current_directory, 'data/small_cases/opendss/{model}/buscoord.dss'.format(model=model)) 36 | ) 37 | r.parse(m) 38 | m.set_names() 39 | m.build_networkx() 40 | m.direct_from_source() 41 | m.set_node_voltages() 42 | #TODO: Log properly 43 | print('>OpenDSS model {model} red...'.format(model=model)) 44 | output_path = tempfile.TemporaryDirectory() 45 | w = Writer(output_path=output_path) 46 | w.write(m) 47 | #TODO: Log properly 48 | print('>...and written to Ephasor.\n') 49 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/ieee_8500node/Transformers.dss: -------------------------------------------------------------------------------- 1 | 2 | ! Put Source Impedance in as a Reactor 3 | New Reactor.HVMV_Sub_HSB bus1=SourceBus bus2=HVMV_Sub_HSB r=0 x=(1.051 0.88 0.001 3 * - - 115 12.47 / sqr *) normamps=400 emergamps=400 4 | 5 | ! HV/MV Substation connected Delta/grounded-wye 6 | New Transformer.HVMV_Sub phases=3 windings=2 buses=(HVMV_Sub_HSB, regxfmr_HVMV_Sub_LSB.1.2.3.0) 7 | ~ conns=(delta wye) 8 | ~ kvs=(115, 12.47) kvas=(27500, 27500) 9 | ~ xhl=15.51 sub=y subname=HVMV_Sub 10 | ~ wdg=1 %r=0.67202 11 | ~ wdg=2 %r=0.67202 12 | 13 | ! Three single-phase voltage regulators on feeder 14 | ! Define transformer part as low-impedance 2-winding Y-Y transformer 15 | New Transformer.FEEDER_REGA phases=1 windings=2 Bank=FEEDER_REG buses=(regxfmr_HVMV_Sub_LSB.1, _HVMV_Sub_LSB.1) conns=(wye, wye) kvs=(7.2, 7.2) kvas=(27500, 27500) xhl=0.1 %loadloss=.001 wdg=2 Maxtap=1.1 Mintap=0.9 ppm=0 16 | New Transformer.FEEDER_REGB phases=1 windings=2 Bank=FEEDER_REG buses=(regxfmr_HVMV_Sub_LSB.2, _HVMV_Sub_LSB.2) conns=(wye, wye) kvs=(7.2, 7.2) kvas=(27500, 27500) xhl=0.1 %loadloss=.001 wdg=2 Maxtap=1.1 Mintap=0.9 ppm=0 17 | New Transformer.FEEDER_REGC phases=1 windings=2 Bank=FEEDER_REG buses=(regxfmr_HVMV_Sub_LSB.3, _HVMV_Sub_LSB.3) conns=(wye, wye) kvs=(7.2, 7.2) kvas=(27500, 27500) xhl=0.1 %loadloss=.001 wdg=2 Maxtap=1.1 Mintap=0.9 ppm=0 18 | 19 | ! Voltage regulator controls 20 | New RegControl.FEEDER_REGA transformer=FEEDER_REGA winding=2 vreg=126.5 ptratio=60 band=2 21 | New RegControl.FEEDER_REGB transformer=FEEDER_REGB winding=2 vreg=126.5 ptratio=60 band=2 22 | New RegControl.FEEDER_REGC transformer=FEEDER_REGC winding=2 vreg=126.5 ptratio=60 band=2 23 | -------------------------------------------------------------------------------- /tests/default_values/test_remove_opendss_default_values.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | test_remove_opendss_default_values.py 5 | ---------------------------------- 6 | 7 | Tests for checking the remove_opendss_default_values option. 8 | """ 9 | 10 | import os 11 | import pytest 12 | import numpy as np 13 | 14 | from ditto.store import Store 15 | from ditto.readers.opendss.read import Reader 16 | 17 | current_directory = os.path.realpath(os.path.dirname(__file__)) 18 | 19 | 20 | def test_remove_opendss_default_values(): 21 | m = Store() 22 | r = Reader( 23 | master_file=os.path.join(current_directory, "test_default_values.dss"), 24 | remove_opendss_default_values_flag=True, 25 | ) 26 | r.parse(m) 27 | m.set_names() 28 | 29 | assert m["line1"].faultrate == None 30 | assert m["line1"].impedance_matrix == None 31 | assert m["line1"].capacitance_matrix == None 32 | 33 | assert m["cap1"].connection_type == None 34 | assert m["cap1"].low == None 35 | assert m["cap1"].high == None 36 | assert m["cap1"].delay == None 37 | assert m["cap1"].pt_ratio == None 38 | assert m["cap1"].ct_ratio == None 39 | assert m["cap1"].pt_phase == None 40 | 41 | assert m["reg1"].reactances == None 42 | 43 | assert m["regulator_reg1"].ct_prim == None 44 | assert m["regulator_reg1"].delay == None 45 | assert m["regulator_reg1"].highstep == None 46 | assert m["regulator_reg1"].pt_ratio == None 47 | assert m["regulator_reg1"].bandwidth == None 48 | assert m["regulator_reg1"].bandcenter == None 49 | 50 | assert m["load_load1"].connection_type == None 51 | assert m["load_load1"].vmin == None 52 | assert m["load_load1"].vmax == None 53 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/ieee_8500node/Run_8500Node.dss: -------------------------------------------------------------------------------- 1 | ! REV 2 2 | !---------------------------------------------------------------------------------------------------------------------------- 3 | ! OpenDSS script to control the running of the IEEE 8500-Node Distrubution Test Feeder 4 | ! Balanced Load Case 5 | !---------------------------------------------------------------------------------------------------------------------------- 6 | 7 | ! To execute, select one or more line and right-click, select Do Selected 8 | ! 1. Select from Compile through Solve and execute 9 | ! 2. Select one or more of the statements to display results and execute 10 | 11 | !---------------------------------------------------------------------------------------------------------------------------- 12 | 13 | ! Edit the path name to indicate the correct location of the Master file. 14 | 15 | Compile (master.dss) 16 | 17 | New Energymeter.m1 Line.ln5815900-1 1 18 | 19 | Set Maxiterations=20 ! Sometimes the solution takes more than the default 15 iterations 20 | 21 | Solve 22 | 23 | Show Voltage LN Nodes 24 | Show Currents Elem Resid 25 | Show Powers kVA elem 26 | 27 | Set ShowExport=yes 28 | Export Currents 29 | Export Powers 30 | Export voltages 31 | 32 | // *********************** Plotting ***************************** 33 | 34 | Set markCapacitors=yes CapMarkersize=3 35 | Set markRegulators=yes RegMarkersize=5 36 | Interpolate 37 | Plot Circuit Power Max=5000 dots=n labels=n C1=Blue 1ph=3 ! $00FF0000 38 | 39 | Plot Circuit voltage Max=0 dots=n n C1=Blue C2=$FF00FF 1ph=3 40 | 41 | plot circuit Losses Max=50 dots=n labels=n subs=y C1=Blue 42 | 43 | plot profile ph=all 44 | plot profile ph=1 45 | 46 | summary 47 | 48 | show taps 49 | -------------------------------------------------------------------------------- /ditto/readers/synergi/utils.py: -------------------------------------------------------------------------------- 1 | # Downloading mdbtools 2 | import platform 3 | import os 4 | import tarfile 5 | from urllib.request import urlretrieve 6 | import sys 7 | 8 | current_dir = os.path.realpath(os.path.dirname(__file__)) 9 | tar_file_name = os.path.join(current_dir, "mdbtools.tar.gz") 10 | mdb_dir = os.path.join(current_dir, "mdbtools") 11 | 12 | if platform.system() == "Windows": 13 | URL = "https://github.com/kdheepak/mdbtools/releases/download/download/mdbtools-win.tar.gz" 14 | elif platform.system() == "Darwin": 15 | URL = "https://github.com/kdheepak/mdbtools/releases/download/download/mdbtools-osx.tar.gz" 16 | else: 17 | URL = "https://github.com/kdheepak/mdbtools/releases/download/download/mdbtools-linux.tar.gz" 18 | 19 | 20 | def download_mdbtools(url): 21 | urlretrieve(url, tar_file_name) 22 | with tarfile.open(tar_file_name) as tf: 23 | def is_within_directory(directory, target): 24 | 25 | abs_directory = os.path.abspath(directory) 26 | abs_target = os.path.abspath(target) 27 | 28 | prefix = os.path.commonprefix([abs_directory, abs_target]) 29 | 30 | return prefix == abs_directory 31 | 32 | def safe_extract(tar, path=".", members=None, *, numeric_owner=False): 33 | 34 | for member in tar.getmembers(): 35 | member_path = os.path.join(path, member.name) 36 | if not is_within_directory(path, member_path): 37 | raise Exception("Attempted Path Traversal in Tar File") 38 | 39 | tar.extractall(path, members, numeric_owner=numeric_owner) 40 | 41 | 42 | safe_extract(tf, mdb_dir) 43 | -------------------------------------------------------------------------------- /tests/writers/opendss/Lines/test_lines_write.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | test_lines_write.py 4 | ---------------------------------- 5 | 6 | Tests for writing Line objects from DiTTo to OpenDSS. 7 | """ 8 | 9 | import os 10 | import pytest 11 | import tempfile 12 | import json 13 | 14 | from ditto.store import Store 15 | from ditto.writers.opendss.write import Writer 16 | from ditto.readers.opendss.read import Reader 17 | from ditto.writers.json.write import Writer as Json_Writer 18 | 19 | current_directory = os.path.realpath(os.path.dirname(__file__)) 20 | 21 | 22 | # TODO: fix broken test 23 | # this test returns a different json when reading from opendss 24 | @pytest.mark.xfail 25 | def test_lines_write(): 26 | m = Store() 27 | 28 | r = Reader( 29 | master_file=os.path.join( 30 | current_directory, "../../../readers/opendss/Lines/test_linegeometries.dss" 31 | ) 32 | ) 33 | r.parse(m) 34 | m.set_names() 35 | 36 | output_path = tempfile.gettempdir() 37 | jw = Json_Writer(output_path=output_path) 38 | jw.write(m) 39 | 40 | w = Writer(output_path=output_path) 41 | w.write(m) 42 | 43 | # Check that the OpenDSS writer created a Master file 44 | assert os.path.exists(os.path.join(output_path, "Master.dss")) 45 | 46 | r_w = Reader(master_file=os.path.join(output_path, "Master.dss")) 47 | r_w.parse(m) 48 | m.set_names() 49 | 50 | jw = Json_Writer(output_path="./") 51 | jw.write(m) 52 | 53 | with open(os.path.join(output_path, "Model.json"), "r") as f1: 54 | reader_json = json.load(f1) 55 | with open("./Model.json", "r") as f2: 56 | writer_json = json.load(f2) 57 | 58 | assert reader_json["model"] == writer_json["model"] 59 | -------------------------------------------------------------------------------- /tests/test_cyme_to_opendss.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | test_cyme_to_opendss 5 | ---------------------------------- 6 | 7 | Tests for Cyme --> OpenDSS conversion 8 | """ 9 | import six 10 | 11 | if six.PY2: 12 | from backports import tempfile 13 | else: 14 | import tempfile 15 | import os 16 | import pytest as pt 17 | 18 | current_directory = os.path.realpath(os.path.dirname(__file__)) 19 | 20 | 21 | def test_cyme_to_opendss(): 22 | ''' 23 | Test the Cyme to OpenDSS conversion. 24 | ''' 25 | list_of_directories = [] 26 | from ditto.store import Store 27 | from ditto.readers.cyme.read import Reader 28 | from ditto.writers.opendss.write import Writer 29 | import opendssdirect as dss 30 | cyme_models=[f for f in os.listdir(os.path.join(current_directory, 'data/small_cases/cyme/')) if not f.startswith('.')] 31 | for model in cyme_models: 32 | print(model) 33 | m = Store() 34 | r = Reader(data_folder_path=os.path.join(current_directory, 'data/small_cases/cyme',model)) 35 | r.parse(m) 36 | #TODO: Log properly 37 | # print('>Cyme model {model} read...'.format(model=model)) 38 | output_path = tempfile.TemporaryDirectory() 39 | list_of_directories.append(output_path) 40 | w = Writer(output_path=output_path.name) 41 | w.write(m) 42 | #TODO: Log properly 43 | # print('>...and written to OpenDSS.\n') 44 | print(model) 45 | dss.run_command("clear") 46 | # dss.run_command('redirect {master}'.format(master=os.path.join(output_path,'master.dss'))) 47 | # dss.run_command('Solve') 48 | #TODO: Log properly 49 | # print('>Circuit {model} solved.\n'.format(model=model)) 50 | -------------------------------------------------------------------------------- /tests/readers/synergi/test_unit_converter.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ditto.readers.synergi.length_units import convert_length_unit, SynergiValueType 4 | 5 | 6 | @pytest.mark.parametrize( 7 | "value, value_type, length_unit, expected", 8 | [ 9 | (39.3701, SynergiValueType.SUL, "english2", pytest.approx(1.0)), 10 | (3.28084, SynergiValueType.MUL, "english2", pytest.approx(1.0)), 11 | (1.0, SynergiValueType.LUL, "english2", pytest.approx(1609.34)), 12 | (1609.34, SynergiValueType.Per_LUL, "english2", pytest.approx(1.0, abs=1e-5)), 13 | (39.3701, SynergiValueType.SUL, "english1", pytest.approx(1.0)), 14 | (3.28084, SynergiValueType.MUL, "english1", pytest.approx(1.0)), 15 | (1.0, SynergiValueType.LUL, "english1", pytest.approx(304.8)), 16 | (304.8, SynergiValueType.Per_LUL, "english1", pytest.approx(1.0)), 17 | (1000.0, SynergiValueType.SUL, "metric", 1.0), 18 | (2.0, SynergiValueType.MUL, "metric", 2.0), 19 | (1.0, SynergiValueType.LUL, "metric", 1000.0), 20 | (1000.0, SynergiValueType.Per_LUL, "metric", 1.0), 21 | ], 22 | ) 23 | def test_convert_units(value, value_type, length_unit, expected): 24 | actual = convert_length_unit(value, value_type, length_unit) 25 | assert actual == expected 26 | 27 | 28 | def test_raises_error_invalid_value_type(): 29 | with pytest.raises(ValueError): 30 | convert_length_unit(1.0, "a", "english2") 31 | 32 | 33 | def test_raises_error_invalid_unit_type(): 34 | with pytest.raises(ValueError): 35 | convert_length_unit(1.0, SynergiValueType.SUL, 1) 36 | 37 | 38 | def test_raises_error_invalid_unit_value(): 39 | with pytest.raises(ValueError): 40 | convert_length_unit(1.0, SynergiValueType.SUL, "english3") 41 | -------------------------------------------------------------------------------- /tests/persistence/test_persistence.py: -------------------------------------------------------------------------------- 1 | import six 2 | if six.PY2: 3 | from backports import tempfile 4 | else: 5 | import tempfile 6 | 7 | import pytest as pt 8 | import os 9 | from ditto.readers.opendss.read import Reader as Reader_opendss 10 | from ditto.readers.cyme.read import Reader as Reader_cyme 11 | from ditto.writers.json.write import Writer 12 | from ditto.store import Store 13 | import logging 14 | import json_tricks 15 | 16 | logger = logging.getLogger(__name__) 17 | test_list = os.walk('data') 18 | for (dirpath, dirname, files) in test_list: 19 | if files !=[]: 20 | reader_type = dirpath.split('\\')[2] 21 | m = Store() 22 | if reader_type == 'opendss': 23 | reader = Reader_opendss(master_file = os.path.join('..',dirpath,'master.dss'), buscoordinates_file = os.path.join('..',dirpath,'buscoord.dss')) 24 | elif reader_type == 'cyme': 25 | reader = Reader_cyme(data_folder_path=os.path.join('..',dirpath)) 26 | else: 27 | #Update with other tests if they get added to the persistence tests 28 | continue 29 | reader.parse(m) 30 | m.set_names() 31 | output_path = tempfile.TemporaryDirectory() 32 | w = Writer(output_path=output_path.name, log_path=output_path) 33 | w.write(m) 34 | original = json_tricks.load(open(os.path.join(dirpath,files[0]),'r')) 35 | update = json_tricks.load(open(os.path.join(output_path.name,'Model.json'),'r')) 36 | try: 37 | assert update["model"] == original["model"] 38 | except AssertionError as e: 39 | logger.error("Model differs for usecase {loc}".format(loc = dirpath)) 40 | e.args += ("Model differs for usecase {loc}".format(loc = dirpath),) 41 | raise 42 | 43 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | This folders has some conversion examples of larger test systems. 4 | 5 | ### Example 1: IEEE 8500 6 | 7 | #### OpenDSS ---> Cyme 8 | 9 | To run this example, open a terminal and run: 10 | 11 | ```bash 12 | $ python ieee_8500_opendss_to_cyme.py 13 | ``` 14 | 15 | Alternatively, you can use the CLI and the configuration file ```ieee_8500_opendss_to_cyme.json```. Open a terminal and run: 16 | 17 | ```bash 18 | $ ditto-cli convert --from dss --input ieee_8500_to_cyme.json --to cyme --output . 19 | ``` 20 | 21 | 22 | ### Example 2: EPRI J1 Feeder 23 | 24 | #### OpenDSS —> Cyme 25 | 26 | To run this example, open a terminal and run: 27 | 28 | ```bash 29 | $ python epri_j1_opendss_to_cyme.py 30 | ``` 31 | 32 | Alternatively, you can use the CLI and the configuration file ```epri_j1_opendss_to_cyme.json```. Open a terminal and run: 33 | 34 | ```bash 35 | $ ditto-cli convert --from dss --input epri_j1_opendss_to_cyme.json --to cyme --output . 36 | ``` 37 | 38 | ### Example 3: IEEE 123 39 | 40 | #### Gridlab-D —> OpenDSS 41 | 42 | To run this example, open a terminal and run: 43 | 44 | ```bash 45 | $ python ieee_123node_gridlabd_to_opendss.py 46 | ``` 47 | 48 | Alternatively, you can use the CLI: 49 | 50 | ```bash 51 | $ ditto-cli convert --from glm --input ../tests/data/big_cases/gridlabd/ieee_123node/123_node.glm --to dss --output . 52 | ``` 53 | 54 | #### CYME —> OpenDSS 55 | 56 | To run this example, open a terminal and run: 57 | 58 | ```bash 59 | $ python ieee_123node_cyme_to_opendss.py 60 | ``` 61 | 62 | Alternatively, you can use the CLI and the configuration file ```ieee_123node_cyme_to_opendss.json```. Open a terminal and run: 63 | 64 | ```bash 65 | $ ditto-cli convert --from cyme --input ieee_123node_cyme_to_opendss.json --to dss --output . 66 | ``` 67 | 68 | -------------------------------------------------------------------------------- /ditto/consistency/check_loops.py: -------------------------------------------------------------------------------- 1 | import networkx as nx 2 | from ditto.network.network import Network 3 | from ditto.models.power_source import PowerSource 4 | from ditto.models.load import Load 5 | 6 | """ 7 | This function sets all the switch states in networkx model and then sees if there are any loops in the resulting network 8 | 9 | Parameters: 10 | model: ditto.store.Store 11 | The DiTTo storage object with the full network representation 12 | verbose: boolean 13 | Whether to print information about which nodes caused problems 14 | """ 15 | 16 | def check_loops(model, verbose=True): 17 | all_sources = [] 18 | for i in model.models: 19 | if isinstance(i,PowerSource) and i.connecting_element is not None: 20 | all_sources.append(i) 21 | elif isinstance(i,PowerSource): 22 | print('Warning - a PowerSource element has a None connecting element') 23 | 24 | # TODO: Address issue in network.py where a single source is used to determine bfs order 25 | if len(all_sources) == 0: 26 | raise('Model does not contain any power source. Required to build networkx graph') 27 | 28 | for source in all_sources: 29 | print('Checking loops for source '+source.name) 30 | ditto_graph = Network() 31 | ditto_graph.build(model,source.connecting_element) 32 | ditto_graph.set_attributes(model) 33 | ditto_graph.remove_open_switches(model) # This deletes the switches inside the networkx graph only 34 | 35 | loops = nx.cycle_basis(ditto_graph.graph) 36 | number_of_loops = len(loops) 37 | if number_of_loops > 0: 38 | if verbose: 39 | print('Loops found:') 40 | print(loops) 41 | if number_of_loops == 0: 42 | return True 43 | return False 44 | 45 | 46 | -------------------------------------------------------------------------------- /examples/ieee_123node_cyme_to_opendss.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import time 5 | 6 | #Import the CYME reader 7 | from ditto.readers.cyme.read import Reader 8 | 9 | #Import the OpenDSS writer 10 | from ditto.writers.opendss.write import Writer 11 | 12 | #Import Store 13 | from ditto.store import Store 14 | 15 | def main(): 16 | ''' 17 | Conversion example of the IEEE 123 node system. 18 | CYME ---> OpenDSS example. 19 | This example uses the glm file located in tests/data/big_cases/cyme/ieee_123node 20 | ''' 21 | 22 | #Path settings (assuming it is run from examples folder) 23 | #Change this if you wish to use another system 24 | path = '../tests/data/big_cases/cyme/ieee_123node' 25 | 26 | ############################ 27 | # STEP 1: READ FROM CYME # 28 | ############################ 29 | # 30 | #Create a Store object 31 | print('>>> Creating empty model...') 32 | model = Store() 33 | 34 | #Instanciate a Reader object 35 | r = Reader(data_folder_path = path) 36 | 37 | #Parse (can take some time for large systems...) 38 | print('>>> Reading from CYME...') 39 | start = time.time() #basic timer 40 | r.parse(model) 41 | end = time.time() 42 | print('...Done (in {} seconds'.format(end-start)) 43 | 44 | ############################## 45 | # STEP 2: WRITE TO OpenDSS # 46 | ############################## 47 | # 48 | #Instanciate a Writer object 49 | w = Writer(output_path = './') 50 | 51 | #Write to OpenDSS (can also take some time for large systems...) 52 | print('>>> Writing to OpenDSS...') 53 | start = time.time() #basic timer 54 | w.write(model) 55 | end = time.time() 56 | print('...Done (in {} seconds'.format(end-start)) 57 | 58 | if __name__ == '__main__': 59 | main() 60 | 61 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/ieee_8500node/Run_8500Node_Unbal.dss: -------------------------------------------------------------------------------- 1 | ! REV 2 2 | !---------------------------------------------------------------------------------------------------------------------------- 3 | ! OpenDSS script to control the running of the IEEE 4800-bus Distrubution Test Feeder 4 | ! Unbalanced Load Case 5 | !---------------------------------------------------------------------------------------------------------------------------- 6 | 7 | ! To execute, select one or more line and right-click, select Do Selected 8 | ! 1. Select from Compile through Solve and execute 9 | ! 2. Select one or more of the statements to display results and execute 10 | 11 | !---------------------------------------------------------------------------------------------------------------------------- 12 | 13 | ! Edit the path name to indicate the correct location of the Master file. 14 | 15 | Compile (Master-unbal.dss) ! unbalanced load master 16 | 17 | New Energymeter.m1 Line.ln5815900-1 1 18 | New Monitor.m1 Line.ln5815900-1 1 19 | 20 | Set Maxiterations=20 ! Sometimes the solution takes more than the default 15 iterations 21 | 22 | Solve 23 | 24 | Show Voltage LN Nodes 25 | Show Currents Elem Resid 26 | Show Powers kVA elem 27 | 28 | Set ShowExport=yes 29 | Export Currents 30 | Export Powers 31 | Export voltages 32 | 33 | ! Plot the circuit with blue lines and with 1-phase lines dashed 34 | interpolate 35 | Plot Circuit Power Max=2000 dots=n labels=n C1=Blue 1ph=3 36 | 37 | ! Plot the voltage profile and include all phases 38 | Plot profile phases=all 39 | 40 | Export profile phases=all 41 | 42 | ! Plot the voltage profile and include only phase "1" 43 | Plot profile phases=1 44 | 45 | Plot Circuit voltage Max=0 n n C1=$00FF0000 C2=$FF00FF 46 | 47 | plot circuit Losses Max=20 dots=n labels=n subs=y C1=$00FF0000 48 | 49 | summary 50 | 51 | show taps 52 | help 53 | -------------------------------------------------------------------------------- /examples/ieee_123node_gridlabd_to_opendss.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import time 5 | 6 | #Import the Gridlab-D reader 7 | from ditto.readers.gridlabd.read import Reader 8 | 9 | #Import the OpenDSS writer 10 | from ditto.writers.opendss.write import Writer 11 | 12 | #Import Store 13 | from ditto.store import Store 14 | 15 | def main(): 16 | ''' 17 | Conversion example of the IEEE 123 node system. 18 | Gridlab-D ---> OpenDSS example. 19 | This example uses the glm file located in tests/data/big_cases/gridlabd/ieee_123node 20 | ''' 21 | 22 | #Path settings (assuming it is run from examples folder) 23 | #Change this if you wish to use another system 24 | path = '../tests/data/big_cases/gridlabd/ieee_123node' 25 | 26 | ################################# 27 | # STEP 1: READ FROM GRIDLAB-D # 28 | ################################# 29 | # 30 | #Create a Store object 31 | print('>>> Creating empty model...') 32 | model = Store() 33 | 34 | #Instanciate a Reader object 35 | r = Reader(input_file = os.path.join(path, '123_node.glm')) 36 | 37 | #Parse (can take some time for large systems...) 38 | print('>>> Reading from Gridlab-D...') 39 | start = time.time() #basic timer 40 | r.parse(model) 41 | end = time.time() 42 | print('...Done (in {} seconds'.format(end-start)) 43 | 44 | ############################## 45 | # STEP 2: WRITE TO OpenDSS # 46 | ############################## 47 | # 48 | #Instanciate a Writer object 49 | w = Writer(output_path = './') 50 | 51 | #Write to OpenDSS (can also take some time for large systems...) 52 | print('>>> Writing to OpenDSS...') 53 | start = time.time() #basic timer 54 | w.write(model) 55 | end = time.time() 56 | print('...Done (in {} seconds'.format(end-start)) 57 | 58 | if __name__ == '__main__': 59 | main() 60 | 61 | -------------------------------------------------------------------------------- /ditto/readers/synergi/db_parser.py: -------------------------------------------------------------------------------- 1 | # Remove this import since it works only for Windows... 2 | # import win32com.client 3 | import pandas as pd 4 | import os 5 | 6 | from . import pandas_access as mdb 7 | 8 | 9 | class DbParser: 10 | """ 11 | Class implementing the reading of the MDB tables used by Synergi. 12 | """ 13 | 14 | def __init__(self, input_file, **kwargs): 15 | """ 16 | Class constructor. 17 | """ 18 | self.SynergiDictionary = {} 19 | self.paths = {} 20 | self.paths["Synergi File"] = input_file 21 | 22 | if "warehouse" in kwargs: 23 | self.paths["warehouse"] = kwargs["warehouse"] 24 | 25 | self.ParseSynergiDatabase() 26 | 27 | def ParseSynergiDatabase(self): 28 | """ 29 | Use Pandas Access to convert the MDB tables to Pandas DataFrames. 30 | """ 31 | print("Opening synergie database - ", self.paths["Synergi File"]) 32 | table_list = mdb.list_tables(self.paths["Synergi File"]) 33 | 34 | table_list_warehouse = [] 35 | if "warehouse" in self.paths: 36 | print("Opening warehouse database - ", self.paths["warehouse"]) 37 | table_list_warehouse = mdb.list_tables(self.paths["warehouse"]) 38 | 39 | for table in table_list: 40 | self.SynergiDictionary[table] = self.ToLowerCase( 41 | mdb.read_table(self.paths["Synergi File"], table) 42 | ) 43 | 44 | for table in table_list_warehouse: 45 | self.SynergiDictionary[table] = self.ToLowerCase( 46 | mdb.read_table(self.paths["warehouse"], table) 47 | ) 48 | return 49 | 50 | def ToLowerCase(self, df): 51 | """ 52 | This function converts all the input data to lower case. 53 | """ 54 | df = df.apply(lambda x: x.str.lower() if x.dtype == "object" else x) 55 | return df 56 | -------------------------------------------------------------------------------- /tests/readers/opendss/Nodes/test_nodes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | test_nodes.py 5 | ---------------------------------- 6 | 7 | Tests for checking all the attributes of nodes. 8 | """ 9 | import logging 10 | import os 11 | 12 | import six 13 | 14 | import tempfile 15 | import pytest as pt 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | current_directory = os.path.realpath(os.path.dirname(__file__)) 20 | 21 | 22 | def test_nodes(): 23 | from ditto.store import Store 24 | from ditto.readers.opendss.read import Reader 25 | 26 | # test on the test_nodes.dss 27 | m = Store() 28 | r = Reader( 29 | master_file=os.path.join(current_directory, "test_nodes.dss"), 30 | buscoordinates_file=os.path.join(current_directory, "buscoord.dss"), 31 | ) 32 | r.parse(m) 33 | m.set_names() 34 | 35 | assert (m["bus1"].name) == "bus1" 36 | assert (m["bus1"].nominal_voltage) == float(12.47) * 10 ** 3 37 | assert (m["bus1"].positions[0].long) == float(300) 38 | assert (m["bus1"].positions[0].lat) == float(400) 39 | assert (m["bus1"].positions[0].elevation) == 0 40 | assert (m["bus1"].feeder_name) == "sourcebus_src" 41 | 42 | assert (m["sourcebus"].name) == "sourcebus" 43 | assert (m["sourcebus"].nominal_voltage) == float(12.47) * 10 ** 3 44 | assert (m["sourcebus"].positions[0].long) == float(1674346.56814483) 45 | assert (m["sourcebus"].positions[0].lat) == float(12272927.0644858) 46 | assert (m["sourcebus"].positions[0].elevation) == 0 47 | assert (m["sourcebus"].feeder_name) == "sourcebus_src" 48 | 49 | assert (m["b1"].name) == "b1" 50 | assert (m["b1"].nominal_voltage) == float(12.47) * 10 ** 3 51 | assert (m["b1"].positions[0].long) == float(1578139) 52 | assert (m["b1"].positions[0].lat) == float(14291312) 53 | assert (m["b1"].positions[0].elevation) == 0 54 | assert (m["b1"].feeder_name) == "sourcebus_src" 55 | -------------------------------------------------------------------------------- /tests/readers/opendss/Powersource/test_powersource.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | test_powersource.py 5 | ---------------------------------- 6 | 7 | Tests for parsing all values of power source from OpenDSS into DiTTo. 8 | """ 9 | 10 | import os 11 | import math 12 | import pytest 13 | import numpy as np 14 | from numpy import testing as npt 15 | 16 | from ditto.store import Store 17 | from ditto.readers.opendss.read import Reader 18 | 19 | current_directory = os.path.realpath(os.path.dirname(__file__)) 20 | 21 | 22 | def test_powersource(): 23 | m = Store() 24 | r = Reader( 25 | master_file=os.path.join(current_directory, "test_powersource.dss"), 26 | buscoordinates_file=os.path.join(current_directory, "buscoord.dss"), 27 | ) 28 | r.parse(m) 29 | m.set_names() 30 | 31 | assert m["Vsource.source"].name == "Vsource.source" 32 | assert m["Vsource.source"].nominal_voltage == 230.0 * 10 ** 3 33 | assert m["Vsource.source"].per_unit == 0.99 34 | assert m["Vsource.source"].is_sourcebus == 1 35 | assert m["Vsource.source"].rated_power == 150000000.0 36 | # MVASc3 = baseKVA^2 / Z1 ; Z1 = sqrt( r1^2 + x1^2) 37 | emerg_power = int((230.0) ** 2 / math.sqrt(1.1208 ** 2 + 3.5169 ** 2)) * 10 ** 6 38 | npt.assert_almost_equal(m["Vsource.source"].emergency_power/10e9, emerg_power/10e9, decimal=4) 39 | 40 | assert m["Vsource.source"].zero_sequence_impedance == 1.1208 + 3.5169j 41 | assert m["Vsource.source"].positive_sequence_impedance == 1.1208 + 3.5169j 42 | assert m["Vsource.source"].connecting_element == "sourcebus" 43 | assert m["Vsource.source"].phases[0].default_value == "A" 44 | assert m["Vsource.source"].phases[1].default_value == "B" 45 | assert m["Vsource.source"].phases[2].default_value == "C" 46 | assert (m["Vsource.source"].positions[0].long) == float(200) 47 | assert (m["Vsource.source"].positions[0].lat) == float(400) 48 | -------------------------------------------------------------------------------- /examples/epri_j1_opendss_to_cyme.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import time 5 | 6 | #Import the OpenDSS reader 7 | from ditto.readers.opendss.read import Reader 8 | 9 | #Import the Cyme writer 10 | from ditto.writers.cyme.write import Writer 11 | 12 | #Import Store 13 | from ditto.store import Store 14 | 15 | def main(): 16 | ''' 17 | Conversion example of a relatively large test system: the EPRI J1 feeder. 18 | OpenDSS ---> CYME example. 19 | This example uses the dss files located in tests/data/big_cases/opendss/epri_j1 20 | ''' 21 | 22 | #Path settings (assuming it is run from examples folder) 23 | #Change this if you wish to use another system 24 | path = '../tests/data/big_cases/opendss/epri_j1' 25 | 26 | ############################### 27 | # STEP 1: READ FROM OPENDSS # 28 | ############################### 29 | # 30 | #Create a Store object 31 | print('>>> Creating empty model...') 32 | model = Store() 33 | 34 | #Instanciate a Reader object 35 | r = Reader(master_file = os.path.join(path, 'master.dss'), 36 | buscoordinates_file = os.path.join(path, 'buscoords.dss')) 37 | 38 | #Parse (can take some time for large systems...) 39 | print('>>> Reading from OpenDSS...') 40 | start = time.time() #basic timer 41 | r.parse(model) 42 | end = time.time() 43 | print('...Done (in {} seconds'.format(end-start)) 44 | 45 | ########################### 46 | # STEP 2: WRITE TO CYME # 47 | ########################### 48 | # 49 | #Instanciate a Writer object 50 | w = Writer(output_path = './') 51 | 52 | #Write to CYME (can also take some time for large systems...) 53 | print('>>> Writing to CYME...') 54 | start = time.time() #basic timer 55 | w.write(model) 56 | end = time.time() 57 | print('...Done (in {} seconds'.format(end-start)) 58 | 59 | if __name__ == '__main__': 60 | main() 61 | 62 | -------------------------------------------------------------------------------- /examples/ieee_8500_opendss_to_cyme.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import time 5 | 6 | #Import the OpenDSS reader 7 | from ditto.readers.opendss.read import Reader 8 | 9 | #Import the Cyme writer 10 | from ditto.writers.cyme.write import Writer 11 | 12 | #Import Store 13 | from ditto.store import Store 14 | 15 | def main(): 16 | ''' 17 | Conversion example of a relatively large test system: the IEEE 8500 node. 18 | OpenDSS ---> CYME example. 19 | This example uses the dss files located in tests/data/big_cases/opendss/ieee_8500node 20 | ''' 21 | 22 | #Path settings (assuming it is run from examples folder) 23 | #Change this if you wish to use another system 24 | path = '../tests/data/big_cases/opendss/ieee_8500node' 25 | 26 | ############################### 27 | # STEP 1: READ FROM OPENDSS # 28 | ############################### 29 | # 30 | #Create a Store object 31 | print('>>> Creating empty model...') 32 | model = Store() 33 | 34 | #Instanciate a Reader object 35 | r = Reader(master_file = os.path.join(path, 'Master-unbal.dss'), 36 | buscoordinates_file = os.path.join(path, 'buscoord.dss')) 37 | 38 | #Parse (can take some time for large systems...) 39 | print('>>> Reading from OpenDSS...') 40 | start = time.time() #basic timer 41 | r.parse(model) 42 | end = time.time() 43 | print('...Done (in {} seconds'.format(end-start)) 44 | 45 | ########################### 46 | # STEP 2: WRITE TO CYME # 47 | ########################### 48 | # 49 | #Instanciate a Writer object 50 | w = Writer(output_path = './') 51 | 52 | #Write to CYME (can also take some time for large systems...) 53 | print('>>> Writing to CYME...') 54 | start = time.time() #basic timer 55 | w.write(model) 56 | end = time.time() 57 | print('...Done (in {} seconds'.format(end-start)) 58 | 59 | if __name__ == '__main__': 60 | main() 61 | 62 | -------------------------------------------------------------------------------- /tests/data/small_cases/opendss/ieee_4node/master.dss: -------------------------------------------------------------------------------- 1 | clear 2 | 3 | ! IEEE 4-bus test case Y-Y Stepdown Balanced 4 | ! Based on script developed by Alan Dunn and Steve Sparling 5 | 6 | new circuit.4BusYYbal basekV=12.47 phases=3 7 | ! **** HAVE TO STIFFEN THE SOURCE UP A LITTLE; THE TEST CASE ASSUMES AN INFINITE BUS 8 | ~ mvasc3=200000 200000 9 | 10 | set earthmodel=carson 11 | 12 | ! **** DEFINE WIRE DATA 13 | new wiredata.conductor Runits=mi Rac=0.306 GMRunits=ft GMRac=0.0244 Radunits=in Diam=0.721 14 | new wiredata.neutral Runits=mi Rac=0.592 GMRunits=ft GMRac=0.00814 Radunits=in Diam=0.563 15 | 16 | ! **** DEFINE LINE GEOMETRY; REDUCE OUT THE NEUTRAL 17 | new linegeometry.4wire nconds=4 nphases=3 reduce=yes 18 | ~ cond=1 wire=conductor units=ft x=-4 h=28 19 | ~ cond=2 wire=conductor units=ft x=-1.5 h=28 20 | ~ cond=3 wire=conductor units=ft x=3 h=28 21 | ~ cond=4 wire=neutral units=ft x=0 h=24 22 | 23 | ! **** 12.47 KV LINE 24 | new line.line1 geometry=4wire length=2000 units=ft bus1=sourcebus bus2=n2 25 | 26 | ! **** 3-PHASE STEP-DOWN TRANSFORMER 12.47/4.16 KV Y-Y 27 | new transformer.t1 xhl=6 28 | ~ wdg=1 bus=n2 conn=wye kV=12.47 kVA=6000 %r=0.5 29 | ~ wdg=2 bus=n3 conn=wye kV=4.16 kVA=6000 %r=0.5 30 | 31 | ! **** 4.16 KV LINE 32 | new line.line2 bus1=n3 bus2=n4 geometry=4wire length=2500 units=ft 33 | 34 | ! **** WYE-CONNECTED 4.16 KV LOAD 35 | new load.load1 phases=3 bus1=n4 conn=wye kV=4.16 kW=5400 pf=0.9 model=1 36 | ! **** HAVE TO ALLOW P, Q TO REMAIN CONSTANT TO ABOUT .79 PU -- THIS IS ASSUMED IN TEST CASE 37 | ! **** DEFAULT IN DSS IS .95, BELOW WHICH IT REVERTS TO LINEAR MODEL 38 | ~ vminpu=0.75 ! model will remain const p,q down to 0.75 pu voltage 39 | 40 | set voltagebases=[12.47, 4.16] 41 | calcvoltagebases ! **** let DSS compute voltage bases 42 | solve 43 | 44 | ! Various reports ... 45 | //show voltages LN Nodes 46 | //show currents resid=yes elements ! this shows sum of phase currents 47 | //Show Powers kva Elements 48 | -------------------------------------------------------------------------------- /tests/readers/cyme/test_load_value_mapping.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from ditto.readers.cyme.read import Reader 4 | 5 | 6 | @pytest.mark.parametrize('load_type, v1, v2, expected', [ 7 | # '0', 0, or 'kw_kvar' modes just return the values 8 | ('0', 1, 2, (1, 2)), 9 | (0, 1, 2, (1, 2)), 10 | ('kw_kvar', 1, 2, (1, 2)), 11 | 12 | # '1', 1, or 'kva_pf' modes. v1 is kva, v2 is pf 13 | ('1', 100, 0.9, (90, pytest.approx(43.58898))), 14 | (1, 100, 0.9, (90, pytest.approx(43.58898))), 15 | ('kva_pf', 100, 0.9, (90, pytest.approx(43.58898))), 16 | 17 | # Edge conditions. pf 1.0 and 0.0 18 | (1, 100, 1.0, (100, 0.0)), 19 | (1, 100, 0.0, (0.0, 100.0)), 20 | 21 | # '2', 2, or 'kw_pf' modes. v1 is kw, v2 is pf 22 | ('2', 90, 0.9, (90, pytest.approx(43.58898))), 23 | (2, 90, 0.9, (90, pytest.approx(43.58898))), 24 | ('kw_pf', 90, 0.9, (90, pytest.approx(43.58898))), 25 | 26 | # Edge condition, pf of 1.0 27 | ('2', 90, 1.0, (90, 0.0)), 28 | ]) 29 | def test_load_value_mapping(load_type, v1, v2, expected): 30 | reader = Reader() 31 | actual = reader.load_value_type_mapping( 32 | load_type, v1, v2 33 | ) 34 | assert actual == expected 35 | 36 | 37 | def test_load_value_mapping_invalid_type(): 38 | reader = Reader() 39 | 40 | with pytest.raises(ValueError): 41 | reader.load_value_type_mapping(0.0, 100, 10) 42 | 43 | 44 | @pytest.mark.parametrize('v1, v2', [ 45 | ('a', 10), 46 | (10, 'b'), 47 | ]) 48 | def test_load_value_mapping_invalid_value(v1, v2): 49 | reader = Reader() 50 | 51 | with pytest.raises(ValueError): 52 | reader.load_value_type_mapping(0, v1, v2) 53 | 54 | 55 | @pytest.mark.parametrize('load_type', [ 56 | 3, 57 | '3', 58 | 'amp_pf', 59 | ]) 60 | def test_load_value_mapping_amp_pf_not_impl(load_type): 61 | reader = Reader() 62 | 63 | with pytest.raises(NotImplementedError): 64 | reader.load_value_type_mapping(load_type, 100, 0.9) 65 | -------------------------------------------------------------------------------- /ditto/core.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function, unicode_literals 3 | from builtins import super, range, zip, round, map 4 | 5 | import types 6 | 7 | 8 | class DiTToBase(object): 9 | """Base class for all objects in the core""" 10 | 11 | def __init__(self, *args, **kwargs): 12 | 13 | self._register_callbacks( 14 | kwargs.pop("init_callback", None), 15 | kwargs.pop("get_callback", None), 16 | kwargs.pop("set_callback", None), 17 | kwargs.pop("del_callback", None), 18 | ) 19 | 20 | for k, v in kwargs.items(): 21 | if self._is_property(k): 22 | setattr(self, k, v) 23 | else: 24 | raise AttributeError( 25 | 'Unable to set "{}" on class {}.'.format(k, self.__class__.__name__) 26 | ) 27 | 28 | try: 29 | self._callback_init(**kwargs) 30 | except AttributeError: 31 | pass 32 | 33 | def _is_property(self, name): 34 | """Returns True if the object has a property with the specified name.""" 35 | return isinstance(getattr(self.__class__, name, None), PropertyType) 36 | 37 | def iter_properties(self): 38 | for p in dir(self.__class__): 39 | if self._is_property(p): 40 | yield p, getattr(self, p) 41 | 42 | def _register_callbacks(self, c_init=None, c_get=None, c_set=None, c_del=None): 43 | 44 | if c_init is not None: 45 | self._callback_init = types.MethodType(c_init, self, self.__class__) 46 | if c_get is not None: 47 | self._callback_get = types.MethodType(c_get, self, self.__class__) 48 | if c_set is not None: 49 | self._callback_set = types.MethodType(c_set, self, self.__class__) 50 | if c_del is not None: 51 | self._callback_del = types.MethodType(c_del, self, self.__class__) 52 | 53 | 54 | class DiTToTypeError(TypeError): 55 | pass 56 | 57 | 58 | class PropertyType(object): 59 | pass 60 | -------------------------------------------------------------------------------- /ditto/default_values/opendss_default_values.json: -------------------------------------------------------------------------------- 1 | { 2 | "Line":{ 3 | "faultrate":0.1, 4 | "R1":0.0580, 5 | "X1":0.1206, 6 | "R0":0.1784, 7 | "X0":0.4047, 8 | "C1":3.4, 9 | "C0":1.6, 10 | "length":1, 11 | "rmatrix":[ 12 | [ 13 | 0.09813333, 14 | 0.04013333, 15 | 0.04013333 16 | ], 17 | [ 18 | 0.04013333, 19 | 0.09813333, 20 | 0.04013333 21 | ], 22 | [ 23 | 0.04013333, 24 | 0.04013333, 25 | 0.09813333 26 | ] 27 | ], 28 | "xmatrix":[ 29 | [ 30 | 0.2153, 31 | 0.0947, 32 | 0.0947 33 | ], 34 | [ 35 | 0.0947, 36 | 0.2153, 37 | 0.0947 38 | ], 39 | [ 40 | 0.0947, 41 | 0.0947, 42 | 0.2153 43 | ] 44 | ], 45 | "cmatrix":[ 46 | [ 47 | 2.8, 48 | -0.6, 49 | -0.6 50 | ], 51 | [ 52 | -0.6, 53 | 2.8, 54 | -0.6 55 | ], 56 | [ 57 | -0.6, 58 | -0.6, 59 | 2.8 60 | ] 61 | ], 62 | "is_switch":"False" 63 | }, 64 | "Wire":{ 65 | "ampacity":400.0, 66 | "emergency_ampacity":600.0, 67 | "insulation_thickness":0 68 | }, 69 | "Capacitor":{ 70 | "connection_type":"Y", 71 | "low":115, 72 | "high":126, 73 | "delay":15, 74 | "pt_ratio":60, 75 | "ct_ratio":60, 76 | "pt_phase":"A" 77 | }, 78 | "Transformer":{ 79 | "connection_type":"Y", 80 | "reactances":[ 81 | 7 82 | ] 83 | }, 84 | "Regulator":{ 85 | "ct_prim":300, 86 | "delay":15, 87 | "highstep":16, 88 | "pt_ratio":60, 89 | "bandwidth":3, 90 | "bandcenter":120 91 | }, 92 | "Load":{ 93 | "connection_type":"Y", 94 | "vmin":0.95, 95 | "vmax":1.05 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/ieee_8500node/CapControls.DSS: -------------------------------------------------------------------------------- 1 | ! Capacitor Control Definitions 2 | ! these controls monitor one phase of the specified line and control one phase of the associated capacitor bank 3 | ! Each control monitors a single phase and controls a single phase capacitor (see capacitor definitions) 4 | 5 | New CapControl.CAPBank2A_Ctrl Capacitor=CAPBank2A element=line.CAP_1A terminal=1 type=kvar ptratio=1 ctratio=1 ONsetting=150 OFFsetting=-225 VoltOverride=Y Vmin=7110 Vmax=7740 Delay=100 Delayoff=100 6 | New CapControl.CAPBank2B_Ctrl Capacitor=CAPBank2B element=line.CAP_1B terminal=1 type=kvar ptratio=1 ctratio=1 ONsetting=150 OFFsetting=-225 VoltOverride=Y Vmin=7110 Vmax=7740 Delay=101 Delayoff=101 7 | New CapControl.CAPBank2C_Ctrl Capacitor=CAPBank2C element=line.CAP_1C terminal=1 type=kvar ptratio=1 ctratio=1 ONsetting=150 OFFsetting=-225 VoltOverride=Y Vmin=7110 Vmax=7740 Delay=102 Delayoff=102 8 | 9 | 10 | New CapControl.CAPBank1A_Ctrl Capacitor=CAPBank1A element=line.CAP_2A terminal=1 type=kvar ptratio=1 ctratio=1 ONsetting=150 OFFsetting=-225 VoltOverride=Y Vmin=7110 Vmax=7740 Delay=100 Delayoff=90 11 | New CapControl.CAPBank1B_Ctrl Capacitor=CAPBank1B element=line.CAP_2B terminal=1 type=kvar ptratio=1 ctratio=1 ONsetting=150 OFFsetting=-225 VoltOverride=Y Vmin=7110 Vmax=7740 Delay=101 Delayoff=91 12 | New CapControl.CAPBank1C_Ctrl Capacitor=CAPBank1C element=line.CAP_2C terminal=1 type=kvar ptratio=1 ctratio=1 ONsetting=150 OFFsetting=-225 VoltOverride=Y Vmin=7110 Vmax=7740 Delay=102 Delayoff=92 13 | 14 | 15 | New CapControl.CAPBank0A_Ctrl Capacitor=CAPBank0A element=line.CAP_3A terminal=1 type=kvar ptratio=1 ctratio=1 ONsetting=200 OFFsetting=-300 VoltOverride=Y Vmin=7110 Vmax=7740 Delay=100 Delayoff=80 16 | New CapControl.CAPBank0B_Ctrl Capacitor=CAPBank0B element=line.CAP_3B terminal=1 type=kvar ptratio=1 ctratio=1 ONsetting=200 OFFsetting=-300 VoltOverride=Y Vmin=7110 Vmax=7740 Delay=101 Delayoff=81 17 | New CapControl.CAPBank0C_Ctrl Capacitor=CAPBank0C element=line.CAP_3C terminal=1 type=kvar ptratio=1 ctratio=1 ONsetting=200 OFFsetting=-300 VoltOverride=Y Vmin=7110 Vmax=7740 Delay=102 Delayoff=82 18 | -------------------------------------------------------------------------------- /tests/readers/opendss/Attributes.json: -------------------------------------------------------------------------------- 1 | { 2 | "OpenDSS": { 3 | "Line": ["name","nominal_voltage","line_type","length","from_element","to_element","is_fuse","is_switch","faultrate","wires","impedance_matrix", 4 | "capacitance_matrix","feeder_name","is_recloser","is_breaker","nameclass"], 5 | "Wire": ["phase","nameclass","X","Y","diameter","gmr","ampacity","emergency_ampacity","resistance","insulation_thickness","is_open","concentric_neutral_gmr","concentric_neutral_resistance","concentric_neutral_diameter","concentric_outside_diameter","concentric_neutral_nstrand"], 6 | "Capacitor": ["name","nominal_voltage","connection_type","delay","mode","low","high","resistance","reactance","pt_ratio", 7 | "ct_ratio","pt_phase","connecting_element","measuring_element","feeder_name"], 8 | "PhaseCapacitor" : ["phase","var"], 9 | "Transformer" : ["name", "noload_loss","phase_shift","from_element","to_element","reactances","windings","loadloss","normhkva","is_center_tap","feeder_name"], 10 | "Winding" : ["connection_type","voltage_type","nominal_voltage","voltage_limit","resistance","phase_windings","rated_power","emergency_power"], 11 | "PhaseWinding" : ["tap_position","phase","compensator_r","compensator_x"], 12 | "Regulator" : ["name","delay","highstep","lowstep","pt_ratio","phase_shift","bandwidth","bandcenter","voltage_limit","from_element","to_element", 13 | "connected_transformer","pt_phase","winding","ct_prim","reactances","feeder_name","setpoint"], 14 | "Load" : ["name","connection_type","nominal_voltage","vmin","vmax","phase_loads","connecting_element","feeder_name"], 15 | "PhaseLoad" : ["phase","p","q","model","use_zip","ppercentcurrent","qpercentcurrent","ppercentpower","qpercentpower","ppercentimpedance","qpercentimpedance"], 16 | "PowerSource" : ["name","nominal_voltage","per_unit","phases","positions","is_sourcebus","rated_power", 17 | "emergency_power","connecting_element","positive_sequence_impedance","zero_sequence_impedance"], 18 | "Nodes" : ["name","nominal_voltage","phases","positions","feeder_name"] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ditto/models/timeseries.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from builtins import super, range, zip, round, map 3 | 4 | from .base import DiTToHasTraits, Float, Unicode, Any, Int, List, observe, Instance 5 | 6 | from .position import Position 7 | 8 | 9 | class Timeseries(DiTToHasTraits): 10 | 11 | data = Instance( 12 | "pandas.DataFrame", 13 | help="""This is the data that is stored in the timeseries object.""", 14 | default_value=None, 15 | ) 16 | data_label = Unicode( 17 | help="The label assigned to the dataset. This describes what the name should be when it is outputted to through the writers" 18 | "", 19 | default_value=None, 20 | ) 21 | interval = Float( 22 | help="""This is the interval in the default units that the timeseries data is recorded at. E.g. minute data would have an interval of 60 for a unit of seconds""", 23 | ).tag(default=None) 24 | data_location = Unicode( 25 | help="""The absolute location on disk of the data""", default_value=None 26 | ) 27 | 28 | data_location_kvar = Unicode( 29 | help="""The absolute location on disk of the data""", default_value=None 30 | ) 31 | 32 | data_type = Unicode( 33 | help="""This is the python datatype of the timeseries e.g. float, complex etc.""", 34 | default_value=None, 35 | ) 36 | loaded = Int( 37 | help="""A boolean describing whether the data is in memory or on disk. If this is 1, the data is loaded into the data field. Otherwise it is not in memory and is on disk at data_location""", 38 | ).tag(default=None) 39 | 40 | scale_factor = Float( 41 | help="""A number to multiply the entire timeseries by for scaling purposes""", 42 | default_value=1, 43 | ) 44 | 45 | substation_name = Unicode( 46 | help="""The name of the substation to which the object is connected.""", 47 | ).tag(default=None) 48 | feeder_name = Unicode( 49 | help="""The name of the feeder the object is on.""", 50 | ).tag(default=None) 51 | 52 | def build(self, model): 53 | self._model = model 54 | -------------------------------------------------------------------------------- /scripts/clean_logs.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from builtins import super, range, zip, round, map 3 | 4 | import logging 5 | import os 6 | from glob import glob 7 | import argparse 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | def main(): 12 | '''This module is designed for cleaning log files that might accumulate in the validation folder. 13 | 14 | **Usage:** 15 | 16 | There are two ways to use this module: 17 | 18 | - Remove all log files: 19 | 20 | $ python clean_logs.py 21 | 22 | - Remove only a subset of the log files: 23 | 24 | $ python clean_logs.py -f ./logs/reader/opendss 25 | 26 | This will remove all opendss reader log files for example. 27 | 28 | $ python clean_logs.py -f ./logs/writer 29 | 30 | This will remove all writer log files. 31 | 32 | .. note:: Names of removed files are printed when they are deleted. 33 | 34 | .. todo:: Add time capabilities like 'delete all log files older than yesterday' 35 | 36 | Author: Nicolas Gensollen. November 2017. 37 | 38 | ''' 39 | #Parse the arguments 40 | parser = argparse.ArgumentParser() 41 | 42 | #Feeder list 43 | parser.add_argument('-f', action='append', dest='folder_list', default=[]) 44 | 45 | results = parser.parse_args() 46 | 47 | #If nothing is provided, clean everything... 48 | if results.folder_list == []: 49 | remove_log_and_return_subfolders('./logs/') 50 | else: 51 | [remove_log_and_return_subfolders(folder) for folder in results.folder_list] 52 | 53 | 54 | def remove_log_and_return_subfolders(path): 55 | '''Remove log files with the following strategy: 56 | - List *.log in the current folder and remove them 57 | - List all subfolders and repeat 58 | 59 | ''' 60 | log_files = glob(path.strip('/') + '/*.log') 61 | for log_file in log_files: 62 | os.remove(log_file) 63 | logger.debug('-->cleaned:: {}'.format(log_file)) 64 | 65 | subfolders = [path.strip('/') + '/' + x for x in os.listdir(path) if '.' not in x] 66 | if len(subfolders) == 0: 67 | return 68 | else: 69 | return [remove_log_and_return_subfolders(folder) for folder in subfolders] 70 | 71 | 72 | if __name__ == '__main__': 73 | main() 74 | -------------------------------------------------------------------------------- /tests/default_values/test_default_values.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | test_default_values.py 5 | ---------------------------------- 6 | 7 | Tests for assigning default values for Line 8 | """ 9 | 10 | import os 11 | import pytest 12 | import numpy as np 13 | 14 | from ditto.store import Store 15 | from ditto.readers.opendss.read import Reader 16 | 17 | current_directory = os.path.realpath(os.path.dirname(__file__)) 18 | 19 | 20 | def test_default_values(): 21 | m = Store() 22 | r = Reader( 23 | master_file=os.path.join(current_directory, "test_default_values.dss"), 24 | default_values_file=os.path.join(current_directory, "test_default_values.json"), 25 | ) 26 | r.parse(m) 27 | m.set_names() 28 | 29 | assert m["line1"].faultrate == 0.2 30 | 31 | assert m["line1"].impedance_matrix == [ 32 | [(0.00113148 + 0.000884886j), (0.000142066 + 0.000366115j)], 33 | [(0.000142066 + 0.000366115j), (0.00113362 + 0.000882239j)], 34 | ] 35 | 36 | assert m["line1"].capacitance_matrix == [ 37 | [(0.00733718 + 0j), (-0.00239809 + 0j)], 38 | [(-0.00239809 + 0j), (0.00733718 + 0j)], 39 | ] 40 | 41 | phased_wires = {} 42 | for wire in m["line1"].wires: 43 | phased_wires[wire.phase] = wire 44 | 45 | # Ampacity 46 | for p in ["A", "B", "C"]: 47 | assert phased_wires[p].ampacity == 200 48 | assert phased_wires[p].emergency_ampacity == 400 49 | 50 | assert m["cap1"].connection_type == "Y" 51 | assert m["cap1"].low == 114 52 | assert m["cap1"].high == 125 53 | assert m["cap1"].delay == 10 54 | assert m["cap1"].pt_ratio == 50 55 | assert m["cap1"].ct_ratio == 50 56 | assert m["cap1"].pt_phase == "A" 57 | 58 | assert m["reg1"].reactances == [6] 59 | 60 | assert m["regulator_reg1"].ct_prim == 300 61 | assert m["regulator_reg1"].delay == 16 62 | assert m["regulator_reg1"].highstep == 15 63 | assert m["regulator_reg1"].pt_ratio == 60 64 | assert m["regulator_reg1"].bandwidth == 3 65 | assert m["regulator_reg1"].bandcenter == 130 66 | 67 | assert m["load_load1"].connection_type == "Y" 68 | assert m["load_load1"].vmin == 0.95 69 | assert m["load_load1"].vmax == 1.05 70 | -------------------------------------------------------------------------------- /ditto/metric_computer.py: -------------------------------------------------------------------------------- 1 | import os 2 | import datetime 3 | import traceback 4 | import json 5 | 6 | import logging 7 | 8 | from .store import Store 9 | from .converter import Converter 10 | from .metrics.network_analysis import NetworkAnalyzer as network_analyzer 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class MetricComputer(Converter): 16 | """TODO""" 17 | 18 | def __init__( 19 | self, 20 | registered_reader_class, 21 | input_path, 22 | output_format, 23 | output_path, 24 | by_feeder, 25 | verbose=True, 26 | **kwargs 27 | ): 28 | """MetricComputer class CONSTRUCTOR.""" 29 | self.by_feeder = by_feeder 30 | self.output_format = output_format 31 | # Call super 32 | super(MetricComputer, self).__init__( 33 | registered_reader_class, 34 | None, 35 | input_path, 36 | output_path, 37 | verbose=True, 38 | **kwargs 39 | ) 40 | 41 | def convert(self): 42 | """Cannot call convert.""" 43 | raise NotImplementedError( 44 | "MetricComputer cannot convert. Use Converter instead." 45 | ) 46 | 47 | def compute(self): 48 | """Compute the metrics""" 49 | inputs = self.get_inputs(self.feeder) 50 | 51 | self.configure_reader(inputs) 52 | self.reader.parse(self.m) 53 | 54 | self.net = network_analyzer(self.m) 55 | self.net.model.set_names() 56 | # If we compute the metrics per feeder, we need to have the objects taged with their feeder_names 57 | if self.by_feeder: 58 | # Split the network into feeders (assumes objects have been taged) 59 | self.net.split_network_into_feeders() 60 | self.net.compute_all_metrics_per_feeder() 61 | else: 62 | self.net.compute_all_metrics() 63 | 64 | # Export to the required format 65 | if self.output_format.lower() == "json": 66 | self.net.export_json(os.path.join(self.output_path, "metrics.json")) 67 | elif self.output_format.lower() in ["xlsx", "excel", "xls"]: 68 | self.net.export(os.path.join(self.output_path, "metrics.xlsx")) 69 | -------------------------------------------------------------------------------- /tests/test_reader.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | test_reader 5 | ---------------------------------- 6 | 7 | Tests for `ditto` module readers 8 | """ 9 | import os 10 | import pytest as pt 11 | from ditto.store import Store 12 | 13 | current_directory = os.path.realpath(os.path.dirname(__file__)) 14 | 15 | 16 | def test_gld_reader(): 17 | gridlabd_models_dir = os.path.join( 18 | current_directory, "data", "small_cases", "gridlabd" 19 | ) 20 | gridlabd_models = [ 21 | f for f in os.listdir(gridlabd_models_dir) if not f.startswith(".") 22 | ] 23 | from ditto.readers.gridlabd.read import Reader 24 | 25 | for model in gridlabd_models: 26 | m = Store() 27 | r = Reader(input_file=os.path.join(gridlabd_models_dir, model, "node.glm")) 28 | r.parse(m) 29 | 30 | 31 | def test_cyme_reader(): 32 | """ 33 | TODO 34 | """ 35 | from ditto.readers.cyme.read import Reader 36 | from ditto.store import Store 37 | 38 | cyme_models_dir = os.path.join(current_directory, "data", "small_cases", "cyme") 39 | cyme_models = [f for f in os.listdir(cyme_models_dir) if not f.startswith(".")] 40 | for model in cyme_models: 41 | m = Store() 42 | r = Reader(data_folder_path=os.path.join(cyme_models_dir, model)) 43 | r.parse(m) 44 | # TODO: Log properly 45 | print(">Cyme model {model} parsed.\n".format(model=model)) 46 | 47 | 48 | # @pt.mark.skip("Segfault occurs") 49 | def test_opendss_reader(): 50 | """ 51 | TODO 52 | """ 53 | from ditto.readers.opendss.read import Reader 54 | from ditto.store import Store 55 | 56 | opendss_models_dir = os.path.join( 57 | current_directory, "data", "small_cases", "opendss" 58 | ) 59 | opendss_models = [ 60 | f for f in os.listdir(opendss_models_dir) if not f.startswith(".") 61 | ] 62 | for model in opendss_models: 63 | m = Store() 64 | r = Reader( 65 | master_file=os.path.join(opendss_models_dir, model, "master.dss"), 66 | buscoordinates_file=os.path.join(opendss_models_dir, model, "buscoord.dss"), 67 | ) 68 | r.parse(m) 69 | # TODO: Log properly 70 | print(">OpenDSS model {model} parsed.\n".format(model=model)) 71 | 72 | 73 | def test_dew_reader(): 74 | """ 75 | TODO 76 | """ 77 | pass 78 | -------------------------------------------------------------------------------- /ditto/models/generator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function 3 | from builtins import super, range, zip, round, map 4 | 5 | from .base import ( 6 | DiTToHasTraits, 7 | Float, 8 | Unicode, 9 | Any, 10 | Int, 11 | List, 12 | observe, 13 | Instance, 14 | Complex, 15 | Bool, 16 | ) 17 | 18 | from .position import Position 19 | 20 | 21 | class Generator(DiTToHasTraits): 22 | 23 | name = Unicode(help="""Name of the power source object""") 24 | nominal_voltage = Float( 25 | help="""This parameter defines the base voltage at the power source.""", 26 | default_value=None, 27 | ) 28 | connecting_element = Unicode( 29 | help="""Name of the bus the generator is connected to.""", default_value=None 30 | ) 31 | substation_name = Unicode( 32 | help="""The name of the substation to which the object is connected.""", 33 | default_value=None, 34 | ) 35 | forced_on = Unicode( 36 | help="""Check if generator active.""", 37 | default_value=None 38 | ) 39 | model = Int( 40 | help="""Type of generator.""", 41 | default_value=None 42 | ) 43 | power_factor = Float( 44 | help="""Default power factor. In watts.""", 45 | default_value=None, 46 | ) 47 | rated_power = Float( 48 | help="""Rated power of the device. In watts.""", default_value=None 49 | ) 50 | phases = List( 51 | Instance(Unicode), 52 | help="""This parameter is a list of all the phases at the power source.""", 53 | ) 54 | positions = List( 55 | Instance(Position), 56 | help="""This parameter is a list of positional points describing the power source - it should only contain one. The positions are objects containing elements of long, lat and elevation.""", 57 | ) 58 | v_max_pu = Float( 59 | help="""The per-unit maximum voltage. Beyond this constant impedance model is applied""", 60 | default_value=None, 61 | ) 62 | v_min_pu = Float( 63 | help="""The per-unit minimum voltage. Below this, constant impedance model is applied""", 64 | default_value=None, 65 | ) 66 | feeder_name = Unicode( 67 | help="""The name of the feeder the object is on.""", default_value=None 68 | ) 69 | 70 | def build(self, model): 71 | self._model = model 72 | -------------------------------------------------------------------------------- /tests/readers/opendss/Loads/test_loads.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | test_loads.py 5 | ---------------------------------- 6 | 7 | Tests for parsing all values of Loads from OpenDSS into DiTTo. 8 | """ 9 | 10 | import os 11 | import math 12 | import pytest 13 | import numpy as np 14 | 15 | from ditto.store import Store 16 | from ditto.readers.opendss.read import Reader 17 | from ditto.default_values.default_values_json import Default_Values 18 | 19 | current_directory = os.path.realpath(os.path.dirname(__file__)) 20 | 21 | 22 | def test_loads(): 23 | m = Store() 24 | r = Reader(master_file=os.path.join(current_directory, "test_loads.dss")) 25 | r.parse(m) 26 | m.set_names() 27 | 28 | # Reading OpenDSS default values 29 | d_v = Default_Values( 30 | os.path.join( 31 | current_directory, 32 | "../../../../ditto/default_values/opendss_default_values.json", 33 | ) 34 | ) 35 | parsed_values = d_v.parse() 36 | 37 | precision = 0.001 38 | assert m["load_zipv"].name == "load_zipv" 39 | assert m["load_zipv"].connection_type == parsed_values["Load"]["connection_type"] 40 | assert m["load_zipv"].vmin == 0.0 41 | assert m["load_zipv"].vmax == 1.2 42 | assert m["load_zipv"].connecting_element == "load" 43 | assert m["load_zipv"].nominal_voltage == 1 * 10 ** 3 44 | assert m["load_zipv"].feeder_name == "src_src" 45 | assert m["load_zipv"].peak_coincident_p == None 46 | assert m["load_zipv"].peak_coincident_q == None 47 | assert len(m["load_zipv"].phase_loads) == 1 # Load is a one phase load 48 | assert m["load_zipv"].phase_loads[0].phase == "A" 49 | assert m["load_zipv"].phase_loads[0].p == 1 * 10 ** 3 50 | assert m["load_zipv"].phase_loads[0].q == pytest.approx( 51 | 1.0 * math.sqrt(1.0 / 0.88 ** 2 - 1) * 10 ** 3, precision 52 | ) 53 | assert m["load_zipv"].phase_loads[0].model == 8 54 | assert m["load_zipv"].phase_loads[0].use_zip == 1 55 | assert m["load_zipv"].phase_loads[0].ppercentcurrent == -0.9855 * 100 56 | assert m["load_zipv"].phase_loads[0].qpercentcurrent == -2.963 * 100 57 | assert m["load_zipv"].phase_loads[0].ppercentpower == 1.1305 * 100 58 | assert m["load_zipv"].phase_loads[0].qpercentpower == 1.404 * 100 59 | assert m["load_zipv"].phase_loads[0].ppercentimpedance == 0.855 * 100 60 | assert m["load_zipv"].phase_loads[0].qpercentimpedance == 2.559 * 100 61 | -------------------------------------------------------------------------------- /ditto/models/winding.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function 3 | from builtins import super, range, zip, round, map 4 | 5 | from .base import ( 6 | DiTToHasTraits, 7 | Float, 8 | Unicode, 9 | Any, 10 | Int, 11 | List, 12 | Bool, 13 | observe, 14 | Instance, 15 | ) 16 | 17 | from .position import Position 18 | from .phase_winding import PhaseWinding 19 | 20 | 21 | class Winding(DiTToHasTraits): 22 | connection_type = Unicode( 23 | help="""The connection type (D, Y, Z, A) for Delta, Wye, Zigzag or autotransformer.""", 24 | default_value=None, 25 | ) 26 | voltage_type = Int( 27 | help="""0 for a high voltage connection, 2 for a low voltage connection, and 1 for an intermediary connection.""", 28 | default_value=None, 29 | ) 30 | nominal_voltage = Float( 31 | help="""The nominal voltage of the transformer winding""", default_value=None 32 | ) 33 | voltage_limit = Float( 34 | help="""The maximum voltage allowed on the PT secondary.""", default_value=None 35 | ) 36 | resistance = Float( 37 | help="""The per unit resistance of the winding. For a representation with only one resistance for the entire transformer, this is split equally between the windings.""", 38 | default_value=None, 39 | ) 40 | reverse_resistance = Float( 41 | help="""The per unit resistance of the winding with reverse powerflow. For a representation with only one resistance for the entire transformer, this is split equally between the windings.""", 42 | default_value=None, 43 | ) 44 | phase_windings = List( 45 | Instance(PhaseWinding), 46 | help="""A list of phasewinding objects which contain the phase, tap position and compensator settings""", 47 | default_value=None, 48 | ) 49 | is_grounded = Bool( 50 | help="""The boolean value to indicate whether the winding is grounded or not""", 51 | ) 52 | 53 | # Added by Nicolas (August 2017) 54 | # Better results are obtained if the rated power is specified at the windings rather 55 | # than for the whole transformer 56 | rated_power = Float(help="""The rated power of the winding""", default_value=None) 57 | emergency_power = Float( 58 | help="""The emergency power of the winding""", default_value=None 59 | ) 60 | 61 | def build(self, model): 62 | self._model = model 63 | -------------------------------------------------------------------------------- /ditto/models/feeder_metadata.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, division, print_function 2 | from builtins import super, range, zip, round, map 3 | 4 | from .base import ( 5 | DiTToHasTraits, 6 | Float, 7 | Complex, 8 | Unicode, 9 | Any, 10 | Int, 11 | List, 12 | observe, 13 | Instance, 14 | ) 15 | 16 | 17 | class Feeder_metadata(DiTToHasTraits): 18 | """TODO 19 | 20 | """ 21 | name = Unicode(help="""Name of the feeder object""") 22 | nominal_voltage = Float( 23 | help="""Nominal voltage at the feeder head.""", default_value=None 24 | ) 25 | headnode = Unicode(help="""Name of the headnode/FEEDERHEAD.""", default_value=None) 26 | transformer = Unicode( 27 | help="""Name of the transformer representing the substation.""", 28 | default_value=None, 29 | ) 30 | substation = Unicode( 31 | help="""Name of the object representing the substation in the feeder files (often a bus downstream of the transformer).""", 32 | default_value=None, 33 | ) 34 | operating_voltage = Float( 35 | help="""Operating voltage at the feeder head.""", default_value=None 36 | ) 37 | operating_angle1 = Float(help="""Angle 1.""", default_value=None) 38 | operating_angle2 = Float(help="""Angle 2.""", default_value=None) 39 | operating_angle3 = Float(help="""Angle 3.""", default_value=None) 40 | positive_sequence_resistance = Float( 41 | help="""Positive sequence resistance for the source equivalent.""", 42 | default_value=None, 43 | ) 44 | positive_sequence_reactance = Float( 45 | help="""Positive sequence reactance for the source equivalent.""", 46 | default_value=None, 47 | ) 48 | zero_sequence_resistance = Float( 49 | help="""Zero sequence resistance for the source equivalent.""", 50 | default_value=None, 51 | ) 52 | zero_sequence_reactance = Float( 53 | help="""Zero sequence reactance for the source equivalent.""", 54 | default_value=None, 55 | ) 56 | negative_sequence_resistance = Float( 57 | help="""Negative sequence resistance for the source equivalent.""", 58 | default_value=None, 59 | ) 60 | negative_sequence_reactance = Float( 61 | help="""Negative sequence reactance for the source equivalent.""", 62 | default_value=None, 63 | ) 64 | 65 | def build(self, model, Asset=None, ConnectivityNode=None, Location=None): 66 | 67 | self._model = model 68 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/ieee_8500node/Fuses.DSS: -------------------------------------------------------------------------------- 1 | New Fuse.LN6201670-1 LINE.LN6201670-1 1 fusecurve=tlink Ratedcurrent=10 2 | New Fuse.LN6409873-1 LINE.LN6409873-1 1 fusecurve=tlink Ratedcurrent=10 3 | New Fuse.LN6260017-1 LINE.LN6260017-1 1 fusecurve=tlink Ratedcurrent=10 4 | New Fuse.LN5985355-3 LINE.LN5985355-3 1 fusecurve=tlink Ratedcurrent=10 5 | New Fuse.LN5895802-1 LINE.LN5895802-1 1 fusecurve=tlink Ratedcurrent=10 6 | New Fuse.LN5804798-3 LINE.LN5804798-3 1 fusecurve=tlink Ratedcurrent=10 7 | New Fuse.LN5898058-2 LINE.LN5898058-2 1 fusecurve=tlink Ratedcurrent=10 8 | New Fuse.LN5986923-1 LINE.LN5986923-1 1 fusecurve=tlink Ratedcurrent=10 9 | New Fuse.LN5712587-2 LINE.LN5712587-2 1 fusecurve=tlink Ratedcurrent=10 10 | New Fuse.LN6106583-5 LINE.LN6106583-5 1 fusecurve=tlink Ratedcurrent=10 11 | New Fuse.LN6292464-1 LINE.LN6292464-1 1 fusecurve=tlink Ratedcurrent=10 12 | New Fuse.LN6229831-1 LINE.LN6229831-1 1 fusecurve=tlink Ratedcurrent=10 13 | New Fuse.LN5744326-1 LINE.LN5744326-1 1 fusecurve=tlink Ratedcurrent=10 14 | New Fuse.LN5774470-2 LINE.LN5774470-2 1 fusecurve=tlink Ratedcurrent=10 15 | New Fuse.LN6505944-3 LINE.LN6505944-3 1 fusecurve=tlink Ratedcurrent=10 16 | New Fuse.LN6138609-1 LINE.LN6138609-1 1 fusecurve=tlink Ratedcurrent=10 17 | New Fuse.LN5712477-3 LINE.LN5712477-3 1 fusecurve=tlink Ratedcurrent=10 18 | New Fuse.LN5683833-1 LINE.LN5683833-1 1 fusecurve=tlink Ratedcurrent=10 19 | New Fuse.LN5955074-2 LINE.LN5955074-2 1 fusecurve=tlink Ratedcurrent=10 20 | New Fuse.LN6141147-1 LINE.LN6141147-1 1 fusecurve=tlink Ratedcurrent=10 21 | New Fuse.LN5532741-1 LINE.LN5532741-1 1 fusecurve=tlink Ratedcurrent=10 22 | New Fuse.LN81048102-4 LINE.LN81048102-4 1 fusecurve=tlink Ratedcurrent=10 23 | New Fuse.LN81048100-7 LINE.LN81048100-7 1 fusecurve=tlink Ratedcurrent=10 24 | New Fuse.LN5562961-1 LINE.LN5562961-1 1 fusecurve=tlink Ratedcurrent=10 25 | New Fuse.LN8979346-5 LINE.LN8979346-5 1 fusecurve=tlink Ratedcurrent=10 26 | New Fuse.LN8979344-4 LINE.LN8979344-4 1 fusecurve=tlink Ratedcurrent=10 27 | New Fuse.LN7061777-3 LINE.LN7061777-3 1 fusecurve=tlink Ratedcurrent=10 28 | New Fuse.LN5970852-1 LINE.LN5970852-1 1 fusecurve=tlink Ratedcurrent=10 29 | New Fuse.LN5928544-2 LINE.LN5928544-2 1 fusecurve=tlink Ratedcurrent=10 30 | New Fuse.LN6991377-9 LINE.LN6991377-9 1 fusecurve=tlink Ratedcurrent=10 -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/epri_j1/monitors.dss: -------------------------------------------------------------------------------- 1 | !- Define Energy Meter @ Sub 2 | New EnergyMeter.J1 element=Line.OH_5964927408 terminal=1 !peakcurrent=[393, 393, 393] 3 | 4 | ! - Define all monitors 5 | 6 | New monitor.subVI element=Transformer.SubXfmr terminal=2 mode=0 7 | New monitor.subPQ element=Transformer.SubXfmr terminal=1 mode=65 PPolar=No 8 | New monitor.feederPQ element=Line.OH_5964927408 terminal=1 mode=65 PPolar=No 9 | 10 | !Sub 11 | New monitor.Tap9 element=Transformer.SubXfmr terminal=2 mode=2 12 | 13 | ! 1st regulator 14 | New monitor.Tap5 element=Transformer.regxfmr_B4874 terminal=2 mode=2 15 | New monitor.Tap7 element=Transformer.regxfmr_B4872 terminal=2 mode=2 16 | New monitor.Tap8 element=Transformer.regxfmr_B4868 terminal=2 mode=2 17 | 18 | !Middle Regulator - just downstraam 19 | New monitor.Tap1 element=Transformer.regxfmr_B18865 terminal=2 mode=2 20 | New monitor.Tap3 element=Transformer.regxfmr_B18863 terminal=2 mode=2 21 | New monitor.Tap6 element=Transformer.regxfmr_B18864 terminal=2 mode=2 22 | 23 | !2Phase regs 24 | New monitor.Tap2 element=Transformer.regxfmr_B19008 terminal=2 mode=2 25 | New monitor.Tap4 element=Transformer.regxfmr_B19010 terminal=2 mode=2 26 | 27 | !close to head of feeder 28 | New monitor.Cap3 element=Capacitor.B4862-1 mode=33 29 | New monitor.Cap4 element=Capacitor.B4829-1 mode=33 30 | New monitor.Cap5 element=Capacitor.B4877-1 mode=33 31 | !Mid 32 | New monitor.Cap1 element=Capacitor.B4909-1 mode=33 33 | !end 34 | New monitor.Cap2 element=Capacitor.B18944 mode=33 35 | 36 | 37 | New monitor.VoltageTap1 element=Transformer.regxfmr_B18865 terminal=2 mode=0 38 | New monitor.VoltageTap2 element=Transformer.regxfmr_B19008 terminal=2 mode=0 39 | New monitor.VoltageTap3 element=Transformer.regxfmr_B18863 terminal=2 mode=0 40 | New monitor.VoltageTap4 element=Transformer.regxfmr_B19010 terminal=2 mode=0 41 | New monitor.VoltageTap5 element=Transformer.regxfmr_B4874 terminal=2 mode=0 42 | New monitor.VoltageTap6 element=Transformer.regxfmr_B18864 terminal=2 mode=0 43 | New monitor.VoltageTap7 element=Transformer.regxfmr_B4872 terminal=2 mode=0 44 | New monitor.VoltageTap8 element=Transformer.regxfmr_B4868 terminal=2 mode=0 45 | New monitor.VoltageCap1 element=Capacitor.B4909-1 mode=0 46 | New monitor.VoltageCap2 element=Capacitor.B18944 mode=0 47 | New monitor.VoltageCap3 element=Capacitor.B4862-1 mode=0 48 | New monitor.VoltageCap4 element=Capacitor.B4829-1 mode=0 49 | New monitor.VoltageCap5 element=Capacitor.B4877-1 mode=0 50 | New monitor.HighestImpedanceBus element=LINE.OH_5962929303 terminal=2 mode=0 -------------------------------------------------------------------------------- /scripts/compare.py: -------------------------------------------------------------------------------- 1 | # coding: utf8 2 | 3 | from __future__ import absolute_import, division, print_function 4 | from builtins import super, range, zip, round, map 5 | 6 | import logging 7 | import numpy as np 8 | import pandas as pd 9 | import argparse 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | def rms(df1, df2, key): 15 | '''Computes root mean squares on the given key. 16 | 17 | ''' 18 | rms = [] 19 | for i1, row1 in df1.iterrows(): 20 | for i2, row2 in df2.iterrows(): 21 | if row1['Bus'] == row2['Bus']: 22 | try: 23 | rms.append((row1[key] - row2[key])**2) 24 | except: 25 | raise ValueError('{} not in dataframe'.format(key)) 26 | return sum(rms) 27 | 28 | 29 | def absolute(df1, df2, key): 30 | '''Computes sum of the absolute differences on the given key. 31 | 32 | ''' 33 | abss = [] 34 | for i1, row1 in df1.iterrows(): 35 | for i2, row2 in df2.iterrows(): 36 | if row1['Bus'] == row2['Bus']: 37 | try: 38 | abss.append(abs(row1[key] - row2[key])) 39 | except: 40 | raise ValueError('{} not in dataframe'.format(key)) 41 | return sum(abss) 42 | 43 | 44 | def main(): 45 | '''Compare two power flow results. 46 | 47 | **Usage:** 48 | 49 | $ python compare.py -p1 ./inputs/opendss/ieee_13_node -p2 ./outputs/from_cyme/to_opendss/ieee_13_node 50 | 51 | This will look for "voltage_profile.csv" in both directories and load them into a Pandas dataframe. 52 | 53 | For now, this only computes the root mean square error for each phase (in p.u). 54 | 55 | ''' 56 | #Parse the arguments 57 | parser = argparse.ArgumentParser() 58 | 59 | #Feeder list 60 | parser.add_argument('-p1', action='store', dest='path1') 61 | 62 | #Format from 63 | parser.add_argument('-p2', action='store', dest='path2') 64 | 65 | results = parser.parse_args() 66 | 67 | path1 = results.path1 68 | path2 = results.path2 69 | 70 | df1 = pd.read_csv(path1 + '/voltage_profile.csv') 71 | df2 = pd.read_csv(path2 + '/voltage_profile.csv') 72 | 73 | #RMS 74 | for p, k in zip(['A', 'B', 'C'], [' pu1', ' pu2', ' pu3']): 75 | logger.debug('Phase {p} : rms={r}'.format(p=p, r=rms(df1, df2, k))) 76 | 77 | #Absolute 78 | for p, k in zip(['A', 'B', 'C'], [' pu1', ' pu2', ' pu3']): 79 | logger.debug('Phase {p} : |error|={r}'.format(p=p, r=absolute(df1, df2, k))) 80 | 81 | 82 | if __name__ == '__main__': 83 | main() 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ditto/readers/synergi/mdbtools.tar.gz 2 | ditto/readers/synergi/mdbtools 3 | 4 | # Editor generated files # 5 | ########################## 6 | .vscode 7 | .mypy_cache 8 | 9 | # OS generated files # 10 | ###################### 11 | .DS_Store 12 | .DS_Store? 13 | ._* 14 | .Spotlight-V100 15 | .Trashes 16 | ehthumbs.db 17 | Thumbs.db 18 | 19 | # Python files # 20 | ################ 21 | *.egg-info 22 | __pycache__ 23 | 24 | docs/_build 25 | .ipynb_checkpoints 26 | 27 | # Compiled Python files 28 | *.pyc 29 | 30 | # Folder view configuration files 31 | Desktop.ini 32 | 33 | 34 | # Created by https://www.gitignore.io/api/python,jupyternotebook 35 | 36 | ### JupyterNotebook ### 37 | .ipynb_checkpoints 38 | */.ipynb_checkpoints/* 39 | 40 | # Remove previous ipynb_checkpoints 41 | # git rm -r .ipynb_checkpoints/ 42 | # 43 | ### Python ### 44 | # Byte-compiled / optimized / DLL files 45 | __pycache__/ 46 | *.py[cod] 47 | *$py.class 48 | 49 | # C extensions 50 | *.so 51 | 52 | # log files 53 | *.log 54 | 55 | # Distribution / packaging 56 | .Python 57 | build/ 58 | develop-eggs/ 59 | dist/ 60 | downloads/ 61 | eggs/ 62 | .eggs/ 63 | lib/ 64 | lib64/ 65 | parts/ 66 | sdist/ 67 | var/ 68 | wheels/ 69 | *.egg-info/ 70 | .installed.cfg 71 | *.egg 72 | 73 | # PyInstaller 74 | # Usually these files are written by a python script from a template 75 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 76 | *.manifest 77 | *.spec 78 | 79 | # Installer logs 80 | pip-log.txt 81 | pip-delete-this-directory.txt 82 | 83 | # Unit test / coverage reports 84 | htmlcov/ 85 | .tox/ 86 | .coverage 87 | .coverage.* 88 | .cache 89 | .pytest_cache/ 90 | nosetests.xml 91 | coverage.xml 92 | *.cover 93 | .hypothesis/ 94 | 95 | # Translations 96 | *.mo 97 | *.pot 98 | 99 | # Flask stuff: 100 | instance/ 101 | .webassets-cache 102 | 103 | # Scrapy stuff: 104 | .scrapy 105 | 106 | # Sphinx documentation 107 | docs/_build/ 108 | 109 | # PyBuilder 110 | target/ 111 | 112 | # Jupyter Notebook 113 | 114 | # pyenv 115 | .python-version 116 | 117 | # celery beat schedule file 118 | celerybeat-schedule.* 119 | 120 | # SageMath parsed files 121 | *.sage.py 122 | 123 | # Environments 124 | .env 125 | .venv 126 | env/ 127 | venv/ 128 | ENV/ 129 | env.bak/ 130 | venv.bak/ 131 | 132 | # Spyder project settings 133 | .spyderproject 134 | .spyproject 135 | 136 | # Rope project settings 137 | .ropeproject 138 | 139 | # mkdocs documentation 140 | /site 141 | 142 | # mypy 143 | .mypy_cache/ 144 | 145 | 146 | # End of https://www.gitignore.io/api/python,jupyternotebook 147 | -------------------------------------------------------------------------------- /tests/test_metric_extraction.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | test_metric_extraction 5 | ---------------------------------- 6 | 7 | Tests the metric extraction capability of DiTTo 8 | """ 9 | import logging 10 | import os 11 | 12 | import six 13 | 14 | import tempfile 15 | import pytest 16 | import pytest as pt 17 | 18 | current_directory = os.path.realpath(os.path.dirname(__file__)) 19 | 20 | logger = logging.getLogger(__name__) 21 | 22 | 23 | def test_metric_extraction(): 24 | """ 25 | This test reads all small OpenDSS test cases, set the nominal voltages using a 26 | system_structure_modifier object and compute all metrics using a network analyzer object. 27 | Finally, it exports the metrics to excel and Json formats. 28 | """ 29 | from ditto.readers.opendss.read import Reader 30 | from ditto.store import Store 31 | from ditto.modify.system_structure import system_structure_modifier 32 | from ditto.metrics.network_analysis import NetworkAnalyzer as network_analyzer 33 | 34 | opendss_models = [ 35 | f 36 | for f in os.listdir( 37 | os.path.join(current_directory, "data/small_cases/opendss/") 38 | ) 39 | if not f.startswith(".") 40 | ] 41 | opendss_models.remove("storage_test") 42 | 43 | for model in opendss_models: 44 | m = Store() 45 | r = Reader( 46 | master_file=os.path.join( 47 | current_directory, 48 | "data/small_cases/opendss/{model}/master.dss".format(model=model), 49 | ), 50 | buscoordinates_file=os.path.join( 51 | current_directory, 52 | "data/small_cases/opendss/{model}/buscoord.dss".format(model=model), 53 | ), 54 | ) 55 | r.parse(m) 56 | m.set_names() 57 | 58 | # Create a modifier object 59 | modifier = system_structure_modifier(m) 60 | 61 | # And set the nominal voltages of the elements since we don't have it from OpenDSS 62 | modifier.set_nominal_voltages_recur() 63 | modifier.set_nominal_voltages_recur_line() 64 | 65 | # Create a Network analyszer object with the modified model 66 | net = network_analyzer(modifier.model, True, "sourcebus") 67 | net.model.set_names() 68 | 69 | # Compute all the available metrics 70 | net.compute_all_metrics() 71 | 72 | output_path = tempfile.gettempdir() 73 | 74 | # Export them to excel 75 | net.export(os.path.join(output_path, "metrics.xlsx")) 76 | 77 | # Export them to JSON 78 | net.export_json(os.path.join(output_path, "metrics.json")) 79 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | The most common usage is when one has a system in a given format, say OpenDSS, and wants to convert it to another format, say CYME. The convertion process will always be structured in the following way: 4 | 5 | - Instanciate a store object which will store all the objects and attributes of your model 6 | 7 | ```python 8 | from ditto.store import Store 9 | m=Store() 10 | ``` 11 | 12 | - Instanciate a reader object with the required arguments. These arguments may vary depending on the format. For example, the OpenDSS reader expects a path for the master.dss file as well as a path for the buscoord.dss file. 13 | 14 | ```python 15 | from ditto.readers.opendss.read import reader 16 | opendss_reader=reader(master_file='/Users/johny_cash/IEEE_13_node/master.dss, 17 | buscoordinates_file='/Users/johny_cash/IEEE_13_node/buscoords.dss') 18 | ``` 19 | 20 | - Call the parse method of the reader with the store object as argument. 21 | 22 | ```python 23 | opendss_reader.parse(m) 24 | ``` 25 | 26 | - Instanciate a writer object with the required arguments (output folder path…). 27 | 28 | ```python 29 | from ditto.writers.cyme.write import writer 30 | cyme_writer=writer(output_path='./', log_path='./') 31 | ``` 32 | 33 | - Call the write method of the writer with the store object as argument. 34 | 35 | ```python 36 | cyme_writer.write(m) 37 | ``` 38 | 39 | 40 | **How is the code structured?** 41 | 42 | Here is a simplified view of the code structure (only the key components are displayed): 43 | 44 | ```eval_rst 45 | DiTTo 46 | │ README.md 47 | │ docs 48 | | tests 49 | │ 50 | └───ditto 51 | │ store.py 52 | │ 53 | └───core 54 | | │ base.py 55 | | │ _factory.py 56 | | │ exceptions.py 57 | | 58 | └───readers 59 | | └───cyme 60 | | | | read.py 61 | | └───opendss 62 | | | read.py 63 | | 64 | └───writers 65 | └───cyme 66 | | | write.py 67 | └───opendss 68 | | write.py 69 | ``` 70 | 71 | Most of the code is located in the ditto directory. The core structure responsible for storing the objects is implemented in the core folder. Readers are implemented in readers and writers in writers. These two folders then contain the different format in different subfolders. 72 | 73 | **Example 1:** The OpenDSS reader is located in: 74 | 75 | ```bash 76 | DiTTo/ditto/readers/opendss/read.py 77 | ``` 78 | 79 | And can be imported with: 80 | 81 | ```python 82 | from ditto.readers.opendss.read import Reader 83 | ``` 84 | 85 | **Example 2:** The CYME writer is located in: 86 | 87 | ```bash 88 | DiTTo/ditto/writers/cyme/write.py 89 | ``` 90 | 91 | And can be imported with: 92 | 93 | ```python 94 | from ditto.writers.cyme.write import Writer 95 | ``` 96 | 97 | 98 | -------------------------------------------------------------------------------- /ditto/consistency/check_loads_connected.py: -------------------------------------------------------------------------------- 1 | 2 | import networkx as nx 3 | from ditto.network.network import Network 4 | from ditto.models.power_source import PowerSource 5 | from ditto.models.load import Load 6 | 7 | """ 8 | This uses the DiTTo Network functionality to check that there is a path from a load to the source node. Considers switch states. Transformer consistency checked elsewhere 9 | Parameters: 10 | model: ditto.store.Store 11 | The DiTTo storage object with the full network representation 12 | verbose: boolean 13 | Whether to print information about which nodes caused problems 14 | 15 | """ 16 | def check_loads_connected(model,verbose=True): 17 | all_sources = [] 18 | all_loads = set() 19 | load_source_map = {} 20 | result = True 21 | 22 | for i in model.models: 23 | if isinstance(i,PowerSource) and i.connecting_element is not None: 24 | all_sources.append(i) 25 | elif isinstance(i,PowerSource): 26 | print('Warning - a PowerSource element has a None connecting element') 27 | if isinstance(i,Load): 28 | all_loads.add(i) 29 | load_source_map[i.name] = [] 30 | 31 | if len(all_sources) == 0: 32 | print('Model does not contain any power source') 33 | return False 34 | 35 | for source in all_sources: 36 | ditto_graph = Network() 37 | ditto_graph.build(model,source.connecting_element) 38 | ditto_graph.set_attributes(model) 39 | ditto_graph.remove_open_switches(model) # This deletes the switches inside the networkx graph only 40 | source_name = source.connecting_element 41 | all_paths = nx.single_source_shortest_path(ditto_graph.graph,source_name) 42 | 43 | for load in all_loads: 44 | min_dist = float('inf') 45 | load_connection = load.connecting_element 46 | if load_connection in all_paths: 47 | load_source_map[load.name].append(source_name) 48 | 49 | 50 | result = True 51 | sourceless_loads = [] 52 | multi_source_loads = {} 53 | for load in load_source_map: 54 | if len(load_source_map[load]) == 0: 55 | result = False 56 | sourceless_loads.append(load) 57 | if len(load_source_map[load]) >1: 58 | result = False 59 | multi_source_loads[load.name] = load_source_map[load] 60 | 61 | if verbose: 62 | if len(sourceless_loads) > 0: 63 | print('Loads missing sources:') 64 | for load in sourceless_loads: 65 | print(load) 66 | 67 | if len(multi_source_loads)> 0: 68 | print('Loads with multiple sources:') 69 | for load in multi_source_loads: 70 | print(load+ ': ' +multi_source_loads[load]) 71 | 72 | return result 73 | 74 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | DiTTo implements a many-to-one-to-many architecture, meaning that a distribution system is represented in a core structure whithin DiTTo and that parsers need to be developped to link this core representation to existing formats. 4 | 5 | These parsers can be classified in: 6 | 7 | - ```readers``` which read from an existing format such as ```OpenDSS``` and build the core representation with the input data 8 | - ```writers``` which ouptut the core representation to a given existing format. 9 | 10 | The main strength of this approach compared to classic one-to-one mappings lies in its modularity. When implementing a new reader for a given format, the capability to export to all supported formats comes for free. 11 | 12 | ## Readers 13 | 14 | ### Available 15 | 16 | Here is a list of the available readers and their status: 17 | 18 | | Readers | Version assumed | Implementation stage | 19 | | :------: | :-------------: | :-------------------------------: | 20 | | OpenDSS | todo | tested and stable | 21 | | CYME | todo | tested and stable | 22 | | GridlabD | todo | stable, needs more testing | 23 | | Synergi | todo | untested | 24 | | DEW | todo | untested, still under development | 25 | | WindMill | Todo | Untested, still under development | 26 | 27 | There are two slightly different readers available: ```CSV``` and ```JSON``` which are not distribution system modeling formats but data representation formats. The readers can be used to load objects into DiTTo from basic ```CSV``` or ```JSON``` formats. They can be extremely useful for dumping a DiTTo model as is on disk and reload it later, or to apply specific modifications to existing DiTTo models (see more on TODO). 28 | 29 | ### Planned 30 | 31 | Here is a list of readers we wish to implement in a near future. All contributions are welcome! 32 | 33 | - CIM 34 | - Ephasor 35 | 36 | ## Writers 37 | 38 | ### Available 39 | 40 | Here is a list of the available writers and their status: 41 | 42 | | Writers | Version assumed | Implementation stage | 43 | | :------: | :-------------: | :-------------------------------: | 44 | | OpenDSS | todo | tested and stable | 45 | | CYME | todo | tested and stable | 46 | | Ephasor | todo | Untested | 47 | | GridlabD | todo | Untested, still under development | 48 | 49 | As for ```readers``` the JSON writer enables one to save a raw DiTTo model to disk. 50 | 51 | ### Planned 52 | 53 | Here is a list of the writers we wish to implement in a near future. All contributions are welcome! 54 | 55 | - WindMill 56 | - Synergi 57 | - DEW 58 | - CIM 59 | - CSV 60 | 61 | 62 | -------------------------------------------------------------------------------- /tests/writers/opendss/capacitors/test_capacitor_writing.py: -------------------------------------------------------------------------------- 1 | """ 2 | test_capacitor_writing.py 3 | ---------------------------------- 4 | 5 | Tests for writing Capacitor objects from DiTTo to OpenDSS. 6 | """ 7 | 8 | import os 9 | import math 10 | import pytest 11 | import tempfile 12 | import numpy as np 13 | 14 | from ditto.store import Store 15 | from ditto.writers.opendss.write import Writer 16 | from ditto.models.capacitor import Capacitor 17 | from ditto.models.phase_capacitor import PhaseCapacitor 18 | 19 | current_directory = os.path.realpath(os.path.dirname(__file__)) 20 | 21 | 22 | def test_single_phase_capacitor_writing(): 23 | m = Store() 24 | 25 | # Create a one phase, 100kVar capacitor on phase A 26 | cap1 = Capacitor(m) 27 | cap1.name = "cap1" 28 | cap1.nominal_voltage = 4.16 * 10 ** 3 29 | cap1.connecting_element = "bus23" 30 | cap1_A = PhaseCapacitor(m) 31 | cap1_A.phase = "A" 32 | cap1_A.var = 100 * 10 ** 3 33 | cap1.phase_capacitors.append(cap1_A) 34 | 35 | output_path = tempfile.gettempdir() 36 | w = Writer(output_path=output_path) 37 | w.write(m) 38 | 39 | # Check that the OpenDSS writer created a Master file 40 | assert os.path.exists(os.path.join(output_path, "Master.dss")) 41 | 42 | # Check that the OpenDSS writer created a Capacitors.dss file 43 | assert os.path.exists(os.path.join(output_path, "Capacitors.dss")) 44 | 45 | with open(os.path.join(output_path, "Capacitors.dss"), "r") as fp: 46 | lines = fp.readlines() 47 | 48 | assert ( 49 | len(lines) == 2 50 | ) # There is one line with the capacitor string and one empty line 51 | assert lines[0] == "New Capacitor.cap1 bus1=bus23.1 phases=1 Kv=4.16 Kvar=100.0\n" 52 | 53 | 54 | def test_three_phase_capacitor_writing(): 55 | m = Store() 56 | 57 | # Create a three phase, 900kVar capacitor 58 | cap1 = Capacitor(m) 59 | cap1.connecting_element = "bus66" 60 | cap1.nominal_voltage = 4.16 * 10 ** 3 61 | cap1.name = "cap1" 62 | 63 | for phase in ["A", "B", "C"]: 64 | cap1.phase_capacitors.append(PhaseCapacitor(m, phase=phase, var=300 * 10 ** 3)) 65 | 66 | output_path = tempfile.gettempdir() 67 | w = Writer(output_path=output_path) 68 | w.write(m) 69 | 70 | # Check that the OpenDSS writer created a Master file 71 | assert os.path.exists(os.path.join(output_path, "Master.dss")) 72 | 73 | # Check that the OpenDSS writer created a Capacitors.dss file 74 | assert os.path.exists(os.path.join(output_path, "Capacitors.dss")) 75 | 76 | with open(os.path.join(output_path, "Capacitors.dss"), "r") as fp: 77 | lines = fp.readlines() 78 | 79 | assert ( 80 | len(lines) == 2 81 | ) # There is one line with the capacitor string and one empty line 82 | assert lines[0] == "New Capacitor.cap1 bus1=bus66 phases=3 Kv=4.16 Kvar=900.0\n" 83 | -------------------------------------------------------------------------------- /tests/readers/opendss/Capacitors/test_capacitor_connectivity.dss: -------------------------------------------------------------------------------- 1 | Clear 2 | 3 | New circuit.test_capacitor_connectivity basekv=4.16 pu=1.01 phases=3 bus1=SourceBus 4 | 5 | ! Capacitor Cap1 should be a three phase capacitor (3 PhaseCapacitor objects) connected to bus1 6 | New Capacitor.Cap1 Bus1=bus1 phases=3 kVAR=600 kV=4.16 7 | 8 | ! Capacitor Cap2 should be a one phase capacitor (1 PhaseCapacitor object) connected to bus2 on phase C 9 | New Capacitor.Cap2 Bus1=bus2.3 phases=1 kVAR=100 kV=2.4 10 | 11 | ! Capacitor Cap3 should be a one phase capacitor (1 PhaseCapacitor object) connected to bus3 on phase A 12 | New Capacitor.Cap3 Bus1=bus3.1 phases=1 kVAR=200.37 kV=2.4 13 | 14 | ! Capacitor Cap4 should be a two phase capacitor (2 PhaseCapacitor objects) connected to bus4 on phase A and C 15 | New Capacitor.Cap4 Bus1=bus4.1.3 phases=2 kVAR=400 kV=2.4 16 | 17 | ! Capacitors from epri_j1 18 | New Linecode.OH-3X_477AAC_4/0AAACN nphases=3 r1=0.12241009 x1=0.39494091 r0=0.33466485 x0=1.2742766 c1=11.1973 c0=4.8089 units=km baseFreq=60 normamps=732 emergamps=871 19 | New Linecode.OH-3X_4CU_4CUN nphases=3 r1=0.85376372 x1=0.49484991 r0=1.2302027 x0=1.5569817 c1=8.7903 c0=4.2476 units=km baseFreq=60 normamps=142 emergamps=142 20 | New Line.OH_B4904 bus1=B4909.1.2.3 bus2=B4904.1.2.3 length=161.84879 units=m linecode=OH-3X_477AAC_4/0AAACN phases=3 enabled=True 21 | New Line.OH_B18944 bus1=B18941.1.2.3 bus2=B18944.1.2.3 length=141.1224 units=m linecode=OH-3X_4CU_4CUN phases=3 enabled=True 22 | 23 | New Capacitor.B4909-1 bus=B4909 kV=12.47 kvar=900 conn=wye 24 | New Capcontrol.B4909-1 Capacitor=B4909-1 element=Line.OH_B4904 terminal=1 Delay=30 25 | ~ type=volt ON=120.5 OFF=125 PTphase=2 PTratio=60 26 | 27 | New Capacitor.B4909-2 bus=B4909 kV=12.47 kvar=900 conn=wye 28 | New Capcontrol.B4909-2 Capacitor=B4909-2 element=Line.OH_B4904 terminal=1 Delay=30 29 | ~ type=volt Vmin=120.5 Vmax=125 PTphase=2 PTratio=60 30 | 31 | New Capacitor.B18944-1 bus=B18941 kV=12.47 kvar=1200 conn=wye 32 | New Capcontrol.B18944-1 Capacitor=B18944-1 element=Line.OH_B18944 terminal=1 Delay=31 33 | ~ type=volt ON=118 OFF=124 PTphase=1 PTratio=60 34 | 35 | New Capacitor.B18944-2 bus=B18941 kV=12.47 kvar=1200 conn=wye 36 | New Capcontrol.B18944-2 Capacitor=B18944-2 element=Line.OH_B18944 terminal=1 Delay=31 37 | ~ type=volt Vmin=118 Vmax=124 PTphase=1 PTratio=60 38 | 39 | ! Capacitors from ieee 8500-node test feeder 40 | New Capacitor.CAPBank0A Bus1=R42246.1 kv=7.2 kvar=400 phases=1 conn=wye 41 | New Capacitor.CAPBank0B Bus1=R42246.2 kv=7.2 kvar=400 phases=1 conn=wye 42 | New Capacitor.CAPBank0C Bus1=R42246.3 kv=7.2 kvar=400 phases=1 conn=wye 43 | 44 | ! This is a 3-phase capacitor bank 45 | New Capacitor.CAPBank3 Bus1=R18242 kv=12.47112 kvar=900 conn=wye 46 | 47 | ! 3-phase capacitor with number of phases mentioned 48 | New Capacitor.CAPBank3-1 Bus1=R18242 kv=12.47112 kvar=900 conn=wye phases=3 49 | 50 | 51 | Set Voltagebases=[4.16, 2.4] 52 | Calcvoltagebases 53 | Solve 54 | -------------------------------------------------------------------------------- /tests/data/small_cases/gridlabd/ieee_4node/node.glm: -------------------------------------------------------------------------------- 1 | // $id$ 2 | // Copyright (C) 2008 Battelle Memorial Institute 3 | 4 | // 4 Node Feeder: Balanced step-down grY-grY 5 | 6 | ///////////////////////////////////////////// 7 | // BEGIN 8 | ///////////////////////////////////////////// 9 | 10 | clock { 11 | timestamp '2000-01-01 0:00:00'; 12 | timezone EST+5EDT; 13 | } 14 | 15 | module powerflow; 16 | 17 | object overhead_line_conductor:100 { 18 | geometric_mean_radius 0.0244; 19 | resistance 0.306; 20 | } 21 | 22 | object overhead_line_conductor:101 { 23 | geometric_mean_radius 0.00814; 24 | resistance 0.592; 25 | } 26 | 27 | object line_spacing:200 { 28 | distance_AB 2.5; 29 | distance_BC 4.5; 30 | distance_AC 7.0; 31 | distance_AN 5.656854; 32 | distance_BN 4.272002; 33 | distance_CN 5.0; 34 | } 35 | 36 | object line_configuration:300 { 37 | conductor_A overhead_line_conductor:100; 38 | conductor_B overhead_line_conductor:100; 39 | conductor_C overhead_line_conductor:100; 40 | conductor_N overhead_line_conductor:101; 41 | spacing line_spacing:200; 42 | } 43 | 44 | object transformer_configuration:400 { 45 | connect_type 1; 46 | power_rating 6000; 47 | powerA_rating 2000; 48 | powerB_rating 2000; 49 | powerC_rating 2000; 50 | primary_voltage 12470; 51 | secondary_voltage 4160; 52 | resistance 0.01; 53 | reactance 0.06; 54 | } 55 | 56 | object node { 57 | name node1; 58 | phases "ABCN"; 59 | voltage_A +7199.558+0.000j; 60 | voltage_B -3599.779-6235.000j; 61 | voltage_C -3599.779+6235.000j; 62 | nominal_voltage 7200; 63 | } 64 | 65 | object overhead_line:12 { 66 | phases "ABCN"; 67 | from node1; 68 | to node2; 69 | length 2000; 70 | configuration line_configuration:300; 71 | } 72 | 73 | object node { 74 | name node2; 75 | phases "ABCN"; 76 | voltage_A +7199.558+0.000j; 77 | voltage_B -3599.779-6235.000j; 78 | voltage_C -3599.779+6235.000j; 79 | nominal_voltage 7200; 80 | } 81 | 82 | object transformer:23 { 83 | phases "ABCN"; 84 | from node2; 85 | to node3; 86 | configuration transformer_configuration:400; 87 | } 88 | 89 | object node { 90 | name node3; 91 | phases "ABCN"; 92 | voltage_A +2401.777+0.000j; 93 | voltage_B -1200.889-2080.000j; 94 | voltage_C -1200.889+2080.000j; 95 | nominal_voltage 2400; 96 | } 97 | 98 | object overhead_line:34 { 99 | phases "ABCN"; 100 | from node3; 101 | to load4; 102 | length 2500; 103 | configuration line_configuration:300; 104 | } 105 | 106 | object load { 107 | name load4; 108 | phases "ABCN"; 109 | voltage_A +2401.777+0.000j; 110 | voltage_B -1200.889-2080.000j; 111 | voltage_C -1200.889+2080.000j; 112 | constant_power_A +1800000.000+871779.789j; 113 | constant_power_B +1800000.000+871779.789j; 114 | constant_power_C +1800000.000+871779.789j; 115 | nominal_voltage 2400; 116 | } 117 | 118 | 119 | /////////////////////////////// 120 | // END 121 | /////////////////////////////// 122 | -------------------------------------------------------------------------------- /tests/data/ditto-validation/cyme/switches/network.txt: -------------------------------------------------------------------------------- 1 | [GENERAL] 2 | DATE=June 04, 2018 at 12:08:21 3 | CYME_VERSION=8.00 4 | CYMDIST_REVISION=8 5 | 6 | [SI] 7 | 8 | [NODE] 9 | FORMAT_NODE=NodeID,CoordX,CoordY,TagText,TagProperties,TagDeltaX,TagDeltaY,TagAngle,TagAlignment,TagBorder,TagBackground,TagTextColor,TagBorderColor,TagBackgroundColor,TagLocation,TagFont,TagTextSize,TagOffset,ZoneID,ExposedCircuitType,BusGap,WorkingDistance,UseUserDefinedFaultCurrent,UserDefinedFaultCurrent,OpeningTimeMode,UserDefinedOpeningTime,EnclosureWidth,EnclosureHeight,EnclosureDepth,CoefficientA,CoefficientK,UserDefinedTimeConstant,TimeConstant,OverrideLFVoltageLimit,HighVoltageLimit,LowVoltageLimit,LoadSheddingActive,MaximumLoadShed,ShedLoadCost,UserDefinedBaseVoltage,Installation,RatedVoltage,RatedCurrent,ANSISymCurrent,ANSIAsymCurrent,PeakCurrent,Standard,TestCircuitPowerFactor 10 | 0,,,NULL,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.000000,0,,,,,,, 11 | 1,,,NULL,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.000000,0,,,,,,, 12 | 2,,,NULL,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0.000000,0,,,,,,, 13 | 14 | [HEADNODES] 15 | FORMAT_HEADNODES=NodeID,NetworkID,ConnectorIndex,StructureID,HarmonicEnveloppe,EquivalentSourceConfiguration,EquivalentSourceSinglePhaseCT,EquivSourceCenterTapPhase,BackgroundHarmonicVoltage 16 | 0,FEEDER1,0,,0,0,0,0,0 17 | 18 | [SOURCE EQUIVALENT] 19 | FORMAT_SOURCEEQUIVALENT=NodeID,LoadModelName,Voltage,OperatingAngle1,OperatingAngle2,OperatingAngle3,PositiveSequenceResistance,PositiveSequenceReactance,NegativeSequenceResistance,NegativeSequenceReactance,ZeroSequenceResistance,ZeroSequenceReactance,OperatingVoltage1,OperatingVoltage2,OperatingVoltage3,BaseMVA,ImpedanceUnit 20 | 0,DEFAULT,13.800000,0.000000,-120.000000,120.000000,0.100000,0.600000,0.100000,0.600000,0.500000,2.000000,7.200000,7.200000,7.200000,100.000000,0 21 | 22 | [SECTION] 23 | FORMAT_SECTION=SectionID,FromNodeID,FromNodeIndex,ToNodeID,ToNodeIndex,Phase,ZoneID,SubNetworkId,EnvironmentID 24 | FORMAT_FEEDER=NetworkID,HeadNodeID,CoordSet,Year,Description,Color,LoadFactor,LossLoadFactorK,Group1,Group2,Group3,TagText,TagProperties,TagDeltaX,TagDeltaY,TagAngle,TagAlignment,TagBorder,TagBackground,TagTextColor,TagBorderColor,TagBackgroundColor,TagLocation,TagFont,TagTextSize,TagOffset,Version,EnvironmentID 25 | FEEDER=FEEDER1,0,1,1527698586,,255,1.000000,0.150000,,,,,16387,13.026886,-18.900939,0.000000,0,2,0,-1,-1,-1,0,Arial,2.000000,0.330000,-1,0 26 | A,0,,1,,ABC,,, 27 | B,1,,2,,AC,,, 28 | 29 | [SWITCH SETTING] 30 | FORMAT_SWITCHSETTING=SectionID,Location,EqID,DeviceNumber,CoordX,CoordY,ClosedPhase,Locked,RC,NStatus,DemandType,Value1A,Value2A,Value1B,Value2B,Value1C,Value2C,Value1ABC,Value2ABC,PhPickup,GrdPickup,Alternate,PhAltPickup,GrdAltPickup,FromNodeID,FaultIndicator,Automated,SensorMode,Strategic,RestorationMode,ConnectionStatus,ByPassOnRestoration,Reversible 31 | A,M,DEFAULT,A,,,AC,0,0,0,,,,,,,,,,0.000000,0.000000,0,0.000000,0.000000,671,0,0,0,0,0,0,0,0 32 | B,M,SWITCH1,B,-101.989755,166.117188,0,0,0,0,,,,,,,,,,0.000000,0.000000,0,0.000000,0.000000,650,0,0,0,0,0,0,0,0 33 | -------------------------------------------------------------------------------- /ditto/models/reactor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, division, print_function 3 | from builtins import super, range, zip, round, map 4 | 5 | from .base import ( 6 | DiTToHasTraits, 7 | Float, 8 | Complex, 9 | Unicode, 10 | Any, 11 | Int, 12 | List, 13 | observe, 14 | Instance, 15 | Bool, 16 | ) 17 | 18 | from .position import Position 19 | from .phase_reactor import PhaseReactor 20 | 21 | 22 | class Reactor(DiTToHasTraits): 23 | """ 24 | TODO 25 | """ 26 | 27 | name = Unicode(help="""Name of the Reactor object""") 28 | nominal_voltage = Float( 29 | help="""This parameter defines the base voltage of the reactor.""", 30 | default_value=None, 31 | ) 32 | from_element = Any( 33 | help="""Name of the node which connects to the 'from' end of the reactor""", 34 | default_value=None, 35 | ) 36 | to_element = Any( 37 | help="""'Name of the node which connects to the 'to' end of the reactor. If set to None, shunt reactor is assumed.""", 38 | default_value=None, 39 | ) 40 | positions = List( 41 | Instance(Position), 42 | help="""This parameter is a list of positional points describing the line. The positions are objects containing elements of long, lat and elevation. The points can be used to map the position of the line. """, 43 | default_value=None, 44 | ) 45 | connection_type = Unicode( 46 | help="""The connection type (D, Y, Z, A) for Delta, Wye, Zigzag.""", 47 | default_value=None, 48 | ) 49 | substation_name = Unicode( 50 | help="""The name of the substation to which the object is connected.""", 51 | default_value=None, 52 | ) 53 | feeder_name = Unicode( 54 | help="""The name of the feeder the object is on.""", default_value=None 55 | ) 56 | is_substation = Bool( 57 | help="""Flag that indicates wheter the element is inside a substation or not.""", 58 | default_value=False, 59 | ) 60 | 61 | faultrate = Float( 62 | help="""The number of faults that occur per year""", default_value=None 63 | ) 64 | phase_reactors = List( 65 | Instance(PhaseReactor), 66 | help="""This parameter is a list of all the phase reactors composing the reactor.""", 67 | default_value=None, 68 | ) 69 | 70 | # NOT SURE ABOUT IMPEDANCE MATRIX.... 71 | # 72 | # impedance_matrix = List(List(Complex),help='''This provides the matrix representation of the reactor impedance in complex form. Computed from the values of GMR and distances of individual wires. Kron reduction is applied to make this a 3x3 matrix.''') 73 | # capacitance_matrix = List(List(Complex),help='''This provides the matrix representation of the reactor capacitance in complex form. Computed from the values of diameters and distances of individual wires. Kron reduction is applied to make this a 3x3 matrix.''') 74 | 75 | def build(self, model): 76 | self._model = model 77 | -------------------------------------------------------------------------------- /tests/data/big_cases/opendss/ieee_8500node/Regulators.dss: -------------------------------------------------------------------------------- 1 | ! voltage regulator definitions 2 | 3 | ! Define three banks of single-phase regulators; each phase is controlled separately 4 | 5 | ! The regulating transformer is defined as a low-impedance, low-loss 2-winding, 1:1, Y-Y transformer to approximate an ideal regulator 6 | ! The control regulates winding 2 7 | 8 | ! Regulator No. 2 9 | 10 | New Transformer.VREG2_A phases=1 windings=2 bank=VREG2 buses=(regxfmr_190-8593.1, 190-8593.1) conns=(wye, wye) kvs=(7.2, 7.2) kvas=(10000, 10000) xhl=0.1 %loadloss=.01 Wdg=2 Maxtap=1.1 Mintap=0.9 ppm=0 11 | New Transformer.VREG2_B phases=1 windings=2 bank=VREG2 buses=(regxfmr_190-8593.2, 190-8593.2) conns=(wye, wye) kvs=(7.2, 7.2) kvas=(10000, 10000) xhl=0.1 %loadloss=.01 Wdg=2 Maxtap=1.1 Mintap=0.9 ppm=0 12 | New Transformer.VREG2_C phases=1 windings=2 bank=VREG2 buses=(regxfmr_190-8593.3, 190-8593.3) conns=(wye, wye) kvs=(7.2, 7.2) kvas=(10000, 10000) xhl=0.1 %loadloss=.01 Wdg=2 Maxtap=1.1 Mintap=0.9 ppm=0 13 | 14 | New RegControl.VREG2_A transformer=VREG2_A winding=2 vreg=125 ptratio=60 band=2 15 | New RegControl.VREG2_B transformer=VREG2_B winding=2 vreg=125 ptratio=60 band=2 16 | New RegControl.VREG2_C transformer=VREG2_C winding=2 vreg=125 ptratio=60 band=2 17 | 18 | ! Regulator No. 3 19 | 20 | New Transformer.VREG3_A phases=1 windings=2 bank=VREG3 buses=(regxfmr_190-8581.1, 190-8581.1) conns=(wye, wye) kvs=(7.2, 7.2) kvas=(10000, 10000) xhl=0.1 %loadloss=.01 Wdg=2 Maxtap=1.1 Mintap=0.9 ppm=0 21 | New Transformer.VREG3_B phases=1 windings=2 bank=VREG3 buses=(regxfmr_190-8581.2, 190-8581.2) conns=(wye, wye) kvs=(7.2, 7.2) kvas=(10000, 10000) xhl=0.1 %loadloss=.01 Wdg=2 Maxtap=1.1 Mintap=0.9 ppm=0 22 | New Transformer.VREG3_C phases=1 windings=2 bank=VREG3 buses=(regxfmr_190-8581.3, 190-8581.3) conns=(wye, wye) kvs=(7.2, 7.2) kvas=(10000, 10000) xhl=0.1 %loadloss=.01 Wdg=2 Maxtap=1.1 Mintap=0.9 ppm=0 23 | 24 | New RegControl.VREG3_A transformer=VREG3_A winding=2 vreg=125 ptratio=60 band=2 25 | New RegControl.VREG3_B transformer=VREG3_B winding=2 vreg=125 ptratio=60 band=2 26 | New RegControl.VREG3_C transformer=VREG3_C winding=2 vreg=125 ptratio=60 band=2 27 | 28 | ! Regulator No. 4 29 | 30 | New Transformer.VREG4_A phases=1 windings=2 bank=VREG4 buses=(regxfmr_190-7361.1, 190-7361.1) conns=(wye, wye) kvs=(7.2, 7.2) kvas=(10000, 10000) xhl=0.1 %loadloss=.01 Wdg=2 Maxtap=1.1 Mintap=0.9 ppm=0 31 | New Transformer.VREG4_B phases=1 windings=2 bank=VREG4 buses=(regxfmr_190-7361.2, 190-7361.2) conns=(wye, wye) kvs=(7.2, 7.2) kvas=(10000, 10000) xhl=0.1 %loadloss=.01 Wdg=2 Maxtap=1.1 Mintap=0.9 ppm=0 32 | New Transformer.VREG4_C phases=1 windings=2 bank=VREG4 buses=(regxfmr_190-7361.3, 190-7361.3) conns=(wye, wye) kvs=(7.2, 7.2) kvas=(10000, 10000) xhl=0.1 %loadloss=.01 Wdg=2 Maxtap=1.1 Mintap=0.9 ppm=0 33 | 34 | New RegControl.VREG4_A transformer=VREG4_A winding=2 vreg=125 ptratio=60 band=2 35 | New RegControl.VREG4_B transformer=VREG4_B winding=2 vreg=125 ptratio=60 band=2 36 | New RegControl.VREG4_C transformer=VREG4_C winding=2 vreg=125 ptratio=60 band=2 37 | -------------------------------------------------------------------------------- /tests/readers/opendss/Loads/test_load_p_and_q.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | test_load_p_and_q.py 5 | ---------------------------------- 6 | 7 | Tests for parsing P and Q values of Loads from OpenDSS into DiTTo. 8 | """ 9 | 10 | import os 11 | import math 12 | import pytest 13 | import numpy as np 14 | 15 | from ditto.store import Store 16 | from ditto.readers.opendss.read import Reader 17 | 18 | current_directory = os.path.realpath(os.path.dirname(__file__)) 19 | 20 | 21 | def test_load_p_and_q(): 22 | m = Store() 23 | r = Reader(master_file=os.path.join(current_directory, "test_load_p_and_q.dss")) 24 | r.parse(m) 25 | m.set_names() 26 | 27 | # P and Q values should be equally divided accross phase loads 28 | # Here we sum P and Q and check that the obtained values match the values in the DSS file 29 | # 30 | precision = 0.001 31 | assert len(m["load_load1"].phase_loads) == 3 # Load1 is a three phase load 32 | assert sum( 33 | [phase_load.p for phase_load in m["load_load1"].phase_loads] 34 | ) == pytest.approx(5400 * 10 ** 3, precision) 35 | assert sum( 36 | [phase_load.q for phase_load in m["load_load1"].phase_loads] 37 | ) == pytest.approx(4285 * 10 ** 3, precision) 38 | 39 | assert len(m["load_load2"].phase_loads) == 3 # Load2 is a three phase load 40 | assert sum( 41 | [phase_load.p for phase_load in m["load_load2"].phase_loads] 42 | ) == pytest.approx(3466 * 10 ** 3, precision) 43 | assert sum( 44 | [phase_load.q for phase_load in m["load_load2"].phase_loads] 45 | ) == pytest.approx(3466.0 * math.sqrt(1.0 / 0.9 ** 2 - 1) * 10 ** 3, precision) 46 | 47 | assert len(m["load_load3"].phase_loads) == 2 # Load3 is a two phase load 48 | assert sum( 49 | [phase_load.p for phase_load in m["load_load3"].phase_loads] 50 | ) == pytest.approx(1600 * 10 ** 3, precision) 51 | assert sum( 52 | [phase_load.q for phase_load in m["load_load3"].phase_loads] 53 | ) == pytest.approx(980 * 10 ** 3, precision) 54 | 55 | assert len(m["load_load4"].phase_loads) == 2 # Load4 is a two phase load 56 | assert sum( 57 | [phase_load.p for phase_load in m["load_load4"].phase_loads] 58 | ) == pytest.approx(1555 * 10 ** 3, precision) 59 | assert sum( 60 | [phase_load.q for phase_load in m["load_load4"].phase_loads] 61 | ) == pytest.approx(1555.0 * math.sqrt(1.0 / 0.95 ** 2 - 1) * 10 ** 3, precision) 62 | 63 | assert len(m["load_load5"].phase_loads) == 1 # Load5 is a one phase load 64 | assert sum( 65 | [phase_load.p for phase_load in m["load_load5"].phase_loads] 66 | ) == pytest.approx(650 * 10 ** 3, precision) 67 | assert sum( 68 | [phase_load.q for phase_load in m["load_load5"].phase_loads] 69 | ) == pytest.approx(500.5 * 10 ** 3, precision) 70 | 71 | assert len(m["load_load6"].phase_loads) == 1 # Load6 is a one phase load 72 | assert sum( 73 | [phase_load.p for phase_load in m["load_load6"].phase_loads] 74 | ) == pytest.approx(623.21 * 10 ** 3, precision) 75 | assert sum( 76 | [phase_load.q for phase_load in m["load_load6"].phase_loads] 77 | ) == pytest.approx(623.21 * math.sqrt(1.0 / 0.85 ** 2 - 1) * 10 ** 3, precision) 78 | --------------------------------------------------------------------------------