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,271 @@
1
+ # AeroViz DataProcess Module
2
+
3
+ 數據處理模組,提供氣膠數據的計算與分析功能。
4
+
5
+ ## 模組結構
6
+
7
+ ```
8
+ dataProcess/
9
+ ├── __init__.py # DataProcess 工廠函數
10
+ ├── core/ # 共用工具 (Writer, run_process, validate_inputs)
11
+ ├── Chemistry/ # 化學成分處理
12
+ ├── Optical/ # 光學特性處理
13
+ ├── SizeDistr/ # 粒徑分布處理
14
+ └── VOC/ # 揮發性有機物處理
15
+ ```
16
+
17
+ ## 快速開始
18
+
19
+ ```python
20
+ from pathlib import Path
21
+ from AeroViz.dataProcess import DataProcess
22
+
23
+ # 建立處理器
24
+ dp = DataProcess(method='SizeDistr', path_out=Path('./output'))
25
+
26
+ # 或直接導入類
27
+ from AeroViz.dataProcess.SizeDistr import SizeDist
28
+ ```
29
+
30
+ ---
31
+
32
+ ## SizeDistr 模組
33
+
34
+ 粒徑分布數據處理。
35
+
36
+ ### 結構
37
+ ```
38
+ SizeDistr/
39
+ ├── __init__.py # SizeDistr (Writer 入口)
40
+ ├── _size_dist.py # SizeDist 核心類
41
+ ├── prop.py # 統計計算函數
42
+ └── merge/ # SMPS-APS 合併演算法 (v0-v4)
43
+ ```
44
+
45
+ ### SizeDist 類
46
+
47
+ ```python
48
+ from AeroViz.dataProcess.SizeDistr import SizeDist
49
+
50
+ # 建立物件
51
+ psd = SizeDist(df_pnsd, state='dlogdp', weighting='n')
52
+
53
+ # 屬性
54
+ psd.data # DataFrame - 原始數據
55
+ psd.dp # ndarray - 粒徑陣列 (nm)
56
+ psd.dlogdp # ndarray - 對數間距
57
+ psd.index # DatetimeIndex - 時間索引
58
+
59
+ # 分布轉換
60
+ psd.to_surface() # 表面積分布 (dS/dlogDp)
61
+ psd.to_volume() # 體積分布 (dV/dlogDp)
62
+ psd.to_extinction(df_RI) # 消光分布 (Mie 理論)
63
+ psd.to_dry(df_gRH) # 乾燥分布 (吸濕校正)
64
+
65
+ # 統計計算
66
+ psd.properties() # GMD, GSD, mode, contribution
67
+ psd.mode_statistics() # 各模態統計 (Nucleation/Aitken/Accumulation/Coarse)
68
+
69
+ # 應用
70
+ psd.lung_deposition() # ICRP 66 肺沉積模型
71
+ ```
72
+
73
+ ### SizeDistr 入口方法
74
+
75
+ ```python
76
+ dp = DataProcess('SizeDistr', Path('./output'))
77
+
78
+ # 基本處理
79
+ dp.basic(df)
80
+
81
+ # SMPS-APS 合併
82
+ dp.merge_SMPS_APS(df_smps, df_aps) # v1
83
+ dp.merge_SMPS_APS_v2(df_smps, df_aps) # v2
84
+ dp.merge_SMPS_APS_v3(df_smps, df_aps) # v3 (multiprocessing)
85
+ dp.merge_SMPS_APS_v4(df_smps, df_aps, df_pm25) # v4 (PM2.5 校正)
86
+
87
+ # 分布計算
88
+ dp.distributions(df_pnsd)
89
+ dp.dry_psd(df_pnsd, df_gRH)
90
+ dp.extinction_distribution(df_pnsd, df_RI)
91
+ dp.extinction_full(df_pnsd, df_RI)
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Chemistry 模組
97
+
98
+ 化學成分數據處理。
99
+
100
+ ### 結構
101
+ ```
102
+ Chemistry/
103
+ ├── __init__.py # Chemistry (Writer 入口)
104
+ ├── _mass_volume.py # 質量重建、體積計算、折射率
105
+ ├── _calculate.py # 衍生參數、氣粒分配比
106
+ ├── _ocec.py # OC/EC 比值計算
107
+ └── _isoropia.py # ISORROPIA 熱力學模型
108
+ ```
109
+
110
+ ### Chemistry 入口方法
111
+
112
+ ```python
113
+ dp = DataProcess('Chemistry', Path('./output'))
114
+
115
+ # 質量重建
116
+ dp.reconstruction_basic(df_chem) # 基本重建 (AS, AN, OM, Soil, SS, EC)
117
+ dp.reconstruction_full(df_chem) # 完整重建 (含 ite,ite_ox)
118
+
119
+ # 體積與折射率
120
+ dp.volume_RI(df_chem) # 體積分率 + 折射率計算
121
+ dp.kappa(df_chem, df_RH) # κ-Köhler 吸濕參數
122
+
123
+ # 衍生參數
124
+ dp.derived(df_chem) # 衍生化學參數
125
+ dp.partition_ratios(df_chem) # 氣粒分配比 (SOR, NOR, NTR, epsilon)
126
+
127
+ # OC/EC
128
+ dp.ocec_ratio(df_ocec) # OC/EC 比值分析
129
+
130
+ # ISORROPIA
131
+ dp.ISORROPIA(df_chem) # 熱力學平衡計算
132
+ ```
133
+
134
+ ### 輸出欄位說明
135
+
136
+ **質量重建:**
137
+ | 欄位 | 說明 |
138
+ |------|------|
139
+ | AS | 硫酸銨 (μg/m³) |
140
+ | AN | 硝酸銨 (μg/m³) |
141
+ | OM | 有機物 (μg/m³) |
142
+ | Soil | 土壤塵 (μg/m³) |
143
+ | SS | 海鹽 (μg/m³) |
144
+ | EC | 元素碳 (μg/m³) |
145
+ | PM25_rc | 重建 PM2.5 (μg/m³) |
146
+
147
+ **氣粒分配比:**
148
+ | 欄位 | 說明 |
149
+ |------|------|
150
+ | SOR | 硫氧化比 SO₄²⁻/(SO₄²⁻+SO₂) |
151
+ | NOR | 氮氧化比 NO₃⁻/(NO₃⁻+NO₂) |
152
+ | epsilon_ite | 硝酸鹽分配係數 |
153
+
154
+ ---
155
+
156
+ ## Optical 模組
157
+
158
+ 光學特性數據處理。
159
+
160
+ ### 結構
161
+ ```
162
+ Optical/
163
+ ├── __init__.py # Optical (Writer 入口)
164
+ ├── _IMPROVE.py # IMPROVE 消光方程
165
+ ├── _mie.py # Mie 理論計算
166
+ ├── _retrieve_RI.py # 折射率反演
167
+ ├── _derived.py # 衍生光學參數
168
+ ├── mie_theory.py # Mie 混合模式
169
+ └── coefficient.py # 散射/吸收係數
170
+ ```
171
+
172
+ ### Optical 入口方法
173
+
174
+ ```python
175
+ dp = DataProcess('Optical', Path('./output'))
176
+
177
+ # 消光計算
178
+ dp.basic(df_sca, df_abs) # 基本消光特性
179
+ dp.IMPROVE(df_mass, df_RH) # IMPROVE 方程 (revised/modified)
180
+ dp.gas_extinction(df_no2, df_temp) # 氣體消光貢獻
181
+
182
+ # Mie 計算
183
+ dp.Mie(df_psd, df_m) # Mie 理論消光
184
+
185
+ # 折射率反演
186
+ dp.retrieve_RI(df_optical, df_pnsd) # 從光學+PSD 反演折射率
187
+
188
+ # 衍生參數
189
+ dp.derived(df_sca, df_abs, ...) # PG, MAC, Ox, 能見度等
190
+ ```
191
+
192
+ ### IMPROVE 輸出
193
+
194
+ ```python
195
+ result = dp.IMPROVE(df_mass, df_RH, method='revised')
196
+
197
+ result['dry'] # 乾燥消光 DataFrame
198
+ result['wet'] # 濕消光 DataFrame (含 ALWC)
199
+ result['ALWC'] # 液態水貢獻
200
+ result['fRH'] # 吸濕成長因子
201
+ ```
202
+
203
+ ---
204
+
205
+ ## VOC 模組
206
+
207
+ 揮發性有機物數據處理。
208
+
209
+ ### VOC 入口方法
210
+
211
+ ```python
212
+ dp = DataProcess('VOC', Path('./output'))
213
+
214
+ dp.potential(df_voc) # 臭氧生成潛勢 (OFP, SOAP)
215
+ ```
216
+
217
+ ---
218
+
219
+ ## 共用功能
220
+
221
+ ### Writer 基類
222
+
223
+ 所有處理模組繼承自 `Writer`,提供:
224
+ - 自動輸出 CSV/Excel
225
+ - 處理進度顯示
226
+ - 錯誤處理
227
+
228
+ ### run_process 裝飾器
229
+
230
+ ```python
231
+ @run_process('處理名稱', '輸出檔名')
232
+ def method(self, ...):
233
+ ...
234
+ return self, output_data
235
+ ```
236
+
237
+ ### validate_inputs 驗證器
238
+
239
+ ```python
240
+ from AeroViz.dataProcess.core import validate_inputs
241
+
242
+ validate_inputs(df, ['SO42-', 'NO3-'], 'Chemistry', COLUMN_DESCRIPTIONS)
243
+ ```
244
+
245
+ ---
246
+
247
+ ## 資料格式要求
248
+
249
+ ### 通用格式
250
+ - **Index**: `DatetimeIndex` (時間索引)
251
+ - **Columns**: 依模組需求
252
+
253
+ ### SizeDist 格式
254
+ ```python
255
+ # 欄位為粒徑值 (nm)
256
+ df.columns = [11.8, 13.6, 15.7, ..., 523.3]
257
+ ```
258
+
259
+ ### Chemistry 格式
260
+ ```python
261
+ # 化學成分欄位
262
+ required = ['SO42-', 'NO3-', 'Cl-', 'Na+', 'NH4+', 'K+', 'Mg2+', 'Ca2+',
263
+ 'OC', 'EC', 'Al', 'Fe', 'Ti', 'PM25']
264
+ ```
265
+
266
+ ### Optical 格式
267
+ ```python
268
+ # 散射/吸收係數
269
+ df_sca.columns = ['G_550', 'R_700', 'B_450'] # Mm⁻¹
270
+ df_abs.columns = ['Abs_880'] # Mm⁻¹
271
+ ```
@@ -0,0 +1,245 @@
1
+ from ..core import Writer, run_process
2
+
3
+ from ._size_dist import SizeDist
4
+
5
+ __all__ = ['SizeDistr', 'SizeDist']
6
+
7
+
8
+ class SizeDistr(Writer):
9
+
10
+ # basic
11
+ @run_process('SizeDistr - basic', 'distr_basic')
12
+ def basic(self, df, hybrid_bin_start_loc=None, unit='nm', bin_range=(0, 20000), input_type='norm'):
13
+ """
14
+ Process particle size distribution data.
15
+
16
+ Parameters
17
+ ----------
18
+ df : DataFrame
19
+ Raw particle size distribution data.
20
+ hybrid_bin_start_loc : int, optional
21
+ Column index where bin spacing changes (for hybrid instruments).
22
+ unit : {'nm', 'um'}, default='nm'
23
+ Unit of particle diameter.
24
+ bin_range : tuple, default=(0, 20000)
25
+ Size range to include (min, max).
26
+ input_type : {'norm', 'raw'}, default='norm'
27
+ Whether input is normalized (dN/dlogDp) or raw (dN).
28
+
29
+ Returns
30
+ -------
31
+ dict
32
+ Distributions and statistics for each size mode.
33
+ """
34
+ import numpy as np
35
+
36
+ # Prepare data
37
+ data = df.copy()
38
+ data.columns = data.keys().to_numpy(float)
39
+
40
+ # Filter by size range
41
+ cols = data.keys()[(data.keys() >= bin_range[0]) & (data.keys() <= bin_range[-1])]
42
+ data = data[cols].copy()
43
+
44
+ dp = data.keys().to_numpy()
45
+
46
+ # Calculate dlogdp
47
+ if hybrid_bin_start_loc is None:
48
+ dlog_dp = np.full(dp.size, np.diff(np.log10(dp)).mean())
49
+ else:
50
+ dlog_dp = np.ones(dp.size)
51
+ dlog_dp[:hybrid_bin_start_loc] = np.diff(np.log10(dp[:hybrid_bin_start_loc])).mean()
52
+ dlog_dp[hybrid_bin_start_loc:] = np.diff(np.log10(dp[hybrid_bin_start_loc:])).mean()
53
+
54
+ # Handle normalization
55
+ if input_type == 'norm':
56
+ data_norm = data
57
+ else:
58
+ data_norm = data / dlog_dp
59
+
60
+ # Create SizeDist and calculate
61
+ psd = SizeDist(data_norm, state='dlogdp', weighting='n')
62
+ psd.dlogdp = dlog_dp
63
+
64
+ out = psd.mode_statistics(unit=unit)
65
+
66
+ # Rename for backward compatibility
67
+ out['other'] = out.pop('statistics')
68
+
69
+ return self, out
70
+
71
+ # merge
72
+ @run_process('SizeDistr - merge_SMPS_APS_v4', 'distr_merge')
73
+ def merge_SMPS_APS_v4(self, df_smps, df_aps, df_pm25, aps_unit='um',
74
+ smps_overlap_lowbound=500, aps_fit_highbound=1000, dndsdv_alg=True,
75
+ times_range=(0.8, 1.25, .05)):
76
+ from .merge import merge_v4
77
+
78
+ out = merge_v4(df_smps, df_aps, df_pm25, aps_unit, smps_overlap_lowbound, aps_fit_highbound, dndsdv_alg,
79
+ times_range)
80
+
81
+ return self, out
82
+
83
+ # merge
84
+ @run_process('SizeDistr - merge_SMPS_APS_v3', 'distr_merge')
85
+ def merge_SMPS_APS_v3(self, df_smps, df_aps, aps_unit='um',
86
+ smps_overlap_lowbound=500, aps_fit_highbound=1000, dndsdv_alg=True):
87
+ from .merge import merge_v3
88
+
89
+ out = merge_v3(df_smps, df_aps, aps_unit, smps_overlap_lowbound, aps_fit_highbound, dndsdv_alg)
90
+
91
+ return self, out
92
+
93
+ # merge
94
+ @run_process('SizeDistr - merge_SMPS_APS_v2', 'distr_merge')
95
+ def merge_SMPS_APS_v2(self, df_smps, df_aps, aps_unit='um',
96
+ smps_overlap_lowbound=500, aps_fit_highbound=1000):
97
+ from .merge import merge_v2
98
+
99
+ out = merge_v2(df_smps, df_aps, aps_unit, smps_overlap_lowbound, aps_fit_highbound)
100
+
101
+ return self, out
102
+
103
+ # merge
104
+ @run_process('SizeDistr - merge_SMPS_APS_v1', 'distr_merge')
105
+ def merge_SMPS_APS(self, df_smps, df_aps, aps_unit='um', shift_mode='mobility',
106
+ smps_overlap_lowbound=523, aps_fit_highbound=800):
107
+ from .merge import merge_v1
108
+
109
+ out = merge_v1(df_smps, df_aps, aps_unit, shift_mode, smps_overlap_lowbound, aps_fit_highbound)
110
+
111
+ return self, out
112
+
113
+ # Distribution calculations
114
+ @run_process('SizeDistr - distributions', 'distr_calc')
115
+ def distributions(self, df_pnsd):
116
+ """
117
+ Calculate number, surface, and volume distributions with properties.
118
+
119
+ Parameters
120
+ ----------
121
+ df_pnsd : DataFrame
122
+ Particle number size distribution data.
123
+
124
+ Returns
125
+ -------
126
+ dict
127
+ Dictionary with 'number', 'surface', 'volume', and 'properties' DataFrames.
128
+ """
129
+ from pandas import concat
130
+
131
+ psd = SizeDist(df_pnsd, weighting='n')
132
+
133
+ number = psd.data
134
+ surface = psd.to_surface()
135
+ volume = psd.to_volume()
136
+
137
+ # Calculate properties for each distribution type
138
+ props_n = psd.properties()
139
+ props_s = SizeDist(surface, weighting='s').properties()
140
+ props_v = SizeDist(volume, weighting='v').properties()
141
+
142
+ out = {
143
+ 'number': number,
144
+ 'surface': surface,
145
+ 'volume': volume,
146
+ 'properties': concat([props_n, props_s, props_v], axis=1)
147
+ }
148
+
149
+ return self, out
150
+
151
+ # Dry PSD
152
+ @run_process('SizeDistr - dry_psd', 'distr_dry')
153
+ def dry_psd(self, df_pnsd, df_gRH, uniform=True):
154
+ """
155
+ Convert ambient PSD to dry PSD.
156
+
157
+ Parameters
158
+ ----------
159
+ df_pnsd : DataFrame
160
+ Particle number size distribution data.
161
+ df_gRH : DataFrame
162
+ DataFrame with 'gRH' column (growth factor).
163
+ uniform : bool, default=True
164
+ Whether to apply uniform growth factor.
165
+
166
+ Returns
167
+ -------
168
+ DataFrame
169
+ Dry particle size distribution.
170
+ """
171
+ psd = SizeDist(df_pnsd)
172
+ out = psd.to_dry(df_gRH, uniform=uniform)
173
+
174
+ return self, out
175
+
176
+ # Extinction distribution
177
+ @run_process('SizeDistr - extinction', 'distr_ext')
178
+ def extinction_distribution(self, df_pnsd, df_RI, method='internal', result_type='extinction'):
179
+ """
180
+ Calculate extinction distribution using Mie theory.
181
+
182
+ Parameters
183
+ ----------
184
+ df_pnsd : DataFrame
185
+ Particle number size distribution (dN/dlogDp).
186
+ df_RI : DataFrame
187
+ Refractive index data (n, k columns).
188
+ method : {'internal', 'external', 'core_shell', 'sensitivity'}, default='internal'
189
+ Mixing method for Mie calculation.
190
+ result_type : {'extinction', 'scattering', 'absorption'}, default='extinction'
191
+ Type of optical property.
192
+
193
+ Returns
194
+ -------
195
+ dict
196
+ - 'distribution': Extinction size distribution (Mm⁻¹)
197
+ - 'properties': Statistical properties (GMD, GSD, mode)
198
+ """
199
+ psd = SizeDist(df_pnsd)
200
+ ext_dist = psd.to_extinction(df_RI, method=method, result_type=result_type)
201
+ ext_props = SizeDist(ext_dist, weighting=f'ext_{method[:2]}').properties()
202
+
203
+ out = {
204
+ 'distribution': ext_dist,
205
+ 'properties': ext_props
206
+ }
207
+
208
+ return self, out
209
+
210
+ # Full extinction analysis (internal + external)
211
+ @run_process('SizeDistr - extinction_full', 'distr_ext_full')
212
+ def extinction_full(self, df_pnsd, df_RI, result_type='extinction'):
213
+ """
214
+ Calculate extinction using both internal and external mixing.
215
+
216
+ Parameters
217
+ ----------
218
+ df_pnsd : DataFrame
219
+ Particle number size distribution (dN/dlogDp).
220
+ df_RI : DataFrame
221
+ Refractive index data with volume ratios.
222
+ result_type : {'extinction', 'scattering', 'absorption'}, default='extinction'
223
+ Type of optical property.
224
+
225
+ Returns
226
+ -------
227
+ dict
228
+ - 'internal': Internal mixing distribution
229
+ - 'external': External mixing distribution
230
+ - 'properties_internal': Internal properties
231
+ - 'properties_external': External properties
232
+ """
233
+ psd = SizeDist(df_pnsd)
234
+
235
+ ext_internal = psd.to_extinction(df_RI, method='internal', result_type=result_type)
236
+ ext_external = psd.to_extinction(df_RI, method='external', result_type=result_type)
237
+
238
+ out = {
239
+ 'internal': ext_internal,
240
+ 'external': ext_external,
241
+ 'properties_internal': SizeDist(ext_internal, weighting='ext_in').properties(),
242
+ 'properties_external': SizeDist(ext_external, weighting='ext_ex').properties()
243
+ }
244
+
245
+ return self, out