Решатель fsolve не сходится к желаемым решениям

Я пытаюсь найти на поверхности эллипсоида точки, которые также находятся внутри усеченного конуса. Центры эллипсоида и усеченной пирамиды расположены в точках 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 в качестве первоначального предположения, код сходится к этому значению (которое не является допустимым решением), повторяемому снова и снова. . Решениями должны быть все точки усеченной пирамиды, лежащие на поверхности эллипсоида. Я не понимаю, почему мой код ведет себя не так, как ожидалось? Может ли кто-нибудь помочь мне узнать?

Вы написали, что «... решение, к которому сходится код, — это x0». Если x0 действительно является решением, почему оно должно сходиться в другую точку? Вы имеете в виду, что x0 не является решением, но все равно возвращает его как решение? Какая статистика сообщается? Включенный код выдает ошибку при запуске.

Matt Haberland 04.05.2024 02:20

@MattHaberland Я отредактировал, чтобы указать, что x0 не является допустимым решением.

aheuchamps 04.05.2024 09:14
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
2
94
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваш подход имеет ряд проблем.

  1. Пересечение двух поверхностей представляет собой кривую. Кривая в пространстве имеет только один параметр, поэтому вам не следует использовать тройной вложенный цикл для каких-либо действий.

  2. Похоже, у вас сложилось впечатление, что полярный угол тета в эллипсоиде и усеченной пирамиде один и тот же. Это не. Тета и фи в эллипсоиде — это просто параметры: на самом деле они не являются сферическими полярными углами (если только эллипсоид не превратится в сферу).

  3. Пересечений может не быть или на самом деле могут быть две кривые пересечения, в зависимости от высоты усеченного конуса. fsolve никогда не сможет найти оба (или даже указать, что он нашел).

  4. Вы можете решить проблему точно. Для каждого полярного угла фи для образующих пирамиды может быть 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. Цена за понятность: мы заменяем встроенную математику изображением.

jlandercy 06.05.2024 19:42

Да, я действительно не видел альтернативного способа разобраться с математикой. Мне также необходимо сохранить резервную копию моего объяснения (оригинал находится в Word), на случай, если мне позже придется исправлять опечатки.

lastchance 06.05.2024 20:33

Другие вопросы по теме