@@ -546,3 +546,150 @@ TEST_CASE("Test OperatorNode equality with staticNamedArgs", "[DecompGraph::Core
546546 REQUIRE (pauliRotX == pauliRotX2);
547547 REQUIRE_FALSE (pauliRotX == pauliRotY);
548548}
549+
550+ // //////////////
551+ // Adjoint Tests
552+ // //////////////
553+
554+ TEST_CASE (" Self-adjoint Adjoint(H) -> H" , " [DecompGraph::Solver]" ) {
555+ const OperatorNode h{" H[][1]{}" , " Hadamard" };
556+ const OperatorNode adjH{" Adjoint(H[][1]{})" , " Adjoint(Hadamard)" };
557+
558+ const WeightedGateset gateset{{{h.name , 1.0 }}};
559+ const std::vector<RuleNode> rules{
560+ {" adj_h_to_h" , adjH, {{h, 1 }}},
561+ };
562+
563+ const DecompositionGraph graph ({adjH}, gateset, rules);
564+ DecompositionSolver solver (graph);
565+ const auto result = solver.solve ();
566+
567+ const auto &chosen = result.at (adjH);
568+ REQUIRE_FALSE (chosen.isBasis );
569+ REQUIRE (chosen.ruleName == " adj_h_to_h" );
570+ REQUIRE (chosen.totalCost == 1.0 );
571+ REQUIRE (result.at (h).isBasis );
572+ }
573+
574+ TEST_CASE (" Adjoint(RZ) -> RZ resolves at RZ's cost" , " [DecompGraph::Solver]" ) {
575+ const OperatorNode rz{" RZ[f64][1]{}" , " RZ" };
576+ const OperatorNode adjRz{" Adjoint(RZ[f64][1]{})" , " Adjoint(RZ)" };
577+
578+ const WeightedGateset gateset{{{rz.name , 4.0 }}};
579+ const std::vector<RuleNode> rules{
580+ {" adj_rz_to_rz" , adjRz, {{rz, 1 }}},
581+ };
582+
583+ const DecompositionGraph graph ({adjRz}, gateset, rules);
584+ DecompositionSolver solver (graph);
585+ const auto result = solver.solve ();
586+
587+ const auto &chosen = result.at (adjRz);
588+ REQUIRE_FALSE (chosen.isBasis );
589+ REQUIRE (chosen.ruleName == " adj_rz_to_rz" );
590+ REQUIRE (chosen.totalCost == 4.0 );
591+ REQUIRE (result.at (rz).isBasis );
592+ }
593+
594+ TEST_CASE (" Adjoint(Rot) -> Adjoint(RZ) Adjoint(RY) Adjoint(RZ)" , " [DecompGraph::Solver]" ) {
595+ const OperatorNode rz{" RZ[f64][1]{}" , " RZ" };
596+ const OperatorNode ry{" RY[f64][1]{}" , " RY" };
597+ const OperatorNode adjRz{" Adjoint(RZ[f64][1]{})" , " Adjoint(RZ)" };
598+ const OperatorNode adjRy{" Adjoint(RY[f64][1]{})" , " Adjoint(RY)" };
599+ const OperatorNode adjRot{" Adjoint(Rot[f64,f64,f64][1]{})" , " Adjoint(Rot)" };
600+
601+ const WeightedGateset gateset{{{rz.name , 1.0 }, {ry.name , 2.0 }}};
602+ const std::vector<RuleNode> rules{
603+ {" adj_rot_distribute" , adjRot, {{adjRz, 2 }, {adjRy, 1 }}},
604+ {" adj_rz_to_rz" , adjRz, {{rz, 1 }}},
605+ {" adj_ry_to_ry" , adjRy, {{ry, 1 }}},
606+ };
607+
608+ const DecompositionGraph graph ({adjRot}, gateset, rules);
609+ DecompositionSolver solver (graph);
610+ const auto result = solver.solve ();
611+
612+ const auto &chosen = result.at (adjRot);
613+ REQUIRE_FALSE (chosen.isBasis );
614+ REQUIRE (chosen.ruleName == " adj_rot_distribute" );
615+ REQUIRE (chosen.totalCost == 4.0 );
616+
617+ REQUIRE_FALSE (result.at (adjRz).isBasis );
618+ REQUIRE (result.at (adjRz).ruleName == " adj_rz_to_rz" );
619+ REQUIRE_FALSE (result.at (adjRy).isBasis );
620+ REQUIRE (result.at (adjRy).ruleName == " adj_ry_to_ry" );
621+ REQUIRE (result.at (rz).isBasis );
622+ REQUIRE (result.at (ry).isBasis );
623+ }
624+
625+ TEST_CASE (" Competing adjoint pathways are picked by cost" , " [DecompGraph::Solver]" ) {
626+ const OperatorNode rz{" RZ[f64][1]{}" , " RZ" };
627+ const OperatorNode ry{" RY[f64][1]{}" , " RY" };
628+ const OperatorNode adjRz{" Adjoint(RZ[f64][1]{})" , " Adjoint(RZ)" };
629+ const OperatorNode adjRy{" Adjoint(RY[f64][1]{})" , " Adjoint(RY)" };
630+ const OperatorNode adjRot{" Adjoint(Rot[f64,f64,f64][1]{})" , " Adjoint(Rot)" };
631+
632+ const WeightedGateset gateset{{{rz.name , 1.0 }, {ry.name , 2.0 }}};
633+
634+ const RuleNode distribute{" adj_rot_distribute" , adjRot, {{adjRz, 2 }, {adjRy, 1 }}};
635+ const RuleNode adjRzToRz{" adj_rz_to_rz" , adjRz, {{rz, 1 }}};
636+ const RuleNode adjRyToRy{" adj_ry_to_ry" , adjRy, {{ry, 1 }}};
637+
638+ SECTION (" dedicated pathway wins when it is cheaper" ) {
639+ const RuleNode dedicated{" adj_rot_dedicated" , adjRot, {{rz, 1 }}}; // cost 1 < 4
640+ const DecompositionGraph graph ({adjRot}, gateset,
641+ {dedicated, distribute, adjRzToRz, adjRyToRy});
642+ DecompositionSolver solver (graph);
643+ const auto result = solver.solve ();
644+ REQUIRE (result.at (adjRot).ruleName == " adj_rot_dedicated" );
645+ REQUIRE (result.at (adjRot).totalCost == 1.0 );
646+ }
647+
648+ SECTION (" distribution pathway wins when the dedicated rule is more expensive" ) {
649+ const RuleNode dedicated{" adj_rot_dedicated" , adjRot, {{rz, 10 }}}; // cost 10 > 4
650+ const DecompositionGraph graph ({adjRot}, gateset,
651+ {dedicated, distribute, adjRzToRz, adjRyToRy});
652+ DecompositionSolver solver (graph);
653+ const auto result = solver.solve ();
654+ REQUIRE (result.at (adjRot).ruleName == " adj_rot_distribute" );
655+ REQUIRE (result.at (adjRot).totalCost == 4.0 );
656+ }
657+ }
658+
659+ TEST_CASE (" A non-adjoint decomposition can produce an adjoint input" , " [DecompGraph::Solver]" ) {
660+ const OperatorNode h{" H[][1]{}" , " Hadamard" };
661+ const OperatorNode cnot{" CNOT[][2]{}" , " CNOT" };
662+ const OperatorNode phaseShift{" PhaseShift[f64][1]{}" , " PhaseShift" };
663+ const OperatorNode adjT{" Adjoint(T[][1]{})" , " Adjoint(T)" };
664+ const OperatorNode myGate{" MyGate[][1]{}" , " MyGate" };
665+
666+ const WeightedGateset gateset{{{h.name , 1.0 }, {cnot.name , 5.0 }, {phaseShift.name , 1.0 }}};
667+ const std::vector<RuleNode> rules{
668+ {" mygate_decomp" , myGate, {{h, 1 }, {adjT, 1 }, {cnot, 1 }}}, // emits an Adjoint(T)
669+ {" adj_t_to_phaseshift" , adjT, {{phaseShift, 1 }}},
670+ };
671+
672+ const DecompositionGraph graph ({myGate}, gateset, rules);
673+ DecompositionSolver solver (graph);
674+ const auto result = solver.solve ();
675+
676+ REQUIRE (result.at (myGate).ruleName == " mygate_decomp" );
677+ REQUIRE_FALSE (result.at (adjT).isBasis );
678+ REQUIRE (result.at (adjT).ruleName == " adj_t_to_phaseshift" );
679+ REQUIRE (result.at (phaseShift).isBasis );
680+ REQUIRE (result.at (h).isBasis );
681+ REQUIRE (result.at (cnot).isBasis );
682+ }
683+
684+ TEST_CASE (" Adjoint of an invalid target fails instead of collapsing to the base" ,
685+ " [DecompGraph::Solver]" ) {
686+ const OperatorNode measure{" M[][1]{}" , " Measure" };
687+ const OperatorNode adjMeasure{" Adjoint(M[][1]{})" , " Adjoint(Measure)" };
688+
689+ const WeightedGateset gateset{{{measure.name , 1.0 }}};
690+ const std::vector<RuleNode> rules{};
691+
692+ const DecompositionGraph graph ({adjMeasure}, gateset, rules);
693+ DecompositionSolver solver (graph);
694+ REQUIRE_THROWS_AS (solver.solve (), GraphSolverFailedError);
695+ }
0 commit comments