pyshs-optics 4.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
py_shs/HRS.py ADDED
@@ -0,0 +1,521 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ HRS.py -- Py_SHS : Hyper-Rayleigh Scattering of a single molecule (Python / NumPy)
5
+
6
+ Derived from SHS.py for the special case N = 1, position (0,0,0),
7
+ molecular orientation (phi, theta, psi) = (0,0,0).
8
+
9
+ No orientation file is required: the molecular hyperpolarizability beta
10
+ is used directly. The isotropic orientational average is performed over
11
+ the laboratory-frame Euler angles of the molecule (same grid as SHS).
12
+
13
+ Because r = 0 the phase factor is identically 1, so the coherent sum
14
+ reduces to the single-molecule tensor. Numerically this recovers exactly
15
+ the result of SHS.py run with N = 1 and a one-line orientation file
16
+ "0 0 0 0 0 0".
17
+
18
+ Keywords : polarplot_single | polarplot_integrate | angle_scattering
19
+ Usage : python HRS.py keyword beta_file output_file [--legacy]
20
+ stdin : wavelength (nm), Re(n), Im(n), then the angle(s) of the mode
21
+ (the wavelength and the index are read to keep the same input protocol as the
22
+ other modules; they do not change the default `raw` intensities -- they only
23
+ enter the radiative prefactor (|K|^2/4pi)^2 when PYSHS_UNITS=rad is requested)
24
+
25
+ Options / environment
26
+ ---------------------
27
+ --legacy (or PYSHS_LEGACY=1) reproduces two defects of the old C code: the
28
+ normalisation of bv1, bv2, bh1, bh2 (not divided by 8 pi^2) and the
29
+ pairing of the sin*cos cross terms of bh1, bh2. After the isotropic
30
+ orientational average these four coefficients are identically zero (the
31
+ odd terms are the real part of a Hermitian-symmetric contraction that the
32
+ chiral invariants cannot feed), so --legacy only changes rounding noise in
33
+ them and none of av, bv, cv, ah, bh, ch, I2, I4. Kept for completeness;
34
+ see tests/test_core.py (TestLegacyMode) for the exact definitions.
35
+ """
36
+ import os
37
+ import sys
38
+ import math
39
+
40
+ import numpy as np
41
+
42
+ from py_shs import shs_units
43
+ from py_shs import shs_angles
44
+ from py_shs import shs_io
45
+
46
+ # ---- orientation grid (same values as SHS.py / the C code) ----
47
+ PAS = 10
48
+ N_PHI = 2 * PAS # phi in [0, 2 pi)
49
+ N_THETA = PAS # theta in [0, pi]
50
+ N_PSI = 2 * PAS # psi in [0, 2 pi)
51
+ PI = math.pi
52
+
53
+
54
+ # --------------------------------------------------------------------------
55
+ # Small helpers (identical to SHS.py)
56
+ # --------------------------------------------------------------------------
57
+ # number format of the intensities: historical '%f' when the multiplier is 1,
58
+ # scientific notation otherwise (values can be ~1e-9 with PYSHS_UNITS=rad)
59
+ FMT = "%f"
60
+
61
+
62
+ class TokenReader:
63
+ """Reads whitespace separated values from stdin like C scanf."""
64
+
65
+ def __init__(self):
66
+ self.buf = []
67
+
68
+ def next(self):
69
+ while not self.buf:
70
+ line = sys.stdin.readline()
71
+ if line == "":
72
+ raise EOFError("unexpected end of the standard input")
73
+ self.buf = line.split()[::-1]
74
+ return self.buf.pop()
75
+
76
+ def real(self):
77
+ return float(self.next())
78
+
79
+ def integer(self):
80
+ return int(self.next())
81
+
82
+
83
+ def say(text="", end="\n"):
84
+ print(text, end=end, flush=True)
85
+
86
+
87
+ def euler_matrices(phi, theta, psi):
88
+ """ZYZ Euler matrices, identical to Txx..Tzz of the C code.
89
+ phi, theta, psi : arrays of shape (n,) -> T : (n, 3, 3)"""
90
+ sp, cp = np.sin(phi), np.cos(phi)
91
+ st, ct = np.sin(theta), np.cos(theta)
92
+ ss, cs = np.sin(psi), np.cos(psi)
93
+ T = np.empty(np.shape(phi) + (3, 3))
94
+ T[..., 0, 0] = -sp * ss + ct * cp * cs
95
+ T[..., 0, 1] = -sp * cs - ct * cp * ss
96
+ T[..., 0, 2] = st * cp
97
+ T[..., 1, 0] = cp * ss + ct * sp * cs
98
+ T[..., 1, 1] = cp * cs - ct * ss * sp
99
+ T[..., 1, 2] = st * sp
100
+ T[..., 2, 0] = -st * cs
101
+ T[..., 2, 1] = st * ss
102
+ T[..., 2, 2] = ct
103
+ return T
104
+
105
+
106
+ _JK = ((0, 0), (1, 1), (0, 1))
107
+
108
+
109
+ def rotate_needed(T, S):
110
+ """L[n,i,q] = sum_a T[n,i,a] * ( sum_bc T[n,j,b] T[n,k,c] S[n,a,b,c] ),
111
+ q <-> (j,k) in ((0,0),(1,1),(0,1))."""
112
+ L = np.empty(S.shape[:1] + (3, 3), dtype=S.dtype)
113
+ for q, (j, k) in enumerate(_JK):
114
+ w = np.einsum("nb,nc,nabc->na", T[:, j, :], T[:, k, :], S)
115
+ L[:, :, q] = np.einsum("nia,na->ni", T, w)
116
+ return L
117
+
118
+
119
+ def pol_sums(w, xx, yy, xy):
120
+ """Weighted sums (a, b, c, b1, b2) of the intensity coefficients."""
121
+ xyc = np.conj(xy)
122
+ return np.array([
123
+ np.sum(w * (xx * np.conj(xx)).real),
124
+ np.sum(w * (4.0 * (xy * xyc).real + 2.0 * (xx * np.conj(yy)).real)),
125
+ np.sum(w * (yy * np.conj(yy)).real),
126
+ np.sum(w * 4.0 * (xx * xyc).real),
127
+ np.sum(w * 4.0 * (yy * xyc).real),
128
+ ])
129
+
130
+
131
+ # --------------------------------------------------------------------------
132
+ # Inputs
133
+ # --------------------------------------------------------------------------
134
+ def read_beta(name):
135
+ """1 header line + 27 lines "name value"."""
136
+ beta = np.zeros((3, 3, 3))
137
+ try:
138
+ with open(name, "rt") as f:
139
+ f.readline()
140
+ for i in range(3):
141
+ for j in range(3):
142
+ for k in range(3):
143
+ beta[i, j, k] = float(f.readline().split()[1])
144
+ except (OSError, IndexError, ValueError):
145
+ return None
146
+ # The intensity formulas use beta_{i,jk} only for (j,k) = (x,x), (y,y), (x,y) and
147
+ # assume beta_{ijk} = beta_{ikj} (intrinsic permutation symmetry). Symmetrise in the
148
+ # last two indices so that every module treats any input tensor the same way.
149
+ asym = np.abs(beta - beta.transpose(0, 2, 1)).max()
150
+ if asym > 1e-12 * max(np.abs(beta).max(), 1e-300):
151
+ say("WARNING: the tensor is not symmetric in its last two indices "
152
+ "(max |beta_ijk - beta_ikj| = %.3e): it has been symmetrised, "
153
+ "beta_ijk = beta_ikj = (beta_ijk + beta_ikj)/2." % asym)
154
+ beta = 0.5 * (beta + beta.transpose(0, 2, 1))
155
+ return beta
156
+
157
+
158
+ # --------------------------------------------------------------------------
159
+ # Core calculation (HRS = single dipole at origin, identity orientation)
160
+ # --------------------------------------------------------------------------
161
+ def theta_rule(n):
162
+ """Quadrature nodes/weights for int_0^pi f(theta) sin(theta) dtheta.
163
+
164
+ Default: Gauss-Legendre in cos(theta) (exact for polynomials of degree <= 2n-1,
165
+ weights sum to exactly 2). PYSHS_THETA_RULE=midpoint restores the former midpoint
166
+ rule (weights h*sin(theta), sum slightly different from 2).
167
+ """
168
+ if os.environ.get("PYSHS_THETA_RULE", "gauss").strip().lower() == "midpoint":
169
+ h = PI / n
170
+ th = (np.arange(n) + 0.5) * h
171
+ return th, h * np.sin(th)
172
+ x, wl = np.polynomial.legendre.leggauss(n)
173
+ return np.arccos(x), wl
174
+
175
+
176
+
177
+ class Orientations:
178
+ """Orientation table (Euler matrices and weights), computed ONCE."""
179
+
180
+ def __init__(self):
181
+ hx, hz = 2 * PI / N_PHI, 2 * PI / N_PSI
182
+ th_nodes, th_w = theta_rule(N_THETA)
183
+ i, j, k = np.meshgrid(np.arange(N_PHI), np.arange(N_THETA),
184
+ np.arange(N_PSI), indexing="ij")
185
+ i, j, k = i.ravel(), j.ravel(), k.ravel()
186
+ self.T = euler_matrices(0.5 * hx + i * hx, th_nodes[j], 0.5 * hz + k * hz)
187
+ self.w = hx * hz * th_w[j]
188
+ self.n = i.size
189
+
190
+
191
+ class AngleTable:
192
+ """Angle dependent constants, computed ONCE."""
193
+
194
+ def __init__(self, theta_s, petitk, grandk):
195
+ theta_s = np.asarray(theta_s, dtype=np.float64)
196
+ self.cs, self.sn = np.cos(theta_s), np.sin(theta_s)
197
+ # q is not needed for HRS (r = 0 => phase = 1), but cs/sn are used
198
+ # in finalize for the H polarisation combination.
199
+
200
+
201
+ def lab_amplitudes(S, ori):
202
+ """Lab-frame amplitudes A[n, i, q] (q = xx, yy, xy) for every orientation.
203
+ They do not depend on the scattering angle, so they are computed once.
204
+ S is real for HRS (phase = 1; the imaginary half S[:, 27:] is zero)."""
205
+ n = ori.n
206
+ Lr = rotate_needed(ori.T, S[:, :27].reshape(n, 3, 3, 3))
207
+ return Lr + 0j
208
+
209
+
210
+ def combine(A, w, c, s, legacy):
211
+ """Polarisation sums and orientational average for one angle (cos c, sin s).
212
+ Returns the (2, 5) array [V; H] of coefficients (a, b, c, b1, b2), unnormalised."""
213
+ V = pol_sums(w, A[:, 0, 0], A[:, 0, 1], A[:, 0, 2])
214
+ Hxx = c * A[:, 1, 0] - s * A[:, 2, 0]
215
+ Hyy = c * A[:, 1, 1] - s * A[:, 2, 1]
216
+ Hxy = c * A[:, 1, 2] - s * A[:, 2, 2]
217
+ H = pol_sums(w, Hxx, Hyy, Hxy)
218
+ if legacy:
219
+ gyx, gzx, gyy, gzy = A[:, 1, 0], A[:, 2, 0], A[:, 1, 1], A[:, 2, 1]
220
+ gyxy, gzxy = A[:, 1, 2], A[:, 2, 2]
221
+
222
+ def r4(u, v):
223
+ return 4.0 * (u * np.conj(v)).real
224
+
225
+ H[3] += np.sum(w * (-r4(Hxx, Hxy)))
226
+ H[3] += np.sum(w * (c * c * r4(gyx, gyxy) + s * s * r4(gzx, gzxy)
227
+ - s * c * (r4(gzx, gzxy) + r4(gyx, gyxy))))
228
+ H[4] += np.sum(w * (-r4(Hyy, Hxy)))
229
+ H[4] += np.sum(w * (c * c * r4(gyy, gyxy) + s * s * r4(gzy, gzxy)
230
+ - s * c * (r4(gzy, gzxy) + r4(gyy, gyxy))))
231
+ return np.vstack((V, H))
232
+
233
+
234
+ def finalize(S, ori, ang, ag, legacy):
235
+ """Rotation to the lab frame and orientational average for angle ag
236
+ (kept as one call for the tests; compute() rotates only once)."""
237
+ return combine(lab_amplitudes(S, ori), ori.w, ang.cs[ag], ang.sn[ag], legacy)
238
+
239
+
240
+ def compute(beta, petitk, grandk, theta_s, legacy):
241
+ """HRS calculation for a single molecule.
242
+ beta is already the molecular tensor (meso = mol for identity orientation).
243
+ Returns acc[nang, 2, 5]."""
244
+ nang = len(theta_s)
245
+ ori = Orientations()
246
+ ang = AngleTable(theta_s, petitk, grandk)
247
+ acc = np.zeros((nang, 2, 5))
248
+
249
+ # Single dipole at origin with identity orientation: the phase factor is 1, so the
250
+ # lab-frame amplitudes are the same for every angle; only the H combination
251
+ # (cos Theta * Y - sin Theta * Z) depends on the angle.
252
+ S = np.zeros((ori.n, 54))
253
+ S[:, :27] = beta.reshape(27) # same tensor for all orientations
254
+ A = lab_amplitudes(S, ori)
255
+ next_print = 0
256
+ for ag in range(nang):
257
+ acc[ag] = combine(A, ori.w, ang.cs[ag], ang.sn[ag], legacy)
258
+ pct = int(100.0 * (ag + 1) / nang)
259
+ if pct >= next_print:
260
+ say("calculation in progress : %f pourcent " % pct)
261
+ next_print = pct + 5
262
+ say("calculation in progress : %f pourcent " % 100.0)
263
+ return acc
264
+
265
+
266
+ # --------------------------------------------------------------------------
267
+ # Outputs (identical format to SHS.py)
268
+ # --------------------------------------------------------------------------
269
+ def write_header(f, b):
270
+ f.write("********************************************************\n")
271
+ f.write("**** Py_SHS: An open source software ****\n")
272
+ f.write("**** about Second Harmonic Scattering ****\n")
273
+ f.write("**** Dr Pierre-Marie GASSIN and Dr Gaelle GASSIN ****\n")
274
+ f.write("**** ICGM - ENSCM ****\n")
275
+ f.write("**** june 2021 ****\n")
276
+ f.write("********************************************************\n")
277
+ f.write(" \n")
278
+ f.write(" Don't forget to cite this work: \n")
279
+ f.write(" J. Chem. Inf. Model. 2020, 60, 12, pp 5912–5917 \n")
280
+ f.write(" \n")
281
+ f.write("********************************************************\n")
282
+ f.write("********************************************************\n")
283
+ f.write("****** SIMULATION RESULTS *******\n")
284
+ f.write("********************************************************\n")
285
+ f.write("****** SURFACE HYPERPOLARISABILITY TENSOR ******\n")
286
+ f.write("\n")
287
+ f.write(" xxx=%f xxy=%f xxz= %f \n" % (b[0, 0, 0], b[0, 0, 1], b[0, 0, 2]))
288
+ f.write(" xyx=%f xyy=%f xyz= %f \n" % (b[0, 1, 0], b[0, 1, 1], b[0, 1, 2]))
289
+ f.write(" xzx=%f xzy=%f xzz= %f \n" % (b[0, 2, 0], b[0, 2, 1], b[0, 2, 2]))
290
+ f.write("\n")
291
+ f.write(" yxx=%f yxy=%f yxz= %f \n" % (b[1, 0, 0], b[1, 0, 1], b[1, 0, 2]))
292
+ f.write(" yyx=%f yyy=%f yyz= %f \n" % (b[1, 1, 0], b[1, 1, 1], b[1, 1, 2]))
293
+ f.write(" yzx=%f yzy=%f yzz= %f \n" % (b[1, 2, 0], b[1, 2, 1], b[1, 2, 2]))
294
+ f.write("\n")
295
+ f.write(" zxx=%f zxy=%f zxz= %f \n" % (b[2, 0, 0], b[2, 0, 1], b[2, 0, 2]))
296
+ f.write(" zyx=%f zyy=%f zyz= %f \n" % (b[2, 1, 0], b[2, 1, 1], b[2, 1, 2]))
297
+ f.write(" zzx=%f zzy=%f zzz= %f \n" % (b[2, 2, 0], b[2, 2, 1], b[2, 2, 2]))
298
+ f.write("\n")
299
+ f.write("*******************************************************\n")
300
+
301
+
302
+ def report_polar(fout, deg, ndip, V, H):
303
+ """Final report of polarplot_single / polarplot_integrate."""
304
+ av, bv, cv, bv1, bv2 = V[0], V[1], V[2], V[3], V[4]
305
+ ah, bh, ch, bh1, bh2 = H[0], H[1], H[2], H[3], H[4]
306
+ with np.errstate(all="ignore"):
307
+ I4v = (av - bv + cv) / (3 * av + bv + 3 * cv)
308
+ I2v = 4.0 * (av - cv) / (3 * av + bv + 3 * cv)
309
+ I4h = (ah - bh + ch) / (3 * ah + bh + 3 * ch)
310
+ I2h = 4.0 * (ah - ch) / (3 * ah + bh + 3 * ch)
311
+
312
+ say("")
313
+ say("**********calculation complete ************")
314
+ for name, val in (("av", av), ("bv", bv), ("bv1", bv1), ("bv2", bv2), ("cv", cv),
315
+ ("ah", ah), ("bh", bh), ("bh1", bh1), ("bh2", bh2), ("ch", ch)):
316
+ say(("%s = " + FMT) % (name, val))
317
+ say("********************************************")
318
+ say("I2v = %f " % I2v)
319
+ say("I4v = %f " % I4v)
320
+ say("I2h = %f " % I2h)
321
+ say("I4h = %f " % I4h)
322
+
323
+ fout.write(" \n")
324
+ fout.write(" POLAR PLOT - CONFIGURATION AT %f ° \n" % deg)
325
+ fout.write(" \n")
326
+ fout.write("nombre de dipole = %d \n" % ndip)
327
+ fout.write("*************************\n")
328
+ fout.write("I2v=%f I4v=%f \n" % (I2v, I4v))
329
+ fout.write("I2h=%f I4h=%f \n" % (I2h, I4h))
330
+ fout.write("*************************\n")
331
+ fout.write("av=%f bv=%f bv1=%f bv2=%f cv= %f \n".replace("%f", FMT) % (av, bv, bv1, bv2, cv))
332
+ fout.write("ah=%f bh=%f bh1=%f bh2=%f ch= %f \n".replace("%f", FMT) % (ah, bh, bh1, bh2, ch))
333
+
334
+ try:
335
+ fp = open(shs_io.path("out_plot"), "w", encoding="utf-8")
336
+ except OSError:
337
+ say("impossible d'ouvrir le fichier pour ecrire gnuplot!", end="")
338
+ return
339
+ with fp:
340
+ fp.write("gamma IV(gamma) IH(gamma)\n")
341
+ g = 2.0 * PI * np.arange(100) / 100
342
+ c, s = np.cos(g), np.sin(g)
343
+ Iv = av * c**4 + bv * c**2 * s**2 + cv * s**4 + bv1 * c**3 * s + bv2 * c * s**3
344
+ Ih = ah * c**4 + bh * c**2 * s**2 + ch * s**4 + bh1 * c**3 * s + bh2 * c * s**3
345
+ for gg, a, b in zip(g, Iv, Ih):
346
+ fp.write(("%f " + FMT + " " + FMT + "\n") % (gg, a, b))
347
+
348
+
349
+ # --------------------------------------------------------------------------
350
+ def main(argv):
351
+ legacy = os.environ.get("PYSHS_LEGACY", "") not in ("", "0")
352
+ if "--legacy" in argv:
353
+ legacy = True
354
+ argv = [a for a in argv if a != "--legacy"]
355
+ if legacy:
356
+ print("NOTE: legacy mode (old C quirks) only affects bv1, bv2, bh1, bh2, which are "
357
+ "zero after the isotropic average; av, bv, cv, ah, bh, ch are unchanged.")
358
+ try:
359
+ sys.stdout.reconfigure(encoding="utf-8")
360
+ except AttributeError:
361
+ pass
362
+
363
+ say("********************************************************")
364
+ say("**** Py_SHS: An open source software ****")
365
+ say("**** about Second Harmonic Scattering ****")
366
+ say("**** Dr Pierre-Marie GASSIN and Dr Gaelle GASSIN ****")
367
+ say("**** ICGM - ENSCM ****")
368
+ say("**** june 2021 ****")
369
+ say("********************************************************")
370
+ say(" ")
371
+ say(" Don't forget to cite this work: ")
372
+ say(" J. Chem. Inf. Model. 2020, 60, 12, pp 5912–5917 ")
373
+ say(" ")
374
+ say("********************************************************")
375
+ say("********************************************************")
376
+ say("******** Enter the input parameters **********")
377
+ say("********************************************************")
378
+
379
+ # HRS: 3 arguments (keyword, beta_file, output_file) — no orientation file
380
+ if len(argv) != 4:
381
+ say("Computation failed: ")
382
+ say("3 arguments expected : the keyword of the calculation, "
383
+ "the beta inputfile and the outputfile. ")
384
+ say("For more details see the documentation ")
385
+ return 1
386
+
387
+ keyword = argv[1]
388
+ mode_single = keyword == "polarplot_single"
389
+ mode_integ = keyword == "polarplot_integrate"
390
+ mode_angle = keyword == "angle_scattering"
391
+ if not (mode_single or mode_integ or mode_angle):
392
+ say("--------------------- ----------------------------------------------------")
393
+ say(" ERROR in the name of the keyword ")
394
+ say(" USE ONLY polarplot_single OR polarplot_integrate OR angle_scattering !! ")
395
+ say(" ! no calculation has been done ! Retry with the correct keyword ")
396
+ say("--------------------------------------------------------------------------")
397
+ return 1
398
+
399
+ rd = TokenReader()
400
+ try:
401
+ # HRS: no "Number of dipoles" — always N = 1
402
+ ndip = 1
403
+ say("HRS mode: single dipole at origin (N = 1)")
404
+ say("Enter the wavelength in nm? ", end="")
405
+ lam = rd.real()
406
+ say("The wavelength is: %f " % lam)
407
+ say("Enter the real part of the refractive index? ", end="")
408
+ nr = rd.real()
409
+ say("Enter the imaginary part of the refractive index? ", end="")
410
+ ni = rd.real()
411
+ except (EOFError, ValueError):
412
+ return 1
413
+
414
+ n = complex(nr, ni)
415
+ say("The refractive index is: %f + I*%f " % (n.real, n.imag))
416
+ say("The input beta file is %s and the output file is %s "
417
+ % (argv[2], argv[3]))
418
+ grandk = 4.0 * PI * n / lam
419
+ petitk = 2.0 * PI * n / lam
420
+
421
+ beta = read_beta(argv[2])
422
+ if beta is None:
423
+ say("Problem to read the input beta file \n!")
424
+ return 1
425
+
426
+ shs_io.set_output(argv[3])
427
+
428
+ try:
429
+ fout = open(argv[3], "w", encoding="utf-8")
430
+ except OSError:
431
+ say("problem to open the output file to write results!")
432
+ return 1
433
+
434
+ unit = shs_units.settings(grandk, "raw")
435
+ for _l in unit.lines():
436
+ say(_l)
437
+ write_header(fout, beta)
438
+ shs_units.write_header_lines(fout, unit)
439
+
440
+ # ---------- angles ----------
441
+ t1 = t2 = 0.0
442
+ nang = 1
443
+ try:
444
+ if mode_single:
445
+ say("Enter the scattered angle in degree (0°=transmission) ? ", end="")
446
+ t1 = rd.real()
447
+ say(" ****************************** ")
448
+ say("********** Begin calculation polar plot at %f ° ********" % t1)
449
+ elif mode_integ:
450
+ say("Enter the beginning scattered angle in degree (0°=transmission) ? ", end="")
451
+ t1 = rd.real()
452
+ say(" ****************************** ")
453
+ say("Enter the final scattered angle in degree (0°=transmission) ? ", end="")
454
+ t2 = rd.real()
455
+ say(" ****************************** ")
456
+ say(" Number of integrale points ? ", end="")
457
+ nang = rd.integer()
458
+ say(" ****************************** ")
459
+ if nang < 1:
460
+ return 1
461
+ else:
462
+ fout.write(" \n")
463
+ fout.write("* HRS angular distribution \n")
464
+ fout.write(" \n")
465
+ fout.write("nombre de dipole = %d \n" % ndip)
466
+ fout.write(" ****************OUTPUT****************************************\n")
467
+ fout.write(" grandtheta IPSS-IH(0°) IPPP-IH(90°) ISSS-IV(0°) ISPP-IV(90°) \n")
468
+ say("Enter the number of points between 0 and 180° for the angular HRS analysis ?", end="")
469
+ pas2 = rd.integer()
470
+ if pas2 < 1:
471
+ return 1
472
+ nang = shs_angles.count(pas2)
473
+ say("********** Begin HRS Angular Distribution calculation**************")
474
+ except (EOFError, ValueError):
475
+ return 1
476
+
477
+ ia = np.arange(nang)
478
+ if mode_angle:
479
+ th = shs_angles.scattering_angles(pas2)
480
+ elif mode_integ:
481
+ th = (np.full(nang, t1) if nang == 1
482
+ else t1 + ia * (t2 - t1) / (nang - 1)) * 2.0 * PI / 360.0
483
+ else:
484
+ th = np.full(1, t1 * 2.0 * PI / 360.0)
485
+
486
+ acc = compute(beta, petitk, grandk, th, legacy)
487
+
488
+ norm = unit.multiplier / (8.0 * PI * PI) # units: see shs_units.py
489
+ global FMT
490
+ if unit.multiplier != 1.0:
491
+ FMT = "%.6e"
492
+ if mode_angle:
493
+ with open(shs_io.path("out_plot"), "w", encoding="utf-8") as fgr:
494
+ fgr.write(" grandtheta IH(0°) IH(90°) IV(0°) IV(90°) \n")
495
+ for k in range(nang):
496
+ deg = th[k] * 360.0 / (2.0 * PI)
497
+ IH_0, IH_90 = acc[k, 1, 0] * norm, acc[k, 1, 2] * norm
498
+ IV_0, IV_90 = acc[k, 0, 0] * norm, acc[k, 0, 2] * norm
499
+ say("angle value: %f " % deg)
500
+ say("IH_0=%f IH_90=%f IV_0=%f IV_90=%f ".replace("%f", FMT)
501
+ % (IH_0, IH_90, IV_0, IV_90))
502
+ row = (" %f %f %f %f %f \n".replace("%f", FMT).replace(FMT, "%f", 1)) % (deg, IH_0, IH_90, IV_0, IV_90)
503
+ fout.write(row)
504
+ fgr.write(row)
505
+ say("calculation finished ")
506
+ else:
507
+ tot = acc.sum(axis=0) # (2, 5)
508
+ f = norm / nang
509
+ if legacy:
510
+ tot[:, :3] *= f
511
+ tot[:, 3:] *= unit.multiplier / nang
512
+ else:
513
+ tot *= f
514
+ report_polar(fout, th[nang - 1] * 360.0 / (2.0 * PI), ndip,
515
+ tot[0], tot[1])
516
+ fout.close()
517
+ return 0
518
+
519
+
520
+ if __name__ == "__main__":
521
+ sys.exit(main(sys.argv))