[CP-SAT] simplify python proto generation workflow

This commit is contained in:
Laurent Perron
2025-07-17 12:55:17 +02:00
parent 442b71c829
commit 0720713896
14 changed files with 204 additions and 477 deletions

View File

@@ -698,10 +698,6 @@ add_custom_command(
$<TARGET_FILE:routing_pybind11> ${PYTHON_PROJECT}/routing/python
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:cp_model_helper_pybind11> ${PYTHON_PROJECT}/sat/python
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:cp_model_builder_pybind> ${PYTHON_PROJECT}/sat/python
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:sat_parameters_builder_pybind> ${PYTHON_PROJECT}/sat/python
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:rcpsp_pybind11> ${PYTHON_PROJECT}/scheduling/python
COMMAND ${CMAKE_COMMAND} -E copy
@@ -727,9 +723,7 @@ add_custom_command(
$<$<BOOL:${BUILD_MATH_OPT}>:math_opt_elemental_pybind11>
$<$<BOOL:${BUILD_MATH_OPT}>:math_opt_io_pybind11>
$<TARGET_NAME_IF_EXISTS:pdlp_pybind11>
cp_model_builder_pybind
cp_model_helper_pybind11
sat_parameters_builder_pybind
rcpsp_pybind11
set_cover_pybind11
sorted_interval_list_pybind11

View File

@@ -23,9 +23,7 @@ py_binary(
"//ortools/graph/python:linear_sum_assignment.so",
"//ortools/graph/python:max_flow.so",
"//ortools/graph/python:min_cost_flow.so",
"//ortools/sat/python:cp_model_builder_pybind.so",
"//ortools/sat/python:cp_model_helper.so",
"//ortools/sat/python:sat_parameters_builder_pybind.so",
],
tags = ["manual"],
deps = [

View File

@@ -126,8 +126,6 @@ setup(
'@PYTHON_PROJECT@.sat.colab':['*.pyi', 'py.typed'],
'@PYTHON_PROJECT@.sat.python':[
'$<TARGET_FILE_NAME:cp_model_helper_pybind11>',
'$<TARGET_FILE_NAME:cp_model_builder_pybind>',
'$<TARGET_FILE_NAME:sat_parameters_builder_pybind>',
'*.pyi',
'py.typed'
],

View File

@@ -57,55 +57,12 @@ cc_library(
)
cc_binary(
name = "gen_cp_model_builder_pybind",
srcs = ["gen_cp_model_builder_pybind.cc"],
name = "gen_proto_builder_pybind11",
srcs = ["gen_proto_builder_pybind11.cc"],
deps = [
":wrappers",
"//ortools/base",
"//ortools/sat:cp_model_cc_proto",
"@abseil-cpp//absl/log:die_if_null",
"@abseil-cpp//absl/strings:str_format",
],
)
genrule(
name = "run_gen_cp_model_builder_pybind",
outs = ["cp_model_builder_pybind.cc"],
cmd = "$(location :gen_cp_model_builder_pybind) > $@",
tools = [":gen_cp_model_builder_pybind"],
)
pybind_extension(
name = "cp_model_builder",
srcs = [
"cp_model_builder_pybind.cc",
],
visibility = ["//visibility:public"],
deps = [
"//ortools/port:proto_utils",
"//ortools/sat:cp_model_cc_proto",
"@abseil-cpp//absl/base:nullability",
"@abseil-cpp//absl/strings",
"@protobuf",
],
)
py_test(
name = "cp_model_builder_test",
srcs = ["cp_model_builder_test.py"],
deps = [
":cp_model_builder",
requirement("absl-py"),
"//ortools/sat:cp_model_py_pb2",
],
)
cc_binary(
name = "gen_sat_parameters_builder_pybind",
srcs = ["gen_sat_parameters_builder_pybind.cc"],
deps = [
":wrappers",
"//ortools/base",
"//ortools/sat:sat_parameters_cc_proto",
"@abseil-cpp//absl/log:die_if_null",
"@abseil-cpp//absl/strings:str_format",
@@ -113,34 +70,15 @@ cc_binary(
)
genrule(
name = "run_gen_sat_parameters_builder_pybind",
outs = ["sat_parameters_builder_pybind.cc"],
cmd = "$(location :gen_sat_parameters_builder_pybind) > $@",
tools = [":gen_sat_parameters_builder_pybind"],
name = "run_gen_proto_builder_pybind11",
outs = ["proto_builder_pybind11.h"],
cmd = "$(location :gen_proto_builder_pybind11) > $@",
tools = [":gen_proto_builder_pybind11"],
)
pybind_extension(
name = "sat_parameters_builder",
srcs = [
"sat_parameters_builder_pybind.cc",
],
visibility = ["//visibility:public"],
deps = [
"//ortools/port:proto_utils",
"//ortools/sat:sat_parameters_cc_proto",
"@abseil-cpp//absl/base:nullability",
"@abseil-cpp//absl/strings",
"@protobuf",
],
)
py_test(
name = "sat_parameters_builder_test",
srcs = ["sat_parameters_builder_test.py"],
deps = [
":sat_parameters_builder",
requirement("absl-py"),
],
cc_library(
name = "proto_builder_pybind11",
hdrs = ["proto_builder_pybind11.h"],
)
pybind_extension(
@@ -150,6 +88,7 @@ pybind_extension(
deps = [
":linear_expr",
":linear_expr_doc",
":proto_builder_pybind11",
"//ortools/sat:cp_model_cc_proto",
"//ortools/sat:cp_model_utils",
"//ortools/sat:sat_parameters_cc_proto",
@@ -162,9 +101,7 @@ py_test(
name = "cp_model_helper_test",
srcs = ["cp_model_helper_test.py"],
deps = [
":cp_model_builder",
":cp_model_helper",
":sat_parameters_builder",
"//ortools/util/python:sorted_interval_list",
requirement("absl-py"),
],
@@ -175,9 +112,7 @@ py_library(
srcs = ["cp_model.py"],
visibility = ["//visibility:public"],
deps = [
":cp_model_builder",
":cp_model_helper",
":sat_parameters_builder",
requirement("numpy"),
requirement("pandas"),
"//ortools/util/python:sorted_interval_list",

View File

@@ -26,12 +26,12 @@ target_link_libraries(${WRAPPERS_NAME} PUBLIC
protobuf::libprotobuf)
add_library(${PROJECT_NAMESPACE}::${WRAPPERS_NAME} ALIAS ${WRAPPERS_NAME})
# gen_cp_model_builder_pybind code generator.
add_executable(gen_cp_model_builder_pybind)
target_sources(gen_cp_model_builder_pybind PRIVATE "gen_cp_model_builder_pybind.cc")
target_include_directories(gen_cp_model_builder_pybind PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_features(gen_cp_model_builder_pybind PRIVATE cxx_std_17)
target_link_libraries(gen_cp_model_builder_pybind PRIVATE
# gen_proto_builder_pybind11 code generator.
add_executable(gen_proto_builder_pybind11)
target_sources(gen_proto_builder_pybind11 PRIVATE "gen_proto_builder_pybind11.cc")
target_include_directories(gen_proto_builder_pybind11 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_features(gen_proto_builder_pybind11 PRIVATE cxx_std_17)
target_link_libraries(gen_proto_builder_pybind11 PRIVATE
absl::flags_commandlineflag
absl::flags_parse
absl::flags_usage
@@ -43,101 +43,25 @@ target_link_libraries(gen_cp_model_builder_pybind PRIVATE
include(GNUInstallDirs)
if(APPLE)
set_target_properties(gen_cp_model_builder_pybind PROPERTIES INSTALL_RPATH
set_target_properties(gen_proto_builder_pybind11 PROPERTIES INSTALL_RPATH
"@loader_path/../${CMAKE_INSTALL_LIBDIR};@loader_path")
elseif(UNIX)
cmake_path(RELATIVE_PATH CMAKE_INSTALL_FULL_LIBDIR
BASE_DIRECTORY ${CMAKE_INSTALL_FULL_BINDIR}
OUTPUT_VARIABLE libdir_relative_path)
set_target_properties(gen_cp_model_builder_pybind PROPERTIES
set_target_properties(gen_proto_builder_pybind11 PROPERTIES
INSTALL_RPATH "$ORIGIN/${libdir_relative_path}")
endif()
install(TARGETS gen_cp_model_builder_pybind)
install(TARGETS gen_proto_builder_pybind11)
add_custom_command(
OUTPUT cp_model_builder_pybind.cc
COMMAND gen_cp_model_builder_pybind > cp_model_builder_pybind.cc
COMMENT "Generate C++ cp_model_builder_pybind.cc"
OUTPUT proto_builder_pybind11.h
COMMAND gen_proto_builder_pybind11 > proto_builder_pybind11.h
COMMENT "Generate C++ proto_builder_pybind11.h"
VERBATIM)
# gen_sat_parameters_builder_pybind code generator.
add_executable(gen_sat_parameters_builder_pybind)
target_sources(gen_sat_parameters_builder_pybind PRIVATE "gen_sat_parameters_builder_pybind.cc")
target_include_directories(gen_sat_parameters_builder_pybind PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_features(gen_sat_parameters_builder_pybind PRIVATE cxx_std_17)
target_link_libraries(gen_sat_parameters_builder_pybind PRIVATE
absl::flags_commandlineflag
absl::flags_parse
absl::flags_usage
absl::die_if_null
absl::str_format
protobuf::libprotobuf
${PROJECT_NAMESPACE}::ortools_proto
${PROJECT_NAMESPACE}::${WRAPPERS_NAME})
include(GNUInstallDirs)
if(APPLE)
set_target_properties(gen_sat_parameters_builder_pybind PROPERTIES INSTALL_RPATH
"@loader_path/../${CMAKE_INSTALL_LIBDIR};@loader_path")
elseif(UNIX)
cmake_path(RELATIVE_PATH CMAKE_INSTALL_FULL_LIBDIR
BASE_DIRECTORY ${CMAKE_INSTALL_FULL_BINDIR}
OUTPUT_VARIABLE libdir_relative_path)
set_target_properties(gen_sat_parameters_builder_pybind PROPERTIES
INSTALL_RPATH "$ORIGIN/${libdir_relative_path}")
endif()
install(TARGETS gen_sat_parameters_builder_pybind)
add_custom_command(
OUTPUT sat_parameters_builder_pybind.cc
COMMAND gen_sat_parameters_builder_pybind > sat_parameters_builder_pybind.cc
COMMENT "Generate C++ sat_parameters_builder_pybind.cc"
VERBATIM)
# Generate both pybind extensins (cp_model_builder and sat_parameters_builder).
pybind11_add_module(cp_model_builder_pybind MODULE cp_model_builder_pybind.cc)
set_target_properties(cp_model_builder_pybind PROPERTIES
LIBRARY_OUTPUT_NAME "cp_model_builder")
# note: macOS is APPLE and also UNIX !
if(APPLE)
set_target_properties(cp_model_builder_pybind PROPERTIES
SUFFIX ".so"
INSTALL_RPATH "@loader_path;@loader_path/../../../${PYTHON_PROJECT}/.libs")
elseif(UNIX)
set_target_properties(cp_model_builder_pybind PROPERTIES
INSTALL_RPATH "$ORIGIN:$ORIGIN/../../../${PYTHON_PROJECT}/.libs")
endif()
target_link_libraries(cp_model_builder_pybind PRIVATE
${PROJECT_NAMESPACE}::ortools
protobuf::libprotobuf)
target_include_directories(cp_model_builder_pybind PRIVATE ${protobuf_SOURCE_DIR})
add_library(${PROJECT_NAMESPACE}::cp_model_builder_pybind ALIAS cp_model_builder_pybind)
pybind11_add_module(sat_parameters_builder_pybind MODULE sat_parameters_builder_pybind.cc)
set_target_properties(sat_parameters_builder_pybind PROPERTIES
LIBRARY_OUTPUT_NAME "sat_parameters_builder")
# note: macOS is APPLE and also UNIX !
if(APPLE)
set_target_properties(sat_parameters_builder_pybind PROPERTIES
SUFFIX ".so"
INSTALL_RPATH "@loader_path;@loader_path/../../../${PYTHON_PROJECT}/.libs")
elseif(UNIX)
set_target_properties(sat_parameters_builder_pybind PROPERTIES
INSTALL_RPATH "$ORIGIN:$ORIGIN/../../../${PYTHON_PROJECT}/.libs")
endif()
target_link_libraries(sat_parameters_builder_pybind PRIVATE
${PROJECT_NAMESPACE}::ortools
protobuf::libprotobuf)
target_include_directories(sat_parameters_builder_pybind PRIVATE ${protobuf_SOURCE_DIR})
add_library(${PROJECT_NAMESPACE}::sat_parameters_builder_pybind ALIAS sat_parameters_builder_pybind)
pybind11_add_module(cp_model_helper_pybind11 MODULE cp_model_helper.cc)
pybind11_add_module(cp_model_helper_pybind11 MODULE cp_model_helper.cc proto_builder_pybind11.h)
set_target_properties(cp_model_helper_pybind11 PROPERTIES
LIBRARY_OUTPUT_NAME "cp_model_helper")

View File

@@ -64,14 +64,7 @@ import warnings
import numpy as np
import pandas as pd
# Make sure the generated cp_model_helper is imported before the builder
# modules as the import duplicates versions of the protobufs.
from ortools.sat.python import (
cp_model_helper as cmh,
) # pylint: disable=g-bad-import-order
from ortools.sat.python import cp_model_builder as cmb
from ortools.sat.python import sat_parameters_builder as spb
from ortools.sat.python import cp_model_helper as cmh
from ortools.util.python import sorted_interval_list
# Import external types.
@@ -82,6 +75,10 @@ FlatIntExpr = cmh.FlatIntExpr
LinearExpr = cmh.LinearExpr
IntVar = cmh.IntVar
NotBooleanVariable = cmh.NotBooleanVariable
CpModelProto = cmh.CpModelProto
CpSolverStatus = cmh.CpSolverStatus
CpSolverResponse = cmh.CpSolverResponse
SatParameters = cmh.SatParameters
# The classes below allow linear expressions to be expressed naturally with the
@@ -94,52 +91,52 @@ INT32_MIN = -(2**31)
INT32_MAX = 2**31 - 1
# CpSolver status (exported to avoid importing cp_model_cp2).
UNKNOWN = cmb.CpSolverStatus.UNKNOWN
UNKNOWN = cmb.CpSolverStatus.UNKNOWN
MODEL_INVALID = cmb.CpSolverStatus.MODEL_INVALID
FEASIBLE = cmb.CpSolverStatus.FEASIBLE
INFEASIBLE = cmb.CpSolverStatus.INFEASIBLE
OPTIMAL = cmb.CpSolverStatus.OPTIMAL
UNKNOWN = cmh.CpSolverStatus.UNKNOWN
UNKNOWN = cmh.CpSolverStatus.UNKNOWN
MODEL_INVALID = cmh.CpSolverStatus.MODEL_INVALID
FEASIBLE = cmh.CpSolverStatus.FEASIBLE
INFEASIBLE = cmh.CpSolverStatus.INFEASIBLE
OPTIMAL = cmh.CpSolverStatus.OPTIMAL
# Variable selection strategy
CHOOSE_FIRST = cmb.DecisionStrategyProto.VariableSelectionStrategy.CHOOSE_FIRST
CHOOSE_FIRST = cmh.DecisionStrategyProto.VariableSelectionStrategy.CHOOSE_FIRST
CHOOSE_LOWEST_MIN = (
cmb.DecisionStrategyProto.VariableSelectionStrategy.CHOOSE_LOWEST_MIN
cmh.DecisionStrategyProto.VariableSelectionStrategy.CHOOSE_LOWEST_MIN
)
CHOOSE_HIGHEST_MAX = (
cmb.DecisionStrategyProto.VariableSelectionStrategy.CHOOSE_HIGHEST_MAX
cmh.DecisionStrategyProto.VariableSelectionStrategy.CHOOSE_HIGHEST_MAX
)
CHOOSE_MIN_DOMAIN_SIZE = (
cmb.DecisionStrategyProto.VariableSelectionStrategy.CHOOSE_MIN_DOMAIN_SIZE
cmh.DecisionStrategyProto.VariableSelectionStrategy.CHOOSE_MIN_DOMAIN_SIZE
)
CHOOSE_MAX_DOMAIN_SIZE = (
cmb.DecisionStrategyProto.VariableSelectionStrategy.CHOOSE_MAX_DOMAIN_SIZE
cmh.DecisionStrategyProto.VariableSelectionStrategy.CHOOSE_MAX_DOMAIN_SIZE
)
# Domain reduction strategy
SELECT_MIN_VALUE = cmb.DecisionStrategyProto.DomainReductionStrategy.SELECT_MIN_VALUE
SELECT_MAX_VALUE = cmb.DecisionStrategyProto.DomainReductionStrategy.SELECT_MAX_VALUE
SELECT_LOWER_HALF = cmb.DecisionStrategyProto.DomainReductionStrategy.SELECT_LOWER_HALF
SELECT_UPPER_HALF = cmb.DecisionStrategyProto.DomainReductionStrategy.SELECT_UPPER_HALF
SELECT_MIN_VALUE = cmh.DecisionStrategyProto.DomainReductionStrategy.SELECT_MIN_VALUE
SELECT_MAX_VALUE = cmh.DecisionStrategyProto.DomainReductionStrategy.SELECT_MAX_VALUE
SELECT_LOWER_HALF = cmh.DecisionStrategyProto.DomainReductionStrategy.SELECT_LOWER_HALF
SELECT_UPPER_HALF = cmh.DecisionStrategyProto.DomainReductionStrategy.SELECT_UPPER_HALF
SELECT_MEDIAN_VALUE = (
cmb.DecisionStrategyProto.DomainReductionStrategy.SELECT_MEDIAN_VALUE
cmh.DecisionStrategyProto.DomainReductionStrategy.SELECT_MEDIAN_VALUE
)
SELECT_RANDOM_HALF = (
cmb.DecisionStrategyProto.DomainReductionStrategy.SELECT_RANDOM_HALF
cmh.DecisionStrategyProto.DomainReductionStrategy.SELECT_RANDOM_HALF
)
# Search branching
AUTOMATIC_SEARCH = spb.SatParameters.SearchBranching.AUTOMATIC_SEARCH
FIXED_SEARCH = spb.SatParameters.SearchBranching.FIXED_SEARCH
PORTFOLIO_SEARCH = spb.SatParameters.SearchBranching.PORTFOLIO_SEARCH
LP_SEARCH = spb.SatParameters.SearchBranching.LP_SEARCH
PSEUDO_COST_SEARCH = spb.SatParameters.SearchBranching.PSEUDO_COST_SEARCH
AUTOMATIC_SEARCH = cmh.SatParameters.SearchBranching.AUTOMATIC_SEARCH
FIXED_SEARCH = cmh.SatParameters.SearchBranching.FIXED_SEARCH
PORTFOLIO_SEARCH = cmh.SatParameters.SearchBranching.PORTFOLIO_SEARCH
LP_SEARCH = cmh.SatParameters.SearchBranching.LP_SEARCH
PSEUDO_COST_SEARCH = cmh.SatParameters.SearchBranching.PSEUDO_COST_SEARCH
PORTFOLIO_WITH_QUICK_RESTART_SEARCH = (
spb.SatParameters.SearchBranching.PORTFOLIO_WITH_QUICK_RESTART_SEARCH
cmh.SatParameters.SearchBranching.PORTFOLIO_WITH_QUICK_RESTART_SEARCH
)
HINT_SEARCH = spb.SatParameters.SearchBranching.HINT_SEARCH
PARTIAL_FIXED_SEARCH = spb.SatParameters.SearchBranching.PARTIAL_FIXED_SEARCH
RANDOMIZED_SEARCH = spb.SatParameters.SearchBranching.RANDOMIZED_SEARCH
HINT_SEARCH = cmh.SatParameters.SearchBranching.HINT_SEARCH
PARTIAL_FIXED_SEARCH = cmh.SatParameters.SearchBranching.PARTIAL_FIXED_SEARCH
RANDOMIZED_SEARCH = cmh.SatParameters.SearchBranching.RANDOMIZED_SEARCH
# Type aliases
IntegralT = Union[int, np.int8, np.uint8, np.int32, np.uint32, np.int64, np.uint64]
@@ -187,7 +184,7 @@ ArcT = Tuple[IntegralT, IntegralT, LiteralT]
_IndexOrSeries = Union[pd.Index, pd.Series]
def short_name(model: cmb.CpModelProto, i: int) -> str:
def short_name(model: cmh.CpModelProto, i: int) -> str:
"""Returns a short name of an integer variable, or its negation."""
if i >= 0:
return str(IntVar(model, i))
@@ -196,8 +193,8 @@ def short_name(model: cmb.CpModelProto, i: int) -> str:
def short_expr_name(
model: cmb.CpModelProto,
e: cmb.LinearExpressionProto,
model: cmh.CpModelProto,
e: cmh.LinearExpressionProto,
) -> str:
"""Pretty-print LinearExpressionProto instances."""
if not e.vars:
@@ -231,8 +228,8 @@ def arg_is_boolean(x: Any) -> bool:
def rebuild_from_linear_expression_proto(
proto: cmb.LinearExpressionProto,
model_proto: cmb.CpModelProto,
proto: cmh.LinearExpressionProto,
model_proto: cmh.CpModelProto,
) -> LinearExprT:
"""Recreate a LinearExpr from a LinearExpressionProto."""
num_elements = len(proto.vars)
@@ -344,7 +341,7 @@ class Constraint:
return self.__index
@property
def proto(self) -> cmb.ConstraintProto:
def proto(self) -> cmh.ConstraintProto:
"""Returns the constraint protobuf."""
return self.__cp_model.proto.constraints[self.__index]
@@ -365,7 +362,7 @@ class Constraint:
def Index(self) -> int:
return self.index
def Proto(self) -> cmb.ConstraintProto:
def Proto(self) -> cmh.ConstraintProto:
return self.proto
# pylint: enable=invalid-name
@@ -394,16 +391,16 @@ class IntervalVar:
def __init__(
self,
model: cmb.CpModelProto,
start: Union[cmb.LinearExpressionProto, int],
size: Optional[cmb.LinearExpressionProto],
end: Optional[cmb.LinearExpressionProto],
model: cmh.CpModelProto,
start: Union[cmh.LinearExpressionProto, int],
size: Optional[cmh.LinearExpressionProto],
end: Optional[cmh.LinearExpressionProto],
is_present_index: Optional[int],
name: Optional[str],
) -> None:
self.__model: cmb.CpModelProto = model
self.__model: cmh.CpModelProto = model
self.__index: int
self.__ct: cmb.ConstraintProto
self.__ct: cmh.ConstraintProto
# As with the IntVar::__init__ method, we hack the __init__ method to
# support two use cases:
# case 1: called when creating a new interval variable.
@@ -443,12 +440,12 @@ class IntervalVar:
return self.__index
@property
def proto(self) -> cmb.ConstraintProto:
def proto(self) -> cmh.ConstraintProto:
"""Returns the interval protobuf."""
return self.__model.constraints[self.__index]
@property
def model_proto(self) -> cmb.CpModelProto:
def model_proto(self) -> cmh.CpModelProto:
"""Returns the model protobuf."""
return self.__model
@@ -502,7 +499,7 @@ class IntervalVar:
def Index(self) -> int:
return self.index
def Proto(self) -> cmb.ConstraintProto:
def Proto(self) -> cmh.ConstraintProto:
return self.proto
StartExpr = start_expr
@@ -548,7 +545,7 @@ class CpModel:
"""
def __init__(self) -> None:
self.__model: cmb.CpModelProto = cmb.CpModelProto()
self.__model: cmh.CpModelProto = cmh.CpModelProto()
self.__constant_map: Dict[IntegralT, int] = {}
# Naming.
@@ -1966,7 +1963,7 @@ class CpModel:
return str(self.__model)
@property
def proto(self) -> cmb.CpModelProto:
def proto(self) -> cmh.CpModelProto:
"""Returns the underlying CpModelProto."""
return self.__model
@@ -2022,9 +2019,9 @@ class CpModel:
def parse_linear_expression(
self, linear_expr: LinearExprT, negate: bool = False
) -> cmb.LinearExpressionProto:
) -> cmh.LinearExpressionProto:
"""Returns a LinearExpressionProto built from a LinearExpr instance."""
result: cmb.LinearExpressionProto = cmb.LinearExpressionProto()
result: cmh.LinearExpressionProto = cmh.LinearExpressionProto()
mult = -1 if negate else 1
if isinstance(linear_expr, IntegralTypes):
result.offset = int(linear_expr) * mult
@@ -2091,8 +2088,8 @@ class CpModel:
def add_decision_strategy(
self,
variables: Sequence[IntVar],
var_strategy: cmb.DecisionStrategyProto.VariableSelectionStrategy,
domain_strategy: cmb.DecisionStrategyProto.DomainReductionStrategy,
var_strategy: cmh.DecisionStrategyProto.VariableSelectionStrategy,
domain_strategy: cmh.DecisionStrategyProto.DomainReductionStrategy,
) -> None:
"""Adds a search strategy to the model.
@@ -2105,7 +2102,7 @@ class CpModel:
solve() will fail.
"""
strategy: cmb.DecisionStrategyProto = self.__model.search_strategy.add()
strategy: cmh.DecisionStrategyProto = self.__model.search_strategy.add()
for v in variables:
expr = strategy.exprs.add()
if v.index >= 0:
@@ -2223,7 +2220,7 @@ class CpModel:
def SetName(self, name: str) -> None:
self.name = name
def Proto(self) -> cmb.CpModelProto:
def Proto(self) -> cmh.CpModelProto:
return self.proto
NewIntVar = new_int_var
@@ -2314,7 +2311,7 @@ class CpSolver:
def __init__(self) -> None:
self.__response_wrapper: Optional[cmh.ResponseWrapper] = None
self.parameters: spb.SatParameters = spb.SatParameters()
self.parameters: cmh.SatParameters = cmh.SatParameters()
self.log_callback: Optional[Callable[[str], None]] = None
self.best_bound_callback: Optional[Callable[[float], None]] = None
self.__solve_wrapper: Optional[cmh.SolveWrapper] = None
@@ -2324,7 +2321,7 @@ class CpSolver:
self,
model: CpModel,
solution_callback: Optional["CpSolverSolutionCallback"] = None,
) -> cmb.CpSolverStatus:
) -> cmh.CpSolverStatus:
"""Solves a problem and passes each solution to the callback if not null."""
with self.__lock:
self.__solve_wrapper = cmh.SolveWrapper()
@@ -2497,7 +2494,7 @@ class CpSolver:
return self._checked_response.user_time()
@property
def response_proto(self) -> cmb.CpSolverResponse:
def response_proto(self) -> cmh.CpSolverResponse:
"""Returns the response object."""
return self._checked_response.response()
@@ -2557,7 +2554,7 @@ class CpSolver:
def ObjectiveValue(self) -> float:
return self.objective_value
def ResponseProto(self) -> cmb.CpSolverResponse:
def ResponseProto(self) -> cmh.CpSolverResponse:
return self.response_proto
def ResponseStats(self) -> str:
@@ -2567,7 +2564,7 @@ class CpSolver:
self,
model: CpModel,
solution_callback: Optional["CpSolverSolutionCallback"] = None,
) -> cmb.CpSolverStatus:
) -> cmh.CpSolverStatus:
return self.solve(model, solution_callback)
def SolutionInfo(self) -> str:
@@ -2596,7 +2593,7 @@ class CpSolver:
def SolveWithSolutionCallback(
self, model: CpModel, callback: "CpSolverSolutionCallback"
) -> cmb.CpSolverStatus:
) -> cmh.CpSolverStatus:
"""DEPRECATED Use solve() with the callback argument."""
warnings.warn(
"solve_with_solution_callback is deprecated; use solve() with"
@@ -2607,7 +2604,7 @@ class CpSolver:
def SearchForAllSolutions(
self, model: CpModel, callback: "CpSolverSolutionCallback"
) -> cmb.CpSolverStatus:
) -> cmh.CpSolverStatus:
"""DEPRECATED Use solve() with the right parameter.
Search for all solutions of a satisfiability problem.
@@ -2641,7 +2638,7 @@ class CpSolver:
enumerate_all = self.parameters.enumerate_all_solutions
self.parameters.enumerate_all_solutions = True
status: cmb.CpSolverStatus = self.solve(model, callback)
status: cmh.CpSolverStatus = self.solve(model, callback)
# Restore parameter.
self.parameters.enumerate_all_solutions = enumerate_all
@@ -2802,7 +2799,7 @@ class CpSolverSolutionCallback(cmh.SolutionCallback):
return self.UserTime()
@property
def response_proto(self) -> cmb.CpSolverResponse:
def response_proto(self) -> cmh.CpSolverResponse:
"""Returns the response object."""
if not self.has_response():
raise RuntimeError("solve() has not been called.")

View File

@@ -1,79 +0,0 @@
#!/usr/bin/env python3
# Copyright 2010-2025 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from absl.testing import absltest
from ortools.sat import cp_model_pb2
from ortools.sat.python import cp_model_builder
class CpModelBuilderTest(absltest.TestCase):
def test_basic(self):
model_proto = cp_model_builder.CpModelProto()
# Singular message.
objective = model_proto.objective
# Singular int.
self.assertEqual(objective.offset, 0)
objective.offset = 123
self.assertEqual(objective.offset, 123)
# Set a message.
new_obj = cp_model_builder.CpObjectiveProto()
new_obj.offset = 456
model_proto.objective = new_obj
self.assertEqual(objective.offset, 456)
# Large int.
objective.offset = 500000000000
self.assertEqual(objective.offset, 500000000000)
# Repeated message.
my_var = model_proto.variables.add()
# Singular string.
self.assertEqual(my_var.name, "")
my_var.name = "my_var"
self.assertEqual(my_var.name, "my_var")
my_var.domain.extend([0, 1])
domain = list(my_var.domain)
self.assertLen(domain, 2)
self.assertEqual(domain[0], 0)
self.assertEqual(domain[1], 1)
# Repeated int.
objective.vars.append(0)
self.assertLen(objective.vars, 1)
self.assertEqual(objective.vars[0], 0)
objective.vars[0] = 42
self.assertEqual(objective.vars[0], 42)
# Singular enum
search_strategy = model_proto.search_strategy.add()
self.assertEqual(
search_strategy.variable_selection_strategy,
cp_model_builder.DecisionStrategyProto.CHOOSE_FIRST,
)
search_strategy.variable_selection_strategy = (
cp_model_builder.DecisionStrategyProto.CHOOSE_LOWEST_MIN
)
self.assertEqual(
search_strategy.variable_selection_strategy,
cp_model_pb2.DecisionStrategyProto.CHOOSE_LOWEST_MIN,
)
if __name__ == "__main__":
absltest.main()

View File

@@ -25,6 +25,7 @@
#include "absl/functional/any_invocable.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "ortools/port/proto_utils.h" // IWYU: keep
#include "ortools/sat/cp_model.pb.h"
#include "ortools/sat/cp_model_utils.h"
#include "ortools/sat/python/linear_expr.h"
@@ -500,8 +501,6 @@ void ClearCtName(int index, std::shared_ptr<CpModelProto> model_proto) {
}
PYBIND11_MODULE(cp_model_helper, m) {
py::module::import("ortools.sat.python.cp_model_builder");
py::module::import("ortools.sat.python.sat_parameters_builder");
py::module::import("ortools.util.python.sorted_interval_list");
// We keep the CamelCase name for the SolutionCallback class to be
@@ -1303,6 +1302,9 @@ PYBIND11_MODULE(cp_model_helper, m) {
"not supported."));
return false;
});
#define IMPORT_PROTO_WRAPPER_CODE
#include "ortools/sat/python/proto_builder_pybind11.h"
#undef IMPORT_PROTO_WRAPPER_CODE
} // NOLINT(readability/fn_size)
} // namespace operations_research::sat::python

View File

@@ -18,9 +18,7 @@ import sys
from absl.testing import absltest
from ortools.sat.python import cp_model_builder
from ortools.sat.python import cp_model_helper as cmh
from ortools.sat.python import sat_parameters_builder
from ortools.util.python import sorted_interval_list
@@ -58,7 +56,7 @@ class CpModelHelperTest(absltest.TestCase):
variables { domain: [ -10, 10 ] }
variables { domain: [ -5, -5, 3, 6 ] }
"""
model = cp_model_builder.CpModelProto()
model = cmh.CpModelProto()
self.assertTrue(model.parse_text_format(model_string))
d0 = cmh.CpSatHelper.variable_domain(model.variables[0])
@@ -99,13 +97,13 @@ class CpModelHelperTest(absltest.TestCase):
coeffs: -1
scaling_factor: -1
}"""
model = cp_model_builder.CpModelProto()
model = cmh.CpModelProto()
self.assertTrue(model.parse_text_format(model_string))
solve_wrapper = cmh.SolveWrapper()
response_wrapper = solve_wrapper.solve_and_return_response_wrapper(model)
self.assertEqual(cp_model_builder.OPTIMAL, response_wrapper.status())
self.assertEqual(cmh.OPTIMAL, response_wrapper.status())
self.assertEqual(30.0, response_wrapper.objective_value())
def test_simple_solve_with_core(self):
@@ -140,21 +138,21 @@ class CpModelHelperTest(absltest.TestCase):
coeffs: -1
scaling_factor: -1
}"""
model = cp_model_builder.CpModelProto()
model = cmh.CpModelProto()
self.assertTrue(model.parse_text_format(model_string))
parameters = sat_parameters_builder.SatParameters()
parameters = cmh.SatParameters()
parameters.optimize_with_core = True
solve_wrapper = cmh.SolveWrapper()
solve_wrapper.set_parameters(parameters)
response_wrapper = solve_wrapper.solve_and_return_response_wrapper(model)
self.assertEqual(cp_model_builder.OPTIMAL, response_wrapper.status())
self.assertEqual(cmh.OPTIMAL, response_wrapper.status())
self.assertEqual(30.0, response_wrapper.objective_value())
def test_simple_solve_with_proto_api(self):
model = cp_model_builder.CpModelProto()
model = cmh.CpModelProto()
x = model.variables.add()
x.domain.extend([-10, 10])
y = model.variables.add()
@@ -172,7 +170,7 @@ class CpModelHelperTest(absltest.TestCase):
solve_wrapper = cmh.SolveWrapper()
response_wrapper = solve_wrapper.solve_and_return_response_wrapper(model)
self.assertEqual(cp_model_builder.OPTIMAL, response_wrapper.status())
self.assertEqual(cmh.OPTIMAL, response_wrapper.status())
self.assertEqual(30.0, response_wrapper.objective_value())
self.assertEqual(30.0, response_wrapper.best_objective_bound())
self.assertRaises(TypeError, response_wrapper.value, None)
@@ -186,19 +184,19 @@ class CpModelHelperTest(absltest.TestCase):
constraints {
linear { vars: 0 vars: 1 coeffs: 1 coeffs: 1 domain: 6 domain: 6 } }
"""
model = cp_model_builder.CpModelProto()
model = cmh.CpModelProto()
self.assertTrue(model.parse_text_format(model_string))
solve_wrapper = cmh.SolveWrapper()
callback = Callback()
solve_wrapper.add_solution_callback(callback)
params = sat_parameters_builder.SatParameters()
params = cmh.SatParameters()
params.enumerate_all_solutions = True
solve_wrapper.set_parameters(params)
response_wrapper = solve_wrapper.solve_and_return_response_wrapper(model)
self.assertEqual(5, callback.solution_count())
self.assertEqual(cp_model_builder.OPTIMAL, response_wrapper.status())
self.assertEqual(cmh.OPTIMAL, response_wrapper.status())
def test_best_bound_callback(self):
model_string = """
@@ -213,13 +211,13 @@ class CpModelHelperTest(absltest.TestCase):
offset: 0.6
}
"""
model = cp_model_builder.CpModelProto()
model = cmh.CpModelProto()
self.assertTrue(model.parse_text_format(model_string))
solve_wrapper = cmh.SolveWrapper()
best_bound_callback = BestBoundCallback()
solve_wrapper.add_best_bound_callback(best_bound_callback.new_best_bound)
params = sat_parameters_builder.SatParameters()
params = cmh.SatParameters()
params.num_workers = 1
params.linearization_level = 2
params.log_search_progress = True
@@ -227,7 +225,7 @@ class CpModelHelperTest(absltest.TestCase):
response_wrapper = solve_wrapper.solve_and_return_response_wrapper(model)
self.assertEqual(2.6, best_bound_callback.best_bound)
self.assertEqual(cp_model_builder.OPTIMAL, response_wrapper.status())
self.assertEqual(cmh.OPTIMAL, response_wrapper.status())
def test_model_stats(self):
model_string = """
@@ -263,13 +261,13 @@ class CpModelHelperTest(absltest.TestCase):
}
name: 'testModelStats'
"""
model = cp_model_builder.CpModelProto()
model = cmh.CpModelProto()
self.assertTrue(model.parse_text_format(model_string))
stats = cmh.CpSatHelper.model_stats(model)
self.assertTrue(stats)
def test_int_lin_expr(self):
model = cp_model_builder.CpModelProto()
model = cmh.CpModelProto()
x = cmh.IntVar(model).with_name("x")
self.assertTrue(x.is_integer())
self.assertIsInstance(x, cmh.IntVar)
@@ -316,7 +314,7 @@ class CpModelHelperTest(absltest.TestCase):
self.assertEqual(str(e12), "(x + (-y) + (-2 * z))")
def test_float_lin_expr(self):
model = cp_model_builder.CpModelProto()
model = cmh.CpModelProto()
x = cmh.IntVar(model).with_name("x")
self.assertTrue(x.is_integer())
self.assertIsInstance(x, cmh.IntVar)
@@ -362,5 +360,89 @@ class CpModelHelperTest(absltest.TestCase):
self.assertEqual(str(e12), "(3.1 * (x + 2))")
class CpModelBuilderTest(absltest.TestCase):
def test_basic(self):
model_proto = cmh.CpModelProto()
# Singular message.
objective = model_proto.objective
# Singular int.
self.assertEqual(objective.offset, 0)
objective.offset = 123
self.assertEqual(objective.offset, 123)
# Set a message.
new_obj = cmh.CpObjectiveProto()
new_obj.offset = 456
model_proto.objective = new_obj
self.assertEqual(objective.offset, 456)
# Large int.
objective.offset = 500000000000
self.assertEqual(objective.offset, 500000000000)
# Repeated message.
my_var = model_proto.variables.add()
# Singular string.
self.assertEqual(my_var.name, "")
my_var.name = "my_var"
self.assertEqual(my_var.name, "my_var")
my_var.domain.extend([0, 1])
domain = list(my_var.domain)
self.assertLen(domain, 2)
self.assertEqual(domain[0], 0)
self.assertEqual(domain[1], 1)
# Repeated int.
objective.vars.append(0)
self.assertLen(objective.vars, 1)
self.assertEqual(objective.vars[0], 0)
objective.vars[0] = 42
self.assertEqual(objective.vars[0], 42)
# Singular enum
search_strategy = model_proto.search_strategy.add()
self.assertEqual(
search_strategy.variable_selection_strategy,
cmh.DecisionStrategyProto.CHOOSE_FIRST,
)
search_strategy.variable_selection_strategy = (
cmh.DecisionStrategyProto.CHOOSE_LOWEST_MIN
)
self.assertEqual(
search_strategy.variable_selection_strategy,
cmh.DecisionStrategyProto.CHOOSE_LOWEST_MIN,
)
class SatParametersBuilderTest(absltest.TestCase):
def test_basic_api(self) -> None:
params = cmh.SatParameters()
# Test that we can set and get an integer parameter.
params.num_workers = 10
self.assertEqual(params.num_workers, 10)
# Test that we can set and get an enum parameter.
self.assertEqual(
params.clause_cleanup_ordering,
cmh.SatParameters.ClauseOrdering.CLAUSE_ACTIVITY,
)
params.clause_cleanup_ordering = cmh.SatParameters.ClauseOrdering.CLAUSE_LBD
self.assertEqual(
params.clause_cleanup_ordering,
cmh.SatParameters.ClauseOrdering.CLAUSE_LBD,
)
# Test that we can set and get a repeated string parameter.
params.subsolvers.append("no_lp")
self.assertLen(params.subsolvers, 1)
self.assertEqual(params.subsolvers[0], "no_lp")
if __name__ == "__main__":
absltest.main()

View File

@@ -22,7 +22,6 @@ import numpy as np
import pandas as pd
from ortools.sat.python import cp_model
from ortools.sat.python import cp_model_builder
from ortools.sat.python import cp_model_helper as cmh
@@ -585,7 +584,7 @@ class CpModelTest(absltest.TestCase):
model.add(3 <= -1)
model.minimize(x)
solver = cp_model.CpSolver()
status: cp_model_builder.CpSolverStatus = solver.solve(model)
status: cmh.CpSolverStatus = solver.solve(model)
self.assertEqual("INFEASIBLE", status.name)
def test_sum(self) -> None:
@@ -1308,7 +1307,7 @@ class CpModelTest(absltest.TestCase):
self.assertEqual(~i.size_expr(), ~y)
self.assertRaises(TypeError, i.start_expr().negated)
proto = cp_model_builder.LinearExpressionProto()
proto = cmh.LinearExpressionProto()
proto.vars.append(x.index)
proto.coeffs.append(1)
proto.vars.append(y.index)

View File

@@ -18,6 +18,7 @@
#include "absl/strings/str_format.h"
#include "ortools/sat/cp_model.pb.h"
#include "ortools/sat/python/wrappers.h"
#include "ortools/sat/sat_parameters.pb.h"
namespace operations_research::sat::python {
@@ -26,28 +27,13 @@ void ParseAndGenerate() {
R"(
// This is a generated file, do not edit.
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "google/protobuf/text_format.h"
#include "pybind11/numpy.h"
#include "pybind11/pybind11.h"
#include "pybind11/pytypes.h"
#include "pybind11/stl.h"
#include "ortools/port/proto_utils.h"
#include "ortools/sat/cp_model.pb.h"
namespace py = ::pybind11;
namespace operations_research::sat::python {
PYBIND11_MODULE(cp_model_builder, py_module) {
#if defined(IMPORT_PROTO_WRAPPER_CODE)
%s
} // PYBIND11_MODULE
} // namespace operations_research::sat::python
#endif // defined(IMPORT_PROTO_WRAPPER_CODE)
)",
GeneratePybindCode({ABSL_DIE_IF_NULL(CpModelProto::descriptor()),
ABSL_DIE_IF_NULL(CpSolverResponse::descriptor())}));
ABSL_DIE_IF_NULL(CpSolverResponse::descriptor()),
ABSL_DIE_IF_NULL(SatParameters::descriptor())}));
}
} // namespace operations_research::sat::python

View File

@@ -1,59 +0,0 @@
// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "absl/flags/parse.h"
#include "absl/flags/usage.h"
#include "absl/log/die_if_null.h"
#include "absl/log/initialize.h"
#include "absl/strings/str_format.h"
#include "ortools/sat/python/wrappers.h"
#include "ortools/sat/sat_parameters.pb.h"
namespace operations_research::sat::python {
void ParseAndGenerate() {
absl::PrintF(
R"(
// This is a generated file, do not edit.
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "google/protobuf/text_format.h"
#include "pybind11/numpy.h"
#include "pybind11/pybind11.h"
#include "pybind11/pytypes.h"
#include "pybind11/stl.h"
#include "ortools/port/proto_utils.h"
#include "ortools/sat/sat_parameters.pb.h"
namespace py = ::pybind11;
namespace operations_research::sat::python {
PYBIND11_MODULE(sat_parameters_builder, py_module) {
%s
} // PYBIND11_MODULE
} // namespace operations_research::sat::python
)",
GeneratePybindCode({ABSL_DIE_IF_NULL(SatParameters::descriptor())}));
}
} // namespace operations_research::sat::python
int main(int argc, char* argv[]) {
// We do not use InitGoogle() to avoid linking with or-tools as this would
// create a circular dependency.
absl::InitializeLog();
absl::SetProgramUsageMessage(argv[0]);
absl::ParseCommandLine(argc, argv);
operations_research::sat::python::ParseAndGenerate();
return 0;
}

View File

@@ -1,50 +0,0 @@
#!/usr/bin/env python3
# Copyright 2010-2025 Google LLC
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Test sat parameters builder."""
from absl.testing import absltest
from ortools.sat.python import sat_parameters_builder
class SatParametersBuilderTest(absltest.TestCase):
def test_basic_api(self) -> None:
params = sat_parameters_builder.SatParameters()
# Test that we can set and get an integer parameter.
params.num_workers = 10
self.assertEqual(params.num_workers, 10)
# Test that we can set and get an enum parameter.
self.assertEqual(
params.clause_cleanup_ordering,
sat_parameters_builder.SatParameters.ClauseOrdering.CLAUSE_ACTIVITY,
)
params.clause_cleanup_ordering = (
sat_parameters_builder.SatParameters.ClauseOrdering.CLAUSE_LBD
)
self.assertEqual(
params.clause_cleanup_ordering,
sat_parameters_builder.SatParameters.ClauseOrdering.CLAUSE_LBD,
)
# Test that we can set and get a repeated string parameter.
params.subsolvers.append("no_lp")
self.assertLen(params.subsolvers, 1)
self.assertEqual(params.subsolvers[0], "no_lp")
if __name__ == "__main__":
absltest.main()

View File

@@ -232,7 +232,7 @@ class Generator {
// ptr.
void GenerateRepeatedPtrDecl(const google::protobuf::Descriptor& msg) {
absl::SubstituteAndAppend(&out_, R"(
py::class_<google::protobuf::RepeatedPtrField<$0>>(py_module, "repeated_$1")
py::class_<google::protobuf::RepeatedPtrField<$0>>(m, "repeated_$1")
.def("add",
[](google::protobuf::RepeatedPtrField<$0>* self) {
return self->Add();
@@ -265,7 +265,7 @@ class Generator {
void GenerateRepeatedScalarDecl(absl::string_view scalar_type) {
if (scalar_type == "std::string") {
absl::StrAppend(&out_, R"(
py::class_<google::protobuf::RepeatedPtrField<std::string>>(py_module, "repeated_scalar_std_string")
py::class_<google::protobuf::RepeatedPtrField<std::string>>(m, "repeated_scalar_std_string")
.def("append",
[](google::protobuf::RepeatedPtrField<std::string>* self, std::string str) {
self->Add(std::move(str));
@@ -299,7 +299,7 @@ class Generator {
} else {
absl::SubstituteAndAppend(
&out_, R"(
py::class_<google::protobuf::RepeatedField<$0>>(py_module, "repeated_scalar_$1")
py::class_<google::protobuf::RepeatedField<$0>>(m, "repeated_scalar_$1")
.def("append", [](google::protobuf::RepeatedField<$0>* self, $0 value) {
self->Add(value);
})
@@ -404,13 +404,13 @@ class Generator {
}
}
// Returns the wrapper name for a message (or "py_module" if `msg` is null).
// Returns the wrapper name for a message (or "m" if `msg` is null).
// Dies if the scope is not found.
std::string GetWrapperName(const google::protobuf::Descriptor* msg) {
const auto it = wrapper_id_.find(msg);
CHECK(it != wrapper_id_.end())
<< "wrapper id not found: " << msg->full_name();
if (msg == nullptr) return "py_module";
if (msg == nullptr) return "m";
return absl::StrCat("gen_", it->second);
}