diff --git a/expreduce/builtin_arithmetic.go b/expreduce/builtin_arithmetic.go index 5945f0b..17067f1 100644 --- a/expreduce/builtin_arithmetic.go +++ b/expreduce/builtin_arithmetic.go @@ -382,7 +382,7 @@ func getArithmeticDefinitions() (defs []Definition) { Attributes: []string{"Flat", "Listable", "NumericFunction", "OneIdentity", "Orderless"}, Default: "1", Rules: []Rule{ - {"Times[a_^Optional[m_], a_^Optional[n_], rest___]", "a^(m+n)*rest"}, + {"Verbatim[Times][beg___, a_^Optional[m_], a_^Optional[n_], end___]", "beg*a^(m+n)*end"}, {"Times[den_Integer^-1, num_Integer, rest___]", "Rational[num,den] * rest"}, {"(1/Infinity)", "0"}, {"Times[ComplexInfinity, rest___]", "ComplexInfinity"}, diff --git a/expreduce/builtin_comparison.go b/expreduce/builtin_comparison.go index cf5c300..9075e3e 100644 --- a/expreduce/builtin_comparison.go +++ b/expreduce/builtin_comparison.go @@ -450,8 +450,7 @@ func getComparisonDefinitions() (defs []Definition) { } } i -= 1 - this.Parts = append([]Ex{this.Parts[0]}, this.Parts[i:]...) - return this + return NewExpression(append([]Ex{this.Parts[0]}, this.Parts[i:]...)) }, SimpleExamples: []TestInstruction{ &SameTest{"3", "Max[1,2,3]"}, diff --git a/expreduce/builtin_list.go b/expreduce/builtin_list.go index 4033768..5d6dc97 100644 --- a/expreduce/builtin_list.go +++ b/expreduce/builtin_list.go @@ -606,15 +606,12 @@ func GetListDefinitions() (defs []Definition) { expr, isExpr := this.Parts[1].(*Expression) if isExpr { toReturn := NewExpression([]Ex{expr.Parts[0]}) + seen := map[uint64]bool{} for _, orig := range expr.Parts[1:] { - isDupe := false - for _, deduped := range toReturn.Parts[1:] { - if IsSameQ(orig, deduped, &es.CASLogger) { - isDupe = true - break - } - } + hash := hashEx(orig) + _, isDupe := seen[hash] if !isDupe { + seen[hash] = true toReturn.Parts = append(toReturn.Parts, orig) } } @@ -626,6 +623,7 @@ func GetListDefinitions() (defs []Definition) { &SameTest{"{b,a}", "DeleteDuplicates[{b,a,b}]"}, &SameTest{"foo[b,a]", "DeleteDuplicates[foo[b,a,b]]"}, &SameTest{"{}", "DeleteDuplicates[{}]"}, + &SameTest{"10000", "Length[DeleteDuplicates[Range[10000]]]"}, }, }) defs = append(defs, Definition{ diff --git a/expreduce/builtin_sort.go b/expreduce/builtin_sort.go index 93eaab2..88c0b79 100644 --- a/expreduce/builtin_sort.go +++ b/expreduce/builtin_sort.go @@ -17,6 +17,7 @@ func GetSortDefinitions() (defs []Definition) { exp, ok := this.Parts[1].(*Expression) if ok { sortedExp := exp.DeepCopy().(*Expression) + sortedExp.evaledHash = 0 sortedExp.cachedHash = 0 sort.Sort(sortedExp) return sortedExp @@ -39,6 +40,7 @@ func GetSortDefinitions() (defs []Definition) { }, Tests: []TestInstruction{ &SameTest{"{x, 2*x, 2*x^2, y, 2*y, 2*y^2}", "Sort[{x, 2*x, y, 2*y, 2*y^2, 2*x^2}]"}, + &SameTest{"{1/a,a,a^2,1/b^2,1/b,b,b^2,1/(a+b)^2,a+b,a+b,(a+b)^2,1/foo[x]^2,foo[x],foo[x]^2}", "Sort[{a^-1,a,a^2,b^-2,b^-1,b,b^2,(a+b)^-2,(a+b),(a+b)^2,a+b,foo[x],foo[x]^-2,foo[x]^2}]"}, }, }) defs = append(defs, Definition{ diff --git a/expreduce/builtin_system.go b/expreduce/builtin_system.go index 766f845..76f40be 100644 --- a/expreduce/builtin_system.go +++ b/expreduce/builtin_system.go @@ -8,15 +8,12 @@ import "runtime/pprof" import "log" import "io/ioutil" import "github.com/op/go-logging" -import "hash/fnv" import "flag" var mymemprofile = flag.String("mymemprofile", "", "write memory profile to this file") func hashEx(e Ex) uint64 { - h := fnv.New64a() - e.Hash(&h) - return h.Sum64() + return e.Hash() } func exprToN(es *EvalState, e Ex) Ex { diff --git a/expreduce/cas.go b/expreduce/cas.go index 2c7817c..6e4ed7f 100644 --- a/expreduce/cas.go +++ b/expreduce/cas.go @@ -4,8 +4,6 @@ package expreduce -import "hash" - type ToStringFnType (func(*Expression, string) (bool, string)) // A nasty global to keep track of ToString functions. TODO: Fix this. @@ -19,5 +17,5 @@ type Ex interface { IsEqual(b Ex, cl *CASLogger) string DeepCopy() Ex NeedsEval() bool - Hash(h *hash.Hash64) + Hash() uint64 } diff --git a/expreduce/ex_expression.go b/expreduce/ex_expression.go index 60dc8ab..91936cf 100644 --- a/expreduce/ex_expression.go +++ b/expreduce/ex_expression.go @@ -4,9 +4,10 @@ import "bytes" import "math/big" import "sort" import "fmt" +import "encoding/binary" import "time" import "flag" -import "hash" +import "hash/fnv" var printevals = flag.Bool("printevals", false, "") var checkhashes = flag.Bool("checkhashes", false, "") @@ -15,6 +16,7 @@ type Expression struct { Parts []Ex needsEval bool correctlyInstantiated bool + evaledHash uint64 cachedHash uint64 } @@ -103,7 +105,7 @@ func (this *Expression) Eval(es *EvalState) Ex { lastExHash := uint64(0) var lastEx Ex = this currExHash := hashEx(this) - if currExHash == this.cachedHash { + if currExHash == this.evaledHash { return this } var currEx Ex = this @@ -112,7 +114,7 @@ func (this *Expression) Eval(es *EvalState) Ex { lastExHash = currExHash curr, isExpr := currEx.(*Expression) if *checkhashes { - if isExpr && curr.cachedHash != 0 && currExHash != curr.cachedHash { + if isExpr && curr.evaledHash != 0 && currExHash != curr.evaledHash { fmt.Printf("invalid cache: %v. Used to be %v\n", curr, lastEx) } lastEx = currEx @@ -124,7 +126,7 @@ func (this *Expression) Eval(es *EvalState) Ex { return retVal } } - if isExpr && currExHash == curr.cachedHash { + if isExpr && currExHash == curr.evaledHash { return curr } @@ -184,7 +186,11 @@ func (this *Expression) Eval(es *EvalState) Ex { if es.trace != nil && !es.IsFrozen() { es.trace = NewExpression([]Ex{&Symbol{"List"}}) } + oldHash := curr.Parts[i].Hash() curr.Parts[i] = curr.Parts[i].Eval(es) + if oldHash != curr.Parts[i].Hash() { + curr.cachedHash = 0 + } if es.trace != nil && !es.IsFrozen() { if len(es.trace.Parts) > 2 { // The DeepCopy here doesn't seem to affect anything, but @@ -279,7 +285,7 @@ func (this *Expression) Eval(es *EvalState) Ex { curr, isExpr := currEx.(*Expression) if isExpr { curr.needsEval = false - curr.cachedHash = currExHash + curr.evaledHash = currExHash } return currEx } @@ -421,6 +427,7 @@ func (this *Expression) DeepCopy() Ex { } thiscopy.needsEval = this.needsEval thiscopy.correctlyInstantiated = this.correctlyInstantiated + thiscopy.evaledHash = this.evaledHash thiscopy.cachedHash = this.cachedHash return thiscopy } @@ -430,6 +437,7 @@ func (this *Expression) ShallowCopy() *Expression { thiscopy.Parts = append([]Ex{}, this.Parts...) thiscopy.needsEval = this.needsEval thiscopy.correctlyInstantiated = this.correctlyInstantiated + thiscopy.evaledHash = this.evaledHash thiscopy.cachedHash = this.cachedHash return thiscopy } @@ -455,11 +463,23 @@ func (this *Expression) NeedsEval() bool { return this.needsEval } -func (this *Expression) Hash(h *hash.Hash64) { - (*h).Write([]byte{72, 5, 244, 86, 5, 210, 69, 30}) +func (this *Expression) Hash() uint64 { + // Will generate stale hashes but offers significant speedup. Use with care. + //if this.cachedHash > 0 { + //return this.cachedHash + //} + h := fnv.New64a() + h.Write([]byte{72, 5, 244, 86, 5, 210, 69, 30}) for _, part := range this.Parts { - part.Hash(h) + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, part.Hash()) + h.Write(b) } + //if this.cachedHash > 0 && h.Sum64() != this.cachedHash { + //fmt.Printf("%v stale hash!!!\n", this) + //} + this.cachedHash = h.Sum64() + return h.Sum64() } func NewExpression(parts []Ex) *Expression { diff --git a/expreduce/ex_integer.go b/expreduce/ex_integer.go index b262b67..64b37d0 100644 --- a/expreduce/ex_integer.go +++ b/expreduce/ex_integer.go @@ -2,7 +2,7 @@ package expreduce import "fmt" import "math/big" -import "hash" +import "hash/fnv" // Integer numbers represented by big.Int type Integer struct { @@ -54,10 +54,12 @@ func NewInt(i int64) *Integer { return &Integer{big.NewInt(i)} } -func (this *Integer) Hash(h *hash.Hash64) { - (*h).Write([]byte{242, 99, 84, 113, 102, 46, 118, 94}) +func (this *Integer) Hash() uint64 { + h := fnv.New64a() + h.Write([]byte{242, 99, 84, 113, 102, 46, 118, 94}) bytes, _ := this.Val.MarshalText() - (*h).Write(bytes) + h.Write(bytes) + return h.Sum64() } func (this *Integer) AsBigFloat() *big.Float { diff --git a/expreduce/ex_rational.go b/expreduce/ex_rational.go index 17bf5f7..bea5730 100644 --- a/expreduce/ex_rational.go +++ b/expreduce/ex_rational.go @@ -2,7 +2,7 @@ package expreduce import "fmt" import "math/big" -import "hash" +import "hash/fnv" type Rational struct { Num *big.Int @@ -96,12 +96,14 @@ func NewRational(n *big.Int, d *big.Int) *Rational { return &Rational{n, d, true} } -func (this *Rational) Hash(h *hash.Hash64) { - (*h).Write([]byte{90, 82, 214, 51, 52, 7, 7, 33}) +func (this *Rational) Hash() uint64 { + h := fnv.New64a() + h.Write([]byte{90, 82, 214, 51, 52, 7, 7, 33}) nBytes, _ := this.Num.MarshalText() - (*h).Write(nBytes) + h.Write(nBytes) dBytes, _ := this.Den.MarshalText() - (*h).Write(dBytes) + h.Write(dBytes) + return h.Sum64() } func (this *Rational) AsBigFloat() *big.Float { diff --git a/expreduce/ex_real.go b/expreduce/ex_real.go index f19ca17..0e1c8a4 100644 --- a/expreduce/ex_real.go +++ b/expreduce/ex_real.go @@ -3,7 +3,7 @@ package expreduce import "fmt" import "math/big" import "bytes" -import "hash" +import "hash/fnv" // Floating point numbers represented by big.Float type Flt struct { @@ -68,10 +68,12 @@ func (this *Flt) NeedsEval() bool { return false } -func (this *Flt) Hash(h *hash.Hash64) { - (*h).Write([]byte{195, 244, 76, 249, 227, 115, 88, 251}) +func (this *Flt) Hash() uint64 { + h := fnv.New64a() + h.Write([]byte{195, 244, 76, 249, 227, 115, 88, 251}) bytes, _ := this.Val.MarshalText() - (*h).Write(bytes) + h.Write(bytes) + return h.Sum64() } func (this *Flt) AddI(i *Integer) { diff --git a/expreduce/ex_string.go b/expreduce/ex_string.go index cc8ab1e..7f47ce5 100644 --- a/expreduce/ex_string.go +++ b/expreduce/ex_string.go @@ -1,7 +1,7 @@ package expreduce import "fmt" -import "hash" +import "hash/fnv" type String struct { Val string @@ -42,7 +42,9 @@ func (this *String) NeedsEval() bool { return false } -func (this *String) Hash(h *hash.Hash64) { - (*h).Write([]byte{102, 206, 57, 172, 207, 100, 198, 133}) - (*h).Write([]byte(this.Val)) +func (this *String) Hash() uint64 { + h := fnv.New64a() + h.Write([]byte{102, 206, 57, 172, 207, 100, 198, 133}) + h.Write([]byte(this.Val)) + return h.Sum64() } diff --git a/expreduce/ex_symbol.go b/expreduce/ex_symbol.go index b5b8c20..5c956d7 100644 --- a/expreduce/ex_symbol.go +++ b/expreduce/ex_symbol.go @@ -2,7 +2,7 @@ package expreduce import "fmt" import "sort" -import "hash" +import "hash/fnv" // Symbols are defined by a string-based name type Symbol struct { @@ -229,9 +229,11 @@ func (this *Symbol) NeedsEval() bool { return false } -func (this *Symbol) Hash(h *hash.Hash64) { - (*h).Write([]byte{107, 10, 247, 23, 33, 221, 163, 156}) - (*h).Write([]byte(this.Name)) +func (this *Symbol) Hash() uint64 { + h := fnv.New64a() + h.Write([]byte{107, 10, 247, 23, 33, 221, 163, 156}) + h.Write([]byte(this.Name)) + return h.Sum64() } func ContainsSymbol(e Ex, name string) bool { diff --git a/expreduce/order.go b/expreduce/order.go index 6c93e47..6aae510 100644 --- a/expreduce/order.go +++ b/expreduce/order.go @@ -74,6 +74,13 @@ func ExOrder(a Ex, b Ex) int64 { aAsExp, }), b) } + if aIsPow && !bIsPow { + return ExOrder(a, NewExpression([]Ex{ + &Symbol{"Power"}, + bAsExp, + NewInt(1), + })) + } if bIsPow && aIsTimes { return ExOrder(aAsExp, NewExpression([]Ex{ &Symbol{"Times"}, @@ -81,6 +88,13 @@ func ExOrder(a Ex, b Ex) int64 { bAsExp, })) } + if !aIsPow && bIsPow { + return ExOrder(NewExpression([]Ex{ + &Symbol{"Power"}, + aAsExp, + NewInt(1), + }), b) + } timesMode := aIsTimes && bIsTimes if !timesMode { for i := 0; i < Min(len(aAsExp.Parts), len(bAsExp.Parts)); i++ { diff --git a/expreduce/replace.go b/expreduce/replace.go index 08ae7be..35f7ff9 100644 --- a/expreduce/replace.go +++ b/expreduce/replace.go @@ -52,6 +52,7 @@ func ReplacePDInternal(e Ex, pm *PDManager) Ex { } asExpr, isExpr := e.(*Expression) if isExpr { + asExpr.evaledHash = 0 asExpr.cachedHash = 0 for i := range asExpr.Parts { asExpr.Parts[i] = ReplacePDInternal(asExpr.Parts[i], pm) diff --git a/expreduce/resources/power.m b/expreduce/resources/power.m index 2f14700..e26362e 100644 --- a/expreduce/resources/power.m +++ b/expreduce/resources/power.m @@ -92,7 +92,7 @@ Exponent::usage = "`Exponent[p, var]` returns the degree of `p` with respect to the variable `var`."; Exponent[expr_/p_Plus, var_, head_] := Exponent[expr, var, head]; -Exponent[expr_, var_, head_] := +Exponent[expr_, var_, head_] := If[expr === 0, head[], Module[{e = expr, v = var, h = head, theCases, toCheck}, toCheck = expr // Expand; toCheck = If[Head[toCheck] === Plus, toCheck, {toCheck}]; @@ -100,7 +100,7 @@ Cases[toCheck, p_.*v^Optional[exp_] -> exp] // DeleteDuplicates; If[Length[theCases] =!= Length[toCheck], PrependTo[theCases, 0]]; h @@ theCases - ]; + ]]; Exponent[expr_, var_] := Exponent[expr, var, Max]; Attributes[Exponent] = {Listable, Protected}; Tests`Exponent = { @@ -128,7 +128,10 @@ ESameTest[{-2}, Exponent[x^(-2), x, List]], ESameTest[{1}, Exponent[(a*x)/(3 + x^3), x, List]], ESameTest[{0,1}, Exponent[1 + b*x + x^2 - (x*(1 + a*x))/a, x, List]], - ESameTest[{0,1}, Exponent[1 + x + x^2 - (x*(1 + 2*x))/2, x, List]] + ESameTest[{0,1}, Exponent[1 + x + x^2 - (x*(1 + 2*x))/2, x, List]], + ESameTest[{}, Exponent[0, x, List]], + ESameTest[{}, Exponent[0, x+2, List]], + ESameTest[{}, Exponent[0, 0, List]] ], EKnownFailures[ ESameTest[{0,1}, Exponent[1 + x^x^x, x^x^x, List]] ] @@ -169,18 +172,22 @@ ] }; +(*TODO: timing counter of head evals*) PolynomialQuotientRemainder::usage = "`PolynomialQuotientRemainder[poly_, div_, var_]` returns the quotient and remainder of `poly` divided by `div` treating `var` as the polynomial variable."; ExpreduceLeadingCoeff[p_, x_] := Coefficient[p, x^Exponent[p, x]]; PolynomialQuotientRemainder[inp_, inq_, v_] := - Module[{a = inp, b = inq, x = v, r, d, c, i, s, q}, + Module[{a = inp, b = inq, x = v, r, d, c, i, s, q, rExp}, (*I should think carefully about when I use = vs := to avoid unwanted evaluation*) q = 0; r = a; d = Exponent[b, x]; - c = ExpreduceLeadingCoeff[b, x]; + c = Coefficient[b, x^d]; i = 1; - While[Exponent[r, x] >= d && i < 20, - s = (ExpreduceLeadingCoeff[r, x]/c)*x^(Exponent[r, x] - d); + While[rExp = Exponent[r, x]; rExp >= d && i < 20, + (*Looks like we get the coefficient and the exponent of the leading term here. Perhaps we can just grab the leading term and get both at once. And maybe we can exploit the canonical ordering*) + (*But all of this seems wrong. I think the slowness resides in the expreduce interpreter.*) + (*TODO tomorrow: find out what 'power' function takes up the most time and optimize it by making kernel changes*) + s = (Coefficient[r, x^rExp]/c)*x^(rExp - d); q = q + s; r = r - s*b; i = i + 1; @@ -345,10 +352,12 @@ h = 1; i = 1; While[v =!= 0 && i < 20, - delta = Exponent[u, x] - Exponent[v, x]; - beta = (-1)^(delta + 1)*Exponent[u, x]*h^delta; - h = h*(Exponent[v, x]/h)^delta; - newU = v; + uEx = Exponent[u, x]; + vEx = Exponent[v, x]; + delta = uEx - vEx; + beta = (-1)^(delta + 1)*uEx*h^delta; + h = h*(vEx/h)^delta; + newU := v; newV = PolynomialRemainder[u, v, x]/beta; u = newU; v = newV; @@ -411,24 +420,23 @@ FactorSquareFree::usage = "`FactorSquareFree[poly]` computes the square free factorization of `poly`."; FactorSquareFree[poly_] := - Module[{f = poly, a, b, nb, c, d, i, res, fprime, polyvar}, - If[Length[Variables[f]] != 1, Return[f]]; - polyvar = Variables[f][[1]]; + Module[{f = poly, a, b, nb, c, d, i, res, fprime, polyvar, vars}, + vars = Variables[f]; + If[Length[vars] != 1, Return[f]]; + polyvar = vars[[1]]; If[! PolynomialQ[f, polyvar], Return[f]]; fprime = D[f, polyvar]; a = PolynomialGCD[f, fprime]; res = If[SquareFreeQ[a], a, a // FactorSquareFree]; - nb = f/a // PSimplify; - c = fprime/a // PSimplify; + nb = PolynomialQuotient[f,a,polyvar]; + c = PolynomialQuotient[fprime,a,polyvar]; d = c - D[nb, polyvar]; i = 1; b = nb; - (*Print[{Subscript[a, i-1],Subscript[b, i],Subscript[c, i], - Subscript[d, i]}];*) While[b =!= 1 && i < 20, a = PolynomialGCD[b, d]; - nb = b/a // PSimplify; - c = d/a // PSimplify; + nb = PolynomialQuotient[b,a,polyvar]; + c = PolynomialQuotient[d,a,polyvar]; res = res*If[SquareFreeQ[a], a, a // FactorSquareFree]; i = i + 1; b = nb;