Esempio n. 1
0
def get_r(pre_event_volts, Y, branch, unspecified_inds, powerflowpreVY):
    """
    Compute r (reduced version) as described in Equations __ and (8) of the paper.
    """
    postY = Y.copy()
    PowerNetwork.remove_line_from_Y(postY, branch)
    r = powerflow(pre_event_volts, postY) - powerflowpreVY
    r = r[unspecified_inds]
    return r
Esempio n. 2
0
def single_FLiER_test(power_network, pmus, noise, branch_to_fail, use_filter=True,
                      pre_event_volts = None, FLiER_type = "Jacobian", 
                      verbose=False):
    """
    Run a FLiER test for a specific line failure.
    
    Inputs:
    power_network - The power network in which the test will occur.
    pmus - A list of bus indices with PMUs.
    line_to_fail - The transmission line that will fail.
    
    Outputs:
    score_dict - A dictionary with keys that are indices into the 'edges' list
       and values that are tij's. The "no line failure" case has
       key -1.
    ts_computed - The number of tij's that were computed (lines that got past 
       the filter).
    """
    powernet = power_network.copy()
    Y = powernet.Y.copy()
    
    try:
        if pre_event_volts is None:
            V = powernet.simulate()
            pre_event_volts = np.hstack([np.angle(V), np.abs(V)])
                
        powernet.remove_branch(branch_to_fail)
    
        V = powernet.simulate()
        post_event_volts = np.hstack([np.angle(V), np.abs(V)])
        
    except RuntimeError as e:
        # If the solver for the power flow equations fails to converge.
        if verbose:
            print str(e)
        return False, False, False
        
    if noise > 0:
        post_event_volts += np.random.normal(0, noise, len(post_event_volts))
    
    # Get the matrix that projects a voltage vector onto the subspace
    # rendered observable by the PMUs.
    E = PowerNetwork.get_observable_projection_matrix(Y, pmus)
    
    # Run FLiER
    if FLiER_type == "Jacobian":
        res = FLiER(Y, powernet.branches, pre_event_volts, post_event_volts, 
                    E, powernet.buses, powernet.slack_inds, 
                    use_filter=use_filter, verbose=verbose)
    elif FLiER_type == "DC_Approximation":
        res = DC_FLiER(powernet.dc_mat, powernet.branches, pre_event_volts, 
                       post_event_volts, E, powernet.slack_inds, verbose=verbose)
    else:
        assert False
    score_dict, initial_score_dict, fraction_ts_computed = res

    return score_dict, initial_score_dict, fraction_ts_computed
Esempio n. 3
0
def split_bus(powernet, sub, splitting_nodes):
    """Create a new Power_Network with a split substation.

    Inputs:
    powernet (Power_Network) - The original power network.
    sub (Ring_Substation) - The substation that will split.
    splitting_nodes (List of ints) - The substation nodes that will split from
       the substation.

    Output:
    Power_Network. The new power network that results from the substation 
    splitting.
    """
    new_buses = {bus.ID: bus.copy() for bus in powernet.buses}
    newbusID = max([bus.ID for bus in powernet.buses]) + 1
    newbusind = len(powernet.buses)
    new_bus = Bus(newbusind, newbusID, voltage=sub.voltage)
    new_buses[newbusID] = new_bus
    old_bus = new_buses[sub.ID]
    for node in splitting_nodes:
        new_bus.load += sub.nodes[node].load
        old_bus.load -= sub.nodes[node].load

        new_bus.generation += sub.nodes[node].generation
        old_bus.generation -= sub.nodes[node].generation

        new_bus.shunt_adm += sub.nodes[node].shunt_adm
        old_bus.shunt_adm -= sub.nodes[node].shunt_adm

    splitting_branch_IDs = [
        sub.branchID_at_node(sn) for sn in splitting_nodes
        if sub.branchID_at_node(sn) is not None
    ]
    new_branches = {
        branch.ID: branch.copy()
        for branch in powernet.branches.values()
    }
    for branch_ID in splitting_branch_IDs:
        branch = new_branches[branch_ID]
        assert len(branch.buses) == 2
        if branch.buses[0].ID == old_bus.ID:
            branch.buses = (new_bus, branch.buses[1])
        elif branch.buses[1].ID == old_bus.ID:
            branch.buses = (branch.buses[0], new_bus)
        else:
            assert False

    return PowerNetwork(new_buses, new_branches)
Esempio n. 4
0
def main(args):
    """Run a test of FLiER.

    Parameters are passed to the function in pairs, where args[i] is a flag
    and args[i+1] is a parameter value. Valid flag, value combinations are the 
    following:
    -network [filename]: A .cdf or .m file containing the network to be read in.
       A .cdf file is in the IEEE Common Data Format (see 
       https://www.ee.washington.edu/research/pstca/). A .m file is in MATPOWER
       format.
    -use_filter ["True", "False"]: Whether or not to use the FLiER filtering
       procedure. Default is True.
    -pmus [int,...,int]: A list of the indices of buses on which to place PMUs.
    -test_type ["Full", "Single_Lines"]: If "Full", perform a test of a 
       substation splitting into two nodes. If "Single_Lines", perform a line 
       failure test. Default is "Full".
    -test_scenarios [**]: A list of the scenarios to test.
    -noise [double]: Add noise to pre- and post-event voltage readings. Value 
       specifies standard deviation of noise. Default is 0.0.
    -write_file [filename]: Where to write the output data. Default is "out.txt".
    -verbose ["True", "False"]: Turn verbose output to the command-line on or 
       off. Default is "False".

    **test_scenarios format: A semicolon-separated list in which each item is a 
       scenario. Each item is a substation index, a comma, and then a 
       space-separated list of substation nodes that split from the substation.
       For example, "86,1 2 3;176,1 2;539,7;702,4 5".
    """
    print "Running FLiER test with the following options:"
    print "\n".join([
        "{0}: {1}".format(args[i], args[i + 1])
        for i in range(1, len(args), 2)
    ])
    out = read_inputs(args)
    (filename, pmus, write_filename, test_type, noise, test_scenarios,
     use_filter, verbose) = out

    if filename.endswith('.m'):
        powernet = PowerNetwork.powernet_from_matpower(filename)
    else:
        powernet = PowerNetwork.powernet_from_IEEECDF(filename)
    # Simulate the pre-event network voltages.
    V = powernet.simulate()
    pre_event_volts = np.hstack([np.angle(V), np.abs(V)])
    curr_FLiER = FLiER_Substation(powernet,
                                  pre_event_volts,
                                  pmus,
                                  verbose=verbose)

    solution_ranks = []
    filter_ranks = []
    num_tests = 0
    num_missed = 0
    file = open(write_filename, 'w')
    # Iterate over test cases. For each case, make the specified change to the
    # network and simulate the resulting voltages. Then run FLiER to attempt to
    # identify the change that occurred.
    for sub in curr_FLiER.substations:
        # For each substation, iterate over possible tests. The set of tests
        # returned by the iterator depends on test_type.
        for splitting_nodes in sub.node_iterator(test_type):
            key = (sub.index, tuple(splitting_nodes))
            if test_scenarios is not None and key not in test_scenarios:
                continue
            if verbose:
                if test_type == 'Full':
                    print "On bus {0}, splitting nbrs {1}".format(
                        sub.index, splitting_nodes)
                else:
                    bab = sub.bus_across_branch(splitting_nodes[0])
                    print "On line {0}".format((sub.index, bab.index))
            try:
                # Make change according to test scenario, simulate results.
                # Some changes result in simulation nonconvergence. In this
                # case, skip the scenario.
                post_event_volts = get_post_event_volts(
                    sub, splitting_nodes, powernet, curr_FLiER)
            except RuntimeError as e:
                print str(e)
                continue

            if noise > 0:
                # Add noise if called for.
                noisy_prev = pre_event_volts.copy()
                noisy_prev += np.random.normal(0, noise, len(noisy_prev))
                noisy_postv = post_event_volts.copy()
                noisy_postv += np.random.normal(0, noise, len(noisy_postv))
                noisy_FLiER = FLiER_Substation(powernet, noisy_prev, pmus)
                out = noisy_FLiER.find_topology_error(noisy_postv, test_type,
                                                      use_filter)
            else:
                # Run FLiER
                out = curr_FLiER.find_topology_error(post_event_volts,
                                                     test_type, use_filter)
            scores, filter_scores, fraction_ts_computed = out
            solkey = (sub.index, tuple(splitting_nodes))  # Solution key

            num_tests += 1
            if scores is False:
                continue
            if solkey not in scores:
                if verbose:
                    print "Solution not present"
                num_missed += 1
                score_of_solution = np.Inf
            else:
                score_of_solution = scores[solkey]
                rank = np.sum(scores.values() <= score_of_solution)
                buses_ordered = [
                    y[0][0] if y[0] != -1 else -1
                    for y in sorted(scores.items(), key=lambda x: x[1])
                ]
                # bus_rank is the rank of the correct answer if we only care about
                # getting the substation index correct and not the specific nodes
                # that split.
                bus_rank = buses_ordered.index(sub.index) + 1
            if score_of_solution == np.Inf:
                # Bit of a hack here. Occasionally the filter removes the
                # correct answer entirely from the list of possibilities. In
                # this case we just set the rank of the solution to something
                # large.
                rank = 300
                bus_rank = 300

            if verbose:
                print "Solution score: {0}".format(score_of_solution)
                print "Solution rank: {0}".format(rank)
                print "Correct bus rank: {0}".format(bus_rank)
                print "Number scores not filtered: {0}".format(len(scores))

            # Update scoring data structures. Write the results of this test to
            # the output file.
            solution_ranks.append(rank)
            sol_filter_score = filter_scores[solkey]
            filter_ranks.append(
                np.sum(filter_scores.values() <= sol_filter_score))
            write_to_file(file, solkey, out, curr_FLiER.substations)

    file.close()

    print num_tests
    print "{0} missed entirely".format(num_missed)
    sorted_solranklist, sorted_filtersolranklist = zip(
        *sorted(zip(solution_ranks, filter_ranks), key=lambda x: x[0]))
    print "Mean solution rank: {0}, Mean filtered solution rank: {1}".format(
        np.mean(sorted_solranklist), np.mean(sorted_filtersolranklist))
    print "Median sol rank: {0}, median filtered sol rank: {1}".format(
        np.median(sorted_solranklist), np.median(sorted_filtersolranklist))
    print "Frac correct: {0}, frac filtered correct: {1}".format(
        np.sum(np.array(sorted_solranklist) == 1) / float(num_tests),
        np.sum(np.array(sorted_filtersolranklist) == 1) / float(num_tests))
    print "Frac in top 3: {0}, frac filtered in top 3: {1}".format(
        np.sum(np.array(sorted_solranklist) <= 3) / float(num_tests),
        np.sum(np.array(sorted_filtersolranklist) <= 3) / float(num_tests))
Esempio n. 5
0
def single_FLiER_test(power_network,
                      pmus,
                      noise,
                      branch_to_fail,
                      use_filter=True,
                      pre_event_volts=None,
                      FLiER_type="Jacobian",
                      verbose=False):
    """
    Run a FLiER test for a specific line failure.
    
    Inputs:
    power_network - The power network in which the test will occur.
    pmus - A list of bus indices with PMUs.
    line_to_fail - The transmission line that will fail.
    
    Outputs:
    score_dict - A dictionary with keys that are indices into the 'edges' list
       and values that are tij's. The "no line failure" case has
       key -1.
    ts_computed - The number of tij's that were computed (lines that got past 
       the filter).
    """
    powernet = power_network.copy()
    Y = powernet.Y.copy()

    try:
        if pre_event_volts is None:
            V = powernet.simulate()
            pre_event_volts = np.hstack([np.angle(V), np.abs(V)])

        powernet.remove_branch(branch_to_fail)

        V = powernet.simulate()
        post_event_volts = np.hstack([np.angle(V), np.abs(V)])

    except RuntimeError as e:
        # If the solver for the power flow equations fails to converge.
        if verbose:
            print str(e)
        return False, False, False

    if noise > 0:
        post_event_volts += np.random.normal(0, noise, len(post_event_volts))

    # Get the matrix that projects a voltage vector onto the subspace
    # rendered observable by the PMUs.
    E = PowerNetwork.get_observable_projection_matrix(Y, pmus)

    # Run FLiER
    if FLiER_type == "Jacobian":
        res = FLiER(Y,
                    powernet.branches,
                    pre_event_volts,
                    post_event_volts,
                    E,
                    powernet.buses,
                    powernet.slack_inds,
                    use_filter=use_filter,
                    verbose=verbose)
    elif FLiER_type == "DC_Approximation":
        res = DC_FLiER(powernet.dc_mat,
                       powernet.branches,
                       pre_event_volts,
                       post_event_volts,
                       E,
                       powernet.slack_inds,
                       verbose=verbose)
    else:
        assert False
    score_dict, initial_score_dict, fraction_ts_computed = res

    return score_dict, initial_score_dict, fraction_ts_computed
Esempio n. 6
0
def network_FLiER_test(network_filename,
                       pmus,
                       noise,
                       use_filter=True,
                       test_branches=None,
                       FLiER_type="Jacobian",
                       verbose=False):
    """
    Simulate the failure of each line in the network one at a run, and run
    FLiER for each case.
    
    Inputs:
    network_filename - The path to the file containing the network in IEEE CDF
       format.
    pmus - A list of bus indices with PMUs on them.
    
    Outputs:
    
    """
    if network_filename.endswith('.m'):
        powernet = PowerNetwork.powernet_from_matpower(network_filename)
    else:
        powernet = PowerNetwork.powernet_from_IEEECDF(network_filename)
    num_lines = len(powernet.branches)
    solution_ranks = []  # Contains ranks of correct answers
    score_list = []  # Contains tij's
    fraction_ts_computed_list = []  # How many tij's computed per test.
    dict_of_score_dicts = dict()
    num_filtered_out = 0

    V = powernet.simulate()
    pre_event_volts = np.hstack([np.angle(V), np.abs(V)])

    if test_branches is None:
        test_branches = range(len(powernet.branches))
    for i in test_branches:
        branch = powernet.branches[i]
        if verbose:
            print "On line {0}".format([bus.index for bus in branch.buses])
        out = single_FLiER_test(powernet,
                                pmus,
                                noise,
                                branch_to_fail=branch,
                                use_filter=use_filter,
                                pre_event_volts=pre_event_volts,
                                FLiER_type=FLiER_type,
                                verbose=verbose)
        score_dict, initial_score_dict, fraction_ts_computed = out
        if score_dict == False:
            # then power flow equation solver did not converge
            continue

        fraction_ts_computed_list.append(fraction_ts_computed)
        if i in score_dict:
            score_of_solution = score_dict[i]
            rank = np.sum(score_dict.values() <= score_of_solution)
            solution_ranks.append(rank)
        else:
            # Then the correct line was filtered out.
            # Just choose the rank as last, and some large tij
            # filler value.
            if verbose:
                print "Solution filtered out."
            score_of_solution = 100
            rank = num_lines
            solution_ranks.append(rank)
            num_filtered_out += 1
        dict_of_score_dicts[i] = (score_dict, initial_score_dict,
                                  fraction_ts_computed)

        # Produce a list of (key, value) dictionary elements sorted by value.
        # Then split that list to create a sorted list of keys and a sorted list of
        # values. Recall that the keys are indices into the list of edges.
        branch_inds, scores = zip(
            *sorted(score_dict.iteritems(), key=lambda x: x[1]))

        if verbose:
            print score_of_solution, rank, len(score_dict)
        score_list.append((scores, score_of_solution))

    if verbose:
        print "{0} filtered out entirely.".format(num_filtered_out)
    return score_list, dict_of_score_dicts, powernet.branches, solution_ranks, fraction_ts_computed_list
def main(args):
    """Run a test of FLiER.

    Parameters are passed to the function in pairs, where args[i] is a flag
    and args[i+1] is a parameter value. Valid flag, value combinations are the 
    following:
    -network [filename]: A .cdf or .m file containing the network to be read in.
       A .cdf file is in the IEEE Common Data Format (see 
       https://www.ee.washington.edu/research/pstca/). A .m file is in MATPOWER
       format.
    -use_filter ["True", "False"]: Whether or not to use the FLiER filtering
       procedure. Default is True.
    -pmus [int,...,int]: A list of the indices of buses on which to place PMUs.
    -test_type ["Full", "Single_Lines"]: If "Full", perform a test of a 
       substation splitting into two nodes. If "Single_Lines", perform a line 
       failure test. Default is "Full".
    -test_scenarios [**]: A list of the scenarios to test.
    -noise [double]: Add noise to pre- and post-event voltage readings. Value 
       specifies standard deviation of noise. Default is 0.0.
    -write_file [filename]: Where to write the output data. Default is "out.txt".
    -verbose ["True", "False"]: Turn verbose output to the command-line on or 
       off. Default is "False".

    **test_scenarios format: A semicolon-separated list in which each item is a 
       scenario. Each item is a substation index, a comma, and then a 
       space-separated list of substation nodes that split from the substation.
       For example, "86,1 2 3;176,1 2;539,7;702,4 5".
    """
    print "Running FLiER test with the following options:"
    print "\n".join(["{0}: {1}".format(args[i], args[i+1]) for i in range(1, len(args), 2)])
    out = read_inputs(args)
    (filename, pmus, write_filename, test_type, 
             noise, test_scenarios, use_filter, verbose) = out

    if filename.endswith('.m'):
        powernet = PowerNetwork.powernet_from_matpower(filename)
    else:
        powernet = PowerNetwork.powernet_from_IEEECDF(filename)
    # Simulate the pre-event network voltages.
    V = powernet.simulate()
    pre_event_volts = np.hstack([np.angle(V), np.abs(V)])
    curr_FLiER = FLiER_Substation(powernet, pre_event_volts, pmus, verbose=verbose)
    
    solution_ranks = []
    filter_ranks = []
    num_tests = 0
    num_missed = 0
    file = open(write_filename, 'w')
    # Iterate over test cases. For each case, make the specified change to the
    # network and simulate the resulting voltages. Then run FLiER to attempt to
    # identify the change that occurred.
    for sub in curr_FLiER.substations:
        # For each substation, iterate over possible tests. The set of tests 
        # returned by the iterator depends on test_type.
        for splitting_nodes in sub.node_iterator(test_type):
            key = (sub.index, tuple(splitting_nodes))
            if test_scenarios is not None and key not in test_scenarios:
                continue
            if verbose:
                if test_type == 'Full':
                    print "On bus {0}, splitting nbrs {1}".format(sub.index, 
                                                                  splitting_nodes)
                else:
                    bab = sub.bus_across_branch(splitting_nodes[0])
                    print "On line {0}".format((sub.index, bab.index))
            try:
                # Make change according to test scenario, simulate results.
                # Some changes result in simulation nonconvergence. In this 
                # case, skip the scenario.
                post_event_volts = get_post_event_volts(sub, splitting_nodes, 
                                                        powernet, curr_FLiER)
            except RuntimeError as e:
                print str(e)
                continue
                
            if noise > 0:
                # Add noise if called for.
                noisy_prev = pre_event_volts.copy()
                noisy_prev += np.random.normal(0, noise, len(noisy_prev))
                noisy_postv = post_event_volts.copy()
                noisy_postv += np.random.normal(0, noise, len(noisy_postv))
                noisy_FLiER = FLiER_Substation(powernet, noisy_prev, pmus)
                out = noisy_FLiER.find_topology_error(noisy_postv, test_type,
                                                      use_filter)
            else:               
                # Run FLiER
                out = curr_FLiER.find_topology_error(post_event_volts, test_type,
                                                     use_filter)
            scores, filter_scores, fraction_ts_computed = out
            solkey = (sub.index, tuple(splitting_nodes)) # Solution key
            
            num_tests += 1
            if scores is False:
                continue
            if solkey not in scores:
                if verbose:
                    print "Solution not present"
                num_missed += 1
                score_of_solution = np.Inf
            else:
                score_of_solution = scores[solkey]
                rank = np.sum(scores.values() <= score_of_solution)
                buses_ordered = [y[0][0] if y[0] != -1 else -1 for y in 
                                 sorted(scores.items(), key = lambda x : x[1])]
                # bus_rank is the rank of the correct answer if we only care about
                # getting the substation index correct and not the specific nodes
                # that split.
                bus_rank = buses_ordered.index(sub.index) + 1
            if score_of_solution == np.Inf:
                # Bit of a hack here. Occasionally the filter removes the 
                # correct answer entirely from the list of possibilities. In 
                # this case we just set the rank of the solution to something 
                # large.
                rank = 300
                bus_rank = 300
                
            if verbose:
                print "Solution score: {0}".format(score_of_solution)
                print "Solution rank: {0}".format(rank)
                print "Correct bus rank: {0}".format(bus_rank)
                print "Number scores not filtered: {0}".format(len(scores))
            
            # Update scoring data structures. Write the results of this test to
            # the output file.
            solution_ranks.append(rank)
            sol_filter_score = filter_scores[solkey]
            filter_ranks.append(np.sum(filter_scores.values() <= sol_filter_score))
            write_to_file(file, solkey, out, curr_FLiER.substations)
            
    file.close()
    
    print num_tests
    print "{0} missed entirely".format(num_missed)
    sorted_solranklist, sorted_filtersolranklist = zip(*sorted(zip(solution_ranks, filter_ranks), key = lambda x : x[0]))
    print "Mean solution rank: {0}, Mean filtered solution rank: {1}".format(np.mean(sorted_solranklist), np.mean(sorted_filtersolranklist))
    print "Median sol rank: {0}, median filtered sol rank: {1}".format(np.median(sorted_solranklist), np.median(sorted_filtersolranklist))
    print "Frac correct: {0}, frac filtered correct: {1}".format(np.sum(np.array(sorted_solranklist) == 1) / float(num_tests), np.sum(np.array(sorted_filtersolranklist) == 1) / float(num_tests))
    print "Frac in top 3: {0}, frac filtered in top 3: {1}".format(np.sum(np.array(sorted_solranklist) <= 3) / float(num_tests), np.sum(np.array(sorted_filtersolranklist) <= 3) / float(num_tests))
Esempio n. 8
0
def network_FLiER_test(network_filename, pmus, noise, use_filter=True, 
                       test_branches = None, FLiER_type = "Jacobian", 
                       verbose=False):
    """
    Simulate the failure of each line in the network one at a run, and run
    FLiER for each case.
    
    Inputs:
    network_filename - The path to the file containing the network in IEEE CDF
       format.
    pmus - A list of bus indices with PMUs on them.
    
    Outputs:
    
    """
    if network_filename.endswith('.m'):
        powernet = PowerNetwork.powernet_from_matpower(network_filename)
    else:
        powernet = PowerNetwork.powernet_from_IEEECDF(network_filename)
    num_lines = len(powernet.branches)
    solution_ranks = [] # Contains ranks of correct answers
    score_list = [] # Contains tij's
    fraction_ts_computed_list = [] # How many tij's computed per test.
    dict_of_score_dicts = dict()
    num_filtered_out = 0
    
    V = powernet.simulate()
    pre_event_volts = np.hstack([np.angle(V), np.abs(V)])

    if test_branches is None:
        test_branches = range(len(powernet.branches))
    for i in test_branches:
        branch = powernet.branches[i]
        if verbose:
            print "On line {0}".format([bus.index for bus in branch.buses])
        out = single_FLiER_test(powernet, pmus, noise, branch_to_fail=branch, 
                                use_filter=use_filter, pre_event_volts=pre_event_volts,
                                FLiER_type=FLiER_type, verbose=verbose)
        score_dict, initial_score_dict, fraction_ts_computed = out
        if score_dict == False:
            # then power flow equation solver did not converge
            continue
        
        fraction_ts_computed_list.append(fraction_ts_computed)
        if i in score_dict:
            score_of_solution = score_dict[i]
            rank = np.sum(score_dict.values() <= score_of_solution)
            solution_ranks.append(rank)
        else:
            # Then the correct line was filtered out.
            # Just choose the rank as last, and some large tij
            # filler value.
            if verbose:
                print "Solution filtered out."
            score_of_solution = 100
            rank = num_lines
            solution_ranks.append(rank)
            num_filtered_out += 1
        dict_of_score_dicts[i] = (score_dict, initial_score_dict, fraction_ts_computed)
            
        # Produce a list of (key, value) dictionary elements sorted by value.
        # Then split that list to create a sorted list of keys and a sorted list of
        # values. Recall that the keys are indices into the list of edges.
        branch_inds, scores = zip(*sorted(score_dict.iteritems(), key=lambda x: x[1]))
        
        if verbose:
            print score_of_solution, rank, len(score_dict)
        score_list.append((scores, score_of_solution))
        
    if verbose:
        print "{0} filtered out entirely.".format(num_filtered_out)
    return score_list, dict_of_score_dicts, powernet.branches, solution_ranks, fraction_ts_computed_list