diff --git a/examples/test_rubi.m b/examples/test_rubi.m index 4a03486..adc84d7 100644 --- a/examples/test_rubi.m +++ b/examples/test_rubi.m @@ -1,5 +1,7 @@ -testproblems = ReadList["/Users/cwalker32/Code/gocode/src/github.com/corywalker/expreduce/test_rubi/test_rubi.m"]; -testproblems = DeleteCases[testproblems, Null]; +(*testproblems = ReadList["/Users/cwalker32/Code/gocode/src/github.com/corywalker/expreduce/test_rubi/test_rubi.m"];*) +(*testproblems = DeleteCases[testproblems, Null];*) +testproblems = ReadList["/Users/cwalker32/Downloads/test.m"][[1]]; +Print[Length[testproblems]]; testi = 1; diff --git a/expreduce.go b/expreduce.go index 3615140..6966f40 100644 --- a/expreduce.go +++ b/expreduce.go @@ -8,10 +8,13 @@ import ( "log" "os" "runtime/pprof" + "net/http" + _ "net/http/pprof" ) var debug = flag.Bool("debug", false, "Debug mode. No initial definitions.") var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") +var netprofile = flag.Bool("netprofile", false, "Enable live profiling at http://localhost:8080/debug/pprof/") func main() { flag.Parse() @@ -23,6 +26,9 @@ func main() { pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } + if *netprofile { + go http.ListenAndServe(":8080", nil) + } rl, err := readline.NewEx(&readline.Config{ HistoryFile: "/tmp/readline.tmp", diff --git a/expreduce/blank.go b/expreduce/blank.go index f44f09a..01dc307 100644 --- a/expreduce/blank.go +++ b/expreduce/blank.go @@ -56,6 +56,7 @@ func IsBlankTypeCapturing(e Ex, target Ex, head Ex, pm *PDManager, cl *CASLogger toMatch, ispd := pm.patternDefined[sAsSymbol.Name] if !ispd { toMatch = target + pm.LazyMakeMap() pm.patternDefined[sAsSymbol.Name] = target } if !IsSameQ(toMatch, target, cl) { diff --git a/expreduce/builtin_system.go b/expreduce/builtin_system.go index 27c1455..c1db6af 100644 --- a/expreduce/builtin_system.go +++ b/expreduce/builtin_system.go @@ -184,7 +184,7 @@ func applyModuleFn(this *Expression, es *EvalState) (Ex, bool) { es.defined[pl.uniqueName] = Def{ downvalues: []DownValue{ DownValue{ - rule: *NewExpression([]Ex{ + rule: NewExpression([]Ex{ &Symbol{"System`Rule"}, &Symbol{pl.uniqueName}, rhs, @@ -195,6 +195,7 @@ func applyModuleFn(this *Expression, es *EvalState) (Ex, bool) { } else { es.defined[pl.uniqueName] = Def{} } + pm.LazyMakeMap() pm.patternDefined[pl.sym.Name] = &Symbol{pl.uniqueName} } toReturn = ReplacePD(toReturn, es, pm) diff --git a/expreduce/cas_test.go b/expreduce/cas_test.go index 3730f27..682a459 100644 --- a/expreduce/cas_test.go +++ b/expreduce/cas_test.go @@ -110,6 +110,31 @@ func TestLowLevel(t *testing.T) { es := NewEvalState() + lhs := NewExpression([]Ex{ + &Symbol{"System`Power"}, + NewExpression([]Ex{ + &Symbol{"System`Plus"}, + &Symbol{"Global`a"}, + &Symbol{"Global`b"}, + &Symbol{"Global`c"}, + }), + NewInt(0), + }) + rule := NewExpression([]Ex{ + &Symbol{"System`Rule"}, + NewExpression([]Ex{ + &Symbol{"System`Power"}, + NewExpression([]Ex{ + &Symbol{"System`Blank"}, + }), + NewInt(0), + }), + NewInt(99), + }) + for numi := 0; numi < 700000; numi++ { + Replace(lhs, rule, es) + } + // Test basic float functionality var f *Flt = &Flt{big.NewFloat(5.5)} assert.Equal(t, "5.5", f.String()) diff --git a/expreduce/definition.go b/expreduce/definition.go index f6e9315..f646ac5 100644 --- a/expreduce/definition.go +++ b/expreduce/definition.go @@ -3,7 +3,7 @@ package expreduce import "bytes" type DownValue struct { - rule Expression + rule *Expression specificity int } @@ -22,7 +22,7 @@ func CopyDefs(in map[string]Def) map[string]Def { newDef := Def{} for _, dv := range v.downvalues { newDv := DownValue{ - rule: *dv.rule.DeepCopy().(*Expression), + rule: dv.rule.DeepCopy().(*Expression), specificity: dv.specificity, } newDef.downvalues = append(newDef.downvalues, newDv) diff --git a/expreduce/evalstate.go b/expreduce/evalstate.go index db2e5e2..e85b5d5 100644 --- a/expreduce/evalstate.go +++ b/expreduce/evalstate.go @@ -207,6 +207,17 @@ func (this *EvalState) GetDef(name string, lhs Ex) (Ex, bool, *Expression) { if !this.IsDef(name) { return nil, false, nil } + // Special case for checking simple variable definitions like "a = 5". + // TODO: Perhaps split out single var values into the Definition to avoid + // iterating over every one. + if _, lhsIsSym := lhs.(*Symbol); lhsIsSym { + for _, def := range this.defined[name].downvalues { + if _, symDef := def.rule.Parts[1].(*Symbol); symDef { + return def.rule.Parts[2], true, def.rule + } + } + return nil, false, nil + } this.Debugf("Inside GetDef(\"%s\",%s)", name, lhs) for i := range this.defined[name].downvalues { def := this.defined[name].downvalues[i].rule @@ -219,7 +230,7 @@ func (this *EvalState) GetDef(name string, lhs Ex) (Ex, bool, *Expression) { started = time.Now().UnixNano() } - res, replaced := Replace(lhs, &def, this) + res, replaced := Replace(lhs, def, this) if this.isProfiling { elapsed := float64(time.Now().UnixNano()-started) / 1000000000 @@ -228,7 +239,7 @@ func (this *EvalState) GetDef(name string, lhs Ex) (Ex, bool, *Expression) { } if replaced { - return res, true, &def + return res, true, def } } return nil, false, nil @@ -276,7 +287,10 @@ func (this *EvalState) MarkSeen(name string) { // Attempts to compute a specificity metric for a rule. Higher specificity rules // should be tried first. -func ruleSpecificity(lhs Ex, rhs Ex) int { +func ruleSpecificity(lhs Ex, rhs Ex, name string) int { + if name == "Rubi`Int" { + return 100 + } // I define complexity as the length of the Lhs.String() // because it is simple, and it works for most of the common cases. We wish // to attempt f[x_Integer] before we attempt f[x_]. If LHSs map to the same @@ -338,7 +352,7 @@ func (this *EvalState) Define(lhs Ex, rhs Ex) { newDef := Def{ downvalues: []DownValue{ DownValue{ - rule: *NewExpression([]Ex{ + rule: NewExpression([]Ex{ &Symbol{"System`Rule"}, lhs, rhs, }), }, @@ -349,14 +363,14 @@ func (this *EvalState) Define(lhs Ex, rhs Ex) { } // Overwrite identical rules. - for i := range this.defined[name].downvalues { - existingRule := this.defined[name].downvalues[i].rule + for _, dv := range this.defined[name].downvalues { + existingRule := dv.rule existingLhs := existingRule.Parts[1] if IsSameQ(existingLhs, lhs, &this.CASLogger) { existingRhsCond := maskNonConditional(existingRule.Parts[2]) newRhsCond := maskNonConditional(rhs) if IsSameQ(existingRhsCond, newRhsCond, &this.CASLogger) { - this.defined[name].downvalues[i].rule.Parts[2] = rhs + dv.rule.Parts[2] = rhs return } } @@ -365,16 +379,17 @@ func (this *EvalState) Define(lhs Ex, rhs Ex) { // Insert into definitions for name. Maintain order of decreasing // complexity. var tmp = this.defined[name] - newSpecificity := ruleSpecificity(lhs, rhs) - for i := range this.defined[name].downvalues { - if this.defined[name].downvalues[i].specificity == 0 { - this.defined[name].downvalues[i].specificity = ruleSpecificity( - this.defined[name].downvalues[i].rule.Parts[1], - this.defined[name].downvalues[i].rule.Parts[2], + newSpecificity := ruleSpecificity(lhs, rhs, name) + for i, dv := range this.defined[name].downvalues { + if dv.specificity == 0 { + dv.specificity = ruleSpecificity( + dv.rule.Parts[1], + dv.rule.Parts[2], + name, ) } - if this.defined[name].downvalues[i].specificity < newSpecificity { - newRule := *NewExpression([]Ex{&Symbol{"System`Rule"}, lhs, rhs}) + if dv.specificity < newSpecificity { + newRule := NewExpression([]Ex{&Symbol{"System`Rule"}, lhs, rhs}) tmp.downvalues = append( tmp.downvalues[:i], append( @@ -389,7 +404,7 @@ func (this *EvalState) Define(lhs Ex, rhs Ex) { return } } - tmp.downvalues = append(tmp.downvalues, DownValue{rule: *NewExpression([]Ex{&Symbol{"System`Rule"}, lhs, rhs})}) + tmp.downvalues = append(tmp.downvalues, DownValue{rule: NewExpression([]Ex{&Symbol{"System`Rule"}, lhs, rhs})}) this.defined[name] = tmp } diff --git a/expreduce/ex_expression.go b/expreduce/ex_expression.go index abf4f05..b3b2f0c 100644 --- a/expreduce/ex_expression.go +++ b/expreduce/ex_expression.go @@ -31,7 +31,7 @@ func HeadAssertion(ex Ex, head string) (*Expression, bool) { } } } - return NewEmptyExpression(), false + return nil, false } func headExAssertion(ex Ex, head Ex, cl *CASLogger) (*Expression, bool) { @@ -41,7 +41,7 @@ func headExAssertion(ex Ex, head Ex, cl *CASLogger) (*Expression, bool) { return expr, true } } - return NewEmptyExpression(), false + return nil, false } func OperatorAssertion(ex Ex, opHead string) (*Expression, *Expression, bool) { @@ -57,7 +57,7 @@ func OperatorAssertion(ex Ex, opHead string) (*Expression, *Expression, bool) { } } } - return NewEmptyExpression(), NewEmptyExpression(), false + return nil, nil, false } func tryReturnValue(e Ex) (Ex, bool) { diff --git a/expreduce/matchq.go b/expreduce/matchq.go index a010efe..0393f92 100644 --- a/expreduce/matchq.go +++ b/expreduce/matchq.go @@ -11,34 +11,42 @@ type matchIter interface { } type dummyMatchIter struct { - isMatchQ bool pm *PDManager - isDone bool } func (this *dummyMatchIter) next() (bool, *PDManager, bool) { - return this.isMatchQ, this.pm, this.isDone + return true, this.pm, true } func NewMatchIter(a Ex, b Ex, pm *PDManager, es *EvalState) (matchIter, bool) { - // Special case for Except - except, isExcept := HeadAssertion(b, "System`Except") - if isExcept { + patternHead := "" + patExpr, patIsExpr := b.(*Expression) + if patIsExpr { + sym, isSym := patExpr.Parts[0].(*Symbol) + if isSym { + patternHead = sym.Name + } + } + if patternHead == "System`Except" { + except := patExpr if len(except.Parts) == 2 { - matchq, _ := IsMatchQ(a, except.Parts[1], EmptyPD(), es) - return &dummyMatchIter{!matchq, pm, true}, true + matchq, _ := IsMatchQ(a, except.Parts[1], pm, es) + if !matchq { + return &dummyMatchIter{pm}, true + } + return nil, false } else if len(except.Parts) == 3 { - matchq, _ := IsMatchQ(a, except.Parts[1], EmptyPD(), es) + matchq, _ := IsMatchQ(a, except.Parts[1], pm, es) if !matchq { matchqb, newPm := IsMatchQ(a, except.Parts[2], pm, es) - return &dummyMatchIter{matchqb, newPm, true}, true + if matchqb { + return &dummyMatchIter{newPm}, true + } } - return &dummyMatchIter{false, pm, true}, true + return nil, false } - } - // Special case for Alternatives - alts, isAlts := HeadAssertion(b, "System`Alternatives") - if isAlts { + } else if patternHead == "System`Alternatives" { + alts := patExpr for _, alt := range alts.Parts[1:] { // I recently changed the third argument from EmptyPD() to pm // because MatchQ[{a, b}, {a_, k | a_}] was returning True, causing @@ -46,16 +54,14 @@ func NewMatchIter(a Ex, b Ex, pm *PDManager, es *EvalState) (matchIter, bool) { // similar changes to the other pattern clauses. matchq, newPD := IsMatchQ(a, alt, pm, es) if matchq { - return &dummyMatchIter{matchq, newPD, true}, true + return &dummyMatchIter{newPD}, true } } - return &dummyMatchIter{false, pm, true}, true - } - // Special case for PatternTest - patternTest, isPT := HeadAssertion(b, "System`PatternTest") - if isPT { + return nil, false + } else if patternHead == "System`PatternTest" { + patternTest := patExpr if len(patternTest.Parts) == 3 { - matchq, newPD := IsMatchQ(a, patternTest.Parts[1], EmptyPD(), es) + matchq, newPD := IsMatchQ(a, patternTest.Parts[1], pm, es) if matchq { // Some Q functions are very simple and occur very often. For // some of these, skip the Eval() call and return the boolean @@ -68,9 +74,9 @@ func NewMatchIter(a Ex, b Ex, pm *PDManager, es *EvalState) (matchIter, bool) { } if qFunction != nil { if qFunction(a) { - return &dummyMatchIter{true, newPD, true}, true + return &dummyMatchIter{newPD}, true } else { - return &dummyMatchIter{false, pm, true}, true + return nil, false } } } @@ -85,18 +91,16 @@ func NewMatchIter(a Ex, b Ex, pm *PDManager, es *EvalState) (matchIter, bool) { resSymbol, resIsSymbol := res.(*Symbol) if resIsSymbol { if resSymbol.Name == "System`True" { - return &dummyMatchIter{true, newPD, true}, true + return &dummyMatchIter{newPD}, true } } } - return &dummyMatchIter{false, pm, true}, true + return nil, false } - } - // Special case for Condition - condition, isCond := HeadAssertion(b, "System`Condition") - if isCond { + } else if patternHead == "System`Condition" { + condition := patExpr if len(condition.Parts) == 3 { - mi, cont := NewMatchIter(a, condition.Parts[1], EmptyPD(), es) + mi, cont := NewMatchIter(a, condition.Parts[1], pm, es) for cont { matchq, newPD, done := mi.next() cont = !done @@ -107,26 +111,22 @@ func NewMatchIter(a Ex, b Ex, pm *PDManager, es *EvalState) (matchIter, bool) { resSymbol, resIsSymbol := res.(*Symbol) if resIsSymbol { if resSymbol.Name == "System`True" { - return &dummyMatchIter{true, newPD, true}, true + return &dummyMatchIter{newPD}, true } } } } } - } - // Special case for Optional - optional, isOptional := HeadAssertion(b, "System`Optional") - if isOptional { + } else if patternHead == "System`Optional" { + optional := patExpr if len(optional.Parts) == 2 { matchq, newPD := IsMatchQ(a, optional.Parts[1], pm, es) if matchq { - return &dummyMatchIter{matchq, newPD, true}, true + return &dummyMatchIter{newPD}, true } } - } - // Special case for HoldPattern - holdPattern, isHoldPattern := HeadAssertion(b, "System`HoldPattern") - if isHoldPattern { + } else if patternHead == "System`HoldPattern" { + holdPattern := patExpr if len(holdPattern.Parts) == 2 { return NewMatchIter(a, holdPattern.Parts[1], pm, es) } @@ -174,18 +174,24 @@ func NewMatchIter(a Ex, b Ex, pm *PDManager, es *EvalState) (matchIter, bool) { if IsBlankTypeOnly(b) { ibtc, ibtcNewPDs := IsBlankTypeCapturing(b, a, headEx, pm, &es.CASLogger) if ibtc { - return &dummyMatchIter{true, ibtcNewPDs, true}, true + return &dummyMatchIter{ibtcNewPDs}, true } - return &dummyMatchIter{false, EmptyPD(), true}, true + return nil, false } // Handle special case for matching Rational[a_Integer, b_Integer] if aIsRational && bIsExpression { matchq, newPm := isMatchQRational(aRational, bExpression, pm, es) - return &dummyMatchIter{matchq, newPm, true}, true + if matchq { + return &dummyMatchIter{newPm}, true + } + return nil, false } else if aIsExpression && bIsRational { matchq, newPm := isMatchQRational(bRational, aExpression, pm, es) - return &dummyMatchIter{matchq, newPm, true}, true + if matchq { + return &dummyMatchIter{newPm}, true + } + return nil, false } canAssumeHead := false @@ -230,9 +236,12 @@ func NewMatchIter(a Ex, b Ex, pm *PDManager, es *EvalState) (matchIter, bool) { if !assumingHead { if aIsFlt || aIsInteger || aIsString || aIsSymbol || aIsRational { - return &dummyMatchIter{IsSameQ(a, b, &es.CASLogger), EmptyPD(), true}, true + if IsSameQ(a, b, &es.CASLogger) { + return &dummyMatchIter{nil}, true + } + return nil, false } else if !(aIsExpression && bIsExpression) { - return &dummyMatchIter{false, EmptyPD(), true}, true + return nil, false } } @@ -253,7 +262,7 @@ func NewMatchIter(a Ex, b Ex, pm *PDManager, es *EvalState) (matchIter, bool) { isFlat := attrs.Flat && !forceOrdered nomi, ok := NewSequenceMatchIter(aExpression.Parts[startI:], bExpression.Parts[startI:], isOrderless, isFlat, sequenceHead, pm, es) if !ok { - return &dummyMatchIter{false, pm, true}, true + return nil, false } return nomi, true } @@ -345,7 +354,7 @@ func (ami *assignedMatchIter) next() bool { } for i := len(toAddReversed) - 1; i >= 0; i-- { updatedPm := p.pm - if toAddReversed[i].Len() > 0 { + if toAddReversed[i] != nil && toAddReversed[i].Len() > 0 { if len(toAddReversed) > 1 { updatedPm = CopyPD(p.pm) } diff --git a/expreduce/pdmanager.go b/expreduce/pdmanager.go index 06b66ca..bdee0c4 100644 --- a/expreduce/pdmanager.go +++ b/expreduce/pdmanager.go @@ -11,19 +11,31 @@ type PDManager struct { } func EmptyPD() *PDManager { - return &PDManager{make(map[string]Ex)} + return &PDManager{nil} } func CopyPD(orig *PDManager) (dest *PDManager) { dest = EmptyPD() // We do not care that this iterates in a random order. - for k, v := range (*orig).patternDefined { - (*dest).patternDefined[k] = v.DeepCopy() + if (*orig).Len() > 0 { + dest.LazyMakeMap() + for k, v := range (*orig).patternDefined { + (*dest).patternDefined[k] = v.DeepCopy() + } } return } +func (this *PDManager) LazyMakeMap() { + if this.patternDefined == nil { + this.patternDefined = make(map[string]Ex) + } +} + func (this *PDManager) Update(toAdd *PDManager) { + if (*toAdd).Len() > 0 { + this.LazyMakeMap() + } // We do not care that this iterates in a random order. for k, v := range (*toAdd).patternDefined { (*this).patternDefined[k] = v @@ -31,6 +43,9 @@ func (this *PDManager) Update(toAdd *PDManager) { } func (this *PDManager) Len() int { + if this.patternDefined == nil { + return 0 + } return len(this.patternDefined) } @@ -94,11 +109,14 @@ func DefineSequence(lhs parsedForm, sequence []Ex, pm *PDManager, sequenceHead s attemptDefine = NewExpression(append([]Ex{head}, sequence...)) } - defined, ispd := pm.patternDefined[lhs.patSym.Name] - if ispd && !IsSameQ(defined, attemptDefine, &es.CASLogger) { - es.Debugf("patterns do not match! continuing.") - return false + if pm.patternDefined != nil { + defined, ispd := pm.patternDefined[lhs.patSym.Name] + if ispd && !IsSameQ(defined, attemptDefine, &es.CASLogger) { + es.Debugf("patterns do not match! continuing.") + return false + } } + pm.LazyMakeMap() pm.patternDefined[lhs.patSym.Name] = attemptDefine } return true diff --git a/expreduce/replace.go b/expreduce/replace.go index ce5bb88..1a48f4c 100644 --- a/expreduce/replace.go +++ b/expreduce/replace.go @@ -62,6 +62,9 @@ func ReplacePDInternal(e Ex, pm *PDManager) Ex { } func ReplacePD(this Ex, es *EvalState, pm *PDManager) Ex { + if pm == nil { + return this + } containsAny := false for k := range pm.patternDefined { if ContainsSymbol(this, k) { diff --git a/expreduce/resources.go b/expreduce/resources.go index 14b7fd1..48a3471 100644 --- a/expreduce/resources.go +++ b/expreduce/resources.go @@ -108,7 +108,7 @@ func resourcesArithmeticM() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "resources/arithmetic.m", size: 9505, mode: os.FileMode(420), modTime: time.Unix(1502176362, 0)} + info := bindataFileInfo{name: "resources/arithmetic.m", size: 9505, mode: os.FileMode(420), modTime: time.Unix(1504586851, 0)} a := &asset{bytes: bytes, info: info} return a, nil }