diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 56c6bcc3..670b140e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,7 +6,7 @@ on: jobs: test_nosnoc: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -19,6 +19,7 @@ jobs: virtualenv --python=python3 env source env/bin/activate which python + pip install setuptools pip install -e . pip install -r requirements-test.txt @@ -37,7 +38,7 @@ jobs: - name: Upload test results if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: test-results path: test-results/ diff --git a/examples/motor_with_friction/motor_with_friction_ocp.py b/examples/motor_with_friction/motor_with_friction_ocp.py index 65b9f358..99aff5a8 100644 --- a/examples/motor_with_friction/motor_with_friction_ocp.py +++ b/examples/motor_with_friction/motor_with_friction_ocp.py @@ -10,8 +10,7 @@ X0 = np.array([0, 0, 0, 0, 0]) X_TARGET = np.array([0.01, 0, 0.01, 0, 0]) - -def get_motor_with_friction_ocp_description(): +def get_motor_with_friction_ocp_description(USE_FO_DYNAMICS): # Parameters m1 = 1.03 # slide mass @@ -52,11 +51,18 @@ def get_motor_with_friction_ocp_description(): C1 = np.array([0, -F_R / m1, 0, 0, 0]) # v1 >0 C2 = -C1 # v1<0 - # switching dynamics with different friction froces - f_1 = A @ x + B @ u + C1 - # v1>0 - f_2 = A @ x + B @ u + C2 - # v1<0 + if USE_FO_DYNAMICS: + f_0 = A @ x + B @ u + f_1 = C1 + # v1>0 + f_2 = C2 + # v1<0 + else: + # switching dynamics with different friction froces + f_1 = A @ x + B @ u + C1 + # v1>0 + f_2 = A @ x + B @ u + C2 + # v1<0 # All modes F = [horzcat(f_1, f_2)] @@ -71,8 +77,10 @@ def get_motor_with_friction_ocp_description(): # Stage cost f_q = u**2 - - model = nosnoc.NosnocModel(x=x, F=F, S=S, c=c, x0=X0, u=u) + if USE_FO_DYNAMICS: + model = nosnoc.NosnocModel(x=x, f_0=f_0, F=F, S=S, c=c, x0=X0, u=u) + else: + model = nosnoc.NosnocModel(x=x, F=F, S=S, c=c, x0=X0, u=u) ocp = nosnoc.NosnocOcp(lbu=lbu, ubu=ubu, f_q=f_q, g_terminal=g_terminal) return model, ocp @@ -98,7 +106,7 @@ def solve_ocp(opts=None): if opts is None: opts = get_default_options() - [model, ocp] = get_motor_with_friction_ocp_description() + [model, ocp] = get_motor_with_friction_ocp_description(USE_FO_DYNAMICS=True) opts.terminal_time = 0.08 diff --git a/nosnoc/model.py b/nosnoc/model.py index 6c7f4a01..18155a78 100644 --- a/nosnoc/model.py +++ b/nosnoc/model.py @@ -27,6 +27,7 @@ class NosnocModel: :param x: state variables :param x0: initial state :param F: set of state equations for the different regions + :param f_0: part of rhs in the PSS that remains unchanged/smooth. default zero. :param c: set of region boundaries :param S: determination of the boundaries region connecting different state equations with each boundary zone @@ -58,6 +59,7 @@ def __init__(self, x: ca.SX, x0: Optional[np.ndarray], F: Optional[List[ca.SX]] = None, + f_0: Optional[ca.SX] = None, c: Optional[List[ca.SX]] = None, S: Optional[List[np.ndarray]] = None, g_Stewart: Optional[List[ca.SX]] = None, @@ -82,6 +84,7 @@ def __init__(self, self.theta: List[ca.SX] = theta self.F: Optional[List[ca.SX]] = F self.f_x: List[ca.SX] = f_x + self.f_0: Optional[ca.SX] = f_0 self.g_z: ca.SX = g_z self.c: List[ca.SX] = c self.S: List[np.ndarray] = S @@ -272,7 +275,10 @@ def preprocess_model(self, opts: NosnocOpts): f_x = casadi_vertcat_list(self.f_x) z[0:sum(n_c_sys)] = casadi_vertcat_list(self.alpha) else: - f_x = ca.SX.zeros((n_x, 1)) + if self.f_0 is None: + f_x = ca.SX.zeros((n_x, 1)) + else: + f_x = self.f_0 # initalize f_x with base dynamics f_0 if opts.pss_mode == PssMode.STEWART: for ii in range(n_sys): diff --git a/requirements-test.txt b/requirements-test.txt index 80feef67..cf7bbc12 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,27 +1,4 @@ # Additional test requirements -pytest==7.3.0 -pytest-cov==4.0.0 -parameterized==0.9.0 -# Fixed version dependencies -casadi>=3.6 -contourpy==1.0.7 -coverage==7.2.3 -cycler==0.11.0 -exceptiongroup==1.1.1 -fonttools==4.38.0 -importlib-resources==5.12.0 -iniconfig==2.0.0 -kiwisolver==1.4.4 -matplotlib==3.7.0 -numpy==1.24.2 -packaging==23.0 -Pillow==9.4.0 -pluggy==1.0.0 -pyparsing==3.0.9 -python-dateutil==2.8.2 -scipy==1.10.1 -setuptools-scm==7.1.0 -six==1.16.0 -tomli==2.0.1 -typing_extensions==4.5.0 -zipp==3.14.0 +pytest +pytest-cov +parameterized diff --git a/setup.py b/setup.py index e6008680..3731834f 100644 --- a/setup.py +++ b/setup.py @@ -1,25 +1,31 @@ from distutils.core import setup -setup(name='nosnoc', - version='0.1', - python_requires='>=3.8', - description='Nonsmooth Numerical Optimal Control for Python', -# url='', - author='Jonathan Frey, Armin Nurkanovic', - # use_scm_version={ - # "fallback_version": "0.1-local", - # "root": "../..", - # "relative_to": __file__ - # }, +setup( + name='nosnoc', + version='0.1', + python_requires='>=3.8', + description='Nonsmooth Numerical Optimal Control for Python', + # url='', + author='Anton Pozharskiy, Jonathan Frey, Armin Nurkanovic', + # use_scm_version={ + # "fallback_version": "0.1-local", + # "root": "../..", + # "relative_to": __file__ + # }, license='BSD', -# packages = find_packages(), - include_package_data = True, - py_modules=[], - setup_requires=['setuptools_scm'], - install_requires=[ - 'numpy>=1.20.0,<2.0.0', - 'scipy', - 'casadi==3.6.3', - 'matplotlib', - ] + packages = ["nosnoc"], + include_package_data = True, + py_modules=[], + setup_requires=['setuptools_scm'], + install_requires=[ + 'numpy>=1.20.0', + 'scipy', + 'casadi', + 'matplotlib', + ], + tests_require=[ + 'pytest', + 'pytest-cov', + 'parametrized' + ] ) diff --git a/test/test_ocp.py b/test/test_ocp.py index 200348fa..6d4fb76b 100644 --- a/test/test_ocp.py +++ b/test/test_ocp.py @@ -87,8 +87,8 @@ def test_combination(self, equidistant_control_grid, step_equilibration, irk_rep self.assertTrue(np.allclose(x_traj[-1][:2], X_TARGET, atol=1e-4), message) self.assertTrue(np.allclose(t_grid[-1], TERMINAL_TIME, atol=1e-6), message) self.assertTrue(np.allclose(t_grid[0], 0.0, atol=1e-6), message) - self.assertTrue(np.alltrue(u_traj < UBU), message) - self.assertTrue(np.alltrue(u_traj > LBU), message) + self.assertTrue(np.all(u_traj < UBU), message) + self.assertTrue(np.all(u_traj > LBU), message) if __name__ == "__main__": diff --git a/test/test_parametric_ocp.py b/test/test_parametric_ocp.py index e78271d3..94bbe869 100644 --- a/test/test_parametric_ocp.py +++ b/test/test_parametric_ocp.py @@ -11,7 +11,7 @@ def test_one_parametric_ocp(self): results_parametric = solve_paramteric_example(with_global_var=False) self.assertTrue(np.allclose(ref_results["w_sol"], results_parametric["w_sol"], atol=1e-6)) - self.assertTrue(np.alltrue(ref_results["nlp_iter"] == results_parametric["nlp_iter"])) + self.assertTrue(np.all(ref_results["nlp_iter"] == results_parametric["nlp_iter"])) self.assertEqual(results_parametric["v_global"].shape, (1, 0)) self.assertEqual(ref_results["v_global"].shape, (1, 0))