#include #include #include #include #include #include #include #include #include "pds.hpp" #include "gurobi_solve.hpp" #include "draw_grid.hpp" void printResult( const pds::PowerGrid& graph, const pds::map& active, const pds::set& observed ) { size_t active_count = 0, inactive_count = 0, zero_injection_count = 0, observed_count = 0; auto filter_active = [&active](auto v) -> bool { return active.at(v) == pds::PmuState::Active; }; if (false) { auto active = graph.vertices() | ranges::views::filter(filter_active) | ranges::to(); fmt::print("active nodes: {}\n", active); } for (const auto &v: graph.vertices()) { switch (active.at(v)) { case pds::PmuState::Active: ++active_count; break; case pds::PmuState::Inactive: ++inactive_count; break; case pds::PmuState::Blank: break; } zero_injection_count += graph[v].zero_injection; observed_count += observed.contains(v); } fmt::print( "graph (n={}, m={}, #active={}, #inactive={}, #observed={}, #zero_injection={})\n", graph.numVertices(), graph.numEdges(), active_count, inactive_count, observed_count, zero_injection_count); bool feasible = pds::observed(graph, observed); fmt::print("solved: {}\n", feasible); } void printGraph( const pds::PowerGrid& graph ) { std::cout << "graph {\n"; for (auto v: graph.vertices()) { std::cout << graph[v].name << "; "; } std::cout << "\n"; for (auto e: graph.edges()) { std::cout << graph[graph.source(e)].name << " -- " << graph[graph.target(e)].name << ";\n"; } std::cout << "}\n"; } auto now() { return std::chrono::high_resolution_clock::now(); } template auto ms(T time) { return std::chrono::duration_cast(time); } bool simpleReductions(pds::PdsState& state, const std::function& callback) { bool changed = false; if (state.disableLowDegree()) { callback(state, "low_degree"); changed = true; } while (state.collapseLeaves()) { callback(state, "leaves"); changed = true; } if (state.reduceObservedNonZi()) { callback(state, "non_zi"); changed = true; } while (state.collapseDegreeTwo()) { callback(state, "path"); changed = true; } if (state.collapseObservedEdges()) { callback(state, "observed_edges"); changed = true; } return changed; } bool applyReductions(pds::PdsState& state, const std::function& callback) { bool changed = false; while (simpleReductions(state, callback)) { changed = true; } if (state.disableObservationNeighborhood()) { callback(state, "observation_neighborhood"); changed = true; } if (state.activateNecessaryNodes()) { callback(state, "necessary_nodes"); changed = true; } return changed; } void processBoost(const std::string& filename) { fmt::print("loading graph...\n"); auto t0 = now(); auto graph = pds::import_graphml(filename, true); auto t1 = now(); fmt::print("graph loaded in {}\n", ms(t1 - t0)); std::fflush(nullptr); //printGraph(graph); fmt::print("computing layout...\n"); auto layout = pds::layoutGraph(graph); auto t2 = now(); fmt::print("layout computed in {}\n", ms(t2 - t1)); if (graph.numVertices() < 1000) { fmt::print("solve exact\n"); auto t3 = now(); auto active = graph.vertices() | ranges::views::transform([](pds::PowerGrid::vertex_descriptor v) { return std::make_pair(v, pds::PmuState::Blank);}) | ranges::to>(); pds::solve_pds(graph, active); auto t4 = now(); fmt::print("solved in {}", ms(t4 - t3)); pds::set observed; printResult(graph, active, observed); pds::dominate(graph, active, observed); pds::propagate(graph, observed); printResult(graph, active, observed); pds::drawGrid(graph, active, observed, "out/4_solve_input.svg", layout); } { fmt::print("solve with reductions\n"); pds::PdsState state(graph); bool drawReductionSteps = true; size_t counter = 0; auto drawState = [&](const pds::PdsState& state, const std::string& name) mutable { if (drawReductionSteps) { pds::drawGrid(state.graph(), state.active(), state.observed(), fmt::format("out/1_red_{:04}_{}.svg", counter, name), layout); ++counter; } }; pds::drawGrid(state.graph(), state.active(), state.observed(), "out/0_input.svg", layout); fmt::print("applying reductions\n"); auto t_startReduction = now(); while (applyReductions(state, drawState)) { } auto t_endReduction = now(); fmt::print("reductions took {}\n", ms(t_endReduction - t_startReduction)); printResult(state.graph(), state.active(), state.observed()); pds::drawGrid(state.graph(), state.active(), state.observed(), "out/1_red_preprocessed.svg", layout); bool drawTrivial = false; auto t_solveStart = now(); bool optimal = true; if (true) { auto subproblems = state.subproblems(true); ranges::sort(subproblems, [](const pds::PdsState &left, const pds::PdsState &right) -> bool { return left.graph().numVertices() < right.graph().numVertices(); }); for (size_t i = 0, j = 0; auto &subproblem: subproblems) { if (subproblem.solveTrivial()) { if (drawTrivial) { pds::drawGrid(subproblem.graph(), subproblem.active(), subproblem.observed(), fmt::format("out/comp_trivial_{:03}.svg", i), layout); ++i; } } else { fmt::print("solving subproblem {}\n", j); printResult(subproblem.graph(), subproblem.active(), subproblem.observed()); pds::drawGrid(subproblem.graph(), subproblem.active(), subproblem.observed(), fmt::format("out/comp_{:03}_0unsolved.svg", j), layout); while (applyReductions(subproblem, [](const auto&, const auto&) { })); pds::drawGrid(subproblem.graph(), subproblem.active(), subproblem.observed(), fmt::format("out/comp_{:03}_1reductions.svg", j), layout); auto active = subproblem.active(); auto t_subSolve = now(); if (!pds::solve_pds(subproblem.graph(), active, false, 60 * 60)) { fmt::print("infeasible subproblem {}\n", j); } auto t_subEnd = now(); for (auto v: subproblem.graph().vertices()) { if (active.at(v) == pds::PmuState::Active) { subproblem.setActive(v); } } fmt::print("subproblem solved in {}\n", ms(t_subEnd - t_subSolve)); fmt::print("solve result:\n"); printResult(subproblem.graph(), subproblem.active(), subproblem.observed()); pds::drawGrid(subproblem.graph(), subproblem.active(), subproblem.observed(), fmt::format("out/comp_{:03}_2solved.svg", j), layout); ++j; } for (auto v: subproblem.graph().vertices()) { if (subproblem.isActive(v)) { state.setActive(v); } } } } else { auto active = state.active(); if (!pds::solve_pds(state.graph(), active)) { fmt::print("infeasible\n"); } for (auto v: state.graph().vertices()) { if (active.at(v) == pds::PmuState::Active) { state.setActive(v); } } } auto t_solveEnd = now(); if (state.allObserved()) { fmt::print("solved in {}\n", ms(t_solveEnd - t_solveStart)); } else { fmt::print("not solved in {}\n", ms(t_solveEnd - t_solveStart)); } pds::drawGrid(state.graph(), state.active(), state.observed(), "out/2_solved_preprocessed.svg", layout); auto active = state.active(); pds::set observed; pds::dominate(graph, active, observed); pds::propagate(graph, observed); pds::drawGrid(graph, active, observed, "out/3_solved.svg", layout); printResult(graph, active, observed); auto isUnobserved = [&state] (auto v) -> bool { return !state.isObserved(v); }; fmt::print("#unobserved = {}", ranges::distance(state.graph().vertices() | ranges::views::filter(isUnobserved))); } } struct DrawOptions { bool drawInput; bool drawSolution; bool drawReductions; bool drawSubproblems; bool drawAny() { return drawInput || drawSolution || drawReductions || drawSubproblems; } }; int run(int argc, const char** argv) { using namespace pds; using std::string, std::vector; using namespace std::string_literals; namespace po = boost::program_options; namespace fs = std::filesystem; po::options_description desc("options"); desc.add_options() ("help,h", "show this help") ("graph,f", po::value(), "input graph") ("outdir,o", po::value()->default_value("out"), "output directory") ( "solve", po::value()->default_value("subproblem"), "gurobi solve method. Can be any of [none,full,subproblem]" ) ("print-solve", "print intermediate solve state") ("print-state,p", "print solve state after each step") ("time-limit,t", po::value()->default_value(600.0), "time limit for gurobi in seconds") ("reductions,r", "apply reductions before exact solving") ( "all-zi,z", po::value()->default_value(false)->implicit_value(true), "consider all vertices zero-inection" ) ( "draw,d", po::value>()->default_value({"none"s}, "none")->implicit_value({"all"s}, "all")->composing(), "can be one of [none,all,input,solution,reductions,subproblems]" ) ; po::positional_options_description pos; pos.add("graph", 1); po::variables_map vm; po::store(po::command_line_parser(argc, argv).options(desc).positional(pos).run(), vm); po::notify(vm); if (vm.count("help")) { desc.print(std::cout); return 1; } auto outdir = vm["outdir"].as(); switch (fs::status(outdir).type()) { case fs::file_type::none: case fs::file_type::not_found: fs::create_directories(outdir); break; case fs::file_type::directory: case fs::file_type::symlink: break; default: fmt::print(stderr, "could not create output directory '{}'", outdir); return 1; } DrawOptions drawOptions{}; auto drawOption = vm["draw"].as>(); for (auto d: drawOption) { if (d == "none") { drawOptions.drawSubproblems = drawOptions.drawReductions = drawOptions.drawSolution = drawOptions.drawInput = false; } else if (d == "all") { drawOptions.drawSubproblems = drawOptions.drawReductions = drawOptions.drawSolution = drawOptions.drawInput = true; } else if (d == "input") { drawOptions.drawInput = true; } else if (d == "solution") { drawOptions.drawSolution = true; } else if (d == "reductions") { drawOptions.drawReductions = true; } else if (d == "subproblems") { drawOptions.drawSubproblems = true; } else { fmt::print(stderr, "invalid draw option {}\n", d); return 1; } } if (!set{"none"s, "full"s, "subproblem"s}.contains(vm["solve"].as())) { fmt::print(stderr, "invalid solve option: {}\n", vm["solve"].as()); return 1; } if (!vm.count("graph")) { fmt::print(stderr, "no input given\n"); return 1; } PdsState state(import_graphml(vm["graph"].as(), vm["all-zi"].as())); auto input = state; map layout; if (drawOptions.drawAny()) { fmt::print("computing layout\n"); auto t0 = now(); layout = pds::layoutGraph(state.graph()); auto t1 = now(); fmt::print("computed layout in {}\n", ms(t1-t0)); } if (drawOptions.drawInput) { drawGrid(state.graph(), state.active(), state.observed(), fmt::format("{}/0_input.svg", outdir), layout); } auto printState = [&](const PdsState& state) { if (vm.count("print-state")) printResult(state.graph(), state.active(), state.observed()); }; fmt::print("input:\n"); printState(state); auto tSolveStart = now(); size_t counter = 0; if (vm.count("reductions")) { auto drawCallback = [&](const pds::PdsState &state, const std::string &name) mutable { if (drawOptions.drawReductions) { pds::drawGrid(state.graph(), state.active(), state.observed(), fmt::format("{}/1_red_{:04}_{}.svg", outdir, counter, name), layout); ++counter; } }; fmt::print("applying reductions\n"); while (applyReductions(state, drawCallback)) { }; auto tReductions = now(); printState(state); fmt::print("reductions took {}\n", ms(tReductions - tSolveStart)); } vector subproblems = state.subproblems(true); if (drawOptions.drawSubproblems) { ranges::sort(subproblems, [](const pds::PdsState &left, const pds::PdsState &right) -> bool { return left.graph().numVertices() < right.graph().numVertices(); }); for (size_t i = 0; auto &subproblem: subproblems) { if (!subproblem.allObserved()) { pds::drawGrid( subproblem.graph(), subproblem.active(), subproblem.observed(), fmt::format("{}/comp_{:03}_0unsolved.svg", outdir, i), layout); ++i; } } } for (size_t i = 0; auto &subproblem: subproblems) { auto tSub = now(); //if (vm.count("reductions")) { // applyReductions(state, [](const auto&, const auto&) {}); // if (drawOptions.drawSubproblems && drawOptions.drawReductions) { // pds::drawGrid( // subproblem.graph(), subproblem.active(), subproblem.observed(), // fmt::format("{}/comp_{:03}_1reductions.svg", outdir, i), layout); // } // printState(state); //} if (!subproblem.solveTrivial()) { fmt::print("solving subproblem {}\n", i); printState(state); if (vm["solve"].as() == "subproblem") { solve_pds(subproblem, vm.count("print-solve"), vm["time-limit"].as()); for (auto v: subproblem.graph().vertices()) { if (subproblem.isActive(v)) { state.setActive(v); } } auto tSubEnd = now(); fmt::print("solved subproblem {} in {}", i, ms(tSubEnd - tSub)); } if (drawOptions.drawSubproblems && drawOptions.drawSolution) { pds::drawGrid( subproblem.graph(), subproblem.active(), subproblem.observed(), fmt::format("{}/comp_{:03}_2solved.svg", outdir, i), layout); } ++i; } } if (vm["solve"].as() == "full") { solve_pds(state, vm.count("print-solve"), vm["time-limit"].as()); } for (auto v: state.graph().vertices()) { if (state.isActive(v)) { input.setActive(v); } } auto tSolveEnd = now(); if (drawOptions.drawSolution) { drawGrid(state.graph(), state.active(), state.observed(), fmt::format("{}/2_solved_preprocessed.svg", outdir), layout); drawGrid(input.graph(), input.active(), input.observed(), fmt::format("{}/3_solved.svg", outdir), layout); } fmt::print("solved in {}\n", ms(tSolveEnd - tSolveStart)); printState(state); return 0; } int main(int argc, const char** argv) { //std::string filename = argv[1]; //processBoost(filename); run(argc, argv); return 0; }