目的関数の定義

ブラックボックスな目的関数の例

ブラックボックス最適化においての目的関数は、数値シミュレーションや実験計測(とその後処理)などのように、入力→出力マッピングが複雑なブラックボックス関数になります。Amplify-BBOptは、そのようなブラックボックスな目的関数値を最小とするような入力値を探索します。

以下は、典型的なブラックボックス関数の一例です。

数値シミュレーションに基づくもの

以下の例では、翼の設計において、「揚力 \(L\) と抵抗力 \(D\) の比 (\(L/D\)) で定義される揚抗比を最大化する」最適化問題を想定します。Amplify-BBOpt では最小化問題として扱うため、本ブラックボックス関数は「揚抗比」の負の値を返し、この負値を最小化するように探索を実施します。

import WingSimulator  # 仮のシミュレーター

def my_blackbox_func_simulation(
    wing_width: float,
    wing_height: float,
    wing_angle: float,
) -> float:
    """シミュレーションで得られた翼に働く揚力と抵抗力の比(揚抗比)の負値を返却する."""
    simulator = WingSimulator(wing_width, wing_height, wing_angle)
    lift_force, drag_force = simulator.simulate()

    return -lift_force / drag_force

実験計測に基づくもの

もし、上記の翼の設計を実験計測に基づき行う場合は、次のようなブラックボックスな目的関数を実装することができます。この場合、Amplify-BBOpt が提示する新しい実験条件に従って実験を実施し、その結果を Amplify-BBOpt へ入力することで次の最適化の探索が開始される流れになります。

def my_blackbox_func_experiment(
    wing_width: float,
    wing_height: float,
    wing_angle: float,
) -> float:
    """実験条件を提示し、計測結果を入力、揚抗比の負値を返却する."""
    print(f"{wing_width=}, {wing_height=}, {wing_angle=} の条件で計測を行ってください"
    lift_force = input("得られた揚力を入力してください")
    drag_force = input("得られた抵抗力を入力してください")

    return -lift_force / drag_force

逆問題に基づくもの

ある出力を実現するような実験やシミュレーションへの入力値を決定する問題は逆問題と呼ばれます。ブラックボックス最適化は逆問題にも適用可能です。

上記の翼の設計において、予め決められた目標揚抗比を実現するような翼の設計を実施したい場合は、次のようにブラックボックスな目的関数を実装することができます。

import WingSimulator  # 仮のシミュレーター

# 予め決められた「揚力と抵抗力の比(L/D比)」
target_lift_drag_ratio = 0.1

def my_blackbox_func_inverse(
    wing_width: float,
    wing_height: float,
    wing_angle: float,
) -> float:
    """シミュレーションで得られた翼に働く揚力と抵抗力の比(揚抗比)とターゲット値との差の2乗を返却."""
    simulator = WingSimulator(wing_width, wing_height, wing_angle)
    lift_force, drag_force = simulator.simulate()

    return (target_lift_drag_ratio - lift_force / drag_force) ** 2

@blackbox によるブラックボックス関数の定義

Amplify-BBOpt では、ブラックボックス関数として扱う関数には、@blackbox デコレーター付与します。これにより、決定変数の作成に記載の方法で定義される決定変数とブラックボックス関数引数の紐づけを行うことができます。

基本的な定義

以下は前述のブラックボックス関数を使った実装例ですが、このように、ブラックボックス関数の各引数に対するデフォルト値として、決定変数の定義とブラックボックス関数への紐づけを同時に行うことが可能です。

1from amplify_bbopt import RealVariable, blackbox
2
3@blackbox
4def my_blackbox_func_simulation(
5    wing_width: float = RealVariable(bounds=(1, 20)),
6    wing_height: float = RealVariable(bounds=(1, 5)),
7    wing_angle: float = RealVariable(bounds=(0, 45)),
8) -> float:
9    # ...ブラックボックス関数処理...

Note

デフォルト値として紐づけを行う場合、環境によっては mypy や Pylance 等の型チェッカーによる警告が出ることがあります(上コードの場合、5~7 行目)。そのままでも実行に問題はないですが、気になるようであれば、警告行の末尾に # type: ignore を挿入してください。

より複雑な引数を有するブラックボックス関数の定義

次のように異なる種類の決定変数が混在している場合においても、@blackbox デコレーターによるブラックボックス関数の定義と引数と決定変数の紐づけが可能です。

from amplify_bbopt import BinaryVariable, IntegerVariable, RealVariable, blackbox

@blackbox
def my_blackbox_complex(
    x: float = RealVariable(bounds=(1, 20)),  # 実数決定変数
    i: int = IntegerVariable(bounds=(1, 5)),  # 整数決定変数
    q: int = BinaryVariable(),  # バイナリ決定変数
    y: list[float] = [RealVariable(bounds=(1, 20)) for _ in range(3)],  # 実数決定変数リスト
) -> float:
    # ...ブラックボックス関数処理...

Tip

Annotated を用いて紐づけ

以下のように、Python 3.12 以降での標準の機能である Annotated を使うことでも紐づけを行うことができます。

from amplify_bbopt import RealVariable, blackbox
from typing import Annotated

@blackbox
def my_blackbox_func_simulation(
    wing_width: Annotated[float, RealVariable(bounds=(1, 20))],
    wing_height: Annotated[float, RealVariable(bounds=(1, 5))],
    wing_angle: Annotated[float, RealVariable(bounds=(0, 45))],
) -> float:
    # ...ブラックボックス関数処理...

Hint

デコレータ @blackbox の効果について

デコレータ @blackbox に基づき定義されたブラックボックス関数は、BlackBoxFuncBase を継承したブラックボックス関数クラスのインスタンスとして取り扱われます。このクラスは次のようなアトリビュートを持ちます。

アトリビュート名

説明

name

ブラックボックス関数オブジェクトの名前

variables

ブラックボックス関数で考慮される決定変数

特に、variables を使うことで、当該のブラックボックス関数で考慮される決定変数オブジェクトをブラックボックス関数自身から取り出すことができます。これは、後述の制約条件(ハード制約)に関する多項式を実装する際などに有用です。

import WingSimulator  # 仮のシミュレーター
from amplify_bbopt import RealVariable, blackbox

@blackbox
def my_blackbox_func(
    wing_width: float = RealVariable(bounds=(1, 20)),
    wing_height: float = RealVariable(bounds=(1, 5)),
    wing_angle: float = RealVariable(bounds=(0, 45)),
) -> float:
    """シミュレーションで得られた翼に働く揚力と抵抗力の比(揚抗比)の負値を返却する."""
    simulator = WingSimulator(wing_width, wing_height, wing_angle)
    lift_force, drag_force = simulator.simulate()

    return -lift_force / drag_force


# 決定変数オブジェクトをブラックボックス関数クラスのアトリビュートとして取り出すことが可能
variables = my_blackbox_func.variables
print(variables.wing_width)  # wing_width
print(variables.wing_height)  # wing_height
print(variables.wing_angle)  # wing_angle

# クラスのインスタンスですが、通常の関数と同様な実行も可能
my_blackbox_func(15, 2, 25)