potentials.py 3.0 KB

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