Питон: пересечение сфер

Я чрезвычайно новичок в программировании, но я решил заняться интересным проектом, поскольку недавно я узнал, как представлять сферу в параметрической форме. При пересечении трех сфер существуют две точки пересечения, которые различны, если только они не перекрываются только в особой точке.

Параметрическое представление сферы:

Код, который я имею, модифицирован из ответа от Python/matplotlib: построение трехмерного куба, сферы и вектора? , добавив возможность диктовать начало координат x, y и z и радиус сферы. Многие подобные вопросы были написаны в C++, Java и С#, которые я вообще не могу понять (я едва знаю, что я делаю, так что будь проще для меня).

Мой код:

import numpy as np

def make_sphere_x(x, radius):
  u, v = np.mgrid[0:2 * np.pi:5000j, 0:np.pi:2500j]
  x += radius * np.cos(u) * np.sin(v)
  return x

def make_sphere_y(y, radius):
  u, v = np.mgrid[0:2 * np.pi:5000j, 0:np.pi:2500j]
  y += radius * np.sin(u) * np.sin(v)
  return y

def make_sphere_z(z, radius):
  u, v = np.mgrid[0:2 * np.pi:5000j, 0:np.pi:2500j]
  z += radius * np.cos(v)
  return z

#x values
sphere_1_x = make_sphere_x(0, 2)
sphere_2_x = make_sphere_x(1, 3)
sphere_3_x = make_sphere_x(-1, 4)
#y values
sphere_1_y = make_sphere_y(0, 2)
sphere_2_y = make_sphere_y(1, 3)
sphere_3_y = make_sphere_y(0, 4)
#z values
sphere_1_z = make_sphere_z(0, 2)
sphere_2_z = make_sphere_z(1, 3)
sphere_3_z = make_sphere_z(-2, 4)

#intercept of x-values
intercept_x = list(filter(lambda x: x in sphere_1_x, sphere_2_x))
intercept_x = list(filter(lambda x: x in intercept_x, sphere_3_x))
print(intercept_x)

Проблемы:

  1. Ясно, что должен быть лучший способ найти перехваты. Прямо сейчас код генерирует точки через равные интервалы с количеством интервалов, которое я определяю под мнимым числом в np.mgrid. Если это увеличить, шансы на пересечение должны увеличиться (я думаю), но когда я пытаюсь увеличить его до 10000j или выше, он просто выплевывает ошибку памяти.

  2. В массиве есть очевидные пробелы, и этот метод, скорее всего, будет ошибочным, даже если у меня есть доступ к суперкомпьютеру и я могу довести значение до непристойного значения. Прямо сейчас код приводит к нулевому набору.

  3. Код крайне неэффективен, не то чтобы это приоритет, но людям нравятся вещи в тройках, верно?

Не стесняйтесь разжигать меня за ошибки новичка в кодировании или задавать вопросы о переполнении стека. Ваша помощь очень ценится.

Ответы

Ответ 1

Используя scipy.optimize.fsolve, вы можете найти корень данной функции, учитывая первоначальное предположение, которое находится где-то в диапазоне вашего решения. Я использовал этот подход, чтобы решить вашу проблему, и он, кажется, работает для меня. Единственным недостатком является то, что он обеспечивает только одно пересечение. Чтобы найти второй, вам придется повозиться с начальными условиями, пока fsolve не найдет второй корень.

Сначала мы определяем наши сферы, определяя (произвольные) радиусы и центры для каждой сферы:

a1 = np.array([0,0,0])
r1 = .4
a2 = np.array([.3,0,0])
r2 = .5
a3 = np.array([0,.3,0])
r3 = .5

Затем мы определяем, как преобразовать обратно в декартовы координаты при заданных углах u,v

def position(a,r,u,v):
    return a + r*np.array([np.cos(u)*np.sin(v),np.sin(u)*np.sin(v),np.cos(v)])

Теперь мы думаем о том, какое уравнение нам нужно найти корень. Для любой точки пересечения считается, что для идеального u1,v1,u2,v2,u3,v3 положения position(a1,r1,u1,v1) = position(a2,r2,u2,v2) = position(a3,r3,u3,v3) равны. Таким образом, мы находим три уравнения, которые должны быть нулями, а именно разности двух векторов положения. Фактически, поскольку каждый вектор имеет 3 компонента, у нас есть 9 уравнений, что более чем достаточно для определения наших 6 переменных.

Мы находим функцию для минимизации как:

def f(args):
    u1,v1,u2,v2,u3,v3,_,_,_ = args
    pos1 = position(a1,r1,u1,v1)
    pos2 = position(a2,r2,u2,v2)
    pos3 = position(a3,r3,u3,v3)
    return np.array([pos1 - pos2, pos1 - pos3, pos2 - pos3]).flatten()

fsolve требует одинакового количества входных и выходных аргументов. Поскольку у нас есть 9 уравнений, но только 6 переменных, я просто использовал 3 фиктивные переменные, поэтому размеры совпадают. Сглаживание массива в последней строке необходимо, поскольку fsolve принимает только 1D-массивы.

Теперь пересечение можно найти с помощью fsolve и (довольно случайного) предположения:

guess = np.array([np.pi/4,np.pi/4,np.pi/4,np.pi/4,np.pi/4,np.pi/4,0,0,0])

x0 = fsolve(f,guess)
u1,v1,u2,v2,u3,v3,_,_,_ = x0

Вы можете проверить правильность результата, подключив полученные углы к функции position.

Ответ 2

Эту проблему лучше решить с помощью тригонометрии.

Разобрав задачу на двумерные круги, мы могли бы сделать:

import math
import numpy

class Circle():
    def __init__(self, cx, cy, r):
        """initialise Circle and set main properties"""
        self.centre = numpy.array([cx, cy])
        self.radius = r

    def find_intercept(self, c2):
        """find the intercepts between the current Circle and a second c2"""
        #Find the distance between the circles
        s = c2.centre - self.centre
        self.dx, self.dy = s
        self.d = math.sqrt(numpy.sum(s**2))
        #Test if there is an overlap.  Note: this won't detect if one circle completly surrounds the other.
        if self.d > (self.radius + c2.radius):
            print("no interaction")
        else:
            #trigonometry
            self.theta = math.atan2(self.dy,self.dx)
            #cosine rule
            self.cosA = (c2.radius**2 - self.radius**2 + self.d**2)/(2*c2.radius*self.d)
            self.A = math.acos(self.cosA)

            self.Ia = c2.centre - [math.cos(self.A+self.theta)*c2.radius, math.sin(self.A+self.theta)*c2.radius]
            self.Ib = c2.centre - [math.cos(self.A-self.theta)*c2.radius,-math.sin(self.A-self.theta)*c2.radius]

            print("Interaction points are : ", self.Ia, " and: ", self.Ib)


#define two arbitrary circles 
c1 = Circle(2,5,5)
c2 = Circle(1,6,4)


#find the intercepts
c1.find_intercept(c2)

#test results by reversing the operation
c2.find_intercept(c1)