//! `cargo xtask libraries`: writes the libraries RustFig makes itself (plan //! D13) as `.fig` files under `libraries/`, from code, so the parts stay //! consistent and can be changed in one place. Today: `libraries/Feynman/`, //! `libraries/QuantumCircuits/`, `libraries/OpticsLab/` and //! `libraries/PhysicsCircuits/`. //! The output is deterministic (the same bytes on every run); a test checks //! that the committed files equal what the generator writes. //! //! Conventions: Fig units (1200 per inch), the 1/4-inch grid (300 units; the //! arrows' mid points on the 1/8-inch grid, 150), every part one compound //! with a description as its comment (the library search reads it), xfig's //! standard attributes (thickness 2, black, depth 50; vertices and blobs in //! front at depth 40). mod circuits; mod optics; use rustfig_format::{write, FileFormat}; use rustfig_geom::Point; use rustfig_model::*; use std::f64::consts::PI; use std::path::Path; /// The grid parts snap to: 1/8 inch (line ends are on the 1/4-inch grid). #[cfg_attr(not(test), allow(dead_code))] // used by the tests pub const GRID: f64 = 150.0; /// One library figure: file name (without `.fig`), the compound, and the /// points where lines join (all on the grid). #[cfg_attr(not(test), allow(dead_code))] // `ends` is checked by the tests pub struct Part { pub name: String, pub object: Object, pub ends: Vec, } fn p(x: f64, y: f64) -> Point { Point::new(x.round(), y.round()) } fn style(line: LineKind, thickness: i32, depth: i32) -> Style { Style { line, thickness, depth, style_val: match line { LineKind::Dashed => 6.0, LineKind::Dotted => 4.0, _ => 0.0, }, ..Style::default() } } fn arrow_head(size: f64) -> Arrow { Arrow { kind: 1, style: 1, thickness: 1.0, width: size * 0.6, height: size, } } fn compound(objects: Vec, comments: &[&str]) -> Object { let mut o = Object::new(ObjectKind::Compound(Compound { objects })); o.comments = comments.iter().map(|s| s.to_string()).collect(); o } fn polyline(s: Style, pts: Vec, fwd: Option) -> Object { let mut l = Polyline::new(PolyKind::Polyline, s, pts); l.forward_arrow = fwd; Object::new(ObjectKind::Polyline(l)) } fn spline(s: Style, pts: Vec) -> Object { let mut sp = Spline::new(SplineKind::Interpolated, false, s, pts); let n = sp.shapes.len(); for (i, sh) in sp.shapes.iter_mut().enumerate() { // xfig's interpolated splines: -1 inside, 0 at the open ends. *sh = if i == 0 || i + 1 == n { 0.0 } else { -1.0 }; } Object::new(ObjectKind::Spline(sp)) } fn arc_obj(s: Style, a: Point, m: Point, b: Point, fwd: Option) -> Option { let mut arc = Arc::through(s, a, m, b, false)?; arc.forward_arrow = fwd; Some(Object::new(ObjectKind::Arc(arc))) } /// The curve a line runs along: a straight segment, or the half circle on /// the chord `a`-`b`, bulging to the left of the direction of travel. #[derive(Clone, Copy)] enum Route { Line(Point, Point), Arc(Point, Point), } impl Route { fn ends(self) -> (Point, Point) { match self { Route::Line(a, b) | Route::Arc(a, b) => (a, b), } } fn length(self) -> f64 { let (a, b) = self.ends(); let c = (b.x - a.x).hypot(b.y - a.y); match self { Route::Line(..) => c, Route::Arc(..) => c * PI / 2.0, } } /// Position and unit normal at `s` in [0, 1] (arc length fraction). fn at(self, s: f64) -> (Point, (f64, f64)) { let (a, b) = self.ends(); let c = (b.x - a.x).hypot(b.y - a.y).max(1e-9); let (ux, uy) = ((b.x - a.x) / c, (b.y - a.y) / c); let n = (uy, -ux); match self { Route::Line(..) => (Point::new(a.x + (b.x - a.x) * s, a.y + (b.y - a.y) * s), n), Route::Arc(..) => { let (cx, cy, r) = ((a.x + b.x) / 2.0, (a.y + b.y) / 2.0, c / 2.0); let th = PI * s; let (c0, s0) = (th.cos(), th.sin()); let x = cx - r * c0 * ux + r * s0 * n.0; let y = cy - r * c0 * uy + r * s0 * n.1; (Point::new(x, y), (-c0 * ux + s0 * n.0, -c0 * uy + s0 * n.1)) } } } fn offset(self, s: f64, d: f64) -> Point { let (q, n) = self.at(s); p(q.x + n.0 * d, q.y + n.1 * d) } fn at_rounded(self, s: f64) -> Point { let q = self.at(s).0; p(q.x, q.y) } /// Number of wave periods for a wavelength of about 1/4 inch. fn periods(self) -> usize { ((self.length() / 300.0).round() as usize).max(1) } } const THICK: i32 = 2; const AMP: f64 = 75.0; /// How far an arrow head on a line reaches past the point where the line /// goes on: the head is as wide as the line `d` before its tip /// (`d = line width × head length / head width`), so the continuing line /// starts under the head instead of poking out of its tip. fn tip_overshoot(thickness: i32, head: &Arrow) -> f64 { if head.width <= 0.0 { return 0.0; } rustfig_geom::stroke_width(f64::from(thickness)) * head.height / head.width } /// Fermion line with the arrow in the middle: two pieces, one ending in the /// arrow head, whose tip lies a little past the middle so that the arrow /// sits on the line ([`tip_overshoot`]). `reverse` points the arrow back /// (anti-fermion). fn fermion(path: Route, reverse: bool) -> Vec { let s = style(LineKind::Solid, THICK, 50); let (a, b) = path.ends(); let arrow = arrow_head(150.0); // The tip's place along the route, as a fraction of its length. let ds = tip_overshoot(THICK, &arrow) / path.length().max(1.0); let tip = if reverse { 0.5 - ds } else { 0.5 + ds }; let head = Some(arrow); match path { Route::Line(..) => { let (m, t) = (path.at_rounded(0.5), path.at_rounded(tip)); if reverse { vec![polyline(s, vec![a, m], None), polyline(s, vec![b, t], head)] } else { vec![polyline(s, vec![a, t], head), polyline(s, vec![m, b], None)] } } Route::Arc(..) => { let top = path.at_rounded(0.5); let halves = if reverse { [ arc_obj(s, b, path.at_rounded((1.0 + tip) / 2.0), path.at_rounded(tip), head), arc_obj(s, a, path.at_rounded(0.25), top, None), ] } else { [ arc_obj(s, a, path.at_rounded(tip / 2.0), path.at_rounded(tip), head), arc_obj(s, top, path.at_rounded(0.75), b, None), ] }; halves.into_iter().flatten().collect() } } } /// Dashed (scalar), dotted (ghost) or thin solid line. fn plain(path: Route, line: LineKind, thickness: i32) -> Vec { let s = style(line, thickness, 50); let (a, b) = path.ends(); match path { Route::Line(..) => vec![polyline(s, vec![a, b], None)], Route::Arc(..) => arc_obj(s, a, path.at_rounded(0.5), b, None).into_iter().collect(), } } /// Sine wave (photon, W/Z boson, graviton) along the path: points at the /// quarter periods of an interpolated spline, so the curve is smooth and the /// wiggles are evenly spaced. fn wave(path: Route, thickness: i32) -> Object { let n = path.periods(); let steps = 8 * n; let pts: Vec = (0..=steps) .map(|i| { let s = i as f64 / steps as f64; path.offset(s, AMP * (2.0 * PI * n as f64 * s).sin()) }) .collect(); spline(style(LineKind::Solid, thickness, 50), pts) } /// Curly gluon line: a prolate cycloid (loops) along the path, 16 points per /// loop, starting and ending on the path. fn gluon(path: Route) -> Object { let n = path.periods(); let l = path.length(); let a = l / n as f64 / (2.0 * PI); let b = 2.0 * a; let steps = 16 * n; let pts: Vec = (0..=steps) .map(|i| { let tau = 2.0 * PI * n as f64 * i as f64 / steps as f64; let u = a * tau + b * (1.0 - tau.cos()); let v = -b * tau.sin(); path.offset((u / l).clamp(0.0, 1.0), v) }) .collect(); spline(style(LineKind::Solid, THICK, 50), pts) } fn dot(c: Point, r: f64) -> Object { let mut s = style(LineKind::Solid, 1, 40); s.fill_color = ColorRef::BLACK; s.area_fill = 20; circle(c, r, s) } fn circle(c: Point, r: f64, s: Style) -> Object { Object::new(ObjectKind::Ellipse(Ellipse { kind: EllipseKind::CircleRadius, style: s, direction: 1, angle: 0.0, center: c, radii: p(r, r), start: c, end: p(c.x + r, c.y), })) } fn momentum(a: Point, b: Point) -> Object { polyline( style(LineKind::Solid, 1, 50), vec![a, b], Some(Arrow { kind: 1, style: 1, thickness: 1.0, width: 75.0, height: 120.0, }), ) } /// A LaTeX label (text with the TeX flag) for the examples. fn label(x: f64, y: f64, tex: &str) -> Object { Object::new(ObjectKind::Text(Text { justify: Justify::Center, color: ColorRef::BLACK, depth: 30, pen_style: -1, font: 0, size: 12.0, angle: 0.0, flags: text_flags::SPECIAL, height: 135.0, length: 120.0 * tex.chars().count() as f64, origin: p(x, y), text: tex.to_string(), local_font: None, })) } fn part(name: &str, objects: Vec, comments: &[&str], ends: Vec) -> Part { Part { name: name.to_string(), object: compound(objects, comments), ends, } } /// The line parts: horizontal (1 inch, four periods), in one orientation /// (the user's decision, 2026-10-25: turning is one click in xfig), and the /// half-circle arcs for loops. fn line_parts(out: &mut Vec) { let h = Route::Line(p(300.0, 900.0), p(1500.0, 900.0)); { let (path, size) = (h, "horizontal, 1 inch"); let ends = vec![path.ends().0, path.ends().1]; let mk = |name: &str, what: &str, how: &str, objs: Vec| { let c1 = format!("Feynman diagram: {what}, {size}"); let c2 = format!("{how} Ends on the 1/4-inch grid; join parts end to end. Keywords: feynman particle physics"); part(name, objs, &[&c1, &c2], ends.clone()) }; out.push(mk( "fermion", "fermion line (electron, quark, muon)", "Arrow along the line.", fermion(path, false), )); out.push(mk( "anti_fermion", "anti-fermion line (positron, antiquark)", "Arrow against the line.", fermion(path, true), )); out.push(mk( "scalar", "scalar line (Higgs, squark), dashed", "Dashed.", plain(path, LineKind::Dashed, THICK), )); out.push(mk( "ghost", "ghost line, dotted", "Dotted.", plain(path, LineKind::Dotted, THICK), )); out.push(mk( "photon", "photon line (gamma), wavy", "Sine wave of 1/4-inch wavelength.", vec![wave(path, THICK)], )); out.push(mk( "boson_wz", "W or Z boson line, heavy wavy", "Thicker sine wave.", vec![wave(path, 3)], )); out.push(mk( "gluon", "gluon line (g), curly", "Loops 1/4 inch apart.", vec![gluon(path)], )); let mut g = vec![wave(path, THICK)]; g.extend(plain(path, LineKind::Solid, 1)); out.push(mk( "graviton", "graviton line, wavy with a straight line through it", "Double line.", g, )); } let arc = Route::Arc(p(300.0, 900.0), p(1500.0, 900.0)); let ends = vec![arc.ends().0, arc.ends().1]; let mk = |name: &str, what: &str, objs: Vec| { let c1 = format!("Feynman diagram: {what}, half circle on a chord of 1 inch (loops, self-energy)"); part( name, objs, &[ &c1, "Ends on the 1/4-inch grid. Keywords: feynman loop particle physics", ], ends.clone(), ) }; out.push(mk("fermion_arc", "fermion line", fermion(arc, false))); out.push(mk("anti_fermion_arc", "anti-fermion line", fermion(arc, true))); out.push(mk("scalar_arc", "scalar line", plain(arc, LineKind::Dashed, THICK))); out.push(mk("photon_arc", "photon line", vec![wave(arc, THICK)])); out.push(mk("gluon_arc", "gluon line", vec![gluon(arc)])); } fn other_parts(out: &mut Vec) { let c = p(300.0, 300.0); out.push(part( "vertex_dot", vec![dot(c, 45.0)], &[ "Feynman diagram: vertex dot (interaction point, counterterm)", "Place on the point where lines meet. Keywords: feynman vertex", ], vec![c], )); let c = p(600.0, 600.0); let mut s = style(LineKind::Solid, THICK, 40); s.fill_color = ColorRef::WHITE; s.area_fill = 45; out.push(part( "blob_hatched", vec![circle(c, 300.0, s)], &[ "Feynman diagram: hatched blob (effective vertex, form factor, hadronic part)", "Circle of 1/2 inch diameter, hatched at 45 degrees. Keywords: feynman blob", ], vec![c], )); let mut s = style(LineKind::Solid, THICK, 40); s.fill_color = ColorRef::WHITE; s.area_fill = 14; out.push(part( "blob_grey", vec![circle(c, 300.0, s)], &[ "Feynman diagram: grey blob (effective vertex, form factor, hadronic part)", "Circle of 1/2 inch diameter, grey fill. Keywords: feynman blob", ], vec![c], )); for (name, dir, a, b) in [ ("momentum_right", "to the right", p(300.0, 300.0), p(900.0, 300.0)), ("momentum_left", "to the left", p(900.0, 300.0), p(300.0, 300.0)), ("momentum_up", "up", p(300.0, 900.0), p(300.0, 300.0)), ("momentum_down", "down", p(300.0, 300.0), p(300.0, 900.0)), ] { let c1 = format!("Feynman diagram: momentum arrow {dir}"); out.push(part( name, vec![momentum(a, b)], &[ &c1, "Short arrow of 1/2 inch to place beside a line; add the label k or p as LaTeX text. Keywords: feynman momentum", ], vec![a, b], )); } } /// The complete example diagrams, built from the same generators as the parts. fn examples(out: &mut Vec) { let line = Route::Line; let mut ex = |name: &str, what: &str, objs: Vec| { let c1 = format!("Feynman diagram example: {what}"); out.push(part( name, objs, &[ &c1, "Made from the library's parts; edit the labels (LaTeX text). Keywords: feynman example", ], Vec::new(), )); }; // e+ e- -> mu+ mu-, s channel. let (v1, v2) = (p(1200.0, 1500.0), p(2400.0, 1500.0)); let mut o = Vec::new(); o.extend(fermion(line(p(300.0, 2400.0), v1), false)); o.extend(fermion(line(p(300.0, 600.0), v1), true)); o.push(wave(line(v1, v2), THICK)); o.extend(fermion(line(v2, p(3300.0, 2400.0)), false)); o.extend(fermion(line(v2, p(3300.0, 600.0)), true)); o.push(dot(v1, 45.0)); o.push(dot(v2, 45.0)); o.push(label(300.0, 2700.0, "$e^-$")); o.push(label(300.0, 450.0, "$e^+$")); o.push(label(3300.0, 2700.0, "$\\mu^-$")); o.push(label(3300.0, 450.0, "$\\mu^+$")); o.push(label(1800.0, 1200.0, "$\\gamma^*$")); ex( "example_ee_to_mumu", "e+ e- annihilation into mu+ mu- (s channel, one virtual photon)", o, ); // Compton scattering, s channel. let (v1, v2) = (p(1200.0, 1800.0), p(2400.0, 1800.0)); let mut o = Vec::new(); o.extend(fermion(line(p(300.0, 1800.0), v1), false)); o.extend(fermion(line(v1, v2), false)); o.extend(fermion(line(v2, p(3300.0, 1800.0)), false)); o.push(wave(line(p(300.0, 900.0), v1), THICK)); o.push(wave(line(v2, p(3300.0, 900.0)), THICK)); o.push(dot(v1, 45.0)); o.push(dot(v2, 45.0)); o.push(label(300.0, 2100.0, "$e^-$")); o.push(label(3300.0, 2100.0, "$e^-$")); o.push(label(300.0, 750.0, "$\\gamma$")); o.push(label(3300.0, 750.0, "$\\gamma$")); o.push(label(1800.0, 2100.0, "$p+k$")); ex( "example_compton", "Compton scattering e- gamma to e- gamma (s channel)", o, ); // One-loop fermion self-energy. let (v1, v2) = (p(1200.0, 1500.0), p(2400.0, 1500.0)); let mut o = Vec::new(); o.extend(fermion(line(p(300.0, 1500.0), v1), false)); o.extend(fermion(line(v1, v2), false)); o.push(wave(Route::Arc(v1, v2), THICK)); o.extend(fermion(line(v2, p(3300.0, 1500.0)), false)); o.push(dot(v1, 45.0)); o.push(dot(v2, 45.0)); o.push(label(300.0, 1800.0, "$p$")); o.push(label(3300.0, 1800.0, "$p$")); o.push(label(1800.0, 1800.0, "$p-k$")); o.push(label(1800.0, 600.0, "$k$")); ex( "example_self_energy", "one-loop fermion self-energy (photon loop on a fermion line)", o, ); // Gluon exchange between two quarks (t channel). let (v1, v2) = (p(1200.0, 900.0), p(1200.0, 2100.0)); let mut o = Vec::new(); for v in [v1, v2] { o.extend(fermion(line(p(300.0, v.y), v), false)); o.extend(fermion(line(v, p(2100.0, v.y)), false)); o.push(dot(v, 45.0)); } o.push(gluon(line(v1, v2))); o.push(label(300.0, 750.0, "$q$")); o.push(label(2100.0, 750.0, "$q$")); o.push(label(300.0, 2400.0, "$q$")); o.push(label(2100.0, 2400.0, "$q$")); o.push(label(1500.0, 1500.0, "$g$")); ex( "example_gluon_exchange", "quark-quark scattering by one gluon exchange (t channel)", o, ); } /// Every figure of the Feynman library, in file-name order. pub fn feynman() -> Vec { let mut v = Vec::new(); line_parts(&mut v); other_parts(&mut v); examples(&mut v); v.sort_by(|a, b| a.name.cmp(&b.name)); v } // --------------------------------------------------------------------------- // QuantumCircuits: wires, gates, controls, measurement and example circuits // in the look of Qcircuit / quantikz. Qubit wires are horizontal lines 1 inch // long (x 300..1500, ends on the 1/4-inch grid); wires of multi-wire parts are // 1/2 inch apart (y 600, 1200, 1800), so parts stack on the 1/2-inch grid. // --------------------------------------------------------------------------- /// Half the side of a single-qubit gate box. const GATE: f64 = 180.0; /// Half the distance between the two lines of a classical wire. const CGAP: f64 = 30.0; fn solid(thickness: i32, depth: i32) -> Style { style(LineKind::Solid, thickness, depth) } fn white_box(a: Point, b: Point) -> Object { let mut s = solid(THICK, 40); s.fill_color = ColorRef::WHITE; s.area_fill = 20; Object::new(ObjectKind::Polyline(Polyline::rect(s, a, b))) } /// Gate box centred at (`x`, `y`), `half_w` wide on each side, with a LaTeX /// label (none for the empty gate). fn gate_box(x: f64, y: f64, half_w: f64, tex: &str) -> Vec { let mut v = vec![white_box(p(x - half_w, y - GATE), p(x + half_w, y + GATE))]; if !tex.is_empty() { v.push(label(x, y + 60.0, tex)); } v } fn qwire(x0: f64, x1: f64, y: f64) -> Object { polyline(solid(THICK, 50), vec![p(x0, y), p(x1, y)], None) } /// Classical wire: two parallel lines. fn cwire(x0: f64, x1: f64, y: f64) -> Vec { let s = solid(1, 50); vec![ polyline(s, vec![p(x0, y - CGAP), p(x1, y - CGAP)], None), polyline(s, vec![p(x0, y + CGAP), p(x1, y + CGAP)], None), ] } /// Classical control line (vertical, double). fn cvline(x: f64, y0: f64, y1: f64) -> Vec { let s = solid(1, 50); vec![ polyline(s, vec![p(x - CGAP, y0), p(x - CGAP, y1)], None), polyline(s, vec![p(x + CGAP, y0), p(x + CGAP, y1)], None), ] } fn vline(x: f64, y0: f64, y1: f64) -> Object { polyline(solid(THICK, 50), vec![p(x, y0), p(x, y1)], None) } fn ctrl(x: f64, y: f64) -> Object { dot(p(x, y), 60.0) } fn ctrl_open(x: f64, y: f64) -> Object { let mut s = solid(THICK, 40); s.fill_color = ColorRef::WHITE; s.area_fill = 20; circle(p(x, y), 75.0, s) } /// Target: circle with a cross (the CNOT's plus in a circle). fn target(x: f64, y: f64) -> Vec { let mut s = solid(THICK, 40); s.fill_color = ColorRef::WHITE; s.area_fill = 20; let r = 120.0; vec![ circle(p(x, y), r, s), polyline(solid(THICK, 39), vec![p(x - r, y), p(x + r, y)], None), polyline(solid(THICK, 39), vec![p(x, y - r), p(x, y + r)], None), ] } /// SWAP end: a cross. fn swap_x(x: f64, y: f64) -> Vec { let d = 90.0; let s = solid(THICK, 40); vec![ polyline(s, vec![p(x - d, y - d), p(x + d, y + d)], None), polyline(s, vec![p(x - d, y + d), p(x + d, y - d)], None), ] } /// Measurement meter: gate box with an arc and a needle. fn meter(x: f64, y: f64) -> Vec { let mut v = gate_box(x, y, GATE, ""); if let Some(a) = arc_obj( solid(1, 39), p(x - 120.0, y + 60.0), p(x, y - 60.0), p(x + 120.0, y + 60.0), None, ) { v.push(a); } v.push(polyline( solid(1, 39), vec![p(x, y + 90.0), p(x + 90.0, y - 60.0)], Some(Arrow { kind: 1, style: 1, thickness: 1.0, width: 45.0, height: 75.0, }), )); v } fn barrier(x: f64, y0: f64, y1: f64) -> Object { polyline(style(LineKind::Dashed, 1, 45), vec![p(x, y0), p(x, y1)], None) } fn qc_part(name: &str, what: &str, how: &str, objs: Vec, ends: Vec) -> Part { let c1 = format!("Quantum circuit: {what}"); let c2 = format!("{how} Keywords: quantum circuit qubit gate qcircuit quantikz"); part(name, objs, &[&c1, &c2], ends) } const Y0: f64 = 600.0; const Y1: f64 = 1200.0; const Y2: f64 = 1800.0; fn qc_parts(out: &mut Vec) { let wends = |ys: &[f64]| -> Vec { ys.iter().flat_map(|&y| [p(300.0, y), p(1500.0, y)]).collect() }; let w1 = wends(&[Y0]); let w2 = wends(&[Y0, Y1]); let w3 = wends(&[Y0, Y1, Y2]); let with_wire = |mut objs: Vec, ys: &[f64]| -> Vec { let mut v: Vec = ys.iter().map(|&y| qwire(300.0, 1500.0, y)).collect(); v.append(&mut objs); v }; out.push(qc_part( "wire", "qubit wire, 1 inch", "Horizontal line; ends on the 1/4-inch grid. Wires of a circuit are 1/2 inch apart.", vec![qwire(300.0, 1500.0, Y0)], w1.clone(), )); out.push(qc_part( "wire_classical", "classical wire (double line), 1 inch", "Carries a measurement result; ends on the 1/4-inch grid.", cwire(300.0, 1500.0, Y0), w1.clone(), )); for (name, tex, what) in [ ("gate_h", "$H$", "Hadamard gate H"), ("gate_x", "$X$", "Pauli X gate (NOT)"), ("gate_y", "$Y$", "Pauli Y gate"), ("gate_z", "$Z$", "Pauli Z gate"), ("gate_s", "$S$", "phase gate S"), ("gate_t", "$T$", "T gate (pi/8 gate)"), ("gate_empty", "", "empty gate box for any single-qubit gate"), ] { out.push(qc_part( name, &format!("{what}, single-qubit gate on a wire"), "Square box centred on the wire. The label is LaTeX text with the TeX flag; edit it with the Text mode.", with_wire(gate_box(900.0, Y0, GATE, tex), &[Y0]), w1.clone(), )); } out.push(qc_part( "gate_rx", "rotation gate R_x(theta), single-qubit gate on a wire", "Wide box centred on the wire; LaTeX text with the TeX flag, change x and the angle as needed.", with_wire(gate_box(900.0, Y0, 330.0, "$R_x(\\theta)$"), &[Y0]), w1.clone(), )); out.push(qc_part( "measure", "measurement (meter: box with arc and needle) on a wire", "Projective measurement in the computational basis; continue with a classical wire.", with_wire(meter(900.0, Y0), &[Y0]), w1.clone(), )); out.push(qc_part( "control_dot", "control (filled dot) on a wire", "Draw a vertical line to the target.", with_wire(vec![ctrl(900.0, Y0)], &[Y0]), w1.clone(), )); out.push(qc_part( "control_open", "open control (hollow circle) on a wire, acts when the control qubit is 0", "Draw a vertical line to the target.", with_wire(vec![ctrl_open(900.0, Y0)], &[Y0]), w1.clone(), )); out.push(qc_part( "target", "target (circle with a cross) on a wire, the target of a CNOT", "Draw a vertical line to the control.", with_wire(target(900.0, Y0), &[Y0]), w1.clone(), )); let mut v = vec![vline(900.0, Y0, Y1), ctrl(900.0, Y0)]; v.extend(target(900.0, Y1)); out.push(qc_part( "cnot", "CNOT (controlled NOT), control above, target below, two wires", "Complete part on two wires 1/2 inch apart. Flip it vertically for the other direction.", with_wire(v, &[Y0, Y1]), w2.clone(), )); let v = vec![vline(900.0, Y0, Y1), ctrl(900.0, Y0), ctrl(900.0, Y1)]; out.push(qc_part( "cz", "CZ (controlled Z, two filled dots), two wires", "Complete part on two wires 1/2 inch apart; symmetric in the two qubits.", with_wire(v, &[Y0, Y1]), w2.clone(), )); let mut v = vec![vline(900.0, Y0, Y1)]; v.extend(swap_x(900.0, Y0)); v.extend(swap_x(900.0, Y1)); out.push(qc_part( "swap", "SWAP gate (two crosses joined by a line), two wires", "Complete part on two wires 1/2 inch apart.", with_wire(v, &[Y0, Y1]), w2.clone(), )); let mut v = gate_box(900.0, (Y0 + Y1) / 2.0, GATE, "$U$"); v[0] = white_box(p(720.0, Y0 - 150.0), p(1080.0, Y1 + 150.0)); out.push(qc_part( "gate_multi2", "multi-qubit gate box spanning 2 wires", "Tall box over two wires; label in the middle (LaTeX text with the TeX flag).", with_wire(v, &[Y0, Y1]), w2.clone(), )); let mut v = gate_box(900.0, Y1, GATE, "$U$"); v[0] = white_box(p(720.0, Y0 - 150.0), p(1080.0, Y2 + 150.0)); out.push(qc_part( "gate_multi3", "multi-qubit gate box spanning 3 wires", "Tall box over three wires; label in the middle (LaTeX text with the TeX flag).", with_wire(v, &[Y0, Y1, Y2]), w3.clone(), )); for (name, tex, what) in [ ("init_0", "$|0\\rangle$", "initial state |0> at the left end of a wire"), ("init_1", "$|1\\rangle$", "initial state |1> at the left end of a wire"), ( "ket_in", "$|\\psi\\rangle$", "input ket |psi> at the left end of a wire", ), ] { out.push(qc_part( name, what, "Label left of the wire (LaTeX text with the TeX flag); the wire starts on the grid.", vec![label(360.0, Y0 + 60.0, tex), qwire(600.0, 1500.0, Y0)], vec![p(600.0, Y0), p(1500.0, Y0)], )); } out.push(qc_part( "ket_out", "output ket |psi'> at the right end of a wire", "Label right of the wire (LaTeX text with the TeX flag).", vec![label(1440.0, Y0 + 60.0, "$|\\psi'\\rangle$"), qwire(300.0, 1200.0, Y0)], vec![p(300.0, Y0), p(1200.0, Y0)], )); out.push(qc_part( "barrier", "barrier (dashed vertical line) across two wires", "Separates stages of a circuit; scale it to span more wires.", vec![barrier(900.0, Y0 - 150.0, Y1 + 150.0)], Vec::new(), )); out.push(qc_part( "control_classical", "classical control line (double vertical line) between two wires", "Runs from a measurement down to a gate that is controlled by the result.", cvline(900.0, Y0, Y1), vec![p(900.0, Y0), p(900.0, Y1)], )); } /// A circuit under construction. struct Circuit { objs: Vec, } impl Circuit { fn new() -> Circuit { Circuit { objs: Vec::new() } } fn q(&mut self, x0: f64, x1: f64, y: f64) { self.objs.push(qwire(x0, x1, y)); } fn c(&mut self, x0: f64, x1: f64, y: f64) { self.objs.extend(cwire(x0, x1, y)); } fn gate(&mut self, x: f64, y: f64, tex: &str) { self.objs.extend(gate_box(x, y, GATE, tex)); } fn cnot(&mut self, x: f64, c: f64, t: f64) { self.objs.push(vline(x, c, t)); self.objs.push(ctrl(x, c)); self.objs.extend(target(x, t)); } fn ctrl_gate(&mut self, x: f64, c: f64, t: f64, tex: &str) { self.objs.push(vline(x, c, t)); self.objs.push(ctrl(x, c)); self.gate(x, t, tex); } fn meter(&mut self, x: f64, y: f64) { self.objs.extend(meter(x, y)); } fn text(&mut self, x: f64, y: f64, tex: &str) { self.objs.push(label(x, y + 60.0, tex)); } } fn qc_examples(out: &mut Vec) { let mut ex = |name: &str, what: &str, c: Circuit| { let c1 = format!("Quantum circuit example: {what}"); out.push(part( name, c.objs, &[ &c1, "Made from the library's parts; edit the labels (LaTeX text). Keywords: quantum circuit example qubit gate", ], Vec::new(), )); }; // Bell state: H, CNOT, measurement of both qubits. let mut c = Circuit::new(); c.q(600.0, 2100.0, Y0); c.q(600.0, 2100.0, Y1); c.c(2100.0, 2700.0, Y0); c.c(2100.0, 2700.0, Y1); c.text(330.0, Y0, "$|0\\rangle$"); c.text(330.0, Y1, "$|0\\rangle$"); c.gate(900.0, Y0, "$H$"); c.cnot(1500.0, Y0, Y1); c.meter(2100.0, Y0); c.meter(2100.0, Y1); ex( "example_bell", "Bell-state preparation (Hadamard, CNOT) and measurement of both qubits", c, ); // GHZ on three qubits. let mut c = Circuit::new(); for y in [Y0, Y1, Y2] { c.q(600.0, 2400.0, y); c.text(330.0, y, "$|0\\rangle$"); } c.gate(900.0, Y0, "$H$"); c.cnot(1500.0, Y0, Y1); c.cnot(2100.0, Y1, Y2); ex( "example_ghz", "GHZ-state preparation on 3 qubits (Hadamard, two CNOTs)", c, ); // Teleportation of |psi> from the top qubit to the bottom one. let mut c = Circuit::new(); c.q(600.0, 3600.0, Y0); c.c(3600.0, 4800.0, Y0); c.q(600.0, 3600.0, Y1); c.c(3600.0, 4200.0, Y1); c.q(600.0, 5100.0, Y2); c.text(330.0, Y0, "$|\\psi\\rangle$"); c.text(330.0, Y1, "$|0\\rangle$"); c.text(330.0, Y2, "$|0\\rangle$"); c.gate(900.0, Y1, "$H$"); c.cnot(1500.0, Y1, Y2); c.objs.push(barrier(1950.0, Y0 - 150.0, Y2 + 150.0)); c.cnot(2400.0, Y0, Y1); c.gate(3000.0, Y0, "$H$"); c.meter(3600.0, Y0); c.meter(3600.0, Y1); c.objs.extend(cvline(4200.0, Y1, Y2 - GATE)); c.gate(4200.0, Y2, "$X$"); c.objs.extend(cvline(4800.0, Y0, Y2 - GATE)); c.gate(4800.0, Y2, "$Z$"); c.text(5460.0, Y2, "$|\\psi\\rangle$"); ex( "example_teleportation", "quantum teleportation of |psi> (Bell pair, Bell measurement, classical wires, controlled X and Z)", c, ); // QFT on 3 qubits. let mut c = Circuit::new(); for (i, y) in [Y0, Y1, Y2].into_iter().enumerate() { c.q(600.0, 4800.0, y); c.text(330.0, y, &format!("$|x_{i}\\rangle$")); c.text(5100.0, y, &format!("$|y_{i}\\rangle$")); } c.gate(900.0, Y0, "$H$"); c.ctrl_gate(1500.0, Y1, Y0, "$R_2$"); c.ctrl_gate(2100.0, Y2, Y0, "$R_3$"); c.gate(2700.0, Y1, "$H$"); c.ctrl_gate(3300.0, Y2, Y1, "$R_2$"); c.gate(3900.0, Y2, "$H$"); c.objs.push(vline(4500.0, Y0, Y2)); c.objs.extend(swap_x(4500.0, Y0)); c.objs.extend(swap_x(4500.0, Y2)); ex( "example_qft", "quantum Fourier transform on 3 qubits (Hadamards, controlled phase rotations, SWAP)", c, ); } /// Every figure of the QuantumCircuits library, in file-name order. pub fn quantum_circuits() -> Vec { let mut v = Vec::new(); qc_parts(&mut v); qc_examples(&mut v); v.sort_by(|a, b| a.name.cmp(&b.name)); v } pub use circuits::physics_circuits; pub use optics::optics_lab; /// The `.fig` text of a part. pub fn render(part: &Part) -> Result { let mut doc = Document::new(); doc.settings.units = Units::Inches; doc.objects.push(std::sync::Arc::new(part.object.clone())); doc.assign_ids(); write(&doc, FileFormat::Fig).map_err(|e| format!("{}: {e:?}", part.name)) } /// Write the generated libraries: `libraries/Feynman/*.fig` and /// `libraries/QuantumCircuits/*.fig` `libraries/OpticsLab/*.fig` and `libraries/PhysicsCircuits/*.fig`. pub fn run(root: &Path) -> Result<(), String> { for (name, parts) in [ ("Feynman", feynman()), ("QuantumCircuits", quantum_circuits()), ("OpticsLab", optics_lab()), ("PhysicsCircuits", physics_circuits()), ] { let dir = root.join("libraries").join(name); std::fs::create_dir_all(&dir).map_err(|e| format!("{}: {e}", dir.display()))?; for part in &parts { let path = dir.join(format!("{}.fig", part.name)); std::fs::write(&path, render(part)?).map_err(|e| format!("{}: {e}", path.display()))?; } println!("libraries: wrote {} figures to {}", parts.len(), dir.display()); } Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn generator_is_deterministic() { let a: Vec = feynman().iter().map(|p| render(p).unwrap()).collect(); let b: Vec = feynman().iter().map(|p| render(p).unwrap()).collect(); assert_eq!(a, b); assert!(a.len() >= 20); } #[test] fn names_are_unique_and_plain() { let parts = feynman(); let mut names: Vec<&str> = parts.iter().map(|p| p.name.as_str()).collect(); names.sort_unstable(); names.dedup(); assert_eq!(names.len(), parts.len()); for n in names { assert!(n.chars().all(|c| c.is_ascii_alphanumeric() || c == '_'), "{n}"); } } #[test] fn every_figure_reads_back_as_one_compound_with_a_comment() { for part in feynman() { let text = render(&part).unwrap(); let doc = rustfig_format::read_any(text.as_bytes()).unwrap(); assert_eq!(doc.objects.len(), 1, "{}", part.name); let o = &doc.objects[0]; assert!(matches!(o.kind, ObjectKind::Compound(_)), "{}", part.name); assert!( o.comments.first().is_some_and(|c| c.starts_with("Feynman diagram")), "{}", part.name ); } } #[test] fn part_ends_lie_on_the_grid() { for part in feynman() { for e in &part.ends { assert!( (e.x / GRID).fract() == 0.0 && (e.y / GRID).fract() == 0.0, "{} {e:?}", part.name ); } } } #[test] fn committed_files_match_the_generator() { let dir = crate::root().join("libraries").join("Feynman"); for part in feynman() { let path = dir.join(format!("{}.fig", part.name)); let on_disk = std::fs::read_to_string(&path).unwrap_or_default(); assert_eq!( on_disk.replace("\r\n", "\n"), render(&part).unwrap(), "{} is stale: run cargo xtask libraries", path.display() ); } } #[test] fn quantum_circuits_are_deterministic_and_plain() { let a: Vec = quantum_circuits().iter().map(|p| render(p).unwrap()).collect(); let b: Vec = quantum_circuits().iter().map(|p| render(p).unwrap()).collect(); assert_eq!(a, b); assert!(a.len() > 25); let parts = quantum_circuits(); let mut names: Vec<&str> = parts.iter().map(|p| p.name.as_str()).collect(); names.sort_unstable(); names.dedup(); assert_eq!(names.len(), parts.len()); assert!(names .iter() .all(|n| n.chars().all(|c| c.is_ascii_alphanumeric() || c == '_'))); } #[test] fn quantum_figures_read_back_as_one_compound_with_a_comment() { for part in quantum_circuits() { let text = render(&part).unwrap(); let doc = rustfig_format::read_any(text.as_bytes()).unwrap(); assert_eq!(doc.objects.len(), 1, "{}", part.name); let o = &doc.objects[0]; assert!(matches!(o.kind, ObjectKind::Compound(_)), "{}", part.name); assert!( o.comments.first().is_some_and(|c| c.starts_with("Quantum circuit")), "{}", part.name ); assert!(o.comments.iter().any(|c| c.contains("Keywords: quantum circuit"))); } } #[test] fn quantum_part_ends_lie_on_the_quarter_inch_grid() { for part in quantum_circuits() { for e in &part.ends { assert!( (e.x / 300.0).fract() == 0.0 && (e.y / 300.0).fract() == 0.0, "{} {e:?}", part.name ); } } } #[test] fn committed_quantum_files_match_the_generator() { let dir = crate::root().join("libraries").join("QuantumCircuits"); for part in quantum_circuits() { let path = dir.join(format!("{}.fig", part.name)); let on_disk = std::fs::read_to_string(&path).unwrap_or_default(); assert_eq!( on_disk.replace("\r\n", "\n"), render(&part).unwrap(), "{} is stale: run cargo xtask libraries", path.display() ); } } #[test] fn optics_lab_is_deterministic_and_plain() { let a: Vec = optics_lab().iter().map(|p| render(p).unwrap()).collect(); let b: Vec = optics_lab().iter().map(|p| render(p).unwrap()).collect(); assert_eq!(a, b); assert!(a.len() > 40); let parts = optics_lab(); let mut names: Vec<&str> = parts.iter().map(|p| p.name.as_str()).collect(); names.sort_unstable(); names.dedup(); assert_eq!(names.len(), parts.len()); assert!(names .iter() .all(|n| n.chars().all(|c| c.is_ascii_alphanumeric() || c == '_'))); } #[test] fn optics_lab_figures_read_back_as_one_compound_with_a_comment() { for part in optics_lab() { let text = render(&part).unwrap(); let doc = rustfig_format::read_any(text.as_bytes()).unwrap(); assert_eq!(doc.objects.len(), 1, "{}", part.name); let o = &doc.objects[0]; assert!(matches!(o.kind, ObjectKind::Compound(_)), "{}", part.name); assert!( o.comments.first().is_some_and(|c| c.starts_with("Optics lab")), "{}", part.name ); assert!(o.comments.iter().any(|c| c.contains("Keywords: optics"))); } } #[test] fn optics_lab_beam_ports_lie_on_the_quarter_inch_grid() { for part in optics_lab() { for e in &part.ends { assert!( (e.x / 300.0).fract() == 0.0 && (e.y / 300.0).fract() == 0.0, "{} {e:?}", part.name ); } } } #[test] fn committed_optics_lab_files_match_the_generator() { let dir = crate::root().join("libraries").join("OpticsLab"); for part in optics_lab() { let path = dir.join(format!("{}.fig", part.name)); let on_disk = std::fs::read_to_string(&path).unwrap_or_default(); assert_eq!( on_disk.replace("\r\n", "\n"), render(&part).unwrap(), "{} is stale: run cargo xtask libraries", path.display() ); } } #[test] fn physics_circuits_are_deterministic_and_plain() { let a: Vec = physics_circuits().iter().map(|p| render(p).unwrap()).collect(); let b: Vec = physics_circuits().iter().map(|p| render(p).unwrap()).collect(); assert_eq!(a, b); assert!(a.len() >= 30); let parts = physics_circuits(); let mut names: Vec<&str> = parts.iter().map(|p| p.name.as_str()).collect(); names.sort_unstable(); names.dedup(); assert_eq!(names.len(), parts.len()); assert!(names .iter() .all(|n| n.chars().all(|c| c.is_ascii_alphanumeric() || c == '_'))); } #[test] fn physics_circuits_read_back_as_one_compound_with_a_comment() { for part in physics_circuits() { let text = render(&part).unwrap(); let doc = rustfig_format::read_any(text.as_bytes()).unwrap(); assert_eq!(doc.objects.len(), 1, "{}", part.name); let o = &doc.objects[0]; assert!(matches!(o.kind, ObjectKind::Compound(_)), "{}", part.name); assert!( o.comments.first().is_some_and(|c| c.starts_with("Physics circuits")), "{}", part.name ); assert!(o.comments.iter().any(|c| c.contains("Keywords: circuit"))); } } #[test] fn physics_circuit_pins_lie_on_the_quarter_inch_grid() { for part in physics_circuits() { for e in &part.ends { assert!( (e.x / 300.0).fract() == 0.0 && (e.y / 300.0).fract() == 0.0, "{} {e:?}", part.name ); } } } #[test] fn two_terminal_parts_have_pins_one_inch_apart_and_one_orientation() { let parts = physics_circuits(); for name in circuits::TWO_TERMINAL { let part = parts.iter().find(|p| p.name == name).unwrap(); assert_eq!(part.ends.len(), 2, "{name}"); let (a, b) = (part.ends[0], part.ends[1]); assert_eq!(a.y, b.y, "{name}"); assert_eq!((a.x - b.x).abs(), 1200.0, "{name}"); } // One orientation per part: no turned copies in the library. assert!(parts.iter().all(|p| !p.name.ends_with("_h") && !p.name.ends_with("_v"))); } #[test] fn drawn_leads_reach_the_pins() { for part in physics_circuits() { if part.ends.is_empty() { continue; } let ObjectKind::Compound(c) = &part.object.kind else { panic!("{}", part.name); }; let pts: Vec = c .objects .iter() .filter_map(|o| match &o.kind { ObjectKind::Polyline(l) => Some(l.points.clone()), ObjectKind::Ellipse(e) => Some(vec![e.center]), _ => None, }) .flatten() .collect(); for e in &part.ends { assert!( pts.iter().any(|q| (q.x - e.x).abs() < 1.5 && (q.y - e.y).abs() < 1.5), "{}: nothing is drawn at pin {e:?}", part.name ); } } } #[test] fn committed_physics_circuit_files_match_the_generator() { let dir = crate::root().join("libraries").join("PhysicsCircuits"); for part in physics_circuits() { let path = dir.join(format!("{}.fig", part.name)); let on_disk = std::fs::read_to_string(&path).unwrap_or_default(); assert_eq!( on_disk.replace("\r\n", "\n"), render(&part).unwrap(), "{} is stale: run cargo xtask libraries", path.display() ); } } } .