quantrs2_circuit/optimization/
noise.rs

1//! Noise-aware circuit optimization
2//!
3//! This module provides optimization passes that consider quantum device noise
4//! characteristics when making optimization decisions.
5
6use crate::builder::Circuit;
7use crate::optimization::passes::OptimizationPassExt;
8use crate::optimization::{CircuitMetrics, CostModel, OptimizationPass};
9use crate::routing::CouplingMap;
10use quantrs2_core::{
11    error::{QuantRS2Error, QuantRS2Result},
12    gate::GateOp,
13    qubit::QubitId,
14};
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17
18/// Noise model for quantum devices
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct NoiseModel {
21    /// Single-qubit gate error rates (per qubit)
22    pub single_qubit_errors: HashMap<usize, f64>,
23    /// Two-qubit gate error rates (per qubit pair)
24    pub two_qubit_errors: HashMap<(usize, usize), f64>,
25    /// T1 coherence times (microseconds)
26    pub t1_times: HashMap<usize, f64>,
27    /// T2 coherence times (microseconds)
28    pub t2_times: HashMap<usize, f64>,
29    /// Readout fidelities
30    pub readout_fidelities: HashMap<usize, f64>,
31    /// Gate execution times (nanoseconds)
32    pub gate_times: HashMap<String, f64>,
33    /// Crosstalk matrix
34    pub crosstalk_matrix: Option<Vec<Vec<f64>>>,
35}
36
37impl NoiseModel {
38    /// Create a new empty noise model
39    #[must_use]
40    pub fn new() -> Self {
41        Self {
42            single_qubit_errors: HashMap::new(),
43            two_qubit_errors: HashMap::new(),
44            t1_times: HashMap::new(),
45            t2_times: HashMap::new(),
46            readout_fidelities: HashMap::new(),
47            gate_times: HashMap::new(),
48            crosstalk_matrix: None,
49        }
50    }
51
52    /// Create a uniform noise model for testing
53    #[must_use]
54    pub fn uniform(num_qubits: usize) -> Self {
55        let mut model = Self::new();
56
57        // Default error rates
58        let single_error = 1e-3;
59        let two_qubit_error = 1e-2;
60        let t1 = 100.0; // microseconds
61        let t2 = 50.0; // microseconds
62        let readout_fidelity = 0.99;
63
64        for i in 0..num_qubits {
65            model.single_qubit_errors.insert(i, single_error);
66            model.t1_times.insert(i, t1);
67            model.t2_times.insert(i, t2);
68            model.readout_fidelities.insert(i, readout_fidelity);
69
70            for j in (i + 1)..num_qubits {
71                model.two_qubit_errors.insert((i, j), two_qubit_error);
72            }
73        }
74
75        // Default gate times (nanoseconds)
76        model.gate_times.insert("H".to_string(), 20.0);
77        model.gate_times.insert("X".to_string(), 20.0);
78        model.gate_times.insert("Y".to_string(), 20.0);
79        model.gate_times.insert("Z".to_string(), 0.0); // Virtual Z
80        model.gate_times.insert("S".to_string(), 0.0);
81        model.gate_times.insert("T".to_string(), 0.0);
82        model.gate_times.insert("CNOT".to_string(), 200.0);
83        model.gate_times.insert("CZ".to_string(), 200.0);
84        model.gate_times.insert("SWAP".to_string(), 600.0); // 3 CNOTs
85
86        model
87    }
88
89    /// Create a realistic noise model based on IBM devices
90    #[must_use]
91    pub fn ibm_like(num_qubits: usize) -> Self {
92        let mut model = Self::new();
93
94        // IBM-like parameters
95        for i in 0..num_qubits {
96            model.single_qubit_errors.insert(i, 1e-4); // Good single-qubit gates
97            model.t1_times.insert(i, (i as f64).mul_add(10.0, 100.0)); // Varying T1
98            model.t2_times.insert(i, (i as f64).mul_add(5.0, 80.0)); // Varying T2
99            model
100                .readout_fidelities
101                .insert(i, 0.95 + (i as f64 * 0.01).min(0.04));
102
103            for j in (i + 1)..num_qubits {
104                // Two-qubit errors vary by connectivity
105                let error = if (i as isize - j as isize).abs() == 1 {
106                    5e-3 // Adjacent qubits
107                } else {
108                    1e-2 // Non-adjacent (if connected)
109                };
110                model.two_qubit_errors.insert((i, j), error);
111            }
112        }
113
114        // IBM-like gate times
115        model.gate_times.insert("H".to_string(), 35.0);
116        model.gate_times.insert("X".to_string(), 35.0);
117        model.gate_times.insert("Y".to_string(), 35.0);
118        model.gate_times.insert("Z".to_string(), 0.0);
119        model.gate_times.insert("S".to_string(), 0.0);
120        model.gate_times.insert("T".to_string(), 0.0);
121        model.gate_times.insert("CNOT".to_string(), 500.0);
122        model.gate_times.insert("CZ".to_string(), 300.0);
123        model.gate_times.insert("SWAP".to_string(), 1500.0);
124
125        model
126    }
127
128    /// Get error rate for a single-qubit gate
129    #[must_use]
130    pub fn single_qubit_error(&self, qubit: usize) -> f64 {
131        self.single_qubit_errors
132            .get(&qubit)
133            .copied()
134            .unwrap_or(1e-3)
135    }
136
137    /// Get error rate for a two-qubit gate
138    #[must_use]
139    pub fn two_qubit_error(&self, q1: usize, q2: usize) -> f64 {
140        let key = (q1.min(q2), q1.max(q2));
141        self.two_qubit_errors.get(&key).copied().unwrap_or(1e-2)
142    }
143
144    /// Get T1 time for a qubit
145    #[must_use]
146    pub fn t1_time(&self, qubit: usize) -> f64 {
147        self.t1_times.get(&qubit).copied().unwrap_or(100.0)
148    }
149
150    /// Get T2 time for a qubit
151    #[must_use]
152    pub fn t2_time(&self, qubit: usize) -> f64 {
153        self.t2_times.get(&qubit).copied().unwrap_or(50.0)
154    }
155
156    /// Get gate execution time
157    #[must_use]
158    pub fn gate_time(&self, gate_name: &str) -> f64 {
159        self.gate_times.get(gate_name).copied().unwrap_or(100.0)
160    }
161
162    /// Calculate error probability for a gate
163    pub fn gate_error_probability(&self, gate: &dyn GateOp) -> f64 {
164        let qubits = gate.qubits();
165        match qubits.len() {
166            1 => self.single_qubit_error(qubits[0].id() as usize),
167            2 => self.two_qubit_error(qubits[0].id() as usize, qubits[1].id() as usize),
168            _ => 0.1, // Multi-qubit gates are expensive
169        }
170    }
171}
172
173impl Default for NoiseModel {
174    fn default() -> Self {
175        Self::new()
176    }
177}
178
179/// Noise-aware cost model that considers device characteristics
180#[derive(Debug, Clone)]
181pub struct NoiseAwareCostModel {
182    noise_model: NoiseModel,
183    coupling_map: Option<CouplingMap>,
184    /// Weight for error rate in cost calculation
185    pub error_weight: f64,
186    /// Weight for execution time in cost calculation
187    pub time_weight: f64,
188    /// Weight for coherence effects
189    pub coherence_weight: f64,
190}
191
192impl NoiseAwareCostModel {
193    /// Create a new noise-aware cost model
194    #[must_use]
195    pub const fn new(noise_model: NoiseModel) -> Self {
196        Self {
197            noise_model,
198            coupling_map: None,
199            error_weight: 1000.0,
200            time_weight: 1.0,
201            coherence_weight: 100.0,
202        }
203    }
204
205    /// Set the coupling map for connectivity analysis
206    #[must_use]
207    pub fn with_coupling_map(mut self, coupling_map: CouplingMap) -> Self {
208        self.coupling_map = Some(coupling_map);
209        self
210    }
211
212    /// Calculate cost for a single gate
213    pub fn gate_cost(&self, gate: &dyn GateOp) -> f64 {
214        let error_prob = self.noise_model.gate_error_probability(gate);
215        let exec_time = self.noise_model.gate_time(gate.name());
216
217        // Base cost from error probability and execution time
218        let mut cost = self
219            .error_weight
220            .mul_add(error_prob, self.time_weight * exec_time);
221
222        // Add coherence penalty for long operations
223        if exec_time > 100.0 {
224            let qubits = gate.qubits();
225            for qubit in qubits {
226                let t2 = self.noise_model.t2_time(qubit.id() as usize);
227                let coherence_penalty = exec_time / (t2 * 1000.0); // Convert μs to ns
228                cost += self.coherence_weight * coherence_penalty;
229            }
230        }
231
232        cost
233    }
234
235    /// Calculate total circuit cost
236    #[must_use]
237    pub fn circuit_cost<const N: usize>(&self, circuit: &Circuit<N>) -> f64 {
238        let mut total_cost = 0.0;
239        let mut total_time = 0.0;
240
241        // Simple sequential cost model (no parallelism analysis)
242        for gate in circuit.gates() {
243            total_cost += self.gate_cost(gate.as_ref());
244            total_time += self.noise_model.gate_time(gate.name());
245        }
246
247        // Add coherence penalties for total execution time
248        if total_time > 0.0 {
249            for i in 0..N {
250                let t2 = self.noise_model.t2_time(i);
251                let coherence_penalty = total_time / (t2 * 1000.0);
252                total_cost += self.coherence_weight * coherence_penalty;
253            }
254        }
255
256        total_cost
257    }
258}
259
260impl CostModel for NoiseAwareCostModel {
261    fn gate_cost(&self, gate: &dyn GateOp) -> f64 {
262        let error_prob = self.noise_model.gate_error_probability(gate);
263        let exec_time = self.noise_model.gate_time(gate.name());
264
265        // Base cost from error probability and execution time
266        let mut cost = self
267            .error_weight
268            .mul_add(error_prob, self.time_weight * exec_time);
269
270        // Add coherence penalty for long operations
271        if exec_time > 100.0 {
272            let qubits = gate.qubits();
273            for qubit in qubits {
274                let t2 = self.noise_model.t2_time(qubit.id() as usize);
275                let coherence_penalty = exec_time / (t2 * 1000.0); // Convert μs to ns
276                cost += self.coherence_weight * coherence_penalty;
277            }
278        }
279
280        cost
281    }
282
283    fn circuit_cost_from_gates(&self, gates: &[Box<dyn GateOp>]) -> f64 {
284        let mut total_cost = 0.0;
285        let mut total_time = 0.0;
286
287        // Simple sequential cost model (no parallelism analysis)
288        for gate in gates {
289            total_cost += self.gate_cost(gate.as_ref());
290            total_time += self.noise_model.gate_time(gate.name());
291        }
292
293        total_cost
294    }
295
296    fn weights(&self) -> super::cost_model::CostWeights {
297        super::cost_model::CostWeights {
298            gate_count: 1.0,
299            execution_time: self.time_weight,
300            error_rate: self.error_weight,
301            circuit_depth: 1.0,
302        }
303    }
304
305    fn is_native(&self, gate: &dyn GateOp) -> bool {
306        // Consider basic gates as native
307        matches!(
308            gate.name(),
309            "H" | "X" | "Y" | "Z" | "S" | "T" | "CNOT" | "CZ"
310        )
311    }
312}
313
314/// Optimization pass that reduces circuit depth to minimize decoherence
315#[derive(Debug, Clone)]
316pub struct CoherenceOptimization {
317    noise_model: NoiseModel,
318    max_parallel_gates: usize,
319}
320
321impl CoherenceOptimization {
322    /// Create a new coherence optimization pass
323    #[must_use]
324    pub const fn new(noise_model: NoiseModel) -> Self {
325        Self {
326            noise_model,
327            max_parallel_gates: 10,
328        }
329    }
330
331    /// Analyze parallelizable gates
332    fn find_parallel_gates<const N: usize>(&self, circuit: &Circuit<N>) -> Vec<Vec<usize>> {
333        let gates = circuit.gates();
334        let mut parallel_groups = Vec::new();
335        let mut used_qubits = vec![false; N];
336        let mut current_group = Vec::new();
337
338        for (i, gate) in gates.iter().enumerate() {
339            let gate_qubits: Vec<_> = gate.qubits().iter().map(|q| q.id() as usize).collect();
340
341            // Check if this gate conflicts with current group
342            let conflicts = gate_qubits.iter().any(|&q| used_qubits[q]);
343
344            if conflicts || current_group.len() >= self.max_parallel_gates {
345                // Start new group
346                if !current_group.is_empty() {
347                    parallel_groups.push(current_group);
348                    current_group = Vec::new();
349                    used_qubits.fill(false);
350                }
351            }
352
353            // Add gate to current group
354            current_group.push(i);
355            for &q in &gate_qubits {
356                used_qubits[q] = true;
357            }
358        }
359
360        if !current_group.is_empty() {
361            parallel_groups.push(current_group);
362        }
363
364        parallel_groups
365    }
366}
367
368impl OptimizationPass for CoherenceOptimization {
369    fn name(&self) -> &'static str {
370        "CoherenceOptimization"
371    }
372
373    fn apply_to_gates(
374        &self,
375        gates: Vec<Box<dyn GateOp>>,
376        _cost_model: &dyn CostModel,
377    ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
378        // For now, return the original gates
379        // TODO: Implement gate reordering based on parallel groups
380        Ok(gates)
381    }
382
383    fn should_apply(&self) -> bool {
384        true
385    }
386}
387
388/// Optimization pass that prioritizes low-noise qubit assignments
389#[derive(Debug, Clone)]
390pub struct NoiseAwareMapping {
391    noise_model: NoiseModel,
392    coupling_map: CouplingMap,
393}
394
395impl NoiseAwareMapping {
396    /// Create a new noise-aware mapping pass
397    #[must_use]
398    pub const fn new(noise_model: NoiseModel, coupling_map: CouplingMap) -> Self {
399        Self {
400            noise_model,
401            coupling_map,
402        }
403    }
404
405    /// Score a qubit assignment based on noise characteristics
406    fn score_assignment(&self, logical_qubits: &[usize], physical_qubits: &[usize]) -> f64 {
407        let mut score = 0.0;
408
409        // Prefer qubits with lower error rates
410        for (&logical, &physical) in logical_qubits.iter().zip(physical_qubits.iter()) {
411            score += 1.0 / (1.0 + self.noise_model.single_qubit_error(physical));
412        }
413
414        // Prefer assignments that minimize two-qubit gate errors
415        for i in 0..logical_qubits.len() {
416            for j in (i + 1)..logical_qubits.len() {
417                let p1 = physical_qubits[i];
418                let p2 = physical_qubits[j];
419
420                if self.coupling_map.are_connected(p1, p2) {
421                    let error = self.noise_model.two_qubit_error(p1, p2);
422                    score += 1.0 / (1.0 + error);
423                }
424            }
425        }
426
427        score
428    }
429}
430
431impl OptimizationPass for NoiseAwareMapping {
432    fn name(&self) -> &'static str {
433        "NoiseAwareMapping"
434    }
435
436    fn apply_to_gates(
437        &self,
438        gates: Vec<Box<dyn GateOp>>,
439        _cost_model: &dyn CostModel,
440    ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
441        // For now, return the original gates
442        // TODO: Implement qubit remapping based on noise characteristics
443        Ok(gates)
444    }
445
446    fn should_apply(&self) -> bool {
447        true
448    }
449}
450
451/// Optimization pass that inserts dynamical decoupling sequences
452#[derive(Debug, Clone)]
453pub struct DynamicalDecoupling {
454    noise_model: NoiseModel,
455    /// Minimum idle time to insert decoupling (nanoseconds)
456    pub min_idle_time: f64,
457    /// Decoupling sequence type
458    pub sequence_type: DecouplingSequence,
459}
460
461/// Types of dynamical decoupling sequences
462#[derive(Debug, Clone)]
463pub enum DecouplingSequence {
464    /// XY-4 sequence
465    XY4,
466    /// CPMG sequence
467    CPMG,
468    /// XY-8 sequence
469    XY8,
470}
471
472impl DynamicalDecoupling {
473    /// Create a new dynamical decoupling pass
474    #[must_use]
475    pub const fn new(noise_model: NoiseModel) -> Self {
476        Self {
477            noise_model,
478            min_idle_time: 1000.0, // 1 microsecond
479            sequence_type: DecouplingSequence::XY4,
480        }
481    }
482
483    /// Calculate idle times between gates
484    fn analyze_idle_times<const N: usize>(&self, circuit: &Circuit<N>) -> Vec<(usize, f64)> {
485        let mut idle_times = Vec::new();
486
487        // Simplified analysis - in practice would need detailed scheduling
488        for (i, gate) in circuit.gates().iter().enumerate() {
489            let exec_time = self.noise_model.gate_time(gate.name());
490
491            // Check for potential idle time after this gate
492            if i + 1 < circuit.num_gates() {
493                // Simplified: assume some idle time exists
494                idle_times.push((i, 500.0)); // 500ns idle time
495            }
496        }
497
498        idle_times
499    }
500}
501
502impl OptimizationPass for DynamicalDecoupling {
503    fn name(&self) -> &'static str {
504        "DynamicalDecoupling"
505    }
506
507    fn apply_to_gates(
508        &self,
509        gates: Vec<Box<dyn GateOp>>,
510        _cost_model: &dyn CostModel,
511    ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
512        // For now, return the original gates
513        // TODO: Insert decoupling sequences during idle periods
514        Ok(gates)
515    }
516
517    fn should_apply(&self) -> bool {
518        true
519    }
520}
521
522/// Comprehensive noise-aware optimization pass manager
523#[derive(Debug)]
524pub struct NoiseAwareOptimizer {
525    noise_model: NoiseModel,
526    coupling_map: Option<CouplingMap>,
527    cost_model: NoiseAwareCostModel,
528}
529
530impl NoiseAwareOptimizer {
531    /// Create a new noise-aware optimizer
532    #[must_use]
533    pub fn new(noise_model: NoiseModel) -> Self {
534        let cost_model = NoiseAwareCostModel::new(noise_model.clone());
535
536        Self {
537            noise_model,
538            coupling_map: None,
539            cost_model,
540        }
541    }
542
543    /// Set the coupling map
544    #[must_use]
545    pub fn with_coupling_map(mut self, coupling_map: CouplingMap) -> Self {
546        self.cost_model = self.cost_model.with_coupling_map(coupling_map.clone());
547        self.coupling_map = Some(coupling_map);
548        self
549    }
550
551    /// Get all noise-aware optimization passes
552    #[must_use]
553    pub fn get_passes(&self) -> Vec<Box<dyn OptimizationPass>> {
554        let mut passes: Vec<Box<dyn OptimizationPass>> = Vec::new();
555
556        // Add coherence optimization
557        passes.push(Box::new(CoherenceOptimization::new(
558            self.noise_model.clone(),
559        )));
560
561        // Add noise-aware mapping if coupling map is available
562        if let Some(ref coupling_map) = self.coupling_map {
563            passes.push(Box::new(NoiseAwareMapping::new(
564                self.noise_model.clone(),
565                coupling_map.clone(),
566            )));
567        }
568
569        // Add dynamical decoupling
570        passes.push(Box::new(DynamicalDecoupling::new(self.noise_model.clone())));
571
572        passes
573    }
574
575    /// Optimize a circuit with noise awareness
576    ///
577    /// This method applies all noise-aware optimization passes to the circuit,
578    /// including coherence optimization, noise-aware mapping, and dynamical decoupling.
579    ///
580    /// # Arguments
581    /// * `circuit` - The quantum circuit to optimize
582    ///
583    /// # Returns
584    /// An optimized circuit with improved noise characteristics
585    ///
586    /// # Examples
587    /// ```ignore
588    /// let noise_model = NoiseModel::uniform(4);
589    /// let optimizer = NoiseAwareOptimizer::new(noise_model);
590    /// let optimized = optimizer.optimize(&circuit)?;
591    /// ```
592    pub fn optimize<const N: usize>(&self, circuit: &Circuit<N>) -> QuantRS2Result<Circuit<N>> {
593        // Convert circuit gates to a mutable vector for optimization
594        let mut gates: Vec<Box<dyn GateOp>> =
595            circuit.gates().iter().map(|g| g.clone_gate()).collect();
596
597        // Apply each optimization pass in sequence
598        let passes = self.get_passes();
599        for pass in &passes {
600            if pass.should_apply() {
601                gates = pass.apply_to_gates(gates, &self.cost_model)?;
602            }
603        }
604
605        // For now, return a clone of the original circuit since the passes don't
606        // actually modify gates yet (they're implemented as pass-through).
607        // When the pass implementations are completed, this will need to reconstruct
608        // the circuit from the optimized gates.
609        // TODO: Reconstruct circuit from gates once passes are fully implemented
610        Ok(circuit.clone())
611    }
612
613    /// Estimate circuit fidelity
614    #[must_use]
615    pub fn estimate_fidelity<const N: usize>(&self, circuit: &Circuit<N>) -> f64 {
616        let mut total_error_prob = 0.0;
617
618        for gate in circuit.gates() {
619            let error_prob = self.noise_model.gate_error_probability(gate.as_ref());
620            total_error_prob += error_prob;
621        }
622
623        // Simple first-order approximation
624        (1.0 - total_error_prob).max(0.0)
625    }
626
627    /// Get the noise model
628    #[must_use]
629    pub const fn noise_model(&self) -> &NoiseModel {
630        &self.noise_model
631    }
632
633    /// Get the cost model
634    #[must_use]
635    pub const fn cost_model(&self) -> &NoiseAwareCostModel {
636        &self.cost_model
637    }
638}
639
640#[cfg(test)]
641mod tests {
642    use super::*;
643    use quantrs2_core::gate::{multi::CNOT, single::Hadamard};
644
645    #[test]
646    fn test_noise_model_creation() {
647        let model = NoiseModel::uniform(4);
648
649        assert_eq!(model.single_qubit_error(0), 1e-3);
650        assert_eq!(model.two_qubit_error(0, 1), 1e-2);
651        assert_eq!(model.t1_time(0), 100.0);
652        assert_eq!(model.gate_time("CNOT"), 200.0);
653    }
654
655    #[test]
656    fn test_ibm_noise_model() {
657        let model = NoiseModel::ibm_like(3);
658
659        assert_eq!(model.single_qubit_error(0), 1e-4);
660        assert_eq!(model.gate_time("H"), 35.0);
661        assert!(model.t1_time(1) > model.t1_time(0));
662    }
663
664    #[test]
665    fn test_noise_aware_cost_model() {
666        let noise_model = NoiseModel::uniform(4);
667        let cost_model = NoiseAwareCostModel::new(noise_model);
668
669        let h_gate = Hadamard { target: QubitId(0) };
670        let cnot_gate = CNOT {
671            control: QubitId(0),
672            target: QubitId(1),
673        };
674
675        let h_cost = cost_model.gate_cost(&h_gate);
676        let cnot_cost = cost_model.gate_cost(&cnot_gate);
677
678        // CNOT should be more expensive than Hadamard
679        assert!(cnot_cost > h_cost);
680    }
681
682    #[test]
683    fn test_coherence_optimization() {
684        let noise_model = NoiseModel::uniform(4);
685        let optimizer = CoherenceOptimization::new(noise_model.clone());
686
687        let mut circuit = Circuit::<4>::new();
688        circuit
689            .add_gate(Hadamard { target: QubitId(0) })
690            .expect("Failed to add Hadamard gate");
691        circuit
692            .add_gate(CNOT {
693                control: QubitId(0),
694                target: QubitId(1),
695            })
696            .expect("Failed to add CNOT gate");
697
698        let cost_model = NoiseAwareCostModel::new(noise_model);
699        let result = optimizer.apply(&circuit, &cost_model);
700        assert!(result.is_ok());
701    }
702
703    #[test]
704    fn test_noise_aware_optimizer() {
705        let noise_model = NoiseModel::uniform(4);
706        let optimizer = NoiseAwareOptimizer::new(noise_model);
707
708        let mut circuit = Circuit::<4>::new();
709        circuit
710            .add_gate(Hadamard { target: QubitId(0) })
711            .expect("Failed to add Hadamard gate");
712        circuit
713            .add_gate(CNOT {
714                control: QubitId(0),
715                target: QubitId(1),
716            })
717            .expect("Failed to add CNOT gate");
718
719        let optimized = optimizer
720            .optimize(&circuit)
721            .expect("Optimization should succeed");
722        let fidelity = optimizer.estimate_fidelity(&optimized);
723
724        assert!(fidelity > 0.9); // Should have high fidelity for simple circuit
725        assert!(fidelity <= 1.0);
726    }
727}