Skip to content

【代码贡献】Fix interpolation_comparator returning the wrong comparison direction - #94

Open
mnn31 wants to merge 1 commit into
OriginQ:developfrom
mnn31:fix/interpolation-comparator-range
Open

mnn31 wants to merge 1 commit into
OriginQ:developfrom
mnn31:fix/interpolation-comparator-range

Conversation

@mnn31

@mnn31 mnn31 commented Sep 21, 2026

Copy link
Copy Markdown

Problem

The documented contract for QCmp.interpolation_comparator is set by two
clauses of its docstring. The Returns clause:

The comparison result qubit would be in state :math:|1\rangle when the
quantum state satisfies the comparison condition, otherwise
:math:|0\rangle.

and the interpolation rule:

When comparing the quantum state with inf+delta, the probability of
smaller is delta.

with inf := a - 0.5 for a state a. Together with function='g' being
documented as >, that fixes the answer: the flag must read
P(state > value), where state is spread over the unit interval
[state - 0.5, state + 0.5].

The docstring example pins the same thing numerically. State '110' is 3,
value = 3.3 = inf + 0.8, so the rule gives P(smaller) = 0.8 and the example
prints 0.2 for function='g'. 0.2 is P(state > value), not
P(state < value), so g must be the > direction.

The implementation returns the other direction almost everywhere. Probability
of the comparison qubit reading |1> for each state of a 2-qubit register,
reuse=True:

value function develop documented
1.4 g 1.0, 0.1, 0.0, 0.0 0.0, 0.1, 1.0, 1.0
1.4 s 0.0, 0.9, 1.0, 1.0 1.0, 0.9, 0.0, 0.0
2.0 g 1.0, 1.0, 0.5, 0.5 0.0, 0.0, 0.5, 1.0
2.0 s 0.0, 0.0, 0.5, 0.5 1.0, 1.0, 0.5, 0.0
4.0 g 1.0, 1.0, 1.0, 1.0 0.0, 0.0, 0.0, 0.0
4.0 s 0.0, 0.0, 0.0, 0.0 1.0, 1.0, 1.0, 1.0

Precisely, develop returns the complement of the documented answer at every
state except two:

  • state == round(value), where it is correct. This is the case the docstring
    example covers.
  • state == round(value) + 1 when round(value) is even, where it returns the
    interpolated probability instead of a sharp 0 or 1. The value=2.0, g row
    above shows this: state 3 reads 0.5 where the answer is 1.

Classifying every measurement of the sweeps below against those three
categories leaves nothing unaccounted for (other: 0 in both):

n = 2, 3, 4; reuse False and True; 21 values; both modes; every basis state
  2352 checks: correct 196, complement 2108, interpolated at round(value)+1 48, other 0
n = 3; reuse True; 179 values k/20 for k = 1..179; both modes; every basis state
  2864 checks: correct 304, complement 2428, interpolated at round(value)+1 132, other 0

The value=4.0 rows are the out-of-range case: no 2-qubit state is above 4.0,
yet g flags every state. int_comparator on the same out-of-range integers
returns 0 for g and 1 for s.

For integer value, interpolation_comparator should agree with
int_comparator except at state == value, where it smooths to 0.5:

value=1, function='g', 2-qubit register
int_comparator           0.0, 0.0, 1.0, 1.0
interpolation_comparator 1.0, 0.5, 0.0, 0.0

Root cause

pyqpanda-algorithm/pyqpanda_alg/QCmp/QCmp.py, interpolation_comparator
(lines 174-289 on develop, body from line 224).

The ancilla chain is int_comparator's chain with the bit-0 comparison
softened by an RY coin: _qor where the corresponding bit of value_int is
0, TOFFOLI where it is 1. That chain evaluates state >= value_int,
propagating the bit-0 result only while the higher bits are equal. Three things
do not line up with it:

  1. The output polarity is the opposite of int_comparator, which pre-flips
    q_cmp for s/seq and leaves it alone for g/geq.
    interpolation_comparator pre-flips for g, so the chain result is
    complemented for the wrong mode.
  2. value_res = value - value_int + 0.5 is P(state < value), while the slot
    it feeds in the chain needs P(state > value).
  3. Where bit 0 of value_int is 0, the chain drops q_state[0]
    (circuit << RY(q_anc_cmp[0], angle)), so a state differing from
    value_int only in bit 0 is treated as equal to it.

(1) and (2) cancel when state == value_int, which is why the interpolated
probability at that state is right and the direction elsewhere is not. (3) is
independent and produces the second exception above.

The docstring also gives the interval as inf:=a-0.5 to sup:=a+1.5. A
width-2 interval would make the stated rule read delta/2 rather than delta,
and would not give the example's 0.2, so this looks like a typo for a+0.5;
this PR changes that one token so the description matches the rule and the
example.

Fix

Three changes, in the same style as int_comparator:

    if function == 's':
        circuit << X(q_cmp)

    elif function == 'g':
        pass
    value_res = value_int - value + 0.5
            if value_inverse[0] == 0:
                circuit << _zero_control(q_state[0], RY(q_anc_cmp[0], angle)) \
                        << CNOT(q_state[0], q_anc_cmp[0])

The third one makes bit 0 behave like the rest of the chain: q_state[0] = 1
against a 0 bit means strictly greater, so the flag is set outright, and the
coin applies only on the equal branch.

Cost, where bit 0 of round(value) is 0, is an uncontrolled RY becoming a
zero-controlled RY plus a CNOT. Measured on value=2.4, 3-qubit register,
function='g', reuse=True: two-qubit gates 0 to 2, depth 6 to 9, X count 6
to 7. Where bit 0 of round(value) is 1 the bit-0 construction is untouched
and only the polarity X moves between the two modes; on value=3.3, same
register and mode, depth stays 6 and two-qubit gates stay 1, with the X count
going 6 to 5 for g and 5 to 6 for s.

Verification

The develop column of the repro table becomes the documented column in
every row.

Exhaustive check against P(state > value) and P(state < value), with each
state treated as the unit interval [state - 0.5, state + 0.5]:

  • values [0.0, 0.2, 0.5, 0.9, 1.0, 1.3, 1.5, 2.0, 2.5, 2.7, 3.0, 3.3, 4.0, 5.0, 6.5, 7.0, 8.0, 15.0, 16.0, 31.0, 100.0], registers of 2, 3 and 4
    qubits, reuse both ways, both modes, every basis state: 2352 checks, 0
    mismatch above 1e-9, largest absolute error 1.11e-16. On develop the same
    sweep is 2156 mismatches.
  • values k/20 for k = 1..179 (0.05 to 8.95), 3-qubit register,
    reuse=True, both modes, every basis state: 2864 checks, 0 mismatch above
    1e-9, largest absolute error 2.22e-16. On develop the same sweep is 2560
    mismatches.

The docstring example is unchanged to within floating-point rounding: develop
prints {'0': 0.7999999999999997, '1': 0.20000000000000023} and this branch
prints {'0': 0.7999999999999997, '1': 0.20000000000000018}, a difference of
5e-17. The docstring literal is left as it is.

On a 3-qubit uniform superposition through the value=2.4, function='g',
reuse=True circuit, the state-register marginals are unchanged: 0.5 on each
of the three qubits and 0.125 on each of the 8 joint outcomes, before and
after. Note that with reuse=True this function does not uncompute
q_anc_cmp[0], before or after this change: on that circuit it reads 0.9 on
develop and 0.55 here, where int_comparator under reuse=True returns its
ancillas to |0>. That is existing behaviour and this PR does not change it.

Tests

test/QAlgBase/Test_comparator_interpolation_comparator.py existed but every
line was commented out. Un-commented it, brought it up to the current
pyqpanda3 API, and extended it to 49 tests: the docstring example, interpolated
probabilities for non-integer values in both modes, the sharp answers outside
the interpolated interval, integer values checked against the classical
predicate over every state of 2- and 3-qubit registers, values above the
register range, the upper and lower edges of the range, reuse agreement, a
superposition whose flag probability is the mean of the basis results, the
ValueError on negative input and the NameError on an unknown function.

cd test && python -m pytest -o addopts="" -q
# 66 passed  (17 before, 49 new)

Against the unpatched module the new file is 27 failed, 22 passed.

This does not conflict with #42, which changes qft_qubit_comparator in the
same file; a local test merge of the two branches is clean and the combined
suite is 90 passed.

The ancilla chain evaluates the geq predicate, so the residual angle and
the g/s output polarity both have to match int_comparator. They were
inverted, which cancelled out only when the state equals round(value),
the single case the docstring example covers. Bit 0 of the state was
also dropped from the chain when that bit of round(value) is 0, so a
state one above an even round(value) returned the interpolated
probability instead of certainty.

Also correct sup in the interval description: the stated rule and the
worked example both require a width-1 interval.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant