見出し画像

No. 8 C++で学ぶ最適化ソフトウェア開発: 作ったものを動かしてバグを探す話

※忙しい人はタイトルと太字部分だけ読めば大体OK!

 C++による最適化ソフトウェア開発に関連する連載も8回目ですね!本日は,作ったもの(作りかけですが)を動かしながらバグを探していく話をしていこうと思います.


はじめに最適化ソフトウェアの概要など

 2024年の末から開発してきた最適化ソフトウェアも,かなり形になってきました.このソフトウェアで用いている最適化アルゴリズムは「逐次二次計画法」と呼ばれるもので,英語ではSQPなどと略されます.Sequential Quadratic Programmingの略ですね.解きたい問題を狭い範囲で2次関数で近似して最適解のある方向を見つけ,少し進んだらまた別の2次関数をあてて方向を見つけて進む・・・を繰り返す方法です.この2次関数を使う部分をしてQuadratic,ちょっと進んで状況に合わせて何回も解くことをしてSequential,プログラミングして使うことを前提にしている(手作業じゃできないでしょ)をしてProgrammingというわけですね.
 歴史的には提案からすでに40年近く経過している方法だと思いますが,当時と比べるとコンピュータの性能が桁違いなので,現在でもかなり強力なアルゴリズムとして知られています.なお,40年というのには自信がないです.というのも,二次計画法の研究自体は1950〜1960年代にやられていたようなのですが,それを逐次実行するという話がコンピュータの発展で現実味を帯びた時期が不明なので.また,もちろん提案当時から比べると細かいアルゴリズムも格段に進化しています.筆者も修士1年生の時にSQPを初めて作り(移植に近かった),その恩恵にあずかってきました.色々とコツはあるのですが,宇宙探査機の機動設計や各種の制御則設計,システムの幾何的な形状など,やりたいことさえ数式で表現できていればかなりの確率で答えを見つけてくれます

ソフトウェア開発永遠の課題:自分の作ったものは正しいのか?

 さて,筆者はプログラマではないので,自分で作ったソフトウェアを自分の研究用に使うことがほとんどです.また,作ったものを教え子に渡して使ってもらうこともあります.このようなセミプロのようなプログラミングを行う筆者のような人でも,いつも気にしていることがあるのです.それは

  • 自分の作ったソフトウェアに間違いはないのか?

ということです.そしてタチの悪いことに,この疑念が晴れることはおそらく一生ありません...というのも,いつどこでどのようなミスが発見されるかわからないからです.例えば10年前に開発したソフトが何の問題もなく今まで動作しているとしても,特定の問題を解かせた時にレアなバグが10年の時を経て判明する可能性は否定できません(Hello worldくらいなら完全なコーディングが可能だけど).プログラムを書く人は程度の差こそあれ,これと同じ疑念を心の中に抱えながら開発を進め完成品を世に送り出しているはずです.
 筆者の体験談ですが,人からもらったコードにミスを見つけてしまうことは結構あります.例えば以前,パワーアシスト台車(荷物台にモータをつけて運び手の負担を減らすシステム)のシミュレーションコードを引き継いだときもそうでした.そのシミュレータだと台車が前に進んだ時しか計算できないようになっている,というバグを見つけたことがあります.また,後から見返して自分のソフトにバグを見つけることもまぁまぁあります.もちろん,ミスがあったとて世に出した結論が影響を受けないように種々の工夫をしておきます.ですが,やっぱりバグなんてない方が良いに決まってます.で,このようなバグはどうやって撮れば良いのでしょうか?

動くからといって正しいわけではない

 さて,プログラミングという作業においては,まずソースコードを書き,その後にコンパイルという作業を経てコンピュータが理解可能な形式に変換することでソフトウェアとして実行可能となります.で,このコンパイルですが,実はそうすんなりいくものでもありません.ソースコードに一箇所でも不備があれば途中でコンパイルが止まってしまいます.プログラミング言語が人にとって分かりやすいものであるといっても,やはり決まったルールに沿ってきっちり書く必要があるので,最初のコンパイルがいきなり成功してしまうことはほぼ無いと言って良いでしょう.ということで,一旦ソースコードが書き上がると,今度はコンパイルを通すためにコードをチェックしていくことになります.そうして細かい修正を行いバグを取り除いていくことで,コンパイルが成功し晴れてソフトウェアが実行可能となります.この実行可能な状態を指して「動く」などと言います(多分).
 しかし,問題はここからです.プログラミング初学者のうちは文法もままならないことが多いので,このコンパイルを通すという作業に気を取られがちになります.ようやくコンパイルが通った際の喜びもきっとひとしおでしょう.しかし,これが落とし穴となります.というのも,コンパイルが通るのはあくまでそのコードが正しい文法ルールで書かれているだけのことであって,実行可能だからといってプログラムの中身が正しいとは限らないからです.
 例えば,「a=a+1」と「a=a-1」という2つの数式問題を考えてみましょう.もちろん,我々にとってこの2式は全く異なる意味を持ちます.しかし,コンパイルを行うコンピュータにとってはどちらも正しい文法で書かれた式であり,従ってコードにどちらを書いたとてコンパイルは通ってしまうのです.こういった問題はプログラムの構成やルールといった枠組みのチェックで見つけることが困難なので,発見・修正にはまた別のアプローチを取る必要があるのです.その方法とは

  • コードを目で1行ずつチェックする

  • 答えの分かっている問題を解かせて正しい答えが得られるかを観察する

あたりだと思います.コードのチェックについては,特に説明の必要もないでしょう.上から順番に見ていき,自分の意図した通りに書けているかを確認するのです.ただ,コードはプログラミング言語の文章の羅列ですから,この読んでチェックする方法だけでコードの正しさを保証することは難しいでしょう.そこで,2つ目に挙げた方法,答えのわかっている問題を解かせるという方法が効果を発揮します.あらかじめ問題の答えがわかっていれば,自分の作ったプログラムがその答えを求められるか否かで正しさをチェック出来そうですよね.

 ということで,続いては作りかけの最適化ソフトウェアに2つの問題を解かせるということをやらせてみます!

解かせてみた1:教科書に掲載された最適化問題

 さて,まずは教科書に載っている問題が解けないとどうしようもありませんのでこちらから.元にした教科書はこちらでしたね:

現在中古で4000円...大学レベルの数学の知識がないと読めないので,買おうとした方は慎重にご判断を.というか,おそらく最新の本を買った方が良いです(筆者は手元にあったので).さて,解かせる問題はこちらです:

目的関数:-x + 10x^2+10y^2(最小化)
制約条件:x^2+y^2-1=0
             :x-0.5≧0
             :y+0.5≧0

茨木,福島,FORTRAN77 最適化プログラミング,岩波書店,1991, p. 181.

何だか複雑そうですが,よく見ると2次(x,yの掛け算の回数が最大2回)の関数を最小化するxとyを探す問題になっていますね.また制約条件として,3つの式が並んでいます.1つ目が等式(=のこと)拘束,2,3つ目が不等式(≧のこと)拘束と呼んだりします.制約を満たした状態で目的関数を最も小さくするx,yを探してこい,ということですね.とは言っても数式に慣れていない人も多そうなので、ちょっと可視化してみましょうか

テスト問題を可視化した図.一番位置の低いところを探せばクリアとなる.

a)は最小化したい目的関数がxとyの値でどのように変わるかを計算したもの,b)はaの一部を切り出したもので,等式・不等式拘束条件を考慮するとどの部分で解を探すことになるかを示したものです.3次元表示すると,坂になっている面上で一番低い場所を探す問題だとわかりますね(青が濃い場所ほど低い).また等式条件の式を見て,高校時代の記憶がある人なら「円だ!」となるかもしれません.そう,実は図b)の赤い線が示すように,半径1の円上にある解を探す問題になっているのです.もう少し言葉を変えると,赤い線で示される円の上で最も低い場所を探す,となります.なお不等式の条件は,xとyの範囲を限定しているだけですね.もちろん目で見て(というか式を直接解いて)解を探すこともできそうですが,筆者ひさかわの作ったソフトはきちんとこの問題を解くことができるのでしょうか?

最適解探索の様子

はい,無事に解くことができましたね.初期値をテキトーに与えてから解かせたものですが,計算が進むごとに赤い円上に拘束されている最適解に向かって進み,うまいこと収束していることが分かります.さらっと使いましたが,こういう状態を「収束」と呼びます.
 実は,上の例においては初期値が最適値よりも低いところに取られています.つまり,最小化という意味だけで言えば初期値の方が良い結果ということになります.ただ,今回は等式と不等式拘束を課しているので,SQPのアルゴリズムに従って解かれた問題がきちんと坂を登って最適値に落ち着くことが実現されているのですね.なお数値解法(ちょっとずつ動かして近い答えを求める)なので,ピッタリと答えが得られるわけではありません.今回の精度は100万分の1程度ですが(答えが1の場合,筆者のソフトで得られる答えは0.999999),これは計算条件の設定次第でもっと良くすることもできます.しかし,実用上は細かい桁まで合わせても無意味なことが多いです.

解かせてみた2:ネットで拾った最適化問題

 ネットからも例題を拾ってきたので,解かせてみましょう.リンクを貼っておきます:

https://appmath.tus.ac.jp/cms/wp-content/uploads/2023/01/No.4_胡コラム.pdf,accessed on Jan. 9, 2025

目的関数           : 160x + 80y(最大化)
不等式拘束条件: 20x+15y≦3000
                              6x+10y≦1200
                              x≧0,y≧0

胡艶楠,身近な問題を解く!組合せ最適化! Chap. 2 線形計画問題と整数計画問題, accessed on Jan 9, 2025.

対象が一次関数なので,筆者のソフトにとってはこっちの方が素性が悪い可能性があります.問題としては先ほどより単純で簡単なのですが,わざわざ二次計画法で解く必要もないという意味ですね.今回は,実行画面で確認してみましょうか.

問題の実行画面

こちらは計算終了時点の画面表示を示していますが,しっかりと解けていることが分かります.本来,線形問題は2回微分できないのでヘンなことしてるなぁと思うのですが,強引に進んでしまうようですね.ちなみに,正しい答えはx = 125,y=100/3なので,それらと比較しても精度も十分といえるでしょう.こういう作業を積み重ねて,動作に対する自信を深めていくのです.なお、答えは求められていますが問題としては1問目より解きづらいため、繰り返し計算の数は3回ほど増えてしまっています。

おわりに

  色々とエッセイ的な記事が続いてしまったので,No. 8では実際に開発中のソフトを持ち出し,その動きもお見せしながら語ってみました.すでにソフトウェアがある程度形になっているので,こういった内容がお届けできるというわけです.
プログラム学習において最初の関門は,コンパイルに通るコードが書けるようになることだと思います.ですが,ある程度コーディングの作業に慣れてくると,今度はその動作が正しいかどうかも検証の対象となってきます.自分1人で使用している分には良いのですが,結果を世に出したり,プログラムを人に譲渡したり,ましてや販売して利益を得るようなことをし出せば,避けては通れないでしょう.これを読んでくださっている方の中にはプログラミング初心者の方もおられるかと思うのですが,「コンパイル通らない地獄」から抜け出された暁には,「次は動作検証の沼だな」という心算をしておいてくださると良いと思います

Kengo HISAKAWA


いいなと思ったら応援しよう!