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:
- Bio-mimetic approach in digital artificial neuro-science
- Biomimetic Cells: A New Frontier in Brain Informatics
- Advanced Biomimetic Cells Architecture for Parallel Operations in AI
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
- Fitting Pipeline
- Composition
- Metrics
- Interpolators
- Observation Spaces
- Infrastructure
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 inseed_labelsgets at least one cell. Pair this withtopology='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 current1/n_cweighting 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→ defaultsqrt_allocator(textbook anti-imbalance)'sqrt','proportional','equal'→ lookup inbmc.core.allocation.ALLOCATORS- A callable with signature
(labels, counts, K) -> (n_classes,) int - An
np.ndarrayof 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) == K1 <= k_c <= counts[c](at least one seed per class, capped at per-class sample count)- Deterministic output for the same inputs
- Raises
ValueErrorwhenK < n_classesorK > 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:
firedis(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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9f4d2e4193e9ca8771aa733298d2aa8d4b66be2387d5613495cfd5c112df64eb
|
|
| MD5 |
fd4aa7b2b91a0791047cd4cf5ed50c5d
|
|
| BLAKE2b-256 |
45202dcc7050387b358ccf5f5a14982efcc18137a33339ee77a203efda777e42
|
File details
Details for the file biomimetic_framework-0.2.1-py3-none-any.whl.
File metadata
- Download URL: biomimetic_framework-0.2.1-py3-none-any.whl
- Upload date:
- Size: 154.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
679dd8e280c70e4b155fbd8e7fc751b0d8ecc734ab06d5467867122afee1c1c0
|
|
| MD5 |
3b48aabaa7dc0af0895ffd2d02f381eb
|
|
| BLAKE2b-256 |
4e3c51b648b624d6d6d4833d4034dc45f694735e3f9a41b35cd041d93a5af390
|