Python SDK v1 から v2 へのマイグレーションガイド#
この節では、Protocol Buffer ベースの v1 から Rust + PyO3 ベースの v2 への移行について説明します。
⚠️ 重要: raw 属性の非推奨化#
v2 では、移行済みのすべてのクラス (Instance、Solution、SampleSet など) で raw 属性が非推奨になりました。.raw への直接アクセスは避けてください。代わりに、各クラス自身が提供する method や property を使います。
例:
# ❌ 非推奨: raw 経由でアクセスしない
solution.raw.evaluated_constraints[0].evaluated_value
instance.raw.decision_variables
sample_set.raw.samples
# ✅ 推奨: 直接 property / method を使う
solution.get_constraint_value(0)
instance.decision_variables # list を返す property になりました
sample_set.get(sample_id)
これらの直接 method には次の利点があります。
native Rust 実装による性能向上
型安全性の改善
より簡潔で直感的な API
後方互換性のため raw 属性はまだ利用できますが、将来のバージョンで削除される予定です。
import の変更#
旧方式 (v1):
from ommx.v1.constraint_pb2 import Constraint, Equality
from ommx.v1.function_pb2 import Function
from ommx.v1.linear_pb2 import Linear
from ommx.v1 import Instance, DecisionVariable
新方式 (v2) - 推奨:
# すべて ommx.v1 から統一的に import する
from ommx.v1 import (
Instance, DecisionVariable, Constraint,
Function, Linear, Quadratic, Polynomial,
Solution, State, SampleSet,
# 新しい evaluated 型 (v2.0.0rc3+)
EvaluatedDecisionVariable, EvaluatedConstraint,
SampledDecisionVariable, SampledConstraint
)
DecisionVariable の factory method#
引き続き利用できる method:
# of_type method (引き続き利用可能)
DecisionVariable.of_type(
DecisionVariable.BINARY, var.idx,
lower=var.lb, upper=var.ub, name=var.name
)
新しく追加された method:
# より簡潔な type-specific factory method
DecisionVariable.binary(var.idx, name=var.name)
DecisionVariable.integer(var.idx, lower=var.lb, upper=var.ub, name=var.name)
DecisionVariable.continuous(var.idx, lower=var.lb, upper=var.ub, name=var.name)
Function の作成#
旧方式:
# Protocol Buffer object を直接作る
Function(constant=constant)
Function(linear=Linear(terms=terms, constant=constant))
新方式:
# 統一された constructor
Function(constant) # scalar value から作成
Function(linear) # Linear object から作成
Function(quadratic) # Quadratic object から作成
# Linear object の作成
linear = Linear(terms=terms, constant=constant)
Constraint の作成#
旧方式:
# Protocol Buffer object を直接作る
Constraint(
id=id,
equality=Equality.EQUALITY_EQUAL_TO_ZERO,
function=function,
name=name,
)
新方式:
# 直接 constructor で作成する (ommx.v1.Function を使う)
constraint = Constraint(
id=id,
function=function, # ommx.v1.Function を使う
equality=Constraint.EQUAL_TO_ZERO, # Python SDK の定数を使う
name=name,
)
Function の検査と変換#
旧方式:
# Protocol Buffer の HasField check
if function.HasField("linear"):
linear_terms = function.linear.terms
constant = function.linear.constant
新方式:
# Function.degree() で多項式次数を確認し、直接 property access を使う
degree = function.degree()
if degree == 0:
# 定数関数
constant = function.constant_term
elif degree == 1:
# 線形関数 - 直接 property access
linear_terms = function.linear_terms # dict[int, float]
constant = function.constant_term # float
elif degree == 2:
# 二次関数 - 直接 property access
quadratic_terms = function.quadratic_terms # dict[tuple[int, int], float]
linear_terms = function.linear_terms # dict[int, float]
constant = function.constant_term # float
# 実際の adapter 利用例 (PySCIPOpt):
def _make_linear_expr(self, f: Function) -> pyscipopt.Expr:
return (
pyscipopt.quicksum(
coeff * self.varname_map[str(id)]
for id, coeff in f.linear_terms.items()
)
+ f.constant_term
)
def _make_quadratic_expr(self, f: Function) -> pyscipopt.Expr:
# 二次項
quad_terms = pyscipopt.quicksum(
self.varname_map[str(row)] * self.varname_map[str(col)] * coeff
for (row, col), coeff in f.quadratic_terms.items()
)
# 線形項
linear_terms = pyscipopt.quicksum(
coeff * self.varname_map[str(var_id)]
for var_id, coeff in f.linear_terms.items()
)
return quad_terms + linear_terms + f.constant_term
移行手順#
import を更新する: Protocol Buffer からの直接 import (
*_pb2) を削除し、ommx.v1からの統一 import に変更する。Function の検査方法を変更する:
.HasField()を.degree()check と直接 property access に置き換える。新しい method を利用する: より簡潔な type-specific factory method (
binary()、integer()、continuous()) を利用できます。
よくある問題と解決策#
AttributeError: 'builtins.Function' object has no attribute 'HasField':.degree()check の後に直接 property access (.linear_terms、.constant_termなど) を使います。TypeError: 'float' object is not callable:function.constant_termは method ではなく property としてアクセスします。function.constant_term()ではありません。.raw属性の利用:raw属性は非推奨です。性能と型安全性のため、各クラスが直接提供する method (solution.get_constraint_value()、instance.decision_variablesなど) を使ってください。
重要な注意点#
すべて
ommx.v1から統一的に import し、Protocol Buffer からの直接 import を避けてください。constraint type を判定する場合は、次数の低い順 (constant → linear → quadratic) に確認してください。
新しく利用できる method#
Function class#
# 情報取得
function.degree() -> int # Function の次数
function.num_terms() -> int # 項数
# 直接 property access (推奨)
function.constant_term # float - 定数項
function.linear_terms # dict[int, float] - 線形項の係数
function.quadratic_terms # dict[tuple[int, int], float] - 二次項の係数
# 評価
function.evaluate(state: State | dict[int, float]) -> float
function.partial_evaluate(state: State | dict[int, float]) -> Function
Solution class (v2.0.0rc3+)#
# 新しい list-based property (Instance と一貫)
solution.decision_variables # list[EvaluatedDecisionVariable] - ID 順に sort 済み
solution.constraints # list[EvaluatedConstraint] - ID 順に sort 済み
# 新しい個別 access method
solution.get_decision_variable_by_id(variable_id: int) -> EvaluatedDecisionVariable
solution.get_constraint_by_id(constraint_id: int) -> EvaluatedConstraint
SampleSet class (v2.0.0rc3+)#
# 新しい list-based property (Instance と一貫)
sample_set.decision_variables # list[SampledDecisionVariable] - ID 順に sort 済み
sample_set.constraints # list[SampledConstraint] - ID 順に sort 済み
# 新しい個別 access method
sample_set.get_sample_by_id(sample_id: int) -> Solution # get() の alias
sample_set.get_decision_variable_by_id(variable_id: int) -> SampledDecisionVariable
sample_set.get_constraint_by_id(constraint_id: int) -> SampledConstraint
推奨される実装 pattern#
# 統一 import (v2.0.0rc3+)
from ommx.v1 import (
Instance, DecisionVariable, Constraint,
Function, Linear, Solution, State, SampleSet,
# 一貫した API access のための新しい evaluated 型
EvaluatedDecisionVariable, EvaluatedConstraint,
SampledDecisionVariable, SampledConstraint
)
# DecisionVariable の作成 (新しい factory method)
var1 = DecisionVariable.binary(0, name="x1")
var2 = DecisionVariable.integer(1, lower=0, upper=10, name="x2")
# Function の検査 (直接 property access)
if objective.degree() == 1:
terms = objective.linear_terms # dict[int, float]
constant = objective.constant_term # float
elif objective.degree() == 2:
linear_terms = objective.linear_terms # dict[int, float]
quadratic_terms = objective.quadratic_terms # dict[tuple[int, int], float]
constant = objective.constant_term # float
# 全 class で一貫した API pattern (v2.0.0rc3+)
# 3 つの class はすべて同じ pattern に従います。
# Instance
for var in instance.decision_variables: # list[DecisionVariable]
process_variable(var.id, var)
var = instance.get_decision_variable_by_id(var_id) # DecisionVariable
# Solution
for var in solution.decision_variables: # list[EvaluatedDecisionVariable]
process_evaluated_variable(var.id, var.value)
var = solution.get_decision_variable_by_id(var_id) # EvaluatedDecisionVariable
# SampleSet
for var in sample_set.decision_variables: # list[SampledDecisionVariable]
process_sampled_variable(var.id, var.samples)
var = sample_set.get_decision_variable_by_id(var_id) # SampledDecisionVariable
State constructor の変更 (PyO3 移行)#
改善: State(entries=...) constructor は、dict[int, float] と Iterable[tuple[int, float]] の両方を受け取れるように拡張されました。
以前 (Protobuf):
# protobuf State ではこれらが動作していた
state = State(entries=zip(variables, values)) # ✅ 動作
state = State(entries=[(1, 0.5), (2, 1.0)]) # ✅ 動作
以後 (PyO3) - 拡張 constructor:
# これらの pattern はすべて拡張 PyO3 State constructor で動作する
state = State(entries=zip(variables, values)) # ✅ iterable で動作
state = State(entries=[(1, 0.5), (2, 1.0)]) # ✅ iterable で動作
state = State(entries=dict(zip(variables, values))) # ✅ dict で動作
state = State(entries={1: 0.5, 2: 1.0}) # ✅ dict で動作
Adapter code 例:
# adapter code 内 (例: ommx-openjij-adapter)
def decode_to_samples(response: oj.Response) -> Samples:
# どちらの pattern も拡張 PyO3 State で動作する:
state = State(entries=zip(response.variables, sample)) # ✅ 直接動作
# または
state = State(entries=dict(zip(response.variables, sample))) # ✅ こちらも動作
移行状況:
✅ 完了:
ommx.v1.Stateを PyO3_ommx_rust.Stateへ移行✅ 完了: State constructor を dict と iterable の両方を受け取れるように拡張
✅ 完了: State constructor 変更に対する adapter 互換性修正
✅ OpenJij adapter:
zip()とdict(zip())の両 pattern に対応✅ PyScipOpt adapter: protobuf / PyO3 互換性のため
to_state()function を拡張✅ legacy protobuf State を含むように
ToStatetype alias を拡張
✅ 完了:
ommx.v1.Solutionを PyO3_ommx_rust.Solutionへ移行✅ 完了:
ommx.v1.SampleSetを PyO3_ommx_rust.SampleSetへ移行
Solution API の変更#
改善: Solution は constraint と dual variable にアクセスする直接 method を提供するようになりました。
Constraint value へのアクセス#
以前 (Protobuf):
# evaluated_constraints list への直接 access
solution.raw.evaluated_constraints[0].evaluated_value # ❌ 利用不可
以後 (PyO3):
# 新しい getter method を使う
solution.get_constraint_value(0) # ✅ ID で constraint value を取得
Dual variable の管理#
以前 (Protobuf):
# constraint object を直接変更
for constraint in solution.raw.evaluated_constraints:
constraint.dual_variable = dual_variables[constraint.id] # ❌ 利用不可
以後 (PyO3):
# 新しい setter / getter method を使う
solution.set_dual_variable(constraint_id, dual_value) # ✅ ID で dual variable を設定
solution.get_dual_variable(constraint_id) # ✅ ID で dual variable を取得
Constraint ID へのアクセス#
以前 (Protobuf):
# evaluated_constraints を iterate する
for constraint in solution.raw.evaluated_constraints:
id = constraint.id # ❌ 利用不可
以後 (PyO3):
# constraint_ids property を使う
for constraint_id in solution.constraint_ids: # ✅ constraint ID の set を返す
value = solution.get_constraint_value(constraint_id)
Adapter 実装例#
HiGHS Adapter:
# 旧方式
for constraint in solution.raw.evaluated_constraints:
if constraint.id < row_dual_len:
constraint.dual_variable = row_dual[constraint.id]
# 新方式
for constraint_id in solution.constraint_ids:
if constraint_id < row_dual_len:
solution.set_dual_variable(constraint_id, row_dual[constraint_id])
Python-MIP Adapter:
# 旧方式
for constraint in solution.raw.evaluated_constraints:
id = constraint.id
if id in dual_variables:
constraint.dual_variable = dual_variables[id]
# 新方式
for constraint_id, dual_value in dual_variables.items():
solution.set_dual_variable(constraint_id, dual_value)
完全な Solution API reference#
# Properties
solution.objective # float - objective value
solution.constraint_ids # set[int] - すべての constraint ID
solution.decision_variable_ids # set[int] - すべての decision variable ID
solution.feasible # bool - feasibility status
solution.feasible_relaxed # bool - relaxed feasibility status
# 新しい list-based property (v2.0.0rc3+)
solution.decision_variables # list[EvaluatedDecisionVariable] - ID 順に sort 済み
solution.constraints # list[EvaluatedConstraint] - ID 順に sort 済み
# Methods
solution.get_constraint_value(constraint_id: int) -> float
solution.get_dual_variable(constraint_id: int) -> Optional[float]
solution.set_dual_variable(constraint_id: int, value: Optional[float]) -> None
solution.extract_decision_variables(name: str) -> dict[tuple[int, ...], float]
solution.extract_constraints(name: str) -> dict[tuple[int, ...], float]
# 新しい個別 access method (v2.0.0rc3+)
solution.get_decision_variable_by_id(variable_id: int) -> EvaluatedDecisionVariable
solution.get_constraint_by_id(constraint_id: int) -> EvaluatedConstraint
# State access (後方互換)
solution.state # variable value を持つ State object
solution.state.entries # dict[int, float] - variable ID から value への mapping
Solution API の一貫性改善 (v2.0.0rc3+)#
改善: Solution API は、一貫性のため Instance と同じ pattern に従うようになりました。
新しい property:
# List-based access (Instance と一貫)
solution.decision_variables # list[EvaluatedDecisionVariable] - ID 順に sort 済み
solution.constraints # list[EvaluatedConstraint] - ID 順に sort 済み
# ID による個別 access (Instance と一貫)
solution.get_decision_variable_by_id(variable_id: int) -> EvaluatedDecisionVariable
solution.get_constraint_by_id(constraint_id: int) -> EvaluatedConstraint
移行 pattern:
# Before: constraint value への直接 access
solution.get_constraint_value(constraint_id)
solution.get_dual_variable(constraint_id)
# After: constraint object 経由で access (代替 pattern)
constraint = solution.get_constraint_by_id(constraint_id)
value = constraint.evaluated_value
dual_var = constraint.dual_variable
# どちらの pattern も後方互換性のため support されています
SampleSet API reference#
SampleSet class は、sample へのアクセスと data 抽出のための直接 method を提供するようになりました。
# Properties
sample_set.sample_ids # set[int] - すべての sample ID
sample_set.feasible_ids # set[int] - feasible な sample ID
sample_set.best_feasible_id # Optional[int] - 最良 feasible sample の ID
sample_set.best_feasible # Optional[Solution] - 最良 feasible solution
# 新しい list-based property (v2.0.0rc3+)
sample_set.decision_variables # list[SampledDecisionVariable] - ID 順に sort 済み
sample_set.constraints # list[SampledConstraint] - ID 順に sort 済み
# Methods
sample_set.get(sample_id: int) -> Solution
sample_set.extract_decision_variables(name: str, sample_id: int) -> dict[tuple[int, ...], float]
sample_set.extract_constraints(name: str, sample_id: int) -> dict[tuple[int, ...], float]
# 新しい個別 access method (v2.0.0rc3+)
sample_set.get_sample_by_id(sample_id: int) -> Solution # get() の alias
sample_set.get_decision_variable_by_id(variable_id: int) -> SampledDecisionVariable
sample_set.get_constraint_by_id(constraint_id: int) -> SampledConstraint
SampleSet API の一貫性改善 (v2.0.0rc3+)#
改善: SampleSet API は、一貫性のため Instance と Solution と同じ pattern に従うようになりました。
新しい property:
# List-based access (Instance と Solution と一貫)
sample_set.decision_variables # list[SampledDecisionVariable] - ID 順に sort 済み
sample_set.constraints # list[SampledConstraint] - ID 順に sort 済み
# ID による個別 access (Instance と Solution と一貫)
sample_set.get_sample_by_id(sample_id: int) -> Solution # 既存の get() の alias
sample_set.get_decision_variable_by_id(variable_id: int) -> SampledDecisionVariable
sample_set.get_constraint_by_id(constraint_id: int) -> SampledConstraint
API 一貫性の達成: 3 つの core class はすべて同じ pattern に従うようになりました。
Instance:
decision_variables→list[DecisionVariable]、get_decision_variable_by_id()→DecisionVariableSolution:
decision_variables→list[EvaluatedDecisionVariable]、get_decision_variable_by_id()→EvaluatedDecisionVariableSampleSet:
decision_variables→list[SampledDecisionVariable]、get_decision_variable_by_id()→SampledDecisionVariable
同じ pattern が constraint と他の access method にも適用されます。
Adapter API の変更 (v2.0-rc.4)#
破壊的変更: Instance API の method が property に変わり、返り値の型も dictionary から list に変わりました。
Instance API の変更#
Before:
# dictionary を返す method
for var_id, var in instance.decision_variables().items():
process_variable(var_id, var)
for constraint_id, constraint in instance.constraints().items():
process_constraint(constraint_id, constraint)
# iteration のための raw access
for var_id, var in instance.raw.decision_variables.items():
process_variable(var_id, var)
After:
# list を返す property (ID 順)
for var in instance.decision_variables:
process_variable(var.id, var)
for constraint in instance.constraints:
process_constraint(constraint.id, constraint)
# raw access は不要 - property を直接使う
for var in instance.decision_variables:
process_variable(var.id, var)
Instance sense への access#
Before:
if instance.raw.sense == Instance.MAXIMIZE:
# maximize を処理
elif instance.raw.sense == Instance.MINIMIZE:
# minimize を処理
After:
if instance.sense == Instance.MAXIMIZE:
# maximize を処理
elif instance.sense == Instance.MINIMIZE:
# minimize を処理
Adapter 実装の更新#
Python-MIP Adapter:
# Before
def _set_decision_variables(self):
for var_id, var in self.instance.raw.decision_variables.items():
# variable を処理
# After
def _set_decision_variables(self):
for var in self.instance.decision_variables:
# var.id と var property を使って variable を処理
State 作成 pattern:
# Before
return State(entries={
var_id: data.var_by_name(str(var_id)).x
for var_id, var in self.instance.raw.decision_variables.items()
})
# After
return State(entries={
var.id: data.var_by_name(str(var.id)).x
for var in self.instance.decision_variables
})
新しい Instance method#
個別 access:
# ID で特定の item を取得する (ID がない場合は KeyError)
var = instance.get_decision_variable_by_id(variable_id) # 個別 variable access
constraint = instance.get_constraint_by_id(constraint_id) # 個別 constraint access
removed_constraint = instance.get_removed_constraint(constraint_id) # 個別 removed constraint access
Adapter で必要な変更#
.rawaccess を置き換える:.raw.decision_variables.items()の代わりに直接 property を使う。iteration pattern を更新する:
dict.items()から list の直接 iteration に変更する。個別 ID に access する: dict key の代わりに各 object の
.idproperty を使う。sense access を更新する:
instance.raw.senseではなくinstance.senseを使う。新しい個別 access method を使う:
get_decision_variable_by_id()とget_constraint_by_id()を使って個別 item に access する。
Adapter 向け移行 checklist#
[ ]
instance.raw.decision_variables.items()をinstance.decision_variablesに置き換える。[ ]
instance.raw.constraints.items()をinstance.constraintsに置き換える。[ ]
instance.raw.senseをinstance.senseに置き換える。[ ] variable access を
(var_id, var)からvarに更新する (var.idを使う)。[ ] constraint access を
(constraint_id, constraint)からconstraintに更新する (constraint.idを使う)。[ ] test assertion を
len(instance.decision_variables())からlen(instance.decision_variables)に更新する。[ ] 個別 variable access には
instance.get_decision_variable_by_id(id)を使う。[ ] 個別 constraint access には
instance.get_constraint_by_id(id)を使う。