diff --git a/.github/workflows/coverage1.yml b/.github/workflows/coverage1.yml index d3b8809..9a47e37 100644 --- a/.github/workflows/coverage1.yml +++ b/.github/workflows/coverage1.yml @@ -36,7 +36,7 @@ jobs: - name: Test Coverage run: | - go test -v -cover ./... -coverprofile coverage.out -coverpkg ./... + go test -v -cover $(go list ./... | grep -v /examples/) -coverprofile coverage.out -coverpkg ./... go tool cover -func coverage.out -o coverage2.out - name: Upload coverage reports to Codecov diff --git a/README.md b/README.md index 3835aeb..b9499c4 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,10 @@ This project was motivated by the need for a symbolic math package for defining optimization and control theory problems in Go, but symbolic mathematics is a topic that covers a wide range of applications. If this tool is not useful for your purpose, then you might find one of the following projects more helpful: + While other symbolic math libraries exist for Go, they typically focus on: - Computer Algebra Systems that will help you get a final expression (in text) from arbitrary math input (often in text) \[[expreduce](https://github.com/corywalker/expreduce),[sm](https://github.com/Konstantin8105/sm)\] - Implementing Algorithms from [Domain-Specific Languages of Mathematics](https://github.com/DSLsofMath/DSLsofMath) Course \[[gosymbol](https://github.com/victorbrun/gosymbol/tree/main)\] +- Machine Learning and algorithms needed to perform automatic differentiation \[[gorgonia](https://github.com/gorgonia/gorgonia)\] \ No newline at end of file diff --git a/smErrors/negative_exponent.go b/smErrors/negative_exponent.go new file mode 100644 index 0000000..bfbf444 --- /dev/null +++ b/smErrors/negative_exponent.go @@ -0,0 +1,20 @@ +package smErrors + +import "fmt" + +/* +negative_exponent.go +Description: + + Functions related to the negative exponent error. +*/ + +// Type Definition +type NegativeExponentError struct { + Exponent int +} + +// Error +func (e NegativeExponentError) Error() string { + return fmt.Sprintf("received negative exponent (%v); expected non-negative exponent", e.Exponent) +} diff --git a/symbolic/constant.go b/symbolic/constant.go index d1c4b26..18d80fb 100644 --- a/symbolic/constant.go +++ b/symbolic/constant.go @@ -121,6 +121,10 @@ func (c K) Plus(rightIn interface{}) Expression { // Convert to VectorExpression ve, _ := ToVectorExpression(right) return ve.Plus(c) + case mat.Dense: + return c.Plus(DenseToKMatrix(right)) + case *mat.Dense: + return c.Plus(DenseToKMatrix(*right)) case KMatrix, VariableMatrix, MonomialMatrix, PolynomialMatrix: // Convert to MatrixExpression me, _ := ToMatrixExpression(right) @@ -379,3 +383,33 @@ Description: func (c K) String() string { return fmt.Sprintf("%v", float64(c)) } + +/* +Substitute +Description: + + Substitutes the variable vIn with the expression eIn. +*/ +func (c K) Substitute(vIn Variable, eIn ScalarExpression) Expression { + return c +} + +/* +SubstituteAccordingTo +Description: + + Substitutes the variables in the map with the corresponding expressions. +*/ +func (c K) SubstituteAccordingTo(subMap map[Variable]Expression) Expression { + return c +} + +/* +Power +Description: + + Computes the power of the constant. +*/ +func (c K) Power(exponent int) Expression { + return ScalarPowerTemplate(c, exponent) +} diff --git a/symbolic/constant_matrix.go b/symbolic/constant_matrix.go index 6a66669..303e9d7 100644 --- a/symbolic/constant_matrix.go +++ b/symbolic/constant_matrix.go @@ -142,6 +142,23 @@ func (km KMatrix) Plus(e interface{}) Expression { return km.Plus(rightAsVM) // Reuse VariableMatrix case + case mat.Dense: + return km.Plus(DenseToKMatrix(right)) // Reuse KMatrix case + + case *mat.Dense: + return km.Plus(*right) // Reuse mat.Dense case + + case KMatrix: + // Create the result matrix + var result KMatrix = make([][]K, nR) + for rIndex := 0; rIndex < nR; rIndex++ { + result[rIndex] = make([]K, nC) + for cIndex := 0; cIndex < nC; cIndex++ { + result[rIndex][cIndex] = km[rIndex][cIndex] + right[rIndex][cIndex] + } + } + return result + case VariableMatrix: // Create the result matrix var result PolynomialMatrix = make([][]Polynomial, nR) @@ -257,6 +274,32 @@ func (km KMatrix) Multiply(e interface{}) Expression { case K: return km.Multiply(float64(right)) // Reuse float64 case + case Polynomial: + // Choose the correct output type based on the size of km + nR, nC := km.Dims()[0], km.Dims()[1] + switch { + case (nR == 1) && (nC == 1): + // If the output is a scalar, return a scalar + return km[0][0].Multiply(right) + case nC == 1: + // If the output is a vector, return a vector + var outputVec PolynomialVector = make([]Polynomial, nR) + for rIndex := 0; rIndex < nR; rIndex++ { + outputVec[rIndex] = km[rIndex][0].Multiply(right.Copy()).(Polynomial) + } + return outputVec + default: + // If the output is a matrix, return a matrix + var outputMat PolynomialMatrix = make([][]Polynomial, nR) + for rIndex := 0; rIndex < nR; rIndex++ { + outputMat[rIndex] = make([]Polynomial, nC) + for cIndex := 0; cIndex < nC; cIndex++ { + outputMat[rIndex][cIndex] = km[rIndex][cIndex].Multiply(right.Copy()).(Polynomial) + } + } + return outputMat + } + case *mat.VecDense: // Use gonum's built-in multiplication function var product mat.VecDense @@ -673,3 +716,33 @@ Description: func (km KMatrix) Degree() int { return 0 } + +/* +Substitute +Description: + + Substitutes all occurrences of variable vIn with the expression eIn. +*/ +func (km KMatrix) Substitute(vIn Variable, eIn ScalarExpression) Expression { + return km +} + +/* +SubstituteAccordingTo +Description: + + Substitutes all occurrences of the variables in the map with the corresponding expressions. +*/ +func (km KMatrix) SubstituteAccordingTo(subMap map[Variable]Expression) Expression { + return km +} + +/* +Power +Description: + + Raises the constant matrix to the power of the input integer. +*/ +func (km KMatrix) Power(exponent int) Expression { + return MatrixPowerTemplate(km, exponent) +} diff --git a/symbolic/constant_vector.go b/symbolic/constant_vector.go index c4865d9..0e743ef 100644 --- a/symbolic/constant_vector.go +++ b/symbolic/constant_vector.go @@ -591,3 +591,33 @@ Description: func (kv KVector) Degree() int { return 0 } + +/* +Substitute +Description: + + Substitutes all occurrences of variable vIn with the expression eIn. +*/ +func (kv KVector) Substitute(vIn Variable, eIn ScalarExpression) Expression { + return kv +} + +/* +SubstituteAccordingTo +Description: + + Substitutes all occurrences of the variables in the map with the corresponding expressions. +*/ +func (kv KVector) SubstituteAccordingTo(subMap map[Variable]Expression) Expression { + return kv +} + +/* +Power +Description: + + Raises the scalar expression to the power of the input integer. +*/ +func (kv KVector) Power(exponent int) Expression { + return VectorPowerTemplate(kv, exponent) +} diff --git a/symbolic/constraint.go b/symbolic/constraint.go index 4f233e5..ee3b905 100644 --- a/symbolic/constraint.go +++ b/symbolic/constraint.go @@ -12,6 +12,7 @@ type Constraint interface { Right() Expression ConstrSense() ConstrSense Check() error + IsLinear() bool } func IsConstraint(c interface{}) bool { diff --git a/symbolic/expression.go b/symbolic/expression.go index 0e67b7c..b26428a 100644 --- a/symbolic/expression.go +++ b/symbolic/expression.go @@ -55,6 +55,16 @@ type Expression interface { // String returns a string representation of the expression String() string + + // Substitute returns the expression with the variable vIn replaced with the expression eIn + Substitute(vIn Variable, eIn ScalarExpression) Expression + + // SubstituteAccordingToMap returns the expression with the variables in the map replaced with the corresponding expressions + SubstituteAccordingTo(subMap map[Variable]Expression) Expression + + // Power + // Raises the scalar expression to the power of the input integer + Power(exponent int) Expression } /* diff --git a/symbolic/matrix_expression.go b/symbolic/matrix_expression.go index 1b1352d..c6ead3a 100644 --- a/symbolic/matrix_expression.go +++ b/symbolic/matrix_expression.go @@ -2,6 +2,7 @@ package symbolic import ( "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/smErrors" "gonum.org/v1/gonum/mat" ) @@ -67,6 +68,16 @@ type MatrixExpression interface { // String returns a string representation of the expression String() string + + // Substitute returns the expression with the variable vIn replaced with the expression eIn + Substitute(vIn Variable, eIn ScalarExpression) Expression + + // SubstituteAccordingTo returns the expression with the variables in the map replaced with the corresponding expressions + SubstituteAccordingTo(subMap map[Variable]Expression) Expression + + // Power + // Raises the scalar expression to the power of the input integer + Power(exponent int) Expression } /* @@ -78,6 +89,8 @@ Description: func IsMatrixExpression(e interface{}) bool { // Check each type switch e.(type) { + case *mat.Dense: + return true case mat.Dense: return true case KMatrix: @@ -111,6 +124,8 @@ func ToMatrixExpression(e interface{}) (MatrixExpression, error) { // Convert switch e2 := e.(type) { + case *mat.Dense: + return DenseToKMatrix(*e2), nil case mat.Dense: return DenseToKMatrix(e2), nil case KMatrix: @@ -123,8 +138,280 @@ func ToMatrixExpression(e interface{}) (MatrixExpression, error) { return e2, nil default: return DenseToKMatrix(ZerosMatrix(1, 1)), fmt.Errorf( - "unexpected vector expression conversion requested for type %T!", + "unexpected matrix expression conversion requested for object \"%v\" of type %T!", + e, e, ) } } + +/* +IsSquare +Description: + + Determines whether the input matrix expression is square. +*/ +func IsSquare(e MatrixExpression) bool { + dims := e.Dims() + return dims[0] == dims[1] +} + +/* +MatrixPowerTemplate +Description: + + Template for the matrix power function. +*/ +func MatrixPowerTemplate(me MatrixExpression, exponent int) MatrixExpression { + // Input Processing + err := me.Check() + if err != nil { + panic(err) + } + + // Check if the matrix is square + if !IsSquare(me) { + panic(fmt.Errorf("matrix is not square; cannot raise to power")) + } + + // Check if the power is non-negative + if exponent < 0 { + panic(smErrors.NegativeExponentError{ + Exponent: exponent, + }) + } + + // Algorithm + var out MatrixExpression = K(0).Plus(Identity(me.Dims()[0])).(MatrixExpression) + for i := 0; i < exponent; i++ { + out = out.Multiply(me).(MatrixExpression) + } + return out +} + +/* +MatrixSubstituteTemplate +Description: + + Template for the matrix substitute function. +*/ +func MatrixSubstituteTemplate(me MatrixExpression, vIn Variable, seIn ScalarExpression) MatrixExpression { + // Input Processing + err := me.Check() + if err != nil { + panic(err) + } + + err = vIn.Check() + if err != nil { + panic(err) + } + + err = seIn.Check() + if err != nil { + panic(err) + } + + // Algorithm + var out [][]ScalarExpression + for ii := 0; ii < me.Dims()[0]; ii++ { + var tempRow []ScalarExpression + for jj := 0; jj < me.Dims()[1]; jj++ { + newElt := me.At(ii, jj).Substitute(vIn, seIn) + tempRow = append(tempRow, newElt.(ScalarExpression)) + } + out = append(out, tempRow) + } + return ConcretizeMatrixExpression(out) +} + +/* +ConcretizeMatrixExpression +Description: + + Converts the input expression to a valid type that implements "MatrixExpression". +*/ +func ConcretizeMatrixExpression(sliceIn [][]ScalarExpression) MatrixExpression { + // Input Processing + if len(sliceIn) == 0 { + panic( + fmt.Errorf( + "the input slice is empty, which is not recognized as a VectorExpression.", + ), + ) + } + + // Check the number of columns in each row + numCols := len(sliceIn[0]) + for ii, row := range sliceIn { + if len(row) != numCols { + panic( + fmt.Errorf( + "all rows in the input slice must have the same number of columns, but row %v has %v columns (expected %v).", + ii, + len(row), + numCols, + ), + ) + + } + } + + // Check the type of all expressions + var ( + containsConstant bool = false + isAllVariables bool = true + containsVariable bool = false + containsMonomial bool = false + containsPolynomial bool = false + ) + + for ii, row := range sliceIn { + for jj, elt := range row { + // Check each element in row + if _, tf := elt.(Variable); !tf { + isAllVariables = false + } + + switch elt.(type) { + case K: + containsConstant = true + case Variable: + containsVariable = true + case Monomial: + containsMonomial = true + case Polynomial: + containsPolynomial = true + default: + panic( + fmt.Errorf( + "unexpected expression type in matrix expression at [%v,%v]: %T", + ii, jj, + elt, + ), + ) + } + } + } + + // Convert + switch { + case containsPolynomial: + // Convert to a polynomial vector + var out PolynomialMatrix + for _, row_ii := range sliceIn { + var tempRow []Polynomial + for _, elt := range row_ii { + switch tempE := elt.(type) { + case Polynomial: + tempRow = append(tempRow, tempE) + case Monomial: + tempRow = append(tempRow, tempE.ToPolynomial()) + case Variable: + tempRow = append(tempRow, tempE.ToPolynomial()) + case K: + tempRow = append(tempRow, tempE.ToPolynomial()) + default: + panic( + smErrors.UnsupportedInputError{ + FunctionName: "ConcretizeVectorExpression", + Input: tempE, + }, + ) + } + } + out = append(out, tempRow) + } + + return out + + case containsMonomial || (containsVariable && containsConstant): + // Convert to a monomial vector + var out MonomialMatrix + for _, row_ii := range sliceIn { + var tempRow []Monomial + for _, elt := range row_ii { + switch tempE := elt.(type) { + case Monomial: + tempRow = append(tempRow, tempE) + case Variable: + tempRow = append(tempRow, tempE.ToMonomial()) + case K: + tempRow = append(tempRow, tempE.ToMonomial()) + default: + panic( + smErrors.UnsupportedInputError{ + FunctionName: "ConcretizeVectorExpression", + Input: tempE, + }, + ) + } + } + out = append(out, tempRow) + } + + return out + + case isAllVariables: + // Convert to a variable vector + var out VariableMatrix + for _, row_ii := range sliceIn { + var tempRow []Variable + for _, elt := range row_ii { + switch tempE := elt.(type) { + case Variable: + tempRow = append(tempRow, tempE) + default: + panic( + smErrors.UnsupportedInputError{ + FunctionName: "ConcretizeVectorExpression", + Input: tempE, + }, + ) + } + } + out = append(out, tempRow) + } + + return out + + case containsConstant: + // Convert to a constant vector + var out KMatrix + for ii, row_ii := range sliceIn { + var tempRow []K + for jj, elt := range row_ii { + eltAsK, tf := elt.(K) + if !tf { + panic( + fmt.Errorf( + "unexpected expression type in vector expression at entry [%v,%v]: %T", + ii, jj, + elt, + ), + ) + } + tempRow = append(tempRow, eltAsK) + } + + out = append(out, tempRow) + } + + return out + + default: + panic( + fmt.Errorf( + "unrecognized vector expression type in ConcretizeMatrixExpression.\n"+ + "containsConstant = %v\n"+ + "isAllVariables = %v\n"+ + "containsMonomial = %v\n"+ + "containsPolynomial = %v\n", + containsConstant, + isAllVariables, + containsMonomial, + containsPolynomial, + ), + ) + } +} diff --git a/symbolic/monomial.go b/symbolic/monomial.go index 32f54ba..3175494 100644 --- a/symbolic/monomial.go +++ b/symbolic/monomial.go @@ -159,12 +159,6 @@ func (m Monomial) Minus(e interface{}) Expression { return Minus(m, rightAsE) } - // Algorithm - switch right := e.(type) { - case float64: - return m.Minus(K(right)) // Reuse K case - } - // Unrecognized response is a panic panic( smErrors.UnsupportedInputError{ @@ -549,7 +543,11 @@ func (m Monomial) DerivativeWrt(vIn Variable) Expression { // monomial. var monomialOut Monomial - if m.Exponents[foundIndex] == 1 { + switch { + case (m.Exponents[foundIndex] == 1) && (len(m.VariableFactors) == 1): + // Return the exponent + return K(m.Coefficient) + case m.Exponents[foundIndex] == 1: // If the degree of vIn is 1, then remove it from the monomial monomialOut.Coefficient = m.Coefficient for ii, variable := range m.VariableFactors { @@ -558,8 +556,9 @@ func (m Monomial) DerivativeWrt(vIn Variable) Expression { monomialOut.Exponents = append(monomialOut.Exponents, m.Exponents[ii]) } } - } else { + default: monomialOut = m + monomialOut.Coefficient = m.Coefficient * float64(m.Exponents[foundIndex]) monomialOut.Exponents[foundIndex] -= 1 } @@ -663,3 +662,87 @@ func (m Monomial) Copy() Monomial { // Return return mCopy } + +/* +Substitute +Description: + + Substitutes all occurrences of variable vIn with the expression eIn. +*/ +func (m Monomial) Substitute(vIn Variable, eIn ScalarExpression) Expression { + // Input Processing + err := m.Check() + if err != nil { + panic(err) + } + + err = vIn.Check() + if err != nil { + panic(err) + } + + err = eIn.Check() + if err != nil { + panic(err) + } + + // Algorithm + // Find the index of the variable in the monomial + foundIndex, _ := FindInSlice(vIn, m.VariableFactors) + + // If the variable is not in the monomial, then return the monomial + if foundIndex == -1 { + return m + } + + // If the variable is in the monomial, + // then compute the product of the monomial with the expression + var prod Expression = K(m.Coefficient) + for ii, variable := range m.VariableFactors { + prod = prod.Multiply(variable.Substitute(vIn, eIn).Power(m.Exponents[ii])) + } + + // Return + return prod +} + +/* +SubstituteAccordingTo +Description: + + Substitutes all occurrences of the variables in the map with the corresponding expressions. +*/ +func (m Monomial) SubstituteAccordingTo(subMap map[Variable]Expression) Expression { + // Input Processing + err := m.Check() + if err != nil { + panic(err) + } + + err = CheckSubstitutionMap(subMap) + if err != nil { + panic(err) + } + + // Algorithm + // Create the monomial + var out Expression = K(0.0) + + // Iterate through each variable in the monomial + for tempVar, tempExp := range subMap { + out = out.Substitute(tempVar, tempExp.(ScalarExpression)) + } + + // Return + return out +} + +/* +Power +Description: + + Computes the power of the monomial. +*/ +func (m Monomial) Power(exponent int) Expression { + return ScalarPowerTemplate(m, exponent) +} diff --git a/symbolic/monomial_matrix.go b/symbolic/monomial_matrix.go index 84a3805..eb68549 100644 --- a/symbolic/monomial_matrix.go +++ b/symbolic/monomial_matrix.go @@ -251,16 +251,6 @@ func (mm MonomialMatrix) Minus(e interface{}) Expression { return Minus(mm, eAsE) } - // Constants - switch right := e.(type) { - case float64: - return mm.Minus(K(right)) - case mat.Dense: - return mm.Minus(DenseToKMatrix(right)) - case *mat.Dense: - return mm.Minus(DenseToKMatrix(*right)) - } - // If we've gotten this far, the input is not recognized panic( smErrors.UnsupportedInputError{ @@ -515,30 +505,19 @@ func (mm MonomialMatrix) DerivativeWrt(vIn Variable) Expression { } // Compute the Derivative of each monomial - var dmm MonomialMatrix + var dmm [][]ScalarExpression for _, row := range mm { - var dmmRow []Monomial + var dmmRow []ScalarExpression for _, monomial := range row { dMonomial := monomial.DerivativeWrt(vIn) - var dMonomialAsMonomial Monomial - switch dMonomial.(type) { - case Monomial: - dMonomialAsMonomial = dMonomial.(Monomial) - case K: - dMonomialAsMonomial = dMonomial.(K).ToMonomial() - default: - panic( - fmt.Errorf("unexpected type of derivative: %T (%v)", dMonomial, dMonomial), - ) - } - // Add the converted dMonomial to dmmRow - dmmRow = append(dmmRow, dMonomialAsMonomial) + dMonomialAsSE, _ := ToScalarExpression(dMonomial) + dmmRow = append(dmmRow, dMonomialAsSE) // Add the converted dMonomial to dmmRow } dmm = append(dmm, dmmRow) } // Return the derivative - return dmm + return ConcretizeMatrixExpression(dmm) } /* @@ -649,3 +628,50 @@ func (mm MonomialMatrix) Degree() int { // Return return maxDegree } + +/* +Substitute +Description: + + Substitutes the variable v with the expression e in the monomial matrix. +*/ +func (mm MonomialMatrix) Substitute(v Variable, se ScalarExpression) Expression { + return MatrixSubstituteTemplate(mm, v, se) +} + +/* +SubstituteAccordingTo +Description: + + Substitutes the variables in the monomial matrix according to the map provided in substitutions. +*/ +func (mm MonomialMatrix) SubstituteAccordingTo(substitutions map[Variable]Expression) Expression { + // Input Processing + err := mm.Check() + if err != nil { + panic(err) + } + + err = CheckSubstitutionMap(substitutions) + if err != nil { + panic(err) + } + + // Algorithm + var out MatrixExpression = mm + for v, expr := range substitutions { + postSub := out.Substitute(v, expr.(ScalarExpression)) + out = postSub.(MatrixExpression) + } + return out +} + +/* +Power +Description: + + Returns the monomial matrix raised to the power of the input integer. +*/ +func (mm MonomialMatrix) Power(exponent int) Expression { + return MatrixPowerTemplate(mm, exponent) +} diff --git a/symbolic/monomial_vector.go b/symbolic/monomial_vector.go index f6cd124..27a51ff 100644 --- a/symbolic/monomial_vector.go +++ b/symbolic/monomial_vector.go @@ -636,3 +636,50 @@ func (mv MonomialVector) Degree() int { // Return return maxDegree } + +/* +Substitute +Description: + + This function substitutes the input variable with the input scalar expression. +*/ +func (mv MonomialVector) Substitute(vIn Variable, seIn ScalarExpression) Expression { + return VectorSubstituteTemplate(mv, vIn, seIn) +} + +/* +SubstituteAccordingTo +Description: + + This function substitutes all instances of variables in the substitutions map with their corresponding expressions. +*/ +func (mv MonomialVector) SubstituteAccordingTo(subMap map[Variable]Expression) Expression { + // Input Processing + err := mv.Check() + if err != nil { + panic(err) + } + + err = CheckSubstitutionMap(subMap) + if err != nil { + panic(err) + } + + // Setup + var out VectorExpression = mv + for varKey, expr := range subMap { + postSub := out.Substitute(varKey, expr.(ScalarExpression)) + out = postSub.(VectorExpression) + } + return out +} + +/* +Power +Description: + + This function raises the monomial vector to the input power. +*/ +func (mv MonomialVector) Power(exponent int) Expression { + return VectorPowerTemplate(mv, exponent) +} diff --git a/symbolic/polynomial.go b/symbolic/polynomial.go index 02b866b..8822b3a 100644 --- a/symbolic/polynomial.go +++ b/symbolic/polynomial.go @@ -239,12 +239,6 @@ func (p Polynomial) Minus(e interface{}) Expression { return Minus(p, eAsE) } - // Constants - switch right := e.(type) { - case float64: - return p.Copy().Minus(K(right)) - } - // If the function has reached this point, then // the input is not recognized panic( @@ -429,7 +423,13 @@ Description: The transpose operator when applied to a scalar is just the same scalar object. */ func (p Polynomial) Transpose() Expression { - return p + // Input Processing + err := p.Check() + if err != nil { + panic(err) + } + + return p.Copy() } /* @@ -624,8 +624,19 @@ func (p Polynomial) DerivativeWrt(vIn Variable) Expression { } // Append - components := monomial.DerivativeWrt(vIn) - derivative.Monomials = append(derivative.Monomials, components.(Monomial)) + dMonomial := monomial.DerivativeWrt(vIn) + switch component := dMonomial.(type) { + case Monomial: + derivative.Monomials = append(derivative.Monomials, component) + case K: + // Skip zero monomials + if float64(component) == 0.0 { + continue + } + derivative.Monomials = append(derivative.Monomials, component.ToMonomial()) + default: + panic(fmt.Errorf("Unexpected type in Polynomial.Derivative: %T", component)) + } } // If the derivative is empty, then return 0.0 @@ -752,3 +763,74 @@ func (p Polynomial) String() string { // Return return polynomialString } + +/* +Substitute +Description: + + This method substitutes the variable vIn with the expression eIn. +*/ +func (p Polynomial) Substitute(vIn Variable, eIn ScalarExpression) Expression { + // Input Processing + err := p.Check() + if err != nil { + panic(err) + } + + err = vIn.Check() + if err != nil { + panic(err) + } + + err = eIn.Check() + if err != nil { + panic(err) + } + + // Algorithm + var out Expression = K(0.0) + for _, monomial := range p.Monomials { + newMonomial := monomial.Substitute(vIn, eIn) + out = out.Plus(newMonomial).(Polynomial).Simplify() + } + + return out +} + +/* +SubstituteAccordingTo +Description: + + This method substitutes the variables in the map with the corresponding expressions. +*/ +func (p Polynomial) SubstituteAccordingTo(subMap map[Variable]Expression) Expression { + // Input Processing + err := p.Check() + if err != nil { + panic(err) + } + + err = CheckSubstitutionMap(subMap) + if err != nil { + panic(err) + } + + // Algorithm + var out Expression = K(0.0) + for _, monomial := range p.Monomials { + newMonomial := monomial.SubstituteAccordingTo(subMap) + out = out.Plus(newMonomial) + } + + return out +} + +/* +Power +Description: + + Computes the power of the constant. +*/ +func (p Polynomial) Power(exponent int) Expression { + return ScalarPowerTemplate(p, exponent) +} diff --git a/symbolic/polynomial_like.go b/symbolic/polynomial_like.go index 1c88399..b879cc0 100644 --- a/symbolic/polynomial_like.go +++ b/symbolic/polynomial_like.go @@ -60,6 +60,15 @@ type PolynomialLike interface { // Degree returns the degree of the expression Degree() int + + // Substitute returns the expression with the variable vIn replaced with the expression eIn + Substitute(vIn Variable, eIn ScalarExpression) Expression + + // SubstituteAccordingTo returns the expression with the variables in the map replaced with the corresponding expressions + SubstituteAccordingTo(subMap map[Variable]Expression) Expression + + // Power returns the expression raised to the power of the input exponent + Power(exponent int) Expression } /* diff --git a/symbolic/polynomial_like_matrix.go b/symbolic/polynomial_like_matrix.go index cdf9a7d..27393ce 100644 --- a/symbolic/polynomial_like_matrix.go +++ b/symbolic/polynomial_like_matrix.go @@ -70,6 +70,15 @@ type PolynomialLikeMatrix interface { // Degree returns the degree of the expression Degree() int + + // Substitute returns the expression with the variable vIn replaced with the expression eIn + Substitute(vIn Variable, eIn ScalarExpression) Expression + + // SubstitueAccordingTo returns the expression with the variables in the map replaced with the corresponding expressions + SubstituteAccordingTo(subMap map[Variable]Expression) Expression + + // Power returns the expression raised to the power of the input exponent + Power(exponent int) Expression } /* diff --git a/symbolic/polynomial_like_scalar.go b/symbolic/polynomial_like_scalar.go index 4c8daee..d569efb 100644 --- a/symbolic/polynomial_like_scalar.go +++ b/symbolic/polynomial_like_scalar.go @@ -65,6 +65,15 @@ type PolynomialLikeScalar interface { // String returns a string representation of the expression String() string + + // Substitute replaces the variable v with the expression e + Substitute(v Variable, se ScalarExpression) Expression + + // SubstituteAccordingTo replaces the variables in the expression with the expressions in the map + SubstituteAccordingTo(subMap map[Variable]Expression) Expression + + // Power raises the expression to the power of the input integer + Power(exponent int) Expression } /* diff --git a/symbolic/polynomial_like_vector.go b/symbolic/polynomial_like_vector.go index 5e4373a..c648e04 100644 --- a/symbolic/polynomial_like_vector.go +++ b/symbolic/polynomial_like_vector.go @@ -83,6 +83,15 @@ type PolynomialLikeVector interface { // Degree returns the degree of the expression Degree() int + + // Substitute returns the expression with the variable vIn replaced with the expression eIn + Substitute(vIn Variable, eIn ScalarExpression) Expression + + // SubstitueAccordingTo returns the expression with the variables in the map replaced with the corresponding expressions + SubstituteAccordingTo(subMap map[Variable]Expression) Expression + + // Power returns the expression raised to the power of the input exponent + Power(exponent int) Expression } /* diff --git a/symbolic/polynomial_matrix.go b/symbolic/polynomial_matrix.go index bcc1d75..f22ed50 100644 --- a/symbolic/polynomial_matrix.go +++ b/symbolic/polynomial_matrix.go @@ -637,3 +637,51 @@ func (pm PolynomialMatrix) Degree() int { } return maxDegree } + +/* +Substitute +Description: + + Substitutes the variable vIn with the expression eIn in the polynomial matrix. +*/ +func (pm PolynomialMatrix) Substitute(vIn Variable, eIn ScalarExpression) Expression { + return MatrixSubstituteTemplate(pm, vIn, eIn) +} + +/* +SubstituteAccordingTo +Description: + + Substitutes the variables in the polynomial matrix with the corresponding expressions in the map. +*/ +func (pm PolynomialMatrix) SubstituteAccordingTo(subMap map[Variable]Expression) Expression { + // Input Processing + err := pm.Check() + if err != nil { + panic(err) + } + + err = CheckSubstitutionMap(subMap) + if err != nil { + panic(err) + } + + // Algorithm + var out MatrixExpression = pm + for v, e := range subMap { + outSubbed := out.Substitute(v, e.(ScalarExpression)) + out = outSubbed.(MatrixExpression) + } + + return out +} + +/* +Power +Description: + + Raises the polynomial matrix to the power of the input integer. +*/ +func (pm PolynomialMatrix) Power(exponent int) Expression { + return MatrixPowerTemplate(pm, exponent) +} diff --git a/symbolic/polynomial_vector.go b/symbolic/polynomial_vector.go index cf4d583..004aba8 100644 --- a/symbolic/polynomial_vector.go +++ b/symbolic/polynomial_vector.go @@ -594,3 +594,51 @@ func (pv PolynomialVector) Degree() int { return maxDegree } + +/* +Substitute +Description: + + Substitutes the variable vIn with the expression eIn in the polynomial vector. +*/ +func (pv PolynomialVector) Substitute(vIn Variable, eIn ScalarExpression) Expression { + return VectorSubstituteTemplate(pv, vIn, eIn) +} + +/* +SubstituteAccordingTo +Description: + + Substitutes the variables in the polynomial vector with the expressions in the map. +*/ +func (pv PolynomialVector) SubstituteAccordingTo(subMap map[Variable]Expression) Expression { + // Input Checking + err := pv.Check() + if err != nil { + panic(err) + } + + err = CheckSubstitutionMap(subMap) + if err != nil { + panic(err) + } + + // Algorithm + var result VectorExpression = pv + for tempVar, tempExpr := range subMap { + resultSubbed := result.Substitute(tempVar, tempExpr.(ScalarExpression)) + result = resultSubbed.(VectorExpression) + } + + return result +} + +/* +Power +Description: + + Computes the power of the polynomial vector. +*/ +func (pv PolynomialVector) Power(exponent int) Expression { + return VectorPowerTemplate(pv, exponent) +} diff --git a/symbolic/scalar_expression.go b/symbolic/scalar_expression.go index e2c8083..9ad3f04 100644 --- a/symbolic/scalar_expression.go +++ b/symbolic/scalar_expression.go @@ -2,6 +2,7 @@ package symbolic import ( "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/smErrors" "gonum.org/v1/gonum/mat" ) @@ -62,6 +63,16 @@ type ScalarExpression interface { // String returns a string representation of the expression String() string + + // Substitute replaces the variable vIn with the expression eIn + Substitute(vIn Variable, seIn ScalarExpression) Expression + + // SubstituteAccordingTo returns the expression with the variables in the map replaced with the corresponding expressions + SubstituteAccordingTo(subMap map[Variable]Expression) Expression + + // Power + // Raises the scalar expression to the power of the input integer + Power(exponent int) Expression } // NewExpr returns a new expression with a single additive constant value, c, @@ -133,3 +144,31 @@ func ToScalarExpression(e interface{}) (ScalarExpression, error) { ) } } + +/* +ScalarPowerTemplate +Description: + + Defines the template for the scalar power operation. +*/ +func ScalarPowerTemplate(base ScalarExpression, exponent int) Expression { + // Setup + + // Input Processing + err := base.Check() + if err != nil { + panic(err) + } + + if exponent < 0 { + panic(smErrors.NegativeExponentError{Exponent: exponent}) + } + + // Algorithm + var result Expression = K(1.0) + for i := 0; i < exponent; i++ { + result = result.Multiply(base) + } + + return result +} diff --git a/symbolic/utils.go b/symbolic/utils.go index 2b8c15b..db42fa2 100644 --- a/symbolic/utils.go +++ b/symbolic/utils.go @@ -157,3 +157,52 @@ func CheckDimensionsInComparison(left, right Expression, senseIn ConstrSense) er // If dimensions match, then return nothing. return nil } + +/* +CheckSubstitutionMap +Description: + + This function verifies that the input substitution map is valid. i.e., the map should contain: + 1. Valid variables as keys + 2. Valid expressions as values + 3. The values should also be scalar expressions +*/ +func CheckSubstitutionMap(subMap map[Variable]Expression) error { + var err error + // Check each key, value pair in the map + for tempVar, tempExpr := range subMap { + // Verify that the key is a valid variable + err = tempVar.Check() + if err != nil { + return fmt.Errorf( + "key %v in the substitution map is not a valid variable: %v", + tempVar, + err, + ) + } + + // Verify that the value is a valid expression + err = tempExpr.Check() + if err != nil { + return fmt.Errorf( + "value %v in the substitution map[%v] is not a valid expression: %v", + tempExpr, + tempVar, + err, + ) + } + + // Verify that the value is a scalar expression + if !IsScalarExpression(tempExpr) { + return fmt.Errorf( + "value %v in the substitution map[%v] is not a scalar expression (received %T)", + tempExpr, + tempVar, + tempExpr, + ) + } + } + + // All checks passed + return nil +} diff --git a/symbolic/variable.go b/symbolic/variable.go index 7cfccf2..2354f52 100644 --- a/symbolic/variable.go +++ b/symbolic/variable.go @@ -448,16 +448,14 @@ Description: Creates a new binary variable. */ -func NewBinaryVariable(envs ...Environment) Variable { +func NewBinaryVariable(envs ...*Environment) Variable { // Constants // Input Processing - var currentEnv Environment + var currentEnv = &BackgroundEnvironment switch len(envs) { case 1: currentEnv = envs[0] - default: - currentEnv = BackgroundEnvironment } // Get New Index @@ -552,3 +550,70 @@ Description: func (v Variable) String() string { return v.Name } + +/* +Substitute +Description: + + Substitutes the variable vIn with the expression eIn. +*/ +func (v Variable) Substitute(vIn Variable, seIn ScalarExpression) Expression { + // Input Processing + err := v.Check() + if err != nil { + panic(err) + } + + err = vIn.Check() + if err != nil { + panic(err) + } + + err = seIn.Check() + if err != nil { + panic(err) + } + + // Algorithm + if v.ID == vIn.ID { + return seIn + } else { + return v + } +} + +/* +SubstituteAccordingTo +Description: + + Substitutes the variable in the map with the corresponding expression. +*/ +func (v Variable) SubstituteAccordingTo(subMap map[Variable]Expression) Expression { + // Input Processing + err := v.Check() + if err != nil { + panic(err) + } + + err = CheckSubstitutionMap(subMap) + if err != nil { + panic(err) + } + + // Algorithm + if e, ok := subMap[v]; ok { + return e + } else { + return v + } +} + +/* +Power +Description: + + Computes the power of the variable. +*/ +func (v Variable) Power(exponent int) Expression { + return ScalarPowerTemplate(v, exponent) +} diff --git a/symbolic/variable_matrix.go b/symbolic/variable_matrix.go index 98646b1..b6dca7f 100644 --- a/symbolic/variable_matrix.go +++ b/symbolic/variable_matrix.go @@ -697,3 +697,51 @@ Description: func (vm VariableMatrix) Degree() int { return 1 } + +/* +Substitute +Description: + + This function substitutes the variable vIn with the expression eIn. +*/ +func (vm VariableMatrix) Substitute(vIn Variable, eIn ScalarExpression) Expression { + return MatrixSubstituteTemplate(vm, vIn, eIn) +} + +/* +SubstituteAccordingTo +Description: + + This function substitutes the variables in the map with the corresponding expressions. +*/ +func (vm VariableMatrix) SubstituteAccordingTo(subMap map[Variable]Expression) Expression { + // Input Processing + err := vm.Check() + if err != nil { + panic(err) + } + + err = CheckSubstitutionMap(subMap) + if err != nil { + panic(err) + } + + // Algorithm + var out MatrixExpression = vm + for v, e := range subMap { + outSubbed := out.Substitute(v, e.(ScalarExpression)) + out = outSubbed.(MatrixExpression) + } + + return out +} + +/* +Power +Description: + + This function raises the variable matrix to a given power. +*/ +func (vm VariableMatrix) Power(exponent int) Expression { + return MatrixPowerTemplate(vm, exponent) +} diff --git a/symbolic/variable_vector.go b/symbolic/variable_vector.go index e7e5542..3321d2b 100644 --- a/symbolic/variable_vector.go +++ b/symbolic/variable_vector.go @@ -144,6 +144,17 @@ func (vv VariableVector) Plus(rightIn interface{}) Expression { pv = append(pv, tempPolynomial) } return pv + case VariableVector, MonomialVector, PolynomialVector: + // Setup + rightAsVE, _ := right.(VectorExpression) + + // Create a slice of scalarexpressions + var out []ScalarExpression + for ii := 0; ii < vv.Len(); ii++ { + seII, _ := vv[ii].Plus(rightAsVE.AtVec(ii)).(ScalarExpression) + out = append(out, seII) + } + return ConcretizeVectorExpression(out) default: panic( smErrors.UnsupportedInputError{ @@ -189,16 +200,6 @@ func (vv VariableVector) Minus(rightIn interface{}) Expression { return Minus(vv, rightAsE) } - // Algorithm for non-expressions - switch right := rightIn.(type) { - case float64: - return vv.Minus(K(right)) // Use K method - case mat.VecDense: - return vv.Minus(VecDenseToKVector(right)) // Use KVector method - case *mat.VecDense: - return vv.Minus(VecDenseToKVector(*right)) // Use KVector method - } - // If input isn't recognized, then panic panic( smErrors.UnsupportedInputError{ @@ -584,3 +585,52 @@ Description: func (vv VariableVector) Degree() int { return 1 } + +/* +Substitute +Description: + + Substitute returns the expression with the variable vIn replaced with the expression eIn +*/ +func (vv VariableVector) Substitute(vIn Variable, seIn ScalarExpression) Expression { + return VectorSubstituteTemplate(vv, vIn, seIn) +} + +/* +SubstituteAccordingTo +Description: + + Substitute replaces all instances of the variables in the map with the corresponding expressions. +*/ +func (vv VariableVector) SubstituteAccordingTo(subMap map[Variable]Expression) Expression { + // Input Processing + err := vv.Check() + if err != nil { + panic(err) + } + + err = CheckSubstitutionMap(subMap) + if err != nil { + panic(err) + } + + // Algorithm + var out VectorExpression = vv + for tempVar, tempExpr := range subMap { + outSubbed := out.Substitute(tempVar, tempExpr.(ScalarExpression)) + out = outSubbed.(VectorExpression) + } + + return out + +} + +/* +Power +Description: + + Raises the variable vector to the power of the input integer +*/ +func (vv VariableVector) Power(exponent int) Expression { + return VectorPowerTemplate(vv, exponent) +} diff --git a/symbolic/vector_expression.go b/symbolic/vector_expression.go index d09b822..120e04f 100644 --- a/symbolic/vector_expression.go +++ b/symbolic/vector_expression.go @@ -8,6 +8,7 @@ Description: import ( "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/smErrors" "gonum.org/v1/gonum/mat" ) @@ -80,6 +81,15 @@ type VectorExpression interface { // String returns a string representation of the expression String() string + + // Substitute returns the expression with the variable vIn replaced with the expression eIn + Substitute(vIn Variable, eIn ScalarExpression) Expression + + // SubstituteAccordingTo returns the expression with the variables in the map replaced with the corresponding expressions + SubstituteAccordingTo(subMap map[Variable]Expression) Expression + + // Power returns the expression raised to the power of the input exponent + Power(exponent int) Expression } ///* @@ -120,6 +130,8 @@ Description: func IsVectorExpression(e interface{}) bool { // Check each type switch e.(type) { + case *mat.VecDense: + return true case mat.VecDense: return true case KVector: @@ -155,6 +167,8 @@ func ToVectorExpression(e interface{}) (VectorExpression, error) { switch e2 := e.(type) { case KVector: return e2, nil + case *mat.VecDense: + return VecDenseToKVector(*e2), nil case mat.VecDense: return VecDenseToKVector(e2), nil case VariableVector: @@ -170,3 +184,226 @@ func ToVectorExpression(e interface{}) (VectorExpression, error) { ) } } + +/* +ConcretizeVectorExpression +Description: + + Converts the input expression to a valid type that implements "VectorExpression". +*/ +func ConcretizeVectorExpression(sliceIn []ScalarExpression) VectorExpression { + // Input Processing + if len(sliceIn) == 0 { + panic( + fmt.Errorf( + "the input slice is empty, which is not recognized as a VectorExpression.", + ), + ) + } + + // Check the type of all expressions + var ( + containsConstant bool = false + isAllVariables bool = true + containsVariable bool = false + containsMonomial bool = false + containsPolynomial bool = false + ) + + for _, expr := range sliceIn { + if _, tf := expr.(Variable); !tf { + isAllVariables = false + } + + switch expr.(type) { + case K: + containsConstant = true + case Variable: + containsVariable = true + case Monomial: + containsMonomial = true + case Polynomial: + containsPolynomial = true + default: + panic( + fmt.Errorf("unexpected expression type in vector expression: %T", expr), + ) + } + } + + // Convert + switch { + case containsPolynomial: + // Convert to a polynomial vector + var out PolynomialVector + for _, e_ii := range sliceIn { + switch tempE := e_ii.(type) { + case Polynomial: + out = append(out, tempE) + case Monomial: + out = append(out, tempE.ToPolynomial()) + case Variable: + out = append(out, tempE.ToPolynomial()) + case K: + out = append(out, tempE.ToPolynomial()) + default: + panic( + smErrors.UnsupportedInputError{ + FunctionName: "ConcretizeVectorExpression", + Input: tempE, + }, + ) + } + } + + return out + + case containsMonomial || (containsConstant && containsVariable): + // Convert to a monomial vector + var out MonomialVector + for _, e_ii := range sliceIn { + switch tempE := e_ii.(type) { + case Monomial: + out = append(out, tempE) + case Variable: + out = append(out, tempE.ToMonomial()) + case K: + out = append(out, tempE.ToMonomial()) + default: + panic( + smErrors.UnsupportedInputError{ + FunctionName: "ConcretizeVectorExpression", + Input: tempE, + }, + ) + } + } + + return out + + case isAllVariables: + // Convert to a variable vector + var out VariableVector + for _, e_ii := range sliceIn { + switch tempE := e_ii.(type) { + case Variable: + out = append(out, tempE) + default: + panic( + smErrors.UnsupportedInputError{ + FunctionName: "ConcretizeVectorExpression", + Input: tempE, + }, + ) + } + } + + return out + + case containsConstant: + // Convert to a constant vector + var out KVector + for ii, i2 := range sliceIn { + i2AsK, tf := i2.(K) + if !tf { + panic( + fmt.Errorf( + "unexpected expression type in vector expression at %v: %T", + ii, + i2, + ), + ) + } + out = append(out, i2AsK) + } + + return out + + default: + panic( + fmt.Errorf( + "unrecognized vector expression type in ConcretizeVectorExpression.\n"+ + "containsConstant = %v\n"+ + "isAllVariables = %v\n"+ + "containsMonomial = %v\n"+ + "containsPolynomial = %v\n", + containsConstant, + isAllVariables, + containsMonomial, + containsPolynomial, + ), + ) + } +} + +/* +VectorSubstituteTemplate +Description: + + Defines the template for the vector substitution operation. +*/ +func VectorSubstituteTemplate(ve VectorExpression, vIn Variable, se ScalarExpression) VectorExpression { + // Input Processing + err := ve.Check() + if err != nil { + panic(err) + } + + err = vIn.Check() + if err != nil { + panic(err) + } + + err = se.Check() + if err != nil { + panic(err) + } + + // Algorithm + var result []ScalarExpression + for ii := 0; ii < ve.Len(); ii++ { + eltII := ve.AtVec(ii) + postSub := eltII.Substitute(vIn, se) + result = append(result, postSub.(ScalarExpression)) + } + + return ConcretizeVectorExpression(result) +} + +/* +VectorPowerTemplate +Description: + + Defines the template for the vector power operation. +*/ +func VectorPowerTemplate(base VectorExpression, exponent int) Expression { + // Setup + + // Input Processing + err := base.Check() + if err != nil { + panic(err) + } + + if exponent < 0 { + panic(smErrors.NegativeExponentError{Exponent: exponent}) + } + + if base.Len() != 1 { + panic( + fmt.Errorf( + "the Power operation is only defined for vectors of length 1, but the input vector has length %v.", + base.Len(), + ), + ) + + } + + // Algorithm + var result Expression = K(1.0) + for i := 0; i < exponent; i++ { + result = result.Multiply(base.AtVec(0)) + } + + return result +} diff --git a/testing/symbolic/constant_matrix_test.go b/testing/symbolic/constant_matrix_test.go index 0c1595d..d5856d5 100644 --- a/testing/symbolic/constant_matrix_test.go +++ b/testing/symbolic/constant_matrix_test.go @@ -11,6 +11,8 @@ import ( getKMatrix "github.com/MatProGo-dev/SymbolicMath.go/get/KMatrix" "github.com/MatProGo-dev/SymbolicMath.go/smErrors" "github.com/MatProGo-dev/SymbolicMath.go/symbolic" + "math" + "reflect" "strings" "testing" ) @@ -1182,3 +1184,161 @@ func TestKMatrix_String1(t *testing.T) { } } } + +/* +TestKMatrix_DerivativeWrt1 +Description: + + Tests that the DerivativeWrt() method properly returns a matrix of all + zeros when the KMatrix is any nonzero constant. +*/ +func TestKMatrix_DerivativeWrt1(t *testing.T) { + // Setup + A := getKMatrix.From([][]float64{ + {1, 2, 3}, + {4, 5, 6}, + }) + + // Verify that derivative is a KMatrix as well + derivative := A.DerivativeWrt(symbolic.NewVariable()) + if _, ok := derivative.(symbolic.KMatrix); !ok { + t.Errorf("Expected derivative to be a KMatrix; got %T", derivative) + } + + // Verify that the derivative is all zeros + nR, nC := A.Dims()[0], A.Dims()[1] + for rowIndex := 0; rowIndex < nR; rowIndex++ { + for colIndex := 0; colIndex < nC; colIndex++ { + if elt := float64(derivative.(symbolic.KMatrix).At(rowIndex, colIndex).(symbolic.K)); elt != 0.0 { + t.Errorf("Expected derivative to be all zeros; got %v", derivative) + } + } + } +} + +/* +TestKMatrix_Degree1 +Description: + + Tests that the Degree() method properly returns the degree of the KMatrix. (zero) +*/ +func TestKMatrix_Degree1(t *testing.T) { + // Setup + A := getKMatrix.From([][]float64{ + {1, 2, 3}, + {4, 5, 6}, + }) + + // Verify that the degree is zero + if deg := A.Degree(); deg != 0 { + t.Errorf("Expected degree to be 0; got %v", deg) + } +} + +/* +TestKMatrix_Substitute1 +Description: + + Tests that the Substitute() method properly runs the substitute method when given + a variable to substite for and an expression to substitute with. In this case, the substitution SHOULD + NOT CHANGE ANYTHING. +*/ +func TestKMatrix_Substitute1(t *testing.T) { + // Setup + A := getKMatrix.From([][]float64{ + {1, 2, 3}, + {4, 5, 6}, + }) + + // Substitute + sub := A.Substitute(symbolic.NewVariable(), symbolic.NewVariable()) + + // Verify that the result is the same as the original + if !reflect.DeepEqual(A, sub) { + t.Errorf("Expected substitution to not change anything; got %v", sub) + } +} + +/* +TestKMatrix_SubstituteAccordingTo1 +Description: + + Tests that the SubstituteAccordingTo() method properly runs the substitute method when given + a map of substitutions. In this case, the substitution SHOULD NOT CHANGE ANYTHING. +*/ +func TestKMatrix_SubstituteAccordingTo1(t *testing.T) { + // Setup + A := getKMatrix.From([][]float64{ + {1, 2, 3}, + {4, 5, 6}, + }) + + // Substitute + sub := A.SubstituteAccordingTo( + map[symbolic.Variable]symbolic.Expression{ + symbolic.NewVariable(): symbolic.NewVariable(), + }) + + // Verify that the result is the same as the original + if !reflect.DeepEqual(A, sub) { + t.Errorf("Expected substitution to not change anything; got %v", sub) + } +} + +/* +TestKMatrix_Power1 +Description: + + Tests that the Power() method properly raises the KMatrix to the power of 1. +*/ +func TestKMatrix_Power1(t *testing.T) { + // Setup + A := getKMatrix.From([][]float64{ + {1, 2, 3}, + {4, 5, 6}, + {7, 8, 9}, + }) + + // Power + pow := A.Power(1) + + // Verify that the result is the same as the original + if !reflect.DeepEqual(A, pow) { + t.Errorf("Expected power to not change anything; got %v", pow) + } +} + +/* +TestKMatrix_Power2 +Description: + + Tests that the Power() method properly raises the KMatrix to the power of 2. +*/ +func TestKMatrix_Power2(t *testing.T) { + // Setup + A := getKMatrix.From([][]float64{ + {1, 0, 0}, + {0, 2, 0}, + {0, 0, 3}, + }) + + // Power + pow := A.Power(2) + + // Verify that the result is a diagonal matrix with + // the diagonal elements being the squares of the original + nR, nC := A.Dims()[0], A.Dims()[1] + for rowIndex := 0; rowIndex < nR; rowIndex++ { + for colIndex := 0; colIndex < nC; colIndex++ { + if rowIndex == colIndex { + if elt := float64(pow.(symbolic.KMatrix).At(rowIndex, colIndex).(symbolic.K)); elt != math.Pow(float64(A.At(rowIndex, colIndex).(symbolic.K)), 2) { + t.Errorf("Expected diagonal element to be squared; got %v", pow) + } + } else { + if elt := float64(pow.(symbolic.KMatrix).At(rowIndex, colIndex).(symbolic.K)); elt != 0 { + t.Errorf("Expected off-diagonal element to be zero; got %v", pow) + } + } + } + } +} diff --git a/testing/symbolic/constant_test.go b/testing/symbolic/constant_test.go index a39b647..4d282a6 100644 --- a/testing/symbolic/constant_test.go +++ b/testing/symbolic/constant_test.go @@ -757,6 +757,82 @@ func TestConstant_Minus3(t *testing.T) { k1.Minus(s1) } +/* +TestConstant_Minus4 +Description: + + Tests that the Minus() method properly subtracts a float64 from a well-defined constant K. +*/ +func TestConstant_Minus4(t *testing.T) { + // Setup + k1 := symbolic.K(3.14) + i1 := 2.0 + + // Test + res := k1.Minus(i1) + + // Check that result is a constant + if _, tf := res.(symbolic.K); !tf { + t.Errorf( + "expected Minus() to return a K; received %T", + res, + ) + } + + // Check that the proper result is revealed. + if !floats.EqualApprox( + []float64{float64(res.(symbolic.K))}, + []float64{1.14}, + 0.001, + ) { + t.Errorf( + "expected constant to be 1.14; received %v", + res, + ) + } +} + +/* +TestConstant_Minus5 +Description: + + Tests that the Minus() method properly panics when called with an expression that is not well-defined. + (in this case, it's a variable with a lower bound greater than the upper bound). +*/ +func TestConstant_Minus5(t *testing.T) { + // Setup + k1 := symbolic.K(3.14) + v1 := symbolic.Variable{ + Lower: 1, + Upper: 0, + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected Minus() to panic when given a bad symbolic.Variable; received nothing", + ) + } + + rAsError := r.(error) + expectedError := fmt.Errorf("lower bound (%v) of variable must be less than upper bound (%v)", v1.Lower, v1.Upper) + if !strings.Contains( + rAsError.Error(), + expectedError.Error(), + ) { + t.Errorf( + "expected Minus() to panic with error \"%v\"; received %v", + expectedError, + rAsError, + ) + } + }() + + k1.Minus(v1) +} + /* TestConstant_LessEq1 Description: @@ -1357,3 +1433,74 @@ func TestConstant_DerivativeWrt1(t *testing.T) { ) } } + +/* +TestConstant_Substitute1 +Description: + + Tests that the Substitute() method properly returns + a constant when called with a variable that we want to replace with a new variable. +*/ +func TestConstant_Substitute1(t *testing.T) { + // Constants + k1 := symbolic.K(3.14) + + // Test + substituted := k1.Substitute(symbolic.NewVariable(), symbolic.NewVariable()) + + // Verifies that the value of substituted is the same as k1 + if float64(substituted.(symbolic.K)) != 3.14 { + t.Errorf( + "expected Substitute() to return a K 3.14; received %v", + substituted, + ) + } +} + +/* +TestConstant_Substitute2 +Description: + + Tests that the Substitute() method properly returns + a constant when called with a variable that we want to replace with a constant. +*/ +func TestConstant_Substitute2(t *testing.T) { + // Constants + k1 := symbolic.K(3.14) + + // Test + substituted := k1.Substitute(symbolic.NewVariable(), symbolic.K(2.71)) + + // Verifies that the value of substituted is the same as k1 + if float64(substituted.(symbolic.K)) != 3.14 { + t.Errorf( + "expected Substitute() to return a K 3.14; received %v", + substituted, + ) + } +} + +/* +TestConstant_SubstituteAccordingTo1 +Description: + + Tests that the SubstituteAccordingTo() method properly returns + a constant when called with a map that does not contain the variable +*/ +func TestConstant_SubstituteAccordingTo1(t *testing.T) { + // Constants + k1 := symbolic.K(3.14) + + // Test + substituted := k1.SubstituteAccordingTo(map[symbolic.Variable]symbolic.Expression{ + symbolic.NewVariable(): symbolic.NewVariable().Minus(1.0), + }) + + // Verifies that the value of substituted is the same as k1 + if float64(substituted.(symbolic.K)) != 3.14 { + t.Errorf( + "expected SubstituteAccordingTo() to return a K 3.14; received %v", + substituted, + ) + } +} diff --git a/testing/symbolic/matrix_expression_test.go b/testing/symbolic/matrix_expression_test.go index 72b5e72..34e7577 100644 --- a/testing/symbolic/matrix_expression_test.go +++ b/testing/symbolic/matrix_expression_test.go @@ -2,6 +2,7 @@ package symbolic_test import ( "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/smErrors" "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "strings" "testing" @@ -46,3 +47,369 @@ func TestMatrixExpression_ToMatrixExpression1(t *testing.T) { } } } + +/* +TestMatrixExpression_ConcretizeMatrixExpression1 +Description: + + Tests the conversion of a slice of slices of constants (K) to a KMatrix. +*/ +func TestMatrixExpression_ConcretizeMatrixExpression1(t *testing.T) { + // Setup + x := [][]symbolic.ScalarExpression{ + {symbolic.K(1.0), symbolic.K(2), symbolic.K(3)}, + {symbolic.K(4), symbolic.K(5), symbolic.K(6)}, + {symbolic.K(7), symbolic.K(8), symbolic.K(9)}, + } + + // Test + me := symbolic.ConcretizeMatrixExpression(x) + + // Check that me is a KMatrix + if _, ok := me.(symbolic.KMatrix); !ok { + t.Errorf("Expected a KMatrix; received %T", me) + } + + // Check that me is the correct KMatrix (i.e., it has the correct dimensions) + if me.Dims()[0] != 3 || me.Dims()[1] != 3 { + t.Errorf("Expected a 3x3 KMatrix; received %dx%d KMatrix", me.Dims()[0], me.Dims()[1]) + } + +} + +/* +TestMatrixExpression_ConcretizeMatrixExpression2 +Description: + + Tests the conversion of a slice of slices of Monomials to a MonomialMatrix. +*/ +func TestMatrixExpression_ConcretizeMatrixExpression2(t *testing.T) { + // Setup + m := symbolic.NewVariable().ToMonomial() + x := [][]symbolic.ScalarExpression{ + {m, m}, + {m, m}, + } + + // Test + me := symbolic.ConcretizeMatrixExpression(x) + + // Check that me is a MonomialMatrix + if _, ok := me.(symbolic.MonomialMatrix); !ok { + t.Errorf("Expected a MonomialMatrix; received %T", me) + } + + // Check that me is the correct MonomialMatrix (i.e., it has the correct dimensions) + if me.Dims()[0] != 2 || me.Dims()[1] != 2 { + t.Errorf("Expected a 2x2 MonomialMatrix; received %dx%d MonomialMatrix", me.Dims()[0], me.Dims()[1]) + } +} + +/* +TestMatrixExpression_ConcretizeMatrixExpression3 +Description: + + Tests the conversion of a slice of slices of Polynomials to a PolynomialMatrix. +*/ +func TestMatrixExpression_ConcretizeMatrixExpression3(t *testing.T) { + // Setup + p := symbolic.NewVariable().ToPolynomial() + x := [][]symbolic.ScalarExpression{ + {p, p}, + {p, p}, + } + + // Test + me := symbolic.ConcretizeMatrixExpression(x) + + // Check that me is a PolynomialMatrix + if _, ok := me.(symbolic.PolynomialMatrix); !ok { + t.Errorf("Expected a PolynomialMatrix; received %T", me) + } + + // Check that me is the correct PolynomialMatrix (i.e., it has the correct dimensions) + if me.Dims()[0] != 2 || me.Dims()[1] != 2 { + t.Errorf("Expected a 2x2 PolynomialMatrix; received %dx%d PolynomialMatrix", me.Dims()[0], me.Dims()[1]) + } +} + +/* +TestMatrixExpression_ConcretizeMatrixExpression4 +Description: + + Tests the conversion of a slice of slices of Variable objects to a VariableMatrix. +*/ +func TestMatrixExpression_ConcretizeMatrixExpression4(t *testing.T) { + // Setup + v := symbolic.NewVariable() + x := [][]symbolic.ScalarExpression{ + {v, v}, + {v, v}, + {v, v}, + } + + // Test + me := symbolic.ConcretizeMatrixExpression(x) + + // Check that me is a VariableMatrix + if _, ok := me.(symbolic.VariableMatrix); !ok { + t.Errorf("Expected a VariableMatrix; received %T", me) + } + + // Check that me is the correct VariableMatrix (i.e., it has the correct dimensions) + if me.Dims()[0] != 3 || me.Dims()[1] != 2 { + t.Errorf("Expected a 3x2 VariableMatrix; received %dx%d VariableMatrix", me.Dims()[0], me.Dims()[1]) + } + +} + +/* +TestMatrixExpression_ConcretizeMatrixExpression5 +Description: + + Tests the conversion of a slice of slices constaining constants (K) and variables (symbolic.variable) expressions + to a MonomialMatrix. +*/ +func TestMatrixExpression_ConcretizeMatrixExpression5(t *testing.T) { + // Setup + k := symbolic.K(2) + v := symbolic.NewVariable() + x := [][]symbolic.ScalarExpression{ + {k, v}, + {v, k}, + } + + // Test + me := symbolic.ConcretizeMatrixExpression(x) + + // Check that me is a MonomialMatrix + if _, ok := me.(symbolic.MonomialMatrix); !ok { + t.Errorf("Expected a MonomialMatrix; received %T", me) + } + + // Check that me is the correct MonomialMatrix (i.e., it has the correct dimensions) + if me.Dims()[0] != 2 || me.Dims()[1] != 2 { + t.Errorf("Expected a 2x2 MonomialMatrix; received %dx%d MonomialMatrix", me.Dims()[0], me.Dims()[1]) + } +} + +/* +TestMatrixExpression_MatrixPowerTemplate1 +Description: + + Tests that the matrix power template properly panics when called with a MatrixExpression that is not + well-defined (in this case, a MonomialMatrix). +*/ +func TestMatrixExpression_MatrixPowerTemplate1(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}, + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("Expected a panic when calling MatrixPowerTemplate 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.MatrixPowerTemplate(x, 2) +} + +/* +TestMatrixExpression_MatrixPowerTemplate2 +Description: + + Tests that the matrix power template properly panics when called with a MatrixExpression that is well-defined + but is not square. +*/ +func TestMatrixExpression_MatrixPowerTemplate2(t *testing.T) { + // Setup + m := symbolic.NewVariable().ToMonomial() + x := symbolic.MonomialMatrix{ + {m, m}, + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("Expected a panic when calling MatrixPowerTemplate on a non-square 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(), "matrix is not square") { + t.Errorf("Expected the panic to contain the error message %v; received %v", "Matrix is not square", rAsE.Error()) + } + }() + symbolic.MatrixPowerTemplate(x, 2) +} + +/* +TestMatrixExpression_MatrixPowerTemplate3 +Description: + + Tests that the matrix power template properly panics when called with a MatrixExpression that is well-defined + and is square but with a negative exponent. +*/ +func TestMatrixExpression_MatrixPowerTemplate3(t *testing.T) { + // Setup + m := symbolic.NewVariable().ToMonomial() + x := symbolic.MonomialMatrix{ + {m, m}, + {m, m}, + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("Expected a panic when calling MatrixPowerTemplate with a negative exponent; received nil") + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf("Expected the panic to be an error; received %T", r) + } + + expectedError := smErrors.NegativeExponentError{ + Exponent: -2, + } + if !strings.Contains(rAsE.Error(), expectedError.Error()) { + t.Errorf("Expected the panic to contain the error message %v; received %v", "Negative exponent", rAsE.Error()) + } + }() + symbolic.MatrixPowerTemplate(x, -2) +} + +/* +TestMatrixExpression_MatrixSubstituteTemplate1 +Description: + + Tests that the matrix substitute template properly panics when called with a MatrixExpression that is not + well-defined (in this case, a MonomialMatrix). +*/ +func TestMatrixExpression_MatrixSubstituteTemplate1(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}, + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("Expected a panic when calling MatrixSubstituteTemplate 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.MatrixSubstituteTemplate(x, symbolic.NewVariable(), symbolic.NewVariable()) +} + +/* +TestMatrixExpression_MatrixSubstituteTemplate2 +Description: + + Tests that the matrix substitute template properly panics when called with a MatrixExpression that is well-defined + and a variable vIn that is not well-defined. +*/ +func TestMatrixExpression_MatrixSubstituteTemplate2(t *testing.T) { + // Setup + m := symbolic.NewVariable().ToMonomial() + x := symbolic.MonomialMatrix{ + {m, m}, + {m, m}, + } + v1 := symbolic.Variable{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("Expected a panic when calling MatrixSubstituteTemplate with an invalid variable; 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(), v1.Check().Error()) { + t.Errorf("Expected the panic to contain the error message %v; received %v", "the input variable is not well-defined", rAsE.Error()) + } + }() + symbolic.MatrixSubstituteTemplate(x, v1, symbolic.NewVariable()) +} + +/* +TestMatrixExpression_MatrixSubstituteTemplate3 +Description: + + Tests that the matrix substitute template properly panics when called with a MatrixExpression that is well-defined, + a variable vIn that is well-defined, but an expression eIn that is not well-defined (in this case a monomial). +*/ +func TestMatrixExpression_MatrixSubstituteTemplate3(t *testing.T) { + // Setup + m := symbolic.NewVariable().ToMonomial() + x := symbolic.MonomialMatrix{ + {m, m}, + {m, m}, + } + v1 := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 1.2, + VariableFactors: []symbolic.Variable{symbolic.NewVariable(), symbolic.NewVariable()}, + Exponents: []int{1}, + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("Expected a panic when calling MatrixSubstituteTemplate with an invalid expression; 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(), m1.Check().Error()) { + t.Errorf("Expected the panic to contain the error message %v; received %v", m1.Check().Error(), rAsE.Error()) + } + }() + symbolic.MatrixSubstituteTemplate(x, v1, m1) +} diff --git a/testing/symbolic/monomial_matrix_test.go b/testing/symbolic/monomial_matrix_test.go index 18c4dc2..bc704e6 100644 --- a/testing/symbolic/monomial_matrix_test.go +++ b/testing/symbolic/monomial_matrix_test.go @@ -776,6 +776,211 @@ func TestMonomialMatrix_Plus9(t *testing.T) { mm.Plus("a") } +/* +TestMonomialMatrix_Minus1 +Description: + + Verifies that the Minus() method panics if the monomial matrix + that it is called on is not well formed. +*/ +func TestMonomialMatrix_Minus1(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 1.0, + VariableFactors: []symbolic.Variable{v1}, + Exponents: []int{1, 2}, + } + var mm symbolic.MonomialMatrix = [][]symbolic.Monomial{ + {m1, m1}, + {m1}, + {m1, m1}, + } + mm2 := mm + + // Test + defer func() { + if r := recover(); r == nil { + t.Errorf( + "expected Minus() to panic; it did not", + ) + } + }() + mm.Minus(mm2) +} + +/* +TestMonomialMatrix_Minus2 +Description: + + Verifies that the Minus() method panics if the second expression + input to the method is not well formed. +*/ +func TestMonomialMatrix_Minus2(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + var mm symbolic.MonomialMatrix = [][]symbolic.Monomial{ + {v1.ToMonomial(), v1.ToMonomial()}, + {v1.ToMonomial(), v1.ToMonomial()}, + } + var mm2 symbolic.MonomialMatrix + + expectedError := mm2.Check() + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected Minus() to panic; it did not", + ) + } + + // Check that the error is correct + err, ok := r.(error) + if !ok { + t.Errorf( + "expected Minus() to panic with an error; it panicked with %v", + r, + ) + } + + if !strings.Contains( + err.Error(), + expectedError.Error(), + ) { + t.Errorf( + "expected Minus() to panic with error \"%v\"; it panicked with \"%v\"", + expectedError, + err, + ) + } + + }() + mm.Minus(mm2) + +} + +/* +TestMonomialMatrix_Minus3 +Description: + + Verifies that the Minus() method panics if the two expressions + are not the same size. (In this case the first expression + is 3 x 2 and the second is 2 x 2). +*/ +func TestMonomialMatrix_Minus3(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + var mm symbolic.MonomialMatrix = [][]symbolic.Monomial{ + {v1.ToMonomial(), v1.ToMonomial()}, + {v1.ToMonomial(), v1.ToMonomial()}, + {v1.ToMonomial(), v1.ToMonomial()}, + } + var mm2 symbolic.MonomialMatrix = [][]symbolic.Monomial{ + {v1.ToMonomial(), v1.ToMonomial()}, + {v1.ToMonomial(), v1.ToMonomial()}, + } + expectedError := smErrors.DimensionError{ + Arg1: mm, + Arg2: mm2, + Operation: "Minus", + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected Minus() to panic; it did not", + ) + } + + // Check that the error is correct + err, ok := r.(error) + if !ok { + t.Errorf( + "expected Minus() to panic with an error; it panicked with %v", + r, + ) + } + + if !strings.Contains( + err.Error(), + expectedError.Error(), + ) { + t.Errorf( + "expected Minus() to panic with error \"%v\"; it panicked with \"%v\"", + expectedError, + err, + ) + } + + }() + mm.Minus(mm2) +} + +/* +TestMonomialMatrix_Minus4 +Description: + + Tests that the Minus() method properly subtracts a constant from a matrix + of Monomials. (In this case, the monomial matrix is a 2 x2 matrix + of single variables and the constant is 1.0). +*/ +func TestMonomialMatrix_Minus4(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + var mm symbolic.MonomialMatrix = [][]symbolic.Monomial{ + {v1.ToMonomial(), v1.ToMonomial()}, + {v1.ToMonomial(), v1.ToMonomial()}, + } + f2 := 1.0 + + // Test + difference := mm.Minus(f2) + + // Check that the difference is of the PolynomialMatrix type + _, ok := difference.(symbolic.PolynomialMatrix) + if !ok { + t.Errorf( + "expected Minus() to return a PolynomialMatrix; received %v", + difference, + ) + } + + // Check that each entry in the difference: + // 1. Contains 2 monomials + // 2. Contains the original monomial + // 3. Contains the constant monomial + for _, row := range difference.(symbolic.PolynomialMatrix) { + for _, polynomial := range row { + if len(polynomial.Monomials) != 2 { + t.Errorf( + "expected Minus() to return a PolynomialMatrix with 2 monomials; received %v", + polynomial.Monomials, + ) + } + + // Check that the first monomial is the original monomial + if v1Index := polynomial.VariableMonomialIndex(v1); v1Index == -1 { + t.Errorf( + "expected Minus() to return a PolynomialMatrix with the original monomial; received %v", + polynomial, + ) + } + + // Check that the second monomial is the constant monomial + if constIndex := polynomial.ConstantMonomialIndex(); polynomial.Monomials[constIndex].Coefficient != -1.0 { + t.Errorf( + "expected Minus() to return a PolynomialMatrix with the constant monomial; received %v", + polynomial.Monomials[1], + ) + } + } + } +} + /* TestMonomialMatrix_Multiply1 Description: @@ -1429,6 +1634,175 @@ func TestMonomialMatrix_Eq2(t *testing.T) { } } +/* +TestMonomialMatrix_DerivativeWrt1 +Description: + + This test checks that the DerivativeWrt() method properly panics when it is called + with a monomial matrix that is not well formed. +*/ +func TestMonomialMatrix_DerivativeWrt1(t *testing.T) { + // Constants + var mm symbolic.MonomialMatrix + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected DerivativeWrt() to panic; it did not", + ) + } + + rAsE, ok := r.(error) + if !ok { + t.Errorf( + "expected DerivativeWrt() to panic with an error; it panicked with %v", + r, + ) + } + + expectedError := mm.Check() + if !strings.Contains(rAsE.Error(), expectedError.Error()) { + t.Errorf( + "expected DerivativeWrt() to panic with error \"%v\"; it panicked with \"%v\"", + expectedError, + rAsE, + ) + } + }() + mm.DerivativeWrt(symbolic.NewVariable()) +} + +/* +TestMonomialMatrix_DerivativeWrt2 +Description: + + This test checks that the DerivativeWrt() method properly panics when it is called + with a monomial matrix that is well-formed and a variable that is not well-defined. +*/ +func TestMonomialMatrix_DerivativeWrt2(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + badV := symbolic.Variable{} + m1 := v1.ToMonomial() + var mm symbolic.MonomialMatrix = [][]symbolic.Monomial{ + {m1, m1}, + {m1, m1}, + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected DerivativeWrt() to panic; it did not", + ) + } + + rAsE, ok := r.(error) + if !ok { + t.Errorf( + "expected DerivativeWrt() to panic with an error; it panicked with %v", + r, + ) + } + + expectedError := badV.Check() + if !strings.Contains(rAsE.Error(), expectedError.Error()) { + t.Errorf( + "expected DerivativeWrt() to panic with error \"%v\"; it panicked with \"%v\"", + expectedError, + rAsE, + ) + } + }() + + mm.DerivativeWrt(badV) +} + +/* +TestMonomialMatrix_DerivativeWrt3 +Description: + + This test checks that the DerivativeWrt() method properly returns a matrix of + monomials that are the derivatives of the original monomials with respect to + the given variable. +*/ +func TestMonomialMatrix_DerivativeWrt3(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + v2 := symbolic.NewVariable() + m1 := v1.ToMonomial() + m2 := v2.ToMonomial() + var mm symbolic.MonomialMatrix = [][]symbolic.Monomial{ + {m1, m2}, + {m1, m2}, + } + + // Test + derivatives := mm.DerivativeWrt(v1) + + // Check that the dimensions of the derivatives are (2,2) + if dims := derivatives.Dims(); dims[0] != 2 || dims[1] != 2 { + t.Errorf( + "expected DerivativeWrt() to return a MonomialMatrix with dimensions (2,2); received %v", + dims, + ) + } + + dAsMM, ok := derivatives.(symbolic.KMatrix) + if !ok { + t.Errorf( + "expected DerivativeWrt() to return a MonomialMatrix; received %T", + derivatives, + ) + + } + + // Check that the derivatives are correct + for ii, row := range dAsMM { + for jj, derivative := range row { + // Check that the derivative is the correct monomial + if ii == 0 { + if jj == 0 { + if float64(derivative) != 1.0 { + t.Errorf( + "expected DerivativeWrt() to return a MonomialMatrix with derivative %v at (0,0); received %v", + 1.0, + derivative, + ) + } + } else { + if float64(derivative) != 0.0 { + t.Errorf( + "expected DerivativeWrt() to return a MonomialMatrix with derivative 0.0 at (0,1); received %v", + derivative, + ) + } + } + } else { + if jj == 0 { + if float64(derivative) != 1.0 { + t.Errorf( + "expected DerivativeWrt() to return a MonomialMatrix with derivative %v at (1,1); received %v", + 1.0, + derivative, + ) + } + } else { + if float64(derivative) != 0.0 { + t.Errorf( + "expected DerivativeWrt() to return a MonomialMatrix with derivative 0.0 at (1,0); received %v", + derivative, + ) + } + } + } + } + } +} + /* TestMonomialMatrix_Transpose2 Description: @@ -1632,3 +2006,264 @@ func TestMonomialMatrix_String2(t *testing.T) { mm.String() } + +/* +TestMonomialMatrix_Degree1 +Description: + + Tests that the Degree() method properly panics when called with a monomial matrix + that is not well-defined. +*/ +func TestMonomialMatrix_Degree1(t *testing.T) { + // Constants + var mm symbolic.MonomialMatrix + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected Degree() to panic; it did not", + ) + } + + rAsE, ok := r.(error) + if !ok { + t.Errorf( + "expected Degree() to panic with an error; it panicked with %v", + r, + ) + } + + expectedError := mm.Check() + if !strings.Contains(rAsE.Error(), expectedError.Error()) { + t.Errorf( + "expected Degree() to panic with error \"%v\"; it panicked with \"%v\"", + expectedError, + rAsE, + ) + } + }() + + mm.Degree() +} + +/* +TestMonomialMatrix_Degree2 +Description: + + Tests that the Degree() method properly returns the degree of the monomial matrix + when called with a well-defined monomial matrix. +*/ +func TestMonomialMatrix_Degree2(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + v2 := symbolic.NewVariable() + v3 := symbolic.NewVariable() + m1 := v1.ToMonomial() + m2 := v2.ToMonomial() + m3 := v3.ToMonomial() + var mm symbolic.MonomialMatrix = [][]symbolic.Monomial{ + {m1, m2, m3}, + {m1, symbolic.Monomial{Coefficient: 3.14, VariableFactors: []symbolic.Variable{v1, v2, v3}, Exponents: []int{1, 3, 5}}, m3}, + } + + // Test + degree := mm.Degree() + + // Check that the degree is correct + if degree != 9 { + t.Errorf( + "expected Degree() to return 1; received %v", + degree, + ) + } +} + +/* +TestMonomialMatrix_Substitute1 +Description: + + Tests that the Substitute() method properly substitutes a variable in a given + monomial matrix with a polynomial. This should lead to the matrix of monomials + becoming a matrix of polynomials. Each entry of the matrix should have the same number + of monomials as the first polynomial. +*/ +func TestMonomialMatrix_Substitute1(t *testing.T) { + // Setup + v1 := symbolic.NewVariable() + v2 := symbolic.NewVariable() + p3 := v1.Plus(v2).(symbolic.Polynomial) + + mm1 := symbolic.MonomialMatrix{ + {v1.ToMonomial(), v1.ToMonomial()}, + {v1.ToMonomial(), v1.ToMonomial()}, + } + + // Test + substituted := mm1.Substitute(v1, p3) + + // Check that substituted is a PolynomialMatrix + sAsPM, ok := substituted.(symbolic.PolynomialMatrix) + if !ok { + t.Errorf( + "expected Substitute() to return a PolynomialMatrix; received %v", + substituted, + ) + } + + // Check that each entry in the substituted matrix has the same number of monomials + // as p3 + for _, row := range sAsPM { + for _, polynomial := range row { + if len(polynomial.Monomials) != len(p3.Monomials) { + t.Errorf( + "expected Substitute() to return a PolynomialMatrix with %v monomials; received %v", + len(p3.Monomials), + len(polynomial.Monomials), + ) + } + } + } +} + +/* +TestMonomialMatrix_SubstituteWrt1 +Description: + + Tests that the SubstituteWrt() method properly substitutes a variable in a given + monomial matrix with a polynomial. This should lead to the matrix of monomials + becoming a matrix of polynomials. Each entry of the matrix should have the same number + of monomials as the test polynomial. +*/ +func TestMonomialMatrix_SubstituteAccordingTo1(t *testing.T) { + // Setup + v1 := symbolic.NewVariable() + v2 := symbolic.NewVariable() + p3 := v1.Plus(v2).(symbolic.Polynomial) + + mm1 := symbolic.MonomialMatrix{ + {v1.ToMonomial(), v1.ToMonomial()}, + {v1.ToMonomial(), v1.ToMonomial()}, + } + + // Test + substituted := mm1.SubstituteAccordingTo( + map[symbolic.Variable]symbolic.Expression{v1: p3}, + ) + + // Check that substituted is a PolynomialMatrix + sAsPM, ok := substituted.(symbolic.PolynomialMatrix) + if !ok { + t.Errorf( + "expected SubstituteWrt() to return a PolynomialMatrix; received %v", + substituted, + ) + } + + // Check that each entry in the substituted matrix has the same number of monomials + // as p3 + for _, row := range sAsPM { + for _, polynomial := range row { + if len(polynomial.Monomials) != len(p3.Monomials) { + t.Errorf( + "expected SubstituteWrt() to return a PolynomialMatrix with %v monomials; received %v", + len(p3.Monomials), + len(polynomial.Monomials), + ) + } + } + } +} + +/* +TestMonomialMatrix_SubstituteWrt2 +Description: + + Tests that the SubstituteWrt() method properly panics when called with a monomial matrix + that is not well-defined. +*/ +func TestMonomialMatrix_SubstituteAccordingTo2(t *testing.T) { + // Constants + var mm symbolic.MonomialMatrix + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected SubstituteAccordingTo() to panic; it did not", + ) + } + + rAsE, ok := r.(error) + if !ok { + t.Errorf( + "expected SubstituteAccordingTo() to panic with an error; it panicked with %v", + r, + ) + } + + expectedError := mm.Check() + if !strings.Contains(rAsE.Error(), expectedError.Error()) { + t.Errorf( + "expected SubstituteAccordingTo() to panic with error \"%v\"; it panicked with \"%v\"", + expectedError, + rAsE, + ) + } + }() + + mm.SubstituteAccordingTo(map[symbolic.Variable]symbolic.Expression{}) +} + +/* +TestMonomialMatrix_SubstituteWrt3 +Description: + + Tests that the SubstituteWrt() method properly panics when called with a monomial matrix + that is well-defined and a substitution map that is not well-defined. +*/ +func TestMonomialMatrix_SubstituteAccordingTo3(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + m1 := v1.ToMonomial() + var mm symbolic.MonomialMatrix = [][]symbolic.Monomial{ + {m1, m1}, + {m1, m1}, + } + + testMap := map[symbolic.Variable]symbolic.Expression{ + v1: symbolic.Variable{}, + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected SubstituteAccordingTo() to panic; it did not", + ) + } + + rAsE, ok := r.(error) + if !ok { + t.Errorf( + "expected SubstituteAccordingTo() to panic with an error; it panicked with %v", + r, + ) + } + + expectedError := symbolic.CheckSubstitutionMap(testMap) + if !strings.Contains(rAsE.Error(), expectedError.Error()) { + t.Errorf( + "expected SubstituteAccordingTo() to panic with error \"%v\"; it panicked with \"%v\"", + expectedError, + rAsE, + ) + } + }() + + mm.SubstituteAccordingTo(testMap) + t.Errorf("expected SubstituteAccordingTo() to panic; it did not") +} diff --git a/testing/symbolic/monomial_test.go b/testing/symbolic/monomial_test.go index b8f6cc4..8aebb33 100644 --- a/testing/symbolic/monomial_test.go +++ b/testing/symbolic/monomial_test.go @@ -8,6 +8,7 @@ Description: import ( "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/smErrors" "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "strings" "testing" @@ -402,6 +403,151 @@ func TestMonomial_Plus8(t *testing.T) { } } +/* +TestMonomial_Minus1 +Description: + + Verifies that the Minus() method panics when called using a monomial that is + not well-defined. +*/ +func TestMonomial_Minus1(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 3.14, + VariableFactors: []symbolic.Variable{v1}, + Exponents: []int{1, 2}, + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("expected Minus to panic; received nil") + } + + // Check that error is as expected: + rAsE := r.(error) + if !strings.Contains( + rAsE.Error(), + m1.Check().Error(), + ) { + t.Errorf( + "expected error message to contain %v; received %v", + m1.Check(), + rAsE.Error(), + ) + } + }() + m1.Minus(m1) +} + +/* +TestMonomial_Minus2 +Description: + + Verifies that the Monomial.Minus function panics when the monomial used to call it + is well-defined but the second input expression is not well-defined (a variable). +*/ +func TestMonomial_Minus2(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 3.14, + VariableFactors: []symbolic.Variable{v1}, + Exponents: []int{1}, + } + + // Test + defer func() { + if r := recover(); r == nil { + t.Errorf("expected Minus to panic; received nil") + } + }() + m1.Minus(symbolic.Variable{}) +} + +/* +TestMonomial_Minus3 +Description: + + Verifies that the Monomial.Minus function returns a valid polynomial when the input to it + is a floating point number (when the original monomial is NOT a constant). +*/ +func TestMonomial_Minus3(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 3.14, + VariableFactors: []symbolic.Variable{v1}, + Exponents: []int{2}, + } + f1 := 3.14 + + // Compute Difference + difference := m1.Minus(f1) + + // Verify that the difference is a polynomial + diffAsP, tf := difference.(symbolic.Polynomial) + if !tf { + t.Errorf( + "expected difference to be a K; received %T", + difference, + ) + } + + // Verify that the polynomial contains 2 monomials + if len(diffAsP.Monomials) != 2 { + t.Errorf( + "expected difference to have 2 monomials; received %v", + len(diffAsP.Monomials), + ) + } +} + +/* +TestMonomial_Minus4 +Description: + + Verifies that the Monomial.Minus function panics when the monomial used to call it + is well-defined but the second input is not an expression at all (in this case, it's a string). +*/ +func TestMonomial_Minus4(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 3.14, + VariableFactors: []symbolic.Variable{v1}, + Exponents: []int{1}, + } + + s2 := "x" + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("expected Minus to panic; received nil") + } + + // Collect error and compare it to expectation + rAse := r.(error) + expectedError := smErrors.UnsupportedInputError{ + Input: s2, + FunctionName: "Monomial.Minus", + } + + if !strings.Contains(rAse.Error(), expectedError.Error()) { + t.Errorf( + "expected error message to contain %v; received %v", + expectedError, + rAse.Error(), + ) + } + }() + _ = m1.Minus(s2) +} + /* TestMonomial_Multiply1 Description: @@ -480,6 +626,95 @@ func TestMonomial_Multiply3(t *testing.T) { m1.Multiply("x") } +/* +TestMonomial_Multiply4 +Description: + + Verifies that the Monomial.Multiply function returns a valid monomial + when the monomial used to call it is well-defined and the input is a float64. +*/ +func TestMonomial_Multiply4(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 3.14, + VariableFactors: []symbolic.Variable{v1}, + Exponents: []int{2}, + } + f1 := 3.14 + + // Compute Product + product := m1.Multiply(f1) + + // Verify that the product is a monomial + prodAsM, tf := product.(symbolic.Monomial) + if !tf { + t.Errorf( + "expected product to be a K; received %T", + product, + ) + } + + if prodAsM.Coefficient != 3.14*3.14 { + t.Errorf( + "expected product coefficient to be %v; received %v", + 3.14*3.14, + prodAsM.Coefficient, + ) + } + +} + +/* +TestMonomial_Multiply5 +Description: + + Verifies that the Monomial.Multiply function returns a valid polynomial + when the monomial used to call it is well-defined and the input is a polynomial. + The number of monomials in the product should match the number in the input polynomial. +*/ +func TestMonomial_Multiply5(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}, + } + m2 := symbolic.Monomial{ + Coefficient: 3.14, + VariableFactors: []symbolic.Variable{v1}, + Exponents: []int{1}, + } + p1 := symbolic.Polynomial{ + Monomials: []symbolic.Monomial{ + m2, + symbolic.K(3.14).ToMonomial(), + }, + } + + // Compute Product + product := m1.Multiply(p1) + + // Verify that the product is a polynomial + prodAsP, tf := product.(symbolic.Polynomial) + if !tf { + t.Errorf( + "expected product to be a polynomial; received %T", + product, + ) + } + + // Verify that the polynomial has 2 monomials + if len(prodAsP.Monomials) != 2 { + t.Errorf( + "expected product to have 2 monomials; received %v", + len(prodAsP.Monomials), + ) + } +} + /* TestMonomial_Transpose1 Description: @@ -965,6 +1200,80 @@ func TestMonomial_LinearCoeff3(t *testing.T) { } } +/* +TestMonomial_LinearCoeff4 +Description: + + Verifies that the Monomial.LinearCoeff function panics when + the monomial is not well-defined. +*/ +func TestMonomial_LinearCoeff4(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + v2 := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 3.14, + VariableFactors: []symbolic.Variable{v1, v2}, + Exponents: []int{1}, + } + + // Test + defer func() { + if r := recover(); r == nil { + t.Errorf( + "expected LinearCoeff to panic; received nil", + ) + } + }() + + m1.LinearCoeff() +} + +/* +TestMonomial_LinearCoeff5 +Description: + + Verifies that the Monomial.LinearCoeff function panics when + there are multiple input variables slices to it. +*/ +func TestMonomial_LinearCoeff5(t *testing.T) { + // Constants + N := 5 + vv1 := symbolic.NewVariableVector(N) + vv2 := symbolic.NewVariableVector(N) + m1 := symbolic.Monomial{ + Coefficient: 3.14, + VariableFactors: []symbolic.Variable{vv1[0]}, + Exponents: []int{1}, + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected LinearCoeff to panic; received nil", + ) + } + + // Check that error is as expected: + rAsE := r.(error) + if !strings.Contains( + rAsE.Error(), + "Expected 0 or 1 input.", + ) { + t.Errorf( + "expected error message to contain %v; received %v", + "Expected 0 or 1 input.", + rAsE.Error(), + ) + } + }() + + m1.LinearCoeff(vv1, vv2) + +} + /* TestMonomial_IsConstant1 Description: @@ -1151,8 +1460,8 @@ func TestMonomial_DerivativeWrt2(t *testing.T) { // Compute DerivativeWrt derivative := m1.DerivativeWrt(v1) - // Verify that the derivative is a monomial - derivativeAsM, tf := derivative.(symbolic.Monomial) + // Verify that the derivative is a constant (K) + derivativeAsK, tf := derivative.(symbolic.K) if !tf { t.Errorf( "expected derivative to be a monomial; received %T", @@ -1161,10 +1470,10 @@ func TestMonomial_DerivativeWrt2(t *testing.T) { } // Verify that the derivative is a constant - if derivativeAsM.Coefficient != 3.14 { + if float64(derivativeAsK) != 3.14 { t.Errorf( "expected derivative to be a constant; received %v", - derivativeAsM.Coefficient, + derivativeAsK, ) } } @@ -1199,6 +1508,102 @@ func TestMonomial_DerivativeWrt3(t *testing.T) { } } +/* +TestMonomial_DerivativeWrt4 +Description: + + Verifies that the Monomial.DerivativeWrt function + returns a monomial with the correct coefficient when the variable we are differentiating + with respect to appears in the original monomial and has degree >= 2. +*/ +func TestMonomial_DerivativeWrt4(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 3.14, + VariableFactors: []symbolic.Variable{v1}, + Exponents: []int{2}, + } + + // Compute DerivativeWrt + derivative := m1.DerivativeWrt(v1) + + // Verify that the derivative is a monomial + derivativeAsM, tf := derivative.(symbolic.Monomial) + if !tf { + t.Errorf( + "expected derivative to be a monomial; received %T", + derivative, + ) + } + + // Verify that the derivative is a constant + if derivativeAsM.Coefficient != 3.14*2 { + t.Errorf( + "expected derivative coefficient to be %v; received %v", + 3.14*2, + derivativeAsM.Coefficient, + ) + } +} + +/* +TestMonomial_DerivativeWrt5 +Description: + + Verifies that the Monomial.DerivativeWrt function creates a proper ouput monomial + that contains one less variable in VariableFactors and one less exponent in Exponents + when we are differentiative with respect to a variable that appears with exponent 1 + in the monomial. +*/ +func TestMonomial_DerivativeWrt5(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}, + } + + // Compute DerivativeWrt + derivative := m1.DerivativeWrt(v1) + + // Verify that the derivative is a monomial + derivativeAsM, tf := derivative.(symbolic.Monomial) + if !tf { + t.Errorf( + "expected derivative to be a monomial; received %T", + derivative, + ) + } + + // Verify that the derivative is a constant + if derivativeAsM.Coefficient != 3.14*1 { + t.Errorf( + "expected derivative coefficient to be %v; received %v", + 3.14*1, + derivativeAsM.Coefficient, + ) + } + + // Verify that the derivative has one less variable + if len(derivativeAsM.VariableFactors) != 1 { + t.Errorf( + "expected derivative to have 1 variable; received %v", + len(derivativeAsM.VariableFactors), + ) + } + + // Verify that the derivative has one less exponent + if len(derivativeAsM.Exponents) != 1 { + t.Errorf( + "expected derivative to have 1 exponent; received %v", + len(derivativeAsM.Exponents), + ) + } +} + /* TestMonomial_String1 Description: @@ -1261,5 +1666,5 @@ func TestMonomial_String2(t *testing.T) { } }() - m1.String() + _ = m1.String() } diff --git a/testing/symbolic/monomial_vector_test.go b/testing/symbolic/monomial_vector_test.go index 0309772..c08e475 100644 --- a/testing/symbolic/monomial_vector_test.go +++ b/testing/symbolic/monomial_vector_test.go @@ -5,6 +5,8 @@ import ( getKVector "github.com/MatProGo-dev/SymbolicMath.go/get/KVector" "github.com/MatProGo-dev/SymbolicMath.go/smErrors" "github.com/MatProGo-dev/SymbolicMath.go/symbolic" + "gonum.org/v1/gonum/mat" + "math" "strings" "testing" ) @@ -852,6 +854,225 @@ func TestMonomialVector_Plus12(t *testing.T) { } } +/* +TestMonomialVector_Minus1 +Description: + + Verifies that the Minus() method throws a panic when an improperly + initialized vector of monomials is given. +*/ +func TestMonomialVector_Minus1(t *testing.T) { + // Constants + mv := symbolic.MonomialVector{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected mv.Minus(3.14) to panic; received %v", + mv.Minus(3.14), + ) + } + }() + + mv.Minus(3.14) +} + +/* +TestMonomialVector_Minus2 +Description: + + Verifies that the Minus() method throws a panic when a well-formed + vector of monomials is subtracted from an improperly initialized expression + (in this case a monomial matrix). +*/ +func TestMonomialVector_Minus2(t *testing.T) { + // Constants + mv := symbolic.MonomialVector{ + symbolic.NewVariable().ToMonomial(), + symbolic.NewVariable().ToMonomial(), + } + pm := symbolic.MonomialMatrix{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected mv.Minus(pm) to panic; received %v", + mv.Minus(pm), + ) + } + }() + + mv.Minus(pm) +} + +/* +TestMonomialVector_Minus3 +Description: + + Verifies that the Minus() method throws a panic when a well-formed + vector of monomials is subtracted from a well formed vector expression + OF THE WRONG DIMENSIONs. +*/ +func TestMonomialVector_Minus3(t *testing.T) { + // Constants + mv := symbolic.MonomialVector{ + symbolic.NewVariable().ToMonomial(), + symbolic.NewVariable().ToMonomial(), + } + pm := symbolic.PolynomialMatrix{ + { + symbolic.NewVariable().ToPolynomial(), + symbolic.NewVariable().ToPolynomial(), + symbolic.NewVariable().ToPolynomial(), + }, + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected mv.Minus(pm) to panic; received %v", + mv.Minus(pm), + ) + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf( + "Expected mv.Minus(pm) to panic with an error; received %v", + r, + ) + } + + if !strings.Contains( + rAsE.Error(), + smErrors.DimensionError{ + Operation: "Minus", + Arg1: mv, + Arg2: pm, + }.Error(), + ) { + t.Errorf( + "Expected mv.Minus(pm) to panic with an error containing \"dimensions\"; received %v", + rAsE.Error(), + ) + } + }() + + mv.Minus(pm) +} + +/* +TestMonomialVector_Minus4 +Description: + + Verifies that the Minus() method returns the correct value when a + well-formed vector of monomials is subtracted from a float64. + The result should be a polynomial vector where each polynomial contains + two monomials, one that is a constant and one that is a variable. +*/ +func TestMonomialVector_Minus4(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + mv := symbolic.MonomialVector{v1.ToMonomial(), v1.ToMonomial()} + f2 := 3.14 + + // Test + difference := mv.Minus(f2) + + // Verify that the difference is a polynomial vector + if _, tf := difference.(symbolic.PolynomialVector); !tf { + t.Errorf( + "expected difference to be a PolynomialVector; received %T", + difference, + ) + } + + // Verify that each polynomial contains two monomials + for _, polynomial := range difference.(symbolic.PolynomialVector) { + if len(polynomial.Monomials) != 2 { + t.Errorf( + "expected len(polynomial.Monomials) to be 2; received %v", + len(polynomial.Monomials), + ) + } + + for _, monomial := range polynomial.Monomials { + if (!monomial.IsConstant()) && (!monomial.IsVariable(v1)) { + t.Errorf("expected monomial to be a variable or a constant; received %v", monomial) + } + } + + } +} + +/* +TestMonomialVector_Minus5 +Description: + + Verifies that the Minus() method returns the correct value when a + well-formed vector of monomials is subtracted from a *mat.VecDense object + of appropriate dimension. +*/ +func TestMonomialVector_Minus5(t *testing.T) { + // Setup + v1 := symbolic.NewVariable() + v2 := symbolic.NewVariable() + mv := symbolic.MonomialVector{v1.ToMonomial(), v2.ToMonomial()} + + // Create a *mat.VecDense object + vec := mat.NewVecDense(2, []float64{1, 2}) + + // Test + difference := mv.Minus(vec) + + // Verify that the difference is a polynomial vector + if _, tf := difference.(symbolic.PolynomialVector); !tf { + t.Errorf( + "expected difference to be a PolynomialVector; received %T", + difference, + ) + } + + // Verify that each polynomial contains two monomials + for ii, polynomial := range difference.(symbolic.PolynomialVector) { + if len(polynomial.Monomials) != 2 { + t.Errorf( + "expected len(polynomial.Monomials) to be 2; received %v", + len(polynomial.Monomials), + ) + } + + // Verify that each monomial is the correct value + for _, monomial := range polynomial.Monomials { + if monomial.IsConstant() { + switch ii { + case 0: + if monomial.Coefficient != -1.0 { + t.Errorf( + "expected monomial.Coefficient to be -1.0; received %v", + monomial.Coefficient, + ) + } + case 1: + if monomial.Coefficient != -2.0 { + t.Errorf( + "expected monomial.Coefficient to be -2.0; received %v", + monomial.Coefficient, + ) + } + } + + } + } + } +} + /* TestMonomialVector_Multiply1 Description: @@ -1405,7 +1626,7 @@ func TestMonomialVector_Derivative3(t *testing.T) { derivative := mv.DerivativeWrt(v1) // Verify that the derivative is a K vector - if _, tf := derivative.(symbolic.MonomialVector); !tf { + if _, tf := derivative.(symbolic.KVector); !tf { t.Errorf( "expected derivative to be a MonomialVector; received %T", derivative, @@ -1414,20 +1635,12 @@ func TestMonomialVector_Derivative3(t *testing.T) { // Verify that each element of the derivative is just the coefficient // from the original monomial vector mv - for ii, monomial := range derivative.(symbolic.MonomialVector) { - // Check that the monomial is a constant - if !monomial.IsConstant() { - t.Errorf( - "expected monomial to be a constant; received %v", - monomial, - ) - } - - if monomial.Coefficient != mv[ii].Coefficient { + for ii, d_ii := range derivative.(symbolic.KVector) { + if float64(d_ii) != mv[ii].Coefficient { t.Errorf( "expected constant to be %v; received %v", mv[ii].Coefficient, - monomial.Coefficient, + float64(d_ii), ) } } @@ -1462,7 +1675,7 @@ func TestMonomialVector_Derivative4(t *testing.T) { derivative := mv.DerivativeWrt(v1) // Verify that the derivative is a K vector - d_v1, tf := derivative.(symbolic.MonomialVector) + d_v1, tf := derivative.(symbolic.KVector) if !tf { t.Errorf( "expected derivative to be a MonomialVector; received %T", @@ -1471,33 +1684,161 @@ func TestMonomialVector_Derivative4(t *testing.T) { } // Verify that the first element of the derivative is a constant and nonzero - if !d_v1[0].IsConstant() { + if float64(d_v1[0]) != 3.14 { t.Errorf( - "expected derivative[0] to be a constant; received %v", + "expected derivative[0].Coefficient to be 3.14; received %v", d_v1[0], ) } - if d_v1[0].Coefficient != 3.14 { + // Verify that the second element of the derivative is a constant and zero + if float64(d_v1[1]) != 0 { t.Errorf( - "expected derivative[0].Coefficient to be 3.14; received %v", - d_v1[0].Coefficient, + "expected derivative[1].Coefficient to be 0; received %v", + d_v1[1], ) } - // Verify that the second element of the derivative is a constant and zero - if !d_v1[1].IsConstant() { +} + +/* +TestMonomialVector_Degree1 +Description: + + Verifies that the Degree() method panics when this is called with a MonomialVector that is not well-defined. +*/ +func TestMonomialVector_Degree1(t *testing.T) { + // Constants + mv := symbolic.MonomialVector{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected mv.Degree() to panic; received %v", + mv.Degree(), + ) + } + + err := r.(error) + expectedError := mv.Check() + if err.Error() != expectedError.Error() { + t.Errorf( + "Expected error to be %v; received %v", + expectedError, + err, + ) + } + + }() + + mv.Degree() + t.Errorf("Test should panic before this is reached!") +} + +/* +TestMonomialVector_Degree2 +Description: + + Verifies that the Degree() method returns the correct value when called with a well-defined MonomialVector. + This should be the maximum of all of the monomials inside the vector. +*/ +func TestMonomialVector_Degree2(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + v2 := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 3.14, + VariableFactors: []symbolic.Variable{v1}, + Exponents: []int{2}, + } + m2 := symbolic.Monomial{ + Coefficient: 3.14, + VariableFactors: []symbolic.Variable{v2}, + Exponents: []int{1}, + } + mv := symbolic.MonomialVector{m1, m2} + + // Test + degree := mv.Degree() + + // Verify that the degree is correct + if degree != 2 { t.Errorf( - "expected derivative[1] to be a constant; received %v", - d_v1[1], + "expected degree to be 2; received %v", + degree, + ) + } +} + +/* +TestMonomialVector_Power1 +Description: + + Verifies that the Power() method panics when called with a vector that has Len() greater + than 1. +*/ +func TestMonomialVector_Power1(t *testing.T) { + // Constants + mv := symbolic.NewVariableVector(10).ToMonomialVector() + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected mv.Power(3) to panic; received %v", + mv.Power(3), + ) + } + }() + + mv.Power(3) + t.Errorf("Test should panic before this is reached!") +} + +/* +TestMonomialVector_Power2 +Description: + + Verifies that the Power() method returns the correct value when called with a vector + of Len() == 1. The result should be a monomial vector where each monomial is raised to + the power of the input integer. +*/ +func TestMonomialVector_Power2(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 3.14, + VariableFactors: []symbolic.Variable{v1}, + Exponents: []int{1}, + } + mv := symbolic.MonomialVector{m1} + + // Test + power := mv.Power(3) + + // Verify that the power is a monomial vector + if _, tf := power.(symbolic.Monomial); !tf { + t.Errorf( + "expected power to be a MonomialVector; received %T", + power, ) } - if d_v1[1].Coefficient != 0 { + // Verify that each monomial is raised to the power of 3 + if power.(symbolic.Monomial).Exponents[0] != 3 { t.Errorf( - "expected derivative[1].Coefficient to be 0; received %v", - d_v1[1].Coefficient, + "expected monomial.Exponents[0] to be 3; received %v", + power.(symbolic.Monomial).Exponents[0], ) } + if power.(symbolic.Monomial).Coefficient != math.Pow(3.14, 3) { + t.Errorf( + "expected monomial.Coefficient to be 3.14^3; received %v", + power.(symbolic.Monomial).Coefficient, + ) + } } diff --git a/testing/symbolic/polynomial_test.go b/testing/symbolic/polynomial_test.go index 35868bd..b87b9a0 100644 --- a/testing/symbolic/polynomial_test.go +++ b/testing/symbolic/polynomial_test.go @@ -11,6 +11,7 @@ import ( 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" ) @@ -1455,6 +1456,155 @@ func TestPolynomial_Multiply6(t *testing.T) { } } +/* +TestPolynomial_Multiply7 +Description: + + Verifies that the Polynomial.Multiply properly panics when the input polynomial is not well-defined. +*/ +func TestPolynomial_Multiply7(t *testing.T) { + // Setup + p1 := symbolic.Polynomial{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected Multiply to panic when called with an invalid polynomial; received nil", + ) + } + + rAsE := r.(error) + if rAsE.Error() != p1.Check().Error() { + t.Errorf( + "expected Multiply to panic with error %v; received %v", + p1.Check(), + rAsE, + ) + } + }() + + // Call the Multiply method + p1.Multiply(p1) +} + +/* +TestPolynomial_Multiply8 +Description: + + Verifies that the Polynomial.Multiply method returns the correct output + when called with a well-defined polynomial and a well-defined constant matrix. + The resulting polynomial should be a polynomial matrix. +*/ +func TestPolynomial_Multiply8(t *testing.T) { + // Setup + p1 := symbolic.NewVariable().ToPolynomial() + km1 := getKMatrix.From([][]float64{ + {3.14, 2.71}, + {1.0, 0.0}, + }) + + // Test + prod := p1.Multiply(km1) + + // Verify that the product is a polynomial matrix + prodAsPM, tf := prod.(symbolic.PolynomialMatrix) + if !tf { + t.Errorf( + "expected %v * %v to return a polynomial matrix; received %T", + p1, + km1, + prod, + ) + } + + // Verify that the coefficients of the product are correct + for ii, pRow := range prodAsPM { + for jj, p := range pRow { + if len(p.Monomials) != 1 { + t.Errorf( + "expected %v * %v to have 1 monomial; received %v", + p1, + km1, + len(p.Monomials), + ) + } + + if prodAsPM[ii][jj].Monomials[0].Coefficient != float64(km1.At(ii, jj).(symbolic.K)) { + t.Errorf( + "expected %v * %v to have coefficient %v; received %v", + p1, + km1, + km1.At(ii, jj), + prodAsPM[ii][jj].Monomials[0].Coefficient, + ) + } + + } + } +} + +/* +TestPolynomial_Transpose1 +Description: + + Verifies that the output of the transpose of a polynomial + is the same as the original polynomial when the polynomial + is well-defined. +*/ +func TestPolynomial_Transpose1(t *testing.T) { + // Setup + p1 := symbolic.NewVariable().ToPolynomial() + + // Test + pT := p1.Transpose() + + // Verify that the transpose is the same as the original + if !reflect.DeepEqual(p1, pT) { + t.Errorf( + "expected %v^T to be %v; received %v", + p1, + p1, + pT, + ) + + } +} + +/* +TestPolynomial_Transpose2 +Description: + + Verifies that the Transpose() method properly panics when the polynomial is not well-defined. +*/ +func TestPolynomial_Transpose2(t *testing.T) { + // Setup + p1 := symbolic.Polynomial{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected Transpose to panic when called with an invalid polynomial; received nil", + ) + } + + rAsE := r.(error) + if rAsE.Error() != p1.Check().Error() { + t.Errorf( + "expected Transpose to panic with error %v; received %v", + p1.Check(), + rAsE, + ) + } + }() + + // Call the Transpose method + p1.Transpose() +} + /* TestPolynomial_LessEq1 Description: @@ -1666,6 +1816,198 @@ func TestPolynomial_Constant1(t *testing.T) { } } +/* +TestPolynomial_Constant2 +Description: + + Verifies that the Polynomial.Constant method panics when called on a polynomial + that is not well-defined. +*/ +func TestPolynomial_Constant2(t *testing.T) { + // Constants + p1 := symbolic.Polynomial{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected Constant to panic when called with an invalid polynomial; received nil", + ) + } + }() + + // Call the Constant method + p1.Constant() +} + +/* +TestPolynomial_Simplify1 +Description: + + Verifies that the Polynomial.Simplify method panics when called with a polynomial + that is not well-defined. +*/ +func TestPolynomial_Simplify1(t *testing.T) { + // Constants + p1 := symbolic.Polynomial{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected Simplify to panic when called with an invalid polynomial; received nil", + ) + } + }() + + // Call the Simplify method + p1.Simplify() +} + +/* +TestPolynomial_Simplify2 +Description: + + Verifies that the Polynomial.Simplify method returns the same polynomial, + when the polynomial contains one monomial with a coefficient of 0. +*/ +func TestPolynomial_Simplify2(t *testing.T) { + // Constants + p1 := symbolic.Polynomial{ + Monomials: []symbolic.Monomial{ + symbolic.Monomial{Coefficient: 0, VariableFactors: []symbolic.Variable{}, Exponents: []int{}}, + }, + } + + // Test + simp := p1.Simplify() + if !reflect.DeepEqual(p1, simp) { + t.Errorf( + "expected %v to simplify to %v; received %v", + p1, + p1, + simp, + ) + } +} + +/* +TestPolynomial_DerivativeWrt1 +Description: + + Verifies that the Polynomial.DerivativeWrt method panics when called with a polynomial + that is not well-defined. +*/ +func TestPolynomial_DerivativeWrt1(t *testing.T) { + // Constants + p1 := symbolic.Polynomial{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected DerivativeWrt to panic when called with an invalid polynomial; received nil", + ) + } + }() + + // Call the DerivativeWrt method + p1.DerivativeWrt(symbolic.NewVariable()) +} + +/* +TestPolynomial_DerivativeWrt2 +Description: + + Verifies that the Polynomial.DerivativeWrt method panics when the polynomial + is well-defined, but the variable wrt is not well-defined. +*/ +func TestPolynomial_DerivativeWrt2(t *testing.T) { + // Constants + p1 := symbolic.NewVariable().ToPolynomial() + v1 := symbolic.Variable{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected DerivativeWrt to panic when called with an invalid variable; received nil", + ) + } + + rAsE := r.(error) + if rAsE.Error() != v1.Check().Error() { + + } + }() + + // Call the DerivativeWrt method + p1.DerivativeWrt(v1) +} + +/* +TestPolynomial_DerivativeWrt3 +Description: + + Verifies that the Polynomial.DerivativeWrt method returns the correct output + of 0 when the well-defined polynomial contains variables and the wrt variable + is not in the polynomial. +*/ +func TestPolynomial_DerivativeWrt3(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + v2 := symbolic.NewVariable() + + p1 := symbolic.Polynomial{ + Monomials: []symbolic.Monomial{ + symbolic.Monomial{Coefficient: 1, VariableFactors: []symbolic.Variable{v1}, Exponents: []int{1}}, + symbolic.Monomial{Coefficient: 2, VariableFactors: []symbolic.Variable{v2}, Exponents: []int{2}}, + }, + } + + // Test + derivative := p1.DerivativeWrt(symbolic.NewVariable()) + expected := symbolic.K(0.0) + if !reflect.DeepEqual(expected, derivative) { + t.Errorf( + "expected %v.derivative(%v) to be %v; received %v", + p1, + symbolic.NewVariable(), + expected, + derivative, + ) + } +} + +/* +TestPolynomial_Degree1 +Description: + + Verifies that the Polynomial.Degree method panics when called with a polynomial + that is not well-defined. +*/ +func TestPolynomial_Degree1(t *testing.T) { + // Constants + p1 := symbolic.Polynomial{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected Degree to panic when called with an invalid polynomial; received nil", + ) + } + }() + + // Call the Degree method + p1.Degree() +} + /* TestPolynomial_IsLinear1 Description: @@ -1696,3 +2038,168 @@ func TestPolynomial_IsLinear1(t *testing.T) { ) } } + +/* +TestPolynomial_IsConstant1 +Description: + + Verifies that the Polynomial.IsConstant method panics when called with a polynomial + that is not well-defined. +*/ +func TestPolynomial_IsConstant1(t *testing.T) { + // Constants + p1 := symbolic.Polynomial{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected IsConstant to panic when called with an invalid polynomial; received nil", + ) + } + }() + + // Call the IsConstant method + p1.IsConstant() +} + +/* +TestPolynomial_IsConstant2 +Description: + + Verifies that the Polynomial.IsConstant method returns true + when called with a polynomial that is a constant (i.e., only contains one monomial that has just a coefficient). +*/ +func TestPolynomial_IsConstant2(t *testing.T) { + // Constants + p1 := symbolic.Polynomial{ + Monomials: []symbolic.Monomial{ + symbolic.Monomial{Coefficient: 3.14, VariableFactors: []symbolic.Variable{}, Exponents: []int{}}, + }, + } + + // Test + if !p1.IsConstant() { + t.Errorf( + "expected %v to be constant; received %v", + p1, + p1.IsConstant(), + ) + } +} + +/* +TestPolynomial_String1 +Description: + + Verifies that the Polynomial.String method panics when called with a polynomial + that is not well-defined. +*/ +func TestPolynomial_String1(t *testing.T) { + // Constants + p1 := symbolic.Polynomial{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected String to panic when called with an invalid polynomial; received nil", + ) + } + }() + + // Call the String method + p1.String() +} + +/* +TestPolynomial_Substitute1 +Description: + + Verifies that the Polynomial.Substitute method panics when called with a polynomial + that is not well-defined. +*/ +func TestPolynomial_Substitute1(t *testing.T) { + // Constants + p1 := symbolic.Polynomial{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected Substitute to panic when called with an invalid polynomial; received nil", + ) + } + }() + + // Call the Substitute method + p1.Substitute(symbolic.NewVariable(), symbolic.NewVariable()) +} + +/* +TestPolynomial_Substitute2 +Description: + + Verifies that the Polynomial.Substitute method returns the correct output + when called with a well-defined polynomial, a well-defined variable and a well-defined expression to use for substitution. +*/ +func TestPolynomial_Substitute2(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + v2 := symbolic.NewVariable() + p1 := symbolic.Polynomial{ + Monomials: []symbolic.Monomial{ + symbolic.Monomial{Coefficient: 1, VariableFactors: []symbolic.Variable{v1}, Exponents: []int{1}}, + }, + } + + // Test + sub := p1.Substitute(v1, v2.Multiply(3.0).(symbolic.ScalarExpression)) + if sub.(symbolic.Polynomial).Monomials[0].Coefficient != 3.0 { + t.Errorf( + "expected %v.substitute(%v, %v) to have coefficient 3.0; received %v", + p1, + v1, + v2.Multiply(3.0), + sub.(symbolic.Polynomial).Monomials[0].Coefficient, + ) + } +} + +/* +TestPolynomial_Substitute3 +Description: + + Verifies that the Polynomial.Substitute method panics when the variable used for substitution + is not well-defiend, but the polynomial is well-defined. +*/ +func TestPolynomial_Substitute3(t *testing.T) { + // Constants + v1 := symbolic.Variable{} + p1 := symbolic.NewVariable().ToPolynomial() + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "expected Substitute to panic when called with an invalid variable; received nil", + ) + } + + rAsE := r.(error) + if rAsE.Error() != v1.Check().Error() { + t.Errorf( + "expected Substitute to panic with error %v; received %v", + v1.Check(), + rAsE, + ) + } + }() + + // Call the Substitute method + p1.Substitute(v1, symbolic.NewVariable()) +} diff --git a/testing/symbolic/scalar_expression_test.go b/testing/symbolic/scalar_expression_test.go index 6846288..b6fafa1 100644 --- a/testing/symbolic/scalar_expression_test.go +++ b/testing/symbolic/scalar_expression_test.go @@ -1,6 +1,7 @@ package symbolic_test import ( + "github.com/MatProGo-dev/SymbolicMath.go/smErrors" "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "strings" "testing" @@ -82,25 +83,6 @@ func TestScalarExpression_ToScalarExpression2(t *testing.T) { } } -/* -TestScalarExpression_IsLinear1 -Description: - - Tests that a float64 is identified as a linear expression. -*/ -func TestScalarExpression_IsLinear1(t *testing.T) { - // Constants - x := symbolic.K(3.0) - - // Test - if !symbolic.IsLinear(x) { - t.Errorf( - "Expected IsLinear(%T) to be true; received false", - x, - ) - } -} - /* TestScalarExpression_ToScalarExpression1 Description: @@ -130,6 +112,25 @@ func TestScalarExpression_ToScalarExpression3(t *testing.T) { } } +/* +TestScalarExpression_IsLinear1 +Description: + + Tests that a float64 is identified as a linear expression. +*/ +func TestScalarExpression_IsLinear1(t *testing.T) { + // Constants + x := symbolic.K(3.0) + + // Test + if !symbolic.IsLinear(x) { + t.Errorf( + "Expected IsLinear(%T) to be true; received false", + x, + ) + } +} + /* TestScalarExpression_IsLinear2 Description: @@ -152,3 +153,76 @@ func TestScalarExpression_IsLinear2(t *testing.T) { ) } } + +/* +TestScalarExpression_ScalarPowerTemplate1 +Description: + + Tests that the ScalarPowerTemplate() function panics if the input scalar expression + is not well-defined. +*/ +func TestScalarExpression_ScalarPowerTemplate1(t *testing.T) { + // Setup + m1 := symbolic.Monomial{ + Coefficient: 2.0, + VariableFactors: []symbolic.Variable{symbolic.NewVariable()}, + Exponents: []int{1, 2}, + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("Expected panic; received nil") + } + + rAsE := r.(error) + if !strings.Contains( + rAsE.Error(), + m1.Check().Error(), + ) { + t.Errorf( + "Expected panic message to contain 'not recognized as a ScalarExpression'; received %v", + rAsE.Error(), + ) + } + }() + + // Call Function + symbolic.ScalarPowerTemplate(m1, 2) +} + +/* +TestScalarExpression_ScalarPowerTemplate2 +Description: + + Tests that the ScalarPowerTemplate() function properly panics when a well-defined scalar + expression is provided, but the power is negative. +*/ +func TestScalarExpression_ScalarPowerTemplate2(t *testing.T) { + // Setup + x := symbolic.K(2.0) + testExponent := -2 + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("Expected panic; received nil") + } + + rAsE := r.(error) + if !strings.Contains( + rAsE.Error(), + smErrors.NegativeExponentError{Exponent: testExponent}.Error(), + ) { + t.Errorf( + "Expected panic message to contain 'power must be a non-negative integer'; received %v", + rAsE.Error(), + ) + } + }() + + // Call Function + symbolic.ScalarPowerTemplate(x, testExponent) +} diff --git a/testing/symbolic/utils_test.go b/testing/symbolic/utils_test.go index 0484b25..572a269 100644 --- a/testing/symbolic/utils_test.go +++ b/testing/symbolic/utils_test.go @@ -7,6 +7,7 @@ Description: */ import ( + "fmt" "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "testing" ) @@ -208,6 +209,111 @@ func TestUtils_FindInSlice9(t *testing.T) { } } +/* +TestUtils_FindInSlice10 +Description: + + This test verifies that the FindInSlice function returns an error when the + first input is a string and the second input is a slice of something else (in this case a slice of strings). +*/ +func TestUtils_FindInSlice10(t *testing.T) { + // Constants + x := "x" + slice := []int{2, 3, 4, 1} + + // Test + _, err := symbolic.FindInSlice(x, slice) + if err == nil { + t.Errorf( + "Expected FindInSlice to return an error; received nil", + ) + } else { + expectedError := fmt.Errorf( + "the input slice is of type %T, but the element we're searching for is of type %T", + slice, + x, + ) + if err.Error() != expectedError.Error() { + t.Errorf( + "Expected error message to be '%v'; received %v", + expectedError, + err.Error(), + ) + } + } +} + +/* +TestUtils_FindInSlice11 +Description: + + This test verifies that the FindInSlice function returns an error when the + the first input is a Variable and the second input is a slice of something else (in this case a slice of strings). +*/ +func TestUtils_FindInSlice11(t *testing.T) { + // Constants + x := symbolic.NewVariable() + slice := []int{2, 3, 4, 1} + + // Test + _, err := symbolic.FindInSlice(x, slice) + if err == nil { + t.Errorf( + "Expected FindInSlice to return an error; received nil", + ) + } else { + expectedError := fmt.Errorf( + "the input slice is of type %T, but the element we're searching for is of type %T", + slice, + x, + ) + if err.Error() != expectedError.Error() { + t.Errorf( + "Expected error message to be '%v'; received %v", + expectedError, + err.Error(), + ) + } + } +} + +/* +TestUtils_FindInSlice12 +Description: + + This test verifies that the FindInSlice function returns an error when the + the first input is an error (an unsupported type). The second input can be anything else. +*/ +func TestUtils_FindInSlice12(t *testing.T) { + // Constants + x := fmt.Errorf("error") + slice := []int{2, 3, 4, 1} + + // Test + _, err := symbolic.FindInSlice(x, slice) + if err == nil { + t.Errorf( + "Expected FindInSlice to return an error; received nil", + ) + } else { + allowedTypes := []string{ + "string", "int", "uint64", "Variable", + } + expectedError := fmt.Errorf( + "the FindInSlice() function was only defined for types %v, not type %T:", + allowedTypes, + x, + ) + if err.Error() != expectedError.Error() { + t.Errorf( + "Expected error message to be '%v'; received %v", + expectedError, + err.Error(), + ) + } + } +} + /* TestUtils_Unique1 Description: @@ -267,3 +373,161 @@ func TestUtils_Unique3(t *testing.T) { ) } } + +/* +TestUtils_Unique4 +Description: + + This test verifies that the unique function returns a slice of length 3 + when the input slice has length 3 and all elements are unique. +*/ +func TestUtils_Unique4(t *testing.T) { + // Constants + slice := []uint64{13, 14, 15} + + // Test + if len(symbolic.Unique(slice)) != 3 { + t.Errorf( + "Expected Unique to return a slice of length 3; received %v", + symbolic.Unique(slice), + ) + } +} + +/* +TestUtils_CheckSubstitutionMap1 +Description: + + This test verifies that the CheckSubstitutionMap function returns an error + when the input map contains a variable that is not well-defined. +*/ +func TestUtils_CheckSubstitutionMap1(t *testing.T) { + // Constants + badVar := symbolic.Variable{2, -1, -2, symbolic.Binary, "Russ"} + varMap := map[symbolic.Variable]symbolic.Expression{ + symbolic.NewVariable(): symbolic.K(3), + badVar: symbolic.K(4), + } + + // Test + err := symbolic.CheckSubstitutionMap(varMap) + if err == nil { + t.Errorf( + "Expected CheckSubstitutionMap to return an error; received nil", + ) + } else { + expectedError := fmt.Errorf( + "key %v in the substitution map is not a valid variable: %v", + badVar, + badVar.Check(), + ) + if err.Error() != expectedError.Error() { + t.Errorf( + "Expected error message to be '%v'; received %v", + expectedError, + err, + ) + } + } +} + +/* +TestUtils_CheckSubstitutionMap2 +Description: + + This test verifies that the CheckSubstitutionMap function returns an error + when the input map contains a value/mapped item that is not well-defined. +*/ +func TestUtils_CheckSubstitutionMap2(t *testing.T) { + // Constants + goodVar := symbolic.NewVariable() + badVar := symbolic.Variable{2, -1, -2, symbolic.Binary, "Russ"} + varMap := map[symbolic.Variable]symbolic.Expression{ + symbolic.NewVariable(): symbolic.K(3), + goodVar: badVar, + } + + // Test + err := symbolic.CheckSubstitutionMap(varMap) + if err == nil { + t.Errorf( + "Expected CheckSubstitutionMap to return an error; received nil", + ) + } else { + expectedError := fmt.Errorf( + "value %v in the substitution map[%v] is not a valid expression: %v", + badVar, + goodVar, + badVar.Check(), + ) + if err.Error() != expectedError.Error() { + t.Errorf( + "Expected error message to be '%v'; received %v", + expectedError, + err, + ) + } + } +} + +/* +TestUtils_CheckSubstitutionMap3 +Description: + + This test verifies that the CheckSubstitutionMap function returns an error + when the input map contains a value/mapped item that is not a scalar expression. +*/ +func TestUtils_CheckSubstitutionMap3(t *testing.T) { + // Constants + goodVar := symbolic.NewVariable() + badVar := symbolic.NewVariableMatrix(2, 2) + varMap := map[symbolic.Variable]symbolic.Expression{ + symbolic.NewVariable(): symbolic.K(3), + goodVar: badVar, + } + + // Test + err := symbolic.CheckSubstitutionMap(varMap) + if err == nil { + t.Errorf( + "Expected CheckSubstitutionMap to return an error; received nil", + ) + } else { + expectedError := fmt.Errorf( + "value %v in the substitution map[%v] is not a scalar expression (received %T)", + badVar, + goodVar, + badVar, + ) + if err.Error() != expectedError.Error() { + t.Errorf( + "Expected error message to be '%v'; received %v", + expectedError, + err, + ) + } + } +} + +/* +TestUtils_CheckSubstitutionMap4 +Description: + + This test verifies that the CheckSubstitutionMap function returns no error + when the input map is well-defined (has valid variable keys and its values are all valid scalar expresssions). +*/ +func TestUtils_CheckSubstitutionMap4(t *testing.T) { + // Constants + varMap := map[symbolic.Variable]symbolic.Expression{ + symbolic.NewVariable(): symbolic.K(3), + symbolic.NewVariable(): symbolic.K(4), + } + + // Test + if err := symbolic.CheckSubstitutionMap(varMap); err != nil { + t.Errorf( + "Expected CheckSubstitutionMap to return nil; received %v", + err, + ) + } +} diff --git a/testing/symbolic/variable_test.go b/testing/symbolic/variable_test.go index 499b534..7d9aaa0 100644 --- a/testing/symbolic/variable_test.go +++ b/testing/symbolic/variable_test.go @@ -561,6 +561,161 @@ func TestVariable_Minus1(t *testing.T) { } } +/* +TestVariable_Minus2 +Description: + + Verifies that the Minus() method works properly when subtracting a float64 from a variable. +*/ +func TestVariable_Minus2(t *testing.T) { + // Constants + x := symbolic.NewVariable() + + // Test + diff := x.Minus(3.14) + if diff.(symbolic.ScalarExpression).Constant() != -3.14 { + t.Errorf( + "expected %v - 3.14 to have constant component -3.14; received %v", + x, + x.Minus(3.14), + ) + } + + // Test that diff is a polynomial with 2 terms + diffAsPoly, tf := diff.(symbolic.Polynomial) + if !tf { + t.Errorf( + "expected %v - 3.14 to be a polynomial; received %T", + x, + diff, + ) + } + + if len(diffAsPoly.Monomials) != 2 { + t.Errorf( + "expected %v - 3.14 to have 2 terms; received %v", + x, + len(diffAsPoly.Monomials), + ) + } +} + +/* +TestVariable_Minus3 +Description: + + Verifies that the Minus() method properly panics when called on a variable + that is not well-defined. +*/ +func TestVariable_Minus3(t *testing.T) { + // Constants + x := symbolic.NewVariable() + + // Test + defer func() { + if r := recover(); r == nil { + t.Errorf("The code did not panic") + } + }() + x.Minus("hello") +} + +/* +TestVariable_Minus4 +Description: + + This tests that the Minus() function properly panics when called on a variable + that is not well-defined. +*/ +func TestVariable_Minus4(t *testing.T) { + // Constants + var x symbolic.Variable + + // Test + defer func() { + if r := recover(); r == nil { + t.Errorf("The code did not panic") + } + }() + x.Minus(3.4) +} + +/* +TestVariable_Minus5 +Description: + + Verifies that the Minus() method properly panics when the + input to the Minus() method's input is not a well-expressed. +*/ +func TestVariable_Minus5(t *testing.T) { + // Constants + x := symbolic.NewVariable() + badVar := symbolic.Variable{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("The code did not panic") + } + + rAsErr := r.(error) + if !strings.Contains( + rAsErr.Error(), + badVar.Check().Error(), + ) { + t.Errorf("Expected error message to contain %v; received %v", badVar.Check().Error(), rAsErr.Error()) + } + + }() + x.Minus(badVar) +} + +/* +TestVariable_Minus6 +Description: + + Verifies that the Minus() method properly produces a polynomial + when you subtract a float64 from the target variable. +*/ +func TestVariable_Minus6(t *testing.T) { + // Constants + x := symbolic.NewVariable() + f1 := 3.14 + + // Test + diff := x.Minus(f1) + if diff.(symbolic.ScalarExpression).Constant() != -f1 { + t.Errorf( + "expected %v - %v to have constant component %v; received %v", + x, + f1, + -f1, + diff.(symbolic.ScalarExpression).Constant(), + ) + } + + // Test that diff is a polynomial with 2 terms + diffAsPoly, tf := diff.(symbolic.Polynomial) + if !tf { + t.Errorf( + "expected %v - %v to be a polynomial; received %T", + x, + f1, + diff, + ) + } + + if len(diffAsPoly.Monomials) != 2 { + t.Errorf( + "expected %v - %v to have 2 terms; received %v", + x, + f1, + len(diffAsPoly.Monomials), + ) + } +} + /* TestVariable_LessEq1 Description: @@ -1060,6 +1215,38 @@ func TestVariable_Multiply5(t *testing.T) { } } +/* +TestVariable_Multiply6 +Description: + + Tests that the Multiply() method properly panics when called with a + well-defined variable and an expression that is not well-defined + (in this case, a variable). +*/ +func TestVariable_Multiply6(t *testing.T) { + // Constants + x := symbolic.NewVariable() + uninit := symbolic.Variable{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("The code did not panic") + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf("The panic was not an error") + } + + if !strings.Contains(rAsE.Error(), uninit.Check().Error()) { + t.Errorf("The panic was not due to a bad Variable") + } + }() + x.Multiply(symbolic.Variable{}) +} + /* TestVariable_String1 Description: @@ -1079,3 +1266,583 @@ func TestVariable_String1(t *testing.T) { ) } } + +/* +TestVariable_Transpose1 +Description: + + Tests that the Transpose() method returns the same variable that + it was called on. +*/ +func TestVariable_Transpose1(t *testing.T) { + // Constants + x := symbolic.NewVariable() + + // Algorthm + xT := x.Transpose() + + xTAsV, tf := xT.(symbolic.Variable) + if !tf { + t.Errorf( + "expected %v.T to be a variable; received %T", + x, + xT, + ) + } + + if xTAsV.ID != x.ID { + t.Errorf( + "expected %v.T to be %v; received %v", + x, + x, + xT, + ) + } +} + +/* +TestVariable_NewBinaryVariable1 +Description: + + Tests that the NewBinaryVariable() method properly creates + a variable with binary type. +*/ +func TestVariable_NewBinaryVariable1(t *testing.T) { + // Constants + x := symbolic.NewBinaryVariable() + + // Test + if x.Type != symbolic.Binary { + t.Errorf( + "expected %v to be a binary variable; received %v", + x, + x.Type, + ) + } +} + +/* +TestVariable_NewBinaryVariable2 +Description: + + Tests that the NewBinaryVariable() method properly creates + a variable with a unique ID when an environment is provided. +*/ +func TestVariable_NewBinaryVariable2(t *testing.T) { + // Constants + env := symbolic.Environment{ + Name: "test-nbv2", + } + symbolic.NewVariable(&env) + x := symbolic.NewBinaryVariable(&env) + + // Test that the ID is unique + if x.Type != symbolic.Binary { + t.Errorf( + "expected %v to be a binary variable; received %v", + x, + x.Type, + ) + } + + // Test that the ID is greater than 0 + if x.ID <= 0 { + t.Errorf( + "expected %v to have a unique ID; received %v", + x, + x.ID, + ) + } +} + +/* +TestVariable_DerivativeWrt1 +Description: + + Tests that the DerivativeWrt() method properly panics when called + on a variable that is not well-defined. +*/ +func TestVariable_DerivativeWrt1(t *testing.T) { + // Constants + var x symbolic.Variable + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("The code did not panic") + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf("The panic was not an error") + } + + if !strings.Contains(rAsE.Error(), x.Check().Error()) { + t.Errorf("The panic was not due to a bad Variable") + } + }() + x.DerivativeWrt(x) +} + +/* +TestVariable_DerivativeWrt2 +Description: + + Tests that the DerivativeWrt() method properly panics when a valid + variable is used to call it, but the input is not well-defined. +*/ + +/* +TestVariable_DerivativeWrt2 +Description: + + Tests that the DerivativeWrt() method properly returns zero when + called on a variable with a w.r.t. variable that is different + from the receiver. +*/ +func TestVariable_DerivativeWrt2(t *testing.T) { + // Constants + x := symbolic.NewVariable() + y := symbolic.NewVariable() + + // Test + der := x.DerivativeWrt(y) + + derAsScalar, tf := der.(symbolic.ScalarExpression) + if !tf { + t.Errorf( + "expected d(%v)/d(%v) to be a scalar expression; received %T", + x, + y, + der, + ) + + } + if derAsScalar.Constant() != 0.0 { + t.Errorf( + "expected d(%v)/d(%v) to be 0.0; received %v", + x, + y, + derAsScalar.Constant(), + ) + } +} + +/* +TestVariable_DerivativeWrt3 +Description: + + Tests that the DerivativeWrt() method properly returns 1.0 when + called on a variable with a w.r.t. variable that is the same + as the receiver. +*/ +func TestVariable_DerivativeWrt3(t *testing.T) { + // Constants + x := symbolic.NewVariable() + + // Test + der := x.DerivativeWrt(x) + + derAsScalar, tf := der.(symbolic.ScalarExpression) + if !tf { + t.Errorf( + "expected d(%v)/d(%v) to be a scalar expression; received %T", + x, + x, + der, + ) + + } + if derAsScalar.Constant() != 1.0 { + t.Errorf( + "expected d(%v)/d(%v) to be 1.0; received %v", + x, + x, + derAsScalar.Constant(), + ) + } +} + +/* +TestVariable_DerivativeWrt4 +Description: + + Tests that the DerivativeWrt() method properly panics when called + with a well-defined variable receiver and an input that is not + well-defined. +*/ +func TestVariable_DerivativeWrt4(t *testing.T) { + // Constants + x := symbolic.NewVariable() + uninit := symbolic.Variable{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("The code did not panic") + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf("The panic was not an error") + } + + if !strings.Contains(rAsE.Error(), uninit.Check().Error()) { + t.Errorf("The panic was not due to a bad Variable") + } + }() + x.DerivativeWrt(symbolic.Variable{}) +} + +/* +TestVariable_Substitute1 +Description: + + Tests that the Substitute() method properly panics when called + on a variable that is not well-defined. +*/ +func TestVariable_Substitute1(t *testing.T) { + // Constants + var x symbolic.Variable + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("The code did not panic") + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf("The panic was not an error") + } + + if !strings.Contains(rAsE.Error(), x.Check().Error()) { + t.Errorf("The panic was not due to a bad Variable") + } + }() + x.Substitute(x, x) +} + +/* +TestVariable_Substitute2 +Description: + + Tests that the Substitute() method properly panics when called with a variable input vIn + that is not well-defined. +*/ +func TestVariable_Substitute2(t *testing.T) { + // Constants + x := symbolic.NewVariable() + var vIn symbolic.Variable + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("The code did not panic") + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf("The panic was not an error") + } + + if !strings.Contains(rAsE.Error(), vIn.Check().Error()) { + t.Errorf("The panic was not due to a bad Variable") + } + }() + + x.Substitute(vIn, x) +} + +/* +TestVariable_Substitute3 +Description: + + Tests that the Substitute() method properly panics when called with: + - A well-defined variable receiver (v) + - A well-defined variable input vIn + - An expression that is not well-defined +*/ +func TestVariable_Substitute3(t *testing.T) { + // Constants + x := symbolic.NewVariable() + y := symbolic.NewVariable() + var uninit symbolic.Variable + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("The code did not panic") + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf("The panic was not an error") + } + + if !strings.Contains(rAsE.Error(), uninit.Check().Error()) { + t.Errorf("The panic was not due to a bad Variable") + } + }() + x.Substitute(y, uninit) +} + +/* +TestVariable_Substitute4 +Description: + + Tests that the Substitute() method properly returns the variable + that it was called on when the input variable is different. + (And everything is well-defined.) +*/ +func TestVariable_Substitute4(t *testing.T) { + // Constants + x := symbolic.NewVariable() + y := symbolic.NewVariable() + + sumAsScalar := y.Plus(3.114).(symbolic.ScalarExpression) + + // Test + sub := x.Substitute(y, sumAsScalar) + + subAsV, tf := sub.(symbolic.Variable) + if !tf { + t.Errorf( + "expected %v.sub(%v, %v) to be a variable; received %T", + x, + y, + x, + sub, + ) + } + + if subAsV.ID != x.ID { + t.Errorf( + "expected %v.sub(%v, %v) to be %v; received %v", + x, + y, + x, + x, + sub, + ) + } +} + +/* +TestVariable_Substitute5 +Description: + + Tests that the Substitute() method properly returns the expression + that it was called on when the input variable is the same as the replacement variable + vIn (and everything is well-defined). +*/ +func TestVariable_Substitute5(t *testing.T) { + // Constants + x := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 3.14, + } + + // Test + sub := x.Substitute(x, m1) + + _, tf := sub.(symbolic.Monomial) + if !tf { + t.Errorf( + "expected %v.sub(%v, %v) to be a variable; received %T", + x, + x, + x, + sub, + ) + } +} + +/* +TestVariable_SubstituteAccordingTo1 +Description: + + Tests that the SubstituteAccordingTo() method properly panics when called + on a variable that is not well-defined. +*/ +func TestVariable_SubstituteAccordingTo1(t *testing.T) { + // Constants + var x symbolic.Variable + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("The code did not panic") + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf("The panic was not an error") + } + + if !strings.Contains(rAsE.Error(), x.Check().Error()) { + t.Errorf("The panic was not due to a bad Variable") + } + }() + x.SubstituteAccordingTo(map[symbolic.Variable]symbolic.Expression{ + x: x, + }) +} + +/* +TestVariable_SubstituteAccordingTo2 +Description: + + Tests that the SubstituteAccordingTo() method properly panics when called with a + map that contains a well-defined variable key and a value that is not well-defined. +*/ +func TestVariable_SubstituteAccordingTo2(t *testing.T) { + // Constants + x := symbolic.NewVariable() + var uninit symbolic.Variable + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("The code did not panic") + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf("The panic was not an error") + } + + if !strings.Contains(rAsE.Error(), uninit.Check().Error()) { + t.Errorf("The panic was not due to a bad Variable") + } + }() + x.SubstituteAccordingTo(map[symbolic.Variable]symbolic.Expression{ + x: uninit, + }) +} + +/* +TestVariable_SubstituteAccordingTo3 +Description: + + Tests that the SubstituteAccordingTo() method properly returns a temporary + expression (a unique variable) when the input map contains a key that is + the same as the input variable (the receiver). +*/ +func TestVariable_SubstituteAccordingTo3(t *testing.T) { + // Constants + x := symbolic.NewVariable() + y := symbolic.NewVariable() + + // Test + sub := x.SubstituteAccordingTo(map[symbolic.Variable]symbolic.Expression{ + x: y, + }) + + subAsV, tf := sub.(symbolic.Variable) + if !tf { + t.Errorf( + "expected %v.sub(%v, %v) to be a variable; received %T", + x, + x, + y, + sub, + ) + } + + if subAsV.ID != y.ID { + t.Errorf( + "expected %v.sub(%v, %v) to be a unique variable; received %v", + x, + x, + y, + sub, + ) + } +} + +/* +TestVariable_SubstituteAccordingTo4 +Description: + + Tests that the SubstituteAccordingTo() method properly returns a the same variable + when the input map DOES NOT contain a key that is + the same as the input variable (the receiver). +*/ +func TestVariable_SubstituteAccordingTo4(t *testing.T) { + // Constants + x := symbolic.NewVariable() + y := symbolic.NewVariable() + + // Test + sub := x.SubstituteAccordingTo(map[symbolic.Variable]symbolic.Expression{ + y: x, + }) + + subAsV, tf := sub.(symbolic.Variable) + if !tf { + t.Errorf( + "expected %v.sub(%v, %v) to be a variable; received %T", + x, + x, + y, + sub, + ) + } + + if subAsV.ID != x.ID { + t.Errorf( + "expected %v.sub(%v, %v) to be a unique variable; received %v", + x, + x, + y, + sub, + ) + } +} + +/* +TestVariable_Power1 +Description: + + Tests that the variable correctly becomes a monomial when raised to a power + greater than 1. +*/ +func TestVariable_Power1(t *testing.T) { + // Constants + x := symbolic.NewVariable() + + // Test + pow := x.Power(3) + mon, tf := pow.(symbolic.Monomial) + if !tf { + t.Errorf( + "expected %v^3 to be a monomial; received %T", + x, + pow, + ) + } + + if mon.Exponents[0] != 3 { + t.Errorf( + "expected %v^3 to have an exponent of 3; received %v", + x, + mon.Exponents[0], + ) + } + + if mon.Coefficient != 1.0 { + t.Errorf( + "expected %v^3 to have a coefficient of 1.0; received %v", + x, + mon.Coefficient, + ) + } +} diff --git a/testing/symbolic/variable_vector_test.go b/testing/symbolic/variable_vector_test.go index 4745b82..c4fc9cf 100644 --- a/testing/symbolic/variable_vector_test.go +++ b/testing/symbolic/variable_vector_test.go @@ -425,6 +425,171 @@ func TestVariableVector_Plus7(t *testing.T) { vv1.Plus(s2) } +/* +TestVariableVector_Minus1 +Description: + + Tests that the Minus method correctly panics when a vector expression that is not + well-defined used to call Minus(). +*/ +func TestVariableVector_Minus1(t *testing.T) { + // Setup + vv1 := symbolic.VariableVector{ + symbolic.Variable{}, + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected vv1.Minus(vv2) to panic; received no panic", + ) + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf( + "Expected vv1.Minus(vv2) to panic with an error; received %v", + r, + ) + } + + // Check that the error message is correct + expectedError := vv1.Check() + if !strings.Contains( + rAsE.Error(), + expectedError.Error(), + ) { + t.Errorf( + "Expected vv1.Minus(vv2) to panic with a specific error; instead received %v", + r, + ) + } + }() + + // Test + vv1.Minus(vv1) + t.Errorf("This line should not be reached") +} + +/* +TestVariableVector_Minus2 +Description: + + Tests that the Minus method correctly panics when a vector expression that is + well-defined but has a different length than the other operand in the call to Minus(). +*/ +func TestVariableVector_Minus2(t *testing.T) { + // Setup + vv1 := symbolic.NewVariableVector(111) + vv2 := symbolic.NewVariableVector(110) + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected vv1.Minus(vv2) to panic; received no panic", + ) + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf( + "Expected vv1.Minus(vv2) to panic with an error; received %v", + r, + ) + } + + // Check that the error message is correct + if rAsE.Error() != (smErrors.DimensionError{ + Operation: "Minus", + Arg1: vv1, + Arg2: vv2, + }).Error() { + t.Errorf( + "Expected vv1.Minus(vv2) to panic with a DimensionError; received %v", + r, + ) + } + }() + + // Test + vv1.Minus(vv2) + t.Errorf("This line should not be reached") +} + +/* +TestVariableVector_Minus3 +Description: + + Tests that the Minus method correctly panics when a vector expression that is + well-defined is used to call Minus(), but the input to the minus is not well-defined. +*/ +func TestVariableVector_Minus3(t *testing.T) { + // Setup + vv1 := symbolic.NewVariableVector(111) + vv2 := symbolic.VariableVector{ + symbolic.Variable{}, + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected vv1.Minus(vv2) to panic; received no panic", + ) + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf( + "Expected vv1.Minus(vv2) to panic with an error; received %v", + r, + ) + } + + // Check that the error message is correct + if !strings.Contains( + rAsE.Error(), + "element 0 has an issue:", + ) { + t.Errorf( + "Expected vv1.Minus(vv2) to panic with a specific error; received %v", + r, + ) + } + }() + + // Test + vv1.Minus(vv2) + t.Errorf("This line should not be reached") +} + +/* +TestVariableVector_Minus4 +Description: + + Tests that the Minus method correctly returns a PolynomialVector object when + a VariableVector is subtracted from another VariableVector. +*/ +func TestVariableVector_Minus4(t *testing.T) { + // Setup + vv1 := symbolic.NewVariableVector(111) + vv2 := symbolic.NewVariableVector(111) + + // Test + r := vv1.Minus(vv2) + if _, ok := r.(symbolic.PolynomialVector); !ok { + t.Errorf( + "Expected vv1.Minus(vv2) to return a PolynomialVector object; received %T", + r, + ) + } +} + /* TestVariableVector_Multiply1 Description: @@ -1293,3 +1458,33 @@ func TestVariableVector_String1(t *testing.T) { } } } + +/* +TestVariableVector_Power1 +Description: + + Verifies that the Power method returns a MonomialVector object when + computing a power >= 2 for a well-defined variable vector. +*/ +func TestVariableVector_Power1(t *testing.T) { + // Constants + vv := symbolic.NewVariableVector(1) + + // Test + r := vv.Power(2) + rAsVV, ok := r.(symbolic.Monomial) + if !ok { + t.Errorf( + "Expected vv.Power(2) to return a MonomialVector object; received %T", + r, + ) + } + + if rAsVV.Dims()[0] != 1 { + t.Errorf( + "Expected r to have length 1; received %v", + rAsVV.Dims()[0], + ) + } + +} diff --git a/testing/symbolic/vector_expression_test.go b/testing/symbolic/vector_expression_test.go index a9701ae..27cb931 100644 --- a/testing/symbolic/vector_expression_test.go +++ b/testing/symbolic/vector_expression_test.go @@ -2,6 +2,7 @@ package symbolic_test import ( "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/smErrors" "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "strings" "testing" @@ -49,3 +50,382 @@ func TestVectorExpression_ToVectorExpression1(t *testing.T) { } } } + +/* +TestVectorExpression_ConcretizeVectorExpression1 +Description: + + Tests the conversion of a slice of constants (K) to a KVector. +*/ +func TestVectorExpression_ConcretizeVectorExpression1(t *testing.T) { + // Constants + k1 := symbolic.K(2) + k2 := symbolic.K(3) + k3 := symbolic.K(4) + k4 := symbolic.K(5) + slice := []symbolic.ScalarExpression{k1, k2, k3, k4} + + // Test + v := symbolic.ConcretizeVectorExpression(slice) + if _, tf := v.(symbolic.KVector); !tf { + t.Errorf("expected a KVector; received %T", v) + } +} + +/* +TestVectorExpression_ConcretizeVectorExpression2 +Description: + + Tests the conversion of a slice of all variables to a VariableVector. +*/ +func TestVectorExpression_ConcretizeVectorExpression2(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + v2 := symbolic.NewVariable() + v3 := symbolic.NewVariable() + v4 := symbolic.NewVariable() + slice := []symbolic.ScalarExpression{v1, v2, v3, v4} + + // Test + v := symbolic.ConcretizeVectorExpression(slice) + if _, tf := v.(symbolic.VariableVector); !tf { + t.Errorf("expected a VariableVector; received %T", v) + } +} + +/* +TestVectorExpression_ConcretizeVectorExpression3 +Description: + + Tests the conversion of a slice of all monomials to a MonomialVector. +*/ +func TestVectorExpression_ConcretizeVectorExpression3(t *testing.T) { + // Constants + m1 := symbolic.NewVariable().ToMonomial() + m2 := symbolic.NewVariable().ToMonomial() + m3 := symbolic.NewVariable().ToMonomial() + m4 := symbolic.NewVariable().ToMonomial() + slice := []symbolic.ScalarExpression{m1, m2, m3, m4} + + // Test + v := symbolic.ConcretizeVectorExpression(slice) + if _, tf := v.(symbolic.MonomialVector); !tf { + t.Errorf("expected a MonomialVector; received %T", v) + } +} + +/* +TestVectorExpression_ConcretizeVectorExpression4 +Description: + + Tests the conversion of a slice of all polynomials to a PolynomialVector. +*/ +func TestVectorExpression_ConcretizeVectorExpression4(t *testing.T) { + // Constants + p1 := symbolic.NewVariable().ToPolynomial() + p2 := symbolic.NewVariable().ToPolynomial() + p3 := symbolic.NewVariable().ToPolynomial() + p4 := symbolic.NewVariable().ToPolynomial() + slice := []symbolic.ScalarExpression{p1, p2, p3, p4} + + // Test + v := symbolic.ConcretizeVectorExpression(slice) + if _, tf := v.(symbolic.PolynomialVector); !tf { + t.Errorf("expected a PolynomialVector; received %T", v) + } +} + +/* +TestVectorExpression_ConcretizeVectorExpression5 +Description: + + Tests the conversion of a slice containing constant (K) and variable expressions to a + vector of monomials (MonomialVector). +*/ +func TestVectorExpression_ConcretizeVectorExpression5(t *testing.T) { + // Constants + k1 := symbolic.K(2) + v1 := symbolic.NewVariable() + k2 := symbolic.K(3) + v2 := symbolic.NewVariable() + k3 := symbolic.K(4) + v3 := symbolic.NewVariable() + k4 := symbolic.K(5) + v4 := symbolic.NewVariable() + slice := []symbolic.ScalarExpression{k1, v1, k2, v2, k3, v3, k4, v4} + + // Test + v := symbolic.ConcretizeVectorExpression(slice) + if _, tf := v.(symbolic.MonomialVector); !tf { + t.Errorf("expected a MonomialVector; received %T", v) + } +} + +/* +TestVectorExpression_ConcretizeVectorExpression6 +Description: + + Tests the conversion of a slice containing a constant and a polynomial to a PolynomialVector. +*/ +func TestVectorExpression_ConcretizeVectorExpression6(t *testing.T) { + // Constants + k := symbolic.K(2) + p := symbolic.NewVariable().ToPolynomial() + slice := []symbolic.ScalarExpression{k, p} + + // Test + v := symbolic.ConcretizeVectorExpression(slice) + if _, tf := v.(symbolic.PolynomialVector); !tf { + t.Errorf("expected a PolynomialVector; received %T", v) + } +} + +/* +TestVectorExpression_ConcretizeVectorExpression7 +Description: + + Tests that the function panics when the input slice is empty. +*/ +func TestVectorExpression_ConcretizeVectorExpression7(t *testing.T) { + // Test + defer func() { + if r := recover(); r == nil { + t.Errorf("expected a panic when the input slice is empty; received nil") + } + }() + symbolic.ConcretizeVectorExpression([]symbolic.ScalarExpression{}) + t.Errorf("Problem! The function did not panic when the input slice was empty") +} + +/* +TestVectorExpression_ConcretizeVectorExpression8 +Description: + + Tests that the function correctly generates a polynomial vector from a slice of ScalarExpression objects, + one is a polynomial, one is a monomial, and the other is a variable. +*/ +func TestVectorExpression_ConcretizeVectorExpression8(t *testing.T) { + // Constants + p := symbolic.NewVariable().ToPolynomial() + m := symbolic.NewVariable().ToMonomial() + v := symbolic.NewVariable() + slice := []symbolic.ScalarExpression{p, m, v} + + // Test + pv := symbolic.ConcretizeVectorExpression(slice) + + vOut, tf := pv.(symbolic.PolynomialVector) + if !tf { + t.Errorf("expected a PolynomialVector; received %T", pv) + } + + if len(vOut) != 3 { + t.Errorf("expected a PolynomialVector of length 3; received %v", len(vOut)) + } +} + +/* +TestVectorExpression_VectorSubstituteTemplate1 +Description: + + Verify that the VectorSubstituteTemplate() function panics when the input vector is + not a well-defined VectorExpression. +*/ +func TestVectorExpression_VectorSubstituteTemplate1(t *testing.T) { + // Constants + testVec := symbolic.VariableVector{ + symbolic.NewVariable(), + symbolic.Variable{}, + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("expected a panic when the input vector is not a well-defined VectorExpression; received nil") + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf("expected a panic of type error; received %T", r) + } + + if !strings.Contains( + rAsE.Error(), + testVec.Check().Error(), + ) { + t.Errorf("expected error message to contain %v; received %v", + "unexpected expression type in vector expression: symbolic.Variable", + rAsE.Error(), + ) + } + }() + symbolic.VectorSubstituteTemplate(testVec, symbolic.NewVariable(), symbolic.NewVariable()) + t.Errorf("Problem! The function did not panic when the input vector was not a well-defined VectorExpression") +} + +/* +TestVectorExpression_VectorSubstituteTemplate2 +Description: + + Verify that the VectorSubstituteTemplate() function panics when the input vector is + well-defined but the input target variable is not well-defined. +*/ +func TestVectorExpression_VectorSubstituteTemplate2(t *testing.T) { + // Constants + testVec := symbolic.VariableVector{ + symbolic.NewVariable(), + symbolic.NewVariable(), + } + badVar := symbolic.Variable{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("expected a panic when the input target variable is not well-defined; received nil") + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf("expected a panic of type error; received %T", r) + } + + if !strings.Contains( + rAsE.Error(), + badVar.Check().Error(), + ) { + t.Errorf("expected error message to contain %v; received %v", + badVar.Check().Error(), + rAsE.Error(), + ) + } + }() + symbolic.VectorSubstituteTemplate(testVec, badVar, symbolic.NewVariable()) + t.Errorf("Problem! The function did not panic when the input target variable was not well-defined") +} + +/* +TestVectorExpression_VectorSubstituteTemplate3 +Description: + + Verify that the VectorSubstituteTemplate() function panics when the input vector is + well-defined, the input target variable is well-defined, but the input scalar expression + is not well-defined. +*/ +func TestVectorExpression_VectorSubstituteTemplate3(t *testing.T) { + // Constants + testVec := symbolic.VariableVector{ + symbolic.NewVariable(), + symbolic.NewVariable(), + } + badSE := symbolic.Variable{} + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("expected a panic when the input scalar expression is not well-defined; received nil") + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf("expected a panic of type error; received %T", r) + } + + if !strings.Contains( + rAsE.Error(), + badSE.Check().Error(), + ) { + t.Errorf("expected error message to contain %v; received %v", + badSE.Check().Error(), + rAsE.Error(), + ) + } + }() + symbolic.VectorSubstituteTemplate(testVec, symbolic.NewVariable(), badSE) + t.Errorf("Problem! The function did not panic when the input scalar expression was not well-defined") +} + +/* +TestVectorExpression_VectorPowerTemplate1 +Description: + + Verify that the VectorPowerTemplate() function panics when the input vector is + not a well-defined VectorExpression. +*/ +func TestVectorExpression_VectorPowerTemplate1(t *testing.T) { + // Constants + testVec := symbolic.VariableVector{ + symbolic.NewVariable(), + symbolic.Variable{}, + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("expected a panic when the input vector is not a well-defined VectorExpression; received nil") + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf("expected a panic of type error; received %T", r) + } + + if !strings.Contains( + rAsE.Error(), + testVec.Check().Error(), + ) { + t.Errorf("expected error message to contain %v; received %v", + "unexpected expression type in vector expression: symbolic.Variable", + rAsE.Error(), + ) + } + }() + symbolic.VectorPowerTemplate(testVec, 2) + t.Errorf("Problem! The function did not panic when the input vector was not a well-defined VectorExpression") +} + +/* +TestVectorExpression_VectorPowerTemplate2 +Description: + + Verify that the VectorPowerTemplate() function panics when the input vector is + well-defined but the input power is less than 0. +*/ +func TestVectorExpression_VectorPowerTemplate2(t *testing.T) { + // Constants + testVec := symbolic.VariableVector{ + symbolic.NewVariable(), + symbolic.NewVariable(), + } + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf("expected a panic when the input power is less than 0; received nil") + } + + rAsE, tf := r.(error) + if !tf { + t.Errorf("expected a panic of type error; received %T", r) + } + + expectedError := smErrors.NegativeExponentError{ + -1, + } + if !strings.Contains( + rAsE.Error(), + expectedError.Error(), + ) { + t.Errorf("expected error message to contain %v; received %v", + expectedError.Error(), + rAsE.Error(), + ) + } + }() + symbolic.VectorPowerTemplate(testVec, -1) + t.Errorf("Problem! The function did not panic when the input power was less than 0") +}