import datetime
from typing import Callable, Any
import scipy.io
import numpy as np
from numba import jit
from enum import Enum
from gtsimulation.common import Units, Regions
from gtsimulation.magnetic_field import AbsBfield
import sys
class ModelTypes(Enum):
base = 1,
expX = 2,
neCL = 3,
twistX = 4,
nebCor = 5,
cre10 = 6,
synCG = 7
[docs]
class UF23(AbsBfield):
ToMeters = Units.kpc2m
def __init__(self, model_type: ModelTypes | str = ModelTypes.base, use_noise=True, **kwargs):
super().__init__(**kwargs)
self.Region = Regions.Galaxy
self.ModelName = "UF23"
self.model_type = model_type if isinstance(model_type, ModelTypes) else ModelTypes[model_type]
self.Units = "kpc"
self.use_noise = use_noise
self.float_min = sys.float_info.min
# Regular field #
# Disk field
self.r_0 = 5 # kpc
self.r_1 = 5 # kpc
self.r_2 = 20 # kpc
self.w_1 = 0.5 # kpc
self.w_2 = 0.5 # kpc
# TODO add 'twistX' model
match self.model_type:
case ModelTypes.base:
# Disk field
self.pitch = 10.11 * np.pi / 180 # rad
self.z_disk = 0.794 # kpc
self.w_disk = 0.107 # kpc
self.B_1 = 1.09 # muG
self.B_2 = 2.66 # muG
self.B_3 = 3.12 # muG
self.phi_1 = 263 * np.pi / 180 # rad
self.phi_2 = 97.8 * np.pi / 180 # rad
self.phi_3 = 35.1 * np.pi / 180 # rad
# Toroidal halo
self.B_n = 3.26 # muG
self.B_s = -3.09 # muG
self.z_t = 4.0 # kpc
self.r_t = 10.19 # kpc
self.w_t = 1.7 # kpc
self.t = None # Myr
# Poloidal halo
self.B_p = 0.978 # muG
self.p = 1.43
self.z_p = 4.5 # kpc
self.r_p = 7.29 # kpc
self.w_p = 0.112 # kpc
self.a_c = 1e6 # kpc -- because 'a_c' -> inf
# Other model parameters
self.xi = 0.346
self.beta_str = 1 - (1 + self.xi) ** 2
case ModelTypes.expX:
# Disk field
self.pitch = 10.03 * np.pi / 180 # rad
self.z_disk = 0.715 # kpc
self.w_disk = 0.099 # kpc
self.B_1 = 0.99 # muG
self.B_2 = 2.18 # muG
self.B_3 = 3.12 # muG
self.phi_1 = 247 * np.pi / 180 # rad
self.phi_2 = 98.6 * np.pi / 180 # rad
self.phi_3 = 34.9 * np.pi / 180 # rad
# Toroidal halo
self.B_n = 2.71 # muG
self.B_s = -2.57 # muG
self.z_t = 5.5 # kpc
self.r_t = 10.13 # kpc
self.w_t = 2.1 # kpc
self.t = None # Myr
# Poloidal halo
self.B_p = 5.8 # muG
self.p = 1.95
self.z_p = 2.37 # kpc
self.r_p = 7.29 # kpc
self.w_p = None # kpc
self.a_c = 6.2 # kpc
# Other model parameters
self.xi = 0.51
self.beta_str = 1 - (1 + self.xi) ** 2
case ModelTypes.neCL:
# Disk field
self.pitch = 11.9 * np.pi / 180 # rad
self.z_disk = 0.674 # kpc
self.w_disk = 0.061 # kpc
self.B_1 = 1.43 # muG
self.B_2 = 1.4 # muG
self.B_3 = 3.44 # muG
self.phi_1 = 200 * np.pi / 180 # rad
self.phi_2 = 135 * np.pi / 180 # rad
self.phi_3 = 65 * np.pi / 180 # rad
# Toroidal halo
self.B_n = 2.63 # muG
self.B_s = -2.57 # muG
self.z_t = 4.6 # kpc
self.r_t = 10.13 # kpc
self.w_t = 1.15 # kpc
self.t = None # Myr
# Poloidal halo
self.B_p = 0.984 # muG
self.p = 1.68
self.z_p = 3.65 # kpc
self.r_p = 7.41 # kpc
self.w_p = 0.142 # kpc
self.a_c = 1e6 # kpc -- because 'a_c' -> inf
# Other model parameters
self.xi = 0.336
self.beta_str = 1 - (1 + self.xi) ** 2
case ModelTypes.nebCor:
# Disk field
self.pitch = 10.15 * np.pi / 180 # rad
self.z_disk = 0.812 # kpc
self.w_disk = 0.119 # kpc
self.B_1 = 1.41 # muG
self.B_2 = 3.53 # muG
self.B_3 = 4.13 # muG
self.phi_1 = 264 * np.pi / 180 # rad
self.phi_2 = 97.6 * np.pi / 180 # rad
self.phi_3 = 36.4 * np.pi / 180 # rad
# Toroidal halo
self.B_n = 4.6 # muG
self.B_s = -4.5 # muG
self.z_t = 3.6 # kpc
self.r_t = 10.21 # kpc
self.w_t = 1.7 # kpc
self.t = None # Myr
# Poloidal halo
self.B_p = 1.35 # muG
self.p = 1.34
self.z_p = 4.8 # kpc
self.r_p = 7.25 # kpc
self.w_p = 0.143 # kpc
self.a_c = 1e6 # kpc -- because 'a_c' -> inf
# Other model parameters
self.xi = 0.0
self.beta_str = 1 - (1 + self.xi) ** 2
case ModelTypes.synCG:
# Disk field
self.pitch = 9.90 * np.pi / 180 # rad
self.z_disk = 0.622 # kpc
self.w_disk = 0.067 # kpc
self.B_1 = 0.81 # muG
self.B_2 = 2.06 # muG
self.B_3 = 2.94 # muG
self.phi_1 = 230 * np.pi / 180 # rad
self.phi_2 = 97.4 * np.pi / 180 # rad
self.phi_3 = 32.9 * np.pi / 180 # rad
# Toroidal halo
self.B_n = 2.40 # muG
self.B_s = -2.09 # muG
self.z_t = 5.6 # kpc
self.r_t = 9.42 # kpc
self.w_t = 0.92 # kpc
self.t = None # Myr
# Poloidal halo
self.B_p = 0.809 # muG
self.p = 1.58
self.z_p = 3.53 # kpc
self.r_p = 7.46 # kpc
self.w_p = 0.150 # kpc
self.a_c = 1e6 # kpc -- because 'a_c' -> inf
# Other model parameters
self.xi = 0.63
self.beta_str = 1 - (1 + self.xi) ** 2
case ModelTypes.cre10:
# Disk field
self.pitch = 10.16 * np.pi / 180 # rad
self.z_disk = 0.808 # kpc
self.w_disk = 0.108 # kpc
self.B_1 = 1.20 # muG
self.B_2 = 2.75 # muG
self.B_3 = 3.21 # muG
self.phi_1 = 265 * np.pi / 180 # rad
self.phi_2 = 98.2 * np.pi / 180 # rad
self.phi_3 = 35.9 * np.pi / 180 # rad
# Toroidal halo
self.B_n = 3.7 # muG
self.B_s = -3.50 # muG
self.z_t = 2.9 # kpc
self.r_t = 10.41 # kpc
self.w_t = 1.7 # kpc
self.t = None # Myr
# Poloidal halo
self.B_p = 0.969 # muG
self.p = 1.42
self.z_p = 4.6 # kpc
self.r_p = 7.30 # kpc
self.w_p = 0.109 # kpc
self.a_c = 1e6 # kpc -- because 'a_c' -> inf
# Other model parameters
self.xi = 0.250
self.beta_str = 1 - (1 + self.xi) ** 2
[docs]
def CalcBfield(self, x, y, z, **kwargs):
return self.__calc_b_field(x, y, z,
self.pitch, self.z_disk, self.w_disk,
self.B_1, self.B_2, self.B_3,
self.phi_1, self.phi_2, self.phi_3,
self.r_0, self.r_1, self.r_2,
self.w_1, self.w_2,
self.B_n, self.B_s,
self.z_t, self.r_t, self.w_t, self.t,
self.B_p, self.p, self.z_p, self.r_p, self.w_p, self.a_c,
self.float_min,
self.use_noise, self.model_type.value[0])
@staticmethod
@jit(fastmath=True, nopython=True)
def __calc_b_field(x, y, z,
pitch, z_disk, w_disk,
B_1, B_2, B_3,
phi_1, phi_2, phi_3,
r_0, r_1, r_2,
w_1, w_2,
B_n, B_s,
z_t, r_t, w_t, t,
B_p, p, z_p, r_p, w_p, a_c,
float_min,
use_noise, model_type_value):
R = np.sqrt(x ** 2 + y ** 2 + z ** 2)
r = np.sqrt(x ** 2 + y ** 2)
phi = np.arctan2(y, x)
phi = phi if phi >= 0 else phi + 2 * np.pi
# Disk
phi_0 = phi - np.log(r / r_0) / np.tan(pitch)
B_ref = (B_1 * np.cos(1 * (phi_0 - phi_1)) + B_2 * np.cos(2 * (phi_0 - phi_2)) +
B_3 * np.cos(3 * (phi_0 - phi_3)))
sigmoid = lambda x_arg: 1.0 / (1.0 + np.exp(-x_arg))
h_disk = lambda z_arg: 1 - sigmoid((np.abs(z_arg) - z_disk) / w_disk)
g_disk = lambda r_arg: ((1 - sigmoid((r_arg - r_2) / w_2)) * sigmoid((r_arg - r_1) / w_1) *
(1 - np.exp(-r_arg ** 2)))
Br_d = np.sin(pitch) * r_0 / r * B_ref * h_disk(z) * g_disk(r)
Bphi_d = np.cos(pitch) * r_0 / r * B_ref * h_disk(z) * g_disk(r)
Bx_d = Br_d * np.cos(phi) - Bphi_d * np.sin(phi)
By_d = Br_d * np.sin(phi) + Bphi_d * np.cos(phi)
# Toroidal halo
Bphi_t = (1 - h_disk(z)) * np.exp(-np.abs(z) / z_t) * (1 - sigmoid((r - r_t) / w_t))
if z > 0:
Bphi_t *= B_n
else:
Bphi_t *= B_s
Bx_t = Bphi_t * np.sin(phi)
By_t = Bphi_t * np.cos(phi)
# Poloidal halo
c = np.power(a_c / z_p, p)
delta = np.power(a_c, p) + c * np.power(np.abs(z), p) - np.power(r, p)
k = 4 * np.power(a_c, p) * np.power(r, p)
a_p = 0.5 * k / (np.sqrt(np.power(delta, 2) + k) + delta)
a = np.power(a_p, 1 / p)
r_over_a = 1 / np.power(2 * np.power(a_c, p) / (np.sqrt(np.power(delta, 2) + k) + delta), 1 / p)
# sign_z = -1 if z < 0 else 1
# Radial functions
if model_type_value == 2:
f_x = np.exp(-a / r_p)
else:
f_x = 1 - sigmoid((a - r_p) / w_p)
B0 = B_p * f_x
if r <= float_min:
Br_p = 0.0
else:
Br_p = B0 * c * a / r_over_a * np.sign(z) * np.power(np.abs(z), p - 1) / np.sqrt(np.power(delta, 2) + k)
Bz_p = B0 * np.power(r_over_a, p - 2) * (a_p + np.power(a_c, p)) / np.sqrt(np.power(delta, 2) + k)
Bx_p = Br_p * np.cos(phi)
By_p = -Br_p * np.sin(phi)
# Total regular field
Bx = Bx_d + Bx_t + Bx_p
By = By_d + By_t + By_p
Bz = Bz_p
Babs = np.sqrt(Bx ** 2 + By ** 2 + Bz ** 2)
# TODO add irregular field
return 0.1 * Bx, 0.1 * By, 0.1 * Bz
[docs]
def UpdateState(self, new_date):
pass
[docs]
def to_string(self):
s = f"""UF23
Noise: {self.use_noise}
Model type: {self.model_type.name}"""
return s