AeroViz 0.1.21__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. AeroViz/__init__.py +13 -0
  2. AeroViz/__pycache__/__init__.cpython-312.pyc +0 -0
  3. AeroViz/data/DEFAULT_DATA.csv +1417 -0
  4. AeroViz/data/DEFAULT_PNSD_DATA.csv +1417 -0
  5. AeroViz/data/hysplit_example_data.txt +101 -0
  6. AeroViz/dataProcess/Chemistry/__init__.py +149 -0
  7. AeroViz/dataProcess/Chemistry/__pycache__/__init__.cpython-312.pyc +0 -0
  8. AeroViz/dataProcess/Chemistry/_calculate.py +557 -0
  9. AeroViz/dataProcess/Chemistry/_isoropia.py +150 -0
  10. AeroViz/dataProcess/Chemistry/_mass_volume.py +487 -0
  11. AeroViz/dataProcess/Chemistry/_ocec.py +172 -0
  12. AeroViz/dataProcess/Chemistry/isrpia.cnf +21 -0
  13. AeroViz/dataProcess/Chemistry/isrpia2.exe +0 -0
  14. AeroViz/dataProcess/Optical/PyMieScatt_update.py +577 -0
  15. AeroViz/dataProcess/Optical/_IMPROVE.py +452 -0
  16. AeroViz/dataProcess/Optical/__init__.py +281 -0
  17. AeroViz/dataProcess/Optical/__pycache__/PyMieScatt_update.cpython-312.pyc +0 -0
  18. AeroViz/dataProcess/Optical/__pycache__/__init__.cpython-312.pyc +0 -0
  19. AeroViz/dataProcess/Optical/__pycache__/mie_theory.cpython-312.pyc +0 -0
  20. AeroViz/dataProcess/Optical/_derived.py +518 -0
  21. AeroViz/dataProcess/Optical/_extinction.py +123 -0
  22. AeroViz/dataProcess/Optical/_mie_sd.py +912 -0
  23. AeroViz/dataProcess/Optical/_retrieve_RI.py +243 -0
  24. AeroViz/dataProcess/Optical/coefficient.py +72 -0
  25. AeroViz/dataProcess/Optical/fRH.pkl +0 -0
  26. AeroViz/dataProcess/Optical/mie_theory.py +260 -0
  27. AeroViz/dataProcess/README.md +271 -0
  28. AeroViz/dataProcess/SizeDistr/__init__.py +245 -0
  29. AeroViz/dataProcess/SizeDistr/__pycache__/__init__.cpython-312.pyc +0 -0
  30. AeroViz/dataProcess/SizeDistr/__pycache__/_size_dist.cpython-312.pyc +0 -0
  31. AeroViz/dataProcess/SizeDistr/_size_dist.py +810 -0
  32. AeroViz/dataProcess/SizeDistr/merge/README.md +93 -0
  33. AeroViz/dataProcess/SizeDistr/merge/__init__.py +20 -0
  34. AeroViz/dataProcess/SizeDistr/merge/_merge_v0.py +251 -0
  35. AeroViz/dataProcess/SizeDistr/merge/_merge_v0_1.py +246 -0
  36. AeroViz/dataProcess/SizeDistr/merge/_merge_v1.py +255 -0
  37. AeroViz/dataProcess/SizeDistr/merge/_merge_v2.py +244 -0
  38. AeroViz/dataProcess/SizeDistr/merge/_merge_v3.py +518 -0
  39. AeroViz/dataProcess/SizeDistr/merge/_merge_v4.py +422 -0
  40. AeroViz/dataProcess/SizeDistr/prop.py +62 -0
  41. AeroViz/dataProcess/VOC/__init__.py +14 -0
  42. AeroViz/dataProcess/VOC/__pycache__/__init__.cpython-312.pyc +0 -0
  43. AeroViz/dataProcess/VOC/_potential_par.py +108 -0
  44. AeroViz/dataProcess/VOC/support_voc.json +446 -0
  45. AeroViz/dataProcess/__init__.py +66 -0
  46. AeroViz/dataProcess/__pycache__/__init__.cpython-312.pyc +0 -0
  47. AeroViz/dataProcess/core/__init__.py +272 -0
  48. AeroViz/dataProcess/core/__pycache__/__init__.cpython-312.pyc +0 -0
  49. AeroViz/mcp_server.py +352 -0
  50. AeroViz/plot/__init__.py +13 -0
  51. AeroViz/plot/__pycache__/__init__.cpython-312.pyc +0 -0
  52. AeroViz/plot/__pycache__/bar.cpython-312.pyc +0 -0
  53. AeroViz/plot/__pycache__/box.cpython-312.pyc +0 -0
  54. AeroViz/plot/__pycache__/pie.cpython-312.pyc +0 -0
  55. AeroViz/plot/__pycache__/radar.cpython-312.pyc +0 -0
  56. AeroViz/plot/__pycache__/regression.cpython-312.pyc +0 -0
  57. AeroViz/plot/__pycache__/scatter.cpython-312.pyc +0 -0
  58. AeroViz/plot/__pycache__/violin.cpython-312.pyc +0 -0
  59. AeroViz/plot/bar.py +126 -0
  60. AeroViz/plot/box.py +69 -0
  61. AeroViz/plot/distribution/__init__.py +1 -0
  62. AeroViz/plot/distribution/__pycache__/__init__.cpython-312.pyc +0 -0
  63. AeroViz/plot/distribution/__pycache__/distribution.cpython-312.pyc +0 -0
  64. AeroViz/plot/distribution/distribution.py +576 -0
  65. AeroViz/plot/meteorology/CBPF.py +295 -0
  66. AeroViz/plot/meteorology/__init__.py +3 -0
  67. AeroViz/plot/meteorology/__pycache__/CBPF.cpython-312.pyc +0 -0
  68. AeroViz/plot/meteorology/__pycache__/__init__.cpython-312.pyc +0 -0
  69. AeroViz/plot/meteorology/__pycache__/hysplit.cpython-312.pyc +0 -0
  70. AeroViz/plot/meteorology/__pycache__/wind_rose.cpython-312.pyc +0 -0
  71. AeroViz/plot/meteorology/hysplit.py +93 -0
  72. AeroViz/plot/meteorology/wind_rose.py +77 -0
  73. AeroViz/plot/optical/__init__.py +1 -0
  74. AeroViz/plot/optical/__pycache__/__init__.cpython-312.pyc +0 -0
  75. AeroViz/plot/optical/__pycache__/optical.cpython-312.pyc +0 -0
  76. AeroViz/plot/optical/optical.py +388 -0
  77. AeroViz/plot/pie.py +210 -0
  78. AeroViz/plot/radar.py +184 -0
  79. AeroViz/plot/regression.py +200 -0
  80. AeroViz/plot/scatter.py +174 -0
  81. AeroViz/plot/templates/__init__.py +6 -0
  82. AeroViz/plot/templates/__pycache__/__init__.cpython-312.pyc +0 -0
  83. AeroViz/plot/templates/__pycache__/ammonium_rich.cpython-312.pyc +0 -0
  84. AeroViz/plot/templates/__pycache__/contour.cpython-312.pyc +0 -0
  85. AeroViz/plot/templates/__pycache__/corr_matrix.cpython-312.pyc +0 -0
  86. AeroViz/plot/templates/__pycache__/diurnal_pattern.cpython-312.pyc +0 -0
  87. AeroViz/plot/templates/__pycache__/koschmieder.cpython-312.pyc +0 -0
  88. AeroViz/plot/templates/__pycache__/metal_heatmap.cpython-312.pyc +0 -0
  89. AeroViz/plot/templates/ammonium_rich.py +34 -0
  90. AeroViz/plot/templates/contour.py +47 -0
  91. AeroViz/plot/templates/corr_matrix.py +267 -0
  92. AeroViz/plot/templates/diurnal_pattern.py +61 -0
  93. AeroViz/plot/templates/koschmieder.py +95 -0
  94. AeroViz/plot/templates/metal_heatmap.py +164 -0
  95. AeroViz/plot/timeseries/__init__.py +2 -0
  96. AeroViz/plot/timeseries/__pycache__/__init__.cpython-312.pyc +0 -0
  97. AeroViz/plot/timeseries/__pycache__/template.cpython-312.pyc +0 -0
  98. AeroViz/plot/timeseries/__pycache__/timeseries.cpython-312.pyc +0 -0
  99. AeroViz/plot/timeseries/template.py +47 -0
  100. AeroViz/plot/timeseries/timeseries.py +446 -0
  101. AeroViz/plot/utils/__init__.py +4 -0
  102. AeroViz/plot/utils/__pycache__/__init__.cpython-312.pyc +0 -0
  103. AeroViz/plot/utils/__pycache__/_color.cpython-312.pyc +0 -0
  104. AeroViz/plot/utils/__pycache__/_unit.cpython-312.pyc +0 -0
  105. AeroViz/plot/utils/__pycache__/plt_utils.cpython-312.pyc +0 -0
  106. AeroViz/plot/utils/__pycache__/sklearn_utils.cpython-312.pyc +0 -0
  107. AeroViz/plot/utils/_color.py +71 -0
  108. AeroViz/plot/utils/_unit.py +55 -0
  109. AeroViz/plot/utils/fRH.json +390 -0
  110. AeroViz/plot/utils/plt_utils.py +92 -0
  111. AeroViz/plot/utils/sklearn_utils.py +49 -0
  112. AeroViz/plot/utils/units.json +89 -0
  113. AeroViz/plot/violin.py +80 -0
  114. AeroViz/rawDataReader/FLOW.md +138 -0
  115. AeroViz/rawDataReader/__init__.py +220 -0
  116. AeroViz/rawDataReader/__pycache__/__init__.cpython-312.pyc +0 -0
  117. AeroViz/rawDataReader/config/__init__.py +0 -0
  118. AeroViz/rawDataReader/config/__pycache__/__init__.cpython-312.pyc +0 -0
  119. AeroViz/rawDataReader/config/__pycache__/supported_instruments.cpython-312.pyc +0 -0
  120. AeroViz/rawDataReader/config/supported_instruments.py +135 -0
  121. AeroViz/rawDataReader/core/__init__.py +658 -0
  122. AeroViz/rawDataReader/core/__pycache__/__init__.cpython-312.pyc +0 -0
  123. AeroViz/rawDataReader/core/__pycache__/logger.cpython-312.pyc +0 -0
  124. AeroViz/rawDataReader/core/__pycache__/pre_process.cpython-312.pyc +0 -0
  125. AeroViz/rawDataReader/core/__pycache__/qc.cpython-312.pyc +0 -0
  126. AeroViz/rawDataReader/core/__pycache__/report.cpython-312.pyc +0 -0
  127. AeroViz/rawDataReader/core/logger.py +171 -0
  128. AeroViz/rawDataReader/core/pre_process.py +308 -0
  129. AeroViz/rawDataReader/core/qc.py +961 -0
  130. AeroViz/rawDataReader/core/report.py +579 -0
  131. AeroViz/rawDataReader/script/AE33.py +173 -0
  132. AeroViz/rawDataReader/script/AE43.py +151 -0
  133. AeroViz/rawDataReader/script/APS.py +339 -0
  134. AeroViz/rawDataReader/script/Aurora.py +191 -0
  135. AeroViz/rawDataReader/script/BAM1020.py +90 -0
  136. AeroViz/rawDataReader/script/BC1054.py +161 -0
  137. AeroViz/rawDataReader/script/EPA.py +79 -0
  138. AeroViz/rawDataReader/script/GRIMM.py +68 -0
  139. AeroViz/rawDataReader/script/IGAC.py +140 -0
  140. AeroViz/rawDataReader/script/MA350.py +179 -0
  141. AeroViz/rawDataReader/script/Minion.py +218 -0
  142. AeroViz/rawDataReader/script/NEPH.py +199 -0
  143. AeroViz/rawDataReader/script/OCEC.py +173 -0
  144. AeroViz/rawDataReader/script/Q-ACSM.py +12 -0
  145. AeroViz/rawDataReader/script/SMPS.py +389 -0
  146. AeroViz/rawDataReader/script/TEOM.py +181 -0
  147. AeroViz/rawDataReader/script/VOC.py +106 -0
  148. AeroViz/rawDataReader/script/Xact.py +244 -0
  149. AeroViz/rawDataReader/script/__init__.py +28 -0
  150. AeroViz/rawDataReader/script/__pycache__/AE33.cpython-312.pyc +0 -0
  151. AeroViz/rawDataReader/script/__pycache__/AE43.cpython-312.pyc +0 -0
  152. AeroViz/rawDataReader/script/__pycache__/APS.cpython-312.pyc +0 -0
  153. AeroViz/rawDataReader/script/__pycache__/Aurora.cpython-312.pyc +0 -0
  154. AeroViz/rawDataReader/script/__pycache__/BAM1020.cpython-312.pyc +0 -0
  155. AeroViz/rawDataReader/script/__pycache__/BC1054.cpython-312.pyc +0 -0
  156. AeroViz/rawDataReader/script/__pycache__/EPA.cpython-312.pyc +0 -0
  157. AeroViz/rawDataReader/script/__pycache__/GRIMM.cpython-312.pyc +0 -0
  158. AeroViz/rawDataReader/script/__pycache__/IGAC.cpython-312.pyc +0 -0
  159. AeroViz/rawDataReader/script/__pycache__/MA350.cpython-312.pyc +0 -0
  160. AeroViz/rawDataReader/script/__pycache__/Minion.cpython-312.pyc +0 -0
  161. AeroViz/rawDataReader/script/__pycache__/NEPH.cpython-312.pyc +0 -0
  162. AeroViz/rawDataReader/script/__pycache__/OCEC.cpython-312.pyc +0 -0
  163. AeroViz/rawDataReader/script/__pycache__/Q-ACSM.cpython-312.pyc +0 -0
  164. AeroViz/rawDataReader/script/__pycache__/SMPS.cpython-312.pyc +0 -0
  165. AeroViz/rawDataReader/script/__pycache__/TEOM.cpython-312.pyc +0 -0
  166. AeroViz/rawDataReader/script/__pycache__/VOC.cpython-312.pyc +0 -0
  167. AeroViz/rawDataReader/script/__pycache__/Xact.cpython-312.pyc +0 -0
  168. AeroViz/rawDataReader/script/__pycache__/__init__.cpython-312.pyc +0 -0
  169. AeroViz/tools/__init__.py +2 -0
  170. AeroViz/tools/__pycache__/__init__.cpython-312.pyc +0 -0
  171. AeroViz/tools/__pycache__/database.cpython-312.pyc +0 -0
  172. AeroViz/tools/__pycache__/dataclassifier.cpython-312.pyc +0 -0
  173. AeroViz/tools/database.py +95 -0
  174. AeroViz/tools/dataclassifier.py +117 -0
  175. AeroViz/tools/dataprinter.py +58 -0
  176. aeroviz-0.1.21.dist-info/METADATA +294 -0
  177. aeroviz-0.1.21.dist-info/RECORD +180 -0
  178. aeroviz-0.1.21.dist-info/WHEEL +5 -0
  179. aeroviz-0.1.21.dist-info/licenses/LICENSE +21 -0
  180. aeroviz-0.1.21.dist-info/top_level.txt +1 -0
@@ -0,0 +1,243 @@
1
+ """
2
+ Refractive index retrieval from optical measurements.
3
+
4
+ This module provides functions for retrieving the complex refractive
5
+ index of aerosol particles using a grid search minimization approach
6
+ based on Mie theory calculations.
7
+
8
+ Required Columns
9
+ ----------------
10
+ For retrieve_RI:
11
+ - Extinction : Extinction coefficient (Mm-1)
12
+ - Scattering : Scattering coefficient (Mm-1)
13
+ - Absorption : Absorption coefficient (Mm-1)
14
+ - PNSD columns : Particle number size distribution (dN/dlogDp)
15
+ """
16
+
17
+ import numpy as np
18
+ from pandas import DataFrame
19
+
20
+ from AeroViz.dataProcess.core import validate_inputs
21
+
22
+ __all__ = ['retrieve_RI', 'grid_search_RI', 'get_required_columns']
23
+
24
+ # Required columns for optical data
25
+ REQUIRED_OPTICAL_COLUMNS = ['Extinction', 'Scattering', 'Absorption']
26
+
27
+ COLUMN_DESCRIPTIONS = {
28
+ 'Extinction': 'Extinction coefficient 消光係數 (Mm-1)',
29
+ 'Scattering': 'Scattering coefficient 散射係數 (Mm-1)',
30
+ 'Absorption': 'Absorption coefficient 吸收係數 (Mm-1)',
31
+ }
32
+
33
+
34
+ def grid_search_RI(bext_mea: float,
35
+ bsca_mea: float,
36
+ babs_mea: float,
37
+ dp: np.ndarray,
38
+ ndp: np.ndarray,
39
+ dlogdp: float = 0.014,
40
+ wavelength: float = 550,
41
+ n_range: tuple = (1.33, 1.60),
42
+ k_range: tuple = (0.00, 0.60),
43
+ space_size: int = 31
44
+ ) -> tuple:
45
+ """
46
+ Retrieve the complex refractive index using grid search minimization.
47
+
48
+ This function performs a two-stage grid search to find the refractive
49
+ index that best matches the measured optical properties.
50
+
51
+ Parameters
52
+ ----------
53
+ bext_mea : float
54
+ Measured extinction coefficient (Mm-1).
55
+ bsca_mea : float
56
+ Measured scattering coefficient (Mm-1).
57
+ babs_mea : float
58
+ Measured absorption coefficient (Mm-1).
59
+ dp : np.ndarray
60
+ Particle diameter array (nm).
61
+ ndp : np.ndarray
62
+ Number concentration for each diameter (dN/dlogDp).
63
+ dlogdp : float, default=0.014
64
+ Logarithmic bin width.
65
+ wavelength : float, default=550
66
+ Wavelength for Mie calculation (nm).
67
+ n_range : tuple, default=(1.33, 1.60)
68
+ Range of real refractive index (n) to search.
69
+ k_range : tuple, default=(0.00, 0.60)
70
+ Range of imaginary refractive index (k) to search.
71
+ space_size : int, default=31
72
+ Number of grid points in each dimension.
73
+
74
+ Returns
75
+ -------
76
+ tuple
77
+ (n_retrieved, k_retrieved) - The retrieved refractive index components.
78
+ """
79
+ from .mie_theory import Mie_PESD
80
+
81
+ n_array = np.linspace(n_range[0], n_range[1], num=space_size)
82
+ k_array = np.linspace(k_range[0], k_range[1], space_size)
83
+ delta_array = np.zeros((space_size, space_size))
84
+
85
+ # First pass: coarse grid search
86
+ for ki, k in enumerate(k_array):
87
+ for ni, n in enumerate(n_array):
88
+ m = n + (1j * k)
89
+
90
+ ext_dist, sca_dist, abs_dist = Mie_PESD(m, wavelength, dp, ndp)
91
+
92
+ bext_cal = sum(ext_dist) * dlogdp
93
+ bsca_cal = sum(sca_dist) * dlogdp
94
+ babs_cal = sum(abs_dist) * dlogdp
95
+
96
+ # Normalized chi-squared
97
+ delta_array[ni][ki] = ((babs_mea - babs_cal) / 18.23) ** 2 + \
98
+ ((bsca_mea - bsca_cal) / 83.67) ** 2
99
+
100
+ # Find minimum and refine
101
+ min_delta = delta_array.argmin()
102
+ next_n = n_array[(min_delta // space_size)]
103
+ next_k = k_array[(min_delta % space_size)]
104
+
105
+ # Second pass: fine grid search around the minimum
106
+ n_min = max(next_n - 0.02, 1.33)
107
+ n_max = next_n + 0.02
108
+ k_min = max(next_k - 0.04, 0)
109
+ k_max = next_k + 0.04
110
+ fine_size = 41
111
+
112
+ n_fine = np.linspace(n_min, n_max, fine_size)
113
+ k_fine = np.linspace(k_min, k_max, fine_size)
114
+ delta_fine = np.zeros((fine_size, fine_size))
115
+
116
+ for ki, k in enumerate(k_fine):
117
+ for ni, n in enumerate(n_fine):
118
+ m = n + (1j * k)
119
+
120
+ ext_dist, sca_dist, abs_dist = Mie_PESD(m, wavelength, dp, ndp)
121
+
122
+ bext_cal = sum(ext_dist) * dlogdp
123
+ bsca_cal = sum(sca_dist) * dlogdp
124
+ babs_cal = sum(abs_dist) * dlogdp
125
+
126
+ delta_fine[ni][ki] = ((bext_mea - bext_cal) / 18.23) ** 2 + \
127
+ ((bsca_mea - bsca_cal) / 83.67) ** 2
128
+
129
+ min_delta_fine = delta_fine.argmin()
130
+ n_retrieved = n_fine[(min_delta_fine // fine_size)]
131
+ k_retrieved = k_fine[(min_delta_fine % fine_size)]
132
+
133
+ return n_retrieved, k_retrieved
134
+
135
+
136
+ def retrieve_RI(df_optical: DataFrame,
137
+ df_pnsd: DataFrame,
138
+ dlogdp: float = 0.014,
139
+ wavelength: float = 550,
140
+ n_range: tuple = (1.33, 1.60),
141
+ k_range: tuple = (0.00, 0.60),
142
+ space_size: int = 31
143
+ ) -> DataFrame:
144
+ """
145
+ Retrieve refractive index for a time series of measurements.
146
+
147
+ Parameters
148
+ ----------
149
+ df_optical : DataFrame
150
+ Optical measurements with required columns:
151
+ - Extinction : Extinction coefficient (Mm-1)
152
+ - Scattering : Scattering coefficient (Mm-1)
153
+ - Absorption : Absorption coefficient (Mm-1)
154
+ df_pnsd : DataFrame
155
+ Particle number size distribution with diameter columns (nm).
156
+ Column names should be diameter values (e.g., 10.0, 20.0, ...).
157
+ dlogdp : float, default=0.014
158
+ Logarithmic bin width.
159
+ wavelength : float, default=550
160
+ Wavelength for Mie calculation (nm).
161
+ n_range : tuple, default=(1.33, 1.60)
162
+ Range of real refractive index to search.
163
+ k_range : tuple, default=(0.00, 0.60)
164
+ Range of imaginary refractive index to search.
165
+ space_size : int, default=31
166
+ Number of grid points for initial search.
167
+
168
+ Returns
169
+ -------
170
+ DataFrame
171
+ Retrieved refractive index with columns: re_real, re_imaginary.
172
+
173
+ Raises
174
+ ------
175
+ ValueError
176
+ If required columns are missing from df_optical or df_pnsd is empty.
177
+
178
+ Examples
179
+ --------
180
+ >>> cols = get_required_columns()
181
+ >>> print(cols['retrieve_RI'])
182
+ """
183
+ from pandas import concat
184
+
185
+ # Validate optical data
186
+ validate_inputs(df_optical, REQUIRED_OPTICAL_COLUMNS, 'retrieve_RI', COLUMN_DESCRIPTIONS)
187
+
188
+ # Validate PNSD data
189
+ if df_pnsd is None or df_pnsd.empty:
190
+ raise ValueError(
191
+ "\nretrieve_RI() 需要粒徑分布資料!\n"
192
+ " 必要輸入: df_pnsd (Particle Number Size Distribution)\n"
193
+ " 欄位格式: 粒徑值作為欄位名稱 (nm)"
194
+ )
195
+
196
+ combined = concat([df_optical, df_pnsd], axis=1).dropna()
197
+ dp = np.array(df_pnsd.columns, dtype=float)
198
+
199
+ results = []
200
+
201
+ for idx, row in combined.iterrows():
202
+ bext_mea = row['Extinction']
203
+ bsca_mea = row['Scattering']
204
+ babs_mea = row['Absorption']
205
+ ndp = np.array(row[df_pnsd.columns])
206
+
207
+ n_ret, k_ret = grid_search_RI(
208
+ bext_mea, bsca_mea, babs_mea,
209
+ dp, ndp, dlogdp, wavelength,
210
+ n_range, k_range, space_size
211
+ )
212
+
213
+ results.append({'re_real': n_ret, 're_imaginary': k_ret})
214
+
215
+ result_df = DataFrame(results, index=combined.index)
216
+
217
+ return result_df.reindex(df_optical.index)
218
+
219
+
220
+ def get_required_columns():
221
+ """
222
+ Get required column names for refractive index retrieval.
223
+
224
+ Returns
225
+ -------
226
+ dict
227
+ Dictionary with function names as keys and required columns as values.
228
+
229
+ Examples
230
+ --------
231
+ >>> cols = get_required_columns()
232
+ >>> print(cols['retrieve_RI'])
233
+ """
234
+ return {
235
+ 'retrieve_RI': {
236
+ 'df_optical': REQUIRED_OPTICAL_COLUMNS.copy(),
237
+ 'df_pnsd': 'Diameter values as column names (nm)'
238
+ },
239
+ 'grid_search_RI': {
240
+ 'description': '單點反演,需提供標量值',
241
+ 'inputs': ['bext_mea', 'bsca_mea', 'babs_mea', 'dp (array)', 'ndp (array)']
242
+ }
243
+ }
@@ -0,0 +1,72 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ from scipy.optimize import curve_fit
4
+
5
+
6
+ def _scaCoe(df, instru, specified_band: list):
7
+ band_Neph = np.array([450, 550, 700])
8
+ band_Aurora = np.array([450, 525, 635])
9
+
10
+ band = band_Neph if instru == 'Neph' else band_Aurora
11
+
12
+ df_sca = df.copy().dropna()
13
+
14
+ if instru == 'Neph':
15
+ df_out = df_sca[['G']].copy()
16
+ df_out.columns = [f'sca_{_band}' for _band in specified_band]
17
+ else:
18
+ df_out = df_sca.apply(get_species_wavelength, axis=1, result_type='expand', args=(specified_band,))
19
+ df_out.columns = [f'sca_{_band}' for _band in specified_band]
20
+
21
+ # calculate
22
+ df_SAE = df[['B', 'G', 'R']].dropna().apply(get_Angstrom_exponent, axis=1, result_type='expand', args=(band,))
23
+ df_SAE.columns = ['SAE', 'SAE_intercept']
24
+
25
+ _df = pd.concat([df_out, df_SAE['SAE']], axis=1)
26
+
27
+ return _df.reindex(df.index)
28
+
29
+
30
+ def _absCoe(df, instru, specified_band: list):
31
+ band_AE33 = np.array([370, 470, 520, 590, 660, 880, 950])
32
+ band_BC1054 = np.array([370, 430, 470, 525, 565, 590, 660, 700, 880, 950])
33
+ band_MA350 = np.array([375, 470, 528, 625, 880])
34
+
35
+ MAE_AE33 = np.array([18.47, 14.54, 13.14, 11.58, 10.35, 7.77, 7.19]) * 1e-3
36
+ MAE_BC1054 = np.array([18.48, 15.90, 14.55, 13.02, 12.10, 11.59, 10.36, 9.77, 7.77, 7.20]) * 1e-3
37
+ MAE_MA350 = np.array([24.069, 19.070, 17.028, 14.091, 10.120]) * 1e-3
38
+
39
+ band = band_AE33 if instru == 'AE33' else band_BC1054
40
+ MAE = MAE_AE33 if instru == 'AE33' else MAE_BC1054
41
+ eBC = 'BC6' if instru == 'AE33' else 'BC9'
42
+
43
+ # calculate
44
+ df_abs = (df.copy().dropna() * MAE).copy()
45
+
46
+ df_out = df_abs.apply(get_species_wavelength, axis=1, result_type='expand', args=(specified_band,))
47
+ df_out.columns = [f'abs_{_band}' for _band in specified_band]
48
+ df_out['eBC'] = df[eBC]
49
+
50
+ df_AAE = df_abs.apply(get_Angstrom_exponent, axis=1, result_type='expand', args=(band,))
51
+ df_AAE.columns = ['AAE', 'AAE_intercept']
52
+ df_AAE = df_AAE.mask((-df_AAE['AAE'] < 0.8) | (-df_AAE['AAE'] > 2.)).copy()
53
+
54
+ _df = pd.concat([df_out, df_AAE['AAE']], axis=1)
55
+ return _df.reindex(df.index)
56
+
57
+
58
+ def get_species_wavelength(df, specified_band):
59
+ func = lambda wavelength, _sl, _int: _sl * wavelength + _int
60
+ popt, pcov = curve_fit(func, specified_band, df.values)
61
+
62
+ return func(np.array(specified_band), *popt)
63
+
64
+
65
+ def get_Angstrom_exponent(df, band):
66
+ if (df <= 0).any():
67
+ return pd.Series([np.nan, np.nan], index=['slope', 'intercept']) # 返回包含 NaN 的 Series,保持 DataFrame 结构
68
+
69
+ func = lambda wavelength, _sl, _int: _sl * wavelength + _int
70
+ popt, _ = curve_fit(func, np.log(band), np.log(df))
71
+
72
+ return pd.Series(popt, index=['slope', 'intercept']) # 返回带有索引的 Series
Binary file
@@ -0,0 +1,260 @@
1
+ from typing import Sequence, Literal
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ from numpy import exp, log, log10, sqrt, pi
6
+
7
+ from .PyMieScatt_update import AutoMieQ
8
+
9
+
10
+ def Mie_Q(m: complex,
11
+ wavelength: float,
12
+ dp: float | Sequence[float]
13
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
14
+ """
15
+ Calculate Mie scattering efficiency (Q) for given spherical particle diameter(s).
16
+
17
+ Parameters
18
+ ----------
19
+ m : complex
20
+ The complex refractive index of the particles.
21
+ wavelength : float
22
+ The wavelength of the incident light (in nm).
23
+ dp : float | Sequence[float]
24
+ Particle diameters (in nm), can be a single value or Sequence object.
25
+
26
+ Returns
27
+ -------
28
+ Q_ext : ndarray
29
+ The Mie extinction efficiency for each particle diameter.
30
+ Q_sca : ndarray
31
+ The Mie scattering efficiency for each particle diameter.
32
+ Q_abs : ndarray
33
+ The Mie absorption efficiency for each particle diameter.
34
+
35
+ Examples
36
+ --------
37
+ >>> Q_ext, Q_sca, Q_abs = Mie_Q(m=complex(1.5, 0.02), wavelength=550, dp=[100, 200, 300, 400])
38
+ """
39
+ # Ensure dp is a numpy array
40
+ dp = np.atleast_1d(dp)
41
+
42
+ # Transpose for proper unpacking
43
+ Q_ext, Q_sca, Q_abs, g, Q_pr, Q_back, Q_ratio = np.array([AutoMieQ(m, wavelength, _dp) for _dp in dp]).T
44
+
45
+ return Q_ext, Q_sca, Q_abs
46
+
47
+
48
+ def Mie_MEE(m: complex,
49
+ wavelength: float,
50
+ dp: float | Sequence[float],
51
+ density: float
52
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
53
+ """
54
+ Calculate mass extinction efficiency and other parameters.
55
+
56
+ Parameters
57
+ ----------
58
+ m : complex
59
+ The complex refractive index of the particles.
60
+ wavelength : float
61
+ The wavelength of the incident light.
62
+ dp : float | Sequence[float]
63
+ List of particle sizes or a single value.
64
+ density : float
65
+ The density of particles.
66
+
67
+ Returns
68
+ -------
69
+ MEE : ndarray
70
+ The mass extinction efficiency for each particle diameter.
71
+ MSE : ndarray
72
+ The mass scattering efficiency for each particle diameter.
73
+ MAE : ndarray
74
+ The mass absorption efficiency for each particle diameter.
75
+
76
+ Examples
77
+ --------
78
+ >>> MEE, MSE, MAE = Mie_MEE(m=complex(1.5, 0.02), wavelength=550, dp=[100, 200, 300, 400], density=1.2)
79
+ """
80
+ Q_ext, Q_sca, Q_abs = Mie_Q(m, wavelength, dp)
81
+
82
+ MEE = (3 * Q_ext) / (2 * density * dp) * 1000
83
+ MSE = (3 * Q_sca) / (2 * density * dp) * 1000
84
+ MAE = (3 * Q_abs) / (2 * density * dp) * 1000
85
+
86
+ return MEE, MSE, MAE
87
+
88
+
89
+ def Mie_PESD(m: complex,
90
+ wavelength: float = 550,
91
+ dp: float | Sequence[float] = None,
92
+ ndp: float | Sequence[float] = None,
93
+ lognormal: bool = False,
94
+ dp_range: tuple = (1, 2500),
95
+ geoMean: float = 200,
96
+ geoStdDev: float = 2,
97
+ numberOfParticles: float = 1e6,
98
+ numberOfBins: int = 167,
99
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
100
+ """
101
+ Simultaneously calculate "extinction distribution" and "integrated results" using the Mie_Q method.
102
+
103
+ Parameters
104
+ ----------
105
+ m : complex
106
+ The complex refractive index of the particles.
107
+ wavelength : float
108
+ The wavelength of the incident light.
109
+ dp : float | Sequence[float]
110
+ Particle sizes.
111
+ ndp : float | Sequence[float]
112
+ Number concentration from SMPS or APS in the units of dN/dlogdp.
113
+ lognormal : bool, optional
114
+ Whether to use lognormal distribution for ndp. Default is False.
115
+ dp_range : tuple, optional
116
+ Range of particle sizes. Default is (1, 2500) nm.
117
+ geoMean : float, optional
118
+ Geometric mean of the particle size distribution. Default is 200 nm.
119
+ geoStdDev : float, optional
120
+ Geometric standard deviation of the particle size distribution. Default is 2.
121
+ numberOfParticles : float, optional
122
+ Number of particles. Default is 1e6.
123
+ numberOfBins : int, optional
124
+ Number of bins for the lognormal distribution. Default is 167.
125
+
126
+ Returns
127
+ -------
128
+ ext_dist : ndarray
129
+ The extinction distribution for the given data.
130
+ sca_dist : ndarray
131
+ The scattering distribution for the given data.
132
+ abs_dist : ndarray
133
+ The absorption distribution for the given data.
134
+
135
+ Notes
136
+ -----
137
+ return in "dext/dlogdp", please make sure input the dNdlogdp data.
138
+
139
+ Examples
140
+ --------
141
+ >>> Ext, Sca, Abs = Mie_PESD(m=complex(1.5, 0.02), wavelength=550, dp=[100, 200, 500, 1000], ndp=[100, 50, 30, 20])
142
+ """
143
+ if lognormal:
144
+ dp = np.logspace(log10(dp_range[0]), log10(dp_range[1]), numberOfBins)
145
+
146
+ ndp = numberOfParticles * (1 / (log(geoStdDev) * sqrt(2 * pi)) *
147
+ exp(-(log(dp) - log(geoMean)) ** 2 / (2 * log(geoStdDev) ** 2)))
148
+
149
+ # dN / dlogdp
150
+ ndp = np.atleast_1d(ndp)
151
+
152
+ Q_ext, Q_sca, Q_abs = Mie_Q(m, wavelength, dp)
153
+
154
+ # The 1e-6 here is so that the final value is the same as the unit 1/10^6m.
155
+ Ext = Q_ext * (pi / 4 * dp ** 2) * ndp * 1e-6
156
+ Sca = Q_sca * (pi / 4 * dp ** 2) * ndp * 1e-6
157
+ Abs = Q_abs * (pi / 4 * dp ** 2) * ndp * 1e-6
158
+
159
+ return Ext, Sca, Abs
160
+
161
+
162
+ def internal(dist: pd.Series,
163
+ dp: float | Sequence[float],
164
+ wavelength: float = 550,
165
+ result_type: Literal['extinction', 'scattering', 'absorption'] = 'extinction'
166
+ ) -> np.ndarray:
167
+ """
168
+ Calculate the extinction distribution by internal mixing model.
169
+
170
+ Parameters
171
+ ----------
172
+ dist : pd.Series
173
+ Particle size distribution data.
174
+ dp : float | Sequence[float]
175
+ Diameter(s) of the particles, either a single value or a sequence.
176
+ wavelength : float, optional
177
+ Wavelength of the incident light, default is 550 nm.
178
+ result_type : {'extinction', 'scattering', 'absorption'}, optional
179
+ Type of result to calculate, defaults to 'extinction'.
180
+
181
+ Returns
182
+ -------
183
+ np.ndarray
184
+ Extinction distribution calculated based on the internal mixing model.
185
+ """
186
+ ext_dist, sca_dist, abs_dist = Mie_PESD(m=complex(dist['n_amb'], dist['k_amb']),
187
+ wavelength=wavelength,
188
+ dp=dp,
189
+ ndp=np.array(dist[:np.size(dp)]))
190
+
191
+ if result_type == 'extinction':
192
+ return ext_dist
193
+ elif result_type == 'scattering':
194
+ return sca_dist
195
+ else:
196
+ return abs_dist
197
+
198
+
199
+ # return dict(ext=ext_dist, sca=sca_dist, abs=abs_dist)
200
+
201
+
202
+ def external(dist: pd.Series,
203
+ dp: float | Sequence[float],
204
+ wavelength: float = 550,
205
+ result_type: Literal['extinction', 'scattering', 'absorption'] = 'extinction'
206
+ ) -> np.ndarray:
207
+ """
208
+ Calculate the extinction distribution by external mixing model.
209
+
210
+ Parameters
211
+ ----------
212
+ dist : pd.Series
213
+ Particle size distribution data.
214
+ dp : float | Sequence[float]
215
+ Diameter(s) of the particles, either a single value or a sequence.
216
+ wavelength : float, optional
217
+ Wavelength of the incident light, default is 550 nm.
218
+ result_type : {'extinction', 'scattering', 'absorption'}, optional
219
+ Type of result to calculate, defaults to 'extinction'.
220
+
221
+ Returns
222
+ -------
223
+ np.ndarray
224
+ Extinction distribution calculated based on the external mixing model.
225
+ """
226
+ refractive_dic = {'AS_volume_ratio': complex(1.53, 0.00),
227
+ 'AN_volume_ratio': complex(1.55, 0.00),
228
+ 'OM_volume_ratio': complex(1.54, 0.00),
229
+ 'Soil_volume_ratio': complex(1.56, 0.01),
230
+ 'SS_volume_ratio': complex(1.54, 0.00),
231
+ 'EC_volume_ratio': complex(1.80, 0.54),
232
+ 'ALWC_volume_ratio': complex(1.33, 0.00)}
233
+
234
+ ndp = np.array(dist[:np.size(dp)])
235
+ mie_results = (
236
+ Mie_PESD(refractive_dic[_specie], wavelength, dp, dist[_specie] / (1 + dist['ALWC_volume_ratio']) * ndp) for
237
+ _specie in refractive_dic)
238
+
239
+ ext_dist, sca_dist, abs_dist = (np.sum([res[0] for res in mie_results], axis=0),
240
+ np.sum([res[1] for res in mie_results], axis=0),
241
+ np.sum([res[2] for res in mie_results], axis=0))
242
+
243
+ if result_type == 'extinction':
244
+ return ext_dist
245
+ elif result_type == 'scattering':
246
+ return sca_dist
247
+ else:
248
+ return abs_dist
249
+
250
+
251
+ def core_shell():
252
+ pass
253
+
254
+
255
+ def sensitivity():
256
+ pass
257
+
258
+
259
+ if __name__ == '__main__':
260
+ result = Mie_Q(m=complex(1.5, 0.02), wavelength=550, dp=[100., 200.])