Skip to content

Adoption to new easyscience (fitting, parameter unique name #173

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 30 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
fad0e66
adjustments to new parameter and fitting structure in easyscience
andped10 Jun 27, 2024
4d42e74
almost working
andped10 Jun 27, 2024
fc815c8
minor cleaning
andped10 Jul 2, 2024
1817d3a
tests cleaning
andped10 Jul 2, 2024
49ff36a
adjustments model
andped10 Jul 2, 2024
a3f5ffe
adjustments fitting
andped10 Jul 2, 2024
fb6f968
tests adjusted
andped10 Jul 2, 2024
8042883
test all 3 classes of minimizers
andped10 Jul 4, 2024
6640d72
clean global_obejct
andped10 Aug 14, 2024
e00e73f
use minimizer enumerator
andped10 Aug 14, 2024
c7e7882
towards a functional setup
andped10 Aug 15, 2024
1f5d0eb
more green tests
andped10 Aug 15, 2024
489d01e
more unique name
andped10 Aug 16, 2024
8221830
Tests are passing
andped10 Aug 19, 2024
23fd48e
code cleaning
andped10 Aug 19, 2024
806de85
ruff
andped10 Aug 20, 2024
8ae034b
fix imports
andped10 Aug 20, 2024
54d6382
new way of cleaning map
andped10 Aug 20, 2024
4b45d63
more carefull cleaning
andped10 Aug 20, 2024
b666d0f
rename for consistency
andped10 Aug 20, 2024
4a7db8d
cleaning from easyscience
andped10 Aug 20, 2024
18718b8
update easyscience dependency
andped10 Aug 21, 2024
4dae7fd
adjustments to tutorials
andped10 Aug 27, 2024
c66f1cd
adjustment for tutorials
andped10 Aug 27, 2024
a13dc0b
pr response
andped10 Aug 27, 2024
444d975
PR response code comments
andped10 Aug 27, 2024
e5ba9c9
PR code comment
andped10 Aug 27, 2024
c7bfaf6
cleaning notebook
andped10 Aug 28, 2024
1750d92
use easyscience from branch
andped10 Aug 28, 2024
f812cfa
easyscience develop
andped10 Aug 28, 2024
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
4 changes: 2 additions & 2 deletions docs/src/sample/material_library.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ The construction of a :py:class:`Material` is achieved as shown below.
name='Boron'
)

The above object will have the properties of :py:attr:`sld` and :py:attr:`isld`, which will have values of :code:`6.908 1 / angstrom ** 2` and :code:`-0.278 1 / angstrom ** 2` respectively.
The above object will have the properties of :py:attr:`sld` and :py:attr:`isld`, which will have values of :code:`6.908 1/angstrom^2` and :code:`-0.278 1/angstrom^2` respectively.
As is shown in the `tutorials`_, a material can be used to construct a :py:class:`Layer` from which `slab models`_ are created.

:py:class:`MaterialDensity`
Expand Down Expand Up @@ -78,7 +78,7 @@ So to produce a :py:class:`MaterialSolvated` that is 20 % D2O in a polymer, the
name='Solvated Polymer'
)

For the :py:attr:`solvated_polymer` object, the :py:attr:`sld` will be :code:`2.872 1 / angstrom ** 2` (the weighted average of the two scattering length densities).
For the :py:attr:`solvated_polymer` object, the :py:attr:`sld` will be :code:`2.872 1/angstrom^2` (the weighted average of the two scattering length densities).
The :py:class:`MaterialSolvated` includes a constraint such that if the value of either constituent scattering length densities (both real and imaginary components) or the fraction changes, then the resulting material :py:attr:`sld` and :py:attr:`isld` will change appropriately.

.. _`assemblies`: ./assemblies_library.html
Expand Down
4 changes: 2 additions & 2 deletions docs/src/tutorials/sample/multi_contrast.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@
"metadata": {},
"outputs": [],
"source": [
"d13d2o.head_layer.thickness.raw_value, d70d2o.head_layer.thickness.raw_value, d83acmw.head_layer.thickness.raw_value"
"d13d2o.head_layer.thickness.value, d70d2o.head_layer.thickness.value, d83acmw.head_layer.thickness.value"
]
},
{
Expand All @@ -386,7 +386,7 @@
"metadata": {},
"outputs": [],
"source": [
"d13d2o.head_layer.thickness.raw_value, d70d2o.head_layer.thickness.raw_value, d83acmw.head_layer.thickness.raw_value"
"d13d2o.head_layer.thickness.value, d70d2o.head_layer.thickness.value, d83acmw.head_layer.thickness.value"
]
},
{
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ classifiers = [
]
requires-python = ">=3.9,<3.12"
dependencies = [
"easyscience>=0.6.0",
'easyscience @ git+https://github.com/EasyScience/EasyScience.git@develop',
"scipp>=23.12.0",
"refnx>=0.1.15",
"refl1d>=0.8.14",
Expand Down
17 changes: 9 additions & 8 deletions src/easyreflectometry/calculators/bornagain/calculator.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
__author__ = 'github.com/arm61'

import numpy as np
from easyscience.Objects.Inferface import ItemContainer

from easyreflectometry.experiment import Model
from easyreflectometry.sample import Layer
from easyreflectometry.sample import Material
from easyreflectometry.sample import MaterialMixture
from easyreflectometry.sample import Multilayer
from easyscience.Objects.Inferface import ItemContainer

from ..calculator_base import CalculatorBase
from .wrapper import BornAgainWrapper
Expand Down Expand Up @@ -66,7 +67,7 @@ def create(self, model: Material | Layer | Multilayer | Model) -> list[ItemConta
r_list = []
t_ = type(model)
if issubclass(t_, Material):
key = model.uid
key = model.unique_name
if key not in self._wrapper.storage['material'].keys():
self._wrapper.create_material(key)
r_list.append(
Expand All @@ -78,7 +79,7 @@ def create(self, model: Material | Layer | Multilayer | Model) -> list[ItemConta
)
)
elif issubclass(t_, MaterialMixture):
key = model.uid
key = model.unique_name
if key not in self._wrapper.storage['material'].keys():
self._wrapper.create_material(key)
r_list.append(
Expand All @@ -90,7 +91,7 @@ def create(self, model: Material | Layer | Multilayer | Model) -> list[ItemConta
)
)
elif issubclass(t_, Layer):
key = model.uid
key = model.unique_name
if key not in self._wrapper.storage['layer'].keys():
self._wrapper.create_layer(key)
r_list.append(
Expand All @@ -101,9 +102,9 @@ def create(self, model: Material | Layer | Multilayer | Model) -> list[ItemConta
self._wrapper.update_layer,
)
)
self.assign_material_to_layer(model.material.uid, key)
self.assign_material_to_layer(model.material.unique_name, key)
elif issubclass(t_, Multilayer):
key = model.uid
key = model.unique_name
self._wrapper.create_item(key)
r_list.append(
ItemContainer(
Expand All @@ -114,7 +115,7 @@ def create(self, model: Material | Layer | Multilayer | Model) -> list[ItemConta
)
)
for i in model.layers:
self.add_layer_to_item(i.uid, model.uid)
self.add_layer_to_item(i.unique_name, model.unique_name)
elif issubclass(t_, Model):
self._wrapper.create_model()
r_list.append(
Expand All @@ -126,7 +127,7 @@ def create(self, model: Material | Layer | Multilayer | Model) -> list[ItemConta
)
)
for i in model.structure:
self.add_item_to_model(i.uid)
self.add_item_to_model(i.unique_name)
return r_list

def assign_material_to_layer(self, material_id: int, layer_id: int) -> None:
Expand Down
16 changes: 8 additions & 8 deletions src/easyreflectometry/calculators/calculator_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def create(self, model: Material | Layer | Multilayer | Model) -> list[ItemConta
r_list = []
t_ = type(model)
if issubclass(t_, Material):
key = model.uid
key = model.unique_name
if key not in self._wrapper.storage['material'].keys():
self._wrapper.create_material(key)
r_list.append(
Expand All @@ -66,7 +66,7 @@ def create(self, model: Material | Layer | Multilayer | Model) -> list[ItemConta
)
)
elif issubclass(t_, MaterialMixture):
key = model.uid
key = model.unique_name
if key not in self._wrapper.storage['material'].keys():
self._wrapper.create_material(key)
r_list.append(
Expand All @@ -78,7 +78,7 @@ def create(self, model: Material | Layer | Multilayer | Model) -> list[ItemConta
)
)
elif issubclass(t_, Layer):
key = model.uid
key = model.unique_name
if key not in self._wrapper.storage['layer'].keys():
self._wrapper.create_layer(key)
r_list.append(
Expand All @@ -89,9 +89,9 @@ def create(self, model: Material | Layer | Multilayer | Model) -> list[ItemConta
self._wrapper.update_layer,
)
)
self.assign_material_to_layer(model.material.uid, key)
self.assign_material_to_layer(model.material.unique_name, key)
elif issubclass(t_, BaseAssembly):
key = model.uid
key = model.unique_name
self._wrapper.create_item(key)
r_list.append(
ItemContainer(
Expand All @@ -102,9 +102,9 @@ def create(self, model: Material | Layer | Multilayer | Model) -> list[ItemConta
)
)
for i in model.layers:
self.add_layer_to_item(i.uid, model.uid)
self.add_layer_to_item(i.unique_name, model.unique_name)
elif issubclass(t_, Model):
key = model.uid
key = model.unique_name
self._wrapper.create_model(key)
r_list.append(
ItemContainer(
Expand All @@ -115,7 +115,7 @@ def create(self, model: Material | Layer | Multilayer | Model) -> list[ItemConta
)
)
for i in model.sample:
self.add_item_to_model(i.uid, key)
self.add_item_to_model(i.unique_name, key)
return r_list

def assign_material_to_layer(self, material_id: str, layer_id: str) -> None:
Expand Down
3 changes: 2 additions & 1 deletion src/easyreflectometry/calculators/refl1d/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
from typing import Tuple

import numpy as np
from easyreflectometry.experiment.resolution_functions import PercentageFhwm
from refl1d import model
from refl1d import names

from easyreflectometry.experiment.resolution_functions import PercentageFhwm

from ..wrapper_base import WrapperBase

RESOLUTION_PADDING = 3.5
Expand Down
3 changes: 2 additions & 1 deletion src/easyreflectometry/calculators/refnx/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
from typing import Tuple

import numpy as np
from easyreflectometry.experiment.resolution_functions import PercentageFhwm
from refnx import reflect

from easyreflectometry.experiment.resolution_functions import PercentageFhwm

from ..wrapper_base import WrapperBase


Expand Down
47 changes: 29 additions & 18 deletions src/easyreflectometry/experiment/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

__author__ = 'github.com/arm61'

import copy
from numbers import Number
from typing import Union

import numpy as np
import yaml
from easyscience.Objects.new_variable import Parameter
from easyscience.Objects.ObjectClasses import BaseObj
from easyscience.Objects.ObjectClasses import Parameter

from easyreflectometry.parameter_utils import get_as_parameter
from easyreflectometry.sample import BaseAssembly
Expand Down Expand Up @@ -100,7 +101,7 @@ def add_item(self, *assemblies: list[BaseAssembly]) -> None:
if issubclass(arg.__class__, BaseAssembly):
self.sample.append(arg)
if self.interface is not None:
self.interface().add_item_to_model(arg.uid, self.uid)
self.interface().add_item_to_model(arg.unique_name, self.unique_name)
else:
raise ValueError(f'Object {arg} is not a valid type, must be a child of BaseAssembly.')

Expand All @@ -115,8 +116,8 @@ def duplicate_item(self, idx: int) -> None:
duplicate_layers.append(
Layer(
material=i.material,
thickness=i.thickness.raw_value,
roughness=i.roughness.raw_value,
thickness=i.thickness.value,
roughness=i.roughness.value,
name=i.name + ' duplicate',
interface=i.interface,
)
Expand All @@ -132,9 +133,10 @@ def remove_item(self, idx: int) -> None:

:param idx: Index of the item to remove.
"""
if self.interface is not None:
self.interface().remove_item_from_model(self.sample[idx].uid, self.uid)
item_unique_name = self.sample[idx].unique_name
del self.sample[idx]
if self.interface is not None:
self.interface().remove_item_from_model(item_unique_name, self.unique_name)

@property
def resolution_function(self) -> ResolutionFunction:
Expand Down Expand Up @@ -164,11 +166,6 @@ def interface(self, new_interface) -> None:
self.generate_bindings()
self._interface().set_resolution_function(self._resolution_function)

@property
def uid(self) -> int:
"""Return a UID from the borg map."""
return self._borg.map.convert_id_to_key(self)

# Representation
@property
def _dict_repr(self) -> dict[str, dict[str, str]]:
Expand All @@ -181,16 +178,16 @@ def _dict_repr(self) -> dict[str, dict[str, str]]:

return {
self.name: {
'scale': self.scale.raw_value,
'background': self.background.raw_value,
'scale': float(self.scale.value),
'background': float(self.background.value),
'resolution': resolution,
'sample': self.sample._dict_repr,
}
}

def __repr__(self) -> str:
"""String representation of the layer."""
return yaml.dump(self._dict_repr, sort_keys=False)
return yaml.dump(self._dict_repr, sort_keys=False, allow_unicode=True)

def as_dict(self, skip: list = None) -> dict:
"""Produces a cleaned dict using a custom as_dict method to skip necessary things.
Expand All @@ -200,26 +197,40 @@ def as_dict(self, skip: list = None) -> dict:
"""
if skip is None:
skip = []
skip.extend(['sample', 'resolution_function', 'interface'])
this_dict = super().as_dict(skip=skip)
this_dict['sample'] = self.sample.as_dict(skip=skip)
this_dict['resolution_function'] = self.resolution_function.as_dict()
if self.interface is None:
this_dict['interface'] = None
else:
this_dict['interface'] = self.interface().name
return this_dict

@classmethod
def from_dict(cls, this_dict: dict) -> Model:
def from_dict(cls, passed_dict: dict) -> Model:
"""
Create a Model from a dictionary.

:param this_dict: dictionary of the Model
:return: Model
"""
# Causes circular import if imported at the top
from easyreflectometry.calculators import CalculatorFactory

this_dict = copy.deepcopy(passed_dict)
resolution_function = ResolutionFunction.from_dict(this_dict['resolution_function'])
del this_dict['resolution_function']
sample = Sample.from_dict(this_dict['sample'])
del this_dict['sample']
interface_name = this_dict['interface']
del this_dict['interface']
if interface_name is not None:
interface = CalculatorFactory()
interface.switch(interface_name)
else:
interface = None

model = super().from_dict(this_dict)

model.sample = sample
model.resolution_function = resolution_function
model.interface = interface
return model
27 changes: 23 additions & 4 deletions src/easyreflectometry/experiment/model_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

__author__ = 'github.com/arm61'

from typing import List
from typing import Optional

from easyreflectometry.sample.base_element_collection import SIZE_DEFAULT_COLLECTION
Expand All @@ -19,13 +20,20 @@ def __init__(
*models: Optional[tuple[Model]],
name: str = 'EasyModels',
interface=None,
populate_if_none: bool = True,
**kwargs,
):
if not models:
models = [Model(interface=interface) for _ in range(SIZE_DEFAULT_COLLECTION)]
if populate_if_none:
models = [Model(interface=interface) for _ in range(SIZE_DEFAULT_COLLECTION)]
else:
models = []
super().__init__(name, interface, *models, **kwargs)
self.interface = interface

# Needed by the as_dict functionality
self.populate_if_none = False
Copy link
Member

Choose a reason for hiding this comment

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

there's no description of this boolean. Why is it needed in as_dict?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

added code comment:
# Needed to ensure an empty list is created when saving and instatiating the object as_dict -> from_dict
# Else collisions might occur in global_object.map


def add_model(self, new_model: Model):
"""
Add a model to the models.
Expand All @@ -42,6 +50,11 @@ def remove_model(self, idx: int):
"""
del self[idx]

def as_dict(self, skip: List[str] | None = None) -> dict:
this_dict = super().as_dict(skip)
this_dict['populate_if_none'] = self.populate_if_none
return this_dict

@classmethod
def from_dict(cls, this_dict: dict) -> ModelCollection:
"""
Expand All @@ -50,11 +63,17 @@ def from_dict(cls, this_dict: dict) -> ModelCollection:
:param data: The dictionary for the collection
:return: An instance of the collection
"""
collection = super().from_dict(this_dict) # type: ModelCollection
collection_dict = this_dict.copy()
# We neeed to call from_dict on the base class to get the models
dict_data = collection_dict['data']
del collection_dict['data']

collection = super().from_dict(collection_dict) # type: ModelCollection

for model_data in dict_data:
collection.add_model(Model.from_dict(model_data))

if len(collection) != len(this_dict['data']):
raise ValueError(f"Expected {len(collection)} models, got {len(this_dict['data'])}")
for i, model_data in enumerate(this_dict['data']):
collection[i] = Model.from_dict(model_data)

return collection
Loading
Loading