Tutorial 1 — the synthetic pipeline
morel ships with a synthetic-graph demo so you can verify your install and learn the API without downloading a dataset.
Run the demo
python examples/demo.py
The script builds a 20-user / 50-item synthetic graph, masks 40% of modalities, runs the completion stage, and prints ranking metrics. Total runtime is a few seconds.
What the demo does, step by step
The same flow that examples/demo.py runs is a useful reference for
how morel fits together. Each numbered section maps to a function in
the demo.
1. Build a synthetic bipartite graph
import numpy as np
from morel.data.build import bipartite
rng = np.random.default_rng(0)
users, items = 20, 50
ui = bipartite(
rng.integers(0, users, size=200),
rng.integers(0, items, size=200),
users,
items,
)
bipartite() builds a sparse CSR matrix of shape (users, items)
where every entry is the count of (user, item) interactions.
2. Build per-item features and a mask
features = {
"visual": rng.normal(size=(items, 16)).astype(np.float32),
"text": rng.normal(size=(items, 8)).astype(np.float32),
}
mask = bernoulli(items, 2, 0.4, seed=42).to_numpy()
Two modalities per item (visual is 16-dim, text is 8-dim). 40% of
the items × modalities cells are zeroed out to simulate missing
data.
3. Build the item co-occurrence graph
from morel.data.build import cooccurrence
adjacency = cooccurrence(ui)
This returns the symmetric item–item adjacency sign(Uᵀ U) with
self-loops removed. morel's retrieval stage uses this graph to find
context for each query item.
4. Construct the pipeline
from morel import Config, Pipeline
pipeline = Pipeline(Config(), dims={"visual": 16, "text": 8})
pipeline.attach(features, mask, adjacency)
The pipeline is the orchestrator. attach registers the
feature tensors, mask, and adjacency with the pipeline so every
later forward call can find them.
5. Complete the missing modalities
import torch
output = pipeline(
{k: torch.from_numpy(v) for k, v in features.items()},
torch.from_numpy(mask),
adjacency,
index=torch.arange(items),
training=False,
)
pipeline(..., training=False) switches the model into eval mode for
the call. output.completed is a dict[modality_name, tensor] with
the reconstructed feature vectors, and output.routing is the
soft routing over the codebook.
6. Rank users against items
from morel.recommend import Light
scores = Light(users=users, items=items, embed=32, layers=2)(
torch.arange(users), torch.arange(items), ui
)
The ranker is the LightGCN-style recommender from the paper. The
matrix is (users, items); take the top-K entries per row to get
recommendations.
Inspect the outputs
print("completed visual:", output.completed["visual"].shape)
# torch.Size([50, 16])
print("routing weights:", output.routing.shape)
# torch.Size([50, 100])
print("score matrix:", scores.shape)
# torch.Size([20, 50])
Compare these against the demo's expected output to confirm the install is wired correctly.
Next steps
- Tutorial: real Amazon Reviews data — replace the synthetic features with a real dataset.
- How-to: configure morel — change a dimension, swap a stage, or pin the seed.