diff --git a/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqr_f.png b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqr_f.png new file mode 100644 index 00000000000..99a11de0c28 Binary files /dev/null and b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqr_f.png differ diff --git a/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqr_fx.png b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqr_fx.png new file mode 100644 index 00000000000..933d4f15d9e Binary files /dev/null and b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqr_fx.png differ diff --git a/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqr_fxx.png b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqr_fxx.png new file mode 100644 index 00000000000..35b4590975e Binary files /dev/null and b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqr_fxx.png differ diff --git a/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqrt_c4_err.png b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqrt_c4_err.png new file mode 100644 index 00000000000..1664a0f5ae7 Binary files /dev/null and b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqrt_c4_err.png differ diff --git a/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqrt_c4_f.png b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqrt_c4_f.png new file mode 100644 index 00000000000..63964459e5e Binary files /dev/null and b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqrt_c4_f.png differ diff --git a/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqrt_c4_fx.png b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqrt_c4_fx.png new file mode 100644 index 00000000000..705240880a5 Binary files /dev/null and b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqrt_c4_fx.png differ diff --git a/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqrt_c4_fxx.png b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqrt_c4_fxx.png new file mode 100644 index 00000000000..220b925ca0f Binary files /dev/null and b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sgnsqrt_c4_fxx.png differ diff --git a/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sinc_f.png b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sinc_f.png new file mode 100644 index 00000000000..89f65c1c635 Binary files /dev/null and b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sinc_f.png differ diff --git a/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sinc_fx.png b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sinc_fx.png new file mode 100644 index 00000000000..29b9a61bb1c Binary files /dev/null and b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sinc_fx.png differ diff --git a/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sinc_fxx.png b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sinc_fxx.png new file mode 100644 index 00000000000..2d44e546ea2 Binary files /dev/null and b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/figs/sinc_fxx.png differ diff --git a/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/index.rst b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/index.rst new file mode 100644 index 00000000000..6a8f379cc6a --- /dev/null +++ b/doc/OnlineDocs/explanation/modeling_utils/aslfunctions/index.rst @@ -0,0 +1,114 @@ +aslfunctions +============ + +Pyomo provides a set of AMPL user-defined functions that commonly occur but cannot be easily written as Pyomo expressions. + +Using These AMPL External Functions +----------------------------------- + +Build +~~~~~ + +You must build the Pyomo extensions to use these functions. Run ``pyomo build-extensions`` in the terminal and make sure the ``aslfunctions`` build status is "ok." + +Example +~~~~~~~ + +.. doctest:: + + >>> import pyomo.environ as pyo + >>> from pyomo.common.fileutils import find_library + >>> flib = find_library("aslfunctions") + >>> m = pyo.ConcreteModel(name = 'AMPLExternalFunctions') + >>> m.sinc = pyo.ExternalFunction(library=flib, function="sinc") + >>> m.x = pyo.Var() + >>> m.z = pyo.Var() + >>> m.constraint = pyo.Constraint(expr = m.z == m.sinc(m.x)) + +Functions +--------- + +sinc(x) +~~~~~~~ + +This function is defined as: + +.. math:: + + \text{sinc}(x) = \begin{cases} + \sin(x) / x & \text{if } x \neq 0 \\ + 1 & \text{if } x = 0 + \end{cases} + +In this implementation, the region :math:`-0.1 < x < 0.1` is replaced by a Taylor series with enough terms that the function should be at least :math:`C^2` smooth. The difference between the function and the Tayor series is near the limits of machine precision, about :math:`1 \times 10^{-16}` for the function value, :math:`1 \times 10^{-16}` for the first derivative, and :math:`1 \times 10^{-14}` for the second derivative. + +These figures show the sinc(x) function, the Taylor series and where the Taylor series is used. + +.. image:: figs/sinc_f.png + +.. image:: figs/sinc_fx.png + +.. image:: figs/sinc_fxx.png + + +sgnsqr(x) +~~~~~~~~~ + +This function is defined as: + +.. math:: + + \text{sgnsqr}(x) = \text{sgn}(x)x^2 + +This function is only :math:`C^1` smooth because at 0 the second derivative is undefined and the jumps from -2 to 2. + +sgnsqr_c4(x) +~~~~~~~~~~~~ + + +This function is defined as: + +.. math:: + + \operatorname{sgnsqr\_c4}(x) = + \begin{cases} + \operatorname{sgn}(x)\,x^2 & \text{if } |x| \ge 0.1, \\ + \displaystyle\sum_{i=0}^{11} c_i x^i & \text{if } |x| < 0.1 + \end{cases} + +This function is :math:`C^4` smooth. The region :math:`-0.1 < x < 0.1` is replaced by an 11th order polynomial that approximates :math:`\text{sgn}(x)x^2`. This function has well behaved derivatives at :math:`x=0`. If you need to use this function with very small numbers and high accuracy is important, you can scale the argument up (e.g. :math:`\operatorname{sgnsqr\_c4}(sx)/s^2`). + +These figures show the sgnsqr(x) function compared to the smooth approximation sgnsqr_c4(x). + +.. image:: figs/sgnsqr_f.png + +.. image:: figs/sgnsqr_fx.png + +.. image:: figs/sgnsqr_fxx.png + + +sgnsqrt_c4(x) +~~~~~~~~~~~~~ + +This function is a signed square root approximation defined as: + +.. math:: + + \operatorname{sgnsqrt\_c4}(x) = + \begin{cases} + \operatorname{sgn}(x)\,|x|^{0.5} & \text{if } |x| \ge 0.1, \\ + \displaystyle\sum_{i=0}^{11} c_i x^i & \text{if } |x| < 0.1 + \end{cases} + +This function is :math:`C^4` smooth. The region :math:`-0.1 < x < 0.1` is replaced by an 11th order polynomial that approximates :math:`\text{sgn}(x)|x|^{0.5}`. This function has well behaved derivatives at :math:`x=0`. If you need to use this function with very small numbers and high accuracy is important, you can scale the argument up (e.g. :math:`\operatorname{sgnsqrt\_c4}(sx)/s^{0.5}`). + +These figures show the signed square root function compared to the smooth approximation sgnsqrt_c4(x). + +.. image:: figs/sgnsqrt_c4_err.png + +.. image:: figs/sgnsqrt_c4_f.png + +.. image:: figs/sgnsqrt_c4_fx.png + +.. image:: figs/sgnsqrt_c4_fxx.png + diff --git a/doc/OnlineDocs/explanation/modeling_utils/index.rst b/doc/OnlineDocs/explanation/modeling_utils/index.rst index 16560899ebd..c601e894279 100644 --- a/doc/OnlineDocs/explanation/modeling_utils/index.rst +++ b/doc/OnlineDocs/explanation/modeling_utils/index.rst @@ -8,7 +8,7 @@ Modeling Utilities latex_printer preprocessing scaling - + aslfunctions/index .. diff --git a/pyomo/contrib/aslfunctions/__init__.py b/pyomo/contrib/aslfunctions/__init__.py new file mode 100644 index 00000000000..6eb9ea8b81d --- /dev/null +++ b/pyomo/contrib/aslfunctions/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2025 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain +# rights in this software. +# This software is distributed under the 3-clause BSD License. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/aslfunctions/build.py b/pyomo/contrib/aslfunctions/build.py new file mode 100644 index 00000000000..f7a99716f29 --- /dev/null +++ b/pyomo/contrib/aslfunctions/build.py @@ -0,0 +1,32 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2025 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain +# rights in this software. +# This software is distributed under the 3-clause BSD License. +# ___________________________________________________________________________ + +import sys +from pyomo.common.cmake_builder import build_cmake_project + + +def build_aslfunctions(user_args=[], parallel=None): + return build_cmake_project( + targets=["src"], + package_name="aslfunctions", + description="Useful AMPL external functions", + user_args=["-DBUILD_AMPLASL_IF_NEEDED=ON"] + user_args, + parallel=parallel, + ) + + +class LibASLFunctionsBuilder(object): + def __call__(self, parallel): + return build_aslfunctions(parallel=parallel) + + +if __name__ == "__main__": + build_aslfunctions(sys.argv[1:]) diff --git a/pyomo/contrib/aslfunctions/plugins.py b/pyomo/contrib/aslfunctions/plugins.py new file mode 100644 index 00000000000..8d464caea24 --- /dev/null +++ b/pyomo/contrib/aslfunctions/plugins.py @@ -0,0 +1,17 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2025 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain +# rights in this software. +# This software is distributed under the 3-clause BSD License. +# ___________________________________________________________________________ + +from pyomo.common.extensions import ExtensionBuilderFactory +from pyomo.contrib.aslfunctions.build import LibASLFunctionsBuilder + + +def load(): + ExtensionBuilderFactory.register("aslfunctions")(LibASLFunctionsBuilder) diff --git a/pyomo/contrib/aslfunctions/src/CMakeLists.txt b/pyomo/contrib/aslfunctions/src/CMakeLists.txt new file mode 100644 index 00000000000..4e3e56d3f6d --- /dev/null +++ b/pyomo/contrib/aslfunctions/src/CMakeLists.txt @@ -0,0 +1,43 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2025 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain +# rights in this software. +# This software is distributed under the 3-clause BSD License. +# ___________________________________________________________________________ + +cmake_minimum_required(VERSION 3.0...3.31) +# This was developed against CMake 3.0, and appears to comply with 3.5 + +PROJECT( aslfunctions ) + +INCLUDE( + "${CMAKE_CURRENT_SOURCE_DIR}/../../ampl_function_demo/src/FindASL.cmake") + +# Targets in this project +OPTION(BUILD_EXTERNAL_FCN_LIBRARY + "Build the ASL external function example library" ON) + +IF( BUILD_EXTERNAL_FCN_LIBRARY ) + ADD_LIBRARY( aslfunctions SHARED "functions.cpp" ) + TARGET_LINK_LIBRARIES( aslfunctions + PUBLIC ${ASL_LIBRARY} ${CMAKE_DL_LIBS}) + TARGET_INCLUDE_DIRECTORIES( aslfunctions + PUBLIC ${ASL_INCLUDE_DIR} + INTERFACE . ) + # If you need a CPP directive defined when building the library (e.g., + # for managing __declspec(dllimport) under Windows) uncomment the + # following: + #TARGET_COMPILE_DEFINITIONS( aslfunctions PRIVATE BUILDING_ASL_DEMO ) + #SET_TARGET_PROPERTIES( aslfunctions PROPERTIES ENABLE_EXPORTS 1 ) + INSTALL( TARGETS aslfunctions LIBRARY DESTINATION lib + RUNTIME DESTINATION lib ) + IF( BUILD_AMPLASL ) + # If we are building AMPL/asl (from FindASL), it is possible that we + # are linking against it, so we will add the appropriate dependency + add_dependencies(aslfunctions ampl_asl) + ENDIF() +ENDIF() diff --git a/pyomo/contrib/aslfunctions/src/functions.cpp b/pyomo/contrib/aslfunctions/src/functions.cpp new file mode 100644 index 00000000000..f3754f72423 --- /dev/null +++ b/pyomo/contrib/aslfunctions/src/functions.cpp @@ -0,0 +1,207 @@ +/* ___________________________________________________________________________ + * Pyomo: Python Optimization Modeling Objects + * Copyright (c) 2008-2025 + * National Technology and Engineering Solutions of Sandia, LLC + * Under the terms of Contract DE-NA0003525 with National Technology and + * Engineering Solutions of Sandia, LLC, the U.S. Government retains certain + * rights in this software. + * This software is distributed under the 3-clause BSD License. + * ___________________________________________________________________________ +*/ +#include +#include "funcadd.h" + +extern real sinc(arglist *al) { + real x = al->ra[al->at[0]]; + real y = 0; + real r = 0.1; + if(fabs(x) < r){ // use taylor series near 0 + y = 1 + - x * x / 3.0 / 2.0 + + pow(x, 4) / 5.0 / 4.0 / 3.0 / 2.0 + - pow(x, 6) / 7.0 / 6.0 / 5.0 / 4.0 / 3.0 / 2.0 + + pow(x, 8) / 9.0 / 8.0 / 7.0 / 6.0 / 5.0 / 4.0 / 3.0 / 2.0; + if (al->derivs!=NULL) { + al->derivs[0] = - x / 3.0 + + pow(x, 3) / 5.0 / 3.0 / 2.0 + - pow(x, 5) / 7.0 / 5.0 / 4.0 / 3.0 / 2.0 + + pow(x, 7) / 9.0 / 7.0 / 6.0 / 5.0 / 4.0 / 3.0 / 2.0 + - pow(x, 9) /11.0 / 9.0 / 8.0 / 7.0 / 6.0 / 5.0 / 4.0 / 3.0 / 2.0; + } + if (al->hes!=NULL) { + al->hes[0] = - 1 / 3.0 + + pow(x, 2) / 5.0 / 2.0 + - pow(x, 4) / 7.0 / 4.0 / 3.0 / 2.0 + + pow(x, 6) / 9.0 / 6.0 / 5.0 / 4.0 / 3.0 / 2.0 + - pow(x, 8) /11.0 / 8.0 / 7.0 / 6.0 / 5.0 / 4.0 / 3.0 / 2.0; + } + } + else{ // away from 0 use sin(x) / x + y = sin(x) / x; + if (al->derivs!=NULL) { + al->derivs[0] = cos(x) / x - sin(x) / x / x; + } + if (al->hes!=NULL) { + al->hes[0] = -sin(x) / x - 2 * cos(x) / x / x + + 2 * sin(x) / x / x / x; + } + } + return y; +} + +extern real sgnsqr(arglist *al) { + real x = al->ra[al->at[0]]; + real y = copysign(x * x, x); + + // Compute the first derivative, if requested. + if (al->derivs!=NULL) { + al->derivs[0] = 2 * fabs(x); + } + + // Compute the second derivative, if requested. + if (al->hes!=NULL) { + al->hes[0] = copysign(2, x); + } + return y; +} + +extern real sgnsqr_c4(arglist *al) { + real x = al->ra[al->at[0]]; + const real c[] = { + 0.0, + 0.0273437500000006, + 3.1086244689504383e-15, + 10.937499999999813, + 6.821210263296962e-13, + -546.8749999999808, + -1.2114878257930237e-10, + 21874.99999999906, + 7.251377026937331e-09, + -390624.99999998155, + -1.582935171828823e-07, + }; + const real r = 0.1; + real y = 0; + unsigned char i = 0; + + if(fabs(x) < r){ + for(i=0; i<11; ++i){ + y += c[i] * pow(x, i); + } + if (al->derivs!=NULL) { + al->derivs[0] = 0; + for(i=1; i<11; ++i){ + al->derivs[0] += c[i] * i * pow(x, i - 1); + } + } + if (al->hes!=NULL) { + al->hes[0] = 0; + for(i=2; i<11; ++i){ + al->hes[0] += c[i] * i * (i - 1) * pow(x, i - 2); + } + } + } + else{ + y = copysign(x * x, x); + if (al->derivs!=NULL) { + al->derivs[0] = 2 * fabs(x); + } + if (al->hes!=NULL) { + al->hes[0] = copysign(2, x); + } + } + + return y; +} + + +extern real sgnsqrt_c4(arglist *al) { + real x = al->ra[al->at[0]]; + const real c[] = { + 0.0, + 5.1186281462197964, + -5.115907697472721e-13, + -409.490251697574, + -2.4253192047278085e-12, + 34124.18764146428, + 3.827043323720301e-09, + -1574962.506529117, + -2.7353306479626024e-07, + 30109577.330703676, + 6.383782391594595e-06, + }; + const real r = 0.1; + real y = 0; + unsigned char i = 0; + + if(fabs(x) < r){ + for(i=0; i<11; ++i){ + y += c[i] * pow(x, i); + } + if (al->derivs!=NULL) { + al->derivs[0] = 0; + for(i=1; i<11; ++i){ + al->derivs[0] += c[i] * i * pow(x, i - 1); + } + } + if (al->hes!=NULL) { + al->hes[0] = 0; + for(i=2; i<11; ++i){ + al->hes[0] += c[i] * i * (i - 1) * pow(x, i - 2); + } + } + } + else{ + y = copysign(sqrt(fabs(x)), x); + if (al->derivs!=NULL) { + al->derivs[0] = 1.0 / 2.0 / sqrt(fabs(x)); + } + if (al->hes!=NULL) { + al->hes[0] = -copysign(1.0/4.0*pow(fabs(x), -1.5), x); + } + } + + return y; +} + + +// Register external functions defined in this library with the ASL +void funcadd(AmplExports *ae){ + // Arguments for addfunc (this is not fully detailed; see funcadd.h) + // 1) Name of function in AMPL + // 2) Function pointer to C function + // 3) see FUNCADD_TYPE enum in funcadd.h + // 4) Number of arguments + // >=0 indicates a fixed number of arguments + // < 0 indicates a variable length list (requiring at least -(n+1) + // arguments) + // 5) Void pointer to function info + addfunc( + "sinc", + (rfunc)sinc, + FUNCADD_REAL_VALUED, + 1, + NULL + ); + addfunc( + "sgnsqr", + (rfunc)sgnsqr, + FUNCADD_REAL_VALUED, + 1, + NULL + ); + addfunc( + "sgnsqr_c4", + (rfunc)sgnsqr_c4, + FUNCADD_REAL_VALUED, + 1, + NULL + ); + addfunc( + "sgnsqrt_c4", + (rfunc)sgnsqrt_c4, + FUNCADD_REAL_VALUED, + 1, + NULL + ); +} diff --git a/pyomo/contrib/aslfunctions/tests/__init__.py b/pyomo/contrib/aslfunctions/tests/__init__.py new file mode 100644 index 00000000000..6eb9ea8b81d --- /dev/null +++ b/pyomo/contrib/aslfunctions/tests/__init__.py @@ -0,0 +1,10 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2025 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain +# rights in this software. +# This software is distributed under the 3-clause BSD License. +# ___________________________________________________________________________ diff --git a/pyomo/contrib/aslfunctions/tests/test_functions.py b/pyomo/contrib/aslfunctions/tests/test_functions.py new file mode 100644 index 00000000000..919d5587ab8 --- /dev/null +++ b/pyomo/contrib/aslfunctions/tests/test_functions.py @@ -0,0 +1,134 @@ +# ___________________________________________________________________________ +# +# Pyomo: Python Optimization Modeling Objects +# Copyright (c) 2008-2025 +# National Technology and Engineering Solutions of Sandia, LLC +# Under the terms of Contract DE-NA0003525 with National Technology and +# Engineering Solutions of Sandia, LLC, the U.S. Government retains certain +# rights in this software. +# This software is distributed under the 3-clause BSD License. +# ___________________________________________________________________________ + +import platform +import math +import pyomo.environ as pyo +import pyomo.common.unittest as unittest +from pyomo.common.fileutils import find_library + +flib = find_library("aslfunctions") +is_pypy = platform.python_implementation().lower().startswith("pypy") + + +@unittest.skipIf(not flib, 'Could not find the "aslfunctions" library') +@unittest.skipIf(is_pypy, "Cannot evaluate external functions under pypy") +class TestAMPLExternalFunction(unittest.TestCase): + + def test_eval_sqnsqr_function_fgh(self): + m = pyo.ConcreteModel() + m.tf = pyo.ExternalFunction(library=flib, function="sgnsqr") + + f, g, h = m.tf.evaluate_fgh((2,)) + self.assertEqual(f, 4) + self.assertEqual(g, [4]) + self.assertEqual(h, [2]) + + f, g, h = m.tf.evaluate_fgh((-2,)) + self.assertAlmostEqual(f, -4) + self.assertStructuredAlmostEqual(g, [4]) + self.assertStructuredAlmostEqual(h, [-2]) + + def test_eval_sqnsqr_c4_function_fgh(self): + m = pyo.ConcreteModel() + m.tf = pyo.ExternalFunction(library=flib, function="sgnsqr_c4") + + f, g, h = m.tf.evaluate_fgh((2,)) + self.assertEqual(f, 4) + self.assertEqual(g, [4]) + self.assertEqual(h, [2]) + + f, g, h = m.tf.evaluate_fgh((-2,)) + self.assertAlmostEqual(f, -4) + self.assertStructuredAlmostEqual(g, [4]) + self.assertStructuredAlmostEqual(h, [-2]) + + dx = 1e-9 + x = 0.1 + + f1, g1, h1 = m.tf.evaluate_fgh((x + dx,)) + f2, g2, h2 = m.tf.evaluate_fgh((x - dx,)) + self.assertAlmostEqual(f1, f2) + self.assertStructuredAlmostEqual(g1, g2) + self.assertStructuredAlmostEqual(h1, h2) + + dx = 1e-9 + x = -0.1 + + f1, g1, h1 = m.tf.evaluate_fgh((x + dx,)) + f2, g2, h2 = m.tf.evaluate_fgh((x - dx,)) + self.assertAlmostEqual(f1, f2) + self.assertStructuredAlmostEqual(g1, g2) + self.assertStructuredAlmostEqual(h1, h2) + + def test_eval_sinc_function_fgh(self): + m = pyo.ConcreteModel() + m.tf = pyo.ExternalFunction(library=flib, function="sinc") + + f, g, h = m.tf.evaluate_fgh((0,)) + self.assertAlmostEqual(f, 1.0) + self.assertAlmostEqual(g, [0]) + self.assertAlmostEqual(h, [-1 / 3.0]) + + dx = 1e-10 + f, g, h = m.tf.evaluate_fgh((2,)) + self.assertAlmostEqual(f, math.sin(2) / 2) + self.assertStructuredAlmostEqual(g, [(math.sin(2 + dx) / (2 + dx) - f) / dx], 5) + + dx = 1e-10 + x = 0.1 + + f1, g1, h1 = m.tf.evaluate_fgh((x + dx,)) + f2, g2, h2 = m.tf.evaluate_fgh((x - dx,)) + self.assertAlmostEqual(f1, f2) + self.assertStructuredAlmostEqual(g1, g2) + self.assertStructuredAlmostEqual(h1, h2) + + dx = 1e-10 + x = 0.1 + + f1, g1, h1 = m.tf.evaluate_fgh((x + dx,)) + f2, g2, h2 = m.tf.evaluate_fgh((x - dx,)) + self.assertAlmostEqual(f1, f2) + self.assertStructuredAlmostEqual(g1, g2) + self.assertStructuredAlmostEqual(h1, h2) + + def test_eval_sqnsqrt_c4_function_fgh(self): + m = pyo.ConcreteModel() + m.tf = pyo.ExternalFunction(library=flib, function="sgnsqrt_c4") + + f, g, h = m.tf.evaluate_fgh((4,)) + self.assertEqual(f, 2) + self.assertEqual(g, [0.5 * 4 ** (-0.5)]) + self.assertEqual(h, [-0.25 * 4 ** (-3 / 2)]) + + f, g, h = m.tf.evaluate_fgh((-4,)) + self.assertAlmostEqual(f, -2) + self.assertStructuredAlmostEqual(g, [0.5 * 4 ** (-0.5)]) + self.assertStructuredAlmostEqual(h, [0.25 * 4 ** (-3 / 2)]) + + dx = 1e-9 + x = 0.1 + + f1, g1, h1 = m.tf.evaluate_fgh((x + dx,)) + f2, g2, h2 = m.tf.evaluate_fgh((x - dx,)) + self.assertAlmostEqual(f1, f2) + self.assertStructuredAlmostEqual(g1, g2) + self.assertStructuredAlmostEqual(h1, h2) + + dx = 1e-9 + x = -0.1 + + f1, g1, h1 = m.tf.evaluate_fgh((x + dx,)) + f2, g2, h2 = m.tf.evaluate_fgh((x - dx,)) + self.assertAlmostEqual(f1, f2) + self.assertStructuredAlmostEqual(g1, g2) + self.assertStructuredAlmostEqual(h1, h2) diff --git a/pyomo/environ/__init__.py b/pyomo/environ/__init__.py index 7e2e4caaab1..be01195b3d6 100644 --- a/pyomo/environ/__init__.py +++ b/pyomo/environ/__init__.py @@ -46,6 +46,7 @@ def _do_import(pkg_name): 'pyomo.contrib.gdpopt', 'pyomo.contrib.gjh', 'pyomo.contrib.gdp_bounds', + 'pyomo.contrib.aslfunctions', 'pyomo.contrib.mcpp', 'pyomo.contrib.mindtpy', 'pyomo.contrib.multistart', diff --git a/pyomo/environ/tests/test_package_layout.py b/pyomo/environ/tests/test_package_layout.py index cd6ca156c46..828fa757bbe 100644 --- a/pyomo/environ/tests/test_package_layout.py +++ b/pyomo/environ/tests/test_package_layout.py @@ -39,6 +39,7 @@ join('contrib', 'ampl_function_demo', 'src'), join('contrib', 'appsi', 'cmodel', 'src'), join('contrib', 'cspline_external', 'src'), + join('contrib', 'aslfunctions', 'src'), join('contrib', 'simplification', 'ginac', 'src'), join('contrib', 'pynumero', 'src'), join('core', 'tests', 'data', 'baselines'), diff --git a/setup.py b/setup.py index 043b0a637f2..d91f330c028 100644 --- a/setup.py +++ b/setup.py @@ -275,6 +275,8 @@ def __ne__(self, other): package_data={ "pyomo.contrib.ampl_function_demo": ["src/*"], "pyomo.contrib.appsi.cmodel": ["src/*"], + "pyomo.contrib.cspline_external": ["src/*"], + "pyomo.contrib.aslfunctions": ["src/*"], "pyomo.contrib.mcpp": ["*.cpp"], "pyomo.contrib.pynumero": ['src/*', 'src/tests/*'], "pyomo.contrib.viewer": ["*.ui"],