-
Notifications
You must be signed in to change notification settings - Fork 1
Sample Model #56
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
base: develop
Are you sure you want to change the base?
Sample Model #56
Conversation
|
Code coverage is not 100%, but I think all the essentials are there, and I'd rather get feedback on the important things than polish tests that may need to change anyway :) Edit: apparently codecov is upset about the missing tests, so I added tests and discovered a minor bug in the process. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #56 +/- ##
===========================================
+ Coverage 96.60% 96.73% +0.13%
===========================================
Files 12 13 +1
Lines 412 521 +109
===========================================
+ Hits 398 504 +106
- Misses 14 17 +3
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
rozyczko
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
part 1 after a quick look
| Numeric = Union[float, int] | ||
|
|
||
|
|
||
| class SampleModel(ObjBase, MutableMapping): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't you inherit from TheoreticalModelBase or at least from BaseObjectCollection?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably? I'm not exactly sure
| params = [] | ||
| for comp in self.components.values(): | ||
| params.extend(comp.get_parameters()) | ||
| return params |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or just
from itertools import chain
# Create generator for temperature parameter
temp_params = (self._temperature,) if self._temperature is not None else ()
# Create generator for component parameters
comp_params = (param for comp in self.components.values()
for param in comp.get_parameters())
# Chain them together and return as list
return list(chain(temp_params, comp_params))(slight teaching aspect for itertools - a VERY important python module)
| if is_not_fixed and is_independent: | ||
| fit_parameters.append(parameter) | ||
|
|
||
| return fit_parameters |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, not a bad loop (easy to see what's being done) but if you want to make it more "modular" you could use list comprehension, e.g.
def is_fit_parameter(param: Parameter) -> bool:
"""Check if a parameter can be used for fitting."""
return (not getattr(param, "fixed", False) and
getattr(param, "_independent", True))
return [param for param in self.get_parameters() if is_fit_parameter(param)]|
@rozyczko Would you have another look when you have time? I'm working on other things the rest of the day and am away tomorrow, so no rush. |
rozyczko
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few more comments after pondering on the file for too long ;)
| """ | ||
|
|
||
| CollectionBase.__init__(self, name=name) | ||
| TheoreticalModelBase.__init__(self, name=name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be just
super().__init__(name=name)to properly handle multiple inheritances. Otherwise it violates MRO best practices and makes maintenance more difficult (by being too explicit)
| ------- | ||
| List[ModelComponent] | ||
| """ | ||
| return list(self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The class mixes direct list access (list(self)) with a components property. Pick one approach:
- Either make
componentsthe primary interface and removelist(self)calls - Or fully embrace the collection interface and remove the
componentsproperty
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I will embrace the collection
| Name to assign to the component. If None, uses the component's own name. | ||
| """ | ||
| if not isinstance(component, ModelComponent): | ||
| raise TypeError("component must be an instance of ModelComponent.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe uppercase Component since it is the start of the sentence?
|
|
||
| # Add initial components if provided. Mostly used for serialization. | ||
| if data: | ||
| # Just to be safe |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment adds no value. Either explain WHY it's needed or remove it, please.
|
|
||
|
|
||
| class SampleModel(CollectionBase, TheoreticalModelBase): | ||
| """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would probably be useful to add __len__ so querying the model is simpler
>>> model = SampleModel()
>>> model.add_component(Gaussian(name="g1"))
>>> len(model)To do this, just query super():
def __len__(self) -> int:
return super().__len__()There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Already works! :)
| self.insert(index=len(self), value=component) | ||
|
|
||
| def remove_component(self, name: str): | ||
| """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why find all indices if you only delete the first? This is either inefficient or you expect duplicate names?
Just delete the first index found.
| List[ModelComponent] | ||
| """ | ||
| return list(self) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, this creation of a new list every time components() is accessed is pretty wasteful. Either cache it or use list(self) directly as said earlier.
| ) # noqa: E501 | ||
|
|
||
| def convert_unit(self, unit: Union[str, sc.Unit]) -> None: | ||
| """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please consider adding the rollback mechanism, which you did in ModelComponent.
| if not self.components: | ||
| raise ValueError("No components in the model to evaluate.") | ||
| result = None | ||
| for component in list(self): | ||
| value = component.evaluate(x) | ||
| result = value if result is None else result + value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or just
if not self.components:
raise ValueError("No components in the model to evaluate.")
return sum(component.evaluate(x) for component in self)|
|
||
| class SampleModel(CollectionBase, TheoreticalModelBase): | ||
| """ | ||
| A model of the scattering from a sample, combining multiple model components. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, you could extend the API to include __contains__ so we can do:
>>> model = SampleModel()
>>> gaussian = Gaussian(name="g1")
>>> model.add_component(gaussian)
>>> "g1" in model
True
>>> gaussian in model
True
>>> "nonexistent" in model
Falsewith something like
def __contains__(self, item: Union[str, ModelComponent]) -> bool:
if isinstance(item, str):
# Check by component name
return any(comp.name == item for comp in self)
elif isinstance(item, ModelComponent):
# Check by component instance
return any(comp is item for comp in self)
else:
return False|
As always, very useful comments :) here's the updated version |
damskii9992
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First and by far the biggest comment.
Do you WANT your SampleModel to BE a list and dictionary? Or do you want it to contain a list/dictionary?
It should only be the former if you REALLY want to be able to iterate over your model, slice it to get components, or do len(sample_model) instead of, for example len(sample_model.components).
I don't really think you want it to be a list/dictionary . . .
And even if you did, you should probably write up your own collections class, as relying on CollectionBase is dangerous as it is subject to be removed (or at least heavily modified).
Well, I honestly don't care too much exactly what it is. I want to have a collection of components that I/the user can easily access and work with, I want it to be integrated with the rest of corelib, i.e. reuse as much of corelib as possible, which also helps minimizing the amount of code I have to write/maintain in dynamics-lib. Would you recommend the components to be stored as a dict or list? |
|
Updated to no longer use I'm still using the |
Adding SampleModel which can contain ModelComponents and calculate the model at given x, with and without detailed balance.