Skip to content

[ENH] - Add explicit {aperiodic, periodic} 'Modes' support #298

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

Merged
merged 244 commits into from
Apr 10, 2025
Merged

Conversation

TomDonoghue
Copy link
Member

@TomDonoghue TomDonoghue commented Aug 7, 2023

This PR updates to use fit "modes" to define and use aperiodic_mode and periodic_mode as flexible fit modes that not only define and support a way to add new fit modes to the model, but also set up the infrastructure to allow for passing in fit functions. In addition, it adds a lot of updates and developments along the way, perhaps most notably further updating the model object organization to use sub-objects instead of multiple inheritance.

"Fit Mode" idea

The notable update re-modes is specparam.modes, where there is a definition of functionality to define a fit mode. For a brief overview, the base idea is that for each of the {aperiodic, periodic} modes, we define a "Mode" for how we fit this. Specifically, this is basically the fit function itself, but also, we define a set of associated meta-data that describes the parameters and so on. The core of this update is to update a lot of hard-coded stuff across the code, so that as much as possible it reads needed information dynamically from the Mode object, allowing for flexibility (across things like accessing parameters, printing results, reports, plots, IO, manipulating objects). By using this Mode information, we don't have to hard-code information about number of parameters / labels, etc, and can read this on the fly from the Mode definition.

# Example of defining a mode - define a 'Mode' representation of our current "fixed" aperiodic model
ap_fixed = Mode(\
    name='fixed', component='aperiodic', description='Fit an exponential, with no knee.',
    func=expo_nk_function,  params={'offset' : 'offset-description, 'exponent' : 'exponent-description'}, 
    freq_space='linear', powers_space='log10')

Passing in fit functions

In addition, this also allows for the basics of passing in a new fit function (note that this is currently a basic proof of concept).

# Define a new fit function
def knee_func2(xs, *params):
    
    ys = np.zeros_like(xs)
    offset, knee, exp = params
    ys = ys + offset - np.log10(np.sqrt(knee) + xs**exp)
    
    return ys

# Define new aperiodic fit mode
params = {
        'offset' : 'Offset of the aperiodic component.',
        'knee_freq' : 'Square root of the knee of the aperiodic component.',
        'exponent' : 'Exponent of the aperiodic component.',
    }

ap_knee2 = Mode(name='knee', component='aperiodic', 
                description='Fit an exponential, with a knee (as a frequency).',
                func=knee_func2, params=params,
                freq_space='linear', powers_space='log10')

# Initialize a model, and fit with the new aperiodic fit mode
fm2 = SpectralModel(aperiodic_mode=ap_knee2, verbose=False)
fm2.report(freqs, powers)
# This runs, and returns a knee value - from the new fit function (is different from "internal" knee mode)

This is a simple example (doesn't change much about the number of parameters, etc), but is a simple example to show the idea.

Related Updates in this PR

This PR also continues to model object reorganization started in #291 (and partly in #354) - most notably to keep the 'sub-objects' organization developed there, but now to add sub-objects to the Model object, rather than multiple inheritance that attaches all the attributes to the object at the same level. This changes the API a bit, but most of the main user-facing functionality is updated to stay the same, with some more nuanced access points now needing updates (e.g. going from Model.n_peaks_ -> Model.results.n_peaks_).

There is also a wide range of associated changes in the PR, including updating to use the Modes approach across:

  • model string and reports
  • sim functionality
  • update of data objects for Modes updates
  • update of Algorithm to support use of different algorithms

Going Forward

If you explore the Mode object, there are some aspects that look further forward, in particular defining the data spacing. The idea is that a particular mode can be defined on a particular spacing (for each of 'freqs' and 'powers'), and so by defining this, and ultimately setting and checking this in relating to the Data object, one can define arbitrary fit functions across arbitrary data representations. When integrated with allowing for arbitrary fit procedures from #291, we get the full level of generalization!

@TomDonoghue TomDonoghue changed the base branch from main to basemodel August 7, 2023 20:44
@TomDonoghue TomDonoghue changed the title [WIP] - Add explicit 'Modes' support for {aperiodic, periodic} function selection / management [WIP] - Add explicit {aperiodic, periodic} 'Modes' support Aug 7, 2023
@TomDonoghue
Copy link
Member Author

@ryanhammonds & @voytek - tagging you here so you can get an eye on this, to see where I'm trying to move things here, and how this relates to #291. It's not ready for a review in any real way - but thoughts (especially "yay" / "nay" votes on this direction) are appreciated!

@voytek
Copy link
Contributor

voytek commented Aug 7, 2023

On the surface this looks good. I can't quite tell, but does this construction allow for something like the dual-knee fitting approach we've been playing with, for example?

@TomDonoghue
Copy link
Member Author

On the surface this looks good. I can't quite tell, but does this construction allow for something like the dual-knee fitting approach we've been playing with, for example?

@voytek - yeh, the goal is that it extends to this - the Mode definition would include the function and list of parameters, and then the rest of the code would read this definition to dynamically updates things like the expected number of parameters and labels, etc. That part of it isn't built in yet - but I don't currently foresee any major roadblocks to doing it!

If this looks good as a first draft / direction - I can keep working to get to some proof of concepts that get a bit further and show support for an actually new / different fit function!

@ryanhammonds
Copy link
Contributor

Cool, this looks like it track's important/relevant metadata for new models. What is the plan for handling parameter guess procedures? I guess that's kinda related to #291. I guess for each parameter of new models model there could be either a hard-coded guess/bounds or new procedures to generate the guess/bounds.

@TomDonoghue TomDonoghue added the 2.0 Targetted for the specparam 2.0 release. label Aug 22, 2023
@TomDonoghue TomDonoghue mentioned this pull request Apr 4, 2024
12 tasks
@TomDonoghue TomDonoghue mentioned this pull request Apr 8, 2025
@TomDonoghue TomDonoghue changed the title [WIP] - Add explicit {aperiodic, periodic} 'Modes' support [ENH] - Add explicit {aperiodic, periodic} 'Modes' support Apr 10, 2025
@fooof-tools fooof-tools deleted a comment from codecov bot Apr 10, 2025
@TomDonoghue
Copy link
Member Author

This PR has become a bit of a beast, and I don't think there's much utility in trying to review the extensive diffs. The updates here are now all done in way that everything works for using modes - with a bit more work probably to come on a bit more clean up and generalization. I'm going to merge this in now, and we can go from there for further work.

@TomDonoghue TomDonoghue merged commit fbce418 into main Apr 10, 2025
8 of 9 checks passed
@TomDonoghue TomDonoghue deleted the modes branch April 10, 2025 17:02
@fooof-tools fooof-tools deleted a comment from codecov bot Apr 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2.0 Targetted for the specparam 2.0 release.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants