Skip to content

Model refactoring stage 1: "evaluate" normalization#2580

Merged
embray merged 11 commits into
astropy:masterfrom
embray:modeling/model-refactor-1
Aug 6, 2014
Merged

Model refactoring stage 1: "evaluate" normalization#2580
embray merged 11 commits into
astropy:masterfrom
embray:modeling/model-refactor-1

Conversation

@embray

@embray embray commented May 29, 2014

Copy link
Copy Markdown
Member

This PR is to demonstrate one aspect of where I'm going with modeling refactoring. This PR is not entirely complete--there are some missing features, and a few models don't work with it (in particular composite models, though they are being refactored as well). This PR does not yet demonstrate the full advantage of the changes it presents. I just wanted to get it out there so interested parties could get an idea of where things are going.

The primary change demonstrated by this PR is as follows: Most model subclasses will no longer implement a __call__ method manually. Model.__call__ is implemented in the base class, and is not generally overridden by subclasses. There's nothing stopping a subclass from overriding it, but I'm trying to move things a direction where it generally won't be necessary.

Instead, when implementing a new model, developers/users must supply a method called evaluate. This is almost completely analogous to the existing implementation of FittableModel (formerly ParametricModel) subclasses that required implementing an eval method. (The name change is mostly justified by the fact that "eval" is a Python builtin, so using it confuses some syntax highlighters; also it doesn't need to be typed often so a little verbosity doesn't hurt).

The evaluate method may be either a normal instance method, a classmethod, or a staticmethod. Regardless, it takes a fixed set of arguments, consisting of all of the function's independent variables, followed by the functional parameters needed to evaluate the function. The parameters are always provided in a fixed order based on the order they were defined on the model class. The arguments are always assumed to by Numpy arrays that are already in the correct shape to be evaluated. The key difference here is that all models are implemented this way, not just "fittable models", which may eventually be removed as a separate class. The goal is to reduce the number of different ways of implementing the model, as well as the amount of boilerplate needed.

One advantage of this is that it makes it easy to swap out implementations of evaluate for any given model. As long as the implementation is wrapped to take arguments in the correct order it could be a Numpy implementation (like we currently use), or a function using numexpr, or written in C or Cython, or what have you. A related advantage, not yet demonstrated by this PR, is that it will make some aspects of implementing fitters simpler and, I think, more efficient. I am working on another set of changes on top of both this and #2441 that I hope will demonstrate the full advantages.

Update: You can view the docs for this PR here: http://embray.github.io/astropy/modeling/index.html

@embray embray added this to the v1.0.0 milestone May 29, 2014
@hamogu

hamogu commented May 30, 2014

Copy link
Copy Markdown
Member

I like the way this is going. I did not go through all lines for how the inputs are prepared, transposed, etc. but I like the idea.

@mdboom

mdboom commented May 30, 2014

Copy link
Copy Markdown
Contributor

This makes a lot of sense. One thing that occurs to me is that prepare_inputs/prepare_outputs could get expensive if called for every step in a long transformation chain (even to ultimately discover that the inputs are already in the correct form). But that's all conjecture, so you should probably ignore as "premature optimization" for now.

@mhvk

mhvk commented May 30, 2014

Copy link
Copy Markdown
Contributor

The one comment I have is whether it might not be better to give multiple inputs as one tuple instead of separately. This allows the model to be agnostic about the number of inputs, which can be very useful, e.g., when fitting a spectrum to a linear combination of an arbitrary number of bases (as might happen in spectral synthesis, or in determining a line broadening function (Rucinski 2002AJ....124.1746R), etc). One implementation would be to make this similar to what now seems to be done for outputs (where even a single output is turned into a tuple, so one can iterate over it). The thing I'm less sure about is whether a single input should just be passed on as is, or whether it should be a tuple too (probably the former is best).

@embray

embray commented May 30, 2014

Copy link
Copy Markdown
Member Author

@hamogu

I like the way this is going. I did not go through all lines for how the inputs are prepared, transposed, etc. but I like the idea.

Currently that's just a reflection of the existing implementation. I'm reworking the exact details of how inputs/outputs are prepared in a side-branch. Still working out some of the details though.

@embray

embray commented May 30, 2014

Copy link
Copy Markdown
Member Author

@mdboom

One thing that occurs to me is that prepare_inputs/prepare_outputs could get expensive if called for every step in a long transformation chain (even to ultimately discover that the inputs are already in the correct form).

Part of the reason for splitting out prepare_inputs and prepare_outputs is that, the goal anyway, is that they would be used only once in most computations. They generally aren't used during fitting (except maybe one use of prepare_inputs at the beginning). And although not implemented yet, they are where compound models would prepare the inputs for going through a chain of, but then use the evaluate methods directly for the computations.

The Model.__call__ interface is a useful user-level API, but the idea here is to avoid it when possible for more complex code that uses these models internally.

@embray

embray commented May 30, 2014

Copy link
Copy Markdown
Member Author

@mhvk That's a discussion worth having (and that has already been happening in other channels) but is mostly orthogonal to the main purpose of this PR, which is just one building block for future updates.

At some point I think I am going to work on an APE or series of APEs to clarify all the details and provide full justification for various design decisions. That would be a good place to talk about details like that.

@mhvk

mhvk commented May 30, 2014

Copy link
Copy Markdown
Contributor

@embray - I guess you are right that at the moment there is a relatively simple change of __call__ to evaluate. Indeed, it would seem that even the code would be (mostly) identical if one were to change to a tuple (just remove the * in the argument list?). So, do ignore. Overall, I really like the direction!

embray added a commit to embray/astropy that referenced this pull request Jun 20, 2014
…ear fits only, which is the only place that is supported for now). Right now this only supports fitting with *one* input across all models (such as when evaluating the model with model_set_axis=False). This is actually in line with what was previously supported. The only major difference is that it's no longer to transpose the 'y' input before passing to the fitter. This is, as I see it, an internal detail of how the fitting algorithm works and not something users should be responsible for. I'll clean this up further once I've merged this work astropy#2580, which will make further improvements easier.
@embray

embray commented Jul 12, 2014

Copy link
Copy Markdown
Member Author

Rebased this to fully incorporate the work from #2634. This was a tad tricky but I think it will work. This was a necessary precursor to any further heavy duty work on models :<

@embray

embray commented Jul 23, 2014

Copy link
Copy Markdown
Member Author

Great. Apparently my doc build went from one warning to two with that last commit. I'll check this out again tomorrow....

embray added a commit to embray/astropy that referenced this pull request Jul 29, 2014
@embray

embray commented Jul 29, 2014

Copy link
Copy Markdown
Member Author

Updated with a link to the docs as well: http://embray.github.io/astropy/modeling/index.html

@cdeil

cdeil commented Jul 29, 2014

Copy link
Copy Markdown
Member

@embray I'm getting the following modeling test failures with Python 2.7 on Mac:
https://gist.github.com/cdeil/315b00aa3e6387e9aba3

@embray

embray commented Jul 29, 2014

Copy link
Copy Markdown
Member Author

@cdeil That was just introduced a couple hours ago in e5fcec0 which was just sloppy. I'm going to fix it up.

@embray

embray commented Jul 30, 2014

Copy link
Copy Markdown
Member Author

I think after rebasing this a few times Travis has given up updated the build status for this PR. However it is still doing the builds. The current build (for 282a0c8) is here: https://travis-ci.org/astropy/astropy/builds/31286649

@embray

embray commented Jul 31, 2014

Copy link
Copy Markdown
Member Author

Ok, FWIW the latest build passed.

Comment thread docs/modeling/new.rst

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines 171-194 need one level of indentation.

@embray

embray commented Aug 1, 2014

Copy link
Copy Markdown
Member Author

Note, as of 6d1598e this PR now incorporates the fixes from #2811 as well.

@embray

embray commented Aug 1, 2014

Copy link
Copy Markdown
Member Author

@cdeil I updated this PR to include more graceful deprecation for the eval -> evaluate switch (at least I think). I tested gammapy against this and all tests now pass. I'll check photutils as well shortly.

@embray

embray commented Aug 1, 2014

Copy link
Copy Markdown
Member Author

Confirmed, all phoutils tests pass against this PR as well.

@cdeil

cdeil commented Aug 4, 2014

Copy link
Copy Markdown
Member

@embray Thanks for keeping eval for now!

If we want the upcoming photutils and gammapy 0.1 releases to work with Astropy 0.4, we should simply do nothing and only rename eval -> evaluate once Astropy 1.0 is released and Astropy 0.4 support is dropped for photutils and gammapy, right?

travis-ci shows a unit test error with Python 2.6 from the deprecate line.

Comment thread docs/modeling/new.rst

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you make this a Python code-block so that the code is syntax highlighted?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, sure, I thought normally it did that automatically.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, I think normally it does, but it didn't in this case due to the syntax error. That said, I prefer the explicit code-block directive.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ... I was wondering why Python syntax highlighting didn't already work with the :: code block ... so Sphinx gives up if there's a SyntaxError without generating a warning? ... if yes, I think that's not very nice.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not exactly sure how it's handling :: blocks without a language specification. Because those blocks don't necessarily contain source code, much less Python source code (or whatever the default language is set to). So it might just try parsing it as the default language, and only highlighting if parsing succeeds (and treating it as plaintext otherwise).

@cdeil

cdeil commented Aug 4, 2014

Copy link
Copy Markdown
Member

Currently the example in the docs that explains how to implement a new model does override __call__ and then explains that this is rarely needed. I'd suggest instead to keep it simple and not override __call__.

If there's an case more than a few users might run into there could be an advanced example further down that explains about __call__ and why it's necessary to override it here, but probably a sentence and a link to PolynomialModel as an example would be sufficient.

@embray

embray commented Aug 4, 2014

Copy link
Copy Markdown
Member Author

@cdeil You're right--actually __call__ should almost never be overwritten. It's not even necessary for the polynomial models, actually (it's sufficient there to override prepare_inputs).

@embray

embray commented Aug 4, 2014

Copy link
Copy Markdown
Member Author

@cdeil #2817 should, I think, fix the issue with deprecated in Python 2.6. I'll incorporate it into this PR.

embray added 9 commits August 5, 2014 12:02
generic __call__, with all the machinery for evaluating a given model in
a .evaluate() method that every model is required to have--preferably a
staticmethod or a classmethod so that it can implemented easily
independtly of the model class it belongs to.

The new system is not yet implemented for composite models or for
polynomial models, which will require some further refactoring.
…f implementing all its core calculation in an evaluate method. This includes prepare_input and prepare_output methods that can be overridden by subclasses and used also by fitters. The exact semantics of prepare_input and prepare_output I am not quite happy with yet--currently they do what is necessary to preserve the existing input handling, which I intend to rework. Finally, Model.param_sets is no longer an ndarray, but just a normal list (albeit of ndarrays). This was the only way I could get it to work consistently with multi-dimensional parameter values. I plan to replace Model.param_sets with something else later.
… mention of process_input/output but I think that's a pretty advanced thing most models won't need to touch). Fixed, I think, the documentation references that were failing
@embray

embray commented Aug 5, 2014

Copy link
Copy Markdown
Member Author

Rebased with the fixes from #2817. Will address the other comments from @cdeil shortly.

@embray

embray commented Aug 5, 2014

Copy link
Copy Markdown
Member Author

On second thought, rereading that section it already explains the only reason to override __call__ is if you want to make explicit the names of the arguments. That's going to change shortly but for now it holds.

@embray

embray commented Aug 5, 2014

Copy link
Copy Markdown
Member Author

Without any further comments I'll go ahead and merge this I think.

embray added a commit that referenced this pull request Aug 6, 2014
Model refactoring stage 1: "evaluate" normalization
@embray embray merged commit 356c407 into astropy:master Aug 6, 2014
@embray embray deleted the modeling/model-refactor-1 branch August 6, 2014 15:58
@embray embray mentioned this pull request Aug 7, 2014
@astrofrog astrofrog mentioned this pull request Nov 21, 2014
10 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants