Expose Ewald parameter to Python
[qpms.git] / qpms / tmatrices.c
blob44b8ed03dc430c553e8dbc1cb7a91d64e72a003b
1 #define _POSIX_C_SOURCE 200809L // for getline()
2 #define lapack_int int
3 #define lapack_complex_double complex double
4 #define lapack_complex_double_real(z) (creal(z))
5 #define lapack_complex_double_imag(z) (cimag(z))
6 #include <lapacke.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <cblas.h>
10 #include <unistd.h>
11 #include "scatsystem.h"
12 #include "indexing.h"
13 #include "vswf.h"
14 #include "groups.h"
15 #include "symmetries.h"
16 #include <assert.h>
17 #include "vectors.h"
18 #include "quaternions.h"
19 #include <string.h>
20 #include "qpms_error.h"
21 #include "tmatrices.h"
22 #include "qpms_specfunc.h"
23 #include "normalisation.h"
24 #include <errno.h>
25 #include <pthread.h>
26 #include <gsl/gsl_integration.h>
27 #include <gsl/gsl_errno.h>
28 #include "optim.h"
29 #include "oshacks.h"
31 // These are quite arbitrarily chosen constants for the quadrature in qpms_tmatrix_axialsym_fill()
32 #define TMATRIX_AXIALSYM_INTEGRAL_EPSREL (1e-5)
33 #define TMATRIX_AXIALSYM_INTEGRAL_EPSABS (1e-8)
35 #define HBAR (1.05457162825e-34)
36 #define ELECTRONVOLT (1.602176487e-19)
37 #define SCUFF_OMEGAUNIT (3e14)
39 #define SQ(x) ((x)*(x))
40 #define MAX(x,y) ((x) < (y) ? (y) : (x))
41 #define MIN(x,y) ((x) > (y) ? (y) : (x))
43 qpms_tmatrix_t *qpms_tmatrix_init(const qpms_vswf_set_spec_t *bspec) {
44 qpms_tmatrix_t *t;
45 QPMS_CRASHING_MALLOC(t, sizeof(qpms_tmatrix_t));
46 t->spec = bspec;
47 size_t n = bspec->n;
48 QPMS_CRASHING_CALLOC(t->m, n*n, sizeof(complex double));
49 t->owns_m = true;
50 return t;
53 qpms_tmatrix_t *qpms_tmatrix_init_from_generator(
54 const qpms_vswf_set_spec_t *bspec,
55 qpms_tmatrix_generator_t gen,
56 complex double omega) {
57 qpms_tmatrix_t *t = qpms_tmatrix_init(bspec);
58 QPMS_ENSURE_SUCCESS(gen.function(t, omega, gen.params));
59 return t;
62 qpms_tmatrix_t *qpms_tmatrix_copy(const qpms_tmatrix_t *T) {
63 qpms_tmatrix_t *t = qpms_tmatrix_init(T->spec);
64 size_t n = T->spec->n;
65 for(size_t i = 0; i < n*n; ++i)
66 t->m[i] = T->m[i];
67 return t;
70 void qpms_tmatrix_free(qpms_tmatrix_t *t){
71 if(t && t->owns_m) free(t->m);
72 free(t);
75 qpms_tmatrix_t *qpms_tmatrix_apply_symop_inplace(
76 qpms_tmatrix_t *T,
77 const complex double *M
80 //qpms_tmatrix_t *t = qpms_tmatrix_init(T->spec);
81 const size_t n = T->spec->n;
82 complex double tmp[n][n];
83 // tmp = M T
84 const complex double one = 1, zero = 0;
85 cblas_zgemm(CblasRowMajor,
86 CblasNoTrans,
87 CblasNoTrans,
88 n, n, n, &one, M, n, T->m, n, &zero, tmp, n);
89 // t->m = tmp M* = M T M*
90 cblas_zgemm(CblasRowMajor,
91 CblasNoTrans,
92 CblasConjTrans,
93 n, n, n, &one, tmp, n, M, n, &zero, T->m, n);
94 return T;
97 qpms_tmatrix_t *qpms_tmatrix_apply_symop(
98 const qpms_tmatrix_t *T,
99 const complex double *M
102 qpms_tmatrix_t *t = qpms_tmatrix_init(T->spec);
103 const size_t n = T->spec->n;
104 complex double tmp[n][n];
105 // tmp = M T
106 const complex double one = 1, zero = 0;
107 cblas_zgemm(CblasRowMajor,
108 CblasNoTrans,
109 CblasNoTrans,
110 n, n, n, &one, M, n, T->m, n, &zero, tmp, n);
111 // t->m = tmp M* = M T M*
112 cblas_zgemm(CblasRowMajor,
113 CblasNoTrans,
114 CblasConjTrans,
115 n, n, n, &one, tmp, n, M, n, &zero, t->m, n);
116 return t;
119 qpms_errno_t qpms_symmetrise_tmdata_irot3arr(
120 complex double *tmdata, const size_t tmcount,
121 const qpms_vswf_set_spec_t *bspec,
122 const size_t n_symops, const qpms_irot3_t *symops) {
123 const size_t n = bspec->n;
124 qpms_tmatrix_t *tmcopy = qpms_tmatrix_init(bspec);
125 complex double *symop_matrices = malloc(n*n*sizeof(complex double) * n_symops);
126 if(!symop_matrices) qpms_pr_error_at_flf(__FILE__, __LINE__, __func__,
127 "malloc() failed.");
128 for (size_t i = 0; i < n_symops; ++i)
129 qpms_irot3_uvswfi_dense(symop_matrices + i*n*n, bspec, symops[i]);
130 complex double tmp[n][n];
131 const complex double one = 1, zero = 0;
132 for (size_t tmi = 0; tmi < tmcount; ++tmi) {
133 // Move the data in tmcopy; we will then write the sum directly into tmdata.
134 memcpy(tmcopy->m, tmdata+n*n*tmi, n*n*sizeof(complex double));
135 memset(tmdata+n*n*tmi, 0, n*n*sizeof(complex double));
136 for (size_t i = 0; i < n_symops; ++i) {
137 const complex double *const M = symop_matrices + i*n*n;
138 // tmp = M T
139 cblas_zgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans,
140 n, n, n, &one, M, n, tmcopy->m, n, &zero, tmp, n);
141 // tmdata[...] += tmp M* = M T M*
142 cblas_zgemm(CblasRowMajor, CblasNoTrans, CblasConjTrans,
143 n, n, n, &one, tmp, n, M, n, &one, tmdata + tmi*n*n, n);
145 for (size_t ii = 0; ii < n*n; ++ii)
146 tmdata[n*n*tmi+ii] /= n_symops;
148 free(symop_matrices);
149 qpms_tmatrix_free(tmcopy);
150 return QPMS_SUCCESS;
153 qpms_errno_t qpms_symmetrise_tmdata_finite_group(
154 complex double *tmdata, const size_t tmcount,
155 const qpms_vswf_set_spec_t *bspec,
156 const qpms_finite_group_t *pointgroup) {
157 if (!(pointgroup->rep3d)) qpms_pr_error_at_flf(__FILE__, __LINE__, __func__,
158 "This function requires pointgroup->rep3d to be set correctly!");
159 return qpms_symmetrise_tmdata_irot3arr(tmdata, tmcount, bspec,
160 pointgroup->order, pointgroup->rep3d);
163 qpms_tmatrix_t *qpms_tmatrix_symmetrise_irot3arr_inplace(
164 qpms_tmatrix_t *T,
165 size_t n_symops,
166 const qpms_irot3_t *symops
168 if(qpms_symmetrise_tmdata_irot3arr(T->m, 1,
169 T->spec, n_symops, symops) != QPMS_SUCCESS)
170 return NULL;
171 else return T;
174 qpms_tmatrix_t *qpms_tmatrix_symmetrise_finite_group_inplace(
175 qpms_tmatrix_t *T,
176 const qpms_finite_group_t *pointgroup
178 if(qpms_symmetrise_tmdata_finite_group(T->m, 1,
179 T->spec, pointgroup) != QPMS_SUCCESS)
180 return NULL;
181 else return T;
184 qpms_tmatrix_t *qpms_tmatrix_symmetrise_involution_inplace(
185 qpms_tmatrix_t *T,
186 const complex double *M
189 qpms_tmatrix_t *t = qpms_tmatrix_apply_symop(T, M);
190 const size_t n = T->spec->n;
191 for(size_t i = 0; i < n*n; ++i)
192 T->m[i] = 0.5 * (t->m[i] + T->m[i]);
193 qpms_tmatrix_free(t);
194 return T;
197 qpms_tmatrix_t *qpms_tmatrix_symmetrise_involution(
198 const qpms_tmatrix_t *T,
199 const complex double *M
202 qpms_tmatrix_t *t = qpms_tmatrix_init(T->spec);
203 const size_t n = T->spec->n;
204 complex double tmp[n][n];
205 // tmp = M T
206 const complex double one = 1, zero = 0;
207 cblas_zgemm(CblasRowMajor,
208 CblasNoTrans,
209 CblasNoTrans,
210 n, n, n, &one, M, n, T->m, n, &zero, tmp, n);
211 // t->m = tmp M* = M T M*
212 cblas_zgemm(CblasRowMajor,
213 CblasNoTrans,
214 CblasConjTrans,
215 n, n, n, &one, tmp, n, M, n, &zero, t->m, n);
216 for(size_t i = 0; i < n*n; ++i)
217 t->m[i] = 0.5 * (t->m[i] + T->m[i]);
218 return t;
221 qpms_tmatrix_t *qpms_tmatrix_symmetrise_C_inf(const qpms_tmatrix_t *T) {
222 qpms_tmatrix_t *t = qpms_tmatrix_copy(T);
223 return qpms_tmatrix_symmetrise_C_inf_inplace(t);
226 qpms_tmatrix_t *qpms_tmatrix_symmetrise_C_inf_inplace(qpms_tmatrix_t *T) {
227 const size_t n = T->spec->n;
228 for (size_t row = 0; row < n; row++) {
229 qpms_m_t rm = qpms_uvswfi2m(T->spec->ilist[row]);
230 for (size_t col = 0; col < n; col++) {
231 qpms_m_t cm = qpms_uvswfi2m(T->spec->ilist[col]);
232 if (rm == cm)
233 ;// No-op // t->m[n*row + col] = T->m[n*row + col];
234 else
235 T->m[n*row + col] = 0;
238 return T;
241 qpms_tmatrix_t *qpms_tmatrix_symmetrise_C_N(const qpms_tmatrix_t *T, int N) {
242 qpms_tmatrix_t *t = qpms_tmatrix_copy(T);
243 return qpms_tmatrix_symmetrise_C_N_inplace(t, N);
246 qpms_tmatrix_t *qpms_tmatrix_symmetrise_C_N_inplace(qpms_tmatrix_t *T, int N) {
247 const size_t n = T->spec->n;
248 for (size_t row = 0; row < n; row++) {
249 qpms_m_t rm = qpms_uvswfi2m(T->spec->ilist[row]);
250 for (size_t col = 0; col < n; col++) {
251 qpms_m_t cm = qpms_uvswfi2m(T->spec->ilist[col]);
252 if (((rm - cm) % N) == 0)
253 ; // T->m[n*row + col] = T->m[n*row + col];
254 else
255 T->m[n*row + col] = 0;
258 return T;
261 bool qpms_tmatrix_isclose(const qpms_tmatrix_t *A, const qpms_tmatrix_t *B,
262 const double rtol, const double atol)
264 if (!qpms_vswf_set_spec_isidentical(A->spec, B->spec))
265 return false;
266 if (A->m == B->m)
267 return true;
268 const size_t n = A->spec->n;
269 for (size_t i = 0; i < n*n; ++i) {
270 const double tol = atol + rtol * (cabs(B->m[i]));
271 if ( cabs(B->m[i] - A->m[i]) > tol )
272 return false;
274 return true;
277 qpms_tmatrix_interpolator_t *qpms_tmatrix_interpolator_create(const size_t incount,
278 const double *freqs, const qpms_tmatrix_t *ta, const gsl_interp_type *iptype//, const bool copy_bspec
280 if (incount <= 0) return NULL;
281 qpms_tmatrix_interpolator_t *ip = malloc(sizeof(qpms_tmatrix_interpolator_t));
283 if (copy_bspec) {
284 ip->bspec = qpms_vswf_set_spec_copy(ta[0].spec);
285 ip->owns_bspec = true;
287 else {
289 ip->bspec = ta[0].spec;
290 // ip->owns_bspec = false;
292 const size_t n = ip->bspec->n;
294 // check if all matrices have the same bspec
295 for (size_t i = 0; i < incount; ++i)
296 QPMS_ENSURE(qpms_vswf_set_spec_isidentical(ip->bspec, ta[i].spec),
297 "All T-matrices for interpolation must have the same basis!");
299 QPMS_CRASHING_CALLOC(ip->splines_real, n*n, sizeof(gsl_spline *));
300 QPMS_CRASHING_CALLOC(ip->splines_imag, n*n,sizeof(gsl_spline *));
301 for (size_t row = 0; row < n; ++row)
302 for (size_t col = 0; col < n; ++col) {
303 double y_real[incount], y_imag[incount];
304 bool n0_real = false, n0_imag = false;
305 for (size_t i = 0; i < incount; ++i) {
306 complex double telem = ta[i].m[n * row + col];
307 if ((y_real[i] = creal(telem))) n0_real = true;
308 if ((y_imag[i] = cimag(telem))) n0_imag = true;
310 if (n0_real) {
311 gsl_spline *s =
312 ip->splines_real[n * row + col] = gsl_spline_alloc(iptype, incount);
313 QPMS_ENSURE_SUCCESS(gsl_spline_init(s, freqs, y_real, incount));
315 else ip->splines_real[n * row + col] = NULL;
316 if (n0_imag) {
317 gsl_spline *s =
318 ip->splines_imag[n * row + col] = gsl_spline_alloc(iptype, incount);
319 QPMS_ENSURE_SUCCESS(gsl_spline_init(s, freqs, y_imag, incount));
321 else ip->splines_imag[n * row + col] = NULL;
323 return ip;
326 void qpms_tmatrix_interpolator_free(qpms_tmatrix_interpolator_t *ip) {
327 if (ip) {
328 const size_t n = ip->bspec->n;
329 for (size_t i = 0; i < n*n; ++i) {
330 if (ip->splines_real[i]) gsl_spline_free(ip->splines_real[i]);
331 if (ip->splines_imag[i]) gsl_spline_free(ip->splines_imag[i]);
333 //if (ip->owns_bspec)
334 // qpms_vswf_set_spec_free(ip->bspec);
335 free(ip);
339 qpms_tmatrix_t *qpms_tmatrix_interpolator_eval(const qpms_tmatrix_interpolator_t *ip, double freq) {
340 qpms_tmatrix_t *t = qpms_tmatrix_init(ip->bspec);
341 QPMS_ENSURE_SUCCESS(qpms_tmatrix_interpolator_eval_fill(t, ip, freq));
342 return t;
345 qpms_errno_t qpms_tmatrix_interpolator_eval_fill(qpms_tmatrix_t *t,
346 const qpms_tmatrix_interpolator_t *ip, double freq) {
347 QPMS_ENSURE(qpms_vswf_set_spec_isidentical(t->spec, ip->bspec), "Tried to fill a T-matrix with an incompatible interpolator!");
348 const size_t n = ip->bspec->n;
349 for (size_t i = 0; i < n*n; ++i){
350 if (ip->splines_real[i]) t->m[i] = gsl_spline_eval(ip->splines_real[i], freq, NULL /*does this work?*/);
351 if (ip->splines_imag[i]) t->m[i] += I* gsl_spline_eval(ip->splines_imag[i], freq, NULL /*does this work?*/);
353 return QPMS_SUCCESS;
356 qpms_errno_t qpms_tmatrix_generator_interpolator(qpms_tmatrix_t *t,
357 complex double omega, const void *interp)
359 return qpms_tmatrix_interpolator_eval_fill(t,
360 (const qpms_tmatrix_interpolator_t *) interp, omega);
363 double qpms_SU2eV(double e_SU) {
364 return e_SU * SCUFF_OMEGAUNIT / (ELECTRONVOLT / HBAR);
367 double qpms_SU2SI(double e_SU) {
368 return e_SU * SCUFF_OMEGAUNIT;
371 /// TODO doc and more checks
372 qpms_errno_t qpms_read_scuff_tmatrix(
373 FILE *f, ///< file handle
374 const qpms_vswf_set_spec_t * bs, ///< VSWF set spec
375 size_t *const n, ///< Number of successfully loaded t-matrices
376 double* *const freqs, ///< Frequencies in SI units
377 double* *const freqs_su, ///< Frequencies in SCUFF units (optional)
378 qpms_tmatrix_t* *const tmatrices_array, ///< The resulting T-matrices (optional).
379 complex double* *const tmdata
381 if (!(freqs && n && tmdata))
382 qpms_pr_error_at_flf(__FILE__, __LINE__, __func__,
383 "freqs, n, and tmdata are mandatory arguments and must not be NULL.");
384 if(bs->norm & (QPMS_NORMALISATION_REVERSE_AZIMUTHAL_PHASE
385 | QPMS_NORMALISATION_SPHARM_REAL))
386 QPMS_NOT_IMPLEMENTED("Sorry, only standard complex-spherical harmonic based waves are supported right now");
387 int n_alloc = 128; // First chunk to allocate
388 *n = 0;
389 *freqs = malloc(n_alloc * sizeof(double));
390 if (freqs_su) *freqs_su = malloc(n_alloc * sizeof(double));
391 *tmdata = malloc(sizeof(complex double) * bs->n * bs->n * n_alloc);
392 if (!*freqs || (!freqs_su != !*freqs_su) || !*tmdata)
393 qpms_pr_error_at_flf(__FILE__, __LINE__, __func__,
394 "malloc() failed.");
395 size_t linebufsz = 256;
396 char *linebuf = malloc(linebufsz);
397 ssize_t readchars;
398 double lastfreq_su = NAN;
399 while((readchars = getline(&linebuf, &linebufsz, f)) != -1) {
400 if (linebuf[0] == '#') continue;
401 int Alpha, LAlpha, MAlpha, PAlpha, Beta, LBeta, MBeta, PBeta;
402 double currentfreq_su, tr, ti;
403 QPMS_ENSURE(11 == sscanf(linebuf, "%lf %d %d %d %d %d %d %d %d %lf %lf",
404 &currentfreq_su, &Alpha, &LAlpha, &MAlpha, &PAlpha,
405 &Beta, &LBeta, &MBeta, &PBeta, &tr, &ti),
406 "Malformed T-matrix file");
407 if (currentfreq_su != lastfreq_su) { // New frequency -> new T-matrix
408 ++*n;
409 lastfreq_su = currentfreq_su;
410 if(*n > n_alloc) {
411 n_alloc *= 2;
412 QPMS_CRASHING_REALLOC(*freqs, n_alloc * sizeof(double));
413 if (freqs_su) {QPMS_CRASHING_REALLOC(*freqs_su, n_alloc * sizeof(double));}
414 QPMS_CRASHING_REALLOC(*tmdata, sizeof(complex double) * bs->n * bs->n * n_alloc);
416 if (freqs_su) (*freqs_su)[*n-1] = currentfreq_su;
417 (*freqs)[*n-1] = qpms_SU2SI(currentfreq_su);
419 for(size_t i = 0; i < bs->n * bs->n; ++i)
420 (*tmdata)[(*n-1)*bs->n*bs->n + i] = NAN + I*NAN;
422 qpms_vswf_type_t TAlpha, TBeta;
423 switch(PAlpha) {
424 case 0: TAlpha = QPMS_VSWF_MAGNETIC; break;
425 case 1: TAlpha = QPMS_VSWF_ELECTRIC; break;
426 default: QPMS_WTF;
428 switch(PBeta) {
429 case 0: TBeta = QPMS_VSWF_MAGNETIC; break;
430 case 1: TBeta = QPMS_VSWF_ELECTRIC; break;
431 default: QPMS_WTF;
433 qpms_uvswfi_t srcui = qpms_tmn2uvswfi(TAlpha, MAlpha, LAlpha),
434 destui = qpms_tmn2uvswfi(TBeta, MBeta, LBeta);
435 ssize_t srci = qpms_vswf_set_spec_find_uvswfi(bs, srcui),
436 desti = qpms_vswf_set_spec_find_uvswfi(bs, destui);
437 if (srci == -1 || desti == -1)
438 /* This element has not been requested in bs->ilist. */
439 continue;
440 else
441 (*tmdata)[(*n-1)*bs->n*bs->n + desti*bs->n + srci] = (tr + I*ti)
442 * qpms_normalisation_factor_uvswfi(bs->norm, srcui)
443 / qpms_normalisation_factor_uvswfi(bs->norm, destui)
444 * qpms_normalisation_factor_uvswfi(QPMS_NORMALISATION_CONVENTION_SCUFF, destui)
445 / qpms_normalisation_factor_uvswfi(QPMS_NORMALISATION_CONVENTION_SCUFF, srcui);
447 free(linebuf);
448 // free some more memory
449 n_alloc = *n;
450 *freqs = realloc(*freqs, n_alloc * sizeof(double));
451 if (freqs_su) *freqs_su = realloc(*freqs_su, n_alloc * sizeof(double));
452 if (tmatrices_array) *tmatrices_array = realloc(*tmatrices_array, n_alloc * sizeof(qpms_tmatrix_t));
453 *tmdata = realloc(*tmdata, sizeof(complex double) * bs->n * bs->n * n_alloc);
454 if (!*freqs || (!freqs_su != !*freqs_su) || !*tmdata)
455 qpms_pr_error_at_flf(__FILE__, __LINE__, __func__,
456 "realloc() failed.");
457 if (tmatrices_array) {
458 *tmatrices_array = malloc(n_alloc * sizeof(qpms_tmatrix_t));
459 if (!*tmatrices_array)
460 qpms_pr_error_at_flf(__FILE__, __LINE__, __func__,
461 "malloc() failed.");
462 for (size_t i = 0; i < *n; ++i) {
463 (*tmatrices_array)[i].spec = bs;
464 (*tmatrices_array)[i].m = (*tmdata) + i * bs->n * bs->n;
465 (*tmatrices_array)[i].owns_m = false;
468 return QPMS_SUCCESS;
471 bool qpms_load_scuff_tmatrix_crash_on_failure = true;
473 qpms_errno_t qpms_load_scuff_tmatrix(
474 const char *path, ///< file path
475 const qpms_vswf_set_spec_t * bs, ///< VSWF set spec
476 size_t *const n, ///< Number of successfully loaded t-matrices
477 double **const freqs, ///< Frequencies in SI units
478 double ** const freqs_su, ///< Frequencies in SCUFF units (optional)
479 qpms_tmatrix_t ** const tmatrices_array, ///< The resulting T-matrices (optional).
480 complex double ** const tmdata
482 FILE *f = fopen(path, "r");
483 if (!f) {
484 if (qpms_load_scuff_tmatrix_crash_on_failure) {
485 QPMS_PR_ERROR("Could not open T-matrix file %s", path);
486 } else return errno;
488 qpms_errno_t retval =
489 qpms_read_scuff_tmatrix(f, bs, n, freqs, freqs_su, tmatrices_array, tmdata);
491 for (size_t i = 0; i < *n * bs->n * bs->n; ++i)
492 if(isnan(creal((*tmdata)[i])) || isnan(cimag((*tmdata)[i]))) {
493 QPMS_WARN("Encountered NAN in a loaded T-matrix");
494 retval |= QPMS_NAN_ENCOUNTERED;
495 break;
498 if(fclose(f)) { QPMS_PR_ERROR("Could not close the T-matrix file %s "
499 "(well, that's weird, since it's read only).", path); }
501 return retval;
504 complex double *qpms_mie_coefficients_reflection(
505 complex double *target, ///< Target array of length bspec->n. If NULL, a new one will be allocated.
506 const qpms_vswf_set_spec_t *bspec, ///< Defines which of the coefficients are calculated.
507 double a, ///< Radius of the sphere.
508 complex double k_i, ///< Wave number of the internal material of the sphere.
509 complex double k_e, ///< Wave number of the surrounding medium.
510 complex double mu_i, ///< Relative permeability of the sphere material.
511 complex double mu_e, ///< Relative permeability of the surrounding medium.
512 qpms_bessel_t J_ext,
513 qpms_bessel_t J_scat
514 // TODO J_ext, J_scat?
517 * This implementation pretty much copies mie_coefficients() from qpms_p.py, so
518 * any bugs there should affect this function as well and perhaps vice versa.
520 * Compared to mie_coefficients() from qpms_p.py, this has opposite sign.
522 QPMS_ENSURE(J_ext != J_scat, "J_ext and J_scat must not be equal. Perhaps you want J_ext = 1 and J_scat = 3 anyways.");
523 if (!target) QPMS_CRASHING_MALLOC(target, bspec->n * sizeof(complex double));
524 qpms_l_t lMax = bspec->lMax;
525 complex double x_i = k_i * a;
526 complex double x_e = k_e * a;
527 // complex double m = k_i / k_e;
528 complex double eta_inv_i = k_i / mu_i;
529 complex double eta_inv_e = k_e / mu_e;
531 complex double zi[lMax + 2];
532 complex double ze[lMax + 2];
533 complex double zs[lMax + 2];
534 complex double RH[lMax + 1] /* MAGNETIC */, RV[lMax+1] /* ELECTRIC */;
535 QPMS_ENSURE_SUCCESS(qpms_sph_bessel_fill(QPMS_BESSEL_REGULAR, lMax+1, x_i, zi));
536 QPMS_ENSURE_SUCCESS(qpms_sph_bessel_fill(J_ext, lMax+1, x_e, ze));
537 QPMS_ENSURE_SUCCESS(qpms_sph_bessel_fill(J_scat, lMax+1, x_e, zs));
538 for (qpms_l_t l = 0; l <= lMax; ++l) {
539 // Bessel function derivatives as in DLMF 10.51.2
540 complex double dzi = -zi[l+1] + l/x_i*zi[l];
541 complex double dze = -ze[l+1] + l/x_e*ze[l];
542 complex double dzs = -zs[l+1] + l/x_e*zs[l];
543 complex double fi = zi[l]/x_i+dzi;
544 complex double fs = zs[l]/x_e+dzs;
545 complex double fe = ze[l]/x_e+dze;
546 RV[l] = (-eta_inv_i * fe * zi[l] + eta_inv_e * ze[l] * fi)/(-eta_inv_e * fi * zs[l] + eta_inv_i * zi[l] * fs);
547 RH[l] = (-eta_inv_e * fe * zi[l] + eta_inv_i * ze[l] * fi)/(-eta_inv_i * fi * zs[l] + eta_inv_e * zi[l] * fs);
550 for (size_t i = 0; i < bspec->n; ++i) {
551 qpms_l_t l; qpms_m_t m; qpms_vswf_type_t t;
552 QPMS_ENSURE_SUCCESS(qpms_uvswfi2tmn(bspec->ilist[i], &t, &m, &l));
553 assert(l <= lMax);
554 switch(t) {
555 case QPMS_VSWF_ELECTRIC:
556 target[i] = RV[l];
557 break;
558 case QPMS_VSWF_MAGNETIC:
559 target[i] = RH[l];
560 break;
561 default: QPMS_WTF;
564 return target;
567 /// Replaces the contents of an existing T-matrix with that of a spherical nanoparticle calculated using the Lorentz-mie theory.
568 qpms_errno_t qpms_tmatrix_spherical_fill(qpms_tmatrix_t *t, ///< T-matrix whose contents are to be replaced. Not NULL.
569 double a, ///< Radius of the sphere.
570 complex double k_i, ///< Wave number of the internal material of the sphere.
571 complex double k_e, ///< Wave number of the surrounding medium.
572 complex double mu_i, ///< Relative permeability of the sphere material.
573 complex double mu_e ///< Relative permeability of the surrounding medium.
575 complex double *miecoeffs = qpms_mie_coefficients_reflection(NULL, t->spec, a, k_i, k_e, mu_i, mu_e,
576 QPMS_BESSEL_REGULAR, QPMS_HANKEL_PLUS);
577 memset(t->m, 0, SQ(t->spec->n)*sizeof(complex double));
578 for(size_t i = 0; i < t->spec->n; ++i)
579 t->m[t->spec->n*i + i] = miecoeffs[i];
580 free(miecoeffs);
581 return QPMS_SUCCESS;
584 qpms_errno_t qpms_tmatrix_generator_sphere(qpms_tmatrix_t *t,
585 complex double omega, const void *params)
587 const qpms_tmatrix_generator_sphere_param_t *p = params;
588 qpms_epsmu_t out = p->outside.function(omega, p->outside.params);
589 qpms_epsmu_t in = p->inside.function(omega, p->inside.params);
590 return qpms_tmatrix_spherical_fill(t,
591 p->radius,
592 qpms_wavenumber(omega, in),
593 qpms_wavenumber(omega, out),
594 in.mu, out.mu);
597 /// Convenience function to calculate T-matrix of a non-magnetic spherical \
598 particle using the permittivity values, replacing existing T-matrix data.
599 qpms_errno_t qpms_tmatrix_spherical_mu0_fill(
600 qpms_tmatrix_t *t, ///< T-matrix whose contents are to be replaced. Not NULL.
601 double a, ///< Radius of the sphere.
602 double omega, ///< Angular frequency.
603 complex double epsilon_fg, ///< Permittivity of the sphere.
604 complex double epsilon_bg ///< Permittivity of the background medium.
607 complex double k_i = csqrt(epsilon_fg) * omega / SPEED_OF_LIGHT;
608 complex double k_e = csqrt(epsilon_bg) * omega / SPEED_OF_LIGHT;
609 return qpms_tmatrix_spherical_fill(t, a, k_i, k_e, 1, 1);
612 complex double *qpms_apply_tmatrix(
613 complex double *f, const complex double *a, const qpms_tmatrix_t *T)
615 const size_t n = T->spec->n;
616 if(!f)
617 QPMS_CRASHING_CALLOC(f, n, sizeof(complex double));
618 const complex double one = 1;
619 const complex double zero = 0;
620 cblas_zgemv(CblasRowMajor, CblasNoTrans, n, n, &one, T->m, n, a, 1, &zero, f, 1);
621 return f;
624 qpms_arc_function_retval_t qpms_arc_sphere(double theta, const void *R) {
625 qpms_arc_function_retval_t retval = {*(const double*)R, 0};
626 return retval;
629 qpms_arc_function_retval_t qpms_arc_cylinder(double theta, const void *param) {
630 const qpms_arc_cylinder_params_t *p = param;
631 double thresh = atan(2 * p->R / p->h);
632 QPMS_ENSURE(theta >= 0 && theta <= M_PI,
633 "theta = %g, but it must lie in interval [0, M_PI]", theta);
634 qpms_arc_function_retval_t res;
635 if (theta < thresh) {
636 // Upper base
637 res.r = 0.5 * p->h / cos(theta);
638 res.beta = -theta;
639 } else if (theta <= M_PI - thresh) {
640 // Side
641 res.r = p->R / cos(theta - M_PI_2);
642 res.beta = -theta + M_PI_2;
643 } else {
644 // Lower base
645 res.r = 0.5 * p->h / cos(theta - M_PI);
646 res.beta = -theta + M_PI;
648 return res;
652 struct tmatrix_axialsym_integral_param_t {
653 const qpms_vswf_set_spec_t *bspec;
654 qpms_l_t l, l_in;
655 qpms_m_t m; // m_in = -m
656 qpms_vswf_type_t t, t_in;
657 qpms_arc_function_t f;
658 complex double k_in, k, z_in, z;
659 bool realpart; // Otherwise imaginary part
660 qpms_bessel_t btype; // For Q QPMS_HANKEL_PLUS, for R QPMS_BESSEL_REGULAR
663 static double tmatrix_axialsym_integrand(double theta, void *param) {
664 // Pretty inefficient; either real or imaginary part is thrown away etc.
665 struct tmatrix_axialsym_integral_param_t *p = param;
666 qpms_arc_function_retval_t rb = p->f.function(theta, p->f.params);
667 csph_t kr = {rb.r * p->k, theta, 0},
668 kr_in = {rb.r * p->k_in, theta, 0};
670 csphvec_t y_el = qpms_vswf_single_el_csph(p->m, p->l, kr, p->btype, p->bspec->norm);
671 csphvec_t y_mg = qpms_vswf_single_mg_csph(p->m, p->l, kr, p->btype, p->bspec->norm);
672 csphvec_t v_in_el = qpms_vswf_single_el_csph(-p->m, p->l_in, kr_in, QPMS_BESSEL_REGULAR, p->bspec->norm);
673 csphvec_t v_in_mg = qpms_vswf_single_mg_csph(-p->m, p->l_in, kr_in, QPMS_BESSEL_REGULAR, p->bspec->norm);
674 csphvec_t y1, y2, v_in1, v_in2;
675 switch(p->t) {
676 case QPMS_VSWF_ELECTRIC: y1 = y_el; y2 = y_mg; break;
677 case QPMS_VSWF_MAGNETIC: y1 = y_mg; y2 = y_el; break;
678 default: QPMS_WTF;
680 switch(p->t_in) {
681 case QPMS_VSWF_ELECTRIC: v_in1 = v_in_mg; v_in2 = v_in_el; break;
682 case QPMS_VSWF_MAGNETIC: v_in1 = v_in_el; v_in2 = v_in_mg; break;
683 default: QPMS_WTF;
685 // Normal vector components
686 double nrc = cos(rb.beta), nthetac = sin(rb.beta);
687 // First triple product
688 complex double tp1 = nrc * (y1.thetac * v_in1.phic - y1.phic * v_in1.thetac)
689 + nthetac * (y1.phic * v_in1.rc - y1.rc * v_in1.phic);
690 // Second thiple product
691 complex double tp2 = nrc * (y2.thetac * v_in2.phic - y2.phic * v_in2.thetac)
692 + nthetac * (y2.phic * v_in2.rc - y2.rc * v_in2.phic);
693 double jac = SQ(rb.r) * sin(theta) / nrc; // Jacobian
694 complex double res = jac * (p->z/p->z_in * tp1 + tp2);
695 return p->realpart ? creal(res) : cimag(res);
700 long qpms_axsym_tmatrix_integration_nthreads_default = 4;
701 long qpms_axsym_tmatrix_integration_nthreads_override = 0;
703 void qpms_axsym_tmatrix_integration_set_nthreads(long n) {
704 qpms_axsym_tmatrix_integration_nthreads_override = n;
707 struct qpms_tmatrix_axialsym_fill_integration_thread_arg {
708 complex double *Q, *R;
709 const qpms_vswf_set_spec_t *bspecQR;
710 double epsrel, epsabs;
711 size_t intlimit;
712 qpms_arc_function_t f;
713 const struct tmatrix_axialsym_integral_param_t *p_template; // Thread must copy this and set its own realpart and btype fields.
714 size_t *i2start_ptr;
715 pthread_mutex_t *i2start_mutex;
718 // This wrapper retries to calculate the T-matrix integral and if it fails, reduces torelance.
719 static inline int axint_wrapper(const gsl_function *f, double epsabs, double epsrel,
720 size_t intlimit, gsl_integration_workspace *w, double *result, double *abserr) {
721 static int tol_exceeded = 0;
722 double errfac;
723 int retval;
724 for (errfac = 1; epsabs*errfac < 0.01 && epsrel*errfac < 0.01; errfac *= 8) {
725 retval = gsl_integration_qag(f, 0, M_PI, epsabs*errfac, epsrel*errfac,
726 intlimit, 2, w, result, abserr);
727 if(QPMS_LIKELY(!retval)) {
728 if(QPMS_LIKELY(errfac == 1))
729 return 0;
730 else {
731 QPMS_DEBUG(QPMS_DBGMSG_INTEGRATION,
732 "T-matrix quadrature succeeded only after %g-fold"
733 "tolerance reduction."); // TODO more details here?
734 return GSL_EROUND;
737 else if(retval == GSL_EROUND) {
738 if (!tol_exceeded) {
739 QPMS_WARN("T-matrix quadrature failed; retrying with worse tolerances."
740 " (this warning will be shown only once).");
742 tol_exceeded += 1;
744 else QPMS_ENSURE_SUCCESS(retval);
746 QPMS_PR_ERROR("T-matrix quadrature failed even after harsh tolerance reduction.");
750 static void * qpms_tmatrix_axialsym_fill_integration_thread(void *arg) {
751 const struct qpms_tmatrix_axialsym_fill_integration_thread_arg *a = arg;
752 struct tmatrix_axialsym_integral_param_t p = *a->p_template;
753 const gsl_function f = {tmatrix_axialsym_integrand, (void *) &p};
754 gsl_integration_workspace *w = gsl_integration_workspace_alloc(a->intlimit);
756 while(1) {
757 QPMS_ENSURE_SUCCESS(pthread_mutex_lock(a->i2start_mutex));
758 if(*(a->i2start_ptr) >= a->bspecQR->n) {// Everything already done, leave thread
759 QPMS_ENSURE_SUCCESS(pthread_mutex_unlock(a->i2start_mutex));
760 break;
762 const size_t i2 = *(a->i2start_ptr);
763 ++*(a->i2start_ptr);
764 QPMS_ENSURE_SUCCESS(pthread_mutex_unlock(a->i2start_mutex));
765 for(size_t i1 = 0; i1 < a->bspecQR->n; ++i1) {
766 // NOTE that the Q', R' matrices are !TRANSPOSED! here because of zgetrs
767 const size_t iQR = i1 + i2 * a->bspecQR->n;
768 qpms_m_t m1, m2;
769 qpms_l_t l1, l2;
770 qpms_vswf_type_t t1, t2;
771 const qpms_uvswfi_t u1 = a->bspecQR->ilist[i1], u2 = a->bspecQR->ilist[i2];
772 QPMS_ENSURE_SUCCESS(qpms_uvswfi2tmn(u1, &t1, &m1, &l1));
773 QPMS_ENSURE_SUCCESS(qpms_uvswfi2tmn(u2, &t2, &m2, &l2));
774 if (m1 + m2) {
775 a->Q[iQR] = 0;
776 a->R[iQR] = 0;
777 } else {
778 p.m = m1;
779 p.l = l1; p.l_in = l2;
780 p.t = t1; p.t_in = t2;
782 double result;
783 double abserr; // gsl_integration_qag() needs this; thrown away for now
784 // Re(R')
785 p.btype = QPMS_BESSEL_REGULAR;
786 p.realpart = true;
787 axint_wrapper(&f, a->epsabs, a->epsrel, a->intlimit, w, &result, &abserr);
788 a->R[iQR] = result;
790 // Im(R')
791 p.realpart = false;
792 axint_wrapper(&f, a->epsabs, a->epsrel, a->intlimit, w, &result, &abserr);
793 a->R[iQR] += I*result;
795 // Re(Q')
796 p.btype = QPMS_HANKEL_PLUS;
797 p.realpart = true;
798 axint_wrapper(&f, a->epsabs, a->epsrel, a->intlimit, w, &result, &abserr);
799 a->Q[iQR] = result;
801 // Im(Q')
802 p.realpart = false;
803 axint_wrapper(&f, a->epsabs, a->epsrel, a->intlimit, w, &result, &abserr);
804 a->Q[iQR] += I*result;
808 gsl_integration_workspace_free(w);
811 qpms_errno_t qpms_tmatrix_axialsym_fill(
812 qpms_tmatrix_t *t, complex double omega, qpms_epsmu_t outside,
813 qpms_epsmu_t inside,qpms_arc_function_t shape, qpms_l_t lMaxQR)
815 QPMS_UNTESTED; // TODO also check whether this is valid for all norms.
816 const qpms_vswf_set_spec_t *bspec = t->spec;
817 struct tmatrix_axialsym_integral_param_t p;
818 p.k = qpms_wavenumber(omega, outside);
819 p.k_in = qpms_wavenumber(omega, inside);
820 p.z = qpms_waveimpedance(outside);
821 p.z_in = qpms_waveimpedance(inside);
822 p.f = shape;
823 p.bspec = bspec;
825 if (lMaxQR < bspec->lMax) lMaxQR = bspec->lMax;
826 qpms_vswf_set_spec_t *bspecQR = qpms_vswf_set_spec_from_lMax(lMaxQR, bspec->norm);
827 size_t *reindex = qpms_vswf_set_reindex(bspec, bspecQR);
829 // Q' and R' matrices
830 complex double *Q, *R;
831 QPMS_CRASHING_MALLOC(Q, sizeof(complex double) * SQ(bspecQR->n));
832 QPMS_CRASHING_MALLOC(R, sizeof(complex double) * SQ(bspecQR->n));
834 // Not sure what the size should be, but this should be more than enough.
835 const size_t intlimit = 1024;
836 const double epsrel = TMATRIX_AXIALSYM_INTEGRAL_EPSREL;
837 const double epsabs = TMATRIX_AXIALSYM_INTEGRAL_EPSABS;
839 //==== multi-threaded integration begin
840 size_t i2start = 0;
841 pthread_mutex_t i2start_mutex;
842 QPMS_ENSURE_SUCCESS(pthread_mutex_init(&i2start_mutex, NULL));
843 const struct qpms_tmatrix_axialsym_fill_integration_thread_arg
844 arg = {Q, R, bspecQR, epsrel, epsabs, intlimit, shape, &p, &i2start, &i2start_mutex};
846 // FIXME THIS IS NOT PORTABLE (_SC_NPROCESSORS_ONLN is not a standard POSIX thing)
847 long nthreads;
848 if (qpms_axsym_tmatrix_integration_nthreads_override > 0) {
849 nthreads = qpms_axsym_tmatrix_integration_nthreads_override;
850 QPMS_DEBUG(QPMS_DBGMSG_THREADS, "Using overriding value of %ld thread(s).",
851 nthreads);
852 } else {
853 nthreads = get_ncpus();
854 if (nthreads < 1) {
855 QPMS_DEBUG(QPMS_DBGMSG_THREADS, "_SC_NPROCESSORS_ONLN returned %ld, using %ld thread(s) instead.",
856 nthreads, qpms_axsym_tmatrix_integration_nthreads_default);
857 nthreads = qpms_axsym_tmatrix_integration_nthreads_default;
858 } else {
859 QPMS_DEBUG(QPMS_DBGMSG_THREADS, "_SC_NRPOCESSORS_ONLN returned %ld.", nthreads);
862 nthreads = MIN(nthreads, bspec->n); // don't make redundant threads
863 pthread_t thread_ids[nthreads];
864 for(long thi = 0; thi < nthreads; ++thi)
865 QPMS_ENSURE_SUCCESS(pthread_create(thread_ids + thi, NULL,
866 qpms_tmatrix_axialsym_fill_integration_thread,
867 (void *) &arg));
868 for(long thi = 0; thi < nthreads; ++thi) {
869 void *retval;
870 QPMS_ENSURE_SUCCESS(pthread_join(thread_ids[thi], &retval));
872 QPMS_ENSURE_SUCCESS(pthread_mutex_destroy(&i2start_mutex));
873 //===== multi-threaded integration end
876 // Compute T-matrix; maybe it would be better solved with some sparse matrix mechanism,
877 // but fukkit.
878 const size_t n = bspecQR->n;
879 lapack_int *pivot;
880 QPMS_CRASHING_MALLOC(pivot, sizeof(lapack_int) * n);
881 QPMS_ENSURE_SUCCESS(LAPACKE_zgetrf(LAPACK_ROW_MAJOR, n, n, Q, n, pivot));
882 // Solve Q'^T X = R'^T where X will be -T^T
883 // Note that Q'^T, R'^T are already (transposed) in Q, R.
884 QPMS_ENSURE_SUCCESS(LAPACKE_zgetrs(LAPACK_ROW_MAJOR, 'N', n, n /*nrhs*/,
885 Q, n, pivot, R, n));
886 // R now contains -T^T.
887 for(size_t i1 = 0; i1 < bspec->n; ++i1)
888 for(size_t i2 = 0; i2 < bspec->n; ++i2) {
889 if (reindex[i1] == ~(size_t) 0 && reindex[i2] == ~(size_t) 0) QPMS_WTF;
890 const size_t it = i1 * bspec->n + i2;
891 const size_t iQR = reindex[i1] + reindex[i2] * bspecQR->n;
892 t->m[it] = -R[iQR];
895 free(pivot); free(R); free(Q); free(reindex);
896 qpms_vswf_set_spec_free(bspecQR);
897 return QPMS_SUCCESS;
900 qpms_errno_t qpms_tmatrix_axialsym_RQ_transposed_fill(complex double *target,
901 complex double omega, qpms_epsmu_t outside, qpms_epsmu_t inside,
902 qpms_arc_function_t shape, qpms_l_t lMaxQR, qpms_normalisation_t norm,qpms_bessel_t J)
904 QPMS_UNTESTED; // TODO also check whether this is valid for all norms.
905 struct tmatrix_axialsym_integral_param_t p;
907 qpms_vswf_set_spec_t *bspecQR = qpms_vswf_set_spec_from_lMax(lMaxQR, norm);
909 p.k = qpms_wavenumber(omega, outside);
910 p.k_in = qpms_wavenumber(omega, inside);
911 p.z = qpms_waveimpedance(outside);
912 p.z_in = qpms_waveimpedance(inside);
913 p.f = shape;
914 p.bspec = bspecQR;
915 p.btype = J;
916 const gsl_function f = {tmatrix_axialsym_integrand, (void *) &p};
919 // Not sure what the size should be, but this should be more than enough.
920 const size_t intlimit = 1024;
921 const double epsrel = TMATRIX_AXIALSYM_INTEGRAL_EPSREL;
922 const double epsabs = TMATRIX_AXIALSYM_INTEGRAL_EPSABS;
923 gsl_integration_workspace *w = gsl_integration_workspace_alloc(intlimit);
924 for(size_t i1 = 0; i1 < bspecQR->n; ++i1)
925 for(size_t i2 = 0; i2 < bspecQR->n; ++i2) {
926 // NOTE that the Q', R' matrices are !TRANSPOSED! here because of zgetrs
927 const size_t iQR = i1 + i2 * bspecQR->n;
928 qpms_m_t m1, m2;
929 qpms_l_t l1, l2;
930 qpms_vswf_type_t t1, t2;
931 const qpms_uvswfi_t u1 = bspecQR->ilist[i1], u2 = bspecQR->ilist[i2];
932 QPMS_ENSURE_SUCCESS(qpms_uvswfi2tmn(u1, &t1, &m1, &l1));
933 QPMS_ENSURE_SUCCESS(qpms_uvswfi2tmn(u2, &t2, &m2, &l2));
934 if (m1 + m2) {
935 target[iQR] = 0;
936 } else {
937 p.m = m1;
938 p.l = l1; p.l_in = l2;
939 p.t = t1; p.t_in = t2;
941 double result;
942 double abserr; // gsl_integration_qag() needs this; thrown away for now
943 // Re(R' or Q')
944 p.realpart = true;
945 QPMS_ENSURE_SUCCESS(gsl_integration_qag(&f, 0, M_PI, epsabs, epsrel,
946 intlimit, 2, w, &result, &abserr));
947 target[iQR] = result;
949 // Im(R' or Q')
950 p.realpart = false;
951 QPMS_ENSURE_SUCCESS(gsl_integration_qag(&f, 0, M_PI, epsabs, epsrel,
952 intlimit, 2, w, &result, &abserr));
953 target[iQR] += I*result;
956 gsl_integration_workspace_free(w);
958 qpms_vswf_set_spec_free(bspecQR);
959 return QPMS_SUCCESS;
962 qpms_errno_t qpms_tmatrix_generator_axialsym_RQ_transposed_fill(complex double *target,
963 complex double omega, const qpms_tmatrix_generator_axialsym_param_t *param,
964 qpms_normalisation_t norm, qpms_bessel_t J)
966 const qpms_tmatrix_generator_axialsym_param_t *p = param;
967 return qpms_tmatrix_axialsym_RQ_transposed_fill(target, omega,
968 p->outside.function(omega, p->outside.params),
969 p->inside.function(omega, p->inside.params),
970 p->shape,
971 p->lMax_extend, norm, J);
974 qpms_errno_t qpms_tmatrix_generator_axialsym(qpms_tmatrix_t *t, complex double omega, const void *param)
976 const qpms_tmatrix_generator_axialsym_param_t *p = param;
977 return qpms_tmatrix_axialsym_fill(t, omega,
978 p->outside.function(omega, p->outside.params),
979 p->inside.function(omega, p->inside.params),
980 p->shape,
981 p->lMax_extend);
984 qpms_errno_t qpms_tmatrix_generator_constant(qpms_tmatrix_t *t,
985 complex double omega, const void *tmorig) {
986 QPMS_UNTESTED;
987 // TODO more thorough check on bspec!
988 const qpms_tmatrix_t *orig = tmorig;
989 const size_t tocopy = SQ(MIN(t->spec->n, orig->spec->n));
990 memcpy(t->m, orig->m, tocopy*sizeof(complex double));
991 memset(t->m+tocopy, 0, (SQ(t->spec->n)-tocopy)*sizeof(complex double));
992 return QPMS_SUCCESS;
995 void qpms_tmatrix_operation_clear(qpms_tmatrix_operation_t *f) {
996 switch(f->typ) {
997 case QPMS_TMATRIX_OPERATION_NOOP:
998 break;
999 case QPMS_TMATRIX_OPERATION_LRMATRIX:
1000 if(f->op.lrmatrix.owns_m)
1001 free(f->op.lrmatrix.m);
1002 break;
1003 case QPMS_TMATRIX_OPERATION_SCMULZ:
1004 if(f->op.scmulz.owns_m)
1005 free(f->op.scmulz.m);
1006 break;
1007 case QPMS_TMATRIX_OPERATION_IROT3:
1008 break;
1009 case QPMS_TMATRIX_OPERATION_IROT3ARR:
1010 if(f->op.irot3arr.owns_ops)
1011 free(f->op.irot3arr.ops);
1012 break;
1013 case QPMS_TMATRIX_OPERATION_FINITE_GROUP_SYMMETRISE: // Group never owned
1014 break;
1015 case QPMS_TMATRIX_OPERATION_COMPOSE_SUM:
1017 struct qpms_tmatrix_operation_compose_sum * const o =
1018 &(f->op.compose_sum);
1019 if(o->opmem) {
1020 for(size_t i = 0; i < o->n; ++i)
1021 qpms_tmatrix_operation_clear(&(o->opmem[i]));
1022 free(o->opmem);
1024 free(o->ops);
1026 break;
1027 case QPMS_TMATRIX_OPERATION_COMPOSE_CHAIN:
1029 struct qpms_tmatrix_operation_compose_chain * const o =
1030 &(f->op.compose_chain);
1031 if(o->opmem) {
1032 for(size_t i = 0; i < o->n; ++i)
1033 if(o->ops_owned[i]) {
1034 // A bit complicated yet imperfect sanity/bound check,
1035 // but this way we at least get rid of the const discard warning.
1036 ptrdiff_t d = o->ops[i] - o->opmem;
1037 QPMS_ENSURE(d >= 0 && d < o->opmem_size,
1038 "Bound check failed; is there a bug in the"
1039 " construction of compose_chain operation?\n"
1040 "opmem_size = %zu, o->ops[%zu] - o->opmem = %td.",
1041 o->opmem_size, i, d);
1042 qpms_tmatrix_operation_clear(o->opmem + d);
1044 free(o->opmem);
1045 free(o->ops_owned);
1047 free(o->ops);
1049 break;
1050 default:
1051 QPMS_WTF;
1055 void qpms_tmatrix_operation_copy(qpms_tmatrix_operation_t *dest, const qpms_tmatrix_operation_t *src) {
1056 memcpy(dest, src, sizeof(qpms_tmatrix_operation_t));
1057 switch(dest->typ) {
1058 case QPMS_TMATRIX_OPERATION_NOOP:
1059 break;
1060 case QPMS_TMATRIX_OPERATION_LRMATRIX:
1061 QPMS_CRASHING_MALLOCPY(dest->op.lrmatrix.m, src->op.lrmatrix.m,
1062 sizeof(complex double) * src->op.lrmatrix.m_size);
1063 dest->op.lrmatrix.owns_m = true;
1064 break;
1065 case QPMS_TMATRIX_OPERATION_SCMULZ:
1066 QPMS_CRASHING_MALLOCPY(dest->op.scmulz.m, src->op.scmulz.m,
1067 sizeof(complex double) * src->op.scmulz.m_size);
1068 dest->op.scmulz.owns_m = true;
1069 break;
1070 case QPMS_TMATRIX_OPERATION_IROT3:
1071 break;
1072 case QPMS_TMATRIX_OPERATION_IROT3ARR:
1073 QPMS_CRASHING_MALLOCPY(dest->op.irot3arr.ops, src->op.irot3arr.ops,
1074 src->op.irot3arr.n * sizeof(qpms_irot3_t));
1075 dest->op.irot3arr.owns_ops = true;
1076 break;
1077 case QPMS_TMATRIX_OPERATION_FINITE_GROUP_SYMMETRISE: // Group never owned
1078 break;
1079 case QPMS_TMATRIX_OPERATION_COMPOSE_SUM:
1081 struct qpms_tmatrix_operation_compose_sum * const o =
1082 &(dest->op.compose_sum);
1083 QPMS_CRASHING_MALLOC(o->ops, o->n * sizeof(*(o->ops)));
1084 QPMS_CRASHING_MALLOC(o->opmem, o->n * sizeof(*(o->opmem)));
1085 for(size_t i = 0; i < o->n; ++i) {
1086 qpms_tmatrix_operation_copy(o->opmem + i, src->op.compose_sum.ops[i]);
1087 o->ops[i] = o->opmem + i;
1090 break;
1091 case QPMS_TMATRIX_OPERATION_COMPOSE_CHAIN:
1093 struct qpms_tmatrix_operation_compose_chain * const o =
1094 &(dest->op.compose_chain);
1095 QPMS_CRASHING_MALLOC(o->ops, o->n * sizeof(*(o->ops)));
1096 QPMS_CRASHING_MALLOC(o->ops_owned, o->n * sizeof(*(o->ops_owned)));
1097 QPMS_CRASHING_MALLOC(o->opmem, o->n * sizeof(*(o->opmem)));
1098 for(size_t i = 0; i < o->n; ++i) {
1099 qpms_tmatrix_operation_copy(o->opmem + i, src->op.compose_chain.ops[i]);
1100 o->ops[i] = o->opmem + i;
1101 o->ops_owned[i] = true;
1104 break;
1105 default:
1106 QPMS_WTF;
1108 dest->typ = src->typ;
1111 void qpms_tmatrix_operation_compose_chain_init(qpms_tmatrix_operation_t *dest,
1112 size_t nops, size_t opmem_size) {
1113 if (nops == 0) {
1114 QPMS_WARN("Tried to create a composed (chain) operation of zero size;"
1115 "setting to no-op instead.");
1116 *dest = qpms_tmatrix_operation_noop;
1118 if (nops < opmem_size)
1119 QPMS_WARN("Allocating buffer for %zu operations, in a chained operation of"
1120 " only %zu elemens, that does not seem to make sense.", opmem_size, nops);
1121 dest->typ = QPMS_TMATRIX_OPERATION_COMPOSE_CHAIN;
1122 struct qpms_tmatrix_operation_compose_chain * const o =
1123 &(dest->op.compose_chain);
1124 o->n = nops;
1125 QPMS_CRASHING_MALLOC(o->ops, nops * sizeof(*(o->ops)));
1126 if (opmem_size != 0) {
1127 QPMS_CRASHING_CALLOC(o->ops_owned, o->n, sizeof(_Bool));
1128 QPMS_CRASHING_MALLOC(o->opmem, opmem_size * sizeof(*(o->opmem)));
1129 } else {
1130 o->ops_owned = NULL;
1131 o->opmem = NULL;
1133 o->opmem_size = opmem_size;
1136 qpms_tmatrix_t *qpms_tmatrix_mv(qpms_tmatrix_t *dest,
1137 const qpms_tmatrix_t *orig) {
1138 QPMS_ENSURE(qpms_vswf_set_spec_isidentical(dest->spec, orig->spec),
1139 "Basis specifications must be identical!");
1140 memcpy(dest->m, orig->m, SQ(orig->spec->n)*sizeof(complex double));
1141 return dest;
1144 qpms_tmatrix_t *qpms_tmatrix_apply_operation_replace(qpms_tmatrix_t *dest,
1145 const qpms_tmatrix_operation_t *op, const qpms_tmatrix_t *orig) {
1146 //QPMS_ENSURE(qpms_vswf_set_spec_isidentical(dest->spec, orig->spec),
1147 // "Basis specifications must be identical!");
1148 // TODO should I check also for dest->owns_m?
1149 // FIXME this is highly inoptimal; the hierarchy should be such
1150 // that _operation() and operation_inplace() call this, and not the
1151 // other way around
1152 qpms_tmatrix_mv(dest, orig);
1153 return qpms_tmatrix_apply_operation_inplace(op, dest);
1156 qpms_tmatrix_t *qpms_tmatrix_apply_operation(
1157 const qpms_tmatrix_operation_t *f, const qpms_tmatrix_t *orig) {
1158 // Certain operations could be optimized, but the effect would be marginal.
1159 qpms_tmatrix_t *res = qpms_tmatrix_copy(orig);
1160 return qpms_tmatrix_apply_operation_inplace(f, res);
1163 static qpms_tmatrix_t *qtao_compose_sum_inplace(qpms_tmatrix_t *T,
1164 const struct qpms_tmatrix_operation_compose_sum *cs) {
1165 qpms_tmatrix_t *tmp_target = qpms_tmatrix_init(T->spec);
1166 qpms_tmatrix_t *sum = qpms_tmatrix_init(T->spec);
1167 for (size_t i = 0; i < cs->n; ++i) {
1168 memcpy(tmp_target->m, T->m, SQ(T->spec->n) * sizeof(complex double));
1169 QPMS_ENSURE(qpms_tmatrix_apply_operation_inplace(cs->ops[i] , tmp_target),
1170 "Got NULL pointer from qpms_tmatrix_apply_operation_inplace, hupsis!");
1171 for (size_t j = 0; j < SQ(T->spec->n); ++j)
1172 sum->m[j] += tmp_target->m[j];
1174 for(size_t j = 0; j < SQ(T->spec->n); ++j)
1175 T->m[j] = sum->m[j] * cs->factor;
1176 qpms_tmatrix_free(sum);
1177 qpms_tmatrix_free(tmp_target);
1178 return T;
1181 static qpms_tmatrix_t *qtao_compose_chain_inplace(qpms_tmatrix_t *T,
1182 const struct qpms_tmatrix_operation_compose_chain *cc) {
1183 for(size_t i = 0; i < cc->n; ++i)
1184 qpms_tmatrix_apply_operation_inplace(cc->ops[i], T);
1185 return T;
1188 static qpms_tmatrix_t *qtao_scmulz_inplace(qpms_tmatrix_t *T,
1189 const struct qpms_tmatrix_operation_scmulz *s) {
1190 for(size_t i = 0; i < SQ(T->spec->n); ++i)
1191 T->m[i] *= s->m[i];
1192 return T;
1195 qpms_tmatrix_t *qpms_tmatrix_apply_operation_inplace(
1196 const qpms_tmatrix_operation_t *f, qpms_tmatrix_t *T) {
1197 switch(f->typ) {
1198 case QPMS_TMATRIX_OPERATION_NOOP:
1199 return T;
1200 case QPMS_TMATRIX_OPERATION_LRMATRIX:
1201 return qpms_tmatrix_apply_symop_inplace(T, f->op.lrmatrix.m);
1202 case QPMS_TMATRIX_OPERATION_IROT3:
1203 return qpms_tmatrix_symmetrise_irot3arr_inplace(T, 1, &(f->op.irot3));
1204 case QPMS_TMATRIX_OPERATION_FINITE_GROUP_SYMMETRISE:
1205 return qpms_tmatrix_symmetrise_finite_group_inplace(T, f->op.finite_group);
1206 case QPMS_TMATRIX_OPERATION_COMPOSE_SUM:
1207 return qtao_compose_sum_inplace(T, &(f->op.compose_sum));
1208 case QPMS_TMATRIX_OPERATION_COMPOSE_CHAIN:
1209 return qtao_compose_chain_inplace(T, &(f->op.compose_chain));
1210 case QPMS_TMATRIX_OPERATION_SCMULZ:
1211 return qtao_scmulz_inplace(T, &(f->op.scmulz));
1212 default:
1213 QPMS_WTF;