def sweep_to_proto(
    sweep: cirq.Sweep,
    *,
    out: run_context_pb2.Sweep | None = None,
    sweep_transformer: Callable[[sweeps.SingleSweep], sweeps.SingleSweep] = lambda x: x,
) -> run_context_pb2.Sweep:
    """Converts a Sweep to v2 protobuf message.

    Args:
        sweep: The sweep to convert.
        out: Optional message to be populated. If not given, a new message will
            be created.
        sweep_transformer: A function called on Linspace, Points.

    Returns:
        Populated sweep protobuf message.

    Raises:
        ValueError: If the conversion cannot be completed successfully.
    """
    if out is None:
        out = run_context_pb2.Sweep()
    if sweep is cirq.UnitSweep:
        pass
    elif isinstance(sweep, cirq.Product):
        out.sweep_function.function_type = run_context_pb2.SweepFunction.PRODUCT
        for factor in sweep.factors:
            sweep_to_proto(
                factor, out=out.sweep_function.sweeps.add(), sweep_transformer=sweep_transformer
            )
    elif isinstance(sweep, cirq.ZipLongest):
        out.sweep_function.function_type = run_context_pb2.SweepFunction.ZIP_LONGEST
        for s in sweep.sweeps:
            sweep_to_proto(
                s, out=out.sweep_function.sweeps.add(), sweep_transformer=sweep_transformer
            )
    elif isinstance(sweep, cirq.Zip):
        out.sweep_function.function_type = run_context_pb2.SweepFunction.ZIP
        for s in sweep.sweeps:
            sweep_to_proto(
                s, out=out.sweep_function.sweeps.add(), sweep_transformer=sweep_transformer
            )
    elif isinstance(sweep, cirq.Concat):
        out.sweep_function.function_type = run_context_pb2.SweepFunction.CONCAT
        for s in sweep.sweeps:
            sweep_to_proto(
                s, out=out.sweep_function.sweeps.add(), sweep_transformer=sweep_transformer
            )
    elif isinstance(sweep, cirq.Linspace) and not isinstance(sweep.key, sympy.Expr):
        sweep = cast(cirq.Linspace, sweep_transformer(sweep))
        out.single_sweep.parameter_key = sweep.key
        if isinstance(sweep.start, tunits.Value):
            unit = sweep.start.unit
            out.single_sweep.linspace.first_point = sweep.start[unit]
            out.single_sweep.linspace.last_point = sweep.stop[unit]
            out.single_sweep.linspace.num_points = sweep.length
            unit.to_proto(out.single_sweep.linspace.unit)
        else:
            out.single_sweep.linspace.first_point = sweep.start
            out.single_sweep.linspace.last_point = sweep.stop
            out.single_sweep.linspace.num_points = sweep.length
        # Encode the metadata if present
        if isinstance(sweep.metadata, Metadata):
            out.single_sweep.metadata.MergeFrom(metadata_to_proto(sweep.metadata))
        else:
            # Use duck-typing to support google-internal Parameter objects
            if sweep.metadata and getattr(sweep.metadata, 'path', None):
                out.single_sweep.parameter.path.extend(sweep.metadata.path)
            if sweep.metadata and getattr(sweep.metadata, 'idx', None):
                out.single_sweep.parameter.idx = sweep.metadata.idx
            if sweep.metadata and getattr(sweep.metadata, 'units', None):
                out.single_sweep.parameter.units = sweep.metadata.units
    elif isinstance(sweep, cirq.Points) and not isinstance(sweep.key, sympy.Expr):
        sweep = cast(cirq.Points, sweep_transformer(sweep))
        out.single_sweep.parameter_key = sweep.key
        if len(sweep.points) == 1:
            out.single_sweep.const_value.MergeFrom(_build_sweep_const(sweep.points[0]))
        else:
            if isinstance(sweep.points[0], tunits.Value):
                unit = sweep.points[0].unit
                out.single_sweep.points.points.extend(p[unit] for p in sweep.points)
                unit.to_proto(out.single_sweep.points.unit)
            else:
                out.single_sweep.points.points.extend(sweep.points)
        # Encode the metadata if present
        if isinstance(sweep.metadata, Metadata):
            out.single_sweep.metadata.MergeFrom(metadata_to_proto(sweep.metadata))
        else:
            # Use duck-typing to support google-internal Parameter objects
            if sweep.metadata and getattr(sweep.metadata, 'path', None):
                out.single_sweep.parameter.path.extend(sweep.metadata.path)
            if sweep.metadata and getattr(sweep.metadata, 'idx', None):
                out.single_sweep.parameter.idx = sweep.metadata.idx
            if sweep.metadata and getattr(sweep.metadata, 'units', None):
                out.single_sweep.parameter.units = sweep.metadata.units
    elif isinstance(sweep, cirq.ListSweep):
        sweep_dict: dict[str, list[float]] = {}
        for param_resolver in sweep:
            for key in param_resolver:
                if key not in sweep_dict:
                    sweep_dict[cast(str, key)] = []
                sweep_dict[cast(str, key)].append(cast(float, param_resolver.value_of(key)))
        out.sweep_function.function_type = run_context_pb2.SweepFunction.ZIP
        for key in sweep_dict:
            sweep_to_proto(
                cirq.Points(key, sweep_dict[key]),
                out=out.sweep_function.sweeps.add(),
                sweep_transformer=sweep_transformer,
            )
    else:
        raise ValueError(f'cannot convert to v2 Sweep proto: {sweep}')
    return out
Converts a Sweep to v2 protobuf message.

    Args:
        sweep: The sweep to convert.
        out: Optional message to be populated. If not given, a new message will
            be created.
        sweep_transformer: A function called on Linspace, Points.

    Returns:
        Populated sweep protobuf message.

    Raises:
        ValueError: If the conversion cannot be completed successfully.
    
cirq-google/cirq_google/api/v2/sweeps.py