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

Feature/add global error to predict multistep #65

Merged
merged 16 commits into from
Aug 16, 2022
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
83 changes: 83 additions & 0 deletions .githooks/pre-commit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env bash

# Git pre-commit hook to check staged Python files for formatting issues with
# yapf.
#
# INSTALLING: Copy this script into `.git/hooks/pre-commit`, and mark it as
# executable.
#
# This requires that yapf is installed and runnable in the environment running
# the pre-commit hook.
#
# When running, this first checks for unstaged changes to staged files, and if
# there are any, it will exit with an error. Files with unstaged changes will be
# printed.
#
# If all staged files have no unstaged changes, it will run yapf against them,
# leaving the formatting changes unstaged. Changed files will be printed.
#
# BUGS: This does not leave staged changes alone when used with the -a flag to
# git commit, due to the fact that git stages ALL unstaged files when that flag
# is used.

# Find all staged Python files, and exit early if there aren't any.
PYTHON_FILES=()
while IFS=$'\n' read -r line; do PYTHON_FILES+=("$line"); done \
< <(git diff --name-only --cached --diff-filter=AM | grep --color=never '.py$')
if [ ${#PYTHON_FILES[@]} -eq 0 ]; then
exit 0
fi

########## PIP VERSION #############
# Verify that yapf is installed; if not, warn and exit.
if ! command -v yapf >/dev/null; then
echo 'yapf not on path; can not format. Please install yapf:'
echo ' pip install yapf'
exit 2
fi
######### END PIP VERSION ##########

########## PIPENV VERSION ##########
# if ! pipenv run yapf --version 2>/dev/null 2>&1; then
# echo 'yapf not on path; can not format. Please install yapf:'
# echo ' pipenv install yapf'
# exit 2
# fi
###### END PIPENV VERSION ##########


# Check for unstaged changes to files in the index.
CHANGED_FILES=()
while IFS=$'\n' read -r line; do CHANGED_FILES+=("$line"); done \
< <(git diff --name-only "${PYTHON_FILES[@]}")
if [ ${#CHANGED_FILES[@]} -gt 0 ]; then
echo 'You have unstaged changes to some files in your commit; skipping '
echo 'auto-format. Please stage, stash, or revert these changes. You may '
echo 'find `git stash -k` helpful here.'
echo 'Files with unstaged changes:' "${CHANGED_FILES[@]}"
exit 1
fi

# Format all staged files, then exit with an error code if any have uncommitted
# changes.
echo 'Formatting staged Python files . . .'

########## PIP VERSION #############
yapf -i -r "${PYTHON_FILES[@]}"
######### END PIP VERSION ##########

########## PIPENV VERSION ##########
# pipenv run yapf -i -r "${PYTHON_FILES[@]}"
###### END PIPENV VERSION ##########


CHANGED_FILES=()
while IFS=$'\n' read -r line; do CHANGED_FILES+=("$line"); done \
< <(git diff --name-only "${PYTHON_FILES[@]}")
if [ ${#CHANGED_FILES[@]} -gt 0 ]; then
echo 'Reformatted staged files. Please review and stage the changes.'
echo 'Files updated: ' "${CHANGED_FILES[@]}"
exit 1
else
exit 0
fi
12 changes: 12 additions & 0 deletions .github/workflows/check-formatting.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: Check formatting
on: [push]
jobs:
formatting-check:
name: Formatting Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: run YAPF to test if python code is correctly formatted
uses: AlexanderMelde/yapf-action@master
with:
args: --verbose
3 changes: 3 additions & 0 deletions .isort.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[isort]
multi_line_output=3
include_trailing_comma = true
25 changes: 0 additions & 25 deletions .mypy.ini
Original file line number Diff line number Diff line change
@@ -1,29 +1,4 @@
[mypy]

# https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
[mypy-sklearn.*]
ignore_missing_imports = True

[mypy-abc]
ignore_missing_imports = True

[mypy-pandas]
ignore_missing_imports = True

[mypy-scipy]
ignore_missing_imports = True

[mypy-scipy.*]
ignore_missing_imports = True

[mypy-joblib]
ignore_missing_imports = True

[mypy-picos]
ignore_missing_imports = True

[mypy-optht]
ignore_missing_imports = True

[mypy-pytest]
ignore_missing_imports = True
10 changes: 10 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ The documentation can be compiled using
$ cd doc
$ make html

If you want a hook to check source code formatting before allowing a commit,
you can use

.. code-block:: sh

$ cd .git/hooks/
$ ln -s ../../.githooks/pre-commit.sh .
$ chmod +x ./pre-commit.sh

You will need ``yapf`` installed for this.

Related packages
================
Expand Down
1 change: 1 addition & 0 deletions constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ scikit-learn>=1.0.1
PICOS>=2.2.52
pandas>=1.3.1
optht>=0.2.0
Deprecated>=1.2.13

pytest
matplotlib
Expand Down
4 changes: 1 addition & 3 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,15 @@
#
import os
import sys
sys.path.insert(0, os.path.abspath('../'))

sys.path.insert(0, os.path.abspath('../'))

# -- Project information -----------------------------------------------------

project = 'pykoop'
copyright = '2021, Steven Dahdah'
author = 'Steven Dahdah'


# -- General configuration ---------------------------------------------------

# Add any Sphinx extension module names here, as strings. They can be
Expand Down Expand Up @@ -49,7 +48,6 @@
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']


# -- Options for HTML output -------------------------------------------------

# The theme to use for HTML and HTML Help pages. See the documentation for
Expand Down
15 changes: 14 additions & 1 deletion doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ The documentation can be compiled using
$ cd doc
$ make html

If you want a hook to check source code formatting before allowing a commit,
you can use

.. code-block:: sh

$ cd .git/hooks/
$ ln -s ../../.githooks/pre-commit.sh .
$ chmod +x ./pre-commit.sh

You will need ``yapf`` installed for this.

Related packages
================
Expand Down Expand Up @@ -182,7 +192,10 @@ References
.. [bilinear] Daniel Bruder, Xun Fu, and Ram Vasudevan. "Advantages of bilinear
Koopman realizations for the modeling and control of systems with unknown
dynamics." arXiv:2010.09961v3 [cs.RO] (2020).
https://arxiv.org/abs/2010.09961v3
.. [local] Giorgos Mamakoukas, Ian Abraham, and Todd D. Murphey. "Learning
Stable Models for Prediction and Control." arXiv:2005.04291v2 [cs.RO]
(2022). https://arxiv.org/abs/2005.04291v2 https://arxiv.org/abs/2010.09961v3
.. [scorers] https://scikit-learn.org/stable/modules/model_evaluation.html

Citation
========
Expand Down
142 changes: 142 additions & 0 deletions examples/example_pipeline_vdp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
"""Example of how to use the Koopman pipeline."""

import numpy as np
from matplotlib import pyplot as plt

import pykoop
import pykoop.dynamic_models


def main() -> None:
"""Demonstrate how to use the Koopman pipeline."""
# Get sample data
X_vdp = pykoop.example_data_vdp()

# Create pipeline
kp = pykoop.KoopmanPipeline(
lifting_functions=[(
'sp',
pykoop.SplitPipeline(
lifting_functions_state=[
('pl', pykoop.PolynomialLiftingFn(order=3))
],
lifting_functions_input=None,
),
)],
regressor=pykoop.Edmd(),
)

# Take last episode for validation
X_train = X_vdp[X_vdp[:, 0] < 4]
X_valid = X_vdp[X_vdp[:, 0] == 4]

# Fit the pipeline
kp.fit(X_train, n_inputs=1, episode_feature=True)

# Extract initial conditions and input from validation episode
x0 = X_valid[[0], 1:3]
u = X_valid[:, 3:]

# Predict with re-lifting between timesteps (default)
X_pred_local = kp.predict_state(
x0,
u,
relift_state=True,
episode_feature=False,
)

# Predict without re-lifting between timesteps
X_pred_global = kp.predict_state(
x0,
u,
relift_state=False,
episode_feature=False,
)

# Plot trajectories in phase space
fig, ax = plt.subplots(constrained_layout=True)
ax.plot(
X_valid[:, 1],
X_valid[:, 2],
label='True trajectory',
)
ax.plot(
X_pred_local[:, 0],
X_pred_local[:, 1],
'--',
label='Local prediction',
)
ax.plot(
X_pred_global[:, 0],
X_pred_global[:, 1],
'--',
label='Global prediction',
)
ax.set_xlabel('$x_1[k]$')
ax.set_ylabel('$x_2[k]$')
ax.legend()
ax.grid(linestyle='--')

# Lift validation set
Psi_valid = kp.lift(X_valid[:, 1:], episode_feature=False)

# Predict lifted state with re-lifting between timesteps (default)
Psi_pred_local = kp.predict_state(
x0,
u,
relift_state=True,
return_lifted=True,
return_input=True,
episode_feature=False,
)

# Predict lifted state without re-lifting between timesteps
Psi_pred_global = kp.predict_state(
x0,
u,
relift_state=False,
return_lifted=True,
return_input=True,
episode_feature=False,
)

fig, ax = plt.subplots(
kp.n_states_out_,
1,
constrained_layout=True,
sharex=True,
squeeze=False,
)
for i in range(ax.shape[0]):
ax[i, 0].plot(Psi_valid[:, i], label='True trajectory')
ax[i, 0].plot(Psi_pred_local[:, i], '--', label='Local prediction')
ax[i, 0].plot(Psi_pred_global[:, i], '--', label='Global prediction')
ax[i, 0].grid(linestyle='--')
ax[i, 0].set_ylabel(rf'$\vartheta_{i + 1}[k]$')

ax[-1, 0].set_xlabel('$k$')
ax[0, 0].legend()

fig, ax = plt.subplots(
kp.n_inputs_out_,
1,
constrained_layout=True,
sharex=True,
squeeze=False,
)
for i in range(ax.shape[0]):
j = kp.n_states_out_ + i
ax[i, 0].plot(Psi_valid[:, j], label='True trajectory')
ax[i, 0].plot(Psi_pred_local[:, j], '--', label='Local prediction')
ax[i, 0].plot(Psi_pred_global[:, j], '--', label='Global prediction')
ax[i, 0].grid(linestyle='--')

ax[-1, 0].set_xlabel('$k$')
ax[0, 0].legend()
ax[0, 0].set_ylabel('$u[k]$')

plt.show()


if __name__ == '__main__':
main()
37 changes: 28 additions & 9 deletions pykoop/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
"""Koopman operator identification library in Python."""

from .koopman_pipeline import (EpisodeDependentLiftingFn,
EpisodeIndependentLiftingFn, KoopmanLiftingFn,
KoopmanPipeline, KoopmanRegressor,
SplitPipeline, combine_episodes, shift_episodes,
split_episodes, strip_initial_conditions)
from .lifting_functions import (BilinearInputLiftingFn, DelayLiftingFn,
PolynomialLiftingFn, SkLearnLiftingFn)
from .koopman_pipeline import (
EpisodeDependentLiftingFn,
EpisodeIndependentLiftingFn,
KoopmanLiftingFn,
KoopmanPipeline,
KoopmanRegressor,
SplitPipeline,
combine_episodes,
extract_initial_conditions,
extract_input,
score_state,
shift_episodes,
split_episodes,
strip_initial_conditions,
)
from .lifting_functions import (
BilinearInputLiftingFn,
DelayLiftingFn,
PolynomialLiftingFn,
SkLearnLiftingFn,
)
from .regressors import Dmd, Dmdc, Edmd
from .tsvd import Tsvd
from .util import (AnglePreprocessor, example_data_msd, random_input,
random_state)
from .util import (
AnglePreprocessor,
example_data_msd,
example_data_vdp,
random_input,
random_state,
)
Loading