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 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/go.mod b/go.mod index ee9cb18..76a33a2 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.1.4 diff --git a/go.sum b/go.sum index b151481..51762e4 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,16 @@ -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= +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= +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= +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= +gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= 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/constr_sense.go b/optim/constr_sense.go new file mode 100644 index 0000000..0f511a7 --- /dev/null +++ b/optim/constr_sense.go @@ -0,0 +1,52 @@ +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' +} + +/* +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/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/model.go b/optim/model.go index 79dd43e..3c1cb49 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,24 +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} -} - -//// ShowLog instructs the solver to show the log or not. -//func (m *Model) ShowLog(shouldShow bool) { -// m.ShowLog = shouldShow -//} - -// SetTimeLimit sets the solver time limit for the model. -func (m *Model) SetTimeLimit(dur time.Duration) { - m.TimeLimit = dur + return &Model{Name: name} } /* @@ -168,47 +155,24 @@ func (m *Model) SetObjective(e Expression, sense ObjSense) error { 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 -//} +/* +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..049a7d0 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: @@ -106,14 +110,38 @@ func (sc ScalarConstraint) Simplify() (ScalarConstraint, error) { } -// 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 = '>' -) +/* +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 + 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 0497aae..93a980e 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,13 @@ 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) + + // 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/scalar_linear_expr.go b/optim/scalar_linear_expr.go index cebd5e9..6cc63ed 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,30 @@ 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) { + // 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 { + return nil, err + } + + 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 d36c977..c4d5754 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,44 @@ 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) { + // Input Checking + err := qe.Check() + if err != nil { + return nil, err + } + + // Convert Q, L and C to symbolic + symQ := symbolic.DenseToKMatrix(qe.Q) + symL := symbolic.VecDenseToKVector(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) + 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/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/var_vector.go b/optim/var_vector.go index a176c50..f379a90 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" ) @@ -293,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...) } @@ -372,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, + ) } } @@ -432,3 +434,32 @@ 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) { + // Input Checking + err := vv.Check() + if err != nil { + return nil, err + } + + // Algorithm + // Create the symbolic vector + symVVec := symbolic.VariableVector{} + + // Add each variable to the vector + for _, elt := range vv.Elements { + 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) + } + + // Return the symbolic vector + return symVVec, nil +} diff --git a/optim/var_vector_transpose.go b/optim/var_vector_transpose.go index 2ff1405..8d1faf6 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,58 @@ Description: 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: + + This method converts the VarVectorTranspose to a symbolic expression + (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{} + + // 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/vars.go b/optim/vars.go index 1e810b3..a008cad 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,30 @@ 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) { + // Input Checking + err := v.Check() + if err != nil { + return nil, err + } + + // Create base variable and fill its elements + symVar := symbolic.Variable{ + ID: v.ID, + Lower: v.Lower, + Upper: v.Upper, + Type: symbolic.VarType(v.Vtype), + Name: fmt.Sprintf("x_{%v}", v.ID), + } + + // Algorithm + return symVar, nil +} diff --git a/optim/vector_constant.go b/optim/vector_constant.go index dcb907b..ef9890e 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 @@ -20,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 @@ -413,3 +427,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..c55cc87 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 @@ -20,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 @@ -383,3 +397,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..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 } @@ -71,3 +82,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..1c8e286 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,14 @@ 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) + + //Check + // Checks the expression for any errors + Check() 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/objective.go b/problem/objective.go new file mode 100644 index 0000000..584e789 --- /dev/null +++ b/problem/objective.go @@ -0,0 +1,16 @@ +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} +} diff --git a/problem/objective_sense.go b/problem/objective_sense.go new file mode 100644 index 0000000..bc4a825 --- /dev/null +++ b/problem/objective_sense.go @@ -0,0 +1,28 @@ +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 + SenseFind ObjSense = 0 +) + +/* +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 + } +} diff --git a/problem/optimization_problem.go b/problem/optimization_problem.go new file mode 100644 index 0000000..7b0d480 --- /dev/null +++ b/problem/optimization_problem.go @@ -0,0 +1,270 @@ +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 Objective +} + +// 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 (op *OptimizationProblem) AddVariable() symbolic.Variable { + return op.AddRealVariable() +} + +/* +AddRealVariable +Description: + + Adds a Real variable to the model and returns said variable. +*/ +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 (op *OptimizationProblem) AddVariableClassic(lower, upper float64, vtype symbolic.VarType) symbolic.Variable { + id := uint64(len(op.Variables)) + newVar := symbolic.Variable{ + ID: id, + Lower: lower, + Upper: upper, + Type: vtype, + Name: fmt.Sprintf("x_%v", id), + } + op.Variables = append(op.Variables, newVar) + return newVar +} + +// AddBinaryVar adds a binary variable to the model and returns said variable. +func (op *OptimizationProblem) AddBinaryVariable() symbolic.Variable { + return op.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 (op *OptimizationProblem) AddVariableVector(dim int) symbolic.VariableVector { + // Constants + + // Algorithm + varSlice := make([]symbolic.Variable, dim) + for eltIndex := 0; eltIndex < dim; eltIndex++ { + varSlice[eltIndex] = op.AddVariable() + } + return varSlice +} + +/* +AddVariableVectorClassic +Description: + + The classic version of AddVariableVector defined in the original goop. +*/ +func (op *OptimizationProblem) AddVariableVectorClassic( + num int, lower, upper float64, vtype symbolic.VarType, +) symbolic.VariableVector { + stID := uint64(len(op.Variables)) + vs := make([]symbolic.Variable, num) + for i := range vs { + vs[i] = symbolic.Variable{ + ID: stID + uint64(i), + Lower: lower, + Upper: upper, + Type: vtype, + Name: fmt.Sprintf("x_%v", stID+uint64(i)), + } + } + + op.Variables = append(op.Variables, vs...) + return vs +} + +// AddBinaryVariableVector adds a vector of binary variables to the model and +// returns the slice. +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 (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. + + // 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 (op *OptimizationProblem) AddBinaryVariableMatrix(rows, cols int) [][]symbolic.Variable { + return op.AddVariableMatrix(rows, cols, 0, 1, symbolic.Binary) +} + +/* +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 (op *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 + op.Objective = *NewObjective(se, sense) + return nil +} + +/* +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 + + // Input Processing + err := inputConstraint.Check() + if err != nil { + return symbolic.ScalarConstraint{}, err + } + + // Convert LHS to symbolic expression + lhs, _ := inputConstraint.Left().ToSymbolic() + + // Convert RHS to symbolic expression + rhs, _ := inputConstraint.Right().ToSymbolic() + + // Get Sense + sense := inputConstraint.ConstrSense().ToSymbolic() + + // Convert + switch { + case symbolic.IsScalarExpression(lhs): + return symbolic.ScalarConstraint{ + LeftHandSide: lhs.(symbolic.ScalarExpression), + RightHandSide: rhs.(symbolic.ScalarExpression), + Sense: sense, + }, nil + case symbolic.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) + } + +} + +/* +From +Description: + + Converts the given input into an optimization problem. +*/ +func From(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 + } + + 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 { + 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. + 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 + 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 + } + + // Done + return newOptimProblem, nil + +} 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/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/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: diff --git a/testing/optim/var_vector_test.go b/testing/optim/var_vector_test.go index 9d9f472..efbe3af 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" @@ -335,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) } @@ -1065,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) + } + } } @@ -1478,3 +1489,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) + } +} diff --git a/testing/optim/var_vector_transpose_test.go b/testing/optim/var_vector_transpose_test.go index 6822308..2814349 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" @@ -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(), ), @@ -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, @@ -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) + } +} 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) + } +} 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) + } +} 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/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, + ) + } + +} diff --git a/testing/problem/optimization_problem_test.go b/testing/problem/optimization_problem_test.go new file mode 100644 index 0000000..71ddd45 --- /dev/null +++ b/testing/problem/optimization_problem_test.go @@ -0,0 +1,947 @@ +package problem_test + +/* +optimization_problem_test.go +Description: + + Tests for all functions and objects defined in the optimization_problem.go file. +*/ + +import ( + "fmt" + "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" +) + +/* +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 := "TestProblem1" + + // New Problem + p1 := problem.NewProblem(name) + + // Check that the name is as expected in the problem. + if p1.Name != name { + t.Errorf("expected the name of the problem to be %v; received %v", + name, p1.Name) + } + + // Check that the number of variables is zero. + if len(p1.Variables) != 0 { + t.Errorf("expected the number of variables to be 0; received %v", + len(p1.Variables)) + } + + // Check that the number of constraints is zero. + if len(p1.Constraints) != 0 { + t.Errorf("expected the number of constraints to be 0; received %v", + len(p1.Constraints)) + } +} + +/* +TestOptimizationProblem_AddVariable1 +Description: + + Tests the AddVariable function with a simple problem. +*/ +func TestOptimizationProblem_AddVariable1(t *testing.T) { + // Constants + p1 := problem.NewProblem("TestProblem1") + + // Algorithm + p1.AddVariable() + + // Check that the number of variables is one. + if len(p1.Variables) != 1 { + t.Errorf("expected the number of variables to be 1; received %v", + len(p1.Variables)) + } + + // Verify that the type of the variable is as expected. + if p1.Variables[0].Type != symbolic.Continuous { + t.Errorf("expected the type of the variable to be %v; received %v", + symbolic.Continuous, p1.Variables[0].Type) + } +} + +/* +TestOptimizationProblem_AddRealVariable1 +Description: + + Tests the AddRealVariable function with a simple problem. +*/ +func TestOptimizationProblem_AddRealVariable1(t *testing.T) { + // Constants + p1 := problem.NewProblem("TestProblem1") + + // Algorithm + p1.AddRealVariable() + + // Check that the number of variables is one. + if len(p1.Variables) != 1 { + t.Errorf("expected the number of variables to be 1; received %v", + len(p1.Variables)) + } + + // Verify that the type of the variable is as expected. + if p1.Variables[0].Type != symbolic.Continuous { + t.Errorf("expected the type of the variable to be %v; received %v", + symbolic.Continuous, p1.Variables[0].Type) + } +} + +/* +TestOptimizationProblem_AddVariableClassic1 +Description: + + Tests the AddVariableClassic function with a simple problem. +*/ +func TestOptimizationProblem_AddVariableClassic1(t *testing.T) { + // Constants + p1 := problem.NewProblem("TestProblem1") + + // Algorithm + p1.AddVariableClassic(0, 1, symbolic.Binary) + + // Check that the number of variables is one. + if len(p1.Variables) != 1 { + t.Errorf("expected the number of variables to be 1; received %v", + len(p1.Variables)) + } + + // Verify that the type of the variable is as expected. + if p1.Variables[0].Type != symbolic.Binary { + t.Errorf("expected the type of the variable to be %v; received %v", + symbolic.Binary, p1.Variables[0].Type) + } +} + +/* +TestOptimizationProblem_AddBinaryVariable1 +Description: + + Tests the AddBinaryVariable function with a simple problem. +*/ +func TestOptimizationProblem_AddBinaryVariable1(t *testing.T) { + // Constants + p1 := problem.NewProblem("TestProblem1") + + // Algorithm + p1.AddBinaryVariable() + + // Check that the number of variables is one. + if len(p1.Variables) != 1 { + t.Errorf("expected the number of variables to be 1; received %v", + len(p1.Variables)) + } + + // Verify that the type of the variable is as expected. + if p1.Variables[0].Type != symbolic.Binary { + t.Errorf("expected the type of the variable to be %v; received %v", + symbolic.Binary, p1.Variables[0].Type) + } +} + +/* +TestOptimizationProblem_AddVariableVector1 +Description: + + Tests the AddVariableVector function with a simple problem. +*/ +func TestOptimizationProblem_AddVariableVector1(t *testing.T) { + // Constants + p1 := problem.NewProblem("TestProblem1") + dim := 5 + + // Algorithm + p1.AddVariableVector(dim) + + // Check that the number of variables is as expected. + if len(p1.Variables) != dim { + t.Errorf("expected the number of variables to be %v; received %v", + dim, 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.Continuous, v.Type) + } + } +} + +/* +TestOptimizationProblem_AddVariableVectorClassic1 +Description: + + Tests the AddVariableVectorClassic function with a simple problem. +*/ +func TestOptimizationProblem_AddVariableVectorClassic1(t *testing.T) { + // Constants + p1 := problem.NewProblem("TestProblem1") + dim := 5 + + // Algorithm + p1.AddVariableVectorClassic(dim, 0, 1, symbolic.Binary) + + // Check that the number of variables is as expected. + if len(p1.Variables) != dim { + t.Errorf("expected the number of variables to be %v; received %v", + dim, len(p1.Variables)) + } + + // Verify that the type of the variables is as expected. + 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) + } + } +} + +/* +TestOptimizationProblem_AddBinaryVariableVector1 +Description: + + Tests the AddBinaryVariableVector function with a simple problem. +*/ +func TestOptimizationProblem_AddBinaryVariableVector1(t *testing.T) { + // Constants + p1 := problem.NewProblem("TestProblem1") + dim := 5 + + // Algorithm + p1.AddBinaryVariableVector(dim) + + // Check that the number of variables is as expected. + if len(p1.Variables) != dim { + t.Errorf("expected the number of variables to be %v; received %v", + dim, len(p1.Variables)) + } + + // Verify that the type of the variables is as expected. + 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) + } + } +} + +/* +TestOptimizationProblem_AddVariableMatrix1 +Description: + + Tests the AddVariableMatrix function with a simple problem. +*/ +func TestOptimizationProblem_AddVariableMatrix1(t *testing.T) { + // Constants + p1 := problem.NewProblem("TestProblem1") + rows := 5 + cols := 5 + + // Algorithm + p1.AddVariableMatrix(rows, cols, 0, 1, symbolic.Binary) + + // 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_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: + + 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_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: + + 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 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) + } + } +} + +/* +TestOptimizationProblem_From1 +Description: + + Tests the From function with a simple + model that doesn't have an objective. +*/ +func TestOptimizationProblem_From1(t *testing.T) { + // Constants + model := optim.NewModel( + "TestOptimizationProblem_From1", + ) + + N := 5 + for ii := 0; ii < N; ii++ { + 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 { + 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, + ) + } + } +} + +/* +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) + } + + 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) + 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_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) + } + } +} + +/* +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) + } + } +} 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, 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), - ) - } - } - } - -}