以下の内容はhttps://ujimushisradjp.hatenablog.jp/entry/2024/05/28/220118より取得しました。


JuMPを使って算額その1004を解く

お詫び

最初に断わっておきますが,完全に元ネタは算額(その1004)のパクりです。つつしんでお詫び申し上げます。

※ 追記[2025-07-11] 上記リンク切れを修正しました。

目的

とは言うものの個人的な目的もあって,

  • せっかくのJulia言語,SymPyで解くのはPythonの環境に依存しててセットアップ等が色々大変
  • せっかくのJulia言語,pyplotで描画するのはPythonの環境に依存してて...以下略

ということで,Pythonに依存しない(というかPythonのことよく分からないので)環境で解くにはどうしたらいいか?, そこでJuMPを使って試行錯誤してみたというのがこの記事の内容です。

問題

算額(その1004)を参照ください。

解いた例

JuMPの場合は,モデル化だけで実際のSolverは他のバイナリを利用するようです。が詳しいことは分かりません。 今回はIpoptのラッパー,Ipopt.jlを利用します。

なので,厳密にはpure Juliaかどうかは微妙なのですが,Pythonには依存しない感じです。

using JuMP, Ipopt, Latexify, LaTeXStrings

model = begin
    _model = Model(Ipopt.Optimizer)

    # 変数
    @variables(_model, begin
                   a == 25 / 2
                   b == 15 / 2
                   x₁  0
                   r₁  0
                   r₂
               end)

    # 制限条件
    @constraint(_model, r₂ == b / 2)
    @constraint(_model, 条件₁, (a^2 - b^2) * (b^2 - r₁^2) / b^2 == x₁^2)
    @constraint(_model, 条件₂, x₁^2 + r₂^2 == (r₁ +r₂)^2)

    # @objectiveを指定せず最適化
    optimize!(_model)

    println("a=$(value(a))")
    println("b=$(value(b))")
    println("x₁=$(value(x₁))")
    println("r₁=$(value(r₁))")
    println("r₂=$(value(r₂))")

    _model
end

render( model |> latex_formulation |> string |> LaTeXString,
        MIME("image/png"); name="算額1004")

解が一つしかないので,@objective抜きでも解けるようです。

実行結果は次のような感じです。

julia> include("sangaku_1004.jl")
  Activating project at `~/blog/test`
This is Ipopt version 3.14.14, running with linear solver MUMPS 5.6.2.

Number of nonzeros in equality constraint Jacobian...:        7
Number of nonzeros in inequality constraint Jacobian.:        0
Number of nonzeros in Lagrangian Hessian.............:        5

Total number of variables............................:        3
                     variables with only lower bounds:        2
                variables with lower and upper bounds:        0
                     variables with only upper bounds:        0
Total number of equality constraints.................:        3
Total number of inequality constraints...............:        0
        inequality constraints with only lower bounds:        0
   inequality constraints with lower and upper bounds:        0
        inequality constraints with only upper bounds:        0

iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
   0  0.0000000e+00 1.00e+02 1.00e+00  -1.0 0.00e+00    -  0.00e+00 0.00e+00   0
   1  0.0000000e+00 5.62e+05 2.03e+09  -1.0 1.80e+03    -  5.49e-06 2.50e-01f  3
   2  0.0000000e+00 1.41e+05 5.08e+08  -1.0 2.26e+02    -  1.00e+00 1.00e+00h  1
   3  0.0000000e+00 3.51e+04 1.27e+08  -1.0 1.12e+02    -  1.00e+00 1.00e+00h  1
   4  0.0000000e+00 8.76e+03 3.17e+07  -1.0 5.62e+01    -  1.00e+00 1.00e+00h  1
   5  0.0000000e+00 2.17e+03 7.92e+06  -1.0 2.80e+01    -  1.00e+00 1.00e+00h  1
   6  0.0000000e+00 5.19e+02 2.00e+06  -1.0 1.38e+01    -  1.00e+00 1.00e+00h  1
   7  0.0000000e+00 1.10e+02 5.09e+05  -1.0 6.43e+00    -  1.00e+00 1.00e+00h  1
   8  0.0000000e+00 1.46e+01 1.10e+05  -1.0 2.44e+00    -  1.00e+00 1.00e+00h  1
   9  0.0000000e+00 4.72e-01 7.09e+03  -1.0 4.75e-01    -  1.00e+00 1.00e+00h  1
iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
  10  0.0000000e+00 5.53e-04 1.54e+01  -1.0 1.81e-02    -  1.00e+00 1.00e+00h  1
  11  0.0000000e+00 7.92e-10 0.00e+00  -1.0 2.46e-05    -  1.00e+00 1.00e+00h  1

Number of Iterations....: 11

                                   (scaled)                 (unscaled)
Objective...............:   0.0000000000000000e+00    0.0000000000000000e+00
Dual infeasibility......:   0.0000000000000000e+00    0.0000000000000000e+00
Constraint violation....:   7.9246120776588214e-10    7.9246120776588214e-10
Variable bound violation:   0.0000000000000000e+00    0.0000000000000000e+00
Complementarity.........:   0.0000000000000000e+00    0.0000000000000000e+00
Overall NLP error.......:   7.9246120776588214e-10    7.9246120776588214e-10


Number of objective function evaluations             = 13
Number of objective gradient evaluations             = 12
Number of equality constraint evaluations            = 16
Number of inequality constraint evaluations          = 0
Number of equality constraint Jacobian evaluations   = 12
Number of inequality constraint Jacobian evaluations = 0
Number of Lagrangian Hessian evaluations             = 11
Total seconds in IPOPT                               = 0.007

EXIT: Optimal Solution Found.
a=12.5
b=7.5
x₁=7.683749084961452
r₁=4.800000000008585
r₂=3.75

一つの利点は,プログラムの最後のようにモデルをlatexの数式に出力できるところでしょうか? レンダリングしたものは次の通りです。

SymPyと比較しての欠点はa, b不定の時には解けないとか,それぞれの変数の関係性は 分からないというところでしょうか?

解だけ必要な時は特に問題ないかもしれません。

図の描画

一応次のようなコードで描画しました。

myplot_ellipseの中身については,scalemakeshape関数の定義位置がバージョン1,バージョン2で違うので, 仕方なく場合分けしています。

かなり手を入れていますが,それなりに元の図を再現できていると思います。(自画自賛)

Plots.jlGRバックエンドはあまりドキュメント化されていませんが, 使いこなすと色々な表現が可能だということは何となく分かってきたような気がする今日この頃です。

# 先ほどのモデルの変数
a, b, x₁, r₁, r₂ = map(x -> value(x), JuMP.all_variables(model))

using Plots
import GR
gr()

function myplot_ellipse!(plt, x0, y0, a, b, linecolor=:black)
    _scale, _makeshape = if isdefined(Plots, :PlotsBase)
        (PlotsBase.Shapes.scale, PlotsBase.Shapes.makeshape)
    else
        (Plots.scale, Plots.makeshape)
    end
    plot!(plt,
          Plots.translate(_scale(_makeshape(60), a, b), x0, y0),
          fillcolor=nothing, linecolor=linecolor, label="")
end

myplot_circle!(plt, x0, y0, r, linecolor=:black) = myplot_ellipse!(plt, x0, y0, r, r, linecolor)

function mypoint!(plt, text, x, y, mycolor=:black, valign=:vcenter, halign=:hcenter, family="JuiseeHWSZ-Regular")
    plot!(plt; annotations=(x, y, text), annotationfontfamily=family,
          annotationvalign=valign, annotationhalign=halign,
          annotationcolor=mycolor)
    scatter!(plt, (x, y); label="", markerstrokecolor=mycolor, markercolor=mycolor)
end

plt = plot(; xlims=(-13, 13), ylims=(-8, 8),
           xticks=((-2:2).* (a*2/4), ["-a","-a/2", "0", "a/2", "a"]),
           yticks=((-2:2).* (b*2/4), ["-b", "-b/2", "0", "b/2", "b"]),
           grid=nothing, size=(1000, 600), aspect_ratio=1)
           
vline!(plt, [0], linecolor=:gray, label="")
hline!(plt, [0], linecolor=:gray, label="")

myplot_ellipse!(plt, 0, 0, a, b, :red)
myplot_circle!(plt, x₁, 0, r₁, :blue)
myplot_circle!(plt, -x₁, 0, r₁, :blue)
myplot_circle!(plt, 0, r₂, r₂, :green)
myplot_circle!(plt, 0, -r₂, r₂, :green)

mypoint!(plt, " b", 0, b, :red, :bottom, :left)
mypoint!(plt, " a", a, 0, :red, :bottom, :left)

mypoint!(plt, "乙円: r₂, (0, r₂)", 0, r₂, :green, :top)
mypoint!(plt, "甲円: r₁, (x₁, 0)", x₁, 0, :blue, :top)

savefig(plt, "算額1004図.png")

※ 指定が誤っていたため(誤: aspect →正: aspect_ratio),図は縦横比が若干狂っていると思います。

さいごに

実はかなり試行錯誤を繰り返していて,何故解けているのかよく分かっていないところがあります。 そのうちエラい方々が色々解説してくれるでしょう。

このように,よく分からないうちに問題が解けてしまうのはJuMPの利点かもしれません。

よく分からないけど解だけ欲しい,頭を使うより腕力を使いたガールな方には JuMPも一つの方法かもしれません。




以上の内容はhttps://ujimushisradjp.hatenablog.jp/entry/2024/05/28/220118より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14