Nie będą się po prostu układać jak pierwiastki n-tego stopnia z 1 na dowolnym okręgu bedacym przekrojem wspomnianej sfery? (Przekroj plaszczyzna przechodząca przez srodek)
Masz na myśli pierwiastki zespolone tak?
Raczej nie rozłożą się na jednym okręgu, wciąż miałyby energię potencjalną wyższą niż w przypadku rozłożenia na całej kuli ;) Energia potencjalna ładunku maleje z kwadratem odległości, więc ogólnie będą dążyły do wyrównania odległości między każdą parą ładunków. Podejrzewam że dla uproszczenia można by wziąć jakąś projekcję i sprowadzić problem do zagadnienia 2D.
Idąc tym tokiem zdefiniowałem sobie funkcję celu (opartą o iloczyn skalarny - w końcu im bardziej przeciwne punkty, tym mniejszą da wartość, nie potrzebuję przecież liczyć energii potencjalnej ;) ) i minimalizujemy sumę iloczynów skalarnych (tu maksymalizujemy liczbę przeciwną i jeszcze przeliczoną żeby maksimum było równe 1, ale to szczegół):
import math
import numpy as np
def to_3D(array: np.ndarray):
arr = []
for pt in array[:]:
arr.append([
math.cos(pt[0]) * math.cos(pt[1]),
math.sin(pt[0]) * math.cos(pt[1]),
math.sin(pt[1])
])
return np.array(arr)
def dist_3D_fun(array: np.ndarray):
_3d_vecs = to_3D(array)
result = _3d_vecs @ _3d_vecs.transpose()
return 1.0 - np.sum(result)/result.shape[0]
Zakładam, że proponowane rozwiązania to kąty względem osi OX i OZ - bo tak ;) więc potrzebuję przeliczenia na współrzędne kartezjańskie, by dostać iloczyn skalarny - dla uproszczenia sfera ma zerowy promień.
No to teraz odpalamy sobie optymalizację, kiedyś tam z nudów naskrobałem na kolanie klasę Specimen z bieda-operatorami genetycznymi na np.ndarray'sach, więc je wykorzystam (tutaj dla dwóch punktów, stąd shape [2, 2]:
threshold = 1e-7
steps_limit = 10000
dimensions = [2,2]
a = Specimen(dimensions)
b = Specimen(dimensions)
a(dist_3D_fun)
b(dist_3D_fun)
c = a + b
steps = 0
delta = threshold
while delta >= threshold and steps < steps_limit:
steps += 1
if a.performance < b.performance:
d = b
b = a
a = d
# print("Step {0}: {1:.9f}".format(steps, a.performance))
if c.performance >= a.performance:
delta = c.performance - a.performance
b = a
a = c
elif c.performance >= b.performance:
delta = c.performance - b.performance
b = c
c = a + b
c.mutate(1.0/(steps ** 0.5))(dist_3D_fun)
print("Step {0}: {1:.3f}".format(steps, a.performance))
print(to_3D(a._genes))
No i wskazało przeciwległe punkty (z jakimś tam niewielkim błędem na 3-cim/4-tym miejscu po przecinku):
Step 4228: 1.000
[[-0.59341103 0.4789695 -0.64687832]
[ 0.59333623 -0.47878774 0.64708147]]
No to testujemy dla jakiejś ciekawszej liczby, np. n=11 (shape [11, 2]):
Step 10000: 0.060
[[ 0.92665822 0.36947295 0.06924074]
[-0.15082731 -0.28915089 0.94532687]
[ 0.94456119 -0.2580341 0.20303339]
[-0.5927377 -0.0256576 0.80498678]
[ 0.57147413 0.18094127 0.80042337]
[ 0.73409681 0.34770983 -0.58326645]
[-0.81766341 0.03907948 0.57436865]
[-0.07086159 -0.23513502 0.96937617]
[-0.33180022 -0.1300778 0.93433847]
[-0.00689443 0.00534034 -0.99996197]
[ 0.36834138 0.14222481 -0.91874737]]
Wynik 0.060 brzmi słabo, jak tak się przyjrzymy to część kropek faktycznie jest skupiona w jednym miejscu (choć nie ogarnąłem jeszcze, jak wyświetlić interaktywny wykres 3D w Jupyter Notebook):
Próbujemy jeszcze raz:
Step 5990: 1.000
[[ 0.23065809 -0.07382626 0.97023014]
[-0.29096953 0.77687333 -0.55839463]
[ 0.52335038 -0.81229086 -0.25746445]
[-0.49236392 -0.30186805 -0.816366 ]
[ 0.13249796 -0.65986807 -0.73960694]
[-0.14103619 0.03070679 -0.98952811]
[-0.39512705 0.24833482 0.88442322]
[ 0.11671746 0.22656547 -0.96697731]
[-0.15520067 0.43405139 0.88741881]
[ 0.76863071 0.10062412 0.63172907]
[-0.29581929 0.02816136 0.95482872]]
I dostajemy wykres - na pierwszy rzut oka wygląda to trochę lepiej, choć ciężko stwierdzić nie mogąc sobie poobracać i poprzeglądać z każdej strony jak te ładunki nam się rozłożyły:
@exp co do ustawienia pięciu, dostałem przykładowo coś takiego:
Step 3749: 1.000
[[-0.47633634 -0.04123628 0.87829566]
[-0.2678149 0.39115383 -0.88049637]
[-0.85745299 0.50291791 -0.10884828]
[ 0.67367885 -0.70312072 -0.22754794]
[ 0.92800035 -0.15393657 0.33929174]]