様々なデータをOMMX Artifact形式で共有する#
数理最適化のワークフローでは、多様なデータの生成と管理が不可欠です。これらのデータを適切に管理することで、計算結果の再現性が確保され、チーム内での効率的な共有が可能になります。
OMMXは、これらの多様なデータを効率的かつシンプルに管理する仕組みを提供します。具体的には、OMMX Artifactというデータ形式を定義し、最適化計算に関連する多様なデータの保存・管理・共有をOMMX SDKによって可能にします。
事前準備:共有するデータ#
まず共有するべきデータを用意しましょう。ナップザック問題を表す ommx.v1.Instance
を作成し、SCIPによる最適化計算を行います。さらに最適化計算に対する分析結果も共有します。今回はこれらの処理の詳細は本題から離れるので省略します。
Show code cell source
from ommx.v1 import Instance, DecisionVariable, Constraint
from ommx_pyscipopt_adapter.adapter import OMMXPySCIPOptAdapter
import pandas as pd
# 0-1ナップサック問題のデータを用意する
data = {
# 各アイテムの価値
"v": [10, 13, 18, 31, 7, 15],
# 各アイテムの重さ
"w": [11, 15, 20, 35, 10, 33],
# ナップサックの耐荷重
"W": 47,
# アイテムの総数
"N": 6,
}
# 決定変数を定義する
x = [
# バイナリ変数 x_i を定義する
DecisionVariable.binary(
# 決定変数のIDを指定する
id=i,
# 決定変数の名前を指定する
name="x",
# 決定変数の添え字を指定する
subscripts=[i],
)
# バイナリ変数を num_items 個だけ用意する
for i in range(data["N"])
]
# 目的関数を定義する
objective = sum(data["v"][i] * x[i] for i in range(data["N"]))
# 制約条件を定義する
constraint = Constraint(
# 制約条件の名前
name = "重量制限",
# 制約式の左辺を指定する
function=sum(data["w"][i] * x[i] for i in range(data["N"])) - data["W"],
# 等式制約 (==0) or 不等式制約 (<=0) を指定する
equality=Constraint.LESS_THAN_OR_EQUAL_TO_ZERO,
)
# インスタンスを作成する
instance = Instance.from_components(
# インスタンスに含まれる全ての決定変数を登録する
decision_variables=x,
# 目的関数を登録する
objective=objective,
# 全ての制約条件を登録する
constraints=[constraint],
# 最大化問題であることを指定する
sense=Instance.MAXIMIZE,
)
# SCIPで解く
solution = OMMXPySCIPOptAdapter.solve(instance)
# 最適解の分析をする
df_vars = solution.decision_variables
df = pd.DataFrame.from_dict(
{
"アイテムの番号": df_vars.index,
"ナップサックに入れるか?": df_vars["value"].apply(lambda x: "入れる" if x == 1.0 else "入れない"),
}
)
変数名 |
説明 |
値 |
||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
0-1ナップサック問題に対応する |
Instance(raw=decision_variables {
kind: KIND_BINARY
bound {
upper: 1
}
name: "x"
subscripts: 0
}
decision_variables {
id: 1
kind: KIND_BINARY
bound {
upper: 1
}
name: "x"
subscripts: 1
}
decision_variables {
id: 2
kind: KIND_BINARY
bound {
upper: 1
}
name: "x"
subscripts: 2
}
decision_variables {
id: 3
kind: KIND_BINARY
bound {
upper: 1
}
name: "x"
subscripts: 3
}
decision_variables {
id: 4
kind: KIND_BINARY
bound {
upper: 1
}
name: "x"
subscripts: 4
}
decision_variables {
id: 5
kind: KIND_BINARY
bound {
upper: 1
}
name: "x"
subscripts: 5
}
objective {
linear {
terms {
coefficient: 10
}
terms {
id: 1
coefficient: 13
}
terms {
id: 2
coefficient: 18
}
terms {
id: 3
coefficient: 31
}
terms {
id: 4
coefficient: 7
}
terms {
id: 5
coefficient: 15
}
}
}
constraints {
equality: EQUALITY_LESS_THAN_OR_EQUAL_TO_ZERO
function {
linear {
terms {
coefficient: 11
}
terms {
id: 1
coefficient: 15
}
terms {
id: 2
coefficient: 20
}
terms {
id: 3
coefficient: 35
}
terms {
id: 4
coefficient: 10
}
terms {
id: 5
coefficient: 33
}
constant: -47
}
}
name: "重量制限"
}
sense: SENSE_MAXIMIZE
, annotations={})
|
||||||||||||||||||||||||
|
0-1ナップサック問題をSCIPで解いた計算結果が格納されている |
Solution(raw=state {
entries {
key: 0
value: 1
}
entries {
key: 1
value: 1
}
entries {
key: 2
value: 1
}
entries {
key: 3
value: 0
}
entries {
key: 4
value: 0
}
entries {
key: 5
value: 0
}
}
objective: 41
decision_variables {
kind: KIND_BINARY
bound {
upper: 1
}
name: "x"
subscripts: 0
}
decision_variables {
id: 1
kind: KIND_BINARY
bound {
upper: 1
}
name: "x"
subscripts: 1
}
decision_variables {
id: 2
kind: KIND_BINARY
bound {
upper: 1
}
name: "x"
subscripts: 2
}
decision_variables {
id: 3
kind: KIND_BINARY
bound {
upper: 1
}
name: "x"
subscripts: 3
}
decision_variables {
id: 4
kind: KIND_BINARY
bound {
upper: 1
}
name: "x"
subscripts: 4
}
decision_variables {
id: 5
kind: KIND_BINARY
bound {
upper: 1
}
name: "x"
subscripts: 5
}
evaluated_constraints {
equality: EQUALITY_LESS_THAN_OR_EQUAL_TO_ZERO
evaluated_value: -1
used_decision_variable_ids: 0
used_decision_variable_ids: 1
used_decision_variable_ids: 2
used_decision_variable_ids: 3
used_decision_variable_ids: 4
used_decision_variable_ids: 5
name: "重量制限"
}
feasible: true
optimality: OPTIMALITY_OPTIMAL
feasible_relaxed: true
, annotations={})
|
||||||||||||||||||||||||
|
0-1ナップサック問題の入力データ |
{'v': [10, 13, 18, 31, 7, 15], 'w': [11, 15, 20, 35, 10, 33], 'W': 47, 'N': 6}
|
||||||||||||||||||||||||
|
0-1ナップサック問題の最適解表す |
|
ファイルとしてOMMX Artfactを作成する#
OMMX Artifactはファイルで管理する方法と、コンテナのように名前で管理する方法がありますが、ここではまずファイルを使った方法を紹介します。OMMX SDKを使って、上記のデータをOMMX Artifact形式の新しいファイル my_instance.ommx
に保存しましょう。まず ArtifactBuilder
を用意します。
import os
from ommx.artifact import ArtifactBuilder
# OMMX Artifactファイルの名前を指定する
filename = "my_instance.ommx"
# 既にファイルが存在している場合は削除する
if os.path.exists(filename):
os.remove(filename)
# 1. OMMX Artifactファイルを作成するためのビルダーを作成する
builder = ArtifactBuilder.new_archive_unnamed(filename)
ArtifactBuilder
にはいくつかコンストラクタがあり、コンテナとして名前で管理するか、アーカイブファイルとして管理するかを選択できます。コンテナのようにコンテナレジストリを使ってPushとPullを行う場合は名前が必須ですが、アーカイブファイルを使う場合は名前が不要です。ここではアーカイブファイルとして管理する ArtifactBuilder.new_archive_unnamed
を使います。
コンストラクタ |
説明 |
---|---|
コンテナとして名前で管理する |
|
アーカイブファイルとコンテナの両方として扱えるようにする |
|
アーカイブファイルとして管理する |
|
GitHub Container Registryに合わせてコンテナの名前を決める |
どの方法で初期化しても同じように ommx.v1.Instance
や他のデータを保存することが出来ます。上で用意したデータを追加してみましょう。
# ommx.v1.Instance オブジェクトを追加する
desc_instance = builder.add_instance(instance)
# ommx.v1.Solution オブジェクトを追加する
desc_solution = builder.add_solution(solution)
# pandas.DataFrame オブジェクトを追加する
desc_df = builder.add_dataframe(df, title="ナップサック問題の最適解")
# JSONに変換可能なオブジェクトを追加する
desc_json = builder.add_json(data, title="ナップサック問題のデータ")
OMMX Artifactではレイヤーという単位でデータを管理しますが、各レイヤーは中身がどんな種類のデータなのかを表現するためにMedia Typeを保持しており、add_instance
などの関数はこれらを適切に設定した上でレイヤーを追加します。この関数は生成したレイヤーの情報を保持した Description
オブジェクトを返します。
desc_json.to_dict()
{'mediaType': 'application/json',
'digest': 'sha256:6cbfaaa7f97e84d8b46da95b81cf4d5158df3a9bd439f8c60be26adaa16ab3cf',
'size': 78,
'annotations': {'org.ommx.user.title': 'ナップサック問題のデータ'}}
add_json
に追加した title="..."
という部分はレイヤーのアノテーション(注釈)として保存されます。OMMX Artifactというのは人間のためのデータ形式なので、これは基本的には人間が読むための情報です。ArtifactBuilder.add_*
関数はいずれも任意のキーワード引数を受け取り、自動的に org.ommx.user.
以下の名前空間に変換します。
さて最後に build
を呼び出してファイルに保存しましょう。
# 3. OMMX Artifactファイルを作成する
artifact = builder.build()
この artifact
は次説で説明する、今保存したファイルを読み込んだものと同じものです。ファイルが出来上がったか確認してみましょう:
! ls $filename
my_instance.ommx
あとはこの my_instance.ommx
を通常のファイル共有の方法で共有すれば、他の人とデータを共有することができます。
OMMX Artfact形式のファイルを読み取る#
次に保存したOMMX Artifactを読み込みましょう。アーカイブ形式で保存したOMMX Artifactを読み込むには Artifact.load_archive
を使います
from ommx.artifact import Artifact
# ローカルにあるOMMX Artifactファイルを読み込む
artifact = Artifact.load_archive(filename)
OMMX Artifactはレイヤーという単位でデータを管理しますが、このレイヤーのデータはマニフェスト(目録)として内包されており、アーカイブファイル全体を読み込まずに確認することが可能です。Artifact.layers
によって含まれるレイヤーの Descriptor
を取得できます。これにはそのレイヤーのMediaTypeとアノテーションが含まれています。
import pandas as pd
# 見やすいように pandas.DataFrame に変換する
pd.DataFrame({
"Media Type": desc.media_type,
"Size (Bytes)": desc.size
} | desc.annotations
for desc in artifact.layers
)
Media Type | Size (Bytes) | org.ommx.user.title | |
---|---|---|---|
0 | application/org.ommx.v1.instance | 325 | NaN |
1 | application/org.ommx.v1.solution | 266 | NaN |
2 | application/vnd.apache.parquet | 3157 | ナップサック問題の最適解 |
3 | application/json | 78 | ナップサック問題のデータ |
例えばレイヤー3に入っているJSONを取得するには Artifact.get_json
を使います。この関数はMedia Typeが application/json
である事を確認し、バイト列をJSON文字列としてPythonオブジェクトに復元します。
artifact.get_json(artifact.layers[3])
{'v': [10, 13, 18, 31, 7, 15], 'w': [11, 15, 20, 35, 10, 33], 'W': 47, 'N': 6}