Skip to content

Commit

Permalink
Merge pull request #191 from corywalker/corywalker
Browse files Browse the repository at this point in the history
Corywalker
  • Loading branch information
corywalker committed Nov 26, 2018
2 parents e689a9e + c13088f commit 87b27ec
Show file tree
Hide file tree
Showing 18 changed files with 236 additions and 42 deletions.
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -13,6 +13,10 @@ Now that the kernel understands the product rule, when it later encounters a pat

The term rewriting system and pattern matching engine is fairly advanced. The computer algebra system at this stage is extremely limited, but simple calculus and algebraic manipulation is certainly supported (see examples below). If you are looking for a more mature computer algebra system, please consider using Mathematica (proprietary) or Mathics (open source, Sympy-backed).

![Jupyter screenshot](/images/jupyter_screenshot.png)

(This screenshot demonstrates the Jupyter notebook interface for Expreduce. This Jupyter extension can be found [here](https://github.com/mmatera/iwolfram).)

# Install and run

[DOWNLOAD HERE](https://github.com/corywalker/expreduce/releases/latest)
Expand Down
6 changes: 3 additions & 3 deletions expreduce.go
Expand Up @@ -24,7 +24,7 @@ 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/")
var scriptfile = flag.String("script", "", "script `file` to read from")
var initfile = flag.String("initfile", "", "A script to run on initialization.")
var loadRubi = flag.Bool("loadrubi", true, "Load the Rubi definitions for integral support on startup.")
var preloadRubi = flag.Bool("preloadrubi", false, "Preload the Rubi definitions for integral support on startup.")

func main() {
flag.Parse()
Expand All @@ -44,8 +44,8 @@ func main() {
fmt.Printf("Welcome to Expreduce!\n\n")

es := expreduce.NewEvalState()
if *loadRubi {
fmt.Println("Loading Rubi snapshot for integral support. Disable with -loadrubi=false.")
if *preloadRubi {
fmt.Println("Pre-loading Rubi snapshot for integral support. Disable with -preloadrubi=false.")
es.Eval(atoms.E(atoms.S("LoadRubiBundledSnapshot")))
fmt.Println("Done loading Rubi snapshot.")
fmt.Print("\n")
Expand Down
31 changes: 15 additions & 16 deletions expreduce/atoms/ex_complex.go
Expand Up @@ -14,28 +14,27 @@ type Complex struct {
needsEval bool
}

func (cmplx *Complex) StringForm(p expreduceapi.ToStringParams) string {
if p.Form == "FullForm" {
return fmt.Sprintf("Complex[%v, %v]", cmplx.Re, cmplx.Im)
}
iString := "I"
if p.Form == "TeXForm" {
iString = "i"
}
func (cmplx *Complex) AsExpr() expreduceapi.Ex {
iSym := S("I")
reInt, reIsInt := cmplx.Re.(*Integer)
imInt, imIsInt := cmplx.Im.(*Integer)
if reIsInt && reInt.Val.Sign() == 0 {
if imIsInt && imInt.Val.Int64() == 1 {
return iString
}
if imIsInt && imInt.Val.Int64() == -1 {
return fmt.Sprintf("(-%v)", iString)
return iSym
}
p.PreviousHead = "System`Times"
return fmt.Sprintf("(%v*%v)", cmplx.Im.StringForm(p), iString)
return E(S("Times"), cmplx.Im, iSym)
}
if imIsInt && imInt.Val.Int64() == 1 {
return E(S("Plus"), cmplx.Re, iSym)
}
return E(S("Plus"), cmplx.Re, E(S("Times"), cmplx.Im, iSym))
}

func (cmplx *Complex) StringForm(p expreduceapi.ToStringParams) string {
if p.Form == "FullForm" {
return fmt.Sprintf("Complex[%v, %v]", cmplx.Re, cmplx.Im)
}
p.PreviousHead = "System`Plus"
return fmt.Sprintf("(%v + %v*%v)", cmplx.Re.StringForm(p), cmplx.Im.StringForm(p), iString)
return cmplx.AsExpr().StringForm(p)
}

func (cmplx *Complex) IsEqual(other expreduceapi.Ex) string {
Expand Down
9 changes: 0 additions & 9 deletions expreduce/atoms/ex_integer.go
Expand Up @@ -5,7 +5,6 @@ import (
"hash/fnv"
"math/big"

"github.com/corywalker/expreduce/expreduce/parser/parens"
"github.com/corywalker/expreduce/pkg/expreduceapi"
)

Expand All @@ -20,14 +19,6 @@ type Integer struct {
}*/

func (thisInt *Integer) StringForm(params expreduceapi.ToStringParams) string {
if thisInt.Val.Cmp(big.NewInt(0)) < 0 {
if parens.NeedsParens("System`Times", params.PreviousHead) {
if params.Form == "TeXForm" {
return fmt.Sprintf("{(%d)}", thisInt.Val)
}
return fmt.Sprintf("(%d)", thisInt.Val)
}
}
return fmt.Sprintf("%d", thisInt.Val)
}

Expand Down
3 changes: 3 additions & 0 deletions expreduce/atoms/ex_symbol.go
Expand Up @@ -21,6 +21,9 @@ func formatSymName(name string, params expreduceapi.ToStringParams) string {
if name == "E" {
return "e"
}
if name == "I" {
return "i"
}
if name == "Pi" {
return "\\pi"
}
Expand Down
130 changes: 124 additions & 6 deletions expreduce/builtin_arithmetic.go
@@ -1,11 +1,14 @@
package expreduce

import (
"bytes"
"fmt"
"math/big"
"strings"

"github.com/corywalker/expreduce/expreduce/atoms"
"github.com/corywalker/expreduce/expreduce/iterspec"
"github.com/corywalker/expreduce/expreduce/parser/parens"
"github.com/corywalker/expreduce/pkg/expreduceapi"
)

Expand Down Expand Up @@ -113,12 +116,74 @@ func collectTerms(e expreduceapi.ExpressionInterface) expreduceapi.ExpressionInt
return collected
}

func splitFrac(ex expreduceapi.Ex) (num expreduceapi.Ex, den expreduceapi.Ex) {
asPow, isPow := atoms.HeadAssertion(ex, "System`Power")
if isPow {
if asPow.Len() != 2 {
return ex, nil
}
powInt, powIsInt := asPow.GetPart(2).(*atoms.Integer)
if !powIsInt {
return ex, nil
}
if powInt.Val.Int64() == -1 {
return nil, asPow.GetPart(1)
}
}
asRat, isRat := ex.(*atoms.Rational)
if isRat {
if asRat.Num.Int64() == 1 {
return nil, atoms.NewInteger(asRat.Den)
}
return atoms.NewInteger(asRat.Num), atoms.NewInteger(asRat.Den)
}
return ex, nil
}

func getArithmeticDefinitions() (defs []Definition) {
defs = append(defs, Definition{
Name: "Plus",
Default: "0",
toString: func(this expreduceapi.ExpressionInterface, params expreduceapi.ToStringParams) (bool, string) {
return toStringInfix(this.GetParts()[1:], " + ", "System`Plus", params)
toString: func(this expreduceapi.ExpressionInterface, p expreduceapi.ToStringParams) (bool, string) {
thisHead := "System`Plus"
parts := this.GetParts()[1:]
if p.Form != "InputForm" && p.Form != "OutputForm" && p.Form != "TeXForm" {
return false, ""
}
if len(parts) < 2 {
return false, ""
}
addParens := parens.NeedsParens(thisHead, p.PreviousHead)
var buffer bytes.Buffer
if addParens {
if p.Form == "TeXForm" {
buffer.WriteString("{\\left(")
} else {
buffer.WriteString("(")
}
}
nextParams := p
nextParams.PreviousHead = thisHead
for i := 0; i < len(parts); i++ {
toWrite := parts[i].StringForm(nextParams)
if i != 0 {
if toWrite[0] == '-' {
buffer.WriteString(" - ")
toWrite = toWrite[1:]
} else {
buffer.WriteString(" + ")
}
}
buffer.WriteString(toWrite)
}
if addParens {
if p.Form == "TeXForm" {
buffer.WriteString("\\right)}")
} else {
buffer.WriteString(")")
}
}
return true, buffer.String()
},
legacyEvalFn: func(this expreduceapi.ExpressionInterface, es expreduceapi.EvalStateInterface) expreduceapi.Ex {
// Calls without argument receive identity values
Expand Down Expand Up @@ -174,11 +239,64 @@ func getArithmeticDefinitions() (defs []Definition) {
if params.Form == "TeXForm" {
delim = " "
}
ok, res := toStringInfix(this.GetParts()[1:], delim, "System`Times", params)
if ok && strings.HasPrefix(res, "(-1)"+delim) {
return ok, "-" + res[5:]

timesParts := atoms.E(atoms.S("Times"))
for _, part := range this.GetParts()[1:] {
partAsComplex, partIsComplex := part.(*atoms.Complex)
if partIsComplex {
asExpr := partAsComplex.AsExpr()
cmplxTimes, cmplxIsTimes := atoms.HeadAssertion(asExpr, "System`Times")
if cmplxIsTimes {
for _, cmplxPart := range cmplxTimes.GetParts()[1:] {
timesParts.AppendEx(cmplxPart)
}
continue
}
}
timesParts.AppendEx(part)
}

num := atoms.E(atoms.S("Times"))
den := atoms.E(atoms.S("Times"))
for _, part := range timesParts.GetParts()[1:] {
numPart, denPart := splitFrac(part)
if numPart != nil {
num.AppendEx(numPart)
}
if denPart != nil {
den.AppendEx(denPart)
}
}
if den.Len() > 0 {
numOk, numStr := toStringInfix(num.GetParts()[1:], delim, "System`Times", params)
if num.Len() == 1 {
numOk, numStr = true, num.GetPart(1).StringForm(params)
}
denOk, denStr := toStringInfix(den.GetParts()[1:], delim, "System`Times", params)
if den.Len() == 1 {
denOk, denStr = true, den.GetPart(1).StringForm(params)
}
if !numOk || !denOk {
return false, ""
}
prefix := ""
if strings.HasPrefix(numStr, "-1"+delim) {
prefix = "-"
numStr = numStr[3:]
}
if params.Form == "TeXForm" {
return true, fmt.Sprintf("%v\\frac{%v}{%v}", prefix, numStr, denStr)
}
return true, fmt.Sprintf("%v(%v)/(%v)", prefix, numStr, denStr)
}
ok, res := toStringInfix(num.GetParts()[1:], delim, "System`Times", params)
if !ok {
return false, ""
}
if strings.HasPrefix(res, "-1"+delim) {
return true, "-" + res[3:]
}
return ok, res
return true, res
},
legacyEvalFn: func(this expreduceapi.ExpressionInterface, es expreduceapi.EvalStateInterface) expreduceapi.Ex {
// Calls without argument receive identity values
Expand Down
1 change: 1 addition & 0 deletions expreduce/builtin_calculus.go
Expand Up @@ -2,6 +2,7 @@ package expreduce

func getCalculusDefinitions() (defs []Definition) {
defs = append(defs, Definition{Name: "D"})
defs = append(defs, Definition{Name: "Grad"})
defs = append(defs, Definition{Name: "Integrate"})
return
}
25 changes: 24 additions & 1 deletion expreduce/builtin_list.go
Expand Up @@ -43,6 +43,28 @@ func toStringList(this expreduceapi.ExpressionInterface, params expreduceapi.ToS
return true, buffer.String()
}

func toStringPart(this expreduceapi.ExpressionInterface, params expreduceapi.ToStringParams) (bool, string) {
if params.Form == "FullForm" {
return false, ""
}
subParams := params
subParams.PreviousHead = "<TOPLEVEL>"
var buffer bytes.Buffer
for i, e := range this.GetParts()[1:] {
if i == 0 {
buffer.WriteString(e.StringForm(params))
buffer.WriteString("[[")
} else {
buffer.WriteString(e.StringForm(subParams))
if i != len(this.GetParts()[1:])-1 {
buffer.WriteString(",")
}
}
}
buffer.WriteString("]]")
return true, buffer.String()
}

func memberQ(components []expreduceapi.Ex, item expreduceapi.Ex, es expreduceapi.EvalStateInterface) bool {
for _, part := range components {
if matchq, _ := matcher.IsMatchQ(part, item, matcher.EmptyPD(), es); matchq {
Expand Down Expand Up @@ -496,7 +518,8 @@ func getListDefinitions() (defs []Definition) {
},
})
defs = append(defs, Definition{
Name: "Part",
Name: "Part",
toString: toStringPart,
legacyEvalFn: func(this expreduceapi.ExpressionInterface, es expreduceapi.EvalStateInterface) expreduceapi.Ex {
if len(this.GetParts()) == 1 {
return this
Expand Down
2 changes: 2 additions & 0 deletions expreduce/builtin_trig.go
Expand Up @@ -63,5 +63,7 @@ func getTrigDefinitions() (defs []Definition) {
Name: "TrigToExp",
OmitDocumentation: true,
})
defs = append(defs, Definition{Name: "Degree"})
defs = append(defs, Definition{Name: "RotationMatrix"})
return
}
1 change: 1 addition & 0 deletions expreduce/evalstate.go
Expand Up @@ -183,6 +183,7 @@ func (es *EvalState) Init(loadAllDefs bool) {
es.MarkSeen("System`EllipticF")
es.MarkSeen("System`ProductLog")
es.MarkSeen("System`FresnelS")
es.MarkSeen("System`Gamma")

es.MarkSeen("System`Cosh")
es.MarkSeen("System`Sinh")
Expand Down
1 change: 1 addition & 0 deletions expreduce/parser/interp.go
Expand Up @@ -480,6 +480,7 @@ func ReplaceSyms(in string) string {
in = strings.Replace(in, "\\[Omega]", "ω", -1)
in = strings.Replace(in, "\\[CapitalOmega]", "Ω", -1)
in = strings.Replace(in, "\\[Alpha]", "α", -1)
in = strings.Replace(in, "\\[Theta]", "θ", -1)
return in
}

Expand Down
4 changes: 2 additions & 2 deletions expreduce/resources.go
Git LFS file not shown
9 changes: 6 additions & 3 deletions expreduce/resources/arithmetic.m
Expand Up @@ -21,10 +21,10 @@
EStringTest["(1.*(a + b))", "(a + b)/1."],
EStringTest["(2*(a + b))", "2*(a + b)"],
EStringTest["(a*(b + c))", "a*(b + c)"],
EStringTest["(-a + -b)", "-1*(a + b)"],
EStringTest["(-a + -b)", "-(a + b)"],
EStringTest["(-a - b)", "-1*(a + b)"],
EStringTest["(-a - b)", "-(a + b)"],
EStringTest["((-1.)*(a + b))", "-1.*(a + b)"],
EStringTest["(-a + -b)", "(a + b)/-1"],
EStringTest["(-a - b)", "(a + b)/-1"],
EStringTest["((-1.)*(a + b))", "(a + b)/-1."],

(*Test that we do not delete all the addends*)
Expand Down Expand Up @@ -213,6 +213,9 @@
ESameTest[I/(2 Sqrt[3] a^2), (0+1/6*I)*3^(1/2)*a^(-2)],
(* Test wouldntBeLessThanNegOne. *)
ESameTest[(1/3)*3^(-1/2), (1/3)*3^(-1/2)],

ESameTest[Sqrt[2/\[Pi]], Sqrt[2]*Sqrt[1/Pi]],
ESameTest[Sqrt[3/(2 \[Pi])], Sqrt[3/2]*Sqrt[1/Pi]],
], EKnownFailures[
ESameTest[-2^(1/3), (-2)*2^(-2/3)],
ESameTest[-2^(1+a), (-2)*2^(a)],
Expand Down

0 comments on commit 87b27ec

Please sign in to comment.