Я пытаюсь найти на поверхности эллипсоида точки, которые также находятся внутри усеченного конуса.
Центры эллипсоида и усеченной пирамиды расположены в точках ce и cf соответственно.
Эллипсоид имеет длину полуосей, определенную в l.
Усеченная пирамида имела радиус r1 у основания, отверстие div и высоту h.
Чтобы решить возникшую проблему, я использую python вместе с numpy и scipy.
Более конкретно, я использую функцию fsolve из scipy.optimize, чтобы минимизировать разницу между уравнениями эллипсоида и усеченного конуса.
На основе этой функции у меня есть якобиан.
Мой код
import numpy as np
from scipy.optimize import fsolve
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def get_ellispoid(c, l):
X_ell = c[0] + l[0] * np.outer(np.sin(Phi), np.cos(Theta))
Y_ell = c[1] + l[1] * np.outer(np.sin(Phi), np.sin(Theta))
Z_ell = c[2] + l[2] * np.outer(np.cos(Phi), np.ones_like(Theta))
return X_ell, Y_ell, Z_ell
def get_conical_frustum(c: tuple[float, float, float],
r1: float,
alpha: float,
z: np.ndarray,
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
Theta, Z = np.meshgrid(theta, z)
R = r1 + Z * np.tan(alpha)
X_fru = c[0] + R * np.cos(Theta)
Y_fru = c[1] + R * np.sin(Theta)
Z_fru = c[2] + Z
return X_fru, Y_fru, Z_fru
def ellipsoid(theta, phi, ce, l):
return np.array(
[ce[0] + l[0] * np.cos(theta) * np.cos(phi),
ce[1] + l[1] * np.sin(theta) * np.cos(phi),
ce[2] + l[2] * np.sin(phi)]
)
def frustum(theta, z, cf, r1, div):
return np.array(
[cf[0] + (r1 + z * np.tan(div)) * np.cos(theta),
cf[1] + (r1 + z * np.tan(div)) * np.sin(theta),
cf[2] + z]
)
# Define the function for which we want to find the roots
def func(x, theta, phi, z, ce, cf, l, r1, div):
return ellipsoid(theta, phi, ce, l) - frustum(theta, z, cf, r1, div)
# Define the Jacobian matrix of the function
def jacobian(x, theta, phi, z, ce, cf, l, r1, div):
return np.array(
[
[-(l[0] * np.cos(phi) - r1 - z * np.tan(div)) * np.sin(theta), -l[0] * np.cos(theta) * np.sin(phi), -np.tan(div) * np.cos(theta)],
[-(l[1] * np.cos(phi) - r1 - z * np.tan(div)) * np.cos(theta), -l[1] * np.cos(theta) * np.sin(phi), -np.tan(div) * np.sin(theta)],
[0.0, l[2] * np.cos(phi), -1],
]
)
num_points = 20
theta = np.linspace(0, 2*np.pi, num_points)
phi = np.linspace(0, np.pi, num_points)
Theta, Phi = np.meshgrid(theta, phi)
# Ellipsoid
ce = [0.0, 0.0, 0.0]
l = [1.5, 1.1, 0.45]
# Conical frustum
cf = [0.0, 0.0, -1.5]
h = np.linalg.norm(np.array(cf) - np.array(ce))
z = np.linspace(cf[-1], h, num_points)
r1 = 0.15
div = np.pi / 18
# Initial guess
x0 = np.array([0.0, 0.0, ce[-1]])
# Iterate over theta, phi, and z values
sol = []
for it, t in enumerate(theta):
for ip, p in enumerate(phi):
for iz, z_val in enumerate(z):
try:
root = fsolve(func, [t, p, z_val], args=(t, p, z_val, ce, cf, l, r1, div), fprime=jacobian)
sol.append(root)
except RuntimeError:
continue
sol = np.array(sol)
# Create a 3D plot
fig = plt.figure(figsize = (8, 6))
ax = fig.add_subplot(111, projection = '3d')
# Plot the volumes
xell, yell, zell = get_ellispoid(ce, l)
ax.plot_surface(xell, yell, zell, color = 'r', alpha = 0.005)
xfru, yfru, zfru = get_conical_frustum(cf, r1, div, z)
ax.plot_surface(xfru, yfru, zfru, color = 'b', alpha = 0.35)
# Plot the intersections
ax.scatter(sol[:, 0], sol[:, 1], sol[:, 2], color = 'g', s = 1.5)
# Set labels and title
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
# Show the plot
plt.show()
Когда код запускается, точки, к которым сходится код, являются начальным условием [t, p, z_val] или, если я попытаюсь поставить x0 в качестве первоначального предположения, код сходится к этому значению (которое не является допустимым решением), повторяемому снова и снова. .
Решениями должны быть все точки усеченной пирамиды, лежащие на поверхности эллипсоида.
Я не понимаю, почему мой код ведет себя не так, как ожидалось?
Может ли кто-нибудь помочь мне узнать?
@MattHaberland Я отредактировал, чтобы указать, что x0 не является допустимым решением.






Ваш подход имеет ряд проблем.
Пересечение двух поверхностей представляет собой кривую. Кривая в пространстве имеет только один параметр, поэтому вам не следует использовать тройной вложенный цикл для каких-либо действий.
Похоже, у вас сложилось впечатление, что полярный угол тета в эллипсоиде и усеченной пирамиде один и тот же. Это не. Тета и фи в эллипсоиде — это просто параметры: на самом деле они не являются сферическими полярными углами (если только эллипсоид не превратится в сферу).
Пересечений может не быть или на самом деле могут быть две кривые пересечения, в зависимости от высоты усеченного конуса. fsolve никогда не сможет найти оба (или даже указать, что он нашел).
Вы можете решить проблему точно. Для каждого полярного угла фи для образующих пирамиды может быть 0, 1 или 2 значения z в зависимости от количества пересечений. Эти значения являются просто действительными корнями квадратного уравнения, которое можно задать для каждого значения фи — см. конец этого поста.
Ниже я привожу решение кода и эскизное доказательство построения квадратных уравнений внизу этого поста. Обратите внимание: я не знаю, имеете ли вы в виду r1 как больший или меньший радиус усеченного конуса. Я взял его как больший, но мне пришлось увеличить его, чтобы получить какое-либо пересечение. Я вообще не использую fsolve: в этом нет необходимости, и проблему это не решит.
import math
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Ellipsoid
xe, ye, ze = 0.0, 0.0, 0.0
a, b, c = 1.5, 1.1, 0.45
# Conical frustum
xf, yf, zf = 0.0, 0.0, -1.5
h = 1.5
R = 0.45
alpha = math.pi / 18
tanalpha = math.tan( alpha )
angles = np.linspace( 0.0, 2 * np.pi, 40 )
upper = []
lower = []
for phi in angles:
# Set up coefficients in quadratic equation
cosphi, sinphi = math.cos( phi ), math.sin( phi )
x1, x2 = xf - xe + ( R + zf * tanalpha ) * cosphi, tanalpha * cosphi
y1, y2 = yf - ye + ( R + zf * tanalpha ) * sinphi, tanalpha * sinphi
z1, z2 = ze, 1
A = ( x2 / a ) ** 2 + ( y2 / b ) ** 2 + ( z2 / c ) ** 2
B = -2 * ( x1 * x2 / a ** 2 + y1 * y2 / b ** 2 + z1 * z2 / c ** 2 )
C = ( x1 / a ) ** 2 + ( y1 / a ) ** 2 + ( z1 / c ) ** 2 - 1
discriminant = B ** 2 - 4 * A * C
if discriminant >= 0:
root1 = ( -B - math.sqrt( discriminant ) ) / ( 2 * A )
root2 = -B / A - root1
if zf <= root1 <= zf + h: lower.append( [ xf + ( R - ( root1 - zf ) * tanalpha ) * cosphi, yf + ( R - ( root1 - zf ) * tanalpha ) * sinphi, root1 ] )
if zf <= root2 <= zf + h: upper.append( [ xf + ( R - ( root2 - zf ) * tanalpha ) * cosphi, yf + ( R - ( root2 - zf ) * tanalpha ) * sinphi, root2 ] )
lower = np.array( lower )
upper = np.array( upper )
################################################################################################
# Create a 3D plot
def get_ellipsoid( c, l ):
num_points = 20
th = np.linspace( 0, 2*np.pi, num_points )
ph = np.linspace( 0, np.pi, num_points )
Theta, Phi = np.meshgrid(th, ph)
X_ell = c[0] + l[0] * np.outer(np.sin(Phi), np.cos(Theta))
Y_ell = c[1] + l[1] * np.outer(np.sin(Phi), np.sin(Theta))
Z_ell = c[2] + l[2] * np.outer(np.cos(Phi), np.ones_like(Theta))
return X_ell, Y_ell, Z_ell
def get_frustum( c, r1, alpha, h ):
num_points = 20
th = np.linspace( 0, 2*np.pi, num_points )
z = np.linspace( 0, h, num_points )
Theta, Z = np.meshgrid(th, z)
R = r1 - Z * np.tan(alpha)
X_fru = c[0] + R * np.cos(Theta)
Y_fru = c[1] + R * np.sin(Theta)
Z_fru = c[2] + Z
return X_fru, Y_fru, Z_fru
fig = plt.figure(figsize = (8, 6))
ax = fig.add_subplot(111, projection = '3d')
# Plot the volumes
xell, yell, zell = get_ellipsoid( [xe,ye,ze], [a,b,c] )
ax.plot_surface(xell, yell, zell, color = 'r', alpha = 0.005)
xfru, yfru, zfru = get_frustum( [xf,yf,zf], R, alpha, h )
ax.plot_surface( xfru, yfru, zfru, color = 'b', alpha = 0.35)
# Plot the intersections
if lower.size > 0: ax.plot( lower[:,0], lower[:, 1], lower[:, 2], color = 'g' )
if upper.size > 0: ax.plot( upper[:,0], upper[:, 1], upper[:, 2], color = 'g' )
# Set labels and title
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()
Выход:
Краткое описание решения. Вам придется выполнить длинную алгебру, чтобы найти коэффициенты квадратичного: они заданы в коде.
Когда я вижу ваши последние сообщения, я действительно чувствую, что уравнения TeX отсутствуют в SO. Цена за понятность: мы заменяем встроенную математику изображением.
Да, я действительно не видел альтернативного способа разобраться с математикой. Мне также необходимо сохранить резервную копию моего объяснения (оригинал находится в Word), на случай, если мне позже придется исправлять опечатки.
Вы написали, что «... решение, к которому сходится код, — это
x0». Еслиx0действительно является решением, почему оно должно сходиться в другую точку? Вы имеете в виду, чтоx0не является решением, но все равно возвращает его как решение? Какая статистика сообщается? Включенный код выдает ошибку при запуске.