From 21c1bd90e44d243b204d7b10a52ac1118632766c Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Wed, 13 Aug 2025 15:49:11 -0400 Subject: [PATCH 1/7] Adding a new function which forces the standard LP to be in a specific optimization sense, if it is not already. --- problem/optimization_problem.go | 46 ++++++++++++++ testing/problem/optimization_problem_test.go | 63 ++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/problem/optimization_problem.go b/problem/optimization_problem.go index 349e4c5..398e8d5 100644 --- a/problem/optimization_problem.go +++ b/problem/optimization_problem.go @@ -831,6 +831,52 @@ func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem, return problemInStandardForm, slackVariables, nil } +/* +ToLPStandardForm2 +Description: + + Transforms the given linear program (represented in an OptimizationProblem object) + into a standard form (i.e., only linear equality constraints and a linear objective function). + + max c^T * x + subject to + A * x = b + x >= 0 + + Where: + - A is a matrix of coefficients, + - b is a vector of constants, and + - c is the vector of coefficients for the objective function. + This method also returns the slack variables (i.e., the variables that + are added to the problem to convert the inequalities into equalities). +*/ +func (problemIn *OptimizationProblem) ToLPStandardForm2() (*OptimizationProblem, []symbolic.Variable, error) { + // Input Processing + err := problemIn.Check() + if err != nil { + return nil, nil, fmt.Errorf("the optimization problem is not well-formed: %v", err) + } + + // Use the existing method to convert to standard form 1 + problemInStandardForm, slackVariables, err := problemIn.ToLPStandardForm1() + if err != nil { + return nil, nil, err + } + + // Modify the objective function to be a maximization problem, + // if it is not already. + if problemInStandardForm.Objective.Sense == SenseMinimize { + newObjectiveExpression := problemInStandardForm.Objective.Expression.Multiply(-1.0) + err = problemInStandardForm.SetObjective(newObjectiveExpression, SenseMaximize) + if err != nil { + return nil, nil, fmt.Errorf("there was a problem setting the new objective function: %v", err) + } + } + + // Return the new problem and the slack variables + return problemInStandardForm, slackVariables, nil +} + /* WithAllPositiveVariableConstraintsRemoved Description: diff --git a/testing/problem/optimization_problem_test.go b/testing/problem/optimization_problem_test.go index 2be4cb7..f19c259 100644 --- a/testing/problem/optimization_problem_test.go +++ b/testing/problem/optimization_problem_test.go @@ -2943,3 +2943,66 @@ func TestOptimizationProblem_SimplifyConstraints3(t *testing.T) { L1, L2) } } + +/* +TestOptimizationProblem_ToLPStandardForm2_1 +Description: + + Tests the ToLPStandardForm2 function with a simple problem + that contains: + - a linear objective, + - a MINIMIZATION sense + - 1 variable, + - and a single linear inequality constraint (SenseGreaterThanEqual). + The result should be a problem with 2 variables and 1 constraint. + The sense of the resulting problem should be MAXIMIZATION. +*/ +func TestOptimizationProblem_ToLPStandardForm2_1(t *testing.T) { + // Constants + p1 := problem.NewProblem("TestOptimizationProblem_ToLPStandardForm2_1") + v1 := p1.AddVariable() + c1 := v1.GreaterEq(-1.0) + + p1.Constraints = append(p1.Constraints, c1) + + // Create good objective + p1.Objective = *problem.NewObjective( + v1, + problem.SenseMinimize, + ) + + // Algorithm + p2, _, err := p1.ToLPStandardForm2() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // Check that the number of variables is as expected. + expectedNumVariables := 0 + expectedNumVariables += 2 * len(p1.Variables) // original variables (positive and negative halfs) + expectedNumVariables += len(p1.Constraints) // slack variables + if len(p2.Variables) != expectedNumVariables { + t.Errorf("expected the number of variables to be %v; received %v", + expectedNumVariables, len(p2.Variables)) + } + + // Check that the number of constraints is as expected. + if len(p2.Constraints) != 1 { + t.Errorf("expected the number of constraints to be %v; received %v", + 1, len(p2.Constraints)) + } + + // Verify that all constraints are equality constraints + for _, c := range p2.Constraints { + if c.ConstrSense() != symbolic.SenseEqual { + t.Errorf("expected the constraint to be an equality constraint; received %v", + c.ConstrSense()) + } + } + + // Verify that the sense of the objective is MAXIMIZATION + if p2.Objective.Sense != problem.SenseMaximize { + t.Errorf("expected the sense of the objective to be %v; received %v", + problem.SenseMaximize, p2.Objective.Sense) + } +} From e5dd4837b9f4cc70438e642e85c6244006637619 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Fri, 29 Aug 2025 15:21:38 -0400 Subject: [PATCH 2/7] Added tests to try to catch when problem is not well-defined --- .../optimization_problem/not_well_defined.go | 10 ++ problem/optimization_problem.go | 14 ++- testing/problem/optimization_problem_test.go | 103 ++++++++++++++++++ 3 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 mpiErrors/optimization_problem/not_well_defined.go diff --git a/mpiErrors/optimization_problem/not_well_defined.go b/mpiErrors/optimization_problem/not_well_defined.go new file mode 100644 index 0000000..a8e9bc5 --- /dev/null +++ b/mpiErrors/optimization_problem/not_well_defined.go @@ -0,0 +1,10 @@ +package optimization_problem_errors + +type NotWellDefinedError struct { + ProblemName string + ErrorSource error +} + +func (e NotWellDefinedError) Error() string { + return "the problem " + e.ProblemName + " is not well defined: " + e.ErrorSource.Error() +} diff --git a/problem/optimization_problem.go b/problem/optimization_problem.go index 398e8d5..3bb252c 100644 --- a/problem/optimization_problem.go +++ b/problem/optimization_problem.go @@ -5,6 +5,7 @@ import ( "github.com/MatProGo-dev/MatProInterface.go/causeOfProblemNonlinearity" "github.com/MatProGo-dev/MatProInterface.go/mpiErrors" + ope "github.com/MatProGo-dev/MatProInterface.go/mpiErrors/optimization_problem" "github.com/MatProGo-dev/MatProInterface.go/optim" getKVector "github.com/MatProGo-dev/SymbolicMath.go/get/KVector" "github.com/MatProGo-dev/SymbolicMath.go/symbolic" @@ -665,7 +666,7 @@ func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem, // Input Processing err := problemIn.Check() if err != nil { - return nil, nil, fmt.Errorf("the optimization problem is not well-formed: %v", err) + return nil, nil, problemIn.MakeNotWellDefinedError() } // Check if the problem is linear @@ -854,7 +855,7 @@ func (problemIn *OptimizationProblem) ToLPStandardForm2() (*OptimizationProblem, // Input Processing err := problemIn.Check() if err != nil { - return nil, nil, fmt.Errorf("the optimization problem is not well-formed: %v", err) + return nil, nil, problemIn.MakeNotWellDefinedError() } // Use the existing method to convert to standard form 1 @@ -866,6 +867,8 @@ func (problemIn *OptimizationProblem) ToLPStandardForm2() (*OptimizationProblem, // Modify the objective function to be a maximization problem, // if it is not already. if problemInStandardForm.Objective.Sense == SenseMinimize { + // If the problem is a minimization problem, + // then we can convert it to a maximization problem by negating the objective function. newObjectiveExpression := problemInStandardForm.Objective.Expression.Multiply(-1.0) err = problemInStandardForm.SetObjective(newObjectiveExpression, SenseMaximize) if err != nil { @@ -1058,3 +1061,10 @@ func (op *OptimizationProblem) SimplifyConstraints() { // Set the new constraints op.Constraints = newConstraints } + +func (op *OptimizationProblem) MakeNotWellDefinedError() ope.NotWellDefinedError { + return ope.NotWellDefinedError{ + ProblemName: op.Name, + ErrorSource: op.Check(), + } +} diff --git a/testing/problem/optimization_problem_test.go b/testing/problem/optimization_problem_test.go index f19c259..0193f31 100644 --- a/testing/problem/optimization_problem_test.go +++ b/testing/problem/optimization_problem_test.go @@ -1217,6 +1217,66 @@ func TestOptimizationProblem_IsLinear4(t *testing.T) { } } +/* +TestOptimizationProblem_IsLinear5 +Description: + + Tests the IsLinear function with an optimization problem + that is NOT well-defined. + The function should cause a panic. +*/ +func TestOptimizationProblem_IsLinear5(t *testing.T) { + // Constants + p1 := problem.NewProblem("TestOptimizationProblem_IsLinear5") + vv1 := p1.AddVariableVector(3) + c1 := vv1.AtVec(0).LessEq(1.0) + c2 := symbolic.ScalarConstraint{ + LeftHandSide: vv1.AtVec(1), + RightHandSide: symbolic.Monomial{ + Coefficient: 1.0, + VariableFactors: []symbolic.Variable{vv1.AtVec(1).(symbolic.Variable)}, + Exponents: []int{1, 2}, + }, + Sense: symbolic.SenseLessThanEqual, + } + + // Add constraints + p1.Constraints = append(p1.Constraints, c1) + p1.Constraints = append(p1.Constraints, c2) + + // Create good objective + p1.Objective = *problem.NewObjective( + vv1.Transpose().Multiply(symbolic.OnesVector(3)), + problem.SenseMinimize, + ) + + // Algorithm should panic with a not well-defined error + defer func() { + r := recover() + if r == nil { + t.Errorf("expected a panic; received none") + } + + err, tf := r.(error) + if !tf { + t.Errorf("expected a panic of type error; received %T", r) + } + + // Create the expected error + expectedError := p1.Check() + if !strings.Contains( + err.Error(), + expectedError.Error(), + ) { + t.Errorf("expected a not well-defined panic; received %v", r) + } + + }() + + p1.IsLinear() + t.Errorf("expected a panic; received none") +} + /* TestOptimizationProblem_LinearInequalityConstraintMatrices1 Description: @@ -2737,6 +2797,49 @@ func TestOptimizationProblem_ToLPStandardForm1_9(t *testing.T) { } } +/* +TestOptimizationProblem_ToLPStandardForm1_10 +Description: + + This method verifies that the method will return an error + if the optimization problem is not well-defined. + In this case, we will create a problem with a constraint + that has mismatched dimensions. +*/ +func TestOptimizationProblem_ToLPStandardForm1_10(t *testing.T) { + // Setup + N := 10 + + // Create objective function + p1 := problem.NewProblem("TestOptimizationProblem_ToLPStandardForm1_10") + x := p1.AddVariableVector(N) + p1.Constraints = append( + p1.Constraints, + symbolic.VectorConstraint{ + LeftHandSide: x, + RightHandSide: symbolic.VecDenseToKVector(symbolic.OnesVector(N + 1)), // Mismatched dimensions + Sense: symbolic.SenseLessThanEqual, + }, + ) + + p1.Objective = *problem.NewObjective( + x.Transpose().Multiply(symbolic.OnesVector(x.Len())), + problem.SenseMinimize, + ) + + // Call the ToLPStandardForm1 + _, _, err := p1.ToLPStandardForm1() + if err == nil { + t.Errorf("expected an error; received nil") + } + + // Create the expected error + expectedError := p1.MakeNotWellDefinedError() + if err.Error() != expectedError.Error() { + t.Errorf("unexpected error: %v", err) + } +} + /* TestOptimizationProblem_CheckIfLinear1 Description: From 6185b6e1d1f40c0a99337aa9a29279a87c9694f2 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Fri, 29 Aug 2025 15:24:57 -0400 Subject: [PATCH 3/7] Added tests for the CopyVariable() method --- testing/problem/optimization_problem_test.go | 34 ++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/testing/problem/optimization_problem_test.go b/testing/problem/optimization_problem_test.go index 0193f31..c14d4d7 100644 --- a/testing/problem/optimization_problem_test.go +++ b/testing/problem/optimization_problem_test.go @@ -3109,3 +3109,37 @@ func TestOptimizationProblem_ToLPStandardForm2_1(t *testing.T) { problem.SenseMaximize, p2.Objective.Sense) } } + +/* +TestOptimizationProblem_CopyVariable1 +Description: + + This method tests that the CopyVariable method for OptimizationProblem + properly creates a copy of one variable in an optimization problem. + Check that: + - the new variable has a different ID than the one that is copied + - the new variable has a slightly different name than the one that is copied +*/ +func TestOptimizationProblem_CopyVariable1(t *testing.T) { + p1 := problem.NewProblem("TestOptimizationProblem_CopyVariable1") + v1 := p1.AddVariable() + + // Create a copy of the variable + v2 := p1.CopyVariable(v1) + + // Check that the new variable has a different ID + if v1.ID == v2.ID { + t.Errorf( + "expected the new variable to have a different ID; received %v", + v2.ID, + ) + } + + // Check that the new variable has a slightly different name + if v1.Name == v2.Name { + t.Errorf( + "expected the new variable to have a slightly different name; received %v", + v2.Name, + ) + } +} From e20c863739d2bbd0f61abda93f214fd81c7ee250 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Fri, 29 Aug 2025 15:29:11 -0400 Subject: [PATCH 4/7] Updated SymbolicMath.go dependency --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e6400c7..67f674c 100644 --- a/go.mod +++ b/go.mod @@ -6,4 +6,4 @@ toolchain go1.23.9 require gonum.org/v1/gonum v0.16.0 -require github.com/MatProGo-dev/SymbolicMath.go v0.2.3 +require github.com/MatProGo-dev/SymbolicMath.go v0.2.4 diff --git a/go.sum b/go.sum index 3e67137..a464570 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,7 @@ github.com/MatProGo-dev/SymbolicMath.go v0.2.2 h1:U9nLLgtslRXbweCsgW9uSw8AvoGMgj github.com/MatProGo-dev/SymbolicMath.go v0.2.2/go.mod h1:tW8thj4pkaTV9lFNU3OCKmwQ3mZ2Eim6S4JpHRDfRvU= github.com/MatProGo-dev/SymbolicMath.go v0.2.3 h1:ffkUVU1oKzw2jj6fEu4BKW2YEYOWq55fwD7FOP9cY6k= github.com/MatProGo-dev/SymbolicMath.go v0.2.3/go.mod h1:tW8thj4pkaTV9lFNU3OCKmwQ3mZ2Eim6S4JpHRDfRvU= +github.com/MatProGo-dev/SymbolicMath.go v0.2.4 h1:SxvgOJBpx9H6ZHISyF3A79gOd1pHJd8Nywrqf4sJZTs= +github.com/MatProGo-dev/SymbolicMath.go v0.2.4/go.mod h1:tW8thj4pkaTV9lFNU3OCKmwQ3mZ2Eim6S4JpHRDfRvU= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= From d21263c7e60f71fba2860c372cc2c88cd20311d4 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Fri, 29 Aug 2025 15:41:09 -0400 Subject: [PATCH 5/7] Upgraded dependencies again + tweaked some optimization problem tests to respect NEW expectations of ToLPStandardForm --- go.mod | 2 +- go.sum | 6 ++-- problem/optimization_problem.go | 30 ++++---------------- testing/problem/optimization_problem_test.go | 18 ++++++++---- 4 files changed, 21 insertions(+), 35 deletions(-) diff --git a/go.mod b/go.mod index 67f674c..9998af4 100644 --- a/go.mod +++ b/go.mod @@ -6,4 +6,4 @@ toolchain go1.23.9 require gonum.org/v1/gonum v0.16.0 -require github.com/MatProGo-dev/SymbolicMath.go v0.2.4 +require github.com/MatProGo-dev/SymbolicMath.go v0.2.4-1 diff --git a/go.sum b/go.sum index a464570..ba3f84f 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ -github.com/MatProGo-dev/SymbolicMath.go v0.2.2 h1:U9nLLgtslRXbweCsgW9uSw8AvoGMgjq2luQtXIuN3eA= -github.com/MatProGo-dev/SymbolicMath.go v0.2.2/go.mod h1:tW8thj4pkaTV9lFNU3OCKmwQ3mZ2Eim6S4JpHRDfRvU= -github.com/MatProGo-dev/SymbolicMath.go v0.2.3 h1:ffkUVU1oKzw2jj6fEu4BKW2YEYOWq55fwD7FOP9cY6k= -github.com/MatProGo-dev/SymbolicMath.go v0.2.3/go.mod h1:tW8thj4pkaTV9lFNU3OCKmwQ3mZ2Eim6S4JpHRDfRvU= +github.com/MatProGo-dev/SymbolicMath.go v0.2.4-1 h1:SIj6oFJgavWtArs8toeHCPfxOefGMplWSkNvlR9P2Ac= +github.com/MatProGo-dev/SymbolicMath.go v0.2.4-1/go.mod h1:tW8thj4pkaTV9lFNU3OCKmwQ3mZ2Eim6S4JpHRDfRvU= github.com/MatProGo-dev/SymbolicMath.go v0.2.4 h1:SxvgOJBpx9H6ZHISyF3A79gOd1pHJd8Nywrqf4sJZTs= github.com/MatProGo-dev/SymbolicMath.go v0.2.4/go.mod h1:tW8thj4pkaTV9lFNU3OCKmwQ3mZ2Eim6S4JpHRDfRvU= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= diff --git a/problem/optimization_problem.go b/problem/optimization_problem.go index 3bb252c..97da2c2 100644 --- a/problem/optimization_problem.go +++ b/problem/optimization_problem.go @@ -900,30 +900,12 @@ func (op *OptimizationProblem) WithAllPositiveVariableConstraintsRemoved() *Opti newProblem.Variables = append(newProblem.Variables, variable) } - // Copy the constraints - for _, constraintII := range op.Constraints { - // Check if the constraint is a x >= 0 constraint - if symbolic.SenseGreaterThanEqual == constraintII.ConstrSense() { - lhsContains1Variable := len(constraintII.Left().Variables()) == 1 - rhs, rhsIsConstant := constraintII.Right().(symbolic.K) - if lhsContains1Variable && rhsIsConstant { - if float64(rhs) == 0.0 { - // If the constraint is of the form x >= 0, we can remove it - continue - } - } - } - - // Check if the constraint is a 0 <= x constraint - if symbolic.SenseLessThanEqual == constraintII.ConstrSense() { - rhsContains1Variable := len(constraintII.Left().Variables()) == 1 - lhs, lhsIsConstant := constraintII.Right().(symbolic.K) - if rhsContains1Variable && lhsIsConstant { - if float64(lhs) == 0.0 { - // If the constraint is of the form 0 <= x, we can remove it - continue - } - } + // Reduce the constraints to scalar constraints + scalarConstraints := symbolic.CompileConstraintsIntoScalarConstraints(op.Constraints) + for _, constraintII := range scalarConstraints { + // Check if the constraint is a (Non-negativity) x >= 0 or 0 <= x constraint + if constraintII.IsNonnegativityConstraint() { + continue } // Otherwise, we can keep the constraint diff --git a/testing/problem/optimization_problem_test.go b/testing/problem/optimization_problem_test.go index c14d4d7..9bb6f27 100644 --- a/testing/problem/optimization_problem_test.go +++ b/testing/problem/optimization_problem_test.go @@ -2347,9 +2347,12 @@ func TestOptimizationProblem_ToLPStandardForm1_2(t *testing.T) { } // Check that the number of constraints is as expected. - if len(p2.Constraints) != 1 { - t.Errorf("expected the number of constraints to be %v; received %v", - 5, len(p2.Constraints)) + if len(p2.Constraints) != 5 { + t.Errorf( + "expected the number of constraints to be %v; received %v", + 5, + len(p2.Constraints), + ) } // Verify that all constraints are equality constraints @@ -2414,9 +2417,12 @@ func TestOptimizationProblem_ToLPStandardForm1_3(t *testing.T) { } // Check that the number of constraints is as expected. - if len(p2.Constraints) != 1 { - t.Errorf("expected the number of constraints to be %v; received %v", - expectedNumVariables, len(p2.Constraints)) + if len(p2.Constraints) != A2.Dims()[0] { + t.Errorf( + "expected the number of constraints to be %v; received %v", + A2.Dims()[0], + len(p2.Constraints), + ) } // Verify that all constraints are equality constraints From 98f8c290ba26ef7cc24566f25d16d9d011991986 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Fri, 29 Aug 2025 15:43:19 -0400 Subject: [PATCH 6/7] Updated some tests --- problem/optimization_problem.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/problem/optimization_problem.go b/problem/optimization_problem.go index 97da2c2..874ab7e 100644 --- a/problem/optimization_problem.go +++ b/problem/optimization_problem.go @@ -661,6 +661,12 @@ Description: Where A is a matrix of coefficients, b is a vector of constants, and c is the vector of coefficients for the objective function. This method also returns the slack variables (i.e., the variables that are added to the problem to convert the inequalities into equalities). + +Note: + + This method will transform the vector or matrix constraints in the input problem + into a set of scalar constraints. Thus, the number of constraints in your problem may + "seem" to change. */ func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem, []symbolic.Variable, error) { // Input Processing @@ -674,8 +680,8 @@ func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem, return nil, nil, problemIn.CheckIfLinear() } - // Setup - problemWithAllPositiveVariables, err := problemIn.ToProblemWithAllPositiveVariables() + // Change the problem so that it is written in terms of strictly positive variables + problemWithAllPositiveVariables, err := problemIn.ToProblemWithAllPositiveVariables() // Note: This method may change the number of variables and constraints in the problem. if err != nil { return nil, nil, err } From 9adcbffabf4490798d80a627c3a085c5b91ff2c0 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Fri, 29 Aug 2025 15:49:05 -0400 Subject: [PATCH 7/7] Removing unused cases in ToLPStandardForm1 --- problem/optimization_problem.go | 110 +++++++------------------------- 1 file changed, 24 insertions(+), 86 deletions(-) diff --git a/problem/optimization_problem.go b/problem/optimization_problem.go index 874ab7e..248adbc 100644 --- a/problem/optimization_problem.go +++ b/problem/optimization_problem.go @@ -715,94 +715,32 @@ func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem, case symbolic.SenseEqual: // No need to do anything case symbolic.SenseGreaterThanEqual: - switch concreteConstraint := constraint.(type) { - case symbolic.ScalarConstraint: - // Add a new SCALAR slack variable to the right hand side - problemInStandardForm.AddVariableClassic(0.0, symbolic.Infinity.Constant(), symbolic.Continuous) - nVariables := len(problemInStandardForm.Variables) - problemInStandardForm.Variables[nVariables-1].Name = problemInStandardForm.Variables[nVariables-1].Name + " (slack)" - slackVariables = append( - slackVariables, - problemInStandardForm.Variables[nVariables-1], - ) - - newRHS = newRHS.Plus(problemInStandardForm.Variables[nVariables-1]) - case symbolic.VectorConstraint: - // Add a new VECTOR slack variable to the right hand side - // TODO(Kwesi): Revisit this when we have a proper Len() method for constraints. - dims := concreteConstraint.Dims() - nRows := dims[0] - problemInStandardForm.AddVariableVectorClassic( - nRows, - 0.0, - symbolic.Infinity.Constant(), - symbolic.Continuous, - ) - nVariables := len(problemInStandardForm.Variables) - for jj := nRows - 1; jj >= 0; jj-- { - problemInStandardForm.Variables[nVariables-1-jj].Name = problemInStandardForm.Variables[nVariables-1-jj].Name + " (slack)" - slackVariables = append( - slackVariables, - problemInStandardForm.Variables[nVariables-1-jj], - ) - } - - // Add the slack variable to the right hand side - newRHS = newRHS.Plus( - symbolic.VariableVector(problemInStandardForm.Variables[nVariables-nRows : nVariables]), - ) - default: - return nil, nil, fmt.Errorf( - "Unexpected constraint type: %T for \"ToStandardFormWithSlackVariables\" with %v sense", - constraint, - constraint.ConstrSense(), - ) + // Constraints MUST be scalar at this point + + // Add a new SCALAR slack variable to the right hand side + problemInStandardForm.AddVariableClassic(0.0, symbolic.Infinity.Constant(), symbolic.Continuous) + nVariables := len(problemInStandardForm.Variables) + problemInStandardForm.Variables[nVariables-1].Name = problemInStandardForm.Variables[nVariables-1].Name + " (slack)" + slackVariables = append( + slackVariables, + problemInStandardForm.Variables[nVariables-1], + ) + + newRHS = newRHS.Plus(problemInStandardForm.Variables[nVariables-1]) - } case symbolic.SenseLessThanEqual: - // Use a switch statement to handle different dimensions of the constraint - switch concreteConstraint := constraint.(type) { - case symbolic.ScalarConstraint: - // Add a new SCALAR slack variable to the left hand side - problemInStandardForm.AddVariableClassic(0.0, symbolic.Infinity.Constant(), symbolic.Continuous) - nVariables := len(problemInStandardForm.Variables) - problemInStandardForm.Variables[nVariables-1].Name = problemInStandardForm.Variables[nVariables-1].Name + " (slack)" - slackVariables = append( - slackVariables, - problemInStandardForm.Variables[nVariables-1], - ) - newLHS = newLHS.Plus(problemInStandardForm.Variables[nVariables-1]) - case symbolic.VectorConstraint: - // Add a new VECTOR slack variable to the left hand side - // TODO(Kwesi): Revisit this when we have a proper Len() method for constraints. - dims := concreteConstraint.Dims() - nRows := dims[0] - problemInStandardForm.AddVariableVectorClassic( - nRows, - 0.0, - symbolic.Infinity.Constant(), - symbolic.Continuous, - ) - nVariables := len(problemInStandardForm.Variables) - for jj := nRows - 1; jj >= 0; jj-- { - problemInStandardForm.Variables[nVariables-1-jj].Name = problemInStandardForm.Variables[nVariables-1-jj].Name + " (slack)" - slackVariables = append( - slackVariables, - problemInStandardForm.Variables[nVariables-1-jj], - ) - // fmt.Printf("Slack variable %d: %v\n", jj, problemInStandardForm.Variables[nVariables-1-jj]) - } - // Add the slack variable to the left hand side - newLHS = newLHS.Plus( - symbolic.VariableVector(problemInStandardForm.Variables[nVariables-nRows : nVariables]), - ) - default: - return nil, nil, fmt.Errorf( - "Unexpected constraint type %T for \"ToStandardFormWithSlackVariables\" with %v sense", - constraint, - constraint.ConstrSense(), - ) - } + // Constraints MUST be scalar at this point + + // Add a new SCALAR slack variable to the left hand side + problemInStandardForm.AddVariableClassic(0.0, symbolic.Infinity.Constant(), symbolic.Continuous) + nVariables := len(problemInStandardForm.Variables) + problemInStandardForm.Variables[nVariables-1].Name = problemInStandardForm.Variables[nVariables-1].Name + " (slack)" + slackVariables = append( + slackVariables, + problemInStandardForm.Variables[nVariables-1], + ) + newLHS = newLHS.Plus(problemInStandardForm.Variables[nVariables-1]) + default: return nil, nil, fmt.Errorf( "Unknown constraint sense: " + constraint.ConstrSense().String(),