From fdc30aeb368c91a470afbf0a4b57809c18e889c0 Mon Sep 17 00:00:00 2001 From: Tschan Date: Wed, 8 Jul 2026 22:10:31 +0200 Subject: [PATCH] Debugging the OFRAnalyser, it is now completely functional. Remaining to-do's: Check transformations, implement median, mode, mean and variance methods of Distribution class, implement sample number list check for flow file. The two to-do's in the GetData method of the OFRAnalyser can be neglected as they are already fulfilled. --- .vscode/settings.json | 2 +- defs/computing.py | 270 ++++++------------ main.py | 8 +- .../202607023_OFR_Analysis_01.ipynb | 17 +- 4 files changed, 107 insertions(+), 190 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index cd19d6f..c6bde6a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -52,7 +52,7 @@ "editor.suggestOnTriggerCharacters": false, "window.zoomLevel": -1, "editor.wordWrap": "on", - "editor.rulers": [140], + "editor.rulers": [145], "notebook.editorOptionsCustomizations": {"editor.rulers":[]}, "editor.fontSize": 16, "jupiter.notebookFileRoot": "${workspaceFolder}/" diff --git a/defs/computing.py b/defs/computing.py index 16dfcb5..1474bd0 100644 --- a/defs/computing.py +++ b/defs/computing.py @@ -24,30 +24,47 @@ class Distribution(): warnings.warn("Already in desired format. Transformation has no effect.") return - # TODO: check the transformations! + # TODO: check the transformations! + # TODO: check the order of the transformation (first variable, then function, or vice versa) to avoid having a mess! match to: case 'linear': if self.__form == 'ln': self.var = np.exp(self.var) self.fct = np.divide(self.fct, self.var) + self.__form = 'linear' elif self.__form == 'log10': self.var = np.power(10, self.var) self.fct = np.divide(self.fct, self.var * np.log(10)) + self.__form = 'linear' case 'ln': if self.__form == 'linear': self.var = np.log(self.var) self.fct = np.multiply(self.var, self.fct) + self.__form = 'ln' elif self.__form == 'log10': self.var = np.log(np.power(10, self.var)) self.fct = np.divide(self.fct, np.log(10)) + self.__form = 'ln' case 'log10': if self.__form == 'linear': - self.var = np.log10(self.var) self.fct = np.multiply(self.var, self.fct * np.log(10)) + self.var = np.log10(self.var) + self.__form = 'log10' elif self.__form == 'ln': self.var = np.log10(np.exp(self.var)) self.fct = np.multiply(self.fct, np.log(10)) + self.__form = 'log10' + + def update_units(self, new_var_units, new_fct_units): + + old_from = self.__form + self.transform(to='linear') + + self.var = self.var * new_var_units + self.fct = self.fct * new_fct_units + + self.transform(to=old_from) def integrate(self) -> np.float64: return np.trapezoid(y = self.fct, x = self.var) @@ -154,6 +171,12 @@ class OFRSample: else: continue + self.aerosol_flow = self.aerosol_flow * 6E-4 + self.SOA_density = self.SOA_density * 1E3 + self.lower_diameter_bound = self.lower_diameter_bound * 1E-9 + self.upper_diameter_bound = self.upper_diameter_bound * 1E-9 + + # Input data from file is in (nm) return [np.array(midpoints), np.array(counts)] @@ -178,21 +201,21 @@ class OFRSample: # Distributions of the relevant quantities (all relative to nanometer) data = self.__get_data() - self.Count = Distribution(variable_array = np.log10(data[0]), - function_array = data[1], - form='log10') - self.Length = Distribution(variable_array = np.log10(data[0]), - function_array = data[0] * data[1], - form = 'log10') - self.Surface = Distribution(variable_array = np.log(data[0]), - function_array = ((math.pi) * data[0] ** 2) * data[1], - form = 'log10') - self.Volume = Distribution(variable_array = np.log10(data[0]), - function_array = ((math.pi / 6) * data[0] ** 3) * data[1], - form = 'log10') - self.Mass = Distribution(variable_array = np.log10(data[0]), - function_array = (self.SOA_density * (math.pi / 6) * data[0] ** 3) * data[1], - form='log10') + + # Getting Distribution objects + self.Count = Distribution(variable_array = np.log10(data[0]), function_array = data[1], form='log10') + self.Count.update_units(new_fct_units=1E15, new_var_units=1E-9) + + self.Length = Distribution(variable_array = np.log10(data[0]), function_array = data[0]*data[1], form = 'log10') + self.Length.update_units(new_fct_units=1E6, new_var_units=1E-9) + + self.Surface = Distribution(variable_array = np.log(data[0]), function_array = ((math.pi)* data[0] ** 2) * data[1], form = 'log10') + self.Surface.update_units(new_fct_units=1E-3, new_var_units=1E-9) + + self.Volume = Distribution(variable_array = np.log10(data[0]),function_array = ((math.pi / 6) * data[0] ** 3) * data[1], form = 'log10') + self.Volume.update_units(new_fct_units=1E-12, new_var_units=1E-9) + + self.Mass = Distribution(variable_array = self.Volume.var, function_array = self.Volume.fct * self.SOA_density, form='log10') class OFRAnalyser: @@ -225,48 +248,53 @@ class OFRAnalyser: def GetData(self) -> None: + # TODO make this work also for a specific sample! + # TODO convert everything is SI units, making live a lot easier... + self.__sample_number_list_check(self.sample_number_list) - numbers_of_sample = [] - number_concentrations = [] - length_concentrations = [] - surface_concentrations = [] - volume_concentrations = [] - aerosol_densities = [] - mass_concentrations = [] - starting_times = [] - stopping_times = [] - sampling_times = [] - dry_air_flows = [] - species_flows = [] - ozone_flows = [] - total_flows = None - cpc_flows = [] + numbers_of_sample = [] # (-) + number_concentrations = [] # (#/m^3) + length_concentrations = [] # (m/m^3) + surface_concentrations = [] # (m^2/m^3) + volume_concentrations = [] # (m^3/m^3) + aerosol_densities = [] # (kg/m^3) + mass_concentrations = [] # (kg/m^3) + starting_times = [] # (s) + stopping_times = [] # (s) + sampling_times = [] # (s) + dry_air_flows = [] # (m^3/s) + species_flows = [] # (m^3/s) + ozone_flows = [] # (m^3/s) + total_flows = None # (m^3/s) + cpc_flows = [] # (m^3/s) + mass_production_rate = None # (kg/s) + mass_produced = None # (kg) + cumulative_mass_produced = None # (kg) + for sample_number in self.sample_number_list: sample = OFRSample(source_file=self.data_source_file, sample_number = sample_number, data_separator = self.__data_separator) numbers_of_sample.append(sample_number) number_concentrations.append(sample.Count.integrate()) - length_concentrations.append(sample.Length.integrate() / 1E6) - surface_concentrations.append(sample.Surface.integrate() / 1E12) - volume_concentrations.append(sample.Volume.integrate() / 1E9) + length_concentrations.append(sample.Length.integrate()) + surface_concentrations.append(sample.Surface.integrate()) + volume_concentrations.append(sample.Volume.integrate()) aerosol_densities.append(sample.SOA_density) - mass_concentrations.append(sample.Mass.integrate() / 1E12) + mass_concentrations.append(sample.Mass.integrate()) - if sample_number == 1: - starting_times.append(0.00) - stopping_times.append(sample.sampling_time) - - elif sample_number >= 1: - starting_times.append(stopping_times[-1]) - stopping_times.append(stopping_times[-1] + sample.sampling_time) + starting_times.append((sample_number - 1) * sample.sampling_time) + stopping_times.append(sample_number * sample.sampling_time) sampling_times.append(sample.sampling_time) out_flag = False with open(self.flows_source_file, 'r', errors='ignore') as file: + + j = 1 + for line_counter, line in enumerate(file.readlines()): if out_flag == True: @@ -283,156 +311,42 @@ class OFRAnalyser: while i < int(items[-1]): - dry_air_flows.append(float(items[0])) - species_flows.append(float(items[1])) - ozone_flows.append(float(items[2])) - cpc_flows.append(float(items[3])) + if j in self.sample_number_list: + dry_air_flows.append(float(items[0]) * 6E-4) + species_flows.append(float(items[1]) * 6E-4) + ozone_flows.append(float(items[2]) * 6E-4) + cpc_flows.append(float(items[3]) * 6E-4) i += 1 + j += 1 else: continue - total_flows = [(flow_1 + flow_2 + flow_3) * 1E3 for flow_1, flow_2, flow_3 in zip(dry_air_flows, species_flows, ozone_flows)] + total_flows = [(flow_1 + flow_2 + flow_3) for flow_1, flow_2, flow_3 in zip(dry_air_flows, species_flows, ozone_flows)] mass_production_rate = [(mass * flow) for mass, flow in zip(mass_concentrations, total_flows)] mass_produced = [(mpr * duration) for mpr, duration in zip(mass_production_rate, sampling_times)] cumulative_mass_produced = np.cumsum(np.array(mass_produced)).tolist() self.data = pd.DataFrame({'Sample (-)': numbers_of_sample, - 'Number (#/cm^3)': number_concentrations, - 'Length (mm/cm^3)': length_concentrations, - 'Surface (mm^2/cm^3)': surface_concentrations, - 'Volume (μm^3/cm^3)': volume_concentrations, - 'Density (pg/μm^3)': aerosol_densities, - 'Mass (mg/cm^3)': mass_concentrations, + 'Number (#/m^3)': number_concentrations, + 'Length (m/m^3)': length_concentrations, + 'Surface (m^2/m^3)': surface_concentrations, + 'Volume (m^3/m^3)': volume_concentrations, + 'Density (kg/m^3)': aerosol_densities, + 'Mass (kg/m^3)': mass_concentrations, 'Start (s)': starting_times, 'Stop (s)': stopping_times, 'Duration (s)': sampling_times, - 'Dry Air Flow (L/min)': dry_air_flows, - 'Species Flow (L/min)': species_flows, - 'Ozone Flow (L/min)': ozone_flows, - 'Total Flow (cm^3/s)': total_flows, - 'CPC Flow (L/min)': cpc_flows, - 'Mass Production Rate (mg/s)': mass_production_rate, - 'Mass Produced (mg)': mass_produced, - 'Cumulative Mass Produced (mg)': cumulative_mass_produced}) + 'Dry Air Flow (m^3/s)': dry_air_flows, + 'Species Flow (m^3/s)': species_flows, + 'Ozone Flow (m^3/s)': ozone_flows, + 'Total Flow (m^3/s)': total_flows, + 'CPC Flow (m^3/s)': cpc_flows, + 'Mass Production Rate (kg/s)': mass_production_rate, + 'Mass Produced (kg)': mass_produced, + 'Cumulative Mass Produced (kg)': cumulative_mass_produced}) - def GetPlottableData(self, which_data = 'Mass'): - - times = [] - output_list = [] - - match which_data: - case 'Number': - key = 'Number (#/cm^3)' - case 'Length': - key = 'Length (mm/cm^3)' - case 'Surface': - key = 'Surface (mm^2/cm^3)' - case 'Volume': - key = 'Volume (μm^3/cm^3)' - case 'Mass': - key = 'Mass (mg/cm^3)' - - times.append(0.00) - output_list.append(0.00) - - for i, row in self.data.iterrows(): - - times.append(row['Time (s)']) - times.append(times[-1]) - - output_list.append(output_list[-1]) - output_list.append(row[key]) - - if i == self.max_samples - 1: - - times.append(row['Time (s)']) - times.append(times[-1]) - - output_list.append(output_list[-1]) - output_list.append(0.00) - - - return times, output_list - - - def GetFlowData(self, flow_source_file): - - # For a step diagram - - self.flow_source_file = flow_source_file - - dry_air_flows = [] - species_flows = [] - ozone_flows = [] - cnc_flows = [] - starts = [] - stops = [] - smpls = [] - - out_flag = False - - with open(self.flow_source_file, 'r', errors='ignore') as file: - i = 0 - for line_counter, line in enumerate(file.readlines()): - if out_flag == True: - continue - if line_counter == 6: - items = line.split() - dry_air_flows.append(float(items[0])) - species_flows.append(float(items[1])) - ozone_flows.append(float(items[2])) - cnc_flows.append(float(items[-1])) - starts.append(0) - stops.append(float(items[3]) * float(items[4])) - smpls.append(float(items[4])) - i += 1 - elif line_counter > 6 and not line.startswith('-'): - items = line.split() - dry_air_flows.append(float(items[0])) - species_flows.append(float(items[1])) - ozone_flows.append(float(items[2])) - cnc_flows.append(float(items[-1])) - starts.append(stops[i-1]) - stops.append(stops[i-1] + float(items[3]) * float(items[4])) - smpls.append(float(items[4])) - i += 1 - if line_counter > 6 and line.startswith('-'): - out_flag = True - else: - continue - - self.flow_data = pd.DataFrame({'Dry Air (L/min)': dry_air_flows, 'Species (L/min)': species_flows, 'Ozone (L/min)': ozone_flows, 'Start (s)': starts, 'Stop (s)': stops, 'CNC Flow (L/min)': cnc_flows, 'Samples': smpls}) - - def GetPlottableFlowData(self, which_flow_rate='Species'): - - times = [] - output_list = [] - - match which_flow_rate: - case 'Species': - key = 'Species (L/min)' - case 'Ozone': - key = 'Ozone (L/min)' - case 'Dry Air': - key = 'Dry Air (L/min)' - - for _, row in self.flow_data.iterrows(): - times.append(row['Start (s)']) - times.append(row['Stop (s)']) - - # Now append relevant data twice so that the data can be plotted correctly - i = 0 - while i < 2: - output_list.append(row[key]) - i += 1 - - return times, output_list - - def GetMassProductionRates(self): - - pass ### ----------------------------------------------------------------------------------------------------------------------------------- ### ### \ No newline at end of file diff --git a/main.py b/main.py index 39aade7..fe3d528 100644 --- a/main.py +++ b/main.py @@ -1,17 +1,13 @@ # Imports -#%% from defs.computing import OFRSample, OFRAnalyser import numpy as np from pathlib import Path - # Path to the source file source_file = Path(r"C:\Users\tschan_d\Documents\data_local\20260612_SET02_EXP01_full.txt") flows_source_file = Path(r"C:\Users\tschan_d\Documents\data_local\20260612_SET02_EXP01_full_flows.txt") -analyser = OFRAnalyser(data_source_file=source_file, flows_source_file=flows_source_file, sample_number_list=range(1, 157)) +# Code to debug +analyser = OFRAnalyser(data_source_file=source_file, flows_source_file=flows_source_file, sample_number_list=[33, 35]) analyser.GetData() print(analyser.data) - - -# %% diff --git a/run/OFR_Analysis/202607023_OFR_Analysis_01.ipynb b/run/OFR_Analysis/202607023_OFR_Analysis_01.ipynb index ff229b2..cac766b 100644 --- a/run/OFR_Analysis/202607023_OFR_Analysis_01.ipynb +++ b/run/OFR_Analysis/202607023_OFR_Analysis_01.ipynb @@ -10,7 +10,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "8769766b", "metadata": {}, "outputs": [], @@ -85,16 +85,15 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "8db19b57", "metadata": {}, "outputs": [], "source": [ "sample_number_list = [int(i) for i in range(1, 157)]\n", "\n", - "analyser = OFRAnalyser(data_source_file = source_file, flows_source_file = flows_source_file, sample_number_list = sample_number_list)\n", + "analyser = OFRAnalyser(data_source_file = source_file, flows_source_file = flows_source_file, sample_number_list = [33, 35, 37])\n", "analyser.GetData()\n", - "\n", "analyser.data.to_csv(path_or_buf=r\"C:\\Users\\tschan_d\\Documents\\data_local\\data.csv\", index=False)" ] }, @@ -129,7 +128,7 @@ "\n", "ax.set_xlim([1, 156])\n", "ax.set_ylim([0, 1.2E6])\n", - "ax_2.set_ylim([0, 15])\n", + "#ax_2.set_ylim([0, 15])\n", "\n", "lines_1, labels_1 = ax.get_legend_handles_labels()\n", "lines_2, labels_2 = ax_2.get_legend_handles_labels()\n", @@ -220,6 +219,14 @@ "\n", "plt.savefig(\"20260723_OFR_Analysis_01_Experiment_Overview_bottom.pdf\", format='pdf', dpi=400)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c6110f8c", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": {