Skip to main content

HomOps Reference

HomConstMul

Multiply an encrypted value by a fixed plaintext (weights, a mask, a per-channel scale).

CmArithmetic
RunsServer
Changes shapeYes - broadcast
Changes scaleYes - × the plaintext's encoding scale
Levels spent1 by default, 0 with with_modswitch=False
RotatesNo
KeysNone

What it does

HomConstMul multiplies a ciphertext element by element with a plaintext tensor you attach via set_data: model weights, a batch-norm scale, an averaging mask, a selector. One operand is encrypted, the other is fixed data, so no key is needed.
It is the plaintext counterpart of HomMul (ciphertext × ciphertext). Like every multiply, it grows the scale; unlike HomMul, it needs no square key. Its with_modswitch default matches HomMul: True.
You will often meet it inside composites: HomBatchNorm uses it for the scale step, HomExpand and HomRunningSum use it internally for their stage masks.

Implicit HomConstMul: just write x * pt

When you multiply an encrypted value by a plaintext with the * operator, the compiler infers a HomConstMul behind the scenes:
pipeline.pyPYTHON
y = x * pt        # HomConstMul inferred (ciphertext × plaintext)
result = x * y    # HomMul inferred (ciphertext × ciphertext)
Write HomConstMul(...) explicitly when you need to set a parameter, or when the plaintext arrives later via set_data.

Signature

HomConstMul(
    dims,                      # required: plaintext multiplier shape
    with_modswitch=True,
    rows_budget=None,
    pt_scale=None,              # defaults to the context's pt_scale
)
The plaintext comes in through set_data:
pipeline.pyPYTHON
pipeline.set_data(op_index, scale_vector)

Parameters

ParameterTypeDefaultDescription
dimsshaperequiredExpected plaintext multiplier shape, used for the broadcast check at compile time. The tensor attached with set_data must be compatible with this shape.
with_modswitchboolTrueRun a mod-switch after the multiply: rescale the result and spend one level. The default is True, matching HomMul.
rows_budgetsequenceNoneRestricts which levels the mod-switch may spend, by absolute row index. Only relevant when with_modswitch=True. See rows_budget.
pt_scaleintcontext pt_scaleThe encoding scale for the plaintext. The output scale is the input scale times this value. Lower it to keep scale growth small (at the cost of constant precision); leave it unset for the standard plan.
Rules that depend on the value
SettingRuleIf you break it
dims / data.shapeMust broadcast with the ciphertext shape. When the plaintext is broadcast on the n axis, it must be broadcast exactly there (e.g. (C, 1, 1) against (C, H, W)).Compilation fails.
with_modswitchAt least one level must still be droppable when the mod-switch runs.Compilation fails.

Requirements

  • set_data called. The plaintext must be attached before the pipeline compiles.
  • Broadcastable shapes. As above; the efficient packed path runs when the plaintext spans the n axis at full length, the broadcast fallback otherwise.
These surface as compilation errors; see Compilation errors.

Shape effect - yes

Rule: the output shape is the broadcast of the ciphertext shape and the plaintext shape.
The n axis is marked in bold:
CiphertextPlaintextOutput
(128,)(128,)(128,) - packed along the n axis
(C, H, W)(C, 1, 1)(C, H, W) - per-channel scale
(1, , P)(1, K²)(1, , P) - pooling mask
shape: (C, H, W)scale: S
HomConstMul(pt: (C, 1, 1))
shape: (C, H, W)scale: S × pt_scale
Shape and the n axis are covered on Concepts.

Scale effect - yes

Rule: the output scale is the input scale times the plaintext's encoding scale, the pt_scale parameter:
StageEffect on scale
After the multiplyscale_out = scale_in × pt_scale (parameter; context default when unset)
with_modswitch=TrueThen divided by the dropped prime - spending one level.
So a HomConstMul at the default pt_scale grows the scale exactly like one ciphertext multiply, and pt_scale=1 (encoding integers) leaves it unchanged. How scale moves through a pipeline is on Ciphertext state - Scale.

Level budget

ConfigurationLevels spent
with_modswitch=True (default)1 - drops one prime from the chain
with_modswitch=False0 - scale keeps climbing until you rescale
rows_budget limits which level the optional drop may touch. How primes are organized in the chain is explained on Level budget - The chain.

Keys

None. A ciphertext-plaintext multiply produces no oversized intermediate, so no square key; nothing rotates.

Example

Explicit HomConstMul with rescale

Scale a 128-slot vector and pay the growth down in the same op:
pipeline.pyPYTHON
from lattica_build.operators import HomConstMul
from lattica_build.base_classes.hom_pipeline import HomomorphicPipeline
from lattica_build.operators.composite.sequential import SequentialHomOp

pipeline = HomomorphicPipeline(
    hom=SequentialHomOp(
        HomConstMul(dims=(128,), with_modswitch=True),
    ),
    input_shape=(128,),
)
pipeline.set_data(0, scale_vector)   # shape (128,)
The database multiply in exact search is a HomConstMul against the expanded query:
pipeline.pyPYTHON
HomExpand(k=64, k_axis=1, stage_sizes=[4, 4, 4], stages_per_level=3),
HomConstMul(dims=db.shape, with_modswitch=True),
HomAxisSum(dim=2, keep_dim=True),

See also