Strilanc/Quirk

View on GitHub
src/gates/ParametrizedRotationGates.js

Summary

Maintainability
F
6 days
Test Coverage
/**
 * Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {GateBuilder} from "../circuit/Gate.js"
import {GatePainting} from "../draw/GatePainting.js"
import {Complex, PARSE_COMPLEX_TOKEN_MAP_RAD} from "../math/Complex.js"
import {Matrix} from "../math/Matrix.js"
import {ketArgs, ketShader, ketShaderPhase, ketInputGateShaderCode} from "../circuit/KetShaderUtil.js"
import {WglArg} from "../webgl/WglArg.js"
import {Util} from "../base/Util.js";
import {parseFormula} from "../math/FormulaParser.js";
import {XExp, YExp, ZExp} from "./ExponentiatingGates.js";
import {Config} from "../Config.js";

let ParametrizedRotationGates = {};

/**
 * @param {!string} pattern
 * @param {!int} xyz
 * @param {!number} tScale
 * @returns {!function(args: !GateDrawParams)}
 */
function configurableRotationDrawer(pattern, xyz, tScale) {
    let xScale = [1, 0.5, -1][xyz];
    let yScale = [1, 1, -0.5][xyz];
    return args => {
        GatePainting.paintBackground(args, Config.TIME_DEPENDENT_HIGHLIGHT_COLOR);
        GatePainting.paintOutline(args);
        let text = pattern;
        if (!args.isInToolbox) {
            text = text.split('f(t)').join(args.gate.param);
        }
        GatePainting.paintGateSymbol(args, text, pattern.indexOf('^') !== -1);
        GatePainting.paintGateButton(args);

        let isStable = args.gate.stableDuration() === Infinity;
        if ((!args.isInToolbox || args.isHighlighted) && !isStable) {
            let rads = tScale * parseTimeFormula(args.gate.param, args.stats.time*2-1, false) || 0;
            GatePainting.paintCycleState(args, rads, xScale, yScale);
        }
    };
}

/**
 * @param {!GateDrawParams} args
 */
function exponent_to_A_len_painter(args) {
    let v = args.getGateContext('Input Range A');
    let denom_exponent = v === undefined ? 'ⁿ' : Util.digits_to_superscript_digits('' + v.length);
    let symbol = args.gate.symbol.replace('ⁿ', denom_exponent);
    GatePainting.paintBackground(args);
    GatePainting.paintOutline(args);
    GatePainting.paintGateSymbol(args, symbol);
}

const X_TO_A_SHADER = ketShader(
    `
        uniform float factor;
        ${ketInputGateShaderCode('A')}
    `,
    `
        float angle = read_input_A() * factor / _gen_input_span_A;
        float c = cos(angle) * 0.5;
        float s = sin(angle) * 0.5;
        vec2 u = vec2(0.5 + c, s);
        vec2 v = vec2(0.5 - c, -s);
        // multiply state by the matrix [[u, v], [v, u]]
        vec2 amp2 = inp(1.0-out_id);
        return cmul(u, amp) + cmul(v, amp2);
    `);

const Y_TO_A_SHADER = ketShader(
    `
        uniform float factor;
        ${ketInputGateShaderCode('A')}
    `,
    `
        float angle = read_input_A() * factor / _gen_input_span_A;
        float c = cos(angle) * 0.5;
        float s = sin(angle) * 0.5;
        vec2 u = vec2(c + 0.5, s);
        vec2 v = vec2(s, 0.5 - c);
        // multiply state by the matrix [[u, v], [-v, u]]
        vec2 amp2 = inp(1.0-out_id);
        vec2 vs = v * (-1.0 + 2.0 * out_id);
        return cmul(u, amp) + cmul(vs, amp2);
    `);

const Z_TO_A_SHADER = ketShaderPhase(
    `
        uniform float factor;
        ${ketInputGateShaderCode('A')}
    `,
    `
        return read_input_A() * out_id * factor / _gen_input_span_A;
    `);

ParametrizedRotationGates.XToA = new GateBuilder().
    setSerializedId("X^(A/2^n)").
    setSymbol("X^A/2ⁿ").
    setTitle("Parametrized X Gate").
    setBlurb("Rotates the target by input A / 2ⁿ'th of a half turn around the X axis.\n" +
        "n is the number of qubits in input A.").
    setRequiredContextKeys('Input NO_DEFAULT Range A').
    setDrawer(exponent_to_A_len_painter).
    setActualEffectToShaderProvider(ctx => X_TO_A_SHADER.withArgs(
        ...ketArgs(ctx, 1, ['A']),
        WglArg.float('factor', Math.PI))).
    promiseEffectIsStable().
    promiseEffectIsUnitary().
    gate;

ParametrizedRotationGates.XToMinusA = new GateBuilder().
    setAlternate(ParametrizedRotationGates.XToA).
    setSerializedId("X^(-A/2^n)").
    setSymbol("X^-A/2ⁿ").
    setTitle("Parametrized -X Gate").
    setBlurb("Counter-rotates the target by input A / 2ⁿ'th of a half turn around the X axis.\n" +
        "n is the number of qubits in input A.").
    setRequiredContextKeys('Input NO_DEFAULT Range A').
    setDrawer(exponent_to_A_len_painter).
    setActualEffectToShaderProvider(ctx => X_TO_A_SHADER.withArgs(
        ...ketArgs(ctx, 1, ['A']),
        WglArg.float('factor', -Math.PI))).
    promiseEffectIsStable().
    promiseEffectIsUnitary().
    gate;

ParametrizedRotationGates.YToA = new GateBuilder().
    setSerializedId("Y^(A/2^n)").
    setSymbol("Y^A/2ⁿ").
    setTitle("Parametrized Y Gate").
    setBlurb("Rotates the target by input A / 2ⁿ'th of a half turn around the Y axis.\n" +
        "n is the number of qubits in input A.").
    setRequiredContextKeys('Input NO_DEFAULT Range A').
    setDrawer(exponent_to_A_len_painter).
    setActualEffectToShaderProvider(ctx => Y_TO_A_SHADER.withArgs(
        ...ketArgs(ctx, 1, ['A']),
        WglArg.float('factor', Math.PI))).
    promiseEffectIsStable().
    promiseEffectIsUnitary().
    gate;

ParametrizedRotationGates.YToMinusA = new GateBuilder().
    setAlternate(ParametrizedRotationGates.YToA).
    setSerializedId("Y^(-A/2^n)").
    setSymbol("Y^-A/2ⁿ").
    setTitle("Parametrized -Y Gate").
    setBlurb("Counter-rotates the target by input A / 2ⁿ'th of a half turn around the Y axis.\n" +
        "n is the number of qubits in input A.").
    setRequiredContextKeys('Input NO_DEFAULT Range A').
    setDrawer(exponent_to_A_len_painter).
    setActualEffectToShaderProvider(ctx => Y_TO_A_SHADER.withArgs(
        ...ketArgs(ctx, 1, ['A']),
        WglArg.float('factor', -Math.PI))).
    promiseEffectIsStable().
    promiseEffectIsUnitary().
    gate;

ParametrizedRotationGates.ZToA = new GateBuilder().
    setSerializedId("Z^(A/2^n)").
    setSymbol("Z^A/2ⁿ").
    setTitle("Parametrized Z Gate").
    setBlurb("Rotates the target by input A / 2ⁿ'th of a half turn around the Z axis.\n" +
        "n is the number of qubits in input A.").
    setRequiredContextKeys('Input NO_DEFAULT Range A').
    setDrawer(exponent_to_A_len_painter).
    setActualEffectToShaderProvider(ctx => Z_TO_A_SHADER.withArgs(
        ...ketArgs(ctx, 1, ['A']),
        WglArg.float('factor', Math.PI))).
    promiseEffectIsStable().
    promiseEffectOnlyPhases().
    gate;

ParametrizedRotationGates.ZToMinusA = new GateBuilder().
    setAlternate(ParametrizedRotationGates.ZToA).
    setSerializedId("Z^(-A/2^n)").
    setSymbol("Z^-A/2ⁿ").
    setTitle("Parametrized -Z Gate").
    setBlurb("Counter-rotates the target by input A / 2ⁿ'th of a half turn around the Z axis.\n" +
        "n is the number of qubits in input A.").
    setRequiredContextKeys('Input NO_DEFAULT Range A').
    setDrawer(exponent_to_A_len_painter).
    setActualEffectToShaderProvider(ctx => Z_TO_A_SHADER.withArgs(
        ...ketArgs(ctx, 1, ['A']),
        WglArg.float('factor', -Math.PI))).
    promiseEffectIsStable().
    promiseEffectOnlyPhases().
    gate;

/**
 * @param {!string} formula
 * @param {undefined|!number} time
 * @param {!boolean} warn
 * @returns {undefined|!number}
 */
function parseTimeFormula(formula, time, warn) {
    let tokenMap = new Map([...PARSE_COMPLEX_TOKEN_MAP_RAD.entries()]);
    if (time !== undefined) {
        tokenMap.set('t', time);
    }
    try {
        let angle = Complex.from(parseFormula(formula, tokenMap));
        if (Math.abs(angle.imag) > 0.0001) {
            throw new Error(`Non-real angle: ${formula} = ${angle}`);
        }
        return angle.real;
    } catch (ex) {
        if (warn) {
            console.warn(ex);
        }
        return undefined;
    }
}

/**
 * @param {!GateCheckArgs} args
 * @returns {undefined|!string}
 */
function badFormulaDetector(args) {
    if (typeof args.gate.param === 'number') {
        return args.gate.param;
    } else if (typeof args.gate.param === 'string') {
        for (let t of [0.01, 0.63, 0.98]) {
            if (parseTimeFormula(args.gate.param, t, false) === undefined) {
                return 'bad\nformula';
            }
        }
        return undefined;
    } else {
        return 'bad\nvalue';
    }
}

/**
 * @param {!Gate} gate
 */
function updateUsingFormula(gate) {
    let stable = parseTimeFormula(gate.param, undefined, false) !== undefined;
    gate._stableDuration = stable ? Infinity : 0;

    if (typeof gate.param === 'string') {
        gate.width = Math.ceil((gate.param.length+1)/5);
        gate.alternate = gate._copy();
        gate.alternate.alternate = gate;
        if (gate.param.startsWith('-(') && gate.param.endsWith(')')) {
            gate.alternate.param = gate.param.substring(2, gate.param.length - 1);
        } else {
            gate.alternate.param = '-(' + gate.param + ')';
        }
    } else {
        gate.width = 1;
        gate.alternate = gate;
    }
}

/**
 * @param {!string} quantityName
 * @returns {!function(gate: !Gate): !Gate}
 */
function angleClicker(quantityName) {
    return oldGate => {
        let txt = prompt(
            `Enter a formula to use for the ${quantityName}.\n` +
            "\n" +
            "The formula can depend on the time variable t.\n" +
            "Time t starts at -1, grows to +1 over time, then jumps back to -1.\n" +
            "Invalid results will default to 0.\n" +
            "\n" +
            "Available constants: e, pi\n" +
            "Available functions: cos, sin, acos, asin, tan, atan, ln, sqrt, exp\n" +
            "Available operators: + * / - ^",
            '' + oldGate.param);
        if (txt === null || txt.trim() === '') {
            return oldGate;
        }
        return oldGate.withParam(txt);
    };
}

ParametrizedRotationGates.FormulaicRotationX = new GateBuilder().
    setSerializedIdAndSymbol("X^ft").
    setTitle("Formula X Rotation").
    setBlurb("Rotates around X by an amount determined by a formula.").
    setDrawer(configurableRotationDrawer('X^f(t)', 0, Math.PI)).
    setWidth(2).
    setExtraDisableReasonFinder(badFormulaDetector).
    setOnClickGateFunc(angleClicker("X gate's exponent")).
    setEffectToTimeVaryingMatrix((t, formula) => {
        let exponent = parseTimeFormula(formula, t*2-1, true) || 0;
        return Matrix.fromPauliRotation(exponent/2, 0, 0);
    }).
    setWithParamPropertyRecomputeFunc(updateUsingFormula).
    promiseEffectIsUnitary().
    gate.withParam('sin(pi t)');

ParametrizedRotationGates.FormulaicRotationY = new GateBuilder().
    setSerializedIdAndSymbol("Y^ft").
    setTitle("Formula Y Rotation").
    setBlurb("Rotates around Y by an amount determined by a formula.").
    setDrawer(configurableRotationDrawer('Y^f(t)', 1, Math.PI)).
    setWidth(2).
    setExtraDisableReasonFinder(badFormulaDetector).
    setOnClickGateFunc(angleClicker("Y gate's exponent")).
    setEffectToTimeVaryingMatrix((t, formula) => {
        let exponent = parseTimeFormula(formula, t*2-1, true) || 0;
        return Matrix.fromPauliRotation(0, exponent/2, 0);
    }).
    setWithParamPropertyRecomputeFunc(updateUsingFormula).
    promiseEffectIsUnitary().
    gate.withParam('sin(pi t)');

ParametrizedRotationGates.FormulaicRotationZ = new GateBuilder().
    setSerializedIdAndSymbol("Z^ft").
    setTitle("Formula Z Rotation").
    setBlurb("Rotates around Z by an amount determined by a formula.").
    setDrawer(configurableRotationDrawer('Z^f(t)', 2, Math.PI)).
    setWidth(2).
    setExtraDisableReasonFinder(badFormulaDetector).
    setOnClickGateFunc(angleClicker("Z gate's exponent")).
    setEffectToTimeVaryingMatrix((t, formula) => {
        let exponent = parseTimeFormula(formula, t*2-1, true) || 0;
        return Matrix.fromPauliRotation(0, 0, exponent/2);
    }).
    setWithParamPropertyRecomputeFunc(updateUsingFormula).
    promiseEffectOnlyPhases().
    gate.withParam('sin(pi t)');

ParametrizedRotationGates.FormulaicRotationRx = new GateBuilder().
    setSerializedIdAndSymbol("Rxft").
    setTitle("Formula Rx Gate").
    setBlurb("Rotates around X by an angle in radians determined by a formula.").
    setDrawer(configurableRotationDrawer('Rx(f(t))', 0, 1)).
    setWidth(2).
    setExtraDisableReasonFinder(badFormulaDetector).
    setOnClickGateFunc(angleClicker("Rx gate's angle in radians")).
    setEffectToTimeVaryingMatrix((t, formula) => XExp((parseTimeFormula(formula, t*2-1, true) || 0) / Math.PI / 4)).
    setWithParamPropertyRecomputeFunc(updateUsingFormula).
    promiseEffectIsUnitary().
    gate.withParam('pi t^2');

ParametrizedRotationGates.FormulaicRotationRy = new GateBuilder().
    setSerializedIdAndSymbol("Ryft").
    setTitle("Formula Ry Gate").
    setBlurb("Rotates around Y by an angle in radians determined by a formula.").
    setDrawer(configurableRotationDrawer('Ry(f(t))', 1, 1)).
    setWidth(2).
    setExtraDisableReasonFinder(badFormulaDetector).
    setOnClickGateFunc(angleClicker("Ry gate's angle in radians")).
    setEffectToTimeVaryingMatrix((t, formula) => YExp((parseTimeFormula(formula, t*2-1, true) || 0) / Math.PI / 4)).
    setWithParamPropertyRecomputeFunc(updateUsingFormula).
    promiseEffectIsUnitary().
    gate.withParam('pi t^2');

ParametrizedRotationGates.FormulaicRotationRz = new GateBuilder().
    setSerializedIdAndSymbol("Rzft").
    setTitle("Formula Rz Gate").
    setBlurb("Rotates around Z by an angle in radians determined by a formula.").
    setDrawer(configurableRotationDrawer('Rz(f(t))', 2, 1)).
    setWidth(2).
    setExtraDisableReasonFinder(badFormulaDetector).
    setOnClickGateFunc(angleClicker("Rz gate's angle in radians")).
    setEffectToTimeVaryingMatrix((t, formula) => ZExp((parseTimeFormula(formula, t*2-1, true) || 0) / Math.PI / 4)).
    setWithParamPropertyRecomputeFunc(updateUsingFormula).
    promiseEffectOnlyPhases().
    gate.withParam('pi t^2');

ParametrizedRotationGates.all =[
    ParametrizedRotationGates.XToA,
    ParametrizedRotationGates.XToMinusA,
    ParametrizedRotationGates.YToA,
    ParametrizedRotationGates.YToMinusA,
    ParametrizedRotationGates.ZToA,
    ParametrizedRotationGates.ZToMinusA,
    ParametrizedRotationGates.FormulaicRotationX,
    ParametrizedRotationGates.FormulaicRotationY,
    ParametrizedRotationGates.FormulaicRotationZ,
    ParametrizedRotationGates.FormulaicRotationRx,
    ParametrizedRotationGates.FormulaicRotationRy,
    ParametrizedRotationGates.FormulaicRotationRz,
];

export {ParametrizedRotationGates}