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

Qiskit / qiskit / 15645153029

13 Jun 2025 10:31PM CUT coverage: 88.021% (+0.02%) from 88.004%
15645153029

Pull #14599

github

web-flow
Merge 948c85bfa into 4d7d46ebc
Pull Request #14599: Port `expr::structurally_equivalent` to Rust.

850 of 951 new or added lines in 5 files covered. (89.38%)

25 existing lines in 6 files now uncovered.

83854 of 95266 relevant lines covered (88.02%)

514730.45 hits per line

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

61.9
/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 pyo3::{Bound, FromPyObject, PyAny, PyResult};
18
use std::cell::RefCell;
19

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

30
impl<'py> FromPyObject<'py> for Condition {
31
    fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
52✔
32
        if let Ok((bit, value)) = ob.extract::<(ShareableClbit, usize)>() {
52✔
33
            Ok(Condition::Bit(bit, value))
38✔
34
        } else if let Ok((register, value)) = ob.extract::<(ClassicalRegister, usize)>() {
14✔
35
            Ok(Condition::Register(register, value))
2✔
36
        } else {
37
            Ok(Condition::Expr(ob.extract()?))
12✔
38
        }
39
    }
52✔
40
}
41

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

52
impl<'py> FromPyObject<'py> for Target {
53
    fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult<Self> {
12✔
54
        if let Ok(bit) = ob.extract::<ShareableClbit>() {
12✔
NEW
55
            Ok(Target::Bit(bit))
×
56
        } else if let Ok(register) = ob.extract::<ClassicalRegister>() {
12✔
NEW
57
            Ok(Target::Register(register))
×
58
        } else {
59
            Ok(Target::Expr(ob.extract()?))
12✔
60
        }
61
    }
12✔
62
}
63

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

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

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

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

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

NEW
151
                let new_order: HashMap<ShareableClbit, usize> = mapped_bits_order
×
NEW
152
                    .into_iter()
×
NEW
153
                    .enumerate()
×
NEW
154
                    .map(|(i, bit)| (bit, i))
×
NEW
155
                    .collect();
×
NEW
156

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

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

×
NEW
172
                Condition::Register(
×
NEW
173
                    mapped_theirs,
×
NEW
174
                    usize::from_str_radix(&mapped_str, 2).unwrap(),
×
NEW
175
                )
×
176
            }
177
        })
178
    }
52✔
179

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

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

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

10✔
246
        let mapped_bits: Vec<_> = theirs.iter().map(|b| self.bit_map[&b].clone()).collect();
22✔
247
        let mapped_theirs = self
10✔
248
            .target_cregs
10✔
249
            .iter()
10✔
250
            .find(|register| {
14✔
251
                let register: Vec<_> = register.bits().collect();
14✔
252
                mapped_bits == register
14✔
253
            })
14✔
254
            .cloned()
10✔
255
            .map(Ok::<_, PyErr>)
10✔
256
            .unwrap_or_else(|| {
10✔
NEW
257
                let mapped_theirs = ClassicalRegister::new_alias(None, mapped_bits.clone());
×
NEW
258
                add_register(&mapped_theirs)?;
×
NEW
259
                Ok(mapped_theirs)
×
260
            })?;
10✔
261

262
        self.register_map
10✔
263
            .borrow_mut()
10✔
264
            .insert(theirs.name().to_string(), mapped_theirs.clone());
10✔
265
        Ok(mapped_theirs)
10✔
266
    }
12✔
267
}
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

© 2025 Coveralls, Inc