Skip to content

Commit df72a98

Browse files
committed
Initial commit
0 parents  commit df72a98

File tree

14 files changed

+723
-0
lines changed

14 files changed

+723
-0
lines changed

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
bin
2+
project/project
3+
project/target
4+
target
5+
.settings
6+
.classpath
7+
.project
8+
.cache
9+

README.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# *Sequencer* – purely algorithmic number sequence identification
2+
3+
Sequencer identifies number sequences. That is, given a list of numbers like
4+
5+
```
6+
(a(n)) = 1, 2, 4, 8, 16, 32, ...
7+
```
8+
9+
it finds a formula that generates them, in this case
10+
11+
```
12+
a(n) = 2^(n-1) for n >= 1
13+
```
14+
15+
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.
16+
17+
For verification, the system uses a hybrid approach of a fast numerical checker followed by a symbolic verifier powered by the [Symja](https://bitbucket.org/axelclk/symja_android_library/wiki/Home) computer algebra system. Coupled with some tricks and heuristics designed to quickly generate potentially interesting formulas, Sequencer can identify sequences with very complex closed forms in a matter of seconds when run on commodity hardware.
18+
19+
Sequencer is capable of finding closed forms that are beyond any existing system like [OEIS](http://oeis.org/), [Superseeker](http://oeis.org/ol.html) and [Wolfram Alpha](http://www.wolframalpha.com/). It is particularly strong where recurrence relations or unusual combinations of functions are involved. For example, none of the services mentioned above can currently make sense of the sequence
20+
21+
```
22+
(a(n)) = 1, 1, 1, 3, 5, 15, 43, 273, ...
23+
```
24+
25+
while Sequencer reveals that it satisfies the recurrence relation
26+
27+
```
28+
a(1) = 1
29+
a(2) = 1
30+
a(3) = 1
31+
a(n) = a(n-2)^2+a(n-1)+a(n-3) for n >= 4
32+
```
33+
34+
and provides the continuation
35+
36+
```
37+
2137, 76709, 4643751, 5888916569, 21570312343279, ...
38+
```
39+
40+
## Installation and usage
41+
42+
Sequencer requires [Java](https://www.java.com) to run. Download the latest Sequencer JAR from the [releases page](https://github.com/p-e-w/sequencer/releases) and execute it from a terminal with the numbers to be matched as arguments, i.e.
43+
44+
```
45+
java -jar sequencer.jar 1 2 3 4 5
46+
```
47+
48+
Running the program without arguments displays a help text explaining the various command line parameters that can be used to fine-tune how searches are performed.
49+
50+
## Development
51+
52+
Sequencer is written in Scala. To compile Sequencer from source, you need [Git](http://www.git-scm.com/), a [JDK](http://www.oracle.com/technetwork/java/index.html), the [Scala compiler](http://www.scala-lang.org/), and [sbt](http://www.scala-sbt.org/). Once all of these are installed and on your `PATH`, you are ready to build and run Sequencer:
53+
54+
```
55+
git clone https://github.com/p-e-w/sequencer.git
56+
cd sequencer
57+
sbt run
58+
```
59+
60+
The release JAR can then be created using
61+
62+
```
63+
sbt assembly
64+
```
65+
66+
and will be located at `target/scala-X.XX/sequencer.jar`.
67+
68+
To develop Sequencer using a Scala IDE, have sbt generate project files with a plugin like [sbteclipse](https://github.com/typesafehub/sbteclipse) or [sbt-idea](https://github.com/mpeltonen/sbt-idea).
69+
70+
## Credits
71+
72+
Besides its runtime and compilation environment (Java, Scala and sbt), Sequencer depends on the [Symja](https://bitbucket.org/axelclk/symja_android_library/wiki/Home) computer algebra system and the [scopt](https://github.com/scopt/scopt) command line parser. The standalone release JARs are built using the excellent [sbt-assembly](https://github.com/sbt/sbt-assembly) plugin.
73+
74+
## License
75+
76+
Copyright © 2015 Philipp Emanuel Weidmann (<[email protected]>)
77+
78+
Released under the terms of the [GNU General Public License, Version 3](https://gnu.org/licenses/gpl.html)

build.sbt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
libraryDependencies ++= Seq(
2+
"com.github.scopt" %% "scopt" % "3.3.0",
3+
"log4j" % "log4j" % "1.2.17",
4+
"axelclk" % "symja" % "2015-02-08" from "https://bitbucket.org/axelclk/symja_android_library/downloads/symja-2015-02-08.jar"
5+
)
6+
7+
resolvers += Resolver.sonatypeRepo("public")
8+
9+
assemblyJarName in assembly := "sequencer.jar"
10+

project/assembly.sbt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0")
2+
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Sequencer – purely algorithmic number sequence identification
3+
*
4+
* Copyright (c) 2015 Philipp Emanuel Weidmann <[email protected]>
5+
*
6+
* Nemo vir est qui mundum non reddat meliorem.
7+
*
8+
* Released under the terms of the GNU General Public License, Version 3
9+
*/
10+
11+
package com.worldwidemann.sequencer
12+
13+
trait Expression {
14+
def evaluate(arguments: Seq[Double], index: Int, sequence: Seq[Double]): Double
15+
def render(arguments: Seq[String]): String
16+
}
17+
18+
case object EmptyExpression extends Expression {
19+
def evaluate(arguments: Seq[Double], index: Int, sequence: Seq[Double]) = 0
20+
def render(arguments: Seq[String]) = if (arguments.isEmpty) "O" else "O(" + arguments.mkString(",") + ")"
21+
}
22+
23+
case class IndexFunction(symbol: String, f: Int => Double) extends Expression {
24+
def evaluate(arguments: Seq[Double], index: Int, sequence: Seq[Double]) = f(index)
25+
def render(arguments: Seq[String]) = "(" + symbol + ")"
26+
}
27+
28+
case class PreviousElement(offset: Int) extends Expression {
29+
def evaluate(arguments: Seq[Double], index: Int, sequence: Seq[Double]) =
30+
if (index > offset)
31+
sequence(index - offset - 1)
32+
else throw new IllegalArgumentException("Reference to nonexisting element")
33+
def render(arguments: Seq[String]) = "(a" + offset + ")"
34+
}
35+
36+
case class Number(symbol: String, value: Double) extends Expression {
37+
def evaluate(arguments: Seq[Double], index: Int, sequence: Seq[Double]) = value
38+
def render(arguments: Seq[String]) = "(" + symbol + ")"
39+
}
40+
41+
case class UnaryPrefixOperator(symbol: String, f: Double => Double) extends Expression {
42+
def evaluate(arguments: Seq[Double], index: Int, sequence: Seq[Double]) = f(arguments(0))
43+
def render(arguments: Seq[String]) = symbol + "(" + arguments(0) + ")"
44+
}
45+
46+
case class BinaryInfixOperator(symbol: String, f: (Double, Double) => Double) extends Expression {
47+
def evaluate(arguments: Seq[Double], index: Int, sequence: Seq[Double]) = f(arguments(0), arguments(1))
48+
def render(arguments: Seq[String]) = "(" + arguments(0) + ")" + symbol + "(" + arguments(1) + ")"
49+
}
50+
51+
case class BinaryPrefixOperator(symbol: String, f: (Double, Double) => Double) extends Expression {
52+
def evaluate(arguments: Seq[Double], index: Int, sequence: Seq[Double]) = f(arguments(0), arguments(1))
53+
def render(arguments: Seq[String]) = symbol + "(" + arguments(0) + "," + arguments(1) + ")"
54+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
* Sequencer – purely algorithmic number sequence identification
3+
*
4+
* Copyright (c) 2015 Philipp Emanuel Weidmann <[email protected]>
5+
*
6+
* Nemo vir est qui mundum non reddat meliorem.
7+
*
8+
* Released under the terms of the GNU General Public License, Version 3
9+
*/
10+
11+
package com.worldwidemann.sequencer
12+
13+
import org.apache.commons.math3.util.CombinatoricsUtils
14+
15+
class FormulaGenerator(configuration: Configuration) {
16+
// Note: Rarely used expressions are disabled to increase search speed
17+
private val expressions = List(
18+
// Atomic expressions
19+
List(
20+
Number("1", 1),
21+
Number("2", 2),
22+
Number("3", 3),
23+
Number("4", 4),
24+
Number("5", 5),
25+
//Number("6", 6),
26+
//Number("7", 7),
27+
//Number("8", 8),
28+
//Number("9", 9),
29+
Number("10", 10),
30+
// Common functions of the index variable are hardcoded as atomic expressions
31+
// in order to speed up searches for formulas that contain them
32+
IndexFunction("(n)", n => n),
33+
IndexFunction("(n)+1", n => n + 1),
34+
IndexFunction("(n)-1", n => n - 1),
35+
IndexFunction("2*(n)", n => 2 * n),
36+
IndexFunction("(n)^2", n => n * n),
37+
IndexFunction("2^(n)", n => math.pow(2, n))) ++
38+
(if (configuration.recurrenceRelations) List(
39+
PreviousElement(1),
40+
PreviousElement(2),
41+
PreviousElement(3))
42+
else List()) ++
43+
(if (configuration.transcendentalFunctions) List(
44+
Number("Pi", math.Pi) //,
45+
//Number("E", math.E)
46+
)
47+
else List()),
48+
49+
// Unary operators
50+
List(
51+
UnaryPrefixOperator("-", x => -x),
52+
//UnaryPrefixOperator("Sqrt", x => math.sqrt(x)),
53+
UnaryPrefixOperator("Abs", x => math.abs(x)),
54+
//UnaryPrefixOperator("Sign", x => math.signum(x)),
55+
UnaryPrefixOperator("Floor", x => math.floor(x)) //,
56+
//UnaryPrefixOperator("Ceiling", x => math.ceil(x)),
57+
//UnaryPrefixOperator("Round", x => math.round(x))
58+
) ++
59+
(if (configuration.combinatorialFunctions) List(
60+
UnaryPrefixOperator("Factorial", x => {
61+
if (x.isWhole && 0 <= x && x <= 10)
62+
CombinatoricsUtils.factorialDouble(x.toInt)
63+
else throw new IllegalArgumentException("Argument " + x + " is invalid for the Factorial function")
64+
}))
65+
else List()) ++
66+
(if (configuration.transcendentalFunctions) List(
67+
//UnaryPrefixOperator("Exp", x => math.exp(x)),
68+
//UnaryPrefixOperator("Log", x => math.log(x)),
69+
UnaryPrefixOperator("Sin", x => math.sin(x)),
70+
UnaryPrefixOperator("Cos", x => math.cos(x)) //,
71+
//UnaryPrefixOperator("Tan", x => math.tan(x))
72+
)
73+
else List()),
74+
75+
// Binary operators
76+
List(
77+
BinaryInfixOperator("+", (x, y) => x + y),
78+
BinaryInfixOperator("-", (x, y) => x - y),
79+
BinaryInfixOperator("*", (x, y) => x * y),
80+
BinaryInfixOperator("/", (x, y) => x / y),
81+
BinaryInfixOperator("^", (x, y) => math.pow(x, y)) //,
82+
//BinaryPrefixOperator("Max", (x, y) => math.max(x, y)),
83+
//BinaryPrefixOperator("Min", (x, y) => math.min(x, y))
84+
) ++
85+
(if (configuration.combinatorialFunctions) List(
86+
BinaryPrefixOperator("Binomial", (x, y) => {
87+
if (x.isWhole && 0 <= x && x <= 10 && y.isWhole && 0 <= y && y <= x)
88+
CombinatoricsUtils.binomialCoefficientDouble(x.toInt, y.toInt)
89+
else throw new IllegalArgumentException("Arguments " + x + ", " + y + " are invalid for the Binomial function")
90+
}))
91+
else List()))
92+
93+
private def rollNodeExpression(node: Node) = {
94+
val allowedExpressions = expressions(node.children.size)
95+
node.expressionIndex = if (node.expressionIndex + 1 >= allowedExpressions.size) 0 else node.expressionIndex + 1
96+
node.expression = allowedExpressions(node.expressionIndex)
97+
// Indicate whether we rolled over or not
98+
node.expressionIndex == 0
99+
}
100+
101+
// The callback pattern is much more efficient than returning a collection
102+
// because the number of formulas grows extremely fast with the number of nodes
103+
def getFormulas(nodes: Int, formulaCallback: Node => Unit, progressCallback: Double => Unit) = {
104+
val trees = new TreeGenerator(expressions.size - 1).getTrees(nodes)
105+
106+
for ((tree, i) <- trees.view.zipWithIndex) {
107+
val nodes = tree.getTreeNodes
108+
// Set initial expressions
109+
nodes.foreach(node => rollNodeExpression(node))
110+
111+
var lastRolled = false
112+
while (!lastRolled) {
113+
// Pass the formula in its current form.
114+
// The object is mutable, so any processing has to happen in the callback
115+
formulaCallback(tree)
116+
117+
var j = 0
118+
var rolled = true
119+
while (j < nodes.size && rolled) {
120+
rolled = rollNodeExpression(nodes(j))
121+
j += 1
122+
if (j == nodes.size && rolled)
123+
lastRolled = true
124+
}
125+
}
126+
127+
progressCallback((i + 1).toDouble / trees.size)
128+
}
129+
}
130+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Sequencer – purely algorithmic number sequence identification
3+
*
4+
* Copyright (c) 2015 Philipp Emanuel Weidmann <[email protected]>
5+
*
6+
* Nemo vir est qui mundum non reddat meliorem.
7+
*
8+
* Released under the terms of the GNU General Public License, Version 3
9+
*/
10+
11+
package com.worldwidemann.sequencer
12+
13+
import scala.collection.mutable.ListBuffer
14+
15+
class Node {
16+
var expression: Expression = EmptyExpression
17+
// Ensures that the initial roll selects the first valid expression (see FormulaGenerator)
18+
var expressionIndex = -1
19+
20+
val children = new ListBuffer[Node]
21+
22+
def getCopy: Node = {
23+
val copy = new Node
24+
copy.expression = expression
25+
copy.expressionIndex = expressionIndex
26+
children.foreach(child => {
27+
copy.children += child.getCopy
28+
})
29+
copy
30+
}
31+
32+
// Returns all nodes in the tree
33+
def getTreeNodes: Seq[Node] = {
34+
val treeNodes = new ListBuffer[Node]
35+
treeNodes += this
36+
children.foreach(child => {
37+
treeNodes ++= child.getTreeNodes
38+
})
39+
treeNodes
40+
}
41+
42+
def evaluate(index: Int, sequence: Seq[Double]): Double =
43+
expression.evaluate(children.map(child => child.evaluate(index, sequence)), index, sequence)
44+
45+
override def toString = expression.render(children.map(child => child.toString))
46+
47+
override def equals(that: Any): Boolean = {
48+
that match {
49+
// Consider two nodes equal if their associated trees are structurally equal
50+
case that: Node => that.isInstanceOf[Node] && that.toString == toString
51+
case _ => false
52+
}
53+
}
54+
55+
override def hashCode: Int = toString.hashCode
56+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Sequencer – purely algorithmic number sequence identification
3+
*
4+
* Copyright (c) 2015 Philipp Emanuel Weidmann <[email protected]>
5+
*
6+
* Nemo vir est qui mundum non reddat meliorem.
7+
*
8+
* Released under the terms of the GNU General Public License, Version 3
9+
*/
10+
11+
package com.worldwidemann.sequencer
12+
13+
import scala.collection.mutable.ListBuffer
14+
15+
object Predictor {
16+
def predict(formula: Node, sequence: Seq[Double], elements: Int) = {
17+
val sequenceNew = new ListBuffer[Double]
18+
sequenceNew ++= sequence
19+
val startIndex = Utilities.getStartIndex(formula)
20+
21+
(sequence.size + 1 to sequence.size + elements).map(index => {
22+
var formulaNew = formula.toString.replace("(n)", "(" + index + ")")
23+
// Substitute values of previous elements
24+
for (offset <- 1 to startIndex - 1)
25+
formulaNew = formulaNew.replace("(a" + offset + ")", Utilities.getSymbolicForm(sequenceNew(index - offset - 1)))
26+
val result = Utilities.evaluateSymja(formulaNew)
27+
28+
if (!Utilities.isDouble(result))
29+
throw new RuntimeException("Unable to predict sequence for formula " + formulaNew.toString)
30+
31+
val newElement = result.toDouble
32+
sequenceNew += newElement
33+
newElement
34+
})
35+
}
36+
}

0 commit comments

Comments
 (0)