Skip to content

CDS Cookbook

A problem-oriented recipe book. Each recipe solves one concrete task with copy-pasteable code that runs against the real CDS API — no toy stubs. The recipes are grouped by domain, and every snippet is pure-Python: no NumPy, no pandas, no extra installs unless the recipe says so.

Looking for a guided walkthrough instead? See Tour of Numerical Methods. For the public surface of any module, see the API Reference.


Table of contents


Core data models

CDS carries structured research artifacts in Domain, Hypothesis, and HypothesisStatus. These travel through every higher-level module.

Encode a falsifiable Hypothesis

A Hypothesis needs an id, a statement, a domain, and a research_question. Testable consequences go in predictions; confidence is clamped to [0.0, 1.0].

from cds.core.models import Domain, Hypothesis, HypothesisStatus

h = Hypothesis(
    id="H-001",
    statement="Increasing reactor temperature from 410K to 415K raises yield by >5%.",
    domain=Domain.CHEMISTRY,
    research_question="How does temperature affect yield?",
    assumptions=["reaction rate is Arrhenius in this range", "no side reactions"],
    predictions=["yield_delta > 0.05"],
    confidence=0.7,
    status=HypothesisStatus.TESTABLE,
)
print(h.predictions)          # the testable consequences
print(h.to_markdown())        # a structured write-up

Serialize a Hypothesis to JSON

import json
print(json.dumps(h.to_dict(), indent=2))  # enums -> strings, ready to store

Math utilities

Solve a linear system A·x = b

from cds.math_utils import solve_linear

# 2x +  y = 5
#  x + 3y = 10
A = [[2.0, 1.0], [1.0, 3.0]]
b = [5.0, 10.0]
print(solve_linear(A, b))  # [1.0, 3.0]

Largest eigenvalue via power iteration

from cds.math_utils import power_iteration

M = [[4.0, 1.0], [1.0, 3.0]]
eigval, eigvec = power_iteration(M, max_iter=200)
print(eigval)  # ~4.618 (the dominant eigenvalue)

Numerical derivative and integral

from cds.math_utils import derivative, integral

# d/dx of x^3 at x=2 is 12
print(derivative(lambda x: x ** 3, 2.0))      # ~12.0
# integral of x^2 from 0..3 is 9
print(integral(lambda x: x ** 2, 0.0, 3.0))   # ~9.0

Orthonormalize a basis (Gram-Schmidt)

from cds.math_utils import gram_schmidt

basis = [[1.0, 1.0, 0.0], [0.0, 1.0, 1.0]]
print(gram_schmidt(basis))  # two orthonormal vectors

Probability

Evaluate and compare distributions

from cds.probability import gaussian_pdf, exponential_pdf

# Probability density of a Normal(0,1) at z=1.0
print(gaussian_pdf(1.0, mu=0.0, sigma=1.0))   # ~0.2420
# Exponential(lambda=2) density at t=0.5  (``lam`` is the rate parameter)
print(exponential_pdf(0.5, lam=2.0))          # ~0.7358

PMFs for count data

from cds.probability import binomial_pmf, poisson_pmf

# 3 successes out of 10 fair-coin flips
print(binomial_pmf(3, n=10, p=0.5))           # ~0.1172
# A call center with mean 4 calls/hour seeing exactly 6
print(poisson_pmf(6, lam=4.0))                # ~0.1042

Uniform sampling for a quick simulation

from cds.probability import uniform_sample

# 5 draws between 0 and 10
print(uniform_sample(0.0, 10.0, n=5))

Scientific formulas

Look up a physical constant and chain a formula

from cds.scientific import get_constant, photon_energy, de_broglie_wavelength

h = get_constant("h")          # Planck constant
# Energy of a 500nm photon: E = h*f, f = c/lambda
c = get_constant("c")
E = photon_energy(h * c / 500e-9)
print(f"{E:.3e} J")
print(de_broglie_wavelength(9.109e-31, 1e6))  # electron at 1e6 m/s

Gravity, escape velocity, and black-hole radius

from cds.scientific import gravitational_force, escape_velocity, schwarzschild_radius

F = gravitational_force(5.972e24, 7.348e22, 3.844e8)   # Earth–Moon pull
v = escape_velocity(5.972e24, 6.371e6)                 # Earth escape v ~11186 m/s
rs = schwarzschild_radius(5.972e24)                    # Earth as a BH ~8.87mm
print(F, v, rs)

Graph algorithms

CDS graphs use integer vertices (a dense array model, optimized for algorithms). Build one with Graph(n_vertices=..., directed=...).

Shortest path on a weighted graph

from cds.graph import Graph, dijkstra

# 5 nodes (0..4). Dijkstra returns a distance map and a predecessor map.
g = Graph(n_vertices=5, directed=False)
for u, v, w in [(0, 1, 4), (0, 2, 2), (1, 2, 1), (1, 3, 5), (2, 4, 10), (3, 4, 2)]:
    g.add_edge(u, v, weight=w)

dists, prev = dijkstra(g, 0)
print(dists[4])  # cost to reach node 4 from node 0

Minimum spanning tree and cycle detection

from cds.graph import Graph, kruskal_mst, has_cycle

g = Graph(n_vertices=4, directed=False)
for u, v, w in [(0, 1, 1), (1, 2, 2), (0, 2, 3), (2, 3, 5)]:
    g.add_edge(u, v, weight=w)

mst_edges, total = kruskal_mst(g)
print(mst_edges)         # the cheapest tree connecting all vertices
print(total)             # MST total weight
print(has_cycle(g))      # True (the 0-2 edge closes a triangle)

Topological order of a dependency graph

from cds.graph import Graph, topological_sort

# 0=compile, 1=test, 2=package, 3=deploy
dag = Graph(n_vertices=4, directed=True)
dag.add_edge(0, 1, weight=1)  # compile -> test
dag.add_edge(1, 2, weight=1)  # test -> package
dag.add_edge(2, 3, weight=1)  # package -> deploy
print(topological_sort(dag))  # [0, 1, 2, 3] — build order, sources first

Signal processing

Pull a frequency out of a noisy signal

import math
from cds.signals import power_spectrum

# A 50Hz sine sampled at 1000Hz for 0.2s, plus a slower drift.
n = 200
signal = [math.sin(2 * math.pi * 50 * i / 1000) + 0.3 * i / n for i in range(n)]
freqs = power_spectrum(signal)
peak = max(range(len(freqs)), key=lambda k: freqs[k])
print(peak)  # bin near 50Hz dominates

Fast FFT (radix-2) on a power-of-two window

import math
from cds.signals import fft_radix2

x = [math.sin(2 * math.pi * 3 * k / 64) for k in range(64)]  # exactly 3 cycles
spec = fft_radix2(x)
print(abs(spec[3]))  # the 3rd bin carries almost all the energy

Design and apply a Butterworth low-pass

butter_lowpass takes the filter order and a normalized cutoff (in (0, 1) relative to Nyquist); apply_filter runs it over a signal.

from cds.signals import butter_lowpass, apply_filter

coeffs = butter_lowpass(order=4, cutoff=0.2)
smoothed = apply_filter([1, 2, 5, 3, 8, 6, 9, 4], coeffs)
print([round(v, 3) for v in smoothed])  # high-frequency jitter attenuated

Statistics

Descriptive stats and correlation

from cds.stats import mean, stdev, correlation

a = [12.5, 14.3, 11.8, 15.1, 13.7]
b = [3.1, 4.0, 2.9, 4.4, 3.8]
print(mean(a), stdev(a))        # 13.48, ~1.31
print(correlation(a, b))        # strongly positive

Fit a line and read off the slope

from cds.stats import linear_regression

x = [1, 2, 3, 4, 5]
y = [2.1, 3.9, 6.2, 7.8, 10.1]
reg = linear_regression(x, y)
print(f"y = {reg.slope:.3f}x + {reg.intercept:.3f}  (r²={reg.r_squared:.3f})")

One-sample and two-sample t-tests

from cds.stats import one_sample_ttest, two_sample_ttest

sample = [4.8, 5.1, 4.9, 5.0, 5.2]
r1 = one_sample_ttest(sample, popmean=5.0)
print(r1.p_value)              # is the mean different from 5.0?

group_a = [22, 25, 19, 24, 23]
group_b = [28, 30, 27, 31, 29]
r2 = two_sample_ttest(group_a, group_b)
print(r2.p_value)              # do the two groups differ?

Chi-square goodness-of-fit and effect size

from cds.stats import chi_square_gof, cohens_d

# Is a die fair? observed counts vs uniform expected
observed = [8, 9, 12, 7, 11, 13]
gof = chi_square_gof(observed, expected=[10] * 6)
print(gof.p_value)             # large => consistent with fair

print(cohens_d([1, 2, 3, 4], [4, 5, 6, 7]))  # standardized mean gap

Time-series

Smooth, difference, and decompose

import math
from cds.stats import moving_average, difference, seasonal_decompose

series = [10 + 0.5 * i + 3 * math.sin(i / 2) for i in range(48)]
print(moving_average(series, window=4)[:4])     # local smoothing
stationary = difference(series)                 # remove the linear trend
trend, seasonal, residual = seasonal_decompose(series, period=12)

Test for stationarity (KPSS) and autocorrelation (Ljung-Box)

from cds.stats import kpss_statistic, ljung_box, autocorrelation_function

kpss = kpss_statistic(series)
print(kpss.statistic, kpss.p_value)             # low p => reject stationarity

lb = ljung_box(series, lags=10)
print(lb.p_value)                               # are values autocorrelated?

acfs = autocorrelation_function(series, max_lag=12)
print([round(a, 3) for a in acfs])

Forecast with exponential smoothing

from cds.stats import exponential_smoothing

forecast = exponential_smoothing(series, alpha=0.3)
print([round(f, 2) for f in forecast[-5:]])     # smoothed recent level

Monte Carlo

Estimate π by dart-throwing

estimate_pi returns an MCResult with .estimate, .samples, and .std_error (the Monte Carlo standard error).

from cds.montecarlo import estimate_pi

result = estimate_pi(n_samples=50_000)
print(result.estimate, result.std_error)  # ~3.14, with an error bar

Integrate a 1-D function by random sampling

mc_integrate is single-variable: pass f, the bounds [a, b], and a sample count.

from cds.montecarlo import mc_integrate

# Integral of sin(x) from 0..pi is 2.0.
r = mc_integrate(math.sin, 0.0, math.pi, n_samples=200_000)
print(r.estimate, r.std_error)  # ~2.0, with a Monte Carlo error estimate

Simulate a 2-D random walk

from cds.montecarlo import random_walk_2d

path = random_walk_2d(steps=1000)   # list of (x, y) positions
print(len(path))                    # 1001 points (start + 1000 steps)

Numerical integration

The 1-D rules (trapezoid, simpson, gaussian_quadrature) return a plain float. The higher-accuracy rules (romberg, adaptive_simpson) return a QuadratureResult carrying .value and an .error_estimate.

Trapezoid vs Simpson vs Gauss-Legendre on a smooth curve

import math
from cds.numerical_integration import trapezoid, simpson, gaussian_quadrature

f = lambda x: math.exp(-x ** 2)        # integral 0..1 ~0.746824
print(trapezoid(f, 0.0, 1.0, n=100))             # float
print(simpson(f, 0.0, 1.0, n=100))               # float
print(gaussian_quadrature(f, 0.0, 1.0, n=5))     # float, high accuracy w/ few pts

Adaptive Simpson where the integrand is spiky

from cds.numerical_integration import adaptive_simpson

# A narrow Gaussian peak: adaptive refinement concentrates effort near it.
f = lambda x: math.exp(-((x - 0.5) ** 2) / 0.001)
r = adaptive_simpson(f, 0.0, 1.0, tol=1e-8)      # QuadratureResult
print(r.value, r.error_estimate)

Integrate over a 2-D rectangle

The 2-D rules also return a plain float.

from cds.numerical_integration import simpson_2d, gaussian_quadrature_2d

# Volume of a Gaussian bump over the unit square.
g = lambda x, y: math.exp(-(x ** 2 + y ** 2))
print(simpson_2d(g, 0.0, 1.0, 0.0, 1.0, nx=20, ny=20))
print(gaussian_quadrature_2d(g, 0.0, 1.0, 0.0, 1.0, n=4))

Differential equations

CDS ODE solvers take the signature f(t, y) -> y', plus t0, y0, t_end, and a step size dt.

Solve an ODE with RK4

import math
from cds.diffeq import rk4

# dy/dt = -y, y(0)=1  =>  y(t) = e^-t, so y(2) = e^-2 ≈ 0.1353
decay = lambda t, y: -y
sol = rk4(decay, t0=0.0, y0=1.0, t_end=2.0, dt=0.01)
print(sol.y[-1])           # ~0.1353
print(len(sol.t))          # number of time steps

Adaptive stepping with RK45

from cds.diffeq import rk45

# Logistic growth — adaptive RK45 takes bigger steps where it can.
logistic = lambda t, y: 0.5 * y * (1 - y / 10)
sol = rk45(logistic, t0=0.0, y0=0.5, t_end=20.0, rtol=1e-6)
print(sol.y[-1])           # settles near the carrying capacity 10

A coupled system (predator–prey)

solve_system returns (times, states) — a list of time points and a list of state vectors.

from cds.diffeq import solve_system

# Lotka–Volterra: dx/dt = ax - bxy, dy/dt = dxy - cy
def lotka(t, state):
    x, y = state
    return [1.1 * x - 0.4 * x * y, 0.1 * x * y - 0.4 * y]

ts, ys = solve_system(lotka, t0=0.0, y0=[10.0, 5.0], t_end=40.0, dt=0.05)
print(len(ys))             # two trajectories oscillating out of phase

Optimization

Minimize a scalar function

from cds.optimization import gradient_descent

# Minimum of (x-3)^2 is at x=3
r = gradient_descent(lambda x: (x - 3) ** 2, x0=0.0, lr=0.1)
print(r.x, r.value, r.converged)   # 3.0, 0.0, True

Minimize a multivariate quadratic

For the vector overload, the objective takes a single list argument (the parameter vector), not separate coordinates.

from cds.optimization import gradient_descent

# Rosenbrock-ish bowl: f(x,y) = (x-1)^2 + 100*(y-x^2)^2, written as f(v)
f = lambda v: (v[0] - 1) ** 2 + 100 * (v[1] - v[0] ** 2) ** 2
r = gradient_descent(f, x0=[0.0, 0.0], lr=0.001, max_iter=50000)
print(r.x)   # approaches [1.0, 1.0]

Find a root with Newton's method

import math
from cds.optimization import newton_method

# Root of cos(x) near x=1 is pi/2
root = newton_method(math.cos, x0=1.0)
print(root.x, math.pi / 2)         # ~1.5708

Adam on a noisy objective

from cds.optimization import adam

r = adam(lambda x: (x - 2) ** 2, x0=5.0, lr=0.1, max_iter=1000)
print(r.x)   # ~2.0; Adam's momentum handles the flat gradient well

Symbolic modeling

Build, differentiate, and export an expression

from cds.modeling import Variable, Sin, Exp

x = Variable("x")
f = Sin(x) * Exp(x)          # sin(x)·e^x
df = f.diff("x")             # symbolic derivative
print(f.to_str(), df.to_str())
print(f.to_latex())          # LaTeX for papers / docs
print(df.evaluate({"x": 0.0}))   # 1.0 (= e^0 (sin0 + cos0))

Group equations into a model and take a Jacobian

from cds.modeling import MathModel, Variable, Constant

t, a = Variable("t"), Variable("a")
m = MathModel(name="kinematics", parameters={"a": 9.81},
              variables=["t", "v0", "x0"])
v0, x0 = Variable("v0"), Variable("x0")
m.add_equation("velocity", v0 + a * t)
m.add_equation("position", x0 + v0 * t + Constant(0.5) * a * t * t)
print(m.evaluate({"t": 2.0, "v0": 0.0, "x0": 0.0}))
print(m.jacobian("t"))   # symbolic partials w.r.t. time

Solve and fit a model

from cds.modeling import Variable, Constant, MathModel, solve_equation, fit_parameters

x = Variable("x")
root = solve_equation(x ** 2 - Constant(2.0), variable="x", x0=1.0)
print(root.x)             # sqrt(2) ~1.4142

# Recover a velocity v from position observations p = v*t.
fit_model = MathModel(name="linear", parameters={"v": 0.0}, variables=["t"])
fit_model.add_equation("position", Variable("v") * Variable("t"))
observed = [({"t": float(i)}, 3.0 * i) for i in range(5)]
fit = fit_parameters(fit_model, observed=observed,
                     parameter_names=["v"], x0=[0.1], target_label="position")
print(fit.parameters["v"])   # ~3.0

Machine learning

Train a small MLP to fit a curve

from cds.ml import MLP, Layer

net = MLP([
    Layer(input_size=1, output_size=8, activation="relu"),
    Layer(input_size=8, output_size=1, activation="identity"),
])
X = [[i / 10] for i in range(-20, 21)]               # inputs in [-2, 2]
y = [[xi[0] ** 2] for xi in X]                        # learn y = x^2
info = net.train(X, y, epochs=500, lr=0.01)
print(info["final_loss"], info["converged"])
print(net.predict([1.5]))                             # close to 2.25

Inspect what each layer learned

for i, layer in enumerate(net.layers):
    print(f"layer {i}: {len(layer.weights)} neurons, activation={layer.activation}")

NLP

Train a BPE tokenizer and encode text

train_bpe takes a single corpus string and returns a ready BPETokenizer (equipped with encode / decode).

from cds.nlp import train_bpe

corpus = "the quick brown fox the lazy dog a quick fox jumps"
tok = train_bpe(corpus, vocab_size=80, min_frequency=1)
ids = tok.encode("the quick fox")
print(ids)                          # subword token ids
print(tok.decode(ids))              # round-trip back to text

Compute scaled dot-product attention

scaled_dot_product_attention returns the attended values (the weighted sum of V); derive the attention weights yourself when you need them.

from cds.nlp import scaled_dot_product_attention

# Three "tokens" with 4-dim embeddings.
Q = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]]
K = Q
V = [[1.0], [2.0], [3.0]]
out = scaled_dot_product_attention(Q, K, V)
print(out)   # 3x1 attended output

Run a mini autograd computation

cds.nlp.Tensor is a scalar autograd node: build one per scalar value and chain it with *, +, relu, ... backward() takes no argument — it seeds the output gradient to 1.0 and walks the graph back to the leaves.

from cds.nlp import Tensor, relu

a = Tensor(2.0, requires_grad=True)
b = Tensor(3.0, requires_grad=True)
c = relu(a * b)        # 6.0, with a and b as parents
c.backward()           # seeds 1.0, propagates grads back
print(c.data, a.grad, b.grad)   # 6.0  3.0  2.0

Data analysis

Load CSV data and normalize a column

A DataTable exposes columns via column(name) and column_as_float(name).

from cds.data_analysis import load_csv, z_score

table = load_csv("measurements.csv")            # DataTable
col = table.column_as_float("temp")
print(z_score(col)[:3])                         # standardized values, mean 0

Moving average and visualization

from cds.data_analysis import moving_average, plot_line

series = [1, 3, 2, 5, 4, 6, 5, 8, 7, 9]
smoothed = moving_average(series, window=3)
print(plot_line(series))     # ASCII line chart in the terminal

Round-trip data through pandas (optional extra)

to_dataframe takes a DataSet, which wraps a list of row dicts.

# pip install "cds[pandas]"
from cds.data_analysis import DataSet, to_dataframe, from_dataframe

ds = DataSet(data=[{"temp": 1.0}, {"temp": 2.0}, {"temp": 3.0}])
df = to_dataframe(ds)        # DataSet -> pandas.DataFrame
print(df.describe())
ds2 = from_dataframe(df)     # and back, losslessly

Matplotlib plotting

Optional extra — core CDS stays zero-dependency.

pip install "cognitive-discovery-system[plot]"

Plot a series, histogram, and ACF

import math
from cds.plot import plot_series, plot_histogram, plot_acf, save_figure

y = [math.sin(2 * math.pi * i / 40) for i in range(100)]
save_figure(plot_series(y, title="Sine"), "series.png")
save_figure(plot_histogram(y, bins=20), "hist.png")
save_figure(plot_acf(y, max_lag=20), "acf.png")

Regression, multi-series, heatmap

from cds.plot import plot_regression, plot_multi_series, plot_heatmap, save_figure

x = [1, 2, 3, 4, 5]
y = [2.1, 3.9, 6.0, 8.2, 9.8]
save_figure(plot_regression(x, y), "fit.png")
save_figure(plot_multi_series({"y": y, "2x": [2 * v for v in x]}), "multi.png")
save_figure(plot_heatmap([[1, 2], [3, 4], [5, 6]]), "heat.png")

Waveform + power spectrum with cds.signals

import math
from cds.plot import plot_waveform, plot_power_spectrum, save_figure

n, fs = 128, 128.0
signal = [math.sin(2 * math.pi * 8 * t / fs) for t in range(n)]
save_figure(plot_waveform(signal, sample_rate=fs), "wave.png")
save_figure(plot_power_spectrum(signal, sample_rate=fs), "power.png")

CLI (with [plot] installed):

cds plot 1,5,3,8,2 --file out.png
cds plot 1,2,2,3,5 --kind hist --file hist.png
cds plot 1,0,1,0,1,0,1,0,1,0 --kind acf --file acf.png

See also examples/plot_demo.py.


Hypothesis generation

Generate structured hypotheses offline

from cds.hypothesis import generate_hypotheses

hypos = generate_hypotheses(
    "Why is the measured Hubble constant lower in some methods?",
    domain="cosmology",
    n=3,
)
for h in hypos:
    print(h.statement, "=>", h.predictions)

Evaluate a hypothesis against observed data

HypothesisEvaluator.evaluate(hypothesis, data) takes a Hypothesis and an EvaluationData dict keyed by the dispatch you want to run — here "chi_square_gof", whose value is a ChiSquareGofPayload ({"observed": ..., "expected": ...}).

from cds.core.models import Domain, Hypothesis, HypothesisStatus
from cds.hypothesis import HypothesisEvaluator, ChiSquareGofPayload

h = Hypothesis(
    id="H-die", statement="a six-sided die is fair",
    domain=Domain.MATHEMATICS, research_question="is the die fair?",
    status=HypothesisStatus.TESTABLE,
)
payload: ChiSquareGofPayload = {
    "observed": [8, 12, 10, 11, 9, 10],
    "expected": [10, 10, 10, 10, 10, 10],
}
result = HypothesisEvaluator().evaluate(h, {"chi_square_gof": payload})
print(result.p_value, result.decision)   # large p => consistent with fair

Knowledge organization

Build a concept graph and traverse it

A KnowledgeGraph is created with a name. add_concept(name, description=...) creates a node; add_relation(source, target, kind) links two named concepts.

from cds.knowledge import KnowledgeGraph

kg = KnowledgeGraph(name="mechanics")
kg.add_concept("momentum", description="p = m·v")
kg.add_concept("force", description="F = dp/dt")
kg.add_relation("force", "momentum", "depends-on")
print(kg.neighbors("force"))          # what force relates to

Attach research notes and search across everything

A Notebook is created with a name. Add notes with add_note(note_id, title, body, ...), then search ranks hits from both the graph and the notebook by relevance.

from cds.knowledge import Notebook, search

nb = Notebook(name="mechanics")
nb.add_note(note_id="n1", title="Newton II",
            body="F equals the rate of change of momentum.",
            linked_concepts=["force", "momentum"])
hits = search(kg, nb, query="rate of change")
print(len(hits), "matches")           # ranked by relevance

Every recipe above uses only the documented public API. If a recipe doesn't behave as shown, the API reference for that module (api.md) is the source of truth for signatures and defaults.