RationalFunction — 有理式

概要

有理式 (RationalFunction<T>) は 2 つの多項式の商 $f(x) = P(x)/Q(x)$ を表す型である。 分子 $P$・分母 $Q$ をそれぞれ Polynomial<T> として保持し、 構築時に既定で 最大公約因子 (GCD) を自動約分する。

  • 体としての演算 — 有理式は体をなし、四則 ($+ - \times \div$) が閉じている。
  • 微分・合成・冪 — 商の微分公式・関数合成・整数冪をサポートする。
  • 部分分数分解 — 単純極について Heaviside 法で $\sum_k A_k/(x - r_k)$ に分解する。
  • 極零相殺 — 近似 GCD により伝達関数の余分な極零対を相殺し最小実現を得る。

係数型 $T$ は doublefloat のほか多倍長 Float・有理数 Rational なども使える (部分分数分解・極零など根を扱う関数は浮動小数型が前提)。

構築

コンストラクタ意味
RationalFunction()零有理式 $0/1$
RationalFunction(const T& c)定数 $c/1$
RationalFunction(const Polynomial<T>& p)多項式を分子に $P(x)/1$
RationalFunction(const Polynomial<T>& num, const Polynomial<T>& den, bool doSimplify = true)分子・分母を指定 (既定で GCD 約分)
RationalFunction(std::initializer_list<T> num, std::initializer_list<T> den, bool doSimplify = true)係数リスト (昇べき順) から構築

パラメータ:

引数説明
num, denstd::initializer_list<T> / Polynomial<T>分子・分母。係数リストは昇べき順 ({c0, c1, c2, ...} = $c_0 + c_1 x + c_2 x^2 + \cdots$)
doSimplifybooltrue (既定) なら GCD で共通因子を約分。false で約分せず保持

約分により次数が下がる例 ($\frac{x^2 - 1}{x - 1} = x + 1$):

// (x^2 - 1) / (x - 1)  昇べき係数: {-1,0,1} / {-1,1}
RationalFunction<double> f({-1, 0, 1}, {-1, 1});
std::cout << f.toString();  // 実行結果: x+1   (共通因子 (x-1) を約分)
std::cout << f(3.0);        // 実行結果: 4

アクセサ・約分

メンバ戻り値説明
numerator()const Polynomial<T>&約分後の分子多項式
denominator()const Polynomial<T>&約分後の分母多項式
numeratorDegree()int分子の次数
denominatorDegree()int分母の次数
isZero()bool分子が零なら true
simplify()bool共通 GCD 因子を約分。約分が起きれば true
approximateSimplify(double tol = 1e-10)bool近似 GCD でほぼ共通な因子を約分 (浮動小数の極零相殺向け)
chopSmallValues(const T& eps)void(浮動小数型) eps 未満の係数を 0 にして約分

四則演算

有理式同士、有理式とスカラー $c$、有理式と多項式の間で四則が定義される (いずれも結果を自動約分)。 複合代入 (+= -= *= /=) と単項 - も用意する。

演算規則
f + g$\dfrac{P_1}{Q_1} + \dfrac{P_2}{Q_2} = \dfrac{P_1 Q_2 + P_2 Q_1}{Q_1 Q_2}$ (分母一致時は最適化)
f - g$\dfrac{P_1 Q_2 - P_2 Q_1}{Q_1 Q_2}$
f * g$\dfrac{P_1 P_2}{Q_1 Q_2}$
f / g$\dfrac{P_1 Q_2}{Q_1 P_2}$
f == g$P_1 Q_2 = P_2 Q_1$ で判定 (交差乗算)
RationalFunction<double> f({1}, {1, 1});     // 1/(1+x)
RationalFunction<double> g({0, 1}, {1, 1});  // x/(1+x)
std::cout << (f + g).toString();   // 実行結果: 1            (1/(1+x) + x/(1+x) = 1)
std::cout << (f * g).toString();   // 実行結果: x/(x^2+2x+1)
std::cout << (f / g).toString();   // 実行結果: 1/x

評価・合成

呼び出し戻り値説明
f(x) (スカラー x)U$P(x)/Q(x)$ を数値評価
f(p) (多項式 p)RationalFunction<T>多項式を代入して合成
compose(f, g)RationalFunction<T>有理式同士の合成 $f(g(x))$
RationalFunction<double> f({1}, {1, 1});     // 1/(1+x)
RationalFunction<double> g({0, 1}, {1, 1});  // x/(1+x)
auto h = compose(f, g);            // f(g(x)) = 1 / (1 + x/(1+x)) = (1+x)/(1+2x)
std::cout << h.toString();         // 実行結果: (x+1)/(2x+1)
std::cout << h(1.0);               // 実行結果: 0.666666666666667   (= 2/3)

微分・逆数・冪

メンバ戻り値説明
derivative()RationalFunction<T>商の微分: $\dfrac{d}{dx}\dfrac{P}{Q} = \dfrac{P'Q - PQ'}{Q^2}$
reciprocal()RationalFunction<T>逆数 $Q(x)/P(x)$
pow(int n)RationalFunction<T>整数冪 $(P/Q)^n$。$n < 0$ なら逆数の $|n|$ 乗
RationalFunction<double> f({1}, {1, 1});  // 1/(1+x)
std::cout << f.derivative().toString();   // 実行結果: -1/(x^2+2x+1)   (= -1/(1+x)^2)
std::cout << f.reciprocal().toString();   // 実行結果: x+1
std::cout << f.pow(2).toString();         // 実行結果: 1/(x^2+2x+1)

部分分数分解

分母が単純根 (重根なし) のとき、Heaviside 法で $f(x) = \text{quotient}(x) + \sum_k \dfrac{A_k}{x - r_k}$ に分解する。 留数は $A_k = P(r_k)/Q'(r_k)$。

結果型:

template<typename T>
struct PartialFraction {
    Complex<T> coefficient;  // 留数 A_k
    Complex<T> pole;         // 極 r_k
};

template<typename T>
struct PartialFractionExpansion {
    Polynomial<T>                  quotient;  // deg(P) >= deg(Q) のときの多項式部
    std::vector<PartialFraction<T>> terms;     // 各部分分数項
    bool                          success;   // 分解に成功したか
};
関数戻り値説明
partialFractions(f, eps, maxIter)PartialFractionExpansion<T>Heaviside 分解 (単純根のみ)。重根検出時は success = false
poles(f, eps, maxIter)std::vector<Complex<T>>極 (分母 $Q$ の根)
zeros(f, eps, maxIter)std::vector<Complex<T>>零点 (分子 $P$ の根)
residue(f, pole)Complex<T>単純極での留数 $P(\text{pole})/Q'(\text{pole})$

パラメータ:

引数説明
fconst RationalFunction<T>&分解する有理式 ($T$ は浮動小数型)
epsT重根判定・根の許容誤差 (既定 $\approx 100\,\varepsilon$)
maxItersize_t根の反復上限 (既定 1000)
// 1 / (x^2 - 1) = 1/((x-1)(x+1))  昇べき: {1} / {-1,0,1}
RationalFunction<double> f({1}, {-1, 0, 1});
auto pfe = partialFractions(f);
std::cout << pfe.success;       // 実行結果: true
std::cout << pfe.terms.size();  // 実行結果: 2
std::cout << toString(pfe);     // 実行結果: (0.5)/(x - (1)) + (-0.5)/(x - (-1))

auto ps = poles(f);            // 極 {1, -1}
// ps[0].re = 1,  ps[1].re = -1

極零相殺 (最小実現)

伝達関数を扱う際、分子・分母にほぼ等しい極零対が現れると数値的に不要な次数増加となる。 minimalRealization は近似 GCD でこれを相殺し、最小実現を返す (IIR フィルタの極零相殺など)。

関数説明
minimalRealization(tf, tol = 1e-10)有理式から極零相殺して最小実現を返す
minimalRealization(num, den, tol = 1e-10)分子・分母多項式から直接最小実現
chopSmallValues(f, eps)微小係数を 0 にして約分した有理式を返す (自由関数版)

使用例

#include <math/core/RationalFunction.hpp>
#include <math/core/RationalFunction_partialFractions.hpp>
#include <iostream>
using namespace sangi;

int main() {
    // (x^2 - 1)/(x - 1) は (x+1) に自動約分される
    RationalFunction<double> f({-1, 0, 1}, {-1, 1});
    std::cout << "f = " << f.toString() << "\n";      // f = x+1

    // 四則: 1/(1+x) + x/(1+x) = 1
    RationalFunction<double> a({1}, {1, 1}), b({0, 1}, {1, 1});
    std::cout << "a+b = " << (a + b).toString() << "\n"; // a+b = 1

    // 微分: d/dx 1/(1+x) = -1/(1+x)^2
    std::cout << "da  = " << a.derivative().toString() << "\n"; // da = -1/(x^2+2x+1)

    // 部分分数: 1/(x^2-1) = 0.5/(x-1) - 0.5/(x+1)
    RationalFunction<double> r({1}, {-1, 0, 1});
    auto pfe = partialFractions(r);
    if (pfe.success) std::cout << toString(pfe) << "\n";
    // (0.5)/(x - (1)) + (-0.5)/(x - (-1))
}

関連モジュール

有理式は多項式の上に構築され、根の計算に求根モジュールを用いる。