Skip to content
This repository has been archived by the owner on Jan 12, 2024. It is now read-only.

Commit

Permalink
Optimize Grover's Search Sudoku Sample (#695)
Browse files Browse the repository at this point in the history
* Encode sudoku constraints into search space

Greatly optimizes performace

(See https://devblogs.microsoft.com/qsharp/analyzing-sudoku-solver-using-resources-estimation/)

* Apply syntactic and documentation suggestions from code review

Co-authored-by: Cassandra Granade <cgranade@gmail.com>

* Fix issue in paramerization

* Improve code readability

* Improve examples to better reflect performance

* Implement code comment suggestions from code review

* Add warning for examples that are slow to complete

* Fix documentation of Unit operation having Output

* Remove redundant condition

* Fix documentation issues for sudoku grover

* Fix error introduced through code cleanup

* Apply requested stylistic changes from PR

* Implement change suggestions from PR

* Apply comment suggestions from PR

Co-authored-by: Cassandra Granade <cgranade@gmail.com>

* Update comments to reflect new names

Co-authored-by: Mariia Mykhailova <michaylova@gmail.com>

* Improve documentation wording

* Add subgrid constraints

* Change `bool` to `var`

Co-authored-by: Cassandra Granade <cgranade@gmail.com>

Co-authored-by: Cassandra Granade <cgranade@gmail.com>
Co-authored-by: Mariia Mykhailova <michaylova@gmail.com>
  • Loading branch information
3 people committed Aug 11, 2022
1 parent 3b14a5a commit 7aa6881
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 294 deletions.
280 changes: 43 additions & 237 deletions samples/algorithms/sudoku-grover/ColoringGroverWithConstraints.qs
Expand Up @@ -30,8 +30,8 @@ namespace Microsoft.Quantum.Samples.ColoringGroverWithConstraints {
/// ## register
/// The register of qubits to be measured.
operation MeasureColoring (bitsPerColor : Int, register : Qubit[]) : Int[] {
let numVertices = Length(register) / bitsPerColor;
let colorPartitions = Partitioned(ConstantArray(numVertices - 1, bitsPerColor), register);
let nVertices = Length(register) / bitsPerColor;
let colorPartitions = Partitioned([bitsPerColor, size=(nVertices - 1)], register);
return ForEach(MeasureColor, colorPartitions);
}

Expand Down Expand Up @@ -60,23 +60,20 @@ namespace Microsoft.Quantum.Samples.ColoringGroverWithConstraints {
}

/// # Summary
/// Oracle for verifying vertex coloring, including color constraints from
/// non qubit vertices.
/// Oracle for verifying vertex coloring
///
/// # Input
/// ## numVertices
/// ## nVertices
/// The number of vertices in the graph.
/// ## bitsPerColor
/// The bits per color e.g. 2 bits per color allows for 4 colors.
/// ## edges
/// The array of (Vertex#,Vertex#) specifying the Vertices that can not be
/// the same color.
/// ## startingColorConstraints
/// The array of (Vertex#,Color) specifying the disallowed colors for vertices.
///
/// # Output
/// A unitary operation that applies `oracle` on the target register if the control
/// register state corresponds to the bit mask `bits`.
/// An marking oracle that marks as allowed those states in which the colors of qubits related by an edge constraint are not equal.
///
///
/// # Example
/// Consider the following 4x4 Sudoku puzzle
Expand Down Expand Up @@ -113,259 +110,67 @@ namespace Microsoft.Quantum.Samples.ColoringGroverWithConstraints {
/// 1---2
/// ```
/// i.e. every vertex is connected to each other.
/// Additionally, we require that:
///
/// - vertices 0 and 1 do not get colors 2 and 3.
/// - vertices 2 and 3 do not get colors 3 and 0.
/// - vertices 0 and 2 do not get colors 1 and 3.
/// - vertices 1 and 3 do not get colors 2 and 0.
/// This results in edges (vertices that can not be same color):
/// `edges = [(1, 0),(2, 0),(3, 0),(3, 1),(3, 2)]`
/// This is saying that vertex 1 can not have same color as vertex 0 etc.
/// and startingColorConstraints = [(0, 1),(0, 3),(0, 2),(1, 2),(1, 0),
/// (1, 3),(2, 1),(2, 3),(2, 0),(3, 2),(3, 0),(3, 1)]
/// This is saying that vertex 0 is not allowed to have values 1,3,2
/// and vertex 1 is not allowed to have values 2,0,3
/// and vertex 2 is not allowed to have values 1,3,0
/// and vertex 3 is not allowed to have values 2,0,1
/// A valid graph coloring solution is: [0,1,2,3]
/// i.e. vertex 0 has color 0, vertex 1 has color 1 etc.
operation ApplyVertexColoringOracle (
numVertices : Int, bitsPerColor : Int, edges : (Int, Int)[],
startingColorConstraints : (Int, Int)[],
nVertices : Int,
bitsPerColor : Int,
edges : (Int, Int)[],
colorsRegister : Qubit[],
target : Qubit
)
: Unit is Adj + Ctl {
let nEdges = Length(edges);
let nStartingColorConstraints = Length(startingColorConstraints);
// we are looking for a solution that:
// (a) has no edge with same color at both ends and
// (b) has no Vertex with a color that violates the starting color constraints.
// we are looking for a solution that has no edge with same color at both ends
use edgeConflictQubits = Qubit[nEdges];
use startingColorConflictQubits = Qubit[nStartingColorConstraints];
within {
ConstrainByEdgeAndStartingColors(
colorsRegister, edges, startingColorConstraints,
edgeConflictQubits, startingColorConflictQubits, bitsPerColor
);
} apply {
// If there are no conflicts (all qubits are in 0 state), the vertex coloring is valid.
ControlledOnInt(0, X)(edgeConflictQubits + startingColorConflictQubits, target);
}
}

operation ConstrainByEdgeAndStartingColors(
colorsRegister : Qubit[], edges : (Int, Int)[],
startingColorConstraints : (Int, Int)[],
edgeConflictQubits : Qubit[], startingColorConflictQubits : Qubit[], bitsPerColor: Int
)
: Unit is Adj + Ctl {
for ((start, end), conflictQubit) in Zipped(edges, edgeConflictQubits) {
// Check that endpoints of the edge have different colors:
// apply ColorEqualityOracle_Nbit oracle;
// if the colors are the same the result will be 1, indicating a conflict
ApplyColorEqualityOracle(
colorsRegister[start * bitsPerColor .. (start + 1) * bitsPerColor - 1],
colorsRegister[end * bitsPerColor .. (end + 1) * bitsPerColor - 1],
conflictQubit
);
}
for ((cell, value), conflictQubit) in Zipped(startingColorConstraints, startingColorConflictQubits) {
// Check that cell does not clash with starting colors.
ControlledOnInt(value, X)(
colorsRegister[cell * bitsPerColor .. (cell + 1) * bitsPerColor - 1],
conflictQubit
);
}

}

/// # Summary
/// Oracle for verifying vertex coloring, including color constraints
/// from non qubit vertices. This is the same as ApplyVertexColoringOracle,
/// but hardcoded to 4 bits per color and restriction that colors are
/// limited to 0 to 8.
///
/// # Input
/// ## numVertices
/// The number of vertices in the graph.
/// ## edges
/// The array of (Vertex#,Vertex#) specifying the Vertices that can not
/// be the same color.
/// ## startingColorConstraints
/// The array of (Vertex#,Color) specifying the disallowed colors for vertices.
/// ## colorsRegister
/// The color register.
/// ## target
/// The target of the operation.
///
/// # Output
/// A unitary operation that applies `oracle` on the target register if the control
/// register state corresponds to the bit mask `bits`.
///
/// # Example
/// Consider the following 9x9 Sudoku puzzle:
/// ```
/// -------------------------------------
/// | | 6 | 2 | 7 | 8 | 3 | 4 | 0 | 1 |
/// -------------------------------------
/// | 8 | | 1 | 6 | 2 | 4 | 3 | 7 | 5 |
/// -------------------------------------
/// | 7 | 3 | 4 | 5 | 0 | 1 | 8 | 6 | 2 |
/// -------------------------------------
/// | 6 | 8 | 7 | 1 | 5 | 0 | 2 | 4 | 3 |
/// -------------------------------------
/// | 4 | 1 | 5 | 3 | 6 | 2 | 7 | 8 | 0 |
/// -------------------------------------
/// | 0 | 2 | 3 | 4 | 7 | 8 | 1 | 5 | 6 |
/// -------------------------------------
/// | 3 | 5 | 8 | 0 | 1 | 7 | 6 | 2 | 4 |
/// -------------------------------------
/// | 1 | 7 | 6 | 2 | 4 | 5 | 0 | 3 | 8 |
/// -------------------------------------
/// | 2 | 4 | 0 | 8 | 3 | 6 | 5 | 1 | 7 |
/// -------------------------------------
/// ```
/// The challenge is to fill the empty squares with numbers 0 to 8
/// that are unique in row, column and the top left 3x3 square
/// This is a graph coloring problem where the colors are 0 to 8
/// and the empty cells are the vertices. The vertices can be defined as
/// ```
/// -----------------
/// | 0 | | | | ...
/// -----------------
/// | | 1 | | | ...
/// -----------------
/// | | | | | ...
/// ...
/// ```
/// The graph is
/// ```
/// 0---1
/// ```
/// Additionally, we also require that
/// - vertex 0 can not have value 6,2,7,8,3,4,0,1 (row constraint)
/// or value 8,7,6,4,0,3,1,2 (col constraint)
/// - vertex 1 can not value 8,1,6,2,4,3,7,5 (row constraint)
/// or value 6,3,8,1,2,5,7,4 (col constraint)
/// This results in edges (vertices that can not be same color)
/// edges = [(1, 0)]
/// This is saying that vertex 1 can not have same color as vertex 0
/// and startingColorConstraints = [(0, 8),(0, 7),(0, 6),(0, 4),(0, 0),(0, 3),
/// (0, 1),(0, 2),(1, 6),(1, 3),(1, 8),(1, 1),(1, 2),(1, 5),(1, 7),(1, 4)]
/// The colors found must be from 0 to 8, which requires 4 bits per color.
/// A valid graph coloring solution is: [5,0]
/// i.e. vertex 0 has color 5, vertex 1 has color 0.
operation ApplyVertexColoringOracle4Bit9Color (numVertices : Int, edges : (Int, Int)[],
startingColorConstraints : (Int, Int)[],
colorsRegister : Qubit[], target : Qubit) : Unit is Adj+Ctl {
let nEdges = Length(edges);
let bitsPerColor = 4; // 4 bits per color
let nStartingColorConstraints = Length(startingColorConstraints);
// we are looking for a solution that:
// (a) has no edge with same color at both ends and
// (b) has no Vertex with a color that violates the starting color constraints.
use edgeConflictQubits = Qubit[nEdges];
use startingColorConflictQubits = Qubit[nStartingColorConstraints];
use vertexColorConflictQubits = Qubit[numVertices];
within {
ConstrainByEdgeAndStartingColors(
colorsRegister, edges, startingColorConstraints,
edgeConflictQubits, startingColorConflictQubits, bitsPerColor
);
let zippedColorAndConfictQubit = Zipped(
Partitioned(ConstantArray(numVertices, bitsPerColor), colorsRegister),
vertexColorConflictQubits
);
for (color, conflictQubit) in zippedColorAndConfictQubit {
// Only allow colors from 0 to 8 i.e. if bit #3 = 1, then bits 2..0 must be 000.
use tempQubit = Qubit();
within {
ApplyOrOracle(color[0 .. 2], tempQubit);
} apply{
// AND color's most significant bit with OR of least significant bits.
// This will set conflictQubit to 1 if color > 8.
CCNOT(color[3], tempQubit, conflictQubit);
}
for ((start, end), conflictQubit) in Zipped(edges, edgeConflictQubits) {
// Check that endpoints of the edge have different colors:
// apply ApplyColorEqualityOracle oracle;
// if the colors are the same the result will be 1, indicating a conflict
ApplyColorEqualityOracle(
colorsRegister[start * bitsPerColor .. (start + 1) * bitsPerColor - 1],
colorsRegister[end * bitsPerColor .. (end + 1) * bitsPerColor - 1],
conflictQubit
);
}
} apply {
// If there are no conflicts (all qubits are in 0 state), the vertex coloring is valid.
ControlledOnInt(0, X)(edgeConflictQubits + startingColorConflictQubits + vertexColorConflictQubits, target);
ControlledOnInt(0, X)(edgeConflictQubits, target);
}
}

/// # Summary
/// OR oracle for an arbitrary number of qubits in query register.
///
/// # Inputs
/// ## queryRegister
/// Qubit register to query.
/// ## target
/// Target qubit for storing oracle result.
operation ApplyOrOracle (queryRegister : Qubit[], target : Qubit) : Unit is Adj {
// x₀ ∨ x₁ = ¬ (¬x₀ ∧ ¬x₁)
// First, flip target if both qubits are in |0⟩ state.
ControlledOnInt(0, X)(queryRegister, target);
// Then flip target again to get negation.
X(target);
}

/// # Summary
/// Using Grover's search to find vertex coloring.
///
/// # Input
/// ## numVertices
/// ## nVertices
/// The number of Vertices in the graph.
/// ## bitsPerColor
/// The number of bits per color.
/// ## maxIterations
/// An estimate of the maximum iterations needed.
/// ## nIterations
/// The number of iterations needed.
/// ## oracle
/// The Oracle used to find solution.
/// ## statePrep
/// An operation that prepares an equal superposition of all basis states in the search space.
///
/// # Output
/// Int Array giving the color of each vertex.
///
/// # Remarks
/// See https://github.com/microsoft/QuantumKatas/tree/main/SolveSATWithGrover
/// for original implementation in SolveSATWithGrover Kata.
operation FindColorsWithGrover (numVertices : Int, bitsPerColor : Int, maxIterations : Int,
oracle : ((Qubit[], Qubit) => Unit is Adj)) : Int[] {
// This task is similar to task 2.2 from SolveSATWithGrover kata,
// but the percentage of correct solutions is potentially higher.
mutable coloring = new Int[numVertices];
/// An array giving the color of each vertex.
operation FindColorsWithGrover(
nVertices : Int,
bitsPerColor : Int,
nIterations : Int,
oracle : ((Qubit[], Qubit) => Unit is Adj),
statePrep : (Qubit[] => Unit is Adj)) : Int[] {

// Note that coloring register has the number of qubits that is
// twice the number of vertices (bitsPerColor qubits per vertex).
use register = Qubit[bitsPerColor * numVertices];
use output = Qubit();
// Coloring register has bitsPerColor qubits for each vertex
use register = Qubit[bitsPerColor * nVertices];

mutable correct = false;
mutable iter = 1;
// Try for one iteration, if it fails, try again for one more iteration and repeat until maxIterations is reached.
repeat {
Message($"Trying search with {iter} iterations...");
ApplyGroversAlgorithmLoop(register, oracle, iter);
let res = MultiM(register);
// to check whether the result is correct, apply the oracle to the
// register plus auxiliary after measurement.
oracle(register, output);
if (MResetZ(output) == One) {
set correct = true;
// Read off coloring.
set coloring = MeasureColoring(bitsPerColor, register);
}
ResetAll(register);
} until (correct or iter > maxIterations)
fixup {
set iter += 1;
}
if (not correct) {
fail "Failed to find a coloring.";
Message($"Trying search with {nIterations} iterations...");
if (nIterations > 75) {
Message($"Warning: This might take a while");
}

return coloring;
ApplyGroversAlgorithmLoop(register, oracle, statePrep, nIterations);
return MeasureColoring(bitsPerColor, register);
}

/// # Summary
Expand Down Expand Up @@ -407,21 +212,22 @@ namespace Microsoft.Quantum.Samples.ColoringGroverWithConstraints {
/// ## iterations
/// The number of iterations to try.
///
/// # Output
/// # Remarks
/// Unitary implementing Grover's search algorithm.
operation ApplyGroversAlgorithmLoop(
register : Qubit[],
oracle : ((Qubit[], Qubit) => Unit is Adj),
statePrep : (Qubit[] => Unit is Adj),
iterations : Int
)
: Unit {
let applyPhaseOracle = ApplyPhaseOracle(oracle, _);
ApplyToEach(H, register);
statePrep(register);

for _ in 1 .. iterations {
applyPhaseOracle(register);
within {
ApplyToEachA(H, register);
Adjoint statePrep(register);
ApplyToEachA(X, register);
} apply {
Controlled Z(Most(register), Tail(register));
Expand Down

0 comments on commit 7aa6881

Please sign in to comment.