2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【F#で数理最適化】Flipsに入門してみた

2
Last updated at Posted at 2026-08-08

Flipsに入門してみました. ただの備忘録です. 私の個人的な趣味です.

目次

背景

  • F#でOR-Toolsを使ってみた
  • 何となく, F#っぽくない?ような気がした
  • F#っぽい書き方が出来るライブラリは無いか?->Flipsに行きつく

※F#っぽくない?ような気がした件について.

大きく2つあります.

1つ目は, OR-Toolsでは制約式や目的関数を生成する際, model.Addという書き方をします.
個人的に, この書き方がオブジェクト指向的に感じておりまして. F#っぽくないな?と, 感じます.

2つ目は, F#で制約式を追加する際, 例えばLinearExpr.op_GreaterThanOrEqualという書き方をします.(<=のようなオペレータを使用すれば良いのでは?と思いましたが, 私では上手く行かず.)

let expr = LinearExpr.Sum(vars)
model.Add(LinearExpr.op_GreaterThanOrEqual (expr, int64 10)) |> ignore

こちらの書き方が, 個人的には少し受け入れ難いというか... F#の関数型的な書き味が変わるな?と感じました.

C#だと, 個人的に違和感がありません. C#ではオペレータがそのまま使えます.

solver.Add(1.0 * var1 <= 300.0);
solver.Add(0.5 * var1 + 0.4 * var2 <= 200.0);

Flipsとは?

先述した個人的な違和感を解消してくれそうなライブラリとして, Flipsに行きつきました.

まず, Flipsとは?公式ドキュメントによると, 以下のように書いてあります.

Flips is an F# library for modeling and solving Linear Programming (LP) and Mixed-Integer Programming (MIP) problems. It is inspired by the work of the PuLP library for Python and the excellent Gurobi Python library. It builds on the work of the outstanding Google OR-Tools library and the OPTANO library.

ざっくり日本語訳すると,

  • Flipsとは, F#の為の線形計画法(LP)と混合整数計画法(MIP)をモデリング出来て求解出来るライブラリ
  • PuLPやGurobiのPythonライブラリにインスパイアされた
  • OR-ToolsとOPTANOを使って動く

要は, OR-ToolsとOPTANOのF#用ラッパーライブラリと思うと良いのでは?と思います.

実際にコードを写経して書き替えてみた

導入は簡単です. nugetすれば良し. .NET勢ならすぐに導入できます.

公式ドキュメントにはシンプルなフードトラックを模した数理最適化問題の求解が書かれています. ただ, 手続き的な書き方が多いと感じます.
恐らく, C#などの.NET勢やPython/Julia等のピュア最適解人材をターゲットとしていて, 関数型プログラミングにはあまり慣れていないことを想定しているのかな?と邪推します. 下衆の勘繰りと読み流してください.

私はF#っぽい書き方をしたいので, ちょっとだけ書き換えました.

ドメイン部分

Flipsを呼び出し, 商品と決定変数を生成します.

// Program.fs
namespace Core

module Program =
    open Flips
    open Flips.Types

    /// 商品のデータ構造(ドメインモデル)
    type Item = {
        Name: string
        Profit: float
        MaxSupply: float
        Weight: float
    }

    /// 元のデータと決定変数をペアで保持する
    type ItemDecision = {
        Item: Item
        Decision: Decision
    }

計算と可視化部分

サクッと関数を作っておきます.

visualizeSolution関数について, 個人的に面白かったのが, 通常のオブジェクト指向的な考え方なら, Solution.ObjectiveValue()やObjective.Valueなどで目的関数の値にアクセス出来るようにするはずです.

しかし, わざわざObjective.evaluate solution objectiveと, 解と目的関数を評価関数に渡しています. 関数型プログラミングでは状態を持つことが出来ないので, このような表示方法になるのだと思います.

// Program.fs
namespace Core

module Program =
    open Flips
    open Flips.Types

    // -----------------------------------------------
    // 中略
    // -----------------------------------------------

    /// 商品名から変数を作成する
    let createContinuousDecision (itemName: string) : Decision =
        Decision.createContinuous $"NumberOf{itemName}" 0.0 infinity

    /// 目的関数を生成する
    let createObjective (objectiveExpression: LinearExpression): Objective =
        Objective.create "MaximizeRevenue" Maximize objectiveExpression

    /// 供給量に関する制約式を生成する
    let createSupplyConstraint (pair: ItemDecision): Constraint =
        Constraint.create ($"MaxOf{pair.Item.Name}") (pair.Decision <== pair.Item.MaxSupply)

    /// トラックの重さに関する制約式を生成する
    let createWeightConstraint (maxTruckWeight: float) (weightExpr: LinearExpression): Constraint =
        Constraint.create ("MaxWeight") (weightExpr <== maxTruckWeight)

    /// 正常に解が出た場合に標準出力で表示する
    let visualizeSolution (solution: Solution) (objective: Objective): unit =
        let obj = Objective.evaluate solution objective
        printfn $"Objective Value: %f{obj}"
        solution.DecisionResults
            |> Map.toList
            |> List.iter (fun (dec, value) ->
                let (DecisionName name) = dec.Name
                printfn $"%s{name}: %f{value}"
            )
    // -----------------------------------------------
    // 中略
    // -----------------------------------------------

メイン関数

メイン関数では, これまでに作った道具たちを組み合わせていきます.
全体的にパイプライン演算子(|>)で書けていると思います.

// Program.fs
namespace Core

module Program =
    open Flips
    open Flips.Types

    // -----------------------------------------------
    // 中略
    // -----------------------------------------------

    [<EntryPoint>]
    let main (args: string array) =
        // アイテムリスト, 固定値を定義
        let items: Item list = [
            {Name = "Hamburger"; Profit = 1.5; MaxSupply = 300.0; Weight = 0.5}
            {Name = "HotDog"   ; Profit = 1.2; MaxSupply = 200.0; Weight = 0.4}
        ]
        let maxTruckWeight = 200.0
        // アイテムとその変数を生成
        let itemDecisions =
            items |> List.map (fun item -> {
                Item=item
                Decision = createContinuousDecision item.Name
            })

        // 目的関数を生成
        let objective =
            itemDecisions
            |> List.sumBy (fun pair -> pair.Item.Profit * pair.Decision)
            |> createObjective
        // 各商品の数量に関する制約を生成
        let supplyConstraints =
            itemDecisions
            |> List.map (fun pair ->
                createSupplyConstraint pair
            )
        // 各商品の重さに関する制約を生成
        let weightConstraint =
            itemDecisions
            |> List.sumBy (fun pair -> pair.Item.Weight * pair.Decision)
            |> createWeightConstraint maxTruckWeight
        // モデルを生成
        let model =
            Model.create objective
            |> Model.addConstraints supplyConstraints
            |> Model.addConstraint weightConstraint
        // 設定ファイルを作る
        let settings = {
            SolverType = SolverType.CBC
            MaxDuration = 10_000L
            WriteLPFile = None
            WriteMPSFile = None
        }

        // 求解して画面表示
        let result = Solver.solve settings model
        printfn "-- Result --"
        match result with
        | Optimal solution ->
            visualizeSolution solution objective
        | _ ->
            printfn $"Unable to solve. Error: %A{result}"
        0

個人的な感想

全体的にF#っぽく書ける, 面白いライブラリだと思います. ただ, 0-1変数(Boolean/Integer decision)の扱い方や応用パターンについてはもう少し調べてみたいな, と思っています.

また, 私のコードはザックリとしか書いていませんので, 本来なら解き方のコンフィグなどは外だしするべきだな, とも思っています.

読んでくれてありがとうございました.

Appendix

リンク集

  • Flips公式ドキュメント
    • 情熱的に色々書いてあります
    • 分枝限定法や局所探索等のリンク集もあります
    • 暇な時にでも覗いてみます
  • FlipsのGitHub
    • FlipsのGitHubです
    • たった18人のコントリビュータで回してるってすごいな...
    • Languagesは100パーセントF#, 愛がスゴイ

コードの全文

コードの全文を載せます.

// Program.fs
namespace Core

module Program =
    open Flips
    open Flips.Types

    /// 商品のデータ構造(ドメインモデル)
    type Item = {
        Name: string
        Profit: float
        MaxSupply: float
        Weight: float
    }

    /// 元のデータと決定変数をペアで保持する
    type ItemDecision = {
        Item: Item
        Decision: Decision
    }

    /// 商品名から変数を作成する
    let createContinuousDecision (itemName: string) : Decision =
        Decision.createContinuous $"NumberOf{itemName}" 0.0 infinity

    /// 目的関数を生成する
    let createObjective (objectiveExpression: LinearExpression): Objective =
        Objective.create "MaximizeRevenue" Maximize objectiveExpression

    /// 供給量に関する制約式を生成する
    let createSupplyConstraint (pair: ItemDecision): Constraint =
        Constraint.create ($"MaxOf{pair.Item.Name}") (pair.Decision <== pair.Item.MaxSupply)

    /// トラックの重さに関する制約式を生成する
    let createWeightConstraint (maxTruckWeight: float) (weightExpr: LinearExpression): Constraint =
        Constraint.create ("MaxWeight") (weightExpr <== maxTruckWeight)

    /// 正常に解が出た場合に標準出力で表示する
    let visualizeSolution (solution: Solution) (objective: Objective): unit =
        let obj = Objective.evaluate solution objective
        printfn $"Objective Value: %f{obj}"
        solution.DecisionResults
            |> Map.toList
            |> List.iter (fun (dec, value) ->
                let (DecisionName name) = dec.Name
                printfn $"%s{name}: %f{value}"
            )

    [<EntryPoint>]
    let main (args: string array) =
        // アイテムリスト, 固定値を定義
        let items: Item list = [
            {Name = "Hamburger"; Profit = 1.5; MaxSupply = 300.0; Weight = 0.5}
            {Name = "HotDog"   ; Profit = 1.2; MaxSupply = 200.0; Weight = 0.4}
        ]
        let maxTruckWeight = 200.0
        // アイテムとその変数を生成
        let itemDecisions =
            items |> List.map (fun item -> {
                Item=item
                Decision = createContinuousDecision item.Name
            })

        // 目的関数を生成
        let objective =
            itemDecisions
            |> List.sumBy (fun pair -> pair.Item.Profit * pair.Decision)
            |> createObjective
        // 各商品の数量に関する制約を生成
        let supplyConstraints =
            itemDecisions
            |> List.map (fun pair ->
                createSupplyConstraint pair
            )
        // 各商品の重さに関する制約を生成
        let weightConstraint =
            itemDecisions
            |> List.sumBy (fun pair -> pair.Item.Weight * pair.Decision)
            |> createWeightConstraint maxTruckWeight
        // モデルを生成
        let model =
            Model.create objective
            |> Model.addConstraints supplyConstraints
            |> Model.addConstraint weightConstraint
        // 設定ファイルを作る
        let settings = {
            SolverType = SolverType.CBC
            MaxDuration = 10_000L
            WriteLPFile = None
            WriteMPSFile = None
        }

        // 求解して画面表示
        let result = Solver.solve settings model
        printfn "-- Result --"
        match result with
        | Optimal solution ->
            visualizeSolution solution objective
        | _ ->
            printfn $"Unable to solve. Error: %A{result}"
        0

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?