Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

performance optimize _generate_fourier_series_np #516

Merged
merged 8 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -147,11 +147,12 @@ Harmonic model
- de-duplicate the expression of months in their harmonic form (`#415 <https://github.com/MESMER-group/mesmer/pull/415>`_)
move creation of the month array to the deepest level (`#487 <https://github.com/MESMER-group/mesmer/pull/487>`_).
- fix indexing of harmonic model coefficients (`#415 <https://github.com/MESMER-group/mesmer/pull/415>`_)
- Refactor variable names, small code improvements, fixes and clean docstring
- Refactor variable names, small code improvements, optimization, fixes and clean docstring
(`#415 <https://github.com/MESMER-group/mesmer/pull/415>`_,
`#424 <https://github.com/MESMER-group/mesmer/pull/424>`_,
`#433 <https://github.com/MESMER-group/mesmer/pull/433>`_, and
`#512 <https://github.com/MESMER-group/mesmer/pull/512>`_)
`#433 <https://github.com/MESMER-group/mesmer/pull/433>`_,
`#512 <https://github.com/MESMER-group/mesmer/pull/512>`_, and
`#512 <https://github.com/MESMER-group/mesmer/pull/512>`_).
- add tests (
`#431 <https://github.com/MESMER-group/mesmer/pull/431>`_, and
`#458 <https://github.com/MESMER-group/mesmer/pull/458>`_)
Expand Down
47 changes: 31 additions & 16 deletions mesmer/stats/_harmonic_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,33 @@ def _generate_fourier_series_np(yearly_predictor, coeffs):
Fourier Series of order n calculated over yearly_predictor and months with coeffs.

"""
order = int(coeffs.size / 4)
n_years = yearly_predictor.size // 12
# NOTE: months from 0..11 for consistency with standard fourier series and fft
months = np.tile(np.arange(12), n_years)

seasonal_cycle = np.nansum(
[
(coeffs[idx * 4] * yearly_predictor + coeffs[idx * 4 + 1])
* np.cos(2 * np.pi * i * months / 12)
+ (coeffs[idx * 4 + 2] * yearly_predictor + coeffs[idx * 4 + 3])
* np.sin(2 * np.pi * i * months / 12)
for idx, i in enumerate(range(1, order + 1))
],
axis=0,
)
return seasonal_cycle

# NOTE: performance-optimized fourier series generation

coeffs = coeffs[~np.isnan(coeffs)]
order = coeffs.size // 4

# create 2D array of angles with shape (months, order)
# as 2 * np.pi * k * np.arange(12).reshape(-1, 1) / 12 but faster

factor = 2 * np.pi / 12
k = np.arange(1.0, order + 1)
alpha = np.arange(12 * factor, step=factor).reshape(-1, 1) * k

# combine cosine and sine into one array
cos_sin = np.empty((12, order * 2))
cos_sin[:, :order] = np.cos(alpha)
cos_sin[:, order:] = np.sin(alpha)

# sum coefficients - equivalent to np.sum(cos_sin * coeffs[0::2], axis=1)
coeff_a = cos_sin @ coeffs[0::2]
coeff_b = cos_sin @ coeffs[1::2]

# reshape yearly_predictor so the coeffs are correctly broadcast
yearly_predictor = yearly_predictor.reshape(-1, 12)
seasonal_cycle = coeff_a * yearly_predictor + coeff_b

return seasonal_cycle.flatten()


def predict_harmonic_model(yearly_predictor, coeffs, time, time_dim="time"):
Expand Down Expand Up @@ -171,6 +182,10 @@ def _calculate_bic(n_samples, order, mse):
"""

n_params = order * 4

# assume mse smaller eps is 'perfect' - only relevant for noiseless test data
mse = mse if mse > np.finfo(float).eps else 0.0
Comment on lines +186 to +187
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


return n_samples * np.log(mse) + n_params * np.log(n_samples)


Expand Down
2 changes: 1 addition & 1 deletion tests/unit/test_harmonic_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def test_generate_fourier_series_np():

result = _generate_fourier_series_np(yearly_predictor, coeffs)

np.testing.assert_equal(result, expected)
np.testing.assert_allclose(result, expected, rtol=1e-13)

coeffs = np.array([1, -2, 3.14, -1])
result = _generate_fourier_series_np(yearly_predictor, coeffs)
Expand Down
Loading