はじめに (対象読者・この記事でわかること)

この記事は、Swiftで数値計算やアルゴリズム実装に挑戦したいプログラミング中級者以上の方を対象としています。
特に「3点の座標から二次関数(y = ax² + bx + c)を導き出す」問題に興味がある方や、線形代数の基礎であるガウス・ジョルダン法を実際のコードで体感したい方に最適です。

本記事を読むことで、以下のことができるようになります。

  • 3点の (x, y) 座標から係数 a, b, c を求める数式を導出する理論的背景が理解できる。
  • Swift で行列操作とガウス・ジョルダン法を実装し、実際に二次関数を計算できる。
  • 実装上の落とし穴(特異行列や数値誤差)とその対策方法を把握できる。

本テーマは、機械学習前処理やグラフ描画、ゲームの物理計算など、座標データを解析する多くの場面で応用可能です。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。

  • Swift の基本的な文法と関数定義、配列操作。
  • 行列やベクトルといった線形代数の概念(特に行基本変形)。
  • Xcode でのプロジェクト作成とコンパイル手順。

ガウス・ジョルダン法による二次関数係数算出の概要

二次関数 y = ax² + bx + c は、3 つの未知数 a, b, c を持ちます。
任意の 3 点 (x₁, y₁), (x₂, y₂), (x₃, y₃) が与えられれば、次の連立一次方程式が成立します。

x₁²·a + x₁·b + c = y₁
x₂²·a + x₂·b + c = y₂
x₃²·a + x₃·b + c = y₃

この連立方程式を行列形式で表すと A·p = y となります。

  • A は 3×3 の係数行列
  • p = [a, b, c]ᵀ は未知ベクトル
  • y = [y₁, y₂, y₃]ᵀ は定数ベクトル

ガウス・ジョルダン法は、行列 A を単位行列に変形しながら同時にベクトル y も変形することで、p を直接求める手法です。今回は 拡大係数行列([A | y])に対してピボット選択と行基本変形を行い、最終的に右側の列が求める係数となります。

Swift での実装手順

以下では、Swift 5.7 以降で動作するシンプルな実装例を示します。ポイントは「可読性を保ちつつ、行列操作を汎用的に行える」ことです。

Step 1: 基本的な行列型とユーティリティの定義

Swift
import Foundation /// 2 次元配列で表す行列型(Double のみ対象) struct Matrix { var rows: Int var cols: Int private var data: [[Double]] init(_ data: [[Double]]) { self.rows = data.count self.cols = data.first?.count ?? 0 self.data = data } subscript(row: Int, col: Int) -> Double { get { data[row][col] } set { data[row][col] = newValue } } /// 行交換 mutating func swapRows(_ i: Int, _ j: Int) { guard i != j else { return } data.swapAt(i, j) } /// 行に定数倍を加える(row_i = row_i + factor * row_j) mutating func addRow(_ target: Int, _ source: Int, factor: Double) { for col in 0..<cols { data[target][col] += factor * data[source][col] } } /// 行を定数で割る(row_i = row_i / divisor) mutating func scaleRow(_ row: Int, divisor: Double) { for col in 0..<cols { data[row][col] /= divisor } } /// デバッグ用に文字列表現 func description() -> String { data.map { $0.map { String(format: "% .4f", $0) }.joined(separator: "\t") } .joined(separator: "\n") } }

解説
- Matrix は 2 次元配列をラップし、行列サイズと要素アクセスのインターフェイスを提供します。
- swapRows, addRow, scaleRow はガウス・ジョルダン法で頻繁に用いる基本変形です。
- description() はデバッグ時に現在の行列を見やすく出力できます。

Step 2: ガウス・ジョルダン法本体

Swift
/// 拡大係数行列 (A | y) に対してガウス・ジョルダン法を適用し、解ベクトルを取得する /// - Parameter augmented: 3×4 の拡大行列 /// - Returns: 係数 (a, b, c) の配列、失敗したら nil func gaussJordan(_ augmented: Matrix) -> [Double]? { var mat = augmented let n = mat.rows // 3 // 前進消去(ピボット選択+列の正規化) for pivot in 0..<n { // ピボットが 0 になる場合は下の行と入れ替え if abs(mat[pivot, pivot]) < 1e-12 { var swapped = false for row in (pivot+1)..<n { if abs(mat[row, pivot]) > 1e-12 { mat.swapRows(pivot, row) swapped = true break } } if !swapped { return nil } // 特異行列(解無し) } // ピボット列を 1 に正規化 let pivotVal = mat[pivot, pivot] mat.scaleRow(pivot, divisor: pivotVal) // 他の行からピボット列を除去 for row in 0..<n where row != pivot { let factor = -mat[row, pivot] mat.addRow(row, pivot, factor: factor) } } // 右端の列が解になる(a, b, c) return [mat[0, 3], mat[1, 3], mat[2, 3]] }

ポイント

  • ピボットが 0 に近い場合は下の行と入れ替えて特異行列を回避します。
  • scaleRow でピボットを 1 に正規化し、addRow で他行からピボット列を消します。
  • 3 行 3 列の小規模行列でも、この汎用的な実装は拡張が容易です。

Step 3: ラッパー関数で二次関数係数を取得

Swift
/// 3点の座標から二次関数の係数 (a, b, c) を計算 /// - Parameter points: [(x: Double, y: Double)] の配列 (必ず 3 要素) /// - Returns: (a, b, c) のタプル、計算できなければ nil func quadraticCoefficients(from points: [(x: Double, y: Double)]) -> (a: Double, b: Double, c: Double)? { guard points.count == 3 else { return nil } // 拡大係数行列を作成 var rows: [[Double]] = [] for p in points { rows.append([p.x * p.x, p.x, 1.0, p.y]) } let aug = Matrix(rows) // ガウス・ジョルダンで解く guard let coeff = gaussJordan(aug) else { return nil } return (a: coeff[0], b: coeff[1], c: coeff[2]) }

使用例

Swift
let points = [(x: 1.0, y: 6.0), (x: 2.0, y: 11.0), (x: 3.0, y: 18.0)] if let result = quadraticCoefficients(from: points) { print("求めた二次関数: y = \(result.a)·x² + \(result.b)·x + \(result.c)") // 出力例: 求めた二次関数: y = 1.0·x² + 2.0·x + 3.0 } else { print("係数が求められませんでした。入力が線形従属か確認してください。") }

ハマった点やエラー解決

発生した問題 原因 解決策
Fatal error: Index out of range 行列サイズが 3×4 ではなく 3×3 になっていた 拡大行列作成時に定数項 y を 4 列目に正しく追加。
解が nil になる 3 点が同一直線上にある(行列が特異) 入力点が必ず非共線であることを事前チェックし、エラーメッセージで警告。
小数点誤差でピボットが極端に小さい 浮動小数点演算の限界 ピボット選択時に絶対値が閾値 (1e-12) 以下なら行交換を行う。
コードが冗長に見える 行列操作を手続き的に書いたため Matrix 構造体に演算メソッドをまとめ、再利用性を向上させた。

解決策の詳細

  1. 行列サイズの確認
    - Matrix 初期化時に rowscols を自動取得させ、augmented が 3×4 であることを assert で検証すると、開発中に即座にバグが捕捉できます。

  2. 特異行列対策
    - ピボット選択で行交換を行うだけでなく、部分ピボット(列の最大絶対値を持つ行を選ぶ)にすると、数値的安定性が向上します。実装は for row in pivot..<n のループで最大値検索を追加すれば簡単です。

  3. テストの自動化
    - XCTest で数組の既知解を持つ点集合をテストケースにし、XCTAssertEquala, b, c が期待値と一致するか確認すると、変更時のリグレッション防止に有効です。

まとめ

本記事では、3 点の座標から二次関数の係数 (a, b, c) をガウス・ジョルダン法で求める Swift 実装 を段階的に解説しました。

  • ポイント1: 行列操作を抽象化した Matrix 構造体でコードをシンプルに保ちつつ、行基本変形を実装。
  • ポイント2: ピボットが 0 に近い場合の行交換や数値誤差対策を組み込み、特異行列に対処。
  • ポイント3: quadraticCoefficients ラッパーで実務的に使える API を提供し、サンプルコードで実際の出力例を示した。

これにより、読者は 「座標データから即座に二次関数を導出できる」 技術を身につけ、グラフ描画やシミュレーション、簡易回帰分析など多様な場面で活用できるようになります。次回は 「任意点数に対する最小二乗法による高次多項式フィッティング」 について解説する予定です。

参考資料