Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions examples/constraints1/vector_constraints.go
Original file line number Diff line number Diff line change
@@ -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)
}
45 changes: 45 additions & 0 deletions symbolic/basic_environment.go
Original file line number Diff line number Diff line change
@@ -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")
20 changes: 4 additions & 16 deletions symbolic/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Comment on lines +9 to 13
Copy link

Copilot AI Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing Environment from a concrete struct to an interface is a breaking API change. Consider adding a migration path: e.g., provide a type alias for the old struct (if applicable), document the change, and add deprecated wrapper constructors/functions that accept *BasicEnvironment to ease transition, or add helper constructors like NewBasicEnvironmentPtr() returning *BasicEnvironment for callers that previously passed *Environment.

Copilot uses AI. Check for mistakes.
49 changes: 29 additions & 20 deletions symbolic/variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ type Variable struct {
Upper float64
Type VarType
Name string
// Environment is the environment that the variable belongs to.
Environment Environment
}

/*
Expand Down Expand Up @@ -416,7 +418,7 @@ func (v Variable) Transpose() Expression {
NewVariable
Description:
*/
func NewVariable(envs ...*Environment) Variable {
func NewVariable(envs ...Environment) Variable {
return NewContinuousVariable(envs...)
}

Expand All @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions symbolic/variable_matrix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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...)
}

Expand All @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions symbolic/variable_vector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
106 changes: 106 additions & 0 deletions testing/symbolic/polynomial_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
}
}
Loading