mixle.scientist module

Optional local scientific-assistant workflow.

Scientist assembles several Mixle surfaces into one offline-oriented object:

  • modality encoders for images and text, loaded from the local Hugging Face cache when available;

  • certified heads over encoder latents through study;

  • a substrate-backed ask workflow using local evidence, skills, and an optional local language model;

  • factuality and provenance checks for produced answers.

The module is intentionally optional. Heavy assets such as CLIP, MiniLM, and SmolLM2 are lazy-loaded and shared per process, and the package sets offline Hugging Face defaults at import time.

LATENT_SPACES: dict[str, dict[str, Any]] = {'image': {'aligned_with': (), 'dim': 512, 'encoder': 'openai/clip-vit-base-patch32', 'revision': '3d74acf9a28c67741b2f4f2ea7635f0aaf6f0268', 'space_id': 'clip-vit-base-patch32.image_features/v1'}, 'text': {'aligned_with': (), 'dim': 384, 'encoder': 'sentence-transformers/all-MiniLM-L6-v2', 'revision': '1110a243fdf4706b3f48f1d95db1a4f5529b4d41', 'space_id': 'all-MiniLM-L6-v2.sentence_embedding/v1'}}

The latent space each modality encoder produces – two SEPARATE spaces, not one shared one.

encode_images() returns 512-dimensional CLIP image features; encode_texts() returns 384-dimensional embeddings from an independently trained MiniLM. There is no projection between them, no paired alignment objective, no common schema and no learned bridge, so a vector from one space carries no defined relation to a vector from the other: they cannot be compared by distance, averaged, or stacked (the dimensions do not even match). Both encoders’ docstrings used to claim they encoded into “the shared scientific latent space”; they do not. Fit a separate head per space (study() takes latents from exactly one of them), and treat any cross-space claim as requiring a bridge that would have to be trained and evidenced first.

latent_space(modality)[source]

The representation contract for one modality’s encoder (see LATENT_SPACES).

Parameters:

modality (str)

Return type:

dict[str, Any]

scientist_asset_manifest()[source]

Return the immutable external-asset identities used by this module.

Return type:

dict[str, dict[str, str]]

encode_images(images, *, batch=32)[source]

CLIP ViT-B/32 image features, (n, 512) – the real image leaf. Accepts PIL images/arrays.

Parameters:
Return type:

ndarray

encode_texts(texts)[source]

MiniLM sentence embeddings (n, 384) – the real text leaf.

Parameters:

texts (Any)

Return type:

ndarray

generate(prompt, *, max_new_tokens=96, temperature=0.0)[source]

One completion from the local LLM (SmolLM2-360M-Instruct) – the 99%-local answerer.

Parameters:
  • prompt (str)

  • max_new_tokens (int)

  • temperature (float)

Return type:

str

class StudiedModel(head, classes, certificate, qhat, alpha, class_priors, train_seconds, provenance=<factory>)[source]

Bases: object

A certified predictor over ONE encoder’s latents -> closed-form head, with its receipts.

Not cross-modal: it is fit on latents from a single space (see LATENT_SPACES), and provenance["latent_dim"] records which one it was trained in. Predicting on latents from the other encoder is a category error, not a modality it also covers.

Parameters:
predict(z)[source]

Return the most likely class for each latent vector.

Parameters:

z (ndarray)

Return type:

ndarray

predict_proba(z)[source]

Return normalized class probabilities for each latent vector.

Parameters:

z (ndarray)

Return type:

ndarray

prediction_sets(z)[source]

Conformal label sets at level 1-alpha; ambiguous cases may return multiple labels.

Parameters:

z (ndarray)

Return type:

list[list[Any]]

abstains(z)[source]

True where the conformal set is not a single label – the ‘do not trust a point guess’ flag.

Parameters:

z (ndarray)

Return type:

ndarray

study(latents, labels, *, alpha=0.1, cal_frac=0.25, seed=0)[source]

Fit a CERTIFIED classifier over encoder latents: closed-form Gaussian class-conditionals + a split-conformal abstention rail. No gradient descent anywhere – the certificate proves it.

alpha (the split-conformal miscoverage level) must be in [0.0, 1.0] – the same inclusive domain as conformal_label_threshold(), which computes qhat below (0.0 and 1.0 are valid boundaries, not just interior values). cal_frac must be in (0.0, 1.0) exclusive so both the calibration and fit splits stay non-empty. latents must be two-dimensional (n, d) and labels must align with it one-to-one.

Parameters:
Return type:

StudiedModel

class EdgeArtifact(model, bytes, torch_free, family, teacher_accuracy, student_accuracy, agreement, provenance=<factory>)[source]

Bases: object

A capability compressed to run on a constrained device: the student + its footprint + retention.

Parameters:
property retention: float

The share of the teacher’s accuracy retained by the edge student.

nan when the teacher scored zero on the validation set: the ratio’s denominator is not a valid accuracy to retain a share of, and reporting 0.0 there is indistinguishable from a student that genuinely retained nothing.

render()[source]

Render a compact human-readable edge-artifact receipt.

Return type:

str

distill_to_edge(teacher_predict, train_inputs, val_inputs, val_truth, *, max_bytes=500_000, torch_free=True, n_init=3, n_iter=3, seed=0)[source]

Compress a foundation capability into a torch-free edge artifact, with a retention receipt.

teacher_predict is the capability (a raw_input -> label callable – e.g. CLIP zero-shot, or a MiniLM + certified head). Its labels on train_inputs are the distillation target; the student learns them from the RAW inputs under a device byte budget, so the deployed artifact needs neither torch nor the foundation model. The receipt measures what survives: student accuracy vs the teacher’s, and their agreement. Boundary: this works only when the student’s own features carry the signal (text n-grams do; raw pooled pixels do NOT recover a vision foundation model).

train_inputs/val_inputs/val_truth may be any iterable (including a one-shot generator): each is materialized to a list exactly once, at entry, and that list is what every subsequent pass (teacher labeling, student training, validation metrics, provenance counts) reuses.

Parameters:
  • teacher_predict (Any)

  • train_inputs (Any)

  • val_inputs (Any)

  • val_truth (Any)

  • max_bytes (int)

  • torch_free (bool)

  • n_init (int)

  • n_iter (int)

  • seed (int)

Return type:

EdgeArtifact

class ResearchProposal(question, missing, nearest_knowledge=<factory>, options=<factory>, note='')[source]

Bases: object

A knowledge gap made actionable: what is missing and the ranked ways to acquire it.

Parameters:
best()[source]

Return the highest-ranked acquisition option, if one exists.

Return type:

dict[str, Any] | None

render()[source]

Render the proposal as a concise research plan.

Return type:

str

class Conjecture(question, sources=<factory>, status='conjecture', proposal=None)[source]

Bases: object

A curiosity-generated question: explicitly NOT knowledge – a hypothesis with a proposed test.

Parameters:
  • question (str)

  • sources (list[str])

  • status (str)

  • proposal (ResearchProposal | None)

render()[source]

Render the conjecture and its best proposed test.

Return type:

str

class Scientist(knowledge=None)[source]

Bases: object

The laptop multi-modality scientific reasoner (see module docstring).

Multi-modality, not cross-modal: it carries an image encoder and a text encoder whose latent spaces are separate and unaligned (see LATENT_SPACES); it does not relate one modality’s latents to the other’s.

Parameters:

knowledge (Any) – a Substrate of what it may cite (built if omitted).

ask’s abstention decision comes from retrieval confidence and the post-hoc factuality check (see ask’s own docstring), not from the local model’s self-assessed uncertainty – a 360M model’s own confidence is a poor abstention signal. Semantic-entropy gating of the kind mixle.inference.uq provides is a real, separate mechanism in this package (ExternalModel wraps it); Scientist deliberately uses the retrieval/factuality path instead.

learn(docs, *, source='user')[source]

Ingest documents into the citable knowledge (secrets redacted before indexing).

docs is EITHER one document (a str) or a collection of documents. A bare string is one document: iterating it directly made every character its own knowledge item, so the ordinary single-document call learn("hello world") reported 11 documents ingested and stored ["h", "e", ..., "d"] as separate citable items – destroying document structure and leaving later retrieval and provenance hanging off accidental character fragments.

Any other iterable is materialized once (so a one-shot generator is not consumed by the redaction pass and then found empty by the ingest), and each item must be a string document; anything else is rejected by position rather than silently stringified into a citable “fact”. Returns the number of documents ingested.

Parameters:
Return type:

int

add_action(action)[source]

Mount a capability (a physics solver, a simulator, a fitted skill) as a reasoner action.

Parameters:

action (Any)

Return type:

Scientist

ask(question, *, min_confidence=0.2)[source]

Answer with citations or abstain. The local LLM composes ONLY from retrieved evidence, and its own uncertainty is measured: an answer it cannot ground or is not confident of is withheld.

Parameters:
Return type:

Any

propose(question, investigation=None)[source]

Turn an abstention into a research plan: what is missing, and ranked ways to acquire it.

This is the difference between a dead end and a scientist: an explicit “I don’t know” comes back with the acquisition options – add knowledge, run a mounted capability, fit a model to data, simulate, or delegate outward – each with what it would take and what it would settle. The ranking is EIG-per-cost over the mounted actions plus the generic acquisition strategies.

Parameters:
  • question (str)

  • investigation (Any)

Return type:

ResearchProposal

investigate(question, **kw)[source]

ask, but an abstention comes back WITH its research proposal attached – never a bare no.

Parameters:
Return type:

Any

wonder(topic=None, *, n=3, seed=0)[source]

Generate testable conjectures from what it knows – curiosity with receipts attached.

Pairs of knowledge items (optionally biased toward topic) are handed to the local LLM with the instruction to propose a QUESTION or HYPOTHESIS connecting them. Every output is labeled a CONJECTURE and carries a proposed test (the research-proposal machinery), and is checked NOT to already be answerable from the substrate – curiosity about what it does not know, not rediscovery of what it does.

Parameters:
Return type:

list[Conjecture]

static perceive(images)[source]

Encode images into the "image" latent space: (n, 512) CLIP image features.

This is NOT the same space read() produces, and nothing here bridges the two – see LATENT_SPACES. Fit a head per space; do not mix their vectors.

Parameters:

images (Any)

Return type:

ndarray

static read(texts)[source]

Encode texts into the "text" latent space: (n, 384) MiniLM sentence embeddings.

This is NOT the same space perceive() produces, and nothing here bridges the two – see LATENT_SPACES. Fit a head per space; do not mix their vectors.

Parameters:

texts (Any)

Return type:

ndarray

static study(latents, labels, **kw)[source]

Fit a certified perception head over latent vectors.

Parameters:
Return type:

StudiedModel

static distill_to_edge(teacher_predict, train_inputs, val_inputs, val_truth, **kw)[source]

Compress a foundation capability into a torch-free edge artifact (see distill_to_edge()).

Parameters:
  • teacher_predict (Any)

  • train_inputs (Any)

  • val_inputs (Any)

  • val_truth (Any)

  • kw (Any)

Return type:

EdgeArtifact