constructor(numWires,
                columns,
                outerRowOffset=0,
                outerContext=new Map(),
                customGateSet=new CustomGateSet(),
                isNested=false,
                customInitialValues=new Map()) {
        if (numWires < 0) {
            throw new DetailedError("Bad numWires", {numWires})
        }
        if (!Array.isArray(columns)) {
            throw new DetailedError("Bad columns", {numWires, columns})
        }
        if (!columns.every(e => e instanceof GateColumn)) {
            throw new DetailedError("Not a GateColumn", {columns})
        }
        if (!columns.every(e => e.gates.length === numWires)) {
            throw new DetailedError("Wrong gate count in a column", {numWires, columns})
        }

        /** @type {!int} */
        this.numWires = numWires;
        /** @type {!Array.<!GateColumn>} */
        this.columns = columns;
        /** @type {!CustomGateSet} */
        this.customGateSet = customGateSet;

        this.outerRowOffset = outerRowOffset;
        this.outerContext = outerContext;
        this.isNested = isNested;
        /** @type {!Map.<!int, !string>} */
        this.customInitialValues = new Map();
        for (let [k, v] of customInitialValues.entries()) {
            if (!Number.isInteger(k) || k < 0) {
                throw new DetailedError('Initial state key out of range.', {customInitialValues, numWires});
            }
            if (k >= this.numWires) {
                continue;
            }
            this.customInitialValues.set(k, v);
        }

        /**
         * @type {!Array.<!Array.<undefined|!string>>}
         * @private
         */
        this._colRowDisabledReason = [];
        /**
         * @type {!Array.<undefined|!int>}
         * @private
         */
        this._measureMasks = [0];
        let mask = 0;
        let prevStickyCtx = new Map();
        for (let col of columns) {
            let {allReasons: rowReasons, stickyCtx} = col.perRowDisabledReasons(
                mask,
                outerRowOffset,
                outerContext,
                prevStickyCtx,
                isNested);
            mask = col.nextMeasureMask(mask, rowReasons);
            this._colRowDisabledReason.push(rowReasons);
            this._measureMasks.push(mask);
            prevStickyCtx = stickyCtx;
        }

        /**
         * @type {!Map.<!string, !{col: !int, row: !int, gate: !Gate}>}
         * @private
         */
        this._gateSlotCoverMap = this._computeGateSlotCoverMap();

        /**
         * @type {!Map.<!string, !Array.<!Map.<!string, *>>>}
         * @private
         */
        this._cachedColumnContexts = new Map();
    }
@param {!int} numWires
@param {!Array.<!GateColumn>} columns
@param {undefined|!int=} outerRowOffset
@param {undefined|!Map.<!string, *>=} outerContext
@param {!CustomGateSet} customGateSet
@param {!boolean} isNested
@param {!Map.<!int, !string>} customInitialValues