syned 1.0.47__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.
Files changed (54) hide show
  1. syned/__init__.py +0 -0
  2. syned/__test/__init__.py +46 -0
  3. syned/__test/test.py +28 -0
  4. syned/beamline/__init__.py +1 -0
  5. syned/beamline/beamline.py +155 -0
  6. syned/beamline/beamline_element.py +76 -0
  7. syned/beamline/element_coordinates.py +199 -0
  8. syned/beamline/optical_element.py +47 -0
  9. syned/beamline/optical_element_with_surface_shape.py +126 -0
  10. syned/beamline/optical_elements/__init__.py +1 -0
  11. syned/beamline/optical_elements/absorbers/__init__.py +0 -0
  12. syned/beamline/optical_elements/absorbers/absorber.py +21 -0
  13. syned/beamline/optical_elements/absorbers/beam_stopper.py +64 -0
  14. syned/beamline/optical_elements/absorbers/filter.py +61 -0
  15. syned/beamline/optical_elements/absorbers/holed_filter.py +67 -0
  16. syned/beamline/optical_elements/absorbers/slit.py +81 -0
  17. syned/beamline/optical_elements/crystals/__init__.py +1 -0
  18. syned/beamline/optical_elements/crystals/crystal.py +70 -0
  19. syned/beamline/optical_elements/gratings/__init__.py +1 -0
  20. syned/beamline/optical_elements/gratings/grating.py +279 -0
  21. syned/beamline/optical_elements/ideal_elements/__init__.py +1 -0
  22. syned/beamline/optical_elements/ideal_elements/ideal_element.py +16 -0
  23. syned/beamline/optical_elements/ideal_elements/ideal_fzp.py +183 -0
  24. syned/beamline/optical_elements/ideal_elements/ideal_lens.py +54 -0
  25. syned/beamline/optical_elements/ideal_elements/screen.py +16 -0
  26. syned/beamline/optical_elements/mirrors/__init__.py +1 -0
  27. syned/beamline/optical_elements/mirrors/mirror.py +39 -0
  28. syned/beamline/optical_elements/multilayers/__init__.py +46 -0
  29. syned/beamline/optical_elements/multilayers/multilayer.py +45 -0
  30. syned/beamline/optical_elements/refractors/__init__.py +1 -0
  31. syned/beamline/optical_elements/refractors/crl.py +79 -0
  32. syned/beamline/optical_elements/refractors/interface.py +61 -0
  33. syned/beamline/optical_elements/refractors/lens.py +105 -0
  34. syned/beamline/shape.py +2803 -0
  35. syned/storage_ring/__init__.py +1 -0
  36. syned/storage_ring/electron_beam.py +804 -0
  37. syned/storage_ring/empty_light_source.py +40 -0
  38. syned/storage_ring/light_source.py +90 -0
  39. syned/storage_ring/magnetic_structure.py +8 -0
  40. syned/storage_ring/magnetic_structures/__init__.py +1 -0
  41. syned/storage_ring/magnetic_structures/bending_magnet.py +329 -0
  42. syned/storage_ring/magnetic_structures/insertion_device.py +169 -0
  43. syned/storage_ring/magnetic_structures/undulator.py +413 -0
  44. syned/storage_ring/magnetic_structures/wiggler.py +27 -0
  45. syned/syned_object.py +264 -0
  46. syned/util/__init__.py +22 -0
  47. syned/util/json_tools.py +198 -0
  48. syned/widget/__init__.py +0 -0
  49. syned/widget/widget_decorator.py +67 -0
  50. syned-1.0.47.dist-info/METADATA +88 -0
  51. syned-1.0.47.dist-info/RECORD +54 -0
  52. syned-1.0.47.dist-info/WHEEL +5 -0
  53. syned-1.0.47.dist-info/licenses/LICENSE +20 -0
  54. syned-1.0.47.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2803 @@
1
+ """
2
+ Classes with geometrical shapes.
3
+
4
+ * Shape: Base class for all possible geometric shapes.
5
+ * SurfaceShape: Base class to caracterize the shape (sphere, flat, etc.) of the optical element surface
6
+ * BoundaryShape: Base class to characterize the optical element dimensions (rectangle, etc.).
7
+
8
+ Additional classes help to define flags:
9
+ * Convexity: NONE = -1, UPWARD = 0, DOWNWARD = 1
10
+ * Direction: TANGENTIAL = 0, SAGITTAL = 1
11
+ * Side: SOURCE = 0, IMAGE = 1
12
+ """
13
+ import numpy
14
+ from syned.syned_object import SynedObject
15
+ from collections import OrderedDict
16
+
17
+ class Convexity:
18
+ NONE = -1
19
+ UPWARD = 0
20
+ DOWNWARD = 1
21
+
22
+ class Direction:
23
+ TANGENTIAL = 0
24
+ SAGITTAL = 1
25
+
26
+ class Side:
27
+ SOURCE = 0
28
+ IMAGE = 1
29
+
30
+ class Shape(SynedObject):
31
+ """
32
+ Constructor.
33
+
34
+ """
35
+ def __init__(self):
36
+ SynedObject.__init__(self)
37
+
38
+ class SurfaceShape(Shape):
39
+ """
40
+ Constructor.
41
+
42
+ Parameters
43
+ ----------
44
+ convexity : int (as defined by Convexity), optional
45
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
46
+
47
+ """
48
+ def __init__(self, convexity = Convexity.UPWARD):
49
+ Shape.__init__(self)
50
+
51
+ self._convexity = convexity
52
+
53
+ def get_convexity(self):
54
+ """
55
+ Gets the convexity flag.
56
+
57
+ Returns
58
+ -------
59
+ int
60
+ NONE = -1, UPWARD = 0, DOWNWARD = 1
61
+
62
+ """
63
+ return self._convexity
64
+
65
+ class BoundaryShape(Shape):
66
+ """
67
+ Constructor.
68
+ """
69
+ def __init__(self):
70
+ Shape.__init__(self)
71
+
72
+ def get_boundaries(self):
73
+ """
74
+ Returns the boundary shape. It must be defined in the children classes.
75
+
76
+ Raises
77
+ ------
78
+ NotImplementedError
79
+
80
+
81
+ """
82
+ raise NotImplementedError()
83
+
84
+ #############################
85
+ # Subclasses for SurfaceShape
86
+ #############################
87
+
88
+ class Cylinder(SynedObject):
89
+ """
90
+ Defines that a surface shape is cylindrical in one direction.
91
+
92
+ Usage: must be used with double inheritance in other classes (e.g. ParabolicCylinder).
93
+ It should not be used standalone.
94
+
95
+ Parameters
96
+ ----------
97
+ cylinder_direction : int, optional
98
+ TANGENTIAL = 0, SAGITTAL = 1.
99
+
100
+ """
101
+ def __init__(self, cylinder_direction=Direction.TANGENTIAL):
102
+ self._cylinder_direction = cylinder_direction
103
+ # support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
104
+ self._add_support_text([
105
+ ("cylinder_direction" , "(0=tangential, 1=sagittal)", " " ),
106
+ ] )
107
+
108
+ def get_cylinder_direction(self):
109
+ """
110
+ Returns the cylinder direction.
111
+
112
+ Returns
113
+ -------
114
+ int
115
+ TANGENTIAL = 0, SAGITTAL = 1.
116
+
117
+ """
118
+ return self._cylinder_direction
119
+
120
+ class Conic(SurfaceShape):
121
+ """
122
+ Defines a conic surface shape expresses via the 10 conic coeffcients.
123
+
124
+ Parameters
125
+ ----------
126
+ conic_coefficients : list, optional
127
+ A list with the 10 coefficients.
128
+
129
+ """
130
+ def __init__(self,
131
+ conic_coefficients=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]):
132
+ SurfaceShape.__init__(self, convexity=Convexity.NONE)
133
+
134
+ # stored as numpy array, not as list, to avoid in i/o to interpret the items as syned objects.
135
+ self._conic_coefficients = numpy.array(conic_coefficients)
136
+
137
+
138
+ self._set_support_text([
139
+ ("conic_coefficients" , "Conic coeffs. ", " " ),
140
+ ] )
141
+
142
+ def get_conic_coefficients(self):
143
+ """
144
+ Returns the coefficients.
145
+
146
+ Returns
147
+ -------
148
+ list
149
+ A list with the 10 coefficients.
150
+
151
+ """
152
+ return list(self._conic_coefficients)
153
+
154
+ class Plane(SurfaceShape):
155
+ """
156
+ Defines a plane surface shape.
157
+ """
158
+ def __init__(self):
159
+ SurfaceShape.__init__(self, convexity=Convexity.NONE)
160
+
161
+ class Sphere(SurfaceShape):
162
+ """
163
+ Defines an spherical surface.
164
+
165
+ Parameters
166
+ ----------
167
+ radius : float
168
+ The sphere radius.
169
+ convexity : int (as defined by Convexity), optional
170
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
171
+
172
+ """
173
+ def __init__(self, radius=1.0, convexity=Convexity.UPWARD):
174
+ SurfaceShape.__init__(self, convexity=convexity)
175
+ self._radius = radius
176
+
177
+ # support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
178
+ self._set_support_text([
179
+ ("radius" , "Sphere radius ", "m" ),
180
+ ("convexity" , "(0=upwards, 1=downwards)", " "),
181
+ ] )
182
+
183
+
184
+ @classmethod
185
+ def create_sphere_from_radius(cls, radius=0.0, convexity=Convexity.UPWARD):
186
+ """
187
+ Defines an spherical surface.
188
+
189
+ Parameters
190
+ ----------
191
+ radius : float
192
+ The sphere radius.
193
+ convexity : int (as defined by Convexity), optional
194
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
195
+
196
+ Returns
197
+ -------
198
+ instance of Sphere
199
+
200
+ """
201
+ return Sphere(radius, convexity)
202
+
203
+ @classmethod
204
+ def create_sphere_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003, convexity=Convexity.UPWARD):
205
+ """
206
+ Defines an spherical surface.
207
+
208
+ Parameters
209
+ ----------
210
+ p : float, optional
211
+ distance source-optical element.
212
+ q : float, optional
213
+ distance optical element to focus.
214
+ grazing_angle : float, optional
215
+ grazing angle in rad.
216
+ convexity : int (as defined by Convexity), optional
217
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
218
+
219
+ Returns
220
+ -------
221
+ instance of Sphere
222
+
223
+ """
224
+ sphere = Sphere(convexity=convexity)
225
+ sphere.initialize_from_p_q(p, q, grazing_angle)
226
+
227
+ return sphere
228
+
229
+ def initialize_from_p_q(self, p=2.0, q=1.0, grazing_angle=0.003):
230
+ """
231
+ Defines an spherical surface.
232
+
233
+ Parameters
234
+ ----------
235
+ p : float, optional
236
+ distance source-optical element.
237
+ q : float, optional
238
+ distance optical element to focus.
239
+ grazing_angle : float, optional
240
+ grazing angle in rad.
241
+
242
+ """
243
+ self._radius = Sphere.get_radius_from_p_q(p, q, grazing_angle)
244
+
245
+ @classmethod
246
+ def get_radius_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003):
247
+ """
248
+ Calculates the radius of the sphere from factory parameters (1/p+1/q=2/(R sin theta_grazing)))
249
+
250
+ Parameters
251
+ ----------
252
+ p : float, optional
253
+ distance source-optical element.
254
+ q : float, optional
255
+ distance optical element to focus.
256
+ grazing_angle : float, optional
257
+ grazing angle in rad.
258
+
259
+ Returns
260
+ -------
261
+ float
262
+ the calculated radius.
263
+
264
+ """
265
+ # 1/p + 1/q = 2/(R cos(pi/2 - gr.a.))
266
+ return (2*p*q/(p+q))/numpy.sin(grazing_angle)
267
+
268
+ def get_radius(self):
269
+ """
270
+ Returns the radius of the sphere.
271
+
272
+ Returns
273
+ -------
274
+ float
275
+ The radius of the sphere.
276
+
277
+ """
278
+ return self._radius
279
+
280
+
281
+ class SphericalCylinder(Sphere, Cylinder):
282
+ """
283
+ Constructor.
284
+
285
+ Parameters
286
+ ----------
287
+ radius : float
288
+ the radius of the circular section.
289
+ convexity : int (as defined by Convexity), optional
290
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
291
+ cylinder_direction : int (as defined by Direction), optional
292
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
293
+
294
+ """
295
+ def __init__(self,
296
+ radius=1.0,
297
+ convexity=Convexity.UPWARD,
298
+ cylinder_direction=Direction.TANGENTIAL):
299
+ Sphere.__init__(self, radius, convexity)
300
+ Cylinder.__init__(self, cylinder_direction)
301
+
302
+ @classmethod
303
+ def create_spherical_cylinder_from_radius(cls, radius=0.0, convexity=Convexity.UPWARD, cylinder_direction=Direction.TANGENTIAL):
304
+ """
305
+ Creates a spherical cylinder.
306
+
307
+ Parameters
308
+ ----------
309
+ radius : float
310
+ the radius of the circular section.
311
+ convexity : int (as defined by Convexity), optional
312
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
313
+ cylinder_direction : int (as defined by Direction), optional
314
+ TANGENTIAL = 0, SAGITTAL = 1.
315
+
316
+ Returns
317
+ -------
318
+ instance of SphericalCylinder
319
+
320
+ """
321
+ return SphericalCylinder(radius, convexity, cylinder_direction)
322
+
323
+ @classmethod
324
+ def create_spherical_cylinder_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003,
325
+ convexity=Convexity.UPWARD, cylinder_direction=Direction.TANGENTIAL):
326
+ """
327
+
328
+ Parameters
329
+ ----------
330
+ p : float, optional
331
+ distance source-optical element.
332
+ q : float, optional
333
+ distance optical element to focus.
334
+ grazing_angle : float, optional
335
+ grazing angle in rad.
336
+ convexity : int (as defined by Convexity), optional
337
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
338
+ cylinder_direction : int (as defined by Direction), optional
339
+ TANGENTIAL = 0, SAGITTAL = 1.
340
+
341
+ Returns
342
+ -------
343
+ instance of SphericalCylinder
344
+
345
+ """
346
+ spherical_cylinder = SphericalCylinder(convexity=convexity, cylinder_direction=cylinder_direction)
347
+ spherical_cylinder.initialize_from_p_q(p, q, grazing_angle)
348
+
349
+ return spherical_cylinder
350
+
351
+ def initialize_from_p_q(self, p=2.0, q=1.0, grazing_angle=0.003):
352
+ """
353
+ Calculates and sets the radius of curvature in the corresponding direction (tangential or sagittal.
354
+ Parameters
355
+ ----------
356
+ p : float, optional
357
+ distance source-optical element.
358
+ q : float, optional
359
+ distance optical element to focus.
360
+ grazing_angle : float, optional
361
+ grazing angle in rad.
362
+
363
+ """
364
+ if self._cylinder_direction == Direction.TANGENTIAL:
365
+ self._radius = Sphere.get_radius_from_p_q(p, q, grazing_angle)
366
+ elif self._cylinder_direction == Direction.SAGITTAL:
367
+ self._radius = SphericalCylinder.get_radius_from_p_q_sagittal(p, q, grazing_angle)
368
+
369
+ @classmethod
370
+ def get_radius_from_p_q_sagittal(cls, p=2.0, q=1.0, grazing_angle=0.003):
371
+ """
372
+ Calculates the sagittal radius from the factory parameters (1/p + 1/q = 2 sin(grazing_angle)/Rs).
373
+
374
+ Parameters
375
+ ----------
376
+ p : float, optional
377
+ distance source-optical element.
378
+ q : float, optional
379
+ distance optical element to focus.
380
+ grazing_angle : float, optional
381
+ grazing angle in rad.
382
+
383
+ Returns
384
+ -------
385
+ float
386
+ The calculated radius.
387
+
388
+ """
389
+ # 1/p + 1/q = 2 cos(pi/2 - gr.a.)/r
390
+ return (2*p*q/(p+q))*numpy.sin(grazing_angle)
391
+
392
+ class Ellipsoid(SurfaceShape):
393
+ """
394
+ Constructor.
395
+
396
+ Ellipsoid: Revolution ellipsoid (rotation around major axis).
397
+ It is defined with three parameters: axes of the ellipse and an additional parameter
398
+ defining the position of the origin of the mirror. This additional parameter can be "p", "x0", "y0"
399
+ or the angle beta from the ellipsoid center (tan(beta)=y0/x0). For simplicity, we store "p" in syned.
400
+
401
+ Parameters
402
+ ----------
403
+ min_axis : float, optional
404
+ the ellipse minor axis.
405
+ maj_axis : float, optional
406
+ the ellipse majot axis.
407
+ p_focus : float, optional
408
+ the distance from the first focus (source position) to the mirror pole.
409
+ convexity : int (as defined by Convexity), optional
410
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
411
+
412
+ References
413
+ ----------
414
+ Some equations can be found here: https://github.com/srio/shadow3-docs/blob/master/doc/conics.pdf
415
+
416
+ """
417
+ def __init__(self, min_axis=0.0, maj_axis=0.0, p_focus=0.0, convexity=Convexity.UPWARD):
418
+ SurfaceShape.__init__(self, convexity)
419
+
420
+ self._min_axis = min_axis
421
+ self._maj_axis = maj_axis
422
+ self._p_focus = p_focus
423
+ # support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
424
+ self._set_support_text([
425
+ ("min_axis" , "Ellipse major axis ", "m" ),
426
+ ("maj_axis" , "Ellipse minor axis ", "m"),
427
+ ("p_focus" , "Ellipse p (source-focus to pole) ", "m"),
428
+ ("convexity" , "(0=upwards, 1=downwards)", " "),
429
+ ] )
430
+
431
+
432
+ @classmethod
433
+ def create_ellipsoid_from_axes(cls, min_axis=0.0, maj_axis=0.0, p_focus=0.0, convexity=Convexity.UPWARD):
434
+ """
435
+ Creates an ellipsoid.
436
+
437
+ Parameters
438
+ ----------
439
+ min_axis : float, optional
440
+ the ellipse minor axis.
441
+ maj_axis : float, optional
442
+ the ellipse majot axis.
443
+ p_focus : float, optional
444
+ the distance from the first focus (source position) to the mirror pole.
445
+ convexity : int (as defined by Convexity), optional
446
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
447
+
448
+ Returns
449
+ -------
450
+ instance of Ellipsoid
451
+
452
+ """
453
+ return Ellipsoid(min_axis, maj_axis, p_focus, convexity)
454
+
455
+ @classmethod
456
+ def create_ellipsoid_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003, convexity=Convexity.UPWARD):
457
+ """
458
+ Creates an ellipsoid from factory parameters.
459
+
460
+ Parameters
461
+ ----------
462
+ p : float, optional
463
+ distance source-optical element.
464
+ q : float, optional
465
+ distance optical element to focus.
466
+ grazing_angle : float, optional
467
+ grazing angle in rad.
468
+ convexity : int (as defined by Convexity), optional
469
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
470
+
471
+ Returns
472
+ -------
473
+ instance of Ellipsoid
474
+
475
+ """
476
+ ellipsoid = Ellipsoid(convexity=convexity)
477
+ ellipsoid.initialize_from_p_q(p, q, grazing_angle)
478
+
479
+ return ellipsoid
480
+
481
+ def initialize_from_p_q(self, p=2.0, q=1.0, grazing_angle=0.003):
482
+ """
483
+ Sets the ellipsoid parameters as calculated from the factory parameters.
484
+
485
+ Parameters
486
+ ----------
487
+ p : float, optional
488
+ distance source-optical element.
489
+ q : float, optional
490
+ distance optical element to focus.
491
+ grazing_angle : float, optional
492
+ grazing angle in rad.
493
+
494
+ """
495
+ self._min_axis, self._maj_axis = Ellipsoid.get_axis_from_p_q(p, q, grazing_angle)
496
+ self._p_focus = p
497
+
498
+ def initialize_from_shadow_parameters(self, axmaj=2.0, axmin=1.0, ell_the=0.003, convexity=Convexity.UPWARD):
499
+ """
500
+ Sets the ellipsoid parameters as calculated from the parameters used in SHADOW.
501
+
502
+ Parameters
503
+ ----------
504
+ min_axis : float, optional
505
+ the ellipse minor axis.
506
+ maj_axis : float, optional
507
+ the ellipse majot axis.
508
+ ell_the : float, optional
509
+ the angle beta from the ellipsoid center in rads.
510
+ convexity : int (as defined by Convexity), optional
511
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
512
+
513
+ """
514
+ tanbeta2 = numpy.tan(ell_the) ** 2
515
+ y = axmaj * axmin / numpy.sqrt(axmin ** 2 + axmaj ** 2 * tanbeta2)
516
+ z = y * numpy.tan(ell_the)
517
+ c = numpy.sqrt(axmaj ** 2 - axmin ** 2)
518
+ p = numpy.sqrt( (y + c)**2 + z**2)
519
+
520
+ self.__init__(axmin, axmaj, p, convexity)
521
+
522
+ def get_axes(self):
523
+ """
524
+ Returns the ellipsoid axes.
525
+ Note that the third axis of the ellipsoid is the same as the minor axis (revolution ellipsoid).
526
+
527
+ Returns
528
+ -------
529
+ tuple
530
+ (minor_axis, major_axis)
531
+ """
532
+ return self._min_axis, self._maj_axis
533
+
534
+ def get_p_q(self, grazing_angle=0.003):
535
+ """
536
+ Returns p and q for a given grazing angle.
537
+
538
+ Parameters
539
+ ----------
540
+ grazing_angle : float
541
+ The grazing angle in rad.
542
+
543
+ Returns
544
+ -------
545
+ tuple
546
+ (p, q)
547
+
548
+ """
549
+ return Ellipsoid.get_p_q_from_axis(self._min_axis, self._maj_axis, grazing_angle)
550
+
551
+ # semiaxes etc
552
+ def get_a(self):
553
+ """
554
+ Returns a = half of the major axis.
555
+
556
+ Returns
557
+ -------
558
+ float
559
+
560
+ """
561
+ return 0.5 * self._maj_axis
562
+
563
+ def get_b(self):
564
+ """
565
+ Returns b = half of the minor axis.
566
+
567
+ Returns
568
+ -------
569
+ float
570
+
571
+ """
572
+ return 0.5 * self._min_axis
573
+
574
+ def get_c(self):
575
+ """
576
+ Returns c = sqrt(a^2 - b^2).
577
+
578
+ Returns
579
+ -------
580
+ float
581
+
582
+ """
583
+ return numpy.sqrt(self.get_a()**2 - self.get_b()**2)
584
+
585
+ def get_p_focus(self):
586
+ """
587
+ Returns p (=p_focus).
588
+
589
+ Returns
590
+ -------
591
+ float
592
+
593
+ """
594
+ return self._p_focus
595
+
596
+ def get_q_focus(self):
597
+ """
598
+ Returns q.
599
+
600
+ Returns
601
+ -------
602
+ float
603
+
604
+ """
605
+ return 2 * self.get_a() - self.get_p_focus()
606
+
607
+ def get_eccentricity(self):
608
+ """
609
+ returns the eccentricity e = c / a.
610
+
611
+ Returns
612
+ -------
613
+ float
614
+
615
+ """
616
+ return self.get_c() / self.get_a()
617
+
618
+ def get_grazing_angle(self):
619
+ """
620
+ Returns the grazing angle.
621
+
622
+ Returns
623
+ -------
624
+ float
625
+
626
+ """
627
+ return numpy.arcsin(self.get_b() / numpy.sqrt(self.get_p_focus() * self.get_q_focus()))
628
+
629
+ def get_mirror_center(self):
630
+ """
631
+ Returns the coordinates of the mirror pole or center.
632
+
633
+ Returns
634
+ -------
635
+ tuple
636
+ (coor_along_axis_maj, coor_along_axis_min).
637
+
638
+ """
639
+ coor_along_axis_maj = (self.get_p_focus()**2 - self.get_q_focus()**1) / (4 * self.get_c())
640
+ coor_along_axis_min = self.get_b * numpy.sqrt(1 - (coor_along_axis_maj / self.get_a())**2)
641
+ return coor_along_axis_maj, coor_along_axis_min
642
+
643
+ def get_angle_pole_from_origin(self):
644
+ """
645
+ Return the angle from pole to origin (beta).
646
+
647
+ Returns
648
+ -------
649
+ float
650
+
651
+ """
652
+ x1, x2 = self.get_mirror_center()
653
+ return numpy.arctan(x2 / x1)
654
+
655
+ @classmethod
656
+ def get_axis_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003):
657
+ """
658
+ Calculates the ellipse axes from the factory parameters.
659
+
660
+ Parameters
661
+ ----------
662
+ p : float, optional
663
+ distance source-optical element.
664
+ q : float, optional
665
+ distance optical element to focus.
666
+ grazing_angle : float, optional
667
+ grazing angle in rad.
668
+
669
+ Returns
670
+ -------
671
+ tuple
672
+ (minor_axis, major_axis).
673
+
674
+ """
675
+ # see calculation of ellipse axis in shadow_kernel.f90 row 3605
676
+ min_axis = 2*numpy.sqrt(p*q)*numpy.sin(grazing_angle)
677
+ maj_axis = (p + q)
678
+
679
+ return min_axis, maj_axis
680
+
681
+ @classmethod
682
+ def get_p_q_from_axis(cls, min_axis=2.0, maj_axis=1.0, grazing_angle=0.003):
683
+ """
684
+ Calculates the p and q values from axis and grazing angle.
685
+
686
+ Parameters
687
+ ----------
688
+ min_axis : float, optional
689
+ the ellipse minor axis.
690
+ maj_axis : float, optional
691
+ the ellipse majot axis.
692
+ grazing_angle : float, optional
693
+ grazing angle in rad.
694
+
695
+ Returns
696
+ -------
697
+ tuple
698
+ (p, q).
699
+
700
+ """
701
+ a = maj_axis/2
702
+ b = min_axis/2
703
+ p = a + numpy.sqrt(a**2 - (b/numpy.sin(grazing_angle))**2)
704
+ q = maj_axis - p
705
+
706
+ return p, q
707
+
708
+ class EllipticalCylinder(Ellipsoid, Cylinder):
709
+ """
710
+ Constructor.
711
+
712
+ Parameters
713
+ ----------
714
+ min_axis : float, optional
715
+ the ellipse minor axis.
716
+ maj_axis : float, optional
717
+ the ellipse majot axis.
718
+ p_focus : float, optional
719
+ the distance from the first focus (source position) to the mirror pole.
720
+ convexity : int (as defined by Convexity), optional
721
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
722
+ cylinder_direction : int (as defined by Direction), optional
723
+ TANGENTIAL = 0, SAGITTAL = 1.
724
+
725
+ """
726
+ def __init__(self,
727
+ min_axis=0.0,
728
+ maj_axis=0.0,
729
+ p_focus=0.0,
730
+ convexity=Convexity.UPWARD,
731
+ cylinder_direction=Direction.TANGENTIAL):
732
+ Ellipsoid.__init__(self, min_axis, maj_axis, p_focus, convexity)
733
+ Cylinder.__init__(self, cylinder_direction)
734
+
735
+ @classmethod
736
+ def create_elliptical_cylinder_from_axes(cls, min_axis=0.0, maj_axis=0.0, p_focus=0.0,
737
+ convexity=Convexity.UPWARD, cylinder_direction=Direction.TANGENTIAL):
738
+ """
739
+ Returns an EllipticalCylinder instance from main parameters.
740
+
741
+ Parameters
742
+ ----------
743
+ min_axis : float, optional
744
+ the ellipse minor axis.
745
+ maj_axis : float, optional
746
+ the ellipse majot axis.
747
+ p_focus : float, optional
748
+ the distance from the first focus (source position) to the mirror pole.
749
+ convexity : int (as defined by Convexity), optional
750
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
751
+ cylinder_direction : int (as defined by Direction), optional
752
+ TANGENTIAL = 0, SAGITTAL = 1.
753
+
754
+ Returns
755
+ -------
756
+ instance of EllipticalCylinder
757
+
758
+ """
759
+ return EllipticalCylinder(min_axis, maj_axis, p_focus, convexity, cylinder_direction)
760
+
761
+ @classmethod
762
+ def create_elliptical_cylinder_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003,
763
+ convexity=Convexity.UPWARD, cylinder_direction=Direction.TANGENTIAL):
764
+ """
765
+ Returns an EllipticalCylinder instance from factory parameters.
766
+
767
+ Parameters
768
+ ----------
769
+ p : float, optional
770
+ distance source-optical element.
771
+ q : float, optional
772
+ distance optical element to focus.
773
+ grazing_angle : float, optional
774
+ grazing angle in rad.
775
+ convexity : int (as defined by Convexity), optional
776
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
777
+ cylinder_direction : int (as defined by Direction), optional
778
+ TANGENTIAL = 0, SAGITTAL = 1.
779
+
780
+ Returns
781
+ -------
782
+ instance of EllipticalCylinder
783
+
784
+ """
785
+ elliptical_cylinder = EllipticalCylinder(convexity=convexity, cylinder_direction=cylinder_direction)
786
+ elliptical_cylinder.initialize_from_p_q(p, q, grazing_angle)
787
+
788
+ return elliptical_cylinder
789
+
790
+ def initialize_from_p_q(self, p=2.0, q=1.0, grazing_angle=0.003):
791
+ """
792
+ Sets the ellipsoid parameters for given factory parameters.
793
+
794
+ Parameters
795
+ ----------
796
+ p : float, optional
797
+ distance source-optical element.
798
+ q : float, optional
799
+ distance optical element to focus.
800
+ grazing_angle : float, optional
801
+ grazing angle in rad.
802
+
803
+ """
804
+ if self._cylinder_direction == Direction.SAGITTAL: raise NotImplementedError("Operation not possible for SAGITTAL direction")
805
+
806
+ super().initialize_from_p_q(p, q, grazing_angle)
807
+
808
+ def get_p_q(self, grazing_angle=0.003):
809
+ """
810
+ Returns p and q distances for a given grazing angle.
811
+
812
+ Parameters
813
+ ----------
814
+ grazing_angle : float
815
+ The grazing angle in rad.
816
+
817
+ Returns
818
+ -------
819
+ tuple
820
+ (p, q).
821
+
822
+ """
823
+ if self._cylinder_direction == Direction.SAGITTAL: raise NotImplementedError("Operation not possible for SAGITTAL direction")
824
+
825
+ return super().get_p_q(grazing_angle)
826
+
827
+ class Hyperboloid(SurfaceShape):
828
+ """
829
+ Constructor.
830
+
831
+ Hyperboloid: Revolution hyperboloid (two sheets: rotation around major axis).
832
+ It is defined with three parameters: axes of the hyperbola and an additional parameter
833
+ defining the position of the origin of the mirror. This additional parameter can be "p", "x0", "y0"
834
+ or the angle beta from the ellipsoid center (tan(beta)=y0/x0). For simplicity, we store "p" in syned.
835
+
836
+ Parameters
837
+ ----------
838
+ min_axis : float, optional
839
+ the hyperbola minor axis.
840
+ maj_axis : float, optional
841
+ the hyperbola majot axis.
842
+ p_focus : float, optional
843
+ the distance from the first focus (source position) to the mirror pole.
844
+ convexity : int (as defined by Convexity), optional
845
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
846
+
847
+ References
848
+ ----------
849
+ Some equations can be found here: https://github.com/srio/shadow3-docs/blob/master/doc/conics.pdf
850
+
851
+ """
852
+ def __init__(self, min_axis=0.0, maj_axis=0.0, p_focus=0.0, convexity=Convexity.UPWARD):
853
+ SurfaceShape.__init__(self, convexity)
854
+
855
+ self._min_axis = min_axis
856
+ self._maj_axis = maj_axis
857
+ self._p_focus = p_focus
858
+ # support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
859
+ self._set_support_text([
860
+ ("min_axis" , "Hyperbola major axis ", "m" ),
861
+ ("maj_axis" , "Hyperbola minor axis ", "m"),
862
+ ("p_focus" , "Hyperbola p (source-focus to pole) ", "m"),
863
+ ("convexity" , "(0=upwards, 1=downwards)", " "),
864
+ ] )
865
+
866
+ @classmethod
867
+ def create_hyperboloid_from_axes(cls, min_axis=0.0, maj_axis=0.0, p_focus=0.0, convexity=Convexity.UPWARD):
868
+ """
869
+ Creates an hyperboloid from main parameters.
870
+
871
+ Parameters
872
+ ----------
873
+ min_axis : float, optional
874
+ the ellipse minor axis.
875
+ maj_axis : float, optional
876
+ the ellipse majot axis.
877
+ p_focus : float, optional
878
+ the angle beta from the hyperbola center in rads.
879
+ convexity : int (as defined by Convexity), optional
880
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
881
+
882
+ Returns
883
+ -------
884
+ instance of Hyperboloid
885
+
886
+ """
887
+ return Hyperboloid(min_axis, maj_axis, p_focus, convexity)
888
+
889
+ @classmethod
890
+ def create_hyperboloid_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003, convexity=Convexity.UPWARD):
891
+ """
892
+ Creates an hyperboloid from factory parameters.
893
+
894
+ Parameters
895
+ ----------
896
+ p : float, optional
897
+ distance source-optical element.
898
+ q : float, optional
899
+ distance optical element to focus.
900
+ grazing_angle : float, optional
901
+ grazing angle in rad.
902
+ convexity : int (as defined by Convexity), optional
903
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
904
+
905
+ Returns
906
+ -------
907
+ instance of Hyoerboloid
908
+
909
+ """
910
+ hyperboloid = Hyperboloid(convexity=convexity)
911
+ hyperboloid.initialize_from_p_q(p, q, grazing_angle)
912
+
913
+ return hyperboloid
914
+
915
+ def initialize_from_p_q(self, p=2.0, q=1.0, grazing_angle=0.003):
916
+ """
917
+ Sets the hyperboloid parameters as calculated from the factory parameters.
918
+
919
+ Parameters
920
+ ----------
921
+ p : float, optional
922
+ distance source-optical element.
923
+ q : float, optional
924
+ distance optical element to focus.
925
+ grazing_angle : float, optional
926
+ grazing angle in rad.
927
+
928
+ """
929
+ self._min_axis, self._maj_axis = Hyperboloid.get_axis_from_p_q(p, q, grazing_angle)
930
+ self._p_focus = p
931
+
932
+ # TODO:
933
+ def initialize_from_shadow_parameters(self, axmaj=2.0, axmin=1.0, ell_the=0.003, convexity=Convexity.UPWARD):
934
+ """
935
+ Sets the hyperboloid parameters as calculated from the parameters used in SHADOW.
936
+ Note that in SHADOW3 the definition of the hyperbola from the factory parameters is buggy.
937
+
938
+ Raises
939
+ ------
940
+ NotImplementedError
941
+
942
+ """
943
+ raise NotImplementedError("TODO")
944
+
945
+ def get_axes(self):
946
+ """
947
+ Returns the hyperboloid axes.
948
+ Note that the third axis of the ellipsoid is the same as the minor axis (revolution ellipsoid).
949
+
950
+ Returns
951
+ -------
952
+ tuple
953
+ (minor_axis, major_axis)
954
+ """
955
+ return self._min_axis, self._maj_axis
956
+
957
+ def get_p_q(self, grazing_angle=0.003):
958
+ """
959
+ Returns p and q for a given grazing angle.
960
+
961
+ Parameters
962
+ ----------
963
+ grazing_angle : float
964
+ The grazing angle in rad.
965
+
966
+ Returns
967
+ -------
968
+ tuple
969
+ (p, q)
970
+
971
+ """
972
+ return Hyperboloid.get_p_q_from_axis(self._min_axis, self._maj_axis, grazing_angle)
973
+
974
+ # semiaxes etc
975
+ def get_a(self):
976
+ """
977
+ Returns a = half of the major axis.
978
+
979
+ Returns
980
+ -------
981
+ float
982
+
983
+ """
984
+ return 0.5 * self._maj_axis
985
+
986
+ def get_b(self):
987
+ """
988
+ Returns b = half of the minor axis.
989
+
990
+ Returns
991
+ -------
992
+ float
993
+
994
+ """
995
+ return 0.5 * self._min_axis
996
+
997
+ def get_c(self):
998
+ """
999
+ Returns c = sqrt(a^2 + b^2).
1000
+
1001
+ Returns
1002
+ -------
1003
+ float
1004
+
1005
+ """
1006
+ return numpy.sqrt(self.get_a()**2 + self.get_b()**2)
1007
+
1008
+ def get_p_focus(self):
1009
+ """
1010
+ Returns p (=p_focus).
1011
+
1012
+ Returns
1013
+ -------
1014
+ float
1015
+
1016
+ """
1017
+ return self._p_focus
1018
+
1019
+ def get_q_focus(self):
1020
+ """
1021
+ Returns q.
1022
+
1023
+ Returns
1024
+ -------
1025
+ float
1026
+
1027
+ """
1028
+ return self.get_p_focus() - 2 * self.get_a()
1029
+
1030
+ def get_eccentricity(self):
1031
+ """
1032
+ returns the eccentricity e = c / a.
1033
+
1034
+ Returns
1035
+ -------
1036
+ float
1037
+
1038
+ """
1039
+ return self.get_c / self.get_a()
1040
+
1041
+ def get_grazing_angle(self):
1042
+ """
1043
+ Returns the grazing angle.
1044
+
1045
+ Returns
1046
+ -------
1047
+ float
1048
+
1049
+ """
1050
+ return numpy.arcsin(self.get_b() / numpy.sqrt(self.get_p_focus() * self.get_q_focus()))
1051
+
1052
+
1053
+ def get_mirror_center(self): #TODO:
1054
+ """
1055
+ Returns the coordinates of the mirror pole or center.
1056
+
1057
+ Raises
1058
+ ------
1059
+ NotImplementedError
1060
+
1061
+ """
1062
+ raise NotImplementedError("TODO")
1063
+
1064
+ def get_angle_pole_from_origin(self): #TODO:
1065
+ """
1066
+ Return the angle from pole to origin (beta).
1067
+
1068
+ Raises
1069
+ ------
1070
+ NotImplementedError
1071
+
1072
+ """
1073
+ raise NotImplementedError("TODO")
1074
+
1075
+ @classmethod
1076
+ def get_axis_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003, branch_sign=+1):
1077
+ """
1078
+ Calculates the hyperbola axes from the factory parameters.
1079
+
1080
+ Parameters
1081
+ ----------
1082
+ p : float, optional
1083
+ distance source-optical element.
1084
+ q : float, optional
1085
+ distance optical element to focus.
1086
+ grazing_angle : float, optional
1087
+ grazing angle in rad.
1088
+ branch_sign : int
1089
+ +1 (positive) or -1 (negative) branch.
1090
+
1091
+ Returns
1092
+ -------
1093
+ tuple
1094
+ (minor_axis, major_axis).
1095
+
1096
+ """
1097
+ min_axis = 2*numpy.sqrt(p*q)*numpy.sin(grazing_angle)
1098
+ maj_axis = (p - q) * branch_sign
1099
+
1100
+ return min_axis, maj_axis
1101
+
1102
+ # TODO:
1103
+ @classmethod
1104
+ def get_p_q_from_axis(cls, min_axis=2.0, maj_axis=1.0, grazing_angle=0.003):
1105
+ """
1106
+ Calculates the p and q values from axis and grazing angle.
1107
+
1108
+ Raises
1109
+ ------
1110
+ NotImplementedError
1111
+
1112
+ """
1113
+ raise NotImplementedError("TODO")
1114
+
1115
+ class HyperbolicCylinder(Hyperboloid, Cylinder):
1116
+ """
1117
+ Constructor.
1118
+
1119
+ Parameters
1120
+ ----------
1121
+ min_axis : float, optional
1122
+ the ellipse minor axis.
1123
+ maj_axis : float, optional
1124
+ the ellipse majot axis.
1125
+ p_focus : float, optional
1126
+ the distance from the first focus (source position) to the mirror pole.
1127
+ convexity : int (as defined by Convexity), optional
1128
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
1129
+ cylinder_direction : int (as defined by Direction), optional
1130
+ TANGENTIAL = 0, SAGITTAL = 1.
1131
+
1132
+ """
1133
+ def __init__(self,
1134
+ min_axis=0.0,
1135
+ maj_axis=0.0,
1136
+ p_focus=0.0,
1137
+ convexity=Convexity.UPWARD,
1138
+ cylinder_direction=Direction.TANGENTIAL):
1139
+ Hyperboloid.__init__(self, min_axis, maj_axis, p_focus, convexity)
1140
+ Cylinder.__init__(self, cylinder_direction)
1141
+
1142
+
1143
+ @classmethod
1144
+ def create_hyperbolic_cylinder_from_axes(cls, min_axis=0.0, maj_axis=0.0, p_focus=0.0,
1145
+ convexity=Convexity.UPWARD, cylinder_direction=Direction.TANGENTIAL):
1146
+ """
1147
+ Returns an HyperbolicCylinder instance from main parameters.
1148
+
1149
+ Parameters
1150
+ ----------
1151
+ min_axis : float, optional
1152
+ the ellipse minor axis.
1153
+ maj_axis : float, optional
1154
+ the ellipse majot axis.
1155
+ p_focus : float, optional
1156
+ the distance from the first focus (source position) to the mirror pole.
1157
+ convexity : int (as defined by Convexity), optional
1158
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
1159
+ cylinder_direction : int (as defined by Direction), optional
1160
+ TANGENTIAL = 0, SAGITTAL = 1.
1161
+
1162
+ Returns
1163
+ -------
1164
+ instance of HyperbolicCylinder
1165
+
1166
+ """
1167
+ return HyperbolicCylinder(min_axis, maj_axis, p_focus, convexity, cylinder_direction)
1168
+
1169
+ @classmethod
1170
+ def create_hyperbolic_cylinder_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003,
1171
+ convexity=Convexity.UPWARD, cylinder_direction=Direction.TANGENTIAL):
1172
+ """
1173
+ Returns an HyperbolicCylinder instance from factory parameters.
1174
+
1175
+ Parameters
1176
+ ----------
1177
+ p : float, optional
1178
+ distance source-optical element.
1179
+ q : float, optional
1180
+ distance optical element to focus.
1181
+ grazing_angle : float, optional
1182
+ grazing angle in rad.
1183
+ convexity : int (as defined by Convexity), optional
1184
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
1185
+ cylinder_direction : int (as defined by Direction), optional
1186
+ TANGENTIAL = 0, SAGITTAL = 1.
1187
+
1188
+ Returns
1189
+ -------
1190
+ instance of HyperbolicCylinder
1191
+
1192
+ """
1193
+ hyperbolic_cylinder = HyperbolicCylinder(convexity=convexity, cylinder_direction=cylinder_direction)
1194
+ hyperbolic_cylinder.initialize_from_p_q(p, q, grazing_angle)
1195
+
1196
+ return hyperbolic_cylinder
1197
+
1198
+ def initialize_from_p_q(self, p=2.0, q=1.0, grazing_angle=0.003):
1199
+ """
1200
+ Sets the hyperboloid parameters for given factory parameters.
1201
+
1202
+ Parameters
1203
+ ----------
1204
+ p : float, optional
1205
+ distance source-optical element.
1206
+ q : float, optional
1207
+ distance optical element to focus.
1208
+ grazing_angle : float, optional
1209
+ grazing angle in rad.
1210
+
1211
+ """
1212
+ if self._cylinder_direction == Direction.SAGITTAL: raise NotImplementedError("Operation not possible for SAGITTAL direction")
1213
+
1214
+ super().initialize_from_p_q(p, q, grazing_angle)
1215
+
1216
+ def get_p_q(self, grazing_angle=0.003):
1217
+ """
1218
+ Returns p and q distances for a given grazing angle.
1219
+
1220
+ Parameters
1221
+ ----------
1222
+ grazing_angle : float
1223
+ The grazing angle in rad.
1224
+
1225
+ Returns
1226
+ -------
1227
+ tuple
1228
+ (p, q).
1229
+
1230
+ """
1231
+ if self._cylinder_direction == Direction.SAGITTAL: raise NotImplementedError("Operation not possible for SAGITTAL direction")
1232
+
1233
+ return super().get_p_q(grazing_angle)
1234
+
1235
+ class Paraboloid(SurfaceShape):
1236
+ """
1237
+ Constructor.
1238
+
1239
+ Paraboloid: Revolution paraboloid (rotation around symmetry axis).
1240
+
1241
+ It is defined with three parameters: the parabola_parameter and two more parameters
1242
+ defining the position of the origin of the mirror.
1243
+
1244
+ The parabola_parameter = 2 * focal_length = - 0.5 * ccc_9 / ccc_2
1245
+
1246
+ The additional parameter can be the focal distances
1247
+ ("p" or "q", one is infinity), "x0", "y0" or the grazing angle.
1248
+ Here, we selected the at_infinity and the finite focal distance p or q or distance from
1249
+ the mirror pole to focus (pole to focus).
1250
+
1251
+ The parabola equation is:
1252
+
1253
+ ccc_2 y^2 + ccc_9 z = 0 or
1254
+
1255
+ y^2 = -ccc_9/ccc_2 z = 2 parabola_parameter z = 4 focal_length z
1256
+
1257
+ The focus is at (0, 0, focal_length).
1258
+
1259
+ The directrix is at (0, 0, -focal_length).
1260
+
1261
+ The distance from the directrix to focus is 2 * focal_length.
1262
+
1263
+ The radius of curvature at the vertex is 2 * focal_length.
1264
+
1265
+ Parameters
1266
+ ----------
1267
+ parabola_parameter : float, optional
1268
+ parabola_parameter = 2 * focal_length = - 0.5 * ccc_9 / ccc_2. Equation: y^2 = 2 parabola_parameter z.
1269
+ at_infinity : int (as defined by Side), optional
1270
+ SOURCE = 0, IMAGE = 1.
1271
+ pole_to_focus : float, optional
1272
+ The p distance.
1273
+ convexity : int (as defined by Convexity), optional
1274
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
1275
+
1276
+ References
1277
+ ----------
1278
+ https://en.wikipedia.org/wiki/Parabola
1279
+
1280
+ https://doi.org/10.1107/S1600577522004593
1281
+
1282
+ Some equations can be found here: https://github.com/srio/shadow3-docs/blob/master/doc/conics.pdf
1283
+
1284
+ """
1285
+ def __init__(self,
1286
+ parabola_parameter=0.0,
1287
+ at_infinity=Side.SOURCE,
1288
+ pole_to_focus=None,
1289
+ convexity=Convexity.UPWARD):
1290
+ SurfaceShape.__init__(self, convexity)
1291
+
1292
+ self._parabola_parameter = parabola_parameter
1293
+ self._at_infinity = at_infinity
1294
+ self._pole_to_focus = pole_to_focus
1295
+ # support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
1296
+ self._set_support_text([
1297
+ ("parabola_parameter" , "Parabola parameter ", "m" ),
1298
+ ("at_infinity" , "(0=source, 1=image)", " " ),
1299
+ ("pole_to_focus" , "pole to focus", "m"),
1300
+ ("convexity" , "(0=upwards, 1=downwards)", " "),
1301
+ ] )
1302
+
1303
+ @classmethod
1304
+ def create_paraboloid_from_parabola_parameter(cls, parabola_parameter=0.0, at_infinity=Side.SOURCE,
1305
+ pole_to_focus=None, convexity=Convexity.UPWARD):
1306
+ """
1307
+ Create a paraboloid.
1308
+
1309
+ Parameters
1310
+ ----------
1311
+ parabola_parameter : float, optional
1312
+ parabola_parameter = 2 * focal_distance = - 0.5 * ccc_9 / ccc_2.
1313
+ at_infinity : int (as defined by Side), optional
1314
+ SOURCE = 0, IMAGE = 1.
1315
+ pole_to_focus : float, optional
1316
+ The p distance.
1317
+ convexity : int (as defined by Convexity), optional
1318
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
1319
+
1320
+ Returns
1321
+ -------
1322
+ instance of Paraboloid
1323
+
1324
+ """
1325
+ return Paraboloid(parabola_parameter, at_infinity=at_infinity, pole_to_focus=pole_to_focus, convexity=convexity)
1326
+
1327
+ @classmethod
1328
+ def create_paraboloid_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003,
1329
+ at_infinity=Side.SOURCE, convexity=Convexity.UPWARD):
1330
+ """
1331
+ Creates a paraboloid from the factory parameters.
1332
+
1333
+ Parameters
1334
+ ----------
1335
+ p : float
1336
+ The distance p (used if at_infinity=Side.IMAGE)
1337
+ q : float
1338
+ The distance q (used if at_infinity=Side.SOURCE)
1339
+ grazing_angle : float
1340
+ The distance p
1341
+ at_infinity : int (as defined by Side), optional
1342
+ SOURCE = 0, IMAGE = 1.
1343
+ convexity : int (as defined by Convexity), optional
1344
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
1345
+
1346
+ Returns
1347
+ -------
1348
+ instance of Paraboloid
1349
+
1350
+ """
1351
+ paraboloid = Paraboloid(convexity=convexity)
1352
+ paraboloid.initialize_from_p_q(p, q, grazing_angle=grazing_angle, at_infinity=at_infinity)
1353
+
1354
+ return paraboloid
1355
+
1356
+ def initialize_from_p_q(self, p=2.0, q=1.0, grazing_angle=0.003, at_infinity=Side.SOURCE):
1357
+ """
1358
+ Sets the paraboloid parameters as calculated from the factory parameters.
1359
+
1360
+ Parameters
1361
+ ----------
1362
+ p : float
1363
+ The distance p (used if at_infinity=Side.IMAGE)
1364
+ q : float
1365
+ The distance q (used if at_infinity=Side.SOURCE)
1366
+ grazing_angle : float
1367
+ The distance p
1368
+ at_infinity : int (as defined by Side), optional
1369
+ SOURCE = 0, IMAGE = 1.
1370
+
1371
+ Returns
1372
+ -------
1373
+ instance of Paraboloid
1374
+
1375
+ """
1376
+ self._parabola_parameter = Paraboloid.get_parabola_parameter_from_p_q(p=p, q=q, grazing_angle=grazing_angle, at_infinity=at_infinity)
1377
+ self._at_infinity = at_infinity
1378
+ if at_infinity == Side.SOURCE:
1379
+ self._pole_to_focus = q
1380
+ elif at_infinity == Side.IMAGE:
1381
+ self._pole_to_focus = p
1382
+
1383
+ @classmethod
1384
+ def get_parabola_parameter_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003, at_infinity=Side.SOURCE):
1385
+ """
1386
+ Calculates the parabola parameter from the factory parameters.
1387
+
1388
+ Parameters
1389
+ ----------
1390
+ p : float
1391
+ The distance p (used if at_infinity=Side.IMAGE)
1392
+ q : float
1393
+ The distance q (used if at_infinity=Side.SOURCE)
1394
+ grazing_angle : float
1395
+ The distance p
1396
+ at_infinity : int (as defined by Side), optional
1397
+ SOURCE = 0, IMAGE = 1.
1398
+
1399
+ Returns
1400
+ -------
1401
+ float
1402
+ The parabola parameter.
1403
+
1404
+ """
1405
+ if at_infinity == Side.IMAGE:
1406
+ return 2*p*(numpy.sin(grazing_angle))**2
1407
+ elif at_infinity == Side.SOURCE:
1408
+ return 2*q*(numpy.sin(grazing_angle))**2
1409
+
1410
+ def get_parabola_parameter(self):
1411
+ """
1412
+ Returns the parabola parameter.
1413
+
1414
+ Returns
1415
+ -------
1416
+ float
1417
+
1418
+ """
1419
+ return self._parabola_parameter
1420
+
1421
+ def get_at_infinity(self):
1422
+ """
1423
+ Returns the "at_infinity" flag.
1424
+
1425
+ Returns
1426
+ -------
1427
+ int (as defined by Side)
1428
+ SOURCE = 0, IMAGE = 1.
1429
+
1430
+ """
1431
+ return self._at_infinity
1432
+
1433
+ def get_pole_to_focus(self):
1434
+ """
1435
+ Returns the distance from focus to pole.
1436
+
1437
+ Returns
1438
+ -------
1439
+ float
1440
+
1441
+ """
1442
+ return self._pole_to_focus
1443
+
1444
+ def get_grazing_angle(self):
1445
+ """
1446
+ Returns the grazing angle.
1447
+
1448
+ Returns
1449
+ -------
1450
+ float
1451
+
1452
+ """
1453
+ return numpy.arcsin( numpy.sqrt( self.get_parabola_parameter() / (2 * self.get_pole_to_focus())))
1454
+
1455
+
1456
+ class ParabolicCylinder(Paraboloid, Cylinder):
1457
+ """
1458
+ Constructor.
1459
+
1460
+ Parameters
1461
+ ----------
1462
+ parabola_parameter : float, optional
1463
+ parabola_parameter = 2 * focal_distance = - 0.5 * ccc_9 / ccc_2.
1464
+ at_infinity : int (as defined by Side), optional
1465
+ SOURCE = 0, IMAGE = 1.
1466
+ pole_to_focus : float, optional
1467
+ The p distance.
1468
+ convexity : int (as defined by Convexity), optional
1469
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
1470
+ cylinder_direction : int (as defined by Direction), optional
1471
+ TANGENTIAL = 0, SAGITTAL = 1.
1472
+
1473
+ """
1474
+ def __init__(self,
1475
+ parabola_parameter=0.0,
1476
+ at_infinity=Side.SOURCE,
1477
+ pole_to_focus=None,
1478
+ convexity=Convexity.UPWARD,
1479
+ cylinder_direction=Direction.TANGENTIAL):
1480
+ Paraboloid.__init__(self, parabola_parameter=parabola_parameter, at_infinity=at_infinity,
1481
+ pole_to_focus=pole_to_focus, convexity=convexity)
1482
+ Cylinder.__init__(self, cylinder_direction)
1483
+
1484
+ @classmethod
1485
+ def create_parabolic_cylinder_from_parabola_parameter(cls,
1486
+ parabola_parameter=0.0,
1487
+ at_infinity=Side.SOURCE,
1488
+ pole_to_focus=None,
1489
+ convexity=Convexity.UPWARD,
1490
+ cylinder_direction=Direction.TANGENTIAL):
1491
+ """
1492
+ Returns a ParabolicCylinder instance.
1493
+
1494
+ Parameters
1495
+ ----------
1496
+ parabola_parameter : float, optional
1497
+ parabola_parameter = 2 * focal_distance = - 0.5 * ccc_9 / ccc_2.
1498
+ at_infinity : int (as defined by Side), optional
1499
+ SOURCE = 0, IMAGE = 1.
1500
+ pole_to_focus : float, optional
1501
+ The p distance.
1502
+ convexity : int (as defined by Convexity), optional
1503
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
1504
+ cylinder_direction : int (as defined by Direction), optional
1505
+ TANGENTIAL = 0, SAGITTAL = 1.
1506
+
1507
+ Returns
1508
+ -------
1509
+ instance of ParabolicCylinder
1510
+
1511
+ """
1512
+ return ParabolicCylinder(parabola_parameter, at_infinity, pole_to_focus, convexity, cylinder_direction)
1513
+
1514
+ @classmethod
1515
+ def create_parabolic_cylinder_from_p_q(cls,
1516
+ p=2.0,
1517
+ q=1.0,
1518
+ grazing_angle=0.003,
1519
+ at_infinity=Side.SOURCE,
1520
+ convexity=Convexity.UPWARD,
1521
+ cylinder_direction=Direction.TANGENTIAL):
1522
+ """
1523
+ Returns a ParabolicCylinder instance from factory parameters.
1524
+
1525
+ Parameters
1526
+ ----------
1527
+ p : float
1528
+ The distance p (used if at_infinity=Side.IMAGE)
1529
+ q : float
1530
+ The distance q (used if at_infinity=Side.SOURCE)
1531
+ grazing_angle : float
1532
+ The distance p
1533
+ at_infinity : int (as defined by Side), optional
1534
+ SOURCE = 0, IMAGE = 1.
1535
+ convexity : int (as defined by Convexity), optional
1536
+ NONE = -1, UPWARD = 0, DOWNWARD = 1.
1537
+ cylinder_direction : int (as defined by Direction), optional
1538
+ TANGENTIAL = 0, SAGITTAL = 1.
1539
+
1540
+ Returns
1541
+ -------
1542
+ instance of ParabolicCylinder
1543
+
1544
+ """
1545
+ parabolic_cylinder = ParabolicCylinder(convexity=convexity, cylinder_direction=cylinder_direction)
1546
+ parabolic_cylinder.initialize_from_p_q(p, q, grazing_angle, at_infinity)
1547
+
1548
+ return parabolic_cylinder
1549
+
1550
+ def initialize_from_p_q(self, p=2.0, q=1.0, grazing_angle=0.003, at_infinity=Side.SOURCE):
1551
+ """
1552
+ Sets the parameters calculated from factory parameters.
1553
+
1554
+ Parameters
1555
+ ----------
1556
+ p : float
1557
+ The distance p (used if at_infinity=Side.IMAGE)
1558
+ q : float
1559
+ The distance q (used if at_infinity=Side.SOURCE)
1560
+ grazing_angle : float
1561
+ The distance p
1562
+ at_infinity : int (as defined by Side), optional
1563
+ SOURCE = 0, IMAGE = 1.
1564
+
1565
+ Returns
1566
+ -------
1567
+ instance of ParabolicCylinder
1568
+
1569
+ """
1570
+ if self._cylinder_direction == Direction.SAGITTAL:
1571
+ raise NotImplementedError("Operation not possible for SAGITTAL direction")
1572
+
1573
+ return super().initialize_from_p_q(p, q, grazing_angle, at_infinity)
1574
+
1575
+ class Toroid(SurfaceShape):
1576
+ """
1577
+ Creator.
1578
+
1579
+ Parameters
1580
+ ----------
1581
+ min_radius : float, optional
1582
+ The toroid minor radius
1583
+ maj_radius : float, optional
1584
+ The toroid major radius. Note that this is the "optical" major radius at the farest surface from the center
1585
+ of the toroid. Indeed, it corresponds to the "toroid major radius" plus the min_radius.
1586
+
1587
+ """
1588
+ def __init__(self, min_radius=0.0, maj_radius=0.0):
1589
+ SurfaceShape.__init__(self, convexity=Convexity.NONE)
1590
+
1591
+ self._min_radius = min_radius
1592
+ self._maj_radius = maj_radius
1593
+
1594
+ # support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
1595
+ self._set_support_text([
1596
+ ("min_radius" , "Minor radius r ", "m" ),
1597
+ ("maj_radius" , "Major (optical) radius R (R=Ro+r)", "m" ),
1598
+ ] )
1599
+
1600
+ @classmethod
1601
+ def create_toroid_from_radii(cls, min_radius=0.0, maj_radius=0.0):
1602
+ """
1603
+ returns a Toroid from main parameters (radii).
1604
+
1605
+ Parameters
1606
+ ----------
1607
+ min_radius : float, optional
1608
+ The toroid minor radius
1609
+ maj_radius : float, optional
1610
+ The toroid major radius. Note that this is the "optical" major radius at the farest surface from the center
1611
+ of the toroid. Indeed, it corresponds to the "toroid major radius" plus the min_radius.
1612
+
1613
+ Returns
1614
+ -------
1615
+ instance of Toroid
1616
+
1617
+ """
1618
+ return Toroid(min_radius, maj_radius)
1619
+
1620
+ @classmethod
1621
+ def create_toroid_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003):
1622
+ """
1623
+ returns a Toroid from factory parameters.
1624
+
1625
+ Parameters
1626
+ ----------
1627
+ p : float, optional
1628
+ distance source-optical element.
1629
+ q : float, optional
1630
+ distance optical element to focus.
1631
+ grazing_angle : float, optional
1632
+ grazing angle in rad.
1633
+
1634
+ Returns
1635
+ -------
1636
+ instance of Toroid
1637
+
1638
+ """
1639
+ R = 2 / numpy.sin(grazing_angle) * p * q / (p + q)
1640
+ r = 2 * numpy.sin(grazing_angle) * p * q / (p + q)
1641
+ return Toroid(min_radius=r, maj_radius=R)
1642
+
1643
+ def get_radii(self):
1644
+ """
1645
+ Returns the radii.
1646
+
1647
+ Returns
1648
+ -------
1649
+ tuple
1650
+ (min_radius, maj_radius).
1651
+
1652
+ """
1653
+ return self._min_radius, self._maj_radius
1654
+
1655
+ def get_min_radius(self):
1656
+ """
1657
+ Returns the minor radius.
1658
+
1659
+ Returns
1660
+ -------
1661
+ float
1662
+
1663
+ """
1664
+ return self._min_radius
1665
+
1666
+ def get_maj_radius(self):
1667
+ """
1668
+ Returns the major (optical) radius.
1669
+
1670
+ Returns
1671
+ -------
1672
+ float
1673
+
1674
+ """
1675
+ return self._maj_radius
1676
+
1677
+ def initialize_from_p_q(self, p=2.0, q=1.0, grazing_angle=0.003):
1678
+ """
1679
+ Sets the parameters calculated from the factory parameters.
1680
+
1681
+ Parameters
1682
+ ----------
1683
+ p : float, optional
1684
+ distance source-optical element.
1685
+ q : float, optional
1686
+ distance optical element to focus.
1687
+ grazing_angle : float, optional
1688
+ grazing angle in rad.
1689
+
1690
+ """
1691
+ self._maj_radius = Sphere.get_radius_from_p_q(p, q, grazing_angle)
1692
+ self._min_radius = SphericalCylinder.get_radius_from_p_q_sagittal(p, q, grazing_angle)
1693
+
1694
+ # FROM SHADOW3:
1695
+ #! C
1696
+ #! C NOTE : The major radius is the in reality the radius of the torus
1697
+ #! C max. circle. The true major radius is then
1698
+ #! C
1699
+ # R_MAJ = R_MAJ - R_MIN
1700
+ self._maj_radius -= self._min_radius
1701
+
1702
+
1703
+ # This is exactly the same as OasysSurfaceData
1704
+ # class OasysSurfaceData(object):
1705
+ class NumericalMesh(SurfaceShape):
1706
+ """
1707
+ Implements an optical surface from a numerical mesh.
1708
+
1709
+ Constructor.
1710
+
1711
+ Parameters
1712
+ ----------
1713
+ xx : numpy array, optional
1714
+ The x vector.
1715
+ yy : numpy array, optional
1716
+ The y vector.
1717
+ zz : numpy array, optional
1718
+ The z (2D) array.
1719
+ surface_data_file : str, optional
1720
+ a file name from where the dara may come.
1721
+
1722
+ Notes
1723
+ -----
1724
+ This is exactly the same as OasysSurfaceData class OasysSurfaceData(object), with added methods.
1725
+
1726
+ """
1727
+ def __init__(self,
1728
+ xx=None,
1729
+ yy=None,
1730
+ zz=None,
1731
+ surface_data_file=None):
1732
+ self._xx = xx
1733
+ self._yy = yy
1734
+ self._zz = zz
1735
+ self._surface_data_file=surface_data_file
1736
+
1737
+ def has_surface_data(self):
1738
+ """
1739
+ Returns True is data is loaded.
1740
+
1741
+ Returns
1742
+ -------
1743
+ boolean
1744
+
1745
+ """
1746
+ return not (self._xx is None or self._yy is None or self._zz is None)
1747
+
1748
+ def has_surface_data_file(self):
1749
+ """
1750
+ Returns True is data file is set.
1751
+
1752
+ Returns
1753
+ -------
1754
+ boolean
1755
+
1756
+ """
1757
+ return not self._surface_data_file is None
1758
+
1759
+
1760
+ ##############################
1761
+ # subclasses for BoundaryShape
1762
+ ##############################
1763
+
1764
+
1765
+ class Rectangle(BoundaryShape):
1766
+ """
1767
+ Constructor.
1768
+
1769
+ Parameters
1770
+ ----------
1771
+ x_left : float, optional
1772
+ The coordinate (signed) of the minimum (left) along the X axis.
1773
+ x_right : float, optional
1774
+ The coordinate (signed) of the maximum (right) along the X axis.
1775
+ y_bottom : float, optional
1776
+ The coordinate (signed) of the minimum (left) along the Y axis.
1777
+ y_top : float, optional
1778
+ The coordinate (signed) of the maximum (right) along the Y axis.
1779
+ """
1780
+ def __init__(self, x_left=-0.010, x_right=0.010, y_bottom=-0.020, y_top=0.020):
1781
+ super().__init__()
1782
+
1783
+ self._x_left = x_left
1784
+ self._x_right = x_right
1785
+ self._y_bottom = y_bottom
1786
+ self._y_top = y_top
1787
+
1788
+ # support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
1789
+ self._set_support_text([
1790
+ ("x_left" , "x (width) minimum (signed) ", "m" ),
1791
+ ("x_right" , "x (width) maximum (signed) ", "m" ),
1792
+ ("y_bottom" , "y (length) minimum (signed) ", "m" ),
1793
+ ("y_top" , "y (length) maximum (signed) ", "m" ),
1794
+ ] )
1795
+
1796
+ def get_boundaries(self):
1797
+ """
1798
+ Return the rectangle coordinates.
1799
+
1800
+ Returns
1801
+ -------
1802
+ tuple
1803
+ (x_left, x_right, y_bottom, y_top).
1804
+
1805
+ """
1806
+ return self._x_left, self._x_right, self._y_bottom, self._y_top
1807
+
1808
+ def set_boundaries(self,x_left=-0.010, x_right=0.010, y_bottom=-0.020, y_top=0.020):
1809
+ """
1810
+ Sets the rectangle coordinates.
1811
+
1812
+ Parameters
1813
+ ----------
1814
+ x_left : float, optional
1815
+ The coordinate (signed) of the minimum (left) along the X axis.
1816
+ x_right : float, optional
1817
+ The coordinate (signed) of the maximum (right) along the X axis.
1818
+ y_bottom : float, optional
1819
+ The coordinate (signed) of the minimum (left) along the Y axis.
1820
+ y_top : float, optional
1821
+ The coordinate (signed) of the maximum (right) along the Y axis.
1822
+
1823
+ """
1824
+ self._x_left = x_left
1825
+ self._x_right = x_right
1826
+ self._y_bottom = y_bottom
1827
+ self._y_top = y_top
1828
+
1829
+ def set_width_and_length(self,width=10e-3,length=30e-3):
1830
+ """
1831
+ Sets the rectangle parameters from width and length (centered at the origin).
1832
+
1833
+ Parameters
1834
+ ----------
1835
+ width : float, optional
1836
+ The rectangle width.
1837
+ length : float, optional
1838
+ The rectangle length.
1839
+
1840
+ """
1841
+ self._x_left = -0.5 * width
1842
+ self._x_right = 0.5 * width
1843
+ self._y_bottom = -0.5 * length
1844
+ self._y_top = 0.5 * length
1845
+
1846
+ class Ellipse(BoundaryShape):
1847
+ """
1848
+ Constructor.
1849
+
1850
+ Parameters
1851
+ ----------
1852
+ a_axis_min : float, optional
1853
+ The coordinate (signed) of the minimum (left) along the major axis.
1854
+ a_axis_max : float, optional
1855
+ The coordinate (signed) of the maximum (right) along the major axis.
1856
+ b_axis_min : float, optional
1857
+ The coordinate (signed) of the minimum (left) along the minor axis.
1858
+ b_axis_max : float, optional
1859
+ The coordinate (signed) of the maximum (right) along the minor axis.
1860
+
1861
+ """
1862
+ def __init__(self, a_axis_min=-10e-6, a_axis_max=10e-6, b_axis_min=-5e-6, b_axis_max=5e-6):
1863
+ super().__init__()
1864
+
1865
+ self._a_axis_min = a_axis_min
1866
+ self._a_axis_max = a_axis_max
1867
+ self._b_axis_min = b_axis_min
1868
+ self._b_axis_max = b_axis_max
1869
+ # support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
1870
+ self._set_support_text([
1871
+ ("a_axis_min" , "x (width) axis starts (signed) ", "m" ),
1872
+ ("a_axis_max" , "x (width) axis ends (signed) ", "m" ),
1873
+ ("b_axis_min" , "y (length) axis starts (signed) ", "m" ),
1874
+ ("b_axis_max" , "y (length) axis ends (signed) ", "m" ),
1875
+ ] )
1876
+
1877
+ def get_boundaries(self):
1878
+ """
1879
+ Returns the coordinates of the ellipse.
1880
+
1881
+ Returns
1882
+ -------
1883
+ tuple
1884
+ (a_axis_min, a_axis_max, b_axis_min, b_axis_max).
1885
+
1886
+ """
1887
+ return self._a_axis_min, self._a_axis_max, self._b_axis_min, self._b_axis_max
1888
+
1889
+ def get_axis(self):
1890
+ """
1891
+ Returns the length of the ellipse axes.
1892
+
1893
+ Returns
1894
+ -------
1895
+ tuple
1896
+ (a_length, b_length).
1897
+
1898
+ """
1899
+ return numpy.abs(self._a_axis_max - self._a_axis_min), numpy.abs(self._b_axis_max - self._b_axis_min)
1900
+
1901
+
1902
+ class TwoEllipses(BoundaryShape):
1903
+ """
1904
+ Constructor.
1905
+
1906
+ Parameters
1907
+ ----------
1908
+ a1_axis_min : float, optional
1909
+ The coordinate (signed) of the minimum (left) along the major axis of ellipse 1.
1910
+ a1_axis_max : float, optional
1911
+ The coordinate (signed) of the maximum (right) along the major axis of ellipse 1.
1912
+ b1_axis_min : float, optional
1913
+ TThe coordinate (signed) of the minimum (left) along the minor axis of ellipse 1.
1914
+ b1_axis_max : float, optional
1915
+ The coordinate (signed) of the maximum (right) along the minor axis of ellipse 1.
1916
+ a2_axis_min : float, optional
1917
+ The coordinate (signed) of the minimum (left) along the major axis of ellipse 2.
1918
+ a2_axis_max : float, optional
1919
+ The coordinate (signed) of the maximum (right) along the major axis of ellipse 2.
1920
+ b2_axis_min : float, optional
1921
+ TThe coordinate (signed) of the minimum (left) along the minor axis of ellipse 2.
1922
+ b2_axis_max : float, optional
1923
+ The coordinate (signed) of the maximum (right) along the minor axis of ellipse 2.
1924
+
1925
+ """
1926
+ def __init__(self,
1927
+ a1_axis_min=-10e-6, a1_axis_max=10e-6, b1_axis_min=-5e-6, b1_axis_max=5e-6,
1928
+ a2_axis_min=-20e-6, a2_axis_max=20e-6, b2_axis_min=-8e-6, b2_axis_max=8e-6):
1929
+ super().__init__()
1930
+
1931
+ self._a1_axis_min = a1_axis_min
1932
+ self._a1_axis_max = a1_axis_max
1933
+ self._b1_axis_min = b1_axis_min
1934
+ self._b1_axis_max = b1_axis_max
1935
+ self._a2_axis_min = a2_axis_min
1936
+ self._a2_axis_max = a2_axis_max
1937
+ self._b2_axis_min = b2_axis_min
1938
+ self._b2_axis_max = b2_axis_max
1939
+ # support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
1940
+ self._set_support_text([
1941
+ ("a1_axis_min", "x (width) axis 1 starts (signed) ", "m" ),
1942
+ ("a1_axis_max", "x (width) axis 1 ends (signed) ", "m" ),
1943
+ ("b1_axis_min", "y (length) axis 1 starts (signed) ", "m" ),
1944
+ ("b1_axis_max", "y (length) axis 1 ends (signed) ", "m" ),
1945
+ ("a2_axis_min", "x (width) axis 2 starts (signed) ", "m"),
1946
+ ("a2_axis_max", "x (width) axis 2 ends (signed) ", "m"),
1947
+ ("b2_axis_min", "y (length) axis 2 starts (signed) ", "m"),
1948
+ ("b2_axis_max", "y (length) axis 2 ends (signed) ", "m"),
1949
+ ] )
1950
+
1951
+ def get_boundaries(self):
1952
+ """
1953
+ Return the coordinates of the ellipses.
1954
+
1955
+ Returns
1956
+ -------
1957
+ tuple
1958
+ (a1_axis_min, a1_axis_max, b1_axis_min, b1_axis_max, a2_axis_min, a2_axis_max, b2_axis_min, b2_axis_max).
1959
+
1960
+ """
1961
+ return \
1962
+ self._a1_axis_min, self._a1_axis_max, self._b1_axis_min, self._b1_axis_max, \
1963
+ self._a2_axis_min, self._a2_axis_max, self._b2_axis_min, self._b2_axis_max
1964
+
1965
+ def get_axis(self):
1966
+ """
1967
+ Returns the lengths of the axes of the two ellipses.
1968
+
1969
+ Returns
1970
+ -------
1971
+ tuple
1972
+ (a1_length, b1_length, a2_length, b2_length).
1973
+
1974
+ """
1975
+ return \
1976
+ numpy.abs(self._a1_axis_max - self._a1_axis_min), numpy.abs(self._b1_axis_max - self._b2_axis_min), \
1977
+ numpy.abs(self._a2_axis_max - self._a2_axis_min), numpy.abs(self._b2_axis_max - self._b2_axis_min)
1978
+
1979
+
1980
+ class Circle(BoundaryShape):
1981
+ """
1982
+ Constructor.
1983
+
1984
+ Parameters
1985
+ ----------
1986
+ radius : float
1987
+ The radius of the circle.
1988
+ x_center : float
1989
+ The x coordinate of the center of the circle.
1990
+ y_center : float
1991
+ The y coordinate of the center of the circle.
1992
+
1993
+ """
1994
+ def __init__(self,radius=50e-6,x_center=0.0,y_center=0.0):
1995
+ super().__init__()
1996
+
1997
+ self._radius = radius
1998
+ self._x_center = x_center
1999
+ self._y_center = y_center
2000
+ # support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
2001
+ self._set_support_text([
2002
+ ("radius" , "radius ", "m" ),
2003
+ ("x_center" , "x center (signed) ", "m" ),
2004
+ ("y_center" , "y center (signed) ", "m" ),
2005
+ ] )
2006
+
2007
+ def get_boundaries(self):
2008
+ """
2009
+ Returns the circle parameters.
2010
+
2011
+ Returns
2012
+ -------
2013
+ tuple
2014
+ (radius, x_center, y_center).
2015
+
2016
+ """
2017
+ return self._radius, self._x_center, self._y_center
2018
+
2019
+ def set_boundaries(self, radius=1.0, x_center=0.0, y_center=0.0):
2020
+ """
2021
+ Sets the circle parameters.
2022
+
2023
+ Parameters
2024
+ ----------
2025
+ radius : float
2026
+ The radius of the circle.
2027
+ x_center : float
2028
+ The x coordinate of the center of the circle.
2029
+ y_center : float
2030
+ The y coordinate of the center of the circle.
2031
+
2032
+ """
2033
+ self._radius = radius
2034
+ self._x_center = x_center
2035
+ self._y_center = y_center
2036
+
2037
+ def get_radius(self):
2038
+ """
2039
+ Returns the radius of the circle.
2040
+
2041
+ Returns
2042
+ -------
2043
+ float
2044
+
2045
+ """
2046
+ return self._radius
2047
+
2048
+ def get_center(self):
2049
+ """
2050
+ Returns the coordinates of the circle.
2051
+
2052
+ Returns
2053
+ -------
2054
+ list
2055
+ [x_center, y_center]
2056
+
2057
+ """
2058
+ return [self._x_center,self._y_center]
2059
+
2060
+ class Polygon(BoundaryShape):
2061
+ """
2062
+ Constructor.
2063
+
2064
+ Parameters
2065
+ ----------
2066
+ x : list, optional
2067
+ A list with the X coordinates of the patch vertices.
2068
+ y : list, optional
2069
+ A list with the Y coordinates of the patch vertices.
2070
+
2071
+ """
2072
+ def __init__(self,x=[],y=[]):
2073
+ super().__init__()
2074
+
2075
+ self._x = numpy.array(x)
2076
+ self._y = numpy.array(y)
2077
+ # support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
2078
+ self._set_support_text([
2079
+ ("x" , "x vertices ", "m" ),
2080
+ ("y" , "y vertices ", "m" ),
2081
+ ] )
2082
+
2083
+ def get_boundaries(self):
2084
+ """
2085
+ Returns the coordinates of the patch vertices.
2086
+
2087
+ Returns
2088
+ -------
2089
+ tuple
2090
+ (list_of_x_coordinates, list_of_y_coordinates).
2091
+
2092
+ """
2093
+ return self._x, self._y
2094
+
2095
+ def set_boundaries(self, x, y):
2096
+ """
2097
+ Sets the coordinates of the patch vertices.
2098
+
2099
+ Parameters
2100
+ ----------
2101
+ x : list
2102
+ A list with the X coordinates of the patch vertices.
2103
+ y : list
2104
+ A list with the Y coordinates of the patch vertices.
2105
+
2106
+ """
2107
+ self._x = numpy.array(x)
2108
+ self._y = numpy.array(y)
2109
+
2110
+ def get_number_of_vertices(self):
2111
+ """
2112
+ Returns the number of vertices.
2113
+
2114
+ Returns
2115
+ -------
2116
+ int
2117
+
2118
+ """
2119
+ n = numpy.array(self._x).size
2120
+ if (numpy.abs(self._x[0] - self._x[-1]) < 1e-10) and (numpy.abs(self._y[0] - self._y[-1]) < 1e-10):
2121
+ # print(">>>>> same first and last point")
2122
+ n -= 1
2123
+ return n
2124
+
2125
+ def get_polygon(self):
2126
+ """
2127
+ Returns the vertices arranges as a polugon.
2128
+
2129
+ Returns
2130
+ -------
2131
+ list
2132
+ [[x0,y0], [x1,y1], ...]
2133
+
2134
+ """
2135
+ polygon = []
2136
+ for i in range(self.get_number_of_vertices()):
2137
+ polygon.append([self._x[i], self._y[i]])
2138
+
2139
+ return polygon
2140
+
2141
+ def check_inside_vector(self, x0, y0):
2142
+ """
2143
+ Checks if a set of points are inside the patch (closed as polygon).
2144
+
2145
+ Parameters
2146
+ ----------
2147
+ x0 : numpy array
2148
+ The X coordinates of the points to check.
2149
+ y0 : numpy array
2150
+ The Y coordinates of the points to check.
2151
+
2152
+ Returns
2153
+ -------
2154
+ numpy array
2155
+ 0=No, 1=Yes (inside).
2156
+
2157
+ References
2158
+ ----------
2159
+ https://stackoverflow.com/questions/36399381/whats-the-fastest-way-of-checking-if-a-point-is-inside-a-polygon-in-python
2160
+
2161
+ """
2162
+ # see https://stackoverflow.com/questions/36399381/whats-the-fastest-way-of-checking-if-a-point-is-inside-a-polygon-in-python
2163
+ poly = self.get_polygon()
2164
+ n = len(poly)
2165
+ x = numpy.array(x0)
2166
+ y = numpy.array(y0)
2167
+
2168
+ inside = numpy.zeros(x.size, numpy.bool_)
2169
+ p2x = 0.0
2170
+ p2y = 0.0
2171
+ xints = 0.0
2172
+ p1x, p1y = poly[0]
2173
+
2174
+ for i in range(n + 1):
2175
+ p2x, p2y = poly[i % n]
2176
+
2177
+ idx = numpy.nonzero((y > min(p1y, p2y)) & (y <= max(p1y, p2y)) & (x <= max(p1x, p2x)))[0]
2178
+ if len(idx > 0): # added intuitively by srio TODO: make some tests to compare with self.check_insize
2179
+ if p1y != p2y:
2180
+ xints = (y[idx] - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
2181
+ if p1x == p2x:
2182
+ inside[idx] = ~inside[idx]
2183
+ else:
2184
+ idxx = idx[x[idx] <= xints]
2185
+ inside[idxx] = ~inside[idxx]
2186
+
2187
+ p1x, p1y = p2x, p2y
2188
+ return inside
2189
+
2190
+ def check_inside(self, x, y):
2191
+ """
2192
+ Checks if a set of points are inside the patch (closed as polygon).
2193
+
2194
+ Parameters
2195
+ ----------
2196
+ x0 : list
2197
+ The X coordinates of the points to check.
2198
+ y0 : list
2199
+ The Y coordinates of the points to check.
2200
+
2201
+ Returns
2202
+ -------
2203
+ numpy array
2204
+ 0=No, 1=Yes (inside).
2205
+
2206
+ References
2207
+ ----------
2208
+ https://stackoverflow.com/questions/36399381/whats-the-fastest-way-of-checking-if-a-point-is-inside-a-polygon-in-python
2209
+
2210
+ """
2211
+ return [self.check_inside_one_point(xi, yi) for xi, yi in zip(x, y)]
2212
+
2213
+ def check_inside_one_point(self, x0, y0):
2214
+ """
2215
+ Checks if a single point is inside the patch (closed as polygon).
2216
+
2217
+ Parameters
2218
+ ----------
2219
+ x0 : float
2220
+ The X coordinate pf the point to check.
2221
+ y0 : float
2222
+ The Y coordinate pf the point to check.
2223
+
2224
+ Returns
2225
+ -------
2226
+ boolean
2227
+
2228
+ References
2229
+ ----------
2230
+ https://stackoverflow.com/questions/36399381/whats-the-fastest-way-of-checking-if-a-point-is-inside-a-polygon-in-python
2231
+
2232
+ """
2233
+ # see https://stackoverflow.com/questions/36399381/whats-the-fastest-way-of-checking-if-a-point-is-inside-a-polygon-in-python
2234
+ poly = self.get_polygon()
2235
+ x = x0
2236
+ y = y0
2237
+ n = len(poly)
2238
+ inside = False
2239
+ p2x = 0.0
2240
+ p2y = 0.0
2241
+ xints = 0.0
2242
+ p1x, p1y = poly[0]
2243
+ for i in range(n + 1):
2244
+ p2x, p2y = poly[i % n]
2245
+ if y > min(p1y, p2y):
2246
+ if y <= max(p1y, p2y):
2247
+ if x <= max(p1x, p2x):
2248
+ if p1y != p2y:
2249
+ xints = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
2250
+ if p1x == p2x or x <= xints:
2251
+ inside = not inside
2252
+ p1x, p1y = p2x, p2y
2253
+
2254
+ return inside
2255
+
2256
+ def check_outside(self, x0, y0):
2257
+ """
2258
+ Checks if a set of points are outside the patch (closed as polygon).
2259
+
2260
+ Parameters
2261
+ ----------
2262
+ x0 : list
2263
+ The X coordinates of the points to check.
2264
+ y0 : list
2265
+ The Y coordinates of the points to check.
2266
+
2267
+ Returns
2268
+ -------
2269
+ numpy array
2270
+ 0=No, 1=Yes (outside).
2271
+
2272
+ References
2273
+ ----------
2274
+ https://stackoverflow.com/questions/36399381/whats-the-fastest-way-of-checking-if-a-point-is-inside-a-polygon-in-python
2275
+
2276
+ """
2277
+ inside = self.check_inside(x0, y0)
2278
+ if isinstance(inside, list):
2279
+ out = []
2280
+ for item in inside:
2281
+ out.append(not(item))
2282
+ else:
2283
+ out = not(inside)
2284
+
2285
+ return out
2286
+
2287
+
2288
+ class MultiplePatch(BoundaryShape):
2289
+ """
2290
+ Constructor.
2291
+
2292
+ Parameters
2293
+ ----------
2294
+ patch_list : list
2295
+ A list of patches (each one can be a Circle, Rectangle, Polygon, etc.)
2296
+
2297
+ """
2298
+ def __init__(self, patch_list=None):
2299
+ super().__init__()
2300
+
2301
+ if patch_list is None:
2302
+ self._patch_list = []
2303
+ else:
2304
+ self._patch_list = patch_list
2305
+ # support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
2306
+ self._set_support_text([
2307
+ ("patch_list", "Multiple Patch", ""),
2308
+ ])
2309
+
2310
+
2311
+ # overwrites the SynedObject method for dealing with list
2312
+ def to_dictionary(self):
2313
+ """
2314
+ Gets the dictionary with the multiple patch parameters.
2315
+
2316
+ Returns
2317
+ -------
2318
+ dict
2319
+
2320
+ """
2321
+ dict_to_save = OrderedDict()
2322
+ dict_to_save.update({"CLASS_NAME":self.__class__.__name__})
2323
+
2324
+ dict_to_save["patch_list"] = [el.to_dictionary() for el in self._patch_list]
2325
+
2326
+ return dict_to_save
2327
+
2328
+
2329
+ def reset(self):
2330
+ """
2331
+ Removes all existing patches.
2332
+ """
2333
+ self._patch_list = []
2334
+
2335
+ def get_number_of_patches(self):
2336
+ """
2337
+ Returns the number of stored patches.
2338
+
2339
+ Returns
2340
+ -------
2341
+ int
2342
+
2343
+ """
2344
+ return len(self._patch_list)
2345
+
2346
+ def get_boundaries(self):
2347
+ """
2348
+ Returns a list with the concatenated boundaries of the sotred patches.
2349
+
2350
+ Returns
2351
+ -------
2352
+ list
2353
+
2354
+ """
2355
+ boundaries_list = []
2356
+ for i in range(self.get_number_of_patches()):
2357
+ boundaries_list.extend(list(self._patch_list[i].get_boundaries()))
2358
+ return tuple(boundaries_list)
2359
+
2360
+ def append_patch(self,patch=BoundaryShape()):
2361
+ """
2362
+ Append a patch.
2363
+
2364
+ Parameters
2365
+ ----------
2366
+ patch : instance of Rectangle, Circle, etc.
2367
+
2368
+ """
2369
+ self._patch_list.append(patch)
2370
+
2371
+ def append_rectangle(self,x_left=-0.010,x_right=0.010,y_bottom=-0.020,y_top=0.020):
2372
+ """
2373
+ Appends a rectangle.
2374
+
2375
+ Parameters
2376
+ ----------
2377
+ x_left : float, optional
2378
+ The coordinate (signed) of the minimum (left) along the X axis.
2379
+ x_right : float, optional
2380
+ The coordinate (signed) of the maximum (right) along the X axis.
2381
+ y_bottom : float, optional
2382
+ The coordinate (signed) of the minimum (left) along the Y axis.
2383
+ y_top : float, optional
2384
+ The coordinate (signed) of the maximum (right) along the Y axis.
2385
+
2386
+
2387
+ """
2388
+ self.append_patch(Rectangle(x_left=x_left, x_right=x_right, y_bottom=y_bottom, y_top=y_top))
2389
+
2390
+ def append_circle(self,radius, x_center=0.0, y_center=0.0):
2391
+ """
2392
+ Appends a circle.
2393
+
2394
+ Parameters
2395
+ ----------
2396
+ radius : float
2397
+ The radius of the circle.
2398
+ x_center : float
2399
+ The x coordinate of the center of the circle.
2400
+ y_center : float
2401
+ The y coordinate of the center of the circle.
2402
+
2403
+ """
2404
+ self.append_patch(Circle(radius, x_center=x_center, y_center=y_center))
2405
+
2406
+ def append_ellipse(self,a_axis_min, a_axis_max, b_axis_min, b_axis_max):
2407
+ """
2408
+ Appends an ellipse.
2409
+
2410
+ Parameters
2411
+ ----------
2412
+ a_axis_min : float, optional
2413
+ The coordinate (signed) of the minimum (left) along the major axis.
2414
+ a_axis_max : float, optional
2415
+ The coordinate (signed) of the maximum (right) along the major axis.
2416
+ b_axis_min : float, optional
2417
+ TThe coordinate (signed) of the minimum (left) along the minor axis.
2418
+ b_axis_max : float, optional
2419
+ The coordinate (signed) of the maximum (right) along the minor axis.
2420
+
2421
+ """
2422
+ self.append_patch(Ellipse(a_axis_min, a_axis_max, b_axis_min, b_axis_max))
2423
+
2424
+ def append_polygon(self,x, y):
2425
+ """
2426
+ Appends a polygon.
2427
+
2428
+ Parameters
2429
+ ----------
2430
+ x : list
2431
+ The polygon X coordinates.
2432
+ y : list
2433
+ The polygon Y coordinates.
2434
+
2435
+ Returns
2436
+ -------
2437
+
2438
+ """
2439
+ self.append_patch(Polygon(x, y))
2440
+
2441
+ def get_patches(self):
2442
+ """
2443
+ Returns a list with the patches.
2444
+
2445
+ Returns
2446
+ -------
2447
+ list
2448
+
2449
+ """
2450
+ return self._patch_list
2451
+
2452
+ def get_patch(self, index):
2453
+ """
2454
+ Returns the patch corresponding to a given index.
2455
+
2456
+ Parameters
2457
+ ----------
2458
+ index : int
2459
+ The index of the wanted patch.
2460
+
2461
+ Returns
2462
+ -------
2463
+ instance of BoundaryShape (Circle, Rectangle, etc.).
2464
+
2465
+ """
2466
+ return self.get_patches()[index]
2467
+
2468
+ def get_name_of_patch(self,index):
2469
+ """
2470
+ Returns the name of the patch with a given index.
2471
+
2472
+ Parameters
2473
+ ----------
2474
+ index : int
2475
+ The index of the wanted patch.
2476
+
2477
+ Returns
2478
+ -------
2479
+ str
2480
+
2481
+ """
2482
+ return self._patch_list[index].__class__.__name__
2483
+
2484
+ class DoubleRectangle(MultiplePatch):
2485
+ """
2486
+ Constructor.
2487
+
2488
+ Parameters
2489
+ ----------
2490
+ x_left1 : float, optional
2491
+ The coordinate (signed) of the minimum (left) along the X axis of rectangle 1.
2492
+ x_right1 : float, optional
2493
+ The coordinate (signed) of the maximum (right) along the X axis of rectangle 1.
2494
+ y_bottom1 : float, optional
2495
+ The coordinate (signed) of the minimum (left) along the Y axis of rectangle 1.
2496
+ y_top1 : float, optional
2497
+ The coordinate (signed) of the maximum (right) along the Y axis of rectangle 1.
2498
+ x_left2 : float, optional
2499
+ The coordinate (signed) of the minimum (left) along the X axis of rectangle 2.
2500
+ x_right2 : float, optional
2501
+ The coordinate (signed) of the maximum (right) along the X axis of rectangle 2.
2502
+ y_bottom2 : float, optional
2503
+ The coordinate (signed) of the minimum (left) along the Y axis of rectangle 2.
2504
+ y_top2 : float, optional
2505
+ The coordinate (signed) of the maximum (right) along the Y axis of rectangle 2.
2506
+ """
2507
+ def __init__(self, x_left1=-0.010, x_right1=0.0, y_bottom1=-0.020, y_top1=0.0,
2508
+ x_left2=-0.010, x_right2=0.010, y_bottom2=-0.001, y_top2=0.020):
2509
+ super().__init__()
2510
+ self.reset()
2511
+ self.append_patch(Rectangle(x_left=x_left1, x_right=x_right1, y_bottom=y_bottom1, y_top=y_top1))
2512
+ self.append_patch(Rectangle(x_left=x_left2, x_right=x_right2, y_bottom=y_bottom2, y_top=y_top2))
2513
+
2514
+ # support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
2515
+ self._set_support_text([
2516
+ ("x_left1" , "x (width) minimum (signed) ", "m" ),
2517
+ ("x_right1" , "x (width) maximum (signed) ", "m" ),
2518
+ ("y_bottom1" , "y (length) minimum (signed) ", "m" ),
2519
+ ("y_top1" , "y (length) maximum (signed) ", "m" ),
2520
+ ("x_left2" , "x (width) minimum (signed) ", "m" ),
2521
+ ("x_right2" , "x (width) maximum (signed) ", "m" ),
2522
+ ("y_bottom2" , "y (length) minimum (signed) ", "m" ),
2523
+ ("y_top2" , "y (length) maximum (signed) ", "m" ),
2524
+ ] )
2525
+
2526
+ def set_boundaries(self,x_left1=-0.010, x_right1=0.0, y_bottom1=-0.020, y_top1=0.0,
2527
+ x_left2=-0.010, x_right2=0.010, y_bottom2=-0.001, y_top2=0.020):
2528
+ self._patch_list[0].set_boundaries(x_left1, x_right1, y_bottom1, y_top1)
2529
+ self._patch_list[1].set_boundaries(x_left2, x_right2, y_bottom2, y_top2)
2530
+
2531
+ class DoubleEllipse(MultiplePatch):
2532
+ """
2533
+ Constructor.
2534
+
2535
+ Parameters
2536
+ ----------
2537
+ a_axis_min1 : float, optional
2538
+ The coordinate (signed) of the minimum (left) along the major axis of ellipse 1.
2539
+ a_axis_max1 : float, optional
2540
+ The coordinate (signed) of the maximum (right) along the major axis of ellipse 1.
2541
+ b_axis_min1 : float, optional
2542
+ The coordinate (signed) of the minimum (left) along the minor axis of ellipse 1.
2543
+ b_axis_max1 : float, optional
2544
+ The coordinate (signed) of the maximum (right) along the minor axis of ellipse 1.
2545
+ a_axis_min2 : float, optional
2546
+ The coordinate (signed) of the minimum (left) along the major axis of ellipse 2.
2547
+ a_axis_max2 : float, optional
2548
+ The coordinate (signed) of the maximum (right) along the major axis of ellipse 2.
2549
+ b_axis_min2 : float, optional
2550
+ The coordinate (signed) of the minimum (left) along the minor axis of ellipse 2.
2551
+ b_axis_max2 : float, optional
2552
+ The coordinate (signed) of the maximum (right) along the minor axis of ellipse 2.
2553
+
2554
+ """
2555
+ def __init__(self, a_axis_min1=-0.010, a_axis_max1=0.0, b_axis_min1=-0.020, b_axis_max1=0.0,
2556
+ a_axis_min2=-0.010, a_axis_max2=0.010, b_axis_min2=-0.001, b_axis_max2=0.020):
2557
+
2558
+ super().__init__()
2559
+ self.reset()
2560
+ self.append_patch(Ellipse(a_axis_min1, a_axis_max1, b_axis_min1, b_axis_max1))
2561
+ self.append_patch(Ellipse(a_axis_min2, a_axis_max2, b_axis_min2, b_axis_max2))
2562
+ self._set_support_text([
2563
+ ("a_axis_min1" , "x (width) axis starts (signed) ", "m" ),
2564
+ ("a_axis_max1" , "x (width) axis ends (signed) ", "m" ),
2565
+ ("b_axis_min1" , "y (length) axis starts (signed) ", "m" ),
2566
+ ("b_axis_max1" , "y (length) axis ends (signed) ", "m" ),
2567
+ ("a_axis_min2" , "x (width) axis starts (signed) ", "m" ),
2568
+ ("a_axis_max2" , "x (width) axis ends (signed) ", "m" ),
2569
+ ("b_axis_min2" , "y (length) axis starts (signed) ", "m" ),
2570
+ ("b_axis_max2" , "y (length) axis ends (signed) ", "m" ),
2571
+ ] )
2572
+ def set_boundaries(self,a_axis_min1=-0.010, a_axis_max1=0.0, b_axis_min1=-0.020, b_axis_max1=0.0,
2573
+ a_axis_min2=-0.010, a_axis_max2=0.010, b_axis_min2=-0.001, b_axis_max2=0.020):
2574
+ """
2575
+ Sets the coordinates of the ellipses.
2576
+
2577
+ Parameters
2578
+ ----------
2579
+ a_axis_min1 : float, optional
2580
+ The coordinate (signed) of the minimum (left) along the major axis of ellipse 1.
2581
+ a_axis_max1 : float, optional
2582
+ The coordinate (signed) of the maximum (right) along the major axis of ellipse 1.
2583
+ b_axis_min1 : float, optional
2584
+ The coordinate (signed) of the minimum (left) along the minor axis of ellipse 1.
2585
+ b_axis_max1 : float, optional
2586
+ The coordinate (signed) of the maximum (right) along the minor axis of ellipse 1.
2587
+ a_axis_min2 : float, optional
2588
+ The coordinate (signed) of the minimum (left) along the major axis of ellipse 2.
2589
+ a_axis_max2 : float, optional
2590
+ The coordinate (signed) of the maximum (right) along the major axis of ellipse 2.
2591
+ b_axis_min2 : float, optional
2592
+ The coordinate (signed) of the minimum (left) along the minor axis of ellipse 2.
2593
+ b_axis_max2 : float, optional
2594
+ The coordinate (signed) of the maximum (right) along the minor axis of ellipse 2.
2595
+
2596
+ """
2597
+ self._patch_list[0].set_boundaries(a_axis_min1,a_axis_max1,b_axis_min1,b_axis_max1)
2598
+ self._patch_list[1].set_boundaries(a_axis_min2,a_axis_max2,b_axis_min2,b_axis_max2)
2599
+
2600
+ class DoubleCircle(MultiplePatch):
2601
+ """
2602
+ Constructor.
2603
+
2604
+ Parameters
2605
+ ----------
2606
+ radius1 : float
2607
+ The radius of the circle 1.
2608
+ x_center1 : float
2609
+ The x coordinate of the center of the circle 1.
2610
+ y_center1 : float
2611
+ The y coordinate of the center of the circle 1.
2612
+ radius2 : float
2613
+ The radius of the circle 2.
2614
+ x_center2 : float
2615
+ The x coordinate of the center of the circle 2.
2616
+ y_center2 : float
2617
+ The y coordinate of the center of the circle 2.
2618
+
2619
+ """
2620
+ def __init__(self, radius1=50e-6,x_center1=0.0,y_center1=0.0,
2621
+ radius2=50e-6,x_center2=100e-6,y_center2=100e-6):
2622
+ super().__init__()
2623
+ self.reset()
2624
+ self.append_patch(Circle(radius1,x_center1,y_center1))
2625
+ self.append_patch(Circle(radius2,x_center2,y_center2))
2626
+ # support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
2627
+ self._set_support_text([
2628
+ ("radius1" , "radius ", "m" ),
2629
+ ("x_center1" , "x center (signed) ", "m" ),
2630
+ ("y_center1" , "y center (signed) ", "m" ),
2631
+ ("radius2" , "radius ", "m" ),
2632
+ ("x_center2" , "x center (signed) ", "m" ),
2633
+ ("y_center2" , "y center (signed) ", "m" ),
2634
+ ] )
2635
+ def set_boundaries(self,radius1=50e-6,x_center1=0.0,y_center1=0.0,
2636
+ radius2=50e-6,x_center2=100e-6,y_center2=100e-6):
2637
+ """
2638
+ Sets the coordinates of the circles.
2639
+
2640
+ Parameters
2641
+ ----------
2642
+ radius1 : float
2643
+ The radius of the circle 1.
2644
+ x_center1 : float
2645
+ The x coordinate of the center of the circle 1.
2646
+ y_center1 : float
2647
+ The y coordinate of the center of the circle 1.
2648
+ radius2 : float
2649
+ The radius of the circle 2.
2650
+ x_center2 : float
2651
+ The x coordinate of the center of the circle 2.
2652
+ y_center2 : float
2653
+ The y coordinate of the center of the circle 2.
2654
+
2655
+ """
2656
+ self._patch_list[0].set_boundaries(radius1,x_center1,y_center1)
2657
+ self._patch_list[1].set_boundaries(radius2,x_center2,y_center2)
2658
+
2659
+
2660
+
2661
+ if __name__=="__main__":
2662
+
2663
+
2664
+
2665
+ p = 20
2666
+ q = 10
2667
+ theta_graz = 0.003
2668
+
2669
+ #
2670
+ # sphere
2671
+ #
2672
+ # sph = Sphere()
2673
+ sph = Sphere.create_sphere_from_p_q(10, 10, 0.021)
2674
+ print(sph.info())
2675
+
2676
+ #
2677
+ # Ellipsoid
2678
+ #
2679
+ ell = Ellipsoid()
2680
+ ell.initialize_from_p_q(p, q, theta_graz)
2681
+
2682
+
2683
+ #
2684
+ # toroid
2685
+ #
2686
+ par = Toroid.create_toroid_from_p_q(p=p, q=q, grazing_angle=theta_graz)
2687
+ print("inputs p, q, theta_graz: ", p, q, theta_graz)
2688
+ radii = par.get_radii()
2689
+ print("toroid radii: ", radii )
2690
+ R = 2 / numpy.sin(theta_graz) * p * q / (p + q)
2691
+ r = 2 * numpy.sin(theta_graz) * p * q / (p + q)
2692
+ assert ((radii[0] - R) < 1e-10 )
2693
+ assert ((radii[0] - r) < 1e-10 )
2694
+ print(par.info())
2695
+
2696
+ #
2697
+ # paraboloid
2698
+ #
2699
+ at_infinity = Side.SOURCE
2700
+
2701
+ par = Paraboloid.create_paraboloid_from_p_q(p=p, q=q, grazing_angle=theta_graz, at_infinity=at_infinity, convexity=Convexity.UPWARD)
2702
+ print("inputs p, q, theta_graz: ", p, q, theta_graz, at_infinity)
2703
+ print ("parabola p or q: ",par.get_pole_to_focus())
2704
+ print("parabola par: ", par.get_parabola_parameter())
2705
+ print("parabola grazing_angle: ", par.get_grazing_angle())
2706
+ if par.get_at_infinity() == Side.SOURCE:
2707
+ assert (numpy.abs(q - par.get_pole_to_focus()) < 1e-10 )
2708
+ else:
2709
+ assert (numpy.abs(p - par.get_pole_to_focus()) < 1e-10)
2710
+ assert (numpy.abs(theta_graz - par.get_grazing_angle()) < 1e-10)
2711
+ print(par.info())
2712
+
2713
+ #
2714
+ # parabolic cylinder: TODO: check that the info is not good for double inheritage
2715
+ #
2716
+ a = Cylinder()
2717
+ print(a.info())
2718
+ print(a.to_dictionary())
2719
+
2720
+ parC = ParabolicCylinder(par, a)
2721
+ print(parC.info())
2722
+
2723
+
2724
+
2725
+ #
2726
+ # some other checks...
2727
+ #
2728
+
2729
+ # conic coeffs.
2730
+ ccc = Conic()
2731
+ print(ccc.get_conic_coefficients())
2732
+ print(ccc.info())
2733
+ ccc.to_json("tmp.json")
2734
+ from syned.util.json_tools import load_from_json_file
2735
+ tmp = load_from_json_file("tmp.json")
2736
+ print("returned class: ",type(tmp))
2737
+ print(ccc.to_dictionary())
2738
+ print(tmp.to_dictionary())
2739
+ # from deepdiff import DeepDiff # use this because == gives an error
2740
+ # assert (len(DeepDiff(ccc.to_dictionary(), tmp.to_dictionary())) == 0)
2741
+
2742
+
2743
+
2744
+
2745
+ # circle
2746
+ circle = Circle(3.0)
2747
+ print(circle.get_radius(),circle.get_center())
2748
+ print(circle.get_boundaries())
2749
+
2750
+
2751
+
2752
+ # patches
2753
+ patches = MultiplePatch()
2754
+
2755
+ patches.append_rectangle(-0.02,-0.01,-0.001,0.001)
2756
+ patches.append_rectangle(0.01,0.02,-0.001,0.001)
2757
+ patches.append_polygon([-0.02,-0.02,0.02,0.02], [-0.02,0.02,0.02,-0.02])
2758
+
2759
+ print(patches.get_number_of_patches(),patches.get_boundaries())
2760
+ for patch in patches.get_patches():
2761
+ print(patch.info())
2762
+ print("Patch 0 is: ",patches.get_name_of_patch(0))
2763
+ print("Patch 1 is: ",patches.get_name_of_patch(1))
2764
+ print(patches.get_boundaries())
2765
+
2766
+
2767
+ # double rectangle
2768
+ double_rectangle = DoubleRectangle()
2769
+ double_rectangle.set_boundaries(-0.02,-0.01,-0.001,0.001,0.01,0.02,-0.001,0.001)
2770
+ print("Rectangle 0 is: ",double_rectangle.get_name_of_patch(0))
2771
+ print("Rectangle 1 is: ",double_rectangle.get_name_of_patch(1))
2772
+ print(double_rectangle.get_boundaries())
2773
+
2774
+ # polygon
2775
+ angle = numpy.linspace(0, 2 * numpy.pi, 5)
2776
+ x = numpy.sin(angle) + 0.5
2777
+ y = numpy.cos(angle) + 0.5
2778
+ poly = Polygon(x=x, y=y)
2779
+ print(poly.info())
2780
+ print("vertices: ", poly.get_number_of_vertices())
2781
+ if False:
2782
+ from srxraylib.plot.gol import plot,set_qt
2783
+ set_qt()
2784
+ plot(x,y)
2785
+ print(poly.get_polygon())
2786
+ print("inside? : ", poly.check_inside([0.5,0],[0.5,5]))
2787
+ print("outside? : ", poly.check_outside([0.5, 0], [0.5, 5]))
2788
+
2789
+
2790
+ # multiple patches
2791
+ patches = MultiplePatch()
2792
+ patches.append_polygon(numpy.array([-1,-1,1,1]),numpy.array([-1,1,1,-1]))
2793
+ x = [-0.00166557, 0.12180897, -0.11252591, -0.12274196, 0.00586896, -0.12999401, -0.12552975, -0.0377907, -0.01094828, -0.13689862]
2794
+ y = [ 0.16279557, -0.00085991, 0.01349174, -0.01371226, 0.01480265, -0.04810334, 0.07198068, -0.03725407, 0.13301309, -0.00296213]
2795
+ x = numpy.array(x)
2796
+ y = numpy.array(y)
2797
+ patch = patches.get_patch(0)
2798
+ # print(patch.check_inside(x,y))
2799
+ for i in range(x.size):
2800
+ tmp = patch.check_inside_one_point(x[i], y[i])
2801
+ print(x[i], y[i], tmp )
2802
+ print("inside? : ", patch.check_inside(x, y), type(patch.check_inside(x, y)))
2803
+ print("inside? : ", patch.check_inside_vector(x, y), type(patch.check_inside_vector(x, y)))