Scale and depth lifecycle
Scale and depth are independently tracked coordinates of CKKS value state in FHElium. The application chooses encoding scales and state transitions. Evaluator operations apply their documented input contract and record the resulting scale, depth, and active modulus rows; callers must establish compatibility that a numerical implementation does not infer.
This page defines the public scale and depth coordinates, valid public depths, transition laws, compatibility requirements, and transition queries.
Scale-depth transition state
A scale- or depth-changing operation is described by the tuple
where:
is the public Q-chain depth stored asvalue.depth; is the positive finite binary64 actual scale stored asvalue.scale; is the ordered tuplevalue.prime_idsthat maps each dense limb row to one configured parameter prime; is the modulus basis, either or .
depth and scale are independent coordinates. Depth selects the active Q suffix, basis independently selects Q or QP, and prime_ids records the resulting RNS rows. Stored operation state also includes shape, polynomial domain, residue representation, component count, dtype, and device. The caller separately ensures the operands use compatible CKKS parameters.
Default scale and actual scale
config.default_scale is the default encoding and planning value
engine.plaintext, engine.encode, and engine.encrypt_message select scale argument is None. Once a value exists, its scale field is the actual scale used by subsequent arithmetic and decoding.
The product
FHElium records the binary64 quotient by the actual group product.
All public scale entry points and value constructors require a value that can be represented as a finite Python float and satisfies
NaN, infinity, zero, negative values, booleans, and strings are rejected with InvalidScaleError. A scale multiplication or division that overflows or underflows the finite-positive range is rejected at the operation that produces it.
Depth addresses active Q groups
Let the configured Q chain be
At public depth max_depth is
In the current parameter order, a complete Q value has
value.prime_ids == tuple(
range(engine.config.q_row_start(value.depth), engine.config.num_q_primes)
)2
3
and a QP value at the same depth appends the special P prime IDs from range(engine.config.num_q_primes, engine.config.total_num_primes). depth selects the active Q suffix, and prime_ids maps each dense limb to its modulus. QP is the auxiliary basis at that depth.
Public value creation accepts depths in [0, engine.max_depth]. Public rescale_to_next_depth and mod_switch_to_next_depth require a following public depth, so from depth
The built-in bootstrap accepts depth engine.max_depth - 1. Its entry rescale removes that Q group and reaches the ordinary terminal group at engine.max_depth. Its scale schedule is documented in Composable CKKS bootstrapping.
Scale-depth transition laws
The following laws define multiplication, rescale, modulus switch, and scale reinterpretation.
Multiplication preserves depth and multiplies scale
For compatible ciphertext operands,
multiply_plaintext applies the same scale-product law to a ciphertext and an operation-ready plaintext. Both multiplication primitives require and return NTT/Montgomery ciphertext state, preserve the active Q basis, and expose domain transitions separately from scale arithmetic.
For real scalar multiplication, the caller-selected scalar scale
The Eager default is
Real scalar addition quantizes its addend at a caller-selected
Rescale changes depth, scale, rows, and payload
At depth rescale_to_next_depth computes
and records
For QP input, the leading Q-group rows are removed and every P row is retained. rounding="nearest" and rounding="floor" select different quotient laws but have the same metadata transition.
Modulus switch advances depth and preserves scale
For a target public depth mod_switch_to_depth restricts each residue polynomial to the target active basis:
The operation restricts residues to the target basis while preserving their coefficient representatives. Message preservation requires that the centered represented value remain within the smaller target modulus.
Scale reinterpretation changes metadata and decoded meaning
reinterpret_at_scale(ciphertext, target_scale) leaves every ciphertext residue unchanged and records the requested target scale:
Because decoding divides by the recorded scale, the interpreted message changes according to
Its optional max_relative_change argument bounds the symmetric ratio between the current and target scales. Addition requires scale compatibility before evaluation.
Addition and subtraction require compatible state
Ciphertext addition and subtraction preserve depth and scale under the following caller compatibility requirements:
with binary64 equality, together with equal shape, component count, domain, basis, residue representation, and prime IDs. A scale difference of one unit in the last place violates this compatibility contract. The caller supplies compatible operands to add, subtract, and sum_ciphertexts; Tensor dispatch does not establish their scale or key relation.
add_plaintext adds the prepared plaintext to ciphertext component zero and preserves the later components. The operands match in depth, actual scale, prime rows, modulus basis, and polynomial domain. The plaintext uses Montgomery residues; the ciphertext uses standard residues in coefficient domain or Montgomery residues in NTT domain. A plaintext has no ciphertext component axis. An unbatched prepared plaintext may broadcast over a ciphertext batch; a batched plaintext has the ciphertext batch shape.
The caller must also select operands produced under compatible CKKS parameters; the application retains parameter provenance for runtime values.
Programs align the two axes separately:
- choose encoding scales and multiplication histories that produce compatible actual scales;
- use
mod_switch_to_depthwhen only the active Q depth must advance; - apply guarded
reinterpret_at_scalewhen the resulting message-ratio change is part of the numerical policy.
Scale and depth effects by operation family
| Operation family | Depth requirements and effects | Scale requirements and effects |
|---|---|---|
plaintext, encode, encrypt_message | Set the requested public depth | Set the requested scale; an omitted argument selects config.default_scale |
encrypt, decrypt | Preserve | Preserve |
| RNS, NTT, coefficient-domain, and residue conversions | Preserve | Preserve |
add, subtract, sum_ciphertexts | Require matching ciphertext state and depth; preserve | Require binary64 equality; preserve |
add_plaintext | Require matching depth and operation-ready plaintext state; preserve ciphertext state | Require binary64 equality; preserve |
negate, relinearize, switch_key, rotations, conjugation | Preserve | Preserve |
multiply, multiply_plaintext | Require equality; preserve | Record the product of operand scales |
add_scalar | Preserve | Preserve; the scalar quantization scale is caller-selected and defaults to the ciphertext scale |
multiply_scalar | Preserve | Multiply by the selected scalar scale, which defaults to the ciphertext scale |
multiply_integer_scalar | Preserve | Preserve |
rescale_to_next_depth | Advance by one | Divide by the actual dropped Q-group product |
mod_switch_to_next_depth | Advance by one | Preserve |
mod_switch_to_depth | Set the requested reachable public depth | Preserve |
reinterpret_at_scale | Preserve | Replace with the requested scale |
Functional arithmetic returns output storage, while an in-place form mutates the selected input and requires valid storage lifetimes. Primitive representation, domain, and residue conversions are defined in State transitions and orthogonality. Arithmetic, component-count, and key-dependent effects are described in Evaluator operation transitions.
Query the rescale transition
The engine exposes the same depth-dependent divisor and binary64 quotient used by rescale_to_next_depth:
drop_divisor = engine.rescale_divisor(depth=ciphertext.depth)
predicted_scale = engine.rescale_output_scale(
input_scale=pre_rescale_scale,
depth=ciphertext.depth,
)
assert predicted_scale == pre_rescale_scale / drop_divisor2
3
4
5
6
7
These queries return the modulus and scale arithmetic of one transition from a provided source depth and input scale.
Binary64 expression ordering is observable. Branches that will be added use a common scale calculation history so their scale metadata satisfies binary64 equality.