Repository: uncscode/particula

Issue:
come up with unit checks for parcel class
validating parcel methods and linting

Comments:

Patch:
diff --git a/.gitignore b/.gitignore
index 8cd503d..edef6da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
 # ignore local .vscode folder
 .vscode/
 
+# ignore local development folder
+private_dev/
+
 # Byte-compiled / optimized / DLL files
 __pycache__/
 *.py[cod]
diff --git a/particula/aerosol_dynamics/parcel.py b/particula/aerosol_dynamics/parcel.py
index 803b541..fa7b3a7 100644
--- a/particula/aerosol_dynamics/parcel.py
+++ b/particula/aerosol_dynamics/parcel.py
@@ -1,45 +1,34 @@
 """
 Class for creating a air parcel to put particles and gases in.
 """
-
-from particula.aerosol_dynamics import environment, particle
+import numpy as np
+from particula.aerosol_dynamics import particle, u
 
 
 class Parcel:
-    """Sets the class for creating a parcel.
+    """
+    Creates an air parcel to track and interact with a list of particle objects.
     This starts the base for particle and gas dynamic simulations.
-    Like the eveolution of a size distribution with time.
-    Or change in temperature causing particle evaporation or gas condensation.
+    For example, eveolution of a size distribution with time.
 
     Attributes:
-        name            (str)           The name of the parcel.
-        particle_data   (list)          The radius of the particle.
-        enviroment      (Environment)   The enviroment of the parcel.
-        TODO: after enviromnetal class is added to main.
-        temperature (float) default: The air temperature in Kelvin.
-        pressure (float) default: The air pressure in atm?.
-        add gases class
+        name            (str)               The name of the parcel.
+        particle_data   (particle list)     Particle objects.
+        enviroment      (object)            Parcel's enviroment.
     """
 
-    def __init__(self, name: str, temperature=300, pressure=101325):
+    def __init__(self, name: str, parcel_environment):
         """
         Constructs the parcel object.
 
         Parameters:
-            name            (str)   The name of the particle.
-            particle_data   (list)  The radius of the particle.
-            temperature     (float) The air temperature in Kelvin.
-                                    default = 300 K
-            pressure        (float) The air pressure in Pascals.
-                                    default = 101325 Pa
+            name           (str),
+            particle_data  (object numpy array),
+            enviroment     (object).
         """
         self._name = name
-        self._particle_data = []
-
-        self._enviroment = environment.Environment(
-            temperature,
-            pressure,
-        )
+        self._particle_data = np.array([])
+        self._enviroment = parcel_environment
 
     def name(self) -> str:
         """Returns the name of the parcel."""
@@ -48,62 +37,107 @@ class Parcel:
     def add_particle(self, particle_object):
         """Adds a particle to the parcel.
         Parameters:
-            particle_data (list): The radius of the particle.
+            particle_object (object)
         """
-        self._particle_data.append(particle_object)
+        self._particle_data = np.append(self._particle_data, particle_object)
 
     def create_and_add_particle(
-        self, name: str, radius, density=1e3, charge=0
+        self, name: str,
+        radius,
+        density=1e3 * u.kg / u.m**3,
+        charge=0 * u.dimensionless
     ):
-        """creats and then Adds a particle to the parcel.
+        """Create and then add a particle to the parcel.
+
         Parameters:
-            particle_data (list): The radius of the particle.
+            name    (str)   [no units],
+            radius  (float) [m]
+        Optional:
+            density (float) [kg/m**3]       default = 1e3 [kg/m**3],
+            charge  (int)   [dimensionless] default = 0 [dimensionless]
         """
-        self._particle_data.append(
+
+        try:  # maybe move to particle class
+            radius.check('m')
+        except AttributeError:
+            print('Please add units to radius. E.g., radius = 150 * u.nm]')
+
+        self.add_particle(
             particle.Particle(name, radius, density, charge)
         )
 
     def create_and_add_list_of_particle(
-        self, name_of_particles, radius_of_particles
+        self,
+        name_of_particles,
+        radi_of_particles,
+        densities_of_particles=None,
+        charges_of_particles=None
     ):
-        """Adds a list of particles to the parcel based on size only.
+        """Adds a list of particles to the parcel based on size only (simple).
+            or delcaring denisties and charges (detailed).
         Parameters:
-            name_of_particles (list): The name of the particles.
-            radius_of_particles (list): The radius of the particle.
-            TODO: add charge and mass as passible parameters.
+            name_of_particles   (str)   [no units],
+            radi_of_particles (list)  [m]
+        Optional:
+            densities_of_particles (list) [kg/m**3],
+            charges_of_particles  (list) [dimensionless]
+        Examples:
+            simple: parcel.create_and_add_list_of_particle(
+                'org2',
+                [1e-9, 2e-9, 3e-9] * u.m
+                )
+            detailed: create_and_add_list_of_particle(
+                'org3',
+                [1e-9, 2e-9, 3e-9] * u.m,
+                [1.8, 1, 1] * u.kg / u.m ** 3,
+                [1, 0, 2] * u.dimensionless)
         """
-        for size_add in radius_of_particles:
-            self.create_and_add_particle(name_of_particles, size_add)
-
-    def remove_particle(self, particle_data):
+        if densities_of_particles is None:
+            for size_add in radi_of_particles:
+                self.create_and_add_particle(name_of_particles, size_add)
+        else:
+            for size_add, density_add, charge_add in zip(
+                radi_of_particles,
+                densities_of_particles,
+                charges_of_particles
+            ):
+                self.create_and_add_particle(
+                    name_of_particles, size_add, density_add, charge_add
+                )
+
+    def remove_particle(self, particle_index):
         """Removes a particle from the parcel.
         Parameters:
-            particle_data (list): The radius of the particle.
+            particle_index (list): int or array of ints
         """
-        self._particle_data.remove(particle_data)
+        self._particle_data = np.delete(self._particle_data, particle_index)
 
     def remove_all_particles(self):
         """Removes all particles from the parcel."""
-        self._particle_data = []
+        self._particle_data = np.array([])
 
-    def particle_classes(self) -> list:
+    def particle_classes_list(self) -> list:
         """Returns the particle data of the parcel."""
         return self._particle_data
 
-    def particle_mass(self) -> float:
+    def particle_masses_list(self) -> float:
         """Returns the mass of the particle. Checks units. [kg]"""
-        return [i.mass() for i in self.particle_classes()]
+        return [i.mass() for i in self.particle_classes_list()]
 
-    def particle_radius(self) -> float:
+    def particle_radi_list(self) -> float:
         """Returns list of radi of particles"""
-        return [i.radius() for i in self.particle_classes()]
+        return [i.radius() for i in self.particle_classes_list()]
+
+    def particle_densities_list(self) -> float:
+        """Returns list of densities of particles."""
+        return [i.density() for i in self.particle_classes_list()]
 
-    def particle_charge(self) -> float:
+    def particle_charges_list(self) -> float:
         """Returns list of charges of particles."""
-        return [i.charge() for i in self.particle_classes()]
+        return [i.charge() for i in self.particle_classes_list()]
 
-    def knudsen_number_particle(self) -> float:
+    def particle_knudsen_numbers_list(self) -> float:
         """Returns list of knudsen numbers of particles."""
         return [
-            i.knudsen_number(self._enviroment) for i in self.particle_classes()
+            i.knudsen_number(self._enviroment) for i in self.particle_classes_list()
         ]


Test patch:
diff --git a/particula/aerosol_dynamics/parcel_test.py b/particula/aerosol_dynamics/parcel_test.py
index 45222d6..a57080e 100644
--- a/particula/aerosol_dynamics/parcel_test.py
+++ b/particula/aerosol_dynamics/parcel_test.py
@@ -2,7 +2,7 @@
 Test suites for parcel class.
 """
 
-from particula.aerosol_dynamics import parcel, particle
+from particula.aerosol_dynamics import parcel, particle, environment, u
 
 small_particle = particle.Particle(
     name="small_particle",
@@ -18,21 +18,96 @@ large_particle = particle.Particle(
     charge=1,
 )
 
+standard_environment = environment.Environment(
+    temperature=298 * u.K,
+    pressure=101325 * u.Pa,
+)
+
 # create a parcel
-simple_parcel = parcel.Parcel('simple', temperature=300, pressure=101325)
+simple_parcel = parcel.Parcel('simple', standard_environment)
 
 # add single particle to parcel
 simple_parcel.add_particle(small_particle)
 simple_parcel.add_particle(large_particle)
-simple_parcel.create_and_add_particle('org1', 50e-9)
+simple_parcel.create_and_add_particle('org1', 500*u.nm)
 # add multiple particles to parcel
-simple_parcel.create_and_add_list_of_particle('org2', [1e-9, 2e-9, 3e-9])
+simple_parcel.create_and_add_list_of_particle('org2', [1e-9, 2e-9, 3e-9] * u.m)
+simple_parcel.create_and_add_list_of_particle(
+    'org2', [1e-9, 2e-9, 3e-9] * u.m,
+    [1.8, 1, 1] * u.kg / u.m ** 3, [1, 0, 2] * u.dimensionless
+)
 
 
 def test_getters():
     """
     Test that the getters work by confirming particle creation
     """
-    assert len(simple_parcel.particle_classes()) == 6
-    assert len(simple_parcel.particle_mass()) == 6
-    assert len(simple_parcel.particle_radius()) == 6
+    assert len(simple_parcel.particle_classes_list()) == 9
+    assert len(simple_parcel.particle_masses_list()) == 9
+    assert len(simple_parcel.particle_radi_list()) == 9
+    assert len(simple_parcel.particle_charges_list()) == 9
+
+
+def test_particle_mass_units():
+    '''
+    Test that the mass of the particles is returned in kg
+    '''
+    assert sum(
+        [i.mass().check('kg')
+            for i in simple_parcel.particle_classes_list()]
+    ) == 9
+
+
+def test_particle_radius_units():
+    '''
+    Test that the radius of the particles is returned in m
+    '''
+    assert sum([i.check('m') for i in simple_parcel.particle_radi_list()]) == 9
+
+
+def test_particle_density_units():
+    '''
+    Test that the density of the particles is returned in kg/m^3
+    '''
+    assert sum(
+        [i.check(u.kg / u.m ** 3)
+            for i in simple_parcel.particle_densities_list()]
+    ) == 9
+
+
+def test_particle_charge_units():
+    '''
+    Test that the charge of the particles is returned in dimensionless
+    '''
+    assert sum(
+        [i.check(u.dimensionless)
+            for i in simple_parcel.particle_charges_list()]
+    ) == 9
+
+
+def test_particle_knudsen_number():
+    '''
+    Test that the knudsen number is returned in dimensionless
+    '''
+    assert sum(
+        [i.check(u.dimensionless)
+            for i in simple_parcel.particle_knudsen_numbers_list()]
+    ) == 9
+
+
+def test_remove_particle():
+    '''
+    Test that the remove particle method works
+    '''
+    simple_parcel.remove_particle([0])
+    assert len(simple_parcel.particle_classes_list()) == 8
+    simple_parcel.remove_particle([2, 4])
+    assert len(simple_parcel.particle_classes_list()) == 6
+
+
+def test_remove_all_particles():
+    '''
+    Test that the remove all particles method works
+    '''
+    simple_parcel.remove_all_particles()
+    assert len(simple_parcel.particle_classes_list()) == 0