diff --git a/README.md b/README.md index 759e9c8..40fbd40 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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), ... ``` diff --git a/src/main/scala/com/worldwidemann/sequencer/Sequencer.scala b/src/main/scala/com/worldwidemann/sequencer/Sequencer.scala index d18db1e..c99ecd1 100644 --- a/src/main/scala/com/worldwidemann/sequencer/Sequencer.scala +++ b/src/main/scala/com/worldwidemann/sequencer/Sequencer.scala @@ -14,13 +14,14 @@ 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] @@ -28,10 +29,10 @@ class Sequencer(configuration: Configuration) { 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 { @@ -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 } } \ No newline at end of file diff --git a/src/main/scala/com/worldwidemann/sequencer/SequencerRunner.scala b/src/main/scala/com/worldwidemann/sequencer/SequencerRunner.scala index 9f8e8e9..72925fb 100644 --- a/src/main/scala/com/worldwidemann/sequencer/SequencerRunner.scala +++ b/src/main/scala/com/worldwidemann/sequencer/SequencerRunner.scala @@ -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() @@ -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) diff --git a/src/main/scala/com/worldwidemann/sequencer/Utilities.scala b/src/main/scala/com/worldwidemann/sequencer/Utilities.scala index 2b1409e..79a5094 100644 --- a/src/main/scala/com/worldwidemann/sequencer/Utilities.scala +++ b/src/main/scala/com/worldwidemann/sequencer/Utilities.scala @@ -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) @@ -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