Nodes/Computational LSNet/Vector Math (Computational LSNet)
ComfyUI Node

Vector Math (Computational LSNet)

Doing Actual Math on a Style Vector

By let-the-name-be-x1·Created 24 days ago·Updated 24 days ago· 2
Vector Math (Computational LSNet)
  • V0
  • V1
  • V2
  • V3
  • V4
  • V5
  • V6
  • V7
  • V8
  • V9
  • F0
  • F1
  • F2
  • F3
  • result
  • value
  • text
◄expressiona*(1-w) + b*w►
◄precision6►

ComfyUI has plenty of "math" nodes and most of them are a letdown the moment a TENSOR shows up at the door - more_math's V inputs are Strings and its F inputs are Floats, so you can't wire a feature vector into them at all. This node was written to fix exactly that: V0…V9 and F0…F3 are any-type ports, so a 2048-dim style vector, a float, and a string can all land on the same node, and expression decides what happens next.

Does it deserve the name? The function count is padded by aliases (cos_sim/cossim/cosine), but the useful half is real and the port fix alone earns the install.

What you'd use it for

The one people actually need: blending styles in the classifier's own space rather than arguing with prompt weights. mix(V0, V1, 0.3) gives you a 70/30 style vector, which you then run through LSNet Features → Artist Tags to read back as an artist string. Because the classifier head is affine, that mix behaves like a proper logit-space ensemble instead of two artist names fighting inside one prompt.

Others that come up constantly:

  • Average several reference images: mean(f0, f1, f2) - element-wise, stays a vector.
  • Borrow a scale: normalize(V0) * norm(V1) - keep the direction of one vector at the magnitude of another. This is the fix for the pack's biggest trap (see below).
  • Matrix work for iterative workflows: stack(f0, f1, f2) builds an (N, 2048) input that Features → Artist Tags can aggregate across.
  • The GA helpers: randn(seed, [S,D]), rownorm, normclip, take, setrow, catrows, seedmix - a population of style vectors mutating across generations is what they exist for.

How it works, and the details that bite

The expression is parsed with a Python ast whitelist - no eval, no imports, no attribute escape. Multi-statement works with newlines or ; - t = normalize(a) then t*0.5 is legal, # comments, and the value of the last statement is the result.

Aliases follow more_math's habit: a b c d are V0–V3, w x y z are V4–V7 - unless you've connected F0–F3, which override w x y z. Unconnected a–d read as 0.0 so a half-wired chain doesn't explode. V and Vcnt give you the list of connected inputs and their count.

Inputs worth setting: expression (defaults to a*(1-w) + b*w, a lerp - which is also a hint at its intended use) and precision (6, the significant digits in the text output; 0–12). Operators include @ for matmul; x[i], x[i:j] and x.shape work too.

Outputs: result (TENSOR - scalars arrive as 0-d tensors), value (FLOAT, meaningful only when the result is a scalar, nan otherwise), and text (STRING with shape, element count, norm, mean and std - the easy way to see what you just computed).

Three traps, in order of how often they catch people:

  1. mean(x) on a single tensor reduces to a scalar. mean(f) is one number, not an averaged vector, and downstream Features → Artist Tags rejects it as "a scalar". mean(f0, f1, f2) is the element-wise version you wanted.
  2. Don't normalize before classifying. The pack's BN scale fact is non-negotiable: ‖f‖ ≈ 49 and the head's running_mean norm is 48.13, so a unit vector is nearly pure constant as far as the head is concerned, and every image collapses to the same artist name. Normalize only for pure direction comparisons, and rescale with normalize(V0) * norm(V1) when you need to go back.
  3. cat flattens matrices. Use catrows/vstack when you're assembling an (N, 2048) block and want to keep two dimensions.

Randomness: randn([S,D]) uses the global RNG and differs every call; randn(seed, [S,D]) is reproducible forever; seedmix(base_seed, n) gives a different-but-reproducible seed per iteration. They run on their own generator, so they don't disturb your sampler seeds.

Install

cd ComfyUI/custom_nodes
git clone https://github.com/let-the-name-be-x1/Computational-LSNet.git
# restart ComfyUI; the pack lands under category "Computational LSNet"

No dependencies: requirements.txt is empty on purpose and pyproject.toml declares dependencies = [], so Manager won't pip-install a torch that clobbers your CUDA build. Manager users: search Computational LSNet, or comfy node install computational-lsnet once it's in the Registry.

Here's the nice property of this node: it's pure tensor arithmetic, so it has no upstream dependency at all. You need the LSNet plugin and its 2.94 GB checkpoint only at the ends of the chain - where the vector is produced (LSNet Common Features) or classified back into names.

Troubleshooting

Everything is 0.0. You referenced a/b/c/d but connected V0/V1 elsewhere, or you're on the F0–F3 override path without meaning to. The text output's shape and stats usually make it obvious.

"Expression result type ... cannot be a tensor." You produced a string or list. Wrap it with stack/cat, or check you didn't accidentally swallow a STRING input from a nearby text node.

Huge text output freezing the UI. Drop precision, or pipe the tensor into Vector → Text / txt with display_limit set and read it there.

CategoryComputational LSNet

Inputs (16)

NameTypeDefaultDescription
expressionoptSTRINGa*(1-w) + b*w—
precisionoptINT60–12—
V0opt*—
V1opt*—
V2opt*—
V3opt*—
V4opt*—
V5opt*—
V6opt*—
V7opt*—
V8opt*—
V9opt*—
F0opt*—
F1opt*—
F2opt*—
F3opt*—

Outputs (3)

NameTypeDescription
resultTENSOR—
valueFLOAT—
textSTRING—