From 6c4ba0286e397632528fe099cc3e1d69c7317c97 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Mon, 29 Sep 2025 22:08:40 -0400 Subject: [PATCH 01/16] Introduce AsSimplifiedExpression() method for simplification and used it to fix some of the substitute methods for Polynomials --- symbolic/constant.go | 10 ++++ symbolic/constant_matrix.go | 11 ++++ symbolic/constant_vector.go | 10 ++++ symbolic/expression.go | 5 ++ symbolic/matrix_expression.go | 3 ++ symbolic/monomial.go | 53 ++++++++++++++++++ symbolic/monomial_matrix.go | 63 ++++++++++++++++++---- symbolic/monomial_vector.go | 28 ++++++++++ symbolic/polynomial.go | 8 ++- symbolic/polynomial_like.go | 3 ++ symbolic/polynomial_like_matrix.go | 3 ++ symbolic/polynomial_like_scalar.go | 3 ++ symbolic/polynomial_like_vector.go | 3 ++ symbolic/polynomial_matrix.go | 20 +++++-- symbolic/polynomial_vector.go | 25 ++++++--- symbolic/scalar_expression.go | 4 ++ symbolic/variable.go | 10 ++++ symbolic/variable_matrix.go | 10 ++++ symbolic/variable_vector.go | 10 ++++ symbolic/vector_expression.go | 3 ++ testing/symbolic/polynomial_test.go | 50 +++++++++++++++-- testing/symbolic/polynomial_vector_test.go | 26 +++++++-- 22 files changed, 332 insertions(+), 29 deletions(-) diff --git a/symbolic/constant.go b/symbolic/constant.go index 6d08e01..ee2485c 100644 --- a/symbolic/constant.go +++ b/symbolic/constant.go @@ -437,3 +437,13 @@ func (c K) At(ii, jj int) ScalarExpression { return c } + +/* +AsSimplifiedExpression +Description: + + Returns the simplest form of the expression. +*/ +func (c K) AsSimplifiedExpression() Expression { + return c +} diff --git a/symbolic/constant_matrix.go b/symbolic/constant_matrix.go index b1da688..f5575f5 100644 --- a/symbolic/constant_matrix.go +++ b/symbolic/constant_matrix.go @@ -747,3 +747,14 @@ Description: func (km KMatrix) Power(exponent int) Expression { return MatrixPowerTemplate(km, exponent) } + +/* +AsSimplifiedExpression +Description: + + Simplifies the constant matrix. Since the constant matrix is always in simplest form, + this function simply returns the original constant matrix. +*/ +func (km KMatrix) AsSimplifiedExpression() Expression { + return km +} diff --git a/symbolic/constant_vector.go b/symbolic/constant_vector.go index 63414f2..1a50405 100644 --- a/symbolic/constant_vector.go +++ b/symbolic/constant_vector.go @@ -665,3 +665,13 @@ Description: func (kv KVector) Power(exponent int) Expression { return VectorPowerTemplate(kv, exponent) } + +/* +AsSimplifiedExpression +Description: + + Returns the simplest form of the expression. +*/ +func (kv KVector) AsSimplifiedExpression() Expression { + return kv +} diff --git a/symbolic/expression.go b/symbolic/expression.go index b47b9fd..ec298cb 100644 --- a/symbolic/expression.go +++ b/symbolic/expression.go @@ -70,6 +70,9 @@ type Expression interface { // At returns the value at the given row and column index At(ii, jj int) ScalarExpression + + // Simplify simplifies the expression and returns the simplified version + AsSimplifiedExpression() Expression } /* @@ -294,6 +297,8 @@ func ConcretizeExpression(e interface{}) Expression { concrete Expression ) switch concreteVal := e.(type) { + case ScalarExpression: + concrete = concreteVal.AsSimplifiedExpression() case []ScalarExpression: concreteVectorE := ConcretizeVectorExpression(concreteVal) // If vector expression is a scalar (i.e., has 1 row), return the scalar expression diff --git a/symbolic/matrix_expression.go b/symbolic/matrix_expression.go index ef74937..7e17ed0 100644 --- a/symbolic/matrix_expression.go +++ b/symbolic/matrix_expression.go @@ -79,6 +79,9 @@ type MatrixExpression interface { // Power // Raises the scalar expression to the power of the input integer Power(exponent int) Expression + + // Simplify simplifies the expression and returns the simplified version + AsSimplifiedExpression() Expression } /* diff --git a/symbolic/monomial.go b/symbolic/monomial.go index 07b7eb1..6ab3047 100644 --- a/symbolic/monomial.go +++ b/symbolic/monomial.go @@ -775,3 +775,56 @@ func (m Monomial) At(ii, jj int) ScalarExpression { // Algorithm return m } + +/* +AsSimplifiedExpression +Description: + + Returns the simplest form of the expression. + - If the monomial contains no variables, + then it is simply the constant coefficient. (return K(m.Coefficient)) + - If the monomial coefficient is zero, + then it is simply the constant zero. (return K(0)) + - If the monomial contains variables BUT all exponents are zero, + then it is simply the constant zero. (return K(0)) + - If the monomial's coefficient is 1.0 and it contains one variable with degree 1, + then it is simply that variable. (return that variable) + - Otherwise, return the monomial itself. +*/ +func (m Monomial) AsSimplifiedExpression() Expression { + // Input Processing + err := m.Check() + if err != nil { + panic(err) + } + + // Algorithm + // - If the monomial is a constant, return the constant + if m.IsConstant() { + return K(m.Coefficient) + } + // - If the monomial's coefficient is zero, return zero + if m.Coefficient == 0.0 { + return K(0.0) + } + // - If the monomial's coefficient is 1.0 and it contains one variable with degree 1, + // then return that variable + if (m.Coefficient == 1.0) && (len(m.VariableFactors) == 1) && (m.Exponents[0] == 1) { + return m.VariableFactors[0] + } + + // - If the monomial contains variables BUT all exponents are zero, + // then return zero + allExponentsZero := true + for _, exp := range m.Exponents { + if exp != 0 { + allExponentsZero = false + break + } + } + if allExponentsZero { + return K(m.Coefficient) + } + + return m +} diff --git a/symbolic/monomial_matrix.go b/symbolic/monomial_matrix.go index dbe8f78..85a4607 100644 --- a/symbolic/monomial_matrix.go +++ b/symbolic/monomial_matrix.go @@ -310,28 +310,38 @@ func (mm MonomialMatrix) Multiply(e interface{}) Expression { return product case VariableVector: if nRows == 1 { - // Output will be a polynomial - var product Polynomial + // Output will be a scalar expression + var product Expression = K(0.0) for ii, monomial := range mm[0] { - product.Monomials = append(product.Monomials, monomial.Multiply(right[ii]).(Monomial)) + product = product.Plus(monomial.Multiply(right[ii])) } - return product.Simplify() + return ConcretizeExpression(product) } else { // Output will be a polynomial matrix - var product PolynomialVector + //TODO: Add thorough tests of this. + var product []ScalarExpression for _, row := range mm { - product_ii := row[0].ToPolynomial().Multiply(right[0]).(Polynomial) + product_ii := row[0].ToPolynomial().Multiply(right[0]) for jj := 1; jj < len(row); jj++ { product_ii = product_ii.Plus( row[jj].ToPolynomial().Multiply(right[jj]), ).(Polynomial) } - product = append(product, product_ii) - product = product.Simplify() + // Enforce that product_ii is a ScalarExpression + product_iiAsSE, tf := product_ii.(ScalarExpression) + if !tf { + panic( + fmt.Errorf( + "error converting product row to ScalarExpression; got type %T", + product_ii, + ), + ) + } + product = append(product, product_iiAsSE) } - return product + return ConcretizeVectorExpression(product) } } @@ -676,3 +686,38 @@ Description: func (mm MonomialMatrix) Power(exponent int) Expression { return MatrixPowerTemplate(mm, exponent) } + +/* +AsSimplifiedExpression +Description: + + Returns the simplest form of the expression. +*/ +func (mm MonomialMatrix) AsSimplifiedExpression() Expression { + // Input Processing + err := mm.Check() + if err != nil { + panic(err) + } + + // Create container for simplified matrix + dims := mm.Dims() + nRows, nCols := dims[0], dims[1] + var simplifiedMM [][]ScalarExpression + for ii := 0; ii < nRows; ii++ { + simplifiedRow := make([]ScalarExpression, nCols) + for jj := 0; jj < nCols; jj++ { + simplified := mm[ii][jj].AsSimplifiedExpression() + simplifiedAsSE, tf := simplified.(ScalarExpression) + if !tf { + panic(fmt.Errorf("error simplifying monomial matrix entry %v,%v", ii, jj)) + } + // Save the converted simplified expression + simplifiedRow[jj] = simplifiedAsSE + } + simplifiedMM = append(simplifiedMM, simplifiedRow) + } + + // Return the simplified matrix + return ConcretizeMatrixExpression(simplifiedMM) +} diff --git a/symbolic/monomial_vector.go b/symbolic/monomial_vector.go index 8022070..c263ff2 100644 --- a/symbolic/monomial_vector.go +++ b/symbolic/monomial_vector.go @@ -731,3 +731,31 @@ Description: func (mv MonomialVector) LinearCoeff(wrt ...[]Variable) mat.Dense { return PolynomialLikeVector_SharedLinearCoeffCalc(mv, wrt...) } + +/* +AsSimplifiedExpression +Description: + + Returns the simplest form of the expression. +*/ +func (mv MonomialVector) AsSimplifiedExpression() Expression { + // Input Processing + err := mv.Check() + if err != nil { + panic(err) + } + + // Simplify each monomial in the vector + var out []ScalarExpression + for ii, monomial := range mv { + simplified := monomial.AsSimplifiedExpression() + simplifiedAsSE, tf := simplified.(ScalarExpression) + if !tf { + panic(fmt.Errorf("error simplifying monomial vector entry %v", ii)) + } + // Add the simplified version of the monomial to the output + out = append(out, simplifiedAsSE) + } + + return ConcretizeVectorExpression(out) +} diff --git a/symbolic/polynomial.go b/symbolic/polynomial.go index 9e2b3a7..1fbc184 100644 --- a/symbolic/polynomial.go +++ b/symbolic/polynomial.go @@ -600,6 +600,10 @@ func (p Polynomial) Simplify() Polynomial { } +func (p Polynomial) AsSimplifiedExpression() Expression { + return p.Simplify() +} + /* DerivativeWrt Description: @@ -794,10 +798,10 @@ func (p Polynomial) Substitute(vIn Variable, eIn ScalarExpression) Expression { var out Expression = K(0.0) for _, monomial := range p.Monomials { newMonomial := monomial.Substitute(vIn, eIn) - out = out.Plus(newMonomial).(Polynomial).Simplify() + out = out.Plus(newMonomial) } - return out + return out.AsSimplifiedExpression() } /* diff --git a/symbolic/polynomial_like.go b/symbolic/polynomial_like.go index 6eb3281..4063579 100644 --- a/symbolic/polynomial_like.go +++ b/symbolic/polynomial_like.go @@ -72,6 +72,9 @@ type PolynomialLike interface { // At returns the value at the given row and column index At(ii, jj int) ScalarExpression + + // Simplify simplifies the expression and returns the simplified version + AsSimplifiedExpression() Expression } /* diff --git a/symbolic/polynomial_like_matrix.go b/symbolic/polynomial_like_matrix.go index 0bb051a..fe21f56 100644 --- a/symbolic/polynomial_like_matrix.go +++ b/symbolic/polynomial_like_matrix.go @@ -80,6 +80,9 @@ type PolynomialLikeMatrix interface { // Power returns the expression raised to the power of the input exponent Power(exponent int) Expression + + // Simplify simplifies the expression and returns the simplified version + AsSimplifiedExpression() Expression } /* diff --git a/symbolic/polynomial_like_scalar.go b/symbolic/polynomial_like_scalar.go index 4fa225f..a6afe45 100644 --- a/symbolic/polynomial_like_scalar.go +++ b/symbolic/polynomial_like_scalar.go @@ -78,6 +78,9 @@ type PolynomialLikeScalar interface { // At returns the value at the given row and column index At(ii, jj int) ScalarExpression + + // Simplify simplifies the expression and returns the simplified version + AsSimplifiedExpression() Expression } /* diff --git a/symbolic/polynomial_like_vector.go b/symbolic/polynomial_like_vector.go index 0cfbe36..f0eb565 100644 --- a/symbolic/polynomial_like_vector.go +++ b/symbolic/polynomial_like_vector.go @@ -97,6 +97,9 @@ type PolynomialLikeVector interface { // Power returns the expression raised to the power of the input exponent Power(exponent int) Expression + + // Simplify simplifies the expression and returns the simplified version + AsSimplifiedExpression() Expression } /* diff --git a/symbolic/polynomial_matrix.go b/symbolic/polynomial_matrix.go index bfa3ea4..7140d94 100644 --- a/symbolic/polynomial_matrix.go +++ b/symbolic/polynomial_matrix.go @@ -557,22 +557,32 @@ Description: Simplifies the polynomial matrix, if possible. */ -func (pm PolynomialMatrix) Simplify() PolynomialMatrix { +func (pm PolynomialMatrix) Simplify() MatrixExpression { // Constants nRows, nCols := pm.Dims()[0], pm.Dims()[1] // Fill container with simplified polynomials - var simplified PolynomialMatrix + var simplified [][]ScalarExpression for rowIndex := 0; rowIndex < nRows; rowIndex++ { - tempRow := make([]Polynomial, nCols) + tempRow := make([]ScalarExpression, nCols) for colIndex := 0; colIndex < nCols; colIndex++ { - tempRow[colIndex] = pm[rowIndex][colIndex].Simplify() + // Simplify the polynomial entry + entry := pm[rowIndex][colIndex] + simplifiedAsSE, tf := entry.AsSimplifiedExpression().(ScalarExpression) + if !tf { + panic(fmt.Errorf("error simplifying polynomial matrix entry %v,%v", rowIndex, colIndex)) + } + tempRow[colIndex] = simplifiedAsSE } simplified = append(simplified, tempRow) } // Return simplified polynomial - return simplified + return ConcretizeMatrixExpression(simplified) +} + +func (pm PolynomialMatrix) AsSimplifiedExpression() Expression { + return pm.Simplify() } /* diff --git a/symbolic/polynomial_vector.go b/symbolic/polynomial_vector.go index 16ccdbf..00ae6ad 100644 --- a/symbolic/polynomial_vector.go +++ b/symbolic/polynomial_vector.go @@ -535,7 +535,7 @@ Description: This method simplifies the polynomial vector. */ -func (pv PolynomialVector) Simplify() PolynomialVector { +func (pv PolynomialVector) Simplify() VectorExpression { // Input Processing err := pv.Check() if err != nil { @@ -545,14 +545,27 @@ func (pv PolynomialVector) Simplify() PolynomialVector { // Constants // Algorithm - var simplified PolynomialVector = make([]Polynomial, pv.Len()) - copy(simplified, pv) + var simplified []ScalarExpression - for ii, polynomial := range simplified { - simplified[ii] = polynomial.Simplify() + for ii, polynomial := range pv { + simplifiedEntryII := polynomial.AsSimplifiedExpression() + entryAsSE, ok := simplifiedEntryII.(ScalarExpression) + if !ok { + panic( + fmt.Errorf( + "error converting polynomial vector entry %v to a scalar expression during simplification", + ii, + ), + ) + } + simplified = append(simplified, entryAsSE) } - return simplified + return ConcretizeVectorExpression(simplified) +} + +func (pv PolynomialVector) AsSimplifiedExpression() Expression { + return pv.Simplify() } /* diff --git a/symbolic/scalar_expression.go b/symbolic/scalar_expression.go index 4a1c447..6a3b7f5 100644 --- a/symbolic/scalar_expression.go +++ b/symbolic/scalar_expression.go @@ -77,6 +77,10 @@ type ScalarExpression interface { // At returns the value at the given row and column index At(ii, jj int) ScalarExpression + + // AsSimplifiedExpression + // Simplifies the expression and returns the simplified version + AsSimplifiedExpression() Expression } // NewExpr returns a new expression with a single additive constant value, c, diff --git a/symbolic/variable.go b/symbolic/variable.go index 5188659..f918511 100644 --- a/symbolic/variable.go +++ b/symbolic/variable.go @@ -665,3 +665,13 @@ func UnionOfVariables(varSlices ...[]Variable) []Variable { } return UniqueVars(allVars) } + +/* +AsSimplifiedExpression +Description: + + Simplifies the expression and returns the simplified version. +*/ +func (v Variable) AsSimplifiedExpression() Expression { + return v +} diff --git a/symbolic/variable_matrix.go b/symbolic/variable_matrix.go index 099dfa7..0ff2bf1 100644 --- a/symbolic/variable_matrix.go +++ b/symbolic/variable_matrix.go @@ -765,3 +765,13 @@ Description: func (vm VariableMatrix) Power(exponent int) Expression { return MatrixPowerTemplate(vm, exponent) } + +/* +AsSimplifiedExpression +Description: + + Simplifies the expression and returns the simplified version. +*/ +func (vm VariableMatrix) AsSimplifiedExpression() Expression { + return vm +} diff --git a/symbolic/variable_vector.go b/symbolic/variable_vector.go index 1de2b21..d843da1 100644 --- a/symbolic/variable_vector.go +++ b/symbolic/variable_vector.go @@ -686,3 +686,13 @@ Description: func (vv VariableVector) Power(exponent int) Expression { return VectorPowerTemplate(vv, exponent) } + +/* +AsSimplifiedExpression +Description: + + Simplifies the expression and returns the simplified version. +*/ +func (vv VariableVector) AsSimplifiedExpression() Expression { + return vv +} diff --git a/symbolic/vector_expression.go b/symbolic/vector_expression.go index 4b1309d..bb74bcc 100644 --- a/symbolic/vector_expression.go +++ b/symbolic/vector_expression.go @@ -94,6 +94,9 @@ type VectorExpression interface { // Power returns the expression raised to the power of the input exponent Power(exponent int) Expression + + // Simplify simplifies the expression and returns the simplified version + AsSimplifiedExpression() Expression } ///* diff --git a/testing/symbolic/polynomial_test.go b/testing/symbolic/polynomial_test.go index b87b9a0..e23f893 100644 --- a/testing/symbolic/polynomial_test.go +++ b/testing/symbolic/polynomial_test.go @@ -7,13 +7,14 @@ Description: */ import ( + "reflect" + "strings" + "testing" + getKMatrix "github.com/MatProGo-dev/SymbolicMath.go/get/KMatrix" getKVector "github.com/MatProGo-dev/SymbolicMath.go/get/KVector" "github.com/MatProGo-dev/SymbolicMath.go/smErrors" "github.com/MatProGo-dev/SymbolicMath.go/symbolic" - "reflect" - "strings" - "testing" ) /* @@ -2203,3 +2204,46 @@ func TestPolynomial_Substitute3(t *testing.T) { // Call the Substitute method p1.Substitute(v1, symbolic.NewVariable()) } + +/* +TestPolynomial_SubstituteWith4 +Description: + + Verifies that the Polynomial.Substitute method correctly computes the substitution + when the polynomial is well-defined and the expression used for substitution is well-defined. + We make the polynomial very long and complex to replicate a bug that occurred in one + of the downstream projects. + + p1 = 1 + x1 + x2 + x3 + ... + x20 + substitute x1 with 2. +*/ +func TestPolynomial_SubstituteWith4(t *testing.T) { + // Constants + N := 20 + x := symbolic.NewVariableVector(N) + + // Create a polynomial that is the sum of 1 and all variables in x + sum1 := symbolic.K(1).Plus( + x.Transpose().Multiply(symbolic.OnesVector(N)), + ) + p1, tf := sum1.(symbolic.Polynomial) + if !tf { + t.Errorf( + "expected %v to be a polynomial; received %T", + sum1, + sum1, + ) + } + + // Test + substitution := p1.Substitute(x[0], symbolic.K(2.0)) + if substitution.(symbolic.Polynomial).Monomials[0].Coefficient != 2.0 { + t.Errorf( + "expected %v.substitute(%v, %v) to have coefficient 2.0; received %v", + p1, + x[0], + symbolic.K(2.0), + substitution.(symbolic.Polynomial).Monomials[0].Coefficient, + ) + } +} diff --git a/testing/symbolic/polynomial_vector_test.go b/testing/symbolic/polynomial_vector_test.go index 9ddb461..caf7745 100644 --- a/testing/symbolic/polynomial_vector_test.go +++ b/testing/symbolic/polynomial_vector_test.go @@ -1953,10 +1953,19 @@ func TestPolynomialVector_Simplify1(t *testing.T) { } // Try to simplify - pvOut := pv.Simplify() + simplified := pv.Simplify() + + // Concretize simplified to polynomial vector + pvOut, ok := simplified.(symbolic.PolynomialVector) + if !ok { + t.Errorf( + "Expected pv.Simplify() to return a PolynomialVector; received %T", + simplified, + ) + } // Check each element of pvOut and verify that it has two monomials. - for _, polynomial := range pvOut { + for _, polynomial := range []symbolic.Polynomial(pvOut) { if len(polynomial.Monomials) != 2 { t.Errorf( "Expected polynomial.Monomials to have length 2; received %v", @@ -1983,10 +1992,19 @@ func TestPolynomialVector_Simplify2(t *testing.T) { } // Try to simplify - pvOut := pv.Simplify() + simplified := pv.Simplify() + + // Concretize simplified to polynomial vector + pvOut, ok := simplified.(symbolic.PolynomialVector) + if !ok { + t.Errorf( + "Expected pv.Simplify() to return a PolynomialVector; received %T", + simplified, + ) + } // Check each element of pvOut and verify that it has two monomials. - for _, polynomial := range pvOut { + for _, polynomial := range []symbolic.Polynomial(pvOut) { if len(polynomial.Monomials) != 2 { t.Errorf( "Expected polynomial.Monomials to have length 2; received %v", From 661e28a7e227100f07c04aeb79db80a3927875da Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Mon, 29 Sep 2025 22:13:56 -0400 Subject: [PATCH 02/16] Fixed test for substitution of Polynomial objects (scalar) --- testing/symbolic/polynomial_test.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/testing/symbolic/polynomial_test.go b/testing/symbolic/polynomial_test.go index e23f893..cfc3c8b 100644 --- a/testing/symbolic/polynomial_test.go +++ b/testing/symbolic/polynomial_test.go @@ -2237,13 +2237,19 @@ func TestPolynomial_SubstituteWith4(t *testing.T) { // Test substitution := p1.Substitute(x[0], symbolic.K(2.0)) - if substitution.(symbolic.Polynomial).Monomials[0].Coefficient != 2.0 { - t.Errorf( - "expected %v.substitute(%v, %v) to have coefficient 2.0; received %v", - p1, - x[0], - symbolic.K(2.0), - substitution.(symbolic.Polynomial).Monomials[0].Coefficient, - ) + + // Search for a constant element in the monomials + for _, m := range substitution.(symbolic.Polynomial).Monomials { + if m.IsConstant() { + if m.Coefficient != 3.0 { + t.Errorf( + "expected (%v).substitute(%v, %v) to have constant 3.0; received %v", + p1, + x[0], + symbolic.K(2.0), + m.Coefficient, + ) + } + } } } From 86979f58e5503fcb280c4545f1af896c04aa6fc2 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Mon, 29 Sep 2025 22:30:52 -0400 Subject: [PATCH 03/16] Increased coverage of MonomialMatrix tests + added a MatrixMultiplicationTemplate --- symbolic/constant_matrix.go | 6 ++ symbolic/matrix_expression.go | 60 +++++++++++++++ testing/symbolic/monomial_matrix_test.go | 94 ++++++++++++++++++++++++ 3 files changed, 160 insertions(+) diff --git a/symbolic/constant_matrix.go b/symbolic/constant_matrix.go index f5575f5..c6b3327 100644 --- a/symbolic/constant_matrix.go +++ b/symbolic/constant_matrix.go @@ -373,6 +373,12 @@ func (km KMatrix) Multiply(e interface{}) Expression { return km.Multiply(&right) // Reuse *mat.Dense case case KMatrix: return km.Multiply(right.ToDense()) // Reuse *mat.Dense case + case VariableMatrix: + return MatrixMultiplyTemplate(km, right) + case MonomialMatrix: + return MatrixMultiplyTemplate(km, right) + case PolynomialMatrix: + return MatrixMultiplyTemplate(km, right) } // If we reach this point, the input is not recognized diff --git a/symbolic/matrix_expression.go b/symbolic/matrix_expression.go index 7e17ed0..30c96d1 100644 --- a/symbolic/matrix_expression.go +++ b/symbolic/matrix_expression.go @@ -193,6 +193,66 @@ func MatrixPowerTemplate(me MatrixExpression, exponent int) MatrixExpression { return out } +/* +MatrixMultiplyTemplate +Description: + + Template for the matrix multiply function. +*/ +func MatrixMultiplyTemplate(left MatrixExpression, right MatrixExpression) MatrixExpression { + // Input Processing + err := left.Check() + if err != nil { + panic(err) + } + + err = right.Check() + if err != nil { + panic(err) + } + + // Check dimensions + leftDims := left.Dims() + rightDims := right.Dims() + + if leftDims[1] != rightDims[0] { + panic( + smErrors.MatrixDimensionError{ + Arg1: left, + Arg2: right, + Operation: "MatrixMultiplyTemplate", + }, + ) + } + + // Algorithm + var out [][]ScalarExpression + for ii := 0; ii < leftDims[0]; ii++ { + var tempRow []ScalarExpression + for jj := 0; jj < rightDims[1]; jj++ { + // Compute the (ii,jj) element of the product + var sum Expression = K(0.0) + for kk := 0; kk < leftDims[1]; kk++ { + sum = sum.Plus(left.At(ii, kk).Multiply(right.At(kk, jj))) + } + sumAsSE, tf := sum.(ScalarExpression) + if !tf { + panic( + fmt.Errorf( + "unexpected expression type in MatrixMultiplyTemplate at entry [%v,%v]: %T", + ii, jj, + sum, + ), + ) + } + tempRow = append(tempRow, sumAsSE) + } + out = append(out, tempRow) + } + + return ConcretizeMatrixExpression(out) +} + /* MatrixSubstituteTemplate Description: diff --git a/testing/symbolic/monomial_matrix_test.go b/testing/symbolic/monomial_matrix_test.go index bfd22d5..ce8b818 100644 --- a/testing/symbolic/monomial_matrix_test.go +++ b/testing/symbolic/monomial_matrix_test.go @@ -2268,3 +2268,97 @@ func TestMonomialMatrix_SubstituteAccordingTo3(t *testing.T) { mm.SubstituteAccordingTo(testMap) t.Errorf("expected SubstituteAccordingTo() to panic; it did not") } + +/* +TestMonomialMatrix_AsSimplifiedExpression1 +Description: + + Tests that the AsSimplifiedExpression() method properly converts a monomial matrix + of constant monomials to a symbolic.KMatrix. +*/ +func TestMonomialMatrix_AsSimplifiedExpression1(t *testing.T) { + // Constants + var mm symbolic.MonomialMatrix = [][]symbolic.Monomial{ + {symbolic.K(1.0).ToMonomial(), symbolic.K(2.0).ToMonomial()}, + {symbolic.K(3.0).ToMonomial(), symbolic.K(4.0).ToMonomial()}, + } + + // Test + simplified := mm.AsSimplifiedExpression() + + // Check that simplified is a KMatrix + sAsCM, ok := simplified.(symbolic.KMatrix) + if !ok { + t.Errorf( + "expected AsSimplifiedExpression() to return a KMatrix; received %v", + simplified, + ) + } + + // Check that the values in the KMatrix are correct + expectedValues := [][]float64{{1.0, 2.0}, {3.0, 4.0}} + for ii := 0; ii < 2; ii++ { + for jj := 0; jj < 2; jj++ { + if float64(sAsCM.At(ii, jj).(symbolic.K)) != expectedValues[ii][jj] { + t.Errorf( + "expected AsSimplifiedExpression() to return a KMatrix with value %v at (%v,%v); received %v", + expectedValues[ii][jj], + ii, jj, + sAsCM.At(ii, jj), + ) + } + } + } +} + +/* +TestMonomialMatrix_Power1 +Description: + + Tests that the Power() method properly computes the power of a square + monomial matrix that represents all constants. + + In the case the matrix is: + [1 2] + [3 4] + + and the exponent is 2, the result should be: + + [7 10] + [15 22] +*/ +func TestMonomialMatrix_Power1(t *testing.T) { + // Constants + var mm symbolic.MonomialMatrix = [][]symbolic.Monomial{ + {symbolic.K(1.0).ToMonomial(), symbolic.K(2.0).ToMonomial()}, + {symbolic.K(3.0).ToMonomial(), symbolic.K(4.0).ToMonomial()}, + } + exponent := 2 + + // Test + powered := mm.Power(exponent) + + // Check that powered is a KMatrix + pAsCM, ok := powered.(symbolic.KMatrix) + if !ok { + t.Errorf( + "expected Power() to return a KMatrix; received %v", + powered, + ) + } + + // Check that the values in the KMatrix are correct + expectedValues := [][]float64{{7.0, 10.0}, {15.0, 22.0}} + for ii := 0; ii < 2; ii++ { + for jj := 0; jj < 2; jj++ { + if float64(pAsCM.At(ii, jj).(symbolic.K)) != expectedValues[ii][jj] { + t.Errorf( + "expected Power() to return a KMatrix with value %v at (%v,%v); received %v", + expectedValues[ii][jj], + ii, jj, + pAsCM.At(ii, jj), + ) + } + } + } +} From 9eda4dab6f3a786d5301b1c56865304312250db5 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Mon, 29 Sep 2025 22:36:50 -0400 Subject: [PATCH 04/16] Add tests for Monomial.AsSimplifiedExpression --- testing/symbolic/monomial_test.go | 186 +++++++++++++++++++++++++++++- 1 file changed, 184 insertions(+), 2 deletions(-) diff --git a/testing/symbolic/monomial_test.go b/testing/symbolic/monomial_test.go index 8aebb33..c57ba66 100644 --- a/testing/symbolic/monomial_test.go +++ b/testing/symbolic/monomial_test.go @@ -8,10 +8,11 @@ Description: import ( "fmt" - "github.com/MatProGo-dev/SymbolicMath.go/smErrors" - "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "strings" "testing" + + "github.com/MatProGo-dev/SymbolicMath.go/smErrors" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" ) /* @@ -1668,3 +1669,184 @@ func TestMonomial_String2(t *testing.T) { _ = m1.String() } + +/* +TestMonomial_AsSimplifiedExpression1 +Description: + + Verifies that the Monomial.AsSimplifiedExpression function properly + panics when the monomial is not well-defined. +*/ +func TestMonomial_AsSimplifiedExpression1(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 3.14, + VariableFactors: []symbolic.Variable{v1}, + Exponents: []int{1, 2}, + } + + // Test + defer func() { + if r := recover(); r == nil { + t.Errorf( + "expected AsSimplifiedExpression to panic; received nil", + ) + } + }() + + m1.AsSimplifiedExpression() + t.Errorf("expected panic; received nil") +} + +/* +TestMonomial_AsSimplifiedExpression2 +Description: + + Verifies that the Monomial.AsSimplifiedExpression function properly + returns a monomial expression when the monomial is well-defined + and the variable has a non-zero exponent. +*/ +func TestMonomial_AsSimplifiedExpression2(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 3.14, + VariableFactors: []symbolic.Variable{v1}, + Exponents: []int{2}, + } + + // Compute AsSimplifiedExpression + simplified := m1.AsSimplifiedExpression() + + // Verify that the simplified is a monomial + simplifiedAsM, tf := simplified.(symbolic.Monomial) + if !tf { + t.Errorf( + "expected simplified to be a monomial; received %T", + simplified, + ) + } + + // Verify that the coefficient is the same + if simplifiedAsM.Coefficient != m1.Coefficient { + t.Errorf( + "expected simplified coefficient to be %v; received %v", + m1.Coefficient, + simplifiedAsM.Coefficient, + ) + } + + // Verify that the variable factors are the same + if len(simplifiedAsM.VariableFactors) != len(m1.VariableFactors) { + t.Errorf( + "expected simplified variable factors to be %v; received %v", + m1.VariableFactors, + simplifiedAsM.VariableFactors, + ) + } else { + for i, v := range simplifiedAsM.VariableFactors { + if v != m1.VariableFactors[i] { + t.Errorf( + "expected simplified variable factors to be %v; received %v", + m1.VariableFactors, + simplifiedAsM.VariableFactors, + ) + } + } + } + + // Verify that the exponents are the same + if len(simplifiedAsM.Exponents) != len(m1.Exponents) { + t.Errorf( + "expected simplified exponents to be %v; received %v", + m1.Exponents, + simplifiedAsM.Exponents, + ) + } else { + for i, e := range simplifiedAsM.Exponents { + if e != m1.Exponents[i] { + t.Errorf( + "expected simplified exponents to be %v; received %v", + m1.Exponents, + simplifiedAsM.Exponents, + ) + } + } + } +} + +/* +TestMonomial_AsSimplifiedExpression3 +Description: + + Verifies that the Monomial.AsSimplifiedExpression function properly + returns a constant expression (K) when the monomial is well-defined + and the variable has a zero exponent. +*/ +func TestMonomial_AsSimplifiedExpression3(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 3.14, + VariableFactors: []symbolic.Variable{v1}, + Exponents: []int{0}, + } + + // Compute AsSimplifiedExpression + simplified := m1.AsSimplifiedExpression() + + // Verify that the simplified is a constant (K) + simplifiedAsK, tf := simplified.(symbolic.K) + if !tf { + t.Errorf( + "expected simplified to be a constant; received %T", + simplified, + ) + } + + // Verify that the simplified is a constant + if float64(simplifiedAsK) != 3.14 { + t.Errorf( + "expected simplified to be a constant; received %v", + simplifiedAsK, + ) + } +} + +/* +TestMonomial_AsSimplifiedExpression4 +Description: + + Verifies that the Monomial.AsSimplifiedExpression function properly + returns a constant expression (K) when the monomial is well-defined + and has no variable factors. +*/ +func TestMonomial_AsSimplifiedExpression4(t *testing.T) { + // Constants + m1 := symbolic.Monomial{ + Coefficient: 3.14, + VariableFactors: []symbolic.Variable{}, + Exponents: []int{}, + } + + // Compute AsSimplifiedExpression + simplified := m1.AsSimplifiedExpression() + + // Verify that the simplified is a constant (K) + simplifiedAsK, tf := simplified.(symbolic.K) + if !tf { + t.Errorf( + "expected simplified to be a constant; received %T", + simplified, + ) + } + + // Verify that the simplified is a constant + if float64(simplifiedAsK) != 3.14 { + t.Errorf( + "expected simplified to be a constant; received %v", + simplifiedAsK, + ) + } +} From 8fad004a9fcaa48ddff8d5ad47ab55daeb0a32bd Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Mon, 29 Sep 2025 22:38:30 -0400 Subject: [PATCH 05/16] Added tests for MonomialVector.AsSimplifiedExpression --- testing/symbolic/monomial_vector_test.go | 68 ++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/testing/symbolic/monomial_vector_test.go b/testing/symbolic/monomial_vector_test.go index 63fa3ee..77c080a 100644 --- a/testing/symbolic/monomial_vector_test.go +++ b/testing/symbolic/monomial_vector_test.go @@ -1882,3 +1882,71 @@ func TestMonomialVector_LinearCoeff1(t *testing.T) { t.Errorf("The two matrices are not equal!") } } + +/* +TestMonomialVector_AsSimplifiedExpression1 +Description: + + Verifies that the AsSimplifiedExpression() method properly panics + when called on an improperly initialized MonomialVector. +*/ +func TestMonomialVector_AsSimplifiedExpression1(t *testing.T) { + // Constants + mv := symbolic.MonomialVector{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected mv.AsSimplifiedExpression() to panic; received %v", + mv.AsSimplifiedExpression(), + ) + } + }() + + mv.AsSimplifiedExpression() + t.Errorf("Test should panic before this is reached!") +} + +/* +TestMonomialVector_AsSimplifiedExpression2 +Description: + + Verifies that the AsSimplifiedExpression() method properly returns + a constant vector when called on a MonomialVector containing + only constant monomials. +*/ +func TestMonomialVector_AsSimplifiedExpression2(t *testing.T) { + // Constants + mv := symbolic.MonomialVector{ + symbolic.Monomial{Coefficient: 3.14}, + symbolic.Monomial{Coefficient: 2.71}, + symbolic.Monomial{Coefficient: 1.41}, + } + + // Test + se := mv.AsSimplifiedExpression() + + // Verify that the result is a KVector + kv, tf := se.(symbolic.KVector) + if !tf { + t.Errorf( + "expected se to be a KVector; received %T", + se, + ) + } + + // Verify that the elements of the KVector are correct + expectedValues := []float64{3.14, 2.71, 1.41} + for ii, val := range kv { + if float64(val) != expectedValues[ii] { + t.Errorf( + "expected kv[%v] to be %v; received %v", + ii, + expectedValues[ii], + float64(val), + ) + } + } +} From 2e48604728711950b5705f4a9c8dafda371b51b4 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Mon, 29 Sep 2025 22:40:22 -0400 Subject: [PATCH 06/16] Added a weird test for PolynomialVector.AsSimplifiedExpression --- testing/symbolic/polynomial_vector_test.go | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/testing/symbolic/polynomial_vector_test.go b/testing/symbolic/polynomial_vector_test.go index caf7745..3eff78e 100644 --- a/testing/symbolic/polynomial_vector_test.go +++ b/testing/symbolic/polynomial_vector_test.go @@ -2013,3 +2013,43 @@ func TestPolynomialVector_Simplify2(t *testing.T) { } } } + +/* +TestPolynomialVector_AsSimplifiedExpression1 +Description: + + This test verifies that the AsSimplifiedExpression method + returns a well-defined simplified expression when called + on a well-defined polynomial vector. +*/ +func TestPolynomialVector_AsSimplifiedExpression1(t *testing.T) { + // Create a polynomial vector + pv := symbolic.PolynomialVector{} + for ii := 0; ii < 20; ii++ { + pv = append(pv, symbolic.NewVariable().Plus(3.14).Plus(2.17).Plus( + symbolic.Monomial{Coefficient: 1.1}, + ).(symbolic.Polynomial)) + } + + // Test + simplified := pv.AsSimplifiedExpression() + + // Concretize simplified to polynomial vector + pvOut, ok := simplified.(symbolic.PolynomialVector) + if !ok { + t.Errorf( + "Expected pv.AsSimplifiedExpression() to return a PolynomialVector; received %T", + simplified, + ) + } + + // Check each element of pvOut and verify that it has two monomials. + for _, polynomial := range []symbolic.Polynomial(pvOut) { + if len(polynomial.Monomials) != 2 { + t.Errorf( + "Expected polynomial.Monomials to have length 2; received %v", + len(polynomial.Monomials), + ) + } + } +} From 79e928e2d8d10ddc2f70f120902d1d2b53b5e0ac Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Mon, 29 Sep 2025 22:42:59 -0400 Subject: [PATCH 07/16] Adding more matrix multiply cases to all Matrix objects --- symbolic/polynomial_matrix.go | 8 ++++++++ symbolic/variable_matrix.go | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/symbolic/polynomial_matrix.go b/symbolic/polynomial_matrix.go index 7140d94..41d1b6c 100644 --- a/symbolic/polynomial_matrix.go +++ b/symbolic/polynomial_matrix.go @@ -326,6 +326,14 @@ func (pm PolynomialMatrix) Multiply(e interface{}) Expression { } return product } + case KMatrix: + return MatrixMultiplyTemplate(pm, right) + case VariableMatrix: + return MatrixMultiplyTemplate(pm, right) + case MonomialMatrix: + return MatrixMultiplyTemplate(pm, right) + case PolynomialMatrix: + return MatrixMultiplyTemplate(pm, right) } // If type isn't recognized, then panic diff --git a/symbolic/variable_matrix.go b/symbolic/variable_matrix.go index 0ff2bf1..111e9fd 100644 --- a/symbolic/variable_matrix.go +++ b/symbolic/variable_matrix.go @@ -384,6 +384,12 @@ func (vm VariableMatrix) Multiply(e interface{}) Expression { } return result } + case VariableMatrix: + return MatrixMultiplyTemplate(vm, right) + case MonomialMatrix: + return MatrixMultiplyTemplate(vm, right) + case PolynomialMatrix: + return MatrixMultiplyTemplate(vm, right) } // panic if the type is not recognized From 64752b4bcc20baa4a5cd6e32381a39e564b939e6 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Mon, 29 Sep 2025 22:47:13 -0400 Subject: [PATCH 08/16] Adding MatrixMultiplyTemplate tests --- testing/symbolic/matrix_expression_test.go | 86 +++++++++++++++++++++- testing/symbolic/variable_matrix_test.go | 40 ++++++++++ 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/testing/symbolic/matrix_expression_test.go b/testing/symbolic/matrix_expression_test.go index 34e7577..dbe003e 100644 --- a/testing/symbolic/matrix_expression_test.go +++ b/testing/symbolic/matrix_expression_test.go @@ -2,10 +2,11 @@ package symbolic_test import ( "fmt" - "github.com/MatProGo-dev/SymbolicMath.go/smErrors" - "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "strings" "testing" + + "github.com/MatProGo-dev/SymbolicMath.go/smErrors" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" ) /* @@ -413,3 +414,84 @@ func TestMatrixExpression_MatrixSubstituteTemplate3(t *testing.T) { }() symbolic.MatrixSubstituteTemplate(x, v1, m1) } + +/* +TestMatrixExpression_MatrixMultiplyTemplate1 +Description: + + Tests that the matrix multiply template properly panics when called with a MatrixExpression that is not + well-defined (in this case, a MonomialMatrix). +*/ +func TestMatrixExpression_MatrixMultiplyTemplate1(t *testing.T) { + // Setup + m := symbolic.Monomial{ + Coefficient: 1.2, + VariableFactors: []symbolic.Variable{symbolic.NewVariable(), symbolic.NewVariable()}, + Exponents: []int{1}, + } + x := symbolic.MonomialMatrix{ + {m, m}, + {m, m}, + } + y := symbolic.MonomialMatrix{ + {m, m}, + {m, m}, + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("Expected a panic when calling MatrixMultiplyTemplate on a MonomialMatrix; received nil") + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf("Expected the panic to be an error; received %T", r) + } + + if !strings.Contains(rAsE.Error(), m.Check().Error()) { + t.Errorf("Expected the panic to contain the error message %v; received %v", m.Check().Error(), rAsE.Error()) + } + }() + symbolic.MatrixMultiplyTemplate(x, y) +} + +/* +TestMatrixExpression_MatrixMultiplyTemplate2 +Description: + + Tests that the matrix multiply template properly panics when called with a MatrixExpression that is well-defined + but with incompatible dimensions. + In this case, we use two KMatrix objects with incompatible dimensions. + The first KMatrix is 2x3 and the second KMatrix is 2x2. +*/ +func TestMatrixExpression_MatrixMultiplyTemplate2(t *testing.T) { + // Setup + x := symbolic.KMatrix{ + {symbolic.K(1), symbolic.K(2), symbolic.K(3)}, + {symbolic.K(4), symbolic.K(5), symbolic.K(6)}, + } + y := symbolic.KMatrix{ + {symbolic.K(7), symbolic.K(8)}, + {symbolic.K(9), symbolic.K(10)}, + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("Expected a panic when calling MatrixMultiplyTemplate with incompatible dimensions; received nil") + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf("Expected the panic to be an error; received %T", r) + } + + if !strings.Contains(rAsE.Error(), "dimension error") { + t.Errorf("Expected the panic to contain the error message %v; received %v", "incompatible dimensions", rAsE.Error()) + } + }() + symbolic.MatrixMultiplyTemplate(x, y) +} diff --git a/testing/symbolic/variable_matrix_test.go b/testing/symbolic/variable_matrix_test.go index 3bbd85b..6c1636f 100644 --- a/testing/symbolic/variable_matrix_test.go +++ b/testing/symbolic/variable_matrix_test.go @@ -1087,6 +1087,46 @@ func TestVariableMatrix_Multiply15(t *testing.T) { } } +/* +TestVariableMatrix_Multiply16 +Description: + + Tests that the Multiply method for a VariableMatrix object that is well-defined + with dimension (2,3) properly multiplies a MonomialMatrix with dimension (3,2). + The resulting object should be a PolynomialMatrix with dimension (2,2) + and each polynomial should contain three monomials. +*/ +func TestVariableMatrix_Multiply16(t *testing.T) { + // Constants + vm := symbolic.VariableMatrix{ + {symbolic.NewVariable(), symbolic.NewVariable(), symbolic.NewVariable()}, + {symbolic.NewVariable(), symbolic.NewVariable(), symbolic.NewVariable()}, + } + mm := symbolic.MonomialMatrix{ + {symbolic.NewVariable().ToMonomial(), symbolic.NewVariable().ToMonomial()}, + {symbolic.NewVariable().ToMonomial(), symbolic.NewVariable().ToMonomial()}, + {symbolic.NewVariable().ToMonomial(), symbolic.NewVariable().ToMonomial()}, + } + + // Compute Product + result := vm.Multiply(mm) + + // Check that object is a PolynomialMatrix + if _, ok := result.(symbolic.PolynomialMatrix); !ok { + t.Errorf("Expected Multiply to return a PolynomialMatrix; received %T", result) + } + + // Check that each polynomial in the result contains three monomials. + pv := result.(symbolic.PolynomialMatrix) + for i := 0; i < pv.Dims()[0]; i++ { + for j := 0; j < pv.Dims()[1]; j++ { + if len(pv[i][j].Monomials) != 3 { + t.Errorf("Expected each polynomial to contain 3 monomials; received %v", len(pv[i][j].Monomials)) + } + } + } +} + /* TestVariableMatrix_Transpose1 Description: From 0b28023c1cb0fb64c913c76b4bc7bd53cd7c6f0d Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Tue, 30 Sep 2025 21:08:01 -0400 Subject: [PATCH 09/16] Switched the Concretize function used in MatrixMultiplyTemplate --- symbolic/constant_matrix.go | 8 +---- symbolic/matrix_expression.go | 9 ++++-- symbolic/monomial_matrix.go | 2 ++ symbolic/polynomial_matrix.go | 8 +---- symbolic/variable_matrix.go | 59 +---------------------------------- 5 files changed, 11 insertions(+), 75 deletions(-) diff --git a/symbolic/constant_matrix.go b/symbolic/constant_matrix.go index c6b3327..4031b12 100644 --- a/symbolic/constant_matrix.go +++ b/symbolic/constant_matrix.go @@ -371,13 +371,7 @@ func (km KMatrix) Multiply(e interface{}) Expression { case mat.Dense: // Use *mat.Dense method return km.Multiply(&right) // Reuse *mat.Dense case - case KMatrix: - return km.Multiply(right.ToDense()) // Reuse *mat.Dense case - case VariableMatrix: - return MatrixMultiplyTemplate(km, right) - case MonomialMatrix: - return MatrixMultiplyTemplate(km, right) - case PolynomialMatrix: + case MatrixExpression: return MatrixMultiplyTemplate(km, right) } diff --git a/symbolic/matrix_expression.go b/symbolic/matrix_expression.go index 30c96d1..2edd2c2 100644 --- a/symbolic/matrix_expression.go +++ b/symbolic/matrix_expression.go @@ -199,7 +199,7 @@ Description: Template for the matrix multiply function. */ -func MatrixMultiplyTemplate(left MatrixExpression, right MatrixExpression) MatrixExpression { +func MatrixMultiplyTemplate(left MatrixExpression, right MatrixExpression) Expression { // Input Processing err := left.Check() if err != nil { @@ -250,7 +250,9 @@ func MatrixMultiplyTemplate(left MatrixExpression, right MatrixExpression) Matri out = append(out, tempRow) } - return ConcretizeMatrixExpression(out) + // Use the general concretization function (not the matrix-specific one) + // because it will also convert matrices to scalars or vectors if needed. + return ConcretizeExpression(out) } /* @@ -297,6 +299,7 @@ Description: */ func ConcretizeMatrixExpression(sliceIn [][]ScalarExpression) MatrixExpression { // Input Processing + // - Check that the input slice is not empty if len(sliceIn) == 0 { panic( fmt.Errorf( @@ -305,7 +308,7 @@ func ConcretizeMatrixExpression(sliceIn [][]ScalarExpression) MatrixExpression { ) } - // Check the number of columns in each row + // - Check the number of columns in each row is the same numCols := len(sliceIn[0]) for ii, row := range sliceIn { if len(row) != numCols { diff --git a/symbolic/monomial_matrix.go b/symbolic/monomial_matrix.go index 85a4607..f68981e 100644 --- a/symbolic/monomial_matrix.go +++ b/symbolic/monomial_matrix.go @@ -344,6 +344,8 @@ func (mm MonomialMatrix) Multiply(e interface{}) Expression { return ConcretizeVectorExpression(product) } + case MatrixExpression: + return MatrixMultiplyTemplate(mm, right) } // Unrecognized response is a panic diff --git a/symbolic/polynomial_matrix.go b/symbolic/polynomial_matrix.go index 41d1b6c..379be61 100644 --- a/symbolic/polynomial_matrix.go +++ b/symbolic/polynomial_matrix.go @@ -326,13 +326,7 @@ func (pm PolynomialMatrix) Multiply(e interface{}) Expression { } return product } - case KMatrix: - return MatrixMultiplyTemplate(pm, right) - case VariableMatrix: - return MatrixMultiplyTemplate(pm, right) - case MonomialMatrix: - return MatrixMultiplyTemplate(pm, right) - case PolynomialMatrix: + case MatrixExpression: return MatrixMultiplyTemplate(pm, right) } diff --git a/symbolic/variable_matrix.go b/symbolic/variable_matrix.go index 111e9fd..a83b3eb 100644 --- a/symbolic/variable_matrix.go +++ b/symbolic/variable_matrix.go @@ -331,64 +331,7 @@ func (vm VariableMatrix) Multiply(e interface{}) Expression { case mat.Dense: // Use the KMatrix case return vm.Multiply(DenseToKMatrix(right)) - case KMatrix: - // Collect dimensions - nResultRows, nResultCols := vm.Dims()[0], right.Dims()[1] - - // Switch on the dimensions of the result - switch { - case (nResultRows == 1) && (nResultCols == 1): - // Scalar result - var result Polynomial = K(0).ToMonomial().ToPolynomial() - - for ii, vmRow := range vm { - for jj, vIJ := range vmRow { - result = result.Plus(vIJ.Multiply(right[jj][ii])).(Polynomial) - } - } - return result - case nResultCols == 1: - // Vector result - var result PolynomialVector = VecDenseToKVector(ZerosVector(nResultRows)).ToPolynomialVector() - - for ii, vmRow := range vm { - for jj, vIJ := range vmRow { - result[ii] = result[ii].Plus(vIJ.Multiply(right[jj][0])).(Polynomial) - } - } - - return result - - default: - // Create result - var result PolynomialMatrix - - for ii := 0; ii < nResultRows; ii++ { - var resultRow []Polynomial - for jj := 0; jj < nResultCols; jj++ { - resultRow = append(resultRow, K(0).ToMonomial().ToPolynomial()) - } - result = append(result, resultRow) - } - - // Fill in the elements of the new matrix - for ii := 0; ii < nResultRows; ii++ { - for jj := 0; jj < nResultCols; jj++ { - // Compute Sum - for kk := 0; kk < vm.Dims()[1]; kk++ { - result[ii][jj] = result[ii][jj].Plus( - vm[ii][kk].Multiply(right[kk][jj]), - ).(Polynomial) - } - } - } - return result - } - case VariableMatrix: - return MatrixMultiplyTemplate(vm, right) - case MonomialMatrix: - return MatrixMultiplyTemplate(vm, right) - case PolynomialMatrix: + case MatrixExpression: return MatrixMultiplyTemplate(vm, right) } From 8c80242aa15e1e31a534fc9da068255183753e51 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Tue, 30 Sep 2025 21:18:38 -0400 Subject: [PATCH 10/16] Testing Multiply + AsSimplifiedExpression --- testing/symbolic/monomial_matrix_test.go | 63 ++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/testing/symbolic/monomial_matrix_test.go b/testing/symbolic/monomial_matrix_test.go index ce8b818..2a50486 100644 --- a/testing/symbolic/monomial_matrix_test.go +++ b/testing/symbolic/monomial_matrix_test.go @@ -1305,6 +1305,46 @@ func TestMonomialMatrix_Multiply7(t *testing.T) { mm.Multiply("a") } +/* +TestMonomialMatrix_Multiply8 +Description: + + Tests that the Multiply() method properly multiplies a matrix + of Monomials with a KMatrix. The result should be a matrix of + monomials where each monomial has the scaled coefficients + of the original monomial. +*/ +func TestMonomialMatrix_Multiply8(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + m1 := v1.ToMonomial() + var mm symbolic.MonomialMatrix = [][]symbolic.Monomial{ + {m1, m1}, + {m1, m1}, + } + km2 := symbolic.DenseToKMatrix(symbolic.OnesMatrix(2, 2)) + + // Test + product := mm.Multiply(km2) + + // Check that the product is of the MonomialMatrix type + productMat, ok := product.(symbolic.PolynomialMatrix) + if !ok { + t.Errorf( + "expected Multiply() to return a PolynomialMatrix; received %v", + product, + ) + } + + // Check that the dimensions of the product are (2,2) + if dims := productMat.Dims(); dims[0] != 2 || dims[1] != 2 { + t.Errorf( + "expected Multiply() to return a PolynomialMatrix with dimensions (2,2); received %v", + dims, + ) + } +} + /* TestMonomialMatrix_Transpose1 Description: @@ -2311,6 +2351,29 @@ func TestMonomialMatrix_AsSimplifiedExpression1(t *testing.T) { } } +/* +TestMonomialMatrix_AsSimplifiedExpression2 +Description: + + Tests that the AsSimplifiedExpression() method properly panics when called + with a monomial matrix that is not well-defined. +*/ +func TestMonomialMatrix_AsSimplifiedExpression2(t *testing.T) { + // Constants + var mm symbolic.MonomialMatrix + + // Test + defer func() { + if r := recover(); r == nil { + t.Errorf( + "expected AsSimplifiedExpression() to panic; it did not", + ) + } + }() + + mm.AsSimplifiedExpression() +} + /* TestMonomialMatrix_Power1 Description: From a249553fc22359597163d90a0640119cdf850e80 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Tue, 30 Sep 2025 21:22:57 -0400 Subject: [PATCH 11/16] Addressed some of the test coverage --- testing/symbolic/constant_matrix_test.go | 23 +++++++++++++ testing/symbolic/matrix_expression_test.go | 39 ++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/testing/symbolic/constant_matrix_test.go b/testing/symbolic/constant_matrix_test.go index eeecf6a..280b769 100644 --- a/testing/symbolic/constant_matrix_test.go +++ b/testing/symbolic/constant_matrix_test.go @@ -1343,3 +1343,26 @@ func TestKMatrix_Power2(t *testing.T) { } } } + +/* +TestKMatrix_AsSimplifiedExpression1 +Description: + + Tests that the AsSimplifiedExpression() method properly returns the KMatrix itself, + as it is already in simplified form. +*/ +func TestKMatrix_AsSimplifiedExpression1(t *testing.T) { + // Setup + A := getKMatrix.From([][]float64{ + {1, 2, 3}, + {4, 5, 6}, + }) + + // Simplify + simp := A.AsSimplifiedExpression() + + // Verify that the result is the same as the original + if !reflect.DeepEqual(A, simp) { + t.Errorf("Expected simplification to not change anything; got %v", simp) + } +} diff --git a/testing/symbolic/matrix_expression_test.go b/testing/symbolic/matrix_expression_test.go index dbe003e..ca44a5b 100644 --- a/testing/symbolic/matrix_expression_test.go +++ b/testing/symbolic/matrix_expression_test.go @@ -495,3 +495,42 @@ func TestMatrixExpression_MatrixMultiplyTemplate2(t *testing.T) { }() symbolic.MatrixMultiplyTemplate(x, y) } + +/* +TestMatrixExpression_MatrixMultiplyTemplate3 +Description: + + Tests the multiplication of a KMatrix and a VariableMatrix using + the MatrixMultiplyTemplate function. This test should trigger a panic + when the second matrix (the VariableMatrix) is not well-defined. +*/ +func TestMatrixExpression_MatrixMultiplyTemplate3(t *testing.T) { + // Setup + x := symbolic.KMatrix{ + {symbolic.K(1), symbolic.K(2)}, + {symbolic.K(3), symbolic.K(4)}, + } + v := symbolic.Variable{} + y := symbolic.VariableMatrix{ + {v, v}, + {v, v}, + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("Expected a panic when calling MatrixMultiplyTemplate with an invalid VariableMatrix; received nil") + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf("Expected the panic to be an error; received %T", r) + } + + if !strings.Contains(rAsE.Error(), v.Check().Error()) { + t.Errorf("Expected the panic to contain the error message %v; received %v", v.Check().Error(), rAsE.Error()) + } + }() + symbolic.MatrixMultiplyTemplate(x, y) +} From c41e9347769ec0c9772096254d299fe23ad55cc1 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Tue, 30 Sep 2025 21:26:13 -0400 Subject: [PATCH 12/16] Adding tests to increase coverage on PolynomialMatrix.Multiply --- testing/symbolic/polynomial_matrix_test.go | 101 +++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/testing/symbolic/polynomial_matrix_test.go b/testing/symbolic/polynomial_matrix_test.go index 951b261..a3e0e1d 100644 --- a/testing/symbolic/polynomial_matrix_test.go +++ b/testing/symbolic/polynomial_matrix_test.go @@ -1207,6 +1207,54 @@ func TestPolynomialMatrix_Multiply8(t *testing.T) { pm1.Multiply(vm1) } +/* +TestPolynomialMatrix_Multiply9 +Description: + + Tests that the Multiply() method properly computes the product of a + polynomial matrix (2 x 3) with a constant matrix (3 x 2). + The output should be a polynomial matrix (2 x 2). +*/ +func TestPolynomialMatrix_Multiply9(t *testing.T) { + // Constants + var pm1 symbolic.PolynomialMatrix = [][]symbolic.Polynomial{ + { + symbolic.NewVariable().ToPolynomial(), + symbolic.NewVariable().ToPolynomial(), + symbolic.NewVariable().ToPolynomial(), + }, + { + symbolic.NewVariable().ToPolynomial(), + symbolic.NewVariable().ToPolynomial(), + symbolic.NewVariable().ToPolynomial(), + }, + } + + km1 := getKMatrix.From([][]float64{ + {1.0, 2.0}, + {3.0, 4.0}, + {5.0, 6.0}, + }) + + // Test + pm2 := pm1.Multiply(km1) + + pm2AsPM, tf := pm2.(symbolic.PolynomialMatrix) + if !tf { + t.Errorf( + "expected pm2 to be a PolynomialMatrix; received %v", + pm2, + ) + } + + if pm2AsPM.Dims()[0] != 2 || pm2AsPM.Dims()[1] != 2 { + t.Errorf( + "expected pm2.Dims() to be [2,2]; received %v", + pm2AsPM.Dims(), + ) + } +} + /* TestPolynomialMatrix_Transpose1 Description: @@ -1795,3 +1843,56 @@ func TestPolynomialMatrix_String1(t *testing.T) { } } + +/* +TestPolynomialMatrix_AsSimplifiedExpression1 +Description: + + Tests that the AsSimplifiedExpression() method properly returns + a PolynomialMatrix when a well-defined polynomial matrix + calls it. The result should be a polynomial matrix with + the same dimensions and number of monomials in each polynomial. +*/ +func TestPolynomialMatrix_AsSimplifiedExpression1(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + p1 := v1.ToPolynomial() + var pm1 symbolic.PolynomialMatrix = [][]symbolic.Polynomial{ + {p1.Minus(3.14).(symbolic.Polynomial), p1.Minus(3.14).(symbolic.Polynomial)}, + {p1.Minus(3.14).(symbolic.Polynomial), p1.Minus(3.14).(symbolic.Polynomial)}, + {p1.Minus(3.14).(symbolic.Polynomial), p1.Minus(3.14).(symbolic.Polynomial)}, + } + + // Test + pm2 := pm1.AsSimplifiedExpression() + + pm2AsPM, tf := pm2.(symbolic.PolynomialMatrix) + if !tf { + t.Errorf( + "expected pm2 to be a PolynomialMatrix; received %v", + pm2, + ) + } + + // Check that the dimensions are the same + if pm1.Dims()[0] != pm2AsPM.Dims()[0] || pm1.Dims()[1] != pm2AsPM.Dims()[1] { + t.Errorf( + "expected pm2.Dims() to be %v; received %v", + pm1.Dims(), + pm2AsPM.Dims(), + ) + } + + // Check that each polynomial in pm2 contains two monomials + for _, pm2Row := range pm2AsPM { + for _, p := range pm2Row { + if len(p.Monomials) != 2 { + t.Errorf( + "expected len(p.Monomials) to be 2; received %v", + len(p.Monomials), + ) + } + } + } + +} From da1f4056c5e370da6a5bf1a9aef69ac314c88445 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Tue, 30 Sep 2025 21:28:58 -0400 Subject: [PATCH 13/16] Added more test coverage for the Monomial.AsSimplifiedExpression function --- testing/symbolic/monomial_test.go | 77 +++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/testing/symbolic/monomial_test.go b/testing/symbolic/monomial_test.go index c57ba66..db3c5c4 100644 --- a/testing/symbolic/monomial_test.go +++ b/testing/symbolic/monomial_test.go @@ -1850,3 +1850,80 @@ func TestMonomial_AsSimplifiedExpression4(t *testing.T) { ) } } + +/* +TestMonomial_AsSimplifiedExpression5 +Description: + + Verifies that the Monomial.AsSimplifiedExpression function properly + returns a constant expression of zero (K(0)) when the monomial is well-defined + and has a coefficient of zero. +*/ +func TestMonomial_AsSimplifiedExpression5(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 0, + VariableFactors: []symbolic.Variable{v1}, + Exponents: []int{2}, + } + + // Compute AsSimplifiedExpression + simplified := m1.AsSimplifiedExpression() + + // Verify that the simplified is a constant (K) + simplifiedAsK, tf := simplified.(symbolic.K) + if !tf { + t.Errorf( + "expected simplified to be a constant; received %T", + simplified, + ) + } + + // Verify that the simplified is a constant + if float64(simplifiedAsK) != 0 { + t.Errorf( + "expected simplified to be a constant; received %v", + simplifiedAsK, + ) + } +} + +/* +TestMonomial_AsSimplifiedExpression6 +Description: + + Verifies that the Monomial.AsSimplifiedExpression function properly + returns a single variable expression when the monomial is well-defined + and has a coefficient of one and a single variable factor with exponent one. +*/ +func TestMonomial_AsSimplifiedExpression6(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 1, + VariableFactors: []symbolic.Variable{v1}, + Exponents: []int{1}, + } + + // Compute AsSimplifiedExpression + simplified := m1.AsSimplifiedExpression() + + // Verify that the simplified is a variable + simplifiedAsV, tf := simplified.(symbolic.Variable) + if !tf { + t.Errorf( + "expected simplified to be a variable; received %T", + simplified, + ) + } + + // Verify that the simplified is the same variable + if simplifiedAsV != v1 { + t.Errorf( + "expected simplified to be %v; received %v", + v1, + simplifiedAsV, + ) + } +} From 77a19a1c9d3170cf1d5f5090ea89643715fbc42c Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Tue, 30 Sep 2025 21:31:56 -0400 Subject: [PATCH 14/16] Added test for Monomial.At() --- testing/symbolic/monomial_test.go | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/testing/symbolic/monomial_test.go b/testing/symbolic/monomial_test.go index db3c5c4..91e257c 100644 --- a/testing/symbolic/monomial_test.go +++ b/testing/symbolic/monomial_test.go @@ -1670,6 +1670,42 @@ func TestMonomial_String2(t *testing.T) { _ = m1.String() } +/* +TestMonomial_At1 +Description: + + Verifies that the Monomial.At function returns the proper value + when the monomial is well-defined and the inputs are 0,0. +*/ +func TestMonomial_At1(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + v2 := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 3.14, + VariableFactors: []symbolic.Variable{v1, v2}, + Exponents: []int{1, 2}, + } + + // Test + val := m1.At(0, 0) + valAsMonomial, tf := val.(symbolic.Monomial) + if !tf { + t.Errorf( + "expected val to be a monomial; received %T", + val, + ) + } + + if valAsMonomial.Coefficient != 3.14 { + t.Errorf( + "expected val coefficient to be %v; received %v", + 3.14, + valAsMonomial.Coefficient, + ) + } +} + /* TestMonomial_AsSimplifiedExpression1 Description: From c0a082e40732fc9edebf1bb21ad887860d5b3889 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Tue, 30 Sep 2025 21:33:47 -0400 Subject: [PATCH 15/16] Added test for KVector.AsSimplifiedExpression --- testing/symbolic/constant_vector_test.go | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/testing/symbolic/constant_vector_test.go b/testing/symbolic/constant_vector_test.go index 96a5cf8..28ae176 100644 --- a/testing/symbolic/constant_vector_test.go +++ b/testing/symbolic/constant_vector_test.go @@ -8,6 +8,7 @@ Description: import ( "fmt" + "reflect" "strings" "testing" @@ -1284,3 +1285,34 @@ func TestConstantVector_Multiply13(t *testing.T) { ) } } + +/* +TestKVector_AsSimplifiedExpression1 +Description: + + Verifies that the AsSimplifiedExpression method correctly + returns the same KVector when called on a KVector. +*/ +func TestKVector_AsSimplifiedExpression1(t *testing.T) { + // Constants + kv1 := symbolic.VecDenseToKVector(symbolic.OnesVector(3)) + + // Test + simplified := kv1.AsSimplifiedExpression() + + // Check that the simplified expression is a KVector + if _, tf := simplified.(symbolic.KVector); !tf { + t.Errorf( + "Expected simplified to be of type KVector; received %v", + simplified, + ) + } + + // Check that the simplified expression is equal to the original + if !reflect.DeepEqual(simplified, kv1) { + t.Errorf( + "Expected simplified to be equal to kv1; received %v", + simplified, + ) + } +} From 82f7c6a2e1f61235d4ff1502ad96d73f28bfab28 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Tue, 30 Sep 2025 21:46:56 -0400 Subject: [PATCH 16/16] Added more cases to KMatrix.Minus + one test --- symbolic/constant_matrix.go | 2 ++ testing/symbolic/constant_matrix_test.go | 34 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/symbolic/constant_matrix.go b/symbolic/constant_matrix.go index 4031b12..4cfa060 100644 --- a/symbolic/constant_matrix.go +++ b/symbolic/constant_matrix.go @@ -224,6 +224,8 @@ func (km KMatrix) Minus(e interface{}) Expression { return km.Minus(DenseToKMatrix(right)) // Reuse KMatrix case case *mat.Dense: return km.Minus(*right) // Reuse mat.Dense case + case Expression: + return km.Plus(right.Multiply(-1.0)) } // If we reach this point, the input is not recognized diff --git a/testing/symbolic/constant_matrix_test.go b/testing/symbolic/constant_matrix_test.go index 280b769..45add5c 100644 --- a/testing/symbolic/constant_matrix_test.go +++ b/testing/symbolic/constant_matrix_test.go @@ -1366,3 +1366,37 @@ func TestKMatrix_AsSimplifiedExpression1(t *testing.T) { t.Errorf("Expected simplification to not change anything; got %v", simp) } } + +/* +TestKMatrix_Minus1 +Description: + + Tests that the Minus() method properly subtracts zero from a KMatrix. + The result should be the same as the original matrix. +*/ +func TestKMatrix_Minus1(t *testing.T) { + // Setup + A := getKMatrix.From([][]float64{ + {1, -2, 3}, + {-4, 5, -6}, + }) + + Z1 := symbolic.ZerosMatrix(2, 3) + + // Algorithm + diff := A.Minus(Z1) + + diffAsKMatrix, tf := diff.(symbolic.KMatrix) + if !tf { + t.Errorf( + "Expected diff to be KMatrix; received type %T", + diff, + ) + } + + // Check the elements of the two matrices + if !reflect.DeepEqual(A, diffAsKMatrix) { + t.Errorf("The two matrices A and diff are not equal!") + } + +}