├── .gitignore
├── LICENSE.md
├── MIT 16.853 Intro to Link Budgets.pdf
├── OLBtools
├── Z136.py
├── __init__.py
├── beams.py
├── reporting.py
└── scintillation.py
├── README.md
├── Z136.1.verification.py
├── example_1.py
├── example_2.py
├── example_3.py
├── poetry.lock
└── pyproject.toml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | bin/
10 | build/
11 | develop-eggs/
12 | dist/
13 | eggs/
14 | lib/
15 | lib64/
16 | parts/
17 | sdist/
18 | var/
19 | *.egg-info/
20 | .installed.cfg
21 | *.egg
22 |
23 | # Installer logs
24 | pip-log.txt
25 | pip-delete-this-directory.txt
26 |
27 | # Unit test / coverage reports
28 | .tox/
29 | .coverage
30 | .cache
31 | nosetests.xml
32 | coverage.xml
33 |
34 | # Translations
35 | *.mo
36 |
37 | # Mr Developer
38 | .mr.developer.cfg
39 | .project
40 | .pydevproject
41 |
42 | # Rope
43 | .ropeproject
44 |
45 | # Django stuff:
46 | *.log
47 | *.pot
48 |
49 | # Sphinx documentation
50 | docs/_build/
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # GNU LESSER GENERAL PUBLIC LICENSE
2 |
3 | Version 3, 29 June 2007
4 |
5 | Copyright © 2007 Free Software Foundation, Inc.
6 |
7 | Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
8 |
9 | This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below.
10 |
11 | ## 0. Additional Definitions.
12 |
13 | As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License.
14 |
15 | "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below.
16 |
17 | An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library.
18 |
19 | A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version".
20 |
21 | The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version.
22 |
23 | The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work.
24 |
25 | ## 1. Exception to Section 3 of the GNU GPL.
26 |
27 | You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL.
28 |
29 | ## 2. Conveying Modified Versions.
30 |
31 | If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version:
32 |
33 | 1. under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or
34 | 2. under the GNU GPL, with none of the additional permissions of this License applicable to that copy.
35 |
36 | ## 3. Object Code Incorporating Material from Library Header Files.
37 |
38 | The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following:
39 |
40 | 1. Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License.
41 | 2. Accompany the object code with a copy of the GNU GPL and this license document.
42 |
43 | ## 4. Combined Works.
44 |
45 | You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following:
46 |
47 | 1. Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License.
48 | 2. Accompany the Combined Work with a copy of the GNU GPL and this license document.
49 | 3. For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document.
50 | 4. Do one of the following:
51 | 1. Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.
52 | 2. Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version.
53 | 5. Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4.4.1., the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4.4.2., you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.)
54 |
55 | ## 5. Combined Libraries.
56 |
57 | You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following:
58 |
59 | 1. Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License.
60 | 2. Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work.
61 |
62 | ## 6. Revised Versions of the GNU Lesser General Public License.
63 |
64 | The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
65 |
66 | Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation.
67 |
68 | If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library.
69 |
--------------------------------------------------------------------------------
/MIT 16.853 Intro to Link Budgets.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MIT-STARLab/Optical-Link-Budget/6da100e88fa34084341a655f28dff41c9b5276ea/MIT 16.853 Intro to Link Budgets.pdf
--------------------------------------------------------------------------------
/OLBtools/Z136.py:
--------------------------------------------------------------------------------
1 | from . import *
2 | import numpy as np
3 |
4 | import warnings
5 | warnings.warn("This library does not come with any garanties of accuracy, please refer to Z136.1 directly for any safety-related or important determinations")
6 |
7 |
8 | def C_A(wavelength:np.ndarray):
9 | C_a = 10**(0.002*(wavelength*1e9-700))
10 | return np.maximum(np.minimum(C_a,5.0),1.0)
11 |
12 | def C_B(wavelength:np.ndarray):
13 | C_b = 10**(0.02*(wavelength*1e9-450))
14 | return np.maximum(C_b,1.0)
15 |
16 | def C_C(wavelength:np.ndarray):
17 | C_c_1 = 10**(0.018*(wavelength*1e9-1150))
18 | C_c_2 = 8+10**(0.040*(wavelength*1e9-1250))
19 | mask = (wavelength < 1250e-9)
20 | C_c = C_c_2*(1-mask) + np.minimum(C_c_1,C_c_2)*mask
21 | return np.maximum(C_c,1.0)
22 |
23 | def T_1(wavelength:np.ndarray):
24 | T_1 = 10*10**(0.02*(wavelength*1e9-450))
25 | return T_1
26 |
27 | def K_lambda(wavelength:np.ndarray):
28 | K_l = 10**(0.01*(1400-wavelength*1e9))
29 | return K_l
30 |
31 |
32 |
33 | def ocular_MPE_point_source_NIR(wavelength:np.ndarray=1064e-9,exposure_time:np.ndarray=30000):
34 | '''Based on Z136.1 table 5c, return MPE in W.m-2
35 | Valid between 700 and 1400 nm'''
36 | retina_MPE = 0
37 | cornea_MPE = 0
38 |
39 | C_a = C_A(wavelength)
40 | C_c = C_C(wavelength)
41 | K_l = K_lambda(wavelength)
42 |
43 | mask_0700_1050 = ( 700e-9 <= wavelength)*(wavelength < 1050e-9)
44 | mask_1050_1200 = (1050e-9 <= wavelength)*(wavelength < 1200e-9)
45 | mask_1200_1400 = (1200e-9 <= wavelength)*(wavelength < 1400e-9)
46 | mask_1050_1400 = mask_1050_1200 + mask_1200_1400
47 |
48 | mask_100fs_10ps = ( 1e-13 <= exposure_time)*(exposure_time < 1e-11)
49 | mask_10ps_5us = ( 1e-11 <= exposure_time)*(exposure_time < 5e-06)
50 | mask_5us_10s = ( 5e-06 <= exposure_time)*(exposure_time < 10)
51 | mask_10ps_13us = ( 1e-11 <= exposure_time)*(exposure_time < 13e-06)
52 | mask_13us_10s = (13e-06 <= exposure_time)*(exposure_time < 10)
53 | mask_10ps_1ms = ( 1e-11 <= exposure_time)*(exposure_time < 1e-03)
54 | mask_1ms_4s = ( 1e-03 <= exposure_time)*(exposure_time < 4)
55 | mask_4s_10s = ( 4 <= exposure_time)*(exposure_time < 10)
56 | mask_above_10s = (exposure_time >= 10)
57 |
58 | retina_MPE += mask_0700_1050 * mask_100fs_10ps * 1e-7
59 | retina_MPE += mask_0700_1050 * mask_10ps_5us * 2e-7*C_a
60 | retina_MPE += mask_0700_1050 * mask_5us_10s * 1.8e-3*C_a*exposure_time**(0.75)
61 |
62 | retina_MPE += mask_1050_1400 * mask_100fs_10ps * 1e-7*C_c
63 | retina_MPE += mask_1050_1400 * mask_10ps_13us * 2e-6*C_c
64 | retina_MPE += mask_1050_1400 * mask_13us_10s * 9e-3*C_c*exposure_time**(0.75)
65 |
66 | retina_MPE /= exposure_time
67 |
68 | retina_MPE += mask_0700_1050 * mask_above_10s * 1e-3*C_a
69 | retina_MPE += mask_1050_1400 * mask_above_10s * 5e-3*C_c
70 |
71 | cornea_MPE += mask_1200_1400 * mask_10ps_1ms * 0.3*K_l
72 | cornea_MPE += mask_1200_1400 * mask_1ms_4s * 0.3*K_l + 0.56*exposure_time**(0.25) - 0.1
73 | cornea_MPE += mask_1200_1400 * mask_4s_10s * 0.3*K_l + 0.7
74 |
75 | cornea_MPE /= exposure_time # convert to W
76 |
77 | cornea_MPE += mask_1200_1400 * mask_above_10s * 0.03*K_l + 0.07
78 |
79 | cornea_MPE *= 1e4 # cm2 to m2
80 | retina_MPE *= 1e4
81 |
82 | MPE = np.minimum( (retina_MPE == 0)*1e20 + retina_MPE, (cornea_MPE == 0)*1e20 + cornea_MPE)
83 | MPE = (MPE < 1e19)*MPE
84 |
85 | return MPE, retina_MPE, cornea_MPE
86 |
87 | def ocular_MPE_point_source_VIS(wavelength:np.ndarray=1064e-9,exposure_time:np.ndarray=30000):
88 | '''Based on Z136.1 table 5b, return MPE in W.m-2
89 | Valid between 400 and 700 nm'''
90 | all_MPE = 0
91 |
92 | C_b = C_B(wavelength)
93 | t_1 = T_1(wavelength)
94 |
95 | mask_400_450 = (400e-9 <= wavelength)*(wavelength < 450e-9)
96 | mask_450_500 = (450e-9 <= wavelength)*(wavelength < 500e-9)
97 | mask_500_700 = (500e-9 <= wavelength)*(wavelength < 700e-9)
98 | mask_400_500 = mask_400_450 + mask_450_500
99 | mask_400_700 = mask_400_500 + mask_500_700
100 |
101 | mask_100fs_10ps = ( 1e-13 <= exposure_time)*(exposure_time < 1e-11)
102 | mask_10ps_5us = ( 1e-11 <= exposure_time)*(exposure_time < 5e-06)
103 | mask_5us_10s = ( 5e-06 <= exposure_time)*(exposure_time < 10)
104 | mask_10s_100s = ( 10 <= exposure_time)*(exposure_time < 100)
105 | mask_10s_T1 = ( 10 <= exposure_time)*(exposure_time < t_1)
106 | mask_T1_100s = ( t_1 <= exposure_time)*(exposure_time < 100)
107 | mask_above_100s = (exposure_time >= 100)
108 |
109 | all_MPE += mask_400_700 * mask_100fs_10ps * 1e-7
110 | all_MPE += mask_400_700 * mask_10ps_5us * 2e-7
111 | all_MPE += mask_400_700 * mask_5us_10s * 1.8e-3*exposure_time**(0.75)
112 |
113 | all_MPE += mask_400_450 * mask_10s_100s * 1e-2
114 | all_MPE += mask_450_500 * mask_T1_100s * 1e-2*C_b
115 |
116 | all_MPE /= exposure_time # convert to W
117 |
118 | all_MPE += mask_400_500 * mask_above_100s * 1e-4*C_b
119 | all_MPE += mask_450_500 * mask_10s_T1 * 1e-3
120 | all_MPE += mask_500_700 * (mask_10s_100s+mask_above_100s) * 1e-3
121 |
122 | all_MPE *= 1e4 # cm2 to m2
123 |
124 | return all_MPE
125 |
126 |
127 | def limiting_aperture_VIS_NIR(wavelength:np.ndarray=1064e-9,exposure_time:np.ndarray=30000):
128 | mask_0400_1200 = ( 400e-9 <= wavelength)*(wavelength < 1200e-9)
129 | mask_1200_1400 = (1200e-9 <= wavelength)*(wavelength <= 1400e-9)
130 | mask_0400_1400 = mask_0400_1200 + mask_1200_1400
131 |
132 | mask_100fs_300ms = ( 1e-13 <= exposure_time)*(exposure_time < 0.3)
133 | mask_300ms_10s = ( 0.3 <= exposure_time)*(exposure_time < 10)
134 | mask_above_10s = (exposure_time >= 10)
135 |
136 | retina = mask_0400_1400 * 7.0
137 |
138 | cornea = mask_1200_1400 * mask_100fs_300ms * 1.0
139 | cornea += mask_1200_1400 * mask_300ms_10s * 1.5*exposure_time**0.375 # "Under normal conditions these exposure durations would not be used for hazard evaluation or classification." so for what?
140 | cornea += mask_1200_1400 * mask_above_10s * 3.5
141 |
142 | skin = mask_0400_1400 * 3.5
143 |
144 | return retina*1e-3, cornea*1e-3, skin*1e-3
--------------------------------------------------------------------------------
/OLBtools/__init__.py:
--------------------------------------------------------------------------------
1 | '''
2 | Copyright © 2020 Paul Serra, Peter Grenfell, Ondrej cierny
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 | The Software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the Software.
8 | '''
9 |
10 | #----------------------------------------------------------
11 | # Imports
12 | from dataclasses import dataclass
13 |
14 | import numpy as np
15 | import mpmath as mp
16 | import scipy.special as scsp
17 | import scipy.optimize as scop
18 | import scipy.integrate as scin
19 | import scipy.interpolate as scit
20 |
21 | mp.mp.dps = 300
22 |
23 | #----------------------------------------------------------
24 | # Constants
25 | qe = 1.60217662e-19 # charge of an electron, C (A.s)
26 | kb = 1.38064852e-23 # Boltzmann, J/K
27 | hp = 6.62607004e-34 # Planck, J.s
28 | c = 299.792458e6 # Speed of light, m/s
29 | T = 298.15 # Temperature of electronics, K
30 | Re = 6378e3 # Earth radius, m
31 | mu = 3.986004418e14 # Standard gravitational parameter, m3/s2
32 | we = 7.292115e-5 # Earth rotation, radians/seconds
33 |
34 | earth_rotation = 7.292115e-5 # Earth angular speed, radians by seconds
35 |
36 | min_log = 1e-30
37 |
38 | #----------------------------------------------------------
39 | # Helpers
40 | def degrees(a): return a*180/np.pi
41 | def radians(a): return a*np.pi/180
42 |
43 | def fwhw_to_radius1e2(fwhm):
44 | return fwhm/np.sqrt(2*np.log(2))
45 | def fwhw_to_diam1e2(fwhm):
46 | return 2*fwhm/np.sqrt(2*np.log(2))
47 | def radius1e2_to_fwhw(radius1e2):
48 | return np.sqrt(2*np.log(2))*radius1e2
49 | def diam1e2_to_fwhw(diam1e2):
50 | import warnings
51 | warnings.warn("Was incorrect", UserWarning)
52 | return np.sqrt(2*np.log(2))*diam1e2/2
53 |
54 | def angular_wave_number(vlambda):return 2*np.pi/vlambda
55 |
56 | def extend_integ_axis(arrays,integ_var):
57 | for ar in arrays:
58 | ar = ar[..., np.newaxis]
59 | arl = np.broadcast_arrays(integ_var,h)
60 | return arl[1:]
61 |
62 | mpin = np.frompyfunc(mp.mpf,1,1)
63 | gamma = np.frompyfunc(mp.gamma,1,1)
64 | besselk = np.frompyfunc(mp.besselk,2,1)
65 | hyp1F2 = np.frompyfunc(mp.hyp1f2,4,1)
66 | mpexp = np.frompyfunc(mp.exp,1,1)
67 | mpsin = np.frompyfunc(mp.sin,1,1)
68 | mpout = np.frompyfunc(float,1,1)
69 |
70 | def beam_radius(W_0,G_Theta,G_Lambda):
71 | return W_0/np.sqrt(G_Theta**2 + G_Lambda**2)
72 |
73 | def apply_min(x):
74 | return x #(x>min_log)*x + (x<=min_log)*min_log
75 |
76 | def filter_maximum(wi, wc, n):
77 | # Smooth maximum based on the transfer function of order n butterworth filter
78 | w = wi/wc
79 | ratio = 1 / np.sqrt(1 + w**(2*n))
80 | if n == 1: return wi*ratio
81 | else: return wi*ratio + wc*(1-ratio)
82 |
83 | #----------------------------------------------------------
84 | # Link Geometry
85 |
86 | def slant_range(h_0,H,zenith,Re):
87 | '''Slant range for a spacecraft, per appendix A
88 | h_0: Station altitude above atmosphere 0 ref, in m
89 | H: spacecraft altitude above atmosphere 0 ref, in m
90 | Re: distance from geotcenter to atmosphere 0 ref, in m
91 | zenith: ground station zenith angle in radians'''
92 | h_0 = h_0 + Re
93 | H = H + Re
94 | return np.sqrt( (h_0*np.cos(zenith))**2 + H**2 - h_0**2 ) - h_0*np.cos(zenith)
95 |
96 | def earth_centered_intertial_to_latitude_longitude(latitude,longitude,thetaE,x,y,z):
97 | '''Peter Grenfell'''
98 |
99 | ctE = np.cos(thetaE); stE = np.sin(thetaE)
100 | cph = np.cos(latitude); sph = np.sin(latitude)
101 | cla = np.cos(longitude); sla = np.sin(longitude)
102 |
103 | tx = cla*cph*x + sla*cph*y + sph*z
104 | ty = -sla*x + cla*y
105 | tz = -cla*sph*x - sla*sph*y + cph*z
106 |
107 | x = ctE*tx + stE*ty
108 | y = -stE*tx + ctE*ty
109 | z = tz
110 |
111 | return x,y,z
112 |
113 |
114 | def pass_azimuth_elevation_and_time(latitude,longitude,altitude,inclination,min_zenith,max_zenith,npts,mu,Re,we):
115 | '''Peter Grenfell
116 | latitude,longitude
117 | orbit_altitude
118 | min_zenith
119 | Re: distance from geotcenter to atmosphere 0 ref, in m'''
120 |
121 | #Slant range at min zenith (max elevation)
122 | sr_min = slant_range(0,altitude,min_zenith,Re)
123 |
124 | #Slant range at max zenith (min elevation)
125 | sr_max = slant_range(0,altitude,max_zenith,Re)
126 |
127 | def law_of_cosines(a,b,c):
128 | #law of cosines, return angle, c is the far side
129 | return np.arccos( (a**2+b**2-c**2)/(2*a*b) )
130 |
131 | #Top of pass in earth centered referential, at OGS lat/long
132 | x = altitude*np.sin(min_zenith)*np.cos(inclination)
133 | y = altitude*np.sin(min_zenith)*np.sin(inclination)
134 | h = Re + altitude*np.cos(min_zenith)
135 |
136 | # Angle of OGS from orbit plan at geocenter.
137 | orbit_plan_angle_geocenter = law_of_cosines(Re,Re+altitude,sr_min)
138 |
139 | # Angle of the horizon at geocenter from vertical (zentith = 0), in corbit plane.
140 | horizon_angle_in_plane_geocenter = law_of_cosines(Re,Re+altitude,sr_max)
141 |
142 | # Angle of horizon our of orbit plane, using spherical pythagorean theorem.
143 | horizon_angle_geocenter = np.arccos( np.cos(horizon_angle_in_plane_geocenter) / np.cos(orbit_plan_angle_geocenter) )
144 |
145 | # Orbit periode
146 | T_orbit = 2*np.pi*np.sqrt( (altitude+Re)**3/mu )
147 |
148 | # time to the horizon from vertical (zentith = 0)
149 | t_horizon = horizon_angle_geocenter*T_orbit/(2*np.pi)
150 |
151 | # Timescale
152 | t = np.linspace(-t_horizon,t_horizon,npts)
153 |
154 | # Orbit phase
155 | sat_phase = 2*np.pi*t/T_orbit
156 |
157 | #Top of pass in earth centered inertial
158 | #x,y,z = earth_centered_intertial_to_latitude_longitude(-latitude,-longitude,0,x=h,y=x,z=y)
159 |
160 | # Circular equatorial orbit
161 | x_orbit = (Re + altitude)*np.cos(sat_phase)
162 | y_orbit = (Re + altitude)*np.sin(sat_phase)
163 | z_orbit = 0
164 |
165 | # offset from OGS, rotation arround y axis
166 | coff = np.cos(orbit_plan_angle_geocenter)
167 | soff = np.sin(orbit_plan_angle_geocenter)
168 | x_off = coff*x_orbit
169 | y_off = y_orbit
170 | z_off = soff*x_orbit
171 |
172 | # Adding inclnation, rotation arround x axis
173 | cinc = np.cos(inclination)
174 | sinc = np.sin(inclination)
175 | x_inc = x_off
176 | y_inc = cinc*y_off + sinc*z_off
177 | z_inc = -sinc*y_off + cinc*z_off
178 |
179 | # Putting it on top off OGS at t0
180 | x, y, z = earth_centered_intertial_to_latitude_longitude(latitude,longitude,0,x_inc,y_inc,z_inc)
181 |
182 | # OGS coordinates
183 | x0,y0,z0 = earth_centered_intertial_to_latitude_longitude(latitude,longitude,we*t,Re,0,0)
184 | x0,y0,z0 = np.broadcast_arrays(x0,y0,z0)
185 | ogs = np.array([x0,y0,z0]).transpose()
186 |
187 | if 0:
188 | import matplotlib.pyplot as plt
189 | from mpl_toolkits.mplot3d import Axes3D
190 |
191 | fig = plt.figure()
192 | ax = fig.add_subplot(111, projection='3d')
193 | ax.plot(x,y,z)
194 | ax.scatter(x0,y0,z0)
195 | ax.scatter(0,0,0)
196 |
197 | # Sat vector from OGS
198 | xs = x-x0
199 | ys = y-y0
200 | zs = z-z0
201 | ns = np.sqrt(xs**2 + ys**2 + zs**2)
202 |
203 | # satelite unit vector
204 | sat = np.array([xs/ns,ys/ns,zs/ns]).transpose()
205 |
206 | # Zentih angle at OGS
207 | zenith = np.arccos((x0*xs + y0*ys + z0*zs)/ns/Re)
208 |
209 | # Sat unit vector in OGS horizontal plane
210 | sat_horizontal = np.cross(ogs,sat)/Re #90 deg away
211 |
212 | # East vector at OGS
213 | east = np.cross([0,0,1],ogs)/Re
214 |
215 | # North vector at OGS
216 | north = np.cross(ogs,east)/Re
217 |
218 | def dot_2D(a,b):
219 | return a[:,0]*b[:,0] + a[:,1]*b[:,1] + a[:,2]*b[:,2]
220 |
221 | cos_east = dot_2D(sat_horizontal,east)
222 | cos_north = dot_2D(sat_horizontal,north)
223 |
224 | # Azimuth angle at OGS
225 | azimuth = np.arctan2(cos_north,-cos_east)
226 |
227 | #on top of lat/long = 0
228 | #+z
229 | #^
230 | #|
231 | #O->+y
232 | #+x
233 |
234 |
235 | return t,zenith,azimuth
236 |
237 | #----------------------------------------------------------
238 | # Link budget functions
239 | #----------------------------------------------------------
240 |
241 | def path_loss_gaussian(beam_radius, wavelength, distance, rx_diameter, pointing_error=0, M2=1.0):
242 | # From Matlab linkbudget, author O cierny / P serra
243 | '''Calculates path loss given a Gaussian beam, and an Rx aperture at a
244 | specific distance. Assumes normalized input power, returns dB loss.
245 | All units in meters / radians.
246 | beam_radius: 1/e^2 radius of the beam at Tx [m]
247 | pointing_error: misalignment between Tx and Rx [rad] (optional)
248 | M2: M square beam quality factor, unitless (optional)'''
249 |
250 | # Calculate beam waist at the given distance due to diffraction [m]
251 | beam_radius_rx = beam_radius * np.sqrt(1 + ((M2*wavelength*distance)/(np.pi*beam_radius**2))**2)
252 |
253 | # Calculate distance from center of Gaussian due to pointing error [m]
254 | radial_distance = np.tan(pointing_error) * distance
255 |
256 | # Calculate irradiance using Gaussian formula [W/m^2]
257 | irradiance = (2/(np.pi*beam_radius_rx**2))*np.exp((-2*radial_distance**2)/beam_radius_rx**2)
258 |
259 | # Calculate power at Rx aperture [W]
260 | rx_area = np.pi*(rx_diameter/2)**2 # [m^2]
261 | rx_power = irradiance * rx_area # [W]
262 |
263 | # Calculate dB loss
264 | path_loss_dB = 10*np.log10(rx_power) # [dB]
265 | return path_loss_dB
266 |
267 | def gaussian_beam_encircled_ratio(tx_radius, wavelength, distance, rx_radius, M2=1.0):
268 | # Calculate beam waist at the given distance due to diffraction [m]
269 | rx_radius_prop = tx_radius * np.sqrt(1 + ((M2*wavelength*distance)/(np.pi*tx_radius**2))**2)
270 |
271 | # beam cut-off ratio
272 | c = rx_radius / rx_radius_prop
273 |
274 | ratio = (1-np.exp(-2*c**2))
275 | return ratio
276 |
277 | def fwhm_divergence_to_1e2_beam_radius(fwhm, wavelength):
278 | # From Matlab linkbudget, author O cierny
279 | '''Calculates the 1/e^2 Gaussian beam radius given the full width half
280 | maximum angle.
281 | fwhm: full width half maximum angle [rad]
282 | beam_radius: 1/e^2 beam radius at origin [m]'''
283 | beam_radius = (wavelength * np.sqrt(2*np.log(2))) / (np.pi * fwhm)
284 | return beam_radius
285 |
286 | def fwhm_to_radius(fwhm, wavelength):
287 | import warnings
288 | warnings.warn("deprecated", DeprecationWarning)
289 | return fwhm_divergence_to_1e2_beam_radius(fwhm, wavelength)
290 |
291 |
292 | def half_1e2_divergence_to_1e2_beam_radius(divergence_1e2:np.ndarray, wavelength) -> np.ndarray:
293 | '''Calculates the 1/e^2 Gaussian beam radius given the 1e2 half-angle'''
294 | beam_radius = wavelength / (np.pi * divergence_1e2)
295 | return beam_radius
296 |
297 | def divergence_to_radius(divergence_1e2:np.ndarray, wavelength) -> np.ndarray:
298 | import warnings
299 | warnings.warn("deprecated", DeprecationWarning)
300 | return half_1e2_divergence_to_1e2_beam_radius(divergence_1e2, wavelength)
301 |
302 | #----------------------------------------------------------
303 | # LOWTRAN functions
304 | #----------------------------------------------------------
305 |
306 | def LOWTRAN_transmittance(zenith,lambda_min,lambda_max,step=1e7/20,model=5,H=0,haze=0):
307 | '''Based on TransmittanceGround2Space.py example
308 | model: 0-6, see Card1 "model" reference in LOWTRAN user manual. 5=subarctic winter
309 | H: altitude of observer [m]
310 | zenith: observer zenith angle [rad]
311 | lambda_min: shortest wavelength [nm] ???
312 | lambda_max: longest wavelength cm^-1 ???
313 | step: wavelength step size
314 | '''
315 |
316 | import lowtran
317 |
318 | c1 = {'model': model,
319 | 'ihaze': haze,
320 | 'h1': H*1e3,
321 | 'angle': degrees(zenith),
322 | 'wlshort': lambda_min*1e9,
323 | 'wllong': lambda_max*1e9,
324 | 'wlstep': 1e7/step,
325 | }
326 |
327 | TR = lowtran.transmittance(c1)
328 |
329 | return TR
330 |
331 | def transmittance(zenith,wavelength,model,H):
332 |
333 | wavelength = np.asarray(wavelength)
334 |
335 | wl_step_large=1e7/20
336 | #wl_step_min=1e7/5
337 |
338 | lambda_low = 1/(1/wavelength.min() + wl_step_large*1e-2)
339 | lambda_high = 1/(1/wavelength.max() - wl_step_large*1e-2)
340 |
341 | #degrees(zenith)
342 | T = LOWTRAN_transmittance(zenith,lambda_low,lambda_high,wl_step_large,model,H)
343 |
344 | x = T.wavelength_nm[::-1]*1e-9
345 | y = T['transmission'][0,::-1,:]
346 | w = wavelength
347 |
348 | if w.shape: ws = w.shape[0]
349 | else: ws = 1
350 |
351 | r = np.zeros((ws,y.shape[1]))
352 | for n in range(y.shape[1]):
353 | r[:,n] = np.interp(w, x, y[:,n])
354 |
355 | return r
356 |
357 | #def MODTRAN_transmittance(zenith
358 |
359 | @dataclass
360 | class Quadcell:
361 | '''Class for a 4-quadrant pin detector, provides methods for postion, noise and noise-equivalent angle
362 | gap: size of the gap between quadrants, in m or PSF size units
363 | responsivity: photodiode repsonsivity in A/W
364 | transimpedance: amplifier gain in V/A or ohm
365 | amplifier_noise: output refered noise of the amplifier, in v/rtHz
366 | bandwidth: bandwidth for the detection, in Hz
367 | '''
368 |
369 | gap:float=0.0
370 | responsivity:float=0.9
371 | transimpedance:float=1e6
372 | amplifier_noise:float=1e-5
373 | bandwidth:float=100.0
374 |
375 | def __post_init__(self):
376 | #Quadrants defined as A,B,C,D, in trigonometric order, in quadcell front view.
377 | # A: +x,+y, B:-x,+y, C:-x,-y, D:+x,-y
378 |
379 | #Cumulative sum of the PSF over each quadrant, as a scipy interpolation function
380 | self.PSF_sum_2D_A = None
381 | self.PSF_sum_2D_B = None
382 | self.PSF_sum_2D_C = None
383 | self.PSF_sum_2D_D = None
384 |
385 | def cumulated_gaussian_PSF_on_square_mask(W,x1,x2,y1,y2):
386 | #W: spot size for the PSF, such as PSF(r) = 2/(pi*W**2)*exp(-2*r**2/w**2)
387 | coef = np.sqrt(2)/W
388 | return 0.25*(scsp.erf(coef*x2)-scsp.erf(coef*x1))*(scsp.erf(coef*y2)-scsp.erf(coef*y1))
389 |
390 | def cumulated_gaussian_PSF_on_quadrant_mask(W,x1,y1,dx,dy):
391 | #W: spot size for the PSF, such as PSF(r) = 2/(pi*W**2)*exp(-2*r**2/w**2)
392 | coef = np.sqrt(2)/W
393 | if dx == 1: cx = 2*np.exp(-(coef*x1)**2)/np.sqrt(np.pi)
394 | else: cx = 1-scsp.erf(coef*x1)
395 | if dy == 1: cy = 2*np.exp(-(coef*y1)**2)/np.sqrt(np.pi)
396 | else: cy = 1-scsp.erf(coef*y1)
397 | return 0.25*cx*cy
398 |
399 | def set_PSF_gaussian(self,W):
400 | #W: spot size for the PSF, such as PSF(r) = 2/(pi*W**2)*exp(-2*r**2/w**2)
401 |
402 | def culative_sum_function(x,y,dx,dy,grid=False):
403 | assert not grid
404 | x_shifted = self.gap/2-x
405 | y_shifted = self.gap/2-y
406 | return Quadcell.cumulated_gaussian_PSF_on_quadrant_mask(W,x_shifted,y_shifted,dx,dy)
407 |
408 | self.set_all_quadrant_sum(culative_sum_function)
409 |
410 | def set_PSF_1D(self,r_samp,v_samp,n_int2d=1000,deg=3):
411 | ''' Compute and set the cumulative sum of the PSF for each quadrant, using radial PSF samples
412 | The PSF values are linearly interpolated
413 | The PSF is automaticaly normalised
414 | r_samp: sample postion, m or interpolation function input unit
415 | v_samp: sample relative intesisty
416 | n_int2d resolution of the cumulative sum on the quadrant
417 | def: interpolation degree for the cumulative sum on the quadrant
418 | '''
419 | r1 = r_samp[:-1]
420 | r2 = r_samp[1:]
421 | v1 = v_samp[:-1]
422 | v2 = v_samp[1:]
423 | normalize = np.pi*np.sum(r2**2*v2 + (r1**2+r1*r2+r2**2)*(v1-v2)/3 - r1**2*v1)
424 | v_samp = v_samp/normalize
425 |
426 | rf = r_samp[-1]
427 | x = np.linspace(-rf,rf,n_int2d)
428 | y = np.linspace(-rf,rf,n_int2d)
429 | xx, yy = np.meshgrid(x,y)
430 | r = np.sqrt(xx**2+yy**2)
431 | v = (r<=rf)*np.interp(r,r_samp,v_samp)
432 |
433 | v_integ = np.cumsum(np.cumsum(v,axis=0),axis=1)*(2*rf/n_int2d)**2
434 |
435 | v_integ_lookup = scit.RectBivariateSpline(x+self.gap/2,y+self.gap/2,v_integ,kx=deg,ky=deg)
436 |
437 | def lookup_function(x,y,dx,dy,sx,sy,grid=False):
438 | if dx==0 and dy==0:
439 | return np.maximum( v_integ_lookup(x*sx, y*sy, 0,0, grid=False), 0)
440 | elif dx==1:
441 | return sx*v_integ_lookup(x*sx, y*sy, 1,0, grid=False)
442 | elif dy==1:
443 | return sy*v_integ_lookup(x*sx, y*sy, 0,1, grid=False)
444 |
445 | self.PSF_sum_2D_A = lambda x,y,dx=0,dy=0:lookup_function(x,y,dx,dy, 1, 1,grid=False)
446 | self.PSF_sum_2D_B = lambda x,y,dx=0,dy=0:lookup_function(x,y,dx,dy,-1, 1,grid=False)
447 | self.PSF_sum_2D_C = lambda x,y,dx=0,dy=0:lookup_function(x,y,dx,dy,-1,-1,grid=False)
448 | self.PSF_sum_2D_D = lambda x,y,dx=0,dy=0:lookup_function(x,y,dx,dy, 1,-1,grid=False)
449 |
450 | def set_all_quadrant_sum(self,sum_function):
451 | # Set all quadrant if the PSF is axi-sytmetrical.
452 | self.PSF_sum_2D_A = lambda x,y,dx=0,dy=0:sum_function( x, y,dx,dy,grid=False)
453 | self.PSF_sum_2D_B = lambda x,y,dx=0,dy=0:sum_function(-x, y,dx,dy,grid=False)
454 | self.PSF_sum_2D_C = lambda x,y,dx=0,dy=0:sum_function(-x,-y,dx,dy,grid=False)
455 | self.PSF_sum_2D_D = lambda x,y,dx=0,dy=0:sum_function( x,-y,dx,dy,grid=False)
456 |
457 | def set_PSF_2D(self,x_samp,y_samp,v_samp,n_int2d=1000,deg=3):
458 | ''' Compute and set the cumulative sum of the PSF for each quadrant, 2D grid PSF samples
459 | The PSF values are linearly interpolated
460 | The PSF is automaticaly normalised
461 | x_samp: sample postion, m or interpolation function input unit
462 | y_samp: sample postion, m or interpolation function input unit
463 | v_samp: sample relative intesisty
464 | n_int2d resolution of the cumulative sum on the quadrant
465 | deg: interpolation degree for the cumulative sum on the quadrant
466 | '''
467 |
468 | normalize = np.sum(v_samp)*(x_samp[1]-x_samp[0])*(y_samp[1]-y_samp[0])
469 | v_samp = v_samp/normalize
470 |
471 | # PSF linear interpolation
472 | x = np.linspace(x_samp[0],x_samp[-1],n_int2d)#[:, np.newaxis]
473 | y = np.linspace(y_samp[0],y_samp[-1],n_int2d)#[np.newaxis, :]
474 | xx, yy = np.meshgrid(x,y)
475 | v_samp_lookup = scit.RectBivariateSpline(x_samp,y_samp,v_samp,kx=1,ky=1)
476 |
477 | dx_dy_integ_coef = (1/n_int2d)**2
478 | dx_dy_integ_coef *= (x_samp[-1]-x_samp[0])*(y_samp[-1]-y_samp[0])
479 |
480 | #integration over each quadrant, and sum interpolation function, shifted by gap value
481 | v = v_samp_lookup( xx, yy, grid=False)
482 | v_integ = np.cumsum(np.cumsum(v,axis=0),axis=1)*dx_dy_integ_coef
483 | self.PSF_sum_2D_A = lambda vx,vy,dx=0,dy=0: (scit.RectBivariateSpline(x+self.gap/2,y+self.gap/2,v_integ,kx=deg,ky=deg))( vx, vy,dx,dy,grid=False)
484 |
485 | v = v_samp_lookup(-xx, yy, grid=False)
486 | v_integ = np.cumsum(np.cumsum(v,axis=0),axis=1)*dx_dy_integ_coef
487 | self.PSF_sum_2D_B = lambda vx,vy,dx=0,dy=0: (scit.RectBivariateSpline(x+self.gap/2,y+self.gap/2,v_integ,kx=deg,ky=deg))(-vx, vy,dx,dy,grid=False)
488 |
489 | v = v_samp_lookup(-xx,-yy, grid=False)
490 | v_integ = np.cumsum(np.cumsum(v,axis=0),axis=1)*dx_dy_integ_coef
491 | self.PSF_sum_2D_C = lambda vx,vy,dx=0,dy=0: (scit.RectBivariateSpline(x+self.gap/2,y+self.gap/2,v_integ,kx=deg,ky=deg))(-vx,-vy,dx,dy,grid=False)
492 |
493 | v = v_samp_lookup( xx,-yy, grid=False)
494 | v_integ = np.cumsum(np.cumsum(v,axis=0),axis=1)*dx_dy_integ_coef
495 | self.PSF_sum_2D_D = lambda vx,vy,dx=0,dy=0: (scit.RectBivariateSpline(x+self.gap/2,y+self.gap/2,v_integ,kx=deg,ky=deg))( vx,-vy,dx,dy,grid=False)
496 |
497 | def eval_quadrants(self,x_spot,y_spot,dx=0,dy=0):
498 | #Normalized quadrant amplitude for a give spot postition
499 | A = self.PSF_sum_2D_A(x_spot,y_spot,dx,dy)
500 | B = self.PSF_sum_2D_B(x_spot,y_spot,dx,dy)
501 | C = self.PSF_sum_2D_C(x_spot,y_spot,dx,dy)
502 | D = self.PSF_sum_2D_D(x_spot,y_spot,dx,dy)
503 | return A,B,C,D
504 |
505 | def response_from_quadrants(self,quadrants):
506 | #give the slope of the quadcell response for given quadrant values
507 | A,B,C,D = quadrants
508 | quad_sum = A+B+C+D
509 | x_resp = ((A+D)-(B+C))/quad_sum
510 | y_resp = ((A+B)-(C+D))/quad_sum
511 | return x_resp, y_resp
512 |
513 | def response(self,x_spot,y_spot):
514 | #give the quadcell response for a give spot position
515 | return self.response_from_quadrants(self.eval_quadrants(x_spot,y_spot))
516 |
517 | def slope_from_quadrant(self,quadrants,quadrants_da):
518 | #give the slope of the quadcell response for given quadrant values and derivative
519 | A,B,C,D = quadrants
520 | A_da,B_da,C_da,D_da = quadrants_da
521 | quad_sum = A+B+C+D
522 | x_resp_da = (2*(B+C)*(A_da+D_da) - 2*(A+D)*(B_da+C_da))/quad_sum**2
523 | y_resp_da = (2*(D+C)*(A_da+B_da) - 2*(A+B)*(D_da+C_da))/quad_sum**2
524 |
525 | return x_resp_da,y_resp_da
526 |
527 | def slope(self,x_spot,y_spot):
528 | #give the slope of the quadcell response for a given spot position
529 | quad = self.eval_quadrants(x_spot,y_spot,dx=0,dy=0)
530 | quad_dx = self.eval_quadrants(x_spot,y_spot,dx=1,dy=0)
531 | quad_dy = self.eval_quadrants(x_spot,y_spot,dx=0,dy=1)
532 |
533 | x_resp_dx,y_resp_dx = self.slope_from_quadrant(quad,quad_dx)
534 | x_resp_dy,y_resp_dy = self.slope_from_quadrant(quad,quad_dy)
535 |
536 | return x_resp_dx,x_resp_dy,y_resp_dx,y_resp_dy
537 |
538 | def angular_slope(self,x_spot,y_spot,focal_lenght,magnification=1):
539 | x_resp_dx,x_resp_dy,y_resp_dx,y_resp_dy = self.slope(x_spot,y_spot)
540 |
541 | # we need x/arctan(x/y, witch has issue for x=0)\
542 | # Use series expension arround zero, we don't need a quadcell with large angles:
543 | def x_over_atanxy(x,y):
544 | return y + x**2/(3*y) - 4*x**4/(45*y**3)
545 |
546 | x_angle = x_over_atanxy(x_spot, focal_lenght)*magnification
547 | y_angle = x_over_atanxy(y_spot, focal_lenght)*magnification
548 |
549 | x_resp_dx = x_resp_dx*x_angle
550 | x_resp_dy = x_resp_dy*x_angle
551 | y_resp_dx = y_resp_dx*y_angle
552 | y_resp_dy = y_resp_dy*y_angle
553 |
554 | return x_resp_dx,x_resp_dy,y_resp_dx,y_resp_dy
555 |
556 | def SNR(self,x_spot,y_spot,optical_power,modulation_depth=1.0):
557 | ''' Return the quacel signal SNR
558 | x_spot, y_spot: postion of the spot on the quadcell in m or PSF postition units
559 | optical_power: total spot power, W
560 |
561 | '''
562 |
563 | A,B,C,D = self.eval_quadrants(x_spot,y_spot,dx=0,dy=0)
564 | all_quadrants = np.stack((A,B,C,D))
565 |
566 | all_power = all_quadrants*optical_power
567 | all_current = all_power*self.responsivity
568 | all_voltage = all_current*self.transimpedance
569 | all_shot_noise = self.transimpedance*np.sqrt(2*qe*all_current*self.bandwidth)
570 | all_amp_noise = self.amplifier_noise*np.sqrt(self.bandwidth)
571 | all_noise = np.sqrt(all_shot_noise**2+all_amp_noise**2)
572 |
573 | signal = sum([all_voltage[i,:,:]*modulation_depth for i in range(4)])
574 | noise = np.sqrt(sum([all_noise[i,:,:]**2 for i in range(4)]))
575 | SNR = signal / noise
576 |
577 | return SNR
578 |
579 | def NEA(self,x_spot,y_spot,optical_power,focal_lenght,magnification=1,modulation_depth=1.0):
580 |
581 | dx_dax, dx_day, dy_dax, dy_day = self.angular_slope(x_spot,y_spot,focal_lenght,magnification)
582 |
583 | SNR = self.SNR(x_spot,y_spot,optical_power,modulation_depth)
584 |
585 | NEA = np.sqrt(1/dx_dax**2 + 1/dy_day**2)/SNR
586 | return NEA
587 |
588 | @dataclass
589 | class Photodiode:
590 | '''Class for an APD detector
591 | Uses dark current and excess noise factor to derive noise, and add NEP inquadrature if specified
592 | '''
593 |
594 | gain:float=1.0
595 | responsivity:float=0.9
596 | bandwidth:float=1e6
597 | excess_noise_factor:float=1.0
598 | dark_current:float=0
599 | amp_noise_density:float=0
600 |
601 | def estimatedNEP(self):
602 | """Find estimated NEP based on shot noise and dark current alone"""
603 | # We want to solve S (signal) for SNR = 1, noise = signal
604 | # S**2 = 2*qe*ENF*(S+dark)*BW + i_amp**2*BW
605 | # S**2 - K*S - K*dark - i_amp**2*BW = 0 with K = 2*qe*ENF*BW
606 | # S = (K + sqrt(K**2 + 4*(K*dark+i_amp**2*BW))) / 2
607 | K = 2*qe*self.excess_noise_factor*self.bandwidth
608 | signal = (K + np.sqrt(K**2 + 4*(K*self.dark_current+self.amp_noise_density**2*self.bandwidth))) / 2
609 | NEP = signal/(self.gain*self.responsivity) / np.sqrt(self.bandwidth)
610 | return NEP
611 |
612 | def signal(self,optical_power):
613 | return self.gain*self.responsivity*optical_power
614 |
615 | def noise(self,optical_power):
616 | APD_shot_noise_squared = 2*qe*self.excess_noise_factor*(self.signal(optical_power)+self.dark_current)*self.bandwidth
617 | amplifier_noise_squared = self.amp_noise_density**2*self.bandwidth
618 | return np.sqrt(APD_shot_noise_squared + amplifier_noise_squared)
619 |
620 |
621 | def SNR(self,optical_power):
622 | #Defined as I/In, In is the noise variance
623 | return self.signal(optical_power)/self.noise(optical_power)
624 |
625 | def supportedBandwidth(self,optical_power,required_SNR):
626 | # Noise RMS is Signal / SNR
627 | supported_noise = self.signal(optical_power)/required_SNR
628 | # Re-arange Photodiode.noise
629 | APD_shot_noise_density_squared = 2*qe*self.excess_noise_factor*(self.signal(optical_power)+self.dark_current)
630 | amplifier_noise_density_squared = self.amp_noise_density**2
631 | supported_bandwidth = supported_noise**2 / (APD_shot_noise_density_squared + amplifier_noise_density_squared)
632 | return supported_bandwidth
633 |
634 | def BER_OOK(SNR):
635 | # With SNR defined as I/In
636 | # signal total amplituide is I, and variance of both 0 and 1 is asumed to In
637 | # Optimal thresholding, at 0 with both normal distribution at +/- I/2
638 | # 0 and 1 CDF(x): 0.5 * [1+ERF( (x +/- I/2)/(sqrt(2)*In) )]
639 | # BER = P(1)*P(0|1) + P(0)*P(1|0) = P(0|1) = 2*CDF(0)
640 | # BER = 0.5*[1+ERF( (-I/2)/(sqrt(2)*In) )]
641 | # BER = 0.5*[1-ERF( I/(In*2*sqrt(2)) )]
642 | # BER = 0.5*[ERFC( (I/In)/(2*sqrt(2)) )]
643 | return 0.5*scsp.erfc(SNR/(2*np.sqrt(2)))
644 |
645 | def SNR_from_BER_OOK(BER):
646 | # With SNR defined as I/In
647 | # Inverse of BER_OOK
648 | # 2*BER = ERFC( (I/In)/(2*sqrt(2)) )
649 | # ERFCinv(2*BER) = (I/In)/(2*sqrt(2))
650 | # I/In = ERFCinv(2*BER)*2*sqrt(2)
651 | return scsp.erfcinv(2*BER)*2*np.sqrt(2)
652 |
653 | def BER_OOK_integrated(SNR,PDF):
654 | BER = BER_OOK(SNR)
655 | BER_out_of_pdf = 0.5*( 1- np.sum(PDF, axis = 0) )
656 | return np.sum(PDF*BER, axis = 0) + BER_out_of_pdf
657 |
658 | def suported_bandwidth_OOK(pd:Photodiode,optical_power,BER):
659 |
660 | #Inverse of BER_OOK:
661 | required_SNR = SNR_from_BER_OOK(BER)
662 |
663 | supported_bandwidth = pd.supportedBandwidth(optical_power,required_SNR)
664 |
665 | return supported_bandwidth
666 |
667 |
668 | # =====================================================================================================
669 | # Check against other link budget
670 | # =====================================================================================================
671 |
672 |
673 |
674 | # =====================================================================================================
675 | # deprecated
676 | # =====================================================================================================
677 |
678 | def gaussian_PSF_on_square_mask(W,x1,x2,y1,y2):
679 | #W: spot size for the PSF, such as PSF(r) = 2/(pi*W**2)*exp(-2*r**2/w**2)
680 | coef = np.sqrt(2)/W
681 | return 0.25*(scsp.erf(coef*x2)-scsp.erf(coef*x1))*(scsp.erf(coef*y2)-scsp.erf(coef*y1))
682 |
683 | def sampled_linear_interpolation_PSF_corner(r_samp,v_samp,n_int2d=1000,deg=3):
684 | r1 = r_samp[:-1]
685 | r2 = r_samp[1:]
686 | v1 = v_samp[:-1]
687 | v2 = v_samp[1:]
688 | normalize = np.pi*np.sum(r2**2*v2 + (r1**2+r1*r2+r2**2)*(v1-v2)/3 - r1**2*v1)
689 | v_samp = v_samp/normalize
690 |
691 | rf = r_samp[-1]
692 | x = np.linspace(-rf,rf,n_int2d)
693 | y = np.linspace(-rf,rf,n_int2d)
694 | xx, yy = np.meshgrid(x,y)
695 | r = np.sqrt(xx**2+yy**2)
696 | v = (r<=rf)*np.interp(r,r_samp,v_samp)
697 |
698 | v_integ = np.cumsum(np.cumsum(v,axis=0),axis=1)*(2*rf/n_int2d)**2
699 |
700 | v_integ_lookup = scit.RectBivariateSpline(x,y,v_integ,kx=deg,ky=deg)
701 |
702 | return v_integ_lookup
703 |
704 | def sampled_2D_interpolation_PSF_corner(x_samp,y_samp,v_samp,n_int2d=1000,deg=3):
705 | normalize = np.sum(v_samp)*(x_samp[1]-x_samp[0])*(y_samp[1]-y_samp[0])
706 | v_samp = v_samp/normalize
707 | center_x = np.sum(v_samp*x_samp)/np.sum(v_samp)
708 | center_y = np.sum(v_samp*y_samp)/np.sum(v_samp)
709 | #print(v_samp.shape)
710 |
711 | x = np.linspace(x_samp[0],x_samp[-1],n_int2d)
712 | y = np.linspace(y_samp[0],y_samp[-1],n_int2d)
713 | xx, yy = np.meshgrid(x,y)
714 | v_samp_lookup = scit.RectBivariateSpline(x_samp,y_samp,v_samp,kx=1,ky=1)
715 | v = v_samp_lookup(x,y)
716 |
717 | v_integ = np.cumsum(np.cumsum(v,axis=0),axis=1)*((x_samp[-1]-x_samp[0])/n_int2d)*((y_samp[-1]-y_samp[0])/n_int2d)
718 |
719 | v_integ_lookup = scit.RectBivariateSpline(x,y,v_integ,kx=deg,ky=deg)
720 |
721 | return v_integ_lookup
--------------------------------------------------------------------------------
/OLBtools/beams.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import matplotlib.pyplot as plt
4 | import matplotlib.patches as mpt
5 |
6 | #----------------------------------------------------------
7 | # Beam convertion functions
8 |
9 | def half1e2_to_FWHM(half1e2:np.ndarray) -> np.ndarray:
10 | '''Convert a half angle or radius to a FWHM matching quantity'''
11 | import warnings
12 | warnings.warn("Was incorrect", UserWarning)
13 | FWHM = half1e2*np.sqrt(2*np.log(2))
14 | return FWHM
15 |
16 | def FWHM_to_half1e2(FWHM:np.ndarray) -> np.ndarray:
17 | '''Convert a FWHM quantity to a matching half angle or radius'''
18 | import warnings
19 | warnings.warn("Was incorrect", UserWarning)
20 | half1e2 = FWHM/np.sqrt(2*np.log(2))
21 | return half1e2
22 |
23 | def W0_to_radius1e2(W0:np.ndarray) -> np.ndarray:
24 | '''In this module, we assume W0 is the 1/e2 Gaussian beam radius'''
25 | radius1e2 = W0
26 | return radius1e2
27 |
28 | def radius1e2_to_W0(radius1e2:np.ndarray) -> np.ndarray:
29 | '''In this module, we assume W0 is the 1/e2 Gaussian beam radius'''
30 | W0 = radius1e2
31 | return W0
32 |
33 | def W0_to_diam1e2(W0:np.ndarray) -> np.ndarray:
34 | '''Convert W0 to the 1/e2 Gaussian beam diameter'''
35 | diam1e2 = 2*W0
36 | return diam1e2
37 |
38 | def diam1e2_to_W0(diam1e2:np.ndarray) -> np.ndarray:
39 | '''Convert the 1/e2 Gaussian beam diameter to W0'''
40 | W0 = 0.5*diam1e2
41 | return W0
42 |
43 | def W0_to_FWHMdiam(W0:np.ndarray) -> np.ndarray:
44 | '''Convert W0 to the Full Width Half Max Gaussian beam diameter'''
45 | FWHM_diam = half1e2_to_FWHM(W0)
46 | return FWHM_diam
47 |
48 | def FWHMdiam_to_W0(FWHM_diam:np.ndarray) -> np.ndarray:
49 | '''Convert the Full Width Half Max Gaussian beam diameter to W0'''
50 | W0 = FWHM_to_half1e2(FWHM_diam)
51 | return W0
52 |
53 | def W0_to_halfangle1e2(W0:np.ndarray, wavelength) -> np.ndarray:
54 | '''Convert W0 to the 1/e2 Max Gaussian beam half-angle, angles in radians and distances in meters.'''
55 | half_angle_1e2 = wavelength / (np.pi * W0)
56 | return half_angle_1e2
57 |
58 | def halfangle1e2_to_W0(half_angle_1e2:np.ndarray, wavelength) -> np.ndarray:
59 | '''Convert the 1/e2 Max Gaussian beam half-angle to W0, angles in radians and distances in meters.'''
60 | W0 = wavelength / (np.pi * half_angle_1e2)
61 | return W0
62 |
63 | def W0_to_fullangle1e2(W0:np.ndarray, wavelength) -> np.ndarray:
64 | '''Convert W0 to the 1/e2 Max Gaussian beam full angle, angles in radians and distances in meters.'''
65 | full_angle_1e2 = 2*W0_to_halfangle1e2(W0, wavelength)
66 | return full_angle_1e2
67 |
68 | def fullangle1e2_to_W0(full_angle_1e2:np.ndarray, wavelength) -> np.ndarray:
69 | '''Convert the 1/e2 Max Gaussian beam full angle to W0, angles in radians and distances in meters.'''
70 | W0 = halfangle1e2_to_W0(full_angle_1e2/2, wavelength)
71 | return W0
72 |
73 | def W0_to_FWHMangle(W0:np.ndarray, wavelength) -> np.ndarray:
74 | '''Convert W0 to the Full Width Half Max Gaussian beam angle, angles in radians and distances in meters.'''
75 | half_angle_1e2 = W0_to_halfangle1e2(W0, wavelength)
76 | FWHM_angle = half1e2_to_FWHM(half_angle_1e2)
77 | return FWHM_angle
78 |
79 | def FWHMangle_to_W0(FWHM_angle:np.ndarray, wavelength) -> np.ndarray:
80 | '''Convert the Full Width Half Max Gaussian beam angle to W0, angles in radians and distances in meters.'''
81 | half_angle_1e2 = FWHM_to_half1e2(FWHM_angle)
82 | W0 = halfangle1e2_to_W0(half_angle_1e2, wavelength)
83 | return W0
84 |
85 | #----------------------------------------------------------
86 | # Gaussian beam fits and centroids
87 |
88 | def ravel_I_x_y(I:np.ndarray, x:np.ndarray, y:np.ndarray) -> tuple[np.ndarray,np.ndarray,np.ndarray]:
89 | '''Check if the I/x/y are already 1d, if not generate a mesh for x/y and flattens it'''
90 |
91 | # Check if the data is already 1D
92 | input_shape = I.shape
93 | is_1d = True
94 | if len(input_shape) > 1:
95 | for size in input_shape[1:]:
96 | if size > 1:
97 | is_1d = False
98 | break
99 |
100 | # if not 1d check dimmention, generate x,y arrays, ravel
101 | if not is_1d:
102 | assert I.shape[0] == x.shape[0]
103 | assert I.shape[1] == y.shape[0]
104 | if (I.shape == x.shape) and (I.shape == x.shape):
105 | xx = x
106 | yy = y
107 | else:
108 | xx,yy = np.meshgrid(y,x)
109 | I = I.flatten()
110 | x = xx.flatten()
111 | y = yy.flatten()
112 |
113 | return I,x,y
114 |
115 | def centroid(I:np.ndarray, x:np.ndarray, y:np.ndarray, threshold:float=None) -> tuple[float,float]:
116 | '''Find the centroid of the I array
117 | I: N x M array
118 | x: Nx1 array, cooridnates
119 | y: Mx1 array, cooridnates
120 | threshold: centroid will ignore value below this. Default is 0.3 of I amplitude
121 | return centroid as tuple, for x and y axis, in x and y units'''
122 |
123 | I,x,y = ravel_I_x_y(I,x,y)
124 |
125 | # If no threhold is available
126 | if threshold is None:
127 | min = np.min(I)
128 | max = np.max(I)
129 | threshold = min + 0.3*(max - min)
130 |
131 | mask = I > threshold
132 | weight = mask*I
133 | total = np.sum(weight)
134 |
135 | x_pondarated = weight * x
136 | x_tot = np.sum(x_pondarated)
137 | x_beam_center = x_tot/total
138 |
139 | y_pondarated = weight * y
140 | y_tot = np.sum(y_pondarated)
141 | y_beam_center = y_tot/total
142 |
143 | return x_beam_center,y_beam_center
144 |
145 | def fit_2d_gaussian(I:np.ndarray, x:np.ndarray, y:np.ndarray, threshold:float=None):
146 | '''Fit a 2d Gaussian distribution to I.
147 | I: N x M array, data to fit
148 | x: Nx1 array, cooridnates
149 | y: Mx1 array, cooridnates
150 | Alternatively, shapes can be 1d N for I,x,y
151 | threshold: centroid will ignore value below this. Default is 0.2 of I amplitude
152 | return:
153 | amplitude in I unit, x0, y0 spot position, sigx, sigy, spot size in x/y units
154 | spot principal axis angle in radian'''
155 |
156 | I,x,y = ravel_I_x_y(I,x,y)
157 |
158 | x0_est, y0_est = centroid(I,x,y, threshold=threshold)
159 |
160 | x -= x0_est
161 | y -= y0_est
162 |
163 | # The model can only handle positive values
164 | min = np.min(I)
165 | assert min >= 0
166 |
167 | # If no threhold is available, set to 20%
168 | max = np.max(I)
169 | if threshold is None:
170 | threshold = 0.4*max
171 |
172 | # Remove based on threshold
173 | mask = I > threshold
174 | I = I[mask]
175 | x = x[mask]
176 | y = y[mask]
177 |
178 | if sum(mask) < 10: print(sum(mask))
179 |
180 | # Normalize
181 | I /= max
182 | angular_scale = np.maximum(np.max(x),np.max(y))
183 | x /= angular_scale
184 | y /= angular_scale
185 |
186 | # Log domain
187 | logI = -np.log(I)
188 |
189 | # varriance
190 | var = I # Flat noise
191 |
192 | # Find needed power of x,y in a very overcomplicated manner
193 | # Positon of power of x, y
194 | xmask = np.array([2, 0, 1, 1, 0, 0])
195 | ymask = np.array([0, 2, 1, 0, 1, 0])
196 | xmask = xmask[np.newaxis,:] + xmask[:,np.newaxis]
197 | ymask = ymask[np.newaxis,:] + ymask[:,np.newaxis]
198 | xx, yy = np.meshgrid(range(6),range(6))
199 | xx = xx.ravel()
200 | yy = yy.ravel()
201 |
202 |
203 | # Recoord coordinated for each needed power
204 | coord_dict = {}
205 | for xidx,yidx,xpow,ypow in zip(xx.ravel(), yy.ravel(), xmask.ravel(), ymask.ravel()):
206 | power = (xpow,ypow)
207 | coord = (xidx,yidx)
208 | if power in coord_dict: coord_dict[power].append(coord)
209 | else: coord_dict[power] = [coord]
210 |
211 | # Compute the sum of powers
212 | power_dict = {}
213 | for power in coord_dict.keys():
214 | power_dict[power] = np.sum((x**power[0])*(y**power[1])*var)
215 |
216 | # Place back the power in the LSQ matrix
217 | HtWH = np.zeros((6,6))
218 | for power, coord_list in coord_dict.items():
219 | value = power_dict[power]
220 | for coord in coord_list:
221 | HtWH[coord] = value
222 | HtWH[5,5] = np.sum(var)
223 |
224 | HtWY = np.array([
225 | np.sum(x**2*var*logI),
226 | np.sum(y**2*var*logI),
227 | np.sum(x*y*var*logI),
228 | np.sum(x*var*logI),
229 | np.sum(y*var*logI),
230 | np.sum(1*var*logI),
231 | ])[:,np.newaxis]
232 |
233 | # Solve the matrix inversion
234 | res = np.linalg.solve(HtWH, HtWY)
235 |
236 | px2, py2, pxy, px, py, p1 = res[:,0]
237 |
238 | # Main axis angle
239 | angle = 0.5*np.arctan2(pxy, py2-px2)
240 |
241 | # Spot size
242 | sig_p = px2 + py2
243 | sig_m = np.sqrt( (py2 - px2)**2 + pxy**2 )
244 | sigy2 = 2 / (sig_p + sig_m)
245 | sigx2 = 2 / (sig_p - sig_m)
246 |
247 | # Sport center
248 | div0 = 1/ (pxy**2 - 4*px2*py2)
249 | y0 = (2*py2*px - pxy*py) * div0
250 | x0 = (2*px2*py - pxy*px) * div0
251 |
252 | # Amplitude
253 | k = np.exp(- ( py2*px**2 - pxy*px*py + px2*py**2 )/( pxy**2 - 2*px2*py2 ) - p1)
254 |
255 | x0 = x0*angular_scale + x0_est
256 | y0 = y0*angular_scale + y0_est
257 | sigx2 *= angular_scale**2
258 | sigy2 *= angular_scale**2
259 |
260 | return k*max, x0, y0, sigx2, sigy2, angle
261 |
262 | def plot_fit_2d_gaussian(ax:plt.Axes, I,x,y, k, x0, y0, sigx2, sigy2, angle, threshold=None):
263 |
264 | # Adapt fit param
265 | sigx = np.sqrt(sigx2)
266 | sigy = np.sqrt(sigy2)
267 |
268 | if threshold: I = I*(I>threshold)
269 |
270 | # Draw the raw data
271 | extent = [x[0],x[-1],y[-1],y[0]]
272 | ax.imshow(I,extent=extent, interpolation='quadric')
273 |
274 | # Cross lines
275 | x_vert = sigx*np.cos(angle)
276 | y_vert = sigx*np.sin(angle)
277 | x_line_vert = [x0 - x_vert, x0 + x_vert]
278 | y_line_vert = [y0 - y_vert, y0 + y_vert]
279 | ax.plot(x_line_vert, y_line_vert, color='r')
280 | x_hori = sigy*np.sin(angle)
281 | y_hori = -sigy*np.cos(angle)
282 | x_line_hori = [x0 - x_hori, x0 + x_hori]
283 | y_line_hori = [y0 - y_hori, y0 + y_hori]
284 | ax.plot(x_line_hori, y_line_hori, color='r')
285 |
286 | # 1/e2 Ellipse
287 | ellipse = mpt.Ellipse(xy=(x0,y0),width=2*sigx,height=2*sigy,angle=angle*180/np.pi,edgecolor='r', fc='None', lw=1)
288 | ax.add_patch(ellipse)
--------------------------------------------------------------------------------
/OLBtools/reporting.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import sys
3 | import os
4 | import numpy as np
5 | import OLBtools as olb # this is weird but i'm not gonna update the whole script
6 | from matplotlib.figure import Figure
7 | from matplotlib.axes import Axes
8 | import matplotlib.pyplot as plt
9 | import io
10 | import base64
11 |
12 | import dataclasses
13 |
14 | class Report:
15 | def __init__(self,title:str='Link Budget'):
16 | self.title = title
17 | self.body = ''
18 |
19 | def AddHtml(self, html):
20 | self.body += '
' + html + '
'
21 |
22 | def AddFigure(self, fig:Figure, alt_text:str=''):
23 | ''' Save the current figure as a base64 embeded in html'''
24 | image_string = io.BytesIO()
25 | fig.savefig(image_string, format='jpg')
26 | image_string.seek(0)
27 | image_base64 = base64.b64encode(image_string.read()).decode()
28 |
29 | self.AddHtml('
' % (image_base64,alt_text))
30 |
31 | def HtmlString(self):
32 |
33 | report_header = f'''
34 | {self.title}
35 | '''
36 | script_file = sys.argv[0]
37 | scrict_name = os.path.basename(script_file)
38 | script_path = os.path.dirname(script_file)
39 |
40 | try:
41 | import git
42 | repo = git.Repo(script_path)
43 | except:
44 | git_string = ''
45 | else:
46 | commit_hash = repo.head.commit.hexsha
47 | changed = [item.a_path for item in repo.index.diff(None)]
48 | if scrict_name in repo.untracked_files: file_status = 'file is not tracked'
49 | elif scrict_name in changed: file_status = 'file changed'
50 | else: file_status = ''
51 | if repo.is_dirty(untracked_files=True): file_status += ', repos is dirty'
52 | git_string = '
Git: %s, %s' % (commit_hash,file_status)
53 |
54 | report_footer = f'''
55 |
56 | File: {scrict_name} {git_string}
57 |
58 | Generated on {str(datetime.datetime.today())}
59 | '''
60 |
61 | return REPORT_STYLE + report_header + self.body + report_footer
62 |
63 | def AsPDF(self, pdf_file_name='link_budget.pdf'):
64 | html_rep = self.HtmlString()
65 |
66 | import pdfkit
67 | # Todo: find a more flexible option
68 | config = pdfkit.configuration(wkhtmltopdf=r'C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe')
69 | pdfkit.from_string(html_rep, pdf_file_name, configuration=config)
70 |
71 | def AsHtmlPage(self, html_file_name='link_budget.html'):
72 | html_rep = self.HtmlString()
73 | with open(html_file_name,'w') as html_file: html_file.write(html_rep)
74 |
75 | def FormatSI(self, value:float):
76 | value_range = [1e0,1e3,1e6,1e9,1e12]
77 | prefix = ['m', '', 'K', 'M', 'G', 'T']
78 |
79 | index = 0
80 | while value > value_range[index]:
81 | index+=1
82 | if index == len(value_range): break
83 | value = value / value_range[index-1]
84 | prefix[index]
85 |
86 | return f'{value:.4g} {prefix[index]}'
87 |
88 |
89 |
90 | class Table:
91 | def __init__(self, content:list, colapse_identical_row:bool=False):
92 | self.content = content
93 | self.colapse_identical_row = colapse_identical_row
94 |
95 | def ColapseRow(self,row:list[str]):
96 | if self.colapse_identical_row:
97 |
98 | last = row[0]
99 | row_html = ''
100 | span_counter = 1
101 |
102 | def comit_last(row_html):
103 | if span_counter > 1: #More than one span, use the html attribute
104 | row_html += '%s | ' % (span_counter, last)
105 | else: row_html += '%s | ' % (last)
106 | return row_html
107 |
108 | for item in row[1:]:
109 | if item == last: # New item is identical to previous one:
110 | span_counter += 1 # We need the span to be longer
111 |
112 | else: # new item is different, comit the span using last
113 | row_html = comit_last(row_html)
114 | span_counter = 1 # back to single span
115 | last = item # current item is saved for later, next span comit
116 |
117 | # Last item need to be added
118 | row_html = comit_last(row_html)
119 |
120 | else:
121 | row_html = ''.join(['%s | ' % col for col in row])
122 |
123 | return row_html
124 |
125 |
126 | def Html(self,html_class='time') -> str:
127 | self.html_rep = ''
128 | for row in self.content:
129 | self.html_rep += '' + self.ColapseRow(row) + '
'
130 | self.html_rep = '' % html_class + self.html_rep + '
'
131 | return self.html_rep
132 |
133 | class CapacityVsRange(Report):
134 |
135 | @dataclasses.dataclass
136 | class InputsCase:
137 | range_start : float
138 | range_end : float
139 | optical_power : float
140 | wavelength : float
141 | beam_divergence : float
142 | receive_diameter : float
143 | APD_gain : float
144 | APD_responsivity : float
145 | APD_bandwidth : float
146 | APD_excess_noise : float
147 | APD_dark_current : float
148 | APD_current_noise : float = 0.0
149 | target_BER : float = 1e-5
150 | M2_factor : float = 1.0
151 | pointing_error : float = 0.0
152 | datarate_to_BW : float = 1.0
153 | db_losses : list[float] = dataclasses.field(default_factory=list)
154 | nametag : str = ''
155 | plot_legend : str = ''
156 |
157 | def textList(self) -> list[str]:
158 | return [
159 | self.nametag,
160 | f'{self.range_start/1000:,.0f} km to {self.range_end/1000:,.0f} km',
161 | f'{self.optical_power*1000:,.0f} mW',
162 | f'{self.wavelength*1e9:.02f} nm',
163 | f'{self.beam_divergence*1e6:.2f} urad 1/e2',
164 | f'{self.receive_diameter*1e3:.2f} mm',
165 | f'{self.APD_gain:.1f}',
166 | f'{self.APD_responsivity:.2f} A/W',
167 | f'{self.APD_bandwidth/1e6:,.0f} MHz',
168 | f'{self.APD_excess_noise:.2f}',
169 | f'{self.APD_dark_current*1e9:.2f} nA',
170 | f'{self.APD_current_noise*1e12:.2f} pA/rtHz',
171 | f'10E{np.log10(self.target_BER):.1f}',
172 | f'{self.pointing_error*1e6:.2f} urad',
173 | f'{self.M2_factor:.2f}',
174 | f'{self.datarate_to_BW:.2f} b/Hz',
175 | ]
176 |
177 | def copy(self) -> 'CapacityVsRange.InputsCase': return dataclasses.replace(self)
178 |
179 | def __init__(self,cases:list[InputsCase] = [], n_points=1000, **karg):
180 | super().__init__(**karg)
181 | self.cases = cases
182 | self.n_points = n_points
183 |
184 | def AddCase(self, case:InputsCase):
185 | self.cases.append(case)
186 | if case.nametag == '': case.nametag = f'Case {len(self.cases)}'
187 |
188 | def MakeCasesTable(self) -> 'Table':
189 | NAME_LIST = [
190 | 'Case Name',
191 | 'Range',
192 | 'Optical power',
193 | 'Wavelength',
194 | 'Beam divergence',
195 | 'Receiver diameter',
196 | 'APD gain',
197 | 'APD responsivity',
198 | 'APD bandwidth',
199 | 'APD excess noise ratio',
200 | 'APD dark current',
201 | 'APD amplifer noise density',
202 | 'Target uncoded BER',
203 | 'Static pointing error',
204 | 'M2 quality factor',
205 | 'Throughput to Bandwidth ratio',
206 | ]
207 |
208 | param_list = list(zip(NAME_LIST, *[case.textList() for case in self.cases]))
209 |
210 | losses = {}
211 |
212 | for case_index in range(len(self.cases)):
213 |
214 | case = self.cases[case_index]
215 |
216 | for loss_name, loss_value in case.db_losses:
217 |
218 | if not loss_name in losses: losses[loss_name] = [0]*len(self.cases)
219 |
220 | losses[loss_name][case_index] = loss_value
221 |
222 | param_list += [[key]+[f'{v:.1f} dB' for v in vals] for key, vals in losses.items()]
223 |
224 |
225 | return Table(param_list, colapse_identical_row=1)
226 |
227 | def ConcatenateCases(self):
228 | '''Copy over the fileds from the case list, and concatenate them as numpy arrays using the second dimention as case index'''
229 |
230 | for fd in dataclasses.fields(self.InputsCase):
231 |
232 | thing_list = [getattr(case, fd.name) for case in self.cases]
233 |
234 | if fd.name in ('nametag','plot_legend'): # The names are just concatenated ins normal python lists
235 | self.__setattr__(fd.name, thing_list)
236 | elif fd.name == 'db_losses': # Losses needs to be added up first
237 | db_losses = [sum(tuple(zip(*loss_list))[1]) for loss_list in thing_list]
238 | self.db_losses = np.array(db_losses)
239 | else: # it's a value that should be a numpy array, wioth second axis as case index
240 | self.__setattr__(fd.name, np.array(thing_list))
241 |
242 | #self.range_start = np.array([case.range_start for case in self.cases])[np.newaxis,:]
243 |
244 | def AddParametersTable(self): self.AddHtml('Parameters
' + self.MakeCasesTable().Html())
245 |
246 | def Run(self,ranges=None):
247 |
248 | # Generate the inut arrays
249 | self.ConcatenateCases()
250 |
251 | # Log scale link ranges vector
252 | if not ranges: link_range = np.logspace(np.log10(self.range_start),np.log10(self.range_end),self.n_points)
253 | else: link_range = np.array(ranges)[:,np.newaxis]
254 |
255 | # Position error at receiver
256 | r = np.tan(self.pointing_error)*link_range
257 |
258 | # Beam waist
259 | W_0 = olb.divergence_to_radius(self.beam_divergence,self.wavelength)
260 |
261 | # Angular wave number, = 2*pi/lambda
262 | k = olb.angular_wave_number(self.wavelength)
263 |
264 | # Range loss for a gaussian beam
265 | range_loss = olb.path_loss_gaussian(W_0, self.wavelength, link_range, self.receive_diameter, r, self.M2_factor)
266 |
267 | # Addingup all losses
268 | all_losses = range_loss-self.db_losses
269 |
270 | # Received power
271 | P_rx_avg = self.optical_power*10**(all_losses/10)
272 |
273 | # Declare photodetector
274 | pd = olb.Photodiode(
275 | gain=self.APD_gain ,
276 | responsivity=self.APD_responsivity,
277 | bandwidth=self.APD_bandwidth,
278 | excess_noise_factor=self.APD_excess_noise,
279 | dark_current=self.APD_dark_current,
280 | amp_noise_density=self.APD_current_noise)
281 |
282 | # Estimate required bandwidth for givien BER
283 | detector_bw = olb.suported_bandwidth_OOK(pd,P_rx_avg,self.target_BER)
284 |
285 | # Supported bandwidth is at most hardware bandwidth
286 | detector_bw = olb.filter_maximum(detector_bw, self.APD_bandwidth, 1)
287 |
288 | # Data is up to 2 time faster than bandwidth
289 | datarate = self.datarate_to_BW*detector_bw
290 |
291 | return link_range, datarate
292 |
293 | def GetThroughputFigure(self) -> tuple[Figure, Axes]:
294 |
295 | link_range, datarate = self.Run()
296 |
297 | fig = plt.figure()
298 | ax = fig.add_subplot(111)
299 | index = 0
300 | for case_name in self.plot_legend:
301 | ax.loglog(link_range[:,index]/1e3, datarate[:,index]/1e6,label=case_name)
302 | index +=1
303 | if index > 1: ax.legend()
304 | ax.set_xlabel('Link range, km')
305 | ax.set_ylabel('Throughput, Mbps')
306 |
307 | return fig, ax
308 |
309 |
310 | def AddSampleTable(self, link_ranges:[int]):
311 |
312 | _, datarate = self.Run(link_ranges)
313 |
314 | sample_list = []
315 |
316 | sample_list += [['Range'] + [case.nametag for case in self.cases]]
317 |
318 | range_index = 0
319 | for range in link_ranges:
320 | sample_list += [[f'{range*1e-3:,.2f} km'] + [f'{self.FormatSI(dt)}bps' for dt in datarate[range_index,:]]]
321 | range_index += 1
322 |
323 |
324 | self.AddHtml(Table(sample_list).Html())
325 |
326 |
327 | def GetReport(self):
328 | return self.rep
329 |
330 | REPORT_STYLE = '''
331 |
368 | '''
369 |
--------------------------------------------------------------------------------
/OLBtools/scintillation.py:
--------------------------------------------------------------------------------
1 | from . import *
2 |
3 | import numpy as np
4 |
5 | import scipy.special as scsp
6 | import scipy.optimize as scop
7 | import scipy.integrate as scin
8 |
9 | #----------------------------------------------------------
10 | # Cn2 Models
11 |
12 | def Cn2_SLC(h):
13 | # Submarine Laser Comunication (SLC) model, day and night, for h > 1500m
14 | # For some reason, the SLC model is for the atmosphere, and is based on the median values for the AIR Force Maui Optical Station
15 | # Laser Beam Propagation through random Media, 2nd edition Larry C. Andrews and Ronald L. Phillips page 481-482
16 | return (1500<=h)*(h<7200)*8.87e-7/h**3 + (7200<=h)*(h<20000)*2e-16/h**(1/2)
17 |
18 | def Cn2_HV_ACTLIMB(h, H0):
19 | # ACTLIMB (Hufnagel-Valley 5/7 with Gurvich
20 | HV = 1.7e-14*np.exp(-h/100) + 2.7e-16*np.exp(-h/1500) + 3.6e-3*(1e-5*h)**10*np.exp(-h/1000)
21 | Nh2 = 1e-7*np.exp(-2*h/H0)
22 | Ck = 1e-10*10**(h/25e3)
23 | return HV + Nh2*Ck
24 |
25 | def Cn2_HV_57(h):
26 | # Hufnagel-Valley 5/7
27 | HV = 1.7e-14*np.exp(-h/100) + 2.7e-16*np.exp(-h/1500) + 3.6e-3*(1e-5*h)**10*np.exp(-h/1000)
28 | return HV
29 |
30 | def Cn2_HV_ACTLIMB_best(h):
31 | # ACTLIMB best (x0.1)
32 | return 0.1*Cn2_HV_ACTLIMB(h)
33 |
34 | def Cn2_HV_ACTLIMB_worst(h):
35 | # ACTLIMB worst (x10)
36 | return 10*Cn2_HV_ACTLIMB(h)
37 |
38 | def Cn2_HV_best(h):
39 | # HV57 best (x0.1)
40 | return 0.1*Cn2_HV_57(h)
41 |
42 | def Cn2_HV_worst(h):
43 | # HV57 worst (x10)
44 | return 10*Cn2_HV_57(h)
45 |
46 | #----------------------------------------------------------
47 |
48 | def Rytov_var(Cn2_h,k,L):
49 | # Laser Beam Propagation through random Media, 2nd edition Larry C. Andrews and Ronald L. Phillips page 140
50 | return 1.23*Cn2_h*k**(5/7)*L**(11/6)
51 |
52 | def Fried_param(zenith,k,Cn2,h_0,H,n_int=1000):
53 | '''Fried's pararamter for a ground station.
54 | Andrews Ch12, p492, Eq23
55 | zenith: zenith angle in radians
56 | k: angular wave number
57 | Cn2: function of h
58 | h_0: ground sation altitude
59 | H: target altitude'''
60 |
61 | # Integration range
62 | h = np.linspace(h_0,np.minimum(H,20e3),n_int,axis=-1)
63 |
64 | # Integral term
65 | integ = np.trapz(Cn2(h),h,axis=-1)
66 |
67 | return (0.42/np.cos(zenith)*k**2*integ)**(-3/5)
68 |
69 | def normalized_distance_uplink(h,h_0,H):
70 | '''Normalized diatance, uplink case, for use in Andrews ch12
71 | Andrews Ch12, p490, Eq14'''
72 | return 1-(h-h_0)/(H-h_0)
73 |
74 | def gaussian_beam_parameters_at_waist(L,k,W_0):
75 | G_Lambda_0 = (2*L)/(k*W_0**2)
76 | G_Theta_0 = 1
77 | return G_Lambda_0,G_Theta_0
78 |
79 | def gaussian_beam_parameters_colimated(L,k,W_0):
80 | G_Lambda_0,G_Theta_0 = gaussian_beam_parameters_at_waist(L,k,W_0)
81 | divisor = G_Lambda_0**2 + G_Theta_0**2
82 | G_Lambda = G_Lambda_0/divisor
83 | G_Theta = G_Theta_0/divisor
84 | return G_Lambda,G_Theta
85 |
86 | def mu3u_par(G_Lambda,G_Theta,Cn2,h_0,H,n_int=1000):
87 | G_Lambda,G_Theta,h_0_i,H_i = np.broadcast_arrays(G_Lambda,G_Theta,h_0,H)
88 |
89 | # Integration range
90 | #h = np.linspace(h_0_i,H_i,n_int,axis=-1)
91 | h = np.linspace(h_0,np.minimum(H,20e3),n_int,axis=-1)
92 |
93 | G_Lambda = G_Lambda[..., np.newaxis]
94 | G_Theta = G_Theta[..., np.newaxis]
95 | h_0_i = h_0_i[..., np.newaxis]
96 | H_i = H_i[..., np.newaxis]
97 |
98 | # Integration variable
99 | hnu = normalized_distance_uplink(h,h_0_i,H_i)
100 |
101 | #Eq 55
102 | mu3u_to_integ = Cn2(h)*(
103 | (hnu*(G_Lambda*hnu + 1j*(1-(1-G_Theta)*hnu)))**(5/6)
104 | - G_Lambda**(5/6)*hnu**(5/3) )
105 | mu3u = np.real(np.trapz(mu3u_to_integ,h,axis=-1))
106 |
107 | return mu3u
108 |
109 | def mu2d_par(Cn2,h_0,H,n_int=1000):
110 | h_0_i,H_i = np.broadcast_arrays(h_0,H)
111 |
112 | # Integration range
113 | #h = np.linspace(h_0_i,H_i,n_int,axis=-1)
114 | h = np.linspace(h_0,np.minimum(H,20e3),n_int,axis=-1)
115 |
116 | h_0_i = h_0_i[..., np.newaxis]
117 | H_i = H_i[..., np.newaxis]
118 |
119 | # Integration variable
120 | hnu = 1-normalized_distance_uplink(h,h_0_i,H_i)
121 |
122 | #Eq 55
123 | mu2d_to_integ = Cn2(h)*hnu**(5/3)
124 | mu2d = np.real(np.trapz(mu2d_to_integ,h,axis=-1))
125 |
126 | return mu2d
127 |
128 | def scintillation_weak_uplink_tracked(G_Lambda,G_Theta,Cn2,h_0,H,zenith,k,n_int=1000):
129 | '''Sicntillation under weak fluctuation theory for a tracked uplink
130 | Andreaws ch12, p503, eq55, and p504, eq58.
131 | G_Lambda and G_Theta: Gausian beam parrameters, per Andrews Ch12 p489 eq9
132 | Cn2: function of h
133 | h_0: ground sation altitude
134 | H: target altitude
135 | return normalized scintillation index squared'''
136 |
137 | mu3u = mu3u_par(G_Lambda,G_Theta,Cn2,h_0,H,n_int=1000)
138 |
139 | #Eq 58
140 | sigBu2 = 8.70*mu3u*k**(7/6)*(H-h_0)**(5/6)/np.cos(zenith)**(11/6)
141 |
142 | return sigBu2
143 |
144 | def scintillation_weak_uplink_tracked_alt(W_0,Cn2,h_0,H,zenith,k,n_int=1000):
145 | '''Sicntillation under weak fluctuation theory for a tracked uplink
146 | Andreaws ch12, p503, eq55, and p504, eq58.
147 | G_Lambda and G_Theta: Gausian beam parrameters, per Andrews Ch12 p489 eq9
148 | Cn2: function of h
149 | h_0: ground sation altitude
150 | H: target altitude
151 | return normalized scintillation index squared'''
152 |
153 | W_0,k_i,h_0_i,H_i = np.broadcast_arrays(W_0,k,h_0,H)
154 |
155 | # Integration range
156 | #h = np.linspace(h_0_i,H_i,n_int,axis=-1)
157 | h = np.linspace(h_0,np.minimum(H,20e3),n_int,axis=-1)
158 |
159 | W_0 = W_0[..., np.newaxis]
160 | k_i = k_i[..., np.newaxis]
161 | h_0_i = h_0_i[..., np.newaxis]
162 | H_i = H_i[..., np.newaxis]
163 |
164 | # Integration variable
165 | hnu = normalized_distance_uplink(h,h_0_i,H_i)
166 |
167 | G_Lambda,G_Theta = gaussian_beam_parameters_colimated(h,k_i,W_0)
168 |
169 | #Eq 55
170 | mu3u_to_integ = Cn2(h)*(
171 | (hnu*(G_Lambda*hnu + 1j*(1-(1-G_Theta)*hnu)))**(5/6)
172 | - G_Lambda**(5/6)*hnu**(5/3) )
173 | mu3u = np.real(np.trapz(mu3u_to_integ,h,axis=-1))
174 |
175 | #Eq 58
176 | sigBu2 = 8.70*mu3u*k**(7/6)*(H-h_0)**(5/6)
177 |
178 | return sigBu2
179 |
180 | def scintillation_downlink_alt(W_0,Cn2,h_0,H,zenith,k,n_int=1000):
181 | '''Sicntillation under weak fluctuation theory for a tracked uplink
182 | Andreaws ch12, p503, eq55, and p504, eq58.
183 | G_Lambda and G_Theta: Gausian beam parrameters, per Andrews Ch12 p489 eq9
184 | Cn2: function of h
185 | h_0: ground sation altitude
186 | H: target altitude
187 | return normalized scintillation index squared'''
188 |
189 | W_0,k_i,h_0_i,H_i = np.broadcast_arrays(W_0,k,h_0,H)
190 |
191 | # Integration range
192 | #h = np.linspace(h_0_i,H_i,n_int,axis=-1)
193 | h = np.linspace(h_0,np.minimum(H,20e3),n_int,axis=-1)
194 |
195 | h_0_i = h_0_i[..., np.newaxis]
196 |
197 | sigR2int = np.trapz(Cn2(h)*(h-h_0_i)**(5/6),h,axis=-1)
198 |
199 | #Eq 38
200 | sigR2 = 2.25*k**(7/6)*sigR2int/np.cos(zenith)**(11/6)
201 |
202 | return sigR2
203 |
204 | def scintillation_uplink_tracked(G_Theta,sigBu2):
205 | '''Sicntillation without weak theory restrictions for a tracked uplink
206 | Andreaws ch12, p506, eq60.
207 | G_Theta: Gausian beam parrameter, per Andrews Ch12 p489 eq9
208 | sigBu2: normalized scintillation index squared, logitudinal axis, under weak fluctuation theory
209 | return normalized scintillation index squared'''
210 |
211 | sigIltracked2 = np.exp(
212 | 0.49*sigBu2/(1+(1+G_Theta)*0.56*sigBu2**(6/5))**(7/6)
213 | + 0.51*sigBu2/(1+0.69*sigBu2**(6/5))**(5/6)
214 | ) - 1
215 |
216 | return sigIltracked2
217 |
218 | def scintillation_uplink_tracked_xy(G_Theta,sigBu2):
219 | '''Sicntillation without weak theory restrictions for a tracked uplink
220 | Andreaws ch12, p506, eq60.
221 | G_Theta: Gausian beam parrameter, per Andrews Ch12 p489 eq9
222 | sigBu2: normalized scintillation index squared, logitudinal axis, under weak fluctuation theory
223 | return normalized scintillation index, small and large scale, squared'''
224 |
225 | sigIltracked2_x = np.exp(0.49*sigBu2/(1+(1+G_Theta)*0.56*sigBu2**(6/5))**(7/6)) - 1
226 | sigIltracked2_y = np.exp(0.51*sigBu2/(1+0.69*sigBu2**(6/5))**(5/6)) - 1
227 |
228 | return sigIltracked2_x,sigIltracked2_y
229 |
230 | def scintillation_downlink_xy(sigR2):
231 | '''Sicntillation for a downlink
232 | Andreaws ch12, p511, eq68.
233 | sigR2: scintillation index for square plane, squared, per Andrews Ch12 p495 eq38
234 | return normalized scintillation index, small and large scale, squared'''
235 |
236 | sigIltracked2_x = np.exp(0.49*sigR2/(1+1.11*sigR2**(6/5))**(7/6)) - 1
237 | sigIltracked2_y = np.exp(0.51*sigR2/(1+0.69*sigR2**(6/5))**(5/6)) - 1
238 |
239 | return sigIltracked2_x,sigIltracked2_y
240 |
241 | def pointing_error_variance(h_0,H,zenith,W_0,k,r_0,Cr=2*np.pi):
242 | '''Pointing error variance under weak fluctuation theory for uplink
243 | Andreaws ch12, p503, eq53, second approximation.
244 | h_0: ground sation altitude, m
245 | H: target altitude, m
246 | zenith: zenith angle in radians
247 | W_0: 1/e2 beam radius at transmit, m
248 | k: angular wave number
249 | r_0: Fired's parrameter, m
250 | '''
251 |
252 | a = Cr**2*W_0**2/r_0**2
253 | sigpe2 = 0.54*(H-h_0)**2/np.cos(zenith)**2*(np.pi/(W_0*k))**2*(2*W_0/r_0)**(5/3) \
254 | *(1-(a/(1+a))**(1/6))
255 |
256 | return sigpe2
257 |
258 | def pointing_error_variance_alt(h_0,H,zenith,W_0,k,r_0,Cr=2*np.pi,n_int=1000):
259 | '''Pointing error variance under weak fluctuation theory for uplink
260 | Andreaws ch12, p503, eq53, second approximation.
261 | h_0: ground sation altitude, m
262 | H: target altitude, m
263 | zenith: zenith angle in radians
264 | W_0: 1/e2 beam radius at transmit, m
265 | k: angular wave number
266 | r_0: Fired's parrameter, m
267 | '''
268 |
269 | h_0_i,H_i = np.broadcast_arrays(h_0,H)
270 |
271 | a = Cr**2*W_0**2/r_0**2
272 |
273 | h = np.linspace(h_0_i,np.minimum(H_i,20e3),n_int,axis=-1)
274 |
275 | h_0_i = h_0_i[..., np.newaxis]
276 | H_i = H_i[..., np.newaxis]
277 |
278 | # Integration variable
279 | hnu = normalized_distance_uplink(h,h_0_i,H_i)
280 |
281 | #Eq 55
282 | sigpe2_integ = np.trapz(Cn2(h)*hnu**2,h,axis=-1)
283 |
284 | sigpe2 = 0.725*(H-h_0)**2/np.cos(zenith)**3*W_0**(-1/3) \
285 | *(1-(a/(1+a))**(1/6))*sigpe2_integ
286 |
287 | return sigpe2
288 |
289 | def scintillation_uplink_untracked(sigpe2,h_0,H,zenith,W_0,r_0,L,r,sigIltracked2,W):
290 |
291 | sigpe = np.sqrt(sigpe2)
292 |
293 | sigIluntracked2 = 5.95*(H-h_0)**2/np.cos(zenith)**2*(2*W_0/r_0)**(5/3) \
294 | *( ((r-sigpe)/(L*W))**2*((r-sigpe) > 0) + (sigpe/(L*W))**2 ) \
295 | + sigIltracked2
296 |
297 | return sigIluntracked2
298 |
299 | def get_scintillation_uplink_untracked(h_0,H,zenith,k,W_0,Cn2,r,Cr=2*np.pi):
300 |
301 | L = slant_range(h_0,H,zenith,Re)
302 |
303 | G_Lambda,G_Theta = gaussian_beam_parameters_colimated(L,k,W_0)
304 |
305 | W = beam_radius(W_0,G_Theta,G_Lambda)
306 |
307 | sigBu2 = scintillation_weak_uplink_tracked(G_Lambda,G_Theta,Cn2,h_0,H,zenith,k)
308 |
309 | sigIltracked2 = scintillation_uplink_tracked(G_Theta,sigBu2)
310 |
311 | r_0 = Fried_param(zenith,k,Cn2,h_0,H)
312 |
313 | sigpe2 = pointing_error_variance(h_0,H,zenith,W_0,k,r_0,Cr)
314 |
315 | sigIluntracked2 = scintillation_uplink_untracked(sigpe2,h_0,H,zenith,W_0,r_0,L,r,sigIltracked2,W)
316 |
317 | return sigIluntracked2
318 |
319 | def get_scintillation_uplink_untracked_xy(h_0,H,zenith,k,W_0,Cn2,r,Cr=2*np.pi):
320 |
321 | L = slant_range(h_0,H,zenith,Re)
322 |
323 | G_Lambda,G_Theta = gaussian_beam_parameters_colimated(L,k,W_0)
324 |
325 | W = beam_radius(W_0,G_Theta,G_Lambda)
326 |
327 | sigBu2 = scintillation_weak_uplink_tracked(G_Lambda,G_Theta,Cn2,h_0,H,zenith,k)
328 |
329 | sigIltracked2_x,sigIltracked2_y = scintillation_uplink_tracked_xy(G_Theta,sigBu2)
330 |
331 | r_0 = Fried_param(zenith,k,Cn2,h_0,H)
332 |
333 | sigpe2 = pointing_error_variance(h_0,H,zenith,W_0,k,r_0,Cr)
334 |
335 | sig2_x = scintillation_uplink_untracked(sigpe2,h_0,H,zenith,W_0,r_0,L,r,sigIltracked2_x,W)
336 |
337 | sig2_y = sigIltracked2_y
338 |
339 | sig2_x, sig2_y = np.broadcast_arrays(sig2_x, sig2_y)
340 |
341 | return apply_min(sig2_x), apply_min(sig2_y)
342 |
343 | def get_scintillation_uplink_tracked_xy(h_0,H,zenith,k,W_0,Cn2,Cr=2*np.pi):
344 |
345 | L = slant_range(h_0,H,zenith,Re)
346 |
347 | G_Lambda,G_Theta = gaussian_beam_parameters_colimated(L,k,W_0)
348 |
349 | W = beam_radius(W_0,G_Theta,G_Lambda)
350 |
351 | sigBu2 = scintillation_weak_uplink_tracked(G_Lambda,G_Theta,Cn2,h_0,H,zenith,k)
352 |
353 | sigIltracked2_x,sigIltracked2_y = scintillation_uplink_tracked_xy(G_Theta,sigBu2)
354 |
355 | sig2_x = sigIltracked2_x
356 |
357 | sig2_y = sigIltracked2_y
358 |
359 | return apply_min(sigIltracked2_x), apply_min(sigIltracked2_y)
360 |
361 | def get_scintillation_downlink_xy(h_0,H,zenith,k,W_0,Cn2,Cr=2*np.pi):
362 |
363 | sigR2 = scintillation_downlink_alt(W_0,Cn2,h_0,H,zenith,k)
364 |
365 | sigIltracked2_x,sigIltracked2_y = scintillation_downlink_xy(sigR2)
366 |
367 | sig2_x = sigIltracked2_x
368 |
369 | sig2_y = sigIltracked2_y
370 |
371 | return apply_min(sigIltracked2_x), apply_min(sigIltracked2_y)
372 |
373 | def gamma_gamma_distrib_pdf(sig2_x,sig2_y,Ie,I):
374 |
375 | alpha = 1/mpin(sig2_x)
376 | beta = 1/mpin(sig2_y)
377 | Ie = mpin(Ie)
378 | I = mpin(I)
379 |
380 | avg = (alpha+beta)/2
381 |
382 | P_I = 2*(alpha*beta)**avg/(gamma(alpha)*gamma(beta)*I)*(I/Ie)**avg*besselk(alpha-beta,2*np.sqrt(alpha*beta*I/Ie))
383 |
384 | return mpout(P_I)
385 |
386 | def gamma_gamma_distrib_cdf_direct(sig2_x,sig2_y,Ie,It):
387 | alpha = 1/mpin(sig2_x)
388 | beta = 1/mpin(sig2_y)
389 | It = mpin(It)/mpin(Ie)
390 |
391 | P_I = np.pi/(mpsin(np.pi*(alpha-beta))*gamma(alpha)*gamma(beta)) \
392 | *( (alpha*beta*It)**beta /( beta*gamma(beta-alpha+1))*hyp1F2( beta, beta+1,beta-alpha+1,alpha*beta*It) \
393 | -(alpha*beta*It)**alpha/(alpha*gamma(alpha-beta+1))*hyp1F2(alpha,alpha+1,alpha-beta+1,alpha*beta*It) )
394 |
395 | return mpout(P_I)
396 |
397 | def gamma_gamma_distrib_cdf_hypercomb(sig2_x,sig2_y,Ie,It):
398 |
399 | pdf = gamma_gamma_distrib_pdf(sig2_x,sig2_y,Ie,I)
400 |
401 | mphypercomb = np.frompyfunc(lambda i,o: mp.hypercomb(i,o,zeroprec=5),2,1) #,verbose=True
402 | array_of_list = np.frompyfunc(lambda u:[u],1,1)
403 |
404 | alpha = 1/mpin(sig2_x)
405 | beta = 1/mpin(sig2_y)
406 |
407 | It = mpin(It)/mpin(Ie)
408 |
409 | def comb_param_function(a,b,I):
410 | tpl1 = ([a*b*I, 1/b], [b, 1], [], [a, b, b-a+1], [b], [b+1, b-a+1], a*b*I)
411 | tpl2 = ([a*b*I, -1/a], [a, 1], [], [a, b, a-b+1], [a], [a+1, a-b+1], a*b*I)
412 | return [tpl1,tpl2]
413 |
414 | hyp_inputs = array_of_list(alpha)+array_of_list(beta)+array_of_list(It)
415 |
416 | P_I = np.pi/(mpsin(np.pi*(alpha-beta))) * mphypercomb(comb_param_function, hyp_inputs)
417 | return mpout(P_I)
418 |
419 |
420 | def gamma_gamma_to_alpha_mu(sig2_x,sig2_y,orders=[2,3],add=False,I0=1):
421 | lgamma = scsp.gammaln
422 | def select_lsq(fun,x0,jac,bounds): return scop.least_squares(fun,x0,jac,bounds,'trf',ftol=1e-15,xtol=1e-15,gtol=1e-15,max_nfev=1000)
423 | array_lsq = np.frompyfunc(select_lsq,4,1)
424 |
425 | bdshape = list(np.broadcast(sig2_x,sig2_y).shape)
426 |
427 | def EXn(n,sigx,sigy,I0):
428 | a = 1/sigx
429 | b = 1/sigy
430 | return scsp.gammaln(a+n) + scsp.gammaln(b+n) - scsp.gammaln(a) - scsp.gammaln(b) - np.log(a*b/I0)*n
431 |
432 | ERgg = [EXn(nx,sig2_x,sig2_y,I0) for nx in orders]
433 |
434 | def ERalphamu(x,ERn,orders):
435 | alpha,mu = x
436 | ERau = [(nx-1)*lgamma(mu)+lgamma(mu+nx/alpha)-nx*lgamma(mu+1/alpha) for nx in orders]
437 | return [ERau[0]-ERn[0],ERau[1]-ERn[1]]
438 |
439 | def Jacobian(x,orders):
440 | alpha,mu = x
441 | digmu = scsp.digamma(mu)
442 | dign = [scsp.digamma(mu+nx/alpha) for nx in orders]
443 | dig1 = scsp.digamma(mu+1/alpha)
444 | return np.array([[ nx/alpha**2*(dig1-dignx), (nx-1)*digmu+dignx-nx*dig1] for nx, dignx in zip(orders,dign)])
445 |
446 | if add:
447 | func = np.frompyfunc(lambda ER0,ER1: lambda x:ERalphamu(x,[ER0,ER1],orders),2,1)(ERgg[0],ERgg[1])
448 | initc = np.frompyfunc(lambda alpha0,mu0:[alpha0,mu0],2,1)(0.5,1)
449 | bnds = np.frompyfunc(lambda a0,m0,a1,m1:([a0,m0],[a1,m1]),4,1)(min_log,min_log,np.inf,np.inf)
450 | else:
451 | func = np.frompyfunc(lambda ER0,ER1: lambda x:ERalphamu(x,[ER0,ER1],orders),2,1)(ERgg[0],ERgg[1])
452 | initc = np.frompyfunc(lambda alpha0,mu0:[alpha0,mu0],2,1)(0.5*np.ones(bdshape),np.sqrt(1/sig2_x/sig2_y))
453 | bnds = np.frompyfunc(lambda a0,m0,a1,m1:([a0,m0],[a1,m1]),4,1)(min_log*np.ones(bdshape),min_log*np.ones(bdshape),np.inf,np.inf)
454 | jac = lambda x:Jacobian(x,orders)
455 |
456 | res = array_lsq(func,initc,jac,bnds)
457 | resa = np.frompyfunc(lambda obj:tuple(obj.x),1,2)(res)
458 | resst = np.frompyfunc(lambda obj:obj.status,1,1)(res)
459 | #assert np.all(resst != 0)
460 | alpha, mu = resa
461 | mu = mu.astype(np.float64)
462 | alpha = alpha.astype(np.float64)
463 | r = np.exp( np.log(mu)/alpha + lgamma(mu) - lgamma(mu+1/alpha) )
464 |
465 | return (alpha,mu,r)
466 |
467 | def alpha_mu_cdf(alpha,mu,r,I0,It):
468 | gvar = mu*(It/I0/r)**alpha
469 | gvar,mu = np.broadcast_arrays(gvar,mu)
470 | cdf = 1 - scsp.gammaincc(mu,gvar)
471 | return cdf
472 |
473 | def alpha_mu_inv_cdf(alpha,mu,r,I0,P):
474 | gvar = scsp.gammainccinv(mu,P)
475 | It = I0*r*(gvar/mu)**(1/alpha)
476 | return It
477 |
478 | def alpha_mu_pdf(alpha,mu,r,I0,It):
479 | lgmu = np.log(mu)
480 | lgal = np.log(alpha)
481 | lgP = np.log(It/I0/r)
482 | return 1/I0*np.exp( lgal + mu*lgmu + (alpha*mu)*lgP - scsp.gammaln(mu) -mu*(It/I0/r)**alpha )
483 |
484 | def alpha_mu_cdf_sum(alpha,mu,r,Pe,internal_scale,output_scale=None,cumulative=False):
485 |
486 | internal_scale_ax = internal_scale[np.newaxis,:]
487 |
488 | if output_scale is None: output_scale = internal_scale
489 |
490 | cdfs = alpha_mu_cdf(alpha[0],mu[0],r[0],Pe[0],internal_scale_ax)
491 | cdfst = cdfs.transpose().flatten()
492 | covoltmp = np.concatenate([[cdfst[0]],cdfst[1:]-cdfst[:-1]],0)
493 |
494 | if cumulative:
495 | covlog = np.interp(output_scale,internal_scale,np.cumsum(covoltmp))
496 | covolres = np.zeros((len(alpha),len(output_scale)))
497 | covolres[0,:] = covlog
498 |
499 | for i in range(1,len(alpha)):
500 | cdfs = alpha_mu_cdf(alpha[i],mu[i],r[i],Pe[i],internal_scale_ax)
501 | cdfst = cdfs.transpose().flatten()
502 | cdfst = np.concatenate([[cdfst[0]],cdfst[1:]-cdfst[:-1]],0)
503 |
504 | covolval = np.convolve(covoltmp,cdfst,mode='full')[0:(0+len(cdfst))]
505 | covoltmp = covolval
506 |
507 | if cumulative:
508 | covlog = np.interp(output_scale,internal_scale,np.cumsum(covolval))
509 | covolres[i,:] = covlog
510 |
511 | if cumulative: return covolres
512 | else: return np.interp(output_scale,internal_scale,np.cumsum(covolval))
513 |
514 | #def SNR0_shot(i_sig):pass
515 |
516 | #def SNR0_APD(i_sig,sig_n):
517 |
518 | def SNR0_NEP(p_sig,NEP,BW):
519 | return p_sig / (NEP*np.sqrt(BW))
520 |
521 | def SNR0_sig(i_sig,sig_n): return i_sig/sig_n
522 |
523 | def gamma_gamma_BER_NEP_single(sig2_x,sig2_y,Ie,NEP,BW):
524 | def integrand(u):
525 | return gamma_gamma_distrib_pdf(sig2_x,sig2_y,Ie,u)*scsp.erfc(SNR0_NEP(u,NEP,BW)*u/(2*np.sqrt(2)))
526 | try:
527 | return 0.5*scin.quad(integrand,0,np.inf)[0]
528 | except ZeroDivisionError:
529 | return 1
530 | gamma_gamma_BER_NEP = np.frompyfunc(gamma_gamma_BER_NEP_single,5,1)
531 |
532 |
533 | def alpha_mu_BER_NEP_single(alpha,mu,r,Ie,NEP,BW):
534 | def integrand(u):
535 | return alpha_mu_pdf(alpha,mu,r,Ie,u)*scsp.erfc(SNR0_NEP(u,NEP,BW)*u/(2*np.sqrt(2)))
536 | try:
537 | #return 0.5*scin.quad(integrand,0,1e10,points=[Ie])[0]
538 | return 0.5*scin.quad(integrand,0,np.inf)[0]
539 | #return 0.5*np.float(scin.romberg(integrand,0,100))
540 | except ZeroDivisionError:
541 | return 1
542 | alpha_mu_BER_NEP = np.frompyfunc(alpha_mu_BER_NEP_single,6,1)
543 |
544 | def alpha_mu_BER_NEP_fixed(alpha,mu,r,Ie,NEP,BW):
545 | u = np.linspace(0,100,10000)
546 | integrand = alpha_mu_pdf(alpha,mu,r,Ie,u)*scsp.erfc(SNR0_NEP(u,NEP,BW)*u/(2*np.sqrt(2)))
547 | return 0.5*scin.trapz(integrand,u)
548 | alpha_mu_BER_NEP_f = np.frompyfunc(alpha_mu_BER_NEP_fixed,6,1)
549 |
550 | '''def gamma_gamma_distrib_cdf_alt7(sig2_x,sig2_y,Ie,It,orders=[2,3]):
551 | lgamma = scsp.gammaln
552 | array_fsolve = np.frompyfunc(scop.fsolve,2,3)
553 | def select_lsq(fun,x0,jac,bounds): return scop.least_squares(fun,x0,jac,bounds,'trf',ftol=1e-15,xtol=1e-15,gtol=1e-15,max_nfev=1000)
554 | array_lsq = np.frompyfunc(select_lsq,4,1)
555 |
556 | a = 1/sig2_x
557 | b = 1/sig2_y
558 | It = It/Ie
559 |
560 | bdshape = list(np.broadcast(a,b).shape)
561 |
562 | abdiv = lgamma(a+1)+lgamma(b+1)
563 | abmul = lgamma(a)+lgamma(b)
564 | ERgg = [lgamma(a+nx)+lgamma(b+nx)+(nx-1)*abmul-abdiv for nx in orders]
565 |
566 | def ERalphamu(x,ERn,orders):
567 | alpha,mu = x
568 |
569 | ERau = [(nx-1)*lgamma(mu)+lgamma(mu+nx/alpha)-nx*lgamma(mu+1/alpha) for nx in orders]
570 |
571 | return [ERau[0]-ERn[0],ERau[1]-ERn[1]]
572 |
573 | def Jacobian(x,orders):
574 | alpha,mu = x
575 | digmu = scsp.digamma(mu)
576 | dign = [scsp.digamma(mu+nx/alpha) for nx in orders]
577 | return np.array([[ nx*(nx-1)/alpha**2*dignx, (nx-1)*(digmu-dignx)] for nx, dignx in zip(orders,dign)])
578 |
579 | func = np.frompyfunc(lambda ER0,ER1: lambda x:ERalphamu(x,[ER0,ER1],orders),2,1)(ERgg[0],ERgg[1])
580 | initc = np.frompyfunc(lambda alpha0,mu0:[alpha0,mu0],2,1)(0.5*np.ones(bdshape),np.sqrt(a*b))
581 | min_log = 1e-30*np.ones(bdshape)
582 | bnds = np.frompyfunc(lambda a0,m0,a1,m1:([a0,m0],[a1,m1]),4,1)(min_log,min_log,np.inf,np.inf)
583 | jac = lambda x:Jacobian(x,orders)
584 |
585 | print('solving...')
586 | res = array_lsq(func,initc,jac,bnds)
587 | resa = np.frompyfunc(lambda obj:tuple(obj.x),1,2)(res)
588 | resst = np.frompyfunc(lambda obj:obj.status,1,1)(res)
589 | assert np.all(resst != 0)
590 | alpha, mu = resa
591 | mu = mu.astype(np.float64)
592 | alpha = alpha.astype(np.float64)
593 | r = np.exp( np.log(mu)/alpha + lgamma(mu) - lgamma(mu+1/alpha) )
594 | print('done!')
595 |
596 | gvar = mu*(It/r)**alpha
597 | gvar,mu = np.broadcast_arrays(gvar,mu)
598 |
599 | cdf = 1 - scsp.gammaincc(mu,gvar)
600 |
601 | return (cdf,alpha,mu,r)'''
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Optical-Link-Budget
2 | An optical link budget tool, with support for scintillation, beam fit, APDs, and 4 quadrant detectors
3 |
4 | ## installing
5 |
6 | ```
7 | $ pip install git+ssh://git@github.com/pkage/Optical-Link-Budget.git
8 | ```
9 |
--------------------------------------------------------------------------------
/Z136.1.verification.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 |
3 | import matplotlib.pyplot as plt
4 |
5 | import OLBtools.Z136 as z136
6 |
7 | def C_A_verification():
8 | plt.figure()
9 | wavelength=np.linspace(400e-9,1400e-9,1000)
10 | plt.semilogy(wavelength, z136.C_A(wavelength))
11 | plt.xlim(400e-9,1400e-9)
12 | plt.ylim(0.8,12)
13 |
14 | def C_B_verification():
15 | plt.figure()
16 | wavelength=np.linspace(400e-9,600e-9,1000)
17 | plt.semilogy(wavelength, z136.C_B(wavelength))
18 | plt.xlim(400e-9,600e-9)
19 | plt.ylim(0.8,1.2e3)
20 |
21 | def C_C_verification():
22 | plt.figure()
23 | wavelength=np.linspace(1050e-9,1400e-9,1000)
24 | plt.semilogy(wavelength, z136.C_C(wavelength))
25 | plt.xlim(1050e-9,1400e-9)
26 | plt.ylim(0.8,1.2e6)
27 |
28 | def MPE_verification():
29 | plt.figure()
30 | exposure_time = np.logspace(-13,1,1000)
31 | _, retina_mpe_850, _ = z136.ocular_MPE_point_source_NIR(850e-9, exposure_time)
32 | mpe_650 = z136.ocular_MPE_point_source_VIS(650e-9, exposure_time)
33 | plt.loglog(exposure_time,retina_mpe_850*exposure_time*1e-4)
34 | plt.loglog(exposure_time,mpe_650*exposure_time*1e-4)
35 | plt.xlim(1e-13,1e1)
36 | plt.ylim(1e-8,1e-1)
37 |
38 |
39 | C_A_verification()
40 | C_B_verification()
41 | C_C_verification()
42 | MPE_verification()
43 | plt.show()
--------------------------------------------------------------------------------
/example_1.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import OLBtools as olb
3 |
4 | # Transmit
5 | P_tx = 200e-3 # Transmit power laser, W
6 | lambda_gl = 1550e-9 # Laser 1 wavelenght, m
7 | beam_width = 15e-6 # beam width, FWMH radian
8 | pointing_error = 5e-6 # radian
9 | tx_system_loss = 3 # dB
10 |
11 | # Receive
12 | apperture = 95e-3 # Apperture diameter, m
13 | rx_system_loss = 3 # dB
14 |
15 | link_range = 1000e3 # Link distance, m
16 |
17 | # position error at receiver
18 | r = np.tan(pointing_error)*link_range
19 |
20 | # beam waist
21 | W_0 = olb.fwhm_to_radius(beam_width,lambda_gl)
22 |
23 | # Angular wave number, = 2*pi/lambda
24 | k = olb.angular_wave_number(lambda_gl)
25 |
26 | range_loss = olb.path_loss_gaussian(W_0, lambda_gl, link_range, apperture, pointing_error)
27 |
28 | all_losses = range_loss-tx_system_loss-rx_system_loss
29 |
30 | P_rx = P_tx*10**(all_losses/10)
31 |
32 | print('Received power: %.3f uW' % (P_rx*1e6))
--------------------------------------------------------------------------------
/example_2.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import scipy.special as scsp
3 | import matplotlib as mpl
4 | import matplotlib.pyplot as plt
5 | import matplotlib.colors as mpc
6 |
7 | import OLBtools as olb
8 | import OLBtools.scintillation as scint
9 |
10 | # Objectives
11 | elevation_min = olb.radians(00) #00 degrees
12 | elevation_max = olb.radians(90) #90 degrees
13 |
14 | # Orbits
15 | altitude = 500e3 # spacecraft altitude
16 |
17 | # Transmit
18 | P_avg = 0.050 # Transmit power laser, W
19 | lambda_gl = 915e-9 # Laser 1 wavelength, m
20 | beam_width = 1000e-6 # beam width, FWMH radian
21 |
22 | # Receive
23 | aperture = 600e-3 # Aperture diameter, m
24 | aperture_scaling = 0.60 # Fraction of clear aperture
25 |
26 | # Detector
27 | Responsivity = 50 # A.W-1
28 | Fn_apd = 3.2 # Excess noise factor @M=100
29 | i_dark_apd = 1.5e-9
30 |
31 | # Losses
32 | pointing_error = 50e-6 # radian
33 | tx_system_loss = 3.00 # dB (10Log)
34 | rx_system_loss = 3.00 # dB (10Log)
35 |
36 | # Atmosphere
37 | Cn2 = scint.Cn2_HV_57 # Hufnagel-valley 5/7 model
38 |
39 | #----------------------------------------------------------
40 |
41 | #500 poitnts, from specified minimum elevation to top.
42 | elevation = np.linspace(elevation_min,elevation_max,500)
43 | zenith = np.pi/2-elevation
44 |
45 | H = altitude
46 | h_0 = 0
47 |
48 | # Link range for elevation range
49 | link_range = olb.slant_range(h_0,H,zenith,olb.Re)
50 |
51 | # Misspointing as distance at receiver
52 | r_s = np.tan(pointing_error)*link_range
53 |
54 | # 1/e2 beam radius
55 | W_0 = olb.fwhm_to_radius(beam_width,lambda_gl)
56 |
57 | # Wavenumber
58 | k = olb.angular_wave_number(lambda_gl)
59 |
60 | range_loss = olb.path_loss_gaussian(W_0, lambda_gl, link_range, aperture, pointing_error)
61 |
62 | all_losses = range_loss-tx_system_loss-rx_system_loss
63 |
64 | # Expected value of the power at he receiver
65 | Pe = aperture_scaling*P_avg*10**(all_losses/10)
66 | Pe = Pe[:,np.newaxis] #on second dim
67 |
68 | # Scintillation
69 | sig2_x, sig2_y = scint.get_scintillation_downlink_xy(h_0,H,zenith,k,W_0,Cn2)
70 | sig2_x = sig2_x[:,np.newaxis]
71 | sig2_y = sig2_y[:,np.newaxis]
72 |
73 | # Power distribution coeficients
74 | alpha,mu,r = scint.gamma_gamma_to_alpha_mu(sig2_x,sig2_y,orders=[2,3])
75 |
76 | # Power log scale, must include 1% to 99% cumulated power
77 | Hv = np.ceil(np.log10(scint.alpha_mu_inv_cdf(alpha,mu,r,Pe,0.01).max()))
78 | Hl = np.floor(np.log10(scint.alpha_mu_inv_cdf(alpha,mu,r,Pe,0.99).min()))
79 | Ps = np.logspace(Hl,Hv,500)
80 | Psn = Ps[np.newaxis,:]#on second dim
81 | # Sacle interval centers
82 | pdfPs = (Ps[1:]+Ps[:-1])/2
83 | dPs = Ps[1:]-Ps[:-1]
84 | dPs = dPs[:,np.newaxis]
85 |
86 | # Cumulative distribution function
87 | cdfs = scint.alpha_mu_cdf(alpha,mu,r,Pe,Psn)
88 | cdfst = cdfs.transpose()
89 |
90 | # Prpablility densisty function, over log scale intervals
91 | pdfst = cdfst[1:,:]-cdfst[:-1,:]
92 |
93 |
94 | pdfPs = pdfPs[:,np.newaxis]
95 |
96 | pd = olb.Photodiode(
97 | gain=1,
98 | responsivity=Responsivity,
99 | bandwidth=10e6,
100 | excess_noise_factor=Fn_apd,
101 | dark_current=i_dark_apd)
102 | SNR_10 = pd.SNR(pdfPs)
103 | BER_10 = olb.BER_OOK_integrated(SNR_10,pdfst)
104 |
105 | pd = olb.Photodiode(
106 | gain=1,
107 | responsivity=Responsivity,
108 | bandwidth=100e6,
109 | excess_noise_factor=Fn_apd,
110 | dark_current=i_dark_apd)
111 | SNR_100 = pd.SNR(pdfPs)
112 | BER_100 = olb.BER_OOK_integrated(SNR_100,pdfst)
113 |
114 | pd = olb.Photodiode(
115 | gain=1,
116 | responsivity=Responsivity,
117 | bandwidth=1000e6,
118 | excess_noise_factor=Fn_apd,
119 | dark_current=i_dark_apd)
120 | SNR_1000 = pd.SNR(pdfPs)
121 | BER_1000 = olb.BER_OOK_integrated(SNR_1000,pdfst)
122 |
123 | if 1:
124 | plt.figure()
125 | plt.yscale('log')
126 | plt.axhline(1e-5,color='k',linestyle='--',label='$BER = 10^{-5}$')
127 | plt.plot(olb.degrees(elevation),BER_10,label='10 MHz detector')
128 | plt.plot(olb.degrees(elevation),BER_100,label='100 MHz detector')
129 | plt.plot(olb.degrees(elevation),BER_1000,label='1 GHz detector')
130 | plt.xlabel('Elevation, degrees')
131 | plt.axvline(30,color='red')
132 | plt.ylabel('BER')
133 | plt.xlim(0,90)
134 | plt.ylim(1e-15,1)
135 | plt.legend()
136 | plt.title('BER, 3dB')
137 |
138 | def plt_1_distrib(sx,sy,psv,pev,pnv):
139 | alpha,mu,r = scint.gamma_gamma_to_alpha_mu(sx,sy,orders=[2,3])
140 | cdfs = scint.alpha_mu_cdf(alpha,mu,r,pev,pnv)
141 | cdfst = cdfs.transpose()
142 |
143 | p01 = scint.alpha_mu_inv_cdf(alpha,mu,r,pev,0.01)
144 | p10 = scint.alpha_mu_inv_cdf(alpha,mu,r,pev,0.10)
145 | p50 = scint.alpha_mu_inv_cdf(alpha,mu,r,pev,0.50)
146 | p90 = scint.alpha_mu_inv_cdf(alpha,mu,r,pev,0.90)
147 | p99 = scint.alpha_mu_inv_cdf(alpha,mu,r,pev,0.99)
148 |
149 | plt.figure()
150 | plt.yscale('log')
151 | plt.pcolormesh(olb.degrees(elevation),psv, 1-cdfst, cmap = 'RdYlBu')
152 | plt.xlabel('Elevation, degrees')
153 | plt.ylabel('Received power $I_{th}$, W')
154 | #plt.plot(olb.degrees(elevation),pev,color='black',label='Without scintillation')
155 | plt.plot(olb.degrees(elevation),p01,color='black',linestyle='-.',label='1% confidence')
156 | plt.plot(olb.degrees(elevation),p10,color='black',linestyle='--',label='10% confidence')
157 | plt.plot(olb.degrees(elevation),p50,color='black',linestyle=':', label='50% confidence')
158 | plt.plot(olb.degrees(elevation),p90,color='black',linestyle='--',label='90% confidence')
159 | plt.plot(olb.degrees(elevation),p99,color='black',linestyle='-.',label='99% confidence')
160 | plt.ylim([psv[0],psv[-1]])
161 | plt.clim([0,1])
162 | plt.legend(loc=4)
163 | plt.colorbar()
164 |
165 | if 1:
166 | plt_1_distrib(sig2_x,sig2_y,Ps,Pe,Psn)
167 | plt.title('$P(I>I_{th})$, 3dB')
168 | plt.ylim([Ps[0],Ps[-1]])
169 |
170 | plt.show()
--------------------------------------------------------------------------------
/example_3.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import matplotlib as mpl
3 | import matplotlib.pyplot as plt
4 | import matplotlib.colors as mpc
5 |
6 | import OLBtools as olb
7 | import OLBtools.scintillation as scint
8 |
9 | elevation = olb.radians(20) #20 degrees
10 |
11 | # Orbits
12 | altitude = 500e3 # spacecraft altitude
13 |
14 | # Transmit
15 | P_avg = 2.50 # Transmit power laser, W
16 | lambda_gl = 978e-9 # Laser 1 wavelength, m
17 | beam_width_min = 1e-6 # beam width, FWMH radian
18 | beam_width_max = 10e-3
19 |
20 | # Receive
21 | aperture = 95e-3 # aperture diameter, m
22 |
23 | # Losses
24 | pointing_error = 5e-6 # radian
25 | tx_system_loss = 3.00 # dB (10Log)
26 | rx_system_loss = 3.00 # dB (10Log)
27 |
28 | # Atmosphere
29 | Cn2 = scint.Cn2_HV_57 #Hufnagel-valley 5/7 model
30 |
31 | #----------------------------------------------------------
32 | # LINK
33 | #----------------------------------------------------------
34 |
35 | zenith = np.pi/2-elevation
36 |
37 | H = altitude
38 | h_0 = 0
39 |
40 | link_range = olb.slant_range(h_0,H,zenith,olb.Re)
41 | r = np.tan(pointing_error)*link_range
42 | beam_width = np.logspace(np.log10(beam_width_min),np.log10(beam_width_max),200)
43 | W_0 = olb.fwhm_to_radius(beam_width,lambda_gl)
44 | k = olb.angular_wave_number(lambda_gl)
45 |
46 | range_loss = olb.path_loss_gaussian(W_0, lambda_gl, link_range, aperture, pointing_error)
47 | all_losses = range_loss-tx_system_loss-rx_system_loss
48 | Pe = P_avg*10**(all_losses/10)
49 |
50 | Ps = np.logspace(-12,-1,200)
51 | Psn = Ps[np.newaxis,:]
52 | Pe = Pe[:,np.newaxis]
53 |
54 |
55 |
56 | sig2_x, sig2_y = scint.get_scintillation_uplink_untracked_xy(h_0,H,zenith,k,W_0,Cn2,r)
57 | sig2_x = sig2_x[:,np.newaxis]
58 | sig2_y = sig2_y[:,np.newaxis]
59 | alpha,mu,r = scint.gamma_gamma_to_alpha_mu(sig2_x,sig2_y,orders=[2,3])
60 |
61 | def plt_1_distrib(sx,sy,psv,pev,pnv):
62 | alpha,mu,r = scint.gamma_gamma_to_alpha_mu(sx,sy,orders=[2,3])
63 | cdfs = scint.alpha_mu_cdf(alpha,mu,r,pev,pnv)
64 | cdfst = cdfs.transpose()
65 |
66 | p50 = scint.alpha_mu_inv_cdf(alpha,mu,r,pev,0.50)
67 | p90 = scint.alpha_mu_inv_cdf(alpha,mu,r,pev,0.90)
68 | p99 = scint.alpha_mu_inv_cdf(alpha,mu,r,pev,0.99)
69 |
70 | if 1:
71 | plt.figure()
72 | plt.title('$P(I>I_{th})$, with $C_n^2 = 10 \\times HV_{5/7}$')
73 | plt.yscale('log')
74 | plt.xscale('log')
75 | plt.pcolormesh(beam_width*1e3,psv, 1-cdfst, cmap = 'RdYlBu')
76 | plt.xlabel('Divergence, mrad')
77 | plt.ylabel('Received power $I_{th}$, W')
78 | plt.plot(beam_width*1e3,pev,color='black',label='Without scintillation')
79 | plt.plot(beam_width*1e3,p50,color='black',linestyle=':', label='50% confidence')
80 | plt.plot(beam_width*1e3,p90,color='black',linestyle='--',label='90% confidence')
81 | plt.plot(beam_width*1e3,p99,color='black',linestyle='-.',label='99% confidence')
82 | plt.ylim([psv[0],psv[-1]])
83 | plt.legend()
84 | plt.colorbar()
85 |
86 | if 1:
87 | plt_1_distrib(sig2_x,sig2_y,Ps,Pe,Psn)
88 | plt.title('$P(I>I_{th})$, uplink, 20 deg elevation')
89 | plt.legend()
90 |
91 | plt.show()
--------------------------------------------------------------------------------
/poetry.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
2 |
3 | [[package]]
4 | name = "contourpy"
5 | version = "1.3.1"
6 | description = "Python library for calculating contours of 2D quadrilateral grids"
7 | optional = false
8 | python-versions = ">=3.10"
9 | files = [
10 | {file = "contourpy-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a045f341a77b77e1c5de31e74e966537bba9f3c4099b35bf4c2e3939dd54cdab"},
11 | {file = "contourpy-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:500360b77259914f7805af7462e41f9cb7ca92ad38e9f94d6c8641b089338124"},
12 | {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2f926efda994cdf3c8d3fdb40b9962f86edbc4457e739277b961eced3d0b4c1"},
13 | {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adce39d67c0edf383647a3a007de0a45fd1b08dedaa5318404f1a73059c2512b"},
14 | {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abbb49fb7dac584e5abc6636b7b2a7227111c4f771005853e7d25176daaf8453"},
15 | {file = "contourpy-1.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0cffcbede75c059f535725c1680dfb17b6ba8753f0c74b14e6a9c68c29d7ea3"},
16 | {file = "contourpy-1.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ab29962927945d89d9b293eabd0d59aea28d887d4f3be6c22deaefbb938a7277"},
17 | {file = "contourpy-1.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:974d8145f8ca354498005b5b981165b74a195abfae9a8129df3e56771961d595"},
18 | {file = "contourpy-1.3.1-cp310-cp310-win32.whl", hash = "sha256:ac4578ac281983f63b400f7fe6c101bedc10651650eef012be1ccffcbacf3697"},
19 | {file = "contourpy-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:174e758c66bbc1c8576992cec9599ce8b6672b741b5d336b5c74e35ac382b18e"},
20 | {file = "contourpy-1.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e8b974d8db2c5610fb4e76307e265de0edb655ae8169e8b21f41807ccbeec4b"},
21 | {file = "contourpy-1.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:20914c8c973f41456337652a6eeca26d2148aa96dd7ac323b74516988bea89fc"},
22 | {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19d40d37c1c3a4961b4619dd9d77b12124a453cc3d02bb31a07d58ef684d3d86"},
23 | {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:113231fe3825ebf6f15eaa8bc1f5b0ddc19d42b733345eae0934cb291beb88b6"},
24 | {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4dbbc03a40f916a8420e420d63e96a1258d3d1b58cbdfd8d1f07b49fcbd38e85"},
25 | {file = "contourpy-1.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a04ecd68acbd77fa2d39723ceca4c3197cb2969633836ced1bea14e219d077c"},
26 | {file = "contourpy-1.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c414fc1ed8ee1dbd5da626cf3710c6013d3d27456651d156711fa24f24bd1291"},
27 | {file = "contourpy-1.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:31c1b55c1f34f80557d3830d3dd93ba722ce7e33a0b472cba0ec3b6535684d8f"},
28 | {file = "contourpy-1.3.1-cp311-cp311-win32.whl", hash = "sha256:f611e628ef06670df83fce17805c344710ca5cde01edfdc72751311da8585375"},
29 | {file = "contourpy-1.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:b2bdca22a27e35f16794cf585832e542123296b4687f9fd96822db6bae17bfc9"},
30 | {file = "contourpy-1.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ffa84be8e0bd33410b17189f7164c3589c229ce5db85798076a3fa136d0e509"},
31 | {file = "contourpy-1.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805617228ba7e2cbbfb6c503858e626ab528ac2a32a04a2fe88ffaf6b02c32bc"},
32 | {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade08d343436a94e633db932e7e8407fe7de8083967962b46bdfc1b0ced39454"},
33 | {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47734d7073fb4590b4a40122b35917cd77be5722d80683b249dac1de266aac80"},
34 | {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ba94a401342fc0f8b948e57d977557fbf4d515f03c67682dd5c6191cb2d16ec"},
35 | {file = "contourpy-1.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efa874e87e4a647fd2e4f514d5e91c7d493697127beb95e77d2f7561f6905bd9"},
36 | {file = "contourpy-1.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1bf98051f1045b15c87868dbaea84f92408337d4f81d0e449ee41920ea121d3b"},
37 | {file = "contourpy-1.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:61332c87493b00091423e747ea78200659dc09bdf7fd69edd5e98cef5d3e9a8d"},
38 | {file = "contourpy-1.3.1-cp312-cp312-win32.whl", hash = "sha256:e914a8cb05ce5c809dd0fe350cfbb4e881bde5e2a38dc04e3afe1b3e58bd158e"},
39 | {file = "contourpy-1.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:08d9d449a61cf53033612cb368f3a1b26cd7835d9b8cd326647efe43bca7568d"},
40 | {file = "contourpy-1.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a761d9ccfc5e2ecd1bf05534eda382aa14c3e4f9205ba5b1684ecfe400716ef2"},
41 | {file = "contourpy-1.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:523a8ee12edfa36f6d2a49407f705a6ef4c5098de4f498619787e272de93f2d5"},
42 | {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece6df05e2c41bd46776fbc712e0996f7c94e0d0543af1656956d150c4ca7c81"},
43 | {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:573abb30e0e05bf31ed067d2f82500ecfdaec15627a59d63ea2d95714790f5c2"},
44 | {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fa36448e6a3a1a9a2ba23c02012c43ed88905ec80163f2ffe2421c7192a5d7"},
45 | {file = "contourpy-1.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ea9924d28fc5586bf0b42d15f590b10c224117e74409dd7a0be3b62b74a501c"},
46 | {file = "contourpy-1.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b75aa69cb4d6f137b36f7eb2ace9280cfb60c55dc5f61c731fdf6f037f958a3"},
47 | {file = "contourpy-1.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:041b640d4ec01922083645a94bb3b2e777e6b626788f4095cf21abbe266413c1"},
48 | {file = "contourpy-1.3.1-cp313-cp313-win32.whl", hash = "sha256:36987a15e8ace5f58d4d5da9dca82d498c2bbb28dff6e5d04fbfcc35a9cb3a82"},
49 | {file = "contourpy-1.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:a7895f46d47671fa7ceec40f31fae721da51ad34bdca0bee83e38870b1f47ffd"},
50 | {file = "contourpy-1.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ddeb796389dadcd884c7eb07bd14ef12408aaae358f0e2ae24114d797eede30"},
51 | {file = "contourpy-1.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19c1555a6801c2f084c7ddc1c6e11f02eb6a6016ca1318dd5452ba3f613a1751"},
52 | {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:841ad858cff65c2c04bf93875e384ccb82b654574a6d7f30453a04f04af71342"},
53 | {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4318af1c925fb9a4fb190559ef3eec206845f63e80fb603d47f2d6d67683901c"},
54 | {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14c102b0eab282427b662cb590f2e9340a9d91a1c297f48729431f2dcd16e14f"},
55 | {file = "contourpy-1.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e806338bfeaa006acbdeba0ad681a10be63b26e1b17317bfac3c5d98f36cda"},
56 | {file = "contourpy-1.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4d76d5993a34ef3df5181ba3c92fabb93f1eaa5729504fb03423fcd9f3177242"},
57 | {file = "contourpy-1.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:89785bb2a1980c1bd87f0cb1517a71cde374776a5f150936b82580ae6ead44a1"},
58 | {file = "contourpy-1.3.1-cp313-cp313t-win32.whl", hash = "sha256:8eb96e79b9f3dcadbad2a3891672f81cdcab7f95b27f28f1c67d75f045b6b4f1"},
59 | {file = "contourpy-1.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:287ccc248c9e0d0566934e7d606201abd74761b5703d804ff3df8935f523d546"},
60 | {file = "contourpy-1.3.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b457d6430833cee8e4b8e9b6f07aa1c161e5e0d52e118dc102c8f9bd7dd060d6"},
61 | {file = "contourpy-1.3.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb76c1a154b83991a3cbbf0dfeb26ec2833ad56f95540b442c73950af2013750"},
62 | {file = "contourpy-1.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:44a29502ca9c7b5ba389e620d44f2fbe792b1fb5734e8b931ad307071ec58c53"},
63 | {file = "contourpy-1.3.1.tar.gz", hash = "sha256:dfd97abd83335045a913e3bcc4a09c0ceadbe66580cf573fe961f4a825efa699"},
64 | ]
65 |
66 | [package.dependencies]
67 | numpy = ">=1.23"
68 |
69 | [package.extras]
70 | bokeh = ["bokeh", "selenium"]
71 | docs = ["furo", "sphinx (>=7.2)", "sphinx-copybutton"]
72 | mypy = ["contourpy[bokeh,docs]", "docutils-stubs", "mypy (==1.11.1)", "types-Pillow"]
73 | test = ["Pillow", "contourpy[test-no-images]", "matplotlib"]
74 | test-no-images = ["pytest", "pytest-cov", "pytest-rerunfailures", "pytest-xdist", "wurlitzer"]
75 |
76 | [[package]]
77 | name = "cycler"
78 | version = "0.12.1"
79 | description = "Composable style cycles"
80 | optional = false
81 | python-versions = ">=3.8"
82 | files = [
83 | {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"},
84 | {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"},
85 | ]
86 |
87 | [package.extras]
88 | docs = ["ipython", "matplotlib", "numpydoc", "sphinx"]
89 | tests = ["pytest", "pytest-cov", "pytest-xdist"]
90 |
91 | [[package]]
92 | name = "fonttools"
93 | version = "4.55.3"
94 | description = "Tools to manipulate font files"
95 | optional = false
96 | python-versions = ">=3.8"
97 | files = [
98 | {file = "fonttools-4.55.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1dcc07934a2165ccdc3a5a608db56fb3c24b609658a5b340aee4ecf3ba679dc0"},
99 | {file = "fonttools-4.55.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f7d66c15ba875432a2d2fb419523f5d3d347f91f48f57b8b08a2dfc3c39b8a3f"},
100 | {file = "fonttools-4.55.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e4ae3592e62eba83cd2c4ccd9462dcfa603ff78e09110680a5444c6925d841"},
101 | {file = "fonttools-4.55.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62d65a3022c35e404d19ca14f291c89cc5890032ff04f6c17af0bd1927299674"},
102 | {file = "fonttools-4.55.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d342e88764fb201286d185093781bf6628bbe380a913c24adf772d901baa8276"},
103 | {file = "fonttools-4.55.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd68c87a2bfe37c5b33bcda0fba39b65a353876d3b9006fde3adae31f97b3ef5"},
104 | {file = "fonttools-4.55.3-cp310-cp310-win32.whl", hash = "sha256:1bc7ad24ff98846282eef1cbeac05d013c2154f977a79886bb943015d2b1b261"},
105 | {file = "fonttools-4.55.3-cp310-cp310-win_amd64.whl", hash = "sha256:b54baf65c52952db65df39fcd4820668d0ef4766c0ccdf32879b77f7c804d5c5"},
106 | {file = "fonttools-4.55.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c4491699bad88efe95772543cd49870cf756b019ad56294f6498982408ab03e"},
107 | {file = "fonttools-4.55.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5323a22eabddf4b24f66d26894f1229261021dacd9d29e89f7872dd8c63f0b8b"},
108 | {file = "fonttools-4.55.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5480673f599ad410695ca2ddef2dfefe9df779a9a5cda89503881e503c9c7d90"},
109 | {file = "fonttools-4.55.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da9da6d65cd7aa6b0f806556f4985bcbf603bf0c5c590e61b43aa3e5a0f822d0"},
110 | {file = "fonttools-4.55.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e894b5bd60d9f473bed7a8f506515549cc194de08064d829464088d23097331b"},
111 | {file = "fonttools-4.55.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aee3b57643827e237ff6ec6d28d9ff9766bd8b21e08cd13bff479e13d4b14765"},
112 | {file = "fonttools-4.55.3-cp311-cp311-win32.whl", hash = "sha256:eb6ca911c4c17eb51853143624d8dc87cdcdf12a711fc38bf5bd21521e79715f"},
113 | {file = "fonttools-4.55.3-cp311-cp311-win_amd64.whl", hash = "sha256:6314bf82c54c53c71805318fcf6786d986461622dd926d92a465199ff54b1b72"},
114 | {file = "fonttools-4.55.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f9e736f60f4911061235603a6119e72053073a12c6d7904011df2d8fad2c0e35"},
115 | {file = "fonttools-4.55.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7a8aa2c5e5b8b3bcb2e4538d929f6589a5c6bdb84fd16e2ed92649fb5454f11c"},
116 | {file = "fonttools-4.55.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07f8288aacf0a38d174445fc78377a97fb0b83cfe352a90c9d9c1400571963c7"},
117 | {file = "fonttools-4.55.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8d5e8916c0970fbc0f6f1bece0063363bb5857a7f170121a4493e31c3db3314"},
118 | {file = "fonttools-4.55.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae3b6600565b2d80b7c05acb8e24d2b26ac407b27a3f2e078229721ba5698427"},
119 | {file = "fonttools-4.55.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:54153c49913f45065c8d9e6d0c101396725c5621c8aee744719300f79771d75a"},
120 | {file = "fonttools-4.55.3-cp312-cp312-win32.whl", hash = "sha256:827e95fdbbd3e51f8b459af5ea10ecb4e30af50221ca103bea68218e9615de07"},
121 | {file = "fonttools-4.55.3-cp312-cp312-win_amd64.whl", hash = "sha256:e6e8766eeeb2de759e862004aa11a9ea3d6f6d5ec710551a88b476192b64fd54"},
122 | {file = "fonttools-4.55.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a430178ad3e650e695167cb53242dae3477b35c95bef6525b074d87493c4bf29"},
123 | {file = "fonttools-4.55.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:529cef2ce91dc44f8e407cc567fae6e49a1786f2fefefa73a294704c415322a4"},
124 | {file = "fonttools-4.55.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e75f12c82127486fac2d8bfbf5bf058202f54bf4f158d367e41647b972342ca"},
125 | {file = "fonttools-4.55.3-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:859c358ebf41db18fb72342d3080bce67c02b39e86b9fbcf1610cca14984841b"},
126 | {file = "fonttools-4.55.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:546565028e244a701f73df6d8dd6be489d01617863ec0c6a42fa25bf45d43048"},
127 | {file = "fonttools-4.55.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aca318b77f23523309eec4475d1fbbb00a6b133eb766a8bdc401faba91261abe"},
128 | {file = "fonttools-4.55.3-cp313-cp313-win32.whl", hash = "sha256:8c5ec45428edaa7022f1c949a632a6f298edc7b481312fc7dc258921e9399628"},
129 | {file = "fonttools-4.55.3-cp313-cp313-win_amd64.whl", hash = "sha256:11e5de1ee0d95af4ae23c1a138b184b7f06e0b6abacabf1d0db41c90b03d834b"},
130 | {file = "fonttools-4.55.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:caf8230f3e10f8f5d7593eb6d252a37caf58c480b19a17e250a63dad63834cf3"},
131 | {file = "fonttools-4.55.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b586ab5b15b6097f2fb71cafa3c98edfd0dba1ad8027229e7b1e204a58b0e09d"},
132 | {file = "fonttools-4.55.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8c2794ded89399cc2169c4d0bf7941247b8d5932b2659e09834adfbb01589aa"},
133 | {file = "fonttools-4.55.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf4fe7c124aa3f4e4c1940880156e13f2f4d98170d35c749e6b4f119a872551e"},
134 | {file = "fonttools-4.55.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:86721fbc389ef5cc1e2f477019e5069e8e4421e8d9576e9c26f840dbb04678de"},
135 | {file = "fonttools-4.55.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:89bdc5d88bdeec1b15af790810e267e8332d92561dce4f0748c2b95c9bdf3926"},
136 | {file = "fonttools-4.55.3-cp38-cp38-win32.whl", hash = "sha256:bc5dbb4685e51235ef487e4bd501ddfc49be5aede5e40f4cefcccabc6e60fb4b"},
137 | {file = "fonttools-4.55.3-cp38-cp38-win_amd64.whl", hash = "sha256:cd70de1a52a8ee2d1877b6293af8a2484ac82514f10b1c67c1c5762d38073e56"},
138 | {file = "fonttools-4.55.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bdcc9f04b36c6c20978d3f060e5323a43f6222accc4e7fcbef3f428e216d96af"},
139 | {file = "fonttools-4.55.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c3ca99e0d460eff46e033cd3992a969658c3169ffcd533e0a39c63a38beb6831"},
140 | {file = "fonttools-4.55.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22f38464daa6cdb7b6aebd14ab06609328fe1e9705bb0fcc7d1e69de7109ee02"},
141 | {file = "fonttools-4.55.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed63959d00b61959b035c7d47f9313c2c1ece090ff63afea702fe86de00dbed4"},
142 | {file = "fonttools-4.55.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5e8d657cd7326eeaba27de2740e847c6b39dde2f8d7cd7cc56f6aad404ddf0bd"},
143 | {file = "fonttools-4.55.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:fb594b5a99943042c702c550d5494bdd7577f6ef19b0bc73877c948a63184a32"},
144 | {file = "fonttools-4.55.3-cp39-cp39-win32.whl", hash = "sha256:dc5294a3d5c84226e3dbba1b6f61d7ad813a8c0238fceea4e09aa04848c3d851"},
145 | {file = "fonttools-4.55.3-cp39-cp39-win_amd64.whl", hash = "sha256:aedbeb1db64496d098e6be92b2e63b5fac4e53b1b92032dfc6988e1ea9134a4d"},
146 | {file = "fonttools-4.55.3-py3-none-any.whl", hash = "sha256:f412604ccbeee81b091b420272841e5ec5ef68967a9790e80bffd0e30b8e2977"},
147 | {file = "fonttools-4.55.3.tar.gz", hash = "sha256:3983313c2a04d6cc1fe9251f8fc647754cf49a61dac6cb1e7249ae67afaafc45"},
148 | ]
149 |
150 | [package.extras]
151 | all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"]
152 | graphite = ["lz4 (>=1.7.4.2)"]
153 | interpolatable = ["munkres", "pycairo", "scipy"]
154 | lxml = ["lxml (>=4.0)"]
155 | pathops = ["skia-pathops (>=0.5.0)"]
156 | plot = ["matplotlib"]
157 | repacker = ["uharfbuzz (>=0.23.0)"]
158 | symfont = ["sympy"]
159 | type1 = ["xattr"]
160 | ufo = ["fs (>=2.2.0,<3)"]
161 | unicode = ["unicodedata2 (>=15.1.0)"]
162 | woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
163 |
164 | [[package]]
165 | name = "gitdb"
166 | version = "4.0.11"
167 | description = "Git Object Database"
168 | optional = false
169 | python-versions = ">=3.7"
170 | files = [
171 | {file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"},
172 | {file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"},
173 | ]
174 |
175 | [package.dependencies]
176 | smmap = ">=3.0.1,<6"
177 |
178 | [[package]]
179 | name = "gitpython"
180 | version = "3.1.43"
181 | description = "GitPython is a Python library used to interact with Git repositories"
182 | optional = false
183 | python-versions = ">=3.7"
184 | files = [
185 | {file = "GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff"},
186 | {file = "GitPython-3.1.43.tar.gz", hash = "sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c"},
187 | ]
188 |
189 | [package.dependencies]
190 | gitdb = ">=4.0.1,<5"
191 |
192 | [package.extras]
193 | doc = ["sphinx (==4.3.2)", "sphinx-autodoc-typehints", "sphinx-rtd-theme", "sphinxcontrib-applehelp (>=1.0.2,<=1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (>=2.0.0,<=2.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)"]
194 | test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"]
195 |
196 | [[package]]
197 | name = "kiwisolver"
198 | version = "1.4.7"
199 | description = "A fast implementation of the Cassowary constraint solver"
200 | optional = false
201 | python-versions = ">=3.8"
202 | files = [
203 | {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8a9c83f75223d5e48b0bc9cb1bf2776cf01563e00ade8775ffe13b0b6e1af3a6"},
204 | {file = "kiwisolver-1.4.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58370b1ffbd35407444d57057b57da5d6549d2d854fa30249771775c63b5fe17"},
205 | {file = "kiwisolver-1.4.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aa0abdf853e09aff551db11fce173e2177d00786c688203f52c87ad7fcd91ef9"},
206 | {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d53103597a252fb3ab8b5845af04c7a26d5e7ea8122303dd7a021176a87e8b9"},
207 | {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:88f17c5ffa8e9462fb79f62746428dd57b46eb931698e42e990ad63103f35e6c"},
208 | {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a9ca9c710d598fd75ee5de59d5bda2684d9db36a9f50b6125eaea3969c2599"},
209 | {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f4d742cb7af1c28303a51b7a27aaee540e71bb8e24f68c736f6f2ffc82f2bf05"},
210 | {file = "kiwisolver-1.4.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e28c7fea2196bf4c2f8d46a0415c77a1c480cc0724722f23d7410ffe9842c407"},
211 | {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e968b84db54f9d42046cf154e02911e39c0435c9801681e3fc9ce8a3c4130278"},
212 | {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0c18ec74c0472de033e1bebb2911c3c310eef5649133dd0bedf2a169a1b269e5"},
213 | {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8f0ea6da6d393d8b2e187e6a5e3fb81f5862010a40c3945e2c6d12ae45cfb2ad"},
214 | {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:f106407dda69ae456dd1227966bf445b157ccc80ba0dff3802bb63f30b74e895"},
215 | {file = "kiwisolver-1.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:84ec80df401cfee1457063732d90022f93951944b5b58975d34ab56bb150dfb3"},
216 | {file = "kiwisolver-1.4.7-cp310-cp310-win32.whl", hash = "sha256:71bb308552200fb2c195e35ef05de12f0c878c07fc91c270eb3d6e41698c3bcc"},
217 | {file = "kiwisolver-1.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:44756f9fd339de0fb6ee4f8c1696cfd19b2422e0d70b4cefc1cc7f1f64045a8c"},
218 | {file = "kiwisolver-1.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:78a42513018c41c2ffd262eb676442315cbfe3c44eed82385c2ed043bc63210a"},
219 | {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d2b0e12a42fb4e72d509fc994713d099cbb15ebf1103545e8a45f14da2dfca54"},
220 | {file = "kiwisolver-1.4.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2a8781ac3edc42ea4b90bc23e7d37b665d89423818e26eb6df90698aa2287c95"},
221 | {file = "kiwisolver-1.4.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46707a10836894b559e04b0fd143e343945c97fd170d69a2d26d640b4e297935"},
222 | {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef97b8df011141c9b0f6caf23b29379f87dd13183c978a30a3c546d2c47314cb"},
223 | {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab58c12a2cd0fc769089e6d38466c46d7f76aced0a1f54c77652446733d2d02"},
224 | {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:803b8e1459341c1bb56d1c5c010406d5edec8a0713a0945851290a7930679b51"},
225 | {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9a9e8a507420fe35992ee9ecb302dab68550dedc0da9e2880dd88071c5fb052"},
226 | {file = "kiwisolver-1.4.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18077b53dc3bb490e330669a99920c5e6a496889ae8c63b58fbc57c3d7f33a18"},
227 | {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6af936f79086a89b3680a280c47ea90b4df7047b5bdf3aa5c524bbedddb9e545"},
228 | {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3abc5b19d24af4b77d1598a585b8a719beb8569a71568b66f4ebe1fb0449460b"},
229 | {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:933d4de052939d90afbe6e9d5273ae05fb836cc86c15b686edd4b3560cc0ee36"},
230 | {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:65e720d2ab2b53f1f72fb5da5fb477455905ce2c88aaa671ff0a447c2c80e8e3"},
231 | {file = "kiwisolver-1.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3bf1ed55088f214ba6427484c59553123fdd9b218a42bbc8c6496d6754b1e523"},
232 | {file = "kiwisolver-1.4.7-cp311-cp311-win32.whl", hash = "sha256:4c00336b9dd5ad96d0a558fd18a8b6f711b7449acce4c157e7343ba92dd0cf3d"},
233 | {file = "kiwisolver-1.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:929e294c1ac1e9f615c62a4e4313ca1823ba37326c164ec720a803287c4c499b"},
234 | {file = "kiwisolver-1.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:e33e8fbd440c917106b237ef1a2f1449dfbb9b6f6e1ce17c94cd6a1e0d438376"},
235 | {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:5360cc32706dab3931f738d3079652d20982511f7c0ac5711483e6eab08efff2"},
236 | {file = "kiwisolver-1.4.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942216596dc64ddb25adb215c3c783215b23626f8d84e8eff8d6d45c3f29f75a"},
237 | {file = "kiwisolver-1.4.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:48b571ecd8bae15702e4f22d3ff6a0f13e54d3d00cd25216d5e7f658242065ee"},
238 | {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad42ba922c67c5f219097b28fae965e10045ddf145d2928bfac2eb2e17673640"},
239 | {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:612a10bdae23404a72941a0fc8fa2660c6ea1217c4ce0dbcab8a8f6543ea9e7f"},
240 | {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9e838bba3a3bac0fe06d849d29772eb1afb9745a59710762e4ba3f4cb8424483"},
241 | {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:22f499f6157236c19f4bbbd472fa55b063db77a16cd74d49afe28992dff8c258"},
242 | {file = "kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693902d433cf585133699972b6d7c42a8b9f8f826ebcaf0132ff55200afc599e"},
243 | {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4e77f2126c3e0b0d055f44513ed349038ac180371ed9b52fe96a32aa071a5107"},
244 | {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:657a05857bda581c3656bfc3b20e353c232e9193eb167766ad2dc58b56504948"},
245 | {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4bfa75a048c056a411f9705856abfc872558e33c055d80af6a380e3658766038"},
246 | {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:34ea1de54beef1c104422d210c47c7d2a4999bdecf42c7b5718fbe59a4cac383"},
247 | {file = "kiwisolver-1.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:90da3b5f694b85231cf93586dad5e90e2d71b9428f9aad96952c99055582f520"},
248 | {file = "kiwisolver-1.4.7-cp312-cp312-win32.whl", hash = "sha256:18e0cca3e008e17fe9b164b55735a325140a5a35faad8de92dd80265cd5eb80b"},
249 | {file = "kiwisolver-1.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:58cb20602b18f86f83a5c87d3ee1c766a79c0d452f8def86d925e6c60fbf7bfb"},
250 | {file = "kiwisolver-1.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:f5a8b53bdc0b3961f8b6125e198617c40aeed638b387913bf1ce78afb1b0be2a"},
251 | {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e6039dcbe79a8e0f044f1c39db1986a1b8071051efba3ee4d74f5b365f5226e"},
252 | {file = "kiwisolver-1.4.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a1ecf0ac1c518487d9d23b1cd7139a6a65bc460cd101ab01f1be82ecf09794b6"},
253 | {file = "kiwisolver-1.4.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ab9ccab2b5bd5702ab0803676a580fffa2aa178c2badc5557a84cc943fcf750"},
254 | {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f816dd2277f8d63d79f9c8473a79fe54047bc0467754962840782c575522224d"},
255 | {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf8bcc23ceb5a1b624572a1623b9f79d2c3b337c8c455405ef231933a10da379"},
256 | {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dea0bf229319828467d7fca8c7c189780aa9ff679c94539eed7532ebe33ed37c"},
257 | {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c06a4c7cf15ec739ce0e5971b26c93638730090add60e183530d70848ebdd34"},
258 | {file = "kiwisolver-1.4.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913983ad2deb14e66d83c28b632fd35ba2b825031f2fa4ca29675e665dfecbe1"},
259 | {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5337ec7809bcd0f424c6b705ecf97941c46279cf5ed92311782c7c9c2026f07f"},
260 | {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c26ed10c4f6fa6ddb329a5120ba3b6db349ca192ae211e882970bfc9d91420b"},
261 | {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c619b101e6de2222c1fcb0531e1b17bbffbe54294bfba43ea0d411d428618c27"},
262 | {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:073a36c8273647592ea332e816e75ef8da5c303236ec0167196793eb1e34657a"},
263 | {file = "kiwisolver-1.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3ce6b2b0231bda412463e152fc18335ba32faf4e8c23a754ad50ffa70e4091ee"},
264 | {file = "kiwisolver-1.4.7-cp313-cp313-win32.whl", hash = "sha256:f4c9aee212bc89d4e13f58be11a56cc8036cabad119259d12ace14b34476fd07"},
265 | {file = "kiwisolver-1.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:8a3ec5aa8e38fc4c8af308917ce12c536f1c88452ce554027e55b22cbbfbff76"},
266 | {file = "kiwisolver-1.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:76c8094ac20ec259471ac53e774623eb62e6e1f56cd8690c67ce6ce4fcb05650"},
267 | {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5d5abf8f8ec1f4e22882273c423e16cae834c36856cac348cfbfa68e01c40f3a"},
268 | {file = "kiwisolver-1.4.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aeb3531b196ef6f11776c21674dba836aeea9d5bd1cf630f869e3d90b16cfade"},
269 | {file = "kiwisolver-1.4.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b7d755065e4e866a8086c9bdada157133ff466476a2ad7861828e17b6026e22c"},
270 | {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08471d4d86cbaec61f86b217dd938a83d85e03785f51121e791a6e6689a3be95"},
271 | {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bbfcb7165ce3d54a3dfbe731e470f65739c4c1f85bb1018ee912bae139e263b"},
272 | {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d34eb8494bea691a1a450141ebb5385e4b69d38bb8403b5146ad279f4b30fa3"},
273 | {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9242795d174daa40105c1d86aba618e8eab7bf96ba8c3ee614da8302a9f95503"},
274 | {file = "kiwisolver-1.4.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a0f64a48bb81af7450e641e3fe0b0394d7381e342805479178b3d335d60ca7cf"},
275 | {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8e045731a5416357638d1700927529e2b8ab304811671f665b225f8bf8d8f933"},
276 | {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4322872d5772cae7369f8351da1edf255a604ea7087fe295411397d0cfd9655e"},
277 | {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:e1631290ee9271dffe3062d2634c3ecac02c83890ada077d225e081aca8aab89"},
278 | {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:edcfc407e4eb17e037bca59be0e85a2031a2ac87e4fed26d3e9df88b4165f92d"},
279 | {file = "kiwisolver-1.4.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4d05d81ecb47d11e7f8932bd8b61b720bf0b41199358f3f5e36d38e28f0532c5"},
280 | {file = "kiwisolver-1.4.7-cp38-cp38-win32.whl", hash = "sha256:b38ac83d5f04b15e515fd86f312479d950d05ce2368d5413d46c088dda7de90a"},
281 | {file = "kiwisolver-1.4.7-cp38-cp38-win_amd64.whl", hash = "sha256:d83db7cde68459fc803052a55ace60bea2bae361fc3b7a6d5da07e11954e4b09"},
282 | {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3f9362ecfca44c863569d3d3c033dbe8ba452ff8eed6f6b5806382741a1334bd"},
283 | {file = "kiwisolver-1.4.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e8df2eb9b2bac43ef8b082e06f750350fbbaf2887534a5be97f6cf07b19d9583"},
284 | {file = "kiwisolver-1.4.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f32d6edbc638cde7652bd690c3e728b25332acbadd7cad670cc4a02558d9c417"},
285 | {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e2e6c39bd7b9372b0be21456caab138e8e69cc0fc1190a9dfa92bd45a1e6e904"},
286 | {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:dda56c24d869b1193fcc763f1284b9126550eaf84b88bbc7256e15028f19188a"},
287 | {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79849239c39b5e1fd906556c474d9b0439ea6792b637511f3fe3a41158d89ca8"},
288 | {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e3bc157fed2a4c02ec468de4ecd12a6e22818d4f09cde2c31ee3226ffbefab2"},
289 | {file = "kiwisolver-1.4.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3da53da805b71e41053dc670f9a820d1157aae77b6b944e08024d17bcd51ef88"},
290 | {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8705f17dfeb43139a692298cb6637ee2e59c0194538153e83e9ee0c75c2eddde"},
291 | {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:82a5c2f4b87c26bb1a0ef3d16b5c4753434633b83d365cc0ddf2770c93829e3c"},
292 | {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce8be0466f4c0d585cdb6c1e2ed07232221df101a4c6f28821d2aa754ca2d9e2"},
293 | {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:409afdfe1e2e90e6ee7fc896f3df9a7fec8e793e58bfa0d052c8a82f99c37abb"},
294 | {file = "kiwisolver-1.4.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5b9c3f4ee0b9a439d2415012bd1b1cc2df59e4d6a9939f4d669241d30b414327"},
295 | {file = "kiwisolver-1.4.7-cp39-cp39-win32.whl", hash = "sha256:a79ae34384df2b615eefca647a2873842ac3b596418032bef9a7283675962644"},
296 | {file = "kiwisolver-1.4.7-cp39-cp39-win_amd64.whl", hash = "sha256:cf0438b42121a66a3a667de17e779330fc0f20b0d97d59d2f2121e182b0505e4"},
297 | {file = "kiwisolver-1.4.7-cp39-cp39-win_arm64.whl", hash = "sha256:764202cc7e70f767dab49e8df52c7455e8de0df5d858fa801a11aa0d882ccf3f"},
298 | {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:94252291e3fe68001b1dd747b4c0b3be12582839b95ad4d1b641924d68fd4643"},
299 | {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b7dfa3b546da08a9f622bb6becdb14b3e24aaa30adba66749d38f3cc7ea9706"},
300 | {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd3de6481f4ed8b734da5df134cd5a6a64fe32124fe83dde1e5b5f29fe30b1e6"},
301 | {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a91b5f9f1205845d488c928e8570dcb62b893372f63b8b6e98b863ebd2368ff2"},
302 | {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fa14dbd66b8b8f470d5fc79c089a66185619d31645f9b0773b88b19f7223c4"},
303 | {file = "kiwisolver-1.4.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:eb542fe7933aa09d8d8f9d9097ef37532a7df6497819d16efe4359890a2f417a"},
304 | {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bfa1acfa0c54932d5607e19a2c24646fb4c1ae2694437789129cf099789a3b00"},
305 | {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:eee3ea935c3d227d49b4eb85660ff631556841f6e567f0f7bda972df6c2c9935"},
306 | {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f3160309af4396e0ed04db259c3ccbfdc3621b5559b5453075e5de555e1f3a1b"},
307 | {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a17f6a29cf8935e587cc8a4dbfc8368c55edc645283db0ce9801016f83526c2d"},
308 | {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10849fb2c1ecbfae45a693c070e0320a91b35dd4bcf58172c023b994283a124d"},
309 | {file = "kiwisolver-1.4.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ac542bf38a8a4be2dc6b15248d36315ccc65f0743f7b1a76688ffb6b5129a5c2"},
310 | {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b01aac285f91ca889c800042c35ad3b239e704b150cfd3382adfc9dcc780e39"},
311 | {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:48be928f59a1f5c8207154f935334d374e79f2b5d212826307d072595ad76a2e"},
312 | {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f37cfe618a117e50d8c240555331160d73d0411422b59b5ee217843d7b693608"},
313 | {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599b5c873c63a1f6ed7eead644a8a380cfbdf5db91dcb6f85707aaab213b1674"},
314 | {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:801fa7802e5cfabe3ab0c81a34c323a319b097dfb5004be950482d882f3d7225"},
315 | {file = "kiwisolver-1.4.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:0c6c43471bc764fad4bc99c5c2d6d16a676b1abf844ca7c8702bdae92df01ee0"},
316 | {file = "kiwisolver-1.4.7.tar.gz", hash = "sha256:9893ff81bd7107f7b685d3017cc6583daadb4fc26e4a888350df530e41980a60"},
317 | ]
318 |
319 | [[package]]
320 | name = "lowtran"
321 | version = "3.1.0"
322 | description = "Model of Earth atmosphere absorption and transmission vs. wavelength and location."
323 | optional = false
324 | python-versions = ">=3.8"
325 | files = [
326 | {file = "lowtran-3.1.0-py3-none-any.whl", hash = "sha256:e9efd6208a074fac488c71b04775ce6964079c2846e6ce5b7c4d6e728c708fca"},
327 | {file = "lowtran-3.1.0.tar.gz", hash = "sha256:51cfc2d882423e32933ef6be765d7aad97c3432300247d04164a0f76613579f5"},
328 | ]
329 |
330 | [package.dependencies]
331 | numpy = "*"
332 | python-dateutil = "*"
333 | xarray = "*"
334 |
335 | [package.extras]
336 | lint = ["flake8", "flake8-blind-except", "flake8-bugbear", "flake8-builtins", "mypy", "types-python-dateutil"]
337 | tests = ["pytest"]
338 |
339 | [[package]]
340 | name = "matplotlib"
341 | version = "3.9.3"
342 | description = "Python plotting package"
343 | optional = false
344 | python-versions = ">=3.9"
345 | files = [
346 | {file = "matplotlib-3.9.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:41b016e3be4e740b66c79a031a0a6e145728dbc248142e751e8dab4f3188ca1d"},
347 | {file = "matplotlib-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e0143975fc2a6d7136c97e19c637321288371e8f09cff2564ecd73e865ea0b9"},
348 | {file = "matplotlib-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f459c8ee2c086455744723628264e43c884be0c7d7b45d84b8cd981310b4815"},
349 | {file = "matplotlib-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:687df7ceff57b8f070d02b4db66f75566370e7ae182a0782b6d3d21b0d6917dc"},
350 | {file = "matplotlib-3.9.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:edd14cf733fdc4f6e6fe3f705af97676a7e52859bf0044aa2c84e55be739241c"},
351 | {file = "matplotlib-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:1c40c244221a1adbb1256692b1133c6fb89418df27bf759a31a333e7912a4010"},
352 | {file = "matplotlib-3.9.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:cf2a60daf6cecff6828bc608df00dbc794380e7234d2411c0ec612811f01969d"},
353 | {file = "matplotlib-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:213d6dc25ce686516208d8a3e91120c6a4fdae4a3e06b8505ced5b716b50cc04"},
354 | {file = "matplotlib-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c52f48eb75fcc119a4fdb68ba83eb5f71656999420375df7c94cc68e0e14686e"},
355 | {file = "matplotlib-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3c93796b44fa111049b88a24105e947f03c01966b5c0cc782e2ee3887b790a3"},
356 | {file = "matplotlib-3.9.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cd1077b9a09b16d8c3c7075a8add5ffbfe6a69156a57e290c800ed4d435bef1d"},
357 | {file = "matplotlib-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:c96eeeb8c68b662c7747f91a385688d4b449687d29b691eff7068a4602fe6dc4"},
358 | {file = "matplotlib-3.9.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0a361bd5583bf0bcc08841df3c10269617ee2a36b99ac39d455a767da908bbbc"},
359 | {file = "matplotlib-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e14485bb1b83eeb3d55b6878f9560240981e7bbc7a8d4e1e8c38b9bd6ec8d2de"},
360 | {file = "matplotlib-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a8d279f78844aad213c4935c18f8292a9432d51af2d88bca99072c903948045"},
361 | {file = "matplotlib-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6c12514329ac0d03128cf1dcceb335f4fbf7c11da98bca68dca8dcb983153a9"},
362 | {file = "matplotlib-3.9.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6e9de2b390d253a508dd497e9b5579f3a851f208763ed67fdca5dc0c3ea6849c"},
363 | {file = "matplotlib-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:d796272408f8567ff7eaa00eb2856b3a00524490e47ad505b0b4ca6bb8a7411f"},
364 | {file = "matplotlib-3.9.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:203d18df84f5288973b2d56de63d4678cc748250026ca9e1ad8f8a0fd8a75d83"},
365 | {file = "matplotlib-3.9.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b651b0d3642991259109dc0351fc33ad44c624801367bb8307be9bfc35e427ad"},
366 | {file = "matplotlib-3.9.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66d7b171fecf96940ce069923a08ba3df33ef542de82c2ff4fe8caa8346fa95a"},
367 | {file = "matplotlib-3.9.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be0ba61f6ff2e6b68e4270fb63b6813c9e7dec3d15fc3a93f47480444fd72f0"},
368 | {file = "matplotlib-3.9.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d6b2e8856dec3a6db1ae51aec85c82223e834b228c1d3228aede87eee2b34f9"},
369 | {file = "matplotlib-3.9.3-cp313-cp313-win_amd64.whl", hash = "sha256:90a85a004fefed9e583597478420bf904bb1a065b0b0ee5b9d8d31b04b0f3f70"},
370 | {file = "matplotlib-3.9.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3119b2f16de7f7b9212ba76d8fe6a0e9f90b27a1e04683cd89833a991682f639"},
371 | {file = "matplotlib-3.9.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:87ad73763d93add1b6c1f9fcd33af662fd62ed70e620c52fcb79f3ac427cf3a6"},
372 | {file = "matplotlib-3.9.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:026bdf3137ab6022c866efa4813b6bbeddc2ed4c9e7e02f0e323a7bca380dfa0"},
373 | {file = "matplotlib-3.9.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760a5e89ebbb172989e8273024a1024b0f084510b9105261b3b00c15e9c9f006"},
374 | {file = "matplotlib-3.9.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a42b9dc42de2cfe357efa27d9c50c7833fc5ab9b2eb7252ccd5d5f836a84e1e4"},
375 | {file = "matplotlib-3.9.3-cp313-cp313t-win_amd64.whl", hash = "sha256:e0fcb7da73fbf67b5f4bdaa57d85bb585a4e913d4a10f3e15b32baea56a67f0a"},
376 | {file = "matplotlib-3.9.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:031b7f5b8e595cc07def77ec5b58464e9bb67dc5760be5d6f26d9da24892481d"},
377 | {file = "matplotlib-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9fa6e193c14d6944e0685cdb527cb6b38b0e4a518043e7212f214113af7391da"},
378 | {file = "matplotlib-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e6eefae6effa0c35bbbc18c25ee6e0b1da44d2359c3cd526eb0c9e703cf055d"},
379 | {file = "matplotlib-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d3e5c7a99bd28afb957e1ae661323b0800d75b419f24d041ed1cc5d844a764"},
380 | {file = "matplotlib-3.9.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:816a966d5d376bf24c92af8f379e78e67278833e4c7cbc9fa41872eec629a060"},
381 | {file = "matplotlib-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fb0b37c896172899a4a93d9442ffdc6f870165f59e05ce2e07c6fded1c15749"},
382 | {file = "matplotlib-3.9.3-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f2a4ea08e6876206d511365b0bc234edc813d90b930be72c3011bbd7898796f"},
383 | {file = "matplotlib-3.9.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:9b081dac96ab19c54fd8558fac17c9d2c9cb5cc4656e7ed3261ddc927ba3e2c5"},
384 | {file = "matplotlib-3.9.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a0a63cb8404d1d1f94968ef35738900038137dab8af836b6c21bb6f03d75465"},
385 | {file = "matplotlib-3.9.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:896774766fd6be4571a43bc2fcbcb1dcca0807e53cab4a5bf88c4aa861a08e12"},
386 | {file = "matplotlib-3.9.3.tar.gz", hash = "sha256:cd5dbbc8e25cad5f706845c4d100e2c8b34691b412b93717ce38d8ae803bcfa5"},
387 | ]
388 |
389 | [package.dependencies]
390 | contourpy = ">=1.0.1"
391 | cycler = ">=0.10"
392 | fonttools = ">=4.22.0"
393 | kiwisolver = ">=1.3.1"
394 | numpy = ">=1.23"
395 | packaging = ">=20.0"
396 | pillow = ">=8"
397 | pyparsing = ">=2.3.1"
398 | python-dateutil = ">=2.7"
399 |
400 | [package.extras]
401 | dev = ["meson-python (>=0.13.1)", "numpy (>=1.25)", "pybind11 (>=2.6,!=2.13.3)", "setuptools (>=64)", "setuptools_scm (>=7)"]
402 |
403 | [[package]]
404 | name = "mpmath"
405 | version = "1.3.0"
406 | description = "Python library for arbitrary-precision floating-point arithmetic"
407 | optional = false
408 | python-versions = "*"
409 | files = [
410 | {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"},
411 | {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"},
412 | ]
413 |
414 | [package.extras]
415 | develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"]
416 | docs = ["sphinx"]
417 | gmpy = ["gmpy2 (>=2.1.0a4)"]
418 | tests = ["pytest (>=4.6)"]
419 |
420 | [[package]]
421 | name = "numpy"
422 | version = "2.2.0"
423 | description = "Fundamental package for array computing in Python"
424 | optional = false
425 | python-versions = ">=3.10"
426 | files = [
427 | {file = "numpy-2.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1e25507d85da11ff5066269d0bd25d06e0a0f2e908415534f3e603d2a78e4ffa"},
428 | {file = "numpy-2.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a62eb442011776e4036af5c8b1a00b706c5bc02dc15eb5344b0c750428c94219"},
429 | {file = "numpy-2.2.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:b606b1aaf802e6468c2608c65ff7ece53eae1a6874b3765f69b8ceb20c5fa78e"},
430 | {file = "numpy-2.2.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:36b2b43146f646642b425dd2027730f99bac962618ec2052932157e213a040e9"},
431 | {file = "numpy-2.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fe8f3583e0607ad4e43a954e35c1748b553bfe9fdac8635c02058023277d1b3"},
432 | {file = "numpy-2.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:122fd2fcfafdefc889c64ad99c228d5a1f9692c3a83f56c292618a59aa60ae83"},
433 | {file = "numpy-2.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3f2f5cddeaa4424a0a118924b988746db6ffa8565e5829b1841a8a3bd73eb59a"},
434 | {file = "numpy-2.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7fe4bb0695fe986a9e4deec3b6857003b4cfe5c5e4aac0b95f6a658c14635e31"},
435 | {file = "numpy-2.2.0-cp310-cp310-win32.whl", hash = "sha256:b30042fe92dbd79f1ba7f6898fada10bdaad1847c44f2dff9a16147e00a93661"},
436 | {file = "numpy-2.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dc1d6d66f8d37843ed281773c7174f03bf7ad826523f73435deb88ba60d2d4"},
437 | {file = "numpy-2.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9874bc2ff574c40ab7a5cbb7464bf9b045d617e36754a7bc93f933d52bd9ffc6"},
438 | {file = "numpy-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0da8495970f6b101ddd0c38ace92edea30e7e12b9a926b57f5fabb1ecc25bb90"},
439 | {file = "numpy-2.2.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0557eebc699c1c34cccdd8c3778c9294e8196df27d713706895edc6f57d29608"},
440 | {file = "numpy-2.2.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:3579eaeb5e07f3ded59298ce22b65f877a86ba8e9fe701f5576c99bb17c283da"},
441 | {file = "numpy-2.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40deb10198bbaa531509aad0cd2f9fadb26c8b94070831e2208e7df543562b74"},
442 | {file = "numpy-2.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2aed8fcf8abc3020d6a9ccb31dbc9e7d7819c56a348cc88fd44be269b37427e"},
443 | {file = "numpy-2.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a222d764352c773aa5ebde02dd84dba3279c81c6db2e482d62a3fa54e5ece69b"},
444 | {file = "numpy-2.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4e58666988605e251d42c2818c7d3d8991555381be26399303053b58a5bbf30d"},
445 | {file = "numpy-2.2.0-cp311-cp311-win32.whl", hash = "sha256:4723a50e1523e1de4fccd1b9a6dcea750c2102461e9a02b2ac55ffeae09a4410"},
446 | {file = "numpy-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:16757cf28621e43e252c560d25b15f18a2f11da94fea344bf26c599b9cf54b73"},
447 | {file = "numpy-2.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cff210198bb4cae3f3c100444c5eaa573a823f05c253e7188e1362a5555235b3"},
448 | {file = "numpy-2.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b92a5828bd4d9aa0952492b7de803135038de47343b2aa3cc23f3b71a3dc4e"},
449 | {file = "numpy-2.2.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:ebe5e59545401fbb1b24da76f006ab19734ae71e703cdb4a8b347e84a0cece67"},
450 | {file = "numpy-2.2.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e2b8cd48a9942ed3f85b95ca4105c45758438c7ed28fff1e4ce3e57c3b589d8e"},
451 | {file = "numpy-2.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57fcc997ffc0bef234b8875a54d4058afa92b0b0c4223fc1f62f24b3b5e86038"},
452 | {file = "numpy-2.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ad7d11b309bd132d74397fcf2920933c9d1dc865487128f5c03d580f2c3d03"},
453 | {file = "numpy-2.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cb24cca1968b21355cc6f3da1a20cd1cebd8a023e3c5b09b432444617949085a"},
454 | {file = "numpy-2.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0798b138c291d792f8ea40fe3768610f3c7dd2574389e37c3f26573757c8f7ef"},
455 | {file = "numpy-2.2.0-cp312-cp312-win32.whl", hash = "sha256:afe8fb968743d40435c3827632fd36c5fbde633b0423da7692e426529b1759b1"},
456 | {file = "numpy-2.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:3a4199f519e57d517ebd48cb76b36c82da0360781c6a0353e64c0cac30ecaad3"},
457 | {file = "numpy-2.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f8c8b141ef9699ae777c6278b52c706b653bf15d135d302754f6b2e90eb30367"},
458 | {file = "numpy-2.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f0986e917aca18f7a567b812ef7ca9391288e2acb7a4308aa9d265bd724bdae"},
459 | {file = "numpy-2.2.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:1c92113619f7b272838b8d6702a7f8ebe5edea0df48166c47929611d0b4dea69"},
460 | {file = "numpy-2.2.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5a145e956b374e72ad1dff82779177d4a3c62bc8248f41b80cb5122e68f22d13"},
461 | {file = "numpy-2.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18142b497d70a34b01642b9feabb70156311b326fdddd875a9981f34a369b671"},
462 | {file = "numpy-2.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7d41d1612c1a82b64697e894b75db6758d4f21c3ec069d841e60ebe54b5b571"},
463 | {file = "numpy-2.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a98f6f20465e7618c83252c02041517bd2f7ea29be5378f09667a8f654a5918d"},
464 | {file = "numpy-2.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e09d40edfdb4e260cb1567d8ae770ccf3b8b7e9f0d9b5c2a9992696b30ce2742"},
465 | {file = "numpy-2.2.0-cp313-cp313-win32.whl", hash = "sha256:3905a5fffcc23e597ee4d9fb3fcd209bd658c352657548db7316e810ca80458e"},
466 | {file = "numpy-2.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a184288538e6ad699cbe6b24859206e38ce5fba28f3bcfa51c90d0502c1582b2"},
467 | {file = "numpy-2.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7832f9e8eb00be32f15fdfb9a981d6955ea9adc8574c521d48710171b6c55e95"},
468 | {file = "numpy-2.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f0dd071b95bbca244f4cb7f70b77d2ff3aaaba7fa16dc41f58d14854a6204e6c"},
469 | {file = "numpy-2.2.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:b0b227dcff8cdc3efbce66d4e50891f04d0a387cce282fe1e66199146a6a8fca"},
470 | {file = "numpy-2.2.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ab153263a7c5ccaf6dfe7e53447b74f77789f28ecb278c3b5d49db7ece10d6d"},
471 | {file = "numpy-2.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e500aba968a48e9019e42c0c199b7ec0696a97fa69037bea163b55398e390529"},
472 | {file = "numpy-2.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:440cfb3db4c5029775803794f8638fbdbf71ec702caf32735f53b008e1eaece3"},
473 | {file = "numpy-2.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a55dc7a7f0b6198b07ec0cd445fbb98b05234e8b00c5ac4874a63372ba98d4ab"},
474 | {file = "numpy-2.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4bddbaa30d78c86329b26bd6aaaea06b1e47444da99eddac7bf1e2fab717bd72"},
475 | {file = "numpy-2.2.0-cp313-cp313t-win32.whl", hash = "sha256:30bf971c12e4365153afb31fc73f441d4da157153f3400b82db32d04de1e4066"},
476 | {file = "numpy-2.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d35717333b39d1b6bb8433fa758a55f1081543de527171543a2b710551d40881"},
477 | {file = "numpy-2.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e12c6c1ce84628c52d6367863773f7c8c8241be554e8b79686e91a43f1733773"},
478 | {file = "numpy-2.2.0-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:b6207dc8fb3c8cb5668e885cef9ec7f70189bec4e276f0ff70d5aa078d32c88e"},
479 | {file = "numpy-2.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a50aeff71d0f97b6450d33940c7181b08be1441c6c193e678211bff11aa725e7"},
480 | {file = "numpy-2.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:df12a1f99b99f569a7c2ae59aa2d31724e8d835fc7f33e14f4792e3071d11221"},
481 | {file = "numpy-2.2.0.tar.gz", hash = "sha256:140dd80ff8981a583a60980be1a655068f8adebf7a45a06a6858c873fcdcd4a0"},
482 | ]
483 |
484 | [[package]]
485 | name = "packaging"
486 | version = "24.2"
487 | description = "Core utilities for Python packages"
488 | optional = false
489 | python-versions = ">=3.8"
490 | files = [
491 | {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
492 | {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
493 | ]
494 |
495 | [[package]]
496 | name = "pandas"
497 | version = "2.2.3"
498 | description = "Powerful data structures for data analysis, time series, and statistics"
499 | optional = false
500 | python-versions = ">=3.9"
501 | files = [
502 | {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"},
503 | {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"},
504 | {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"},
505 | {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"},
506 | {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"},
507 | {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"},
508 | {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"},
509 | {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"},
510 | {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"},
511 | {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"},
512 | {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"},
513 | {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"},
514 | {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"},
515 | {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"},
516 | {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"},
517 | {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"},
518 | {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"},
519 | {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"},
520 | {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"},
521 | {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"},
522 | {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"},
523 | {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"},
524 | {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"},
525 | {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"},
526 | {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"},
527 | {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"},
528 | {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"},
529 | {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"},
530 | {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"},
531 | {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"},
532 | {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"},
533 | {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"},
534 | {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"},
535 | {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"},
536 | {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"},
537 | {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"},
538 | {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"},
539 | {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"},
540 | {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"},
541 | {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"},
542 | {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"},
543 | {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"},
544 | ]
545 |
546 | [package.dependencies]
547 | numpy = [
548 | {version = ">=1.22.4", markers = "python_version < \"3.11\""},
549 | {version = ">=1.23.2", markers = "python_version == \"3.11\""},
550 | {version = ">=1.26.0", markers = "python_version >= \"3.12\""},
551 | ]
552 | python-dateutil = ">=2.8.2"
553 | pytz = ">=2020.1"
554 | tzdata = ">=2022.7"
555 |
556 | [package.extras]
557 | all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"]
558 | aws = ["s3fs (>=2022.11.0)"]
559 | clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"]
560 | compression = ["zstandard (>=0.19.0)"]
561 | computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"]
562 | consortium-standard = ["dataframe-api-compat (>=0.1.7)"]
563 | excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"]
564 | feather = ["pyarrow (>=10.0.1)"]
565 | fss = ["fsspec (>=2022.11.0)"]
566 | gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"]
567 | hdf5 = ["tables (>=3.8.0)"]
568 | html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"]
569 | mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"]
570 | output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"]
571 | parquet = ["pyarrow (>=10.0.1)"]
572 | performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"]
573 | plot = ["matplotlib (>=3.6.3)"]
574 | postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"]
575 | pyarrow = ["pyarrow (>=10.0.1)"]
576 | spss = ["pyreadstat (>=1.2.0)"]
577 | sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"]
578 | test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"]
579 | xml = ["lxml (>=4.9.2)"]
580 |
581 | [[package]]
582 | name = "pdfkit"
583 | version = "1.0.0"
584 | description = "Wkhtmltopdf python wrapper to convert html to pdf using the webkit rendering engine and qt"
585 | optional = false
586 | python-versions = "*"
587 | files = [
588 | {file = "pdfkit-1.0.0-py2-none-any.whl", hash = "sha256:cc122e5aed594198ff7aaa566f2950d2163763576ab891c161bb1f6c630f5a8e"},
589 | {file = "pdfkit-1.0.0-py3-none-any.whl", hash = "sha256:a7a4ca0d978e44fa8310c4909f087052430a6e8e0b1dd7ceef657f139789f96f"},
590 | {file = "pdfkit-1.0.0.tar.gz", hash = "sha256:992f821e1e18fc8a0e701ecae24b51a2d598296a180caee0a24c0af181da02a9"},
591 | ]
592 |
593 | [[package]]
594 | name = "pillow"
595 | version = "11.0.0"
596 | description = "Python Imaging Library (Fork)"
597 | optional = false
598 | python-versions = ">=3.9"
599 | files = [
600 | {file = "pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947"},
601 | {file = "pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba"},
602 | {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086"},
603 | {file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9"},
604 | {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488"},
605 | {file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f"},
606 | {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb"},
607 | {file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97"},
608 | {file = "pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50"},
609 | {file = "pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c"},
610 | {file = "pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1"},
611 | {file = "pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc"},
612 | {file = "pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a"},
613 | {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3"},
614 | {file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5"},
615 | {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b"},
616 | {file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa"},
617 | {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306"},
618 | {file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9"},
619 | {file = "pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5"},
620 | {file = "pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291"},
621 | {file = "pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9"},
622 | {file = "pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923"},
623 | {file = "pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903"},
624 | {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4"},
625 | {file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f"},
626 | {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9"},
627 | {file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7"},
628 | {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6"},
629 | {file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc"},
630 | {file = "pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6"},
631 | {file = "pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47"},
632 | {file = "pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25"},
633 | {file = "pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699"},
634 | {file = "pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38"},
635 | {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2"},
636 | {file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2"},
637 | {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527"},
638 | {file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa"},
639 | {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f"},
640 | {file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb"},
641 | {file = "pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798"},
642 | {file = "pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de"},
643 | {file = "pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84"},
644 | {file = "pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b"},
645 | {file = "pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003"},
646 | {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2"},
647 | {file = "pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a"},
648 | {file = "pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8"},
649 | {file = "pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8"},
650 | {file = "pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904"},
651 | {file = "pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3"},
652 | {file = "pillow-11.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba"},
653 | {file = "pillow-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a"},
654 | {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916"},
655 | {file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d"},
656 | {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7"},
657 | {file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e"},
658 | {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f"},
659 | {file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae"},
660 | {file = "pillow-11.0.0-cp39-cp39-win32.whl", hash = "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4"},
661 | {file = "pillow-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd"},
662 | {file = "pillow-11.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd"},
663 | {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2"},
664 | {file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2"},
665 | {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b"},
666 | {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2"},
667 | {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830"},
668 | {file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734"},
669 | {file = "pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316"},
670 | {file = "pillow-11.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06"},
671 | {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273"},
672 | {file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790"},
673 | {file = "pillow-11.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944"},
674 | {file = "pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739"},
675 | ]
676 |
677 | [package.extras]
678 | docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
679 | fpx = ["olefile"]
680 | mic = ["olefile"]
681 | tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
682 | typing = ["typing-extensions"]
683 | xmp = ["defusedxml"]
684 |
685 | [[package]]
686 | name = "pyparsing"
687 | version = "3.2.0"
688 | description = "pyparsing module - Classes and methods to define and execute parsing grammars"
689 | optional = false
690 | python-versions = ">=3.9"
691 | files = [
692 | {file = "pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84"},
693 | {file = "pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c"},
694 | ]
695 |
696 | [package.extras]
697 | diagrams = ["jinja2", "railroad-diagrams"]
698 |
699 | [[package]]
700 | name = "python-dateutil"
701 | version = "2.9.0.post0"
702 | description = "Extensions to the standard Python datetime module"
703 | optional = false
704 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
705 | files = [
706 | {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
707 | {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
708 | ]
709 |
710 | [package.dependencies]
711 | six = ">=1.5"
712 |
713 | [[package]]
714 | name = "pytz"
715 | version = "2024.2"
716 | description = "World timezone definitions, modern and historical"
717 | optional = false
718 | python-versions = "*"
719 | files = [
720 | {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"},
721 | {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"},
722 | ]
723 |
724 | [[package]]
725 | name = "scipy"
726 | version = "1.14.1"
727 | description = "Fundamental algorithms for scientific computing in Python"
728 | optional = false
729 | python-versions = ">=3.10"
730 | files = [
731 | {file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"},
732 | {file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"},
733 | {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0"},
734 | {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3"},
735 | {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d"},
736 | {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69"},
737 | {file = "scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad"},
738 | {file = "scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5"},
739 | {file = "scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675"},
740 | {file = "scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2"},
741 | {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617"},
742 | {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8"},
743 | {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37"},
744 | {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2"},
745 | {file = "scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2"},
746 | {file = "scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94"},
747 | {file = "scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d"},
748 | {file = "scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07"},
749 | {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5"},
750 | {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc"},
751 | {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310"},
752 | {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066"},
753 | {file = "scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1"},
754 | {file = "scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f"},
755 | {file = "scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79"},
756 | {file = "scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e"},
757 | {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73"},
758 | {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e"},
759 | {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d"},
760 | {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e"},
761 | {file = "scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06"},
762 | {file = "scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84"},
763 | {file = "scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417"},
764 | ]
765 |
766 | [package.dependencies]
767 | numpy = ">=1.23.5,<2.3"
768 |
769 | [package.extras]
770 | dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"]
771 | doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"]
772 | test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"]
773 |
774 | [[package]]
775 | name = "six"
776 | version = "1.17.0"
777 | description = "Python 2 and 3 compatibility utilities"
778 | optional = false
779 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
780 | files = [
781 | {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
782 | {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
783 | ]
784 |
785 | [[package]]
786 | name = "smmap"
787 | version = "5.0.1"
788 | description = "A pure Python implementation of a sliding window memory map manager"
789 | optional = false
790 | python-versions = ">=3.7"
791 | files = [
792 | {file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"},
793 | {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"},
794 | ]
795 |
796 | [[package]]
797 | name = "tzdata"
798 | version = "2024.2"
799 | description = "Provider of IANA time zone data"
800 | optional = false
801 | python-versions = ">=2"
802 | files = [
803 | {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"},
804 | {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"},
805 | ]
806 |
807 | [[package]]
808 | name = "xarray"
809 | version = "2024.11.0"
810 | description = "N-D labeled arrays and datasets in Python"
811 | optional = false
812 | python-versions = ">=3.10"
813 | files = [
814 | {file = "xarray-2024.11.0-py3-none-any.whl", hash = "sha256:6ee94f63ddcbdd0cf3909d1177f78cdac756640279c0e32ae36819a89cdaba37"},
815 | {file = "xarray-2024.11.0.tar.gz", hash = "sha256:1ccace44573ddb862e210ad3ec204210654d2c750bec11bbe7d842dfc298591f"},
816 | ]
817 |
818 | [package.dependencies]
819 | numpy = ">=1.24"
820 | packaging = ">=23.2"
821 | pandas = ">=2.1"
822 |
823 | [package.extras]
824 | accel = ["bottleneck", "flox", "numba (>=0.54)", "numbagg", "opt_einsum", "scipy"]
825 | complete = ["xarray[accel,etc,io,parallel,viz]"]
826 | dev = ["hypothesis", "jinja2", "mypy", "pre-commit", "pytest", "pytest-cov", "pytest-env", "pytest-timeout", "pytest-xdist", "ruff", "sphinx", "sphinx_autosummary_accessors", "xarray[complete]"]
827 | etc = ["sparse"]
828 | io = ["cftime", "fsspec", "h5netcdf", "netCDF4", "pooch", "pydap", "scipy", "zarr"]
829 | parallel = ["dask[complete]"]
830 | viz = ["cartopy", "matplotlib", "nc-time-axis", "seaborn"]
831 |
832 | [metadata]
833 | lock-version = "2.0"
834 | python-versions = ">=3.10,<4.0"
835 | content-hash = "d3a43a2985996928958c8bd1ba5c221c760a5f86ba80696a368773d5c3f29bd5"
836 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.poetry]
2 | name = "OLBtools"
3 | version = "0.1.0"
4 | description = "An optical link budget tool, with support for scintillation, APDs, and 4 quadrant detectors"
5 | authors = ["Paul Serra "]
6 | readme = "README.md"
7 | packages = [{include = "OLBtools"}]
8 |
9 | [tool.poetry.dependencies]
10 | python = ">=3.10,<4.0"
11 | matplotlib = "^3.9.3"
12 | gitpython = "^3.1.43"
13 | numpy = "^2.2.0"
14 | scipy = "^1.14.1"
15 | lowtran = "^3.1.0"
16 | mpmath = "^1.3.0"
17 | pdfkit = "^1.0.0"
18 |
19 |
20 | [build-system]
21 | requires = ["poetry-core"]
22 | build-backend = "poetry.core.masonry.api"
23 |
--------------------------------------------------------------------------------