From 456013e2f8da9c905b620c36d428c23ebd1165b0 Mon Sep 17 00:00:00 2001 From: Gavin Macaulay Date: Wed, 21 Aug 2024 17:20:31 +1200 Subject: [PATCH] Change name of model_types parameter (#22) * Add a new page about other modelling software * add more modelling software * add more software * Revert "add more software" This reverts commit 0b9bf6c83da4ee7e85eec15a21ae039dfd2517b4. * Resources dir for docs, better coordinate system figure * Simplify API for BenchmarkData and ReferenceModels * Update to work with API changes * API polishing Move model_type into the dataframe/dataset function parameter Make reference model API more pythonic Update example code to match these changes * Up the version number * Change model_type to boundary_type - is more descriptive * Up the version --- pyproject.toml | 2 +- src/echosms/dcmmodel.py | 16 +++---- src/echosms/mssmodel.py | 27 ++++++------ src/echosms/psmsmodel.py | 22 +++++----- src/echosms/referencemodels.py | 44 +++++++++---------- src/echosms/resources/target definitions.toml | 30 ++++++------- src/echosms/scattermodelbase.py | 22 +++++----- src/example_code.py | 4 +- 8 files changed, 83 insertions(+), 84 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1d95d0a..2000bf3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ packages = ["src/echosms"] [project] name = 'echosms' -version = '0.1.0' +version = '0.1.1' license = {file = "LICENSE"} keywords = ["acoustic", "backscatter", "model"] authors = [ diff --git a/src/echosms/dcmmodel.py b/src/echosms/dcmmodel.py index c1d0ddc..676fb7a 100644 --- a/src/echosms/dcmmodel.py +++ b/src/echosms/dcmmodel.py @@ -20,11 +20,11 @@ def __init__(self): self.long_name = 'deformed cylinder model' self.short_name = 'dcm' self.analytical_type = 'approximate analytical' - self.model_types = ['fixed rigid', 'pressure release', 'fluid filled'] + self.boundary_types = ['fixed rigid', 'pressure release', 'fluid filled'] self.shapes = ['finite cylinder'] self.max_ka = 20 # [1] - def calculate_ts_single(self, medium_c, medium_rho, a, b, theta, f, model_type, + def calculate_ts_single(self, medium_c, medium_rho, a, b, theta, f, boundary_type, target_c=None, target_rho=None, **kwargs): """ Calculate the scatter from a finite cylinder using the modal series deformed cylinder model. @@ -44,14 +44,14 @@ def calculate_ts_single(self, medium_c, medium_rho, a, b, theta, f, model_type, 90 is dorsal, and 180 is tail on. f : float Frequencies to calculate the scattering at [Hz]. - model_type : str - The model type. Supported model types are given in the model_types class attribute. + boundary_type : str + The model type. Supported model types are given in the boundary_types class attribute. target_c : float, optional Sound speed in the fluid inside the sphere [m/s]. - Only required for `model_type` of ``fluid filled``. + Only required for `boundary_type` of ``fluid filled``. target_rho : float, optional Density of the fluid inside the sphere [kg/m³]. - Only required for `model_type` of ``fluid filled``. + Only required for `boundary_type` of ``fluid filled``. Returns ------- @@ -81,7 +81,7 @@ def calculate_ts_single(self, medium_c, medium_rho, a, b, theta, f, model_type, m = range(30) # this needs to vary with f # Some code varies with model type. - match model_type: + match boundary_type: case 'fixed rigid': series = list(map(lambda m: (-1)**m * eta(m)*(jvp(m, Ka) / h1vp(m, Ka)), m)) case 'pressure release': @@ -101,7 +101,7 @@ def Cm(m): series = list(map(lambda m: 1j**(2*m) * eta(m) / (1 + 1j*Cm(m)), m)) case _: raise ValueError(f'The {self.long_name} model does not support ' - f'a model type of "{model_type}".') + f'a model type of "{boundary_type}".') fbs = 1j*b/pi * (sin(kL*cos(theta_rad)) / (kL*cos(theta_rad))) * sum(series) return 20*log10(abs(fbs)) # ts diff --git a/src/echosms/mssmodel.py b/src/echosms/mssmodel.py index 18c3bbe..c7dc2f4 100644 --- a/src/echosms/mssmodel.py +++ b/src/echosms/mssmodel.py @@ -13,7 +13,7 @@ class MSSModel(ScatterModelBase): """Modal series solution (MSS) scattering model. This class calculates acoustic scatter from spheres and shells with various - boundary conditions, as listed in the ``model_types`` class attribute. + boundary conditions, as listed in the ``boundary_types`` class attribute. """ def __init__(self): @@ -21,12 +21,13 @@ def __init__(self): self.long_name = 'modal series solution' self.short_name = 'mss' self.analytical_type = 'exact' - self.model_types = ['fixed rigid', 'pressure release', 'fluid filled', - 'fluid shell fluid interior', 'fluid shell pressure release interior'] + self.boundary_types = ['fixed rigid', 'pressure release', 'fluid filled', + 'fluid shell fluid interior', + 'fluid shell pressure release interior'] self.shapes = ['sphere'] self.max_ka = 20 # [1] - def calculate_ts_single(self, medium_c, medium_rho, a, theta, f, model_type, + def calculate_ts_single(self, medium_c, medium_rho, a, theta, f, boundary_type, target_c=None, target_rho=None, shell_c=None, shell_rho=None, shell_thickness=None, **kwargs) -> float: @@ -46,24 +47,24 @@ def calculate_ts_single(self, medium_c, medium_rho, a, theta, f, model_type, 90 is dorsal, and 180 is tail on. f : float Frequencies to calculate the scattering at [Hz]. - model_type : str - The model type. Supported model types are given in the model_types class variable. + boundary_type : str + The boundary type. Supported types are given in the boundary_types class variable. target_c : float, optional Sound speed in the fluid inside the sphere [m/s]. - Only required for `model_type` of ``fluid filled``. + Only required for `boundary_type` of ``fluid filled``. target_rho : float, optional Density of the fluid inside the sphere [kg/m³]. - Only required for `model_type` of ``fluid filled``. + Only required for `boundary_type` of ``fluid filled``. shell_c : float, optional Sound speed in the spherical shell [m/s]. - Only required for `model_type`s that include a fluid shell. + Only required for `boundary_type`s that include a fluid shell. shell_rho : float, optional Density in the spherical shell [kg/m³]. - Only required for `model_type`s that include a fluid shell. + Only required for `boundary_type`s that include a fluid shell. shell_thickness : float, optional Thickness of the spherical shell [m]. This value is subtracted from ``a`` to give the radius of the interior sphere. - Only required for `model_type`s that include a fluid shell. + Only required for `boundary_type`s that include a fluid shell. Returns ------- @@ -87,7 +88,7 @@ def calculate_ts_single(self, medium_c, medium_rho, a, theta, f, model_type, n = np.arange(0, round(ka+20)) # Some code varies with model type. - match model_type: + match boundary_type: case 'fixed rigid': A = list(map(lambda x: -spherical_jn(x, ka, True) / h1(x, ka, True), n)) case 'pressure release': @@ -141,7 +142,7 @@ def Cn_fspri(n): A = list(map(Cn_fspri, n)) case _: raise ValueError(f'The {self.long_name} model does not support ' - f'a model type of "{model_type}".') + f'a model type of "{boundary_type}".') fbs = -1j/k0 * np.sum((-1)**n * (2*n+1) * A) return 20*log10(abs(fbs)) # ts diff --git a/src/echosms/psmsmodel.py b/src/echosms/psmsmodel.py index 3c60948..5b68323 100644 --- a/src/echosms/psmsmodel.py +++ b/src/echosms/psmsmodel.py @@ -16,11 +16,11 @@ def __init__(self): self.long_name = 'prolate spheroidal modal series' self.short_name = 'psms' self.analytical_type = 'exact' - self.model_types = ['fixed rigid', 'pressure release', 'fluid filled'] + self.boundary_types = ['fixed rigid', 'pressure release', 'fluid filled'] self.shapes = ['prolate spheroid'] self.max_ka = 10 # [1] - def calculate_ts_single(self, medium_c, medium_rho, a, b, theta, f, model_type, + def calculate_ts_single(self, medium_c, medium_rho, a, b, theta, f, boundary_type, target_c=None, target_rho=None): """Prolate spheroid modal series (PSMS) solution model. @@ -39,14 +39,14 @@ def calculate_ts_single(self, medium_c, medium_rho, a, b, theta, f, model_type, 90 is dorsal, and 180 is tail on. f : float Frequencies to calculate the scattering at [Hz]. - model_type : str - The model type. Supported model types are given in the model_types class variable. + boundary_type : str + The model type. Supported model types are given in the boundary_types class variable. target_c : float, optional Sound speed in the fluid inside the target [m/s]. - Only required for `model_type` of ``fluid filled``. + Only required for `boundary_type` of ``fluid filled``. target_rho : float, optional Density of the fluid inside the target [kg/m³]. - Only required for `model_type` of ``fluid filled``. + Only required for `boundary_type` of ``fluid filled``. Returns ------- @@ -64,17 +64,17 @@ def calculate_ts_single(self, medium_c, medium_rho, a, b, theta, f, model_type, “Prediction of krill target strength by liquid prolate spheroid model,” Fish. Sci., 60, 261–265. """ - match model_type: + match boundary_type: case 'pressure release' | 'fluid filled': pass case 'fixed rigid': - raise ValueError(f'Model type "{model_type}" has not yet been implemented ' + raise ValueError(f'Model type "{boundary_type}" has not yet been implemented ' f'for the {self.long_name} model.') case _: raise ValueError(f'The {self.long_name} model does not support ' - f'a model type of "{model_type}".') + f'a model type of "{boundary_type}".') - if model_type == 'fluid filled': + if boundary_type == 'fluid filled': hc = target_c / medium_c rh = target_rho / medium_rho @@ -125,7 +125,7 @@ def calculate_ts_single(self, medium_c, medium_rho, a, b, theta, f, model_type, s_bs = pro_ang1(m, n, h0, np.cos(theta)) s_inc = pro_ang1(m, n, h0, np.cos(np.pi - theta)) - match model_type: + match boundary_type: case 'fluid filled': # r_type1A, dr_type1A, r_type2A, dr_type2A = rswfp(m, n, h0, xi0, 3) # r_type1B, dr_type1B, _, _ = rswfp(m, n, h0/hc, xi0, 3) diff --git a/src/echosms/referencemodels.py b/src/echosms/referencemodels.py index 1b4dbc6..4d61ca4 100644 --- a/src/echosms/referencemodels.py +++ b/src/echosms/referencemodels.py @@ -1,41 +1,39 @@ """Reference model parameters.""" from pathlib import Path -import pandas as pd import tomllib +import pandas as pd pd.options.mode.copy_on_write = True class ReferenceModels: - """Provide access to reference scattering model parameters.""" + """Provide access to reference scattering model parameters. + + Attributes + ---------- + definitions : dict + A dict representation of the ``target definitions.toml`` file. + """ def __init__(self): - self.defsFilename = Path(__file__).parent/Path('resources')/Path('target definitions.toml') + self.defs_filename = Path(__file__).parent/Path('resources')/Path('target definitions.toml') + + self.definitions = [] - with open(self.defsFilename, 'rb') as f: - self.defs = tomllib.load(f) + with open(self.defs_filename, 'rb') as f: + self.definitions = tomllib.load(f) # check that the names parameter is unique across all models # Substitute parameters names in the target section by the values of those # parameters. - for t in self.defs['target']: + for t in self.definitions['target']: for k, v in t.items(): try: - t[k] = self.defs['parameters'][v] + t[k] = self.definitions['parameters'][v] except (KeyError, TypeError): pass - def models(self): - """Provide the full set of model definitions. - - Returns - ------- - : dict - The contents of the ```target definitions.toml`` file in the form of a dict. - """ - return self.defs - def names(self): """Names of all model definitions. @@ -44,7 +42,7 @@ def names(self): : iterable of str All model names in the ``target definitions/toml`` file. """ - return [n['name'] for n in self.defs['target']] + return [n['name'] for n in self.definitions['target']] def specification(self, name): """Model defintions for a particular model. @@ -59,13 +57,13 @@ def specification(self, name): : dict The model definitions for the requested model or ``None`` if no model with that name. """ - models = pd.DataFrame(self.defs['target']) - m = models.query('name == @name') + models = pd.DataFrame(self.definitions['target']) + m = models.loc[models['name'] == name] if len(m) == 1: m.dropna(axis=1, how='all', inplace=True) return m.iloc[0].to_dict() - else: - return None + + return None def parameters(self, name): """Model parameters for a particular model. @@ -87,7 +85,7 @@ def parameters(self, name): s = self.specification(name) if s is None: - return s + return None # Remove the entries that are not parameters p = s diff --git a/src/echosms/resources/target definitions.toml b/src/echosms/resources/target definitions.toml index fa193ff..7717ae3 100644 --- a/src/echosms/resources/target definitions.toml +++ b/src/echosms/resources/target definitions.toml @@ -63,7 +63,7 @@ example3_medium_sound_speed_dist = {type="arange", start=1490, stop=1500, step=1 [[target]] name = "fixed rigid sphere" shape = "sphere" -model_type = "fixed rigid" +boundary_type = "fixed rigid" description = "A fixed rigid sphere" a = "sphere_radius" medium_rho = "medium_density" @@ -73,7 +73,7 @@ source = "https://doi.org/10.1121/1.4937607" [[target]] name = "pressure release sphere" shape = "sphere" -model_type = "pressure release" +boundary_type = "pressure release" description = "A sphere with a pressure release surface" a = "sphere_radius" medium_rho = "medium_density" @@ -83,7 +83,7 @@ source = "https://doi.org/10.1121/1.4937607" [[target]] name = "gas filled sphere" shape = "sphere" -model_type = "fluid filled" +boundary_type = "fluid filled" description = "A sphere with a fluid-filled interior with properties representative of air" a = "sphere_radius" medium_rho = "medium_density" @@ -95,7 +95,7 @@ source = "https://doi.org/10.1121/1.4937607" [[target]] name = "weakly scattering sphere" shape = "sphere" -model_type = "fluid filled" +boundary_type = "fluid filled" description = "A sphere with a fluid-filled interior with similar density and sound speed to the surrounding medium" a = "sphere_radius" medium_rho = "medium_density" @@ -107,7 +107,7 @@ source = "https://doi.org/10.1121/1.4937607" [[target]] name = "spherical fluid shell with pressure release interior" shape = "sphere" -model_type = "fluid shell pressure release interior" +boundary_type = "fluid shell pressure release interior" description = "A fluid spherical shell with pressure release surface" a = "sphere_radius" shell_thickness = "target_shell_thickness" @@ -120,7 +120,7 @@ source = "https://doi.org/10.1121/1.4937607" [[target]] name = "spherical fluid shell with gas interior" shape = "sphere" -model_type = "fluid shell fluid interior" +boundary_type = "fluid shell fluid interior" description = "A fluid spherical shell with gas interior" a = "sphere_radius" shell_thickness = "target_shell_thickness" @@ -135,7 +135,7 @@ source = "https://doi.org/10.1121/1.4937607" [[target]] name = "spherical fluid shell with weakly scattering interior" shape = "sphere" -model_type = "fluid shell fluid interior" +boundary_type = "fluid shell fluid interior" description = "A fluid spherical shell with a weakly scattering shell and interior" a = "sphere_radius" shell_thickness = "target_shell_thickness" @@ -150,7 +150,7 @@ source = "https://doi.org/10.1121/1.4937607" [[target]] name = "fixed rigid prolate spheroid" shape = "prolate spheroid" -model_type = "fixed rigid" +boundary_type = "fixed rigid" description = "A fixed rigid prolate spheroid" a = "prolate_spheroid_major_axis_radius" b = "prolate_spheroid_minor_axis_radius" @@ -161,7 +161,7 @@ source = "https://doi.org/10.1121/1.4937607" [[target]] name = "pressure release prolate spheroid" shape = "prolate spheroid" -model_type = "pressure release" +boundary_type = "pressure release" description = "A prolate spheroid with a pressure release surface" a = "prolate_spheroid_major_axis_radius" b = "prolate_spheroid_minor_axis_radius" @@ -172,7 +172,7 @@ source = "https://doi.org/10.1121/1.4937607" [[target]] name = "gas filled prolate spheroid" shape = "prolate spheroid" -model_type = "fluid filled" +boundary_type = "fluid filled" description = "A prolate spheroid with a fluid-filled interior with properties representative of air" a = "prolate_spheroid_major_axis_radius" b = "prolate_spheroid_minor_axis_radius" @@ -185,7 +185,7 @@ source = "https://doi.org/10.1121/1.4937607" [[target]] name = "weakly scattering prolate spheroid" shape = "prolate spheroid" -model_type = "fluid filled" +boundary_type = "fluid filled" description = "A prolate spheroid with a fluid-filled interior with similar properties to the surrounding medium" a = "prolate_spheroid_major_axis_radius" b = "prolate_spheroid_minor_axis_radius" @@ -198,7 +198,7 @@ source = "https://doi.org/10.1121/1.4937607" [[target]] name = "fixed rigid finite cylinder" shape = "finite cylinder" -model_type = "fixed rigid" +boundary_type = "fixed rigid" description = "A fixed rigid finite cylinder" a = "finite_cylinder_radius" b = "finite_cylinder_length" @@ -209,7 +209,7 @@ source = "https://doi.org/10.1121/1.4937607" [[target]] name = "pressure release finite cylinder" shape = "finite cylinder" -model_type = "pressure release" +boundary_type = "pressure release" description = "A finite cylinder with a pressure release surface" a = "finite_cylinder_radius" b = "finite_cylinder_length" @@ -220,7 +220,7 @@ source = "https://doi.org/10.1121/1.4937607" [[target]] name = "gas filled finite cylinder" shape = "finite cylinder" -model_type = "fluid filled" +boundary_type = "fluid filled" description = "A finite cylinder with fluid-filled interior with properties representative of air" a = "finite_cylinder_radius" b = "finite_cylinder_length" @@ -233,7 +233,7 @@ source = "https://doi.org/10.1121/1.4937607" [[target]] name = "weakly scattering finite cylinder" shape = "finite cylinder" -model_type = "fluid filled" +boundary_type = "fluid filled" description = "A finite cylinder with fluid-filled interior with properties similar to the surrounding medium" a = "finite_cylinder_radius" b = "finite_cylinder_length" diff --git a/src/echosms/scattermodelbase.py b/src/echosms/scattermodelbase.py index 89ef0a0..f6a05b0 100644 --- a/src/echosms/scattermodelbase.py +++ b/src/echosms/scattermodelbase.py @@ -21,10 +21,11 @@ class ScatterModelBase(abc.ABC): A short version of the model's long name, typically an ancronym. analytical_type : str Whether the model implements an ``exact`` or an ``approximate`` model. - model_types : list of str - The types of boundary conditions that the model provides. + boundary_types : list of str + The types of boundary conditions that the model provides, e.g., 'fixed rigid', + 'pressure release', 'fluid filled' shapes : list of str - The shapes that the model can represent. + The target shapes that the model can represent. max_ka : float An approximate maximum ka value that will result in accurate target strength results. Note that ka is often not the only parameter that determines the accuracy of the model (e.g., @@ -34,13 +35,12 @@ class ScatterModelBase(abc.ABC): @abc.abstractmethod def __init__(self): - self.long_name = '' # the name in words - self.short_name = '' # an abbreviation - self.analytical_type = '' # 'exact', 'approximate' - self.model_types = [] # e.g., 'fixed rigid', 'pressure release', 'fluid filled' - self.shapes = [] # the target shapes that this model can simulate - # An indication of the maximum ka value that this model provides accurate results for - self.max_ka = np.nan # [1] + self.long_name = '' + self.short_name = '' + self.analytical_type = '' + self.boundary_types = [] + self.shapes = [] + self.max_ka = np.nan def calculate_ts(self, data, multiprocess=False): """Calculate the TS for many parameter sets. @@ -81,7 +81,7 @@ def calculate_ts(self, data, multiprocess=False): # ts = df.swifter.apply(self.__ts_helper, axis=1) ts = data.apply(self.__ts_helper, axis=1) else: # this uses just one CPU - # ts = data.apply(self.__ts_helper, args=(model_type,), axis=1) + # ts = data.apply(self.__ts_helper, axis=1) ts = data.apply(self.__ts_helper, axis=1) return ts.to_numpy() # TODO - return data type that matches the input data type diff --git a/src/example_code.py b/src/example_code.py index c853cfd..bc3e461 100644 --- a/src/example_code.py +++ b/src/example_code.py @@ -55,7 +55,7 @@ pass print(f'The {mod.short_name.upper()} ({mod.long_name}) model supports boundary ' - f'types of {mod.model_types}.') + f'types of {mod.boundary_types}.') for name in names: # Get the model parameters used in Jech et al. (2015) for a particular model. @@ -174,7 +174,7 @@ 'f': np.linspace(12, 100, num=400) * 1000, 'theta': np.arange(0, 180, 1), 'a': 0.07, - 'model_type': 'fluid filled', + 'boundary_type': 'fluid filled', 'target_c': 1450, 'target_rho': 1250}