Skip to content

Conversation

@henrikjacobsenfys
Copy link
Member

First draft of sample and resolution model. If I could get some feedback on the general outline that'd be great. There's still a lot of polishing and testing to be done, but the general idea is there. Try out the sample_model.ipynb notebook in examples :)

@henrikjacobsenfys henrikjacobsenfys added [scope] enhancement Adds/improves features (major.MINOR.patch) [priority] medium Normal/default priority labels Jan 13, 2026
@codecov
Copy link

codecov bot commented Jan 13, 2026

Codecov Report

❌ Patch coverage is 92.11470% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 96.26%. Comparing base (2ef5011) to head (ed99238).

Files with missing lines Patch % Lines
src/easydynamics/sample_model/sample_model.py 82.35% 21 Missing ⚠️
...iffusion_model/brownian_translational_diffusion.py 92.85% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop      #69      +/-   ##
===========================================
- Coverage    97.83%   96.26%   -1.58%     
===========================================
  Files           16       21       +5     
  Lines          786     1043     +257     
===========================================
+ Hits           769     1004     +235     
- Misses          17       39      +22     
Flag Coverage Δ
unittests 96.26% <92.11%> (-1.58%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Member

@rozyczko rozyczko left a comment

Choose a reason for hiding this comment

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

A few serious issues highlighted in the code. I'll look at minor issues once these are addressed.

Additionally, I think that the naming and inheritance structure is confusing:

SampleModelBaseSampleModel, ResolutionModel, BackgroundModel
But ResolutionModel and BackgroundModel are not sample models - they're instrument-related
InstrumentModel inherits from NewBase (not ModelBase), creating inconsistency

Recommendation: Consider renaming:

SampleModelBase → QDependentModelBase or SpectralModelBase
Or create separate base classes for InstrumentComponentModelBase vs SampleModelBase

from easyscience.base_classes.model_base import ModelBase
from numpy.typing import ArrayLike

from easydynamics.sample_model import ComponentCollection
Copy link
Member

Choose a reason for hiding this comment

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

package-level import imports all components and diffusion models.
This makes the base class heavy and increases the chance of circular imports.
Consider direct import from the local module (ComponentCollection) to keep the base layer minimal and avoid side effects.

Comment on lines +24 to +25
components: ModelComponent | ComponentCollection | None = None,
Q: Q_type | None = None,
Copy link
Member

Choose a reason for hiding this comment

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

Responsibility split is blurry: SampleModelBase owns ComponentCollection and Q, while SampleModel also owns diffusion models and temperature. There is duplication of evaluation logic. Consider a clearer separation where base handles only collection evaluation and subclasses inject preprocessing (like detailed balance) .

# --------------------------------------------------------------------

def __repr__(self):
return f"{self.__class__.__name__}(unique_name={self.unique_name}, unit={self.unit}), Q = {self.Q}, components = {self.components}"
Copy link
Member

Choose a reason for hiding this comment

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

Didn't you mean self._unit? There's no self.unit here. A test would catch it

Comment on lines 34 to 35
def append_component(self, component: ModelComponent):
"""Append a component to the ResolutionModel. Does not allow DeltaFunction or Polynomial components, as these are not physical resolution components.
Copy link
Member

Choose a reason for hiding this comment

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

This blocks disallowed components, but append_components_from_collection() and the components setter in the base class bypass that check, allowing forbidden components to be inserted indirectly.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think I've fixed this. Tests will show :)

Comment on lines 170 to 171
def _validate_and_convert_Q(self, Q: Q_type) -> np.ndarray:
"""
Copy link
Member

Choose a reason for hiding this comment

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

Consider accepting sc.Variable for consistency

Copy link
Member Author

Choose a reason for hiding this comment

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

Good idea. Made a todo about moving entirely to scipp for internal use of Q. Will be a while though

Comment on lines 67 to 68
def append_component(self, component: ModelComponent) -> None:
if not isinstance(component, ModelComponent):
Copy link
Member

Choose a reason for hiding this comment

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

Changing Q, adding/removing components, or altering diffusion models does not invalidate or regenerate _component_collections. This can silently return stale results. Consider lazy regeneration or a dirty-flag pattern on Q, components, and diffusion models, as mentioned by you earlier in the discussion

Copy link
Member Author

Choose a reason for hiding this comment

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

Yup, this will come last once the rest is polished. It's not too terrible to ask users to run generate_component_collections() before they start doing stuff..

raise TypeError(
f"unit must be None, a string, or a scipp Unit, got {type(unit).__name__}"
)
self._unit = unit
Copy link
Member

Choose a reason for hiding this comment

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

Missing unit property - should be in the base class

Comment on lines 27 to 31
super().__init__(
display_name=display_name,
unique_name=unique_name,
)

Copy link
Member

Choose a reason for hiding this comment

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

SampleModelBase.__init__ never initializes self._component_collections but evaluate accesses it! This will raise AttributeError

@henrikjacobsenfys
Copy link
Member Author

It's missing a few test that I will do soon, and also the "dirty" pattern, but everything else should be there @rozyczko
I know the weekend just started so no rush, just feels good to send it off :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[priority] medium Normal/default priority [scope] enhancement Adds/improves features (major.MINOR.patch)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants