Skip to content

Commit 8c798d6

Browse files
authored
Merge pull request #178 from decargroup/bugfix/175-fix-inverse-fourier-transform-in-parameters-and-docs-of-rff-lifting-functions
Fix inverse Fourier transform naming
2 parents 21073ad + 030c41f commit 8c798d6

File tree

9 files changed

+72
-169
lines changed

9 files changed

+72
-169
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Test package (no MOSEK)
2+
on:
3+
push:
4+
branches: [main]
5+
pull_request:
6+
branches: [main]
7+
jobs:
8+
build:
9+
runs-on: ubuntu-latest
10+
strategy:
11+
fail-fast: false
12+
matrix:
13+
python-version: ['3.8', '3.9', '3.10', '3.11']
14+
steps:
15+
- uses: actions/checkout@v2
16+
- name: Set up Python ${{matrix.python-version}}
17+
uses: actions/setup-python@v2
18+
with:
19+
python-version: ${{matrix.python-version}}
20+
- name: Install dependencies
21+
run: |
22+
python -m pip install --upgrade pip
23+
python -m pip install -r requirements-dev.txt
24+
- name: Test with pytest
25+
run: |
26+
pytest --exitfirst -k "not mosek"

.github/workflows/test-package.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
strategy:
1111
fail-fast: false
1212
matrix:
13-
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
13+
python-version: ['3.12']
1414
steps:
1515
- uses: actions/checkout@v2
1616
- name: Set up Python ${{matrix.python-version}}

CITATION.cff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ authors:
1010
orcid: "https://orcid.org/0000-0002-1987-9268"
1111
affiliation: "McGill University"
1212
title: "decargroup/pykoop"
13-
version: v1.2.4
13+
version: v2.0.0
1414
doi: 10.5281/zenodo.5576490
1515
url: "https://github.com/decargroup/pykoop"

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ If you use this software in your research, please cite it as below or see
221221
url={https://github.com/decargroup/pykoop},
222222
publisher={Zenodo},
223223
author={Steven Dahdah and James Richard Forbes},
224-
version = {{v1.2.4}},
224+
version = {{v2.0.0}},
225225
year={2022},
226226
}
227227

pykoop/kernel_approximation.py

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,8 @@ class RandomFourierKernelApprox(KernelApproximation):
9393
n_features_out_ : int
9494
Number of features output. This attribute is not available in
9595
estimators from :mod:`sklearn.kernel_approximation`.
96-
ift_ : scipy.stats.rv_continuous
97-
Probability distribution corresponding to inverse Fourier transform of
98-
chosen kernel.
96+
ft_ : scipy.stats.rv_continuous
97+
Probability distribution corresponding to Fourier transform of chosen kernel.
9998
random_weights_ : np.ndarray, shape (n_features, n_components)
10099
Random weights to inner-product with features.
101100
random_offsets_ : np.ndarray, shape (n_features, )
@@ -106,7 +105,7 @@ class RandomFourierKernelApprox(KernelApproximation):
106105
Generate random Fourier features from a Gaussian kernel
107106
108107
>>> ka = pykoop.RandomFourierKernelApprox(
109-
... kernel_or_ift='gaussian',
108+
... kernel_or_ft='gaussian',
110109
... n_components=10,
111110
... shape=1,
112111
... random_state=1234,
@@ -117,20 +116,20 @@ class RandomFourierKernelApprox(KernelApproximation):
117116
array([...])
118117
"""
119118

120-
_ift_lookup = {
119+
_ft_lookup = {
121120
'gaussian': scipy.stats.norm,
122121
'laplacian': scipy.stats.cauchy,
123122
'cauchy': scipy.stats.laplace,
124123
}
125-
"""Lookup table for inverse Fourier transform of kernel.
124+
"""Lookup table for Fourier transform of kernel.
126125
127126
Laplacian and Cauchy being swapped is not a typo. They are Fourier
128127
transforms of each other.
129128
"""
130129

131130
def __init__(
132131
self,
133-
kernel_or_ift: Union[str, scipy.stats.rv_continuous] = 'gaussian',
132+
kernel_or_ft: Union[str, scipy.stats.rv_continuous] = 'gaussian',
134133
n_components: int = 100,
135134
shape: float = 1,
136135
method: str = 'weight_offset',
@@ -140,19 +139,19 @@ def __init__(
140139
141140
Parameters
142141
----------
143-
kernel_or_ift : Union[str, scipy.stats.rv_continuous]
142+
kernel_or_ft : Union[str, scipy.stats.rv_continuous]
144143
Kernel to approximate. Possible options are
145144
146-
- ``'gaussian'`` -- Gaussian kernel, with inverse Fourier
147-
transform :class:`scipy.stats.norm` (default),
148-
- ``'laplacian'`` -- Laplacian kernel, with inverse Fourier
149-
transform :class:`scipy.stats.cauchy`, or
150-
- ``'cauchy'`` -- Cauchy kernel, with inverse Fourier transform
151-
:class:`scipy.stats.laplace`.
145+
- ``'gaussian'`` -- Gaussian kernel, with Fourier transform
146+
:class:`scipy.stats.norm` (default),
147+
- ``'laplacian'`` -- Laplacian kernel, with Fourier transform
148+
:class:`scipy.stats.cauchy`, or
149+
- ``'cauchy'`` -- Cauchy kernel, with Fourier transform
150+
:class:`scipy.stats.laplace`.
152151
153152
Alternatively, a positive, shift-invariant kernel can be implicitly
154-
specified by providing its inverse Fourier transform as a
155-
univariate probability distribution subclassing
153+
specified by providing its Fourier transform as a univariate
154+
probability distribution subclassing
156155
:class:`scipy.stats.rv_continuous`.
157156
158157
n_components : int
@@ -179,7 +178,7 @@ def __init__(
179178
random_state : Union[int, np.random.RandomState, None]
180179
Random seed.
181180
"""
182-
self.kernel_or_ift = kernel_or_ift
181+
self.kernel_or_ft = kernel_or_ft
183182
self.n_components = n_components
184183
self.shape = shape
185184
self.method = method
@@ -210,11 +209,11 @@ def fit(
210209
If any of the constructor parameters are incorrect.
211210
"""
212211
X = sklearn.utils.validation.check_array(X)
213-
# Set inverse Fourier transform
214-
if isinstance(self.kernel_or_ift, str):
215-
self.ift_ = self._ift_lookup[self.kernel_or_ift]
212+
# Set Fourier transform
213+
if isinstance(self.kernel_or_ft, str):
214+
self.ft_ = self._ft_lookup[self.kernel_or_ft]
216215
else:
217-
self.ift_ = self.kernel_or_ift
216+
self.ft_ = self.kernel_or_ft
218217
# Validate input
219218
if self.n_components <= 0:
220219
raise ValueError('`n_components` must be positive.')
@@ -230,7 +229,7 @@ def fit(
230229
else:
231230
self.n_features_out_ = self.n_components
232231
# Generate random weights
233-
self.random_weights_ = self.ift_.rvs(
232+
self.random_weights_ = self.ft_.rvs(
234233
scale=1,
235234
size=(self.n_features_in_, self.n_components),
236235
random_state=self.random_state,
@@ -249,7 +248,7 @@ def fit(
249248
# Easiest way to make sure distribution is univariate is to check the
250249
# dimension of the output.
251250
if self.random_weights_.ndim != 2:
252-
raise ValueError('`kernel_or_ift` must specify a univariate '
251+
raise ValueError('`kernel_or_ft` must specify a univariate '
253252
'probability distribution.')
254253
return self
255254

pykoop/koopman_pipeline.py

Lines changed: 0 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import sklearn.base
1010
import sklearn.exceptions
1111
import sklearn.metrics
12-
from deprecated import deprecated
1312
from matplotlib import pyplot as plt
1413
from scipy import linalg
1514

@@ -2410,88 +2409,6 @@ def score(self, X: np.ndarray, y: Optional[np.ndarray] = None) -> float:
24102409
score = scorer(self, X, None)
24112410
return score
24122411

2413-
@deprecated('Use `predict_trajectory` instead')
2414-
def predict_multistep(self, X: np.ndarray) -> np.ndarray:
2415-
"""Perform a multi-step prediction for the first state of each episode.
2416-
2417-
This function takes the first ``min_samples_`` states of the input,
2418-
along with all of its inputs, and predicts the next ``X.shape[0]``
2419-
states of the system. This action is performed on a per-episode basis.
2420-
The state features of ``X`` (other than the first ``min_samples_``
2421-
features) are not used at all.
2422-
2423-
If prediction fails numerically, missing predictions are filled with
2424-
``np.nan``.
2425-
2426-
Parameters
2427-
----------
2428-
X : np.ndarray
2429-
Data matrix.
2430-
2431-
Returns
2432-
-------
2433-
np.ndarray
2434-
Predicted data matrix.
2435-
2436-
Raises
2437-
------
2438-
ValueError
2439-
If an episode is shorter than ``min_samples_``.
2440-
2441-
Warning
2442-
-------
2443-
Deprecated in favour of
2444-
:func:`pykoop.KoopmanPipeline.predict_trajectory`.
2445-
2446-
"""
2447-
sklearn.utils.validation.check_is_fitted(self, 'regressor_fit_')
2448-
X = sklearn.utils.validation.check_array(X, **self._check_array_params)
2449-
# Split episodes
2450-
episodes = split_episodes(X, episode_feature=self.episode_feature_)
2451-
# Loop over episodes
2452-
predictions = []
2453-
for (i, X_i) in episodes:
2454-
# Check length of episode.
2455-
if X_i.shape[0] < self.min_samples_:
2456-
raise ValueError(f'Episode {i} has {X_i.shape[0]} samples but '
2457-
f'`min_samples_`={self.min_samples_} samples '
2458-
'are required.')
2459-
# Index where prediction blows up (if it does)
2460-
crash_index = None
2461-
# Extract initial state and input
2462-
x0 = X_i[:self.min_samples_, :self.n_states_in_]
2463-
u = X_i[:, self.n_states_in_:]
2464-
# Create array to hold predicted states
2465-
X_pred_i = np.zeros((X_i.shape[0], self.n_states_in_))
2466-
# Set the initial condition
2467-
X_pred_i[:self.min_samples_, :] = x0
2468-
# Predict all time steps
2469-
for k in range(self.min_samples_, X_i.shape[0]):
2470-
# Stack episode feature, previous predictions, and input
2471-
X_ik = combine_episodes(
2472-
[(i,
2473-
np.hstack((
2474-
X_pred_i[(k - self.min_samples_):k, :],
2475-
X_i[(k - self.min_samples_):k, self.n_states_in_:],
2476-
)))],
2477-
episode_feature=self.episode_feature_)
2478-
# Predict next step
2479-
try:
2480-
X_pred_ik = self.predict(X_ik)[[-1], :]
2481-
except ValueError:
2482-
crash_index = k
2483-
break
2484-
# Extract data matrix from prediction
2485-
X_pred_i[[k], :] = split_episodes(
2486-
X_pred_ik, episode_feature=self.episode_feature_)[0][1]
2487-
if crash_index is not None:
2488-
X_pred_i[crash_index:, :] = np.nan
2489-
predictions.append((i, X_pred_i))
2490-
# Combine episodes
2491-
X_p = combine_episodes(predictions,
2492-
episode_feature=self.episode_feature_)
2493-
return X_p
2494-
24952412
def predict_trajectory(
24962413
self,
24972414
X0_or_X: np.ndarray,

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
setuptools.setup(
99
name='pykoop',
10-
version='1.2.4',
10+
version='2.0.0',
1111
description=('Koopman operator identification library in Python, '
1212
'compatible with `scikit-learn`'),
1313
long_description=readme,

0 commit comments

Comments
 (0)