0

I am using the following code to find piecewise polynomial functions

import scipy
from scipy.interpolate import UnivariateSpline, splrep

x = np.array([0., 0.75, 1.8, 2.25, 3.75, 4.5, 6.45, 6.75, 
          7.5, 8.325, 10.875, 11.25, 12.525, 12.75, 15., 
          20.85, 21.])
y = np.array([2.83811035, 2.81541896, 3.14311655, 
          3.22373554, 3.43033456, 3.50433385, 
          3.66794514, 3.462296,   3.59480959, 
          3.56250726, 3.6209845,  3.63034523, 
          3.68238915, 3.69096892, 3.75560395, 
          3.83545191, 3.90419498])
          

k = 3  # polynomial order
spl = UnivariateSpline(x, y, k=3, s=0.09)
xs = np.linspace(x.min(), x.max(), 100)
plt.plot(x, y, 'ro', ms=5)
plt.plot(xs, spl(xs), 'cyan', lw=5, alpha=0.3)

# get spline coeffs and knots
tck = (spl._data[8], spl._data[9], k)  # tck = (knots, coefficients, degree)
p = scipy.interpolate.PPoly.from_spline(tck)

# plot each segment and return knots and coeffs
for idx, i in enumerate(range(k, len(spl.get_knots()) + k - 1)):
    xs = np.linspace(p.x[i], p.x[i + 1], 100)
    plt.plot(xs, np.polyval(p.c[:, i], xs - p.x[i]))
    print("knot ", p.x[i], " to ", p.x[i + 1])
    print("coeffs ", p.c[:, i], "\n")
    f0 = lambda x: p.c[0, i] * (x - p.x[i]) ** 3 + p.c[1, i] * (x - p.x[i]) ** 2 + p.c[2, i] * (x - p.x[i]) + p.c[3, i]
    f0 = lambda x: [p.c[:, i] * (x - p.x[i]) ** (3 - i) for i in range(k + 1)]  # k = degree i.e no. of. coeffs = degree +1
    print(f0)
plt.show()

print(f0) outputs <function fit_spline1.<locals>.<lambda> at 0x0000028697B94F70>.

Instead, I would like to display the polynomial expression.

Suggestions on how to do this will be really helpful.

EDIT:

I found the following in a post https://stackoverflow.com/a/60114991/8281509 . But not sure how to use this for my case.

from sympy import lambdify, bspline_basis_set
from sympy.abc import u

basis = bspline_basis_set(tck[2], tck[0],  u)
for i, b in enumerate(basis):
    print(f"Basis {i} :", b)

returns 16 basis function. Instead I expect only 3 for my case

Natasha
  • 1,111
  • 5
  • 28
  • 66
  • You've 2 assignments to `f0`. The first one is simply overwritten by next assignment. `f0` is assigned lambda, so it is a function now and can be called as `f0(2.0)`. – Azhar Khan Sep 08 '22 at 05:09
  • @AzharKhan Thanks for the reply. Yes, both expressions are the same. I actually want to display the expression something like `Piecewise((259.449085976667*u**3 + 332.098590899285*u**2 - 53.8062007647187*u - 8.88178419700125e-16, (u >= -0.332135154281002) & (u <= 0.16061323829714))` – Natasha Sep 08 '22 at 05:15

1 Answers1

1

You can use the string formatting like this:

for idx, i in enumerate(range(k, len(spl.get_knots()) + k - 1)):
    xs = np.linspace(p.x[i], p.x[i + 1], 100)
    plt.plot(xs, np.polyval(p.c[:, i], xs - p.x[i]))
    print("knot ", p.x[i], " to ", p.x[i + 1])
    print("coeffs ", p.c[:, i], "\n")
    print(f"{p.c[0, i]} * {(x - p.x[i])} ** 3 + {p.c[1, i]} * {(x - p.x[i])} ** 2 + {p.c[2, i]} * {(x - p.x[i])} + {p.c[3, i]}")

Output:

0.00041014998689577995 * [ 0.     0.75   1.8    2.25   3.75   4.5    6.45   6.75   7.5    8.325
 10.875 11.25  12.525 12.75  15.    20.85  21.   ] ** 3 + -0.015832105893445334 * [ 0.     0.75   1.8    2.25   3.75   4.5    6.45   6.75   7.5    8.325
 10.875 11.25  12.525 12.75  15.    20.85  21.   ] ** 2 + 0.20309856945144328 * [ 0.     0.75   1.8    2.25   3.75   4.5    6.45   6.75   7.5    8.325
 10.875 11.25  12.525 12.75  15.    20.85  21.   ] + 2.8044698839070015

The array contents are printed. If you don't want it then replace it with literal u like this:

print(f"{p.c[0, i]} * u ** 3 + {p.c[1, i]} * u ** 2 + {p.c[2, i]} * u + {p.c[3, i]}")

Output:

0.00041014998689577995 * u ** 3 + -0.015832105893445334 * u ** 2 + 0.20309856945144328 * u + 2.8044698839070015
Azhar Khan
  • 3,829
  • 11
  • 26
  • 32