AeroViz 0.1.5__py3-none-any.whl → 0.1.7__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.
Potentially problematic release.
This version of AeroViz might be problematic. Click here for more details.
- AeroViz/dataProcess/Chemistry/_mass_volume.py +4 -3
- AeroViz/dataProcess/Chemistry/_ocec.py +20 -7
- AeroViz/dataProcess/Optical/_IMPROVE.py +2 -3
- AeroViz/dataProcess/SizeDistr/__init__.py +6 -10
- AeroViz/plot/__init__.py +1 -0
- AeroViz/plot/meteorology/meteorology.py +2 -0
- AeroViz/plot/optical/optical.py +1 -1
- AeroViz/plot/pie.py +14 -2
- AeroViz/plot/radar.py +184 -0
- AeroViz/plot/scatter.py +16 -7
- AeroViz/plot/templates/koschmieder.py +11 -8
- AeroViz/plot/timeseries/timeseries.py +0 -1
- AeroViz/rawDataReader/__init__.py +75 -70
- AeroViz/rawDataReader/config/supported_instruments.py +70 -38
- AeroViz/rawDataReader/core/__init__.py +208 -178
- AeroViz/rawDataReader/script/AE33.py +1 -1
- AeroViz/rawDataReader/script/AE43.py +1 -1
- AeroViz/rawDataReader/script/APS_3321.py +2 -2
- AeroViz/rawDataReader/script/Aurora.py +1 -1
- AeroViz/rawDataReader/script/BC1054.py +1 -1
- AeroViz/rawDataReader/script/EPA.py +39 -0
- AeroViz/rawDataReader/script/GRIMM.py +1 -1
- AeroViz/rawDataReader/script/IGAC.py +6 -23
- AeroViz/rawDataReader/script/MA350.py +1 -1
- AeroViz/rawDataReader/script/Minion.py +102 -30
- AeroViz/rawDataReader/script/NEPH.py +1 -1
- AeroViz/rawDataReader/script/{Sunset_OCEC.py → OCEC.py} +2 -2
- AeroViz/rawDataReader/script/SMPS.py +77 -0
- AeroViz/rawDataReader/script/TEOM.py +2 -2
- AeroViz/rawDataReader/script/VOC.py +2 -2
- AeroViz/rawDataReader/script/XRF.py +11 -0
- AeroViz/rawDataReader/script/__init__.py +4 -6
- {AeroViz-0.1.5.dist-info → AeroViz-0.1.7.dist-info}/METADATA +57 -32
- {AeroViz-0.1.5.dist-info → AeroViz-0.1.7.dist-info}/RECORD +37 -55
- AeroViz/process/__init__.py +0 -31
- AeroViz/process/core/DataProc.py +0 -19
- AeroViz/process/core/SizeDist.py +0 -90
- AeroViz/process/core/__init__.py +0 -4
- AeroViz/process/method/PyMieScatt_update.py +0 -567
- AeroViz/process/method/__init__.py +0 -2
- AeroViz/process/method/mie_theory.py +0 -260
- AeroViz/process/method/prop.py +0 -62
- AeroViz/process/script/AbstractDistCalc.py +0 -143
- AeroViz/process/script/Chemical.py +0 -177
- AeroViz/process/script/IMPACT.py +0 -49
- AeroViz/process/script/IMPROVE.py +0 -161
- AeroViz/process/script/Others.py +0 -65
- AeroViz/process/script/PSD.py +0 -103
- AeroViz/process/script/PSD_dry.py +0 -93
- AeroViz/process/script/__init__.py +0 -5
- AeroViz/process/script/retrieve_RI.py +0 -69
- AeroViz/rawDataReader/script/EPA_vertical.py +0 -46
- AeroViz/rawDataReader/script/SMPS_TH.py +0 -41
- AeroViz/rawDataReader/script/SMPS_aim11.py +0 -51
- AeroViz/rawDataReader/script/SMPS_genr.py +0 -51
- AeroViz/rawDataReader/script/Table.py +0 -27
- {AeroViz-0.1.5.dist-info → AeroViz-0.1.7.dist-info}/LICENSE +0 -0
- {AeroViz-0.1.5.dist-info → AeroViz-0.1.7.dist-info}/WHEEL +0 -0
- {AeroViz-0.1.5.dist-info → AeroViz-0.1.7.dist-info}/top_level.txt +0 -0
|
@@ -1,260 +0,0 @@
|
|
|
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.])
|
AeroViz/process/method/prop.py
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
from numpy import exp, log
|
|
3
|
-
from scipy.signal import find_peaks
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def geometric(dp: np.ndarray,
|
|
7
|
-
dist: np.ndarray
|
|
8
|
-
) -> tuple[float, float]:
|
|
9
|
-
""" Calculate the geometric mean and standard deviation. """
|
|
10
|
-
|
|
11
|
-
_gmd = (((dist * log(dp)).sum()) / dist.sum())
|
|
12
|
-
|
|
13
|
-
logdp_mesh, gmd_mesh = np.meshgrid(log(dp), _gmd)
|
|
14
|
-
_gsd = ((((logdp_mesh - gmd_mesh) ** 2) * dist).sum() / dist.sum()) ** .5
|
|
15
|
-
|
|
16
|
-
return exp(_gmd), exp(_gsd)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def contribution(dp: np.ndarray,
|
|
20
|
-
dist: np.ndarray
|
|
21
|
-
) -> tuple[float, float, float]:
|
|
22
|
-
""" Calculate the relative contribution of each mode. """
|
|
23
|
-
|
|
24
|
-
ultra = dist[(dp >= 11.8) & (dp < 100)].sum() / dist.sum()
|
|
25
|
-
accum = dist[(dp >= 100) & (dp < 1000)].sum() / dist.sum()
|
|
26
|
-
coars = dist[(dp >= 1000) & (dp < 2500)].sum() / dist.sum()
|
|
27
|
-
|
|
28
|
-
return ultra, accum, coars
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def mode(dp: np.ndarray,
|
|
32
|
-
dist: np.ndarray
|
|
33
|
-
) -> np.ndarray:
|
|
34
|
-
""" Find three peak mode in distribution. """
|
|
35
|
-
|
|
36
|
-
min_value = np.array([dist.min()])
|
|
37
|
-
mode, _ = find_peaks(np.concatenate([min_value, dist, min_value]), distance=len(dist) - 1)
|
|
38
|
-
|
|
39
|
-
return dp[mode - 1]
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def properties(dist,
|
|
43
|
-
dp: np.ndarray,
|
|
44
|
-
dlogdp: np.ndarray,
|
|
45
|
-
weighting: str
|
|
46
|
-
) -> dict:
|
|
47
|
-
""" for apply """
|
|
48
|
-
dist = np.array(dist)
|
|
49
|
-
|
|
50
|
-
gmd, gsd = geometric(dp, dist)
|
|
51
|
-
ultra, accum, coarse = contribution(dp, dist)
|
|
52
|
-
peak = mode(dp, dist)
|
|
53
|
-
|
|
54
|
-
return {key: round(value, 3) for key, value in
|
|
55
|
-
{f'total_{weighting}': (dist * dlogdp).sum(),
|
|
56
|
-
f'GMD_{weighting}': gmd,
|
|
57
|
-
f'GSD_{weighting}': gsd,
|
|
58
|
-
f'mode_{weighting}': peak[0],
|
|
59
|
-
f'ultra_{weighting}': ultra,
|
|
60
|
-
f'accum_{weighting}': accum,
|
|
61
|
-
f'coarse_{weighting}': coarse}
|
|
62
|
-
.items()}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
from abc import ABC, abstractmethod
|
|
2
|
-
from functools import partial
|
|
3
|
-
from typing import Literal
|
|
4
|
-
|
|
5
|
-
import numpy as np
|
|
6
|
-
from pandas import DataFrame, concat
|
|
7
|
-
|
|
8
|
-
from AeroViz.process.core.SizeDist import SizeDist
|
|
9
|
-
from AeroViz.process.method import properties, internal, external, core_shell, sensitivity
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class AbstractDistCalc(ABC):
|
|
13
|
-
@abstractmethod
|
|
14
|
-
def useApply(self) -> DataFrame:
|
|
15
|
-
pass
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class NumberDistCalc(AbstractDistCalc):
|
|
19
|
-
def __init__(self, psd: SizeDist):
|
|
20
|
-
self.psd = psd
|
|
21
|
-
|
|
22
|
-
def useApply(self) -> DataFrame:
|
|
23
|
-
""" Calculate number distribution """
|
|
24
|
-
return self.psd.data
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class SurfaceDistCalc(AbstractDistCalc):
|
|
28
|
-
def __init__(self, psd: SizeDist):
|
|
29
|
-
self.psd = psd
|
|
30
|
-
|
|
31
|
-
def useApply(self) -> DataFrame:
|
|
32
|
-
""" Calculate surface distribution """
|
|
33
|
-
return self.psd.data.dropna().apply(lambda col: np.pi * self.psd.dp ** 2 * np.array(col),
|
|
34
|
-
axis=1, result_type='broadcast').reindex(self.psd.index)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class VolumeDistCalc(AbstractDistCalc):
|
|
38
|
-
def __init__(self, psd: SizeDist):
|
|
39
|
-
self.psd = psd
|
|
40
|
-
|
|
41
|
-
def useApply(self) -> DataFrame:
|
|
42
|
-
""" Calculate volume distribution """
|
|
43
|
-
return self.psd.data.dropna().apply(lambda col: np.pi / 6 * self.psd.dp ** 3 * np.array(col),
|
|
44
|
-
axis=1, result_type='broadcast').reindex(self.psd.index)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class PropertiesDistCalc(AbstractDistCalc):
|
|
48
|
-
def __init__(self, psd: SizeDist):
|
|
49
|
-
self.psd = psd
|
|
50
|
-
|
|
51
|
-
def useApply(self) -> DataFrame:
|
|
52
|
-
""" Calculate properties of distribution """
|
|
53
|
-
return self.psd.data.dropna().apply(partial(properties, dp=self.psd.dp, dlogdp=self.psd.dlogdp,
|
|
54
|
-
weighting=self.psd.weighting),
|
|
55
|
-
axis=1, result_type='expand').reindex(self.psd.index)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class ExtinctionDistCalc(AbstractDistCalc):
|
|
59
|
-
mapping = {'internal': internal,
|
|
60
|
-
'external': external,
|
|
61
|
-
'core_shell': core_shell,
|
|
62
|
-
'sensitivity': sensitivity}
|
|
63
|
-
|
|
64
|
-
def __init__(self,
|
|
65
|
-
psd: SizeDist,
|
|
66
|
-
RI: DataFrame,
|
|
67
|
-
method: Literal['internal', 'external', 'config-shell', 'sensitivity'],
|
|
68
|
-
result_type: Literal['extinction', 'scattering', 'absorption'] = 'extinction'
|
|
69
|
-
):
|
|
70
|
-
self.psd = psd
|
|
71
|
-
self.RI = RI
|
|
72
|
-
if method not in ExtinctionDistCalc.mapping:
|
|
73
|
-
raise ValueError(f"Invalid method: {method}. Valid methods are: {list(ExtinctionDistCalc.mapping.keys())}")
|
|
74
|
-
self.method = ExtinctionDistCalc.mapping[method]
|
|
75
|
-
self.result_type = result_type
|
|
76
|
-
|
|
77
|
-
def useApply(self) -> DataFrame:
|
|
78
|
-
""" Calculate volume distribution """
|
|
79
|
-
combined_data = concat([self.psd.data, self.RI], axis=1).dropna()
|
|
80
|
-
return combined_data.apply(partial(self.method, dp=self.psd.dp, result_type=self.result_type),
|
|
81
|
-
axis=1, result_type='expand').reindex(self.psd.index).set_axis(self.psd.dp, axis=1)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
# TODO:
|
|
85
|
-
class LungDepositsDistCalc(AbstractDistCalc):
|
|
86
|
-
|
|
87
|
-
def __init__(self, psd: SizeDist, lung_curve):
|
|
88
|
-
self.psd = psd
|
|
89
|
-
self.lung_curve = lung_curve
|
|
90
|
-
|
|
91
|
-
def useApply(self) -> DataFrame:
|
|
92
|
-
pass
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
class DistributionCalculator: # 策略模式 (Strategy Pattern)
|
|
96
|
-
""" Interface for distribution calculator """
|
|
97
|
-
|
|
98
|
-
mapping = {'number': NumberDistCalc,
|
|
99
|
-
'surface': SurfaceDistCalc,
|
|
100
|
-
'volume': VolumeDistCalc,
|
|
101
|
-
'property': PropertiesDistCalc,
|
|
102
|
-
'extinction': ExtinctionDistCalc,
|
|
103
|
-
'lung_deposit': LungDepositsDistCalc}
|
|
104
|
-
|
|
105
|
-
def __init__(self,
|
|
106
|
-
calculator: Literal['number', 'surface', 'volume', 'property', 'extinction'],
|
|
107
|
-
psd: SizeDist,
|
|
108
|
-
RI: DataFrame = None,
|
|
109
|
-
method: str = None,
|
|
110
|
-
result_type: str = None
|
|
111
|
-
):
|
|
112
|
-
"""
|
|
113
|
-
Initialize the DistributionCalculator.
|
|
114
|
-
|
|
115
|
-
Parameters:
|
|
116
|
-
calculator (CalculatorType): The type of calculator.
|
|
117
|
-
psd (SizeDist): The particle size distribution data.
|
|
118
|
-
RI (Optional[DataFrame]): The refractive index data. Default is None.
|
|
119
|
-
method (Optional[str]): The method to use. Default is None.
|
|
120
|
-
result_type (Optional[str]): The result type. Default is None.
|
|
121
|
-
"""
|
|
122
|
-
if calculator not in DistributionCalculator.mapping.keys():
|
|
123
|
-
raise ValueError(
|
|
124
|
-
f"Invalid calculator: {calculator}. Valid calculators are: {list(DistributionCalculator.mapping.keys())}")
|
|
125
|
-
self.calculator = DistributionCalculator.mapping[calculator]
|
|
126
|
-
self.psd = psd
|
|
127
|
-
self.RI = RI
|
|
128
|
-
self.method = method
|
|
129
|
-
self.result_type = result_type
|
|
130
|
-
|
|
131
|
-
def useApply(self) -> DataFrame:
|
|
132
|
-
"""
|
|
133
|
-
Apply the calculator to the data.
|
|
134
|
-
|
|
135
|
-
Returns:
|
|
136
|
-
DataFrame: The calculated data.
|
|
137
|
-
"""
|
|
138
|
-
if self.RI is not None:
|
|
139
|
-
return self.calculator(self.psd, self.RI, self.method, self.result_type).useApply()
|
|
140
|
-
elif issubclass(self.calculator, (NumberDistCalc, SurfaceDistCalc, VolumeDistCalc, PropertiesDistCalc)):
|
|
141
|
-
return self.calculator(self.psd).useApply()
|
|
142
|
-
else:
|
|
143
|
-
raise ValueError("RI parameter is required for this calculator type")
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
from pandas import read_csv, concat, notna, DataFrame, to_numeric
|
|
5
|
-
|
|
6
|
-
from AeroViz.process.core import DataProc
|
|
7
|
-
from AeroViz.tools.datareader import DataReader
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class ChemicalProc(DataProc):
|
|
11
|
-
"""
|
|
12
|
-
A class for process chemical data.
|
|
13
|
-
|
|
14
|
-
Parameters:
|
|
15
|
-
-----------
|
|
16
|
-
reset : bool, optional
|
|
17
|
-
If True, resets the process. Default is False.
|
|
18
|
-
filename : str, optional
|
|
19
|
-
The name of the file to process. Default is None.
|
|
20
|
-
|
|
21
|
-
Methods:
|
|
22
|
-
--------
|
|
23
|
-
mass(_df):
|
|
24
|
-
Calculate mass-related parameters.
|
|
25
|
-
|
|
26
|
-
volume(_df):
|
|
27
|
-
Calculate volume-related parameters.
|
|
28
|
-
|
|
29
|
-
volume_average_mixing(_df):
|
|
30
|
-
Calculate volume average mixing parameters.
|
|
31
|
-
|
|
32
|
-
process_data():
|
|
33
|
-
Process data and save the result.
|
|
34
|
-
|
|
35
|
-
Attributes:
|
|
36
|
-
-----------
|
|
37
|
-
DEFAULT_PATH : Path
|
|
38
|
-
The default path for data files.
|
|
39
|
-
|
|
40
|
-
Examples:
|
|
41
|
-
---------
|
|
42
|
-
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
def __init__(self, file_paths: list[Path | str] = None):
|
|
46
|
-
super().__init__()
|
|
47
|
-
self.file_paths = [Path(fp) for fp in file_paths]
|
|
48
|
-
|
|
49
|
-
@staticmethod
|
|
50
|
-
def mass(_df): # Series like
|
|
51
|
-
Ammonium, Sulfate, Nitrate, OC, Soil, SS, EC, PM25 = _df
|
|
52
|
-
status = (Ammonium / 18) / (2 * (Sulfate / 96) + (Nitrate / 62))
|
|
53
|
-
|
|
54
|
-
if status >= 1:
|
|
55
|
-
_df['NH4_status'] = 'Enough'
|
|
56
|
-
_df['AS'] = 1.375 * Sulfate
|
|
57
|
-
_df['AN'] = 1.29 * Nitrate
|
|
58
|
-
|
|
59
|
-
if status < 1:
|
|
60
|
-
_df['NH4_status'] = 'Deficiency'
|
|
61
|
-
mol_A = Ammonium / 18
|
|
62
|
-
mol_S = Sulfate / 96
|
|
63
|
-
mol_N = Nitrate / 62
|
|
64
|
-
residual = mol_A - 2 * mol_S
|
|
65
|
-
|
|
66
|
-
if residual > 0:
|
|
67
|
-
_df['AS'] = 1.375 * Sulfate
|
|
68
|
-
_df['AN'] = residual * 80 if residual <= mol_N else mol_N * 80
|
|
69
|
-
|
|
70
|
-
else:
|
|
71
|
-
_df['AS'] = mol_A / 2 * 132 if mol_A <= 2 * mol_S else mol_S * 132
|
|
72
|
-
_df['AN'] = 0
|
|
73
|
-
|
|
74
|
-
_df['OM'] = 1.8 * OC
|
|
75
|
-
_df['Soil'] = 28.57 * Soil / 1000
|
|
76
|
-
_df['SS'] = 2.54 * SS
|
|
77
|
-
_df['EC'] = EC
|
|
78
|
-
_df['SIA'] = _df['AS'] + _df['AN']
|
|
79
|
-
_df['total_mass'] = _df[['AS', 'AN', 'OM', 'Soil', 'SS', 'EC']].sum()
|
|
80
|
-
species_lst = ['AS', 'AN', 'OM', 'Soil', 'SS', 'EC', 'SIA', 'unknown_mass']
|
|
81
|
-
|
|
82
|
-
_df['unknown_mass'] = PM25 - _df['total_mass'] if PM25 >= _df['total_mass'] else 0
|
|
83
|
-
for _species, _val in _df[species_lst].items():
|
|
84
|
-
_df[f'{_species}_ratio'] = _val / PM25 if PM25 >= _df['total_mass'] else _val / _df['total_mass']
|
|
85
|
-
|
|
86
|
-
return _df['NH4_status':]
|
|
87
|
-
|
|
88
|
-
@staticmethod
|
|
89
|
-
def volume(_df):
|
|
90
|
-
_df['AS_volume'] = (_df['AS'] / 1.76)
|
|
91
|
-
_df['AN_volume'] = (_df['AN'] / 1.73)
|
|
92
|
-
_df['OM_volume'] = (_df['OM'] / 1.4)
|
|
93
|
-
_df['Soil_volume'] = (_df['Soil'] / 2.6)
|
|
94
|
-
_df['SS_volume'] = (_df['SS'] / 2.16)
|
|
95
|
-
_df['EC_volume'] = (_df['EC'] / 1.5)
|
|
96
|
-
_df['ALWC_volume'] = _df['ALWC']
|
|
97
|
-
_df['total_volume'] = sum(_df['AS_volume':'EC_volume'])
|
|
98
|
-
|
|
99
|
-
for _species, _val in _df['AS_volume':'ALWC_volume'].items():
|
|
100
|
-
_df[f'{_species}_ratio'] = _val / _df['total_volume']
|
|
101
|
-
|
|
102
|
-
_df['density'] = _df['total_mass'] / _df['total_volume']
|
|
103
|
-
return _df['AS_volume':]
|
|
104
|
-
|
|
105
|
-
@staticmethod
|
|
106
|
-
def volume_average_mixing(_df):
|
|
107
|
-
_df['n_dry'] = (1.53 * _df['AS_volume_ratio'] +
|
|
108
|
-
1.55 * _df['AN_volume_ratio'] +
|
|
109
|
-
1.55 * _df['OM_volume_ratio'] +
|
|
110
|
-
1.56 * _df['Soil_volume_ratio'] +
|
|
111
|
-
1.54 * _df['SS_volume_ratio'] +
|
|
112
|
-
1.80 * _df['EC_volume_ratio'])
|
|
113
|
-
|
|
114
|
-
_df['k_dry'] = (0.00 * _df['OM_volume_ratio'] +
|
|
115
|
-
0.01 * _df['Soil_volume_ratio'] +
|
|
116
|
-
0.54 * _df["EC_volume_ratio"])
|
|
117
|
-
|
|
118
|
-
# 檢查_df['ALWC']是否缺失 -> 有值才計算ambient的折射率
|
|
119
|
-
if notna(_df['ALWC']):
|
|
120
|
-
v_dry = _df['total_volume']
|
|
121
|
-
v_wet = _df['total_volume'] + _df['ALWC']
|
|
122
|
-
|
|
123
|
-
multiplier = v_dry / v_wet
|
|
124
|
-
_df['ALWC_volume_ratio'] = (1 - multiplier)
|
|
125
|
-
|
|
126
|
-
_df['n_amb'] = (1.53 * _df['AS_volume_ratio'] +
|
|
127
|
-
1.55 * _df['AN_volume_ratio'] +
|
|
128
|
-
1.55 * _df['OM_volume_ratio'] +
|
|
129
|
-
1.56 * _df['Soil_volume_ratio'] +
|
|
130
|
-
1.54 * _df['SS_volume_ratio'] +
|
|
131
|
-
1.80 * _df['EC_volume_ratio']) * multiplier + \
|
|
132
|
-
(1.33 * _df['ALWC_volume_ratio'])
|
|
133
|
-
|
|
134
|
-
_df['k_amb'] = (0.00 * _df['OM_volume_ratio'] +
|
|
135
|
-
0.01 * _df['Soil_volume_ratio'] +
|
|
136
|
-
0.54 * _df['EC_volume_ratio']) * multiplier
|
|
137
|
-
|
|
138
|
-
_df['gRH'] = (v_wet / v_dry) ** (1 / 3)
|
|
139
|
-
|
|
140
|
-
return _df[['n_dry', 'k_dry', 'n_amb', 'k_amb', 'gRH']]
|
|
141
|
-
|
|
142
|
-
@staticmethod
|
|
143
|
-
def kappa(_df, diameter=0.5):
|
|
144
|
-
surface_tension, Mw, density, universal_gas_constant = 0.072, 18, 1, 8.314 # J/mole*K
|
|
145
|
-
|
|
146
|
-
A = 4 * (surface_tension * Mw) / (density * universal_gas_constant * (_df['AT'] + 273))
|
|
147
|
-
power = A / diameter
|
|
148
|
-
a_w = (_df['RH'] / 100) * (np.exp(-power))
|
|
149
|
-
|
|
150
|
-
_df['kappa_chem'] = (_df['gRH'] ** 3 - 1) * (1 - a_w) / a_w
|
|
151
|
-
_df['kappa_vam'] = np.nan
|
|
152
|
-
|
|
153
|
-
@staticmethod
|
|
154
|
-
def ISORROPIA():
|
|
155
|
-
pass
|
|
156
|
-
|
|
157
|
-
def process_data(self, reset: bool = False, save_file: Path | str = None) -> DataFrame:
|
|
158
|
-
save_file = Path(save_file)
|
|
159
|
-
if save_file.exists() and not reset:
|
|
160
|
-
return read_csv(save_file, parse_dates=['Time'], index_col='Time')
|
|
161
|
-
else:
|
|
162
|
-
df = concat([DataReader(file) for file in self.file_paths], axis=1).apply(to_numeric, errors='coerce')
|
|
163
|
-
|
|
164
|
-
df_mass = df[['NH4+', 'SO42-', 'NO3-', 'Optical_OC', 'Fe', 'Na+', 'Optical_EC', 'PM2.5']].dropna().apply(
|
|
165
|
-
self.mass,
|
|
166
|
-
axis=1)
|
|
167
|
-
df_mass['ALWC'] = df['ALWC']
|
|
168
|
-
df_volume = df_mass[['AS', 'AN', 'OM', 'Soil', 'SS', 'EC', 'total_mass', 'ALWC']].dropna().apply(
|
|
169
|
-
self.volume,
|
|
170
|
-
axis=1)
|
|
171
|
-
df_volume['ALWC'] = df['ALWC']
|
|
172
|
-
df_vam = df_volume.dropna().apply(self.volume_average_mixing, axis=1)
|
|
173
|
-
|
|
174
|
-
_df = concat([df_mass, df_volume.drop(['ALWC'], axis=1), df_vam], axis=1).reindex(df.index.copy())
|
|
175
|
-
_df.to_csv(save_file)
|
|
176
|
-
|
|
177
|
-
return _df
|
AeroViz/process/script/IMPACT.py
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
|
|
3
|
-
from pandas import DataFrame, read_csv, concat
|
|
4
|
-
|
|
5
|
-
from AeroViz.process.core import DataProc
|
|
6
|
-
from AeroViz.tools.datareader import DataReader
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class ImpactProc(DataProc):
|
|
10
|
-
"""
|
|
11
|
-
A class for processing impact data.
|
|
12
|
-
|
|
13
|
-
Parameters:
|
|
14
|
-
-----------
|
|
15
|
-
reset : bool, optional
|
|
16
|
-
If True, resets the processing. Default is False.
|
|
17
|
-
save_filename : str or Path, optional
|
|
18
|
-
The name or path to save the processed data. Default is 'IMPACT.csv'.
|
|
19
|
-
|
|
20
|
-
Methods:
|
|
21
|
-
--------
|
|
22
|
-
process_data(reset: bool = False, save_filename: str | Path = 'IMPACT.csv') -> DataFrame:
|
|
23
|
-
Process data and save the result.
|
|
24
|
-
|
|
25
|
-
save_data(data: DataFrame, save_filename: str | Path):
|
|
26
|
-
Save processed data to a file.
|
|
27
|
-
|
|
28
|
-
Attributes:
|
|
29
|
-
-----------
|
|
30
|
-
DEFAULT_PATH : Path
|
|
31
|
-
The default path for data files.
|
|
32
|
-
|
|
33
|
-
Examples:
|
|
34
|
-
---------
|
|
35
|
-
>>> df_custom = ImpactProc().process_data(reset=True, save_filename='custom_file.csv')
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
def __init__(self, file_paths: list[Path | str] = None):
|
|
39
|
-
super().__init__()
|
|
40
|
-
self.file_paths = [Path(fp) for fp in file_paths]
|
|
41
|
-
|
|
42
|
-
def process_data(self, reset: bool = False, save_file: Path | str = None) -> DataFrame:
|
|
43
|
-
save_file = Path(save_file)
|
|
44
|
-
if save_file.exists() and not reset:
|
|
45
|
-
return read_csv(save_file, parse_dates=['Time'], index_col='Time')
|
|
46
|
-
else:
|
|
47
|
-
_df = concat([DataReader(file) for file in self.file_paths], axis=1)
|
|
48
|
-
_df.to_csv(save_file)
|
|
49
|
-
return _df
|