From 724febba45d4d884b9e564693b5ca0442f02dc18 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Tue, 26 Dec 2023 00:44:01 -0500 Subject: [PATCH 01/25] Upgraded coverage1.yaml to track this new branch --- .github/workflows/coverage1.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage1.yaml b/.github/workflows/coverage1.yaml index 0be18ba..0daccbf 100644 --- a/.github/workflows/coverage1.yaml +++ b/.github/workflows/coverage1.yaml @@ -2,7 +2,7 @@ name: Go # The name of the workflow that will appear on Github on: push: - branches: [ main , kr-feature-multiply1 ] + branches: [ main , kr-feature-problem1 ] pull_request: branches: [ main ] # Allows you to run this workflow manually from the Actions tab From 33ff6b2f2cb258043e9626c4de42f24a113b80d1 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Tue, 26 Dec 2023 00:44:44 -0500 Subject: [PATCH 02/25] Attempted To Delete Symbolic Library (Will Later Be Replaced By a New Symbolic Math module) --- symbolic/expression.go | 93 ---------------------- symbolic/matrix/errors.go | 14 ---- symbolic/matrix/utils.go | 40 ---------- symbolic/matrix_constant.go | 70 ---------------- symbolic/matrix_expression.go | 62 --------------- testing/symbolic/matrix/constant_test.go | 52 ------------ testing/symbolic/matrix/expression_test.go | 39 --------- testing/symbolic/matrix/utils_test.go | 34 -------- 8 files changed, 404 deletions(-) delete mode 100644 symbolic/expression.go delete mode 100644 symbolic/matrix/errors.go delete mode 100644 symbolic/matrix/utils.go delete mode 100644 symbolic/matrix_constant.go delete mode 100644 symbolic/matrix_expression.go delete mode 100644 testing/symbolic/matrix/constant_test.go delete mode 100644 testing/symbolic/matrix/expression_test.go delete mode 100644 testing/symbolic/matrix/utils_test.go diff --git a/symbolic/expression.go b/symbolic/expression.go deleted file mode 100644 index bf11a35..0000000 --- a/symbolic/expression.go +++ /dev/null @@ -1,93 +0,0 @@ -package symbolic - -/* -matrix_expression.go -Description: - This file holds all of the functions and methods related to the Expression - interface. -*/ - -/* -Expression -Description: - - This interface should be implemented by and ScalarExpression and VectorExpression -*/ -type Expression interface { - // NumVars returns the number of variables in the expression - NumVars() int - - // Vars returns a slice of the Var ids in the expression - IDs() []uint64 - - // Dims returns a slice describing the true dimensions of a given expression (scalar, vector, or matrix) - Dims() []int - - // Plus adds the current expression to another and returns the resulting - // expression - Plus(e interface{}, errors ...error) (Expression, error) - - // Multiply multiplies the current expression to another and returns the - // resulting expression - Multiply(c interface{}, errors ...error) (Expression, error) - // - //// LessEq returns a less than or equal to (<=) constraint between the - //// current expression and another - //LessEq(e Expression) Constraint - // - //// GreaterEq returns a greater than or equal to (>=) constraint between the - //// current expression and another - //GreaterEq(e Expression) Constraint - // - //// Eq returns an equality (==) constraint between the current expression - //// and another - //Eq(e ScalarExpression) *ScalarConstraint -} - -/* -IsExpression -Description: - - Tests whether or not the input variable is one of the expression types. -*/ -//func IsExpression(e interface{}) bool { -// return IsScalarExpression(e) || IsVectorExpression(e) -//} -// -//func ToExpression(e interface{}) (Expression, error) { -// switch { -// case IsScalarExpression(e): -// return ToScalarExpression(e) -// case IsVectorExpression(e): -// return ToVectorExpression(e) -// default: -// return K(INFINITY), fmt.Errorf("the input expression is not recognized as a scalar or vector expression.") -// } -//} -// -//func CheckDimensionsInMultiplication(left, right Expression) error { -// // Check that the # of columns in left -// // matches the # of rows in right -// if left.Dims()[1] != right.Dims()[0] { -// return DimensionError{ -// Operation: "Multiply", -// Arg1: left, -// Arg2: right, -// } -// } -// // If dimensions match, then return nothing. -// return nil -//} -// -//func CheckDimensionsInAddition(left, right Expression) error { -// // Check that the size of columns in left and right agree -// if (left.Dims()[0] != right.Dims()[0]) || (left.Dims()[1] != right.Dims()[1]) { -// return DimensionError{ -// Operation: "Plus", -// Arg1: left, -// Arg2: right, -// } -// } -// // If dimensions match, then return nothing. -// return nil -//} diff --git a/symbolic/matrix/errors.go b/symbolic/matrix/errors.go deleted file mode 100644 index f1b3407..0000000 --- a/symbolic/matrix/errors.go +++ /dev/null @@ -1,14 +0,0 @@ -package matrix - -import "fmt" - -type TypeError struct { - Object interface{} -} - -func (te TypeError) Error() string { - return fmt.Sprintf( - "objects of type %T are not currently recognized as matrices. if this is unexpected, then feel free to file an issue!", - te.Object, - ) -} diff --git a/symbolic/matrix/utils.go b/symbolic/matrix/utils.go deleted file mode 100644 index 9964ebf..0000000 --- a/symbolic/matrix/utils.go +++ /dev/null @@ -1,40 +0,0 @@ -package matrix - -import "gonum.org/v1/gonum/mat" - -/* -Zeros -Description: - - Returns a dense matrix of all zeros. -*/ -func Zeros(nR, nC int) mat.Dense { - // Create empty slice - elts := make([]float64, nR*nC) - for rowIndex := 0; rowIndex < nR; rowIndex++ { - for colIndex := 0; colIndex < nC; colIndex++ { - elts[rowIndex*nC+colIndex] = 0.0 - } - } - - return *mat.NewDense(nR, nC, elts) -} - -/* -Identity -Description: - - Returns a symmetric matrix that is the identity matrix. - Note: this function assumes lengthIn is a positive number. -*/ -func Identity(dim int) mat.Dense { - // Create the empty matrix. - zeroBase := Zeros(dim, dim) - - // Populate Diagonal - for rowIndex := 0; rowIndex < dim; rowIndex++ { - zeroBase.Set(rowIndex, rowIndex, 1.0) - } - - return zeroBase -} diff --git a/symbolic/matrix_constant.go b/symbolic/matrix_constant.go deleted file mode 100644 index a929e95..0000000 --- a/symbolic/matrix_constant.go +++ /dev/null @@ -1,70 +0,0 @@ -package symbolic - -import ( - "fmt" - "github.com/MatProGo-dev/MatProInterface.go/symbolic/matrix" - "gonum.org/v1/gonum/mat" -) - -/* -MatrixConstant -Description: - - Represents a constant matrix. -*/ -type MatrixConstant mat.Dense - -func (mc MatrixConstant) NumVars() int { - return 0 -} - -/* -IDs -Description: - - Returns the IDs of any variables in the expression. -*/ -func (mc MatrixConstant) IDs() []uint64 { - return []uint64{} -} - -func (mc MatrixConstant) Dims() []int { - dense := mat.Dense(mc) - nR, nC := dense.Dims() - return []int{nR, nC} -} - -/* -Multiply -Description: -*/ -func (mc MatrixConstant) Multiply(rightIn interface{}, errors ...error) (Expression, error) { - return mc, fmt.Errorf("not implemented yet...") -} - -/* -Plus -Description: - - Sums this matrix with something else. -*/ -func (mc MatrixConstant) Plus(rightIn interface{}, errors ...error) (Expression, error) { - return mc, fmt.Errorf("not implemented yet...") -} - -func (mc MatrixConstant) Transpose() Expression { - dims := mc.Dims() - transposed := matrix.Zeros(dims[1], dims[0]) - - mcAsD := mat.Dense(mc) - - for rowIndex := 0; rowIndex < dims[1]; rowIndex++ { - for colIndex := 0; colIndex < dims[0]; colIndex++ { - transposed.Set( - rowIndex, colIndex, - (&mcAsD).At(colIndex, rowIndex), - ) - } - } - return MatrixConstant(transposed) -} diff --git a/symbolic/matrix_expression.go b/symbolic/matrix_expression.go deleted file mode 100644 index cab22cc..0000000 --- a/symbolic/matrix_expression.go +++ /dev/null @@ -1,62 +0,0 @@ -package symbolic - -import ( - "github.com/MatProGo-dev/MatProInterface.go/symbolic/matrix" - "gonum.org/v1/gonum/mat" -) - -//================ -// Type Definition -//================ - -type MatrixExpression interface { - Dims() []int // Computes the dimensions of the input matrix - - // IDs - // Returns the ids of any variables in the matrix expression - IDs() []uint64 - - NumVars() int // Returns the number of variables in the matrix expression -} - -/* -IsMatrixExpression -Description: - - Returns true if and only if the input object is a matrix expression. -*/ -func IsMatrixExpression(e interface{}) bool { - // Check each type - switch e.(type) { - case mat.Dense: - return true - case MatrixConstant: - return true - default: - return false - } -} - -/* -ToMatrixExpression -Description: - - Converts the input object into a valid type that implements the - MatrixExpression interface. -*/ -func ToMatrixExpression(e interface{}) (Expression, error) { - // Input Processing - if !IsMatrixExpression(e) { - return MatrixConstant(matrix.Zeros(1, 1)), matrix.TypeError{e} - } - - // Convert - switch candidate := e.(type) { - case mat.Dense: - return MatrixConstant(candidate), nil - case MatrixConstant: - return candidate, nil - default: - return MatrixConstant(matrix.Zeros(1, 1)), matrix.TypeError{e} - } -} diff --git a/testing/symbolic/matrix/constant_test.go b/testing/symbolic/matrix/constant_test.go deleted file mode 100644 index 5a81ddb..0000000 --- a/testing/symbolic/matrix/constant_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package matrix_test - -import ( - "github.com/MatProGo-dev/MatProInterface.go/symbolic" - "github.com/MatProGo-dev/MatProInterface.go/symbolic/matrix" - "testing" -) - -/* -constant_test.go -Description: - -*/ - -/* -TestConstant_NumVars1 -Description: - - Tests that NumVars() returns 0 for a matrix.Constant -*/ -func TestConstant_NumVars1(t *testing.T) { - // Constants - mat1 := symbolic.MatrixConstant(matrix.Zeros(2, 3)) - - // Algorithm - if mat1.NumVars() != 0 { - t.Errorf( - "mat1.NumVars() = %v =/= 0", - mat1.NumVars(), - ) - } -} - -/* -TestConstant_IDs1 -Description: - - Tests that IDs() returns an empty slice of []uint64{} -*/ -func TestConstant_IDs1(t *testing.T) { - // Constants - mat1 := symbolic.MatrixConstant(matrix.Zeros(2, 3)) - - // Algorithm - ids1 := mat1.IDs() - if len(ids1) != 0 { - t.Errorf( - "len(ids1) = %v =/= 0", - len(ids1), - ) - } -} diff --git a/testing/symbolic/matrix/expression_test.go b/testing/symbolic/matrix/expression_test.go deleted file mode 100644 index 7ffdf61..0000000 --- a/testing/symbolic/matrix/expression_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package matrix_test - -import ( - "github.com/MatProGo-dev/MatProInterface.go/symbolic" - "github.com/MatProGo-dev/MatProInterface.go/symbolic/matrix" - "strings" - "testing" -) - -/* -expression_test.go -Description: - Tests the methods used for handling matrix expressions. -*/ - -/* -TestExpression_ToMatrixExpression1 -Description: - - Tests the matrix expression conversion method when a non-matrix - is provided. -*/ -func TestExpression_ToMatrixExpression1(t *testing.T) { - // Constants - b1 := false - - // Algorithm - _, err := symbolic.ToMatrixExpression(b1) - if err == nil { - t.Errorf("no error was thrown, but there should have been!") - } else { - if !strings.Contains( - err.Error(), - matrix.TypeError{b1}.Error(), - ) { - t.Errorf("unexpected error: %v", err) - } - } -} diff --git a/testing/symbolic/matrix/utils_test.go b/testing/symbolic/matrix/utils_test.go deleted file mode 100644 index 1d48436..0000000 --- a/testing/symbolic/matrix/utils_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package matrix_test - -import ( - "github.com/MatProGo-dev/MatProInterface.go/symbolic" - "github.com/MatProGo-dev/MatProInterface.go/symbolic/matrix" - "testing" -) - -/* -TestUtils_Zeros1 -Description: - Tests that the zeros util function works. -*/ - -func TestUtils_Zeros1(t *testing.T) { - // Constant - mat1 := matrix.Zeros(2, 3) - mat2 := symbolic.MatrixConstant(mat1) - - // Algorithm - dims := mat2.Dims() - for rowIndex := 0; rowIndex < dims[0]; rowIndex++ { - for colIndex := 0; colIndex < dims[1]; colIndex++ { - if mat1.At(rowIndex, colIndex) != 0.0 { - t.Errorf( - "mat1[%v,%v] = %v =/= 0.0", - rowIndex, colIndex, - mat1.At(rowIndex, colIndex), - ) - } - } - } - -} From 91ad5dea5c93d4c37dd26daec143af9e49bc8cec Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Wed, 10 Jan 2024 10:11:54 -0500 Subject: [PATCH 03/25] Introduced Symbolic Matrix From SymbolicMath.go --- go.mod | 9 ++++----- go.sum | 11 ++++++----- testing/optim/var_vector_transpose_test.go | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index ee9cb18..ba8fcfb 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,7 @@ module github.com/MatProGo-dev/MatProInterface.go -go 1.19 +go 1.21 -require ( - github.com/hashicorp/go-version v1.6.0 - gonum.org/v1/gonum v0.12.0 -) +require gonum.org/v1/gonum v0.14.0 + +require github.com/MatProGo-dev/SymbolicMath.go v0.0.0-20240104201035-f9a42f642121 diff --git a/go.sum b/go.sum index b151481..0a98da3 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3 h1:n9HxLrNxWWtEb1cA950nuEEj3QnKbtsCJ6KjcgisNUs= -gonum.org/v1/gonum v0.12.0 h1:xKuo6hzt+gMav00meVPUlXwSdoEJP46BR+wdxQEFK2o= -gonum.org/v1/gonum v0.12.0/go.mod h1:73TDxJfAAHeA8Mk9mf8NlIppyhQNo5GLTcYeqgo2lvY= +github.com/MatProGo-dev/SymbolicMath.go v0.0.0-20240104201035-f9a42f642121 h1:nnJVXcnTvAdkfR4kZRBw4Ot+KoL8S3PijlLTmKOooco= +github.com/MatProGo-dev/SymbolicMath.go v0.0.0-20240104201035-f9a42f642121/go.mod h1:gKbGR/6sYWi2koMUEDIPWBPi6jQPELKle0ijIM+eaHU= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= +gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= diff --git a/testing/optim/var_vector_transpose_test.go b/testing/optim/var_vector_transpose_test.go index 6822308..157058b 100644 --- a/testing/optim/var_vector_transpose_test.go +++ b/testing/optim/var_vector_transpose_test.go @@ -3,7 +3,7 @@ package optim_test import ( "fmt" "github.com/MatProGo-dev/MatProInterface.go/optim" - "github.com/MatProGo-dev/MatProInterface.go/symbolic/matrix" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "gonum.org/v1/gonum/mat" "strings" "testing" @@ -1694,7 +1694,7 @@ func TestVarVectorTranspose_Multiply12(t *testing.T) { vv0 := m.AddVariableVector(N) vvt0 := vv0.Transpose() - mat1 := matrix.Identity(N) + mat1 := symbolic.Identity(N) mat1.Set(1, 1, 3.0) // Attempt Multiplication @@ -1844,7 +1844,7 @@ func TestVarVectorTranspose_Multiply14(t *testing.T) { vv0 := m.AddVariableVector(N) vvt0 := vv0.Transpose() - mat1 := matrix.Zeros(N, N) + mat1 := symbolic.ZerosMatrix(N, N) mat1.Set(1, 1, 4.0) vle2 := optim.VectorLinearExpr{ L: mat1, From 70336e87e12ca2332af9bf7456ef818987a46dc2 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Wed, 10 Jan 2024 10:17:28 -0500 Subject: [PATCH 04/25] Added OptimizationProblem Object and A Single Test --- problem/objective.go | 26 ++++ problem/optimization_problem.go | 203 ++++++++++++++++++++++++++++++ testing/problem/objective_test.go | 32 +++++ 3 files changed, 261 insertions(+) create mode 100644 problem/objective.go create mode 100644 problem/optimization_problem.go create mode 100644 testing/problem/objective_test.go diff --git a/problem/objective.go b/problem/objective.go new file mode 100644 index 0000000..470ef42 --- /dev/null +++ b/problem/objective.go @@ -0,0 +1,26 @@ +package problem + +import "github.com/MatProGo-dev/SymbolicMath.go/symbolic" + +// Objective represents an optimization objective given an expression and +// objective sense (maximize or minimize). +type Objective struct { + symbolic.Expression + Sense ObjSense +} + +// NewObjective returns a new optimization objective given an expression and +// objective sense +func NewObjective(e symbolic.Expression, sense ObjSense) *Objective { + return &Objective{e, sense} +} + +// ObjSense represents whether an optimization objective is to be maximized or +// minimized. This implementation conforms to the Gurobi encoding +type ObjSense int + +// Objective senses (minimize and maximize) encoding using Gurobi's standard +const ( + SenseMinimize ObjSense = 1 + SenseMaximize = -1 +) diff --git a/problem/optimization_problem.go b/problem/optimization_problem.go new file mode 100644 index 0000000..b650d0e --- /dev/null +++ b/problem/optimization_problem.go @@ -0,0 +1,203 @@ +package problem + +import ( + "fmt" + "github.com/MatProGo-dev/MatProInterface.go/optim" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" +) + +// OptimizationProblem represents the overall constrained linear optimization model to be +// solved. OptimizationProblem contains all the variables associated with the optimization +// problem, constraints, objective, and parameters. New variables can only be +// created using an instantiated OptimizationProblem. +type OptimizationProblem struct { + Name string + Variables []symbolic.Variable + Constraints []symbolic.Constraint + Objective symbolic.Expression +} + +// NewProblem returns a new model with some default arguments such as not to show +// the log and no time limit. +func NewProblem(name string) *OptimizationProblem { + return &OptimizationProblem{Name: name} +} + +/* +AddVariable +Description: + + This method adds an "unbounded" continuous variable to the model. +*/ +func (m *OptimizationProblem) AddVariable() symbolic.Variable { + return m.AddRealVariable() +} + +/* +AddRealVariable +Description: + + Adds a Real variable to the model and returns said variable. +*/ +func (m *OptimizationProblem) AddRealVariable() symbolic.Variable { + return m.AddVariableClassic(-optim.INFINITY, optim.INFINITY, symbolic.Continuous) +} + +// AddVariable adds a variable of a given variable type to the model given the lower +// and upper value limits. This variable is returned. +func (m *OptimizationProblem) AddVariableClassic(lower, upper float64, vtype symbolic.VarType) symbolic.Variable { + id := uint64(len(m.Variables)) + newVar := symbolic.Variable{id, lower, upper, vtype} + m.Variables = append(m.Variables, newVar) + return newVar +} + +// AddBinaryVar adds a binary variable to the model and returns said variable. +func (m *OptimizationProblem) AddBinaryVariable() symbolic.Variable { + return m.AddVariableClassic(0, 1, symbolic.Binary) +} + +/* +AddVariableVector +Description: + + Creates a VarVector object using a constructor that assumes you want an "unbounded" vector of real optimization + variables. +*/ +func (m *OptimizationProblem) AddVariableVector(dim int) symbolic.VariableVector { + // Constants + + // Algorithm + varSlice := make([]symbolic.Variable, dim) + for eltIndex := 0; eltIndex < dim; eltIndex++ { + varSlice[eltIndex] = m.AddVariable() + } + return symbolic.VariableVector{Elements: varSlice} +} + +/* +AddVariableVectorClassic +Description: + + The classic version of AddVariableVector defined in the original goop. +*/ +func (m *OptimizationProblem) AddVariableVectorClassic( + num int, lower, upper float64, vtype symbolic.VarType, +) symbolic.VariableVector { + stID := uint64(len(m.Variables)) + vs := make([]symbolic.Variable, num) + for i := range vs { + vs[i] = symbolic.Variable{stID + uint64(i), lower, upper, vtype} + } + + m.Variables = append(m.Variables, vs...) + return symbolic.VariableVector{Elements: vs} +} + +// AddBinaryVariableVector adds a vector of binary variables to the model and +// returns the slice. +func (m *OptimizationProblem) AddBinaryVariableVector(num int) symbolic.VariableVector { + return m.AddVariableVectorClassic(num, 0, 1, symbolic.Binary) +} + +// AddVariableMatrix adds a matrix of variables of a given type to the model with +// lower and upper value limits and returns the resulting slice. +func (m *OptimizationProblem) AddVariableMatrix( + rows, cols int, lower, upper float64, vtype symbolic.VarType, +) [][]symbolic.Variable { + vs := make([][]symbolic.Variable, rows) + for i := range vs { + tempVV := m.AddVariableVectorClassic(cols, lower, upper, vtype) + vs[i] = tempVV.Elements + } + + return vs +} + +// AddBinaryVariableMatrix adds a matrix of binary variables to the model and returns +// the resulting slice. +func (m *OptimizationProblem) AddBinaryVariableMatrix(rows, cols int) [][]symbolic.Variable { + return m.AddVariableMatrix(rows, cols, 0, 1, symbolic.Binary) +} + +// AddConstr adds the given constraint to the model. +func (m *OptimizationProblem) AddConstraint(constr symbolic.Constraint, errors ...error) error { + // Constants + + // Input Processing + err := symbolic.CheckErrors(errors) + if err != nil { + return err + } + + // Algorithm + m.Constraints = append(m.Constraints, constr) + return nil +} + +/* +SetObjective +Description: + sets the objective of the model given an expression and + objective sense. +Notes: + To make this function easier to parse, we will assume an expression + is given, even though objectives are normally scalars. +*/ + +func (m *OptimizationProblem) SetObjective(e symbolic.Expression, sense ObjSense) error { + // Input Processing + se, err := symbolic.ToScalarExpression(e) + if err != nil { + return fmt.Errorf("trouble parsing input expression: %v", err) + } + + // Return + m.Objective = NewObjective(se, sense) + return nil +} + +//// Optimize optimizes the model using the given solver type and returns the +//// solution or an error. +//func (m *OptimizationProblem) Optimize(solver Solver) (*Solution, error) { +// // Variables +// var err error +// +// // Input Processing +// if len(m.Variables) == 0 { +// return nil, errors.New("no variables in model") +// } +// +// solver.ShowLog(m.ShowLog) +// +// if m.TimeLimit > 0 { +// solver.SetTimeLimit(m.TimeLimit.Seconds()) +// } +// +// solver.AddVariables(m.Variables) +// +// for _, constr := range m.Constraints { +// solver.AddConstraint(constr) +// } +// +// mipSol, err := solver.Optimize() +// defer solver.DeleteSolver() +// +// if err != nil { +// return nil, fmt.Errorf("There was an issue while trying to optimize the model: %v", err) +// } +// +// if mipSol.Status != OptimizationStatus_OPTIMAL { +// errorMessage, err := mipSol.Status.ToMessage() +// if err != nil { +// return nil, fmt.Errorf("There was an issue converting optimization status to a message: %v", err) +// } +// return nil, fmt.Errorf( +// "[Code = %d] %s", +// mipSol.Status, +// errorMessage, +// ) +// } +// +// return &mipSol, nil +//} diff --git a/testing/problem/objective_test.go b/testing/problem/objective_test.go new file mode 100644 index 0000000..02b99fb --- /dev/null +++ b/testing/problem/objective_test.go @@ -0,0 +1,32 @@ +package problem_test + +import ( + "github.com/MatProGo-dev/MatProInterface.go/problem" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" + "testing" +) + +/* +TestObjective_NewObjective1 +Description: + + This test verifies that a new objective can be created + with the NewObjective function. It also verifies the types + of the returned value of NewObjective. +*/ +func TestObjective_NewObjective1(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + + // Algorithm + obj1 := problem.NewObjective(v1, problem.SenseMaximize) + + // Check the type of the returned value. + if obj1.Sense != problem.SenseMaximize { + t.Errorf( + "The objective sense is not properly set; received %v!", + obj1.Sense, + ) + } + +} From 08946a1b0f40a924692cd1e2fee5930b30869e05 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sat, 13 Jan 2024 11:33:35 -0500 Subject: [PATCH 05/25] Reworked Model Definition and Optimization Problem Definition --- optim/model.go | 57 ++------------------------------- problem/optimization_problem.go | 45 -------------------------- testing/optim/model_test.go | 31 ------------------ 3 files changed, 2 insertions(+), 131 deletions(-) diff --git a/optim/model.go b/optim/model.go index 79dd43e..84cf633 100644 --- a/optim/model.go +++ b/optim/model.go @@ -2,7 +2,6 @@ package optim import ( "fmt" - "time" ) // Model represents the overall constrained linear optimization model to be @@ -14,14 +13,12 @@ type Model struct { Variables []Variable Constraints []Constraint Obj *Objective - ShowLog bool - TimeLimit time.Duration } // NewModel returns a new model with some default arguments such as not to show -// the log and no time limit. +// the log. func NewModel(name string) *Model { - return &Model{Name: name, ShowLog: false} + return &Model{Name: name} } //// ShowLog instructs the solver to show the log or not. @@ -29,11 +26,6 @@ func NewModel(name string) *Model { // m.ShowLog = shouldShow //} -// SetTimeLimit sets the solver time limit for the model. -func (m *Model) SetTimeLimit(dur time.Duration) { - m.TimeLimit = dur -} - /* AddVariable Description: @@ -167,48 +159,3 @@ func (m *Model) SetObjective(e Expression, sense ObjSense) error { m.Obj = NewObjective(se, sense) return nil } - -//// Optimize optimizes the model using the given solver type and returns the -//// solution or an error. -//func (m *Model) Optimize(solver Solver) (*Solution, error) { -// // Variables -// var err error -// -// // Input Processing -// if len(m.Variables) == 0 { -// return nil, errors.New("no variables in model") -// } -// -// solver.ShowLog(m.ShowLog) -// -// if m.TimeLimit > 0 { -// solver.SetTimeLimit(m.TimeLimit.Seconds()) -// } -// -// solver.AddVariables(m.Variables) -// -// for _, constr := range m.Constraints { -// solver.AddConstraint(constr) -// } -// -// mipSol, err := solver.Optimize() -// defer solver.DeleteSolver() -// -// if err != nil { -// return nil, fmt.Errorf("There was an issue while trying to optimize the model: %v", err) -// } -// -// if mipSol.Status != OptimizationStatus_OPTIMAL { -// errorMessage, err := mipSol.Status.ToMessage() -// if err != nil { -// return nil, fmt.Errorf("There was an issue converting optimization status to a message: %v", err) -// } -// return nil, fmt.Errorf( -// "[Code = %d] %s", -// mipSol.Status, -// errorMessage, -// ) -// } -// -// return &mipSol, nil -//} diff --git a/problem/optimization_problem.go b/problem/optimization_problem.go index b650d0e..8892ce6 100644 --- a/problem/optimization_problem.go +++ b/problem/optimization_problem.go @@ -156,48 +156,3 @@ func (m *OptimizationProblem) SetObjective(e symbolic.Expression, sense ObjSense m.Objective = NewObjective(se, sense) return nil } - -//// Optimize optimizes the model using the given solver type and returns the -//// solution or an error. -//func (m *OptimizationProblem) Optimize(solver Solver) (*Solution, error) { -// // Variables -// var err error -// -// // Input Processing -// if len(m.Variables) == 0 { -// return nil, errors.New("no variables in model") -// } -// -// solver.ShowLog(m.ShowLog) -// -// if m.TimeLimit > 0 { -// solver.SetTimeLimit(m.TimeLimit.Seconds()) -// } -// -// solver.AddVariables(m.Variables) -// -// for _, constr := range m.Constraints { -// solver.AddConstraint(constr) -// } -// -// mipSol, err := solver.Optimize() -// defer solver.DeleteSolver() -// -// if err != nil { -// return nil, fmt.Errorf("There was an issue while trying to optimize the model: %v", err) -// } -// -// if mipSol.Status != OptimizationStatus_OPTIMAL { -// errorMessage, err := mipSol.Status.ToMessage() -// if err != nil { -// return nil, fmt.Errorf("There was an issue converting optimization status to a message: %v", err) -// } -// return nil, fmt.Errorf( -// "[Code = %d] %s", -// mipSol.Status, -// errorMessage, -// ) -// } -// -// return &mipSol, nil -//} diff --git a/testing/optim/model_test.go b/testing/optim/model_test.go index a73e25e..e9bd22e 100644 --- a/testing/optim/model_test.go +++ b/testing/optim/model_test.go @@ -9,7 +9,6 @@ Description: import ( "github.com/MatProGo-dev/MatProInterface.go/optim" "testing" - "time" ) /* @@ -23,41 +22,11 @@ func TestModel_NewModel1(t *testing.T) { m := optim.NewModel("test") // Algorithm - if m.ShowLog { - t.Errorf("ShowLog should be initialized as false.") - } - if m.Name != "test" { t.Errorf("Expected model's name to be %v; received %v", "test", m.Name) } } -/* -TestModel_SetTimeLimit1 -Description: - - Tests the ability of this function to set the time limit for the model solution. -*/ -func TestModel_SetTimeLimit1(t *testing.T) { - // Constants - t1 := time.Now() - dur1 := time.Since(t1) - - m := optim.NewModel("test-settimelimit1") - - // Algorithm - m.SetTimeLimit(dur1) - - if m.TimeLimit != dur1 { - t.Errorf( - "Expected time limit to be %v; received %v", - m.TimeLimit, - dur1, - ) - } - -} - /* TestModel_AddVariable1 Description: From 9c7b559eb6a534bb3e2624b18c8e0e04d6c65439 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sat, 13 Jan 2024 12:13:50 -0500 Subject: [PATCH 06/25] Moved Objective Sense to a New File --- problem/objective.go | 10 ---------- problem/objective_sense.go | 27 +++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 problem/objective_sense.go diff --git a/problem/objective.go b/problem/objective.go index 470ef42..584e789 100644 --- a/problem/objective.go +++ b/problem/objective.go @@ -14,13 +14,3 @@ type Objective struct { func NewObjective(e symbolic.Expression, sense ObjSense) *Objective { return &Objective{e, sense} } - -// ObjSense represents whether an optimization objective is to be maximized or -// minimized. This implementation conforms to the Gurobi encoding -type ObjSense int - -// Objective senses (minimize and maximize) encoding using Gurobi's standard -const ( - SenseMinimize ObjSense = 1 - SenseMaximize = -1 -) diff --git a/problem/objective_sense.go b/problem/objective_sense.go new file mode 100644 index 0000000..c7ee21d --- /dev/null +++ b/problem/objective_sense.go @@ -0,0 +1,27 @@ +package problem + +import "github.com/MatProGo-dev/MatProInterface.go/optim" + +// ObjSense represents whether an optimization objective is to be maximized or +// minimized. This implementation conforms to the Gurobi encoding +type ObjSense int + +// Objective senses (minimize and maximize) encoding using Gurobi's standard +const ( + SenseMinimize ObjSense = 1 + SenseMaximize = -1 +) + +/* +ToObjSense +Description: + + This method converts an input optim.ObjSense to a problem.ObjSense. +*/ +func ToObjSense(sense optim.ObjSense) ObjSense { + if sense == optim.SenseMinimize { + return SenseMinimize + } else { + return SenseMaximize + } +} From cfca9f67b4bc241015e1f8ccd55659d26d76bb06 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sat, 13 Jan 2024 12:15:00 -0500 Subject: [PATCH 07/25] Moved Solution into the Problem Package and Moved Solver to a new package --- optim/solver.go | 18 ---------------- {optim => problem}/solution.go | 9 +++++--- solver/solver.go | 23 +++++++++++++++++++++ testing/{optim => problem}/solution_test.go | 13 ++++++------ 4 files changed, 36 insertions(+), 27 deletions(-) delete mode 100644 optim/solver.go rename {optim => problem}/solution.go (97%) create mode 100644 solver/solver.go rename testing/{optim => problem}/solution_test.go (93%) diff --git a/optim/solver.go b/optim/solver.go deleted file mode 100644 index 6d30a1e..0000000 --- a/optim/solver.go +++ /dev/null @@ -1,18 +0,0 @@ -package optim - -/* -solver.go -Description: - Defines the new interface Solver which should define -*/ - -type Solver interface { - ShowLog(tf bool) error - SetTimeLimit(timeLimit float64) error - AddVariable(varIn Variable) error - AddVariables(varSlice []Variable) error - AddConstraint(constrIn Constraint) error - SetObjective(objectiveIn Objective) error - Optimize() (Solution, error) - DeleteSolver() error -} diff --git a/optim/solution.go b/problem/solution.go similarity index 97% rename from optim/solution.go rename to problem/solution.go index 7c61115..79f7d2c 100644 --- a/optim/solution.go +++ b/problem/solution.go @@ -1,6 +1,9 @@ -package optim +package problem -import "fmt" +import ( + "fmt" + "github.com/MatProGo-dev/MatProInterface.go/optim" +) const ( tinyNum float64 = 0.01 @@ -102,7 +105,7 @@ func (os OptimizationStatus) ToMessage() (string, error) { // } // Value returns the value assigned to the variable in the solution -func (s *Solution) Value(v Variable) float64 { +func (s *Solution) Value(v optim.Variable) float64 { return s.Values[v.ID] } diff --git a/solver/solver.go b/solver/solver.go new file mode 100644 index 0000000..6dc0457 --- /dev/null +++ b/solver/solver.go @@ -0,0 +1,23 @@ +package solver + +import ( + op "github.com/MatProGo-dev/MatProInterface.go/problem" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" +) + +/* +solver.go +Description: + Defines the new interface Solver which should define +*/ + +type Solver interface { + ShowLog(tf bool) error + SetTimeLimit(timeLimit float64) error + AddVariable(varIn symbolic.Variable) error + AddVariables(varSlice []symbolic.Variable) error + AddConstraint(constrIn symbolic.Constraint) error + SetObjective(objectiveIn op.Objective) error + Optimize() (op.Solution, error) + DeleteSolver() error +} diff --git a/testing/optim/solution_test.go b/testing/problem/solution_test.go similarity index 93% rename from testing/optim/solution_test.go rename to testing/problem/solution_test.go index 72a4e19..d7e254e 100644 --- a/testing/optim/solution_test.go +++ b/testing/problem/solution_test.go @@ -1,7 +1,8 @@ -package optim +package problem import ( "github.com/MatProGo-dev/MatProInterface.go/optim" + "github.com/MatProGo-dev/MatProInterface.go/problem" "strings" "testing" ) @@ -15,13 +16,13 @@ Description: func TestSolution_ToMessage1(t *testing.T) { // Constants - tempSol := optim.Solution{ + tempSol := problem.Solution{ Values: map[uint64]float64{ 0: 2.1, 1: 3.14, }, Objective: 2.3, - Status: optim.OptimizationStatus_NODE_LIMIT, + Status: problem.OptimizationStatus_NODE_LIMIT, } // Test the ToMessage() Call on this solution. @@ -44,7 +45,7 @@ func TestSolution_ToMessage2(t *testing.T) { // Test for statusIndex := 1; statusIndex < statusMax; statusIndex++ { - tempStatus := optim.OptimizationStatus(statusIndex) + tempStatus := problem.OptimizationStatus(statusIndex) msg, err := tempStatus.ToMessage() if err != nil { @@ -93,13 +94,13 @@ Description: */ func TestSolution_Value1(t *testing.T) { // Constants - tempSol := optim.Solution{ + tempSol := problem.Solution{ Values: map[uint64]float64{ 0: 2.1, 1: 3.14, }, Objective: 2.3, - Status: optim.OptimizationStatus_NODE_LIMIT, + Status: problem.OptimizationStatus_NODE_LIMIT, } v1 := optim.Variable{ ID: 0, Lower: -optim.INFINITY, Upper: optim.INFINITY, Vtype: optim.Continuous, From be88dd25105b672ab06608028af16346750bedb7 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sat, 13 Jan 2024 12:18:37 -0500 Subject: [PATCH 08/25] Added Check Method for Scalar Constraint and Simplified Model Object --- optim/model.go | 27 +++++++++-- optim/scalar_constraint.go | 26 ++++++++++ problem/optimization_problem.go | 84 +++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 5 deletions(-) diff --git a/optim/model.go b/optim/model.go index 84cf633..3c1cb49 100644 --- a/optim/model.go +++ b/optim/model.go @@ -21,11 +21,6 @@ func NewModel(name string) *Model { return &Model{Name: name} } -//// ShowLog instructs the solver to show the log or not. -//func (m *Model) ShowLog(shouldShow bool) { -// m.ShowLog = shouldShow -//} - /* AddVariable Description: @@ -159,3 +154,25 @@ func (m *Model) SetObjective(e Expression, sense ObjSense) error { m.Obj = NewObjective(se, sense) return nil } + +/* +Check +Description: + + Checks the model for errors. +*/ +func (m *Model) Check() error { + // Constants + + // Verifiy that there is at least one variable in the model. + if len(m.Variables) == 0 { + return fmt.Errorf("the model has no variables!") + } + + // It's okay if there are no constraints. + + // It's okay if there is not an objective. + + // All Checks have passed. + return nil +} diff --git a/optim/scalar_constraint.go b/optim/scalar_constraint.go index 7ff0b8e..1f237ef 100644 --- a/optim/scalar_constraint.go +++ b/optim/scalar_constraint.go @@ -106,6 +106,32 @@ func (sc ScalarConstraint) Simplify() (ScalarConstraint, error) { } +/* +Check +Description: + + Checks the validity of the ScalarConstraint, this makes sure that: + - The Sense if either SenseEqual, SenseLessThanEqual, or SenseGreaterThanEqual +*/ +func (sc ScalarConstraint) Check() error { + // Check sense + switch sc.Sense { + case SenseEqual: + break + case SenseLessThanEqual: + break + case SenseGreaterThanEqual: + break + default: + return fmt.Errorf("the constraint sense is not recognized.") + } + + // Check left and right hand sides? + + // Return + return nil +} + // ConstrSense represents if the constraint x <= y, x >= y, or x == y. For easy // integration with Gurobi, the senses have been encoding using a byte in // the same way Gurobi encodes the constraint senses. diff --git a/problem/optimization_problem.go b/problem/optimization_problem.go index 8892ce6..757b628 100644 --- a/problem/optimization_problem.go +++ b/problem/optimization_problem.go @@ -156,3 +156,87 @@ func (m *OptimizationProblem) SetObjective(e symbolic.Expression, sense ObjSense m.Objective = NewObjective(se, sense) return nil } + +/* +ToScalarConstraint +Description: + + Converts a constraint in the form of a optim.ScalarConstraint + object into a symbolic.ScalarConstraint object. +*/ +func ToScalarConstraint(inputConstraint optim.ScalarConstraint) (symbolic.ScalarConstraint, error) { + // Input Processing + err := inputConstraint.Check() + if err != nil { + return symbolic.ScalarConstraint{}, err + } + + // Convert LHS to symbolic expression + + switch inputConstraint.Sense() { + case optim.EQ: + return ToScalarEq(inputConstraint) + case optim.LE: + return ToScalarLessEq(inputConstraint) + case optim.GE: + return ToScalarGreaterEq(inputConstraint) + default: + return nil, fmt.Errorf("the input constraint sense is not recognized.") + } +} + +/* +ToSymbolicConstraint +Description: + + Converts a constraint in the form of a optim.Constraint object into a symbolic.Constraint object. +*/ +func ToSymbolicConstraint(inputConstraint optim.Constraint) (symbolic.Constraint, error) { + // Input Processing + err := inputConstraint.Check() + if err != nil { + return nil, err + } + + // Algorithm + switch { + case inputConstraint.IsScalar(): + return ToScalarConstraint(inputConstraint) + case inputConstraint.IsVector(): + return ToVectorConstraint(inputConstraint) + default: + return nil, fmt.Errorf("the input constraint is not recognized as a scalar or vector constraint.") + } +} + +/* +ToOptimizationProblem +Description: + + Converts the given input into an optimization problem. +*/ +func ToOptimizationProblem(inputModel optim.Model) (*OptimizationProblem, error) { + // Create a new optimization problem + newOptimProblem := NewProblem(inputModel.Name) + + // Input Processing + err := inputModel.Check() + if err != nil { + return nil, err + } + + // Collect All Variables from Model and copy them into the new optimization + // problem object. + for ii, variable := range inputModel.Variables { + newOptimProblem.Variables = append(newOptimProblem.Variables, symbolic.Variable{ + ID: uint64(ii), + Lower: variable.Lower, + Upper: variable.Upper, + Type: symbolic.VarType(variable.Vtype), + }) + } + + // Collect All Constraints from Model and copy them into the new optimization + // problem object. + +} From 780a61997f02a35dbefba882577a0123a2366f56 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sat, 13 Jan 2024 12:20:21 -0500 Subject: [PATCH 09/25] Commented out unfinished function --- problem/optimization_problem.go | 77 +++++++++++++++++---------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/problem/optimization_problem.go b/problem/optimization_problem.go index 757b628..d758057 100644 --- a/problem/optimization_problem.go +++ b/problem/optimization_problem.go @@ -164,26 +164,26 @@ Description: Converts a constraint in the form of a optim.ScalarConstraint object into a symbolic.ScalarConstraint object. */ -func ToScalarConstraint(inputConstraint optim.ScalarConstraint) (symbolic.ScalarConstraint, error) { - // Input Processing - err := inputConstraint.Check() - if err != nil { - return symbolic.ScalarConstraint{}, err - } - - // Convert LHS to symbolic expression - - switch inputConstraint.Sense() { - case optim.EQ: - return ToScalarEq(inputConstraint) - case optim.LE: - return ToScalarLessEq(inputConstraint) - case optim.GE: - return ToScalarGreaterEq(inputConstraint) - default: - return nil, fmt.Errorf("the input constraint sense is not recognized.") - } -} +//func ToScalarConstraint(inputConstraint optim.ScalarConstraint) (symbolic.ScalarConstraint, error) { +// // Input Processing +// err := inputConstraint.Check() +// if err != nil { +// return symbolic.ScalarConstraint{}, err +// } +// +// // Convert LHS to symbolic expression +// +// switch inputConstraint.Sense() { +// case optim.EQ: +// return ToScalarEq(inputConstraint) +// case optim.LE: +// return ToScalarLessEq(inputConstraint) +// case optim.GE: +// return ToScalarGreaterEq(inputConstraint) +// default: +// return nil, fmt.Errorf("the input constraint sense is not recognized.") +// } +//} /* ToSymbolicConstraint @@ -191,23 +191,23 @@ Description: Converts a constraint in the form of a optim.Constraint object into a symbolic.Constraint object. */ -func ToSymbolicConstraint(inputConstraint optim.Constraint) (symbolic.Constraint, error) { - // Input Processing - err := inputConstraint.Check() - if err != nil { - return nil, err - } - - // Algorithm - switch { - case inputConstraint.IsScalar(): - return ToScalarConstraint(inputConstraint) - case inputConstraint.IsVector(): - return ToVectorConstraint(inputConstraint) - default: - return nil, fmt.Errorf("the input constraint is not recognized as a scalar or vector constraint.") - } -} +//func ToSymbolicConstraint(inputConstraint optim.Constraint) (symbolic.Constraint, error) { +// // Input Processing +// err := inputConstraint.Check() +// if err != nil { +// return nil, err +// } +// +// // Algorithm +// switch { +// case inputConstraint.IsScalar(): +// return ToScalarConstraint(inputConstraint) +// case inputConstraint.IsVector(): +// return ToVectorConstraint(inputConstraint) +// default: +// return nil, fmt.Errorf("the input constraint is not recognized as a scalar or vector constraint.") +// } +//} /* ToOptimizationProblem @@ -239,4 +239,7 @@ func ToOptimizationProblem(inputModel optim.Model) (*OptimizationProblem, error) // Collect All Constraints from Model and copy them into the new optimization // problem object. + // Done + return newOptimProblem, nil + } From c71b6dd5f14ec8753283678fa1073e49f3d81c6b Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sat, 13 Jan 2024 12:30:58 -0500 Subject: [PATCH 10/25] DeletedSolver interface as well as added some To-Do items --- README.md | 4 ++-- solver/solver.go | 23 ----------------------- 2 files changed, 2 insertions(+), 25 deletions(-) delete mode 100644 solver/solver.go diff --git a/README.md b/README.md index ce991d9..97edad0 100644 --- a/README.md +++ b/README.md @@ -140,8 +140,8 @@ code. Hopefully, this is avoided using this format. * [ ] Decide whether or not we really need the Coeffs() method (What is it doing?) * [ ] Write changes to all AtVec() methods to output both elements AND errors (so we can detect out of length calls) * [ ] Determine whether or not to keep the Solution and Solver() interfaces in this module. It seems like they can be solver-specific. -* [ ] Introduce MatrixVar object * [ ] Add Check() to: * [ ] Expression * [ ] ScalarExpression - * [ ] VectorExpression interfaces \ No newline at end of file + * [ ] VectorExpression interfaces +* [ ] Add ToSymbolic() Method for ALL expressions \ No newline at end of file diff --git a/solver/solver.go b/solver/solver.go deleted file mode 100644 index 6dc0457..0000000 --- a/solver/solver.go +++ /dev/null @@ -1,23 +0,0 @@ -package solver - -import ( - op "github.com/MatProGo-dev/MatProInterface.go/problem" - "github.com/MatProGo-dev/SymbolicMath.go/symbolic" -) - -/* -solver.go -Description: - Defines the new interface Solver which should define -*/ - -type Solver interface { - ShowLog(tf bool) error - SetTimeLimit(timeLimit float64) error - AddVariable(varIn symbolic.Variable) error - AddVariables(varSlice []symbolic.Variable) error - AddConstraint(constrIn symbolic.Constraint) error - SetObjective(objectiveIn op.Objective) error - Optimize() (op.Solution, error) - DeleteSolver() error -} From 2ea7517426436f81bb39648f21cd47c14a6e5680 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sun, 14 Jan 2024 13:26:16 -0500 Subject: [PATCH 11/25] Introduced ToSymbolic() Method to Scalar Expressions --- optim/constant.go | 12 +++++++++ optim/scalar_expression.go | 5 ++++ optim/scalar_linear_expr.go | 22 ++++++++++++++++ optim/scalar_quadratic_expression.go | 36 ++++++++++++++++++++++++++ optim/var_vector.go | 38 ++++++++++++++++++++++++++++ optim/vars.go | 23 +++++++++++++++++ 6 files changed, 136 insertions(+) diff --git a/optim/constant.go b/optim/constant.go index 2de490d..7c1a723 100644 --- a/optim/constant.go +++ b/optim/constant.go @@ -2,6 +2,7 @@ package optim import ( "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "gonum.org/v1/gonum/mat" ) @@ -246,3 +247,14 @@ func (c K) Check() error { func (c K) Transpose() Expression { return c } + +/* +ToSymbolic +Description: + + Converts the constant to a symbolic expression (i.e., one that uses the + symbolic math toolbox). +*/ +func (c K) ToSymbolic() (symbolic.Expression, error) { + return symbolic.K(c), nil +} diff --git a/optim/scalar_expression.go b/optim/scalar_expression.go index 0497aae..a8e7b39 100644 --- a/optim/scalar_expression.go +++ b/optim/scalar_expression.go @@ -2,6 +2,7 @@ package optim import ( "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" ) // ScalarExpression represents a linear general expression of the form @@ -58,6 +59,10 @@ type ScalarExpression interface { //Transpose returns the transpose of the given vector expression Transpose() Expression + + //ToSymbolic Returns the symbolic version of the scalar expression + // (i.e., the expression when declared using the symbolic math toolbox). + ToSymbolic() (symbolic.Expression, error) } // NewExpr returns a new expression with a single additive constant value, c, diff --git a/optim/scalar_linear_expr.go b/optim/scalar_linear_expr.go index cebd5e9..fab5a18 100644 --- a/optim/scalar_linear_expr.go +++ b/optim/scalar_linear_expr.go @@ -2,6 +2,7 @@ package optim import ( "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "gonum.org/v1/gonum/mat" ) @@ -421,3 +422,24 @@ Description: func (sle ScalarLinearExpr) Transpose() Expression { return sle } + +/* +ToSymbolic +Description: + + Converts the constant to a symbolic expression (i.e., one that uses the + symbolic math toolbox). +*/ +func (sle ScalarLinearExpr) ToSymbolic() (symbolic.Expression, error) { + // Compute product of L and X + symX, err := sle.X.ToSymbolic() + if err != nil { + return nil, err + } + + tempProduct := symbolic.KVector(sle.L).Transpose().Multiply(symX) + + // Add C + return tempProduct.Plus(symbolic.K(sle.C)), nil + +} diff --git a/optim/scalar_quadratic_expression.go b/optim/scalar_quadratic_expression.go index d36c977..d180fec 100644 --- a/optim/scalar_quadratic_expression.go +++ b/optim/scalar_quadratic_expression.go @@ -2,6 +2,7 @@ package optim import ( "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "gonum.org/v1/gonum/mat" ) @@ -470,3 +471,38 @@ Description: func (qe ScalarQuadraticExpression) Transpose() Expression { return qe } + +/* +ToSymbolic +Description: + + This function converts the quadratic expression into a symbolic expression. + (i.e., one that uses the symbolic math toolbox). +*/ +func (qe ScalarQuadraticExpression) ToSymbolic() (symbolic.Expression, error) { + // Constants + var err error + + // Convert Q, L and C to symbolic + symQ := symbolic.KMatrix(qe.Q) + symL := symbolic.KVector(qe.L) + symC := symbolic.K(qe.C) + + // Comvert X to symbolic + symXExpr, err := qe.X.ToSymbolic() + if err != nil { + return nil, err + } + + symX, ok := symXExpr.(symbolic.VariableVector) + if !ok { + return nil, fmt.Errorf("Could not convert X to symbolic.VariableVector.") + } + + // Perform Multplications in Symbolic + quadraticTerm := symX.Transpose().Multiply(symQ).Multiply(symX) + linearTerm := symL.Transpose().Multiply(symX) + + // Sum all terms together and return it + return quadraticTerm.Plus(linearTerm).Plus(symC), nil +} diff --git a/optim/var_vector.go b/optim/var_vector.go index a176c50..418cd81 100644 --- a/optim/var_vector.go +++ b/optim/var_vector.go @@ -2,6 +2,7 @@ package optim import ( "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "gonum.org/v1/gonum/mat" ) @@ -432,3 +433,40 @@ func (vv VarVector) Check() error { // If nothing was thrown, then return nil! return nil } + +/* +ToSymbolic +Description: + + Converts the variable vector to a symbolic expression. + (i.e., one that uses the symbolic math toolbox). +*/ +func (vv VarVector) ToSymbolic() (symbolic.Expression, error) { + // Constants + + // Algorithm + // Create the symbolic vector + symVVec := symbolic.VariableVector{} + + // Add each variable to the vector + for _, elt := range vv.Elements { + eltAsSymExpr, err := elt.ToSymbolic() + if err != nil { + return nil, fmt.Errorf( + "could not convert variable %v to symbolic variable", + elt, + ) + } + eltAsSymVar, ok := eltAsSymExpr.(symbolic.Variable) + if !ok { + return nil, fmt.Errorf( + "could not convert variable %v to symbolic variable", + elt, + ) + } + symVVec.Elements = append(symVVec.Elements, eltAsSymVar) + } + + // Return the symbolic vector + return symVVec, nil +} diff --git a/optim/vars.go b/optim/vars.go index 1e810b3..94175b8 100644 --- a/optim/vars.go +++ b/optim/vars.go @@ -2,6 +2,7 @@ package optim import ( "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "gonum.org/v1/gonum/mat" ) @@ -411,3 +412,25 @@ func (v Variable) Check() error { func (v Variable) Transpose() Expression { return v } + +/* +ToSymbolic +Description: + + Converts the variable into a symbolic variable + (from the symbolic math toolbox). +*/ +func (v Variable) ToSymbolic() (symbolic.Expression, error) { + // Constants + + // Create base variable and fill its elements + symVar := symbolic.Variable{ + ID: v.ID, + Lower: v.Lower, + Upper: v.Upper, + Type: symbolic.VarType(v.Vtype), + } + + // Algorithm + return symVar, nil +} From 4e6e0129ebb188fdf3dcf1b7eb1590b0b4e2d3da Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sat, 17 Feb 2024 10:44:28 -0500 Subject: [PATCH 12/25] Introduced ToSymbolic() Method to Vector Expressions + Modified the Constraint Interface a Bit to Make the Model To SymbolicProblem method work well --- go.mod | 2 +- go.sum | 4 + optim/constr_sense.go | 34 +++++ optim/constraint.go | 2 + optim/expression.go | 5 + optim/scalar_constraint.go | 16 +-- optim/scalar_linear_expr.go | 2 +- optim/scalar_quadratic_expression.go | 4 +- optim/var_vector.go | 2 +- optim/var_vector_transpose.go | 28 ++++ optim/vector_constant.go | 19 ++- optim/vector_constant_transpose.go | 28 +++- optim/vector_constraint.go | 10 ++ optim/vector_expression.go | 5 + optim/vector_linear_expression.go | 27 ++++ optim/vector_linear_expression_transpose.go | 29 ++++ problem/optimization_problem.go | 149 +++++++++++--------- 17 files changed, 284 insertions(+), 82 deletions(-) create mode 100644 optim/constr_sense.go diff --git a/go.mod b/go.mod index ba8fcfb..493e1e8 100644 --- a/go.mod +++ b/go.mod @@ -4,4 +4,4 @@ go 1.21 require gonum.org/v1/gonum v0.14.0 -require github.com/MatProGo-dev/SymbolicMath.go v0.0.0-20240104201035-f9a42f642121 +require github.com/MatProGo-dev/SymbolicMath.go v0.1.1 diff --git a/go.sum b/go.sum index 0a98da3..eb12264 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,9 @@ github.com/MatProGo-dev/SymbolicMath.go v0.0.0-20240104201035-f9a42f642121 h1:nnJVXcnTvAdkfR4kZRBw4Ot+KoL8S3PijlLTmKOooco= github.com/MatProGo-dev/SymbolicMath.go v0.0.0-20240104201035-f9a42f642121/go.mod h1:gKbGR/6sYWi2koMUEDIPWBPi6jQPELKle0ijIM+eaHU= +github.com/MatProGo-dev/SymbolicMath.go v0.1.0 h1:FUwLQZzZhtgGj6WKyuwQE74P9UYhgbNr5eD5f8zzuu8= +github.com/MatProGo-dev/SymbolicMath.go v0.1.0/go.mod h1:gKbGR/6sYWi2koMUEDIPWBPi6jQPELKle0ijIM+eaHU= +github.com/MatProGo-dev/SymbolicMath.go v0.1.1 h1:YigpL7w5D8qLu0xzDAayMXePBh9LK0/w3ykvuoVZwXQ= +github.com/MatProGo-dev/SymbolicMath.go v0.1.1/go.mod h1:gKbGR/6sYWi2koMUEDIPWBPi6jQPELKle0ijIM+eaHU= golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= diff --git a/optim/constr_sense.go b/optim/constr_sense.go new file mode 100644 index 0000000..6a9826e --- /dev/null +++ b/optim/constr_sense.go @@ -0,0 +1,34 @@ +package optim + +import "github.com/MatProGo-dev/SymbolicMath.go/symbolic" + +// ConstrSense represents if the constraint x <= y, x >= y, or x == y. For easy +// integration with Gurobi, the senses have been encoding using a byte in +// the same way Gurobi encodes the constraint senses. +type ConstrSense byte + +// Different constraint senses conforming to Gurobi's encoding. +const ( + SenseEqual ConstrSense = '=' + SenseLessThanEqual = '<' + SenseGreaterThanEqual = '>' +) + +/* +ToSymbolic +Description: + + Converts a constraint sense to a the appropriate representation + in the symbolic math toolbox. +*/ +func (cs ConstrSense) ToSymbolic() symbolic.ConstrSense { + switch cs { + case SenseEqual: + return symbolic.SenseEqual + case SenseLessThanEqual: + return symbolic.SenseLessThanEqual + case SenseGreaterThanEqual: + return symbolic.SenseGreaterThanEqual + } + return '1' +} diff --git a/optim/constraint.go b/optim/constraint.go index e145ba9..3460296 100644 --- a/optim/constraint.go +++ b/optim/constraint.go @@ -10,6 +10,8 @@ Description: type Constraint interface { Left() Expression Right() Expression + ConstrSense() ConstrSense + Check() error } func IsConstraint(c interface{}) bool { diff --git a/optim/expression.go b/optim/expression.go index 2036947..85dff6f 100644 --- a/optim/expression.go +++ b/optim/expression.go @@ -2,6 +2,7 @@ package optim import ( "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" ) /* @@ -52,6 +53,10 @@ type Expression interface { // Comparison Comparison(rightIn interface{}, sense ConstrSense, errors ...error) (Constraint, error) + + //ToSymbolic + // Converts the expression to a symbolic expression (in SymbolicMath.go) + ToSymbolic() (symbolic.Expression, error) } /* diff --git a/optim/scalar_constraint.go b/optim/scalar_constraint.go index 1f237ef..cd28637 100644 --- a/optim/scalar_constraint.go +++ b/optim/scalar_constraint.go @@ -19,6 +19,10 @@ func (sc ScalarConstraint) Right() Expression { return sc.RightHandSide } +func (sc ScalarConstraint) ConstrSense() ConstrSense { + return sc.Sense +} + /* IsLinear Description: @@ -131,15 +135,3 @@ func (sc ScalarConstraint) Check() error { // Return return nil } - -// ConstrSense represents if the constraint x <= y, x >= y, or x == y. For easy -// integration with Gurobi, the senses have been encoding using a byte in -// the same way Gurobi encodes the constraint senses. -type ConstrSense byte - -// Different constraint senses conforming to Gurobi's encoding. -const ( - SenseEqual ConstrSense = '=' - SenseLessThanEqual = '<' - SenseGreaterThanEqual = '>' -) diff --git a/optim/scalar_linear_expr.go b/optim/scalar_linear_expr.go index fab5a18..a4a902b 100644 --- a/optim/scalar_linear_expr.go +++ b/optim/scalar_linear_expr.go @@ -437,7 +437,7 @@ func (sle ScalarLinearExpr) ToSymbolic() (symbolic.Expression, error) { return nil, err } - tempProduct := symbolic.KVector(sle.L).Transpose().Multiply(symX) + tempProduct := symbolic.VecDenseToKVector(sle.L).Transpose().Multiply(symX) // Add C return tempProduct.Plus(symbolic.K(sle.C)), nil diff --git a/optim/scalar_quadratic_expression.go b/optim/scalar_quadratic_expression.go index d180fec..05d7058 100644 --- a/optim/scalar_quadratic_expression.go +++ b/optim/scalar_quadratic_expression.go @@ -484,8 +484,8 @@ func (qe ScalarQuadraticExpression) ToSymbolic() (symbolic.Expression, error) { var err error // Convert Q, L and C to symbolic - symQ := symbolic.KMatrix(qe.Q) - symL := symbolic.KVector(qe.L) + symQ := symbolic.DenseToKMatrix(qe.Q) + symL := symbolic.VecDenseToKVector(qe.L) symC := symbolic.K(qe.C) // Comvert X to symbolic diff --git a/optim/var_vector.go b/optim/var_vector.go index 418cd81..e83fec0 100644 --- a/optim/var_vector.go +++ b/optim/var_vector.go @@ -464,7 +464,7 @@ func (vv VarVector) ToSymbolic() (symbolic.Expression, error) { elt, ) } - symVVec.Elements = append(symVVec.Elements, eltAsSymVar) + symVVec = append(symVVec, eltAsSymVar) } // Return the symbolic vector diff --git a/optim/var_vector_transpose.go b/optim/var_vector_transpose.go index 2ff1405..a1da50b 100644 --- a/optim/var_vector_transpose.go +++ b/optim/var_vector_transpose.go @@ -2,6 +2,7 @@ package optim import ( "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "gonum.org/v1/gonum/mat" ) @@ -467,3 +468,30 @@ Description: func (vvt VarVectorTranspose) Dims() []int { return []int{1, vvt.Len()} } + +/* +ToSymbolic +Description: + + This method converts the VarVectorTranspose to a symbolic expression + (i.e., an expression made using SymbolicMath.go). +*/ +func (vvt VarVectorTranspose) ToSymbolic() (symbolic.Expression, error) { + // Constants + vm := symbolic.VariableMatrix{} + + // Algorithm + vm = append(vm, make([]symbolic.Variable, vvt.Len())) + for ii, v := range vvt.Elements { + // Convert to symbolic + tempV, err := v.ToSymbolic() + if err != nil { + return nil, err + } + vm[0][ii] = tempV.(symbolic.Variable) + } + + // Return + return vm, nil + +} diff --git a/optim/vector_constant.go b/optim/vector_constant.go index dcb907b..b35f1f4 100644 --- a/optim/vector_constant.go +++ b/optim/vector_constant.go @@ -1,6 +1,9 @@ package optim -import "gonum.org/v1/gonum/mat" +import ( + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" + "gonum.org/v1/gonum/mat" +) /* vector_constant_test.go @@ -413,3 +416,17 @@ Description: func (kv KVector) Dims() []int { return []int{kv.Len(), 1} } + +/* +ToSymbolic +Description: + + This method returns the symbolic version of the constant vector. +*/ +func (kv KVector) ToSymbolic() (symbolic.Expression, error) { + // Constants + kvAsVec := mat.VecDense(kv) + + // Create the symbolic vector + return symbolic.VecDenseToKVector(kvAsVec), nil +} diff --git a/optim/vector_constant_transpose.go b/optim/vector_constant_transpose.go index f4fff4d..831ddfd 100644 --- a/optim/vector_constant_transpose.go +++ b/optim/vector_constant_transpose.go @@ -1,6 +1,9 @@ package optim -import "gonum.org/v1/gonum/mat" +import ( + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" + "gonum.org/v1/gonum/mat" +) /* vector_constant_test.go @@ -383,3 +386,26 @@ Description: func (kvt KVectorTranspose) Dims() []int { return []int{1, kvt.Len()} } + +/* +ToSymbolic +Description: + + This method returns the symbolic version of the KVectorTranspose expression. +*/ +func (kvt KVectorTranspose) ToSymbolic() (symbolic.Expression, error) { + // Constants + kvLen := kvt.Len() + + // Create the symbolic expression + km := symbolic.KMatrix{} + + // Add the constant values + km = append(km, make([]symbolic.K, kvLen)) + for i := 0; i < kvLen; i++ { + kvtAsVD := mat.VecDense(kvt) + km[0][i] = symbolic.K(kvtAsVD.AtVec(i)) + } + + return km, nil +} diff --git a/optim/vector_constraint.go b/optim/vector_constraint.go index e9b1f7f..e70374a 100644 --- a/optim/vector_constraint.go +++ b/optim/vector_constraint.go @@ -71,3 +71,13 @@ func (vc VectorConstraint) Left() Expression { func (vc VectorConstraint) Right() Expression { return vc.RightHandSide } + +/* +ConstrSense +Description: + + Returns the sense of the constraint. +*/ +func (vc VectorConstraint) ConstrSense() ConstrSense { + return vc.Sense +} diff --git a/optim/vector_expression.go b/optim/vector_expression.go index f144b7f..bc131c9 100644 --- a/optim/vector_expression.go +++ b/optim/vector_expression.go @@ -8,6 +8,7 @@ Description: import ( "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "gonum.org/v1/gonum/mat" ) @@ -70,6 +71,10 @@ type VectorExpression interface { // Dims returns the dimensions of the given expression Dims() []int + + //ToSymbolic + // Converts the expression to a symbolic expression (in SymbolicMath.go) + ToSymbolic() (symbolic.Expression, error) } /* diff --git a/optim/vector_linear_expression.go b/optim/vector_linear_expression.go index d4f7be3..2d6f254 100644 --- a/optim/vector_linear_expression.go +++ b/optim/vector_linear_expression.go @@ -8,6 +8,7 @@ Description: import ( "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "gonum.org/v1/gonum/mat" ) @@ -566,3 +567,29 @@ Description: func (vle VectorLinearExpr) Dims() []int { return []int{vle.Len(), 1} } + +/* +ToSymbolic +Description: + + This method returns the symbolic version of the KVectorTranspose expression. +*/ +func (vle VectorLinearExpr) ToSymbolic() (symbolic.Expression, error) { + // Input Processing + err := vle.Check() + if err != nil { + return nil, err + } + + // Constants + L := symbolic.DenseToKMatrix(vle.L) + C := symbolic.VecDenseToKVector(vle.C) + X, err := vle.X.ToSymbolic() + if err != nil { + return nil, err + } + + // Create the symbolic expression + return L.Multiply(X).Plus(C), nil + +} diff --git a/optim/vector_linear_expression_transpose.go b/optim/vector_linear_expression_transpose.go index b4f3e13..43e1c96 100644 --- a/optim/vector_linear_expression_transpose.go +++ b/optim/vector_linear_expression_transpose.go @@ -8,6 +8,7 @@ Description: import ( "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "gonum.org/v1/gonum/mat" ) @@ -571,3 +572,31 @@ func (vlet VectorLinearExpressionTranspose) ToScalarLinearExpression() (ScalarLi C := vlet.C.AtVec(0) return ScalarLinearExpr{L: L, X: vlet.X.Copy(), C: C}, nil } + +/* +ToSymbolic +Description: + + Returns the symbolic version of the vector linear expression + transpose. +*/ +func (vlet VectorLinearExpressionTranspose) ToSymbolic() (symbolic.Expression, error) { + // Check + err := vlet.Check() + if err != nil { + return nil, err + } + + // Constants + L := symbolic.DenseToKMatrix(vlet.L) + C := symbolic.VecDenseToKVector(vlet.C) + X, err := vlet.X.ToSymbolic() + if err != nil { + return nil, err + } + + // Create symbolic expression + return X.Transpose().Multiply( + L.Transpose(), + ).Plus(C.Transpose()), nil +} diff --git a/problem/optimization_problem.go b/problem/optimization_problem.go index d758057..c464ef4 100644 --- a/problem/optimization_problem.go +++ b/problem/optimization_problem.go @@ -19,7 +19,7 @@ type OptimizationProblem struct { // NewProblem returns a new model with some default arguments such as not to show // the log and no time limit. -func NewProblem(name string) *OptimizationProblem { +func New(name string) *OptimizationProblem { return &OptimizationProblem{Name: name} } @@ -47,7 +47,13 @@ func (m *OptimizationProblem) AddRealVariable() symbolic.Variable { // and upper value limits. This variable is returned. func (m *OptimizationProblem) AddVariableClassic(lower, upper float64, vtype symbolic.VarType) symbolic.Variable { id := uint64(len(m.Variables)) - newVar := symbolic.Variable{id, lower, upper, vtype} + newVar := symbolic.Variable{ + ID: id, + Lower: lower, + Upper: upper, + Type: vtype, + Name: fmt.Sprintf("x_%v", id), + } m.Variables = append(m.Variables, newVar) return newVar } @@ -72,7 +78,7 @@ func (m *OptimizationProblem) AddVariableVector(dim int) symbolic.VariableVector for eltIndex := 0; eltIndex < dim; eltIndex++ { varSlice[eltIndex] = m.AddVariable() } - return symbolic.VariableVector{Elements: varSlice} + return varSlice } /* @@ -87,11 +93,17 @@ func (m *OptimizationProblem) AddVariableVectorClassic( stID := uint64(len(m.Variables)) vs := make([]symbolic.Variable, num) for i := range vs { - vs[i] = symbolic.Variable{stID + uint64(i), lower, upper, vtype} + vs[i] = symbolic.Variable{ + ID: stID + uint64(i), + Lower: lower, + Upper: upper, + Type: vtype, + Name: fmt.Sprintf("x_%v", stID+uint64(i)), + } } m.Variables = append(m.Variables, vs...) - return symbolic.VariableVector{Elements: vs} + return vs } // AddBinaryVariableVector adds a vector of binary variables to the model and @@ -104,14 +116,10 @@ func (m *OptimizationProblem) AddBinaryVariableVector(num int) symbolic.Variable // lower and upper value limits and returns the resulting slice. func (m *OptimizationProblem) AddVariableMatrix( rows, cols int, lower, upper float64, vtype symbolic.VarType, -) [][]symbolic.Variable { - vs := make([][]symbolic.Variable, rows) - for i := range vs { - tempVV := m.AddVariableVectorClassic(cols, lower, upper, vtype) - vs[i] = tempVV.Elements - } - - return vs +) symbolic.VariableMatrix { + // TODO: Add support for adding a variable matrix with a given + // environment as well as upper and lower bounds. + return symbolic.NewVariableMatrix(rows, cols) } // AddBinaryVariableMatrix adds a matrix of binary variables to the model and returns @@ -121,14 +129,14 @@ func (m *OptimizationProblem) AddBinaryVariableMatrix(rows, cols int) [][]symbol } // AddConstr adds the given constraint to the model. -func (m *OptimizationProblem) AddConstraint(constr symbolic.Constraint, errors ...error) error { +func (m *OptimizationProblem) AddConstraint(constr symbolic.Constraint) error { // Constants // Input Processing - err := symbolic.CheckErrors(errors) - if err != nil { - return err - } + //err := constr.Check() + //if err != nil { + // return err + //} // Algorithm m.Constraints = append(m.Constraints, constr) @@ -157,57 +165,55 @@ func (m *OptimizationProblem) SetObjective(e symbolic.Expression, sense ObjSense return nil } -/* -ToScalarConstraint -Description: - - Converts a constraint in the form of a optim.ScalarConstraint - object into a symbolic.ScalarConstraint object. -*/ -//func ToScalarConstraint(inputConstraint optim.ScalarConstraint) (symbolic.ScalarConstraint, error) { -// // Input Processing -// err := inputConstraint.Check() -// if err != nil { -// return symbolic.ScalarConstraint{}, err -// } -// -// // Convert LHS to symbolic expression -// -// switch inputConstraint.Sense() { -// case optim.EQ: -// return ToScalarEq(inputConstraint) -// case optim.LE: -// return ToScalarLessEq(inputConstraint) -// case optim.GE: -// return ToScalarGreaterEq(inputConstraint) -// default: -// return nil, fmt.Errorf("the input constraint sense is not recognized.") -// } -//} - /* ToSymbolicConstraint Description: Converts a constraint in the form of a optim.Constraint object into a symbolic.Constraint object. */ -//func ToSymbolicConstraint(inputConstraint optim.Constraint) (symbolic.Constraint, error) { -// // Input Processing -// err := inputConstraint.Check() -// if err != nil { -// return nil, err -// } -// -// // Algorithm -// switch { -// case inputConstraint.IsScalar(): -// return ToScalarConstraint(inputConstraint) -// case inputConstraint.IsVector(): -// return ToVectorConstraint(inputConstraint) -// default: -// return nil, fmt.Errorf("the input constraint is not recognized as a scalar or vector constraint.") -// } -//} +func ToSymbolicConstraint(inputConstraint optim.Constraint) (symbolic.Constraint, error) { + // Input Processing + + // Input Processing + err := inputConstraint.Check() + if err != nil { + return symbolic.ScalarConstraint{}, err + } + + // Convert LHS to symbolic expression + lhs, err := inputConstraint.Left().ToSymbolic() + if err != nil { + return symbolic.ScalarConstraint{}, err + } + + // Convert RHS to symbolic expression + rhs, err := inputConstraint.Right().ToSymbolic() + if err != nil { + return symbolic.ScalarConstraint{}, err + } + + // Get Sense + sense := inputConstraint.ConstrSense().ToSymbolic() + + // Convert + switch { + case optim.IsScalarExpression(lhs): + return symbolic.ScalarConstraint{ + LeftHandSide: lhs.(symbolic.ScalarExpression), + RightHandSide: rhs.(symbolic.ScalarExpression), + Sense: sense, + }, nil + case optim.IsVectorExpression(lhs): + return symbolic.VectorConstraint{ + LeftHandSide: lhs.(symbolic.VectorExpression), + RightHandSide: rhs.(symbolic.VectorExpression), + Sense: sense, + }, nil + default: + return nil, fmt.Errorf("the left hand side of the input (%T) constraint is not recognized.", lhs) + } + +} /* ToOptimizationProblem @@ -238,6 +244,23 @@ func ToOptimizationProblem(inputModel optim.Model) (*OptimizationProblem, error) // Collect All Constraints from Model and copy them into the new optimization // problem object. + for ii, constraint := range inputModel.Constraints { + newConstraint, err := ToSymbolicConstraint(constraint) + if err != nil { + return nil, fmt.Errorf( + "there was a problem creating the %v-th constraint: %v", + ii, + err, + ) + } + newOptimProblem.Constraints = append(newOptimProblem.Constraints, newConstraint) + } + + // Convert Objective + newOptimProblem.Objective, err = inputModel.Obj.ToSymbolic() + if err != nil { + return nil, err + } // Done return newOptimProblem, nil From 5408fe82c607146568114680e014983596e4826a Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sat, 17 Feb 2024 10:47:43 -0500 Subject: [PATCH 13/25] Added First test for New Problem object + Changed naming of function for creating it --- problem/optimization_problem.go | 2 +- testing/problem/optimization_problem_test.go | 48 ++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 testing/problem/optimization_problem_test.go diff --git a/problem/optimization_problem.go b/problem/optimization_problem.go index c464ef4..9626f32 100644 --- a/problem/optimization_problem.go +++ b/problem/optimization_problem.go @@ -19,7 +19,7 @@ type OptimizationProblem struct { // NewProblem returns a new model with some default arguments such as not to show // the log and no time limit. -func New(name string) *OptimizationProblem { +func NewProblem(name string) *OptimizationProblem { return &OptimizationProblem{Name: name} } diff --git a/testing/problem/optimization_problem_test.go b/testing/problem/optimization_problem_test.go new file mode 100644 index 0000000..cff43b6 --- /dev/null +++ b/testing/problem/optimization_problem_test.go @@ -0,0 +1,48 @@ +package problem_test + +/* +optimization_problem_test.go +Description: + + Tests for all functions and objects defined in the optimization_problem.go file. +*/ + +import ( + "github.com/MatProGo-dev/MatProInterface.go/problem" + "testing" +) + +/* +TestOptimizationProblem_NewProblem1 +Description: + + Tests the NewProblem function with a simple name. + Verifies that the name is set correctly and + that zero variables and constraints exist in the fresh + problem. +*/ +func TestOptimizationProblem_NewProblem1(t *testing.T) { + // Constants + name := "TestProblem" + + // New Problem + problem := problem.NewProblem(name) + + // Check that the name is as expected in the problem. + if problem.Name != name { + t.Errorf("expected the name of the problem to be %v; received %v", + name, problem.Name) + } + + // Check that the number of variables is zero. + if len(problem.Variables) != 0 { + t.Errorf("expected the number of variables to be 0; received %v", + len(problem.Variables)) + } + + // Check that the number of constraints is zero. + if len(problem.Constraints) != 0 { + t.Errorf("expected the number of constraints to be 0; received %v", + len(problem.Constraints)) + } +} From bd7add1a4e71ac4e38b86093665ec9def72a1997 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sun, 18 Feb 2024 21:23:21 -0500 Subject: [PATCH 14/25] Added Tests for Objective Sense Object in Problem package --- testing/problem/objective_sense_test.go | 54 ++++++++++++++++++++ testing/problem/optimization_problem_test.go | 2 +- 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 testing/problem/objective_sense_test.go diff --git a/testing/problem/objective_sense_test.go b/testing/problem/objective_sense_test.go new file mode 100644 index 0000000..ff04bef --- /dev/null +++ b/testing/problem/objective_sense_test.go @@ -0,0 +1,54 @@ +package problem_test + +import ( + "github.com/MatProGo-dev/MatProInterface.go/optim" + "github.com/MatProGo-dev/MatProInterface.go/problem" + "testing" +) + +/* +objective_sense_test.go +Description: + + Tests for the objective sense object. +*/ + +/* +TestObjectiveSense_ToObjSense1 +Description: + + Tests the ToObjSense function with a minimization sense. +*/ +func TestObjectiveSense_ToObjSense1(t *testing.T) { + // Constants + sense := optim.SenseMinimize + + // Algorithm + objSense := problem.ToObjSense(sense) + + // Check that the objective sense is as expected. + if objSense != problem.SenseMinimize { + t.Errorf("expected the objective sense to be %v; received %v", + problem.SenseMinimize, objSense) + } +} + +/* +TestObjectiveSense_ToObjSense2 +Description: + + Tests the ToObjSense function with a maximization sense. +*/ +func TestObjectiveSense_ToObjSense2(t *testing.T) { + // Constants + var sense optim.ObjSense = optim.SenseMaximize + + // Algorithm + objSense := problem.ToObjSense(sense) + + // Check that the objective sense is as expected. + if objSense != problem.SenseMaximize { + t.Errorf("expected the objective sense to be %v; received %v", + problem.SenseMaximize, objSense) + } +} diff --git a/testing/problem/optimization_problem_test.go b/testing/problem/optimization_problem_test.go index cff43b6..c889242 100644 --- a/testing/problem/optimization_problem_test.go +++ b/testing/problem/optimization_problem_test.go @@ -23,7 +23,7 @@ Description: */ func TestOptimizationProblem_NewProblem1(t *testing.T) { // Constants - name := "TestProblem" + name := "TestProblem1" // New Problem problem := problem.NewProblem(name) From 4806a41c869e0256bae556c6a1dae462872a86a9 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sun, 18 Feb 2024 21:31:44 -0500 Subject: [PATCH 15/25] Added a few more tests in the optimization problem --- problem/optimization_problem.go | 4 +- testing/problem/optimization_problem_test.go | 203 +++++++++++++++++++ 2 files changed, 205 insertions(+), 2 deletions(-) diff --git a/problem/optimization_problem.go b/problem/optimization_problem.go index 9626f32..aa95950 100644 --- a/problem/optimization_problem.go +++ b/problem/optimization_problem.go @@ -216,12 +216,12 @@ func ToSymbolicConstraint(inputConstraint optim.Constraint) (symbolic.Constraint } /* -ToOptimizationProblem +From Description: Converts the given input into an optimization problem. */ -func ToOptimizationProblem(inputModel optim.Model) (*OptimizationProblem, error) { +func From(inputModel optim.Model) (*OptimizationProblem, error) { // Create a new optimization problem newOptimProblem := NewProblem(inputModel.Name) diff --git a/testing/problem/optimization_problem_test.go b/testing/problem/optimization_problem_test.go index c889242..9a26f61 100644 --- a/testing/problem/optimization_problem_test.go +++ b/testing/problem/optimization_problem_test.go @@ -8,7 +8,9 @@ Description: */ import ( + "github.com/MatProGo-dev/MatProInterface.go/optim" "github.com/MatProGo-dev/MatProInterface.go/problem" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "testing" ) @@ -46,3 +48,204 @@ func TestOptimizationProblem_NewProblem1(t *testing.T) { len(problem.Constraints)) } } + +/* +TestOptimizationProblem_AddVariable1 +Description: + + Tests the AddVariable function with a simple problem. +*/ +func TestOptimizationProblem_AddVariable1(t *testing.T) { + // Constants + problem := problem.NewProblem("TestProblem1") + + // Algorithm + problem.AddVariable() + + // Check that the number of variables is one. + if len(problem.Variables) != 1 { + t.Errorf("expected the number of variables to be 1; received %v", + len(problem.Variables)) + } + + // Verify that the type of the variable is as expected. + if problem.Variables[0].Type != symbolic.Continuous { + t.Errorf("expected the type of the variable to be %v; received %v", + symbolic.Continuous, problem.Variables[0].Type) + } +} + +/* +TestOptimizationProblem_AddRealVariable1 +Description: + + Tests the AddRealVariable function with a simple problem. +*/ +func TestOptimizationProblem_AddRealVariable1(t *testing.T) { + // Constants + problem := problem.NewProblem("TestProblem1") + + // Algorithm + problem.AddRealVariable() + + // Check that the number of variables is one. + if len(problem.Variables) != 1 { + t.Errorf("expected the number of variables to be 1; received %v", + len(problem.Variables)) + } + + // Verify that the type of the variable is as expected. + if problem.Variables[0].Type != symbolic.Continuous { + t.Errorf("expected the type of the variable to be %v; received %v", + symbolic.Continuous, problem.Variables[0].Type) + } +} + +/* +TestOptimizationProblem_AddVariableClassic1 +Description: + + Tests the AddVariableClassic function with a simple problem. +*/ +func TestOptimizationProblem_AddVariableClassic1(t *testing.T) { + // Constants + problem := problem.NewProblem("TestProblem1") + + // Algorithm + problem.AddVariableClassic(0, 1, symbolic.Binary) + + // Check that the number of variables is one. + if len(problem.Variables) != 1 { + t.Errorf("expected the number of variables to be 1; received %v", + len(problem.Variables)) + } + + // Verify that the type of the variable is as expected. + if problem.Variables[0].Type != symbolic.Binary { + t.Errorf("expected the type of the variable to be %v; received %v", + symbolic.Binary, problem.Variables[0].Type) + } +} + +/* +TestOptimizationProblem_AddBinaryVariable1 +Description: + + Tests the AddBinaryVariable function with a simple problem. +*/ +func TestOptimizationProblem_AddBinaryVariable1(t *testing.T) { + // Constants + problem := problem.NewProblem("TestProblem1") + + // Algorithm + problem.AddBinaryVariable() + + // Check that the number of variables is one. + if len(problem.Variables) != 1 { + t.Errorf("expected the number of variables to be 1; received %v", + len(problem.Variables)) + } + + // Verify that the type of the variable is as expected. + if problem.Variables[0].Type != symbolic.Binary { + t.Errorf("expected the type of the variable to be %v; received %v", + symbolic.Binary, problem.Variables[0].Type) + } +} + +/* +TestOptimizationProblem_AddVariableVector1 +Description: + + Tests the AddVariableVector function with a simple problem. +*/ +func TestOptimizationProblem_AddVariableVector1(t *testing.T) { + // Constants + problem := problem.NewProblem("TestProblem1") + dim := 5 + + // Algorithm + problem.AddVariableVector(dim) + + // Check that the number of variables is as expected. + if len(problem.Variables) != dim { + t.Errorf("expected the number of variables to be %v; received %v", + dim, len(problem.Variables)) + } + + // Verify that the type of the variables is as expected. + for _, v := range problem.Variables { + if v.Type != symbolic.Continuous { + t.Errorf("expected the type of the variable to be %v; received %v", + symbolic.Continuous, v.Type) + } + } +} + +/* +TestOptimizationProblem_AddVariableVectorClassic1 +Description: + + Tests the AddVariableVectorClassic function with a simple problem. +*/ +func TestOptimizationProblem_AddVariableVectorClassic1(t *testing.T) { + // Constants + problem := problem.NewProblem("TestProblem1") + dim := 5 + + // Algorithm + problem.AddVariableVectorClassic(dim, 0, 1, symbolic.Binary) + + // Check that the number of variables is as expected. + if len(problem.Variables) != dim { + t.Errorf("expected the number of variables to be %v; received %v", + dim, len(problem.Variables)) + } + + // Verify that the type of the variables is as expected. + for _, v := range problem.Variables { + if v.Type != symbolic.Binary { + t.Errorf("expected the type of the variable to be %v; received %v", + symbolic.Binary, v.Type) + } + } +} + +/* +TestOptimizationProblem_From1 +Description: + + Tests the From function with a simple + model. +*/ +func TestOptimizationProblem_From1(t *testing.T) { + // Constants + model := optim.NewModel( + "TestOptimizationProblem_From1", + ) + + N := 5 + for ii := 0; ii < N; ii++ { + model.AddVariable() + } + + // Algorithm + problem1, err := problem.From(*model) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // Check that the number of variables is as expected. + if len(problem1.Variables) != 5 { + t.Errorf("expected the number of variables to be %v; received %v", + 5, len(problem1.Variables)) + } + + // Verify that the type of the variables is as expected. + for _, v := range problem1.Variables { + if v.Type != symbolic.Continuous { + t.Errorf("expected the type of the variable to be %v; received %v", + symbolic.Continuous, v.Type) + } + } +} From 91654f2a1982742151c0433155308d79ade2214d Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sun, 18 Feb 2024 21:37:59 -0500 Subject: [PATCH 16/25] Corrected some missing elements of the From method --- problem/optimization_problem.go | 4 ++ testing/problem/optimization_problem_test.go | 45 ++++++++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/problem/optimization_problem.go b/problem/optimization_problem.go index aa95950..d609646 100644 --- a/problem/optimization_problem.go +++ b/problem/optimization_problem.go @@ -231,6 +231,10 @@ func From(inputModel optim.Model) (*OptimizationProblem, error) { return nil, err } + if inputModel.Obj == nil { + return nil, fmt.Errorf("the input model has no objective function!") + } + // Collect All Variables from Model and copy them into the new optimization // problem object. for ii, variable := range inputModel.Variables { diff --git a/testing/problem/optimization_problem_test.go b/testing/problem/optimization_problem_test.go index 9a26f61..cd6e3a2 100644 --- a/testing/problem/optimization_problem_test.go +++ b/testing/problem/optimization_problem_test.go @@ -11,6 +11,7 @@ import ( "github.com/MatProGo-dev/MatProInterface.go/optim" "github.com/MatProGo-dev/MatProInterface.go/problem" "github.com/MatProGo-dev/SymbolicMath.go/symbolic" + "strings" "testing" ) @@ -216,7 +217,7 @@ TestOptimizationProblem_From1 Description: Tests the From function with a simple - model. + model that doesn't have an objective. */ func TestOptimizationProblem_From1(t *testing.T) { // Constants @@ -229,6 +230,41 @@ func TestOptimizationProblem_From1(t *testing.T) { model.AddVariable() } + // Algorithm + _, err := problem.From(*model) + if err == nil { + t.Errorf("expected an error; received nil") + } else { + if !strings.Contains( + err.Error(), + "the input model has no objective function!", + ) { + t.Errorf("unexpected error: %v", err) + } + } +} + +/* +TestOptimizationProblem_From2 +Description: + + Tests the From function with a simple + model that doesn't have an objective. +*/ +func TestOptimizationProblem_From2(t *testing.T) { + // Constants + model := optim.NewModel( + "TestOptimizationProblem_From2", + ) + + N := 5 + var tempVar optim.Variable + for ii := 0; ii < N; ii++ { + tempVar = model.AddVariable() + } + + model.SetObjective(tempVar, optim.SenseMaximize) + // Algorithm problem1, err := problem.From(*model) if err != nil { @@ -244,8 +280,11 @@ func TestOptimizationProblem_From1(t *testing.T) { // Verify that the type of the variables is as expected. for _, v := range problem1.Variables { if v.Type != symbolic.Continuous { - t.Errorf("expected the type of the variable to be %v; received %v", - symbolic.Continuous, v.Type) + t.Errorf( + "expected the type of the variable to be %v; received %v", + symbolic.Continuous, + v.Type, + ) } } } From 15481778984fb274fc271028a0e50e7714441ea5 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sat, 24 Feb 2024 18:24:49 -0500 Subject: [PATCH 17/25] Added Enough Updates from SymbolicMath.go to make the constraint method work --- go.mod | 2 +- go.sum | 4 + problem/optimization_problem.go | 63 ++++--- testing/problem/optimization_problem_test.go | 169 +++++++++++++++++++ 4 files changed, 211 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 493e1e8..428d949 100644 --- a/go.mod +++ b/go.mod @@ -4,4 +4,4 @@ go 1.21 require gonum.org/v1/gonum v0.14.0 -require github.com/MatProGo-dev/SymbolicMath.go v0.1.1 +require github.com/MatProGo-dev/SymbolicMath.go v0.1.3 diff --git a/go.sum b/go.sum index eb12264..66e101f 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,10 @@ github.com/MatProGo-dev/SymbolicMath.go v0.1.0 h1:FUwLQZzZhtgGj6WKyuwQE74P9UYhgb github.com/MatProGo-dev/SymbolicMath.go v0.1.0/go.mod h1:gKbGR/6sYWi2koMUEDIPWBPi6jQPELKle0ijIM+eaHU= github.com/MatProGo-dev/SymbolicMath.go v0.1.1 h1:YigpL7w5D8qLu0xzDAayMXePBh9LK0/w3ykvuoVZwXQ= github.com/MatProGo-dev/SymbolicMath.go v0.1.1/go.mod h1:gKbGR/6sYWi2koMUEDIPWBPi6jQPELKle0ijIM+eaHU= +github.com/MatProGo-dev/SymbolicMath.go v0.1.2 h1:9tYbiHWm0doXOSipd02pxtENqBU8WhmHTrDXetPW+ms= +github.com/MatProGo-dev/SymbolicMath.go v0.1.2/go.mod h1:gKbGR/6sYWi2koMUEDIPWBPi6jQPELKle0ijIM+eaHU= +github.com/MatProGo-dev/SymbolicMath.go v0.1.3 h1:IeofFqvZ/jAO6LywlZR/UMl5t/KOyMAvvctVgTzvgHI= +github.com/MatProGo-dev/SymbolicMath.go v0.1.3/go.mod h1:gKbGR/6sYWi2koMUEDIPWBPi6jQPELKle0ijIM+eaHU= golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= diff --git a/problem/optimization_problem.go b/problem/optimization_problem.go index d609646..1f0efe7 100644 --- a/problem/optimization_problem.go +++ b/problem/optimization_problem.go @@ -29,8 +29,8 @@ Description: This method adds an "unbounded" continuous variable to the model. */ -func (m *OptimizationProblem) AddVariable() symbolic.Variable { - return m.AddRealVariable() +func (op *OptimizationProblem) AddVariable() symbolic.Variable { + return op.AddRealVariable() } /* @@ -39,14 +39,14 @@ Description: Adds a Real variable to the model and returns said variable. */ -func (m *OptimizationProblem) AddRealVariable() symbolic.Variable { - return m.AddVariableClassic(-optim.INFINITY, optim.INFINITY, symbolic.Continuous) +func (op *OptimizationProblem) AddRealVariable() symbolic.Variable { + return op.AddVariableClassic(-optim.INFINITY, optim.INFINITY, symbolic.Continuous) } // AddVariable adds a variable of a given variable type to the model given the lower // and upper value limits. This variable is returned. -func (m *OptimizationProblem) AddVariableClassic(lower, upper float64, vtype symbolic.VarType) symbolic.Variable { - id := uint64(len(m.Variables)) +func (op *OptimizationProblem) AddVariableClassic(lower, upper float64, vtype symbolic.VarType) symbolic.Variable { + id := uint64(len(op.Variables)) newVar := symbolic.Variable{ ID: id, Lower: lower, @@ -54,13 +54,13 @@ func (m *OptimizationProblem) AddVariableClassic(lower, upper float64, vtype sym Type: vtype, Name: fmt.Sprintf("x_%v", id), } - m.Variables = append(m.Variables, newVar) + op.Variables = append(op.Variables, newVar) return newVar } // AddBinaryVar adds a binary variable to the model and returns said variable. -func (m *OptimizationProblem) AddBinaryVariable() symbolic.Variable { - return m.AddVariableClassic(0, 1, symbolic.Binary) +func (op *OptimizationProblem) AddBinaryVariable() symbolic.Variable { + return op.AddVariableClassic(0, 1, symbolic.Binary) } /* @@ -70,13 +70,13 @@ Description: Creates a VarVector object using a constructor that assumes you want an "unbounded" vector of real optimization variables. */ -func (m *OptimizationProblem) AddVariableVector(dim int) symbolic.VariableVector { +func (op *OptimizationProblem) AddVariableVector(dim int) symbolic.VariableVector { // Constants // Algorithm varSlice := make([]symbolic.Variable, dim) for eltIndex := 0; eltIndex < dim; eltIndex++ { - varSlice[eltIndex] = m.AddVariable() + varSlice[eltIndex] = op.AddVariable() } return varSlice } @@ -87,10 +87,10 @@ Description: The classic version of AddVariableVector defined in the original goop. */ -func (m *OptimizationProblem) AddVariableVectorClassic( +func (op *OptimizationProblem) AddVariableVectorClassic( num int, lower, upper float64, vtype symbolic.VarType, ) symbolic.VariableVector { - stID := uint64(len(m.Variables)) + stID := uint64(len(op.Variables)) vs := make([]symbolic.Variable, num) for i := range vs { vs[i] = symbolic.Variable{ @@ -102,34 +102,45 @@ func (m *OptimizationProblem) AddVariableVectorClassic( } } - m.Variables = append(m.Variables, vs...) + op.Variables = append(op.Variables, vs...) return vs } // AddBinaryVariableVector adds a vector of binary variables to the model and // returns the slice. -func (m *OptimizationProblem) AddBinaryVariableVector(num int) symbolic.VariableVector { - return m.AddVariableVectorClassic(num, 0, 1, symbolic.Binary) +func (op *OptimizationProblem) AddBinaryVariableVector(num int) symbolic.VariableVector { + return op.AddVariableVectorClassic(num, 0, 1, symbolic.Binary) } // AddVariableMatrix adds a matrix of variables of a given type to the model with // lower and upper value limits and returns the resulting slice. -func (m *OptimizationProblem) AddVariableMatrix( +func (op *OptimizationProblem) AddVariableMatrix( rows, cols int, lower, upper float64, vtype symbolic.VarType, ) symbolic.VariableMatrix { // TODO: Add support for adding a variable matrix with a given // environment as well as upper and lower bounds. - return symbolic.NewVariableMatrix(rows, cols) + + // Create variables + vmOut := symbolic.NewVariableMatrix(rows, cols) + + // Add variables to the problem + for i := 0; i < rows; i++ { + for j := 0; j < cols; j++ { + op.Variables = append(op.Variables, vmOut[i][j]) + } + } + + return vmOut } // AddBinaryVariableMatrix adds a matrix of binary variables to the model and returns // the resulting slice. -func (m *OptimizationProblem) AddBinaryVariableMatrix(rows, cols int) [][]symbolic.Variable { - return m.AddVariableMatrix(rows, cols, 0, 1, symbolic.Binary) +func (op *OptimizationProblem) AddBinaryVariableMatrix(rows, cols int) [][]symbolic.Variable { + return op.AddVariableMatrix(rows, cols, 0, 1, symbolic.Binary) } // AddConstr adds the given constraint to the model. -func (m *OptimizationProblem) AddConstraint(constr symbolic.Constraint) error { +func (op *OptimizationProblem) AddConstraint(constr symbolic.Constraint) error { // Constants // Input Processing @@ -139,7 +150,7 @@ func (m *OptimizationProblem) AddConstraint(constr symbolic.Constraint) error { //} // Algorithm - m.Constraints = append(m.Constraints, constr) + op.Constraints = append(op.Constraints, constr) return nil } @@ -153,7 +164,7 @@ Notes: is given, even though objectives are normally scalars. */ -func (m *OptimizationProblem) SetObjective(e symbolic.Expression, sense ObjSense) error { +func (op *OptimizationProblem) SetObjective(e symbolic.Expression, sense ObjSense) error { // Input Processing se, err := symbolic.ToScalarExpression(e) if err != nil { @@ -161,7 +172,7 @@ func (m *OptimizationProblem) SetObjective(e symbolic.Expression, sense ObjSense } // Return - m.Objective = NewObjective(se, sense) + op.Objective = NewObjective(se, sense) return nil } @@ -197,13 +208,13 @@ func ToSymbolicConstraint(inputConstraint optim.Constraint) (symbolic.Constraint // Convert switch { - case optim.IsScalarExpression(lhs): + case symbolic.IsScalarExpression(lhs): return symbolic.ScalarConstraint{ LeftHandSide: lhs.(symbolic.ScalarExpression), RightHandSide: rhs.(symbolic.ScalarExpression), Sense: sense, }, nil - case optim.IsVectorExpression(lhs): + case symbolic.IsVectorExpression(lhs): return symbolic.VectorConstraint{ LeftHandSide: lhs.(symbolic.VectorExpression), RightHandSide: rhs.(symbolic.VectorExpression), diff --git a/testing/problem/optimization_problem_test.go b/testing/problem/optimization_problem_test.go index cd6e3a2..94172f0 100644 --- a/testing/problem/optimization_problem_test.go +++ b/testing/problem/optimization_problem_test.go @@ -212,6 +212,111 @@ func TestOptimizationProblem_AddVariableVectorClassic1(t *testing.T) { } } +/* +TestOptimizationProblem_AddBinaryVariableVector1 +Description: + + Tests the AddBinaryVariableVector function with a simple problem. +*/ +func TestOptimizationProblem_AddBinaryVariableVector1(t *testing.T) { + // Constants + problem := problem.NewProblem("TestProblem1") + dim := 5 + + // Algorithm + problem.AddBinaryVariableVector(dim) + + // Check that the number of variables is as expected. + if len(problem.Variables) != dim { + t.Errorf("expected the number of variables to be %v; received %v", + dim, len(problem.Variables)) + } + + // Verify that the type of the variables is as expected. + for _, v := range problem.Variables { + if v.Type != symbolic.Binary { + t.Errorf("expected the type of the variable to be %v; received %v", + symbolic.Binary, v.Type) + } + } +} + +/* +TestOptimizationProblem_AddVariableMatrix1 +Description: + + Tests the AddVariableMatrix function with a simple problem. +*/ +func TestOptimizationProblem_AddVariableMatrix1(t *testing.T) { + // Constants + problem := problem.NewProblem("TestProblem1") + rows := 5 + cols := 5 + + // Algorithm + problem.AddVariableMatrix(rows, cols, 0, 1, symbolic.Binary) + + // Check that the number of variables is as expected. + if len(problem.Variables) != rows*cols { + t.Errorf("expected the number of variables to be %v; received %v", + rows*cols, len(problem.Variables)) + } + + // Verify that the type of the variables is as expected. + for _, v := range problem.Variables { + if v.Type != symbolic.Continuous { + t.Errorf("expected the type of the variable to be %v; received %v", + symbolic.Binary, v.Type) + } + } +} + +/* +TestOptimizationProblem_ToSymbolicConstraint1 +Description: + + Tests the ToSymbolicConstraint function with a simple problem. +*/ +func TestOptimizationProblem_ToSymbolicConstraint1(t *testing.T) { + // Constants + model1 := optim.NewModel("TestModel1") + v1 := model1.AddVariable() + v2 := model1.AddVariable() + v3 := model1.AddVariable() + + // Algorithm + sum, err := v1.Plus(v2) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + constr1, err := sum.LessEq(v3) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + constr1prime, err := problem.ToSymbolicConstraint(constr1) + + // Check that constr1prime is a VectorConstraint + if _, ok := constr1prime.(symbolic.ScalarConstraint); !ok { + t.Errorf("expected the type of constr1prime to be %T; received %T", + symbolic.VectorConstraint{}, constr1prime) + } + +} + +/* +TestOptimizationProblem_ToSymbolicConstraint2 +Description: + + Tests the ToSymbolicConstraint function with a simple problem + that has a vector constraint. This vector constraint + will be a GreaterThanEqual vector constraint between + a vector variable and a scalar variable. +*/ +func TestOptimizationProblem_ToSymbolicConstraint2(t *testing.T) { + +} + /* TestOptimizationProblem_From1 Description: @@ -288,3 +393,67 @@ func TestOptimizationProblem_From2(t *testing.T) { } } } + +/* +TestOptimizationProblem_From3 +Description: + + Tests the From function with a convex optimization + model that has a quadratic objective and + at least two constraints. +*/ +func TestOptimizationProblem_From3(t *testing.T) { + // Constants + model := optim.NewModel( + "TestOptimizationProblem_From3", + ) + + N := 5 + var tempVar optim.Variable + for ii := 0; ii < N; ii++ { + tempVar = model.AddVariable() + } + + // Add a quadratic objective + obj, err := tempVar.Multiply(tempVar) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + model.SetObjective(obj, optim.SenseMaximize) + + // Add a constraint + constr1, err := tempVar.LessEq(1.0) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + model.AddConstraint(constr1) + + // Algorithm + problem1, err := problem.From(*model) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // Check that the number of variables is as expected. + if len(problem1.Variables) != N { + t.Errorf("expected the number of variables to be %v; received %v", + N, len(problem1.Variables)) + } + + // Verify that the type of the variables is as expected. + for _, v := range problem1.Variables { + if v.Type != symbolic.Continuous { + t.Errorf( + "expected the type of the variable to be %v; received %v", + symbolic.Continuous, + v.Type, + ) + } + } + + // Check that the number of constraints is as expected. + if len(problem1.Constraints) != 1 { + t.Errorf("expected the number of constraints to be %v; received %v", + 1, len(problem1.Constraints)) + } +} From 31ca2c2a4390d74ec2aa99189ddf85a0e54eec02 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sun, 25 Feb 2024 16:20:24 -0500 Subject: [PATCH 18/25] Added More Check() values to the ToSymbolic() methods and Tested More Of The OptimizationProblem object --- optim/scalar_linear_expr.go | 6 + optim/scalar_quadratic_expression.go | 7 +- optim/var_vector.go | 6 +- optim/var_vector_transpose.go | 28 ++ optim/vars.go | 6 +- problem/objective_sense.go | 1 + problem/optimization_problem.go | 29 +- testing/problem/optimization_problem_test.go | 365 ++++++++++++++++--- 8 files changed, 375 insertions(+), 73 deletions(-) diff --git a/optim/scalar_linear_expr.go b/optim/scalar_linear_expr.go index a4a902b..6cc63ed 100644 --- a/optim/scalar_linear_expr.go +++ b/optim/scalar_linear_expr.go @@ -431,6 +431,12 @@ Description: symbolic math toolbox). */ func (sle ScalarLinearExpr) ToSymbolic() (symbolic.Expression, error) { + // Check for errors + err := sle.Check() + if err != nil { + return nil, err + } + // Compute product of L and X symX, err := sle.X.ToSymbolic() if err != nil { diff --git a/optim/scalar_quadratic_expression.go b/optim/scalar_quadratic_expression.go index 05d7058..a11156b 100644 --- a/optim/scalar_quadratic_expression.go +++ b/optim/scalar_quadratic_expression.go @@ -480,8 +480,11 @@ Description: (i.e., one that uses the symbolic math toolbox). */ func (qe ScalarQuadraticExpression) ToSymbolic() (symbolic.Expression, error) { - // Constants - var err error + // Input Checking + err := qe.Check() + if err != nil { + return nil, err + } // Convert Q, L and C to symbolic symQ := symbolic.DenseToKMatrix(qe.Q) diff --git a/optim/var_vector.go b/optim/var_vector.go index e83fec0..56968cd 100644 --- a/optim/var_vector.go +++ b/optim/var_vector.go @@ -442,7 +442,11 @@ Description: (i.e., one that uses the symbolic math toolbox). */ func (vv VarVector) ToSymbolic() (symbolic.Expression, error) { - // Constants + // Input Checking + err := vv.Check() + if err != nil { + return nil, err + } // Algorithm // Create the symbolic vector diff --git a/optim/var_vector_transpose.go b/optim/var_vector_transpose.go index a1da50b..8d1faf6 100644 --- a/optim/var_vector_transpose.go +++ b/optim/var_vector_transpose.go @@ -469,6 +469,28 @@ func (vvt VarVectorTranspose) Dims() []int { return []int{1, vvt.Len()} } +/* +Check +Description: + + Checks whether or not the VarVector has a sensible initialization. +*/ +func (vvt VarVectorTranspose) Check() error { + // Check that each variable is properly defined + for ii, element := range vvt.Elements { + err := element.Check() + if err != nil { + return fmt.Errorf( + "element %v has an issue: %v", + ii, err, + ) + } + } + + // If nothing was thrown, then return nil! + return nil +} + /* ToSymbolic Description: @@ -477,6 +499,12 @@ Description: (i.e., an expression made using SymbolicMath.go). */ func (vvt VarVectorTranspose) ToSymbolic() (symbolic.Expression, error) { + // Input Processing + err := vvt.Check() + if err != nil { + return nil, err + } + // Constants vm := symbolic.VariableMatrix{} diff --git a/optim/vars.go b/optim/vars.go index 94175b8..5a79272 100644 --- a/optim/vars.go +++ b/optim/vars.go @@ -421,7 +421,11 @@ Description: (from the symbolic math toolbox). */ func (v Variable) ToSymbolic() (symbolic.Expression, error) { - // Constants + // Input Checking + err := v.Check() + if err != nil { + return nil, err + } // Create base variable and fill its elements symVar := symbolic.Variable{ diff --git a/problem/objective_sense.go b/problem/objective_sense.go index c7ee21d..bc4a825 100644 --- a/problem/objective_sense.go +++ b/problem/objective_sense.go @@ -10,6 +10,7 @@ type ObjSense int const ( SenseMinimize ObjSense = 1 SenseMaximize = -1 + SenseFind ObjSense = 0 ) /* diff --git a/problem/optimization_problem.go b/problem/optimization_problem.go index 1f0efe7..8a4f24e 100644 --- a/problem/optimization_problem.go +++ b/problem/optimization_problem.go @@ -14,7 +14,7 @@ type OptimizationProblem struct { Name string Variables []symbolic.Variable Constraints []symbolic.Constraint - Objective symbolic.Expression + Objective Objective } // NewProblem returns a new model with some default arguments such as not to show @@ -139,21 +139,6 @@ func (op *OptimizationProblem) AddBinaryVariableMatrix(rows, cols int) [][]symbo return op.AddVariableMatrix(rows, cols, 0, 1, symbolic.Binary) } -// AddConstr adds the given constraint to the model. -func (op *OptimizationProblem) AddConstraint(constr symbolic.Constraint) error { - // Constants - - // Input Processing - //err := constr.Check() - //if err != nil { - // return err - //} - - // Algorithm - op.Constraints = append(op.Constraints, constr) - return nil -} - /* SetObjective Description: @@ -172,7 +157,7 @@ func (op *OptimizationProblem) SetObjective(e symbolic.Expression, sense ObjSens } // Return - op.Objective = NewObjective(se, sense) + op.Objective = *NewObjective(se, sense) return nil } @@ -272,7 +257,15 @@ func From(inputModel optim.Model) (*OptimizationProblem, error) { } // Convert Objective - newOptimProblem.Objective, err = inputModel.Obj.ToSymbolic() + objectiveExpr, err := inputModel.Obj.ToSymbolic() + if err != nil { + return nil, err + } + + err = newOptimProblem.SetObjective( + objectiveExpr, + ObjSense(inputModel.Obj.Sense), + ) if err != nil { return nil, err } diff --git a/testing/problem/optimization_problem_test.go b/testing/problem/optimization_problem_test.go index 94172f0..04987a6 100644 --- a/testing/problem/optimization_problem_test.go +++ b/testing/problem/optimization_problem_test.go @@ -11,6 +11,7 @@ import ( "github.com/MatProGo-dev/MatProInterface.go/optim" "github.com/MatProGo-dev/MatProInterface.go/problem" "github.com/MatProGo-dev/SymbolicMath.go/symbolic" + "gonum.org/v1/gonum/mat" "strings" "testing" ) @@ -29,24 +30,24 @@ func TestOptimizationProblem_NewProblem1(t *testing.T) { name := "TestProblem1" // New Problem - problem := problem.NewProblem(name) + p1 := problem.NewProblem(name) // Check that the name is as expected in the problem. - if problem.Name != name { + if p1.Name != name { t.Errorf("expected the name of the problem to be %v; received %v", - name, problem.Name) + name, p1.Name) } // Check that the number of variables is zero. - if len(problem.Variables) != 0 { + if len(p1.Variables) != 0 { t.Errorf("expected the number of variables to be 0; received %v", - len(problem.Variables)) + len(p1.Variables)) } // Check that the number of constraints is zero. - if len(problem.Constraints) != 0 { + if len(p1.Constraints) != 0 { t.Errorf("expected the number of constraints to be 0; received %v", - len(problem.Constraints)) + len(p1.Constraints)) } } @@ -58,21 +59,21 @@ Description: */ func TestOptimizationProblem_AddVariable1(t *testing.T) { // Constants - problem := problem.NewProblem("TestProblem1") + p1 := problem.NewProblem("TestProblem1") // Algorithm - problem.AddVariable() + p1.AddVariable() // Check that the number of variables is one. - if len(problem.Variables) != 1 { + if len(p1.Variables) != 1 { t.Errorf("expected the number of variables to be 1; received %v", - len(problem.Variables)) + len(p1.Variables)) } // Verify that the type of the variable is as expected. - if problem.Variables[0].Type != symbolic.Continuous { + if p1.Variables[0].Type != symbolic.Continuous { t.Errorf("expected the type of the variable to be %v; received %v", - symbolic.Continuous, problem.Variables[0].Type) + symbolic.Continuous, p1.Variables[0].Type) } } @@ -84,21 +85,21 @@ Description: */ func TestOptimizationProblem_AddRealVariable1(t *testing.T) { // Constants - problem := problem.NewProblem("TestProblem1") + p1 := problem.NewProblem("TestProblem1") // Algorithm - problem.AddRealVariable() + p1.AddRealVariable() // Check that the number of variables is one. - if len(problem.Variables) != 1 { + if len(p1.Variables) != 1 { t.Errorf("expected the number of variables to be 1; received %v", - len(problem.Variables)) + len(p1.Variables)) } // Verify that the type of the variable is as expected. - if problem.Variables[0].Type != symbolic.Continuous { + if p1.Variables[0].Type != symbolic.Continuous { t.Errorf("expected the type of the variable to be %v; received %v", - symbolic.Continuous, problem.Variables[0].Type) + symbolic.Continuous, p1.Variables[0].Type) } } @@ -110,21 +111,21 @@ Description: */ func TestOptimizationProblem_AddVariableClassic1(t *testing.T) { // Constants - problem := problem.NewProblem("TestProblem1") + p1 := problem.NewProblem("TestProblem1") // Algorithm - problem.AddVariableClassic(0, 1, symbolic.Binary) + p1.AddVariableClassic(0, 1, symbolic.Binary) // Check that the number of variables is one. - if len(problem.Variables) != 1 { + if len(p1.Variables) != 1 { t.Errorf("expected the number of variables to be 1; received %v", - len(problem.Variables)) + len(p1.Variables)) } // Verify that the type of the variable is as expected. - if problem.Variables[0].Type != symbolic.Binary { + if p1.Variables[0].Type != symbolic.Binary { t.Errorf("expected the type of the variable to be %v; received %v", - symbolic.Binary, problem.Variables[0].Type) + symbolic.Binary, p1.Variables[0].Type) } } @@ -136,21 +137,21 @@ Description: */ func TestOptimizationProblem_AddBinaryVariable1(t *testing.T) { // Constants - problem := problem.NewProblem("TestProblem1") + p1 := problem.NewProblem("TestProblem1") // Algorithm - problem.AddBinaryVariable() + p1.AddBinaryVariable() // Check that the number of variables is one. - if len(problem.Variables) != 1 { + if len(p1.Variables) != 1 { t.Errorf("expected the number of variables to be 1; received %v", - len(problem.Variables)) + len(p1.Variables)) } // Verify that the type of the variable is as expected. - if problem.Variables[0].Type != symbolic.Binary { + if p1.Variables[0].Type != symbolic.Binary { t.Errorf("expected the type of the variable to be %v; received %v", - symbolic.Binary, problem.Variables[0].Type) + symbolic.Binary, p1.Variables[0].Type) } } @@ -162,20 +163,20 @@ Description: */ func TestOptimizationProblem_AddVariableVector1(t *testing.T) { // Constants - problem := problem.NewProblem("TestProblem1") + p1 := problem.NewProblem("TestProblem1") dim := 5 // Algorithm - problem.AddVariableVector(dim) + p1.AddVariableVector(dim) // Check that the number of variables is as expected. - if len(problem.Variables) != dim { + if len(p1.Variables) != dim { t.Errorf("expected the number of variables to be %v; received %v", - dim, len(problem.Variables)) + dim, len(p1.Variables)) } // Verify that the type of the variables is as expected. - for _, v := range problem.Variables { + for _, v := range p1.Variables { if v.Type != symbolic.Continuous { t.Errorf("expected the type of the variable to be %v; received %v", symbolic.Continuous, v.Type) @@ -191,20 +192,20 @@ Description: */ func TestOptimizationProblem_AddVariableVectorClassic1(t *testing.T) { // Constants - problem := problem.NewProblem("TestProblem1") + p1 := problem.NewProblem("TestProblem1") dim := 5 // Algorithm - problem.AddVariableVectorClassic(dim, 0, 1, symbolic.Binary) + p1.AddVariableVectorClassic(dim, 0, 1, symbolic.Binary) // Check that the number of variables is as expected. - if len(problem.Variables) != dim { + if len(p1.Variables) != dim { t.Errorf("expected the number of variables to be %v; received %v", - dim, len(problem.Variables)) + dim, len(p1.Variables)) } // Verify that the type of the variables is as expected. - for _, v := range problem.Variables { + for _, v := range p1.Variables { if v.Type != symbolic.Binary { t.Errorf("expected the type of the variable to be %v; received %v", symbolic.Binary, v.Type) @@ -220,20 +221,20 @@ Description: */ func TestOptimizationProblem_AddBinaryVariableVector1(t *testing.T) { // Constants - problem := problem.NewProblem("TestProblem1") + p1 := problem.NewProblem("TestProblem1") dim := 5 // Algorithm - problem.AddBinaryVariableVector(dim) + p1.AddBinaryVariableVector(dim) // Check that the number of variables is as expected. - if len(problem.Variables) != dim { + if len(p1.Variables) != dim { t.Errorf("expected the number of variables to be %v; received %v", - dim, len(problem.Variables)) + dim, len(p1.Variables)) } // Verify that the type of the variables is as expected. - for _, v := range problem.Variables { + for _, v := range p1.Variables { if v.Type != symbolic.Binary { t.Errorf("expected the type of the variable to be %v; received %v", symbolic.Binary, v.Type) @@ -249,21 +250,21 @@ Description: */ func TestOptimizationProblem_AddVariableMatrix1(t *testing.T) { // Constants - problem := problem.NewProblem("TestProblem1") + p1 := problem.NewProblem("TestProblem1") rows := 5 cols := 5 // Algorithm - problem.AddVariableMatrix(rows, cols, 0, 1, symbolic.Binary) + p1.AddVariableMatrix(rows, cols, 0, 1, symbolic.Binary) // Check that the number of variables is as expected. - if len(problem.Variables) != rows*cols { + if len(p1.Variables) != rows*cols { t.Errorf("expected the number of variables to be %v; received %v", - rows*cols, len(problem.Variables)) + rows*cols, len(p1.Variables)) } // Verify that the type of the variables is as expected. - for _, v := range problem.Variables { + for _, v := range p1.Variables { if v.Type != symbolic.Continuous { t.Errorf("expected the type of the variable to be %v; received %v", symbolic.Binary, v.Type) @@ -271,6 +272,31 @@ func TestOptimizationProblem_AddVariableMatrix1(t *testing.T) { } } +/* +TestOptimizationProblem_SetObjective1 +Description: + + Tests the SetObjective function with a simple linear objective. +*/ +func TestOptimizationProblem_SetObjective1(t *testing.T) { + // Constants + p1 := problem.NewProblem("TestOptimizationProblem_SetObjective1") + v1 := p1.AddVariable() + v2 := p1.AddVariable() + + // Algorithm + err := p1.SetObjective(v1.Plus(v2), problem.SenseMaximize) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // Check that the objective is as expected. + if p1.Objective.Sense != problem.SenseMaximize { + t.Errorf("expected the sense of the objective to be %v; received %v", + problem.SenseMaximize, p1.Objective.Sense) + } +} + /* TestOptimizationProblem_ToSymbolicConstraint1 Description: @@ -457,3 +483,240 @@ func TestOptimizationProblem_From3(t *testing.T) { 1, len(problem1.Constraints)) } } + +/* +TestOptimizationProblem_From4 +Description: + + Tests the From function with a convex optimization + problem that has a linear objective and + a vector inequality constraint. +*/ +func TestOptimizationProblem_From4(t *testing.T) { + // Constants + model := optim.NewModel( + "TestOptimizationProblem_From4", + ) + + N := 5 + vv1 := model.AddVariableVector(N) + vle2 := optim.VectorLinearExpr{ + L: *mat.NewDense(2, N, []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}), + X: vv1, + C: *mat.NewVecDense(2, []float64{21, 22}), + } + + // Add a linear objective + obj, err := vv1.Elements[0].Plus(vv1.Elements[2]) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + model.SetObjective(obj, optim.SenseMaximize) + + // Add a vector constraint + constr1, err := vle2.LessEq(*mat.NewVecDense(2, []float64{11, 12})) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + model.AddConstraint(constr1) + + // Algorithm + problem1, err := problem.From(*model) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // Check that the number of variables is as expected. + if len(problem1.Variables) != N { + t.Errorf("expected the number of variables to be %v; received %v", + N, len(problem1.Variables)) + } + + // Verify that the type of the variables is as expected. + for _, v := range problem1.Variables { + if v.Type != symbolic.Continuous { + t.Errorf( + "expected the type of the variable to be %v; received %v", + symbolic.Continuous, + v.Type, + ) + } + } + + // Check that the number of constraints is as expected. + if len(problem1.Constraints) != 1 { + t.Errorf("expected the number of constraints to be %v; received %v", + 1, len(problem1.Constraints)) + } +} + +/* +TestOptimizationProblem_From5 +Description: + + Tests the From function with a convex optimization + problem that has a linear objective and + two vector inequality constraints. +*/ +func TestOptimizationProblem_From5(t *testing.T) { + // Constants + model := optim.NewModel( + "TestOptimizationProblem_From5", + ) + + N := 5 + vv1 := model.AddVariableVector(N) + vle2 := optim.VectorLinearExpr{ + L: *mat.NewDense(2, N, []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}), + X: vv1, + C: *mat.NewVecDense(2, []float64{21, 22}), + } + + vle3 := optim.VectorLinearExpr{ + L: *mat.NewDense(2, N, []float64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}), + X: vv1, + C: *mat.NewVecDense(2, []float64{31, 32}), + } + + // Add a linear objective + obj, err := vv1.Elements[0].Plus(vv1.Elements[2]) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + model.SetObjective(obj, optim.SenseMaximize) + + // Add a vector constraint + constr1, err := vle2.LessEq(*mat.NewVecDense(2, []float64{11, 12})) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + model.AddConstraint(constr1) + + // Add another vector constraint + constr2, err := vle3.LessEq(*mat.NewVecDense(2, []float64{41, 42})) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + model.AddConstraint(constr2) + + // Algorithm + problem1, err := problem.From(*model) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // Check that the number of variables is as expected. + if len(problem1.Variables) != N { + t.Errorf("expected the number of variables to be %v; received %v", + N, len(problem1.Variables)) + } + + // Verify that the type of the variables is as expected. + for _, v := range problem1.Variables { + if v.Type != symbolic.Continuous { + t.Errorf( + "expected the type of the variable to be %v; received %v", + symbolic.Continuous, + v.Type, + ) + } + } +} + +/* +TestOptimizationProblem_From6 +Description: + + Tests the From function properly produces an error + when the input model is not well-defined. +*/ +func TestOptimizationProblem_From6(t *testing.T) { + // Constants + model := optim.NewModel( + "TestOptimizationProblem_From6", + ) + + // Algorithm + _, err := problem.From(*model) + if err == nil { + t.Errorf("unexpected error: %v", err) + } else { + if !strings.Contains( + err.Error(), + "the model has no variables!", + ) { + t.Errorf("unexpected error: %v", err) + } + } +} + +/* +TestOptimizationProblem_From7 +Description: + + Tests the From function properly produces an error + when the input model has an improperly defined objective. +*/ +func TestOptimizationProblem_From7(t *testing.T) { + // Constants + model := optim.NewModel( + "TestOptimizationProblem_From7", + ) + + // Add a variable + model.AddVariable() + + // Algorithm + _, err := problem.From(*model) + if err == nil { + t.Errorf("unexpected error: %v", err) + } else { + if !strings.Contains( + err.Error(), + "the input model has no objective function!", + ) { + t.Errorf("unexpected error: %v", err) + } + } +} + +/* +TestOptimizationProblem_From8 +Description: + + Tests the From function properly produces an error + when the input model has an objective function that + is not well-defined. +*/ +func TestOptimizationProblem_From8(t *testing.T) { + // Constants + model := optim.NewModel( + "TestOptimizationProblem_From8", + ) + + // Add a variable + v1 := model.AddVariable() + + // Add an objective + model.SetObjective( + optim.ScalarLinearExpr{ + L: *mat.NewVecDense(2, []float64{1, 2}), + X: optim.VarVector{Elements: []optim.Variable{v1}}, + C: 1.2, + }, + optim.SenseMaximize, + ) + + // Algorithm + _, err := problem.From(*model) + if err == nil { + t.Errorf("expected an error, received none!") + } else { + if !strings.Contains( + err.Error(), + "the length of L", + ) { + t.Errorf("unexpected error: %v", err) + } + } +} From c737127ecc314f9e50510b63403e8f3f1c064d26 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Wed, 28 Feb 2024 13:08:51 -0500 Subject: [PATCH 19/25] Updated SymbolicMath.go to make tests on problem.From() works --- go.mod | 2 +- go.sum | 2 ++ optim/scalar_quadratic_expression.go | 3 +++ optim/vars.go | 1 + testing/problem/optimization_problem_test.go | 6 +++++- 5 files changed, 12 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 428d949..76a33a2 100644 --- a/go.mod +++ b/go.mod @@ -4,4 +4,4 @@ go 1.21 require gonum.org/v1/gonum v0.14.0 -require github.com/MatProGo-dev/SymbolicMath.go v0.1.3 +require github.com/MatProGo-dev/SymbolicMath.go v0.1.4 diff --git a/go.sum b/go.sum index 66e101f..51762e4 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/MatProGo-dev/SymbolicMath.go v0.1.2 h1:9tYbiHWm0doXOSipd02pxtENqBU8Wh github.com/MatProGo-dev/SymbolicMath.go v0.1.2/go.mod h1:gKbGR/6sYWi2koMUEDIPWBPi6jQPELKle0ijIM+eaHU= github.com/MatProGo-dev/SymbolicMath.go v0.1.3 h1:IeofFqvZ/jAO6LywlZR/UMl5t/KOyMAvvctVgTzvgHI= github.com/MatProGo-dev/SymbolicMath.go v0.1.3/go.mod h1:gKbGR/6sYWi2koMUEDIPWBPi6jQPELKle0ijIM+eaHU= +github.com/MatProGo-dev/SymbolicMath.go v0.1.4 h1:6DaDRYoANmKMkZL0uSUSx7Ibx8Hc9S6AX+7L0hIy1A4= +github.com/MatProGo-dev/SymbolicMath.go v0.1.4/go.mod h1:gKbGR/6sYWi2koMUEDIPWBPi6jQPELKle0ijIM+eaHU= golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= diff --git a/optim/scalar_quadratic_expression.go b/optim/scalar_quadratic_expression.go index a11156b..c4d5754 100644 --- a/optim/scalar_quadratic_expression.go +++ b/optim/scalar_quadratic_expression.go @@ -504,7 +504,10 @@ func (qe ScalarQuadraticExpression) ToSymbolic() (symbolic.Expression, error) { // Perform Multplications in Symbolic quadraticTerm := symX.Transpose().Multiply(symQ).Multiply(symX) + fmt.Println(quadraticTerm) + fmt.Println(symX.Transpose().Multiply(symQ)) linearTerm := symL.Transpose().Multiply(symX) + fmt.Println(linearTerm) // Sum all terms together and return it return quadraticTerm.Plus(linearTerm).Plus(symC), nil diff --git a/optim/vars.go b/optim/vars.go index 5a79272..a008cad 100644 --- a/optim/vars.go +++ b/optim/vars.go @@ -433,6 +433,7 @@ func (v Variable) ToSymbolic() (symbolic.Expression, error) { Lower: v.Lower, Upper: v.Upper, Type: symbolic.VarType(v.Vtype), + Name: fmt.Sprintf("x_{%v}", v.ID), } // Algorithm diff --git a/testing/problem/optimization_problem_test.go b/testing/problem/optimization_problem_test.go index 04987a6..6f055c3 100644 --- a/testing/problem/optimization_problem_test.go +++ b/testing/problem/optimization_problem_test.go @@ -445,7 +445,11 @@ func TestOptimizationProblem_From3(t *testing.T) { if err != nil { t.Errorf("unexpected error: %v", err) } - model.SetObjective(obj, optim.SenseMaximize) + + err = model.SetObjective(obj, optim.SenseMaximize) + if err != nil { + t.Errorf("error while setting objective! %v", err) + } // Add a constraint constr1, err := tempVar.LessEq(1.0) From 9184377b7c1ca6aa2956b8e74193aa758def3c0e Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Wed, 28 Feb 2024 13:18:59 -0500 Subject: [PATCH 20/25] Added More Tests for VarVectorTranspose --- testing/optim/var_vector_transpose_test.go | 124 +++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/testing/optim/var_vector_transpose_test.go b/testing/optim/var_vector_transpose_test.go index 157058b..63f845b 100644 --- a/testing/optim/var_vector_transpose_test.go +++ b/testing/optim/var_vector_transpose_test.go @@ -1907,3 +1907,127 @@ func TestVarVectorTranspose_Multiply14(t *testing.T) { } } + +/* +TestVarVectorTranspose_Check1 +Description: + + Tests the Check method for a VarVectorTranspose. + When there is an incorrectly initialized variable in one of the elements, + then this should throw an error. +*/ +func TestVarVectorTranspose_Check1(t *testing.T) { + // Constants + m := optim.NewModel("VarVectorTranspose_Check1") + N := 4 + + // Create VarVector + vv0 := optim.VarVectorTranspose{} + for ii := 0; ii < N; ii++ { + if ii == 2 { + vv0.Elements = append(vv0.Elements, optim.Variable{Lower: -1.0, Upper: -2.0}) + } else { + vv0.Elements = append(vv0.Elements, m.AddVariable()) + } + } + + // Check + err := vv0.Check() + if err == nil { + t.Errorf("No error was thrown, but we expected one!") + } else { + if !strings.Contains( + err.Error(), + "element 2 has an issue:", + ) { + t.Errorf("Unexpected error: %v", err) + } + } + +} + +/* +TestVarVectorTranspose_Check2 +Description: + + Tests the Check method for a VarVectorTranspose. + For a properly initialized VarVectorTranspose, this should not throw an error. +*/ +func TestVarVectorTranspose_Check2(t *testing.T) { + // Constants + m := optim.NewModel("VarVectorTranspose_Check2") + N := 4 + + // Create VarVector + vvt0 := m.AddVariableVector(N).Transpose().(optim.VarVectorTranspose) + + // Check + err := vvt0.Check() + if err != nil { + t.Errorf("Unexpected error: %v", err) + } +} + +/* +TestVarVectorTranspose_ToSymbolic1 +Description: + + Tests the ToSymbolic method for a VarVectorTranspose + that is not well-defined. This should throw an error. +*/ +func TestVarVectorTranspose_ToSymbolic1(t *testing.T) { + // Constants + m := optim.NewModel("VarVectorTranspose_ToSymbolic1") + N := 4 + + // Create VarVector + vv0 := optim.VarVectorTranspose{} + for ii := 0; ii < N; ii++ { + if ii == 2 { + vv0.Elements = append(vv0.Elements, optim.Variable{Lower: -1.0, Upper: -2.0}) + } else { + vv0.Elements = append(vv0.Elements, m.AddVariable()) + } + } + + // Check + _, err := vv0.ToSymbolic() + if err == nil { + t.Errorf("No error was thrown, but we expected one!") + } else { + if !strings.Contains( + err.Error(), + "element 2 has an issue:", + ) { + t.Errorf("Unexpected error: %v", err) + } + } +} + +/* +TestVarVectorTranspose_ToSymbolic2 +Description: + + Tests the ToSymbolic method for a VarVectorTranspose + that is well-defined. The result should not produce + an error and should be of the type symbolic.VariableMatrix. +*/ +func TestVarVectorTranspose_ToSymbolic2(t *testing.T) { + // Constants + m := optim.NewModel("VarVectorTranspose_ToSymbolic2") + N := 4 + + // Create VarVector + vvt0 := m.AddVariableVector(N).Transpose().(optim.VarVectorTranspose) + + // Check + sym1, err := vvt0.ToSymbolic() + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + _, tf := sym1.(symbolic.VariableMatrix) + if !tf { + t.Errorf("Expected type symbolic.VariableMatrix; received type %T", sym1) + } +} From 7d2b6b27e42b71a2899435a641450a4443e23d09 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Wed, 28 Feb 2024 13:22:53 -0500 Subject: [PATCH 21/25] Added More Tests for VectorLinearExpressionTranspose --- ...vector_linear_expression_transpose_test.go | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/testing/optim/vector_linear_expression_transpose_test.go b/testing/optim/vector_linear_expression_transpose_test.go index 1e3a8ec..0ea12b7 100644 --- a/testing/optim/vector_linear_expression_transpose_test.go +++ b/testing/optim/vector_linear_expression_transpose_test.go @@ -3,6 +3,7 @@ package optim_test import ( "fmt" "github.com/MatProGo-dev/MatProInterface.go/optim" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "gonum.org/v1/gonum/mat" "strings" "testing" @@ -2035,3 +2036,85 @@ func TestVectorLinearExpressionTranspose_ToScalarExpression4(t *testing.T) { } } + +/* +TestVectorLinearExpressionTranspose_ToSymbolic1 +Description: + + ToSymbolic() should throw an error when the vlet1 + is not well-defined. +*/ +func TestVectorLinearExpressionTranspose_ToSymbolic1(t *testing.T) { + // Constants + n := 5 + m := optim.NewModel("VLET ToSymbolic 1") + + // Create arguments + C2 := optim.OnesVector(n - 1) + C2.SetVec(0, 2.71) + vlet1 := optim.VectorLinearExpressionTranspose{ + L: optim.Identity(n), + X: m.AddVariableVector(n), + C: C2, + } + + // Try to run ToSymbolic + _, err := vlet1.ToSymbolic() + if err == nil { + t.Errorf("there was no error thrown, when there should have been.") + } else { + nL, mL := vlet1.L.Dims() + if !strings.Contains( + err.Error(), + fmt.Sprintf( + "Dimension of L (%v x %v) and C (length %v) do not match!", + nL, mL, + vlet1.C.Len(), + ), + ) { + t.Errorf("unexpected error: %v", err) + } + } +} + +/* +TestVectorLinearExpressionTranspose_ToSymbolic2 +Description: + + ToSymbolic() should produce a VectorPolynomialExpression + when the vlet1 is well-defined. +*/ +func TestVectorLinearExpressionTranspose_ToSymbolic2(t *testing.T) { + // Constants + n := 5 + m := optim.NewModel("VLET ToSymbolic 2") + + // Create arguments + C2 := optim.OnesVector(n) + C2.SetVec(0, 2.71) + vlet1 := optim.VectorLinearExpressionTranspose{ + L: optim.Identity(n), + X: m.AddVariableVector(n), + C: C2, + } + + // Try to run ToSymbolic + vpe, err := vlet1.ToSymbolic() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if _, ok := vpe.(symbolic.PolynomialMatrix); !ok { + t.Errorf("expected for the output to be a VectorPolynomialExpression; received %T", vpe) + } + + // Check the dimensions of vpe + nR, nC := vpe.Dims()[0], vpe.Dims()[1] + if nR != 1 { + t.Errorf("expected for the number of rows to be 1; received %v", nR) + } + + if nC != n { + t.Errorf("expected for the number of columns to be %v; received %v", n, nC) + } +} From 6ebb81ba82bff63d07e9dee78b4a18bb80f0d3ec Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Wed, 28 Feb 2024 13:32:09 -0500 Subject: [PATCH 22/25] Simplified VarVector.ToSymbolic and tested it --- optim/var_vector.go | 16 +------- testing/optim/var_vector_test.go | 63 ++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/optim/var_vector.go b/optim/var_vector.go index 56968cd..2cc326e 100644 --- a/optim/var_vector.go +++ b/optim/var_vector.go @@ -454,20 +454,8 @@ func (vv VarVector) ToSymbolic() (symbolic.Expression, error) { // Add each variable to the vector for _, elt := range vv.Elements { - eltAsSymExpr, err := elt.ToSymbolic() - if err != nil { - return nil, fmt.Errorf( - "could not convert variable %v to symbolic variable", - elt, - ) - } - eltAsSymVar, ok := eltAsSymExpr.(symbolic.Variable) - if !ok { - return nil, fmt.Errorf( - "could not convert variable %v to symbolic variable", - elt, - ) - } + eltAsSymExpr, _ := elt.ToSymbolic() // No errors should occur because elts were checked above + eltAsSymVar, _ := eltAsSymExpr.(symbolic.Variable) // No errors should occur because elt must be a variable symVVec = append(symVVec, eltAsSymVar) } diff --git a/testing/optim/var_vector_test.go b/testing/optim/var_vector_test.go index 9d9f472..7bb93d4 100644 --- a/testing/optim/var_vector_test.go +++ b/testing/optim/var_vector_test.go @@ -3,6 +3,7 @@ package optim_test import ( "fmt" "github.com/MatProGo-dev/MatProInterface.go/optim" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "gonum.org/v1/gonum/mat" "strings" "testing" @@ -1478,3 +1479,65 @@ func TestVarVector_Multiply8(t *testing.T) { } } + +/* +TestVarVector_ToSymbolic1 +Description: + + Tests that the ToSymbolic() produces an error when the VarVector has one + variable that is not well-defined. +*/ +func TestVarVector_ToSymbolic1(t *testing.T) { + // Constants + m := optim.NewModel("TestVarVector_ToSymbolic1") + v1 := m.AddVariable() + + vv2 := optim.VarVector{} + for i := 0; i < 10; i++ { + if i == 5 { + vv2.Elements = append(vv2.Elements, optim.Variable{Lower: -1, Upper: -2}) + } else { + vv2.Elements = append(vv2.Elements, v1) + } + } + + // Run ToSymbolic + _, err := vv2.ToSymbolic() + if err == nil { + t.Errorf("expected error, but received none!") + } else { + if !strings.Contains( + err.Error(), + fmt.Sprintf("element %v has an issue: %v", 5, "lower bound (-1) of variable is above upper bound (-2)."), + ) { + t.Errorf("unexpected error: %v", err) + } + } + +} + +/* +TestVarVector_ToSymbolic2 +Description: + + Tests that the ToSymbolic() does not produce an error when the VarVector + is well-defined. In addition, the output should be of type symbolic.VariableVector +*/ +func TestVarVector_ToSymbolic2(t *testing.T) { + // Constants + m := optim.NewModel("TestVarVector_ToSymbolic2") + N := 10 + + vv2 := m.AddVariableVector(N) + + // Run ToSymbolic + symVv2, err := vv2.ToSymbolic() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + _, tf := symVv2.(symbolic.VariableVector) + if !tf { + t.Errorf("expected output to be of type symbolic.VariableVector; received %T instead", symVv2) + } +} From 1b7c77f9e32d34b1501e71cb2d15edb943564912 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Wed, 28 Feb 2024 13:54:38 -0500 Subject: [PATCH 23/25] Added More Tests for OptimizationProblem.From which required standardizing a Check() method for all constraints and adding Check() methods for a few expressions in the optim package --- optim/constr_sense.go | 18 ++ optim/scalar_constraint.go | 12 +- optim/scalar_expression.go | 3 + optim/var_vector.go | 9 +- optim/vector_constant.go | 11 + optim/vector_constant_transpose.go | 11 + optim/vector_constraint.go | 11 + optim/vector_expression.go | 4 + problem/optimization_problem.go | 10 +- testing/problem/optimization_problem_test.go | 223 ++++++++++++++++++- 10 files changed, 298 insertions(+), 14 deletions(-) diff --git a/optim/constr_sense.go b/optim/constr_sense.go index 6a9826e..0f511a7 100644 --- a/optim/constr_sense.go +++ b/optim/constr_sense.go @@ -32,3 +32,21 @@ func (cs ConstrSense) ToSymbolic() symbolic.ConstrSense { } return '1' } + +/* +String +Description: + + Returns the string representation of the constraint sense. +*/ +func (cs ConstrSense) String() string { + switch cs { + case SenseEqual: + return "==" + case SenseLessThanEqual: + return "<=" + case SenseGreaterThanEqual: + return ">=" + } + return "UNRECOGNIZED ConstrSense" +} diff --git a/optim/scalar_constraint.go b/optim/scalar_constraint.go index cd28637..049a7d0 100644 --- a/optim/scalar_constraint.go +++ b/optim/scalar_constraint.go @@ -130,7 +130,17 @@ func (sc ScalarConstraint) Check() error { return fmt.Errorf("the constraint sense is not recognized.") } - // Check left and right hand sides? + // Check left and right hand sides + err := sc.LeftHandSide.Check() + if err != nil { + return fmt.Errorf("left hand side of the constraint is not valid: %v", err) + } + + // Check right hand side + err = sc.RightHandSide.Check() + if err != nil { + return fmt.Errorf("right hand side of the constraint is not valid: %v", err) + } // Return return nil diff --git a/optim/scalar_expression.go b/optim/scalar_expression.go index a8e7b39..93a980e 100644 --- a/optim/scalar_expression.go +++ b/optim/scalar_expression.go @@ -63,6 +63,9 @@ type ScalarExpression interface { //ToSymbolic Returns the symbolic version of the scalar expression // (i.e., the expression when declared using the symbolic math toolbox). ToSymbolic() (symbolic.Expression, error) + + // Check, checks the expression for any errors + Check() error } // NewExpr returns a new expression with a single additive constant value, c, diff --git a/optim/var_vector.go b/optim/var_vector.go index 2cc326e..f379a90 100644 --- a/optim/var_vector.go +++ b/optim/var_vector.go @@ -294,9 +294,6 @@ Description: input rhs as the right hand side if it is valid. */ func (vv VarVector) Eq(rightIn interface{}, errors ...error) (Constraint, error) { - // Constants - - // Algorithm return vv.Comparison(rightIn, SenseEqual, errors...) } @@ -373,7 +370,11 @@ func (vv VarVector) Comparison(rhs interface{}, sense ConstrSense, errors ...err ) default: - return VectorConstraint{}, fmt.Errorf("The Eq() method for VarVector is not implemented yet for type %T!", rhs) + return VectorConstraint{}, fmt.Errorf( + "The VarVector.Comparison (%v) method is not implemented yet for type %T!", + sense, + rhs, + ) } } diff --git a/optim/vector_constant.go b/optim/vector_constant.go index b35f1f4..ef9890e 100644 --- a/optim/vector_constant.go +++ b/optim/vector_constant.go @@ -23,6 +23,17 @@ KVector */ type KVector mat.VecDense // Inherit all methods from mat.VecDense +/* +Check +Description: + + This method checks for errors in the KVector type. + There should never be any. +*/ +func (kv KVector) Check() error { + return nil +} + /* Len diff --git a/optim/vector_constant_transpose.go b/optim/vector_constant_transpose.go index 831ddfd..c55cc87 100644 --- a/optim/vector_constant_transpose.go +++ b/optim/vector_constant_transpose.go @@ -23,6 +23,17 @@ KVectorTranspose */ type KVectorTranspose mat.VecDense // Inherit all methods from mat.VecDense +/* +Check +Description: + + This method checks for errors in the KVectorTranspose type. + There should never be any. +*/ +func (kvt KVectorTranspose) Check() error { + return nil +} + /* Len diff --git a/optim/vector_constraint.go b/optim/vector_constraint.go index e70374a..5cf853e 100644 --- a/optim/vector_constraint.go +++ b/optim/vector_constraint.go @@ -60,6 +60,17 @@ func (vc VectorConstraint) Check() error { ) } + // Check that the left and right hand sides are well-defined + err := vc.LeftHandSide.Check() + if err != nil { + return fmt.Errorf("left hand side of the constraint is not valid: %v", err) + } + + err = vc.RightHandSide.Check() + if err != nil { + return fmt.Errorf("right hand side of the constraint is not valid: %v", err) + } + // All Checks Passed! return nil } diff --git a/optim/vector_expression.go b/optim/vector_expression.go index bc131c9..1c8e286 100644 --- a/optim/vector_expression.go +++ b/optim/vector_expression.go @@ -75,6 +75,10 @@ type VectorExpression interface { //ToSymbolic // Converts the expression to a symbolic expression (in SymbolicMath.go) ToSymbolic() (symbolic.Expression, error) + + //Check + // Checks the expression for any errors + Check() error } /* diff --git a/problem/optimization_problem.go b/problem/optimization_problem.go index 8a4f24e..7b0d480 100644 --- a/problem/optimization_problem.go +++ b/problem/optimization_problem.go @@ -177,16 +177,10 @@ func ToSymbolicConstraint(inputConstraint optim.Constraint) (symbolic.Constraint } // Convert LHS to symbolic expression - lhs, err := inputConstraint.Left().ToSymbolic() - if err != nil { - return symbolic.ScalarConstraint{}, err - } + lhs, _ := inputConstraint.Left().ToSymbolic() // Convert RHS to symbolic expression - rhs, err := inputConstraint.Right().ToSymbolic() - if err != nil { - return symbolic.ScalarConstraint{}, err - } + rhs, _ := inputConstraint.Right().ToSymbolic() // Get Sense sense := inputConstraint.ConstrSense().ToSymbolic() diff --git a/testing/problem/optimization_problem_test.go b/testing/problem/optimization_problem_test.go index 6f055c3..71ddd45 100644 --- a/testing/problem/optimization_problem_test.go +++ b/testing/problem/optimization_problem_test.go @@ -8,6 +8,7 @@ Description: */ import ( + "fmt" "github.com/MatProGo-dev/MatProInterface.go/optim" "github.com/MatProGo-dev/MatProInterface.go/problem" "github.com/MatProGo-dev/SymbolicMath.go/symbolic" @@ -272,6 +273,36 @@ func TestOptimizationProblem_AddVariableMatrix1(t *testing.T) { } } +/* +TestOptimizationProblem_AddBinaryVariableMatrix1 +Description: + + Tests the AddBinaryVariableMatrix function with a simple problem. +*/ +func TestOptimizationProblem_AddBinaryVariableMatrix1(t *testing.T) { + // Constants + p1 := problem.NewProblem("TestProblem1") + rows := 5 + cols := 5 + + // Algorithm + p1.AddBinaryVariableMatrix(rows, cols) + + // Check that the number of variables is as expected. + if len(p1.Variables) != rows*cols { + t.Errorf("expected the number of variables to be %v; received %v", + rows*cols, len(p1.Variables)) + } + + // Verify that the type of the variables is as expected. + for _, v := range p1.Variables { + if v.Type != symbolic.Continuous { + t.Errorf("expected the type of the variable to be %v; received %v", + symbolic.Binary, v.Type) + } + } +} + /* TestOptimizationProblem_SetObjective1 Description: @@ -297,6 +328,32 @@ func TestOptimizationProblem_SetObjective1(t *testing.T) { } } +/* +TestOptimizationProblem_SetObjective2 +Description: + + Tests the SetObjective function with a vector objective + which should cause an error. +*/ +func TestOptimizationProblem_SetObjective2(t *testing.T) { + // Constants + p1 := problem.NewProblem("TestOptimizationProblem_SetObjective2") + v1 := p1.AddVariableVector(5) + + // Algorithm + err := p1.SetObjective(v1, problem.SenseMaximize) + if err == nil { + t.Errorf("expected an error; received nil") + } else { + if !strings.Contains( + err.Error(), + "trouble parsing input expression:", + ) { + t.Errorf("unexpected error: %v", err) + } + } +} + /* TestOptimizationProblem_ToSymbolicConstraint1 Description: @@ -337,10 +394,94 @@ Description: Tests the ToSymbolicConstraint function with a simple problem that has a vector constraint. This vector constraint will be a GreaterThanEqual vector constraint between - a vector variable and a scalar variable. + a vector variable and a vector variable. */ func TestOptimizationProblem_ToSymbolicConstraint2(t *testing.T) { + // Constants + model1 := optim.NewModel("TestModel1") + v1 := model1.AddVariableVector(5) + v2 := model1.AddVariableVector(5) + + // Algorithm + constr1, err := v1.GreaterEq(v2) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + constr1prime, err := problem.ToSymbolicConstraint(constr1) + + // Check that constr1prime is a VectorConstraint + if _, ok := constr1prime.(symbolic.VectorConstraint); !ok { + t.Errorf("expected the type of constr1prime to be %T; received %T", + symbolic.VectorConstraint{}, constr1prime) + } +} +/* +TestOptimizationProblem_ToSymbolicConstraint3 +Description: + + Tests the ToSymbolicConstraint function with a simple problem + that has a LeftHandSide that is not well-defined (in this case, + a variable). This should cause an error. +*/ +func TestOptimizationProblem_ToSymbolicConstraint3(t *testing.T) { + // Constants + model1 := optim.NewModel("TestModel1") + v1 := model1.AddVariable() + v2 := optim.Variable{Lower: 0, Upper: -1} + + // Algorithm + constr1 := optim.ScalarConstraint{ + LeftHandSide: v2, + RightHandSide: v1, + Sense: optim.SenseLessThanEqual, + } + _, err := problem.ToSymbolicConstraint(constr1) + + if err == nil { + t.Errorf("expected an error; received nil") + } else { + if !strings.Contains( + err.Error(), + v2.Check().Error(), + ) { + t.Errorf("unexpected error: %v", err) + } + } +} + +/* +TestOptimizationProblem_ToSymbolicConstraint4 +Description: + + Tests the ToSymbolicConstraint function with a simple constraint + that has a RightHandSide that is not well-defined (in this case, + a variable). This should cause an error. +*/ +func TestOptimizationProblem_ToSymbolicConstraint4(t *testing.T) { + // Constants + model1 := optim.NewModel("TestModel1") + v1 := optim.Variable{Lower: 0, Upper: -1} + v2 := model1.AddVariable() + + // Algorithm + constr1 := optim.ScalarConstraint{ + LeftHandSide: v2, + RightHandSide: v1, + Sense: optim.SenseLessThanEqual, + } + _, err := problem.ToSymbolicConstraint(constr1) + + if err == nil { + t.Errorf("expected an error; received nil") + } else { + if !strings.Contains( + err.Error(), + v1.Check().Error(), + ) { + t.Errorf("unexpected error: %v", err) + } + } } /* @@ -724,3 +865,83 @@ func TestOptimizationProblem_From8(t *testing.T) { } } } + +/* +TestOptimizationProblem_From9 +Description: + + Tests that the From function properly produces an error + when a constraint has been added to the problem that is not well-defined. +*/ +func TestOptimizationProblem_From9(t *testing.T) { + // Constants + model := optim.NewModel( + "TestOptimizationProblem_From9", + ) + + // Add a variable + v1 := model.AddVariable() + + // Add an objective + model.SetObjective(v1, optim.SenseMaximize) + + // Add a constraint + model.AddConstraint(optim.ScalarConstraint{ + LeftHandSide: v1, + RightHandSide: optim.Variable{Lower: 1, Upper: 0}, + Sense: optim.SenseLessThanEqual, + }) + + // Algorithm + _, err := problem.From(*model) + if err == nil { + t.Errorf("expected an error, received none!") + } else { + if !strings.Contains( + err.Error(), + fmt.Sprintf("there was a problem creating the %v-th constraint", 0), + ) { + t.Errorf("unexpected error: %v", err) + } + } +} + +/* +TestOptimizationProblem_From10 +Description: + + Tests that the From function properly produces an error + when the objective is not well-formed. +*/ +func TestOptimizationProblem_From10(t *testing.T) { + // Constants + model := optim.NewModel( + "TestOptimizationProblem_From10", + ) + + // Add a variable + v1 := model.AddVariable() + + // Add an objective + model.SetObjective(optim.Variable{Lower: 0, Upper: -1}, optim.SenseMaximize) + + // Add a constraint + model.AddConstraint(optim.ScalarConstraint{ + LeftHandSide: v1, + RightHandSide: optim.K(1.2), + Sense: optim.SenseLessThanEqual, + }) + + // Algorithm + _, err := problem.From(*model) + if err == nil { + t.Errorf("expected an error, received none!") + } else { + if !strings.Contains( + err.Error(), + model.Obj.Check().Error(), + ) { + t.Errorf("unexpected error: %v", err) + } + } +} From 4508c7f389dc4f842583f93bda6215bd52122d70 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Wed, 28 Feb 2024 14:02:39 -0500 Subject: [PATCH 24/25] Fixed Some Issues With Printing ConstrSense objects --- testing/optim/var_vector_test.go | 32 ++++++++++++++-------- testing/optim/var_vector_transpose_test.go | 4 +-- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/testing/optim/var_vector_test.go b/testing/optim/var_vector_test.go index 7bb93d4..efbe3af 100644 --- a/testing/optim/var_vector_test.go +++ b/testing/optim/var_vector_test.go @@ -336,7 +336,11 @@ func TestVarVector_Eq2(t *testing.T) { // Verify that constraint can be created with no issues. _, err := vv1.Eq(badRHS) - expectedError := fmt.Sprintf("The Eq() method for VarVector is not implemented yet for type %T!", badRHS) + expectedError := fmt.Sprintf( + "The VarVector.Comparison (%v) method is not implemented yet for type %T!", + optim.SenseEqual, + badRHS, + ) if !strings.Contains(err.Error(), expectedError) { t.Errorf("Expected error \"%v\"; received \"%v\"", expectedError, err) } @@ -1066,16 +1070,22 @@ func TestVarVector_GreaterEq1(t *testing.T) { // Compare _, err := vec1.GreaterEq(kv1) - if !strings.Contains( - err.Error(), - fmt.Sprintf( - "The two inputs to comparison '%v' must have the same dimension, but #1 has dimension %v and #2 has dimension %v!", - optim.SenseGreaterThanEqual, - vec1.Len(), - kv1.Len(), - ), - ) { - t.Errorf("Unexpected error when comparing two vectors: %v", err) + expectedError := fmt.Sprintf( + "The two inputs to comparison '%v' must have the same dimension, but #1 has dimension %v and #2 has dimension %v!", + optim.ConstrSense(optim.SenseGreaterThanEqual), + vec1.Len(), + kv1.Len(), + ) + if err == nil { + t.Errorf("No error was thrown, but we expected one!") + } else { + if !strings.Contains( + err.Error(), + expectedError, + ) { + t.Errorf("Unexpected error when comparing two vectors: %v \n expected %v", err, expectedError) + } + } } diff --git a/testing/optim/var_vector_transpose_test.go b/testing/optim/var_vector_transpose_test.go index 63f845b..2814349 100644 --- a/testing/optim/var_vector_transpose_test.go +++ b/testing/optim/var_vector_transpose_test.go @@ -530,7 +530,7 @@ func TestVarVectorTranspose_Comparison5(t *testing.T) { err.Error(), fmt.Sprintf( "The two inputs to comparison '%v' must have the same dimension, but #1 has dimension %v and #2 has dimension %v!", - optim.SenseGreaterThanEqual, + optim.ConstrSense(optim.SenseGreaterThanEqual), vec1.Len(), vec2.Len(), ), @@ -1137,7 +1137,7 @@ func TestVarVectorTranspose_LessEq1(t *testing.T) { err.Error(), fmt.Sprintf( "The two inputs to comparison '%v' must have the same dimension, but #1 has dimension %v and #2 has dimension %v!", - optim.SenseLessThanEqual, + optim.ConstrSense(optim.SenseLessThanEqual), vec1.Len(), kv2.Len(), ), From 01047eb68f7d532bdbe80c870a5ea8df9441ed88 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Wed, 28 Feb 2024 14:06:58 -0500 Subject: [PATCH 25/25] Added TEsts for VectorConstantTranspose object --- .../optim/vector_constant_transposed_test.go | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/testing/optim/vector_constant_transposed_test.go b/testing/optim/vector_constant_transposed_test.go index d85af93..ec7dfff 100644 --- a/testing/optim/vector_constant_transposed_test.go +++ b/testing/optim/vector_constant_transposed_test.go @@ -3,6 +3,7 @@ package optim_test import ( "fmt" "github.com/MatProGo-dev/MatProInterface.go/optim" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "gonum.org/v1/gonum/mat" "strings" "testing" @@ -14,6 +15,23 @@ Description: Tests the new type KVectorTranspose which represents a constant vector. */ +/* +TestKVectorTranspose_Check1 +Description: + + Tests that the Check() method returns nil. +*/ +func TestKVectorTranspose_Check1(t *testing.T) { + // Create a KVectorTranspose + desLength := 4 + var vec1 = optim.KVectorTranspose(optim.OnesVector(desLength)) + + // Check + if vec1.Check() != nil { + t.Errorf("The Check() method should return nil; received %v", vec1.Check()) + } +} + /* TestKVectorTranspose_At1 Description: @@ -1187,3 +1205,28 @@ func TestKVectorTranspose_Transpose1(t *testing.T) { ) } } + +/* +TestKVectorTranspose_ToSymbolic1 +Description: + + Tests that the ToSymbolic function works as expected. + Expects for the error to be nil and for the result to be a symbolic.KMatrix +*/ +func TestKVectorTranspose_ToSymbolic1(t *testing.T) { + // Constants + desLength := 10 + + // Algorithm + vec1 := optim.KVectorTranspose(optim.OnesVector(desLength)) + symVec, err := vec1.ToSymbolic() + if err != nil { + t.Errorf("Unexpected error in ToSymbolic: %v", err) + } + + // Check type + _, ok := symVec.(symbolic.KMatrix) + if !ok { + t.Errorf("Expected symVec to be of type symbolic.KVector; received %T", symVec) + } +}