From 825d76e61589a5c12abae2b78efdfbe13b2a060e Mon Sep 17 00:00:00 2001 From: Andreas Pedersen <48797331+andped10@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:54:58 +0200 Subject: [PATCH] Release v0.0.6 (#126) * Fix pp.toml * Remove flake8 with poetry * Bug fix * Remove docs build * changed dependency to fixed easyCore * and now with fixed typos * pin scipp version * pin hatchling, gitignore * py311 tests pass, scipp 23.08 * added python 3.11 to pipelines * explicit packages different from project name * update scipp * revert scipp version * scipp==23.08.0 * set hatchling version * adjust plot to satisfy scipp.plopp * adjustments * no need for ipympl * removed plural s in neutron * scipp update, dataset to datagroup * plot adjustments * code cleaning * copy to ensure new object * pr response * clean output notebook * revert changes to ort files * ort with plural s * removed python 3.8 * clean notebook * revert change in background.bonds * extract docs dependencies, pipeline rename and clean * code with tests * seems to be working * adjust tests * reordering * update tests * multiline import isort * fix codefactor * able to set name * added -dev to version name to make it clear when pip uses a local module * code analysis pipeline * code ql * pipeline job and toml * no dependency install * code cleaning * initial sorting * initial sorting test and docs * user feedback in pipeline * echo syntax * echo step syntax * wrong formatting test * user feedback syntax * user feedback * user feedback * bumped action for checkout and python-version * fix import * bumped codecov * role back codecov to v3 * remove black related * pr response * use ruff rather than flake8 and isort * test error in linting * back to code with no issues * ruff single line rather than isort multiline=3 and grid * single line import * only formatting * single quote and exclude tests * refactored calculator and interface code * pr respnse * ruff fix * pr repsonse * moved items to module * Revert "moved items to module" This reverts commit d2aca10b6bb8f9ef9d960f0a6c6e6c561f9da321. * moved items to module * revert interfaces * revert interfaces * adjust interfaces * folder and file renaming * factory and base * revert interface * revert interface * test cleaning * typo in file name * fix init * added abstract methods * code formatting and abstract methods * chaning from interface to calculator * notebook from interface to calculator * single qoutes * code cleaning * code cleaning * update notebooks * code formatting * code cleaning * code cleaning * clean up typing * Revert "clean up typing" This reverts commit c7034b54601f42cedbbe867c70ea7bd382d39568. * typing update * wrapper base * wrapper cleaning * code cleaning * code cleaning * sample split in elementals and assemblies * base class for elements * code cleaning * note books * from layers to layer_collection * code cleaning * code cleaning * revert only to fix imports * from structure to sample * code cleaning * split up layers and materials * notebooks and bug * typing * typing * merge corrections * fix born againg * change folder to elements and file names for base classes * changed class name from MultiLayer to Multilayer (small caps) * reordering code cleaning * refactoring * code cleaning * bae_assembly * extending base_assembly * delete bilayer * remove overloaded method * code comments * code cleaning * code comment * fix test * change from multilayer to base_assembly * new material solvated * rename chemical_structure to molecular_formula * proctecting instance variables * ruff used in subsection of test * use MaterialSolvated in notebook * adjust tests * added test * pr response typo * pr response handle bottom and top for 0 layers * PR response clarify need for type * changed to Optional typing * added andreas to maintainers * copyright, item to assembly * reordering structure * updates to docs * clean up layer docstring * updates to docs string * docstring * change code comment to instance creation * vairous docs, docstrings and notebooks * docstring * references * code cleaning * ruff * ruff * roll back unsupported (py3.9) typing * docstring cleaning * removing * from docstring * update docs * PR docstrings * fix introduced bug * fix merge * code cleaning * minor clearning * doc cleaning and generate name in solventmaterial * removed solvated from material name * changed to chemical formula * before changing bottom and top * fix tests and notebooks * ruff for tests in sample * code cleaning * specifying parameters in surfactantlayer from pars * pr response * pr response, remove tail and head layer * chemical to molecular in layer_apm * pr response * pr response * pr response * ruff * apm to area per molecule * clean notebooks * code cleaning * last ruff * code cleaning * introduce head and tail layer in surfactant * import correction * change to solvent fraction * prt files, neutron without s * clean notebooks * docs fixes * revert changes that were not needed * code cleaning * pr response * fix notebooks * consistent usage of front (fitst interaction with neutrons) and back layer * docstring correction * pr response * make it clear that sld is set thorigh constraint * code comments * PR response * from easyCore * update have version is determined * pr response * changes from easycore * fix tests * Adjustments to fit the App (#125) * type as property and add_item * dictionaries * tests pass * workflows from core * use custom dict for classes * made constraint calls more informative * code cleaning * code comments and typing * code comment * final adjustments to app * corebase class introduced * PR response, consolidate base class and move base collection classes * pr response * version 0.0.6 --------- Co-authored-by: arm61 Co-authored-by: Andrew McCluskey Co-authored-by: rozyczko --- .github/release-drafter.yml | 34 ++++++ .github/workflows/documentation-build.yml | 17 ++- .../release-drafter-verify-pr-labels.yml | 23 ++++ .github/workflows/release-drafter.yml | 22 ++++ EasyReflectometry/experiment/model.py | 32 ++++- EasyReflectometry/sample/__init__.py | 4 +- .../sample/assemblies/base_assembly.py | 34 ++---- .../sample/assemblies/gradient_layer.py | 11 +- .../sample/assemblies/multilayer.py | 2 +- .../sample/assemblies/repeating_multilayer.py | 2 +- .../sample/assemblies/surfactant_layer.py | 106 +++++++++-------- .../base_element.py => base_core.py} | 24 +++- .../sample/elements/layers/layer.py | 4 +- .../layers/layer_area_per_molecule.py | 60 ++++++---- .../elements/{ => layers}/layer_collection.py | 4 +- .../sample/elements/materials/material.py | 4 +- .../{ => materials}/material_collection.py | 6 +- .../elements/materials/material_density.py | 15 ++- .../elements/materials/material_mixture.py | 98 +++++++--------- .../elements/materials/material_solvated.py | 25 +++- EasyReflectometry/sample/sample.py | 15 ++- pyproject.toml | 2 +- tests/experiment/test_model.py | 29 ++++- .../sample/assemblies/test_gradient_layer.py | 7 ++ tests/sample/assemblies/test_multilayer.py | 3 +- .../assemblies/test_repeating_multilayer.py | 2 +- .../assemblies/test_surfactant_layer.py | 105 +++++++++-------- .../layers/test_layer_area_per_molecule.py | 61 +++++----- .../materials/test_material_mixture.py | 109 ++++++++---------- .../materials/test_material_solvated.py | 35 +++--- .../sample/elements/test_layer_collection.py | 3 +- .../elements/test_material_collection.py | 3 +- tests/sample/test_sample.py | 20 +++- 33 files changed, 555 insertions(+), 366 deletions(-) create mode 100644 .github/release-drafter.yml create mode 100644 .github/workflows/release-drafter-verify-pr-labels.yml create mode 100644 .github/workflows/release-drafter.yml rename EasyReflectometry/sample/{elements/base_element.py => base_core.py} (52%) rename EasyReflectometry/sample/elements/{ => layers}/layer_collection.py (93%) rename EasyReflectometry/sample/elements/{ => materials}/material_collection.py (91%) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000..cc95caf6 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,34 @@ +# Configuration for the realease-drafter workflow. +# https://github.com/marketplace/actions/release-drafter + +name-template: v$NEXT_PATCH_VERSION 🌈 +tag-template: v$NEXT_PATCH_VERSION +categories: +- title: 🚀 Features + labels: + - feature + - enhancement +- title: 🐛 Bug Fixes + labels: + - fix + - bugfix + - bug +- title: 🧰 Maintenance + labels: + - chore + - documentation +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +version-resolver: + major: + labels: + - major + minor: + labels: + - minor + patch: + labels: + - patch + default: patch +template: | + ## Changes + $CHANGES diff --git a/.github/workflows/documentation-build.yml b/.github/workflows/documentation-build.yml index c47951a3..90a0752b 100644 --- a/.github/workflows/documentation-build.yml +++ b/.github/workflows/documentation-build.yml @@ -1,6 +1,6 @@ -# This pipeline -# - builds developer documentation -# - pushes documentation to gh-pages branch of the same repository +# This workflow will +# - build developer documentation +# - push documentation to gh-pages branch of the same repository # # Deployment is handled by pages-build-deployment bot @@ -26,24 +26,23 @@ jobs: uses: actions/checkout@master with: fetch-depth: 0 # otherwise, you will failed to push refs to dest repo - - name: Upgrade pip - run: | - python -m pip install --upgrade pip - name: Set up Python uses: actions/setup-python@v5 with: python-version: 3.9 - - name: Install Pandoc, EasyReflectometry and dependencies + - name: Install Pandoc, repo and dependencies run: | sudo apt install pandoc - pip install . '.[dev,docs]' + pip install . '.[docs]' + - name: Build and Commit uses: sphinx-notes/pages@master with: install_requirements: true - documentation_path: docs + documentation_path: docs/src - name: Push changes uses: ad-m/github-push-action@master continue-on-error: true with: + github_token: ${{ secrets.CORE_TOKEN }} branch: gh-pages diff --git a/.github/workflows/release-drafter-verify-pr-labels.yml b/.github/workflows/release-drafter-verify-pr-labels.yml new file mode 100644 index 00000000..d231b657 --- /dev/null +++ b/.github/workflows/release-drafter-verify-pr-labels.yml @@ -0,0 +1,23 @@ +# This workflow will +# - verify that a PR has a known label before it can be merged. +# +# A label is needed for the release-drafter workflow to generate the notes. + +name: Verify PR labels +on: + pull_request_target: + types: [opened, labeled, unlabeled, synchronize] + +jobs: + check_pr_labels: + runs-on: ubuntu-latest + name: Verify that the PR has a valid label + steps: + - name: Verify PR label action + uses: jesusvasquez333/verify-pr-label-action@v1.4.0 + id: verify-pr-label + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + valid-labels: chore, fix, bugfix, bug, enhancement, feature, dependencies, documentation + pull-request-number: ${{ github.event.pull_request.number }} + disable-reviews: false diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 00000000..2f0a6f49 --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,22 @@ +# This workflow will +# - draft a new release the next time a release is tagged. +# +# Uses the release-drafter.yml configuration file in the .github directory. +# https://github.com/marketplace/actions/release-drafter + + +name: Release Drafter + +on: + push: + tags: + - 'v*' + +jobs: + update_release_draft: + runs-on: ubuntu-latest + steps: + # Drafts your next Release notes as Pull Requests are merged into "main" + - uses: release-drafter/release-drafter@v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/EasyReflectometry/experiment/model.py b/EasyReflectometry/experiment/model.py index 6ff281d2..2483aa03 100644 --- a/EasyReflectometry/experiment/model.py +++ b/EasyReflectometry/experiment/model.py @@ -9,10 +9,9 @@ from easyCore.Objects.ObjectClasses import BaseObj from easyCore.Objects.ObjectClasses import Parameter +from EasyReflectometry.sample import BaseAssembly from EasyReflectometry.sample import Layer from EasyReflectometry.sample import LayerCollection -from EasyReflectometry.sample import Multilayer -from EasyReflectometry.sample import RepeatingMultilayer from EasyReflectometry.sample import Sample LAYER_DETAILS = { @@ -48,6 +47,13 @@ class Model(BaseObj): It is used to store the information about the experiment and to perform the calculations. """ + # Added in super().__init__ + name: str + sample: Sample + scale: Parameter + background: Parameter + resolution: Parameter + def __init__( self, sample: Sample, @@ -126,16 +132,18 @@ def from_pars( interface=interface, ) - def add_item(self, *items: Layer | RepeatingMultilayer) -> None: + def add_item(self, *assemblies: list[BaseAssembly]) -> None: """Add a layer or item to the model sample. - :param items: Layers or items to add to model sample. + :param assemblies: Assemblies to add to model sample. """ - for arg in items: - if issubclass(arg.__class__, Multilayer): + for arg in assemblies: + 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) + else: + raise ValueError(f'Object {arg} is not a valid type, must be a child of BaseAssembly.') def duplicate_item(self, idx: int) -> None: """Duplicate a given item or layer in a sample. @@ -189,3 +197,15 @@ def _dict_repr(self) -> dict[str, dict[str, str]]: def __repr__(self) -> str: """String representation of the layer.""" return yaml.dump(self._dict_repr, sort_keys=False) + + def as_dict(self, skip: list = None) -> dict: + """Produces a cleaned dict using a custom as_dict method to skip necessary things. + The resulting dict matches the paramters in __init__ + + :param skip: List of keys to skip, defaults to `None`. + """ + if skip is None: + skip = [] + this_dict = super().as_dict(skip=skip) + this_dict['sample'] = self.sample.as_dict() + return this_dict diff --git a/EasyReflectometry/sample/__init__.py b/EasyReflectometry/sample/__init__.py index 016f5b68..39e525f1 100644 --- a/EasyReflectometry/sample/__init__.py +++ b/EasyReflectometry/sample/__init__.py @@ -3,11 +3,11 @@ from .assemblies.multilayer import Multilayer from .assemblies.repeating_multilayer import RepeatingMultilayer from .assemblies.surfactant_layer import SurfactantLayer -from .elements.layer_collection import LayerCollection from .elements.layers.layer import Layer from .elements.layers.layer_area_per_molecule import LayerAreaPerMolecule -from .elements.material_collection import MaterialCollection +from .elements.layers.layer_collection import LayerCollection from .elements.materials.material import Material +from .elements.materials.material_collection import MaterialCollection from .elements.materials.material_mixture import MaterialMixture from .elements.materials.material_solvated import MaterialSolvated from .sample import Sample diff --git a/EasyReflectometry/sample/assemblies/base_assembly.py b/EasyReflectometry/sample/assemblies/base_assembly.py index 9aa5c988..387921b1 100644 --- a/EasyReflectometry/sample/assemblies/base_assembly.py +++ b/EasyReflectometry/sample/assemblies/base_assembly.py @@ -1,16 +1,14 @@ -from abc import abstractmethod from typing import Any from typing import Optional -import yaml from easyCore.Fitting.Constraints import ObjConstraint -from easyCore.Objects.ObjectClasses import BaseObj -from ..elements.layer_collection import LayerCollection +from ..base_core import BaseCore from ..elements.layers.layer import Layer +from ..elements.layers.layer_collection import LayerCollection -class BaseAssembly(BaseObj): +class BaseAssembly(BaseCore): """Assembly of layers. The front layer (front_layer) is the layer the neutron beam starts in, it has an index of 0. The back layer (back_layer) is the final layer from which the unreflected neutron beam is transmitted, @@ -32,31 +30,19 @@ def __init__( interface, **layers: LayerCollection, ): - super().__init__(name=name, **layers) + super().__init__(name=name, interface=interface, **layers) - # Updates interface using property in base object - self.interface = interface # Type is needed when fitting in EasyCore self._type = type self._roughness_constraints_setup = False self._thickness_constraints_setup = False - @abstractmethod - def default(cls, interface=None) -> Any: - ... - - @abstractmethod - def _dict_repr(self) -> dict[str, str]: - ... - @property - def uid(self) -> int: - """Get UID from the borg map""" - return self._borg.map.convert_id_to_key(self) - - def __repr__(self) -> str: - """String representation of the object.""" - return yaml.dump(self._dict_repr, sort_keys=False) + def type(self) -> str: + """Get type of the assembly. + Needed by the GUI. + """ + return self._type @property def front_layer(self) -> Optional[Layer]: @@ -77,7 +63,7 @@ def front_layer(self, layer: Layer) -> None: self.layers[0] = layer @property - def back_layer(self) -> Optional[None]: + def back_layer(self) -> Optional[Layer]: """Get the back layer in the assembly.""" if len(self.layers) < 2: diff --git a/EasyReflectometry/sample/assemblies/gradient_layer.py b/EasyReflectometry/sample/assemblies/gradient_layer.py index 0221dc51..f3ba6045 100644 --- a/EasyReflectometry/sample/assemblies/gradient_layer.py +++ b/EasyReflectometry/sample/assemblies/gradient_layer.py @@ -2,8 +2,8 @@ from numpy import arange -from ..elements.layer_collection import LayerCollection from ..elements.layers.layer import Layer +from ..elements.layers.layer_collection import LayerCollection from ..elements.materials.material import Material from .base_assembly import BaseAssembly @@ -153,12 +153,13 @@ def _dict_repr(self) -> dict[str, str]: } def as_dict(self, skip: list = None) -> dict: + """Produces a cleaned dict using a custom as_dict method to skip necessary things. + The resulting dict matches the paramters in __init__ + + :param skip: List of keys to skip, defaults to `None`. """ - Cleaned dictionary. Custom as_dict method to skip generated layers. - """ - if skip is None: - skip = [] this_dict = super().as_dict(skip=skip) + # Determined in __init__ del this_dict['layers'] return this_dict diff --git a/EasyReflectometry/sample/assemblies/multilayer.py b/EasyReflectometry/sample/assemblies/multilayer.py index 2e531a37..c519ab9b 100644 --- a/EasyReflectometry/sample/assemblies/multilayer.py +++ b/EasyReflectometry/sample/assemblies/multilayer.py @@ -1,7 +1,7 @@ from __future__ import annotations -from ..elements.layer_collection import LayerCollection from ..elements.layers.layer import Layer +from ..elements.layers.layer_collection import LayerCollection from .base_assembly import BaseAssembly diff --git a/EasyReflectometry/sample/assemblies/repeating_multilayer.py b/EasyReflectometry/sample/assemblies/repeating_multilayer.py index 287bd20f..e10d5c27 100644 --- a/EasyReflectometry/sample/assemblies/repeating_multilayer.py +++ b/EasyReflectometry/sample/assemblies/repeating_multilayer.py @@ -4,8 +4,8 @@ from easyCore.Objects.ObjectClasses import Parameter -from ..elements.layer_collection import LayerCollection from ..elements.layers.layer import Layer +from ..elements.layers.layer_collection import LayerCollection from .multilayer import Multilayer REPEATINGMULTILAYER_DETAILS = { diff --git a/EasyReflectometry/sample/assemblies/surfactant_layer.py b/EasyReflectometry/sample/assemblies/surfactant_layer.py index 142dc5fe..e8e24acd 100644 --- a/EasyReflectometry/sample/assemblies/surfactant_layer.py +++ b/EasyReflectometry/sample/assemblies/surfactant_layer.py @@ -5,16 +5,16 @@ from easyCore.Fitting.Constraints import ObjConstraint from easyCore.Objects.ObjectClasses import Parameter -from ..elements.layer_collection import LayerCollection from ..elements.layers.layer_area_per_molecule import LayerAreaPerMolecule +from ..elements.layers.layer_collection import LayerCollection from ..elements.materials.material import Material from .base_assembly import BaseAssembly class SurfactantLayer(BaseAssembly): """A surfactant layer constructs a series of layers representing the - head and tail groups of a surfactant. - This assembly allows the definition of a surfactant or lipid using the chemistry + head and tail groups of a surfactant. + This assembly allows the definition of a surfactant or lipid using the chemistry of the head (head_layer) and tail (tail_layer) regions, additionally this approach will make the application of constraints such as conformal roughness or area per molecule more straight forward. @@ -50,14 +50,14 @@ def __init__( ) self.interface = interface - self.head_layer.area_per_molecule.enabled = True + self.head_layer._area_per_molecule.enabled = True area_per_molecule = ObjConstraint( - dependent_obj=self.head_layer.area_per_molecule, + dependent_obj=self.head_layer._area_per_molecule, operator='', - independent_obj=self.tail_layer.area_per_molecule, + independent_obj=self.tail_layer._area_per_molecule, ) - self.tail_layer.area_per_molecule.user_constraints['area_per_molecule'] = area_per_molecule - self.tail_layer.area_per_molecule.user_constraints['area_per_molecule'].enabled = constrain_area_per_molecule + self.tail_layer._area_per_molecule.user_constraints['area_per_molecule'] = area_per_molecule + self.tail_layer._area_per_molecule.user_constraints['area_per_molecule'].enabled = constrain_area_per_molecule self._setup_roughness_constraints() if conformal_roughness: @@ -91,7 +91,7 @@ def default(cls, interface=None) -> SurfactantLayer: roughness=3.0, name='DPPC Head', ) - return cls([tail, head], name='DPPC', interface=interface) + return cls([tail, head], interface=interface) @classmethod def from_pars( @@ -171,7 +171,7 @@ def head_layer(self, layer: LayerAreaPerMolecule) -> None: @property def constrain_area_per_molecule(self) -> bool: """Get the area per molecule constraint status.""" - return self.tail_layer.area_per_molecule.user_constraints['area_per_molecule'].enabled + return self.tail_layer._area_per_molecule.user_constraints['area_per_molecule'].enabled @constrain_area_per_molecule.setter def constrain_area_per_molecule(self, status: bool): @@ -180,8 +180,8 @@ def constrain_area_per_molecule(self, status: bool): :param x: Boolean description the wanted of the constraint. """ - self.tail_layer.area_per_molecule.user_constraints['area_per_molecule'].enabled = status - self.tail_layer.area_per_molecule.value = self.tail_layer.area_per_molecule.raw_value + self.tail_layer._area_per_molecule.user_constraints['area_per_molecule'].enabled = status + self.tail_layer._area_per_molecule.value = self.tail_layer._area_per_molecule.raw_value @property def conformal_roughness(self) -> bool: @@ -224,66 +224,78 @@ def constain_multiple_contrast( """ if head_layer_thickness: head_layer_thickness_constraint = ObjConstraint( - self.head_layer.thickness, '', another_contrast.head_layer.thickness + dependent_obj=self.head_layer.thickness, + operator='', + independent_obj=another_contrast.head_layer.thickness, + ) + another_contrast.head_layer.thickness.user_constraints[f'{another_contrast.name}'] = ( + head_layer_thickness_constraint ) - another_contrast.head_layer.thickness.user_constraints[ - f'{another_contrast.name}' - ] = head_layer_thickness_constraint if tail_layer_thickness: tail_layer_thickness_constraint = ObjConstraint( - self.tail_layer.thickness, '', another_contrast.tail_layer.thickness + dependent_obj=self.tail_layer.thickness, operator='', independent_obj=another_contrast.tail_layer.thickness + ) + another_contrast.tail_layer.thickness.user_constraints[f'{another_contrast.name}'] = ( + tail_layer_thickness_constraint ) - another_contrast.tail_layer.thickness.user_constraints[ - f'{another_contrast.name}' - ] = tail_layer_thickness_constraint if head_layer_area_per_molecule: head_layer_area_per_molecule_constraint = ObjConstraint( - self.head_layer.area_per_molecule, '', another_contrast.head_layer.area_per_molecule + dependent_obj=self.head_layer._area_per_molecule, + operator='', + independent_obj=another_contrast.head_layer._area_per_molecule, + ) + another_contrast.head_layer._area_per_molecule.user_constraints[f'{another_contrast.name}'] = ( + head_layer_area_per_molecule_constraint ) - another_contrast.head_layer.area_per_molecule.user_constraints[ - f'{another_contrast.name}' - ] = head_layer_area_per_molecule_constraint if tail_layer_area_per_molecule: tail_layer_area_per_molecule_constraint = ObjConstraint( - self.tail_layer.area_per_molecule, '', another_contrast.tail_layer.area_per_molecule + dependent_obj=self.tail_layer._area_per_molecule, + operator='', + independent_obj=another_contrast.tail_layer._area_per_molecule, + ) + another_contrast.tail_layer._area_per_molecule.user_constraints[f'{another_contrast.name}'] = ( + tail_layer_area_per_molecule_constraint ) - another_contrast.tail_layer.area_per_molecule.user_constraints[ - f'{another_contrast.name}' - ] = tail_layer_area_per_molecule_constraint if head_layer_fraction: head_layer_fraction_constraint = ObjConstraint( - self.head_layer.material.fraction, '', another_contrast.head_layer.material.fraction + dependent_obj=self.head_layer.material._fraction, + operator='', + independent_obj=another_contrast.head_layer.material._fraction, + ) + another_contrast.head_layer.material._fraction.user_constraints[f'{another_contrast.name}'] = ( + head_layer_fraction_constraint ) - another_contrast.head_layer.material.fraction.user_constraints[ - f'{another_contrast.name}' - ] = head_layer_fraction_constraint if tail_layer_fraction: tail_layer_fraction_constraint = ObjConstraint( - self.tail_layer.material.fraction, '', another_contrast.tail_layer.material.fraction + dependent_obj=self.tail_layer.material._fraction, + operator='', + independent_obj=another_contrast.tail_layer.material._fraction, + ) + another_contrast.tail_layer.material._fraction.user_constraints[f'{another_contrast.name}'] = ( + tail_layer_fraction_constraint ) - another_contrast.tail_layer.material.fraction.user_constraints[ - f'{another_contrast.name}' - ] = tail_layer_fraction_constraint @property def _dict_repr(self) -> dict: """A simplified dict representation.""" return { - 'head_layer': self.head_layer._dict_repr, - 'tail_layer': self.tail_layer._dict_repr, - 'area per molecule constrained': self.constrain_area_per_molecule, - 'conformal roughness': self.conformal_roughness, + self.name: { + 'head_layer': self.head_layer._dict_repr, + 'tail_layer': self.tail_layer._dict_repr, + 'area per molecule constrained': self.constrain_area_per_molecule, + 'conformal roughness': self.conformal_roughness, + } } def as_dict(self, skip: list = None) -> dict: - """Cleaned dictionary. Custom as_dict method to skip necessary things.""" - if skip is None: - skip = [] + """Produces a cleaned dict using a custom as_dict method to skip necessary things. + The resulting dict matches the paramters in __init__ + + :param skip: List of keys to skip, defaults to `None`. + """ this_dict = super().as_dict(skip=skip) - for i in this_dict['layers']['data']: - del i['material'] - del i['_scattering_length_real'] - del i['_scattering_length_imag'] + this_dict['layers']['data'][0] = self.tail_layer.as_dict() + this_dict['layers']['data'][1] = self.head_layer.as_dict() this_dict['constrain_area_per_molecule'] = self.constrain_area_per_molecule this_dict['conformal_roughness'] = self.conformal_roughness return this_dict diff --git a/EasyReflectometry/sample/elements/base_element.py b/EasyReflectometry/sample/base_core.py similarity index 52% rename from EasyReflectometry/sample/elements/base_element.py rename to EasyReflectometry/sample/base_core.py index fc6c8580..978044d6 100644 --- a/EasyReflectometry/sample/elements/base_element.py +++ b/EasyReflectometry/sample/base_core.py @@ -5,7 +5,7 @@ from easyCore.Objects.ObjectClasses import BaseObj -class BaseElement(BaseObj): +class BaseCore(BaseObj): def __init__( self, name: str, @@ -13,15 +13,15 @@ def __init__( **kwargs, ): super().__init__(name=name, **kwargs) + + # Updates interface using property in base object self.interface = interface @abstractmethod - def default(cls, interface=None) -> Any: - ... + def default(cls, interface=None) -> Any: ... @abstractmethod - def _dict_repr(self) -> dict[str, str]: - ... + def _dict_repr(self) -> dict[str, str]: ... @property def uid(self) -> int: @@ -38,3 +38,17 @@ def __repr__(self) -> str: :rtype: str """ return yaml.dump(self._dict_repr, sort_keys=False) + + # For classes with special serialization needs one must adopt the dict produced by super + # def as_dict(self, skip: list = None) -> dict: + # """Should produce a cleaned dict that matches the paramters in __init__ + # + # :param skip: List of keys to skip, defaults to `None`. + # """ + # if skip is None: + # skip = [] + # this_dict = super().as_dict(skip=skip) + # ... + # Correct the dict here + # ... + # return this_dict diff --git a/EasyReflectometry/sample/elements/layers/layer.py b/EasyReflectometry/sample/elements/layers/layer.py index 2bad0134..5ea4dca8 100644 --- a/EasyReflectometry/sample/elements/layers/layer.py +++ b/EasyReflectometry/sample/elements/layers/layer.py @@ -7,7 +7,7 @@ from easyCore import np from easyCore.Objects.ObjectClasses import Parameter -from ..base_element import BaseElement +from ...base_core import BaseCore from ..materials.material import Material LAYER_DETAILS = { @@ -32,7 +32,7 @@ } -class Layer(BaseElement): +class Layer(BaseCore): # Added in super().__init__ #: Material that makes up the layer. material: Material diff --git a/EasyReflectometry/sample/elements/layers/layer_area_per_molecule.py b/EasyReflectometry/sample/elements/layers/layer_area_per_molecule.py index 8d5c0eca..769c0db0 100644 --- a/EasyReflectometry/sample/elements/layers/layer_area_per_molecule.py +++ b/EasyReflectometry/sample/elements/layers/layer_area_per_molecule.py @@ -71,7 +71,7 @@ def __init__( thickness: Parameter, solvent: Material, solvent_fraction: Parameter, - area_per_molecule: Parameter, + area_per_molecule: float, roughness: Parameter, name: str = 'EasyLayerAreaPerMolecule', interface=None, @@ -94,28 +94,32 @@ def __init__( default_options = deepcopy(LAYER_AREA_PER_MOLECULE_DETAILS) del default_options['sl']['value'] del default_options['isl']['value'] - scattering_length_real = Parameter('scattering_length_real', 0.0, **default_options['sl']) - scattering_length_imag = Parameter('scattering_length_imag', 0.0, **default_options['isl']) + del default_options['area_per_molecule']['value'] + + # Will be added as components later in the init + _scattering_length_real = Parameter('scattering_length_real', 0.0, **default_options['sl']) + _scattering_length_imag = Parameter('scattering_length_imag', 0.0, **default_options['isl']) + _area_per_molecule = Parameter('area_per_molecule', area_per_molecule, **default_options['area_per_molecule']) # Constrain the real part of the sld value for the molecule constraint_sld_real = FunctionalConstraint( dependent_obj=molecule.sld, func=area_per_molecule_to_scattering_length_density, - independent_objs=[scattering_length_real, thickness, area_per_molecule], + independent_objs=[_scattering_length_real, thickness, _area_per_molecule], ) thickness.user_constraints['area_per_molecule'] = constraint_sld_real - area_per_molecule.user_constraints['area_per_molecule'] = constraint_sld_real - scattering_length_real.user_constraints['area_per_molecule'] = constraint_sld_real + _area_per_molecule.user_constraints['area_per_molecule'] = constraint_sld_real + _scattering_length_real.user_constraints['area_per_molecule'] = constraint_sld_real # Constrain the imaginary part of the sld value for the molecule constraint_sld_imag = FunctionalConstraint( dependent_obj=molecule.isld, func=area_per_molecule_to_scattering_length_density, - independent_objs=[scattering_length_imag, thickness, area_per_molecule], + independent_objs=[_scattering_length_imag, thickness, _area_per_molecule], ) thickness.user_constraints['iarea_per_molecule'] = constraint_sld_imag - area_per_molecule.user_constraints['iarea_per_molecule'] = constraint_sld_imag - scattering_length_imag.user_constraints['iarea_per_molecule'] = constraint_sld_imag + _area_per_molecule.user_constraints['iarea_per_molecule'] = constraint_sld_imag + _scattering_length_imag.user_constraints['iarea_per_molecule'] = constraint_sld_imag solvated_molecule = MaterialSolvated( material=molecule, @@ -130,9 +134,9 @@ def __init__( name=name, interface=interface, ) - self._add_component('_scattering_length_real', scattering_length_real) - self._add_component('_scattering_length_imag', scattering_length_imag) - self._add_component('_area_per_molecule', area_per_molecule) + self._add_component('_scattering_length_real', _scattering_length_real) + self._add_component('_scattering_length_imag', _scattering_length_imag) + self._add_component('_area_per_molecule', _area_per_molecule) scattering_length = neutron_scattering_length(molecular_formula) self._scattering_length_real.value = scattering_length.real @@ -147,7 +151,7 @@ def default(cls, interface=None) -> LayerAreaPerMolecule: :param interface: Calculator interface, defaults to `None`. """ - area_per_molecule = Parameter('area_per_molecule', **LAYER_AREA_PER_MOLECULE_DETAILS['area_per_molecule']) + area_per_molecule = LAYER_AREA_PER_MOLECULE_DETAILS['area_per_molecule']['value'] thickness = Parameter('thickness', **LAYER_AREA_PER_MOLECULE_DETAILS['thickness']) roughness = Parameter('roughness', **LAYER_AREA_PER_MOLECULE_DETAILS['roughness']) solvent_fraction = Parameter('solvent_fraction', **LAYER_AREA_PER_MOLECULE_DETAILS['solvent_fraction']) @@ -186,8 +190,6 @@ def from_pars( :param interface: Calculator interface, defaults to `None`. """ default_options = deepcopy(LAYER_AREA_PER_MOLECULE_DETAILS) - del default_options['area_per_molecule']['value'] - area_per_molecule = Parameter('area_per_molecule', area_per_molecule, **default_options['area_per_molecule']) del default_options['thickness']['value'] thickness = Parameter('thickness', thickness, **default_options['thickness']) del default_options['roughness']['value'] @@ -207,9 +209,19 @@ def from_pars( ) @property - def area_per_molecule(self) -> Parameter: + def area_per_molecule(self) -> float: """Get the area per molecule.""" - return self._area_per_molecule + return self._area_per_molecule.raw_value + + @area_per_molecule.setter + def area_per_molecule(self, new_area_per_molecule: float) -> None: + """Set the area per molecule. + + :param new_area_per_molecule: New area per molecule. + """ + if new_area_per_molecule < 0: + raise ValueError('new_area_per_molecule must be greater than 0.0.') + self._area_per_molecule.value = new_area_per_molecule @property def molecule(self) -> Material: @@ -230,7 +242,7 @@ def solvent(self, new_solvent: Material) -> None: self.material.solvent = new_solvent @property - def solvent_fraction(self) -> Parameter: + def solvent_fraction(self) -> float: """Get the fraction of the layer occupied by the solvent. This could be a result of either water solvating the molecule, or incomplete surface coverage of the molecules. """ @@ -267,20 +279,20 @@ def molecular_formula(self, formula_string: str) -> None: @property def _dict_repr(self) -> dict[str, str]: - """Dictionary representation of the :py:class:`Layerarea_per_molecule` object. Produces a simple dictionary""" + """Dictionary representation of the `area_per_molecule` object. Produces a simple dictionary""" dict_repr = super()._dict_repr dict_repr['molecular_formula'] = self._molecular_formula - dict_repr['area_per_molecule'] = f'{self.area_per_molecule.raw_value:.2f} ' f'{self.area_per_molecule.unit}' + dict_repr['area_per_molecule'] = f'{self.area_per_molecule:.2f} ' f'{self._area_per_molecule.unit}' return dict_repr def as_dict(self, skip: list = None) -> dict[str, str]: - """Produces a cleaned using a austom as_dict method to skip necessary things. + """Produces a cleaned dict using a custom as_dict method to skip necessary things. + The resulting dict matches the paramters in __init__ - :param skip: List of keys to skip, defaults to :py:attr:`None`. + :param skip: List of keys to skip, defaults to `None`. """ - if skip is None: - skip = [] this_dict = super().as_dict(skip=skip) + this_dict['solvent_fraction'] = self.material._fraction.as_dict() del this_dict['material'] del this_dict['_scattering_length_real'] del this_dict['_scattering_length_imag'] diff --git a/EasyReflectometry/sample/elements/layer_collection.py b/EasyReflectometry/sample/elements/layers/layer_collection.py similarity index 93% rename from EasyReflectometry/sample/elements/layer_collection.py rename to EasyReflectometry/sample/elements/layers/layer_collection.py index 0f0fba74..2465d9a3 100644 --- a/EasyReflectometry/sample/elements/layer_collection.py +++ b/EasyReflectometry/sample/elements/layers/layer_collection.py @@ -2,8 +2,8 @@ __author__ = 'github.com/arm61' -from .base_element_collection import BaseElementCollection -from .layers.layer import Layer +from ..base_element_collection import BaseElementCollection +from .layer import Layer class LayerCollection(BaseElementCollection): diff --git a/EasyReflectometry/sample/elements/materials/material.py b/EasyReflectometry/sample/elements/materials/material.py index 0df85c37..8b274762 100644 --- a/EasyReflectometry/sample/elements/materials/material.py +++ b/EasyReflectometry/sample/elements/materials/material.py @@ -8,7 +8,7 @@ from easyCore import np from easyCore.Objects.ObjectClasses import Parameter -from ..base_element import BaseElement +from ...base_core import BaseCore MATERIAL_DEFAULTS = { 'sld': { @@ -32,7 +32,7 @@ } -class Material(BaseElement): +class Material(BaseCore): # Added in super().__init__ sld: ClassVar[Parameter] isld: ClassVar[Parameter] diff --git a/EasyReflectometry/sample/elements/material_collection.py b/EasyReflectometry/sample/elements/materials/material_collection.py similarity index 91% rename from EasyReflectometry/sample/elements/material_collection.py rename to EasyReflectometry/sample/elements/materials/material_collection.py index 85304e80..70564258 100644 --- a/EasyReflectometry/sample/elements/material_collection.py +++ b/EasyReflectometry/sample/elements/materials/material_collection.py @@ -2,9 +2,9 @@ __author__ = 'github.com/arm61' -from .base_element_collection import BaseElementCollection -from .materials.material import Material -from .materials.material_mixture import MaterialMixture +from ..base_element_collection import BaseElementCollection +from .material import Material +from .material_mixture import MaterialMixture class MaterialCollection(BaseElementCollection): diff --git a/EasyReflectometry/sample/elements/materials/material_density.py b/EasyReflectometry/sample/elements/materials/material_density.py index a94191d8..388cc5f1 100644 --- a/EasyReflectometry/sample/elements/materials/material_density.py +++ b/EasyReflectometry/sample/elements/materials/material_density.py @@ -174,8 +174,17 @@ def _dict_repr(self) -> dict[str, str]: return mat_dict def as_dict(self, skip: list = []) -> dict[str, str]: - """Custom as_dict method to skip necessary things.""" + """Produces a cleaned dict using a custom as_dict method to skip necessary things. + The resulting dict matches the paramters in __init__ + + :param skip: List of keys to skip, defaults to `None`. + """ this_dict = super().as_dict(skip=skip) - del this_dict['sld'], this_dict['isld'], this_dict['scattering_length_real'] - del this_dict['scattering_length_imag'], this_dict['molecular_weight'] + # From Material + del this_dict['sld'] + del this_dict['isld'] + # Determined in __init__ + del this_dict['scattering_length_real'] + del this_dict['scattering_length_imag'] + del this_dict['molecular_weight'] return this_dict diff --git a/EasyReflectometry/sample/elements/materials/material_mixture.py b/EasyReflectometry/sample/elements/materials/material_mixture.py index 4c4b6ca3..a26eedfa 100644 --- a/EasyReflectometry/sample/elements/materials/material_mixture.py +++ b/EasyReflectometry/sample/elements/materials/material_mixture.py @@ -8,7 +8,7 @@ from EasyReflectometry.special.calculations import weighted_average -from ..base_element import BaseElement +from ...base_core import BaseCore from .material import MATERIAL_DEFAULTS from .material import Material @@ -24,7 +24,7 @@ } -class MaterialMixture(BaseElement): +class MaterialMixture(BaseCore): # Added in super().__init__ _material_a: Material _material_b: Material @@ -44,27 +44,17 @@ def __init__( :param material_b: The second material. :param fraction: The fraction of material_b in material_a. :param name: Name of the material, defaults to None that causes the name to be constructed. - :param interface: Calculator interface, defaults to :py:attr:`None`. + :param interface: Calculator interface, defaults to `None`. """ - super().__init__( - name, - _material_a=material_a, - _material_b=material_b, - _fraction=fraction, - interface=interface, - ) - if name is None: - self._update_name() - sld = weighted_average( - a=self._material_a.sld.raw_value, - b=self._material_b.sld.raw_value, - p=self._fraction.raw_value, + a=material_a.sld.raw_value, + b=material_b.sld.raw_value, + p=fraction.raw_value, ) isld = weighted_average( - a=self._material_a.isld.raw_value, - b=self._material_b.isld.raw_value, - p=self._fraction.raw_value, + a=material_a.isld.raw_value, + b=material_b.isld.raw_value, + p=fraction.raw_value, ) default_options = deepcopy(MATERIAL_DEFAULTS) del default_options['sld']['value'] @@ -73,6 +63,18 @@ def __init__( self._sld = Parameter('sld', sld, **default_options['sld']) self._isld = Parameter('isld', isld, **default_options['isld']) + # To avoid problems when setting the interface + # self._sld and self._isld need to be declared before calling the super constructor + super().__init__( + name, + _material_a=material_a, + _material_b=material_b, + _fraction=fraction, + interface=interface, + ) + if name is None: + self._update_name() + self._materials_constraints() self.interface = interface @@ -105,7 +107,7 @@ def from_pars( :param material_b: The second material. :param fraction: The fraction of material_b in material_a. :param name: Name of the material, defaults to 'EasyMaterialMixture'. - :param interface: Calculator interface, defaults to :py:attr:`None`. + :param interface: Calculator interface, defaults to `None`. """ default_options = deepcopy(MATERIALMIXTURE_DEFAULTS) del default_options['fraction']['value'] @@ -123,12 +125,12 @@ def _get_linkable_attributes(self): return [self._sld, self._isld] @property - def sld(self): - return self._sld + def sld(self) -> float: + return self._sld.raw_value @property - def isld(self): - return self._isld + def isld(self) -> float: + return self._isld.raw_value def _materials_constraints(self): self._sld.enabled = True @@ -153,40 +155,19 @@ def _materials_constraints(self): iconstraint() @property - def fraction(self) -> Parameter: - """ - :return: the fraction of material a. - """ - return self._fraction + def fraction(self) -> float: + """Get the fraction of material_b.""" + return self._fraction.raw_value @fraction.setter def fraction(self, fraction: float) -> None: - """ - Setter for fraction of material a. - - :param fraction: double - """ - if not isinstance(fraction, float): - raise ValueError('fraction must be a float') - self._fraction = fraction + """Setter for fraction of material_b. - @property - def fraction(self) -> Parameter: - """ - :return: the fraction of material a. - """ - return self._fraction - - @fraction.setter - def fraction(self, fraction: float) -> None: - """ - Setter for fraction of material a. - - :param fraction: double + :param fraction: The fraction of material_b in material_a. """ if not isinstance(fraction, float): raise ValueError('fraction must be a float') - self._fraction = fraction + self._fraction.raw_value = fraction @property def material_a(self) -> Material: @@ -231,7 +212,7 @@ def _dict_repr(self) -> dict[str, str]: """A simplified dict representation.""" return { self.name: { - 'fraction': self._fraction.raw_value, + 'fraction': f'{self._fraction.raw_value:.3f} {self._fraction.unit}', 'sld': f'{self._sld.raw_value:.3f}e-6 {self._sld.unit}', 'isld': f'{self._isld.raw_value:.3f}e-6 {self._isld.unit}', 'material_a': self._material_a._dict_repr, @@ -240,10 +221,13 @@ def _dict_repr(self) -> dict[str, str]: } def as_dict(self, skip: list = None) -> dict[str, str]: - """Custom as_dict method to skip necessary things.""" - if skip is None: - skip = [] + """Produces a cleaned dict using a custom as_dict method to skip necessary things. + The resulting dict matches the paramters in __init__ + + :param skip: List of keys to skip, defaults to `None`. + """ this_dict = super().as_dict(skip=skip) - this_dict['material_a'] = self._material_a - this_dict['material_b'] = self._material_b + this_dict['material_a'] = self._material_a.as_dict() + this_dict['material_b'] = self._material_b.as_dict() + this_dict['fraction'] = self._fraction.as_dict() return this_dict diff --git a/EasyReflectometry/sample/elements/materials/material_solvated.py b/EasyReflectometry/sample/elements/materials/material_solvated.py index 59a31c38..7a77ce40 100644 --- a/EasyReflectometry/sample/elements/materials/material_solvated.py +++ b/EasyReflectometry/sample/elements/materials/material_solvated.py @@ -120,7 +120,7 @@ def solvent(self, new_solvent: Material) -> None: self.material_b = new_solvent @property - def solvent_fraction(self) -> Parameter: + def solvent_fraction(self) -> float: """Get the fraction of layer described by the solvent. This might be fraction of: Solvation where solvent is within the layer @@ -153,10 +153,31 @@ def _dict_repr(self) -> dict[str, str]: """A simplified dict representation.""" return { self.name: { - 'solvent_fraction': self.solvent_fraction.raw_value, + 'solvent_fraction': f'{self._fraction.raw_value:.3f} {self._fraction.unit}', 'sld': f'{self._sld.raw_value:.3f}e-6 {self._sld.unit}', 'isld': f'{self._isld.raw_value:.3f}e-6 {self._isld.unit}', 'material': self.material._dict_repr, 'solvent': self.solvent._dict_repr, } } + + def as_dict(self, skip: list = None) -> dict[str, str]: + """Produces a cleaned dict using a custom as_dict method to skip necessary things. + The resulting dict matches the paramters in __init__ + + :param skip: List of keys to skip, defaults to `None`. + """ + this_dict = super().as_dict(skip=skip) + this_dict['material'] = self.material.as_dict() + this_dict['solvent'] = self.solvent.as_dict() + this_dict['solvent_fraction'] = self._fraction.as_dict() + # Property and protected varible from material_mixture + del this_dict['material_a'] + del this_dict['_material_a'] + # Property and protected varible from material_mixture + del this_dict['material_b'] + del this_dict['_material_b'] + # Property and protected varible from material_mixture + del this_dict['fraction'] + del this_dict['_fraction'] + return this_dict diff --git a/EasyReflectometry/sample/sample.py b/EasyReflectometry/sample/sample.py index 3797bdac..286acbaa 100644 --- a/EasyReflectometry/sample/sample.py +++ b/EasyReflectometry/sample/sample.py @@ -76,5 +76,18 @@ def _dict_repr(self) -> dict: return {self.name: [i._dict_repr for i in self]} def __repr__(self) -> str: - """String representation of the layer.""" + """String representation of the sample.""" return yaml.dump(self._dict_repr, sort_keys=False) + + def as_dict(self, skip: list = None) -> dict: + """Produces a cleaned dict using a custom as_dict method to skip necessary things. + The resulting dict matches the paramters in __init__ + + :param skip: List of keys to skip, defaults to `None`. + """ + if skip is None: + skip = [] + this_dict = super().as_dict(skip=skip) + for i, layer in enumerate(self.data): + this_dict['data'][i] = layer.as_dict() + return this_dict diff --git a/pyproject.toml b/pyproject.toml index 82f7ea29..9f02a614 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "EasyReflectometryLib" -version = "0.0.5" +version = "0.0.6" description = "A reflectometry python package built on the EasyScience framework." readme = "README.rst" authors = [ diff --git a/tests/experiment/test_model.py b/tests/experiment/test_model.py index 92b69720..a9912fdd 100644 --- a/tests/experiment/test_model.py +++ b/tests/experiment/test_model.py @@ -9,6 +9,7 @@ import unittest import numpy as np +import pytest from numpy.testing import assert_equal from EasyReflectometry.calculators import CalculatorFactory @@ -16,8 +17,10 @@ from EasyReflectometry.sample import Layer from EasyReflectometry.sample import LayerCollection from EasyReflectometry.sample import Material +from EasyReflectometry.sample import Multilayer from EasyReflectometry.sample import RepeatingMultilayer from EasyReflectometry.sample import Sample +from EasyReflectometry.sample import SurfactantLayer class TestModel(unittest.TestCase): @@ -87,6 +90,8 @@ def test_add_item(self): ls2 = LayerCollection.from_pars(l2, l1, name='twoLayer2') o1 = RepeatingMultilayer.from_pars(ls1, 2.0, 'twoLayerItem1') o2 = RepeatingMultilayer.from_pars(ls2, 1.0, 'oneLayerItem2') + surfactant = SurfactantLayer.default() + multilayer = Multilayer.default() d = Sample.from_pars(o1, name='myModel') mod = Model.from_pars(d, 2, 1e-5, 2.0, 'newModel') assert_equal(len(mod.sample), 1) @@ -94,6 +99,18 @@ def test_add_item(self): assert_equal(len(mod.sample), 2) assert_equal(mod.sample[1].name, 'oneLayerItem2') assert_equal(issubclass(mod.sample[1].__class__, RepeatingMultilayer), True) + mod.add_item(surfactant) + assert_equal(len(mod.sample), 3) + mod.add_item(multilayer) + assert_equal(len(mod.sample), 4) + + def test_add_item_exception(self): + # When + mod = Model.default() + + # Then Expect + with pytest.raises(ValueError): + mod.add_item('not an assembly') def test_add_item_with_interface_refnx(self): interface = CalculatorFactory() @@ -320,6 +337,14 @@ def test_repr(self): ) def test_dict_round_trip(self): - p = Model.default() - q = Model.from_dict(p.as_dict()) + interface = CalculatorFactory() + p = Model.default(interface) + surfactant = SurfactantLayer.default() + p.add_item(surfactant) + multilayer = Multilayer.default() + p.add_item(multilayer) + repeating = RepeatingMultilayer.default() + p.add_item(repeating) + src_dict = p.as_dict() + q = Model.from_dict(src_dict) assert p.as_data_dict() == q.as_data_dict() diff --git a/tests/sample/assemblies/test_gradient_layer.py b/tests/sample/assemblies/test_gradient_layer.py index c46363c2..51188427 100644 --- a/tests/sample/assemblies/test_gradient_layer.py +++ b/tests/sample/assemblies/test_gradient_layer.py @@ -1,6 +1,7 @@ """ Tests for GradientLayer class module """ + from unittest.mock import MagicMock import pytest @@ -178,3 +179,9 @@ def test_prepare_gradient_layers(monkeypatch): assert mock_Layer.from_pars.call_args_list[0][1]['thickness'] == 0.0 assert mock_Layer.from_pars.call_args_list[0][1]['name'] == '0' assert mock_Layer.from_pars.call_args_list[0][1]['interface'] is None + + +def test_dict_round_trip(): + p = GradientLayer.default() + q = GradientLayer.from_dict(p.as_dict()) + assert p.as_data_dict() == q.as_data_dict() diff --git a/tests/sample/assemblies/test_multilayer.py b/tests/sample/assemblies/test_multilayer.py index 0e841091..74a8df4c 100644 --- a/tests/sample/assemblies/test_multilayer.py +++ b/tests/sample/assemblies/test_multilayer.py @@ -1,6 +1,7 @@ """ Tests for MultiLayer class module """ + __author__ = 'github.com/arm61' __version__ = '0.0.1' @@ -11,8 +12,8 @@ from EasyReflectometry.calculators.factory import CalculatorFactory from EasyReflectometry.sample.assemblies.multilayer import Multilayer -from EasyReflectometry.sample.elements.layer_collection import LayerCollection from EasyReflectometry.sample.elements.layers.layer import Layer +from EasyReflectometry.sample.elements.layers.layer_collection import LayerCollection from EasyReflectometry.sample.elements.materials.material import Material diff --git a/tests/sample/assemblies/test_repeating_multilayer.py b/tests/sample/assemblies/test_repeating_multilayer.py index aaf4ed98..9007b9f0 100644 --- a/tests/sample/assemblies/test_repeating_multilayer.py +++ b/tests/sample/assemblies/test_repeating_multilayer.py @@ -13,8 +13,8 @@ from EasyReflectometry.calculators import CalculatorFactory from EasyReflectometry.sample.assemblies.repeating_multilayer import RepeatingMultilayer -from EasyReflectometry.sample.elements.layer_collection import LayerCollection from EasyReflectometry.sample.elements.layers.layer import Layer +from EasyReflectometry.sample.elements.layers.layer_collection import LayerCollection from EasyReflectometry.sample.elements.materials.material import Material diff --git a/tests/sample/assemblies/test_surfactant_layer.py b/tests/sample/assemblies/test_surfactant_layer.py index 7e7321c5..f384dd72 100644 --- a/tests/sample/assemblies/test_surfactant_layer.py +++ b/tests/sample/assemblies/test_surfactant_layer.py @@ -1,6 +1,7 @@ """ Tests for SurfactantLayer class module """ + __author__ = 'github.com/arm61' __version__ = '0.0.1' @@ -15,7 +16,7 @@ class TestSurfactantLayer(unittest.TestCase): def test_default(self): p = SurfactantLayer.default() - assert p.name == 'DPPC' + assert p.name == 'EasySurfactantLayer' assert p._type == 'Surfactant Layer' assert p.layers[0].name == 'DPPC Tail' @@ -37,31 +38,31 @@ def test_from_pars(self): assert p.tail_layer.molecular_formula == 'C8O10H12P' assert p.tail_layer.thickness.raw_value == 12 assert p.tail_layer.solvent.as_data_dict() == h2o.as_data_dict() - assert p.tail_layer.solvent_fraction.raw_value == 0.5 - assert p.tail_layer.area_per_molecule.raw_value == 50 + assert p.tail_layer.solvent_fraction == 0.5 + assert p.tail_layer.area_per_molecule == 50 assert p.tail_layer.roughness.raw_value == 2 assert p.layers[1].name == 'A Test Head Layer' assert p.head_layer.name == 'A Test Head Layer' assert p.head_layer.molecular_formula == 'C10H24' assert p.head_layer.thickness.raw_value == 10 assert p.head_layer.solvent.as_data_dict() == noth2o.as_data_dict() - assert p.head_layer.solvent_fraction.raw_value == 0.2 - assert p.head_layer.area_per_molecule.raw_value == 40 + assert p.head_layer.solvent_fraction == 0.2 + assert p.head_layer.area_per_molecule == 40 assert p.name == 'A Test' def test_constraint_area_per_molecule(self): p = SurfactantLayer.default() - p.tail_layer.area_per_molecule.value = 30 - assert p.tail_layer.area_per_molecule.raw_value == 30.0 - assert p.head_layer.area_per_molecule.raw_value == 48.2 + p.tail_layer._area_per_molecule.value = 30 + assert p.tail_layer.area_per_molecule == 30.0 + assert p.head_layer.area_per_molecule == 48.2 assert p.constrain_area_per_molecule is False p.constrain_area_per_molecule = True - assert p.tail_layer.area_per_molecule.raw_value == 30 - assert p.head_layer.area_per_molecule.raw_value == 30 + assert p.tail_layer.area_per_molecule == 30 + assert p.head_layer.area_per_molecule == 30 assert p.constrain_area_per_molecule is True - p.tail_layer.area_per_molecule.value = 40 - assert p.tail_layer.area_per_molecule.raw_value == 40 - assert p.head_layer.area_per_molecule.raw_value == 40 + p.tail_layer._area_per_molecule.value = 40 + assert p.tail_layer.area_per_molecule == 40 + assert p.head_layer.area_per_molecule == 40 def test_conformal_roughness(self): p = SurfactantLayer.default() @@ -97,46 +98,52 @@ def test_constain_solvent_roughness(self): def test_dict_repr(self): p = SurfactantLayer.default() assert p._dict_repr == { - 'head_layer': { - 'DPPC Head': { - 'material': { - 'C10H18NO8P in D2O': { - 'solvent_fraction': 0.2, - 'sld': '2.269e-6 1 / angstrom ** 2', - 'isld': '0.000e-6 1 / angstrom ** 2', - 'material': { - 'C10H18NO8P': {'sld': '1.246e-6 1 / angstrom ** 2', 'isld': '0.000e-6 1 / angstrom ** 2'} - }, - 'solvent': {'D2O': {'sld': '6.360e-6 1 / angstrom ** 2', 'isld': '0.000e-6 1 / angstrom ** 2'}}, - } + 'EasySurfactantLayer': { + 'head_layer': { + 'DPPC Head': { + 'material': { + 'C10H18NO8P in D2O': { + 'solvent_fraction': '0.200 dimensionless', + 'sld': '2.269e-6 1 / angstrom ** 2', + 'isld': '0.000e-6 1 / angstrom ** 2', + 'material': { + 'C10H18NO8P': {'sld': '1.246e-6 1 / angstrom ** 2', 'isld': '0.000e-6 1 / angstrom ** 2'} + }, + 'solvent': { + 'D2O': {'sld': '6.360e-6 1 / angstrom ** 2', 'isld': '0.000e-6 1 / angstrom ** 2'} + }, + } + }, + 'thickness': '10.000 angstrom', + 'roughness': '3.000 angstrom', }, - 'thickness': '10.000 angstrom', - 'roughness': '3.000 angstrom', + 'molecular_formula': 'C10H18NO8P', + 'area_per_molecule': '48.20 angstrom ** 2', }, - 'molecular_formula': 'C10H18NO8P', - 'area_per_molecule': '48.20 angstrom ** 2', - }, - 'tail_layer': { - 'DPPC Tail': { - 'material': { - 'C32D64 in Air': { - 'solvent_fraction': 0.0, - 'sld': '8.297e-6 1 / angstrom ** 2', - 'isld': '0.000e-6 1 / angstrom ** 2', - 'material': { - 'C32D64': {'sld': '8.297e-6 1 / angstrom ** 2', 'isld': '0.000e-6 1 / angstrom ** 2'} - }, - 'solvent': {'Air': {'sld': '0.000e-6 1 / angstrom ** 2', 'isld': '0.000e-6 1 / angstrom ** 2'}}, - } + 'tail_layer': { + 'DPPC Tail': { + 'material': { + 'C32D64 in Air': { + 'solvent_fraction': '0.000 dimensionless', + 'sld': '8.297e-6 1 / angstrom ** 2', + 'isld': '0.000e-6 1 / angstrom ** 2', + 'material': { + 'C32D64': {'sld': '8.297e-6 1 / angstrom ** 2', 'isld': '0.000e-6 1 / angstrom ** 2'} + }, + 'solvent': { + 'Air': {'sld': '0.000e-6 1 / angstrom ** 2', 'isld': '0.000e-6 1 / angstrom ** 2'} + }, + } + }, + 'thickness': '16.000 angstrom', + 'roughness': '3.000 angstrom', }, - 'thickness': '16.000 angstrom', - 'roughness': '3.000 angstrom', + 'molecular_formula': 'C32D64', + 'area_per_molecule': '48.20 angstrom ** 2', }, - 'molecular_formula': 'C32D64', - 'area_per_molecule': '48.20 angstrom ** 2', - }, - 'area per molecule constrained': False, - 'conformal roughness': False, + 'area per molecule constrained': False, + 'conformal roughness': False, + } } def test_get_head_layer(self): diff --git a/tests/sample/elements/layers/test_layer_area_per_molecule.py b/tests/sample/elements/layers/test_layer_area_per_molecule.py index 8619ac0b..a2e3b379 100644 --- a/tests/sample/elements/layers/test_layer_area_per_molecule.py +++ b/tests/sample/elements/layers/test_layer_area_per_molecule.py @@ -1,6 +1,7 @@ """ Tests for LayerAreaPerMolecule class. """ + import unittest from numpy.testing import assert_almost_equal @@ -13,24 +14,24 @@ class TestLayerAreaPerMolecule(unittest.TestCase): def test_default(self): p = LayerAreaPerMolecule.default() assert p.molecular_formula == 'C10H18NO8P' - assert p.area_per_molecule.raw_value == 48.2 - assert str(p.area_per_molecule.unit) == 'angstrom ** 2' - assert p.area_per_molecule.fixed is True + assert p.area_per_molecule == 48.2 + assert str(p._area_per_molecule.unit) == 'angstrom ** 2' + assert p._area_per_molecule.fixed is True assert p.thickness.raw_value == 10.0 assert str(p.thickness.unit) == 'angstrom' assert p.thickness.fixed is True assert p.roughness.raw_value == 3.3 assert str(p.roughness.unit) == 'angstrom' assert p.roughness.fixed is True - assert_almost_equal(p.material.sld.raw_value, 2.2691419) - assert_almost_equal(p.material.isld.raw_value, 0) + assert_almost_equal(p.material.sld, 2.2691419) + assert_almost_equal(p.material.isld, 0) assert p.material.name == 'C10H18NO8P in D2O' assert p.solvent.sld.raw_value == 6.36 assert p.solvent.isld.raw_value == 0 assert p.solvent.name == 'D2O' - assert p.solvent_fraction.raw_value == 0.2 - assert str(p.solvent_fraction.unit) == 'dimensionless' - assert p.solvent_fraction.fixed is True + assert p.solvent_fraction == 0.2 + assert str(p.material._fraction.unit) == 'dimensionless' + assert p.material._fraction.fixed is True def test_from_pars(self): h2o = Material.from_pars(-0.561, 0, 'H2O') @@ -44,12 +45,12 @@ def test_from_pars(self): name='PG/H2O', ) assert p.molecular_formula == 'C8O10H12P' - assert p.area_per_molecule.raw_value == 50 + assert p.area_per_molecule == 50 assert p.thickness.raw_value == 12 assert p.roughness.raw_value == 2 assert p.solvent.sld.raw_value == -0.561 assert p.solvent.isld.raw_value == 0 - assert p.solvent_fraction.raw_value == 0.5 + assert p.solvent_fraction == 0.5 def test_from_pars_constraint(self): h2o = Material.from_pars(-0.561, 0, 'H2O') @@ -63,19 +64,19 @@ def test_from_pars_constraint(self): name='PG/H2O', ) assert p.molecular_formula == 'C8O10H12P' - assert p.area_per_molecule.raw_value == 50 - assert_almost_equal(p.material.sld.raw_value, 0.31513666667) + assert p.area_per_molecule == 50 + assert_almost_equal(p.material.sld, 0.31513666667) assert p.thickness.raw_value == 12 assert p.roughness.raw_value == 2 assert p.solvent.sld.raw_value == -0.561 assert p.solvent.isld.raw_value == 0 - assert p.solvent_fraction.raw_value == 0.5 - p.area_per_molecule.value = 30 - assert p.area_per_molecule.raw_value == 30 - assert_almost_equal(p.material.sld.raw_value, 0.712227778) + assert p.solvent_fraction == 0.5 + p.area_per_molecule = 30 + assert p.area_per_molecule == 30 + assert_almost_equal(p.material.sld, 0.712227778) p.thickness.value = 10 assert p.thickness.raw_value == 10 - assert_almost_equal(p.material.sld.raw_value, 0.910773333) + assert_almost_equal(p.material.sld, 0.910773333) def test_solvent_change(self): h2o = Material.from_pars(-0.561, 0, 'H2O') @@ -89,24 +90,24 @@ def test_solvent_change(self): name='PG/H2O', ) assert p.molecular_formula == 'C8O10H12P' - assert p.area_per_molecule.raw_value == 50 + assert p.area_per_molecule == 50 print(p.material) - assert_almost_equal(p.material.sld.raw_value, 0.31513666667) + assert_almost_equal(p.material.sld, 0.31513666667) assert p.thickness.raw_value == 12 assert p.roughness.raw_value == 2 assert p.solvent.sld.raw_value == -0.561 assert p.solvent.isld.raw_value == 0 - assert p.solvent_fraction.raw_value == 0.5 + assert p.solvent_fraction == 0.5 d2o = Material.from_pars(6.335, 0, 'D2O') p.solvent = d2o assert p.molecular_formula == 'C8O10H12P' - assert p.area_per_molecule.raw_value == 50 - assert_almost_equal(p.material.sld.raw_value, 3.7631366667) + assert p.area_per_molecule == 50 + assert_almost_equal(p.material.sld, 3.7631366667) assert p.thickness.raw_value == 12 assert p.roughness.raw_value == 2 assert p.solvent.sld.raw_value == 6.335 assert p.solvent.isld.raw_value == 0 - assert p.solvent_fraction.raw_value == 0.5 + assert p.solvent_fraction == 0.5 def test_molecular_formula_change(self): h2o = Material.from_pars(-0.561, 0, 'H2O') @@ -120,24 +121,24 @@ def test_molecular_formula_change(self): name='PG/H2O', ) assert p.molecular_formula == 'C8O10H12P' - assert p.area_per_molecule.raw_value == 50 - assert_almost_equal(p.material.sld.raw_value, 0.31513666667) + assert p.area_per_molecule == 50 + assert_almost_equal(p.material.sld, 0.31513666667) assert p.thickness.raw_value == 12 assert p.roughness.raw_value == 2 assert p.solvent.sld.raw_value == -0.561 assert p.solvent.isld.raw_value == 0 - assert p.solvent_fraction.raw_value == 0.5 + assert p.solvent_fraction == 0.5 assert p.material.name == 'C8O10H12P in H2O' p.molecular_formula = 'C8O10D12P' assert p.molecular_formula == 'C8O10D12P' - assert p.area_per_molecule.raw_value == 50 - assert_almost_equal(p.material.sld.raw_value, 1.3566266666666666) + assert p.area_per_molecule == 50 + assert_almost_equal(p.material.sld, 1.3566266666666666) assert p.thickness.raw_value == 12 assert p.roughness.raw_value == 2 assert p.solvent.sld.raw_value == -0.561 assert p.solvent.isld.raw_value == 0 - assert p.solvent_fraction.raw_value == 0.5 + assert p.solvent_fraction == 0.5 assert p.material.name == 'C8O10D12P in H2O' def test_dict_repr(self): @@ -146,7 +147,7 @@ def test_dict_repr(self): 'EasyLayerAreaPerMolecule': { 'material': { 'C10H18NO8P in D2O': { - 'solvent_fraction': 0.2, + 'solvent_fraction': '0.200 dimensionless', 'sld': '2.269e-6 1 / angstrom ** 2', 'isld': '0.000e-6 1 / angstrom ** 2', 'material': { diff --git a/tests/sample/elements/materials/test_material_mixture.py b/tests/sample/elements/materials/test_material_mixture.py index f6a85cc9..31db84ee 100644 --- a/tests/sample/elements/materials/test_material_mixture.py +++ b/tests/sample/elements/materials/test_material_mixture.py @@ -8,118 +8,107 @@ class TestMaterialMixture(unittest.TestCase): - def test_default(self): p = MaterialMixture.default() - assert p._fraction.raw_value == 0.5 + assert p.fraction == 0.5 assert str(p._fraction.unit) == 'dimensionless' - assert p.sld.raw_value == Material.default().sld.raw_value - assert p.isld.raw_value == Material.default().isld.raw_value - assert str(p.sld.unit) == '1 / angstrom ** 2' - assert str(p.isld.unit) == '1 / angstrom ** 2' + assert p.sld == Material.default().sld.raw_value + assert p.isld == Material.default().isld.raw_value + assert str(p._sld.unit) == '1 / angstrom ** 2' + assert str(p._isld.unit) == '1 / angstrom ** 2' def test_default_constraint(self): p = MaterialMixture.default() - assert p._fraction.raw_value == 0.5 + assert p.fraction == 0.5 assert str(p._fraction.unit) == 'dimensionless' - assert p.sld.raw_value == Material.default().sld.raw_value - assert p.isld.raw_value == Material.default().isld.raw_value + assert p.sld == Material.default().sld.raw_value + assert p.isld == Material.default().isld.raw_value p.material_a.sld.value = 0 p.material_b.isld.value = -1 - assert_almost_equal(p.sld.raw_value, 2.093) - assert_almost_equal(p.isld.raw_value, -0.5) - assert str(p.sld.unit) == '1 / angstrom ** 2' - assert str(p.isld.unit) == '1 / angstrom ** 2' - + assert_almost_equal(p.sld, 2.093) + assert_almost_equal(p.isld, -0.5) + assert str(p._sld.unit) == '1 / angstrom ** 2' + assert str(p._isld.unit) == '1 / angstrom ** 2' + def test_fraction_constraint(self): p = Material.default() q = Material.from_pars(6.908, -0.278, 'Boron') r = MaterialMixture.from_pars(p, q, 0.2) - assert r._fraction.raw_value == 0.2 - assert_almost_equal(r.sld.raw_value, 4.7304) - assert_almost_equal(r.isld.raw_value, -0.0556) + assert r.fraction == 0.2 + assert_almost_equal(r.sld, 4.7304) + assert_almost_equal(r.isld, -0.0556) r._fraction.value = 0.5 - assert r._fraction.raw_value == 0.5 - assert_almost_equal(r.sld.raw_value, 5.54700) - assert_almost_equal(r.isld.raw_value, -0.1390) + assert r.fraction == 0.5 + assert_almost_equal(r.sld, 5.54700) + assert_almost_equal(r.isld, -0.1390) def test_material_a_change(self): p = MaterialMixture.default() - assert p._fraction.raw_value == 0.5 + assert p.fraction == 0.5 assert str(p._fraction.unit) == 'dimensionless' - assert p.sld.raw_value == Material.default().sld.raw_value - assert p.isld.raw_value == Material.default().isld.raw_value + assert p.sld == Material.default().sld.raw_value + assert p.isld == Material.default().isld.raw_value q = Material.from_pars(6.908, -0.278, 'Boron') p.material_a = q - assert p._fraction.raw_value == 0.5 + assert p.fraction == 0.5 assert str(p._fraction.unit) == 'dimensionless' - assert_almost_equal(p.sld.raw_value, 5.54700) - assert_almost_equal(p.isld.raw_value, -0.1390) + assert_almost_equal(p.sld, 5.54700) + assert_almost_equal(p.isld, -0.1390) def test_material_b_change(self): p = MaterialMixture.default() - assert p._fraction.raw_value == 0.5 + assert p.fraction == 0.5 assert str(p._fraction.unit) == 'dimensionless' - assert p.sld.raw_value == Material.default().sld.raw_value - assert p.isld.raw_value == Material.default().isld.raw_value + assert p.sld == Material.default().sld.raw_value + assert p.isld == Material.default().isld.raw_value q = Material.from_pars(6.908, -0.278, 'Boron') p.material_b = q - assert p._fraction.raw_value == 0.5 + assert p.fraction == 0.5 assert str(p._fraction.unit) == 'dimensionless' - assert_almost_equal(p.sld.raw_value, 5.54700) - assert_almost_equal(p.isld.raw_value, -0.1390) + assert_almost_equal(p.sld, 5.54700) + assert_almost_equal(p.isld, -0.1390) def test_material_b_change_double(self): p = MaterialMixture.default() - assert p._fraction.raw_value == 0.5 + assert p.fraction == 0.5 assert str(p._fraction.unit) == 'dimensionless' - assert p.sld.raw_value == Material.default().sld.raw_value - assert p.isld.raw_value == Material.default().isld.raw_value + assert p.sld == Material.default().sld.raw_value + assert p.isld == Material.default().isld.raw_value q = Material.from_pars(6.908, -0.278, 'Boron') p.material_b = q assert p.name == 'EasyMaterial/Boron' - assert p._fraction.raw_value == 0.5 + assert p.fraction == 0.5 assert str(p._fraction.unit) == 'dimensionless' - assert_almost_equal(p.sld.raw_value, 5.54700) - assert_almost_equal(p.isld.raw_value, -0.1390) + assert_almost_equal(p.sld, 5.54700) + assert_almost_equal(p.isld, -0.1390) r = Material.from_pars(0.00, 0.00, 'ACMW') p.material_b = r assert p.name == 'EasyMaterial/ACMW' - assert p._fraction.raw_value == 0.5 + assert p.fraction == 0.5 assert str(p._fraction.unit) == 'dimensionless' - assert_almost_equal(p.sld.raw_value, 2.0930) - assert_almost_equal(p.isld.raw_value, 0.0000) + assert_almost_equal(p.sld, 2.0930) + assert_almost_equal(p.isld, 0.0000) def test_from_pars(self): p = Material.default() q = Material.from_pars(6.908, -0.278, 'Boron') r = MaterialMixture.from_pars(p, q, 0.2) - assert r._fraction.raw_value == 0.2 + assert r.fraction == 0.2 assert str(r._fraction.unit) == 'dimensionless' - assert_almost_equal(r.sld.raw_value, 4.7304) - assert_almost_equal(r.isld.raw_value, -0.0556) - assert str(r.sld.unit) == '1 / angstrom ** 2' - assert str(r.isld.unit) == '1 / angstrom ** 2' + assert_almost_equal(r.sld, 4.7304) + assert_almost_equal(r.isld, -0.0556) + assert str(r._sld.unit) == '1 / angstrom ** 2' + assert str(r._isld.unit) == '1 / angstrom ** 2' def test_dict_repr(self): p = MaterialMixture.default() assert p._dict_repr == { 'EasyMaterial/EasyMaterial': { - 'fraction': 0.5, + 'fraction': '0.500 dimensionless', 'sld': '4.186e-6 1 / angstrom ** 2', 'isld': '0.000e-6 1 / angstrom ** 2', - 'material_a': { - 'EasyMaterial': { - 'sld': '4.186e-6 1 / angstrom ** 2', - 'isld': '0.000e-6 1 / angstrom ** 2' - } - }, - 'material_b': { - 'EasyMaterial': { - 'sld': '4.186e-6 1 / angstrom ** 2', - 'isld': '0.000e-6 1 / angstrom ** 2' - } - } + 'material_a': {'EasyMaterial': {'sld': '4.186e-6 1 / angstrom ** 2', 'isld': '0.000e-6 1 / angstrom ** 2'}}, + 'material_b': {'EasyMaterial': {'sld': '4.186e-6 1 / angstrom ** 2', 'isld': '0.000e-6 1 / angstrom ** 2'}}, } } @@ -127,7 +116,7 @@ def test_dict_round_trip(self): p = MaterialMixture.default() q = MaterialMixture.from_dict(p.as_dict()) assert p.as_data_dict() == q.as_data_dict() - + def test_update_name(self): # When p = MaterialMixture.default() diff --git a/tests/sample/elements/materials/test_material_solvated.py b/tests/sample/elements/materials/test_material_solvated.py index 1b13cc11..a24e46a7 100644 --- a/tests/sample/elements/materials/test_material_solvated.py +++ b/tests/sample/elements/materials/test_material_solvated.py @@ -37,7 +37,7 @@ def test_init(self, material_solvated: MaterialSolvated) -> None: # When Then Expect assert material_solvated.material_a == self.mock_material assert material_solvated.material_b == self.mock_solvent - assert material_solvated.fraction == self.mock_solvent_fraction + assert material_solvated.fraction == 0.1 assert material_solvated.name == 'name' assert material_solvated.interface == self.mock_interface self.mock_interface.generate_bindings.call_count == 2 @@ -76,7 +76,7 @@ def test_set_solvent(self, material_solvated: MaterialSolvated) -> None: def test_solvent_fraction(self, material_solvated: MaterialSolvated) -> None: # When Then Expect - assert material_solvated.solvent_fraction == self.mock_solvent_fraction + assert material_solvated.solvent_fraction == 0.1 def test_set_solvent_fraction(self, material_solvated: MaterialSolvated) -> None: # When Then @@ -102,29 +102,26 @@ def test_set_solvent_fraction_exception(self, material_solvated: MaterialSolvate # When Then Expect (no exception) material_solvated.solvent_fraction = 1.0 - def test_dict_repr(self, material_solvated: MaterialSolvated) -> None: + def test_dict_repr(self) -> None: # When Then - material_solvated._sld = MagicMock() - material_solvated._sld.raw_value = 1.0 - material_solvated._sld.unit = 'sld_unit' - material_solvated._isld = MagicMock() - material_solvated._isld.raw_value = 2.0 - material_solvated._isld.unit = 'isld_unit' - material_solvated.material._dict_repr = 'material_dict_repr' - material_solvated.solvent._dict_repr = 'solvent_dict_repr' - material_solvated.solvent_fraction.raw_value = 'solvent_fraction_value' + p = MaterialSolvated.default() # Expect - assert material_solvated._dict_repr == { - 'name': { - 'solvent_fraction': 'solvent_fraction_value', - 'sld': '1.000e-6 sld_unit', - 'isld': '2.000e-6 isld_unit', - 'material': 'material_dict_repr', - 'solvent': 'solvent_dict_repr', + assert p._dict_repr == { + 'D2O in H2O': { + 'solvent_fraction': '0.200 dimensionless', + 'sld': '4.976e-6 1 / angstrom ** 2', + 'isld': '0.000e-6 1 / angstrom ** 2', + 'material': {'D2O': {'sld': '6.360e-6 1 / angstrom ** 2', 'isld': '0.000e-6 1 / angstrom ** 2'}}, + 'solvent': {'H2O': {'sld': '-0.561e-6 1 / angstrom ** 2', 'isld': '0.000e-6 1 / angstrom ** 2'}}, } } + def test_dict_round_trip(self): + p = MaterialSolvated.default() + q = MaterialSolvated.from_dict(p.as_dict()) + assert p.as_data_dict() == q.as_data_dict() + def test_update_name(self, material_solvated: MaterialSolvated) -> None: # When mock_material_a = MagicMock() diff --git a/tests/sample/elements/test_layer_collection.py b/tests/sample/elements/test_layer_collection.py index 064c1914..a572a0da 100644 --- a/tests/sample/elements/test_layer_collection.py +++ b/tests/sample/elements/test_layer_collection.py @@ -1,6 +1,7 @@ """ Tests for LayerCollection class. """ + __author__ = 'github.com/arm61' __version__ = '0.0.1' @@ -9,8 +10,8 @@ from numpy.testing import assert_equal from EasyReflectometry.sample.assemblies.repeating_multilayer import RepeatingMultilayer -from EasyReflectometry.sample.elements.layer_collection import LayerCollection from EasyReflectometry.sample.elements.layers.layer import Layer +from EasyReflectometry.sample.elements.layers.layer_collection import LayerCollection from EasyReflectometry.sample.elements.materials.material import Material diff --git a/tests/sample/elements/test_material_collection.py b/tests/sample/elements/test_material_collection.py index f95ff600..104b99de 100644 --- a/tests/sample/elements/test_material_collection.py +++ b/tests/sample/elements/test_material_collection.py @@ -1,13 +1,14 @@ """ Tests for LayerCollection class. """ + __author__ = 'github.com/arm61' __version__ = '0.0.1' import unittest -from EasyReflectometry.sample.elements.material_collection import MaterialCollection from EasyReflectometry.sample.elements.materials.material import Material +from EasyReflectometry.sample.elements.materials.material_collection import MaterialCollection class TestLayerCollection(unittest.TestCase): diff --git a/tests/sample/test_sample.py b/tests/sample/test_sample.py index 08f50126..b9acbda9 100644 --- a/tests/sample/test_sample.py +++ b/tests/sample/test_sample.py @@ -1,6 +1,7 @@ """ Tests for Sample class. """ + __author__ = 'github.com/arm61' __version__ = '0.0.1' @@ -8,11 +9,13 @@ from numpy.testing import assert_equal -from EasyReflectometry.sample.assemblies.repeating_multilayer import RepeatingMultilayer -from EasyReflectometry.sample.elements.layer_collection import LayerCollection -from EasyReflectometry.sample.elements.layers.layer import Layer -from EasyReflectometry.sample.elements.materials.material import Material -from EasyReflectometry.sample.sample import Sample +from EasyReflectometry.sample import Layer +from EasyReflectometry.sample import LayerCollection +from EasyReflectometry.sample import Material +from EasyReflectometry.sample import Multilayer +from EasyReflectometry.sample import RepeatingMultilayer +from EasyReflectometry.sample import Sample +from EasyReflectometry.sample import SurfactantLayer class TestSample(unittest.TestCase): @@ -63,5 +66,12 @@ def test_repr(self): def test_dict_round_trip(self): p = Sample.default() + surfactant = SurfactantLayer.default() + p.append(surfactant) + multilayer = Multilayer.default() + p.append(multilayer) + repeating = RepeatingMultilayer.default() + p.append(repeating) + q = Sample.from_dict(p.as_dict()) assert p.as_data_dict() == q.as_data_dict()