5
$\begingroup$

I have a Q# operation:

    operation init_and(a: Qubit, b: Qubit, target: Qubit) : Unit is Adj {
        // Uncomment this when using Toffoli simulator.
        // CCNOT(a, b, target);

        // Uncomment this when doing resource estimates.
        body(...) {
            CCNOT(a, b, target);
        }
        adjoint(...) {
            H(target);
            if (M(target) == One) {
                CZ(a, b);
            }
        }
    }

As you can see from the comments, I have to choose between being able to simulate the operation (when testing the correctness of constructions using it) and being able to correctly cost the operation (because uncomputing it should cost zero T gates).

Is there some way to get the best of both worlds? For example, some way to say "when testing correctness use this, when doing cost estimates use this"? Some way to query "Is the Hadamard available?"?

$\endgroup$
1
$\begingroup$

For the sake of completeness: June release of Q# now offers AutoSubstitution rewrite step, which allows to substitute an implementation of an operation with a different one when running on a specific simulator. This blog post offers more details on how to use it.

The example from the question will look like this:

namespace Scratch {
    open Microsoft.Quantum.Canon;
    open Microsoft.Quantum.Intrinsic;
    open Microsoft.Quantum.Targeting;

    @SubstitutableOnTarget("Scratch.InitAndToffoli", "ToffoliSimulator")
    operation InitAnd(a: Qubit, b: Qubit, target: Qubit) : Unit is Adj {
        // This implementation will be called on any simulator except Toffoli
        body(...) {
            Message("InitAnd body");
            CCNOT(a, b, target);
        }
        adjoint(...) {
            Message("Adjoint InitAnd body");
            H(target);
            if (M(target) == One) {
                CZ(a, b);
            }
        }
    }


    operation InitAndToffoli(a: Qubit, b: Qubit, target: Qubit) : Unit is Adj {
        // This implementation will be called only on Toffoli simulator
        Message("InitAndToffoli");
        CCNOT(a, b, target);
    }


    // Make the sample complete as a standalone executable
    @EntryPoint()
    operation RunIt() : Unit {
        use (a, b, target) = (Qubit(), Qubit(), Qubit());
        InitAnd(a, b, target);
        // The required workaround to make sure substitute operation
        // is available to the runtime - will be removed soon.
        let _ = InitAndToffoli;
    }
}

Now, if you run it from command line:

  • dotnet run will use default full state simulator, and print "InitAnd body"
  • dotnet run -s ToffoliSimulator will use Toffoli with the substitution, and print "InitAndToffoli"
  • dotnet run -s ResourcesEstimator will use resources estimator without substitution, print "InitAnd body" and the resources count.

The accompanying .csproj file looks like this (note the package reference which provides Microsoft.Quantum.Targeting namespace):

<Project Sdk="Microsoft.Quantum.Sdk/0.18.2107153439">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.Quantum.AutoSubstitution" 
      Version="0.18.2107153439" IsQscReference="true" />
  </ItemGroup>
</Project>
$\endgroup$
2
  • 1
    $\begingroup$ Include example code here showing how to do it [linking to an external answer is discouraged] and I'll accept this answer as the solution. $\endgroup$ Aug 2 '21 at 23:59
  • $\begingroup$ @CraigGidney Done :-) I haven't tried this feature before so took a bit of experimenting $\endgroup$ Aug 3 '21 at 2:13
5
$\begingroup$

One possibility is to override a default implementation using an alternative one for a specific simulator. Here, init_and would be the default implementation as in the uncommented code and CCNOT would be the alternative.

In the Q# libraries, we have the exact same case for Microsoft.Quantum.Canon.ApplyAnd and Microsoft.Quantum.Canon.ApplyLowDepthAnd. Here is a cut down version of https://github.com/microsoft/QuantumLibraries/blob/main/Standard/src/Canon/And.cs:

#nullable enable

using System;
using Microsoft.Quantum.Simulation.Core;
using Microsoft.Quantum.Simulation.Simulators;

namespace SameNameSpaceAsQSharpInitAnd {
    public partial class init_and {
        public class Native : init_and {
            private bool use_classical_adjoint;

            public Native(IOperationFactory m) : base(m) {
                use_classical_adjoint = m is ToffoliSimulator;
            }

            public override Func<(Qubit, Qubit, Qubit), QVoid> __AdjointBody__ {
                get {
                    if (use_classical_adjoint) {
                        return base.__Body__;
                    }
                    return base.__AdjointBody__;
                }
            }

        }
    }
}

In your case overrides for __ControlledBody__ and __ControlledAdjointBody__ are not required.

The C# code needs to be implemented in the same project and namespace where you implement the init_and Q# operation.

$\endgroup$
0
2
$\begingroup$

Here is currently how I get this done. I have a Q# file that defines an operation is_toffoli_simulator which is then overriden in a C# file. Then I can use that operation in any method I want, instead of having to do something special for each method. The main downside is that because is_toffoli_simulator returns a value, it prevents the automatic creation of an adjoint.

In Q#:

namespace Hackery {
    operation is_toffoli_simulator() : Bool {
        // HACK: overriden in C# file.
        return false;
    }
}

In C#:

#nullable enable

using System;
using Microsoft.Quantum.Simulation.Core;
using Microsoft.Quantum.Simulation.Simulators;

namespace Hackery {
    public partial class is_toffoli_simulator {
        public class Native : is_toffoli_simulator {
            private Func<QVoid, bool> _body;

            public Native(IOperationFactory m) : base(m) {
                var b = m is ToffoliSimulator;
                _body = args => b;
            }

            public override Func<QVoid, bool> __Body__ => _body;
        }
    }
}

At which point you can do:

    operation init_and(a: Qubit, b: Qubit, target: Qubit) : Unit is Adj {
        body(...) {
            CCNOT(a, b, target);
        }
        adjoint(...) {
            if (is_toffoli_simulator()) {
                CCNOT(a, b, target);
            } else {
                H(target);
                if (M(target) == One) {
                    CZ(a, b);
                }
            }
        }
    }
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.