def encode(
        self, additional_qubits: list[cirq.Qid], unencoded_qubits: list[cirq.Qid]
    ) -> cirq.Circuit:
        """Creates a circuit that encodes the qubits using the code words.

        Args:
            additional_qubits: The list of self.n - self.k qubits needed to encode the qubit.
            unencoded_qubits: The list of self.k qubits that contain the original message.

        Returns:
            A circuit where the self.n qubits are the encoded qubits.
        """
        assert len(additional_qubits) == self.n - self.k
        assert len(unencoded_qubits) == self.k
        qubits = additional_qubits + unencoded_qubits

        circuit = cirq.Circuit()

        # Equation 4.8:
        # This follows the improvements of:
        # https://cs269q.stanford.edu/projects2019/stabilizer_code_report_Y.pdf
        for r, x in enumerate(self.logical_Xs):
            for j in range(self.r, self.n - self.k):
                # By constructions, the first r rows can never contain a Z, as
                # r is defined by the Gaussian elimination as the rank.
                if x[j] == 'X' or x[j] == 'Y':
                    circuit.append(
                        cirq.ControlledOperation(
                            [unencoded_qubits[r]], cirq.X(additional_qubits[j])
                        )
                    )

        gate_dict = {'X': cirq.X, 'Y': cirq.Y, 'Z': cirq.Z}

        for r in range(self.r):
            circuit.append(cirq.H(qubits[r]))

            # Let's consider the first stabilizer:
            # The reason for adding S gate is Y gate we used is the complex format (i.e. to
            # make it Hermitian). It has following four cases: (ignore the phase factor)
            # (I+X@P_2...P_k)|0...0> = |0...0> + |1>|\psi>
            # (I+Y@P_2...P_k)|0...0> = |0...0> + i|1>|\psi>
            # The other forms are not possible in the standard form, by construction.

            # The first case means we need [1,1] vector and controlled gates and in the
            # second case we need [1, i] vector and controlled gates. Corresponding, it is
            # the first column of H and the first column of SH respectively.

            # For the other stabilizers, the process can be repeated, as by definition they
            # commute.

            if self.M[r][r] == 'Y' or self.M[r][r] == 'Z':
                circuit.append(cirq.S(qubits[r]))

            for n in range(self.n):
                if n == r:
                    continue
                if self.M[r][n] == 'I':
                    continue
                op = gate_dict[self.M[r][n]]
                circuit.append(cirq.ControlledOperation([qubits[r]], op(qubits[n])))
        # At this stage, the state vector should be equal to equations 3.17 and 3.18.

        return circuit
Creates a circuit that encodes the qubits using the code words.

        Args:
            additional_qubits: The list of self.n - self.k qubits needed to encode the qubit.
            unencoded_qubits: The list of self.k qubits that contain the original message.

        Returns:
            A circuit where the self.n qubits are the encoded qubits.
        
examples/stabilizer_code.py