物事がどのように計算されるかを見たい場合pdb
は、確かに1つの解決策です。
ただし、 のようなデバッガーpdb
では、何が起こっているのかを大まかに把握するのは困難です。これが、コードを読むことも役立つ理由です。たとえば、何が機能するかを知りたい場合は、存在するa+b
かどうかを確認できます。存在する場合type(a).__add__
は追加を処理するためです。これは、不確実性パッケージの場合です。
と__add__
はいえ、具体的にコード化されるのではなく、一般的なメカニズムを介して不確実性の中で実際に実装されているため、その実装の背後にあるアイデアをお伝えできます。これが最終的に探しているように見えるからです。
あなたの例でa
は、b
オブジェクトはVariable
次のとおりです。
>>> from uncertainties import ufloat
>>> a = ufloat(1, 3)
>>> b = ufloat(2, 4)
>>> type(a)
<class 'uncertainties.Variable'>
次にc = a + b
は実際には と の線形関数でa
ありb
、 と に関する導関数で表されa
ますb
。
>>> c = a + b
>>> type(c)
<class 'uncertainties.AffineScalarFunc'>
>>> c.derivatives
{1.0+/-3.0: 1.0, 2.0+/-4.0: 1.0}
変数に関する関数の導関数がわかっている場合、変数の標準偏差から標準偏差の近似値を簡単に取得できます。
したがって、不確実性パッケージの実装の背後にある主なアイデアは、値が次のいずれかであるということです。
- x = 3.14±0.0.1 や y = 0±0.01 (
Variable
オブジェクト) などの確率変数。標準偏差で表されます。
- または関数の線形近似 (
AffineScalarFunc
オブジェクト: 線形であるため「affine」、値が実数であるため「scalar」、関数であるため「func」)。
より複雑な例を挙げると、z = 2*x+sin(y) は (x, y) = (3.14, 0) で 2*x + y として近似されます。実装では、近似は線形であるため、変数に関する導関数のみが保存されます。
>>> x = ufloat(3.14, 0.01)
>>> y = ufloat(0, 0.01)
>>> from uncertainties.umath import sin
>>> z = 2*x + sin(y)
>>> type(z)
<class 'uncertainties.AffineScalarFunc'>
>>> z.derivatives
{3.14+/-0.01: 2.0, 0.0+/-0.01: 1.0}
したがって、不確実性パッケージによって行われる主な作業は、変数を含む関数の導関数を計算することです。これは、効率的な自動微分法によって行われます。具体的には、 のようなことをするとa+b
、Python は自動的にVariable.__add__()
メソッドを呼び出します。このメソッドは、変数に関する の導関数を計算して新しい線形関数を作成a+b
します ( に関する の導関数は 1 であるため、導関数は両方とも 1a
でa
あり、同じ用b
)。より一般的には、純粋な変数ではなく関数f(a,b) + g(a,b)
を追加a
します。b
連鎖律で計算されます。これが自動微分がどのように機能するかであり、これが不確実性パッケージに実装されているものです。ここで重要な機能はuncertainties.wrap()
. これはパッケージ全体の中で最大かつ最も複雑な関数ですが、コードは大部分がコメントされており、メソッドの詳細が利用可能です。
次に、導関数は、最終関数の標準偏差を変数の標準偏差の関数として提供します (のコードAffineScalarFunc.std_dev()
は非常に単純です。より難しいタスクは、導関数を自動的に計算することです)。