Coverage for src/swiift/lib/potential.py: 0%
106 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-07-01 15:44 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-07-01 15:44 +0200
1import itertools
3import attrs
4import numpy as np
5import scipy.sparse as sparse
7from ..model.model import DiscreteSpectrum, FreeSurfaceWaves, WavesUnderFloe
10@attrs.define
11class FilterSpectrum:
12 gravity: float
13 spectrum: DiscreteSpectrum
14 fsw: FreeSurfaceWaves
15 wuf: WavesUnderFloe
17 @classmethod
18 def from_ice_ocean(cls):
19 pass
22def scattering(self, floes: Sequence[Floe]) -> list[FloeCoupled]:
23 """Compute the scattering due to the array of floe.
25 Each floe in the domain impacts the propagation of the far-field
26 wave. Every edge causes some reflection and transmission.
27 This method compute the back-propagating and forward-propagating
28 waves generated by these edges for a given sequence of `Floe`
29 and returns the corresponding sequence of `FloeCoupled`.
31 Paramerers
32 ----------
33 floes : Sequence of Floe
34 Array of floes causing the scattering.
36 Returns
37 -------
38 list of FloeCoupled
39 The floes in the domain with fully-determined radiated fields.
41 """
42 if len(floes) >= 1:
44 def water_phase(j: int) -> np.ndarray:
45 assert 1 <= j <= nf - 1
46 return np.exp(
47 1j
48 * self.ocean.wave_numbers[:nvp1]
49 * (floes[j].left_edge - (floes[j - 1].left_edge + floes[j - 1].length))
50 )
52 def ice_phase(j: int) -> np.ndarray:
53 assert 0 <= j <= nf - 1
54 floe = floes[j]
55 if not np.isinf(floe.length):
56 return np.exp(1j * floe.length * floe.ice.wave_numbers[:nvp3])
57 else:
58 # si la longueur du floe est infinie, toutes les
59 # atténuations associées à des nombres d'ondes avec une
60 # partie imaginaire non-nulle tendent vers 0
61 msk = np.imag(floe.ice.wave_numbers[:nvp3]) == 0
62 phase = np.real(floe.ice.wave_numbers[:nvp3]) % (2 * PI)
63 return np.exp(1j * phase) * msk
65 nf = len(floes)
66 bs = 4 * self.nv + 8 # size of a square block in lhs matrix
67 nvp3 = self.nv + 3
68 nvp1 = self.nv + 1
69 hbs = bs // 2
70 nfm1 = nf - 1
71 nfp1 = nf + 1
73 # Cinq étapes:
74 # départ avec une matrice identité;
75 # remplissage de la diagonale-bloc principale, en haut puis
76 # en bas, le padding à droite (res à gauche) étant ajouté
77 # avec un produit matriciel;
78 # construction des 2ndes diagonales-bloc supérieure puis
79 # inférieure, le padding étant également ajouté via un
80 # produit matriciel.
82 # True diagonal
83 lhs = sparse.eye(nf * bs, format="csr")
84 # principal block-diagonal, above the diagonal
85 arr = np.empty((nf, hbs, nvp3), complex)
86 for i in range(nf):
87 ice = floes[i].ice
88 arr[i, :nvp1, :] = -ice.transmission_itow * ice_phase(i)
89 arr[i, nvp1:, :] = -ice.reflection_i * ice_phase(i)
90 indices = np.arange(0, nf)
91 indptr = np.zeros(2 * nf + 1)
92 indptr[:-1:2] = np.arange(0, nf)
93 indptr[1::2] = np.arange(1, nfp1)
94 indptr[-1] = nf
95 indices1 = np.arange(1, 2 * nf, 2)
96 indptr1 = np.arange(0, nfp1)
97 diag = sparse.bsr_matrix(
98 (
99 np.broadcast_to(np.eye(nvp3, hbs, dtype="int8"), (nf, nvp3, hbs)),
100 indices1,
101 indptr1,
102 ),
103 shape=(nvp3 * nf, bs * nf),
104 ).tocsr()
105 diag.eliminate_zeros()
106 lhs += (
107 sparse.bsr_matrix(
108 (arr, indices, indptr), shape=(nf * bs, nf * nvp3)
109 ).tocsr()
110 @ diag
111 )
112 # principal block-diagonal, below the diagonal
113 for i in range(nf):
114 ice = floes[i].ice
115 arr[i, :nvp3, :] = -ice.reflection_i * ice_phase(i)
116 arr[i, nvp3:, :] = -ice.transmission_itow * ice_phase(i)
117 indptr[1:] = indptr[0:-1]
118 indptr[0] = 0
119 diag = sparse.bsr_matrix(
120 (
121 np.broadcast_to(np.eye(nvp3, hbs, nvp1, dtype="int8"), (nf, nvp3, hbs)),
122 indices1 - 1,
123 indptr1,
124 ),
125 shape=(nvp3 * nf, bs * nf),
126 ).tocsr()
127 diag.eliminate_zeros()
128 lhs += (
129 sparse.bsr_matrix(
130 (arr, indices, indptr), shape=(nf * bs, nf * nvp3)
131 ).tocsr()
132 @ diag
133 )
134 # 2nd block-diagonal, upper
135 arr = np.empty((nfm1, hbs, nvp1), complex)
136 for i in range(nfm1):
137 ice = floes[i].ice
138 arr[i, :nvp3, :] = -ice.transmission_wtoi * water_phase(i + 1)
139 arr[i, nvp3:, :] = -ice.reflection_w * water_phase(i + 1)
140 indices = np.arange(1, nf)
141 indptr = np.zeros(2 * nf + 1, dtype=int)
142 indptr[0:-1:2] = np.arange(0, nf)
143 indptr[1::2] = np.arange(0, nf)
144 indptr[-1] = nfm1
145 indices1 = np.arange(2, 2 * nf, 2)
146 indptr1 = np.zeros(nfp1, dtype="int")
147 indptr1[1:] = np.arange(0, nf)
148 diag = sparse.bsr_matrix(
149 (
150 np.broadcast_to(np.eye(nvp1, hbs, dtype="int8"), (nfm1, nvp1, hbs)),
151 indices1,
152 indptr1,
153 ),
154 shape=(nvp1 * nf, bs * nf),
155 ).tocsr()
156 diag.eliminate_zeros()
157 lhs += (
158 sparse.bsr_matrix(
159 (arr, indices, indptr), shape=(nf * bs, nf * nvp1)
160 ).tocsr()
161 @ diag
162 )
163 # 2nd block-diagonal, lower
164 for i in range(nfm1):
165 ice = floes[i + 1].ice
166 arr[i, :nvp1, :] = -ice.reflection_w * water_phase(i + 1)
167 arr[i, nvp1:, :] = -ice.transmission_wtoi * water_phase(i + 1)
168 indptr[1:] = indptr[0:-1]
169 indptr[0] = 0
170 indptr1[0:-1] = indptr1[1:]
171 indptr1[-1] = nfm1
172 diag = sparse.bsr_matrix(
173 (
174 np.broadcast_to(
175 np.eye(nvp1, hbs, nvp3, dtype="int8"), (nfm1, nvp1, hbs)
176 ),
177 indices1 - 1,
178 indptr1,
179 ),
180 shape=(nvp1 * nf, bs * nf),
181 ).tocsr()
182 diag.eliminate_zeros()
183 lhs += (
184 sparse.bsr_matrix(
185 (arr, indices - 1, indptr), shape=(nf * bs, nf * nvp1)
186 ).tocsr()
187 @ diag
188 )
190 # forcing; it is assumed that nothing comes from the right
191 arr = np.empty((hbs, 1), dtype=complex)
192 arr[:nvp1, 0] = floes[0].ice.reflection_w[:, 0]
193 arr[nvp1:hbs, 0] = floes[0].ice.transmission_wtoi[:, 0]
194 arr *= self.wave.potential_amplitude
195 rhs = sparse.csc_matrix(arr)
197 # si le dernier floe est semi-infini, cas spécial :
198 # le dernier edge est une transition eau vers glace,
199 # un demi-bloc de moins est nécessaire.
200 if floes[-1].length == np.inf:
201 lhs = lhs[: nfm1 * bs + hbs, : nfm1 * bs + hbs]
202 rhs.resize(((nfm1 * bs + hbs, 1)))
203 else:
204 rhs.resize(((nf * bs, 1)))
206 # résolution du système
207 # spsolve options from benchmark on laptop
208 sol = sparse.linalg.spsolve(lhs, rhs, "NATURAL", False)
210 ww_neg = (sol[i * bs : i * bs + nvp1] for i in range(nf))
211 iw_pos = (sol[i * bs + nvp1 : i * bs + hbs] for i in range(nf))
212 if floes[-1].length == np.inf:
213 # No right_edge, so it doesn't radiate anything
214 l_iw_neg = [None]
215 l_ww_pos = [None]
216 else:
217 # Contribution of the right edge of the rightmost floe
218 l_iw_neg = [sol[nfm1 * bs + hbs : nfm1 * bs + hbs + nvp3]]
219 l_ww_pos = [sol[nfm1 * bs + hbs + nvp3 : nf * bs]]
220 iw_neg = (sol[i * bs + hbs : i * bs + hbs + nvp3] for i in range(nfm1))
221 ww_pos = (sol[i * bs + hbs + nvp3 : (i + 1) * bs] for i in range(nfm1))
223 return [
224 FloeCoupled(floe, (iw_n, iw_p, ww_n, ww_p))
225 for (floe, iw_n, iw_p, ww_n, ww_p) in zip(
226 floes,
227 itertools.chain(iw_neg, l_iw_neg),
228 iw_pos,
229 ww_neg,
230 itertools.chain(ww_pos, l_ww_pos),
231 )
232 ]