Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12", "3.13"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-python@v4
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ repos:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.4.4
rev: v0.11.3
hooks:
- id: ruff
args: [--fix]
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2023-2024 UK Atomic Energy Authority
Copyright (c) 2023-2025 UK Atomic Energy Authority

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ Documentation for the VMCON algorithm and PyVMCON API/use can be found on our [*
## License
PyVMCON is provided under the MIT license, please see the LICENSE file for full details.

Copyright (c) 2023-2024 UK Atomic Energy Authority
Copyright (c) 2023-2025 UK Atomic Energy Authority
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -- Project information -----------------------------------------------------

project = "PyVMCON"
copyright = "2023-2024, UK Atomic Energy Authority"
copyright = "2023-2025, UK Atomic Energy Authority"
author = "Timothy Nunn"

# The full version, including alpha/beta/rc tags
Expand Down
14 changes: 8 additions & 6 deletions examples/converges.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@
"metadata": {},
"outputs": [],
"source": [
"from typing import List\n",
"import numpy as np\n",
"\n",
"from pyvmcon import Problem\n",
"\n",
"def f(x: List):\n",
"\n",
"def f(x: list):\n",
" return (x[0] - 2) ** 2 + (x[1] - 1) ** 2\n",
"\n",
"\n",
"problem = Problem(\n",
" f,\n",
" lambda x: np.array([2 * (x[0] - 2), 2 * (x[1] - 1)]),\n",
Expand All @@ -63,7 +65,7 @@
"source": [
"from pyvmcon import solve\n",
"\n",
"initial_x = np.array([2,2])\n",
"initial_x = np.array([2, 2])\n",
"\n",
"x, lamda_equality, lamda_inequality, result = solve(problem, initial_x)\n",
"\n",
Expand Down Expand Up @@ -98,9 +100,9 @@
"outputs": [],
"source": [
"x, lamda_equality, lamda_inequality, result = solve(\n",
" problem, \n",
" initial_x, \n",
" lbs=np.array([-10, -10]), \n",
" problem,\n",
" initial_x,\n",
" lbs=np.array([-10, -10]),\n",
" ubs=np.array([10, 10]),\n",
")\n",
"\n",
Expand Down
26 changes: 24 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ keywords = ["vmcon", "optimisation", "non-linear constrained optimisation"]
readme = "README.md"
license = { file = "LICENSE" }
authors = [{ name = "Timothy Nunn", email = "timothy.nunn@ukaea.uk" }]
requires-python = ">=3.8"
dependencies = ["numpy>=1.19", "cvxpy>=1.5.2"]
requires-python = ">=3.10"
dependencies = ["numpy>=1.24", "cvxpy>=1.5.2"]
classifiers = [
"Intended Audience :: Science/Research",
"License :: OSI Approved :: MIT License",
Expand All @@ -34,3 +34,25 @@ docs = [

[tool.setuptools.packages.find]
where = ["src"]

[tool.ruff]
target-version = "py310"
extend-exclude = ["docs/"]

[tool.ruff.lint]
select = ["ALL"]
ignore = [
"D203",
"D213",
"D401",
"COM812",
"PLR",
"N818",
"N803",
"N806",
"N802",
]

[tool.ruff.lint.per-file-ignores]
"*.ipynb" = ["T201", "S311", "ANN", "D103"]
"tests/*" = ["INP001", "ANN", "D103", "S101", "SLF001", "PLR0915", "PLR2004"]
8 changes: 5 additions & 3 deletions src/pyvmcon/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Python implementation of the VMCON non-linear constrained optimiser."""

from .exceptions import (
LineSearchConvergenceException,
QSPSolverException,
Expand All @@ -7,11 +9,11 @@
from .vmcon import solve

__all__ = [
"solve",
"AbstractProblem",
"LineSearchConvergenceException",
"Problem",
"QSPSolverException",
"Result",
"VMCONConvergenceException",
"LineSearchConvergenceException",
"QSPSolverException",
"solve",
]
33 changes: 14 additions & 19 deletions src/pyvmcon/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
from typing import Optional
"""Exceptions and errors raised within VMCON."""

import numpy as np

from .problem import Result


class VMCONConvergenceException(Exception):
"""Base class for an exception that indicates VMCON has
failed to converge. This exception allows certain diagnostics
to be passed and propagated with the exception.
"""Base error for VMCON errors.

This exception allows certain diagnostics to be passed and propagated
with the exception.
"""

def __init__(
self,
*args: object,
x: Optional[np.ndarray] = None,
result: Optional[Result] = None,
lamda_equality: Optional[np.ndarray] = None,
lamda_inequality: Optional[np.ndarray] = None,
x: np.ndarray | None = None,
result: Result | None = None,
lamda_equality: np.ndarray | None = None,
lamda_inequality: np.ndarray | None = None,
) -> None:
"""Constructor for the exception raised when VMCON cannot converge
on a feasible solution.
"""Constructs an exception raised within VMCON.

Parameters
----------
Expand All @@ -39,6 +39,7 @@ def __init__(

lamda_inequality : Optional[ndarray]
The jth Lagrange multipliers for the inequality constraints

"""
super().__init__(*args)

Expand All @@ -49,18 +50,12 @@ def __init__(


class _QspSolveException(Exception):
"""An exception that should only be used internally
to identify that the QSP has failed to solve.
"""
"""Internal exception indicating the QSP failed to solve."""


class QSPSolverException(VMCONConvergenceException):
"""Indicates VMCON failed to solve because the QSP Solver was unable
to solve.
"""
"""Indicates VMCON failed to solve because the QSP Solver was unable to solve."""


class LineSearchConvergenceException(VMCONConvergenceException):
"""Indicates the line search portion of VMCON was unable to
solve within a pre-defined number of iterations
"""
"""Indicates the line search was unable to converge."""
35 changes: 22 additions & 13 deletions src/pyvmcon/problem.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
"""Defines the interface for VMCON to exchange data with external software."""

from abc import ABC, abstractmethod
from typing import Callable, List, NamedTuple, TypeVar
from collections.abc import Callable
from typing import NamedTuple, TypeVar

import numpy as np

T = TypeVar("T", np.ndarray, np.number, float)


class Result(NamedTuple):
"""The data from calling a problem"""
"""The data from calling a problem."""

f: T
"""Value of the objective function"""
Expand Down Expand Up @@ -40,29 +43,31 @@ class AbstractProblem(ABC):

@abstractmethod
def __call__(self, x: np.ndarray) -> Result:
pass
"""Evaluate the optimisation problem at input x."""

@property
@abstractmethod
def num_equality(self) -> int:
"""Returns the number of equality constraints this problem has"""
"""The number of equality constraints this problem has."""

@property
def has_equality(self) -> bool:
"""Indicates whether or not this problem has equality constraints."""
return self.num_equality > 0

@property
def has_inequality(self) -> bool:
"""Indicates whether or not this problem has inequality constraints."""
return self.num_inequality > 0

@property
@abstractmethod
def num_inequality(self) -> int:
"""Returns the number of inequality constraints this problem has"""
"""The number of inequality constraints this problem has."""

@property
def total_constraints(self) -> int:
"""Returns the total number of constraints `m`"""
"""The total number of constraints `m`."""
return self.num_equality + self.num_inequality


Expand All @@ -71,20 +76,21 @@ def total_constraints(self) -> int:


class Problem(AbstractProblem):
"""Provides a simple implementation of an AbstractProblem
that essentially acts as a caller to equations to gather all
of the various data.
"""A simple implementation of an AbstractProblem.

It essentially acts as a caller to equations to gather all of the various data.
"""

def __init__(
self,
f: _FunctionAlias,
df: _DerivativeFunctionAlias,
equality_constraints: List[_FunctionAlias],
inequality_constraints: List[_FunctionAlias],
dequality_constraints: List[_DerivativeFunctionAlias],
dinequality_constraints: List[_DerivativeFunctionAlias],
equality_constraints: list[_FunctionAlias],
inequality_constraints: list[_FunctionAlias],
dequality_constraints: list[_DerivativeFunctionAlias],
dinequality_constraints: list[_DerivativeFunctionAlias],
) -> None:
"""Construct the problem."""
super().__init__()

self._f = f
Expand All @@ -95,6 +101,7 @@ def __init__(
self._dinequality_constraints = dinequality_constraints

def __call__(self, x: np.ndarray) -> Result:
"""Evaluate the problem at input point x."""
return Result(
self._f(x),
self._df(x),
Expand All @@ -106,8 +113,10 @@ def __call__(self, x: np.ndarray) -> Result:

@property
def num_equality(self) -> int:
"""The number of equality constraints this problem has."""
return len(self._equality_constraints)

@property
def num_inequality(self) -> int:
"""The number of inequality constraints this problem has."""
return len(self._inequality_constraints)
Loading