diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 96ba822..8488155 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - scala: [ 2.12.15, 2.13.8 ] + scala: [ 2.12.16, 2.13.8, 3.1.3 ] steps: - uses: actions/checkout@v2 - name: Set up JDK diff --git a/build.sbt b/build.sbt index e1213f9..2dd7645 100644 --- a/build.sbt +++ b/build.sbt @@ -6,20 +6,24 @@ val logger = ConsoleLogger() sonatypeProfileName := "com.github.vagmcs" -lazy val root = project.in(file(".")) +lazy val root = project + .in(file(".")) .aggregate(core, oj, lpsolve, gurobi, mosek) .settings(publish / skip := true) // Build settings for Optimus core -lazy val core = project.in(file("core")) +lazy val core = project + .in(file("core")) .enablePlugins(AutomateHeaderPlugin) .settings(Test / logLevel := Level.Info) .settings(name := "optimus") - .settings(Seq( - libraryDependencies ++= Dependencies.Logging, - libraryDependencies ++= Dependencies.ScalaTest, - libraryDependencies ++= Dependencies.Tools - )) + .settings( + Seq( + libraryDependencies ++= Dependencies.Logging, + libraryDependencies ++= Dependencies.ScalaTest, + libraryDependencies ++= Dependencies.Tools + ) + ) // Build settings for Optimus oj solver lazy val oj = Project("solver-oj", file("solver-oj")) @@ -36,31 +40,31 @@ lazy val lpsolve = Project("solver-lp", file("solver-lp")) .settings(libraryDependencies += Dependencies.LpSolve) // Build settings for Optimus gurobi solver -lazy val gurobi = if (file("lib/gurobi.jar").exists) - Project("solver-gurobi", file("solver-gurobi")) +lazy val gurobi = + if (file("lib/gurobi.jar").exists) Project("solver-gurobi", file("solver-gurobi")) .dependsOn(core % "compile->compile ; test->test") .enablePlugins(AutomateHeaderPlugin) .settings(name := "optimus-solver-gurobi") .settings(Compile / unmanagedJars += file("lib/gurobi.jar")) -else - Project("solver-gurobi", file("solver-gurobi")) - .settings({ + else Project("solver-gurobi", file("solver-gurobi")) + .settings { logger.warn { - "Building in the absence of support for the Gurobi solver [ 'gurobi.jar' not found in 'lib' directory ]."} - Seq(name := "optimus-solver-gurobi", publish := { }, publishLocal := { }) - }) + "Building in the absence of support for the Gurobi solver [ 'gurobi.jar' not found in 'lib' directory ]." + } + Seq(name := "optimus-solver-gurobi", publish := {}, publishLocal := {}) + } // Build settings for Optimus mosek solver -lazy val mosek = if (file("lib/mosek.jar").exists) - Project("solver-mosek", file("solver-mosek")) - .dependsOn(core % "compile->compile ; test->test") - .enablePlugins(AutomateHeaderPlugin) - .settings(name := "optimus-solver-mosek") - .settings(Compile / unmanagedJars += file("lib/mosek.jar")) -else - Project("solver-mosek", file("solver-mosek")) - .settings({ +lazy val mosek = + if (file("lib/mosek.jar").exists) Project("solver-mosek", file("solver-mosek")) + .dependsOn(core % "compile->compile ; test->test") + .enablePlugins(AutomateHeaderPlugin) + .settings(name := "optimus-solver-mosek") + .settings(Compile / unmanagedJars += file("lib/mosek.jar")) + else Project("solver-mosek", file("solver-mosek")) + .settings { logger.warn { - "Building in the absence of support for the Mosek solver [ 'mosek.jar' not found in 'lib' directory ]."} - Seq(name := "optimus-solver-mosek", publish := { }, publishLocal := { }) - }) \ No newline at end of file + "Building in the absence of support for the Mosek solver [ 'mosek.jar' not found in 'lib' directory ]." + } + Seq(name := "optimus-solver-mosek", publish := {}, publishLocal := {}) + } diff --git a/core/src/main/scala/optimus/algebra/ConstraintRelation.scala b/core/src/main/scala/optimus/algebra/ConstraintRelation.scala index 3ee96a3..7011caa 100644 --- a/core/src/main/scala/optimus/algebra/ConstraintRelation.scala +++ b/core/src/main/scala/optimus/algebra/ConstraintRelation.scala @@ -16,14 +16,9 @@ package optimus.algebra -import enumeratum._ -import scala.collection.immutable._ +sealed abstract class ConstraintRelation(val entryName: String) -sealed abstract class ConstraintRelation(override val entryName: String) extends EnumEntry - -object ConstraintRelation extends Enum[ConstraintRelation] { - - val values: IndexedSeq[ConstraintRelation] = findValues +object ConstraintRelation { case object GE extends ConstraintRelation(">=") case object LE extends ConstraintRelation("<=") diff --git a/core/src/main/scala/optimus/algebra/Expression.scala b/core/src/main/scala/optimus/algebra/Expression.scala index 2b76cb3..1b4c049 100644 --- a/core/src/main/scala/optimus/algebra/Expression.scala +++ b/core/src/main/scala/optimus/algebra/Expression.scala @@ -17,9 +17,7 @@ package optimus.algebra import com.typesafe.scalalogging.LazyLogging -import gnu.trove.procedure.TLongDoubleProcedure import optimus.algebra.ConstraintRelation._ -import optimus.algebra.AlgebraOps._ /** * Expression abstraction, should be extended by anything that is @@ -50,7 +48,7 @@ abstract class Expression extends LazyLogging { case _ => Product(this, that) } - def unary_-(): Expression = Minus(0, this) + def unary_- : Expression = Minus(0, this) def <:=(that: Expression): Constraint = Constraint(this, LE, that) @@ -116,7 +114,7 @@ abstract class Var(val symbol: String) extends Expression { case _ => Product(this, other) } - override def unary_-(): Expression = Term(Const(-1), Vector(this)) + override def unary_- : Expression = Term(Const(-1), Vector(this)) /** @return the symbol of the variable */ override def toString: String = if (symbol != ANONYMOUS) symbol else s"x@$index" @@ -162,7 +160,7 @@ case class Term(scalar: Const, vars: Vector[Var]) extends Expression { case _ => Product(this, that) } - override def unary_-(): Expression = Term(Const(-scalar.value), vars) + override def unary_- : Expression = Term(Const(-scalar.value), vars) override def toString: String = s"$scalar${vars.mkString("*")}" } @@ -198,7 +196,7 @@ class Const(val value: Double) extends Expression { override def *(other: Expression): Expression = ConstProduct(this, other) - override def unary_-(): Expression = Const(-value) + override def unary_- : Expression = Const(-value) override def toString: String = value.toString @@ -232,14 +230,14 @@ case object Zero extends Const(0) { override def *(expression: Expression): Expression = this - override def unary_-(): Expression = this + override def unary_- : Expression = this } case object One extends Const(1) { override def *(expression: Expression): Expression = this - override def unary_-(): Const = Const(-1) + override def unary_- : Const = Const(-1) } /** @@ -258,7 +256,7 @@ case class ConstProduct(scalar: Const, a: Expression) extends Expression { case _ => LongDoubleMap(a.terms.keys, a.terms.values.map(_ * scalar.value)) } - override def unary_-(): Expression = ConstProduct(Const(-scalar.value), a) + override def unary_- : Expression = ConstProduct(Const(-scalar.value), a) } // ------------------------------------- Operator Expressions ------------------------------------- @@ -307,7 +305,7 @@ case class Plus(override val a: Expression, override val b: Expression) extends protected def op(x: Double, y: Double): Double = x + y - override def unary_-(): Expression = Plus(-a, -b) + override def unary_- : Expression = Plus(-a, -b) } /** @@ -320,7 +318,7 @@ case class Minus(override val a: Expression, override val b: Expression) extends protected def op(x: Double, y: Double): Double = x - y - override def unary_-(): Expression = Minus(b, a) + override def unary_- : Expression = Minus(b, a) } /** diff --git a/core/src/main/scala/optimus/algebra/ExpressionType.scala b/core/src/main/scala/optimus/algebra/ExpressionType.scala index a308288..e62e159 100644 --- a/core/src/main/scala/optimus/algebra/ExpressionType.scala +++ b/core/src/main/scala/optimus/algebra/ExpressionType.scala @@ -16,14 +16,9 @@ package optimus.algebra -import enumeratum._ -import scala.collection.immutable._ +sealed trait ExpressionType -sealed trait ExpressionType extends EnumEntry - -object ExpressionType extends Enum[ExpressionType] { - - val values: IndexedSeq[ExpressionType] = findValues +object ExpressionType { case object CONSTANT extends ExpressionType case object LINEAR extends ExpressionType diff --git a/core/src/main/scala/optimus/optimization/SolverFactory.scala b/core/src/main/scala/optimus/optimization/SolverFactory.scala index 7692646..b10a5ff 100644 --- a/core/src/main/scala/optimus/optimization/SolverFactory.scala +++ b/core/src/main/scala/optimus/optimization/SolverFactory.scala @@ -11,7 +11,7 @@ * \///// \/// \///// \/// \/// \/// \/// \///////// \////////// * * The mathematical programming library for Scala. - * + * */ package optimus.optimization diff --git a/core/src/main/scala/optimus/optimization/enums/PreSolve.scala b/core/src/main/scala/optimus/optimization/enums/PreSolve.scala index 0183865..4f797a9 100644 --- a/core/src/main/scala/optimus/optimization/enums/PreSolve.scala +++ b/core/src/main/scala/optimus/optimization/enums/PreSolve.scala @@ -16,14 +16,9 @@ package optimus.optimization.enums -import enumeratum._ -import scala.collection.immutable._ +sealed abstract class PreSolve(val entryName: String) -sealed abstract class PreSolve(override val entryName: String) extends EnumEntry - -object PreSolve extends Enum[PreSolve] { - - val values: IndexedSeq[PreSolve] = findValues +object PreSolve { case object DISABLED extends PreSolve("Disabled") case object CONSERVATIVE extends PreSolve("Conservative") diff --git a/core/src/main/scala/optimus/optimization/enums/SolutionStatus.scala b/core/src/main/scala/optimus/optimization/enums/SolutionStatus.scala index c924c98..8293838 100644 --- a/core/src/main/scala/optimus/optimization/enums/SolutionStatus.scala +++ b/core/src/main/scala/optimus/optimization/enums/SolutionStatus.scala @@ -16,14 +16,9 @@ package optimus.optimization.enums -import enumeratum._ -import scala.collection.immutable._ +sealed abstract class SolutionStatus(val entryName: String) -sealed abstract class SolutionStatus(override val entryName: String) extends EnumEntry - -object SolutionStatus extends Enum[SolutionStatus] { - - val values: IndexedSeq[SolutionStatus] = findValues +object SolutionStatus { case object NOT_SOLVED extends SolutionStatus("Not solved") case object OPTIMAL extends SolutionStatus("Optimal") diff --git a/core/src/main/scala/optimus/optimization/enums/SolverLib.scala b/core/src/main/scala/optimus/optimization/enums/SolverLib.scala index 8a1c7c9..238f4a0 100644 --- a/core/src/main/scala/optimus/optimization/enums/SolverLib.scala +++ b/core/src/main/scala/optimus/optimization/enums/SolverLib.scala @@ -16,14 +16,9 @@ package optimus.optimization.enums -import enumeratum._ -import scala.collection.immutable._ +sealed trait SolverLib -sealed trait SolverLib extends EnumEntry - -object SolverLib extends Enum[SolverLib] { - - val values: IndexedSeq[SolverLib] = findValues +object SolverLib { case object oJSolver extends SolverLib case object LpSolve extends SolverLib diff --git a/core/src/test/scala/optimus/algebra/AlgebraSpecTest.scala b/core/src/test/scala/optimus/algebra/AlgebraSpecTest.scala index cc5edc6..7b154b7 100644 --- a/core/src/test/scala/optimus/algebra/AlgebraSpecTest.scala +++ b/core/src/test/scala/optimus/algebra/AlgebraSpecTest.scala @@ -22,9 +22,7 @@ import optimus.optimization.MPModel import optimus.algebra.AlgebraOps._ import optimus.optimization.model.{ INFINITE, MPFloatVar, MPIntVar } -/** - * Specification test for algebra. - */ +/** Specification test for algebra. */ final class AlgebraSpecTest extends AnyFunSpec with Matchers { implicit val model: MPModel = MPModel() @@ -116,7 +114,7 @@ final class AlgebraSpecTest extends AnyFunSpec with Matchers { } it("x * (-1) should be equal to -x") { - x * (-1.0) shouldEqual -x + x * -1.0 shouldEqual -x } it("0 - x should be equal to -x") { @@ -179,7 +177,9 @@ final class AlgebraSpecTest extends AnyFunSpec with Matchers { /* * Produces 3.0(x * t) + 2.0(x * y) + 2.0(x * z) + 1.0(y * z) + 2.0(z * t) + 2.0z + 4.0t + 9.0 */ - it("(2*x*y + 2*z*t) + (2*x*z + 4*t + 5) + (3*x*t + z + y*z) + 4 + z should be equal to 3*x*t + 2*x*y + 2*x*z + 1*y*z + 2*z*t + 2*z + 4*t + 9") { + it( + "(2*x*y + 2*z*t) + (2*x*z + 4*t + 5) + (3*x*t + z + y*z) + 4 + z should be equal to 3*x*t + 2*x*y + 2*x*z + 1*y*z + 2*z*t + 2*z + 4*t + 9" + ) { expression1 + expression2 + expression3 + expression4 shouldEqual sum(expressions) } } @@ -223,7 +223,7 @@ final class AlgebraSpecTest extends AnyFunSpec with Matchers { } it("-x + 4*y + x should equal to 4*y") { - -x + 4 * y + x should equal (4 * y) + -x + 4 * y + x should equal(4 * y) } it("(x + y) + (2*x + y) should equal to 3*x + 2*y") { @@ -286,7 +286,10 @@ final class AlgebraSpecTest extends AnyFunSpec with Matchers { val startSum = System.currentTimeMillis() sum(variables1) - info("Summation of " + variables1.length + " variables took " + (System.currentTimeMillis() - startSum) + "ms to calculate") + info( + "Summation of " + variables1.length + " variables took " + (System + .currentTimeMillis() - startSum) + "ms to calculate" + ) val startProd = System.currentTimeMillis() val expr = (x + y + x + y + t + z + t + z + 4.1 * y + x + 5) * (x + y + x + y + t + z + t + z + y + x + 2) diff --git a/core/src/test/scala/optimus/algebra/EncodeDecodeSpecTest.scala b/core/src/test/scala/optimus/algebra/EncodeDecodeSpecTest.scala index a208f11..526d3a7 100644 --- a/core/src/test/scala/optimus/algebra/EncodeDecodeSpecTest.scala +++ b/core/src/test/scala/optimus/algebra/EncodeDecodeSpecTest.scala @@ -50,9 +50,9 @@ final class EncodeDecodeSpecTest extends AnyFunSpec with Matchers with ScalaChec } yield (x, y) forAll(generator) { - case (x: Int, y: Int) => whenever (x != y) { - encode(x) shouldNot be (encode(y)) - } + case (x: Int, y: Int) => whenever(x != y) { + encode(x) shouldNot be(encode(y)) + } } } } @@ -76,10 +76,7 @@ final class EncodeDecodeSpecTest extends AnyFunSpec with Matchers with ScalaChec y <- Gen.choose(0, Int.MaxValue) } yield (x, y) - forAll(generator) { - case (x: Int, y: Int) => - encode(x, y) shouldEqual encode(y, x) - } + forAll(generator) { case (x: Int, y: Int) => encode(x, y) shouldEqual encode(y, x) } } it("An encoding should never be identical to another") { @@ -92,9 +89,9 @@ final class EncodeDecodeSpecTest extends AnyFunSpec with Matchers with ScalaChec } yield (x, y, z, q) forAll(generator) { - case (x: Int, y: Int, z: Int, q: Int) => whenever (x != z && y != q) { - encode(x, y) shouldNot be (encode(z, q)) - } + case (x: Int, y: Int, z: Int, q: Int) => whenever(x != z && y != q) { + encode(x, y) shouldNot be(encode(z, q)) + } } } } diff --git a/core/src/test/scala/optimus/optimization/Diet.scala b/core/src/test/scala/optimus/optimization/Diet.scala index 0e9d78e..6abd3df 100644 --- a/core/src/test/scala/optimus/optimization/Diet.scala +++ b/core/src/test/scala/optimus/optimization/Diet.scala @@ -16,6 +16,7 @@ package optimus.optimization +import optimus.algebra._ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers import optimus.algebra.AlgebraOps._ @@ -23,14 +24,14 @@ import optimus.optimization.enums.{ SolutionStatus, SolverLib } import optimus.optimization.model.MPFloatVar /** - * The goal of the diet problem is to find the cheapest combination of foods - * that satisfy all the daily nutritional requirements of a person. The problem - * is formulated as a linear program where the objective is to minimize the cost - * and meet constraints which require that nutritional needs be satisfied. - * - * We include constraints that regulate the number of calories and the amounts - * of vitamins, minerals, fats, sodium and cholesterol in the diet. - */ + * The goal of the diet problem is to find the cheapest combination of foods + * that satisfy all the daily nutritional requirements of a person. The problem + * is formulated as a linear program where the objective is to minimize the cost + * and meet constraints which require that nutritional needs be satisfied. + * + * We include constraints that regulate the number of calories and the amounts + * of vitamins, minerals, fats, sodium and cholesterol in the diet. + */ trait Diet extends AnyFunSpec with Matchers { def solver: SolverLib @@ -40,12 +41,10 @@ trait Diet extends AnyFunSpec with Matchers { case class Nutriment(name: String) case class Food(x: MPFloatVar, price: Double, contents: Nutriment => Double) - private val nutriments: List[Nutriment] = - List("A", "C", "B1", "B2", "NA", "CAL").map(Nutriment) + private val nutriments: List[Nutriment] = List("A", "C", "B1", "B2", "NA", "CAL").map(Nutriment.apply) // Each food is limited between 2 and 10 - private def createVar(name: String) = - MPFloatVar(name, 2, 10) + private def createVar(name: String) = MPFloatVar(name, 2, 10) val foods: List[Food] = List( (createVar("Beef"), 3.19, List(60, 20, 10, 15, 938, 295)), @@ -61,11 +60,10 @@ trait Diet extends AnyFunSpec with Matchers { describe("Diet Problem") { // for each nutriment, at least 700 must be present in the Diet - for (n <- nutriments) - add(sum(foods) { f => f.x * f.contents(n) } >:= 700) + for (n <- nutriments) add(sum(foods)(f => f.x * f.contents(n)) >:= 700) // minimize the total cost - minimize(sum(foods) { f => f.x * f.price }) + minimize(sum(foods)(f => f.x * f.price)) start() diff --git a/core/src/test/scala/optimus/optimization/Knapsack.scala b/core/src/test/scala/optimus/optimization/Knapsack.scala index 77923ec..af72be5 100644 --- a/core/src/test/scala/optimus/optimization/Knapsack.scala +++ b/core/src/test/scala/optimus/optimization/Knapsack.scala @@ -16,6 +16,7 @@ package optimus.optimization +import optimus.algebra._ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers import optimus.algebra.AlgebraOps._ @@ -24,13 +25,13 @@ import optimus.optimization.model.MPBinaryVar import scala.util.Random /** - * Knapsack problem: Given a set of items, each with a weight and a value, - * determine the number of each item to include in a collection so that the - * total weight is less than or equal to a given limit and the total value is - * as large as possible. It origins from the problem faced by someone who is - * constrained by a fixed-size knapsack and must fill it with the most - * valuable items. - */ + * Knapsack problem: Given a set of items, each with a weight and a value, + * determine the number of each item to include in a collection so that the + * total weight is less than or equal to a given limit and the total value is + * as large as possible. It origins from the problem faced by someone who is + * constrained by a fixed-size knapsack and must fill it with the most + * valuable items. + */ trait Knapsack extends AnyFunSpec with Matchers { def solver: SolverLib @@ -45,9 +46,7 @@ trait Knapsack extends AnyFunSpec with Matchers { implicit val knapsackProblem: MPModel = MPModel(solver) - val items = Array.tabulate(weights.length) { i => - Item(weights(i), utility(i), MPBinaryVar(s"x$i")) - } + val items = Array.tabulate(weights.length)(i => Item(weights(i), utility(i), MPBinaryVar(s"x$i"))) // Maximize the total utility maximize(sum(items)(item => item.x * item.utility)) @@ -90,9 +89,7 @@ trait Knapsack extends AnyFunSpec with Matchers { implicit val knapsackProblem: MPModel = MPModel(solver) - val items = Array.tabulate(weights.length) { i => - Item(weights(i), utility(i), MPBinaryVar(s"x$i")) - } + val items = Array.tabulate(weights.length)(i => Item(weights(i), utility(i), MPBinaryVar(s"x$i"))) // Maximize the total utility maximize(sum(items)(item => item.x * item.utility)) diff --git a/core/src/test/scala/optimus/optimization/MaxFlow.scala b/core/src/test/scala/optimus/optimization/MaxFlow.scala index 609b9cc..a0bac88 100644 --- a/core/src/test/scala/optimus/optimization/MaxFlow.scala +++ b/core/src/test/scala/optimus/optimization/MaxFlow.scala @@ -16,6 +16,7 @@ package optimus.optimization +import optimus.algebra._ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers import optimus.algebra.AlgebraOps._ @@ -23,11 +24,11 @@ import optimus.optimization.enums.{ SolutionStatus, SolverLib } import optimus.optimization.model.MPFloatVar /** - * The Maximum Flow Problem in a network G = (V, E), where V is a set of nodes, - * E within V x V is a set of arcs, is to maximize the flow from one given - * node s (source) to another given node t (sink) subject to conservation of - * flow constraints at each node and flow capacities on each arc. - */ + * The Maximum Flow Problem in a network G = (V, E), where V is a set of nodes, + * E within V x V is a set of arcs, is to maximize the flow from one given + * node s (source) to another given node t (sink) subject to conservation of + * flow constraints at each node and flow capacities on each arc. + */ trait MaxFlow extends AnyFunSpec with Matchers { def solver: SolverLib @@ -49,12 +50,9 @@ trait MaxFlow extends AnyFunSpec with Matchers { Array(0, 0, 0, 0, 0, 63, 0, 0, 20) ) - val x = Array.tabulate(lines.size, columns.size)( - (l, c) => MPFloatVar(s"x($l,$c)", 0, capacities(l)(c)) - ) + val x = Array.tabulate(lines.size, columns.size)((l, c) => MPFloatVar(s"x($l,$c)", 0, capacities(l)(c))) - for (l <- 1 until lines.size) - add(sum(columns)(c => x(l)(c)) - sum(lines)(c => x(c)(l)) := 0) + for (l <- 1 until lines.size) add(sum(columns)(c => x(l)(c)) - sum(lines)(c => x(c)(l)) := 0) maximize(sum(lines)(l => x(l)(columns.size - 1))) @@ -69,8 +67,7 @@ trait MaxFlow extends AnyFunSpec with Matchers { } it("all variables should have a value") { - for (l <- lines; c <- columns) - x(l)(c).value.isDefined shouldBe true + for (l <- lines; c <- columns) x(l)(c).value.isDefined shouldBe true } it(s"$solver constraints should be satisfied") { @@ -79,9 +76,8 @@ trait MaxFlow extends AnyFunSpec with Matchers { // Print solution for (l <- lines) { - for (c <- columns) - print(s"${x(l)(c).value.get} ") - println + for (c <- columns) print(s"${x(l)(c).value.get} ") + println() } release() diff --git a/core/src/test/scala/optimus/optimization/ProblemTests.scala b/core/src/test/scala/optimus/optimization/ProblemTests.scala index e96de58..f13cb48 100644 --- a/core/src/test/scala/optimus/optimization/ProblemTests.scala +++ b/core/src/test/scala/optimus/optimization/ProblemTests.scala @@ -19,38 +19,28 @@ package optimus.optimization import optimus.optimization.enums.SolverLib /** - * A set of MIP problem tests that should be implemented - * by any MIP solver to test their proper functionality. - */ -trait MIPProblemTests - extends Knapsack - with Queens - with Sudoku - with Warehouse - with Workforce { + * A set of MIP problem tests that should be implemented + * by any MIP solver to test their proper functionality. + */ +trait MIPProblemTests extends Knapsack with Queens with Sudoku with Warehouse with Workforce { def solver: SolverLib } /** - * A set of LP problem tests that should be implemented - * by any LP solver to test their proper functionality. - */ -trait LPProblemTests - extends Diet - with MaxFlow - with ProductionPlanning { + * A set of LP problem tests that should be implemented + * by any LP solver to test their proper functionality. + */ +trait LPProblemTests extends Diet with MaxFlow with ProductionPlanning { def solver: SolverLib } /** - * A set of all the above problem tests that should be implemented - * by any LP/MIP solver to test their proper functionality. - */ -trait ProblemTests - extends LPProblemTests - with MIPProblemTests { + * A set of all the above problem tests that should be implemented + * by any LP/MIP solver to test their proper functionality. + */ +trait ProblemTests extends LPProblemTests with MIPProblemTests { def solver: SolverLib } diff --git a/core/src/test/scala/optimus/optimization/ProductionPlanning.scala b/core/src/test/scala/optimus/optimization/ProductionPlanning.scala index 98580a5..50063e0 100644 --- a/core/src/test/scala/optimus/optimization/ProductionPlanning.scala +++ b/core/src/test/scala/optimus/optimization/ProductionPlanning.scala @@ -16,6 +16,7 @@ package optimus.optimization +import optimus.algebra._ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers import optimus.algebra.AlgebraOps._ @@ -23,13 +24,13 @@ import optimus.optimization.enums.{ SolutionStatus, SolverLib } import optimus.optimization.model.MPFloatVar /** - * A number of 12 products can be produced. Each of them has a set of features, - * such as volume, weight, etc. There is a capacity constraint on the total amount - * that can be produced from each feature; for instance, an upper limit of the - * total weight of the produced products. Moreover, each product generates a profit - * per unit produced. The objective is to maximize the total profit, while - * satisfying these capacity constraints. - */ + * A number of 12 products can be produced. Each of them has a set of features, + * such as volume, weight, etc. There is a capacity constraint on the total amount + * that can be produced from each feature; for instance, an upper limit of the + * total weight of the produced products. Moreover, each product generates a profit + * per unit produced. The objective is to maximize the total profit, while + * satisfying these capacity constraints. + */ trait ProductionPlanning extends AnyFunSpec with Matchers { def solver: SolverLib @@ -59,10 +60,9 @@ trait ProductionPlanning extends AnyFunSpec with Matchers { val x = products.map(p => MPFloatVar(s"x$p", 0, 10000)) - maximize(sum(products) { p => x(p) * c(p) }) + maximize(sum(products)(p => x(p) * c(p))) - for (d <- dimensions) - add(sum(products)(p => x(p) * w(d)(p)) <:= b(d)) + for (d <- dimensions) add(sum(products)(p => x(p) * w(d)(p)) <:= b(d)) start() diff --git a/core/src/test/scala/optimus/optimization/Queens.scala b/core/src/test/scala/optimus/optimization/Queens.scala index 51976ea..1b0d663 100644 --- a/core/src/test/scala/optimus/optimization/Queens.scala +++ b/core/src/test/scala/optimus/optimization/Queens.scala @@ -16,6 +16,7 @@ package optimus.optimization +import optimus.algebra._ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers import optimus.algebra.AlgebraOps._ @@ -23,10 +24,10 @@ import optimus.optimization.enums.{ SolutionStatus, SolverLib } import optimus.optimization.model.MPBinaryVar /** - * N-Queens puzzle: Place n chess queens on an n×n chessboard so that no two - * queens threaten each other. Thus, a solution requires that no two queens - * share the same row, column, or diagonal. - */ + * N-Queens puzzle: Place n chess queens on an n×n chessboard so that no two + * queens threaten each other. Thus, a solution requires that no two queens + * share the same row, column, or diagonal. + */ trait Queens extends AnyFunSpec with Matchers { def solver: SolverLib @@ -41,7 +42,7 @@ trait Queens extends AnyFunSpec with Matchers { val x = Array.tabulate(n, n)((l, c) => MPBinaryVar(s"x($l,$c)")) - maximize(sum(lines, columns) { (l, c) => x(l)(c) }) + maximize(sum(lines, columns)((l, c) => x(l)(c))) // At most one queen can be placed in each row for (l <- lines) add(sum(columns)(c => x(l)(c)) <:= 1) @@ -77,9 +78,8 @@ trait Queens extends AnyFunSpec with Matchers { // Print solution as a board for (i <- 0 until n) { - for (j <- 0 until n) - if (x(i)(j).value.get >= .9) print(" Q ") else print(" . ") - println + for (j <- 0 until n) if (x(i)(j).value.get >= .9) print(" Q ") else print(" . ") + println() } release() @@ -95,7 +95,7 @@ trait Queens extends AnyFunSpec with Matchers { val x = Array.tabulate(n, n)((l, c) => MPBinaryVar(s"x($l,$c)")) - maximize(sum(lines, columns) { (l, c) => x(l)(c) }) + maximize(sum(lines, columns)((l, c) => x(l)(c))) // At most one queen can be placed in each row for (l <- lines) add(sum(columns)(c => x(l)(c)) <:= 1) @@ -131,9 +131,8 @@ trait Queens extends AnyFunSpec with Matchers { // Print solution as a board for (i <- 0 until n) { - for (j <- 0 until n) - if (x(i)(j).value.get >= .9) print(" Q ") else print(" . ") - println + for (j <- 0 until n) if (x(i)(j).value.get >= .9) print(" Q ") else print(" . ") + println() } release() diff --git a/core/src/test/scala/optimus/optimization/Sudoku.scala b/core/src/test/scala/optimus/optimization/Sudoku.scala index e65e548..c540275 100644 --- a/core/src/test/scala/optimus/optimization/Sudoku.scala +++ b/core/src/test/scala/optimus/optimization/Sudoku.scala @@ -16,6 +16,7 @@ package optimus.optimization +import optimus.algebra._ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers import optimus.algebra.AlgebraOps._ @@ -23,12 +24,12 @@ import optimus.optimization.enums.{ SolutionStatus, SolverLib } import optimus.optimization.model.MPBinaryVar /** - * Sudoku is a logic-based combinatorial number-placement puzzle. The objective - * is to fill a 9×9 grid with digits so that each column, each row, and each of - * the nine 3×3 sub-grids that compose the grid (also called blocks) contains all - * of the digits from 1 to 9. The puzzle setter provides a partially completed grid, - * which for a well-posed puzzle has a unique optimal solution. - */ + * Sudoku is a logic-based combinatorial number-placement puzzle. The objective + * is to fill a 9×9 grid with digits so that each column, each row, and each of + * the nine 3×3 sub-grids that compose the grid (also called blocks) contains all + * of the digits from 1 to 9. The puzzle setter provides a partially completed grid, + * which for a well-posed puzzle has a unique optimal solution. + */ trait Sudoku extends AnyFunSpec with Matchers { def solver: SolverLib @@ -39,23 +40,18 @@ trait Sudoku extends AnyFunSpec with Matchers { val n = 9 val N = 0 until n - val x = Array.tabulate(n, n, n)( - (l, c, n) => MPBinaryVar(s"x($l, $c, $n)") - ) + val x = Array.tabulate(n, n, n)((l, c, n) => MPBinaryVar(s"x($l, $c, $n)")) maximize(0) // each cell must be assigned exactly one integer - for (l <- N; c <- N) - add(sum(N)(n => x(l)(c)(n)) := 1) + for (l <- N; c <- N) add(sum(N)(n => x(l)(c)(n)) := 1) // cells in the same row must be assigned distinct numbers - for (l <- N; n <- N) - add(sum(N)(c => x(l)(c)(n)) := 1) + for (l <- N; n <- N) add(sum(N)(c => x(l)(c)(n)) := 1) // cells in the same column must be assigned distinct numbers - for (c <- N; n <- N) - add(sum(N)(l => x(l)(c)(n)) := 1) + for (c <- N; n <- N) add(sum(N)(l => x(l)(c)(n)) := 1) // cells in the same region must be assigned distinct numbers for (l1 <- 0 until 3; c1 <- 0 until 3; n <- N) @@ -107,8 +103,7 @@ trait Sudoku extends AnyFunSpec with Matchers { } it("all variables should have a value") { - for (l <- N; c <- N; n <- N) - x(l)(c)(n).value.isDefined shouldBe true + for (l <- N; c <- N; n <- N) x(l)(c)(n).value.isDefined shouldBe true } it(s"$solver constraints should be satisfied") { @@ -117,11 +112,10 @@ trait Sudoku extends AnyFunSpec with Matchers { // Print solution for (l <- N) { - for (c <- N; n <- N; if x(l)(c)(n).value.get == 1) { + for (c <- N; n <- N; if x(l)(c)(n).value.get == 1) if ((c + 1) % 3 == 0) print(s"${n + 1} | ") else print(s"${n + 1} ") - } - println + println() } release() diff --git a/core/src/test/scala/optimus/optimization/Warehouse.scala b/core/src/test/scala/optimus/optimization/Warehouse.scala index e73440f..d8da171 100644 --- a/core/src/test/scala/optimus/optimization/Warehouse.scala +++ b/core/src/test/scala/optimus/optimization/Warehouse.scala @@ -16,11 +16,12 @@ package optimus.optimization +import optimus.algebra._ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers import optimus.algebra.AlgebraOps._ import optimus.optimization.enums.{ SolutionStatus, SolverLib } -import optimus.optimization.model.{ MPBinaryVar, MPFloatVar, MPIntVar } +import optimus.optimization.model.{ MPBinaryVar, MPFloatVar } /** * Facility Location Problem diff --git a/core/src/test/scala/optimus/optimization/Workforce.scala b/core/src/test/scala/optimus/optimization/Workforce.scala index bb4782a..370cb21 100644 --- a/core/src/test/scala/optimus/optimization/Workforce.scala +++ b/core/src/test/scala/optimus/optimization/Workforce.scala @@ -16,6 +16,7 @@ package optimus.optimization +import optimus.algebra._ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers import optimus.algebra.AlgebraOps._ @@ -23,10 +24,10 @@ import optimus.optimization.enums.{ SolutionStatus, SolverLib } import optimus.optimization.model.MPBinaryVar /** - * Assign workers to shifts while satisfying requirements for that day. - * Each worker may or may not be available on a particular day and therefore - * the objective is to minimize the total payment costs. - */ + * Assign workers to shifts while satisfying requirements for that day. + * Each worker may or may not be available on a particular day and therefore + * the objective is to minimize the total payment costs. + */ trait Workforce extends AnyFunSpec with Matchers { def solver: SolverLib @@ -55,17 +56,14 @@ trait Workforce extends AnyFunSpec with Matchers { Array(1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) ) - val assigned = Array.tabulate(workers.size, shifts.size)( - (i, j) => MPBinaryVar(s"x($i,$j)") - ) + val assigned = Array.tabulate(workers.size, shifts.size)((i, j) => MPBinaryVar(s"x($i,$j)")) minimize(sum(workers, shifts)((w, s) => assigned(w)(s) * pay(w).toDouble)) for (s <- shifts.indices) add(sum(workers)(w => assigned(w)(s) * availability(w)(s).toDouble) := shiftRequirements(s).toDouble) - for (w <- workers) - add(sum(shifts)(s => assigned(w)(s)) <:= maxNbShift.toDouble) + for (w <- workers) add(sum(shifts)(s => assigned(w)(s)) <:= maxNbShift.toDouble) start() @@ -78,8 +76,7 @@ trait Workforce extends AnyFunSpec with Matchers { } it("all variables should have a value") { - for (w <- workers; s <- shifts.indices) - assigned(w)(s).value.isDefined shouldBe true + for (w <- workers; s <- shifts.indices) assigned(w)(s).value.isDefined shouldBe true } it(s"$solver constraints should be satisfied") { diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 96fcb25..efe1b94 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -30,7 +30,6 @@ object Dependencies { final val ojAlgorithms = "51.4.0" final val Trove = "3.1.0" final val ScalaXML = "2.1.0" - final val Enums = "1.7.0" } // Logging using slf4j and logback @@ -49,8 +48,7 @@ object Dependencies { // GNU Trove collections and other tools lazy val Tools: Seq[ModuleID] = Seq( "org.scala-lang.modules" %% "scala-xml" % v.ScalaXML, - "net.sf.trove4j" % "core" % v.Trove, - "com.beachape" %% "enumeratum" % v.Enums + "net.sf.trove4j" % "core" % v.Trove ) // LpSolve library for linear programming diff --git a/project/OptimusBuild.scala b/project/OptimusBuild.scala index 2db03e5..6acc54a 100644 --- a/project/OptimusBuild.scala +++ b/project/OptimusBuild.scala @@ -25,8 +25,7 @@ object OptimusBuild extends AutoPlugin { private val logger = ConsoleLogger() - final val logo = - """ + final val logo = """ | /\\\\\ | /\\\///\\\ |/\\\/ \///\\\ /\\\\\\\\\ /\\\ /\\\ @@ -58,46 +57,31 @@ object OptimusBuild extends AutoPlugin { } private val commonSettings: Seq[Setting[_]] = Seq( - organization := "com.github.vagmcs", - description := "Optimus is a mathematical programming library for Scala", - headerLicense := Some(HeaderLicense.Custom(logo)), - headerMappings := headerMappings.value + (HeaderFileType.scala -> HeaderCommentStyle.cStyleBlockComment), - - scalaVersion := "2.13.8", - - crossScalaVersions := Seq("2.13.8", "2.12.16"), - + scalaVersion := "3.1.3", + crossScalaVersions := Seq("3.1.3", "2.13.8", "2.12.16"), autoScalaLibrary := true, - managedScalaInstance := true, - coverageEnabled := false, coverageHighlighting := true, coverageMinimumStmtTotal := 75, - publishMavenStyle := true, Test / publishArtifact := false, pomIncludeRepository := { _ => false }, - resolvers ++= Seq( Resolver.mavenLocal, Resolver.typesafeRepo("releases"), Resolver.sonatypeRepo("releases"), Resolver.sonatypeRepo("snapshots") ), - publishTo := { val nexus = "https://oss.sonatype.org/" - if (isSnapshot.value) - Some("snapshots" at nexus + "content/repositories/snapshots") - else - Some("releases" at nexus + "service/local/staging/deploy/maven2") + if (isSnapshot.value) Some("snapshots" at nexus + "content/repositories/snapshots") + else Some("releases" at nexus + "service/local/staging/deploy/maven2") }, - scmInfo := Some( ScmInfo(url("https://github.com/vagmcs/Optimus"), "scm:git:git@github.com:vagmcs/Optimus.git") ), @@ -126,7 +110,6 @@ object OptimusBuild extends AutoPlugin { ) private lazy val JavaSettings: Seq[Setting[_]] = Seq( - // Java compiler options // source is the source code version required to compile. // target is the oldest JRE version Optimus supports. @@ -136,7 +119,8 @@ object OptimusBuild extends AutoPlugin { javaOptions ++= Seq( "-XX:+DoEscapeAnalysis", "-XX:+OptimizeStringConcat", - "-Dlogback.configurationFile=src/main/resources/logback.xml") + "-Dlogback.configurationFile=src/main/resources/logback.xml" + ) ) private lazy val ScalaSettings: Seq[Setting[_]] = Seq( @@ -146,15 +130,21 @@ object OptimusBuild extends AutoPlugin { case "2.12" | "2.13" => // Scala compiler settings for Scala 2.12.x and 2.13.x Seq( - "-deprecation", // Emit warning and location for usages of deprecated APIs. - "-unchecked", // Enable additional warnings where generated code depends on assumptions. - "-feature", // Emit warning and location for usages of features that should be imported explicitly. - "-target:jvm-1.8", // Target JVM version 1.8. - "-Ywarn-dead-code" // Warn when dead code is identified. + "-deprecation", // Emit warning and location for usages of deprecated APIs. + "-unchecked", // Enable additional warnings where generated code depends on assumptions. + "-feature", // Emit warning and location for usages of features that should be imported explicitly. + "-target:jvm-1.8", // Target JVM version 1.8. + "-Ywarn-dead-code" // Warn when dead code is identified. + ) + case "3" => + // Scala compiler settings for Scala 3.x + Seq( + "-deprecation", // Emit warning and location for usages of deprecated APIs. + "-unchecked", // Enable additional warnings where generated code depends on assumptions. + "-feature" // Emit warning and location for usages of features that should be imported explicitly. ) - case _ => sys.error(s"Unsupported version of Scala '${scalaBinaryVersion.value}'") } } ) -} \ No newline at end of file +} diff --git a/solver-gurobi/src/test/scala/optimus/optimization/GurobiSpecTest.scala b/solver-gurobi/src/test/scala/optimus/optimization/GurobiSpecTest.scala index 9f65f73..74d2128 100644 --- a/solver-gurobi/src/test/scala/optimus/optimization/GurobiSpecTest.scala +++ b/solver-gurobi/src/test/scala/optimus/optimization/GurobiSpecTest.scala @@ -16,14 +16,13 @@ package optimus.optimization +import optimus.algebra._ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers import optimus.optimization.enums.{ PreSolve, SolutionStatus, SolverLib } import optimus.optimization.model.{ MPBinaryVar, MPConstraint, MPFloatVar, MPIntVar } -/** - * Specification for Gurobi solver. - */ +/** Specification for Gurobi solver. */ final class GurobiSpecTest extends AnyFunSpec with Matchers { // Constant objective function tests @@ -112,7 +111,7 @@ final class GurobiSpecTest extends AnyFunSpec with Matchers { val y = MPFloatVar("y", 80, 170) maximize(-2 * x + 5 * y) - subjectTo ( + subjectTo( y >:= -x + 200 ) @@ -149,7 +148,7 @@ final class GurobiSpecTest extends AnyFunSpec with Matchers { val y = MPFloatVar("y", 80, 170) minimize(-2 * x + 5 * y) - subjectTo ( + subjectTo( y >:= -x + 200 ) @@ -186,7 +185,7 @@ final class GurobiSpecTest extends AnyFunSpec with Matchers { val y = MPFloatVar("y", 80, 170) minimize(-2 * x + 5 * y) - subjectTo ( + subjectTo( y >:= -x + 200 ) @@ -223,7 +222,7 @@ final class GurobiSpecTest extends AnyFunSpec with Matchers { val z = MPFloatVar("z", 80, 170) - subjectTo ( + subjectTo( z >:= 170, y >:= -x + 200 ) @@ -265,7 +264,7 @@ final class GurobiSpecTest extends AnyFunSpec with Matchers { val y = MPFloatVar("y", 0, 10) maximize(x + y) - subjectTo ( + subjectTo( x + y >:= 5 ) @@ -421,7 +420,9 @@ final class GurobiSpecTest extends AnyFunSpec with Matchers { var cons: Vector[MPConstraint] = Vector() - maximize(3 * w - 8 * w + 10 * w + 0.001 * x - (-0.999 * x) - 0.3 * 10 * (-y) - 4 * 0.0006 * 0 * (w - x - z) + 2 * z - 2 * z + 4 * z) + maximize( + 3 * w - 8 * w + 10 * w + 0.001 * x - (-0.999 * x) - 0.3 * 10 * (-y) - 4 * 0.0006 * 0 * (w - x - z) + 2 * z - 2 * z + 4 * z + ) cons = cons :+ add(w + x + y + z <:= 40) cons = cons :+ add(2 * w + x - y - z >:= 10) @@ -503,7 +504,7 @@ final class GurobiSpecTest extends AnyFunSpec with Matchers { val x = MPFloatVar("x", 0, 10) maximize(x + 1) - subjectTo ( + subjectTo( x <:= 1 ) @@ -618,7 +619,7 @@ final class GurobiSpecTest extends AnyFunSpec with Matchers { add(-5 * x(0) - 3 * x(1) + x(2) + 3 * x(3) - 2 * x(4) + x(5) >:= -2) add(5 * x(0) - x(1) + 4 * x(2) - 2 * x(3) + 2 * x(4) - x(5) >:= 3) - it ("all variables should be binary") { + it("all variables should be binary") { x.foreach(_.isBinary shouldBe true) } @@ -739,7 +740,9 @@ final class GurobiSpecTest extends AnyFunSpec with Matchers { val x = MPFloatVar.positive("x") val y = MPFloatVar.positive("y") - minimize(x * x + x * x + y * y - y * y + y * y + 7 * x * y - 6 * y * x + x * x - x * x + x - 99.9e-9 * y + 1.0000000999 * y) + minimize( + x * x + x * x + y * y - y * y + y * y + 7 * x * y - 6 * y * x + x * x - x * x + x - 99.9e-9 * y + 1.0000000999 * y + ) cons = cons :+ add(x + y := 1) cons = cons :+ add(x >:= -3) @@ -786,7 +789,7 @@ final class GurobiSpecTest extends AnyFunSpec with Matchers { val y = MPFloatVar.positive("y") minimize(-8 * x - 16 * y + x * x + 4 * y * y) - subjectTo ( + subjectTo( x + y <:= 5, x <:= 3, x >:= 0, diff --git a/solver-lp/src/test/scala/optimus/optimization/LPSolveProblemTests.scala b/solver-lp/src/test/scala/optimus/optimization/LPSolveProblemTests.scala index 344bf7c..a45c100 100644 --- a/solver-lp/src/test/scala/optimus/optimization/LPSolveProblemTests.scala +++ b/solver-lp/src/test/scala/optimus/optimization/LPSolveProblemTests.scala @@ -11,7 +11,7 @@ * \///// \/// \///// \/// \/// \/// \/// \///////// \////////// * * The mathematical programming library for Scala. - * + * */ package optimus.optimization diff --git a/solver-lp/src/test/scala/optimus/optimization/LPSolveSpecTest.scala b/solver-lp/src/test/scala/optimus/optimization/LPSolveSpecTest.scala index a84d7f7..8c4f5a0 100644 --- a/solver-lp/src/test/scala/optimus/optimization/LPSolveSpecTest.scala +++ b/solver-lp/src/test/scala/optimus/optimization/LPSolveSpecTest.scala @@ -11,19 +11,18 @@ * \///// \/// \///// \/// \/// \/// \/// \///////// \////////// * * The mathematical programming library for Scala. - * + * */ package optimus.optimization +import optimus.algebra._ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers import optimus.optimization.enums.{ SolutionStatus, SolverLib } import optimus.optimization.model.{ MPBinaryVar, MPConstraint, MPFloatVar, MPIntVar } -/** - * Specification for LPSolve solver. - */ +/** Specification for LPSolve solver. */ final class LPSolveSpecTest extends AnyFunSpec with Matchers { describe("Constant Program (1)") { @@ -108,7 +107,7 @@ final class LPSolveSpecTest extends AnyFunSpec with Matchers { val y = MPFloatVar("y", 80, 170) maximize(-2 * x + 5 * y) - subjectTo ( + subjectTo( y >:= -x + 200 ) @@ -145,7 +144,7 @@ final class LPSolveSpecTest extends AnyFunSpec with Matchers { val y = MPFloatVar("y", 80, 170) minimize(-2 * x + 5 * y) - subjectTo ( + subjectTo( y >:= -x + 200 ) @@ -182,7 +181,7 @@ final class LPSolveSpecTest extends AnyFunSpec with Matchers { val y = MPFloatVar("y", 80, 170) minimize(-2 * x + 5 * y) - subjectTo ( + subjectTo( y >:= -x + 200 ) @@ -219,7 +218,7 @@ final class LPSolveSpecTest extends AnyFunSpec with Matchers { val z = MPFloatVar("z", 80, 170) - subjectTo ( + subjectTo( z >:= 170, y >:= -x + 200 ) @@ -261,7 +260,7 @@ final class LPSolveSpecTest extends AnyFunSpec with Matchers { val y = MPFloatVar("y", 0, 10) maximize(x + y) - subjectTo ( + subjectTo( x + y >:= 5 ) @@ -417,7 +416,9 @@ final class LPSolveSpecTest extends AnyFunSpec with Matchers { var cons: Vector[MPConstraint] = Vector() - maximize(3 * w - 8 * w + 10 * w + 0.001 * x - (-0.999 * x) - 0.3 * 10 * (-y) - 4 * 0.0006 * 0 * (w - x - z) + 2 * z - 2 * z + 4 * z) + maximize( + 3 * w - 8 * w + 10 * w + 0.001 * x - (-0.999 * x) - 0.3 * 10 * (-y) - 4 * 0.0006 * 0 * (w - x - z) + 2 * z - 2 * z + 4 * z + ) cons = cons :+ add(w + x + y + z <:= 40) cons = cons :+ add(2 * w + x - y - z >:= 10) @@ -586,7 +587,7 @@ final class LPSolveSpecTest extends AnyFunSpec with Matchers { add(-5 * x(0) - 3 * x(1) + x(2) + 3 * x(3) - 2 * x(4) + x(5) >:= -2) add(5 * x(0) - x(1) + 4 * x(2) - 2 * x(3) + 2 * x(4) - x(5) >:= 3) - it ("all variables should be binary") { + it("all variables should be binary") { x.foreach(_.isBinary shouldBe true) } diff --git a/solver-mosek/src/main/scala/optimus/optimization/Mosek.scala b/solver-mosek/src/main/scala/optimus/optimization/Mosek.scala index fd0a1bf..d630c26 100644 --- a/solver-mosek/src/main/scala/optimus/optimization/Mosek.scala +++ b/solver-mosek/src/main/scala/optimus/optimization/Mosek.scala @@ -185,16 +185,15 @@ final class Mosek extends MPSolver { if (indexes.length == 1) { linearIndexes :+= indexes.head linearValues :+= iterator.value - } else - if (indexes.head == indexes(1)) { - rowIndexes :+= indexes.head - colsIndexes :+= indexes.head - quadraticValues :+= 2 * iterator.value - } else { - rowIndexes :+= Math.max(indexes.head, indexes(1)) - colsIndexes :+= Math.min(indexes.head, indexes(1)) - quadraticValues :+= iterator.value - } + } else if (indexes.head == indexes(1)) { + rowIndexes :+= indexes.head + colsIndexes :+= indexes.head + quadraticValues :+= 2 * iterator.value + } else { + rowIndexes :+= Math.max(indexes.head, indexes(1)) + colsIndexes :+= Math.min(indexes.head, indexes(1)) + quadraticValues :+= iterator.value + } } underlyingSolver.putarow(numberOfCons, linearIndexes, linearValues) underlyingSolver.putqconk(numberOfCons, rowIndexes, colsIndexes, quadraticValues) diff --git a/solver-mosek/src/test/scala/optimus/optimization/MosekSpecTest.scala b/solver-mosek/src/test/scala/optimus/optimization/MosekSpecTest.scala index 7d50902..b2133ee 100644 --- a/solver-mosek/src/test/scala/optimus/optimization/MosekSpecTest.scala +++ b/solver-mosek/src/test/scala/optimus/optimization/MosekSpecTest.scala @@ -16,6 +16,7 @@ package optimus.optimization +import optimus.algebra._ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers import optimus.optimization.enums.{ PreSolve, SolutionStatus, SolverLib } @@ -109,7 +110,7 @@ final class MosekSpecTest extends AnyFunSpec with Matchers { val y = MPFloatVar("y", 80, 170) maximize(-2 * x + 5 * y) - subjectTo ( + subjectTo( y >:= -x + 200 ) @@ -151,7 +152,7 @@ final class MosekSpecTest extends AnyFunSpec with Matchers { val y = MPFloatVar("y", 80, 170) minimize(-2 * x + 5 * y) - subjectTo ( + subjectTo( y >:= -x + 200 ) @@ -193,7 +194,7 @@ final class MosekSpecTest extends AnyFunSpec with Matchers { val y = MPFloatVar("y", 80, 170) minimize(-2 * x + 5 * y) - subjectTo ( + subjectTo( y >:= -x + 200 ) @@ -230,7 +231,7 @@ final class MosekSpecTest extends AnyFunSpec with Matchers { val z = MPFloatVar("z", 80, 170) - subjectTo ( + subjectTo( z >:= 170, y >:= -x + 200 ) @@ -277,7 +278,7 @@ final class MosekSpecTest extends AnyFunSpec with Matchers { val y = MPFloatVar("y", 0, 10) maximize(x + y) - subjectTo ( + subjectTo( x + y >:= 5 ) @@ -433,7 +434,9 @@ final class MosekSpecTest extends AnyFunSpec with Matchers { var cons: Vector[MPConstraint] = Vector() - maximize(3 * w - 8 * w + 10 * w + 0.001 * x - (-0.999 * x) - 0.3 * 10 * (-y) - 4 * 0.0006 * 0 * (w - x - z) + 2 * z - 2 * z + 4 * z) + maximize( + 3 * w - 8 * w + 10 * w + 0.001 * x - (-0.999 * x) - 0.3 * 10 * (-y) - 4 * 0.0006 * 0 * (w - x - z) + 2 * z - 2 * z + 4 * z + ) cons = cons :+ add(w + x + y + z <:= 40) cons = cons :+ add(2 * w + x - y - z >:= 10) @@ -515,7 +518,7 @@ final class MosekSpecTest extends AnyFunSpec with Matchers { val x = MPFloatVar("x", 0, 10) maximize(x + 1) - subjectTo ( + subjectTo( x <:= 1 ) @@ -630,7 +633,7 @@ final class MosekSpecTest extends AnyFunSpec with Matchers { add(-5 * x(0) - 3 * x(1) + x(2) + 3 * x(3) - 2 * x(4) + x(5) >:= -2) add(5 * x(0) - x(1) + 4 * x(2) - 2 * x(3) + 2 * x(4) - x(5) >:= 3) - it ("all variables should be binary") { + it("all variables should be binary") { x.foreach(_.isBinary shouldBe true) } @@ -751,7 +754,9 @@ final class MosekSpecTest extends AnyFunSpec with Matchers { val x = MPFloatVar.positive("x") val y = MPFloatVar.positive("y") - minimize(x * x + x * x + y * y - y * y + y * y + 7 * x * y - 6 * y * x + x * x - x * x + x - 99.9e-9 * y + 1.0000000999 * y) + minimize( + x * x + x * x + y * y - y * y + y * y + 7 * x * y - 6 * y * x + x * x - x * x + x - 99.9e-9 * y + 1.0000000999 * y + ) cons = cons :+ add(x + y := 1) cons = cons :+ add(x >:= -3) @@ -798,7 +803,7 @@ final class MosekSpecTest extends AnyFunSpec with Matchers { val y = MPFloatVar.positive("y") minimize(-8 * x - 16 * y + x * x + 4 * y * y) - subjectTo ( + subjectTo( x + y <:= 5, x <:= 3, x >:= 0, diff --git a/solver-oj/src/test/scala/optimus/optimization/OJAlgorithmsSpecTest.scala b/solver-oj/src/test/scala/optimus/optimization/OJAlgorithmsSpecTest.scala index 5017673..b29a262 100644 --- a/solver-oj/src/test/scala/optimus/optimization/OJAlgorithmsSpecTest.scala +++ b/solver-oj/src/test/scala/optimus/optimization/OJAlgorithmsSpecTest.scala @@ -16,6 +16,7 @@ package optimus.optimization +import optimus.algebra._ import org.scalatest.funspec.AnyFunSpec import org.scalatest.matchers.should.Matchers import optimus.optimization.enums.{ PreSolve, SolutionStatus, SolverLib }