Scaling tangent lines in a bezier curve - python

I am currently working on a project about Bezier curves and their properties. I am struggling to understand a concept. I can't seem to understand why you need a scale for the tangent line in a Bezier curve.
This is my code that does the animation.
from tkinter import Tk, Canvas
from graphics_template import *
import math, time
vp_width, vp_height = 1024, 768
w_xmin, w_ymin, w_xmax = -3, -3, 10
w_ymax = w_ymin + (w_xmax - w_xmin)/vp_width * vp_height
B2 = [[0.0, 0.0], # point 0
[7.0, 0.0], # point 1
[1.0, 4.0]] # point 2
V_pos = [] # position
V_vec = [] # velocity vector
animation_done = False
DELTA_TDRAW = 0.02 # 50 fps
def eval_Bezier2(P, t):
# P(t) = (1-t)^2P[0] + 2t(1-t) P[1] + t^2P[2]
res = [0.0, 0.0]
for xy in range(2):
res[xy] = (1-t)*(1-t)*P[0][xy] + 2*t*(1-t)*P[1][xy] + t*t*P[2][xy]
return res
def eval_dBezier2(P, t):
# P'(t) = -2(1-t)P[0] + 2(1-t)P[1]-2tP[1] + 2tP[2]
res = [0.0, 0.0]
for xy in range(2):
res[xy] = -2*(1-t)*P[0][xy] + 2*(1-t)*P[1][xy]-2*t*P[1][xy] + 2*t*P[2][xy]
return res
def draw_Bezier (P, nsteps):
xi = P[0][0]
yi = P[0][1]
t_delta = 1/nsteps
t = t_delta
for ti in range(nsteps):
p = eval_Bezier2(P, t)
draw_line(canvas, xi, yi, p[0], p[1], rgb_col(255, 0 ,0))
draw_small_square(canvas, xi, yi, rgb_col(255, 255, 0))
xi = p[0]
yi = p[1]
t += t_delta
for i in range(len(P)):
draw_small_square(canvas, P[i][0], P[i][1], rgb_col(0, 255, 0))
def do_animation (t):
global animation_done
v_factor = 5 # reparameterization
u = t/v_factor
if (t > v_factor): #animation stops at t = v_factor
animation_done = True
else:
current_pos = eval_Bezier2(B2, u);
V_pos[0] = current_pos[0]
V_pos[1] = current_pos[1]
current_vel = eval_dBezier2(B2, u);
V_vec[0] = current_vel[0]
V_vec[1] = current_vel[1]
def draw_scene ():
draw_grid(canvas)
draw_axis(canvas)
draw_Bezier(B2, 20)
draw_dot(canvas, V_pos[0], V_pos[1], rgb_col(0,255,0))
draw_line(canvas, V_pos[0], V_pos[1], (V_pos[0] + V_vec[0]), (V_pos[1] + V_vec[1]), rgb_col(0,255,0))
def init_scene ():
#no data inits needed
V_pos.append(0.0)
V_pos.append(0.0)
V_vec.append(0.0)
V_vec.append(0.0)
do_animation(0.0)
draw_scene()
window = Tk()
canvas = Canvas(window, width=vp_width, height=vp_height, bg=rgb_col(0,0,0))
canvas.pack()
init_graphics (vp_width, vp_height, w_xmin, w_ymin, w_xmax)
# time.perf_counter() -> float. Return the value (in fractional seconds)
# of a performance counter, i.e. a clock with the highest available resolution
# to measure a short duration. It does include time elapsed during sleep and
# is system-wide. The reference point of the returned value is undefined,
# so that only the difference between the results of consecutive calls is valid.
init_time = time.perf_counter()
prev_draw_time = 0
init_scene ()
while (not animation_done):
draw_dt = time.perf_counter() - init_time - prev_draw_time
if (draw_dt > DELTA_TDRAW): # 50 fps
prev_draw_time += DELTA_TDRAW
do_animation(prev_draw_time)
canvas.delete("all")
draw_scene()
canvas.update()
As you can see, the formulas work and are correct. As you also can see in the do_animation() function, there is a v_factor that makes the t smaller. This is because the animation goes at 50 frames per second. Every t that comes in is 0.02, this is divided by 5 to make the point move accross the curve for 5 seconds before reaching u = 1.
As you may notice, I divided the velocity vector by the v_factor, I don't understand this concept. I know my v_factor is a scale, but I don't understand why I need it. Doesn't the derivative of the bezier curve just output the correct velocity at every point in the curve? I only know that when I remove the v_factor from there, my velocity vector would become too big to fit my screen. As I said, I know it's a scale, but why do I need it? Why don't I need it for the V_pos vector? I tried to understand this concept from this stackoverflow post: How to calculate tangent for cubic bezier curve?, but unfortunately to no succes.

The velocity at any point B(t) is the derivative vector B'(t) so you got that part right, but the derivative (remember not to call it the tangent: tangents are true lines without a start or end point) at a point tells you the velocity "over one unit of time". And inconveniently, an entire Bezier curve only covers a single unit of time (with the t parameter running from 0 to 1)...
So, if we space our t values using some smaller-than-one value, like your curve does by using 20 points (so a t interval of 1/20), we need to scale all those derivatives by 1/(point count) too.
If we look at the unscaled derivatives, they are unwieldy and huge:
But if we scale them by 1/20 so that they represent the velocity vector for the time interval we're actually using, things look really good:
And we see an error between "where the true next point is" and "where the previous point plus its velocity vector says it'll be" based on curvature: the stronger the curvature, the further away from the "true point" a "previous point + velocity over time interval" will be, which we can mitigate by using more points.
If we use only 8 steps in our curve, with the derivatives scaled by 1/8, things don't look all that great:
If we bump that up to 15, with 1/15 scaling, things start to look better:
And while your curve with 20 points looks alright, let's look at what 50 points with 1/50 scaling gets us:
That's preeeetty good.

Other answers are very good, but I would just like to point out that the formula for the scaled derivative is actually the reciprocal with the order of the derivative.
If time interval goes from 0 to 5, the actual velocity is v<sub>5</sub>(t) = v(t) / 5², and the actual acceleration is a<sub>5</sub>(t) = a(t) / 5³. Or more generally, if your interval runs from 0 to x, v(x, t) = v(t) / x² and a(x, t) = a(t) / x³.
So if your goal is to scale the velocity curve, instead of breaking the line into pieces, you could make the time interval longer. Note that for the regular interval of [0,1], you would divide it by 1 in both cases, and it would be the same! This makes some intuitive sense, as the faster you want to complete the curve the faster the dot needs to go.

I don't see you dividing the velocity vector by v_factor. I guess you took that part out?
Anyway, this problem is probably just some elementary calculus. You are evaluating your Bezier curve in terms of the parameter u, and when you calculate the derivatives, you are getting dx/du and dy/du.
It seems that you want to want to show the velocities w.r.t. time, so you need dx/dt and dy/dt. By the chain rule, dx/dt = dx/du * du/dt, and du/dt = 1/v_factor. That's why you need to divide the velocity vector.

Related

Planetary orbit shown as linear graph using rk4

I am trying to simulate the orbit of a planet around a star using the Runge-Kutta 4 method. After speaking to tutors my code should be correct. However, I am not generating my expected 2D orbital plot but instead a linear plot. This is my first time using solve_ivp to solve a second order differential. Can anyone explain why my plots are wrong?
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import solve_ivp
# %% Define derivative function
def f(t, z):
x = z[0] # Position x
y = z[1] # Position y
dx = z[2] # Velocity x
dy = z[3] # Velocity y
G = 6.674 * 10**-11 # Gravitational constant
M = 2 # Mass of binary stars in solar masses
c = 2*G*M
r = np.sqrt(y**2 + x**2) # Distance of planet from stars
zdot = np.empty(6) # Array for integration solutions
zdot[0] = x
zdot[1] = y
zdot[2] = dx # Velocity x
zdot[3] = dy #Velocity y
zdot[4] = (-c/(r**3))*(x) # Acceleration x
zdot[5] = (-c/(r**3))*(y) # Acceleration y
return zdot
# %% Define time spans, initial values, and constants
tspan = np.linspace(0., 10000., 100000000)
xy0 = [0.03, -0.2, 0.008, 0.046, 0.2, 0.3] # Initial positions x,y in R and velocities
# %% Solve differential equation
sol = solve_ivp(lambda t, z: f(t, z), [tspan[0], tspan[-1]], xy0, t_eval=tspan)
# %% Plot
#plot
plt.grid()
plt.subplot(2, 2, 1)
plt.plot(sol.y[0],sol.y[1], color='b')
plt.subplot(2, 2, 2)
plt.plot(sol.t,sol.y[2], color='g')
plt.subplot(2, 2, 3)
plt.plot(sol.t,sol.y[4], color='r')
plt.show()
With the ODE function as given, you are solving in the first components the system
xdot = x
ydot = y
which has well-known exponential solutions. As the exponential factor is the same long both solutions, the xy-plot will move along a line through the origin.
The solution is of course to fill zdot[0:2] with dx,dy, and zdot[2:4] with ax,ay or ddx,ddy or however you want to name the components of the acceleration. Then the initial state also has only 4 components. Or you need to make and treat position and velocity as 3-dimensional.
You need to put units to your constants and care that all use the same units. G as cited is in m^3/kg/s^2, so that any M you define will be in kg, any length is in m and any velocity in m/s. Your constants might appear ridiculously small in that context.
It does not matter what the comment in the code says, there will be no magical conversion. You need to use actual conversion computations to get realistic numbers. For instance using the numbers
G = 6.67408e-11 # m^3 s^-2 kg^-1
AU = 149.597e9 # m
Msun = 1.988435e30 # kg
hour = 60*60 # seconds in an hour
day = hour * 24 # seconds in one day
year = 365.25*day # seconds in a year (not very astronomical)
one could guess that for a sensible binary system of two stars of equal mass one has
M = 2*Msun # now actually 2 sun masses
x0 = 0.03*AU
y0 = -0.2*AU
vx0 = 0.008*AU/day
vy0 = 0.046*AU/day
For the position only AU makes sense as unit, the speed could also be in AU/hour. By https://math.stackexchange.com/questions/4033996/developing-keplers-first-law and Cannot get RK4 to solve for position of orbiting body in Python the speed for a circular orbit of radius R=0.2AU around a combined mass of 2*M is
sqrt(2*M*G/R)=sqrt(4*Msun*G/(0.2*AU)) = 0.00320 * AU/hour = 0.07693 AU/day
which is ... not too unreasonable if the given speeds are actually in AU/day. Invoke the computations from https://math.stackexchange.com/questions/4050575/application-of-the-principle-of-conservation to compute if the Kepler ellipse would look sensible
r0 = (x0**2+y0**2)**0.5
dotr0 = (x0*vx0+y0*vy0)/r0
L = x0*vy0-y0*vx0 # r^2*dotphi = L constant, L^2 = G*M_center*R
dotphi0 = L/r0**2
R = L**2/(G*2*M)
wx = R/r0-1; wy = -dotr0*(R/(G*2*M))**0.5
E = (wx*wx+wy*wy)**0.5; psi = m.atan2(wy,wx)
print(f"half-axis R={R/AU} AU, eccentr. E={E}, init. angle psi={psi}")
print(f"min. rad. = {R/(1+E)/AU} AU, max. rad. = {R/(1-E)/AU} AU")
which returns
half-axis R=0.00750258 AU, eccentr. E=0.96934113, init. angle psi=3.02626659
min. rad. = 0.00380969 AU, max. rad. = 0.24471174 AU
This gives an extremely thin ellipse, which is not that astonishing as the initial velocity points almost directly to the gravity center.
orbit variants with half-day steps marked, lengths in AU
If the velocity components were swapped one would get
half-axis R=0.07528741 AU, eccentr. E=0.62778767, init. angle psi=3.12777251
min. rad. = 0.04625137 AU, max. rad. = 0.20227006 AU
This is a little more balanced.

Solve a second order ode using numpy [duplicate]

I am solving an ODE for an harmonic oscillator numerically with Python. When I add a driving force it makes no difference, so I'm guessing something is wrong with the code. Can anyone see the problem? The (h/m)*f0*np.cos(wd*i) part is the driving force.
import numpy as np
import matplotlib.pyplot as plt
# This code solves the ODE mx'' + bx' + kx = F0*cos(Wd*t)
# m is the mass of the object in kg, b is the damping constant in Ns/m
# k is the spring constant in N/m, F0 is the driving force in N,
# Wd is the frequency of the driving force and x is the position
# Setting up
timeFinal= 16.0 # This is how far the graph will go in seconds
steps = 10000 # Number of steps
dT = timeFinal/steps # Step length
time = np.linspace(0, timeFinal, steps+1)
# Creates an array with steps+1 values from 0 to timeFinal
# Allocating arrays for velocity and position
vel = np.zeros(steps+1)
pos = np.zeros(steps+1)
# Setting constants and initial values for vel. and pos.
k = 0.1
m = 0.01
vel0 = 0.05
pos0 = 0.01
freqNatural = 10.0**0.5
b = 0.0
F0 = 0.01
Wd = 7.0
vel[0] = vel0 #Sets the initial velocity
pos[0] = pos0 #Sets the initial position
# Numerical solution using Euler's
# Splitting the ODE into two first order ones
# v'(t) = -(k/m)*x(t) - (b/m)*v(t) + (F0/m)*cos(Wd*t)
# x'(t) = v(t)
# Using the definition of the derivative we get
# (v(t+dT) - v(t))/dT on the left side of the first equation
# (x(t+dT) - x(t))/dT on the left side of the second
# In the for loop t and dT will be replaced by i and 1
for i in range(0, steps):
vel[i+1] = (-k/m)*dT*pos[i] + vel[i]*(1-dT*b/m) + (dT/m)*F0*np.cos(Wd*i)
pos[i+1] = dT*vel[i] + pos[i]
# Ploting
#----------------
# With no damping
plt.plot(time, pos, 'g-', label='Undampened')
# Damping set to 10% of critical damping
b = (freqNatural/50)*0.1
# Using Euler's again to compute new values for new damping
for i in range(0, steps):
vel[i+1] = (-k/m)*dT*pos[i] + vel[i]*(1-(dT*(b/m))) + (F0*dT/m)*np.cos(Wd*i)
pos[i+1] = dT*vel[i] + pos[i]
plt.plot(time, pos, 'b-', label = '10% of crit. damping')
plt.plot(time, 0*time, 'k-') # This plots the x-axis
plt.legend(loc = 'upper right')
#---------------
plt.show()
The problem here is with the term np.cos(Wd*i). It should be np.cos(Wd*i*dT), that is note that dT has been added into the correct equation, since t = i*dT.
If this correction is made, the simulation looks reasonable. Here's a version with F0=0.001. Note that the driving force is clear in the continued oscillations in the damped condition.
The problem with the original equation is that np.cos(Wd*i) just jumps randomly around the circle, rather than smoothly moving around the circle, causing no net effect in the end. This can be best seen by plotting it directly, but the easiest thing to do is run the original form with F0 very large. Below is F0 = 10 (ie, 10000x the value used in the correct equation), but using the incorrect form of the equation, and it's clear that the driving force here just adds noise as it randomly moves around the circle.
Note that your ODE is well behaved and has an analytical solution. So you could utilize sympy for an alternate approach:
import sympy as sy
sy.init_printing() # Pretty printer for IPython
t,k,m,b,F0,Wd = sy.symbols('t,k,m,b,F0,Wd', real=True) # constants
consts = {k: 0.1, # values
m: 0.01,
b: 0.0,
F0: 0.01,
Wd: 7.0}
x = sy.Function('x')(t) # declare variables
dx = sy.Derivative(x, t)
d2x = sy.Derivative(x, t, 2)
# the ODE:
ode1 = sy.Eq(m*d2x + b*dx + k*x, F0*sy.cos(Wd*t))
sl1 = sy.dsolve(ode1, x) # solve ODE
xs1 = sy.simplify(sl1.subs(consts)).rhs # substitute constants
# Examining the solution, we note C3 and C4 are superfluous
xs2 = xs1.subs({'C3':0, 'C4':0})
dxs2 = xs2.diff(t)
print("Solution x(t) = ")
print(xs2)
print("Solution x'(t) = ")
print(dxs2)
gives
Solution x(t) =
C1*sin(3.16227766016838*t) + C2*cos(3.16227766016838*t) - 0.0256410256410256*cos(7.0*t)
Solution x'(t) =
3.16227766016838*C1*cos(3.16227766016838*t) - 3.16227766016838*C2*sin(3.16227766016838*t) + 0.179487179487179*sin(7.0*t)
The constants C1,C2 can be determined by evaluating x(0),x'(0) for the initial conditions.

Verification of 3D Fractal Slice-Rendering Method

Ray Casting Algorithm
MandelBulb Ray Casting Algorithm Python Example
So, if I understand correctly, the ray casting algorithm requires that an observer be located external to the 3D fractal at which point vectors are drawn from the observer toward a point on the plane normal to the vector and intersecting the origin.
It would seem to me that this would either severely limit the rendered view of the fractal or require stereoscopic 3D reconstruction of the fractal using multiple observer positions (which seems difficult to me). Additionally, no information can be gathered regarding the internal structure of the fractal.
Other Algorithms
Alternatively, Direct Volume Rendering seems intuitive enough however, computationally expensive and potentially inefficient in and of itself. Indirect Volume Rendering using an algorithm such as marching cubes might also employ a bit of a learning curve it seems.
Somewhere in the pdf of the 2nd link it talks about cut plane views in order to see slices of the fractal.
Question:
Why not use cut planes as a rendering method?
1) Using a modified ray tracing algorithm, say we put the observer at point Q at the origin (0, 0, 0).
2) Let us then emit rays from the origin toward the incident plane spanned by y & z point combinations that is slicing the fractal.
3) Calculate the distance to the fractal surface using the algorithm in the 1st link. If the x component of computed distance is within a certain tolerance, dx of the slicing plane, then the y & z coordinates along with the x value of the slicing plane are stored as the x, y, z coordinates. These coordinates are now representative of the surface at that specific slice in x.
4) Let us say that the slicing plane has one degree of freedom in the x direction. By moving the plane in its degree of freedom, we can receive yet another set of x, y, z coordinates for a given slice.
5) The final result is a calculable surface generated by the point cloud created in the previous steps.
6) Additionally, the degree of freedom of the slicing plane can be altered to create an another point cloud which can then be verified against the previous as a means of post-processing.
Please see the image below as a visual aid (the sphere represents the MandelBulb).
Below is my Python code so far, adapted from the first link. I successfully generate the plane of points and am able to get the directions from the origin to the points on the plane. There must be something fundamentally flawed in the distance estimator function because thats where everything breaks down and I get nans for the total distances
def get_plane_points(x, y_res=500, z_res=500, y_min=-10, y_max=10, z_min=-10, z_max=10):
y = np.linspace(y_min, y_max, y_res)
z = np.linspace(z_min, z_max, z_res)
x, y, z = np.meshgrid(x, y, z)
x, y, z = x.reshape(-1), y.reshape(-1) , z.reshape(-1)
P = np.vstack((x, y, z)).T
return P
def get_directions(P):
v = np.array(P - 0)
v = v/np.linalg.norm(v, axis=1)[:, np.newaxis]
return v
#jit
def DistanceEstimator(positions, plane_loc, iterations, degree):
m = positions.shape[0]
x, y, z = np.zeros(m), np.zeros(m), np.zeros(m)
x0, y0, z0 = positions[:, 0], positions[:, 1], positions[:, 2]
dr = np.zeros(m) + 1
r = np.zeros(m)
theta = np.zeros(m)
phi = np.zeros(m)
zr = np.zeros(m)
for _ in range(iterations):
r = np.sqrt(x * x + y * y + z * z)
dx = .01
x_loc = plane_loc
idx = (x < x_loc + dx) & (x > x_loc - dx)
dr[idx] = np.power(r[idx], degree - 1) * degree * dr[idx] + 1.0
theta[idx] = np.arctan2(np.sqrt(x[idx] * x[idx] + y[idx] * y[idx]), z[idx])
phi[idx] = np.arctan2(y[idx], x[idx])
zr[idx] = r[idx] ** degree
theta[idx] = theta[idx] * degree
phi[idx] = phi[idx] * degree
x[idx] = zr[idx] * np.sin(theta[idx]) * np.cos(phi[idx]) + x0[idx]
y[idx] = zr[idx] * np.sin(theta[idx]) * np.sin(phi[idx]) + y0[idx]
z[idx] = zr[idx] * np.cos(theta[idx]) + z0[idx]
return 0.5 * np.log(r) * r / dr
def trace(directions, plane_location, max_steps=50, iterations=50, degree=8):
total_distance = np.zeros(directions.shape[0])
keep_iterations = np.ones_like(total_distance)
steps = np.zeros_like(total_distance)
for _ in range(max_steps):
positions = total_distance[:, np.newaxis] * directions
distance = DistanceEstimator(positions, plane_location, iterations, degree)
total_distance += distance * keep_iterations
steps += keep_iterations
# return 1 - (steps / max_steps) ** power
return total_distance
def run():
plane_location = 2
plane_points = get_plane_points(x=plane_location)
directions = get_directions(plane_points)
distance = trace(directions, plane_location)
return distance
I am eager to hear thoughts on this and what limitations/issues I may encounter. Thanks in advance for the help!
If I am not mistaken, it is not impossible for this algorithm to work. There is inherent potential for problems with any assumptions made about the internal structure of the MandelBulb and what positions an observer is allowed to occupy. That is, if the observer is known to initially be in a zone of convergence then the ray tracing algorithm with return nothing meaningful since the furthest distance that could be measured is 0. This is due to the fact that the current ray tracing algorithm terminates upon first contact with the surface. It is likely this could be altered, however.
Rather than slicing the fractal with plane P, it might make more sense to prevent the termination of the ray upon first contact and instead, terminate based on a distance thats known to exist past the surface of the MandelBulb.

How can I get a fast estimate for the distance between a point and a bicubic spline surface in Python?

How can I get a fast estimate for the distance between a point and a bicubic spline surface in Python? Is there an existing solution that I could leverage in SciPy, NumPy, or some other package?
I've got surface defined by a bicubic interpolation as this:
import numpy as np
import scipy.interpolate
# Define regular grid surface
xmin,xmax,ymin,ymax = 25, 125, -50, 50
x = np.linspace(xmin,xmax, 201)
y = np.linspace(ymin,ymax, 201)
xx, yy = np.meshgrid(x, y)
z_ideal = ( xx**2 + yy**2 ) / 400
z_ideal += z_ideal + np.random.uniform(-0.5, 0.5, z_ideal.shape)
s_ideal = scipy.interpolate.interp2d(x, y, z_ideal, kind='cubic')
and I've got some measured points of that surface:
# Fake some measured points on the surface
z_measured = z_ideal + np.random.uniform(-0.1, 0.1, z_ideal.shape)
s_measured = scipy.interpolate.interp2d(x, y, z_measured, kind='cubic')
p_x = np.random.uniform(xmin,xmax,10000)
p_y = np.random.uniform(ymin,ymax,10000)
p_z = s_measured( p_x, p_y )
I want to find the closest point on the surface s_ideal to each point in p. A general case could have multiple solutions for wildly varying splines, so I'm limiting the problem to surfaces that are known to have only one solution in the vicinity of the point's projection along z.
This isn't a tiny number of measurement or surface definition points, so I'd like to optimize the speed even at the expense of accuracy to maybe 1E-5.
The method that comes to mind is to use a gradient descent approach and do something like for each measurement point p:
Use pt = [p_x, p_y, p_z] as the initial test point, where p_z = s_ideal(pt)
Calculate the slope (tangent) vector m = [ m_x, m_y ] at pt
Calculate the vector r from pt to p: r = p - pt
If the angle theta between r and m is within some threshold of 90 deg, then pt is the final point.
Otherwise, update pt as:
r_len = numpy.linalg.norm(r)
dx = r_len * m_x
dy = r_len * m_y
if theta > 90:
pt = [ p_x + dx, p_y + dy ]
else:
pt = [ p_x - dx, p_y - dy ]
I found this suggesting a method could produce fast results to a very high accuracy for the 1D case, but it's for a single dimension and might be too hard for me to convert to two.
The question seeks to minimize the Euclidian distance between a three-dimensional surface S(x,y,z) and another point x0,y0,z0. The surface is defined on a rectangular (x,y) mesh, where z(x,y) = f(x,y) + random_noise(x,y). The introduction of noise to the "ideal" surface adds considerable complexity to the problem, as it requires the surface to be interpolated using a 2-dimensional third-order spline.
It's not understood why the introduction of noise to the ideal surface is actually necessary. If the ideal surface were truly ideal, it should by understood well enough that a true polynomial fit in x and y could be determined, if not analytically, at least empirically. If the random noise were to simulate an actual measurement, then one only needs to record the measurement enough times until the noise is averaged out to zero. Similarly, the use of signal filtering can help eliminate noise and reveal the signal's true behavior.
To find the closest point on the surface to another point, the distance equation and its derivatives must be used. If the surface can truly only be described using a basis of splines, then one must reconstruct the spline representation and find its derivatives, which is non-trivial. Alternatively, the surface could be evaluated using a fine mesh, but here, one quickly runs into memory issues, which is why interpolation was used in the first place.
However, if we can agree that the surface can be defined using a simple expression in x and y, then the minimization becomes trivial:
For purposes of minimization, it is more convenient to look at the square of the distance d^2(x,y) (z is just a function of x and y) between two points, D(x,y), as it eliminates the square root. To find the critical points of D(x,y), we take its partial derivatives w.r.t x and y and find their roots by setting = 0: d/dx D(x,y) = f1(x,y) = 0 and d/dy D(x,y) = f2(x,y)=0. This is a non-linear system of equations, for which we can solve using scipy.optimize.root. We need only pass root a guess (the projection of the pt of interest onto the surface) and the Jacobian of the system of equations.
import numpy as np
import scipy.interpolate
import scipy.optimize
# Define regular grid surface
xmin,xmax,ymin,ymax = 25, 125, -50, 50
x = np.linspace(xmin,xmax, 201)
y = np.linspace(ymin,ymax, 201)
xx, yy = np.meshgrid(x, y)
z_ideal = ( xx**2 + yy**2 ) / 400
# Fake some measured points on the surface
z_measured = z_ideal + np.random.uniform(-0.1, 0.1, z_ideal.shape)
s_measured = scipy.interpolate.interp2d(x, y, z_measured, kind='cubic')
p_x = np.random.uniform(xmin,xmax,10000)
p_y = np.random.uniform(ymin,ymax,10000)
# z_ideal function
def z(x):
return (x[0] ** 2 + x[1] ** 2) / 400
# returns the system of equations
def f(x,pt):
x0,y0,z0 = pt
f1 = 2*(x[0] - x0) + (z(x)-z0)*x[0]/100
f2 = 2*(x[1] - y0) + (z(x)-z0)*x[1]/100
return [f1,f2]
# returns Jacobian of the system of equations
def jac(x, pt):
x0,y0,z0 = pt
return [[2*x[0]+1/100*(1/400*(z(x)+2*x[0]**2))-z0, x[0]*x[1]/2e4],
[2*x[1]+1/100*(1/400*(z(x)+2*x[1]**2))-z0, x[0]*x[1]/2e4]]
def minimize_distance(pt):
guess = [pt[0],pt[1]]
return scipy.optimize.root(f,guess,jac=jac, args=pt)
# select a random point from the measured data
x0,y0 = p_x[30], p_y[30]
z0 = float(s_measured(x0,y0))
minimize_distance([x0,y0,z0])
Output:
fjac: array([[-0.99419141, -0.1076264 ],
[ 0.1076264 , -0.99419141]])
fun: array([ -1.05033229e-08, -2.63163477e-07])
message: 'The solution converged.'
nfev: 19
njev: 2
qtf: array([ 2.80642738e-07, 2.13792093e-06])
r: array([-2.63044477, -0.48260582, -2.33011149])
status: 1
success: True
x: array([ 110.6726472 , 39.28642206])
Yes! Using K-Means with clustering will do exactly that. So s_ideal will be the target, then you train on p_z. Ultimately you will get centroids that will give you the closest point on the surface s_ideal to each point in p.
Here is a example, it fairly close to what you want.

Is there Implementation of Hawkes Process in PyMC?

I want to use Hawkes process to model some data. I could not find whether PyMC supports Hawkes process. More specifically I want an observed variable with Hawkes Process and learn a posterior on its params.
If it is not there, then could I define it in PyMC in some way e.g. #deterministic etc.??
It's been quite a long time since your question, but I've worked it out on PyMC today so I'd thought I'd share the gist of my implementation for the other people who might get across the same problem. We're going to infer the parameters λ and α of a Hawkes process. I'm not going to cover the temporal scale parameter β, I'll leave that as an exercise for the readers.
First let's generate some data :
def hawkes_intensity(mu, alpha, points, t):
p = np.array(points)
p = p[p <= t]
p = np.exp(p - t)
return mu + alpha * np.sum(p)
def simulate_hawkes(mu, alpha, window):
t = 0
points = []
lambdas = []
while t < window:
m = hawkes_intensity(mu, alpha, points, t)
s = np.random.exponential(scale=1/m)
ratio = hawkes_intensity(mu, alpha, points, t + s)
t = t + s
if t < window:
points.append(t)
lambdas.append(ratio)
else:
break
points = np.sort(np.array(points, dtype=np.float32))
lambdas = np.array(lambdas, dtype=np.float32)
return points, lambdas
# parameters
window = 1000
mu = 8
alpha = 0.25
points, lambdas = simulate_hawkes(mu, alpha, window)
num_points = len(points)
We just generated some temporal points using some functions that I adapted from there : https://nbviewer.jupyter.org/github/MatthewDaws/PointProcesses/blob/master/Temporal%20points%20processes.ipynb
Now, the trick is to create a matrix of size (num_points, num_points) that contains the temporal distance of the ith point from all the other points. So the (i, j) point of the matrix is the temporal interval separating the ith point to the jth. This matrix will be used to compute the sum of the exponentials of the Hawkes process, ie. the self-exciting part. The way to create this matrix as well as the sum of the exponentials is a bit tricky. I'd recommend to check every line yourself so you can see what they do.
tile = np.tile(points, num_points).reshape(num_points, num_points)
tile = np.clip(points[:, None] - tile, 0, np.inf)
tile = np.tril(np.exp(-tile), k=-1)
Σ = np.sum(tile, axis=1)[:-1] # this is our self-exciting sum term
We have points and we have a matrix containg the sum of the excitations term.
The duration between two consecutive events of a Hawkes process follow an exponential distribution of parameter λ = λ0 + ∑ excitation. This is what we are going to model, but first we have to compute the duration between two consecutive points of our generated data.
interval = points[1:] - points[:-1]
We're now ready for inference:
with pm.Model() as model:
λ = pm.Exponential("λ", 1)
α = pm.Uniform("α", 0, 1)
lam = pm.Deterministic("lam", λ + α * Σ)
interarrival = pm.Exponential(
"interarrival", lam, observed=interval)
trace = pm.sample(2000, tune=4000)
pm.plot_posterior(trace, var_names=["λ", "α"])
plt.show()
print(np.mean(trace["λ"]))
print(np.mean(trace["α"]))
7.829
0.284
Note: the tile matrix can become quite large if you have many data points.

Categories