SSDS Documentation
A guide to discovering recurring symbolic structures and constructing reusable mathematical abstractions.
Installation
Install the required dependencies and run SSDS from the project environment.
pip install ssds-core==0.1.0
Quickstart
Create an expression bank and pass it to run_ssds().
from Convert import make_bank
from run_ssds import run_ssds
bank = make_bank([
"Newton's Second Law: F = m*a",
"Kinetic Energy: KE = 1/2*m*v**2",
])
results = run_ssds(
bank,
alpha=0.5,
beta=0.5,
verbose=True,
)
Your First Discovery
SSDS validates the expression bank, extracts symbolic and behavioral features, and compares expressions. It then clusters related forms, generalizes recurring structures, and filters candidate results.
How SSDS Works
SSDS processes an expression bank through these stages:
Expression Bank
↓
Structure Extraction
↓
Behavior Analysis
↓
Similarity
↓
Clustering
↓
Anti-Unification
↓
Candidate Filtering
↓
Candidate Analysis
↓
Optional AI Interpretation
Symbolic Structure
SSDS extracts structural features from each expression, including tree topology, operators, nesting, and variable relationships.
Structural similarity can remain high even when numerical behavior differs. Behavioral analysis handles that distinction.
Behavioral Signatures
SSDS samples numerical responses across input ranges to characterize boundedness, monotonicity, periodicity, variance, derivative behavior, integral behavior, and stability. Sampling ranges and parameter values affect the resulting behavior signatures.
Similarity
SSDS combines structural and behavioral similarity using alpha and beta.
results = run_ssds(
bank,
alpha=0.5,
beta=0.5,
)
Increase alpha to emphasize structure. Increase beta to emphasize behavior. Do not assume the values must sum to 1 unless the implementation enforces that constraint.
Generalization
Anti-unification replaces differing subexpressions while preserving shared symbolic structure. The result represents a candidate abstraction; it does not prove independent mathematical significance.
Novelty Detection
SSDS applies novelty_threshold during candidate filtering.
results = run_ssds(
bank,
novelty_threshold=0.35,
)
Candidates can be classified as novel, repeated, or discarded depending on the filtering logic.
Expression Banks
Create expression banks with make_bank(). The function parses equations into SymPy expressions.
from Convert import make_bank
bank = make_bank([
"Newton's Second Law: F = m*a",
"Kinetic Energy: KE = 1/2*m*v**2",
"Momentum: p = m*v",
])
You can also pass a multiline string:
bank = make_bank("""
F = m*a
E = m*v**2/2
p = m*v
""")
Running SSDS
results = run_ssds(
bank,
verbose=True,
)
Override configuration values explicitly when needed:
results = run_ssds(
bank,
alpha=0.5,
beta=0.5,
sim_threshold=0.85,
novelty_threshold=0.35,
verbose=True,
)
AI Interpretation
Alpha feature: AI interpretation is currently in alpha testing and is unreliable. It can produce incorrect, incomplete, or misleading interpretations. Treat its output as an experimental hypothesis, not a validated mathematical or scientific result.
Set ai_enabled=True to enable the optional AI interpretation stage. Use index to select the zero-based expression passed to the AI system.
bank = make_bank([
"Reynolds Number: Re = U*L/nu", # index 0
"Froude Number: Fr = U/sqrt(g*L)", # index 1
"Mach Number: Ma = U/c", # index 2
])
results = run_ssds(
bank,
ai_enabled=True,
index=1,
)
This selects bank[1] for AI interpretation. index does not determine the starting point of symbolic discovery. Set index=None when no specific expression should be selected.
Configuration
Pass configuration values directly to run_ssds(). When a value is omitted, SSDS uses the configured default.
See the run_ssds() reference for the full parameter list and defaults.
Discovery Results
See Result Objects for what run_ssds() returns.
Interpreting SSDS Results
An SSDS run produces several layers of output. The first block describes the candidate structure. The following blocks show how each member expression was normalized, evaluated, and vectorized. The final blocks report mathematical characterization, and, when AI interpretation is enabled, optional LLM processing.
Canonical Example
── novel structure #4 ──────────────────────────────────
[4] v0*v1*v2*φ1*φ2
behavior : exponential
bounded : False boundedness=0.00
periodic : False monotone=False
variance : 1.148e+15
derivative : mean=5.089e+07
integral : mean=1.731e+08
stability : -5.729e+04
sensitivity : 1.051e-06
members : ['v0*v1*v2*v3*v4', '0.5*v0*v1*v2*v3**2']
novelty : 48.2 | closest: sq
Composition: Op5Eq1(Op5Eq1(w, b, -4, 5, 1), z, 4, Op5Eq1(-5, 5, 2, 2.71, 3.14), Op5Eq1(m, t, m, q, -10))
Variables: ['b', 'm', 'q', 't', 'w', 'z']
Numpy expr: ((((w)*(b)*(-4)*(5)*(1)))*(z)*(4)*(((-5)*(5)*(2)*(2.71)*(3.14)))*(((m)*(t)*(m)*(q)*(-10))))
Result: -490141440.00000006
Vectorized: [-3.40376000e-02 -1.30359431e+02 -5.18200415e+03 -5.71571871e+04
-3.40376000e+05]
Composition: Op5Eq2(w, Op5Eq2(5, q, n, -2, p), Op5Eq2(y, 3.14, z, -10, w), 10, p)
Variables: ['n', 'p', 'q', 'w', 'y', 'z']
Numpy expr: ((w)*(((5)*(q)*(n)*(-2)*(p)))*(((y)*(3.14)*(z)*(-10)*(w)))*(10)*(p))
Result: 18086400.0
Vectorized: [3.14000000e-05 3.90837778e-01 2.62924565e+01 4.08642251e+02
3.14000000e+03]
Composition: Op5Eq3(-4, -4, Op5Eq3(c, u, q, -3, u), Op5Eq3(3.14, 10, m, z, -1), Op5Eq3(-3, -3, w, 0.5, w))
Variables: ['c', 'm', 'q', 'u', 'w', 'z']
Numpy expr: ((-4)*(-4)*(((c)*(u)*(q)*(-3)*(u)))*(((3.14)*(10)*(m)*(z)*(-1)))*(((-3)*(-3)*(w)*(0.5)*(w))))
Result: 97666560.00000001
Vectorized: [6.78240000e-05 8.44209601e-01 5.67917060e+01 8.82667263e+02
6.78240000e+03]
Composition: Op5Eq4(Op5Eq4(-1, u, b, z, -10), -3, -4, z, -2)
Variables: ['b', 'u', 'z']
Numpy expr: ((((-1)*(u)*(b)*(z)*(-10)))*(-3)*(-4)*z*(-2))
Result: -4320.0
Vectorized: [-2.40000000e-02 -2.67759375e+00 -2.19615000e+01 -8.65800938e+01
-2.40000000e+02]
Composition: Op5Eq5(Op5Eq5(5, x, a, 5, 3.14), Op5Eq5(1, x, 0.5, 4, 4), Op5Eq5(n, 4, b, c, 3.14), Op5Eq5(-2, x, p, 10, u), -1)
Variables: ['a', 'b', 'c', 'n', 'p', 'u', 'x']
Numpy expr: ((((5)*(x)*(a)*(5)*(3.14)))*(((1)*(x)*(0.5)*(4)*(4)))*(((n)*(4)*(b)*(c)*(3.14)))*(((-2)*(x)*(p)*(10)*(u)))*(-1))
Result: 38958829056.0
Vectorized: [1.57753600e-04 6.38159924e+00 7.26513158e+02 1.59108947e+04
1.57753600e+05]
Composition: Op5Eq6(Op5Eq6(3, 3.14, b, 3, c), -10, Op5Eq6(5, -5, n, -4, 2), Op5Eq6(u, m, 5, u, -5), Op5Eq6(y, z, w, t, 0.5))
Variables: ['b', 'c', 'm', 'n', 't', 'u', 'w', 'y', 'z']
Numpy expr: ((((3)*(3.14)*(b)*(3)*(c)))*(-10)*(((5)*(-5)*(n)*(-4)*(2)))*(((u)*(m)*(5)*(u)*(-5)))*(((y)*(z)*(w)*(t)*(0.5))))
Result: 1538248319999.9998
Vectorized: [7.06500000e-05 9.28850408e+00 1.78953032e+03 5.52241692e+04
7.06500000e+05]
Composition: Op5Eq7(Op5Eq7(3, 3, t, -4, 1), -10, Op5Eq7(-3, q, 2.71, u, 5), 1, 2)
Variables: ['q', 't', 'u']
Numpy expr: ((((3)*(3)*(t)*(-4)*(1)))*(-10)*(((-3)*(q)*(2.71)*(u)*(5)))*(1)*(2))
Result: -175607.99999999997
Vectorized: [ -29.268 -1004.7155625 -4869.4635 -13623.7966875
-29268. ]
Composition: Op5Eq8(z, -10, Op5Eq8(-3, 10, z, -10, 1), t, Op5Eq8(c, q, 2.71, n, 0.5))
Variables: ['c', 'n', 'q', 't', 'z']
Numpy expr: ((z)*(-10)*(((-3)*(10)*(z)*(-10)*(1)))*(t)*(((c)*(q)*(2.71)*(n)*(0.5))))
Result: -2438999.9999999995
Vectorized: [-4.06500000e-03 -4.79027797e+00 -1.12521804e+02 -8.80786734e+02
-4.06500000e+03]
Composition: Op5Eq9(Op5Eq9(-5, t, y, 5, m), -1, -10, Op5Eq9(4, c, z, u, -1), -2)
Variables: ['c', 'm', 't', 'u', 'y', 'z']
Numpy expr: ((((-5)*(t)*(y)*(5)*(m)))*(-1)*(-10)*(((4)*(c)*(z)*(u)*(-1)))*(-2))
Result: -1440000.0
Vectorized: [-2.00000000e-03 -2.35684033e+00 -5.53612813e+01 -4.33351407e+02
-2.00000000e+03]
Composition: Op5Eq10(z, -1, Op5Eq10(w, -5, w, -4, 2), 3.14, Op5Eq10(-2, a, t, b, z))
Variables: ['a', 'b', 't', 'w', 'z']
Numpy expr: ((z)*(-1)*(((w)*(-5)*(w)*(-4)*(2)))*(3.14)*(((-2)*(a)*(t)*(b)*(z))))
Result: 602880.0
Vectorized: [2.51200000e-05 9.62062224e-02 3.82435731e+00 4.21824259e+01
2.51200000e+02]
Concept: discovered 20 invariants across 5 families. 20 invariants expressed as operator laws. Closed under 20 operations. 4 parameter recursion patterns.
Operator definition: v0*v1*v2*φ1*φ2
Parameters: ['v0', 'v1', 'v2', 'φ1', 'φ2']
Families:
- derivative: Differentiation produces scaled/self-similar operator variants.
* Eq(Derivative(Op(v0, v1, v2, φ1, φ2), v0), v1*v2*φ1*φ2)
* Eq(Derivative(Op(v0, v1, v2, φ1, φ2), v1), v0*v2*φ1*φ2)
* Eq(Derivative(Op(v0, v1, v2, φ1, φ2), v2), v0*v1*φ1*φ2)
* Eq(Derivative(Op(v0, v1, v2, φ1, φ2), φ1), v0*v1*v2*φ2)
* Eq(Derivative(Op(v0, v1, v2, φ1, φ2), φ2), v0*v1*v2*φ1)
- second: Family for second invariants.
* Eq(Derivative(Op(v0, v1, v2, φ1, φ2), (v0, 2)), 0)
* Eq(Derivative(Op(v0, v1, v2, φ1, φ2), (v1, 2)), 0)
* Eq(Derivative(Op(v0, v1, v2, φ1, φ2), (v2, 2)), 0)
* Eq(Derivative(Op(v0, v1, v2, φ1, φ2), (φ1, 2)), 0)
* Eq(Derivative(Op(v0, v1, v2, φ1, φ2), (φ2, 2)), 0)
- integral: Family for integral invariants.
* Eq(Integral(Op(v0, v1, v2, φ1, φ2), v0), v0**2*v1*v2*φ1*φ2/2)
* Eq(Integral(Op(v0, v1, v2, φ1, φ2), v1), v0*v1**2*v2*φ1*φ2/2)
* Eq(Integral(Op(v0, v1, v2, φ1, φ2), v2), v0*v1*v2**2*φ1*φ2/2)
* Eq(Integral(Op(v0, v1, v2, φ1, φ2), φ1), v0*v1*v2*φ1**2*φ2/2)
* Eq(Integral(Op(v0, v1, v2, φ1, φ2), φ2), v0*v1*v2*φ1*φ2**2/2)
- composition: Family for composition invariants.
* Eq(Op(v0, v1, v2, φ1, φ2)**2, Op(v0**2, v1**2, v2**2, φ1**2, φ2**2))
- parameter: Family for parameter invariants.
* Eq(Op(v0, v1, v2, φ1 - 1, φ2), v0*v1*v2*φ2*(φ1 - 1))
* Eq(Op(v0, v1, v2, φ1 + 1, φ2), v0*v1*v2*φ2*(φ1 + 1))
* Eq(Op(v0, v1, v2, φ1, φ2 - 1), v0*v1*v2*φ1*(φ2 - 1))
* Eq(Op(v0, v1, v2, φ1, φ2 + 1), v0*v1*v2*φ1*(φ2 + 1))
Invariants (operator form prioritised):
[OP] composition_closure wrt=None [closed]
Eq(Op(v0, v1, v2, φ1, φ2)**2, Op(v0**2, v1**2, v2**2, φ1**2, φ2**2))
[OP] derivative wrt=v0 [closed] (shift: -2)
Eq(Derivative(Op(v0, v1, v2, φ1, φ2), v0), v1*v2*φ1*φ2)
[OP] derivative wrt=v1 [closed] (shift: -2)
Eq(Derivative(Op(v0, v1, v2, φ1, φ2), v1), v0*v2*φ1*φ2)
[OP] derivative wrt=v2 [closed] (shift: -2)
Eq(Derivative(Op(v0, v1, v2, φ1, φ2), v2), v0*v1*φ1*φ2)
[OP] derivative wrt=φ1 [closed] (shift: -2)
Eq(Derivative(Op(v0, v1, v2, φ1, φ2), φ1), v0*v1*v2*φ2)
[OP] derivative wrt=φ2 [closed] (shift: -2)
Eq(Derivative(Op(v0, v1, v2, φ1, φ2), φ2), v0*v1*v2*φ1)
[OP] integral_closure wrt=v0 [closed]
Eq(Integral(Op(v0, v1, v2, φ1, φ2), v0), v0**2*v1*v2*φ1*φ2/2)
[OP] integral_closure wrt=v1 [closed]
Eq(Integral(Op(v0, v1, v2, φ1, φ2), v1), v0*v1**2*v2*φ1*φ2/2)
[OP] integral_closure wrt=v2 [closed]
Eq(Integral(Op(v0, v1, v2, φ1, φ2), v2), v0*v1*v2**2*φ1*φ2/2)
[OP] integral_closure wrt=φ1 [closed]
Eq(Integral(Op(v0, v1, v2, φ1, φ2), φ1), v0*v1*v2*φ1**2*φ2/2)
[OP] integral_closure wrt=φ2 [closed]
Eq(Integral(Op(v0, v1, v2, φ1, φ2), φ2), v0*v1*v2*φ1*φ2**2/2)
[OP] parameter_recursion wrt=φ1 [closed] (shift: -1)
Eq(Op(v0, v1, v2, φ1 - 1, φ2), v0*v1*v2*φ2*(φ1 - 1))
[OP] parameter_recursion wrt=φ1 [closed] (shift: 1)
Eq(Op(v0, v1, v2, φ1 + 1, φ2), v0*v1*v2*φ2*(φ1 + 1))
[OP] parameter_recursion wrt=φ2 [closed] (shift: -1)
Eq(Op(v0, v1, v2, φ1, φ2 - 1), v0*v1*v2*φ1*(φ2 - 1))
[OP] parameter_recursion wrt=φ2 [closed] (shift: 1)
Eq(Op(v0, v1, v2, φ1, φ2 + 1), v0*v1*v2*φ1*(φ2 + 1))
[OP] second_derivative wrt=v0 [closed]
Eq(Derivative(Op(v0, v1, v2, φ1, φ2), (v0, 2)), 0)
[OP] second_derivative wrt=v1 [closed]
Eq(Derivative(Op(v0, v1, v2, φ1, φ2), (v1, 2)), 0)
[OP] second_derivative wrt=v2 [closed]
Eq(Derivative(Op(v0, v1, v2, φ1, φ2), (v2, 2)), 0)
[OP] second_derivative wrt=φ1 [closed]
Eq(Derivative(Op(v0, v1, v2, φ1, φ2), (φ1, 2)), 0)
[OP] second_derivative wrt=φ2 [closed]
Eq(Derivative(Op(v0, v1, v2, φ1, φ2), (φ2, 2)), 0)
Closure properties: ['composition']
| Field | Meaning | Caveat |
|---|---|---|
novel structure #N | Candidate index or rank and its current novelty-filter category. | “Novel” is an internal category, not proof of mathematical novelty. |
behavior | Behavior classification reported for the analyzed sample. | Interpret it using the configured numerical analyzer and sampling inputs. |
bounded / periodic / monotone | Boolean classifications for the corresponding sampled properties; boundedness is the accompanying score. | False means the implemented test did not classify the sample as satisfying the property. It is not a universal mathematical negation. |
variance | Spread of the sampled numerical values. | Scale depends on input ranges, parameters, normalization, and numeric dtype. |
derivative / integral / stability / sensitivity | Reported derivative, integral, stability, and sensitivity metrics from the configured analyzer. | These names do not establish units-aware or formal meanings such as Lyapunov stability. Missing or non-finite calculations require implementation-specific handling. |
members | Expressions associated with the candidate structure and selected cluster. | Compare them with the source bank and compositions to determine whether they are original, canonicalized, transformed, or expanded expressions for that run. |
novelty / closest | Internal novelty ranking/filtering quantity and the closest comparison label or identifier. In this run, sq is an internal label for the nearest previously seen candidate, and its expansion is not shown here. | The score is not a probability and does not establish scientific or mathematical novelty. |
Composition / Op5EqN | Generated structural representation of a member expression; nested wrappers preserve nested structure. | These are internal five-argument structural wrappers, not user-facing mathematical operators or guaranteed executable code. |
Variables | Distinct symbolic variables detected in the composition; constants are excluded. | Ordering follows variable collection in the implementation, and generalized φ parameters may be separate from concrete member variables. |
Numpy expr | Generated arithmetic representation used for numerical evaluation. | It is diagnostic generated output. Unsupported expressions or conversion failures require evaluator-specific handling. |
Result | Scalar from one concrete substitution/evaluation of a composition. | It is not necessarily the final candidate result. Floating-point values should be compared with tolerances. |
Vectorized | Multiple evaluations over the run’s input vector, used as numerical samples for behavioral characterization. | Exact vector, dtype, and NaN/Inf handling are implementation details. For example, -3.40376000e-02 is approximately -0.0340376. |
Concept block | Counts and named families produced by the mathematical characterization stage, including invariants, operator laws, operations, and parameter recursion patterns. | These counts are analyzer output and should not be read as stronger formal claims than the recorded checks. |
Operator definition / Parameters | Parameterized candidate expression and the generated symbols used for derivative, integral, composition, and shift checks. | This is an internal operator model, not automatically a formally defined mathematical operator. |
Families | Categories such as derivative, second, integral, composition, and parameter for tested symbolic relationships. | The family names describe analyzer groupings, not independent mathematical theories. |
Eq(...) | Machine-readable equality between symbolic left and right expressions, such as a derivative relationship. | It is symbolic analyzer output, not prose or a scientific interpretation. |
[closed] / shift | Status and parameter-shift metadata attached to an invariant record. | [closed] reflects the implemented recognition/closure condition; it does not imply closure under every abstract operation. |
Closure properties | Named operations recognized as closure properties, such as composition. | Fewer names than individual closed records is expected because multiple records can belong to one property. |
Output Layers
candidate structure
↓
member expressions
↓
symbolic composition
↓
numeric evaluation
↓
behavioral characterization
↓
novelty/filtering metadata
↓
optional LLM processing
↓
operator characterization
↓
invariant/relationship records
The candidate, members, composition, operator, and invariant equations are symbolic or structural layers. Results and vectorized values are numerical layers. Novelty and category labels are filtering metadata. LLM grouping and concept text are optional AI-generated output. Derivative, integral, second-derivative, parameter-shift, and closure records are mathematical-analysis output produced by the configured analyzer. Structured return values are the appropriate machine-readable source; much of the printed detail is diagnostic.
Use the Python object returned by run_ssds() as the primary result. The exact schema is implementation-defined by the current project version; this documentation does not invent a field layout that is not exposed by the source. Use verbose output for human inspection, and inspect the returned object or its documented result classes for programmatic processing.
run_ssds()
Runs the full SSDS discovery pipeline on an expression bank.
run_ssds(
bank,
api=None,
ai_enabled=False,
alpha=None,
beta=None,
sim_threshold=None,
novelty_threshold=None,
verbose=True,
index=None,
)
| Argument | Purpose |
|---|---|
bank | Expression bank to analyze. |
api | Optional API interface for supported AI functionality. |
ai_enabled | Enable AI interpretation. |
alpha | Structural similarity weight. |
beta | Behavioral similarity weight. |
sim_threshold | Similarity threshold. |
novelty_threshold | Novelty threshold. |
verbose | Enable diagnostic output. |
index | Select the expression for AI interpretation. |
make_bank()
Parse equations, optionally canonicalize variables, and save a JSON expression bank.
from Convert import make_bank
bank = make_bank([
"Newton's Second Law: F = m*a",
"Kinetic Energy: KE = 1/2*m*v**2",
], output_path="physics_bank.json")
Configuration Defaults
Pass configuration values directly to run_ssds(). When omitted, values come from the defaults in config.py.
Result Objects
The pipeline returns a list of behavior-analysis results grouped by category. With verbose output enabled, the reports and discovery stages are printed while the pipeline runs.
Saving Expression Banks
Use output_path to write the converted bank as JSON. When it is omitted, make_bank() writes to bank.json and selects a numbered path when that file already exists.
bank = make_bank(
equations,
output_path="outputs/my_bank.json",
canonicalize=True,
)
Saving Terminal Output
Use Python's contextlib.redirect_stdout to capture SSDS standard output:
from contextlib import redirect_stdout
from run_ssds import run_ssds
with open("ssds_output.txt", "w") as output_file:
with redirect_stdout(output_file):
results = run_ssds(
bank,
verbose=True,
)
This captures writes to stdout. It does not capture stderr.
Basic Example
from Convert import make_bank
from run_ssds import run_ssds
bank = make_bank("""
F = m*a
E = m*v**2/2
""")
results = run_ssds(bank)
Hidden Distance
(Example coming soon)Use this example area for an expression bank where a shared relationship is not obvious from surface notation.
Turbulence
(Example coming soon)Use turbulence expression banks to study recurring structures across turbulence equations and derived quantities.
Feynman Benchmark
(Example coming soon)Benchmark expression banks compare recurring-structure discovery across known scientific equations.
Methodology
SSDS separates symbolic and behavioral analysis before combining them during similarity evaluation. Generalization operates on related expressions identified by the preceding discovery stages. Candidate filtering removes structures that fail the configured criteria.
Metrics
See the run_ssds() reference for what each parameter controls.
Experiments
Use equation banks to evaluate recurring symbolic structures, algebraically different representations, structural transformations, behavioral similarity, candidate abstractions, generalized operators, novelty detection, and invariant behavior. Keep the expression bank and configuration fixed when comparing runs.
Experimental Results
SSDS produces candidate mathematical abstractions and behavior-analysis results. Validate candidates against the source equations before treating them as mathematical results.
Limitations
Some closure, invariant, and parameter-shift analyses can time out or remain incomplete. Behavioral results depend on sampling ranges and parameter values. AI interpretation is an alpha feature and remains unreliable. AI-generated interpretations can be incorrect, incomplete, or misleading. Some candidates may require additional symbolic or numerical validation before classification.