def test_cyme_to_opendss():
    '''
        Test the Cyme to OpenDSS conversion.
    '''
    list_of_directories = []
    from ditto.store import Store
    from ditto.readers.cyme.read import Reader
    from ditto.writers.opendss.write import Writer
    import opendssdirect as dss
    cyme_models = [
        f for f in os.listdir(
            os.path.join(current_directory, 'data/small_cases/cyme/'))
        if not f.startswith('.')
    ]
    for model in cyme_models:
        print(model)
        m = Store()
        r = Reader(data_folder_path=os.path.join(
            current_directory, 'data/small_cases/cyme', model))
        r.parse(m)
        #TODO: Log properly
        # print('>Cyme model {model} read...'.format(model=model))
        output_path = tempfile.TemporaryDirectory()
        list_of_directories.append(output_path)
        w = Writer(output_path=output_path.name)
        w.write(m)
        #TODO: Log properly
        # print('>...and written to OpenDSS.\n')
        print(model)
        dss.run_command("clear")
def test_cyme_to_ephasor():
    '''
        Test the Cyme to Ephasor conversion.
    '''
    from ditto.store import Store
    from ditto.readers.cyme.read import Reader
    from ditto.writers.ephasor.write import Writer

    cyme_models = [
        f for f in os.listdir(
            os.path.join(current_directory, 'data/small_cases/cyme/'))
        if not f.startswith('.')
    ]
    for model in cyme_models:
        m = Store()
        r = Reader(data_folder_path=os.path.join(
            current_directory, 'data/small_cases/cyme', model))
        r.parse(m)
        #TODO: Log properly
        print('>Cyme model {model} red...'.format(model=model))
        t = tempfile.TemporaryDirectory()
        w = Writer(output_path=t.name)
        w.write(m)
        #TODO: Log properly
        print('>...and written to Ephasor.\n')
    def apply(cls, stack, *args, **kwargs):

        if 'base_dir' in kwargs:
            base_dir = kwargs['base_dir']
        else:
            base_dir = './'

        #if not os.path.exists(os.path.join(base_dir,cyme_location)):
        #    raise ValueError("No folder exists at {}".format(os.path.join(base_dir,cyme_location)))

        if 'network_filename' in kwargs:
            network_filename = kwargs['network_filename']
        else:
            network_filename = 'network.txt' #Default

        if 'equipment_filename' in kwargs:
            equipment_filename = kwargs['equipment_filename']
        else:
            equipment_filename = 'equipment.txt' #Default

        if 'load_filename' in kwargs:
            load_filename = kwargs['load_filename']
        else:
            load_filename = 'load.txt' #Default

        base_model = Store()
        reader = CymeReader(data_folder_path=base_dir,
                            network_filename=network_filename,
                            equipment_filename=equipment_filename,
                            load_filename=load_filename)
        reader.parse(base_model)
        base_model.set_names()
        stack.model = base_model
        return True
def test_transformer_connection_mapping(cyme_value, winding, expected):
    reader = Reader()
    actual = reader.transformer_connection_configuration_mapping(
        cyme_value, winding)
    assert actual == expected

    # Function also takes strings
    actual = reader.transformer_connection_configuration_mapping(
        str(cyme_value), winding)
    assert actual == expected
Example #5
0
def main():
    '''
    Conversion example of the IEEE 123 node system.
    CYME ---> OpenDSS example.
    This example uses the glm file located in tests/data/big_cases/cyme/ieee_123node
    '''

    #Path settings (assuming it is run from examples folder)
    #Change this if you wish to use another system
    path = '../tests/data/big_cases/cyme/ieee_123node'

    ############################
    #  STEP 1: READ FROM CYME  #
    ############################
    #
    #Create a Store object
    print('>>> Creating empty model...')
    model = Store()

    #Instanciate a Reader object
    r = Reader(data_folder_path=path)

    #Parse (can take some time for large systems...)
    print('>>> Reading from CYME...')
    start = time.time()  #basic timer
    r.parse(model)
    end = time.time()
    print('...Done (in {} seconds'.format(end - start))

    ##############################
    #  STEP 2: WRITE TO OpenDSS  #
    ##############################
    #
    #Instanciate a Writer object
    w = Writer(output_path='./')

    #Write to OpenDSS (can also take some time for large systems...)
    print('>>> Writing to OpenDSS...')
    start = time.time()  #basic timer
    w.write(model)
    end = time.time()
    print('...Done (in {} seconds'.format(end - start))
Example #6
0
def test_load_value_mapping(load_type, v1, v2, expected):
    reader = Reader()
    actual = reader.load_value_type_mapping(load_type, v1, v2)
    assert actual == expected
Example #7
0
def test_phase_mapping(cyme_value, expected):
    reader = Reader()
    actual = reader.phase_mapping(cyme_value)
    assert actual == expected
def test_cap_connection_mapping_invalid_type():
    reader = Reader()

    with pytest.raises(ValueError):
        reader.capacitors_connection_mapping(0.0)
def test_cap_connection_mapping(cyme_value, expected):
    reader = Reader()
    actual = reader.capacitors_connection_mapping(cyme_value)
    assert actual == expected
Example #10
0
def test_load_value_mapping_amp_pf_not_impl(load_type):
    reader = Reader()

    with pytest.raises(NotImplementedError):
        reader.load_value_type_mapping(load_type, 100, 0.9)
Example #11
0
def test_load_value_mapping_invalid_value(v1, v2):
    reader = Reader()

    with pytest.raises(ValueError):
        reader.load_value_type_mapping(0, v1, v2)
Example #12
0
def test_load_value_mapping_invalid_type():
    reader = Reader()

    with pytest.raises(ValueError):
        reader.load_value_type_mapping(0.0, 100, 10)
Example #13
0
def test_breakers():
    """
    Tests the breakers parsing.
    """
    from ditto.readers.cyme.read import Reader
    from ditto.store import Store
    from ditto.models.line import Line

    m = Store()
    r = Reader(data_folder_path=os.path.join(
        current_directory, "data", "ditto-validation", "cyme", "breakers"))
    r.parse(m)
    for obj in m.models:
        if isinstance(obj, Line):
            # Section A is ABC and the breaker is closed on ABC
            if obj.name == "a":
                # The line should be a breaker...
                assert obj.is_breaker
                # ...and it should have 4 wires: A, B, C, and N
                assert len(obj.wires) == 4

                for wire in obj.wires:
                    # All wires should be flaged as breaker
                    assert wire.is_breaker

                    # All wires should be closed
                    assert not wire.is_open

                    # The breaker ampacity should be 100 amps for all wires
                    assert wire.ampacity == 100.0

                    # The breaker limit should be 600 amps for all wires
                    assert wire.interrupting_rating == 600.0

            # Section B is AC and the breaker is closed on C only
            elif obj.name == "b":
                # The line should be a breaker...
                assert obj.is_breaker
                # ...and it should have 3 wires: A, C, and N
                assert len(obj.wires) == 3

                for wire in obj.wires:
                    # Check phase is valid
                    assert wire.phase in ["A", "C", "N"]

                    # All wires should be flaged as a breaker
                    assert wire.is_breaker

                    # Wires C and N should be closed
                    if wire.phase in ["C", "N"]:
                        assert not wire.is_open
                    # And should be open on phase A
                    else:
                        assert wire.is_open

                    # The breaker ampacity should be 300 amps for all wires
                    assert wire.ampacity == 300.0

                    # The breaker limit should be 800 amps for all wires
                    assert wire.interrupting_rating == 800.0

            else:
                raise ValueError(
                    "Unknown line name {name}".format(name=obj.name))
def test_transformer_connection_mapping_invalid_winding():
    reader = Reader()

    with pytest.raises(ValueError):
        reader.transformer_connection_configuration_mapping(0.0, 2)
    def apply(cls,
              stack,
              model,
              feeder_file,
              output_substation_folder,
              base_dir=None,
              readme_file=None,
              substation_folder=None):
        # Format is max number of feeders, list of substation numbers
        subs_4kv = [(4, [5]), (8, [13]), (12, [14])]
        subs_25kv = [(6, [11]), (12, [15])]
        subs_1247kv = [(1, [1]), (2, [8]), (3, [2]), (4, [4, 10]), (6, [9]),
                       (8, [3, 7]), (12, [6]), (16, [12])]
        sub_list = None
        logger.debug("Starting add_cyme_substations")
        if base_dir and (not os.path.exists(feeder_file)):
            feeder_file = os.path.join(base_dir, feeder_file)

        if substation_folder == None:
            substation_folder = os.path.join(os.path.dirname(__file__),
                                             'resources')
        elif base_dir and (not os.path.exists(output_substation_folder)):
            output_substation_folder = os.path.join(base_dir,
                                                    output_substation_folder)

        transformer_config = 'Y'  #Default is Delta-Wye. If specified we set it to be wye-wye
        if readme_file is not None and os.path.exists(readme_file):
            f = open(readme_file, 'r')
            info = f.readline().split()
            suffix = info[1][:-2]
            config = info[5]
            if config == 'delta-delta':
                transformer_config = 'D'
            kv = int(float(suffix) * 1000)
            if suffix == '12.47':
                suffix = '1247'
            suffix = '_' + suffix
            if kv == 4000:
                sub_list = subs_4kv
            elif kv == 25000:
                sub_list = subs_25kv
            else:
                sub_list = subs_1247kv
        else:
            kv = 12470
            suffix = '_1247'
            sub_list = subs_1247kv

        from_cyme_layer_dir = None
        # first look in stack
        for layer in stack:
            if layer.name == 'From CYME':
                from_cyme_layer_dir = layer.layer_dir
                break
        # then try this layer's library directory
        if from_cyme_layer_dir is None:
            from_cyme_layer_dir = os.path.join(
                os.path.dirname(os.path.dirname(__file__)), 'from_cyme')
        if not os.path.exists(from_cyme_layer_dir):
            msg = "Cannot find the 'From CYME' layer."
            logger.error(msg)
            raise Exception(msg)

        logger.debug("Building the model network")

        srcs = []
        for obj in model.models:
            if isinstance(obj, PowerSource) and obj.is_sourcebus == 1:
                srcs.append(obj.name)
        srcs = np.unique(srcs)
        if len(srcs) == 0:
            raise ValueError('No PowerSource object found in the model.')
        elif len(srcs) > 1:
            raise ValueError(
                'Mupltiple sourcebus found: {srcs}'.format(srcs=srcs))
        else:
            source = srcs[0]
        logger.debug("Identifying the Source Bus as {src}".format(src=source))
        ''' 
           Substation nodes have the form ***_1247. (or _4 or _25 for 4kv and 25kv systems) 
           The boundary of the substation is ***_69 on the high side.
           The boundary of the substation in a node with no x on the low side
           A feeder defines these boundaries.
           e.g. IHS0_1247->IDT706 is a substation of IHS0_1247 with a boundary of IDT706
           We remove everything between the high and low boundaries when updating a substation
        '''

        model.build_networkx(
            source)  # Used to remove internal edges in substation
        df = pd.read_csv(
            feeder_file, ' '
        )  #The RNM file specifying which feeders belong to which substation
        substation_boundaries_low = {}
        substation_boundaries_high = {}
        substation_transformers = {}
        substation_names = {}
        for index, row in df.iterrows():
            substation = row.iloc[1].lower()
            feeder = row.iloc[2].lower()
            transformer = row.iloc[3].lower()
            transformer = 'tr' + transformer[
                6:]  #The prefix is slightly different with the feeder.txt file
            buses = feeder.split('->')
            bus2 = buses[1]
            bus1 = buses[0]
            if substation in substation_boundaries_low:
                substation_boundaries_low[substation].add(
                    bus2 + '-' + bus1 + 'x'
                )  #Set the first node to be the location with one x sign as sometimes there isn't one with xx
                substation_names[bus2 + '-' + bus1 + 'x'] = substation
            else:
                substation_boundaries_low[substation] = set(
                    [bus2 + '-' + bus1 + 'x'])
                substation_names[bus2 + '-' + bus1 + 'x'] = substation

            if substation not in substation_boundaries_high:
                substation_boundaries_high[substation] = set([
                    substation.replace(suffix, '_69')
                ])  # All substations are from 69kV

            if substation not in substation_transformers:
                substation_transformers[substation] = set([transformer])

        print(substation_names)
        for sub in substation_boundaries_low.keys(
        ):  #sub is the name of the substation and substation_boundaries_low[sub] is a set of all the connected feeders
            logger.debug("Building to_delete and modifier")
            to_delete = Store()
            modifier = Modifier()
            logger.info(
                "Processing substation {}. There are {} in total.".format(
                    sub, len(substation_boundaries_low)))

            all_boundaries = substation_boundaries_low[sub].union(
                substation_boundaries_high[sub])

            # TODO: do a search from the high boundary to the low boundary and include everything inside
            #get_internal_nodes(substation_boundaries_high[sub],substation_boundaries_low[sub])

            feeder_names = list(
                substation_boundaries_low[sub]
            )  # Feeder point of connection to the substation
            internal_nodes = [
                i for i in model.model_names
                if sub in i and isinstance(model[i], Node)
            ]
            internal_edges = [
                i for i in model.model_names
                if sub in i and (isinstance(model[i], Line)
                                 or isinstance(model[i], PowerTransformer))
            ]
            tmp_include_edges = []
            for i in internal_edges:
                if model[i].from_element in feeder_names:
                    internal_nodes.append(model[i].from_element)
                if model[i].to_element in feeder_names:
                    internal_nodes.append(model[i].to_element)
                if 'mv' in model[i].to_element and 'mv' in model[
                        i].from_element:
                    tmp_include_edges.append(
                        i
                    )  #Used to address MV loads attached directly to the substation
            for i in tmp_include_edges:
                internal_edges.remove(i)

            high_boundary = list(substation_boundaries_high[sub])[
                0]  #Should only be one high side boundary point in the set
            internal_nodes.append(high_boundary)
            """
            #import pdb;pdb.set_trace()
            all_nodes.append(sub+'-'+high_boundary+'xxx')
            all_nodes.append(sub+'-'+high_boundary+'xx')
            all_nodes.append(sub+'-'+high_boundary+'x')
            all_nodes.append(sub)
            for low_val in substation_boundaries_low[sub]:
                all_nodes.append(low_val)  # End at these nodes. No reclosers after these.
                last_node = low_val+'-'+sub+'x'
                last_node_rev = sub+'-'+low_val+'x'
                if last_node in model.model_names:
                    all_nodes.append(last_node) #The connection point
                if last_node_rev in model.model_names:
                    all_nodes.append(last_node_rev) #The connection point if the name is backwards. Dypically with UMV nodes
                if last_node+'x' in model.model_names:
                    all_nodes.append(last_node+'x') #This extra node often exists 
                if last_node+'_p' in model.model_names:
                    all_nodes.append(last_node+'_p') #This extra node sometimes exists 
                if last_node+'xx' in model.model_names:
                    all_nodes.append(last_node+'xx') #This extra node sometimes exists too
            """
            all_nodes_set = set(internal_nodes)
            #internal_edges = model.get_internal_edges(all_nodes_set)
            #import pdb;pdb.set_trace()

            # These attribuetes will be used to inform which substation is selected
            rated_power = None
            emergency_power = None
            loadloss = None
            noloadloss = None
            reactance = None
            lat = None
            long = None
            transformer = list(substation_transformers[sub])[
                0]  # Assume only one transformer per substation in RNM

            # Currently using CYME reader which has different names for the transformers to the feeders.txt file
            #            reactance = model[transformer].reactances[0] # Assume the same for all windings in a substation
            #            loadloss = model[transformer].loadloss
            #            noloadloss = model[transformer].noload_loss
            #            rated_power = model[transformer].windings[0].rated_power # Assume the same for all windings in a substation
            #            emergency_power = model[transformer].windings[0].emergency_power # Assume the same for all windings in a substation
            try:
                lat = model[sub].positions[0].lat
                long = model[sub].positions[0].long
            except:
                raise ValueError('{} missing position elements'.format(
                    model[sub].name))

        # import pdb;pdb.set_trace()
        # Mark the internals of the substation for deletion
            for n in all_nodes_set:
                if not n in model.model_names:
                    continue
#                is_endpoint = False
#                for key in substation_boundaries_low:
#                    if n in substation_boundaries_low[key]: #Don't delete the boundaries of the substation
#                        is_endpoint = True
#                        break
#                if is_endpoint:
#                    continue
                obj_name = type(model[n]).__name__
                base_obj = globals()[obj_name](to_delete)
                base_obj.name = n
            for e in internal_edges:
                if not e in model.model_names:
                    continue
                if model[
                        e].from_element in feeder_names:  #Don't remove edge from bus2-bus1-x to the first distribution transformer
                    continue
                obj_name = type(model[e]).__name__
                base_obj = globals()[obj_name](to_delete)
                base_obj.name = e

            num_model_feeders = len(feeder_names)
            not_allocated = True

            low_voltage = kv
            high_voltage = 69000

            #import pdb;pdb.set_trace()
            # Read the CYME models
            random.seed(0)
            for element in sub_list:  # Insert extra logic here to determine which one to use
                if num_model_feeders > element[0]:
                    continue

                sub_file = 'sb' + str(element[1][random.randint(
                    0,
                    len(element[1]) - 1)])
                print(sub_file)
                sub_model = Store()
                reader = CymeReader(data_folder_path=os.path.join(
                    os.path.dirname(__file__), 'resources', sub_file))
                reader.parse(sub_model)
                sub_model.set_names()

                # Set the nominal voltages within the substation using the cyme model transformer and source voltage
                modifier = system_structure_modifier(sub_model)
                modifier.set_nominal_voltages_recur()

                # Determine which nodes in the CYME model connect feeders/HV
                all_substation_connections = []
                available_feeders = 0
                for i in sub_model.models:
                    if isinstance(i, Node) and hasattr(
                            i, 'is_substation_connection'
                    ) and i.is_substation_connection == 1:
                        all_substation_connections.append(i)
                        i.setpoint = 1.03
                        try:
                            if i.nominal_voltage == low_voltage:
                                available_feeders += 1
                        except:
                            raise ValueError(
                                "Nominal Voltages not set correctly in substation"
                            )

                logger.info(
                    "Processing substation {}. There are {} feeders available and {} feeders in RNM"
                    .format(sub, available_feeders, num_model_feeders))
                if available_feeders >= num_model_feeders:
                    boundry_map = {}
                    feeder_cnt = 0
                    ref_lat = 0
                    ref_long = 0
                    for i in sub_model.models:
                        # Remove the source node. This is no longer needed
                        if isinstance(i, PowerSource):
                            sub_model.remove_element(i)

                        # Set a random node as the reference node
                        if isinstance(i, Node) and hasattr(
                                i, 'positions'
                        ) and len(i.positions) > 0 and hasattr(
                                i.positions[0], 'lat'
                        ) and i.positions[0].lat is not None and hasattr(
                                i.positions[0],
                                'long') and i.positions[0].long is not None:
                            ref_lat = i.positions[0].lat
                            ref_long = i.positions[0].long

                    substation_name = substation_names[feeder_names[0].lower()]
                    for i in sub_model.models:

                        # Remove feeder name from substation elements. This is normally set automatically when reading from CYME
                        if hasattr(i, 'feeder_name'):
                            i.feeder_name = ''
                        if hasattr(i, 'substation_name'):
                            i.substation_name = substation_name
                        if hasattr(i, 'is_substation'):
                            i.is_substation = True

                        if isinstance(
                                i, PowerTransformer
                        ) and transformer_config == 'D' and i.windings is not None and len(
                                i.windings) == 2 and i.windings[1] is not None:
                            i.windings[1].connection_type = 'D'

                        # Assign feeder names to the endpoints of the substation
                        if isinstance(i, Node) and hasattr(
                                i, 'is_substation_connection'
                        ) and i.is_substation_connection == 1:
                            if hasattr(
                                    i, 'nominal_voltage'
                            ) and i.nominal_voltage is not None and i.nominal_voltage >= high_voltage:

                                #########  USE no_feeders.txt to inform how many connection points there should be. Skip this substation if the number isn't correct-->>>>
                                #### Need to discuss with Carlos
                                boundry_map[i.name] = high_boundary
                                i.setpoint = 1.0
                                i.name = high_boundary  #TODO: issue of multiple high voltage inputs needs to be addressed
                                #i.feeder_name = 'subtransmission'
                                i.substation_name = substation_name
                                i.is_substation = False
                            elif hasattr(
                                    i, 'nominal_voltage'
                            ) and i.nominal_voltage is not None and i.nominal_voltage < high_voltage:
                                feeder_cnt += 1
                                if feeder_cnt <= num_model_feeders:
                                    endpoint = feeder_names[feeder_cnt -
                                                            1].split('-')[0]
                                    if 'mv' in endpoint:  #The node names for these are reversed for some reason
                                        boundry_map[
                                            i.
                                            name] = substation_name + '-' + endpoint + 'x'
                                        i.name = substation_name + '-' + endpoint + 'x'
                                        if i.name in all_nodes_set:
                                            all_nodes_set.remove(i.name)
                                    else:
                                        boundry_map[i.name] = feeder_names[
                                            feeder_cnt - 1]
                                        i.name = feeder_names[feeder_cnt -
                                                              1].lower()
                                    i.substation_name = substation_name
                                    i.is_substation = False
                                    i.feeder_name = i.substation_name + '->' + endpoint
                                    #import pdb;pdb.set_trace()
                                    if i.substation_name + '->' + endpoint in model.model_names:  # Set the Feedermetadata headnode to be the correct name.
                                        model[i.substation_name + '->' +
                                              endpoint].headnode = i.name
                                else:
                                    i.name = str(
                                        sub_file + '_' + sub + '_' + i.name
                                    ).lower(
                                    )  #ie. No feeders assigned to this berth so using the substation identifiers
                            else:
                                raise ValueError(
                                    "Nominal Voltages not set correctly in substation"
                                )

                        elif hasattr(i, 'name') and (not isinstance(
                                i, Feeder_metadata)):
                            i.name = str(sub_file + '_' + sub + '_' +
                                         i.name).lower()
                        if isinstance(i, Regulator) and hasattr(
                                i, 'connected_transformer'
                        ) and i.connected_transformer is not None:
                            i.connected_transformer = str(
                                sub_file + '_' + sub + '_' +
                                i.connected_transformer).lower()

                        if hasattr(
                                i,
                                'from_element') and i.from_element is not None:
                            if i.from_element in boundry_map:
                                i.from_element = boundry_map[i.from_element]
                            else:
                                i.from_element = str(sub_file + '_' + sub +
                                                     '_' +
                                                     i.from_element).lower()
                        if hasattr(i,
                                   'to_element') and i.to_element is not None:
                            if i.to_element in boundry_map:
                                i.to_element = boundry_map[i.to_element]
                            else:
                                i.to_element = str(sub_file + '_' + sub + '_' +
                                                   i.to_element).lower()

                        if hasattr(
                                i, 'positions'
                        ) and i.positions is not None and len(i.positions) > 0:
                            # import pdb;pdb.set_trace()
                            if ref_long == 0 and ref_lat == 0:
                                logger.warning(
                                    "Warning: Reference co-ords are (0,0)")
                            scale_factor = 1
                            if element[
                                    0] >= 12:  # The larger substations were created with a strange scale factor
                                scale_factor = 1 / 50.0
                            i.positions[0].lat = scale_factor * 7 * (
                                i.positions[0].lat - ref_lat) + lat
                            i.positions[0].long = scale_factor * 10 * (
                                i.positions[0].long - ref_long) + long
                            if len(i.positions) > 1:
                                for k in range(1, len(i.positions)):
                                    i.positions[k].lat = scale_factor * 7 * (
                                        i.positions[k].lat - ref_lat) + lat
                                    i.positions[k].long = scale_factor * 10 * (
                                        i.positions[k].long - ref_long) + long


#import pdb;pdb.set_trace()
                    not_allocated = False
                    sub_model.set_names()
                    to_delete.set_names()
                    reduced_model = modifier.delete(model, to_delete)
                    logger.info("Adding model from {} to model".format(
                        substation_folder))
                    #import pdb;pdb.set_trace()
                    model = modifier.add(
                        reduced_model, sub_model
                    )  #Is it a problem to be modifying the model directly?
                    break
            if not_allocated:
                raise ValueError(
                    'Substation too small. {num} feeders needed.  Exiting...'.
                    format(num=num_model_feeders))

        model.set_names()
        logger.debug("Returning {!r}".format(model))
        return model
Example #16
0
def test_unmapped_inputs_to_connection_configuration_mapping(value):
    with pytest.raises(NotImplementedError):
        Reader().connection_configuration_mapping(value)
Example #17
0
def test_connection_configuration_mapping(value, expected_configuration):
    configuration = Reader().connection_configuration_mapping(value)
    assert configuration == expected_configuration