• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Qiskit / qiskit / 18901832664

29 Oct 2025 08:36AM UTC coverage: 88.189% (+0.002%) from 88.187%
18901832664

Pull #15264

github

web-flow
Merge da0ae41ba into 046aba2ff
Pull Request #15264: Docker-based QPY compatibility tests

93673 of 106219 relevant lines covered (88.19%)

1155761.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

64.56
/crates/circuit/src/variable_mapper.rs
1
// This code is part of Qiskit.
2
//
3
// (C) Copyright IBM 2025
4
//
5
// This code is licensed under the Apache License, Version 2.0. You may
6
// obtain a copy of this license in the LICENSE.txt file in the root directory
7
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8
//
9
// Any modifications or derivative works of this code must retain this
10
// copyright notice, and modified files need to carry a notice indicating
11
// that they have been altered from the originals.
12

13
use crate::bit::{ClassicalRegister, Register, ShareableClbit};
14
use crate::classical::expr;
15
use hashbrown::{HashMap, HashSet};
16
use pyo3::prelude::*;
17
use std::cell::RefCell;
18

19
/// A control flow operation's condition.
20
///
21
/// TODO: move this to control flow mod once that's in Rust.
22
#[derive(IntoPyObject)]
23
pub(crate) enum Condition {
24
    Bit(ShareableClbit, usize),
25
    Register(ClassicalRegister, usize),
26
    Expr(expr::Expr),
27
}
28

29
impl<'a, 'py> FromPyObject<'a, 'py> for Condition {
30
    type Error = <expr::Expr as FromPyObject<'a, 'py>>::Error;
31

32
    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
52✔
33
        if let Ok((bit, value)) = ob.extract::<(ShareableClbit, usize)>() {
52✔
34
            Ok(Condition::Bit(bit, value))
38✔
35
        } else if let Ok((register, value)) = ob.extract::<(ClassicalRegister, usize)>() {
14✔
36
            Ok(Condition::Register(register, value))
2✔
37
        } else {
38
            Ok(Condition::Expr(ob.extract()?))
12✔
39
        }
40
    }
52✔
41
}
42

43
/// A control flow operation's target.
44
///
45
/// TODO: move this to control flow mod once that's in Rust.
46
#[derive(IntoPyObject)]
47
pub(crate) enum SwitchTarget {
48
    Bit(ShareableClbit),
49
    Register(ClassicalRegister),
50
    Expr(expr::Expr),
51
}
52

53
impl<'a, 'py> FromPyObject<'a, 'py> for SwitchTarget {
54
    type Error = <expr::Expr as FromPyObject<'a, 'py>>::Error;
55

56
    fn extract(ob: Borrowed<'a, 'py, PyAny>) -> Result<Self, Self::Error> {
12✔
57
        if let Ok(bit) = ob.extract::<ShareableClbit>() {
12✔
58
            Ok(SwitchTarget::Bit(bit))
×
59
        } else if let Ok(register) = ob.extract::<ClassicalRegister>() {
12✔
60
            Ok(SwitchTarget::Register(register))
×
61
        } else {
62
            Ok(SwitchTarget::Expr(ob.extract()?))
12✔
63
        }
64
    }
12✔
65
}
66

67
pub(crate) struct VariableMapper {
68
    target_cregs: Vec<ClassicalRegister>,
69
    register_map: RefCell<HashMap<String, ClassicalRegister>>,
70
    bit_map: HashMap<ShareableClbit, ShareableClbit>,
71
    var_map: HashMap<expr::Var, expr::Var>,
72
    stretch_map: HashMap<expr::Stretch, expr::Stretch>,
73
}
74

75
impl VariableMapper {
76
    /// Constructs a new mapper.
77
    ///
78
    /// The `stretch_map` is only used for direct calls to [VariableMapper::map_expr]
79
    /// since `condition`s and `target`s expressions are never durations. Provide
80
    /// [None] if you don't need this.
81
    pub fn new(
110,964✔
82
        target_cregs: Vec<ClassicalRegister>,
110,964✔
83
        bit_map: HashMap<ShareableClbit, ShareableClbit>,
110,964✔
84
        var_map: HashMap<expr::Var, expr::Var>,
110,964✔
85
        stretch_map: Option<HashMap<expr::Stretch, expr::Stretch>>,
110,964✔
86
    ) -> Self {
110,964✔
87
        Self {
110,964✔
88
            target_cregs,
110,964✔
89
            register_map: RefCell::default(),
110,964✔
90
            bit_map,
110,964✔
91
            var_map,
110,964✔
92
            stretch_map: stretch_map.unwrap_or_default(),
110,964✔
93
        }
110,964✔
94
    }
110,964✔
95

96
    /// Map the given `condition` so that it only references variables in the destination
97
    /// circuit (as given to this class on initialization).
98
    ///
99
    /// If `allow_reorder` is `true`, then when a legacy condition (the two-tuple form) is made
100
    /// on a register that has a counterpart in the destination with all the same (mapped) bits but
101
    /// in a different order, then that register will be used and the value suitably modified to
102
    /// make the equality condition work.  This is maintaining legacy (tested) behavior of
103
    /// [DAGCircuit::compose]; nowhere else does this, and in general this would require *far*
104
    /// more complex classical rewriting than Qiskit needs to worry about in the full expression
105
    /// era.
106
    pub fn map_condition<F>(
52✔
107
        &self,
52✔
108
        condition: &Condition,
52✔
109
        allow_reorder: bool,
52✔
110
        mut add_register: F,
52✔
111
    ) -> PyResult<Condition>
52✔
112
    where
52✔
113
        F: FnMut(&ClassicalRegister) -> PyResult<()>,
52✔
114
    {
115
        Ok(match condition {
52✔
116
            Condition::Bit(target, value) => {
38✔
117
                Condition::Bit(self.bit_map.get(target).unwrap().clone(), *value)
38✔
118
            }
119
            Condition::Expr(e) => Condition::Expr(self.map_expr(e, &mut add_register)?),
12✔
120
            Condition::Register(target, value) => {
2✔
121
                if !allow_reorder {
2✔
122
                    return Ok(Condition::Register(
123
                        self.map_register(target, &mut add_register)?,
2✔
124
                        *value,
2✔
125
                    ));
126
                }
×
127
                // This is maintaining the legacy behavior of `DAGCircuit.compose`.  We don't
128
                // attempt to speed-up this lookup with a cache, since that would just make the more
129
                // standard cases more annoying to deal with.
130

131
                let mapped_bits_order = target
×
132
                    .bits()
×
133
                    .map(|b| self.bit_map.get(&b).unwrap().clone())
×
134
                    .collect::<Vec<_>>();
×
135
                let mapped_bits_set: HashSet<ShareableClbit> =
×
136
                    mapped_bits_order.iter().cloned().collect();
×
137

138
                let mapped_theirs = self
×
139
                    .target_cregs
×
140
                    .iter()
×
141
                    .find(|register| {
×
142
                        let register_set: HashSet<ShareableClbit> = register.iter().collect();
×
143
                        mapped_bits_set == register_set
×
144
                    })
×
145
                    .cloned()
×
146
                    .map(Ok::<_, PyErr>)
×
147
                    .unwrap_or_else(|| {
×
148
                        let mapped_theirs =
×
149
                            ClassicalRegister::new_alias(None, mapped_bits_order.clone());
×
150
                        add_register(&mapped_theirs)?;
×
151
                        Ok(mapped_theirs)
×
152
                    })?;
×
153

154
                let new_order: HashMap<ShareableClbit, usize> = mapped_bits_order
×
155
                    .into_iter()
×
156
                    .enumerate()
×
157
                    .map(|(i, bit)| (bit, i))
×
158
                    .collect();
×
159

160
                // Build the little-endian bit string
161
                let value_bits: Vec<char> = format!("{:0width$b}", value, width = target.len())
×
162
                    .chars()
×
163
                    .rev()
×
164
                    .collect();
×
165

166
                // Reorder bits and reverse again to go back to big-endian for final conversion
167
                let mapped_str: String = mapped_theirs
×
168
                    .iter() // TODO: we should probably not need to collect to Vec here. Why do we?
×
169
                    .collect::<Vec<_>>()
×
170
                    .into_iter()
×
171
                    .map(|bit| value_bits[*new_order.get(&bit).unwrap()])
×
172
                    .rev()
×
173
                    .collect();
×
174

175
                Condition::Register(
×
176
                    mapped_theirs,
×
177
                    usize::from_str_radix(&mapped_str, 2).unwrap(),
×
178
                )
×
179
            }
180
        })
181
    }
52✔
182

183
    /// Map the real-time variables in a `target` of a `SwitchCaseOp` to the new
184
    /// circuit.
185
    pub fn map_target<F>(
12✔
186
        &self,
12✔
187
        target: &SwitchTarget,
12✔
188
        mut add_register: F,
12✔
189
    ) -> PyResult<SwitchTarget>
12✔
190
    where
12✔
191
        F: FnMut(&ClassicalRegister) -> PyResult<()>,
12✔
192
    {
193
        Ok(match target {
12✔
194
            SwitchTarget::Bit(bit) => SwitchTarget::Bit(self.bit_map.get(bit).cloned().unwrap()),
×
195
            SwitchTarget::Register(register) => {
×
196
                SwitchTarget::Register(self.map_register(register, &mut add_register)?)
×
197
            }
198
            SwitchTarget::Expr(expr) => SwitchTarget::Expr(self.map_expr(expr, &mut add_register)?),
12✔
199
        })
200
    }
12✔
201

202
    /// Map the variables in an [expr::Expr] node to the new circuit.
203
    pub fn map_expr<F>(&self, expr: &expr::Expr, mut add_register: F) -> PyResult<expr::Expr>
24✔
204
    where
24✔
205
        F: FnMut(&ClassicalRegister) -> PyResult<()>,
24✔
206
    {
207
        let mut mapped = expr.clone();
24✔
208
        mapped.visit_mut(|e| match e {
58✔
209
            expr::ExprRefMut::Var(var) => match var {
28✔
210
                expr::Var::Standalone { .. } => {
211
                    if let Some(mapping) = self.var_map.get(var).cloned() {
6✔
212
                        *var = mapping;
6✔
213
                    }
6✔
214
                    Ok(())
6✔
215
                }
216
                expr::Var::Bit { bit } => {
12✔
217
                    let bit = self.bit_map.get(bit).cloned().unwrap();
12✔
218
                    *var = expr::Var::Bit { bit };
12✔
219
                    Ok(())
12✔
220
                }
221
                expr::Var::Register { register, ty } => {
10✔
222
                    let ty = *ty;
10✔
223
                    let register = self.map_register(register, &mut add_register)?;
10✔
224
                    *var = expr::Var::Register { register, ty };
10✔
225
                    Ok(())
10✔
226
                }
227
            },
228
            expr::ExprRefMut::Stretch(stretch) => {
×
229
                if let Some(mapping) = self.stretch_map.get(stretch).cloned() {
×
230
                    *stretch = mapping;
×
231
                }
×
232
                Ok(())
×
233
            }
234
            _ => Ok(()),
30✔
235
        })?;
58✔
236
        Ok(mapped)
24✔
237
    }
24✔
238

239
    /// Map the target's registers to suitable equivalents in the destination, adding an
240
    /// extra one if there's no exact match."""
241
    fn map_register<F>(
12✔
242
        &self,
12✔
243
        theirs: &ClassicalRegister,
12✔
244
        mut add_register: F,
12✔
245
    ) -> PyResult<ClassicalRegister>
12✔
246
    where
12✔
247
        F: FnMut(&ClassicalRegister) -> PyResult<()>,
12✔
248
    {
249
        if let Some(mapped_theirs) = self.register_map.borrow().get(theirs.name()) {
12✔
250
            return Ok(mapped_theirs.clone());
2✔
251
        }
10✔
252

253
        let mapped_bits: Vec<_> = theirs.iter().map(|b| self.bit_map[&b].clone()).collect();
22✔
254
        let mapped_theirs = self
10✔
255
            .target_cregs
10✔
256
            .iter()
10✔
257
            .find(|register| {
14✔
258
                let register: Vec<_> = register.bits().collect();
14✔
259
                mapped_bits == register
14✔
260
            })
14✔
261
            .cloned()
10✔
262
            .map(Ok::<_, PyErr>)
10✔
263
            .unwrap_or_else(|| {
10✔
264
                let mapped_theirs = ClassicalRegister::new_alias(None, mapped_bits.clone());
×
265
                add_register(&mapped_theirs)?;
×
266
                Ok(mapped_theirs)
×
267
            })?;
×
268

269
        self.register_map
10✔
270
            .borrow_mut()
10✔
271
            .insert(theirs.name().to_string(), mapped_theirs.clone());
10✔
272
        Ok(mapped_theirs)
10✔
273
    }
12✔
274
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc