VOOZH about

URL: https://qiita.com/ikuzak/items/47b7a8a88aba5ec6f1ca

⇱ 誤差伝播の計算 #Python3 - Qiita


👁 Image
6

Go to list of users who liked

3

Share on X(Twitter)

Share on Facebook

Add to Hatena Bookmark

More than 5 years have passed since last update.

@ikuzak

誤差伝播の計算

6
Posted at

はじめに

実験結果の処理などで計算をしていると,誤差を考慮しなければならないことが多々あります。
誤差を含む値を使用して計算した関数値の誤差は
$$e=\sqrt{\sum_i^ne_i^2\left(\frac{\partial f}{\partial x_i}\right)^2}$$
となります(ここでこの式の導出をすることはしません)。

ご覧の通り,これを計算するのは面倒です。
割り算でも偏微分が面倒なのに,指数関数とか対数関数が出てきたらとんでもないことになります。
そこで,「退屈なことをPythonにやらせる」ことにしました。

実行環境

  • Windows 10
  • Python 3.8.3

f-stringを利用しているので3.6以降で動作します。

ライブラリ

一応numpyを使っていますが,実は必要ないです。
numpy.sqrtしか使っていませんが,これはx**(1/2)で代替可能です。
なぜわざわざimportしたかと言えば,科学計算でよく出てくる指数関数・対数関数を使えるようにするためです。
copyは標準で入っていると思います。

ソースコード

import numpy
from copy import copy
from math import inf


def calc_eps():
 a = 4 / 3
 b = a - 1
 c = b + b + b
 return 1 - c

EPS = calc_eps()


def eval_func(func, beta):
 if callable(func):
 return func(beta)

 for i, b in enumerate(beta):
 func = func.replace(f'${{{i+1}}}', str(b))
 return eval(func)


def diff(func, beta, index):
 last_d = 0.0
 d = inf
 err = inf
 last_e = inf
 eps = 1
 step = 1.1 # Step for eps
 v = eval_func(func, beta)

 while abs(last_d - d) > EPS:
 if eps <= EPS:
 break
 beta2 = copy(beta)
 beta2[index] += eps
 tmp = eval_func(func, beta2) - v
 if tmp == 0.0:
 break
 err = abs((last_d - d) / (step - 1)) # Estimate error
 if err > last_e:
 break
 last_e = err
 last_d = d
 d = tmp / eps
 eps /= step
 return last_d


def parse_data(data, err=None):
 beta = [x[0] for x in data] if err is None else data
 err = [x[-1] for x in data] if err is None else err
 return beta, err


def calc_error(func, beta, err):
 E = 0.0
 for i, e in enumerate(err):
 E += (diff(func, beta, i)**2) * (e**2)
 return numpy.sqrt(E)


def calc_with_error(func, data, err=None):
 beta, err = parse_data(data, err)
 return eval_func(func, beta), calc_error(func, beta, err)


def get_digit(value):
 return int(numpy.log10(value) // 1)


def separate_exp(value):
 d = get_digit(value)
 m = value / (10**d)
 return m, d


def roundat(val, digit):
 d = get_digit(val)
 sgn = val / numpy.absolute(val)
 coeff = 10 ** (d - digit)
 val /= coeff
 val = int(val + 0.5 * sgn) * coeff
 return val


def format_value(val, err, show_sign=False):
 if val < 0:
 val = -val
 sgn = '-'
 else:
 sgn = '' if not show_sign else '+'

 s_err, d_err = separate_exp(err)
 s_err = int(round(s_err))
 if s_err >= 10:
 s_err //= 10
 d_err += 1
 d_val = get_digit(val)

 d = d_val - d_err

 s_val = roundat(val, d)
 m = f'{s_val:e}'.split('e')[0] + '0' * d
 width = 1 if d <= 0 else 2 + d
 m = m[:width]

 err = str(s_err)

 if not d_val >= d_err:
 d_val = d_err

 return f'{sgn}{m}({err})E{d_val}'


def main():
 f = '${1} * ${2} / ${3}'
 data = [(25.4, 0.5), (1.56, 0.01), (13.5, 0.2)]

 value, error = calc_with_error(f, data)

 print(f'Value: {value}')
 print(f'Error: {error}')
 print(format_value(value, error))


if __name__ == '__main__':
 main()

解説

ダラダラと長いので畳んでおきます。

実行例

上記のコードで示した25.4(5)×1.56(1)/13.5(2)では

Value: 2.9351111111111114
Error: 0.07471981498926973
2.94(7)E0

と表示されます。

検証

実際に手計算で頑張ると,
$$e=\sqrt{\left(\frac{1.56}{13.5}\right)^2\times0.5^2+\left(\frac{25.4}{13.5}\right)^2\times0.01^2+\left(-\frac{25.4\times1.56}{13.5^2}\right)^2\times0.2^2}=0.0747\cdots$$
となり,Pythonでも正確に計算できていることが確認できました。

最後に

ゴリゴリと力業でなんとかしている部分が多くなってしまったので,よりよい書き方等がありましたら教えていただければ幸いです。

6

Go to list of users who liked

3
0

Go to list of comments

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
6

Go to list of users who liked

3