Construct a symbolic interpolating spline through given points using SymPy - python

Pretend I start with some simple dataset which is defined on R2 follows:
DataPointsDomain = [0,1,2,3,4,5]
DataPointsRange = [3,6,5,7,9,1]
With scipy I can make a lazy polynomial spline using the following:
ScipySplineObject = scipy.interpolate.InterpolatedUnivariateSpline(
DataPointsDomain,
DataPointsRange,
k = 1, )
What is the equivalent object in sympy??
SympySplineObject = ...???
(I want to define this object and do analytic sympy manipulation like taking integrals, derivatives, etc... on the sympy object )

In SymPy versions above 1.1.1, including the current development version, there is a built-in method interpolating_spline which takes four arguments: the spline degree, the variable, domain values and range values.
from sympy import *
DataPointsDomain = [0,1,2,3,4,5]
DataPointsRange = [3,6,5,7,9,1]
x = symbols('x')
s = interpolating_spline(3, x, DataPointsDomain, DataPointsRange)
This returns
Piecewise((23*x**3/15 - 33*x**2/5 + 121*x/15 + 3, (x >= 0) & (x <= 2)),
(-2*x**3/3 + 33*x**2/5 - 55*x/3 + 103/5, (x >= 2) & (x <= 3)),
(-28*x**3/15 + 87*x**2/5 - 761*x/15 + 53, (x >= 3) & (x <= 5)))
which is a "not a knot" cubic spline through the given points.
Old answer
An interpolating spline can be constructed with SymPy, but this takes some effort. The method bspline_basis_set returns the basis of B-splines for given x-values, but then it's up to you to find their coefficients.
First, we need the list of knots, which is not exactly the same as the list of x-values (xv below). The endpoints xv[0] and xv[-1] will appear deg+1 times where deg is the degree of the spline, because at the endpoints all the coefficients change values (from something to zero). Also, some of the x-values close to them may not appear at all, as there will be no changes of coefficients there ("not a knot" conditions). Finally, for even-degree splines (yuck) the interior knots are placed midway between data points. So we need this helper function:
from sympy import *
def knots(xv, deg):
if deg % 2 == 1:
j = (deg+1) // 2
interior_knots = xv[j:-j]
else:
j = deg // 2
interior_knots = [Rational(a+b, 2) for a, b in zip(xv[j:-j-1], xv[j+1:-j])]
return [xv[0]] * (deg+1) + interior_knots + [xv[-1]] * (deg+1)
After getting b-splines from bspline_basis_set method, one has to plug in the x-values and form a linear system from which to find the coefficients coeff. At last, the spline is constructed:
xv = [0, 1, 2, 3, 4, 5]
yv = [3, 6, 5, 7, 9, 1]
deg = 3
x = Symbol("x")
basis = bspline_basis_set(deg, knots(xv, deg), x)
A = [[b.subs(x, v) for b in basis] for v in xv]
coeff = linsolve((Matrix(A), Matrix(yv)), symbols('c0:{}'.format(len(xv))))
spline = sum([c*b for c, b in zip(list(coeff)[0], basis)])
print(spline)
This spline is a SymPy object. Here it is for degree 3:
3*Piecewise((-x**3/8 + 3*x**2/4 - 3*x/2 + 1, (x >= 0) & (x <= 2)), (0, True)) + Piecewise((x**3/8 - 9*x**2/8 + 27*x/8 - 27/8, (x >= 3) & (x <= 5)), (0, True)) + 377*Piecewise((19*x**3/72 - 5*x**2/4 + 3*x/2, (x >= 0) & (x <= 2)), (-x**3/9 + x**2 - 3*x + 3, (x >= 2) & (x <= 3)), (0, True))/45 + 547*Piecewise((x**3/9 - 2*x**2/3 + 4*x/3 - 8/9, (x >= 2) & (x <= 3)), (-19*x**3/72 + 65*x**2/24 - 211*x/24 + 665/72, (x >= 3) & (x <= 5)), (0, True))/45 + 346*Piecewise((x**3/30, (x >= 0) & (x <= 2)), (-11*x**3/45 + 5*x**2/3 - 10*x/3 + 20/9, (x >= 2) & (x <= 3)), (31*x**3/180 - 25*x**2/12 + 95*x/12 - 325/36, (x >= 3) & (x <= 5)), (0, True))/45 + 146*Piecewise((-31*x**3/180 + x**2/2, (x >= 0) & (x <= 2)), (11*x**3/45 - 2*x**2 + 5*x - 10/3, (x >= 2) & (x <= 3)), (-x**3/30 + x**2/2 - 5*x/2 + 25/6, (x >= 3) & (x <= 5)), (0, True))/45
You can differentiate it, with
spline.diff(x)
You can integrate it:
integrate(spline, (x, 0, 5)) # 197/3
You can plot it and see it indeed interpolates the given values:
plot(spline, (x, 0, 5))
I even plotted them for degrees 1,2,3 together:
Disclaimers:
The code given above works in the development version of SymPy, and should work in 1.1.2+; there was a bug in B-spline method in previous versions.
Some of this takes a good deal of time because Piecewise objects are slow. In my experience, the basis construction takes longest.

Related

Optimal way to convolute continuous functions in python

I am trying to numerically compute in python integrals of the form
To that aim, I first define two discrete sets of x and t values, let's say
x_samples = np.linspace(-10, 10, 100)
t_samples = np.linspace(0, 1, 100)
dx = x_samples[1]-x_samples[0]
dt = t_samples[1]-t_samples[0]
declare symbolically that the function g(x,t) is equal to 0 if t<0 and discretise the two functions to integrate as
discretG = g(x_samples[None, :], t_samples[:, None])
discretH = h(x_samples[None, :], t_samples[:, None])
I have then tried to run
discretF = signal.fftconvolve(discretG, discretH, mode='full') * dx * dt
Yet, on basic test functions such as
g(x,t) = lambda x,t: np.exp(-np.abs(x))+t
h(x,t) = lambda x,t: np.exp(-np.abs(x))-t
I don't find an agreement between the the numerical integration and the convolution using scipy and I would like to have a fairly fast way of computing these integrals, especially when I only have access to discretised representations of the functions rather than their symbolic one.
According to your code, I assume you want to conduct convolution on two function g and h that are non-zero only on [a, b]*[m,n].
Of course you can use signal.fftconvolve to compute the convolution. The key is don't forget the transformation between the indices inside discretF and the real coordinates. Here I use interpolation to compute for arbitrary (x,t).
import numpy as np
from scipy import signal, interpolate
a = -1
b = 2
m = -10
n = 15
samples_num = 1000
x_eval_index = 200
t_eval_index = 300
x_samples = np.linspace(a, b, samples_num)
t_samples = np.linspace(m, n, samples_num)
dx = x_samples[1]-x_samples[0]
dt = t_samples[1]-t_samples[0]
g = lambda x,t: np.exp(-np.abs(x))+t
h = lambda x,t: np.exp(-np.abs(x))-t
discretG = g(x_samples[None, :], t_samples[:, None])
discretH = h(x_samples[None, :], t_samples[:, None])
discretF = signal.fftconvolve(discretG, discretH, mode='full')
def compute_f(x, t):
if x < 2*a or x > 2*b or t < 2*m or t > 2*n:
return 0
# use interpolation t get data on new point
x_samples_for_conv = np.linspace(2*a, 2*b, 2*samples_num-1)
t_samples_for_conv = np.linspace(2*m, 2*n, 2*samples_num-1)
f = interpolate.RectBivariateSpline(x_samples_for_conv, t_samples_for_conv, discretF.T)
return f(x, t)[0, 0] * dx * dt
Note: you can extend my codes to compute convolution on a meshgrid defined by x and y, where x and y are 1D array. (In my code, x and y are float now)
You can use the following code to explore the "agreement" between "the numerical integration" and "the convolution using scipy" (and also, the correctness of compute_f function above):
# how the convolve work
# for 1D f[i]=sigma_{j} g[j]h[i-j]
sum = 0
for y_idx, y in enumerate(x_samples[0:]):
for s_idx, s in enumerate(t_samples[0:]):
if x_eval_index - y_idx < 0 or t_eval_index - s_idx < 0:
continue
if t_eval_index - s_idx >= len(x_samples[0:]) or x_eval_index - y_idx >= len(t_samples[0:]):
continue
sum += discretG[t_eval_index - s_idx, x_eval_index - y_idx] * discretH[s_idx, y_idx] * dx * dt
print("Do discrete convolution manually, I get: %f" % sum)
print("Do discrete convolution using scipy, I get: %f" % (discretF[t_eval_index, x_eval_index] * dx * dt))
# numerical integral
# the x_val and t_val
# take 1D convolution as example, function defined on [a, b], and index of your samples range from [0, samples_num-1]
# after convolution, function defined on [2a, 2b], index of your samples range from [0, 2*samples_num-2]
dx_prime = (b-a) / (samples_num-1)
dt_prime = (n-m) / (samples_num-1)
x_eval = 2*a + x_eval_index * dx_prime
t_eval = 2*m + t_eval_index * dt_prime
sum = 0
for y in x_samples[:]:
for s in t_samples[:]:
if x_eval - y < a or x_eval - y > b:
continue
if t_eval - s < m or t_eval - s > n:
continue
if y < a or y >= b:
continue
if s < m or s >= n:
continue
sum += g(x_eval - y, t_eval - s) * h(y, s) * dx * dt
print("Do numerical integration, I get: %f" % sum)
print("The convolution result of 'compute_f' is: %f" % compute_f(x_eval, t_eval))
Which gives:
Do discrete convolution manually, I get: -154.771369
Do discrete convolution using scipy, I get: -154.771369
Do numerical integration, I get: -154.771369
The convolution result of 'compute_f' is: -154.771369

What is the difference between (matplotlib) and (SymPy Plotting Module)?

https://scicomp.stackexchange.com/questions/1144/how-can-i-plot-piece-wise-defined-function-in-some-easily-accessed-open-source-t
①Why is it that the above source code can be used to create a plot, but the following source code cannot?
②Can you point me to a web site that has a table comparing (matplotlib) and (SymPy Plotting Module)?
③(matplotlib) to (SymPy Plotting Module) converter and a (SymPy Plotting Module) to (matplotlib) converter would be helpful.
from sympy import *
def define_fn(n):
def fn(x):
if n <= x <= n + 1:
return float(x) - n
elif n + 1 <= x <= n + 2:
return 2.0 - x + n
else:
return 0.0
return fn
f3 = define_fn(3)
f8 = define_fn(8)
print("#",f3)
print("#",f8)
plot(f3,f8)
# <function define_fn.<locals>.fn at 0x000002474E838280>
# <function define_fn.<locals>.fn at 0x000002474E838310>
# SymPyDeprecationWarning: .............
Let's make sure you understand what the link and your adaptation is doing. Your question about the difference in the plot packages suggests you don't have a clear idea of what package is doing what.
You have defined two python functions, that take one number.
In [11]: f3(4)
Out[11]: 1.0
In [12]: f8(9)
Out[12]: 1.0
With just python we can create a list of x values:
In [17]: x = [i / 10 for i in range(120)]
the corresponding y values:
In [18]: y1 = [f3(i) for i in x]
In [19]: y2 = [f8(i) for i in x]
and plot them with:
In [20]: plt.plot(x, y1, x, y2)
Out[20]:
[<matplotlib.lines.Line2D at 0x7fe2589385e0>,
<matplotlib.lines.Line2D at 0x7fe258938610>]
# plotted as you show
The link use np.linspace to create the x values as an array, and np.vectorize to apply the function(s) to all these values. matplotlib works with both arrays and lists.
There's nothing fancy about this use of matplotlib.
Statements like:
if n <= x <= n + 1:
return float(x) - n
only work with single numbers. That's basic python. Using a numpy array for x or a sympy symbol or expression will result in an error.
If x is a sympy symbol:
In [3]: f3(x)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Input In [3], in <module>
----> 1 f3(x)
Input In [1], in define_fn.<locals>.fn(x)
2 def fn(x):
----> 3 if n <= x <= n + 1:
4 return float(x) - n
5 elif n + 1 <= x <= n + 2:
File /usr/local/lib/python3.8/dist-packages/sympy/core/relational.py:398, in Relational.__bool__(self)
397 def __bool__(self):
--> 398 raise TypeError("cannot determine truth value of Relational")
TypeError: cannot determine truth value of Relational
and with an array:
In [4]: f3(np.arange(3))
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Input In [4], in <module>
----> 1 f3(np.arange(3))
Input In [1], in define_fn.<locals>.fn(x)
2 def fn(x):
----> 3 if n <= x <= n + 1:
4 return float(x) - n
5 elif n + 1 <= x <= n + 2:
ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
①Why is it that the above source code can be used to create a plot, but the following source code cannot?
The linked example uses matplotlib which works with numeric inputs. Matplotlib doesn't know about sympy's world.
Sympy's plot module converts sympy expressions to numeric approximations and plots them using matplotlib. Sympy's plot module abstracts away a lot of intricacies to make this work, and hides these from the casual user.
②Can you point me to a web site that has a table comparing (matplotlib) and (SymPy Plotting Module)?
Matplotlib is very huge. Sympy's plotting module uses a subset of functionality, carefully and ingeniously adapted to the symbolic world. Differences don't fit into a table. Matplotlib's extensive documentation can be found at matplotlib.org, but most people only look into the subset they are using. Sympy's plotting documentation fits onto one large webpage. For both libraries you will need extra tutorials, StackOverlow, and maybe diving into the freely available source code if you need functionality that isn't readily available.
③(matplotlib) to (SymPy Plotting Module) converter and a (SymPy Plotting Module) to (matplotlib) converter would be helpful.
That would be a titanic work, with lots of undefined cases. Sympy (as well as matplotlib) is developed by very talented volunteers, with limited resources.
Note that, if you really want to, you can "move" sympy plots to the matplotlib world and extend the plot there.
Here is how your source code could look like in sympy.
First some remarks:
sympy functions and expressions can't work with Python's if-tests, you need symbolic functions such as PieceWise instead.
float(x) doesn't work for sympy's symbols
in general, sympy tries to avoid floats as they are approximate by definition while sympy almost always looks for exact symbolic solutions
from sympy import plot, Symbol, Piecewise, And
def define_fn(n):
def fn(x):
return Piecewise((x - n, (n <= x) & (x <= n + 1)),
(2 - x + n, (n + 1 <= x) & (x <= n + 2)),
(0, True))
return fn
f3 = define_fn(3)
f8 = define_fn(8)
x = Symbol('x', real=True)
plot(f3(x), f8(x), (x, -1, 11))
Although this works, it is not sympy's standard way. Usually, functions are written as expressions. Note how f3 is used as an expression that contains x, instead of the less flexible f3(x) of the previous example.
from sympy import plot, Symbol, Piecewise, And
x = Symbol('x', real=True)
n = Symbol('n', real=True)
fn = Piecewise((x - n, (n <= x) & (x <= n + 1)),
(2 - x + n, (n + 1 <= x) & (x <= n + 2)),
(0, True))
f3 = fn.subs(n, 3) # Piecewise((x - 3, (x >= 3) & (x <= 4)), (5 - x, (x >= 4) & (x <= 5)), (0, True))
f8 = fn.subs(n, 8) # Piecewise((x - 8, (x >= 8) & (x <= 9)), (10 - x, (x >= 9) & (x <= 10)), (0, True))
plot(f3, f8, (x, -1, 11))
print("#",type(f3),f3)
print("#",type(f8),f8)
# <class 'function'> <function define_fn.<locals>.fn at 0x00000246464B9280>
# <class 'function'> <function define_fn.<locals>.fn at 0x00000246464B9310>
print("#",type(f3(x)),f3(x))
print("#",type(f8(x)),f8(x))
# Piecewise Piecewise((x - 3, (x >= 3) & (x <= 4)), (5 - x, (x >= 4) & (x <= 5)), (0, True))
# Piecewise Piecewise((x - 8, (x >= 8) & (x <= 9)), (10 - x, (x >= 9) & (x <= 10)), (0, True))
print("#",type(f3),f3)
print("#",type(f8),f8)
# Piecewise Piecewise((x - 3, (x >= 3) & (x <= 4)), (5 - x, (x >= 4) & (x <= 5)), (0, True))
# Piecewise Piecewise((x - 8, (x >= 8) & (x <= 9)), (10 - x, (x >= 9) & (x <= 10)), (0, True))

Approximating sin using the Taylor series

I'm trying to calculate sin(x) using Taylor series without using factorials.
import math, time
import matplotlib.pyplot as plot
def sin3(x, i=30):
x %= 2 * math.pi
n = 0
dn = x**2 / 2
for c in range(4, 2 * i + 4, 2):
n += dn
dn *= -x**2 / ((c + 1) * (c + 2))
return x - n
def draw_graph(start = -800, end = 800):
y = [sin3(i/100) for i in range(start, end)]
x = [i/100 for i in range(start, end)]
y2 = [math.sin(i/100) for i in range(start, end)]
x2 = [i/100 for i in range(start, end)]
plot.fill_between(x, y, facecolor="none", edgecolor="red", lw=0.7)
plot.fill_between(x2, y2, facecolor="none", edgecolor="blue", lw=0.7)
plot.show()
When you run the draw_graph function it uses matplotlib to draw a graph, the redline is the output from my sin3 function, and the blue line is the correct output from the math.sin method.
As you can see the curve is not quite right, it's not high or low enough (seems to peak at 0.5), and also has strange behavior where it generates a small peak around 0.25 then drops down again. How can I adjust my function to match the correct output of math.sin?
You have the wrong equation for sin(x), and you also have a messed up loop invariant.
The formula for sin(x) is x/1! - x^3/3! + x^5/5! - x^7/7!..., so I really don't know why you're initializing dn to something involving x^2.
You also want to ask yourself: What is my loop invariant? What is the value of dn when I reach the start of my loop. It is clear from the way you update dn that you expect it to be something involving x^i / i!. Yet on the very first iteration of the loop, i=4, yet dn involves x^2.
Here is what you meant to write:
def sin3(x, i=30):
x %= 2 * math.pi
n = 0
dn = x
for c in range(1, 2 * i + 4, 2):
n += dn
dn *= -x**2 / ((c + 1) * (c + 2))
return n

Python SymPy factoring polynomial over complex numbers

I am using SymPy to factor polynomials over the complex numbers.
import sympy as sp
x, y = sp.symbols('x y')
z = sp.expand( (x - 1) * (y - 1) )
factorization_1 = sp.factor(z)
factorization_2 = sp.factor(z, extension = [sp.I])
Here, z has contains the expression xy - x - y + 1, and factorization_1 indeed has the expected value (x - 1)(y - 1), but for some reason factorization_2 has the value x - 1.
Where did the factor (y - 1) go?

Generate random locations within a triangular domain

I want to generate x and y having a uniform distribution and limited by [xmin,xmax] and [ymin,ymax]
The points (x,y) should be inside a triangle.
How can I solve such a problem?
Here's some code that generates points uniformly on an arbitrary triangle in the plane.
import random
def point_on_triangle(pt1, pt2, pt3):
"""
Random point on the triangle with vertices pt1, pt2 and pt3.
"""
x, y = sorted([random.random(), random.random()])
s, t, u = x, y - x, 1 - y
return (s * pt1[0] + t * pt2[0] + u * pt3[0],
s * pt1[1] + t * pt2[1] + u * pt3[1])
The idea is to compute a weighted average of the three vertices, with the weights given by a random break of the unit interval [0, 1] into three pieces (uniformly over all such breaks). Here x and y represent the places at which we break the unit interval, and s, t and u are the length of the pieces following that break. We then use s, t and u as the barycentric coordinates of the point in the triangle.
Here's a variant of the above that avoids the need to sort, instead making use of an absolute value call:
def point_on_triangle2(pt1, pt2, pt3):
"""
Random point on the triangle with vertices pt1, pt2 and pt3.
"""
x, y = random.random(), random.random()
q = abs(x - y)
s, t, u = q, 0.5 * (x + y - q), 1 - 0.5 * (q + x + y)
return (
s * pt1[0] + t * pt2[0] + u * pt3[0],
s * pt1[1] + t * pt2[1] + u * pt3[1],
)
Here's an example usage that generates 10000 points in a triangle:
pt1 = (1, 1)
pt2 = (2, 4)
pt3 = (5, 2)
points = [point_on_triangle(pt1, pt2, pt3) for _ in range(10000)]
And a plot obtained from the above, demonstrating the uniformity. The plot was generated by this code:
import matplotlib.pyplot as plt
x, y = zip(*points)
plt.scatter(x, y, s=0.1)
plt.show()
Here's the image:
And since you tagged the question with the "numpy" tag, here's a NumPy version that generates multiple samples at once. Note that it uses the matrix multiplication operator #, introduced in Python 3.5 and supported in NumPy >= 1.10. You'll need to replace that with a call to np.dot on older Python or NumPy versions.
import numpy as np
def points_on_triangle(v, n):
"""
Give n random points uniformly on a triangle.
The vertices of the triangle are given by the shape
(2, 3) array *v*: one vertex per row.
"""
x = np.sort(np.random.rand(2, n), axis=0)
return np.column_stack([x[0], x[1]-x[0], 1.0-x[1]]) # v
# Example usage
v = np.array([(1, 1), (2, 4), (5, 2)])
points = points_on_triangle(v, 10000)
Ok, time to add another version, I guess. There is known algorithm to sample uniformly in triangle, see paper, chapter 4.2 for details.
Python code:
import math
import random
import matplotlib.pyplot as plt
def trisample(A, B, C):
"""
Given three vertices A, B, C,
sample point uniformly in the triangle
"""
r1 = random.random()
r2 = random.random()
s1 = math.sqrt(r1)
x = A[0] * (1.0 - s1) + B[0] * (1.0 - r2) * s1 + C[0] * r2 * s1
y = A[1] * (1.0 - s1) + B[1] * (1.0 - r2) * s1 + C[1] * r2 * s1
return (x, y)
random.seed(312345)
A = (1, 1)
B = (2, 4)
C = (5, 2)
points = [trisample(A, B, C) for _ in range(10000)]
xx, yy = zip(*points)
plt.scatter(xx, yy, s=0.2)
plt.show()
And result looks like
Uniform on the triangle?
import numpy as np
N = 10 # number of points to create in one go
rvs = np.random.random((N, 2)) # uniform on the unit square
# Now use the fact that the unit square is tiled by the two triangles
# 0 <= y <= x <= 1 and 0 <= x < y <= 1
# which are mapped onto each other (except for the diagonal which has
# probability 0) by swapping x and y.
# We use this map to send all points of the square to the same of the
# two triangles. Because the map preserves areas this will yield
# uniformly distributed points.
rvs = np.where(rvs[:, 0, None]>rvs[:, 1, None], rvs, rvs[:, ::-1])
Finally, transform the coordinates
xmin, ymin, xmax, ymax = -0.1, 1.1, 2.0, 3.3
rvs = np.array((ymin, xmin)) + rvs*(ymax-ymin, xmax-xmin)
Uniform marginals? The simplest solution would be to uniformly concentrate the mass on the line (ymin, xmin) - (ymax, xmax)
rvs = np.random.random((N,))
rvs = np.c_[ymin + (ymax-ymin)*rvs, xmin + (xmax-xmin)*rvs]
but that is not very interesting, is it?

Categories

Resources