Skip to main content

HomOps Reference

HomAdd

Add two encrypted values together.

AdArithmetic
RunsServer
Changes shapeYes - broadcast
Changes scaleNo - scales must match, then stay
Levels spent0 - alignment may cost the higher input one
RotatesNo
KeysNone

What it does

HomAdd adds two ciphertexts element by element. Both operands are encrypted values; there are no plaintext weights and no key involved. It is the cheapest way to combine two encrypted streams: merging parallel branches, residual-style connections, updating an accumulator.
Pass is_sub=True to subtract instead (a − b). HomSub is the same operation: HomSub() is equivalent to HomAdd(is_sub=True).
Two related operators cover the plaintext case: to add a fixed constant or bias to a single encrypted stream, HomConstAdd adds a plaintext and takes the weights via set_data.

Implicit HomAdd: just write x + y

You don't have to spell out HomAdd yourself. When you add two encrypted values with the + operator, the compiler infers a HomAdd behind the scenes:
pipeline.pyPYTHON
y = x + pt        # HomConstAdd inferred (ciphertext + plaintext)
result = x + y    # HomAdd inferred (ciphertext + ciphertext)

Signature

HomAdd(is_sub=False)
HomAdd takes no weights, so there is no set_data call - both operands are ciphertexts from earlier in the computation. The only constructor flag is is_sub.

Parameters

ParameterTypeDefaultDescription
is_subboolFalseIf True, perform element-wise subtraction (modsub) instead of addition. Equivalent to writing HomSub.

Requirements

  • Same n axis. Both inputs must share the same n axis to broadcast.
  • Compatible levels. The two inputs must sit on compatible modulus chains: one's active levels a subset of the other's. When they differ, the higher input is automatically brought down to match before the add; you don't call this. Chains that can't be reconciled fail at compile time.
  • Compatible scales. After level alignment, the two scales must match. If they differ, the input with more levels remaining is rescaled to match the other, spending one of its levels. If the scales differ and neither input has a level to spend on the fix, compilation fails.
These surface as compilation errors; see Compilation errors.

Shape effect - yes

Rule: the two inputs broadcast against each other the way array shapes do, on every axis except the n axis, which must already match. The output shape is the broadcast of the two input shapes.
The n axis is marked in bold in each shape below:
Input AInput BOutput
(128,)(128,)(128,) - element-wise
(4, 8)(4, 1)(4, 8) - B broadcast across the second axis
(C, H, W)(1, H, W)(C, H, W) - B broadcast across channels
The two streams merge at the add:
A(4, 8)@scale S
B(4, 1)@scale S
out(4, 8)@scale S

broadcast shapes, matched scales

Shape and the n axis are covered on Concepts.

Scale effect - no

StageEffect on scale
Inputs at the same scaleUnchanged: scale_out = scale_in
Inputs at different scalesThe input with more levels left is adjusted down to the other's scale before the add, spending one of its levels. The output carries the matched scale.
How scale moves through a pipeline is on Ciphertext state - Scale.

Level budget

ConfigurationLevels spent
Inputs at matching level and scale0
Inputs at different levels, same scale0 from the chain's point of view: the higher input drops primes to match the lower one
Inputs at different scales1 from the higher input, spent on the automatic scale fix
The add itself is free. Any cost comes from alignment, and only when the two branches arrive uneven; the way to avoid it is to plan both branches to land at the same level and scale. How primes are organized in the chain is explained on Level budget - The chain.

Keys

None. Addition needs no relinearization and no rotations, so HomAdd adds nothing to the evaluation key.

Example

Combine a transformed value with a shifted copy of itself: result = x + (x * x). With operator inference you never write HomAdd at all:
pipeline.pyPYTHON
# x  - encrypted input

y = x * x           # HomSquare inferred, with_modswitch=True
result = x + y      # HomAdd inferred
For the add to compile, the two branches must land at compatible levels and scales. Here y sits one level below x after its mod-switch, so x (the higher input) is automatically brought down one level to match; the add itself spends nothing.
A residual-style merge of two parallel branches:
Input
main branch: multiply, rescaleshape (C, H, W), scale S
shortcut branchshape (C, H, W), scale S
HomAdd
shape (C, H, W), scale S
Plan both branches so they exit at the same scale: a branch that multiplied and one that didn't will arrive uneven, and the alignment will bill the richer branch.

See also