Skip to content

[WIP/ENH] - Update object organization and documentation #356

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
2878076
move model components to sub-object
TomDonoghue Apr 12, 2025
0057188
update BaseModel docs
TomDonoghue Apr 12, 2025
38b6734
modeled_spectrum_ -> modeled_spectrum
TomDonoghue Apr 12, 2025
9bb2331
move ModelComponents
TomDonoghue Apr 12, 2025
3b2c2c0
add ModelParameters object
TomDonoghue Apr 12, 2025
114d20a
update to use results.params
TomDonoghue Apr 13, 2025
c5b8f96
add non-default settings to test objects
TomDonoghue Apr 13, 2025
bb3cee9
extend compare_model_objs
TomDonoghue Apr 13, 2025
9804529
udpate IO tests to be more stringent
TomDonoghue Apr 13, 2025
d9101d4
update add_from_dict to clean up & fix algo settings
TomDonoghue Apr 13, 2025
e0cb026
finish removing _check_loaded_results
TomDonoghue Apr 13, 2025
ab9d696
deprecate results fields / add as property to params
TomDonoghue Apr 13, 2025
e4656d9
bands repr -> str
TomDonoghue Apr 13, 2025
d0b8831
drop trailing underscores
TomDonoghue Apr 13, 2025
23c1346
remove trailing underscores - null
TomDonoghue Apr 13, 2025
d161992
clean up data checks
TomDonoghue Apr 13, 2025
08c8961
sweep for docstrings content
TomDonoghue Apr 13, 2025
eb8d39c
tweak / update SettingsDefinitions
TomDonoghue Apr 14, 2025
e8f83d1
tweak Algorithm object for settings
TomDonoghue Apr 14, 2025
91720a3
add SettingsValues
TomDonoghue Apr 14, 2025
4418e1f
use SettingsValues
TomDonoghue Apr 14, 2025
9803c2f
udpate across code to use algorithm.settings
TomDonoghue Apr 14, 2025
47fef97
update spectral fit settings def dict name
TomDonoghue Apr 14, 2025
ad6c0e0
add private_settings to Algorithm
TomDonoghue Apr 15, 2025
cabf8af
use private settings on spectral fit algorithm object
TomDonoghue Apr 15, 2025
71cd252
update process for peak_width_limits -> gaussian std
TomDonoghue Apr 15, 2025
f751262
drop reset internal settings - no longer used
TomDonoghue Apr 15, 2025
7aaeb42
add clear method to settings values
TomDonoghue Apr 15, 2025
944e812
drop _check_loaded_settings
TomDonoghue Apr 15, 2025
4564872
update algo to use cf settings
TomDonoghue Apr 15, 2025
104128c
lints
TomDonoghue Apr 15, 2025
9b51f6d
udpate line spacing and settings docs
TomDonoghue Apr 15, 2025
edce4ec
add AlgorithmCF object
TomDonoghue Apr 17, 2025
e743691
add algo_cf initialize methods
TomDonoghue Apr 17, 2025
33d0eb0
use algocf initialization for ap
TomDonoghue Apr 17, 2025
6df8eb1
update _get_pe_bounds
TomDonoghue Apr 17, 2025
d96ed69
udpate _fit_peaks for modes
TomDonoghue Apr 17, 2025
6048b8c
update peak tuning for modes
TomDonoghue Apr 17, 2025
736a0be
udpate param conversion to use modes
TomDonoghue Apr 17, 2025
c488888
update / clean up descriptions
TomDonoghue Apr 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 118 additions & 46 deletions specparam/algorithms/algorithm.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
"""Define object to manage algorithm implementations."""

import numpy as np

from specparam.utils.checks import check_input_options
from specparam.algorithms.settings import SettingsDefinition
from specparam.algorithms.settings import SettingsDefinition, SettingsValues
from specparam.modutils.docs import docs_get_section, replace_docstring_sections

###################################################################################################
###################################################################################################

FORMATS = ['spectrum', 'spectra', 'spectrogram', 'spectrograms']

DATA_FORMATS = ['spectrum', 'spectra', 'spectrogram', 'spectrograms']

class Algorithm():
"""Template object for defining a fit algorithm.
Expand All @@ -18,25 +20,43 @@ class Algorithm():
Name of the fitting algorithm.
description : str
Description of the fitting algorithm.
settings : dict
Name and description of settings for the fitting algorithm.
format : {'spectrum', 'spectra', 'spectrogram', 'spectrograms'}
Set base format of data model can be applied to.
public_settings : SettingsDefinition or dict
Name and description of public settings for the fitting algorithm.
private_settings : SettingsDefinition or dict, optional
Name and description of private settings for the fitting algorithm.
data_format : {'spectrum', 'spectra', 'spectrogram', 'spectrograms'}
Set base data format the model can be applied to.
modes : Modes
Modes object with fit mode definitions.
data : Data
Data object with spectral data and metadata.
results : Results
Results object with model fit results and metrics.
debug : bool
Whether to run in debug state, raising an error if encountered during fitting.
"""

def __init__(self, name, description, settings, format,
modes=None, data=None, results=None, debug=False):
def __init__(self, name, description, public_settings, private_settings=None,
data_format='spectrum', modes=None, data=None, results=None, debug=False):
"""Initialize Algorithm object."""

self.name = name
self.description = description

if not isinstance(settings, SettingsDefinition):
settings = SettingsDefinition(settings)
self.settings = settings
if not isinstance(public_settings, SettingsDefinition):
public_settings = SettingsDefinition(public_settings)
self.public_settings = public_settings
self.settings = SettingsValues(self.public_settings.names)

if private_settings is None:
private_settings = {}
if not isinstance(private_settings, SettingsDefinition):
private_settings = SettingsDefinition(private_settings)
self.private_settings = private_settings
self._settings = SettingsValues(self.private_settings.names)

check_input_options(format, FORMATS, 'format')
self.format = format
check_input_options(data_format, DATA_FORMATS, 'data_format')
self.data_format = data_format

self.modes = None
self.data = None
Expand All @@ -60,13 +80,11 @@ def add_settings(self, settings):
Parameters
----------
settings : ModelSettings
A data object containing the settings for a power spectrum model.
A data object containing model settings.
"""

for setting in settings._fields:
setattr(self, setting, getattr(settings, setting))

self._check_loaded_settings(settings._asdict())
setattr(self.settings, setting, getattr(settings, setting))


def get_settings(self):
Expand All @@ -78,8 +96,8 @@ def get_settings(self):
Object containing the settings from the current object.
"""

return self.settings.make_model_settings()(\
**{key : getattr(self, key) for key in self.settings.names})
return self.public_settings.make_model_settings()(\
**{key : getattr(self.settings, key) for key in self.public_settings.names})


def get_debug(self):
Expand All @@ -100,32 +118,6 @@ def set_debug(self, debug):
self._debug = debug


def _check_loaded_settings(self, data):
"""Check if settings added, and update the object as needed.

Parameters
----------
data : dict
A dictionary of data that has been added to the object.
"""

# If settings not loaded from file, clear from object, so that default
# settings, which are potentially wrong for loaded data, aren't kept
if not set(self.settings.names).issubset(set(data.keys())):

# Reset all public settings to None
for setting in self.settings.names:
setattr(self, setting, None)

# Reset internal settings so that they are consistent with what was loaded
# Note that this will set internal settings to None, if public settings unavailable
self._reset_internal_settings()


def _reset_internal_settings(self):
""""Can be overloaded if any resetting needed for internal settings."""


def _reset_subobjects(self, modes=None, data=None, results=None):
"""Reset links to sub-objects (mode / data / results).

Expand All @@ -145,3 +137,83 @@ def _reset_subobjects(self, modes=None, data=None, results=None):
self.data = data
if results is not None:
self.results = results


## AlgorithmCF

CURVE_FIT_SETTINGS = SettingsDefinition({
'maxfev' : {
'type' : 'int',
'description' : 'The maximum number of calls to the curve fitting function.',
},
'tol' : {
'type' : 'float',
'description' : \
'The tolerance setting for curve fitting (see scipy.curve_fit: ftol / xtol / gtol).'
},
})

@replace_docstring_sections([docs_get_section(Algorithm.__doc__, 'Parameters')])
class AlgorithmCF(Algorithm):
"""Template object for defining a fit algorithm that uses `curve_fit`.

Parameters
----------
% copied in from Algorithm
"""

def __init__(self, name, description, public_settings, private_settings=None,
data_format='spectrum', modes=None, data=None, results=None, debug=False):
"""Initialize Algorithm object."""

Algorithm.__init__(self, name=name, description=description,
public_settings=public_settings, private_settings=private_settings,
data_format=data_format, modes=modes, data=data, results=results,
debug=debug)

self._cf_settings_desc = CURVE_FIT_SETTINGS
self._cf_settings = SettingsValues(self._cf_settings_desc.names)


def _initialize_bounds(self, mode):
"""Initialize a bounds definition.

Parameters
----------
mode : {'aperiodic', 'periodic'}
Which mode to initialize for.

Returns
-------
bounds : tuple of array
Bounds values.

Notes
-----
Output follows the needed bounds definition for curve_fit, which is:
([low_bound_param1, low_bound_param2],
[high_bound_param1, high_bound_param2])
"""

n_params = getattr(self.modes, mode).n_params
bounds = (np.array([-np.inf] * n_params), np.array([np.inf] * n_params))

return bounds

def _initialize_guess(self, mode):
"""Initialize a guess definition.

Parameters
----------
mode : {'aperiodic', 'periodic'}
Which mode to initialize for.

Returns
-------
guess : 1d array
Guess values.
"""

guess = np.zeros([getattr(self.modes, mode).n_params])

return guess
107 changes: 98 additions & 9 deletions specparam/algorithms/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,48 +5,132 @@
###################################################################################################
###################################################################################################

class SettingsValues():
"""Defines a set of algorithm settings values.

Parameters
----------
names : list of str
Names of the settings to hold values for.

Attributes
----------
values : dict of {str : object}
Settings values.
"""

__slots__ = ('values',)

def __init__(self, names):
"""Initialize settings values."""

self.values = {name : None for name in names}


def __getattr__(self, name):
"""Allow for accessing settings values as attributes."""

try:
return self.values[name]
except KeyError:
raise AttributeError(name)


def __setattr__(self, name, value):
"""Allow for setting settings values as attributes."""

if name == 'values':
super().__setattr__(name, value)
else:
getattr(self, name)
self.values[name] = value


def __getstate__(self):
"""Define how to get object state - for pickling."""

return self.values


def __setstate__(self, state):
"""Define how to set object state - for pickling."""

self.values = state


@property
def names(self):
"""Property attribute for settings names."""

return list(self.values.keys())


def clear(self):
"""Clear all settings - resetting to None."""

for setting in self.names:
self.values[setting] = None


class SettingsDefinition():
"""Defines a set of algorithm settings.

Parameters
----------
settings : dict
definitions : dict
Settings definition.
Each key should be a str name of a setting.
Each value should be a dictionary with keys 'type' and 'description', with str values.

Attributes
----------
names : list of str
Names of the settings defined in the object.
descriptions : dict of {str : str}
Description of each setting.
types : dict of {str : str}
Type for each setting.
values : dict of {str : object}
Value of each setting.
"""

def __init__(self, settings):
def __init__(self, definitions):
"""Initialize settings definition."""

self._settings = settings
self._definitions = definitions


def __len__(self):
"""Define the length of the object as the number of settings."""

def _get_settings_subdict(self, field):
"""Helper function to select from settings dictionary."""
return len(self._definitions)

return {label : self._settings[label][field] for label in self._settings.keys()}

def _get_definitions_subdict(self, field):
"""Helper function to select from definitions dictionary."""

return {label : self._definitions[label][field] for label in self._definitions.keys()}


@property
def names(self):
"""Make property alias for setting names."""

return list(self._settings.keys())
return list(self._definitions.keys())


@property
def types(self):
"""Make property alias for setting types."""

return self._get_settings_subdict('type')
return self._get_definitions_subdict('type')


@property
def descriptions(self):
"""Make property alias for setting descriptions."""

return self._get_settings_subdict('description')
return self._get_definitions_subdict('description')


def make_setting_str(self, name):
Expand Down Expand Up @@ -91,6 +175,11 @@

class ModelSettings(namedtuple('ModelSettings', self.names)):
__slots__ = ()

@property
def names(self):
return list(self._fields)

Check warning on line 181 in specparam/algorithms/settings.py

View check run for this annotation

Codecov / codecov/patch

specparam/algorithms/settings.py#L181

Added line #L181 was not covered by tests

ModelSettings.__doc__ = self.make_docstring()

return ModelSettings
Loading