Source code for plugins.polynomial

#
##
##  SPDX-FileCopyrightText: © 2007-2023 Benedict Verhegghe <bverheg@gmail.com>
##  SPDX-License-Identifier: GPL-3.0-or-later
##
##  This file is part of pyFormex 3.4  (Thu Nov 16 18:07:39 CET 2023)
##  pyFormex is a tool for generating, manipulating and transforming 3D
##  geometrical models by sequences of mathematical operations.
##  Home page: https://pyformex.org
##  Project page: https://savannah.nongnu.org/projects/pyformex/
##  Development: https://gitlab.com/bverheg/pyformex
##  Distributed under the GNU General Public License version 3 or later.
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation, either version 3 of the License, or
##  (at your option) any later version.
##
##  This program is distributed in the hope that it will be useful,
##  but WITHOUT ANY WARRANTY; without even the implied warranty of
##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
##  GNU General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this program.  If not, see http://www.gnu.org/licenses/.
##
#

"""Polynomials

This module defines the class Polynomial, representing a polynomial
in n variables.
"""

import numpy as np
from pyformex import arraytools as at

[docs]class Polynomial(): """A polynomial in `n` dimensions. An n-dimensional polynomial is the sum of `nterms` terms, where each term is the product of a coefficient (a float constant) and a monomial in the `n` variables. A monomial is the product of each of the variables raised to a specific exponent. For example, in a 2-dim space, a polynomial in (x,y) could be:: 2 + 3 * x - x * y - y**2 This contains four terms. We store the monomials as an int array with shape (nterms, ndim) specifying for each term the exponents of each of the variables in the monomial. For the above example this becomes: [(0,0), (1,0), (1,1), 0,1)]. The `nterms` coefficients are stored as floats: [2.0, 3.0, -1.0, -1.0]. Parameters ---------- exp: :term:`array_like` An int array of shape (nterms,ndim) with the exponents of each of the ndim variables in each of the nterms terms of the polynomial. coeff: :term:`array_like` A float array with the coefficients of the terms. If not specified, all coefficients are set to 1. symbol:str, optional A string of length at least `ndim` with the symbols to be used for each of the `ndim` independent variables. The default is set for a 3-d polynomial. Examples -------- >>> p = Polynomial([(0,0),(1,0),(1,1),(0,2)],(2,3,-1,-1)) >>> p.exp array([[0, 0], [1, 0], [1, 1], [0, 2]]) >>> p.coeff array([ 2., 3., -1., -1.]) >>> print(p.atoms()) ['1', 'x', 'x*y', 'y**2'] >>> print(p) 2.0 +3.0*x -1.0*x*y -1.0*y**2 >>> print(repr(p)) Polynomial([[0, 0], [1, 0], [1, 1], [0, 2]], [2.0, 3.0, -1.0, -1.0], 'xyz') >>> print(Polynomial([(2,0), (1,1), (0,2)], (1,2,1), 'ab')) a**2 +2.0*a*b +b**2 """ def __init__(self, exp, coeff=None, symbol='xyz'): """Create an n-d polynomial""" self.exp = at.checkArray(exp, kind='i', ndim=2) if coeff is None: self.coeff = np.ones(self.nterms) else: self.coeff = at.checkArray(coeff, (self.nterms,), 'f', 'i') if len(symbol) < self.ndim: raise ValueError( f"Insufficient symbols for {self.ndim}-dim Polynimial") self.symbol = symbol @property def nterms(self): return self.exp.shape[0] @property def ndim(self): return self.exp.shape[1]
[docs] def degrees(self): """Return the degree of the polynomial in each of the dimensions. The degree is the maximal exponent for each of the dimensions. """ return self.exp.max(axis=0)
[docs] def degree(self): """Return the total degree of the polynomial. The degree is the sum of the degrees for all dimensions. """ return self.degrees().sum()
[docs] def atoms(self): """Return a human representation of the monomials Returns ------- list of str A list of the monomials in the Polynomial Examples -------- >>> Polynomial([(0,0),(1,0),(1,1),(0,2)]).atoms() ['1', 'x', 'x*y', 'y**2'] """ return [monomial(e, self.symbol) for e in self.exp]
[docs] def human(self): """Return a human representation Returns ------- str: A string representation of the Polynomial Examples -------- >>> Polynomial([(0,0),(1,0),(1,1),(0,2)]).human() '1 +x +x*y +y**2' """ mon = self.atoms() mon = [str(c)+'*'+m if c != 1 else m for c, m in zip(self.coeff, mon)] return ' +'.join(mon).replace('*1', '').replace('+-', '-')
__str__ = human def __repr__(self): return (f"Polynomial({self.exp.tolist()}, {self.coeff.tolist()}, " f"{self.symbol!r})")
[docs] def evalAtoms(self, x): """Evaluate the monomials at the given points Parameters ---------- x: :term:`array_like` An (npoints,ndim) array of points where the polynomial is to be evaluated. Returns ------- array The (npoints,nterms) array of values of the `nterms` monomials at the `npoints` points. Examples -------- >>> p = Polynomial([(0,0),(1,0),(1,1),(0,2)],(2,3,-1,-1)) >>> p.evalAtoms([[1,2],[3,0],[2,1]]) array([[1., 1., 2., 4.], [1., 3., 0., 0.], [1., 2., 2., 1.]]) """ x = at.checkArray(x, (-1, self.ndim), 'f', 'i') g = dict([(self.symbol[i], x[:, i]) for i in range(self.ndim)]) atoms = self.atoms() aa = np.zeros((len(x), len(atoms)), at.Float) for k, a in enumerate(atoms): aa[:, k] = eval(a, g) return aa
[docs] def eval(self, x): """Evaluate the polynomial at the given points Parameters ---------- x: :term:`array_like` An (npoints,ndim) array of points where the polynomial is to be evaluated. Returns ------- array The value of the polynomial at the `npoints` points. Examples -------- >>> p = Polynomial([(0,0),(1,0),(1,1),(0,2)],(2,3,-1,-1)) >>> print(p.eval([[1,2],[3,0],[2,1]])) [-1. 11. 5.] """ terms = self.evalAtoms(x) return (self.coeff*terms).sum(axis=-1)
# def polynomial(atoms, x, y=0, z=0): # """Build a matrix of functions of coords. # - `atoms`: a list of text strings representing a mathematical function of # `x`, and possibly of `y` and `z`. # - `x`, `y`, `z`: a list of x- (and optionally y-, z-) values at which the # `atoms` will be evaluated. The lists should have the same length. # Returns a matrix with `nvalues` rows and `natoms` colums. # """ # aa = np.zeros((len(x), len(atoms)), Float) # for k, a in enumerate(atoms): # aa[:, k] = eval(a) # return aa
[docs]def factor(sym, exp): """Return a string with a variable to some exponent Parameters ---------- sum: str The name of the variable exp: int The exponent to which the variable is raised Returns ------- str: The expression with the variable `sym` raised to the power `exp`. Examples -------- >>> factor('x', 3), factor('y', 1), factor('z', 0) ('x**3', 'y', '1') """ if exp == 0: return '1' elif exp == 1: return sym else: return sym+'**'+str(exp)
[docs]def monomial(exp, symbol='xyz'): """Return the monomial for the given exponents. Parameters ---------- exp: tuple of int The integer exponents to which to raise the ndim independent variables. symbol: str A string of at least the same length as `exp`, containing the variables to use for each of the ndim independent variables. The default is set for a 3-dimensional space. Returns ------- str A string representation of a monomial created by raising the symbols to the corresponding exponent. Examples -------- >>> monomial((2,1)) 'x**2*y' >>> monomial((0,3,2)) 'y**3*z**2' """ factors = [factor(symbol[i], j) for i, j in enumerate(exp)] # Join and sanitize (note we do not have '**1') return '*'.join(factors).replace('1*', '').replace('*1', '')
# End