diff --git a/Tests/data/extended_test_problem.json b/Tests/data/extended_test_problem.json deleted file mode 100644 index ec324396698b02170e51101fd2f70f30b34bb375..0000000000000000000000000000000000000000 --- a/Tests/data/extended_test_problem.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "constraints": [ - [ - 4, - 3, - 0 - ] - ], - "durations": [ - 5, - 8, - 7, - 3 - ], - "lowerBound": 0, - "optimalSolution": 10, - "resources": [ - { - "capacity": 2, - "demand": [ - 2, - 1 - ], - "tasks": [ - 0, - 2 - ], - "transition": [] - }, - { - "capacity": 4, - "demand": [ - 3, - 1, - 1 - ], - "tasks": [ - 1, - 2, - 3 - ], - "transition": [] - }, - { - "capacity": 2, - "demand": [ - 2, - 1 - ], - "tasks": [ - 1, - 3 - ], - "transition": [] - } - ] -} \ No newline at end of file diff --git a/Tests/data/graph_builder_config.json b/Tests/data/graph_builder_config.json index 03ad3f9e9d63bec4236bbbe4f9f269756f0c8032..a70efab2057ef7400c1512fb3e42b1d967801bc5 100644 --- a/Tests/data/graph_builder_config.json +++ b/Tests/data/graph_builder_config.json @@ -8,7 +8,9 @@ "extractorName": "ResourceEnergyExtractor" }, "taskFeatureExtractor": { - "arguments": null, + "arguments": { + "legacy": true + }, "extractorName": "TaskTimingFeatureExtractor" }, "topologyExtractor": { diff --git a/Tests/data/test_problem.json b/Tests/data/test_problem.json deleted file mode 100644 index 0a547ab8c40c4b42b2b701873e6900a601802541..0000000000000000000000000000000000000000 --- a/Tests/data/test_problem.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "constraints": [ - [ - 4, - 3, - 0 - ] - ], - "durations": [ - 5, - 8, - 7, - 3 - ], - "lowerBound": 0, - "optimalSolution": 10, - "resources": [ - { - "capacity": 2, - "demand": [ - 2, - 1 - ], - "tasks": [ - 0, - 2 - ], - "transition": [] - }, - { - "capacity": 4, - "demand": [ - 3, - 1, - 1 - ], - "tasks": [ - 1, - 2, - 3 - ], - "transition": [] - } - ] -} \ No newline at end of file diff --git a/Tests/heuristics/ValueHeuristics.cpp b/Tests/heuristics/ValueHeuristics.cpp index 7da1916bd02f165dd5a9db51f3046a40f81f5d4c..95bbd9d6298ebaff9d375ba08d94de39ebf3bf37 100644 --- a/Tests/heuristics/ValueHeuristics.cpp +++ b/Tests/heuristics/ValueHeuristics.cpp @@ -62,8 +62,7 @@ struct LitProvider { TEST(value_heuristics, base_value_heuristic) { using namespace tempo; - using - enum tempo::VariableType; + using enum tempo::heuristics::VariableType; TestValueHeuristic h(0); auto lit = makeBooleanLiteral<int>(true, 0, 0); LitProvider provider(lit); diff --git a/Tests/nn/GNN.cpp b/Tests/nn/GNN.cpp index e4f2c922a1d4a01874449428590c3e2d1377fe54..67c0391bb66fdb015bf16a3ea0e3fde022b1e1c8 100644 --- a/Tests/nn/GNN.cpp +++ b/Tests/nn/GNN.cpp @@ -24,11 +24,6 @@ public: using tempo::nn::EdgeRegressor::extractHeatMap; }; -class TestGNNHeatMap: public tempo::nn::heuristics::GNNEdgePolarityPredictor { -public: - using tempo::nn::heuristics::GNNEdgePolarityPredictor::choosePolarityFromHeatMap; -}; - TEST(nn_GNN, base_gnn_ctor) { TestGNN gnn(tempo::testing::TestData::TestNN); EXPECT_FALSE(gnn.getModel().is_training()); @@ -54,18 +49,16 @@ TEST(nn_GNN, edge_regressor_heat_map){ TEST(nn_GNN, gnn_heat_map_choose_polarity) { using namespace tempo; + using tempo::nn::heuristics::detail::choosePolarityFromHeatMap; Matrix<nn::DataType> heatMap(3, 3, nn::GNN::NoValue); heatMap(0, 1) = 0.9; heatMap(1, 0) = 0.1; heatMap(2, 1) = 1; heatMap(1, 2) = 1; - DistanceConstraint cp(START(0), END(1), 0); - EXPECT_TRUE(TestGNNHeatMap::choosePolarityFromHeatMap(cp.from, cp.to, heatMap)); - EXPECT_FALSE(TestGNNHeatMap::choosePolarityFromHeatMap((~cp).from, (~cp).to, heatMap)); - cp = {START(2), START(1), 0}; - EXPECT_FALSE(TestGNNHeatMap::choosePolarityFromHeatMap(cp.from, cp.to, heatMap)); - EXPECT_FALSE(TestGNNHeatMap::choosePolarityFromHeatMap((~cp).from, (~cp).to, heatMap)); - cp = {START(0), END(2), 0}; - EXPECT_THROW(TestGNNHeatMap::choosePolarityFromHeatMap(cp.from, cp.to, heatMap), std::runtime_error); - EXPECT_THROW(TestGNNHeatMap::choosePolarityFromHeatMap((~cp).from, (~cp).to, heatMap), std::runtime_error); + EXPECT_TRUE(choosePolarityFromHeatMap(0, 1, heatMap)); + EXPECT_FALSE(choosePolarityFromHeatMap(1, 0, heatMap)); + EXPECT_FALSE(choosePolarityFromHeatMap(2, 1, heatMap)); + EXPECT_FALSE(choosePolarityFromHeatMap(1, 2, heatMap)); + EXPECT_THROW(choosePolarityFromHeatMap(0, 2, heatMap), std::runtime_error); + EXPECT_THROW(choosePolarityFromHeatMap(2, 0, heatMap), std::runtime_error); } diff --git a/Tests/nn/GraphBuilder.cpp b/Tests/nn/GraphBuilder.cpp index 46b910ad2cfe9194e66e3c128df64764d529b697..b2ae274434e99f282e75003c5817e42e5e61c7b3 100644 --- a/Tests/nn/GraphBuilder.cpp +++ b/Tests/nn/GraphBuilder.cpp @@ -10,38 +10,34 @@ #include "testing.hpp" -auto getGtTopology(const ProblemInstance &problemInstance) -> tempo::nn::Topology { +auto getGtTopology(const tempo::testing::ProblemInstance &problemInstance) -> tempo::nn::Topology { using namespace tempo::nn; - using namespace tempo::testing; - std::ifstream configFile(TestData::GraphBuilderConfig); - auto config = nlohmann::json::parse(configFile).get<GraphBuilderConfig>(); - auto extractor = TopologyBuilderFactory::getInstance().create(config.topologyExtractor.extractorName, - problemInstance); - return std::visit([](auto &e) { - return e.getTopology(makeSolverState(tempo::Matrix<int>{})); - }, extractor); + MinimalTopologyBuilder builder(problemInstance); + return builder.getTopology(); } void testFeatureExtraction(const std::string &featureName, const std::string &graphKey) { using namespace tempo::nn; using namespace tempo::testing; - const auto [problem, evtNetwork] = createRandomProblem(100, 50); + const auto [problem, scheduler, taskTimings] = createRandomProblem(100, 50); const auto topology = getGtTopology(problem); GraphBuilder graphBuilder(TestData::GraphBuilderConfig, problem); - auto graph = graphBuilder.getGraph(makeSolverState(evtNetwork)); + const auto solverState = makeSolverState(taskTimings, scheduler); + auto graph = graphBuilder.getGraph(solverState); std::ifstream configFile(TestData::GraphBuilderConfig); auto config = nlohmann::json::parse(configFile).at(featureName).get<ExtractorConfig>(); auto extractor = FeatureExtractorFactory::getInstance().create(config.extractorName, config.arguments); - auto gt = std::visit([&evtNetwork, &topology](auto &ext) { return ext(topology, evtNetwork); }, extractor); + auto gt = std::visit([&topology, &problem, &solverState](auto &ext) { return ext(topology, solverState, problem); }, + extractor); EXPECT_TRUE(torch::all(gt == graph.at(graphKey)).item<bool>()); } TEST(nn_GraphBuilder, get_graph_basic) { using namespace tempo::nn; using namespace tempo::testing; - const auto [problem, evtNetwork] = createRandomProblem(3, 2); + const auto [problem, solver, taskNet] = createRandomProblem(3, 2); GraphBuilder graphBuilder(TestData::GraphBuilderConfig, problem); - auto graph = graphBuilder.getGraph(makeSolverState(evtNetwork)); + auto graph = graphBuilder.getGraph(makeSolverState(taskNet, solver)); EXPECT_TRUE(graph.contains(GraphKeys::TaskFeatures)); EXPECT_TRUE(graph.contains(GraphKeys::EdgeFeatures)); EXPECT_TRUE(graph.contains(GraphKeys::ResourceFeatures)); @@ -54,12 +50,10 @@ TEST(nn_GraphBuilder, get_graph_basic) { TEST(nn_GraphBuilder, get_graph_task_features) { - using namespace tempo::nn; testFeatureExtraction("taskFeatureExtractor", tempo::nn::GraphKeys::TaskFeatures); } TEST(nn_GraphBuilder, get_graph_edge_features) { - using namespace tempo::nn; testFeatureExtraction("edgeFeatureExtractor", tempo::nn::GraphKeys::EdgeFeatures); } @@ -70,9 +64,9 @@ TEST(nn_GraphBuilder, get_graph_resource_features) { TEST(nn_GraphBuilder, get_graph_resource_deps) { using namespace tempo::nn; using namespace tempo::testing; - auto [problem, evtNetwork] = createRandomProblem(10, 5); + auto [problem, scheduler, taskNet] = createRandomProblem(10, 5); GraphBuilder graphBuilder(TestData::GraphBuilderConfig, problem); - auto graph = graphBuilder.getGraph(makeSolverState(evtNetwork)); + auto graph = graphBuilder.getGraph(makeSolverState(taskNet, scheduler)); const auto topology = getGtTopology(problem); EXPECT_TRUE(torch::all(topology.resourceDependencies == graph.at(GraphKeys::ResourceDependencies)).item<bool>()); EXPECT_TRUE(torch::all(topology.resourceDemands == graph.at(GraphKeys::ResourceConsumptions)).item<bool>()); @@ -81,9 +75,9 @@ TEST(nn_GraphBuilder, get_graph_resource_deps) { TEST(nn_GraphBuilder, get_graph_topology) { using namespace tempo::nn; using namespace tempo::testing; - const auto [problem, evtNetwork] = createRandomProblem(10, 5); + const auto [problem, scheduler, taskNet] = createRandomProblem(10, 5); GraphBuilder graphBuilder(TestData::GraphBuilderConfig, problem); - const auto graph = graphBuilder.getGraph(makeSolverState(evtNetwork)); + const auto graph = graphBuilder.getGraph(makeSolverState(taskNet, scheduler)); const auto topology = getGtTopology(problem); EXPECT_TRUE(torch::all(topology.edgePairMask == graph.at(GraphKeys::EdgePairMask)).item<bool>()); EXPECT_TRUE(torch::all(topology.edgeResourceRelations == graph.at(GraphKeys::EdgeResourceRelations)).item<bool>()); diff --git a/Tests/nn/MinimalTopologyBuilder.cpp b/Tests/nn/MinimalTopologyBuilder.cpp index 94695fce29454d2ae094f43a5771b7a13cb12661..363b08c2d2c68b04040badde9013bc3ee9e3cd51 100644 --- a/Tests/nn/MinimalTopologyBuilder.cpp +++ b/Tests/nn/MinimalTopologyBuilder.cpp @@ -91,11 +91,15 @@ TEST(nn_MinimalTopologyBuilder, MinimalBuilder_addPrecedenceEdges) { using namespace tempo; using std::views::transform; tempo::nn::impl::TopologyData data{.edgeLookup = tempo::nn::impl::EdgeLookup(100)}; + auto tasks = tempo::testing::createDummyTasks(5); + VarTaskMapping mapping(tasks); auto evtViewer = transform( - [](const auto &dc) { return DistanceConstraint<int>(START(dc.from), END(dc.to), dc.distance); }); + [&tasks](const auto &dc) { + return DistanceConstraint<int>(tasks.at(dc.from).start.id(), tasks.at(dc.to).end.id(), dc.distance); + }); std::vector<DistanceConstraint<int>> edges{{1, 2, 0}, {2, 3, 2}, {3, 4, -1}}; TestMinimalTopologyBuilder::addEdge({2, 5}, true, -1, data); - TestMinimalTopologyBuilder::addPrecedenceEdges(edges | evtViewer, data); + TestMinimalTopologyBuilder::addPrecedenceEdges(edges | evtViewer, mapping, data); ASSERT_EQ(data.edges.size(), 4); EXPECT_EQ(data.edgeIdx.size(), 1); EXPECT_EQ(data.edgeIdx.front(), 0); @@ -163,14 +167,16 @@ void testEdges(const EdgeVector &edges, const EdgeSet >Edges, const EdgeMap &e } } - TEST(nn_MinimalTopologyBuilder, MinimalBuilder_completeSubGraphOneResource) { - std::vector<int> tasks{1, 2, 3}; + using namespace tempo; + auto tasks = tempo::testing::createDummyTasks(4); + std::vector<unsigned> taskIds{1, 2, 3}; std::vector<int> demands{2, 1, 4}; constexpr int Capacity = 4; - Resource<int> resource(tasks, demands, {}, Capacity); + tempo::testing::Resource resource(Capacity, std::vector(tasks.begin() + 1, tasks.end()), demands); + VarTaskMapping mapping(tasks); tempo::nn::impl::TopologyData data{.edgeLookup = tempo::nn::impl::EdgeLookup(100)}; - TestMinimalTopologyBuilder::completeSubGraph(resource, 17, data); + TestMinimalTopologyBuilder::completeSubGraph(resource, 17, mapping, data); ASSERT_EQ(data.taskIdx.size(), 3); ASSERT_EQ(data.resIdx.size(), 3); ASSERT_EQ(data.resDemands.size(), 3); @@ -187,7 +193,7 @@ TEST(nn_MinimalTopologyBuilder, MinimalBuilder_completeSubGraphOneResource) { EXPECT_EQ(r, 17); } - for (auto [t, gtDemand] : iterators::const_zip(tasks, demands)) { + for (auto [t, gtDemand] : iterators::const_zip(taskIds, demands)) { auto res = std::ranges::find(data.taskIdx, t); ASSERT_NE(res, data.taskIdx.end()); auto idx = static_cast<std::size_t>(res - data.taskIdx.begin()); @@ -222,15 +228,22 @@ void checkEdge(const Edge &edge, const EdgeVector &allEdges, std::vector<IndexTy } TEST(nn_MinimalTopologyBuilder, MinimalBuilder_completeSubGraph_multiple_resources) { + using namespace tempo; + auto tasks = tempo::testing::createDummyTasks(4); + VarTaskMapping mapping(tasks); std::vector<int> tasks17{1, 2}; std::vector<int> demands17{2, 1}; constexpr int Capacity17 = 2; tempo::nn::impl::TopologyData data{.edgeLookup = tempo::nn::impl::EdgeLookup(100)}; - TestMinimalTopologyBuilder::completeSubGraph(Resource<int>(tasks17, demands17, {}, Capacity17), 17, data); + TestMinimalTopologyBuilder::completeSubGraph( + tempo::testing::Resource(Capacity17, std::vector(tasks.begin() + 1, tasks.begin() + 3), demands17), + 17, mapping, data); decltype(tasks17) tasks18{1, 2, 3}; decltype(demands17) demands18{2, 1, 1}; constexpr int Capacity18 = 3; - TestMinimalTopologyBuilder::completeSubGraph(Resource<int>(tasks18, demands18, {}, Capacity18), 18, data); + TestMinimalTopologyBuilder::completeSubGraph( + tempo::testing::Resource(Capacity18, std::vector(tasks.begin() + 1, tasks.end()), demands18), + 18, mapping, data); ASSERT_EQ(data.taskIdx.size(), 5); ASSERT_EQ(data.resIdx.size(), 5); ASSERT_EQ(data.resDemands.size(), 5); diff --git a/Tests/nn/TopologyBuilderTest.cpp b/Tests/nn/TopologyBuilderTest.cpp index 4c5a146197ec301f79aa14b0ee87ea73fd129880..b0d35bb1fe31ea7dc45983a93ed7fdf36d73e790 100644 --- a/Tests/nn/TopologyBuilderTest.cpp +++ b/Tests/nn/TopologyBuilderTest.cpp @@ -13,15 +13,16 @@ #include "Tests/testing.hpp" void TopologyBuilderTest::SetUp() { - inst = tempo::serialization::deserializeFromFile<ProblemInstance>(tempo::testing::TestData::TestProblem); - extInst = tempo::serialization::deserializeFromFile<ProblemInstance>(tempo::testing::TestData::ExtendedTestProblem); + auto [i, _] = tempo::testing::createTestProblem(); + inst = std::move(i); + std::tie(extInst, _) = tempo::testing::createExtendedTestProblem(); } -auto TopologyBuilderTest::instance() const noexcept -> const ProblemInstance & { +auto TopologyBuilderTest::instance() const noexcept -> const tempo::testing::ProblemInstance & { return inst; } -auto TopologyBuilderTest::extendedInstance() const noexcept -> const ProblemInstance & { +auto TopologyBuilderTest::extendedInstance() const noexcept -> const tempo::testing::ProblemInstance & { return extInst; } @@ -65,11 +66,12 @@ void TopologyBuilderTest::testResourceDependencies(const tempo::nn::Topology &to std::span(topology.resourceDemands.data_ptr<DataType>(), tasks.size(0))); for (auto [tIdx, rIdx, d] : zRange) { EXPECT_GT(resMat.at(rIdx, tIdx), 0); - EXPECT_EQ(static_cast<DataType>(resMat.at(rIdx, tIdx)) / instance().resources[rIdx].capacity, d); + EXPECT_EQ(static_cast<DataType>(resMat.at(rIdx, tIdx)) / instance().resources()[rIdx].resourceCapacity(), d); } - using namespace std::views; - for (auto t = 0ul; t < instance().durations.size(); ++t) { + using std::views::iota; + using std::views::filter; + for (auto t = 0ul; t < instance().tasks().size(); ++t) { auto gtRange = iota(0ul, resMat.numRows()) | filter([&resMat, t](auto r) { return resMat.at(r, t) > 0; }); std::unordered_set<std::size_t> gtResources(gtRange.begin(), gtRange.end()); @@ -83,15 +85,14 @@ void TopologyBuilderTest::testResourceDependencies(const tempo::nn::Topology &to void TopologyBuilderTest::testEdges(const tempo::nn::Topology &topology) const { using namespace tempo::nn; - using tempo::TASK; const auto consumptions = getResourceMatrix(instance()); auto edges = util::getEdgeView(topology.edgeIndices); EXPECT_EQ(edges.size(), 9); // only for this particular test problem for (auto [from, to] : edges) { - auto res = std::ranges::find_if(instance().constraints, [from, to](const auto &prec) { - return TASK(std::get<0>(prec)) == from and TASK(std::get<1>(prec)) == to; + auto res = std::ranges::find_if(instance().precedences(), [from, to, this](const auto &prec) { + return instance().getMapping()(prec.from) == from and instance().getMapping()(prec.to) == to; }); - if (res != instance().constraints.end()) { + if (res != instance().precedences().end()) { continue; } @@ -144,11 +145,11 @@ void TopologyBuilderTest::testEdgePairMask(const tempo::nn::Topology &topology) } } -auto TopologyBuilderTest::getResourceMatrix(const ProblemInstance &probInstance) -> tempo::Matrix<int> { - tempo::Matrix<int> ret(probInstance.resources.size(), probInstance.durations.size()); - for (const auto [r, res] : iterators::const_enumerate(probInstance.resources)) { +auto TopologyBuilderTest::getResourceMatrix(const tempo::testing::ProblemInstance &probInstance) -> tempo::Matrix<int> { + tempo::Matrix<int> ret(probInstance.resources().size(), probInstance.tasks().size()); + for (const auto [r, res] : iterators::const_enumerate(probInstance.resources())) { for (auto [idx, t] : iterators::enumerate(res)) { - ret.at(r, t) = res.getDemand(idx); + ret.at(r, probInstance.getMapping()(t.id())) = res.getDemand(idx); } } diff --git a/Tests/nn/TopologyBuilderTest.hpp b/Tests/nn/TopologyBuilderTest.hpp index 07a0f5888f1dd614bfba3e05e180a84ec735c2e3..4234a20f24c40de07873c3c1d92b8871196c2186 100644 --- a/Tests/nn/TopologyBuilderTest.hpp +++ b/Tests/nn/TopologyBuilderTest.hpp @@ -8,20 +8,22 @@ #include <gtest/gtest.h> -#include "util/parsing/format.hpp" +#include "testing.hpp" #include "nn/torch_types.hpp" #include "util/Matrix.hpp" +#include "Model.hpp" +#include "util/SchedulingProblemHelper.hpp" class TopologyBuilderTest : public testing::Test { - ProblemInstance inst; - ProblemInstance extInst; + tempo::testing::ProblemInstance inst; + tempo::testing::ProblemInstance extInst; protected: void SetUp() override; - [[nodiscard]] auto instance() const noexcept -> const ProblemInstance&; + [[nodiscard]] auto instance() const noexcept -> const tempo::testing::ProblemInstance&; - [[nodiscard]] auto extendedInstance() const noexcept -> const ProblemInstance&; + [[nodiscard]] auto extendedInstance() const noexcept -> const tempo::testing::ProblemInstance&; void testResourceDependencies(const tempo::nn::Topology &topology) const; @@ -29,7 +31,7 @@ protected: void testEdgePairMask(const tempo::nn::Topology &topology) const; - [[nodiscard]] static auto getResourceMatrix(const ProblemInstance &probInstance) -> tempo::Matrix<int>; + [[nodiscard]] static auto getResourceMatrix(const tempo::testing::ProblemInstance &probInstance) -> tempo::Matrix<int>; public: static void testEdgeResourceRelations(const torch::Tensor &edgeResourceRelations, diff --git a/Tests/nn/feature_extractors.cpp b/Tests/nn/feature_extractors.cpp index 8f4caf91c2421fec753b4cd352261b5ecd68ce78..2861df7ce35006a3836e0416c0b9e9e5948df022 100644 --- a/Tests/nn/feature_extractors.cpp +++ b/Tests/nn/feature_extractors.cpp @@ -6,15 +6,16 @@ #include <gtest/gtest.h> #include <Iterators.hpp> -#include "util/parsing/format.hpp" #include "testing.hpp" #include "nn/feature_extractors.hpp" #include "util/Matrix.hpp" #include "nn/GraphBuilder.hpp" +#include "util/SchedulingProblemHelper.hpp" -auto getInput() -> std::pair<ProblemInstance, tempo::nn::Topology> { + +auto getInput() -> std::pair<tempo::testing::ProblemInstance, tempo::nn::Topology> { using namespace tempo; - auto problem = serialization::deserializeFromFile<ProblemInstance>(tempo::testing::TestData::TestProblem); + auto [problem, _] = tempo::testing::createTestProblem(); nn::MinimalTopologyBuilder topologyBuilder(problem); auto topology = topologyBuilder.getTopology(); return {std::move(problem), std::move(topology)}; @@ -23,52 +24,70 @@ auto getInput() -> std::pair<ProblemInstance, tempo::nn::Topology> { TEST(nn_feature_extractors, TaskTimingExtractor) { using namespace tempo::nn; using namespace tempo; + using namespace tempo::testing; + using T = tempo::testing::TaskSpec; using tempo::testing::random_int; - using tempo::testing::setTaskDurations; - using tempo::testing::setUpperBound; - constexpr bool SatisfiesConcept = feature_extractor<TaskTimingFeatureExtractor, Matrix<int>>; constexpr DataType Ub = 10; - EXPECT_TRUE(SatisfiesConcept); TaskTimingFeatureExtractor extractor; - const auto [instance, topology] = getInput(); - const auto numEvents = instance.durations.size() * 2 + 2; - Matrix<int> eventNet(numEvents, numEvents); - std::vector<tempo::testing::TaskSpec> taskSpecs; - for (std::size_t t = 0; t < instance.durations.size(); ++t) { - taskSpecs.emplace_back(random_int(1, 5), random_int(5, 10), random_int(-10, 0), random_int(-10, 0)); - setTaskDurations(static_cast<tempo::task>(t), taskSpecs.back().minDur, taskSpecs.back().maxDur, - taskSpecs.back().release, taskSpecs.back().deadline, eventNet); - } - - setUpperBound<int>(Ub, eventNet); - auto taskFeatures = extractor(topology, eventNet); - for (auto [idx, task] : iterators::const_enumerate(taskSpecs)) { + auto [tasks, scheduler] = createTasks( + {T{.minDur = 2, .maxDur = 4, .earliestStart = 4, .latestDeadline = static_cast<int>(Ub)}, + T{.minDur = 6, .maxDur = 6, .earliestStart = 0, .latestDeadline = 5}, + T{.minDur = 4, .maxDur = 6, .earliestStart = 2, .latestDeadline = 8}, + T{.minDur = 0, .maxDur = static_cast<int>(Ub), .latestDeadline = static_cast<int>(Ub)}}); + auto sched = tasks.back(); + tasks.pop_back(); + ProblemInstance instance(std::move(tasks), {}, {}, sched); + auto topology = MinimalTopologyBuilder(instance).getTopology(); + auto taskFeatures = extractor(topology, makeSolverState(Matrix<int>{}, scheduler), instance); + for (auto [idx, task] : iterators::const_enumerate(instance.tasks())) { auto features = taskFeatures.slice(0, idx, idx + 1); EXPECT_EQ(features.numel(), 4); - EXPECT_EQ(features[0][0].item<DataType>(), task.minDur/ Ub); - EXPECT_EQ(features[0][1].item<DataType>(), task.maxDur / Ub); - EXPECT_EQ(features[0][2].item<DataType>(), task.release / Ub); - EXPECT_EQ(features[0][3].item<DataType>(), task.deadline / Ub); + EXPECT_EQ(features[0][0].item<DataType>(), task.minDuration(scheduler) / Ub); + EXPECT_EQ(features[0][1].item<DataType>(), task.maxDuration(scheduler) / Ub); + EXPECT_EQ(features[0][2].item<DataType>(), task.getEarliestStart(scheduler) / Ub); + EXPECT_EQ(features[0][3].item<DataType>(), task.getLatestEnd(scheduler) / Ub); } } +TEST(nn_feature_extractors, TaskTimingExtractor_legacy) { + using namespace tempo; + using namespace tempo::nn; + using namespace tempo::testing; + using T = tempo::testing::TaskSpec; + TaskTimingFeatureExtractor extractor{.legacyFeatures = false}; + constexpr int Ub = 10; + auto [tasks, scheduler] = createTasks({T{.minDur = 4, .maxDur = 6, .earliestStart = 2, .latestDeadline = Ub - 2}, + T{.minDur = 0, .maxDur = Ub, .latestDeadline = Ub}}); + auto sched = tasks.back(); + tasks.pop_back(); + ProblemInstance instance(std::move(tasks), {}, {}, sched); + auto topology = MinimalTopologyBuilder(instance).getTopology(); + auto taskFeatures = extractor(topology, makeSolverState(Matrix<int>{}, scheduler), instance); + ASSERT_EQ(taskFeatures.numel(), 4); + EXPECT_FLOAT_EQ(taskFeatures[0][0].item<DataType>(), 0.4); + EXPECT_FLOAT_EQ(taskFeatures[0][1].item<DataType>(), 0.6); + EXPECT_FLOAT_EQ(taskFeatures[0][2].item<DataType>(), 0.2); + EXPECT_FLOAT_EQ(taskFeatures[0][3].item<DataType>(), 0.8); + extractor.legacyFeatures = true; + taskFeatures = extractor(topology, makeSolverState(Matrix<int>{}, scheduler), instance); + EXPECT_FLOAT_EQ(taskFeatures[0][0].item<DataType>(), 0.4); + EXPECT_FLOAT_EQ(taskFeatures[0][1].item<DataType>(), 0.6); + EXPECT_FLOAT_EQ(taskFeatures[0][2].item<DataType>(), -0.2); + EXPECT_FLOAT_EQ(taskFeatures[0][3].item<DataType>(), -0.2); +} + TEST(nn_feature_extractors, ResourceEnergyExtractor) { using namespace tempo::nn; using namespace tempo::serialization; using namespace tempo; using namespace tempo::testing; - constexpr bool SatisfiesConcept = feature_extractor<ResourceEnergyExtractor, Matrix<int>>; - EXPECT_TRUE(SatisfiesConcept); - ProblemInstance instance{.lowerBound = 0, .optimalSolution = 0, .durations = {1, 1, 1}, - .constraints = {}, .resources = {{{0, 1, 2}, {1, 1, 1}, {}, 1}}}; + auto [tasks, scheduler] = tempo::testing::createTasks({{6, 6}, {2, 4}, {1, 5}, {0, 12, 0, 12}}); + auto sched = tasks.back(); + tasks.pop_back(); + ProblemInstance instance(tasks, {{1, tasks, {1, 1, 1}}}, {}, sched); const auto topology = MinimalTopologyBuilder(instance).getTopology(); - Matrix<int> eventNet(8, 8); - setTaskDurations(0, 6, 6, 0, 0, eventNet); - setTaskDurations(1, 2, 4, 0, 0, eventNet); - setTaskDurations(2, 1, 5, 0, 0, eventNet); - setUpperBound(12, eventNet); ResourceEnergyExtractor extractor; - auto resEnergies = extractor(topology, eventNet); + auto resEnergies = extractor(topology, makeSolverState(Matrix<int>{}, scheduler), instance); ASSERT_EQ(resEnergies.size(0), 1); ASSERT_EQ(resEnergies.size(1), 1); EXPECT_EQ(resEnergies.item<DataType>(), 1); @@ -79,16 +98,13 @@ TEST(nn_features_extractors, ResourceEnergyExtractor_complex_consumptions) { using namespace tempo::serialization; using namespace tempo; using namespace tempo::testing; - ProblemInstance instance{.lowerBound = 0, .optimalSolution = 0, .durations = {1, 1, 1}, - .constraints = {}, .resources = {{{0, 1, 2}, {3, 2, 2}, {}, 4}}}; + auto [tasks, scheduler] = tempo::testing::createTasks({{4, 4}, {2, 4}, {8, 10}, {0, 9, 0, 9}}); + auto sched = tasks.back(); + tasks.pop_back(); + ProblemInstance instance(tasks, {{4, tasks, {3, 2, 2}}}, {}, sched); const auto topology = MinimalTopologyBuilder(instance).getTopology(); - Matrix<int> eventNet(8, 8); - setTaskDurations(0, 4, 4, 0, 0, eventNet); - setTaskDurations(1, 2, 4, 0, 0, eventNet); - setTaskDurations(2, 8, 10, 0, 0, eventNet); - setUpperBound(9, eventNet); ResourceEnergyExtractor extractor; - auto resEnergies = extractor(topology, eventNet); + auto resEnergies = extractor(topology, makeSolverState(Matrix<int>{}, scheduler), instance); ASSERT_EQ(resEnergies.size(0), 1); ASSERT_EQ(resEnergies.size(1), 1); EXPECT_EQ(resEnergies.item<DataType>(), 1); @@ -98,19 +114,15 @@ TEST(nn_feature_extractors, TimingEdgeExtractor) { using namespace tempo::nn; using namespace tempo::testing; using namespace tempo; - constexpr bool SatisfiesConcept = feature_extractor<TimingEdgeExtractor, Matrix<int>>; - EXPECT_TRUE(SatisfiesConcept); - tempo::Matrix<int> timings(8, 8); - setTaskDistance(0, 1, 4, timings); - setTaskDistance(1, 0, 3, timings); - setTaskDistance(2, 1, -2, timings); - setTaskDistance(1, 2, 5, timings); - setUpperBound(4, timings); - timings.at(1, 2) = 5; + tempo::Matrix<int> timings(3, 3, {0, 4, 0, + 3, 0, 5, + 0, -2, 0}); + auto [schedule, scheduler] = createTasks({{0, 4, 0, 4}}); Topology topology; EdgeVector edges{{0, 1}, {2, 1}}; topology.edgeIndices = util::makeIndexTensor(edges); - torch::Tensor features = TimingEdgeExtractor()(topology, timings); + torch::Tensor features = TimingEdgeExtractor()(topology, makeSolverState(timings, scheduler), + ProblemInstance({}, {}, {}, schedule.front())); ASSERT_EQ(features.size(0), edges.size()); ASSERT_EQ(features.size(1), 2); ASSERT_EQ(features.sizes().size(), 2); diff --git a/Tests/testing.cpp b/Tests/testing.cpp index 4bcd7dc2793069bbf48f8bf0e71125837b105269..3d84950f52dc1e79a6b3df0479c08dab0f7dcc28 100644 --- a/Tests/testing.cpp +++ b/Tests/testing.cpp @@ -8,5 +8,134 @@ namespace tempo::testing { + auto createTestProblem() -> std::pair<ProblemInstance, DummyScheduler> { + using namespace tempo; + auto [tasks, scheduler] = createTasks({TaskSpec{.minDur = 2, .maxDur = 5}, + TaskSpec{.minDur = 6, .maxDur = 6}, + TaskSpec{.minDur = 2, .maxDur = 7}, + TaskSpec{.minDur = 2, .maxDur = 3}, + TaskSpec{.minDur = 0, .maxDur = Constant::Infinity<int>}}); + auto schedule = tasks.back(); + tasks.pop_back(); + std::vector<Resource> resources{{2, {tasks[0], tasks[2]}, {2, 1}}, + {3, {tasks[1], tasks[2], tasks[3]}, {3, 1, 1}}}; + std::vector<DistanceConstraint<int>> precedences{{tasks[1].start.id(), tasks[0].end.id(), 0}}; + return {ProblemInstance(std::move(tasks), std::move(resources), std::move(precedences), schedule), + std::move(scheduler)} ; + } + + auto createExtendedTestProblem() -> std::pair<ProblemInstance, DummyScheduler> { + auto [problem, scheduler] = createTestProblem(); + auto &resources = const_cast<std::remove_cvref_t<decltype(problem.resources())>&>(problem.resources()); + resources.emplace_back(2, std::vector{problem.tasks().at(1), problem.tasks().at(3)}, std::vector{2, 1}); + return {std::move(problem), std::move(scheduler)}; + } + + + auto createTasks(const std::vector<TaskSpec> &specs) -> std::pair<std::vector<Interval<int>>, DummyScheduler> { + std::vector<Interval<int>> ret; + std::vector<int> upper(specs.size() * VarTaskMapping::NumTemporalVarPerTask, Constant::Infinity<int>); + std::vector<int> lower(specs.size() * VarTaskMapping::NumTemporalVarPerTask, 0); + unsigned idx = 0; + for (const auto &spec : specs) { + if (spec.minDur == spec.maxDur) { + ret.emplace_back(NumericVar{idx, 0}, NumericVar{idx, spec.minDur}, NumericVar{idx + 1, 0}); + } else { + ret.emplace_back(NumericVar{idx, 0}, NumericVar{idx + 1, 0}, NumericVar{idx + 2, 0}); + ++idx; + } + + lower.at(ret.back().duration.id()) = spec.minDur; + upper.at(ret.back().duration.id()) = spec.maxDur; + lower.at(ret.back().start.id()) = spec.earliestStart; + upper.at(ret.back().end.id()) = spec.latestDeadline; + idx += 2; + } + + return {std::move(ret), DummyScheduler(std::move(upper), std::move(lower))}; + } + + auto createDummyTasks(unsigned int numberOfTasks) -> std::vector<Interval<int>> { + std::vector<Interval<int>> ret; + ret.reserve(numberOfTasks); + for (unsigned i = 0; i < numberOfTasks; ++i) { + ret.emplace_back(NumericVar{i, 0}, NumericVar{i, 0}, NumericVar{i, 0}); + } + + return ret; + } + + auto createRandomProblem(std::size_t numTasks, std::size_t numResources, + double precedenceChance) -> std::tuple<ProblemInstance, DummyScheduler, Matrix<int>> { + assert(precedenceChance >= 0 and precedenceChance <= 1); + using namespace tempo; + std::vector<DistanceConstraint<int>> precedences; + Matrix<int> eventNetwork(2 * numTasks + 2, 2 * numTasks + 2); + int upperBound = 0; + std::vector<TaskSpec> taskSpecs; + taskSpecs.reserve(numTasks + 1); + for (int t = 0; t < static_cast<int>(numTasks); ++t) { + taskSpecs.emplace_back(random_int(2, 6), random_int(7, 10), random_int(-7, 0), random_int(-7, 0)); + upperBound += taskSpecs.back().maxDur; + } + + taskSpecs.emplace_back(upperBound / 2, upperBound, 0, upperBound); + auto [tasks, scheduler] = createTasks(taskSpecs); + auto sched = tasks.back(); + tasks.pop_back(); + Matrix<int> taskDistances(numTasks, numTasks, upperBound); + for (unsigned from = 0; from < numTasks; ++from) { + for(unsigned to = 0; to < numTasks; ++to) { + if (from == to) { + continue; + } + + taskDistances.at(from, to) = random_int(-20 * static_cast<int>(precedenceChance), + 20 * static_cast<int>(1 - precedenceChance)); + if (taskDistances.at(from, to) <= 0) { + precedences.emplace_back(tasks.at(from).start.id(), tasks.at(to).end.id(), + eventNetwork.at(from, to)); + } + } + } + + std::vector<Resource> resources; + resources.reserve(numResources); + for (auto r = 0ul; r < numResources; ++r) { + decltype(tasks) consuming; + std::vector<int> demands; + const auto capacity = random_int(1, 5); + for (auto t = 0ul; t < numTasks; ++t) { + auto demand = random_int(0, capacity); + if (demand > 0) { + consuming.emplace_back(tasks.at(t)); + demands.emplace_back(demand); + } + } + + resources.emplace_back(capacity, std::move(consuming), std::move(demands)); + } + + return {ProblemInstance(std::move(tasks), std::move(resources), std::move(precedences), sched), + std::move(scheduler), std::move(taskDistances)}; + + } + + Resource::Resource(int capacity, std::vector<Interval<int>> tasks, std::vector<int> demands) + : std::vector<Interval<int>>(std::move(tasks)), demands(std::move(demands)), capacity(capacity) {} + + int Resource::getDemand(unsigned int taskId) const { + return demands.at(taskId); + } + + int Resource::resourceCapacity() const { + return capacity; + } + + BoundProvider::BoundProvider(std::vector<int> upper, std::vector<int> lower) : u(std::move(upper)), l(std::move(lower)) {} + + int BoundProvider::upper(tempo::var_t var) const { return u.at(var); } + + int BoundProvider::lower(tempo::var_t var) const { return l.at(var); } } diff --git a/Tests/testing.hpp b/Tests/testing.hpp index 7b00f2035282e309e7810c9f1f8533cc1cc82f40..d6b4ff8911d6636e0e745a7c47809a83c8543387 100644 --- a/Tests/testing.hpp +++ b/Tests/testing.hpp @@ -3,25 +3,66 @@ * @date 27.04.23. */ -#ifndef SCHEDCL_TESTING_HPP -#define SCHEDCL_TESTING_HPP +#ifndef TEMPO_TESTING_HPP +#define TEMPO_TESTING_HPP #include <filesystem> #include <random> #include <concepts> +#include <utility> +#include <tuple> #include "util/Matrix.hpp" #include "util/serialization.hpp" #include "Global.hpp" +#include "util/SchedulingProblemHelper.hpp" +#include "Model.hpp" namespace tempo::testing { struct TestData { - static constexpr auto TestProblem = __TEST_DATA_DIR__ "/test_problem.json"; - static constexpr auto ExtendedTestProblem = __TEST_DATA_DIR__ "/extended_test_problem.json"; static constexpr auto GraphBuilderConfig = __TEST_DATA_DIR__ "/graph_builder_config.json"; static constexpr auto TestNN = __TEST_DATA_DIR__ "/Identity_export.pt"; }; + struct Resource : public std::vector<Interval<int>> { + std::vector<int> demands; + int capacity; + Resource(int capacity, std::vector<Interval<int>> tasks, std::vector<int> demands); + [[nodiscard]] int getDemand(unsigned taskId) const; + [[nodiscard]] int resourceCapacity() const; + }; + + using ProblemInstance = tempo::SchedulingProblemHelper<int, Resource>; + + class BoundProvider { + std::vector<int> u, l; + public: + BoundProvider(std::vector<int> upper, std::vector<int> lower); + [[nodiscard]] int upper(tempo::var_t var) const; + [[nodiscard]] int lower(tempo::var_t var) const; + }; + + struct DummyScheduler { + tempo::testing::BoundProvider numeric; + template<typename ...Args> + explicit DummyScheduler(Args &&...args): numeric(std::forward<Args>(args)...) {} + }; + + + auto createTestProblem() -> std::pair<ProblemInstance, DummyScheduler>; + + auto createExtendedTestProblem() -> std::pair<ProblemInstance, DummyScheduler>; + + auto createRandomProblem(std::size_t numTasks, std::size_t numResources, + double precedenceChance = 0.3) -> std::tuple<ProblemInstance, DummyScheduler, Matrix<int>>; + + struct TaskSpec { + int minDur, maxDur, earliestStart{0}, latestDeadline{0}; + }; + + auto createTasks(const std::vector<TaskSpec> &specs) -> std::pair<std::vector<Interval<int>>, DummyScheduler>; + + auto createDummyTasks(unsigned numberOfTasks) -> std::vector<Interval<int>>; /** * Generates a random integer value in [min, max] @@ -52,11 +93,6 @@ namespace tempo::testing { std::uniform_real_distribution<T> dist(min, max); return dist(el); } - - struct TaskSpec { - int minDur, maxDur, release, deadline; - }; - } -#endif //SCHEDCL_TESTING_HPP +#endif //TEMPO_TESTING_HPP diff --git a/Tests/util/scheduling_problem_utils.cpp b/Tests/util/scheduling_problem_utils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..df4b6b8b74ed9985b1c73271dea7767963cc52cd --- /dev/null +++ b/Tests/util/scheduling_problem_utils.cpp @@ -0,0 +1,95 @@ +/** +* @author Tim Luchterhand +* @date 01.07.24 +* @brief +*/ + +#include <gtest/gtest.h> + +#include "util/SchedulingProblemHelper.hpp" +#include "Model.hpp" +#include "testing.hpp" + +TEST(util, VarTaskMapping) { + using namespace tempo; + std::vector<Interval<>> tasks(3); + tasks[0].start = {4, 0}; + tasks[0].end = {4, 7}; + tasks[1].start = {6, 0}; + tasks[1].end = {7, 0}; + tasks[2].start = {5, 0}; + tasks[2].end = {5, 2}; + VarTaskMapping mapping(tasks); + EXPECT_TRUE(mapping.contains(6)); + EXPECT_TRUE(mapping.contains(4)); + EXPECT_FALSE(mapping.contains(-2)); + EXPECT_FALSE(mapping.contains(8)); + EXPECT_EQ(mapping(5), 2); + EXPECT_EQ(mapping(7), mapping(6)); + EXPECT_EQ(mapping(4), 0); + std::vector<Interval<>> empty; + VarTaskMapping emptyMapping(empty); + EXPECT_FALSE(emptyMapping.contains(0)); + EXPECT_FALSE(emptyMapping.contains(3)); +} + +TEST(util, VarTaskMapping_non_continuous) { + using namespace tempo; + std::vector<Interval<>> tasks(3); + tasks[0].start = {4, 0}; + tasks[0].end = {5, 0}; + tasks[1].start = {18, 0}; + tasks[1].end = {19, 0}; + tasks[2].start = {9, 0}; + tasks[2].end = {10, 2}; + EXPECT_THROW(VarTaskMapping{tasks}, std::runtime_error); +} + +TEST(util, SchedulingProblemHelper_basic) { + using namespace tempo; + using T = tempo::testing::TaskSpec; + auto [tasks, _] = tempo::testing::createTasks( + {T{.minDur = 3, .maxDur = 6}, T{.minDur = 5, .maxDur = 5}, T{.minDur = 1, .maxDur = 7}, + T{.minDur = 0, .maxDur = 100}}); + auto sched = tasks.back(); + tasks.pop_back(); + SchedulingProblemHelper<int, tempo::testing::Resource> schedulingProb(tasks, {}, {}, sched); + EXPECT_EQ(tasks, schedulingProb.tasks()); + EXPECT_TRUE(schedulingProb.hasTask(0)); + EXPECT_TRUE(schedulingProb.hasTask(1)); + EXPECT_TRUE(schedulingProb.hasTask(2)); + EXPECT_FALSE(schedulingProb.hasTask(3)); + + EXPECT_TRUE(schedulingProb.hasVariable(tasks[0].start.id())); + EXPECT_TRUE(schedulingProb.hasVariable(tasks[0].end.id())); + EXPECT_TRUE(schedulingProb.hasVariable(tasks[1].start.id())); + EXPECT_TRUE(schedulingProb.hasVariable(tasks[1].end.id())); + EXPECT_TRUE(schedulingProb.hasVariable(tasks[2].start.id())); + EXPECT_TRUE(schedulingProb.hasVariable(tasks[2].end.id())); + EXPECT_FALSE(schedulingProb.hasVariable(tasks[1].duration.id())); + EXPECT_FALSE(schedulingProb.hasVariable(120)); + + EXPECT_EQ(schedulingProb.getTask(tasks.back().end.id()), tasks.back()); + EXPECT_EQ(schedulingProb.getTask(tasks.front().id()), tasks.front()); +} + +TEST(util, SchedulingProblemView_task_distances) { + using namespace tempo; + std::vector<Interval<int>> tasks{{{0, 0}, {1, 0}, NumericVar{}}, + {{2, 0}, {2, 5}, NumericVar{}}, + {{3, 0}, {4, 0}, NumericVar{}}}; + SchedulingProblemHelper<int, tempo::testing::Resource> schedulingProb(tasks, {}, {}, Interval<int>{}); + BacktrackEnvironment env; + DirectedGraph<LabeledEdge<int>> graph(5, &env); + graph.emplace_edge(2, 4, -2); + graph.emplace_edge(0, 2, -1); + graph.emplace_edge(0, 3, 4); + tempo::testing::BoundProvider bounds({6, 5, 3, 8, 4}, {3, 2, 0, 4, 1}); + auto distance = schedulingProb.getTaskDistances(graph, bounds); + EXPECT_EQ(distance(0, 1), 4); + EXPECT_EQ(distance(1, 0), 1); + EXPECT_EQ(distance(0, 2), 5); + EXPECT_EQ(distance(2, 0), 6); + EXPECT_EQ(distance(2, 1), 13); + EXPECT_EQ(distance(1, 2), -2); +} \ No newline at end of file diff --git a/src/cpp/heuristics/ValueHeuristicsManager.cpp b/src/cpp/heuristics/ValueHeuristicsManager.cpp deleted file mode 100644 index 760d08566b6052f7d270853912e7fc201973b415..0000000000000000000000000000000000000000 --- a/src/cpp/heuristics/ValueHeuristicsManager.cpp +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @author Tim Luchterhand - * @date 06.05.24 - * @brief - */ - -#include <stdexcept> - -#include "heuristics/ValueHeuristicsManager.hpp" - -namespace tempo::heuristics { - -auto valHeuristicTypeToString(Options::PolarityHeuristic type) -> std::string { - using enum Options::PolarityHeuristic; - switch (type) { - case Tightest: - return "TightestValue"; - case SolutionGuided: - return "SolutionGuided"; - case Random: - return "RandomBinaryValue"; - default: - throw std::runtime_error("unknown value heuristic type"); - } -} -} // namespace tempo::heuristics \ No newline at end of file diff --git a/src/cpp/heuristics/heuristic_factories.cpp b/src/cpp/heuristics/heuristic_factories.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4e4d1f46847e6db6795f15094c9a802f781acc42 --- /dev/null +++ b/src/cpp/heuristics/heuristic_factories.cpp @@ -0,0 +1,40 @@ +/** +* @author Tim Luchterhand +* @date 09.07.24 +* @brief +*/ + +#include <stdexcept> + +#include "heuristics/heuristic_factories.hpp" + +#define RETURN_NAME(NAME) return #NAME + +namespace tempo::heuristics::detail { + + auto getVarHName(Options::ChoicePointHeuristics heuristic) -> std::string { + switch (heuristic) { + case Options::ChoicePointHeuristics::Tightest: + RETURN_NAME(Tightest); + case Options::ChoicePointHeuristics::WeightedDegree: + RETURN_NAME(WeightedDegree); + case Options::ChoicePointHeuristics::VSIDS: + RETURN_NAME(VSIDS); + default: + throw std::runtime_error("unknown variable heuristic"); + } + } + + auto getValHName(Options::PolarityHeuristic heuristic) -> std::string { + switch (heuristic) { + case Options::PolarityHeuristic::Tightest: + RETURN_NAME(TightestValue); + case Options::PolarityHeuristic::SolutionGuided: + throw std::runtime_error("not implemented yet"); + case Options::PolarityHeuristic::Random: + RETURN_NAME(RandomBinaryValue); + default: + throw std::runtime_error("unknown value heuristic type"); + } + } +} \ No newline at end of file diff --git a/src/cpp/nn/GNNEdgePolarityPredictor.cpp b/src/cpp/nn/GNNEdgePolarityPredictor.cpp index 32eaf707e47fc709ebb28d6645e2437773fc30b5..8f601ce6059dbef48e58889c2efe3a0aa6bb9b4a 100644 --- a/src/cpp/nn/GNNEdgePolarityPredictor.cpp +++ b/src/cpp/nn/GNNEdgePolarityPredictor.cpp @@ -11,20 +11,12 @@ namespace tempo::nn::heuristics { - GNNEdgePolarityPredictor::GNNEdgePolarityPredictor(const std::filesystem::path &modelLocation, - const std::filesystem::path &featureExtractorConfigLocation, - const ProblemInstance &problemInstance) : - gnn(modelLocation), - graphBuilder(featureExtractorConfigLocation, problemInstance) {} - - bool GNNEdgePolarityPredictor::choosePolarityFromHeatMap(event from, event to, const Matrix<DataType> &heatMap) { - from = TASK(from); - to = TASK(to); - const auto edgeProb = heatMap(from, to); - const auto inverseProb = heatMap(to, from); + bool detail::choosePolarityFromHeatMap(unsigned taskFrom, unsigned taskTo, const Matrix<DataType> &heatMap) { + const auto edgeProb = heatMap(taskFrom, taskTo); + const auto inverseProb = heatMap(taskTo, taskFrom); if (edgeProb == GNN::NoValue or inverseProb == GNN::NoValue) { std::stringstream ss; - ss << "Invalid edge in GNN: task edge " << from << " -> " << to << std::endl; + ss << "Invalid edge in GNN: task edge " << taskFrom << " -> " << taskTo << std::endl; throw std::runtime_error(ss.str()); } diff --git a/src/cpp/nn/GraphBuilder.cpp b/src/cpp/nn/GraphBuilder.cpp index e81b13ad4b468dc00e1d1247533da69b8ee9ccc6..ab9e9548f4afe04bd5191828600c63ebf62cf4c7 100644 --- a/src/cpp/nn/GraphBuilder.cpp +++ b/src/cpp/nn/GraphBuilder.cpp @@ -6,16 +6,4 @@ #include "nn/GraphBuilder.hpp" namespace tempo::nn { - - GraphBuilder::GraphBuilder(const std::filesystem::path &configPath, const ProblemInstance &problemInstance) { - auto data = serialization::deserializeFromFile<GraphBuilderConfig>(configPath); - topologyExtractor = TopologyBuilderFactory::getInstance().create(data.topologyExtractor.extractorName, - problemInstance); - taskFeatureExtractor = FeatureExtractorFactory::getInstance().create( - data.taskFeatureExtractor.extractorName, data.taskFeatureExtractor.arguments); - resourceFeatureExtractor = FeatureExtractorFactory::getInstance().create( - data.resourceFeatureExtractor.extractorName, data.resourceFeatureExtractor.arguments); - edgeFeatureExtractor = FeatureExtractorFactory::getInstance().create( - data.edgeFeatureExtractor.extractorName, data.edgeFeatureExtractor.arguments); - } } \ No newline at end of file diff --git a/src/cpp/nn/topology_extractors.cpp b/src/cpp/nn/topology_extractors.cpp index c57d71a8ebfd4a172d7787f04b0e417baf745415..772bc658293d5842b72804ff01098de4bae55ea2 100644 --- a/src/cpp/nn/topology_extractors.cpp +++ b/src/cpp/nn/topology_extractors.cpp @@ -11,29 +11,8 @@ namespace tempo::nn { - MinimalTopologyBuilder::MinimalTopologyBuilder(const ProblemInstance &problem) { - using namespace iterators; - cache.numTasks = problem.durations.size(); - cache.numResources = problem.resources.size(); - impl::TopologyData topologyData{.edgeLookup = impl::EdgeLookup(cache.numTasks * 2 + 2)}; - for (auto [r, resourceSpec]: enumerate(problem.resources, 0l)) { - completeSubGraph(resourceSpec, r, topologyData); - } - - addPrecedenceEdges(problem.constraints, topologyData); - cache.edgeIndices = util::makeIndexTensor(topologyData.edges); - cache.edgePairMask = torch::from_blob(topologyData.edgePairMask.data(), - static_cast<long>(topologyData.edgePairMask.size()), - indexTensorOptions()).clone(); - cache.edgeResourceRelations = util::makeIndexTensor(topologyData.edgeIdx, topologyData.edgeRelResIdx); - cache.resourceDependencies = util::makeIndexTensor(topologyData.taskIdx, topologyData.resIdx); - cache.resourceDemands = torch::from_blob(topologyData.resDemands.data(), - {static_cast<long>(topologyData.taskIdx.size()), 1}, - dataTensorOptions()).clone(); - } - auto MinimalTopologyBuilder::getTopology() const -> const Topology& { - return getTopology(makeSolverState(Matrix<int>{})); + return getTopology(makeSolverState(Matrix<int>{}, 0)); } void MinimalTopologyBuilder::addEdge(const Edge &e, bool isResourceEdge, IndexType maskVal, impl::TopologyData &topologyData) { @@ -48,32 +27,6 @@ namespace tempo::nn { topologyData.edgePairMask.emplace_back(maskVal); } - void MinimalTopologyBuilder::completeSubGraph(const Resource<int> &resourceSpec, IndexType resource, - impl::TopologyData &topologyData) { - const auto &tasks = resourceSpec; - for (std::size_t i = 0; i < tasks.size(); ++i) { - topologyData.taskIdx.emplace_back(tasks[i]); - topologyData.resIdx.emplace_back(resource); - topologyData.resDemands.emplace_back(static_cast<DataType>(resourceSpec.getDemand(i)) / - static_cast<DataType>(resourceSpec.capacity)); - for (std::size_t j = i + 1; j < tasks.size(); ++j) { - Edge e(tasks[i], tasks[j]); - Edge rev(tasks[j], tasks[i]); - topologyData.edgeRelResIdx.emplace_back(resource); - topologyData.edgeRelResIdx.emplace_back(resource); - if (topologyData.edgeLookup.contains(e)) { - topologyData.edgeIdx.emplace_back(topologyData.edgeLookup(e)); - topologyData.edgeIdx.emplace_back(topologyData.edgeLookup(rev)); - continue; - } - - addEdge(e, true, topologyData.pairMaskVal, topologyData); - addEdge(rev, true, topologyData.pairMaskVal, topologyData); - ++topologyData.pairMaskVal; - } - } - } - std::size_t impl::EdgeLookup::getNumEdges() const { std::size_t ret = 0; this->for_each([&ret](auto val) { ret += (val != EdgeLookup::NoValue); }); diff --git a/src/cpp/util/Options.cpp b/src/cpp/util/Options.cpp index a1ba62d6d9f1c8d5274907a44ef8e973cd862dac..dac328eb398402463346f75ccffcf23b7a50b88e 100755 --- a/src/cpp/util/Options.cpp +++ b/src/cpp/util/Options.cpp @@ -7,202 +7,184 @@ // using namespace schedcl; -struct argbase { - virtual ~argbase() {} - virtual void assign() = 0; -}; - -template <typename Opt, typename ClapArg, typename E = void> -struct arg : public argbase { - ClapArg carg; - Opt &opt; - - template <typename... T> - arg(TCLAP::CmdLine &cmd, Opt &opt, T &&...args) - : carg(std::forward<T>(args)...), opt(opt) { - cmd.add(carg); - } - - virtual void assign() override { opt = carg.getValue(); } -}; - -template <typename Opt, typename ClapArg> -struct arg<Opt, ClapArg, typename std::enable_if<std::is_enum<Opt>{}>::type> - : public argbase { - ClapArg carg; - Opt &opt; - - template <typename... T> - arg(TCLAP::CmdLine &cmd, Opt &opt, T &&...args) - : carg(std::forward<T>(args)...), opt(opt) { - cmd.add(carg); - } - - virtual void assign() override { - opt = - static_cast<typename std::remove_reference<Opt>::type>(carg.getValue()); - } -}; - -struct cmdline { - TCLAP::CmdLine cmd; - std::vector<std::unique_ptr<argbase>> args; - - cmdline(const std::string &message, const char delimiter = ' ', - const std::string &version = "none", bool helpAndVersion = true) - : cmd(message, delimiter, version, helpAndVersion) {} - - template <typename ClapArg, typename Opt, typename... T> - void add(Opt &opt, T &&...clapargs) { - args.emplace_back(std::move(std::make_unique<arg<Opt, ClapArg>>( - cmd, opt, std::forward<T>(clapargs)...))); - } - - void parse(int argc, char *argv[]) { - cmd.parse(argc, argv); - for (auto &arg : args) - arg->assign(); - } -}; tempo::Options tempo::parse(int argc, char *argv[]) { using namespace TCLAP; using namespace std::string_literals; - cmdline cmd("schedcl", ' '); - - Options opt; - opt.cmdline = - accumulate(argv, argv + argc, ""s, [&](std::string acc, const char *arg) { - return acc + " " + arg; - }); - - cmd.add<UnlabeledValueArg<std::string>>(opt.instance_file, "file", - "instance file", true, "", "string"); - - cmd.add<ValueArg<int>>( - opt.verbosity, "", "verbosity", - "verbosity level (0:silent,1:quiet,2:improvements only,3:verbose", false, - 2, "int"); - - cmd.add<ValueArg<int>>(opt.seed, "", "seed", "random seed", false, 1, "int"); - - cmd.add<ValueArg<int>>(opt.ub, "", "ub", "initial ub", false, - std::numeric_limits<int>::max(), "int"); - - cmd.add<SwitchArg>(opt.print_sol, "", "print-sol", - "print the best found schedule", false); - - cmd.add<SwitchArg>(opt.print_par, "", "print-par", "print the paramters", - false); - - cmd.add<SwitchArg>(opt.print_mod, "", "print-mod", "print the model", false); - - cmd.add<SwitchArg>(opt.print_ins, "", "print-ins", "print the instance", - false); - - cmd.add<SwitchArg>(opt.print_sta, "", "print-sta", "print the statistics", - false); - - cmd.add<SwitchArg>(opt.print_cmd, "", "print-cmd", "print the command-line", - false); - - cmd.add<ValueArg<std::string>>(opt.dbg_file, "", "dbg", - "Clause-learning dbg file []", false, "", - "string"); - - cmd.add<SwitchArg>(opt.learning, "", "learning", "learn clauses", false); - cmd.add<SwitchArg>(opt.learning, "", "no-learning", - "do not use clause learning", true); - - cmd.add<SwitchArg>(opt.edge_finding, "", "edge-finding", "use edge-finding", - false); - cmd.add<SwitchArg>(opt.edge_finding, "", "no-edge-finding", - "do not use edge-finding", true); - - cmd.add<SwitchArg>(opt.transitivity, "", "transitivity", - "use transitivity reasoning", false); - cmd.add<SwitchArg>(opt.transitivity, "", "no-transitivity", - "do not use transitivity reasoning", true); - - cmd.add<SwitchArg>(opt.dichotomy, "", "dichotomy", "use dichotomic search", - false); - - cmd.add<SwitchArg>(opt.full_up, "", "full-up", - "unit-propagate bound literals", false); - - cmd.add<SwitchArg>(opt.order_bound_watch, "", "order-watched", - "order bound watched lists", false); - - cmd.add<ValueArg<int>>(opt.choice_point_heuristics, "", "cp-heuristic", - "type of heuristic used for choice point selection " - "(0: Tightest, 1: WDEG, 2: VSIDS (default))", - false, 2, "int"); - cmd.add<ValueArg<int>>( - opt.polarity_heuristic, "", "polarity-heuristic", - "type " - "of heuristic used for choice point polarity selection " - "(0: tightest (default), 1: solution guided, 2: random)", - false, 0, "int"); - - cmd.add<ValueArg<double>>( - opt.polarity_epsilon, "", "polarity-epsilon", - "epsilon greedy value for value selection. probability in [0, 1]." - "0 means that the value selected by the heuristic is always" - "chosen, 1 means always random. default: 0.01", - false, 0.01, "double"); - - cmd.add<ValueArg<double>>( - opt.vsids_decay, "", "vsids-decay", - "decay value for the vsids heuristic, only effective if VSIDS is used " - "as choice point heuristic", - false, 0.999, "double"); - - cmd.add<ValueArg<double>>(opt.forgetfulness, "", "forgetfulness", - "clause base reduction factor (0.3)", false, 0.3, - "double"); - - cmd.add<ValueArg<int>>( - opt.minimization, "", "clause-minimization", - "depth for clause minimization (default 1)", - false, 1, "int"); - + Parser p = getBaseParser(); + p.getOptions().cmdline = + accumulate(argv, argv + argc, ""s, [&](const std::string& acc, const char *arg) { + return acc + " " + arg; + }); + + + p.parse(argc, argv); + return p.getOptions(); +} + +auto tempo::getBaseParser() -> Parser { + using namespace TCLAP; + Parser p; + auto &cmd = p.getCmdLine(); + auto &opt = p.getOptions(); + cmd.add<UnlabeledValueArg<std::string>>(opt.instance_file, "file", + "instance file", true, "", "string"); + + cmd.add<ValueArg<int>>( + opt.verbosity, "", "verbosity", + "verbosity level (0:silent,1:quiet,2:improvements only,3:verbose", false, + 2, "int"); + + cmd.add<ValueArg<int>>(opt.seed, "", "seed", "random seed", false, 1, "int"); + + cmd.add<ValueArg<int>>(opt.ub, "", "ub", "initial ub", false, + std::numeric_limits<int>::max(), "int"); + + cmd.add<SwitchArg>(opt.print_sol, "", "print-sol", + "print the best found schedule", false); + + cmd.add<SwitchArg>(opt.print_par, "", "print-par", "print the paramters", + false); + + cmd.add<SwitchArg>(opt.print_mod, "", "print-mod", "print the model", false); + + cmd.add<SwitchArg>(opt.print_ins, "", "print-ins", "print the instance", + false); + + cmd.add<SwitchArg>(opt.print_sta, "", "print-sta", "print the statistics", + false); + + cmd.add<SwitchArg>(opt.print_cmd, "", "print-cmd", "print the command-line", + false); + + cmd.add<ValueArg<std::string>>(opt.dbg_file, "", "dbg", + "Clause-learning dbg file []", false, "", + "string"); + + cmd.add<SwitchArg>(opt.learning, "", "learning", "learn clauses", false); + cmd.add<SwitchArg>(opt.learning, "", "no-learning", + "do not use clause learning", true); + + cmd.add<SwitchArg>(opt.edge_finding, "", "edge-finding", "use edge-finding", + false); + cmd.add<SwitchArg>(opt.edge_finding, "", "no-edge-finding", + "do not use edge-finding", true); + + cmd.add<SwitchArg>(opt.transitivity, "", "transitivity", + "use transitivity reasoning", false); + cmd.add<SwitchArg>(opt.transitivity, "", "no-transitivity", + "do not use transitivity reasoning", true); + + cmd.add<SwitchArg>(opt.dichotomy, "", "dichotomy", "use dichotomic search", + false); + + cmd.add<SwitchArg>(opt.full_up, "", "full-up", + "unit-propagate bound literals", false); + + cmd.add<SwitchArg>(opt.order_bound_watch, "", "order-watched", + "order bound watched lists", false); + + cmd.add<ValueArg<int>>(opt.choice_point_heuristics, "", "cp-heuristic", + "type of heuristic used for choice point selection " + "(0: Tightest, 1: WDEG, 2: VSIDS (default))", + false, 2, "int"); cmd.add<ValueArg<int>>( - opt.greedy_runs, "", "greedy-runs", - "number of randomized greedy runs (default 1)", - false, 1, "int"); - - cmd.add<ValueArg<int>>(opt.forget_strategy, "", "forget-strategy", - "strategy for clause forgetting " - "(0: size (default), 1: literal looseness," - "2: literal activity 3: looseness / activity", - false, 3, "int"); - - cmd.add<ValueArg<std::string>>(opt.restart_policy, "", "restart", - "choice of restart policy (no, luby, geom)", - false, "geom", "string"); - - cmd.add<ValueArg<int>>( - opt.restart_base, "", "restart-base", - "base of the sequence for geometric/luby restarts (default=100)", false, - 128, "int"); - - cmd.add<ValueArg<double>>(opt.restart_factor, "", "restart-factor", - "factor of the geometric sequence (default=1.2)", - false, 1.2, "double"); - - cmd.add<ValueArg<double>>(opt.vsids_epsilon, "", "vsids-epsilon", - "epsilon value for the epsilon greedy vsids " - "heuristic, only effective if epsilon greedy " - "VSIDS is used as choice point heuristic", - false, 0.05, "double"); - - cmd.add<ValueArg<std::string>>( - opt.input_format, "", "input-format", - "format of input file " - "(osp: Open Shop (default), jsp: Job Shop " - "(Lawrence), tsptw: TSP with Time Windows, jstl: JSP with Time Lags)", - false, "osp", "string"); - - cmd.parse(argc, argv); - return opt; + opt.polarity_heuristic, "", "polarity-heuristic", + "type " + "of heuristic used for choice point polarity selection " + "(0: tightest (default), 1: solution guided, 2: random)", + false, 0, "int"); + + cmd.add<ValueArg<double>>( + opt.polarity_epsilon, "", "polarity-epsilon", + "epsilon greedy value for value selection. probability in [0, 1]." + "0 means that the value selected by the heuristic is always" + "chosen, 1 means always random. default: 0.01", + false, 0.01, "double"); + + cmd.add<ValueArg<double>>( + opt.vsids_decay, "", "vsids-decay", + "decay value for the vsids heuristic, only effective if VSIDS is used " + "as choice point heuristic", + false, 0.999, "double"); + + cmd.add<ValueArg<double>>(opt.forgetfulness, "", "forgetfulness", + "clause base reduction factor (0.3)", false, 0.3, + "double"); + + cmd.add<ValueArg<int>>( + opt.minimization, "", "clause-minimization", + "depth for clause minimization (default 1)", + false, 1, "int"); + + cmd.add<ValueArg<int>>( + opt.greedy_runs, "", "greedy-runs", + "number of randomized greedy runs (default 1)", + false, 1, "int"); + + cmd.add<ValueArg<int>>(opt.forget_strategy, "", "forget-strategy", + "strategy for clause forgetting " + "(0: size (default), 1: literal looseness," + "2: literal activity 3: looseness / activity", + false, 3, "int"); + + cmd.add<ValueArg<std::string>>(opt.restart_policy, "", "restart", + "choice of restart policy (no, luby, geom)", + false, "geom", "string"); + + cmd.add<ValueArg<int>>( + opt.restart_base, "", "restart-base", + "base of the sequence for geometric/luby restarts (default=100)", false, + 128, "int"); + + cmd.add<ValueArg<double>>(opt.restart_factor, "", "restart-factor", + "factor of the geometric sequence (default=1.2)", + false, 1.2, "double"); + + cmd.add<ValueArg<double>>(opt.vsids_epsilon, "", "vsids-epsilon", + "epsilon value for the epsilon greedy vsids " + "heuristic, only effective if epsilon greedy " + "VSIDS is used as choice point heuristic", + false, 0.05, "double"); + + cmd.add<ValueArg<std::string>>( + opt.input_format, "", "input-format", + "format of input file " + "(osp: Open Shop (default), jsp: Job Shop " + "(Lawrence), tsptw: TSP with Time Windows, jstl: JSP with Time Lags)", + false, "osp", "string"); + return p; +} + +cmdline::cmdline(const std::string &message, const char delimiter, const std::string &version, bool helpAndVersion) + : cmd(message, delimiter, version, helpAndVersion) {} + +void cmdline::parse(int argc, char **argv) { + cmd.parse(argc, argv); + for (auto &arg: args) { + arg->assign(); + } +} + +tempo::Parser::Parser(const std::string &name) : options(std::make_unique<Options>()), + cmdLine(std::make_unique<cmdline>(name, ' ')) {} + +auto tempo::Parser::getOptions() noexcept -> tempo::Options & { + return *options; +} + +auto tempo::Parser::getOptions() const noexcept -> const tempo::Options & { + return *options; +} + +auto tempo::Parser::getCmdLine() noexcept -> cmdline & { + return *cmdLine; +} + +auto tempo::Parser::getCmdLine() const noexcept -> const cmdline & { + return *cmdLine; +} + +void tempo::Parser::parse(int argc, char **argv) { + cmdLine->parse(argc, argv); } diff --git a/src/cpp/util/SchedulingProblemHelper.cpp b/src/cpp/util/SchedulingProblemHelper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..df0bf641623794d8a2fce7f544fa52173781e6d5 --- /dev/null +++ b/src/cpp/util/SchedulingProblemHelper.cpp @@ -0,0 +1,19 @@ +/** +* @author Tim Luchterhand +* @date 01.07.24 +* @brief +*/ + +#include "util/SchedulingProblemHelper.hpp" + +namespace tempo{ + + unsigned VarTaskMapping::operator()(var_t variable) const noexcept { + return varToTask[variable - offset]; + } + + bool VarTaskMapping::contains(var_t variable) const noexcept { + const var_t idx = variable - offset; + return idx < varToTask.size() and varToTask[idx] != NoTask; + } +} \ No newline at end of file diff --git a/src/examples/checkcl.cpp b/src/examples/checkcl.cpp deleted file mode 100644 index fc05e990ccd544fe1be04d0867d5c4bf859af3a8..0000000000000000000000000000000000000000 --- a/src/examples/checkcl.cpp +++ /dev/null @@ -1,191 +0,0 @@ -/************************************************************************* -minicsp - -Copyright 2010--2011 George Katsirelos - -Minicsp is free software: you can redistribute it and/or modify it -under the terms of the GNU General Public License as published by the -Free Software Foundation, either version 3 of the License, or (at your -option) any later version. - -Minicsp is distributed in the hope that it will be useful, but WITHOUT -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -for more details. - -You should have received a copy of the GNU General Public License -along with minicsp. If not, see <http://www.gnu.org/licenses/>. - -*************************************************************************/ - - -#include <chrono> -#include <iostream> -#include <vector> -#include <filesystem> -#include <optional> - -#include "Scheduler.hpp" -#include "util/parsing/format.hpp" -#include "util/parsing/jsp.hpp" -#include "util/parsing/osp.hpp" - - -using namespace tempo; - - -int main(int argc, char *argv[]) { - Options opt = tempo::parse(argc, argv); - - seed(opt.seed); - - ProblemInstance data; - - size_t num_fails{0}; - size_t num_trivial{0}; - size_t num_search{0}; - - if (opt.input_format == "osp") { - data = osp::read_instance(opt.instance_file); - } - else if (opt.input_format == "jsp") { - data = jsp::read_instance(opt.instance_file); - } - - Scheduler<int> S(opt); - for (auto d : data.durations) { - S.newTask(d, d); - } - - for (auto [x, y, d] : data.constraints) { - - assert(d==0); - - S.newPrecedence(x, y, d); - - } - - for (auto &job : data.resources) { - for (auto ti{job.begin()}; ti!=job.end(); ++ti) { - for (auto tj{ti+1}; tj!=job.end(); ++tj) { - S.newVariable({START(*ti), END(*tj), 0}, {START(*tj), END(*ti), 0}); - } - } - } - -// S.display(std::cout, true, true); - -// std::cout << "\nsave:\n" << S << std::endl; - - std::ifstream cl_file("mcl.txt", std::ifstream::in); - - int t, n, x, y, d; - - std::vector<int> X; - std::vector<int> Y; - std::vector<int> D; - - int line{0}; - do { - - X.clear(); - Y.clear(); - D.clear(); - - S.saveState(); - - cl_file >> t; - cl_file >> n; - if (not cl_file.good()) - break; - - bool trivially_unsat{false}; - - for (auto i{0}; i < n; ++i) { - cl_file >> x; - cl_file >> y; - cl_file >> d; - - X.push_back(x); - Y.push_back(y); - D.push_back(d); - - // std::cout << "add " << prettyEvent(y) << " - " << - // prettyEvent(x) << " <= " << d << std::endl; - - try { - S.newMaximumLag(x, y, d); - } catch (Failure &f) { - trivially_unsat = true; - } - } - - // std::cout << "\nsolve:\n" << S << std::endl; - - if (not trivially_unsat) { - // std::cout << "ok (trivial)\n"; - // } else { - - // std::cout << S << std::endl; - // exit(1); - - bool need_search{true}; - try { - S.propagate(); - } catch (Failure &f) { - need_search = false; - } - - if (need_search) { - - // std::cout << S << std::endl; - - auto nf{S.num_fails}; - S.search(); - - if (S.satisfiable()) { - std::cout << "cl " << line << " (" << (t ? "expl" : "cut") << "): "; - std::cout << "bug!\n"; - - for (size_t i{0}; i < X.size(); ++i) { - std::cout << "> " << prettyEvent(Y[i]) << " - " << prettyEvent(X[i]) - << " <= " << D[i] << std::endl; - } - - exit(1); - } - // else { - // std::cout << "UNSAT\n"; - // } - - ++num_search; - num_fails += (S.num_fails - nf); - } - // else { - // std::cout << "ok\n"; - // } - } else { - ++num_trivial; - } - ++line; - - // std::cout << S.env.level() << " --> 0" - - S.restoreState(0); - - // std::cout << "\nrestore:\n" << S << std::endl; - - // if ((line % 100) == 0) - std::cout << line << ": " << num_trivial << " trivial, " - << (line - num_trivial - num_search) << " easy, " << num_search - << " hard (" << num_fails / num_search << ")" << std::endl; - - // if(line > 100) - // exit(1); - } while(true); - - - - - -} diff --git a/src/examples/testbounds.cpp b/src/examples/testbounds.cpp deleted file mode 100644 index 0d80e419de4b84a7220db352be8b4f6e2f97a1e5..0000000000000000000000000000000000000000 --- a/src/examples/testbounds.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/************************************************************************* -minicsp - -Copyright 2010--2011 George Katsirelos - -Minicsp is free software: you can redistribute it and/or modify it -under the terms of the GNU General Public License as published by the -Free Software Foundation, either version 3 of the License, or (at your -option) any later version. - -Minicsp is distributed in the hope that it will be useful, but WITHOUT -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -for more details. - -You should have received a copy of the GNU General Public License -along with minicsp. If not, see <http://www.gnu.org/licenses/>. - -*************************************************************************/ - - -#include <iostream> - - -#include "Scheduler.hpp" - - -using namespace tempo; - - - - -int main(int argc, char *argv[]) { - - int n = 5; - - Options opt = tempo::parse(argc, argv); - Scheduler<int> s_(opt); - - BoundSystem<int>& S{s_.domain.bounds}; - - S.resize(n); - - std::cout << S << std::endl; - - for(auto l{0}; l<10; ++l) { - - ReversibleObject::env->save(); - - try { - for(auto i{0}; i<3; ++i) { - - event a{static_cast<event>(tempo::random()%n)}; - int k{static_cast<int>(tempo::random()%10000)}; - - event b{static_cast<event>(tempo::random()%n)}; - int l{static_cast<int>(tempo::random()%100)}; - - std::cout << "set " << prettyEvent(a) << " <= " << k << std::endl; - - S.set(UPPER, a, k); - - std::cout << "set " << prettyEvent(b) << " >= " << l << std::endl; - - S.set(LOWER, b, -l); - } - std::cout << std::endl << S << std::endl; - - } catch(std::exception& e) { - std::cout << "negative cycle:\n" << S << std::endl; - break; - } - } - - while(ReversibleObject::env->level() > 0) { - ReversibleObject::env->restore(ReversibleObject::env->level()-1); - std::cout << std::endl << S << std::endl; - } - - -} diff --git a/src/examples/testsearch.cpp b/src/examples/testsearch.cpp deleted file mode 100644 index 907867c2f32dbadf8ea48f1941385163218ea153..0000000000000000000000000000000000000000 --- a/src/examples/testsearch.cpp +++ /dev/null @@ -1,252 +0,0 @@ -/************************************************************************* -minicsp - -Copyright 2010--2011 George Katsirelos - -Minicsp is free software: you can redistribute it and/or modify it -under the terms of the GNU General Public License as published by the -Free Software Foundation, either version 3 of the License, or (at your -option) any later version. - -Minicsp is distributed in the hope that it will be useful, but WITHOUT -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -for more details. - -You should have received a copy of the GNU General Public License -along with minicsp. If not, see <http://www.gnu.org/licenses/>. - -*************************************************************************/ - - -#include <chrono> -#include <iostream> -#include <vector> -#include <filesystem> -#include <optional> - -//#include "Objective.hpp" -#include "Scheduler.hpp" -#include "util/parsing/format.hpp" -#include "util/parsing/jsp.hpp" -#include "util/parsing/jstl.hpp" -#include "util/parsing/osp.hpp" -#include "util/parsing/tsptw.hpp" - -using namespace tempo; - -void load(std::string &solfile_name, std::vector<bool> &solution) { - // std::cout << "load\n"; - - std::ifstream solfile(solfile_name.c_str(), std::ifstream::in); - size_t n; - bool val; - solfile >> n; - - // std::cout << n << std::endl; - - solution.resize(n); - - for (size_t i{0}; i < n; ++i) { - solfile >> val; - - // std::cout << val << std::endl; - - solution[i] = val; - } -} - -int main(int argc, char *argv[]) { - - auto start = std::chrono::system_clock::now(); - Options opt = tempo::parse(argc, argv); - - seed(opt.seed); - - ProblemInstance data; - - int ub{opt.ub}; - -// std::cout << ub << " <> " << INFTY << std::endl; - - if (opt.input_format == "osp") { - data = osp::read_instance(opt.instance_file); - if (ub == INFTY) - ub = osp::getUb<int>(data); - else { - std::cout << ub << " <> " << INFTY << std::endl; - exit(1); - } - } - else if (opt.input_format == "jsp") { - data = jsp::read_instance(opt.instance_file); - if (ub == INFTY) - ub = jsp::getUb<int>(data); - } else if (opt.input_format == "tsptw") { - data = tsptw::read_instance(opt.instance_file); - } else if (opt.input_format == "jstl") { - data = jstl::read_instance(opt.instance_file); - if (ub == INFTY) - ub = jstl::getUb<int>(data); - } - - if (opt.print_ins) { - std::cout << data.durations.size() << " tasks:"; - for (auto d : data.durations) { - std::cout << " " << d; - } - std::cout << std::endl; - - std::cout << data.resources.size() << " resources:"; - for (auto R : data.resources) { - std::cout << " ("; - for (auto x : R) { - std::cout << " " << x; - } - std::cout << ")"; - } - std::cout << std::endl << "ub = " << ub << std::endl; - } - - Scheduler<int> S(opt); - - // std::vector<task> t; - // for (auto d : data.durations) { - // t.push_back(S.newTask(d, d)); - // } - // - // // S.setUpperBound(ub); - // - // for (auto [x, y, k] : data.constraints) { - // - // S.newPrecedence(x, y, k); - // } - // - // // std::cout << data.resources.size() << std::endl; - // // std::cout << data.resources[0].size() << std::endl; - // // // exit(1); - // - // std::vector<var> scope; - // std::vector<var> tasks; - // std::vector<Task<int>> the_tasks; - // for (auto &job : data.resources) { - // for (size_t i{0}; i < job.size(); ++i) { - // tasks.push_back(t[job[i]]); - // the_tasks.push_back(S.getTask(t[job[i]])); - // for (size_t j{i + 1}; j < job.size(); ++j) { - // scope.push_back(S.newVariable( - // {START(t[job[i]]), END(t[job[j]]), -job.getTransitionTime(j, - // i)}, {START(t[job[j]]), END(t[job[i]]), - // -job.getTransitionTime(i, j)})); - // } - // } - // - // // for (auto ti{job.begin()}; ti!=job.end(); ++ti) { - // // for (auto tj{ti+1}; tj!=job.end(); ++tj) { - // // scope.push_back(S.newVariable({START(*ti), END(*tj), 0}, - // // {START(*tj), END(*ti), 0})); - // // } - // // } - // if (opt.edge_finding) { - // S.postEdgeFinding(tasks.begin(), tasks.end(), the_tasks.begin(), - // the_tasks.end(), scope.begin(), scope.end()); - // } - // if (opt.transitivity) { - // S.postTransitivityReasoning(tasks.begin(), tasks.end(), - // scope.begin(), - // scope.end()); - // } - // the_tasks.clear(); - // tasks.clear(); - // scope.clear(); - // } - - S.load(data); - -#ifdef DBG_SOL - if (opt.dbg_file != "") { - std::vector<bool> dbg_sol; - load(opt.dbg_file, dbg_sol); - S.load(dbg_sol); - // std::cout << "load"; - // for (size_t i{0}; i < dbg_sol.size(); ++i) - // std::cout << " " << dbg_sol[i]; - // std::cout << std::endl; - } -#endif - - // std::cout << S << std::endl; - - // S.search(); - - - - int sol; - // if (opt.input_format == "tsptw") { - // // PathLength<int> tour(S, data.resources[0]); - // // S.minimize(tour); - // // sol = tour.upperBound(); - // - // if (opt.print_mod) { - // S.display(std::cout, true, true, true, true, false, false, true); - // std::cout << S << std::endl; - // } - // - // sol = S.satisfiable(); - // std::cout << (sol ? "SAT" : "UNSAT") << std::endl; - // } else { - Makespan<int> makespan(S, ub); - - if (opt.print_mod) { - S.display(std::cout, true, true, true, true, false, false, true); - std::cout << S << std::endl; - } - - // makespan.setUpperBound(ub); - if (opt.dichotomy) - S.optimize_dichotomy(makespan); - else - S.optimize(makespan); - - sol = makespan.primalBound(); - // } - - if (opt.verbosity > tempo::Options::SILENT) { - auto end = std::chrono::system_clock::now(); - auto elapsed = - std::chrono::duration_cast<std::chrono::milliseconds>(end - start); - std::cout << "Objective value = " << sol - << " Execution time = " << elapsed.count() << " ms" - << std::endl; - } - - // auto sol = S.getMakespan(); - // auto sol = makespan.upperBound(); - if (KillHandler::instance().signalReceived()) { - std::cout << "execution aborted, best solution found " << sol << std::endl; - } else if (data.optimalSolution != -1 and sol != data.optimalSolution) { - std::cerr << "Suboptimal solution! " << sol << " vs optimal " - << data.optimalSolution << std::endl; - std::exit(1); - } - - // vector<task> the_tasks; - // - // vector<vector<int>> order; - // - // //order[i][j] -> indice de newVariable() - // - // sort(the_tasks.begin(), the_tasks.end(), [](const task a, const task b) - // { - // return before[oder[a][b]]; - // }; - // ); - - if (opt.print_sol) { - auto before{S.getSolution()}; - std::cout << before.size(); - for (size_t i{0}; i < before.size(); ++i) - std::cout << " " << before[i]; - std::cout << std::endl; - } -} diff --git a/src/examples/torch/scheduler_gnn.cpp b/src/examples/torch/scheduler_gnn.cpp new file mode 100644 index 0000000000000000000000000000000000000000..61e47a4bb9b020e1a96e62d4555aa006f10cc39d --- /dev/null +++ b/src/examples/torch/scheduler_gnn.cpp @@ -0,0 +1,73 @@ +/** +* @author Tim Luchterhand +* @date 10.07.24 +* @brief +*/ + +#include <vector> + +#include "Solver.hpp" +#include "heuristics/GNNValueHeuristics.hpp" +#include "heuristics/heuristic_factories.hpp" +#include "util/SchedulingProblemHelper.hpp" +#include "util/parsing/jsp.hpp" +#include "util/parsing/osp.hpp" + + +template<typename T = int> +class DisjunctiveResource : public vector<tempo::Interval<T>> { +public: + using vector<tempo::Interval<T>>::vector; + + static constexpr auto resourceCapacity() noexcept { return 1; } + + static constexpr auto getDemand(unsigned) noexcept { return 1; } +}; + + + +int main(int argc, char **argv) { + using namespace tempo; + using namespace heuristics; + Parser p = getBaseParser(); + std::string gnnLocation; + std::string featureExtractorConf; + p.getCmdLine().add<TCLAP::ValueArg<std::string>>(gnnLocation, "", "gnn-loc", "Location of the trained GNN", true, + "", "string"); + p.getCmdLine().add<TCLAP::ValueArg<std::string>>(featureExtractorConf, "", "feat-config", + "Location of the feature extractor config", true, "", "string"); + p.parse(argc, argv); + const auto &opt = p.getOptions(); + Solver<> S(opt); + auto schedule{S.newInterval(0, Constant::Infinity<int>, 0, 0, 0, Constant::Infinity<int>)}; + std::vector<DisjunctiveResource<>> resources; + std::vector<Interval<>> tasks; + + if (opt.input_format == "osp") { + osp::parse(opt.instance_file, S, schedule, tasks, resources); + } else { + std::cerr << "problem type " << opt.input_format << " is not (yet) supported" << std::endl; + std::exit(1); + } + + SchedulingProblemHelper problem(std::move(tasks), std::move(resources), {}, schedule); + + for (const auto &consumingTasks: problem.resources()) { + auto constraint = NoOverlap(schedule, consumingTasks); + S.post(constraint); + } + + auto trivialUb = 0; + for (const auto &t : problem.tasks()) { + trivialUb += t.minDuration(S); + } + + trivialUb = std::min(trivialUb, opt.ub); + S.set(schedule.end.before(trivialUb)); + + GNNFullGuidance valBranching(opt.polarity_epsilon, gnnLocation, featureExtractorConf, std::move(problem)); + auto varBranching = make_variable_heuristic(S); + S.setBranchingHeuristic(make_compound_heuristic(std::move(varBranching), std::move(valBranching))); + S.minimize(schedule.end); + return 0; +} \ No newline at end of file diff --git a/src/header/Global.hpp b/src/header/Global.hpp index 945cc674d5b2f344e9a99b2c345597321929809d..afbe0d10ac67bfdc732e68ee0338741f2b8927e6 100644 --- a/src/header/Global.hpp +++ b/src/header/Global.hpp @@ -80,13 +80,6 @@ enum class Priority { High }; -enum class VariableType { - Boolean, - Numeric -}; - -using VariableSelection = std::pair<var_t, VariableType>; - /** * Converts enum to underlying type * @tparam E enum type diff --git a/src/header/Model.hpp b/src/header/Model.hpp index d3fada8db0225e7cb7ed0953d0e76a6b46319eca..e04b82b0f8ddc272c0d6a8fa7d686a3dfb32cf81 100755 --- a/src/header/Model.hpp +++ b/src/header/Model.hpp @@ -24,6 +24,7 @@ //#define DBG_EXTRACT //#define DBG_EXTRACT_SUM +#include "util/traits.hpp" #include "Literal.hpp" #include "constraints/Cardinality.hpp" #include "constraints/PseudoBoolean.hpp" @@ -102,19 +103,22 @@ template <typename T = int> struct NumInfo { template <typename T = int> class NumericVar : public ExpressionFlag { public: - NumericVar() : ExpressionFlag(false) {} + constexpr NumericVar() noexcept: ExpressionFlag(false), data(Constant::NoVar, 0) {}; NumericVar(ExpressionImpl<T> *i) : ExpressionFlag(true), implem(i) {} NumericVar(const var_t i, const T o = 0) : ExpressionFlag(false), data(i, o) {} - T min(Solver<T> &sc) const; - T max(Solver<T> &sc) const; + template<concepts::distance_provider S> + T min(const S &sc) const; - // T solution_min(Solver<T> &sc) const; - // T solution_max(Solver<T> &sc) const; + template<concepts::distance_provider S> + T max(const S &sc) const; - T earliest(Solver<T> &) const; - T latest(Solver<T> &) const; + template<concepts::distance_provider S> + T earliest(const S &) const; + + template<concepts::distance_provider S> + T latest(const S &) const; Literal<T> after(const T t) const; Literal<T> before(const T t) const; @@ -166,13 +170,13 @@ public: #ifdef DBG_EXTRACT std::cout << " extract expr" << std::endl; #endif - + implem->extract(solver, target); - + #ifdef DBG_EXTRACT std::cout << " ==> " << implem->id() << std::endl; #endif - + solver.trash_bin.push_back(implem); } @@ -220,7 +224,7 @@ methods template <typename T = int> class BooleanVar : public ExpressionFlag { public: - BooleanVar() { data._id_ = Constant::NoIndex; } + constexpr BooleanVar() noexcept: ExpressionFlag(false), data(Constant::NoIndex, Constant::NoSemantic) {} BooleanVar(ExpressionImpl<T> *i) : ExpressionFlag(true), implem(i) {} BooleanVar(const var_t i, const index_t f = 0) : ExpressionFlag(false), data(i, f) {} @@ -230,7 +234,7 @@ public: BooleanVar<T> implies(const BooleanVar<T> x) const; var_t id() const { return (ExpressionFlag::_is_expression ? implem->id() : data._id_); } - + index_t semantic() const { return (ExpressionFlag::_is_expression ? implem->semantic() : data._edge_id_); } operator var_t() const { return id(); } @@ -260,11 +264,11 @@ var_t id() const { return (ExpressionFlag::_is_expression ? implem->id() : data. #endif implem->extract(solver, target); - + #ifdef DBG_EXTRACT std::cout << " ==> " << implem->id() << std::endl; #endif - + solver.trash_bin.push_back(implem); } @@ -289,7 +293,7 @@ var_t id() const { return (ExpressionFlag::_is_expression ? implem->id() : data. #endif if (ExpressionFlag::_is_expression) { - + if (implem->id() == Constant::NoVar) { implem->post(solver); } @@ -325,7 +329,11 @@ the schedule */ template <typename T = int> class Interval { public: - Interval() {} + constexpr Interval() noexcept : start(), end(), duration() {} + + Interval(NumericVar<T> start, NumericVar<T> end, NumericVar<T> duration) : + start(start), end(end), duration(duration) {} + Interval(Solver<T> &solver, const T mindur = 0, const T maxdur = Constant::Infinity<T>, const T earliest_start = -Constant::Infinity<T>, @@ -366,16 +374,25 @@ public: // Interval(const Interval<T>&) = default; - T getEarliestStart(Solver<T> &s) const; - T getLatestStart(Solver<T> &s) const; - T getEarliestEnd(Solver<T> &s) const; - T getLatestEnd(Solver<T> &s) const; + template<concepts::distance_provider S> + T getEarliestStart(const S &s) const; + + template<concepts::distance_provider S> + T getLatestStart(const S &s) const; + + template<concepts::distance_provider S> + T getEarliestEnd(const S &s) const; + + template<concepts::distance_provider S> + T getLatestEnd(const S &s) const; bool mustExist(Solver<T> &s) const; bool cannotExist(Solver<T> &s) const; - T minDuration(Solver<T> &s) const; - T maxDuration(Solver<T> &s) const; + template<concepts::distance_provider S> + T minDuration(const S &s) const; + template<concepts::distance_provider S> + T maxDuration(const S &s) const; var_t getStart() const; var_t getEnd() const; @@ -410,7 +427,6 @@ protected: BooleanVar<T> self{Constant::NoVar}; }; - template <typename T = int> class NumericExpressionImpl : public ExpressionImpl<T> { public: @@ -447,7 +463,7 @@ public: var_t extract(Solver<T> &solver, const var_t target = Constant::NoVar) override { - + #ifdef DBG_EXTRACT_SUM this->display(std::cout); std::cout << std::endl; @@ -457,49 +473,49 @@ public: for(index_t i{0}; i < static_cast<index_t>(arguments.size()); ++i) { order.push_back(i); } - - - - + + + + std::sort(order.begin(), order.end(), [&](const index_t i, const index_t j) {return arguments[i].id() < arguments[j].id();}); - - + + std::vector<NumericVar<T>> args; std::vector<var_t> vars; std::vector<T> ws; - - + + // new for (index_t i{0}; i < static_cast<index_t>(arguments.size()); ++i) { args.push_back(arguments[order[i]]); ws.push_back(weights[order[i]]); args.back().extract(solver); - + //#ifdef DBG_EXTRACT_SUM // std::cout << ws.back() << "*[" << args.back() << "]: => increase the bias by " << (ws.back() * args.back().offset()) << std::endl; //#endif -// +// //// increaseBias(ws.back() * args.back().offset()); } - + #ifdef DBG_EXTRACT_SUM arguments = args; weights = ws; this->display(std::cout); std::cout << std::endl; #endif - + T lb{0}; T ub{0}; for(index_t i{static_cast<index_t>(args.size())}; i-->0;) { bool rm{false}; auto x{args[i]}; auto w{ws[i]}; - + #ifdef DBG_EXTRACT_SUM std::cout << w << "*[" << x << "]" ; #endif - + if (w == 0) { rm = true; } else if(i>0 and x.id() == args[i-1].id()) { @@ -512,13 +528,13 @@ public: increaseBias(w * x.min(solver)); rm = true; } - + #ifdef DBG_EXTRACT_SUM std::cout << std::endl; #endif - + if(rm) { - + #ifdef DBG_EXTRACT_SUM std::cout << " rm term " << w << "*" << x << std::endl; #endif @@ -526,17 +542,17 @@ public: ws[i] = ws.back(); args.pop_back(); ws.pop_back(); - } + } #ifdef DBG_EXTRACT_SUM else { std::cout << " keep arg " << args[i] << std::endl; } #endif - + // increaseBias(ws[i] * args[i].offset()); - + } - + #ifdef DBG_EXTRACT_SUM std::cout << args.size() << " arguments remaining\n"; arguments = args; @@ -544,11 +560,11 @@ public: this->display(std::cout); std::cout << std::endl; #endif - + for (index_t i{0}; i < static_cast<index_t>(args.size()); ++i) { auto x{args[i]}; auto w{ws[i]}; - + vars.push_back(x.id()); if (w < 0) { if (lb != -Constant::Infinity<T>) { @@ -585,8 +601,8 @@ public: } } - - + + // // old // T lb{0}; // T ub{0}; @@ -605,9 +621,9 @@ public: // ws.push_back(w); // } // } -// -// -// +// +// +// // for (unsigned i{0}; i < arguments.size(); ++i) { // auto x{arguments[i]}; // auto w{weights[i]}; @@ -683,19 +699,19 @@ public: } SumExpressionImpl<T> &addTerm(const NumericVar<T> &x, const T w = 1) { - + // std::cout << "\nhi "; // this->display(std::cout); // std::cout << std::endl; // std::cout << "add term " << w << "*" << x << std::endl; - + increaseBias(w * x.offset()); if (x.id() != Constant::K) { arguments.push_back(x); weights.push_back(w); } - -// + +// // auto idx{x.id()}; // if (idx == Constant::K) { // // NumericExpressionImpl<T>::self.setOffset(NumericExpressionImpl<T>::self.offset @@ -726,7 +742,7 @@ public: // std::cout << std::endl; return *this; } - + SumExpressionImpl<T> &addTerm(const T k) { // NumericExpressionImpl<T>::self.setOffset( // NumericExpressionImpl<T>::self.offset() + k); @@ -1042,7 +1058,7 @@ public: x.extract(solver); L.push_back(x == false); } - + auto y = solver.newBoolean(); L.push_back(y == true); @@ -1300,7 +1316,7 @@ public: if (solver.getOptions().edge_finding) solver.postEdgeFinding(schedule, this->begin(), this->end(), this->begDisjunct()); - + if (solver.getOptions().transitivity) solver.postTransitivity(schedule, this->begin(), this->end(), this->begDisjunct()); @@ -1366,60 +1382,32 @@ template <typename T> NoOverlapExpression<T> NoOverlap(Interval<T> &schedule) { NumericVar impl */ template<typename T> -T NumericVar<T>::min(Solver<T>& s) const { +template<concepts::distance_provider S> +T NumericVar<T>::min(const S& s) const { T v = s.numeric.lower(id()); - // } else { - // v = -s.numeric.upper(id()); - // } if (v == -Constant::Infinity<T>) return v; return v + offset(); - // } else { - // auto v{s.numeric.upper(id())}; - // if (v == Constant::Infinity<T>) - // return -v; - // return -v + offset(); - // } } template<typename T> -T NumericVar<T>::max(Solver<T>& s) const { +template<concepts::distance_provider S> +T NumericVar<T>::max(const S& s) const { T v = s.numeric.upper(id()); - // } else { - // v = -s.numeric.lower(id()); - // } if (v == Constant::Infinity<T>) return v; return v + offset(); - // } else { - // auto v{-s.numeric.lower(id())}; - // if (v == Constant::Infinity<T>) - // return v; - // return v + offset(); - // } } -// -// template<typename T> -// T NumericVar<T>::solutionMin(Solver<T>& s) const { -// auto v{s.numeric.lower(id())}; -// if (v == -Constant::Infinity<T>) -// return v; -// return v + offset(); -//} -// -// template<typename T> -// T NumericVar<T>::solutionMax(Solver<T>& s) const { -// auto v{s.numeric.upper(*this)}; -// if (v == Constant::Infinity<T>) -// return v; -// return v + offset(); -//} - -template <typename T> T NumericVar<T>::earliest(Solver<T> &s) const { + +template <typename T> +template<concepts::distance_provider S> +T NumericVar<T>::earliest(const S &s) const { return min(s); } -template <typename T> T NumericVar<T>::latest(Solver<T> &s) const { +template <typename T> +template<concepts::distance_provider S> +T NumericVar<T>::latest(const S &s) const { return max(s); } @@ -1638,19 +1626,27 @@ template <typename T> bool Interval<T>::operator==(const Interval<T> &t) const { return id() == t.id(); } -template <typename T> T Interval<T>::getEarliestStart(Solver<T> &solver) const { +template <typename T> +template<concepts::distance_provider S> +T Interval<T>::getEarliestStart(const S &solver) const { return start.earliest(solver); } -template <typename T> T Interval<T>::getLatestStart(Solver<T> &solver) const { +template <typename T> +template<concepts::distance_provider S> +T Interval<T>::getLatestStart(const S &solver) const { return start.latest(solver); } -template <typename T> T Interval<T>::getEarliestEnd(Solver<T> &solver) const { +template <typename T> +template<concepts::distance_provider S> +T Interval<T>::getEarliestEnd(const S &solver) const { return end.earliest(solver); } -template <typename T> T Interval<T>::getLatestEnd(Solver<T> &solver) const { +template <typename T> +template<concepts::distance_provider S> +T Interval<T>::getLatestEnd(const S &solver) const { return end.latest(solver); } @@ -1662,11 +1658,15 @@ template <typename T> bool Interval<T>::cannotExist(Solver<T> &) const { return false; } -template <typename T> T Interval<T>::minDuration(Solver<T> &solver) const { +template <typename T> +template<concepts::distance_provider S> +T Interval<T>::minDuration(const S &solver) const { return duration.min(solver); } -template <typename T> T Interval<T>::maxDuration(Solver<T> &solver) const { +template <typename T> +template<concepts::distance_provider S> +T Interval<T>::maxDuration(const S &solver) const { return duration.max(solver); } diff --git a/src/header/Objective.hpp b/src/header/Objective.hpp index f1eb825c658a145de58226472f824ecaa1a73ab6..4f369b4a345e8b81b11a14f039d81a4d215361fb 100755 --- a/src/header/Objective.hpp +++ b/src/header/Objective.hpp @@ -21,6 +21,8 @@ #ifndef _TEMPO_OBJECTIVE_HPP #define _TEMPO_OBJECTIVE_HPP +#include "Model.hpp" + namespace tempo { template <typename T> class Solver; diff --git a/src/header/Solver.hpp b/src/header/Solver.hpp index 9dbe334da06801bbfea11eccf1b795be401efad5..f8740020f4406177911b4b1567cd3ecd2cfd33e1 100644 --- a/src/header/Solver.hpp +++ b/src/header/Solver.hpp @@ -41,8 +41,7 @@ #include "constraints/OptionalEdgeConstraint.hpp" #include "constraints/PseudoBoolean.hpp" #include "constraints/Transitivity.hpp" -#include "heuristics/HeuristicManager.hpp" -#include "heuristics/ValueHeuristicsManager.hpp" +#include "heuristics/heuristic_factories.hpp" #include "heuristics/impl/DecayingEventActivityMap.hpp" #include "util/KillHandler.hpp" #include "util/Options.hpp" @@ -505,6 +504,9 @@ public: // must be called before the first call to 'search()' void initializeSearch(); + + template<heuristics::heuristic<T> H> + void setBranchingHeuristic(H &&h); // record the backtrack-environment level when calling 'initializeSearch()' int init_level{0}; //@} @@ -639,9 +641,7 @@ public: * @name search strategies */ //@{ - std::optional<heuristics::HeuristicManager<T>> heuristic; - std::optional<heuristics::ValueHeuristicsManager> valueHeuristic; - + heuristics::PolymorphicHeuristic<T> heuristic; RestartManager<Solver<T>> restartPolicy; // @} @@ -685,14 +685,13 @@ public: void printTrace() const; #endif - // to make the activity map public (to heuristics) - heuristics::impl::EventActivityMap<T> *activityMap{NULL}; - + heuristics::impl::EventActivityMap *activityMap{nullptr}; + public: - void setActivityMap(heuristics::impl::EventActivityMap<T> *map) { + void setActivityMap(heuristics::impl::EventActivityMap *map) { activityMap = map; } - heuristics::impl::EventActivityMap<T> *getActivityMap() { + heuristics::impl::EventActivityMap *getActivityMap() { return activityMap; } @@ -2161,14 +2160,21 @@ template <typename T> void Solver<T>::branchRight() { template <typename T> void Solver<T>::initializeSearch() { if(not initialized) { start_time = cpu_time(); - heuristic.emplace(*this, options); - valueHeuristic.emplace(*this); post(&clauses); initialized = true; if(options.verbosity >= Options::QUIET) displayHeader(std::cout); } restartPolicy.initialize(); + if (not heuristic.isValid()) { + heuristic = heuristics::make_heuristic(*this); + } +} + +template<typename T> +template<heuristics::heuristic<T> H> +void Solver<T>::setBranchingHeuristic(H &&h) { + this->heuristic = std::forward<H>(h); } template <typename T> boolean_state Solver<T>::satisfiable() { @@ -2337,8 +2343,7 @@ template <typename T> boolean_state Solver<T>::search() { ++num_choicepoints; ChoicePoint.trigger(); - auto varSelection = heuristic->nextVariable(*this); - Literal<T> d = valueHeuristic->valueDecision(varSelection, *this); + Literal<T> d = heuristic.branch(*this); decisions.push_back(d); #ifdef DBG_TRACE @@ -2665,7 +2670,7 @@ void Solver<T>::wake_me_on(const Literal<T> l, const int c) { else std::cout << "hi\n"; } - + // std::cout << } diff --git a/src/header/constraints/Cardinality.hpp b/src/header/constraints/Cardinality.hpp index d80ffedb1bc581de8e8b6b7ef62ff72b35d34d11..416e42058eeb591e199cb64f6dc879627e047c96 100644 --- a/src/header/constraints/Cardinality.hpp +++ b/src/header/constraints/Cardinality.hpp @@ -24,13 +24,15 @@ #include <cassert> #include <vector> -#include "Solver.hpp" #include "constraints/Constraint.hpp" +#include "ReversibleObject.hpp" //#define DBG_LTRANS namespace tempo { +template<typename T> +class Solver; // enforce sum(l_i) <= bound diff --git a/src/header/constraints/DisjunctiveEdgeFinding.hpp b/src/header/constraints/DisjunctiveEdgeFinding.hpp index 5122451781a42c0898de74a64d2522fe03aded8b..48ad3b33244dbe476392a24c8a3bb6700f6c6f18 100644 --- a/src/header/constraints/DisjunctiveEdgeFinding.hpp +++ b/src/header/constraints/DisjunctiveEdgeFinding.hpp @@ -27,14 +27,17 @@ #include "Explanation.hpp" #include "Global.hpp" -#include "Solver.hpp" #include "constraints/Constraint.hpp" #include "util/SparseSet.hpp" #include "util/ThetaTree.hpp" +#include "Model.hpp" namespace tempo { +template<typename T> +class Solver; + template <typename T> class DisjunctiveEdgeFinding : public Constraint<T> { private: Solver<T> &m_solver; diff --git a/src/header/constraints/PseudoBoolean.hpp b/src/header/constraints/PseudoBoolean.hpp index 14013a1ad26b0f81a1d812d1e40033e49479fc11..c545a79efa438d2992d036641a99c27f306623d0 100644 --- a/src/header/constraints/PseudoBoolean.hpp +++ b/src/header/constraints/PseudoBoolean.hpp @@ -24,7 +24,6 @@ #include <cassert> #include <vector> -#include "Solver.hpp" #include "util/SparseSet.hpp" #include "constraints/Constraint.hpp" @@ -32,6 +31,9 @@ namespace tempo { +template<typename T> +class Solver; + // enforce sum(l_i) <= bound template <typename T> class PseudoBoolean : public Constraint<T> { diff --git a/src/header/constraints/SumConstraint.hpp b/src/header/constraints/SumConstraint.hpp index eea7da929015d4295db5d5f497dae877ca573e8b..07c0f5a93d469c33a21806a36707ddc4f1c56767 100644 --- a/src/header/constraints/SumConstraint.hpp +++ b/src/header/constraints/SumConstraint.hpp @@ -24,7 +24,6 @@ #include <cassert> #include <vector> -#include "Solver.hpp" #include "constraints/Constraint.hpp" #include "util/SparseSet.hpp" @@ -32,6 +31,9 @@ namespace tempo { +template<typename T> +class Solver; + // enforce sum(w_i * x_i) <= bound template <typename T> class SumConstraint : public Constraint<T> { private: diff --git a/src/header/constraints/Transitivity.hpp b/src/header/constraints/Transitivity.hpp index 5eac38fe85fd1a713b5073d50e68e507c9c813fe..dc5ceb8bd6b661a055c1e0fd443b6daa46186085 100644 --- a/src/header/constraints/Transitivity.hpp +++ b/src/header/constraints/Transitivity.hpp @@ -28,15 +28,18 @@ #include "Explanation.hpp" #include "Global.hpp" #include "ReversibleObject.hpp" -#include "Solver.hpp" #include "constraints/Constraint.hpp" #include "util/DisjointSet.hpp" #include "util/SparseSet.hpp" +#include "Model.hpp" //#define DBG_LTRANS namespace tempo { +template<typename T> +class Solver; + template <typename T> class Transitivity : public Constraint<T> { private: Solver<T> &m_solver; diff --git a/src/header/heuristics/BaseBooleanHeuristic.hpp b/src/header/heuristics/BaseBooleanHeuristic.hpp index b5ad06f6c339a1478ef6b3359663e1e6c21c80c3..9721fd688e664b3e83c35fa3adba88855238c542 100644 --- a/src/header/heuristics/BaseBooleanHeuristic.hpp +++ b/src/header/heuristics/BaseBooleanHeuristic.hpp @@ -23,22 +23,16 @@ #include <concepts> #include <stdexcept> +#include <cassert> #include "Global.hpp" #include "Literal.hpp" #include "util/crtp.hpp" #include "util/traits.hpp" +#include "heuristics/heuristic_interface.hpp" namespace tempo::heuristics { -/** - * @brief Contains all information necessary for instantiating value selection - * heuristics - */ -struct ValueHeuristicConfig { - double epsilon; -}; - /** * Interface for value selection heuristic implementations that derive from * BaseBooleanHeuristic @@ -52,16 +46,6 @@ concept binary_heuristic_implementation = requires(H heuristic, { heuristic.choose(x, solver) } -> concepts::same_template<Literal>; }; -/** - * Interface for value selection heuristics - * @tparam H heuristic type - * @tparam Solver information provider (usually the scheduler) - */ -template <typename H, typename Solver> -concept value_heuristic = requires(H heuristic, VariableSelection x, const Solver &solver) { - { heuristic.valueDecision(x, solver) } -> concepts::same_template<Literal>; -}; - template<typename Solver> concept boolean_info_provider = requires(const Solver s, var_t x) { { s.boolean.getLiteral(true, x) } -> concepts::same_template<Literal>; diff --git a/src/header/heuristics/GNNValueHeuristics.hpp b/src/header/heuristics/GNNValueHeuristics.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c63467adb224a8ea96986acd5e4c87c80e30dad3 --- /dev/null +++ b/src/header/heuristics/GNNValueHeuristics.hpp @@ -0,0 +1,66 @@ +/** +* @author Tim Luchterhand +* @date 08.07.24 +* @brief +*/ + +#ifndef TEMPO_GNNVALUEHEURISTICS_HPP +#define TEMPO_GNNVALUEHEURISTICS_HPP + +#include <filesystem> + +#include "util/traits.hpp" +#include "Global.hpp" +#include "nn/GNNEdgePolarityPredictor.hpp" +#include "BaseBooleanHeuristic.hpp" +#include "util/SchedulingProblemHelper.hpp" + +namespace fs = std::filesystem; + +namespace tempo { + template<typename T> + class Solver; +} + +namespace tempo::heuristics { + + /** + * @brief Full guidance GNN based value branching heuristic. + * @detail @copybrief + * The GNN is called at every decision. + * @tparam T timing type + * @tparam R resource type + * @note This Value branching heuristic is only ment to be used for scheduling problems + */ + template<concepts::scalar T, SchedulingResource R> + class GNNFullGuidance: public BaseBooleanHeuristic<GNNFullGuidance<T, R>> { + nn::heuristics::GNNEdgePolarityPredictor<T, R> polarityPredictor; + public: + /** + * Ctor + * @param epsilon epsilon greedy value (see BaseBooleanHeuristic) + * @param modelLocation location of the trained GNN model + * @param featureExtractorConfigLocation location of the feature extractor configuration + * (tempo::nn:GraphBuilderConfig) + * @param problem initial description of the problem + */ + GNNFullGuidance(double epsilon, const fs::path &modelLocation, const fs::path &featureExtractorConfigLocation, + SchedulingProblemHelper <T, R> problem) : BaseBooleanHeuristic<GNNFullGuidance<T, R>>(epsilon), + polarityPredictor(modelLocation, + featureExtractorConfigLocation, + std::move(problem)) {} + + /** + * BaseBooleanHeuristic interface + * @param x variable id + * @param solver solver for which to generate literal + * @returns branching Literal + */ + auto choose(var_t x, const Solver<T> &solver) -> Literal<T> { + polarityPredictor.preEvaluation(solver); + return polarityPredictor.choose(x, solver); + } + }; +} + +#endif //TEMPO_GNNVALUEHEURISTICS_HPP diff --git a/src/header/heuristics/HeuristicManager.hpp b/src/header/heuristics/HeuristicManager.hpp deleted file mode 100644 index c3fc8a73c48770c01a037d63f4ac05f02e2ad3b6..0000000000000000000000000000000000000000 --- a/src/header/heuristics/HeuristicManager.hpp +++ /dev/null @@ -1,99 +0,0 @@ -/************************************************ - * Tempo HeuristicManager.hpp - * - * Copyright 2024 Tim Luchterhand and Emmanuel Hebrard - * - * Tempo is free software: you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Tempo is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - * - * You should have received a copy of the GNU General Public License - * along with Tempo. If not, see <http://www.gnu.org/licenses/>. - * - ***********************************************/ - -#ifndef TEMPO_HEURISTICMANAGER_HPP -#define TEMPO_HEURISTICMANAGER_HPP - -#include <variant> -#include <exception> -#include <utility> - -#include "VSIDS.hpp" -#include "Tightest.hpp" -#include "WeightedDegree.hpp" - - -namespace tempo { - template<typename T> - class Solver; -} - -/** - * @brief namespace containing variable (choice point) selection heuristics - */ -namespace tempo::heuristics { - /** - * @brief Heuristic factory class that can be used to construct different heuristics and at the same time provides a - * consistent interface to callers - */ - template<concepts::scalar T> - class HeuristicManager { - using Implementations = std::variant<Tightest, VSIDS<T>, WeightedDegree<T>>; - - public: - /** - * Ctor: Internally constructs the heuristic inferred from the given - * arguments - * @param solver solver for which to create a heuristic - * @param options options specifying the type of heuristic and further - * config values - * @throws std::runtime_error if an unknown heuristics type was given in - * options - */ - HeuristicManager(Solver<T> &solver, const Options &options) { - //@TODO use factory pattern - switch (options.choice_point_heuristics) { - case Options::ChoicePointHeuristics::Tightest: { - impl.template emplace<Tightest>(); - break; - } - case Options::ChoicePointHeuristics::VSIDS: { - if (options.learning) { - impl.template emplace<VSIDS<T>>(solver); - } else // closest thing if not learning - impl.template emplace<WeightedDegree<T>>(solver); - break; - } - case Options::ChoicePointHeuristics::WeightedDegree: { - impl.template emplace<WeightedDegree<T>>(solver); - break; - } - default: - throw std::runtime_error("unknown heuristic type"); - } - } - - - /** - * Calls the internally stored heuristic with the given arguments - * @param solver solver for which to select the next variable - * @return variable choice consisting of the selected variable and its type - */ - auto nextVariable(Solver<T> &solver) { - return std::visit([&solver](auto &heuristic) { return heuristic.nextVariable(solver); }, impl); - } - - private: - Implementations impl{}; - }; -} - -#endif //TEMPO_HEURISTICMANAGER_HPP - diff --git a/src/header/heuristics/RandomBinaryValue.hpp b/src/header/heuristics/RandomBinaryValue.hpp index bfc799d12978b222b37e6cda1d898302a3452e2f..77b1486d403fe35436cb1d31094c5e161bc3313e 100644 --- a/src/header/heuristics/RandomBinaryValue.hpp +++ b/src/header/heuristics/RandomBinaryValue.hpp @@ -25,7 +25,6 @@ #include "BaseBooleanHeuristic.hpp" #include "Global.hpp" -#include "util/factory_pattern.hpp" namespace tempo::heuristics { @@ -51,8 +50,6 @@ namespace tempo::heuristics { } }; - MAKE_DEFAULT_FACTORY(RandomBinaryValue, const ValueHeuristicConfig &) - } // namespace tempo::heuristics #endif // TEMPO_RANDOMBINARYVALUE_HPP diff --git a/src/header/heuristics/RankingHeuristic.hpp b/src/header/heuristics/RankingHeuristic.hpp index 7f5992054cc76f16b722dc42c22d3c9e7ce652d5..e91019d3154809aba597e9f29a5c1aeac0c8b1d7 100755 --- a/src/header/heuristics/RankingHeuristic.hpp +++ b/src/header/heuristics/RankingHeuristic.hpp @@ -24,6 +24,7 @@ #include <concepts> +#include "heuristic_interface.hpp" #include "util/crtp.hpp" #include "Constant.hpp" diff --git a/src/header/heuristics/Tightest.hpp b/src/header/heuristics/Tightest.hpp index ef37e47ae75dd061488571476b1bd192200a06d0..96a57b1fb8ff2f439c756cc9c19dddcbb2415e75 100755 --- a/src/header/heuristics/Tightest.hpp +++ b/src/header/heuristics/Tightest.hpp @@ -12,6 +12,8 @@ namespace tempo::heuristics { class Tightest : public RankingHeuristic<Tightest> { public: + Tightest() = default; + /** * Calculates the cost for a boolean variable which is the maximum of the distance * between the nodes in both directions of the associated distance constraint diff --git a/src/header/heuristics/TightestValue.hpp b/src/header/heuristics/TightestValue.hpp index 966830dfbc2169d1e7c485cf04b6bba4f4450cd1..6e59073ca4499637b14b8cd007551a5fbc40d166 100644 --- a/src/header/heuristics/TightestValue.hpp +++ b/src/header/heuristics/TightestValue.hpp @@ -12,7 +12,6 @@ #include "DistanceConstraint.hpp" #include "Global.hpp" #include "Literal.hpp" -#include "util/factory_pattern.hpp" #include "util/traits.hpp" @@ -20,10 +19,8 @@ namespace tempo::heuristics { namespace detail { template <typename Solver> -concept distance_provider = requires(const Solver s, var_t x, var_t e) { +concept edge_distance_provider = concepts::distance_provider<Solver> and requires(const Solver s, var_t x) { { s.boolean.getEdge(true, x) } -> concepts::same_template<DistanceConstraint>; - { s.numeric.upper(e) } -> concepts::scalar; - { s.numeric.lower(e) } -> concepts::scalar; }; } @@ -49,7 +46,7 @@ public: * @param solver scheduler instance * @return either POS(cp) or NEG(cp) */ - template <detail::distance_provider Solver> + template <detail::edge_distance_provider Solver> requires(boolean_info_provider<Solver>) static auto choose( var_t x, const Solver &solver) { // @TODO no gap info available -> what should I return? @@ -66,11 +63,6 @@ public: return solver.boolean.getLiteral(gapPos >= gapNeg, x); } }; - -MAKE_FACTORY(TightestValue, const ValueHeuristicConfig &config) { - return TightestValue(config.epsilon); -} -}; } #endif // TEMPO_TIGHTESTVALUE_HPP diff --git a/src/header/heuristics/VSIDS.hpp b/src/header/heuristics/VSIDS.hpp index 1a94c2d1d5173969def2903bf267fa6720a7a04a..88c0ab5fb9fc45b5ca7b8f58c73627378ba7a372 100644 --- a/src/header/heuristics/VSIDS.hpp +++ b/src/header/heuristics/VSIDS.hpp @@ -20,8 +20,7 @@ namespace tempo::heuristics { * the the number of calls to the function VSIDS::updateActivity * The final score of a literal is inversely proportional to its activity (@see getCost) */ - template<typename T> - class VSIDS : public RankingHeuristic<VSIDS<T>> { + class VSIDS : public RankingHeuristic<VSIDS> { public: @@ -29,6 +28,7 @@ namespace tempo::heuristics { * CTor. Infers parameters from given scheduler. Subscribes to events of interest * @param solver target solver */ + template<concepts::scalar T> explicit VSIDS(Solver<T> &solver) : activity(solver, solver.getOptions().vsids_decay), handlerToken(solver.ClauseAdded.subscribe_handled( @@ -45,6 +45,7 @@ namespace tempo::heuristics { ~VSIDS() = default; + template<concepts::scalar T> [[nodiscard]] double getCost(const var_t x, const Solver<T> &solver) const { //@TODO: there shoud be a normalization thingy and Boolean variables without semantic should get the highest value @@ -74,12 +75,13 @@ namespace tempo::heuristics { * @param solver * @todo currently only selects boolean variables */ + template<concepts::scalar T> auto nextVariable(const Solver<T> &solver) const -> VariableSelection { return {this->bestVariable(solver.getBranch(), solver), VariableType::Boolean}; } private: - impl::DecayingEventActivityMap<T> activity; + impl::DecayingEventActivityMap activity; SubscriberHandle handlerToken; }; } diff --git a/src/header/heuristics/ValueHeuristicsManager.hpp b/src/header/heuristics/ValueHeuristicsManager.hpp deleted file mode 100644 index f543d851d39d05a6cf581f98195286570da03ad8..0000000000000000000000000000000000000000 --- a/src/header/heuristics/ValueHeuristicsManager.hpp +++ /dev/null @@ -1,56 +0,0 @@ -/** - * @author Tim Luchterhand - * @date 06.05.24 - * @brief - */ - -#ifndef TEMPO_VALUEHEURISTICSMANAGER_HPP -#define TEMPO_VALUEHEURISTICSMANAGER_HPP -#include <optional> -#include <string> - -#include "RandomBinaryValue.hpp" -//#include "SolutionGuided.hpp" -#include "TightestValue.hpp" -#include "util/Options.hpp" -#include "util/factory_pattern.hpp" -#include "util/traits.hpp" - -namespace tempo { -template <typename T> class Solver; -} - -namespace tempo::heuristics { - -auto valHeuristicTypeToString(Options::PolarityHeuristic type) -> std::string; - -MAKE_FACTORY_PATTERN(ValueHeuristic, ValueHeuristicConfig, TightestValue, - /*SolutionGuided<TightestValue>,*/ RandomBinaryValue) - -class ValueHeuristicsManager { - std::optional<ValueHeuristic> impl; - template <typename T> struct FactoryChecker { - HOLDS_FOR_ALL(ValueHeuristic, value_heuristic, T) - }; - -public: - template <concepts::scalar T> - explicit ValueHeuristicsManager(const Solver<T> &scheduler) { - impl.emplace(ValueHeuristicFactory::getInstance().create( - valHeuristicTypeToString(scheduler.getOptions().polarity_heuristic), - ValueHeuristicConfig{.epsilon = - scheduler.getOptions().polarity_epsilon})); - } - - template <typename Sched> - auto valueDecision(const VariableSelection &selection, const Sched &scheduler) { - static_assert(FactoryChecker<Sched>::template __ValueHeuristic_tester__< - ValueHeuristic>::value, - "At least one heuristic has an invalid signature"); - return std::visit([&](auto &h) { return h.valueDecision(selection, scheduler); }, *impl); - } -}; - -} // namespace tempo::heuristics - -#endif // TEMPO_VALUEHEURISTICSMANAGER_HPP diff --git a/src/header/heuristics/WeightedDegree.hpp b/src/header/heuristics/WeightedDegree.hpp index 60e9f7f32d1f59df95673429df8cc8a51d801959..ed46e8cc3e3b94c44731f1618d33c1acbcf10a53 100755 --- a/src/header/heuristics/WeightedDegree.hpp +++ b/src/header/heuristics/WeightedDegree.hpp @@ -22,17 +22,17 @@ namespace tempo::heuristics { * how often it is involved in a conflict and not how often it is contained in a * learned clause */ - template<typename T> - class WeightedDegree : public RankingHeuristic<WeightedDegree<T>> { + class WeightedDegree : public RankingHeuristic<WeightedDegree> { public: + template<concepts::scalar T> explicit WeightedDegree(Solver<T> &solver) : activity(solver, solver.getOptions().vsids_decay), handlerToken(solver.ConflictEncountered.subscribe_handled( - [this, &solver](auto &expl) { - this->clause.clear(); - expl.explain(Solver<T>::Contradiction, this->clause); - this->activity.update(this->clause, solver); + [this, &solver, clause=std::vector<Literal<T>>{}](auto &expl) mutable { + clause.clear(); + expl.explain(Solver<T>::Contradiction, clause); + this->activity.update(clause, solver); })) {} WeightedDegree(const WeightedDegree &) = delete; @@ -42,6 +42,7 @@ namespace tempo::heuristics { ~WeightedDegree() = default; + template<concepts::scalar T> [[nodiscard]] double getCost(const var_t x, const Solver<T> &solver) const { double dom{1}; @@ -65,14 +66,14 @@ namespace tempo::heuristics { * @param solver * @todo currently only selects boolean variables */ + template<concepts::scalar T> auto nextVariable(const Solver<T> &solver) const -> VariableSelection { return {this->bestVariable(solver.getBranch(), solver), VariableType::Boolean}; } private: - impl::DecayingEventActivityMap<T> activity; + impl::DecayingEventActivityMap activity; SubscriberHandle handlerToken; - std::vector<Literal<T>> clause; }; } diff --git a/src/header/heuristics/heuristic_factories.hpp b/src/header/heuristics/heuristic_factories.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c3d49dcfc4cb6d685bcf7a14d418aaf4fbb924c1 --- /dev/null +++ b/src/header/heuristics/heuristic_factories.hpp @@ -0,0 +1,146 @@ +/** +* @author Tim Luchterhand +* @date 09.07.24 +* @brief +*/ + +#ifndef TEMPO_HEURISTIC_FACTORIES_HPP +#define TEMPO_HEURISTIC_FACTORIES_HPP + +#include <nlohmann/json.hpp> +#include <string> + +#include "heuristic_interface.hpp" +#include "Tightest.hpp" +#include "VSIDS.hpp" +#include "WeightedDegree.hpp" +#include "RandomBinaryValue.hpp" +#include "TightestValue.hpp" + +#include "util/factory_pattern.hpp" +#include "util/Options.hpp" +#include "util/traits.hpp" + +namespace tempo { + template<typename T> + class Solver; +} + +namespace tempo::heuristics { + + /// --- Variable Heuristics --- + + namespace detail { + using VSIDS_M = MovableHeuristic<VSIDS>; + using WeightedDegree_M = MovableHeuristic<WeightedDegree>; + + auto getVarHName(Options::ChoicePointHeuristics heuristic) -> std::string; + auto getValHName(Options::PolarityHeuristic heuristic) -> std::string; + + template<typename ...Heuristics> + struct VariantHeuristicWrapper : std::variant<Heuristics...> { + using std::variant<Heuristics...>::variant; + using std::variant<Heuristics...>::emplace; + + template<concepts::scalar T> requires(variable_heuristic<Heuristics, T> && ...) + auto nextVariable(const Solver<T> &solver) { + return std::visit([&solver](auto &h) {return h.nextVariable(solver);}, *this); + } + + template<concepts::scalar T> requires(value_heuristic<Heuristics, Solver<T>> && ...) + auto valueDecision(VariableSelection x, const Solver<T> &solver) { + return std::visit([x, &solver](auto &h) {return h.valueDecision(x, solver);}, *this); + } + + template<concepts::scalar T> requires(heuristic<Heuristics, T> && ...) + auto branch(const Solver<T> &solver) { + return std::visit([&solver](auto &h) {return h.branch(solver);}, *this); + } + }; + + + } + + using VariableHeuristic = detail::VariantHeuristicWrapper<Tightest, detail::VSIDS_M, detail::WeightedDegree_M>; + + /// Define heuristic factory types here + + MAKE_TEMPLATE_FACTORY(Tightest, concepts::scalar T, const Solver<T> &) { + return Tightest{}; + } + }; + + MAKE_TEMPLATE_P_FACTORY(VSIDS, VariableHeuristic, concepts::scalar T, Solver<T> &solver) { + if(solver.getOptions().learning) { + return detail::VSIDS_M(solver); + } else { + return detail::WeightedDegree_M(solver); + } + } + }; + + MAKE_TEMPLATE_FACTORY(WeightedDegree, concepts::scalar T, Solver<T> &solver) { + return detail::WeightedDegree_M(solver); + } + }; + + MAKE_T_FACTORY_PATTERN(VariableHeuristic, template<concepts::scalar T>, Solver<T>&, Tightest, VSIDS, WeightedDegree) + + /// --- Value Heuristics --- + + /// Add further heuristic types here + + using ValueHeuristic = detail::VariantHeuristicWrapper<TightestValue, RandomBinaryValue>; + + /// Define heuristic factory types here + + MAKE_FACTORY(TightestValue, const Options &options) { + return TightestValue(options.polarity_epsilon); + } + }; + + MAKE_DEFAULT_FACTORY(RandomBinaryValue, const Options&) + + MAKE_FACTORY_PATTERN(ValueHeuristic, const Options&, TightestValue, RandomBinaryValue) + + + /// --- Use these factory methods --- + + /** + * Infers variable selection heuristic from solver options + * @tparam T timing type + * @param solver solver for which to create variable selection heuristic + * @return variable selection heuristic inferred from solver options + */ + template<concepts::scalar T> + auto make_variable_heuristic(Solver<T> &solver) { + const auto &options = solver.getOptions(); + return VariableHeuristicFactory::getInstance().create(detail::getVarHName(options.choice_point_heuristics), + solver); + } + + /** + * Infers value selection heuristic from solver options + * @tparam T timing type + * @param solver solver for which to create value selection heuristic + * @return value selection heuristic inferred from solver options + */ + template<concepts::scalar T> + auto make_value_heuristic(Solver<T> &solver) { + const auto &options = solver.getOptions(); + return ValueHeuristicFactory::getInstance().create(detail::getValHName(options.polarity_heuristic), options); + } + + /** + * Infers branching heuristics to be used from solver options + * @tparam T timing type + * @param solver for which to create branching heuristic + * @return branching heuristic inferred from solver options + */ + template<concepts::scalar T> + auto make_heuristic(Solver<T> &solver) { + return make_compound_heuristic(make_variable_heuristic(solver), make_value_heuristic(solver)); + } +} + +#endif //TEMPO_HEURISTIC_FACTORIES_HPP diff --git a/src/header/heuristics/heuristic_interface.hpp b/src/header/heuristics/heuristic_interface.hpp new file mode 100644 index 0000000000000000000000000000000000000000..7c183f9444cca71549b86684bcaa2d6816a9223c --- /dev/null +++ b/src/header/heuristics/heuristic_interface.hpp @@ -0,0 +1,225 @@ +/** +* @author Tim Luchterhand +* @date 09.07.24 +* @brief +*/ + +#ifndef TEMPO_HEURISTIC_INTERFACE_HPP +#define TEMPO_HEURISTIC_INTERFACE_HPP + +#include <concepts> +#include <memory> +#include <variant> +#include <functional> + +#include "Literal.hpp" +#include "util/traits.hpp" + +namespace tempo { + template<typename T> + class Solver; +} + +namespace tempo::heuristics { + enum class VariableType { + Boolean, + Numeric + }; + + using VariableSelection = std::pair<var_t, VariableType>; + + /** + * Interface for branching heuristics (combined variable and value selection) + * @tparam H heuristic type + * @tparam T timing type + */ + template<typename H, typename T> + concept heuristic = requires(H heuristic, const Solver<T> solver) { + { heuristic.branch(solver) } -> std::same_as<Literal<T>>; + }; + + /** + * Interface for variable selection heuristics + * @tparam H heuristic type + * @tparam T timing type + */ + template<typename H, typename T> + concept variable_heuristic = requires(H heuristic, const Solver<T> solver) { + { heuristic.nextVariable(solver) } -> std::same_as<VariableSelection>; + }; + + /** + * Interface for value selection heuristics + * @tparam H heuristic type + * @tparam Solver information provider (usually the scheduler) + */ + template <typename H, typename Solver> + concept value_heuristic = requires(H heuristic, VariableSelection x, const Solver &solver) { + { heuristic.valueDecision(x, solver) } -> concepts::same_template<Literal>; + }; + + /** + * Wrapper class for heuristics that do not support moving + * @tparam H type of heuristic + */ + template<typename H> + class MovableHeuristic { + std::unique_ptr<H> heuristic; + public: + /** + * Ctor. Constructs the heuristic in place + * @tparam Args argument types + * @param args arguments to the Ctor of H + */ + template<typename ...Args> + explicit MovableHeuristic(Args &&...args) : heuristic(std::make_unique<H>(std::forward<Args>(args)...)) {} + + + /** + * Variable heuristic interface + * @tparam T timing type + * @param solver + * @return variable selection + */ + template<concepts::scalar T> requires(variable_heuristic<H, T>) + auto nextVariable(const Solver<T> &solver) const -> VariableSelection { + return heuristic->nextVariable(solver); + } + + /** + * Value heuristic interface + * @tparam T timing type + * @param x variable selection + * @param solver + * @return branching literal + */ + template<concepts::scalar T> requires(value_heuristic<H, Solver<T>>) + auto valueDecision(VariableSelection x, const Solver<T> &solver) const -> Literal<T> { + return heuristic->valueDecision(x, solver); + } + + /** + * Heuristic interface + * @tparam T timing type + * @param solver + * @return branching literal + */ + template<concepts::scalar T> requires(tempo::heuristics::heuristic<H, T>) + auto branch(const Solver<T> &solver) const -> Literal<T> { + return heuristic->branch(solver); + } + }; + + namespace detail { + template<concepts::scalar T> + struct PolyHeuristicCallableBase { + PolyHeuristicCallableBase() = default; + virtual ~PolyHeuristicCallableBase() = default; + PolyHeuristicCallableBase(PolyHeuristicCallableBase&&) = default; + PolyHeuristicCallableBase &operator=(PolyHeuristicCallableBase&&) = default; + virtual auto invoke(const Solver<T> &) -> Literal<T> = 0; + }; + + template<concepts::scalar T, heuristic<T> H> + struct PolyHeuristicCallable : PolyHeuristicCallableBase<T> { + H impl; + template<typename ...Args> + explicit PolyHeuristicCallable(Args &&...args) : impl(std::forward<Args>(args)...) {} + auto invoke(const Solver<T> &solver) -> Literal<T> override { + return impl.branch(solver); + } + }; + } + + /** + * @brief Polymorphic wrapper for arbitrary branching heuristics + * @details @copybrief + * (combined variable selection + value selection) + * @tparam T timing type + * @note use std::movable_function from c++23 instead of detail::PolyHeuristicCallable implementation in the future + */ + template<concepts::scalar T> + class PolymorphicHeuristic { + std::unique_ptr<detail::PolyHeuristicCallableBase<T>> impl; + public: + /** + * default ctor + * Creates an invalid wrapper that must not be called + */ + constexpr PolymorphicHeuristic() noexcept: impl(nullptr) {} + + /** + * Ctor + * @tparam H heuristic type + */ + template<heuristic<T> H> + PolymorphicHeuristic(H &&heuristic) :impl( + std::make_unique<detail::PolyHeuristicCallable<T, std::decay_t<H>>>(std::forward<H>(heuristic))) {} + + /** + * Branching heuristic interface + * @param solver solver for which to select a branching literal + * @return selected branching literal + * @throws std::runtime_error if wrapper does not contain valid heuristic + */ + auto branch(const Solver<T> &solver) -> Literal<T> { + if (nullptr == impl) { + throw std::runtime_error("no branching heuristic set"); + } + + return impl->invoke(solver); + } + + /** + * Whether the wrapper holds a valid heuristic + */ + [[nodiscard]] bool isValid() const noexcept { + return nullptr != impl; + } + }; + + /** + * @brief General branching heuristic wrapper that holds a separate variable and value selection heuristic + * @details @copybrief + * @tparam VarH type of variable selection heuristic + * @tparam ValH type of value selection heuristic + */ + template<typename VariableHeuristic, typename ValueHeuristic> + class CompoundHeuristic { + VariableHeuristic variableSelector; + ValueHeuristic valueSelector; + public: + + /** + * Ctor + * @tparam VarH type of variable heuristic + * @tparam ValH type of value heuristic + * @param variableHeuristic the variable heuristic + * @param valueHeuristic the value heuristic + */ + template<typename VarH, typename ValH> + CompoundHeuristic(VarH &&variableHeuristic, ValH &&valueHeuristic) : + variableSelector(std::forward<VarH>(variableHeuristic)), + valueSelector(std::forward<ValH>(valueHeuristic)) {} + + template<concepts::scalar T> + requires(variable_heuristic<VariableHeuristic, T> and value_heuristic<ValueHeuristic, Solver<T>>) + auto branch(const Solver<T> &solver) -> Literal<T> { + return valueSelector.valueDecision(variableSelector.nextVariable(solver), solver); + } + }; + + /** + * Helper function to create compound heuristics + * @tparam VarH type of variable heuristic + * @tparam ValH type of value heuristic + * @param variableHeuristic the variable heuristic + * @param valueHeuristic the value heuristic + */ + template<typename VarH, typename ValH> + auto make_compound_heuristic(VarH &&variableHeuristic, ValH &&valueHeuristic) { + return CompoundHeuristic<VarH, ValH>(std::forward<VarH>(variableHeuristic), std::forward<ValH>(valueHeuristic)); + } +} + +#endif //TEMPO_HEURISTIC_INTERFACE_HPP diff --git a/src/header/heuristics/impl/DecayingEventActivityMap.hpp b/src/header/heuristics/impl/DecayingEventActivityMap.hpp index 8f16b24cc33122d8fb78dec435539856ae01169a..59e3444b2b48cc0b655ce4b0cb52c4b132954865 100755 --- a/src/header/heuristics/impl/DecayingEventActivityMap.hpp +++ b/src/header/heuristics/impl/DecayingEventActivityMap.hpp @@ -11,8 +11,7 @@ namespace tempo::heuristics::impl { /** * @brief Records activity for literals. Activity of inactive literals decays which each update */ -template<typename T> - class DecayingEventActivityMap : public EventActivityMap<T> { + class DecayingEventActivityMap : public EventActivityMap { public: /** * @copydoc ActivityMap::ActivityMap @@ -25,9 +24,10 @@ template<typename T> // throw std::runtime_error("invalid decay value"); // } // } - + + template<concepts::scalar T> explicit DecayingEventActivityMap(Solver<T> &solver, double decay) : - EventActivityMap<T>(solver), decay(decay) { + EventActivityMap(solver), decay(decay) { if (decay <= 0 or decay > 1) { throw std::runtime_error("invalid decay value"); } @@ -43,24 +43,25 @@ template<typename T> // EventActivityMap<T>::numeric_activity[x] += increment; // return (EventActivityMap<T>::numeric_activity[x] > maxActivity); // } - + + template<concepts::scalar T> bool incrementActivity(const Literal<T> l, const Solver<T>& solver) noexcept { // std::cout << "increment activity of " << l << std::endl; bool need_rescaling{false}; if(l.isNumeric()) { - EventActivityMap<T>::numeric_activity[l.variable()] += increment; - need_rescaling = EventActivityMap<T>::numeric_activity[l.variable()] > maxActivity; + EventActivityMap::numeric_activity[l.variable()] += increment; + need_rescaling = EventActivityMap::numeric_activity[l.variable()] > maxActivity; } else { - EventActivityMap<T>::boolean_activity[l.variable()] += increment; - need_rescaling = EventActivityMap<T>::boolean_activity[l.variable()] > maxActivity; + EventActivityMap::boolean_activity[l.variable()] += increment; + need_rescaling = EventActivityMap::boolean_activity[l.variable()] > maxActivity; if(l.hasSemantic()) { auto c{solver.boolean.getEdge(l)}; - EventActivityMap<T>::numeric_activity[c.from] += increment; - EventActivityMap<T>::numeric_activity[c.to] += increment; - need_rescaling |= EventActivityMap<T>::numeric_activity[c.from] > maxActivity; - need_rescaling |= EventActivityMap<T>::numeric_activity[c.to] > maxActivity; + EventActivityMap::numeric_activity[c.from] += increment; + EventActivityMap::numeric_activity[c.to] += increment; + need_rescaling |= EventActivityMap::numeric_activity[c.from] > maxActivity; + need_rescaling |= EventActivityMap::numeric_activity[c.to] > maxActivity; } } return need_rescaling; @@ -121,7 +122,7 @@ template<typename T> // increment /= decay; // } - template<class Iterable> + template<class Iterable, concepts::scalar T> void update(Iterable &clause, const Solver<T>& solver) noexcept { bool normalize = false; diff --git a/src/header/heuristics/impl/EventActivityMap.hpp b/src/header/heuristics/impl/EventActivityMap.hpp index cfc009d136775e5f31a41271ad42da12f6015786..328854f9d093ee196055169c73895f83644b96b5 100755 --- a/src/header/heuristics/impl/EventActivityMap.hpp +++ b/src/header/heuristics/impl/EventActivityMap.hpp @@ -7,7 +7,7 @@ #include <concepts> -//#include "Scheduler.hpp" +#include "util/traits.hpp" namespace tempo { @@ -19,7 +19,6 @@ namespace tempo::heuristics::impl { /** * @brief Class that can be used to record the activity on distance constraints */ -template<typename T> class EventActivityMap { public: /** @@ -27,7 +26,8 @@ template<typename T> * @tparam T type of scheduler * @param scheduler scheduler for which to construct the ActivityMap */ - + + template<concepts::scalar T> explicit EventActivityMap(Solver<T> &solver) // : sched(scheduler) { @@ -45,10 +45,12 @@ template<typename T> * @param edge constraint * @return */ + template<concepts::scalar T> constexpr double get(const DistanceConstraint<T>& c) const noexcept { return numeric_activity[c.from] + numeric_activity[c.to]; } - + + template<concepts::scalar T> constexpr double get(const Literal<T> l, const Solver<T> &solver) const noexcept { double a{0}; @@ -65,6 +67,7 @@ template<typename T> return a; } + template<concepts::scalar T> constexpr double get(const var_t x, const Solver<T>& solver) const noexcept { double a{boolean_activity[x]}; // double a{0}; @@ -127,7 +130,6 @@ template<typename T> protected: -// Scheduler<T>& sched; std::vector<double> numeric_activity{}; std::vector<double> boolean_activity{}; }; diff --git a/src/header/nn/GNNEdgePolarityPredictor.hpp b/src/header/nn/GNNEdgePolarityPredictor.hpp index 7a873fe7fdd65e0e8197374982e1c9e498450ea0..e004edb722c8c11397f7d4823055be550df5b0e1 100644 --- a/src/header/nn/GNNEdgePolarityPredictor.hpp +++ b/src/header/nn/GNNEdgePolarityPredictor.hpp @@ -11,13 +11,25 @@ #include "GraphBuilder.hpp" #include "util/Matrix.hpp" #include "util/traits.hpp" -#include "util/parsing/format.hpp" +#include "Literal.hpp" + +namespace tempo { + template<typename T> + class Solver; +} namespace tempo::nn::heuristics { + + namespace detail { + bool choosePolarityFromHeatMap(unsigned taskFrom, unsigned taskTo, const Matrix<DataType> &heatMap); + } + + /** * GNN based value selection heuristic that predicts a probability for each edge and returns the polarity of * choicepoint accordingly */ + template<concepts::scalar T, SchedulingResource R> class GNNEdgePolarityPredictor { public: /** @@ -29,39 +41,48 @@ namespace tempo::nn::heuristics { */ GNNEdgePolarityPredictor(const std::filesystem::path &modelLocation, const std::filesystem::path &featureExtractorConfigLocation, - const ProblemInstance &problemInstance); + SchedulingProblemHelper<T, R> problemInstance) : + gnn(modelLocation), + graphBuilder(featureExtractorConfigLocation, std::move(problemInstance)) {} /** * Returns the choice point in the appropriate polarity according to a GNN edge regressor - * @param cp choicepoint to evaluate - * @return cp or ~cp according to the GNN heat map + * @param x variable for which to make a choice + * @return corresponding positive or negative literal according to the GNN */ - template<typename Sched> - requires(traits::is_same_template_v<DistanceConstraint, decltype(std::declval<Sched>().getEdge( - std::declval<lit>()))>) - lit choose(tempo::var cp, const Sched &scheduler) const{ - // @TODO does not work anymore!!! - auto [from, to] = scheduler.getEdge(POS(cp)); - return choosePolarityFromHeatMap(from, to, edgeHeatMap) ? POS(cp) : NEG(cp); + auto choose(var_t x, const Solver<T> &solver) const -> Literal<T> { + auto edge = solver.boolean.getEdge(true, x); + auto edgeRev = solver.boolean.getEdge(false, x); + const auto &mapping = graphBuilder.getProblem().getMapping(); + assert(mapping.contains(edge.from)); + assert(mapping.contains(edge.to)); + assert(mapping.contains(edgeRev.from)); + assert(mapping.contains(edgeRev.to)); + unsigned from = mapping(edge.from); + unsigned to = mapping(edge.to); + unsigned rfrom = mapping(edgeRev.from); + unsigned rto = mapping(edgeRev.to); + if (from != rto or to != rfrom) { + // @TODO not strictly necessary. simply allow distances between different tasks and use DST for probs + throw std::runtime_error("positive and negative edges have different tasks as endpoints."); + } + + return solver.boolean.getLiteral(detail::choosePolarityFromHeatMap(from, to, edgeHeatMap), x); } /** * Runs the GNN to produce an edge heat map - * @param scheduler scheduler instance for which to produce the heat map + * @param solver scheduler instance for which to produce the heat map */ - template<concepts::scalar T> - void preEvaluation(const Scheduler<T> &scheduler) { - auto graph = graphBuilder.getGraph( - makeSolverState([&](event from, event to) { return scheduler.distance(from, to); })); + void preEvaluation(const Solver<T> &solver) { + auto taskNetwork = graphBuilder.getProblem().getTaskDistances(solver); + auto graph = graphBuilder.getGraph(makeSolverState(std::move(taskNetwork), solver)); edgeHeatMap = gnn.getHeatMap(graph); } - protected: - static bool choosePolarityFromHeatMap(event from, event to, const Matrix<DataType> &heatMap); - private: EdgeRegressor gnn; - GraphBuilder graphBuilder; + GraphBuilder<T, R> graphBuilder; Matrix<DataType> edgeHeatMap{}; }; } diff --git a/src/header/nn/GraphBuilder.hpp b/src/header/nn/GraphBuilder.hpp index 5b72bd263c94cc07cccfa7eadab3bca428d15676..04e756ab44b82730b7e23749b69816df59277a98 100644 --- a/src/header/nn/GraphBuilder.hpp +++ b/src/header/nn/GraphBuilder.hpp @@ -19,14 +19,18 @@ #include "topology_extractors.hpp" #include "torch_types.hpp" #include "util/traits.hpp" -#include "util/parsing/format.hpp" +#include "util/SchedulingProblemHelper.hpp" +#include "util/serialization.hpp" namespace tempo::nn { - MAKE_FACTORY_PATTERN(FeatureExtractor, nlohmann::json, TaskTimingFeatureExtractor, TimingEdgeExtractor, + MAKE_POLYMORPHIC_TYPE(FeatureExtractor, TaskTimingFeatureExtractor, TimingEdgeExtractor, ResourceEnergyExtractor) + MAKE_FACTORY_PATTERN(FeatureExtractor, const nlohmann::json&, TaskTimingFeatureExtractor, TimingEdgeExtractor, ResourceEnergyExtractor) - MAKE_FACTORY_PATTERN(TopologyBuilder, ProblemInstance, MinimalTopologyBuilder) + MAKE_POLYMORPHIC_TYPE(TopologyBuilder, MinimalTopologyBuilder) + MAKE_T_FACTORY_PATTERN(TopologyBuilder, template<ESCAPE(typename T, typename R)>, + ESCAPE(const SchedulingProblemHelper<T, R>&), MinimalTopologyBuilder) /** * Serializable type that holds the configuration of a feature extractor type @@ -51,14 +55,8 @@ namespace tempo::nn { * Class that extracts all tensors necessary for running the inference of a GNN from a given problem and temporal * network */ + template<concepts::scalar T, SchedulingResource R> class GraphBuilder { - template<typename EvtFun> - struct FactoryChecker { - HOLDS_FOR_ALL(FeatureExtractor, feature_extractor, EvtFun) - HOLDS_FOR_ALL(TopologyExtractor, topology_extractor, EvtFun) - static constexpr bool value = __FeatureExtractor_tester__<FeatureExtractor>::value and - __TopologyExtractor_tester__<TopologyBuilder>::value; - }; public: /** @@ -66,29 +64,40 @@ namespace tempo::nn { * @param configPath path to the feature extractor configurations * @param problemInstance Initial problem containing immutable information about tasks and resources */ - GraphBuilder(const std::filesystem::path &configPath, const ProblemInstance &problemInstance); + GraphBuilder(const std::filesystem::path &configPath, SchedulingProblemHelper<T, R> problemInstance) + : problemDefinition(std::move(problemInstance)) { + auto data = serialization::deserializeFromFile<GraphBuilderConfig>(configPath); + topologyExtractor = TopologyBuilderFactory::getInstance().create(data.topologyExtractor.extractorName, + problemDefinition); + taskFeatureExtractor = FeatureExtractorFactory::getInstance().create( + data.taskFeatureExtractor.extractorName, data.taskFeatureExtractor.arguments); + resourceFeatureExtractor = FeatureExtractorFactory::getInstance().create( + data.resourceFeatureExtractor.extractorName, data.resourceFeatureExtractor.arguments); + edgeFeatureExtractor = FeatureExtractorFactory::getInstance().create( + data.edgeFeatureExtractor.extractorName, data.edgeFeatureExtractor.arguments); + } /** * Extracts all features and topology information from a given solver state - * @tparam EvtFun callable that represents distances between events + * @tparam Args Types of solver state fields + * @param state current state of the solver * @return InputGraph containing topology and feature information */ - template<concepts::arbitrary_event_dist_fun EvtFun> - auto getGraph(const SolverState<EvtFun>& state) -> InputGraph { - static_assert(FactoryChecker<EvtFun>::value, - "Not all feature extractors or topology extractors have the correct signature"); + template<typename ...Args> + auto getGraph(const SolverState<Args...> &state) -> InputGraph { auto topology = std::visit( [&](auto &extractor) { return extractor.getTopology(state); }, *topologyExtractor); - auto taskFeats = std::visit( - [t = std::cref(topology), &state](const auto &extractor) { return extractor(t, state.eventNetwork); }, - taskFeatureExtractor); - auto edgeFeats = std::visit( - [t = std::cref(topology), &state](const auto &extractor) { return extractor(t, state.eventNetwork); }, - edgeFeatureExtractor); - auto resourceFeats = std::visit( - [t = std::cref(topology), &state](const auto &extractor) { return extractor(t, state.eventNetwork); }, - resourceFeatureExtractor); + const auto &p = problemDefinition; + auto taskFeats = std::visit([t = std::cref(topology), &p, &state](const auto &extractor) { + return extractor(t, state, p); + }, taskFeatureExtractor); + auto edgeFeats = std::visit([t = std::cref(topology), &p, &state](const auto &extractor) { + return extractor(t, state, p); + }, edgeFeatureExtractor); + auto resourceFeats = std::visit([t = std::cref(topology), &p, &state](const auto &extractor) { + return extractor(t, state, p); + }, resourceFeatureExtractor); InputGraph ret; using K = GraphKeys; ret.insert(K::TaskFeatures, std::move(taskFeats)); @@ -102,8 +111,17 @@ namespace tempo::nn { return ret; } + /** + * gets the stored problem definition + * @return problem definition + */ + auto getProblem() const noexcept -> const SchedulingProblemHelper<T, R>& { + return problemDefinition; + } + private: + SchedulingProblemHelper<T, R> problemDefinition; std::optional<TopologyBuilder> topologyExtractor; FeatureExtractor taskFeatureExtractor; FeatureExtractor edgeFeatureExtractor; diff --git a/src/header/nn/feature_extractors.hpp b/src/header/nn/feature_extractors.hpp index 6ed8c373bdb3f1f52a947a8df7fa580ced027f70..a61c936c8925e829ad730daaa489dff869f218be 100644 --- a/src/header/nn/feature_extractors.hpp +++ b/src/header/nn/feature_extractors.hpp @@ -15,7 +15,7 @@ #include "util/traits.hpp" #include "util/serialization.hpp" -#include "util/task_timings.hpp" +#include "util/SchedulingProblemHelper.hpp" #include "tensor_utils.hpp" #include "torch_types.hpp" #include "util/factory_pattern.hpp" @@ -24,17 +24,6 @@ * @brief namespace containing neural network specific code */ namespace tempo::nn { - /** - * @brief Requirement for a type that can be used as resource or task feature extractor - * @details @copybrief - * Requires a call operator that takes a graph topology and an event network and returns a tensor containing all - * features - * @tparam Extractor - */ - template<typename Extractor, typename EvtFun> - concept feature_extractor = concepts::callable_r<Extractor, torch::Tensor, const Topology, const EvtFun> - and concepts::arbitrary_event_dist_fun<EvtFun>; - /** * @brief Extracts features from timing information of tasks. * @details @copybrief @@ -42,23 +31,32 @@ namespace tempo::nn { * the origin and horizon. */ struct TaskTimingFeatureExtractor { + static constexpr auto LegacyKey = "legacy"; + bool legacyFeatures = false; + /** * Returns a tensor containing timing features for all tasks. + * @tparam DistFun type of distance function + * @tparam S Solver type that has a member that provides upper and lower bounds + * @tparam T timing type + * @tparam R scheduling resource type * @param topology Graph topology for which to extract the features - * @param eventDistances current event network from which the features are generated + * @param state current state of the solver + * @param problem scheduling problem description * @return torch::Tensor containing task features */ - template<concepts::arbitrary_event_dist_fun EvtFun> - auto operator()(const Topology &topology, const EvtFun &eventDistances) const -> torch::Tensor { - using namespace torch::indexing; - const auto ub = upperBound(eventDistances); + template<typename DistFun, concepts::distance_provider S, concepts::scalar T, SchedulingResource R> + auto operator()(const Topology &topology, const SolverState<DistFun, S> &state, + const SchedulingProblemHelper<T, R> &problem) const -> torch::Tensor { + const auto ub = static_cast<DataType>(problem.schedule().getLatestEnd(state.solver)); torch::Tensor ret = torch::empty({static_cast<long>(topology.numTasks), 4}, dataTensorOptions()); - for (long t = 0; t < topology.numTasks; ++t) { - util::sliceAssign(ret, {static_cast<long>(t), util::SliceHere}, - {minDuration(t, eventDistances)/ static_cast<DataType>(ub), - maxDuration(t, eventDistances) / static_cast<DataType>(ub), - earliestStartTime(t, eventDistances) / static_cast<DataType>(ub), - latestCompletion(t, eventDistances) / static_cast<DataType>(ub)}); + for (auto [t, task]: iterators::enumerate(problem.tasks(), 0l)) { + util::sliceAssign(ret, {t, util::SliceHere}, + {task.minDuration(state.solver) / ub, + task.maxDuration(state.solver) / ub, + task.getEarliestStart(state.solver) / ub * + static_cast<DataType>(1 - 2 * legacyFeatures), + task.getLatestEnd(state.solver) / ub - static_cast<DataType>(legacyFeatures)}); } return ret; @@ -76,22 +74,27 @@ namespace tempo::nn { struct ResourceEnergyExtractor { /** * Returns a tensor containing resource energy feature for all resources. + * @tparam DistFun type of distance function + * @tparam S Solver type that has a member that provides upper and lower bounds + * @tparam T timing type + * @tparam R scheduling resource type * @param topology Graph topology for which to extract the features - * @param eventDistances current event network from which the features are generated + * @param state current state of the solver + * @param problem scheduling problem description * @return torch::Tensor containing resource features */ - template<concepts::arbitrary_event_dist_fun EvtFun> - auto operator()(const Topology &topology, const EvtFun &eventDistances) const -> torch::Tensor { - using namespace torch::indexing; - const auto ub = upperBound(eventDistances); + template<typename Dist, concepts::distance_provider S, concepts::scalar T, SchedulingResource R> + auto operator()(const Topology &topology, const SolverState<Dist, S> &state, + const SchedulingProblemHelper<T, R> &problem) const -> torch::Tensor { + const auto ub = static_cast<DataType>(problem.schedule().getLatestEnd(state.solver)); auto ret = torch::zeros({static_cast<long>(topology.numResources), 1}, dataTensorOptions()); const std::span demands(topology.resourceDemands.data_ptr<DataType>(), topology.resourceDemands.size(0)); const auto tasks = util::getIndexSlice(topology.resourceDependencies, 0); const auto resources = util::getIndexSlice(topology.resourceDependencies, 1); std::span energy(ret.data_ptr<DataType>(), topology.numResources); for (auto [task, res, demand] : iterators::zip(tasks, resources, demands)) { - auto avgDuration = static_cast<DataType>(minDuration(task, eventDistances) - + maxDuration(task, eventDistances)) / DataType(2) / ub; + auto avgDuration = static_cast<DataType>(problem.tasks()[task].minDuration(state.solver) + + problem.tasks()[task].maxDuration(state.solver)) / DataType(2) / ub; energy[res] += avgDuration * demand; } @@ -108,30 +111,39 @@ namespace tempo::nn { struct TimingEdgeExtractor { /** * Returns a tensor containing timing features for all edges. + * @tparam DistFun type of distance function + * @tparam S Solver type that has a member that provides upper and lower bounds + * @tparam T timing type + * @tparam R scheduling resource type * @param topology Graph topology for which to extract the features - * @param eventDistances current event network from which the features are generated + * @param state current state of the solver + * @param problem scheduling problem description * @return torch::Tensor containing edge features */ - template<concepts::arbitrary_event_dist_fun EvtFun> - auto operator()(const Topology &topology, const EvtFun &eventDistances) const -> torch::Tensor { - using namespace torch::indexing; - const auto ub = upperBound(eventDistances); + template<concepts::arbitrary_task_dist_fun Dist, typename S, concepts::scalar T, SchedulingResource R> + auto operator()(const Topology &topology, const SolverState<Dist, S> &state, + const SchedulingProblemHelper<T, R> &problem) const -> torch::Tensor { + const auto ub = static_cast<DataType>(problem.schedule().getLatestEnd(state.solver)); const auto edgeFrom = util::getIndexSlice(topology.edgeIndices, 0); const auto edgeTo = util::getIndexSlice(topology.edgeIndices, 1); auto ret = torch::empty({static_cast<long>(edgeFrom.size()), 2}, dataTensorOptions()); for (auto [idx, from, to] : iterators::zip_enumerate(edgeFrom, edgeTo, 0l)) { - const auto tempDiff = taskDistance(from, to, eventDistances); - const auto tempDiffRev = taskDistance(to, from, eventDistances); + const auto tempDiff = state.distance(from, to); + const auto tempDiffRev = state.distance(to, from); const bool isPrecedence = tempDiff <= 0 or tempDiffRev <= 0; util::sliceAssign(ret, {idx, util::SliceHere}, {static_cast<DataType>(isPrecedence), - tempDiff / static_cast<DataType>(ub)}); + static_cast<DataType>(tempDiff) / ub}); } return ret; } }; - MAKE_DEFAULT_FACTORY(TaskTimingFeatureExtractor, const nlohmann::json&) + MAKE_FACTORY(TaskTimingFeatureExtractor, const nlohmann::json &config) { + return TaskTimingFeatureExtractor{.legacyFeatures = config.at( + TaskTimingFeatureExtractor::LegacyKey).get<bool>()}; + } + }; MAKE_DEFAULT_FACTORY(ResourceEnergyExtractor, const nlohmann::json&) MAKE_DEFAULT_FACTORY(TimingEdgeExtractor, const nlohmann::json&) } diff --git a/src/header/nn/topology_extractors.hpp b/src/header/nn/topology_extractors.hpp index e8a1f3966d0db525fead1310804db7d2ed240e30..e8bffa2db06bca8a04c0769d3dabd7991540c09e 100644 --- a/src/header/nn/topology_extractors.hpp +++ b/src/header/nn/topology_extractors.hpp @@ -6,54 +6,21 @@ #ifndef TEMPO_TOPOLOGY_EXTRACTORS_HPP #define TEMPO_TOPOLOGY_EXTRACTORS_HPP -#include <ranges> #include <vector> +#include <cassert> #include "Global.hpp" -#include "util/parsing/format.hpp" +#include "Model.hpp" +#include "util/SchedulingProblemHelper.hpp" #include "torch_types.hpp" #include "tensor_utils.hpp" #include "util/Matrix.hpp" #include "util/factory_pattern.hpp" +#include "util/traits.hpp" #include "DistanceConstraint.hpp" namespace tempo::nn { - /** - * Represents the state of a the solver that can be used to extract the graph topology - * @tparam EvtFun type of event distance function - */ - template<concepts::arbitrary_event_dist_fun EvtFun> - struct SolverState { - template<typename E> - requires(concepts::arbitrary_event_dist_fun<std::remove_cvref_t<E>>) - explicit constexpr SolverState(E &&evt) : eventNetwork(std::forward<E>(evt)) {} - EvtFun eventNetwork; ///< event network functor object - }; - - /** - * Concept that models the interface of a topology extractor - * @tparam Extractor - * @tparam EvtFun type of event function - */ - template<typename Extractor, typename EvtFun> - concept topology_extractor = requires(Extractor e, const SolverState<EvtFun> &state) { - { e.getTopology(state) } -> std::convertible_to<Topology>; - }; - - - /** - * Helper function for SolverState - * @tparam Args argument types to SolverState - * @param args arguments to SolverState - * @return solver state view of the arguments. Depending on the reference type of the arguments, this is only - * a non owning object - */ - template<typename ...Args> - constexpr auto makeSolverState(Args &&...args) { - return SolverState<Args...>(std::forward<Args>(args)...); - } - namespace impl { struct EdgeLookup : protected Matrix<IndexType> { static constexpr IndexType NoValue = std::numeric_limits<IndexType>::lowest(); @@ -109,17 +76,36 @@ namespace tempo::nn { * Ctor * @param problem initial problem instance */ - explicit MinimalTopologyBuilder(const ProblemInstance &problem); + template<concepts::scalar T, SchedulingResource R> + explicit MinimalTopologyBuilder(const SchedulingProblemHelper<T, R> &problem) { + using namespace iterators; + cache.numTasks = problem.tasks().size(); + cache.numResources = problem.resources().size(); + impl::TopologyData topologyData{.edgeLookup = impl::EdgeLookup(cache.numTasks * 2 + 2)}; + for (auto [r, resourceSpec]: enumerate(problem.resources(), 0l)) { + completeSubGraph(resourceSpec, r, problem.getMapping(), topologyData); + } + + addPrecedenceEdges(problem.precedences(), problem.getMapping(), topologyData); + cache.edgeIndices = util::makeIndexTensor(topologyData.edges); + cache.edgePairMask = torch::from_blob(topologyData.edgePairMask.data(), + static_cast<long>(topologyData.edgePairMask.size()), + indexTensorOptions()).clone(); + cache.edgeResourceRelations = util::makeIndexTensor(topologyData.edgeIdx, topologyData.edgeRelResIdx); + cache.resourceDependencies = util::makeIndexTensor(topologyData.taskIdx, topologyData.resIdx); + cache.resourceDemands = torch::from_blob(topologyData.resDemands.data(), + {static_cast<long>(topologyData.taskIdx.size()), 1}, + dataTensorOptions()).clone(); + } /** * Generates a graph topology based on the initial problem instance and on the given decided precedence * constraints - * @tparam EvtFun type of event distance function * @param precedences range of decided precedences * @return the graph topology representing the initial problem and the added precedences */ - template<typename EvtFun> - [[nodiscard]] auto getTopology(const SolverState<EvtFun> &) const -> const Topology &{ + template<typename ...Args> + [[nodiscard]] auto getTopology(const SolverState<Args...> &) const -> const Topology &{ return cache; } @@ -130,12 +116,12 @@ namespace tempo::nn { [[nodiscard]] auto getTopology() const -> const Topology&; protected: - template<std::ranges::range R> - static void addPrecedenceEdges(const R &precedences, impl::TopologyData &topologyData) { - for (auto [from, to, dist] : precedences) { - assert(from != ORIGIN and from != HORIZON and to != ORIGIN and to != HORIZON && - "You probably passed a precedence between tasks instead of events!"); - Edge e(TASK(from), TASK(to)); + template<concepts::ttyped_range<DistanceConstraint> R> + static void addPrecedenceEdges(const R &precedences, const VarTaskMapping &vtMapping, + impl::TopologyData &topologyData) { + for (const auto &p : precedences) { + assert(vtMapping.contains(p.from) and vtMapping.contains(p.to)); + Edge e(vtMapping(p.from), vtMapping(p.to)); if (e.first == e.second or topologyData.edgeLookup.contains(e)) { continue; } @@ -146,16 +132,43 @@ namespace tempo::nn { static void addEdge(const Edge &e, bool isResourceEdge, IndexType maskVal, impl::TopologyData &topologyData); - static void completeSubGraph(const Resource<int> &resourceSpec, IndexType resource, - impl::TopologyData &topologyData); + template<SchedulingResource R> + static void completeSubGraph(const R &resourceSpec, IndexType resource, const VarTaskMapping &vtMapping, + impl::TopologyData &topologyData) { + auto taskId = [&vtMapping](const auto &t) { return vtMapping(t.start.id()); }; + const auto &tasks = resourceSpec; + for (std::size_t i = 0; i < tasks.size(); ++i) { + topologyData.taskIdx.emplace_back(taskId(tasks[i])); + topologyData.resIdx.emplace_back(resource); + topologyData.resDemands.emplace_back(static_cast<DataType>(resourceSpec.getDemand(i)) / + static_cast<DataType>(resourceSpec.resourceCapacity())); + for (std::size_t j = i + 1; j < tasks.size(); ++j) { + Edge e(taskId(tasks[i]), taskId(tasks[j])); + Edge rev(taskId(tasks[j]), taskId(tasks[i])); + topologyData.edgeRelResIdx.emplace_back(resource); + topologyData.edgeRelResIdx.emplace_back(resource); + if (topologyData.edgeLookup.contains(e)) { + topologyData.edgeIdx.emplace_back(topologyData.edgeLookup(e)); + topologyData.edgeIdx.emplace_back(topologyData.edgeLookup(rev)); + continue; + } + + addEdge(e, true, topologyData.pairMaskVal, topologyData); + addEdge(rev, true, topologyData.pairMaskVal, topologyData); + ++topologyData.pairMaskVal; + } + } + } private: Topology cache; }; - MAKE_FACTORY(MinimalTopologyBuilder, const ProblemInstance &problemInstance) { - return MinimalTopologyBuilder(problemInstance); - }}; + MAKE_TEMPLATE_FACTORY(MinimalTopologyBuilder, ESCAPE(concepts::scalar T, SchedulingResource R), + const ESCAPE(SchedulingProblemHelper<T, R>) &problemInstance) { + return MinimalTopologyBuilder(problemInstance); + } + }; } #endif //TEMPO_TOPOLOGY_EXTRACTORS_HPP diff --git a/src/header/nn/torch_types.hpp b/src/header/nn/torch_types.hpp index 0b02bcee66b80b173d8bee20530a62b9e0a5b6d0..9505728bb58ac01a2b3b22a21c42f69d0879c106 100644 --- a/src/header/nn/torch_types.hpp +++ b/src/header/nn/torch_types.hpp @@ -10,6 +10,8 @@ #include <torch/types.h> #include <vector> +#include "util/traits.hpp" + namespace tempo::nn { #ifdef TORCH_USE_GPU @@ -70,6 +72,41 @@ namespace tempo::nn { ResourceDependencies, EdgeResourceRelations, EdgePairMask}; }; + /** + * Represents the state of a the solver that can be used to extract the graph topology + */ + template<concepts::arbitrary_task_dist_fun Dist, typename S> + struct SolverState { + template<typename D, typename Sol> + explicit constexpr + SolverState(D &&distance, Sol &&solver): distance(std::forward<D>(distance)), solver(std::forward<Sol>(solver)) {} + Dist distance; + S solver; + }; + + /** + * Concept that models the interface of a topology extractor + * @tparam Extractor + * @tparam Args template parameters for solver state + */ + template<typename Extractor, typename ...Args> + concept topology_extractor = requires(Extractor e, const SolverState<Args...> &state) { + { e.getTopology(state) } -> std::convertible_to<Topology>; + }; + + + /** + * Helper function for SolverState + * @tparam Args argument types to SolverState + * @param args arguments to SolverState + * @return solver state view of the arguments. Depending on the reference type of the arguments, this is only + * a non owning object + */ + template<typename ...Args> + constexpr auto makeSolverState(Args &&...args) noexcept { + return SolverState<Args...>(std::forward<Args>(args)...); + } + } diff --git a/src/header/util/Options.hpp b/src/header/util/Options.hpp index 1ef0ed73e0f870f8cdc50ee27257ac3c86c5472c..b15702b1e50bab603ad019736e9432451bb1f0f1 100755 --- a/src/header/util/Options.hpp +++ b/src/header/util/Options.hpp @@ -23,16 +23,71 @@ #define __CMDLINE_HPP #include <tclap/CmdLine.h> +#include <limits> +#include <memory> + + +struct argbase { + virtual ~argbase() {} + virtual void assign() = 0; +}; + +template <typename Opt, typename ClapArg, typename E = void> +struct arg : public argbase { + ClapArg carg; + Opt &opt; + + template <typename... T> + arg(TCLAP::CmdLine &cmd, Opt &opt, T &&...args) + : carg(std::forward<T>(args)...), opt(opt) { + cmd.add(carg); + } + + void assign() override { opt = carg.getValue(); } +}; + +template<typename Opt, typename ClapArg> +struct arg<Opt, ClapArg, typename std::enable_if<std::is_enum<Opt>{}>::type> : public argbase { + ClapArg carg; + Opt &opt; + + template<typename... T> + arg(TCLAP::CmdLine &cmd, Opt &opt, T &&...args): carg(std::forward<T>(args)...), opt(opt) { + cmd.add(carg); + } + + void assign() override { + opt = static_cast<typename std::remove_reference<Opt>::type>(carg.getValue()); + } +}; + + +struct cmdline { + TCLAP::CmdLine cmd; + std::vector<std::unique_ptr<argbase>> args; + + explicit cmdline(const std::string &message, char delimiter = ' ', + const std::string &version = "none", bool helpAndVersion = true); + + template <typename ClapArg, typename Opt, typename... T> + void add(Opt &opt, T &&...clapargs) { + args.emplace_back(std::move(std::make_unique<arg<Opt, ClapArg>>( + cmd, opt, std::forward<T>(clapargs)...))); + } + + void parse(int argc, char *argv[]); +}; + namespace tempo { class Options { public: - Options() {} + Options() = default; // the actual options - std::string cmdline{""}; // for reference + std::string cmdline{}; // for reference std::string instance_file{"../data/osp/hurley/j7per0-0.txt"}; enum verbosity { SILENT = 0, QUIET, NORMAL, YACKING, SOLVERINFO }; @@ -96,6 +151,21 @@ public: Options parse(int argc, char *argv[]); +class Parser { + std::unique_ptr<Options> options; + std::unique_ptr<cmdline> cmdLine; +public: + explicit Parser(const std::string &name = "tempo"); + auto getOptions() noexcept -> Options &; + [[nodiscard]] auto getOptions() const noexcept -> const Options &; + auto getCmdLine() noexcept -> cmdline &; + [[nodiscard]] auto getCmdLine() const noexcept -> const cmdline &; + void parse(int argc, char **argv); +}; + +auto getBaseParser() -> Parser; + + static Options no_option; } // namespace tempo diff --git a/src/header/util/SchedulingProblemHelper.hpp b/src/header/util/SchedulingProblemHelper.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d46162f1ee83c4cc035e43bc85d45b6abf0d83e5 --- /dev/null +++ b/src/header/util/SchedulingProblemHelper.hpp @@ -0,0 +1,268 @@ +/** +* @author Tim Luchterhand +* @date 01.07.24 +* @brief utility for extracting timing information between tasks +*/ + +#ifndef TEMPO_SCHEDULINGPROBLEMHELPER_HPP +#define TEMPO_SCHEDULINGPROBLEMHELPER_HPP + +#include <concepts> +#include <limits> +#include <Iterators.hpp> +#include <vector> +#include <ranges> +#include <algorithm> +#include <cassert> +#include <stdexcept> + +#include "DirectedGraph.hpp" +#include "Model.hpp" +#include "util/Matrix.hpp" +#include "util/traits.hpp" + +namespace tempo { + + namespace detail { + template<concepts::scalar T> + struct NoValue { + static constexpr auto value() { + if constexpr (std::floating_point<T>) { + return std::numeric_limits<T>::infinity(); + } else { + return std::numeric_limits<T>::max(); + } + } + }; + + template<concepts::scalar T> + inline constexpr auto NoDistance = detail::NoValue<T>::value(); + + template<typename T, typename Arc> + void getDistances(Matrix<T> &matrix, const std::vector<std::vector<Arc>> &adjacency) noexcept { + for (auto [src, neighbors] : iterators::enumerate(adjacency)) { + for (const auto &dest : neighbors) { + matrix(src, dest) = dest.label(); + } + } + } + + template<typename Arc> + concept labeled_arc = concepts::same_template<Arc, LabeledEdge> or + concepts::same_template<Arc, StampedLabeledEdge>; + + template<typename P, typename T> + concept bound_provider = requires(const P instance, var_t var) { + { instance.upper(var) } -> std::same_as<T>; + { instance.lower(var) } -> std::same_as<T>; + }; + + template<concepts::scalar T, bound_provider<T> BoundProvider> + class TaskDistanceFunction { + const std::vector<Interval<T>> &tasks; + const BoundProvider &boundProvider; + Matrix<T> graphDistances; + public: + + template<labeled_arc Arc> + TaskDistanceFunction(const std::vector<Interval<T>> &tasks, const DirectedGraph<Arc> &varGraph, + const BoundProvider &boundProvider) : + tasks(tasks), boundProvider(boundProvider), + graphDistances(varGraph.size(), varGraph.size(), NoDistance<T>) { + getDistances(graphDistances, varGraph.forward()); + getDistances(graphDistances, varGraph.backward()); + } + + [[nodiscard]] T operator()(unsigned taskFrom, unsigned taskTo) const { + const auto srcVar = tasks[taskFrom].start; + const auto destVar = tasks[taskTo].end; + const auto boundDistance = boundProvider.upper(srcVar.id()) - boundProvider.lower(destVar.id()); + return std::min(graphDistances(srcVar.id(), destVar.id()), boundDistance) + destVar.offset(); + } + }; + } + + template<typename T> + concept SchedulingResource = concepts::ttyped_range<T, Interval> and requires(const T instance, unsigned taskId) { + { instance.resourceCapacity() } -> concepts::scalar; + { instance.getDemand(taskId) } -> concepts::scalar; + }; + + /** + * Mapping from variable id to task id + */ + class VarTaskMapping { + std::vector<unsigned> varToTask; + var_t offset{}; + static constexpr unsigned NoTask = std::numeric_limits<unsigned>::max(); + public: + static constexpr unsigned NumTemporalVarPerTask = 3; + VarTaskMapping() = default; + + /** + * Ctor + * @tparam Tasks range containing tempo::Interval + * @param tasks all tasks of the problem + */ + template<concepts::ttyped_range<tempo::Interval> Tasks> + requires(std::ranges::sized_range<Tasks>) + constexpr explicit VarTaskMapping(const Tasks &tasks) : + varToTask(NumTemporalVarPerTask * std::ranges::size(tasks), NoTask), + offset(std::numeric_limits<var_t>::max()) { + + var_t max = 0; + for (const auto &task : tasks) { + max = std::max({max, task.start.id(), task.end.id()}); + offset = std::min({offset, task.start.id(), task.end.id()}); + } + + if (not varToTask.empty() and max - offset >= NumTemporalVarPerTask * std::ranges::size(tasks)) { + throw std::runtime_error("expected continuous ranges of variable ids"); + } + + for (auto [idx, task]: iterators::enumerate(tasks)) { + varToTask[task.start.id() - offset] = idx; + varToTask[task.end.id() - offset] = idx; + } + } + + /** + * Mapping from variable id to task + * @param variable id of the variable + * @return corresponding task id + */ + [[nodiscard]] unsigned operator()(var_t variable) const noexcept; + + /** + * Whether the mapping contains a variable + * @param variable id of the variable + * @return true if variable is in mapping, false otherwise + */ + [[nodiscard]] bool contains(var_t variable) const noexcept; + + }; + + template<typename T> + class Solver; + + /** + * Class that represents a scheduling problem and provides helper functions + */ + template<concepts::scalar T, SchedulingResource R> + class SchedulingProblemHelper { + using TaskVec = std::vector<Interval<T>>; + using RVec = std::vector<R>; + using PrecVec = std::vector<DistanceConstraint<T>>; + TaskVec t; + RVec res; + PrecVec precs; + VarTaskMapping v2t; + Interval<T> sched; + + public: + SchedulingProblemHelper() = default; + + /** + * CTor + * @param tasks tasks contained in the problem + * @param resources resources in the problem + * @param precedences precedences between variables in the problem + * @param schedule Interval that represents the entire schedule + */ + SchedulingProblemHelper(TaskVec tasks, RVec resources, PrecVec precedences, Interval<T> schedule) : + t(std::move(tasks)), res(std::move(resources)), precs(std::move(precedences)), v2t(t), sched(schedule) {} + + /** + * read only access to the tasks + * @return vector with the tasks + */ + [[nodiscard]] auto tasks() const noexcept -> const TaskVec & { + return t; + } + + /** + * read only access to the resources + * @return vector with the resources + */ + [[nodiscard]] auto resources() const noexcept -> const RVec & { + return res; + } + + /** + * read only access to the precedences + * @return vector with the precedences + */ + [[nodiscard]] auto precedences() const noexcept -> const PrecVec & { + return precs; + } + + /** + * @return task associated with the variable + * @param variable id of a variable + * @note UB if variable not associated with a task + */ + [[nodiscard]] auto getTask(var_t variable) const noexcept -> TaskVec::const_reference { + return t[v2t(variable)]; + } + + /** + * Whether a given task id belongs to the problem + * @param taskId id of a task + * @return true if task id exists in problem, false otherwise + */ + [[nodiscard]] bool hasTask(unsigned taskId) const noexcept { + return taskId < t.size(); + } + + /** + * Whether a given variable id is associated with a task + * @param variable id of a variable + * @return whether the variable is contained in the variable-to-task mapping + */ + [[nodiscard]] bool hasVariable(var_t variable) const noexcept { + return v2t.contains(variable); + } + + /** + * gets the mapping function from tasks to variables + * @return mapping from variables to tasks + */ + [[nodiscard]] auto getMapping() const noexcept -> VarTaskMapping { + return v2t; + } + + /** + * Access to the interval representing the entire schedule + */ + auto schedule() const noexcept -> const Interval<T> & { + return sched; + } + + /** + * Gets a functor object that represents temporal distances between tasks. The distance between two tasks t1 and + * t2 is equal to dist(start_t1, end_t2) + * @tparam BoundProvider Class that provides upper and lower bounds for numeric variables + * @tparam Arc Arc type that contains a distance label + * @param varGraph directed graph representing distances between variables + * @param boundProvider object that provides upper and lower bounds + * @return distance function object that provides distances between tasks + */ + template<detail::bound_provider<T> BoundProvider, detail::labeled_arc Arc> + [[nodiscard]] auto getTaskDistances(const DirectedGraph<Arc> &varGraph, + const BoundProvider &boundProvider) const { + return detail::TaskDistanceFunction<T, BoundProvider>(t, varGraph, boundProvider); + } + + /** + * Function overload of getTaskDistances + * @param solver solver instance used to construct the task distance function + * @return distance function object that provides distances between tasks + */ + [[nodiscard]] auto getTaskDistances(const Solver<T> &solver) const { + using BP = std::remove_cvref_t<decltype(solver.numeric)>; + return detail::TaskDistanceFunction<T, BP>(t, solver.core, solver.numeric); + } + }; +} + +#endif //TEMPO_SCHEDULINGPROBLEMHELPER_HPP diff --git a/src/header/util/factory_pattern.hpp b/src/header/util/factory_pattern.hpp index ef1cf44b1eaa6deb71a057bdc6c6ce82e9d54753..7ff8a137f3766b8022fef28b3a657faa02fbb895 100644 --- a/src/header/util/factory_pattern.hpp +++ b/src/header/util/factory_pattern.hpp @@ -9,6 +9,8 @@ #include <string> #include <variant> +#define ESCAPE(...) __VA_ARGS__ + #define GET_MACRO(_1, _2, _3, _4, _5, _6, NAME, ...) NAME #define TYPE_ENTRY(...) GET_MACRO(__VA_ARGS__, \ TYPE_ENTRY6, TYPE_ENTRY5, TYPE_ENTRY4, TYPE_ENTRY3, TYPE_ENTRY2, TYPE_ENTRY1, TYPE_ENTRY0)(__VA_ARGS__) @@ -39,7 +41,6 @@ FACTORY_TYPE6, FACTORY_TYPE5, FACTORY_TYPE4, FACTORY_TYPE3, FACTORY_TYPE2, FACTO }; #define MAKE_T_FACTORY_PATTERN(TYPE_NAME, T_HEADER, CTOR_ARG, ...) \ - MAKE_POLYMORPHIC_TYPE(TYPE_NAME, __VA_ARGS__) \ class TYPE_NAME##Factory final { \ using TYPE_NAME##FactoryType = std::variant<FACTORY_ENTRY(__VA_ARGS__)>; \ \ @@ -53,7 +54,7 @@ FACTORY_TYPE6, FACTORY_TYPE5, FACTORY_TYPE4, FACTORY_TYPE3, FACTORY_TYPE2, FACTO return instance; \ } \ T_HEADER \ - auto create(const std::string &typeName, const CTOR_ARG &arguments) const \ + auto create(const std::string &typeName, CTOR_ARG arguments) const \ -> TYPE_NAME { \ const auto &constructor = registry.at(typeName); \ return std::visit( \ @@ -77,12 +78,16 @@ struct TYPE##Factory { \ #define MAKE_FACTORY_PATTERN(TYPE_NAME, CTOR_ARG, ...) \ MAKE_T_FACTORY_PATTERN(TYPE_NAME, , CTOR_ARG, __VA_ARGS__) -#define MAKE_FACTORY(TYPE, ...) \ -struct TYPE##Factory { \ - static TYPE create(__VA_ARGS__) +#define MAKE_P_FACTORY(TYPE, P_TYPE, ...) \ +struct TYPE##Factory { \ + static P_TYPE create(__VA_ARGS__) + +#define MAKE_TEMPLATE_P_FACTORY(TYPE, P_TYPE, T_ARG, ARG) \ + struct TYPE##Factory { \ + template <T_ARG> static P_TYPE create(ARG) + +#define MAKE_FACTORY(TYPE, ...) MAKE_P_FACTORY(TYPE, auto, __VA_ARGS__) -#define MAKE_TEMPLATE_FACTORY(TYPE, T_ARG, ARG) \ - struct TYPE##Factory { \ - template <T_ARG> static TYPE create(ARG) +#define MAKE_TEMPLATE_FACTORY(TYPE, T_ARG, ARG) MAKE_TEMPLATE_P_FACTORY(TYPE, auto, ESCAPE(T_ARG), ESCAPE(ARG)) #endif //TEMPO_FACTORY_PATTERN_HPP diff --git a/src/header/util/task_timings.hpp b/src/header/util/task_timings.hpp deleted file mode 100644 index cf245e8637f0330f0380912546c8e061476d24cf..0000000000000000000000000000000000000000 --- a/src/header/util/task_timings.hpp +++ /dev/null @@ -1,97 +0,0 @@ -/** - * @author Tim Luchterhand - * @date 10.11.23 - */ - -#ifndef TEMPO_TASK_TIMINGS_HPP -#define TEMPO_TASK_TIMINGS_HPP -#include "Global.hpp" -#include "traits.hpp" - -namespace tempo { - /** - * Reads out the minimum task duration from an event network - * @tparam EvtFun type of event distance function - * @param t task id - * @param eventDistanceFunction event distance function from which to read out the timing values - * @return minimum task duration - */ - template<concepts::arbitrary_event_dist_fun EvtFun> - constexpr auto minDuration(task t, const EvtFun &eventDistanceFunction) { - return -eventDistanceFunction(END(t), START(t)); - } - - /** - * Reads out the maximum task duration from an event network - * @tparam EvtFun type of event distance function - * @param t task id - * @param eventDistanceFunction event distance function from which to read out the timing values - * @return maximum task duration - */ - template<concepts::arbitrary_event_dist_fun EvtFun> - constexpr auto maxDuration(task t, const EvtFun &eventDistanceFunction) { - return eventDistanceFunction(START(t), END(t)); - } - - /** - * Reads out the earliest starting time of a task from an event network - * @tparam EvtFun type of event distance function - * @param t task id - * @param eventDistanceFunction event distance function from which to read out the timing values - * @return earliest task starting time - */ - template<concepts::arbitrary_event_dist_fun EvtFun> - constexpr auto earliestStartTime(task t, const EvtFun &eventDistanceFunction) { - return eventDistanceFunction(START(t), ORIGIN); - } - - /** - * Reads out the latest deadline of a task from an event network - * @tparam EvtFun type of event distance function - * @param t task id - * @param eventDistanceFunction event distance function from which to read out the timing values - * @return latest task completion time - */ - template<concepts::arbitrary_event_dist_fun EvtFun> - constexpr auto latestCompletion(task t, const EvtFun &eventDistanceFunction) { - return eventDistanceFunction(HORIZON, END(t)); - } - - /** - * Reads out the distance between two tasks from an event network, i.e. Let a and b be two tasks, then the distance - * between a and b is the distance between a's start event and b's end event - * @tparam EvtFun type of event distance function - * @param from id of the first task - * @param to id of the second task - * @param eventDistanceFunction event distance function from which to read out the timing values - * @return distance between START(a) and END(b) - */ - template<concepts::arbitrary_event_dist_fun EvtFun> - constexpr auto taskDistance(task from, task to, const EvtFun &eventDistanceFunction) { - return eventDistanceFunction(START(from), END(to)); - } - - /** - * Reads out the upper bound from an event network, i.e. the distance between the origin and the horizon events - * @tparam EvtFun type of event distance function - * @param eventDistanceFunction event distance function from which to read out the timing values - * @return distance between ORIGIN and HORIZON - */ - template<concepts::arbitrary_event_dist_fun EvtFun> - constexpr auto upperBound(const EvtFun &eventDistanceFunction) { - return eventDistanceFunction(ORIGIN, HORIZON); - } - - /** - * Reads out the lower bound from an event network, i.e. the negative distance between the horizon and origin events - * @tparam EvtFun type of event distance function - * @param eventDistanceFunction event distance function from which to read out the timing values - * @return negative distance between HORIZON and ORIGIN - */ - template<concepts::arbitrary_event_dist_fun EvtFun> - constexpr auto lowerBound(const EvtFun &eventDistanceFunction) { - return -eventDistanceFunction(HORIZON, ORIGIN); - } -} - -#endif //TEMPO_TASK_TIMINGS_HPP diff --git a/src/header/util/traits.hpp b/src/header/util/traits.hpp index 96e71a82a349ef21555105ca8f2449badda2e711..c9ec5cadd5538dc6e82ae2838eec47af2a1d2ee1 100644 --- a/src/header/util/traits.hpp +++ b/src/header/util/traits.hpp @@ -74,12 +74,18 @@ namespace tempo::concepts { template<typename T> concept scalar = std::integral<T> || std::floating_point<T>; -// template<typename E, typename T> -// concept event_dist_fun = callable_r<E, T, event, event>; -// -// template<typename E> -// concept arbitrary_event_dist_fun = std::invocable<E, event, event> && -// scalar<std::remove_reference_t<std::invoke_result_t<E, event, event>>>; + template<typename E, typename T> + concept task_dist_fun = callable_r<E, T, unsigned , unsigned>; + + template<typename E> + concept arbitrary_task_dist_fun = std::invocable<E, unsigned, unsigned> && + scalar<std::remove_reference_t<std::invoke_result_t<E, unsigned, unsigned>>>; + + template<typename S> + concept distance_provider = requires(const S instance, var_t e) { + { instance.numeric.upper(e) } -> scalar; + { instance.numeric.lower(e) } -> scalar; + }; template<typename R, typename T> concept typed_range = std::ranges::range<R> && std::same_as<std::ranges::range_value_t<R>, T>;