potentials.py 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import expansion
  2. import functions as fn
  3. import numpy as np
  4. import parameters
  5. import units_and_constants as uc
  6. import matplotlib.pyplot as plt
  7. from scipy.special import eval_legendre, kv
  8. Array = np.ndarray
  9. ModelParams = parameters.ModelParams
  10. Expansion = expansion.Expansion
  11. def charged_shell_potential(theta: Array | float,
  12. phi: Array | float,
  13. dist: float,
  14. ex: Expansion,
  15. params: ModelParams,
  16. print_idx: int = None) -> Array:
  17. """
  18. Electrostatic potential around a charged shell with patches given by expansion over spherical harmonics.
  19. :param theta: array of azimuthal angles
  20. :param phi: array of polar angles
  21. :param dist: distance between the particles in units of radius R
  22. :param ex: Expansion object detailing patch distribution
  23. :param params: ModelParams object specifying parameter values for the model
  24. :param print_idx: if not None, print a single term for debugging purposes
  25. """
  26. theta, phi = np.broadcast_arrays(theta, phi)
  27. angles_shape = theta.shape
  28. theta = theta.reshape(-1) # ensures that arrays are 1D
  29. phi = phi.reshape(-1)
  30. if not theta.shape == phi.shape:
  31. raise ValueError('theta and phi arrays should have the same shape.')
  32. l_array, m_array = ex.lm_arrays
  33. dist = dist * params.R
  34. l_factors = (fn.coefficient_Cpm(ex.l_array, params.kappaR) * fn.sph_bessel_k(ex.l_array, params.kappa * dist)
  35. / fn.sph_bessel_k(ex.l_array, params.kappaR))
  36. l_factors = ex.repeat_over_m(l_factors)
  37. factors = l_factors[:, None] * ex.coefs[..., None] * fn.sph_harm(l_array[:, None], m_array[:, None], theta[None, :], phi[None, :])
  38. if print_idx is not None:
  39. print(l_array[print_idx], m_array[print_idx], np.real(factors[print_idx]))
  40. pot = (1 / (params.kappa * params.epsilon * uc.CONSTANTS.epsilon0) * np.real(np.sum(factors, axis=-2)))
  41. return pot.reshape(ex.shape + angles_shape)
  42. def inverse_patchy_particle_potential(theta: Array | float,
  43. dist: float,
  44. abar: float,
  45. Zc: float,
  46. Zp: tuple[float, float],
  47. params: ModelParams,
  48. lmax: int = 20):
  49. dist = dist * params.R
  50. out0 = ((Zc + Zp[0] + Zp[1]) * (np.exp(params.kappaR) / (1. + params.kappaR)) *
  51. (np.exp(-params.kappa * dist) / dist) / (params.epsilon * uc.CONSTANTS.epsilon0)) * params.R ** 2
  52. for l in range(2, lmax, 2):
  53. out1 = (Zp[0] * np.power(abar, l) * eval_legendre(l, np.cos(theta))
  54. + Zp[1] * np.power(abar, l) * eval_legendre(l, np.cos(np.pi - theta)))
  55. out0 += (((2 * l + 1.) * (kv(l + 0.5, params.kappa * dist) / kv(l + 1.5, params.kappaR))) * out1
  56. / (params.kappa * params.epsilon * uc.CONSTANTS.epsilon0))
  57. return out0
  58. if __name__ == '__main__':
  59. params = ModelParams(R=150, kappaR=3)
  60. ex1 = expansion.MappedExpansionQuad(0.44, params.kappaR, 0.001, l_max=30)
  61. # ex2 = expansion.SphericalCap(np.array([[0, 0], [np.pi, 0]]), 0.5, 0.003, l_max=70)
  62. theta = np.linspace(0, np.pi, 1000)
  63. phi = 0.
  64. dist = 1
  65. potential_ic = inverse_patchy_particle_potential(theta, dist, 0.44, -0.002, (0.001, 0.001), params)
  66. potential1 = charged_shell_potential(theta, phi, dist, ex1, params)
  67. # potential2 = charged_shell_potential(theta, phi, dist, ex2, params)
  68. # print(potential.shape)
  69. # print(potential)
  70. plt.plot(theta, potential_ic)
  71. plt.plot(theta, potential1.T)
  72. # plt.plot(theta, potential2.T)
  73. plt.show()