Under suspicion that BunchTwissAnalysis::computeBunchMoments is not working correctly, I produced some tests to compare the results of BunchTwissAnalysis to bunch statistics computed via numpy, the results of which are outlined below:
tests/py/orbit/bunch_utils/test_bunch_twiss_analysis.py:322: AssertionError
=================================================== short test summary info ===================================================
FAILED tests/py/orbit/bunch_utils/test_bunch_twiss_analysis.py::test_computeBunchMoments_order2_M02 - assert 0.0 == 3.19521707947...e-08 ± 1.0e-15
FAILED tests/py/orbit/bunch_utils/test_bunch_twiss_analysis.py::test_computeBunchMoments_order2_emitnorm_M02 - assert 0.0 == 1.0007118962927186 ± 1.0e-15
FAILED tests/py/orbit/bunch_utils/test_bunch_twiss_analysis.py::test_computeBunchMoments_order2_dispersion_flag - assert 9.331838635982362e-08 == 9.33183590281...e-08 ± 1.0e-15
FAILED tests/py/orbit/bunch_utils/test_bunch_twiss_analysis.py::test_computeBunchMoments_order3_M03 - assert 0.0 == 4.13235583192...e-13 ± 1.0e-15
FAILED tests/py/orbit/bunch_utils/test_bunch_twiss_analysis.py::test_computeBunchMoments_macrosize_order2_M00_M10_M01 - assert -7.968925040526781e-05 == -0.0002851747...7959 ± 1.0e-15
FAILED tests/py/orbit/bunch_utils/test_bunch_twiss_analysis.py::test_computeBunchMoments_macrosize_order2_M20 - assert 9.368853867319752e-08 == 8.91593940978...e-08 ± 1.0e-12
================================================ 6 failed, 24 passed in 0.16s =================================================
Moments Formula
The principle issue is that, for a 2D distribution, the central moments of overall order $j+k$, weighted by the size of the $i^{th}$ macroparticle $w_i$, are given by:
$$\mu_{xy}^{jk} = \frac{1}{N}\sum_i w_i(x_i-\hat{x})^j(y_i-\hat{y})^k,$$
where $N = \sum_i w_i$.
Manually evaluating some of the low order terms yields the following:
| j |
k |
$\mu_{xy}$ |
| 0 |
0 |
1 |
| 1 |
0 |
$\frac{1}{N} \sum_i w_i (x_i-\hat{x})$ = 0 |
| 0 |
1 |
$\frac{1}{N} \sum_i w_i (y_i-\hat{y})$ = 0 |
| 1 |
1 |
$\frac{1}{N} \sum_i w_i (x_i-\hat{x})(y_i-\hat{y})$ = Cov(x,y) |
| 2 |
0 |
$\frac{1}{N} \sum_i w_i (x_i-\hat{x})^2$ = Var(x) |
| 0 |
2 |
$\frac{1}{N} \sum_i w_i (y_i-\hat{y})^2$ = Var(y) |
Instead, these lines:
|
for(i = 0; i < _order; i++) |
|
momX[i+1] = momX[i]*m_size*((part_coord_arr[ip][0] - dispterm) - xAvg); |
|
|
|
for(i = 0; i< _order; i++) |
|
momY[i+1] = momY[i]*m_size*(part_coord_arr[ip][2] - yAvg); |
|
|
|
for(j = 0; j<_order; j++) |
|
for(i=0 ; i< _order+1-j; i++){ |
|
momentXY[i][j] += momX[i]/pow(xbetaterm, double(i)) * momY[j]/pow(ybetaterm, double(j)); |
|
momentXY[i][j] += momX[i] * momY[j]; |
|
} |
|
} |
are essentially calculating
$$
\mu_{xy}^{jk} = \frac{1}{N}\sum_i w_i^{j+k}(x_i-\hat{x})^j(y_i-\hat{y})^k\left(1+\frac{1}{\beta_x^j\beta_y^k}\right),
$$
which, evaluates to:
| j |
k |
$\mu_{xy}$ |
| 0 |
0 |
$\frac{1}{N} \sum_i 2 = \frac{2i}{N}$ |
| 1 |
0 |
$\frac{1}{N} \sum_i w_i (x_i-\hat{x})\left(1 + \frac{1}{\beta_x}\right)$ |
| 1 |
1 |
$\frac{1}{N} \sum_i w_i^2 (x_i-\hat{x})(y_i-\hat{y}) \left(1 + \frac{1}{\beta_x \beta_y}\right)$ |
and so forth.
This mixing of normalized and raw terms leads to tests on $i>0$, $j=0$ to fail:
tests/py/orbit/bunch_utils/test_bunch_twiss_analysis.py::test_computeBunchMoments_macrosize_order2_M00_M10_M01 - assert -7.968925040526781e-05 == -0.0002851747...7959 ± 1.0e-15
FAILED tests/py/orbit/bunch_utils/test_bunch_twiss_analysis.py::test_computeBunchMoments_macrosize_order2_M20 - assert 9.368853867319752e-08 == 8.91593940978...e-08 ± 1.0e-12
Tests for $i=0$, $j>0$, on the other hand, fail because the outer loop is missing a _order+1, so the element at the last index is always 0
FAILED tests/py/orbit/bunch_utils/test_bunch_twiss_analysis.py::test_computeBunchMoments_order3_M03 - assert 0.0 == 4.13235583192...e-13 ± 1.0e-15
|
for(j = 0; j<_order; j++) |
The branch where macrosize is not set has a bug due to missing braces, but I'm not sure this branch is ever used in-practice.
|
for(j = 0; j<_order; j++) |
|
for(i=0 ; i< _order+1-j; i++) |
|
momentXY[i][j] += momX[i]/pow(xbetaterm, double(i)) * momY[j]/pow(ybetaterm, double(j)); |
|
momentXY[i][j] += momX[i] * momY[j]; |
dispersionflag doesn't do anything
The dispersionflag branch is inaccessible because dispterm is explicitly initialized to 0 here:
and is never modified, so the argument to the if-statement on L167 is always false.
Performance Implications
Lastly, analyzeBunch correctly computes means/covariances. computeBunchMoments itself calls analyzeBunch, which essentially triggers a double loop over the entire bunch every time this method is called. For the time being, users should prefer to call analyzeBunch directly rather than computeBunchMoments for computing the covariance matrix, averages, etc.
Under suspicion that
BunchTwissAnalysis::computeBunchMomentsis not working correctly, I produced some tests to compare the results ofBunchTwissAnalysisto bunch statistics computed via numpy, the results of which are outlined below:Moments Formula
The principle issue is that, for a 2D distribution, the central moments of overall order$j+k$ , weighted by the size of the $i^{th}$ macroparticle $w_i$ , are given by:
where$N = \sum_i w_i$ .
Manually evaluating some of the low order terms yields the following:
Instead, these lines:
PyORBIT3/src/orbit/BunchDiagnostics/BunchTwissAnalysis.cc
Lines 250 to 261 in 51f0568
are essentially calculating
which, evaluates to:
and so forth.
This mixing of normalized and raw terms leads to tests on$i>0$ , $j=0$ to fail:
Tests for$i=0$ , $j>0$ , on the other hand, fail because the outer loop is missing a
_order+1, so the element at the last index is always0PyORBIT3/src/orbit/BunchDiagnostics/BunchTwissAnalysis.cc
Line 256 in 51f0568
The branch where
macrosizeis not set has a bug due to missing braces, but I'm not sure this branch is ever used in-practice.PyORBIT3/src/orbit/BunchDiagnostics/BunchTwissAnalysis.cc
Lines 286 to 289 in 51f0568
dispersionflagdoesn't do anythingThe
dispersionflagbranch is inaccessible becausedisptermis explicitly initialized to0here:PyORBIT3/src/orbit/BunchDiagnostics/BunchTwissAnalysis.cc
Line 153 in 51f0568
and is never modified, so the argument to the if-statement on L167 is always
false.Performance Implications
Lastly,
analyzeBunchcorrectly computes means/covariances.computeBunchMomentsitself callsanalyzeBunch, which essentially triggers a double loop over the entire bunch every time this method is called. For the time being, users should prefer to callanalyzeBunchdirectly rather thancomputeBunchMomentsfor computing the covariance matrix, averages, etc.