File size: 5,352 Bytes
d68c650
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
from .common import *


def u_dot_muvec(mu, eta, w):
	"""
	Dot product of u = (cos(w), sin(w)) with the coordinate vector for mu.

	Args:
		mu (float or array-like): The mu coordinate(s).
		eta (float or array-like): The eta coordinate(s).
		w (float or array-like): The w coordinate(s).

	Returns:
		numpy.ndarray: The calculated dot product for each point.
	"""
	return (0.5 * np.cosh(mu) * np.cos(eta - w) + 0.5 * np.cosh(mu) * np.cos(eta + w)
			+ 0.5 * np.sinh(mu) * np.cos(eta - w) - 0.5 * np.sinh(mu) * np.cos(eta + w))


def u_dot_etavec(mu, eta, w):
	"""
	Dot product of u = (cos(w), sin(w)) with the coordinate vector for eta.

	Args:
		mu (float or array-like): The mu coordinate(s).
		eta (float or array-like): The eta coordinate(s).
		w (float or array-like): The w coordinate(s).

	Returns:
		numpy.ndarray: The calculated dot product for each point.
	"""
	return (-0.5 * np.sinh(mu) * np.sin(eta + w) - 0.5 * np.sinh(mu) * np.sin(eta - w)
			+ 0.5 * np.cosh(mu) * np.sin(eta + w) - 0.5 * np.cosh(mu) * np.sin(eta - w))


def get_system_ellipse(config):
	"""
	For an elliptical geometry, constructs the matrices, grids, and other quantities corresponding to the PDE system
	specified by "config"".

	Args:
		config (dict): Configuration dictionary containing the system parameters.

	Returns:
		tuple: A tuple containing matrices, grids, etc. for the PDE system
	"""
	required_keys = ['nx1', 'nx2', 'nx3', 'ell', 'a2', 'eccentricity']
	optional_keys = []

	missing_keys = [key for key in required_keys if key not in config]
	if missing_keys:
		raise KeyError(f"Missing keys in config: {', '.join(missing_keys)}")

	unused_keys = [key for key in config if key not in required_keys + optional_keys]
	if unused_keys:
		warnings.warn(f"Unused keys in config: {', '.join(unused_keys)}")

	for key in ['nx1', 'nx2', 'nx3']:
		if not isinstance(config[key], int):
			raise TypeError(f"{key} must be an integer.")

	for key in ['nx1', 'nx2', 'nx3', 'ell']:
		if config[key] <= 0:
			raise ValueError(f"{key} must be positive.")

	if not (0 <= config['eccentricity'] < 1.0):
		raise ValueError('eccentricity must be >= 0 and < 1.')

	if config['a2'] <= config['eccentricity']:
		raise ValueError(f'a2 must be greater than the eccentricity.')

	nmu, neta, nw = config['nx1'], config['nx2'], config['nx3']
	minor_axis_outer = config['a2']
	ell = config['ell']
	minor_axis = 1.0 - config['eccentricity']
	major_axis = 1.0
	focal_distance = np.sqrt(major_axis**2 - minor_axis**2)
	mu1 = np.arccosh(major_axis / focal_distance)
	major_axis_outer = np.sqrt(focal_distance**2 + minor_axis_outer**2)
	mu2 = np.arccosh(major_axis_outer / focal_distance)

	# 1D grids
	eta, w = get_angular_grids(neta, nw)
	# mu grid: non-uniform spacing and Dirichlet boundary conditions
	y = np.linspace(-np.pi / 2, np.pi / 2, nmu + 2, dtype = np.float64)
	mu_ = np.log((np.exp(mu2) - np.exp(mu1)) * (np.sin(y) / 2 + 0.5) + np.exp(mu1))
	dmu1 = mu_[1] - mu_[0]
	dmu2 = mu_[-1] - mu_[-2]
	mu = mu_[1:-1]

	# 1D finite-difference operators
	Deta_minus, Deta_plus = d_dx_upwind(eta, neta)
	D2w = d2_dx2_fourth_order(w, nw)
	Dmu_minus, Dmu_plus = d_dx_upwind_nonuniform(mu_, nmu)

	# 3D quantities. Kronecker products are used to build the 3D difference operators
	mu_3D, eta_3D, w_3D = np.meshgrid(mu, eta, w, indexing = 'ij')
	I_mu = sp.eye(nmu)
	I_eta = sp.eye(neta)
	I_w = sp.eye(nw)
	Deta_3D_minus = sp.kron(sp.kron(I_mu, Deta_minus), I_w)
	Deta_3D_plus = sp.kron(sp.kron(I_mu, Deta_plus), I_w)
	D2w_3D = sp.kron(sp.kron(I_mu, I_eta), D2w)
	Dmu_3D_minus = sp.kron(sp.kron(Dmu_minus, I_eta), I_w)
	Dmu_3D_plus = sp.kron(sp.kron(Dmu_plus, I_eta), I_w)

	# Metric tensor. Note that g_12 = g_21 = 0 and g_11 = g_22.
	g_11 = focal_distance * (np.cosh(mu_3D) * np.cosh(mu_3D) * np.cos(eta_3D) * np.cos(eta_3D)
			+ np.sinh(mu_3D) * np.sinh(mu_3D) * np.sin(eta_3D) * np.sin(eta_3D))

	# Dot products
	dp_mu = u_dot_muvec(mu_3D, eta_3D, w_3D)
	dp_eta = u_dot_etavec(mu_3D, eta_3D, w_3D)

	# Coefficient of d / dmu
	Dmu_3D_coeff_meshgrid = dp_mu / g_11
	test_ill_conditioned(Dmu_3D_coeff_meshgrid)

	Dmu_3D_coeff = sp.diags(Dmu_3D_coeff_meshgrid.ravel())

	# Coefficient of d / deta
	Deta_3D_coeff_meshgrid = dp_eta / g_11
	Deta_3D_coeff = sp.diags(Deta_3D_coeff_meshgrid.ravel())

	# Upwind differencing
	Dmu_3D_upwind = upwind_operator(Dmu_3D_minus, Dmu_3D_plus, Dmu_3D_coeff_meshgrid)
	Deta_3D_upwind = upwind_operator(Deta_3D_minus, Deta_3D_plus, Deta_3D_coeff_meshgrid)

	# Full operator
	L = Dmu_3D_coeff @ Dmu_3D_upwind + Deta_3D_coeff @ Deta_3D_upwind - (1 / ell) * D2w_3D

	return L, mu_3D, eta_3D, w_3D, dmu1, dmu2, Dmu_3D_coeff_meshgrid, major_axis_outer


def get_boundary_quantities_ellipse(mu_3D, eta_3D, w_3D):
	"""
	Gets grid coordinates on the boundaries, as well as slice arrays
	for positive/negative angles with respect to the boundary angle.

	Args:
		mu_3D: 3D array of mu values on the grid.
		eta_3D: 3D array of eta values on the grid.
		w_3D (numpy.ndarray): 3D array of w values on the grid.

	Returns:
		tuple: Tuple of the grid coordinates and slice arrays
	"""
	eta_2D_ib = eta_3D[0, ...]
	eta_2D_ob = eta_3D[-1, ...]
	w_2D_ib = w_3D[0, ...]
	w_2D_ob = w_3D[-1, ...]
	ib_slice = u_dot_muvec(mu_3D, eta_3D, w_3D)[0, ...] > 0
	ob_slice = u_dot_muvec(mu_3D, eta_3D, w_3D)[-1, ...] < 0
	return eta_2D_ib, eta_2D_ob, w_2D_ib, w_2D_ob, ib_slice, ob_slice