diff --git a/examples/constraints1/vector_constraints.go b/examples/constraints1/vector_constraints.go new file mode 100644 index 0000000..d6d5c4c --- /dev/null +++ b/examples/constraints1/vector_constraints.go @@ -0,0 +1,26 @@ +package main + +import ( + "fmt" + + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" + + "gonum.org/v1/gonum/mat" +) + +func main() { + // Create the variables + N := 2 + x := symbolic.NewVariableVector(N) + + // Create the constraints + // - x >= -1 + c1 := x.GreaterEq(mat.NewVecDense(N, []float64{-1, -1})) + + // - x <= 1 + c2 := x.LessEq(symbolic.OnesVector(N)) + + // Print the constraints + fmt.Println("Constraint 1:", c1) + fmt.Println("Constraint 2:", c2) +} diff --git a/symbolic/basic_environment.go b/symbolic/basic_environment.go new file mode 100644 index 0000000..b7acf94 --- /dev/null +++ b/symbolic/basic_environment.go @@ -0,0 +1,45 @@ +package symbolic + +type BasicEnvironment struct { + name string + Variables []Variable +} + +func (be *BasicEnvironment) GetName() string { + return be.name +} + +func (be *BasicEnvironment) TrackVariable(v Variable) bool { + // Check if the variable is already in the environment + for _, existingVar := range be.Variables { + if existingVar.ID == v.ID { + // Variable already exists, do not add it again + return false + } + } + + // Add the variable to the environment + be.Variables = append(be.Variables, v) + return true // Variable was added successfully +} + +func (be *BasicEnvironment) AllTrackedVariables() []Variable { + return be.Variables +} + +/* +Public Functions +*/ + +func MakeBasicEnvironment(nameIn string) BasicEnvironment { + return BasicEnvironment{ + name: nameIn, + Variables: []Variable{}, + } +} + +/* +DefaultEnvironment +A variable that exists in the background and used to store information about the variables currently created. +*/ +var DefaultEnvironment = MakeBasicEnvironment("DefaultEnvironment") diff --git a/symbolic/environment.go b/symbolic/environment.go index f387d48..eecc3a4 100644 --- a/symbolic/environment.go +++ b/symbolic/environment.go @@ -6,20 +6,8 @@ Description: Defines the environment where the symbolic variables are stored. */ -type Environment struct { - Name string - Variables []Variable -} - -/* -Public Functions -*/ - -/* -BackgroundEnvironment -A variable that exists in the background and used to store information about the variables currently created. -*/ -var BackgroundEnvironment = Environment{ - Name: "Background", - Variables: []Variable{}, +type Environment interface { + GetName() string + TrackVariable(v Variable) bool + AllTrackedVariables() []Variable } diff --git a/symbolic/variable.go b/symbolic/variable.go index adcc3c7..ad0a8bd 100644 --- a/symbolic/variable.go +++ b/symbolic/variable.go @@ -15,6 +15,8 @@ type Variable struct { Upper float64 Type VarType Name string + // Environment is the environment that the variable belongs to. + Environment Environment } /* @@ -416,7 +418,7 @@ func (v Variable) Transpose() Expression { NewVariable Description: */ -func NewVariable(envs ...*Environment) Variable { +func NewVariable(envs ...Environment) Variable { return NewContinuousVariable(envs...) } @@ -426,30 +428,31 @@ Description: Creates a new continuous variable. */ -func NewContinuousVariable(envs ...*Environment) Variable { +func NewContinuousVariable(envs ...Environment) Variable { // Constants // Input Processing - var currentEnv = &BackgroundEnvironment + var currentEnv Environment = &DefaultEnvironment switch len(envs) { case 1: currentEnv = envs[0] } // Get New Index - nextIdx := len(currentEnv.Variables) + nextIdx := len(currentEnv.AllTrackedVariables()) // Create variable variableOut := Variable{ - ID: uint64(nextIdx), - Lower: float64(-Infinity), - Upper: float64(+Infinity), - Type: Continuous, - Name: fmt.Sprintf("x_%v", nextIdx), + ID: uint64(nextIdx), + Lower: float64(-Infinity), + Upper: float64(+Infinity), + Type: Continuous, + Name: fmt.Sprintf("x_%v", nextIdx), + Environment: currentEnv, } // Update environment - currentEnv.Variables = append(currentEnv.Variables, variableOut) + currentEnv.TrackVariable(variableOut) return variableOut @@ -461,30 +464,31 @@ Description: Creates a new binary variable. */ -func NewBinaryVariable(envs ...*Environment) Variable { +func NewBinaryVariable(envs ...Environment) Variable { // Constants // Input Processing - var currentEnv = &BackgroundEnvironment + var currentEnv Environment = &DefaultEnvironment switch len(envs) { case 1: currentEnv = envs[0] } // Get New Index - nextIdx := len(currentEnv.Variables) + nextIdx := len(currentEnv.AllTrackedVariables()) // Get New Variable Object and add it to environment variableOut := Variable{ - ID: uint64(nextIdx), - Lower: 0.0, - Upper: 1.0, - Type: Binary, - Name: fmt.Sprintf("x_%v", nextIdx), + ID: uint64(nextIdx), + Lower: 0.0, + Upper: 1.0, + Type: Binary, + Name: fmt.Sprintf("x_%v", nextIdx), + Environment: currentEnv, } // Update env - currentEnv.Variables = append(currentEnv.Variables, variableOut) + currentEnv.TrackVariable(variableOut) return variableOut @@ -588,7 +592,12 @@ func (v Variable) Substitute(vIn Variable, seIn ScalarExpression) Expression { } // Algorithm - if v.ID == vIn.ID { + // Replace the variable if: + // 1. The IDs are the same + // 2. The environment is the same + idsMatch := v.ID == vIn.ID + envsMatch := v.Environment == vIn.Environment + if idsMatch && envsMatch { return seIn } else { return v diff --git a/symbolic/variable_matrix.go b/symbolic/variable_matrix.go index 444f032..21b2812 100644 --- a/symbolic/variable_matrix.go +++ b/symbolic/variable_matrix.go @@ -564,7 +564,7 @@ Description: This function creates a new variable matrix and properly initializes each element in it. */ -func NewVariableMatrix(nRows, nCols int, envs ...*Environment) VariableMatrix { +func NewVariableMatrix(nRows, nCols int, envs ...Environment) VariableMatrix { return NewVariableMatrixClassic(nRows, nCols, envs...) } @@ -576,12 +576,12 @@ Description: and properly initializes each element in it when given a list of variables. */ -func NewVariableMatrixClassic(nRows, nCols int, envs ...*Environment) VariableMatrix { +func NewVariableMatrixClassic(nRows, nCols int, envs ...Environment) VariableMatrix { // Collect an environment if one exists - var env *Environment + var env Environment switch len(envs) { case 0: - env = &BackgroundEnvironment + env = &DefaultEnvironment case 1: env = envs[0] default: diff --git a/symbolic/variable_vector.go b/symbolic/variable_vector.go index c67c71b..3c83820 100644 --- a/symbolic/variable_vector.go +++ b/symbolic/variable_vector.go @@ -472,11 +472,11 @@ Description: Returns a new VariableVector object. */ -func NewVariableVector(N int, envs ...*Environment) VariableVector { +func NewVariableVector(N int, envs ...Environment) VariableVector { // Constants // Input Processing - var currentEnv = &BackgroundEnvironment + var currentEnv Environment = &DefaultEnvironment switch len(envs) { case 1: currentEnv = envs[0] diff --git a/testing/symbolic/polynomial_test.go b/testing/symbolic/polynomial_test.go index 04ad865..81799da 100644 --- a/testing/symbolic/polynomial_test.go +++ b/testing/symbolic/polynomial_test.go @@ -2324,3 +2324,109 @@ func TestPolynomial_SubstituteWith4(t *testing.T) { } } } + +/* +TestPolynomial_SubstituteAccordingTo1 +Description: + + This test verifies that when we try to substitute a polynomial + expression (containing two variables) with: + 1. a substitution map that contains the first of the two variables + (x1 in the default environment) + 2. a substitution map that contains a variable that is not in the + expression (x1 in a new environment) +*/ +func TestPolynomial_SubstituteAccordingTo1(t *testing.T) { + // Constants + x1 := symbolic.NewVariable() + x2 := symbolic.NewVariable() + poly := symbolic.K(3).Multiply(x1).Minus( + symbolic.K(4).Multiply(x2), + ).Plus( + symbolic.K(5), + ) + + // 1. Substitution map that contains x1 in the default environment + varMap1 := map[symbolic.Variable]symbolic.Expression{ + x1: symbolic.K(5), + } + sub1 := poly.SubstituteAccordingTo(varMap1) + sub1AsPoly, tf := sub1.(symbolic.Polynomial) + if !tf { + t.Errorf( + "expected %v.substituteAccordingTo(%v) to be a polynomial; received %T", + poly, + varMap1, + sub1, + ) + } + + // Verify that the substituted polynomial has the correct constant and linear coefficients + constantFound := false + x2CoeffFound := false + for _, m := range sub1AsPoly.Monomials { + if m.IsConstant() { + constantFound = true + if m.Coefficient != 20.0 { + t.Errorf( + "expected %v.substituteAccordingTo(%v) to have constant 20.0; received %v", + poly, + varMap1, + m.Coefficient, + ) + } + } else if len(m.Variables()) == 1 && m.Variables()[0] == x2 { + x2CoeffFound = true + if m.Coefficient != -4.0 { + t.Errorf( + "expected %v.substituteAccordingTo(%v) to have x2 coefficient -4.0; received %v", + poly, + varMap1, + m.Coefficient, + ) + } + } + } + if !constantFound { + t.Errorf( + "expected %v.substituteAccordingTo(%v) to have a constant term; received none", + poly, + varMap1, + ) + } + if !x2CoeffFound { + t.Errorf( + "expected %v.substituteAccordingTo(%v) to have an x2 term; received none", + poly, + varMap1, + ) + } + + // 2. Substitution map that contains x1 in a new environment + newEnv := symbolic.MakeBasicEnvironment("test-Polynomial-SubstituteAccordingTo1-2") + x1NewEnv := symbolic.NewVariable(&newEnv) + varMap2 := map[symbolic.Variable]symbolic.Expression{ + x1NewEnv: symbolic.K(5), + } + sub2 := poly.SubstituteAccordingTo(varMap2) + sub2AsPoly, tf := sub2.(symbolic.Polynomial) + if !tf { + t.Errorf( + "expected %v.substituteAccordingTo(%v) to be a polynomial; received %T", + poly, + varMap2, + sub2, + ) + } + + // Verify that the substituted polynomial is the same as the original polynomial + if !reflect.DeepEqual(poly, sub2AsPoly) { + t.Errorf( + "expected %v.substituteAccordingTo(%v) to be %v; received %v", + poly, + varMap2, + poly, + sub2AsPoly, + ) + } +} diff --git a/testing/symbolic/utils_test.go b/testing/symbolic/utils_test.go index 572a269..08588fa 100644 --- a/testing/symbolic/utils_test.go +++ b/testing/symbolic/utils_test.go @@ -8,8 +8,9 @@ Description: import ( "fmt" - "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "testing" + + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" ) /* @@ -403,7 +404,15 @@ Description: */ func TestUtils_CheckSubstitutionMap1(t *testing.T) { // Constants - badVar := symbolic.Variable{2, -1, -2, symbolic.Binary, "Russ"} + var currentEnv symbolic.Environment = &symbolic.DefaultEnvironment + badVar := symbolic.Variable{ + ID: 2, + Lower: -1, + Upper: -2, + Type: symbolic.Binary, + Name: "Russ", + Environment: currentEnv, + } varMap := map[symbolic.Variable]symbolic.Expression{ symbolic.NewVariable(): symbolic.K(3), badVar: symbolic.K(4), @@ -441,7 +450,15 @@ Description: func TestUtils_CheckSubstitutionMap2(t *testing.T) { // Constants goodVar := symbolic.NewVariable() - badVar := symbolic.Variable{2, -1, -2, symbolic.Binary, "Russ"} + var currentEnv symbolic.Environment = &symbolic.DefaultEnvironment + badVar := symbolic.Variable{ + ID: 2, + Lower: -1, + Upper: -2, + Type: symbolic.Binary, + Name: "Russ", + Environment: currentEnv, + } varMap := map[symbolic.Variable]symbolic.Expression{ symbolic.NewVariable(): symbolic.K(3), goodVar: badVar, diff --git a/testing/symbolic/variable_test.go b/testing/symbolic/variable_test.go index 1f25da1..d20bf89 100644 --- a/testing/symbolic/variable_test.go +++ b/testing/symbolic/variable_test.go @@ -1331,9 +1331,7 @@ Description: */ func TestVariable_NewBinaryVariable2(t *testing.T) { // Constants - env := symbolic.Environment{ - Name: "test-nbv2", - } + env := symbolic.MakeBasicEnvironment("test-env") symbolic.NewVariable(&env) x := symbolic.NewBinaryVariable(&env)