Verification of 3D Fractal Slice-Rendering Method - python

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.

Related

Scaling tangent lines in a bezier curve

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.

FWHM calculation using python

I am trying to calculate the FWHM of spectra using python. The spectral description (I'm talking in terms of the physics) for me it's bit complicated and I can't fit the data using some simple Gaussian or Lorentizian profile.
So far I managed to manage interpolation of the data and draw a straight line parallel to the X axis through the half maxima.
How can I find the coordinates of the intersection of the two lines on both sides of the peak?
I know if I take the cursor in those points it will give me the coordinates but I want to automate this process so that it becomes much more user friendly. How can I do that?
from matplotlib import pyplot as mp
import numpy as np
def peak(x, c):
return np.exp(-np.power(x - c, 2) / 16.0)
def lin_interp(x, y, i, half):
return x[i] + (x[i+1] - x[i]) * ((half - y[i]) / (y[i+1] - y[i]))
def half_max_x(x, y):
half = max(y)/2.0
signs = np.sign(np.add(y, -half))
zero_crossings = (signs[0:-2] != signs[1:-1])
zero_crossings_i = np.where(zero_crossings)[0]
return [lin_interp(x, y, zero_crossings_i[0], half),
lin_interp(x, y, zero_crossings_i[1], half)]
# make some fake data
x=np.linspace(0,20,21)
y=peak(x,10)
# find the two crossing points
hmx = half_max_x(x,y)
# print the answer
fwhm = hmx[1] - hmx[0]
print("FWHM:{:.3f}".format(fwhm))
# a convincing plot
half = max(y)/2.0
mp.plot(x,y)
mp.plot(hmx, [half, half])
mp.show()
The (x, y) coordinates of the two points are (hmx[0], half) and (hmx[1], half).
In addition to the previos answer, in case of baseline is not 0, then ((max-min)/2) + min. That's what I did to solve my problem. Tks.

Rotating 1D numpy array of radial intensities into 2D array of spacial intensities

I have a numpy array filled with intensity readings at different radii in a uniform circle (for context, this is a 1D radiative transfer project for protostellar formation models: while much better models exist, my supervisor wasnts me to have the experience of producing one so I understand how others work).
I want to take that 1d array, and "rotate" it through a circle, forming a 2D array of intensities that could then be shown with imshow (or, with a bit of work, aplpy). The final array needs to be 2d, and the projection needs to be Cartesian, not polar.
I can do it with nested for loops, and I can do it with lookup tables, but I have a feeling there must be a neat way of doing it in numpy or something.
Any ideas?
EDIT:
I have had to go back and recreate my (frankly horrible) mess of for loops and if statements that I had before. If I really tried, I could probably get rid of one of the loops and one of the if statements by condensing things down. However, the aim is not to make it work with for loops, but see if there is a built in way to rotate the array.
impB is an array that differs slightly from what I stated it was before. Its actually just a list of radii where particles are detected. I then bin those into radius bins to get the intensity (or frequency if you prefer) in each radius. R is the scale factor for my radius as I run the model in a dimensionless way. iRes is a resolution scale factor, essentially how often I want to sample my radial bins. Everything else should be clear.
radJ = np.ndarray(shape=(2*iRes, 2*iRes)) # Create array of 2xRadius square
for i in range(iRes):
n = len(impB[np.where(impB[:] < ((i+1.) * (R / iRes)))]) # Count number of things within this radius +1
m = len(impB[np.where(impB[:] <= ((i) * (R / iRes)))]) # Count number of things in this radius
a = (((i + 1) * (R / iRes))**2 - ((i) * (R / iRes))**2) * math.pi # A normalisation factor based on area.....dont ask
for x in range(iRes):
for y in range(iRes):
if (x**2 + y**2) < (i * iRes)**2:
if (x**2 + y**2) >= (i * iRes)**2: # Checks for radius, and puts in cartesian space
radJ[x+iRes,y+iRes] = (n-m) / a # Put in actual intensity bins
radJ[x+iRes,-y+iRes] = (n-m) / a
radJ[-x+iRes,y+iRes] = (n-m) / a
radJ[-x+iRes,-y+iRes] = (n-m) / a
Nested loops are a simple approach for that. With ri_data_r and y containing your radius values (difference to the middle pixel) and the array for rotation, respectively, I would suggest:
from scipy import interpolate
import numpy as np
y = np.random.rand(100)
ri_data_r = np.linspace(-len(y)/2,len(y)/2,len(y))
interpol_index = interpolate.interp1d(ri_data_r, y)
xv = np.arange(-1, 1, 0.01) # adjust your matrix values here
X, Y = np.meshgrid(xv, xv)
profilegrid = np.ones(X.shape, float)
for i, x in enumerate(X[0, :]):
for k, y in enumerate(Y[:, 0]):
current_radius = np.sqrt(x ** 2 + y ** 2)
profilegrid[i, k] = interpol_index(current_radius)
print(profilegrid)
This will give you exactly what you are looking for. You just have to take in your array and calculate an symmetric array ri_data_r that has the same length as your data array and contains the distance between the actual data and the middle of the array. The code is doing this automatically.
I stumbled upon this question in a different context and I hope I understood it right. Here are two other ways of doing this. The first uses skimage.transform.warp with interpolation of desired order (here we use order=0 Nearest-neighbor). This method is slower but more precise and needs less memory then the second method.
The second one does not use interpolation, therefore is faster but also less precise and needs way more memory because it stores each 2D array containing one tilt until the end, where they are averaged with np.nanmean().
The difference between both solutions stemmed from the problem of handling the center of the final image where the tilts overlap the most, i.e. the first one would just add values with each tilt ending up out of the original range. This was "solved" by clipping the matrix in each step to a global_min and global_max (consult the code). The second one solves it by taking the mean of the tilts where they overlap, which forces us to use the np.nan.
Please, read the Example of usage and Sanity check sections in order to understand the plot titles.
Solution 1:
import numpy as np
from skimage.transform import warp
def rotate_vector(vector, deg_angle):
# Credit goes to skimage.transform.radon
assert vector.ndim == 1, 'Pass only 1D vectors, e.g. use array.ravel()'
center = vector.size // 2
square = np.zeros((vector.size, vector.size))
square[center,:] = vector
rad_angle = np.deg2rad(deg_angle)
cos_a, sin_a = np.cos(rad_angle), np.sin(rad_angle)
R = np.array([[cos_a, sin_a, -center * (cos_a + sin_a - 1)],
[-sin_a, cos_a, -center * (cos_a - sin_a - 1)],
[0, 0, 1]])
# Approx. 80% of time is spent in this function
return warp(square, R, clip=False, output_shape=((vector.size, vector.size)))
def place_vectors(vectors, deg_angles):
matrix = np.zeros((vectors.shape[-1], vectors.shape[-1]))
global_min, global_max = 0, 0
for i, deg_angle in enumerate(deg_angles):
tilt = rotate_vector(vectors[i], deg_angle)
global_min = tilt.min() if global_min > tilt.min() else global_min
global_max = tilt.max() if global_max < tilt.max() else global_max
matrix += tilt
matrix = np.clip(matrix, global_min, global_max)
return matrix
Solution 2:
Credit for the idea goes to my colleague Michael Scherbela.
import numpy as np
def rotate_vector(vector, deg_angle):
assert vector.ndim == 1, 'Pass only 1D vectors, e.g. use array.ravel()'
square = np.ones([vector.size, vector.size]) * np.nan
radius = vector.size // 2
r_values = np.linspace(-radius, radius, vector.size)
rad_angle = np.deg2rad(deg_angle)
ind_x = np.round(np.cos(rad_angle) * r_values + vector.size/2).astype(np.int)
ind_y = np.round(np.sin(rad_angle) * r_values + vector.size/2).astype(np.int)
ind_x = np.clip(ind_x, 0, vector.size-1)
ind_y = np.clip(ind_y, 0, vector.size-1)
square[ind_y, ind_x] = vector
return square
def place_vectors(vectors, deg_angles):
matrices = []
for deg_angle, vector in zip(deg_angles, vectors):
matrices.append(rotate_vector(vector, deg_angle))
matrix = np.nanmean(np.array(matrices), axis=0)
return np.nan_to_num(matrix, copy=False, nan=0.0)
Example of usage:
r = 100 # Radius of the circle, i.e. half the length of the vector
n = int(np.pi * r / 8) # Number of vectors, e.g. number of tilts in tomography
v = np.ones(2*r) # One vector, e.g. one tilt in tomography
V = np.array([v]*n) # All vectors, e.g. a sinogram in tomography
# Rotate 1D vector to a specific angle (output is 2D)
angle = 45
rotated = rotate_vector(v, angle)
# Rotate each row of a 2D array according to its angle (output is 2D)
angles = np.linspace(-90, 90, num=n, endpoint=False)
inplace = place_vectors(V, angles)
Sanity check:
These are just simple checks which by no means cover all possible edge cases. Depending on your use case you might want to extend the checks and adjust the method.
# I. Sanity check
# Assuming n <= πr and v = np.ones(2r)
# Then sum(inplace) should be approx. equal to (n * (2πr - n)) / π
# which is an area that should be covered by the tilts
desired_area = (n * (2 * np.pi * r - n)) / np.pi
covered_area = np.sum(inplace)
covered_frac = covered_area / desired_area
print(f'This method covered {covered_frac * 100:.2f}% '
'of the area which should be covered in total.')
# II. Sanity check
# Assuming n <= πr and v = np.ones(2r)
# Then a circle M with radius m <= r should be the largest circle which
# is fully covered by the vectors. I.e. its mean should be no less than 1.
# If n = πr then m = r.
# m = n / π
m = int(n / np.pi)
# Code for circular mask not included
mask = create_circular_mask(2*r, 2*r, center=None, radius=m)
m_area = np.mean(inplace[mask])
print(f'Full radius r={r}, radius m={m}, mean(M)={m_area:.4f}.')
Code for plotting:
import matplotlib.pyplot as plt
plt.figure(figsize=(16, 8))
plt.subplot(121)
rotated = np.nan_to_num(rotated) # not necessary in case of the first method
plt.title(
f'Output of rotate_vector(), angle={angle}°\n'
f'Sum is {np.sum(rotated):.2f} and should be {np.sum(v):.2f}')
plt.imshow(rotated, cmap=plt.cm.Greys_r)
plt.subplot(122)
plt.title(
f'Output of place_vectors(), r={r}, n={n}\n'
f'Covered {covered_frac * 100:.2f}% of the area which should be covered.\n'
f'Mean of the circle M is {m_area:.4f} and should be 1.0.')
plt.imshow(inplace)
circle=plt.Circle((r, r), m, color='r', fill=False)
plt.gcf().gca().add_artist(circle)
plt.gcf().gca().legend([circle], [f'Circle M (m={m})'])

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