|
10 | 10 |
|
11 | 11 | package com.worldwidemann.sequencer
|
12 | 12 |
|
13 |
| -import scala.collection.mutable.ListBuffer |
| 13 | +import java.util.concurrent.atomic.AtomicInteger |
| 14 | +import scala.collection.mutable.SynchronizedQueue |
| 15 | +import scala.collection.generic.Growable |
14 | 16 |
|
15 | 17 | case class Configuration(maximumComplexity: Int, maximumIdentifications: Int, predictionLength: Int,
|
16 | 18 | recurrenceRelations: Boolean, combinatorialFunctions: Boolean, transcendentalFunctions: Boolean,
|
17 |
| - numericalTest: Boolean, printProgress: Boolean, outputLaTeX: Boolean) |
| 19 | + parallelSearch: Boolean, numericalTest: Boolean, printProgress: Boolean, outputLaTeX: Boolean) |
18 | 20 |
|
19 | 21 | case class SequenceIdentification(formula: String, continuation: Seq[String])
|
20 | 22 |
|
21 | 23 | class Sequencer(configuration: Configuration) {
|
| 24 | + // Note that neither of the two classes keeps a state, |
| 25 | + // so sharing these objects is thread safe |
| 26 | + private val formulaGenerator = new FormulaGenerator(configuration) |
| 27 | + private val treeGenerator = new TreeGenerator(formulaGenerator.getMaxChildren) |
| 28 | + |
22 | 29 | def identifySequence(sequence: Seq[String]): Seq[SequenceIdentification] = {
|
23 | 30 | val sequenceSimplified = sequence.map(Simplifier.simplify)
|
24 | 31 | val sequenceNumerical = sequenceSimplified.map(Utilities.getNumericalValue)
|
25 | 32 |
|
26 |
| - val identifications = new ListBuffer[SequenceIdentification] |
| 33 | + val identifications = new SynchronizedQueue[SequenceIdentification] |
27 | 34 |
|
28 | 35 | for (nodes <- 1 to configuration.maximumComplexity) {
|
29 |
| - new FormulaGenerator(configuration).getFormulas(nodes, formula => { |
30 |
| - // Consider recurrence relations only if they predict at least one element |
31 |
| - // of the sequence without referencing a seed value |
32 |
| - if (sequence.size > 2 * (Utilities.getStartIndex(formula) - 1)) { |
33 |
| - if (!configuration.numericalTest || Verifier.testFormula(formula, sequenceNumerical)) { |
34 |
| - // Sequence matched numerically (or test skipped) => verify symbolically |
35 |
| - if (Verifier.verifyFormula(formula, sequenceSimplified)) { |
36 |
| - try { |
37 |
| - val continuation = Predictor.predict(formula, sequenceSimplified, configuration.predictionLength) |
38 |
| - .map(element => if (configuration.outputLaTeX) Utilities.getLaTeX(element) else element) |
39 |
| - identifications += SequenceIdentification(getFullFormula(formula, sequenceSimplified), continuation) |
40 |
| - if (configuration.maximumIdentifications > 0 && identifications.distinct.size >= configuration.maximumIdentifications) |
41 |
| - return identifications.distinct.sortBy(_.formula.length) |
42 |
| - } catch { |
43 |
| - // Occasionally, simplification or prediction throw an exception although |
44 |
| - // symbolic verification did not. This indicates a bug in Symja |
45 |
| - // and is simply ignored |
46 |
| - case e: Exception => {} |
47 |
| - } |
48 |
| - } |
49 |
| - } |
50 |
| - } |
51 |
| - }, progress => { |
52 |
| - if (configuration.printProgress) |
53 |
| - print("\rTrying formulas with complexity " + nodes + "... " + "%3d".format((progress * 100).round) + " %") |
| 36 | + val trees = treeGenerator.getTrees(nodes) |
| 37 | + val progress = new AtomicInteger(0) |
| 38 | + |
| 39 | + // TreeGenerator generates tree objects that are completely independent of each other, |
| 40 | + // so searching for formulas based on them can be parallelized |
| 41 | + (if (configuration.parallelSearch) trees.par else trees).foreach(tree => { |
| 42 | + identifySequenceTask(tree, sequenceSimplified, sequenceNumerical, identifications) |
| 43 | + |
| 44 | + if (configuration.printProgress && !maximumReached(identifications)) |
| 45 | + // TODO: Concurrency issues with interleaving print statements? |
| 46 | + print("\rTrying formulas with complexity " + nodes + "... " + |
| 47 | + "%3d".format(((progress.incrementAndGet.toDouble / trees.size) * 100).round) + " %") |
54 | 48 | })
|
55 | 49 |
|
| 50 | + if (maximumReached(identifications)) |
| 51 | + return processIdentifications(identifications) |
| 52 | + |
56 | 53 | if (configuration.printProgress)
|
57 | 54 | println
|
58 | 55 | }
|
59 | 56 |
|
60 |
| - identifications.distinct.sortBy(_.formula.length) |
| 57 | + processIdentifications(identifications) |
| 58 | + } |
| 59 | + |
| 60 | + // Parallel execution unit. |
| 61 | + // Finds formulas generating the sequence based on the specified expression tree skeleton |
| 62 | + private def identifySequenceTask(tree: Node, sequence: Seq[String], sequenceNumerical: Seq[Double], |
| 63 | + identifications: Seq[SequenceIdentification] with Growable[SequenceIdentification]) { |
| 64 | + formulaGenerator.getFormulas(tree, formula => { |
| 65 | + // Consider recurrence relations only if they predict at least one element |
| 66 | + // of the sequence without referencing a seed value |
| 67 | + if (sequence.size > 2 * (Utilities.getStartIndex(formula) - 1)) { |
| 68 | + if (!configuration.numericalTest || Verifier.testFormula(formula, sequenceNumerical)) { |
| 69 | + // Sequence matched numerically (or test skipped) => verify symbolically |
| 70 | + if (Verifier.verifyFormula(formula, sequence)) { |
| 71 | + try { |
| 72 | + val continuation = Predictor.predict(formula, sequence, configuration.predictionLength) |
| 73 | + .map(element => if (configuration.outputLaTeX) Utilities.getLaTeX(element) else element) |
| 74 | + identifications += SequenceIdentification(getFullFormula(formula, sequence), continuation) |
| 75 | + } catch { |
| 76 | + // Occasionally, simplification or prediction throw an exception although |
| 77 | + // symbolic verification did not. This indicates a bug in Symja |
| 78 | + // and is simply ignored |
| 79 | + case e: Exception => {} |
| 80 | + } |
| 81 | + } |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + if (maximumReached(identifications)) |
| 86 | + return |
| 87 | + }) |
| 88 | + } |
| 89 | + |
| 90 | + private def maximumReached(identifications: Seq[SequenceIdentification]) = |
| 91 | + configuration.maximumIdentifications > 0 && identifications.distinct.size >= configuration.maximumIdentifications |
| 92 | + |
| 93 | + private def processIdentifications(identifications: Seq[SequenceIdentification]) = { |
| 94 | + // Sort alphabetically before sorting by length to get deterministic ordering independent of search order |
| 95 | + val sortedIdentifications = identifications.distinct.sortBy(_.formula).sortBy(_.formula.length) |
| 96 | + if (configuration.maximumIdentifications > 0) |
| 97 | + // Necessary because the tasks might find additional formulas |
| 98 | + // before they determine that the maximum has been reached and return |
| 99 | + sortedIdentifications.take(configuration.maximumIdentifications) |
| 100 | + else |
| 101 | + sortedIdentifications |
61 | 102 | }
|
62 | 103 |
|
63 | 104 | // Returns the full descriptive string form of the formula,
|
|
0 commit comments