def get_logical_operations(
    operations: cirq.OP_TREE, initial_mapping: dict[ops.Qid, ops.Qid]
) -> Iterable[cirq.Operation]:
    """Gets the logical operations specified by the physical operations and
    initial mapping.

    Args:
        operations: The physical operations.
        initial_mapping: The initial mapping of physical to logical qubits.

    Raises:
        ValueError: A non-permutation physical operation acts on an unmapped
            qubit.
    """
    mapping = initial_mapping.copy()
    for op in cast(Iterable['cirq.Operation'], ops.flatten_op_tree(operations)):
        if isinstance(op, ops.GateOperation) and isinstance(op.gate, PermutationGate):
            op.gate.update_mapping(mapping, op.qubits)
        else:
            for q in op.qubits:
                if mapping.get(q) is None:
                    raise ValueError(f'Operation {op} acts on unmapped qubit {q}.')
            yield op.transform_qubits(mapping.__getitem__)
Gets the logical operations specified by the physical operations and
    initial mapping.

    Args:
        operations: The physical operations.
        initial_mapping: The initial mapping of physical to logical qubits.

    Raises:
        ValueError: A non-permutation physical operation acts on an unmapped
            qubit.
    
cirq-core/cirq/contrib/acquaintance/permutation.py