Skip to content

Commit

Permalink
nlwpy: Use pytest for tests
Browse files Browse the repository at this point in the history
  • Loading branch information
fdabrandao committed Feb 26, 2024
1 parent 2ab702f commit c61c0cb
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 155 deletions.
30 changes: 2 additions & 28 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
variables:
CIBW_SKIP: pp* cp27-* *_i686 *-win32 *musllinux*
CIBW_ARCHS_MACOS: x86_64 universal2
# CIBW_TEST_COMMAND: python -m amplpy.tests
# CIBW_TEST_REQUIRES: --index-url https://pypi.ampl.com --extra-index-url https://pypi.org/simple ampl_module_base ampl_module_highs pandas numpy
CIBW_TEST_COMMAND: pytest --pyargs nlwpy.tests
CIBW_TEST_REQUIRES: --index-url https://pypi.ampl.com --extra-index-url https://pypi.org/simple ampl_module_base ampl_module_gurobi ampl_module_highs ampl_module_minos amplpy pytest pandas numpy scipy

stages:
- stage: native
Expand Down Expand Up @@ -72,32 +72,6 @@ stages:
aarch64 cp312:
CIBW_BUILD: cp312-*
CIBW_ARCHS_LINUX: aarch64
ppc64le cp37:
CIBW_BUILD: cp37-*
CIBW_ARCHS_LINUX: ppc64le
CIBW_TEST_COMMAND: ## Skip, cannot install SciPy, NumPy
ppc64le cp38:
CIBW_BUILD: cp38-*
CIBW_ARCHS_LINUX: ppc64le
CIBW_TEST_COMMAND:
ppc64le cp39:
CIBW_BUILD: cp39-*
CIBW_ARCHS_LINUX: ppc64le
CIBW_TEST_COMMAND:
ppc64le cp310:
CIBW_BUILD: cp310-*
CIBW_ARCHS_LINUX: ppc64le
CIBW_TEST_COMMAND:
ppc64le cp311:
CIBW_BUILD: cp311-*
CIBW_ARCHS_LINUX: ppc64le
CIBW_TEST_COMMAND:
ppc64le cp312:
CIBW_BUILD: cp312-*
CIBW_ARCHS_LINUX: ppc64le
CIBW_TEST_COMMAND:
# variables:
# CIBW_TEST_REQUIRES: --index-url https://pypi.ampl.com --extra-index-url https://pypi.org/simple ampl_module_base
steps:
- task: UsePythonVersion@0
- bash: docker run --rm --privileged multiarch/qemu-user-static --persistent yes
Expand Down
7 changes: 7 additions & 0 deletions nl-writer2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.DS_Store
.vscode
*.egg-info
*.pyc
*.so
dist/
build/
2 changes: 2 additions & 0 deletions nl-writer2/nlwpy/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from _nlwpy import *
from _nlwpy import __version__
35 changes: 19 additions & 16 deletions nl-writer2/nlwpy/src/nlw_bindings.cc
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ mp::NLSolution NLW2_Solve(mp::NLSolver &nls,
}

///////////////////////////////////////////////////////////////////////////////
PYBIND11_MODULE(nlwpy, m)
PYBIND11_MODULE(_nlwpy, m)
{
m.doc() = R"pbdoc(
AMPL NL Writer library Python API
Expand Down Expand Up @@ -318,21 +318,25 @@ NLSolver

/// NLSuffixSet
py::class_<mp::NLSuffixSet>(m, "NLSuffixSet")
.def("Find", // Find(): return None if not found
[=](mp::NLSuffixSet const& ss,
std::string const& name, int kind) -> py::object {
auto pelem = ss.Find(name, kind);
return py::cast(pelem);
})
.def("Find", // Find(): return None if not found
[=](mp::NLSuffixSet const &ss,
std::string const &name, int kind) -> py::object
{
auto pelem = ss.Find(name, kind);
return py::cast(pelem);
})
.def("__len__", // &mp::NLSuffixSet::size - does not work)
[](const mp::NLSuffixSet &ss) { return ss.size(); })
.def("__iter__", [](const mp::NLSuffixSet &ss) {
return py::make_iterator(ss.begin(), ss.end());
}, py::keep_alive<0, 1>()) /* Keep vector alive while iterator is used */
[](const mp::NLSuffixSet &ss)
{ return ss.size(); })
.def(
"__iter__", [](const mp::NLSuffixSet &ss)
{ return py::make_iterator(ss.begin(), ss.end()); },
py::keep_alive<0, 1>()) /* Keep vector alive while iterator is used */
.def("empty",
[](const mp::NLSuffixSet &ss) { return ss.empty(); })
.def("clear", [](mp::NLSuffixSet &ss) { ss.clear(); })
;
[](const mp::NLSuffixSet &ss)
{ return ss.empty(); })
.def("clear", [](mp::NLSuffixSet &ss)
{ ss.clear(); });

/// NLModel
py::class_<NLWPY_NLModel>(m, "NLModel")
Expand All @@ -346,8 +350,7 @@ NLSolver
.def("SetObjName", &NLWPY_NLModel::SetObjName)
.def("SetWarmstart", &NLWPY_NLModel::SetWarmstart)
.def("SetDualWarmstart", &NLWPY_NLModel::SetDualWarmstart)
.def("AddSuffix", &NLWPY_NLModel::AddSuffix)
;
.def("AddSuffix", &NLWPY_NLModel::AddSuffix);

/// NLSolution
py::class_<mp::NLSolution>(m, "NLSolution")
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -1,42 +1,45 @@
"""
NL Writer Python API test.
Copyright (C) 2024 AMPL Optimization Inc.
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that the copyright notice and this permission notice and warranty
disclaimer appear in supporting documentation.
The author and AMPL Optimization Inc disclaim all warranties with
regard to this software, including all implied warranties of
merchantability and fitness. In no event shall the author be liable
for any special, indirect or consequential damages or any damages
whatsoever resulting from loss of use, data or profits, whether in an
action of contract, negligence or other tortious action, arising out
of or in connection with the use or performance of this software.
Author: Gleb Belov
"""

import sys
import numpy as np
from scipy.sparse import csr_matrix
import numpy as np

from amplpy import modules
import nlwpy

assert nlwpy.__version__ == "0.0.1b0"

nlwo = nlwpy.MakeNLOptionsBasic_Default()
assert 0 == nlwo.n_text_mode_
assert 0 == nlwo.want_nl_comments_
assert 1 == nlwo.flags_
class ModelBuilder:
def __init__(self):
self.prob_name_ = "nlwpy_prob"
self.var_lb_ = [0, -3, 0, -1, -1, -2]
self.var_ub_ = [0, 20, 1, 1e20, -1, 10]
self.var_type_ = [0, 1, 1, 1, 0, 0]
self.var_names_ = ["x1_4", "x2_6", "x3_5", "x4_3", "x5_1", "x6_2"]
self.A_format_ = nlwpy.MatrixFormat.Rowwise
self.A_ = np.array([[0, 1, 1, 1, 0, 1], [0, 1, -1, -1, 0, 1]])
self.row_lb_ = [15, 10]
self.row_ub_ = [15, np.inf]
self.row_names_ = ["C1", "C2"]
self.obj_sense_ = nlwpy.ObjSense.Minimize
self.obj_c0_ = 3.24
self.obj_c_ = [0, 1, 0, 0, 0, 0]
self.Q_format_ = nlwpy.HessianFormat.Square
self.Q_ = np.zeros([6, 6])
self.Q_[3, 3] = 10
self.Q_[3, 5] = 12
self.Q_[4, 4] = 14
self.obj_name_ = "obj[1]"

### Extra input
self.ini_x_i_ = [0, 2]
self.ini_x_v_ = [5, 4]
self.ini_y_i_ = [0]
self.ini_y_v_ = [-12]
self.bas_x_ = [3, 4, 1, 4, 1, 3] ### Basis statuses
self.bas_y_ = [1, 1]

## ---------------------------------------------------------------
class ModelBuilder:
def GetModel(self):
### Solution
self.x_ref_ = [0, 5, 1, -1, -1, 10]
self.obj_val_ref_ = -39.76

def get_model(self):
nlme = nlwpy.NLModel(self.prob_name_)

nlme.SetCols(self.var_lb_, self.var_ub_, self.var_type_)
Expand Down Expand Up @@ -76,9 +79,9 @@ def GetModel(self):

return nlme

def Check(self, sol):
def check(self, sol):
result = True
if not self.ApproxEqual(sol.obj_val_, self.obj_val_ref_):
if not self._approx_equal(sol.obj_val_, self.obj_val_ref_):
print(
"MIQP 1: wrong obj val ({:.17f} !~ {:.17f})".format(
sol.obj_val_, self.obj_val_ref_
Expand All @@ -87,7 +90,7 @@ def Check(self, sol):
result = False

for i in range(len(sol.x_)):
if not self.ApproxEqual(self.x_ref_[i], sol.x_[i]):
if not self._approx_equal(self.x_ref_[i], sol.x_[i]):
print(
"MIQP 1: wrong x[{}] ({:.17f} !~ {:.17f})".format(
i + 1, sol.x_[i], self.x_ref_[i]
Expand Down Expand Up @@ -126,55 +129,25 @@ def Check(self, sol):
)
return result

def ApproxEqual(self, n, m):
def _approx_equal(self, n, m):
return abs(n - m) <= 1e-5 * min(1.0, abs(n) + abs(m))

def __init__(self):
self.prob_name_ = "nlwpy_prob"
self.var_lb_ = [0, -3, 0, -1, -1, -2]
self.var_ub_ = [0, 20, 1, 1e20, -1, 10]
self.var_type_ = [0, 1, 1, 1, 0, 0]
self.var_names_ = ["x1_4", "x2_6", "x3_5", "x4_3", "x5_1", "x6_2"]
self.A_format_ = nlwpy.MatrixFormat.Rowwise
self.A_ = np.array([[0, 1, 1, 1, 0, 1], [0, 1, -1, -1, 0, 1]])
self.row_lb_ = [15, 10]
self.row_ub_ = [15, np.inf]
self.row_names_ = ["C1", "C2"]
self.obj_sense_ = nlwpy.ObjSense.Minimize
self.obj_c0_ = 3.24
self.obj_c_ = [0, 1, 0, 0, 0, 0]
self.Q_format_ = nlwpy.HessianFormat.Square
self.Q_ = np.zeros([6, 6])
self.Q_[3, 3] = 10
self.Q_[3, 5] = 12
self.Q_[4, 4] = 14
self.obj_name_ = "obj[1]"

### Extra input
self.ini_x_i_ = [0, 2]
self.ini_x_v_ = [5, 4]
self.ini_y_i_ = [0]
self.ini_y_v_ = [-12]
self.bas_x_ = [3, 4, 1, 4, 1, 3] ### Basis statuses
self.bas_y_ = [1, 1]

### Solution
self.x_ref_ = [0, 5, 1, -1, -1, 10]
self.obj_val_ref_ = -39.76


def SolveAndCheck(solver, sopts, binary, stub):
def solve_and_check(solver, sopts, binary, stub):
mb = ModelBuilder()
nlme = mb.GetModel()
nlme = mb.get_model()
nlse = nlwpy.NLSolver()
nlopts = nlwpy.MakeNLOptionsBasic_Default()
assert 0 == nlopts.n_text_mode_
assert 0 == nlopts.want_nl_comments_
assert 1 == nlopts.flags_
nlopts.n_text_mode_ = not binary
nlopts.want_nl_comments_ = 1
nlse.SetNLOptions(nlopts)
nlse.SetFileStub(stub)
sol = nlse.Solve(nlme, solver, sopts)
if sol.solve_result_ > -2: ## Some method for this?
if not mb.Check(sol):
if not mb.check(sol):
print("Solution check failed.")
return False
else:
Expand All @@ -184,30 +157,25 @@ def SolveAndCheck(solver, sopts, binary, stub):
return True


argc = len(sys.argv)
argv = sys.argv

if argc < 2:
print(
"AMPL NL Writer Python API example.\n"
"Usage:\n"
' python <this_script> <solver> ["<solver_options>" [binary/text [<stub>]]],\n\n'
"where <solver> is ipopt, gurobi, minos, ...;\n"
"binary/text is the NL format (default: binary.)\n"
"Examples:\n"
' python <this_script> ipopt "" text /tmp/stub\n'
' python <this_script> gurobi "nonconvex=2 funcpieces=-2 funcpieceratio=1e-4"'
)
sys.exit(0)

solver = argv[1] if (argc > 1) else "minos"
sopts = argv[2] if argc > 2 else ""
binary = (argc <= 3) or "text" != argv[3]
stub = argv[4] if argc > 4 else ""

if not SolveAndCheck(solver, sopts, binary, stub):
print("SolveAndCheck() failed.")
sys.exit(1)

## ---------------------------------------------------------------
print("Test finished.")
def find_solver():
lst = modules.installed()
if "gurobi" in lst:
return modules.find("gurobi")
else:
return modules.find("minos")


def test_binary_nl():
solver = find_solver()
sopts = ""
binary = True
stub = ""
assert solve_and_check(solver, sopts, binary, stub)


def test_text_nl():
solver = find_solver()
sopts = ""
binary = False
stub = ""
assert solve_and_check(solver, sopts, binary, stub)
22 changes: 10 additions & 12 deletions nl-writer2/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,6 @@ def compile_args():
return []


ext_modules = [
Extension(
"nlwpy",
["nlwpy/src/nlw_bindings.cc"] + glob.glob("./src/" + "*.cc"),
extra_compile_args=compile_args(),
include_dirs=["include", pybind11.get_include()],
# Example: passing in the version to the compiled code
define_macros=[("VERSION_INFO", __version__)],
),
]

setup(
name="nlwpy",
version=__version__,
Expand All @@ -59,7 +48,16 @@ def compile_args():
url="https://github.com/ampl/mp",
description="Python API for the AMPL NL Writer library",
long_description="",
ext_modules=ext_modules,
packages=["nlwpy"],
ext_modules=[
Extension(
"_nlwpy",
["nlwpy/src/nlw_bindings.cc"] + sorted(glob.glob("./src/" + "*.cc")),
extra_compile_args=compile_args(),
include_dirs=["include", pybind11.get_include()],
define_macros=[("VERSION_INFO", __version__)],
),
],
extras_require={"test": "pytest"},
# Currently, build_ext only provides an optional "highest supported C++
# level" feature, but in the future it may provide more features.
Expand Down

0 comments on commit c61c0cb

Please sign in to comment.