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.
- AeroViz/__init__.py +13 -0
- AeroViz/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/data/DEFAULT_DATA.csv +1417 -0
- AeroViz/data/DEFAULT_PNSD_DATA.csv +1417 -0
- AeroViz/data/hysplit_example_data.txt +101 -0
- AeroViz/dataProcess/Chemistry/__init__.py +149 -0
- AeroViz/dataProcess/Chemistry/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Chemistry/_calculate.py +557 -0
- AeroViz/dataProcess/Chemistry/_isoropia.py +150 -0
- AeroViz/dataProcess/Chemistry/_mass_volume.py +487 -0
- AeroViz/dataProcess/Chemistry/_ocec.py +172 -0
- AeroViz/dataProcess/Chemistry/isrpia.cnf +21 -0
- AeroViz/dataProcess/Chemistry/isrpia2.exe +0 -0
- AeroViz/dataProcess/Optical/PyMieScatt_update.py +577 -0
- AeroViz/dataProcess/Optical/_IMPROVE.py +452 -0
- AeroViz/dataProcess/Optical/__init__.py +281 -0
- AeroViz/dataProcess/Optical/__pycache__/PyMieScatt_update.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Optical/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Optical/__pycache__/mie_theory.cpython-312.pyc +0 -0
- AeroViz/dataProcess/Optical/_derived.py +518 -0
- AeroViz/dataProcess/Optical/_extinction.py +123 -0
- AeroViz/dataProcess/Optical/_mie_sd.py +912 -0
- AeroViz/dataProcess/Optical/_retrieve_RI.py +243 -0
- AeroViz/dataProcess/Optical/coefficient.py +72 -0
- AeroViz/dataProcess/Optical/fRH.pkl +0 -0
- AeroViz/dataProcess/Optical/mie_theory.py +260 -0
- AeroViz/dataProcess/README.md +271 -0
- AeroViz/dataProcess/SizeDistr/__init__.py +245 -0
- AeroViz/dataProcess/SizeDistr/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/SizeDistr/__pycache__/_size_dist.cpython-312.pyc +0 -0
- AeroViz/dataProcess/SizeDistr/_size_dist.py +810 -0
- AeroViz/dataProcess/SizeDistr/merge/README.md +93 -0
- AeroViz/dataProcess/SizeDistr/merge/__init__.py +20 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v0.py +251 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v0_1.py +246 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v1.py +255 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v2.py +244 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v3.py +518 -0
- AeroViz/dataProcess/SizeDistr/merge/_merge_v4.py +422 -0
- AeroViz/dataProcess/SizeDistr/prop.py +62 -0
- AeroViz/dataProcess/VOC/__init__.py +14 -0
- AeroViz/dataProcess/VOC/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/VOC/_potential_par.py +108 -0
- AeroViz/dataProcess/VOC/support_voc.json +446 -0
- AeroViz/dataProcess/__init__.py +66 -0
- AeroViz/dataProcess/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/dataProcess/core/__init__.py +272 -0
- AeroViz/dataProcess/core/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/mcp_server.py +352 -0
- AeroViz/plot/__init__.py +13 -0
- AeroViz/plot/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/bar.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/box.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/pie.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/radar.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/regression.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/scatter.cpython-312.pyc +0 -0
- AeroViz/plot/__pycache__/violin.cpython-312.pyc +0 -0
- AeroViz/plot/bar.py +126 -0
- AeroViz/plot/box.py +69 -0
- AeroViz/plot/distribution/__init__.py +1 -0
- AeroViz/plot/distribution/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/distribution/__pycache__/distribution.cpython-312.pyc +0 -0
- AeroViz/plot/distribution/distribution.py +576 -0
- AeroViz/plot/meteorology/CBPF.py +295 -0
- AeroViz/plot/meteorology/__init__.py +3 -0
- AeroViz/plot/meteorology/__pycache__/CBPF.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/__pycache__/hysplit.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/__pycache__/wind_rose.cpython-312.pyc +0 -0
- AeroViz/plot/meteorology/hysplit.py +93 -0
- AeroViz/plot/meteorology/wind_rose.py +77 -0
- AeroViz/plot/optical/__init__.py +1 -0
- AeroViz/plot/optical/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/optical/__pycache__/optical.cpython-312.pyc +0 -0
- AeroViz/plot/optical/optical.py +388 -0
- AeroViz/plot/pie.py +210 -0
- AeroViz/plot/radar.py +184 -0
- AeroViz/plot/regression.py +200 -0
- AeroViz/plot/scatter.py +174 -0
- AeroViz/plot/templates/__init__.py +6 -0
- AeroViz/plot/templates/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/ammonium_rich.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/contour.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/corr_matrix.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/diurnal_pattern.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/koschmieder.cpython-312.pyc +0 -0
- AeroViz/plot/templates/__pycache__/metal_heatmap.cpython-312.pyc +0 -0
- AeroViz/plot/templates/ammonium_rich.py +34 -0
- AeroViz/plot/templates/contour.py +47 -0
- AeroViz/plot/templates/corr_matrix.py +267 -0
- AeroViz/plot/templates/diurnal_pattern.py +61 -0
- AeroViz/plot/templates/koschmieder.py +95 -0
- AeroViz/plot/templates/metal_heatmap.py +164 -0
- AeroViz/plot/timeseries/__init__.py +2 -0
- AeroViz/plot/timeseries/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/timeseries/__pycache__/template.cpython-312.pyc +0 -0
- AeroViz/plot/timeseries/__pycache__/timeseries.cpython-312.pyc +0 -0
- AeroViz/plot/timeseries/template.py +47 -0
- AeroViz/plot/timeseries/timeseries.py +446 -0
- AeroViz/plot/utils/__init__.py +4 -0
- AeroViz/plot/utils/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/_color.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/_unit.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/plt_utils.cpython-312.pyc +0 -0
- AeroViz/plot/utils/__pycache__/sklearn_utils.cpython-312.pyc +0 -0
- AeroViz/plot/utils/_color.py +71 -0
- AeroViz/plot/utils/_unit.py +55 -0
- AeroViz/plot/utils/fRH.json +390 -0
- AeroViz/plot/utils/plt_utils.py +92 -0
- AeroViz/plot/utils/sklearn_utils.py +49 -0
- AeroViz/plot/utils/units.json +89 -0
- AeroViz/plot/violin.py +80 -0
- AeroViz/rawDataReader/FLOW.md +138 -0
- AeroViz/rawDataReader/__init__.py +220 -0
- AeroViz/rawDataReader/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/config/__init__.py +0 -0
- AeroViz/rawDataReader/config/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/config/__pycache__/supported_instruments.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/config/supported_instruments.py +135 -0
- AeroViz/rawDataReader/core/__init__.py +658 -0
- AeroViz/rawDataReader/core/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/logger.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/pre_process.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/qc.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/__pycache__/report.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/core/logger.py +171 -0
- AeroViz/rawDataReader/core/pre_process.py +308 -0
- AeroViz/rawDataReader/core/qc.py +961 -0
- AeroViz/rawDataReader/core/report.py +579 -0
- AeroViz/rawDataReader/script/AE33.py +173 -0
- AeroViz/rawDataReader/script/AE43.py +151 -0
- AeroViz/rawDataReader/script/APS.py +339 -0
- AeroViz/rawDataReader/script/Aurora.py +191 -0
- AeroViz/rawDataReader/script/BAM1020.py +90 -0
- AeroViz/rawDataReader/script/BC1054.py +161 -0
- AeroViz/rawDataReader/script/EPA.py +79 -0
- AeroViz/rawDataReader/script/GRIMM.py +68 -0
- AeroViz/rawDataReader/script/IGAC.py +140 -0
- AeroViz/rawDataReader/script/MA350.py +179 -0
- AeroViz/rawDataReader/script/Minion.py +218 -0
- AeroViz/rawDataReader/script/NEPH.py +199 -0
- AeroViz/rawDataReader/script/OCEC.py +173 -0
- AeroViz/rawDataReader/script/Q-ACSM.py +12 -0
- AeroViz/rawDataReader/script/SMPS.py +389 -0
- AeroViz/rawDataReader/script/TEOM.py +181 -0
- AeroViz/rawDataReader/script/VOC.py +106 -0
- AeroViz/rawDataReader/script/Xact.py +244 -0
- AeroViz/rawDataReader/script/__init__.py +28 -0
- AeroViz/rawDataReader/script/__pycache__/AE33.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/AE43.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/APS.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Aurora.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/BAM1020.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/BC1054.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/EPA.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/GRIMM.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/IGAC.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/MA350.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Minion.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/NEPH.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/OCEC.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Q-ACSM.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/SMPS.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/TEOM.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/VOC.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/Xact.cpython-312.pyc +0 -0
- AeroViz/rawDataReader/script/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/tools/__init__.py +2 -0
- AeroViz/tools/__pycache__/__init__.cpython-312.pyc +0 -0
- AeroViz/tools/__pycache__/database.cpython-312.pyc +0 -0
- AeroViz/tools/__pycache__/dataclassifier.cpython-312.pyc +0 -0
- AeroViz/tools/database.py +95 -0
- AeroViz/tools/dataclassifier.py +117 -0
- AeroViz/tools/dataprinter.py +58 -0
- aeroviz-0.1.21.dist-info/METADATA +294 -0
- aeroviz-0.1.21.dist-info/RECORD +180 -0
- aeroviz-0.1.21.dist-info/WHEEL +5 -0
- aeroviz-0.1.21.dist-info/licenses/LICENSE +21 -0
- 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.])
|