diff --git a/schedulers/tetrisched/include/tetrisched/CPLEXSolver.hpp b/schedulers/tetrisched/include/tetrisched/CPLEXSolver.hpp index 5157804e..db45f89a 100644 --- a/schedulers/tetrisched/include/tetrisched/CPLEXSolver.hpp +++ b/schedulers/tetrisched/include/tetrisched/CPLEXSolver.hpp @@ -30,6 +30,10 @@ class CPLEXSolver : public Solver { /// Translates the Constraint into a CPLEX expression. IloRange translateConstraint(const ConstraintPtr& constraint) const; + /// Translates the ObjectiveFunction into a CPLEX expression. + IloObjective translateObjectiveFunction( + const ObjectiveFunctionPtr& objectiveFunction) const; + public: /// Create a new CPLEXSolver. CPLEXSolver(); diff --git a/schedulers/tetrisched/include/tetrisched/Expression.hpp b/schedulers/tetrisched/include/tetrisched/Expression.hpp index 60e70efd..038f19b8 100644 --- a/schedulers/tetrisched/include/tetrisched/Expression.hpp +++ b/schedulers/tetrisched/include/tetrisched/Expression.hpp @@ -1,10 +1,10 @@ #ifndef _TETRISCHED_EXPRESSION_HPP_ #define _TETRISCHED_EXPRESSION_HPP_ +#include #include #include #include -#include #include "tetrisched/Partition.hpp" #include "tetrisched/SolverModel.hpp" @@ -49,9 +49,12 @@ struct ParseResult { std::optional endTime; /// The indicator associated with the parsed result. /// This indicator denotes if the expression was satisfied or not. - /// The indicator can return an integer if it is trivially satisfied during parsing. - /// Note that the startTime and endTime are only valid if the indicator is 1. + /// The indicator can return an integer if it is trivially satisfied during + /// parsing. Note that the startTime and endTime are only valid if the + /// indicator is 1. std::optional indicator; + /// The utility associated with the parsed result. + std::optional utility; }; struct PartitionTimePairHasher { @@ -99,6 +102,9 @@ class Expression { Time currentTime) = 0; }; +/// A `ChooseExpression` represents a choice of a required number of machines +/// from the set of resource partitions for the given duration starting at the +/// provided start_time. class ChooseExpression : public Expression { private: /// The Task instance that this ChooseExpression is being inserted into @@ -108,7 +114,7 @@ class ChooseExpression : public Expression { /// choose resources from. Partitions resourcePartitions; /// The number of partitions that this ChooseExpression needs to choose. - uint32_t numRequiredPartitions; + uint32_t numRequiredMachines; /// The start time of the choice represented by this Expression. Time startTime; /// The duration of the choice represented by this Expression. @@ -118,13 +124,30 @@ class ChooseExpression : public Expression { public: ChooseExpression(TaskPtr associatedTask, Partitions resourcePartitions, - uint32_t numRequiredPartitions, Time startTime, - Time duration); + uint32_t numRequiredMachines, Time startTime, Time duration); + void addChild(ExpressionPtr child) override; + ParseResult parse(SolverModelPtr solverModel, Partitions availablePartitions, + CapacityConstraintMap& capacityConstraints, + Time currentTime) override; +}; + +/// An `ObjectiveExpression` collates the objectives from its children and +/// informs the SolverModel of the objective function. +class ObjectiveExpression : public Expression { + private: + /// The sense of this Expression. + ObjectiveType objectiveType; + /// The children of this Expression. + std::vector children; + + public: + ObjectiveExpression(ObjectiveType objectiveType); void addChild(ExpressionPtr child) override; ParseResult parse(SolverModelPtr solverModel, Partitions availablePartitions, CapacityConstraintMap& capacityConstraints, Time currentTime) override; }; + } // namespace tetrisched #endif // _TETRISCHED_EXPRESSION_HPP_ diff --git a/schedulers/tetrisched/include/tetrisched/SolverModel.hpp b/schedulers/tetrisched/include/tetrisched/SolverModel.hpp index c5ae30c9..ba64af08 100644 --- a/schedulers/tetrisched/include/tetrisched/SolverModel.hpp +++ b/schedulers/tetrisched/include/tetrisched/SolverModel.hpp @@ -5,8 +5,8 @@ #include #include #include -#include #include +#include #include "tetrisched/Types.hpp" @@ -182,6 +182,12 @@ class ObjectiveFunctionT { /// Retrieve the number of terms in this ObjectiveFunction. size_t size() const; + + /// Merges this utility with another utility. + void merge(const ObjectiveFunctionT& other); + + /// Annotate friend classes for Solvers so that they have access to internals. + friend tetrisched::CPLEXSolver; }; // Specialize the ObjectiveFunction class for Integer. @@ -197,7 +203,7 @@ class SolverModelT { /// The constraints in this model. std::unordered_map>> constraints; /// The objective function in this model. - std::shared_ptr> objectiveFunction; + std::unique_ptr> objectiveFunction; /// Generate a new solver model. /// Construct a Solver to get an instance of the Model. diff --git a/schedulers/tetrisched/src/CPLEXSolver.cpp b/schedulers/tetrisched/src/CPLEXSolver.cpp index 64037073..5b616a9e 100644 --- a/schedulers/tetrisched/src/CPLEXSolver.cpp +++ b/schedulers/tetrisched/src/CPLEXSolver.cpp @@ -106,6 +106,62 @@ IloRange CPLEXSolver::translateConstraint( return rangeConstraint; } +IloObjective CPLEXSolver::translateObjectiveFunction( + const ObjectiveFunctionPtr& objectiveFunction) const { + IloExpr objectiveExpr(cplexEnv); + + // Construct all the terms. + for (auto& term : objectiveFunction->terms) { + if (term.second) { + // If the variable has not been translated, throw an error. + if (cplexVariables.find(term.second->getId()) == cplexVariables.end()) { + throw tetrisched::exceptions::SolverException( + "Variable " + term.second->getName() + + " not found in CPLEX model."); + } + // Call the relevant function to add the term to the constraint. + switch (term.second->variableType) { + case tetrisched::VariableType::VAR_INTEGER: + objectiveExpr += + term.first * + std::get(cplexVariables.at(term.second->getId())); + break; + case tetrisched::VariableType::VAR_CONTINUOUS: + objectiveExpr += + term.first * + std::get(cplexVariables.at(term.second->getId())); + break; + case tetrisched::VariableType::VAR_INDICATOR: + objectiveExpr += + term.first * + std::get(cplexVariables.at(term.second->getId())); + break; + default: + throw tetrisched::exceptions::SolverException( + "Unsupported variable type: " + term.second->variableType); + } + } else { + objectiveExpr += term.first; + } + } + + // Construct the Sense of the Constraint. + IloObjective objectiveConstraint; + switch (objectiveFunction->objectiveType) { + case tetrisched::ObjectiveType::OBJ_MAXIMIZE: + objectiveConstraint = IloMaximize(cplexEnv, objectiveExpr); + break; + case tetrisched::ObjectiveType::OBJ_MINIMIZE: + objectiveConstraint = IloMinimize(cplexEnv, objectiveExpr); + break; + default: + throw tetrisched::exceptions::SolverException( + "Unsupported objective type: " + objectiveFunction->objectiveType); + } + + return objectiveConstraint; +} + void CPLEXSolver::translateModel() { if (!solverModel) { throw tetrisched::exceptions::SolverException( @@ -132,6 +188,9 @@ void CPLEXSolver::translateModel() { cplexModel.add(translateConstraint(constraint.second)); } + // Translate the objective function. + cplexModel.add(translateObjectiveFunction(solverModel->objectiveFunction)); + // Extract the model to the CPLEX instance. cplexInstance.extract(cplexModel); } diff --git a/schedulers/tetrisched/src/Expression.cpp b/schedulers/tetrisched/src/Expression.cpp index 1771a821..505ef1eb 100644 --- a/schedulers/tetrisched/src/Expression.cpp +++ b/schedulers/tetrisched/src/Expression.cpp @@ -63,19 +63,14 @@ void CapacityConstraintMap::registerUsageForDuration(const Partition& partition, ChooseExpression::ChooseExpression(TaskPtr associatedTask, Partitions resourcePartitions, - uint32_t numRequiredPartitions, - Time startTime, Time duration) + uint32_t numRequiredMachines, Time startTime, + Time duration) : associatedTask(associatedTask), resourcePartitions(resourcePartitions), - numRequiredPartitions(numRequiredPartitions), + numRequiredMachines(numRequiredMachines), startTime(startTime), duration(duration), - endTime(startTime + duration) { - if (numRequiredPartitions > resourcePartitions.size()) { - throw tetrisched::exceptions::ExpressionConstructionException( - "ChooseExpression requires more partitions than are available."); - } -} + endTime(startTime + duration) {} void ChooseExpression::addChild(ExpressionPtr child) { throw tetrisched::exceptions::ExpressionConstructionException( @@ -134,7 +129,7 @@ ParseResult ChooseExpression::parse(SolverModelPtr solverModel, std::to_string(startTime), 0, std::min(static_cast(partition->size()), - numRequiredPartitions)); + numRequiredMachines)); // Add the variable to the demand constraint. fulfillsDemandConstraint->addTerm(1, allocationVar); @@ -146,17 +141,51 @@ ParseResult ChooseExpression::parse(SolverModelPtr solverModel, } // Ensure that if the Choose expression is satisfied, it fulfills the // demand for this expression. Pass the constraint to the model. - fulfillsDemandConstraint->addTerm(-1 * numRequiredPartitions, isSatisfiedVar); + fulfillsDemandConstraint->addTerm(-1 * numRequiredMachines, isSatisfiedVar); solverModel->addConstraint(std::move(fulfillsDemandConstraint)); + // Construct the Utility function for this Choose expression. + auto utility = + std::make_unique(ObjectiveType::OBJ_MAXIMIZE); + utility->addTerm(1, isSatisfiedVar); + // Construct the return value. return ParseResult{ .type = ParseResultType::EXPRESSION_UTILITY, .startTime = startTime, .endTime = endTime, .indicator = isSatisfiedVar, + .utility = std::move(utility), }; } + +ObjectiveExpression::ObjectiveExpression(ObjectiveType objectiveType) + : objectiveType(objectiveType) {} + +void ObjectiveExpression::addChild(ExpressionPtr child) { + children.push_back(std::move(child)); +} + +ParseResult ObjectiveExpression::parse( + SolverModelPtr solverModel, Partitions availablePartitions, + CapacityConstraintMap& capacityConstraints, Time currentTime) { + // Construct the overall utility of this expression. + auto utility = std::make_unique(objectiveType); + + // Parse the children and collect the utiltiies. + for (auto& child : children) { + auto result = child->parse(solverModel, availablePartitions, + capacityConstraints, currentTime); + if (result.type == ParseResultType::EXPRESSION_UTILITY) { + utility->merge(*result.utility.value()); + } + } + + // Add the utility to the SolverModel. + solverModel->setObjectiveFunction(std::move(utility)); + + return ParseResult{.type = ParseResultType::EXPRESSION_NO_UTILITY}; +} } // namespace tetrisched // // standard C/C++ libraries diff --git a/schedulers/tetrisched/src/SolverModel.cpp b/schedulers/tetrisched/src/SolverModel.cpp index 11f08186..289fe86a 100644 --- a/schedulers/tetrisched/src/SolverModel.cpp +++ b/schedulers/tetrisched/src/SolverModel.cpp @@ -186,6 +186,13 @@ size_t ObjectiveFunctionT::size() const { return terms.size(); } +template +void ObjectiveFunctionT::merge(const ObjectiveFunctionT &other) { + for (auto &term : other.terms) { + terms.push_back(term); + } +} + /* * Methods for SolverModel. * These methods provide an implementation of the Constraint class. diff --git a/schedulers/tetrisched/test/test_solver.cpp b/schedulers/tetrisched/test/test_solver.cpp index 7244f20e..493d7806 100644 --- a/schedulers/tetrisched/test/test_solver.cpp +++ b/schedulers/tetrisched/test/test_solver.cpp @@ -79,13 +79,17 @@ TEST(SolverModel, TestCPLEXSolverTranslation) { constraint->addTerm(2, intVar); constraint->addTerm(5); solverModelPtr->addConstraint(std::move(constraint)); + auto objectiveFunction = std::make_unique( + tetrisched::ObjectiveType::OBJ_MAXIMIZE); + objectiveFunction->addTerm(1, intVar); + solverModelPtr->setObjectiveFunction(std::move(objectiveFunction)); solverModelPtr->exportModel("test_solvermodel.lp"); EXPECT_TRUE(std::filesystem::exists("test_solvermodel.lp")) - << "The file test_solvermodel.lp was not created."; - std::filesystem::remove("test_solvermodel.lp"); + << "The file test_solvermodel.lp was not created."; + // std::filesystem::remove("test_solvermodel.lp"); cplexSolver.translateModel(); cplexSolver.exportModel("test_cplexmodel.lp"); EXPECT_TRUE(std::filesystem::exists("test_cplexmodel.lp")) - << "The file test_cplexmodel.lp was not created."; - std::filesystem::remove("test_cplexmodel.lp"); + << "The file test_cplexmodel.lp was not created."; + // std::filesystem::remove("test_cplexmodel.lp"); }