![]() | |||||||||||
| |||||||||||
Python notebooks Description Python notebooks available in the GitHub repository python-notebooks.
Source Files By clicking on a file name, a preview is opened at the bottom of this page. piecewise_linear.ipynb { "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# **Using piecewise linear functions**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "***piecewise_linear.ipynb***\n", "\n", "Example that uses [problem.addpwlcons()](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.addpwlcons.html) and [xpress.pwl()](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/xpress.pwl.html) to approximate a nonlinear univariate function.\n", "\n", "© Copyright 2025 Fair Isaac Corporation\n", "\n", "Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.\n", " \n", "Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n", "\n", "This example uses FICO® Xpress software. By running it, you agree to the Community License terms of the [Xpress Shrinkwrap License Agreement](https://community.fico.com/s/contentdocument/06980000002h0i5AAA) with respect to the FICO® Xpress software. See the [licensing options](https://www.fico.com/en/fico-xpress-trial-and-licensing-options) overview for additional details and information about obtaining a paid license." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Install the xpress package\n", "%pip install -q xpress" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this example, we approximate the nonlinear function $\\sin(5x), \\forall x \\in [0, 2/\\pi]$ using $N$ points.\n", "\n", "As a simple approximation, we work with breakpoints lying on the curve of the original nonlinear function, which are determined by the function value for a sample of specified $x$.\n", "\n", "Start by importing the necessary packages and define a function that returns the breakpoints ($x$ values) and their corresponding $y$ values as lists for a specified $N$ and frequency value." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import xpress as xp\n", "import math\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "def create_segments(N, freq):\n", " \"\"\"N: Number of breakpoints for an approximation with (N-1) segments\"\"\"\n", " \n", " step = (2 / math.pi) / (N - 1) # width of each segment = domain / (N-1)\n", " breakpoints = np.array([i * step for i in range(N)]) # x value of each breakpoint\n", " values = np.sin(freq * breakpoints) # value of the function at each breakpoint\n", "\n", " return breakpoints, values" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Approximation using *problem.addpwlcons()*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The [p.addpwlcons()](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/problem.addpwlcons.html) function can be used for creating and directly adding piecewise linear constraints to a problem by passing **the coordinates of each breakpoint**. \n", "\n", "Each piecewise linear constraint $y = f(x)$ consists of an (input) column $x$, a resultant (output column) $y$ and a piecewise linear function $f$. The piecewise linear function $f$ is described by a number of breakpoints, which are given as combinations of $x$ (breakpoints) and $y$ (values).\n", "\n", "An auxiliary variable $y$ is created to represent the approximated value for any given solution $x$. In our example, this variable is defined as the objective function **to be maximized**, but it could also be modeled via a transfer variable. The nonlinear function, as well as the approximated PWL function are then plotted." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "p = xp.problem()\n", "\n", "x = p.addVariable()\n", "\n", "N = 10 # number of breakpoints\n", "freq = 5 # frequency\n", "breakpoints, values = create_segments(N, freq) # breakpoints and values at breakpoints\n", "\n", "# Piecewise linear approximation using the p.addpwlcons() method\n", "pw = p.addVariable() # auxiliary variable to represent the 'resultant' array and set as objective to maximize\n", "\n", "p.addpwlcons([x], [pw], [0], breakpoints, values) \n", "\n", "p.setObjective(pw, xp.maximize)\n", "\n", "p.optimize()\n", "\n", "print(\"Solution: x = \", p.getSolution(x))\n", "print(\"Value of piecewise linear function:\", xp.evaluate(pw, problem=p))\n", "print(\"Objective function:\", p.attributes.objval)\n", "\n", "# High-resolution plot of the function for comparison\n", "breakpoints_high, slopes_high = create_segments(10000, freq)\n", "\n", "# Plot the results\n", "plt.figure(figsize=(10, 6))\n", "plt.plot(breakpoints, values, label='Approximation', linewidth=2)\n", "plt.plot(breakpoints_high, slopes_high, label='Real function', linewidth=2)\n", "plt.scatter(p.getSolution(x), p.getSolution(pw), color='green', zorder=5, label='Solution') # Add the point to the plot\n", "plt.xlabel('x')\n", "plt.ylabel('y')\n", "plt.title(f'Plot of sin({freq}x) and its approximation using {N} breakpoints')\n", "plt.legend(fontsize=12)\n", "plt.grid(True)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Approximation using *xpress.pwl()*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Alternatively, you can use the [xpress.pwl()](https://www.fico.com/fico-xpress-optimization/docs/latest/solver/optimizer/python/HTML/xpress.pwl.html) method, which returns a piecewise linear function over variable $x$, by taking a Python dictionary as an argument containing as:\n", " - keys: the intervals specified as two-elements tuples (must be pairwise disjoint, i.e., they must not overlap)\n", " - values: linear expressions in a variable. \n", "\n", "A piecewise linear function must use only one variable in all of the dictionary's values. Besides, all values in the dictionary must be either constants or linear functions.\n", "\n", "In this case, besides the breakpoints and corresponding values, we calculate the values for the derivative of the initial function to **compute the slopes** to be used in the formulation of the function values for each interval.\n", "\n", "*Note: the method p.addpwlcons() is better suited for cases where only the coordinates are known, while the xp.pwl() method is more intuitive when the slopes are known*" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "p = xp.problem() # create a problem and add variable x\n", "\n", "x = p.addVariable(ub=4)\n", "\n", "N = 10 # number of breakpoints\n", "freq = 5 # frequency\n", "breakpoints, values = create_segments(N, freq) # breakpoints and values of the function\n", "slopes = freq * np.cos(freq * breakpoints) # derivatives\n", "\n", "# Piecewise linear, discontinuous function over N points: over the\n", "# i-th interval, the function is equal to v[i] + s[i] * (y - b[i])\n", "# where v, s, b are value, slope, and breakpoint.\n", "pw = xp.pwl({(breakpoints[i], breakpoints[i+1]):\n", " values[i] + slopes[i] * (x - breakpoints[i]) for i in range(N - 1)})\n", "\n", "#p.addConstraint(1 >= pw)\n", "p.setObjective(pw, xp.maximize)\n", "\n", "p.optimize()\n", "\n", "print(\"Solution: x = \", p.getSolution(x))\n", "print(\"Value of piecewise linear function:\", xp.evaluate(pw, problem=p))\n", "print(\"Objective function:\", p.attributes.objval)\n", "\n", "# High-resolution plot of the function for comparison\n", "breakpoints1, slopes1 = create_segments(10000, freq)\n", "\n", "# Plot the results\n", "plt.figure(figsize=(10, 6))\n", "plt.plot(breakpoints, values, label='Approximation', linewidth=2)\n", "plt.plot(breakpoints1, slopes1, label='Real function', linewidth=2)\n", "plt.scatter(p.getSolution(x), p.getSolution(pw), color='green', zorder=5, label='Solution') # Add the point to the plot\n", "plt.xlabel('x')\n", "plt.ylabel('y')\n", "plt.title(f'Plot of sin({freq}x) and its approximation using {N} breakpoints')\n", "plt.legend(fontsize=12)\n", "plt.grid(True)\n", "plt.show()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.9" } }, "nbformat": 4, "nbformat_minor": 2 }
| |||||||||||
© Copyright 2025 Fair Isaac Corporation. |