|
@@ -9,6 +9,8 @@ import time
|
|
|
import copy
|
|
|
import matplotlib.pyplot as plt
|
|
|
from typing import Callable, TypeVar
|
|
|
+from scipy.special import eval_legendre
|
|
|
+import plotly.graph_objects as go
|
|
|
|
|
|
|
|
|
Array = np.ndarray
|
|
@@ -114,7 +116,7 @@ class MappedExpansionQuad(Expansion):
|
|
|
|
|
|
coefs = (2 * sigma_m * fn.coef_C_diff(l_array_expanded, kappaR)
|
|
|
* np.sqrt(4 * np.pi * (2 * l_array_expanded + 1)) * np.power(a_bar, l_array_expanded))
|
|
|
- coefs[..., 0] = sigma0
|
|
|
+ coefs[..., 0] = sigma0 / np.sqrt(4 * np.pi)
|
|
|
coefs = rot_sym_expansion(l_array, coefs)
|
|
|
super().__init__(l_array, coefs)
|
|
|
|
|
@@ -122,7 +124,7 @@ class MappedExpansionQuad(Expansion):
|
|
|
class GaussianCharges(Expansion):
|
|
|
|
|
|
def __init__(self, omega_k: Array, lambda_k: Array | float, sigma1: float, l_max: int,
|
|
|
- sigma0: float = 0, equal_charges=True):
|
|
|
+ sigma0: float = 0, equal_charges: bool = True):
|
|
|
omega_k = omega_k.reshape(-1, 2)
|
|
|
if not isinstance(lambda_k, Array):
|
|
|
lambda_k = np.array([lambda_k])
|
|
@@ -142,11 +144,45 @@ class GaussianCharges(Expansion):
|
|
|
* np.conj(fn.sph_harm(full_l_array[None, :, None], full_m_array[None, :, None],
|
|
|
theta_k[None, None, :], phi_k[None, None, :])))
|
|
|
coefs = np.squeeze(4 * np.pi * sigma1 * np.sum(summands, axis=-1))
|
|
|
- coefs[..., 0] = sigma0
|
|
|
+ coefs[..., 0] = sigma0 / np.sqrt(4 * np.pi)
|
|
|
l_array, coefs = purge_unneeded_l(l_array, coefs)
|
|
|
super().__init__(l_array, coefs)
|
|
|
|
|
|
|
|
|
+class SphericalCap(Expansion):
|
|
|
+
|
|
|
+ def __init__(self, omega_k: Array, theta0_k: Array | float, sigma1: float, l_max: int, sigma0: float = 0,
|
|
|
+ equal_sizes: bool = True):
|
|
|
+ omega_k = omega_k.reshape(-1, 2)
|
|
|
+ if not isinstance(theta0_k, Array):
|
|
|
+ theta0_k = np.array(theta0_k)
|
|
|
+ if equal_sizes:
|
|
|
+ if theta0_k.ndim == 0:
|
|
|
+ theta0_k = np.full(omega_k.shape[0], theta0_k)
|
|
|
+ elif theta0_k.ndim == 1:
|
|
|
+ theta0_k = np.full((omega_k.shape[0], theta0_k.shape[0]), theta0_k)
|
|
|
+ else:
|
|
|
+ raise ValueError(f'If equal_charges=True, theta0_k should be a 1D array, got shape {theta0_k.shape}')
|
|
|
+ if theta0_k.shape[0] != omega_k.shape[0]:
|
|
|
+ raise ValueError("Number of charges (length of omega_k) should match the last dimension of theta0_k array.")
|
|
|
+ rotations = Quaternion(np.stack((np.cos(omega_k[..., 0] / 2),
|
|
|
+ np.sin(omega_k[..., 1]) * np.sin(omega_k[..., 0] / 2),
|
|
|
+ np.cos(omega_k[..., 1]) * np.sin(omega_k[..., 0] / 2),
|
|
|
+ np.zeros_like(omega_k[..., 0]))).T)
|
|
|
+ l_array = np.arange(l_max + 1)
|
|
|
+ coefs_l0 = -sigma1 * (np.sqrt(np.pi / (2 * l_array[None, :] + 1)) *
|
|
|
+ (eval_legendre(l_array[None, :] + 1, np.cos(theta0_k[..., None]))
|
|
|
+ - eval_legendre(l_array[None, :] - 1, np.cos(theta0_k[..., None]))))
|
|
|
+ coefs = rot_sym_expansion(l_array, coefs_l0)
|
|
|
+ coefs_all_single_caps = expansion_rotation(rotations, coefs, l_array)
|
|
|
+ # Rotating is implemented in such a way that it rotates every patch to every position,
|
|
|
+ # hence the redundancy of out of diagonal elements.
|
|
|
+ coefs_all = np.sum(np.diagonal(coefs_all_single_caps), axis=-1)
|
|
|
+ if sigma0 is not None:
|
|
|
+ coefs_all[..., 0] = sigma0 / np.sqrt(4 * np.pi)
|
|
|
+ super().__init__(l_array, np.squeeze(coefs_all))
|
|
|
+
|
|
|
+
|
|
|
def map_over_expansion(f: Callable[[Expansion, T], V]) -> Callable[[Expansion, T], V]:
|
|
|
"""Map a function f over the leading axes of an expansion. Uses for loops, so it is kinda slow."""
|
|
|
|
|
@@ -154,7 +190,7 @@ def map_over_expansion(f: Callable[[Expansion, T], V]) -> Callable[[Expansion, T
|
|
|
og_shape = ex.shape
|
|
|
flat_ex = ex.flatten()
|
|
|
results = []
|
|
|
- for i in range(np.prod(og_shape)):
|
|
|
+ for i in range(int(np.prod(og_shape))):
|
|
|
results.append(f(flat_ex[i], *args, **kwargs))
|
|
|
try:
|
|
|
return np.array(results).reshape(og_shape + results[0].shape)
|
|
@@ -206,13 +242,32 @@ def purge_unneeded_l(l_array: Array, coefs: Array) -> (Array, Array):
|
|
|
return l_array, coefs
|
|
|
|
|
|
|
|
|
-def plot_theta_profile(ex: Expansion, phi=0, num=100):
|
|
|
- theta_vals = np.linspace(0, np.pi, num)
|
|
|
+def plot_theta_profile(ex: Expansion, phi: float = 0, num: int = 100, theta_start: float = 0, theta_end: float = np.pi):
|
|
|
+ theta_vals = np.linspace(theta_start, theta_end, num)
|
|
|
charge = ex.charge_value(theta_vals, phi)
|
|
|
- plt.plot(theta_vals, charge)
|
|
|
+ plt.plot(theta_vals, charge.T)
|
|
|
plt.show()
|
|
|
|
|
|
|
|
|
+def plot_charge_3d(ex: Expansion, num_theta=100, num_phi=100):
|
|
|
+ theta = np.linspace(0, np.pi, num_theta)
|
|
|
+ phi = np.linspace(0, 2 * np.pi, num_phi)
|
|
|
+ theta, phi = np.meshgrid(theta, phi)
|
|
|
+ r = ex.charge_value(theta.flatten(), phi.flatten()).reshape(theta.shape)
|
|
|
+
|
|
|
+ # Convert spherical coordinates to Cartesian coordinates
|
|
|
+ x = np.sin(theta) * np.cos(phi)
|
|
|
+ y = np.sin(theta) * np.sin(phi)
|
|
|
+ z = np.cos(theta)
|
|
|
+
|
|
|
+ # Create a heatmap on the sphere
|
|
|
+ fig = go.Figure(data=go.Surface(x=x, y=y, z=z, surfacecolor=r, colorscale='Jet'))
|
|
|
+ fig.update_layout(scene=dict(aspectmode='data'))
|
|
|
+ fig.update_layout(scene=dict(xaxis_title='X', yaxis_title='Y', zaxis_title='Z'))
|
|
|
+
|
|
|
+ fig.show()
|
|
|
+
|
|
|
+
|
|
|
def expansions_to_common_l(ex1: Expansion, ex2: Expansion) -> (Expansion, Expansion):
|
|
|
common_l_array = np.union1d(ex1.l_array, ex2.l_array)
|
|
|
return coefs_fill_missing_l(ex1, common_l_array), coefs_fill_missing_l(ex2, common_l_array)
|
|
@@ -244,9 +299,11 @@ if __name__ == '__main__':
|
|
|
|
|
|
# ex = MappedExpansionQuad(0.44, 3, 1, 10)
|
|
|
# ex = Expansion(np.arange(3), np.array([1, -1, 0, 1, 2, 0, 3, 0, 2]))
|
|
|
- ex = GaussianCharges(omega_k=np.array([[0, 0], [np.pi, 0]]), lambda_k=10, sigma1=0.001, l_max=10)
|
|
|
+ # ex = GaussianCharges(omega_k=np.array([[0, 0], [np.pi, 0]]), lambda_k=10, sigma1=0.001, l_max=10)
|
|
|
+ ex = SphericalCap(np.array([[0, 0], [np.pi / 2, 0]]), 0.5, 0.1, 30)
|
|
|
# print(ex.coefs)
|
|
|
- plot_theta_profile(ex)
|
|
|
+ # plot_theta_profile(ex, num=1000, theta_end=2 * np.pi, phi=0)
|
|
|
+ plot_charge_3d(ex)
|
|
|
|
|
|
# new_coeffs = expansion_rotation(Quaternion(np.arange(20).reshape(5, 4)).normalized, ex.coeffs, ex.l_array)
|
|
|
# print(new_coeffs.shape)
|