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

Qiskit / qiskit / 16914043956

12 Aug 2025 03:53PM UTC coverage: 87.597% (-0.08%) from 87.681%
16914043956

Pull #14568

github

web-flow
Merge 0701b7dca into f57bd12b3
Pull Request #14568: Port control-flow operations to Rust.

2687 of 3124 new or added lines in 58 files covered. (86.01%)

229 existing lines in 26 files now uncovered.

83115 of 94883 relevant lines covered (87.6%)

557008.78 hits per line

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

61.43
/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 crate::operations::{Condition, Target};
16
use hashbrown::{HashMap, HashSet};
17
use num_bigint::BigUint;
18
use num_traits::Num;
19
use pyo3::prelude::*;
20
use pyo3::PyResult;
21
use std::cell::RefCell;
22

23
pub(crate) struct VariableMapper {
24
    target_cregs: Vec<ClassicalRegister>,
25
    register_map: RefCell<HashMap<String, ClassicalRegister>>,
26
    bit_map: HashMap<ShareableClbit, ShareableClbit>,
27
    var_map: HashMap<expr::Var, expr::Var>,
28
    stretch_map: HashMap<expr::Stretch, expr::Stretch>,
29
}
30

31
impl VariableMapper {
32
    /// Constructs a new mapper.
33
    ///
34
    /// The `stretch_map` is only used for direct calls to [VariableMapper::map_expr]
35
    /// since `condition`s and `target`s expressions are never durations. Provide
36
    /// [None] if you don't need this.
37
    pub fn new(
103,336✔
38
        target_cregs: Vec<ClassicalRegister>,
103,336✔
39
        bit_map: HashMap<ShareableClbit, ShareableClbit>,
103,336✔
40
        var_map: HashMap<expr::Var, expr::Var>,
103,336✔
41
        stretch_map: Option<HashMap<expr::Stretch, expr::Stretch>>,
103,336✔
42
    ) -> Self {
103,336✔
43
        Self {
103,336✔
44
            target_cregs,
103,336✔
45
            register_map: RefCell::default(),
103,336✔
46
            bit_map,
103,336✔
47
            var_map,
103,336✔
48
            stretch_map: stretch_map.unwrap_or_default(),
103,336✔
49
        }
103,336✔
50
    }
103,336✔
51

52
    /// Map the given `condition` so that it only references variables in the destination
53
    /// circuit (as given to this class on initialization).
54
    ///
55
    /// If `allow_reorder` is `true`, then when a legacy condition (the two-tuple form) is made
56
    /// on a register that has a counterpart in the destination with all the same (mapped) bits but
57
    /// in a different order, then that register will be used and the value suitably modified to
58
    /// make the equality condition work.  This is maintaining legacy (tested) behavior of
59
    /// [DAGCircuit::compose]; nowhere else does this, and in general this would require *far*
60
    /// more complex classical rewriting than Qiskit needs to worry about in the full expression
61
    /// era.
62
    pub fn map_condition<F>(
52✔
63
        &self,
52✔
64
        condition: &Condition,
52✔
65
        allow_reorder: bool,
52✔
66
        mut add_register: F,
52✔
67
    ) -> PyResult<Condition>
52✔
68
    where
52✔
69
        F: FnMut(&ClassicalRegister) -> PyResult<()>,
52✔
70
    {
71
        Ok(match condition {
52✔
72
            Condition::Bit(target, value) => {
38✔
73
                Condition::Bit(self.bit_map.get(target).unwrap().clone(), *value)
38✔
74
            }
75
            Condition::Expr(e) => Condition::Expr(self.map_expr(e, &mut add_register)?),
12✔
76
            Condition::Register(target, value) => {
2✔
77
                if !allow_reorder {
2✔
78
                    return Ok(Condition::Register(
79
                        self.map_register(target, &mut add_register)?,
2✔
80
                        value.clone(),
2✔
81
                    ));
82
                }
×
83
                // This is maintaining the legacy behavior of `DAGCircuit.compose`.  We don't
84
                // attempt to speed-up this lookup with a cache, since that would just make the more
85
                // standard cases more annoying to deal with.
86

87
                let mapped_bits_order = target
×
88
                    .bits()
×
89
                    .map(|b| self.bit_map.get(&b).unwrap().clone())
×
90
                    .collect::<Vec<_>>();
×
91
                let mapped_bits_set: HashSet<ShareableClbit> =
×
92
                    mapped_bits_order.iter().cloned().collect();
×
93

94
                let mapped_theirs = self
×
95
                    .target_cregs
×
96
                    .iter()
×
97
                    .find(|register| {
×
98
                        let register_set: HashSet<ShareableClbit> = register.iter().collect();
×
99
                        mapped_bits_set == register_set
×
100
                    })
×
101
                    .cloned()
×
102
                    .map(Ok::<_, PyErr>)
×
103
                    .unwrap_or_else(|| {
×
104
                        let mapped_theirs =
×
105
                            ClassicalRegister::new_alias(None, mapped_bits_order.clone());
×
106
                        add_register(&mapped_theirs)?;
×
107
                        Ok(mapped_theirs)
×
108
                    })?;
×
109

110
                let new_order: HashMap<ShareableClbit, usize> = mapped_bits_order
×
111
                    .into_iter()
×
112
                    .enumerate()
×
113
                    .map(|(i, bit)| (bit, i))
×
114
                    .collect();
×
115

116
                // Build the little-endian bit string
117
                let value_bits: Vec<char> = format!("{:0width$b}", value, width = target.len())
×
118
                    .chars()
×
119
                    .rev()
×
120
                    .collect();
×
121

122
                // Reorder bits and reverse again to go back to big-endian for final conversion
123
                let mapped_str: String = mapped_theirs
×
124
                    .iter() // TODO: we should probably not need to collect to Vec here. Why do we?
×
125
                    .collect::<Vec<_>>()
×
126
                    .into_iter()
×
127
                    .map(|bit| value_bits[*new_order.get(&bit).unwrap()])
×
128
                    .rev()
×
129
                    .collect();
×
130

131
                Condition::Register(
×
132
                    mapped_theirs,
×
NEW
133
                    BigUint::from_str_radix(&mapped_str, 2).unwrap(),
×
134
                )
×
135
            }
136
        })
137
    }
52✔
138

139
    /// Map the real-time variables in a `target` of a `SwitchCaseOp` to the new
140
    /// circuit.
141
    pub fn map_target<F>(&self, target: &Target, mut add_register: F) -> PyResult<Target>
12✔
142
    where
12✔
143
        F: FnMut(&ClassicalRegister) -> PyResult<()>,
12✔
144
    {
145
        Ok(match target {
12✔
146
            Target::Bit(bit) => Target::Bit(self.bit_map.get(bit).cloned().unwrap()),
×
147
            Target::Register(register) => {
×
148
                Target::Register(self.map_register(register, &mut add_register)?)
×
149
            }
150
            Target::Expr(expr) => Target::Expr(self.map_expr(expr, &mut add_register)?),
12✔
151
        })
152
    }
12✔
153

154
    /// Map the variables in an [expr::Expr] node to the new circuit.
155
    pub fn map_expr<F>(&self, expr: &expr::Expr, mut add_register: F) -> PyResult<expr::Expr>
24✔
156
    where
24✔
157
        F: FnMut(&ClassicalRegister) -> PyResult<()>,
24✔
158
    {
159
        let mut mapped = expr.clone();
24✔
160
        mapped.visit_mut(|e| match e {
58✔
161
            expr::ExprRefMut::Var(var) => match var {
28✔
162
                expr::Var::Standalone { .. } => {
163
                    if let Some(mapping) = self.var_map.get(var).cloned() {
6✔
164
                        *var = mapping;
6✔
165
                    }
6✔
166
                    Ok(())
6✔
167
                }
168
                expr::Var::Bit { bit } => {
12✔
169
                    let bit = self.bit_map.get(bit).cloned().unwrap();
12✔
170
                    *var = expr::Var::Bit { bit };
12✔
171
                    Ok(())
12✔
172
                }
173
                expr::Var::Register { register, ty } => {
10✔
174
                    let ty = *ty;
10✔
175
                    let register = self.map_register(register, &mut add_register)?;
10✔
176
                    *var = expr::Var::Register { register, ty };
10✔
177
                    Ok(())
10✔
178
                }
179
            },
180
            expr::ExprRefMut::Stretch(stretch) => {
×
181
                if let Some(mapping) = self.stretch_map.get(stretch).cloned() {
×
182
                    *stretch = mapping;
×
183
                }
×
184
                Ok(())
×
185
            }
186
            _ => Ok(()),
30✔
187
        })?;
58✔
188
        Ok(mapped)
24✔
189
    }
24✔
190

191
    /// Map the target's registers to suitable equivalents in the destination, adding an
192
    /// extra one if there's no exact match."""
193
    fn map_register<F>(
12✔
194
        &self,
12✔
195
        theirs: &ClassicalRegister,
12✔
196
        mut add_register: F,
12✔
197
    ) -> PyResult<ClassicalRegister>
12✔
198
    where
12✔
199
        F: FnMut(&ClassicalRegister) -> PyResult<()>,
12✔
200
    {
201
        if let Some(mapped_theirs) = self.register_map.borrow().get(theirs.name()) {
12✔
202
            return Ok(mapped_theirs.clone());
2✔
203
        }
10✔
204

205
        let mapped_bits: Vec<_> = theirs.iter().map(|b| self.bit_map[&b].clone()).collect();
22✔
206
        let mapped_theirs = self
10✔
207
            .target_cregs
10✔
208
            .iter()
10✔
209
            .find(|register| {
14✔
210
                let register: Vec<_> = register.bits().collect();
14✔
211
                mapped_bits == register
14✔
212
            })
14✔
213
            .cloned()
10✔
214
            .map(Ok::<_, PyErr>)
10✔
215
            .unwrap_or_else(|| {
10✔
216
                let mapped_theirs = ClassicalRegister::new_alias(None, mapped_bits.clone());
×
217
                add_register(&mapped_theirs)?;
×
218
                Ok(mapped_theirs)
×
219
            })?;
×
220

221
        self.register_map
10✔
222
            .borrow_mut()
10✔
223
            .insert(theirs.name().to_string(), mapped_theirs.clone());
10✔
224
        Ok(mapped_theirs)
10✔
225
    }
12✔
226
}
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