diff --git a/.vscode/launch.json b/.vscode/launch.json index b79e7ae..805f644 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,14 +9,6 @@ "program": "${file}", "console": "integratedTerminal", "env": {"PYTHONPATH": "${workspaceFolder}/"} - }, - { - "name": "Python Debugger: debug_scrapyard.py", - "type": "debugpy", - "request": "launch", - "program": "${workspaceFolder}/defs/debug_scrapyard.py", - "console": "integratedTerminal", - "env": {"PYTHONPATH": "${workspaceFolder}/"} } ] } \ No newline at end of file diff --git a/README.md b/README.md index 02d446d..71f78e1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,37 @@ -# Welcome! +# Not so fast!!! -Hello! This is the read-me file with some important information for the code project. +Hello! This is the read-me file with information for the code project. Please take some time to read it before going on, as it contains detailed description of code and methods used. So don't be hasty, and have a good look! + +# Code Documentation +## Code Structure: +This code project is structured as follows: +``` +Master_Thesis ++-- .git ++-- .vscode ++-- defs + +-- __init__.py + +-- defs.py ++-- vis + +-- __init__.py + +-- defs.py ++-- .env ++-- .gitignore ++-- .enviornment.yml ++-- main.py ++-- README.md +``` +You are currently reading `README.md`, where the structure of this oroject is explained. You're in the right spot to start, then! + +### Auxilliary directories `.git` and `.vscode` +1. `.git` contains all files related to `git`. These files are usually not interesting, unless you need to go back to old commits or want to know more details. Best leave this alone. For a detailed description of how `git` (and with it the `.git` directory) works, see [`git` description](##Git). +2. `.vscode` contains all files relevant for environment setup to run this project in [Visual Studio Code](https://code.visualstudio.com/). These files are: + 1. `settings.json`: the setup file to set up VS Code in the desired configuration. Feel free to change these as you like. For example, the setting `"python.defaultInterpreterPath": "C:/Users/tschan_d/AppData/Local/miniforge3/envs/MA/python"` needs to be changed to point to the path of the Python interpreter you select. + 2. `launch.json`: the launch configuration file for the project, in which it is specified how VSCode should behave when you launch a job: that is, when you want to run a file or debug it. Again, feel free to customise these settings. +Important here is that +3. `defs` contains source code for data analysis: + 1. `__init__.py` is a file to structure the project like a package. This file is empty, but it is needed such that relative imports work properly. +4. `vis` contains source code for data visualisation. # Coding help ## Conda diff --git a/defs/defs.py b/defs/defs.py index c6bb731..3b71fad 100644 --- a/defs/defs.py +++ b/defs/defs.py @@ -12,27 +12,80 @@ import matplotlib.pyplot as plt class OFRSample: - def __init__(self, number, sourcefile, headlines, density, lower_bound, upper_bound, scan_up, scan_down): + """Object to represent a single sample from the OFR - self.number = number - self.sourcefile = sourcefile - self.headlines = headlines + Description: Bla - self.lower_bound = lower_bound - self.upper_bound = upper_bound + Attributes + ---------- + number: int + The sample number + sourcefile: str + The path to the data file + headlines: int + The number of lines in the data file until raw data is reached + lower_bound: float + The lower bound diameter (nm) of the SMPS + upper_bound: float + The upper bound diameter (nm) of the SMPS + scan_up: float + The time (s) to ramp up voltage in SMPS + scan_down: float + The time (s) to ramp voltage back down in SMPS + delta_time: float + The time interval (s) between two measured data points of the raw count density + SOA_density: float + The density (kg/m3) of the SOA produced in the OFR + + Methods + ------- + __init__(number, sourcefile, headlines, density, lower_bound, upper_bound, scan_up, scan_down, delta_time) -> OFRSample: + The constructor of the Object + """ - self.scan_up = scan_up - self.scan_down = scan_down + def __init__(self, number, sourcefile, headlines, density, lower_bound, upper_bound, scan_up, scan_down, delta_time = 0.1): + + """Constructor of a OFRSample object + + Description: - # in g/cc - self.density = density + Parameters + ---------- + number: int + The sample number + + Returns + ------- + None: None + Does not have a return value + """ - self.diameters = None - self.counts = None + # Sample information + self.number = number # sample number, i.e. 33 + self.sourcefile = sourcefile # where the data is stored, "C:/Users/tschan_d/..." + self.headlines = headlines # Number of lines before raw data is reached, usually 238 + self.lower_bound = lower_bound # Lower Diameter (nm) where CPC works + self.upper_bound = upper_bound # Upper Diameter (nm) where CPC works + self.scan_up = scan_up # Time to scan up the voltage range in DMA + self.scan_down = scan_down # Time to ramp the voltage back down in DMA + self.delta_time = delta_time # Measuring interval to get raw count density; (s) + self.SOA_density = density # Expected density of SOA (kg/m3) - self.__get_data() + # Sample distributions + self.diameters = None # Diameters (nm) + self.count_density = None # Count Density (#/(m3*m*s)) + self.surface_density = None # Surface Density (m2/(m3*m*s)) + self.volume_density = None # Volume Density (m3/(m3*m*s)) + self.mass_density = None # Mass Density (kg/(m3*m*s)) - def __get_data(self): + self.total_count = None # Total Count (#/(m3*s)) + self.total_surface = None # Total Surface (m2/(m3*s)) + self.total_volume = None # Total Volume (m3/(m3*s)) + self.total_mass = None # Total Mass (kg/(m3*s)) + + self.get_data() + + def get_data(self): columns = ['Diameter #' + str(self.number), 'Counts #' + str(self.number)] sample_data = pd.read_csv(filepath_or_buffer=self.sourcefile, header=self.headlines, usecols=columns, index_col=False, @@ -40,31 +93,41 @@ class OFRSample: filter_array = np.where((sample_data[:, 0] >= self.lower_bound) & (sample_data[:, 0] <= self.upper_bound), True, False) - # Particle Diameter: (nm) - self.diameters = sample_data[filter_array, 0] + # Particle Diameter: Raw data in (nm) -> Output data in (m): 1 nm = 1E-9 m + self.diameters = sample_data[filter_array, 0] * 1E-9 - # Particle raw counts: (# / cc) - self.counts = sample_data[filter_array, 1] + # Particle Count Density: Raw data in (#/(cm3*nm*0.1s)) -> Output data in (#/(m3*m*s)): 1/(cm3*nm*s) = 1E16/(m3*m*s) + self.count_density = sample_data[filter_array, 1] * 1E16 - def volume_integrator(self, input_prefix = 1E-9, output_prefix = 1E-2) -> np.float64: + self.surface_density = np.multiply(math.pi * self.diameters ** 2, self.count_density) + self.volume_density = np.multiply((math.pi / 6) * self.diameters ** 3, self.count_density) + self.mass_density = np.multiply(self.SOA_density, self.volume_density) - adjustment_factor = input_prefix / output_prefix + self.total_count = self.__integrator(integrand = self.count_density) + self.total_surface = self.__integrator(integrand = self.surface_density) + self.total_volume = self.__integrator(integrand = self.volume_density) + self.total_mass = self.total_volume * self.SOA_density - # - local_volumes = (math.pi / 6) * (self.diameters * adjustment_factor) ** 3 - local_volume = np.trapezoid(y=self.counts, x=local_volumes) - return local_volume + def __integrator(self, integrand: np.float64) -> np.float64: + + return np.trapezoid(y = integrand, x = self.diameters) class OFRAnalyser: - def __init__(self, sourcefile, headlines = 238, density=1.2, lower_bound = 0, upper_bound = 1896, scan_up=160, scan_down=20) -> None: + def __init__(self, sourcefile, headlines = 238, density=1.2, lower_bound = 0, upper_bound = 1896, scan_up=160, scan_down=20, + flow_rate=5) -> None: + + # where to take data from: self.sourcefile = sourcefile + # how many lines to skip until the raw counts data section is reached: self.headlines = headlines + # assumed SOA density self.density = density self.lower_bound = lower_bound self.upper_bound = upper_bound self.scan_up = scan_up self.scan_down = scan_down + self.flow_rate = flow_rate with open(self.sourcefile) as file: file_reader = csv.reader(file) @@ -77,6 +140,9 @@ class OFRAnalyser: self.max_samples = int((len(head_row) - 1) / 2) # total mass in mg + self.total_counts = None + self.total_surface = None + self.total_volume = None self.total_mass = None def __sample_number_list_check(self, sample_number_list) -> None: @@ -86,35 +152,29 @@ class OFRAnalyser: if last_sample > self.max_samples: raise IndexError("The last sample(s) are out of range of the number of recorded samples") - def GetTotalMass(self, samples = [1], plot = False) -> None: + def AggregateSamples(self, samples = [1]) -> None: self.__sample_number_list_check(samples) + + counts = [] + surfaces = [] + volumes = [] masses = [] - for sample in samples: - local_sample = OFRSample(number=sample, sourcefile=self.sourcefile, headlines=self.headlines, density=self.density, + for samplenumber in samples: + sample = OFRSample(number=samplenumber, sourcefile=self.sourcefile, headlines=self.headlines, density=self.density, lower_bound = self.lower_bound, upper_bound = self.upper_bound, scan_up = self.scan_up, scan_down = self.scan_down) - - # For python notebooks and debugging reasons - if plot == True: - fig = plt.figure() - ax = fig.add_subplot(111) - ax.plot(local_sample.diameters, local_sample.counts) - ax.set_xscale('log') - plt.show() - local_volume = local_sample.volume_integrator() - masses.append(local_volume * local_sample.density) + counts.append(sample.total_count) + surfaces.append(sample.total_surface) + volumes.append(sample.total_volume) + masses.append(sample.total_mass) - total_mass = sum(masses) - self.total_mass = total_mass - - def PlotSampleDistribution(self, sample) -> None: - pass - - def PlotAllDistributions(self): - pass + self.total_count = sum(counts) + self.total_surface = sum(surfaces) + self.total_volume = sum(volumes) + self.total_mass = sum(masses) ### ----------------------------------------------------------------------------------------------------------------------------------- ### ### \ No newline at end of file diff --git a/main.py b/main.py index 799bf23..a3c93c2 100644 --- a/main.py +++ b/main.py @@ -1,12 +1,16 @@ +#%% from defs.defs import OFRAnalyser -from vis.defs import HelloWorld import numpy as np - +#%% analyser = OFRAnalyser(sourcefile="Z:/People/David_Tschan/data/OFR_SMPS/2026/processed/06_June/20260612/20260612_SET02_EXP01_full.csv", - lower_bound=22.87, upper_bound=1000, density=102, scan_up=160, scan_down=20) + lower_bound=22.87, upper_bound=1000, density=1200, scan_up=160, scan_down=20) -samplelist = np.linspace(start=1, stop=analyser.max_samples, num=analyser.max_samples, dtype=int).tolist() +#samplelist = np.linspace(start=1, stop=analyser.max_samples, num=analyser.max_samples, dtype=int).tolist() -analyser.GetTotalMass(samples=[33], plot=True) -print(analyser.total_mass) +#%% +analyser.AggregateSamples(samples=[33]) + +#%% +print(analyser.total_surface) +# %%