Skip to main content

Biomimetic Lookup Cell — parametric geodesic lookup framework for machine learning

Project description

BMC API Reference — 0.2.1

A gradient-free machine learning framework. Instead of learned weights, BMC stores empirical patterns in Voronoi cells ordered along a geodesic path through the data manifold. No backpropagation, no loss functions, no optimizer.

Based on papers by Dr. Ziad Kobti:

Install: pip install biomimetic-framework


Architecture

Input → ObservationSpace → BMCBlock (N independent BMCLayers)
                                ↓
                          PredictionHead (cross-zone patterns)
                                ↓
                             Output

Three Orthogonal Axes (0.2.1)

BMC has three independent design axes. Each axis can be configured without affecting the others:

Axis Values Where
Activation logical (AND/OR exact match) vs metric (closest-wins) bmc.core.activation
Readout exact (fired cell's stored value) vs interpolated (geodesic blend) BMCLayer.forward
Topology (new in 0.2.1) unsupervised (density-based k-means++) vs stratified (per-class k-means++) vs user callable bmc.core.fitting.cluster
Tagging (new in 0.2.1) argmax (Voronoi majority) vs seed_class (preserves class each seed was born under) vs weighted (inverse-frequency) bmc.core.fitting.group_by_output

Quick Topology Decision Guide

  • Balanced classes, well-separated features: topology='unsupervised' (default). No need for the fix.
  • Class boundaries don't align with feature-density peaks (text embeddings, shared visual territory, etc.): topology='stratified', tag_mode='seed_class'. On 20 Newsgroups this lifts macro-F1 by +0.74 at K=8.
  • Imbalanced classes where minorities visually overlap majorities (e.g. handwritten c/C): same as above.
  • Mild imbalance, balanced features: topology='unsupervised' + tag_mode='weighted' as a lightweight alternative.
  • Regression (continuous y): topology has no effect — stratified auto-downgrades to unsupervised.

Supervised Topology Example

from bmc import BMCLayer, ObservationSpace, L2Metric, metric_activation

inp = ObservationSpace(dim=256, dtype=np.float32)
out = ObservationSpace(dim=1, dtype=np.int32)
layer = BMCLayer(inp)

# Default (0.2.0 behavior): unsupervised topology, argmax tagging
layer.fit(X, y, n_clusters=50, output_space=out,
          activation=metric_activation(L2Metric()))

# New (0.2.1): class-stratified topology with seed_class tagging
layer.fit(X, y, n_clusters=50, output_space=out,
          activation=metric_activation(L2Metric()),
          topology='stratified', tag_mode='seed_class')

# Custom per-class budget via dict
layer.fit(X, y, n_clusters=50, output_space=out,
          activation=metric_activation(L2Metric()),
          topology='stratified', tag_mode='seed_class',
          k_per_class={0: 20, 1: 15, 2: 10, 3: 5})

# Square-root (default), proportional, or equal allocator
layer.fit(X, y, n_clusters=50, output_space=out,
          activation=metric_activation(L2Metric()),
          topology='stratified', tag_mode='seed_class',
          k_per_class='equal')

Allocators — per-class seed budgets

For topology='stratified', the allocator decides how to distribute K seeds across classes. Built-in allocators (bmc.core.allocation):

Allocator Rule Use case
sqrt_allocator (default) k_c ∝ √n_c Textbook anti-imbalance: minorities get more than proportional but majority still dominates on big differences
proportional_allocator k_c ∝ n_c Matches natural class distribution; does NOT help with imbalance
equal_allocator k_c = K / n_classes Fully balanced; strongest anti-imbalance setting

All allocators satisfy: sum(k_c) == K, 1 <= k_c <= counts[c], deterministic. User allocators can be any callable with the signature (labels, counts, K) -> np.ndarray[int], or a dict {label: k_c}, or a raw np.ndarray of length n_classes.

from bmc.core.allocation import sqrt_allocator, equal_allocator, resolve_allocator

# Resolver accepts str, callable, ndarray, dict, or None
sqrt_allocator == resolve_allocator('sqrt')
sqrt_allocator == resolve_allocator(None)   # default

# On counts=[80, 10, 5, 3, 2] with K=50:
#   sqrt         → [31, 9, 5, 3, 2]    (minorities get more)
#   proportional → [40, 5, 3, 2, 1]   (matches density)
#   equal        → [10, 10, 10, 10, 10]  (fully balanced; capped if n_c < K/n_classes)

Table of Contents


Core Primitives

ObservationSpace

Describes an input or output data space — dimensionality, dtype, and name.

Import

from bmc import ObservationSpace

Constructor

ObservationSpace(dim, dtype=np.float32, name=None)
Parameter Type Default Description
dim int required Dimensionality. Must be ≥ 1.
dtype np.dtype float32 Data type of values.
name str auto Human-readable label.

Methods

validate(data) → np.ndarray

Checks last dimension matches dim, casts dtype if needed.

Raises ValueError if dimension mismatch.

Properties

dim, dtype, name

Example

inp = ObservationSpace(dim=5, dtype=np.float32, name="features")
out = ObservationSpace(dim=1, dtype=np.int32, name="label")

X = np.array([[1.0, 2.0, 3.0, 4.0, 5.0]])
X_valid = inp.validate(X)  # shape (1, 5), dtype float32

See Also

BMCCell, BMCLayer


BMCCell

The core primitive — a lookup table with K weights and 1 output. Pluggable activation (logical or metric).

Import

from bmc import BMCCell

Constructor

BMCCell(input_space, output_space, activation=None)
Parameter Type Default Description
input_space ObservationSpace required Input data space.
output_space ObservationSpace required Output data space.
activation callable logical_activation Activation function. Use metric_activation(L2Metric()) for distance-based.

Methods

memorize(weights, output) → self

Store weight-output pairs. All weights must be unique. NaN rejected.

Parameter Type Description
weights (K, D) array Input patterns. Each row is one key.
output (O,) array The single output produced when any weight matches.
forward(inputs) → (fired, outputs)

Activate the cell on input(s).

Parameter Type Description
inputs (M, D) array Query inputs.

Returns:

  • fired: (M,) float — 1.0 if activated, 0.0 if not.
  • outputs: (M, O) array — cell output for firing inputs, zero otherwise.

Properties

Property Type Description
K int Number of stored weights
D int Input dimensionality
O int Output dimensionality
is_empty bool True if no weights stored
weights_ (K, D) array Stored weights
output_ (O,) array Stored output

Example

from bmc import BMCCell, ObservationSpace, L2Metric, metric_activation
import numpy as np

inp = ObservationSpace(dim=2, dtype=np.float32)
out = ObservationSpace(dim=1, dtype=np.int32)

### Logical activation (exact match)
cell = BMCCell(inp, out)
cell.memorize(
    np.array([[0, 0], [0, 1], [1, 0]], dtype=np.float32),
    np.array([1], dtype=np.int32),
)
fired, outputs = cell.forward(np.array([[0, 1], [5, 5]], dtype=np.float32))
### fired  → [1.0, 0.0]  (first matches, second doesn't)
### outputs → [[1], [0]]

### Metric activation (closest wins)
cell_m = BMCCell(inp, out, activation=metric_activation(L2Metric()))
cell_m.memorize(np.array([[0, 0]], dtype=np.float32), np.array([0], dtype=np.int32))
fired, outputs = cell_m.forward(np.array([[99, 99]], dtype=np.float32))
### fired → [1.0]  (metric always fires)

See Also

ObservationSpace, activation, BMCLayer


Activation Functions

Determine HOW a cell matches inputs to weights. Pluggable callables with signature (inputs, weights) → (fired, indices).

Import

from bmc import logical_activation, metric_activation

logical_activation

logical_activation(inputs, weights)  (fired, indices)

Exact-match AND per weight, OR across weights. The base activation from BMC theory.

  • fired: (M,) float — 1.0 if any weight matched, 0.0 otherwise.
  • indices: (M,) int — index of first matching weight, -1 if none.

metric_activation

metric_activation(metric_fn)  callable

Factory: wraps a SimilarityMetric into a closest-wins activation.

Parameter Type Description
metric_fn SimilarityMetric Must be a SimilarityMetric instance. Type-enforced.

Returns a callable with same signature as logical_activation. Always fires (there is always a closest weight).

Raises TypeError if metric_fn is not a SimilarityMetric.

Example

import numpy as np
from bmc import logical_activation, metric_activation, L2Metric

weights = np.array([[0, 0], [1, 1]], dtype=np.float32)
inputs = np.array([[0, 0], [0.6, 0.6]], dtype=np.float32)

### Logical: exact match only
fired, idx = logical_activation(inputs, weights)
### fired → [1.0, 0.0]   (only [0,0] matches exactly)
### idx   → [0, -1]

### Metric: closest wins
act = metric_activation(L2Metric())
fired, idx = act(inputs, weights)
### fired → [1.0, 1.0]   (always fires)
### idx   → [0, 1]       ([0.6,0.6] closer to [1,1])

See Also

BMCCell, SimilarityMetric


Fitting Pipeline

Fitting Pipeline

Four standalone functions for training BMCLayers. Each is independently callable. layer.fit() chains them.

Import

from bmc import cluster, assign, group_by_output, order_cells

cluster

cluster(X, n_clusters, metric=None, rng=None, max_iter=50, tol=1e-4,
        *, y=None, topology='unsupervised', k_per_class=None,
        return_labels=False)  seeds  OR  (seeds, seed_labels)

K-means++ initialization + Lloyd's refinement, with an optional supervised topology axis.

Parameter Type Description
X (M, D) Training data.
n_clusters int Number of seeds K.
metric SimilarityMetric Default: L2Metric().
rng Generator For reproducibility.
max_iter int Lloyd iterations.
tol float Convergence tolerance.
y (M,) Class labels. Required when topology='stratified'.
topology str or callable 'unsupervised' (default, pre-0.2.1 behavior), 'stratified' (per-class k-means++), or a user callable (X, y, K, rng, metric) -> seeds or -> (seeds, labels).
k_per_class str | callable | ndarray | dict Allocator for stratified mode — see Allocators. None → sqrt_allocator.
return_labels bool If True, return (seeds, seed_labels) — needed for tag_mode='seed_class' downstream.

Returns: seeds (K, D) when return_labels=False. Otherwise (seeds, seed_labels) where seed_labels is (K,) with the class each seed was born under (or None for unsupervised).

Unsupervised vs Stratified

Unsupervised (default, byte-identical to 0.2.0): k-means++ places seeds at feature-density peaks. Labels enter only at group_by_output via argmax(counts). On overlapping or imbalanced classes, minorities can be silenced.

Stratified (new in 0.2.1): per-class k-means++. Each class's samples are clustered independently with an allocated seed budget. The seed pool always represents every class.

assign

assign(X, seeds, metric=None)  assignments

Voronoi tessellation: assign each point to its nearest seed.

Returns: (M,) int array, index of the nearest seed for each point.

group_by_output

group_by_output(seeds, assignments, y, input_space, output_space, activation=None,
                *, tag_mode='argmax', seed_labels=None)  list[BMCCell]

Construct BMCCells by grouping seeds that share an output label.

Parameter Description
seeds, assignments, y From cluster() and assign().
input_space, output_space Input/output descriptors.
activation BMCCell activation.
tag_mode How to assign labels to seeds — see below.
seed_labels Required when tag_mode='seed_class'. Usually produced by cluster(return_labels=True, topology='stratified').

Tag modes (discrete / integer y only; continuous y ignores this):

  • 'argmax' (default, pre-0.2.1 behavior): each seed's label is the majority class among the training points in its Voronoi region. On overlapping classes, minorities can lose.
  • 'seed_class': each seed keeps the class it was born under (from stratified clustering). Every class in seed_labels gets at least one cell. Pair this with topology='stratified' for the full supervised-topology effect.
  • 'weighted': inverse-frequency-weighted bincount. Minority samples count more per vote. Useful for mild imbalance without stratified seeding. (Note: the current 1/n_c weighting is aggressive and may flip labels on overwhelmingly-majority cells; a gentler weighting is planned for 0.2.2.)

Returns: list of fitted BMCCells, one per unique class in the chosen tag scheme.

order_cells

order_cells(cells, metric=None)  list[BMCCell]

Reorder cells along the geodesic via TSP (nearest-neighbor + 2-opt). Required for interpolated readout.

Returns: same cells, TSP-ordered.

Allocators

Used only when topology='stratified'. k_per_class accepts:

  • None → default sqrt_allocator (textbook anti-imbalance)
  • 'sqrt', 'proportional', 'equal' → lookup in bmc.core.allocation.ALLOCATORS
  • A callable with signature (labels, counts, K) -> (n_classes,) int
  • An np.ndarray of per-class budgets (validated at call time)
  • A dict {label: k_c} (validated at call time)

Examples

Unsupervised (default)
import numpy as np
from bmc import cluster, assign, group_by_output, ObservationSpace

X = np.array([[0,0],[0,1],[1,0],[5,5],[5,6],[6,5]], dtype=np.float32)
y = np.array([0, 0, 0, 1, 1, 1], dtype=np.int32)

seeds = cluster(X, n_clusters=4, rng=np.random.default_rng(42))
assignments = assign(X, seeds)

inp = ObservationSpace(dim=2, dtype=np.float32)
out = ObservationSpace(dim=1, dtype=np.int32)
cells = group_by_output(seeds, assignments, y, inp, out)
### len(cells) → 2  (one per class)
Stratified + seed_class
seeds, seed_labels = cluster(
    X, n_clusters=4, rng=np.random.default_rng(42),
    y=y, topology='stratified', return_labels=True,
)
assignments = assign(X, seeds)
cells = group_by_output(
    seeds, assignments, y, inp, out,
    tag_mode='seed_class', seed_labels=seed_labels,
)
### Every class in y guaranteed at least one cell.
Custom allocator
### Give class 0 more seeds than sqrt would allocate
seeds, seed_labels = cluster(
    X, n_clusters=10, rng=np.random.default_rng(0),
    y=y, topology='stratified', k_per_class={0: 7, 1: 3},
    return_labels=True,
)

See Also

BMCLayer.fit(), distortion


allocation

Per-class seed allocators for stratified clustering. New in 0.2.1.

Import

from bmc.core.allocation import (
    sqrt_allocator,
    proportional_allocator,
    equal_allocator,
    resolve_allocator,
    ALLOCATORS,
)

Built-in Allocators

All three have the same signature:

allocator(labels: np.ndarray, counts: np.ndarray, K: int) -> np.ndarray[int]
Allocator Rule Typical output on counts=[80, 10, 5, 3, 2], K=50
sqrt_allocator (default) k_c ∝ √n_c [31, 9, 5, 3, 2] — anti-imbalance
proportional_allocator k_c ∝ n_c [40, 5, 3, 2, 1] — matches density
equal_allocator k_c ≈ K / n_classes [10, 10, 10, 10, 10] — max balance

Invariants (every allocator satisfies):

  • sum(k_c) == K
  • 1 <= k_c <= counts[c] (at least one seed per class, capped at per-class sample count)
  • Deterministic output for the same inputs
  • Raises ValueError when K < n_classes or K > sum(counts)

resolve_allocator

resolve_allocator(spec, default=sqrt_allocator)  callable

Resolves a user-supplied spec to an allocator callable:

Spec type Resolved to
None default (sqrt)
str Registry lookup: 'sqrt', 'proportional', 'equal'
callable Used directly; must match allocator signature
np.ndarray Fixed allocation, validated at call time
dict {label: k_c} Fixed allocation, keyed by labels, validated at call time

Example

import numpy as np
from bmc.core.allocation import sqrt_allocator, resolve_allocator

labels = np.array([0, 1, 2, 3, 4])
counts = np.array([80, 10, 5, 3, 2])
K = 50

k = sqrt_allocator(labels, counts, K)
### k → array([31, 9, 5, 3, 2])
assert k.sum() == K
assert np.all(k >= 1)
assert np.all(k <= counts)

### User-specified via dict
alloc = resolve_allocator({0: 20, 1: 15, 2: 10, 3: 3, 4: 2})
k_custom = alloc(labels, counts, 50)
### k_custom → array([20, 15, 10, 3, 2])

See Also

cluster, group_by_output


Distortion Functions

Measure where a layer fails. Used by GateBMC for routing decisions.

Import

from bmc import distortion_residual, distortion_reconstruction

distortion_residual (default)

distortion_residual(layer, X, y, metric=None)  scores

Per-cell mean |target − prediction|. Task-aware.

Returns: (N,) float — higher = more distortion.

distortion_reconstruction

distortion_reconstruction(layer, X, y=None, metric=None)  scores

Per-cell mean nearest-weight distance. Task-independent.

Returns: (N,) float — higher = coarser tessellation.

Example

scores_r = distortion_residual(layer, X_train, y_train)
scores_g = distortion_reconstruction(layer, X_train)
### Both shape (N,) — one score per cell
### High scores → cell needs refinement

See Also

GateBMC, SkipBMC


Composition

BMCLayer

Parallel container of BMCCells. Competitive activation — one cell wins per input. Supports fitting, ordering, and interpolated forward.

Import

from bmc import BMCLayer

Constructor

BMCLayer(input_space, cells=None)
Parameter Type Description
input_space ObservationSpace Shared input space for all cells.
cells list[BMCCell] Optional initial cells.

Methods

add(cell) → self

Add a cell. Must have matching input space.

fit(X, y, n_clusters, output_space, ...) → self

4-step pipeline: cluster → assign → group → order (each bypassable).

Parameter Type Default Description
X (M, D) required Training inputs.
y (M,) required Training targets.
n_clusters int required Voronoi seeds K.
output_space ObservationSpace required Cell output space.
metric SimilarityMetric L2 Distance metric.
activation callable None Cell activation function.
seeds (K,D) None Skip clustering if provided.
assignments (M,) None Skip assignment if provided.
order bool True TSP-order cells after grouping.
topology str or callable (keyword-only) 'unsupervised' Clustering topology. See fitting. Pre-0.2.1 default.
tag_mode str (keyword-only) 'argmax' Label tagging rule. See fitting. Pre-0.2.1 default.
k_per_class str/callable/ndarray/dict (keyword-only) None Allocator for stratified mode.
seed_labels (K,) (keyword-only) None Required with tag_mode='seed_class' if user-supplied seeds are also passed.
order(metric) → self

Reorder cells along geodesic via TSP.

forward(X, interpolator=None, metric=None) → (fired, outputs)
  • Hard: fired is (M, N) with one 1.0 per row.
  • Interpolated: two neighboring cells share the signal as arc-length ratios.
compute_arc_lengths(metric) → (N,) float

Properties

N, D, cells, is_empty, is_ordered

Example

from bmc import BMCLayer, ObservationSpace, L2Metric, metric_activation
import numpy as np

X = np.array([[0,0],[0,1],[5,5],[5,6]], dtype=np.float32)
y = np.array([0, 0, 1, 1], dtype=np.int32)

inp = ObservationSpace(dim=2, dtype=np.float32)
out = ObservationSpace(dim=1, dtype=np.int32)
act = metric_activation(L2Metric())

layer = BMCLayer(inp)
layer.fit(X, y, n_clusters=4, output_space=out, activation=act)
### layer.N → 2 (one cell per class)

fired, outputs = layer.forward(np.array([[1,1],[4,4]], dtype=np.float32))
### fired[0] → [1.0, 0.0]  (closer to class 0)
### fired[1] → [0.0, 1.0]  (closer to class 1)
Supervised Topology
### On imbalanced or overlapping classes, use stratified topology with
### seed_class tagging so minority classes always get dedicated seeds.
layer.fit(X, y, n_clusters=50, output_space=out, activation=act,
          topology='stratified', tag_mode='seed_class')

### Custom per-class budget
layer.fit(X, y, n_clusters=50, output_space=out, activation=act,
          topology='stratified', tag_mode='seed_class',
          k_per_class='equal')   # or 'sqrt' (default), 'proportional',
                                 # or a dict / ndarray / callable

See Also

BMCCell, BMCBlock, fitting


BMCBlock

Parallel container of independent BMCLayers. All layers fire independently — no competition between layers. Used for zone decomposition.

Import

from bmc import BMCBlock

Constructor

BMCBlock(layers=None, input_fns=None)
Parameter Type Description
layers list[BMCLayer] Independent layers.
input_fns list[callable] Per-layer input mapping. fn(X) → X_zone. None = identity.

Methods

add(layer, input_fn=None) → self
forward(X, interpolator=None, metric=None) → list[(fired, outputs)]

Runs ALL layers independently. Returns one (fired, outputs) per layer.

fit(X, y, n_clusters_per_layer, output_space, ...) → self

Fits all layers independently on their respective inputs.

flatten_fired(results) → (M, total_cells)

Static method. Concatenates fired signals from all layers.

Properties

N (num layers), layers, total_cells, is_empty

Example

from bmc import BMCBlock, BMCLayer, ObservationSpace, L2Metric, metric_activation
import numpy as np

X = np.random.randn(100, 10).astype(np.float32)
y = np.array([0]*50 + [1]*50, dtype=np.int32)
act = metric_activation(L2Metric())
out = ObservationSpace(dim=1, dtype=np.int32)

### 2 zones: features 0-4 and 5-9
l0 = BMCLayer(ObservationSpace(dim=5, dtype=np.float32))
l0.fit(X[:, :5], y, n_clusters=8, output_space=out, activation=act)
l1 = BMCLayer(ObservationSpace(dim=5, dtype=np.float32))
l1.fit(X[:, 5:], y, n_clusters=8, output_space=out, activation=act)

block = BMCBlock(
    layers=[l0, l1],
    input_fns=[lambda X: X[:, :5], lambda X: X[:, 5:]],
)
results = block.forward(X)
### len(results) → 2, one per zone
### results[0] = (fired_zone0, outputs_zone0)

flat = BMCBlock.flatten_fired(results)
### flat.shape → (100, total_cells)

See Also

BMCLayer, PredictionHead, SkipBMC


BMCNetwork

Sequential container — layers chained via reduce_output().

Import

from bmc import BMCNetwork

Constructor

BMCNetwork(layers=None)

Methods

add(layer) → self

Validates dimension matching between consecutive layers.

forward(X, interpolator=None, metric=None) → (fired, outputs)

Forward through all layers. Each layer's output is reduced to (M, O) for the next.

forward_at(X, depth) → (fired, outputs)

Forward through layers 0..depth (inclusive).

reduce_output(fired, outputs) → (M, O) (static)

Fired-weighted sum of cell outputs.

Properties

depth, layers, input_space, is_empty

Example

from bmc import BMCNetwork

net = BMCNetwork()
net.add(layer_0)  # input D, output O₀
net.add(layer_1)  # input O₀, output O₁
fired, outputs = net.forward(X)

### reduce_output for manual chaining:
intermediate = BMCNetwork.reduce_output(fired_0, outputs_0)
### intermediate.shape → (M, O₀)

See Also

BMCLayer, BMCBlock


PredictionHead

Reads cross-layer activation patterns from a BMCBlock. Finds discriminative patterns like "zone 3 + zone 7 active → class A."

Import

from bmc import PredictionHead

Class Methods

from_block(block, output_space, n_clusters, ...) → PredictionHead
Parameter Type Description
block BMCBlock The block whose patterns to read.
output_space ObservationSpace Task output space.
n_clusters int K for the head's clustering.

Methods

fit_from_block(block, X, y, interpolator=None, metric=None) → self

Forward block on X, flatten fired signals, fit head on flattened patterns.

predict_from_block(block, X, ...) → (fired, outputs)

Forward block, flatten, forward head.

flatten_block(block_results) → (M, total_cells) (static)

Concatenates fired signals from all block layers.

Example

from bmc import BMCBlock, PredictionHead, ObservationSpace

### block has 4 zones, each with 3 cells → total_cells = 12
head = PredictionHead.from_block(block, 
    output_space=ObservationSpace(dim=1, dtype=np.int32),
    n_clusters=16)
head.fit_from_block(block, X_train, y_train)

fired, outputs = head.predict_from_block(block, X_test)
preds = np.array([int(outputs[np.argmax(fired[m])][m, 0]) 
                   for m in range(len(X_test))])

See Also

BMCBlock, GateBMC


GateBMC

Binary soft router between resolution levels. A BMCLayer with 2 cells: low-distortion (stay) and high-distortion (defer to finer layer).

Import

from bmc import GateBMC

Constructor

GateBMC(input_space, n_clusters=8, metric=None, rng=None)

Methods

fit_from_distortion(parent, X, y, distortion_fn=distortion_residual, threshold=None) → self

Trains on binary distortion labels derived from the parent layer.

route(X, interpolator=None, metric=None) → (M,) float

Returns routing weight per input in [0, 1]. 0 = stay, 1 = defer.

Properties

distortion_scores(N,) per-cell scores from last fit.

Example

from bmc import GateBMC, distortion_residual

gate = GateBMC(parent.input_space, n_clusters=8)
gate.fit_from_distortion(parent, X_train, y_train)

weights = gate.route(X_test)
### weights[m] ≈ 0.0 → trust parent layer
### weights[m] ≈ 1.0 → defer to finer layer

See Also

SkipBMC, distortion


MuxBMC

Learned interpolator — compiles a mathematical interpolator into a BMC. The interpolator is the "teacher," the MUX is the "student."

Import

from bmc import MuxBMC

Class Methods

from_layer(parent, n_clusters=16, metric=None, rng=None) → MuxBMC

Methods

compile(parent, X, interpolator=None, metric=None) → self

Run parent with interpolator, learn the blending pattern.

blend(X, metric=None) → (M, N_parent) float

Produce learned blending weights. Non-negative, rows sum to 1.

Example

from bmc import MuxBMC, LinearInterpolator

mux = MuxBMC.from_layer(parent_layer, n_clusters=16)
mux.compile(parent_layer, X_train, interpolator=LinearInterpolator())

weights = mux.blend(X_test)
### weights.shape → (M, N_parent), rows sum to 1

See Also

BMCLayer, Interpolators


SkipBMC

Parallel-layer refinement: BMCBlock(parent, finer) + GateBMC router. Recursive — finer layer can be another SkipBMC.

Import

from bmc import SkipBMC

Constructor

SkipBMC(parent, n_clusters_gate=8, distortion_fn=distortion_residual, metric=None, rng=None)
Parameter Type Description
parent BMCLayer or SkipBMC Coarse layer to refine.
n_clusters_gate int Gate K.
distortion_fn callable Default: distortion_residual.

Methods

fit(X, y, n_clusters_fine, output_space=None, activation=None, *, finer_topology='stratified', finer_tag_mode='seed_class', finer_k_per_class=None) → self

Train gate + finer layer. Finer layer K biased toward distorted regions.

New in 0.2.1: the finer layer uses stratified clustering with seed_class tagging by default. This ensures the refinement actually represents minority classes in confused regions — the original promise of skip connections. The gate stays unsupervised (its target is intrinsically binary distortion).

Parameter Type Default Description
finer_topology str or callable (keyword-only) 'stratified' Topology for the finer layer. Auto-falls back to 'unsupervised' for continuous (float) y regression. Set explicitly to 'unsupervised' for pre-0.2.1 behavior.
finer_tag_mode str (keyword-only) auto Tag mode for the finer layer. Defaults to 'seed_class' when finer_topology='stratified', else 'argmax'.
finer_k_per_class str/callable/ndarray/dict (keyword-only) None Allocator spec when stratified.

Bug fixed in 0.2.1: the candidate subset is now sliced on y alongside X, preventing a class-mismatch that was invisible under argmax but wrong under stratified.

forward(X, interpolator=None, metric=None) → (fired, outputs)

Gated blend: (1-gate) * parent + gate * finer.

predict(X) → preds

Hard prediction from combined fired signal.

Properties

block_, gate_, finer_, distortion_scores, is_fitted_

Example

from bmc import SkipBMC

### Default (0.2.1): stratified + seed_class on the finer layer
skip = SkipBMC(parent_layer)
skip.fit(X_train, y_train, n_clusters_fine=24)
preds = skip.predict(X_test)

### Pre-0.2.1 behavior — unsupervised finer layer
skip_legacy = SkipBMC(parent_layer)
skip_legacy.fit(X_train, y_train, n_clusters_fine=24,
                finer_topology='unsupervised')

### Recursive
skip2 = SkipBMC(skip)
skip2.fit(X_train, y_train, n_clusters_fine=32)

See Also

GateBMC, BMCBlock, distortion


Metrics

SimilarityMetric

Abstract base class for all distance metrics. Subclasses must implement pairwise().

Import

from bmc.metrics.base import SimilarityMetric

Constructor

SimilarityMetric(device=None)

device: "cpu", "cuda", or "auto".

Methods

pairwise(A, B) → (M, K) float (abstract)

Pairwise distances. Must be vectorized.

__call__(a, b) → float

Distance between two vectors. Convenience wrapper.

to(device) → self

Switch device.

Properties

device — resolved device string.

Guarantees

  • Non-negativity: d(a, b) ≥ 0
  • Identity: d(a, a) = 0
  • Symmetry: d(a, b) = d(b, a)

See Also

L2Metric, metric_activation


L2Metric

Euclidean distance metric.

Import

from bmc import L2Metric

Constructor

L2Metric(device=None)
Parameter Type Default Description
device str None "cpu", "cuda", or "auto".

Example

import numpy as np
from bmc import L2Metric

metric = L2Metric()
A = np.array([[1.0, 0.0], [0.0, 1.0]])
B = np.array([[1.0, 0.0]])
dists = metric.pairwise(A, B)
### dists.shape → (2, 1)
### dists[0, 0] → 0.0  (identical)
### dists[1, 0] > 0    (different)

See Also

SimilarityMetric, metric_activation


CosineMetric

Cosine distance metric.

Import

from bmc import CosineMetric

Constructor

CosineMetric(device=None)
Parameter Type Default Description
device str None "cpu", "cuda", or "auto".
eps float 1e-8 Normalization guard.

Example

import numpy as np
from bmc import CosineMetric

metric = CosineMetric()
A = np.array([[1.0, 0.0], [0.0, 1.0]])
B = np.array([[1.0, 0.0]])
dists = metric.pairwise(A, B)
### dists.shape → (2, 1)
### dists[0, 0] → 0.0  (identical)
### dists[1, 0] > 0    (different)

See Also

SimilarityMetric, metric_activation


FrobeniusMetric

Frobenius distance metric.

Import

from bmc import FrobeniusMetric

Constructor

FrobeniusMetric(device=None)
Parameter Type Default Description
device str None "cpu", "cuda", or "auto".

Example

import numpy as np
from bmc import FrobeniusMetric

metric = FrobeniusMetric()
A = np.array([[1.0, 0.0], [0.0, 1.0]])
B = np.array([[1.0, 0.0]])
dists = metric.pairwise(A, B)
### dists.shape → (2, 1)
### dists[0, 0] → 0.0  (identical)
### dists[1, 0] > 0    (different)

See Also

SimilarityMetric, metric_activation


SpectralMetric

Spectral distance metric.

Import

from bmc import SpectralMetric

Constructor

SpectralMetric(device=None)
Parameter Type Default Description
device str None "cpu", "cuda", or "auto".

For flat vectors (M, D), delegates to L2. For matrix inputs (M, H, W), uses per-pair SVD — O(D³) per element.

Example

import numpy as np
from bmc import SpectralMetric

metric = SpectralMetric()
A = np.array([[1.0, 0.0], [0.0, 1.0]])
B = np.array([[1.0, 0.0]])
dists = metric.pairwise(A, B)
### dists.shape → (2, 1)
### dists[0, 0] → 0.0  (identical)
### dists[1, 0] > 0    (different)

See Also

SimilarityMetric, metric_activation


NuclearMetric

Nuclear distance metric.

Import

from bmc import NuclearMetric

Constructor

NuclearMetric(device=None)
Parameter Type Default Description
device str None "cpu", "cuda", or "auto".

For flat vectors (M, D), delegates to L2. For matrix inputs (M, H, W), uses per-pair SVD — O(D³) per element.

Example

import numpy as np
from bmc import NuclearMetric

metric = NuclearMetric()
A = np.array([[1.0, 0.0], [0.0, 1.0]])
B = np.array([[1.0, 0.0]])
dists = metric.pairwise(A, B)
### dists.shape → (2, 1)
### dists[0, 0] → 0.0  (identical)
### dists[1, 0] > 0    (different)

See Also

SimilarityMetric, metric_activation


Interpolators

LinearInterpolator

Piecewise linear between adjacent prototypes. C0 continuous. Fast.

Import

from bmc import LinearInterpolator

Constructor

LinearInterpolator()

Methods

fit(t_values, y_values) → self
Parameter Type Description
t_values (K,) Arc-length positions, normalized [0, 1].
y_values (K,) or (K, D) Values at each position.
evaluate(t) → values

Evaluate at query positions. Clipped to [t_min, t_max].

get_state() → dict / set_state(state) → self

Serialization roundtrip.

Example

t = np.array([0.0, 0.5, 1.0])
y = np.array([0.0, 1.0, 0.0])
interp = LinearInterpolator()
interp.fit(t, y)
interp.evaluate(np.array([0.25, 0.75]))
### → [0.5, 0.5]  (linear blend)

See Also

SplineInterpolator, BMCLayer.forward()


SplineInterpolator

Cubic spline through all prototypes. C2 continuous. Uses scipy.interpolate.CubicSpline.

Import

from bmc import SplineInterpolator

Constructor

SplineInterpolator(bc_type='not-a-knot')
Parameter Type Default Description
bc_type str 'not-a-knot' Boundary condition. Options: 'not-a-knot', 'clamped', 'natural'.

Falls back to LinearInterpolator for < 4 prototypes.

Methods

fit(t, y), evaluate(t), get_state(), set_state(state)

See Also

LinearInterpolator, PCHIPInterpolator


PCHIPInterpolator

Monotone piecewise cubic Hermite (PCHIP). C1 continuous. Prevents overshoot — important when outputs must stay bounded (e.g., probabilities shouldn't go negative).

Import

from bmc import PCHIPInterpolator

Constructor

PCHIPInterpolator()

Falls back to LinearInterpolator for < 2 prototypes.

Methods

fit(t, y), evaluate(t), get_state(), set_state(state)

See Also

SplineInterpolator, KrigingInterpolator


KrigingInterpolator

Gaussian Process (Kriging) interpolation along the geodesic. Kernel-based.

Import

from bmc import KrigingInterpolator

Constructor

KrigingInterpolator(kernel='gaussian', length_scale=1.0, noise=1e-6)
Parameter Type Default Description
kernel str 'gaussian' 'gaussian', 'matern', or 'thin_plate_spline'.
length_scale float 1.0 Controls influence extent.
noise float 1e-6 Regularization.

Methods

fit(t, y), evaluate(t), get_state(), set_state(state)

See Also

KernelInterpolator, PCHIPInterpolator


KernelInterpolator

Inverse Distance Weighting (IDW) or Radial Basis Function (RBF). Global method — all prototypes contribute to every query.

Import

from bmc import KernelInterpolator

Constructor

KernelInterpolator(method='idw', power=2.0)
Parameter Type Default Description
method str 'idw' 'idw' or 'rbf'.
power float 2.0 IDW exponent (ignored for RBF).

Methods

fit(t, y), evaluate(t), get_state(), set_state(state)

See Also

KrigingInterpolator, LinearInterpolator


Observation Spaces

TabularObservationSpace

Splits flat feature vectors into zone subsets for zone-parallel processing.

Import

from bmc import TabularObservationSpace

Constructor

TabularObservationSpace(n_features, n_zones=None, zone_indices=None)
Parameter Type Description
n_features int Total number of features.
n_zones int Number of zones (auto-splits features evenly).
zone_indices list[list[int]] Explicit feature indices per zone. Overrides n_zones.

Methods

extract(X) → (N_zones, M, D_zone)

Decompose input into zone feature vectors.

Properties

n_zones, n_features, feature_dim

Example

obs = TabularObservationSpace(n_features=30, n_zones=6)
### 6 zones of 5 features each
zones = obs.extract(X)  # shape (6, M, 5)

See Also

BMCBlock, ImageObservationSpace


ImageObservationSpace

Overlapping patch grid for grayscale images. Pools patches to fixed size.

Import

from bmc import ImageObservationSpace

Constructor

ImageObservationSpace(image_size, pool_size, grid_n, output_size, method='lanczos')
Parameter Type Description
image_size int Input image size (assumes square).
pool_size int Patch size before pooling.
grid_n int Grid dimension (grid_n × grid_n zones).
output_size int Pooled patch size.
method str Resize method. Default 'lanczos'.

Methods

extract(images) → (N_zones, M, D)
grid_starts() → list[int]

Properties

n_zones (= grid_n²), feature_dim (= output_size²)

Example

obs = ImageObservationSpace(image_size=8, pool_size=3, grid_n=4, output_size=2)
### 16 zones (4×4), each 4D (2×2 pooled patch)
zones = obs.extract(images)  # shape (16, M, 4)

See Also

TabularObservationSpace, BMCBlock


TimeSeriesObservationSpace

Sliding windows for sequential data.

Import

from bmc import TimeSeriesObservationSpace

Constructor

TimeSeriesObservationSpace(series_length, window_size, stride=1)
Parameter Type Description
series_length int Length of input sequences.
window_size int Temporal window per zone.
stride int Step between windows. stride < window = overlap.

Methods

extract(X) → (N_zones, M, D)
window_starts() → list[int]

Properties

n_zones, feature_dim (= window_size × n_features)

Example

obs = TimeSeriesObservationSpace(series_length=140, window_size=28, stride=14)
### 9 overlapping windows
zones = obs.extract(X)  # shape (9, M, 28)

See Also

TextObservationSpace, BMCBlock


TextObservationSpace

Contiguous chunks of embedding sequences.

Import

from bmc import TextObservationSpace

Constructor

TextObservationSpace(seq_len, embed_dim, chunk_size, stride=None)
Parameter Type Description
seq_len int Number of positions in the sequence.
embed_dim int Embedding dimension per position.
chunk_size int Positions per chunk.
stride int Step between chunks. Default = chunk_size (no overlap).

Methods

extract(X) → (N_zones, M, D)

Input shape: (M, seq_len, embed_dim).

chunk_starts() → list[int]

Properties

n_zones, feature_dim (= chunk_size × embed_dim)

Example

obs = TextObservationSpace(seq_len=16, embed_dim=16, chunk_size=2, stride=1)
### 15 overlapping chunks, each 32D
zones = obs.extract(X_seq)  # shape (15, M, 32)

See Also

TimeSeriesObservationSpace, BMCBlock


Infrastructure

Constants

Named defaults for the BMC framework. All magic numbers import from here.

Import

from bmc.constants import DEFAULT_RNG_SEED, EPSILON_ZERO, ...

Values

Constant Value Used for
DEFAULT_RNG_SEED 42 Default random seed
DEFAULT_MEMORY_FRACTION 0.75 RAM/VRAM budget for batching
DEFAULT_MEMORY_MAX_FRACTION 0.99 Warning threshold
DEFAULT_LLOYD_BATCH_MULTIPLIER 10 Mini-batch = min(M, 10·K)
DEFAULT_MAX_ZONES 10 Auto TabularObservationSpace zones
EPSILON_ZERO 1e-12 Division-by-zero guard
EPSILON_NORM 1e-8 Normalization guard
EPSILON_CONVERGENCE 1e-4 Lloyd's convergence tolerance
EPSILON_TSP 1e-10 Minimum 2-opt swap gain

Backend

CPU/CUDA abstraction for pairwise distance computation.

Import

from bmc.backend import get_backend, resolve_device

get_backend

get_backend(device=None)  CPUBackend or CUDABackend
Device Backend Requirements
"cpu" NumPy None
"cuda" PyTorch torch + CUDA GPU
None / "auto" Best available Auto-detect

Backend Methods

All accept NumPy arrays, return NumPy arrays.

Method Signature Description
pairwise_l2(A, B) (M,D)×(K,D) → (M,K) L2 distances
pairwise_cosine(A, B) (M,D)×(K,D) → (M,K) Cosine distances
pairwise_frobenius(A, B) (M,D)×(K,D) → (M,K) Frobenius distances
argmin(dists, axis) (M,K) → (M,) Argmin
bincount(x) (M,) → (max+1,) Bin count

See Also

SimilarityMetric


Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

biomimetic_framework-0.2.1.tar.gz (167.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

biomimetic_framework-0.2.1-py3-none-any.whl (154.9 kB view details)

Uploaded Python 3

File details

Details for the file biomimetic_framework-0.2.1.tar.gz.

File metadata

  • Download URL: biomimetic_framework-0.2.1.tar.gz
  • Upload date:
  • Size: 167.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.12

File hashes

Hashes for biomimetic_framework-0.2.1.tar.gz
Algorithm Hash digest
SHA256 9f4d2e4193e9ca8771aa733298d2aa8d4b66be2387d5613495cfd5c112df64eb
MD5 fd4aa7b2b91a0791047cd4cf5ed50c5d
BLAKE2b-256 45202dcc7050387b358ccf5f5a14982efcc18137a33339ee77a203efda777e42

See more details on using hashes here.

File details

Details for the file biomimetic_framework-0.2.1-py3-none-any.whl.

File metadata

File hashes

Hashes for biomimetic_framework-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 679dd8e280c70e4b155fbd8e7fc751b0d8ecc734ab06d5467867122afee1c1c0
MD5 3b48aabaa7dc0af0895ffd2d02f381eb
BLAKE2b-256 4e3c51b648b624d6d6d4833d4034dc45f694735e3f9a41b35cd041d93a5af390

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page