Skip to content

Commit

Permalink
Optionally output LaTeX instead of plain text
Browse files Browse the repository at this point in the history
  • Loading branch information
p-e-w committed Feb 19, 2015
1 parent 82d3681 commit a35bb71
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 16 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Sequencer identifies number sequences. That is, given a list of numbers like
it finds a formula that generates them, in this case

```
a(n) = 2^(n-1) for n >= 1
a(n) = 2^(n-1)
```

Sequencer employs neither a library of sequences nor a limited set of algorithms to find a closed form. Instead, it generates **all** formulas up to a certain size and then checks them against the provided numbers.
Expand Down Expand Up @@ -42,7 +42,7 @@ and provides the continuation
Sequencer is not limited to processing integers but can identify sequences consisting of arbitrary Symja expressions (provided they can be evaluated numerically). For example, invoking the program with the command line `-u 0 1/2 sqrt(3)/2 1` produces

```
a(n) = Sin(1/6*Pi*(n-1)) for n >= 1
a(n) = Sin(1/6*Pi*(n-1))
Continuation: 1/2*3^(1/2), 1/2, 0, -1/2, (-1/2)*3^(1/2), ...
```

Expand Down
51 changes: 42 additions & 9 deletions src/main/scala/com/worldwidemann/sequencer/Sequencer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,25 @@ import scala.collection.mutable.ListBuffer

case class Configuration(maximumComplexity: Int, maximumIdentifications: Int, predictionLength: Int,
recurrenceRelations: Boolean, combinatorialFunctions: Boolean, transcendentalFunctions: Boolean,
numericalTest: Boolean, printProgress: Boolean)
numericalTest: Boolean, printProgress: Boolean, outputLaTeX: Boolean)

case class SequenceIdentification(formula: String, continuation: Seq[String])

class Sequencer(configuration: Configuration) {
def identifySequence(sequence: Seq[String]): Seq[SequenceIdentification] = {
val sequenceNumerical = sequence.map(Utilities.getNumericalValue)
val sequenceSimplified = sequence.map(Simplifier.simplify)
val sequenceNumerical = sequenceSimplified.map(Utilities.getNumericalValue)

val identifications = new ListBuffer[SequenceIdentification]

for (nodes <- 1 to configuration.maximumComplexity) {
new FormulaGenerator(configuration).getFormulas(nodes, formula => {
if (!configuration.numericalTest || Verifier.testFormula(formula, sequenceNumerical)) {
// Sequence matched numerically (or test skipped) => verify symbolically
if (Verifier.verifyFormula(formula, sequence)) {
if (Verifier.verifyFormula(formula, sequenceSimplified)) {
try {
identifications += SequenceIdentification(getFullFormula(formula, sequence),
Predictor.predict(formula, sequence, configuration.predictionLength))
identifications += SequenceIdentification(getFullFormula(formula, sequenceSimplified),
Predictor.predict(formula, sequenceSimplified, configuration.predictionLength))
if (configuration.maximumIdentifications > 0 && identifications.distinct.size >= configuration.maximumIdentifications)
return identifications.distinct.sortBy(_.formula.length)
} catch {
Expand All @@ -58,12 +59,44 @@ class Sequencer(configuration: Configuration) {
// including seed values for recurrence relations
private def getFullFormula(formula: Node, sequence: Seq[String]) = {
val builder = new StringBuilder
var generalTerm = formula.toString

var generalPart = Simplifier.simplify(formula.toString)
if (configuration.outputLaTeX) {
builder.append("\\begin{align}\n")
generalPart = Utilities.getLaTeX(generalPart)
}

val startIndex = Utilities.getStartIndex(formula)
for (index <- 1 to startIndex - 1) {
builder.append("a(").append(index).append(") = ").append(Simplifier.simplify(sequence(index - 1))).append("\n")
generalTerm = generalTerm.replace("(a" + index + ")", "(a((n)-" + index + "))")
if (configuration.outputLaTeX) {
builder.append("a_").append(index).append(" &= ").append(Utilities.getLaTeX(sequence(index - 1))).append(" \\\\")
// Note that we can not rely on "a1" etc. being surrounded by brackets anymore
// as the formula has already been simplified
generalPart = generalPart.replace("a" + index, "a_{n-" + index + "}")
} else {
builder.append("a(").append(index).append(") = ").append(sequence(index - 1))
generalPart = generalPart.replace("a" + index, "a(n-" + index + ")")
}
builder.append("\n")
}

if (configuration.outputLaTeX)
builder.append("a_n &= ")
else
builder.append("a(n) = ")
builder.append(generalPart)

if (startIndex > 1) {
if (configuration.outputLaTeX) {
builder.append("\\quad \\text{for } n\\geq ").append(startIndex)
} else {
builder.append(" for n >= ").append(startIndex)
}
}
builder.append("a(n) = ").append(Simplifier.simplify(generalTerm)).append(" for n >= ").append(startIndex).toString

if (configuration.outputLaTeX)
builder.append("\n\\end{align}")

builder.toString
}
}
18 changes: 14 additions & 4 deletions src/main/scala/com/worldwidemann/sequencer/SequencerRunner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ object SequencerRunner {
opt[Unit]('s', "symbolic") action { (x, c) =>
c.copy(numericalTest = false)
} text ("skip numerical test (symbolic verification only; slows down search)")
opt[Unit]('l', "latex") action { (x, c) =>
c.copy(outputLaTeX = true)
} text ("output LaTeX instead of plain text")
arg[String]("a_1, a_2, ...") unbounded () action { (x, c) =>
sequence += x
c.copy()
Expand All @@ -60,16 +63,23 @@ object SequencerRunner {
} text ("list of numbers to search for (symbolic expressions allowed)")
}

parser.parse(args, Configuration(6, 5, 5, true, true, true, true, true)) match {
parser.parse(args, Configuration(6, 5, 5, true, true, true, true, true, false)) match {
case Some(configuration) => {
println("Searching for formulas for sequence (a(n)) = " + sequence.mkString(", ") + ", ...")
println("Searching for formulas for sequence " +
(if (configuration.outputLaTeX)
"$(a_n)_{n\\geq 1} = " + sequence.map(Utilities.getLaTeX).mkString(", ") + ", \\dots$"
else
"(a(n)) = " + sequence.mkString(", ") + ", ..."))

val time = System.currentTimeMillis

val identifications = new Sequencer(configuration).identifySequence(sequence)
identifications.foreach(identification => {
println("\n" + identification.formula)
println("Continuation: " + identification.continuation.mkString(", ") + ", ...")
println("\n" + (if (configuration.outputLaTeX) "$$" else "") + identification.formula +
(if (configuration.outputLaTeX) "$$" else ""))
println("Continuation: " + (if (configuration.outputLaTeX) "$" else "") +
identification.continuation.mkString(", ") +
(if (configuration.outputLaTeX) ", \\dots$" else ", ..."))
})

if (identifications.isEmpty)
Expand Down
14 changes: 13 additions & 1 deletion src/main/scala/com/worldwidemann/sequencer/Utilities.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@

package com.worldwidemann.sequencer

import java.io.StringWriter

import org.matheclipse.core.eval.EvalEngine
import org.matheclipse.core.eval.EvalUtilities
import org.matheclipse.core.eval.TeXUtilities

object Utilities {
private val evaluator = new EvalUtilities(false, true)
Expand All @@ -27,7 +31,15 @@ object Utilities {
case e: Exception => false
}

// Retrieves the index from which on the generic part of the formula applies
private val texUtilities = new TeXUtilities(new EvalEngine(true), true)

def getLaTeX(expression: String) = {
val writer = new StringWriter
texUtilities.toTeX(expression, writer)
writer.toString
}

// Retrieves the index from which on the general part of the formula applies
// (1 for non-recurrence formulas)
def getStartIndex(formula: Node) = {
val offsets = formula.getTreeNodes
Expand Down

0 comments on commit a35bb71

Please sign in to comment.