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,422 @@
1
+ # from ContainerHandle.dataProcess.config import _union_index
2
+
3
+ import warnings
4
+ from datetime import datetime as dtm
5
+ from functools import partial
6
+ from multiprocessing import Pool, cpu_count
7
+
8
+ import numpy as np
9
+ from pandas import DataFrame, concat, DatetimeIndex
10
+ # from scipy.interpolate import interp1d
11
+ from scipy.interpolate import UnivariateSpline as unvpline, interp1d
12
+
13
+ warnings.filterwarnings("ignore")
14
+
15
+ __all__ = ['_merge_SMPS_APS']
16
+
17
+
18
+ def _powerlaw_fit(_coeA, _coeB, _aps, _idx, _factor):
19
+ # breakpoint()
20
+
21
+ _smps_fit_df = _coeA * (_aps.keys().values / _factor) ** _coeB
22
+ return DataFrame(((_smps_fit_df.copy() - _aps.copy()) ** 2).sum(axis=1), columns=[_idx])
23
+
24
+
25
+ ## Calculate S2
26
+ ## 1. SMPS and APS power law fitting
27
+ ## 2. shift factor from 0.5 ~ 3
28
+ ## 3. calculate S2
29
+ ## return : S2
30
+ # def _S2_calculate_dN(_smps, _aps):
31
+ def _powerlaw_fit_dN(_smps, _aps, _alg_type):
32
+ print(f"\t\t\t{dtm.now().strftime('%m/%d %X')} : \033[92moverlap range fitting : {_alg_type}\033[0m")
33
+
34
+ ## overlap fitting
35
+ ## parmeter
36
+ _dt_indx = _smps.index
37
+
38
+ ## use SMPS data apply power law fitting
39
+ ## y = Ax^B, A = e**coefa, B = coefb, x = logx, y = logy
40
+ ## ref : http://mathworld.wolfram.com/LeastSquaresFittingPowerLaw.html
41
+ ## power law fit to SMPS num conc at upper bins to log curve
42
+
43
+ ## coefficient A, B
44
+ _smps_qc_cond = ((_smps != 0) & np.isfinite(_smps))
45
+ _smps_qc = _smps.where(_smps_qc_cond)
46
+
47
+ _size = _smps_qc_cond.sum(axis=1)
48
+ _size = _size.where(_size != 0.).copy()
49
+
50
+ _logx, _logy = np.log(_smps_qc.keys()._data.astype(float)), np.log(_smps_qc)
51
+ _x, _y, _xy, _xx = _logx.sum(), _logy.sum(axis=1), (_logx * _logy).sum(axis=1), (_logx ** 2).sum()
52
+
53
+ _coeB = ((_size * _xy - _x * _y) / (_size * _xx - _x ** 2.))
54
+ _coeA = np.exp((_y - _coeB * _x) / _size).values.reshape(-1, 1)
55
+ _coeB = _coeB.values.reshape(-1, 1)
56
+
57
+ ## rebuild shift smps data by coe. A, B
58
+ ## x_shift = (y_ori/A)**(1/B)
59
+ _aps_shift_x = (_aps / _coeA) ** (1 / _coeB)
60
+ _aps_shift_x = _aps_shift_x.where(np.isfinite(_aps_shift_x))
61
+
62
+ ## the least squares of diameter
63
+ ## the shift factor which the closest to 1
64
+ _shift_val = np.arange(0.3, 3.05, .05) ** .5
65
+ # _shift_val = np.arange(0.9, 1.805, .005)**.5
66
+
67
+ _shift_factor = DataFrame(columns=range(_shift_val.size), index=_aps_shift_x.index)
68
+ _shift_factor.loc[:, :] = _shift_val
69
+
70
+ # _dropna_idx = _shift_factor.dropna(how='all').index.copy()
71
+ _dropna_idx = _aps_shift_x.dropna(how='all').index.copy()
72
+
73
+ ## use the target function to get the similar aps and smps bin
74
+ ## S2 = sum( (smps_fit_line(dia) - aps(dia*shift_factor) )**2 )
75
+ ## assumption : the same diameter between smps and aps should get the same conc.
76
+
77
+ ## be sure they art in log value
78
+ _S2 = DataFrame(index=_aps_shift_x.index)
79
+ _dia_table = DataFrame(np.full(_aps_shift_x.shape, _aps_shift_x.keys()),
80
+ columns=_aps_shift_x.keys(), index=_aps_shift_x.index)
81
+
82
+ pool = Pool(cpu_count())
83
+
84
+ _S2 = pool.starmap(partial(_powerlaw_fit, _coeA, _coeB, _aps), list(enumerate(_shift_val)))
85
+
86
+ pool.close()
87
+ pool.join()
88
+
89
+ S2 = concat(_S2, axis=1)[np.arange(_shift_val.size)]
90
+ # S2 /= S2.max(axis=1).to_frame().values
91
+
92
+ shift_factor_dN = DataFrame(
93
+ _shift_factor.loc[_dropna_idx].values[range(len(_dropna_idx)), S2.loc[_dropna_idx].idxmin(axis=1).values],
94
+ index=_dropna_idx).reindex(_dt_indx).astype(float)
95
+
96
+ shift_factor_dN = shift_factor_dN.mask((shift_factor_dN ** 2 < 0.6) | (shift_factor_dN ** 2 > 2.6))
97
+
98
+ return shift_factor_dN
99
+
100
+
101
+ def _corr_fc(_aps_dia, _smps_dia, _smps_dn, _aps_dn, _smooth, _idx, _sh):
102
+ ds_fc = lambda _dt: _dt * _dt.index ** 2 * np.pi
103
+ dv_fc = lambda _dt: _dt * _dt.index ** 3 * np.pi / 6
104
+
105
+ _aps_sh = _aps_dia / _sh
106
+ _aps_sh_inp = _aps_sh.where((_aps_sh >= 500) & (_aps_sh <= 1500.)).copy()
107
+ _aps_sh_corr = _aps_sh.where((_aps_sh >= _smps_dia[-1]) & (_aps_sh <= 1500.)).copy()
108
+
109
+ corr_x = np.append(_smps_dia, _aps_sh_corr.dropna())
110
+
111
+ input_x = np.append(_smps_dia, _aps_sh_inp.dropna())
112
+ input_y = concat([_smps_dn, _aps_dn.iloc[:, ~np.isnan(_aps_sh_inp)]], axis=1)
113
+ input_y.columns = input_x
114
+
115
+ input_x.sort()
116
+ input_y = input_y[input_x]
117
+ corr_y = input_y[corr_x]
118
+
119
+ S2_lst = []
120
+ for (_tm, _inp_y_dn), (_tm, _cor_y_dn) in zip(input_y.dropna(how='all').iterrows(),
121
+ corr_y.dropna(how='all').iterrows()):
122
+ ## corr(spec_data, spec_spline)
123
+ _spl_dt = [unvpline(input_x, _inp_y, s=_smooth)(corr_x) for _inp_y in
124
+ [_inp_y_dn, ds_fc(_inp_y_dn), dv_fc(_inp_y_dn)]]
125
+ _cor_dt = [_cor_y_dn, ds_fc(_cor_y_dn), dv_fc(_cor_y_dn)]
126
+
127
+ _cor_all = sum([np.corrcoef(_cor, _spl)[0, 1] for _cor, _spl in zip(_cor_dt, _spl_dt)])
128
+
129
+ S2_lst.append((3 - _cor_all) / 3)
130
+
131
+ return DataFrame(S2_lst, columns=[_idx])
132
+
133
+
134
+ # def _S2_calculate_dSdV(_smps, _aps, _shft_dn, _S2, smps_ori, aps_ori):
135
+ # def _S2_calculate_dSdV(_smps, _aps, smps_ori=None):
136
+ def _corr_with_dNdSdV(_smps, _aps, _alg_type):
137
+ print(f"\t\t\t{dtm.now().strftime('%m/%d %X')} : \033[92moverlap range correlation : {_alg_type}\033[0m")
138
+
139
+ _smps_dia = _smps.keys().astype(float)
140
+ _aps_dia = _aps.keys().astype(float)
141
+
142
+ all_index = _smps.index.copy()
143
+ qc_index = DatetimeIndex(set(_smps.dropna(how='all').index) & set(_aps.dropna(how='all').index)).sort_values()
144
+
145
+ _smps_dn = _smps.loc[qc_index].copy()
146
+ _aps_dn = _aps.loc[qc_index].copy()
147
+
148
+ ds_fc = lambda _dt: _dt * _dt.index ** 2 * np.pi
149
+ dv_fc = lambda _dt: _dt * _dt.index ** 3 * np.pi / 6
150
+
151
+ _std_bin = np.geomspace(11.8, 19810, 230)
152
+ _merge_bin = _std_bin[(_std_bin >= _smps_dia[-1]) & (_std_bin < 1500)].copy()
153
+
154
+ _smooth = 50
155
+
156
+ _shift_val = np.arange(0.5, 2.605, .005) ** .5
157
+ _shift_val = np.arange(0.9, 2.01, .01) ** .5
158
+ _shift_val = np.arange(0.9, 2.65, .05) ** .5
159
+
160
+ ## spline fitting with shift aps and smps
161
+ pool = Pool(cpu_count())
162
+
163
+ S2_lst = pool.starmap(partial(_corr_fc, _aps_dia, _smps_dia, _smps_dn, _aps_dn, _smooth),
164
+ list(enumerate(_shift_val)))
165
+
166
+ pool.close()
167
+ pool.join()
168
+
169
+ S2_table = concat(S2_lst, axis=1).set_index(qc_index)[np.arange(_shift_val.size)].astype(float).dropna()
170
+ min_shft = S2_table.idxmin(axis=1).values
171
+
172
+ return DataFrame(_shift_val[min_shft.astype(int)], index=S2_table.index).astype(float).reindex(_smps.index)
173
+
174
+
175
+ ## Create merge data
176
+ ## shift all smps bin and remove the aps bin which smaller than the latest old smps bin
177
+ ## Return : merge bins, merge data, density
178
+ def _merge_data(_smps_ori, _aps_ori, _shift_ori, _smps_lb, _aps_hb, _shift_mode, _alg_type):
179
+ print(f"\t\t\t{dtm.now().strftime('%m/%d %X')} : \033[92mcreate merge data : {_shift_mode} and {_alg_type}\033[0m")
180
+
181
+ _ori_idx = _smps_ori.index.copy()
182
+ # _merge_idx = _smps_ori.loc[_aps_ori.dropna(how='all').index].dropna(how='all').index
183
+
184
+ _corr_aps_cond = _aps_ori.keys() < 700
185
+ _corr_aps_ky = _aps_ori.keys()[_corr_aps_cond]
186
+
187
+ _merge_idx = DatetimeIndex(set(_smps_ori.dropna(how='all').index) & set(_aps_ori.dropna(how='all').index) &
188
+ set(_shift_ori.dropna(how='all').index)).sort_values()
189
+
190
+ _smps, _aps, _shift = _smps_ori.loc[_merge_idx], _aps_ori.loc[_merge_idx], _shift_ori.loc[_merge_idx].values
191
+
192
+ ## parameter
193
+ _smps_key, _aps_key = _smps.keys()._data.astype(float), _aps.keys()._data.astype(float)
194
+
195
+ _cntr = 1000
196
+ _bin_lb = _smps_key[-1]
197
+
198
+ ## make shift bins
199
+ _smps_bin = np.full(_smps.shape, _smps_key)
200
+ _aps_bin = np.full(_aps.shape, _aps_key)
201
+
202
+ _std_bin = np.geomspace(_smps_key[0], _aps_key[-1], 230)
203
+ _std_bin_merge = _std_bin[(_std_bin < _cntr) & (_std_bin > _bin_lb)]
204
+ _std_bin_inte1 = _std_bin[_std_bin <= _bin_lb]
205
+ _std_bin_inte2 = _std_bin[_std_bin >= _cntr]
206
+
207
+ if _shift_mode == 'mobility':
208
+ _aps_bin /= _shift
209
+
210
+ elif _shift_mode == 'aerodynamic':
211
+ _smps_bin *= _shift
212
+
213
+ ## merge
214
+ _merge_lst, _corr_lst = [], []
215
+ for _bin_smps, _bin_aps, _dt_smps, _dt_aps, _sh in zip(_smps_bin, _aps_bin, _smps.values, _aps.values, _shift):
216
+ ## keep complete smps bins and data
217
+ ## remove the aps bin data lower than smps bin
218
+ _condi = _bin_aps >= _bin_smps[-1]
219
+
220
+ _merge_bin = np.hstack((_bin_smps, _bin_aps[_condi]))
221
+ _merge_dt = np.hstack((_dt_smps, _dt_aps[_condi]))
222
+
223
+ _merge_fit_loc = (_merge_bin < 1500) & (_merge_bin > _smps_lb)
224
+
225
+ ## coeA and coeB
226
+ _unvpl_fc = unvpline(np.log(_merge_bin[_merge_fit_loc]), np.log(_merge_dt[_merge_fit_loc]), s=50)
227
+ _inte_fc = interp1d(_merge_bin, _merge_dt, kind='linear', fill_value='extrapolate')
228
+
229
+ _merge_dt_fit = np.hstack((_inte_fc(_std_bin_inte1), np.exp(_unvpl_fc(np.log(_std_bin_merge))),
230
+ _inte_fc(_std_bin_inte2)))
231
+
232
+ _merge_lst.append(_merge_dt_fit)
233
+ _corr_lst.append(interp1d(_std_bin, _merge_dt_fit)(_bin_aps[_corr_aps_cond]))
234
+
235
+ _df_merge = DataFrame(_merge_lst, columns=_std_bin, index=_merge_idx)
236
+ _df_merge = _df_merge.mask(_df_merge < 0)
237
+
238
+ _df_corr = DataFrame(_corr_lst, columns=_corr_aps_ky, index=_merge_idx) / _aps_ori.loc[_merge_idx, _corr_aps_ky]
239
+
240
+ ## process output df
241
+ ## average, align with index
242
+ def _out_df(*_df_arg, **_df_kwarg):
243
+ _df = DataFrame(*_df_arg, **_df_kwarg).reindex(_ori_idx)
244
+ _df.index.name = 'time'
245
+ return _df
246
+
247
+ return _out_df(_df_merge), _out_df(_shift_ori ** 2), _out_df(_df_corr)
248
+
249
+
250
+ def _fitness_func(psd, rho, pm25):
251
+ psd_pm25 = psd[psd.keys()[psd.keys().values <= 2500]] * np.diff(np.log10(psd.keys())).mean()
252
+ rho_pm25 = pm25 / (psd_pm25 * np.pi * psd_pm25.keys().values ** 3 / 6 * 1e-9).sum(axis=1, min_count=1)
253
+
254
+ return (rho['density'] - rho_pm25) ** 2
255
+
256
+
257
+ def merge_SMPS_APS(df_smps, df_aps, df_pm25, aps_unit='um', smps_overlap_lowbound=500, aps_fit_highbound=1000,
258
+ dndsdv_alg=True, times_range=(0.8, 1.25, .05)):
259
+ # merge_data, merge_data_dn, merge_data_dsdv, merge_data_cor_dn, density, density_dn, density_dsdv, density_cor_dn = [DataFrame([np.nan])] * 8
260
+
261
+ ## set to the same units
262
+ smps, aps = df_smps.copy(), df_aps.copy()
263
+ smps.columns = smps.keys().to_numpy(float)
264
+ aps.columns = aps.keys().to_numpy(float)
265
+
266
+ if aps_unit == 'um':
267
+ aps.columns = aps.keys() * 1e3
268
+
269
+ fitness_typ = dict(dn=[], cor_dn=[], dndsdv=[], cor_dndsdv=[])
270
+ shift_typ = dict(dn=[], cor_dn=[], dndsdv=[], cor_dndsdv=[])
271
+ oth_typ = dict()
272
+
273
+ times_ary = np.arange(*times_range).round(4)
274
+ # times_ary = np.arange(*(0.8, 0.9, .05)).round(4)
275
+
276
+ for times in times_ary:
277
+
278
+ print(f"\t\t{dtm.now().strftime('%m/%d %X')} : \033[92mSMPS times value : {times}\033[0m")
279
+
280
+ aps_input = aps.copy()
281
+ aps_over = aps_input.loc[:, (aps.keys() > 700) & (aps.keys() < 1000)].copy()
282
+
283
+ smps_input = (smps * times).copy()
284
+ smps_over = smps_input[smps.keys()[smps.keys() > 500]].copy()
285
+
286
+ for _count in range(2):
287
+
288
+ ## shift data calculate
289
+ ## original
290
+ if _count == 0:
291
+ alg_type = 'dn'
292
+ shift = _powerlaw_fit_dN(smps_over, aps_over, alg_type)
293
+
294
+ if dndsdv_alg:
295
+ shift_dsdv = _corr_with_dNdSdV(smps_over, aps_over, 'dndsdv').mask(shift.isna())
296
+
297
+ ## aps correct
298
+ else:
299
+ alg_type = 'cor_dndsdv'
300
+ shift_cor = _powerlaw_fit_dN(smps_over, aps_over, 'cor_dn')
301
+
302
+ if dndsdv_alg:
303
+ shift = _corr_with_dNdSdV(smps_over, aps_over, alg_type).mask(shift_cor.isna())
304
+
305
+ ## merge aps and smps
306
+ ## 1. power law fit (dn) -> return dn data and aps correct factor
307
+ ## 2. correaltion with dn, ds, dv -> return corrected dn_ds_dv data
308
+ if (alg_type == 'dn') | dndsdv_alg:
309
+ merge_arg = (smps_input, aps_input, shift, smps_overlap_lowbound, aps_fit_highbound)
310
+
311
+ merge_data, density, _corr = _merge_data(*merge_arg, 'mobility', _alg_type=alg_type)
312
+ density.columns = ['density']
313
+
314
+ fitness_typ[alg_type].append(_fitness_func(merge_data, density, df_pm25))
315
+ shift_typ[alg_type].append(shift[0])
316
+
317
+ ## without aps correct
318
+ if _count == 0:
319
+ ## merge aps and smps
320
+ ## dn_ds_dv data
321
+ if dndsdv_alg:
322
+ alg_type = 'dndsdv'
323
+ merge_arg = (smps_input, aps_input, shift_dsdv, smps_overlap_lowbound, aps_fit_highbound)
324
+
325
+ merge_data_dsdv, density_dsdv, _ = _merge_data(*merge_arg, 'mobility', _alg_type=alg_type)
326
+ density_dsdv.columns = ['density']
327
+
328
+ fitness_typ[alg_type].append(_fitness_func(merge_data_dsdv, density_dsdv, df_pm25))
329
+ shift_typ[alg_type].append(shift_dsdv[0])
330
+
331
+ ## dn data
332
+ merge_data_dn, density_dn = merge_data.copy(), density.copy()
333
+
334
+ ## correct aps data
335
+ corr = _corr.resample('1d').mean().reindex(smps.index).ffill()
336
+ corr = corr.mask(corr < 1, 1)
337
+
338
+ aps_input.loc[:, corr.keys()] *= corr
339
+ aps_over = aps_input.copy()
340
+
341
+
342
+ ## with aps correct
343
+ else:
344
+ ## merge aps and smps
345
+ ## dn data
346
+ alg_type = 'cor_dn'
347
+ merge_arg = (smps_input, aps_input, shift_cor, smps_overlap_lowbound, aps_fit_highbound)
348
+
349
+ merge_data_cor_dn, density_cor_dn, _ = _merge_data(*merge_arg, 'mobility', _alg_type=alg_type)
350
+ density_cor_dn.columns = ['density']
351
+
352
+ fitness_typ[alg_type].append(_fitness_func(merge_data_cor_dn, density_cor_dn, df_pm25))
353
+ shift_typ[alg_type].append(shift_cor[0])
354
+
355
+ ## get times value and shift value
356
+ out_dic = {}
357
+ for (_typ, _lst), (_typ, _shft) in zip(fitness_typ.items(), shift_typ.items()):
358
+ oth_typ[_typ] = None
359
+ if len(_lst) == 0: continue
360
+
361
+ df_times_min = concat(_lst, axis=1, keys=range(len(_lst))).idxmin(axis=1).dropna().astype(int)
362
+ df_shift = concat(_shft, axis=1, keys=times_ary.tolist()).loc[df_times_min.index].values[
363
+ range(len(df_times_min.index)), df_times_min.values]
364
+
365
+ oth_typ[_typ] = DataFrame(np.array([df_shift, times_ary[df_times_min.values]]).T,
366
+ index=df_times_min.index, columns=['shift', 'times']).reindex(smps.index)
367
+
368
+ ## re-calculate merge_data
369
+ alg_type = ['dn', 'cor_dn', 'dndsdv', 'cor_dndsdv'] if dndsdv_alg else ['dn', 'cor_dn']
370
+
371
+ out_dic = {}
372
+ den_lst, times_lst = [], []
373
+ for _typ in alg_type:
374
+ print(f"\t\t{dtm.now().strftime('%m/%d %X')} : \033[92mre-caculate merge data with times: {_typ}\033[0m")
375
+ typ = oth_typ[_typ]
376
+ smps_input = smps.copy() * typ['times'].to_frame().values
377
+
378
+ corr_typ = corr if 'cor' in _typ else 1
379
+ aps_input = aps.copy()
380
+ aps_input.loc[:, corr.keys()] *= corr_typ
381
+
382
+ merge_arg = (smps_input, aps_input, typ['shift'].to_frame(), smps_overlap_lowbound, aps_fit_highbound)
383
+
384
+ merge_data, density, _corr = _merge_data(*merge_arg, 'mobility', _alg_type=_typ)
385
+ density.columns = ['density']
386
+
387
+ out_dic[f'data_{_typ}'] = merge_data
388
+
389
+ den_lst.append(density)
390
+ times_lst.append(typ['times'])
391
+
392
+ out_rho = concat(den_lst, axis=1)
393
+ out_times = concat(times_lst, axis=1)
394
+ out_rho.columns = alg_type
395
+ out_times.columns = alg_type
396
+
397
+ # breakpoint()
398
+
399
+ ## out
400
+ out_dic.update(dict(density=out_rho, times=out_times))
401
+
402
+ # out_dic = {
403
+ # 'data_cor_dndsdv' : merge_data,
404
+ # 'data_dn' : merge_data_dn,
405
+ # 'data_dndsdv' : merge_data_dsdv,
406
+ # 'data_cor_dn' : merge_data_cor_dn,
407
+
408
+ # 'density' : out_rho,
409
+
410
+ # 'data_all_aer' : merge_data_aer,
411
+
412
+ # 'density_cor_dndsdv' : density,
413
+ # 'density_dn' : density_dn,
414
+ # 'density_dndsdv' : density_dsdv,
415
+ # 'density_cor_dn' : density_cor_dn,
416
+ # }
417
+
418
+ ## process data
419
+ for _nam, _df in out_dic.items():
420
+ out_dic[_nam] = _df.reindex(smps.index).copy()
421
+
422
+ return out_dic
@@ -0,0 +1,62 @@
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()}
@@ -0,0 +1,14 @@
1
+ from ..core import Writer, run_process
2
+
3
+ __all__ = ['VOC']
4
+
5
+
6
+ class VOC(Writer):
7
+
8
+ @run_process('VOC - basic', 'voc_basic')
9
+ def VOC_basic(self, _df_voc):
10
+ from ._potential_par import _basic
11
+
12
+ out = _basic(_df_voc)
13
+
14
+ return self, out
@@ -0,0 +1,108 @@
1
+ from pathlib import Path
2
+
3
+ from pandas import DataFrame, read_json, concat
4
+
5
+
6
+ def _basic(_df_voc):
7
+ with (Path(__file__).parent / 'support_voc.json').open('r', encoding='utf-8', errors='ignore') as f:
8
+ _par = read_json(f)
9
+
10
+ # parameter
11
+ _keys = _df_voc.keys()
12
+
13
+ invalid_keys = [key for key in _df_voc.keys() if key not in set(_par.keys())]
14
+
15
+ if invalid_keys:
16
+ raise KeyError(f'\n\t\t{invalid_keys} are not supported keys.'
17
+ f'\n\t\tPlease check the\033[91m AeroViz/docs/instruments/voc.md\033[0m file to use the correct name.')
18
+
19
+ _MW, _MIR, _SOAP, _KOH = _par.loc['MW', :], _par.loc['MIR', :], _par.loc['SOAP', :], _par.loc['KOH', :]
20
+
21
+ _voc_classify = {
22
+ 'alkane_total': ['Ethane', 'Propane', 'Isobutane', 'n-Butane', 'Isopentane', 'n-Pentane', 'n-Hexane',
23
+ 'n-Heptane', 'n-Octane', 'n-Nonane', 'n-Decane', 'n-Undecane', 'n-Dodecane',
24
+
25
+ 'Cyclopentane', 'Methylcyclopentane', 'Cyclohexane', 'Methylcyclohexane',
26
+
27
+ '2,2-Dimethylbutane', '2,3-Dimethylbutane', '2-Methylpentane', '3-Methylpentane',
28
+ '2,4-Dimethylpentane', '2-Methylhexane', '3-Methylhexane',
29
+ '2,2,4-Trimethylpentane', '2,3,4-Trimethylpentane', '2-Methylheptane', '3-Methylheptane'],
30
+
31
+ 'alkene_total': ['Ethylene', 'Propylene', '1-Butene', 't-2-Butene', 'cis-2-Butene', '1-Pentene', 't-2-Pentene',
32
+ 'cis-2-Pentene', '1-Hexene', 'Isoprene', '1.3-Butadiene', '1-Octene'],
33
+
34
+ 'aromatic_total': ['Benzene', 'Toluene', 'Ethylbenzene', 'm/p-Xylene', 'o-Xylene', 'Styrene',
35
+ 'Isopropylbenzene',
36
+ 'n-Propylbenzene', 'm-Ethyltoluene', 'p-Ethyltoluene', 'o-Ethyltoluene', 'm-Diethylbenzene',
37
+ 'p-Diethylbenzene', '1,2,4-Trimethylbenzene', '1,2,3-Trimethylbenzene',
38
+ '1,3,5-Trimethylbenzene', ],
39
+
40
+ 'alkyne_total': ['Acetylene'],
41
+
42
+ 'OVOC': ['Acetaldehyde', 'Ethanol', 'Acetone', 'IPA', 'Ethyl Acetate', 'Butyl Acetate'],
43
+
44
+ 'ClVOC': ['VCM', 'TCE', 'PCE', '1.4-DCB', '1.2-DCB'],
45
+ }
46
+
47
+ _df_MW = (_df_voc * _MW).copy()
48
+ _df_dic = {
49
+ 'Conc': _df_voc.copy(),
50
+ 'OFP': _df_MW / 48 * _MIR,
51
+ 'SOAP': _df_MW / 24.5 * _SOAP / 100 * 0.054,
52
+ 'LOH': _df_MW / 24.5 / _MW * 0.602 * _KOH,
53
+ }
54
+
55
+ # calculate
56
+ _out = {}
57
+ for _nam, _df in _df_dic.items():
58
+
59
+ _df_out = DataFrame(index=_df_voc.index)
60
+
61
+ for _voc_nam, _voc_lst in _voc_classify.items():
62
+ _lst = list(set(_keys) & set(_voc_lst))
63
+ if len(_lst) == 0:
64
+ continue
65
+
66
+ _df_out = concat([_df[_lst], _df_out], axis=1)
67
+
68
+ _df_out[_voc_nam] = _df[_lst].sum(axis=1, min_count=1)
69
+
70
+ _df_out['Total'] = _df.sum(axis=1, min_count=1)
71
+
72
+ _out[_nam] = _df_out
73
+
74
+ return _out
75
+
76
+
77
+ def markdown_table_to_dataframe():
78
+ import pandas as pd
79
+ from pathlib import Path
80
+
81
+ # support_voc.md
82
+ with open(Path(__file__).parent / 'support_voc.md', 'r', encoding='utf-8') as file:
83
+ markdown_content = file.read()
84
+
85
+ # 將內容分割成行
86
+ lines = markdown_content.strip().split('\n')
87
+
88
+ # 提取表頭
89
+ headers = [col.strip() for col in lines[0].split('|')[1:-1]]
90
+
91
+ # 解析數據行
92
+ data = []
93
+ for line in lines[2:]: # 跳過表頭和分隔行
94
+ columns = [col.strip() for col in line.split('|')[1:-1]]
95
+ data.append(columns)
96
+
97
+ # 創建 DataFrame
98
+ df = pd.DataFrame(data, columns=headers)
99
+
100
+ # 轉換數據類型
101
+ numeric_columns = ['MIR', 'MW', 'SOAP', 'KOH']
102
+ for col in numeric_columns:
103
+ df[col] = pd.to_numeric(df[col], errors='coerce')
104
+ df = df.set_index('Species').T
105
+
106
+ df = df.iloc[:, :-7]
107
+
108
+ df.to_json(Path(__file__).parent / 'support_voc.json', indent=4)