diff --git a/.github/workflows/test-fair-database.yml b/.github/workflows/test-fair-database.yml new file mode 100644 index 00000000..9652a837 --- /dev/null +++ b/.github/workflows/test-fair-database.yml @@ -0,0 +1,54 @@ +name: Tests + +on: + [push, pull_request] + +defaults: + run: + shell: bash + +jobs: + unit-test: + + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-latest, ubuntu-latest, windows-latest] + python-version: ['3.12'] + fail-fast: false + + steps: + + - name: Obtain SasData source from git + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: 'pip' + cache-dependency-path: | + **/test.yml + **/requirements*.txt + + ### Installation of build-dependencies + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + python -m pip install wheel setuptools + python -m pip install -r requirements.txt + python -m pip install -r sasdata/fair_database/requirements.txt + + ### Build and test sasdata + + - name: Build sasdata + run: | + # BUILD SASDATA + python -m pip install -e . + + ### Build documentation (if enabled) + + - name: Test with Django tests + run: | + python sasdata/fair_database/manage.py test sasdata.fair_database diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f5843b63..2f6523eb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: os: [macos-latest, ubuntu-latest, windows-latest] - python-version: ['3.10', '3.11', '3.12'] + python-version: ['3.12'] fail-fast: false env: diff --git a/.gitignore b/.gitignore index 76c0f9e0..cd9465bc 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ **/build /dist .mplconfig +**/db.sqlite3 # doc build /docs/sphinx-docs/build diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..f1958b33 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,19 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + files: "sasdata/fair_database/.*" +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.9.2 + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + files: "sasdata/fair_database/.*" + - id: ruff-format + files: "sasdata/fair_database/.*" +- repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + files: "sasdata/fair_database/.*" diff --git a/conftest.py b/conftest.py new file mode 100644 index 00000000..ab84f56f --- /dev/null +++ b/conftest.py @@ -0,0 +1,10 @@ +import pytest + +def pytest_addoption(parser): + parser.addoption( + "--show_plots", action="store_true", default=False, help="Display diagnostic plots during tests" + ) + +@pytest.fixture +def show_plots(request): + return request.config.getoption("--show_plots") diff --git a/pyproject.toml b/pyproject.toml index 402e4cd5..43b3839c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ dynamic = [ ] description = "Sas Data Loader application" readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.12" license = { text = "BSD-3-Clause" } authors = [ {name = "SasView Team", email = "developers@sasview.org"}, diff --git a/requirements.txt b/requirements.txt index 337e5f04..6d5ff012 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,15 @@ lxml # Calculation numpy +scipy + +# Unit testing +pytest +unittest-xml-reporting + +# Documentation (future) +sphinx +html5lib + +# Other stuff +matplotlib \ No newline at end of file diff --git a/sasdata/ascii_reader_metadata.py b/sasdata/ascii_reader_metadata.py new file mode 100644 index 00000000..4c9974eb --- /dev/null +++ b/sasdata/ascii_reader_metadata.py @@ -0,0 +1,149 @@ +from dataclasses import dataclass, field +from typing import TypeVar +import re + +initial_metadata = { + 'source': ['name', 'radiation', 'type', 'probe_particle', 'beam_size_name', 'beam_size', 'beam_shape', 'wavelength', 'wavelength_min', 'wavelength_max', 'wavelength_spread'], + 'detector': ['name', 'distance', 'offset', 'orientation', 'beam_center', 'pixel_size', 'slit_length'], + 'aperture': ['name', 'type', 'size_name', 'size', 'distance'], + 'collimation': ['name', 'lengths'], + 'process': ['name', 'date', 'description', 'term', 'notes'], + 'sample': ['name', 'sample_id', 'thickness', 'transmission', 'temperature', 'position', 'orientation', 'details'], + 'transmission_spectrum': ['name', 'timestamp', 'transmission', 'transmission_deviation'], + 'magnetic': ['demagnetizing_field', 'saturation_magnetization', 'applied_magnetic_field', 'counting_index'], + 'other': ['title', 'run', 'definition'] +} + +CASING_REGEX = r'[A-Z][a-z]*' + +# First item has the highest precedence. +SEPARATOR_PRECEDENCE = [ + '_', + '-', +] +# If none of these characters exist in that string, use casing. See init_separator + +T = TypeVar('T') + +# TODO: There may be a better place for this. +pairings = {'I': 'dI', 'Q': 'dQ', 'Qx': 'dQx', 'Qy': 'dQy'} +pairing_error = {value: key for key, value in pairings.items()} +# Allows this to be bidirectional. +bidirectional_pairings = pairings | pairing_error + +@dataclass +class AsciiMetadataCategory[T]: + values: dict[str, T] = field(default_factory=dict) + +def default_categories() -> dict[str, AsciiMetadataCategory[str | int]]: + return {key: AsciiMetadataCategory() for key in initial_metadata.keys()} + +@dataclass +class AsciiReaderMetadata: + # Key is the filename. + filename_specific_metadata: dict[str, dict[str, AsciiMetadataCategory[str]]] = field(default_factory=dict) + # True instead of str means use the casing to separate the filename. + filename_separator: dict[str, str | bool] = field(default_factory=dict) + master_metadata: dict[str, AsciiMetadataCategory[int]] = field(default_factory=default_categories) + + def init_separator(self, filename: str): + separator = next(filter(lambda c: c in SEPARATOR_PRECEDENCE, filename), True) + self.filename_separator[filename] = separator + + def filename_components(self, filename: str, cut_off_extension: bool = True, capture: bool = False) -> list[str]: + """Split the filename into several components based on the current separator for that file.""" + separator = self.filename_separator[filename] + # FIXME: This sort of string construction may be an issue. Might need an alternative. + base_str = '({})' if capture else '{}' + if isinstance(separator, str): + splitted = re.split(base_str.replace('{}', separator), filename) + else: + splitted = re.findall(base_str.replace('{}', CASING_REGEX), filename) + # If the last component has a file extensions, remove it. + last_component = splitted[-1] + if cut_off_extension and '.' in last_component: + pos = last_component.index('.') + last_component = last_component[:pos] + splitted[-1] = last_component + return splitted + + def purge_unreachable(self, filename: str): + """This is used when the separator has changed. If lets say we now have 2 components when there were 5 but the + 3rd component was selected, this will now produce an index out of range exception. Thus we'll need to purge this + to stop exceptions from happening.""" + components = self.filename_components(filename) + component_length = len(components) + # Converting to list as this mutates the dictionary as it goes through it. + for category_name, category in list(self.master_metadata.items()): + for key, value in list(category.values.items()): + if value >= component_length: + del self.master_metadata[category_name].values[key] + + def all_file_metadata(self, filename: str) -> dict[str, AsciiMetadataCategory[str]]: + """Return all of the metadata for known for the specified filename. This + will combin the master metadata specified for all files with the + metadata specific to that filename.""" + file_metadata = self.filename_specific_metadata[filename] + components = self.filename_components(filename) + # The ordering here is important. If there are conflicts, the second dictionary will override the first one. + # Conflicts shouldn't really be happening anyway but if they do, we're gonna go with the master metadata taking + # precedence for now. + return_metadata: dict[str, AsciiMetadataCategory[str]] = {} + for category_name, category in (file_metadata | self.master_metadata).items(): + combined_category_dict = category.values | self.master_metadata[category_name].values + new_category_dict: dict[str, str] = {} + for key, value in combined_category_dict.items(): + if isinstance(value, str): + new_category_dict[key] = value + elif isinstance(value, int): + new_category_dict[key] = components[value] + else: + raise TypeError(f'Invalid value for {key} in {category_name}') + new_category = AsciiMetadataCategory(new_category_dict) + return_metadata[category_name] = new_category + return return_metadata + def get_metadata(self, category: str, value: str, filename: str, error_on_not_found=False) -> str | None: + """Get a particular piece of metadata for the filename.""" + components = self.filename_components(filename) + + # We prioritise the master metadata. + + # TODO: Assumes category in master_metadata exists. Is this a reasonable assumption? May need to make sure it is + # definitely in the dictionary. + if value in self.master_metadata[category].values: + index = self.master_metadata[category].values[value] + return components[index] + target_category = self.filename_specific_metadata[filename][category].values + if value in target_category: + return target_category[value] + if error_on_not_found: + raise ValueError('value does not exist in metadata.') + else: + return None + + def update_metadata(self, category: str, key: str, filename: str, new_value: str | int): + """Update the metadata for a filename. If the new_value is a string, + then this new metadata will be specific to that file. Otherwise, if + new_value is an integer, then this will represent the component of the + filename that this metadata applies to all.""" + if isinstance(new_value, str): + self.filename_specific_metadata[filename][category].values[key] = new_value + # TODO: What about the master metadata? Until that's gone, that still takes precedence. + elif isinstance(new_value, int): + self.master_metadata[category].values[key] = new_value + else: + raise TypeError('Invalid type for new_value') + + def clear_metadata(self, category: str, key: str, filename: str): + """Remove any metadata recorded for a certain filename.""" + category_obj = self.filename_specific_metadata[filename][category] + if key in category_obj.values: + del category_obj.values[key] + if key in self.master_metadata[category].values: + del self.master_metadata[category].values[key] + + def add_file(self, new_filename: str): + """Add a filename to the metadata, filling it with some default + categories.""" + # TODO: Fix typing here. Pyright is showing errors. + self.filename_specific_metadata[new_filename] = default_categories() diff --git a/sasdata/checklist.txt b/sasdata/checklist.txt new file mode 100644 index 00000000..c25c7d89 --- /dev/null +++ b/sasdata/checklist.txt @@ -0,0 +1,3 @@ +Things to check once everything is in place: + +1) Do any centigrade fields read in incorrectly? \ No newline at end of file diff --git a/sasdata/data.py b/sasdata/data.py new file mode 100644 index 00000000..70df438a --- /dev/null +++ b/sasdata/data.py @@ -0,0 +1,79 @@ + +import numpy as np + +from sasdata import dataset_types +from sasdata.dataset_types import DatasetType +from sasdata.quantities.quantity import Quantity +from sasdata.metadata import Metadata + + +class SasData: + def __init__(self, name: str, + data_contents: dict[str, Quantity], + dataset_type: DatasetType, + metadata: Metadata, + verbose: bool=False): + + self.name = name + # validate data contents + if not all([key in dataset_type.optional or key in dataset_type.required for key in data_contents.keys()]): + raise ValueError("Columns don't match the dataset type") + self._data_contents = data_contents + self._verbose = verbose + + self.metadata = metadata + + # TODO: Could this be optional? + self.dataset_type: DatasetType = dataset_type + + # Components that need to be organised after creation + self.mask = None # TODO: fill out + self.model_requirements = None # TODO: fill out + + # TODO: Handle the other data types. + @property + def ordinate(self) -> Quantity: + match self.dataset_type: + case dataset_types.one_dim | dataset_types.two_dim: + return self._data_contents["I"] + case dataset_types.sesans: + return self._data_contents["Depolarisation"] + case _: + return None + + @property + def abscissae(self) -> Quantity: + match self.dataset_type: + case dataset_types.one_dim: + return self._data_contents['Q'] + case dataset_types.two_dim: + # Type hinting is a bit lacking. Assume each part of the zip is a scalar value. + data_contents = np.array(list(zip(self._data_contents['Qx'].value, self._data_contents['Qy'].value))) + # Use this value to extract units etc. Assume they will be the same for Qy. + reference_data_content = self._data_contents['Qx'] + # TODO: If this is a derived quantity then we are going to lose that + # information. + # + # TODO: Won't work when there's errors involved. On reflection, we + # probably want to avoid creating a new Quantity but at the moment I + # can't see a way around it. + return Quantity(data_contents, reference_data_content.units) + case dataset_types.sesans: + return self._data_contents["SpinEchoLength"] + case _: + None + + def __getitem__(self, item: str): + return self._data_contents[item] + + def summary(self, indent = " "): + s = f"{self.name}\n" + + for data in sorted(self._data_contents, reverse=True): + s += f"{indent}{data}\n" + + s += "Metadata:\n" + s += "\n" + s += self.metadata.summary() + + return s diff --git a/sasdata/data_backing.py b/sasdata/data_backing.py new file mode 100644 index 00000000..564f466a --- /dev/null +++ b/sasdata/data_backing.py @@ -0,0 +1,126 @@ +from typing import TypeVar, Self +from dataclasses import dataclass +from enum import Enum + +from sasdata.quantities.quantity import NamedQuantity + +DataType = TypeVar("DataType") + +""" Sasdata metadata tree """ + +def shorten_string(string): + lines = string.split("\n") + if len(lines) <= 1: + return string + else: + return lines[0][:30] + " ... " + lines[-1][-30:] + +@dataclass +class Dataset[DataType]: + name: str + data: DataType + attributes: dict[str, Self | str] + + def summary(self, indent_amount: int = 0, indent: str = " ") -> str: + + s = f"{indent*indent_amount}{self.name.split("/")[-1]}:\n" + s += f"{indent*(indent_amount+1)}{shorten_string(str(self.data))}\n" + for key in self.attributes: + value = self.attributes[key] + if isinstance(value, (Group, Dataset)): + value_string = value.summary(indent_amount+1, indent) + else: + value_string = f"{indent * (indent_amount+1)}{key}: {shorten_string(repr(value))}\n" + + s += value_string + + return s + +@dataclass +class Group: + name: str + children: dict[str, Self | Dataset] + + def summary(self, indent_amount: int=0, indent=" "): + s = f"{indent*indent_amount}{self.name.split("/")[-1]}:\n" + for key in self.children: + s += self.children[key].summary(indent_amount+1, indent) + + return s + +class Function: + """ Representation of a (data driven) function, such as I vs Q """ + + def __init__(self, abscissae: list[NamedQuantity], ordinate: NamedQuantity): + self.abscissae = abscissae + self.ordinate = ordinate + + +class FunctionType(Enum): + """ What kind of function is this, should not be relied upon to be perfectly descriptive + + The functions might be parametrised by more variables than the specification + """ + UNKNOWN = 0 + SCATTERING_INTENSITY_VS_Q = 1 + SCATTERING_INTENSITY_VS_Q_2D = 2 + SCATTERING_INTENSITY_VS_Q_3D = 3 + SCATTERING_INTENSITY_VS_ANGLE = 4 + UNKNOWN_METADATA = 20 + TRANSMISSION = 21 + POLARISATION_EFFICIENCY = 22 + UNKNOWN_REALSPACE = 30 + SESANS = 31 + CORRELATION_FUNCTION_1D = 32 + CORRELATION_FUNCTION_2D = 33 + CORRELATION_FUNCTION_3D = 34 + INTERFACE_DISTRIBUTION_FUNCTION = 35 + PROBABILITY_DISTRIBUTION = 40 + PROBABILITY_DENSITY = 41 + +def function_type_identification_key(names): + """ Create a key from the names of data objects that can be used to assign a function type""" + return ":".join([s.lower() for s in sorted(names)]) + +function_fields_to_type = [ + (["Q"], "I", FunctionType.SCATTERING_INTENSITY_VS_Q), + (["Qx", "Qy"], "I", FunctionType.SCATTERING_INTENSITY_VS_Q_2D), + (["Qx", "Qy", "Qz"], "I", FunctionType.SCATTERING_INTENSITY_VS_Q_3D), + (["Z"], "G", FunctionType.SESANS), + (["lambda"], "T", FunctionType.TRANSMISSION) +] + +function_fields_lookup = { + function_type_identification_key(inputs + [output]): function_type for inputs, output, function_type in function_fields_to_type +} + +def build_main_data(data: list[NamedQuantity]) -> Function: + names = [datum.name for datum in data] + identifier = function_type_identification_key(names) + + if identifier in function_fields_lookup: + function_type = function_fields_lookup[identifier] + else: + function_type = FunctionType.UNKNOWN + + match function_type: + case FunctionType.UNKNOWN: + pass + case _: + raise NotImplementedError("Unknown ") + +def key_tree(data: Group | Dataset, indent_amount=0, indent: str = " ") -> str: + """ Show a metadata tree, showing the names of they keys used to access them""" + s = "" + if isinstance(data, Group): + for key in data.children: + s += indent*indent_amount + key + "\n" + s += key_tree(data.children[key], indent_amount=indent_amount+1, indent=indent) + + if isinstance(data, Dataset): + s += indent*indent_amount + "[data]\n" + for key in data.attributes: + s += indent*indent_amount + key + "\n" + s += key_tree(data.attributes[key], indent_amount=indent_amount+1, indent=indent) + + return s \ No newline at end of file diff --git a/sasdata/dataloader/filereader.py b/sasdata/dataloader/filereader.py index 852efe9e..4ca5bce8 100644 --- a/sasdata/dataloader/filereader.py +++ b/sasdata/dataloader/filereader.py @@ -4,7 +4,6 @@ class """ -import pathlib import codecs import logging from abc import abstractmethod @@ -12,8 +11,7 @@ from typing import List, Union, Optional import numpy as np -from sasdata.data_util.loader_exceptions import NoKnownLoaderException, FileContentsException,\ - DataReaderException +from sasdata.data_util.loader_exceptions import NoKnownLoaderException from sasdata.dataloader.data_info import Data1D, Data2D, DataInfo, plottable_1D, plottable_2D,\ combine_data_info_with_plottable from sasdata.data_util.nxsunit import Converter diff --git a/sasdata/dataloader/readers/anton_paar_saxs_reader.py b/sasdata/dataloader/readers/anton_paar_saxs_reader.py index 6d7e4cee..28509f64 100644 --- a/sasdata/dataloader/readers/anton_paar_saxs_reader.py +++ b/sasdata/dataloader/readers/anton_paar_saxs_reader.py @@ -7,7 +7,7 @@ from lxml.etree import Element from sasdata.dataloader.readers.xml_reader import XMLreader -from sasdata.dataloader.data_info import plottable_1D, Data1D, DataInfo, Sample, Source +from sasdata.dataloader.data_info import plottable_1D, DataInfo, Sample, Source from sasdata.dataloader.data_info import Process, Aperture, Collimation, TransmissionSpectrum, Detector from sasdata.data_util.loader_exceptions import FileContentsException, DataReaderException diff --git a/sasdata/dataloader/readers/ascii_reader.py b/sasdata/dataloader/readers/ascii_reader.py index 5c44f2f6..2659c671 100644 --- a/sasdata/dataloader/readers/ascii_reader.py +++ b/sasdata/dataloader/readers/ascii_reader.py @@ -13,7 +13,6 @@ ############################################################################# import logging -import os from typing import Optional from sasdata.dataloader.filereader import FileReader diff --git a/sasdata/dataloader/readers/cansas_reader.py b/sasdata/dataloader/readers/cansas_reader.py index e53d391e..f2fdfed2 100644 --- a/sasdata/dataloader/readers/cansas_reader.py +++ b/sasdata/dataloader/readers/cansas_reader.py @@ -84,7 +84,7 @@ def get_file_contents(self): try: # Raises FileContentsException is_valid_cansas = self.load_file_and_schema(xml_file, '') - except FileContentsException as fc_exc: + except FileContentsException: msg = "CanSAS Reader could not load {}".format(xml_file) if self.extension not in self.ext: # If the file has no associated loader @@ -523,7 +523,7 @@ def process_process_data_object(self, tagname: str, data_point: float, attr: dic elif tagname == 'date' and self.parent_class == 'SASprocess': try: self.process.date = datetime.datetime.fromtimestamp(data_point) - except Exception as e: + except Exception: self.process.date = data_point elif tagname == 'SASprocessnote': self.process.notes.append(data_point) diff --git a/sasdata/dataloader/readers/cansas_reader_HDF5.py b/sasdata/dataloader/readers/cansas_reader_HDF5.py index 53873e30..dab5876b 100644 --- a/sasdata/dataloader/readers/cansas_reader_HDF5.py +++ b/sasdata/dataloader/readers/cansas_reader_HDF5.py @@ -6,13 +6,12 @@ import h5py import numpy as np import re -import os import traceback from typing import Any, Union, Optional from sasdata.dataloader.data_info import plottable_1D, plottable_2D, Data1D, Data2D, DataInfo, Process, Aperture,\ Collimation, TransmissionSpectrum, Detector -from sasdata.data_util.loader_exceptions import FileContentsException, DefaultReaderException +from sasdata.data_util.loader_exceptions import FileContentsException from sasdata.dataloader.filereader import FileReader, decode logger = logging.getLogger(__name__) diff --git a/sasdata/dataloader/readers/danse_reader.py b/sasdata/dataloader/readers/danse_reader.py index 33c4ed6b..0e2750ea 100644 --- a/sasdata/dataloader/readers/danse_reader.py +++ b/sasdata/dataloader/readers/danse_reader.py @@ -11,7 +11,6 @@ #This work benefited from DANSE software developed under NSF award DMR-0520547. #copyright 2008, University of Tennessee ############################################################################# -import math import os import logging @@ -101,7 +100,7 @@ def get_file_contents(self): size_x = int(toks[1]) elif toks[0] == "SIZE_Y": size_y = int(toks[1]) - except ValueError as e: + except ValueError: error_message += "Unable to parse {}. Default value used.\n".format(toks[0]) loaded_correctly = False @@ -119,7 +118,7 @@ def get_file_contents(self): err = float(toks[1]) data.append(val) error.append(err) - except ValueError as exc: + except ValueError: msg = "Unable to parse line {}: {}".format(line_num + data_start_line, data_str.strip()) raise FileContentsException(msg) diff --git a/sasdata/dataloader/readers/red2d_reader.py b/sasdata/dataloader/readers/red2d_reader.py index 8d7cb3b9..1c519ecc 100644 --- a/sasdata/dataloader/readers/red2d_reader.py +++ b/sasdata/dataloader/readers/red2d_reader.py @@ -8,7 +8,6 @@ #See the license text in license.txt #copyright 2008, University of Tennessee ###################################################################### -import os import time from typing import Any diff --git a/sasdata/dataloader/readers/sesans_reader.py b/sasdata/dataloader/readers/sesans_reader.py index e3bfcd33..a7da80f6 100644 --- a/sasdata/dataloader/readers/sesans_reader.py +++ b/sasdata/dataloader/readers/sesans_reader.py @@ -5,7 +5,6 @@ Jurrian Bakker """ -import os import numpy as np diff --git a/sasdata/dataloader/readers/tiff_reader.py b/sasdata/dataloader/readers/tiff_reader.py index 7869d46b..23fdfc66 100644 --- a/sasdata/dataloader/readers/tiff_reader.py +++ b/sasdata/dataloader/readers/tiff_reader.py @@ -40,7 +40,6 @@ def read(self, filename: str = None): """ try: import Image - import TiffImagePlugin Image._initialized=2 except: msg = "tiff_reader: could not load file. Missing Image module." diff --git a/sasdata/dataset_types.py b/sasdata/dataset_types.py new file mode 100644 index 00000000..10feee48 --- /dev/null +++ b/sasdata/dataset_types.py @@ -0,0 +1,82 @@ +""" Information used for providing guesses about what text based files contain """ + +from dataclasses import dataclass + +import sasdata.quantities.units as units + +# +# VERY ROUGH DRAFT - FOR PROTOTYPING PURPOSES +# + +@dataclass +class DatasetType: + name: str + required: list[str] + optional: list[str] + expected_orders: list[list[str]] + + +one_dim = DatasetType( + name="1D I vs Q", + required=["Q", "I"], + optional=["dI", "dQ", "Shadowfactor", "Qmean", "dQl", "dQw"], + expected_orders=[ + ["Q", "I", "dI"], + ["Q", "dQ", "I", "dI"]]) + +two_dim = DatasetType( + name="2D I vs Q", + required=["Qx", "Qy", "I"], + optional=["dQx", "dQy", "dI", "Qz", "ShadowFactor"], + expected_orders=[ + ["Qx", "Qy", "I"], + ["Qx", "Qy", "I", "dI"], + ["Qx", "Qy", "dQx", "dQy", "I", "dI"]]) + +sesans = DatasetType( + name="SESANS", + required=["SpinEchoLength", "Depolarisation", "Wavelength"], + optional=["Transmission", "Polarisation"], + expected_orders=[["z", "G"]]) + +dataset_types = {dataset.name for dataset in [one_dim, two_dim, sesans]} + + +# +# Some default units, this is not how they should be represented, some might not be correct +# +# The unit options should only be those compatible with the field +# + +unit_kinds = { + "Q": units.inverse_length, + "I": units.inverse_length, + "Qx": units.inverse_length, + "Qy": units.inverse_length, + "Qz": units.inverse_length, + "dI": units.inverse_length, + "dQ": units.inverse_length, + "dQx": units.inverse_length, + "dQy": units.inverse_length, + "dQz": units.inverse_length, + "SpinEchoLength": units.length, + "Depolarisation": units.inverse_volume, + "Wavelength": units.length, + "Transmission": units.dimensionless, + "Polarisation": units.dimensionless, + "shadow": units.dimensionless, + "temperature": units.temperature, + "magnetic field": units.magnetic_flux_density +} + +# +# Other possible fields. Ultimately, these should come out of the metadata structure +# + +metadata_fields = [ + "temperature", + "magnetic field", +] + + + diff --git a/sasdata/default_units.py b/sasdata/default_units.py new file mode 100644 index 00000000..e03f4c1a --- /dev/null +++ b/sasdata/default_units.py @@ -0,0 +1,31 @@ +# NOTE: This module will probably be a lot more involved once how this is getting into the configuration will be sorted. + +from sasdata.quantities.units import NamedUnit +import sasdata.quantities.units as unit +from sasdata.dataset_types import unit_kinds + +default_units = { + "Q": [unit.per_nanometer, unit.per_angstrom, unit.per_meter], + "I": [unit.per_centimeter, unit.per_meter], + "dQ": "Q", + "dI": "I", +} + + +def defaults_or_fallback(column_name: str) -> list[NamedUnit]: + value = default_units.get(column_name, unit_kinds[column_name].units) + if isinstance(value, str): + return defaults_or_fallback(value) + return value + + +def first_default_for_fallback(column_name: str) -> NamedUnit: + return defaults_or_fallback(column_name)[0] + + +def get_default_unit(column_name: str, unit_group: unit.UnitGroup): + value = first_default_for_fallback(column_name) + # Fallback to the first unit in the unit group if we don't have a default registered. + if value is None: + return unit_group.units[0] + return value diff --git a/sasdata/distributions.py b/sasdata/distributions.py new file mode 100644 index 00000000..6ad149e0 --- /dev/null +++ b/sasdata/distributions.py @@ -0,0 +1,11 @@ + + +class DistributionModel: + + + @property + def is_density(self) -> bool: + return False + + def standard_deviation(self) -> Quantity: + return NotImplementedError("Variance not implemented yet") diff --git a/sasdata/fair_database/__init__.py b/sasdata/fair_database/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sasdata/fair_database/data/__init__.py b/sasdata/fair_database/data/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sasdata/fair_database/data/admin.py b/sasdata/fair_database/data/admin.py new file mode 100644 index 00000000..1a5e431c --- /dev/null +++ b/sasdata/fair_database/data/admin.py @@ -0,0 +1,11 @@ +from django.contrib import admin +from data import models + +admin.site.register(models.DataFile) +admin.site.register(models.Session) +admin.site.register(models.PublishedState) +admin.site.register(models.DataSet) +admin.site.register(models.MetaData) +admin.site.register(models.Quantity) +admin.site.register(models.OperationTree) +admin.site.register(models.ReferenceQuantity) diff --git a/sasdata/fair_database/data/apps.py b/sasdata/fair_database/data/apps.py new file mode 100644 index 00000000..b882be95 --- /dev/null +++ b/sasdata/fair_database/data/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class DataConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "data" diff --git a/sasdata/fair_database/data/forms.py b/sasdata/fair_database/data/forms.py new file mode 100644 index 00000000..fde1813d --- /dev/null +++ b/sasdata/fair_database/data/forms.py @@ -0,0 +1,9 @@ +from django import forms +from data.models import DataFile + + +# Create the form class. +class DataFileForm(forms.ModelForm): + class Meta: + model = DataFile + fields = ["file", "is_public"] diff --git a/sasdata/fair_database/data/migrations/0001_initial.py b/sasdata/fair_database/data/migrations/0001_initial.py new file mode 100644 index 00000000..e8f7219a --- /dev/null +++ b/sasdata/fair_database/data/migrations/0001_initial.py @@ -0,0 +1,332 @@ +# Generated by Django 5.1.6 on 2025-04-23 18:08 + +import data.models +import django.core.files.storage +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="DataFile", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "is_public", + models.BooleanField( + default=False, help_text="opt in to make your data public" + ), + ), + ( + "file_name", + models.CharField( + blank=True, + default=None, + help_text="File name", + max_length=200, + null=True, + ), + ), + ( + "file", + models.FileField( + default=None, + help_text="This is a file", + storage=django.core.files.storage.FileSystemStorage(), + upload_to="uploaded_files", + ), + ), + ( + "current_user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "users", + models.ManyToManyField( + blank=True, related_name="+", to=settings.AUTH_USER_MODEL + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="DataSet", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "is_public", + models.BooleanField( + default=False, help_text="opt in to make your data public" + ), + ), + ("name", models.CharField(max_length=200)), + ( + "current_user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to=settings.AUTH_USER_MODEL, + ), + ), + ("files", models.ManyToManyField(to="data.datafile")), + ( + "users", + models.ManyToManyField( + blank=True, related_name="+", to=settings.AUTH_USER_MODEL + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="MetaData", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(default="Title", max_length=500)), + ("run", models.JSONField(default=data.models.empty_list)), + ("definition", models.TextField(blank=True, null=True)), + ("instrument", models.JSONField(blank=True, null=True)), + ("process", models.JSONField(default=data.models.empty_list)), + ("sample", models.JSONField(blank=True, null=True)), + ( + "dataset", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="metadata", + to="data.dataset", + ), + ), + ], + ), + migrations.CreateModel( + name="Quantity", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("value", models.JSONField()), + ("variance", models.JSONField()), + ("units", models.CharField(max_length=200)), + ("hash", models.IntegerField()), + ("label", models.CharField(max_length=50)), + ( + "dataset", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="data_contents", + to="data.dataset", + ), + ), + ], + ), + migrations.CreateModel( + name="OperationTree", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "operation", + models.CharField( + choices=[ + ("zero", "0 [Add.Id.]"), + ("one", "1 [Mul.Id.]"), + ("constant", "Constant"), + ("variable", "Variable"), + ("neg", "Neg"), + ("reciprocal", "Inv"), + ("add", "Add"), + ("sub", "Sub"), + ("mul", "Mul"), + ("div", "Div"), + ("pow", "Pow"), + ("transpose", "Transpose"), + ("dot", "Dot"), + ("matmul", "MatMul"), + ("tensor_product", "TensorProduct"), + ], + max_length=20, + ), + ), + ("parameters", models.JSONField(default=data.models.empty_dict)), + ("label", models.CharField(blank=True, max_length=10, null=True)), + ( + "child_operation", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="parent_operations", + to="data.operationtree", + ), + ), + ( + "quantity", + models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="operation_tree", + to="data.quantity", + ), + ), + ], + ), + migrations.CreateModel( + name="ReferenceQuantity", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("value", models.JSONField()), + ("variance", models.JSONField()), + ("units", models.CharField(max_length=200)), + ("hash", models.IntegerField()), + ( + "derived_quantity", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="references", + to="data.quantity", + ), + ), + ], + ), + migrations.CreateModel( + name="Session", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "is_public", + models.BooleanField( + default=False, help_text="opt in to make your data public" + ), + ), + ("title", models.CharField(max_length=200)), + ( + "current_user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "users", + models.ManyToManyField( + blank=True, related_name="+", to=settings.AUTH_USER_MODEL + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="PublishedState", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("published", models.BooleanField(default=False)), + ("doi", models.URLField()), + ( + "session", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="published_state", + to="data.session", + ), + ), + ], + ), + migrations.AddField( + model_name="dataset", + name="session", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="datasets", + to="data.session", + ), + ), + ] diff --git a/sasdata/fair_database/data/migrations/__init__.py b/sasdata/fair_database/data/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sasdata/fair_database/data/models.py b/sasdata/fair_database/data/models.py new file mode 100644 index 00000000..09bae3c0 --- /dev/null +++ b/sasdata/fair_database/data/models.py @@ -0,0 +1,232 @@ +from django.db import models +from django.contrib.auth.models import User +from django.core.files.storage import FileSystemStorage + + +# method for empty list default value +def empty_list(): + return [] + + +# method for empty dictionary default value +def empty_dict(): + return {} + + +class Data(models.Model): + """Base model for data with access-related information.""" + + # owner of the data + current_user = models.ForeignKey( + User, blank=True, null=True, on_delete=models.CASCADE, related_name="+" + ) + + # users that have been granted view access to the data + users = models.ManyToManyField(User, blank=True, related_name="+") + + # is the data public? + is_public = models.BooleanField( + default=False, help_text="opt in to make your data public" + ) + + class Meta: + abstract = True + + +class DataFile(Data): + """Database model for file contents.""" + + # file name + file_name = models.CharField( + max_length=200, default=None, blank=True, null=True, help_text="File name" + ) + + # imported data + # user can either import a file path or actual file + file = models.FileField( + blank=False, + default=None, + help_text="This is a file", + upload_to="uploaded_files", + storage=FileSystemStorage(), + ) + + +class DataSet(Data): + """Database model for a set of data and associated metadata.""" + + # dataset name + name = models.CharField(max_length=200) + + # associated files + files = models.ManyToManyField(DataFile) + + # session the dataset is a part of, if any + session = models.ForeignKey( + "Session", + on_delete=models.CASCADE, + related_name="datasets", + blank=True, + null=True, + ) + + # TODO: update based on SasData class in data.py + # type of dataset + # dataset_type = models.JSONField() + + +class Quantity(models.Model): + """Database model for data quantities such as the ordinate and abscissae.""" + + # data value + value = models.JSONField() + + # variance of the data + variance = models.JSONField() + + # units + units = models.CharField(max_length=200) + + # hash value + hash = models.IntegerField() + + # label, e.g. Q or I(Q) + label = models.CharField(max_length=50) + + # data set the quantity is a part of + dataset = models.ForeignKey( + DataSet, on_delete=models.CASCADE, related_name="data_contents" + ) + + +class ReferenceQuantity(models.Model): + """ + Database models for quantities referenced by variables in an OperationTree. + + Corresponds to the references dictionary in the QuantityHistory class in + sasdata/quantity.py. ReferenceQuantities should be essentially the same as + Quantities but with no operations performed on them and therefore no + OperationTree. + """ + + # data value + value = models.JSONField() + + # variance of the data + variance = models.JSONField() + + # units + units = models.CharField(max_length=200) + + # hash value + hash = models.IntegerField() + + # Quantity whose OperationTree this is a reference for + derived_quantity = models.ForeignKey( + Quantity, + related_name="references", + on_delete=models.CASCADE, + ) + + +# TODO: update based on changes in sasdata/metadata.py +class MetaData(models.Model): + """Database model for scattering metadata""" + + # title + title = models.CharField(max_length=500, default="Title") + + # run + run = models.JSONField(default=empty_list) + + # definition + definition = models.TextField(blank=True, null=True) + + # instrument + instrument = models.JSONField(blank=True, null=True) + + # process + process = models.JSONField(default=empty_list) + + # sample + sample = models.JSONField(blank=True, null=True) + + # associated dataset + dataset = models.OneToOneField( + DataSet, on_delete=models.CASCADE, related_name="metadata" + ) + + +class OperationTree(models.Model): + """Database model for tree of operations performed on a DataSet.""" + + # possible operations + OPERATION_CHOICES = { + "zero": "0 [Add.Id.]", + "one": "1 [Mul.Id.]", + "constant": "Constant", + "variable": "Variable", + "neg": "Neg", + "reciprocal": "Inv", + "add": "Add", + "sub": "Sub", + "mul": "Mul", + "div": "Div", + "pow": "Pow", + "transpose": "Transpose", + "dot": "Dot", + "matmul": "MatMul", + "tensor_product": "TensorProduct", + } + + # operation + operation = models.CharField(max_length=20, choices=OPERATION_CHOICES) + + # parameters + parameters = models.JSONField(default=empty_dict) + + # label (a or b) if the operation is a parameter of a child operation + # maintains ordering of binary operation parameters + label = models.CharField(max_length=10, blank=True, null=True) + + # operation this operation is a parameter for, if any + child_operation = models.ForeignKey( + "self", + on_delete=models.CASCADE, + related_name="parent_operations", + blank=True, + null=True, + ) + + # quantity the operation produces + # only set for base of tree (the quantity's most recent operation) + quantity = models.OneToOneField( + Quantity, + on_delete=models.CASCADE, + blank=True, + null=True, + related_name="operation_tree", + ) + + +class Session(Data): + """Database model for a project save state.""" + + # title + title = models.CharField(max_length=200) + + +class PublishedState(models.Model): + """Database model for a project published state.""" + + # published + published = models.BooleanField(default=False) + + # TODO: update doi as needed when DOI generation is implemented + # doi + doi = models.URLField() + + # session + session = models.OneToOneField( + Session, on_delete=models.CASCADE, related_name="published_state" + ) diff --git a/sasdata/fair_database/data/serializers.py b/sasdata/fair_database/data/serializers.py new file mode 100644 index 00000000..3eb71219 --- /dev/null +++ b/sasdata/fair_database/data/serializers.py @@ -0,0 +1,531 @@ +from django.core.exceptions import ObjectDoesNotExist +from rest_framework import serializers + +from data import models +from fair_database import permissions + + +# TODO: more custom validation, particularly for specific nested dictionary structures +# TODO: custom update methods for nested structures + + +# Determine if an operation does not have parent operations +def constant_or_variable(operation: str): + return operation in ["zero", "one", "constant", "variable"] + + +# Determine if an operation has two parent operations +def binary(operation: str): + return operation in ["add", "sub", "mul", "div", "dot", "matmul", "tensor_product"] + + +class DataFileSerializer(serializers.ModelSerializer): + """Serialization, deserialization, and validation for the DataFile model.""" + + class Meta: + model = models.DataFile + fields = "__all__" + + # TODO: check partial updates + # Check that private data has an owner + def validate(self, data): + if not self.context["is_public"] and not data["current_user"]: + raise serializers.ValidationError("private data must have an owner") + return data + + +class AccessManagementSerializer(serializers.Serializer): + """ + Serialization, deserialization, and validation for granting and revoking + access to instances of any exposed model. + """ + + # The username of a user + username = serializers.CharField(max_length=200, required=False) + # Whether that user has access + access = serializers.BooleanField() + + +class MetaDataSerializer(serializers.ModelSerializer): + """Serialization, deserialization, and validation for the MetaData model.""" + + # associated dataset + dataset = serializers.PrimaryKeyRelatedField( + queryset=models.DataSet, required=False, allow_null=True + ) + + class Meta: + model = models.MetaData + fields = "__all__" + + # Serialize an entry in MetaData + def to_representation(self, instance): + data = super().to_representation(instance) + if "dataset" in data: + data.pop("dataset") + return data + + +class OperationTreeSerializer(serializers.ModelSerializer): + """Serialization, deserialization, and validation for the OperationTree model.""" + + # associated quantity, for root operation + quantity = serializers.PrimaryKeyRelatedField( + queryset=models.Quantity, required=False, allow_null=True + ) + # operation this operation is a parameter for, for non-root operations + child_operation = serializers.PrimaryKeyRelatedField( + queryset=models.OperationTree, required=False, allow_null=True + ) + # parameter label, for non-root operations + label = serializers.CharField(max_length=10, required=False) + + class Meta: + model = models.OperationTree + fields = ["operation", "parameters", "quantity", "label", "child_operation"] + + # Validate parent operations + def validate_parameters(self, value): + if "a" in value: + serializer = OperationTreeSerializer(data=value["a"]) + serializer.is_valid(raise_exception=True) + if "b" in value: + serializer = OperationTreeSerializer(data=value["b"]) + serializer.is_valid(raise_exception=True) + return value + + # Check that the operation has the correct parameters + def validate(self, data): + expected_parameters = { + "zero": [], + "one": [], + "constant": ["value"], + "variable": ["hash_value", "name"], + "neg": ["a"], + "reciprocal": ["a"], + "add": ["a", "b"], + "sub": ["a", "b"], + "mul": ["a", "b"], + "div": ["a", "b"], + "pow": ["a", "power"], + "transpose": ["a", "axes"], + "dot": ["a", "b"], + "matmul": ["a", "b"], + "tensor_product": ["a", "b", "a_index", "b_index"], + } + + for parameter in expected_parameters[data["operation"]]: + if parameter not in data["parameters"]: + raise serializers.ValidationError( + data["operation"] + " requires parameter " + parameter + ) + + return data + + # Serialize an OperationTree instance + def to_representation(self, instance): + data = {"operation": instance.operation, "parameters": instance.parameters} + for parent_operation in instance.parent_operations.all(): + data["parameters"][parent_operation.label] = self.to_representation( + parent_operation + ) + return data + + # Create an OperationTree instance + def create(self, validated_data): + parent_operation1 = None + parent_operation2 = None + if not constant_or_variable(validated_data["operation"]): + parent_operation1 = validated_data["parameters"].pop("a") + parent_operation1["label"] = "a" + if binary(validated_data["operation"]): + parent_operation2 = validated_data["parameters"].pop("b") + parent_operation2["label"] = "b" + operation_tree = models.OperationTree.objects.create(**validated_data) + if parent_operation1: + parent_operation1["child_operation"] = operation_tree + OperationTreeSerializer.create( + OperationTreeSerializer(), validated_data=parent_operation1 + ) + if parent_operation2: + parent_operation2["child_operation"] = operation_tree + OperationTreeSerializer.create( + OperationTreeSerializer(), validated_data=parent_operation2 + ) + return operation_tree + + +class ReferenceQuantitySerializer(serializers.ModelSerializer): + """Serialization, deserialization, and validation for the ReferenceQuantity model.""" + + # quantity whose operation tree this is a reference for + derived_quantity = serializers.PrimaryKeyRelatedField( + queryset=models.Quantity, required=False + ) + + class Meta: + model = models.ReferenceQuantity + fields = ["value", "variance", "units", "hash", "derived_quantity"] + + # serialize a ReferenceQuantity instance + def to_representation(self, instance): + data = super().to_representation(instance) + if "derived_quantity" in data: + data.pop("derived_quantity") + return data + + # create a ReferenceQuantity instance + def create(self, validated_data): + if "label" in validated_data: + validated_data.pop("label") + if "history" in validated_data: + validated_data.pop("history") + return models.ReferenceQuantity.objects.create(**validated_data) + + +class QuantitySerializer(serializers.ModelSerializer): + """Serialization, deserialization, and validation for the Quantity model.""" + + # associated operation tree + operation_tree = OperationTreeSerializer(read_only=False, required=False) + # references for the operation tree + references = ReferenceQuantitySerializer(many=True, read_only=False, required=False) + # quantity label + label = serializers.CharField(max_length=20) + # dataset this is a part of + dataset = serializers.PrimaryKeyRelatedField( + queryset=models.DataSet, required=False, allow_null=True + ) + # serialized JSON form of operation tree and references + history = serializers.JSONField(required=False, allow_null=True) + + class Meta: + model = models.Quantity + fields = [ + "value", + "variance", + "units", + "hash", + "operation_tree", + "references", + "label", + "dataset", + "history", + ] + + # validate references + def validate_history(self, value): + if "references" in value: + for ref in value["references"]: + serializer = ReferenceQuantitySerializer(data=ref) + serializer.is_valid(raise_exception=True) + + # TODO: should variable-only history be assumed to refer to the same Quantity and ignored? + # Extract operation tree from history + def to_internal_value(self, data): + if "history" in data: + data_copy = data.copy() + if "operation_tree" in data["history"]: + operations = data["history"]["operation_tree"] + if ( + "operation" in operations + and not operations["operation"] == "variable" + ): + data_copy["operation_tree"] = operations + return_data = super().to_internal_value(data_copy) + return_data["history"] = data["history"] + return return_data + else: + return super().to_internal_value(data_copy) + return super().to_internal_value(data) + + # Serialize a Quantity instance + def to_representation(self, instance): + data = super().to_representation(instance) + if "dataset" in data: + data.pop("dataset") + if "derived_quantity" in data: + data.pop("derived_quantity") + data["history"] = {} + data["history"]["operation_tree"] = data.pop("operation_tree") + data["history"]["references"] = data.pop("references") + return data + + # Create a Quantity instance + def create(self, validated_data): + operations_tree = None + references = None + if "operation_tree" in validated_data: + operations_tree = validated_data.pop("operation_tree") + if "history" in validated_data: + history = validated_data.pop("history") + if history and "references" in history: + references = history.pop("references") + quantity = models.Quantity.objects.create(**validated_data) + if operations_tree: + operations_tree["quantity"] = quantity + OperationTreeSerializer.create( + OperationTreeSerializer(), validated_data=operations_tree + ) + if references: + for ref in references: + ref["derived_quantity"] = quantity + ReferenceQuantitySerializer.create( + ReferenceQuantitySerializer(), validated_data=ref + ) + return quantity + + +class DataSetSerializer(serializers.ModelSerializer): + """Serialization, deserialization, and validation for the DataSet model.""" + + # associated metadata + metadata = MetaDataSerializer(read_only=False) + # associated files + files = serializers.PrimaryKeyRelatedField( + required=False, many=True, allow_null=True, queryset=models.DataFile.objects + ) + # quantities that make up the dataset + data_contents = QuantitySerializer(many=True, read_only=False) + # session the dataset is a part of, if any + session = serializers.PrimaryKeyRelatedField( + queryset=models.Session, required=False, allow_null=True + ) + # TODO: handle files better + + class Meta: + model = models.DataSet + fields = [ + "id", + "name", + "files", + "metadata", + "data_contents", + "is_public", + "current_user", + "users", + "session", + ] + + # Serialize a DataSet instance + def to_representation(self, instance): + data = super().to_representation(instance) + if "request" in self.context: + files = [ + file.id + for file in instance.files.all() + if ( + file.is_public + or permissions.has_access(self.context["request"], file) + ) + ] + data["files"] = files + return data + + # Check that files exist and user has access to them + def validate_files(self, value): + for file in value: + if not file.is_public and not permissions.has_access( + self.context["request"], file + ): + raise serializers.ValidationError( + "You do not have access to file " + str(file.id) + ) + return value + + # Check that private data has an owner + def validate(self, data): + if ( + not self.context["request"].user.is_authenticated + and "is_public" in data + and not data["is_public"] + ): + raise serializers.ValidationError("private data must have an owner") + if "current_user" in data and ( + data["current_user"] == "" or data["current_user"] is None + ): + if "is_public" in data: + if not data["is_public"]: + raise serializers.ValidationError("private data must have an owner") + else: + if not self.instance.is_public: + raise serializers.ValidationError("private data must have an owner") + return data + + # Create a DataSet instance + def create(self, validated_data): + files = [] + if self.context["request"].user.is_authenticated: + validated_data["current_user"] = self.context["request"].user + metadata_raw = validated_data.pop("metadata") + data_contents = validated_data.pop("data_contents") + if "files" in validated_data: + files = validated_data.pop("files") + dataset = models.DataSet.objects.create(**validated_data) + dataset.files.set(files) + metadata_raw["dataset"] = dataset + MetaDataSerializer.create(MetaDataSerializer(), validated_data=metadata_raw) + for d in data_contents: + d["dataset"] = dataset + QuantitySerializer.create(QuantitySerializer(), validated_data=d) + return dataset + + # TODO: account for updating other attributes + # Update a DataSet instance + def update(self, instance, validated_data): + if "metadata" in validated_data: + metadata_raw = validated_data.pop("metadata") + new_metadata = MetaDataSerializer.update( + MetaDataSerializer(), instance.metadata, validated_data=metadata_raw + ) + instance.metadata = new_metadata + instance.save() + return super().update(instance, validated_data) + + +class PublishedStateSerializer(serializers.ModelSerializer): + """Serialization, deserialization, and validation for the PublishedState model.""" + + # associated session + session = serializers.PrimaryKeyRelatedField( + queryset=models.Session.objects, required=False, allow_null=True + ) + + class Meta: + model = models.PublishedState + fields = "__all__" + + # check that session does not already have a published state + def validate_session(self, value): + try: + published = value.published_state + if published is not None: + raise serializers.ValidationError( + "Only one published state per session" + ) + except models.Session.published_state.RelatedObjectDoesNotExist: + return value + + # set a placeholder DOI + def to_internal_value(self, data): + data_copy = data.copy() + data_copy["doi"] = "http://127.0.0.1:8000/v1/data/session/" + return super().to_internal_value(data_copy) + + # create a PublishedState instance + def create(self, validated_data): + # TODO: generate DOI + validated_data["doi"] = ( + "http://127.0.0.1:8000/v1/data/session/" + + str(validated_data["session"].id) + + "/" + ) + return models.PublishedState.objects.create(**validated_data) + + +class PublishedStateUpdateSerializer(serializers.ModelSerializer): + """Serialization for PublishedState updates. Restricts changes to published field.""" + + class Meta: + model = models.PublishedState + fields = ["published"] + + +class SessionSerializer(serializers.ModelSerializer): + """Serialization, deserialization, and validation for the Session model.""" + + # datasets that make up the session + datasets = DataSetSerializer(read_only=False, many=True) + # associated published state, if any + published_state = PublishedStateSerializer(read_only=False, required=False) + + class Meta: + model = models.Session + fields = [ + "id", + "title", + "published_state", + "datasets", + "current_user", + "is_public", + "users", + ] + + # disallow private unowned sessions + def validate(self, data): + if ( + not self.context["request"].user.is_authenticated + and "is_public" in data + and not data["is_public"] + ): + raise serializers.ValidationError("private sessions must have an owner") + if "current_user" in data and data["current_user"] == "": + if "is_public" in data: + if not "is_public": + raise serializers.ValidationError( + "private sessions must have an owner" + ) + else: + if not self.instance.is_public: + raise serializers.ValidationError( + "private sessions must have an owner" + ) + return data + + # propagate is_public to datasets + def to_internal_value(self, data): + data_copy = data.copy() + if "is_public" in data: + if "datasets" in data: + for dataset in data_copy["datasets"]: + dataset["is_public"] = data["is_public"] + return super().to_internal_value(data_copy) + + # serialize a session instance + def to_representation(self, instance): + session = super().to_representation(instance) + for dataset in session["datasets"]: + dataset.pop("session") + return session + + # Create a Session instance + def create(self, validated_data): + published_state = None + if self.context["request"].user.is_authenticated: + validated_data["current_user"] = self.context["request"].user + if "published_state" in validated_data: + published_state = validated_data.pop("published_state") + datasets = validated_data.pop("datasets") + session = models.Session.objects.create(**validated_data) + if published_state: + published_state["session"] = session + PublishedStateSerializer.create( + PublishedStateSerializer(), validated_data=published_state + ) + for dataset in datasets: + dataset["session"] = session + DataSetSerializer.create( + DataSetSerializer(context=self.context), validated_data=dataset + ) + return session + + # update a session instance + def update(self, instance, validated_data): + if "is_public" in validated_data: + for dataset in instance.datasets.all(): + dataset.is_public = validated_data["is_public"] + dataset.save() + if "published_state" in validated_data: + pb_raw = validated_data.pop("published_state") + try: + PublishedStateUpdateSerializer.update( + PublishedStateUpdateSerializer(), + instance.published_state, + validated_data=pb_raw, + ) + except ObjectDoesNotExist: + pb_raw["session"] = instance + PublishedStateSerializer.create( + PublishedStateSerializer(), validated_data=pb_raw + ) + return super().update(instance, validated_data) diff --git a/sasdata/fair_database/data/test/__init__.py b/sasdata/fair_database/data/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sasdata/fair_database/data/test/test_datafile.py b/sasdata/fair_database/data/test/test_datafile.py new file mode 100644 index 00000000..9c9aae85 --- /dev/null +++ b/sasdata/fair_database/data/test/test_datafile.py @@ -0,0 +1,444 @@ +import os +import shutil + +from django.conf import settings +from django.test import TestCase +from django.db.models import Max +from django.contrib.auth.models import User +from rest_framework.test import APIClient, APITestCase +from rest_framework import status + +from data.models import DataFile + + +# path to a file in example_data/1d_data +def find(filename): + return os.path.join( + os.path.dirname(__file__), "../../../example_data/1d_data", filename + ) + + +class TestLists(TestCase): + """Test get methods for DataFile.""" + + @classmethod + def setUpTestData(cls): + cls.public_test_data = DataFile.objects.create( + id=1, file_name="cyl_400_40.txt", is_public=True + ) + cls.public_test_data.file.save( + "cyl_400_40.txt", open(find("cyl_400_40.txt"), "rb") + ) + cls.user = User.objects.create_user( + username="testUser", password="secret", id=2 + ) + cls.private_test_data = DataFile.objects.create( + id=3, current_user=cls.user, file_name="cyl_400_20.txt", is_public=False + ) + cls.private_test_data.file.save( + "cyl_400_20.txt", open(find("cyl_400_20.txt"), "rb") + ) + cls.client_authenticated = APIClient() + cls.client_authenticated.force_authenticate(user=cls.user) + + # Test list public data + def test_does_list_public(self): + request = self.client_authenticated.get("/v1/data/file/") + self.assertEqual( + request.data, + {"public_data_ids": {1: "cyl_400_40.txt", 3: "cyl_400_20.txt"}}, + ) + + # Test list a user's private data + def test_does_list_user(self): + request = self.client_authenticated.get( + "/v1/data/file/", data={"username": "testUser"}, user=self.user + ) + self.assertEqual(request.data, {"user_data_ids": {3: "cyl_400_20.txt"}}) + + # Test list another user's public data + def test_list_other_user(self): + client_unauthenticated = APIClient() + request = client_unauthenticated.get( + "/v1/data/file/", data={"username": "testUser"}, user=self.user + ) + self.assertEqual(request.data, {"user_data_ids": {}}) + + # Test list a nonexistent user's data + def test_list_nonexistent_user(self): + request = self.client_authenticated.get( + "/v1/data/file/", data={"username": "fakeUser"} + ) + self.assertEqual(request.status_code, status.HTTP_404_NOT_FOUND) + + # Test loading a public data file + def test_does_load_data_info_public(self): + request = self.client_authenticated.get("/v1/data/file/1/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + + # Test loading private data with authorization + def test_does_load_data_info_private(self): + request = self.client_authenticated.get("/v1/data/file/3/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + + # Test loading data that does not exist + def test_load_data_info_nonexistent(self): + request = self.client_authenticated.get("/v1/data/file/5/") + self.assertEqual(request.status_code, status.HTTP_404_NOT_FOUND) + + @classmethod + def tearDownClass(cls): + cls.public_test_data.delete() + cls.private_test_data.delete() + cls.user.delete() + shutil.rmtree(settings.MEDIA_ROOT) + + +class TestingDatabase(APITestCase): + """Test non-get methods for DataFile.""" + + @classmethod + def setUpTestData(cls): + cls.user = User.objects.create_user( + username="testUser", password="secret", id=1 + ) + cls.data = DataFile.objects.create( + id=1, current_user=cls.user, file_name="cyl_400_20.txt", is_public=False + ) + cls.data.file.save("cyl_400_20.txt", open(find("cyl_400_20.txt"), "rb")) + cls.client_authenticated = APIClient() + cls.client_authenticated.force_authenticate(user=cls.user) + cls.client_unauthenticated = APIClient() + + # Test data upload creates data in database + def test_is_data_being_created(self): + file = open(find("cyl_400_40.txt"), "rb") + data = {"is_public": False, "file": file} + request = self.client_authenticated.post("/v1/data/file/", data=data) + max_id = DataFile.objects.aggregate(Max("id"))["id__max"] + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertEqual( + request.data, + { + "current_user": "testUser", + "authenticated": True, + "file_id": max_id, + "file_alternative_name": "cyl_400_40.txt", + "is_public": False, + }, + ) + DataFile.objects.get(id=max_id).delete() + + # Test data upload w/out authenticated user + def test_is_data_being_created_no_user(self): + file = open(find("cyl_testdata.txt"), "rb") + data = {"is_public": True, "file": file} + request = self.client_unauthenticated.post("/v1/data/file/", data=data) + max_id = DataFile.objects.aggregate(Max("id"))["id__max"] + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertEqual( + request.data, + { + "current_user": "", + "authenticated": False, + "file_id": max_id, + "file_alternative_name": "cyl_testdata.txt", + "is_public": True, + }, + ) + DataFile.objects.get(id=max_id).delete() + + # Test whether a user can overwrite data by specifying an in-use id + def test_no_data_overwrite(self): + file = open(find("apoferritin.txt")) + data = {"is_public": True, "file": file, id: 1} + request = self.client_authenticated.post("/v1/data/file/", data=data) + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertEqual(DataFile.objects.get(id=1).file_name, "cyl_400_20.txt") + max_id = DataFile.objects.aggregate(Max("id"))["id__max"] + self.assertEqual( + request.data, + { + "current_user": "testUser", + "authenticated": True, + "file_id": max_id, + "file_alternative_name": "apoferritin.txt", + "is_public": True, + }, + ) + DataFile.objects.get(id=max_id).delete() + + # Test updating file + def test_does_file_upload_update(self): + file = open(find("cyl_testdata1.txt")) + data = {"file": file, "is_public": False} + request = self.client_authenticated.put("/v1/data/file/1/", data=data) + self.assertEqual( + request.data, + { + "current_user": "testUser", + "authenticated": True, + "file_id": 1, + "file_alternative_name": "cyl_testdata1.txt", + "is_public": False, + }, + ) + self.data.file.save("cyl_400_20.txt", open(find("cyl_400_20.txt"), "rb")) + self.data.file_name = "cyl_400_20.txt" + + # Test updating a public file + def test_public_file_upload_update(self): + data_object = DataFile.objects.create( + id=3, current_user=self.user, file_name="cyl_testdata2.txt", is_public=True + ) + data_object.file.save( + "cyl_testdata2.txt", open(find("cyl_testdata2.txt"), "rb") + ) + file = open(find("conalbumin.txt")) + data = {"file": file, "is_public": True} + request = self.client_authenticated.put("/v1/data/file/3/", data=data) + self.assertEqual( + request.data, + { + "current_user": "testUser", + "authenticated": True, + "file_id": 3, + "file_alternative_name": "conalbumin.txt", + "is_public": True, + }, + ) + data_object.delete() + + # Test file upload update fails when unauthorized + def test_unauthorized_file_upload_update(self): + file = open(find("cyl_400_40.txt")) + data = {"file": file, "is_public": False} + request = self.client_unauthenticated.put("/v1/data/file/1/", data=data) + self.assertEqual(request.status_code, status.HTTP_401_UNAUTHORIZED) + + # Test update nonexistent file fails + def test_file_upload_update_not_found(self): + file = open(find("cyl_400_40.txt")) + data = {"file": file, "is_public": False} + request = self.client_unauthenticated.put("/v1/data/file/5/", data=data) + self.assertEqual(request.status_code, status.HTTP_404_NOT_FOUND) + + # Test file download + def test_does_download(self): + request = self.client_authenticated.get( + "/v1/data/file/1/", data={"download": True} + ) + file_contents = b"".join(request.streaming_content) + test_file = open(find("cyl_400_20.txt"), "rb") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual(file_contents, test_file.read()) + + # Test file download fails when unauthorized + def test_unauthorized_download(self): + request2 = self.client_unauthenticated.get( + "/v1/data/file/1/", data={"download": True} + ) + self.assertEqual(request2.status_code, status.HTTP_401_UNAUTHORIZED) + + # Test download nonexistent file + def test_download_nonexistent(self): + request = self.client_authenticated.get( + "/v1/data/file/5/", data={"download": True} + ) + self.assertEqual(request.status_code, status.HTTP_404_NOT_FOUND) + + # Test deleting a file + def test_delete(self): + DataFile.objects.create( + id=6, current_user=self.user, file_name="test.txt", is_public=False + ) + request = self.client_authenticated.delete("/v1/data/file/6/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertFalse(DataFile.objects.filter(pk=6).exists()) + + # Test deleting a file fails when unauthorized + def test_delete_unauthorized(self): + request = self.client_unauthenticated.delete("/v1/data/file/1/") + self.assertEqual(request.status_code, status.HTTP_401_UNAUTHORIZED) + + @classmethod + def tearDownClass(cls): + cls.user.delete() + cls.data.delete() + shutil.rmtree(settings.MEDIA_ROOT) + + +class TestAccessManagement(TestCase): + """Test viewing and managing access for a file.""" + + @classmethod + def setUpTestData(cls): + cls.user1 = User.objects.create_user(username="testUser", password="secret") + cls.user2 = User.objects.create_user(username="testUser2", password="secret2") + cls.private_test_data = DataFile.objects.create( + id=1, current_user=cls.user1, file_name="cyl_400_40.txt", is_public=False + ) + cls.private_test_data.file.save( + "cyl_400_40.txt", open(find("cyl_400_40.txt"), "rb") + ) + cls.shared_test_data = DataFile.objects.create( + id=2, current_user=cls.user1, file_name="cyl_400_20.txt", is_public=False + ) + cls.shared_test_data.file.save( + "cyl_400_20.txt", open(find("cyl_400_20.txt"), "rb") + ) + cls.shared_test_data.users.add(cls.user2) + cls.client_owner = APIClient() + cls.client_owner.force_authenticate(cls.user1) + cls.client_other = APIClient() + cls.client_other.force_authenticate(cls.user2) + + # test viewing no one with access + def test_view_no_access(self): + request = self.client_owner.get("/v1/data/file/1/users/") + data = { + "file": 1, + "file_name": "cyl_400_40.txt", + "is_public": False, + "users": [], + } + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual(request.data, data) + + # test viewing list of users with access + def test_view_access(self): + request = self.client_owner.get("/v1/data/file/2/users/") + data = { + "file": 2, + "file_name": "cyl_400_20.txt", + "is_public": False, + "users": ["testUser2"], + } + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual(request.data, data) + + # test granting another user access to private data + def test_grant_access(self): + data = {"username": "testUser2", "access": True} + request1 = self.client_owner.put("/v1/data/file/1/users/", data=data) + request2 = self.client_other.get("/v1/data/file/1/") + self.assertEqual(request1.status_code, status.HTTP_200_OK) + self.assertEqual(request2.status_code, status.HTTP_200_OK) + self.assertEqual( + request1.data, + { + "username": "testUser2", + "file": 1, + "file_name": "cyl_400_40.txt", + "access": True, + }, + ) + + # test removing another user's access to private data + def test_remove_access(self): + data = {"username": "testUser2", "access": False} + request1 = self.client_other.get("/v1/data/file/2/") + request2 = self.client_owner.put("/v1/data/file/2/users/", data=data) + request3 = self.client_other.get("/v1/data/file/2/") + self.assertEqual(request1.status_code, status.HTTP_200_OK) + self.assertEqual(request2.status_code, status.HTTP_200_OK) + self.assertEqual(request3.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual( + request2.data, + { + "username": "testUser2", + "file": 2, + "file_name": "cyl_400_20.txt", + "access": False, + }, + ) + + # test removing access from a user that already lacks access + def test_remove_no_access(self): + data = {"username": "testUser2", "access": False} + request1 = self.client_other.get("/v1/data/file/1/") + request2 = self.client_owner.put("/v1/data/file/1/users/", data=data) + request3 = self.client_other.get("/v1/data/file/1/") + self.assertEqual(request1.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request2.status_code, status.HTTP_200_OK) + self.assertEqual(request3.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual( + request2.data, + { + "username": "testUser2", + "file": 1, + "file_name": "cyl_400_40.txt", + "access": False, + }, + ) + + # test owner's access cannot be removed + def test_cant_revoke_own_access(self): + data = {"username": "testUser", "access": False} + request1 = self.client_owner.put("/v1/data/file/1/users/", data=data) + request2 = self.client_owner.get("/v1/data/file/1/") + self.assertEqual(request1.status_code, status.HTTP_200_OK) + self.assertEqual(request2.status_code, status.HTTP_200_OK) + self.assertEqual( + request1.data, + { + "username": "testUser", + "file": 1, + "file_name": "cyl_400_40.txt", + "access": True, + }, + ) + + # test giving access to a user that already has access + def test_grant_existing_access(self): + data = {"username": "testUser2", "access": True} + request1 = self.client_other.get("/v1/data/file/2/") + request2 = self.client_owner.put("/v1/data/file/2/users/", data=data) + request3 = self.client_other.get("/v1/data/file/2/") + self.assertEqual(request1.status_code, status.HTTP_200_OK) + self.assertEqual(request2.status_code, status.HTTP_200_OK) + self.assertEqual(request3.status_code, status.HTTP_200_OK) + self.assertEqual( + request2.data, + { + "username": "testUser2", + "file": 2, + "file_name": "cyl_400_20.txt", + "access": True, + }, + ) + + # test that access is read-only for the file + def test_no_edit_access(self): + data = {"is_public": True} + request = self.client_other.put("/v1/data/file/2/", data=data) + self.assertEqual(request.status_code, status.HTTP_403_FORBIDDEN) + self.assertFalse(self.shared_test_data.is_public) + + # test that only the owner can view who has access + def test_only_view_access_to_owned_file(self): + request1 = self.client_other.get("/v1/data/file/1/users/") + request2 = self.client_other.get("/v1/data/file/2/users/") + self.assertEqual(request1.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request2.status_code, status.HTTP_403_FORBIDDEN) + + # test that only the owner can change access + def test_only_edit_access_to_owned_file(self): + data1 = {"username": "testUser2", "access": True} + data2 = {"username": "testUser1", "access": False} + request1 = self.client_other.put("/v1/data/file/1/users/", data=data1) + request2 = self.client_other.put("/v1/data/file/2/users/", data=data2) + request3 = self.client_other.get("/v1/data/file/1/") + request4 = self.client_owner.get("/v1/data/file/2/") + self.assertEqual(request1.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request2.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request3.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request4.status_code, status.HTTP_200_OK) + + @classmethod + def tearDownClass(cls): + cls.user1.delete() + cls.user2.delete() + cls.private_test_data.delete() + cls.shared_test_data.delete() + shutil.rmtree(settings.MEDIA_ROOT) diff --git a/sasdata/fair_database/data/test/test_dataset.py b/sasdata/fair_database/data/test/test_dataset.py new file mode 100644 index 00000000..414ea72a --- /dev/null +++ b/sasdata/fair_database/data/test/test_dataset.py @@ -0,0 +1,737 @@ +import os +import shutil + +from django.conf import settings +from django.contrib.auth.models import User +from django.db.models import Max +from rest_framework.test import APIClient, APITestCase +from rest_framework import status + +from data.models import DataFile, DataSet, MetaData, OperationTree, Quantity + + +# path to a file in example_data/1d_data +def find(filename): + return os.path.join( + os.path.dirname(__file__), "../../../example_data/1d_data", filename + ) + + +class TestDataSet(APITestCase): + """Test HTTP methods of DataSetView.""" + + @classmethod + def setUpTestData(cls): + cls.empty_metadata = { + "title": "New Metadata", + "run": ["X"], + "description": "test", + "instrument": {}, + "process": {}, + "sample": {}, + } + cls.empty_data = [ + { + "value": 0, + "variance": 0, + "units": "no", + "hash": 0, + "label": "test", + "history": {"operation_tree": {}, "references": []}, + } + ] + cls.user1 = User.objects.create_user( + id=1, username="testUser1", password="secret" + ) + cls.user2 = User.objects.create_user( + id=2, username="testUser2", password="secret" + ) + cls.user3 = User.objects.create_user( + id=3, username="testUser3", password="secret" + ) + cls.public_dataset = DataSet.objects.create( + id=1, + current_user=cls.user1, + is_public=True, + name="Dataset 1", + ) + cls.private_dataset = DataSet.objects.create( + id=2, current_user=cls.user1, name="Dataset 2" + ) + cls.unowned_dataset = DataSet.objects.create( + id=3, is_public=True, name="Dataset 3" + ) + cls.private_dataset.users.add(cls.user3) + cls.auth_client1 = APIClient() + cls.auth_client2 = APIClient() + cls.auth_client3 = APIClient() + cls.auth_client1.force_authenticate(cls.user1) + cls.auth_client2.force_authenticate(cls.user2) + cls.auth_client3.force_authenticate(cls.user3) + + # Test a user can list their own private data + def test_list_private(self): + request = self.auth_client1.get("/v1/data/set/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + {"dataset_ids": {1: "Dataset 1", 2: "Dataset 2", 3: "Dataset 3"}}, + ) + + # Test a user can see others' public but not private data in list + def test_list_public(self): + request = self.auth_client2.get("/v1/data/set/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, {"dataset_ids": {1: "Dataset 1", 3: "Dataset 3"}} + ) + + # Test a user can see private data they have been granted access to + def test_list_granted_access(self): + request = self.auth_client3.get("/v1/data/set/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + {"dataset_ids": {1: "Dataset 1", 2: "Dataset 2", 3: "Dataset 3"}}, + ) + + # Test an unauthenticated user can list public data + def test_list_unauthenticated(self): + request = self.client.get("/v1/data/set/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, {"dataset_ids": {1: "Dataset 1", 3: "Dataset 3"}} + ) + + # Test a user can see all data listed by their username + def test_list_username(self): + request = self.auth_client1.get("/v1/data/set/", data={"username": "testUser1"}) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, {"dataset_ids": {1: "Dataset 1", 2: "Dataset 2"}} + ) + + # Test a user can list public data by another user's username + def test_list_username_2(self): + request = self.auth_client1.get("/v1/data/set/", {"username": "testUser2"}) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual(request.data, {"dataset_ids": {}}) + + # Test an unauthenticated user can list public data by a username + def test_list_username_unauthenticated(self): + request = self.client.get("/v1/data/set/", {"username": "testUser1"}) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual(request.data, {"dataset_ids": {1: "Dataset 1"}}) + + # Test listing by a username that doesn't exist + def test_list_wrong_username(self): + request = self.auth_client1.get("/v1/data/set/", {"username": "fakeUser1"}) + self.assertEqual(request.status_code, status.HTTP_404_NOT_FOUND) + + # TODO: test listing by other parameters if functionality is added for that + + # Test creating a dataset with associated metadata + def test_dataset_created(self): + dataset = { + "name": "New Dataset", + "metadata": self.empty_metadata, + "data_contents": self.empty_data, + } + request = self.auth_client1.post("/v1/data/set/", data=dataset, format="json") + max_id = DataSet.objects.aggregate(Max("id"))["id__max"] + new_dataset = DataSet.objects.get(id=max_id) + new_metadata = new_dataset.metadata + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertEqual( + request.data, + { + "dataset_id": max_id, + "name": "New Dataset", + "authenticated": True, + "current_user": "testUser1", + "is_public": False, + }, + ) + self.assertEqual(new_dataset.name, "New Dataset") + self.assertEqual(new_metadata.title, "New Metadata") + self.assertEqual(new_dataset.current_user.username, "testUser1") + new_dataset.delete() + new_metadata.delete() + + # Test creating a dataset while unauthenticated + def test_dataset_created_unauthenticated(self): + dataset = { + "name": "New Dataset", + "metadata": self.empty_metadata, + "is_public": True, + "data_contents": self.empty_data, + } + request = self.client.post("/v1/data/set/", data=dataset, format="json") + max_id = DataSet.objects.aggregate(Max("id"))["id__max"] + new_dataset = DataSet.objects.get(id=max_id) + new_metadata = new_dataset.metadata + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertEqual( + request.data, + { + "dataset_id": max_id, + "name": "New Dataset", + "authenticated": False, + "current_user": "", + "is_public": True, + }, + ) + self.assertEqual(new_dataset.name, "New Dataset") + self.assertIsNone(new_dataset.current_user) + new_dataset.delete() + new_metadata.delete() + + # Test creating a database with associated files + def test_dataset_created_with_files(self): + file = DataFile.objects.create( + id=1, file_name="cyl_testdata.txt", is_public=True + ) + file.file.save("cyl_testdata.txt", open(find("cyl_testdata.txt"))) + dataset = { + "name": "Dataset with file", + "metadata": self.empty_metadata, + "is_public": True, + "data_contents": self.empty_data, + "files": [1], + } + request = self.client.post("/v1/data/set/", data=dataset, format="json") + max_id = DataSet.objects.aggregate(Max("id"))["id__max"] + new_dataset = DataSet.objects.get(id=max_id) + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertEqual( + request.data, + { + "dataset_id": max_id, + "name": "Dataset with file", + "authenticated": False, + "current_user": "", + "is_public": True, + }, + ) + self.assertTrue(file in new_dataset.files.all()) + new_dataset.delete() + file.delete() + + # Test that a dataset cannot be associated with inaccessible files + def test_no_dataset_with_private_files(self): + file = DataFile.objects.create( + id=1, file_name="cyl_testdata.txt", is_public=False, current_user=self.user2 + ) + file.file.save("cyl_testdata.txt", open(find("cyl_testdata.txt"))) + dataset = { + "name": "Dataset with file", + "metadata": self.empty_metadata, + "is_public": True, + "data_contents": self.empty_data, + "files": [1], + } + request = self.client.post("/v1/data/set/", data=dataset, format="json") + file.delete() + self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST) + + # Test that a dataset cannot be associated with nonexistent files + def test_no_dataset_with_nonexistent_files(self): + dataset = { + "name": "Dataset with file", + "metadata": self.empty_metadata, + "is_public": True, + "data_contents": self.empty_data, + "files": [2], + } + request = self.client.post("/v1/data/set/", data=dataset, format="json") + self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST) + + # Test that a dataset cannot be created without metadata + def test_metadata_required(self): + dataset = { + "name": "No metadata", + "is_public": True, + "data_contents": self.empty_data, + } + request = self.auth_client1.post("/v1/data/set/", data=dataset, format="json") + self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST) + + # Test that a private dataset cannot be created without an owner + def test_no_private_unowned_dataset(self): + dataset = { + "name": "Disallowed Dataset", + "metadata": self.empty_metadata, + "is_public": False, + "data_contents": self.empty_data, + } + request = self.client.post("/v1/data/set/", data=dataset, format="json") + self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST) + + # Test whether a user can overwrite data by specifying an in-use id + def test_no_data_overwrite(self): + dataset = { + "id": 2, + "name": "Overwrite Dataset", + "metadata": self.empty_metadata, + "is_public": True, + "data_contents": self.empty_data, + } + request = self.auth_client2.post("/v1/data/set/", data=dataset, format="json") + max_id = DataSet.objects.aggregate(Max("id"))["id__max"] + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertEqual(DataSet.objects.get(id=2).name, "Dataset 2") + self.assertEqual( + request.data, + { + "dataset_id": max_id, + "name": "Overwrite Dataset", + "authenticated": True, + "current_user": "testUser2", + "is_public": True, + }, + ) + DataSet.objects.get(id=max_id).delete() + + @classmethod + def tearDownClass(cls): + cls.public_dataset.delete() + cls.private_dataset.delete() + cls.unowned_dataset.delete() + cls.user1.delete() + cls.user2.delete() + cls.user3.delete() + shutil.rmtree(settings.MEDIA_ROOT) + + +class TestSingleDataSet(APITestCase): + """Tests for HTTP methods of SingleDataSetView.""" + + @classmethod + def setUpTestData(cls): + cls.user1 = User.objects.create_user( + id=1, username="testUser1", password="secret" + ) + cls.user2 = User.objects.create_user( + id=2, username="testUser2", password="secret" + ) + cls.user3 = User.objects.create_user( + id=3, username="testUser3", password="secret" + ) + cls.public_dataset = DataSet.objects.create( + id=1, + current_user=cls.user1, + is_public=True, + name="Dataset 1", + ) + cls.private_dataset = DataSet.objects.create( + id=2, current_user=cls.user1, name="Dataset 2" + ) + cls.unowned_dataset = DataSet.objects.create( + id=3, is_public=True, name="Dataset 3" + ) + cls.metadata = MetaData.objects.create( + id=1, + title="Metadata", + run=0, + definition="test", + instrument="none", + process="none", + sample="none", + dataset=cls.public_dataset, + ) + cls.file = DataFile.objects.create( + id=1, file_name="cyl_testdata.txt", is_public=False, current_user=cls.user1 + ) + cls.file.file.save("cyl_testdata.txt", open(find("cyl_testdata.txt"))) + cls.private_dataset.users.add(cls.user3) + cls.public_dataset.files.add(cls.file) + cls.auth_client1 = APIClient() + cls.auth_client2 = APIClient() + cls.auth_client3 = APIClient() + cls.auth_client1.force_authenticate(cls.user1) + cls.auth_client2.force_authenticate(cls.user2) + cls.auth_client3.force_authenticate(cls.user3) + + # TODO: change load return data + # Test successfully accessing a private dataset + def test_load_private_dataset(self): + request1 = self.auth_client1.get("/v1/data/set/2/") + request2 = self.auth_client3.get("/v1/data/set/2/") + self.assertEqual(request1.status_code, status.HTTP_200_OK) + self.assertEqual(request2.status_code, status.HTTP_200_OK) + self.assertEqual( + request1.data, + { + "id": 2, + "current_user": "testUser1", + "users": [3], + "is_public": False, + "name": "Dataset 2", + "files": [], + "metadata": None, + "data_contents": [], + "session": None, + }, + ) + + # Test successfully accessing a public dataset + def test_load_public_dataset(self): + request1 = self.client.get("/v1/data/set/1/") + request2 = self.auth_client2.get("/v1/data/set/1/") + request3 = self.auth_client1.get("/v1/data/set/1/") + self.assertEqual(request1.status_code, status.HTTP_200_OK) + self.assertEqual(request2.status_code, status.HTTP_200_OK) + self.assertEqual(request3.status_code, status.HTTP_200_OK) + self.assertDictEqual( + request1.data, + { + "id": 1, + "current_user": "testUser1", + "users": [], + "is_public": True, + "name": "Dataset 1", + "files": [], + "metadata": { + "id": 1, + "title": "Metadata", + "run": 0, + "definition": "test", + "instrument": "none", + "process": "none", + "sample": "none", + }, + "data_contents": [], + "session": None, + }, + ) + self.assertEqual(request1.data, request2.data) + self.assertEqual( + request3.data, + { + "id": 1, + "current_user": "testUser1", + "users": [], + "is_public": True, + "name": "Dataset 1", + "files": [1], + "metadata": { + "id": 1, + "title": "Metadata", + "run": 0, + "definition": "test", + "instrument": "none", + "process": "none", + "sample": "none", + }, + "data_contents": [], + "session": None, + }, + ) + + # Test successfully accessing an unowned public dataset + def test_load_unowned_dataset(self): + request1 = self.auth_client1.get("/v1/data/set/3/") + request2 = self.client.get("/v1/data/set/3/") + self.assertEqual(request1.status_code, status.HTTP_200_OK) + self.assertEqual(request2.status_code, status.HTTP_200_OK) + self.assertDictEqual( + request1.data, + { + "id": 3, + "current_user": None, + "users": [], + "is_public": True, + "name": "Dataset 3", + "files": [], + "metadata": None, + "data_contents": [], + "session": None, + }, + ) + + # Test unsuccessfully accessing a private dataset + def test_load_private_dataset_unauthorized(self): + request1 = self.auth_client2.get("/v1/data/set/2/") + request2 = self.client.get("/v1/data/set/2/") + self.assertEqual(request1.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request2.status_code, status.HTTP_401_UNAUTHORIZED) + + # Test only owner can change a private dataset + def test_update_private_dataset(self): + request1 = self.auth_client1.put("/v1/data/set/2/", data={"is_public": True}) + request2 = self.auth_client3.put("/v1/data/set/2/", data={"is_public": False}) + self.assertEqual(request1.status_code, status.HTTP_200_OK) + self.assertEqual(request2.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual( + request1.data, {"data_id": 2, "name": "Dataset 2", "is_public": True} + ) + self.assertTrue(DataSet.objects.get(id=2).is_public) + self.private_dataset.save() + self.assertFalse(DataSet.objects.get(id=2).is_public) + + # Test changing a public dataset + def test_update_public_dataset(self): + request1 = self.auth_client1.put( + "/v1/data/set/1/", data={"name": "Different name"} + ) + request2 = self.auth_client2.put("/v1/data/set/1/", data={"is_public": False}) + self.assertEqual(request1.status_code, status.HTTP_200_OK) + self.assertEqual(request2.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual( + request1.data, {"data_id": 1, "name": "Different name", "is_public": True} + ) + self.assertEqual(DataSet.objects.get(id=1).name, "Different name") + self.public_dataset.save() + + # TODO: test invalid updates if and when those are figured out + + # Test changing an unowned dataset + def test_update_unowned_dataset(self): + request1 = self.auth_client1.put("/v1/data/set/3/", data={"current_user": 1}) + request2 = self.client.put("/v1/data/set/3/", data={"name": "Different name"}) + self.assertEqual(request1.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request2.status_code, status.HTTP_401_UNAUTHORIZED) + + # Test updating metadata + def test_update_dataset_metadata(self): + new_metadata = { + "title": "Updated Metadata", + "run": ["X"], + "definition": "update test", + "instrument": "none", + "process": "none", + "sample": "none", + } + request = self.auth_client1.put( + "/v1/data/set/1/", data={"metadata": new_metadata}, format="json" + ) + dataset = DataSet.objects.get(id=1) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual(dataset.metadata.title, "Updated Metadata") + self.assertEqual(dataset.metadata.id, 1) + self.assertEqual(len(MetaData.objects.all()), 1) + dataset.metadata.delete() + self.metadata = MetaData.objects.create( + id=1, + title="Metadata", + run=0, + definition="test", + instrument="none", + process="none", + sample="none", + dataset=self.public_dataset, + ) + + # Test partially updating metadata + def test_update_dataset_partial_metadata(self): + request = self.auth_client1.put( + "/v1/data/set/1/", + data={"metadata": {"title": "Different Title"}}, + format="json", + ) + dataset = DataSet.objects.get(id=1) + metadata = dataset.metadata + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual(metadata.title, "Different Title") + self.assertEqual(metadata.definition, "test") + self.assertEqual(metadata.id, 1) + metadata.title = "Metadata" + metadata.save() + + # Test updating a dataset's files + def test_update_dataset_files(self): + request = self.auth_client1.put("/v1/data/set/2/", data={"files": [1]}) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual(len(DataSet.objects.get(id=2).files.all()), 1) + self.private_dataset.files.remove(self.file) + + # Test replacing a dataset's files + def test_update_dataset_replace_files(self): + file = DataFile.objects.create( + id=2, file_name="cyl_testdata1.txt", is_public=True, current_user=self.user1 + ) + file.file.save("cyl_testdata1.txt", open(find("cyl_testdata1.txt"))) + request = self.auth_client1.put("/v1/data/set/1/", data={"files": [2]}) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual(len(DataSet.objects.get(id=1).files.all()), 1) + self.assertTrue(file in DataSet.objects.get(id=1).files.all()) + self.public_dataset.files.add(self.file) + self.public_dataset.files.remove(file) + + # Test updating a dataset to have no files + def test_update_dataset_clear_files(self): + request = self.auth_client1.put("/v1/data/set/1/", data={"files": [""]}) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual(len(DataSet.objects.get(id=1).files.all()), 0) + self.public_dataset.files.add(self.file) + + # Test that a dataset cannot be updated to be private and unowned + def test_update_dataset_no_private_unowned(self): + request1 = self.auth_client1.put("/v1/data/set/2/", data={"current_user": ""}) + request2 = self.auth_client1.put( + "/v1/data/set/1/", data={"current_user": "", "is_public": False} + ) + public_dataset = DataSet.objects.get(id=1) + self.assertEqual(request1.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(request2.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(DataSet.objects.get(id=2).current_user, self.user1) + self.assertEqual(public_dataset.current_user, self.user1) + self.assertTrue(public_dataset.is_public) + + # Test deleting a dataset + def test_delete_dataset(self): + quantity = Quantity.objects.create( + id=1, + value=0, + variance=0, + units="none", + hash=0, + label="test", + dataset=self.private_dataset, + ) + neg = OperationTree.objects.create(id=1, operation="neg", quantity=quantity) + OperationTree.objects.create( + id=2, operation="zero", parameters={}, child_operation=neg + ) + request = self.auth_client1.delete("/v1/data/set/2/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual(request.data, {"success": True}) + self.assertRaises(DataSet.DoesNotExist, DataSet.objects.get, id=2) + self.assertRaises(Quantity.DoesNotExist, Quantity.objects.get, id=1) + self.assertRaises(OperationTree.DoesNotExist, OperationTree.objects.get, id=1) + self.assertRaises(OperationTree.DoesNotExist, OperationTree.objects.get, id=2) + self.private_dataset = DataSet.objects.create( + id=2, current_user=self.user1, name="Dataset 2" + ) + + # Test cannot delete a public dataset + def test_delete_public_dataset(self): + request = self.auth_client1.delete("/v1/data/set/1/") + self.assertEqual(request.status_code, status.HTTP_403_FORBIDDEN) + + # Test cannot delete an unowned dataset + def test_delete_unowned_dataset(self): + request = self.auth_client1.delete("/v1/data/set/3/") + self.assertEqual(request.status_code, status.HTTP_403_FORBIDDEN) + + # Test cannot delete another user's dataset + def test_delete_dataset_unauthorized(self): + request1 = self.auth_client2.delete("/v1/data/set/1/") + request2 = self.auth_client3.delete("/v1/data/set/2/") + self.assertEqual(request1.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request2.status_code, status.HTTP_403_FORBIDDEN) + + @classmethod + def tearDownClass(cls): + cls.public_dataset.delete() + cls.private_dataset.delete() + cls.unowned_dataset.delete() + cls.user1.delete() + cls.user2.delete() + cls.user3.delete() + cls.file.delete() + shutil.rmtree(settings.MEDIA_ROOT) + + +class TestDataSetAccessManagement(APITestCase): + """Tests for HTTP methods of DataSetUsersView.""" + + @classmethod + def setUpTestData(cls): + cls.user1 = User.objects.create_user(username="testUser1", password="secret") + cls.user2 = User.objects.create_user(username="testUser2", password="secret") + cls.private_dataset = DataSet.objects.create( + id=1, current_user=cls.user1, name="Dataset 1" + ) + cls.shared_dataset = DataSet.objects.create( + id=2, current_user=cls.user1, name="Dataset 2" + ) + cls.shared_dataset.users.add(cls.user2) + cls.client_owner = APIClient() + cls.client_other = APIClient() + cls.client_owner.force_authenticate(cls.user1) + cls.client_other.force_authenticate(cls.user2) + + # Test listing no users with access + def test_list_access_private(self): + request1 = self.client_owner.get("/v1/data/set/1/users/") + self.assertEqual(request1.status_code, status.HTTP_200_OK) + self.assertEqual( + request1.data, + {"data_id": 1, "name": "Dataset 1", "is_public": False, "users": []}, + ) + + # Test listing users with access + def test_list_access_shared(self): + request1 = self.client_owner.get("/v1/data/set/2/users/") + self.assertEqual(request1.status_code, status.HTTP_200_OK) + self.assertEqual( + request1.data, + { + "data_id": 2, + "name": "Dataset 2", + "is_public": False, + "users": ["testUser2"], + }, + ) + + # Test only owner can view access + def test_list_access_unauthorized(self): + request = self.client_other.get("/v1/data/set/2/users/") + self.assertEqual(request.status_code, status.HTTP_403_FORBIDDEN) + + # Test granting access to a dataset + def test_grant_access(self): + request1 = self.client_owner.put( + "/v1/data/set/1/users/", data={"username": "testUser2", "access": True} + ) + request2 = self.client_other.get("/v1/data/set/1/") + self.assertEqual(request1.status_code, status.HTTP_200_OK) + self.assertEqual(request2.status_code, status.HTTP_200_OK) + self.assertIn( # codespell:ignore + self.user2, DataSet.objects.get(id=1).users.all() + ) + self.assertEqual( + request1.data, + { + "username": "testUser2", + "data_id": 1, + "name": "Dataset 1", + "access": True, + }, + ) + self.private_dataset.users.remove(self.user2) + + # Test revoking access to a dataset + def test_revoke_access(self): + request1 = self.client_owner.put( + "/v1/data/set/2/users/", data={"username": "testUser2", "access": False} + ) + request2 = self.client_other.get("/v1/data/set/2/") + self.assertEqual(request1.status_code, status.HTTP_200_OK) + self.assertEqual(request2.status_code, status.HTTP_403_FORBIDDEN) + self.assertNotIn(self.user2, DataSet.objects.get(id=2).users.all()) + self.assertEqual( + request1.data, + { + "username": "testUser2", + "data_id": 2, + "name": "Dataset 2", + "access": False, + }, + ) + self.shared_dataset.users.add(self.user2) + + # Test only the owner can change access + def test_revoke_access_unauthorized(self): + request1 = self.client_other.put( + "/v1/data/set/2/users/", data={"username": "testUser2", "access": False} + ) + self.assertEqual(request1.status_code, status.HTTP_403_FORBIDDEN) + + @classmethod + def tearDownClass(cls): + cls.private_dataset.delete() + cls.shared_dataset.delete() + cls.user1.delete() + cls.user2.delete() diff --git a/sasdata/fair_database/data/test/test_operation_tree.py b/sasdata/fair_database/data/test/test_operation_tree.py new file mode 100644 index 00000000..de3ee25e --- /dev/null +++ b/sasdata/fair_database/data/test/test_operation_tree.py @@ -0,0 +1,799 @@ +from django.contrib.auth.models import User +from django.db.models import Max +from rest_framework.test import APIClient, APITestCase +from rest_framework import status + +from data.models import DataSet, MetaData, OperationTree, Quantity, ReferenceQuantity + + +class TestCreateOperationTree(APITestCase): + """Tests for creating datasets with operation trees.""" + + @classmethod + def setUpTestData(cls): + cls.dataset = { + "name": "Test Dataset", + "metadata": { + "title": "test metadata", + "run": 1, + "definition": "test", + "instrument": {"source": {}, "collimation": {}, "detectors": {}}, + }, + "data_contents": [ + { + "label": "test", + "value": {"array_contents": [0, 0, 0, 0], "shape": (2, 2)}, + "variance": {"array_contents": [0, 0, 0, 0], "shape": (2, 2)}, + "units": "none", + "hash": 0, + } + ], + "is_public": True, + } + cls.user = User.objects.create_user( + id=1, username="testUser", password="sasview!" + ) + cls.client = APIClient() + cls.client.force_authenticate(cls.user) + + @staticmethod + def get_operation_tree(quantity): + return quantity.operation_tree + + # Test creating quantity with no operations performed (variable-only history) + def test_operation_tree_created_variable(self): + self.dataset["data_contents"][0]["history"] = { + "operation_tree": { + "operation": "variable", + "parameters": {"hash_value": 0, "name": "test"}, + }, + "references": [ + { + "label": "test", + "value": {"array_contents": [0, 0, 0, 0], "shape": (2, 2)}, + "variance": {"array_contents": [0, 0, 0, 0], "shape": (2, 2)}, + "units": "none", + "hash": 0, + "history": {}, + } + ], + } + request = self.client.post("/v1/data/set/", data=self.dataset, format="json") + max_id = DataSet.objects.aggregate(Max("id"))["id__max"] + new_dataset = DataSet.objects.get(id=max_id) + new_quantity = new_dataset.data_contents.get(hash=0) + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertRaises( + Quantity.operation_tree.RelatedObjectDoesNotExist, + self.get_operation_tree, + quantity=new_quantity, + ) + self.assertEqual(len(new_quantity.references.all()), 0) + + # Test creating quantity with unary operation + def test_operation_tree_created_unary(self): + self.dataset["data_contents"][0]["history"] = { + "operation_tree": { + "operation": "reciprocal", + "parameters": { + "a": { + "operation": "variable", + "parameters": {"hash_value": 111, "name": "x"}, + } + }, + }, + "references": [ + {"value": 5, "variance": 0, "units": "none", "hash": 111, "history": {}} + ], + } + request = self.client.post("/v1/data/set/", data=self.dataset, format="json") + max_id = DataSet.objects.aggregate(Max("id"))["id__max"] + new_dataset = DataSet.objects.get(id=max_id) + new_quantity = new_dataset.data_contents.get(hash=0) + reciprocal = new_quantity.operation_tree + variable = reciprocal.parent_operations.all().get(label="a") + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertEqual( + new_quantity.value, {"array_contents": [0, 0, 0, 0], "shape": [2, 2]} + ) + self.assertEqual(reciprocal.operation, "reciprocal") + self.assertEqual(variable.operation, "variable") + self.assertEqual(len(reciprocal.parent_operations.all()), 1) + self.assertEqual(reciprocal.parameters, {}) + self.assertEqual(len(ReferenceQuantity.objects.all()), 1) + self.assertEqual(len(new_quantity.references.all()), 1) + self.assertEqual(new_quantity.references.get(hash=111).value, 5) + + # Test creating quantity with binary operation + def test_operation_tree_created_binary(self): + self.dataset["data_contents"][0]["history"] = { + "operation_tree": { + "operation": "add", + "parameters": { + "a": { + "operation": "variable", + "parameters": {"hash_value": 111, "name": "x"}, + }, + "b": {"operation": "constant", "parameters": {"value": 5}}, + }, + }, + "references": [ + {"value": 5, "variance": 0, "units": "none", "hash": 111, "history": {}} + ], + } + request = self.client.post("/v1/data/set/", data=self.dataset, format="json") + max_id = DataSet.objects.aggregate(Max("id"))["id__max"] + new_dataset = DataSet.objects.get(id=max_id) + new_quantity = new_dataset.data_contents.get(hash=0) + add = new_quantity.operation_tree + variable = add.parent_operations.get(label="a") + constant = add.parent_operations.get(label="b") + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertEqual(add.operation, "add") + self.assertEqual(add.parameters, {}) + self.assertEqual(variable.operation, "variable") + self.assertEqual(variable.parameters, {"hash_value": 111, "name": "x"}) + self.assertEqual(constant.operation, "constant") + self.assertEqual(constant.parameters, {"value": 5}) + self.assertEqual(len(add.parent_operations.all()), 2) + self.assertEqual(len(new_quantity.references.all()), 1) + self.assertEqual(new_quantity.references.get(hash=111).value, 5) + + # Test creating quantity with exponent + def test_operation_tree_created_pow(self): + self.dataset["data_contents"][0]["history"] = { + "operation_tree": { + "operation": "pow", + "parameters": { + "a": { + "operation": "variable", + "parameters": {"hash_value": 111, "name": "x"}, + }, + "power": 2, + }, + }, + "references": [ + {"value": 5, "variance": 0, "units": "none", "hash": 111, "history": {}} + ], + } + request = self.client.post("/v1/data/set/", data=self.dataset, format="json") + max_id = DataSet.objects.aggregate(Max("id"))["id__max"] + new_dataset = DataSet.objects.get(id=max_id) + new_quantity = new_dataset.data_contents.get(hash=0) + pow = new_quantity.operation_tree + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertEqual(pow.operation, "pow") + self.assertEqual(pow.parameters, {"power": 2}) + self.assertEqual(len(new_quantity.references.all()), 1) + self.assertEqual(new_quantity.references.get(hash=111).value, 5) + + # Test creating a transposed quantity + def test_operation_tree_created_transpose(self): + self.dataset["data_contents"][0]["history"] = { + "operation_tree": { + "operation": "transpose", + "parameters": { + "a": { + "operation": "variable", + "parameters": {"hash_value": 111, "name": "x"}, + }, + "axes": [1, 0], + }, + }, + "references": [ + {"value": 5, "variance": 0, "units": "none", "hash": 111, "history": {}} + ], + } + request = self.client.post("/v1/data/set/", data=self.dataset, format="json") + max_id = DataSet.objects.aggregate(Max("id"))["id__max"] + new_dataset = DataSet.objects.get(id=max_id) + new_quantity = new_dataset.data_contents.get(hash=0) + transpose = new_quantity.operation_tree + variable = transpose.parent_operations.get() + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertEqual(transpose.operation, "transpose") + self.assertEqual(transpose.parameters, {"axes": [1, 0]}) + self.assertEqual(variable.operation, "variable") + self.assertEqual(variable.parameters, {"hash_value": 111, "name": "x"}) + self.assertEqual(len(new_quantity.references.all()), 1) + self.assertEqual(new_quantity.references.get(hash=111).value, 5) + + # Test creating a quantity with multiple operations + def test_operation_tree_created_nested(self): + self.dataset["data_contents"][0]["history"] = { + "operation_tree": { + "operation": "neg", + "parameters": { + "a": { + "operation": "mul", + "parameters": { + "a": { + "operation": "constant", + "parameters": {"value": {"type": "int", "value": 7}}, + }, + "b": { + "operation": "variable", + "parameters": {"hash_value": 111, "name": "x"}, + }, + }, + }, + }, + }, + "references": [ + {"value": 5, "variance": 0, "units": "none", "hash": 111, "history": {}} + ], + } + request = self.client.post("/v1/data/set/", data=self.dataset, format="json") + max_id = DataSet.objects.aggregate(Max("id"))["id__max"] + new_dataset = DataSet.objects.get(id=max_id) + new_quantity = new_dataset.data_contents.get(hash=0) + negate = new_quantity.operation_tree + multiply = negate.parent_operations.get() + constant = multiply.parent_operations.get(label="a") + variable = multiply.parent_operations.get(label="b") + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertEqual(negate.operation, "neg") + self.assertEqual(negate.parameters, {}) + self.assertEqual(multiply.operation, "mul") + self.assertEqual(multiply.parameters, {}) + self.assertEqual(constant.operation, "constant") + self.assertEqual(constant.parameters, {"value": {"type": "int", "value": 7}}) + self.assertEqual(variable.operation, "variable") + self.assertEqual(variable.parameters, {"hash_value": 111, "name": "x"}) + self.assertEqual(len(new_quantity.references.all()), 1) + self.assertEqual(new_quantity.references.get(hash=111).value, 5) + + # Test creating a quantity with tensordot + def test_operation_tree_created_tensor(self): + self.dataset["data_contents"][0]["history"] = { + "operation_tree": { + "operation": "tensor_product", + "parameters": { + "a": { + "operation": "variable", + "parameters": {"hash_value": 111, "name": "x"}, + }, + "b": {"operation": "constant", "parameters": {"value": 5}}, + "a_index": 1, + "b_index": 1, + }, + }, + "references": [ + {"value": 5, "variance": 0, "units": "none", "hash": 111, "history": {}} + ], + } + request = self.client.post("/v1/data/set/", data=self.dataset, format="json") + max_id = DataSet.objects.aggregate(Max("id"))["id__max"] + new_dataset = DataSet.objects.get(id=max_id) + new_quantity = new_dataset.data_contents.get(hash=0) + tensor = new_quantity.operation_tree + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertEqual(tensor.operation, "tensor_product") + self.assertEqual(tensor.parameters, {"a_index": 1, "b_index": 1}) + self.assertEqual(len(new_quantity.references.all()), 1) + self.assertEqual(new_quantity.references.get(hash=111).value, 5) + + # Test creating a quantity with no history + def test_operation_tree_created_no_history(self): + if "history" in self.dataset["data_contents"][0]: + self.dataset["data_contents"][0].pop("history") + request = self.client.post( + "/v1/data/set/", data=self.dataset, format="json" + ) + max_id = DataSet.objects.aggregate(Max("id"))["id__max"] + new_dataset = DataSet.objects.get(id=max_id) + new_quantity = new_dataset.data_contents.get(hash=0) + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertIsNone(new_quantity.operation_tree) + self.assertEqual(len(new_quantity.references.all()), 0) + + def tearDown(self): + DataSet.objects.all().delete() + MetaData.objects.all().delete() + Quantity.objects.all().delete() + OperationTree.objects.all().delete() + + @classmethod + def tearDownClass(cls): + cls.user.delete() + + +class TestCreateInvalidOperationTree(APITestCase): + """Tests for creating datasets with invalid operation trees.""" + + @classmethod + def setUpTestData(cls): + cls.dataset = { + "name": "Test Dataset", + "metadata": { + "title": "test metadata", + "run": 1, + "definition": "test", + "instrument": {"source": {}, "collimation": {}, "detectors": {}}, + }, + "data_contents": [ + { + "label": "test", + "value": {"array_contents": [0, 0, 0, 0], "shape": (2, 2)}, + "variance": {"array_contents": [0, 0, 0, 0], "shape": (2, 2)}, + "units": "none", + "hash": 0, + } + ], + "is_public": True, + } + cls.user = User.objects.create_user( + id=1, username="testUser", password="sasview!" + ) + cls.client = APIClient() + cls.client.force_authenticate(cls.user) + + # Test creating a quantity with an invalid operation + def test_create_operation_tree_invalid(self): + self.dataset["data_contents"][0]["history"] = { + "operation_tree": {"operation": "fix", "parameters": {}}, + "references": [], + } + request = self.client.post("/v1/data/set/", data=self.dataset, format="json") + self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(len(DataSet.objects.all()), 0) + self.assertEqual(len(Quantity.objects.all()), 0) + self.assertEqual(len(OperationTree.objects.all()), 0) + + # Test creating a quantity with a nested invalid operation + def test_create_operation_tree_invalid_nested(self): + self.dataset["data_contents"][0]["history"] = { + "operation_tree": { + "operation": "reciprocal", + "parameters": { + "a": { + "operation": "fix", + "parameters": {}, + } + }, + }, + "references": [], + } + request = self.client.post("/v1/data/set/", data=self.dataset, format="json") + self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(len(DataSet.objects.all()), 0) + self.assertEqual(len(Quantity.objects.all()), 0) + self.assertEqual(len(OperationTree.objects.all()), 0) + + # Test creating a unary operation with a missing parameter fails + def test_create_missing_parameter_unary(self): + self.dataset["data_contents"][0]["history"] = { + "operation_tree": {"operation": "neg", "parameters": {}}, + "references": {}, + } + request = self.client.post("/v1/data/set/", data=self.dataset, format="json") + self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(len(DataSet.objects.all()), 0) + self.assertEqual(len(Quantity.objects.all()), 0) + self.assertEqual(len(OperationTree.objects.all()), 0) + + # Test creating a binary operation with a missing parameter fails + def test_create_missing_parameter_binary(self): + self.dataset["data_contents"][0]["history"] = { + "operation_tree": { + "operation": "add", + "parameters": { + "a": { + "operation": "variable", + "parameters": {"hash_value": 111, "name": "x"}, + } + }, + }, + "references": [], + } + request = self.client.post("/v1/data/set/", data=self.dataset, format="json") + self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(len(DataSet.objects.all()), 0) + self.assertEqual(len(Quantity.objects.all()), 0) + self.assertEqual(len(OperationTree.objects.all()), 0) + + # TODO: should variable-only history be ignored? + # Test creating a variable with a missing parameter fails + def test_create_missing_parameter_variable(self): + self.dataset["data_contents"][0]["history"] = { + "operation_tree": { + "operation": "neg", + "parameters": { + "a": {"operation": "variable", "parameters": {"name": "x"}} + }, + }, + "references": [], + } + request = self.client.post("/v1/data/set/", data=self.dataset, format="json") + self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(len(DataSet.objects.all()), 0) + self.assertEqual(len(Quantity.objects.all()), 0) + self.assertEqual(len(OperationTree.objects.all()), 0) + + # Test creating a constant with a missing parameter fails + def test_create_missing_parameter_constant(self): + self.dataset["data_contents"][0]["history"] = { + "operation_tree": { + "operation": "neg", + "parameters": {"a": {"operation": "constant", "parameters": {}}}, + }, + "references": [], + } + request = self.client.post("/v1/data/set/", data=self.dataset, format="json") + self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(len(DataSet.objects.all()), 0) + self.assertEqual(len(Quantity.objects.all()), 0) + self.assertEqual(len(OperationTree.objects.all()), 0) + + # Test creating an exponent with a missing parameter fails + def test_create_missing_parameter_pow(self): + self.dataset["data_contents"][0]["history"] = { + "operation_tree": { + "operation": "pow", + "parameters": { + "a": { + "operation": "variable", + "parameters": {"hash_value": 111, "name": "x"}, + }, + }, + }, + "references": [], + } + request = self.client.post("/v1/data/set/", data=self.dataset, format="json") + self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(len(DataSet.objects.all()), 0) + self.assertEqual(len(Quantity.objects.all()), 0) + self.assertEqual(len(OperationTree.objects.all()), 0) + + # Test creating a transpose with a missing parameter fails + def test_create_missing_parameter_transpose(self): + self.dataset["data_contents"][0]["history"] = { + "operation_tree": { + "operation": "transpose", + "parameters": { + "a": { + "operation": "variable", + "parameters": {"hash_value": 111, "name": "x"}, + }, + }, + }, + "references": [], + } + request = self.client.post("/v1/data/set/", data=self.dataset, format="json") + self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(len(DataSet.objects.all()), 0) + self.assertEqual(len(Quantity.objects.all()), 0) + self.assertEqual(len(OperationTree.objects.all()), 0) + + # Test creating a tensor with a missing parameter fails + def test_create_missing_parameter_tensor(self): + self.dataset["data_contents"][0]["history"] = { + "operation_tree": { + "operation": "tensor_product", + "parameters": { + "a": { + "operation": "variable", + "parameters": {"hash_value": 111, "name": "x"}, + }, + "b": {"operation": "constant", "parameters": {"value": 5}}, + "b_index": 1, + }, + }, + "references": [], + } + request = self.client.post("/v1/data/set/", data=self.dataset, format="json") + self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(len(DataSet.objects.all()), 0) + self.assertEqual(len(Quantity.objects.all()), 0) + self.assertEqual(len(OperationTree.objects.all()), 0) + + # TODO: Test variables have corresponding reference quantities + + @classmethod + def tearDownClass(cls): + cls.user.delete() + + +class TestGetOperationTree(APITestCase): + """Tests for retrieving datasets with operation trees.""" + + @classmethod + def setUpTestData(cls): + cls.user = User.objects.create_user( + id=1, username="testUser", password="sasview!" + ) + cls.dataset = DataSet.objects.create( + id=1, + current_user=cls.user, + name="Test Dataset", + is_public=True, + ) + cls.quantity = Quantity.objects.create( + id=1, + value=0, + variance=0, + label="test", + units="none", + hash=1, + dataset=cls.dataset, + ) + cls.variable = OperationTree.objects.create( + id=1, operation="variable", parameters={"hash_value": 111, "name": "x"} + ) + cls.constant = OperationTree.objects.create( + id=2, operation="constant", parameters={"value": 1} + ) + cls.ref_quantity = ReferenceQuantity.objects.create( + id=1, + value=5, + variance=0, + units="none", + hash=111, + derived_quantity=cls.quantity, + ) + cls.client = APIClient() + cls.client.force_authenticate(cls.user) + + # Test accessing a quantity with no operations performed + def test_get_operation_tree_none(self): + self.ref_quantity.delete() + request = self.client.get("/v1/data/set/1/") + self.ref_quantity = ReferenceQuantity.objects.create( + id=1, + value=5, + variance=0, + units="none", + hash=111, + derived_quantity=self.quantity, + ) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data["data_contents"][0], + { + "label": "test", + "value": 0, + "variance": 0, + "units": "none", + "hash": 1, + "history": { + "operation_tree": None, + "references": [], + }, + }, + ) + + # Test accessing quantity with unary operation + def test_get_operation_tree_unary(self): + inv = OperationTree.objects.create( + id=3, + operation="reciprocal", + quantity=self.quantity, + ) + self.variable.label = "a" + self.variable.child_operation = inv + self.variable.save() + request = self.client.get("/v1/data/set/1/") + self.variable.child_operation = None + self.variable.save() + inv.delete() + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data["data_contents"][0], + { + "label": "test", + "value": 0, + "variance": 0, + "units": "none", + "hash": 1, + "history": { + "operation_tree": { + "operation": "reciprocal", + "parameters": { + "a": { + "operation": "variable", + "parameters": {"hash_value": 111, "name": "x"}, + } + }, + }, + "references": [ + { + "value": 5, + "variance": 0, + "units": "none", + "hash": 111, + } + ], + }, + }, + ) + + # Test accessing quantity with binary operation + def test_get_operation_tree_binary(self): + add = OperationTree.objects.create( + id=3, + operation="add", + quantity=self.quantity, + ) + self.variable.label = "a" + self.variable.child_operation = add + self.variable.save() + self.constant.label = "b" + self.constant.child_operation = add + self.constant.save() + request = self.client.get("/v1/data/set/1/") + self.variable.child_operation = None + self.constant.child_operation = None + self.variable.save() + self.constant.save() + add.delete() + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data["data_contents"][0]["history"]["operation_tree"], + { + "operation": "add", + "parameters": { + "a": { + "operation": "variable", + "parameters": {"hash_value": 111, "name": "x"}, + }, + "b": { + "operation": "constant", + "parameters": {"value": 1}, + }, + }, + }, + ) + + # Test accessing a quantity with exponent + def test_get_operation_tree_pow(self): + power = OperationTree.objects.create( + id=3, + operation="pow", + parameters={"power": 2}, + quantity=self.quantity, + ) + self.variable.label = "a" + self.variable.child_operation = power + self.variable.save() + request = self.client.get("/v1/data/set/1/") + self.variable.child_operation = None + self.variable.save() + power.delete() + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data["data_contents"][0]["history"]["operation_tree"], + { + "operation": "pow", + "parameters": { + "a": { + "operation": "variable", + "parameters": {"hash_value": 111, "name": "x"}, + }, + "power": 2, + }, + }, + ) + + # Test accessing a quantity with multiple operations + def test_get_operation_tree_nested(self): + neg = OperationTree.objects.create( + id=4, operation="neg", quantity=self.quantity + ) + multiply = OperationTree.objects.create( + id=3, operation="mul", child_operation=neg, label="a" + ) + self.constant.label = "a" + self.constant.child_operation = multiply + self.constant.save() + self.variable.label = "b" + self.variable.child_operation = multiply + self.variable.save() + request = self.client.get("/v1/data/set/1/") + self.constant.child_operation = None + self.variable.child_operation = None + self.constant.save() + self.variable.save() + neg.delete() + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data["data_contents"][0]["history"]["operation_tree"], + { + "operation": "neg", + "parameters": { + "a": { + "operation": "mul", + "parameters": { + "a": { + "operation": "constant", + "parameters": {"value": 1}, + }, + "b": { + "operation": "variable", + "parameters": { + "hash_value": 111, + "name": "x", + }, + }, + }, + } + }, + }, + ) + + # Test accessing a transposed quantity + def test_get_operation_tree_transpose(self): + trans = OperationTree.objects.create( + id=3, + operation="transpose", + parameters={"axes": (1, 0)}, + quantity=self.quantity, + ) + self.variable.label = "a" + self.variable.child_operation = trans + self.variable.save() + request = self.client.get("/v1/data/set/1/") + self.variable.child_operation = None + self.variable.save() + trans.delete() + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data["data_contents"][0]["history"]["operation_tree"], + { + "operation": "transpose", + "parameters": { + "a": { + "operation": "variable", + "parameters": {"hash_value": 111, "name": "x"}, + }, + "axes": [1, 0], + }, + }, + ) + + # Test accessing a quantity with tensordot + def test_get_operation_tree_tensordot(self): + tensor = OperationTree.objects.create( + id=3, + operation="tensor_product", + parameters={"a_index": 1, "b_index": 1}, + quantity=self.quantity, + ) + self.variable.label = "a" + self.variable.child_operation = tensor + self.variable.save() + self.constant.label = "b" + self.constant.child_operation = tensor + self.constant.save() + request = self.client.get("/v1/data/set/1/") + self.variable.child_operation = None + self.constant.child_operation = None + self.variable.save() + self.constant.save() + tensor.delete() + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data["data_contents"][0]["history"]["operation_tree"], + { + "operation": "tensor_product", + "parameters": { + "a": { + "operation": "variable", + "parameters": {"hash_value": 111, "name": "x"}, + }, + "b": { + "operation": "constant", + "parameters": {"value": 1}, + }, + "a_index": 1, + "b_index": 1, + }, + }, + ) + + @classmethod + def tearDownClass(cls): + cls.user.delete() + cls.quantity.delete() + cls.dataset.delete() + cls.variable.delete() + cls.constant.delete() diff --git a/sasdata/fair_database/data/test/test_published_state.py b/sasdata/fair_database/data/test/test_published_state.py new file mode 100644 index 00000000..d54f1312 --- /dev/null +++ b/sasdata/fair_database/data/test/test_published_state.py @@ -0,0 +1,583 @@ +from django.contrib.auth.models import User +from django.db.models import Max +from rest_framework import status +from rest_framework.test import APIClient, APITestCase + +from data.models import PublishedState, Session + + +# TODO: account for non-placeholder doi +# Get the placeholder DOI for a session based on id +def doi_generator(id: int): + return "http://127.0.0.1:8000/v1/data/session/" + str(id) + "/" + + +class TestPublishedState(APITestCase): + """Test HTTP methods of PublishedStateView.""" + + @classmethod + def setUpTestData(cls): + cls.user1 = User.objects.create_user( + id=1, username="testUser1", password="secret" + ) + cls.user2 = User.objects.create_user( + id=2, username="testUser2", password="secret" + ) + cls.public_session = Session.objects.create( + id=1, current_user=cls.user1, title="Public Session", is_public=True + ) + cls.private_session = Session.objects.create( + id=2, current_user=cls.user1, title="Private Session", is_public=False + ) + cls.unowned_session = Session.objects.create( + id=3, title="Unowned Session", is_public=True + ) + cls.unpublished_session = Session.objects.create( + id=4, current_user=cls.user1, title="Publishable Session", is_public=True + ) + cls.public_ps = PublishedState.objects.create( + id=1, + doi=doi_generator(1), + published=True, + session=cls.public_session, + ) + cls.private_ps = PublishedState.objects.create( + id=2, + doi=doi_generator(2), + published=False, + session=cls.private_session, + ) + cls.unowned_ps = PublishedState.objects.create( + id=3, + doi=doi_generator(3), + published=True, + session=cls.unowned_session, + ) + cls.auth_client1 = APIClient() + cls.auth_client2 = APIClient() + cls.auth_client1.force_authenticate(cls.user1) + cls.auth_client2.force_authenticate(cls.user2) + + # Test listing published states including those of owned private sessions + def test_list_published_states_private(self): + request = self.auth_client1.get("/v1/data/published/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "published_state_ids": { + 1: { + "title": "Public Session", + "published": True, + "doi": doi_generator(1), + }, + 2: { + "title": "Private Session", + "published": False, + "doi": doi_generator(2), + }, + 3: { + "title": "Unowned Session", + "published": True, + "doi": doi_generator(3), + }, + } + }, + ) + + # Test listing published states of public sessions + def test_list_published_states_public(self): + request = self.auth_client2.get("/v1/data/published/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "published_state_ids": { + 1: { + "title": "Public Session", + "published": True, + "doi": doi_generator(1), + }, + 3: { + "title": "Unowned Session", + "published": True, + "doi": doi_generator(3), + }, + } + }, + ) + + # Test listing published states including sessions with access granted + def test_list_published_states_shared(self): + self.private_session.users.add(self.user2) + request = self.auth_client2.get("/v1/data/published/") + self.private_session.users.remove(self.user2) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "published_state_ids": { + 1: { + "title": "Public Session", + "published": True, + "doi": doi_generator(1), + }, + 2: { + "title": "Private Session", + "published": False, + "doi": doi_generator(2), + }, + 3: { + "title": "Unowned Session", + "published": True, + "doi": doi_generator(3), + }, + } + }, + ) + + # Test listing published states while unauthenticated + def test_list_published_states_unauthenticated(self): + request = self.client.get("/v1/data/published/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "published_state_ids": { + 1: { + "title": "Public Session", + "published": True, + "doi": doi_generator(1), + }, + 3: { + "title": "Unowned Session", + "published": True, + "doi": doi_generator(3), + }, + } + }, + ) + + # Test listing a user's own published states + def test_list_user_published_states_private(self): + request = self.auth_client1.get( + "/v1/data/published/", data={"username": "testUser1"} + ) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "published_state_ids": { + 1: { + "title": "Public Session", + "published": True, + "doi": doi_generator(1), + }, + 2: { + "title": "Private Session", + "published": False, + "doi": doi_generator(2), + }, + } + }, + ) + + # Test listing another user's published states + def test_list_user_published_states_public(self): + request = self.auth_client2.get( + "/v1/data/published/", data={"username": "testUser1"} + ) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "published_state_ids": { + 1: { + "title": "Public Session", + "published": True, + "doi": doi_generator(1), + } + } + }, + ) + + # Test listing another user's published states with access granted + def test_list_user_published_states_shared(self): + self.private_session.users.add(self.user2) + request = self.auth_client2.get( + "/v1/data/published/", data={"username": "testUser1"} + ) + self.private_session.users.remove(self.user2) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "published_state_ids": { + 1: { + "title": "Public Session", + "published": True, + "doi": doi_generator(1), + }, + 2: { + "title": "Private Session", + "published": False, + "doi": doi_generator(2), + }, + } + }, + ) + + # Test listing a user's published states while unauthenticated + def test_list_user_published_states_unauthenticated(self): + request = self.client.get("/v1/data/published/", data={"username": "testUser1"}) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "published_state_ids": { + 1: { + "title": "Public Session", + "published": True, + "doi": doi_generator(1), + } + } + }, + ) + + # Test creating a published state for a private session + def test_published_state_created_private(self): + self.unpublished_session.is_public = False + self.unpublished_session.save() + published_state = {"published": True, "session": 4} + request = self.auth_client1.post("/v1/data/published/", data=published_state) + max_id = PublishedState.objects.aggregate(Max("id"))["id__max"] + new_ps = PublishedState.objects.get(id=max_id) + self.publishable_session = Session.objects.get(id=4) + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertEqual( + request.data, + { + "published_state_id": max_id, + "session_id": 4, + "title": "Publishable Session", + "doi": doi_generator(4), + "published": True, + "current_user": "testUser1", + "is_public": False, + }, + ) + self.assertEqual(self.publishable_session.published_state, new_ps) + self.assertEqual(new_ps.session, self.publishable_session) + new_ps.delete() + self.unpublished_session.is_public = True + self.unpublished_session.save() + + # Test creating a published state for a public session + def test_published_state_created_public(self): + published_state = {"published": False, "session": 4} + request = self.auth_client1.post("/v1/data/published/", data=published_state) + max_id = PublishedState.objects.aggregate(Max("id"))["id__max"] + new_ps = PublishedState.objects.get(id=max_id) + self.publishable_session = Session.objects.get(id=4) + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertEqual( + request.data, + { + "published_state_id": max_id, + "session_id": 4, + "title": "Publishable Session", + "doi": doi_generator(4), + "published": False, + "current_user": "testUser1", + "is_public": True, + }, + ) + self.assertEqual(self.publishable_session.published_state, new_ps) + self.assertEqual(new_ps.session, self.publishable_session) + new_ps.delete() + + # Test that you can't create a published state for an unowned session + def test_published_state_created_unowned(self): + self.unpublished_session.current_user = None + self.unpublished_session.save() + published_state = {"published": True, "session": 4} + request = self.auth_client1.post("/v1/data/published/", data=published_state) + self.assertEqual(request.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(len(PublishedState.objects.all()), 3) + self.unpublished_session.current_user = self.user1 + self.unpublished_session.save() + + # Test that an unauthenticated user cannot create a published state + def test_published_state_created_unauthenticated(self): + published_state = {"published": True, "session": 4} + request = self.client.post("/v1/data/published/", data=published_state) + self.assertEqual(request.status_code, status.HTTP_401_UNAUTHORIZED) + self.assertEqual(len(PublishedState.objects.all()), 3) + + # Test that a user cannot create a published state for a session they don't own + def test_published_state_created_unauthorized(self): + published_state = {"published": True, "session": 4} + request = self.auth_client2.post("/v1/data/published/", data=published_state) + self.assertEqual(request.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(len(PublishedState.objects.all()), 3) + + # Test that only one published state can be created per session + def test_no_duplicate_published_states(self): + published_state = {"published": True, "session": 1} + request = self.auth_client1.post("/v1/data/published/", data=published_state) + self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST) + + @classmethod + def tearDownClass(cls): + cls.public_session.delete() + cls.private_session.delete() + cls.unowned_session.delete() + cls.user1.delete() + cls.user2.delete() + + +class TestSinglePublishedState(APITestCase): + """Test HTTP methods of SinglePublishedStateView.""" + + @classmethod + def setUpTestData(cls): + cls.user1 = User.objects.create_user( + id=1, username="testUser1", password="secret" + ) + cls.user2 = User.objects.create_user( + id=2, username="testUser2", password="secret" + ) + cls.public_session = Session.objects.create( + id=1, current_user=cls.user1, title="Public Session", is_public=True + ) + cls.private_session = Session.objects.create( + id=2, current_user=cls.user1, title="Private Session", is_public=False + ) + cls.unowned_session = Session.objects.create( + id=3, title="Unowned Session", is_public=True + ) + cls.public_ps = PublishedState.objects.create( + id=1, + doi=doi_generator(1), + published=True, + session=cls.public_session, + ) + cls.private_ps = PublishedState.objects.create( + id=2, + doi=doi_generator(2), + published=False, + session=cls.private_session, + ) + cls.unowned_ps = PublishedState.objects.create( + id=3, + doi=doi_generator(3), + published=True, + session=cls.unowned_session, + ) + cls.auth_client1 = APIClient() + cls.auth_client2 = APIClient() + cls.auth_client1.force_authenticate(cls.user1) + cls.auth_client2.force_authenticate(cls.user2) + + # Test viewing a published state of a public session + def test_get_public_published_state(self): + request1 = self.auth_client2.get("/v1/data/published/1/") + request2 = self.client.get("/v1/data/published/1/") + self.assertEqual(request1.status_code, status.HTTP_200_OK) + self.assertEqual(request2.status_code, status.HTTP_200_OK) + self.assertEqual( + request1.data, + { + "id": 1, + "doi": doi_generator(1), + "published": True, + "session": 1, + "title": "Public Session", + "current_user": "testUser1", + "is_public": True, + }, + ) + self.assertEqual(request1.data, request2.data) + + # Test viewing a published state of a private session + def test_get_private_published_state(self): + request = self.auth_client1.get("/v1/data/published/2/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "id": 2, + "doi": doi_generator(2), + "published": False, + "session": 2, + "title": "Private Session", + "current_user": "testUser1", + "is_public": False, + }, + ) + + # Test viewing a published state of an unowned session + def test_get_unowned_published_state(self): + request = self.auth_client1.get("/v1/data/published/3/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "id": 3, + "doi": doi_generator(3), + "published": True, + "session": 3, + "title": "Unowned Session", + "current_user": "", + "is_public": True, + }, + ) + + # Test viewing a published state of a session with access granted + def test_get_shared_published_state(self): + self.private_session.users.add(self.user2) + request = self.auth_client2.get("/v1/data/published/2/") + self.private_session.users.remove(self.user2) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "id": 2, + "doi": doi_generator(2), + "published": False, + "session": 2, + "title": "Private Session", + "current_user": "testUser1", + "is_public": False, + }, + ) + + # Test a user can't view a published state of a private session they don't own + def test_get_private_published_state_unauthorized(self): + request1 = self.client.get("/v1/data/published/2/") + request2 = self.auth_client2.get("/v1/data/published/2/") + self.assertEqual(request1.status_code, status.HTTP_401_UNAUTHORIZED) + self.assertEqual(request2.status_code, status.HTTP_403_FORBIDDEN) + + # Test updating a published state of a public session + def test_update_public_published_state(self): + request = self.auth_client1.put( + "/v1/data/published/1/", data={"published": False} + ) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "published_state_id": 1, + "session_id": 1, + "title": "Public Session", + "published": False, + "is_public": True, + }, + ) + self.assertFalse(PublishedState.objects.get(id=1).published) + self.public_ps.save() + + # Test updating a published state of a private session + def test_update_private_published_state(self): + request = self.auth_client1.put( + "/v1/data/published/2/", data={"published": True} + ) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "published_state_id": 2, + "session_id": 2, + "title": "Private Session", + "published": True, + "is_public": False, + }, + ) + self.assertTrue(PublishedState.objects.get(id=2).published) + self.private_ps.save() + + # Test a user can't update the published state of an unowned session + def test_update_unowned_published_state(self): + request1 = self.auth_client1.put( + "/v1/data/published/3/", data={"published": False} + ) + request2 = self.client.put("/v1/data/published/3/", data={"published": False}) + self.assertEqual(request1.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request2.status_code, status.HTTP_401_UNAUTHORIZED) + self.assertTrue(PublishedState.objects.get(id=3).published) + + # Test a user can't update a public published state unauthorized + def test_update_public_published_state_unauthorized(self): + request1 = self.auth_client2.put( + "/v1/data/published/1/", data={"published": False} + ) + self.public_session.users.add(self.user2) + request2 = self.auth_client2.put( + "/v1/data/published/1/", data={"published": False} + ) + self.public_session.users.remove(self.user2) + request3 = self.client.put("/v1/data/published/1/", data={"published": False}) + self.assertEqual(request1.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request2.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request3.status_code, status.HTTP_401_UNAUTHORIZED) + self.assertTrue(PublishedState.objects.get(id=1).published) + + # Test a user can't update a private published state unauthorized + def test_update_private_published_state_unauthorized(self): + request1 = self.auth_client2.put( + "/v1/data/published/2/", data={"published": True} + ) + self.public_session.users.add(self.user2) + request2 = self.auth_client2.put( + "/v1/data/published/2/", data={"published": True} + ) + self.public_session.users.remove(self.user2) + request3 = self.client.put("/v1/data/published/2/", data={"published": True}) + self.assertEqual(request1.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request2.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request3.status_code, status.HTTP_401_UNAUTHORIZED) + self.assertFalse(PublishedState.objects.get(id=2).published) + + # Test deleting a published state of a private session + def test_delete_private_published_state(self): + request = self.auth_client1.delete("/v1/data/published/2/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual(len(PublishedState.objects.all()), 2) + self.assertEqual(len(Session.objects.all()), 3) + self.assertRaises(PublishedState.DoesNotExist, PublishedState.objects.get, id=2) + self.private_ps = PublishedState.objects.create( + id=2, + doi=doi_generator(2), + published=False, + session=self.private_session, + ) + + # Test a user can't delete a private published state unauthorized + def test_delete_private_published_state_unauthorized(self): + request1 = self.auth_client2.delete("/v1/data/published/2/") + self.private_session.users.add(self.user2) + request2 = self.auth_client2.delete("/v1/data/published/2/") + self.private_session.users.remove(self.user2) + request3 = self.client.delete("/v1/data/published/2/") + self.assertEqual(request1.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request2.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request3.status_code, status.HTTP_401_UNAUTHORIZED) + + # Test a user can't delete a published state of a public + def test_cant_delete_public_published_state(self): + request = self.auth_client1.delete("/v1/data/published/1/") + self.assertEqual(request.status_code, status.HTTP_403_FORBIDDEN) + + # Test a user can't delete an unowned published state + def test_delete_unowned_published_state(self): + request = self.auth_client1.delete("/v1/data/published/3/") + self.assertEqual(request.status_code, status.HTTP_403_FORBIDDEN) + + @classmethod + def tearDownClass(cls): + cls.public_session.delete() + cls.private_session.delete() + cls.unowned_session.delete() + cls.user1.delete() + cls.user2.delete() diff --git a/sasdata/fair_database/data/test/test_session.py b/sasdata/fair_database/data/test/test_session.py new file mode 100644 index 00000000..1f1f95a7 --- /dev/null +++ b/sasdata/fair_database/data/test/test_session.py @@ -0,0 +1,701 @@ +from django.contrib.auth.models import User +from django.db.models import Max +from rest_framework.test import APIClient, APITestCase +from rest_framework import status + +from data.models import DataSet, PublishedState, Session + + +class TestSession(APITestCase): + """Test HTTP methods of SessionView.""" + + @classmethod + def setUpTestData(cls): + cls.user1 = User.objects.create_user( + id=1, username="testUser1", password="secret" + ) + cls.user2 = User.objects.create_user( + id=2, username="testUser2", password="secret" + ) + cls.public_session = Session.objects.create( + id=1, current_user=cls.user1, title="Public Session", is_public=True + ) + cls.private_session = Session.objects.create( + id=2, current_user=cls.user1, title="Private Session", is_public=False + ) + cls.unowned_session = Session.objects.create( + id=3, title="Unowned Session", is_public=True + ) + cls.auth_client1 = APIClient() + cls.auth_client2 = APIClient() + cls.auth_client1.force_authenticate(cls.user1) + cls.auth_client2.force_authenticate(cls.user2) + + # Test listing sessions + def test_list_private(self): + request = self.auth_client1.get("/v1/data/session/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "session_ids": { + 1: "Public Session", + 2: "Private Session", + 3: "Unowned Session", + } + }, + ) + + # Test listing public sessions + def test_list_public(self): + request = self.auth_client2.get("/v1/data/session/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, {"session_ids": {1: "Public Session", 3: "Unowned Session"}} + ) + + # Test listing sessions while unauthenticated + def test_list_unauthenticated(self): + request = self.client.get("/v1/data/session/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, {"session_ids": {1: "Public Session", 3: "Unowned Session"}} + ) + + # Test listing a session with access granted + def test_list_granted_access(self): + self.private_session.users.add(self.user2) + request = self.auth_client2.get("/v1/data/session/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "session_ids": { + 1: "Public Session", + 2: "Private Session", + 3: "Unowned Session", + } + }, + ) + self.private_session.users.remove(self.user2) + + # Test listing by username + def test_list_username(self): + request = self.auth_client1.get( + "/v1/data/session/", data={"username": "testUser1"} + ) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, {"session_ids": {1: "Public Session", 2: "Private Session"}} + ) + + # Test listing by another user's username + def test_list_other_username(self): + request = self.auth_client2.get( + "/v1/data/session/", data={"username": "testUser1"} + ) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual(request.data, {"session_ids": {1: "Public Session"}}) + + # Test creating a public session + def test_session_created(self): + session = { + "title": "New session", + "datasets": [ + { + "name": "New dataset", + "metadata": { + "title": "New metadata", + "run": 0, + "description": "test", + "instrument": {}, + "process": {}, + "sample": {}, + }, + "data_contents": [], + } + ], + "is_public": True, + "published_state": {"published": False}, + } + request = self.auth_client1.post( + "/v1/data/session/", data=session, format="json" + ) + max_id = Session.objects.aggregate(Max("id"))["id__max"] + new_session = Session.objects.get(id=max_id) + new_dataset = new_session.datasets.get() + new_metadata = new_dataset.metadata + new_published_state = new_session.published_state + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertEqual( + request.data, + { + "session_id": max_id, + "title": "New session", + "authenticated": True, + "current_user": "testUser1", + "is_public": True, + }, + ) + self.assertEqual(new_session.title, "New session") + self.assertEqual(new_dataset.name, "New dataset") + self.assertEqual(new_metadata.title, "New metadata") + self.assertEqual(new_session.current_user, self.user1) + self.assertEqual(new_dataset.current_user, self.user1) + self.assertTrue(all([new_session.is_public, new_dataset.is_public])) + self.assertFalse(new_published_state.published) + new_session.delete() + + # Test creating a private session + def test_session_created_private(self): + session = { + "title": "New session", + "datasets": [ + { + "name": "New dataset", + "metadata": { + "title": "New metadata", + "run": 0, + "description": "test", + "instrument": {}, + "process": {}, + "sample": {}, + }, + "data_contents": [], + } + ], + "is_public": False, + } + request = self.auth_client1.post( + "/v1/data/session/", data=session, format="json" + ) + max_id = Session.objects.aggregate(Max("id"))["id__max"] + new_session = Session.objects.get(id=max_id) + new_dataset = new_session.datasets.get() + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertEqual( + request.data, + { + "session_id": max_id, + "title": "New session", + "authenticated": True, + "current_user": "testUser1", + "is_public": False, + }, + ) + self.assertEqual(new_session.current_user, self.user1) + self.assertEqual(new_dataset.current_user, self.user1) + self.assertFalse(any([new_session.is_public, new_dataset.is_public])) + new_session.delete() + + # Test creating a session while unauthenticated + def test_session_created_unauthenticated(self): + session = { + "title": "New session", + "datasets": [ + { + "name": "New dataset", + "metadata": { + "title": "New metadata", + "run": 0, + "description": "test", + "instrument": {}, + "process": {}, + "sample": {}, + }, + "data_contents": [], + } + ], + "is_public": True, + } + request = self.client.post("/v1/data/session/", data=session, format="json") + max_id = Session.objects.aggregate(Max("id"))["id__max"] + new_session = Session.objects.get(id=max_id) + new_dataset = new_session.datasets.get() + self.assertEqual(request.status_code, status.HTTP_201_CREATED) + self.assertEqual( + request.data, + { + "session_id": max_id, + "title": "New session", + "authenticated": False, + "current_user": "", + "is_public": True, + }, + ) + self.assertIsNone(new_session.current_user) + self.assertIsNone(new_dataset.current_user) + self.assertTrue(all([new_session.is_public, new_dataset.is_public])) + new_session.delete() + + # Test that a private session must have an owner + def test_no_private_unowned_session(self): + session = {"title": "New session", "datasets": [], "is_public": False} + request = self.client.post("/v1/data/session/", data=session, format="json") + self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST) + + # Test post fails with dataset validation issue + def test_no_session_invalid_dataset(self): + session = { + "title": "New session", + "datasets": [ + { + "metadata": { + "title": "New metadata", + "run": 0, + "description": "test", + "instrument": {}, + "process": {}, + "sample": {}, + }, + "data_contents": [], + } + ], + "is_public": True, + } + request = self.auth_client1.post( + "/v1/data/session/", data=session, format="json" + ) + self.assertEqual(request.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(len(Session.objects.all()), 3) + self.assertEqual(len(DataSet.objects.all()), 0) + + @classmethod + def tearDownClass(cls): + cls.public_session.delete() + cls.private_session.delete() + cls.unowned_session.delete() + cls.user1.delete() + cls.user2.delete() + + +class TestSingleSession(APITestCase): + """Test HTTP methods of SingleSessionView.""" + + @classmethod + def setUpTestData(cls): + cls.user1 = User.objects.create_user( + id=1, username="testUser1", password="secret" + ) + cls.user2 = User.objects.create_user( + id=2, username="testUser2", password="secret" + ) + cls.public_session = Session.objects.create( + id=1, current_user=cls.user1, title="Public Session", is_public=True + ) + cls.private_session = Session.objects.create( + id=2, current_user=cls.user1, title="Private Session", is_public=False + ) + cls.unowned_session = Session.objects.create( + id=3, title="Unowned Session", is_public=True + ) + cls.public_dataset = DataSet.objects.create( + id=1, + current_user=cls.user1, + is_public=True, + name="Public Dataset", + session=cls.public_session, + ) + cls.private_dataset = DataSet.objects.create( + id=2, + current_user=cls.user1, + name="Private Dataset", + session=cls.private_session, + ) + cls.unowned_dataset = DataSet.objects.create( + id=3, is_public=True, name="Unowned Dataset", session=cls.unowned_session + ) + cls.private_published_state = PublishedState.objects.create( + id=2, + session=cls.private_session, + published=False, + doi="http://localhost:8000/v1/data/session/2/", + ) + cls.auth_client1 = APIClient() + cls.auth_client2 = APIClient() + cls.auth_client1.force_authenticate(cls.user1) + cls.auth_client2.force_authenticate(cls.user2) + + # Test loading another user's public session + def test_get_public_session(self): + request = self.auth_client2.get("/v1/data/session/1/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "id": 1, + "current_user": "testUser1", + "users": [], + "is_public": True, + "title": "Public Session", + "datasets": [ + { + "id": 1, + "current_user": 1, + "users": [], + "is_public": True, + "name": "Public Dataset", + "files": [], + "metadata": None, + "data_contents": [], + } + ], + "published_state": None, + }, + ) + + # Test loading a private session as the owner + def test_get_private_session(self): + request = self.auth_client1.get("/v1/data/session/2/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "id": 2, + "current_user": "testUser1", + "users": [], + "is_public": False, + "title": "Private Session", + "published_state": { + "id": 2, + "published": False, + "doi": "http://localhost:8000/v1/data/session/2/", + "session": 2, + }, + "datasets": [ + { + "id": 2, + "current_user": 1, + "users": [], + "is_public": False, + "name": "Private Dataset", + "files": [], + "metadata": None, + "data_contents": [], + } + ], + }, + ) + + # Test loading a private session as a user with granted access + def test_get_private_session_access_granted(self): + self.private_session.users.add(self.user2) + request = self.auth_client2.get("/v1/data/session/2/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.private_session.users.remove(self.user2) + + # Test loading an unowned session + def test_get_unowned_session(self): + request = self.auth_client1.get("/v1/data/session/3/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "id": 3, + "current_user": None, + "users": [], + "is_public": True, + "title": "Unowned Session", + "published_state": None, + "datasets": [ + { + "id": 3, + "current_user": None, + "users": [], + "is_public": True, + "name": "Unowned Dataset", + "files": [], + "metadata": None, + "data_contents": [], + } + ], + }, + ) + + # Test loading another user's private session + def test_get_private_session_unauthorized(self): + request1 = self.auth_client2.get("/v1/data/session/2/") + request2 = self.client.get("/v1/data/session/2/") + self.assertEqual(request1.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request2.status_code, status.HTTP_401_UNAUTHORIZED) + + # Test updating a public session + def test_update_public_session(self): + request = self.auth_client1.put( + "/v1/data/session/1/", data={"is_public": False} + ) + session = Session.objects.get(id=1) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + {"session_id": 1, "title": "Public Session", "is_public": False}, + ) + self.assertFalse(session.is_public) + session.is_public = False + session.save() + + # Test creating a published state by updating a session + def test_update_session_new_published_state(self): + request = self.auth_client1.put( + "/v1/data/session/1/", + data={"published_state": {"published": False}}, + format="json", + ) + new_published_state = Session.objects.get(id=1).published_state + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertFalse(new_published_state.published) + new_published_state.delete() + + # Test that another user's public session cannot be updated + def test_update_public_session_unauthorized(self): + request1 = self.auth_client2.put( + "/v1/data/session/1/", data={"is_public": False} + ) + request2 = self.client.put("/v1/data/session/1/", data={"is_public": False}) + session = Session.objects.get(id=1) + session.users.add(self.user2) + request3 = self.auth_client2.put( + "/v1/data/session/1/", data={"is_public": False} + ) + session.users.remove(self.user2) + self.assertEqual(request1.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request2.status_code, status.HTTP_401_UNAUTHORIZED) + self.assertEqual(request3.status_code, status.HTTP_403_FORBIDDEN) + self.assertTrue(Session.objects.get(id=1).is_public) + + # Test updating a private session + def test_update_private_session(self): + request1 = self.auth_client1.put( + "/v1/data/session/2/", data={"is_public": True} + ) + session = Session.objects.get(id=2) + self.assertEqual(request1.status_code, status.HTTP_200_OK) + self.assertEqual( + request1.data, + {"session_id": 2, "title": "Private Session", "is_public": True}, + ) + self.assertTrue(session.is_public) + self.assertTrue(session.datasets.get().is_public) + session.is_public = False + session.save() + + # Test updating a published state through its session + def test_update_session_published_state(self): + request = self.auth_client1.put( + "/v1/data/session/2/", + data={"published_state": {"published": True}}, + format="json", + ) + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertTrue(PublishedState.objects.get(id=2).published) + self.private_published_state.save() + + # Test that another user's private session cannot be updated + def test_update_private_session_unauthorized(self): + request1 = self.auth_client2.put( + "/v1/data/session/2/", data={"is_public": True} + ) + request2 = self.client.put("/v1/data/session/2/", data={"is_public": True}) + session = Session.objects.get(id=2) + session.users.add(self.user2) + request3 = self.auth_client2.put( + "/v1/data/session/2/", data={"is_public": True} + ) + session.users.remove(self.user2) + self.assertEqual(request1.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request2.status_code, status.HTTP_401_UNAUTHORIZED) + self.assertEqual(request3.status_code, status.HTTP_403_FORBIDDEN) + self.assertFalse(Session.objects.get(id=2).is_public) + + # Test that an unowned session cannot be updated + def test_update_unowned_session(self): + request = self.auth_client1.put( + "/v1/data/session/3/", data={"is_public": False} + ) + self.assertEqual(request.status_code, status.HTTP_403_FORBIDDEN) + self.assertTrue(Session.objects.get(id=3).is_public) + + # Test deleting a private session + def test_delete_private_session(self): + request = self.auth_client1.delete("/v1/data/session/2/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertRaises(Session.DoesNotExist, Session.objects.get, id=2) + self.assertRaises(DataSet.DoesNotExist, DataSet.objects.get, id=2) + self.assertRaises(PublishedState.DoesNotExist, PublishedState.objects.get, id=2) + self.private_session = Session.objects.create( + id=2, current_user=self.user1, title="Private Session", is_public=False + ) + self.private_dataset = DataSet.objects.create( + id=2, + current_user=self.user1, + name="Private Dataset", + session=self.private_session, + ) + self.private_published_state = PublishedState.objects.create( + id=2, + session=self.private_session, + published=False, + doi="http://localhost:8000/v1/data/session/2/", + ) + + # Test that another user's private session cannot be deleted + def test_delete_private_session_unauthorized(self): + request1 = self.auth_client2.delete("/v1/data/session/2/") + request2 = self.client.delete("/v1/data/session/2/") + self.private_session.users.add(self.user2) + request3 = self.auth_client2.delete("/v1/data/session/2/") + self.private_session.users.remove(self.user2) + self.assertEqual(request1.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request2.status_code, status.HTTP_401_UNAUTHORIZED) + self.assertEqual(request3.status_code, status.HTTP_403_FORBIDDEN) + + # Test that a public session cannot be deleted + def test_delete_public_session(self): + request = self.auth_client1.delete("/v1/data/session/1/") + self.assertEqual(request.status_code, status.HTTP_403_FORBIDDEN) + + # Test that an unowned session cannot be deleted + def test_delete_unowned_session(self): + request = self.auth_client1.delete("/v1/data/session/3/") + self.assertEqual(request.status_code, status.HTTP_403_FORBIDDEN) + + @classmethod + def tearDownClass(cls): + cls.public_session.delete() + cls.private_session.delete() + cls.unowned_session.delete() + cls.user1.delete() + cls.user2.delete() + + +class TestSessionAccessManagement(APITestCase): + """Test HTTP methods of SessionUsersView.""" + + @classmethod + def setUpTestData(cls): + cls.user1 = User.objects.create_user(username="testUser1", password="secret") + cls.user2 = User.objects.create_user(username="testUser2", password="secret") + cls.private_session = Session.objects.create( + id=1, current_user=cls.user1, title="Private Session", is_public=False + ) + cls.shared_session = Session.objects.create( + id=2, current_user=cls.user1, title="Shared Session", is_public=False + ) + cls.private_dataset = DataSet.objects.create( + id=1, + current_user=cls.user1, + name="Private Dataset", + session=cls.private_session, + ) + cls.shared_dataset = DataSet.objects.create( + id=2, + current_user=cls.user1, + name="Shared Dataset", + session=cls.shared_session, + ) + cls.shared_session.users.add(cls.user2) + cls.shared_dataset.users.add(cls.user2) + cls.client_owner = APIClient() + cls.client_other = APIClient() + cls.client_owner.force_authenticate(cls.user1) + cls.client_other.force_authenticate(cls.user2) + + # Test listing access to an unshared session + def test_list_access_private(self): + request = self.client_owner.get("/v1/data/session/1/users/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "session_id": 1, + "title": "Private Session", + "is_public": False, + "users": [], + }, + ) + + # Test listing access to a shared session + def test_list_access_shared(self): + request = self.client_owner.get("/v1/data/session/2/users/") + self.assertEqual(request.status_code, status.HTTP_200_OK) + self.assertEqual( + request.data, + { + "session_id": 2, + "title": "Shared Session", + "is_public": False, + "users": ["testUser2"], + }, + ) + + # Test that only the owner can view access + def test_list_access_unauthorized(self): + request1 = self.client_other.get("/v1/data/session/1/users/") + request2 = self.client_other.get("/v1/data/session/2/users/") + self.assertEqual(request1.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request2.status_code, status.HTTP_403_FORBIDDEN) + + # Test granting access to a session + def test_grant_access(self): + request1 = self.client_owner.put( + "/v1/data/session/1/users/", {"username": "testUser2", "access": True} + ) + request2 = self.client_other.get("/v1/data/session/1/") + request3 = self.client_other.get("/v1/data/set/1/") + self.assertEqual(request1.status_code, status.HTTP_200_OK) + self.assertEqual( + request1.data, + { + "username": "testUser2", + "session_id": 1, + "title": "Private Session", + "access": True, + }, + ) + self.assertEqual(request2.status_code, status.HTTP_200_OK) + self.assertEqual(request3.status_code, status.HTTP_200_OK) + self.assertIn(self.user2, self.private_session.users.all()) # codespell:ignore + self.assertIn(self.user2, self.private_dataset.users.all()) # codespell:ignore + self.private_session.users.remove(self.user2) + self.private_dataset.users.remove(self.user2) + + # Test revoking access to a session + def test_revoke_access(self): + request1 = self.client_owner.put( + "/v1/data/session/2/users/", {"username": "testUser2", "access": False} + ) + request2 = self.client_other.get("/v1/data/session/2/") + request3 = self.client_other.get("/v1/data/session/2/") + self.assertEqual(request1.status_code, status.HTTP_200_OK) + self.assertEqual(request2.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request3.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual( + request1.data, + { + "username": "testUser2", + "session_id": 2, + "title": "Shared Session", + "access": False, + }, + ) + self.assertNotIn(self.user2, self.shared_session.users.all()) + self.assertNotIn(self.user2, self.shared_dataset.users.all()) + self.shared_session.users.add(self.user2) + self.shared_dataset.users.add(self.user2) + + # Test that only the owner can change access + def test_revoke_access_unauthorized(self): + request1 = self.client_other.put( + "/v1/data/session/2/users/", {"username": "testUser2", "access": False} + ) + request2 = self.client_other.get("/v1/data/session/2/") + self.assertEqual(request1.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(request2.status_code, status.HTTP_200_OK) + self.assertIn(self.user2, self.shared_session.users.all()) # codespell:ignore + + @classmethod + def tearDownClass(cls): + cls.private_session.delete() + cls.shared_session.delete() + cls.user1.delete() + cls.user2.delete() diff --git a/sasdata/fair_database/data/urls.py b/sasdata/fair_database/data/urls.py new file mode 100644 index 00000000..0e94f60c --- /dev/null +++ b/sasdata/fair_database/data/urls.py @@ -0,0 +1,49 @@ +from django.urls import path + +from . import views + +urlpatterns = [ + path("file/", views.DataFileView.as_view(), name="view and create files"), + path( + "file//", + views.SingleDataFileView.as_view(), + name="view, download, modify, delete files", + ), + path( + "file//users/", + views.DataFileUsersView.as_view(), + name="manage access to files", + ), + path("set/", views.DataSetView.as_view(), name="view and create datasets"), + path( + "set//", + views.SingleDataSetView.as_view(), + name="load, modify, delete datasets", + ), + path( + "set//users/", + views.DataSetUsersView.as_view(), + name="manage access to datasets", + ), + path("session/", views.SessionView.as_view(), name="view and create sessions"), + path( + "session//", + views.SingleSessionView.as_view(), + name="load, modify, delete sessions", + ), + path( + "session//users/", + views.SessionUsersView.as_view(), + name="manage access to sessions", + ), + path( + "published/", + views.PublishedStateView.as_view(), + name="view and create published states", + ), + path( + "published//", + views.SinglePublishedStateView.as_view(), + name="load, modify, delete published states", + ), +] diff --git a/sasdata/fair_database/data/views.py b/sasdata/fair_database/data/views.py new file mode 100644 index 00000000..7399e676 --- /dev/null +++ b/sasdata/fair_database/data/views.py @@ -0,0 +1,707 @@ +import os +import json + +from django.contrib.auth.models import User +from django.shortcuts import get_object_or_404 +from django.http import ( + HttpResponseBadRequest, + HttpResponseForbidden, + HttpResponse, + Http404, + FileResponse, +) +from rest_framework.response import Response +from rest_framework import status +from rest_framework.views import APIView +from drf_spectacular.utils import extend_schema + +from sasdata.dataloader.loader import Loader +from data.serializers import ( + DataFileSerializer, + DataSetSerializer, + AccessManagementSerializer, + SessionSerializer, + PublishedStateSerializer, + PublishedStateUpdateSerializer, +) +from data.models import DataFile, DataSet, PublishedState, Session +from data.forms import DataFileForm +from fair_database import permissions +from fair_database.permissions import DataPermission + + +class DataFileView(APIView): + """ + View associated with the DataFile model. + + Functionality for viewing a list of files and uploading a new file. + """ + + # List of datafiles + @extend_schema( + description="Retrieve a list of accessible data files by id and filename." + ) + def get(self, request, version=None): + if "username" in request.GET: + search_user = get_object_or_404(User, username=request.GET["username"]) + data_list = {"user_data_ids": {}} + private_data = DataFile.objects.filter(current_user=search_user) + for x in private_data: + if permissions.check_permissions(request, x): + data_list["user_data_ids"][x.id] = x.file_name + else: + public_data = DataFile.objects.all() + data_list = {"public_data_ids": {}} + for x in public_data: + if permissions.check_permissions(request, x): + data_list["public_data_ids"][x.id] = x.file_name + return Response(data_list) + + # Create a datafile + @extend_schema(description="Upload a data file.") + def post(self, request, version=None): + form = DataFileForm(request.data, request.FILES) + if form.is_valid(): + form.save() + db = DataFile.objects.get(pk=form.instance.pk) + serializer = DataFileSerializer( + db, + data={ + "file_name": os.path.basename(form.instance.file.path), + "current_user": None, + "users": [], + }, + context={"is_public": db.is_public}, + ) + if request.user.is_authenticated: + serializer.initial_data["current_user"] = request.user.id + + if serializer.is_valid(raise_exception=True): + serializer.save() + return_data = { + "current_user": request.user.username, + "authenticated": request.user.is_authenticated, + "file_id": db.id, + "file_alternative_name": serializer.data["file_name"], + "is_public": serializer.data["is_public"], + } + return Response(return_data, status=status.HTTP_201_CREATED) + + # Create a datafile + @extend_schema(description="Upload a data file.") + def put(self, request, version=None): + return self.post(request, version) + + +class SingleDataFileView(APIView): + """ + View associated with a single DataFile. + + Functionality for viewing, modifying, or deleting a DataFile. + """ + + # Load the contents of a datafile or download the file to a device + @extend_schema( + description="Retrieve the contents of a data file or download a file." + ) + def get(self, request, data_id, version=None): + data = get_object_or_404(DataFile, id=data_id) + if "download" in request.GET and request.GET["download"]: + if not permissions.check_permissions(request, data): + if not request.user.is_authenticated: + return HttpResponse("Must be authenticated to download", status=401) + return HttpResponseForbidden("data is private") + try: + file = open(data.file.path, "rb") + except Exception as e: + return HttpResponseBadRequest(str(e)) + if file is None: + raise Http404("File not found.") + return FileResponse(file, as_attachment=True) + else: + loader = Loader() + if not permissions.check_permissions(request, data): + if not request.user.is_authenticated: + return HttpResponse("Must be authenticated to view", status=401) + return HttpResponseForbidden( + "Data is either not public or wrong auth token" + ) + data_list = loader.load(data.file.path) + contents = [str(data) for data in data_list] + return_data = {data.file_name: contents} + return Response(return_data) + + # Modify a datafile + @extend_schema(description="Make changes to a data file that you own.") + def put(self, request, data_id, version=None): + db = get_object_or_404(DataFile, id=data_id) + if not permissions.check_permissions(request, db): + if not request.user.is_authenticated: + return HttpResponse("must be authenticated to modify", status=401) + return HttpResponseForbidden("must be the data owner to modify") + form = DataFileForm(request.data, request.FILES, instance=db) + if form.is_valid(): + form.save() + serializer = DataFileSerializer( + db, + data={ + "file_name": os.path.basename(form.instance.file.path), + "current_user": request.user.id, + }, + context={"is_public": db.is_public}, + partial=True, + ) + if serializer.is_valid(raise_exception=True): + serializer.save() + return_data = { + "current_user": request.user.username, + "authenticated": request.user.is_authenticated, + "file_id": db.id, + "file_alternative_name": serializer.data["file_name"], + "is_public": serializer.data["is_public"], + } + return Response(return_data) + + # Delete a datafile + @extend_schema(description="Delete a data file that you own.") + def delete(self, request, data_id, version=None): + db = get_object_or_404(DataFile, id=data_id) + if not permissions.is_owner(request, db): + if not request.user.is_authenticated: + return HttpResponse("Must be authenticated to delete", status=401) + return HttpResponseForbidden("Must be the data owner to delete") + db.delete() + return Response(data={"success": True}) + + +class DataFileUsersView(APIView): + """ + View for the users that have access to a datafile. + + Functionality for accessing a list of users with access and granting or + revoking access. + """ + + # View users with access to a datafile + @extend_schema( + description="Retrieve a list of users that have been granted access to" + " a data file and the file's publicity status." + ) + def get(self, request, data_id, version=None): + db = get_object_or_404(DataFile, id=data_id) + if not permissions.is_owner(request, db): + if not request.user.is_authenticated: + return HttpResponse( + "Must be authenticated to manage access", status=401 + ) + return HttpResponseForbidden("Must be the data owner to manage access") + response_data = { + "file": db.pk, + "file_name": db.file_name, + "is_public": db.is_public, + "users": [user.username for user in db.users.all()], + } + return Response(response_data) + + # Grant or revoke access to a datafile + @extend_schema(description="Grant or revoke a user's access to a data file.") + def put(self, request, data_id, version=None): + db = get_object_or_404(DataFile, id=data_id) + if not permissions.is_owner(request, db): + if not request.user.is_authenticated: + return HttpResponse( + "Must be authenticated to manage access", status=401 + ) + return HttpResponseForbidden("Must be the data owner to manage access") + serializer = AccessManagementSerializer(data=request.data) + serializer.is_valid() + user = get_object_or_404(User, username=serializer.data["username"]) + if serializer.data["access"]: + db.users.add(user) + else: + db.users.remove(user) + response_data = { + "username": user.username, + "file": db.pk, + "file_name": db.file_name, + "access": (serializer.data["access"] or user == db.current_user), + } + return Response(response_data) + + +class DataSetView(APIView): + """ + View associated with the DataSet model. + + Functionality for viewing a list of datasets and creating a dataset. + """ + + permission_classes = [DataPermission] + + # get a list of accessible datasets + @extend_schema(description="Retrieve a list of accessible datasets by id and name.") + def get(self, request, version=None): + data_list = {"dataset_ids": {}} + data = DataSet.objects.all() + if "username" in request.GET: + user = get_object_or_404(User, username=request.GET["username"]) + data = DataSet.objects.filter(current_user=user) + for dataset in data: + if permissions.check_permissions(request, dataset): + data_list["dataset_ids"][dataset.id] = dataset.name + return Response(data=data_list) + + # TODO: enable uploading files as part of dataset creation, not just associating dataset with existing files + # create a dataset + @extend_schema(description="Upload a dataset.") + def post(self, request, version=None): + # TODO: revisit request data format + if isinstance(request.data, str): + serializer = DataSetSerializer( + data=json.loads(request.data), context={"request": request} + ) + else: + serializer = DataSetSerializer( + data=request.data, context={"request": request} + ) + if serializer.is_valid(raise_exception=True): + serializer.save() + db = serializer.instance + response = { + "dataset_id": db.id, + "name": db.name, + "authenticated": request.user.is_authenticated, + "current_user": request.user.username, + "is_public": db.is_public, + } + return Response(data=response, status=status.HTTP_201_CREATED) + + # create a dataset + @extend_schema(description="Upload a dataset.") + def put(self, request, version=None): + return self.post(request, version) + + +class SingleDataSetView(APIView): + """ + View associated with single datasets. + + Functionality for accessing a dataset in a format intended to be loaded + into SasView, modifying a dataset, or deleting a dataset. + """ + + permission_classes = [DataPermission] + + # get a specific dataset + @extend_schema(description="Retrieve a dataset.") + def get(self, request, data_id, version=None): + db = get_object_or_404(DataSet, id=data_id) + if not permissions.check_permissions(request, db): + if not request.user.is_authenticated: + return HttpResponse("Must be authenticated to view dataset", status=401) + return HttpResponseForbidden( + "You do not have permission to view this dataset." + ) + serializer = DataSetSerializer(db, context={"request": request}) + response_data = serializer.data + if db.current_user: + response_data["current_user"] = db.current_user.username + return Response(response_data) + + # edit a specific dataset + @extend_schema(description="Make changes to a dataset that you own.") + def put(self, request, data_id, version=None): + db = get_object_or_404(DataSet, id=data_id) + if not permissions.check_permissions(request, db): + if not request.user.is_authenticated: + return HttpResponse( + "Must be authenticated to modify dataset", status=401 + ) + return HttpResponseForbidden("Cannot modify a dataset you do not own") + serializer = DataSetSerializer( + db, request.data, context={"request": request}, partial=True + ) + clear_files = "files" in request.data and not request.data["files"] + if clear_files: + data_copy = request.data.copy() + data_copy.pop("files") + serializer = DataSetSerializer( + db, data_copy, context={"request": request}, partial=True + ) + if serializer.is_valid(raise_exception=True): + serializer.save() + if clear_files: + db.files.clear() + db.save() + data = {"data_id": db.id, "name": db.name, "is_public": db.is_public} + return Response(data) + + # delete a dataset + @extend_schema(description="Delete a dataset that you own.") + def delete(self, request, data_id, version=None): + db = get_object_or_404(DataSet, id=data_id) + if not permissions.check_permissions(request, db): + if not request.user.is_authenticated: + return HttpResponse( + "Must be authenticated to delete a dataset", status=401 + ) + return HttpResponseForbidden("Not authorized to delete") + db.delete() + return Response({"success": True}) + + +class DataSetUsersView(APIView): + """ + View for the users that have access to a dataset. + + Functionality for accessing a list of users with access and granting or + revoking access. + """ + + permission_classes = [DataPermission] + + # get a list of users with access to dataset data_id + @extend_schema( + description="Retrieve a list of users that have been granted access to" + " a dataset and the dataset's publicity status." + ) + def get(self, request, data_id, version=None): + db = get_object_or_404(DataSet, id=data_id) + if not permissions.is_owner(request, db): + if not request.user.is_authenticated: + return HttpResponse("Must be authenticated to view access", status=401) + return HttpResponseForbidden("Must be the dataset owner to view access") + response_data = { + "data_id": db.id, + "name": db.name, + "is_public": db.is_public, + "users": [user.username for user in db.users.all()], + } + return Response(response_data) + + # grant or revoke a user's access to dataset data_id + @extend_schema(description="Grant or revoke a user's access to a dataset.") + def put(self, request, data_id, version=None): + db = get_object_or_404(DataSet, id=data_id) + if not permissions.is_owner(request, db): + if not request.user.is_authenticated: + return HttpResponse( + "Must be authenticated to manage access", status=401 + ) + return HttpResponseForbidden("Must be the dataset owner to manage access") + serializer = AccessManagementSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + user = get_object_or_404(User, username=serializer.data["username"]) + if serializer.data["access"]: + db.users.add(user) + else: + db.users.remove(user) + response_data = { + "username": user.username, + "data_id": db.id, + "name": db.name, + "access": serializer.data["access"], + } + return Response(response_data) + + +class SessionView(APIView): + """ + View associated with the Session model. + + Functionality for viewing a list of sessions and for creating a session. + """ + + # View a list of accessible sessions + @extend_schema( + description="Retrieve a list of accessible sessions by name and title." + ) + def get(self, request, version=None): + session_list = {"session_ids": {}} + sessions = Session.objects.all() + if "username" in request.GET: + user = get_object_or_404(User, username=request.GET["username"]) + sessions = Session.objects.filter(current_user=user) + for session in sessions: + if permissions.check_permissions(request, session): + session_list["session_ids"][session.id] = session.title + return Response(data=session_list) + + # Create a session + # TODO: revisit response data + @extend_schema(description="Upload a session.") + def post(self, request, version=None): + if isinstance(request.data, str): + serializer = SessionSerializer( + data=json.loads(request.data), context={"request": request} + ) + else: + serializer = SessionSerializer( + data=request.data, context={"request": request} + ) + if serializer.is_valid(raise_exception=True): + serializer.save() + db = serializer.instance + response = { + "session_id": db.id, + "title": db.title, + "authenticated": request.user.is_authenticated, + "current_user": request.user.username, + "is_public": db.is_public, + } + return Response(data=response, status=status.HTTP_201_CREATED) + + # Create a session + @extend_schema(description="Upload a session.") + def put(self, request, version=None): + return self.post(request, version) + + +class SingleSessionView(APIView): + """ + View associated with single sessions. + + Functionality for viewing, modifying, and deleting individual sessions. + """ + + # get a specific session + @extend_schema(description="Retrieve a session.") + def get(self, request, data_id, version=None): + db = get_object_or_404(Session, id=data_id) + if not permissions.check_permissions(request, db): + if not request.user.is_authenticated: + return HttpResponse("Must be authenticated to view session", status=401) + return HttpResponseForbidden( + "You do not have permission to view this session." + ) + serializer = SessionSerializer(db) + response_data = serializer.data + if db.current_user: + response_data["current_user"] = db.current_user.username + return Response(response_data) + + # modify a session + @extend_schema(description="Make changes to a session that you own.") + def put(self, request, data_id, version=None): + db = get_object_or_404(Session, id=data_id) + if not permissions.check_permissions(request, db): + if not request.user.is_authenticated: + return HttpResponse( + "Must be authenticated to modify session", status=401 + ) + return HttpResponseForbidden("Cannot modify a session you do not own") + serializer = SessionSerializer( + db, request.data, context={"request": request}, partial=True + ) + if serializer.is_valid(raise_exception=True): + serializer.save() + data = {"session_id": db.id, "title": db.title, "is_public": db.is_public} + return Response(data) + + # delete a session + @extend_schema(description="Delete a session that you own.") + def delete(self, request, data_id, version=None): + db = get_object_or_404(Session, id=data_id) + if not permissions.check_permissions(request, db): + if not request.user.is_authenticated: + return HttpResponse( + "Must be authenticated to delete a session", status=401 + ) + return HttpResponseForbidden("Not authorized to delete") + db.delete() + return Response({"success": True}) + + +class SessionUsersView(APIView): + """ + View for the users that have access to a session. + + Functionality for accessing a list of users with access and granting or + revoking access. + """ + + # view the users that have access to a specific session + @extend_schema( + description="Retrieve a list of users that have been granted access to" + " a session and the session's publicity status." + ) + def get(self, request, data_id, version=None): + db = get_object_or_404(Session, id=data_id) + if not permissions.is_owner(request, db): + if not request.user.is_authenticated: + return HttpResponse("Must be authenticated to view access", status=401) + return HttpResponseForbidden("Must be the session owner to view access") + response_data = { + "session_id": db.id, + "title": db.title, + "is_public": db.is_public, + "users": [user.username for user in db.users.all()], + } + return Response(response_data) + + # grant or revoke access to a session + @extend_schema(description="Grant or revoke a user's access to a data file.") + def put(self, request, data_id, version=None): + db = get_object_or_404(Session, id=data_id) + if not permissions.is_owner(request, db): + if not request.user.is_authenticated: + return HttpResponse( + "Must be authenticated to manage access", status=401 + ) + return HttpResponseForbidden("Must be the dataset owner to manage access") + serializer = AccessManagementSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + user = get_object_or_404(User, username=serializer.data["username"]) + if serializer.data["access"]: + db.users.add(user) + for dataset in db.datasets.all(): + dataset.users.add(user) + else: + db.users.remove(user) + for dataset in db.datasets.all(): + dataset.users.remove(user) + response_data = { + "username": user.username, + "session_id": db.id, + "title": db.title, + "access": serializer.data["access"], + } + return Response(response_data) + + +class PublishedStateView(APIView): + """ + View associated with the PublishedState model. + + Functionality for viewing a list of session published states and for + creating a published state. + """ + + # View a list of accessible sessions' published states + @extend_schema( + description="Retrieve a list of published states of accessible sessions." + ) + def get(self, request, version=None): + ps_list = {"published_state_ids": {}} + published_states = PublishedState.objects.all() + if "username" in request.GET: + user = get_object_or_404(User, username=request.GET["username"]) + published_states = PublishedState.objects.filter(session__current_user=user) + for ps in published_states: + if permissions.check_permissions(request, ps.session): + ps_list["published_state_ids"][ps.id] = { + "title": ps.session.title, + "published": ps.published, + "doi": ps.doi, + } + return Response(data=ps_list) + + # Create a published state for an existing session + @extend_schema(description="Create a published state for an existing session.") + def post(self, request, version=None): + if isinstance(request.data, str): + serializer = PublishedStateSerializer( + data=json.loads(request.data), context={"request": request} + ) + else: + serializer = PublishedStateSerializer( + data=request.data, context={"request": request} + ) + if serializer.is_valid(raise_exception=True): + if not permissions.is_owner(request, serializer.validated_data["session"]): + if not request.user.is_authenticated: + return HttpResponse( + "Must be authenticated to create a published state for a session", + status=401, + ) + return HttpResponseForbidden( + "Must be the session owner to create a published state for a session" + ) + serializer.save() + db = serializer.instance + response = { + "published_state_id": db.id, + "session_id": db.session.id, + "title": db.session.title, + "doi": db.doi, + "published": db.published, + "current_user": request.user.username, + "is_public": db.session.is_public, + } + return Response(data=response, status=status.HTTP_201_CREATED) + + # Create a published state for an existing session + @extend_schema(description="Create a published state for an existing session.") + def put(self, request, version=None): + return self.post(request, version) + + +class SinglePublishedStateView(APIView): + """ + View associated with specific session published states. + + Functionality for viewing, modifying, and deleting individual published states. + """ + + # View a specific published state + @extend_schema(description="Retrieve a published state.") + def get(self, request, ps_id, version=None): + db = get_object_or_404(PublishedState, id=ps_id) + if not permissions.check_permissions(request, db.session): + if not request.user.is_authenticated: + return HttpResponse( + "Must be authenticated to view published state", status=401 + ) + return HttpResponseForbidden( + "You do not have permission to view this published state." + ) + serializer = PublishedStateSerializer(db) + response_data = serializer.data + response_data["title"] = db.session.title + if db.session.current_user: + response_data["current_user"] = db.session.current_user.username + else: + response_data["current_user"] = "" + response_data["is_public"] = db.session.is_public + return Response(response_data) + + # Modify a published state + @extend_schema( + description="Make changes to the published state of a session that you own." + ) + def put(self, request, ps_id, version=None): + db = get_object_or_404(PublishedState, id=ps_id) + if not permissions.check_permissions(request, db.session): + if not request.user.is_authenticated: + return HttpResponse( + "Must be authenticated to modify published state", status=401 + ) + return HttpResponseForbidden( + "Cannot modify a published state you do not own" + ) + serializer = PublishedStateUpdateSerializer( + db, request.data, context={"request": request}, partial=True + ) + if serializer.is_valid(raise_exception=True): + serializer.save() + data = { + "published_state_id": db.id, + "session_id": db.session.id, + "title": db.session.title, + "published": db.published, + "is_public": db.session.is_public, + } + return Response(data) + + # Delete a published state + @extend_schema(description="Delete the published state of a session that you own.") + def delete(self, request, ps_id, version=None): + db = get_object_or_404(PublishedState, id=ps_id) + if not permissions.check_permissions(request, db.session): + if not request.user.is_authenticated: + return HttpResponse( + "Must be authenticated to delete a published state", status=401 + ) + return HttpResponseForbidden("Not authorized to delete") + db.delete() + return Response({"success": True}) diff --git a/sasdata/fair_database/documentation.yaml b/sasdata/fair_database/documentation.yaml new file mode 100644 index 00000000..22a0488f --- /dev/null +++ b/sasdata/fair_database/documentation.yaml @@ -0,0 +1,1172 @@ +openapi: 3.0.3 +info: + title: SasView Database + version: 0.1.0 + description: A database following the FAIR data principles for SasView, a small + angle scattering analysis application. +paths: + /{version}/data/file/: + get: + operationId: data_file_list + description: Retrieve a list of accessible data files by id and filename. + parameters: + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: "#components/schemas/DataFileList" + post: + operationId: data_file_create + description: Upload a data file. + parameters: + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: "#components/schemas/DataFileCreate" + responses: + '201': + description: CREATED + content: + application/json: + schema: + $ref: "#components/schemas/DataFileCreated" + put: + operationId: data_file_create_2 + description: Upload a data file. + parameters: + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: "components/schemas/DataFileCreate" + responses: + '201': + description: CREATED + content: + application/json: + schema: + $ref: "#components/schemas/DataFileCreated" + /{version}/data/file/{data_id}/: + get: + operationId: data_file_retrieve + description: Retrieve the contents of a data file or download a file. + parameters: + - in: path + name: data_id + schema: + type: integer + required: true + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + requestBlock: + schema: + $ref: "#components/schemas/DataFileGet" + responses: + '200': + description: OK + content: + application/json: + schema: + oneOf: + - $ref: "#components/schemas/DataFile" + - $ref: "components/schemas/DataFileDownload" + put: + operationId: data_file_update_2 + description: Make changes to a data file that you own. + parameters: + - in: path + name: data_id + schema: + type: integer + required: true + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + requestBody: + required: true + content: + multipart/form-data: + schema: + $ref: "components/schemas/DataFileCreate" + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: "#components/schemas/DataFileCreated" + delete: + operationId: data_file_destroy + description: Delete a data file that you own. + parameters: + - in: path + name: data_id + schema: + type: integer + required: true + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: "#components/schemas/Delete" + /{version}/data/file/{data_id}/users/: + get: + operationId: data_file_users_retrieve + description: Retrieve a list of users that have been granted access to a data + file and the file's publicity status. + parameters: + - in: path + name: data_id + schema: + type: integer + required: true + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '200': + description: OK + content: + application/json: + schema: + allOf: + - $ref: "#components/schemas/UsersList" + - type: object + properties: + file: + type: integer + file_name: + type: string + put: + operationId: data_file_users_update + description: Grant or revoke a user's access to a data file. + parameters: + - in: path + name: data_id + schema: + type: integer + required: true + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + requestBody: + required: true + content: + application/json: + schema: + $ref: "#components/schemas/ManageAccess" + responses: + '200': + description: OK + content: + application/json: + schema: + allOf: + - $ref: "#components/schemas/ManageAccess" + - type: object + properties: + file: + type: integer + file_name: + type: string + /{version}/data/published/: + get: + operationId: data_published_retrieve + description: Retrieve a list of published states of accessible sessions. + parameters: + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '200': + description: OK + post: + operationId: data_published_create + description: Create a published state for an existing session. + parameters: + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '201': + description: CREATED + put: + operationId: data_published_update + description: Create a published state for an existing session. + parameters: + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '201': + description: CREATED + /{version}/data/published/{ps_id}/: + get: + operationId: data_published_retrieve_2 + description: Retrieve a published state. + parameters: + - in: path + name: ps_id + schema: + type: integer + required: true + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '200': + description: OK + put: + operationId: data_published_update_2 + description: Make changes to the published state of a session that you own. + parameters: + - in: path + name: ps_id + schema: + type: integer + required: true + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '200': + description: OK + delete: + operationId: data_published_destroy + description: Delete the published state of a session that you own. + parameters: + - in: path + name: ps_id + schema: + type: integer + required: true + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '200': + description: OK + /{version}/data/session/: + get: + operationId: data_session_retrieve + description: Retrieve a list of accessible sessions by name and title. + parameters: + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '200': + description: OK + post: + operationId: data_session_create + description: Upload a session. + parameters: + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '201': + description: CREATED + put: + operationId: data_session_update + description: Upload a session. + parameters: + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '201': + description: CREATED + /{version}/data/session/{data_id}/: + get: + operationId: data_session_retrieve_2 + description: Retrieve a session. + parameters: + - in: path + name: data_id + schema: + type: integer + required: true + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '200': + description: OK + put: + operationId: data_session_update_2 + description: Make changes to a session that you own. + parameters: + - in: path + name: data_id + schema: + type: integer + required: true + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '200': + description: OK + delete: + operationId: data_session_destroy + description: Delete a session that you own. + parameters: + - in: path + name: data_id + schema: + type: integer + required: true + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '200': + description: OK + /{version}/data/session/{data_id}/users/: + get: + operationId: data_session_users_retrieve + description: Retrieve a list of users that have been granted access to a session + and the session's publicity status. + parameters: + - in: path + name: data_id + schema: + type: integer + required: true + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '200': + description: OK + put: + operationId: data_session_users_update + description: Grant or revoke a user's access to a data file. + parameters: + - in: path + name: data_id + schema: + type: integer + required: true + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '200': + description: OK + /{version}/data/set/: + get: + operationId: data_set_retrieve + description: Retrieve a list of accessible datasets by id and name. + parameters: + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + responses: + '200': + description: OK + post: + operationId: data_set_create + description: Upload a dataset. + parameters: + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + responses: + '201': + description: CREATED + put: + operationId: data_set_update + description: Upload a dataset. + parameters: + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + responses: + '201': + description: CREATED + /{version}/data/set/{data_id}/: + get: + operationId: data_set_retrieve_2 + description: Retrieve a dataset. + parameters: + - in: path + name: data_id + schema: + type: integer + required: true + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + responses: + '200': + description: OK + put: + operationId: data_set_update_2 + description: Make changes to a dataset that you own. + parameters: + - in: path + name: data_id + schema: + type: integer + required: true + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + responses: + '200': + description: OK + delete: + operationId: data_set_destroy + description: Delete a dataset that you own. + parameters: + - in: path + name: data_id + schema: + type: integer + required: true + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + responses: + '200': + description: OK + /{version}/data/set/{data_id}/users/: + get: + operationId: data_set_users_retrieve + description: Retrieve a list of users that have been granted access to a dataset + and the dataset's publicity status. + parameters: + - in: path + name: data_id + schema: + type: integer + required: true + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + responses: + '200': + description: OK + put: + operationId: data_set_users_update + description: Grant or revoke a user's access to a dataset. + parameters: + - in: path + name: data_id + schema: + type: integer + required: true + - in: path + name: version + schema: + type: string + pattern: ^(v1)$ + required: true + tags: + - data + security: + - knoxApiToken: [] + - cookieAuth: [] + responses: + '200': + description: OK + /auth/login/: + post: + operationId: auth_login_create + description: |- + Check the credentials and return the REST Token + if the credentials are valid and authenticated. + Calls Django Auth login method to register User ID + in Django session framework + + Accept the following POST parameters: username, password + Return the REST Framework Token Object's key. + tags: + - auth + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Login' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Login' + multipart/form-data: + schema: + $ref: '#/components/schemas/Login' + required: true + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/Login' + description: '' + /auth/logout/: + post: + operationId: auth_logout_create + description: |- + Calls Django logout method and delete the Token object + assigned to the current User object. + + Accepts/Returns nothing. + tags: + - auth + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RestAuthDetail' + description: '' + /auth/password/change/: + post: + operationId: auth_password_change_create + description: |- + Calls Django Auth SetPasswordForm save method. + + Accepts the following POST parameters: new_password1, new_password2 + Returns the success/fail message. + tags: + - auth + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PasswordChange' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PasswordChange' + multipart/form-data: + schema: + $ref: '#/components/schemas/PasswordChange' + required: true + security: + - knoxApiToken: [] + - cookieAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/RestAuthDetail' + description: '' + /auth/register/: + post: + operationId: auth_register_create + description: |- + Registers a new user. + + Accepts the following POST parameters: username, email, password1, password2. + tags: + - auth + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Register' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Register' + multipart/form-data: + schema: + $ref: '#/components/schemas/Register' + required: true + security: + - knoxApiToken: [] + - cookieAuth: [] + - {} + responses: + '201': + content: + application/json: + schema: + $ref: '#/components/schemas/Register' + description: '' + /auth/user/: + get: + operationId: auth_user_retrieve + description: |- + Reads and updates UserModel fields + Accepts GET, PUT, PATCH methods. + + Default accepted fields: username, first_name, last_name + Default display fields: pk, username, email, first_name, last_name + Read-only fields: pk, email + + Returns UserModel fields. + tags: + - auth + security: + - knoxApiToken: [] + - cookieAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDetails' + description: '' + put: + operationId: auth_user_update + description: |- + Reads and updates UserModel fields + Accepts GET, PUT, PATCH methods. + + Default accepted fields: username, first_name, last_name + Default display fields: pk, username, email, first_name, last_name + Read-only fields: pk, email + + Returns UserModel fields. + tags: + - auth + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserDetails' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserDetails' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserDetails' + required: true + security: + - knoxApiToken: [] + - cookieAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDetails' + description: '' + patch: + operationId: auth_user_partial_update + description: |- + Reads and updates UserModel fields + Accepts GET, PUT, PATCH methods. + + Default accepted fields: username, first_name, last_name + Default display fields: pk, username, email, first_name, last_name + Read-only fields: pk, email + + Returns UserModel fields. + tags: + - auth + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedUserDetails' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedUserDetails' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedUserDetails' + security: + - knoxApiToken: [] + - cookieAuth: [] + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/UserDetails' + description: '' +components: + schemas: + Delete: + type: object + properties: + success: + type: boolean + UsersList: + type: object + properties: + is_public: + type: boolean + users: + type: array + items: + type: string + ManageAccess: + type: object + properties: + username: + type: string + access: + type: boolean + DataFileList: + type: object + properties: + data_ids: + type: object + additionalProperties: + filename: + type: string + DataFileCreate: + type: object + properties: + filename: + type: string + file: + type: string + format: binary + DataFileCreated: + type: object + properties: + current_user: + type: string + authenticated: + type: boolean + file_id: + type: integer + file_alternative_name: + type: string + is_public: + type: boolean + DataFileGet: + type: object + properties: + download: + type: boolean + DataFile: + type: object + properties: + filename: + type: object + additionalProperties: + type: array + items: + type: string + DataFileDownload: + type: string + format: binary + Login: + type: object + properties: + username: + type: string + email: + type: string + format: email + password: + type: string + required: + - password + DataSetList: + type: object + properties: + dataset_ids: + type: object + additionalProperties: + name: + type: string + DataSetCreate: + type: object + properties: + name: + type: string + is_public: + type: boolean + data_contents: + type: array + items: + type: object + properties: + label: + type: string + value: + type: object + additionalProperties: true + PasswordChange: + type: object + properties: + new_password1: + type: string + maxLength: 128 + new_password2: + type: string + maxLength: 128 + required: + - new_password1 + - new_password2 + PatchedUserDetails: + type: object + description: User model w/o password + properties: + pk: + type: integer + readOnly: true + title: ID + username: + type: string + description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_ + only. + pattern: ^[\w.@+-]+$ + maxLength: 150 + email: + type: string + format: email + readOnly: true + title: Email address + first_name: + type: string + maxLength: 150 + last_name: + type: string + maxLength: 150 + Register: + type: object + properties: + username: + type: string + maxLength: 150 + minLength: 1 + email: + type: string + format: email + password1: + type: string + writeOnly: true + password2: + type: string + writeOnly: true + required: + - password1 + - password2 + - username + RestAuthDetail: + type: object + properties: + detail: + type: string + readOnly: true + required: + - detail + UserDetails: + type: object + description: User model w/o password + properties: + pk: + type: integer + readOnly: true + title: ID + username: + type: string + description: Required. 150 characters or fewer. Letters, digits and @/./+/-/_ + only. + pattern: ^[\w.@+-]+$ + maxLength: 150 + email: + type: string + format: email + readOnly: true + title: Email address + first_name: + type: string + maxLength: 150 + last_name: + type: string + maxLength: 150 + required: + - email + - pk + - username + securitySchemes: + cookieAuth: + type: apiKey + in: cookie + name: sessionid + knoxApiToken: + type: apiKey + in: header + name: Authorization + description: Token-based authentication with required prefix "Token" diff --git a/sasdata/fair_database/fair_database/__init__.py b/sasdata/fair_database/fair_database/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sasdata/fair_database/fair_database/asgi.py b/sasdata/fair_database/fair_database/asgi.py new file mode 100644 index 00000000..a10c9b21 --- /dev/null +++ b/sasdata/fair_database/fair_database/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for fair_database project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fair_database.settings") + +application = get_asgi_application() diff --git a/sasdata/fair_database/fair_database/create_example_session.py b/sasdata/fair_database/fair_database/create_example_session.py new file mode 100644 index 00000000..6c10d990 --- /dev/null +++ b/sasdata/fair_database/fair_database/create_example_session.py @@ -0,0 +1,97 @@ +import requests + +session = { + "title": "Example Session", + "datasets": [ + { + "name": "Dataset 1", + "metadata": { + "title": "Metadata 1", + "run": 1, + "description": "test", + "instrument": {}, + "process": {}, + "sample": {}, + }, + "data_contents": [ + { + "value": 0, + "variance": 0, + "units": "no", + "hash": 0, + "label": "Quantity 1", + "history": {"operation_tree": {}, "references": []}, + } + ], + }, + { + "name": "Dataset 2", + "metadata": { + "title": "Metadata 2", + "run": 2, + "description": "test", + "instrument": {}, + "process": {}, + "sample": {}, + }, + "data_contents": [ + { + "label": "Quantity 2", + "value": {"array_contents": [0, 0, 0, 0], "shape": (2, 2)}, + "variance": {"array_contents": [0, 0, 0, 0], "shape": (2, 2)}, + "units": "none", + "hash": 0, + "history": { + "operation_tree": { + "operation": "neg", + "parameters": { + "a": { + "operation": "mul", + "parameters": { + "a": { + "operation": "constant", + "parameters": { + "value": {"type": "int", "value": 7} + }, + }, + "b": { + "operation": "variable", + "parameters": { + "hash_value": 111, + "name": "x", + }, + }, + }, + }, + }, + }, + "references": [ + { + "value": 5, + "variance": 0, + "units": "none", + "hash": 111, + "history": {}, + } + ], + }, + } + ], + }, + ], + "is_public": False, +} + +url = "http://127.0.0.1:8000/v1/data/session/" +login_data = {"email": "test@test.org", "username": "testUser", "password": "sasview!"} +response = requests.post("http://127.0.0.1:8000/auth/login/", data=login_data) +if response.status_code != 200: + register_data = { + "email": "test@test.org", + "username": "testUser", + "password1": "sasview!", + "password2": "sasview!", + } + response = requests.post("http://127.0.0.1:8000/auth/register/", data=register_data) +token = response.json()["token"] +requests.request("POST", url, json=session, headers={"Authorization": "Token " + token}) diff --git a/sasdata/fair_database/fair_database/permissions.py b/sasdata/fair_database/fair_database/permissions.py new file mode 100644 index 00000000..74be5f33 --- /dev/null +++ b/sasdata/fair_database/fair_database/permissions.py @@ -0,0 +1,29 @@ +from rest_framework.permissions import BasePermission + + +# check if a request is made by an object's owner +def is_owner(request, obj): + return request.user.is_authenticated and request.user == obj.current_user + + +# check if a request is made by a user with read access +def has_access(request, obj): + return is_owner(request, obj) or ( + request.user.is_authenticated and request.user in obj.users.all() + ) + + +class DataPermission(BasePermission): + # check if a request has the correct permissions for a specific object + def has_object_permission(self, request, view, obj): + if request.method == "GET": + return obj.is_public or has_access(request, obj) + elif request.method == "DELETE": + return not obj.is_public and is_owner(request, obj) + else: + return is_owner(request, obj) + + +# check if a request has the correct permissions for a specific object +def check_permissions(request, obj): + return DataPermission().has_object_permission(request, None, obj) diff --git a/sasdata/fair_database/fair_database/settings.py b/sasdata/fair_database/fair_database/settings.py new file mode 100644 index 00000000..f3ec69ed --- /dev/null +++ b/sasdata/fair_database/fair_database/settings.py @@ -0,0 +1,202 @@ +""" +Django settings for fair_database project. + +Generated by 'django-admin startproject' using Django 5.1.5. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.1/ref/settings/ +""" + +import os +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "django-insecure--f-t5!pdhq&4)^&xenr^k0e8n%-h06jx9d0&2kft(!+1$xzig)" + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + "data.apps.DataConfig", + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.sites", + "rest_framework", + "rest_framework.authtoken", + "allauth", + "allauth.account", + "allauth.socialaccount", + "allauth.socialaccount.providers.orcid", + "dj_rest_auth", + "dj_rest_auth.registration", + "knox", + "user_app.apps.UserAppConfig", + "drf_spectacular", +] + +SITE_ID = 1 + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "allauth.account.middleware.AccountMiddleware", +] + +ROOT_URLCONF = "fair_database.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "fair_database.wsgi.application" + +# Authentication +AUTHENTICATION_BACKENDS = ( + "django.contrib.auth.backends.ModelBackend", + "allauth.account.auth_backends.AuthenticationBackend", +) + +REST_FRAMEWORK = { + "DEFAULT_AUTHENTICATION_CLASSES": [ + "knox.auth.TokenAuthentication", + "rest_framework.authentication.SessionAuthentication", + ], + "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", +} + +REST_AUTH = { + "TOKEN_SERIALIZER": "user_app.serializers.KnoxSerializer", + "USER_DETAILS_SERIALIZER": "dj_rest_auth.serializers.UserDetailsSerializer", + "TOKEN_MODEL": "knox.models.AuthToken", + "TOKEN_CREATOR": "user_app.util.create_knox_token", +} + +SPECTACULAR_SETTINGS = { + "TITLE": "SasView Database", + "DESCRIPTION": "A database following the FAIR data principles for SasView," + " a small angle scattering analysis application.", + "VERSION": "0.1.0", + "SERVE_INCLUDE_SCHEMA": False, +} + +# allauth settings +HEADLESS_ONLY = True +ACCOUNT_EMAIL_VERIFICATION = "none" + +# to enable ORCID, register for credentials through ORCID and fill out client_id and secret +# https://info.orcid.org/documentation/integration-guide/ +# https://docs.allauth.org/en/latest/socialaccount/index.html +SOCIALACCOUNT_PROVIDERS = { + "orcid": { + "APPS": [ + { + "client_id": "", + "secret": "", + "key": "", + } + ], + "SCOPE": [ + "profile", + "email", + ], + "AUTH_PARAMETERS": {"access_type": "online"}, + # Base domain of the API. Default value: 'orcid.org', for the production API + "BASE_DOMAIN": "sandbox.orcid.org", # for the sandbox API + # Member API or Public API? Default: False (for the public API) + "MEMBER_API": False, + } +} + +# Database +# https://docs.djangoproject.com/en/5.1/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.1/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/4.2/howto/static-files/ + + +STATIC_ROOT = os.path.join(BASE_DIR, "static") +STATIC_URL = "/static/" + +# instead of doing this, create a create a new media_root +MEDIA_ROOT = os.path.join(BASE_DIR, "media") +MEDIA_URL = "/media/" + +# Default primary key field type +# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/sasdata/fair_database/fair_database/test_permissions.py b/sasdata/fair_database/fair_database/test_permissions.py new file mode 100644 index 00000000..df76d1a6 --- /dev/null +++ b/sasdata/fair_database/fair_database/test_permissions.py @@ -0,0 +1,293 @@ +import os +import shutil + +from django.conf import settings +from django.contrib.auth.models import User +from rest_framework import status +from rest_framework.test import APITestCase + +from data.models import DataFile + + +def find(filename): + return os.path.join( + os.path.dirname(__file__), "../../example_data/1d_data", filename + ) + + +def auth_header(response): + return {"Authorization": "Token " + response.data["token"]} + + +class DataListPermissionsTests(APITestCase): + """Test permissions of data views using user_app for authentication.""" + + @classmethod + def setUpTestData(cls): + cls.user = User.objects.create_user( + username="testUser", password="secret", id=1, email="email@domain.com" + ) + cls.user2 = User.objects.create_user( + username="testUser2", password="secret", id=2, email="email2@domain.com" + ) + cls.unowned_test_data = DataFile.objects.create( + id=1, file_name="cyl_400_40.txt", is_public=True + ) + cls.unowned_test_data.file.save( + "cyl_400_40.txt", open(find("cyl_400_40.txt"), "rb") + ) + cls.private_test_data = DataFile.objects.create( + id=2, current_user=cls.user, file_name="cyl_400_20.txt", is_public=False + ) + cls.private_test_data.file.save( + "cyl_400_20.txt", open(find("cyl_400_20.txt"), "rb") + ) + cls.public_test_data = DataFile.objects.create( + id=3, current_user=cls.user, file_name="cyl_testdata.txt", is_public=True + ) + cls.public_test_data.file.save( + "cyl_testdata.txt", open(find("cyl_testdata.txt"), "rb") + ) + cls.login_data_1 = { + "username": "testUser", + "password": "secret", + "email": "email@domain.com", + } + cls.login_data_2 = { + "username": "testUser2", + "password": "secret", + "email": "email2@domain.com", + } + + # Authenticated user can view list of data + def test_list_authenticated(self): + token = self.client.post("/auth/login/", data=self.login_data_1) + response = self.client.get("/v1/data/file/", headers=auth_header(token)) + response2 = self.client.get( + "/v1/data/file/", data={"username": "testUser"}, headers=auth_header(token) + ) + self.assertEqual( + response.data, + { + "public_data_ids": { + 1: "cyl_400_40.txt", + 2: "cyl_400_20.txt", + 3: "cyl_testdata.txt", + } + }, + ) + self.assertEqual( + response2.data, + {"user_data_ids": {2: "cyl_400_20.txt", 3: "cyl_testdata.txt"}}, + ) + + # Authenticated user cannot view other users' private data on list + def test_list_authenticated_2(self): + token = self.client.post("/auth/login/", data=self.login_data_2) + response = self.client.get("/v1/data/file/", headers=auth_header(token)) + response2 = self.client.get( + "/v1/data/file/", data={"username": "testUser"}, headers=auth_header(token) + ) + response3 = self.client.get( + "/v1/data/file/", data={"username": "testUser2"}, headers=auth_header(token) + ) + self.assertEqual( + response.data, + {"public_data_ids": {1: "cyl_400_40.txt", 3: "cyl_testdata.txt"}}, + ) + self.assertEqual(response2.status_code, status.HTTP_200_OK) + self.assertEqual(response2.data, {"user_data_ids": {3: "cyl_testdata.txt"}}) + self.assertEqual(response3.data, {"user_data_ids": {}}) + + # Unauthenticated user can view list of public data + def test_list_unauthenticated(self): + response = self.client.get("/v1/data/file/") + response2 = self.client.get("/v1/data/file/", data={"username": "testUser"}) + self.assertEqual( + response.data, + {"public_data_ids": {1: "cyl_400_40.txt", 3: "cyl_testdata.txt"}}, + ) + self.assertEqual(response2.status_code, status.HTTP_200_OK) + self.assertEqual(response2.data, {"user_data_ids": {3: "cyl_testdata.txt"}}) + + # Authenticated user can load public data and owned private data + def test_load_authenticated(self): + token = self.client.post("/auth/login/", data=self.login_data_1) + response = self.client.get("/v1/data/file/1/", headers=auth_header(token)) + response2 = self.client.get("/v1/data/file/2/", headers=auth_header(token)) + response3 = self.client.get("/v1/data/file/3/", headers=auth_header(token)) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response2.status_code, status.HTTP_200_OK) + self.assertEqual(response3.status_code, status.HTTP_200_OK) + + # Authenticated user cannot load others' private data + def test_load_unauthorized(self): + token = self.client.post("/auth/login/", data=self.login_data_2) + response = self.client.get("/v1/data/file/2/", headers=auth_header(token)) + response2 = self.client.get("/v1/data/file/3/", headers=auth_header(token)) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response2.status_code, status.HTTP_200_OK) + + # Unauthenticated user can load public data only + def test_load_unauthenticated(self): + response = self.client.get("/v1/data/file/1/") + response2 = self.client.get("/v1/data/file/2/") + response3 = self.client.get("/v1/data/file/3/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response2.status_code, status.HTTP_401_UNAUTHORIZED) + self.assertEqual(response3.status_code, status.HTTP_200_OK) + + # Authenticated user can upload data + def test_upload_authenticated(self): + token = self.client.post("/auth/login/", data=self.login_data_1) + file = open(find("cyl_testdata1.txt"), "rb") + data = {"file": file, "is_public": False} + response = self.client.post( + "/v1/data/file/", data=data, headers=auth_header(token) + ) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual( + response.data, + { + "current_user": "testUser", + "authenticated": True, + "file_id": 4, + "file_alternative_name": "cyl_testdata1.txt", + "is_public": False, + }, + ) + DataFile.objects.get(id=4).delete() + + # Unauthenticated user can upload public data only + def test_upload_unauthenticated(self): + file = open(find("cyl_testdata2.txt"), "rb") + file2 = open(find("cyl_testdata2.txt"), "rb") + data = {"file": file, "is_public": True} + data2 = {"file": file2, "is_public": False} + response = self.client.post("/v1/data/file/", data=data) + response2 = self.client.post("/v1/data/file/", data=data2) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual( + response.data, + { + "current_user": "", + "authenticated": False, + "file_id": 4, + "file_alternative_name": "cyl_testdata2.txt", + "is_public": True, + }, + ) + self.assertEqual(response2.status_code, status.HTTP_400_BAD_REQUEST) + + # Authenticated user can update own data + def test_upload_put_authenticated(self): + token = self.client.post("/auth/login/", data=self.login_data_1) + data = {"is_public": False} + response = self.client.put( + "/v1/data/file/2/", data=data, headers=auth_header(token) + ) + response2 = self.client.put( + "/v1/data/file/3/", data=data, headers=auth_header(token) + ) + self.assertEqual( + response.data, + { + "current_user": "testUser", + "authenticated": True, + "file_id": 2, + "file_alternative_name": "cyl_400_20.txt", + "is_public": False, + }, + ) + self.assertEqual( + response2.data, + { + "current_user": "testUser", + "authenticated": True, + "file_id": 3, + "file_alternative_name": "cyl_testdata.txt", + "is_public": False, + }, + ) + DataFile.objects.get(id=3).is_public = True + + # Authenticated user cannot update unowned data + def test_upload_put_unauthorized(self): + token = self.client.post("/auth/login/", data=self.login_data_2) + file = open(find("cyl_400_40.txt")) + data = {"file": file, "is_public": False} + response = self.client.put( + "/v1/data/file/1/", data=data, headers=auth_header(token) + ) + response2 = self.client.put( + "/v1/data/file/2/", data=data, headers=auth_header(token) + ) + response3 = self.client.put( + "/v1/data/file/3/", data=data, headers=auth_header(token) + ) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response2.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response3.status_code, status.HTTP_403_FORBIDDEN) + + # Unauthenticated user cannot update data + def test_upload_put_unauthenticated(self): + file = open(find("cyl_400_40.txt")) + data = {"file": file, "is_public": False} + response = self.client.put("/v1/data/file/1/", data=data) + response2 = self.client.put("/v1/data/file/2/", data=data) + response3 = self.client.put("/v1/data/file/3/", data=data) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + self.assertEqual(response2.status_code, status.HTTP_401_UNAUTHORIZED) + self.assertEqual(response3.status_code, status.HTTP_401_UNAUTHORIZED) + + # Authenticated user can download public and own data + def test_download_authenticated(self): + token = self.client.post("/auth/login/", data=self.login_data_1) + response = self.client.get( + "/v1/data/file/1/", data={"download": True}, headers=auth_header(token) + ) + response2 = self.client.get( + "/v1/data/file/2/", data={"download": True}, headers=auth_header(token) + ) + response3 = self.client.get( + "/v1/data/file/3/", data={"download": True}, headers=auth_header(token) + ) + b"".join(response.streaming_content) + b"".join(response2.streaming_content) + b"".join(response3.streaming_content) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response2.status_code, status.HTTP_200_OK) + self.assertEqual(response3.status_code, status.HTTP_200_OK) + + # Authenticated user cannot download others' data + def test_download_unauthorized(self): + token = self.client.post("/auth/login/", data=self.login_data_2) + response = self.client.get( + "/v1/data/file/2/", data={"download": True}, headers=auth_header(token) + ) + response2 = self.client.get( + "/v1/data/file/3/", data={"download": True}, headers=auth_header(token) + ) + b"".join(response2.streaming_content) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(response2.status_code, status.HTTP_200_OK) + + # Unauthenticated user cannot download private data + def test_download_unauthenticated(self): + response = self.client.get("/v1/data/file/1/", data={"download": True}) + response2 = self.client.get("/v1/data/file/2/", data={"download": True}) + response3 = self.client.get("/v1/data/file/3/", data={"download": True}) + b"".join(response.streaming_content) + b"".join(response3.streaming_content) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response2.status_code, status.HTTP_401_UNAUTHORIZED) + self.assertEqual(response3.status_code, status.HTTP_200_OK) + + @classmethod + def tearDownClass(cls): + cls.user.delete() + cls.user2.delete() + cls.public_test_data.delete() + cls.private_test_data.delete() + cls.unowned_test_data.delete() + shutil.rmtree(settings.MEDIA_ROOT) diff --git a/sasdata/fair_database/fair_database/upload_example_data.py b/sasdata/fair_database/fair_database/upload_example_data.py new file mode 100644 index 00000000..60e21af4 --- /dev/null +++ b/sasdata/fair_database/fair_database/upload_example_data.py @@ -0,0 +1,46 @@ +import os +import logging +import requests + +from glob import glob + +EXAMPLE_DATA_DIR = os.environ.get("EXAMPLE_DATA_DIR", "../../example_data") + + +def parse_1D(): + dir_1d = os.path.join(EXAMPLE_DATA_DIR, "1d_data") + if not os.path.isdir(dir_1d): + logging.error("1D Data directory not found at: {}".format(dir_1d)) + return + for file_path in glob(os.path.join(dir_1d, "*")): + upload_file(file_path) + + +def parse_2D(): + dir_2d = os.path.join(EXAMPLE_DATA_DIR, "2d_data") + if not os.path.isdir(dir_2d): + logging.error("2D Data directory not found at: {}".format(dir_2d)) + return + for file_path in glob(os.path.join(dir_2d, "*")): + upload_file(file_path) + + +def parse_sesans(): + sesans_dir = os.path.join(EXAMPLE_DATA_DIR, "sesans_data") + if not os.path.isdir(sesans_dir): + logging.error("Sesans Data directory not found at: {}".format(sesans_dir)) + return + for file_path in glob(os.path.join(sesans_dir, "*")): + upload_file(file_path) + + +def upload_file(file_path): + url = "http://localhost:8000/v1/data/file/" + file = open(file_path, "rb") + requests.request("POST", url, data={"is_public": True}, files={"file": file}) + + +if __name__ == "__main__": + parse_1D() + parse_2D() + parse_sesans() diff --git a/sasdata/fair_database/fair_database/urls.py b/sasdata/fair_database/fair_database/urls.py new file mode 100644 index 00000000..56c88ce2 --- /dev/null +++ b/sasdata/fair_database/fair_database/urls.py @@ -0,0 +1,42 @@ +""" +URL configuration for fair_database project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.1/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" + +from django.contrib import admin +from django.urls import include, path, re_path +from drf_spectacular.views import ( + SpectacularAPIView, + SpectacularRedocView, + SpectacularSwaggerView, +) + +urlpatterns = [ + re_path(r"^(?P(v1))/data/", include("data.urls")), + path("admin/", admin.site.urls), + path("accounts/", include("allauth.urls")), # needed for social auth + path("auth/", include("user_app.urls")), + path("api/schema/", SpectacularAPIView.as_view(), name="schema"), + path( + "api/schema/swagger-ui/", + SpectacularSwaggerView.as_view(url_name="schema"), + name="swagger-ui", + ), + path( + "api/schema/redoc/", + SpectacularRedocView.as_view(url_name="schema"), + name="redoc", + ), +] diff --git a/sasdata/fair_database/fair_database/wsgi.py b/sasdata/fair_database/fair_database/wsgi.py new file mode 100644 index 00000000..5dfc4819 --- /dev/null +++ b/sasdata/fair_database/fair_database/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for fair_database project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fair_database.settings") + +application = get_wsgi_application() diff --git a/sasdata/fair_database/manage.py b/sasdata/fair_database/manage.py new file mode 100755 index 00000000..7d7e9724 --- /dev/null +++ b/sasdata/fair_database/manage.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" + +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fair_database.settings") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/sasdata/fair_database/requirements.txt b/sasdata/fair_database/requirements.txt new file mode 100644 index 00000000..22b32934 --- /dev/null +++ b/sasdata/fair_database/requirements.txt @@ -0,0 +1,8 @@ +#this requirements extends the base sasview requirements files +#to get both you will need to run this after base requirements files +django +djangorestframework +dj-rest-auth +django-allauth +django-rest-knox +drf-spectacular diff --git a/sasdata/fair_database/user_app/__init__.py b/sasdata/fair_database/user_app/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sasdata/fair_database/user_app/admin.py b/sasdata/fair_database/user_app/admin.py new file mode 100644 index 00000000..846f6b40 --- /dev/null +++ b/sasdata/fair_database/user_app/admin.py @@ -0,0 +1 @@ +# Register your models here. diff --git a/sasdata/fair_database/user_app/apps.py b/sasdata/fair_database/user_app/apps.py new file mode 100644 index 00000000..83a29dec --- /dev/null +++ b/sasdata/fair_database/user_app/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UserAppConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "user_app" diff --git a/sasdata/fair_database/user_app/migrations/__init__.py b/sasdata/fair_database/user_app/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sasdata/fair_database/user_app/models.py b/sasdata/fair_database/user_app/models.py new file mode 100644 index 00000000..6b202199 --- /dev/null +++ b/sasdata/fair_database/user_app/models.py @@ -0,0 +1 @@ +# Create your models here. diff --git a/sasdata/fair_database/user_app/serializers.py b/sasdata/fair_database/user_app/serializers.py new file mode 100644 index 00000000..c739fea2 --- /dev/null +++ b/sasdata/fair_database/user_app/serializers.py @@ -0,0 +1,15 @@ +from rest_framework import serializers + +from dj_rest_auth.serializers import UserDetailsSerializer + + +class KnoxSerializer(serializers.Serializer): + """ + Serializer for Knox authentication. + """ + + token = serializers.SerializerMethodField() + user = UserDetailsSerializer() + + def get_token(self, obj): + return obj["token"][1] diff --git a/sasdata/fair_database/user_app/tests.py b/sasdata/fair_database/user_app/tests.py new file mode 100644 index 00000000..7d0bae94 --- /dev/null +++ b/sasdata/fair_database/user_app/tests.py @@ -0,0 +1,170 @@ +from django.test import TestCase +from rest_framework import status +from rest_framework.test import APIClient + +from django.contrib.auth.models import User + + +# Create your tests here. +class AuthTests(TestCase): + """Tests for authentication endpoints.""" + + @classmethod + def setUpTestData(cls): + cls.client1 = APIClient() + cls.client2 = APIClient() + cls.register_data = { + "email": "email@domain.org", + "username": "testUser", + "password1": "sasview!", + "password2": "sasview!", + } + cls.login_data = { + "username": "testUser", + "email": "email@domain.org", + "password": "sasview!", + } + cls.login_data_2 = { + "username": "testUser2", + "email": "email2@domain.org", + "password": "sasview!", + } + cls.user = User.objects.create_user( + id=1, username="testUser2", password="sasview!", email="email2@domain.org" + ) + cls.client_authenticated = APIClient() + cls.client_authenticated.force_authenticate(user=cls.user) + + # Create an authentication header for a given token + def auth_header(self, response): + return {"Authorization": "Token " + response.data["token"]} + + # Test if registration successfully creates a new user and logs in + def test_register(self): + response = self.client1.post("/auth/register/", data=self.register_data) + user = User.objects.get(username="testUser") + response2 = self.client1.get("/auth/user/", headers=self.auth_header(response)) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(user.email, self.register_data["email"]) + self.assertEqual(response2.status_code, status.HTTP_200_OK) + user.delete() + + # Test if login successful + def test_login(self): + response = self.client1.post("/auth/login/", data=self.login_data_2) + response2 = self.client1.get("/auth/user/", headers=self.auth_header(response)) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response2.status_code, status.HTTP_200_OK) + + # Test simultaneous login by multiple clients + def test_multiple_login(self): + response = self.client1.post("/auth/login/", data=self.login_data_2) + response2 = self.client2.post("/auth/login/", data=self.login_data_2) + response3 = self.client1.get("/auth/user/", headers=self.auth_header(response)) + response4 = self.client2.get("/auth/user/", headers=self.auth_header(response2)) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response2.status_code, status.HTTP_200_OK) + self.assertEqual(response3.status_code, status.HTTP_200_OK) + self.assertEqual(response4.status_code, status.HTTP_200_OK) + self.assertNotEqual(response.content, response2.content) + + # Test get user information + def test_user_get(self): + response = self.client_authenticated.get("/auth/user/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + response.content, + b'{"pk":1,"username":"testUser2","email":"email2@domain.org","first_name":"","last_name":""}', + ) + + # Test changing username + def test_user_put_username(self): + data = {"username": "newName"} + response = self.client_authenticated.put("/auth/user/", data=data) + self.user.username = "testUser2" + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + response.content, + b'{"pk":1,"username":"newName","email":"email2@domain.org","first_name":"","last_name":""}', + ) + + # Test changing username and first and last name + def test_user_put_name(self): + data = {"username": "newName", "first_name": "Clark", "last_name": "Kent"} + response = self.client_authenticated.put("/auth/user/", data=data) + self.user.first_name = "" + self.user.last_name = "" + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + response.content, + b'{"pk":1,"username":"newName","email":"email2@domain.org","first_name":"Clark","last_name":"Kent"}', + ) + + # Test user info inaccessible when unauthenticated + def test_user_unauthenticated(self): + response = self.client1.get("/auth/user/") + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + self.assertEqual( + response.content, + b'{"detail":"Authentication credentials were not provided."}', + ) + + # Test logout is successful after login + def test_login_logout(self): + self.client1.post("/auth/login/", data=self.login_data_2) + response = self.client1.post("/auth/logout/") + response2 = self.client1.get("/auth/user/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.content, b'{"detail":"Successfully logged out."}') + self.assertEqual(response2.status_code, status.HTTP_401_UNAUTHORIZED) + + # Test logout is successful after registration + def test_register_logout(self): + self.client1.post("/auth/register/", data=self.register_data) + response = self.client1.post("/auth/logout/") + response2 = self.client1.get("/auth/user/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.content, b'{"detail":"Successfully logged out."}') + self.assertEqual(response2.status_code, status.HTTP_401_UNAUTHORIZED) + User.objects.get(username="testUser").delete() + + # Test multiple logins for the same account log out independently + def test_multiple_logout(self): + self.client1.post("/auth/login/", data=self.login_data_2) + token = self.client2.post("/auth/login/", data=self.login_data_2) + response = self.client1.post("/auth/logout/") + response2 = self.client2.get("/auth/user/", headers=self.auth_header(token)) + response3 = self.client2.post("/auth/logout/") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response2.status_code, status.HTTP_200_OK) + self.assertEqual(response3.status_code, status.HTTP_200_OK) + + # Test login is successful after registering then logging out + def test_register_login(self): + register_response = self.client1.post( + "/auth/register/", data=self.register_data + ) + logout_response = self.client1.post("/auth/logout/") + login_response = self.client1.post("/auth/login/", data=self.login_data) + self.assertEqual(register_response.status_code, status.HTTP_201_CREATED) + self.assertEqual(logout_response.status_code, status.HTTP_200_OK) + self.assertEqual(login_response.status_code, status.HTTP_200_OK) + User.objects.get(username="testUser").delete() + + # Test password is successfully changed + def test_password_change(self): + data = { + "new_password1": "sasview?", + "new_password2": "sasview?", + "old_password": "sasview!", + } + self.login_data_2["password"] = "sasview?" + response = self.client_authenticated.post("/auth/password/change/", data=data) + login_response = self.client1.post("/auth/login/", data=self.login_data_2) + self.login_data_2["password"] = "sasview!" + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(login_response.status_code, status.HTTP_200_OK) + + @classmethod + def tearDownClass(cls): + cls.user.delete() diff --git a/sasdata/fair_database/user_app/urls.py b/sasdata/fair_database/user_app/urls.py new file mode 100644 index 00000000..cf44e8d6 --- /dev/null +++ b/sasdata/fair_database/user_app/urls.py @@ -0,0 +1,14 @@ +from django.urls import path +from dj_rest_auth.views import LogoutView, UserDetailsView, PasswordChangeView +from .views import KnoxLoginView, KnoxRegisterView + +"""Urls for authentication. Orcid login not functional. See settings.py for ORCID activation.""" + +urlpatterns = [ + path("register/", KnoxRegisterView.as_view(), name="register"), + path("login/", KnoxLoginView.as_view(), name="login"), + path("logout/", LogoutView.as_view(), name="logout"), + path("user/", UserDetailsView.as_view(), name="view user information"), + path("password/change/", PasswordChangeView.as_view(), name="change password"), + # path("login/orcid/", OrcidLoginView.as_view(), name="orcid login"), +] diff --git a/sasdata/fair_database/user_app/util.py b/sasdata/fair_database/user_app/util.py new file mode 100644 index 00000000..dc7b3502 --- /dev/null +++ b/sasdata/fair_database/user_app/util.py @@ -0,0 +1,7 @@ +from knox.models import AuthToken + + +# create an authentication token +def create_knox_token(token_model, user, serializer): + token = AuthToken.objects.create(user=user) + return token diff --git a/sasdata/fair_database/user_app/views.py b/sasdata/fair_database/user_app/views.py new file mode 100644 index 00000000..32113a74 --- /dev/null +++ b/sasdata/fair_database/user_app/views.py @@ -0,0 +1,40 @@ +from rest_framework.response import Response +from dj_rest_auth.views import LoginView +from dj_rest_auth.registration.views import RegisterView, SocialLoginView +from allauth.account.utils import complete_signup +from allauth.account import app_settings as allauth_settings +from allauth.socialaccount.providers.orcid.views import OrcidOAuth2Adapter + +from user_app.serializers import KnoxSerializer +from user_app.util import create_knox_token + +# Login using knox tokens rather than django-rest-framework tokens. + + +class KnoxLoginView(LoginView): + def get_response(self): + serializer_class = self.get_response_serializer() + + data = {"user": self.user, "token": self.token} + serializer = serializer_class(instance=data, context={"request": self.request}) + + return Response(serializer.data, status=200) + + +# Registration using knox tokens rather than django-rest-framework tokens. +class KnoxRegisterView(RegisterView): + def get_response_data(self, user): + return KnoxSerializer({"user": user, "token": self.token}).data + + def perform_create(self, serializer): + user = serializer.save(self.request) + self.token = create_knox_token(None, user, None) + complete_signup( + self.request._request, user, allauth_settings.EMAIL_VERIFICATION, None + ) + return user + + +# For ORCID login +class OrcidLoginView(SocialLoginView): + adapter_class = OrcidOAuth2Adapter diff --git a/sasdata/file_converter/ascii2d_loader.py b/sasdata/file_converter/ascii2d_loader.py index ab1d7a7a..b2f19233 100644 --- a/sasdata/file_converter/ascii2d_loader.py +++ b/sasdata/file_converter/ascii2d_loader.py @@ -86,7 +86,7 @@ def _load_points(lines, start_line, num_points): if len(dimensions) != 3: raise ValueError() width = int(dimensions[0]) height = int(dimensions[1]) - except ValueError as e: + except ValueError: err_msg = "File incorrectly formatted.\n" err_msg += ("Expected line {} to be of the form: " " .").format(current_line + 1) diff --git a/sasdata/guess.py b/sasdata/guess.py new file mode 100644 index 00000000..0f8896cb --- /dev/null +++ b/sasdata/guess.py @@ -0,0 +1,61 @@ +from sasdata.dataset_types import DatasetType, two_dim, one_dim +from scipy.stats import mode + + +def guess_column_count(split_csv: list[list[str]], starting_pos: int) -> int: + """Guess the amount of columns present in the data.""" + candidate_lines = split_csv[starting_pos::] + return int(mode([len(line) for line in candidate_lines]).mode) + + +def guess_columns(col_count: int, dataset_type: DatasetType) -> list[str]: + """Based on the amount of columns specified in col_count, try to find a set + of columns that best matchs the dataset_type. + + """ + # Ideally we want an exact match but if the ordering is bigger than the col + # count then we can accept that as well. + for order_list in dataset_type.expected_orders: + if ( + len(order_list) >= col_count + or order_list == dataset_type.expected_orders[-1] + ): + return_value = order_list[:] + # If we have any extra columns than expected, then we'll just ignore them. + excess = col_count - len(order_list) + for _ in range(excess): + return_value.append("") + return return_value + + return dataset_type.expected_orders[-1] + + +symbols_to_ignore = [".", "-", "+", "e", "E"] + + +def guess_starting_position(split_csv: list[list[str]]) -> int: + """Try to look for a line where the first item in the row can be converted + to a number. If such a line doesn't exist, try to look for a line where the + first item in the row can be converted to a number. If such a line doesn't + exist, then just return 0 as the starting position. + + """ + for i, row in enumerate(split_csv): + all_nums = True + for column in row: + amended_column = column + for symbol in symbols_to_ignore: + amended_column = amended_column.replace(symbol, "") + if not amended_column.isdigit(): + all_nums = False + break + if all_nums: + return i + return 0 + + +def guess_dataset_type(filename: str) -> DatasetType: + if filename.lower().endswith(".dat"): + return two_dim + else: + return one_dim diff --git a/sasdata/manual_tests/__init__.py b/sasdata/manual_tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sasdata/manual_tests/interpolation.py b/sasdata/manual_tests/interpolation.py new file mode 100644 index 00000000..c46078ba --- /dev/null +++ b/sasdata/manual_tests/interpolation.py @@ -0,0 +1,44 @@ +import numpy as np +import matplotlib.pyplot as plt + +from sasdata.quantities.quantity import NamedQuantity +from sasdata.quantities.plotting import quantity_plot +from sasdata.quantities import units + +from sasdata.transforms.rebinning import calculate_interpolation_matrix_1d +from sasdata.transforms.rebinning import InterpolationOptions + +def linear_interpolation_check(): + + for from_bins in [(-10, 10, 10), + (-10, 10, 1000), + (-15, 5, 10), + (15,5, 10)]: + for to_bins in [ + (-15, 0, 10), + (-15, 15, 10), + (0, 20, 100)]: + + plt.figure() + + x = NamedQuantity("x", np.linspace(*from_bins), units=units.meters) + y = x**2 + + quantity_plot(x, y) + + new_x = NamedQuantity("x_new", np.linspace(*to_bins), units=units.meters) + + rebin_mat = calculate_interpolation_matrix_1d(x, new_x, order=InterpolationOptions.LINEAR) + + new_y = y @ rebin_mat + + quantity_plot(new_x, new_y) + + print(new_y.history.summary()) + + plt.show() + + + + +linear_interpolation_check() \ No newline at end of file diff --git a/sasdata/metadata.py b/sasdata/metadata.py new file mode 100644 index 00000000..539a7d49 --- /dev/null +++ b/sasdata/metadata.py @@ -0,0 +1,241 @@ +""" +Contains classes describing the metadata for a scattering run + +The metadata is structures around the CANSas format version 1.1, found at +https://www.cansas.org/formats/canSAS1d/1.1/doc/specification.html + +Metadata from other file formats should be massaged to fit into the data classes presented here. +Any useful metadata which cannot be included in these classes represent a bug in the CANSas format. + +""" + +from dataclasses import dataclass + +from sasdata.quantities.quantity import Quantity +from numpy import ndarray + +@dataclass(kw_only=True) +class Vec3: + """A three-vector of measured quantities""" + x : Quantity[float] | None + y : Quantity[float] | None + z : Quantity[float] | None + +@dataclass(kw_only=True) +class Rot3: + """A measured rotation in 3-space""" + roll : Quantity[float] | None + pitch : Quantity[float] | None + yaw : Quantity[float] | None + +@dataclass(kw_only=True) +class Detector: + """ + Detector information + """ + name : str | None + distance : Quantity[float] | None + offset : Vec3 | None + orientation : Rot3 | None + beam_center : Vec3 | None + pixel_size : Vec3 | None + slit_length : Quantity[float] | None + + + def summary(self): + return (f"Detector:\n" + f" Name: {self.name}\n" + f" Distance: {self.distance}\n" + f" Offset: {self.offset}\n" + f" Orientation: {self.orientation}\n" + f" Beam center: {self.beam_center}\n" + f" Pixel size: {self.pixel_size}\n" + f" Slit length: {self.slit_length}\n") + + +@dataclass(kw_only=True) +class Aperture: + distance: Quantity[float] | None + size: Vec3 | None + size_name: str | None + name: str | None + type_: str | None + + def summary(self): + return (f" Aperture:\n" + f" Name: {self.name}\n" + f" Aperture size: {self.size}\n" + f" Aperture distance: {self.distance}\n") + +@dataclass(kw_only=True) +class Collimation: + """ + Class to hold collimation information + """ + + length: Quantity[float] | None + apertures: list[Aperture] + + def summary(self): + + return ( + f"Collimation:\n" + f" Length: {self.length}\n"+ + "".join([a.summary() for a in self.apertures])) + +@dataclass(kw_only=True) +class BeamSize: + name: str | None + size: Vec3 | None + +@dataclass(kw_only=True) +class Source: + radiation: str | None + beam_shape: str | None + beam_size: BeamSize | None + wavelength : Quantity[float] | None + wavelength_min : Quantity[float] | None + wavelength_max : Quantity[float] | None + wavelength_spread : Quantity[float] | None + + def summary(self) -> str: + return ( + f"Source:\n" + f" Radiation: {self.radiation}\n" + f" Shape: {self.beam_shape}\n" + f" Wavelength: {self.wavelength}\n" + f" Min. Wavelength: {self.wavelength_min}\n" + f" Max. Wavelength: {self.wavelength_max}\n" + f" Wavelength Spread: {self.wavelength_spread}\n" + f" Beam Size: {self.beam_size}\n" + ) + +@dataclass(kw_only=True) +class Sample: + """ + Class to hold the sample description + """ + name: str | None + sample_id : str | None + thickness : Quantity[float] | None + transmission: float | None + temperature : Quantity[float] | None + position : Vec3 | None + orientation : Rot3 | None + details : list[str] + + def summary(self) -> str: + return (f"Sample:\n" + f" ID: {self.sample_id}\n" + f" Transmission: {self.transmission}\n" + f" Thickness: {self.thickness}\n" + f" Temperature: {self.temperature}\n" + f" Position: {self.position}\n" + f" Orientation: {self.orientation}\n") + + +@dataclass(kw_only=True) +class Process: + """ + Class that holds information about the processes + performed on the data. + """ + name : str | None + date : str | None + description : str | None + terms : dict[str, str | Quantity[float]] + notes: list[str] + + def single_line_desc(self): + """ + Return a single line string representing the process + """ + return f"{self.name.value} {self.date.value} {self.description.value}" + + def summary(self): + if self.terms: + termInfo = " Terms:\n" + "\n".join( + [f" {k}: {v}" for k, v in self.terms.items()]) + "\n" + else: + termInfo = "" + + if self.notes: + noteInfo = " Notes:\n" + "\n".join( + [f" {note}" for note in self.notes]) + "\n" + else: + noteInfo = "" + + return (f"Process:\n" + f" Name: {self.name}\n" + f" Date: {self.date}\n" + f" Description: {self.description}\n" + f"{termInfo}" + f"{noteInfo}" + ) + + +@dataclass +class Instrument: + collimations : list[Collimation] + source : Source | None + detector : list[Detector] + + def summary(self): + return ( + "\n".join([c.summary() for c in self.collimations]) + + "".join([d.summary() for d in self.detector]) + + (self.source.summary() if self.source is not None else "")) + +@dataclass(kw_only=True) +class MetaNode: + name: str + attrs: dict[str, str] + contents: str | Quantity | ndarray | list["MetaNode"] + def to_string(self, header=""): + """Convert node to pretty printer string""" + if self.attrs: + attributes = f"\n{header} Attributes:\n" + "\n".join([f"{header} {k}: {v}" for k, v in self.attrs.items()]) + else: + attributes = "" + if self.contents: + if type(self.contents) is str: + children = f"\n{header} {self.contents}" + else: + children = "".join([n.to_string(header + " ") for n in self.contents]) + else: + children = "" + + return f"\n{header}{self.name}:{attributes}{children}" + def filter(self, name: str) -> list[ndarray | Quantity | str]: + match self.contents: + case str() | ndarray() | Quantity(): + if name == self.name: + return [self.contents] + case list(): + return [y for x in self.contents for y in x.filter(name)] + case _: + raise RuntimeError(f"Cannot filter contents of type {type(self.contents)}: {self.contents}") + return [] + + +@dataclass(kw_only=True) +class Metadata: + title: str | None + run: list[str] + definition: str | None + process: list[Process] + sample: Sample | None + instrument: Instrument | None + raw: MetaNode + + def summary(self): + run_string = self.run[0] if len(self.run) == 1 else self.run + return ( + f" {self.title}, Run: {run_string}\n" + + " " + "="*len(self.title if self.title else "") + + "=======" + + "="*len(run_string) + "\n\n" + + f"Definition: {self.title}\n" + + "".join([p.summary() for p in self.process]) + + (self.sample.summary() if self.sample else "") + + (self.instrument.summary() if self.instrument else "")) diff --git a/sasdata/model_requirements.py b/sasdata/model_requirements.py new file mode 100644 index 00000000..a7ba69b4 --- /dev/null +++ b/sasdata/model_requirements.py @@ -0,0 +1,196 @@ +from abc import ABC, abstractmethod + +from functools import singledispatch +import numpy as np +from typing import Self +from scipy.special import j0 + +from sasdata.data import SasData +from sasdata.quantities import units +from sasdata import dataset_types +from sasdata.quantities.quantity import Operation + + +class ModellingRequirements(ABC): + """Requirements that need to be passed to any modelling step""" + + dimensionality: int + operation: Operation + + def __add__(self, other: Self) -> Self: + return self.compose(other) + + @singledispatch + def compose(self, other: Self) -> Self: + # Compose uses the reversed order + return compose(other, self) + + @abstractmethod + def preprocess_q(self, data: np.ndarray, full_data: SasData) -> np.ndarray: + """Transform the Q values before processing in the model""" + pass + + @abstractmethod + def postprocess_iq(self, data: np.ndarray, full_data: SasData) -> np.ndarray: + """Transform the I(Q) values after running the model""" + pass + + +class ComposeRequirements(ModellingRequirements): + """Composition of two models""" + + first: ModellingRequirements + second: ModellingRequirements + + def __init__(self, fst, snd): + self.first = fst + self.second = snd + + def preprocess_q(self, data: np.ndarray, full_data: SasData) -> np.ndarray: + """Perform both transformations in order""" + return self.second.preprocess_q( + self.first.preprocess_q(data, full_data), full_data + ) + + def postprocess_iq(self, data: np.ndarray, full_data: SasData) -> np.ndarray: + """Perform both transformations in order""" + return self.second.postprocess_iq( + self.first.postprocess_iq(data, full_data), full_data + ) + + +class SesansModel(ModellingRequirements): + """Perform Hankel transform for SESANS""" + + def preprocess_q(self, spin_echo_length: np.ndarray, full_data: SasData) -> np.ndarray: + """Calculate the q values needed to perform the Hankel transform + + Note: this is undefined for the case when spin_echo_lengths contains + exactly one element and that values is zero. + + """ + # FIXME: Actually do the Hankel transform + spin_echo_length = np.asarray(spin_echo_length) + if len(spin_echo_length) == 1: + q_min, q_max = 0.01 * 2 * np.pi / spin_echo_length[-1], 10 * 2 * np.pi / spin_echo_length[0] + else: + # TODO: Why does q_min depend on the number of correlation lengths? + # TODO: Why does q_max depend on the correlation step size? + q_min = 0.1 * 2 * np.pi / (np.size(spin_echo_length) * spin_echo_length[-1]) + q_max = 2 * np.pi / (spin_echo_length[1] - spin_echo_length[0]) + + # TODO: Possibly make this adjustable + log_spacing = 1.0003 + self.q = np.exp(np.arange(np.log(q_min), np.log(q_max), np.log(log_spacing))) + + dq = np.diff(self.q) + dq = np.insert(dq, 0, dq[0]) + + self.H0 = dq / (2 * np.pi) * self.q + + self.H = np.outer(self.q, spin_echo_length) + j0(self.H, out=self.H) + self.H *= (dq * self.q / (2 * np.pi)).reshape((-1, 1)) + + reptheta = np.outer( + self.q, + full_data._data_contents["Wavelength"].in_units_of(units.angstroms) + / (2 * np.pi), + ) + # Note: Using inplace update with reptheta => arcsin(reptheta). + # When q L / 2 pi > 1 that means wavelength is too large to + # reach that q value at any angle. These should produce theta = NaN + # without any warnings. + # + # Reverse the condition to protect against NaN. We can't use + # theta > zaccept since all comparisons with NaN return False. + zaccept = [ + x.terms["zmax"] for x in full_data.metadata.process if "zmax" in x.terms + ][0] + with np.errstate(invalid="ignore"): + mask = ~(np.arcsin(reptheta) <= zaccept.in_units_of(units.radians)) + self.H[mask] = 0 + + return self.q + + def postprocess_iq(self, data: np.ndarray, full_data: SasData) -> np.ndarray: + """ + Apply the SESANS transform to the computed I(q) + """ + G0 = self.H0 @ data + G = self.H.T @ data + P = G - G0 + + return P + + +class SmearModel(ModellingRequirements): + """Perform a slit smearing""" + + def preprocess_q(self, data: np.ndarray, full_data: SasData) -> np.ndarray: + """Perform smearing transform""" + # FIXME: Actually do the smearing transform + return data + + def postprocess_iq(self, data: np.ndarray, full_data: SasData) -> np.ndarray: + """Perform smearing transform""" + # FIXME: Actually do the smearing transform + return data + + +class NullModel(ModellingRequirements): + """A model that does nothing""" + + def compose(self, other: ModellingRequirements) -> ModellingRequirements: + return other + + def preprocess_q(self, data: np.ndarray, _full_data: SasData) -> np.ndarray: + """Do nothing""" + return data + + def postprocess_iq(self, data: np.ndarray, _full_data: SasData) -> np.ndarray: + """Do nothing""" + return data + + +def compose( + a: ModellingRequirements, b: ModellingRequirements +) -> ModellingRequirements: + return ComposeRequirements(a, b) + + +def guess_requirements(data: SasData) -> ModellingRequirements: + """Use names of axes and units to guess what kind of processing needs to be done""" + if data.dataset_type == dataset_types.sesans: + return SesansModel() + pass + + +@singledispatch +def compose( + second: ModellingRequirements, first: ModellingRequirements +) -> ModellingRequirements: + """Compose to models together + + This function uses a reverse order so that it can perform dispatch on + the *second* term, since the classes already had a chance to dispatch + on the first parameter + + """ + return ComposeRequirements(first, second) + + +@compose.register +def _(second: NullModel, first: ModellingRequirements) -> ModellingRequirements: + """Null model is the identity element of composition""" + return first + + +@compose.register +def _(second: SesansModel, first: ModellingRequirements) -> ModellingRequirements: + match first: + case SmearModel(): + # To the first approximation, there is no slit smearing in SESANS data + return second + case _: + return ComposeRequirements(first, second) diff --git a/sasdata/postprocess.py b/sasdata/postprocess.py new file mode 100644 index 00000000..82ce6142 --- /dev/null +++ b/sasdata/postprocess.py @@ -0,0 +1,16 @@ +""" + +Post processing for loaded files + +""" + +def fix_mantid_units_error(data: SasData) -> SasData: + pass + + + +def apply_fixes(data: SasData, mantid_unit_error=True): + if mantid_unit_error: + data = fix_mantid_units_error(data) + + return data diff --git a/sasdata/quantities/__init__.py b/sasdata/quantities/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sasdata/quantities/_accessor_base.py b/sasdata/quantities/_accessor_base.py new file mode 100644 index 00000000..22f6aadd --- /dev/null +++ b/sasdata/quantities/_accessor_base.py @@ -0,0 +1,152 @@ +from typing import TypeVar + +from sasdata.quantities.quantity import Quantity +import sasdata.quantities.units as units +from sasdata.quantities.units import Unit +from sasdata.quantities.unit_parser import parse_unit + +from sasdata.data_backing import Group, Dataset + +# logger = logging.getLogger("Accessors") +class LoggerDummy: + def info(self, data): + print(data) +logger = LoggerDummy() + +DataType = TypeVar("DataType") +OutputType = TypeVar("OutputType") + + +class AccessorTarget: + def __init__(self, data: Group, verbose=False, prefix_tokens: tuple=()): + self._data = data + self.verbose = verbose + + self.prefix_tokens = list(prefix_tokens) + + def with_path_prefix(self, path_prexix: str): + """ Get an accessor that looks at a subtree of the metadata with the supplied prefix + + For example, accessors aiming at a.b, when the target it c.d will look at c.d.a.b + """ + return AccessorTarget(self._data, + verbose=self.verbose, + prefix_tokens=tuple(self.prefix_tokens + [path_prexix])) + + def get_value(self, path: str): + + tokens = self.prefix_tokens + path.split(".") + + if self.verbose: + logger.info(f"Finding: {path}") + logger.info(f"Full path: {tokens}") + + # Navigate the tree from the entry we need + + current_tree_position: Group | Dataset = self._data + + for token in tokens: + + options = token.split("|") + + if isinstance(current_tree_position, Group): + + found = False + for option in options: + if option in current_tree_position.children: + current_tree_position = current_tree_position.children[option] + found = True + + if self.verbose: + logger.info(f"Found option: {option}") + + if not found: + if self.verbose: + logger.info(f"Failed to find any of {options} on group {current_tree_position.name}. Options: " + + ",".join([key for key in current_tree_position.children])) + return None + + elif isinstance(current_tree_position, Dataset): + + found = False + for option in options: + if option in current_tree_position.attributes: + current_tree_position = current_tree_position.attributes[option] + found = True + + if self.verbose: + logger.info(f"Found option: {option}") + + if not found: + if self.verbose: + logger.info(f"Failed to find any of {options} on attribute {current_tree_position.name}. Options: " + + ",".join([key for key in current_tree_position.attributes])) + return None + + if self.verbose: + logger.info(f"Found value: {current_tree_position}") + + return current_tree_position.data + + + +class Accessor[DataType, OutputType]: + """ Base class """ + def __init__(self, target_object: AccessorTarget, value_target: str): + self.target_object = target_object + self.value_target = value_target + + @property + def value(self) -> OutputType | None: + return self.target_object.get_value(self.value_target) + +class StringAccessor(Accessor[str, str]): + """ String based fields """ + @property + def value(self) -> str | None: + return self.target_object.get_value(self.value_target) + +class FloatAccessor(Accessor[float, float]): + """ Float based fields """ + @property + def value(self) -> float | None: + return self.target_object.get_value(self.value_target) + + + + +class QuantityAccessor[DataType](Accessor[DataType, Quantity[DataType]]): + """ Base class for accessors that work with quantities that have units """ + def __init__(self, target_object: AccessorTarget, value_target: str, unit_target: str, default_unit=units.none): + super().__init__(target_object, value_target) + self._unit_target = unit_target + self.default_unit = default_unit + + def _numerical_part(self) -> DataType | None: + """ Numerical part of the data """ + return self.target_object.get_value(self.value_target) + + def _unit_part(self) -> str | None: + """ String form of units for the data """ + return self.target_object.get_value(self._unit_target) + + @property + def unit(self) -> Unit: + u = self._unit_part() + if u is None: + return self.default_unit + else: + return parse_unit(u) + + @property + def value(self) -> Quantity[DataType] | None: + if self._unit_part() is not None and self._numerical_part() is not None: + return Quantity(self._numerical_part(), self.unit) + return None + + @property + def quantity(self): + if self._unit_part() is not None and self._numerical_part() is not None: + return Quantity(self._numerical_part(), self.unit) + return None + diff --git a/sasdata/quantities/_autogen_warning.py b/sasdata/quantities/_autogen_warning.py new file mode 100644 index 00000000..5adb4b56 --- /dev/null +++ b/sasdata/quantities/_autogen_warning.py @@ -0,0 +1,79 @@ +warning_text = """ + +This file is autogenerated! + +Do not edit by hand, instead edit the files that build it (%s) + + + + +DDDDDDDDDDDDD NNNNNNNN NNNNNNNN tttt +D::::::::::::DDD N:::::::N N::::::N ttt:::t +D:::::::::::::::DD N::::::::N N::::::N t:::::t +DDD:::::DDDDD:::::D N:::::::::N N::::::N t:::::t + D:::::D D:::::D ooooooooooo N::::::::::N N::::::N ooooooooooo ttttttt:::::ttttttt + D:::::D D:::::D oo:::::::::::oo N:::::::::::N N::::::N oo:::::::::::oo t:::::::::::::::::t + D:::::D D:::::Do:::::::::::::::o N:::::::N::::N N::::::No:::::::::::::::ot:::::::::::::::::t + D:::::D D:::::Do:::::ooooo:::::o N::::::N N::::N N::::::No:::::ooooo:::::otttttt:::::::tttttt + D:::::D D:::::Do::::o o::::o N::::::N N::::N:::::::No::::o o::::o t:::::t + D:::::D D:::::Do::::o o::::o N::::::N N:::::::::::No::::o o::::o t:::::t + D:::::D D:::::Do::::o o::::o N::::::N N::::::::::No::::o o::::o t:::::t + D:::::D D:::::D o::::o o::::o N::::::N N:::::::::No::::o o::::o t:::::t tttttt +DDD:::::DDDDD:::::D o:::::ooooo:::::o N::::::N N::::::::No:::::ooooo:::::o t::::::tttt:::::t +D:::::::::::::::DD o:::::::::::::::o N::::::N N:::::::No:::::::::::::::o tt::::::::::::::t +D::::::::::::DDD oo:::::::::::oo N::::::N N::::::N oo:::::::::::oo tt:::::::::::tt +DDDDDDDDDDDDD ooooooooooo NNNNNNNN NNNNNNN ooooooooooo ttttttttttt + + + + + + + + + dddddddd +EEEEEEEEEEEEEEEEEEEEEE d::::::d iiii tttt BBBBBBBBBBBBBBBBB +E::::::::::::::::::::E d::::::d i::::i ttt:::t B::::::::::::::::B +E::::::::::::::::::::E d::::::d iiii t:::::t B::::::BBBBBB:::::B +EE::::::EEEEEEEEE::::E d:::::d t:::::t BB:::::B B:::::B + E:::::E EEEEEE ddddddddd:::::d iiiiiiittttttt:::::ttttttt B::::B B:::::Byyyyyyy yyyyyyy + E:::::E dd::::::::::::::d i:::::it:::::::::::::::::t B::::B B:::::B y:::::y y:::::y + E::::::EEEEEEEEEE d::::::::::::::::d i::::it:::::::::::::::::t B::::BBBBBB:::::B y:::::y y:::::y + E:::::::::::::::E d:::::::ddddd:::::d i::::itttttt:::::::tttttt B:::::::::::::BB y:::::y y:::::y + E:::::::::::::::E d::::::d d:::::d i::::i t:::::t B::::BBBBBB:::::B y:::::y y:::::y + E::::::EEEEEEEEEE d:::::d d:::::d i::::i t:::::t B::::B B:::::B y:::::y y:::::y + E:::::E d:::::d d:::::d i::::i t:::::t B::::B B:::::B y:::::y:::::y + E:::::E EEEEEEd:::::d d:::::d i::::i t:::::t tttttt B::::B B:::::B y:::::::::y +EE::::::EEEEEEEE:::::Ed::::::ddddd::::::ddi::::::i t::::::tttt:::::t BB:::::BBBBBB::::::B y:::::::y +E::::::::::::::::::::E d:::::::::::::::::di::::::i tt::::::::::::::t B:::::::::::::::::B y:::::y +E::::::::::::::::::::E d:::::::::ddd::::di::::::i tt:::::::::::tt B::::::::::::::::B y:::::y +EEEEEEEEEEEEEEEEEEEEEE ddddddddd dddddiiiiiiii ttttttttttt BBBBBBBBBBBBBBBBB y:::::y + y:::::y + y:::::y + y:::::y + y:::::y + yyyyyyy + + + + dddddddd +HHHHHHHHH HHHHHHHHH d::::::d +H:::::::H H:::::::H d::::::d +H:::::::H H:::::::H d::::::d +HH::::::H H::::::HH d:::::d + H:::::H H:::::H aaaaaaaaaaaaa nnnn nnnnnnnn ddddddddd:::::d + H:::::H H:::::H a::::::::::::a n:::nn::::::::nn dd::::::::::::::d + H::::::HHHHH::::::H aaaaaaaaa:::::an::::::::::::::nn d::::::::::::::::d + H:::::::::::::::::H a::::ann:::::::::::::::nd:::::::ddddd:::::d + H:::::::::::::::::H aaaaaaa:::::a n:::::nnnn:::::nd::::::d d:::::d + H::::::HHHHH::::::H aa::::::::::::a n::::n n::::nd:::::d d:::::d + H:::::H H:::::H a::::aaaa::::::a n::::n n::::nd:::::d d:::::d + H:::::H H:::::H a::::a a:::::a n::::n n::::nd:::::d d:::::d +HH::::::H H::::::HHa::::a a:::::a n::::n n::::nd::::::ddddd::::::dd +H:::::::H H:::::::Ha:::::aaaa::::::a n::::n n::::n d:::::::::::::::::d +H:::::::H H:::::::H a::::::::::aa:::a n::::n n::::n d:::::::::ddd::::d +HHHHHHHHH HHHHHHHHH aaaaaaaaaa aaaa nnnnnn nnnnnn ddddddddd ddddd + + + +""" \ No newline at end of file diff --git a/sasdata/quantities/_build_tables.py b/sasdata/quantities/_build_tables.py new file mode 100644 index 00000000..e0b1e41f --- /dev/null +++ b/sasdata/quantities/_build_tables.py @@ -0,0 +1,446 @@ +""" +Builds a data file containing details of units +""" + +import numpy as np +from collections import defaultdict, namedtuple +from _units_base import Dimensions +from _autogen_warning import warning_text + +Magnitude = namedtuple("Magnitude", ["symbol", "special_symbol", "latex_symbol", "name", "scale"]) + +bigger_magnitudes: list[Magnitude] = [ + Magnitude("E", None, None, "exa", 1e18), + Magnitude("P", None, None, "peta", 1e15), + Magnitude("T", None, None, "tera", 1e12), + Magnitude("G", None, None, "giga", 1e9), + Magnitude("M", None, None, "mega", 1e6), + Magnitude("k", None, None, "kilo", 1e3) ] + +smaller_magnitudes: list[Magnitude] = [ + Magnitude("m", None, None, "milli", 1e-3), + Magnitude("u", "µ", r"\mu", "micro", 1e-6), + Magnitude("n", None, None, "nano", 1e-9), + Magnitude("p", None, None, "pico", 1e-12), + Magnitude("f", None, None, "femto", 1e-15), + Magnitude("a", None, None, "atto", 1e-18)] + +unusual_magnitudes: list[Magnitude] = [ + Magnitude("d", None, None, "deci", 1e-1), + Magnitude("c", None, None, "centi", 1e-2) +] + +all_magnitudes = bigger_magnitudes + smaller_magnitudes + +UnitData = namedtuple("UnitData", ["symbol", "special_symbol", "latex_symbol", "singular", "plural", "scale", "length", "time", "mass", "current", "temperature", "moles_hint", "angle_hint", "magnitudes"]) + +# Length, time, mass, current, temperature +base_si_units = [ + UnitData("m", None, None, "meter", "meters", 1, 1, 0, 0, 0, 0, 0, 0, all_magnitudes + unusual_magnitudes), + UnitData("s", None, None, "second", "seconds", 1, 0, 1, 0, 0, 0, 0, 0, smaller_magnitudes), + UnitData("g", None, None, "gram", "grams", 1e-3, 0, 0, 1, 0, 0, 0, 0, all_magnitudes), + UnitData("A", None, None, "ampere", "amperes", 1, 0, 0, 0, 1, 0, 0, 0, all_magnitudes), + UnitData("K", None, None, "kelvin", "kelvin", 1, 0, 0, 0, 0, 1, 0, 0, all_magnitudes) ] + +derived_si_units = [ + UnitData("Hz", None, None, "hertz", "hertz", 1, 0, -1, 0, 0, 0, 0, 0, all_magnitudes), + UnitData("N", None, None, "newton", "newtons", 1, 1, -2, 1, 0, 0, 0, 0, all_magnitudes), + UnitData("Pa", None, None, "pascal", "pascals", 1, -1, -2, 1, 0, 0, 0, 0, all_magnitudes), + UnitData("J", None, None, "joule", "joules", 1, 2, -2, 1, 0, 0, 0, 0, all_magnitudes), + UnitData("W", None, None, "watt", "watts", 1, 2, -3, 1, 0, 0, 0, 0, all_magnitudes), + UnitData("C", None, None, "coulomb", "coulombs", 1, 0, 1, 0, 1, 0, 0, 0, all_magnitudes), + UnitData("V", None, None, "volts", "volts", 1, 2, -3, 1, -1, 0, 0, 0, all_magnitudes), + UnitData("Ohm", "Ω", r"\Omega", "ohm", "ohms", 1, 2, -3, 1, -2, 0, 0, 0, all_magnitudes), + UnitData("F", None, None, "farad", "farads", 1, -2, 4, -1, 2, 0, 0, 0, all_magnitudes), + UnitData("S", None, None, "siemens", "siemens", 1, -2, 3, -1, 2, 0, 0, 0, all_magnitudes), + UnitData("Wb", None, None, "weber", "webers", 1, 2, -2, 1, -1, 0, 0, 0, all_magnitudes), + UnitData("T", None, None, "tesla", "tesla", 1, 0, -2, 1, -1, 0, 0, 0, all_magnitudes), + UnitData("H", None, None, "henry", "henry", 1, 2, -2, 1, -2, 0, 0, 0, all_magnitudes), +] + +non_si_dimensioned_units: list[tuple[str, str | None, str, str, float, int, int, int, int, int, int, int, list]] = [ + UnitData("Ang", "Å", r"\AA", "angstrom", "angstroms", 1e-10, 1, 0, 0, 0, 0, 0, 0, []), + UnitData("micron", None, None, "micron", "microns", 1e-6, 1, 0, 0, 0, 0, 0, 0, []), + UnitData("min", None, None, "minute", "minutes", 60, 0, 1, 0, 0, 0, 0, 0, []), + UnitData("h", None, None, "hour", "hours", 360, 0, 1, 0, 0, 0, 0, 0, []), + UnitData("d", None, None, "day", "days", 360*24, 0, 1, 0, 0, 0, 0, 0, []), + UnitData("y", None, None, "year", "years", 360*24*365.2425, 0, 1, 0, 0, 0, 0, 0, []), + UnitData("deg", None, None, "degree", "degrees", 180/np.pi, 0, 0, 0, 0, 0, 0, 1, []), + UnitData("rad", None, None, "radian", "radians", 1, 0, 0, 0, 0, 0, 0, 1, []), + UnitData("sr", None, None, "stradian", "stradians", 1, 0, 0, 0, 0, 0, 0, 2, []), + UnitData("l", None, None, "litre", "litres", 1e-3, 3, 0, 0, 0, 0, 0, 0, []), + UnitData("eV", None, None, "electronvolt", "electronvolts", 1.602176634e-19, 2, -2, 1, 0, 0, 0, 0, all_magnitudes), + UnitData("au", None, None, "atomic mass unit", "atomic mass units", 1.660538921e-27, 0, 0, 1, 0, 0, 0, 0, []), + UnitData("mol", None, None, "mole", "moles", 6.02214076e23, 0, 0, 0, 0, 0, 1, 0, smaller_magnitudes), + UnitData("kgForce", None, None, "kg force", "kg force", 9.80665, 1, -2, 1, 0, 0, 0, 0, []), + UnitData("C", None, None, "degree Celsius", "degrees Celsius", 1, 0, 0, 0, 0, 1, 0, 0, []), + UnitData("miles", None, None, "mile", "miles", 1760*3*0.3048, 1, 0, 0, 0, 0, 0, 0, []), + UnitData("yrd", None, None, "yard", "yards", 3*0.3048, 1, 0, 0, 0, 0, 0, 0, []), + UnitData("ft", None, None, "foot", "feet", 0.3048, 1, 0, 0, 0, 0, 0, 0, []), + UnitData("in", None, None, "inch", "inches", 0.0254, 1, 0, 0, 0, 0, 0, 0, []), + UnitData("lb", None, None, "pound", "pounds", 0.45359237, 0, 0, 1, 0, 0, 0, 0, []), + UnitData("lbf", None, None, "pound force", "pounds force", 4.448222, 1, -2, 1, 0, 0, 0, 0, []), + UnitData("oz", None, None, "ounce", "ounces", 0.45359237/16, 0, 0, 1, 0, 0, 0, 0, []), + UnitData("psi", None, None, "pound force per square inch", "pounds force per square inch", 4.448222/(0.0254**2), -1, -2, 1, 0, 0, 0, 0, []), +] + +non_si_dimensionless_units: list[tuple[str, str | None, str, str, float, int, int, int, int, int, int, int, list]] = [ + UnitData("none", None, None, "none", "none", 1, 0, 0, 0, 0, 0, 0, 0, []), + UnitData("percent", "%", r"\%", "percent", "percent", 0.01, 0, 0, 0, 0, 0, 0, 0, []) +] + +non_si_units = non_si_dimensioned_units + non_si_dimensionless_units + +# TODO: +# Add Hartree? Rydberg? Bohrs? +# Add CGS + +# Two stages of aliases, to make sure units don't get lost + +aliases_1 = { + "A": ["Amps", "amps"], + "C": ["Coulombs", "coulombs"] +} + +aliases_2 = { + "y": ["yr", "year"], + "d": ["day"], + "h": ["hr", "hour"], + "Ang": ["A", "Å"], + "au": ["amu"], + "percent": ["%"], + "deg": ["degr", "Deg", "degree", "degrees", "Degrees"], + "none": ["Counts", "counts", "cnts", "Cnts", "a.u.", "fraction", "Fraction"], + "K": ["C"] # Ugh, cansas +} + + + +all_units = base_si_units + derived_si_units + non_si_units + +encoding = "utf-8" + +def format_name(name: str): + return name.lower().replace(" ", "_") + +with open("units.py", 'w', encoding=encoding) as fid: + + # Write warning header + fid.write('"""'+(warning_text%"_build_tables.py, _units_base.py")+'"""') + + # Write in class definitions + fid.write("\n\n" + "#\n" + "# Included from _units_base.py\n" + "#\n\n") + + with open("_units_base.py", 'r') as base: + for line in base: + # unicode_superscript is a local module when called from + # _unit_tables.py but a submodule of sasdata.quantities + # when called from units.py. This condition patches the + # line when the copy is made. + if line.startswith("from unicode_superscript"): + fid.write(line.replace("from unicode_superscript", "from sasdata.quantities.unicode_superscript")) + else: + fid.write(line) + + # Write in unit definitions + fid.write("\n\n" + "#\n" + "# Specific units \n" + "#\n\n") + + symbol_lookup = {} + unit_types_temp = defaultdict(list) # Keep track of unit types + unit_types = defaultdict(list) + + for unit_def in all_units: + + formatted_plural = format_name(unit_def.plural) + formatted_singular = format_name(unit_def.singular) + + dimensions = Dimensions(unit_def.length, unit_def.time, unit_def.mass, unit_def.current, unit_def.temperature, unit_def.moles_hint, unit_def.angle_hint) + fid.write(f"{formatted_plural} = NamedUnit({unit_def.scale}, Dimensions({unit_def.length}, {unit_def.time}, {unit_def.mass}, {unit_def.current}, {unit_def.temperature}, {unit_def.moles_hint}, {unit_def.angle_hint})," + f"name='{formatted_plural}'," + f"ascii_symbol='{unit_def.symbol}'," + f"{'' if unit_def.latex_symbol is None else f"""latex_symbol=r'{unit_def.latex_symbol}',""" }" + f"symbol='{unit_def.symbol if unit_def.special_symbol is None else unit_def.special_symbol}')\n") + + symbol_lookup[unit_def.symbol] = formatted_plural + if unit_def.special_symbol is not None: + symbol_lookup[unit_def.special_symbol] = formatted_plural + + unit_types_temp[hash(dimensions)].append( + (unit_def.symbol, unit_def.special_symbol, formatted_singular, formatted_plural, unit_def.scale, dimensions)) + + unit_types[hash(dimensions)].append(formatted_plural) + + for mag in unit_def.magnitudes: + + # Work out the combined symbol, accounts for unicode or not + combined_special_symbol = (mag.symbol if mag.special_symbol is None else mag.special_symbol) + \ + (unit_def.symbol if unit_def.special_symbol is None else unit_def.special_symbol) + + combined_symbol = mag.symbol + unit_def.symbol + + # Combined unit name + combined_name_singular = f"{mag.name}{formatted_singular}" + combined_name_plural = f"{mag.name}{formatted_plural}" + + combined_scale = unit_def.scale * mag.scale + + latex_symbol = None + if unit_def.latex_symbol is not None and mag.latex_symbol is not None: + latex_symbol = f"{{{mag.latex_symbol}}}{unit_def.latex_symbol}" + elif unit_def.latex_symbol is not None: + latex_symbol = f"{mag.symbol}{unit_def.latex_symbol}" + elif mag.latex_symbol is not None: + latex_symbol = f"{{{mag.latex_symbol}}}{unit_def.symbol}" + + # Units + dimensions = Dimensions(unit_def.length, unit_def.time, unit_def.mass, unit_def.current, unit_def.temperature, unit_def.moles_hint, unit_def.angle_hint) + fid.write(f"{combined_name_plural} = NamedUnit({combined_scale}, " + f"Dimensions({unit_def.length}, {unit_def.time}, {unit_def.mass}, {unit_def.current}, {unit_def.temperature}, {unit_def.moles_hint}, {unit_def.angle_hint})," + f"name='{combined_name_plural}'," + f"ascii_symbol='{combined_symbol}'," + f"{'' if latex_symbol is None else f"""latex_symbol=r'{latex_symbol}',""" }" + f"symbol='{combined_special_symbol}')\n") + + symbol_lookup[combined_symbol] = combined_name_plural + symbol_lookup[combined_special_symbol] = combined_name_plural + + unit_types_temp[hash(dimensions)].append( + (combined_symbol, combined_special_symbol, combined_name_singular, + combined_name_plural, combined_scale, dimensions)) + + unit_types[hash(dimensions)].append(combined_name_plural) + + # + # Higher dimensioned types + # + + length_units = unit_types_temp[hash(Dimensions(length=1))] + time_units = unit_types_temp[hash(Dimensions(time=1))] + mass_units = unit_types_temp[hash(Dimensions(mass=1))] + amount_units = unit_types_temp[hash(Dimensions(moles_hint=1))] + + # Length based + for symbol, special_symbol, singular, plural, scale, _ in length_units: + for prefix, power, name, unicode_suffix in [ + ("square_", 2, plural, '²'), + ("cubic_", 3, plural, '³'), + ("per_", -1, singular, '⁻¹'), + ("per_square_", -2, singular,'⁻²'), + ("per_cubic_", -3, singular,'⁻³')]: + + dimensions = Dimensions(length=power) + unit_name = prefix + name + unit_special_symbol = (symbol if special_symbol is None else special_symbol) + unicode_suffix + unit_symbol = symbol + f"^{power}" + fid.write(f"{unit_name} = NamedUnit({scale**power}, Dimensions(length={power}), " + f"name='{unit_name}', " + f"ascii_symbol='{unit_symbol}', " + f"symbol='{unit_special_symbol}')\n") + + unit_types[hash(dimensions)].append(unit_name) + + # Speed and acceleration + for length_symbol, length_special_symbol, _, length_name, length_scale, _ in length_units: + for time_symbol, time_special_symbol, time_name, _, time_scale, _ in time_units: + speed_name = length_name + "_per_" + time_name + accel_name = length_name + "_per_square_" + time_name + + speed_dimensions = Dimensions(length=1, time=-1) + accel_dimensions = Dimensions(length=1, time=-2) + + length_special = length_special_symbol if length_special_symbol is not None else length_symbol + time_special = time_special_symbol if time_special_symbol is not None else time_symbol + + fid.write(f"{speed_name} " + f"= NamedUnit({length_scale / time_scale}, " + f"Dimensions(length=1, time=-1), " + f"name='{speed_name}', " + f"ascii_symbol='{length_symbol}/{time_symbol}', " + f"symbol='{length_special}{time_special}⁻¹')\n") + + fid.write(f"{accel_name} = NamedUnit({length_scale / time_scale**2}, " + f"Dimensions(length=1, time=-2), " + f"name='{accel_name}', " + f"ascii_symbol='{length_symbol}/{time_symbol}^2', " + f"symbol='{length_special}{time_special}⁻²')\n") + + unit_types[hash(speed_dimensions)].append(speed_name) + unit_types[hash(accel_dimensions)].append(accel_name) + + # Density + for length_symbol, length_special_symbol, length_name, _, length_scale, _ in length_units: + for mass_symbol, mass_special_symbol, _, mass_name, mass_scale, _ in mass_units: + + name = mass_name + "_per_cubic_" + length_name + + dimensions = Dimensions(length=-3, mass=1) + + mass_special = mass_symbol if mass_special_symbol is None else mass_special_symbol + length_special = length_symbol if length_special_symbol is None else length_special_symbol + + fid.write(f"{name} " + f"= NamedUnit({mass_scale / length_scale**3}, " + f"Dimensions(length=-3, mass=1), " + f"name='{name}', " + f"ascii_symbol='{mass_symbol} {length_symbol}^-3', " + f"symbol='{mass_special}{length_special}⁻³')\n") + + unit_types[hash(dimensions)].append(name) + + # Concentration + for length_symbol, length_special_symbol, length_name, _, length_scale, _ in length_units: + for amount_symbol, amount_special_symbol, _, amount_name, amount_scale, _ in amount_units: + + name = amount_name + "_per_cubic_" + length_name + + dimensions = Dimensions(length=-3, moles_hint=1) + + length_special = length_symbol if length_special_symbol is None else length_special_symbol + amount_special = amount_symbol if amount_special_symbol is None else amount_special_symbol + + fid.write(f"{name} " + f"= NamedUnit({amount_scale / length_scale**3}, " + f"Dimensions(length=-3, moles_hint=1), " + f"name='{name}', " + f"ascii_symbol='{amount_symbol} {length_symbol}^-3', " + f"symbol='{amount_special}{length_special}⁻³')\n") + + unit_types[hash(dimensions)].append(name) + + # TODO: Torque, Momentum, Entropy + + # + # Add aliases to symbol lookup table + # + + # Apply the alias transforms sequentially + for aliases in [aliases_1, aliases_2]: + for base_name in aliases: + alias_list = aliases[base_name] + for alias in alias_list: + symbol_lookup[alias] = symbol_lookup[base_name] + + # + # Write out the symbol lookup table + # + fid.write("\n#\n# Lookup table from symbols to units\n#\n\n") + fid.write("symbol_lookup = {\n") + for k in symbol_lookup: + if k != "none": + fid.write(f' "{k}": {symbol_lookup[k]},\n') + fid.write("}\n\n") + + # + # Collections of units by type + # + + dimension_names = [ + ("length", Dimensions(length=1)), + ("area", Dimensions(length=2)), + ("volume", Dimensions(length=3)), + ("inverse_length", Dimensions(length=-1)), + ("inverse_area", Dimensions(length=-2)), + ("inverse_volume", Dimensions(length=-3)), + ("time", Dimensions(time=1)), + ("rate", Dimensions(time=-1)), + ("speed", Dimensions(length=1, time=-1)), + ("acceleration", Dimensions(length=1, time=-2)), + ("density", Dimensions(length=-3, mass=1)), + ("force", Dimensions(1, -2, 1, 0, 0)), + ("pressure", Dimensions(-1, -2, 1, 0, 0)), + ("energy", Dimensions(2, -2, 1, 0, 0)), + ("power", Dimensions(2, -3, 1, 0, 0)), + ("charge", Dimensions(0, 1, 0, 1, 0)), + ("potential", Dimensions(2, -3, 1, -1, 0)), + ("resistance", Dimensions(2, -3, 1, -2, 0)), + ("capacitance", Dimensions(-2, 4, -1, 2, 0)), + ("conductance", Dimensions(-2, 3, -1, 2, 0)), + ("magnetic_flux", Dimensions(2, -2, 1, -1, 0)), + ("magnetic_flux_density", Dimensions(0, -2, 1, -1, 0)), + ("inductance", Dimensions(2, -2, 1, -2, 0)), + ("temperature", Dimensions(temperature=1)), + ("dimensionless", Dimensions()), + ("angle", Dimensions(angle_hint=1)), + ("solid_angle", Dimensions(angle_hint=2)), + ("amount", Dimensions(moles_hint=1)), + ("concentration", Dimensions(length=-3, moles_hint=1)), + ] + + fid.write("\n#\n# Units by type \n#\n\n") + + for dimension_name, dimensions in dimension_names: + + + fid.write(f"\n" + f"{dimension_name} = UnitGroup(\n" + f" name = '{dimension_name}', \n" + f" units = [\n") + + for unit_name in unit_types[hash(dimensions)]: + fid.write(" " + unit_name + ",\n") + + fid.write("])\n") + + + # List of dimensions + fid.write("\n\n") + fid.write("unit_group_names = [\n") + for dimension_name, _ in dimension_names: + fid.write(f" '{dimension_name}',\n") + fid.write("]\n\n") + + fid.write("unit_groups = {\n") + for dimension_name, _ in dimension_names: + fid.write(f" '{dimension_name}': {dimension_name},\n") + fid.write("}\n\n") + + +with open("accessors.py", 'w', encoding=encoding) as fid: + + + fid.write('"""'+(warning_text%"_build_tables.py, _accessor_base.py")+'"""\n\n') + + with open("_accessor_base.py", 'r') as base: + for line in base: + fid.write(line) + + for dimension_name, dimensions in dimension_names: + + accessor_name = dimension_name.capitalize().replace("_", "") + "Accessor" + + fid.write(f"\n" + f"class {accessor_name}[T](QuantityAccessor[T]):\n" + f" dimension_name = '{dimension_name}'\n" + f" \n") + + for unit_name in unit_types[hash(dimensions)]: + fid.write(f" @property\n" + f" def {unit_name}(self) -> T:\n" + f" quantity = self.quantity\n" + f" if quantity is None:\n" + f" return None\n" + f" else:\n" + f" return quantity.in_units_of(units.{unit_name})\n" + f"\n") + + fid.write("\n") + +with open("si.py", 'w') as fid: + + fid.write('"""'+(warning_text%"_build_tables.py")+'"""\n\n') + si_unit_names = [values.plural for values in base_si_units + derived_si_units if values.plural != "grams"] + ["kilograms"] + + for name in si_unit_names: + + fid.write(f"from sasdata.quantities.units import {name}\n") + + fid.write("\nall_si = [\n") + for name in si_unit_names: + fid.write(f" {name},\n") + fid.write("]\n") diff --git a/sasdata/quantities/_units_base.py b/sasdata/quantities/_units_base.py new file mode 100644 index 00000000..351a040e --- /dev/null +++ b/sasdata/quantities/_units_base.py @@ -0,0 +1,364 @@ +from dataclasses import dataclass +from typing import Sequence, Self +from fractions import Fraction + +import numpy as np + +from unicode_superscript import int_as_unicode_superscript + +class DimensionError(Exception): + pass + +class Dimensions: + """ + + Note that some SI Base units are not useful from the perspecive of the sasview project, and make things + behave badly. In particular: moles and angular measures are dimensionless, and candelas are really a weighted + measure of power. + + We do however track angle and amount, because its really useful for formatting units + + """ + def __init__(self, + length: int = 0, + time: int = 0, + mass: int = 0, + current: int = 0, + temperature: int = 0, + moles_hint: int = 0, + angle_hint: int = 0): + + self.length = length + self.time = time + self.mass = mass + self.current = current + self.temperature = temperature + self.moles_hint = moles_hint + self.angle_hint = angle_hint + + @property + def is_dimensionless(self): + """ Is this dimension dimensionless (ignores moles_hint and angle_hint) """ + return self.length == 0 and self.time == 0 and self.mass == 0 and self.current == 0 and self.temperature == 0 + + def __mul__(self: Self, other: Self): + + if not isinstance(other, Dimensions): + return NotImplemented + + return Dimensions( + self.length + other.length, + self.time + other.time, + self.mass + other.mass, + self.current + other.current, + self.temperature + other.temperature, + self.moles_hint + other.moles_hint, + self.angle_hint + other.angle_hint) + + def __truediv__(self: Self, other: Self): + + if not isinstance(other, Dimensions): + return NotImplemented + + return Dimensions( + self.length - other.length, + self.time - other.time, + self.mass - other.mass, + self.current - other.current, + self.temperature - other.temperature, + self.moles_hint - other.moles_hint, + self.angle_hint - other.angle_hint) + + def __pow__(self, power: int | float): + + if not isinstance(power, (int, float)): + return NotImplemented + + frac = Fraction(power).limit_denominator(500) # Probably way bigger than needed, 10 would probably be fine + denominator = frac.denominator + numerator = frac.numerator + + # Throw errors if dimension is not a multiple of the denominator + + if self.length % denominator != 0: + raise DimensionError(f"Cannot apply power of {frac} to unit with length dimensionality {self.length}") + + if self.time % denominator != 0: + raise DimensionError(f"Cannot apply power of {frac} to unit with time dimensionality {self.time}") + + if self.mass % denominator != 0: + raise DimensionError(f"Cannot apply power of {frac} to unit with mass dimensionality {self.mass}") + + if self.current % denominator != 0: + raise DimensionError(f"Cannot apply power of {frac} to unit with current dimensionality {self.current}") + + if self.temperature % denominator != 0: + raise DimensionError(f"Cannot apply power of {frac} to unit with temperature dimensionality {self.temperature}") + + if self.moles_hint % denominator != 0: + raise DimensionError(f"Cannot apply power of {frac} to unit with moles hint dimensionality of {self.moles_hint}") + + if self.angle_hint % denominator != 0: + raise DimensionError(f"Cannot apply power of {frac} to unit with angle hint dimensionality of {self.angle_hint}") + + return Dimensions( + (self.length * numerator) // denominator, + (self.time * numerator) // denominator, + (self.mass * numerator) // denominator, + (self.current * numerator) // denominator, + (self.temperature * numerator) // denominator, + (self.moles_hint * numerator) // denominator, + (self.angle_hint * numerator) // denominator) + + def __eq__(self: Self, other: Self): + if isinstance(other, Dimensions): + return (self.length == other.length and + self.time == other.time and + self.mass == other.mass and + self.current == other.current and + self.temperature == other.temperature and + self.moles_hint == other.moles_hint and + self.angle_hint == other.angle_hint) + + return NotImplemented + + def __hash__(self): + """ Unique representation of units using Godel like encoding""" + + two_powers = 0 + if self.length < 0: + two_powers += 1 + + if self.time < 0: + two_powers += 2 + + if self.mass < 0: + two_powers += 4 + + if self.current < 0: + two_powers += 8 + + if self.temperature < 0: + two_powers += 16 + + if self.moles_hint < 0: + two_powers += 32 + + if self.angle_hint < 0: + two_powers += 64 + + return 2**two_powers * 3**abs(self.length) * 5**abs(self.time) * \ + 7**abs(self.mass) * 11**abs(self.current) * 13**abs(self.temperature) * \ + 17**abs(self.moles_hint) * 19**abs(self.angle_hint) + + def __repr__(self): + tokens = [] + for name, size in [ + ("length", self.length), + ("time", self.time), + ("mass", self.mass), + ("current", self.current), + ("temperature", self.temperature), + ("amount", self.moles_hint), + ("angle", self.angle_hint)]: + + if size == 0: + pass + elif size == 1: + tokens.append(f"{name}") + else: + tokens.append(f"{name}{int_as_unicode_superscript(size)}") + + return ' '.join(tokens) + + def si_repr(self): + tokens = [] + for name, size in [ + ("kg", self.mass), + ("m", self.length), + ("s", self.time), + ("A", self.current), + ("K", self.temperature), + ("mol", self.moles_hint)]: + + if size == 0: + pass + elif size == 1: + tokens.append(f"{name}") + else: + tokens.append(f"{name}{int_as_unicode_superscript(size)}") + + match self.angle_hint: + case 0: + pass + case 2: + tokens.append("sr") + case -2: + tokens.append("sr" + int_as_unicode_superscript(-1)) + case _: + tokens.append("rad" + int_as_unicode_superscript(self.angle_hint)) + + return ''.join(tokens) + + +class Unit: + def __init__(self, + si_scaling_factor: float, + dimensions: Dimensions): + + self.scale = si_scaling_factor + self.dimensions = dimensions + + def _components(self, tokens: Sequence["UnitToken"]): + pass + + def __mul__(self: Self, other: "Unit"): + if not isinstance(other, Unit): + return NotImplemented + + return Unit(self.scale * other.scale, self.dimensions * other.dimensions) + + def __truediv__(self: Self, other: "Unit"): + if not isinstance(other, Unit): + return NotImplemented + + return Unit(self.scale / other.scale, self.dimensions / other.dimensions) + + def __rtruediv__(self: Self, other: "Unit"): + if isinstance(other, Unit): + return Unit(other.scale / self.scale, other.dimensions / self.dimensions) + elif isinstance(other, (int, float)): + return Unit(other / self.scale, self.dimensions ** -1) + else: + return NotImplemented + + def __pow__(self, power: int | float): + if not isinstance(power, int | float): + return NotImplemented + + return Unit(self.scale**power, self.dimensions**power) + + + def equivalent(self: Self, other: "Unit"): + return self.dimensions == other.dimensions + + def __eq__(self: Self, other: "Unit"): + return self.equivalent(other) and np.abs(np.log(self.scale/other.scale)) < 1e-5 + + def si_equivalent(self): + """ Get the SI unit corresponding to this unit""" + return Unit(1, self.dimensions) + + def _format_unit(self, format_process: list["UnitFormatProcessor"]): + for processor in format_process: + pass + + def __repr__(self): + if self.scale == 1: + # We're in SI + return self.dimensions.si_repr() + + else: + return f"Unit[{self.scale}, {self.dimensions}]" + + @staticmethod + def parse(unit_string: str) -> "Unit": + pass + +class NamedUnit(Unit): + """ Units, but they have a name, and a symbol + + :si_scaling_factor: Number of these units per SI equivalent + :param dimensions: Dimensions object representing the dimensionality of these units + :param name: Name of unit - string without unicode + :param ascii_symbol: Symbol for unit without unicode + :param symbol: Unicode symbol + """ + def __init__(self, + si_scaling_factor: float, + dimensions: Dimensions, + name: str | None = None, + ascii_symbol: str | None = None, + latex_symbol: str | None = None, + symbol: str | None = None): + + super().__init__(si_scaling_factor, dimensions) + self.name = name + self.ascii_symbol = ascii_symbol + self.symbol = symbol + self.latex_symbol = latex_symbol if latex_symbol is not None else ascii_symbol + + def __repr__(self): + return self.name + + def __eq__(self, other): + """Match other units exactly or match strings against ANY of our names""" + match other: + case str(): + return self.name == other or self.name == f"{other}s" or self.ascii_symbol == other or self.symbol == other + case NamedUnit(): + return self.name == other.name \ + and self.ascii_symbol == other.ascii_symbol and self.symbol == other.symbol + case Unit(): + return self.equivalent(other) and np.abs(np.log(self.scale/other.scale)) < 1e-5 + case _: + return False + + + def startswith(self, prefix: str) -> bool: + """Check if any representation of the unit begins with the prefix string""" + prefix = prefix.lower() + return (self.name is not None and self.name.lower().startswith(prefix)) \ + or (self.ascii_symbol is not None and self.ascii_symbol.lower().startswith(prefix)) \ + or (self.symbol is not None and self.symbol.lower().startswith(prefix)) + +# +# Parsing plan: +# Require unknown amounts of units to be explicitly positive or negative? +# +# + + + +@dataclass +class ProcessedUnitToken: + """ Mid processing representation of formatted units """ + base_string: str + exponent_string: str + latex_exponent_string: str + exponent: int + +class UnitFormatProcessor: + """ Represents a step in the unit processing pipeline""" + def apply(self, scale, dimensions) -> tuple[ProcessedUnitToken, float, Dimensions]: + """ This will be called to deal with each processing stage""" + +class RequiredUnitFormatProcessor(UnitFormatProcessor): + """ This unit is required to exist in the formatting """ + def __init__(self, unit: Unit, power: int = 1): + self.unit = unit + self.power = power + def apply(self, scale, dimensions) -> tuple[float, Dimensions, ProcessedUnitToken]: + new_scale = scale / (self.unit.scale * self.power) + new_dimensions = self.unit.dimensions / (dimensions**self.power) + token = ProcessedUnitToken(self.unit, self.power) + + return new_scale, new_dimensions, token +class GreedyAbsDimensionUnitFormatProcessor(UnitFormatProcessor): + """ This processor minimises the dimensionality of the unit by multiplying by as many + units of the specified type as needed """ + def __init__(self, unit: Unit): + self.unit = unit + + def apply(self, scale, dimensions) -> tuple[ProcessedUnitToken, float, Dimensions]: + pass + +class GreedyAbsDimensionUnitFormatProcessor(UnitFormatProcessor): + pass + +class UnitGroup: + """ A group of units that all have the same dimensionality """ + def __init__(self, name: str, units: list[NamedUnit]): + self.name = name + self.units = sorted(units, key=lambda unit: unit.scale) + diff --git a/sasdata/quantities/absolute_temperature.py b/sasdata/quantities/absolute_temperature.py new file mode 100644 index 00000000..ecfd0e6d --- /dev/null +++ b/sasdata/quantities/absolute_temperature.py @@ -0,0 +1,15 @@ +from typing import TypeVar + +from sasdata.quantities.quantity import Quantity +from sasdata.quantities.accessors import TemperatureAccessor + + +DataType = TypeVar("DataType") +class AbsoluteTemperatureAccessor(TemperatureAccessor[DataType]): + """ Parsing for absolute temperatures """ + @property + def value(self) -> Quantity[DataType] | None: + if self._numerical_part() is None: + return None + else: + return Quantity.parse(self._numerical_part(), self._unit_part(), absolute_temperature=True) diff --git a/sasdata/quantities/accessors.py b/sasdata/quantities/accessors.py new file mode 100644 index 00000000..0ba0fe9e --- /dev/null +++ b/sasdata/quantities/accessors.py @@ -0,0 +1,10729 @@ +""" + +This file is autogenerated! + +Do not edit by hand, instead edit the files that build it (_build_tables.py, _accessor_base.py) + + + + +DDDDDDDDDDDDD NNNNNNNN NNNNNNNN tttt +D::::::::::::DDD N:::::::N N::::::N ttt:::t +D:::::::::::::::DD N::::::::N N::::::N t:::::t +DDD:::::DDDDD:::::D N:::::::::N N::::::N t:::::t + D:::::D D:::::D ooooooooooo N::::::::::N N::::::N ooooooooooo ttttttt:::::ttttttt + D:::::D D:::::D oo:::::::::::oo N:::::::::::N N::::::N oo:::::::::::oo t:::::::::::::::::t + D:::::D D:::::Do:::::::::::::::o N:::::::N::::N N::::::No:::::::::::::::ot:::::::::::::::::t + D:::::D D:::::Do:::::ooooo:::::o N::::::N N::::N N::::::No:::::ooooo:::::otttttt:::::::tttttt + D:::::D D:::::Do::::o o::::o N::::::N N::::N:::::::No::::o o::::o t:::::t + D:::::D D:::::Do::::o o::::o N::::::N N:::::::::::No::::o o::::o t:::::t + D:::::D D:::::Do::::o o::::o N::::::N N::::::::::No::::o o::::o t:::::t + D:::::D D:::::D o::::o o::::o N::::::N N:::::::::No::::o o::::o t:::::t tttttt +DDD:::::DDDDD:::::D o:::::ooooo:::::o N::::::N N::::::::No:::::ooooo:::::o t::::::tttt:::::t +D:::::::::::::::DD o:::::::::::::::o N::::::N N:::::::No:::::::::::::::o tt::::::::::::::t +D::::::::::::DDD oo:::::::::::oo N::::::N N::::::N oo:::::::::::oo tt:::::::::::tt +DDDDDDDDDDDDD ooooooooooo NNNNNNNN NNNNNNN ooooooooooo ttttttttttt + + + + + + + + + dddddddd +EEEEEEEEEEEEEEEEEEEEEE d::::::d iiii tttt BBBBBBBBBBBBBBBBB +E::::::::::::::::::::E d::::::d i::::i ttt:::t B::::::::::::::::B +E::::::::::::::::::::E d::::::d iiii t:::::t B::::::BBBBBB:::::B +EE::::::EEEEEEEEE::::E d:::::d t:::::t BB:::::B B:::::B + E:::::E EEEEEE ddddddddd:::::d iiiiiiittttttt:::::ttttttt B::::B B:::::Byyyyyyy yyyyyyy + E:::::E dd::::::::::::::d i:::::it:::::::::::::::::t B::::B B:::::B y:::::y y:::::y + E::::::EEEEEEEEEE d::::::::::::::::d i::::it:::::::::::::::::t B::::BBBBBB:::::B y:::::y y:::::y + E:::::::::::::::E d:::::::ddddd:::::d i::::itttttt:::::::tttttt B:::::::::::::BB y:::::y y:::::y + E:::::::::::::::E d::::::d d:::::d i::::i t:::::t B::::BBBBBB:::::B y:::::y y:::::y + E::::::EEEEEEEEEE d:::::d d:::::d i::::i t:::::t B::::B B:::::B y:::::y y:::::y + E:::::E d:::::d d:::::d i::::i t:::::t B::::B B:::::B y:::::y:::::y + E:::::E EEEEEEd:::::d d:::::d i::::i t:::::t tttttt B::::B B:::::B y:::::::::y +EE::::::EEEEEEEE:::::Ed::::::ddddd::::::ddi::::::i t::::::tttt:::::t BB:::::BBBBBB::::::B y:::::::y +E::::::::::::::::::::E d:::::::::::::::::di::::::i tt::::::::::::::t B:::::::::::::::::B y:::::y +E::::::::::::::::::::E d:::::::::ddd::::di::::::i tt:::::::::::tt B::::::::::::::::B y:::::y +EEEEEEEEEEEEEEEEEEEEEE ddddddddd dddddiiiiiiii ttttttttttt BBBBBBBBBBBBBBBBB y:::::y + y:::::y + y:::::y + y:::::y + y:::::y + yyyyyyy + + + + dddddddd +HHHHHHHHH HHHHHHHHH d::::::d +H:::::::H H:::::::H d::::::d +H:::::::H H:::::::H d::::::d +HH::::::H H::::::HH d:::::d + H:::::H H:::::H aaaaaaaaaaaaa nnnn nnnnnnnn ddddddddd:::::d + H:::::H H:::::H a::::::::::::a n:::nn::::::::nn dd::::::::::::::d + H::::::HHHHH::::::H aaaaaaaaa:::::an::::::::::::::nn d::::::::::::::::d + H:::::::::::::::::H a::::ann:::::::::::::::nd:::::::ddddd:::::d + H:::::::::::::::::H aaaaaaa:::::a n:::::nnnn:::::nd::::::d d:::::d + H::::::HHHHH::::::H aa::::::::::::a n::::n n::::nd:::::d d:::::d + H:::::H H:::::H a::::aaaa::::::a n::::n n::::nd:::::d d:::::d + H:::::H H:::::H a::::a a:::::a n::::n n::::nd:::::d d:::::d +HH::::::H H::::::HHa::::a a:::::a n::::n n::::nd::::::ddddd::::::dd +H:::::::H H:::::::Ha:::::aaaa::::::a n::::n n::::n d:::::::::::::::::d +H:::::::H H:::::::H a::::::::::aa:::a n::::n n::::n d:::::::::ddd::::d +HHHHHHHHH HHHHHHHHH aaaaaaaaaa aaaa nnnnnn nnnnnn ddddddddd ddddd + + + +""" + +from typing import TypeVar + +from sasdata.quantities.quantity import Quantity +import sasdata.quantities.units as units +from sasdata.quantities.units import Unit +from sasdata.quantities.unit_parser import parse_unit + +from sasdata.data_backing import Group, Dataset + +# logger = logging.getLogger("Accessors") +class LoggerDummy: + def info(self, data): + print(data) +logger = LoggerDummy() + +DataType = TypeVar("DataType") +OutputType = TypeVar("OutputType") + + +class AccessorTarget: + def __init__(self, data: Group, verbose=False, prefix_tokens: tuple=()): + self._data = data + self.verbose = verbose + + self.prefix_tokens = list(prefix_tokens) + + def with_path_prefix(self, path_prexix: str): + """ Get an accessor that looks at a subtree of the metadata with the supplied prefix + + For example, accessors aiming at a.b, when the target it c.d will look at c.d.a.b + """ + return AccessorTarget(self._data, + verbose=self.verbose, + prefix_tokens=tuple(self.prefix_tokens + [path_prexix])) + + def get_value(self, path: str): + + tokens = self.prefix_tokens + path.split(".") + + if self.verbose: + logger.info(f"Finding: {path}") + logger.info(f"Full path: {tokens}") + + # Navigate the tree from the entry we need + + current_tree_position: Group | Dataset = self._data + + for token in tokens: + + options = token.split("|") + + if isinstance(current_tree_position, Group): + + found = False + for option in options: + if option in current_tree_position.children: + current_tree_position = current_tree_position.children[option] + found = True + + if self.verbose: + logger.info(f"Found option: {option}") + + if not found: + if self.verbose: + logger.info(f"Failed to find any of {options} on group {current_tree_position.name}. Options: " + + ",".join([key for key in current_tree_position.children])) + return None + + elif isinstance(current_tree_position, Dataset): + + found = False + for option in options: + if option in current_tree_position.attributes: + current_tree_position = current_tree_position.attributes[option] + found = True + + if self.verbose: + logger.info(f"Found option: {option}") + + if not found: + if self.verbose: + logger.info(f"Failed to find any of {options} on attribute {current_tree_position.name}. Options: " + + ",".join([key for key in current_tree_position.attributes])) + return None + + if self.verbose: + logger.info(f"Found value: {current_tree_position}") + + return current_tree_position.data + + + +class Accessor[DataType, OutputType]: + """ Base class """ + def __init__(self, target_object: AccessorTarget, value_target: str): + self.target_object = target_object + self.value_target = value_target + + @property + def value(self) -> OutputType | None: + return self.target_object.get_value(self.value_target) + +class StringAccessor(Accessor[str, str]): + """ String based fields """ + @property + def value(self) -> str | None: + return self.target_object.get_value(self.value_target) + +class FloatAccessor(Accessor[float, float]): + """ Float based fields """ + @property + def value(self) -> float | None: + return self.target_object.get_value(self.value_target) + + + + +class QuantityAccessor[DataType](Accessor[DataType, Quantity[DataType]]): + """ Base class for accessors that work with quantities that have units """ + def __init__(self, target_object: AccessorTarget, value_target: str, unit_target: str, default_unit=units.none): + super().__init__(target_object, value_target) + self._unit_target = unit_target + self.default_unit = default_unit + + def _numerical_part(self) -> DataType | None: + """ Numerical part of the data """ + return self.target_object.get_value(self.value_target) + + def _unit_part(self) -> str | None: + """ String form of units for the data """ + return self.target_object.get_value(self._unit_target) + + @property + def unit(self) -> Unit: + u = self._unit_part() + if u is None: + return self.default_unit + else: + return parse_unit(u) + + @property + def value(self) -> Quantity[DataType] | None: + if self._unit_part() is not None and self._numerical_part() is not None: + return Quantity(self._numerical_part(), self.unit) + return None + + @property + def quantity(self): + if self._unit_part() is not None and self._numerical_part() is not None: + return Quantity(self._numerical_part(), self.unit) + return None + + +class LengthAccessor[T](QuantityAccessor[T]): + dimension_name = 'length' + + @property + def meters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters) + + @property + def exameters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters) + + @property + def petameters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters) + + @property + def terameters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters) + + @property + def gigameters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters) + + @property + def megameters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters) + + @property + def kilometers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers) + + @property + def millimeters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters) + + @property + def micrometers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers) + + @property + def nanometers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers) + + @property + def picometers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers) + + @property + def femtometers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers) + + @property + def attometers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers) + + @property + def decimeters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters) + + @property + def centimeters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters) + + @property + def angstroms(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms) + + @property + def microns(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns) + + @property + def miles(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles) + + @property + def yards(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards) + + @property + def feet(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet) + + @property + def inches(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches) + + + +class AreaAccessor[T](QuantityAccessor[T]): + dimension_name = 'area' + + @property + def square_meters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_meters) + + @property + def square_exameters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_exameters) + + @property + def square_petameters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_petameters) + + @property + def square_terameters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_terameters) + + @property + def square_gigameters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_gigameters) + + @property + def square_megameters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_megameters) + + @property + def square_kilometers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_kilometers) + + @property + def square_millimeters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_millimeters) + + @property + def square_micrometers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_micrometers) + + @property + def square_nanometers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_nanometers) + + @property + def square_picometers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_picometers) + + @property + def square_femtometers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_femtometers) + + @property + def square_attometers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_attometers) + + @property + def square_decimeters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_decimeters) + + @property + def square_centimeters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_centimeters) + + @property + def square_angstroms(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_angstroms) + + @property + def square_microns(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_microns) + + @property + def square_miles(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_miles) + + @property + def square_yards(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_yards) + + @property + def square_feet(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_feet) + + @property + def square_inches(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.square_inches) + + + +class VolumeAccessor[T](QuantityAccessor[T]): + dimension_name = 'volume' + + @property + def litres(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.litres) + + @property + def cubic_meters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_meters) + + @property + def cubic_exameters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_exameters) + + @property + def cubic_petameters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_petameters) + + @property + def cubic_terameters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_terameters) + + @property + def cubic_gigameters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_gigameters) + + @property + def cubic_megameters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_megameters) + + @property + def cubic_kilometers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_kilometers) + + @property + def cubic_millimeters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_millimeters) + + @property + def cubic_micrometers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_micrometers) + + @property + def cubic_nanometers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_nanometers) + + @property + def cubic_picometers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_picometers) + + @property + def cubic_femtometers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_femtometers) + + @property + def cubic_attometers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_attometers) + + @property + def cubic_decimeters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_decimeters) + + @property + def cubic_centimeters(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_centimeters) + + @property + def cubic_angstroms(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_angstroms) + + @property + def cubic_microns(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_microns) + + @property + def cubic_miles(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_miles) + + @property + def cubic_yards(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_yards) + + @property + def cubic_feet(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_feet) + + @property + def cubic_inches(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.cubic_inches) + + + +class InverselengthAccessor[T](QuantityAccessor[T]): + dimension_name = 'inverse_length' + + @property + def per_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_meter) + + @property + def per_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_exameter) + + @property + def per_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_petameter) + + @property + def per_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_terameter) + + @property + def per_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_gigameter) + + @property + def per_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_megameter) + + @property + def per_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_kilometer) + + @property + def per_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_millimeter) + + @property + def per_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_micrometer) + + @property + def per_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_nanometer) + + @property + def per_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_picometer) + + @property + def per_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_femtometer) + + @property + def per_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_attometer) + + @property + def per_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_decimeter) + + @property + def per_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_centimeter) + + @property + def per_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_angstrom) + + @property + def per_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_micron) + + @property + def per_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_mile) + + @property + def per_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_yard) + + @property + def per_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_foot) + + @property + def per_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_inch) + + + +class InverseareaAccessor[T](QuantityAccessor[T]): + dimension_name = 'inverse_area' + + @property + def per_square_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_meter) + + @property + def per_square_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_exameter) + + @property + def per_square_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_petameter) + + @property + def per_square_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_terameter) + + @property + def per_square_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_gigameter) + + @property + def per_square_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_megameter) + + @property + def per_square_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_kilometer) + + @property + def per_square_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_millimeter) + + @property + def per_square_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_micrometer) + + @property + def per_square_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_nanometer) + + @property + def per_square_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_picometer) + + @property + def per_square_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_femtometer) + + @property + def per_square_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_attometer) + + @property + def per_square_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_decimeter) + + @property + def per_square_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_centimeter) + + @property + def per_square_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_angstrom) + + @property + def per_square_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_micron) + + @property + def per_square_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_mile) + + @property + def per_square_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_yard) + + @property + def per_square_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_foot) + + @property + def per_square_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_square_inch) + + + +class InversevolumeAccessor[T](QuantityAccessor[T]): + dimension_name = 'inverse_volume' + + @property + def per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_meter) + + @property + def per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_exameter) + + @property + def per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_petameter) + + @property + def per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_terameter) + + @property + def per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_gigameter) + + @property + def per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_megameter) + + @property + def per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_kilometer) + + @property + def per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_millimeter) + + @property + def per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_micrometer) + + @property + def per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_nanometer) + + @property + def per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_picometer) + + @property + def per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_femtometer) + + @property + def per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_attometer) + + @property + def per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_decimeter) + + @property + def per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_centimeter) + + @property + def per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_angstrom) + + @property + def per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_micron) + + @property + def per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_mile) + + @property + def per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_yard) + + @property + def per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_foot) + + @property + def per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.per_cubic_inch) + + + +class TimeAccessor[T](QuantityAccessor[T]): + dimension_name = 'time' + + @property + def seconds(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.seconds) + + @property + def milliseconds(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milliseconds) + + @property + def microseconds(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microseconds) + + @property + def nanoseconds(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanoseconds) + + @property + def picoseconds(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picoseconds) + + @property + def femtoseconds(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtoseconds) + + @property + def attoseconds(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attoseconds) + + @property + def minutes(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.minutes) + + @property + def hours(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.hours) + + @property + def days(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.days) + + @property + def years(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.years) + + + +class RateAccessor[T](QuantityAccessor[T]): + dimension_name = 'rate' + + @property + def hertz(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.hertz) + + @property + def exahertz(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exahertz) + + @property + def petahertz(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petahertz) + + @property + def terahertz(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terahertz) + + @property + def gigahertz(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigahertz) + + @property + def megahertz(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megahertz) + + @property + def kilohertz(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilohertz) + + @property + def millihertz(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millihertz) + + @property + def microhertz(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microhertz) + + @property + def nanohertz(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanohertz) + + @property + def picohertz(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picohertz) + + @property + def femtohertz(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtohertz) + + @property + def attohertz(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attohertz) + + + +class SpeedAccessor[T](QuantityAccessor[T]): + dimension_name = 'speed' + + @property + def meters_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_second) + + @property + def meters_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_millisecond) + + @property + def meters_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_microsecond) + + @property + def meters_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_nanosecond) + + @property + def meters_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_picosecond) + + @property + def meters_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_femtosecond) + + @property + def meters_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_attosecond) + + @property + def meters_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_minute) + + @property + def meters_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_hour) + + @property + def meters_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_day) + + @property + def meters_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_year) + + @property + def exameters_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_second) + + @property + def exameters_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_millisecond) + + @property + def exameters_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_microsecond) + + @property + def exameters_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_nanosecond) + + @property + def exameters_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_picosecond) + + @property + def exameters_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_femtosecond) + + @property + def exameters_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_attosecond) + + @property + def exameters_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_minute) + + @property + def exameters_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_hour) + + @property + def exameters_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_day) + + @property + def exameters_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_year) + + @property + def petameters_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_second) + + @property + def petameters_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_millisecond) + + @property + def petameters_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_microsecond) + + @property + def petameters_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_nanosecond) + + @property + def petameters_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_picosecond) + + @property + def petameters_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_femtosecond) + + @property + def petameters_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_attosecond) + + @property + def petameters_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_minute) + + @property + def petameters_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_hour) + + @property + def petameters_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_day) + + @property + def petameters_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_year) + + @property + def terameters_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_second) + + @property + def terameters_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_millisecond) + + @property + def terameters_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_microsecond) + + @property + def terameters_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_nanosecond) + + @property + def terameters_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_picosecond) + + @property + def terameters_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_femtosecond) + + @property + def terameters_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_attosecond) + + @property + def terameters_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_minute) + + @property + def terameters_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_hour) + + @property + def terameters_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_day) + + @property + def terameters_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_year) + + @property + def gigameters_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_second) + + @property + def gigameters_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_millisecond) + + @property + def gigameters_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_microsecond) + + @property + def gigameters_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_nanosecond) + + @property + def gigameters_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_picosecond) + + @property + def gigameters_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_femtosecond) + + @property + def gigameters_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_attosecond) + + @property + def gigameters_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_minute) + + @property + def gigameters_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_hour) + + @property + def gigameters_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_day) + + @property + def gigameters_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_year) + + @property + def megameters_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_second) + + @property + def megameters_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_millisecond) + + @property + def megameters_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_microsecond) + + @property + def megameters_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_nanosecond) + + @property + def megameters_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_picosecond) + + @property + def megameters_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_femtosecond) + + @property + def megameters_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_attosecond) + + @property + def megameters_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_minute) + + @property + def megameters_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_hour) + + @property + def megameters_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_day) + + @property + def megameters_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_year) + + @property + def kilometers_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_second) + + @property + def kilometers_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_millisecond) + + @property + def kilometers_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_microsecond) + + @property + def kilometers_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_nanosecond) + + @property + def kilometers_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_picosecond) + + @property + def kilometers_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_femtosecond) + + @property + def kilometers_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_attosecond) + + @property + def kilometers_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_minute) + + @property + def kilometers_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_hour) + + @property + def kilometers_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_day) + + @property + def kilometers_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_year) + + @property + def millimeters_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_second) + + @property + def millimeters_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_millisecond) + + @property + def millimeters_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_microsecond) + + @property + def millimeters_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_nanosecond) + + @property + def millimeters_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_picosecond) + + @property + def millimeters_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_femtosecond) + + @property + def millimeters_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_attosecond) + + @property + def millimeters_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_minute) + + @property + def millimeters_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_hour) + + @property + def millimeters_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_day) + + @property + def millimeters_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_year) + + @property + def micrometers_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_second) + + @property + def micrometers_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_millisecond) + + @property + def micrometers_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_microsecond) + + @property + def micrometers_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_nanosecond) + + @property + def micrometers_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_picosecond) + + @property + def micrometers_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_femtosecond) + + @property + def micrometers_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_attosecond) + + @property + def micrometers_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_minute) + + @property + def micrometers_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_hour) + + @property + def micrometers_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_day) + + @property + def micrometers_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_year) + + @property + def nanometers_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_second) + + @property + def nanometers_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_millisecond) + + @property + def nanometers_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_microsecond) + + @property + def nanometers_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_nanosecond) + + @property + def nanometers_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_picosecond) + + @property + def nanometers_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_femtosecond) + + @property + def nanometers_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_attosecond) + + @property + def nanometers_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_minute) + + @property + def nanometers_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_hour) + + @property + def nanometers_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_day) + + @property + def nanometers_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_year) + + @property + def picometers_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_second) + + @property + def picometers_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_millisecond) + + @property + def picometers_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_microsecond) + + @property + def picometers_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_nanosecond) + + @property + def picometers_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_picosecond) + + @property + def picometers_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_femtosecond) + + @property + def picometers_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_attosecond) + + @property + def picometers_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_minute) + + @property + def picometers_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_hour) + + @property + def picometers_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_day) + + @property + def picometers_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_year) + + @property + def femtometers_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_second) + + @property + def femtometers_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_millisecond) + + @property + def femtometers_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_microsecond) + + @property + def femtometers_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_nanosecond) + + @property + def femtometers_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_picosecond) + + @property + def femtometers_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_femtosecond) + + @property + def femtometers_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_attosecond) + + @property + def femtometers_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_minute) + + @property + def femtometers_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_hour) + + @property + def femtometers_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_day) + + @property + def femtometers_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_year) + + @property + def attometers_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_second) + + @property + def attometers_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_millisecond) + + @property + def attometers_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_microsecond) + + @property + def attometers_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_nanosecond) + + @property + def attometers_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_picosecond) + + @property + def attometers_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_femtosecond) + + @property + def attometers_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_attosecond) + + @property + def attometers_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_minute) + + @property + def attometers_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_hour) + + @property + def attometers_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_day) + + @property + def attometers_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_year) + + @property + def decimeters_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_second) + + @property + def decimeters_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_millisecond) + + @property + def decimeters_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_microsecond) + + @property + def decimeters_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_nanosecond) + + @property + def decimeters_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_picosecond) + + @property + def decimeters_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_femtosecond) + + @property + def decimeters_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_attosecond) + + @property + def decimeters_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_minute) + + @property + def decimeters_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_hour) + + @property + def decimeters_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_day) + + @property + def decimeters_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_year) + + @property + def centimeters_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_second) + + @property + def centimeters_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_millisecond) + + @property + def centimeters_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_microsecond) + + @property + def centimeters_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_nanosecond) + + @property + def centimeters_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_picosecond) + + @property + def centimeters_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_femtosecond) + + @property + def centimeters_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_attosecond) + + @property + def centimeters_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_minute) + + @property + def centimeters_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_hour) + + @property + def centimeters_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_day) + + @property + def centimeters_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_year) + + @property + def angstroms_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_second) + + @property + def angstroms_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_millisecond) + + @property + def angstroms_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_microsecond) + + @property + def angstroms_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_nanosecond) + + @property + def angstroms_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_picosecond) + + @property + def angstroms_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_femtosecond) + + @property + def angstroms_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_attosecond) + + @property + def angstroms_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_minute) + + @property + def angstroms_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_hour) + + @property + def angstroms_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_day) + + @property + def angstroms_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_year) + + @property + def microns_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_second) + + @property + def microns_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_millisecond) + + @property + def microns_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_microsecond) + + @property + def microns_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_nanosecond) + + @property + def microns_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_picosecond) + + @property + def microns_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_femtosecond) + + @property + def microns_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_attosecond) + + @property + def microns_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_minute) + + @property + def microns_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_hour) + + @property + def microns_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_day) + + @property + def microns_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_year) + + @property + def miles_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_second) + + @property + def miles_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_millisecond) + + @property + def miles_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_microsecond) + + @property + def miles_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_nanosecond) + + @property + def miles_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_picosecond) + + @property + def miles_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_femtosecond) + + @property + def miles_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_attosecond) + + @property + def miles_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_minute) + + @property + def miles_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_hour) + + @property + def miles_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_day) + + @property + def miles_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_year) + + @property + def yards_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_second) + + @property + def yards_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_millisecond) + + @property + def yards_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_microsecond) + + @property + def yards_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_nanosecond) + + @property + def yards_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_picosecond) + + @property + def yards_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_femtosecond) + + @property + def yards_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_attosecond) + + @property + def yards_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_minute) + + @property + def yards_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_hour) + + @property + def yards_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_day) + + @property + def yards_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_year) + + @property + def feet_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_second) + + @property + def feet_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_millisecond) + + @property + def feet_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_microsecond) + + @property + def feet_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_nanosecond) + + @property + def feet_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_picosecond) + + @property + def feet_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_femtosecond) + + @property + def feet_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_attosecond) + + @property + def feet_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_minute) + + @property + def feet_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_hour) + + @property + def feet_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_day) + + @property + def feet_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_year) + + @property + def inches_per_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_second) + + @property + def inches_per_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_millisecond) + + @property + def inches_per_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_microsecond) + + @property + def inches_per_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_nanosecond) + + @property + def inches_per_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_picosecond) + + @property + def inches_per_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_femtosecond) + + @property + def inches_per_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_attosecond) + + @property + def inches_per_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_minute) + + @property + def inches_per_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_hour) + + @property + def inches_per_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_day) + + @property + def inches_per_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_year) + + + +class AccelerationAccessor[T](QuantityAccessor[T]): + dimension_name = 'acceleration' + + @property + def meters_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_square_second) + + @property + def meters_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_square_millisecond) + + @property + def meters_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_square_microsecond) + + @property + def meters_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_square_nanosecond) + + @property + def meters_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_square_picosecond) + + @property + def meters_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_square_femtosecond) + + @property + def meters_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_square_attosecond) + + @property + def meters_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_square_minute) + + @property + def meters_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_square_hour) + + @property + def meters_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_square_day) + + @property + def meters_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meters_per_square_year) + + @property + def exameters_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_square_second) + + @property + def exameters_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_square_millisecond) + + @property + def exameters_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_square_microsecond) + + @property + def exameters_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_square_nanosecond) + + @property + def exameters_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_square_picosecond) + + @property + def exameters_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_square_femtosecond) + + @property + def exameters_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_square_attosecond) + + @property + def exameters_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_square_minute) + + @property + def exameters_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_square_hour) + + @property + def exameters_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_square_day) + + @property + def exameters_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exameters_per_square_year) + + @property + def petameters_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_square_second) + + @property + def petameters_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_square_millisecond) + + @property + def petameters_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_square_microsecond) + + @property + def petameters_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_square_nanosecond) + + @property + def petameters_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_square_picosecond) + + @property + def petameters_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_square_femtosecond) + + @property + def petameters_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_square_attosecond) + + @property + def petameters_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_square_minute) + + @property + def petameters_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_square_hour) + + @property + def petameters_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_square_day) + + @property + def petameters_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petameters_per_square_year) + + @property + def terameters_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_square_second) + + @property + def terameters_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_square_millisecond) + + @property + def terameters_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_square_microsecond) + + @property + def terameters_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_square_nanosecond) + + @property + def terameters_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_square_picosecond) + + @property + def terameters_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_square_femtosecond) + + @property + def terameters_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_square_attosecond) + + @property + def terameters_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_square_minute) + + @property + def terameters_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_square_hour) + + @property + def terameters_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_square_day) + + @property + def terameters_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terameters_per_square_year) + + @property + def gigameters_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_square_second) + + @property + def gigameters_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_square_millisecond) + + @property + def gigameters_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_square_microsecond) + + @property + def gigameters_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_square_nanosecond) + + @property + def gigameters_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_square_picosecond) + + @property + def gigameters_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_square_femtosecond) + + @property + def gigameters_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_square_attosecond) + + @property + def gigameters_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_square_minute) + + @property + def gigameters_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_square_hour) + + @property + def gigameters_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_square_day) + + @property + def gigameters_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigameters_per_square_year) + + @property + def megameters_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_square_second) + + @property + def megameters_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_square_millisecond) + + @property + def megameters_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_square_microsecond) + + @property + def megameters_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_square_nanosecond) + + @property + def megameters_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_square_picosecond) + + @property + def megameters_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_square_femtosecond) + + @property + def megameters_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_square_attosecond) + + @property + def megameters_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_square_minute) + + @property + def megameters_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_square_hour) + + @property + def megameters_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_square_day) + + @property + def megameters_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megameters_per_square_year) + + @property + def kilometers_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_square_second) + + @property + def kilometers_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_square_millisecond) + + @property + def kilometers_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_square_microsecond) + + @property + def kilometers_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_square_nanosecond) + + @property + def kilometers_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_square_picosecond) + + @property + def kilometers_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_square_femtosecond) + + @property + def kilometers_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_square_attosecond) + + @property + def kilometers_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_square_minute) + + @property + def kilometers_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_square_hour) + + @property + def kilometers_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_square_day) + + @property + def kilometers_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilometers_per_square_year) + + @property + def millimeters_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_square_second) + + @property + def millimeters_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_square_millisecond) + + @property + def millimeters_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_square_microsecond) + + @property + def millimeters_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_square_nanosecond) + + @property + def millimeters_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_square_picosecond) + + @property + def millimeters_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_square_femtosecond) + + @property + def millimeters_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_square_attosecond) + + @property + def millimeters_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_square_minute) + + @property + def millimeters_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_square_hour) + + @property + def millimeters_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_square_day) + + @property + def millimeters_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimeters_per_square_year) + + @property + def micrometers_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_square_second) + + @property + def micrometers_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_square_millisecond) + + @property + def micrometers_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_square_microsecond) + + @property + def micrometers_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_square_nanosecond) + + @property + def micrometers_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_square_picosecond) + + @property + def micrometers_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_square_femtosecond) + + @property + def micrometers_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_square_attosecond) + + @property + def micrometers_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_square_minute) + + @property + def micrometers_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_square_hour) + + @property + def micrometers_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_square_day) + + @property + def micrometers_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrometers_per_square_year) + + @property + def nanometers_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_square_second) + + @property + def nanometers_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_square_millisecond) + + @property + def nanometers_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_square_microsecond) + + @property + def nanometers_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_square_nanosecond) + + @property + def nanometers_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_square_picosecond) + + @property + def nanometers_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_square_femtosecond) + + @property + def nanometers_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_square_attosecond) + + @property + def nanometers_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_square_minute) + + @property + def nanometers_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_square_hour) + + @property + def nanometers_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_square_day) + + @property + def nanometers_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanometers_per_square_year) + + @property + def picometers_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_square_second) + + @property + def picometers_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_square_millisecond) + + @property + def picometers_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_square_microsecond) + + @property + def picometers_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_square_nanosecond) + + @property + def picometers_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_square_picosecond) + + @property + def picometers_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_square_femtosecond) + + @property + def picometers_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_square_attosecond) + + @property + def picometers_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_square_minute) + + @property + def picometers_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_square_hour) + + @property + def picometers_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_square_day) + + @property + def picometers_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picometers_per_square_year) + + @property + def femtometers_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_square_second) + + @property + def femtometers_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_square_millisecond) + + @property + def femtometers_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_square_microsecond) + + @property + def femtometers_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_square_nanosecond) + + @property + def femtometers_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_square_picosecond) + + @property + def femtometers_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_square_femtosecond) + + @property + def femtometers_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_square_attosecond) + + @property + def femtometers_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_square_minute) + + @property + def femtometers_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_square_hour) + + @property + def femtometers_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_square_day) + + @property + def femtometers_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtometers_per_square_year) + + @property + def attometers_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_square_second) + + @property + def attometers_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_square_millisecond) + + @property + def attometers_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_square_microsecond) + + @property + def attometers_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_square_nanosecond) + + @property + def attometers_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_square_picosecond) + + @property + def attometers_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_square_femtosecond) + + @property + def attometers_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_square_attosecond) + + @property + def attometers_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_square_minute) + + @property + def attometers_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_square_hour) + + @property + def attometers_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_square_day) + + @property + def attometers_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attometers_per_square_year) + + @property + def decimeters_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_square_second) + + @property + def decimeters_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_square_millisecond) + + @property + def decimeters_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_square_microsecond) + + @property + def decimeters_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_square_nanosecond) + + @property + def decimeters_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_square_picosecond) + + @property + def decimeters_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_square_femtosecond) + + @property + def decimeters_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_square_attosecond) + + @property + def decimeters_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_square_minute) + + @property + def decimeters_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_square_hour) + + @property + def decimeters_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_square_day) + + @property + def decimeters_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.decimeters_per_square_year) + + @property + def centimeters_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_square_second) + + @property + def centimeters_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_square_millisecond) + + @property + def centimeters_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_square_microsecond) + + @property + def centimeters_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_square_nanosecond) + + @property + def centimeters_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_square_picosecond) + + @property + def centimeters_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_square_femtosecond) + + @property + def centimeters_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_square_attosecond) + + @property + def centimeters_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_square_minute) + + @property + def centimeters_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_square_hour) + + @property + def centimeters_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_square_day) + + @property + def centimeters_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.centimeters_per_square_year) + + @property + def angstroms_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_square_second) + + @property + def angstroms_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_square_millisecond) + + @property + def angstroms_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_square_microsecond) + + @property + def angstroms_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_square_nanosecond) + + @property + def angstroms_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_square_picosecond) + + @property + def angstroms_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_square_femtosecond) + + @property + def angstroms_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_square_attosecond) + + @property + def angstroms_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_square_minute) + + @property + def angstroms_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_square_hour) + + @property + def angstroms_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_square_day) + + @property + def angstroms_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.angstroms_per_square_year) + + @property + def microns_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_square_second) + + @property + def microns_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_square_millisecond) + + @property + def microns_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_square_microsecond) + + @property + def microns_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_square_nanosecond) + + @property + def microns_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_square_picosecond) + + @property + def microns_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_square_femtosecond) + + @property + def microns_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_square_attosecond) + + @property + def microns_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_square_minute) + + @property + def microns_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_square_hour) + + @property + def microns_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_square_day) + + @property + def microns_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microns_per_square_year) + + @property + def miles_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_square_second) + + @property + def miles_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_square_millisecond) + + @property + def miles_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_square_microsecond) + + @property + def miles_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_square_nanosecond) + + @property + def miles_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_square_picosecond) + + @property + def miles_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_square_femtosecond) + + @property + def miles_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_square_attosecond) + + @property + def miles_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_square_minute) + + @property + def miles_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_square_hour) + + @property + def miles_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_square_day) + + @property + def miles_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.miles_per_square_year) + + @property + def yards_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_square_second) + + @property + def yards_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_square_millisecond) + + @property + def yards_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_square_microsecond) + + @property + def yards_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_square_nanosecond) + + @property + def yards_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_square_picosecond) + + @property + def yards_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_square_femtosecond) + + @property + def yards_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_square_attosecond) + + @property + def yards_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_square_minute) + + @property + def yards_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_square_hour) + + @property + def yards_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_square_day) + + @property + def yards_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.yards_per_square_year) + + @property + def feet_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_square_second) + + @property + def feet_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_square_millisecond) + + @property + def feet_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_square_microsecond) + + @property + def feet_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_square_nanosecond) + + @property + def feet_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_square_picosecond) + + @property + def feet_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_square_femtosecond) + + @property + def feet_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_square_attosecond) + + @property + def feet_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_square_minute) + + @property + def feet_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_square_hour) + + @property + def feet_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_square_day) + + @property + def feet_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.feet_per_square_year) + + @property + def inches_per_square_second(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_square_second) + + @property + def inches_per_square_millisecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_square_millisecond) + + @property + def inches_per_square_microsecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_square_microsecond) + + @property + def inches_per_square_nanosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_square_nanosecond) + + @property + def inches_per_square_picosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_square_picosecond) + + @property + def inches_per_square_femtosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_square_femtosecond) + + @property + def inches_per_square_attosecond(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_square_attosecond) + + @property + def inches_per_square_minute(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_square_minute) + + @property + def inches_per_square_hour(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_square_hour) + + @property + def inches_per_square_day(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_square_day) + + @property + def inches_per_square_year(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.inches_per_square_year) + + + +class DensityAccessor[T](QuantityAccessor[T]): + dimension_name = 'density' + + @property + def grams_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_meter) + + @property + def exagrams_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_meter) + + @property + def petagrams_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_meter) + + @property + def teragrams_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_meter) + + @property + def gigagrams_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_meter) + + @property + def megagrams_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_meter) + + @property + def kilograms_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_meter) + + @property + def milligrams_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_meter) + + @property + def micrograms_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_meter) + + @property + def nanograms_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_meter) + + @property + def picograms_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_meter) + + @property + def femtograms_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_meter) + + @property + def attograms_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_meter) + + @property + def atomic_mass_units_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_meter) + + @property + def pounds_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_meter) + + @property + def ounces_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_meter) + + @property + def grams_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_exameter) + + @property + def exagrams_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_exameter) + + @property + def petagrams_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_exameter) + + @property + def teragrams_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_exameter) + + @property + def gigagrams_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_exameter) + + @property + def megagrams_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_exameter) + + @property + def kilograms_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_exameter) + + @property + def milligrams_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_exameter) + + @property + def micrograms_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_exameter) + + @property + def nanograms_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_exameter) + + @property + def picograms_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_exameter) + + @property + def femtograms_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_exameter) + + @property + def attograms_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_exameter) + + @property + def atomic_mass_units_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_exameter) + + @property + def pounds_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_exameter) + + @property + def ounces_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_exameter) + + @property + def grams_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_petameter) + + @property + def exagrams_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_petameter) + + @property + def petagrams_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_petameter) + + @property + def teragrams_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_petameter) + + @property + def gigagrams_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_petameter) + + @property + def megagrams_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_petameter) + + @property + def kilograms_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_petameter) + + @property + def milligrams_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_petameter) + + @property + def micrograms_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_petameter) + + @property + def nanograms_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_petameter) + + @property + def picograms_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_petameter) + + @property + def femtograms_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_petameter) + + @property + def attograms_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_petameter) + + @property + def atomic_mass_units_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_petameter) + + @property + def pounds_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_petameter) + + @property + def ounces_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_petameter) + + @property + def grams_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_terameter) + + @property + def exagrams_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_terameter) + + @property + def petagrams_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_terameter) + + @property + def teragrams_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_terameter) + + @property + def gigagrams_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_terameter) + + @property + def megagrams_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_terameter) + + @property + def kilograms_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_terameter) + + @property + def milligrams_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_terameter) + + @property + def micrograms_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_terameter) + + @property + def nanograms_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_terameter) + + @property + def picograms_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_terameter) + + @property + def femtograms_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_terameter) + + @property + def attograms_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_terameter) + + @property + def atomic_mass_units_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_terameter) + + @property + def pounds_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_terameter) + + @property + def ounces_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_terameter) + + @property + def grams_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_gigameter) + + @property + def exagrams_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_gigameter) + + @property + def petagrams_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_gigameter) + + @property + def teragrams_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_gigameter) + + @property + def gigagrams_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_gigameter) + + @property + def megagrams_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_gigameter) + + @property + def kilograms_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_gigameter) + + @property + def milligrams_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_gigameter) + + @property + def micrograms_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_gigameter) + + @property + def nanograms_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_gigameter) + + @property + def picograms_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_gigameter) + + @property + def femtograms_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_gigameter) + + @property + def attograms_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_gigameter) + + @property + def atomic_mass_units_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_gigameter) + + @property + def pounds_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_gigameter) + + @property + def ounces_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_gigameter) + + @property + def grams_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_megameter) + + @property + def exagrams_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_megameter) + + @property + def petagrams_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_megameter) + + @property + def teragrams_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_megameter) + + @property + def gigagrams_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_megameter) + + @property + def megagrams_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_megameter) + + @property + def kilograms_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_megameter) + + @property + def milligrams_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_megameter) + + @property + def micrograms_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_megameter) + + @property + def nanograms_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_megameter) + + @property + def picograms_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_megameter) + + @property + def femtograms_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_megameter) + + @property + def attograms_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_megameter) + + @property + def atomic_mass_units_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_megameter) + + @property + def pounds_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_megameter) + + @property + def ounces_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_megameter) + + @property + def grams_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_kilometer) + + @property + def exagrams_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_kilometer) + + @property + def petagrams_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_kilometer) + + @property + def teragrams_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_kilometer) + + @property + def gigagrams_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_kilometer) + + @property + def megagrams_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_kilometer) + + @property + def kilograms_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_kilometer) + + @property + def milligrams_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_kilometer) + + @property + def micrograms_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_kilometer) + + @property + def nanograms_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_kilometer) + + @property + def picograms_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_kilometer) + + @property + def femtograms_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_kilometer) + + @property + def attograms_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_kilometer) + + @property + def atomic_mass_units_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_kilometer) + + @property + def pounds_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_kilometer) + + @property + def ounces_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_kilometer) + + @property + def grams_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_millimeter) + + @property + def exagrams_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_millimeter) + + @property + def petagrams_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_millimeter) + + @property + def teragrams_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_millimeter) + + @property + def gigagrams_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_millimeter) + + @property + def megagrams_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_millimeter) + + @property + def kilograms_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_millimeter) + + @property + def milligrams_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_millimeter) + + @property + def micrograms_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_millimeter) + + @property + def nanograms_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_millimeter) + + @property + def picograms_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_millimeter) + + @property + def femtograms_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_millimeter) + + @property + def attograms_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_millimeter) + + @property + def atomic_mass_units_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_millimeter) + + @property + def pounds_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_millimeter) + + @property + def ounces_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_millimeter) + + @property + def grams_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_micrometer) + + @property + def exagrams_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_micrometer) + + @property + def petagrams_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_micrometer) + + @property + def teragrams_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_micrometer) + + @property + def gigagrams_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_micrometer) + + @property + def megagrams_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_micrometer) + + @property + def kilograms_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_micrometer) + + @property + def milligrams_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_micrometer) + + @property + def micrograms_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_micrometer) + + @property + def nanograms_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_micrometer) + + @property + def picograms_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_micrometer) + + @property + def femtograms_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_micrometer) + + @property + def attograms_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_micrometer) + + @property + def atomic_mass_units_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_micrometer) + + @property + def pounds_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_micrometer) + + @property + def ounces_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_micrometer) + + @property + def grams_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_nanometer) + + @property + def exagrams_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_nanometer) + + @property + def petagrams_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_nanometer) + + @property + def teragrams_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_nanometer) + + @property + def gigagrams_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_nanometer) + + @property + def megagrams_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_nanometer) + + @property + def kilograms_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_nanometer) + + @property + def milligrams_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_nanometer) + + @property + def micrograms_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_nanometer) + + @property + def nanograms_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_nanometer) + + @property + def picograms_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_nanometer) + + @property + def femtograms_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_nanometer) + + @property + def attograms_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_nanometer) + + @property + def atomic_mass_units_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_nanometer) + + @property + def pounds_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_nanometer) + + @property + def ounces_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_nanometer) + + @property + def grams_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_picometer) + + @property + def exagrams_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_picometer) + + @property + def petagrams_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_picometer) + + @property + def teragrams_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_picometer) + + @property + def gigagrams_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_picometer) + + @property + def megagrams_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_picometer) + + @property + def kilograms_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_picometer) + + @property + def milligrams_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_picometer) + + @property + def micrograms_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_picometer) + + @property + def nanograms_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_picometer) + + @property + def picograms_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_picometer) + + @property + def femtograms_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_picometer) + + @property + def attograms_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_picometer) + + @property + def atomic_mass_units_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_picometer) + + @property + def pounds_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_picometer) + + @property + def ounces_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_picometer) + + @property + def grams_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_femtometer) + + @property + def exagrams_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_femtometer) + + @property + def petagrams_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_femtometer) + + @property + def teragrams_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_femtometer) + + @property + def gigagrams_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_femtometer) + + @property + def megagrams_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_femtometer) + + @property + def kilograms_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_femtometer) + + @property + def milligrams_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_femtometer) + + @property + def micrograms_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_femtometer) + + @property + def nanograms_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_femtometer) + + @property + def picograms_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_femtometer) + + @property + def femtograms_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_femtometer) + + @property + def attograms_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_femtometer) + + @property + def atomic_mass_units_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_femtometer) + + @property + def pounds_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_femtometer) + + @property + def ounces_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_femtometer) + + @property + def grams_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_attometer) + + @property + def exagrams_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_attometer) + + @property + def petagrams_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_attometer) + + @property + def teragrams_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_attometer) + + @property + def gigagrams_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_attometer) + + @property + def megagrams_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_attometer) + + @property + def kilograms_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_attometer) + + @property + def milligrams_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_attometer) + + @property + def micrograms_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_attometer) + + @property + def nanograms_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_attometer) + + @property + def picograms_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_attometer) + + @property + def femtograms_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_attometer) + + @property + def attograms_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_attometer) + + @property + def atomic_mass_units_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_attometer) + + @property + def pounds_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_attometer) + + @property + def ounces_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_attometer) + + @property + def grams_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_decimeter) + + @property + def exagrams_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_decimeter) + + @property + def petagrams_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_decimeter) + + @property + def teragrams_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_decimeter) + + @property + def gigagrams_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_decimeter) + + @property + def megagrams_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_decimeter) + + @property + def kilograms_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_decimeter) + + @property + def milligrams_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_decimeter) + + @property + def micrograms_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_decimeter) + + @property + def nanograms_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_decimeter) + + @property + def picograms_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_decimeter) + + @property + def femtograms_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_decimeter) + + @property + def attograms_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_decimeter) + + @property + def atomic_mass_units_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_decimeter) + + @property + def pounds_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_decimeter) + + @property + def ounces_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_decimeter) + + @property + def grams_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_centimeter) + + @property + def exagrams_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_centimeter) + + @property + def petagrams_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_centimeter) + + @property + def teragrams_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_centimeter) + + @property + def gigagrams_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_centimeter) + + @property + def megagrams_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_centimeter) + + @property + def kilograms_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_centimeter) + + @property + def milligrams_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_centimeter) + + @property + def micrograms_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_centimeter) + + @property + def nanograms_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_centimeter) + + @property + def picograms_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_centimeter) + + @property + def femtograms_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_centimeter) + + @property + def attograms_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_centimeter) + + @property + def atomic_mass_units_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_centimeter) + + @property + def pounds_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_centimeter) + + @property + def ounces_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_centimeter) + + @property + def grams_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_angstrom) + + @property + def exagrams_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_angstrom) + + @property + def petagrams_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_angstrom) + + @property + def teragrams_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_angstrom) + + @property + def gigagrams_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_angstrom) + + @property + def megagrams_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_angstrom) + + @property + def kilograms_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_angstrom) + + @property + def milligrams_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_angstrom) + + @property + def micrograms_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_angstrom) + + @property + def nanograms_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_angstrom) + + @property + def picograms_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_angstrom) + + @property + def femtograms_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_angstrom) + + @property + def attograms_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_angstrom) + + @property + def atomic_mass_units_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_angstrom) + + @property + def pounds_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_angstrom) + + @property + def ounces_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_angstrom) + + @property + def grams_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_micron) + + @property + def exagrams_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_micron) + + @property + def petagrams_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_micron) + + @property + def teragrams_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_micron) + + @property + def gigagrams_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_micron) + + @property + def megagrams_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_micron) + + @property + def kilograms_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_micron) + + @property + def milligrams_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_micron) + + @property + def micrograms_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_micron) + + @property + def nanograms_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_micron) + + @property + def picograms_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_micron) + + @property + def femtograms_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_micron) + + @property + def attograms_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_micron) + + @property + def atomic_mass_units_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_micron) + + @property + def pounds_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_micron) + + @property + def ounces_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_micron) + + @property + def grams_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_mile) + + @property + def exagrams_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_mile) + + @property + def petagrams_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_mile) + + @property + def teragrams_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_mile) + + @property + def gigagrams_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_mile) + + @property + def megagrams_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_mile) + + @property + def kilograms_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_mile) + + @property + def milligrams_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_mile) + + @property + def micrograms_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_mile) + + @property + def nanograms_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_mile) + + @property + def picograms_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_mile) + + @property + def femtograms_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_mile) + + @property + def attograms_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_mile) + + @property + def atomic_mass_units_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_mile) + + @property + def pounds_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_mile) + + @property + def ounces_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_mile) + + @property + def grams_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_yard) + + @property + def exagrams_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_yard) + + @property + def petagrams_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_yard) + + @property + def teragrams_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_yard) + + @property + def gigagrams_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_yard) + + @property + def megagrams_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_yard) + + @property + def kilograms_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_yard) + + @property + def milligrams_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_yard) + + @property + def micrograms_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_yard) + + @property + def nanograms_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_yard) + + @property + def picograms_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_yard) + + @property + def femtograms_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_yard) + + @property + def attograms_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_yard) + + @property + def atomic_mass_units_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_yard) + + @property + def pounds_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_yard) + + @property + def ounces_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_yard) + + @property + def grams_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_foot) + + @property + def exagrams_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_foot) + + @property + def petagrams_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_foot) + + @property + def teragrams_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_foot) + + @property + def gigagrams_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_foot) + + @property + def megagrams_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_foot) + + @property + def kilograms_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_foot) + + @property + def milligrams_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_foot) + + @property + def micrograms_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_foot) + + @property + def nanograms_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_foot) + + @property + def picograms_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_foot) + + @property + def femtograms_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_foot) + + @property + def attograms_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_foot) + + @property + def atomic_mass_units_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_foot) + + @property + def pounds_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_foot) + + @property + def ounces_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_foot) + + @property + def grams_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.grams_per_cubic_inch) + + @property + def exagrams_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exagrams_per_cubic_inch) + + @property + def petagrams_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petagrams_per_cubic_inch) + + @property + def teragrams_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teragrams_per_cubic_inch) + + @property + def gigagrams_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigagrams_per_cubic_inch) + + @property + def megagrams_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megagrams_per_cubic_inch) + + @property + def kilograms_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilograms_per_cubic_inch) + + @property + def milligrams_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milligrams_per_cubic_inch) + + @property + def micrograms_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micrograms_per_cubic_inch) + + @property + def nanograms_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanograms_per_cubic_inch) + + @property + def picograms_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picograms_per_cubic_inch) + + @property + def femtograms_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtograms_per_cubic_inch) + + @property + def attograms_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attograms_per_cubic_inch) + + @property + def atomic_mass_units_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.atomic_mass_units_per_cubic_inch) + + @property + def pounds_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_per_cubic_inch) + + @property + def ounces_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ounces_per_cubic_inch) + + + +class ForceAccessor[T](QuantityAccessor[T]): + dimension_name = 'force' + + @property + def newtons(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.newtons) + + @property + def exanewtons(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exanewtons) + + @property + def petanewtons(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petanewtons) + + @property + def teranewtons(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teranewtons) + + @property + def giganewtons(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.giganewtons) + + @property + def meganewtons(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.meganewtons) + + @property + def kilonewtons(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilonewtons) + + @property + def millinewtons(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millinewtons) + + @property + def micronewtons(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micronewtons) + + @property + def nanonewtons(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanonewtons) + + @property + def piconewtons(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.piconewtons) + + @property + def femtonewtons(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtonewtons) + + @property + def attonewtons(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attonewtons) + + @property + def kg_force(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kg_force) + + @property + def pounds_force(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_force) + + + +class PressureAccessor[T](QuantityAccessor[T]): + dimension_name = 'pressure' + + @property + def pascals(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pascals) + + @property + def exapascals(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exapascals) + + @property + def petapascals(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petapascals) + + @property + def terapascals(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terapascals) + + @property + def gigapascals(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigapascals) + + @property + def megapascals(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megapascals) + + @property + def kilopascals(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilopascals) + + @property + def millipascals(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millipascals) + + @property + def micropascals(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micropascals) + + @property + def nanopascals(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanopascals) + + @property + def picopascals(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picopascals) + + @property + def femtopascals(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtopascals) + + @property + def attopascals(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attopascals) + + @property + def pounds_force_per_square_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.pounds_force_per_square_inch) + + + +class EnergyAccessor[T](QuantityAccessor[T]): + dimension_name = 'energy' + + @property + def joules(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.joules) + + @property + def exajoules(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exajoules) + + @property + def petajoules(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petajoules) + + @property + def terajoules(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terajoules) + + @property + def gigajoules(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigajoules) + + @property + def megajoules(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megajoules) + + @property + def kilojoules(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilojoules) + + @property + def millijoules(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millijoules) + + @property + def microjoules(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microjoules) + + @property + def nanojoules(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanojoules) + + @property + def picojoules(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picojoules) + + @property + def femtojoules(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtojoules) + + @property + def attojoules(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attojoules) + + @property + def electronvolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.electronvolts) + + @property + def exaelectronvolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exaelectronvolts) + + @property + def petaelectronvolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petaelectronvolts) + + @property + def teraelectronvolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teraelectronvolts) + + @property + def gigaelectronvolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigaelectronvolts) + + @property + def megaelectronvolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megaelectronvolts) + + @property + def kiloelectronvolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kiloelectronvolts) + + @property + def millielectronvolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millielectronvolts) + + @property + def microelectronvolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microelectronvolts) + + @property + def nanoelectronvolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanoelectronvolts) + + @property + def picoelectronvolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picoelectronvolts) + + @property + def femtoelectronvolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtoelectronvolts) + + @property + def attoelectronvolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attoelectronvolts) + + + +class PowerAccessor[T](QuantityAccessor[T]): + dimension_name = 'power' + + @property + def watts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.watts) + + @property + def exawatts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exawatts) + + @property + def petawatts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petawatts) + + @property + def terawatts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terawatts) + + @property + def gigawatts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigawatts) + + @property + def megawatts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megawatts) + + @property + def kilowatts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilowatts) + + @property + def milliwatts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milliwatts) + + @property + def microwatts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microwatts) + + @property + def nanowatts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanowatts) + + @property + def picowatts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picowatts) + + @property + def femtowatts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtowatts) + + @property + def attowatts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attowatts) + + + +class ChargeAccessor[T](QuantityAccessor[T]): + dimension_name = 'charge' + + @property + def coulombs(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.coulombs) + + @property + def exacoulombs(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exacoulombs) + + @property + def petacoulombs(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petacoulombs) + + @property + def teracoulombs(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teracoulombs) + + @property + def gigacoulombs(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigacoulombs) + + @property + def megacoulombs(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megacoulombs) + + @property + def kilocoulombs(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilocoulombs) + + @property + def millicoulombs(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millicoulombs) + + @property + def microcoulombs(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microcoulombs) + + @property + def nanocoulombs(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanocoulombs) + + @property + def picocoulombs(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picocoulombs) + + @property + def femtocoulombs(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtocoulombs) + + @property + def attocoulombs(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attocoulombs) + + + +class PotentialAccessor[T](QuantityAccessor[T]): + dimension_name = 'potential' + + @property + def volts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.volts) + + @property + def exavolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exavolts) + + @property + def petavolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petavolts) + + @property + def teravolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teravolts) + + @property + def gigavolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigavolts) + + @property + def megavolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megavolts) + + @property + def kilovolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilovolts) + + @property + def millivolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millivolts) + + @property + def microvolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microvolts) + + @property + def nanovolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanovolts) + + @property + def picovolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picovolts) + + @property + def femtovolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtovolts) + + @property + def attovolts(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attovolts) + + + +class ResistanceAccessor[T](QuantityAccessor[T]): + dimension_name = 'resistance' + + @property + def ohms(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.ohms) + + @property + def exaohms(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exaohms) + + @property + def petaohms(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petaohms) + + @property + def teraohms(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teraohms) + + @property + def gigaohms(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigaohms) + + @property + def megaohms(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megaohms) + + @property + def kiloohms(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kiloohms) + + @property + def milliohms(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milliohms) + + @property + def microohms(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microohms) + + @property + def nanoohms(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanoohms) + + @property + def picoohms(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picoohms) + + @property + def femtoohms(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtoohms) + + @property + def attoohms(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attoohms) + + + +class CapacitanceAccessor[T](QuantityAccessor[T]): + dimension_name = 'capacitance' + + @property + def farads(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.farads) + + @property + def exafarads(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exafarads) + + @property + def petafarads(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petafarads) + + @property + def terafarads(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terafarads) + + @property + def gigafarads(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigafarads) + + @property + def megafarads(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megafarads) + + @property + def kilofarads(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilofarads) + + @property + def millifarads(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millifarads) + + @property + def microfarads(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microfarads) + + @property + def nanofarads(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanofarads) + + @property + def picofarads(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picofarads) + + @property + def femtofarads(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtofarads) + + @property + def attofarads(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attofarads) + + + +class ConductanceAccessor[T](QuantityAccessor[T]): + dimension_name = 'conductance' + + @property + def siemens(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.siemens) + + @property + def exasiemens(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exasiemens) + + @property + def petasiemens(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petasiemens) + + @property + def terasiemens(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terasiemens) + + @property + def gigasiemens(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigasiemens) + + @property + def megasiemens(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megasiemens) + + @property + def kilosiemens(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilosiemens) + + @property + def millisiemens(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millisiemens) + + @property + def microsiemens(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microsiemens) + + @property + def nanosiemens(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanosiemens) + + @property + def picosiemens(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picosiemens) + + @property + def femtosiemens(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtosiemens) + + @property + def attosiemens(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attosiemens) + + + +class MagneticfluxAccessor[T](QuantityAccessor[T]): + dimension_name = 'magnetic_flux' + + @property + def webers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.webers) + + @property + def exawebers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exawebers) + + @property + def petawebers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petawebers) + + @property + def terawebers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terawebers) + + @property + def gigawebers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigawebers) + + @property + def megawebers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megawebers) + + @property + def kilowebers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilowebers) + + @property + def milliwebers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.milliwebers) + + @property + def microwebers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microwebers) + + @property + def nanowebers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanowebers) + + @property + def picowebers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picowebers) + + @property + def femtowebers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtowebers) + + @property + def attowebers(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attowebers) + + + +class MagneticfluxdensityAccessor[T](QuantityAccessor[T]): + dimension_name = 'magnetic_flux_density' + + @property + def tesla(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.tesla) + + @property + def exatesla(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exatesla) + + @property + def petatesla(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petatesla) + + @property + def teratesla(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.teratesla) + + @property + def gigatesla(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigatesla) + + @property + def megatesla(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megatesla) + + @property + def kilotesla(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilotesla) + + @property + def millitesla(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millitesla) + + @property + def microtesla(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microtesla) + + @property + def nanotesla(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanotesla) + + @property + def picotesla(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picotesla) + + @property + def femtotesla(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtotesla) + + @property + def attotesla(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attotesla) + + + +class InductanceAccessor[T](QuantityAccessor[T]): + dimension_name = 'inductance' + + @property + def henry(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.henry) + + @property + def exahenry(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exahenry) + + @property + def petahenry(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petahenry) + + @property + def terahenry(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terahenry) + + @property + def gigahenry(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigahenry) + + @property + def megahenry(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megahenry) + + @property + def kilohenry(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilohenry) + + @property + def millihenry(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millihenry) + + @property + def microhenry(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microhenry) + + @property + def nanohenry(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanohenry) + + @property + def picohenry(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picohenry) + + @property + def femtohenry(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtohenry) + + @property + def attohenry(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attohenry) + + + +class TemperatureAccessor[T](QuantityAccessor[T]): + dimension_name = 'temperature' + + @property + def kelvin(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kelvin) + + @property + def exakelvin(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.exakelvin) + + @property + def petakelvin(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.petakelvin) + + @property + def terakelvin(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.terakelvin) + + @property + def gigakelvin(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.gigakelvin) + + @property + def megakelvin(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.megakelvin) + + @property + def kilokelvin(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.kilokelvin) + + @property + def millikelvin(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millikelvin) + + @property + def microkelvin(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.microkelvin) + + @property + def nanokelvin(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanokelvin) + + @property + def picokelvin(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picokelvin) + + @property + def femtokelvin(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtokelvin) + + @property + def attokelvin(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attokelvin) + + @property + def degrees_celsius(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.degrees_celsius) + + + +class DimensionlessAccessor[T](QuantityAccessor[T]): + dimension_name = 'dimensionless' + + @property + def none(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.none) + + @property + def percent(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.percent) + + + +class AngleAccessor[T](QuantityAccessor[T]): + dimension_name = 'angle' + + @property + def degrees(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.degrees) + + @property + def radians(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.radians) + + + +class SolidangleAccessor[T](QuantityAccessor[T]): + dimension_name = 'solid_angle' + + @property + def stradians(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.stradians) + + + +class AmountAccessor[T](QuantityAccessor[T]): + dimension_name = 'amount' + + @property + def moles(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles) + + @property + def millimoles(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles) + + @property + def micromoles(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles) + + @property + def nanomoles(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles) + + @property + def picomoles(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles) + + @property + def femtomoles(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles) + + @property + def attomoles(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles) + + + +class ConcentrationAccessor[T](QuantityAccessor[T]): + dimension_name = 'concentration' + + @property + def moles_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_meter) + + @property + def millimoles_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_meter) + + @property + def micromoles_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_meter) + + @property + def nanomoles_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_meter) + + @property + def picomoles_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_meter) + + @property + def femtomoles_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_meter) + + @property + def attomoles_per_cubic_meter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_meter) + + @property + def moles_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_exameter) + + @property + def millimoles_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_exameter) + + @property + def micromoles_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_exameter) + + @property + def nanomoles_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_exameter) + + @property + def picomoles_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_exameter) + + @property + def femtomoles_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_exameter) + + @property + def attomoles_per_cubic_exameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_exameter) + + @property + def moles_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_petameter) + + @property + def millimoles_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_petameter) + + @property + def micromoles_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_petameter) + + @property + def nanomoles_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_petameter) + + @property + def picomoles_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_petameter) + + @property + def femtomoles_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_petameter) + + @property + def attomoles_per_cubic_petameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_petameter) + + @property + def moles_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_terameter) + + @property + def millimoles_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_terameter) + + @property + def micromoles_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_terameter) + + @property + def nanomoles_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_terameter) + + @property + def picomoles_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_terameter) + + @property + def femtomoles_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_terameter) + + @property + def attomoles_per_cubic_terameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_terameter) + + @property + def moles_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_gigameter) + + @property + def millimoles_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_gigameter) + + @property + def micromoles_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_gigameter) + + @property + def nanomoles_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_gigameter) + + @property + def picomoles_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_gigameter) + + @property + def femtomoles_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_gigameter) + + @property + def attomoles_per_cubic_gigameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_gigameter) + + @property + def moles_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_megameter) + + @property + def millimoles_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_megameter) + + @property + def micromoles_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_megameter) + + @property + def nanomoles_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_megameter) + + @property + def picomoles_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_megameter) + + @property + def femtomoles_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_megameter) + + @property + def attomoles_per_cubic_megameter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_megameter) + + @property + def moles_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_kilometer) + + @property + def millimoles_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_kilometer) + + @property + def micromoles_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_kilometer) + + @property + def nanomoles_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_kilometer) + + @property + def picomoles_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_kilometer) + + @property + def femtomoles_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_kilometer) + + @property + def attomoles_per_cubic_kilometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_kilometer) + + @property + def moles_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_millimeter) + + @property + def millimoles_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_millimeter) + + @property + def micromoles_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_millimeter) + + @property + def nanomoles_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_millimeter) + + @property + def picomoles_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_millimeter) + + @property + def femtomoles_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_millimeter) + + @property + def attomoles_per_cubic_millimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_millimeter) + + @property + def moles_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_micrometer) + + @property + def millimoles_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_micrometer) + + @property + def micromoles_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_micrometer) + + @property + def nanomoles_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_micrometer) + + @property + def picomoles_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_micrometer) + + @property + def femtomoles_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_micrometer) + + @property + def attomoles_per_cubic_micrometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_micrometer) + + @property + def moles_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_nanometer) + + @property + def millimoles_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_nanometer) + + @property + def micromoles_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_nanometer) + + @property + def nanomoles_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_nanometer) + + @property + def picomoles_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_nanometer) + + @property + def femtomoles_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_nanometer) + + @property + def attomoles_per_cubic_nanometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_nanometer) + + @property + def moles_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_picometer) + + @property + def millimoles_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_picometer) + + @property + def micromoles_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_picometer) + + @property + def nanomoles_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_picometer) + + @property + def picomoles_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_picometer) + + @property + def femtomoles_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_picometer) + + @property + def attomoles_per_cubic_picometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_picometer) + + @property + def moles_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_femtometer) + + @property + def millimoles_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_femtometer) + + @property + def micromoles_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_femtometer) + + @property + def nanomoles_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_femtometer) + + @property + def picomoles_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_femtometer) + + @property + def femtomoles_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_femtometer) + + @property + def attomoles_per_cubic_femtometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_femtometer) + + @property + def moles_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_attometer) + + @property + def millimoles_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_attometer) + + @property + def micromoles_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_attometer) + + @property + def nanomoles_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_attometer) + + @property + def picomoles_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_attometer) + + @property + def femtomoles_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_attometer) + + @property + def attomoles_per_cubic_attometer(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_attometer) + + @property + def moles_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_decimeter) + + @property + def millimoles_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_decimeter) + + @property + def micromoles_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_decimeter) + + @property + def nanomoles_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_decimeter) + + @property + def picomoles_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_decimeter) + + @property + def femtomoles_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_decimeter) + + @property + def attomoles_per_cubic_decimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_decimeter) + + @property + def moles_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_centimeter) + + @property + def millimoles_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_centimeter) + + @property + def micromoles_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_centimeter) + + @property + def nanomoles_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_centimeter) + + @property + def picomoles_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_centimeter) + + @property + def femtomoles_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_centimeter) + + @property + def attomoles_per_cubic_centimeter(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_centimeter) + + @property + def moles_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_angstrom) + + @property + def millimoles_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_angstrom) + + @property + def micromoles_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_angstrom) + + @property + def nanomoles_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_angstrom) + + @property + def picomoles_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_angstrom) + + @property + def femtomoles_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_angstrom) + + @property + def attomoles_per_cubic_angstrom(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_angstrom) + + @property + def moles_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_micron) + + @property + def millimoles_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_micron) + + @property + def micromoles_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_micron) + + @property + def nanomoles_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_micron) + + @property + def picomoles_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_micron) + + @property + def femtomoles_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_micron) + + @property + def attomoles_per_cubic_micron(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_micron) + + @property + def moles_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_mile) + + @property + def millimoles_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_mile) + + @property + def micromoles_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_mile) + + @property + def nanomoles_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_mile) + + @property + def picomoles_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_mile) + + @property + def femtomoles_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_mile) + + @property + def attomoles_per_cubic_mile(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_mile) + + @property + def moles_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_yard) + + @property + def millimoles_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_yard) + + @property + def micromoles_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_yard) + + @property + def nanomoles_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_yard) + + @property + def picomoles_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_yard) + + @property + def femtomoles_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_yard) + + @property + def attomoles_per_cubic_yard(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_yard) + + @property + def moles_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_foot) + + @property + def millimoles_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_foot) + + @property + def micromoles_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_foot) + + @property + def nanomoles_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_foot) + + @property + def picomoles_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_foot) + + @property + def femtomoles_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_foot) + + @property + def attomoles_per_cubic_foot(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_foot) + + @property + def moles_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.moles_per_cubic_inch) + + @property + def millimoles_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.millimoles_per_cubic_inch) + + @property + def micromoles_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.micromoles_per_cubic_inch) + + @property + def nanomoles_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.nanomoles_per_cubic_inch) + + @property + def picomoles_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.picomoles_per_cubic_inch) + + @property + def femtomoles_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.femtomoles_per_cubic_inch) + + @property + def attomoles_per_cubic_inch(self) -> T: + quantity = self.quantity + if quantity is None: + return None + else: + return quantity.in_units_of(units.attomoles_per_cubic_inch) + + diff --git a/sasdata/quantities/constants.py b/sasdata/quantities/constants.py new file mode 100644 index 00000000..e69de29b diff --git a/sasdata/quantities/math_operations_test.py b/sasdata/quantities/math_operations_test.py new file mode 100644 index 00000000..5bda5a2c --- /dev/null +++ b/sasdata/quantities/math_operations_test.py @@ -0,0 +1,152 @@ +""" Tests for math operations """ + +import pytest + +import numpy as np +from sasdata.quantities.quantity import NamedQuantity, tensordot, transpose +from sasdata.quantities import units + +order_list = [ + [0, 1, 2, 3], + [0, 2, 1], + [1, 0], + [0, 1], + [2, 0, 1], + [3, 1, 2, 0] +] + +@pytest.mark.parametrize("order", order_list) +def test_transpose_raw(order: list[int]): + """ Check that the transpose operation changes the order of indices correctly - uses sizes as way of tracking""" + + input_shape = tuple([i+1 for i in range(len(order))]) + expected_shape = tuple([i+1 for i in order]) + + input_mat = np.zeros(input_shape) + + measured_mat = transpose(input_mat, axes=tuple(order)) + + assert measured_mat.shape == expected_shape + + +@pytest.mark.parametrize("order", order_list) +def test_transpose_raw(order: list[int]): + """ Check that the transpose operation changes the order of indices correctly - uses sizes as way of tracking""" + input_shape = tuple([i + 1 for i in range(len(order))]) + expected_shape = tuple([i + 1 for i in order]) + + input_mat = NamedQuantity("testmat", np.zeros(input_shape), units=units.none) + + measured_mat = transpose(input_mat, axes=tuple(order)) + + assert measured_mat.value.shape == expected_shape + + +rng_seed = 1979 +tensor_product_with_identity_sizes = (4,6,5) + +@pytest.mark.parametrize("index, size", [tup for tup in enumerate(tensor_product_with_identity_sizes)]) +def test_tensor_product_with_identity_quantities(index, size): + """ Check the correctness of the tensor product by multiplying by the identity (quantity, quantity)""" + np.random.seed(rng_seed) + + x = NamedQuantity("x", np.random.rand(*tensor_product_with_identity_sizes), units=units.meters) + y = NamedQuantity("y", np.eye(size), units.seconds) + + z = tensordot(x, y, index, 0) + + # Check units + assert z.units == units.meters * units.seconds + + # Expected sizes - last index gets moved to end + output_order = [i for i in (0, 1, 2) if i != index] + [index] + output_sizes = [tensor_product_with_identity_sizes[i] for i in output_order] + + assert z.value.shape == tuple(output_sizes) + + # Restore original order and check + reverse_order = [-1, -1, -1] + for to_index, from_index in enumerate(output_order): + reverse_order[from_index] = to_index + + z_reordered = transpose(z, axes = tuple(reverse_order)) + + assert z_reordered.value.shape == tensor_product_with_identity_sizes + + # Check values + + mat_in = x.in_si() + mat_out = transpose(z, axes=tuple(reverse_order)).in_si() + + assert np.all(np.abs(mat_in - mat_out) < 1e-10) + + +@pytest.mark.parametrize("index, size", [tup for tup in enumerate(tensor_product_with_identity_sizes)]) +def test_tensor_product_with_identity_quantity_matrix(index, size): + """ Check the correctness of the tensor product by multiplying by the identity (quantity, matrix)""" + np.random.seed(rng_seed) + + x = NamedQuantity("x", np.random.rand(*tensor_product_with_identity_sizes), units.meters) + y = np.eye(size) + + z = tensordot(x, y, index, 0) + + assert z.units == units.meters + + # Expected sizes - last index gets moved to end + output_order = [i for i in (0, 1, 2) if i != index] + [index] + output_sizes = [tensor_product_with_identity_sizes[i] for i in output_order] + + assert z.value.shape == tuple(output_sizes) + + # Restore original order and check + reverse_order = [-1, -1, -1] + for to_index, from_index in enumerate(output_order): + reverse_order[from_index] = to_index + + z_reordered = transpose(z, axes = tuple(reverse_order)) + + assert z_reordered.value.shape == tensor_product_with_identity_sizes + + # Check values + + mat_in = x.in_si() + mat_out = transpose(z, axes=tuple(reverse_order)).in_si() + + assert np.all(np.abs(mat_in - mat_out) < 1e-10) + + +@pytest.mark.parametrize("index, size", [tup for tup in enumerate(tensor_product_with_identity_sizes)]) +def test_tensor_product_with_identity_matrix_quantity(index, size): + """ Check the correctness of the tensor product by multiplying by the identity (matrix, quantity)""" + np.random.seed(rng_seed) + + x = np.random.rand(*tensor_product_with_identity_sizes) + y = NamedQuantity("y", np.eye(size), units.seconds) + + z = tensordot(x, y, index, 0) + + assert z.units == units.seconds + + + # Expected sizes - last index gets moved to end + output_order = [i for i in (0, 1, 2) if i != index] + [index] + output_sizes = [tensor_product_with_identity_sizes[i] for i in output_order] + + assert z.value.shape == tuple(output_sizes) + + # Restore original order and check + reverse_order = [-1, -1, -1] + for to_index, from_index in enumerate(output_order): + reverse_order[from_index] = to_index + + z_reordered = transpose(z, axes = tuple(reverse_order)) + + assert z_reordered.value.shape == tensor_product_with_identity_sizes + + # Check values + + mat_in = x + mat_out = transpose(z, axes=tuple(reverse_order)).in_si() + + assert np.all(np.abs(mat_in - mat_out) < 1e-10) diff --git a/sasdata/quantities/notes.rst b/sasdata/quantities/notes.rst new file mode 100644 index 00000000..9c4f47c4 --- /dev/null +++ b/sasdata/quantities/notes.rst @@ -0,0 +1,23 @@ +Mutability +---------- + +DataSets: Immutable +Quantities: Immutable +Units: Hard coded + +Quantity methods +---------------- + +in_* methods return numbers/arrays in a given unit system +to_* converts to different units + + +Identifying of Quantities +-------------------- + +There are two choices when it comes to keeping track of quantities for error propagation. +Either we give them names, in which case we risk collisions, or we use hashes, which can potentially +have issues with things not being identified correctly. + +The decision here is to use hashes of the data, not names, because it would be too easy to +give different things the same name. \ No newline at end of file diff --git a/sasdata/quantities/numerical_encoding.py b/sasdata/quantities/numerical_encoding.py new file mode 100644 index 00000000..39943948 --- /dev/null +++ b/sasdata/quantities/numerical_encoding.py @@ -0,0 +1,72 @@ +import numpy as np +from scipy.sparse import coo_matrix, csr_matrix, csc_matrix, coo_array, csr_array, csc_array + +import base64 +import struct + + +def numerical_encode(obj: int | float | np.ndarray | coo_matrix | coo_array | csr_matrix | csr_array | csc_matrix | csc_array): + + if isinstance(obj, int): + return {"type": "int", + "value": obj} + + elif isinstance(obj, float): + return {"type": "float", + "value": base64.b64encode(bytearray(struct.pack('d', obj))).decode("utf-8")} + + elif isinstance(obj, np.ndarray): + return { + "type": "numpy", + "value": base64.b64encode(obj.tobytes()).decode("utf-8"), + "dtype": obj.dtype.str, + "shape": list(obj.shape) + } + + elif isinstance(obj, (coo_matrix, coo_array, csr_matrix, csr_array, csc_matrix, csc_array)): + + output = { + "type": obj.__class__.__name__, # not robust to name changes, but more concise + "dtype": obj.dtype.str, + "shape": list(obj.shape) + } + + if isinstance(obj, (coo_array, coo_matrix)): + + output["data"] = numerical_encode(obj.data) + output["coords"] = [numerical_encode(coord) for coord in obj.coords] + + + elif isinstance(obj, (csr_array, csr_matrix)): + pass + + + elif isinstance(obj, (csc_array, csc_matrix)): + + pass + + + return output + + else: + raise TypeError(f"Cannot serialise object of type: {type(obj)}") + +def numerical_decode(data: dict[str, str | int | list[int]]) -> int | float | np.ndarray | coo_matrix | coo_array | csr_matrix | csr_array | csc_matrix | csc_array: + obj_type = data["type"] + + match obj_type: + case "int": + return int(data["value"]) + + case "float": + return struct.unpack('d', base64.b64decode(data["value"]))[0] + + case "numpy": + value = base64.b64decode(data["value"]) + dtype = np.dtype(data["dtype"]) + shape = tuple(data["shape"]) + return np.frombuffer(value, dtype=dtype).reshape(*shape) + + case _: + raise ValueError(f"Cannot decode objects of type '{obj_type}'") + diff --git a/sasdata/quantities/operations_examples.py b/sasdata/quantities/operations_examples.py new file mode 100644 index 00000000..4509a86a --- /dev/null +++ b/sasdata/quantities/operations_examples.py @@ -0,0 +1,11 @@ +from sasdata.quantities.operations import Variable, Mul + +x = Variable("x") +y = Variable("y") +z = Variable("z") +f = Mul(Mul(x, y), z) + + +dfdx = f.derivative(x).derivative(y).derivative(z) + +print(dfdx.summary()) diff --git a/sasdata/quantities/operations_test.py b/sasdata/quantities/operations_test.py new file mode 100644 index 00000000..6767e32a --- /dev/null +++ b/sasdata/quantities/operations_test.py @@ -0,0 +1,68 @@ +import pytest + +from sasdata.quantities.quantity import Operation, \ + Neg, Inv, \ + Add, Sub, Mul, Div, Pow, \ + Variable, Constant, AdditiveIdentity, MultiplicativeIdentity + +operation_with_everything = \ + Div( + Pow( + Mul( + Sub( + Add( + Neg(Inv(MultiplicativeIdentity())), + Variable("x")), + Constant(7)), + AdditiveIdentity()), + 2), + Variable("y")) + +def test_serialise_deserialise(): + print(operation_with_everything._serialise_json()) + + serialised = operation_with_everything.serialise() + deserialised = Operation.deserialise(serialised) + reserialised = deserialised.serialise() + + assert serialised == reserialised + + +@pytest.mark.parametrize("op, a, b, result", [ + (Add, 1, 1, 2), + (Add, 7, 8, 15), + (Sub, 1, 1, 0), + (Sub, 7, 8, -1), + (Mul, 1, 1, 1), + (Mul, 7, 8, 56), + (Div, 1, 1, 1), + (Div, 7, 8, 7/8), + (Pow, 1, 1, 1), + (Pow, 7, 2, 49)]) +def test_binary_evaluation(op, a, b, result): + f = op(Constant(a), b if op == Pow else Constant(b)) + assert f.evaluate({}) == result + +x = Variable("x") +y = Variable("y") +z = Variable("z") +@pytest.mark.parametrize("x_over_x", [ + Div(x,x), + Mul(Inv(x), x), + Mul(x, Inv(x)), +]) +def test_dx_over_x_by_dx_should_be_zero(x_over_x): + + + dfdx = x_over_x.derivative(x) + + print(dfdx.summary()) + + assert dfdx == AdditiveIdentity() + + +def test_d_xyz_by_components_should_be_1(): + f = Mul(Mul(x, y), z) + assert f.derivative(x).derivative(y).derivative(z) == MultiplicativeIdentity() + + diff --git a/sasdata/quantities/plotting.py b/sasdata/quantities/plotting.py new file mode 100644 index 00000000..854e23f5 --- /dev/null +++ b/sasdata/quantities/plotting.py @@ -0,0 +1,23 @@ +import matplotlib.pyplot as plt +from numpy.typing import ArrayLike + +from sasdata.quantities.quantity import Quantity, NamedQuantity + + +def quantity_plot(x: Quantity[ArrayLike], y: Quantity[ArrayLike], *args, **kwargs): + plt.plot(x.value, y.value, *args, **kwargs) + + x_name = x.name if isinstance(x, NamedQuantity) else "x" + y_name = y.name if isinstance(y, NamedQuantity) else "y" + + plt.xlabel(f"{x_name} / {x.units}") + plt.ylabel(f"{y_name} / {y.units}") + +def quantity_scatter(x: Quantity[ArrayLike], y: Quantity[ArrayLike], *args, **kwargs): + plt.scatter(x.value, y.value, *args, **kwargs) + + x_name = x.name if isinstance(x, NamedQuantity) else "x" + y_name = y.name if isinstance(y, NamedQuantity) else "y" + + plt.xlabel(f"{x_name} / {x.units}") + plt.ylabel(f"{y_name} / {y.units}") diff --git a/sasdata/quantities/quantities_tests.py b/sasdata/quantities/quantities_tests.py new file mode 100644 index 00000000..8ab0a41c --- /dev/null +++ b/sasdata/quantities/quantities_tests.py @@ -0,0 +1,124 @@ +import numpy as np + +from sasdata.quantities.quantity import Quantity, UnitError +import sasdata.quantities.units as units +import sasdata.quantities.si as si +import pytest +def test_in_units_of_calculation(): + """ Just a couple of unit conversions """ + assert Quantity(1, units.meters).in_units_of(units.kilometers) == 1e-3 + assert Quantity(10, units.minutes).in_units_of(units.seconds) == 600 + assert Quantity(7, units.kilonewtons).in_units_of(units.kg_force) == pytest.approx(7000 / 9.81, abs=1) + assert Quantity(0, units.meters).in_units_of(units.exameters) == 0 + + +def test_unit_compounding_pow(): + """ Test units compound correctly when __pow__ is used""" + assert (Quantity(1, units.millimeters) ** 2).in_units_of(units.square_meters) == 1e-6 + assert (Quantity(1, units.minutes) ** 3).in_units_of(units.seconds ** 3) == 60 ** 3 + +def test_pow_scaling(): + q2 = Quantity(1000, units.millimeters)**2 + assert q2.units.scale == 1e-6 + assert q2.value == 1e6 + + +def test_unit_compounding_mul(): + """ Test units compound correctly when __mul__ is used""" + assert (Quantity(4, units.minutes) * Quantity(0.25, units.hertz)).in_units_of(units.none) == 60 + assert (Quantity(250, units.volts) * Quantity(8, units.amperes)).in_units_of(units.kilowatts) == 2 + +def test_unit_compounding_div(): + """ Test units compound correctly when __truediv__ is used""" + assert (Quantity(10, units.kilometers) / Quantity(2, units.minutes) + ).in_units_of(units.meters_per_second) == pytest.approx(250/3, abs=1e-6) + + assert (Quantity(1, units.nanowebers) / (Quantity(1, units.millimeters) ** 2)).in_units_of(units.millitesla) == 1 + +def test_value_mul(): + """ Test value part of quantities multiply correctly""" + assert (Quantity(1j, units.seconds) * Quantity(1j, units.watts)).in_units_of(units.joules) == -1 + +def test_scalar_mul(): + assert (Quantity(1, units.seconds) * 10).in_units_of(units.seconds) == 10 + assert (10 * Quantity(1, units.seconds)).in_units_of(units.seconds) == 10 + assert (1000 * Quantity(1, units.milliseconds)).in_units_of(units.seconds) == 1 + +def test_scalar_div(): + + assert (Quantity(1, units.seconds) / 10).in_units_of(units.seconds) == 0.1 + assert (10 / Quantity(1, units.seconds)).in_units_of(units.hertz) == 10 + assert (0.001 / Quantity(1, units.milliseconds)).in_units_of(units.hertz) == 1 + +def test_good_add_sub(): + """ Test that adding and subtracting units works """ + assert (Quantity(1, units.seconds) + Quantity(1, units.milliseconds)).in_units_of(units.seconds) == 1.001 + assert (Quantity(1, units.seconds) - Quantity(1, units.milliseconds)).in_units_of(units.seconds) == 0.999 + + assert (Quantity(1, units.inches) + Quantity(1, units.feet)).in_units_of(units.inches) == pytest.approx(13, abs=1e-8) + +@pytest.mark.parametrize("unit_in, power, unit_out", [ + (units.meters**2, 1/2, units.meters), + (units.meters**3, 1/3, units.meters), + (units.meters**3, 2/3, units.meters**2), + (units.meters**3, -5/3, units.meters**-5), + (units.none, 1/10, units.none), + (units.none, 19/17, units.none), + (units.none, np.pi, units.none) +]) +def test_good_non_integer_unit_powers(unit_in, power, unit_out): + """ Check that we can do various square and cube root stuff if we need to, + If dimensionless, we should be able to do arbitrary powers + """ + assert unit_in**power == unit_out + +@pytest.mark.parametrize("unit, power", [ + (units.meters, 1/2), + (units.milliohms, 1/3), + (units.meters, 3/2), + (units.meters**2, 2/3) +]) +def test_bad_non_integer_unit_powers(unit, power): + """ Check that we get an error if we try and do something silly with powers""" + with pytest.raises(units.DimensionError): + x = unit**power + + +@pytest.mark.parametrize("unit_1", si.all_si) +@pytest.mark.parametrize("unit_2", si.all_si) +def test_mixed_quantity_add_sub(unit_1, unit_2): + if unit_1.equivalent(unit_2): + assert (Quantity(0, unit_1) + Quantity(0, unit_2)).in_units_of(unit_1) == 0 + + else: + with pytest.raises(UnitError): + Quantity(1, unit_1) + Quantity(1, unit_2) + +def assert_unit_ratio(u1: units.Unit, u2: units.Unit, value: float, abs=1e-9): + """ Helper function for testing units that are multiples of each other """ + + assert u1.equivalent(u2), "Units should be compatible for this test" + assert (Quantity(1, u1) / Quantity(1, u2)).in_units_of(units.none) == pytest.approx(value, abs=abs) + + +def test_american_units(): + assert_unit_ratio(units.feet, units.inches, 12) + assert_unit_ratio(units.yards, units.inches, 36) + assert_unit_ratio(units.miles, units.inches, 63360) + assert_unit_ratio(units.pounds_force_per_square_inch, units.pounds_force / (units.inches**2), 1, abs=1e-5) + +def test_percent(): + assert Quantity(5, units.percent).in_units_of(units.none) == pytest.approx(0.05, 1e-10) + +@pytest.mark.parametrize("unit_1", si.all_si) +@pytest.mark.parametrize("unit_2", si.all_si) +def test_conversion_errors(unit_1, unit_2): + """ Test conversion errors are thrown when units are not compatible """ + + if unit_1 == unit_2: + assert Quantity(1, unit_1).in_units_of(unit_2) == 1 + + else: + with pytest.raises(UnitError): + Quantity(1, units.seconds).in_units_of(units.meters) + diff --git a/sasdata/quantities/quantity.py b/sasdata/quantities/quantity.py new file mode 100644 index 00000000..d30b8799 --- /dev/null +++ b/sasdata/quantities/quantity.py @@ -0,0 +1,1459 @@ +from typing import Self + +import numpy as np +from numpy._typing import ArrayLike + +from sasdata.quantities import units +from sasdata.quantities.numerical_encoding import numerical_decode, numerical_encode +from sasdata.quantities.units import Unit, NamedUnit + +import hashlib + +from typing import Any, TypeVar, Union + +import json + +T = TypeVar("T") + + + + + +################### Quantity based operations, need to be here to avoid cyclic dependencies ##################### + +def transpose(a: Union["Quantity[ArrayLike]", ArrayLike], axes: tuple | None = None): + """ Transpose an array or an array based quantity, can also do reordering of axes""" + if isinstance(a, Quantity): + + if axes is None: + return DerivedQuantity(value=np.transpose(a.value, axes=axes), + units=a.units, + history=QuantityHistory.apply_operation(Transpose, a.history)) + + else: + return DerivedQuantity(value=np.transpose(a.value, axes=axes), + units=a.units, + history=QuantityHistory.apply_operation(Transpose, a.history, axes=axes)) + + else: + return np.transpose(a, axes=axes) + + +def dot(a: Union["Quantity[ArrayLike]", ArrayLike], b: Union["Quantity[ArrayLike]", ArrayLike]): + """ Dot product of two arrays or two array based quantities """ + a_is_quantity = isinstance(a, Quantity) + b_is_quantity = isinstance(b, Quantity) + + if a_is_quantity or b_is_quantity: + + # If its only one of them that is a quantity, convert the other one + + if not a_is_quantity: + a = Quantity(a, units.none) + + if not b_is_quantity: + b = Quantity(b, units.none) + + return DerivedQuantity( + value=np.dot(a.value, b.value), + units=a.units * b.units, + history=QuantityHistory.apply_operation(Dot, a.history, b.history)) + + else: + return np.dot(a, b) + +def tensordot(a: Union["Quantity[ArrayLike]", ArrayLike] | ArrayLike, b: Union["Quantity[ArrayLike]", ArrayLike], a_index: int, b_index: int): + """ Tensor dot product - equivalent to contracting two tensors, such as + + A_{i0, i1, i2, i3...} and B_{j0, j1, j2...} + + e.g. if a_index is 1 and b_index is zero, it will be the sum + + C_{i0, i2, i3 ..., j1, j2 ...} = sum_k A_{i0, k, i2, i3 ...} B_{k, j1, j2 ...} + + (I think, have to check what happens with indices TODO!) + + """ + + a_is_quantity = isinstance(a, Quantity) + b_is_quantity = isinstance(b, Quantity) + + if a_is_quantity or b_is_quantity: + + # If its only one of them that is a quantity, convert the other one + + if not a_is_quantity: + a = Quantity(a, units.none) + + if not b_is_quantity: + b = Quantity(b, units.none) + + return DerivedQuantity( + value=np.tensordot(a.value, b.value, axes=(a_index, b_index)), + units=a.units * b.units, + history=QuantityHistory.apply_operation( + TensorDot, + a.history, + b.history, + a_index=a_index, + b_index=b_index)) + + else: + return np.tensordot(a, b, axes=(a_index, b_index)) + + +################### Operation Definitions ####################################### + +def hash_and_name(hash_or_name: int | str): + """ Infer the name of a variable from a hash, or the hash from the name + + Note: hash_and_name(hash_and_name(number)[1]) is not the identity + however: hash_and_name(hash_and_name(number)) is + """ + + if isinstance(hash_or_name, str): + hash_value = hash(hash_or_name) + name = hash_or_name + + return hash_value, name + + elif isinstance(hash_or_name, int): + hash_value = hash_or_name + name = f"#{hash_or_name}" + + return hash_value, name + + elif isinstance(hash_or_name, tuple): + return hash_or_name + + else: + raise TypeError("Variable name_or_hash_value must be either str or int") + +class Operation: + + serialisation_name = "unknown" + def summary(self, indent_amount: int = 0, indent: str=" "): + """ Summary of the operation tree""" + + s = f"{indent_amount*indent}{self._summary_open()}(\n" + + for chunk in self._summary_components(): + s += chunk.summary(indent_amount+1, indent) + "\n" + + s += f"{indent_amount*indent})" + + return s + def _summary_open(self): + """ First line of summary """ + + def _summary_components(self) -> list["Operation"]: + return [] + def evaluate(self, variables: dict[int, T]) -> T: + + """ Evaluate this operation """ + + def _derivative(self, hash_value: int) -> "Operation": + """ Get the derivative of this operation """ + + def _clean(self): + """ Clean up this operation - i.e. remove silly things like 1*x """ + return self + + def derivative(self, variable: Union[str, int, "Variable"], simplify=True): + if isinstance(variable, Variable): + hash_value = variable.hash_value + else: + hash_value, _ = hash_and_name(variable) + + derivative = self._derivative(hash_value) + + if not simplify: + return derivative + + derivative_string = derivative.serialise() + + # print("---------------") + # print("Base") + # print("---------------") + # print(derivative.summary()) + + # Inefficient way of doing repeated simplification, but it will work + for i in range(100): # set max iterations + + derivative = derivative._clean() + # + # print("-------------------") + # print("Iteration", i+1) + # print("-------------------") + # print(derivative.summary()) + # print("-------------------") + + new_derivative_string = derivative.serialise() + + if derivative_string == new_derivative_string: + break + + derivative_string = new_derivative_string + + return derivative + + @staticmethod + def deserialise(data: str) -> "Operation": + json_data = json.loads(data) + return Operation.deserialise_json(json_data) + + @staticmethod + def deserialise_json(json_data: dict) -> "Operation": + + operation = json_data["operation"] + parameters = json_data["parameters"] + cls = _serialisation_lookup[operation] + + try: + return cls._deserialise(parameters) + + except NotImplementedError: + raise NotImplementedError(f"No method to deserialise {operation} with {parameters} (cls={cls})") + + @staticmethod + def _deserialise(parameters: dict) -> "Operation": + raise NotImplementedError("Deserialise not implemented for this class") + + def serialise(self) -> str: + return json.dumps(self._serialise_json()) + + def _serialise_json(self) -> dict[str, Any]: + return {"operation": self.serialisation_name, + "parameters": self._serialise_parameters()} + + def _serialise_parameters(self) -> dict[str, Any]: + raise NotImplementedError("_serialise_parameters not implemented") + + def __eq__(self, other: "Operation"): + return NotImplemented + +class ConstantBase(Operation): + pass + +class AdditiveIdentity(ConstantBase): + + serialisation_name = "zero" + def evaluate(self, variables: dict[int, T]) -> T: + return 0 + + def _derivative(self, hash_value: int) -> Operation: + return AdditiveIdentity() + + @staticmethod + def _deserialise(parameters: dict) -> "Operation": + return AdditiveIdentity() + + def _serialise_parameters(self) -> dict[str, Any]: + return {} + + def summary(self, indent_amount: int=0, indent=" "): + return f"{indent_amount*indent}0 [Add.Id.]" + + def __eq__(self, other): + if isinstance(other, AdditiveIdentity): + return True + elif isinstance(other, Constant): + if other.value == 0: + return True + + return False + + + +class MultiplicativeIdentity(ConstantBase): + + serialisation_name = "one" + + def evaluate(self, variables: dict[int, T]) -> T: + return 1 + + def _derivative(self, hash_value: int): + return AdditiveIdentity() + + @staticmethod + def _deserialise(parameters: dict) -> "Operation": + return MultiplicativeIdentity() + + + def _serialise_parameters(self) -> dict[str, Any]: + return {} + + + def summary(self, indent_amount: int=0, indent=" "): + return f"{indent_amount*indent}1 [Mul.Id.]" + + def __eq__(self, other): + if isinstance(other, MultiplicativeIdentity): + return True + elif isinstance(other, Constant): + if other.value == 1: + return True + + return False + + +class Constant(ConstantBase): + + serialisation_name = "constant" + def __init__(self, value): + self.value = value + + def summary(self, indent_amount: int = 0, indent: str=" "): + return repr(self.value) + + def evaluate(self, variables: dict[int, T]) -> T: + return self.value + + def _derivative(self, hash_value: int): + return AdditiveIdentity() + + def _clean(self): + + if self.value == 0: + return AdditiveIdentity() + + elif self.value == 1: + return MultiplicativeIdentity() + + else: + return self + + @staticmethod + def _deserialise(parameters: dict) -> "Operation": + value = numerical_decode(parameters["value"]) + return Constant(value) + + + def _serialise_parameters(self) -> dict[str, Any]: + return {"value": numerical_encode(self.value)} + + def summary(self, indent_amount: int=0, indent=" "): + return f"{indent_amount*indent}{self.value}" + + def __eq__(self, other): + if isinstance(other, AdditiveIdentity): + return self.value == 0 + + elif isinstance(other, MultiplicativeIdentity): + return self.value == 1 + + elif isinstance(other, Constant): + if other.value == self.value: + return True + + return False + + +class Variable(Operation): + + serialisation_name = "variable" + def __init__(self, name_or_hash_value: int | str | tuple[int, str]): + self.hash_value, self.name = hash_and_name(name_or_hash_value) + + def evaluate(self, variables: dict[int, T]) -> T: + try: + return variables[self.hash_value] + except KeyError: + raise ValueError(f"Variable dictionary didn't have an entry for {self.name} (hash={self.hash_value})") + + def _derivative(self, hash_value: int) -> Operation: + if hash_value == self.hash_value: + return MultiplicativeIdentity() + else: + return AdditiveIdentity() + + @staticmethod + def _deserialise(parameters: dict) -> "Operation": + hash_value = parameters["hash_value"] + name = parameters["name"] + + return Variable((hash_value, name)) + + def _serialise_parameters(self) -> dict[str, Any]: + return {"hash_value": self.hash_value, + "name": self.name} + + def summary(self, indent_amount: int = 0, indent: str=" "): + return f"{indent_amount*indent}{self.name}" + + def __eq__(self, other): + if isinstance(other, Variable): + return self.hash_value == other.hash_value + + return False + +class UnaryOperation(Operation): + + def __init__(self, a: Operation): + self.a = a + + def _serialise_parameters(self) -> dict[str, Any]: + return {"a": self.a._serialise_json()} + + def _summary_components(self) -> list["Operation"]: + return [self.a] + + + + +class Neg(UnaryOperation): + + serialisation_name = "neg" + def evaluate(self, variables: dict[int, T]) -> T: + return -self.a.evaluate(variables) + + def _derivative(self, hash_value: int): + return Neg(self.a._derivative(hash_value)) + + def _clean(self): + + clean_a = self.a._clean() + + if isinstance(clean_a, Neg): + # Removes double negations + return clean_a.a + + elif isinstance(clean_a, Constant): + return Constant(-clean_a.value)._clean() + + else: + return Neg(clean_a) + + @staticmethod + def _deserialise(parameters: dict) -> "Operation": + return Neg(Operation.deserialise_json(parameters["a"])) + + + def _summary_open(self): + return "Neg" + + def __eq__(self, other): + if isinstance(other, Neg): + return other.a == self.a + + +class Inv(UnaryOperation): + + serialisation_name = "reciprocal" + + def evaluate(self, variables: dict[int, T]) -> T: + return 1/self.a.evaluate(variables) + + def _derivative(self, hash_value: int) -> Operation: + return Neg(Div(self.a._derivative(hash_value), Mul(self.a, self.a))) + + def _clean(self): + clean_a = self.a._clean() + + if isinstance(clean_a, Inv): + # Removes double negations + return clean_a.a + + elif isinstance(clean_a, Neg): + # cannonicalise 1/-a to -(1/a) + # over multiple iterations this should have the effect of ordering and gathering Neg and Inv + return Neg(Inv(clean_a.a)) + + elif isinstance(clean_a, Constant): + return Constant(1/clean_a.value)._clean() + + else: + return Inv(clean_a) + + + @staticmethod + def _deserialise(parameters: dict) -> "Operation": + return Inv(Operation.deserialise_json(parameters["a"])) + + def _summary_open(self): + return "Inv" + + + def __eq__(self, other): + if isinstance(other, Inv): + return other.a == self.a + +class BinaryOperation(Operation): + def __init__(self, a: Operation, b: Operation): + self.a = a + self.b = b + + def _clean(self): + return self._clean_ab(self.a._clean(), self.b._clean()) + + def _clean_ab(self, a, b): + raise NotImplementedError("_clean_ab not implemented") + + def _serialise_parameters(self) -> dict[str, Any]: + return {"a": self.a._serialise_json(), + "b": self.b._serialise_json()} + + @staticmethod + def _deserialise_ab(parameters) -> tuple[Operation, Operation]: + return (Operation.deserialise_json(parameters["a"]), + Operation.deserialise_json(parameters["b"])) + + + def _summary_components(self) -> list["Operation"]: + return [self.a, self.b] + + def _self_cls(self) -> type: + """ Own class""" + def __eq__(self, other): + if isinstance(other, self._self_cls()): + return other.a == self.a and self.b == other.b + +class Add(BinaryOperation): + + serialisation_name = "add" + + def _self_cls(self) -> type: + return Add + def evaluate(self, variables: dict[int, T]) -> T: + return self.a.evaluate(variables) + self.b.evaluate(variables) + + def _derivative(self, hash_value: int) -> Operation: + return Add(self.a._derivative(hash_value), self.b._derivative(hash_value)) + + def _clean_ab(self, a, b): + + if isinstance(a, AdditiveIdentity): + # Convert 0 + b to b + return b + + elif isinstance(b, AdditiveIdentity): + # Convert a + 0 to a + return a + + elif isinstance(a, ConstantBase) and isinstance(b, ConstantBase): + # Convert constant "a"+"b" to "a+b" + return Constant(a.evaluate({}) + b.evaluate({}))._clean() + + elif isinstance(a, Neg): + if isinstance(b, Neg): + # Convert (-a)+(-b) to -(a+b) + return Neg(Add(a.a, b.a)) + else: + # Convert (-a) + b to b-a + return Sub(b, a.a) + + elif isinstance(b, Neg): + # Convert a+(-b) to a-b + return Sub(a, b.a) + + elif a == b: + return Mul(Constant(2), a) + + else: + return Add(a, b) + + @staticmethod + def _deserialise(parameters: dict) -> "Operation": + return Add(*BinaryOperation._deserialise_ab(parameters)) + + def _summary_open(self): + return "Add" + +class Sub(BinaryOperation): + + serialisation_name = "sub" + + + def _self_cls(self) -> type: + return Sub + def evaluate(self, variables: dict[int, T]) -> T: + return self.a.evaluate(variables) - self.b.evaluate(variables) + + def _derivative(self, hash_value: int) -> Operation: + return Sub(self.a._derivative(hash_value), self.b._derivative(hash_value)) + + def _clean_ab(self, a, b): + if isinstance(a, AdditiveIdentity): + # Convert 0 - b to -b + return Neg(b) + + elif isinstance(b, AdditiveIdentity): + # Convert a - 0 to a + return a + + elif isinstance(a, ConstantBase) and isinstance(b, ConstantBase): + # Convert constant pair "a" - "b" to "a-b" + return Constant(a.evaluate({}) - b.evaluate({}))._clean() + + elif isinstance(a, Neg): + if isinstance(b, Neg): + # Convert (-a)-(-b) to b-a + return Sub(b.a, a.a) + else: + # Convert (-a)-b to -(a+b) + return Neg(Add(a.a, b)) + + elif isinstance(b, Neg): + # Convert a-(-b) to a+b + return Add(a, b.a) + + elif a == b: + return AdditiveIdentity() + + else: + return Sub(a, b) + + @staticmethod + def _deserialise(parameters: dict) -> "Operation": + return Sub(*BinaryOperation._deserialise_ab(parameters)) + + + def _summary_open(self): + return "Sub" + +class Mul(BinaryOperation): + + serialisation_name = "mul" + + + def _self_cls(self) -> type: + return Mul + def evaluate(self, variables: dict[int, T]) -> T: + return self.a.evaluate(variables) * self.b.evaluate(variables) + + def _derivative(self, hash_value: int) -> Operation: + return Add(Mul(self.a, self.b._derivative(hash_value)), Mul(self.a._derivative(hash_value), self.b)) + + def _clean_ab(self, a, b): + + if isinstance(a, AdditiveIdentity) or isinstance(b, AdditiveIdentity): + # Convert 0*b or a*0 to 0 + return AdditiveIdentity() + + elif isinstance(a, MultiplicativeIdentity): + # Convert 1*b to b + return b + + elif isinstance(b, MultiplicativeIdentity): + # Convert a*1 to a + return a + + elif isinstance(a, ConstantBase) and isinstance(b, ConstantBase): + # Convert constant "a"*"b" to "a*b" + return Constant(a.evaluate({}) * b.evaluate({}))._clean() + + elif isinstance(a, Inv) and isinstance(b, Inv): + return Inv(Mul(a.a, b.a)) + + elif isinstance(a, Inv) and not isinstance(b, Inv): + return Div(b, a.a) + + elif not isinstance(a, Inv) and isinstance(b, Inv): + return Div(a, b.a) + + elif isinstance(a, Neg): + return Neg(Mul(a.a, b)) + + elif isinstance(b, Neg): + return Neg(Mul(a, b.a)) + + elif a == b: + return Pow(a, 2) + + elif isinstance(a, Pow) and a.a == b: + return Pow(b, a.power + 1) + + elif isinstance(b, Pow) and b.a == a: + return Pow(a, b.power + 1) + + elif isinstance(a, Pow) and isinstance(b, Pow) and a.a == b.a: + return Pow(a.a, a.power + b.power) + + else: + return Mul(a, b) + + + @staticmethod + def _deserialise(parameters: dict) -> "Operation": + return Mul(*BinaryOperation._deserialise_ab(parameters)) + + + def _summary_open(self): + return "Mul" + +class Div(BinaryOperation): + + serialisation_name = "div" + + + def _self_cls(self) -> type: + return Div + + def evaluate(self, variables: dict[int, T]) -> T: + return self.a.evaluate(variables) / self.b.evaluate(variables) + + def _derivative(self, hash_value: int) -> Operation: + return Sub(Div(self.a.derivative(hash_value), self.b), + Div(Mul(self.a, self.b.derivative(hash_value)), Mul(self.b, self.b))) + + def _clean_ab(self, a, b): + if isinstance(a, AdditiveIdentity): + # Convert 0/b to 0 + return AdditiveIdentity() + + elif isinstance(a, MultiplicativeIdentity): + # Convert 1/b to inverse of b + return Inv(b) + + elif isinstance(b, MultiplicativeIdentity): + # Convert a/1 to a + return a + + elif isinstance(a, ConstantBase) and isinstance(b, ConstantBase): + # Convert constants "a"/"b" to "a/b" + return Constant(self.a.evaluate({}) / self.b.evaluate({}))._clean() + + + elif isinstance(a, Inv) and isinstance(b, Inv): + return Div(b.a, a.a) + + elif isinstance(a, Inv) and not isinstance(b, Inv): + return Inv(Mul(a.a, b)) + + elif not isinstance(a, Inv) and isinstance(b, Inv): + return Mul(a, b.a) + + elif a == b: + return MultiplicativeIdentity() + + elif isinstance(a, Pow) and a.a == b: + return Pow(b, a.power - 1) + + elif isinstance(b, Pow) and b.a == a: + return Pow(a, 1 - b.power) + + elif isinstance(a, Pow) and isinstance(b, Pow) and a.a == b.a: + return Pow(a.a, a.power - b.power) + + else: + return Div(a, b) + + + @staticmethod + def _deserialise(parameters: dict) -> "Operation": + return Div(*BinaryOperation._deserialise_ab(parameters)) + + def _summary_open(self): + return "Div" + +class Pow(Operation): + + serialisation_name = "pow" + + def __init__(self, a: Operation, power: float): + self.a = a + self.power = power + + def evaluate(self, variables: dict[int, T]) -> T: + return self.a.evaluate(variables) ** self.power + + def _derivative(self, hash_value: int) -> Operation: + if self.power == 0: + return AdditiveIdentity() + + elif self.power == 1: + return self.a._derivative(hash_value) + + else: + return Mul(Constant(self.power), Mul(Pow(self.a, self.power-1), self.a._derivative(hash_value))) + + def _clean(self) -> Operation: + a = self.a._clean() + + if self.power == 1: + return a + + elif self.power == 0: + return MultiplicativeIdentity() + + elif self.power == -1: + return Inv(a) + + else: + return Pow(a, self.power) + + + def _serialise_parameters(self) -> dict[str, Any]: + return {"a": Operation._serialise_json(self.a), + "power": self.power} + + @staticmethod + def _deserialise(parameters: dict) -> "Operation": + return Pow(Operation.deserialise_json(parameters["a"]), parameters["power"]) + + def summary(self, indent_amount: int=0, indent=" "): + return (f"{indent_amount*indent}Pow\n" + + self.a.summary(indent_amount+1, indent) + "\n" + + f"{(indent_amount+1)*indent}{self.power}\n" + + f"{indent_amount*indent})") + + def __eq__(self, other): + if isinstance(other, Pow): + return self.a == other.a and self.power == other.power + + + +# +# Matrix operations +# + +class Transpose(Operation): + """ Transpose operation - as per numpy""" + + serialisation_name = "transpose" + + def __init__(self, a: Operation, axes: tuple[int] | None = None): + self.a = a + self.axes = axes + + def evaluate(self, variables: dict[int, T]) -> T: + return np.transpose(self.a.evaluate(variables)) + + def _derivative(self, hash_value: int) -> Operation: + return Transpose(self.a.derivative(hash_value)) # TODO: Check! + + def _clean(self): + clean_a = self.a._clean() + return Transpose(clean_a) + + + def _serialise_parameters(self) -> dict[str, Any]: + if self.axes is None: + return { "a": self.a._serialise_json() } + else: + return { + "a": self.a._serialise_json(), + "axes": list(self.axes) + } + + + @staticmethod + def _deserialise(parameters: dict) -> "Operation": + if "axes" in parameters: + return Transpose( + a=Operation.deserialise_json(parameters["a"]), + axes=tuple(parameters["axes"])) + else: + return Transpose( + a=Operation.deserialise_json(parameters["a"])) + + + def _summary_open(self): + return "Transpose" + + def __eq__(self, other): + if isinstance(other, Transpose): + return other.a == self.a + + +class Dot(BinaryOperation): + """ Dot product - backed by numpy's dot method""" + + serialisation_name = "dot" + + def evaluate(self, variables: dict[int, T]) -> T: + return dot(self.a.evaluate(variables), self.b.evaluate(variables)) + + def _derivative(self, hash_value: int) -> Operation: + return Add( + Dot(self.a, + self.b._derivative(hash_value)), + Dot(self.a._derivative(hash_value), + self.b)) + + def _clean_ab(self, a, b): + return Dot(a, b) # Do nothing for now + + + @staticmethod + def _deserialise(parameters: dict) -> "Operation": + return Dot(*BinaryOperation._deserialise_ab(parameters)) + + def _summary_open(self): + return "Dot" + + +# TODO: Add to base operation class, and to quantities +class MatMul(BinaryOperation): + """ Matrix multiplication, using __matmul__ dunder""" + + serialisation_name = "matmul" + + def evaluate(self, variables: dict[int, T]) -> T: + return self.a.evaluate(variables) @ self.b.evaluate(variables) + + def _derivative(self, hash_value: int) -> Operation: + return Add( + MatMul(self.a, + self.b._derivative(hash_value)), + MatMul(self.a._derivative(hash_value), + self.b)) + + def _clean_ab(self, a, b): + + if isinstance(a, AdditiveIdentity) or isinstance(b, AdditiveIdentity): + # Convert 0*b or a*0 to 0 + return AdditiveIdentity() + + elif isinstance(a, ConstantBase) and isinstance(b, ConstantBase): + # Convert constant "a"@"b" to "a@b" + return Constant(a.evaluate({}) @ b.evaluate({}))._clean() + + elif isinstance(a, Neg): + return Neg(Mul(a.a, b)) + + elif isinstance(b, Neg): + return Neg(Mul(a, b.a)) + + return MatMul(a, b) + + + @staticmethod + def _deserialise(parameters: dict) -> "Operation": + return MatMul(*BinaryOperation._deserialise_ab(parameters)) + + def _summary_open(self): + return "MatMul" + +class TensorDot(Operation): + serialisation_name = "tensor_product" + + def __init__(self, a: Operation, b: Operation, a_index: int, b_index: int): + self.a = a + self.b = b + self.a_index = a_index + self.b_index = b_index + + def evaluate(self, variables: dict[int, T]) -> T: + return tensordot(self.a, self.b, self.a_index, self.b_index) + + + def _serialise_parameters(self) -> dict[str, Any]: + return { + "a": self.a._serialise_json(), + "b": self.b._serialise_json(), + "a_index": self.a_index, + "b_index": self.b_index } + + @staticmethod + def _deserialise(parameters: dict) -> "Operation": + return TensorDot(a = Operation.deserialise_json(parameters["a"]), + b = Operation.deserialise_json(parameters["b"]), + a_index=int(parameters["a_index"]), + b_index=int(parameters["b_index"])) + + def _summary_open(self): + return "TensorProduct" + + +_serialisable_classes = [AdditiveIdentity, MultiplicativeIdentity, Constant, + Variable, + Neg, Inv, + Add, Sub, Mul, Div, Pow, + Transpose, Dot, MatMul, TensorDot] + +_serialisation_lookup = {cls.serialisation_name: cls for cls in _serialisable_classes} + + +class UnitError(Exception): + """ Errors caused by unit specification not being correct """ + +def hash_data_via_numpy(*data: ArrayLike): + + md5_hash = hashlib.md5() + + for datum in data: + data_bytes = np.array(datum).tobytes() + md5_hash.update(data_bytes) + + # Hash function returns a hex string, we want an int + return int(md5_hash.hexdigest(), 16) + + + +##################################### +# # +# # +# # +# Quantities begin here # +# # +# # +# # +##################################### + + + +QuantityType = TypeVar("QuantityType") + + +class QuantityHistory: + """ Class that holds the information for keeping track of operations done on quantities """ + + def __init__(self, operation_tree: Operation, references: dict[int, "Quantity"]): + self.operation_tree = operation_tree + self.references = references + + self.reference_key_list = [key for key in self.references] + self.si_reference_values = {key: self.references[key].in_si() for key in self.references} + + def jacobian(self) -> list[Operation]: + """ Derivative of this quantity's operation history with respect to each of the references """ + + # Use the hash value to specify the variable of differentiation + return [self.operation_tree.derivative(key) for key in self.reference_key_list] + + def _recalculate(self): + """ Recalculate the value of this object - primary use case is for testing """ + return self.operation_tree.evaluate(self.references) + + def variance_propagate(self, quantity_units: Unit, covariances: dict[tuple[int, int]: "Quantity"] = {}): + """ Do standard error propagation to calculate the uncertainties associated with this quantity + + :param quantity_units: units in which the output should be calculated + :param covariances: off diagonal entries for the covariance matrix + """ + + if covariances: + raise NotImplementedError("User specified covariances not currently implemented") + + jacobian = self.jacobian() + + evaluated_jacobian = [entry.evaluate(self.references) for entry in jacobian] + + hash_values = [key for key in self.references] + output = None + + for hash_value, jac_component in zip(hash_values, evaluated_jacobian): + if output is None: + output = jac_component * (self.references[hash_value].variance * jac_component) + else: + output += jac_component * (self.references[hash_value].variance * jac_component) + + return output + + + @staticmethod + def variable(quantity: "Quantity"): + """ Create a history that starts with the provided data """ + return QuantityHistory(Variable(quantity.hash_value), {quantity.hash_value: quantity}) + + @staticmethod + def apply_operation(operation: type[Operation], *histories: "QuantityHistory", **extra_parameters) -> "QuantityHistory": + """ Apply an operation to the history + + This is slightly unsafe as it is possible to attempt to apply an n-ary operation to a number of trees other + than n, but it is relatively concise. Because it is concise we'll go with this for now and see if it causes + any problems down the line. It is a private static method to discourage misuse. + + """ + + # Copy references over, even though it overrides on collision, + # this should behave because only data based variables should be represented. + # Should not be a problem any more than losing histories + references = {} + for history in histories: + references.update(history.references) + + return QuantityHistory( + operation(*[history.operation_tree for history in histories], **extra_parameters), + references) + + def has_variance(self): + for key in self.references: + if self.references[key].has_variance: + return True + + return False + + def summary(self): + + variable_strings = [self.references[key].string_repr for key in self.references] + + s = "Variables: "+",".join(variable_strings) + s += "\n" + s += self.operation_tree.summary() + + return s + + +class Quantity[QuantityType]: + + + def __init__(self, + value: QuantityType, + units: Unit, + standard_error: QuantityType | None = None, + hash_seed = ""): + + self.value = value + """ Numerical value of this data, in the specified units""" + + self.units = units + """ Units of this data """ + + self._hash_seed = hash_seed + """ Retain this for copying operations""" + + self.hash_value = -1 + """ Hash based on value and uncertainty for data, -1 if it is a derived hash value """ + + self._variance = None + """ Contains the variance if it is data driven """ + + if standard_error is None: + self.hash_value = hash_data_via_numpy(hash_seed, value) + else: + self._variance = standard_error ** 2 + self.hash_value = hash_data_via_numpy(hash_seed, value, standard_error) + + self.history = QuantityHistory.variable(self) + + # TODO: Adding this method as a temporary measure but we need a single + # method that does this. + def with_standard_error(self, standard_error: "Quantity"): + if standard_error.units.equivalent(self.units): + return Quantity( + value=self.value, + units=self.units, + standard_error=standard_error.in_units_of(self.units),) + else: + raise UnitError(f"Standard error units ({standard_error.units}) " + f"are not compatible with value units ({self.units})") + + @property + def has_variance(self): + return self._variance is not None + + @property + def variance(self) -> "Quantity": + """ Get the variance of this object""" + if self._variance is None: + return Quantity(np.zeros_like(self.value), self.units**2) + else: + return Quantity(self._variance, self.units**2) + + def standard_deviation(self) -> "Quantity": + return self.variance ** 0.5 + + def in_units_of(self, units: Unit) -> QuantityType: + """ Get this quantity in other units """ + if self.units.equivalent(units): + return (self.units.scale / units.scale) * self.value + else: + raise UnitError(f"Target units ({units}) not compatible with existing units ({self.units}).") + + def to_units_of(self, new_units: Unit) -> "Quantity[QuantityType]": + new_value, new_error = self.in_units_of_with_standard_error(new_units) + return Quantity(value=new_value, + units=new_units, + standard_error=new_error, + hash_seed=self._hash_seed) + + def variance_in_units_of(self, units: Unit) -> QuantityType: + """ Get the variance of quantity in other units """ + variance = self.variance + if variance.units.equivalent(units): + return (variance.units.scale / units.scale) * variance + else: + raise UnitError(f"Target units ({units}) not compatible with existing units ({variance.units}).") + + def in_si(self): + si_units = self.units.si_equivalent() + return self.in_units_of(si_units) + + def in_units_of_with_standard_error(self, units): + variance = self.variance + units_squared = units**2 + + if variance.units.equivalent(units_squared): + + return self.in_units_of(units), np.sqrt(self.variance.in_units_of(units_squared)) + else: + raise UnitError(f"Target units ({units}) not compatible with existing units ({variance.units}).") + + def in_si_with_standard_error(self): + if self.has_variance: + return self.in_units_of_with_standard_error(self.units.si_equivalent()) + else: + return self.in_si(), None + + def __eq__(self: Self, other: Self) -> bool | np.ndarray: + return self.value == other.in_units_of(self.units) + + + def __mul__(self: Self, other: ArrayLike | Self ) -> Self: + if isinstance(other, Quantity): + return DerivedQuantity( + self.value * other.value, + self.units * other.units, + history=QuantityHistory.apply_operation(Mul, self.history, other.history)) + + else: + return DerivedQuantity(self.value * other, self.units, + QuantityHistory( + Mul( + self.history.operation_tree, + Constant(other)), + self.history.references)) + + def __rmul__(self: Self, other: ArrayLike | Self): + if isinstance(other, Quantity): + return DerivedQuantity( + other.value * self.value, + other.units * self.units, + history=QuantityHistory.apply_operation( + Mul, + other.history, + self.history)) + + else: + return DerivedQuantity(other * self.value, self.units, + QuantityHistory( + Mul( + Constant(other), + self.history.operation_tree), + self.history.references)) + + + def __matmul__(self, other: ArrayLike | Self): + if isinstance(other, Quantity): + return DerivedQuantity( + self.value @ other.value, + self.units * other.units, + history=QuantityHistory.apply_operation( + MatMul, + self.history, + other.history)) + else: + return DerivedQuantity( + self.value @ other, + self.units, + QuantityHistory( + MatMul( + self.history.operation_tree, + Constant(other)), + self.history.references)) + + def __rmatmul__(self, other: ArrayLike | Self): + if isinstance(other, Quantity): + return DerivedQuantity( + other.value @ self.value, + other.units * self.units, + history=QuantityHistory.apply_operation( + MatMul, + other.history, + self.history)) + + else: + return DerivedQuantity(other @ self.value, self.units, + QuantityHistory( + MatMul( + Constant(other), + self.history.operation_tree), + self.history.references)) + + + def __truediv__(self: Self, other: float | Self) -> Self: + if isinstance(other, Quantity): + return DerivedQuantity( + self.value / other.value, + self.units / other.units, + history=QuantityHistory.apply_operation( + Div, + self.history, + other.history)) + + else: + return DerivedQuantity(self.value / other, self.units, + QuantityHistory( + Div( + Constant(other), + self.history.operation_tree), + self.history.references)) + + def __rtruediv__(self: Self, other: float | Self) -> Self: + if isinstance(other, Quantity): + return DerivedQuantity( + other.value / self.value, + other.units / self.units, + history=QuantityHistory.apply_operation( + Div, + other.history, + self.history + )) + + else: + return DerivedQuantity( + other / self.value, + self.units ** -1, + QuantityHistory( + Div( + Constant(other), + self.history.operation_tree), + self.history.references)) + + def __add__(self: Self, other: Self | ArrayLike) -> Self: + if isinstance(other, Quantity): + if self.units.equivalent(other.units): + return DerivedQuantity( + self.value + (other.value * other.units.scale) / self.units.scale, + self.units, + QuantityHistory.apply_operation( + Add, + self.history, + other.history)) + else: + raise UnitError(f"Units do not have the same dimensionality: {self.units} vs {other.units}") + + else: + raise UnitError(f"Cannot perform addition/subtraction non-quantity {type(other)} with quantity") + + # Don't need __radd__ because only quantity/quantity operations should be allowed + + def __neg__(self): + return DerivedQuantity(-self.value, self.units, + QuantityHistory.apply_operation( + Neg, + self.history + )) + + def __sub__(self: Self, other: Self | ArrayLike) -> Self: + return self + (-other) + + def __rsub__(self: Self, other: Self | ArrayLike) -> Self: + return (-self) + other + + def __pow__(self: Self, other: int | float): + return DerivedQuantity(self.value ** other, + self.units ** other, + QuantityHistory( + Pow( + self.history.operation_tree, + other), + self.history.references)) + + @staticmethod + def _array_repr_format(arr: np.ndarray): + """ Format the array """ + order = len(arr.shape) + reshaped = arr.reshape(-1) + if len(reshaped) <= 2: + numbers = ",".join([f"{n}" for n in reshaped]) + else: + numbers = f"{reshaped[0]} ... {reshaped[-1]}" + + # if len(reshaped) <= 4: + # numbers = ",".join([f"{n}" for n in reshaped]) + # else: + # numbers = f"{reshaped[0]}, {reshaped[1]} ... {reshaped[-2]}, {reshaped[-1]}" + + return "["*order + numbers + "]"*order + + def __repr__(self): + + if isinstance(self.units, NamedUnit): + + value = self.value + error = self.standard_deviation().in_units_of(self.units) + unit_string = self.units.symbol + + else: + value, error = self.in_si_with_standard_error() + unit_string = self.units.dimensions.si_repr() + + if isinstance(self.value, np.ndarray): + # Get the array in short form + numeric_string = self._array_repr_format(value) + + if self.has_variance: + numeric_string += " ± " + self._array_repr_format(error) + + else: + numeric_string = f"{value}" + if self.has_variance: + numeric_string += f" ± {error}" + + return numeric_string + " " + unit_string + + @staticmethod + def parse(number_or_string: str | ArrayLike, unit: str, absolute_temperature: False): + pass + + @property + def string_repr(self): + return str(self.hash_value) + + +class NamedQuantity[QuantityType](Quantity[QuantityType]): + def __init__(self, + name: str, + value: QuantityType, + units: Unit, + standard_error: QuantityType | None = None): + + super().__init__(value, units, standard_error=standard_error, hash_seed=name) + self.name = name + + def __repr__(self): + return f"[{self.name}] " + super().__repr__() + + def to_units_of(self, new_units: Unit) -> "NamedQuantity[QuantityType]": + new_value, new_error = self.in_units_of_with_standard_error(new_units) + return NamedQuantity(value=new_value, + units=new_units, + standard_error=new_error, + name=self.name) + + def with_standard_error(self, standard_error: Quantity): + if standard_error.units.equivalent(self.units): + return NamedQuantity( + value=self.value, + units=self.units, + standard_error=standard_error.in_units_of(self.units), + name=self.name) + + else: + raise UnitError(f"Standard error units ({standard_error.units}) " + f"are not compatible with value units ({self.units})") + + + @property + def string_repr(self): + return self.name + +class DerivedQuantity[QuantityType](Quantity[QuantityType]): + def __init__(self, value: QuantityType, units: Unit, history: QuantityHistory): + super().__init__(value, units, standard_error=None) + + self.history = history + self._variance_cache = None + self._has_variance = history.has_variance() + + + def to_units_of(self, new_units: Unit) -> "Quantity[QuantityType]": + # TODO: Lots of tests needed for this + return DerivedQuantity( + value=self.in_units_of(new_units), + units=new_units, + history=self.history) + + @property + def has_variance(self): + return self._has_variance + + @property + def variance(self) -> Quantity: + if self._variance_cache is None: + self._variance_cache = self.history.variance_propagate(self.units) + + return self._variance_cache diff --git a/sasdata/quantities/quantity_error_tests.py b/sasdata/quantities/quantity_error_tests.py new file mode 100644 index 00000000..e8e9378f --- /dev/null +++ b/sasdata/quantities/quantity_error_tests.py @@ -0,0 +1,154 @@ +from sasdata.quantities import units +from sasdata.quantities.quantity import NamedQuantity +import pytest +import numpy as np + +@pytest.mark.parametrize("x_err, y_err, x_units, y_units", + [(1, 1, units.meters, units.meters), + (1, 1, units.centimeters, units.centimeters), + (1, 2, units.meters, units.millimeters)]) +def test_addition_propagation(x_err, y_err, x_units, y_units): + """ Test that errors in addition of independent variables works with different units in the mix""" + + expected_err = np.sqrt((x_err*x_units.scale)**2 + (y_err*y_units.scale)**2) + + x = NamedQuantity("x", 0, x_units, standard_error=x_err) + y = NamedQuantity("y", 0, y_units, standard_error=y_err) + + _, err = (x + y).in_si_with_standard_error() + + assert err == pytest.approx(expected_err, abs=1e-8) + +@pytest.mark.parametrize("x_val, y_val, x_units, y_units", + [(1, 1, units.meters, units.meters), + (1, 1, units.centimeters, units.centimeters), + (2, 2, units.meters, units.meters), + (1, 2, units.centimeters, units.centimeters), + (1, 2, units.meters, units.millimeters), + (3, 4, units.milliseconds, units.microseconds), + (0, 1, units.meters, units.meters)]) +def test_asymmetry_propagation(x_val, y_val, x_units, y_units): + + x = NamedQuantity("x", x_val, x_units, standard_error=np.sqrt(x_val)) + y = NamedQuantity("y", y_val, y_units, standard_error=np.sqrt(y_val)) + + x_si, x_err = x.in_si_with_standard_error() + y_si, y_err = y.in_si_with_standard_error() + + numerator = x-y + denominator = x+y + a = numerator/denominator + + # Check numerator and denominator + expected_error = np.sqrt(x_err ** 2 + y_err ** 2) + + value, error = numerator.in_si_with_standard_error() + assert error == pytest.approx(expected_error, rel=1e-6) + + value, error = denominator.in_si_with_standard_error() + assert error == pytest.approx(expected_error, rel=1e-6) + + # check whole thing + value, error = a.in_si_with_standard_error() + expected_error = (2 / (x_si + y_si)**2) * np.sqrt((x_err*y_si)**2 + (y_err*x_si)**2) + assert error == pytest.approx(expected_error, rel=1e-6) + +@pytest.mark.parametrize("x_val, y_val, x_units, y_units", + [(1, 1, units.meters, units.meters), + (1, 1, units.centimeters, units.centimeters), + (2, 2, units.meters, units.meters), + (1, 2, units.centimeters, units.centimeters), + (1, 2, units.meters, units.millimeters), + (3, 4, units.milliseconds, units.microseconds), + (0, 1, units.meters, units.meters)]) +def test_power_propagation(x_val, y_val, x_units, y_units): + + x = NamedQuantity("x", x_val, x_units, standard_error=np.sqrt(x_val)) + y = NamedQuantity("y", y_val, y_units, standard_error=np.sqrt(y_val)) + + x_si, x_err = x.in_si_with_standard_error() + y_si, y_err = y.in_si_with_standard_error() + + x_var = x_err ** 2 + y_var = y_err ** 2 + + z = (x*y)**3 + + # check whole thing + value, error = z.in_si_with_standard_error() + expected_variance = 9*((x_si*y_si)**4)*(x_var*y_si*y_si + x_si*x_si*y_var) + assert error == pytest.approx(np.sqrt(expected_variance), rel=1e-6) + +@pytest.mark.parametrize("k", [0.1, 0.5, 1, 2, 10]) +@pytest.mark.parametrize("x_val, y_val, x_units, y_units", + [(1, 1, units.meters, units.meters), + (1, 1, units.centimeters, units.centimeters), + (2, 2, units.meters, units.meters), + (1, 2, units.centimeters, units.centimeters), + (1, 2, units.meters, units.millimeters), + (3, 4, units.milliseconds, units.microseconds), + (0, 1, units.meters, units.meters), + (0, 0, units.meters, units.meters)]) +def test_complex_power_propagation(x_val, y_val, x_units, y_units, k): + + x = NamedQuantity("x", x_val, x_units, standard_error=np.sqrt(k*x_val)) + y = NamedQuantity("y", y_val, y_units, standard_error=np.sqrt(k*y_val)) + + x_si, x_err = x.in_si_with_standard_error() + y_si, y_err = y.in_si_with_standard_error() + + x_var = x_err ** 2 + y_var = y_err ** 2 + + z = (x+y)**3 + x**3 + y**3 + + value, error = z.in_si_with_standard_error() + expected_variance = \ + 9*x_var*(x_si**2 + (x_si+y_si)**2)**2 + \ + 9*y_var*(y_si**2 + (x_si+y_si)**2)**2 + + assert error == pytest.approx(np.sqrt(expected_variance), rel=1e-6) + +@pytest.mark.parametrize("k_x", [0.1, 0.5, 1, 2, 10]) +@pytest.mark.parametrize("k_y", [0.1, 0.5, 1, 2, 10]) +@pytest.mark.parametrize("x_val, y_val, x_units, y_units", + [(1, 1, units.meters, units.meters), + (1, 1, units.centimeters, units.centimeters), + (2, 2, units.meters, units.meters), + (1, 2, units.centimeters, units.centimeters), + (1, 2, units.meters, units.millimeters), + (3, 4, units.milliseconds, units.microseconds), + (0, 1, units.meters, units.meters), + (0, 0, units.meters, units.meters)]) +def test_complex_propagation(x_val, y_val, x_units, y_units, k_x, k_y): + + x = NamedQuantity("x", x_val, x_units, standard_error=np.sqrt(k_x*x_val)) + y = NamedQuantity("y", y_val, y_units, standard_error=np.sqrt(k_y*y_val)) + + cx = NamedQuantity("cx", 1.7, x_units) + cy = NamedQuantity("cy", 1.2, y_units) + c0 = 4*NamedQuantity("c0", value=7, units=units.none) + + cx_si = cx.in_si() + cy_si = cy.in_si() + + c0_si = c0.in_si() + + x_si, x_err = x.in_si_with_standard_error() + y_si, y_err = y.in_si_with_standard_error() + + x_var = x_err ** 2 + y_var = y_err ** 2 + + z = (((x-cx)**4 + (y-cy)**4)**(1/4)) + c0*(-x-y) + + value, error = z.in_si_with_standard_error() + + denom_factor = ((x_si - cx_si)**4 + (y_si - cy_si)**4)**(-3/4) + x_num = (cx_si - x_si)**3 + y_num = (cy_si - y_si)**3 + + expected_variance = x_var*(c0_si + x_num*denom_factor)**2 + y_var*(c0_si + y_num*denom_factor)**2 + + assert error == pytest.approx(np.sqrt(expected_variance), rel=1e-8) + diff --git a/sasdata/quantities/quantity_examples.py b/sasdata/quantities/quantity_examples.py new file mode 100644 index 00000000..3675d62f --- /dev/null +++ b/sasdata/quantities/quantity_examples.py @@ -0,0 +1,8 @@ +from sasdata.quantities.quantity import NamedQuantity +from sasdata.quantities import units + +x = NamedQuantity("x", 1, units.meters, standard_error=1) +y = NamedQuantity("y", 1, units.decimeters, standard_error=1) + +print(x+y) +print((x+y).to_units_of(units.centimeters)) \ No newline at end of file diff --git a/sasdata/quantities/si.py b/sasdata/quantities/si.py new file mode 100644 index 00000000..871b6ee2 --- /dev/null +++ b/sasdata/quantities/si.py @@ -0,0 +1,119 @@ +""" + +This file is autogenerated! + +Do not edit by hand, instead edit the files that build it (_build_tables.py) + + + + +DDDDDDDDDDDDD NNNNNNNN NNNNNNNN tttt +D::::::::::::DDD N:::::::N N::::::N ttt:::t +D:::::::::::::::DD N::::::::N N::::::N t:::::t +DDD:::::DDDDD:::::D N:::::::::N N::::::N t:::::t + D:::::D D:::::D ooooooooooo N::::::::::N N::::::N ooooooooooo ttttttt:::::ttttttt + D:::::D D:::::D oo:::::::::::oo N:::::::::::N N::::::N oo:::::::::::oo t:::::::::::::::::t + D:::::D D:::::Do:::::::::::::::o N:::::::N::::N N::::::No:::::::::::::::ot:::::::::::::::::t + D:::::D D:::::Do:::::ooooo:::::o N::::::N N::::N N::::::No:::::ooooo:::::otttttt:::::::tttttt + D:::::D D:::::Do::::o o::::o N::::::N N::::N:::::::No::::o o::::o t:::::t + D:::::D D:::::Do::::o o::::o N::::::N N:::::::::::No::::o o::::o t:::::t + D:::::D D:::::Do::::o o::::o N::::::N N::::::::::No::::o o::::o t:::::t + D:::::D D:::::D o::::o o::::o N::::::N N:::::::::No::::o o::::o t:::::t tttttt +DDD:::::DDDDD:::::D o:::::ooooo:::::o N::::::N N::::::::No:::::ooooo:::::o t::::::tttt:::::t +D:::::::::::::::DD o:::::::::::::::o N::::::N N:::::::No:::::::::::::::o tt::::::::::::::t +D::::::::::::DDD oo:::::::::::oo N::::::N N::::::N oo:::::::::::oo tt:::::::::::tt +DDDDDDDDDDDDD ooooooooooo NNNNNNNN NNNNNNN ooooooooooo ttttttttttt + + + + + + + + + dddddddd +EEEEEEEEEEEEEEEEEEEEEE d::::::d iiii tttt BBBBBBBBBBBBBBBBB +E::::::::::::::::::::E d::::::d i::::i ttt:::t B::::::::::::::::B +E::::::::::::::::::::E d::::::d iiii t:::::t B::::::BBBBBB:::::B +EE::::::EEEEEEEEE::::E d:::::d t:::::t BB:::::B B:::::B + E:::::E EEEEEE ddddddddd:::::d iiiiiiittttttt:::::ttttttt B::::B B:::::Byyyyyyy yyyyyyy + E:::::E dd::::::::::::::d i:::::it:::::::::::::::::t B::::B B:::::B y:::::y y:::::y + E::::::EEEEEEEEEE d::::::::::::::::d i::::it:::::::::::::::::t B::::BBBBBB:::::B y:::::y y:::::y + E:::::::::::::::E d:::::::ddddd:::::d i::::itttttt:::::::tttttt B:::::::::::::BB y:::::y y:::::y + E:::::::::::::::E d::::::d d:::::d i::::i t:::::t B::::BBBBBB:::::B y:::::y y:::::y + E::::::EEEEEEEEEE d:::::d d:::::d i::::i t:::::t B::::B B:::::B y:::::y y:::::y + E:::::E d:::::d d:::::d i::::i t:::::t B::::B B:::::B y:::::y:::::y + E:::::E EEEEEEd:::::d d:::::d i::::i t:::::t tttttt B::::B B:::::B y:::::::::y +EE::::::EEEEEEEE:::::Ed::::::ddddd::::::ddi::::::i t::::::tttt:::::t BB:::::BBBBBB::::::B y:::::::y +E::::::::::::::::::::E d:::::::::::::::::di::::::i tt::::::::::::::t B:::::::::::::::::B y:::::y +E::::::::::::::::::::E d:::::::::ddd::::di::::::i tt:::::::::::tt B::::::::::::::::B y:::::y +EEEEEEEEEEEEEEEEEEEEEE ddddddddd dddddiiiiiiii ttttttttttt BBBBBBBBBBBBBBBBB y:::::y + y:::::y + y:::::y + y:::::y + y:::::y + yyyyyyy + + + + dddddddd +HHHHHHHHH HHHHHHHHH d::::::d +H:::::::H H:::::::H d::::::d +H:::::::H H:::::::H d::::::d +HH::::::H H::::::HH d:::::d + H:::::H H:::::H aaaaaaaaaaaaa nnnn nnnnnnnn ddddddddd:::::d + H:::::H H:::::H a::::::::::::a n:::nn::::::::nn dd::::::::::::::d + H::::::HHHHH::::::H aaaaaaaaa:::::an::::::::::::::nn d::::::::::::::::d + H:::::::::::::::::H a::::ann:::::::::::::::nd:::::::ddddd:::::d + H:::::::::::::::::H aaaaaaa:::::a n:::::nnnn:::::nd::::::d d:::::d + H::::::HHHHH::::::H aa::::::::::::a n::::n n::::nd:::::d d:::::d + H:::::H H:::::H a::::aaaa::::::a n::::n n::::nd:::::d d:::::d + H:::::H H:::::H a::::a a:::::a n::::n n::::nd:::::d d:::::d +HH::::::H H::::::HHa::::a a:::::a n::::n n::::nd::::::ddddd::::::dd +H:::::::H H:::::::Ha:::::aaaa::::::a n::::n n::::n d:::::::::::::::::d +H:::::::H H:::::::H a::::::::::aa:::a n::::n n::::n d:::::::::ddd::::d +HHHHHHHHH HHHHHHHHH aaaaaaaaaa aaaa nnnnnn nnnnnn ddddddddd ddddd + + + +""" + +from sasdata.quantities.units import meters +from sasdata.quantities.units import seconds +from sasdata.quantities.units import amperes +from sasdata.quantities.units import kelvin +from sasdata.quantities.units import hertz +from sasdata.quantities.units import newtons +from sasdata.quantities.units import pascals +from sasdata.quantities.units import joules +from sasdata.quantities.units import watts +from sasdata.quantities.units import coulombs +from sasdata.quantities.units import volts +from sasdata.quantities.units import ohms +from sasdata.quantities.units import farads +from sasdata.quantities.units import siemens +from sasdata.quantities.units import webers +from sasdata.quantities.units import tesla +from sasdata.quantities.units import henry +from sasdata.quantities.units import kilograms + +all_si = [ + meters, + seconds, + amperes, + kelvin, + hertz, + newtons, + pascals, + joules, + watts, + coulombs, + volts, + ohms, + farads, + siemens, + webers, + tesla, + henry, + kilograms, +] diff --git a/sasdata/quantities/test_numerical_encoding.py b/sasdata/quantities/test_numerical_encoding.py new file mode 100644 index 00000000..80cfbad9 --- /dev/null +++ b/sasdata/quantities/test_numerical_encoding.py @@ -0,0 +1,68 @@ +""" Tests for the encoding and decoding of numerical data""" + +import numpy as np +import pytest + +from sasdata.quantities.numerical_encoding import numerical_encode, numerical_decode + + +@pytest.mark.parametrize("value", [-100.0, -10.0, -1.0, 0.0, 0.5, 1.0, 10.0, 100.0, 1e100]) +def test_float_encode_decode(value: float): + + assert isinstance(value, float) # Make sure we have the right inputs + + encoded = numerical_encode(value) + decoded = numerical_decode(encoded) + + assert isinstance(decoded, float) + assert value == decoded + +@pytest.mark.parametrize("value", [-100, -10, -1, 0, 1, 10, 100, 1000000000000000000000000000000000]) +def test_int_encode_decode(value: int): + + assert isinstance(value, int) # Make sure we have the right inputs + + encoded = numerical_encode(value) + decoded = numerical_decode(encoded) + + assert isinstance(decoded, int) + assert value == decoded + +@pytest.mark.parametrize("shape", [ + (2,3,4), + (1,2), + (10,5,10), + (1,), + (4,), + (0, ) ]) +def test_numpy_float_encode_decode(shape): + np.random.seed(1776) + test_matrix = np.random.rand(*shape) + + encoded = numerical_encode(test_matrix) + decoded = numerical_decode(encoded) + + assert decoded.dtype == test_matrix.dtype + assert decoded.shape == test_matrix.shape + assert np.all(decoded == test_matrix) + +@pytest.mark.parametrize("dtype", [int, float, complex]) +def test_numpy_dtypes_encode_decode(dtype): + test_matrix = np.zeros((3,3), dtype=dtype) + + encoded = numerical_encode(test_matrix) + decoded = numerical_decode(encoded) + + assert decoded.dtype == test_matrix.dtype + +@pytest.mark.parametrize("dtype", [int, float, complex]) +@pytest.mark.parametrize("shape, n, m", [ + ((8, 8), (1,3,5),(2,5,7)), + ((6, 8), (1,0,5),(0,5,0)), + ((6, 1), (1, 0, 5), (0, 0, 0)), +]) +def test_coo_matrix_encode_decode(shape, n, m, dtype): + + i_indices = + + values = np.arange(10) \ No newline at end of file diff --git a/sasdata/quantities/unicode_superscript.py b/sasdata/quantities/unicode_superscript.py new file mode 100644 index 00000000..81f90f2d --- /dev/null +++ b/sasdata/quantities/unicode_superscript.py @@ -0,0 +1,12 @@ + +_ascii_version = "0123456789-" +_unicode_version = "⁰¹²³⁴⁵⁶⁷⁸⁹⁻" + +def int_as_unicode_superscript(number: int): + string = str(number) + + for old, new in zip(_ascii_version, _unicode_version): + string = string.replace(old, new) + + return string + diff --git a/sasdata/quantities/unit_formatting.py b/sasdata/quantities/unit_formatting.py new file mode 100644 index 00000000..59aa3bc5 --- /dev/null +++ b/sasdata/quantities/unit_formatting.py @@ -0,0 +1,12 @@ + +import numpy as np + +def solve_contributions(target: float, scales: list[float], max_power: int=4, tol=1e-5): + log_target = np.log10(target) + log_scale_pairs = sorted([(i, np.log10(scale)) for i, scale in enumerate(scales)], key=lambda x: x[1]) + + ordering = [i for i, _ in log_scale_pairs] + log_scale = [l for _, l in log_scale_pairs] + + powers = [0 for _ in scales] + diff --git a/sasdata/quantities/unit_parser.py b/sasdata/quantities/unit_parser.py new file mode 100644 index 00000000..0252c36b --- /dev/null +++ b/sasdata/quantities/unit_parser.py @@ -0,0 +1,186 @@ +from sasdata.quantities.units import Dimensions, NamedUnit, Unit, symbol_lookup, unit_groups, UnitGroup +from re import findall, fullmatch + +# TODO: This shouldn't be in this file but I don't want to edit Lucas' code before he is finished. + +all_units_groups = [group.units for group in unit_groups.values()] +unit_groups_by_dimension_hash = {hash(group.units[0].dimensions): group for group in unit_groups.values()} +all_units: list[NamedUnit] = [] +for group in all_units_groups: + all_units.extend(group) + +def split_unit_str(unit_str: str) -> list[str]: + """Separate the letters from the numbers in unit_str""" + return findall(r'[A-Za-zΩ%Å]+|[-\d]+|/', unit_str) + +def validate_unit_str(unit_str: str) -> bool: + """Validate whether unit_str is valid. This doesn't mean that the unit specified in unit_str exists but rather it + only consists of letters, and numbers as a unit string should.""" + return fullmatch(r'[A-Za-zΩ%Å^1-9\-\+/\ \.]+', unit_str) is not None + +def parse_single_unit(unit_str: str, unit_group: UnitGroup | None = None, longest_unit: bool = True) -> tuple[Unit | None, str]: + """Attempts to find a single unit for unit_str. Return this unit, and the remaining string in a tuple. If a unit + cannot be parsed, the unit will be None, and the remaining string will be the entire unit_str. + + The shortest_unit parameter specifies how to resolve ambiguities. If it is true, then it will parse the longest unit + available. Otherwise, it will stop parsing as soon as it has found any unit. + + If unit_group is set, it will only try to parse units within that group. This is useful for resolving ambiguities. + """ + current_unit = '' + string_pos = 0 + if unit_group is None: + lookup_dict = symbol_lookup + else: + lookup_dict = dict([(name, unit) for name, unit in symbol_lookup.items() if unit in unit_group.units]) + for next_char in unit_str: + potential_unit_str = current_unit + next_char + potential_symbols = [symbol for symbol, unit in lookup_dict.items() + if symbol.startswith(potential_unit_str) + or unit.startswith(potential_unit_str)] + if len(potential_symbols) == 0: + break + string_pos += 1 + current_unit = potential_unit_str + if not longest_unit and current_unit in lookup_dict.keys(): + break + if current_unit == '': + return None, unit_str + matching_types = [unit for symbol, unit in lookup_dict.items() + if symbol == current_unit or unit == current_unit] + if not matching_types: + raise KeyError(f"No known type matching {current_unit}") + final_unit = matching_types[0] + remaining_str = unit_str[string_pos::] + return final_unit, remaining_str + +def parse_unit_strs(unit_str: str, current_units: list[Unit] | None=None, longest_unit: bool = True) -> list[Unit]: + """Recursively parse units from unit_str until no more characters are present.""" + if current_units is None: + current_units = [] + if unit_str == '': + return current_units + parsed_unit, remaining_str = parse_single_unit(unit_str, longest_unit=longest_unit) + if parsed_unit is not None: + current_units += [parsed_unit] + return parse_unit_strs(remaining_str, current_units, longest_unit) + else: + raise ValueError(f'Could not interpret {remaining_str}') + +# Its probably useful to work out the unit first, and then later work out if a named unit exists for it. Hence why there +# are two functions. + +def parse_unit_stack(unit_str: str, longest_unit: bool = True) -> list[Unit]: + """Split unit_str into a stack of parsed units.""" + unit_stack: list[Unit] = [] + split_str = split_unit_str(unit_str) + inverse_next_unit = False + for token in split_str: + try: + if token == '/': + inverse_next_unit = True + continue + power = int(token) + to_modify = unit_stack[-1] + modified = to_modify ** power + # modified = unit_power(to_modify, power) + unit_stack[-1] = modified + except ValueError: + new_units = parse_unit_strs(token, None, longest_unit) + if inverse_next_unit: + # TODO: Assume the power is going to be -1. This might not be true. + power = -1 + new_units[0] = new_units[0] ** power + # new_units[0] = unit_power(new_units[0], power) + unit_stack += new_units + # This error will happen if it tries to read a modifier but there are no units on the stack. We will just have + # to ignore it. Strings being parsed shouldn't really have it anyway (e.g. -1m). + except IndexError: + pass + return unit_stack + +def parse_unit(unit_str: str, longest_unit: bool = True) -> Unit: + """Parse unit_str into a unit.""" + try: + if not validate_unit_str(unit_str): + raise ValueError('unit_str contains forbidden characters.') + parsed_unit = Unit(1, Dimensions()) + unit_stack = parse_unit_stack(unit_str, longest_unit) + for unit in unit_stack: + # parsed_unit = combine_units(parsed_unit, unit) + parsed_unit *= unit + return parsed_unit + except KeyError: + raise ValueError(f'Unit string contains an unrecognised pattern: {unit_str}') + +def parse_unit_from_group(unit_str: str, from_group: UnitGroup) -> Unit | None: + """Tries to use the given unit group to resolve ambiguities. Parse a unit twice with different options, and returns + whatever conforms to the unit group.""" + longest_parsed_unit = parse_unit(unit_str, True) + shortest_parsed_unit = parse_unit(unit_str, False) + if longest_parsed_unit in from_group.units: + return longest_parsed_unit + elif shortest_parsed_unit in from_group.units: + return shortest_parsed_unit + else: + return None + +def parse_named_unit(unit_string: str, rtol: float=1e-14) -> NamedUnit: + """Parses unit into a named unit. Parses unit into a Unit if it is not already, and then finds an equivaelent named + unit. Please note that this might not be the expected unit from the string itself. E.g. 'kgm/2' will become + newtons. + + :param unit_string: string describing the units, e.g. km/s + :param rtol: relative tolerance for matching scale factors + """ + unit = parse_unit(unit_string) + named_unit = find_named_unit(unit) + if named_unit is None: + raise ValueError(f"We don't have a for this unit: '{unit}'") + else: + return named_unit + +def find_named_unit(unit: Unit, rtol: float=1e-14) -> NamedUnit | None: + """ Find a named unit matching the one provided """ + dimension_hash = hash(unit.dimensions) + if dimension_hash in unit_groups_by_dimension_hash: + unit_group = unit_groups_by_dimension_hash[hash(unit.dimensions)] + + for named_unit in unit_group.units: + if abs(named_unit.scale - unit.scale) < rtol*named_unit.scale: + return named_unit + + return None + + +def parse_named_unit_from_group(unit_str: str, from_group: UnitGroup) -> NamedUnit: + """Parses unit_str into a named unit. The named unit found must be part of from_group. If two units are found, the + unit that is present in from_group is returned. This is useful in cases of ambiguities.""" + parsed_unit = parse_unit_from_group(unit_str, from_group) + if parsed_unit is None: + raise ValueError('That unit cannot be parsed from the specified group.') + return find_named_unit(parsed_unit) + +def parse(string: str, + name_lookup: bool = True, + longest_unit: bool = True, + lookup_rtol: float = 1e-14): + + unit = parse_unit(string, longest_unit=longest_unit) + if name_lookup: + named = find_named_unit(unit, rtol=lookup_rtol) + if named is not None: + return named + + return unit + + +if __name__ == "__main__": + to_parse = input('Enter a unit to parse: ') + try: + generic_unit = parse_unit(to_parse) + print(f'Generic Unit: {generic_unit}') + named_unit = find_named_unit(generic_unit) + print(f'Named Unit: {named_unit}') + except ValueError: + print('There is no named unit available.') diff --git a/sasdata/quantities/units.py b/sasdata/quantities/units.py new file mode 100644 index 00000000..860dbb53 --- /dev/null +++ b/sasdata/quantities/units.py @@ -0,0 +1,3622 @@ +""" + +This file is autogenerated! + +Do not edit by hand, instead edit the files that build it (_build_tables.py, _units_base.py) + + + + +DDDDDDDDDDDDD NNNNNNNN NNNNNNNN tttt +D::::::::::::DDD N:::::::N N::::::N ttt:::t +D:::::::::::::::DD N::::::::N N::::::N t:::::t +DDD:::::DDDDD:::::D N:::::::::N N::::::N t:::::t + D:::::D D:::::D ooooooooooo N::::::::::N N::::::N ooooooooooo ttttttt:::::ttttttt + D:::::D D:::::D oo:::::::::::oo N:::::::::::N N::::::N oo:::::::::::oo t:::::::::::::::::t + D:::::D D:::::Do:::::::::::::::o N:::::::N::::N N::::::No:::::::::::::::ot:::::::::::::::::t + D:::::D D:::::Do:::::ooooo:::::o N::::::N N::::N N::::::No:::::ooooo:::::otttttt:::::::tttttt + D:::::D D:::::Do::::o o::::o N::::::N N::::N:::::::No::::o o::::o t:::::t + D:::::D D:::::Do::::o o::::o N::::::N N:::::::::::No::::o o::::o t:::::t + D:::::D D:::::Do::::o o::::o N::::::N N::::::::::No::::o o::::o t:::::t + D:::::D D:::::D o::::o o::::o N::::::N N:::::::::No::::o o::::o t:::::t tttttt +DDD:::::DDDDD:::::D o:::::ooooo:::::o N::::::N N::::::::No:::::ooooo:::::o t::::::tttt:::::t +D:::::::::::::::DD o:::::::::::::::o N::::::N N:::::::No:::::::::::::::o tt::::::::::::::t +D::::::::::::DDD oo:::::::::::oo N::::::N N::::::N oo:::::::::::oo tt:::::::::::tt +DDDDDDDDDDDDD ooooooooooo NNNNNNNN NNNNNNN ooooooooooo ttttttttttt + + + + + + + + + dddddddd +EEEEEEEEEEEEEEEEEEEEEE d::::::d iiii tttt BBBBBBBBBBBBBBBBB +E::::::::::::::::::::E d::::::d i::::i ttt:::t B::::::::::::::::B +E::::::::::::::::::::E d::::::d iiii t:::::t B::::::BBBBBB:::::B +EE::::::EEEEEEEEE::::E d:::::d t:::::t BB:::::B B:::::B + E:::::E EEEEEE ddddddddd:::::d iiiiiiittttttt:::::ttttttt B::::B B:::::Byyyyyyy yyyyyyy + E:::::E dd::::::::::::::d i:::::it:::::::::::::::::t B::::B B:::::B y:::::y y:::::y + E::::::EEEEEEEEEE d::::::::::::::::d i::::it:::::::::::::::::t B::::BBBBBB:::::B y:::::y y:::::y + E:::::::::::::::E d:::::::ddddd:::::d i::::itttttt:::::::tttttt B:::::::::::::BB y:::::y y:::::y + E:::::::::::::::E d::::::d d:::::d i::::i t:::::t B::::BBBBBB:::::B y:::::y y:::::y + E::::::EEEEEEEEEE d:::::d d:::::d i::::i t:::::t B::::B B:::::B y:::::y y:::::y + E:::::E d:::::d d:::::d i::::i t:::::t B::::B B:::::B y:::::y:::::y + E:::::E EEEEEEd:::::d d:::::d i::::i t:::::t tttttt B::::B B:::::B y:::::::::y +EE::::::EEEEEEEE:::::Ed::::::ddddd::::::ddi::::::i t::::::tttt:::::t BB:::::BBBBBB::::::B y:::::::y +E::::::::::::::::::::E d:::::::::::::::::di::::::i tt::::::::::::::t B:::::::::::::::::B y:::::y +E::::::::::::::::::::E d:::::::::ddd::::di::::::i tt:::::::::::tt B::::::::::::::::B y:::::y +EEEEEEEEEEEEEEEEEEEEEE ddddddddd dddddiiiiiiii ttttttttttt BBBBBBBBBBBBBBBBB y:::::y + y:::::y + y:::::y + y:::::y + y:::::y + yyyyyyy + + + + dddddddd +HHHHHHHHH HHHHHHHHH d::::::d +H:::::::H H:::::::H d::::::d +H:::::::H H:::::::H d::::::d +HH::::::H H::::::HH d:::::d + H:::::H H:::::H aaaaaaaaaaaaa nnnn nnnnnnnn ddddddddd:::::d + H:::::H H:::::H a::::::::::::a n:::nn::::::::nn dd::::::::::::::d + H::::::HHHHH::::::H aaaaaaaaa:::::an::::::::::::::nn d::::::::::::::::d + H:::::::::::::::::H a::::ann:::::::::::::::nd:::::::ddddd:::::d + H:::::::::::::::::H aaaaaaa:::::a n:::::nnnn:::::nd::::::d d:::::d + H::::::HHHHH::::::H aa::::::::::::a n::::n n::::nd:::::d d:::::d + H:::::H H:::::H a::::aaaa::::::a n::::n n::::nd:::::d d:::::d + H:::::H H:::::H a::::a a:::::a n::::n n::::nd:::::d d:::::d +HH::::::H H::::::HHa::::a a:::::a n::::n n::::nd::::::ddddd::::::dd +H:::::::H H:::::::Ha:::::aaaa::::::a n::::n n::::n d:::::::::::::::::d +H:::::::H H:::::::H a::::::::::aa:::a n::::n n::::n d:::::::::ddd::::d +HHHHHHHHH HHHHHHHHH aaaaaaaaaa aaaa nnnnnn nnnnnn ddddddddd ddddd + + + +""" + +# +# Included from _units_base.py +# + +from dataclasses import dataclass +from typing import Sequence, Self +from fractions import Fraction + +import numpy as np + +from sasdata.quantities.unicode_superscript import int_as_unicode_superscript + +class DimensionError(Exception): + pass + +class Dimensions: + """ + + Note that some SI Base units are not useful from the perspecive of the sasview project, and make things + behave badly. In particular: moles and angular measures are dimensionless, and candelas are really a weighted + measure of power. + + We do however track angle and amount, because its really useful for formatting units + + """ + def __init__(self, + length: int = 0, + time: int = 0, + mass: int = 0, + current: int = 0, + temperature: int = 0, + moles_hint: int = 0, + angle_hint: int = 0): + + self.length = length + self.time = time + self.mass = mass + self.current = current + self.temperature = temperature + self.moles_hint = moles_hint + self.angle_hint = angle_hint + + @property + def is_dimensionless(self): + """ Is this dimension dimensionless (ignores moles_hint and angle_hint) """ + return self.length == 0 and self.time == 0 and self.mass == 0 and self.current == 0 and self.temperature == 0 + + def __mul__(self: Self, other: Self): + + if not isinstance(other, Dimensions): + return NotImplemented + + return Dimensions( + self.length + other.length, + self.time + other.time, + self.mass + other.mass, + self.current + other.current, + self.temperature + other.temperature, + self.moles_hint + other.moles_hint, + self.angle_hint + other.angle_hint) + + def __truediv__(self: Self, other: Self): + + if not isinstance(other, Dimensions): + return NotImplemented + + return Dimensions( + self.length - other.length, + self.time - other.time, + self.mass - other.mass, + self.current - other.current, + self.temperature - other.temperature, + self.moles_hint - other.moles_hint, + self.angle_hint - other.angle_hint) + + def __pow__(self, power: int | float): + + if not isinstance(power, (int, float)): + return NotImplemented + + frac = Fraction(power).limit_denominator(500) # Probably way bigger than needed, 10 would probably be fine + denominator = frac.denominator + numerator = frac.numerator + + # Throw errors if dimension is not a multiple of the denominator + + if self.length % denominator != 0: + raise DimensionError(f"Cannot apply power of {frac} to unit with length dimensionality {self.length}") + + if self.time % denominator != 0: + raise DimensionError(f"Cannot apply power of {frac} to unit with time dimensionality {self.time}") + + if self.mass % denominator != 0: + raise DimensionError(f"Cannot apply power of {frac} to unit with mass dimensionality {self.mass}") + + if self.current % denominator != 0: + raise DimensionError(f"Cannot apply power of {frac} to unit with current dimensionality {self.current}") + + if self.temperature % denominator != 0: + raise DimensionError(f"Cannot apply power of {frac} to unit with temperature dimensionality {self.temperature}") + + if self.moles_hint % denominator != 0: + raise DimensionError(f"Cannot apply power of {frac} to unit with moles hint dimensionality of {self.moles_hint}") + + if self.angle_hint % denominator != 0: + raise DimensionError(f"Cannot apply power of {frac} to unit with angle hint dimensionality of {self.angle_hint}") + + return Dimensions( + (self.length * numerator) // denominator, + (self.time * numerator) // denominator, + (self.mass * numerator) // denominator, + (self.current * numerator) // denominator, + (self.temperature * numerator) // denominator, + (self.moles_hint * numerator) // denominator, + (self.angle_hint * numerator) // denominator) + + def __eq__(self: Self, other: Self): + if isinstance(other, Dimensions): + return (self.length == other.length and + self.time == other.time and + self.mass == other.mass and + self.current == other.current and + self.temperature == other.temperature and + self.moles_hint == other.moles_hint and + self.angle_hint == other.angle_hint) + + return NotImplemented + + def __hash__(self): + """ Unique representation of units using Godel like encoding""" + + two_powers = 0 + if self.length < 0: + two_powers += 1 + + if self.time < 0: + two_powers += 2 + + if self.mass < 0: + two_powers += 4 + + if self.current < 0: + two_powers += 8 + + if self.temperature < 0: + two_powers += 16 + + if self.moles_hint < 0: + two_powers += 32 + + if self.angle_hint < 0: + two_powers += 64 + + return 2**two_powers * 3**abs(self.length) * 5**abs(self.time) * \ + 7**abs(self.mass) * 11**abs(self.current) * 13**abs(self.temperature) * \ + 17**abs(self.moles_hint) * 19**abs(self.angle_hint) + + def __repr__(self): + tokens = [] + for name, size in [ + ("length", self.length), + ("time", self.time), + ("mass", self.mass), + ("current", self.current), + ("temperature", self.temperature), + ("amount", self.moles_hint), + ("angle", self.angle_hint)]: + + if size == 0: + pass + elif size == 1: + tokens.append(f"{name}") + else: + tokens.append(f"{name}{int_as_unicode_superscript(size)}") + + return ' '.join(tokens) + + def si_repr(self): + tokens = [] + for name, size in [ + ("kg", self.mass), + ("m", self.length), + ("s", self.time), + ("A", self.current), + ("K", self.temperature), + ("mol", self.moles_hint)]: + + if size == 0: + pass + elif size == 1: + tokens.append(f"{name}") + else: + tokens.append(f"{name}{int_as_unicode_superscript(size)}") + + match self.angle_hint: + case 0: + pass + case 2: + tokens.append("sr") + case -2: + tokens.append("sr" + int_as_unicode_superscript(-1)) + case _: + tokens.append("rad" + int_as_unicode_superscript(self.angle_hint)) + + return ''.join(tokens) + + +class Unit: + def __init__(self, + si_scaling_factor: float, + dimensions: Dimensions): + + self.scale = si_scaling_factor + self.dimensions = dimensions + + def _components(self, tokens: Sequence["UnitToken"]): + pass + + def __mul__(self: Self, other: "Unit"): + if not isinstance(other, Unit): + return NotImplemented + + return Unit(self.scale * other.scale, self.dimensions * other.dimensions) + + def __truediv__(self: Self, other: "Unit"): + if not isinstance(other, Unit): + return NotImplemented + + return Unit(self.scale / other.scale, self.dimensions / other.dimensions) + + def __rtruediv__(self: Self, other: "Unit"): + if isinstance(other, Unit): + return Unit(other.scale / self.scale, other.dimensions / self.dimensions) + elif isinstance(other, (int, float)): + return Unit(other / self.scale, self.dimensions ** -1) + else: + return NotImplemented + + def __pow__(self, power: int | float): + if not isinstance(power, int | float): + return NotImplemented + + return Unit(self.scale**power, self.dimensions**power) + + + def equivalent(self: Self, other: "Unit"): + return self.dimensions == other.dimensions + + def __eq__(self: Self, other: "Unit"): + return self.equivalent(other) and np.abs(np.log(self.scale/other.scale)) < 1e-5 + + def si_equivalent(self): + """ Get the SI unit corresponding to this unit""" + return Unit(1, self.dimensions) + + def _format_unit(self, format_process: list["UnitFormatProcessor"]): + for processor in format_process: + pass + + def __repr__(self): + if self.scale == 1: + # We're in SI + return self.dimensions.si_repr() + + else: + return f"Unit[{self.scale}, {self.dimensions}]" + + @staticmethod + def parse(unit_string: str) -> "Unit": + pass + +class NamedUnit(Unit): + """ Units, but they have a name, and a symbol + + :si_scaling_factor: Number of these units per SI equivalent + :param dimensions: Dimensions object representing the dimensionality of these units + :param name: Name of unit - string without unicode + :param ascii_symbol: Symbol for unit without unicode + :param symbol: Unicode symbol + """ + def __init__(self, + si_scaling_factor: float, + dimensions: Dimensions, + name: str | None = None, + ascii_symbol: str | None = None, + latex_symbol: str | None = None, + symbol: str | None = None): + + super().__init__(si_scaling_factor, dimensions) + self.name = name + self.ascii_symbol = ascii_symbol + self.symbol = symbol + self.latex_symbol = latex_symbol if latex_symbol is not None else ascii_symbol + + def __repr__(self): + return self.name + + def __eq__(self, other): + """Match other units exactly or match strings against ANY of our names""" + match other: + case str(): + return self.name == other or self.name == f"{other}s" or self.ascii_symbol == other or self.symbol == other + case NamedUnit(): + return self.name == other.name \ + and self.ascii_symbol == other.ascii_symbol and self.symbol == other.symbol + case Unit(): + return self.equivalent(other) and np.abs(np.log(self.scale/other.scale)) < 1e-5 + case _: + return False + + + def startswith(self, prefix: str) -> bool: + """Check if any representation of the unit begins with the prefix string""" + prefix = prefix.lower() + return (self.name is not None and self.name.lower().startswith(prefix)) \ + or (self.ascii_symbol is not None and self.ascii_symbol.lower().startswith(prefix)) \ + or (self.symbol is not None and self.symbol.lower().startswith(prefix)) + +# +# Parsing plan: +# Require unknown amounts of units to be explicitly positive or negative? +# +# + + + +@dataclass +class ProcessedUnitToken: + """ Mid processing representation of formatted units """ + base_string: str + exponent_string: str + latex_exponent_string: str + exponent: int + +class UnitFormatProcessor: + """ Represents a step in the unit processing pipeline""" + def apply(self, scale, dimensions) -> tuple[ProcessedUnitToken, float, Dimensions]: + """ This will be called to deal with each processing stage""" + +class RequiredUnitFormatProcessor(UnitFormatProcessor): + """ This unit is required to exist in the formatting """ + def __init__(self, unit: Unit, power: int = 1): + self.unit = unit + self.power = power + def apply(self, scale, dimensions) -> tuple[float, Dimensions, ProcessedUnitToken]: + new_scale = scale / (self.unit.scale * self.power) + new_dimensions = self.unit.dimensions / (dimensions**self.power) + token = ProcessedUnitToken(self.unit, self.power) + + return new_scale, new_dimensions, token +class GreedyAbsDimensionUnitFormatProcessor(UnitFormatProcessor): + """ This processor minimises the dimensionality of the unit by multiplying by as many + units of the specified type as needed """ + def __init__(self, unit: Unit): + self.unit = unit + + def apply(self, scale, dimensions) -> tuple[ProcessedUnitToken, float, Dimensions]: + pass + +class GreedyAbsDimensionUnitFormatProcessor(UnitFormatProcessor): + pass + +class UnitGroup: + """ A group of units that all have the same dimensionality """ + def __init__(self, name: str, units: list[NamedUnit]): + self.name = name + self.units = sorted(units, key=lambda unit: unit.scale) + + + +# +# Specific units +# + +meters = NamedUnit(1, Dimensions(1, 0, 0, 0, 0, 0, 0),name='meters',ascii_symbol='m',symbol='m') +exameters = NamedUnit(1e+18, Dimensions(1, 0, 0, 0, 0, 0, 0),name='exameters',ascii_symbol='Em',symbol='Em') +petameters = NamedUnit(1000000000000000.0, Dimensions(1, 0, 0, 0, 0, 0, 0),name='petameters',ascii_symbol='Pm',symbol='Pm') +terameters = NamedUnit(1000000000000.0, Dimensions(1, 0, 0, 0, 0, 0, 0),name='terameters',ascii_symbol='Tm',symbol='Tm') +gigameters = NamedUnit(1000000000.0, Dimensions(1, 0, 0, 0, 0, 0, 0),name='gigameters',ascii_symbol='Gm',symbol='Gm') +megameters = NamedUnit(1000000.0, Dimensions(1, 0, 0, 0, 0, 0, 0),name='megameters',ascii_symbol='Mm',symbol='Mm') +kilometers = NamedUnit(1000.0, Dimensions(1, 0, 0, 0, 0, 0, 0),name='kilometers',ascii_symbol='km',symbol='km') +millimeters = NamedUnit(0.001, Dimensions(1, 0, 0, 0, 0, 0, 0),name='millimeters',ascii_symbol='mm',symbol='mm') +micrometers = NamedUnit(1e-06, Dimensions(1, 0, 0, 0, 0, 0, 0),name='micrometers',ascii_symbol='um',latex_symbol=r'{\mu}m',symbol='µm') +nanometers = NamedUnit(1e-09, Dimensions(1, 0, 0, 0, 0, 0, 0),name='nanometers',ascii_symbol='nm',symbol='nm') +picometers = NamedUnit(1e-12, Dimensions(1, 0, 0, 0, 0, 0, 0),name='picometers',ascii_symbol='pm',symbol='pm') +femtometers = NamedUnit(1e-15, Dimensions(1, 0, 0, 0, 0, 0, 0),name='femtometers',ascii_symbol='fm',symbol='fm') +attometers = NamedUnit(1e-18, Dimensions(1, 0, 0, 0, 0, 0, 0),name='attometers',ascii_symbol='am',symbol='am') +decimeters = NamedUnit(0.1, Dimensions(1, 0, 0, 0, 0, 0, 0),name='decimeters',ascii_symbol='dm',symbol='dm') +centimeters = NamedUnit(0.01, Dimensions(1, 0, 0, 0, 0, 0, 0),name='centimeters',ascii_symbol='cm',symbol='cm') +seconds = NamedUnit(1, Dimensions(0, 1, 0, 0, 0, 0, 0),name='seconds',ascii_symbol='s',symbol='s') +milliseconds = NamedUnit(0.001, Dimensions(0, 1, 0, 0, 0, 0, 0),name='milliseconds',ascii_symbol='ms',symbol='ms') +microseconds = NamedUnit(1e-06, Dimensions(0, 1, 0, 0, 0, 0, 0),name='microseconds',ascii_symbol='us',latex_symbol=r'{\mu}s',symbol='µs') +nanoseconds = NamedUnit(1e-09, Dimensions(0, 1, 0, 0, 0, 0, 0),name='nanoseconds',ascii_symbol='ns',symbol='ns') +picoseconds = NamedUnit(1e-12, Dimensions(0, 1, 0, 0, 0, 0, 0),name='picoseconds',ascii_symbol='ps',symbol='ps') +femtoseconds = NamedUnit(1e-15, Dimensions(0, 1, 0, 0, 0, 0, 0),name='femtoseconds',ascii_symbol='fs',symbol='fs') +attoseconds = NamedUnit(1e-18, Dimensions(0, 1, 0, 0, 0, 0, 0),name='attoseconds',ascii_symbol='as',symbol='as') +grams = NamedUnit(0.001, Dimensions(0, 0, 1, 0, 0, 0, 0),name='grams',ascii_symbol='g',symbol='g') +exagrams = NamedUnit(1000000000000000.0, Dimensions(0, 0, 1, 0, 0, 0, 0),name='exagrams',ascii_symbol='Eg',symbol='Eg') +petagrams = NamedUnit(1000000000000.0, Dimensions(0, 0, 1, 0, 0, 0, 0),name='petagrams',ascii_symbol='Pg',symbol='Pg') +teragrams = NamedUnit(1000000000.0, Dimensions(0, 0, 1, 0, 0, 0, 0),name='teragrams',ascii_symbol='Tg',symbol='Tg') +gigagrams = NamedUnit(1000000.0, Dimensions(0, 0, 1, 0, 0, 0, 0),name='gigagrams',ascii_symbol='Gg',symbol='Gg') +megagrams = NamedUnit(1000.0, Dimensions(0, 0, 1, 0, 0, 0, 0),name='megagrams',ascii_symbol='Mg',symbol='Mg') +kilograms = NamedUnit(1.0, Dimensions(0, 0, 1, 0, 0, 0, 0),name='kilograms',ascii_symbol='kg',symbol='kg') +milligrams = NamedUnit(1e-06, Dimensions(0, 0, 1, 0, 0, 0, 0),name='milligrams',ascii_symbol='mg',symbol='mg') +micrograms = NamedUnit(1e-09, Dimensions(0, 0, 1, 0, 0, 0, 0),name='micrograms',ascii_symbol='ug',latex_symbol=r'{\mu}g',symbol='µg') +nanograms = NamedUnit(1.0000000000000002e-12, Dimensions(0, 0, 1, 0, 0, 0, 0),name='nanograms',ascii_symbol='ng',symbol='ng') +picograms = NamedUnit(1e-15, Dimensions(0, 0, 1, 0, 0, 0, 0),name='picograms',ascii_symbol='pg',symbol='pg') +femtograms = NamedUnit(1e-18, Dimensions(0, 0, 1, 0, 0, 0, 0),name='femtograms',ascii_symbol='fg',symbol='fg') +attograms = NamedUnit(1.0000000000000001e-21, Dimensions(0, 0, 1, 0, 0, 0, 0),name='attograms',ascii_symbol='ag',symbol='ag') +amperes = NamedUnit(1, Dimensions(0, 0, 0, 1, 0, 0, 0),name='amperes',ascii_symbol='A',symbol='A') +exaamperes = NamedUnit(1e+18, Dimensions(0, 0, 0, 1, 0, 0, 0),name='exaamperes',ascii_symbol='EA',symbol='EA') +petaamperes = NamedUnit(1000000000000000.0, Dimensions(0, 0, 0, 1, 0, 0, 0),name='petaamperes',ascii_symbol='PA',symbol='PA') +teraamperes = NamedUnit(1000000000000.0, Dimensions(0, 0, 0, 1, 0, 0, 0),name='teraamperes',ascii_symbol='TA',symbol='TA') +gigaamperes = NamedUnit(1000000000.0, Dimensions(0, 0, 0, 1, 0, 0, 0),name='gigaamperes',ascii_symbol='GA',symbol='GA') +megaamperes = NamedUnit(1000000.0, Dimensions(0, 0, 0, 1, 0, 0, 0),name='megaamperes',ascii_symbol='MA',symbol='MA') +kiloamperes = NamedUnit(1000.0, Dimensions(0, 0, 0, 1, 0, 0, 0),name='kiloamperes',ascii_symbol='kA',symbol='kA') +milliamperes = NamedUnit(0.001, Dimensions(0, 0, 0, 1, 0, 0, 0),name='milliamperes',ascii_symbol='mA',symbol='mA') +microamperes = NamedUnit(1e-06, Dimensions(0, 0, 0, 1, 0, 0, 0),name='microamperes',ascii_symbol='uA',latex_symbol=r'{\mu}A',symbol='µA') +nanoamperes = NamedUnit(1e-09, Dimensions(0, 0, 0, 1, 0, 0, 0),name='nanoamperes',ascii_symbol='nA',symbol='nA') +picoamperes = NamedUnit(1e-12, Dimensions(0, 0, 0, 1, 0, 0, 0),name='picoamperes',ascii_symbol='pA',symbol='pA') +femtoamperes = NamedUnit(1e-15, Dimensions(0, 0, 0, 1, 0, 0, 0),name='femtoamperes',ascii_symbol='fA',symbol='fA') +attoamperes = NamedUnit(1e-18, Dimensions(0, 0, 0, 1, 0, 0, 0),name='attoamperes',ascii_symbol='aA',symbol='aA') +kelvin = NamedUnit(1, Dimensions(0, 0, 0, 0, 1, 0, 0),name='kelvin',ascii_symbol='K',symbol='K') +exakelvin = NamedUnit(1e+18, Dimensions(0, 0, 0, 0, 1, 0, 0),name='exakelvin',ascii_symbol='EK',symbol='EK') +petakelvin = NamedUnit(1000000000000000.0, Dimensions(0, 0, 0, 0, 1, 0, 0),name='petakelvin',ascii_symbol='PK',symbol='PK') +terakelvin = NamedUnit(1000000000000.0, Dimensions(0, 0, 0, 0, 1, 0, 0),name='terakelvin',ascii_symbol='TK',symbol='TK') +gigakelvin = NamedUnit(1000000000.0, Dimensions(0, 0, 0, 0, 1, 0, 0),name='gigakelvin',ascii_symbol='GK',symbol='GK') +megakelvin = NamedUnit(1000000.0, Dimensions(0, 0, 0, 0, 1, 0, 0),name='megakelvin',ascii_symbol='MK',symbol='MK') +kilokelvin = NamedUnit(1000.0, Dimensions(0, 0, 0, 0, 1, 0, 0),name='kilokelvin',ascii_symbol='kK',symbol='kK') +millikelvin = NamedUnit(0.001, Dimensions(0, 0, 0, 0, 1, 0, 0),name='millikelvin',ascii_symbol='mK',symbol='mK') +microkelvin = NamedUnit(1e-06, Dimensions(0, 0, 0, 0, 1, 0, 0),name='microkelvin',ascii_symbol='uK',latex_symbol=r'{\mu}K',symbol='µK') +nanokelvin = NamedUnit(1e-09, Dimensions(0, 0, 0, 0, 1, 0, 0),name='nanokelvin',ascii_symbol='nK',symbol='nK') +picokelvin = NamedUnit(1e-12, Dimensions(0, 0, 0, 0, 1, 0, 0),name='picokelvin',ascii_symbol='pK',symbol='pK') +femtokelvin = NamedUnit(1e-15, Dimensions(0, 0, 0, 0, 1, 0, 0),name='femtokelvin',ascii_symbol='fK',symbol='fK') +attokelvin = NamedUnit(1e-18, Dimensions(0, 0, 0, 0, 1, 0, 0),name='attokelvin',ascii_symbol='aK',symbol='aK') +hertz = NamedUnit(1, Dimensions(0, -1, 0, 0, 0, 0, 0),name='hertz',ascii_symbol='Hz',symbol='Hz') +exahertz = NamedUnit(1e+18, Dimensions(0, -1, 0, 0, 0, 0, 0),name='exahertz',ascii_symbol='EHz',symbol='EHz') +petahertz = NamedUnit(1000000000000000.0, Dimensions(0, -1, 0, 0, 0, 0, 0),name='petahertz',ascii_symbol='PHz',symbol='PHz') +terahertz = NamedUnit(1000000000000.0, Dimensions(0, -1, 0, 0, 0, 0, 0),name='terahertz',ascii_symbol='THz',symbol='THz') +gigahertz = NamedUnit(1000000000.0, Dimensions(0, -1, 0, 0, 0, 0, 0),name='gigahertz',ascii_symbol='GHz',symbol='GHz') +megahertz = NamedUnit(1000000.0, Dimensions(0, -1, 0, 0, 0, 0, 0),name='megahertz',ascii_symbol='MHz',symbol='MHz') +kilohertz = NamedUnit(1000.0, Dimensions(0, -1, 0, 0, 0, 0, 0),name='kilohertz',ascii_symbol='kHz',symbol='kHz') +millihertz = NamedUnit(0.001, Dimensions(0, -1, 0, 0, 0, 0, 0),name='millihertz',ascii_symbol='mHz',symbol='mHz') +microhertz = NamedUnit(1e-06, Dimensions(0, -1, 0, 0, 0, 0, 0),name='microhertz',ascii_symbol='uHz',latex_symbol=r'{\mu}Hz',symbol='µHz') +nanohertz = NamedUnit(1e-09, Dimensions(0, -1, 0, 0, 0, 0, 0),name='nanohertz',ascii_symbol='nHz',symbol='nHz') +picohertz = NamedUnit(1e-12, Dimensions(0, -1, 0, 0, 0, 0, 0),name='picohertz',ascii_symbol='pHz',symbol='pHz') +femtohertz = NamedUnit(1e-15, Dimensions(0, -1, 0, 0, 0, 0, 0),name='femtohertz',ascii_symbol='fHz',symbol='fHz') +attohertz = NamedUnit(1e-18, Dimensions(0, -1, 0, 0, 0, 0, 0),name='attohertz',ascii_symbol='aHz',symbol='aHz') +newtons = NamedUnit(1, Dimensions(1, -2, 1, 0, 0, 0, 0),name='newtons',ascii_symbol='N',symbol='N') +exanewtons = NamedUnit(1e+18, Dimensions(1, -2, 1, 0, 0, 0, 0),name='exanewtons',ascii_symbol='EN',symbol='EN') +petanewtons = NamedUnit(1000000000000000.0, Dimensions(1, -2, 1, 0, 0, 0, 0),name='petanewtons',ascii_symbol='PN',symbol='PN') +teranewtons = NamedUnit(1000000000000.0, Dimensions(1, -2, 1, 0, 0, 0, 0),name='teranewtons',ascii_symbol='TN',symbol='TN') +giganewtons = NamedUnit(1000000000.0, Dimensions(1, -2, 1, 0, 0, 0, 0),name='giganewtons',ascii_symbol='GN',symbol='GN') +meganewtons = NamedUnit(1000000.0, Dimensions(1, -2, 1, 0, 0, 0, 0),name='meganewtons',ascii_symbol='MN',symbol='MN') +kilonewtons = NamedUnit(1000.0, Dimensions(1, -2, 1, 0, 0, 0, 0),name='kilonewtons',ascii_symbol='kN',symbol='kN') +millinewtons = NamedUnit(0.001, Dimensions(1, -2, 1, 0, 0, 0, 0),name='millinewtons',ascii_symbol='mN',symbol='mN') +micronewtons = NamedUnit(1e-06, Dimensions(1, -2, 1, 0, 0, 0, 0),name='micronewtons',ascii_symbol='uN',latex_symbol=r'{\mu}N',symbol='µN') +nanonewtons = NamedUnit(1e-09, Dimensions(1, -2, 1, 0, 0, 0, 0),name='nanonewtons',ascii_symbol='nN',symbol='nN') +piconewtons = NamedUnit(1e-12, Dimensions(1, -2, 1, 0, 0, 0, 0),name='piconewtons',ascii_symbol='pN',symbol='pN') +femtonewtons = NamedUnit(1e-15, Dimensions(1, -2, 1, 0, 0, 0, 0),name='femtonewtons',ascii_symbol='fN',symbol='fN') +attonewtons = NamedUnit(1e-18, Dimensions(1, -2, 1, 0, 0, 0, 0),name='attonewtons',ascii_symbol='aN',symbol='aN') +pascals = NamedUnit(1, Dimensions(-1, -2, 1, 0, 0, 0, 0),name='pascals',ascii_symbol='Pa',symbol='Pa') +exapascals = NamedUnit(1e+18, Dimensions(-1, -2, 1, 0, 0, 0, 0),name='exapascals',ascii_symbol='EPa',symbol='EPa') +petapascals = NamedUnit(1000000000000000.0, Dimensions(-1, -2, 1, 0, 0, 0, 0),name='petapascals',ascii_symbol='PPa',symbol='PPa') +terapascals = NamedUnit(1000000000000.0, Dimensions(-1, -2, 1, 0, 0, 0, 0),name='terapascals',ascii_symbol='TPa',symbol='TPa') +gigapascals = NamedUnit(1000000000.0, Dimensions(-1, -2, 1, 0, 0, 0, 0),name='gigapascals',ascii_symbol='GPa',symbol='GPa') +megapascals = NamedUnit(1000000.0, Dimensions(-1, -2, 1, 0, 0, 0, 0),name='megapascals',ascii_symbol='MPa',symbol='MPa') +kilopascals = NamedUnit(1000.0, Dimensions(-1, -2, 1, 0, 0, 0, 0),name='kilopascals',ascii_symbol='kPa',symbol='kPa') +millipascals = NamedUnit(0.001, Dimensions(-1, -2, 1, 0, 0, 0, 0),name='millipascals',ascii_symbol='mPa',symbol='mPa') +micropascals = NamedUnit(1e-06, Dimensions(-1, -2, 1, 0, 0, 0, 0),name='micropascals',ascii_symbol='uPa',latex_symbol=r'{\mu}Pa',symbol='µPa') +nanopascals = NamedUnit(1e-09, Dimensions(-1, -2, 1, 0, 0, 0, 0),name='nanopascals',ascii_symbol='nPa',symbol='nPa') +picopascals = NamedUnit(1e-12, Dimensions(-1, -2, 1, 0, 0, 0, 0),name='picopascals',ascii_symbol='pPa',symbol='pPa') +femtopascals = NamedUnit(1e-15, Dimensions(-1, -2, 1, 0, 0, 0, 0),name='femtopascals',ascii_symbol='fPa',symbol='fPa') +attopascals = NamedUnit(1e-18, Dimensions(-1, -2, 1, 0, 0, 0, 0),name='attopascals',ascii_symbol='aPa',symbol='aPa') +joules = NamedUnit(1, Dimensions(2, -2, 1, 0, 0, 0, 0),name='joules',ascii_symbol='J',symbol='J') +exajoules = NamedUnit(1e+18, Dimensions(2, -2, 1, 0, 0, 0, 0),name='exajoules',ascii_symbol='EJ',symbol='EJ') +petajoules = NamedUnit(1000000000000000.0, Dimensions(2, -2, 1, 0, 0, 0, 0),name='petajoules',ascii_symbol='PJ',symbol='PJ') +terajoules = NamedUnit(1000000000000.0, Dimensions(2, -2, 1, 0, 0, 0, 0),name='terajoules',ascii_symbol='TJ',symbol='TJ') +gigajoules = NamedUnit(1000000000.0, Dimensions(2, -2, 1, 0, 0, 0, 0),name='gigajoules',ascii_symbol='GJ',symbol='GJ') +megajoules = NamedUnit(1000000.0, Dimensions(2, -2, 1, 0, 0, 0, 0),name='megajoules',ascii_symbol='MJ',symbol='MJ') +kilojoules = NamedUnit(1000.0, Dimensions(2, -2, 1, 0, 0, 0, 0),name='kilojoules',ascii_symbol='kJ',symbol='kJ') +millijoules = NamedUnit(0.001, Dimensions(2, -2, 1, 0, 0, 0, 0),name='millijoules',ascii_symbol='mJ',symbol='mJ') +microjoules = NamedUnit(1e-06, Dimensions(2, -2, 1, 0, 0, 0, 0),name='microjoules',ascii_symbol='uJ',latex_symbol=r'{\mu}J',symbol='µJ') +nanojoules = NamedUnit(1e-09, Dimensions(2, -2, 1, 0, 0, 0, 0),name='nanojoules',ascii_symbol='nJ',symbol='nJ') +picojoules = NamedUnit(1e-12, Dimensions(2, -2, 1, 0, 0, 0, 0),name='picojoules',ascii_symbol='pJ',symbol='pJ') +femtojoules = NamedUnit(1e-15, Dimensions(2, -2, 1, 0, 0, 0, 0),name='femtojoules',ascii_symbol='fJ',symbol='fJ') +attojoules = NamedUnit(1e-18, Dimensions(2, -2, 1, 0, 0, 0, 0),name='attojoules',ascii_symbol='aJ',symbol='aJ') +watts = NamedUnit(1, Dimensions(2, -3, 1, 0, 0, 0, 0),name='watts',ascii_symbol='W',symbol='W') +exawatts = NamedUnit(1e+18, Dimensions(2, -3, 1, 0, 0, 0, 0),name='exawatts',ascii_symbol='EW',symbol='EW') +petawatts = NamedUnit(1000000000000000.0, Dimensions(2, -3, 1, 0, 0, 0, 0),name='petawatts',ascii_symbol='PW',symbol='PW') +terawatts = NamedUnit(1000000000000.0, Dimensions(2, -3, 1, 0, 0, 0, 0),name='terawatts',ascii_symbol='TW',symbol='TW') +gigawatts = NamedUnit(1000000000.0, Dimensions(2, -3, 1, 0, 0, 0, 0),name='gigawatts',ascii_symbol='GW',symbol='GW') +megawatts = NamedUnit(1000000.0, Dimensions(2, -3, 1, 0, 0, 0, 0),name='megawatts',ascii_symbol='MW',symbol='MW') +kilowatts = NamedUnit(1000.0, Dimensions(2, -3, 1, 0, 0, 0, 0),name='kilowatts',ascii_symbol='kW',symbol='kW') +milliwatts = NamedUnit(0.001, Dimensions(2, -3, 1, 0, 0, 0, 0),name='milliwatts',ascii_symbol='mW',symbol='mW') +microwatts = NamedUnit(1e-06, Dimensions(2, -3, 1, 0, 0, 0, 0),name='microwatts',ascii_symbol='uW',latex_symbol=r'{\mu}W',symbol='µW') +nanowatts = NamedUnit(1e-09, Dimensions(2, -3, 1, 0, 0, 0, 0),name='nanowatts',ascii_symbol='nW',symbol='nW') +picowatts = NamedUnit(1e-12, Dimensions(2, -3, 1, 0, 0, 0, 0),name='picowatts',ascii_symbol='pW',symbol='pW') +femtowatts = NamedUnit(1e-15, Dimensions(2, -3, 1, 0, 0, 0, 0),name='femtowatts',ascii_symbol='fW',symbol='fW') +attowatts = NamedUnit(1e-18, Dimensions(2, -3, 1, 0, 0, 0, 0),name='attowatts',ascii_symbol='aW',symbol='aW') +coulombs = NamedUnit(1, Dimensions(0, 1, 0, 1, 0, 0, 0),name='coulombs',ascii_symbol='C',symbol='C') +exacoulombs = NamedUnit(1e+18, Dimensions(0, 1, 0, 1, 0, 0, 0),name='exacoulombs',ascii_symbol='EC',symbol='EC') +petacoulombs = NamedUnit(1000000000000000.0, Dimensions(0, 1, 0, 1, 0, 0, 0),name='petacoulombs',ascii_symbol='PC',symbol='PC') +teracoulombs = NamedUnit(1000000000000.0, Dimensions(0, 1, 0, 1, 0, 0, 0),name='teracoulombs',ascii_symbol='TC',symbol='TC') +gigacoulombs = NamedUnit(1000000000.0, Dimensions(0, 1, 0, 1, 0, 0, 0),name='gigacoulombs',ascii_symbol='GC',symbol='GC') +megacoulombs = NamedUnit(1000000.0, Dimensions(0, 1, 0, 1, 0, 0, 0),name='megacoulombs',ascii_symbol='MC',symbol='MC') +kilocoulombs = NamedUnit(1000.0, Dimensions(0, 1, 0, 1, 0, 0, 0),name='kilocoulombs',ascii_symbol='kC',symbol='kC') +millicoulombs = NamedUnit(0.001, Dimensions(0, 1, 0, 1, 0, 0, 0),name='millicoulombs',ascii_symbol='mC',symbol='mC') +microcoulombs = NamedUnit(1e-06, Dimensions(0, 1, 0, 1, 0, 0, 0),name='microcoulombs',ascii_symbol='uC',latex_symbol=r'{\mu}C',symbol='µC') +nanocoulombs = NamedUnit(1e-09, Dimensions(0, 1, 0, 1, 0, 0, 0),name='nanocoulombs',ascii_symbol='nC',symbol='nC') +picocoulombs = NamedUnit(1e-12, Dimensions(0, 1, 0, 1, 0, 0, 0),name='picocoulombs',ascii_symbol='pC',symbol='pC') +femtocoulombs = NamedUnit(1e-15, Dimensions(0, 1, 0, 1, 0, 0, 0),name='femtocoulombs',ascii_symbol='fC',symbol='fC') +attocoulombs = NamedUnit(1e-18, Dimensions(0, 1, 0, 1, 0, 0, 0),name='attocoulombs',ascii_symbol='aC',symbol='aC') +volts = NamedUnit(1, Dimensions(2, -3, 1, -1, 0, 0, 0),name='volts',ascii_symbol='V',symbol='V') +exavolts = NamedUnit(1e+18, Dimensions(2, -3, 1, -1, 0, 0, 0),name='exavolts',ascii_symbol='EV',symbol='EV') +petavolts = NamedUnit(1000000000000000.0, Dimensions(2, -3, 1, -1, 0, 0, 0),name='petavolts',ascii_symbol='PV',symbol='PV') +teravolts = NamedUnit(1000000000000.0, Dimensions(2, -3, 1, -1, 0, 0, 0),name='teravolts',ascii_symbol='TV',symbol='TV') +gigavolts = NamedUnit(1000000000.0, Dimensions(2, -3, 1, -1, 0, 0, 0),name='gigavolts',ascii_symbol='GV',symbol='GV') +megavolts = NamedUnit(1000000.0, Dimensions(2, -3, 1, -1, 0, 0, 0),name='megavolts',ascii_symbol='MV',symbol='MV') +kilovolts = NamedUnit(1000.0, Dimensions(2, -3, 1, -1, 0, 0, 0),name='kilovolts',ascii_symbol='kV',symbol='kV') +millivolts = NamedUnit(0.001, Dimensions(2, -3, 1, -1, 0, 0, 0),name='millivolts',ascii_symbol='mV',symbol='mV') +microvolts = NamedUnit(1e-06, Dimensions(2, -3, 1, -1, 0, 0, 0),name='microvolts',ascii_symbol='uV',latex_symbol=r'{\mu}V',symbol='µV') +nanovolts = NamedUnit(1e-09, Dimensions(2, -3, 1, -1, 0, 0, 0),name='nanovolts',ascii_symbol='nV',symbol='nV') +picovolts = NamedUnit(1e-12, Dimensions(2, -3, 1, -1, 0, 0, 0),name='picovolts',ascii_symbol='pV',symbol='pV') +femtovolts = NamedUnit(1e-15, Dimensions(2, -3, 1, -1, 0, 0, 0),name='femtovolts',ascii_symbol='fV',symbol='fV') +attovolts = NamedUnit(1e-18, Dimensions(2, -3, 1, -1, 0, 0, 0),name='attovolts',ascii_symbol='aV',symbol='aV') +ohms = NamedUnit(1, Dimensions(2, -3, 1, -2, 0, 0, 0),name='ohms',ascii_symbol='Ohm',latex_symbol=r'\Omega',symbol='Ω') +exaohms = NamedUnit(1e+18, Dimensions(2, -3, 1, -2, 0, 0, 0),name='exaohms',ascii_symbol='EOhm',latex_symbol=r'E\Omega',symbol='EΩ') +petaohms = NamedUnit(1000000000000000.0, Dimensions(2, -3, 1, -2, 0, 0, 0),name='petaohms',ascii_symbol='POhm',latex_symbol=r'P\Omega',symbol='PΩ') +teraohms = NamedUnit(1000000000000.0, Dimensions(2, -3, 1, -2, 0, 0, 0),name='teraohms',ascii_symbol='TOhm',latex_symbol=r'T\Omega',symbol='TΩ') +gigaohms = NamedUnit(1000000000.0, Dimensions(2, -3, 1, -2, 0, 0, 0),name='gigaohms',ascii_symbol='GOhm',latex_symbol=r'G\Omega',symbol='GΩ') +megaohms = NamedUnit(1000000.0, Dimensions(2, -3, 1, -2, 0, 0, 0),name='megaohms',ascii_symbol='MOhm',latex_symbol=r'M\Omega',symbol='MΩ') +kiloohms = NamedUnit(1000.0, Dimensions(2, -3, 1, -2, 0, 0, 0),name='kiloohms',ascii_symbol='kOhm',latex_symbol=r'k\Omega',symbol='kΩ') +milliohms = NamedUnit(0.001, Dimensions(2, -3, 1, -2, 0, 0, 0),name='milliohms',ascii_symbol='mOhm',latex_symbol=r'm\Omega',symbol='mΩ') +microohms = NamedUnit(1e-06, Dimensions(2, -3, 1, -2, 0, 0, 0),name='microohms',ascii_symbol='uOhm',latex_symbol=r'{\mu}\Omega',symbol='µΩ') +nanoohms = NamedUnit(1e-09, Dimensions(2, -3, 1, -2, 0, 0, 0),name='nanoohms',ascii_symbol='nOhm',latex_symbol=r'n\Omega',symbol='nΩ') +picoohms = NamedUnit(1e-12, Dimensions(2, -3, 1, -2, 0, 0, 0),name='picoohms',ascii_symbol='pOhm',latex_symbol=r'p\Omega',symbol='pΩ') +femtoohms = NamedUnit(1e-15, Dimensions(2, -3, 1, -2, 0, 0, 0),name='femtoohms',ascii_symbol='fOhm',latex_symbol=r'f\Omega',symbol='fΩ') +attoohms = NamedUnit(1e-18, Dimensions(2, -3, 1, -2, 0, 0, 0),name='attoohms',ascii_symbol='aOhm',latex_symbol=r'a\Omega',symbol='aΩ') +farads = NamedUnit(1, Dimensions(-2, 4, -1, 2, 0, 0, 0),name='farads',ascii_symbol='F',symbol='F') +exafarads = NamedUnit(1e+18, Dimensions(-2, 4, -1, 2, 0, 0, 0),name='exafarads',ascii_symbol='EF',symbol='EF') +petafarads = NamedUnit(1000000000000000.0, Dimensions(-2, 4, -1, 2, 0, 0, 0),name='petafarads',ascii_symbol='PF',symbol='PF') +terafarads = NamedUnit(1000000000000.0, Dimensions(-2, 4, -1, 2, 0, 0, 0),name='terafarads',ascii_symbol='TF',symbol='TF') +gigafarads = NamedUnit(1000000000.0, Dimensions(-2, 4, -1, 2, 0, 0, 0),name='gigafarads',ascii_symbol='GF',symbol='GF') +megafarads = NamedUnit(1000000.0, Dimensions(-2, 4, -1, 2, 0, 0, 0),name='megafarads',ascii_symbol='MF',symbol='MF') +kilofarads = NamedUnit(1000.0, Dimensions(-2, 4, -1, 2, 0, 0, 0),name='kilofarads',ascii_symbol='kF',symbol='kF') +millifarads = NamedUnit(0.001, Dimensions(-2, 4, -1, 2, 0, 0, 0),name='millifarads',ascii_symbol='mF',symbol='mF') +microfarads = NamedUnit(1e-06, Dimensions(-2, 4, -1, 2, 0, 0, 0),name='microfarads',ascii_symbol='uF',latex_symbol=r'{\mu}F',symbol='µF') +nanofarads = NamedUnit(1e-09, Dimensions(-2, 4, -1, 2, 0, 0, 0),name='nanofarads',ascii_symbol='nF',symbol='nF') +picofarads = NamedUnit(1e-12, Dimensions(-2, 4, -1, 2, 0, 0, 0),name='picofarads',ascii_symbol='pF',symbol='pF') +femtofarads = NamedUnit(1e-15, Dimensions(-2, 4, -1, 2, 0, 0, 0),name='femtofarads',ascii_symbol='fF',symbol='fF') +attofarads = NamedUnit(1e-18, Dimensions(-2, 4, -1, 2, 0, 0, 0),name='attofarads',ascii_symbol='aF',symbol='aF') +siemens = NamedUnit(1, Dimensions(-2, 3, -1, 2, 0, 0, 0),name='siemens',ascii_symbol='S',symbol='S') +exasiemens = NamedUnit(1e+18, Dimensions(-2, 3, -1, 2, 0, 0, 0),name='exasiemens',ascii_symbol='ES',symbol='ES') +petasiemens = NamedUnit(1000000000000000.0, Dimensions(-2, 3, -1, 2, 0, 0, 0),name='petasiemens',ascii_symbol='PS',symbol='PS') +terasiemens = NamedUnit(1000000000000.0, Dimensions(-2, 3, -1, 2, 0, 0, 0),name='terasiemens',ascii_symbol='TS',symbol='TS') +gigasiemens = NamedUnit(1000000000.0, Dimensions(-2, 3, -1, 2, 0, 0, 0),name='gigasiemens',ascii_symbol='GS',symbol='GS') +megasiemens = NamedUnit(1000000.0, Dimensions(-2, 3, -1, 2, 0, 0, 0),name='megasiemens',ascii_symbol='MS',symbol='MS') +kilosiemens = NamedUnit(1000.0, Dimensions(-2, 3, -1, 2, 0, 0, 0),name='kilosiemens',ascii_symbol='kS',symbol='kS') +millisiemens = NamedUnit(0.001, Dimensions(-2, 3, -1, 2, 0, 0, 0),name='millisiemens',ascii_symbol='mS',symbol='mS') +microsiemens = NamedUnit(1e-06, Dimensions(-2, 3, -1, 2, 0, 0, 0),name='microsiemens',ascii_symbol='uS',latex_symbol=r'{\mu}S',symbol='µS') +nanosiemens = NamedUnit(1e-09, Dimensions(-2, 3, -1, 2, 0, 0, 0),name='nanosiemens',ascii_symbol='nS',symbol='nS') +picosiemens = NamedUnit(1e-12, Dimensions(-2, 3, -1, 2, 0, 0, 0),name='picosiemens',ascii_symbol='pS',symbol='pS') +femtosiemens = NamedUnit(1e-15, Dimensions(-2, 3, -1, 2, 0, 0, 0),name='femtosiemens',ascii_symbol='fS',symbol='fS') +attosiemens = NamedUnit(1e-18, Dimensions(-2, 3, -1, 2, 0, 0, 0),name='attosiemens',ascii_symbol='aS',symbol='aS') +webers = NamedUnit(1, Dimensions(2, -2, 1, -1, 0, 0, 0),name='webers',ascii_symbol='Wb',symbol='Wb') +exawebers = NamedUnit(1e+18, Dimensions(2, -2, 1, -1, 0, 0, 0),name='exawebers',ascii_symbol='EWb',symbol='EWb') +petawebers = NamedUnit(1000000000000000.0, Dimensions(2, -2, 1, -1, 0, 0, 0),name='petawebers',ascii_symbol='PWb',symbol='PWb') +terawebers = NamedUnit(1000000000000.0, Dimensions(2, -2, 1, -1, 0, 0, 0),name='terawebers',ascii_symbol='TWb',symbol='TWb') +gigawebers = NamedUnit(1000000000.0, Dimensions(2, -2, 1, -1, 0, 0, 0),name='gigawebers',ascii_symbol='GWb',symbol='GWb') +megawebers = NamedUnit(1000000.0, Dimensions(2, -2, 1, -1, 0, 0, 0),name='megawebers',ascii_symbol='MWb',symbol='MWb') +kilowebers = NamedUnit(1000.0, Dimensions(2, -2, 1, -1, 0, 0, 0),name='kilowebers',ascii_symbol='kWb',symbol='kWb') +milliwebers = NamedUnit(0.001, Dimensions(2, -2, 1, -1, 0, 0, 0),name='milliwebers',ascii_symbol='mWb',symbol='mWb') +microwebers = NamedUnit(1e-06, Dimensions(2, -2, 1, -1, 0, 0, 0),name='microwebers',ascii_symbol='uWb',latex_symbol=r'{\mu}Wb',symbol='µWb') +nanowebers = NamedUnit(1e-09, Dimensions(2, -2, 1, -1, 0, 0, 0),name='nanowebers',ascii_symbol='nWb',symbol='nWb') +picowebers = NamedUnit(1e-12, Dimensions(2, -2, 1, -1, 0, 0, 0),name='picowebers',ascii_symbol='pWb',symbol='pWb') +femtowebers = NamedUnit(1e-15, Dimensions(2, -2, 1, -1, 0, 0, 0),name='femtowebers',ascii_symbol='fWb',symbol='fWb') +attowebers = NamedUnit(1e-18, Dimensions(2, -2, 1, -1, 0, 0, 0),name='attowebers',ascii_symbol='aWb',symbol='aWb') +tesla = NamedUnit(1, Dimensions(0, -2, 1, -1, 0, 0, 0),name='tesla',ascii_symbol='T',symbol='T') +exatesla = NamedUnit(1e+18, Dimensions(0, -2, 1, -1, 0, 0, 0),name='exatesla',ascii_symbol='ET',symbol='ET') +petatesla = NamedUnit(1000000000000000.0, Dimensions(0, -2, 1, -1, 0, 0, 0),name='petatesla',ascii_symbol='PT',symbol='PT') +teratesla = NamedUnit(1000000000000.0, Dimensions(0, -2, 1, -1, 0, 0, 0),name='teratesla',ascii_symbol='TT',symbol='TT') +gigatesla = NamedUnit(1000000000.0, Dimensions(0, -2, 1, -1, 0, 0, 0),name='gigatesla',ascii_symbol='GT',symbol='GT') +megatesla = NamedUnit(1000000.0, Dimensions(0, -2, 1, -1, 0, 0, 0),name='megatesla',ascii_symbol='MT',symbol='MT') +kilotesla = NamedUnit(1000.0, Dimensions(0, -2, 1, -1, 0, 0, 0),name='kilotesla',ascii_symbol='kT',symbol='kT') +millitesla = NamedUnit(0.001, Dimensions(0, -2, 1, -1, 0, 0, 0),name='millitesla',ascii_symbol='mT',symbol='mT') +microtesla = NamedUnit(1e-06, Dimensions(0, -2, 1, -1, 0, 0, 0),name='microtesla',ascii_symbol='uT',latex_symbol=r'{\mu}T',symbol='µT') +nanotesla = NamedUnit(1e-09, Dimensions(0, -2, 1, -1, 0, 0, 0),name='nanotesla',ascii_symbol='nT',symbol='nT') +picotesla = NamedUnit(1e-12, Dimensions(0, -2, 1, -1, 0, 0, 0),name='picotesla',ascii_symbol='pT',symbol='pT') +femtotesla = NamedUnit(1e-15, Dimensions(0, -2, 1, -1, 0, 0, 0),name='femtotesla',ascii_symbol='fT',symbol='fT') +attotesla = NamedUnit(1e-18, Dimensions(0, -2, 1, -1, 0, 0, 0),name='attotesla',ascii_symbol='aT',symbol='aT') +henry = NamedUnit(1, Dimensions(2, -2, 1, -2, 0, 0, 0),name='henry',ascii_symbol='H',symbol='H') +exahenry = NamedUnit(1e+18, Dimensions(2, -2, 1, -2, 0, 0, 0),name='exahenry',ascii_symbol='EH',symbol='EH') +petahenry = NamedUnit(1000000000000000.0, Dimensions(2, -2, 1, -2, 0, 0, 0),name='petahenry',ascii_symbol='PH',symbol='PH') +terahenry = NamedUnit(1000000000000.0, Dimensions(2, -2, 1, -2, 0, 0, 0),name='terahenry',ascii_symbol='TH',symbol='TH') +gigahenry = NamedUnit(1000000000.0, Dimensions(2, -2, 1, -2, 0, 0, 0),name='gigahenry',ascii_symbol='GH',symbol='GH') +megahenry = NamedUnit(1000000.0, Dimensions(2, -2, 1, -2, 0, 0, 0),name='megahenry',ascii_symbol='MH',symbol='MH') +kilohenry = NamedUnit(1000.0, Dimensions(2, -2, 1, -2, 0, 0, 0),name='kilohenry',ascii_symbol='kH',symbol='kH') +millihenry = NamedUnit(0.001, Dimensions(2, -2, 1, -2, 0, 0, 0),name='millihenry',ascii_symbol='mH',symbol='mH') +microhenry = NamedUnit(1e-06, Dimensions(2, -2, 1, -2, 0, 0, 0),name='microhenry',ascii_symbol='uH',latex_symbol=r'{\mu}H',symbol='µH') +nanohenry = NamedUnit(1e-09, Dimensions(2, -2, 1, -2, 0, 0, 0),name='nanohenry',ascii_symbol='nH',symbol='nH') +picohenry = NamedUnit(1e-12, Dimensions(2, -2, 1, -2, 0, 0, 0),name='picohenry',ascii_symbol='pH',symbol='pH') +femtohenry = NamedUnit(1e-15, Dimensions(2, -2, 1, -2, 0, 0, 0),name='femtohenry',ascii_symbol='fH',symbol='fH') +attohenry = NamedUnit(1e-18, Dimensions(2, -2, 1, -2, 0, 0, 0),name='attohenry',ascii_symbol='aH',symbol='aH') +angstroms = NamedUnit(1e-10, Dimensions(1, 0, 0, 0, 0, 0, 0),name='angstroms',ascii_symbol='Ang',latex_symbol=r'\AA',symbol='Å') +microns = NamedUnit(1e-06, Dimensions(1, 0, 0, 0, 0, 0, 0),name='microns',ascii_symbol='micron',symbol='micron') +minutes = NamedUnit(60, Dimensions(0, 1, 0, 0, 0, 0, 0),name='minutes',ascii_symbol='min',symbol='min') +hours = NamedUnit(360, Dimensions(0, 1, 0, 0, 0, 0, 0),name='hours',ascii_symbol='h',symbol='h') +days = NamedUnit(8640, Dimensions(0, 1, 0, 0, 0, 0, 0),name='days',ascii_symbol='d',symbol='d') +years = NamedUnit(3155695.2, Dimensions(0, 1, 0, 0, 0, 0, 0),name='years',ascii_symbol='y',symbol='y') +degrees = NamedUnit(57.29577951308232, Dimensions(0, 0, 0, 0, 0, 0, 1),name='degrees',ascii_symbol='deg',symbol='deg') +radians = NamedUnit(1, Dimensions(0, 0, 0, 0, 0, 0, 1),name='radians',ascii_symbol='rad',symbol='rad') +stradians = NamedUnit(1, Dimensions(0, 0, 0, 0, 0, 0, 2),name='stradians',ascii_symbol='sr',symbol='sr') +litres = NamedUnit(0.001, Dimensions(3, 0, 0, 0, 0, 0, 0),name='litres',ascii_symbol='l',symbol='l') +electronvolts = NamedUnit(1.602176634e-19, Dimensions(2, -2, 1, 0, 0, 0, 0),name='electronvolts',ascii_symbol='eV',symbol='eV') +exaelectronvolts = NamedUnit(0.1602176634, Dimensions(2, -2, 1, 0, 0, 0, 0),name='exaelectronvolts',ascii_symbol='EeV',symbol='EeV') +petaelectronvolts = NamedUnit(0.0001602176634, Dimensions(2, -2, 1, 0, 0, 0, 0),name='petaelectronvolts',ascii_symbol='PeV',symbol='PeV') +teraelectronvolts = NamedUnit(1.602176634e-07, Dimensions(2, -2, 1, 0, 0, 0, 0),name='teraelectronvolts',ascii_symbol='TeV',symbol='TeV') +gigaelectronvolts = NamedUnit(1.6021766339999998e-10, Dimensions(2, -2, 1, 0, 0, 0, 0),name='gigaelectronvolts',ascii_symbol='GeV',symbol='GeV') +megaelectronvolts = NamedUnit(1.6021766339999998e-13, Dimensions(2, -2, 1, 0, 0, 0, 0),name='megaelectronvolts',ascii_symbol='MeV',symbol='MeV') +kiloelectronvolts = NamedUnit(1.602176634e-16, Dimensions(2, -2, 1, 0, 0, 0, 0),name='kiloelectronvolts',ascii_symbol='keV',symbol='keV') +millielectronvolts = NamedUnit(1.6021766339999998e-22, Dimensions(2, -2, 1, 0, 0, 0, 0),name='millielectronvolts',ascii_symbol='meV',symbol='meV') +microelectronvolts = NamedUnit(1.602176634e-25, Dimensions(2, -2, 1, 0, 0, 0, 0),name='microelectronvolts',ascii_symbol='ueV',latex_symbol=r'{\mu}eV',symbol='µeV') +nanoelectronvolts = NamedUnit(1.602176634e-28, Dimensions(2, -2, 1, 0, 0, 0, 0),name='nanoelectronvolts',ascii_symbol='neV',symbol='neV') +picoelectronvolts = NamedUnit(1.6021766339999998e-31, Dimensions(2, -2, 1, 0, 0, 0, 0),name='picoelectronvolts',ascii_symbol='peV',symbol='peV') +femtoelectronvolts = NamedUnit(1.602176634e-34, Dimensions(2, -2, 1, 0, 0, 0, 0),name='femtoelectronvolts',ascii_symbol='feV',symbol='feV') +attoelectronvolts = NamedUnit(1.602176634e-37, Dimensions(2, -2, 1, 0, 0, 0, 0),name='attoelectronvolts',ascii_symbol='aeV',symbol='aeV') +atomic_mass_units = NamedUnit(1.660538921e-27, Dimensions(0, 0, 1, 0, 0, 0, 0),name='atomic_mass_units',ascii_symbol='au',symbol='au') +moles = NamedUnit(6.02214076e+23, Dimensions(0, 0, 0, 0, 0, 1, 0),name='moles',ascii_symbol='mol',symbol='mol') +millimoles = NamedUnit(6.02214076e+20, Dimensions(0, 0, 0, 0, 0, 1, 0),name='millimoles',ascii_symbol='mmol',symbol='mmol') +micromoles = NamedUnit(6.02214076e+17, Dimensions(0, 0, 0, 0, 0, 1, 0),name='micromoles',ascii_symbol='umol',latex_symbol=r'{\mu}mol',symbol='µmol') +nanomoles = NamedUnit(602214076000000.0, Dimensions(0, 0, 0, 0, 0, 1, 0),name='nanomoles',ascii_symbol='nmol',symbol='nmol') +picomoles = NamedUnit(602214076000.0, Dimensions(0, 0, 0, 0, 0, 1, 0),name='picomoles',ascii_symbol='pmol',symbol='pmol') +femtomoles = NamedUnit(602214076.0, Dimensions(0, 0, 0, 0, 0, 1, 0),name='femtomoles',ascii_symbol='fmol',symbol='fmol') +attomoles = NamedUnit(602214.076, Dimensions(0, 0, 0, 0, 0, 1, 0),name='attomoles',ascii_symbol='amol',symbol='amol') +kg_force = NamedUnit(9.80665, Dimensions(1, -2, 1, 0, 0, 0, 0),name='kg_force',ascii_symbol='kgForce',symbol='kgForce') +degrees_celsius = NamedUnit(1, Dimensions(0, 0, 0, 0, 1, 0, 0),name='degrees_celsius',ascii_symbol='C',symbol='C') +miles = NamedUnit(1609.344, Dimensions(1, 0, 0, 0, 0, 0, 0),name='miles',ascii_symbol='miles',symbol='miles') +yards = NamedUnit(0.9144000000000001, Dimensions(1, 0, 0, 0, 0, 0, 0),name='yards',ascii_symbol='yrd',symbol='yrd') +feet = NamedUnit(0.3048, Dimensions(1, 0, 0, 0, 0, 0, 0),name='feet',ascii_symbol='ft',symbol='ft') +inches = NamedUnit(0.0254, Dimensions(1, 0, 0, 0, 0, 0, 0),name='inches',ascii_symbol='in',symbol='in') +pounds = NamedUnit(0.45359237, Dimensions(0, 0, 1, 0, 0, 0, 0),name='pounds',ascii_symbol='lb',symbol='lb') +pounds_force = NamedUnit(4.448222, Dimensions(1, -2, 1, 0, 0, 0, 0),name='pounds_force',ascii_symbol='lbf',symbol='lbf') +ounces = NamedUnit(0.028349523125, Dimensions(0, 0, 1, 0, 0, 0, 0),name='ounces',ascii_symbol='oz',symbol='oz') +pounds_force_per_square_inch = NamedUnit(6894.757889515779, Dimensions(-1, -2, 1, 0, 0, 0, 0),name='pounds_force_per_square_inch',ascii_symbol='psi',symbol='psi') +none = NamedUnit(1, Dimensions(0, 0, 0, 0, 0, 0, 0),name='none',ascii_symbol='none',symbol='none') +percent = NamedUnit(0.01, Dimensions(0, 0, 0, 0, 0, 0, 0),name='percent',ascii_symbol='percent',latex_symbol=r'\%',symbol='%') +square_meters = NamedUnit(1, Dimensions(length=2), name='square_meters', ascii_symbol='m^2', symbol='m²') +cubic_meters = NamedUnit(1, Dimensions(length=3), name='cubic_meters', ascii_symbol='m^3', symbol='m³') +per_meter = NamedUnit(1.0, Dimensions(length=-1), name='per_meter', ascii_symbol='m^-1', symbol='m⁻¹') +per_square_meter = NamedUnit(1.0, Dimensions(length=-2), name='per_square_meter', ascii_symbol='m^-2', symbol='m⁻²') +per_cubic_meter = NamedUnit(1.0, Dimensions(length=-3), name='per_cubic_meter', ascii_symbol='m^-3', symbol='m⁻³') +square_exameters = NamedUnit(1e+36, Dimensions(length=2), name='square_exameters', ascii_symbol='Em^2', symbol='Em²') +cubic_exameters = NamedUnit(1e+54, Dimensions(length=3), name='cubic_exameters', ascii_symbol='Em^3', symbol='Em³') +per_exameter = NamedUnit(1e-18, Dimensions(length=-1), name='per_exameter', ascii_symbol='Em^-1', symbol='Em⁻¹') +per_square_exameter = NamedUnit(1e-36, Dimensions(length=-2), name='per_square_exameter', ascii_symbol='Em^-2', symbol='Em⁻²') +per_cubic_exameter = NamedUnit(1e-54, Dimensions(length=-3), name='per_cubic_exameter', ascii_symbol='Em^-3', symbol='Em⁻³') +square_petameters = NamedUnit(1e+30, Dimensions(length=2), name='square_petameters', ascii_symbol='Pm^2', symbol='Pm²') +cubic_petameters = NamedUnit(1e+45, Dimensions(length=3), name='cubic_petameters', ascii_symbol='Pm^3', symbol='Pm³') +per_petameter = NamedUnit(1e-15, Dimensions(length=-1), name='per_petameter', ascii_symbol='Pm^-1', symbol='Pm⁻¹') +per_square_petameter = NamedUnit(1e-30, Dimensions(length=-2), name='per_square_petameter', ascii_symbol='Pm^-2', symbol='Pm⁻²') +per_cubic_petameter = NamedUnit(1e-45, Dimensions(length=-3), name='per_cubic_petameter', ascii_symbol='Pm^-3', symbol='Pm⁻³') +square_terameters = NamedUnit(1e+24, Dimensions(length=2), name='square_terameters', ascii_symbol='Tm^2', symbol='Tm²') +cubic_terameters = NamedUnit(1e+36, Dimensions(length=3), name='cubic_terameters', ascii_symbol='Tm^3', symbol='Tm³') +per_terameter = NamedUnit(1e-12, Dimensions(length=-1), name='per_terameter', ascii_symbol='Tm^-1', symbol='Tm⁻¹') +per_square_terameter = NamedUnit(1e-24, Dimensions(length=-2), name='per_square_terameter', ascii_symbol='Tm^-2', symbol='Tm⁻²') +per_cubic_terameter = NamedUnit(1e-36, Dimensions(length=-3), name='per_cubic_terameter', ascii_symbol='Tm^-3', symbol='Tm⁻³') +square_gigameters = NamedUnit(1e+18, Dimensions(length=2), name='square_gigameters', ascii_symbol='Gm^2', symbol='Gm²') +cubic_gigameters = NamedUnit(1e+27, Dimensions(length=3), name='cubic_gigameters', ascii_symbol='Gm^3', symbol='Gm³') +per_gigameter = NamedUnit(1e-09, Dimensions(length=-1), name='per_gigameter', ascii_symbol='Gm^-1', symbol='Gm⁻¹') +per_square_gigameter = NamedUnit(1e-18, Dimensions(length=-2), name='per_square_gigameter', ascii_symbol='Gm^-2', symbol='Gm⁻²') +per_cubic_gigameter = NamedUnit(1e-27, Dimensions(length=-3), name='per_cubic_gigameter', ascii_symbol='Gm^-3', symbol='Gm⁻³') +square_megameters = NamedUnit(1000000000000.0, Dimensions(length=2), name='square_megameters', ascii_symbol='Mm^2', symbol='Mm²') +cubic_megameters = NamedUnit(1e+18, Dimensions(length=3), name='cubic_megameters', ascii_symbol='Mm^3', symbol='Mm³') +per_megameter = NamedUnit(1e-06, Dimensions(length=-1), name='per_megameter', ascii_symbol='Mm^-1', symbol='Mm⁻¹') +per_square_megameter = NamedUnit(1e-12, Dimensions(length=-2), name='per_square_megameter', ascii_symbol='Mm^-2', symbol='Mm⁻²') +per_cubic_megameter = NamedUnit(1e-18, Dimensions(length=-3), name='per_cubic_megameter', ascii_symbol='Mm^-3', symbol='Mm⁻³') +square_kilometers = NamedUnit(1000000.0, Dimensions(length=2), name='square_kilometers', ascii_symbol='km^2', symbol='km²') +cubic_kilometers = NamedUnit(1000000000.0, Dimensions(length=3), name='cubic_kilometers', ascii_symbol='km^3', symbol='km³') +per_kilometer = NamedUnit(0.001, Dimensions(length=-1), name='per_kilometer', ascii_symbol='km^-1', symbol='km⁻¹') +per_square_kilometer = NamedUnit(1e-06, Dimensions(length=-2), name='per_square_kilometer', ascii_symbol='km^-2', symbol='km⁻²') +per_cubic_kilometer = NamedUnit(1e-09, Dimensions(length=-3), name='per_cubic_kilometer', ascii_symbol='km^-3', symbol='km⁻³') +square_millimeters = NamedUnit(1e-06, Dimensions(length=2), name='square_millimeters', ascii_symbol='mm^2', symbol='mm²') +cubic_millimeters = NamedUnit(1e-09, Dimensions(length=3), name='cubic_millimeters', ascii_symbol='mm^3', symbol='mm³') +per_millimeter = NamedUnit(1000.0, Dimensions(length=-1), name='per_millimeter', ascii_symbol='mm^-1', symbol='mm⁻¹') +per_square_millimeter = NamedUnit(1000000.0, Dimensions(length=-2), name='per_square_millimeter', ascii_symbol='mm^-2', symbol='mm⁻²') +per_cubic_millimeter = NamedUnit(999999999.9999999, Dimensions(length=-3), name='per_cubic_millimeter', ascii_symbol='mm^-3', symbol='mm⁻³') +square_micrometers = NamedUnit(1e-12, Dimensions(length=2), name='square_micrometers', ascii_symbol='um^2', symbol='µm²') +cubic_micrometers = NamedUnit(9.999999999999999e-19, Dimensions(length=3), name='cubic_micrometers', ascii_symbol='um^3', symbol='µm³') +per_micrometer = NamedUnit(1000000.0, Dimensions(length=-1), name='per_micrometer', ascii_symbol='um^-1', symbol='µm⁻¹') +per_square_micrometer = NamedUnit(1000000000000.0001, Dimensions(length=-2), name='per_square_micrometer', ascii_symbol='um^-2', symbol='µm⁻²') +per_cubic_micrometer = NamedUnit(1.0000000000000001e+18, Dimensions(length=-3), name='per_cubic_micrometer', ascii_symbol='um^-3', symbol='µm⁻³') +square_nanometers = NamedUnit(1e-18, Dimensions(length=2), name='square_nanometers', ascii_symbol='nm^2', symbol='nm²') +cubic_nanometers = NamedUnit(1.0000000000000002e-27, Dimensions(length=3), name='cubic_nanometers', ascii_symbol='nm^3', symbol='nm³') +per_nanometer = NamedUnit(999999999.9999999, Dimensions(length=-1), name='per_nanometer', ascii_symbol='nm^-1', symbol='nm⁻¹') +per_square_nanometer = NamedUnit(9.999999999999999e+17, Dimensions(length=-2), name='per_square_nanometer', ascii_symbol='nm^-2', symbol='nm⁻²') +per_cubic_nanometer = NamedUnit(9.999999999999999e+26, Dimensions(length=-3), name='per_cubic_nanometer', ascii_symbol='nm^-3', symbol='nm⁻³') +square_picometers = NamedUnit(1e-24, Dimensions(length=2), name='square_picometers', ascii_symbol='pm^2', symbol='pm²') +cubic_picometers = NamedUnit(1e-36, Dimensions(length=3), name='cubic_picometers', ascii_symbol='pm^3', symbol='pm³') +per_picometer = NamedUnit(1000000000000.0, Dimensions(length=-1), name='per_picometer', ascii_symbol='pm^-1', symbol='pm⁻¹') +per_square_picometer = NamedUnit(1e+24, Dimensions(length=-2), name='per_square_picometer', ascii_symbol='pm^-2', symbol='pm⁻²') +per_cubic_picometer = NamedUnit(1e+36, Dimensions(length=-3), name='per_cubic_picometer', ascii_symbol='pm^-3', symbol='pm⁻³') +square_femtometers = NamedUnit(1e-30, Dimensions(length=2), name='square_femtometers', ascii_symbol='fm^2', symbol='fm²') +cubic_femtometers = NamedUnit(1.0000000000000003e-45, Dimensions(length=3), name='cubic_femtometers', ascii_symbol='fm^3', symbol='fm³') +per_femtometer = NamedUnit(999999999999999.9, Dimensions(length=-1), name='per_femtometer', ascii_symbol='fm^-1', symbol='fm⁻¹') +per_square_femtometer = NamedUnit(9.999999999999999e+29, Dimensions(length=-2), name='per_square_femtometer', ascii_symbol='fm^-2', symbol='fm⁻²') +per_cubic_femtometer = NamedUnit(9.999999999999998e+44, Dimensions(length=-3), name='per_cubic_femtometer', ascii_symbol='fm^-3', symbol='fm⁻³') +square_attometers = NamedUnit(1.0000000000000001e-36, Dimensions(length=2), name='square_attometers', ascii_symbol='am^2', symbol='am²') +cubic_attometers = NamedUnit(1.0000000000000002e-54, Dimensions(length=3), name='cubic_attometers', ascii_symbol='am^3', symbol='am³') +per_attometer = NamedUnit(9.999999999999999e+17, Dimensions(length=-1), name='per_attometer', ascii_symbol='am^-1', symbol='am⁻¹') +per_square_attometer = NamedUnit(9.999999999999999e+35, Dimensions(length=-2), name='per_square_attometer', ascii_symbol='am^-2', symbol='am⁻²') +per_cubic_attometer = NamedUnit(9.999999999999997e+53, Dimensions(length=-3), name='per_cubic_attometer', ascii_symbol='am^-3', symbol='am⁻³') +square_decimeters = NamedUnit(0.010000000000000002, Dimensions(length=2), name='square_decimeters', ascii_symbol='dm^2', symbol='dm²') +cubic_decimeters = NamedUnit(0.0010000000000000002, Dimensions(length=3), name='cubic_decimeters', ascii_symbol='dm^3', symbol='dm³') +per_decimeter = NamedUnit(10.0, Dimensions(length=-1), name='per_decimeter', ascii_symbol='dm^-1', symbol='dm⁻¹') +per_square_decimeter = NamedUnit(99.99999999999999, Dimensions(length=-2), name='per_square_decimeter', ascii_symbol='dm^-2', symbol='dm⁻²') +per_cubic_decimeter = NamedUnit(999.9999999999999, Dimensions(length=-3), name='per_cubic_decimeter', ascii_symbol='dm^-3', symbol='dm⁻³') +square_centimeters = NamedUnit(0.0001, Dimensions(length=2), name='square_centimeters', ascii_symbol='cm^2', symbol='cm²') +cubic_centimeters = NamedUnit(1.0000000000000002e-06, Dimensions(length=3), name='cubic_centimeters', ascii_symbol='cm^3', symbol='cm³') +per_centimeter = NamedUnit(100.0, Dimensions(length=-1), name='per_centimeter', ascii_symbol='cm^-1', symbol='cm⁻¹') +per_square_centimeter = NamedUnit(10000.0, Dimensions(length=-2), name='per_square_centimeter', ascii_symbol='cm^-2', symbol='cm⁻²') +per_cubic_centimeter = NamedUnit(999999.9999999999, Dimensions(length=-3), name='per_cubic_centimeter', ascii_symbol='cm^-3', symbol='cm⁻³') +square_angstroms = NamedUnit(1.0000000000000001e-20, Dimensions(length=2), name='square_angstroms', ascii_symbol='Ang^2', symbol='Ų') +cubic_angstroms = NamedUnit(1e-30, Dimensions(length=3), name='cubic_angstroms', ascii_symbol='Ang^3', symbol='ų') +per_angstrom = NamedUnit(10000000000.0, Dimensions(length=-1), name='per_angstrom', ascii_symbol='Ang^-1', symbol='Å⁻¹') +per_square_angstrom = NamedUnit(1e+20, Dimensions(length=-2), name='per_square_angstrom', ascii_symbol='Ang^-2', symbol='Å⁻²') +per_cubic_angstrom = NamedUnit(9.999999999999999e+29, Dimensions(length=-3), name='per_cubic_angstrom', ascii_symbol='Ang^-3', symbol='Å⁻³') +square_microns = NamedUnit(1e-12, Dimensions(length=2), name='square_microns', ascii_symbol='micron^2', symbol='micron²') +cubic_microns = NamedUnit(9.999999999999999e-19, Dimensions(length=3), name='cubic_microns', ascii_symbol='micron^3', symbol='micron³') +per_micron = NamedUnit(1000000.0, Dimensions(length=-1), name='per_micron', ascii_symbol='micron^-1', symbol='micron⁻¹') +per_square_micron = NamedUnit(1000000000000.0001, Dimensions(length=-2), name='per_square_micron', ascii_symbol='micron^-2', symbol='micron⁻²') +per_cubic_micron = NamedUnit(1.0000000000000001e+18, Dimensions(length=-3), name='per_cubic_micron', ascii_symbol='micron^-3', symbol='micron⁻³') +square_miles = NamedUnit(2589988.110336, Dimensions(length=2), name='square_miles', ascii_symbol='miles^2', symbol='miles²') +cubic_miles = NamedUnit(4168181825.44058, Dimensions(length=3), name='cubic_miles', ascii_symbol='miles^3', symbol='miles³') +per_mile = NamedUnit(0.0006213711922373339, Dimensions(length=-1), name='per_mile', ascii_symbol='miles^-1', symbol='miles⁻¹') +per_square_mile = NamedUnit(3.861021585424458e-07, Dimensions(length=-2), name='per_square_mile', ascii_symbol='miles^-2', symbol='miles⁻²') +per_cubic_mile = NamedUnit(2.399127585789277e-10, Dimensions(length=-3), name='per_cubic_mile', ascii_symbol='miles^-3', symbol='miles⁻³') +square_yards = NamedUnit(0.8361273600000002, Dimensions(length=2), name='square_yards', ascii_symbol='yrd^2', symbol='yrd²') +cubic_yards = NamedUnit(0.7645548579840002, Dimensions(length=3), name='cubic_yards', ascii_symbol='yrd^3', symbol='yrd³') +per_yard = NamedUnit(1.0936132983377076, Dimensions(length=-1), name='per_yard', ascii_symbol='yrd^-1', symbol='yrd⁻¹') +per_square_yard = NamedUnit(1.19599004630108, Dimensions(length=-2), name='per_square_yard', ascii_symbol='yrd^-2', symbol='yrd⁻²') +per_cubic_yard = NamedUnit(1.3079506193143917, Dimensions(length=-3), name='per_cubic_yard', ascii_symbol='yrd^-3', symbol='yrd⁻³') +square_feet = NamedUnit(0.09290304, Dimensions(length=2), name='square_feet', ascii_symbol='ft^2', symbol='ft²') +cubic_feet = NamedUnit(0.028316846592000004, Dimensions(length=3), name='cubic_feet', ascii_symbol='ft^3', symbol='ft³') +per_foot = NamedUnit(3.280839895013123, Dimensions(length=-1), name='per_foot', ascii_symbol='ft^-1', symbol='ft⁻¹') +per_square_foot = NamedUnit(10.763910416709722, Dimensions(length=-2), name='per_square_foot', ascii_symbol='ft^-2', symbol='ft⁻²') +per_cubic_foot = NamedUnit(35.314666721488585, Dimensions(length=-3), name='per_cubic_foot', ascii_symbol='ft^-3', symbol='ft⁻³') +square_inches = NamedUnit(0.00064516, Dimensions(length=2), name='square_inches', ascii_symbol='in^2', symbol='in²') +cubic_inches = NamedUnit(1.6387064e-05, Dimensions(length=3), name='cubic_inches', ascii_symbol='in^3', symbol='in³') +per_inch = NamedUnit(39.37007874015748, Dimensions(length=-1), name='per_inch', ascii_symbol='in^-1', symbol='in⁻¹') +per_square_inch = NamedUnit(1550.0031000062002, Dimensions(length=-2), name='per_square_inch', ascii_symbol='in^-2', symbol='in⁻²') +per_cubic_inch = NamedUnit(61023.74409473229, Dimensions(length=-3), name='per_cubic_inch', ascii_symbol='in^-3', symbol='in⁻³') +meters_per_second = NamedUnit(1.0, Dimensions(length=1, time=-1), name='meters_per_second', ascii_symbol='m/s', symbol='ms⁻¹') +meters_per_square_second = NamedUnit(1.0, Dimensions(length=1, time=-2), name='meters_per_square_second', ascii_symbol='m/s^2', symbol='ms⁻²') +meters_per_millisecond = NamedUnit(1000.0, Dimensions(length=1, time=-1), name='meters_per_millisecond', ascii_symbol='m/ms', symbol='mms⁻¹') +meters_per_square_millisecond = NamedUnit(1000000.0, Dimensions(length=1, time=-2), name='meters_per_square_millisecond', ascii_symbol='m/ms^2', symbol='mms⁻²') +meters_per_microsecond = NamedUnit(1000000.0, Dimensions(length=1, time=-1), name='meters_per_microsecond', ascii_symbol='m/us', symbol='mµs⁻¹') +meters_per_square_microsecond = NamedUnit(1000000000000.0, Dimensions(length=1, time=-2), name='meters_per_square_microsecond', ascii_symbol='m/us^2', symbol='mµs⁻²') +meters_per_nanosecond = NamedUnit(999999999.9999999, Dimensions(length=1, time=-1), name='meters_per_nanosecond', ascii_symbol='m/ns', symbol='mns⁻¹') +meters_per_square_nanosecond = NamedUnit(9.999999999999999e+17, Dimensions(length=1, time=-2), name='meters_per_square_nanosecond', ascii_symbol='m/ns^2', symbol='mns⁻²') +meters_per_picosecond = NamedUnit(1000000000000.0, Dimensions(length=1, time=-1), name='meters_per_picosecond', ascii_symbol='m/ps', symbol='mps⁻¹') +meters_per_square_picosecond = NamedUnit(1.0000000000000001e+24, Dimensions(length=1, time=-2), name='meters_per_square_picosecond', ascii_symbol='m/ps^2', symbol='mps⁻²') +meters_per_femtosecond = NamedUnit(999999999999999.9, Dimensions(length=1, time=-1), name='meters_per_femtosecond', ascii_symbol='m/fs', symbol='mfs⁻¹') +meters_per_square_femtosecond = NamedUnit(9.999999999999999e+29, Dimensions(length=1, time=-2), name='meters_per_square_femtosecond', ascii_symbol='m/fs^2', symbol='mfs⁻²') +meters_per_attosecond = NamedUnit(9.999999999999999e+17, Dimensions(length=1, time=-1), name='meters_per_attosecond', ascii_symbol='m/as', symbol='mas⁻¹') +meters_per_square_attosecond = NamedUnit(9.999999999999999e+35, Dimensions(length=1, time=-2), name='meters_per_square_attosecond', ascii_symbol='m/as^2', symbol='mas⁻²') +meters_per_minute = NamedUnit(0.016666666666666666, Dimensions(length=1, time=-1), name='meters_per_minute', ascii_symbol='m/min', symbol='mmin⁻¹') +meters_per_square_minute = NamedUnit(0.0002777777777777778, Dimensions(length=1, time=-2), name='meters_per_square_minute', ascii_symbol='m/min^2', symbol='mmin⁻²') +meters_per_hour = NamedUnit(0.002777777777777778, Dimensions(length=1, time=-1), name='meters_per_hour', ascii_symbol='m/h', symbol='mh⁻¹') +meters_per_square_hour = NamedUnit(7.71604938271605e-06, Dimensions(length=1, time=-2), name='meters_per_square_hour', ascii_symbol='m/h^2', symbol='mh⁻²') +meters_per_day = NamedUnit(0.00011574074074074075, Dimensions(length=1, time=-1), name='meters_per_day', ascii_symbol='m/d', symbol='md⁻¹') +meters_per_square_day = NamedUnit(1.3395919067215363e-08, Dimensions(length=1, time=-2), name='meters_per_square_day', ascii_symbol='m/d^2', symbol='md⁻²') +meters_per_year = NamedUnit(3.168873850681143e-07, Dimensions(length=1, time=-1), name='meters_per_year', ascii_symbol='m/y', symbol='my⁻¹') +meters_per_square_year = NamedUnit(1.0041761481530735e-13, Dimensions(length=1, time=-2), name='meters_per_square_year', ascii_symbol='m/y^2', symbol='my⁻²') +exameters_per_second = NamedUnit(1e+18, Dimensions(length=1, time=-1), name='exameters_per_second', ascii_symbol='Em/s', symbol='Ems⁻¹') +exameters_per_square_second = NamedUnit(1e+18, Dimensions(length=1, time=-2), name='exameters_per_square_second', ascii_symbol='Em/s^2', symbol='Ems⁻²') +exameters_per_millisecond = NamedUnit(1e+21, Dimensions(length=1, time=-1), name='exameters_per_millisecond', ascii_symbol='Em/ms', symbol='Emms⁻¹') +exameters_per_square_millisecond = NamedUnit(1e+24, Dimensions(length=1, time=-2), name='exameters_per_square_millisecond', ascii_symbol='Em/ms^2', symbol='Emms⁻²') +exameters_per_microsecond = NamedUnit(1e+24, Dimensions(length=1, time=-1), name='exameters_per_microsecond', ascii_symbol='Em/us', symbol='Emµs⁻¹') +exameters_per_square_microsecond = NamedUnit(1e+30, Dimensions(length=1, time=-2), name='exameters_per_square_microsecond', ascii_symbol='Em/us^2', symbol='Emµs⁻²') +exameters_per_nanosecond = NamedUnit(9.999999999999999e+26, Dimensions(length=1, time=-1), name='exameters_per_nanosecond', ascii_symbol='Em/ns', symbol='Emns⁻¹') +exameters_per_square_nanosecond = NamedUnit(9.999999999999999e+35, Dimensions(length=1, time=-2), name='exameters_per_square_nanosecond', ascii_symbol='Em/ns^2', symbol='Emns⁻²') +exameters_per_picosecond = NamedUnit(1e+30, Dimensions(length=1, time=-1), name='exameters_per_picosecond', ascii_symbol='Em/ps', symbol='Emps⁻¹') +exameters_per_square_picosecond = NamedUnit(1e+42, Dimensions(length=1, time=-2), name='exameters_per_square_picosecond', ascii_symbol='Em/ps^2', symbol='Emps⁻²') +exameters_per_femtosecond = NamedUnit(1e+33, Dimensions(length=1, time=-1), name='exameters_per_femtosecond', ascii_symbol='Em/fs', symbol='Emfs⁻¹') +exameters_per_square_femtosecond = NamedUnit(9.999999999999999e+47, Dimensions(length=1, time=-2), name='exameters_per_square_femtosecond', ascii_symbol='Em/fs^2', symbol='Emfs⁻²') +exameters_per_attosecond = NamedUnit(9.999999999999999e+35, Dimensions(length=1, time=-1), name='exameters_per_attosecond', ascii_symbol='Em/as', symbol='Emas⁻¹') +exameters_per_square_attosecond = NamedUnit(9.999999999999999e+53, Dimensions(length=1, time=-2), name='exameters_per_square_attosecond', ascii_symbol='Em/as^2', symbol='Emas⁻²') +exameters_per_minute = NamedUnit(1.6666666666666666e+16, Dimensions(length=1, time=-1), name='exameters_per_minute', ascii_symbol='Em/min', symbol='Emmin⁻¹') +exameters_per_square_minute = NamedUnit(277777777777777.78, Dimensions(length=1, time=-2), name='exameters_per_square_minute', ascii_symbol='Em/min^2', symbol='Emmin⁻²') +exameters_per_hour = NamedUnit(2777777777777778.0, Dimensions(length=1, time=-1), name='exameters_per_hour', ascii_symbol='Em/h', symbol='Emh⁻¹') +exameters_per_square_hour = NamedUnit(7716049382716.05, Dimensions(length=1, time=-2), name='exameters_per_square_hour', ascii_symbol='Em/h^2', symbol='Emh⁻²') +exameters_per_day = NamedUnit(115740740740740.73, Dimensions(length=1, time=-1), name='exameters_per_day', ascii_symbol='Em/d', symbol='Emd⁻¹') +exameters_per_square_day = NamedUnit(13395919067.215364, Dimensions(length=1, time=-2), name='exameters_per_square_day', ascii_symbol='Em/d^2', symbol='Emd⁻²') +exameters_per_year = NamedUnit(316887385068.1143, Dimensions(length=1, time=-1), name='exameters_per_year', ascii_symbol='Em/y', symbol='Emy⁻¹') +exameters_per_square_year = NamedUnit(100417.61481530734, Dimensions(length=1, time=-2), name='exameters_per_square_year', ascii_symbol='Em/y^2', symbol='Emy⁻²') +petameters_per_second = NamedUnit(1000000000000000.0, Dimensions(length=1, time=-1), name='petameters_per_second', ascii_symbol='Pm/s', symbol='Pms⁻¹') +petameters_per_square_second = NamedUnit(1000000000000000.0, Dimensions(length=1, time=-2), name='petameters_per_square_second', ascii_symbol='Pm/s^2', symbol='Pms⁻²') +petameters_per_millisecond = NamedUnit(1e+18, Dimensions(length=1, time=-1), name='petameters_per_millisecond', ascii_symbol='Pm/ms', symbol='Pmms⁻¹') +petameters_per_square_millisecond = NamedUnit(1e+21, Dimensions(length=1, time=-2), name='petameters_per_square_millisecond', ascii_symbol='Pm/ms^2', symbol='Pmms⁻²') +petameters_per_microsecond = NamedUnit(1e+21, Dimensions(length=1, time=-1), name='petameters_per_microsecond', ascii_symbol='Pm/us', symbol='Pmµs⁻¹') +petameters_per_square_microsecond = NamedUnit(1e+27, Dimensions(length=1, time=-2), name='petameters_per_square_microsecond', ascii_symbol='Pm/us^2', symbol='Pmµs⁻²') +petameters_per_nanosecond = NamedUnit(1e+24, Dimensions(length=1, time=-1), name='petameters_per_nanosecond', ascii_symbol='Pm/ns', symbol='Pmns⁻¹') +petameters_per_square_nanosecond = NamedUnit(1e+33, Dimensions(length=1, time=-2), name='petameters_per_square_nanosecond', ascii_symbol='Pm/ns^2', symbol='Pmns⁻²') +petameters_per_picosecond = NamedUnit(1e+27, Dimensions(length=1, time=-1), name='petameters_per_picosecond', ascii_symbol='Pm/ps', symbol='Pmps⁻¹') +petameters_per_square_picosecond = NamedUnit(1.0000000000000001e+39, Dimensions(length=1, time=-2), name='petameters_per_square_picosecond', ascii_symbol='Pm/ps^2', symbol='Pmps⁻²') +petameters_per_femtosecond = NamedUnit(9.999999999999999e+29, Dimensions(length=1, time=-1), name='petameters_per_femtosecond', ascii_symbol='Pm/fs', symbol='Pmfs⁻¹') +petameters_per_square_femtosecond = NamedUnit(1e+45, Dimensions(length=1, time=-2), name='petameters_per_square_femtosecond', ascii_symbol='Pm/fs^2', symbol='Pmfs⁻²') +petameters_per_attosecond = NamedUnit(1e+33, Dimensions(length=1, time=-1), name='petameters_per_attosecond', ascii_symbol='Pm/as', symbol='Pmas⁻¹') +petameters_per_square_attosecond = NamedUnit(9.999999999999998e+50, Dimensions(length=1, time=-2), name='petameters_per_square_attosecond', ascii_symbol='Pm/as^2', symbol='Pmas⁻²') +petameters_per_minute = NamedUnit(16666666666666.666, Dimensions(length=1, time=-1), name='petameters_per_minute', ascii_symbol='Pm/min', symbol='Pmmin⁻¹') +petameters_per_square_minute = NamedUnit(277777777777.7778, Dimensions(length=1, time=-2), name='petameters_per_square_minute', ascii_symbol='Pm/min^2', symbol='Pmmin⁻²') +petameters_per_hour = NamedUnit(2777777777777.778, Dimensions(length=1, time=-1), name='petameters_per_hour', ascii_symbol='Pm/h', symbol='Pmh⁻¹') +petameters_per_square_hour = NamedUnit(7716049382.716049, Dimensions(length=1, time=-2), name='petameters_per_square_hour', ascii_symbol='Pm/h^2', symbol='Pmh⁻²') +petameters_per_day = NamedUnit(115740740740.74074, Dimensions(length=1, time=-1), name='petameters_per_day', ascii_symbol='Pm/d', symbol='Pmd⁻¹') +petameters_per_square_day = NamedUnit(13395919.067215364, Dimensions(length=1, time=-2), name='petameters_per_square_day', ascii_symbol='Pm/d^2', symbol='Pmd⁻²') +petameters_per_year = NamedUnit(316887385.0681143, Dimensions(length=1, time=-1), name='petameters_per_year', ascii_symbol='Pm/y', symbol='Pmy⁻¹') +petameters_per_square_year = NamedUnit(100.41761481530735, Dimensions(length=1, time=-2), name='petameters_per_square_year', ascii_symbol='Pm/y^2', symbol='Pmy⁻²') +terameters_per_second = NamedUnit(1000000000000.0, Dimensions(length=1, time=-1), name='terameters_per_second', ascii_symbol='Tm/s', symbol='Tms⁻¹') +terameters_per_square_second = NamedUnit(1000000000000.0, Dimensions(length=1, time=-2), name='terameters_per_square_second', ascii_symbol='Tm/s^2', symbol='Tms⁻²') +terameters_per_millisecond = NamedUnit(1000000000000000.0, Dimensions(length=1, time=-1), name='terameters_per_millisecond', ascii_symbol='Tm/ms', symbol='Tmms⁻¹') +terameters_per_square_millisecond = NamedUnit(1e+18, Dimensions(length=1, time=-2), name='terameters_per_square_millisecond', ascii_symbol='Tm/ms^2', symbol='Tmms⁻²') +terameters_per_microsecond = NamedUnit(1e+18, Dimensions(length=1, time=-1), name='terameters_per_microsecond', ascii_symbol='Tm/us', symbol='Tmµs⁻¹') +terameters_per_square_microsecond = NamedUnit(1e+24, Dimensions(length=1, time=-2), name='terameters_per_square_microsecond', ascii_symbol='Tm/us^2', symbol='Tmµs⁻²') +terameters_per_nanosecond = NamedUnit(1e+21, Dimensions(length=1, time=-1), name='terameters_per_nanosecond', ascii_symbol='Tm/ns', symbol='Tmns⁻¹') +terameters_per_square_nanosecond = NamedUnit(9.999999999999999e+29, Dimensions(length=1, time=-2), name='terameters_per_square_nanosecond', ascii_symbol='Tm/ns^2', symbol='Tmns⁻²') +terameters_per_picosecond = NamedUnit(1e+24, Dimensions(length=1, time=-1), name='terameters_per_picosecond', ascii_symbol='Tm/ps', symbol='Tmps⁻¹') +terameters_per_square_picosecond = NamedUnit(1e+36, Dimensions(length=1, time=-2), name='terameters_per_square_picosecond', ascii_symbol='Tm/ps^2', symbol='Tmps⁻²') +terameters_per_femtosecond = NamedUnit(9.999999999999999e+26, Dimensions(length=1, time=-1), name='terameters_per_femtosecond', ascii_symbol='Tm/fs', symbol='Tmfs⁻¹') +terameters_per_square_femtosecond = NamedUnit(9.999999999999999e+41, Dimensions(length=1, time=-2), name='terameters_per_square_femtosecond', ascii_symbol='Tm/fs^2', symbol='Tmfs⁻²') +terameters_per_attosecond = NamedUnit(9.999999999999999e+29, Dimensions(length=1, time=-1), name='terameters_per_attosecond', ascii_symbol='Tm/as', symbol='Tmas⁻¹') +terameters_per_square_attosecond = NamedUnit(9.999999999999999e+47, Dimensions(length=1, time=-2), name='terameters_per_square_attosecond', ascii_symbol='Tm/as^2', symbol='Tmas⁻²') +terameters_per_minute = NamedUnit(16666666666.666666, Dimensions(length=1, time=-1), name='terameters_per_minute', ascii_symbol='Tm/min', symbol='Tmmin⁻¹') +terameters_per_square_minute = NamedUnit(277777777.7777778, Dimensions(length=1, time=-2), name='terameters_per_square_minute', ascii_symbol='Tm/min^2', symbol='Tmmin⁻²') +terameters_per_hour = NamedUnit(2777777777.7777777, Dimensions(length=1, time=-1), name='terameters_per_hour', ascii_symbol='Tm/h', symbol='Tmh⁻¹') +terameters_per_square_hour = NamedUnit(7716049.382716049, Dimensions(length=1, time=-2), name='terameters_per_square_hour', ascii_symbol='Tm/h^2', symbol='Tmh⁻²') +terameters_per_day = NamedUnit(115740740.74074075, Dimensions(length=1, time=-1), name='terameters_per_day', ascii_symbol='Tm/d', symbol='Tmd⁻¹') +terameters_per_square_day = NamedUnit(13395.919067215364, Dimensions(length=1, time=-2), name='terameters_per_square_day', ascii_symbol='Tm/d^2', symbol='Tmd⁻²') +terameters_per_year = NamedUnit(316887.38506811426, Dimensions(length=1, time=-1), name='terameters_per_year', ascii_symbol='Tm/y', symbol='Tmy⁻¹') +terameters_per_square_year = NamedUnit(0.10041761481530735, Dimensions(length=1, time=-2), name='terameters_per_square_year', ascii_symbol='Tm/y^2', symbol='Tmy⁻²') +gigameters_per_second = NamedUnit(1000000000.0, Dimensions(length=1, time=-1), name='gigameters_per_second', ascii_symbol='Gm/s', symbol='Gms⁻¹') +gigameters_per_square_second = NamedUnit(1000000000.0, Dimensions(length=1, time=-2), name='gigameters_per_square_second', ascii_symbol='Gm/s^2', symbol='Gms⁻²') +gigameters_per_millisecond = NamedUnit(1000000000000.0, Dimensions(length=1, time=-1), name='gigameters_per_millisecond', ascii_symbol='Gm/ms', symbol='Gmms⁻¹') +gigameters_per_square_millisecond = NamedUnit(1000000000000000.0, Dimensions(length=1, time=-2), name='gigameters_per_square_millisecond', ascii_symbol='Gm/ms^2', symbol='Gmms⁻²') +gigameters_per_microsecond = NamedUnit(1000000000000000.0, Dimensions(length=1, time=-1), name='gigameters_per_microsecond', ascii_symbol='Gm/us', symbol='Gmµs⁻¹') +gigameters_per_square_microsecond = NamedUnit(1e+21, Dimensions(length=1, time=-2), name='gigameters_per_square_microsecond', ascii_symbol='Gm/us^2', symbol='Gmµs⁻²') +gigameters_per_nanosecond = NamedUnit(1e+18, Dimensions(length=1, time=-1), name='gigameters_per_nanosecond', ascii_symbol='Gm/ns', symbol='Gmns⁻¹') +gigameters_per_square_nanosecond = NamedUnit(9.999999999999999e+26, Dimensions(length=1, time=-2), name='gigameters_per_square_nanosecond', ascii_symbol='Gm/ns^2', symbol='Gmns⁻²') +gigameters_per_picosecond = NamedUnit(1e+21, Dimensions(length=1, time=-1), name='gigameters_per_picosecond', ascii_symbol='Gm/ps', symbol='Gmps⁻¹') +gigameters_per_square_picosecond = NamedUnit(1.0000000000000001e+33, Dimensions(length=1, time=-2), name='gigameters_per_square_picosecond', ascii_symbol='Gm/ps^2', symbol='Gmps⁻²') +gigameters_per_femtosecond = NamedUnit(1e+24, Dimensions(length=1, time=-1), name='gigameters_per_femtosecond', ascii_symbol='Gm/fs', symbol='Gmfs⁻¹') +gigameters_per_square_femtosecond = NamedUnit(1e+39, Dimensions(length=1, time=-2), name='gigameters_per_square_femtosecond', ascii_symbol='Gm/fs^2', symbol='Gmfs⁻²') +gigameters_per_attosecond = NamedUnit(9.999999999999999e+26, Dimensions(length=1, time=-1), name='gigameters_per_attosecond', ascii_symbol='Gm/as', symbol='Gmas⁻¹') +gigameters_per_square_attosecond = NamedUnit(1e+45, Dimensions(length=1, time=-2), name='gigameters_per_square_attosecond', ascii_symbol='Gm/as^2', symbol='Gmas⁻²') +gigameters_per_minute = NamedUnit(16666666.666666666, Dimensions(length=1, time=-1), name='gigameters_per_minute', ascii_symbol='Gm/min', symbol='Gmmin⁻¹') +gigameters_per_square_minute = NamedUnit(277777.77777777775, Dimensions(length=1, time=-2), name='gigameters_per_square_minute', ascii_symbol='Gm/min^2', symbol='Gmmin⁻²') +gigameters_per_hour = NamedUnit(2777777.777777778, Dimensions(length=1, time=-1), name='gigameters_per_hour', ascii_symbol='Gm/h', symbol='Gmh⁻¹') +gigameters_per_square_hour = NamedUnit(7716.049382716049, Dimensions(length=1, time=-2), name='gigameters_per_square_hour', ascii_symbol='Gm/h^2', symbol='Gmh⁻²') +gigameters_per_day = NamedUnit(115740.74074074074, Dimensions(length=1, time=-1), name='gigameters_per_day', ascii_symbol='Gm/d', symbol='Gmd⁻¹') +gigameters_per_square_day = NamedUnit(13.395919067215363, Dimensions(length=1, time=-2), name='gigameters_per_square_day', ascii_symbol='Gm/d^2', symbol='Gmd⁻²') +gigameters_per_year = NamedUnit(316.88738506811427, Dimensions(length=1, time=-1), name='gigameters_per_year', ascii_symbol='Gm/y', symbol='Gmy⁻¹') +gigameters_per_square_year = NamedUnit(0.00010041761481530735, Dimensions(length=1, time=-2), name='gigameters_per_square_year', ascii_symbol='Gm/y^2', symbol='Gmy⁻²') +megameters_per_second = NamedUnit(1000000.0, Dimensions(length=1, time=-1), name='megameters_per_second', ascii_symbol='Mm/s', symbol='Mms⁻¹') +megameters_per_square_second = NamedUnit(1000000.0, Dimensions(length=1, time=-2), name='megameters_per_square_second', ascii_symbol='Mm/s^2', symbol='Mms⁻²') +megameters_per_millisecond = NamedUnit(1000000000.0, Dimensions(length=1, time=-1), name='megameters_per_millisecond', ascii_symbol='Mm/ms', symbol='Mmms⁻¹') +megameters_per_square_millisecond = NamedUnit(1000000000000.0, Dimensions(length=1, time=-2), name='megameters_per_square_millisecond', ascii_symbol='Mm/ms^2', symbol='Mmms⁻²') +megameters_per_microsecond = NamedUnit(1000000000000.0, Dimensions(length=1, time=-1), name='megameters_per_microsecond', ascii_symbol='Mm/us', symbol='Mmµs⁻¹') +megameters_per_square_microsecond = NamedUnit(1e+18, Dimensions(length=1, time=-2), name='megameters_per_square_microsecond', ascii_symbol='Mm/us^2', symbol='Mmµs⁻²') +megameters_per_nanosecond = NamedUnit(1000000000000000.0, Dimensions(length=1, time=-1), name='megameters_per_nanosecond', ascii_symbol='Mm/ns', symbol='Mmns⁻¹') +megameters_per_square_nanosecond = NamedUnit(1e+24, Dimensions(length=1, time=-2), name='megameters_per_square_nanosecond', ascii_symbol='Mm/ns^2', symbol='Mmns⁻²') +megameters_per_picosecond = NamedUnit(1e+18, Dimensions(length=1, time=-1), name='megameters_per_picosecond', ascii_symbol='Mm/ps', symbol='Mmps⁻¹') +megameters_per_square_picosecond = NamedUnit(1e+30, Dimensions(length=1, time=-2), name='megameters_per_square_picosecond', ascii_symbol='Mm/ps^2', symbol='Mmps⁻²') +megameters_per_femtosecond = NamedUnit(9.999999999999999e+20, Dimensions(length=1, time=-1), name='megameters_per_femtosecond', ascii_symbol='Mm/fs', symbol='Mmfs⁻¹') +megameters_per_square_femtosecond = NamedUnit(9.999999999999999e+35, Dimensions(length=1, time=-2), name='megameters_per_square_femtosecond', ascii_symbol='Mm/fs^2', symbol='Mmfs⁻²') +megameters_per_attosecond = NamedUnit(1e+24, Dimensions(length=1, time=-1), name='megameters_per_attosecond', ascii_symbol='Mm/as', symbol='Mmas⁻¹') +megameters_per_square_attosecond = NamedUnit(9.999999999999999e+41, Dimensions(length=1, time=-2), name='megameters_per_square_attosecond', ascii_symbol='Mm/as^2', symbol='Mmas⁻²') +megameters_per_minute = NamedUnit(16666.666666666668, Dimensions(length=1, time=-1), name='megameters_per_minute', ascii_symbol='Mm/min', symbol='Mmmin⁻¹') +megameters_per_square_minute = NamedUnit(277.77777777777777, Dimensions(length=1, time=-2), name='megameters_per_square_minute', ascii_symbol='Mm/min^2', symbol='Mmmin⁻²') +megameters_per_hour = NamedUnit(2777.777777777778, Dimensions(length=1, time=-1), name='megameters_per_hour', ascii_symbol='Mm/h', symbol='Mmh⁻¹') +megameters_per_square_hour = NamedUnit(7.716049382716049, Dimensions(length=1, time=-2), name='megameters_per_square_hour', ascii_symbol='Mm/h^2', symbol='Mmh⁻²') +megameters_per_day = NamedUnit(115.74074074074075, Dimensions(length=1, time=-1), name='megameters_per_day', ascii_symbol='Mm/d', symbol='Mmd⁻¹') +megameters_per_square_day = NamedUnit(0.013395919067215363, Dimensions(length=1, time=-2), name='megameters_per_square_day', ascii_symbol='Mm/d^2', symbol='Mmd⁻²') +megameters_per_year = NamedUnit(0.3168873850681143, Dimensions(length=1, time=-1), name='megameters_per_year', ascii_symbol='Mm/y', symbol='Mmy⁻¹') +megameters_per_square_year = NamedUnit(1.0041761481530735e-07, Dimensions(length=1, time=-2), name='megameters_per_square_year', ascii_symbol='Mm/y^2', symbol='Mmy⁻²') +kilometers_per_second = NamedUnit(1000.0, Dimensions(length=1, time=-1), name='kilometers_per_second', ascii_symbol='km/s', symbol='kms⁻¹') +kilometers_per_square_second = NamedUnit(1000.0, Dimensions(length=1, time=-2), name='kilometers_per_square_second', ascii_symbol='km/s^2', symbol='kms⁻²') +kilometers_per_millisecond = NamedUnit(1000000.0, Dimensions(length=1, time=-1), name='kilometers_per_millisecond', ascii_symbol='km/ms', symbol='kmms⁻¹') +kilometers_per_square_millisecond = NamedUnit(1000000000.0, Dimensions(length=1, time=-2), name='kilometers_per_square_millisecond', ascii_symbol='km/ms^2', symbol='kmms⁻²') +kilometers_per_microsecond = NamedUnit(1000000000.0, Dimensions(length=1, time=-1), name='kilometers_per_microsecond', ascii_symbol='km/us', symbol='kmµs⁻¹') +kilometers_per_square_microsecond = NamedUnit(1000000000000000.0, Dimensions(length=1, time=-2), name='kilometers_per_square_microsecond', ascii_symbol='km/us^2', symbol='kmµs⁻²') +kilometers_per_nanosecond = NamedUnit(999999999999.9999, Dimensions(length=1, time=-1), name='kilometers_per_nanosecond', ascii_symbol='km/ns', symbol='kmns⁻¹') +kilometers_per_square_nanosecond = NamedUnit(9.999999999999999e+20, Dimensions(length=1, time=-2), name='kilometers_per_square_nanosecond', ascii_symbol='km/ns^2', symbol='kmns⁻²') +kilometers_per_picosecond = NamedUnit(1000000000000000.0, Dimensions(length=1, time=-1), name='kilometers_per_picosecond', ascii_symbol='km/ps', symbol='kmps⁻¹') +kilometers_per_square_picosecond = NamedUnit(1e+27, Dimensions(length=1, time=-2), name='kilometers_per_square_picosecond', ascii_symbol='km/ps^2', symbol='kmps⁻²') +kilometers_per_femtosecond = NamedUnit(9.999999999999999e+17, Dimensions(length=1, time=-1), name='kilometers_per_femtosecond', ascii_symbol='km/fs', symbol='kmfs⁻¹') +kilometers_per_square_femtosecond = NamedUnit(1e+33, Dimensions(length=1, time=-2), name='kilometers_per_square_femtosecond', ascii_symbol='km/fs^2', symbol='kmfs⁻²') +kilometers_per_attosecond = NamedUnit(9.999999999999999e+20, Dimensions(length=1, time=-1), name='kilometers_per_attosecond', ascii_symbol='km/as', symbol='kmas⁻¹') +kilometers_per_square_attosecond = NamedUnit(1e+39, Dimensions(length=1, time=-2), name='kilometers_per_square_attosecond', ascii_symbol='km/as^2', symbol='kmas⁻²') +kilometers_per_minute = NamedUnit(16.666666666666668, Dimensions(length=1, time=-1), name='kilometers_per_minute', ascii_symbol='km/min', symbol='kmmin⁻¹') +kilometers_per_square_minute = NamedUnit(0.2777777777777778, Dimensions(length=1, time=-2), name='kilometers_per_square_minute', ascii_symbol='km/min^2', symbol='kmmin⁻²') +kilometers_per_hour = NamedUnit(2.7777777777777777, Dimensions(length=1, time=-1), name='kilometers_per_hour', ascii_symbol='km/h', symbol='kmh⁻¹') +kilometers_per_square_hour = NamedUnit(0.007716049382716049, Dimensions(length=1, time=-2), name='kilometers_per_square_hour', ascii_symbol='km/h^2', symbol='kmh⁻²') +kilometers_per_day = NamedUnit(0.11574074074074074, Dimensions(length=1, time=-1), name='kilometers_per_day', ascii_symbol='km/d', symbol='kmd⁻¹') +kilometers_per_square_day = NamedUnit(1.3395919067215363e-05, Dimensions(length=1, time=-2), name='kilometers_per_square_day', ascii_symbol='km/d^2', symbol='kmd⁻²') +kilometers_per_year = NamedUnit(0.0003168873850681143, Dimensions(length=1, time=-1), name='kilometers_per_year', ascii_symbol='km/y', symbol='kmy⁻¹') +kilometers_per_square_year = NamedUnit(1.0041761481530735e-10, Dimensions(length=1, time=-2), name='kilometers_per_square_year', ascii_symbol='km/y^2', symbol='kmy⁻²') +millimeters_per_second = NamedUnit(0.001, Dimensions(length=1, time=-1), name='millimeters_per_second', ascii_symbol='mm/s', symbol='mms⁻¹') +millimeters_per_square_second = NamedUnit(0.001, Dimensions(length=1, time=-2), name='millimeters_per_square_second', ascii_symbol='mm/s^2', symbol='mms⁻²') +millimeters_per_millisecond = NamedUnit(1.0, Dimensions(length=1, time=-1), name='millimeters_per_millisecond', ascii_symbol='mm/ms', symbol='mmms⁻¹') +millimeters_per_square_millisecond = NamedUnit(1000.0000000000001, Dimensions(length=1, time=-2), name='millimeters_per_square_millisecond', ascii_symbol='mm/ms^2', symbol='mmms⁻²') +millimeters_per_microsecond = NamedUnit(1000.0000000000001, Dimensions(length=1, time=-1), name='millimeters_per_microsecond', ascii_symbol='mm/us', symbol='mmµs⁻¹') +millimeters_per_square_microsecond = NamedUnit(1000000000.0, Dimensions(length=1, time=-2), name='millimeters_per_square_microsecond', ascii_symbol='mm/us^2', symbol='mmµs⁻²') +millimeters_per_nanosecond = NamedUnit(1000000.0, Dimensions(length=1, time=-1), name='millimeters_per_nanosecond', ascii_symbol='mm/ns', symbol='mmns⁻¹') +millimeters_per_square_nanosecond = NamedUnit(1000000000000000.0, Dimensions(length=1, time=-2), name='millimeters_per_square_nanosecond', ascii_symbol='mm/ns^2', symbol='mmns⁻²') +millimeters_per_picosecond = NamedUnit(1000000000.0, Dimensions(length=1, time=-1), name='millimeters_per_picosecond', ascii_symbol='mm/ps', symbol='mmps⁻¹') +millimeters_per_square_picosecond = NamedUnit(1.0000000000000001e+21, Dimensions(length=1, time=-2), name='millimeters_per_square_picosecond', ascii_symbol='mm/ps^2', symbol='mmps⁻²') +millimeters_per_femtosecond = NamedUnit(1000000000000.0, Dimensions(length=1, time=-1), name='millimeters_per_femtosecond', ascii_symbol='mm/fs', symbol='mmfs⁻¹') +millimeters_per_square_femtosecond = NamedUnit(9.999999999999999e+26, Dimensions(length=1, time=-2), name='millimeters_per_square_femtosecond', ascii_symbol='mm/fs^2', symbol='mmfs⁻²') +millimeters_per_attosecond = NamedUnit(1000000000000000.0, Dimensions(length=1, time=-1), name='millimeters_per_attosecond', ascii_symbol='mm/as', symbol='mmas⁻¹') +millimeters_per_square_attosecond = NamedUnit(1e+33, Dimensions(length=1, time=-2), name='millimeters_per_square_attosecond', ascii_symbol='mm/as^2', symbol='mmas⁻²') +millimeters_per_minute = NamedUnit(1.6666666666666667e-05, Dimensions(length=1, time=-1), name='millimeters_per_minute', ascii_symbol='mm/min', symbol='mmmin⁻¹') +millimeters_per_square_minute = NamedUnit(2.7777777777777776e-07, Dimensions(length=1, time=-2), name='millimeters_per_square_minute', ascii_symbol='mm/min^2', symbol='mmmin⁻²') +millimeters_per_hour = NamedUnit(2.777777777777778e-06, Dimensions(length=1, time=-1), name='millimeters_per_hour', ascii_symbol='mm/h', symbol='mmh⁻¹') +millimeters_per_square_hour = NamedUnit(7.71604938271605e-09, Dimensions(length=1, time=-2), name='millimeters_per_square_hour', ascii_symbol='mm/h^2', symbol='mmh⁻²') +millimeters_per_day = NamedUnit(1.1574074074074074e-07, Dimensions(length=1, time=-1), name='millimeters_per_day', ascii_symbol='mm/d', symbol='mmd⁻¹') +millimeters_per_square_day = NamedUnit(1.3395919067215364e-11, Dimensions(length=1, time=-2), name='millimeters_per_square_day', ascii_symbol='mm/d^2', symbol='mmd⁻²') +millimeters_per_year = NamedUnit(3.168873850681143e-10, Dimensions(length=1, time=-1), name='millimeters_per_year', ascii_symbol='mm/y', symbol='mmy⁻¹') +millimeters_per_square_year = NamedUnit(1.0041761481530735e-16, Dimensions(length=1, time=-2), name='millimeters_per_square_year', ascii_symbol='mm/y^2', symbol='mmy⁻²') +micrometers_per_second = NamedUnit(1e-06, Dimensions(length=1, time=-1), name='micrometers_per_second', ascii_symbol='um/s', symbol='µms⁻¹') +micrometers_per_square_second = NamedUnit(1e-06, Dimensions(length=1, time=-2), name='micrometers_per_square_second', ascii_symbol='um/s^2', symbol='µms⁻²') +micrometers_per_millisecond = NamedUnit(0.001, Dimensions(length=1, time=-1), name='micrometers_per_millisecond', ascii_symbol='um/ms', symbol='µmms⁻¹') +micrometers_per_square_millisecond = NamedUnit(1.0, Dimensions(length=1, time=-2), name='micrometers_per_square_millisecond', ascii_symbol='um/ms^2', symbol='µmms⁻²') +micrometers_per_microsecond = NamedUnit(1.0, Dimensions(length=1, time=-1), name='micrometers_per_microsecond', ascii_symbol='um/us', symbol='µmµs⁻¹') +micrometers_per_square_microsecond = NamedUnit(1000000.0, Dimensions(length=1, time=-2), name='micrometers_per_square_microsecond', ascii_symbol='um/us^2', symbol='µmµs⁻²') +micrometers_per_nanosecond = NamedUnit(999.9999999999999, Dimensions(length=1, time=-1), name='micrometers_per_nanosecond', ascii_symbol='um/ns', symbol='µmns⁻¹') +micrometers_per_square_nanosecond = NamedUnit(999999999999.9999, Dimensions(length=1, time=-2), name='micrometers_per_square_nanosecond', ascii_symbol='um/ns^2', symbol='µmns⁻²') +micrometers_per_picosecond = NamedUnit(1000000.0, Dimensions(length=1, time=-1), name='micrometers_per_picosecond', ascii_symbol='um/ps', symbol='µmps⁻¹') +micrometers_per_square_picosecond = NamedUnit(1e+18, Dimensions(length=1, time=-2), name='micrometers_per_square_picosecond', ascii_symbol='um/ps^2', symbol='µmps⁻²') +micrometers_per_femtosecond = NamedUnit(999999999.9999999, Dimensions(length=1, time=-1), name='micrometers_per_femtosecond', ascii_symbol='um/fs', symbol='µmfs⁻¹') +micrometers_per_square_femtosecond = NamedUnit(9.999999999999998e+23, Dimensions(length=1, time=-2), name='micrometers_per_square_femtosecond', ascii_symbol='um/fs^2', symbol='µmfs⁻²') +micrometers_per_attosecond = NamedUnit(999999999999.9999, Dimensions(length=1, time=-1), name='micrometers_per_attosecond', ascii_symbol='um/as', symbol='µmas⁻¹') +micrometers_per_square_attosecond = NamedUnit(9.999999999999999e+29, Dimensions(length=1, time=-2), name='micrometers_per_square_attosecond', ascii_symbol='um/as^2', symbol='µmas⁻²') +micrometers_per_minute = NamedUnit(1.6666666666666667e-08, Dimensions(length=1, time=-1), name='micrometers_per_minute', ascii_symbol='um/min', symbol='µmmin⁻¹') +micrometers_per_square_minute = NamedUnit(2.7777777777777777e-10, Dimensions(length=1, time=-2), name='micrometers_per_square_minute', ascii_symbol='um/min^2', symbol='µmmin⁻²') +micrometers_per_hour = NamedUnit(2.7777777777777776e-09, Dimensions(length=1, time=-1), name='micrometers_per_hour', ascii_symbol='um/h', symbol='µmh⁻¹') +micrometers_per_square_hour = NamedUnit(7.716049382716049e-12, Dimensions(length=1, time=-2), name='micrometers_per_square_hour', ascii_symbol='um/h^2', symbol='µmh⁻²') +micrometers_per_day = NamedUnit(1.1574074074074074e-10, Dimensions(length=1, time=-1), name='micrometers_per_day', ascii_symbol='um/d', symbol='µmd⁻¹') +micrometers_per_square_day = NamedUnit(1.3395919067215363e-14, Dimensions(length=1, time=-2), name='micrometers_per_square_day', ascii_symbol='um/d^2', symbol='µmd⁻²') +micrometers_per_year = NamedUnit(3.168873850681143e-13, Dimensions(length=1, time=-1), name='micrometers_per_year', ascii_symbol='um/y', symbol='µmy⁻¹') +micrometers_per_square_year = NamedUnit(1.0041761481530734e-19, Dimensions(length=1, time=-2), name='micrometers_per_square_year', ascii_symbol='um/y^2', symbol='µmy⁻²') +nanometers_per_second = NamedUnit(1e-09, Dimensions(length=1, time=-1), name='nanometers_per_second', ascii_symbol='nm/s', symbol='nms⁻¹') +nanometers_per_square_second = NamedUnit(1e-09, Dimensions(length=1, time=-2), name='nanometers_per_square_second', ascii_symbol='nm/s^2', symbol='nms⁻²') +nanometers_per_millisecond = NamedUnit(1e-06, Dimensions(length=1, time=-1), name='nanometers_per_millisecond', ascii_symbol='nm/ms', symbol='nmms⁻¹') +nanometers_per_square_millisecond = NamedUnit(0.001, Dimensions(length=1, time=-2), name='nanometers_per_square_millisecond', ascii_symbol='nm/ms^2', symbol='nmms⁻²') +nanometers_per_microsecond = NamedUnit(0.001, Dimensions(length=1, time=-1), name='nanometers_per_microsecond', ascii_symbol='nm/us', symbol='nmµs⁻¹') +nanometers_per_square_microsecond = NamedUnit(1000.0000000000001, Dimensions(length=1, time=-2), name='nanometers_per_square_microsecond', ascii_symbol='nm/us^2', symbol='nmµs⁻²') +nanometers_per_nanosecond = NamedUnit(1.0, Dimensions(length=1, time=-1), name='nanometers_per_nanosecond', ascii_symbol='nm/ns', symbol='nmns⁻¹') +nanometers_per_square_nanosecond = NamedUnit(1000000000.0, Dimensions(length=1, time=-2), name='nanometers_per_square_nanosecond', ascii_symbol='nm/ns^2', symbol='nmns⁻²') +nanometers_per_picosecond = NamedUnit(1000.0000000000001, Dimensions(length=1, time=-1), name='nanometers_per_picosecond', ascii_symbol='nm/ps', symbol='nmps⁻¹') +nanometers_per_square_picosecond = NamedUnit(1000000000000000.1, Dimensions(length=1, time=-2), name='nanometers_per_square_picosecond', ascii_symbol='nm/ps^2', symbol='nmps⁻²') +nanometers_per_femtosecond = NamedUnit(1000000.0, Dimensions(length=1, time=-1), name='nanometers_per_femtosecond', ascii_symbol='nm/fs', symbol='nmfs⁻¹') +nanometers_per_square_femtosecond = NamedUnit(1e+21, Dimensions(length=1, time=-2), name='nanometers_per_square_femtosecond', ascii_symbol='nm/fs^2', symbol='nmfs⁻²') +nanometers_per_attosecond = NamedUnit(1000000000.0, Dimensions(length=1, time=-1), name='nanometers_per_attosecond', ascii_symbol='nm/as', symbol='nmas⁻¹') +nanometers_per_square_attosecond = NamedUnit(1e+27, Dimensions(length=1, time=-2), name='nanometers_per_square_attosecond', ascii_symbol='nm/as^2', symbol='nmas⁻²') +nanometers_per_minute = NamedUnit(1.6666666666666667e-11, Dimensions(length=1, time=-1), name='nanometers_per_minute', ascii_symbol='nm/min', symbol='nmmin⁻¹') +nanometers_per_square_minute = NamedUnit(2.777777777777778e-13, Dimensions(length=1, time=-2), name='nanometers_per_square_minute', ascii_symbol='nm/min^2', symbol='nmmin⁻²') +nanometers_per_hour = NamedUnit(2.777777777777778e-12, Dimensions(length=1, time=-1), name='nanometers_per_hour', ascii_symbol='nm/h', symbol='nmh⁻¹') +nanometers_per_square_hour = NamedUnit(7.71604938271605e-15, Dimensions(length=1, time=-2), name='nanometers_per_square_hour', ascii_symbol='nm/h^2', symbol='nmh⁻²') +nanometers_per_day = NamedUnit(1.1574074074074076e-13, Dimensions(length=1, time=-1), name='nanometers_per_day', ascii_symbol='nm/d', symbol='nmd⁻¹') +nanometers_per_square_day = NamedUnit(1.3395919067215365e-17, Dimensions(length=1, time=-2), name='nanometers_per_square_day', ascii_symbol='nm/d^2', symbol='nmd⁻²') +nanometers_per_year = NamedUnit(3.1688738506811433e-16, Dimensions(length=1, time=-1), name='nanometers_per_year', ascii_symbol='nm/y', symbol='nmy⁻¹') +nanometers_per_square_year = NamedUnit(1.0041761481530736e-22, Dimensions(length=1, time=-2), name='nanometers_per_square_year', ascii_symbol='nm/y^2', symbol='nmy⁻²') +picometers_per_second = NamedUnit(1e-12, Dimensions(length=1, time=-1), name='picometers_per_second', ascii_symbol='pm/s', symbol='pms⁻¹') +picometers_per_square_second = NamedUnit(1e-12, Dimensions(length=1, time=-2), name='picometers_per_square_second', ascii_symbol='pm/s^2', symbol='pms⁻²') +picometers_per_millisecond = NamedUnit(1e-09, Dimensions(length=1, time=-1), name='picometers_per_millisecond', ascii_symbol='pm/ms', symbol='pmms⁻¹') +picometers_per_square_millisecond = NamedUnit(1e-06, Dimensions(length=1, time=-2), name='picometers_per_square_millisecond', ascii_symbol='pm/ms^2', symbol='pmms⁻²') +picometers_per_microsecond = NamedUnit(1e-06, Dimensions(length=1, time=-1), name='picometers_per_microsecond', ascii_symbol='pm/us', symbol='pmµs⁻¹') +picometers_per_square_microsecond = NamedUnit(1.0, Dimensions(length=1, time=-2), name='picometers_per_square_microsecond', ascii_symbol='pm/us^2', symbol='pmµs⁻²') +picometers_per_nanosecond = NamedUnit(0.001, Dimensions(length=1, time=-1), name='picometers_per_nanosecond', ascii_symbol='pm/ns', symbol='pmns⁻¹') +picometers_per_square_nanosecond = NamedUnit(999999.9999999999, Dimensions(length=1, time=-2), name='picometers_per_square_nanosecond', ascii_symbol='pm/ns^2', symbol='pmns⁻²') +picometers_per_picosecond = NamedUnit(1.0, Dimensions(length=1, time=-1), name='picometers_per_picosecond', ascii_symbol='pm/ps', symbol='pmps⁻¹') +picometers_per_square_picosecond = NamedUnit(1000000000000.0, Dimensions(length=1, time=-2), name='picometers_per_square_picosecond', ascii_symbol='pm/ps^2', symbol='pmps⁻²') +picometers_per_femtosecond = NamedUnit(999.9999999999999, Dimensions(length=1, time=-1), name='picometers_per_femtosecond', ascii_symbol='pm/fs', symbol='pmfs⁻¹') +picometers_per_square_femtosecond = NamedUnit(9.999999999999999e+17, Dimensions(length=1, time=-2), name='picometers_per_square_femtosecond', ascii_symbol='pm/fs^2', symbol='pmfs⁻²') +picometers_per_attosecond = NamedUnit(999999.9999999999, Dimensions(length=1, time=-1), name='picometers_per_attosecond', ascii_symbol='pm/as', symbol='pmas⁻¹') +picometers_per_square_attosecond = NamedUnit(9.999999999999998e+23, Dimensions(length=1, time=-2), name='picometers_per_square_attosecond', ascii_symbol='pm/as^2', symbol='pmas⁻²') +picometers_per_minute = NamedUnit(1.6666666666666667e-14, Dimensions(length=1, time=-1), name='picometers_per_minute', ascii_symbol='pm/min', symbol='pmmin⁻¹') +picometers_per_square_minute = NamedUnit(2.7777777777777775e-16, Dimensions(length=1, time=-2), name='picometers_per_square_minute', ascii_symbol='pm/min^2', symbol='pmmin⁻²') +picometers_per_hour = NamedUnit(2.7777777777777776e-15, Dimensions(length=1, time=-1), name='picometers_per_hour', ascii_symbol='pm/h', symbol='pmh⁻¹') +picometers_per_square_hour = NamedUnit(7.716049382716049e-18, Dimensions(length=1, time=-2), name='picometers_per_square_hour', ascii_symbol='pm/h^2', symbol='pmh⁻²') +picometers_per_day = NamedUnit(1.1574074074074073e-16, Dimensions(length=1, time=-1), name='picometers_per_day', ascii_symbol='pm/d', symbol='pmd⁻¹') +picometers_per_square_day = NamedUnit(1.3395919067215364e-20, Dimensions(length=1, time=-2), name='picometers_per_square_day', ascii_symbol='pm/d^2', symbol='pmd⁻²') +picometers_per_year = NamedUnit(3.168873850681143e-19, Dimensions(length=1, time=-1), name='picometers_per_year', ascii_symbol='pm/y', symbol='pmy⁻¹') +picometers_per_square_year = NamedUnit(1.0041761481530734e-25, Dimensions(length=1, time=-2), name='picometers_per_square_year', ascii_symbol='pm/y^2', symbol='pmy⁻²') +femtometers_per_second = NamedUnit(1e-15, Dimensions(length=1, time=-1), name='femtometers_per_second', ascii_symbol='fm/s', symbol='fms⁻¹') +femtometers_per_square_second = NamedUnit(1e-15, Dimensions(length=1, time=-2), name='femtometers_per_square_second', ascii_symbol='fm/s^2', symbol='fms⁻²') +femtometers_per_millisecond = NamedUnit(1e-12, Dimensions(length=1, time=-1), name='femtometers_per_millisecond', ascii_symbol='fm/ms', symbol='fmms⁻¹') +femtometers_per_square_millisecond = NamedUnit(1e-09, Dimensions(length=1, time=-2), name='femtometers_per_square_millisecond', ascii_symbol='fm/ms^2', symbol='fmms⁻²') +femtometers_per_microsecond = NamedUnit(1e-09, Dimensions(length=1, time=-1), name='femtometers_per_microsecond', ascii_symbol='fm/us', symbol='fmµs⁻¹') +femtometers_per_square_microsecond = NamedUnit(0.001, Dimensions(length=1, time=-2), name='femtometers_per_square_microsecond', ascii_symbol='fm/us^2', symbol='fmµs⁻²') +femtometers_per_nanosecond = NamedUnit(1e-06, Dimensions(length=1, time=-1), name='femtometers_per_nanosecond', ascii_symbol='fm/ns', symbol='fmns⁻¹') +femtometers_per_square_nanosecond = NamedUnit(1000.0, Dimensions(length=1, time=-2), name='femtometers_per_square_nanosecond', ascii_symbol='fm/ns^2', symbol='fmns⁻²') +femtometers_per_picosecond = NamedUnit(0.001, Dimensions(length=1, time=-1), name='femtometers_per_picosecond', ascii_symbol='fm/ps', symbol='fmps⁻¹') +femtometers_per_square_picosecond = NamedUnit(1000000000.0000001, Dimensions(length=1, time=-2), name='femtometers_per_square_picosecond', ascii_symbol='fm/ps^2', symbol='fmps⁻²') +femtometers_per_femtosecond = NamedUnit(1.0, Dimensions(length=1, time=-1), name='femtometers_per_femtosecond', ascii_symbol='fm/fs', symbol='fmfs⁻¹') +femtometers_per_square_femtosecond = NamedUnit(1000000000000000.0, Dimensions(length=1, time=-2), name='femtometers_per_square_femtosecond', ascii_symbol='fm/fs^2', symbol='fmfs⁻²') +femtometers_per_attosecond = NamedUnit(1000.0, Dimensions(length=1, time=-1), name='femtometers_per_attosecond', ascii_symbol='fm/as', symbol='fmas⁻¹') +femtometers_per_square_attosecond = NamedUnit(1e+21, Dimensions(length=1, time=-2), name='femtometers_per_square_attosecond', ascii_symbol='fm/as^2', symbol='fmas⁻²') +femtometers_per_minute = NamedUnit(1.6666666666666667e-17, Dimensions(length=1, time=-1), name='femtometers_per_minute', ascii_symbol='fm/min', symbol='fmmin⁻¹') +femtometers_per_square_minute = NamedUnit(2.777777777777778e-19, Dimensions(length=1, time=-2), name='femtometers_per_square_minute', ascii_symbol='fm/min^2', symbol='fmmin⁻²') +femtometers_per_hour = NamedUnit(2.777777777777778e-18, Dimensions(length=1, time=-1), name='femtometers_per_hour', ascii_symbol='fm/h', symbol='fmh⁻¹') +femtometers_per_square_hour = NamedUnit(7.71604938271605e-21, Dimensions(length=1, time=-2), name='femtometers_per_square_hour', ascii_symbol='fm/h^2', symbol='fmh⁻²') +femtometers_per_day = NamedUnit(1.1574074074074075e-19, Dimensions(length=1, time=-1), name='femtometers_per_day', ascii_symbol='fm/d', symbol='fmd⁻¹') +femtometers_per_square_day = NamedUnit(1.3395919067215363e-23, Dimensions(length=1, time=-2), name='femtometers_per_square_day', ascii_symbol='fm/d^2', symbol='fmd⁻²') +femtometers_per_year = NamedUnit(3.168873850681143e-22, Dimensions(length=1, time=-1), name='femtometers_per_year', ascii_symbol='fm/y', symbol='fmy⁻¹') +femtometers_per_square_year = NamedUnit(1.0041761481530735e-28, Dimensions(length=1, time=-2), name='femtometers_per_square_year', ascii_symbol='fm/y^2', symbol='fmy⁻²') +attometers_per_second = NamedUnit(1e-18, Dimensions(length=1, time=-1), name='attometers_per_second', ascii_symbol='am/s', symbol='ams⁻¹') +attometers_per_square_second = NamedUnit(1e-18, Dimensions(length=1, time=-2), name='attometers_per_square_second', ascii_symbol='am/s^2', symbol='ams⁻²') +attometers_per_millisecond = NamedUnit(1e-15, Dimensions(length=1, time=-1), name='attometers_per_millisecond', ascii_symbol='am/ms', symbol='amms⁻¹') +attometers_per_square_millisecond = NamedUnit(1.0000000000000002e-12, Dimensions(length=1, time=-2), name='attometers_per_square_millisecond', ascii_symbol='am/ms^2', symbol='amms⁻²') +attometers_per_microsecond = NamedUnit(1.0000000000000002e-12, Dimensions(length=1, time=-1), name='attometers_per_microsecond', ascii_symbol='am/us', symbol='amµs⁻¹') +attometers_per_square_microsecond = NamedUnit(1.0000000000000002e-06, Dimensions(length=1, time=-2), name='attometers_per_square_microsecond', ascii_symbol='am/us^2', symbol='amµs⁻²') +attometers_per_nanosecond = NamedUnit(1e-09, Dimensions(length=1, time=-1), name='attometers_per_nanosecond', ascii_symbol='am/ns', symbol='amns⁻¹') +attometers_per_square_nanosecond = NamedUnit(1.0, Dimensions(length=1, time=-2), name='attometers_per_square_nanosecond', ascii_symbol='am/ns^2', symbol='amns⁻²') +attometers_per_picosecond = NamedUnit(1.0000000000000002e-06, Dimensions(length=1, time=-1), name='attometers_per_picosecond', ascii_symbol='am/ps', symbol='amps⁻¹') +attometers_per_square_picosecond = NamedUnit(1000000.0000000001, Dimensions(length=1, time=-2), name='attometers_per_square_picosecond', ascii_symbol='am/ps^2', symbol='amps⁻²') +attometers_per_femtosecond = NamedUnit(0.001, Dimensions(length=1, time=-1), name='attometers_per_femtosecond', ascii_symbol='am/fs', symbol='amfs⁻¹') +attometers_per_square_femtosecond = NamedUnit(1000000000000.0, Dimensions(length=1, time=-2), name='attometers_per_square_femtosecond', ascii_symbol='am/fs^2', symbol='amfs⁻²') +attometers_per_attosecond = NamedUnit(1.0, Dimensions(length=1, time=-1), name='attometers_per_attosecond', ascii_symbol='am/as', symbol='amas⁻¹') +attometers_per_square_attosecond = NamedUnit(1e+18, Dimensions(length=1, time=-2), name='attometers_per_square_attosecond', ascii_symbol='am/as^2', symbol='amas⁻²') +attometers_per_minute = NamedUnit(1.6666666666666668e-20, Dimensions(length=1, time=-1), name='attometers_per_minute', ascii_symbol='am/min', symbol='ammin⁻¹') +attometers_per_square_minute = NamedUnit(2.777777777777778e-22, Dimensions(length=1, time=-2), name='attometers_per_square_minute', ascii_symbol='am/min^2', symbol='ammin⁻²') +attometers_per_hour = NamedUnit(2.7777777777777778e-21, Dimensions(length=1, time=-1), name='attometers_per_hour', ascii_symbol='am/h', symbol='amh⁻¹') +attometers_per_square_hour = NamedUnit(7.71604938271605e-24, Dimensions(length=1, time=-2), name='attometers_per_square_hour', ascii_symbol='am/h^2', symbol='amh⁻²') +attometers_per_day = NamedUnit(1.1574074074074074e-22, Dimensions(length=1, time=-1), name='attometers_per_day', ascii_symbol='am/d', symbol='amd⁻¹') +attometers_per_square_day = NamedUnit(1.3395919067215363e-26, Dimensions(length=1, time=-2), name='attometers_per_square_day', ascii_symbol='am/d^2', symbol='amd⁻²') +attometers_per_year = NamedUnit(3.1688738506811433e-25, Dimensions(length=1, time=-1), name='attometers_per_year', ascii_symbol='am/y', symbol='amy⁻¹') +attometers_per_square_year = NamedUnit(1.0041761481530734e-31, Dimensions(length=1, time=-2), name='attometers_per_square_year', ascii_symbol='am/y^2', symbol='amy⁻²') +decimeters_per_second = NamedUnit(0.1, Dimensions(length=1, time=-1), name='decimeters_per_second', ascii_symbol='dm/s', symbol='dms⁻¹') +decimeters_per_square_second = NamedUnit(0.1, Dimensions(length=1, time=-2), name='decimeters_per_square_second', ascii_symbol='dm/s^2', symbol='dms⁻²') +decimeters_per_millisecond = NamedUnit(100.0, Dimensions(length=1, time=-1), name='decimeters_per_millisecond', ascii_symbol='dm/ms', symbol='dmms⁻¹') +decimeters_per_square_millisecond = NamedUnit(100000.00000000001, Dimensions(length=1, time=-2), name='decimeters_per_square_millisecond', ascii_symbol='dm/ms^2', symbol='dmms⁻²') +decimeters_per_microsecond = NamedUnit(100000.00000000001, Dimensions(length=1, time=-1), name='decimeters_per_microsecond', ascii_symbol='dm/us', symbol='dmµs⁻¹') +decimeters_per_square_microsecond = NamedUnit(100000000000.0, Dimensions(length=1, time=-2), name='decimeters_per_square_microsecond', ascii_symbol='dm/us^2', symbol='dmµs⁻²') +decimeters_per_nanosecond = NamedUnit(100000000.0, Dimensions(length=1, time=-1), name='decimeters_per_nanosecond', ascii_symbol='dm/ns', symbol='dmns⁻¹') +decimeters_per_square_nanosecond = NamedUnit(1e+17, Dimensions(length=1, time=-2), name='decimeters_per_square_nanosecond', ascii_symbol='dm/ns^2', symbol='dmns⁻²') +decimeters_per_picosecond = NamedUnit(100000000000.0, Dimensions(length=1, time=-1), name='decimeters_per_picosecond', ascii_symbol='dm/ps', symbol='dmps⁻¹') +decimeters_per_square_picosecond = NamedUnit(1.0000000000000001e+23, Dimensions(length=1, time=-2), name='decimeters_per_square_picosecond', ascii_symbol='dm/ps^2', symbol='dmps⁻²') +decimeters_per_femtosecond = NamedUnit(100000000000000.0, Dimensions(length=1, time=-1), name='decimeters_per_femtosecond', ascii_symbol='dm/fs', symbol='dmfs⁻¹') +decimeters_per_square_femtosecond = NamedUnit(1e+29, Dimensions(length=1, time=-2), name='decimeters_per_square_femtosecond', ascii_symbol='dm/fs^2', symbol='dmfs⁻²') +decimeters_per_attosecond = NamedUnit(1e+17, Dimensions(length=1, time=-1), name='decimeters_per_attosecond', ascii_symbol='dm/as', symbol='dmas⁻¹') +decimeters_per_square_attosecond = NamedUnit(1e+35, Dimensions(length=1, time=-2), name='decimeters_per_square_attosecond', ascii_symbol='dm/as^2', symbol='dmas⁻²') +decimeters_per_minute = NamedUnit(0.0016666666666666668, Dimensions(length=1, time=-1), name='decimeters_per_minute', ascii_symbol='dm/min', symbol='dmmin⁻¹') +decimeters_per_square_minute = NamedUnit(2.777777777777778e-05, Dimensions(length=1, time=-2), name='decimeters_per_square_minute', ascii_symbol='dm/min^2', symbol='dmmin⁻²') +decimeters_per_hour = NamedUnit(0.0002777777777777778, Dimensions(length=1, time=-1), name='decimeters_per_hour', ascii_symbol='dm/h', symbol='dmh⁻¹') +decimeters_per_square_hour = NamedUnit(7.71604938271605e-07, Dimensions(length=1, time=-2), name='decimeters_per_square_hour', ascii_symbol='dm/h^2', symbol='dmh⁻²') +decimeters_per_day = NamedUnit(1.1574074074074075e-05, Dimensions(length=1, time=-1), name='decimeters_per_day', ascii_symbol='dm/d', symbol='dmd⁻¹') +decimeters_per_square_day = NamedUnit(1.3395919067215364e-09, Dimensions(length=1, time=-2), name='decimeters_per_square_day', ascii_symbol='dm/d^2', symbol='dmd⁻²') +decimeters_per_year = NamedUnit(3.168873850681143e-08, Dimensions(length=1, time=-1), name='decimeters_per_year', ascii_symbol='dm/y', symbol='dmy⁻¹') +decimeters_per_square_year = NamedUnit(1.0041761481530735e-14, Dimensions(length=1, time=-2), name='decimeters_per_square_year', ascii_symbol='dm/y^2', symbol='dmy⁻²') +centimeters_per_second = NamedUnit(0.01, Dimensions(length=1, time=-1), name='centimeters_per_second', ascii_symbol='cm/s', symbol='cms⁻¹') +centimeters_per_square_second = NamedUnit(0.01, Dimensions(length=1, time=-2), name='centimeters_per_square_second', ascii_symbol='cm/s^2', symbol='cms⁻²') +centimeters_per_millisecond = NamedUnit(10.0, Dimensions(length=1, time=-1), name='centimeters_per_millisecond', ascii_symbol='cm/ms', symbol='cmms⁻¹') +centimeters_per_square_millisecond = NamedUnit(10000.0, Dimensions(length=1, time=-2), name='centimeters_per_square_millisecond', ascii_symbol='cm/ms^2', symbol='cmms⁻²') +centimeters_per_microsecond = NamedUnit(10000.0, Dimensions(length=1, time=-1), name='centimeters_per_microsecond', ascii_symbol='cm/us', symbol='cmµs⁻¹') +centimeters_per_square_microsecond = NamedUnit(10000000000.0, Dimensions(length=1, time=-2), name='centimeters_per_square_microsecond', ascii_symbol='cm/us^2', symbol='cmµs⁻²') +centimeters_per_nanosecond = NamedUnit(10000000.0, Dimensions(length=1, time=-1), name='centimeters_per_nanosecond', ascii_symbol='cm/ns', symbol='cmns⁻¹') +centimeters_per_square_nanosecond = NamedUnit(1e+16, Dimensions(length=1, time=-2), name='centimeters_per_square_nanosecond', ascii_symbol='cm/ns^2', symbol='cmns⁻²') +centimeters_per_picosecond = NamedUnit(10000000000.0, Dimensions(length=1, time=-1), name='centimeters_per_picosecond', ascii_symbol='cm/ps', symbol='cmps⁻¹') +centimeters_per_square_picosecond = NamedUnit(1e+22, Dimensions(length=1, time=-2), name='centimeters_per_square_picosecond', ascii_symbol='cm/ps^2', symbol='cmps⁻²') +centimeters_per_femtosecond = NamedUnit(10000000000000.0, Dimensions(length=1, time=-1), name='centimeters_per_femtosecond', ascii_symbol='cm/fs', symbol='cmfs⁻¹') +centimeters_per_square_femtosecond = NamedUnit(1e+28, Dimensions(length=1, time=-2), name='centimeters_per_square_femtosecond', ascii_symbol='cm/fs^2', symbol='cmfs⁻²') +centimeters_per_attosecond = NamedUnit(1e+16, Dimensions(length=1, time=-1), name='centimeters_per_attosecond', ascii_symbol='cm/as', symbol='cmas⁻¹') +centimeters_per_square_attosecond = NamedUnit(1e+34, Dimensions(length=1, time=-2), name='centimeters_per_square_attosecond', ascii_symbol='cm/as^2', symbol='cmas⁻²') +centimeters_per_minute = NamedUnit(0.00016666666666666666, Dimensions(length=1, time=-1), name='centimeters_per_minute', ascii_symbol='cm/min', symbol='cmmin⁻¹') +centimeters_per_square_minute = NamedUnit(2.777777777777778e-06, Dimensions(length=1, time=-2), name='centimeters_per_square_minute', ascii_symbol='cm/min^2', symbol='cmmin⁻²') +centimeters_per_hour = NamedUnit(2.777777777777778e-05, Dimensions(length=1, time=-1), name='centimeters_per_hour', ascii_symbol='cm/h', symbol='cmh⁻¹') +centimeters_per_square_hour = NamedUnit(7.71604938271605e-08, Dimensions(length=1, time=-2), name='centimeters_per_square_hour', ascii_symbol='cm/h^2', symbol='cmh⁻²') +centimeters_per_day = NamedUnit(1.1574074074074074e-06, Dimensions(length=1, time=-1), name='centimeters_per_day', ascii_symbol='cm/d', symbol='cmd⁻¹') +centimeters_per_square_day = NamedUnit(1.3395919067215363e-10, Dimensions(length=1, time=-2), name='centimeters_per_square_day', ascii_symbol='cm/d^2', symbol='cmd⁻²') +centimeters_per_year = NamedUnit(3.168873850681143e-09, Dimensions(length=1, time=-1), name='centimeters_per_year', ascii_symbol='cm/y', symbol='cmy⁻¹') +centimeters_per_square_year = NamedUnit(1.0041761481530735e-15, Dimensions(length=1, time=-2), name='centimeters_per_square_year', ascii_symbol='cm/y^2', symbol='cmy⁻²') +angstroms_per_second = NamedUnit(1e-10, Dimensions(length=1, time=-1), name='angstroms_per_second', ascii_symbol='Ang/s', symbol='Ås⁻¹') +angstroms_per_square_second = NamedUnit(1e-10, Dimensions(length=1, time=-2), name='angstroms_per_square_second', ascii_symbol='Ang/s^2', symbol='Ås⁻²') +angstroms_per_millisecond = NamedUnit(1e-07, Dimensions(length=1, time=-1), name='angstroms_per_millisecond', ascii_symbol='Ang/ms', symbol='Åms⁻¹') +angstroms_per_square_millisecond = NamedUnit(0.0001, Dimensions(length=1, time=-2), name='angstroms_per_square_millisecond', ascii_symbol='Ang/ms^2', symbol='Åms⁻²') +angstroms_per_microsecond = NamedUnit(0.0001, Dimensions(length=1, time=-1), name='angstroms_per_microsecond', ascii_symbol='Ang/us', symbol='ŵs⁻¹') +angstroms_per_square_microsecond = NamedUnit(100.0, Dimensions(length=1, time=-2), name='angstroms_per_square_microsecond', ascii_symbol='Ang/us^2', symbol='ŵs⁻²') +angstroms_per_nanosecond = NamedUnit(0.09999999999999999, Dimensions(length=1, time=-1), name='angstroms_per_nanosecond', ascii_symbol='Ang/ns', symbol='Åns⁻¹') +angstroms_per_square_nanosecond = NamedUnit(100000000.0, Dimensions(length=1, time=-2), name='angstroms_per_square_nanosecond', ascii_symbol='Ang/ns^2', symbol='Åns⁻²') +angstroms_per_picosecond = NamedUnit(100.0, Dimensions(length=1, time=-1), name='angstroms_per_picosecond', ascii_symbol='Ang/ps', symbol='Åps⁻¹') +angstroms_per_square_picosecond = NamedUnit(100000000000000.02, Dimensions(length=1, time=-2), name='angstroms_per_square_picosecond', ascii_symbol='Ang/ps^2', symbol='Åps⁻²') +angstroms_per_femtosecond = NamedUnit(100000.0, Dimensions(length=1, time=-1), name='angstroms_per_femtosecond', ascii_symbol='Ang/fs', symbol='Åfs⁻¹') +angstroms_per_square_femtosecond = NamedUnit(1e+20, Dimensions(length=1, time=-2), name='angstroms_per_square_femtosecond', ascii_symbol='Ang/fs^2', symbol='Åfs⁻²') +angstroms_per_attosecond = NamedUnit(100000000.0, Dimensions(length=1, time=-1), name='angstroms_per_attosecond', ascii_symbol='Ang/as', symbol='Åas⁻¹') +angstroms_per_square_attosecond = NamedUnit(9.999999999999999e+25, Dimensions(length=1, time=-2), name='angstroms_per_square_attosecond', ascii_symbol='Ang/as^2', symbol='Åas⁻²') +angstroms_per_minute = NamedUnit(1.6666666666666668e-12, Dimensions(length=1, time=-1), name='angstroms_per_minute', ascii_symbol='Ang/min', symbol='Åmin⁻¹') +angstroms_per_square_minute = NamedUnit(2.7777777777777778e-14, Dimensions(length=1, time=-2), name='angstroms_per_square_minute', ascii_symbol='Ang/min^2', symbol='Åmin⁻²') +angstroms_per_hour = NamedUnit(2.777777777777778e-13, Dimensions(length=1, time=-1), name='angstroms_per_hour', ascii_symbol='Ang/h', symbol='Åh⁻¹') +angstroms_per_square_hour = NamedUnit(7.716049382716049e-16, Dimensions(length=1, time=-2), name='angstroms_per_square_hour', ascii_symbol='Ang/h^2', symbol='Åh⁻²') +angstroms_per_day = NamedUnit(1.1574074074074074e-14, Dimensions(length=1, time=-1), name='angstroms_per_day', ascii_symbol='Ang/d', symbol='Åd⁻¹') +angstroms_per_square_day = NamedUnit(1.3395919067215363e-18, Dimensions(length=1, time=-2), name='angstroms_per_square_day', ascii_symbol='Ang/d^2', symbol='Åd⁻²') +angstroms_per_year = NamedUnit(3.168873850681143e-17, Dimensions(length=1, time=-1), name='angstroms_per_year', ascii_symbol='Ang/y', symbol='Åy⁻¹') +angstroms_per_square_year = NamedUnit(1.0041761481530734e-23, Dimensions(length=1, time=-2), name='angstroms_per_square_year', ascii_symbol='Ang/y^2', symbol='Åy⁻²') +microns_per_second = NamedUnit(1e-06, Dimensions(length=1, time=-1), name='microns_per_second', ascii_symbol='micron/s', symbol='microns⁻¹') +microns_per_square_second = NamedUnit(1e-06, Dimensions(length=1, time=-2), name='microns_per_square_second', ascii_symbol='micron/s^2', symbol='microns⁻²') +microns_per_millisecond = NamedUnit(0.001, Dimensions(length=1, time=-1), name='microns_per_millisecond', ascii_symbol='micron/ms', symbol='micronms⁻¹') +microns_per_square_millisecond = NamedUnit(1.0, Dimensions(length=1, time=-2), name='microns_per_square_millisecond', ascii_symbol='micron/ms^2', symbol='micronms⁻²') +microns_per_microsecond = NamedUnit(1.0, Dimensions(length=1, time=-1), name='microns_per_microsecond', ascii_symbol='micron/us', symbol='micronµs⁻¹') +microns_per_square_microsecond = NamedUnit(1000000.0, Dimensions(length=1, time=-2), name='microns_per_square_microsecond', ascii_symbol='micron/us^2', symbol='micronµs⁻²') +microns_per_nanosecond = NamedUnit(999.9999999999999, Dimensions(length=1, time=-1), name='microns_per_nanosecond', ascii_symbol='micron/ns', symbol='micronns⁻¹') +microns_per_square_nanosecond = NamedUnit(999999999999.9999, Dimensions(length=1, time=-2), name='microns_per_square_nanosecond', ascii_symbol='micron/ns^2', symbol='micronns⁻²') +microns_per_picosecond = NamedUnit(1000000.0, Dimensions(length=1, time=-1), name='microns_per_picosecond', ascii_symbol='micron/ps', symbol='micronps⁻¹') +microns_per_square_picosecond = NamedUnit(1e+18, Dimensions(length=1, time=-2), name='microns_per_square_picosecond', ascii_symbol='micron/ps^2', symbol='micronps⁻²') +microns_per_femtosecond = NamedUnit(999999999.9999999, Dimensions(length=1, time=-1), name='microns_per_femtosecond', ascii_symbol='micron/fs', symbol='micronfs⁻¹') +microns_per_square_femtosecond = NamedUnit(9.999999999999998e+23, Dimensions(length=1, time=-2), name='microns_per_square_femtosecond', ascii_symbol='micron/fs^2', symbol='micronfs⁻²') +microns_per_attosecond = NamedUnit(999999999999.9999, Dimensions(length=1, time=-1), name='microns_per_attosecond', ascii_symbol='micron/as', symbol='micronas⁻¹') +microns_per_square_attosecond = NamedUnit(9.999999999999999e+29, Dimensions(length=1, time=-2), name='microns_per_square_attosecond', ascii_symbol='micron/as^2', symbol='micronas⁻²') +microns_per_minute = NamedUnit(1.6666666666666667e-08, Dimensions(length=1, time=-1), name='microns_per_minute', ascii_symbol='micron/min', symbol='micronmin⁻¹') +microns_per_square_minute = NamedUnit(2.7777777777777777e-10, Dimensions(length=1, time=-2), name='microns_per_square_minute', ascii_symbol='micron/min^2', symbol='micronmin⁻²') +microns_per_hour = NamedUnit(2.7777777777777776e-09, Dimensions(length=1, time=-1), name='microns_per_hour', ascii_symbol='micron/h', symbol='micronh⁻¹') +microns_per_square_hour = NamedUnit(7.716049382716049e-12, Dimensions(length=1, time=-2), name='microns_per_square_hour', ascii_symbol='micron/h^2', symbol='micronh⁻²') +microns_per_day = NamedUnit(1.1574074074074074e-10, Dimensions(length=1, time=-1), name='microns_per_day', ascii_symbol='micron/d', symbol='micrond⁻¹') +microns_per_square_day = NamedUnit(1.3395919067215363e-14, Dimensions(length=1, time=-2), name='microns_per_square_day', ascii_symbol='micron/d^2', symbol='micrond⁻²') +microns_per_year = NamedUnit(3.168873850681143e-13, Dimensions(length=1, time=-1), name='microns_per_year', ascii_symbol='micron/y', symbol='microny⁻¹') +microns_per_square_year = NamedUnit(1.0041761481530734e-19, Dimensions(length=1, time=-2), name='microns_per_square_year', ascii_symbol='micron/y^2', symbol='microny⁻²') +miles_per_second = NamedUnit(1609.344, Dimensions(length=1, time=-1), name='miles_per_second', ascii_symbol='miles/s', symbol='miless⁻¹') +miles_per_square_second = NamedUnit(1609.344, Dimensions(length=1, time=-2), name='miles_per_square_second', ascii_symbol='miles/s^2', symbol='miless⁻²') +miles_per_millisecond = NamedUnit(1609344.0, Dimensions(length=1, time=-1), name='miles_per_millisecond', ascii_symbol='miles/ms', symbol='milesms⁻¹') +miles_per_square_millisecond = NamedUnit(1609344000.0000002, Dimensions(length=1, time=-2), name='miles_per_square_millisecond', ascii_symbol='miles/ms^2', symbol='milesms⁻²') +miles_per_microsecond = NamedUnit(1609344000.0000002, Dimensions(length=1, time=-1), name='miles_per_microsecond', ascii_symbol='miles/us', symbol='milesµs⁻¹') +miles_per_square_microsecond = NamedUnit(1609344000000000.0, Dimensions(length=1, time=-2), name='miles_per_square_microsecond', ascii_symbol='miles/us^2', symbol='milesµs⁻²') +miles_per_nanosecond = NamedUnit(1609344000000.0, Dimensions(length=1, time=-1), name='miles_per_nanosecond', ascii_symbol='miles/ns', symbol='milesns⁻¹') +miles_per_square_nanosecond = NamedUnit(1.609344e+21, Dimensions(length=1, time=-2), name='miles_per_square_nanosecond', ascii_symbol='miles/ns^2', symbol='milesns⁻²') +miles_per_picosecond = NamedUnit(1609344000000000.0, Dimensions(length=1, time=-1), name='miles_per_picosecond', ascii_symbol='miles/ps', symbol='milesps⁻¹') +miles_per_square_picosecond = NamedUnit(1.609344e+27, Dimensions(length=1, time=-2), name='miles_per_square_picosecond', ascii_symbol='miles/ps^2', symbol='milesps⁻²') +miles_per_femtosecond = NamedUnit(1.609344e+18, Dimensions(length=1, time=-1), name='miles_per_femtosecond', ascii_symbol='miles/fs', symbol='milesfs⁻¹') +miles_per_square_femtosecond = NamedUnit(1.609344e+33, Dimensions(length=1, time=-2), name='miles_per_square_femtosecond', ascii_symbol='miles/fs^2', symbol='milesfs⁻²') +miles_per_attosecond = NamedUnit(1.609344e+21, Dimensions(length=1, time=-1), name='miles_per_attosecond', ascii_symbol='miles/as', symbol='milesas⁻¹') +miles_per_square_attosecond = NamedUnit(1.609344e+39, Dimensions(length=1, time=-2), name='miles_per_square_attosecond', ascii_symbol='miles/as^2', symbol='milesas⁻²') +miles_per_minute = NamedUnit(26.822400000000002, Dimensions(length=1, time=-1), name='miles_per_minute', ascii_symbol='miles/min', symbol='milesmin⁻¹') +miles_per_square_minute = NamedUnit(0.44704, Dimensions(length=1, time=-2), name='miles_per_square_minute', ascii_symbol='miles/min^2', symbol='milesmin⁻²') +miles_per_hour = NamedUnit(4.4704, Dimensions(length=1, time=-1), name='miles_per_hour', ascii_symbol='miles/h', symbol='milesh⁻¹') +miles_per_square_hour = NamedUnit(0.012417777777777778, Dimensions(length=1, time=-2), name='miles_per_square_hour', ascii_symbol='miles/h^2', symbol='milesh⁻²') +miles_per_day = NamedUnit(0.18626666666666666, Dimensions(length=1, time=-1), name='miles_per_day', ascii_symbol='miles/d', symbol='milesd⁻¹') +miles_per_square_day = NamedUnit(2.1558641975308643e-05, Dimensions(length=1, time=-2), name='miles_per_square_day', ascii_symbol='miles/d^2', symbol='milesd⁻²') +miles_per_year = NamedUnit(0.0005099808118350593, Dimensions(length=1, time=-1), name='miles_per_year', ascii_symbol='miles/y', symbol='milesy⁻¹') +miles_per_square_year = NamedUnit(1.61606485897326e-10, Dimensions(length=1, time=-2), name='miles_per_square_year', ascii_symbol='miles/y^2', symbol='milesy⁻²') +yards_per_second = NamedUnit(0.9144000000000001, Dimensions(length=1, time=-1), name='yards_per_second', ascii_symbol='yrd/s', symbol='yrds⁻¹') +yards_per_square_second = NamedUnit(0.9144000000000001, Dimensions(length=1, time=-2), name='yards_per_square_second', ascii_symbol='yrd/s^2', symbol='yrds⁻²') +yards_per_millisecond = NamedUnit(914.4000000000001, Dimensions(length=1, time=-1), name='yards_per_millisecond', ascii_symbol='yrd/ms', symbol='yrdms⁻¹') +yards_per_square_millisecond = NamedUnit(914400.0000000001, Dimensions(length=1, time=-2), name='yards_per_square_millisecond', ascii_symbol='yrd/ms^2', symbol='yrdms⁻²') +yards_per_microsecond = NamedUnit(914400.0000000001, Dimensions(length=1, time=-1), name='yards_per_microsecond', ascii_symbol='yrd/us', symbol='yrdµs⁻¹') +yards_per_square_microsecond = NamedUnit(914400000000.0001, Dimensions(length=1, time=-2), name='yards_per_square_microsecond', ascii_symbol='yrd/us^2', symbol='yrdµs⁻²') +yards_per_nanosecond = NamedUnit(914400000.0, Dimensions(length=1, time=-1), name='yards_per_nanosecond', ascii_symbol='yrd/ns', symbol='yrdns⁻¹') +yards_per_square_nanosecond = NamedUnit(9.144e+17, Dimensions(length=1, time=-2), name='yards_per_square_nanosecond', ascii_symbol='yrd/ns^2', symbol='yrdns⁻²') +yards_per_picosecond = NamedUnit(914400000000.0001, Dimensions(length=1, time=-1), name='yards_per_picosecond', ascii_symbol='yrd/ps', symbol='yrdps⁻¹') +yards_per_square_picosecond = NamedUnit(9.144000000000002e+23, Dimensions(length=1, time=-2), name='yards_per_square_picosecond', ascii_symbol='yrd/ps^2', symbol='yrdps⁻²') +yards_per_femtosecond = NamedUnit(914400000000000.0, Dimensions(length=1, time=-1), name='yards_per_femtosecond', ascii_symbol='yrd/fs', symbol='yrdfs⁻¹') +yards_per_square_femtosecond = NamedUnit(9.144e+29, Dimensions(length=1, time=-2), name='yards_per_square_femtosecond', ascii_symbol='yrd/fs^2', symbol='yrdfs⁻²') +yards_per_attosecond = NamedUnit(9.144e+17, Dimensions(length=1, time=-1), name='yards_per_attosecond', ascii_symbol='yrd/as', symbol='yrdas⁻¹') +yards_per_square_attosecond = NamedUnit(9.144e+35, Dimensions(length=1, time=-2), name='yards_per_square_attosecond', ascii_symbol='yrd/as^2', symbol='yrdas⁻²') +yards_per_minute = NamedUnit(0.015240000000000002, Dimensions(length=1, time=-1), name='yards_per_minute', ascii_symbol='yrd/min', symbol='yrdmin⁻¹') +yards_per_square_minute = NamedUnit(0.00025400000000000005, Dimensions(length=1, time=-2), name='yards_per_square_minute', ascii_symbol='yrd/min^2', symbol='yrdmin⁻²') +yards_per_hour = NamedUnit(0.00254, Dimensions(length=1, time=-1), name='yards_per_hour', ascii_symbol='yrd/h', symbol='yrdh⁻¹') +yards_per_square_hour = NamedUnit(7.055555555555557e-06, Dimensions(length=1, time=-2), name='yards_per_square_hour', ascii_symbol='yrd/h^2', symbol='yrdh⁻²') +yards_per_day = NamedUnit(0.00010583333333333335, Dimensions(length=1, time=-1), name='yards_per_day', ascii_symbol='yrd/d', symbol='yrdd⁻¹') +yards_per_square_day = NamedUnit(1.224922839506173e-08, Dimensions(length=1, time=-2), name='yards_per_square_day', ascii_symbol='yrd/d^2', symbol='yrdd⁻²') +yards_per_year = NamedUnit(2.897618249062837e-07, Dimensions(length=1, time=-1), name='yards_per_year', ascii_symbol='yrd/y', symbol='yrdy⁻¹') +yards_per_square_year = NamedUnit(9.182186698711705e-14, Dimensions(length=1, time=-2), name='yards_per_square_year', ascii_symbol='yrd/y^2', symbol='yrdy⁻²') +feet_per_second = NamedUnit(0.3048, Dimensions(length=1, time=-1), name='feet_per_second', ascii_symbol='ft/s', symbol='fts⁻¹') +feet_per_square_second = NamedUnit(0.3048, Dimensions(length=1, time=-2), name='feet_per_square_second', ascii_symbol='ft/s^2', symbol='fts⁻²') +feet_per_millisecond = NamedUnit(304.8, Dimensions(length=1, time=-1), name='feet_per_millisecond', ascii_symbol='ft/ms', symbol='ftms⁻¹') +feet_per_square_millisecond = NamedUnit(304800.00000000006, Dimensions(length=1, time=-2), name='feet_per_square_millisecond', ascii_symbol='ft/ms^2', symbol='ftms⁻²') +feet_per_microsecond = NamedUnit(304800.00000000006, Dimensions(length=1, time=-1), name='feet_per_microsecond', ascii_symbol='ft/us', symbol='ftµs⁻¹') +feet_per_square_microsecond = NamedUnit(304800000000.0, Dimensions(length=1, time=-2), name='feet_per_square_microsecond', ascii_symbol='ft/us^2', symbol='ftµs⁻²') +feet_per_nanosecond = NamedUnit(304800000.0, Dimensions(length=1, time=-1), name='feet_per_nanosecond', ascii_symbol='ft/ns', symbol='ftns⁻¹') +feet_per_square_nanosecond = NamedUnit(3.048e+17, Dimensions(length=1, time=-2), name='feet_per_square_nanosecond', ascii_symbol='ft/ns^2', symbol='ftns⁻²') +feet_per_picosecond = NamedUnit(304800000000.0, Dimensions(length=1, time=-1), name='feet_per_picosecond', ascii_symbol='ft/ps', symbol='ftps⁻¹') +feet_per_square_picosecond = NamedUnit(3.048e+23, Dimensions(length=1, time=-2), name='feet_per_square_picosecond', ascii_symbol='ft/ps^2', symbol='ftps⁻²') +feet_per_femtosecond = NamedUnit(304800000000000.0, Dimensions(length=1, time=-1), name='feet_per_femtosecond', ascii_symbol='ft/fs', symbol='ftfs⁻¹') +feet_per_square_femtosecond = NamedUnit(3.048e+29, Dimensions(length=1, time=-2), name='feet_per_square_femtosecond', ascii_symbol='ft/fs^2', symbol='ftfs⁻²') +feet_per_attosecond = NamedUnit(3.048e+17, Dimensions(length=1, time=-1), name='feet_per_attosecond', ascii_symbol='ft/as', symbol='ftas⁻¹') +feet_per_square_attosecond = NamedUnit(3.0479999999999997e+35, Dimensions(length=1, time=-2), name='feet_per_square_attosecond', ascii_symbol='ft/as^2', symbol='ftas⁻²') +feet_per_minute = NamedUnit(0.00508, Dimensions(length=1, time=-1), name='feet_per_minute', ascii_symbol='ft/min', symbol='ftmin⁻¹') +feet_per_square_minute = NamedUnit(8.466666666666667e-05, Dimensions(length=1, time=-2), name='feet_per_square_minute', ascii_symbol='ft/min^2', symbol='ftmin⁻²') +feet_per_hour = NamedUnit(0.0008466666666666667, Dimensions(length=1, time=-1), name='feet_per_hour', ascii_symbol='ft/h', symbol='fth⁻¹') +feet_per_square_hour = NamedUnit(2.351851851851852e-06, Dimensions(length=1, time=-2), name='feet_per_square_hour', ascii_symbol='ft/h^2', symbol='fth⁻²') +feet_per_day = NamedUnit(3.527777777777778e-05, Dimensions(length=1, time=-1), name='feet_per_day', ascii_symbol='ft/d', symbol='ftd⁻¹') +feet_per_square_day = NamedUnit(4.083076131687243e-09, Dimensions(length=1, time=-2), name='feet_per_square_day', ascii_symbol='ft/d^2', symbol='ftd⁻²') +feet_per_year = NamedUnit(9.658727496876123e-08, Dimensions(length=1, time=-1), name='feet_per_year', ascii_symbol='ft/y', symbol='fty⁻¹') +feet_per_square_year = NamedUnit(3.060728899570568e-14, Dimensions(length=1, time=-2), name='feet_per_square_year', ascii_symbol='ft/y^2', symbol='fty⁻²') +inches_per_second = NamedUnit(0.0254, Dimensions(length=1, time=-1), name='inches_per_second', ascii_symbol='in/s', symbol='ins⁻¹') +inches_per_square_second = NamedUnit(0.0254, Dimensions(length=1, time=-2), name='inches_per_square_second', ascii_symbol='in/s^2', symbol='ins⁻²') +inches_per_millisecond = NamedUnit(25.4, Dimensions(length=1, time=-1), name='inches_per_millisecond', ascii_symbol='in/ms', symbol='inms⁻¹') +inches_per_square_millisecond = NamedUnit(25400.0, Dimensions(length=1, time=-2), name='inches_per_square_millisecond', ascii_symbol='in/ms^2', symbol='inms⁻²') +inches_per_microsecond = NamedUnit(25400.0, Dimensions(length=1, time=-1), name='inches_per_microsecond', ascii_symbol='in/us', symbol='inµs⁻¹') +inches_per_square_microsecond = NamedUnit(25400000000.0, Dimensions(length=1, time=-2), name='inches_per_square_microsecond', ascii_symbol='in/us^2', symbol='inµs⁻²') +inches_per_nanosecond = NamedUnit(25399999.999999996, Dimensions(length=1, time=-1), name='inches_per_nanosecond', ascii_symbol='in/ns', symbol='inns⁻¹') +inches_per_square_nanosecond = NamedUnit(2.5399999999999996e+16, Dimensions(length=1, time=-2), name='inches_per_square_nanosecond', ascii_symbol='in/ns^2', symbol='inns⁻²') +inches_per_picosecond = NamedUnit(25400000000.0, Dimensions(length=1, time=-1), name='inches_per_picosecond', ascii_symbol='in/ps', symbol='inps⁻¹') +inches_per_square_picosecond = NamedUnit(2.54e+22, Dimensions(length=1, time=-2), name='inches_per_square_picosecond', ascii_symbol='in/ps^2', symbol='inps⁻²') +inches_per_femtosecond = NamedUnit(25399999999999.996, Dimensions(length=1, time=-1), name='inches_per_femtosecond', ascii_symbol='in/fs', symbol='infs⁻¹') +inches_per_square_femtosecond = NamedUnit(2.54e+28, Dimensions(length=1, time=-2), name='inches_per_square_femtosecond', ascii_symbol='in/fs^2', symbol='infs⁻²') +inches_per_attosecond = NamedUnit(2.5399999999999996e+16, Dimensions(length=1, time=-1), name='inches_per_attosecond', ascii_symbol='in/as', symbol='inas⁻¹') +inches_per_square_attosecond = NamedUnit(2.5399999999999998e+34, Dimensions(length=1, time=-2), name='inches_per_square_attosecond', ascii_symbol='in/as^2', symbol='inas⁻²') +inches_per_minute = NamedUnit(0.00042333333333333334, Dimensions(length=1, time=-1), name='inches_per_minute', ascii_symbol='in/min', symbol='inmin⁻¹') +inches_per_square_minute = NamedUnit(7.055555555555555e-06, Dimensions(length=1, time=-2), name='inches_per_square_minute', ascii_symbol='in/min^2', symbol='inmin⁻²') +inches_per_hour = NamedUnit(7.055555555555556e-05, Dimensions(length=1, time=-1), name='inches_per_hour', ascii_symbol='in/h', symbol='inh⁻¹') +inches_per_square_hour = NamedUnit(1.9598765432098765e-07, Dimensions(length=1, time=-2), name='inches_per_square_hour', ascii_symbol='in/h^2', symbol='inh⁻²') +inches_per_day = NamedUnit(2.9398148148148147e-06, Dimensions(length=1, time=-1), name='inches_per_day', ascii_symbol='in/d', symbol='ind⁻¹') +inches_per_square_day = NamedUnit(3.4025634430727023e-10, Dimensions(length=1, time=-2), name='inches_per_square_day', ascii_symbol='in/d^2', symbol='ind⁻²') +inches_per_year = NamedUnit(8.048939580730103e-09, Dimensions(length=1, time=-1), name='inches_per_year', ascii_symbol='in/y', symbol='iny⁻¹') +inches_per_square_year = NamedUnit(2.5506074163088065e-15, Dimensions(length=1, time=-2), name='inches_per_square_year', ascii_symbol='in/y^2', symbol='iny⁻²') +grams_per_cubic_meter = NamedUnit(0.001, Dimensions(length=-3, mass=1), name='grams_per_cubic_meter', ascii_symbol='g m^-3', symbol='gm⁻³') +exagrams_per_cubic_meter = NamedUnit(1000000000000000.0, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_meter', ascii_symbol='Eg m^-3', symbol='Egm⁻³') +petagrams_per_cubic_meter = NamedUnit(1000000000000.0, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_meter', ascii_symbol='Pg m^-3', symbol='Pgm⁻³') +teragrams_per_cubic_meter = NamedUnit(1000000000.0, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_meter', ascii_symbol='Tg m^-3', symbol='Tgm⁻³') +gigagrams_per_cubic_meter = NamedUnit(1000000.0, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_meter', ascii_symbol='Gg m^-3', symbol='Ggm⁻³') +megagrams_per_cubic_meter = NamedUnit(1000.0, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_meter', ascii_symbol='Mg m^-3', symbol='Mgm⁻³') +kilograms_per_cubic_meter = NamedUnit(1.0, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_meter', ascii_symbol='kg m^-3', symbol='kgm⁻³') +milligrams_per_cubic_meter = NamedUnit(1e-06, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_meter', ascii_symbol='mg m^-3', symbol='mgm⁻³') +micrograms_per_cubic_meter = NamedUnit(1e-09, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_meter', ascii_symbol='ug m^-3', symbol='µgm⁻³') +nanograms_per_cubic_meter = NamedUnit(1.0000000000000002e-12, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_meter', ascii_symbol='ng m^-3', symbol='ngm⁻³') +picograms_per_cubic_meter = NamedUnit(1e-15, Dimensions(length=-3, mass=1), name='picograms_per_cubic_meter', ascii_symbol='pg m^-3', symbol='pgm⁻³') +femtograms_per_cubic_meter = NamedUnit(1e-18, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_meter', ascii_symbol='fg m^-3', symbol='fgm⁻³') +attograms_per_cubic_meter = NamedUnit(1.0000000000000001e-21, Dimensions(length=-3, mass=1), name='attograms_per_cubic_meter', ascii_symbol='ag m^-3', symbol='agm⁻³') +atomic_mass_units_per_cubic_meter = NamedUnit(1.660538921e-27, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_meter', ascii_symbol='au m^-3', symbol='aum⁻³') +pounds_per_cubic_meter = NamedUnit(0.45359237, Dimensions(length=-3, mass=1), name='pounds_per_cubic_meter', ascii_symbol='lb m^-3', symbol='lbm⁻³') +ounces_per_cubic_meter = NamedUnit(0.028349523125, Dimensions(length=-3, mass=1), name='ounces_per_cubic_meter', ascii_symbol='oz m^-3', symbol='ozm⁻³') +grams_per_cubic_exameter = NamedUnit(1e-57, Dimensions(length=-3, mass=1), name='grams_per_cubic_exameter', ascii_symbol='g Em^-3', symbol='gEm⁻³') +exagrams_per_cubic_exameter = NamedUnit(1e-39, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_exameter', ascii_symbol='Eg Em^-3', symbol='EgEm⁻³') +petagrams_per_cubic_exameter = NamedUnit(9.999999999999999e-43, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_exameter', ascii_symbol='Pg Em^-3', symbol='PgEm⁻³') +teragrams_per_cubic_exameter = NamedUnit(1e-45, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_exameter', ascii_symbol='Tg Em^-3', symbol='TgEm⁻³') +gigagrams_per_cubic_exameter = NamedUnit(1e-48, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_exameter', ascii_symbol='Gg Em^-3', symbol='GgEm⁻³') +megagrams_per_cubic_exameter = NamedUnit(9.999999999999999e-52, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_exameter', ascii_symbol='Mg Em^-3', symbol='MgEm⁻³') +kilograms_per_cubic_exameter = NamedUnit(9.999999999999999e-55, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_exameter', ascii_symbol='kg Em^-3', symbol='kgEm⁻³') +milligrams_per_cubic_exameter = NamedUnit(9.999999999999998e-61, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_exameter', ascii_symbol='mg Em^-3', symbol='mgEm⁻³') +micrograms_per_cubic_exameter = NamedUnit(9.999999999999999e-64, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_exameter', ascii_symbol='ug Em^-3', symbol='µgEm⁻³') +nanograms_per_cubic_exameter = NamedUnit(1.0000000000000001e-66, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_exameter', ascii_symbol='ng Em^-3', symbol='ngEm⁻³') +picograms_per_cubic_exameter = NamedUnit(1e-69, Dimensions(length=-3, mass=1), name='picograms_per_cubic_exameter', ascii_symbol='pg Em^-3', symbol='pgEm⁻³') +femtograms_per_cubic_exameter = NamedUnit(1e-72, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_exameter', ascii_symbol='fg Em^-3', symbol='fgEm⁻³') +attograms_per_cubic_exameter = NamedUnit(1e-75, Dimensions(length=-3, mass=1), name='attograms_per_cubic_exameter', ascii_symbol='ag Em^-3', symbol='agEm⁻³') +atomic_mass_units_per_cubic_exameter = NamedUnit(1.6605389209999996e-81, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_exameter', ascii_symbol='au Em^-3', symbol='auEm⁻³') +pounds_per_cubic_exameter = NamedUnit(4.5359237e-55, Dimensions(length=-3, mass=1), name='pounds_per_cubic_exameter', ascii_symbol='lb Em^-3', symbol='lbEm⁻³') +ounces_per_cubic_exameter = NamedUnit(2.8349523125e-56, Dimensions(length=-3, mass=1), name='ounces_per_cubic_exameter', ascii_symbol='oz Em^-3', symbol='ozEm⁻³') +grams_per_cubic_petameter = NamedUnit(1.0000000000000001e-48, Dimensions(length=-3, mass=1), name='grams_per_cubic_petameter', ascii_symbol='g Pm^-3', symbol='gPm⁻³') +exagrams_per_cubic_petameter = NamedUnit(1e-30, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_petameter', ascii_symbol='Eg Pm^-3', symbol='EgPm⁻³') +petagrams_per_cubic_petameter = NamedUnit(1e-33, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_petameter', ascii_symbol='Pg Pm^-3', symbol='PgPm⁻³') +teragrams_per_cubic_petameter = NamedUnit(1.0000000000000001e-36, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_petameter', ascii_symbol='Tg Pm^-3', symbol='TgPm⁻³') +gigagrams_per_cubic_petameter = NamedUnit(1.0000000000000001e-39, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_petameter', ascii_symbol='Gg Pm^-3', symbol='GgPm⁻³') +megagrams_per_cubic_petameter = NamedUnit(1e-42, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_petameter', ascii_symbol='Mg Pm^-3', symbol='MgPm⁻³') +kilograms_per_cubic_petameter = NamedUnit(1.0000000000000001e-45, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_petameter', ascii_symbol='kg Pm^-3', symbol='kgPm⁻³') +milligrams_per_cubic_petameter = NamedUnit(1e-51, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_petameter', ascii_symbol='mg Pm^-3', symbol='mgPm⁻³') +micrograms_per_cubic_petameter = NamedUnit(1.0000000000000002e-54, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_petameter', ascii_symbol='ug Pm^-3', symbol='µgPm⁻³') +nanograms_per_cubic_petameter = NamedUnit(1.0000000000000002e-57, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_petameter', ascii_symbol='ng Pm^-3', symbol='ngPm⁻³') +picograms_per_cubic_petameter = NamedUnit(1.0000000000000001e-60, Dimensions(length=-3, mass=1), name='picograms_per_cubic_petameter', ascii_symbol='pg Pm^-3', symbol='pgPm⁻³') +femtograms_per_cubic_petameter = NamedUnit(1.0000000000000002e-63, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_petameter', ascii_symbol='fg Pm^-3', symbol='fgPm⁻³') +attograms_per_cubic_petameter = NamedUnit(1.0000000000000001e-66, Dimensions(length=-3, mass=1), name='attograms_per_cubic_petameter', ascii_symbol='ag Pm^-3', symbol='agPm⁻³') +atomic_mass_units_per_cubic_petameter = NamedUnit(1.660538921e-72, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_petameter', ascii_symbol='au Pm^-3', symbol='auPm⁻³') +pounds_per_cubic_petameter = NamedUnit(4.5359237000000005e-46, Dimensions(length=-3, mass=1), name='pounds_per_cubic_petameter', ascii_symbol='lb Pm^-3', symbol='lbPm⁻³') +ounces_per_cubic_petameter = NamedUnit(2.8349523125000003e-47, Dimensions(length=-3, mass=1), name='ounces_per_cubic_petameter', ascii_symbol='oz Pm^-3', symbol='ozPm⁻³') +grams_per_cubic_terameter = NamedUnit(1e-39, Dimensions(length=-3, mass=1), name='grams_per_cubic_terameter', ascii_symbol='g Tm^-3', symbol='gTm⁻³') +exagrams_per_cubic_terameter = NamedUnit(1e-21, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_terameter', ascii_symbol='Eg Tm^-3', symbol='EgTm⁻³') +petagrams_per_cubic_terameter = NamedUnit(1e-24, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_terameter', ascii_symbol='Pg Tm^-3', symbol='PgTm⁻³') +teragrams_per_cubic_terameter = NamedUnit(1e-27, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_terameter', ascii_symbol='Tg Tm^-3', symbol='TgTm⁻³') +gigagrams_per_cubic_terameter = NamedUnit(9.999999999999999e-31, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_terameter', ascii_symbol='Gg Tm^-3', symbol='GgTm⁻³') +megagrams_per_cubic_terameter = NamedUnit(9.999999999999999e-34, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_terameter', ascii_symbol='Mg Tm^-3', symbol='MgTm⁻³') +kilograms_per_cubic_terameter = NamedUnit(1e-36, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_terameter', ascii_symbol='kg Tm^-3', symbol='kgTm⁻³') +milligrams_per_cubic_terameter = NamedUnit(9.999999999999999e-43, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_terameter', ascii_symbol='mg Tm^-3', symbol='mgTm⁻³') +micrograms_per_cubic_terameter = NamedUnit(1e-45, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_terameter', ascii_symbol='ug Tm^-3', symbol='µgTm⁻³') +nanograms_per_cubic_terameter = NamedUnit(1.0000000000000001e-48, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_terameter', ascii_symbol='ng Tm^-3', symbol='ngTm⁻³') +picograms_per_cubic_terameter = NamedUnit(1e-51, Dimensions(length=-3, mass=1), name='picograms_per_cubic_terameter', ascii_symbol='pg Tm^-3', symbol='pgTm⁻³') +femtograms_per_cubic_terameter = NamedUnit(1e-54, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_terameter', ascii_symbol='fg Tm^-3', symbol='fgTm⁻³') +attograms_per_cubic_terameter = NamedUnit(1.0000000000000001e-57, Dimensions(length=-3, mass=1), name='attograms_per_cubic_terameter', ascii_symbol='ag Tm^-3', symbol='agTm⁻³') +atomic_mass_units_per_cubic_terameter = NamedUnit(1.6605389209999997e-63, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_terameter', ascii_symbol='au Tm^-3', symbol='auTm⁻³') +pounds_per_cubic_terameter = NamedUnit(4.5359237e-37, Dimensions(length=-3, mass=1), name='pounds_per_cubic_terameter', ascii_symbol='lb Tm^-3', symbol='lbTm⁻³') +ounces_per_cubic_terameter = NamedUnit(2.8349523125e-38, Dimensions(length=-3, mass=1), name='ounces_per_cubic_terameter', ascii_symbol='oz Tm^-3', symbol='ozTm⁻³') +grams_per_cubic_gigameter = NamedUnit(1e-30, Dimensions(length=-3, mass=1), name='grams_per_cubic_gigameter', ascii_symbol='g Gm^-3', symbol='gGm⁻³') +exagrams_per_cubic_gigameter = NamedUnit(1e-12, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_gigameter', ascii_symbol='Eg Gm^-3', symbol='EgGm⁻³') +petagrams_per_cubic_gigameter = NamedUnit(1e-15, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_gigameter', ascii_symbol='Pg Gm^-3', symbol='PgGm⁻³') +teragrams_per_cubic_gigameter = NamedUnit(1e-18, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_gigameter', ascii_symbol='Tg Gm^-3', symbol='TgGm⁻³') +gigagrams_per_cubic_gigameter = NamedUnit(1e-21, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_gigameter', ascii_symbol='Gg Gm^-3', symbol='GgGm⁻³') +megagrams_per_cubic_gigameter = NamedUnit(1e-24, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_gigameter', ascii_symbol='Mg Gm^-3', symbol='MgGm⁻³') +kilograms_per_cubic_gigameter = NamedUnit(1e-27, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_gigameter', ascii_symbol='kg Gm^-3', symbol='kgGm⁻³') +milligrams_per_cubic_gigameter = NamedUnit(9.999999999999999e-34, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_gigameter', ascii_symbol='mg Gm^-3', symbol='mgGm⁻³') +micrograms_per_cubic_gigameter = NamedUnit(1.0000000000000001e-36, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_gigameter', ascii_symbol='ug Gm^-3', symbol='µgGm⁻³') +nanograms_per_cubic_gigameter = NamedUnit(1.0000000000000001e-39, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_gigameter', ascii_symbol='ng Gm^-3', symbol='ngGm⁻³') +picograms_per_cubic_gigameter = NamedUnit(1e-42, Dimensions(length=-3, mass=1), name='picograms_per_cubic_gigameter', ascii_symbol='pg Gm^-3', symbol='pgGm⁻³') +femtograms_per_cubic_gigameter = NamedUnit(1e-45, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_gigameter', ascii_symbol='fg Gm^-3', symbol='fgGm⁻³') +attograms_per_cubic_gigameter = NamedUnit(1.0000000000000001e-48, Dimensions(length=-3, mass=1), name='attograms_per_cubic_gigameter', ascii_symbol='ag Gm^-3', symbol='agGm⁻³') +atomic_mass_units_per_cubic_gigameter = NamedUnit(1.660538921e-54, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_gigameter', ascii_symbol='au Gm^-3', symbol='auGm⁻³') +pounds_per_cubic_gigameter = NamedUnit(4.5359237e-28, Dimensions(length=-3, mass=1), name='pounds_per_cubic_gigameter', ascii_symbol='lb Gm^-3', symbol='lbGm⁻³') +ounces_per_cubic_gigameter = NamedUnit(2.8349523125e-29, Dimensions(length=-3, mass=1), name='ounces_per_cubic_gigameter', ascii_symbol='oz Gm^-3', symbol='ozGm⁻³') +grams_per_cubic_megameter = NamedUnit(1.0000000000000001e-21, Dimensions(length=-3, mass=1), name='grams_per_cubic_megameter', ascii_symbol='g Mm^-3', symbol='gMm⁻³') +exagrams_per_cubic_megameter = NamedUnit(0.001, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_megameter', ascii_symbol='Eg Mm^-3', symbol='EgMm⁻³') +petagrams_per_cubic_megameter = NamedUnit(1e-06, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_megameter', ascii_symbol='Pg Mm^-3', symbol='PgMm⁻³') +teragrams_per_cubic_megameter = NamedUnit(1e-09, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_megameter', ascii_symbol='Tg Mm^-3', symbol='TgMm⁻³') +gigagrams_per_cubic_megameter = NamedUnit(1e-12, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_megameter', ascii_symbol='Gg Mm^-3', symbol='GgMm⁻³') +megagrams_per_cubic_megameter = NamedUnit(1e-15, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_megameter', ascii_symbol='Mg Mm^-3', symbol='MgMm⁻³') +kilograms_per_cubic_megameter = NamedUnit(1e-18, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_megameter', ascii_symbol='kg Mm^-3', symbol='kgMm⁻³') +milligrams_per_cubic_megameter = NamedUnit(1e-24, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_megameter', ascii_symbol='mg Mm^-3', symbol='mgMm⁻³') +micrograms_per_cubic_megameter = NamedUnit(1e-27, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_megameter', ascii_symbol='ug Mm^-3', symbol='µgMm⁻³') +nanograms_per_cubic_megameter = NamedUnit(1.0000000000000003e-30, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_megameter', ascii_symbol='ng Mm^-3', symbol='ngMm⁻³') +picograms_per_cubic_megameter = NamedUnit(1e-33, Dimensions(length=-3, mass=1), name='picograms_per_cubic_megameter', ascii_symbol='pg Mm^-3', symbol='pgMm⁻³') +femtograms_per_cubic_megameter = NamedUnit(1.0000000000000001e-36, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_megameter', ascii_symbol='fg Mm^-3', symbol='fgMm⁻³') +attograms_per_cubic_megameter = NamedUnit(1.0000000000000001e-39, Dimensions(length=-3, mass=1), name='attograms_per_cubic_megameter', ascii_symbol='ag Mm^-3', symbol='agMm⁻³') +atomic_mass_units_per_cubic_megameter = NamedUnit(1.6605389209999997e-45, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_megameter', ascii_symbol='au Mm^-3', symbol='auMm⁻³') +pounds_per_cubic_megameter = NamedUnit(4.535923700000001e-19, Dimensions(length=-3, mass=1), name='pounds_per_cubic_megameter', ascii_symbol='lb Mm^-3', symbol='lbMm⁻³') +ounces_per_cubic_megameter = NamedUnit(2.8349523125000004e-20, Dimensions(length=-3, mass=1), name='ounces_per_cubic_megameter', ascii_symbol='oz Mm^-3', symbol='ozMm⁻³') +grams_per_cubic_kilometer = NamedUnit(1e-12, Dimensions(length=-3, mass=1), name='grams_per_cubic_kilometer', ascii_symbol='g km^-3', symbol='gkm⁻³') +exagrams_per_cubic_kilometer = NamedUnit(1000000.0, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_kilometer', ascii_symbol='Eg km^-3', symbol='Egkm⁻³') +petagrams_per_cubic_kilometer = NamedUnit(1000.0, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_kilometer', ascii_symbol='Pg km^-3', symbol='Pgkm⁻³') +teragrams_per_cubic_kilometer = NamedUnit(1.0, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_kilometer', ascii_symbol='Tg km^-3', symbol='Tgkm⁻³') +gigagrams_per_cubic_kilometer = NamedUnit(0.001, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_kilometer', ascii_symbol='Gg km^-3', symbol='Ggkm⁻³') +megagrams_per_cubic_kilometer = NamedUnit(1e-06, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_kilometer', ascii_symbol='Mg km^-3', symbol='Mgkm⁻³') +kilograms_per_cubic_kilometer = NamedUnit(1e-09, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_kilometer', ascii_symbol='kg km^-3', symbol='kgkm⁻³') +milligrams_per_cubic_kilometer = NamedUnit(9.999999999999999e-16, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_kilometer', ascii_symbol='mg km^-3', symbol='mgkm⁻³') +micrograms_per_cubic_kilometer = NamedUnit(1e-18, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_kilometer', ascii_symbol='ug km^-3', symbol='µgkm⁻³') +nanograms_per_cubic_kilometer = NamedUnit(1.0000000000000001e-21, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_kilometer', ascii_symbol='ng km^-3', symbol='ngkm⁻³') +picograms_per_cubic_kilometer = NamedUnit(1.0000000000000001e-24, Dimensions(length=-3, mass=1), name='picograms_per_cubic_kilometer', ascii_symbol='pg km^-3', symbol='pgkm⁻³') +femtograms_per_cubic_kilometer = NamedUnit(1e-27, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_kilometer', ascii_symbol='fg km^-3', symbol='fgkm⁻³') +attograms_per_cubic_kilometer = NamedUnit(1e-30, Dimensions(length=-3, mass=1), name='attograms_per_cubic_kilometer', ascii_symbol='ag km^-3', symbol='agkm⁻³') +atomic_mass_units_per_cubic_kilometer = NamedUnit(1.6605389209999997e-36, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_kilometer', ascii_symbol='au km^-3', symbol='aukm⁻³') +pounds_per_cubic_kilometer = NamedUnit(4.5359237000000004e-10, Dimensions(length=-3, mass=1), name='pounds_per_cubic_kilometer', ascii_symbol='lb km^-3', symbol='lbkm⁻³') +ounces_per_cubic_kilometer = NamedUnit(2.8349523125000003e-11, Dimensions(length=-3, mass=1), name='ounces_per_cubic_kilometer', ascii_symbol='oz km^-3', symbol='ozkm⁻³') +grams_per_cubic_millimeter = NamedUnit(1000000.0, Dimensions(length=-3, mass=1), name='grams_per_cubic_millimeter', ascii_symbol='g mm^-3', symbol='gmm⁻³') +exagrams_per_cubic_millimeter = NamedUnit(1e+24, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_millimeter', ascii_symbol='Eg mm^-3', symbol='Egmm⁻³') +petagrams_per_cubic_millimeter = NamedUnit(1e+21, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_millimeter', ascii_symbol='Pg mm^-3', symbol='Pgmm⁻³') +teragrams_per_cubic_millimeter = NamedUnit(1e+18, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_millimeter', ascii_symbol='Tg mm^-3', symbol='Tgmm⁻³') +gigagrams_per_cubic_millimeter = NamedUnit(1000000000000000.0, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_millimeter', ascii_symbol='Gg mm^-3', symbol='Ggmm⁻³') +megagrams_per_cubic_millimeter = NamedUnit(999999999999.9999, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_millimeter', ascii_symbol='Mg mm^-3', symbol='Mgmm⁻³') +kilograms_per_cubic_millimeter = NamedUnit(999999999.9999999, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_millimeter', ascii_symbol='kg mm^-3', symbol='kgmm⁻³') +milligrams_per_cubic_millimeter = NamedUnit(999.9999999999999, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_millimeter', ascii_symbol='mg mm^-3', symbol='mgmm⁻³') +micrograms_per_cubic_millimeter = NamedUnit(1.0, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_millimeter', ascii_symbol='ug mm^-3', symbol='µgmm⁻³') +nanograms_per_cubic_millimeter = NamedUnit(0.001, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_millimeter', ascii_symbol='ng mm^-3', symbol='ngmm⁻³') +picograms_per_cubic_millimeter = NamedUnit(1e-06, Dimensions(length=-3, mass=1), name='picograms_per_cubic_millimeter', ascii_symbol='pg mm^-3', symbol='pgmm⁻³') +femtograms_per_cubic_millimeter = NamedUnit(1e-09, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_millimeter', ascii_symbol='fg mm^-3', symbol='fgmm⁻³') +attograms_per_cubic_millimeter = NamedUnit(1e-12, Dimensions(length=-3, mass=1), name='attograms_per_cubic_millimeter', ascii_symbol='ag mm^-3', symbol='agmm⁻³') +atomic_mass_units_per_cubic_millimeter = NamedUnit(1.6605389209999997e-18, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_millimeter', ascii_symbol='au mm^-3', symbol='aumm⁻³') +pounds_per_cubic_millimeter = NamedUnit(453592370.0, Dimensions(length=-3, mass=1), name='pounds_per_cubic_millimeter', ascii_symbol='lb mm^-3', symbol='lbmm⁻³') +ounces_per_cubic_millimeter = NamedUnit(28349523.125, Dimensions(length=-3, mass=1), name='ounces_per_cubic_millimeter', ascii_symbol='oz mm^-3', symbol='ozmm⁻³') +grams_per_cubic_micrometer = NamedUnit(1000000000000000.1, Dimensions(length=-3, mass=1), name='grams_per_cubic_micrometer', ascii_symbol='g um^-3', symbol='gµm⁻³') +exagrams_per_cubic_micrometer = NamedUnit(1.0000000000000001e+33, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_micrometer', ascii_symbol='Eg um^-3', symbol='Egµm⁻³') +petagrams_per_cubic_micrometer = NamedUnit(1.0000000000000002e+30, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_micrometer', ascii_symbol='Pg um^-3', symbol='Pgµm⁻³') +teragrams_per_cubic_micrometer = NamedUnit(1.0000000000000002e+27, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_micrometer', ascii_symbol='Tg um^-3', symbol='Tgµm⁻³') +gigagrams_per_cubic_micrometer = NamedUnit(1.0000000000000001e+24, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_micrometer', ascii_symbol='Gg um^-3', symbol='Ggµm⁻³') +megagrams_per_cubic_micrometer = NamedUnit(1.0000000000000001e+21, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_micrometer', ascii_symbol='Mg um^-3', symbol='Mgµm⁻³') +kilograms_per_cubic_micrometer = NamedUnit(1.0000000000000001e+18, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_micrometer', ascii_symbol='kg um^-3', symbol='kgµm⁻³') +milligrams_per_cubic_micrometer = NamedUnit(1000000000000.0001, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_micrometer', ascii_symbol='mg um^-3', symbol='mgµm⁻³') +micrograms_per_cubic_micrometer = NamedUnit(1000000000.0000002, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_micrometer', ascii_symbol='ug um^-3', symbol='µgµm⁻³') +nanograms_per_cubic_micrometer = NamedUnit(1000000.0000000003, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_micrometer', ascii_symbol='ng um^-3', symbol='ngµm⁻³') +picograms_per_cubic_micrometer = NamedUnit(1000.0000000000002, Dimensions(length=-3, mass=1), name='picograms_per_cubic_micrometer', ascii_symbol='pg um^-3', symbol='pgµm⁻³') +femtograms_per_cubic_micrometer = NamedUnit(1.0000000000000002, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_micrometer', ascii_symbol='fg um^-3', symbol='fgµm⁻³') +attograms_per_cubic_micrometer = NamedUnit(0.0010000000000000002, Dimensions(length=-3, mass=1), name='attograms_per_cubic_micrometer', ascii_symbol='ag um^-3', symbol='agµm⁻³') +atomic_mass_units_per_cubic_micrometer = NamedUnit(1.660538921e-09, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_micrometer', ascii_symbol='au um^-3', symbol='auµm⁻³') +pounds_per_cubic_micrometer = NamedUnit(4.5359237000000006e+17, Dimensions(length=-3, mass=1), name='pounds_per_cubic_micrometer', ascii_symbol='lb um^-3', symbol='lbµm⁻³') +ounces_per_cubic_micrometer = NamedUnit(2.8349523125000004e+16, Dimensions(length=-3, mass=1), name='ounces_per_cubic_micrometer', ascii_symbol='oz um^-3', symbol='ozµm⁻³') +grams_per_cubic_nanometer = NamedUnit(9.999999999999998e+23, Dimensions(length=-3, mass=1), name='grams_per_cubic_nanometer', ascii_symbol='g nm^-3', symbol='gnm⁻³') +exagrams_per_cubic_nanometer = NamedUnit(9.999999999999997e+41, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_nanometer', ascii_symbol='Eg nm^-3', symbol='Egnm⁻³') +petagrams_per_cubic_nanometer = NamedUnit(9.999999999999998e+38, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_nanometer', ascii_symbol='Pg nm^-3', symbol='Pgnm⁻³') +teragrams_per_cubic_nanometer = NamedUnit(9.999999999999997e+35, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_nanometer', ascii_symbol='Tg nm^-3', symbol='Tgnm⁻³') +gigagrams_per_cubic_nanometer = NamedUnit(9.999999999999998e+32, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_nanometer', ascii_symbol='Gg nm^-3', symbol='Ggnm⁻³') +megagrams_per_cubic_nanometer = NamedUnit(9.999999999999997e+29, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_nanometer', ascii_symbol='Mg nm^-3', symbol='Mgnm⁻³') +kilograms_per_cubic_nanometer = NamedUnit(9.999999999999997e+26, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_nanometer', ascii_symbol='kg nm^-3', symbol='kgnm⁻³') +milligrams_per_cubic_nanometer = NamedUnit(9.999999999999997e+20, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_nanometer', ascii_symbol='mg nm^-3', symbol='mgnm⁻³') +micrograms_per_cubic_nanometer = NamedUnit(9.999999999999999e+17, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_nanometer', ascii_symbol='ug nm^-3', symbol='µgnm⁻³') +nanograms_per_cubic_nanometer = NamedUnit(1000000000000000.0, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_nanometer', ascii_symbol='ng nm^-3', symbol='ngnm⁻³') +picograms_per_cubic_nanometer = NamedUnit(999999999999.9999, Dimensions(length=-3, mass=1), name='picograms_per_cubic_nanometer', ascii_symbol='pg nm^-3', symbol='pgnm⁻³') +femtograms_per_cubic_nanometer = NamedUnit(999999999.9999999, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_nanometer', ascii_symbol='fg nm^-3', symbol='fgnm⁻³') +attograms_per_cubic_nanometer = NamedUnit(999999.9999999999, Dimensions(length=-3, mass=1), name='attograms_per_cubic_nanometer', ascii_symbol='ag nm^-3', symbol='agnm⁻³') +atomic_mass_units_per_cubic_nanometer = NamedUnit(1.6605389209999994, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_nanometer', ascii_symbol='au nm^-3', symbol='aunm⁻³') +pounds_per_cubic_nanometer = NamedUnit(4.535923699999999e+26, Dimensions(length=-3, mass=1), name='pounds_per_cubic_nanometer', ascii_symbol='lb nm^-3', symbol='lbnm⁻³') +ounces_per_cubic_nanometer = NamedUnit(2.8349523124999993e+25, Dimensions(length=-3, mass=1), name='ounces_per_cubic_nanometer', ascii_symbol='oz nm^-3', symbol='oznm⁻³') +grams_per_cubic_picometer = NamedUnit(1.0000000000000001e+33, Dimensions(length=-3, mass=1), name='grams_per_cubic_picometer', ascii_symbol='g pm^-3', symbol='gpm⁻³') +exagrams_per_cubic_picometer = NamedUnit(1e+51, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_picometer', ascii_symbol='Eg pm^-3', symbol='Egpm⁻³') +petagrams_per_cubic_picometer = NamedUnit(1e+48, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_picometer', ascii_symbol='Pg pm^-3', symbol='Pgpm⁻³') +teragrams_per_cubic_picometer = NamedUnit(1.0000000000000001e+45, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_picometer', ascii_symbol='Tg pm^-3', symbol='Tgpm⁻³') +gigagrams_per_cubic_picometer = NamedUnit(1e+42, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_picometer', ascii_symbol='Gg pm^-3', symbol='Ggpm⁻³') +megagrams_per_cubic_picometer = NamedUnit(1.0000000000000001e+39, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_picometer', ascii_symbol='Mg pm^-3', symbol='Mgpm⁻³') +kilograms_per_cubic_picometer = NamedUnit(1e+36, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_picometer', ascii_symbol='kg pm^-3', symbol='kgpm⁻³') +milligrams_per_cubic_picometer = NamedUnit(1e+30, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_picometer', ascii_symbol='mg pm^-3', symbol='mgpm⁻³') +micrograms_per_cubic_picometer = NamedUnit(1.0000000000000002e+27, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_picometer', ascii_symbol='ug pm^-3', symbol='µgpm⁻³') +nanograms_per_cubic_picometer = NamedUnit(1.0000000000000003e+24, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_picometer', ascii_symbol='ng pm^-3', symbol='ngpm⁻³') +picograms_per_cubic_picometer = NamedUnit(1.0000000000000001e+21, Dimensions(length=-3, mass=1), name='picograms_per_cubic_picometer', ascii_symbol='pg pm^-3', symbol='pgpm⁻³') +femtograms_per_cubic_picometer = NamedUnit(1.0000000000000001e+18, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_picometer', ascii_symbol='fg pm^-3', symbol='fgpm⁻³') +attograms_per_cubic_picometer = NamedUnit(1000000000000000.1, Dimensions(length=-3, mass=1), name='attograms_per_cubic_picometer', ascii_symbol='ag pm^-3', symbol='agpm⁻³') +atomic_mass_units_per_cubic_picometer = NamedUnit(1660538921.0, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_picometer', ascii_symbol='au pm^-3', symbol='aupm⁻³') +pounds_per_cubic_picometer = NamedUnit(4.5359237000000005e+35, Dimensions(length=-3, mass=1), name='pounds_per_cubic_picometer', ascii_symbol='lb pm^-3', symbol='lbpm⁻³') +ounces_per_cubic_picometer = NamedUnit(2.8349523125000003e+34, Dimensions(length=-3, mass=1), name='ounces_per_cubic_picometer', ascii_symbol='oz pm^-3', symbol='ozpm⁻³') +grams_per_cubic_femtometer = NamedUnit(9.999999999999997e+41, Dimensions(length=-3, mass=1), name='grams_per_cubic_femtometer', ascii_symbol='g fm^-3', symbol='gfm⁻³') +exagrams_per_cubic_femtometer = NamedUnit(9.999999999999998e+59, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_femtometer', ascii_symbol='Eg fm^-3', symbol='Egfm⁻³') +petagrams_per_cubic_femtometer = NamedUnit(9.999999999999997e+56, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_femtometer', ascii_symbol='Pg fm^-3', symbol='Pgfm⁻³') +teragrams_per_cubic_femtometer = NamedUnit(9.999999999999997e+53, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_femtometer', ascii_symbol='Tg fm^-3', symbol='Tgfm⁻³') +gigagrams_per_cubic_femtometer = NamedUnit(9.999999999999997e+50, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_femtometer', ascii_symbol='Gg fm^-3', symbol='Ggfm⁻³') +megagrams_per_cubic_femtometer = NamedUnit(9.999999999999997e+47, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_femtometer', ascii_symbol='Mg fm^-3', symbol='Mgfm⁻³') +kilograms_per_cubic_femtometer = NamedUnit(9.999999999999998e+44, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_femtometer', ascii_symbol='kg fm^-3', symbol='kgfm⁻³') +milligrams_per_cubic_femtometer = NamedUnit(9.999999999999996e+38, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_femtometer', ascii_symbol='mg fm^-3', symbol='mgfm⁻³') +micrograms_per_cubic_femtometer = NamedUnit(9.999999999999997e+35, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_femtometer', ascii_symbol='ug fm^-3', symbol='µgfm⁻³') +nanograms_per_cubic_femtometer = NamedUnit(1e+33, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_femtometer', ascii_symbol='ng fm^-3', symbol='ngfm⁻³') +picograms_per_cubic_femtometer = NamedUnit(9.999999999999997e+29, Dimensions(length=-3, mass=1), name='picograms_per_cubic_femtometer', ascii_symbol='pg fm^-3', symbol='pgfm⁻³') +femtograms_per_cubic_femtometer = NamedUnit(9.999999999999997e+26, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_femtometer', ascii_symbol='fg fm^-3', symbol='fgfm⁻³') +attograms_per_cubic_femtometer = NamedUnit(9.999999999999998e+23, Dimensions(length=-3, mass=1), name='attograms_per_cubic_femtometer', ascii_symbol='ag fm^-3', symbol='agfm⁻³') +atomic_mass_units_per_cubic_femtometer = NamedUnit(1.6605389209999992e+18, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_femtometer', ascii_symbol='au fm^-3', symbol='aufm⁻³') +pounds_per_cubic_femtometer = NamedUnit(4.5359236999999985e+44, Dimensions(length=-3, mass=1), name='pounds_per_cubic_femtometer', ascii_symbol='lb fm^-3', symbol='lbfm⁻³') +ounces_per_cubic_femtometer = NamedUnit(2.834952312499999e+43, Dimensions(length=-3, mass=1), name='ounces_per_cubic_femtometer', ascii_symbol='oz fm^-3', symbol='ozfm⁻³') +grams_per_cubic_attometer = NamedUnit(9.999999999999998e+50, Dimensions(length=-3, mass=1), name='grams_per_cubic_attometer', ascii_symbol='g am^-3', symbol='gam⁻³') +exagrams_per_cubic_attometer = NamedUnit(9.999999999999999e+68, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_attometer', ascii_symbol='Eg am^-3', symbol='Egam⁻³') +petagrams_per_cubic_attometer = NamedUnit(9.999999999999998e+65, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_attometer', ascii_symbol='Pg am^-3', symbol='Pgam⁻³') +teragrams_per_cubic_attometer = NamedUnit(9.999999999999999e+62, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_attometer', ascii_symbol='Tg am^-3', symbol='Tgam⁻³') +gigagrams_per_cubic_attometer = NamedUnit(9.999999999999998e+59, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_attometer', ascii_symbol='Gg am^-3', symbol='Ggam⁻³') +megagrams_per_cubic_attometer = NamedUnit(9.999999999999999e+56, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_attometer', ascii_symbol='Mg am^-3', symbol='Mgam⁻³') +kilograms_per_cubic_attometer = NamedUnit(9.999999999999999e+53, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_attometer', ascii_symbol='kg am^-3', symbol='kgam⁻³') +milligrams_per_cubic_attometer = NamedUnit(9.999999999999997e+47, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_attometer', ascii_symbol='mg am^-3', symbol='mgam⁻³') +micrograms_per_cubic_attometer = NamedUnit(1e+45, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_attometer', ascii_symbol='ug am^-3', symbol='µgam⁻³') +nanograms_per_cubic_attometer = NamedUnit(1e+42, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_attometer', ascii_symbol='ng am^-3', symbol='ngam⁻³') +picograms_per_cubic_attometer = NamedUnit(1e+39, Dimensions(length=-3, mass=1), name='picograms_per_cubic_attometer', ascii_symbol='pg am^-3', symbol='pgam⁻³') +femtograms_per_cubic_attometer = NamedUnit(9.999999999999999e+35, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_attometer', ascii_symbol='fg am^-3', symbol='fgam⁻³') +attograms_per_cubic_attometer = NamedUnit(1e+33, Dimensions(length=-3, mass=1), name='attograms_per_cubic_attometer', ascii_symbol='ag am^-3', symbol='agam⁻³') +atomic_mass_units_per_cubic_attometer = NamedUnit(1.6605389209999997e+27, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_attometer', ascii_symbol='au am^-3', symbol='auam⁻³') +pounds_per_cubic_attometer = NamedUnit(4.5359237e+53, Dimensions(length=-3, mass=1), name='pounds_per_cubic_attometer', ascii_symbol='lb am^-3', symbol='lbam⁻³') +ounces_per_cubic_attometer = NamedUnit(2.8349523125e+52, Dimensions(length=-3, mass=1), name='ounces_per_cubic_attometer', ascii_symbol='oz am^-3', symbol='ozam⁻³') +grams_per_cubic_decimeter = NamedUnit(0.9999999999999998, Dimensions(length=-3, mass=1), name='grams_per_cubic_decimeter', ascii_symbol='g dm^-3', symbol='gdm⁻³') +exagrams_per_cubic_decimeter = NamedUnit(9.999999999999997e+17, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_decimeter', ascii_symbol='Eg dm^-3', symbol='Egdm⁻³') +petagrams_per_cubic_decimeter = NamedUnit(999999999999999.8, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_decimeter', ascii_symbol='Pg dm^-3', symbol='Pgdm⁻³') +teragrams_per_cubic_decimeter = NamedUnit(999999999999.9998, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_decimeter', ascii_symbol='Tg dm^-3', symbol='Tgdm⁻³') +gigagrams_per_cubic_decimeter = NamedUnit(999999999.9999998, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_decimeter', ascii_symbol='Gg dm^-3', symbol='Ggdm⁻³') +megagrams_per_cubic_decimeter = NamedUnit(999999.9999999998, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_decimeter', ascii_symbol='Mg dm^-3', symbol='Mgdm⁻³') +kilograms_per_cubic_decimeter = NamedUnit(999.9999999999998, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_decimeter', ascii_symbol='kg dm^-3', symbol='kgdm⁻³') +milligrams_per_cubic_decimeter = NamedUnit(0.0009999999999999998, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_decimeter', ascii_symbol='mg dm^-3', symbol='mgdm⁻³') +micrograms_per_cubic_decimeter = NamedUnit(9.999999999999997e-07, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_decimeter', ascii_symbol='ug dm^-3', symbol='µgdm⁻³') +nanograms_per_cubic_decimeter = NamedUnit(9.999999999999999e-10, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_decimeter', ascii_symbol='ng dm^-3', symbol='ngdm⁻³') +picograms_per_cubic_decimeter = NamedUnit(9.999999999999998e-13, Dimensions(length=-3, mass=1), name='picograms_per_cubic_decimeter', ascii_symbol='pg dm^-3', symbol='pgdm⁻³') +femtograms_per_cubic_decimeter = NamedUnit(9.999999999999999e-16, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_decimeter', ascii_symbol='fg dm^-3', symbol='fgdm⁻³') +attograms_per_cubic_decimeter = NamedUnit(9.999999999999999e-19, Dimensions(length=-3, mass=1), name='attograms_per_cubic_decimeter', ascii_symbol='ag dm^-3', symbol='agdm⁻³') +atomic_mass_units_per_cubic_decimeter = NamedUnit(1.6605389209999993e-24, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_decimeter', ascii_symbol='au dm^-3', symbol='audm⁻³') +pounds_per_cubic_decimeter = NamedUnit(453.5923699999999, Dimensions(length=-3, mass=1), name='pounds_per_cubic_decimeter', ascii_symbol='lb dm^-3', symbol='lbdm⁻³') +ounces_per_cubic_decimeter = NamedUnit(28.349523124999994, Dimensions(length=-3, mass=1), name='ounces_per_cubic_decimeter', ascii_symbol='oz dm^-3', symbol='ozdm⁻³') +grams_per_cubic_centimeter = NamedUnit(999.9999999999999, Dimensions(length=-3, mass=1), name='grams_per_cubic_centimeter', ascii_symbol='g cm^-3', symbol='gcm⁻³') +exagrams_per_cubic_centimeter = NamedUnit(9.999999999999999e+20, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_centimeter', ascii_symbol='Eg cm^-3', symbol='Egcm⁻³') +petagrams_per_cubic_centimeter = NamedUnit(9.999999999999999e+17, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_centimeter', ascii_symbol='Pg cm^-3', symbol='Pgcm⁻³') +teragrams_per_cubic_centimeter = NamedUnit(999999999999999.9, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_centimeter', ascii_symbol='Tg cm^-3', symbol='Tgcm⁻³') +gigagrams_per_cubic_centimeter = NamedUnit(999999999999.9999, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_centimeter', ascii_symbol='Gg cm^-3', symbol='Ggcm⁻³') +megagrams_per_cubic_centimeter = NamedUnit(999999999.9999999, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_centimeter', ascii_symbol='Mg cm^-3', symbol='Mgcm⁻³') +kilograms_per_cubic_centimeter = NamedUnit(999999.9999999999, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_centimeter', ascii_symbol='kg cm^-3', symbol='kgcm⁻³') +milligrams_per_cubic_centimeter = NamedUnit(0.9999999999999998, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_centimeter', ascii_symbol='mg cm^-3', symbol='mgcm⁻³') +micrograms_per_cubic_centimeter = NamedUnit(0.0009999999999999998, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_centimeter', ascii_symbol='ug cm^-3', symbol='µgcm⁻³') +nanograms_per_cubic_centimeter = NamedUnit(1e-06, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_centimeter', ascii_symbol='ng cm^-3', symbol='ngcm⁻³') +picograms_per_cubic_centimeter = NamedUnit(9.999999999999999e-10, Dimensions(length=-3, mass=1), name='picograms_per_cubic_centimeter', ascii_symbol='pg cm^-3', symbol='pgcm⁻³') +femtograms_per_cubic_centimeter = NamedUnit(1e-12, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_centimeter', ascii_symbol='fg cm^-3', symbol='fgcm⁻³') +attograms_per_cubic_centimeter = NamedUnit(9.999999999999999e-16, Dimensions(length=-3, mass=1), name='attograms_per_cubic_centimeter', ascii_symbol='ag cm^-3', symbol='agcm⁻³') +atomic_mass_units_per_cubic_centimeter = NamedUnit(1.6605389209999996e-21, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_centimeter', ascii_symbol='au cm^-3', symbol='aucm⁻³') +pounds_per_cubic_centimeter = NamedUnit(453592.36999999994, Dimensions(length=-3, mass=1), name='pounds_per_cubic_centimeter', ascii_symbol='lb cm^-3', symbol='lbcm⁻³') +ounces_per_cubic_centimeter = NamedUnit(28349.523124999996, Dimensions(length=-3, mass=1), name='ounces_per_cubic_centimeter', ascii_symbol='oz cm^-3', symbol='ozcm⁻³') +grams_per_cubic_angstrom = NamedUnit(9.999999999999999e+26, Dimensions(length=-3, mass=1), name='grams_per_cubic_angstrom', ascii_symbol='g Ang^-3', symbol='gÅ⁻³') +exagrams_per_cubic_angstrom = NamedUnit(1e+45, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_angstrom', ascii_symbol='Eg Ang^-3', symbol='EgÅ⁻³') +petagrams_per_cubic_angstrom = NamedUnit(9.999999999999999e+41, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_angstrom', ascii_symbol='Pg Ang^-3', symbol='PgÅ⁻³') +teragrams_per_cubic_angstrom = NamedUnit(1e+39, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_angstrom', ascii_symbol='Tg Ang^-3', symbol='TgÅ⁻³') +gigagrams_per_cubic_angstrom = NamedUnit(9.999999999999999e+35, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_angstrom', ascii_symbol='Gg Ang^-3', symbol='GgÅ⁻³') +megagrams_per_cubic_angstrom = NamedUnit(1e+33, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_angstrom', ascii_symbol='Mg Ang^-3', symbol='MgÅ⁻³') +kilograms_per_cubic_angstrom = NamedUnit(9.999999999999999e+29, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_angstrom', ascii_symbol='kg Ang^-3', symbol='kgÅ⁻³') +milligrams_per_cubic_angstrom = NamedUnit(9.999999999999998e+23, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_angstrom', ascii_symbol='mg Ang^-3', symbol='mgÅ⁻³') +micrograms_per_cubic_angstrom = NamedUnit(1e+21, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_angstrom', ascii_symbol='ug Ang^-3', symbol='µgÅ⁻³') +nanograms_per_cubic_angstrom = NamedUnit(1.0000000000000001e+18, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_angstrom', ascii_symbol='ng Ang^-3', symbol='ngÅ⁻³') +picograms_per_cubic_angstrom = NamedUnit(1000000000000000.0, Dimensions(length=-3, mass=1), name='picograms_per_cubic_angstrom', ascii_symbol='pg Ang^-3', symbol='pgÅ⁻³') +femtograms_per_cubic_angstrom = NamedUnit(1000000000000.0, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_angstrom', ascii_symbol='fg Ang^-3', symbol='fgÅ⁻³') +attograms_per_cubic_angstrom = NamedUnit(1000000000.0, Dimensions(length=-3, mass=1), name='attograms_per_cubic_angstrom', ascii_symbol='ag Ang^-3', symbol='agÅ⁻³') +atomic_mass_units_per_cubic_angstrom = NamedUnit(1660.5389209999996, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_angstrom', ascii_symbol='au Ang^-3', symbol='auÅ⁻³') +pounds_per_cubic_angstrom = NamedUnit(4.5359237e+29, Dimensions(length=-3, mass=1), name='pounds_per_cubic_angstrom', ascii_symbol='lb Ang^-3', symbol='lbÅ⁻³') +ounces_per_cubic_angstrom = NamedUnit(2.8349523125e+28, Dimensions(length=-3, mass=1), name='ounces_per_cubic_angstrom', ascii_symbol='oz Ang^-3', symbol='ozÅ⁻³') +grams_per_cubic_micron = NamedUnit(1000000000000000.1, Dimensions(length=-3, mass=1), name='grams_per_cubic_micron', ascii_symbol='g micron^-3', symbol='gmicron⁻³') +exagrams_per_cubic_micron = NamedUnit(1.0000000000000001e+33, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_micron', ascii_symbol='Eg micron^-3', symbol='Egmicron⁻³') +petagrams_per_cubic_micron = NamedUnit(1.0000000000000002e+30, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_micron', ascii_symbol='Pg micron^-3', symbol='Pgmicron⁻³') +teragrams_per_cubic_micron = NamedUnit(1.0000000000000002e+27, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_micron', ascii_symbol='Tg micron^-3', symbol='Tgmicron⁻³') +gigagrams_per_cubic_micron = NamedUnit(1.0000000000000001e+24, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_micron', ascii_symbol='Gg micron^-3', symbol='Ggmicron⁻³') +megagrams_per_cubic_micron = NamedUnit(1.0000000000000001e+21, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_micron', ascii_symbol='Mg micron^-3', symbol='Mgmicron⁻³') +kilograms_per_cubic_micron = NamedUnit(1.0000000000000001e+18, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_micron', ascii_symbol='kg micron^-3', symbol='kgmicron⁻³') +milligrams_per_cubic_micron = NamedUnit(1000000000000.0001, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_micron', ascii_symbol='mg micron^-3', symbol='mgmicron⁻³') +micrograms_per_cubic_micron = NamedUnit(1000000000.0000002, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_micron', ascii_symbol='ug micron^-3', symbol='µgmicron⁻³') +nanograms_per_cubic_micron = NamedUnit(1000000.0000000003, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_micron', ascii_symbol='ng micron^-3', symbol='ngmicron⁻³') +picograms_per_cubic_micron = NamedUnit(1000.0000000000002, Dimensions(length=-3, mass=1), name='picograms_per_cubic_micron', ascii_symbol='pg micron^-3', symbol='pgmicron⁻³') +femtograms_per_cubic_micron = NamedUnit(1.0000000000000002, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_micron', ascii_symbol='fg micron^-3', symbol='fgmicron⁻³') +attograms_per_cubic_micron = NamedUnit(0.0010000000000000002, Dimensions(length=-3, mass=1), name='attograms_per_cubic_micron', ascii_symbol='ag micron^-3', symbol='agmicron⁻³') +atomic_mass_units_per_cubic_micron = NamedUnit(1.660538921e-09, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_micron', ascii_symbol='au micron^-3', symbol='aumicron⁻³') +pounds_per_cubic_micron = NamedUnit(4.5359237000000006e+17, Dimensions(length=-3, mass=1), name='pounds_per_cubic_micron', ascii_symbol='lb micron^-3', symbol='lbmicron⁻³') +ounces_per_cubic_micron = NamedUnit(2.8349523125000004e+16, Dimensions(length=-3, mass=1), name='ounces_per_cubic_micron', ascii_symbol='oz micron^-3', symbol='ozmicron⁻³') +grams_per_cubic_mile = NamedUnit(2.399127585789277e-13, Dimensions(length=-3, mass=1), name='grams_per_cubic_mile', ascii_symbol='g miles^-3', symbol='gmiles⁻³') +exagrams_per_cubic_mile = NamedUnit(239912.7585789277, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_mile', ascii_symbol='Eg miles^-3', symbol='Egmiles⁻³') +petagrams_per_cubic_mile = NamedUnit(239.9127585789277, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_mile', ascii_symbol='Pg miles^-3', symbol='Pgmiles⁻³') +teragrams_per_cubic_mile = NamedUnit(0.2399127585789277, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_mile', ascii_symbol='Tg miles^-3', symbol='Tgmiles⁻³') +gigagrams_per_cubic_mile = NamedUnit(0.0002399127585789277, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_mile', ascii_symbol='Gg miles^-3', symbol='Ggmiles⁻³') +megagrams_per_cubic_mile = NamedUnit(2.399127585789277e-07, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_mile', ascii_symbol='Mg miles^-3', symbol='Mgmiles⁻³') +kilograms_per_cubic_mile = NamedUnit(2.399127585789277e-10, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_mile', ascii_symbol='kg miles^-3', symbol='kgmiles⁻³') +milligrams_per_cubic_mile = NamedUnit(2.399127585789277e-16, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_mile', ascii_symbol='mg miles^-3', symbol='mgmiles⁻³') +micrograms_per_cubic_mile = NamedUnit(2.3991275857892774e-19, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_mile', ascii_symbol='ug miles^-3', symbol='µgmiles⁻³') +nanograms_per_cubic_mile = NamedUnit(2.3991275857892774e-22, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_mile', ascii_symbol='ng miles^-3', symbol='ngmiles⁻³') +picograms_per_cubic_mile = NamedUnit(2.399127585789277e-25, Dimensions(length=-3, mass=1), name='picograms_per_cubic_mile', ascii_symbol='pg miles^-3', symbol='pgmiles⁻³') +femtograms_per_cubic_mile = NamedUnit(2.3991275857892772e-28, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_mile', ascii_symbol='fg miles^-3', symbol='fgmiles⁻³') +attograms_per_cubic_mile = NamedUnit(2.399127585789277e-31, Dimensions(length=-3, mass=1), name='attograms_per_cubic_mile', ascii_symbol='ag miles^-3', symbol='agmiles⁻³') +atomic_mass_units_per_cubic_mile = NamedUnit(3.98384473264786e-37, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_mile', ascii_symbol='au miles^-3', symbol='aumiles⁻³') +pounds_per_cubic_mile = NamedUnit(1.0882259675705365e-10, Dimensions(length=-3, mass=1), name='pounds_per_cubic_mile', ascii_symbol='lb miles^-3', symbol='lbmiles⁻³') +ounces_per_cubic_mile = NamedUnit(6.801412297315853e-12, Dimensions(length=-3, mass=1), name='ounces_per_cubic_mile', ascii_symbol='oz miles^-3', symbol='ozmiles⁻³') +grams_per_cubic_yard = NamedUnit(0.0013079506193143919, Dimensions(length=-3, mass=1), name='grams_per_cubic_yard', ascii_symbol='g yrd^-3', symbol='gyrd⁻³') +exagrams_per_cubic_yard = NamedUnit(1307950619314391.8, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_yard', ascii_symbol='Eg yrd^-3', symbol='Egyrd⁻³') +petagrams_per_cubic_yard = NamedUnit(1307950619314.3918, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_yard', ascii_symbol='Pg yrd^-3', symbol='Pgyrd⁻³') +teragrams_per_cubic_yard = NamedUnit(1307950619.3143919, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_yard', ascii_symbol='Tg yrd^-3', symbol='Tgyrd⁻³') +gigagrams_per_cubic_yard = NamedUnit(1307950.6193143919, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_yard', ascii_symbol='Gg yrd^-3', symbol='Ggyrd⁻³') +megagrams_per_cubic_yard = NamedUnit(1307.9506193143918, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_yard', ascii_symbol='Mg yrd^-3', symbol='Mgyrd⁻³') +kilograms_per_cubic_yard = NamedUnit(1.3079506193143917, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_yard', ascii_symbol='kg yrd^-3', symbol='kgyrd⁻³') +milligrams_per_cubic_yard = NamedUnit(1.3079506193143917e-06, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_yard', ascii_symbol='mg yrd^-3', symbol='mgyrd⁻³') +micrograms_per_cubic_yard = NamedUnit(1.3079506193143919e-09, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_yard', ascii_symbol='ug yrd^-3', symbol='µgyrd⁻³') +nanograms_per_cubic_yard = NamedUnit(1.307950619314392e-12, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_yard', ascii_symbol='ng yrd^-3', symbol='ngyrd⁻³') +picograms_per_cubic_yard = NamedUnit(1.3079506193143919e-15, Dimensions(length=-3, mass=1), name='picograms_per_cubic_yard', ascii_symbol='pg yrd^-3', symbol='pgyrd⁻³') +femtograms_per_cubic_yard = NamedUnit(1.3079506193143918e-18, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_yard', ascii_symbol='fg yrd^-3', symbol='fgyrd⁻³') +attograms_per_cubic_yard = NamedUnit(1.307950619314392e-21, Dimensions(length=-3, mass=1), name='attograms_per_cubic_yard', ascii_symbol='ag yrd^-3', symbol='agyrd⁻³') +atomic_mass_units_per_cubic_yard = NamedUnit(2.1719029101176016e-27, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_yard', ascii_symbol='au yrd^-3', symbol='auyrd⁻³') +pounds_per_cubic_yard = NamedUnit(0.5932764212577828, Dimensions(length=-3, mass=1), name='pounds_per_cubic_yard', ascii_symbol='lb yrd^-3', symbol='lbyrd⁻³') +ounces_per_cubic_yard = NamedUnit(0.037079776328611425, Dimensions(length=-3, mass=1), name='ounces_per_cubic_yard', ascii_symbol='oz yrd^-3', symbol='ozyrd⁻³') +grams_per_cubic_foot = NamedUnit(0.035314666721488586, Dimensions(length=-3, mass=1), name='grams_per_cubic_foot', ascii_symbol='g ft^-3', symbol='gft⁻³') +exagrams_per_cubic_foot = NamedUnit(3.5314666721488584e+16, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_foot', ascii_symbol='Eg ft^-3', symbol='Egft⁻³') +petagrams_per_cubic_foot = NamedUnit(35314666721488.586, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_foot', ascii_symbol='Pg ft^-3', symbol='Pgft⁻³') +teragrams_per_cubic_foot = NamedUnit(35314666721.48859, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_foot', ascii_symbol='Tg ft^-3', symbol='Tgft⁻³') +gigagrams_per_cubic_foot = NamedUnit(35314666.72148859, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_foot', ascii_symbol='Gg ft^-3', symbol='Ggft⁻³') +megagrams_per_cubic_foot = NamedUnit(35314.66672148858, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_foot', ascii_symbol='Mg ft^-3', symbol='Mgft⁻³') +kilograms_per_cubic_foot = NamedUnit(35.314666721488585, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_foot', ascii_symbol='kg ft^-3', symbol='kgft⁻³') +milligrams_per_cubic_foot = NamedUnit(3.5314666721488586e-05, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_foot', ascii_symbol='mg ft^-3', symbol='mgft⁻³') +micrograms_per_cubic_foot = NamedUnit(3.5314666721488584e-08, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_foot', ascii_symbol='ug ft^-3', symbol='µgft⁻³') +nanograms_per_cubic_foot = NamedUnit(3.531466672148859e-11, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_foot', ascii_symbol='ng ft^-3', symbol='ngft⁻³') +picograms_per_cubic_foot = NamedUnit(3.531466672148859e-14, Dimensions(length=-3, mass=1), name='picograms_per_cubic_foot', ascii_symbol='pg ft^-3', symbol='pgft⁻³') +femtograms_per_cubic_foot = NamedUnit(3.5314666721488585e-17, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_foot', ascii_symbol='fg ft^-3', symbol='fgft⁻³') +attograms_per_cubic_foot = NamedUnit(3.531466672148859e-20, Dimensions(length=-3, mass=1), name='attograms_per_cubic_foot', ascii_symbol='ag ft^-3', symbol='agft⁻³') +atomic_mass_units_per_cubic_foot = NamedUnit(5.864137857317526e-26, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_foot', ascii_symbol='au ft^-3', symbol='auft⁻³') +pounds_per_cubic_foot = NamedUnit(16.018463373960138, Dimensions(length=-3, mass=1), name='pounds_per_cubic_foot', ascii_symbol='lb ft^-3', symbol='lbft⁻³') +ounces_per_cubic_foot = NamedUnit(1.0011539608725086, Dimensions(length=-3, mass=1), name='ounces_per_cubic_foot', ascii_symbol='oz ft^-3', symbol='ozft⁻³') +grams_per_cubic_inch = NamedUnit(61.02374409473229, Dimensions(length=-3, mass=1), name='grams_per_cubic_inch', ascii_symbol='g in^-3', symbol='gin⁻³') +exagrams_per_cubic_inch = NamedUnit(6.102374409473229e+19, Dimensions(length=-3, mass=1), name='exagrams_per_cubic_inch', ascii_symbol='Eg in^-3', symbol='Egin⁻³') +petagrams_per_cubic_inch = NamedUnit(6.102374409473229e+16, Dimensions(length=-3, mass=1), name='petagrams_per_cubic_inch', ascii_symbol='Pg in^-3', symbol='Pgin⁻³') +teragrams_per_cubic_inch = NamedUnit(61023744094732.29, Dimensions(length=-3, mass=1), name='teragrams_per_cubic_inch', ascii_symbol='Tg in^-3', symbol='Tgin⁻³') +gigagrams_per_cubic_inch = NamedUnit(61023744094.732285, Dimensions(length=-3, mass=1), name='gigagrams_per_cubic_inch', ascii_symbol='Gg in^-3', symbol='Ggin⁻³') +megagrams_per_cubic_inch = NamedUnit(61023744.094732285, Dimensions(length=-3, mass=1), name='megagrams_per_cubic_inch', ascii_symbol='Mg in^-3', symbol='Mgin⁻³') +kilograms_per_cubic_inch = NamedUnit(61023.74409473229, Dimensions(length=-3, mass=1), name='kilograms_per_cubic_inch', ascii_symbol='kg in^-3', symbol='kgin⁻³') +milligrams_per_cubic_inch = NamedUnit(0.06102374409473228, Dimensions(length=-3, mass=1), name='milligrams_per_cubic_inch', ascii_symbol='mg in^-3', symbol='mgin⁻³') +micrograms_per_cubic_inch = NamedUnit(6.102374409473229e-05, Dimensions(length=-3, mass=1), name='micrograms_per_cubic_inch', ascii_symbol='ug in^-3', symbol='µgin⁻³') +nanograms_per_cubic_inch = NamedUnit(6.10237440947323e-08, Dimensions(length=-3, mass=1), name='nanograms_per_cubic_inch', ascii_symbol='ng in^-3', symbol='ngin⁻³') +picograms_per_cubic_inch = NamedUnit(6.102374409473229e-11, Dimensions(length=-3, mass=1), name='picograms_per_cubic_inch', ascii_symbol='pg in^-3', symbol='pgin⁻³') +femtograms_per_cubic_inch = NamedUnit(6.10237440947323e-14, Dimensions(length=-3, mass=1), name='femtograms_per_cubic_inch', ascii_symbol='fg in^-3', symbol='fgin⁻³') +attograms_per_cubic_inch = NamedUnit(6.10237440947323e-17, Dimensions(length=-3, mass=1), name='attograms_per_cubic_inch', ascii_symbol='ag in^-3', symbol='agin⁻³') +atomic_mass_units_per_cubic_inch = NamedUnit(1.0133230217444687e-22, Dimensions(length=-3, mass=1), name='atomic_mass_units_per_cubic_inch', ascii_symbol='au in^-3', symbol='auin⁻³') +pounds_per_cubic_inch = NamedUnit(27679.904710203125, Dimensions(length=-3, mass=1), name='pounds_per_cubic_inch', ascii_symbol='lb in^-3', symbol='lbin⁻³') +ounces_per_cubic_inch = NamedUnit(1729.9940443876953, Dimensions(length=-3, mass=1), name='ounces_per_cubic_inch', ascii_symbol='oz in^-3', symbol='ozin⁻³') +moles_per_cubic_meter = NamedUnit(6.02214076e+23, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_meter', ascii_symbol='mol m^-3', symbol='molm⁻³') +millimoles_per_cubic_meter = NamedUnit(6.02214076e+20, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_meter', ascii_symbol='mmol m^-3', symbol='mmolm⁻³') +micromoles_per_cubic_meter = NamedUnit(6.02214076e+17, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_meter', ascii_symbol='umol m^-3', symbol='µmolm⁻³') +nanomoles_per_cubic_meter = NamedUnit(602214076000000.0, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_meter', ascii_symbol='nmol m^-3', symbol='nmolm⁻³') +picomoles_per_cubic_meter = NamedUnit(602214076000.0, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_meter', ascii_symbol='pmol m^-3', symbol='pmolm⁻³') +femtomoles_per_cubic_meter = NamedUnit(602214076.0, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_meter', ascii_symbol='fmol m^-3', symbol='fmolm⁻³') +attomoles_per_cubic_meter = NamedUnit(602214.076, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_meter', ascii_symbol='amol m^-3', symbol='amolm⁻³') +moles_per_cubic_exameter = NamedUnit(6.0221407599999995e-31, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_exameter', ascii_symbol='mol Em^-3', symbol='molEm⁻³') +millimoles_per_cubic_exameter = NamedUnit(6.02214076e-34, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_exameter', ascii_symbol='mmol Em^-3', symbol='mmolEm⁻³') +micromoles_per_cubic_exameter = NamedUnit(6.02214076e-37, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_exameter', ascii_symbol='umol Em^-3', symbol='µmolEm⁻³') +nanomoles_per_cubic_exameter = NamedUnit(6.022140759999999e-40, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_exameter', ascii_symbol='nmol Em^-3', symbol='nmolEm⁻³') +picomoles_per_cubic_exameter = NamedUnit(6.022140759999999e-43, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_exameter', ascii_symbol='pmol Em^-3', symbol='pmolEm⁻³') +femtomoles_per_cubic_exameter = NamedUnit(6.02214076e-46, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_exameter', ascii_symbol='fmol Em^-3', symbol='fmolEm⁻³') +attomoles_per_cubic_exameter = NamedUnit(6.022140759999999e-49, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_exameter', ascii_symbol='amol Em^-3', symbol='amolEm⁻³') +moles_per_cubic_petameter = NamedUnit(6.02214076e-22, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_petameter', ascii_symbol='mol Pm^-3', symbol='molPm⁻³') +millimoles_per_cubic_petameter = NamedUnit(6.0221407600000005e-25, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_petameter', ascii_symbol='mmol Pm^-3', symbol='mmolPm⁻³') +micromoles_per_cubic_petameter = NamedUnit(6.02214076e-28, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_petameter', ascii_symbol='umol Pm^-3', symbol='µmolPm⁻³') +nanomoles_per_cubic_petameter = NamedUnit(6.02214076e-31, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_petameter', ascii_symbol='nmol Pm^-3', symbol='nmolPm⁻³') +picomoles_per_cubic_petameter = NamedUnit(6.0221407600000005e-34, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_petameter', ascii_symbol='pmol Pm^-3', symbol='pmolPm⁻³') +femtomoles_per_cubic_petameter = NamedUnit(6.022140760000001e-37, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_petameter', ascii_symbol='fmol Pm^-3', symbol='fmolPm⁻³') +attomoles_per_cubic_petameter = NamedUnit(6.022140760000001e-40, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_petameter', ascii_symbol='amol Pm^-3', symbol='amolPm⁻³') +moles_per_cubic_terameter = NamedUnit(6.022140759999999e-13, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_terameter', ascii_symbol='mol Tm^-3', symbol='molTm⁻³') +millimoles_per_cubic_terameter = NamedUnit(6.02214076e-16, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_terameter', ascii_symbol='mmol Tm^-3', symbol='mmolTm⁻³') +micromoles_per_cubic_terameter = NamedUnit(6.02214076e-19, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_terameter', ascii_symbol='umol Tm^-3', symbol='µmolTm⁻³') +nanomoles_per_cubic_terameter = NamedUnit(6.02214076e-22, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_terameter', ascii_symbol='nmol Tm^-3', symbol='nmolTm⁻³') +picomoles_per_cubic_terameter = NamedUnit(6.02214076e-25, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_terameter', ascii_symbol='pmol Tm^-3', symbol='pmolTm⁻³') +femtomoles_per_cubic_terameter = NamedUnit(6.02214076e-28, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_terameter', ascii_symbol='fmol Tm^-3', symbol='fmolTm⁻³') +attomoles_per_cubic_terameter = NamedUnit(6.0221407599999995e-31, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_terameter', ascii_symbol='amol Tm^-3', symbol='amolTm⁻³') +moles_per_cubic_gigameter = NamedUnit(0.000602214076, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_gigameter', ascii_symbol='mol Gm^-3', symbol='molGm⁻³') +millimoles_per_cubic_gigameter = NamedUnit(6.022140760000001e-07, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_gigameter', ascii_symbol='mmol Gm^-3', symbol='mmolGm⁻³') +micromoles_per_cubic_gigameter = NamedUnit(6.02214076e-10, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_gigameter', ascii_symbol='umol Gm^-3', symbol='µmolGm⁻³') +nanomoles_per_cubic_gigameter = NamedUnit(6.02214076e-13, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_gigameter', ascii_symbol='nmol Gm^-3', symbol='nmolGm⁻³') +picomoles_per_cubic_gigameter = NamedUnit(6.02214076e-16, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_gigameter', ascii_symbol='pmol Gm^-3', symbol='pmolGm⁻³') +femtomoles_per_cubic_gigameter = NamedUnit(6.02214076e-19, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_gigameter', ascii_symbol='fmol Gm^-3', symbol='fmolGm⁻³') +attomoles_per_cubic_gigameter = NamedUnit(6.02214076e-22, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_gigameter', ascii_symbol='amol Gm^-3', symbol='amolGm⁻³') +moles_per_cubic_megameter = NamedUnit(602214.076, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_megameter', ascii_symbol='mol Mm^-3', symbol='molMm⁻³') +millimoles_per_cubic_megameter = NamedUnit(602.214076, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_megameter', ascii_symbol='mmol Mm^-3', symbol='mmolMm⁻³') +micromoles_per_cubic_megameter = NamedUnit(0.602214076, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_megameter', ascii_symbol='umol Mm^-3', symbol='µmolMm⁻³') +nanomoles_per_cubic_megameter = NamedUnit(0.000602214076, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_megameter', ascii_symbol='nmol Mm^-3', symbol='nmolMm⁻³') +picomoles_per_cubic_megameter = NamedUnit(6.02214076e-07, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_megameter', ascii_symbol='pmol Mm^-3', symbol='pmolMm⁻³') +femtomoles_per_cubic_megameter = NamedUnit(6.02214076e-10, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_megameter', ascii_symbol='fmol Mm^-3', symbol='fmolMm⁻³') +attomoles_per_cubic_megameter = NamedUnit(6.02214076e-13, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_megameter', ascii_symbol='amol Mm^-3', symbol='amolMm⁻³') +moles_per_cubic_kilometer = NamedUnit(602214076000000.0, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_kilometer', ascii_symbol='mol km^-3', symbol='molkm⁻³') +millimoles_per_cubic_kilometer = NamedUnit(602214076000.0, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_kilometer', ascii_symbol='mmol km^-3', symbol='mmolkm⁻³') +micromoles_per_cubic_kilometer = NamedUnit(602214076.0, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_kilometer', ascii_symbol='umol km^-3', symbol='µmolkm⁻³') +nanomoles_per_cubic_kilometer = NamedUnit(602214.076, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_kilometer', ascii_symbol='nmol km^-3', symbol='nmolkm⁻³') +picomoles_per_cubic_kilometer = NamedUnit(602.214076, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_kilometer', ascii_symbol='pmol km^-3', symbol='pmolkm⁻³') +femtomoles_per_cubic_kilometer = NamedUnit(0.602214076, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_kilometer', ascii_symbol='fmol km^-3', symbol='fmolkm⁻³') +attomoles_per_cubic_kilometer = NamedUnit(0.000602214076, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_kilometer', ascii_symbol='amol km^-3', symbol='amolkm⁻³') +moles_per_cubic_millimeter = NamedUnit(6.0221407599999995e+32, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_millimeter', ascii_symbol='mol mm^-3', symbol='molmm⁻³') +millimoles_per_cubic_millimeter = NamedUnit(6.02214076e+29, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_millimeter', ascii_symbol='mmol mm^-3', symbol='mmolmm⁻³') +micromoles_per_cubic_millimeter = NamedUnit(6.0221407599999996e+26, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_millimeter', ascii_symbol='umol mm^-3', symbol='µmolmm⁻³') +nanomoles_per_cubic_millimeter = NamedUnit(6.02214076e+23, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_millimeter', ascii_symbol='nmol mm^-3', symbol='nmolmm⁻³') +picomoles_per_cubic_millimeter = NamedUnit(6.02214076e+20, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_millimeter', ascii_symbol='pmol mm^-3', symbol='pmolmm⁻³') +femtomoles_per_cubic_millimeter = NamedUnit(6.02214076e+17, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_millimeter', ascii_symbol='fmol mm^-3', symbol='fmolmm⁻³') +attomoles_per_cubic_millimeter = NamedUnit(602214076000000.0, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_millimeter', ascii_symbol='amol mm^-3', symbol='amolmm⁻³') +moles_per_cubic_micrometer = NamedUnit(6.022140760000001e+41, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_micrometer', ascii_symbol='mol um^-3', symbol='molµm⁻³') +millimoles_per_cubic_micrometer = NamedUnit(6.022140760000001e+38, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_micrometer', ascii_symbol='mmol um^-3', symbol='mmolµm⁻³') +micromoles_per_cubic_micrometer = NamedUnit(6.0221407600000004e+35, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_micrometer', ascii_symbol='umol um^-3', symbol='µmolµm⁻³') +nanomoles_per_cubic_micrometer = NamedUnit(6.022140760000001e+32, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_micrometer', ascii_symbol='nmol um^-3', symbol='nmolµm⁻³') +picomoles_per_cubic_micrometer = NamedUnit(6.022140760000001e+29, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_micrometer', ascii_symbol='pmol um^-3', symbol='pmolµm⁻³') +femtomoles_per_cubic_micrometer = NamedUnit(6.022140760000001e+26, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_micrometer', ascii_symbol='fmol um^-3', symbol='fmolµm⁻³') +attomoles_per_cubic_micrometer = NamedUnit(6.0221407600000005e+23, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_micrometer', ascii_symbol='amol um^-3', symbol='amolµm⁻³') +moles_per_cubic_nanometer = NamedUnit(6.022140759999999e+50, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_nanometer', ascii_symbol='mol nm^-3', symbol='molnm⁻³') +millimoles_per_cubic_nanometer = NamedUnit(6.022140759999999e+47, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_nanometer', ascii_symbol='mmol nm^-3', symbol='mmolnm⁻³') +micromoles_per_cubic_nanometer = NamedUnit(6.022140759999999e+44, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_nanometer', ascii_symbol='umol nm^-3', symbol='µmolnm⁻³') +nanomoles_per_cubic_nanometer = NamedUnit(6.022140759999998e+41, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_nanometer', ascii_symbol='nmol nm^-3', symbol='nmolnm⁻³') +picomoles_per_cubic_nanometer = NamedUnit(6.0221407599999985e+38, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_nanometer', ascii_symbol='pmol nm^-3', symbol='pmolnm⁻³') +femtomoles_per_cubic_nanometer = NamedUnit(6.022140759999999e+35, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_nanometer', ascii_symbol='fmol nm^-3', symbol='fmolnm⁻³') +attomoles_per_cubic_nanometer = NamedUnit(6.022140759999999e+32, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_nanometer', ascii_symbol='amol nm^-3', symbol='amolnm⁻³') +moles_per_cubic_picometer = NamedUnit(6.0221407600000005e+59, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_picometer', ascii_symbol='mol pm^-3', symbol='molpm⁻³') +millimoles_per_cubic_picometer = NamedUnit(6.0221407600000005e+56, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_picometer', ascii_symbol='mmol pm^-3', symbol='mmolpm⁻³') +micromoles_per_cubic_picometer = NamedUnit(6.022140760000001e+53, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_picometer', ascii_symbol='umol pm^-3', symbol='µmolpm⁻³') +nanomoles_per_cubic_picometer = NamedUnit(6.0221407600000005e+50, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_picometer', ascii_symbol='nmol pm^-3', symbol='nmolpm⁻³') +picomoles_per_cubic_picometer = NamedUnit(6.02214076e+47, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_picometer', ascii_symbol='pmol pm^-3', symbol='pmolpm⁻³') +femtomoles_per_cubic_picometer = NamedUnit(6.022140760000001e+44, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_picometer', ascii_symbol='fmol pm^-3', symbol='fmolpm⁻³') +attomoles_per_cubic_picometer = NamedUnit(6.022140760000001e+41, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_picometer', ascii_symbol='amol pm^-3', symbol='amolpm⁻³') +moles_per_cubic_femtometer = NamedUnit(6.022140759999998e+68, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_femtometer', ascii_symbol='mol fm^-3', symbol='molfm⁻³') +millimoles_per_cubic_femtometer = NamedUnit(6.022140759999998e+65, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_femtometer', ascii_symbol='mmol fm^-3', symbol='mmolfm⁻³') +micromoles_per_cubic_femtometer = NamedUnit(6.022140759999999e+62, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_femtometer', ascii_symbol='umol fm^-3', symbol='µmolfm⁻³') +nanomoles_per_cubic_femtometer = NamedUnit(6.022140759999998e+59, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_femtometer', ascii_symbol='nmol fm^-3', symbol='nmolfm⁻³') +picomoles_per_cubic_femtometer = NamedUnit(6.022140759999998e+56, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_femtometer', ascii_symbol='pmol fm^-3', symbol='pmolfm⁻³') +femtomoles_per_cubic_femtometer = NamedUnit(6.022140759999998e+53, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_femtometer', ascii_symbol='fmol fm^-3', symbol='fmolfm⁻³') +attomoles_per_cubic_femtometer = NamedUnit(6.022140759999998e+50, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_femtometer', ascii_symbol='amol fm^-3', symbol='amolfm⁻³') +moles_per_cubic_attometer = NamedUnit(6.022140759999998e+77, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_attometer', ascii_symbol='mol am^-3', symbol='molam⁻³') +millimoles_per_cubic_attometer = NamedUnit(6.022140759999999e+74, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_attometer', ascii_symbol='mmol am^-3', symbol='mmolam⁻³') +micromoles_per_cubic_attometer = NamedUnit(6.022140759999999e+71, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_attometer', ascii_symbol='umol am^-3', symbol='µmolam⁻³') +nanomoles_per_cubic_attometer = NamedUnit(6.022140759999999e+68, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_attometer', ascii_symbol='nmol am^-3', symbol='nmolam⁻³') +picomoles_per_cubic_attometer = NamedUnit(6.022140759999999e+65, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_attometer', ascii_symbol='pmol am^-3', symbol='pmolam⁻³') +femtomoles_per_cubic_attometer = NamedUnit(6.022140759999999e+62, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_attometer', ascii_symbol='fmol am^-3', symbol='fmolam⁻³') +attomoles_per_cubic_attometer = NamedUnit(6.022140759999999e+59, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_attometer', ascii_symbol='amol am^-3', symbol='amolam⁻³') +moles_per_cubic_decimeter = NamedUnit(6.022140759999998e+26, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_decimeter', ascii_symbol='mol dm^-3', symbol='moldm⁻³') +millimoles_per_cubic_decimeter = NamedUnit(6.0221407599999985e+23, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_decimeter', ascii_symbol='mmol dm^-3', symbol='mmoldm⁻³') +micromoles_per_cubic_decimeter = NamedUnit(6.022140759999999e+20, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_decimeter', ascii_symbol='umol dm^-3', symbol='µmoldm⁻³') +nanomoles_per_cubic_decimeter = NamedUnit(6.022140759999999e+17, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_decimeter', ascii_symbol='nmol dm^-3', symbol='nmoldm⁻³') +picomoles_per_cubic_decimeter = NamedUnit(602214075999999.9, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_decimeter', ascii_symbol='pmol dm^-3', symbol='pmoldm⁻³') +femtomoles_per_cubic_decimeter = NamedUnit(602214075999.9999, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_decimeter', ascii_symbol='fmol dm^-3', symbol='fmoldm⁻³') +attomoles_per_cubic_decimeter = NamedUnit(602214075.9999999, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_decimeter', ascii_symbol='amol dm^-3', symbol='amoldm⁻³') +moles_per_cubic_centimeter = NamedUnit(6.022140759999999e+29, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_centimeter', ascii_symbol='mol cm^-3', symbol='molcm⁻³') +millimoles_per_cubic_centimeter = NamedUnit(6.022140759999999e+26, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_centimeter', ascii_symbol='mmol cm^-3', symbol='mmolcm⁻³') +micromoles_per_cubic_centimeter = NamedUnit(6.022140759999999e+23, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_centimeter', ascii_symbol='umol cm^-3', symbol='µmolcm⁻³') +nanomoles_per_cubic_centimeter = NamedUnit(6.022140759999999e+20, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_centimeter', ascii_symbol='nmol cm^-3', symbol='nmolcm⁻³') +picomoles_per_cubic_centimeter = NamedUnit(6.022140759999999e+17, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_centimeter', ascii_symbol='pmol cm^-3', symbol='pmolcm⁻³') +femtomoles_per_cubic_centimeter = NamedUnit(602214075999999.9, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_centimeter', ascii_symbol='fmol cm^-3', symbol='fmolcm⁻³') +attomoles_per_cubic_centimeter = NamedUnit(602214075999.9999, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_centimeter', ascii_symbol='amol cm^-3', symbol='amolcm⁻³') +moles_per_cubic_angstrom = NamedUnit(6.022140759999999e+53, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_angstrom', ascii_symbol='mol Ang^-3', symbol='molÅ⁻³') +millimoles_per_cubic_angstrom = NamedUnit(6.02214076e+50, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_angstrom', ascii_symbol='mmol Ang^-3', symbol='mmolÅ⁻³') +micromoles_per_cubic_angstrom = NamedUnit(6.022140759999999e+47, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_angstrom', ascii_symbol='umol Ang^-3', symbol='µmolÅ⁻³') +nanomoles_per_cubic_angstrom = NamedUnit(6.02214076e+44, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_angstrom', ascii_symbol='nmol Ang^-3', symbol='nmolÅ⁻³') +picomoles_per_cubic_angstrom = NamedUnit(6.02214076e+41, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_angstrom', ascii_symbol='pmol Ang^-3', symbol='pmolÅ⁻³') +femtomoles_per_cubic_angstrom = NamedUnit(6.022140759999999e+38, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_angstrom', ascii_symbol='fmol Ang^-3', symbol='fmolÅ⁻³') +attomoles_per_cubic_angstrom = NamedUnit(6.02214076e+35, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_angstrom', ascii_symbol='amol Ang^-3', symbol='amolÅ⁻³') +moles_per_cubic_micron = NamedUnit(6.022140760000001e+41, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_micron', ascii_symbol='mol micron^-3', symbol='molmicron⁻³') +millimoles_per_cubic_micron = NamedUnit(6.022140760000001e+38, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_micron', ascii_symbol='mmol micron^-3', symbol='mmolmicron⁻³') +micromoles_per_cubic_micron = NamedUnit(6.0221407600000004e+35, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_micron', ascii_symbol='umol micron^-3', symbol='µmolmicron⁻³') +nanomoles_per_cubic_micron = NamedUnit(6.022140760000001e+32, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_micron', ascii_symbol='nmol micron^-3', symbol='nmolmicron⁻³') +picomoles_per_cubic_micron = NamedUnit(6.022140760000001e+29, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_micron', ascii_symbol='pmol micron^-3', symbol='pmolmicron⁻³') +femtomoles_per_cubic_micron = NamedUnit(6.022140760000001e+26, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_micron', ascii_symbol='fmol micron^-3', symbol='fmolmicron⁻³') +attomoles_per_cubic_micron = NamedUnit(6.0221407600000005e+23, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_micron', ascii_symbol='amol micron^-3', symbol='amolmicron⁻³') +moles_per_cubic_mile = NamedUnit(144478840228220.0, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_mile', ascii_symbol='mol miles^-3', symbol='molmiles⁻³') +millimoles_per_cubic_mile = NamedUnit(144478840228.22003, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_mile', ascii_symbol='mmol miles^-3', symbol='mmolmiles⁻³') +micromoles_per_cubic_mile = NamedUnit(144478840.22822002, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_mile', ascii_symbol='umol miles^-3', symbol='µmolmiles⁻³') +nanomoles_per_cubic_mile = NamedUnit(144478.84022822, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_mile', ascii_symbol='nmol miles^-3', symbol='nmolmiles⁻³') +picomoles_per_cubic_mile = NamedUnit(144.47884022822, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_mile', ascii_symbol='pmol miles^-3', symbol='pmolmiles⁻³') +femtomoles_per_cubic_mile = NamedUnit(0.14447884022822002, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_mile', ascii_symbol='fmol miles^-3', symbol='fmolmiles⁻³') +attomoles_per_cubic_mile = NamedUnit(0.00014447884022822003, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_mile', ascii_symbol='amol miles^-3', symbol='amolmiles⁻³') +moles_per_cubic_yard = NamedUnit(7.876662736640442e+23, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_yard', ascii_symbol='mol yrd^-3', symbol='molyrd⁻³') +millimoles_per_cubic_yard = NamedUnit(7.876662736640442e+20, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_yard', ascii_symbol='mmol yrd^-3', symbol='mmolyrd⁻³') +micromoles_per_cubic_yard = NamedUnit(7.876662736640442e+17, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_yard', ascii_symbol='umol yrd^-3', symbol='µmolyrd⁻³') +nanomoles_per_cubic_yard = NamedUnit(787666273664044.2, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_yard', ascii_symbol='nmol yrd^-3', symbol='nmolyrd⁻³') +picomoles_per_cubic_yard = NamedUnit(787666273664.0442, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_yard', ascii_symbol='pmol yrd^-3', symbol='pmolyrd⁻³') +femtomoles_per_cubic_yard = NamedUnit(787666273.6640443, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_yard', ascii_symbol='fmol yrd^-3', symbol='fmolyrd⁻³') +attomoles_per_cubic_yard = NamedUnit(787666.2736640442, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_yard', ascii_symbol='amol yrd^-3', symbol='amolyrd⁻³') +moles_per_cubic_foot = NamedUnit(2.1266989388929195e+25, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_foot', ascii_symbol='mol ft^-3', symbol='molft⁻³') +millimoles_per_cubic_foot = NamedUnit(2.1266989388929197e+22, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_foot', ascii_symbol='mmol ft^-3', symbol='mmolft⁻³') +micromoles_per_cubic_foot = NamedUnit(2.1266989388929196e+19, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_foot', ascii_symbol='umol ft^-3', symbol='µmolft⁻³') +nanomoles_per_cubic_foot = NamedUnit(2.1266989388929196e+16, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_foot', ascii_symbol='nmol ft^-3', symbol='nmolft⁻³') +picomoles_per_cubic_foot = NamedUnit(21266989388929.2, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_foot', ascii_symbol='pmol ft^-3', symbol='pmolft⁻³') +femtomoles_per_cubic_foot = NamedUnit(21266989388.9292, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_foot', ascii_symbol='fmol ft^-3', symbol='fmolft⁻³') +attomoles_per_cubic_foot = NamedUnit(21266989.388929196, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_foot', ascii_symbol='amol ft^-3', symbol='amolft⁻³') +moles_per_cubic_inch = NamedUnit(3.6749357664069658e+28, Dimensions(length=-3, moles_hint=1), name='moles_per_cubic_inch', ascii_symbol='mol in^-3', symbol='molin⁻³') +millimoles_per_cubic_inch = NamedUnit(3.674935766406966e+25, Dimensions(length=-3, moles_hint=1), name='millimoles_per_cubic_inch', ascii_symbol='mmol in^-3', symbol='mmolin⁻³') +micromoles_per_cubic_inch = NamedUnit(3.674935766406966e+22, Dimensions(length=-3, moles_hint=1), name='micromoles_per_cubic_inch', ascii_symbol='umol in^-3', symbol='µmolin⁻³') +nanomoles_per_cubic_inch = NamedUnit(3.674935766406966e+19, Dimensions(length=-3, moles_hint=1), name='nanomoles_per_cubic_inch', ascii_symbol='nmol in^-3', symbol='nmolin⁻³') +picomoles_per_cubic_inch = NamedUnit(3.674935766406966e+16, Dimensions(length=-3, moles_hint=1), name='picomoles_per_cubic_inch', ascii_symbol='pmol in^-3', symbol='pmolin⁻³') +femtomoles_per_cubic_inch = NamedUnit(36749357664069.664, Dimensions(length=-3, moles_hint=1), name='femtomoles_per_cubic_inch', ascii_symbol='fmol in^-3', symbol='fmolin⁻³') +attomoles_per_cubic_inch = NamedUnit(36749357664.069664, Dimensions(length=-3, moles_hint=1), name='attomoles_per_cubic_inch', ascii_symbol='amol in^-3', symbol='amolin⁻³') + +# +# Lookup table from symbols to units +# + +symbol_lookup = { + "m": meters, + "Em": exameters, + "Pm": petameters, + "Tm": terameters, + "Gm": gigameters, + "Mm": megameters, + "km": kilometers, + "mm": millimeters, + "um": micrometers, + "µm": micrometers, + "nm": nanometers, + "pm": picometers, + "fm": femtometers, + "am": attometers, + "dm": decimeters, + "cm": centimeters, + "s": seconds, + "ms": milliseconds, + "us": microseconds, + "µs": microseconds, + "ns": nanoseconds, + "ps": picoseconds, + "fs": femtoseconds, + "as": attoseconds, + "g": grams, + "Eg": exagrams, + "Pg": petagrams, + "Tg": teragrams, + "Gg": gigagrams, + "Mg": megagrams, + "kg": kilograms, + "mg": milligrams, + "ug": micrograms, + "µg": micrograms, + "ng": nanograms, + "pg": picograms, + "fg": femtograms, + "ag": attograms, + "A": angstroms, + "EA": exaamperes, + "PA": petaamperes, + "TA": teraamperes, + "GA": gigaamperes, + "MA": megaamperes, + "kA": kiloamperes, + "mA": milliamperes, + "uA": microamperes, + "µA": microamperes, + "nA": nanoamperes, + "pA": picoamperes, + "fA": femtoamperes, + "aA": attoamperes, + "K": kelvin, + "EK": exakelvin, + "PK": petakelvin, + "TK": terakelvin, + "GK": gigakelvin, + "MK": megakelvin, + "kK": kilokelvin, + "mK": millikelvin, + "uK": microkelvin, + "µK": microkelvin, + "nK": nanokelvin, + "pK": picokelvin, + "fK": femtokelvin, + "aK": attokelvin, + "Hz": hertz, + "EHz": exahertz, + "PHz": petahertz, + "THz": terahertz, + "GHz": gigahertz, + "MHz": megahertz, + "kHz": kilohertz, + "mHz": millihertz, + "uHz": microhertz, + "µHz": microhertz, + "nHz": nanohertz, + "pHz": picohertz, + "fHz": femtohertz, + "aHz": attohertz, + "N": newtons, + "EN": exanewtons, + "PN": petanewtons, + "TN": teranewtons, + "GN": giganewtons, + "MN": meganewtons, + "kN": kilonewtons, + "mN": millinewtons, + "uN": micronewtons, + "µN": micronewtons, + "nN": nanonewtons, + "pN": piconewtons, + "fN": femtonewtons, + "aN": attonewtons, + "Pa": pascals, + "EPa": exapascals, + "PPa": petapascals, + "TPa": terapascals, + "GPa": gigapascals, + "MPa": megapascals, + "kPa": kilopascals, + "mPa": millipascals, + "uPa": micropascals, + "µPa": micropascals, + "nPa": nanopascals, + "pPa": picopascals, + "fPa": femtopascals, + "aPa": attopascals, + "J": joules, + "EJ": exajoules, + "PJ": petajoules, + "TJ": terajoules, + "GJ": gigajoules, + "MJ": megajoules, + "kJ": kilojoules, + "mJ": millijoules, + "uJ": microjoules, + "µJ": microjoules, + "nJ": nanojoules, + "pJ": picojoules, + "fJ": femtojoules, + "aJ": attojoules, + "W": watts, + "EW": exawatts, + "PW": petawatts, + "TW": terawatts, + "GW": gigawatts, + "MW": megawatts, + "kW": kilowatts, + "mW": milliwatts, + "uW": microwatts, + "µW": microwatts, + "nW": nanowatts, + "pW": picowatts, + "fW": femtowatts, + "aW": attowatts, + "C": kelvin, + "EC": exacoulombs, + "PC": petacoulombs, + "TC": teracoulombs, + "GC": gigacoulombs, + "MC": megacoulombs, + "kC": kilocoulombs, + "mC": millicoulombs, + "uC": microcoulombs, + "µC": microcoulombs, + "nC": nanocoulombs, + "pC": picocoulombs, + "fC": femtocoulombs, + "aC": attocoulombs, + "V": volts, + "EV": exavolts, + "PV": petavolts, + "TV": teravolts, + "GV": gigavolts, + "MV": megavolts, + "kV": kilovolts, + "mV": millivolts, + "uV": microvolts, + "µV": microvolts, + "nV": nanovolts, + "pV": picovolts, + "fV": femtovolts, + "aV": attovolts, + "Ohm": ohms, + "Ω": ohms, + "EOhm": exaohms, + "EΩ": exaohms, + "POhm": petaohms, + "PΩ": petaohms, + "TOhm": teraohms, + "TΩ": teraohms, + "GOhm": gigaohms, + "GΩ": gigaohms, + "MOhm": megaohms, + "MΩ": megaohms, + "kOhm": kiloohms, + "kΩ": kiloohms, + "mOhm": milliohms, + "mΩ": milliohms, + "uOhm": microohms, + "µΩ": microohms, + "nOhm": nanoohms, + "nΩ": nanoohms, + "pOhm": picoohms, + "pΩ": picoohms, + "fOhm": femtoohms, + "fΩ": femtoohms, + "aOhm": attoohms, + "aΩ": attoohms, + "F": farads, + "EF": exafarads, + "PF": petafarads, + "TF": terafarads, + "GF": gigafarads, + "MF": megafarads, + "kF": kilofarads, + "mF": millifarads, + "uF": microfarads, + "µF": microfarads, + "nF": nanofarads, + "pF": picofarads, + "fF": femtofarads, + "aF": attofarads, + "S": siemens, + "ES": exasiemens, + "PS": petasiemens, + "TS": terasiemens, + "GS": gigasiemens, + "MS": megasiemens, + "kS": kilosiemens, + "mS": millisiemens, + "uS": microsiemens, + "µS": microsiemens, + "nS": nanosiemens, + "pS": picosiemens, + "fS": femtosiemens, + "aS": attosiemens, + "Wb": webers, + "EWb": exawebers, + "PWb": petawebers, + "TWb": terawebers, + "GWb": gigawebers, + "MWb": megawebers, + "kWb": kilowebers, + "mWb": milliwebers, + "uWb": microwebers, + "µWb": microwebers, + "nWb": nanowebers, + "pWb": picowebers, + "fWb": femtowebers, + "aWb": attowebers, + "T": tesla, + "ET": exatesla, + "PT": petatesla, + "TT": teratesla, + "GT": gigatesla, + "MT": megatesla, + "kT": kilotesla, + "mT": millitesla, + "uT": microtesla, + "µT": microtesla, + "nT": nanotesla, + "pT": picotesla, + "fT": femtotesla, + "aT": attotesla, + "H": henry, + "EH": exahenry, + "PH": petahenry, + "TH": terahenry, + "GH": gigahenry, + "MH": megahenry, + "kH": kilohenry, + "mH": millihenry, + "uH": microhenry, + "µH": microhenry, + "nH": nanohenry, + "pH": picohenry, + "fH": femtohenry, + "aH": attohenry, + "Ang": angstroms, + "Å": angstroms, + "micron": microns, + "min": minutes, + "h": hours, + "d": days, + "y": years, + "deg": degrees, + "rad": radians, + "sr": stradians, + "l": litres, + "eV": electronvolts, + "EeV": exaelectronvolts, + "PeV": petaelectronvolts, + "TeV": teraelectronvolts, + "GeV": gigaelectronvolts, + "MeV": megaelectronvolts, + "keV": kiloelectronvolts, + "meV": millielectronvolts, + "ueV": microelectronvolts, + "µeV": microelectronvolts, + "neV": nanoelectronvolts, + "peV": picoelectronvolts, + "feV": femtoelectronvolts, + "aeV": attoelectronvolts, + "au": atomic_mass_units, + "mol": moles, + "mmol": millimoles, + "umol": micromoles, + "µmol": micromoles, + "nmol": nanomoles, + "pmol": picomoles, + "fmol": femtomoles, + "amol": attomoles, + "kgForce": kg_force, + "miles": miles, + "yrd": yards, + "ft": feet, + "in": inches, + "lb": pounds, + "lbf": pounds_force, + "oz": ounces, + "psi": pounds_force_per_square_inch, + "percent": percent, + "%": percent, + "Amps": amperes, + "amps": amperes, + "Coulombs": degrees_celsius, + "coulombs": degrees_celsius, + "yr": years, + "year": years, + "day": days, + "hr": hours, + "hour": hours, + "amu": atomic_mass_units, + "degr": degrees, + "Deg": degrees, + "degree": degrees, + "degrees": degrees, + "Degrees": degrees, + "Counts": none, + "counts": none, + "cnts": none, + "Cnts": none, + "a.u.": none, + "fraction": none, + "Fraction": none, +} + + +# +# Units by type +# + + +length = UnitGroup( + name = 'length', + units = [ + meters, + exameters, + petameters, + terameters, + gigameters, + megameters, + kilometers, + millimeters, + micrometers, + nanometers, + picometers, + femtometers, + attometers, + decimeters, + centimeters, + angstroms, + microns, + miles, + yards, + feet, + inches, +]) + +area = UnitGroup( + name = 'area', + units = [ + square_meters, + square_exameters, + square_petameters, + square_terameters, + square_gigameters, + square_megameters, + square_kilometers, + square_millimeters, + square_micrometers, + square_nanometers, + square_picometers, + square_femtometers, + square_attometers, + square_decimeters, + square_centimeters, + square_angstroms, + square_microns, + square_miles, + square_yards, + square_feet, + square_inches, +]) + +volume = UnitGroup( + name = 'volume', + units = [ + litres, + cubic_meters, + cubic_exameters, + cubic_petameters, + cubic_terameters, + cubic_gigameters, + cubic_megameters, + cubic_kilometers, + cubic_millimeters, + cubic_micrometers, + cubic_nanometers, + cubic_picometers, + cubic_femtometers, + cubic_attometers, + cubic_decimeters, + cubic_centimeters, + cubic_angstroms, + cubic_microns, + cubic_miles, + cubic_yards, + cubic_feet, + cubic_inches, +]) + +inverse_length = UnitGroup( + name = 'inverse_length', + units = [ + per_meter, + per_exameter, + per_petameter, + per_terameter, + per_gigameter, + per_megameter, + per_kilometer, + per_millimeter, + per_micrometer, + per_nanometer, + per_picometer, + per_femtometer, + per_attometer, + per_decimeter, + per_centimeter, + per_angstrom, + per_micron, + per_mile, + per_yard, + per_foot, + per_inch, +]) + +inverse_area = UnitGroup( + name = 'inverse_area', + units = [ + per_square_meter, + per_square_exameter, + per_square_petameter, + per_square_terameter, + per_square_gigameter, + per_square_megameter, + per_square_kilometer, + per_square_millimeter, + per_square_micrometer, + per_square_nanometer, + per_square_picometer, + per_square_femtometer, + per_square_attometer, + per_square_decimeter, + per_square_centimeter, + per_square_angstrom, + per_square_micron, + per_square_mile, + per_square_yard, + per_square_foot, + per_square_inch, +]) + +inverse_volume = UnitGroup( + name = 'inverse_volume', + units = [ + per_cubic_meter, + per_cubic_exameter, + per_cubic_petameter, + per_cubic_terameter, + per_cubic_gigameter, + per_cubic_megameter, + per_cubic_kilometer, + per_cubic_millimeter, + per_cubic_micrometer, + per_cubic_nanometer, + per_cubic_picometer, + per_cubic_femtometer, + per_cubic_attometer, + per_cubic_decimeter, + per_cubic_centimeter, + per_cubic_angstrom, + per_cubic_micron, + per_cubic_mile, + per_cubic_yard, + per_cubic_foot, + per_cubic_inch, +]) + +time = UnitGroup( + name = 'time', + units = [ + seconds, + milliseconds, + microseconds, + nanoseconds, + picoseconds, + femtoseconds, + attoseconds, + minutes, + hours, + days, + years, +]) + +rate = UnitGroup( + name = 'rate', + units = [ + hertz, + exahertz, + petahertz, + terahertz, + gigahertz, + megahertz, + kilohertz, + millihertz, + microhertz, + nanohertz, + picohertz, + femtohertz, + attohertz, +]) + +speed = UnitGroup( + name = 'speed', + units = [ + meters_per_second, + meters_per_millisecond, + meters_per_microsecond, + meters_per_nanosecond, + meters_per_picosecond, + meters_per_femtosecond, + meters_per_attosecond, + meters_per_minute, + meters_per_hour, + meters_per_day, + meters_per_year, + exameters_per_second, + exameters_per_millisecond, + exameters_per_microsecond, + exameters_per_nanosecond, + exameters_per_picosecond, + exameters_per_femtosecond, + exameters_per_attosecond, + exameters_per_minute, + exameters_per_hour, + exameters_per_day, + exameters_per_year, + petameters_per_second, + petameters_per_millisecond, + petameters_per_microsecond, + petameters_per_nanosecond, + petameters_per_picosecond, + petameters_per_femtosecond, + petameters_per_attosecond, + petameters_per_minute, + petameters_per_hour, + petameters_per_day, + petameters_per_year, + terameters_per_second, + terameters_per_millisecond, + terameters_per_microsecond, + terameters_per_nanosecond, + terameters_per_picosecond, + terameters_per_femtosecond, + terameters_per_attosecond, + terameters_per_minute, + terameters_per_hour, + terameters_per_day, + terameters_per_year, + gigameters_per_second, + gigameters_per_millisecond, + gigameters_per_microsecond, + gigameters_per_nanosecond, + gigameters_per_picosecond, + gigameters_per_femtosecond, + gigameters_per_attosecond, + gigameters_per_minute, + gigameters_per_hour, + gigameters_per_day, + gigameters_per_year, + megameters_per_second, + megameters_per_millisecond, + megameters_per_microsecond, + megameters_per_nanosecond, + megameters_per_picosecond, + megameters_per_femtosecond, + megameters_per_attosecond, + megameters_per_minute, + megameters_per_hour, + megameters_per_day, + megameters_per_year, + kilometers_per_second, + kilometers_per_millisecond, + kilometers_per_microsecond, + kilometers_per_nanosecond, + kilometers_per_picosecond, + kilometers_per_femtosecond, + kilometers_per_attosecond, + kilometers_per_minute, + kilometers_per_hour, + kilometers_per_day, + kilometers_per_year, + millimeters_per_second, + millimeters_per_millisecond, + millimeters_per_microsecond, + millimeters_per_nanosecond, + millimeters_per_picosecond, + millimeters_per_femtosecond, + millimeters_per_attosecond, + millimeters_per_minute, + millimeters_per_hour, + millimeters_per_day, + millimeters_per_year, + micrometers_per_second, + micrometers_per_millisecond, + micrometers_per_microsecond, + micrometers_per_nanosecond, + micrometers_per_picosecond, + micrometers_per_femtosecond, + micrometers_per_attosecond, + micrometers_per_minute, + micrometers_per_hour, + micrometers_per_day, + micrometers_per_year, + nanometers_per_second, + nanometers_per_millisecond, + nanometers_per_microsecond, + nanometers_per_nanosecond, + nanometers_per_picosecond, + nanometers_per_femtosecond, + nanometers_per_attosecond, + nanometers_per_minute, + nanometers_per_hour, + nanometers_per_day, + nanometers_per_year, + picometers_per_second, + picometers_per_millisecond, + picometers_per_microsecond, + picometers_per_nanosecond, + picometers_per_picosecond, + picometers_per_femtosecond, + picometers_per_attosecond, + picometers_per_minute, + picometers_per_hour, + picometers_per_day, + picometers_per_year, + femtometers_per_second, + femtometers_per_millisecond, + femtometers_per_microsecond, + femtometers_per_nanosecond, + femtometers_per_picosecond, + femtometers_per_femtosecond, + femtometers_per_attosecond, + femtometers_per_minute, + femtometers_per_hour, + femtometers_per_day, + femtometers_per_year, + attometers_per_second, + attometers_per_millisecond, + attometers_per_microsecond, + attometers_per_nanosecond, + attometers_per_picosecond, + attometers_per_femtosecond, + attometers_per_attosecond, + attometers_per_minute, + attometers_per_hour, + attometers_per_day, + attometers_per_year, + decimeters_per_second, + decimeters_per_millisecond, + decimeters_per_microsecond, + decimeters_per_nanosecond, + decimeters_per_picosecond, + decimeters_per_femtosecond, + decimeters_per_attosecond, + decimeters_per_minute, + decimeters_per_hour, + decimeters_per_day, + decimeters_per_year, + centimeters_per_second, + centimeters_per_millisecond, + centimeters_per_microsecond, + centimeters_per_nanosecond, + centimeters_per_picosecond, + centimeters_per_femtosecond, + centimeters_per_attosecond, + centimeters_per_minute, + centimeters_per_hour, + centimeters_per_day, + centimeters_per_year, + angstroms_per_second, + angstroms_per_millisecond, + angstroms_per_microsecond, + angstroms_per_nanosecond, + angstroms_per_picosecond, + angstroms_per_femtosecond, + angstroms_per_attosecond, + angstroms_per_minute, + angstroms_per_hour, + angstroms_per_day, + angstroms_per_year, + microns_per_second, + microns_per_millisecond, + microns_per_microsecond, + microns_per_nanosecond, + microns_per_picosecond, + microns_per_femtosecond, + microns_per_attosecond, + microns_per_minute, + microns_per_hour, + microns_per_day, + microns_per_year, + miles_per_second, + miles_per_millisecond, + miles_per_microsecond, + miles_per_nanosecond, + miles_per_picosecond, + miles_per_femtosecond, + miles_per_attosecond, + miles_per_minute, + miles_per_hour, + miles_per_day, + miles_per_year, + yards_per_second, + yards_per_millisecond, + yards_per_microsecond, + yards_per_nanosecond, + yards_per_picosecond, + yards_per_femtosecond, + yards_per_attosecond, + yards_per_minute, + yards_per_hour, + yards_per_day, + yards_per_year, + feet_per_second, + feet_per_millisecond, + feet_per_microsecond, + feet_per_nanosecond, + feet_per_picosecond, + feet_per_femtosecond, + feet_per_attosecond, + feet_per_minute, + feet_per_hour, + feet_per_day, + feet_per_year, + inches_per_second, + inches_per_millisecond, + inches_per_microsecond, + inches_per_nanosecond, + inches_per_picosecond, + inches_per_femtosecond, + inches_per_attosecond, + inches_per_minute, + inches_per_hour, + inches_per_day, + inches_per_year, +]) + +acceleration = UnitGroup( + name = 'acceleration', + units = [ + meters_per_square_second, + meters_per_square_millisecond, + meters_per_square_microsecond, + meters_per_square_nanosecond, + meters_per_square_picosecond, + meters_per_square_femtosecond, + meters_per_square_attosecond, + meters_per_square_minute, + meters_per_square_hour, + meters_per_square_day, + meters_per_square_year, + exameters_per_square_second, + exameters_per_square_millisecond, + exameters_per_square_microsecond, + exameters_per_square_nanosecond, + exameters_per_square_picosecond, + exameters_per_square_femtosecond, + exameters_per_square_attosecond, + exameters_per_square_minute, + exameters_per_square_hour, + exameters_per_square_day, + exameters_per_square_year, + petameters_per_square_second, + petameters_per_square_millisecond, + petameters_per_square_microsecond, + petameters_per_square_nanosecond, + petameters_per_square_picosecond, + petameters_per_square_femtosecond, + petameters_per_square_attosecond, + petameters_per_square_minute, + petameters_per_square_hour, + petameters_per_square_day, + petameters_per_square_year, + terameters_per_square_second, + terameters_per_square_millisecond, + terameters_per_square_microsecond, + terameters_per_square_nanosecond, + terameters_per_square_picosecond, + terameters_per_square_femtosecond, + terameters_per_square_attosecond, + terameters_per_square_minute, + terameters_per_square_hour, + terameters_per_square_day, + terameters_per_square_year, + gigameters_per_square_second, + gigameters_per_square_millisecond, + gigameters_per_square_microsecond, + gigameters_per_square_nanosecond, + gigameters_per_square_picosecond, + gigameters_per_square_femtosecond, + gigameters_per_square_attosecond, + gigameters_per_square_minute, + gigameters_per_square_hour, + gigameters_per_square_day, + gigameters_per_square_year, + megameters_per_square_second, + megameters_per_square_millisecond, + megameters_per_square_microsecond, + megameters_per_square_nanosecond, + megameters_per_square_picosecond, + megameters_per_square_femtosecond, + megameters_per_square_attosecond, + megameters_per_square_minute, + megameters_per_square_hour, + megameters_per_square_day, + megameters_per_square_year, + kilometers_per_square_second, + kilometers_per_square_millisecond, + kilometers_per_square_microsecond, + kilometers_per_square_nanosecond, + kilometers_per_square_picosecond, + kilometers_per_square_femtosecond, + kilometers_per_square_attosecond, + kilometers_per_square_minute, + kilometers_per_square_hour, + kilometers_per_square_day, + kilometers_per_square_year, + millimeters_per_square_second, + millimeters_per_square_millisecond, + millimeters_per_square_microsecond, + millimeters_per_square_nanosecond, + millimeters_per_square_picosecond, + millimeters_per_square_femtosecond, + millimeters_per_square_attosecond, + millimeters_per_square_minute, + millimeters_per_square_hour, + millimeters_per_square_day, + millimeters_per_square_year, + micrometers_per_square_second, + micrometers_per_square_millisecond, + micrometers_per_square_microsecond, + micrometers_per_square_nanosecond, + micrometers_per_square_picosecond, + micrometers_per_square_femtosecond, + micrometers_per_square_attosecond, + micrometers_per_square_minute, + micrometers_per_square_hour, + micrometers_per_square_day, + micrometers_per_square_year, + nanometers_per_square_second, + nanometers_per_square_millisecond, + nanometers_per_square_microsecond, + nanometers_per_square_nanosecond, + nanometers_per_square_picosecond, + nanometers_per_square_femtosecond, + nanometers_per_square_attosecond, + nanometers_per_square_minute, + nanometers_per_square_hour, + nanometers_per_square_day, + nanometers_per_square_year, + picometers_per_square_second, + picometers_per_square_millisecond, + picometers_per_square_microsecond, + picometers_per_square_nanosecond, + picometers_per_square_picosecond, + picometers_per_square_femtosecond, + picometers_per_square_attosecond, + picometers_per_square_minute, + picometers_per_square_hour, + picometers_per_square_day, + picometers_per_square_year, + femtometers_per_square_second, + femtometers_per_square_millisecond, + femtometers_per_square_microsecond, + femtometers_per_square_nanosecond, + femtometers_per_square_picosecond, + femtometers_per_square_femtosecond, + femtometers_per_square_attosecond, + femtometers_per_square_minute, + femtometers_per_square_hour, + femtometers_per_square_day, + femtometers_per_square_year, + attometers_per_square_second, + attometers_per_square_millisecond, + attometers_per_square_microsecond, + attometers_per_square_nanosecond, + attometers_per_square_picosecond, + attometers_per_square_femtosecond, + attometers_per_square_attosecond, + attometers_per_square_minute, + attometers_per_square_hour, + attometers_per_square_day, + attometers_per_square_year, + decimeters_per_square_second, + decimeters_per_square_millisecond, + decimeters_per_square_microsecond, + decimeters_per_square_nanosecond, + decimeters_per_square_picosecond, + decimeters_per_square_femtosecond, + decimeters_per_square_attosecond, + decimeters_per_square_minute, + decimeters_per_square_hour, + decimeters_per_square_day, + decimeters_per_square_year, + centimeters_per_square_second, + centimeters_per_square_millisecond, + centimeters_per_square_microsecond, + centimeters_per_square_nanosecond, + centimeters_per_square_picosecond, + centimeters_per_square_femtosecond, + centimeters_per_square_attosecond, + centimeters_per_square_minute, + centimeters_per_square_hour, + centimeters_per_square_day, + centimeters_per_square_year, + angstroms_per_square_second, + angstroms_per_square_millisecond, + angstroms_per_square_microsecond, + angstroms_per_square_nanosecond, + angstroms_per_square_picosecond, + angstroms_per_square_femtosecond, + angstroms_per_square_attosecond, + angstroms_per_square_minute, + angstroms_per_square_hour, + angstroms_per_square_day, + angstroms_per_square_year, + microns_per_square_second, + microns_per_square_millisecond, + microns_per_square_microsecond, + microns_per_square_nanosecond, + microns_per_square_picosecond, + microns_per_square_femtosecond, + microns_per_square_attosecond, + microns_per_square_minute, + microns_per_square_hour, + microns_per_square_day, + microns_per_square_year, + miles_per_square_second, + miles_per_square_millisecond, + miles_per_square_microsecond, + miles_per_square_nanosecond, + miles_per_square_picosecond, + miles_per_square_femtosecond, + miles_per_square_attosecond, + miles_per_square_minute, + miles_per_square_hour, + miles_per_square_day, + miles_per_square_year, + yards_per_square_second, + yards_per_square_millisecond, + yards_per_square_microsecond, + yards_per_square_nanosecond, + yards_per_square_picosecond, + yards_per_square_femtosecond, + yards_per_square_attosecond, + yards_per_square_minute, + yards_per_square_hour, + yards_per_square_day, + yards_per_square_year, + feet_per_square_second, + feet_per_square_millisecond, + feet_per_square_microsecond, + feet_per_square_nanosecond, + feet_per_square_picosecond, + feet_per_square_femtosecond, + feet_per_square_attosecond, + feet_per_square_minute, + feet_per_square_hour, + feet_per_square_day, + feet_per_square_year, + inches_per_square_second, + inches_per_square_millisecond, + inches_per_square_microsecond, + inches_per_square_nanosecond, + inches_per_square_picosecond, + inches_per_square_femtosecond, + inches_per_square_attosecond, + inches_per_square_minute, + inches_per_square_hour, + inches_per_square_day, + inches_per_square_year, +]) + +density = UnitGroup( + name = 'density', + units = [ + grams_per_cubic_meter, + exagrams_per_cubic_meter, + petagrams_per_cubic_meter, + teragrams_per_cubic_meter, + gigagrams_per_cubic_meter, + megagrams_per_cubic_meter, + kilograms_per_cubic_meter, + milligrams_per_cubic_meter, + micrograms_per_cubic_meter, + nanograms_per_cubic_meter, + picograms_per_cubic_meter, + femtograms_per_cubic_meter, + attograms_per_cubic_meter, + atomic_mass_units_per_cubic_meter, + pounds_per_cubic_meter, + ounces_per_cubic_meter, + grams_per_cubic_exameter, + exagrams_per_cubic_exameter, + petagrams_per_cubic_exameter, + teragrams_per_cubic_exameter, + gigagrams_per_cubic_exameter, + megagrams_per_cubic_exameter, + kilograms_per_cubic_exameter, + milligrams_per_cubic_exameter, + micrograms_per_cubic_exameter, + nanograms_per_cubic_exameter, + picograms_per_cubic_exameter, + femtograms_per_cubic_exameter, + attograms_per_cubic_exameter, + atomic_mass_units_per_cubic_exameter, + pounds_per_cubic_exameter, + ounces_per_cubic_exameter, + grams_per_cubic_petameter, + exagrams_per_cubic_petameter, + petagrams_per_cubic_petameter, + teragrams_per_cubic_petameter, + gigagrams_per_cubic_petameter, + megagrams_per_cubic_petameter, + kilograms_per_cubic_petameter, + milligrams_per_cubic_petameter, + micrograms_per_cubic_petameter, + nanograms_per_cubic_petameter, + picograms_per_cubic_petameter, + femtograms_per_cubic_petameter, + attograms_per_cubic_petameter, + atomic_mass_units_per_cubic_petameter, + pounds_per_cubic_petameter, + ounces_per_cubic_petameter, + grams_per_cubic_terameter, + exagrams_per_cubic_terameter, + petagrams_per_cubic_terameter, + teragrams_per_cubic_terameter, + gigagrams_per_cubic_terameter, + megagrams_per_cubic_terameter, + kilograms_per_cubic_terameter, + milligrams_per_cubic_terameter, + micrograms_per_cubic_terameter, + nanograms_per_cubic_terameter, + picograms_per_cubic_terameter, + femtograms_per_cubic_terameter, + attograms_per_cubic_terameter, + atomic_mass_units_per_cubic_terameter, + pounds_per_cubic_terameter, + ounces_per_cubic_terameter, + grams_per_cubic_gigameter, + exagrams_per_cubic_gigameter, + petagrams_per_cubic_gigameter, + teragrams_per_cubic_gigameter, + gigagrams_per_cubic_gigameter, + megagrams_per_cubic_gigameter, + kilograms_per_cubic_gigameter, + milligrams_per_cubic_gigameter, + micrograms_per_cubic_gigameter, + nanograms_per_cubic_gigameter, + picograms_per_cubic_gigameter, + femtograms_per_cubic_gigameter, + attograms_per_cubic_gigameter, + atomic_mass_units_per_cubic_gigameter, + pounds_per_cubic_gigameter, + ounces_per_cubic_gigameter, + grams_per_cubic_megameter, + exagrams_per_cubic_megameter, + petagrams_per_cubic_megameter, + teragrams_per_cubic_megameter, + gigagrams_per_cubic_megameter, + megagrams_per_cubic_megameter, + kilograms_per_cubic_megameter, + milligrams_per_cubic_megameter, + micrograms_per_cubic_megameter, + nanograms_per_cubic_megameter, + picograms_per_cubic_megameter, + femtograms_per_cubic_megameter, + attograms_per_cubic_megameter, + atomic_mass_units_per_cubic_megameter, + pounds_per_cubic_megameter, + ounces_per_cubic_megameter, + grams_per_cubic_kilometer, + exagrams_per_cubic_kilometer, + petagrams_per_cubic_kilometer, + teragrams_per_cubic_kilometer, + gigagrams_per_cubic_kilometer, + megagrams_per_cubic_kilometer, + kilograms_per_cubic_kilometer, + milligrams_per_cubic_kilometer, + micrograms_per_cubic_kilometer, + nanograms_per_cubic_kilometer, + picograms_per_cubic_kilometer, + femtograms_per_cubic_kilometer, + attograms_per_cubic_kilometer, + atomic_mass_units_per_cubic_kilometer, + pounds_per_cubic_kilometer, + ounces_per_cubic_kilometer, + grams_per_cubic_millimeter, + exagrams_per_cubic_millimeter, + petagrams_per_cubic_millimeter, + teragrams_per_cubic_millimeter, + gigagrams_per_cubic_millimeter, + megagrams_per_cubic_millimeter, + kilograms_per_cubic_millimeter, + milligrams_per_cubic_millimeter, + micrograms_per_cubic_millimeter, + nanograms_per_cubic_millimeter, + picograms_per_cubic_millimeter, + femtograms_per_cubic_millimeter, + attograms_per_cubic_millimeter, + atomic_mass_units_per_cubic_millimeter, + pounds_per_cubic_millimeter, + ounces_per_cubic_millimeter, + grams_per_cubic_micrometer, + exagrams_per_cubic_micrometer, + petagrams_per_cubic_micrometer, + teragrams_per_cubic_micrometer, + gigagrams_per_cubic_micrometer, + megagrams_per_cubic_micrometer, + kilograms_per_cubic_micrometer, + milligrams_per_cubic_micrometer, + micrograms_per_cubic_micrometer, + nanograms_per_cubic_micrometer, + picograms_per_cubic_micrometer, + femtograms_per_cubic_micrometer, + attograms_per_cubic_micrometer, + atomic_mass_units_per_cubic_micrometer, + pounds_per_cubic_micrometer, + ounces_per_cubic_micrometer, + grams_per_cubic_nanometer, + exagrams_per_cubic_nanometer, + petagrams_per_cubic_nanometer, + teragrams_per_cubic_nanometer, + gigagrams_per_cubic_nanometer, + megagrams_per_cubic_nanometer, + kilograms_per_cubic_nanometer, + milligrams_per_cubic_nanometer, + micrograms_per_cubic_nanometer, + nanograms_per_cubic_nanometer, + picograms_per_cubic_nanometer, + femtograms_per_cubic_nanometer, + attograms_per_cubic_nanometer, + atomic_mass_units_per_cubic_nanometer, + pounds_per_cubic_nanometer, + ounces_per_cubic_nanometer, + grams_per_cubic_picometer, + exagrams_per_cubic_picometer, + petagrams_per_cubic_picometer, + teragrams_per_cubic_picometer, + gigagrams_per_cubic_picometer, + megagrams_per_cubic_picometer, + kilograms_per_cubic_picometer, + milligrams_per_cubic_picometer, + micrograms_per_cubic_picometer, + nanograms_per_cubic_picometer, + picograms_per_cubic_picometer, + femtograms_per_cubic_picometer, + attograms_per_cubic_picometer, + atomic_mass_units_per_cubic_picometer, + pounds_per_cubic_picometer, + ounces_per_cubic_picometer, + grams_per_cubic_femtometer, + exagrams_per_cubic_femtometer, + petagrams_per_cubic_femtometer, + teragrams_per_cubic_femtometer, + gigagrams_per_cubic_femtometer, + megagrams_per_cubic_femtometer, + kilograms_per_cubic_femtometer, + milligrams_per_cubic_femtometer, + micrograms_per_cubic_femtometer, + nanograms_per_cubic_femtometer, + picograms_per_cubic_femtometer, + femtograms_per_cubic_femtometer, + attograms_per_cubic_femtometer, + atomic_mass_units_per_cubic_femtometer, + pounds_per_cubic_femtometer, + ounces_per_cubic_femtometer, + grams_per_cubic_attometer, + exagrams_per_cubic_attometer, + petagrams_per_cubic_attometer, + teragrams_per_cubic_attometer, + gigagrams_per_cubic_attometer, + megagrams_per_cubic_attometer, + kilograms_per_cubic_attometer, + milligrams_per_cubic_attometer, + micrograms_per_cubic_attometer, + nanograms_per_cubic_attometer, + picograms_per_cubic_attometer, + femtograms_per_cubic_attometer, + attograms_per_cubic_attometer, + atomic_mass_units_per_cubic_attometer, + pounds_per_cubic_attometer, + ounces_per_cubic_attometer, + grams_per_cubic_decimeter, + exagrams_per_cubic_decimeter, + petagrams_per_cubic_decimeter, + teragrams_per_cubic_decimeter, + gigagrams_per_cubic_decimeter, + megagrams_per_cubic_decimeter, + kilograms_per_cubic_decimeter, + milligrams_per_cubic_decimeter, + micrograms_per_cubic_decimeter, + nanograms_per_cubic_decimeter, + picograms_per_cubic_decimeter, + femtograms_per_cubic_decimeter, + attograms_per_cubic_decimeter, + atomic_mass_units_per_cubic_decimeter, + pounds_per_cubic_decimeter, + ounces_per_cubic_decimeter, + grams_per_cubic_centimeter, + exagrams_per_cubic_centimeter, + petagrams_per_cubic_centimeter, + teragrams_per_cubic_centimeter, + gigagrams_per_cubic_centimeter, + megagrams_per_cubic_centimeter, + kilograms_per_cubic_centimeter, + milligrams_per_cubic_centimeter, + micrograms_per_cubic_centimeter, + nanograms_per_cubic_centimeter, + picograms_per_cubic_centimeter, + femtograms_per_cubic_centimeter, + attograms_per_cubic_centimeter, + atomic_mass_units_per_cubic_centimeter, + pounds_per_cubic_centimeter, + ounces_per_cubic_centimeter, + grams_per_cubic_angstrom, + exagrams_per_cubic_angstrom, + petagrams_per_cubic_angstrom, + teragrams_per_cubic_angstrom, + gigagrams_per_cubic_angstrom, + megagrams_per_cubic_angstrom, + kilograms_per_cubic_angstrom, + milligrams_per_cubic_angstrom, + micrograms_per_cubic_angstrom, + nanograms_per_cubic_angstrom, + picograms_per_cubic_angstrom, + femtograms_per_cubic_angstrom, + attograms_per_cubic_angstrom, + atomic_mass_units_per_cubic_angstrom, + pounds_per_cubic_angstrom, + ounces_per_cubic_angstrom, + grams_per_cubic_micron, + exagrams_per_cubic_micron, + petagrams_per_cubic_micron, + teragrams_per_cubic_micron, + gigagrams_per_cubic_micron, + megagrams_per_cubic_micron, + kilograms_per_cubic_micron, + milligrams_per_cubic_micron, + micrograms_per_cubic_micron, + nanograms_per_cubic_micron, + picograms_per_cubic_micron, + femtograms_per_cubic_micron, + attograms_per_cubic_micron, + atomic_mass_units_per_cubic_micron, + pounds_per_cubic_micron, + ounces_per_cubic_micron, + grams_per_cubic_mile, + exagrams_per_cubic_mile, + petagrams_per_cubic_mile, + teragrams_per_cubic_mile, + gigagrams_per_cubic_mile, + megagrams_per_cubic_mile, + kilograms_per_cubic_mile, + milligrams_per_cubic_mile, + micrograms_per_cubic_mile, + nanograms_per_cubic_mile, + picograms_per_cubic_mile, + femtograms_per_cubic_mile, + attograms_per_cubic_mile, + atomic_mass_units_per_cubic_mile, + pounds_per_cubic_mile, + ounces_per_cubic_mile, + grams_per_cubic_yard, + exagrams_per_cubic_yard, + petagrams_per_cubic_yard, + teragrams_per_cubic_yard, + gigagrams_per_cubic_yard, + megagrams_per_cubic_yard, + kilograms_per_cubic_yard, + milligrams_per_cubic_yard, + micrograms_per_cubic_yard, + nanograms_per_cubic_yard, + picograms_per_cubic_yard, + femtograms_per_cubic_yard, + attograms_per_cubic_yard, + atomic_mass_units_per_cubic_yard, + pounds_per_cubic_yard, + ounces_per_cubic_yard, + grams_per_cubic_foot, + exagrams_per_cubic_foot, + petagrams_per_cubic_foot, + teragrams_per_cubic_foot, + gigagrams_per_cubic_foot, + megagrams_per_cubic_foot, + kilograms_per_cubic_foot, + milligrams_per_cubic_foot, + micrograms_per_cubic_foot, + nanograms_per_cubic_foot, + picograms_per_cubic_foot, + femtograms_per_cubic_foot, + attograms_per_cubic_foot, + atomic_mass_units_per_cubic_foot, + pounds_per_cubic_foot, + ounces_per_cubic_foot, + grams_per_cubic_inch, + exagrams_per_cubic_inch, + petagrams_per_cubic_inch, + teragrams_per_cubic_inch, + gigagrams_per_cubic_inch, + megagrams_per_cubic_inch, + kilograms_per_cubic_inch, + milligrams_per_cubic_inch, + micrograms_per_cubic_inch, + nanograms_per_cubic_inch, + picograms_per_cubic_inch, + femtograms_per_cubic_inch, + attograms_per_cubic_inch, + atomic_mass_units_per_cubic_inch, + pounds_per_cubic_inch, + ounces_per_cubic_inch, +]) + +force = UnitGroup( + name = 'force', + units = [ + newtons, + exanewtons, + petanewtons, + teranewtons, + giganewtons, + meganewtons, + kilonewtons, + millinewtons, + micronewtons, + nanonewtons, + piconewtons, + femtonewtons, + attonewtons, + kg_force, + pounds_force, +]) + +pressure = UnitGroup( + name = 'pressure', + units = [ + pascals, + exapascals, + petapascals, + terapascals, + gigapascals, + megapascals, + kilopascals, + millipascals, + micropascals, + nanopascals, + picopascals, + femtopascals, + attopascals, + pounds_force_per_square_inch, +]) + +energy = UnitGroup( + name = 'energy', + units = [ + joules, + exajoules, + petajoules, + terajoules, + gigajoules, + megajoules, + kilojoules, + millijoules, + microjoules, + nanojoules, + picojoules, + femtojoules, + attojoules, + electronvolts, + exaelectronvolts, + petaelectronvolts, + teraelectronvolts, + gigaelectronvolts, + megaelectronvolts, + kiloelectronvolts, + millielectronvolts, + microelectronvolts, + nanoelectronvolts, + picoelectronvolts, + femtoelectronvolts, + attoelectronvolts, +]) + +power = UnitGroup( + name = 'power', + units = [ + watts, + exawatts, + petawatts, + terawatts, + gigawatts, + megawatts, + kilowatts, + milliwatts, + microwatts, + nanowatts, + picowatts, + femtowatts, + attowatts, +]) + +charge = UnitGroup( + name = 'charge', + units = [ + coulombs, + exacoulombs, + petacoulombs, + teracoulombs, + gigacoulombs, + megacoulombs, + kilocoulombs, + millicoulombs, + microcoulombs, + nanocoulombs, + picocoulombs, + femtocoulombs, + attocoulombs, +]) + +potential = UnitGroup( + name = 'potential', + units = [ + volts, + exavolts, + petavolts, + teravolts, + gigavolts, + megavolts, + kilovolts, + millivolts, + microvolts, + nanovolts, + picovolts, + femtovolts, + attovolts, +]) + +resistance = UnitGroup( + name = 'resistance', + units = [ + ohms, + exaohms, + petaohms, + teraohms, + gigaohms, + megaohms, + kiloohms, + milliohms, + microohms, + nanoohms, + picoohms, + femtoohms, + attoohms, +]) + +capacitance = UnitGroup( + name = 'capacitance', + units = [ + farads, + exafarads, + petafarads, + terafarads, + gigafarads, + megafarads, + kilofarads, + millifarads, + microfarads, + nanofarads, + picofarads, + femtofarads, + attofarads, +]) + +conductance = UnitGroup( + name = 'conductance', + units = [ + siemens, + exasiemens, + petasiemens, + terasiemens, + gigasiemens, + megasiemens, + kilosiemens, + millisiemens, + microsiemens, + nanosiemens, + picosiemens, + femtosiemens, + attosiemens, +]) + +magnetic_flux = UnitGroup( + name = 'magnetic_flux', + units = [ + webers, + exawebers, + petawebers, + terawebers, + gigawebers, + megawebers, + kilowebers, + milliwebers, + microwebers, + nanowebers, + picowebers, + femtowebers, + attowebers, +]) + +magnetic_flux_density = UnitGroup( + name = 'magnetic_flux_density', + units = [ + tesla, + exatesla, + petatesla, + teratesla, + gigatesla, + megatesla, + kilotesla, + millitesla, + microtesla, + nanotesla, + picotesla, + femtotesla, + attotesla, +]) + +inductance = UnitGroup( + name = 'inductance', + units = [ + henry, + exahenry, + petahenry, + terahenry, + gigahenry, + megahenry, + kilohenry, + millihenry, + microhenry, + nanohenry, + picohenry, + femtohenry, + attohenry, +]) + +temperature = UnitGroup( + name = 'temperature', + units = [ + kelvin, + exakelvin, + petakelvin, + terakelvin, + gigakelvin, + megakelvin, + kilokelvin, + millikelvin, + microkelvin, + nanokelvin, + picokelvin, + femtokelvin, + attokelvin, + degrees_celsius, +]) + +dimensionless = UnitGroup( + name = 'dimensionless', + units = [ + none, + percent, +]) + +angle = UnitGroup( + name = 'angle', + units = [ + degrees, + radians, +]) + +solid_angle = UnitGroup( + name = 'solid_angle', + units = [ + stradians, +]) + +amount = UnitGroup( + name = 'amount', + units = [ + moles, + millimoles, + micromoles, + nanomoles, + picomoles, + femtomoles, + attomoles, +]) + +concentration = UnitGroup( + name = 'concentration', + units = [ + moles_per_cubic_meter, + millimoles_per_cubic_meter, + micromoles_per_cubic_meter, + nanomoles_per_cubic_meter, + picomoles_per_cubic_meter, + femtomoles_per_cubic_meter, + attomoles_per_cubic_meter, + moles_per_cubic_exameter, + millimoles_per_cubic_exameter, + micromoles_per_cubic_exameter, + nanomoles_per_cubic_exameter, + picomoles_per_cubic_exameter, + femtomoles_per_cubic_exameter, + attomoles_per_cubic_exameter, + moles_per_cubic_petameter, + millimoles_per_cubic_petameter, + micromoles_per_cubic_petameter, + nanomoles_per_cubic_petameter, + picomoles_per_cubic_petameter, + femtomoles_per_cubic_petameter, + attomoles_per_cubic_petameter, + moles_per_cubic_terameter, + millimoles_per_cubic_terameter, + micromoles_per_cubic_terameter, + nanomoles_per_cubic_terameter, + picomoles_per_cubic_terameter, + femtomoles_per_cubic_terameter, + attomoles_per_cubic_terameter, + moles_per_cubic_gigameter, + millimoles_per_cubic_gigameter, + micromoles_per_cubic_gigameter, + nanomoles_per_cubic_gigameter, + picomoles_per_cubic_gigameter, + femtomoles_per_cubic_gigameter, + attomoles_per_cubic_gigameter, + moles_per_cubic_megameter, + millimoles_per_cubic_megameter, + micromoles_per_cubic_megameter, + nanomoles_per_cubic_megameter, + picomoles_per_cubic_megameter, + femtomoles_per_cubic_megameter, + attomoles_per_cubic_megameter, + moles_per_cubic_kilometer, + millimoles_per_cubic_kilometer, + micromoles_per_cubic_kilometer, + nanomoles_per_cubic_kilometer, + picomoles_per_cubic_kilometer, + femtomoles_per_cubic_kilometer, + attomoles_per_cubic_kilometer, + moles_per_cubic_millimeter, + millimoles_per_cubic_millimeter, + micromoles_per_cubic_millimeter, + nanomoles_per_cubic_millimeter, + picomoles_per_cubic_millimeter, + femtomoles_per_cubic_millimeter, + attomoles_per_cubic_millimeter, + moles_per_cubic_micrometer, + millimoles_per_cubic_micrometer, + micromoles_per_cubic_micrometer, + nanomoles_per_cubic_micrometer, + picomoles_per_cubic_micrometer, + femtomoles_per_cubic_micrometer, + attomoles_per_cubic_micrometer, + moles_per_cubic_nanometer, + millimoles_per_cubic_nanometer, + micromoles_per_cubic_nanometer, + nanomoles_per_cubic_nanometer, + picomoles_per_cubic_nanometer, + femtomoles_per_cubic_nanometer, + attomoles_per_cubic_nanometer, + moles_per_cubic_picometer, + millimoles_per_cubic_picometer, + micromoles_per_cubic_picometer, + nanomoles_per_cubic_picometer, + picomoles_per_cubic_picometer, + femtomoles_per_cubic_picometer, + attomoles_per_cubic_picometer, + moles_per_cubic_femtometer, + millimoles_per_cubic_femtometer, + micromoles_per_cubic_femtometer, + nanomoles_per_cubic_femtometer, + picomoles_per_cubic_femtometer, + femtomoles_per_cubic_femtometer, + attomoles_per_cubic_femtometer, + moles_per_cubic_attometer, + millimoles_per_cubic_attometer, + micromoles_per_cubic_attometer, + nanomoles_per_cubic_attometer, + picomoles_per_cubic_attometer, + femtomoles_per_cubic_attometer, + attomoles_per_cubic_attometer, + moles_per_cubic_decimeter, + millimoles_per_cubic_decimeter, + micromoles_per_cubic_decimeter, + nanomoles_per_cubic_decimeter, + picomoles_per_cubic_decimeter, + femtomoles_per_cubic_decimeter, + attomoles_per_cubic_decimeter, + moles_per_cubic_centimeter, + millimoles_per_cubic_centimeter, + micromoles_per_cubic_centimeter, + nanomoles_per_cubic_centimeter, + picomoles_per_cubic_centimeter, + femtomoles_per_cubic_centimeter, + attomoles_per_cubic_centimeter, + moles_per_cubic_angstrom, + millimoles_per_cubic_angstrom, + micromoles_per_cubic_angstrom, + nanomoles_per_cubic_angstrom, + picomoles_per_cubic_angstrom, + femtomoles_per_cubic_angstrom, + attomoles_per_cubic_angstrom, + moles_per_cubic_micron, + millimoles_per_cubic_micron, + micromoles_per_cubic_micron, + nanomoles_per_cubic_micron, + picomoles_per_cubic_micron, + femtomoles_per_cubic_micron, + attomoles_per_cubic_micron, + moles_per_cubic_mile, + millimoles_per_cubic_mile, + micromoles_per_cubic_mile, + nanomoles_per_cubic_mile, + picomoles_per_cubic_mile, + femtomoles_per_cubic_mile, + attomoles_per_cubic_mile, + moles_per_cubic_yard, + millimoles_per_cubic_yard, + micromoles_per_cubic_yard, + nanomoles_per_cubic_yard, + picomoles_per_cubic_yard, + femtomoles_per_cubic_yard, + attomoles_per_cubic_yard, + moles_per_cubic_foot, + millimoles_per_cubic_foot, + micromoles_per_cubic_foot, + nanomoles_per_cubic_foot, + picomoles_per_cubic_foot, + femtomoles_per_cubic_foot, + attomoles_per_cubic_foot, + moles_per_cubic_inch, + millimoles_per_cubic_inch, + micromoles_per_cubic_inch, + nanomoles_per_cubic_inch, + picomoles_per_cubic_inch, + femtomoles_per_cubic_inch, + attomoles_per_cubic_inch, +]) + + +unit_group_names = [ + 'length', + 'area', + 'volume', + 'inverse_length', + 'inverse_area', + 'inverse_volume', + 'time', + 'rate', + 'speed', + 'acceleration', + 'density', + 'force', + 'pressure', + 'energy', + 'power', + 'charge', + 'potential', + 'resistance', + 'capacitance', + 'conductance', + 'magnetic_flux', + 'magnetic_flux_density', + 'inductance', + 'temperature', + 'dimensionless', + 'angle', + 'solid_angle', + 'amount', + 'concentration', +] + +unit_groups = { + 'length': length, + 'area': area, + 'volume': volume, + 'inverse_length': inverse_length, + 'inverse_area': inverse_area, + 'inverse_volume': inverse_volume, + 'time': time, + 'rate': rate, + 'speed': speed, + 'acceleration': acceleration, + 'density': density, + 'force': force, + 'pressure': pressure, + 'energy': energy, + 'power': power, + 'charge': charge, + 'potential': potential, + 'resistance': resistance, + 'capacitance': capacitance, + 'conductance': conductance, + 'magnetic_flux': magnetic_flux, + 'magnetic_flux_density': magnetic_flux_density, + 'inductance': inductance, + 'temperature': temperature, + 'dimensionless': dimensionless, + 'angle': angle, + 'solid_angle': solid_angle, + 'amount': amount, + 'concentration': concentration, +} + diff --git a/sasdata/quantities/units_tests.py b/sasdata/quantities/units_tests.py new file mode 100644 index 00000000..9fea2a6b --- /dev/null +++ b/sasdata/quantities/units_tests.py @@ -0,0 +1,46 @@ +import sasdata.quantities.units as units +from sasdata.quantities.units import Unit + +class EqualUnits: + def __init__(self, test_name: str, *units): + self.test_name = "Equality: " + test_name + self.units: list[Unit] = list(units) + + def run_test(self): + for i, unit_1 in enumerate(self.units): + for unit_2 in self.units[i+1:]: + assert unit_1.equivalent(unit_2), "Units should be equivalent" + assert unit_1 == unit_2, "Units should be equal" + + +class EquivalentButUnequalUnits: + def __init__(self, test_name: str, *units): + self.test_name = "Equivalence: " + test_name + self.units: list[Unit] = list(units) + + def run_test(self): + for i, unit_1 in enumerate(self.units): + for unit_2 in self.units[i+1:]: + assert unit_1.equivalent(unit_2), "Units should be equivalent" + assert unit_1 != unit_2, "Units should not be equal" + + +tests = [ + + EqualUnits("Pressure", + units.pascals, + units.newtons / units.meters ** 2, + units.micronewtons * units.millimeters ** -2), + + EqualUnits("Resistance", + units.ohms, + units.volts / units.amperes, + 1e-3/units.millisiemens) + + +] + + +for test in tests: + print(test.test_name) + test.run_test() diff --git a/sasdata/slicing/__init__.py b/sasdata/slicing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sasdata/slicing/geometry.py b/sasdata/slicing/geometry.py new file mode 100644 index 00000000..e69de29b diff --git a/sasdata/slicing/meshes/__init__.py b/sasdata/slicing/meshes/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sasdata/slicing/meshes/delaunay_mesh.py b/sasdata/slicing/meshes/delaunay_mesh.py new file mode 100644 index 00000000..a19c2ac5 --- /dev/null +++ b/sasdata/slicing/meshes/delaunay_mesh.py @@ -0,0 +1,32 @@ +import numpy as np +from scipy.spatial import Delaunay + +from sasdata.slicing.meshes.mesh import Mesh + +def delaunay_mesh(x, y) -> Mesh: + """ Create a triangulated mesh based on input points """ + + input_data = np.array((x, y)).T + delaunay = Delaunay(input_data) + + return Mesh(points=input_data, cells=delaunay.simplices) + + +if __name__ == "__main__": + import matplotlib.pyplot as plt + + points = np.random.random((100, 2)) + mesh = delaunay_mesh(points[:,0], points[:,1]) + mesh.show(actually_show=False) + + print(mesh.cells[50]) + + # pick random cell to show + for cell in mesh.cells_to_edges[10]: + a, b = mesh.edges[cell] + plt.plot( + [mesh.points[a][0], mesh.points[b][0]], + [mesh.points[a][1], mesh.points[b][1]], + color='r') + + plt.show() diff --git a/sasdata/slicing/meshes/mesh.py b/sasdata/slicing/meshes/mesh.py new file mode 100644 index 00000000..81766339 --- /dev/null +++ b/sasdata/slicing/meshes/mesh.py @@ -0,0 +1,242 @@ +from typing import Sequence + +import numpy as np + +import matplotlib.pyplot as plt +from matplotlib import cm +from matplotlib.collections import LineCollection + +from sasdata.slicing.meshes.util import closed_loop_edges + +class Mesh: + def __init__(self, + points: np.ndarray, + cells: Sequence[Sequence[int]]): + + """ + Object representing a mesh. + + Parameters are the values: + mesh points + map from edge to points + map from cells to edges + + it is done this way to ensure a non-redundant representation of cells and edges, + however there are no checks for the topology of the mesh, this is assumed to be done by + whatever creates it. There are also no checks for ordering of cells. + + :param points: points in 2D forming vertices of the mesh + :param cells: ordered lists of indices of points forming each cell (face) + + """ + + self.points = points + self.cells = cells + + # Get edges + + edges = set() + for cell_index, cell in enumerate(cells): + + for a, b in closed_loop_edges(cell): + # make sure the representation is unique + if a > b: + edges.add((a, b)) + else: + edges.add((b, a)) + + self.edges = list(edges) + + # Associate edges with faces + + edge_lookup = {edge: i for i, edge in enumerate(self.edges)} + self.cells_to_edges = [] + self.cells_to_edges_signs = [] + + for cell in cells: + + this_cell_data = [] + this_sign_data = [] + + for a, b in closed_loop_edges(cell): + # make sure the representation is unique + if a > b: + this_cell_data.append(edge_lookup[(a, b)]) + this_sign_data.append(1) + else: + this_cell_data.append(edge_lookup[(b, a)]) + this_sign_data.append(-1) + + self.cells_to_edges.append(this_cell_data) + self.cells_to_edges_signs.append(this_sign_data) + + # Counts for elements + self.n_points = self.points.shape[0] + self.n_edges = len(self.edges) + self.n_cells = len(self.cells) + + # Areas + self._areas = None + + + @property + def areas(self): + """ Areas of cells """ + + if self._areas is None: + # Calculate areas + areas = [] + for cell in self.cells: + # Use triangle shoelace formula, basically calculate the + # determinant based on of triangles with one point at 0,0 + a_times_2 = 0.0 + for i1, i2 in closed_loop_edges(cell): + p1 = self.points[i1, :] + p2 = self.points[i2, :] + a_times_2 += p1[0]*p2[1] - p1[1]*p2[0] + + areas.append(0.5*np.abs(a_times_2)) + + # Save in cache + self._areas = np.array(areas) + + # Return cache + return self._areas + + + def show(self, actually_show=True, show_labels=False, **kwargs): + """ Show on a plot """ + ax = plt.gca() + segments = [[self.points[edge[0]], self.points[edge[1]]] for edge in self.edges] + line_collection = LineCollection(segments=segments, **kwargs) + ax.add_collection(line_collection) + + if show_labels: + text_color = kwargs["color"] if "color" in kwargs else 'k' + for i, cell in enumerate(self.cells): + xy = np.sum(self.points[cell, :], axis=0)/len(cell) + ax.text(xy[0], xy[1], str(i), horizontalalignment="center", verticalalignment="center", color=text_color) + + x_limits = [np.min(self.points[:,0]), np.max(self.points[:,0])] + y_limits = [np.min(self.points[:,1]), np.max(self.points[:,1])] + + plt.xlim(x_limits) + plt.ylim(y_limits) + + if actually_show: + plt.show() + + def locate_points(self, x: np.ndarray, y: np.ndarray): + """ Find the cells that contain the specified points""" + + x = x.reshape(-1) + y = y.reshape(-1) + + xy = np.concatenate(([x], [y]), axis=1) + + # The most simple implementation is not particularly fast, especially in python + # + # Less obvious, but hopefully faster strategy + # + # Ultimately, checking the inclusion of a point within a polygon + # requires checking the crossings of a half line with the polygon's + # edges. + # + # A fairly efficient thing to do is to check every edge for crossing + # the axis parallel lines x=point_x. + # Then these edges that cross can map back to the polygons they're in + # and a final check for inclusion can be done with the edge sign property + # and some explicit checking of the + # + # Basic idea is: + # 1) build a matrix for each point-edge pair + # True if the edge crosses the half-line above a point + # 2) for each cell get the winding number by evaluating the + # sum of the component edges, weighted 1/-1 according to direction + + + edges = np.array(self.edges) + + edge_xy_1 = self.points[edges[:, 0], :] + edge_xy_2 = self.points[edges[:, 1], :] + + edge_x_1 = edge_xy_1[:, 0] + edge_x_2 = edge_xy_2[:, 0] + + + + # Make an n_edges-by-n_inputs boolean matrix that indicates which of the + # edges cross x=points_x line + crossers = np.logical_xor( + edge_x_1.reshape(-1, 1) < x.reshape(1, -1), + edge_x_2.reshape(-1, 1) < x.reshape(1, -1)) + + # Calculate the gradients, some might be infs, but none that matter will be + # TODO: Disable warnings + gradients = (edge_xy_2[:, 1] - edge_xy_1[:, 1]) / (edge_xy_2[:, 0] - edge_xy_1[:, 0]) + + # Distance to crossing points edge 0 + delta_x = x.reshape(1, -1) - edge_x_1.reshape(-1, 1) + + # Signed distance from point to y (doesn't really matter which sign) + delta_y = gradients.reshape(-1, 1) * delta_x + edge_xy_1[:, 1:] - y.reshape(1, -1) + + score_matrix = np.logical_and(delta_y > 0, crossers) + + output = -np.ones(len(x), dtype=int) + for cell_index, (cell_edges, sign) in enumerate(zip(self.cells_to_edges, self.cells_to_edges_signs)): + cell_score = np.sum(score_matrix[cell_edges, :] * np.array(sign).reshape(-1, 1), axis=0) + points_in_cell = np.abs(cell_score) == 1 + output[points_in_cell] = cell_index + + return output + + def show_data(self, + data: np.ndarray, + cmap='winter', + mesh_color='white', + show_mesh=False, + actually_show=True, + density=False): + + """ Show with data """ + + colormap = cm.get_cmap(cmap, 256) + + data = data.reshape(-1) + + if density: + data = data / self.areas + + cmin = np.min(data) + cmax = np.max(data) + + color_index_map = np.array(255 * (data - cmin) / (cmax - cmin), dtype=int) + + for cell, color_index in zip(self.cells, color_index_map): + + color = colormap(color_index) + + plt.fill(self.points[cell, 0], self.points[cell, 1], color=color, edgecolor=None) + + if show_mesh: + self.show(actually_show=False, color=mesh_color) + + if actually_show: + self.show() + + +if __name__ == "__main__": + from test.slicers.meshes_for_testing import location_test_mesh, location_test_points_x, location_test_points_y + + cell_indices = location_test_mesh.locate_points(location_test_points_x, location_test_points_y) + + print(cell_indices) + + for i in range(location_test_mesh.n_cells): + inds = cell_indices == i + plt.scatter( + location_test_points_x.reshape(-1)[inds], + location_test_points_y.reshape(-1)[inds]) + + location_test_mesh.show() \ No newline at end of file diff --git a/sasdata/slicing/meshes/meshmerge.py b/sasdata/slicing/meshes/meshmerge.py new file mode 100644 index 00000000..0cd9ac04 --- /dev/null +++ b/sasdata/slicing/meshes/meshmerge.py @@ -0,0 +1,165 @@ +import numpy as np + +from sasdata.slicing.meshes.mesh import Mesh +from sasdata.slicing.meshes.delaunay_mesh import delaunay_mesh + +import time + +def meshmerge(mesh_a: Mesh, mesh_b: Mesh) -> tuple[Mesh, np.ndarray, np.ndarray]: + """ Take two lists of polygons and find their intersections + + Polygons in each of the input variables should not overlap i.e. a point in space should be assignable to + at most one polygon in mesh_a and at most one polygon in mesh_b + + Mesh topology should be sensible, otherwise bad things might happen, also, the cells of the input meshes + must be in order (which is assumed by the mesh class constructor anyway). + + :returns: + 1) A triangulated mesh based on both sets of polygons together + 2) The indices of the mesh_a polygon that corresponds to each triangle, -1 for nothing + 3) The indices of the mesh_b polygon that corresponds to each triangle, -1 for nothing + + """ + + t0 = time.time() + + # Find intersections of all edges in mesh one with edges in mesh two + + # Fastest way might just be to calculate the intersections of all lines on edges, + # see whether we need filtering afterwards + + edges_a = np.array(mesh_a.edges, dtype=int) + edges_b = np.array(mesh_b.edges, dtype=int) + + edge_a_1 = mesh_a.points[edges_a[:, 0], :] + edge_a_2 = mesh_a.points[edges_a[:, 1], :] + edge_b_1 = mesh_b.points[edges_b[:, 0], :] + edge_b_2 = mesh_b.points[edges_b[:, 1], :] + + a_grid, b_grid = np.mgrid[0:mesh_a.n_edges, 0:mesh_b.n_edges] + a_grid = a_grid.reshape(-1) + b_grid = b_grid.reshape(-1) + + p1 = edge_a_1[a_grid, :] + p2 = edge_a_2[a_grid, :] + p3 = edge_b_1[b_grid, :] + p4 = edge_b_2[b_grid, :] + + # + # TODO: Investigate whether adding a bounding box check will help with speed, seems likely as most edges wont cross + # + + # + # Solve the equations + # + # z_a1 + s delta_z_a = z_b1 + t delta_z_b + # + # for z = (x, y) + # + + start_point_diff = p1 - p3 + + delta1 = p2 - p1 + delta3 = p4 - p3 + + deltas = np.concatenate(([-delta1], [delta3]), axis=0) + deltas = np.moveaxis(deltas, 0, 2) + + non_singular = np.linalg.det(deltas) != 0 + + st = np.linalg.solve( + deltas[non_singular], + # Reshape is required because solve accepts matrices of shape + # (M) or (..., M, K) for the second parameter, but ours shape + # is (..., M). We add an extra dimension to force our matrix + # into the shape (..., M, 1), which meets the expectations. + # + # + # Due to the reshaping work mentioned above, the final result + # has an extra element of length 1. We then index this extra + # dimension to get back to the result we wanted. + np.expand_dims(start_point_diff[non_singular], axis=2))[:, :, 0] + + # Find the points where s and t are in (0, 1) + + intersection_inds = np.logical_and( + np.logical_and(0 < st[:, 0], st[:, 0] < 1), + np.logical_and(0 < st[:, 1], st[:, 1] < 1)) + + start_points_for_intersections = p1[non_singular][intersection_inds, :] + deltas_for_intersections = delta1[non_singular][intersection_inds, :] + + points_to_add = start_points_for_intersections + st[intersection_inds, 0].reshape(-1,1) * deltas_for_intersections + + t1 = time.time() + print("Edge intersections:", t1 - t0) + + # Build list of all input points, in a way that we can check for coincident points + + + points = np.concatenate(( + mesh_a.points, + mesh_b.points, + points_to_add + )) + + + # Remove coincident points + + points = np.unique(points, axis=0) + + # Triangulate based on these intersections + + output_mesh = delaunay_mesh(points[:, 0], points[:, 1]) + + + t2 = time.time() + print("Delaunay:", t2 - t1) + + + # Find centroids of all output triangles, and find which source cells they belong to + + ## step 1) Assign -1 to all cells of original meshes + assignments_a = -np.ones(output_mesh.n_cells, dtype=int) + assignments_b = -np.ones(output_mesh.n_cells, dtype=int) + + ## step 2) Find centroids of triangulated mesh (just needs to be a point inside, but this is a good one) + centroids = [] + for cell in output_mesh.cells: + centroid = np.sum(output_mesh.points[cell, :]/3, axis=0) + centroids.append(centroid) + + centroids = np.array(centroids) + + t3 = time.time() + print("Centroids:", t3 - t2) + + + ## step 3) Find where points belong based on Mesh classes point location algorithm + + assignments_a = mesh_a.locate_points(centroids[:, 0], centroids[:, 1]) + assignments_b = mesh_b.locate_points(centroids[:, 0], centroids[:, 1]) + + t4 = time.time() + print("Assignments:", t4 - t3) + + return output_mesh, assignments_a, assignments_b + + +def main(): + from voronoi_mesh import voronoi_mesh + + n1 = 100 + n2 = 100 + + m1 = voronoi_mesh(np.random.random(n1), np.random.random(n1)) + m2 = voronoi_mesh(np.random.random(n2), np.random.random(n2)) + + + mesh, assignement1, assignement2 = meshmerge(m1, m2) + + mesh.show() + + +if __name__ == "__main__": + main() diff --git a/sasdata/slicing/meshes/util.py b/sasdata/slicing/meshes/util.py new file mode 100644 index 00000000..b78a9e07 --- /dev/null +++ b/sasdata/slicing/meshes/util.py @@ -0,0 +1,10 @@ +from typing import Sequence, TypeVar + +T = TypeVar("T") + +def closed_loop_edges(values: Sequence[T]) -> tuple[T, T]: + """ Generator for a closed loop of edge pairs """ + for pair in zip(values, values[1:]): + yield pair + + yield values[-1], values[0] \ No newline at end of file diff --git a/sasdata/slicing/meshes/voronoi_mesh.py b/sasdata/slicing/meshes/voronoi_mesh.py new file mode 100644 index 00000000..d47dc2c4 --- /dev/null +++ b/sasdata/slicing/meshes/voronoi_mesh.py @@ -0,0 +1,96 @@ +import numpy as np +from scipy.spatial import Voronoi + + +from sasdata.slicing.meshes.mesh import Mesh + +def voronoi_mesh(x, y, debug_plot=False) -> Mesh: + """ Create a mesh based on a voronoi diagram of points """ + + input_data = np.array((x.reshape(-1), y.reshape(-1))).T + + # Need to make sure mesh covers a finite region, probably not important for + # much data stuff, but is important for plotting + # + # * We want the cells at the edge of the mesh to have a reasonable size, definitely not infinite + # * The exact size doesn't matter that much + # * It should work well with a grid, but also + # * ...it should be robust so that if the data isn't on a grid, it doesn't cause any serious problems + # + # Plan: Create a square border of points that are totally around the points, this is + # at the distance it would be if it was an extra row of grid points + # to do this we'll need + # 1) an estimate of the grid spacing + # 2) the bounding box of the grid + # + + + # Use the median area of finite voronoi cells as an estimate + voronoi = Voronoi(input_data) + finite_cells = [region for region in voronoi.regions if -1 not in region and len(region) > 0] + premesh = Mesh(points=voronoi.vertices, cells=finite_cells) + + area_spacing = np.median(premesh.areas) + gap = np.sqrt(area_spacing) + + # Bounding box is easy + x_min, y_min = np.min(input_data, axis=0) + x_max, y_max = np.max(input_data, axis=0) + + # Create a border + n_x = int(np.round((x_max - x_min)/gap)) + n_y = int(np.round((y_max - y_min)/gap)) + + top_bottom_xs = np.linspace(x_min - gap, x_max + gap, n_x + 3) + left_right_ys = np.linspace(y_min, y_max, n_y + 1) + + top = np.array([top_bottom_xs, (y_max + gap) * np.ones_like(top_bottom_xs)]) + bottom = np.array([top_bottom_xs, (y_min - gap) * np.ones_like(top_bottom_xs)]) + left = np.array([(x_min - gap) * np.ones_like(left_right_ys), left_right_ys]) + right = np.array([(x_max + gap) * np.ones_like(left_right_ys), left_right_ys]) + + added_points = np.concatenate((top, bottom, left, right), axis=1).T + + if debug_plot: + import matplotlib.pyplot as plt + plt.scatter(x, y) + plt.scatter(added_points[:, 0], added_points[:, 1]) + plt.show() + + new_points = np.concatenate((input_data, added_points), axis=0) + voronoi = Voronoi(new_points) + + # Remove the cells that correspond to the added edge points, + # Because the points on the edge of the square are (weakly) convex, these + # regions be infinite + + # finite_cells = [region for region in voronoi.regions if -1 not in region and len(region) > 0] + + # ... however, we can just use .region_points + input_regions = voronoi.point_region[:input_data.shape[0]] + cells = [voronoi.regions[region_index] for region_index in input_regions] + + return Mesh(points=voronoi.vertices, cells=cells) + + +def square_grid_check(): + values = np.linspace(-10, 10, 21) + x, y = np.meshgrid(values, values) + + mesh = voronoi_mesh(x, y) + + mesh.show(show_labels=True) + +def random_grid_check(): + import matplotlib.pyplot as plt + points = np.random.random((100, 2)) + mesh = voronoi_mesh(points[:, 0], points[:, 1], True) + mesh.show(actually_show=False) + plt.scatter(points[:, 0], points[:, 1]) + plt.show() + + +if __name__ == "__main__": + square_grid_check() + # random_grid_check() + diff --git a/sasdata/slicing/rebinning.py b/sasdata/slicing/rebinning.py new file mode 100644 index 00000000..f2c76de6 --- /dev/null +++ b/sasdata/slicing/rebinning.py @@ -0,0 +1,149 @@ +from abc import ABC, abstractmethod +from typing import Optional +from dataclasses import dataclass + +import numpy as np + +from sasdata.slicing.meshes.mesh import Mesh +from sasdata.slicing.meshes.voronoi_mesh import voronoi_mesh +from sasdata.slicing.meshes.meshmerge import meshmerge + +import time + +@dataclass +class CacheData: + """ Data cached for repeated calculations with the same coordinates """ + input_coordinates: np.ndarray # Input data + input_coordinates_mesh: Mesh # Mesh of the input data + merged_mesh_data: tuple[Mesh, np.ndarray, np.ndarray] # mesh information about the merging + + +class Rebinner(ABC): + + + def __init__(self): + """ Base class for rebinning methods""" + + self._bin_mesh_cache: Optional[Mesh] = None # cached version of the output bin mesh + + # Output dependent caching + self._input_cache: Optional[CacheData] = None + + + @abstractmethod + def _bin_coordinates(self) -> np.ndarray: + """ Coordinates for the output bins """ + + @abstractmethod + def _bin_mesh(self) -> Mesh: + """ Get the meshes used for binning """ + + @property + def allowable_orders(self) -> list[int]: + return [-1, 0, 1] + + @property + def bin_mesh(self) -> Mesh: + + if self._bin_mesh_cache is None: + bin_mesh = self._bin_mesh() + self._bin_mesh_cache = bin_mesh + + return self._bin_mesh_cache + + def _post_processing(self, coordinates, values) -> tuple[np.ndarray, np.ndarray]: + """ Perform post-processing on the mesh binned values """ + # Default is to do nothing, override if needed + return coordinates, values + + def _calculate(self, input_coordinates: np.ndarray, input_data: np.ndarray, order: int) -> np.ndarray: + """ Main calculation """ + + if order == -1: + # Construct the input output mapping just based on input points being the output cells, + # Equivalent to the original binning method + + mesh = self.bin_mesh + bin_identities = mesh.locate_points(input_coordinates[:,0], input_coordinates[:, 1]) + output_data = np.zeros(mesh.n_cells, dtype=float) + + for index, bin in enumerate(bin_identities): + if bin >= 0: + output_data[bin] += input_data[index] + + return output_data + + else: + # Use a mapping based on meshes + + # Either create de-cache the appropriate mesh + # Why not use a hash? Hashing takes time, equality checks are pretty fast, need to check equality + # when there is a hit anyway in case of very rare chance of collision, hits are the most common case, + # we want it to work 100% of the time, not 99.9999% + if self._input_cache is not None and np.all(self._input_cache.input_coordinates == input_coordinates): + + input_coordinate_mesh = self._input_cache.input_coordinates_mesh + merge_data = self._input_cache.merged_mesh_data + + else: + # Calculate mesh data + input_coordinate_mesh = voronoi_mesh(input_coordinates[:,0], input_coordinates[:, 1]) + self._data_mesh_cache = input_coordinate_mesh + + merge_data = meshmerge(self.bin_mesh, input_coordinate_mesh) + + # Cache mesh data + self._input_cache = CacheData( + input_coordinates=input_coordinates, + input_coordinates_mesh=input_coordinate_mesh, + merged_mesh_data=merge_data) + + merged_mesh, merged_to_output, merged_to_input = merge_data + + # Calculate values according to the order parameter + t0 = time.time() + if order == 0: + # Based on the overlap of cells only + + input_areas = input_coordinate_mesh.areas + output = np.zeros(self.bin_mesh.n_cells, dtype=float) + + for input_index, output_index, area in zip(merged_to_input, merged_to_output, merged_mesh.areas): + if input_index == -1 or output_index == -1: + # merged region does not correspond to anything of interest + continue + + output[output_index] += input_data[input_index] * area / input_areas[input_index] + + print("Main calc:", time.time() - t0) + + return output + + elif order == 1: + # Linear interpolation requires the following relationship with the data, + # as the input data is the total over the whole input cell, the linear + # interpolation requires continuity at the vertices, and a constraint on the + # integral. + # + # We can take each of the input points, and the associated values, and solve a system + # of linear equations that gives a total value. + + raise NotImplementedError("1st order (linear) interpolation currently not implemented") + + else: + raise ValueError(f"Expected order to be in {self.allowable_orders}, got {order}") + + def sum(self, x: np.ndarray, y: np.ndarray, data: np.ndarray, order: int = 0) -> np.ndarray: + """ Return the summed data in the output bins """ + return self._calculate(np.array((x.reshape(-1), y.reshape(-1))).T, data.reshape(-1), order) + + def error_propagate(self, input_coordinates: np.ndarray, data: np.ndarray, errors) -> np.ndarray: + raise NotImplementedError("Error propagation not implemented yet") + + def resolution_propagate(self, input_coordinates: np.ndarray, data: np.ndarray, errors) -> np.ndarray: + raise NotImplementedError("Resolution propagation not implemented yet") + + def average(self, x: np.ndarray, y: np.ndarray, data: np.ndarray, order: int = 0) -> np.ndarray: + """ Return the averaged data in the output bins """ + return self._calculate(np.array((x, y)).T, data.reshape(-1), order) / self.bin_mesh.areas + diff --git a/sasdata/slicing/sample_polygons.py b/sasdata/slicing/sample_polygons.py new file mode 100644 index 00000000..e12fb1e8 --- /dev/null +++ b/sasdata/slicing/sample_polygons.py @@ -0,0 +1,31 @@ +import numpy as np + +def wedge(q0, q1, theta0, theta1, clockwise=False, n_points_per_degree=2): + + # Traverse a rectangle in curvilinear coordinates (q0, theta0), (q0, theta1), (q1, theta1), (q1, theta0) + if clockwise: + if theta1 > theta0: + theta0 += 2*np.pi + + else: + if theta0 > theta1: + theta1 += 2*np.pi + + subtended_angle = np.abs(theta1 - theta0) + n_points = int(subtended_angle*180*n_points_per_degree/np.pi)+1 + + angles = np.linspace(theta0, theta1, n_points) + + xs = np.concatenate((q0*np.cos(angles), q1*np.cos(angles[::-1]))) + ys = np.concatenate((q0*np.sin(angles), q1*np.sin(angles[::-1]))) + + return np.array((xs, ys)).T + + +if __name__ == "__main__": + import matplotlib.pyplot as plt + xy = wedge(0.3, 0.6, 2, 3) + + plt.plot(xy[:,0], xy[:,1]) + plt.show() + diff --git a/sasdata/slicing/slicer_demo.py b/sasdata/slicing/slicer_demo.py new file mode 100644 index 00000000..af3ee985 --- /dev/null +++ b/sasdata/slicing/slicer_demo.py @@ -0,0 +1,120 @@ +""" Dev docs: Demo to show the behaviour of the re-binning methods """ + +import numpy as np + +import matplotlib.pyplot as plt + +from sasdata.slicing.slicers.AnularSector import AnularSector +from sasdata.slicing.meshes.voronoi_mesh import voronoi_mesh + + + +if __name__ == "__main__": + q_range = 1.5 + demo1 = True + demo2 = True + + # Demo of sums, annular sector over some not very circular data + + if demo1: + + x = (2 * q_range) * (np.random.random(400) - 0.5) + y = (2 * q_range) * (np.random.random(400) - 0.5) + + display_mesh = voronoi_mesh(x, y) + + + def lobe_test_function(x, y): + return 1 + np.sin(x*np.pi/q_range)*np.sin(y*np.pi/q_range) + + + random_lobe_data = lobe_test_function(x, y) + + plt.figure("Input Dataset 1") + display_mesh.show_data(random_lobe_data, actually_show=False) + + data_order_0 = [] + data_order_neg1 = [] + + sizes = np.linspace(0.1, 1, 100) + + for index, size in enumerate(sizes): + q0 = 0.75 - 0.6*size + q1 = 0.75 + 0.6*size + phi0 = np.pi/2 - size + phi1 = np.pi/2 + size + + rebinner = AnularSector(q0, q1, phi0, phi1) + + data_order_neg1.append(rebinner.sum(x, y, random_lobe_data, order=-1)) + data_order_0.append(rebinner.sum(x, y, random_lobe_data, order=0)) + + if index % 10 == 0: + plt.figure("Regions 1") + rebinner.bin_mesh.show(actually_show=False) + + plt.title("Regions") + + plt.figure("Sum of region, dataset 1") + + plt.plot(sizes, data_order_neg1) + plt.plot(sizes, data_order_0) + + plt.legend(["Order -1", "Order 0"]) + plt.title("Sum over region") + + + # Demo of averaging, annular sector over ring shaped data + + if demo2: + + x, y = np.meshgrid(np.linspace(-q_range, q_range, 41), np.linspace(-q_range, q_range, 41)) + x = x.reshape(-1) + y = y.reshape(-1) + + display_mesh = voronoi_mesh(x, y) + + + def ring_test_function(x, y): + r = np.sqrt(x**2 + y**2) + return np.log(np.sinc(r*1.5)**2) + + + grid_ring_data = ring_test_function(x, y) + + plt.figure("Input Dataset 2") + display_mesh.show_data(grid_ring_data, actually_show=False) + + data_order_0 = [] + data_order_neg1 = [] + + sizes = np.linspace(0.1, 1, 100) + + for index, size in enumerate(sizes): + q0 = 0.25 + q1 = 1.25 + + phi0 = np.pi/2 - size + phi1 = np.pi/2 + size + + rebinner = AnularSector(q0, q1, phi0, phi1) + + data_order_neg1.append(rebinner.average(x, y, grid_ring_data, order=-1)) + data_order_0.append(rebinner.average(x, y, grid_ring_data, order=0)) + + if index % 10 == 0: + plt.figure("Regions 2") + rebinner.bin_mesh.show(actually_show=False) + + plt.title("Regions") + + plt.figure("Average of region 2") + + plt.plot(sizes, data_order_neg1) + plt.plot(sizes, data_order_0) + + plt.legend(["Order -1", "Order 0"]) + plt.title("Sum over region") + + plt.show() + diff --git a/sasdata/slicing/slicers/AnularSector.py b/sasdata/slicing/slicers/AnularSector.py new file mode 100644 index 00000000..4ace3441 --- /dev/null +++ b/sasdata/slicing/slicers/AnularSector.py @@ -0,0 +1,43 @@ +import numpy as np + +from sasdata.slicing.rebinning import Rebinner +from sasdata.slicing.meshes.mesh import Mesh + +class AnularSector(Rebinner): + """ A single annular sector (wedge sum)""" + def __init__(self, q0: float, q1: float, phi0: float, phi1: float, points_per_degree: int=2): + super().__init__() + + self.q0 = q0 + self.q1 = q1 + self.phi0 = phi0 + self.phi1 = phi1 + + self.points_per_degree = points_per_degree + + def _bin_mesh(self) -> Mesh: + + n_points = np.max([int(1 + 180*self.points_per_degree*(self.phi1 - self.phi0) / np.pi), 2]) + + angles = np.linspace(self.phi0, self.phi1, n_points) + + row1 = self.q0 * np.array([np.cos(angles), np.sin(angles)]) + row2 = self.q1 * np.array([np.cos(angles), np.sin(angles)])[:, ::-1] + + points = np.concatenate((row1, row2), axis=1).T + + cells = [[i for i in range(2*n_points)]] + + return Mesh(points=points, cells=cells) + + def _bin_coordinates(self) -> np.ndarray: + return np.array([], dtype=float) + + +def main(): + """ Just show a random example""" + AnularSector(1, 2, 1, 2).bin_mesh.show() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/sasdata/slicing/slicers/__init__.py b/sasdata/slicing/slicers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sasdata/slicing/transforms.py b/sasdata/slicing/transforms.py new file mode 100644 index 00000000..323a7a52 --- /dev/null +++ b/sasdata/slicing/transforms.py @@ -0,0 +1,58 @@ +import numpy as np +from scipy.spatial import Voronoi +import matplotlib.pyplot as plt +from matplotlib import cm + + +# Some test data + +qx_base_values = np.linspace(-10, 10, 21) +qy_base_values = np.linspace(-10, 10, 21) + +qx, qy = np.meshgrid(qx_base_values, qy_base_values) + +include = np.logical_not((np.abs(qx) < 2) & (np.abs(qy) < 2)) + +qx = qx[include] +qy = qy[include] + +r = np.sqrt(qx**2 + qy**2) + +data = np.log((1+np.cos(3*r))*np.exp(-r*r)) + +colormap = cm.get_cmap('winter', 256) + +def get_data_mesh(x, y, data): + + input_data = np.array((x, y)).T + voronoi = Voronoi(input_data) + + # plt.scatter(voronoi.vertices[:,0], voronoi.vertices[:,1]) + # plt.scatter(voronoi.points[:,0], voronoi.points[:,1]) + + cmin = np.min(data) + cmax = np.max(data) + + color_index_map = np.array(255 * (data - cmin) / (cmax - cmin), dtype=int) + + for point_index, points in enumerate(voronoi.points): + + region_index = voronoi.point_region[point_index] + region = voronoi.regions[region_index] + + if len(region) > 0: + + if -1 in region: + + pass + + else: + + color = colormap(color_index_map[point_index]) + + circly = region + [region[0]] + plt.fill(voronoi.vertices[circly, 0], voronoi.vertices[circly, 1], color=color, edgecolor="white") + + plt.show() + +get_data_mesh(qx.reshape(-1), qy.reshape(-1), data) \ No newline at end of file diff --git a/sasdata/temp_ascii_reader.py b/sasdata/temp_ascii_reader.py new file mode 100644 index 00000000..653db441 --- /dev/null +++ b/sasdata/temp_ascii_reader.py @@ -0,0 +1,221 @@ +from sasdata.ascii_reader_metadata import ( + AsciiMetadataCategory, + AsciiReaderMetadata, + pairings, + bidirectional_pairings, +) +from sasdata.data import SasData +from sasdata.dataset_types import DatasetType, one_dim, unit_kinds +from sasdata.guess import ( + guess_column_count, + guess_columns, + guess_starting_position, + guess_dataset_type, +) +from sasdata.default_units import get_default_unit +from sasdata.quantities.units import NamedUnit +from sasdata.quantities.quantity import Quantity +from sasdata.metadata import MetaNode, Metadata +from enum import Enum +from dataclasses import dataclass, field +import numpy as np +import re +from os import path +from dataclasses import replace + + +class AsciiSeparator(Enum): + Comma = (0,) + Whitespace = (1,) + Tab = 2 + + +# TODO: Turn them all of for now so the caller can turn one of them on. But is this the desired behaviour? +def initialise_separator_dict(initial_value: bool = False) -> dict[str, bool]: + return {"Whitespace": initial_value, "Comma": initial_value, "Tab": initial_value} + + +@dataclass +class AsciiReaderParams: + """This object contains the parameters that are used to load a series of + ASCII files. These parameters can be generated by the ASCII Reader Dialog + when using SasView.""" + + # These will be the FULL file path. Will need to convert to basenames for some functions. + filenames: list[str] + # The unit object for the column should only be None if the column is ! + columns: list[tuple[str, NamedUnit | None]] + metadata: AsciiReaderMetadata = field(default_factory=AsciiReaderMetadata) + starting_line: int = 0 + excluded_lines: set[int] = field(default_factory=set) + separator_dict: dict[str, bool] = field(default_factory=initialise_separator_dict) + # Take a copy in case its mutated (which it shouldn't be) + dataset_type: DatasetType = field(default_factory=lambda: replace(one_dim)) + + def __post_init__(self): + self.initialise_metadata() + + def initialise_metadata(self): + for filename in self.filenames: + basename = path.basename(filename) + if basename not in self.metadata.filename_separator: + self.metadata.filename_separator[basename] = "_" + self.metadata.filename_specific_metadata[basename] = {} + + @property + def columns_included(self) -> list[tuple[str, NamedUnit]]: + return [ + column + for column in self.columns + if column[0] != "" and isinstance(column[1], NamedUnit) + ] + + +# TODO: Should I make this work on a list of filenames as well? +def guess_params_from_filename( + filename: str, dataset_type: DatasetType +) -> AsciiReaderParams: + # Lets just assume we want all of the seaprators on. This seems to work for most files. + separator_dict = initialise_separator_dict(True) + with open(filename) as file: + lines = file.readlines() + lines_split = [split_line(separator_dict, line) for line in lines] + startpos = guess_starting_position(lines_split) + colcount = guess_column_count(lines_split, startpos) + columns = [ + (x, get_default_unit(x, unit_kinds[x])) + for x in guess_columns(colcount, dataset_type) + if x in unit_kinds + ] + params = AsciiReaderParams( + [filename], + columns, + starting_line=startpos, + separator_dict=separator_dict, + dataset_type=guess_dataset_type(filename), + ) + return params + + +def split_line(separator_dict: dict[str, bool], line: str) -> list[str]: + """Split a line in a CSV file based on which seperators the user has + selected on the widget. + + """ + expr = "" + for seperator, isenabled in separator_dict.items(): + if isenabled: + if expr != r"": + expr += r"|" + match seperator: + case "Comma": + seperator_text = r"," + case "Whitespace": + seperator_text = r"\s+" + case "Tab": + seperator_text = r"\t" + expr += seperator_text + + return re.split(expr, line.strip()) + + +# TODO: Implement error handling. +def load_quantities(params: AsciiReaderParams, filename: str) -> dict[str, Quantity]: + """Load a list of quantities from the filename based on the params.""" + with open(filename) as ascii_file: + lines = ascii_file.readlines() + arrays: list[np.ndarray] = [] + for _ in params.columns_included: + arrays.append(np.zeros(len(lines) - params.starting_line)) + for i, current_line in enumerate(lines): + if i < params.starting_line or current_line in params.excluded_lines: + continue + line_split = split_line(params.separator_dict, current_line) + try: + for j, token in enumerate(line_split): + # Sometimes in the split, there might be an extra column that doesn't need to be there (e.g. an empty + # string.) This won't convert to a float so we need to ignore it. + if j >= len(params.columns_included): + continue + # TODO: Data might not be floats. Maybe don't hard code this. + arrays[j][i - params.starting_line] = float(token) + except ValueError: + # If any of the lines contain non-numerical data, then this line can't be read in as a quantity so it + # should be ignored entirely. + print(f"Line {i + 1} skipped.") + continue + file_quantities = { + name: Quantity(arrays[i], unit) + for i, (name, unit) in enumerate(params.columns_included) + } + return file_quantities + + +def import_metadata(metadata: dict[str, AsciiMetadataCategory[str]]) -> MetaNode: + root_contents = [] + for top_level_key, top_level_item in metadata.items(): + children = [] + for metadatum_name, metadatum in top_level_item.values.items(): + children.append(MetaNode(name=metadatum_name, attrs={}, contents=metadatum)) + if top_level_key == "other": + root_contents.extend(children) + else: + group = MetaNode(name=top_level_key, attrs={}, contents=children) + root_contents.append(group) + return MetaNode(name="root", attrs={}, contents=root_contents) + + +def merge_uncertainties(quantities: dict[str, Quantity]) -> dict[str, Quantity]: + """Data in the ASCII files will have the uncertainties in a separate column. + This function will merge columns of data with the columns containing their + uncertainties so that both are in one Quantity object.""" + new_quantities: dict[str, Quantity] = {} + error_quantity_names = pairings.values() + for name, quantity in quantities.items(): + if name in error_quantity_names: + continue + pairing = bidirectional_pairings.get(name, "") + error_quantity = None + for other_name, other_quantity in quantities.items(): + if other_name == pairing: + error_quantity = other_quantity + if error_quantity is not None: + to_add = quantity.with_standard_error(error_quantity) + else: + to_add = quantity + new_quantities[name] = to_add + return new_quantities + + +def load_data(params: AsciiReaderParams) -> list[SasData]: + """This loads a series of SasData objects based on the params. The amount of + SasData objects loaded will depend on how many filenames are present in the + list contained in the params.""" + loaded_data: list[SasData] = [] + for filename in params.filenames: + quantities = load_quantities(params, filename) + raw_metadata = import_metadata( + params.metadata.all_file_metadata(path.basename(filename)) + ) + metadata = Metadata( + title=None, + run=[], + definition=None, + sample=None, + instrument=None, + process=None, + raw=raw_metadata, + ) + data = SasData( + path.basename(filename), + merge_uncertainties(quantities), + params.dataset_type, + metadata, + ) + loaded_data.append(data) + return loaded_data + + +def load_data_default_params(filename: str) -> list[SasData]: + params = guess_params_from_filename(filename, guess_dataset_type(filename)) + return load_data(params) diff --git a/sasdata/temp_hdf5_reader.py b/sasdata/temp_hdf5_reader.py new file mode 100644 index 00000000..e6a25e52 --- /dev/null +++ b/sasdata/temp_hdf5_reader.py @@ -0,0 +1,340 @@ +import h5py + + +import logging + +import numpy as np +from typing import Callable, Tuple + + +from h5py._hl.dataset import Dataset as HDF5Dataset +from h5py._hl.group import Group as HDF5Group + + +from sasdata.data import SasData +from sasdata.dataset_types import one_dim +from sasdata.data_backing import Dataset as SASDataDataset, Group as SASDataGroup +from sasdata.metadata import Instrument, Collimation, Aperture, Source, BeamSize, Detector, Vec3, \ + Rot3, Sample, Process, MetaNode, Metadata + +from sasdata.quantities.quantity import NamedQuantity, Quantity +from sasdata.quantities import units +from sasdata.quantities.unit_parser import parse + +# test_file = "./example_data/1d_data/33837rear_1D_1.75_16.5_NXcanSAS_v3.h5" +# test_file = "./example_data/1d_data/33837rear_1D_1.75_16.5_NXcanSAS.h5" +test_file = "./example_data/2d_data/BAM_2D.h5" +# test_file = "./example_data/2d_data/14250_2D_NoDetInfo_NXcanSAS_v3.h5" +# test_file = "./example_data/2d_data/33837rear_2D_1.75_16.5_NXcanSAS_v3.h5" + +logger = logging.getLogger(__name__) + + +def recurse_hdf5(hdf5_entry): + if isinstance(hdf5_entry, HDF5Dataset): + # + # print(hdf5_entry.dtype) + # print(type(hdf5_entry.dtype)) + + attributes = {name: hdf5_entry.attrs[name] for name in hdf5_entry.attrs} + + if isinstance(hdf5_entry.dtype, np.dtypes.BytesDType): + data = hdf5_entry[()][0].decode("utf-8") + + return SASDataDataset[str]( + name=hdf5_entry.name, data=data, attributes=attributes + ) + + else: + data = np.array(hdf5_entry, dtype=hdf5_entry.dtype) + + return SASDataDataset[np.ndarray]( + name=hdf5_entry.name, data=data, attributes=attributes + ) + + elif isinstance(hdf5_entry, HDF5Group): + return SASDataGroup( + name=hdf5_entry.name, + children={key: recurse_hdf5(hdf5_entry[key]) for key in hdf5_entry.keys()}, + ) + + else: + raise TypeError( + f"Unknown type found during HDF5 parsing: {type(hdf5_entry)} ({hdf5_entry})" + ) + + +GET_UNITS_FROM_ELSEWHERE = units.meters + + +def connected_data(node: SASDataGroup, name_prefix="") -> dict[str, Quantity]: + """In the context of NeXus files, load a group of data entries that are organised together + match up the units and errors with their values""" + # Gather together data with its error terms + + uncertainty_map = {} + uncertainties = set() + entries = {} + + for name in node.children: + child = node.children[name] + + if "units" in child.attributes: + units = parse(child.attributes["units"]) + else: + units = GET_UNITS_FROM_ELSEWHERE + + quantity = NamedQuantity( + name=name_prefix + child.name, value=child.data, units=units + ) + + # Turns out people can't be trusted to use the same keys here + if "uncertainty" in child.attributes or "uncertainties" in child.attributes: + try: + uncertainty_name = child.attributes["uncertainty"] + except: + uncertainty_name = child.attributes["uncertainties"] + uncertainty_map[name] = uncertainty_name + uncertainties.add(uncertainty_name) + + entries[name] = quantity + + output : dict[str, Quantity] = {} + + for name, entry in entries.items(): + if name not in uncertainties: + if name in uncertainty_map: + uncertainty = entries[uncertainty_map[name]] + new_entry = entry.with_standard_error(uncertainty) + output[name] = new_entry + else: + output[name] = entry + + return output + +### Begin metadata parsing code + +def parse_quantity(node : HDF5Group) -> Quantity[float]: + """Pull a single quantity with length units out of an HDF5 node""" + magnitude = node.astype(float)[0] + unit = node.attrs["units"] + return Quantity(magnitude, units.symbol_lookup[unit]) + +def parse_string(node : HDF5Group) -> str: + """Access string data from a node""" + return node.asstr()[0] + +def opt_parse[T](node: HDF5Group, key: str, subparser: Callable[[HDF5Group], T]) -> T | None: + """Parse a subnode if it is present""" + if key in node: + return subparser(node[key]) + return None + +def attr_parse(node: HDF5Group, key: str) -> str | None: + """Parse an attribute if it is present""" + if key in node.attrs: + return node.attrs[key] + return None + + +def parse_apterture(node : HDF5Group) -> Aperture: + distance = opt_parse(node, "distance", parse_quantity) + name = attr_parse(node, "name") + size = opt_parse(node, "size", parse_vec3) + size_name = None + type_ = attr_parse(node, "type") + if size: + size_name = attr_parse(node["size"], "name") + else: + size_name = None + return Aperture(distance=distance, size=size, size_name=size_name, name=name, type_=type_) + +def parse_beam_size(node : HDF5Group) -> BeamSize: + name = attr_parse(node, "name") + size = parse_vec3(node) + return BeamSize(name=name, size=size) + +def parse_source(node : HDF5Group) -> Source: + radiation = opt_parse(node, "radiation", parse_string) + beam_shape = opt_parse(node, "beam_shape", parse_string) + beam_size = opt_parse(node, "beam_size", parse_beam_size) + wavelength = opt_parse(node, "wavelength", parse_quantity) + wavelength_min = opt_parse(node, "wavelength_min", parse_quantity) + wavelength_max = opt_parse(node, "wavelength_max", parse_quantity) + wavelength_spread = opt_parse(node, "wavelength_spread", parse_quantity) + return Source( + radiation=radiation, + beam_shape=beam_shape, + beam_size=beam_size, + wavelength=wavelength, + wavelength_min=wavelength_min, + wavelength_max=wavelength_max, + wavelength_spread=wavelength_spread, + ) + +def parse_vec3(node : HDF5Group) -> Vec3: + """Parse a measured 3-vector""" + x = opt_parse(node, "x", parse_quantity) + y = opt_parse(node, "y", parse_quantity) + z = opt_parse(node, "z", parse_quantity) + return Vec3(x=x, y=y, z=z) + +def parse_rot3(node : HDF5Group) -> Rot3: + """Parse a measured rotation""" + roll = opt_parse(node, "roll", parse_quantity) + pitch = opt_parse(node, "pitch", parse_quantity) + yaw = opt_parse(node, "yaw", parse_quantity) + return Rot3(roll=roll, pitch=pitch, yaw=yaw) + +def parse_detector(node : HDF5Group) -> Detector: + name = opt_parse(node, "name", parse_string) + distance = opt_parse(node, "SDD", parse_quantity) + offset = opt_parse(node, "offset", parse_vec3) + orientation = opt_parse(node, "orientation", parse_rot3) + beam_center = opt_parse(node, "beam_center", parse_vec3) + pixel_size = opt_parse(node, "pixel_size", parse_vec3) + slit_length = opt_parse(node, "slit_length", parse_quantity) + + return Detector(name=name, + distance=distance, + offset=offset, + orientation=orientation, + beam_center=beam_center, + pixel_size=pixel_size, + slit_length=slit_length) + + + +def parse_collimation(node : HDF5Group) -> Collimation: + length = opt_parse(node, "length", parse_quantity) + return Collimation(length=length, apertures=[parse_apterture(node[ap]) + for ap in node if "aperture" in ap]) + + +def parse_instrument(node : HDF5Group) -> Instrument: + return Instrument( + collimations= [parse_collimation(node[x]) for x in node if "collimation" in x], + detector=[parse_detector(node[d]) for d in node if "detector" in d], + source=parse_source(node["sassource"]), + ) + +def parse_sample(node : HDF5Group) -> Sample: + name = attr_parse(node, "name") + sample_id = opt_parse(node, "ID", parse_string) + thickness = opt_parse(node, "thickness", parse_quantity) + transmission = opt_parse(node, "transmission", lambda n: float(n[0].astype(str))) + temperature = opt_parse(node, "temperature", parse_quantity) + position = opt_parse(node, "position", parse_vec3) + orientation = opt_parse(node, "orientation", parse_rot3) + details : list[str] = [node[d].asstr()[0] for d in node if "details" in d] + return Sample(name=name, + sample_id=sample_id, + thickness=thickness, + transmission=transmission, + temperature=temperature, + position=position, + orientation=orientation, + details=details) + +def parse_term(node : HDF5Group) -> Tuple[str, str | Quantity[float]] | None: + name = attr_parse(node, "name") + unit = attr_parse(node, "unit") + value = attr_parse(node, "value") + if name is None or value is None: + return None + if unit and unit.strip(): + return (name, Quantity(float(value), units.symbol_lookup[unit])) + return (name, value) + + +def parse_process(node : HDF5Group) -> Process: + name = opt_parse(node, "name", parse_string) + date = opt_parse(node, "date", parse_string) + description = opt_parse(node, "description", parse_string) + term_values = [parse_term(node[n]) for n in node if "term" in n] + terms = {tup[0]: tup[1] for tup in term_values if tup is not None} + notes = [parse_string(node[n]) for n in node if "note" in n] + return Process(name=name, date=date, description=description, terms=terms, notes=notes) + +def load_raw(node: HDF5Group | HDF5Dataset) -> MetaNode: + name = node.name.split("/")[-1] + match node: + case HDF5Group(): + attrib = {a: node.attrs[a] for a in node.attrs} + contents = [load_raw(node[v]) for v in node] + return MetaNode(name=name, attrs=attrib, contents=contents) + case HDF5Dataset(dtype=dt): + attrib = {a: node.attrs[a] for a in node.attrs} + if (str(dt).startswith("|S")): + if "units" in attrib: + contents = Quantity(float(node.asstr()[0]), parse(attrib["units"])) + else: + contents = node.asstr()[0] + else: + if "units" in attrib and attrib["units"]: + contents = Quantity(node[:], parse(attrib["units"])) + else: + contents = node[:] + return MetaNode(name=name, attrs=attrib, contents=contents) + case _: + raise RuntimeError(f"Cannot load raw data of type {type(node)}") + +def parse_metadata(node : HDF5Group) -> Metadata: + instrument = opt_parse(node, "sasinstrument", parse_instrument) + sample = opt_parse(node, "sassample", parse_sample) + process = [parse_process(node[p]) for p in node if "sasprocess" in p] + title = opt_parse(node, "title", parse_string) + run = [parse_string(node[r]) for r in node if "run" in r] + definition = opt_parse(node, "definition", parse_string) + raw = load_raw(node) + return Metadata(process=process, + instrument=instrument, + sample=sample, + title=title, + run=run, + raw=raw, + definition=definition) + +### End Metadata parsing code + + +def load_data(filename: str) -> dict[str, SasData]: + with h5py.File(filename, "r") as f: + loaded_data: dict[str, SasData] = {} + + for root_key in f.keys(): + entry = f[root_key] + + data_contents : dict[str, Quantity] = {} + + entry_keys = [key for key in entry if "entry" in key] + + if "sasdata" not in entry_keys and "data" not in entry_keys: + logger.warning("No sasdata or data key") + + for key in entry_keys: + component = entry[key] + lower_key = key.lower() + if lower_key == "sasdata" or lower_key == "data": + datum = recurse_hdf5(component) + # TODO: Use named identifier + data_contents = connected_data(datum, "FILE_ID_HERE") + + metadata = parse_metadata(f[root_key]) + + loaded_data[root_key] = SasData( + name=root_key, + dataset_type=one_dim, + data_contents=data_contents, + metadata=metadata, + verbose=False, + ) + + return loaded_data + + +if __name__ == "__main__": + data = load_data(test_file) + + for dataset in data.values(): + print(dataset.summary()) diff --git a/sasdata/temp_sesans_reader.py b/sasdata/temp_sesans_reader.py new file mode 100644 index 00000000..866ec4be --- /dev/null +++ b/sasdata/temp_sesans_reader.py @@ -0,0 +1,208 @@ +""" +Import SESANS data in SasData format +""" + +from sasdata.data import SasData +from sasdata.data_util.loader_exceptions import FileContentsException +from sasdata.dataset_types import sesans +from sasdata.quantities.quantity import Quantity +from sasdata.metadata import ( + Metadata, + Sample, + MetaNode, + Process, +) +from sasdata.quantities import unit_parser, units +from collections import defaultdict +from itertools import groupby +import re +import numpy as np + + +def parse_version(lines: list[str]) -> tuple[str, list[str]]: + header = lines[0] + m = re.search(r"FileFormatVersion\s+(\S+)", header) + + if m is None: + raise FileContentsException( + "Sesans file does not contain File Format Version header" + ) + + return (m.group(0), lines[1:]) + + +def parse_title(kvs: dict[str, str]) -> str: + """Get the title from the key value store""" + if "Title" in kvs: + return kvs["Title"] + elif "DataFileTitle" in kvs: + return kvs["DataFileTitle"] + for k, v in kvs.items(): + if "Title" in k: + return v + return "" + + +def parse_kvs_quantity(key: str, kvs: dict[str, str]) -> Quantity | None: + if key not in kvs or key + "_unit" not in kvs: + return None + return Quantity(value=float(kvs[key]), units=unit_parser.parse(kvs[key + "_unit"])) + + +def parse_sample(kvs: dict[str, str]) -> Sample: + """Get the sample info from the key value store""" + + thickness = parse_kvs_quantity("Thickness", kvs) + if thickness is None: + raise FileContentsException( + "SES format must include sample thickness to normalise calculations" + ) + + return Sample( + name=kvs.get("Sample"), + sample_id=None, + thickness=thickness, + transmission=None, + temperature=None, + position=None, + orientation=None, + details=[], + ) + + +def parse_process(kvs: dict[str, str]) -> Process: + ymax = parse_kvs_quantity("Theta_ymax", kvs) + zmax = parse_kvs_quantity("Theta_zmax", kvs) + orientation = kvs.get("Orientation") + + if ymax is None: + raise FileContentsException("SES file must specify Theta_ymax") + if zmax is None: + raise FileContentsException("SES file must specify Theta_zmax") + if orientation is None: + raise FileContentsException("SES file must include encoding orientation") + + terms: dict[str, str | Quantity] = { + "ymax": ymax, + "zmax": zmax, + "orientation": orientation, + } + + return Process( + name="SESANS Processing", + date=None, + description="Polarisation measurement through a SESANS instrument", + terms=terms, + notes=[], + ) + + +def parse_metanode(kvs: dict[str, str]) -> MetaNode: + """Convert header into metanode""" + contents: list[MetaNode] = [] + title = parse_title(kvs) + + for k, v in kvs.items(): + if v.endswith("_unit") and v[:-5] in kvs: + # This is the unit for another term + continue + if v + "_unit" in kvs: + contents.append( + MetaNode( + name=k, + attrs={}, + contents=Quantity( + value=float(v), units=unit_parser.parse(kvs[k + "_unit"]) + ), + ) + ) + else: + contents.append(MetaNode(name=k, attrs={}, contents=v)) + + return MetaNode(name=title, attrs={}, contents=contents) + + +def parse_metadata(lines: list[str]) -> tuple[Metadata, dict[str, str], list[str]]: + parts = [ + [y for y in x] + for (_, x) in groupby(lines, lambda x: x.startswith("BEGIN_DATA")) + ] + + if len(parts) != 3: + raise FileContentsException("SES file should have exactly one data section") + + # Parse key value store + kvs: dict[str, str] = {} + for line in parts[0]: + m = re.search(r"(\S+)\s+(.+)\n", line) + if not m: + continue + kvs[m.group(1)] = m.group(2) + + return ( + Metadata( + process=[parse_process(kvs)], + instrument=None, + sample=parse_sample(kvs), + title=parse_title(kvs), + run=[], + definition=None, + raw=parse_metanode(kvs), + ), + kvs, + parts[2], + ) + + +def parse_data(lines: list[str], kvs: dict[str, str]) -> dict[str, Quantity]: + + data_contents: dict[str, Quantity] = {} + headers = lines[0].split() + points = defaultdict(list) + for line in lines[1:]: + values = line.split() + for idx, v in enumerate(values): + points[headers[idx]].append(float(v)) + + for h in points.keys(): + if h.endswith("_error") and h[:-6] in headers: + # This was an error line + continue + unit = units.none + if h + "_unit" in kvs: + unit = unit_parser.parse(kvs[h + "_unit"]) + + error = None + if h + "_error" in headers: + error = np.asarray(points[h + "_error"]) + + data_contents[h] = Quantity( + value=np.asarray(points[h]), + units=unit, + standard_error=error, + ) + + for required in ["SpinEchoLength", "Depolarisation", "Wavelength"]: + if required not in data_contents: + raise FileContentsException(f"SES file missing {required}") + + return data_contents + + +def parse_sesans(lines: list[str]) -> SasData: + version, lines = parse_version(lines) + metadata, kvs, lines = parse_metadata(lines) + data_contents = parse_data(lines, kvs) + return SasData( + name="Sesans", + dataset_type=sesans, + data_contents=data_contents, + metadata=metadata, + verbose=False, + ) + + +def load_data(filename) -> SasData: + with open(filename) as infile: + lines = infile.readlines() + return parse_sesans(lines) diff --git a/sasdata/temp_xml_reader.py b/sasdata/temp_xml_reader.py new file mode 100644 index 00000000..a8c3ef4e --- /dev/null +++ b/sasdata/temp_xml_reader.py @@ -0,0 +1,352 @@ +import logging +from lxml import etree +import numpy as np +from typing import Callable + +from sasdata.data import SasData +from sasdata.dataset_types import one_dim +from sasdata.metadata import ( + Instrument, + Collimation, + Aperture, + Source, + BeamSize, + Detector, + Vec3, + Rot3, + Sample, + Process, + MetaNode, + Metadata, +) +from sasdata.quantities.quantity import Quantity +import sasdata.quantities.unit_parser as unit_parser +from sasdata.quantities.units import Unit, none as unitless + +logger = logging.getLogger(__name__) + +test_file = "./example_data/1d_data/ISIS_Polymer_Blend_TK49.xml" + +ns = { + "cansas11": "urn:cansas1d:1.1", + "cansas10": "urn:cansas1d:1.0", + "cansas_old": "cansas1d/1.0", +} + + +def parse_string(node: etree._Element, _version: str) -> str: + """Access string data from a node""" + return "".join(node.itertext()) + + +def parse_quantity(node: etree._Element, version: str) -> Quantity[float]: + """Pull a single quantity with length units out of an XML node""" + magnitude = float(parse_string(node, version)) + try: + unit = unit_parser.parse(node.attrib["unit"]) + except ValueError: + logger.warning( + f'Could not parse unit "{node.attrib["unit"]}". Marking value as unitless' + ) + unit = unitless + return Quantity(magnitude, unit) + + +def attr_parse(node: etree._Element, key: str) -> str | None: + """Parse an attribute if it is present""" + if key in node.attrib: + return node.attrib[key] + return None + + +def opt_parse[T]( + node: etree._Element, + key: str, + version: str, + subparser: Callable[[etree._Element, str], T], +) -> T | None: + """Parse subnode if preset""" + if (inner_node := node.find(f"{version}:{key}", ns)) is not None: + return subparser(inner_node, version) + return None + + +def all_parse[T]( + node: etree._Element, + key: str, + version: str, + subparser: Callable[[etree._Element, str], T], +) -> list[T]: + """Parse subnode if preset""" + return [subparser(n, version) for n in node.findall(f"{version}:{key}", ns)] + + +def parse_vec3(node: etree._Element, version: str) -> Vec3: + """Parse a measured 3-vector""" + x = opt_parse(node, "x", version, parse_quantity) + y = opt_parse(node, "y", version, parse_quantity) + z = opt_parse(node, "z", version, parse_quantity) + return Vec3(x=x, y=y, z=z) + + +def parse_rot3(node: etree._Element, version: str) -> Rot3: + """Parse a measured rotation""" + roll = opt_parse(node, "roll", version, parse_quantity) + pitch = opt_parse(node, "pitch", version, parse_quantity) + yaw = opt_parse(node, "yaw", version, parse_quantity) + return Rot3(roll=roll, pitch=pitch, yaw=yaw) + + +def parse_term(node: etree._Element, version: str) -> str | Quantity[float]: + """Parse a process term, which may be a measured quantity or a string""" + if "unit" in node.attrib: + return parse_quantity(node, version) + else: + return parse_string(node, version) + + +def parse_process(node: etree._Element, version: str) -> Process: + """Parse an experimental process""" + name = opt_parse(node, "name", version, parse_string) + date = opt_parse(node, "date", version, parse_string) + description = opt_parse(node, "description", version, parse_string) + terms = { + t.attrib["name"]: parse_term(t, version) + for t in node.findall(f"{version}:term", ns) + } + notes = [ + parse_string(note, version) + for note in node.findall(f"{version}:SASprocessnote", ns) + ] + notes = [n.strip() for n in notes if n is not None and n.strip()] + return Process( + name=name, date=date, description=description, terms=terms, notes=notes + ) + + +def parse_beam_size(node: etree._Element, version: str) -> BeamSize: + """Parse a beam size""" + return BeamSize(name=attr_parse(node, "name"), size=parse_vec3(node, version)) + + +def parse_source(node: etree._Element, version: str) -> Source: + """Parse a radiation source""" + return Source( + radiation=opt_parse(node, "radiation", version, parse_string), + beam_size=opt_parse(node, "beam_size", version, parse_beam_size), + beam_shape=opt_parse(node, "beam_shape", version, parse_string), + wavelength=opt_parse(node, "wavelength", version, parse_quantity), + wavelength_min=opt_parse(node, "wavelength_min", version, parse_quantity), + wavelength_max=opt_parse(node, "wavelength_max", version, parse_quantity), + wavelength_spread=opt_parse(node, "wavelength_spread", version, parse_quantity), + ) + + +def parse_detector(node: etree._Element, version: str) -> Detector: + """Parse signal detector metadata""" + return Detector( + name=opt_parse(node, "name", version, parse_string), + distance=opt_parse(node, "SDD", version, parse_quantity), + offset=opt_parse(node, "offset", version, parse_vec3), + orientation=opt_parse(node, "orientation", version, parse_rot3), + beam_center=opt_parse(node, "beam_center", version, parse_vec3), + pixel_size=opt_parse(node, "pixel_size", version, parse_vec3), + slit_length=opt_parse(node, "slit_length", version, parse_quantity), + ) + + +def parse_aperture(node: etree._Element, version: str) -> Aperture: + """Parse an aperture description""" + size = opt_parse(node, "size", version, parse_vec3) + if size and (innerSize := node.find(f"{version}:size", ns)) is not None: + size_name = attr_parse(innerSize, "name") + else: + size_name = None + return Aperture( + distance=opt_parse(node, "distance", version, parse_quantity), + size=size, + size_name=size_name, + name=attr_parse(node, "name"), + type_=attr_parse(node, "type"), + ) + + +def parse_collimation(node: etree._Element, version: str) -> Collimation: + """Parse a beam collimation""" + return Collimation( + length=opt_parse(node, "length", version, parse_quantity), + apertures=all_parse(node, "aperture", version, parse_aperture), + ) + + +def parse_instrument(node: etree._Element, version: str) -> Instrument: + """Parse instrument metadata""" + source = opt_parse(node, "SASsource", version, parse_source) + detector = all_parse(node, "SASdetector", version, parse_detector) + collimations = all_parse(node, "SAScollimation", version, parse_collimation) + return Instrument(source=source, detector=detector, collimations=collimations) + + +def parse_sample(node: etree._Element, version: str) -> Sample: + """Parse sample metadata""" + return Sample( + name=attr_parse(node, "name"), + sample_id=opt_parse(node, "ID", version, parse_string), + thickness=opt_parse(node, "thickness", version, parse_quantity), + transmission=opt_parse( + node, "transmission", version, lambda n, v: float(parse_string(n, v)) + ), + temperature=opt_parse(node, "temperature", version, parse_quantity), + position=opt_parse(node, "position", version, parse_vec3), + orientation=opt_parse(node, "orientation", version, parse_rot3), + details=all_parse(node, "details", version, parse_string), + ) + + +def parse_data(node: etree._Element, version: str) -> dict[str, Quantity]: + """Parse scattering data""" + aos = [] + keys = set() + # Units for quantities + us: dict[str, Unit] = {} + for idata in node.findall(f"{version}:Idata", ns): + struct = {} + for value in idata.getchildren(): + name = etree.QName(value).localname + if value.text is None or parse_string(value, version).strip() == "": + continue + if name not in us: + unit = ( + unit_parser.parse(value.attrib["unit"]) + if "unit" in value.attrib + else unitless + ) + us[name] = unit + struct[name] = float(parse_string(value, version)) + keys.add(name) + aos.append(struct) + + # Convert array of structures to structure of arrays + soa: dict[str, list[float]] = {} + for key in keys: + soa[key] = [] + for point in aos: + for key in keys: + if key in point: + soa[key].append(point[key]) + else: + soa[key].append(np.nan) + + uncertainties = set([x for x in keys if x.endswith("dev") and x[:-3] in keys]) + keys = keys.difference(uncertainties) + + result: dict[str, Quantity] = {} + for k in keys: + result[k] = Quantity(np.array(soa[k]), us[k]) + if k + "dev" in uncertainties: + result[k] = result[k].with_standard_error( + Quantity(np.array(soa[k + "dev"]), us[k + "dev"]) + ) + + return result + + +def get_cansas_version(root: etree._Element) -> str | None: + """Find the cansas version of a file""" + for n, v in ns.items(): + if root.tag == "{" + v + "}SASroot": + return n + return None + + +def load_raw(node: etree._Element, version: str) -> MetaNode: + attrib = {k: v for k, v in node.attrib.items()} + nodes = [n for n in node if not isinstance(n, etree._Comment)] + contents: Quantity[float] | str | list[MetaNode] = "" + if nodes: + contents = [load_raw(n, version) for n in nodes] + else: + if "unit" in attrib and attrib["unit"]: + value = parse_string(node, version) + if value: + try: + contents = Quantity(float(value), unit_parser.parse(attrib["unit"])) + except ValueError: + contents = value + else: + contents = value + else: + contents = parse_string(node, version) + return MetaNode(name=etree.QName(node).localname, attrs=attrib, contents=contents) + + +def load_data(filename: str) -> dict[str, SasData]: + """Load scattering data from an XML file""" + loaded_data: dict[str, SasData] = {} + tree = etree.parse(filename) + root = tree.getroot() + + version = get_cansas_version(root) + + if version is None: + logger.error(f"Invalid root: {root.tag!r}") + return loaded_data + + # How many data sets have we loaded? + dataindex = 1 + + for entry in tree.getroot().findall(f"{version}:SASentry", ns): + name = attr_parse(entry, "name") + + title = opt_parse(entry, "Title", version, parse_string) + + if name is None: + if title is None: + name = f"SasData{dataindex:02}" + else: + name = title + + metadata = Metadata( + title=title, + run=all_parse(entry, "Run", version, parse_string), + instrument=opt_parse(entry, "SASinstrument", version, parse_instrument), + process=all_parse(entry, "SASprocess", version, parse_process), + sample=opt_parse(entry, "SASsample", version, parse_sample), + definition=opt_parse(entry, "SASdefinition", version, parse_string), + raw=load_raw(root, version), + ) + + data = {} + + datacount = 0 + for n in entry.findall(f"{version}:SASdata", ns): + datacount += 1 + data_set = parse_data(n, version) + data = data_set + break + + loaded_data[name] = SasData( + name=name, + dataset_type=one_dim, + data_contents=data, + metadata=metadata, + verbose=False, + ) + dataindex += 1 + return loaded_data + + +if __name__ == "__main__": + import sys + + if len(sys.argv) > 1: + files = sys.argv[1:] + else: + files = [test_file] + + for f in files: + print(f) + data = load_data(f) + + for dataset in data.values(): + print(dataset.summary()) diff --git a/sasdata/transforms/post_process.py b/sasdata/transforms/post_process.py new file mode 100644 index 00000000..e69de29b diff --git a/sasdata/transforms/rebinning.py b/sasdata/transforms/rebinning.py new file mode 100644 index 00000000..7114e582 --- /dev/null +++ b/sasdata/transforms/rebinning.py @@ -0,0 +1,260 @@ +""" Algorithms for interpolation and rebinning """ + +import numpy as np +from numpy._typing import ArrayLike + +from sasdata.quantities.quantity import Quantity +from scipy.sparse import coo_matrix + +from enum import Enum + +class InterpolationOptions(Enum): + NEAREST_NEIGHBOUR = 0 + LINEAR = 1 + CUBIC = 3 + +class InterpolationError(Exception): + """ We probably want to raise exceptions because interpolation is not appropriate/well-defined, + not the same as numerical issues that will raise ValueErrors""" + + +def calculate_interpolation_matrix_1d(input_axis: Quantity[ArrayLike], + output_axis: Quantity[ArrayLike], + mask: ArrayLike | None = None, + order: InterpolationOptions = InterpolationOptions.LINEAR, + is_density=False): + + """ Calculate the matrix that converts values recorded at points specified by input_axis to + values recorded at points specified by output_axis""" + + # We want the input values in terms of the output units, will implicitly check compatability + # TODO: incorporate mask + + working_units = output_axis.units + + input_x = input_axis.in_units_of(working_units) + output_x = output_axis.in_units_of(working_units) + + # Get the array indices that will map the array to a sorted one + input_sort = np.argsort(input_x) + output_sort = np.argsort(output_x) + + input_unsort = np.arange(len(input_x), dtype=int)[input_sort] + output_unsort = np.arange(len(output_x), dtype=int)[output_sort] + + sorted_in = input_x[input_sort] + sorted_out = output_x[output_sort] + + n_in = len(sorted_in) + n_out = len(sorted_out) + + conversion_matrix = None # output + + match order: + case InterpolationOptions.NEAREST_NEIGHBOUR: + + # COO Sparse matrix definition data + i_entries = [] + j_entries = [] + + crossing_points = 0.5*(sorted_out[1:] + sorted_out[:-1]) + + # Find the output values nearest to each of the input values + i=0 + for k, crossing_point in enumerate(crossing_points): + while i < n_in and sorted_in[i] < crossing_point: + i_entries.append(i) + j_entries.append(k) + i += 1 + + # All the rest in the last bin + while i < n_in: + i_entries.append(i) + j_entries.append(n_out-1) + i += 1 + + i_entries = input_unsort[np.array(i_entries, dtype=int)] + j_entries = output_unsort[np.array(j_entries, dtype=int)] + values = np.ones_like(i_entries, dtype=float) + + conversion_matrix = coo_matrix((values, (i_entries, j_entries)), shape=(n_in, n_out)) + + case InterpolationOptions.LINEAR: + + # Leverage existing linear interpolation methods to get the mapping + # do a linear interpolation on indices + # the floor should give the left bin + # the ceil should give the right bin + # the fractional part should give the relative weightings + + input_indices = np.arange(n_in, dtype=int) + output_indices = np.arange(n_out, dtype=int) + + fractional = np.interp(x=sorted_out, xp=sorted_in, fp=input_indices, left=0, right=n_in-1) + + left_bins = np.floor(fractional).astype(int) + right_bins = np.ceil(fractional).astype(int) + + right_weight = fractional % 1 + left_weight = 1 - right_weight + + # There *should* be no repeated entries for both i and j in the main part, but maybe at the ends + # If left bin is the same as right bin, then we only want one entry, and the weight should be 1 + + same = left_bins == right_bins + not_same = ~same + + same_bins = left_bins[same] # could equally be right bins, they're the same + + same_indices = output_indices[same] + not_same_indices = output_indices[not_same] + + j_entries_sorted = np.concatenate((same_indices, not_same_indices, not_same_indices)) + i_entries_sorted = np.concatenate((same_bins, left_bins[not_same], right_bins[not_same])) + + i_entries = input_unsort[i_entries_sorted] + j_entries = output_unsort[j_entries_sorted] + + # weights don't need to be unsorted # TODO: check this is right, it should become obvious if we use unsorted data + weights = np.concatenate((np.ones_like(same_bins, dtype=float), left_weight[not_same], right_weight[not_same])) + + conversion_matrix = coo_matrix((weights, (i_entries, j_entries)), shape=(n_in, n_out)) + + case InterpolationOptions.CUBIC: + # Cubic interpolation, much harder to implement because we can't just cheat and use numpy + + input_indices = np.arange(n_in, dtype=int) + output_indices = np.arange(n_out, dtype=int) + + # Find the location of the largest value in sorted_in that + # is less than every value of sorted_out + lower_bound = ( + np.sum(np.where(np.less.outer(sorted_in, sorted_out), 1, 0), axis=0) - 1 + ) + + # We're using the Finite Difference Cubic Hermite spline + # https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Interpolation_on_an_arbitrary_interval + # https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Finite_difference + + x1 = sorted_in[lower_bound] # xₖ on the wiki + x2 = sorted_in[lower_bound + 1] # xₖ₊₁ on the wiki + + x0 = sorted_in[lower_bound[lower_bound - 1 >= 0] - 1] # xpₖ₋₁ on the wiki + x0 = np.hstack([np.zeros(x1.size - x0.size), x0]) + + x3 = sorted_in[ + lower_bound[lower_bound + 2 < sorted_in.size] + 2 + ] # xₖ₊₂ on the wiki + x3 = np.hstack([x3, np.zeros(x2.size - x3.size)]) + + t = (sorted_out - x1) / (x2 - x1) # t on the wiki + + y0 = ( + -t * (x1 - x2) * (t**2 - 2 * t + 1) / (2 * x0 - 2 * x1) + ) # The coefficient to pₖ₋₁ on the wiki + y1 = ( + -t * (t**2 - 2 * t + 1) * (x0 - 2 * x1 + x2) + + (x0 - x1) * (3 * t**3 - 5 * t**2 + 2) + ) / (2 * (x0 - x1)) # The coefficient to pₖ + y2 = ( + t + * ( + -t * (t - 1) * (x1 - 2 * x2 + x3) + + (x2 - x3) * (-3 * t**2 + 4 * t + 1) + ) + / (2 * (x2 - x3)) + ) # The coefficient to pₗ₊₁ + y3 = t**2 * (t - 1) * (x1 - x2) / (2 * (x2 - x3)) # The coefficient to pₖ₊₂ + + conversion_matrix = np.zeros((n_in, n_out)) + + (row, column) = np.indices(conversion_matrix.shape) + + mask1 = row == lower_bound[column] + + conversion_matrix[np.roll(mask1, -1, axis=0)] = y0 + conversion_matrix[mask1] = y1 + conversion_matrix[np.roll(mask1, 1, axis=0)] = y2 + + # Special boundary condition for y3 + pick = np.roll(mask1, 2, axis=0) + pick[0:1, :] = 0 + if pick.any(): + conversion_matrix[pick] = y3 + + case _: + raise InterpolationError(f"Unsupported interpolation order: {order}") + + if mask is None: + return conversion_matrix, None + + else: + # Create a new mask + + # Convert to numerical values + # Conservative masking: anything touched by the previous mask is now masked + new_mask = (np.array(mask, dtype=float) @ conversion_matrix) != 0.0 + + return conversion_matrix, new_mask + + +def calculate_interpolation_matrix_2d_axis_axis(input_1: Quantity[ArrayLike], + input_2: Quantity[ArrayLike], + output_1: Quantity[ArrayLike], + output_2: Quantity[ArrayLike], + mask, + order: InterpolationOptions = InterpolationOptions.LINEAR, + is_density: bool = False): + + # This is just the same 1D matrices things + + match order: + case InterpolationOptions.NEAREST_NEIGHBOUR: + pass + + case InterpolationOptions.LINEAR: + pass + + case InterpolationOptions.CUBIC: + pass + + case _: + pass + + +def calculate_interpolation_matrix(input_axes: list[Quantity[ArrayLike]], + output_axes: list[Quantity[ArrayLike]], + data: ArrayLike | None = None, + mask: ArrayLike | None = None): + + # TODO: We probably should delete this, but lets keep it for now + + if len(input_axes) not in (1, 2): + raise InterpolationError("Interpolation is only supported for 1D and 2D data") + + if len(input_axes) == 1 and len(output_axes) == 1: + # Check for dimensionality + input_axis = input_axes[0] + output_axis = output_axes[0] + + if len(input_axis.value.shape) == 1: + if len(output_axis.value.shape) == 1: + calculate_interpolation_matrix_1d() + + if len(output_axes) != len(input_axes): + # Input or output axes might be 2D matrices + pass + + + +def rebin(data: Quantity[ArrayLike], + axes: list[Quantity[ArrayLike]], + new_axes: list[Quantity[ArrayLike]], + mask: ArrayLike | None = None, + interpolation_order: int = 1): + + """ This algorithm is only for operations that preserve dimensionality, + i.e. non-projective rebinning. + """ + + pass \ No newline at end of file diff --git a/sasdata/trend.py b/sasdata/trend.py new file mode 100644 index 00000000..45ce2909 --- /dev/null +++ b/sasdata/trend.py @@ -0,0 +1,87 @@ +from dataclasses import dataclass +from sasdata.data import SasData +from sasdata.data_backing import Dataset, Group +import numpy as np +from sasdata.quantities.quantity import Quantity +from sasdata.transforms.rebinning import calculate_interpolation_matrix_1d + +# Axis strs refer to the name of their associated NamedQuantity. + +# TODO: This probably shouldn't be here but will keep it here for now. +# TODO: Not sure how to type hint the return. +def get_metadatum_from_path(data: SasData, metadata_path: list[str]): + current_group = data._raw_metadata + for path_item in metadata_path: + current_item = current_group.children.get(path_item, None) + if current_item is None or (isinstance(current_item, Dataset) and path_item != metadata_path[-1]): + raise ValueError('Path does not lead to valid a metadatum.') + elif isinstance(current_item, Group): + current_group = current_item + else: + return current_item.data + raise ValueError('End of path without finding a dataset.') + + +@dataclass +class Trend: + data: list[SasData] + # This is going to be a path to a specific metadatum. + # + # TODO: But what if the trend axis will be a particular NamedQuantity? Will probably need to think on this. + trend_axis: list[str] + + # Designed to take in a particular value of the trend axis, and return the SasData object that matches it. + # TODO: Not exaclty sure what item's type will be. It could depend on where it is pointing to. + def __getitem__(self, item) -> SasData: + for datum in self.data: + metadatum = get_metadatum_from_path(datum, self.trend_axis) + if metadatum == item: + return datum + raise KeyError() + @property + def trend_axes(self) -> list[float]: + return [get_metadatum_from_path(datum, self.trend_axis) for datum in self.data] + + # TODO: Assumes there are at least 2 items in data. Is this reasonable to assume? Should there be error handling for + # situations where this may not be the case? + def all_axis_match(self, axis: str) -> bool: + reference_data = self.data[0] + data_axis = reference_data[axis] + for datum in self.data[1::]: + axis_datum = datum[axis] + # FIXME: Linter is complaining about typing. + if not np.all(np.isclose(axis_datum.value, data_axis.value)): + return False + return True + + # TODO: For now, return a new trend, but decide later. Shouldn't be too hard to change. + def interpolate(self, axis: str) -> "Trend": + new_data: list[SasData] = [] + reference_data = self.data[0] + # TODO: I don't like the repetition here. Can probably abstract a function for this ot make it clearer. + data_axis = reference_data[axis] + for i, datum in enumerate(self.data): + if i == 0: + # This is already the reference axis; no need to interpolate it. + continue + # TODO: Again, repetition + axis_datum = datum[axis] + # TODO: There are other options which may need to be filled (or become new params to this method) + mat, _ = calculate_interpolation_matrix_1d(axis_datum, data_axis) + new_quantities: dict[str, Quantity] = {} + for name, quantity in datum._data_contents.items(): + if name == axis: + new_quantities[name] = data_axis + continue + new_quantities[name] = quantity @ mat + + new_datum = SasData( + name=datum.name, + data_contents=new_quantities, + dataset_type=datum.dataset_type, + metadata=datum.metadata, + ) + new_data.append(new_datum) + new_trend = Trend(new_data, + self.trend_axis) + return new_trend diff --git a/sasdata/util.py b/sasdata/util.py new file mode 100644 index 00000000..2cd6e3f4 --- /dev/null +++ b/sasdata/util.py @@ -0,0 +1,17 @@ +from typing import TypeVar, Callable + +T = TypeVar("T") + +def cache[T](fun: Callable[[], T]): + """ Decorator to store values """ + + cache_state = [False, None] + + def wrapper() -> T: + if not cache_state[0]: + cache_state[0] = True + cache_state[1] = fun() + + return cache_state[1] + + return wrapper \ No newline at end of file diff --git a/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/10_1000_1340_10.csv b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/10_1000_1340_10.csv new file mode 100644 index 00000000..8e9c7066 --- /dev/null +++ b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/10_1000_1340_10.csv @@ -0,0 +1,105 @@ +3.624299999999999744e-02 7.295645247999999583e+01 1.637502872666669873e+01 +4.068929999999999769e-02 3.496757764333329987e+01 1.132330254166670080e+01 +4.510000000000000120e-02 2.871506291000000033e+01 8.366418418333330109e+00 +4.957960000000000145e-02 3.062621534000000167e+01 6.807088010000000189e+00 +5.414959999999999912e-02 1.698840335499999910e+01 5.315930135000000334e+00 +5.867460000000000037e-02 1.810732352000000134e+01 4.582343003333329889e+00 +6.313670000000000393e-02 1.758858145166669829e+01 3.753068668333329860e+00 +6.771770000000000567e-02 1.874808681500000063e+01 3.129801861666670071e+00 +7.237100000000000477e-02 1.219239694000000007e+01 2.701832888333330018e+00 +7.692330000000000001e-02 1.502033604000000011e+01 2.415586504999999828e+00 +8.164470000000000061e-02 1.051882241166670084e+01 2.003147743333329789e+00 +8.639470000000000482e-02 1.266539752666670005e+01 1.813845073333330005e+00 +9.106759999999999855e-02 1.164225125833329955e+01 1.590423455000000041e+00 +9.573710000000000553e-02 9.248378555000000389e+00 1.453449206666669991e+00 +1.004952999999999957e-01 1.082198768499999986e+01 1.296534581666670016e+00 +1.052397000000000055e-01 9.754544071666670035e+00 1.192202876666669908e+00 +1.099886000000000058e-01 9.770793396666670461e+00 1.074958141666670031e+00 +1.148422000000000054e-01 9.667035006666669261e+00 9.803857316666669819e-01 +1.197769999999999946e-01 8.381104240000000871e+00 8.964596183333329860e-01 +1.247698000000000002e-01 7.884219706666669936e+00 8.320707283333329540e-01 +1.298370000000000080e-01 8.175124921666670375e+00 7.497416000000000080e-01 +1.349077999999999944e-01 7.421197476666669957e+00 7.207336583333330271e-01 +1.399244000000000043e-01 8.545903931666670061e+00 6.648524133333330033e-01 +1.450733999999999913e-01 7.172791390000000433e+00 6.314494133333330428e-01 +1.502658000000000049e-01 6.110795001666669890e+00 5.924928833333330536e-01 +1.556062000000000001e-01 7.011488443333329990e+00 5.430119199999999813e-01 +1.572456999999999883e-01 7.121472013333329798e+00 1.740741033333330079e-01 +1.609471000000000096e-01 7.438929439999999893e+00 5.263131250000000483e-01 +1.661874999999999880e-01 6.540084590000000198e+00 4.946959550000000205e-01 +1.706292000000000086e-01 6.367655253333330378e+00 1.455746566666669961e-01 +1.715615000000000057e-01 6.476111691666670112e+00 4.707941449999999972e-01 +1.771270000000000067e-01 5.720913878333329983e+00 4.355610400000000104e-01 +1.827315999999999940e-01 6.117868691666670244e+00 4.186226383333330192e-01 +1.842428999999999872e-01 5.857041180000000402e+00 1.254778649999999940e-01 +1.883173999999999959e-01 5.849837826666670182e+00 4.069392433333329784e-01 +1.939794999999999991e-01 5.447672994999999574e+00 3.927526633333329742e-01 +1.982414999999999872e-01 5.391186461666669594e+00 1.085076650000000031e-01 +1.997663000000000078e-01 5.394637556666670442e+00 3.662608233333329855e-01 +2.055810999999999888e-01 5.784545713333329786e+00 3.603836316666669815e-01 +2.114655000000000007e-01 4.574601945000000391e+00 3.312713700000000094e-01 +2.120113000000000136e-01 5.084515549999999884e+00 9.927742500000000248e-02 +2.161903000000000019e-01 4.516358434999999893e+00 4.168106166666670220e-01 +2.259823999999999999e-01 4.782964421666670241e+00 8.791053666666670541e-02 +2.397845000000000115e-01 4.501217386666669817e+00 8.259429166666669431e-02 +2.537096999999999825e-01 4.179436571666670375e+00 7.562756833333329765e-02 +2.676275000000000182e-01 3.979478888333329856e+00 6.987285499999999761e-02 +2.817857999999999752e-01 3.702226940000000077e+00 6.482911333333329917e-02 +2.956398000000000081e-01 3.605992423333329810e+00 6.203536333333330155e-02 +3.099044000000000243e-01 3.377018353333329781e+00 5.670042833333330257e-02 +3.240645999999999805e-01 3.095807218333329836e+00 5.498817999999999762e-02 +3.388027000000000122e-01 2.841171323333330001e+00 4.897826333333329951e-02 +3.539140000000000064e-01 2.795649415000000193e+00 4.845330666666670255e-02 +3.687090000000000090e-01 2.622854690000000044e+00 4.475559833333329907e-02 +3.839813000000000254e-01 2.527809641666669993e+00 4.273165666666670082e-02 +3.988975000000000160e-01 2.232305030000000023e+00 4.094381166666669764e-02 +4.106734000000000218e-01 2.129986383333330124e+00 4.154637366666669857e-02 +4.136514000000000024e-01 2.084593566666669950e+00 3.891198166666669928e-02 +4.290628000000000219e-01 1.988620215000000080e+00 3.594587333333330165e-02 +4.295516999999999808e-01 1.964928603499999982e+00 3.519640899999999795e-02 +4.446014999999999828e-01 1.750614441666670018e+00 3.497378000000000292e-02 +4.466716000000000020e-01 1.709819834666669980e+00 3.112847050000000157e-02 +4.603325999999999807e-01 1.676685876666669905e+00 3.265084166666670090e-02 +4.685437000000000074e-01 1.638395569999999912e+00 3.101072850000000103e-02 +4.764860000000000206e-01 1.575855291666669933e+00 3.097528500000000171e-02 +4.834083000000000130e-01 1.443926158166670026e+00 2.913516383333330032e-02 +4.923204999999999942e-01 1.443778989999999984e+00 3.072023333333330150e-02 +5.046621000000000024e-01 1.382474827333330047e+00 2.709115466666670025e-02 +5.082708000000000226e-01 1.368563626666670086e+00 2.832584499999999880e-02 +5.230728999999999518e-01 1.117650907000000027e+00 2.515010599999999846e-02 +5.249162000000000550e-01 1.271594368333329950e+00 2.652784333333330080e-02 +5.416775999999999813e-01 1.132208539999999930e+00 2.601087833333329963e-02 +5.444001000000000534e-01 1.080469498666670081e+00 2.559191900000000117e-02 +5.583067999999999920e-01 1.032465565000000085e+00 2.510816333333330125e-02 +5.612614999999999688e-01 1.012286258500000091e+00 2.336018449999999885e-02 +5.753956999999999544e-01 9.763539783333330391e-01 2.332539999999999961e-02 +5.834072999999999620e-01 8.965217239999999643e-01 2.303063099999999933e-02 +5.927398999999999862e-01 9.096560916666670549e-01 2.248173499999999922e-02 +6.005747000000000169e-01 8.250965611666669641e-01 2.186496933333329992e-02 +6.099058000000000535e-01 8.168162183333329551e-01 2.170944500000000082e-02 +6.218546000000000351e-01 7.414260915000000507e-01 2.121375600000000028e-02 +6.275003000000000108e-01 7.444278466666669480e-01 2.046154333333330064e-02 +6.412082999999999533e-01 6.366966366666669819e-01 1.883217216666669899e-02 +6.447154999999999969e-01 6.422991433333330447e-01 2.075286000000000144e-02 +6.655497999999999692e-01 5.866493959999999896e-01 1.815403650000000160e-02 +6.843702000000000396e-01 5.515869356666670553e-01 2.025276583333330063e-02 +7.058105999999999547e-01 4.475886111666669831e-01 1.701240433333330027e-02 +7.258693999999999980e-01 4.775394179999999933e-01 1.840697383333329828e-02 +7.490480000000000471e-01 4.047520649999999942e-01 1.505153366666669990e-02 +7.720086999999999922e-01 3.731668894999999875e-01 1.706827766666670076e-02 +7.920989999999999975e-01 3.681129878333330163e-01 1.507231049999999996e-02 +8.155097999999999514e-01 3.208610430000000124e-01 1.529166149999999953e-02 +8.364475000000000104e-01 2.962226738333330056e-01 1.456896033333330079e-02 +8.619377000000000288e-01 2.765821119999999911e-01 1.371801400000000060e-02 +8.846403999999999934e-01 2.799105873333330163e-01 1.446314666666670065e-02 +9.081462000000000145e-01 2.318482950000000098e-01 1.260493333333330065e-02 +9.334447000000000161e-01 2.175556724999999914e-01 1.357374916666670081e-02 +9.551832000000000100e-01 1.903255399999999875e-01 1.273357800000000060e-02 +9.812971999999999806e-01 1.841620115000000002e-01 1.195187833333329931e-02 +1.006807800000000030e+00 1.608915558333330054e-01 1.233432733333329930e-02 +1.030406600000000061e+00 1.533644885000000069e-01 1.172346633333330029e-02 +1.057784300000000011e+00 1.661925581666670038e-01 1.091501716666670035e-02 +1.084600999999999926e+00 1.630933589999999933e-01 1.128103283333329980e-02 +1.109902299999999897e+00 1.327154809999999963e-01 1.088576583333330031e-02 +1.137751600000000085e+00 1.179623709999999964e-01 1.042595833333329926e-02 +1.166036300000000026e+00 1.293750036666669878e-01 1.024355400000000020e-02 \ No newline at end of file diff --git a/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/11_2000_1340_10.csv b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/11_2000_1340_10.csv new file mode 100644 index 00000000..bfbf36dc --- /dev/null +++ b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/11_2000_1340_10.csv @@ -0,0 +1,105 @@ +3.625559999999999894e-02 4.452644707000000324e+01 1.628119149999999848e+01 +4.070349999999999663e-02 2.677295466333330154e+01 1.128515222166669929e+01 +4.511570000000000163e-02 2.350029203500000108e+01 8.338724138333329705e+00 +4.959680000000000338e-02 1.744413458166669884e+01 6.751264438333329565e+00 +5.416850000000000137e-02 9.909433441666669395e+00 5.280963783333329609e+00 +5.869510000000000005e-02 1.174714312999999954e+01 4.547423594999999708e+00 +6.315869999999999818e-02 1.312634901166670076e+01 3.725749208333330120e+00 +6.774130000000000429e-02 1.654730030333330149e+01 3.113655078333330106e+00 +7.239619999999999389e-02 7.792796271666669661e+00 2.672180411666670086e+00 +7.695009999999999351e-02 1.265325572666669984e+01 2.397233363333330036e+00 +8.167309999999999848e-02 6.069752713333330441e+00 1.971069876666670107e+00 +8.642469999999999319e-02 1.300927747833329917e+01 1.813635340000000040e+00 +9.109929999999999417e-02 9.757094948333330464e+00 1.573592551666670003e+00 +9.577040000000000552e-02 5.713518095000000407e+00 1.422093728333329921e+00 +1.005303000000000030e-01 7.313534973333330136e+00 1.265234731666670109e+00 +1.052763000000000032e-01 8.841184973333330532e+00 1.182077201666670074e+00 +1.100268999999999969e-01 7.404252198333329815e+00 1.051346621666670034e+00 +1.148821000000000009e-01 8.380590695000000423e+00 9.664303683333329564e-01 +1.198187000000000002e-01 7.413483668333330279e+00 8.855263516666670442e-01 +1.248133000000000020e-01 6.593134025000000342e+00 8.175650066666669824e-01 +1.298822000000000032e-01 6.731246111666670195e+00 7.333046399999999521e-01 +1.349548000000000136e-01 5.519098301666669926e+00 6.986229283333329487e-01 +1.399730999999999892e-01 7.581442331666670142e+00 6.528076566666669578e-01 +1.451239000000000001e-01 4.842304316666670161e+00 6.043489516666670225e-01 +1.503181000000000100e-01 5.135523888333329623e+00 5.802922499999999539e-01 +1.556604000000000043e-01 6.037988904999999740e+00 5.310080216666670516e-01 +1.573070000000000024e-01 6.020141886666669606e+00 1.700767950000000028e-01 +1.610031000000000101e-01 6.268605329999999753e+00 5.111983383333329467e-01 +1.662453999999999876e-01 5.536486726666669966e+00 4.815289200000000269e-01 +1.706957000000000058e-01 5.285752210000000062e+00 1.415798849999999887e-01 +1.716212000000000015e-01 5.016643598333329734e+00 4.522174083333330152e-01 +1.771887000000000045e-01 5.127789406666670047e+00 4.276572833333330270e-01 +1.827951999999999910e-01 5.311901800000000229e+00 4.077617766666670196e-01 +1.843146999999999980e-01 5.273006283333329769e+00 1.230909116666669967e-01 +1.883829999999999949e-01 4.028933333333330147e+00 3.832458766666669847e-01 +1.940469999999999973e-01 4.805727509999999647e+00 3.842635300000000198e-01 +1.983187999999999895e-01 4.783697818333330076e+00 1.059592133333329966e-01 +1.998358000000000079e-01 4.823453656666670142e+00 3.585670233333330126e-01 +2.056525999999999910e-01 4.659267076666670171e+00 3.454626916666669878e-01 +2.115392000000000106e-01 3.961550343333330115e+00 3.229926716666670083e-01 +2.120939000000000019e-01 4.350835990000000209e+00 9.606802500000000133e-02 +2.162656000000000023e-01 4.681036608333330129e+00 4.188060433333329891e-01 +2.260705000000000076e-01 4.224838659999999635e+00 8.541253666666670519e-02 +2.398779000000000050e-01 3.935926101666670007e+00 7.996145500000000073e-02 +2.538084999999999924e-01 3.694058728333330155e+00 7.335489833333329324e-02 +2.677318000000000198e-01 3.684019681666669932e+00 6.840629833333329579e-02 +2.818956000000000239e-01 3.452043701666669850e+00 6.355296333333329550e-02 +2.957549999999999901e-01 3.354666878333329993e+00 6.073878666666669701e-02 +3.100250999999999979e-01 3.092418856666669935e+00 5.527531000000000111e-02 +3.241909000000000041e-01 2.870290291666670157e+00 5.380070499999999728e-02 +3.389346999999999777e-01 2.729991994999999783e+00 4.836916333333329820e-02 +3.540519999999999778e-01 2.658729508333330216e+00 4.771163500000000224e-02 +3.688526999999999778e-01 2.488934241666670211e+00 4.403299000000000102e-02 +3.841308999999999974e-01 2.412009111666669980e+00 4.209804999999999797e-02 +3.990528999999999882e-01 2.203036439999999985e+00 4.070905166666669711e-02 +4.105667000000000066e-01 1.996977714666670067e+00 4.083280150000000164e-02 +4.138126999999999778e-01 2.043262161666670185e+00 3.862582833333329940e-02 +4.292300000000000004e-01 1.982205959999999934e+00 3.583282166666670182e-02 +4.294401999999999942e-01 1.834877223666669943e+00 3.458454216666669717e-02 +4.447747000000000228e-01 1.712660204999999936e+00 3.471672166666670001e-02 +4.465555999999999970e-01 1.615437673500000004e+00 3.068944266666669834e-02 +4.605119999999999769e-01 1.681440621666669966e+00 3.260043333333329657e-02 +4.684220000000000050e-01 1.529958281999999947e+00 3.049975383333329917e-02 +4.766716999999999760e-01 1.561241171666670091e+00 3.083583000000000157e-02 +4.832827000000000095e-01 1.369049060333330070e+00 2.875037449999999842e-02 +4.925123000000000140e-01 1.406416503333330015e+00 3.046807833333330107e-02 +5.045309999999999517e-01 1.316827885166669931e+00 2.677764066666669940e-02 +5.084689000000000014e-01 1.339987331666669945e+00 2.812536666666669988e-02 +5.229369999999999852e-01 1.082862526333330022e+00 2.496820599999999973e-02 +5.251208000000000542e-01 1.259172833333330077e+00 2.640966666666669932e-02 +5.418887000000000009e-01 1.134540908333329989e+00 2.596733000000000027e-02 +5.442586999999999842e-01 1.026980480500000015e+00 2.532101549999999854e-02 +5.585244000000000320e-01 1.057657990000000048e+00 2.518052499999999874e-02 +5.611156999999999950e-01 9.343203683333329845e-01 2.299225916666669881e-02 +5.756198999999999621e-01 9.598931866666670087e-01 2.319752666666670057e-02 +5.832557000000000436e-01 8.443178690000000541e-01 2.277228483333329848e-02 +5.929708999999999675e-01 9.115965033333329748e-01 2.244499833333329919e-02 +6.004186999999999719e-01 7.992612106666669991e-01 2.172028233333329894e-02 +6.101434999999999498e-01 8.528685350000000387e-01 2.184797833333329900e-02 +6.216930999999999985e-01 7.049236858333329803e-01 2.102741616666670144e-02 +6.277447999999999917e-01 7.853513183333330483e-01 2.062051499999999898e-02 +6.410417000000000476e-01 6.132204243333330140e-01 1.871036433333329863e-02 +6.449667999999999513e-01 7.011444599999999694e-01 2.101788333333329956e-02 +6.653769000000000489e-01 5.661603698333329548e-01 1.804829366666670099e-02 +6.841924999999999812e-01 5.085138883333329973e-01 2.002898349999999994e-02 +7.056272000000000100e-01 4.352650703333330040e-01 1.694186849999999855e-02 +7.256808000000000147e-01 4.355121908333329794e-01 1.820155999999999857e-02 +7.488534000000000024e-01 3.927147446666670039e-01 1.498729066666669961e-02 +7.718082000000000553e-01 3.535320510000000138e-01 1.696024266666670138e-02 +7.918933000000000222e-01 3.527103021666669891e-01 1.499266833333330086e-02 +8.152979999999999672e-01 3.240461856666669860e-01 1.528614783333329986e-02 +8.362302999999999820e-01 2.791947966666670222e-01 1.448550166666670060e-02 +8.617137999999999742e-01 2.463675190000000070e-01 1.359101549999999943e-02 +8.844106000000000467e-01 2.696912470000000228e-01 1.440389033333329925e-02 +9.079102999999999479e-01 2.246905299999999994e-01 1.256477649999999946e-02 +9.332021999999999817e-01 2.085629360000000043e-01 1.352310049999999944e-02 +9.549351000000000367e-01 1.738771078333329889e-01 1.265774100000000013e-02 +9.810423000000000338e-01 1.708308721666670082e-01 1.189189666666670003e-02 +1.006546299999999894e+00 1.505248680000000061e-01 1.228292216666669948e-02 +1.030138999999999916e+00 1.537520601666670095e-01 1.171224066666669977e-02 +1.057509599999999939e+00 1.534308791666670058e-01 1.086003916666669969e-02 +1.084319299999999986e+00 1.338797798333329903e-01 1.116524300000000004e-02 +1.109614000000000100e+00 1.189064603333330056e-01 1.082743033333329920e-02 +1.137456000000000023e+00 1.107916548333330031e-01 1.039160416666670000e-02 +1.165733499999999978e+00 1.125385351666669947e-01 1.017680349999999963e-02 \ No newline at end of file diff --git a/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/12_3000_1340_10.csv b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/12_3000_1340_10.csv new file mode 100644 index 00000000..9ae1c5f3 --- /dev/null +++ b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/12_3000_1340_10.csv @@ -0,0 +1,105 @@ +3.624610000000000332e-02 5.667029333499999666e+01 1.635708034166669833e+01 +4.069289999999999713e-02 2.663486052833329865e+01 1.131616966500000032e+01 +4.510390000000000232e-02 2.091145430999999988e+01 8.353784624999999409e+00 +4.958379999999999732e-02 1.655762528666669908e+01 6.766916158333329712e+00 +5.415430000000000244e-02 7.405398096666670149e+00 5.285856098333329811e+00 +5.867970000000000130e-02 1.609157327499999823e+01 4.579871853333330023e+00 +6.314219999999999555e-02 2.074624494500000083e+01 3.774208823333330187e+00 +6.772359999999999491e-02 1.622769223499999924e+01 3.121007619999999871e+00 +7.237720000000000264e-02 8.100976058333330343e+00 2.681633823333330113e+00 +7.692999999999999838e-02 7.297140411666670268e+00 2.370097993333330155e+00 +8.165179999999999660e-02 8.662000215000000836e+00 1.993900453333329992e+00 +8.640209999999999557e-02 1.195176863500000053e+01 1.811378463333330080e+00 +9.107550000000000368e-02 1.017929826333329935e+01 1.581605778333329937e+00 +9.574539999999999440e-02 8.299761948333330253e+00 1.447854706666670044e+00 +1.005039999999999961e-01 7.184010728333330320e+00 1.267957188333330043e+00 +1.052488000000000035e-01 7.705754226666670093e+00 1.175315996666669971e+00 +1.099981000000000014e-01 6.940137736666669888e+00 1.050239939999999983e+00 +1.148520999999999986e-01 7.227232895000000212e+00 9.582211533333330200e-01 +1.197872999999999993e-01 6.058664864999999899e+00 8.747766333333329980e-01 +1.247806000000000054e-01 5.235616768333329674e+00 8.060124016666669888e-01 +1.298481999999999970e-01 6.895559143333329644e+00 7.374701233333329498e-01 +1.349194999999999978e-01 5.596115646666669718e+00 7.017485283333330104e-01 +1.399364999999999915e-01 7.271002904999999572e+00 6.515097483333329720e-01 +1.450858999999999899e-01 5.001139740000000167e+00 6.081432883333329764e-01 +1.502787999999999902e-01 5.397041578333330314e+00 5.852662783333330010e-01 +1.556196999999999997e-01 5.201047356666670396e+00 5.230793849999999523e-01 +1.572660999999999920e-01 5.527525920000000426e+00 1.689539216666670063e-01 +1.609609999999999930e-01 6.050507654999999652e+00 5.103277550000000495e-01 +1.662019000000000135e-01 5.215723743333329665e+00 4.792083599999999999e-01 +1.706513000000000058e-01 5.031433279999999897e+00 1.411615750000000113e-01 +1.715762999999999872e-01 4.986483428333330359e+00 4.534299233333329848e-01 +1.771423000000000025e-01 5.106750501666669884e+00 4.289189133333329851e-01 +1.827474000000000043e-01 5.398633823333329751e+00 4.103611200000000236e-01 +1.842668000000000084e-01 4.914610723333329823e+00 1.221897900000000065e-01 +1.883336999999999928e-01 4.776135458333330419e+00 3.942386333333329773e-01 +1.939963000000000104e-01 4.310256373333330338e+00 3.794851616666670147e-01 +1.982673000000000074e-01 4.375084450000000125e+00 1.047416733333329936e-01 +1.997836000000000056e-01 4.425991520000000179e+00 3.548793383333330165e-01 +2.055989000000000011e-01 4.542295369999999721e+00 3.452195166666670034e-01 +2.114837999999999996e-01 4.392925553333330235e+00 3.296180283333329797e-01 +2.120387999999999995e-01 4.102498828333329683e+00 9.538819833333339604e-02 +2.162090000000000123e-01 4.067448908333330060e+00 4.099168333333330083e-01 +2.260118000000000127e-01 3.879140883333330070e+00 8.427184499999999800e-02 +2.398155999999999899e-01 3.717842756666669857e+00 7.930888666666670306e-02 +2.537425999999999848e-01 3.531988756666669893e+00 7.293178499999999898e-02 +2.676623000000000197e-01 3.501111030000000124e+00 6.786378666666670334e-02 +2.818223999999999729e-01 3.329498083333330083e+00 6.325724833333329356e-02 +2.956782000000000021e-01 3.079447669999999970e+00 5.969948499999999658e-02 +3.099446000000000145e-01 2.946954811666670171e+00 5.483356999999999815e-02 +3.241067000000000253e-01 2.705831398333330196e+00 5.323661166666669720e-02 +3.388467000000000007e-01 2.601996516666670090e+00 4.799289000000000333e-02 +3.539599999999999969e-01 2.517872383333330077e+00 4.725612833333329987e-02 +3.687568999999999986e-01 2.374362781666670141e+00 4.368410333333330037e-02 +3.840311000000000141e-01 2.195996148333330122e+00 4.125977166666670165e-02 +3.989493000000000067e-01 2.109030429999999789e+00 4.042385333333330111e-02 +4.107986000000000137e-01 1.888483604166669938e+00 4.043749766666669687e-02 +4.137051999999999952e-01 1.969810571666670063e+00 3.843483833333329741e-02 +4.291185000000000138e-01 1.859797011666669997e+00 3.539587999999999762e-02 +4.296826999999999730e-01 1.762954635166670059e+00 3.439219566666670141e-02 +4.446591999999999767e-01 1.624618943333330012e+00 3.443567000000000156e-02 +4.468077999999999772e-01 1.588049316333330019e+00 3.067751716666669917e-02 +4.603923999999999794e-01 1.585295316666669896e+00 3.227559333333329672e-02 +4.686866000000000088e-01 1.509168766666669992e+00 3.051112400000000058e-02 +4.765479000000000243e-01 1.429500135000000061e+00 3.031986666666669841e-02 +4.835556999999999772e-01 1.309360340500000053e+00 2.857159116666670162e-02 +4.923843999999999999e-01 1.363123808333329912e+00 3.037624166666669928e-02 +5.048160000000000425e-01 1.296483956833329954e+00 2.677855866666669846e-02 +5.083368000000000331e-01 1.254868451666669937e+00 2.782200166666669999e-02 +5.232324000000000419e-01 1.028253859833329953e+00 2.481625150000000071e-02 +5.249844000000000177e-01 1.223231273333329927e+00 2.634227000000000096e-02 +5.417480000000000073e-01 1.058531658333329961e+00 2.569255333333329838e-02 +5.445661000000000529e-01 1.022632484000000064e+00 2.537817800000000124e-02 +5.583793000000000228e-01 1.009770163333330029e+00 2.504100833333329848e-02 +5.614325999999999484e-01 9.522996788333329965e-01 2.313905700000000107e-02 +5.754704000000000486e-01 9.158985283333339611e-01 2.307270666666669939e-02 +5.835850999999999678e-01 8.544030651666669751e-01 2.288279616666670166e-02 +5.928168000000000326e-01 8.553651300000000290e-01 2.224781666666670113e-02 +6.007578000000000085e-01 7.747371155000000176e-01 2.167619850000000042e-02 +6.099849999999999994e-01 7.987110383333330121e-01 2.165553833333330042e-02 +6.220442000000000471e-01 6.615610463333330138e-01 2.089781950000000124e-02 +6.275817000000000201e-01 7.150054516666669580e-01 2.035196333333329916e-02 +6.414037999999999684e-01 6.088626698333330367e-01 1.874337833333329997e-02 +6.447992000000000168e-01 6.630811316666670452e-01 2.089752166666669977e-02 +6.657526999999999751e-01 5.716572943333330103e-01 1.811807299999999843e-02 +6.845788999999999902e-01 5.092866856666670161e-01 2.008440916666669879e-02 +7.060256999999999783e-01 4.113589818333330261e-01 1.688841199999999848e-02 +7.260906999999999778e-01 4.258370573333329911e-01 1.820484666666670123e-02 +7.492763000000000062e-01 3.814062795000000006e-01 1.498164383333329928e-02 +7.722440999999999889e-01 3.582434820000000020e-01 1.702263550000000097e-02 +7.923405000000000031e-01 3.290160251666670033e-01 1.493438950000000078e-02 +8.157583999999999946e-01 3.190799841666669967e-01 1.530228649999999975e-02 +8.367025000000000157e-01 2.762952625000000273e-01 1.450753649999999943e-02 +8.622005000000000363e-01 2.544186793333330088e-01 1.365137966666669922e-02 +8.849101000000000328e-01 2.424038613333329983e-01 1.432358516666669933e-02 +9.084231000000000389e-01 2.172930054999999971e-01 1.256612799999999933e-02 +9.337292000000000369e-01 2.202721591666670087e-01 1.359930866666670020e-02 +9.554743999999999460e-01 1.777866800000000025e-01 1.269970833333330072e-02 +9.815962999999999772e-01 1.676419470000000134e-01 1.190602366666669923e-02 +1.007114700000000029e+00 1.595580418333329975e-01 1.234223233333330005e-02 +1.030720700000000045e+00 1.541775503333329966e-01 1.173861049999999975e-02 +1.058106800000000014e+00 1.554276226666669869e-01 1.088984299999999975e-02 +1.084931700000000054e+00 1.440364893333329899e-01 1.122487333333329999e-02 +1.110240600000000022e+00 1.198653424999999995e-01 1.085281416666670010e-02 +1.138098400000000066e+00 1.211473946666670048e-01 1.044690799999999954e-02 +1.166391799999999979e+00 1.189307901666669942e-01 1.021903716666669980e-02 \ No newline at end of file diff --git a/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/13_8000_1340_10.csv b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/13_8000_1340_10.csv new file mode 100644 index 00000000..ee5f13e0 --- /dev/null +++ b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/13_8000_1340_10.csv @@ -0,0 +1,105 @@ +3.625239999999999713e-02 6.302187949999999717e+01 1.635904319500000170e+01 +4.070000000000000007e-02 2.785521429499999968e+01 1.130956581166669928e+01 +4.511180000000000051e-02 1.008215536333329965e+01 8.312546360000000689e+00 +4.959249999999999770e-02 1.581932412333330085e+01 6.758513281666670203e+00 +5.416379999999999806e-02 7.750620351666669627e+00 5.282679641666669923e+00 +5.868999999999999911e-02 1.257659237500000060e+01 4.560014873333329888e+00 +6.315320000000000655e-02 1.891331758666670027e+01 3.761777425000000008e+00 +6.773540000000000116e-02 1.328150150333330082e+01 3.102479755000000061e+00 +7.238989999999999314e-02 9.795755306666670492e+00 2.689289660000000026e+00 +7.694339999999999513e-02 1.021541878833330053e+01 2.386645821666669942e+00 +8.166600000000000248e-02 7.264236756666670125e+00 1.982897978333330036e+00 +8.641719999999999957e-02 9.727612021666670827e+00 1.793249476666670006e+00 +9.109140000000000292e-02 9.940133039999999198e+00 1.578285125000000066e+00 +9.576210000000000278e-02 5.869161616666669801e+00 1.426304660000000002e+00 +1.005216000000000026e-01 6.271050506666670188e+00 1.258989316666669911e+00 +1.052672000000000052e-01 4.575212496666670070e+00 1.144957958333330028e+00 +1.100174000000000013e-01 6.675946670000000083e+00 1.046739891666669919e+00 +1.148721999999999938e-01 4.569861300000000348e+00 9.307519933333330275e-01 +1.198083000000000065e-01 4.073412075000000243e+00 8.536948699999999945e-01 +1.248023999999999939e-01 5.517398765000000260e+00 8.081908083333330106e-01 +1.298709000000000113e-01 6.055929844999999645e+00 7.277152516666669513e-01 +1.349431000000000103e-01 4.214499321666670184e+00 6.853886750000000028e-01 +1.399610000000000021e-01 4.711855326666669619e+00 6.206856016666669751e-01 +1.451112999999999986e-01 4.280988370000000209e+00 5.992362466666669718e-01 +1.503050999999999970e-01 3.350940026666669791e+00 5.604130616666670450e-01 +1.556469000000000047e-01 4.037392881666669986e+00 5.086757983333329847e-01 +1.572660999999999920e-01 4.116049464999999685e+00 1.639383783333329958e-01 +1.609891999999999990e-01 4.767404383333330387e+00 4.936060199999999787e-01 +1.662309999999999899e-01 4.092517224999999925e+00 4.643677516666669947e-01 +1.706513000000000058e-01 3.775210913333329810e+00 1.365719850000000068e-01 +1.716062999999999894e-01 4.067082121666669714e+00 4.413313999999999848e-01 +1.771733000000000058e-01 3.397890750000000182e+00 4.070909349999999871e-01 +1.827794000000000085e-01 4.061001236666670344e+00 3.925402433333329832e-01 +1.842668000000000084e-01 3.799252254999999856e+00 1.178310383333329991e-01 +1.883665999999999952e-01 3.208655455000000156e+00 3.733191000000000148e-01 +1.940302000000000138e-01 3.206445976666670195e+00 3.650098783333329822e-01 +1.982673000000000074e-01 3.425276903333330125e+00 1.008325449999999956e-01 +1.998185000000000100e-01 3.354815260000000077e+00 3.406662516666669749e-01 +2.056348000000000065e-01 3.721227830000000125e+00 3.339491649999999923e-01 +2.115208000000000088e-01 3.612948971666670062e+00 3.193275150000000062e-01 +2.120387999999999995e-01 3.205896688333329969e+00 9.145261000000000362e-02 +2.162467999999999890e-01 3.645113248333330169e+00 4.021025750000000176e-01 +2.260118000000000127e-01 3.104582965000000083e+00 8.081593333333329798e-02 +2.398155999999999899e-01 3.037513673333330111e+00 7.613482000000000582e-02 +2.537425999999999848e-01 2.836682165000000033e+00 6.969811833333329487e-02 +2.676623000000000197e-01 2.819590748333329788e+00 6.462849833333329796e-02 +2.818223999999999729e-01 2.721404538333330070e+00 6.033223166666670106e-02 +2.956782000000000021e-01 2.736251186666669888e+00 5.799425166666669768e-02 +3.099446000000000145e-01 2.543946483333329844e+00 5.286760000000000070e-02 +3.241067000000000253e-01 2.287160831666669836e+00 5.112056499999999976e-02 +3.388467000000000007e-01 2.288673078333329780e+00 4.649771999999999933e-02 +3.539599999999999969e-01 2.199149091666670053e+00 4.568711999999999773e-02 +3.687568999999999986e-01 2.068072668333329922e+00 4.217328666666669834e-02 +3.840311000000000141e-01 1.969221529999999998e+00 4.012745166666670249e-02 +3.989493000000000067e-01 1.908232941666669902e+00 3.938963333333329875e-02 +4.107629999999999892e-01 1.727702871333330004e+00 3.958386533333330126e-02 +4.137051999999999952e-01 1.726875600000000066e+00 3.720425833333330240e-02 +4.291185000000000138e-01 1.581841901666670047e+00 3.398506666666670228e-02 +4.296455000000000135e-01 1.691278518333330094e+00 3.404221949999999830e-02 +4.446591999999999767e-01 1.488087740000000103e+00 3.374035333333329917e-02 +4.467690999999999746e-01 1.413946998166669911e+00 2.991126200000000096e-02 +4.603923999999999794e-01 1.391984139999999925e+00 3.130147333333330173e-02 +4.686460000000000070e-01 1.392956528666670080e+00 2.997462383333330052e-02 +4.765479000000000243e-01 1.301627080000000047e+00 2.965699000000000113e-02 +4.835137999999999936e-01 1.193676802499999967e+00 2.800566683333330018e-02 +4.923843999999999999e-01 1.242278698333330045e+00 2.973970833333329858e-02 +5.047722000000000042e-01 1.149708217833329993e+00 2.613050733333329920e-02 +5.083368000000000331e-01 1.161310218333329924e+00 2.733334000000000111e-02 +5.231871000000000160e-01 9.503032668333329935e-01 2.446244750000000148e-02 +5.249844000000000177e-01 1.102167341666669964e+00 2.571981833333330039e-02 +5.417480000000000073e-01 9.754589783333329489e-01 2.525416166666670167e-02 +5.445189000000000279e-01 9.366835949999999800e-01 2.497178450000000008e-02 +5.583793000000000228e-01 8.831162099999999571e-01 2.438054666666670048e-02 +5.613839000000000468e-01 8.824506341666670250e-01 2.281443349999999828e-02 +5.754704000000000486e-01 8.100822066666669707e-01 2.253018666666670167e-02 +5.835346000000000144e-01 7.776236106666669645e-01 2.252483566666670101e-02 +5.928168000000000326e-01 7.764561633333330049e-01 2.182597999999999830e-02 +6.007057000000000091e-01 6.983251131666670108e-01 2.131793766666669962e-02 +6.099849999999999994e-01 7.099790583333329685e-01 2.117932666666669933e-02 +6.219902999999999960e-01 6.466382168333330016e-01 2.081252199999999997e-02 +6.275817000000000201e-01 6.603706066666670260e-01 2.006477833333330033e-02 +6.413482000000000349e-01 5.710830485000000234e-01 1.856948533333329862e-02 +6.447992000000000168e-01 5.732715750000000332e-01 2.040912500000000018e-02 +6.656950000000000367e-01 5.203726888333329859e-01 1.789587166666670170e-02 +6.845196000000000058e-01 4.749653629999999738e-01 1.990720683333329841e-02 +7.059646000000000532e-01 3.822856073333329996e-01 1.675657983333329881e-02 +7.260278000000000009e-01 3.886542691666670102e-01 1.802539466666670115e-02 +7.492113999999999718e-01 3.564554191666670091e-01 1.487377883333330063e-02 +7.721772000000000080e-01 3.333074428333330230e-01 1.689668533333330003e-02 +7.922717999999999705e-01 3.187338046666670088e-01 1.488059016666670037e-02 +8.156877999999999629e-01 3.010368924999999862e-01 1.521491216666670011e-02 +8.366301000000000432e-01 2.406595508333330136e-01 1.435568400000000050e-02 +8.621258000000000532e-01 2.223576200000000058e-01 1.352156766666669924e-02 +8.848333999999999921e-01 2.207387865000000060e-01 1.422139816666669922e-02 +9.083444000000000518e-01 2.134049949999999862e-01 1.254214099999999971e-02 +9.336484000000000449e-01 1.885945263333330124e-01 1.346345033333330027e-02 +9.553916000000000075e-01 1.698076676666669949e-01 1.265998783333329922e-02 +9.815112999999999754e-01 1.668850050000000029e-01 1.189438716666670059e-02 +1.007027499999999964e+00 1.441018685000000077e-01 1.227580483333329947e-02 +1.030631400000000086e+00 1.406578583333329968e-01 1.168073399999999991e-02 +1.058015099999999986e+00 1.399035711666669901e-01 1.082874783333329961e-02 +1.084837700000000016e+00 1.486492919999999884e-01 1.123307866666669978e-02 +1.110144500000000090e+00 1.008698655000000027e-01 1.077987283333329931e-02 +1.137999800000000006e+00 1.027075286666670056e-01 1.037853683333330064e-02 +1.166290800000000072e+00 1.157098001666670012e-01 1.020088966666670045e-02 \ No newline at end of file diff --git a/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/1_0_1340_10.csv b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/1_0_1340_10.csv new file mode 100644 index 00000000..1c5c261e --- /dev/null +++ b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/1_0_1340_10.csv @@ -0,0 +1,105 @@ +3.625239999999999713e-02 2.859171986188329811e+03 2.282233352000000082e+01 +4.070000000000000007e-02 1.037695935384999984e+03 1.390765389166669941e+01 +4.511180000000000051e-02 4.617414862783330136e+02 9.644274246666670436e+00 +4.959249999999999770e-02 2.830472148050000101e+02 7.651757100000000200e+00 +5.416379999999999806e-02 1.737180786733329967e+02 5.901133003333329796e+00 +5.868999999999999911e-02 1.284318533800000068e+02 5.055296333333330061e+00 +6.315320000000000655e-02 1.083304533916670067e+02 4.179970964999999872e+00 +6.773540000000000116e-02 8.164665742500000079e+01 3.449109084999999908e+00 +7.238989999999999314e-02 6.842710629666669320e+01 3.015091889999999886e+00 +7.694339999999999513e-02 6.503887503333329789e+01 2.712566869999999852e+00 +8.166600000000000248e-02 5.768616606333330310e+01 2.292022508333329878e+00 +8.641719999999999957e-02 4.726633163000000337e+01 2.052834279999999900e+00 +9.109140000000000292e-02 4.170182763166670270e+01 1.806841736666670029e+00 +9.576210000000000278e-02 3.278791741500000256e+01 1.635979810000000034e+00 +1.005216000000000026e-01 3.137094696333329935e+01 1.457612156666669989e+00 +1.052672000000000052e-01 2.858316747499999977e+01 1.352856671666669897e+00 +1.100174000000000013e-01 2.685582243333330155e+01 1.222659363333330029e+00 +1.148721999999999938e-01 2.463809722666670154e+01 1.115379456666669933e+00 +1.198083000000000065e-01 1.979913578000000030e+01 1.002652691666670037e+00 +1.248023999999999939e-01 1.884005529499999909e+01 9.371682333333329895e-01 +1.298709000000000113e-01 2.011378014833330141e+01 8.660532583333330203e-01 +1.349431000000000103e-01 1.548196221999999977e+01 8.040137483333329449e-01 +1.399610000000000021e-01 1.553817986500000004e+01 7.396018783333330182e-01 +1.451112999999999986e-01 1.380290046833330031e+01 7.008050850000000498e-01 +1.503050999999999970e-01 1.290957537833329916e+01 6.659874700000000258e-01 +1.556469000000000047e-01 1.188963399333329995e+01 5.958023666666669715e-01 +1.572456999999999883e-01 1.305280697499999931e+01 1.930089849999999940e-01 +1.609891999999999990e-01 1.134609165166670053e+01 5.714861916666670316e-01 +1.662309999999999899e-01 1.103891432833330022e+01 5.468199316666669807e-01 +1.706292000000000086e-01 1.121912493499999997e+01 1.614639983333329976e-01 +1.716062999999999894e-01 1.098087770333330049e+01 5.221648316666670508e-01 +1.771733000000000058e-01 9.846715853333330770e+00 4.826313150000000052e-01 +1.827794000000000085e-01 1.030369242166669963e+01 4.679303400000000002e-01 +1.842428999999999872e-01 9.629214631666670243e+00 1.387244099999999924e-01 +1.883665999999999952e-01 9.108624106666670883e+00 4.454082083333330000e-01 +1.940302000000000138e-01 8.056377178333329780e+00 4.232006716666670276e-01 +1.982414999999999872e-01 8.605072379999999299e+00 1.202604099999999981e-01 +1.998185000000000100e-01 8.229729869999999892e+00 3.995894999999999864e-01 +2.056348000000000065e-01 8.070094290000000115e+00 3.879820366666669740e-01 +2.115208000000000088e-01 7.741634115000000094e+00 3.686955350000000187e-01 +2.120113000000000136e-01 7.664225765000000301e+00 1.093220133333329958e-01 +2.162467999999999890e-01 7.363320653333330412e+00 4.626756216666669808e-01 +2.259823999999999999e-01 6.848802841666669750e+00 9.609777999999999376e-02 +2.397845000000000115e-01 6.337189861666669977e+00 9.020952999999999611e-02 +2.537096999999999825e-01 5.738702153333330003e+00 8.211384833333329469e-02 +2.676275000000000182e-01 5.346591223333329701e+00 7.572218666666670484e-02 +2.817857999999999752e-01 5.009933698333330021e+00 7.051459333333330581e-02 +2.956398000000000081e-01 4.597692046666669974e+00 6.646157833333329878e-02 +3.099044000000000243e-01 4.193400725000000051e+00 6.031815000000000093e-02 +3.240645999999999805e-01 3.804645918333330101e+00 5.825413166666670167e-02 +3.388027000000000122e-01 3.560888523333329836e+00 5.213344166666669666e-02 +3.539140000000000064e-01 3.375211385000000064e+00 5.107971833333330158e-02 +3.687090000000000090e-01 3.188264295000000192e+00 4.732182333333329743e-02 +3.839813000000000254e-01 2.892990108333330035e+00 4.441047333333329739e-02 +3.988975000000000160e-01 2.640608908333330174e+00 4.290645666666669661e-02 +4.106379000000000001e-01 2.501690660833329805e+00 4.331324033333330131e-02 +4.136514000000000024e-01 2.477246653333330162e+00 4.077528500000000139e-02 +4.290628000000000219e-01 2.358423203333329887e+00 3.769917166666669761e-02 +4.295145000000000213e-01 2.266923564999999918e+00 3.645828216666670285e-02 +4.446014999999999828e-01 2.023246533333329822e+00 3.627248666666670063e-02 +4.466328999999999994e-01 2.032174506166669836e+00 3.243397416666669864e-02 +4.603325999999999807e-01 1.951935533333329920e+00 3.395880333333330114e-02 +4.685032000000000085e-01 1.797322475499999905e+00 3.168831166666669780e-02 +4.764860000000000206e-01 1.814704324999999896e+00 3.212894000000000166e-02 +4.833663999999999739e-01 1.722621554333330085e+00 3.037123383333329915e-02 +4.923204999999999942e-01 1.658358661666669898e+00 3.178619500000000320e-02 +5.046184000000000225e-01 1.539108265666669917e+00 2.774163983333330016e-02 +5.082708000000000226e-01 1.547966548333330028e+00 2.920229666666670013e-02 +5.230276000000000369e-01 1.345057283333330078e+00 2.608127533333329945e-02 +5.249162000000000550e-01 1.417396718333330030e+00 2.724554333333329900e-02 +5.416775999999999813e-01 1.270799306666670070e+00 2.670250999999999875e-02 +5.443529999999999758e-01 1.250087865666670073e+00 2.633086933333329827e-02 +5.583067999999999920e-01 1.165305416666670091e+00 2.577655499999999961e-02 +5.612129000000000145e-01 1.147836548999999984e+00 2.393862083333329893e-02 +5.753956999999999544e-01 1.115183805000000028e+00 2.400332833333329932e-02 +5.833568000000000087e-01 1.044019996999999922e+00 2.366755300000000090e-02 +5.927398999999999862e-01 1.004017331666670065e+00 2.296542000000000028e-02 +6.005226999999999649e-01 9.482225546666670501e-01 2.240195116666670108e-02 +6.099058000000000535e-01 9.388455449999999480e-01 2.233828499999999939e-02 +6.218008000000000424e-01 8.511757086666670302e-01 2.168908733333330119e-02 +6.275003000000000108e-01 8.553386066666669452e-01 2.101305833333329959e-02 +6.411527999999999672e-01 7.761240236666669956e-01 1.940277716666670080e-02 +6.447154999999999969e-01 7.270576883333329521e-01 2.120466833333330137e-02 +6.654921999999999782e-01 7.007768106666669716e-01 1.861042133333330045e-02 +6.843110000000000026e-01 6.466361541666669766e-01 2.069310833333330019e-02 +7.057493999999999712e-01 5.771519323333329510e-01 1.752841666666669906e-02 +7.258065000000000211e-01 5.204018098333329512e-01 1.860044799999999859e-02 +7.489831000000000127e-01 4.984340896666670240e-01 1.540761000000000040e-02 +7.719418000000000113e-01 4.436713720000000083e-01 1.738589999999999927e-02 +7.920304000000000233e-01 4.316635041666669892e-01 1.532890283333330009e-02 +8.154392000000000307e-01 3.868879691666670118e-01 1.556857449999999969e-02 +8.363751000000000380e-01 3.670109628333330098e-01 1.484866983333330004e-02 +8.618630000000000457e-01 3.329731463333330255e-01 1.392995016666669951e-02 +8.845638000000000112e-01 3.302565831666670060e-01 1.467645416666669977e-02 +9.080675999999999748e-01 2.757098590000000016e-01 1.276754683333329934e-02 +9.333637999999999657e-01 2.804865558333329845e-01 1.382540016666669938e-02 +9.551005000000000189e-01 2.398962168333330092e-01 1.292395533333329932e-02 +9.812121999999999788e-01 2.360268241666670097e-01 1.213837400000000039e-02 +1.006720599999999965e+00 2.134167958333330062e-01 1.253179649999999930e-02 +1.030317399999999939e+00 2.102511210000000130e-01 1.193319533333330081e-02 +1.057692700000000041e+00 2.135461601666669984e-01 1.107935333333330032e-02 +1.084507099999999946e+00 1.969431385000000034e-01 1.140526600000000071e-02 +1.109806099999999907e+00 1.561050018333330069e-01 1.096976283333329916e-02 +1.137653000000000025e+00 1.629041234999999976e-01 1.057719600000000051e-02 +1.165935399999999955e+00 1.774132069999999894e-01 1.040658966666670009e-02 \ No newline at end of file diff --git a/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/2_20_1340_10.csv b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/2_20_1340_10.csv new file mode 100644 index 00000000..3fc89561 --- /dev/null +++ b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/2_20_1340_10.csv @@ -0,0 +1,105 @@ +3.623980000000000257e-02 4.717095203250000282e+02 1.736400694499999986e+01 +4.068580000000000113e-02 2.786014172683330230e+02 1.198827076999999974e+01 +4.509610000000000007e-02 1.807710803516669955e+02 8.834240069999999889e+00 +4.957530000000000270e-02 1.404167299050000111e+02 7.187393765000000379e+00 +5.414490000000000275e-02 1.070548824566670021e+02 5.660295944999999662e+00 +5.866960000000000230e-02 9.429733434666670178e+01 4.914365443333330141e+00 +6.313130000000000130e-02 8.753748113499999306e+01 4.086659178333330367e+00 +6.771190000000000542e-02 8.007891920833330346e+01 3.441741584999999937e+00 +7.236470000000000402e-02 5.999202779333329971e+01 2.970499293333329849e+00 +7.691660000000000164e-02 5.902839900166669906e+01 2.678938321666669786e+00 +8.163760000000000461e-02 4.960764509333330352e+01 2.245417474999999996e+00 +8.638719999999999732e-02 4.414010876166670272e+01 2.032560301666670011e+00 +9.105969999999999342e-02 4.256424763833329905e+01 1.812761751666670085e+00 +9.572880000000000278e-02 3.308233848333330229e+01 1.638201501666670001e+00 +1.004865999999999954e-01 2.810971494833329842e+01 1.433384486666670066e+00 +1.052305999999999936e-01 2.588485028666670118e+01 1.331128581666670030e+00 +1.099790999999999963e-01 2.266535039333329848e+01 1.188278135000000013e+00 +1.148321999999999954e-01 2.068870146000000076e+01 1.081535063333330049e+00 +1.197666000000000008e-01 1.976825064833330003e+01 1.002416109999999971e+00 +1.247589999999999949e-01 1.708519936000000072e+01 9.212166283333329542e-01 +1.298257999999999912e-01 1.961539944500000132e+01 8.615532866666669731e-01 +1.348960999999999910e-01 1.509279464499999968e+01 8.002327049999999886e-01 +1.399122999999999895e-01 1.461401991333329953e+01 7.302237983333329518e-01 +1.450607999999999898e-01 1.234817605166669985e+01 6.862621599999999544e-01 +1.502527999999999919e-01 1.236984282499999921e+01 6.604921383333329787e-01 +1.555927000000000004e-01 1.196552256666669933e+01 5.966093183333329719e-01 +1.572660999999999920e-01 1.278021405166670021e+01 1.919547649999999994e-01 +1.609331999999999985e-01 1.153752966666669977e+01 5.736198133333330063e-01 +1.661730999999999903e-01 1.078861117833329963e+01 5.440816866666670082e-01 +1.706513000000000058e-01 1.087118622666669943e+01 1.601861350000000073e-01 +1.715465999999999935e-01 9.877732214999999982e+00 5.101304616666669789e-01 +1.771117000000000108e-01 1.016148657499999963e+01 4.860413033333330080e-01 +1.827158000000000115e-01 1.012859923499999937e+01 4.659961616666670192e-01 +1.842668000000000084e-01 9.892309961666670759e+00 1.394281016666669981e-01 +1.883010999999999990e-01 9.370592901666670471e+00 4.483600366666670167e-01 +1.939626999999999879e-01 7.953713988333330320e+00 4.220674716666670268e-01 +1.982673000000000074e-01 8.536490116666669792e+00 1.198682733333329975e-01 +1.997490000000000099e-01 8.016174306666670191e+00 3.972042266666669930e-01 +2.055633000000000044e-01 7.936747634999999690e+00 3.864501066666670148e-01 +2.114472000000000018e-01 7.092097319999999705e+00 3.613801233333330254e-01 +2.120387999999999995e-01 7.719984431666669700e+00 1.093860133333330042e-01 +2.161715999999999915e-01 7.279911723333330364e+00 4.614210216666669861e-01 +2.260118000000000127e-01 6.899103116666670310e+00 9.616077666666669743e-02 +2.398155999999999899e-01 6.131303501666669931e+00 8.927448999999999801e-02 +2.537425999999999848e-01 5.657937448333330011e+00 8.168489666666670090e-02 +2.676623000000000197e-01 5.142021448333330191e+00 7.478100833333330144e-02 +2.818223999999999729e-01 4.914665098333330207e+00 7.002304500000000598e-02 +2.956782000000000021e-01 4.446721935000000236e+00 6.572301166666670580e-02 +3.099446000000000145e-01 4.282954679999999570e+00 6.061547666666670248e-02 +3.241067000000000253e-01 3.767117071666670203e+00 5.800978500000000121e-02 +3.388467000000000007e-01 3.499952983333329826e+00 5.180699999999999888e-02 +3.539599999999999969e-01 3.303444670000000194e+00 5.069738166666670071e-02 +3.687568999999999986e-01 2.950319708333330126e+00 4.620904333333329672e-02 +3.840311000000000141e-01 2.864367984999999894e+00 4.422433499999999656e-02 +3.989493000000000067e-01 2.552979848333329915e+00 4.244176999999999672e-02 +4.107629999999999892e-01 2.431688859833330163e+00 4.295243233333329719e-02 +4.137051999999999952e-01 2.340746346666669808e+00 4.009275166666669693e-02 +4.291185000000000138e-01 2.236674611666670032e+00 3.708904500000000104e-02 +4.296455000000000135e-01 2.145105044333329936e+00 3.592934700000000037e-02 +4.446591999999999767e-01 1.951663006666670030e+00 3.589577499999999782e-02 +4.467690999999999746e-01 1.941018092166669984e+00 3.204738566666669869e-02 +4.603923999999999794e-01 1.841567858333329921e+00 3.340552499999999841e-02 +4.686460000000000070e-01 1.795296687500000044e+00 3.165418133333330192e-02 +4.765479000000000243e-01 1.725552725000000009e+00 3.166947999999999985e-02 +4.835137999999999936e-01 1.648896234833330032e+00 3.002704050000000111e-02 +4.923843999999999999e-01 1.592235150000000043e+00 3.142748499999999806e-02 +5.047722000000000042e-01 1.524318554666669989e+00 2.765916716666669967e-02 +5.083368000000000331e-01 1.470192684999999999e+00 2.879683666666670028e-02 +5.231871000000000160e-01 1.276008043333330066e+00 2.578494833333330044e-02 +5.249844000000000177e-01 1.335736669999999959e+00 2.682150333333329847e-02 +5.417480000000000073e-01 1.255200871666670048e+00 2.659473333333330081e-02 +5.445189000000000279e-01 1.181751707666669926e+00 2.602010650000000092e-02 +5.583793000000000228e-01 1.119981195000000040e+00 2.552491833333329907e-02 +5.613839000000000468e-01 1.109540133333329903e+00 2.376074416666670158e-02 +5.754704000000000486e-01 1.035568078333330089e+00 2.359670999999999991e-02 +5.835346000000000144e-01 1.009673415166669974e+00 2.350527950000000019e-02 +5.928168000000000326e-01 9.958746566666669686e-01 2.289775833333329916e-02 +6.007057000000000091e-01 9.162153768333329840e-01 2.224946400000000005e-02 +6.099849999999999994e-01 8.912180400000000446e-01 2.207391333333329902e-02 +6.219902999999999960e-01 8.127960751666669648e-01 2.151158183333330004e-02 +6.275817000000000201e-01 8.004247816666669735e-01 2.072389499999999912e-02 +6.413482000000000349e-01 7.426332359999999744e-01 1.925514349999999861e-02 +6.447992000000000168e-01 7.076525866666669717e-01 2.108122000000000121e-02 +6.656950000000000367e-01 6.442563610000000551e-01 1.837696600000000152e-02 +6.845196000000000058e-01 6.085390748333330269e-01 2.050695833333330068e-02 +7.059646000000000532e-01 5.280073378333329792e-01 1.732566899999999840e-02 +7.260278000000000009e-01 5.220653703333330009e-01 1.859470200000000115e-02 +7.492113999999999718e-01 4.598426163333330097e-01 1.525465250000000023e-02 +7.721772000000000080e-01 4.332637708333330062e-01 1.732895283333329983e-02 +7.922717999999999705e-01 4.105990393333330268e-01 1.523655316666669944e-02 +8.156877999999999629e-01 3.602943534999999975e-01 1.545041316666669919e-02 +8.366301000000000432e-01 3.406887861666669792e-01 1.473824349999999957e-02 +8.621258000000000532e-01 3.067601776666670221e-01 1.382606316666670082e-02 +8.848333999999999921e-01 3.015524316666670090e-01 1.454982116666670051e-02 +9.083444000000000518e-01 2.607474308333330160e-01 1.270642616666669937e-02 +9.336484000000000449e-01 2.588003018333330241e-01 1.373286349999999940e-02 +9.553916000000000075e-01 2.226530176666670080e-01 1.285228899999999938e-02 +9.815112999999999754e-01 2.127235584999999929e-01 1.205009299999999957e-02 +1.007027499999999964e+00 2.082924603333330127e-01 1.250585733333330063e-02 +1.030631400000000086e+00 1.912464838333330086e-01 1.185822249999999960e-02 +1.058015099999999986e+00 1.948314409999999941e-01 1.101004149999999966e-02 +1.084837700000000016e+00 1.896849941666670092e-01 1.137308499999999979e-02 +1.110144500000000090e+00 1.497566153333330097e-01 1.094208966666669960e-02 +1.137999800000000006e+00 1.454676966666670068e-01 1.051448583333330043e-02 +1.166290800000000072e+00 1.467227160000000030e-01 1.029956333333329963e-02 \ No newline at end of file diff --git a/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/3_35_1340_10.csv b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/3_35_1340_10.csv new file mode 100644 index 00000000..5ef6a326 --- /dev/null +++ b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/3_35_1340_10.csv @@ -0,0 +1,105 @@ +3.625239999999999713e-02 2.531792558183329902e+02 1.680830338499999854e+01 +4.070000000000000007e-02 1.901177223299999923e+02 1.173664666500000031e+01 +4.511180000000000051e-02 1.217924648083329942e+02 8.645296186666669769e+00 +4.959249999999999770e-02 1.094275780066670052e+02 7.073034475000000043e+00 +5.416379999999999806e-02 8.791320813333329909e+01 5.581711248333330211e+00 +5.868999999999999911e-02 8.309104453833330695e+01 4.860535583333329690e+00 +6.315320000000000655e-02 7.081360745666670198e+01 4.003961999999999577e+00 +6.773540000000000116e-02 6.343291181166669901e+01 3.355398363333330192e+00 +7.238989999999999314e-02 4.842191466166669755e+01 2.903796691666669982e+00 +7.694339999999999513e-02 4.694420552666669977e+01 2.605583116666669863e+00 +8.166600000000000248e-02 4.367521950333330238e+01 2.207077676666670207e+00 +8.641719999999999957e-02 3.957930205499999943e+01 1.999356729999999915e+00 +9.109140000000000292e-02 3.553857484166670133e+01 1.762052579999999979e+00 +9.576210000000000278e-02 2.783675838666669833e+01 1.597004463333330015e+00 +1.005216000000000026e-01 2.705964543666669897e+01 1.423240978333329965e+00 +1.052672000000000052e-01 2.262947438833329983e+01 1.302317253333330038e+00 +1.100174000000000013e-01 2.182584247666670052e+01 1.179366926666669979e+00 +1.148721999999999938e-01 2.121788853500000016e+01 1.084366495000000041e+00 +1.198083000000000065e-01 1.779511935000000022e+01 9.833421683333329888e-01 +1.248023999999999939e-01 1.651748253666670152e+01 9.145048883333329881e-01 +1.298709000000000113e-01 1.721928703000000027e+01 8.380137100000000228e-01 +1.349431000000000103e-01 1.436323046666669967e+01 7.917284633333330213e-01 +1.399610000000000021e-01 1.550343697666670018e+01 7.379972516666669646e-01 +1.451112999999999986e-01 1.303663926666670037e+01 6.920347900000000108e-01 +1.503050999999999970e-01 1.200503839166669984e+01 6.556345366666670449e-01 +1.556469000000000047e-01 1.229304255666670009e+01 5.989305183333329952e-01 +1.573070000000000024e-01 1.257057899666670053e+01 1.910309249999999903e-01 +1.609891999999999990e-01 1.193592632666669928e+01 5.769697900000000379e-01 +1.662309999999999899e-01 1.125234698666669964e+01 5.482133000000000145e-01 +1.706957000000000058e-01 1.058112505166669948e+01 1.590339783333329926e-01 +1.716062999999999894e-01 1.070644689999999954e+01 5.183003699999999547e-01 +1.771733000000000058e-01 9.479454351666669609e+00 4.778282466666670114e-01 +1.827794000000000085e-01 9.400985496666670826e+00 4.569814249999999967e-01 +1.843146999999999980e-01 9.549487438333329692e+00 1.380682866666670117e-01 +1.883665999999999952e-01 8.342261173333330504e+00 4.359566316666669827e-01 +1.940302000000000138e-01 8.124126898333329905e+00 4.232381216666670221e-01 +1.983187999999999895e-01 8.328415036666669380e+00 1.189532799999999946e-01 +1.998185000000000100e-01 8.328210934999999537e+00 3.999954083333330246e-01 +2.056348000000000065e-01 8.350671104999999983e+00 3.905236483333329733e-01 +2.115208000000000088e-01 6.929636241666670138e+00 3.588886250000000167e-01 +2.120939000000000019e-01 7.535892504999999630e+00 1.085254183333329986e-01 +2.162467999999999890e-01 7.407988318333329936e+00 4.625349066666670228e-01 +2.260705000000000076e-01 6.743451298333329902e+00 9.541167166666669752e-02 +2.398779000000000050e-01 6.099490819999999758e+00 8.899463999999999986e-02 +2.538084999999999924e-01 5.555039073333330357e+00 8.113394833333330280e-02 +2.677318000000000198e-01 5.152730110000000252e+00 7.469547999999999466e-02 +2.818956000000000239e-01 4.837557096666669665e+00 6.957829666666670576e-02 +2.957549999999999901e-01 4.369791838333330070e+00 6.527595833333330044e-02 +3.100250999999999979e-01 4.110463069999999774e+00 5.977720166666670304e-02 +3.241909000000000041e-01 3.813721606666669928e+00 5.811357666666670113e-02 +3.389346999999999777e-01 3.461479851666669827e+00 5.155417166666669687e-02 +3.540519999999999778e-01 3.230298750000000219e+00 5.028755499999999767e-02 +3.688526999999999778e-01 2.998165658333329819e+00 4.634052999999999783e-02 +3.841308999999999974e-01 2.784144708333330165e+00 4.378963833333329725e-02 +3.990528999999999882e-01 2.595613363333329815e+00 4.256657166666669850e-02 +4.108341999999999827e-01 2.335089765499999803e+00 4.258898366666669794e-02 +4.138126999999999778e-01 2.324415418333329875e+00 3.994880166666670007e-02 +4.292300000000000004e-01 2.216248561666669836e+00 3.693022666666669757e-02 +4.297198999999999880e-01 2.134554158666670087e+00 3.596130699999999791e-02 +4.447747000000000228e-01 1.890609146666669904e+00 3.555346833333330320e-02 +4.468464999999999798e-01 1.908117816499999897e+00 3.198120466666670020e-02 +4.605119999999999769e-01 1.871098863333330087e+00 3.348654166666670262e-02 +4.687272000000000105e-01 1.810652348333330108e+00 3.178039216666669886e-02 +4.766716999999999760e-01 1.698277889999999957e+00 3.148857166666670093e-02 +4.835976000000000163e-01 1.613169754500000108e+00 2.993151466666670035e-02 +4.925123000000000140e-01 1.548358499999999971e+00 3.116390166666669834e-02 +5.048597000000000223e-01 1.494630669833330039e+00 2.759253283333330115e-02 +5.084689000000000014e-01 1.491590500000000041e+00 2.885127333333329866e-02 +5.232776999999999568e-01 1.248566430833329965e+00 2.572220416666669979e-02 +5.251208000000000542e-01 1.383180581666670017e+00 2.700447666666670049e-02 +5.418887000000000009e-01 1.221960488333329931e+00 2.639246833333330072e-02 +5.446132999999999669e-01 1.208971178333329899e+00 2.618446533333329898e-02 +5.585244000000000320e-01 1.107120358333330001e+00 2.542247833333330029e-02 +5.614812000000000136e-01 1.085889866500000078e+00 2.370511449999999909e-02 +5.756198999999999621e-01 1.024119451666670066e+00 2.350574000000000066e-02 +5.836356999999999795e-01 9.666399098333330331e-01 2.336446566666669847e-02 +5.929708999999999675e-01 9.753983600000000198e-01 2.276144833333329856e-02 +6.008097999999999494e-01 8.857038164999999630e-01 2.215761016666669900e-02 +6.101434999999999498e-01 9.144217283333330171e-01 2.215592499999999992e-02 +6.220980999999999872e-01 8.054969749999999484e-01 2.151697083333330152e-02 +6.277447999999999917e-01 7.972735800000000372e-01 2.067797666666670170e-02 +6.414592999999999545e-01 7.033510668333330385e-01 1.912904316666669963e-02 +6.449667999999999513e-01 7.010848466666670387e-01 2.101707333333329916e-02 +6.658104000000000244e-01 6.363828563333330246e-01 1.837497983333330129e-02 +6.846381999999999746e-01 5.758642446666669690e-01 2.039069533333329881e-02 +7.060868999999999618e-01 5.109788090000000338e-01 1.728495966666670006e-02 +7.261535999999999547e-01 4.885223020000000194e-01 1.847847583333329935e-02 +7.493412999999999879e-01 4.387949984999999775e-01 1.519840233333329994e-02 +7.723109999999999697e-01 3.923397161666670185e-01 1.717436333333329998e-02 +7.924090999999999774e-01 3.993512075000000272e-01 1.521433699999999965e-02 +8.158290999999999737e-01 3.564691013333329828e-01 1.545670316666669999e-02 +8.367750000000000465e-01 3.230009236666669947e-01 1.469029500000000078e-02 +8.622752000000000194e-01 2.878051181666669844e-01 1.377513966666669976e-02 +8.849867000000000150e-01 2.848005155000000177e-01 1.450056933333329981e-02 +9.085018000000000260e-01 2.630901753333330095e-01 1.273161166666669959e-02 +9.338100999999999763e-01 2.556641304999999753e-01 1.373873700000000087e-02 +9.555571000000000481e-01 2.110106781666669928e-01 1.282516266666670034e-02 +9.816814000000000373e-01 1.989191335000000116e-01 1.201689483333330012e-02 +1.007201999999999931e+00 1.798726026666669919e-01 1.241756500000000020e-02 +1.030810000000000004e+00 1.697684260000000001e-01 1.179553216666670047e-02 +1.058198500000000042e+00 1.911874938333329998e-01 1.101163099999999916e-02 +1.085025700000000093e+00 1.546144220000000014e-01 1.126302133333329999e-02 +1.110336800000000013e+00 1.274498698333330071e-01 1.087918116666669946e-02 +1.138196999999999903e+00 1.312019681666669879e-01 1.048030450000000079e-02 +1.166492899999999944e+00 1.202452754999999984e-01 1.022370933333329943e-02 \ No newline at end of file diff --git a/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/4_50_1340_10.csv b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/4_50_1340_10.csv new file mode 100644 index 00000000..51227d3b --- /dev/null +++ b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/4_50_1340_10.csv @@ -0,0 +1,105 @@ +3.624299999999999744e-02 2.421839941750000094e+02 1.681259771499999900e+01 +4.068929999999999769e-02 1.253477160583330061e+02 1.158261931833330038e+01 +4.510000000000000120e-02 1.038159109083329952e+02 8.606540843333329249e+00 +4.957960000000000145e-02 9.508877155000000414e+01 7.037626288333330038e+00 +5.414959999999999912e-02 7.142443308833330207e+01 5.530319266666669620e+00 +5.867460000000000037e-02 6.747951957166669956e+01 4.803320423333330424e+00 +6.313670000000000393e-02 6.491403129500000091e+01 3.984481885000000112e+00 +6.771770000000000567e-02 5.688806046833330043e+01 3.329560004999999823e+00 +7.237100000000000477e-02 4.031379161166670144e+01 2.864987329999999943e+00 +7.692330000000000001e-02 4.363721252500000247e+01 2.591659659999999921e+00 +8.164470000000000061e-02 3.499940050499999700e+01 2.159604903333330217e+00 +8.639470000000000482e-02 3.331168452000000002e+01 1.961486934999999932e+00 +9.106759999999999855e-02 3.141853618500000067e+01 1.737193953333330043e+00 +9.573710000000000553e-02 2.514925802833329982e+01 1.580249494999999893e+00 +1.004952999999999957e-01 2.418445705499999931e+01 1.404480071666670105e+00 +1.052397000000000055e-01 2.311071394833329862e+01 1.309208076666670051e+00 +1.099886000000000058e-01 2.234431306333329914e+01 1.186371084999999992e+00 +1.148422000000000054e-01 2.015011891333330141e+01 1.077548355000000013e+00 +1.197769999999999946e-01 1.762490517499999854e+01 9.840672733333329925e-01 +1.247698000000000002e-01 1.703488854000000075e+01 9.213895800000000413e-01 +1.298370000000000080e-01 1.644401272166669870e+01 8.327218266666670532e-01 +1.349077999999999944e-01 1.614025673666670002e+01 8.110203433333329492e-01 +1.399244000000000043e-01 1.432045983666669997e+01 7.277294366666670067e-01 +1.450733999999999913e-01 1.348007003166670081e+01 6.981156733333330200e-01 +1.502658000000000049e-01 1.217057146500000009e+01 6.589065133333330548e-01 +1.556062000000000001e-01 1.311354035333330081e+01 6.087518283333329672e-01 +1.573412999999999895e-01 1.262766263333329952e+01 1.918068450000000036e-01 +1.609471000000000096e-01 1.195521582166669994e+01 5.786270049999999721e-01 +1.661874999999999880e-01 1.070419187999999977e+01 5.435432616666669992e-01 +1.707328999999999930e-01 1.060603477499999947e+01 1.596232366666670011e-01 +1.715615000000000057e-01 1.106375028666669991e+01 5.234683166666670440e-01 +1.771270000000000067e-01 9.097135046666670277e+00 4.748174949999999783e-01 +1.827315999999999940e-01 9.232441778333329907e+00 4.562185283333329844e-01 +1.843548999999999882e-01 9.582483613333330652e+00 1.386399050000000077e-01 +1.883173999999999959e-01 9.681451038333330317e+00 4.521609416666669823e-01 +1.939794999999999991e-01 8.227612430000000643e+00 4.254379616666669750e-01 +1.983621000000000134e-01 8.295318078333329126e+00 1.192443799999999970e-01 +1.997663000000000078e-01 8.131970284999999521e+00 3.988034016666670012e-01 +2.055810999999999888e-01 7.142658796666670362e+00 3.773835349999999922e-01 +2.114655000000000007e-01 7.164933761666669731e+00 3.624743966666669759e-01 +2.121402000000000010e-01 7.387669431666670228e+00 1.083523799999999981e-01 +2.161903000000000019e-01 6.864321103333329788e+00 4.553583783333329804e-01 +2.261198000000000097e-01 6.847039901666669870e+00 9.614554833333330275e-02 +2.399302000000000101e-01 6.151464958333329847e+00 8.952354166666670610e-02 +2.538638999999999757e-01 5.563245829999999614e+00 8.146051666666670465e-02 +2.677901999999999783e-01 5.162996156666669556e+00 7.501275500000000040e-02 +2.819571000000000160e-01 4.731078383333329640e+00 6.938886333333330048e-02 +2.958196000000000159e-01 4.470382223333330352e+00 6.595420000000000449e-02 +3.100928000000000018e-01 4.071269568333329758e+00 5.983178000000000107e-02 +3.242616999999999861e-01 3.818289136666670025e+00 5.835058500000000342e-02 +3.390086999999999962e-01 3.464278166666669989e+00 5.175618166666669934e-02 +3.541291999999999773e-01 3.297458013333330218e+00 5.076983000000000190e-02 +3.689332000000000167e-01 3.070901008333330129e+00 4.683460500000000137e-02 +3.842147000000000201e-01 2.769801061666670172e+00 4.388707166666670073e-02 +3.991399999999999948e-01 2.510583560000000158e+00 4.232430333333329908e-02 +4.106022999999999756e-01 2.374815773499999949e+00 4.286182233333329927e-02 +4.139030000000000209e-01 2.373871081666670158e+00 4.032223833333330176e-02 +4.293236999999999748e-01 2.231085370000000179e+00 3.713437166666670036e-02 +4.294773000000000063e-01 2.156539414333329852e+00 3.612328016666670194e-02 +4.448717999999999839e-01 1.876962131666670031e+00 3.561285166666670193e-02 +4.465942999999999996e-01 1.963582529333329996e+00 3.226501116666669750e-02 +4.606124999999999803e-01 1.886281606666670108e+00 3.367513833333329876e-02 +4.684626000000000068e-01 1.795604685000000034e+00 3.177854483333329705e-02 +4.767757000000000245e-01 1.729912848333329922e+00 3.174894833333329752e-02 +4.833245999999999931e-01 1.643333701666670033e+00 3.012282700000000132e-02 +4.926197999999999966e-01 1.601281793333330095e+00 3.152871666666669931e-02 +5.045747000000000426e-01 1.513868059666670041e+00 2.772222300000000070e-02 +5.085798000000000263e-01 1.480025179999999940e+00 2.889593166666670071e-02 +5.229823000000000111e-01 1.292098316666669966e+00 2.594417883333329997e-02 +5.252354000000000189e-01 1.389690103333329985e+00 2.712855999999999948e-02 +5.420070000000000165e-01 1.207626951666670001e+00 2.641061333333330138e-02 +5.443057999999999508e-01 1.171074812333330106e+00 2.606778300000000062e-02 +5.586463000000000401e-01 1.131968480000000055e+00 2.562754666666669859e-02 +5.611642999999999493e-01 1.119771183833329964e+00 2.388942000000000149e-02 +5.757455000000000211e-01 1.050324178333329916e+00 2.370775166666670014e-02 +5.833063000000000553e-01 9.890430198333329814e-01 2.349970633333330061e-02 +5.931003000000000247e-01 9.529422783333330038e-01 2.272385333333330065e-02 +6.004707000000000239e-01 9.087951714999999986e-01 2.229426183333330092e-02 +6.102765999999999469e-01 8.968441200000000224e-01 2.213925333333329956e-02 +6.217470000000000496e-01 8.116193323333330545e-01 2.157820200000000119e-02 +6.278818000000000454e-01 7.745292049999999984e-01 2.063141833333330052e-02 +6.410972000000000337e-01 7.304547335000000086e-01 1.926881999999999900e-02 +6.451074999999999449e-01 7.030634049999999746e-01 2.109099333333330079e-02 +6.654345000000000399e-01 6.451808323333330097e-01 1.843812350000000044e-02 +6.842517000000000182e-01 5.969281796666670026e-01 2.051784966666669874e-02 +7.056883000000000461e-01 4.875458616666670242e-01 1.721726566666669997e-02 +7.257436999999999916e-01 5.206052460000000215e-01 1.864389433333329960e-02 +7.489183000000000368e-01 4.716855048333329914e-01 1.534349166666670004e-02 +7.718749999999999778e-01 3.996116843333329949e-01 1.723059933333330115e-02 +7.919618000000000491e-01 3.949704010000000265e-01 1.521841983333330033e-02 +8.153685999999999989e-01 3.711572033333330189e-01 1.553813883333329988e-02 +8.363026999999999544e-01 3.373120053333329982e-01 1.476541233333330053e-02 +8.617884000000000100e-01 3.012111169999999727e-01 1.384248800000000001e-02 +8.844872000000000289e-01 3.093801609999999869e-01 1.462152149999999991e-02 +9.079890000000000461e-01 2.725293346666670113e-01 1.278188633333329945e-02 +9.332829999999999737e-01 2.562172424999999976e-01 1.375849099999999943e-02 +9.550178000000000278e-01 2.195073688333329942e-01 1.287299783333330054e-02 +9.811271999999999771e-01 1.993873673333330099e-01 1.203331399999999982e-02 +1.006633399999999900e+00 1.965978770000000042e-01 1.249421516666670076e-02 +1.030228200000000038e+00 1.888581198333330047e-01 1.187900783333330039e-02 +1.057601100000000072e+00 2.019180355000000093e-01 1.106158266666669963e-02 +1.084413199999999966e+00 1.710308969999999873e-01 1.133507299999999933e-02 +1.109710100000000033e+00 1.385269876666669897e-01 1.092970799999999978e-02 +1.137554500000000024e+00 1.323425321666669985e-01 1.049619949999999920e-02 +1.165834400000000048e+00 1.352444690000000060e-01 1.028587266666670073e-02 \ No newline at end of file diff --git a/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/5_75_1340_10.csv b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/5_75_1340_10.csv new file mode 100644 index 00000000..e4a5c8ff --- /dev/null +++ b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/5_75_1340_10.csv @@ -0,0 +1,105 @@ +3.623830000000000107e-02 1.262534512933330006e+02 1.649893105666669868e+01 +4.068399999999999794e-02 1.096408472683330046e+02 1.152156481833329948e+01 +4.509420000000000095e-02 6.843298548666669490e+01 8.484508865000000455e+00 +4.957310000000000189e-02 5.337196919333329959e+01 6.882409771666670117e+00 +5.414259999999999906e-02 4.824018401499999698e+01 5.433445991666670416e+00 +5.866699999999999693e-02 4.665372951666670076e+01 4.705492196666670068e+00 +6.312850000000000406e-02 5.221271013999999866e+01 3.917987768333329957e+00 +6.770890000000000242e-02 4.827582027833329903e+01 3.280544881666669799e+00 +7.236159999999999815e-02 3.618855914666669804e+01 2.837183351666670017e+00 +7.691330000000000389e-02 3.079150051333330040e+01 2.510836073333329921e+00 +8.163410000000000111e-02 3.296728457500000076e+01 2.143401925000000041e+00 +8.638339999999999907e-02 3.069841431000000043e+01 1.940150145000000048e+00 +9.105580000000000618e-02 2.710592771666669876e+01 1.703459686666670025e+00 +9.572469999999999590e-02 2.329590637833329936e+01 1.563327263333329942e+00 +1.004822999999999966e-01 2.091072460333329985e+01 1.376591528333330094e+00 +1.052260000000000001e-01 1.912562908333330114e+01 1.273402691666670083e+00 +1.099742999999999971e-01 1.787115249333330169e+01 1.146192773333329917e+00 +1.148272999999999933e-01 1.794118791666669921e+01 1.055988855000000060e+00 +1.197614000000000040e-01 1.653599519333329937e+01 9.723875616666669552e-01 +1.247536000000000062e-01 1.582743532666670028e+01 9.084356083333330334e-01 +1.298200999999999938e-01 1.478155951166669979e+01 8.152435350000000192e-01 +1.348902999999999908e-01 1.334408521333329922e+01 7.818770066666670404e-01 +1.399062000000000083e-01 1.377281605666670039e+01 7.205883533333330426e-01 +1.450545000000000029e-01 1.181691977333330001e+01 6.799812333333330461e-01 +1.502462999999999993e-01 1.213584320000000005e+01 6.572146300000000219e-01 +1.555860000000000021e-01 1.192358865166669979e+01 5.953690533333330093e-01 +1.572933999999999999e-01 1.181612013833330010e+01 1.889312700000000123e-01 +1.609261999999999915e-01 1.175466066000000076e+01 5.752044899999999572e-01 +1.661659000000000053e-01 1.042143580833329963e+01 5.392668716666669804e-01 +1.706808999999999965e-01 1.011778497999999971e+01 1.577585016666669948e-01 +1.715392000000000028e-01 9.758357795000000223e+00 5.081139666666669719e-01 +1.771040000000000114e-01 8.903400566666670457e+00 4.716962699999999731e-01 +1.827079000000000064e-01 9.178007346666669619e+00 4.546196783333329994e-01 +1.842987000000000097e-01 9.009516103333330861e+00 1.364224366666670074e-01 +1.882929000000000130e-01 8.406366826666669567e+00 4.368516150000000264e-01 +1.939542999999999962e-01 7.892936534999999587e+00 4.208100816666670019e-01 +1.983015999999999945e-01 7.968528981666669786e+00 1.178380566666669960e-01 +1.997403000000000095e-01 7.734987613333330181e+00 3.934730833333329736e-01 +2.055543999999999982e-01 7.979108915000000302e+00 3.863922049999999886e-01 +2.114380999999999899e-01 6.834691483333330098e+00 3.579354766666669740e-01 +2.120755000000000001e-01 7.473622876666669690e+00 1.084324933333330049e-01 +2.161621999999999988e-01 6.843431601666670083e+00 4.540542233333330069e-01 +2.260508999999999991e-01 6.624537448333329692e+00 9.508442500000000019e-02 +2.398570999999999898e-01 6.030806045000000282e+00 8.883706333333329930e-02 +2.537865999999999733e-01 5.548485336666669987e+00 8.121552666666670417e-02 +2.677086000000000188e-01 5.056279183333329819e+00 7.439664999999999473e-02 +2.818711999999999884e-01 4.746517688333329765e+00 6.929181333333329917e-02 +2.957293999999999756e-01 4.360488485000000303e+00 6.532353000000000465e-02 +3.099983000000000044e-01 4.042259101666670240e+00 5.956615500000000257e-02 +3.241628999999999761e-01 3.667612291666670021e+00 5.754424666666670130e-02 +3.389054000000000233e-01 3.392261260000000167e+00 5.132888666666669819e-02 +3.540212999999999832e-01 3.153369070000000107e+00 5.001504333333330055e-02 +3.688208000000000042e-01 2.957433689999999782e+00 4.622241666666670329e-02 +3.840975999999999835e-01 2.782086464999999897e+00 4.383930500000000202e-02 +3.990183999999999953e-01 2.497954614999999823e+00 4.216718166666669904e-02 +4.106379000000000001e-01 2.347253106833329994e+00 4.254085666666670290e-02 +4.137768000000000002e-01 2.313526113333329803e+00 3.995030166666670157e-02 +4.291927999999999854e-01 2.205677500000000180e+00 3.693004333333330114e-02 +4.295145000000000213e-01 2.125140473833329935e+00 3.583346700000000079e-02 +4.447362000000000259e-01 1.900926653333329996e+00 3.564656666666669860e-02 +4.466328999999999994e-01 1.939722618500000051e+00 3.202840516666669718e-02 +4.604721999999999982e-01 1.890633683333329929e+00 3.361991000000000285e-02 +4.685032000000000085e-01 1.753475375666670111e+00 3.146806816666670309e-02 +4.766304000000000096e-01 1.692544018333330014e+00 3.150160500000000197e-02 +4.833663999999999739e-01 1.571138671166669942e+00 2.967553150000000126e-02 +4.924697000000000102e-01 1.562743266666670072e+00 3.127265833333330025e-02 +5.046184000000000225e-01 1.517538195333330009e+00 2.762060549999999920e-02 +5.084248000000000101e-01 1.452519751666669912e+00 2.870172666666670133e-02 +5.230276000000000369e-01 1.280607860833330003e+00 2.579378549999999937e-02 +5.250753000000000226e-01 1.364635400000000054e+00 2.694985833333329861e-02 +5.418418000000000401e-01 1.180339848333330055e+00 2.622176999999999841e-02 +5.443529999999999758e-01 1.172893769833329936e+00 2.597214383333330129e-02 +5.584759999999999724e-01 1.116791568333330043e+00 2.550004666666669945e-02 +5.612129000000000145e-01 1.071752901666670033e+00 2.359418099999999879e-02 +5.755700000000000260e-01 1.003165499999999932e+00 2.343291166666670172e-02 +5.833568000000000087e-01 9.490407108333329678e-01 2.324037416666669895e-02 +5.929195000000000437e-01 9.286979850000000036e-01 2.255620333333329883e-02 +6.005226999999999649e-01 8.938147573333330431e-01 2.214501316666669939e-02 +6.100906000000000384e-01 8.779068549999999860e-01 2.199904499999999832e-02 +6.218008000000000424e-01 8.078191183333329750e-01 2.148254866666670163e-02 +6.276903999999999817e-01 7.890844949999999969e-01 2.066168499999999908e-02 +6.411527999999999672e-01 6.990414535000000207e-01 1.907308200000000162e-02 +6.449108999999999536e-01 7.027759316666669642e-01 2.104888333333329933e-02 +6.654921999999999782e-01 6.693195063333330364e-01 1.846815400000000051e-02 +6.843110000000000026e-01 5.654079498333329790e-01 2.030432683333329921e-02 +7.057493999999999712e-01 4.733216641666669888e-01 1.710506283333329894e-02 +7.258065000000000211e-01 4.881572298333329840e-01 1.844279849999999957e-02 +7.489831000000000127e-01 4.439720878333329734e-01 1.519038399999999948e-02 +7.719418000000000113e-01 4.063687451666669892e-01 1.720554133333329974e-02 +7.920304000000000233e-01 4.025809184999999957e-01 1.519988700000000047e-02 +8.154392000000000307e-01 3.522328571666670238e-01 1.541253149999999988e-02 +8.363751000000000380e-01 3.005755748333330257e-01 1.457862766666669953e-02 +8.618630000000000457e-01 2.801856756666670223e-01 1.372444566666669932e-02 +8.845638000000000112e-01 3.046253318333330129e-01 1.455751649999999925e-02 +9.080675999999999748e-01 2.540008221666669730e-01 1.267830250000000041e-02 +9.333637999999999657e-01 2.481020686666670083e-01 1.368678783333330054e-02 +9.551005000000000189e-01 1.938199579999999866e-01 1.274076333333330063e-02 +9.812121999999999788e-01 1.905731619999999904e-01 1.196892649999999926e-02 +1.006720599999999965e+00 1.784818836666670072e-01 1.239337400000000040e-02 +1.030317399999999939e+00 1.750911911666669929e-01 1.179671083333330012e-02 +1.057692700000000041e+00 1.705610396666669970e-01 1.092493383333329945e-02 +1.084507099999999946e+00 1.719191243333330066e-01 1.130716749999999965e-02 +1.109806099999999907e+00 1.366376699999999889e-01 1.089438483333329995e-02 +1.137653000000000025e+00 1.239800338333330032e-01 1.044124700000000072e-02 +1.165935399999999955e+00 1.310211675000000076e-01 1.024458533333330069e-02 \ No newline at end of file diff --git a/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/6_100_1340_10.csv b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/6_100_1340_10.csv new file mode 100644 index 00000000..2b03b80a --- /dev/null +++ b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/6_100_1340_10.csv @@ -0,0 +1,105 @@ +3.625239999999999713e-02 1.213206534416670053e+02 1.651067908166670151e+01 +4.070000000000000007e-02 8.158564289333330066e+01 1.146150313666669973e+01 +4.511180000000000051e-02 6.728303165833330013e+01 8.493610868333329122e+00 +4.959249999999999770e-02 5.609321657833329766e+01 6.902234358333330100e+00 +5.416379999999999806e-02 4.208391674666670212e+01 5.417946521666669568e+00 +5.868999999999999911e-02 4.572918046833329697e+01 4.708583066666670369e+00 +6.315320000000000655e-02 4.506950746166670285e+01 3.890110903333329873e+00 +6.773540000000000116e-02 3.932771859999999720e+01 3.240109296666669803e+00 +7.238989999999999314e-02 3.425092975666670014e+01 2.830867521666669884e+00 +7.694339999999999513e-02 2.785716006499999864e+01 2.497106951666669961e+00 +8.166600000000000248e-02 2.647316349333329910e+01 2.106834523333330100e+00 +8.641719999999999957e-02 2.865549820000000025e+01 1.929370503333329934e+00 +9.109140000000000292e-02 2.617781355833329826e+01 1.699746431666669944e+00 +9.576210000000000278e-02 2.262414874833330103e+01 1.560889624999999947e+00 +1.005216000000000026e-01 2.072404344833330114e+01 1.377539340000000001e+00 +1.052672000000000052e-01 1.924028949499999896e+01 1.276625569999999987e+00 +1.100174000000000013e-01 1.836748323999999855e+01 1.152518286666670111e+00 +1.148721999999999938e-01 1.699007782666669897e+01 1.049408108333329981e+00 +1.198083000000000065e-01 1.493712180833330017e+01 9.595881783333329862e-01 +1.248023999999999939e-01 1.484142020166670051e+01 9.009069050000000356e-01 +1.298709000000000113e-01 1.473895257666669956e+01 8.163899350000000110e-01 +1.349431000000000103e-01 1.308340819666669930e+01 7.807371766666669766e-01 +1.399610000000000021e-01 1.350568340499999920e+01 7.192249033333329988e-01 +1.451112999999999986e-01 1.173611615333330072e+01 6.804578466666669767e-01 +1.503050999999999970e-01 1.154060497666669960e+01 6.523238550000000080e-01 +1.556469000000000047e-01 1.191625231333330071e+01 5.964806683333330195e-01 +1.573344999999999883e-01 1.139197852166670089e+01 1.879402166666669927e-01 +1.609891999999999990e-01 1.125507998666670062e+01 5.708663616666670437e-01 +1.662309999999999899e-01 1.041113468166670053e+01 5.402433599999999503e-01 +1.707255000000000023e-01 1.013307594333329931e+01 1.580824633333330065e-01 +1.716062999999999894e-01 9.500610554999999735e+00 5.062648633333329817e-01 +1.771733000000000058e-01 8.706795813333329193e+00 4.704599616666669815e-01 +1.827794000000000085e-01 9.156035623333330875e+00 4.553047450000000107e-01 +1.843469000000000080e-01 9.118870355000000316e+00 1.370399833333330042e-01 +1.883665999999999952e-01 7.952454938333329615e+00 4.325042000000000053e-01 +1.940302000000000138e-01 8.034647969999999972e+00 4.232443883333329993e-01 +1.983534999999999882e-01 8.119139649999999264e+00 1.185909816666669975e-01 +1.998185000000000100e-01 7.527596430000000005e+00 3.919050766666670182e-01 +2.056348000000000065e-01 7.553855535000000287e+00 3.822255366666669762e-01 +2.115208000000000088e-01 6.617426820000000376e+00 3.561540533333329983e-01 +2.121309999999999862e-01 7.390867828333330003e+00 1.083323783333329932e-01 +2.162467999999999890e-01 6.635593964999999983e+00 4.517541333333329745e-01 +2.261101000000000083e-01 6.488236141666670065e+00 9.474941333333329607e-02 +2.399198999999999915e-01 5.939089416666670118e+00 8.864513000000000253e-02 +2.538528999999999924e-01 5.445706584999999933e+00 8.096326333333329905e-02 +2.677785999999999778e-01 5.020975218333330048e+00 7.440106833333330616e-02 +2.819448999999999983e-01 4.782109235000000069e+00 6.958310166666670238e-02 +2.958067999999999809e-01 4.377367741666669865e+00 6.553075166666670615e-02 +3.100794000000000050e-01 4.070373518333330054e+00 5.980910666666670178e-02 +3.242476000000000247e-01 3.688249826666670117e+00 5.775312500000000238e-02 +3.389940000000000175e-01 3.411490240000000007e+00 5.151532000000000333e-02 +3.541138999999999815e-01 3.221007965000000084e+00 5.041618500000000225e-02 +3.689172000000000007e-01 2.942401639999999929e+00 4.624927499999999941e-02 +3.841980999999999868e-01 2.767105533333329870e+00 4.386071833333329839e-02 +3.991226999999999969e-01 2.564823933333329808e+00 4.256582833333329846e-02 +4.106734000000000218e-01 2.300881685499999829e+00 4.238948299999999864e-02 +4.138850000000000029e-01 2.413576710000000070e+00 4.049426666666670199e-02 +4.293050999999999950e-01 2.233726248333329778e+00 3.713503166666669991e-02 +4.295516999999999808e-01 2.156876818999999834e+00 3.601888033333330158e-02 +4.448524999999999840e-01 1.902652508333330106e+00 3.572283666666670188e-02 +4.466716000000000020e-01 1.934442038833330102e+00 3.205653166666670023e-02 +4.605926000000000187e-01 1.874115690000000001e+00 3.360869833333329781e-02 +4.685437000000000074e-01 1.737730488500000003e+00 3.144913299999999717e-02 +4.767550999999999872e-01 1.772107871666670054e+00 3.193823833333329920e-02 +4.834083000000000130e-01 1.581873448666669901e+00 2.976716433333330067e-02 +4.925984999999999947e-01 1.570737606666670061e+00 3.137136000000000091e-02 +5.046621000000000024e-01 1.492830698499999942e+00 2.756047650000000016e-02 +5.085577999999999488e-01 1.485373084999999982e+00 2.891283166666670096e-02 +5.230728999999999518e-01 1.159546245333330061e+00 2.533816566666670031e-02 +5.252126000000000294e-01 1.354185358333330091e+00 2.695080999999999866e-02 +5.419834999999999514e-01 1.186315898333329955e+00 2.629908666666670031e-02 +5.444001000000000534e-01 1.201936935333330014e+00 2.613158683333329999e-02 +5.586221000000000103e-01 1.109243371666670086e+00 2.550917499999999852e-02 +5.612614999999999688e-01 1.098874749833330000e+00 2.373973216666670077e-02 +5.757206000000000268e-01 1.029040688333330067e+00 2.359920000000000073e-02 +5.834072999999999620e-01 9.811238644999999980e-01 2.340685516666669852e-02 +5.930746000000000073e-01 9.534806916666670462e-01 2.272023833333329870e-02 +6.005747000000000169e-01 8.956047156666669951e-01 2.218217800000000031e-02 +6.102501999999999649e-01 8.805252883333329894e-01 2.205115333333329888e-02 +6.218546000000000351e-01 8.076579554999999688e-01 2.150926133333330020e-02 +6.278546000000000404e-01 8.143317416666669972e-01 2.081983000000000097e-02 +6.412082999999999533e-01 7.185076576666670212e-01 1.917512450000000146e-02 +6.450795999999999752e-01 7.187583950000000499e-01 2.116659499999999985e-02 +6.655497999999999692e-01 6.417931828333329758e-01 1.838261283333330123e-02 +6.843702000000000396e-01 5.677104609999999996e-01 2.033867249999999835e-02 +7.058105999999999547e-01 5.146355146666670155e-01 1.728668516666670082e-02 +7.258693999999999980e-01 5.183306771666670310e-01 1.859332783333330144e-02 +7.490480000000000471e-01 4.413184218333329745e-01 1.519721516666669957e-02 +7.720086999999999922e-01 4.069291728333330194e-01 1.722663266666669968e-02 +7.920989999999999975e-01 3.912903401666669723e-01 1.517220816666670080e-02 +8.155097999999999514e-01 3.620392363333330144e-01 1.546880433333329939e-02 +8.364475000000000104e-01 3.218374210000000124e-01 1.467598383333330002e-02 +8.619377000000000288e-01 2.793489951666670024e-01 1.373524349999999915e-02 +8.846403999999999934e-01 2.729738648333330242e-01 1.444214883333330007e-02 +9.081462000000000145e-01 2.731246803333329809e-01 1.275928200000000026e-02 +9.334447000000000161e-01 2.513956430000000020e-01 1.371339566666670076e-02 +9.551832000000000100e-01 2.075616728333329886e-01 1.280443116666669934e-02 +9.812971999999999806e-01 2.026315226666670077e-01 1.202260966666669935e-02 +1.006807800000000030e+00 1.715856353333329865e-01 1.237966916666670067e-02 +1.030406600000000061e+00 1.813217336666670121e-01 1.183022066666669994e-02 +1.057784300000000011e+00 1.874246033333329953e-01 1.099227083333330010e-02 +1.084600999999999926e+00 1.682342156666669919e-01 1.130467983333329970e-02 +1.109902299999999897e+00 1.305937931666669993e-01 1.088363799999999930e-02 +1.137751600000000085e+00 1.264251891666670069e-01 1.045857849999999936e-02 +1.166036300000000026e+00 1.206125236666669986e-01 1.021913349999999977e-02 \ No newline at end of file diff --git a/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/7_200_1340_10.csv b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/7_200_1340_10.csv new file mode 100644 index 00000000..e8e9c9a0 --- /dev/null +++ b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/7_200_1340_10.csv @@ -0,0 +1,105 @@ +3.625239999999999713e-02 8.265676436333329491e+01 1.638320715333329858e+01 +4.070000000000000007e-02 5.464383480166669926e+01 1.136626790833330070e+01 +4.511180000000000051e-02 2.996549328500000087e+01 8.362137733333330658e+00 +4.959249999999999770e-02 3.902000664833330035e+01 6.829579628333330099e+00 +5.416379999999999806e-02 2.960868386499999971e+01 5.359635035000000158e+00 +5.868999999999999911e-02 2.775202533166670094e+01 4.620562245000000345e+00 +6.315320000000000655e-02 2.923420741833329828e+01 3.806268636666669813e+00 +6.773540000000000116e-02 2.925570898666670061e+01 3.181662941666670186e+00 +7.238989999999999314e-02 2.438109743166669929e+01 2.769415394999999780e+00 +7.694339999999999513e-02 2.082174377999999848e+01 2.449056793333329818e+00 +8.166600000000000248e-02 2.093264007666670068e+01 2.067833788333329981e+00 +8.641719999999999957e-02 2.065156679166669917e+01 1.869354308333329939e+00 +9.109140000000000292e-02 2.096371475999999845e+01 1.658336066666669995e+00 +9.576210000000000278e-02 1.686565956500000141e+01 1.512923419999999908e+00 +1.005216000000000026e-01 1.674424940666670025e+01 1.343149441666670052e+00 +1.052672000000000052e-01 1.453594207833329932e+01 1.233355748333329949e+00 +1.100174000000000013e-01 1.743436942499999986e+01 1.141890178333329953e+00 +1.148721999999999938e-01 1.514907325499999935e+01 1.030380964999999982e+00 +1.198083000000000065e-01 1.335097668833330076e+01 9.427780516666669497e-01 +1.248023999999999939e-01 1.334016233833330034e+01 8.846290449999999472e-01 +1.298709000000000113e-01 1.370878951499999943e+01 8.044898483333330352e-01 +1.349431000000000103e-01 1.169732300833329930e+01 7.648545733333329544e-01 +1.399610000000000021e-01 1.178831958166670013e+01 6.994071549999999471e-01 +1.451112999999999986e-01 1.065116290166669977e+01 6.676857533333330208e-01 +1.503050999999999970e-01 9.325193926666669242e+00 6.273500633333329857e-01 +1.556469000000000047e-01 1.029459589333329994e+01 5.781039766666670188e-01 +1.572456999999999883e-01 1.000640059000000015e+01 1.830944850000000013e-01 +1.609891999999999990e-01 1.045812633999999974e+01 5.605480683333330383e-01 +1.662309999999999899e-01 9.379805284999999770e+00 5.273011283333329802e-01 +1.706292000000000086e-01 9.152038171666669442e+00 1.545153300000000063e-01 +1.716062999999999894e-01 9.072271783333329509e+00 5.002084749999999858e-01 +1.771733000000000058e-01 8.299759079999999400e+00 4.647556766666670058e-01 +1.827794000000000085e-01 8.918414625000000484e+00 4.514085349999999996e-01 +1.842428999999999872e-01 8.303275895000000517e+00 1.338776349999999948e-01 +1.883665999999999952e-01 8.087114149999999668e+00 4.329608600000000029e-01 +1.940302000000000138e-01 6.940626968333329927e+00 4.097805333333329747e-01 +1.982414999999999872e-01 7.399220633333330355e+00 1.156899150000000043e-01 +1.998185000000000100e-01 7.671813116666670318e+00 3.925434549999999856e-01 +2.056348000000000065e-01 7.058416683333329722e+00 3.753756766666669908e-01 +2.115208000000000088e-01 6.186979153333330039e+00 3.502447450000000240e-01 +2.120113000000000136e-01 6.509825833333329648e+00 1.046767516666669978e-01 +2.162467999999999890e-01 6.237968744999999871e+00 4.443455150000000242e-01 +2.259823999999999999e-01 6.054194650000000344e+00 9.278840666666669790e-02 +2.397845000000000115e-01 5.564811654999999746e+00 8.685724833333340056e-02 +2.537096999999999825e-01 5.167096618333330227e+00 7.958348833333329930e-02 +2.676275000000000182e-01 4.857346653333330266e+00 7.348198000000000230e-02 +2.817857999999999752e-01 4.540460176666670122e+00 6.833893833333329337e-02 +2.956398000000000081e-01 4.237780543333330208e+00 6.471250666666669704e-02 +3.099044000000000243e-01 3.935048769999999863e+00 5.903304000000000190e-02 +3.240645999999999805e-01 3.478254921666669830e+00 5.662396666666669881e-02 +3.388027000000000122e-01 3.303115776666670111e+00 5.088800999999999741e-02 +3.539140000000000064e-01 3.111107356666670043e+00 4.976782999999999901e-02 +3.687090000000000090e-01 2.860701315000000022e+00 4.573441000000000312e-02 +3.839813000000000254e-01 2.699693565000000017e+00 4.341666500000000012e-02 +3.988975000000000160e-01 2.495346641666670084e+00 4.210437499999999944e-02 +4.106734000000000218e-01 2.304978501333330154e+00 4.227540733333329942e-02 +4.136514000000000024e-01 2.281161938333330141e+00 3.975256666666669714e-02 +4.290628000000000219e-01 2.190969609999999790e+00 3.681797500000000278e-02 +4.295516999999999808e-01 2.110397390999999789e+00 3.571657216666670326e-02 +4.446014999999999828e-01 1.879588453333330023e+00 3.550642500000000118e-02 +4.466716000000000020e-01 1.816268564666670082e+00 3.148649049999999866e-02 +4.603325999999999807e-01 1.793649588333330103e+00 3.313105499999999953e-02 +4.685437000000000074e-01 1.770170460833329962e+00 3.148902633333330175e-02 +4.764860000000000206e-01 1.682140176666669928e+00 3.141608500000000331e-02 +4.834083000000000130e-01 1.536765404999999918e+00 2.947832866666669910e-02 +4.923204999999999942e-01 1.511959243333329983e+00 3.099057499999999937e-02 +5.046621000000000024e-01 1.465441131499999994e+00 2.736933133333329868e-02 +5.082708000000000226e-01 1.365583310000000106e+00 2.825172333333330135e-02 +5.230728999999999518e-01 1.154780808166669948e+00 2.524863800000000033e-02 +5.249162000000000550e-01 1.329800611666670074e+00 2.675280666666670151e-02 +5.416775999999999813e-01 1.188958583333330044e+00 2.623523499999999911e-02 +5.444001000000000534e-01 1.154485974333330001e+00 2.585743066666670170e-02 +5.583067999999999920e-01 1.077189841666670089e+00 2.527868166666669830e-02 +5.612614999999999688e-01 1.051792144833330056e+00 2.347724083333330158e-02 +5.753956999999999544e-01 1.014095294999999952e+00 2.346036166666670003e-02 +5.834072999999999620e-01 9.588631039999999661e-01 2.325032483333330097e-02 +5.927398999999999862e-01 9.388917833333330076e-01 2.258296499999999998e-02 +6.005747000000000169e-01 8.756694673333329515e-01 2.203778849999999886e-02 +6.099058000000000535e-01 8.473022983333330371e-01 2.182166666666670099e-02 +6.218546000000000351e-01 7.881510335000000422e-01 2.137202599999999883e-02 +6.275003000000000108e-01 7.785123416666670515e-01 2.058900166666670015e-02 +6.412082999999999533e-01 7.088910750000000371e-01 1.908878433333329946e-02 +6.447154999999999969e-01 6.898300916666669780e-01 2.096122833333330035e-02 +6.655497999999999692e-01 6.325061706666670336e-01 1.830133016666669887e-02 +6.843702000000000396e-01 5.535274884999999978e-01 2.022582083333330019e-02 +7.058105999999999547e-01 4.772884753333330177e-01 1.710068249999999873e-02 +7.258693999999999980e-01 4.794482545000000040e-01 1.838371499999999839e-02 +7.490480000000000471e-01 4.303608649999999813e-01 1.512209783333329921e-02 +7.720086999999999922e-01 3.958678310000000033e-01 1.713988450000000080e-02 +7.920989999999999975e-01 3.804415833333329999e-01 1.509590366666670007e-02 +8.155097999999999514e-01 3.391658666666669819e-01 1.534219399999999997e-02 +8.364475000000000104e-01 2.920511656666670008e-01 1.453006433333330072e-02 +8.619377000000000288e-01 2.768105268333330149e-01 1.369777266666669970e-02 +8.846403999999999934e-01 2.773741731666670152e-01 1.443021766666669967e-02 +9.081462000000000145e-01 2.284839270000000033e-01 1.257401600000000036e-02 +9.334447000000000161e-01 2.262680790000000108e-01 1.358766933333330033e-02 +9.551832000000000100e-01 2.014745451666669906e-01 1.275664699999999943e-02 +9.812971999999999806e-01 1.828399243333329871e-01 1.193000216666669985e-02 +1.006807800000000030e+00 1.768480391666669982e-01 1.237525766666669989e-02 +1.030406600000000061e+00 1.607457411666670111e-01 1.173357283333329934e-02 +1.057784300000000011e+00 1.603443938333329877e-01 1.087976483333330004e-02 +1.084600999999999926e+00 1.672401265000000026e-01 1.127955949999999950e-02 +1.109902299999999897e+00 1.308651896666669923e-01 1.086452716666670010e-02 +1.137751600000000085e+00 1.208445169999999985e-01 1.042120399999999988e-02 +1.166036300000000026e+00 1.219414448333329959e-01 1.020481383333330001e-02 \ No newline at end of file diff --git a/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/8_300_1340_10.csv b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/8_300_1340_10.csv new file mode 100644 index 00000000..c576fb9b --- /dev/null +++ b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/8_300_1340_10.csv @@ -0,0 +1,105 @@ +3.625710000000000044e-02 7.586499487000000386e+01 1.637401323166670153e+01 +4.070529999999999982e-02 4.481643682666670259e+01 1.134473448500000003e+01 +4.511769999999999670e-02 3.187475403666670104e+01 8.372120233333330219e+00 +4.959899999999999726e-02 2.997679580000000144e+01 6.801308398333329563e+00 +5.417079999999999812e-02 2.394855565499999983e+01 5.340431286666669664e+00 +5.869760000000000255e-02 2.142582576833330066e+01 4.594792676666670239e+00 +6.316149999999999542e-02 3.089957102166669856e+01 3.816311058333329953e+00 +6.774429999999999341e-02 2.654007947166670078e+01 3.169185818333330129e+00 +7.239929999999999977e-02 1.943375908333329960e+01 2.742530958333329938e+00 +7.695340000000000513e-02 1.976728253666669843e+01 2.443851029999999813e+00 +8.167670000000000485e-02 1.925900297000000094e+01 2.058348746666669893e+00 +8.642850000000000532e-02 1.891829230833329945e+01 1.858069830000000033e+00 +9.110319999999999530e-02 1.827805419833330092e+01 1.639507476666669961e+00 +9.577460000000000140e-02 1.569160155166670023e+01 1.504524439999999963e+00 +1.005346000000000017e-01 1.326347007166669911e+01 1.315847523333329994e+00 +1.052808999999999967e-01 1.528716061000000082e+01 1.240626203333329958e+00 +1.100316999999999962e-01 1.357029930000000029e+01 1.108701733333329997e+00 +1.148871000000000059e-01 1.268526336333330029e+01 1.008324261666670107e+00 +1.198237999999999942e-01 1.157206131499999913e+01 9.266636050000000013e-01 +1.248187000000000046e-01 1.139260776499999928e+01 8.664271866666669597e-01 +1.298877999999999977e-01 1.288652256499999993e+01 7.969746983333330093e-01 +1.349605999999999861e-01 1.079102049499999971e+01 7.560191466666670301e-01 +1.399791999999999981e-01 1.105679143000000053e+01 6.920319183333329960e-01 +1.451301000000000119e-01 1.044385611833330074e+01 6.659680816666669889e-01 +1.503246000000000027e-01 9.389620521666669717e+00 6.284466633333329888e-01 +1.556671000000000027e-01 9.365269319999999453e+00 5.685768949999999711e-01 +1.572933999999999999e-01 9.202126339999999516e+00 1.807199866666669985e-01 +1.610100999999999893e-01 9.725111731666670423e+00 5.526473100000000027e-01 +1.662526000000000004e-01 8.453553321666669618e+00 5.170390433333329483e-01 +1.706808999999999965e-01 8.508661829999999426e+00 1.525917300000000087e-01 +1.716285999999999923e-01 8.248625778333330771e+00 4.912094450000000223e-01 +1.771963000000000010e-01 7.860151886666669974e+00 4.601271083333329792e-01 +1.828030999999999962e-01 8.179063558333330874e+00 4.431557566666670112e-01 +1.842987000000000097e-01 7.789301021666670266e+00 1.322509683333329966e-01 +1.883911000000000058e-01 7.284922560000000047e+00 4.239183200000000151e-01 +1.940553999999999890e-01 6.860865920000000173e+00 4.091373166666669725e-01 +1.983015999999999945e-01 6.984366938333329777e+00 1.143283233333329957e-01 +1.998445000000000082e-01 7.321059296666669880e+00 3.888106266666669919e-01 +2.056614999999999971e-01 6.516638536666669701e+00 3.691254600000000163e-01 +2.115482999999999947e-01 5.813378858333329902e+00 3.460794933333329881e-01 +2.120755000000000001e-01 6.410695145000000039e+00 1.044179233333329959e-01 +2.162748999999999922e-01 6.046752721666670105e+00 4.416217800000000193e-01 +2.260508999999999991e-01 5.970100884999999913e+00 9.257100833333330170e-02 +2.398570999999999898e-01 5.328183368333330172e+00 8.599138666666669706e-02 +2.537865999999999733e-01 5.025059630000000332e+00 7.909936500000000481e-02 +2.677086000000000188e-01 4.638679231666669622e+00 7.265197833333329747e-02 +2.818711999999999884e-01 4.320586221666670390e+00 6.748263000000000178e-02 +2.957293999999999756e-01 4.003143766666670267e+00 6.376038000000000538e-02 +3.099983000000000044e-01 3.820362695000000031e+00 5.861029000000000239e-02 +3.241628999999999761e-01 3.476478435000000200e+00 5.668581000000000314e-02 +3.389054000000000233e-01 3.203212216666670109e+00 5.052067666666670148e-02 +3.540212999999999832e-01 3.082675891666669887e+00 4.970334166666669912e-02 +3.688208000000000042e-01 2.852994409999999981e+00 4.575685000000000169e-02 +3.840975999999999835e-01 2.619206445000000105e+00 4.310591500000000159e-02 +3.990183999999999953e-01 2.447015261666670050e+00 4.192961333333330293e-02 +4.106022999999999756e-01 2.250148667666670210e+00 4.208387400000000028e-02 +4.137768000000000002e-01 2.199470903333330174e+00 3.941821333333329902e-02 +4.291927999999999854e-01 2.152990283333330090e+00 3.668624166666670239e-02 +4.294773000000000063e-01 2.066339865166670009e+00 3.559078633333329772e-02 +4.447362000000000259e-01 1.856158275000000080e+00 3.543832166666670280e-02 +4.465942999999999996e-01 1.863514126000000104e+00 3.172593550000000345e-02 +4.604721999999999982e-01 1.785743791666670077e+00 3.313367500000000132e-02 +4.684626000000000068e-01 1.701883971166670007e+00 3.125393900000000141e-02 +4.766304000000000096e-01 1.673134868333330028e+00 3.141082333333330284e-02 +4.833245999999999931e-01 1.561424375333329895e+00 2.963426116666669982e-02 +4.924697000000000102e-01 1.528741043333329941e+00 3.110879000000000075e-02 +5.045747000000000426e-01 1.440318964999999896e+00 2.730927600000000038e-02 +5.084248000000000101e-01 1.414442154999999923e+00 2.852017166666670142e-02 +5.229823000000000111e-01 1.195010044500000035e+00 2.544837383333330150e-02 +5.250753000000000226e-01 1.305492214999999900e+00 2.666661666666670163e-02 +5.418418000000000401e-01 1.152673440000000049e+00 2.608773333333330030e-02 +5.443057999999999508e-01 1.145610492499999911e+00 2.585580999999999990e-02 +5.584759999999999724e-01 1.067225758333329999e+00 2.525753999999999846e-02 +5.611642999999999493e-01 1.067666433166670092e+00 2.357715833333329930e-02 +5.755700000000000260e-01 1.029394661666670041e+00 2.355942499999999842e-02 +5.833063000000000553e-01 9.377488744999999959e-01 2.319254466666669998e-02 +5.929195000000000437e-01 9.657185166666669707e-01 2.274125999999999925e-02 +6.004707000000000239e-01 8.602252591666670334e-01 2.200228683333330104e-02 +6.100906000000000384e-01 8.435776883333330201e-01 2.182633499999999879e-02 +6.217470000000000496e-01 7.760975660000000165e-01 2.134885499999999992e-02 +6.276903999999999817e-01 7.741666183333330009e-01 2.058969000000000077e-02 +6.410972000000000337e-01 7.019246166666669451e-01 1.908517183333329967e-02 +6.449108999999999536e-01 6.851353900000000108e-01 2.095862666666669857e-02 +6.654345000000000399e-01 5.961630181666669470e-01 1.818150283333330036e-02 +6.842517000000000182e-01 5.507593983333329835e-01 2.023759049999999948e-02 +7.056883000000000461e-01 4.903288296666670210e-01 1.717216466666670119e-02 +7.257436999999999916e-01 4.865799499999999833e-01 1.843612033333329875e-02 +7.489183000000000368e-01 4.252237993333329857e-01 1.512044316666670031e-02 +7.718749999999999778e-01 3.910920323333330062e-01 1.713838283333329882e-02 +7.919618000000000491e-01 3.921714671666670093e-01 1.515910683333330025e-02 +8.153685999999999989e-01 3.357406613333330236e-01 1.534501599999999952e-02 +8.363026999999999544e-01 3.075498956666670169e-01 1.460560383333329992e-02 +8.617884000000000100e-01 2.857851470000000171e-01 1.374501849999999921e-02 +8.844872000000000289e-01 2.740424961666669823e-01 1.443190633333329975e-02 +9.079890000000000461e-01 2.574230581666669959e-01 1.269050000000000039e-02 +9.332829999999999737e-01 2.258197691666669893e-01 1.359980449999999980e-02 +9.550178000000000278e-01 1.934376846666669980e-01 1.273933533333329940e-02 +9.811271999999999771e-01 1.665732448333329951e-01 1.188447333333329976e-02 +1.006633399999999900e+00 1.640185443333329884e-01 1.234030849999999922e-02 +1.030228200000000038e+00 1.357939996666669979e-01 1.165456533333330061e-02 +1.057601100000000072e+00 1.607766528333330058e-01 1.089187166666670016e-02 +1.084413199999999966e+00 1.505459054999999935e-01 1.123158833333329915e-02 +1.109710100000000033e+00 1.282342724999999961e-01 1.086581349999999994e-02 +1.137554500000000024e+00 1.161345933333329944e-01 1.041549200000000015e-02 +1.165834400000000048e+00 1.239725849999999963e-01 1.022116500000000081e-02 \ No newline at end of file diff --git a/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/9_600_1340_10.csv b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/9_600_1340_10.csv new file mode 100644 index 00000000..c869ca7d --- /dev/null +++ b/test/mumag/FeNiB_perpendicular_Bersweiler_et_al/9_600_1340_10.csv @@ -0,0 +1,105 @@ +3.625239999999999713e-02 5.114892415500000311e+01 1.631213251166670020e+01 +4.070000000000000007e-02 4.576787608166669941e+01 1.134772132833330005e+01 +4.511180000000000051e-02 4.331816215999999997e+01 8.407984515000000769e+00 +4.959249999999999770e-02 2.519454865000000154e+01 6.784784965000000057e+00 +5.416379999999999806e-02 1.407789967999999980e+01 5.302135776666670353e+00 +5.868999999999999911e-02 1.337527417166669963e+01 4.558895663333330184e+00 +6.315320000000000655e-02 2.088227957833329995e+01 3.767553325000000175e+00 +6.773540000000000116e-02 2.352067832833330030e+01 3.153463791666669902e+00 +7.238989999999999314e-02 1.459065788833330046e+01 2.714546644999999980e+00 +7.694339999999999513e-02 1.643097480333329941e+01 2.423213508333330157e+00 +8.166600000000000248e-02 1.564276924666670077e+01 2.035339249999999822e+00 +8.641719999999999957e-02 1.609254856500000130e+01 1.837833853333330048e+00 +9.109140000000000292e-02 1.279946007166670086e+01 1.598433549999999981e+00 +9.576210000000000278e-02 1.168004799999999932e+01 1.472505774999999906e+00 +1.005216000000000026e-01 1.239115217500000021e+01 1.308776653333330042e+00 +1.052672000000000052e-01 1.104442360666669920e+01 1.203200884999999998e+00 +1.100174000000000013e-01 1.264824788499999997e+01 1.100505863333330003e+00 +1.148721999999999938e-01 1.068150069333329988e+01 9.895295950000000396e-01 +1.198083000000000065e-01 1.062919121333329997e+01 9.177280949999999660e-01 +1.248023999999999939e-01 8.480228023333330256e+00 8.376397483333329896e-01 +1.298709000000000113e-01 1.054387282333330056e+01 7.736622133333329598e-01 +1.349431000000000103e-01 8.823326103333329229e+00 7.353996000000000421e-01 +1.399610000000000021e-01 9.107303678333330765e+00 6.707318450000000487e-01 +1.451112999999999986e-01 7.568946575000000010e+00 6.354211899999999691e-01 +1.503050999999999970e-01 7.486937655000000191e+00 6.076642716666670330e-01 +1.556469000000000047e-01 8.316071640000000542e+00 5.572019216666670438e-01 +1.572933999999999999e-01 8.082806423333330770e+00 1.772221983333329975e-01 +1.609891999999999990e-01 8.406204656666670161e+00 5.374420016666670019e-01 +1.662309999999999899e-01 7.305961626666669595e+00 5.035922400000000243e-01 +1.706808999999999965e-01 7.409980823333330413e+00 1.490785450000000067e-01 +1.716062999999999894e-01 7.126663556666669841e+00 4.782201200000000263e-01 +1.771733000000000058e-01 6.104670323333330373e+00 4.398585833333329975e-01 +1.827794000000000085e-01 6.314649313333330127e+00 4.207942599999999755e-01 +1.842987000000000097e-01 6.985669296666669581e+00 1.295300000000000062e-01 +1.883665999999999952e-01 7.196837438333330006e+00 4.229003000000000068e-01 +1.940302000000000138e-01 5.456149725000000394e+00 3.926154199999999928e-01 +1.983015999999999945e-01 6.101519538333329606e+00 1.111746183333330029e-01 +1.998185000000000100e-01 6.715507050000000255e+00 3.818104066666669905e-01 +2.056348000000000065e-01 5.954089436666669677e+00 3.622545733333329965e-01 +2.115208000000000088e-01 5.823000606666670187e+00 3.462099583333330122e-01 +2.120755000000000001e-01 5.689073146666670411e+00 1.016859966666670001e-01 +2.162467999999999890e-01 5.274567024999999632e+00 4.291691899999999782e-01 +2.260508999999999991e-01 5.306054011666669901e+00 9.002336499999999408e-02 +2.398570999999999898e-01 4.872989734999999989e+00 8.416759833333330165e-02 +2.537865999999999733e-01 4.590923198333330291e+00 7.736351666666670124e-02 +2.677086000000000188e-01 4.288802803333330083e+00 7.121471833333330170e-02 +2.818711999999999884e-01 4.093612151666669696e+00 6.655526166666669852e-02 +2.957293999999999756e-01 3.790488160000000217e+00 6.286535166666669394e-02 +3.099983000000000044e-01 3.646502630000000078e+00 5.789795166666669712e-02 +3.241628999999999761e-01 3.269809508333330061e+00 5.578990999999999811e-02 +3.389054000000000233e-01 3.057003066666669877e+00 4.992672166666670130e-02 +3.540212999999999832e-01 3.010102380000000188e+00 4.942106333333329965e-02 +3.688208000000000042e-01 2.669906414999999811e+00 4.496581333333329877e-02 +3.840975999999999835e-01 2.619258584999999862e+00 4.314465499999999704e-02 +3.990183999999999953e-01 2.335828473333330102e+00 4.143680999999999753e-02 +4.106379000000000001e-01 2.148179208166669962e+00 4.160297799999999879e-02 +4.137768000000000002e-01 2.129094906666670006e+00 3.911823833333329808e-02 +4.291927999999999854e-01 2.070782439999999891e+00 3.633175833333329718e-02 +4.295145000000000213e-01 2.006558257333329820e+00 3.534480766666669993e-02 +4.447362000000000259e-01 1.796411879999999961e+00 3.518650999999999723e-02 +4.466328999999999994e-01 1.795892682000000073e+00 3.145603500000000025e-02 +4.604721999999999982e-01 1.744876515000000072e+00 3.296921333333330262e-02 +4.685032000000000085e-01 1.640983006833329982e+00 3.099933850000000102e-02 +4.766304000000000096e-01 1.643527214999999986e+00 3.129624333333329983e-02 +4.833663999999999739e-01 1.494623838666669924e+00 2.933965966666670158e-02 +4.924697000000000102e-01 1.485128543333330109e+00 3.092004166666669981e-02 +5.046184000000000225e-01 1.428664912499999939e+00 2.726148050000000087e-02 +5.084248000000000101e-01 1.392996424999999983e+00 2.844018666666670025e-02 +5.230276000000000369e-01 1.160762609500000098e+00 2.531033383333329903e-02 +5.250753000000000226e-01 1.272159111666669951e+00 2.652694499999999969e-02 +5.418418000000000401e-01 1.157124196666670102e+00 2.613007999999999997e-02 +5.443529999999999758e-01 1.137421045666670016e+00 2.582160033333329857e-02 +5.584759999999999724e-01 1.068463686666669910e+00 2.528335666666670090e-02 +5.612129000000000145e-01 1.030207043666669930e+00 2.342105016666670009e-02 +5.755700000000000260e-01 9.590708749999999894e-01 2.323855999999999838e-02 +5.833568000000000087e-01 9.226566683333330410e-01 2.312843766666669923e-02 +5.929195000000000437e-01 9.361494049999999900e-01 2.261169999999999847e-02 +6.005226999999999649e-01 8.377321681666669573e-01 2.190618299999999921e-02 +6.100906000000000384e-01 8.074355933333330348e-01 2.165867833333329912e-02 +6.218008000000000424e-01 7.338905468333329907e-01 2.116956166666670094e-02 +6.276903999999999817e-01 7.588048750000000453e-01 2.052974999999999939e-02 +6.411527999999999672e-01 6.602236648333329461e-01 1.891721183333330142e-02 +6.449108999999999536e-01 6.976061483333330093e-01 2.103810166666670103e-02 +6.654921999999999782e-01 5.868389913333329488e-01 1.814479133333329886e-02 +6.843110000000000026e-01 5.442379206666669855e-01 2.020865366666670104e-02 +7.057493999999999712e-01 4.561023925000000090e-01 1.703726683333330050e-02 +7.258065000000000211e-01 4.680557555000000036e-01 1.835656600000000124e-02 +7.489831000000000127e-01 4.286472281666670048e-01 1.513358700000000043e-02 +7.719418000000000113e-01 3.942902281666669784e-01 1.715291783333329836e-02 +7.920304000000000233e-01 3.917498161666669865e-01 1.515774683333329965e-02 +8.154392000000000307e-01 3.437863165000000221e-01 1.537817166666670052e-02 +8.363751000000000380e-01 2.882515494999999817e-01 1.453092849999999998e-02 +8.618630000000000457e-01 2.511196196666670155e-01 1.361783299999999933e-02 +8.845638000000000112e-01 2.644882184999999830e-01 1.439263916666670001e-02 +9.080675999999999748e-01 2.444909116666670046e-01 1.264447816666670020e-02 +9.333637999999999657e-01 2.291532736666669900e-01 1.361314100000000048e-02 +9.551005000000000189e-01 1.942403736666669933e-01 1.274254833333329957e-02 +9.812121999999999788e-01 1.854263468333330056e-01 1.195107366666670057e-02 +1.006720599999999965e+00 1.833105325000000119e-01 1.241118433333330065e-02 +1.030317399999999939e+00 1.560276093333330116e-01 1.172809033333330024e-02 +1.057692700000000041e+00 1.687616888333330067e-01 1.091902633333330028e-02 +1.084507099999999946e+00 1.609880563333329906e-01 1.126864266666669986e-02 +1.109806099999999907e+00 1.237764021666669934e-01 1.085079116666669979e-02 +1.137653000000000025e+00 1.252411816666670064e-01 1.044549983333330039e-02 +1.165935399999999955e+00 1.143170270000000016e-01 1.018908599999999998e-02 \ No newline at end of file diff --git a/test/mumag/Nanoperm_perpendicular_Honecker_et_al/1_33_1640_22.874115.csv b/test/mumag/Nanoperm_perpendicular_Honecker_et_al/1_33_1640_22.874115.csv new file mode 100644 index 00000000..160f11da --- /dev/null +++ b/test/mumag/Nanoperm_perpendicular_Honecker_et_al/1_33_1640_22.874115.csv @@ -0,0 +1,60 @@ +2.732000000000000053e-02 5.247581629999999677e+03 5.122294999999999732e+01 +3.090999999999999998e-02 3.911350339999999960e+03 4.422301999999999822e+01 +3.452999999999999819e-02 2.897911560000000009e+03 3.806515000000000271e+01 +3.812999999999999723e-02 2.151411560000000009e+03 3.279795000000000016e+01 +4.173999999999999932e-02 1.629610879999999952e+03 2.854479999999999862e+01 +4.542999999999999816e-02 1.229812930000000051e+03 2.479730999999999952e+01 +4.646000000000000130e-02 1.191418850000000020e+03 2.440716000000000108e+01 +4.909000000000000169e-02 9.198557799999999816e+02 2.144593000000000060e+01 +5.254999999999999949e-02 7.981839199999999437e+02 1.997728999999999999e+01 +5.270999999999999991e-02 7.205761899999999969e+02 1.898125999999999891e+01 +5.630999999999999894e-02 5.509914999999999736e+02 1.659806000000000026e+01 +5.870999999999999830e-02 5.012570499999999925e+02 1.583125000000000071e+01 +5.988000000000000267e-02 4.332142900000000054e+02 1.471757999999999988e+01 +6.353999999999999926e-02 3.420333299999999781e+02 1.307732999999999990e+01 +6.482999999999999874e-02 3.398080699999999865e+02 1.303472000000000008e+01 +6.716999999999999360e-02 2.726683699999999817e+02 1.167622000000000071e+01 +7.073000000000000120e-02 2.204248299999999858e+02 1.049821000000000026e+01 +7.095999999999999530e-02 2.235476199999999949e+02 1.057230999999999987e+01 +7.434999999999999942e-02 1.760958799999999940e+02 9.383390000000000342e+00 +7.724000000000000310e-02 1.542382200000000125e+02 8.781750000000000611e+00 +7.799999999999999989e-02 1.457825499999999863e+02 8.537639999999999674e+00 +8.164000000000000423e-02 1.201677600000000012e+02 7.751380000000000159e+00 +8.346000000000000640e-02 1.089934400000000068e+02 7.382189999999999586e+00 +8.525000000000000633e-02 9.836262000000000683e+01 7.012940000000000396e+00 +8.881999999999999618e-02 8.192439000000000249e+01 6.400170000000000137e+00 +8.962000000000000521e-02 7.733648999999999774e+01 6.218379999999999797e+00 +9.572999999999999565e-02 5.882012000000000285e+01 5.423099999999999810e+00 +1.018000000000000016e-01 4.406902000000000186e+01 4.694090000000000096e+00 +1.080300000000000010e-01 3.487624000000000279e+01 4.175900000000000389e+00 +1.141999999999999960e-01 2.769241999999999848e+01 3.721049999999999969e+00 +1.202399999999999997e-01 2.227824000000000026e+01 3.337530000000000108e+00 +1.263900000000000023e-01 1.858717000000000041e+01 3.048540000000000028e+00 +1.325999999999999956e-01 1.587791999999999959e+01 2.817619999999999791e+00 +1.388199999999999990e-01 1.416899000000000086e+01 2.661669999999999980e+00 +1.449499999999999955e-01 1.174597999999999942e+01 2.423430000000000195e+00 +1.509900000000000131e-01 1.047161000000000008e+01 2.288190000000000168e+00 +1.571599999999999941e-01 9.681599999999999540e+00 2.200180000000000025e+00 +1.633799999999999975e-01 8.894579999999999487e+00 2.108859999999999957e+00 +1.695599999999999885e-01 8.317479999999999762e+00 2.039299999999999891e+00 +1.756800000000000028e-01 7.913870000000000182e+00 1.989200000000000079e+00 +1.818599999999999939e-01 7.771530000000000271e+00 1.971230000000000038e+00 +1.880699999999999872e-01 7.882909999999999862e+00 1.985309999999999908e+00 +1.941799999999999915e-01 7.707720000000000127e+00 1.963130000000000042e+00 +2.002800000000000136e-01 7.676859999999999573e+00 1.959189999999999987e+00 +2.064199999999999924e-01 7.872259999999999813e+00 1.983970000000000011e+00 +2.126000000000000112e-01 7.870400000000000063e+00 1.983729999999999993e+00 +2.187500000000000000e-01 7.977940000000000254e+00 1.997239999999999904e+00 +2.248699999999999866e-01 8.152039999999999509e+00 2.018920000000000048e+00 +2.310500000000000054e-01 8.298420000000000130e+00 2.036960000000000104e+00 +2.372700000000000087e-01 8.716689999999999827e+00 2.087660000000000071e+00 +2.434399999999999897e-01 8.782600000000000406e+00 2.095540000000000180e+00 +2.495600000000000041e-01 8.933479999999999421e+00 2.113469999999999960e+00 +2.556899999999999729e-01 9.374109999999999943e+00 2.164960000000000218e+00 +2.618500000000000272e-01 9.457430000000000447e+00 2.174560000000000048e+00 +2.680500000000000105e-01 9.702469999999999928e+00 2.202550000000000008e+00 +2.742100000000000093e-01 9.923930000000000362e+00 2.227549999999999919e+00 +2.802999999999999936e-01 1.008966000000000030e+01 2.246070000000000011e+00 +2.864599999999999924e-01 1.031367999999999974e+01 2.270869999999999944e+00 +2.926699999999999857e-01 1.058960000000000079e+01 2.301039999999999974e+00 +2.988199999999999745e-01 1.062256999999999962e+01 2.304619999999999891e+00 \ No newline at end of file diff --git a/test/mumag/Nanoperm_perpendicular_Honecker_et_al/2_42_1640_23.456895.csv b/test/mumag/Nanoperm_perpendicular_Honecker_et_al/2_42_1640_23.456895.csv new file mode 100644 index 00000000..80562dff --- /dev/null +++ b/test/mumag/Nanoperm_perpendicular_Honecker_et_al/2_42_1640_23.456895.csv @@ -0,0 +1,60 @@ +2.732000000000000053e-02 3.883615650000000187e+03 4.406595000000000084e+01 +3.090000000000000038e-02 3.072013609999999971e+03 3.919192000000000320e+01 +3.452999999999999819e-02 2.355659860000000208e+03 3.431953000000000031e+01 +3.812999999999999723e-02 1.806985030000000052e+03 3.005815000000000126e+01 +4.173999999999999932e-02 1.389839799999999968e+03 2.636132999999999882e+01 +4.542000000000000204e-02 1.072684349999999995e+03 2.315906000000000020e+01 +4.644000000000000211e-02 9.624380499999999756e+02 2.193669999999999831e+01 +4.907999999999999863e-02 8.065360500000000457e+02 2.008153000000000077e+01 +5.253000000000000030e-02 6.666923000000000457e+02 1.825777000000000072e+01 +5.270999999999999991e-02 6.432540800000000445e+02 1.793395999999999901e+01 +5.630000000000000282e-02 4.985809499999999730e+02 1.578894000000000020e+01 +5.868999999999999911e-02 4.290448299999999904e+02 1.464658000000000015e+01 +5.986999999999999961e-02 3.918163299999999936e+02 1.399671999999999983e+01 +6.353000000000000314e-02 3.098850299999999720e+02 1.244758999999999993e+01 +6.481000000000000649e-02 2.946982499999999732e+02 1.213874000000000031e+01 +6.715999999999999748e-02 2.516384400000000028e+02 1.121692000000000000e+01 +7.072000000000000508e-02 2.028407500000000141e+02 1.007076999999999956e+01 +7.094000000000000306e-02 1.948358800000000031e+02 9.870050000000000878e+00 +7.434000000000000330e-02 1.633427599999999984e+02 9.037219999999999587e+00 +7.721000000000000085e-02 1.371933899999999937e+02 8.282310000000000727e+00 +7.799000000000000377e-02 1.347819700000000012e+02 8.209199999999999164e+00 +8.162999999999999423e-02 1.123165999999999940e+02 7.493879999999999875e+00 +8.343000000000000416e-02 9.602442000000000633e+01 6.929079999999999906e+00 +8.523999999999999633e-02 9.249591999999999814e+01 6.800589999999999691e+00 +8.880000000000000393e-02 7.934887999999999408e+01 6.298759999999999692e+00 +8.959000000000000297e-02 7.016214999999999691e+01 5.922930000000000028e+00 +9.568999999999999728e-02 5.357988000000000284e+01 5.175900000000000389e+00 +1.017699999999999994e-01 4.050641000000000247e+01 4.500359999999999694e+00 +1.079900000000000027e-01 3.194395000000000095e+01 3.996500000000000163e+00 +1.141599999999999976e-01 2.563089000000000084e+01 3.579870000000000108e+00 +1.202000000000000013e-01 2.144641999999999982e+01 3.274630000000000152e+00 +1.263499999999999901e-01 1.696240999999999843e+01 2.912249999999999783e+00 +1.325600000000000112e-01 1.453672000000000075e+01 2.695990000000000109e+00 +1.387700000000000045e-01 1.265718999999999994e+01 2.515670000000000073e+00 +1.449000000000000010e-01 1.114897000000000027e+01 2.361029999999999962e+00 +1.509399999999999908e-01 1.005156999999999989e+01 2.241830000000000211e+00 +1.571099999999999997e-01 9.313710000000000377e+00 2.157970000000000166e+00 +1.633300000000000030e-01 8.668530000000000513e+00 2.081890000000000018e+00 +1.695099999999999940e-01 7.810069999999999624e+00 1.976120000000000099e+00 +1.756300000000000083e-01 7.695079999999999920e+00 1.961519999999999930e+00 +1.817999999999999894e-01 7.222220000000000084e+00 1.900290000000000035e+00 +1.880100000000000104e-01 7.386999999999999567e+00 1.921850000000000058e+00 +1.941199999999999870e-01 7.239320000000000199e+00 1.902539999999999898e+00 +2.002200000000000091e-01 7.279099999999999682e+00 1.907759999999999900e+00 +2.063500000000000056e-01 7.406340000000000146e+00 1.924360000000000070e+00 +2.125299999999999967e-01 7.439199999999999591e+00 1.928630000000000067e+00 +2.186800000000000133e-01 7.556879999999999598e+00 1.943820000000000103e+00 +2.247899999999999898e-01 7.730520000000000280e+00 1.966029999999999944e+00 +2.309799999999999909e-01 8.106099999999999639e+00 2.013220000000000010e+00 +2.371999999999999942e-01 8.398609999999999687e+00 2.049220000000000041e+00 +2.433599999999999930e-01 8.171440000000000481e+00 2.021319999999999784e+00 +2.494800000000000073e-01 8.478289999999999438e+00 2.058920000000000083e+00 +2.556100000000000039e-01 8.796279999999999433e+00 2.097170000000000201e+00 +2.617700000000000027e-01 8.973420000000000840e+00 2.118189999999999795e+00 +2.679699999999999860e-01 9.270730000000000359e+00 2.152989999999999959e+00 +2.741299999999999848e-01 9.463879999999999626e+00 2.175300000000000011e+00 +2.802100000000000146e-01 9.409150000000000347e+00 2.169000000000000039e+00 +2.863700000000000134e-01 9.709509999999999863e+00 2.203349999999999920e+00 +2.925800000000000067e-01 9.851300000000000168e+00 2.219380000000000130e+00 +2.987299999999999955e-01 9.904920000000000613e+00 2.225410000000000110e+00 \ No newline at end of file diff --git a/test/mumag/Nanoperm_perpendicular_Honecker_et_al/3_61_1640_23.748285.csv b/test/mumag/Nanoperm_perpendicular_Honecker_et_al/3_61_1640_23.748285.csv new file mode 100644 index 00000000..6e68726d --- /dev/null +++ b/test/mumag/Nanoperm_perpendicular_Honecker_et_al/3_61_1640_23.748285.csv @@ -0,0 +1,60 @@ +2.732000000000000053e-02 2.792353740000000016e+03 3.736545000000000272e+01 +3.090000000000000038e-02 2.271702040000000125e+03 3.370239000000000118e+01 +3.452000000000000207e-02 1.788897279999999910e+03 2.990733000000000175e+01 +3.812000000000000111e-02 1.407023470000000088e+03 2.652380000000000138e+01 +4.173000000000000320e-02 1.095009520000000066e+03 2.339882000000000062e+01 +4.542000000000000204e-02 8.376734699999999521e+02 2.046549999999999869e+01 +4.644000000000000211e-02 7.836296200000000454e+02 1.979430999999999941e+01 +4.907000000000000250e-02 6.414952399999999670e+02 1.790943000000000040e+01 +5.253000000000000030e-02 5.516777200000000221e+02 1.660839999999999961e+01 +5.269999999999999685e-02 5.141714299999999866e+02 1.603388999999999953e+01 +5.628999999999999976e-02 3.988418399999999906e+02 1.412165000000000070e+01 +5.868999999999999911e-02 3.579082799999999907e+02 1.337737000000000087e+01 +5.985999999999999654e-02 3.164397999999999911e+02 1.257854999999999990e+01 +6.351999999999999313e-02 2.486343500000000120e+02 1.114976000000000056e+01 +6.481000000000000649e-02 2.482066299999999899e+02 1.114016999999999946e+01 +6.715000000000000135e-02 2.006787799999999891e+02 1.001695999999999920e+01 +7.070999999999999508e-02 1.657927200000000028e+02 9.104739999999999611e+00 +7.094000000000000306e-02 1.667760000000000105e+02 9.131700000000000372e+00 +7.431999999999999718e-02 1.326982299999999952e+02 8.145500000000000185e+00 +7.721000000000000085e-02 1.181177100000000024e+02 7.684980000000000366e+00 +7.796999999999999764e-02 1.108474499999999949e+02 7.444709999999999717e+00 +8.161999999999999811e-02 9.087302999999999997e+01 6.740660000000000096e+00 +8.343000000000000416e-02 8.290343000000000018e+01 6.438299999999999912e+00 +8.522000000000000408e-02 7.588799000000000206e+01 6.159869999999999735e+00 +8.878999999999999393e-02 6.486343999999999710e+01 5.694890000000000008e+00 +8.959000000000000297e-02 6.133471999999999724e+01 5.537810000000000343e+00 +9.568999999999999728e-02 4.711737999999999715e+01 4.853729999999999656e+00 +1.017699999999999994e-01 3.554666000000000281e+01 4.215840000000000032e+00 +1.079900000000000027e-01 2.833098000000000027e+01 3.763710000000000111e+00 +1.141599999999999976e-01 2.318925000000000125e+01 3.405089999999999950e+00 +1.202000000000000013e-01 1.876435000000000031e+01 3.063029999999999919e+00 +1.263499999999999901e-01 1.568601999999999919e+01 2.800539999999999807e+00 +1.325600000000000112e-01 1.346410000000000018e+01 2.594619999999999926e+00 +1.387700000000000045e-01 1.173840000000000039e+01 2.422639999999999905e+00 +1.449000000000000010e-01 1.054757999999999996e+01 2.296469999999999789e+00 +1.509399999999999908e-01 9.084839999999999804e+00 2.131299999999999972e+00 +1.571099999999999997e-01 8.239589999999999748e+00 2.029729999999999812e+00 +1.633300000000000030e-01 7.767660000000000231e+00 1.970739999999999936e+00 +1.695099999999999940e-01 7.631929999999999659e+00 1.953449999999999909e+00 +1.756300000000000083e-01 7.253739999999999633e+00 1.904430000000000067e+00 +1.817999999999999894e-01 6.968580000000000219e+00 1.866630000000000011e+00 +1.880100000000000104e-01 7.029829999999999579e+00 1.874810000000000088e+00 +1.941199999999999870e-01 6.805609999999999715e+00 1.844670000000000032e+00 +2.002200000000000091e-01 7.007130000000000081e+00 1.871779999999999999e+00 +2.063500000000000056e-01 7.083709999999999951e+00 1.881979999999999986e+00 +2.125299999999999967e-01 6.971890000000000143e+00 1.867070000000000007e+00 +2.186800000000000133e-01 7.549360000000000070e+00 1.942849999999999966e+00 +2.247899999999999898e-01 7.451310000000000322e+00 1.930199999999999916e+00 +2.309799999999999909e-01 7.449589999999999712e+00 1.929969999999999963e+00 +2.371999999999999942e-01 7.776720000000000077e+00 1.971889999999999921e+00 +2.433599999999999930e-01 8.157130000000000436e+00 2.019550000000000178e+00 +2.494800000000000073e-01 8.339280000000000470e+00 2.041970000000000063e+00 +2.556100000000000039e-01 8.448100000000000165e+00 2.055250000000000021e+00 +2.617700000000000027e-01 8.746420000000000528e+00 2.091219999999999857e+00 +2.679699999999999860e-01 9.040720000000000312e+00 2.126110000000000166e+00 +2.741299999999999848e-01 9.130409999999999471e+00 2.136629999999999807e+00 +2.802100000000000146e-01 9.247719999999999274e+00 2.150319999999999787e+00 +2.863700000000000134e-01 9.291150000000000020e+00 2.155359999999999943e+00 +2.925800000000000067e-01 9.640140000000000597e+00 2.195469999999999811e+00 +2.987299999999999955e-01 9.537290000000000489e+00 2.183720000000000105e+00 \ No newline at end of file diff --git a/test/mumag/Nanoperm_perpendicular_Honecker_et_al/4_103_1640_24.039675.csv b/test/mumag/Nanoperm_perpendicular_Honecker_et_al/4_103_1640_24.039675.csv new file mode 100644 index 00000000..c3350620 --- /dev/null +++ b/test/mumag/Nanoperm_perpendicular_Honecker_et_al/4_103_1640_24.039675.csv @@ -0,0 +1,60 @@ +2.733000000000000013e-02 2.320909860000000208e+03 3.406544999999999845e+01 +3.090999999999999998e-02 1.910828909999999951e+03 3.090978000000000137e+01 +3.454000000000000126e-02 1.519815650000000005e+03 2.756643000000000043e+01 +3.814000000000000029e-02 1.188850680000000011e+03 2.438083999999999918e+01 +4.175000000000000239e-02 9.312768700000000308e+02 2.157865999999999929e+01 +4.544000000000000122e-02 7.201697299999999586e+02 1.897589999999999932e+01 +4.646000000000000130e-02 6.728012599999999566e+02 1.834122999999999948e+01 +4.909999999999999781e-02 5.453608799999999519e+02 1.651304000000000016e+01 +5.256000000000000255e-02 4.724414499999999748e+02 1.536946999999999974e+01 +5.272000000000000297e-02 4.318700699999999983e+02 1.469472999999999985e+01 +5.632000000000000201e-02 3.382353699999999890e+02 1.300453000000000081e+01 +5.870999999999999830e-02 3.073326000000000136e+02 1.239621999999999957e+01 +5.988999999999999879e-02 2.634156100000000151e+02 1.147639999999999993e+01 +6.356000000000000538e-02 2.076918699999999944e+02 1.019048000000000087e+01 +6.483999999999999486e-02 2.125336499999999944e+02 1.030857999999999919e+01 +6.718999999999999972e-02 1.688028199999999970e+02 9.187020000000000408e+00 +7.073999999999999733e-02 1.362696899999999971e+02 8.254379999999999384e+00 +7.097000000000000530e-02 1.410024300000000039e+02 8.396499999999999631e+00 +7.435999999999999555e-02 1.127603699999999947e+02 7.508670000000000400e+00 +7.724999999999999922e-02 9.965560999999999581e+01 7.058880000000000265e+00 +7.800999999999999601e-02 9.194693999999999789e+01 6.780369999999999564e+00 +8.165999999999999648e-02 7.522768999999999551e+01 6.133009999999999629e+00 +8.347000000000000253e-02 7.049939999999999429e+01 5.937149999999999928e+00 +8.526000000000000245e-02 6.411020000000000607e+01 5.661719999999999864e+00 +8.883000000000000618e-02 5.376234999999999786e+01 5.184709999999999930e+00 +8.963000000000000134e-02 5.217504000000000275e+01 5.107590000000000074e+00 +9.574000000000000565e-02 4.054399999999999693e+01 4.502439999999999998e+00 +1.018199999999999938e-01 3.096013999999999911e+01 3.934470000000000134e+00 +1.080399999999999971e-01 2.426012000000000057e+01 3.482819999999999805e+00 +1.142200000000000021e-01 1.991522000000000148e+01 3.155569999999999986e+00 +1.202600000000000058e-01 1.696463999999999928e+01 2.912440000000000140e+00 +1.264099999999999946e-01 1.406012999999999913e+01 2.651429999999999954e+00 +1.326199999999999879e-01 1.186475000000000080e+01 2.435649999999999871e+00 +1.388300000000000090e-01 1.075843000000000060e+01 2.319310000000000205e+00 +1.449699999999999878e-01 9.447770000000000223e+00 2.173449999999999882e+00 +1.510100000000000053e-01 8.521739999999999426e+00 2.064189999999999969e+00 +1.571799999999999864e-01 7.782460000000000377e+00 1.972620000000000040e+00 +1.634099999999999997e-01 7.576889999999999681e+00 1.946390000000000065e+00 +1.695800000000000085e-01 7.175270000000000259e+00 1.894109999999999960e+00 +1.756999999999999951e-01 6.609770000000000145e+00 1.817930000000000046e+00 +1.818799999999999861e-01 6.690150000000000041e+00 1.828950000000000076e+00 +1.880999999999999894e-01 6.763010000000000410e+00 1.838889999999999914e+00 +1.942000000000000115e-01 6.479930000000000412e+00 1.799989999999999979e+00 +2.003099999999999881e-01 6.931930000000000369e+00 1.861709999999999976e+00 +2.064499999999999946e-01 7.058690000000000353e+00 1.878649999999999931e+00 +2.126300000000000134e-01 6.966739999999999711e+00 1.866379999999999928e+00 +2.187699999999999922e-01 7.155350000000000321e+00 1.891469999999999985e+00 +2.248999999999999888e-01 7.328820000000000334e+00 1.914260000000000073e+00 +2.310800000000000076e-01 7.623789999999999623e+00 1.952409999999999979e+00 +2.373000000000000109e-01 7.870739999999999625e+00 1.983780000000000099e+00 +2.434699999999999920e-01 7.865639999999999965e+00 1.983130000000000059e+00 +2.495900000000000063e-01 8.081390000000000740e+00 2.010149999999999881e+00 +2.557300000000000129e-01 8.480309999999999349e+00 2.059159999999999879e+00 +2.618900000000000117e-01 8.708700000000000330e+00 2.086710000000000065e+00 +2.680899999999999950e-01 8.823320000000000718e+00 2.100389999999999979e+00 +2.742499999999999938e-01 9.030450000000000088e+00 2.124909999999999854e+00 +2.803300000000000236e-01 9.222670000000000812e+00 2.147400000000000198e+00 +2.864999999999999769e-01 9.345430000000000348e+00 2.161649999999999849e+00 +2.927100000000000257e-01 9.660539999999999239e+00 2.197789999999999910e+00 +2.988600000000000145e-01 9.559969999999999857e+00 2.186319999999999819e+00 \ No newline at end of file diff --git a/test/mumag/Nanoperm_perpendicular_Honecker_et_al/5_312_1640_24.331065.csv b/test/mumag/Nanoperm_perpendicular_Honecker_et_al/5_312_1640_24.331065.csv new file mode 100644 index 00000000..17968ec9 --- /dev/null +++ b/test/mumag/Nanoperm_perpendicular_Honecker_et_al/5_312_1640_24.331065.csv @@ -0,0 +1,60 @@ +2.733000000000000013e-02 1.791495509999999967e+03 2.992905000000000015e+01 +3.091999999999999957e-02 1.477992469999999912e+03 2.718449000000000026e+01 +3.454000000000000126e-02 1.172791189999999915e+03 2.421561000000000163e+01 +3.814000000000000029e-02 9.196233300000000099e+02 2.144322000000000017e+01 +4.175000000000000239e-02 7.160111200000000053e+02 1.892102999999999824e+01 +4.544000000000000122e-02 5.391461900000000469e+02 1.641867999999999839e+01 +4.646000000000000130e-02 4.929036699999999769e+02 1.569877999999999929e+01 +4.909999999999999781e-02 4.129331099999999992e+02 1.436894000000000027e+01 +5.256000000000000255e-02 3.482022600000000239e+02 1.319473999999999947e+01 +5.272999999999999909e-02 3.242194700000000012e+02 1.273222999999999949e+01 +5.632000000000000201e-02 2.454095199999999863e+02 1.107722000000000051e+01 +5.870999999999999830e-02 2.218204599999999971e+02 1.053139000000000003e+01 +5.990000000000000185e-02 1.931666899999999885e+02 9.827680000000000859e+00 +6.356000000000000538e-02 1.505020900000000097e+02 8.674739999999999895e+00 +6.483999999999999486e-02 1.511605900000000133e+02 8.693690000000000140e+00 +6.718999999999999972e-02 1.202856799999999993e+02 7.755180000000000184e+00 +7.074999999999999345e-02 9.616129999999999711e+01 6.934020000000000294e+00 +7.097000000000000530e-02 9.870516999999999541e+01 7.025140000000000384e+00 +7.435999999999999555e-02 7.818730999999999653e+01 6.252489999999999881e+00 +7.724999999999999922e-02 6.837958000000000425e+01 5.847199999999999953e+00 +7.802000000000000601e-02 6.371482000000000312e+01 5.644239999999999924e+00 +8.165999999999999648e-02 5.179379999999999740e+01 5.088899999999999757e+00 +8.347000000000000253e-02 4.836007000000000033e+01 4.917320000000000135e+00 +8.526999999999999857e-02 4.457972000000000179e+01 4.721210000000000129e+00 +8.884000000000000230e-02 3.751116999999999990e+01 4.330770000000000231e+00 +8.963000000000000134e-02 3.544118999999999886e+01 4.209579999999999878e+00 +9.574000000000000565e-02 2.739192999999999856e+01 3.700810000000000155e+00 +1.018199999999999938e-01 2.084316000000000102e+01 3.228250000000000064e+00 +1.080399999999999971e-01 1.702296000000000120e+01 2.917440000000000033e+00 +1.142200000000000021e-01 1.391883999999999943e+01 2.638069999999999915e+00 +1.202600000000000058e-01 1.148202000000000034e+01 2.396040000000000170e+00 +1.264099999999999946e-01 9.887589999999999435e+00 2.223460000000000214e+00 +1.326199999999999879e-01 8.618710000000000093e+00 2.075899999999999856e+00 +1.388300000000000090e-01 7.875820000000000043e+00 1.984420000000000073e+00 +1.449699999999999878e-01 7.119869999999999699e+00 1.886779999999999902e+00 +1.510100000000000053e-01 6.691399999999999793e+00 1.829129999999999923e+00 +1.571799999999999864e-01 6.133020000000000138e+00 1.751139999999999919e+00 +1.634099999999999997e-01 5.984770000000000145e+00 1.729850000000000110e+00 +1.695800000000000085e-01 5.802669999999999995e+00 1.703330000000000011e+00 +1.756999999999999951e-01 5.772070000000000256e+00 1.698830000000000062e+00 +1.818799999999999861e-01 5.866640000000000299e+00 1.712690000000000046e+00 +1.880999999999999894e-01 5.880740000000000300e+00 1.714749999999999996e+00 +1.942000000000000115e-01 5.856810000000000294e+00 1.711260000000000003e+00 +2.003099999999999881e-01 6.138060000000000294e+00 1.751870000000000038e+00 +2.064499999999999946e-01 6.406990000000000407e+00 1.789830000000000032e+00 +2.126300000000000134e-01 6.462060000000000137e+00 1.797509999999999941e+00 +2.187699999999999922e-01 6.501079999999999970e+00 1.802929999999999922e+00 +2.248999999999999888e-01 6.991179999999999950e+00 1.869650000000000034e+00 +2.310800000000000076e-01 7.014639999999999986e+00 1.872779999999999889e+00 +2.373000000000000109e-01 7.136879999999999669e+00 1.889029999999999987e+00 +2.434699999999999920e-01 7.547889999999999766e+00 1.942660000000000053e+00 +2.495900000000000063e-01 7.608620000000000161e+00 1.950460000000000083e+00 +2.557300000000000129e-01 7.913459999999999717e+00 1.989149999999999974e+00 +2.618900000000000117e-01 8.245919999999999916e+00 2.030510000000000037e+00 +2.680899999999999950e-01 8.604509999999999437e+00 2.074190000000000200e+00 +2.742499999999999938e-01 8.711750000000000327e+00 2.087070000000000203e+00 +2.803300000000000236e-01 8.863739999999999952e+00 2.105199999999999960e+00 +2.864999999999999769e-01 8.911099999999999355e+00 2.110819999999999919e+00 +2.927100000000000257e-01 9.338430000000000675e+00 2.160839999999999872e+00 +2.988600000000000145e-01 9.385640000000000427e+00 2.166290000000000049e+00 \ No newline at end of file diff --git a/test/mumag/Nanoperm_perpendicular_Honecker_et_al/6_1270_1640_24.331065.csv b/test/mumag/Nanoperm_perpendicular_Honecker_et_al/6_1270_1640_24.331065.csv new file mode 100644 index 00000000..72272ed8 --- /dev/null +++ b/test/mumag/Nanoperm_perpendicular_Honecker_et_al/6_1270_1640_24.331065.csv @@ -0,0 +1,60 @@ +2.732000000000000053e-02 1.517870979999999918e+03 2.754878000000000071e+01 +3.090999999999999998e-02 1.252144950000000108e+03 2.502143999999999835e+01 +3.452999999999999819e-02 9.849326300000000174e+02 2.219157999999999831e+01 +3.812999999999999723e-02 7.635778900000000249e+02 1.953941999999999979e+01 +4.173999999999999932e-02 5.910564900000000534e+02 1.719093000000000160e+01 +4.542999999999999816e-02 4.454160299999999779e+02 1.492340000000000089e+01 +4.644000000000000211e-02 4.097451399999999921e+02 1.431337000000000081e+01 +4.907999999999999863e-02 3.412573399999999992e+02 1.306249000000000038e+01 +5.254000000000000337e-02 2.833860500000000116e+02 1.190348999999999968e+01 +5.270999999999999991e-02 2.587516299999999774e+02 1.137434999999999974e+01 +5.630000000000000282e-02 1.972410900000000140e+02 9.930790000000000006e+00 +5.868999999999999911e-02 1.820288400000000024e+02 9.540150000000000574e+00 +5.988000000000000267e-02 1.548462399999999946e+02 8.799039999999999750e+00 +6.353999999999999926e-02 1.208091600000000057e+02 7.772039999999999615e+00 +6.481000000000000649e-02 1.228846300000000014e+02 7.838510000000000311e+00 +6.716999999999999360e-02 9.485747999999999536e+01 6.886849999999999916e+00 +7.072000000000000508e-02 7.616365000000000407e+01 6.171050000000000146e+00 +7.094999999999999918e-02 7.959243999999999630e+01 6.308419999999999916e+00 +7.434000000000000330e-02 6.139544999999999675e+01 5.540549999999999642e+00 +7.721999999999999698e-02 5.528307999999999822e+01 5.257520000000000415e+00 +7.799000000000000377e-02 5.105310999999999666e+01 5.052380000000000315e+00 +8.164000000000000423e-02 4.131436000000000064e+01 4.545020000000000060e+00 +8.343000000000000416e-02 3.883191999999999666e+01 4.406349999999999767e+00 +8.523999999999999633e-02 3.386838999999999800e+01 4.115120000000000111e+00 +8.881000000000000005e-02 2.873742000000000019e+01 3.790610000000000035e+00 +8.959999999999999909e-02 2.744166999999999845e+01 3.704159999999999897e+00 +9.569999999999999341e-02 2.114314999999999856e+01 3.251399999999999846e+00 +1.017799999999999955e-01 1.639198000000000022e+01 2.862859999999999960e+00 +1.079999999999999988e-01 1.302345000000000041e+01 2.551810000000000134e+00 +1.141699999999999937e-01 1.067233000000000054e+01 2.310010000000000119e+00 +1.202099999999999974e-01 8.927680000000000504e+00 2.112779999999999880e+00 +1.263600000000000001e-01 7.691300000000000026e+00 1.961030000000000051e+00 +1.325600000000000112e-01 6.633219999999999672e+00 1.821159999999999890e+00 +1.387799999999999867e-01 6.187759999999999927e+00 1.758939999999999948e+00 +1.449100000000000110e-01 5.397269999999999790e+00 1.642749999999999932e+00 +1.509500000000000008e-01 5.127299999999999969e+00 1.601140000000000008e+00 +1.571200000000000097e-01 4.947350000000000136e+00 1.572789999999999910e+00 +1.633400000000000130e-01 4.791970000000000063e+00 1.547900000000000054e+00 +1.695200000000000040e-01 4.839830000000000076e+00 1.555609999999999937e+00 +1.756399999999999906e-01 4.855599999999999916e+00 1.558140000000000081e+00 +1.818099999999999994e-01 4.971189999999999998e+00 1.576580000000000092e+00 +1.880199999999999927e-01 4.996179999999999843e+00 1.580540000000000056e+00 +1.941299999999999970e-01 5.231489999999999974e+00 1.617329999999999934e+00 +2.002299999999999913e-01 5.461660000000000181e+00 1.652519999999999989e+00 +2.063699999999999979e-01 5.566430000000000433e+00 1.668299999999999894e+00 +2.125400000000000067e-01 5.749649999999999928e+00 1.695529999999999982e+00 +2.186899999999999955e-01 5.962720000000000020e+00 1.726660000000000084e+00 +2.248100000000000098e-01 6.366220000000000212e+00 1.784129999999999994e+00 +2.309900000000000009e-01 6.576419999999999710e+00 1.813339999999999952e+00 +2.372100000000000042e-01 6.993730000000000224e+00 1.869990000000000041e+00 +2.433800000000000130e-01 7.112300000000000288e+00 1.885780000000000012e+00 +2.494999999999999996e-01 7.346860000000000390e+00 1.916619999999999990e+00 +2.556300000000000239e-01 7.629410000000000025e+00 1.953130000000000033e+00 +2.617900000000000227e-01 7.827810000000000379e+00 1.978359999999999896e+00 +2.679900000000000060e-01 8.252810000000000201e+00 2.031359999999999832e+00 +2.741399999999999948e-01 8.304840000000000444e+00 2.037749999999999950e+00 +2.802200000000000246e-01 8.705450000000000799e+00 2.086320000000000174e+00 +2.863899999999999779e-01 8.881330000000000169e+00 2.107289999999999885e+00 +2.926000000000000267e-01 9.112230000000000274e+00 2.134510000000000129e+00 +2.987500000000000155e-01 9.303969999999999629e+00 2.156849999999999934e+00 diff --git a/test/mumag/NdFeB_parallel_Bick_et_al/1_8000_1600_1070.csv b/test/mumag/NdFeB_parallel_Bick_et_al/1_8000_1600_1070.csv new file mode 100644 index 00000000..efaf03f7 --- /dev/null +++ b/test/mumag/NdFeB_parallel_Bick_et_al/1_8000_1600_1070.csv @@ -0,0 +1,53 @@ +0.02466 11161.7 105.066 +0.02779 8582.24 78.8702 +0.03118 6929.14 63.4309 +0.03472 5567.83 52.0489 +0.03798 4896.87 48.9428 +0.04103 4301.45 44.2537 +0.04439 3931.56 35.32 +0.04789 3532.24 33.8506 +0.05145 3225.93 29.1479 +0.05487 3001.52 30.0366 +0.05809 2808.67 26.8966 +0.06147 2656.59 24.6464 +0.06487 2544.49 24.3544 +0.06823 2417.04 22.1946 +0.07159 2285.04 21.753 +0.07491 2209.24 20.4624 +0.07817 2057.47 19.703 +0.08149 2022.7 18.6311 +0.08492 1895.55 17.4353 +0.08832 1842.28 17.2068 +0.09171 1764.56 16.4013 +0.09497 1702.47 16.2564 +0.09818 1650.68 15.7993 +0.10176 1598.61 13.5448 +0.10537 1517.07 14.6455 +0.10864 1470.19 13.6872 +0.11191 1422.64 13.7771 +0.11529 1373.82 12.5952 +0.11874 1333.67 12.468 +0.12204 1309.38 12.7116 +0.1253 1256.04 11.6755 +0.12868 1224.03 11.535 +0.13213 1180.94 10.9145 +0.13557 1163.16 10.9427 +0.13878 1135.89 11.2995 +0.14204 1129.91 10.3422 +0.14544 1101.7 10.3463 +0.14888 1050.46 9.68171 +0.1523 1034.3 9.79525 +0.15558 1022.51 9.86417 +0.15895 1003.15 9.06883 +0.16231 984.02 9.61305 +0.16567 956.076 8.69603 +0.16916 941.039 8.81872 +0.17256 914.22 8.5457 +0.1759 903.557 8.49062 +0.1792 862.822 8.49027 +0.18246 857.39 8.12443 +0.18596 822.372 7.58423 +0.18945 803.539 7.77639 +0.19275 787.933 7.6279 +0.19605 759.06 7.64242 +0.1993 746.444 7.32989 diff --git a/test/mumag/NdFeB_parallel_Bick_et_al/2_10000_1600_1070.csv b/test/mumag/NdFeB_parallel_Bick_et_al/2_10000_1600_1070.csv new file mode 100644 index 00000000..ede4f812 --- /dev/null +++ b/test/mumag/NdFeB_parallel_Bick_et_al/2_10000_1600_1070.csv @@ -0,0 +1,53 @@ +0.02466 9785.87 100.498 +0.02779 7387.61 74.8808 +0.03118 5638.9 58.6706 +0.03472 4473.77 47.7572 +0.03797 3723.19 43.7134 +0.04102 3256.4 39.3915 +0.04439 2850.7 30.7019 +0.04789 2561.59 29.3595 +0.05145 2280.01 24.9612 +0.05487 2158.85 25.8372 +0.05809 1997.24 23.0262 +0.06147 1891.61 21.0832 +0.06486 1796.03 20.6896 +0.06823 1698.02 18.8045 +0.07158 1613.84 18.4277 +0.0749 1560.07 17.3367 +0.07817 1481.58 16.8421 +0.08149 1400.5 15.6189 +0.08492 1362.71 14.8771 +0.08832 1326.21 14.6884 +0.09171 1308.1 14.1935 +0.09497 1238.48 13.9232 +0.09817 1217.12 13.6217 +0.10176 1182.01 11.7148 +0.10536 1125.29 12.6688 +0.10863 1086.39 11.8029 +0.1119 1069.27 11.9783 +0.11528 1037.53 10.9946 +0.11874 1000.25 10.8391 +0.12203 1030.21 11.3207 +0.12529 996.562 10.4091 +0.12867 937.635 10.1418 +0.13213 932.787 9.7458 +0.13557 936.093 9.83598 +0.13878 902.903 10.118 +0.14204 895.331 9.2379 +0.14544 894.523 9.3444 +0.14887 873.213 8.84172 +0.15229 870.028 8.99453 +0.15558 846.466 8.9992 +0.15895 842.358 8.32813 +0.1623 809.53 8.74144 +0.16566 822.783 8.08431 +0.16915 792.943 8.10552 +0.17255 774.308 7.87783 +0.1759 781.539 7.90816 +0.17919 756.562 7.97296 +0.18245 745.925 7.59066 +0.18596 725.982 7.13904 +0.18944 711.483 7.3287 +0.19275 709.157 7.24903 +0.19604 677.704 7.2411 +0.19929 659.648 6.90112 diff --git a/test/mumag/NdFeB_parallel_Bick_et_al/3_12000_1600_1070.csv b/test/mumag/NdFeB_parallel_Bick_et_al/3_12000_1600_1070.csv new file mode 100644 index 00000000..1c96f3d5 --- /dev/null +++ b/test/mumag/NdFeB_parallel_Bick_et_al/3_12000_1600_1070.csv @@ -0,0 +1,53 @@ +0.02467 9119.79 98.2483 +0.02779 6716.34 72.6007 +0.03118 4992.66 56.2161 +0.03472 3831.98 45.1388 +0.03798 3145.6 40.9924 +0.04103 2635.02 36.2785 +0.0444 2353.33 28.4064 +0.04789 2051.02 26.7601 +0.05146 1820.51 22.7202 +0.05487 1702.5 23.3101 +0.0581 1582.43 20.8224 +0.06148 1455.78 18.7977 +0.06487 1396.8 18.5029 +0.06824 1347.58 16.9495 +0.07159 1258.02 16.4371 +0.07491 1231.28 15.5554 +0.07818 1164.47 15.0776 +0.0815 1146.97 14.2378 +0.08493 1109.77 13.533 +0.08833 1040.08 13.1167 +0.09172 1030.03 12.6919 +0.09498 996.502 12.5769 +0.09819 973.153 12.2625 +0.10178 933.855 10.4847 +0.10538 941.324 11.6421 +0.10865 891.14 10.747 +0.11192 890.927 10.9919 +0.1153 863.999 10.0862 +0.11876 860.652 10.1051 +0.12205 833.905 10.2392 +0.12531 831.414 9.54046 +0.12869 796.354 9.38666 +0.13215 809.376 9.11842 +0.13559 805.133 9.14629 +0.1388 791.474 9.50691 +0.14205 787.287 8.69915 +0.14546 761.22 8.65866 +0.1489 779.201 8.38098 +0.15232 771.323 8.50152 +0.1556 748.08 8.49235 +0.15897 748.238 7.8713 +0.16233 756.864 8.48551 +0.16569 723.545 7.61209 +0.16918 723.039 7.76626 +0.17258 714.249 7.58349 +0.17592 707.068 7.55031 +0.17922 695.999 7.67253 +0.18248 687.854 7.31121 +0.18599 672.503 6.89206 +0.18947 656.046 7.05985 +0.19277 644.64 6.93548 +0.19607 633.62 7.0287 +0.19932 617.369 6.7009 diff --git a/test/mumag/NdFeB_parallel_Bick_et_al/4_14000_1600_1070.csv b/test/mumag/NdFeB_parallel_Bick_et_al/4_14000_1600_1070.csv new file mode 100644 index 00000000..e349acef --- /dev/null +++ b/test/mumag/NdFeB_parallel_Bick_et_al/4_14000_1600_1070.csv @@ -0,0 +1,53 @@ +0.02466 8578.27 96.3713 +0.02778 6201.5 70.6884 +0.03117 4666.55 54.9081 +0.03471 3532.71 43.8556 +0.03797 2779.72 39.1195 +0.04102 2401.14 35.0436 +0.04438 2010.93 26.6446 +0.04788 1789.13 25.3004 +0.05144 1629 21.7057 +0.05485 1414.39 21.5471 +0.05808 1338.47 19.3969 +0.06145 1255.55 17.6425 +0.06485 1194.78 17.2789 +0.06822 1131.88 15.6792 +0.07157 1093.29 15.4062 +0.07489 1076.75 14.6325 +0.07815 990.83 13.9891 +0.08147 993.984 13.3123 +0.0849 949.429 12.5828 +0.0883 906.082 12.3081 +0.09169 901.543 11.9196 +0.09495 883.023 11.8721 +0.09815 842.784 11.4424 +0.10174 819.526 9.8676 +0.10534 802.736 10.7893 +0.10861 795.767 10.1828 +0.11188 791.163 10.3837 +0.11526 783.07 9.63416 +0.11872 771.734 9.59199 +0.12201 755.014 9.75865 +0.12527 763.25 9.14929 +0.12865 738.983 9.05459 +0.1321 735.65 8.71334 +0.13554 713.743 8.62175 +0.13875 720.548 9.08471 +0.142 726.975 8.37203 +0.14541 705.893 8.34698 +0.14884 717.099 8.0504 +0.15226 709.825 8.16294 +0.15554 709.595 8.27884 +0.15891 699.293 7.61937 +0.16227 704.678 8.19256 +0.16563 698.926 7.48706 +0.16912 685.563 7.56396 +0.17251 673.043 7.36594 +0.17586 671.398 7.36238 +0.17915 664.082 7.50135 +0.18241 667.157 7.20373 +0.18592 646.099 6.75787 +0.1894 640.973 6.98357 +0.1927 634.489 6.88246 +0.196 608.639 6.89728 +0.19924 599.163 6.60299 diff --git a/test/mumag/NdFeB_parallel_Bick_et_al/5_16000_1600_1070.csv b/test/mumag/NdFeB_parallel_Bick_et_al/5_16000_1600_1070.csv new file mode 100644 index 00000000..03d157c5 --- /dev/null +++ b/test/mumag/NdFeB_parallel_Bick_et_al/5_16000_1600_1070.csv @@ -0,0 +1,53 @@ +0.02466 8403.24 95.8365 +0.02779 5866.26 69.5434 +0.03118 4356.78 53.6923 +0.03472 3299.25 42.8649 +0.03798 2597.38 38.2386 +0.04103 2129.79 33.557 +0.04439 1878.69 25.9888 +0.04789 1624.61 24.3861 +0.05145 1439.52 20.6737 +0.05487 1263.65 20.5759 +0.05809 1171.99 18.386 +0.06147 1079.56 16.577 +0.06487 1041.15 16.3129 +0.06823 1001.7 14.8847 +0.07159 961.645 14.563 +0.07491 923.442 13.6706 +0.07817 896.915 13.3973 +0.08149 883.652 12.6286 +0.08492 862.753 12.0581 +0.08832 824.722 11.795 +0.09171 784.35 11.1756 +0.09497 769.294 11.1579 +0.09818 755.349 10.8901 +0.10176 749.016 9.48228 +0.10537 742.393 10.4072 +0.10864 731.812 9.80011 +0.11191 725.902 9.97489 +0.11529 687.755 9.06119 +0.11874 695.826 9.13917 +0.12204 706.826 9.47552 +0.1253 686.062 8.69528 +0.12868 672.907 8.67583 +0.13213 693.14 8.48682 +0.13557 670.728 8.38538 +0.13878 665.705 8.76139 +0.14204 671.833 8.08011 +0.14544 675.608 8.18129 +0.14888 672.094 7.81614 +0.1523 674.683 7.97469 +0.15558 656.506 7.99112 +0.15895 676.236 7.50792 +0.16231 665.98 7.99164 +0.16567 649.237 7.2359 +0.16916 663.887 7.46039 +0.17256 654.732 7.28088 +0.1759 649.138 7.26044 +0.1792 637.918 7.36805 +0.18246 634.954 7.04588 +0.18596 615.952 6.61209 +0.18945 606.075 6.80618 +0.19275 602.38 6.72418 +0.19605 583.877 6.77081 +0.1993 592.832 6.58159 diff --git a/test/quantities/utest_math_operations.py b/test/quantities/utest_math_operations.py new file mode 100644 index 00000000..5bda5a2c --- /dev/null +++ b/test/quantities/utest_math_operations.py @@ -0,0 +1,152 @@ +""" Tests for math operations """ + +import pytest + +import numpy as np +from sasdata.quantities.quantity import NamedQuantity, tensordot, transpose +from sasdata.quantities import units + +order_list = [ + [0, 1, 2, 3], + [0, 2, 1], + [1, 0], + [0, 1], + [2, 0, 1], + [3, 1, 2, 0] +] + +@pytest.mark.parametrize("order", order_list) +def test_transpose_raw(order: list[int]): + """ Check that the transpose operation changes the order of indices correctly - uses sizes as way of tracking""" + + input_shape = tuple([i+1 for i in range(len(order))]) + expected_shape = tuple([i+1 for i in order]) + + input_mat = np.zeros(input_shape) + + measured_mat = transpose(input_mat, axes=tuple(order)) + + assert measured_mat.shape == expected_shape + + +@pytest.mark.parametrize("order", order_list) +def test_transpose_raw(order: list[int]): + """ Check that the transpose operation changes the order of indices correctly - uses sizes as way of tracking""" + input_shape = tuple([i + 1 for i in range(len(order))]) + expected_shape = tuple([i + 1 for i in order]) + + input_mat = NamedQuantity("testmat", np.zeros(input_shape), units=units.none) + + measured_mat = transpose(input_mat, axes=tuple(order)) + + assert measured_mat.value.shape == expected_shape + + +rng_seed = 1979 +tensor_product_with_identity_sizes = (4,6,5) + +@pytest.mark.parametrize("index, size", [tup for tup in enumerate(tensor_product_with_identity_sizes)]) +def test_tensor_product_with_identity_quantities(index, size): + """ Check the correctness of the tensor product by multiplying by the identity (quantity, quantity)""" + np.random.seed(rng_seed) + + x = NamedQuantity("x", np.random.rand(*tensor_product_with_identity_sizes), units=units.meters) + y = NamedQuantity("y", np.eye(size), units.seconds) + + z = tensordot(x, y, index, 0) + + # Check units + assert z.units == units.meters * units.seconds + + # Expected sizes - last index gets moved to end + output_order = [i for i in (0, 1, 2) if i != index] + [index] + output_sizes = [tensor_product_with_identity_sizes[i] for i in output_order] + + assert z.value.shape == tuple(output_sizes) + + # Restore original order and check + reverse_order = [-1, -1, -1] + for to_index, from_index in enumerate(output_order): + reverse_order[from_index] = to_index + + z_reordered = transpose(z, axes = tuple(reverse_order)) + + assert z_reordered.value.shape == tensor_product_with_identity_sizes + + # Check values + + mat_in = x.in_si() + mat_out = transpose(z, axes=tuple(reverse_order)).in_si() + + assert np.all(np.abs(mat_in - mat_out) < 1e-10) + + +@pytest.mark.parametrize("index, size", [tup for tup in enumerate(tensor_product_with_identity_sizes)]) +def test_tensor_product_with_identity_quantity_matrix(index, size): + """ Check the correctness of the tensor product by multiplying by the identity (quantity, matrix)""" + np.random.seed(rng_seed) + + x = NamedQuantity("x", np.random.rand(*tensor_product_with_identity_sizes), units.meters) + y = np.eye(size) + + z = tensordot(x, y, index, 0) + + assert z.units == units.meters + + # Expected sizes - last index gets moved to end + output_order = [i for i in (0, 1, 2) if i != index] + [index] + output_sizes = [tensor_product_with_identity_sizes[i] for i in output_order] + + assert z.value.shape == tuple(output_sizes) + + # Restore original order and check + reverse_order = [-1, -1, -1] + for to_index, from_index in enumerate(output_order): + reverse_order[from_index] = to_index + + z_reordered = transpose(z, axes = tuple(reverse_order)) + + assert z_reordered.value.shape == tensor_product_with_identity_sizes + + # Check values + + mat_in = x.in_si() + mat_out = transpose(z, axes=tuple(reverse_order)).in_si() + + assert np.all(np.abs(mat_in - mat_out) < 1e-10) + + +@pytest.mark.parametrize("index, size", [tup for tup in enumerate(tensor_product_with_identity_sizes)]) +def test_tensor_product_with_identity_matrix_quantity(index, size): + """ Check the correctness of the tensor product by multiplying by the identity (matrix, quantity)""" + np.random.seed(rng_seed) + + x = np.random.rand(*tensor_product_with_identity_sizes) + y = NamedQuantity("y", np.eye(size), units.seconds) + + z = tensordot(x, y, index, 0) + + assert z.units == units.seconds + + + # Expected sizes - last index gets moved to end + output_order = [i for i in (0, 1, 2) if i != index] + [index] + output_sizes = [tensor_product_with_identity_sizes[i] for i in output_order] + + assert z.value.shape == tuple(output_sizes) + + # Restore original order and check + reverse_order = [-1, -1, -1] + for to_index, from_index in enumerate(output_order): + reverse_order[from_index] = to_index + + z_reordered = transpose(z, axes = tuple(reverse_order)) + + assert z_reordered.value.shape == tensor_product_with_identity_sizes + + # Check values + + mat_in = x + mat_out = transpose(z, axes=tuple(reverse_order)).in_si() + + assert np.all(np.abs(mat_in - mat_out) < 1e-10) diff --git a/test/quantities/utest_operations.py b/test/quantities/utest_operations.py new file mode 100644 index 00000000..6767e32a --- /dev/null +++ b/test/quantities/utest_operations.py @@ -0,0 +1,68 @@ +import pytest + +from sasdata.quantities.quantity import Operation, \ + Neg, Inv, \ + Add, Sub, Mul, Div, Pow, \ + Variable, Constant, AdditiveIdentity, MultiplicativeIdentity + +operation_with_everything = \ + Div( + Pow( + Mul( + Sub( + Add( + Neg(Inv(MultiplicativeIdentity())), + Variable("x")), + Constant(7)), + AdditiveIdentity()), + 2), + Variable("y")) + +def test_serialise_deserialise(): + print(operation_with_everything._serialise_json()) + + serialised = operation_with_everything.serialise() + deserialised = Operation.deserialise(serialised) + reserialised = deserialised.serialise() + + assert serialised == reserialised + + +@pytest.mark.parametrize("op, a, b, result", [ + (Add, 1, 1, 2), + (Add, 7, 8, 15), + (Sub, 1, 1, 0), + (Sub, 7, 8, -1), + (Mul, 1, 1, 1), + (Mul, 7, 8, 56), + (Div, 1, 1, 1), + (Div, 7, 8, 7/8), + (Pow, 1, 1, 1), + (Pow, 7, 2, 49)]) +def test_binary_evaluation(op, a, b, result): + f = op(Constant(a), b if op == Pow else Constant(b)) + assert f.evaluate({}) == result + +x = Variable("x") +y = Variable("y") +z = Variable("z") +@pytest.mark.parametrize("x_over_x", [ + Div(x,x), + Mul(Inv(x), x), + Mul(x, Inv(x)), +]) +def test_dx_over_x_by_dx_should_be_zero(x_over_x): + + + dfdx = x_over_x.derivative(x) + + print(dfdx.summary()) + + assert dfdx == AdditiveIdentity() + + +def test_d_xyz_by_components_should_be_1(): + f = Mul(Mul(x, y), z) + assert f.derivative(x).derivative(y).derivative(z) == MultiplicativeIdentity() + + diff --git a/test/quantities/utest_quantities.py b/test/quantities/utest_quantities.py new file mode 100644 index 00000000..86f05934 --- /dev/null +++ b/test/quantities/utest_quantities.py @@ -0,0 +1,131 @@ +import numpy as np + +from sasdata.quantities.quantity import Quantity, UnitError +import sasdata.quantities.units as units +import sasdata.quantities.si as si +import pytest +def test_in_units_of_calculation(): + """ Just a couple of unit conversions """ + assert Quantity(1, units.meters).in_units_of(units.kilometers) == 1e-3 + assert Quantity(10, units.minutes).in_units_of(units.seconds) == 600 + assert Quantity(7, units.kilonewtons).in_units_of(units.kg_force) == pytest.approx(7000 / 9.81, abs=1) + assert Quantity(0, units.meters).in_units_of(units.exameters) == 0 + + +def test_unit_compounding_pow(): + """ Test units compound correctly when __pow__ is used""" + assert (Quantity(1, units.millimeters) ** 2).in_units_of(units.square_meters) == 1e-6 + assert (Quantity(1, units.minutes) ** 3).in_units_of(units.seconds ** 3) == 60 ** 3 + +def test_pow_scaling(): + q2 = Quantity(1000, units.millimeters)**2 + assert q2.units.scale == 1e-6 + assert q2.value == 1e6 + + +def test_unit_compounding_mul(): + """ Test units compound correctly when __mul__ is used""" + assert (Quantity(4, units.minutes) * Quantity(0.25, units.hertz)).in_units_of(units.none) == 60 + assert (Quantity(250, units.volts) * Quantity(8, units.amperes)).in_units_of(units.kilowatts) == 2 + +def test_unit_compounding_div(): + """ Test units compound correctly when __truediv__ is used""" + assert (Quantity(10, units.kilometers) / Quantity(2, units.minutes) + ).in_units_of(units.meters_per_second) == pytest.approx(250/3, abs=1e-6) + + assert (Quantity(1, units.nanowebers) / (Quantity(1, units.millimeters) ** 2)).in_units_of(units.millitesla) == 1 + +def test_value_mul(): + """ Test value part of quantities multiply correctly""" + assert (Quantity(1j, units.seconds) * Quantity(1j, units.watts)).in_units_of(units.joules) == -1 + +def test_scalar_mul(): + assert (Quantity(1, units.seconds) * 10).in_units_of(units.seconds) == 10 + assert (10 * Quantity(1, units.seconds)).in_units_of(units.seconds) == 10 + assert (1000 * Quantity(1, units.milliseconds)).in_units_of(units.seconds) == 1 + +def test_scalar_div(): + + assert (Quantity(1, units.seconds) / 10).in_units_of(units.seconds) == 0.1 + assert (10 / Quantity(1, units.seconds)).in_units_of(units.hertz) == 10 + assert (0.001 / Quantity(1, units.milliseconds)).in_units_of(units.hertz) == 1 + +def test_good_add_sub(): + """ Test that adding and subtracting units works """ + assert (Quantity(1, units.seconds) + Quantity(1, units.milliseconds)).in_units_of(units.seconds) == 1.001 + assert (Quantity(1, units.seconds) - Quantity(1, units.milliseconds)).in_units_of(units.seconds) == 0.999 + + assert (Quantity(1, units.inches) + Quantity(1, units.feet)).in_units_of(units.inches) == pytest.approx(13, abs=1e-8) + +@pytest.mark.parametrize("unit_in, power, unit_out", [ + (units.meters**2, 1/2, units.meters), + (units.meters**3, 1/3, units.meters), + (units.meters**3, 2/3, units.meters**2), + (units.meters**3, -5/3, units.meters**-5), + (units.none, 1/10, units.none), + (units.none, 19/17, units.none), + (units.none, np.pi, units.none) +]) +def test_good_non_integer_unit_powers(unit_in, power, unit_out): + """ Check that we can do various square and cube root stuff if we need to, + If dimensionless, we should be able to do arbitrary powers + """ + assert unit_in**power == unit_out + +@pytest.mark.parametrize("unit, power", [ + (units.meters, 1/2), + (units.milliohms, 1/3), + (units.meters, 3/2), + (units.meters**2, 2/3) +]) +def test_bad_non_integer_unit_powers(unit, power): + """ Check that we get an error if we try and do something silly with powers""" + with pytest.raises(units.DimensionError): + x = unit**power + + +@pytest.mark.parametrize("unit_1", si.all_si) +@pytest.mark.parametrize("unit_2", si.all_si) +def test_mixed_quantity_add_sub(unit_1, unit_2): + if unit_1.equivalent(unit_2): + assert (Quantity(0, unit_1) + Quantity(0, unit_2)).in_units_of(unit_1) == 0 + + else: + with pytest.raises(UnitError): + Quantity(1, unit_1) + Quantity(1, unit_2) + +def assert_unit_ratio(u1: units.Unit, u2: units.Unit, value: float, abs=1e-9): + """ Helper function for testing units that are multiples of each other """ + + assert u1.equivalent(u2), "Units should be compatible for this test" + assert (Quantity(1, u1) / Quantity(1, u2)).in_units_of(units.none) == pytest.approx(value, abs=abs) + + +def test_american_units(): + assert_unit_ratio(units.feet, units.inches, 12) + assert_unit_ratio(units.yards, units.inches, 36) + assert_unit_ratio(units.miles, units.inches, 63360) + assert_unit_ratio(units.pounds_force_per_square_inch, units.pounds_force / (units.inches**2), 1, abs=1e-5) + +def test_percent(): + assert Quantity(5, units.percent).in_units_of(units.none) == pytest.approx(0.05, 1e-10) + +@pytest.mark.parametrize("unit_1", si.all_si) +@pytest.mark.parametrize("unit_2", si.all_si) +def test_conversion_errors(unit_1, unit_2): + """ Test conversion errors are thrown when units are not compatible """ + + if unit_1 == unit_2: + assert Quantity(1, unit_1).in_units_of(unit_2) == 1 + + else: + with pytest.raises(UnitError): + Quantity(1, units.seconds).in_units_of(units.meters) + + +@pytest.mark.quantity +def test_equality(): + assert Quantity(1.0, units.angstroms) == Quantity(1.0, units.angstroms) + assert Quantity(1.0, units.angstroms) == Quantity(0.1, units.nanometers) + assert Quantity(1.0, units.angstroms) != Quantity(0.1, units.angstroms) + assert Quantity(1.0, units.angstroms) == Quantity(1.0e-10, units.meters) diff --git a/test/quantities/utest_quantity_error.py b/test/quantities/utest_quantity_error.py new file mode 100644 index 00000000..e8e9378f --- /dev/null +++ b/test/quantities/utest_quantity_error.py @@ -0,0 +1,154 @@ +from sasdata.quantities import units +from sasdata.quantities.quantity import NamedQuantity +import pytest +import numpy as np + +@pytest.mark.parametrize("x_err, y_err, x_units, y_units", + [(1, 1, units.meters, units.meters), + (1, 1, units.centimeters, units.centimeters), + (1, 2, units.meters, units.millimeters)]) +def test_addition_propagation(x_err, y_err, x_units, y_units): + """ Test that errors in addition of independent variables works with different units in the mix""" + + expected_err = np.sqrt((x_err*x_units.scale)**2 + (y_err*y_units.scale)**2) + + x = NamedQuantity("x", 0, x_units, standard_error=x_err) + y = NamedQuantity("y", 0, y_units, standard_error=y_err) + + _, err = (x + y).in_si_with_standard_error() + + assert err == pytest.approx(expected_err, abs=1e-8) + +@pytest.mark.parametrize("x_val, y_val, x_units, y_units", + [(1, 1, units.meters, units.meters), + (1, 1, units.centimeters, units.centimeters), + (2, 2, units.meters, units.meters), + (1, 2, units.centimeters, units.centimeters), + (1, 2, units.meters, units.millimeters), + (3, 4, units.milliseconds, units.microseconds), + (0, 1, units.meters, units.meters)]) +def test_asymmetry_propagation(x_val, y_val, x_units, y_units): + + x = NamedQuantity("x", x_val, x_units, standard_error=np.sqrt(x_val)) + y = NamedQuantity("y", y_val, y_units, standard_error=np.sqrt(y_val)) + + x_si, x_err = x.in_si_with_standard_error() + y_si, y_err = y.in_si_with_standard_error() + + numerator = x-y + denominator = x+y + a = numerator/denominator + + # Check numerator and denominator + expected_error = np.sqrt(x_err ** 2 + y_err ** 2) + + value, error = numerator.in_si_with_standard_error() + assert error == pytest.approx(expected_error, rel=1e-6) + + value, error = denominator.in_si_with_standard_error() + assert error == pytest.approx(expected_error, rel=1e-6) + + # check whole thing + value, error = a.in_si_with_standard_error() + expected_error = (2 / (x_si + y_si)**2) * np.sqrt((x_err*y_si)**2 + (y_err*x_si)**2) + assert error == pytest.approx(expected_error, rel=1e-6) + +@pytest.mark.parametrize("x_val, y_val, x_units, y_units", + [(1, 1, units.meters, units.meters), + (1, 1, units.centimeters, units.centimeters), + (2, 2, units.meters, units.meters), + (1, 2, units.centimeters, units.centimeters), + (1, 2, units.meters, units.millimeters), + (3, 4, units.milliseconds, units.microseconds), + (0, 1, units.meters, units.meters)]) +def test_power_propagation(x_val, y_val, x_units, y_units): + + x = NamedQuantity("x", x_val, x_units, standard_error=np.sqrt(x_val)) + y = NamedQuantity("y", y_val, y_units, standard_error=np.sqrt(y_val)) + + x_si, x_err = x.in_si_with_standard_error() + y_si, y_err = y.in_si_with_standard_error() + + x_var = x_err ** 2 + y_var = y_err ** 2 + + z = (x*y)**3 + + # check whole thing + value, error = z.in_si_with_standard_error() + expected_variance = 9*((x_si*y_si)**4)*(x_var*y_si*y_si + x_si*x_si*y_var) + assert error == pytest.approx(np.sqrt(expected_variance), rel=1e-6) + +@pytest.mark.parametrize("k", [0.1, 0.5, 1, 2, 10]) +@pytest.mark.parametrize("x_val, y_val, x_units, y_units", + [(1, 1, units.meters, units.meters), + (1, 1, units.centimeters, units.centimeters), + (2, 2, units.meters, units.meters), + (1, 2, units.centimeters, units.centimeters), + (1, 2, units.meters, units.millimeters), + (3, 4, units.milliseconds, units.microseconds), + (0, 1, units.meters, units.meters), + (0, 0, units.meters, units.meters)]) +def test_complex_power_propagation(x_val, y_val, x_units, y_units, k): + + x = NamedQuantity("x", x_val, x_units, standard_error=np.sqrt(k*x_val)) + y = NamedQuantity("y", y_val, y_units, standard_error=np.sqrt(k*y_val)) + + x_si, x_err = x.in_si_with_standard_error() + y_si, y_err = y.in_si_with_standard_error() + + x_var = x_err ** 2 + y_var = y_err ** 2 + + z = (x+y)**3 + x**3 + y**3 + + value, error = z.in_si_with_standard_error() + expected_variance = \ + 9*x_var*(x_si**2 + (x_si+y_si)**2)**2 + \ + 9*y_var*(y_si**2 + (x_si+y_si)**2)**2 + + assert error == pytest.approx(np.sqrt(expected_variance), rel=1e-6) + +@pytest.mark.parametrize("k_x", [0.1, 0.5, 1, 2, 10]) +@pytest.mark.parametrize("k_y", [0.1, 0.5, 1, 2, 10]) +@pytest.mark.parametrize("x_val, y_val, x_units, y_units", + [(1, 1, units.meters, units.meters), + (1, 1, units.centimeters, units.centimeters), + (2, 2, units.meters, units.meters), + (1, 2, units.centimeters, units.centimeters), + (1, 2, units.meters, units.millimeters), + (3, 4, units.milliseconds, units.microseconds), + (0, 1, units.meters, units.meters), + (0, 0, units.meters, units.meters)]) +def test_complex_propagation(x_val, y_val, x_units, y_units, k_x, k_y): + + x = NamedQuantity("x", x_val, x_units, standard_error=np.sqrt(k_x*x_val)) + y = NamedQuantity("y", y_val, y_units, standard_error=np.sqrt(k_y*y_val)) + + cx = NamedQuantity("cx", 1.7, x_units) + cy = NamedQuantity("cy", 1.2, y_units) + c0 = 4*NamedQuantity("c0", value=7, units=units.none) + + cx_si = cx.in_si() + cy_si = cy.in_si() + + c0_si = c0.in_si() + + x_si, x_err = x.in_si_with_standard_error() + y_si, y_err = y.in_si_with_standard_error() + + x_var = x_err ** 2 + y_var = y_err ** 2 + + z = (((x-cx)**4 + (y-cy)**4)**(1/4)) + c0*(-x-y) + + value, error = z.in_si_with_standard_error() + + denom_factor = ((x_si - cx_si)**4 + (y_si - cy_si)**4)**(-3/4) + x_num = (cx_si - x_si)**3 + y_num = (cy_si - y_si)**3 + + expected_variance = x_var*(c0_si + x_num*denom_factor)**2 + y_var*(c0_si + y_num*denom_factor)**2 + + assert error == pytest.approx(np.sqrt(expected_variance), rel=1e-8) + diff --git a/test/quantities/utest_units.py b/test/quantities/utest_units.py new file mode 100644 index 00000000..9fea2a6b --- /dev/null +++ b/test/quantities/utest_units.py @@ -0,0 +1,46 @@ +import sasdata.quantities.units as units +from sasdata.quantities.units import Unit + +class EqualUnits: + def __init__(self, test_name: str, *units): + self.test_name = "Equality: " + test_name + self.units: list[Unit] = list(units) + + def run_test(self): + for i, unit_1 in enumerate(self.units): + for unit_2 in self.units[i+1:]: + assert unit_1.equivalent(unit_2), "Units should be equivalent" + assert unit_1 == unit_2, "Units should be equal" + + +class EquivalentButUnequalUnits: + def __init__(self, test_name: str, *units): + self.test_name = "Equivalence: " + test_name + self.units: list[Unit] = list(units) + + def run_test(self): + for i, unit_1 in enumerate(self.units): + for unit_2 in self.units[i+1:]: + assert unit_1.equivalent(unit_2), "Units should be equivalent" + assert unit_1 != unit_2, "Units should not be equal" + + +tests = [ + + EqualUnits("Pressure", + units.pascals, + units.newtons / units.meters ** 2, + units.micronewtons * units.millimeters ** -2), + + EqualUnits("Resistance", + units.ohms, + units.volts / units.amperes, + 1e-3/units.millisiemens) + + +] + + +for test in tests: + print(test.test_name) + test.run_test() diff --git a/test/sasdataloader/reference/14250.txt b/test/sasdataloader/reference/14250.txt new file mode 100644 index 00000000..6f11aba7 --- /dev/null +++ b/test/sasdataloader/reference/14250.txt @@ -0,0 +1,51 @@ +sasentry01 + [FILE_ID_HERE/sasentry01/sasdata/I] [[0.0 ... 0.0]] ± [[0.0 ... 0.0]] cm⁻¹ + [FILE_ID_HERE/sasentry01/sasdata/Qx] [[-0.11925 ... 0.11925000000000001]] Å⁻¹ + [FILE_ID_HERE/sasentry01/sasdata/Qy] [[-0.11925 ... 0.11925000000000001]] Å⁻¹ +Metadata: + + High C high V 63, 900oC, 10h 1.65T_SANS, Run: 14250 + =================================================== + +Definition: High C high V 63, 900oC, 10h 1.65T_SANS +Process: + Name: Mantid_generated_NXcanSAS + Date: 2016-12-06T17:15:48 + Description: None + Term: None + Notes: None +Sample: + ID: None + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Aperture: + Name: None + Aperture size: None + Aperture distance: None +Collimation: + Length: None +Detector: + Name: None + Distance: None + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: Spallation Neutron Source + Shape: None + Wavelength: None + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: None + Beam Size: None +Transmission Spectrum: + Name: None + Timestamp: None + Wavelengths: None + Transmission: None + diff --git a/test/sasdataloader/reference/33837.txt b/test/sasdataloader/reference/33837.txt new file mode 100644 index 00000000..26f36b96 --- /dev/null +++ b/test/sasdataloader/reference/33837.txt @@ -0,0 +1,50 @@ +sasentry01 + [FILE_ID_HERE/sasentry01/sasdata/I] [5.416094671273121 ... 0.33697913143947616] ± [0.6152247543248875 ... 0.19365125082205084] m + [FILE_ID_HERE/sasentry01/sasdata/Q] [0.0041600000000000005 ... 0.6189241619415587] m +Metadata: + + MH4_5deg_16T_SLOW, Run: 33837 + ============================= + +Definition: MH4_5deg_16T_SLOW +Process: + Name: Mantid_generated_NXcanSAS + Date: 11-May-2016 12:20:43 + Description: None + Term: None + Notes: None +Sample: + ID: None + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Aperture: + Name: None + Aperture size: None + Aperture distance: None +Collimation: + Length: None +Detector: + Name: None + Distance: None + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: Spallation Neutron Source + Shape: None + Wavelength: None + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: None + Beam Size: None +Transmission Spectrum: + Name: None + Timestamp: None + Wavelengths: None + Transmission: None + diff --git a/test/sasdataloader/reference/33837_v3.txt b/test/sasdataloader/reference/33837_v3.txt new file mode 100644 index 00000000..f4d205b2 --- /dev/null +++ b/test/sasdataloader/reference/33837_v3.txt @@ -0,0 +1,50 @@ +sasentry01 + [FILE_ID_HERE/sasentry01/sasdata/I] [5.416094671273121 ... 0.33697913143947616] ± [0.6152247543248875 ... 0.19365125082205084] none + [FILE_ID_HERE/sasentry01/sasdata/Q] [0.0041600000000000005 ... 0.6189241619415587] Å⁻¹ +Metadata: + + MH4_5deg_16T_SLOW, Run: 33837 + ============================= + +Definition: MH4_5deg_16T_SLOW +Process: + Name: Mantid_generated_NXcanSAS + Date: 2016-07-04T10:34:34 + Description: None + Term: None + Notes: None +Sample: + ID: None + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Aperture: + Name: None + Aperture size: None + Aperture distance: None +Collimation: + Length: None +Detector: + Name: None + Distance: None + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: Spallation Neutron Source + Shape: None + Wavelength: None + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: None + Beam Size: None +Transmission Spectrum: + Name: None + Timestamp: None + Wavelengths: None + Transmission: None + diff --git a/test/sasdataloader/reference/BAM.txt b/test/sasdataloader/reference/BAM.txt new file mode 100644 index 00000000..bf6328b1 --- /dev/null +++ b/test/sasdataloader/reference/BAM.txt @@ -0,0 +1,51 @@ +sasentry01 + [FILE_ID_HERE/sasentry01/data/I] [[-4132.585671758142 ... -4139.954861346877]] ± [[1000000000.0 ... 1000000000.0]] m⁻¹ + [FILE_ID_HERE/sasentry01/data/Imask] [[True ... True]] m⁻¹ + [FILE_ID_HERE/sasentry01/data/Q] [[[-0.10733919639695527 ... 0.0]]] Å⁻¹ +Metadata: + + Qais oriented iron test 1, Run: 12345 + ===================================== + +Definition: Qais oriented iron test 1 +Process: + Name: None + Date: None + Description: None + Term: None + Notes: None +Sample: + ID: None + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Aperture: + Name: None + Aperture size: None + Aperture distance: None +Collimation: + Length: None +Detector: + Name: None + Distance: None + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: None + Shape: None + Wavelength: None + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: None + Beam Size: None +Transmission Spectrum: + Name: None + Timestamp: None + Wavelengths: None + Transmission: None + diff --git a/test/sasdataloader/reference/ISIS_1_0.txt b/test/sasdataloader/reference/ISIS_1_0.txt new file mode 100644 index 00000000..57ff7ed2 --- /dev/null +++ b/test/sasdataloader/reference/ISIS_1_0.txt @@ -0,0 +1,49 @@ +79680main_1D_2.2_10.0 + Q + I +Metadata: + + TK49 c10_SANS, Run: 79680 + ========================= + +Definition: TK49 c10_SANS +Process: + Name: Mantid generated CanSAS1D XML + Date: 02-Aug-2013 16:54:14 + Description: None + Terms: + svn: 2.5.3 + user_file: K:/masks/MASKLOQ_MAN_132E_Lu_Banjo_12mm.txt +Sample: + ID: TK49 c10_SANS + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: HAB + Distance: 0.575 m + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Detector: + Name: main-detector-bank + Distance: 4.14502 m + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: neutron + Shape: None + Wavelength: None + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: None + Beam Size: None diff --git a/test/sasdataloader/reference/ISIS_1_1.txt b/test/sasdataloader/reference/ISIS_1_1.txt new file mode 100644 index 00000000..48df868f --- /dev/null +++ b/test/sasdataloader/reference/ISIS_1_1.txt @@ -0,0 +1,49 @@ +79680main_1D_2.2_10.0 + Q + I +Metadata: + + TK49 c10_SANS, Run: 79680 + ========================= + +Definition: TK49 c10_SANS +Process: + Name: Mantid generated CanSAS1D XML + Date: 02-Aug-2013 16:53:56 + Description: None + Terms: + svn: 2.5.3 + user_file: K:/masks/MASKLOQ_MAN_132E_Lu_Banjo_12mm.txt +Sample: + ID: TK49 c10_SANS + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: HAB + Distance: 0.575 m + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Detector: + Name: main-detector-bank + Distance: 4.14502 m + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: neutron + Shape: None + Wavelength: None + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: None + Beam Size: None diff --git a/test/sasdataloader/reference/ISIS_1_1_doubletrans.txt b/test/sasdataloader/reference/ISIS_1_1_doubletrans.txt new file mode 100644 index 00000000..48df868f --- /dev/null +++ b/test/sasdataloader/reference/ISIS_1_1_doubletrans.txt @@ -0,0 +1,49 @@ +79680main_1D_2.2_10.0 + Q + I +Metadata: + + TK49 c10_SANS, Run: 79680 + ========================= + +Definition: TK49 c10_SANS +Process: + Name: Mantid generated CanSAS1D XML + Date: 02-Aug-2013 16:53:56 + Description: None + Terms: + svn: 2.5.3 + user_file: K:/masks/MASKLOQ_MAN_132E_Lu_Banjo_12mm.txt +Sample: + ID: TK49 c10_SANS + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: HAB + Distance: 0.575 m + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Detector: + Name: main-detector-bank + Distance: 4.14502 m + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: neutron + Shape: None + Wavelength: None + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: None + Beam Size: None diff --git a/test/sasdataloader/reference/ISIS_1_1_notrans.txt b/test/sasdataloader/reference/ISIS_1_1_notrans.txt new file mode 100644 index 00000000..48df868f --- /dev/null +++ b/test/sasdataloader/reference/ISIS_1_1_notrans.txt @@ -0,0 +1,49 @@ +79680main_1D_2.2_10.0 + Q + I +Metadata: + + TK49 c10_SANS, Run: 79680 + ========================= + +Definition: TK49 c10_SANS +Process: + Name: Mantid generated CanSAS1D XML + Date: 02-Aug-2013 16:53:56 + Description: None + Terms: + svn: 2.5.3 + user_file: K:/masks/MASKLOQ_MAN_132E_Lu_Banjo_12mm.txt +Sample: + ID: TK49 c10_SANS + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: HAB + Distance: 0.575 m + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Detector: + Name: main-detector-bank + Distance: 4.14502 m + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: neutron + Shape: None + Wavelength: None + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: None + Beam Size: None diff --git a/test/sasdataloader/reference/MAR07232_rest.txt b/test/sasdataloader/reference/MAR07232_rest.txt new file mode 100644 index 00000000..7535683a --- /dev/null +++ b/test/sasdataloader/reference/MAR07232_rest.txt @@ -0,0 +1,32 @@ +sasentry01 +Metadata: + + MAR07232_rest_out.dat, Run: 2 + ============================= + +Definition: MAR07232_rest_out.dat +Sample: + ID: + Transmission: 0.84357 + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: + Distance: None + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: neutron + Shape: None + Wavelength: None + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: None + Beam Size: None diff --git a/test/sasdataloader/reference/TestExtensions.txt b/test/sasdataloader/reference/TestExtensions.txt new file mode 100644 index 00000000..e833c9da --- /dev/null +++ b/test/sasdataloader/reference/TestExtensions.txt @@ -0,0 +1,49 @@ +TK49 c10_SANS + Q + I +Metadata: + + TK49 c10_SANS, Run: 79680 + ========================= + +Definition: TK49 c10_SANS +Process: + Name: Mantid generated CanSAS1D XML + Date: 02-Aug-2013 16:53:56 + Description: + Terms: + svn: 2.5.3 + user_file: K:/masks/MASKLOQ_MAN_132E_Lu_Banjo_12mm.txt +Sample: + ID: TK49 c10_SANS + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: HAB + Distance: 575.0 mm + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Detector: + Name: main-detector-bank + Distance: 4145.02 mm + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: neutron + Shape: None + Wavelength: None + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: None + Beam Size: None diff --git a/test/sasdataloader/reference/cansas1d.txt b/test/sasdataloader/reference/cansas1d.txt new file mode 100644 index 00000000..e16366bb --- /dev/null +++ b/test/sasdataloader/reference/cansas1d.txt @@ -0,0 +1,74 @@ +Test title + Q + I +Metadata: + + Test title, Run: 1234 + ===================== + +Definition: Test title +Process: + Name: spol + Date: 04-Sep-2007 18:35:02 + Description: None + Terms: + radialstep: 10.0 mm + sector_width: 180.0 deg + sector_orient: 0.0 deg + MASK_file: USER:MASK.COM + Notes: + AvA1 0.0000E+00 AsA2 1.0000E+00 XvA3 1.0526E+03 XsA4 + 5.2200E-02 XfA5 0.0000E+00 + S... 13597 0 2.26E+02 2A 5mM 0%D2O Sbak 13594 0 1.13E+02 + H2O Buffer + V... 13552 3 1.00E+00 H2O5m +Process: + Name: NCNR-IGOR + Date: 03-SEP-2006 11:42:47 + Description: + Terms: + average_type: Circular + SAM_file: SEP06064.SA3_AJJ_L205 + BKD_file: SEP06064.SA3_AJJ_L205 + EMP_file: SEP06064.SA3_AJJ_L205 + DIV_file: SEP06064.SA3_AJJ_L205 + MASK_file: SEP06064.SA3_AJJ_L205 + ABS:TSTAND: 1 + ABS:DSTAND: 1.0 mm + ABS:IZERO: 230.09 + ABS:XSECT: 1.0 mm + Notes: + No Information +Sample: + ID: SI600-new-long + Transmission: 0.327 + Thickness: 1.03 mm + Temperature: 0.0 K + Position: Vec3(x=10.0 mm, y=0.0 mm, z=None) + Orientation: Rot3(roll=22.5 deg, pitch=0.02 deg, yaw=None) +Collimation: + Length: 123.0 mm + Aperture: + Name: source + Aperture size: Vec3(x=50.0 mm, y=None, z=None) + Aperture distance: 11.0 m + Aperture: + Name: sample + Aperture size: Vec3(x=1.0 mm, y=None, z=None) + Aperture distance: None +Detector: + Name: fictional hybrid + Distance: 4.15 m + Offset: Vec3(x=1.0 mm, y=2.0 mm, z=None) + Orientation: Rot3(roll=1.0 deg, pitch=0.0 deg, yaw=0.0 deg) + Beam center: Vec3(x=322.64 mm, y=327.68 mm, z=None) + Pixel size: Vec3(x=5.0 mm, y=5.0 mm, z=None) + Slit length: None +Source: + Radiation: neutron + Shape: disc + Wavelength: 6.0 Å + Min. Wavelength: 0.22 nm + Max. Wavelength: 1.0 nm + Wavelength Spread: 14.3 % + Beam Size: BeamSize(name='bm', size=Vec3(x=12.0 mm, y=13.0 mm, z=None)) diff --git a/test/sasdataloader/reference/cansas1d_badunits.txt b/test/sasdataloader/reference/cansas1d_badunits.txt new file mode 100644 index 00000000..4c61f204 --- /dev/null +++ b/test/sasdataloader/reference/cansas1d_badunits.txt @@ -0,0 +1,72 @@ +Test title + Q + I +Metadata: + + Test title, Run: 1234 + ===================== + +Definition: Test title +Process: + Name: spol + Date: 04-Sep-2007 18:35:02 + Description: None + Terms: + radialstep: 10.0 mm + sector_width: 4.1416 rad + sector_orient: 0.0 deg + MASK_file: USER:MASK.COM + Notes: + AvA1 0.0000E+00 AsA2 1.0000E+00 XvA3 1.0526E+03 XsA4 + 5.2200E-02 XfA5 0.0000E+00 + S... 13597 0 2.26E+02 2A 5mM 0%D2O Sbak 13594 0 1.13E+02 + H2O Buffer + V... 13552 3 1.00E+00 H2O5m +Process: + Name: NCNR-IGOR + Date: 03-SEP-2006 11:42:47 + Description: + Terms: + average_type: Circular + SAM_file: SEP06064.SA3_AJJ_L205 + BKD_file: SEP06064.SA3_AJJ_L205 + EMP_file: SEP06064.SA3_AJJ_L205 + DIV_file: SEP06064.SA3_AJJ_L205 + MASK_file: SEP06064.SA3_AJJ_L205 + ABS:TSTAND: 1 + ABS:DSTAND: 1.0 mm + ABS:IZERO: 230.09 + ABS:XSECT: 1.0 mm +Sample: + ID: SI600-new-long + Transmission: 0.327 + Thickness: 0.00103 none + Temperature: 0.0 K + Position: Vec3(x=10000000.0 nm, y=0.0 mm, z=None) + Orientation: Rot3(roll=0.39269908 rad, pitch=0.00034906585 rad, yaw=None) +Collimation: + Length: 0.123 m + Aperture: + Name: source + Aperture size: Vec3(x=50000.0 µm, y=None, z=None) + Aperture distance: 1100.0 cm + Aperture: + Name: sample + Aperture size: Vec3(x=0.1 cm, y=None, z=None) + Aperture distance: None +Detector: + Name: fictional hybrid + Distance: 4.15 m + Offset: Vec3(x=1000000.0 nm, y=2000.0 µm, z=None) + Orientation: Rot3(roll=0.0174533 rad, pitch=0.0 rad, yaw=0.0 rad) + Beam center: Vec3(x=0.32264 m, y=0.32768 m, z=None) + Pixel size: Vec3(x=0.5 cm, y=0.5 cm, z=None) + Slit length: None +Source: + Radiation: neutron + Shape: disc + Wavelength: 0.6 nm + Min. Wavelength: 2.2 Å + Max. Wavelength: 10.0 Å + Wavelength Spread: 14.3 % + Beam Size: BeamSize(name='bm', size=Vec3(x=0.012 m, y=13000.0 µm, z=None)) diff --git a/test/sasdataloader/reference/cansas1d_notitle.txt b/test/sasdataloader/reference/cansas1d_notitle.txt new file mode 100644 index 00000000..0cdf9819 --- /dev/null +++ b/test/sasdataloader/reference/cansas1d_notitle.txt @@ -0,0 +1,74 @@ +SasData01 + Q + I +Metadata: + + None, Run: 1234 + =========== + +Definition: None +Process: + Name: spol + Date: 04-Sep-2007 18:35:02 + Description: None + Terms: + radialstep: 10.0 mm + sector_width: 180.0 deg + sector_orient: 0.0 deg + MASK_file: USER:MASK.COM + Notes: + AvA1 0.0000E+00 AsA2 1.0000E+00 XvA3 1.0526E+03 XsA4 + 5.2200E-02 XfA5 0.0000E+00 + S... 13597 0 2.26E+02 2A 5mM 0%D2O Sbak 13594 0 1.13E+02 + H2O Buffer + V... 13552 3 1.00E+00 H2O5m +Process: + Name: NCNR-IGOR + Date: 03-SEP-2006 11:42:47 + Description: + Terms: + average_type: Circular + SAM_file: SEP06064.SA3_AJJ_L205 + BKD_file: SEP06064.SA3_AJJ_L205 + EMP_file: SEP06064.SA3_AJJ_L205 + DIV_file: SEP06064.SA3_AJJ_L205 + MASK_file: SEP06064.SA3_AJJ_L205 + ABS:TSTAND: 1 + ABS:DSTAND: 1.0 mm + ABS:IZERO: 230.09 + ABS:XSECT: 1.0 mm + Notes: + No Information +Sample: + ID: SI600-new-long + Transmission: 0.327 + Thickness: 1.03 mm + Temperature: 0.0 K + Position: Vec3(x=10.0 mm, y=0.0 mm, z=None) + Orientation: Rot3(roll=22.5 deg, pitch=0.02 deg, yaw=None) +Collimation: + Length: 123.0 mm + Aperture: + Name: source + Aperture size: Vec3(x=50.0 mm, y=None, z=None) + Aperture distance: 11.0 m + Aperture: + Name: sample + Aperture size: Vec3(x=1.0 mm, y=None, z=None) + Aperture distance: None +Detector: + Name: fictional hybrid + Distance: 4.15 m + Offset: Vec3(x=1.0 mm, y=2.0 mm, z=None) + Orientation: Rot3(roll=1.0 deg, pitch=0.0 deg, yaw=0.0 deg) + Beam center: Vec3(x=322.64 mm, y=327.68 mm, z=None) + Pixel size: Vec3(x=5.0 mm, y=5.0 mm, z=None) + Slit length: None +Source: + Radiation: neutron + Shape: disc + Wavelength: 6.0 Å + Min. Wavelength: 0.22 nm + Max. Wavelength: 1.0 nm + Wavelength Spread: 14.3 % + Beam Size: BeamSize(name='bm', size=Vec3(x=12.0 mm, y=13.0 mm, z=None)) diff --git a/test/sasdataloader/reference/cansas1d_slit.txt b/test/sasdataloader/reference/cansas1d_slit.txt new file mode 100644 index 00000000..cbc9bdcf --- /dev/null +++ b/test/sasdataloader/reference/cansas1d_slit.txt @@ -0,0 +1,74 @@ +Test title + dQw + dQl + Q + I +Metadata: + + Test title, Run: 1234 + ===================== + +Definition: Test title +Process: + Name: spol + Date: 04-Sep-2007 18:35:02 + Description: None + Terms: + radialstep: 10.0 mm + sector_width: 180.0 deg + sector_orient: 0.0 deg + MASK_file: USER:MASK.COM + Notes: + AvA1 0.0000E+00 AsA2 1.0000E+00 XvA3 1.0526E+03 XsA4 + 5.2200E-02 XfA5 0.0000E+00 + S... 13597 0 2.26E+02 2A 5mM 0%D2O Sbak 13594 0 1.13E+02 + H2O Buffer + V... 13552 3 1.00E+00 H2O5m +Process: + Name: NCNR-IGOR + Date: 03-SEP-2006 11:42:47 + Description: + Terms: + average_type: Circular + SAM_file: SEP06064.SA3_AJJ_L205 + BKD_file: SEP06064.SA3_AJJ_L205 + EMP_file: SEP06064.SA3_AJJ_L205 + DIV_file: SEP06064.SA3_AJJ_L205 + MASK_file: SEP06064.SA3_AJJ_L205 + ABS:TSTAND: 1 + ABS:DSTAND: 1.0 mm + ABS:IZERO: 230.09 + ABS:XSECT: 1.0 mm +Sample: + ID: SI600-new-long + Transmission: 0.327 + Thickness: 1.03 mm + Temperature: 0.0 K + Position: Vec3(x=10.0 mm, y=0.0 mm, z=None) + Orientation: Rot3(roll=22.5 deg, pitch=0.02 deg, yaw=None) +Collimation: + Length: 123.0 mm + Aperture: + Name: source + Aperture size: Vec3(x=50.0 mm, y=None, z=None) + Aperture distance: 11.0 m + Aperture: + Name: sample + Aperture size: Vec3(x=1.0 mm, y=None, z=None) + Aperture distance: None +Detector: + Name: fictional hybrid + Distance: 4.15 m + Offset: Vec3(x=1.0 mm, y=2.0 mm, z=None) + Orientation: Rot3(roll=1.0 deg, pitch=0.0 deg, yaw=0.0 deg) + Beam center: Vec3(x=322.64 mm, y=327.68 mm, z=None) + Pixel size: Vec3(x=5.0 mm, y=5.0 mm, z=None) + Slit length: None +Source: + Radiation: neutron + Shape: disc + Wavelength: 6.0 Å + Min. Wavelength: 0.22 nm + Max. Wavelength: 1.0 nm + Wavelength Spread: 14.3 % + Beam Size: BeamSize(name='bm', size=Vec3(x=12.0 mm, y=13.0 mm, z=None)) diff --git a/test/sasdataloader/reference/cansas1d_units.txt b/test/sasdataloader/reference/cansas1d_units.txt new file mode 100644 index 00000000..68db7bc4 --- /dev/null +++ b/test/sasdataloader/reference/cansas1d_units.txt @@ -0,0 +1,72 @@ +Test title + Q + I +Metadata: + + Test title, Run: 1234 + ===================== + +Definition: Test title +Process: + Name: spol + Date: 04-Sep-2007 18:35:02 + Description: None + Terms: + radialstep: 10.0 mm + sector_width: 4.1416 rad + sector_orient: 0.0 deg + MASK_file: USER:MASK.COM + Notes: + AvA1 0.0000E+00 AsA2 1.0000E+00 XvA3 1.0526E+03 XsA4 + 5.2200E-02 XfA5 0.0000E+00 + S... 13597 0 2.26E+02 2A 5mM 0%D2O Sbak 13594 0 1.13E+02 + H2O Buffer + V... 13552 3 1.00E+00 H2O5m +Process: + Name: NCNR-IGOR + Date: 03-SEP-2006 11:42:47 + Description: + Terms: + average_type: Circular + SAM_file: SEP06064.SA3_AJJ_L205 + BKD_file: SEP06064.SA3_AJJ_L205 + EMP_file: SEP06064.SA3_AJJ_L205 + DIV_file: SEP06064.SA3_AJJ_L205 + MASK_file: SEP06064.SA3_AJJ_L205 + ABS:TSTAND: 1 + ABS:DSTAND: 1.0 mm + ABS:IZERO: 230.09 + ABS:XSECT: 1.0 mm +Sample: + ID: SI600-new-long + Transmission: 0.327 + Thickness: 0.00103 m + Temperature: 0.0 K + Position: Vec3(x=10000000.0 nm, y=0.0 mm, z=None) + Orientation: Rot3(roll=0.39269908 rad, pitch=0.00034906585 rad, yaw=None) +Collimation: + Length: 0.123 m + Aperture: + Name: source + Aperture size: Vec3(x=50000.0 µm, y=None, z=None) + Aperture distance: 1100.0 cm + Aperture: + Name: sample + Aperture size: Vec3(x=0.1 cm, y=None, z=None) + Aperture distance: None +Detector: + Name: fictional hybrid + Distance: 4.15 m + Offset: Vec3(x=1000000.0 nm, y=2000.0 µm, z=None) + Orientation: Rot3(roll=0.0174533 rad, pitch=0.0 rad, yaw=0.0 rad) + Beam center: Vec3(x=0.32264 m, y=0.32768 m, z=None) + Pixel size: Vec3(x=0.5 cm, y=0.5 cm, z=None) + Slit length: None +Source: + Radiation: neutron + Shape: disc + Wavelength: 0.6 nm + Min. Wavelength: 2.2 Å + Max. Wavelength: 10.0 Å + Wavelength Spread: 14.3 % + Beam Size: BeamSize(name='bm', size=Vec3(x=0.012 m, y=13000.0 µm, z=None)) diff --git a/test/sasdataloader/reference/cansas_test.txt b/test/sasdataloader/reference/cansas_test.txt new file mode 100644 index 00000000..60333cc0 --- /dev/null +++ b/test/sasdataloader/reference/cansas_test.txt @@ -0,0 +1,49 @@ +ILL-D11 example1: 2A 5mM 0%D2O + Q + I +Metadata: + + ILL-D11 example1: 2A 5mM 0%D2O, Run: g013586.001 + ================================================ + +Definition: ILL-D11 example1: 2A 5mM 0%D2O +Process: + Name: spol + Date: 04-Sep-2007 18:12:27 + Description: None + Terms: + radialstep: 10.0 mm + sector_width: 180.0 deg + sector_orient: 0.0 deg + Flux_monitor: 1.00 + Count_time: 900.0 s + Q_resolution: estimated + Notes: + AvA1 0.0000E+00 AsA2 1.0000E+00 XvA3 1.1111E+01 XsA4 5.2200E-02 XfA5 0.0000E+00 + S... 13586 0 5.63E+01 2A 5mM 0%D2O Sbak 13567 0 1.88E+01 H2O buffer + Sbak 13533 0 7.49E+00 H2O buffer V... 13552 1 1.00E+00 Normal 10m from +Sample: + ID: None + Transmission: 0.0 + Thickness: 0.0 mm + Temperature: 32.0 K + Position: Vec3(x=10.0 mm, y=0.0 mm, z=None) + Orientation: Rot3(roll=0.02 deg, pitch=None, yaw=None) +Collimation: + Length: None +Detector: + Name: + Distance: 10.0 m + Offset: None + Orientation: Rot3(roll=0.0 deg, pitch=None, yaw=None) + Beam center: Vec3(x=311.0 mm, y=315.0 mm, z=None) + Pixel size: Vec3(x=10.0 mm, y=10.0 mm, z=None) + Slit length: None +Source: + Radiation: neutron + Shape: None + Wavelength: 6.0 Å + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: 0.0 Å + Beam Size: BeamSize(name=None, size=Vec3(x=30.0 mm, y=0.0 mm, z=None)) diff --git a/test/sasdataloader/reference/cansas_test_modified.txt b/test/sasdataloader/reference/cansas_test_modified.txt new file mode 100644 index 00000000..9ccd51e7 --- /dev/null +++ b/test/sasdataloader/reference/cansas_test_modified.txt @@ -0,0 +1,49 @@ +ILL-D11 example1: 2A 5mM 0%D2O + Q + I +Metadata: + + ILL-D11 example1: 2A 5mM 0%D2O, Run: g013586.001 + ================================================ + +Definition: ILL-D11 example1: 2A 5mM 0%D2O +Process: + Name: spol + Date: 04-Sep-2007 18:12:27 + Description: None + Terms: + radialstep: 10.0 mm + sector_width: 180.0 deg + sector_orient: 0.0 deg + Flux_monitor: 1.00 + Count_time: 900.0 s + Q_resolution: estimated + Notes: + AvA1 0.0000E+00 AsA2 1.0000E+00 XvA3 1.1111E+01 XsA4 5.2200E-02 XfA5 0.0000E+00 + S... 13586 0 5.63E+01 2A 5mM 0%D2O Sbak 13567 0 1.88E+01 H2O buffer + Sbak 13533 0 7.49E+00 H2O buffer V... 13552 1 1.00E+00 Normal 10m from +Sample: + ID: This is a test file + Transmission: 0.0 + Thickness: 0.0 mm + Temperature: 32.0 K + Position: Vec3(x=10.0 mm, y=0.0 mm, z=None) + Orientation: Rot3(roll=0.02 deg, pitch=None, yaw=None) +Collimation: + Length: 10.0 m +Detector: + Name: + Distance: 10.0 m + Offset: None + Orientation: Rot3(roll=0.0 deg, pitch=None, yaw=None) + Beam center: Vec3(x=311.0 mm, y=315.0 mm, z=None) + Pixel size: Vec3(x=10.0 mm, y=10.0 mm, z=None) + Slit length: None +Source: + Radiation: neutron + Shape: None + Wavelength: 6.0 Å + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: 0.0 Å + Beam Size: BeamSize(name=None, size=Vec3(x=30.0 mm, y=0.0 mm, z=None)) diff --git a/test/sasdataloader/reference/cansas_xml_multisasentry_multisasdata.txt b/test/sasdataloader/reference/cansas_xml_multisasentry_multisasdata.txt new file mode 100644 index 00000000..a968b1af --- /dev/null +++ b/test/sasdataloader/reference/cansas_xml_multisasentry_multisasdata.txt @@ -0,0 +1,340 @@ +AF1410:10 + Q + I +Metadata: + + AF1410-10 (AF1410 steel aged 10 h), Run: ['nuclear sector', 'nuclear+magnetic sector'] + =========================================== + +Definition: AF1410-10 (AF1410 steel aged 10 h) +Sample: + ID: AF1410-10 (AF1410 steel aged 10 h) + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: area + Distance: None + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: neutron + Shape: None + Wavelength: 0.85 nm + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: 25.0 % + Beam Size: None +AF1410:1h + Q + I +Metadata: + + AF1410-1h (AF1410 steel aged 1 h), Run: ['nuclear sector', 'nuclear+magnetic sector'] + ========================================== + +Definition: AF1410-1h (AF1410 steel aged 1 h) +Sample: + ID: AF1410-1h (AF1410 steel aged 1 h) + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: area + Distance: None + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: neutron + Shape: None + Wavelength: 0.85 nm + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: 25.0 % + Beam Size: None +AF1410:20 + Q + I +Metadata: + + AF1410-20 (AF1410 steel aged 20 h), Run: nuclear+magnetic sector + ================================================================ + +Definition: AF1410-20 (AF1410 steel aged 20 h) +Sample: + ID: AF1410-20 (AF1410 steel aged 20 h) + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: area + Distance: None + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: neutron + Shape: None + Wavelength: 0.85 nm + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: 25.0 % + Beam Size: None +AF1410:2h + Q + I +Metadata: + + AF1410-2h (AF1410 steel aged 2 h), Run: ['nuclear sector', 'nuclear+magnetic sector'] + ========================================== + +Definition: AF1410-2h (AF1410 steel aged 2 h) +Sample: + ID: AF1410-2h (AF1410 steel aged 2 h) + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: area + Distance: None + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: neutron + Shape: None + Wavelength: 0.85 nm + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: 25.0 % + Beam Size: None +AF1410:50 + Q + I +Metadata: + + AF1410-50 (AF1410 steel aged 50 h), Run: ['nuclear sector', 'nuclear+magnetic sector'] + =========================================== + +Definition: AF1410-50 (AF1410 steel aged 50 h) +Sample: + ID: AF1410-50 (AF1410 steel aged 50 h) + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: area + Distance: None + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: neutron + Shape: None + Wavelength: 0.85 nm + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: 25.0 % + Beam Size: None +AF1410:5h + Q + I +Metadata: + + AF1410-5h (AF1410 steel aged 5 h), Run: ['nuclear sector', 'nuclear+magnetic sector'] + ========================================== + +Definition: AF1410-5h (AF1410 steel aged 5 h) +Sample: + ID: AF1410-5h (AF1410 steel aged 5 h) + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: area + Distance: None + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: neutron + Shape: None + Wavelength: 0.85 nm + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: 25.0 % + Beam Size: None +AF1410:8h + Q + I +Metadata: + + AF1410-8h (AF1410 steel aged 8 h), Run: ['nuclear sector', 'nuclear+magnetic sector'] + ========================================== + +Definition: AF1410-8h (AF1410 steel aged 8 h) +Sample: + ID: AF1410-8h (AF1410 steel aged 8 h) + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: area + Distance: None + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: neutron + Shape: None + Wavelength: 0.85 nm + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: 25.0 % + Beam Size: None +AF1410:cc + Q + I +Metadata: + + AF1410-cc (AF1410 steel aged 100 h), Run: ['nuclear sector', 'nuclear+magnetic sector'] + ============================================ + +Definition: AF1410-cc (AF1410 steel aged 100 h) +Sample: + ID: AF1410-cc (AF1410 steel aged 100 h) + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: area + Distance: None + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: neutron + Shape: None + Wavelength: 0.85 nm + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: 25.0 % + Beam Size: None +AF1410:hf + Q + I +Metadata: + + AF1410-hf (AF1410 steel aged 0.5 h), Run: ['nuclear sector', 'nuclear+magnetic sector'] + ============================================ + +Definition: AF1410-hf (AF1410 steel aged 0.5 h) +Sample: + ID: AF1410-hf (AF1410 steel aged 0.5 h) + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: area + Distance: None + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: neutron + Shape: None + Wavelength: 0.85 nm + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: 25.0 % + Beam Size: None +AF1410:qu + Q + I +Metadata: + + AF1410-qu (AF1410 steel aged 0.25 h), Run: ['nuclear sector', 'nuclear+magnetic sector'] + ============================================= + +Definition: AF1410-qu (AF1410 steel aged 0.25 h) +Sample: + ID: AF1410-qu (AF1410 steel aged 0.25 h) + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: area + Distance: None + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: neutron + Shape: None + Wavelength: 0.85 nm + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: 25.0 % + Beam Size: None diff --git a/test/sasdataloader/reference/nxcansas_1Dand2D_multisasdata.txt b/test/sasdataloader/reference/nxcansas_1Dand2D_multisasdata.txt new file mode 100644 index 00000000..4ddf88a9 --- /dev/null +++ b/test/sasdataloader/reference/nxcansas_1Dand2D_multisasdata.txt @@ -0,0 +1,44 @@ +sasentry01 +Metadata: + + MH4_5deg_16T_SLOW, Run: 33837 + ============================= + +Definition: MH4_5deg_16T_SLOW +Process: + Name: Mantid generated CanSAS1D XML + Date: 11-May-2016 12:15:34 + Description: None +Sample: + ID: + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: front-detector + Distance: 2845.260009765625 mm + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Detector: + Name: rear-detector + Distance: 4385.27978515625 mm + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: Spallation Neutron Source + Shape: None + Wavelength: None + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: None + Beam Size: None diff --git a/test/sasdataloader/reference/nxcansas_1Dand2D_multisasentry.txt b/test/sasdataloader/reference/nxcansas_1Dand2D_multisasentry.txt new file mode 100644 index 00000000..037254ac --- /dev/null +++ b/test/sasdataloader/reference/nxcansas_1Dand2D_multisasentry.txt @@ -0,0 +1,88 @@ +sasentry01 +Metadata: + + MH4_5deg_16T_SLOW, Run: 33837 + ============================= + +Definition: MH4_5deg_16T_SLOW +Process: + Name: Mantid generated CanSAS1D XML + Date: 11-May-2016 12:15:34 + Description: None +Sample: + ID: + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: front-detector + Distance: 2845.260009765625 mm + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Detector: + Name: rear-detector + Distance: 4385.27978515625 mm + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: Spallation Neutron Source + Shape: None + Wavelength: None + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: None + Beam Size: None +sasentry02 +Metadata: + + MH4_5deg_16T_SLOW, Run: 33837 + ============================= + +Definition: MH4_5deg_16T_SLOW +Process: + Name: Mantid generated CanSAS1D XML + Date: 11-May-2016 12:15:34 + Description: None +Sample: + ID: + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: front-detector + Distance: 2845.260009765625 mm + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Detector: + Name: rear-detector + Distance: 4385.27978515625 mm + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: Spallation Neutron Source + Shape: None + Wavelength: None + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: None + Beam Size: None diff --git a/test/sasdataloader/reference/simpleexamplefile.txt b/test/sasdataloader/reference/simpleexamplefile.txt new file mode 100644 index 00000000..5349c963 --- /dev/null +++ b/test/sasdataloader/reference/simpleexamplefile.txt @@ -0,0 +1,41 @@ +sasentry01 +Metadata: + + None, Run: None + =============== + +Definition: None +Process: + Name: None + Date: None + Description: None + Term: None + Notes: None +Sample: + ID: None + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Detector: + Name: None + Distance: None + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: None + Shape: None + Wavelength: None + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: None + Beam Size: None +Transmission Spectrum: + Name: None + Timestamp: None + Wavelengths: None + Transmission: None diff --git a/test/sasdataloader/reference/sphere2micron.txt b/test/sasdataloader/reference/sphere2micron.txt new file mode 100644 index 00000000..350711fb --- /dev/null +++ b/test/sasdataloader/reference/sphere2micron.txt @@ -0,0 +1,26 @@ +Sesans + Wavelength + SpinEchoLength + Polarisation + Depolarisation +Metadata: + + Polystyrene of Markus Strobl, Full Sine, ++ only, Run: [] + ======================================================== + +Definition: Polystyrene of Markus Strobl, Full Sine, ++ only +Process: + Name: SESANS Processing + Date: None + Description: Polarisation measurement through a SESANS instrument + Terms: + ymax: 0.0168 rad + zmax: 0.0168 rad + orientation: Z +Sample: + ID: None + Transmission: None + Thickness: 0.2 cm + Temperature: None + Position: None + Orientation: None diff --git a/test/sasdataloader/reference/sphere_isis.txt b/test/sasdataloader/reference/sphere_isis.txt new file mode 100644 index 00000000..bdf0120f --- /dev/null +++ b/test/sasdataloader/reference/sphere_isis.txt @@ -0,0 +1,25 @@ +Sesans + Wavelength + SpinEchoLength + Depolarisation +Metadata: + + PMMA in Mixed Deuterated decalin, Run: [] + ======================================= + +Definition: PMMA in Mixed Deuterated decalin +Process: + Name: SESANS Processing + Date: None + Description: Polarisation measurement through a SESANS instrument + Terms: + ymax: 0.09 rad + zmax: 0.09 rad + orientation: Z +Sample: + ID: None + Transmission: None + Thickness: 2.0 mm + Temperature: None + Position: None + Orientation: None diff --git a/test/sasdataloader/reference/valid_cansas_xml.txt b/test/sasdataloader/reference/valid_cansas_xml.txt new file mode 100644 index 00000000..3b3993ab --- /dev/null +++ b/test/sasdataloader/reference/valid_cansas_xml.txt @@ -0,0 +1,49 @@ +80514main_1D_2.2_10.0 + Q + I +Metadata: + + LOQ_Standard_TK49_SANS, Run: 80514 + ================================== + +Definition: LOQ_Standard_TK49_SANS +Process: + Name: Mantid generated CanSAS1D XML + Date: 10-Oct-2013 16:00:29 + Description: None + Terms: + svn: 2.6.20130902.1504 + user_file: K:/masks/MASKLOQ_MAN_133D_Xpress_8mm.txt +Sample: + ID: LOQ_Standard_TK49_SANS + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: HAB + Distance: 0.579 m + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Detector: + Name: main-detector-bank + Distance: 4.14902 m + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: Spallation Neutron Source + Shape: None + Wavelength: None + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: None + Beam Size: None diff --git a/test/sasdataloader/reference/x25000_no_di.txt b/test/sasdataloader/reference/x25000_no_di.txt new file mode 100644 index 00000000..10063d02 --- /dev/null +++ b/test/sasdataloader/reference/x25000_no_di.txt @@ -0,0 +1,32 @@ +sasentry01 +Metadata: + + , Run: + ======= + +Definition: +Sample: + ID: + Transmission: None + Thickness: None + Temperature: None + Position: None + Orientation: None +Collimation: + Length: None +Detector: + Name: + Distance: None + Offset: None + Orientation: None + Beam center: None + Pixel size: None + Slit length: None +Source: + Radiation: neutron + Shape: None + Wavelength: None + Min. Wavelength: None + Max. Wavelength: None + Wavelength Spread: None + Beam Size: None diff --git a/test/sasdataloader/utest_new_sesans.py b/test/sasdataloader/utest_new_sesans.py new file mode 100644 index 00000000..7aac9e3b --- /dev/null +++ b/test/sasdataloader/utest_new_sesans.py @@ -0,0 +1,78 @@ +""" +Unit tests for the new recursive cansas reader +""" + +import numpy as np +import os +import pytest + + +from sasdata.model_requirements import guess_requirements, ComposeRequirements, SmearModel, SesansModel, NullModel +from sasdata.temp_sesans_reader import load_data +from sasdata.quantities import units, unit_parser + +test_file_names = ["sphere2micron", "sphere_isis"] + + +def local_load(path: str): + """Get local file path""" + return os.path.join(os.path.dirname(__file__), path) + + +@pytest.mark.sesans +@pytest.mark.parametrize("f", test_file_names) +def test_load_file(f): + data = load_data(local_load(f"sesans_data/{f}.ses")) + + with open(local_load(f"reference/{f}.txt")) as infile: + expected = "".join(infile.readlines()) + assert data.summary() == expected + +@pytest.mark.sesans +def test_sesans_modelling(): + data = load_data(local_load("sesans_data/sphere2micron.ses")) + req = guess_requirements(data) + assert type(req) is SesansModel + + + def sphere(qr): + def sas_3j1x_x(x): + return (np.sin(x) - x * np.cos(x))/x**3 + def form_volume(x): + return np.pi * 4.0 / 3.0 * x**3 + radius = 10000 # 1 micron + + bes = sas_3j1x_x(q*radius) + contrast = 5.4e-7 # Contrast is hard coded for best fit + form = contrast * form_volume(radius) * bes + f2 = 1.0e-4*form*form + return f2 + + # The Hankel transform of x is -r^-3 + x = data._data_contents["SpinEchoLength"].in_units_of(units.angstroms) + q = req.preprocess_q(x, data) + result = req.postprocess_iq(sphere(q), data) + + y, yerr = data._data_contents["Depolarisation"].in_units_of_with_standard_error(unit_parser.parse("A-2 cm-1")) + assert y.shape == result.shape + + xi_squared = np.sum( ((y - result) / yerr)**2 ) / len(y) + assert 1.0 < xi_squared < 1.5 + + + +@pytest.mark.sesans +def test_model_algebra(): + ses = SesansModel() + sme = SmearModel() + null = NullModel() + + # Ignore slit smearing if we perform a sesans transform afterwards + assert type(sme + ses) is SesansModel + # However, it is possible for the spin echo lengths to have some + # smearing between them. + assert type(ses + sme) is ComposeRequirements + assert type(null + ses) is SesansModel + assert type(null + sme) is SmearModel + assert type(ses + null) is SesansModel + assert type(sme + null) is SmearModel diff --git a/test/sasdataloader/utest_sasdataload.py b/test/sasdataloader/utest_sasdataload.py new file mode 100644 index 00000000..8803052e --- /dev/null +++ b/test/sasdataloader/utest_sasdataload.py @@ -0,0 +1,81 @@ +""" +Unit tests for the new recursive cansas reader +""" + +import numpy as np +import os +import pytest + + +from sasdata.quantities.quantity import Quantity +import sasdata.quantities.units as units +from sasdata.temp_hdf5_reader import load_data as hdf_load_data +from sasdata.temp_xml_reader import load_data as xml_load_data + +test_hdf_file_names = [ + # "simpleexamplefile", + "nxcansas_1Dand2D_multisasentry", + "nxcansas_1Dand2D_multisasdata", + "MAR07232_rest", + "x25000_no_di", +] + +test_xml_file_names = [ + "ISIS_1_0", + "ISIS_1_1", + "ISIS_1_1_doubletrans", + "ISIS_1_1_notrans", + "TestExtensions", + "cansas1d", + "cansas1d_badunits", + "cansas1d_notitle", + "cansas1d_slit", + "cansas1d_units", + "cansas_test", + "cansas_test_modified", + "cansas_xml_multisasentry_multisasdata", + "valid_cansas_xml", +] + + +def local_load(path: str): + """Get local file path""" + return os.path.join(os.path.dirname(__file__), path) + + +@pytest.mark.sasdata +@pytest.mark.parametrize("f", test_hdf_file_names) +def test_hdf_load_file(f): + data = hdf_load_data(local_load(f"data/{f}.h5")) + + with open(local_load(f"reference/{f}.txt"), encoding="utf-8") as infile: + expected = "".join(infile.readlines()) + keys = sorted([d for d in data]) + assert "".join(data[k].summary() for k in keys) == expected + + +@pytest.mark.sasdata +@pytest.mark.parametrize("f", test_xml_file_names) +def test_xml_load_file(f): + data = xml_load_data(local_load(f"data/{f}.xml")) + + with open(local_load(f"reference/{f}.txt"), encoding="utf-8") as infile: + expected = "".join(infile.readlines()) + keys = sorted([d for d in data]) + assert "".join(data[k].summary() for k in keys) == expected + + +@pytest.mark.sasdata +def test_filter_data(): + data = xml_load_data(local_load("data/cansas1d_notitle.xml")) + for k, v in data.items(): + assert v.metadata.raw.filter("transmission") == ["0.327"] + assert v.metadata.raw.filter("wavelength")[0] == Quantity(6.0, units.angstroms) + assert v.metadata.raw.filter("SDD")[0] == Quantity(4.15, units.meters) + data = hdf_load_data(local_load("data/nxcansas_1Dand2D_multisasentry.h5")) + for k, v in data.items(): + assert v.metadata.raw.filter("radiation") == ["Spallation Neutron Source"] + assert v.metadata.raw.filter("SDD") == [ + Quantity(np.array([2845.26], dtype=np.float32), units.millimeters), + Quantity(np.array([4385.28], dtype=np.float32), units.millimeters) + ] diff --git a/test/slicers/__init__.py b/test/slicers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/slicers/meshes_for_testing.py b/test/slicers/meshes_for_testing.py new file mode 100644 index 00000000..fb346e79 --- /dev/null +++ b/test/slicers/meshes_for_testing.py @@ -0,0 +1,115 @@ +""" +Meshes used in testing along with some expected values +""" + +import numpy as np + +from sasdata.slicing.meshes.voronoi_mesh import voronoi_mesh +from sasdata.slicing.meshes.mesh import Mesh +from sasdata.slicing.meshes.meshmerge import meshmerge + +coords = np.arange(-4, 5) +grid_mesh = voronoi_mesh(*np.meshgrid(coords, coords)) + + +item_1 = np.array([ + [-3.5, -0.5], + [-0.5, 3.5], + [ 0.5, 3.5], + [ 3.5, -0.5], + [ 0.0, 1.5]], dtype=float) + +item_2 = np.array([ + [-1.0, -2.0], + [-2.0, -2.0], + [-2.0, -1.0], + [-1.0, -1.0]], dtype=float) + +mesh_points = np.concatenate((item_1, item_2), axis=0) +cells = [[0,1,2,3,4],[5,6,7,8]] + +shape_mesh = Mesh(mesh_points, cells) + +# Subset of the mappings that meshmerge should include +# This can be read off the plots generated below + + +expected_shape_mappings = [ + (100, -1), + (152, -1), + (141, -1), + (172, -1), + (170, -1), + (0, -1), + (1, -1), + (8, 0), + (9, 0), + (37, 0), + (83, 0), + (190, 1), + (186, 1), + (189, 1), + (193, 1) +] + +expected_grid_mappings = [ + (89, 0), + (90, 1), + (148, 16), + (175, 35), + (60, 47), + (44, 47), + (80, 60) +] + +# +# Mesh location tests +# + +location_test_mesh_points = np.array([ + [0, 0], # 0 + [0, 1], # 1 + [0, 2], # 2 + [1, 0], # 3 + [1, 1], # 4 + [1, 2], # 5 + [2, 0], # 6 + [2, 1], # 7 + [2, 2]], dtype=float) + +location_test_mesh_cells = [ + [0, 1, 4, 3], + [1, 2, 5, 4], + [3, 4, 7, 6], + [4, 5, 8, 7]] + +location_test_mesh = Mesh(location_test_mesh_points, location_test_mesh_cells) + +test_coords = 0.25 + 0.5*np.arange(4) +location_test_points_x, location_test_points_y = np.meshgrid(test_coords, test_coords) + +if __name__ == "__main__": + + import matplotlib.pyplot as plt + + combined_mesh, _, _ = meshmerge(grid_mesh, shape_mesh) + + plt.figure() + combined_mesh.show(actually_show=False, show_labels=True, color='k') + grid_mesh.show(actually_show=False, show_labels=True, color='r') + + plt.xlim([-5, 5]) + plt.ylim([-5, 5]) + + plt.figure() + combined_mesh.show(actually_show=False, show_labels=True, color='k') + shape_mesh.show(actually_show=False, show_labels=True, color='r') + + plt.xlim([-5, 5]) + plt.ylim([-5, 5]) + + plt.figure() + location_test_mesh.show(actually_show=False, show_labels=True) + plt.scatter(location_test_points_x, location_test_points_y) + + plt.show() diff --git a/test/slicers/utest_meshmerge.py b/test/slicers/utest_meshmerge.py new file mode 100644 index 00000000..d83892de --- /dev/null +++ b/test/slicers/utest_meshmerge.py @@ -0,0 +1,32 @@ +""" +Tests for mesh merging operations. + +It's pretty hard to test componentwise, but we can do some tests of the general behaviour +""" + +from sasdata.slicing.meshes.meshmerge import meshmerge +from test.slicers.meshes_for_testing import ( + grid_mesh, shape_mesh, expected_grid_mappings, expected_shape_mappings) + + +def test_meshmerge_mappings(): + """ Test the output of meshmerge is correct + + IMPORTANT IF TESTS FAIL!!!... The docs for scipy.spatial.Voronoi and Delaunay + say that the ordering of faces might depend on machine precession. Thus, these + tests might not be reliable... we'll see how they play out + """ + + import sys + if sys.platform == "darwin": + # It does indeed rely on machine precision, only run on windows and linux + return + + combined_mesh, grid_mappings, shape_mappings = meshmerge(grid_mesh, shape_mesh) + + for triangle_cell, grid_cell in expected_grid_mappings: + assert grid_mappings[triangle_cell] == grid_cell + + for triangle_cell, shape_cell in expected_shape_mappings: + assert shape_mappings[triangle_cell] == shape_cell + diff --git a/test/slicers/utest_point_assignment.py b/test/slicers/utest_point_assignment.py new file mode 100644 index 00000000..4a5c2428 --- /dev/null +++ b/test/slicers/utest_point_assignment.py @@ -0,0 +1,4 @@ + + +def test_location_assignment(): + pass \ No newline at end of file diff --git a/test/transforms/utest_interpolation.py b/test/transforms/utest_interpolation.py new file mode 100644 index 00000000..7011d48a --- /dev/null +++ b/test/transforms/utest_interpolation.py @@ -0,0 +1,101 @@ +import pytest +import numpy as np +from matplotlib import pyplot as plt +from numpy.typing import ArrayLike +from typing import Callable + +from sasdata.quantities.plotting import quantity_plot +from sasdata.quantities.quantity import NamedQuantity, Quantity +from sasdata.quantities import units + +from sasdata.transforms.rebinning import calculate_interpolation_matrix_1d, InterpolationOptions + +test_functions = [ + lambda x: x**2, + lambda x: 2*x, + lambda x: x**3 +] + +test_interpolation_orders = [ + InterpolationOptions.LINEAR, + InterpolationOptions.CUBIC +] + + +@pytest.mark.parametrize("fun", test_functions) +@pytest.mark.parametrize("order", test_interpolation_orders) +def test_interpolate_matrix_inside(fun: Callable[[Quantity[ArrayLike]], Quantity[ArrayLike]], order: InterpolationOptions, show_plots: bool): + original_points = NamedQuantity("x_base", np.linspace(-10,10, 31), units.meters) + test_points = NamedQuantity("x_test", np.linspace(-5, 5, 11), units.meters) + + + mapping, _ = calculate_interpolation_matrix_1d(original_points, test_points, order=order) + + y_original = fun(original_points) + y_test = y_original @ mapping + y_expected = fun(test_points) + + test_units = y_expected.units + + y_values_test = y_test.in_units_of(test_units) + y_values_expected = y_expected.in_units_of(test_units) + + if show_plots: + print(y_values_test) + print(y_values_expected) + + quantity_plot(original_points, y_original) + quantity_plot(test_points, y_test) + quantity_plot(test_points, y_expected) + plt.show() + + assert len(y_values_test) == len(y_values_expected) + + for t, e in zip(y_values_test, y_values_expected): + assert t == pytest.approx(e, abs=2) + + +@pytest.mark.parametrize("fun", test_functions) +@pytest.mark.parametrize("order", test_interpolation_orders) +def test_interpolate_different_units(fun: Callable[[Quantity[ArrayLike]], Quantity[ArrayLike]], order: InterpolationOptions, show_plots: bool): + original_points = NamedQuantity("x_base", np.linspace(-10,10, 107), units.meters) + test_points = NamedQuantity("x_test", np.linspace(-5000, 5000, 11), units.millimeters) + + mapping, _ = calculate_interpolation_matrix_1d(original_points, test_points, order=order) + + y_original = fun(original_points) + y_test = y_original @ mapping + y_expected = fun(test_points) + + test_units = y_expected.units + + y_values_test = y_test.in_units_of(test_units) + y_values_expected = y_expected.in_units_of(test_units) + + if show_plots: + print(y_values_test) + print(y_test.in_si()) + print(y_values_expected) + + plt.plot(original_points.in_si(), y_original.in_si()) + plt.plot(test_points.in_si(), y_test.in_si(), "x") + plt.plot(test_points.in_si(), y_expected.in_si(), "o") + plt.show() + + assert len(y_values_test) == len(y_values_expected) + + for t, e in zip(y_values_test, y_values_expected): + assert t == pytest.approx(e, rel=5e-2) + +@pytest.mark.parametrize("order", test_interpolation_orders) +def test_linear(order: InterpolationOptions): + """ Test linear interpolation between two points""" + x_and_y = NamedQuantity("x_base", np.linspace(-10, 10, 2), units.meters) + new_x = NamedQuantity("x_test", np.linspace(-5000, 5000, 101), units.millimeters) + + mapping, _ = calculate_interpolation_matrix_1d(x_and_y, new_x, order=order) + + linear_points = x_and_y @ mapping + + for t, e in zip(new_x.in_si(), linear_points.in_si()): + assert t == pytest.approx(e, rel=1e-3) diff --git a/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/10_1000_1340_10.csv b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/10_1000_1340_10.csv new file mode 100644 index 00000000..8e9c7066 --- /dev/null +++ b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/10_1000_1340_10.csv @@ -0,0 +1,105 @@ +3.624299999999999744e-02 7.295645247999999583e+01 1.637502872666669873e+01 +4.068929999999999769e-02 3.496757764333329987e+01 1.132330254166670080e+01 +4.510000000000000120e-02 2.871506291000000033e+01 8.366418418333330109e+00 +4.957960000000000145e-02 3.062621534000000167e+01 6.807088010000000189e+00 +5.414959999999999912e-02 1.698840335499999910e+01 5.315930135000000334e+00 +5.867460000000000037e-02 1.810732352000000134e+01 4.582343003333329889e+00 +6.313670000000000393e-02 1.758858145166669829e+01 3.753068668333329860e+00 +6.771770000000000567e-02 1.874808681500000063e+01 3.129801861666670071e+00 +7.237100000000000477e-02 1.219239694000000007e+01 2.701832888333330018e+00 +7.692330000000000001e-02 1.502033604000000011e+01 2.415586504999999828e+00 +8.164470000000000061e-02 1.051882241166670084e+01 2.003147743333329789e+00 +8.639470000000000482e-02 1.266539752666670005e+01 1.813845073333330005e+00 +9.106759999999999855e-02 1.164225125833329955e+01 1.590423455000000041e+00 +9.573710000000000553e-02 9.248378555000000389e+00 1.453449206666669991e+00 +1.004952999999999957e-01 1.082198768499999986e+01 1.296534581666670016e+00 +1.052397000000000055e-01 9.754544071666670035e+00 1.192202876666669908e+00 +1.099886000000000058e-01 9.770793396666670461e+00 1.074958141666670031e+00 +1.148422000000000054e-01 9.667035006666669261e+00 9.803857316666669819e-01 +1.197769999999999946e-01 8.381104240000000871e+00 8.964596183333329860e-01 +1.247698000000000002e-01 7.884219706666669936e+00 8.320707283333329540e-01 +1.298370000000000080e-01 8.175124921666670375e+00 7.497416000000000080e-01 +1.349077999999999944e-01 7.421197476666669957e+00 7.207336583333330271e-01 +1.399244000000000043e-01 8.545903931666670061e+00 6.648524133333330033e-01 +1.450733999999999913e-01 7.172791390000000433e+00 6.314494133333330428e-01 +1.502658000000000049e-01 6.110795001666669890e+00 5.924928833333330536e-01 +1.556062000000000001e-01 7.011488443333329990e+00 5.430119199999999813e-01 +1.572456999999999883e-01 7.121472013333329798e+00 1.740741033333330079e-01 +1.609471000000000096e-01 7.438929439999999893e+00 5.263131250000000483e-01 +1.661874999999999880e-01 6.540084590000000198e+00 4.946959550000000205e-01 +1.706292000000000086e-01 6.367655253333330378e+00 1.455746566666669961e-01 +1.715615000000000057e-01 6.476111691666670112e+00 4.707941449999999972e-01 +1.771270000000000067e-01 5.720913878333329983e+00 4.355610400000000104e-01 +1.827315999999999940e-01 6.117868691666670244e+00 4.186226383333330192e-01 +1.842428999999999872e-01 5.857041180000000402e+00 1.254778649999999940e-01 +1.883173999999999959e-01 5.849837826666670182e+00 4.069392433333329784e-01 +1.939794999999999991e-01 5.447672994999999574e+00 3.927526633333329742e-01 +1.982414999999999872e-01 5.391186461666669594e+00 1.085076650000000031e-01 +1.997663000000000078e-01 5.394637556666670442e+00 3.662608233333329855e-01 +2.055810999999999888e-01 5.784545713333329786e+00 3.603836316666669815e-01 +2.114655000000000007e-01 4.574601945000000391e+00 3.312713700000000094e-01 +2.120113000000000136e-01 5.084515549999999884e+00 9.927742500000000248e-02 +2.161903000000000019e-01 4.516358434999999893e+00 4.168106166666670220e-01 +2.259823999999999999e-01 4.782964421666670241e+00 8.791053666666670541e-02 +2.397845000000000115e-01 4.501217386666669817e+00 8.259429166666669431e-02 +2.537096999999999825e-01 4.179436571666670375e+00 7.562756833333329765e-02 +2.676275000000000182e-01 3.979478888333329856e+00 6.987285499999999761e-02 +2.817857999999999752e-01 3.702226940000000077e+00 6.482911333333329917e-02 +2.956398000000000081e-01 3.605992423333329810e+00 6.203536333333330155e-02 +3.099044000000000243e-01 3.377018353333329781e+00 5.670042833333330257e-02 +3.240645999999999805e-01 3.095807218333329836e+00 5.498817999999999762e-02 +3.388027000000000122e-01 2.841171323333330001e+00 4.897826333333329951e-02 +3.539140000000000064e-01 2.795649415000000193e+00 4.845330666666670255e-02 +3.687090000000000090e-01 2.622854690000000044e+00 4.475559833333329907e-02 +3.839813000000000254e-01 2.527809641666669993e+00 4.273165666666670082e-02 +3.988975000000000160e-01 2.232305030000000023e+00 4.094381166666669764e-02 +4.106734000000000218e-01 2.129986383333330124e+00 4.154637366666669857e-02 +4.136514000000000024e-01 2.084593566666669950e+00 3.891198166666669928e-02 +4.290628000000000219e-01 1.988620215000000080e+00 3.594587333333330165e-02 +4.295516999999999808e-01 1.964928603499999982e+00 3.519640899999999795e-02 +4.446014999999999828e-01 1.750614441666670018e+00 3.497378000000000292e-02 +4.466716000000000020e-01 1.709819834666669980e+00 3.112847050000000157e-02 +4.603325999999999807e-01 1.676685876666669905e+00 3.265084166666670090e-02 +4.685437000000000074e-01 1.638395569999999912e+00 3.101072850000000103e-02 +4.764860000000000206e-01 1.575855291666669933e+00 3.097528500000000171e-02 +4.834083000000000130e-01 1.443926158166670026e+00 2.913516383333330032e-02 +4.923204999999999942e-01 1.443778989999999984e+00 3.072023333333330150e-02 +5.046621000000000024e-01 1.382474827333330047e+00 2.709115466666670025e-02 +5.082708000000000226e-01 1.368563626666670086e+00 2.832584499999999880e-02 +5.230728999999999518e-01 1.117650907000000027e+00 2.515010599999999846e-02 +5.249162000000000550e-01 1.271594368333329950e+00 2.652784333333330080e-02 +5.416775999999999813e-01 1.132208539999999930e+00 2.601087833333329963e-02 +5.444001000000000534e-01 1.080469498666670081e+00 2.559191900000000117e-02 +5.583067999999999920e-01 1.032465565000000085e+00 2.510816333333330125e-02 +5.612614999999999688e-01 1.012286258500000091e+00 2.336018449999999885e-02 +5.753956999999999544e-01 9.763539783333330391e-01 2.332539999999999961e-02 +5.834072999999999620e-01 8.965217239999999643e-01 2.303063099999999933e-02 +5.927398999999999862e-01 9.096560916666670549e-01 2.248173499999999922e-02 +6.005747000000000169e-01 8.250965611666669641e-01 2.186496933333329992e-02 +6.099058000000000535e-01 8.168162183333329551e-01 2.170944500000000082e-02 +6.218546000000000351e-01 7.414260915000000507e-01 2.121375600000000028e-02 +6.275003000000000108e-01 7.444278466666669480e-01 2.046154333333330064e-02 +6.412082999999999533e-01 6.366966366666669819e-01 1.883217216666669899e-02 +6.447154999999999969e-01 6.422991433333330447e-01 2.075286000000000144e-02 +6.655497999999999692e-01 5.866493959999999896e-01 1.815403650000000160e-02 +6.843702000000000396e-01 5.515869356666670553e-01 2.025276583333330063e-02 +7.058105999999999547e-01 4.475886111666669831e-01 1.701240433333330027e-02 +7.258693999999999980e-01 4.775394179999999933e-01 1.840697383333329828e-02 +7.490480000000000471e-01 4.047520649999999942e-01 1.505153366666669990e-02 +7.720086999999999922e-01 3.731668894999999875e-01 1.706827766666670076e-02 +7.920989999999999975e-01 3.681129878333330163e-01 1.507231049999999996e-02 +8.155097999999999514e-01 3.208610430000000124e-01 1.529166149999999953e-02 +8.364475000000000104e-01 2.962226738333330056e-01 1.456896033333330079e-02 +8.619377000000000288e-01 2.765821119999999911e-01 1.371801400000000060e-02 +8.846403999999999934e-01 2.799105873333330163e-01 1.446314666666670065e-02 +9.081462000000000145e-01 2.318482950000000098e-01 1.260493333333330065e-02 +9.334447000000000161e-01 2.175556724999999914e-01 1.357374916666670081e-02 +9.551832000000000100e-01 1.903255399999999875e-01 1.273357800000000060e-02 +9.812971999999999806e-01 1.841620115000000002e-01 1.195187833333329931e-02 +1.006807800000000030e+00 1.608915558333330054e-01 1.233432733333329930e-02 +1.030406600000000061e+00 1.533644885000000069e-01 1.172346633333330029e-02 +1.057784300000000011e+00 1.661925581666670038e-01 1.091501716666670035e-02 +1.084600999999999926e+00 1.630933589999999933e-01 1.128103283333329980e-02 +1.109902299999999897e+00 1.327154809999999963e-01 1.088576583333330031e-02 +1.137751600000000085e+00 1.179623709999999964e-01 1.042595833333329926e-02 +1.166036300000000026e+00 1.293750036666669878e-01 1.024355400000000020e-02 \ No newline at end of file diff --git a/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/11_2000_1340_10.csv b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/11_2000_1340_10.csv new file mode 100644 index 00000000..bfbf36dc --- /dev/null +++ b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/11_2000_1340_10.csv @@ -0,0 +1,105 @@ +3.625559999999999894e-02 4.452644707000000324e+01 1.628119149999999848e+01 +4.070349999999999663e-02 2.677295466333330154e+01 1.128515222166669929e+01 +4.511570000000000163e-02 2.350029203500000108e+01 8.338724138333329705e+00 +4.959680000000000338e-02 1.744413458166669884e+01 6.751264438333329565e+00 +5.416850000000000137e-02 9.909433441666669395e+00 5.280963783333329609e+00 +5.869510000000000005e-02 1.174714312999999954e+01 4.547423594999999708e+00 +6.315869999999999818e-02 1.312634901166670076e+01 3.725749208333330120e+00 +6.774130000000000429e-02 1.654730030333330149e+01 3.113655078333330106e+00 +7.239619999999999389e-02 7.792796271666669661e+00 2.672180411666670086e+00 +7.695009999999999351e-02 1.265325572666669984e+01 2.397233363333330036e+00 +8.167309999999999848e-02 6.069752713333330441e+00 1.971069876666670107e+00 +8.642469999999999319e-02 1.300927747833329917e+01 1.813635340000000040e+00 +9.109929999999999417e-02 9.757094948333330464e+00 1.573592551666670003e+00 +9.577040000000000552e-02 5.713518095000000407e+00 1.422093728333329921e+00 +1.005303000000000030e-01 7.313534973333330136e+00 1.265234731666670109e+00 +1.052763000000000032e-01 8.841184973333330532e+00 1.182077201666670074e+00 +1.100268999999999969e-01 7.404252198333329815e+00 1.051346621666670034e+00 +1.148821000000000009e-01 8.380590695000000423e+00 9.664303683333329564e-01 +1.198187000000000002e-01 7.413483668333330279e+00 8.855263516666670442e-01 +1.248133000000000020e-01 6.593134025000000342e+00 8.175650066666669824e-01 +1.298822000000000032e-01 6.731246111666670195e+00 7.333046399999999521e-01 +1.349548000000000136e-01 5.519098301666669926e+00 6.986229283333329487e-01 +1.399730999999999892e-01 7.581442331666670142e+00 6.528076566666669578e-01 +1.451239000000000001e-01 4.842304316666670161e+00 6.043489516666670225e-01 +1.503181000000000100e-01 5.135523888333329623e+00 5.802922499999999539e-01 +1.556604000000000043e-01 6.037988904999999740e+00 5.310080216666670516e-01 +1.573070000000000024e-01 6.020141886666669606e+00 1.700767950000000028e-01 +1.610031000000000101e-01 6.268605329999999753e+00 5.111983383333329467e-01 +1.662453999999999876e-01 5.536486726666669966e+00 4.815289200000000269e-01 +1.706957000000000058e-01 5.285752210000000062e+00 1.415798849999999887e-01 +1.716212000000000015e-01 5.016643598333329734e+00 4.522174083333330152e-01 +1.771887000000000045e-01 5.127789406666670047e+00 4.276572833333330270e-01 +1.827951999999999910e-01 5.311901800000000229e+00 4.077617766666670196e-01 +1.843146999999999980e-01 5.273006283333329769e+00 1.230909116666669967e-01 +1.883829999999999949e-01 4.028933333333330147e+00 3.832458766666669847e-01 +1.940469999999999973e-01 4.805727509999999647e+00 3.842635300000000198e-01 +1.983187999999999895e-01 4.783697818333330076e+00 1.059592133333329966e-01 +1.998358000000000079e-01 4.823453656666670142e+00 3.585670233333330126e-01 +2.056525999999999910e-01 4.659267076666670171e+00 3.454626916666669878e-01 +2.115392000000000106e-01 3.961550343333330115e+00 3.229926716666670083e-01 +2.120939000000000019e-01 4.350835990000000209e+00 9.606802500000000133e-02 +2.162656000000000023e-01 4.681036608333330129e+00 4.188060433333329891e-01 +2.260705000000000076e-01 4.224838659999999635e+00 8.541253666666670519e-02 +2.398779000000000050e-01 3.935926101666670007e+00 7.996145500000000073e-02 +2.538084999999999924e-01 3.694058728333330155e+00 7.335489833333329324e-02 +2.677318000000000198e-01 3.684019681666669932e+00 6.840629833333329579e-02 +2.818956000000000239e-01 3.452043701666669850e+00 6.355296333333329550e-02 +2.957549999999999901e-01 3.354666878333329993e+00 6.073878666666669701e-02 +3.100250999999999979e-01 3.092418856666669935e+00 5.527531000000000111e-02 +3.241909000000000041e-01 2.870290291666670157e+00 5.380070499999999728e-02 +3.389346999999999777e-01 2.729991994999999783e+00 4.836916333333329820e-02 +3.540519999999999778e-01 2.658729508333330216e+00 4.771163500000000224e-02 +3.688526999999999778e-01 2.488934241666670211e+00 4.403299000000000102e-02 +3.841308999999999974e-01 2.412009111666669980e+00 4.209804999999999797e-02 +3.990528999999999882e-01 2.203036439999999985e+00 4.070905166666669711e-02 +4.105667000000000066e-01 1.996977714666670067e+00 4.083280150000000164e-02 +4.138126999999999778e-01 2.043262161666670185e+00 3.862582833333329940e-02 +4.292300000000000004e-01 1.982205959999999934e+00 3.583282166666670182e-02 +4.294401999999999942e-01 1.834877223666669943e+00 3.458454216666669717e-02 +4.447747000000000228e-01 1.712660204999999936e+00 3.471672166666670001e-02 +4.465555999999999970e-01 1.615437673500000004e+00 3.068944266666669834e-02 +4.605119999999999769e-01 1.681440621666669966e+00 3.260043333333329657e-02 +4.684220000000000050e-01 1.529958281999999947e+00 3.049975383333329917e-02 +4.766716999999999760e-01 1.561241171666670091e+00 3.083583000000000157e-02 +4.832827000000000095e-01 1.369049060333330070e+00 2.875037449999999842e-02 +4.925123000000000140e-01 1.406416503333330015e+00 3.046807833333330107e-02 +5.045309999999999517e-01 1.316827885166669931e+00 2.677764066666669940e-02 +5.084689000000000014e-01 1.339987331666669945e+00 2.812536666666669988e-02 +5.229369999999999852e-01 1.082862526333330022e+00 2.496820599999999973e-02 +5.251208000000000542e-01 1.259172833333330077e+00 2.640966666666669932e-02 +5.418887000000000009e-01 1.134540908333329989e+00 2.596733000000000027e-02 +5.442586999999999842e-01 1.026980480500000015e+00 2.532101549999999854e-02 +5.585244000000000320e-01 1.057657990000000048e+00 2.518052499999999874e-02 +5.611156999999999950e-01 9.343203683333329845e-01 2.299225916666669881e-02 +5.756198999999999621e-01 9.598931866666670087e-01 2.319752666666670057e-02 +5.832557000000000436e-01 8.443178690000000541e-01 2.277228483333329848e-02 +5.929708999999999675e-01 9.115965033333329748e-01 2.244499833333329919e-02 +6.004186999999999719e-01 7.992612106666669991e-01 2.172028233333329894e-02 +6.101434999999999498e-01 8.528685350000000387e-01 2.184797833333329900e-02 +6.216930999999999985e-01 7.049236858333329803e-01 2.102741616666670144e-02 +6.277447999999999917e-01 7.853513183333330483e-01 2.062051499999999898e-02 +6.410417000000000476e-01 6.132204243333330140e-01 1.871036433333329863e-02 +6.449667999999999513e-01 7.011444599999999694e-01 2.101788333333329956e-02 +6.653769000000000489e-01 5.661603698333329548e-01 1.804829366666670099e-02 +6.841924999999999812e-01 5.085138883333329973e-01 2.002898349999999994e-02 +7.056272000000000100e-01 4.352650703333330040e-01 1.694186849999999855e-02 +7.256808000000000147e-01 4.355121908333329794e-01 1.820155999999999857e-02 +7.488534000000000024e-01 3.927147446666670039e-01 1.498729066666669961e-02 +7.718082000000000553e-01 3.535320510000000138e-01 1.696024266666670138e-02 +7.918933000000000222e-01 3.527103021666669891e-01 1.499266833333330086e-02 +8.152979999999999672e-01 3.240461856666669860e-01 1.528614783333329986e-02 +8.362302999999999820e-01 2.791947966666670222e-01 1.448550166666670060e-02 +8.617137999999999742e-01 2.463675190000000070e-01 1.359101549999999943e-02 +8.844106000000000467e-01 2.696912470000000228e-01 1.440389033333329925e-02 +9.079102999999999479e-01 2.246905299999999994e-01 1.256477649999999946e-02 +9.332021999999999817e-01 2.085629360000000043e-01 1.352310049999999944e-02 +9.549351000000000367e-01 1.738771078333329889e-01 1.265774100000000013e-02 +9.810423000000000338e-01 1.708308721666670082e-01 1.189189666666670003e-02 +1.006546299999999894e+00 1.505248680000000061e-01 1.228292216666669948e-02 +1.030138999999999916e+00 1.537520601666670095e-01 1.171224066666669977e-02 +1.057509599999999939e+00 1.534308791666670058e-01 1.086003916666669969e-02 +1.084319299999999986e+00 1.338797798333329903e-01 1.116524300000000004e-02 +1.109614000000000100e+00 1.189064603333330056e-01 1.082743033333329920e-02 +1.137456000000000023e+00 1.107916548333330031e-01 1.039160416666670000e-02 +1.165733499999999978e+00 1.125385351666669947e-01 1.017680349999999963e-02 \ No newline at end of file diff --git a/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/12_3000_1340_10.csv b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/12_3000_1340_10.csv new file mode 100644 index 00000000..9ae1c5f3 --- /dev/null +++ b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/12_3000_1340_10.csv @@ -0,0 +1,105 @@ +3.624610000000000332e-02 5.667029333499999666e+01 1.635708034166669833e+01 +4.069289999999999713e-02 2.663486052833329865e+01 1.131616966500000032e+01 +4.510390000000000232e-02 2.091145430999999988e+01 8.353784624999999409e+00 +4.958379999999999732e-02 1.655762528666669908e+01 6.766916158333329712e+00 +5.415430000000000244e-02 7.405398096666670149e+00 5.285856098333329811e+00 +5.867970000000000130e-02 1.609157327499999823e+01 4.579871853333330023e+00 +6.314219999999999555e-02 2.074624494500000083e+01 3.774208823333330187e+00 +6.772359999999999491e-02 1.622769223499999924e+01 3.121007619999999871e+00 +7.237720000000000264e-02 8.100976058333330343e+00 2.681633823333330113e+00 +7.692999999999999838e-02 7.297140411666670268e+00 2.370097993333330155e+00 +8.165179999999999660e-02 8.662000215000000836e+00 1.993900453333329992e+00 +8.640209999999999557e-02 1.195176863500000053e+01 1.811378463333330080e+00 +9.107550000000000368e-02 1.017929826333329935e+01 1.581605778333329937e+00 +9.574539999999999440e-02 8.299761948333330253e+00 1.447854706666670044e+00 +1.005039999999999961e-01 7.184010728333330320e+00 1.267957188333330043e+00 +1.052488000000000035e-01 7.705754226666670093e+00 1.175315996666669971e+00 +1.099981000000000014e-01 6.940137736666669888e+00 1.050239939999999983e+00 +1.148520999999999986e-01 7.227232895000000212e+00 9.582211533333330200e-01 +1.197872999999999993e-01 6.058664864999999899e+00 8.747766333333329980e-01 +1.247806000000000054e-01 5.235616768333329674e+00 8.060124016666669888e-01 +1.298481999999999970e-01 6.895559143333329644e+00 7.374701233333329498e-01 +1.349194999999999978e-01 5.596115646666669718e+00 7.017485283333330104e-01 +1.399364999999999915e-01 7.271002904999999572e+00 6.515097483333329720e-01 +1.450858999999999899e-01 5.001139740000000167e+00 6.081432883333329764e-01 +1.502787999999999902e-01 5.397041578333330314e+00 5.852662783333330010e-01 +1.556196999999999997e-01 5.201047356666670396e+00 5.230793849999999523e-01 +1.572660999999999920e-01 5.527525920000000426e+00 1.689539216666670063e-01 +1.609609999999999930e-01 6.050507654999999652e+00 5.103277550000000495e-01 +1.662019000000000135e-01 5.215723743333329665e+00 4.792083599999999999e-01 +1.706513000000000058e-01 5.031433279999999897e+00 1.411615750000000113e-01 +1.715762999999999872e-01 4.986483428333330359e+00 4.534299233333329848e-01 +1.771423000000000025e-01 5.106750501666669884e+00 4.289189133333329851e-01 +1.827474000000000043e-01 5.398633823333329751e+00 4.103611200000000236e-01 +1.842668000000000084e-01 4.914610723333329823e+00 1.221897900000000065e-01 +1.883336999999999928e-01 4.776135458333330419e+00 3.942386333333329773e-01 +1.939963000000000104e-01 4.310256373333330338e+00 3.794851616666670147e-01 +1.982673000000000074e-01 4.375084450000000125e+00 1.047416733333329936e-01 +1.997836000000000056e-01 4.425991520000000179e+00 3.548793383333330165e-01 +2.055989000000000011e-01 4.542295369999999721e+00 3.452195166666670034e-01 +2.114837999999999996e-01 4.392925553333330235e+00 3.296180283333329797e-01 +2.120387999999999995e-01 4.102498828333329683e+00 9.538819833333339604e-02 +2.162090000000000123e-01 4.067448908333330060e+00 4.099168333333330083e-01 +2.260118000000000127e-01 3.879140883333330070e+00 8.427184499999999800e-02 +2.398155999999999899e-01 3.717842756666669857e+00 7.930888666666670306e-02 +2.537425999999999848e-01 3.531988756666669893e+00 7.293178499999999898e-02 +2.676623000000000197e-01 3.501111030000000124e+00 6.786378666666670334e-02 +2.818223999999999729e-01 3.329498083333330083e+00 6.325724833333329356e-02 +2.956782000000000021e-01 3.079447669999999970e+00 5.969948499999999658e-02 +3.099446000000000145e-01 2.946954811666670171e+00 5.483356999999999815e-02 +3.241067000000000253e-01 2.705831398333330196e+00 5.323661166666669720e-02 +3.388467000000000007e-01 2.601996516666670090e+00 4.799289000000000333e-02 +3.539599999999999969e-01 2.517872383333330077e+00 4.725612833333329987e-02 +3.687568999999999986e-01 2.374362781666670141e+00 4.368410333333330037e-02 +3.840311000000000141e-01 2.195996148333330122e+00 4.125977166666670165e-02 +3.989493000000000067e-01 2.109030429999999789e+00 4.042385333333330111e-02 +4.107986000000000137e-01 1.888483604166669938e+00 4.043749766666669687e-02 +4.137051999999999952e-01 1.969810571666670063e+00 3.843483833333329741e-02 +4.291185000000000138e-01 1.859797011666669997e+00 3.539587999999999762e-02 +4.296826999999999730e-01 1.762954635166670059e+00 3.439219566666670141e-02 +4.446591999999999767e-01 1.624618943333330012e+00 3.443567000000000156e-02 +4.468077999999999772e-01 1.588049316333330019e+00 3.067751716666669917e-02 +4.603923999999999794e-01 1.585295316666669896e+00 3.227559333333329672e-02 +4.686866000000000088e-01 1.509168766666669992e+00 3.051112400000000058e-02 +4.765479000000000243e-01 1.429500135000000061e+00 3.031986666666669841e-02 +4.835556999999999772e-01 1.309360340500000053e+00 2.857159116666670162e-02 +4.923843999999999999e-01 1.363123808333329912e+00 3.037624166666669928e-02 +5.048160000000000425e-01 1.296483956833329954e+00 2.677855866666669846e-02 +5.083368000000000331e-01 1.254868451666669937e+00 2.782200166666669999e-02 +5.232324000000000419e-01 1.028253859833329953e+00 2.481625150000000071e-02 +5.249844000000000177e-01 1.223231273333329927e+00 2.634227000000000096e-02 +5.417480000000000073e-01 1.058531658333329961e+00 2.569255333333329838e-02 +5.445661000000000529e-01 1.022632484000000064e+00 2.537817800000000124e-02 +5.583793000000000228e-01 1.009770163333330029e+00 2.504100833333329848e-02 +5.614325999999999484e-01 9.522996788333329965e-01 2.313905700000000107e-02 +5.754704000000000486e-01 9.158985283333339611e-01 2.307270666666669939e-02 +5.835850999999999678e-01 8.544030651666669751e-01 2.288279616666670166e-02 +5.928168000000000326e-01 8.553651300000000290e-01 2.224781666666670113e-02 +6.007578000000000085e-01 7.747371155000000176e-01 2.167619850000000042e-02 +6.099849999999999994e-01 7.987110383333330121e-01 2.165553833333330042e-02 +6.220442000000000471e-01 6.615610463333330138e-01 2.089781950000000124e-02 +6.275817000000000201e-01 7.150054516666669580e-01 2.035196333333329916e-02 +6.414037999999999684e-01 6.088626698333330367e-01 1.874337833333329997e-02 +6.447992000000000168e-01 6.630811316666670452e-01 2.089752166666669977e-02 +6.657526999999999751e-01 5.716572943333330103e-01 1.811807299999999843e-02 +6.845788999999999902e-01 5.092866856666670161e-01 2.008440916666669879e-02 +7.060256999999999783e-01 4.113589818333330261e-01 1.688841199999999848e-02 +7.260906999999999778e-01 4.258370573333329911e-01 1.820484666666670123e-02 +7.492763000000000062e-01 3.814062795000000006e-01 1.498164383333329928e-02 +7.722440999999999889e-01 3.582434820000000020e-01 1.702263550000000097e-02 +7.923405000000000031e-01 3.290160251666670033e-01 1.493438950000000078e-02 +8.157583999999999946e-01 3.190799841666669967e-01 1.530228649999999975e-02 +8.367025000000000157e-01 2.762952625000000273e-01 1.450753649999999943e-02 +8.622005000000000363e-01 2.544186793333330088e-01 1.365137966666669922e-02 +8.849101000000000328e-01 2.424038613333329983e-01 1.432358516666669933e-02 +9.084231000000000389e-01 2.172930054999999971e-01 1.256612799999999933e-02 +9.337292000000000369e-01 2.202721591666670087e-01 1.359930866666670020e-02 +9.554743999999999460e-01 1.777866800000000025e-01 1.269970833333330072e-02 +9.815962999999999772e-01 1.676419470000000134e-01 1.190602366666669923e-02 +1.007114700000000029e+00 1.595580418333329975e-01 1.234223233333330005e-02 +1.030720700000000045e+00 1.541775503333329966e-01 1.173861049999999975e-02 +1.058106800000000014e+00 1.554276226666669869e-01 1.088984299999999975e-02 +1.084931700000000054e+00 1.440364893333329899e-01 1.122487333333329999e-02 +1.110240600000000022e+00 1.198653424999999995e-01 1.085281416666670010e-02 +1.138098400000000066e+00 1.211473946666670048e-01 1.044690799999999954e-02 +1.166391799999999979e+00 1.189307901666669942e-01 1.021903716666669980e-02 \ No newline at end of file diff --git a/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/13_8000_1340_10.csv b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/13_8000_1340_10.csv new file mode 100644 index 00000000..ee5f13e0 --- /dev/null +++ b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/13_8000_1340_10.csv @@ -0,0 +1,105 @@ +3.625239999999999713e-02 6.302187949999999717e+01 1.635904319500000170e+01 +4.070000000000000007e-02 2.785521429499999968e+01 1.130956581166669928e+01 +4.511180000000000051e-02 1.008215536333329965e+01 8.312546360000000689e+00 +4.959249999999999770e-02 1.581932412333330085e+01 6.758513281666670203e+00 +5.416379999999999806e-02 7.750620351666669627e+00 5.282679641666669923e+00 +5.868999999999999911e-02 1.257659237500000060e+01 4.560014873333329888e+00 +6.315320000000000655e-02 1.891331758666670027e+01 3.761777425000000008e+00 +6.773540000000000116e-02 1.328150150333330082e+01 3.102479755000000061e+00 +7.238989999999999314e-02 9.795755306666670492e+00 2.689289660000000026e+00 +7.694339999999999513e-02 1.021541878833330053e+01 2.386645821666669942e+00 +8.166600000000000248e-02 7.264236756666670125e+00 1.982897978333330036e+00 +8.641719999999999957e-02 9.727612021666670827e+00 1.793249476666670006e+00 +9.109140000000000292e-02 9.940133039999999198e+00 1.578285125000000066e+00 +9.576210000000000278e-02 5.869161616666669801e+00 1.426304660000000002e+00 +1.005216000000000026e-01 6.271050506666670188e+00 1.258989316666669911e+00 +1.052672000000000052e-01 4.575212496666670070e+00 1.144957958333330028e+00 +1.100174000000000013e-01 6.675946670000000083e+00 1.046739891666669919e+00 +1.148721999999999938e-01 4.569861300000000348e+00 9.307519933333330275e-01 +1.198083000000000065e-01 4.073412075000000243e+00 8.536948699999999945e-01 +1.248023999999999939e-01 5.517398765000000260e+00 8.081908083333330106e-01 +1.298709000000000113e-01 6.055929844999999645e+00 7.277152516666669513e-01 +1.349431000000000103e-01 4.214499321666670184e+00 6.853886750000000028e-01 +1.399610000000000021e-01 4.711855326666669619e+00 6.206856016666669751e-01 +1.451112999999999986e-01 4.280988370000000209e+00 5.992362466666669718e-01 +1.503050999999999970e-01 3.350940026666669791e+00 5.604130616666670450e-01 +1.556469000000000047e-01 4.037392881666669986e+00 5.086757983333329847e-01 +1.572660999999999920e-01 4.116049464999999685e+00 1.639383783333329958e-01 +1.609891999999999990e-01 4.767404383333330387e+00 4.936060199999999787e-01 +1.662309999999999899e-01 4.092517224999999925e+00 4.643677516666669947e-01 +1.706513000000000058e-01 3.775210913333329810e+00 1.365719850000000068e-01 +1.716062999999999894e-01 4.067082121666669714e+00 4.413313999999999848e-01 +1.771733000000000058e-01 3.397890750000000182e+00 4.070909349999999871e-01 +1.827794000000000085e-01 4.061001236666670344e+00 3.925402433333329832e-01 +1.842668000000000084e-01 3.799252254999999856e+00 1.178310383333329991e-01 +1.883665999999999952e-01 3.208655455000000156e+00 3.733191000000000148e-01 +1.940302000000000138e-01 3.206445976666670195e+00 3.650098783333329822e-01 +1.982673000000000074e-01 3.425276903333330125e+00 1.008325449999999956e-01 +1.998185000000000100e-01 3.354815260000000077e+00 3.406662516666669749e-01 +2.056348000000000065e-01 3.721227830000000125e+00 3.339491649999999923e-01 +2.115208000000000088e-01 3.612948971666670062e+00 3.193275150000000062e-01 +2.120387999999999995e-01 3.205896688333329969e+00 9.145261000000000362e-02 +2.162467999999999890e-01 3.645113248333330169e+00 4.021025750000000176e-01 +2.260118000000000127e-01 3.104582965000000083e+00 8.081593333333329798e-02 +2.398155999999999899e-01 3.037513673333330111e+00 7.613482000000000582e-02 +2.537425999999999848e-01 2.836682165000000033e+00 6.969811833333329487e-02 +2.676623000000000197e-01 2.819590748333329788e+00 6.462849833333329796e-02 +2.818223999999999729e-01 2.721404538333330070e+00 6.033223166666670106e-02 +2.956782000000000021e-01 2.736251186666669888e+00 5.799425166666669768e-02 +3.099446000000000145e-01 2.543946483333329844e+00 5.286760000000000070e-02 +3.241067000000000253e-01 2.287160831666669836e+00 5.112056499999999976e-02 +3.388467000000000007e-01 2.288673078333329780e+00 4.649771999999999933e-02 +3.539599999999999969e-01 2.199149091666670053e+00 4.568711999999999773e-02 +3.687568999999999986e-01 2.068072668333329922e+00 4.217328666666669834e-02 +3.840311000000000141e-01 1.969221529999999998e+00 4.012745166666670249e-02 +3.989493000000000067e-01 1.908232941666669902e+00 3.938963333333329875e-02 +4.107629999999999892e-01 1.727702871333330004e+00 3.958386533333330126e-02 +4.137051999999999952e-01 1.726875600000000066e+00 3.720425833333330240e-02 +4.291185000000000138e-01 1.581841901666670047e+00 3.398506666666670228e-02 +4.296455000000000135e-01 1.691278518333330094e+00 3.404221949999999830e-02 +4.446591999999999767e-01 1.488087740000000103e+00 3.374035333333329917e-02 +4.467690999999999746e-01 1.413946998166669911e+00 2.991126200000000096e-02 +4.603923999999999794e-01 1.391984139999999925e+00 3.130147333333330173e-02 +4.686460000000000070e-01 1.392956528666670080e+00 2.997462383333330052e-02 +4.765479000000000243e-01 1.301627080000000047e+00 2.965699000000000113e-02 +4.835137999999999936e-01 1.193676802499999967e+00 2.800566683333330018e-02 +4.923843999999999999e-01 1.242278698333330045e+00 2.973970833333329858e-02 +5.047722000000000042e-01 1.149708217833329993e+00 2.613050733333329920e-02 +5.083368000000000331e-01 1.161310218333329924e+00 2.733334000000000111e-02 +5.231871000000000160e-01 9.503032668333329935e-01 2.446244750000000148e-02 +5.249844000000000177e-01 1.102167341666669964e+00 2.571981833333330039e-02 +5.417480000000000073e-01 9.754589783333329489e-01 2.525416166666670167e-02 +5.445189000000000279e-01 9.366835949999999800e-01 2.497178450000000008e-02 +5.583793000000000228e-01 8.831162099999999571e-01 2.438054666666670048e-02 +5.613839000000000468e-01 8.824506341666670250e-01 2.281443349999999828e-02 +5.754704000000000486e-01 8.100822066666669707e-01 2.253018666666670167e-02 +5.835346000000000144e-01 7.776236106666669645e-01 2.252483566666670101e-02 +5.928168000000000326e-01 7.764561633333330049e-01 2.182597999999999830e-02 +6.007057000000000091e-01 6.983251131666670108e-01 2.131793766666669962e-02 +6.099849999999999994e-01 7.099790583333329685e-01 2.117932666666669933e-02 +6.219902999999999960e-01 6.466382168333330016e-01 2.081252199999999997e-02 +6.275817000000000201e-01 6.603706066666670260e-01 2.006477833333330033e-02 +6.413482000000000349e-01 5.710830485000000234e-01 1.856948533333329862e-02 +6.447992000000000168e-01 5.732715750000000332e-01 2.040912500000000018e-02 +6.656950000000000367e-01 5.203726888333329859e-01 1.789587166666670170e-02 +6.845196000000000058e-01 4.749653629999999738e-01 1.990720683333329841e-02 +7.059646000000000532e-01 3.822856073333329996e-01 1.675657983333329881e-02 +7.260278000000000009e-01 3.886542691666670102e-01 1.802539466666670115e-02 +7.492113999999999718e-01 3.564554191666670091e-01 1.487377883333330063e-02 +7.721772000000000080e-01 3.333074428333330230e-01 1.689668533333330003e-02 +7.922717999999999705e-01 3.187338046666670088e-01 1.488059016666670037e-02 +8.156877999999999629e-01 3.010368924999999862e-01 1.521491216666670011e-02 +8.366301000000000432e-01 2.406595508333330136e-01 1.435568400000000050e-02 +8.621258000000000532e-01 2.223576200000000058e-01 1.352156766666669924e-02 +8.848333999999999921e-01 2.207387865000000060e-01 1.422139816666669922e-02 +9.083444000000000518e-01 2.134049949999999862e-01 1.254214099999999971e-02 +9.336484000000000449e-01 1.885945263333330124e-01 1.346345033333330027e-02 +9.553916000000000075e-01 1.698076676666669949e-01 1.265998783333329922e-02 +9.815112999999999754e-01 1.668850050000000029e-01 1.189438716666670059e-02 +1.007027499999999964e+00 1.441018685000000077e-01 1.227580483333329947e-02 +1.030631400000000086e+00 1.406578583333329968e-01 1.168073399999999991e-02 +1.058015099999999986e+00 1.399035711666669901e-01 1.082874783333329961e-02 +1.084837700000000016e+00 1.486492919999999884e-01 1.123307866666669978e-02 +1.110144500000000090e+00 1.008698655000000027e-01 1.077987283333329931e-02 +1.137999800000000006e+00 1.027075286666670056e-01 1.037853683333330064e-02 +1.166290800000000072e+00 1.157098001666670012e-01 1.020088966666670045e-02 \ No newline at end of file diff --git a/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/1_0_1340_10.csv b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/1_0_1340_10.csv new file mode 100644 index 00000000..1c5c261e --- /dev/null +++ b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/1_0_1340_10.csv @@ -0,0 +1,105 @@ +3.625239999999999713e-02 2.859171986188329811e+03 2.282233352000000082e+01 +4.070000000000000007e-02 1.037695935384999984e+03 1.390765389166669941e+01 +4.511180000000000051e-02 4.617414862783330136e+02 9.644274246666670436e+00 +4.959249999999999770e-02 2.830472148050000101e+02 7.651757100000000200e+00 +5.416379999999999806e-02 1.737180786733329967e+02 5.901133003333329796e+00 +5.868999999999999911e-02 1.284318533800000068e+02 5.055296333333330061e+00 +6.315320000000000655e-02 1.083304533916670067e+02 4.179970964999999872e+00 +6.773540000000000116e-02 8.164665742500000079e+01 3.449109084999999908e+00 +7.238989999999999314e-02 6.842710629666669320e+01 3.015091889999999886e+00 +7.694339999999999513e-02 6.503887503333329789e+01 2.712566869999999852e+00 +8.166600000000000248e-02 5.768616606333330310e+01 2.292022508333329878e+00 +8.641719999999999957e-02 4.726633163000000337e+01 2.052834279999999900e+00 +9.109140000000000292e-02 4.170182763166670270e+01 1.806841736666670029e+00 +9.576210000000000278e-02 3.278791741500000256e+01 1.635979810000000034e+00 +1.005216000000000026e-01 3.137094696333329935e+01 1.457612156666669989e+00 +1.052672000000000052e-01 2.858316747499999977e+01 1.352856671666669897e+00 +1.100174000000000013e-01 2.685582243333330155e+01 1.222659363333330029e+00 +1.148721999999999938e-01 2.463809722666670154e+01 1.115379456666669933e+00 +1.198083000000000065e-01 1.979913578000000030e+01 1.002652691666670037e+00 +1.248023999999999939e-01 1.884005529499999909e+01 9.371682333333329895e-01 +1.298709000000000113e-01 2.011378014833330141e+01 8.660532583333330203e-01 +1.349431000000000103e-01 1.548196221999999977e+01 8.040137483333329449e-01 +1.399610000000000021e-01 1.553817986500000004e+01 7.396018783333330182e-01 +1.451112999999999986e-01 1.380290046833330031e+01 7.008050850000000498e-01 +1.503050999999999970e-01 1.290957537833329916e+01 6.659874700000000258e-01 +1.556469000000000047e-01 1.188963399333329995e+01 5.958023666666669715e-01 +1.572456999999999883e-01 1.305280697499999931e+01 1.930089849999999940e-01 +1.609891999999999990e-01 1.134609165166670053e+01 5.714861916666670316e-01 +1.662309999999999899e-01 1.103891432833330022e+01 5.468199316666669807e-01 +1.706292000000000086e-01 1.121912493499999997e+01 1.614639983333329976e-01 +1.716062999999999894e-01 1.098087770333330049e+01 5.221648316666670508e-01 +1.771733000000000058e-01 9.846715853333330770e+00 4.826313150000000052e-01 +1.827794000000000085e-01 1.030369242166669963e+01 4.679303400000000002e-01 +1.842428999999999872e-01 9.629214631666670243e+00 1.387244099999999924e-01 +1.883665999999999952e-01 9.108624106666670883e+00 4.454082083333330000e-01 +1.940302000000000138e-01 8.056377178333329780e+00 4.232006716666670276e-01 +1.982414999999999872e-01 8.605072379999999299e+00 1.202604099999999981e-01 +1.998185000000000100e-01 8.229729869999999892e+00 3.995894999999999864e-01 +2.056348000000000065e-01 8.070094290000000115e+00 3.879820366666669740e-01 +2.115208000000000088e-01 7.741634115000000094e+00 3.686955350000000187e-01 +2.120113000000000136e-01 7.664225765000000301e+00 1.093220133333329958e-01 +2.162467999999999890e-01 7.363320653333330412e+00 4.626756216666669808e-01 +2.259823999999999999e-01 6.848802841666669750e+00 9.609777999999999376e-02 +2.397845000000000115e-01 6.337189861666669977e+00 9.020952999999999611e-02 +2.537096999999999825e-01 5.738702153333330003e+00 8.211384833333329469e-02 +2.676275000000000182e-01 5.346591223333329701e+00 7.572218666666670484e-02 +2.817857999999999752e-01 5.009933698333330021e+00 7.051459333333330581e-02 +2.956398000000000081e-01 4.597692046666669974e+00 6.646157833333329878e-02 +3.099044000000000243e-01 4.193400725000000051e+00 6.031815000000000093e-02 +3.240645999999999805e-01 3.804645918333330101e+00 5.825413166666670167e-02 +3.388027000000000122e-01 3.560888523333329836e+00 5.213344166666669666e-02 +3.539140000000000064e-01 3.375211385000000064e+00 5.107971833333330158e-02 +3.687090000000000090e-01 3.188264295000000192e+00 4.732182333333329743e-02 +3.839813000000000254e-01 2.892990108333330035e+00 4.441047333333329739e-02 +3.988975000000000160e-01 2.640608908333330174e+00 4.290645666666669661e-02 +4.106379000000000001e-01 2.501690660833329805e+00 4.331324033333330131e-02 +4.136514000000000024e-01 2.477246653333330162e+00 4.077528500000000139e-02 +4.290628000000000219e-01 2.358423203333329887e+00 3.769917166666669761e-02 +4.295145000000000213e-01 2.266923564999999918e+00 3.645828216666670285e-02 +4.446014999999999828e-01 2.023246533333329822e+00 3.627248666666670063e-02 +4.466328999999999994e-01 2.032174506166669836e+00 3.243397416666669864e-02 +4.603325999999999807e-01 1.951935533333329920e+00 3.395880333333330114e-02 +4.685032000000000085e-01 1.797322475499999905e+00 3.168831166666669780e-02 +4.764860000000000206e-01 1.814704324999999896e+00 3.212894000000000166e-02 +4.833663999999999739e-01 1.722621554333330085e+00 3.037123383333329915e-02 +4.923204999999999942e-01 1.658358661666669898e+00 3.178619500000000320e-02 +5.046184000000000225e-01 1.539108265666669917e+00 2.774163983333330016e-02 +5.082708000000000226e-01 1.547966548333330028e+00 2.920229666666670013e-02 +5.230276000000000369e-01 1.345057283333330078e+00 2.608127533333329945e-02 +5.249162000000000550e-01 1.417396718333330030e+00 2.724554333333329900e-02 +5.416775999999999813e-01 1.270799306666670070e+00 2.670250999999999875e-02 +5.443529999999999758e-01 1.250087865666670073e+00 2.633086933333329827e-02 +5.583067999999999920e-01 1.165305416666670091e+00 2.577655499999999961e-02 +5.612129000000000145e-01 1.147836548999999984e+00 2.393862083333329893e-02 +5.753956999999999544e-01 1.115183805000000028e+00 2.400332833333329932e-02 +5.833568000000000087e-01 1.044019996999999922e+00 2.366755300000000090e-02 +5.927398999999999862e-01 1.004017331666670065e+00 2.296542000000000028e-02 +6.005226999999999649e-01 9.482225546666670501e-01 2.240195116666670108e-02 +6.099058000000000535e-01 9.388455449999999480e-01 2.233828499999999939e-02 +6.218008000000000424e-01 8.511757086666670302e-01 2.168908733333330119e-02 +6.275003000000000108e-01 8.553386066666669452e-01 2.101305833333329959e-02 +6.411527999999999672e-01 7.761240236666669956e-01 1.940277716666670080e-02 +6.447154999999999969e-01 7.270576883333329521e-01 2.120466833333330137e-02 +6.654921999999999782e-01 7.007768106666669716e-01 1.861042133333330045e-02 +6.843110000000000026e-01 6.466361541666669766e-01 2.069310833333330019e-02 +7.057493999999999712e-01 5.771519323333329510e-01 1.752841666666669906e-02 +7.258065000000000211e-01 5.204018098333329512e-01 1.860044799999999859e-02 +7.489831000000000127e-01 4.984340896666670240e-01 1.540761000000000040e-02 +7.719418000000000113e-01 4.436713720000000083e-01 1.738589999999999927e-02 +7.920304000000000233e-01 4.316635041666669892e-01 1.532890283333330009e-02 +8.154392000000000307e-01 3.868879691666670118e-01 1.556857449999999969e-02 +8.363751000000000380e-01 3.670109628333330098e-01 1.484866983333330004e-02 +8.618630000000000457e-01 3.329731463333330255e-01 1.392995016666669951e-02 +8.845638000000000112e-01 3.302565831666670060e-01 1.467645416666669977e-02 +9.080675999999999748e-01 2.757098590000000016e-01 1.276754683333329934e-02 +9.333637999999999657e-01 2.804865558333329845e-01 1.382540016666669938e-02 +9.551005000000000189e-01 2.398962168333330092e-01 1.292395533333329932e-02 +9.812121999999999788e-01 2.360268241666670097e-01 1.213837400000000039e-02 +1.006720599999999965e+00 2.134167958333330062e-01 1.253179649999999930e-02 +1.030317399999999939e+00 2.102511210000000130e-01 1.193319533333330081e-02 +1.057692700000000041e+00 2.135461601666669984e-01 1.107935333333330032e-02 +1.084507099999999946e+00 1.969431385000000034e-01 1.140526600000000071e-02 +1.109806099999999907e+00 1.561050018333330069e-01 1.096976283333329916e-02 +1.137653000000000025e+00 1.629041234999999976e-01 1.057719600000000051e-02 +1.165935399999999955e+00 1.774132069999999894e-01 1.040658966666670009e-02 \ No newline at end of file diff --git a/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/2_20_1340_10.csv b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/2_20_1340_10.csv new file mode 100644 index 00000000..3fc89561 --- /dev/null +++ b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/2_20_1340_10.csv @@ -0,0 +1,105 @@ +3.623980000000000257e-02 4.717095203250000282e+02 1.736400694499999986e+01 +4.068580000000000113e-02 2.786014172683330230e+02 1.198827076999999974e+01 +4.509610000000000007e-02 1.807710803516669955e+02 8.834240069999999889e+00 +4.957530000000000270e-02 1.404167299050000111e+02 7.187393765000000379e+00 +5.414490000000000275e-02 1.070548824566670021e+02 5.660295944999999662e+00 +5.866960000000000230e-02 9.429733434666670178e+01 4.914365443333330141e+00 +6.313130000000000130e-02 8.753748113499999306e+01 4.086659178333330367e+00 +6.771190000000000542e-02 8.007891920833330346e+01 3.441741584999999937e+00 +7.236470000000000402e-02 5.999202779333329971e+01 2.970499293333329849e+00 +7.691660000000000164e-02 5.902839900166669906e+01 2.678938321666669786e+00 +8.163760000000000461e-02 4.960764509333330352e+01 2.245417474999999996e+00 +8.638719999999999732e-02 4.414010876166670272e+01 2.032560301666670011e+00 +9.105969999999999342e-02 4.256424763833329905e+01 1.812761751666670085e+00 +9.572880000000000278e-02 3.308233848333330229e+01 1.638201501666670001e+00 +1.004865999999999954e-01 2.810971494833329842e+01 1.433384486666670066e+00 +1.052305999999999936e-01 2.588485028666670118e+01 1.331128581666670030e+00 +1.099790999999999963e-01 2.266535039333329848e+01 1.188278135000000013e+00 +1.148321999999999954e-01 2.068870146000000076e+01 1.081535063333330049e+00 +1.197666000000000008e-01 1.976825064833330003e+01 1.002416109999999971e+00 +1.247589999999999949e-01 1.708519936000000072e+01 9.212166283333329542e-01 +1.298257999999999912e-01 1.961539944500000132e+01 8.615532866666669731e-01 +1.348960999999999910e-01 1.509279464499999968e+01 8.002327049999999886e-01 +1.399122999999999895e-01 1.461401991333329953e+01 7.302237983333329518e-01 +1.450607999999999898e-01 1.234817605166669985e+01 6.862621599999999544e-01 +1.502527999999999919e-01 1.236984282499999921e+01 6.604921383333329787e-01 +1.555927000000000004e-01 1.196552256666669933e+01 5.966093183333329719e-01 +1.572660999999999920e-01 1.278021405166670021e+01 1.919547649999999994e-01 +1.609331999999999985e-01 1.153752966666669977e+01 5.736198133333330063e-01 +1.661730999999999903e-01 1.078861117833329963e+01 5.440816866666670082e-01 +1.706513000000000058e-01 1.087118622666669943e+01 1.601861350000000073e-01 +1.715465999999999935e-01 9.877732214999999982e+00 5.101304616666669789e-01 +1.771117000000000108e-01 1.016148657499999963e+01 4.860413033333330080e-01 +1.827158000000000115e-01 1.012859923499999937e+01 4.659961616666670192e-01 +1.842668000000000084e-01 9.892309961666670759e+00 1.394281016666669981e-01 +1.883010999999999990e-01 9.370592901666670471e+00 4.483600366666670167e-01 +1.939626999999999879e-01 7.953713988333330320e+00 4.220674716666670268e-01 +1.982673000000000074e-01 8.536490116666669792e+00 1.198682733333329975e-01 +1.997490000000000099e-01 8.016174306666670191e+00 3.972042266666669930e-01 +2.055633000000000044e-01 7.936747634999999690e+00 3.864501066666670148e-01 +2.114472000000000018e-01 7.092097319999999705e+00 3.613801233333330254e-01 +2.120387999999999995e-01 7.719984431666669700e+00 1.093860133333330042e-01 +2.161715999999999915e-01 7.279911723333330364e+00 4.614210216666669861e-01 +2.260118000000000127e-01 6.899103116666670310e+00 9.616077666666669743e-02 +2.398155999999999899e-01 6.131303501666669931e+00 8.927448999999999801e-02 +2.537425999999999848e-01 5.657937448333330011e+00 8.168489666666670090e-02 +2.676623000000000197e-01 5.142021448333330191e+00 7.478100833333330144e-02 +2.818223999999999729e-01 4.914665098333330207e+00 7.002304500000000598e-02 +2.956782000000000021e-01 4.446721935000000236e+00 6.572301166666670580e-02 +3.099446000000000145e-01 4.282954679999999570e+00 6.061547666666670248e-02 +3.241067000000000253e-01 3.767117071666670203e+00 5.800978500000000121e-02 +3.388467000000000007e-01 3.499952983333329826e+00 5.180699999999999888e-02 +3.539599999999999969e-01 3.303444670000000194e+00 5.069738166666670071e-02 +3.687568999999999986e-01 2.950319708333330126e+00 4.620904333333329672e-02 +3.840311000000000141e-01 2.864367984999999894e+00 4.422433499999999656e-02 +3.989493000000000067e-01 2.552979848333329915e+00 4.244176999999999672e-02 +4.107629999999999892e-01 2.431688859833330163e+00 4.295243233333329719e-02 +4.137051999999999952e-01 2.340746346666669808e+00 4.009275166666669693e-02 +4.291185000000000138e-01 2.236674611666670032e+00 3.708904500000000104e-02 +4.296455000000000135e-01 2.145105044333329936e+00 3.592934700000000037e-02 +4.446591999999999767e-01 1.951663006666670030e+00 3.589577499999999782e-02 +4.467690999999999746e-01 1.941018092166669984e+00 3.204738566666669869e-02 +4.603923999999999794e-01 1.841567858333329921e+00 3.340552499999999841e-02 +4.686460000000000070e-01 1.795296687500000044e+00 3.165418133333330192e-02 +4.765479000000000243e-01 1.725552725000000009e+00 3.166947999999999985e-02 +4.835137999999999936e-01 1.648896234833330032e+00 3.002704050000000111e-02 +4.923843999999999999e-01 1.592235150000000043e+00 3.142748499999999806e-02 +5.047722000000000042e-01 1.524318554666669989e+00 2.765916716666669967e-02 +5.083368000000000331e-01 1.470192684999999999e+00 2.879683666666670028e-02 +5.231871000000000160e-01 1.276008043333330066e+00 2.578494833333330044e-02 +5.249844000000000177e-01 1.335736669999999959e+00 2.682150333333329847e-02 +5.417480000000000073e-01 1.255200871666670048e+00 2.659473333333330081e-02 +5.445189000000000279e-01 1.181751707666669926e+00 2.602010650000000092e-02 +5.583793000000000228e-01 1.119981195000000040e+00 2.552491833333329907e-02 +5.613839000000000468e-01 1.109540133333329903e+00 2.376074416666670158e-02 +5.754704000000000486e-01 1.035568078333330089e+00 2.359670999999999991e-02 +5.835346000000000144e-01 1.009673415166669974e+00 2.350527950000000019e-02 +5.928168000000000326e-01 9.958746566666669686e-01 2.289775833333329916e-02 +6.007057000000000091e-01 9.162153768333329840e-01 2.224946400000000005e-02 +6.099849999999999994e-01 8.912180400000000446e-01 2.207391333333329902e-02 +6.219902999999999960e-01 8.127960751666669648e-01 2.151158183333330004e-02 +6.275817000000000201e-01 8.004247816666669735e-01 2.072389499999999912e-02 +6.413482000000000349e-01 7.426332359999999744e-01 1.925514349999999861e-02 +6.447992000000000168e-01 7.076525866666669717e-01 2.108122000000000121e-02 +6.656950000000000367e-01 6.442563610000000551e-01 1.837696600000000152e-02 +6.845196000000000058e-01 6.085390748333330269e-01 2.050695833333330068e-02 +7.059646000000000532e-01 5.280073378333329792e-01 1.732566899999999840e-02 +7.260278000000000009e-01 5.220653703333330009e-01 1.859470200000000115e-02 +7.492113999999999718e-01 4.598426163333330097e-01 1.525465250000000023e-02 +7.721772000000000080e-01 4.332637708333330062e-01 1.732895283333329983e-02 +7.922717999999999705e-01 4.105990393333330268e-01 1.523655316666669944e-02 +8.156877999999999629e-01 3.602943534999999975e-01 1.545041316666669919e-02 +8.366301000000000432e-01 3.406887861666669792e-01 1.473824349999999957e-02 +8.621258000000000532e-01 3.067601776666670221e-01 1.382606316666670082e-02 +8.848333999999999921e-01 3.015524316666670090e-01 1.454982116666670051e-02 +9.083444000000000518e-01 2.607474308333330160e-01 1.270642616666669937e-02 +9.336484000000000449e-01 2.588003018333330241e-01 1.373286349999999940e-02 +9.553916000000000075e-01 2.226530176666670080e-01 1.285228899999999938e-02 +9.815112999999999754e-01 2.127235584999999929e-01 1.205009299999999957e-02 +1.007027499999999964e+00 2.082924603333330127e-01 1.250585733333330063e-02 +1.030631400000000086e+00 1.912464838333330086e-01 1.185822249999999960e-02 +1.058015099999999986e+00 1.948314409999999941e-01 1.101004149999999966e-02 +1.084837700000000016e+00 1.896849941666670092e-01 1.137308499999999979e-02 +1.110144500000000090e+00 1.497566153333330097e-01 1.094208966666669960e-02 +1.137999800000000006e+00 1.454676966666670068e-01 1.051448583333330043e-02 +1.166290800000000072e+00 1.467227160000000030e-01 1.029956333333329963e-02 \ No newline at end of file diff --git a/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/3_35_1340_10.csv b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/3_35_1340_10.csv new file mode 100644 index 00000000..5ef6a326 --- /dev/null +++ b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/3_35_1340_10.csv @@ -0,0 +1,105 @@ +3.625239999999999713e-02 2.531792558183329902e+02 1.680830338499999854e+01 +4.070000000000000007e-02 1.901177223299999923e+02 1.173664666500000031e+01 +4.511180000000000051e-02 1.217924648083329942e+02 8.645296186666669769e+00 +4.959249999999999770e-02 1.094275780066670052e+02 7.073034475000000043e+00 +5.416379999999999806e-02 8.791320813333329909e+01 5.581711248333330211e+00 +5.868999999999999911e-02 8.309104453833330695e+01 4.860535583333329690e+00 +6.315320000000000655e-02 7.081360745666670198e+01 4.003961999999999577e+00 +6.773540000000000116e-02 6.343291181166669901e+01 3.355398363333330192e+00 +7.238989999999999314e-02 4.842191466166669755e+01 2.903796691666669982e+00 +7.694339999999999513e-02 4.694420552666669977e+01 2.605583116666669863e+00 +8.166600000000000248e-02 4.367521950333330238e+01 2.207077676666670207e+00 +8.641719999999999957e-02 3.957930205499999943e+01 1.999356729999999915e+00 +9.109140000000000292e-02 3.553857484166670133e+01 1.762052579999999979e+00 +9.576210000000000278e-02 2.783675838666669833e+01 1.597004463333330015e+00 +1.005216000000000026e-01 2.705964543666669897e+01 1.423240978333329965e+00 +1.052672000000000052e-01 2.262947438833329983e+01 1.302317253333330038e+00 +1.100174000000000013e-01 2.182584247666670052e+01 1.179366926666669979e+00 +1.148721999999999938e-01 2.121788853500000016e+01 1.084366495000000041e+00 +1.198083000000000065e-01 1.779511935000000022e+01 9.833421683333329888e-01 +1.248023999999999939e-01 1.651748253666670152e+01 9.145048883333329881e-01 +1.298709000000000113e-01 1.721928703000000027e+01 8.380137100000000228e-01 +1.349431000000000103e-01 1.436323046666669967e+01 7.917284633333330213e-01 +1.399610000000000021e-01 1.550343697666670018e+01 7.379972516666669646e-01 +1.451112999999999986e-01 1.303663926666670037e+01 6.920347900000000108e-01 +1.503050999999999970e-01 1.200503839166669984e+01 6.556345366666670449e-01 +1.556469000000000047e-01 1.229304255666670009e+01 5.989305183333329952e-01 +1.573070000000000024e-01 1.257057899666670053e+01 1.910309249999999903e-01 +1.609891999999999990e-01 1.193592632666669928e+01 5.769697900000000379e-01 +1.662309999999999899e-01 1.125234698666669964e+01 5.482133000000000145e-01 +1.706957000000000058e-01 1.058112505166669948e+01 1.590339783333329926e-01 +1.716062999999999894e-01 1.070644689999999954e+01 5.183003699999999547e-01 +1.771733000000000058e-01 9.479454351666669609e+00 4.778282466666670114e-01 +1.827794000000000085e-01 9.400985496666670826e+00 4.569814249999999967e-01 +1.843146999999999980e-01 9.549487438333329692e+00 1.380682866666670117e-01 +1.883665999999999952e-01 8.342261173333330504e+00 4.359566316666669827e-01 +1.940302000000000138e-01 8.124126898333329905e+00 4.232381216666670221e-01 +1.983187999999999895e-01 8.328415036666669380e+00 1.189532799999999946e-01 +1.998185000000000100e-01 8.328210934999999537e+00 3.999954083333330246e-01 +2.056348000000000065e-01 8.350671104999999983e+00 3.905236483333329733e-01 +2.115208000000000088e-01 6.929636241666670138e+00 3.588886250000000167e-01 +2.120939000000000019e-01 7.535892504999999630e+00 1.085254183333329986e-01 +2.162467999999999890e-01 7.407988318333329936e+00 4.625349066666670228e-01 +2.260705000000000076e-01 6.743451298333329902e+00 9.541167166666669752e-02 +2.398779000000000050e-01 6.099490819999999758e+00 8.899463999999999986e-02 +2.538084999999999924e-01 5.555039073333330357e+00 8.113394833333330280e-02 +2.677318000000000198e-01 5.152730110000000252e+00 7.469547999999999466e-02 +2.818956000000000239e-01 4.837557096666669665e+00 6.957829666666670576e-02 +2.957549999999999901e-01 4.369791838333330070e+00 6.527595833333330044e-02 +3.100250999999999979e-01 4.110463069999999774e+00 5.977720166666670304e-02 +3.241909000000000041e-01 3.813721606666669928e+00 5.811357666666670113e-02 +3.389346999999999777e-01 3.461479851666669827e+00 5.155417166666669687e-02 +3.540519999999999778e-01 3.230298750000000219e+00 5.028755499999999767e-02 +3.688526999999999778e-01 2.998165658333329819e+00 4.634052999999999783e-02 +3.841308999999999974e-01 2.784144708333330165e+00 4.378963833333329725e-02 +3.990528999999999882e-01 2.595613363333329815e+00 4.256657166666669850e-02 +4.108341999999999827e-01 2.335089765499999803e+00 4.258898366666669794e-02 +4.138126999999999778e-01 2.324415418333329875e+00 3.994880166666670007e-02 +4.292300000000000004e-01 2.216248561666669836e+00 3.693022666666669757e-02 +4.297198999999999880e-01 2.134554158666670087e+00 3.596130699999999791e-02 +4.447747000000000228e-01 1.890609146666669904e+00 3.555346833333330320e-02 +4.468464999999999798e-01 1.908117816499999897e+00 3.198120466666670020e-02 +4.605119999999999769e-01 1.871098863333330087e+00 3.348654166666670262e-02 +4.687272000000000105e-01 1.810652348333330108e+00 3.178039216666669886e-02 +4.766716999999999760e-01 1.698277889999999957e+00 3.148857166666670093e-02 +4.835976000000000163e-01 1.613169754500000108e+00 2.993151466666670035e-02 +4.925123000000000140e-01 1.548358499999999971e+00 3.116390166666669834e-02 +5.048597000000000223e-01 1.494630669833330039e+00 2.759253283333330115e-02 +5.084689000000000014e-01 1.491590500000000041e+00 2.885127333333329866e-02 +5.232776999999999568e-01 1.248566430833329965e+00 2.572220416666669979e-02 +5.251208000000000542e-01 1.383180581666670017e+00 2.700447666666670049e-02 +5.418887000000000009e-01 1.221960488333329931e+00 2.639246833333330072e-02 +5.446132999999999669e-01 1.208971178333329899e+00 2.618446533333329898e-02 +5.585244000000000320e-01 1.107120358333330001e+00 2.542247833333330029e-02 +5.614812000000000136e-01 1.085889866500000078e+00 2.370511449999999909e-02 +5.756198999999999621e-01 1.024119451666670066e+00 2.350574000000000066e-02 +5.836356999999999795e-01 9.666399098333330331e-01 2.336446566666669847e-02 +5.929708999999999675e-01 9.753983600000000198e-01 2.276144833333329856e-02 +6.008097999999999494e-01 8.857038164999999630e-01 2.215761016666669900e-02 +6.101434999999999498e-01 9.144217283333330171e-01 2.215592499999999992e-02 +6.220980999999999872e-01 8.054969749999999484e-01 2.151697083333330152e-02 +6.277447999999999917e-01 7.972735800000000372e-01 2.067797666666670170e-02 +6.414592999999999545e-01 7.033510668333330385e-01 1.912904316666669963e-02 +6.449667999999999513e-01 7.010848466666670387e-01 2.101707333333329916e-02 +6.658104000000000244e-01 6.363828563333330246e-01 1.837497983333330129e-02 +6.846381999999999746e-01 5.758642446666669690e-01 2.039069533333329881e-02 +7.060868999999999618e-01 5.109788090000000338e-01 1.728495966666670006e-02 +7.261535999999999547e-01 4.885223020000000194e-01 1.847847583333329935e-02 +7.493412999999999879e-01 4.387949984999999775e-01 1.519840233333329994e-02 +7.723109999999999697e-01 3.923397161666670185e-01 1.717436333333329998e-02 +7.924090999999999774e-01 3.993512075000000272e-01 1.521433699999999965e-02 +8.158290999999999737e-01 3.564691013333329828e-01 1.545670316666669999e-02 +8.367750000000000465e-01 3.230009236666669947e-01 1.469029500000000078e-02 +8.622752000000000194e-01 2.878051181666669844e-01 1.377513966666669976e-02 +8.849867000000000150e-01 2.848005155000000177e-01 1.450056933333329981e-02 +9.085018000000000260e-01 2.630901753333330095e-01 1.273161166666669959e-02 +9.338100999999999763e-01 2.556641304999999753e-01 1.373873700000000087e-02 +9.555571000000000481e-01 2.110106781666669928e-01 1.282516266666670034e-02 +9.816814000000000373e-01 1.989191335000000116e-01 1.201689483333330012e-02 +1.007201999999999931e+00 1.798726026666669919e-01 1.241756500000000020e-02 +1.030810000000000004e+00 1.697684260000000001e-01 1.179553216666670047e-02 +1.058198500000000042e+00 1.911874938333329998e-01 1.101163099999999916e-02 +1.085025700000000093e+00 1.546144220000000014e-01 1.126302133333329999e-02 +1.110336800000000013e+00 1.274498698333330071e-01 1.087918116666669946e-02 +1.138196999999999903e+00 1.312019681666669879e-01 1.048030450000000079e-02 +1.166492899999999944e+00 1.202452754999999984e-01 1.022370933333329943e-02 \ No newline at end of file diff --git a/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/4_50_1340_10.csv b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/4_50_1340_10.csv new file mode 100644 index 00000000..51227d3b --- /dev/null +++ b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/4_50_1340_10.csv @@ -0,0 +1,105 @@ +3.624299999999999744e-02 2.421839941750000094e+02 1.681259771499999900e+01 +4.068929999999999769e-02 1.253477160583330061e+02 1.158261931833330038e+01 +4.510000000000000120e-02 1.038159109083329952e+02 8.606540843333329249e+00 +4.957960000000000145e-02 9.508877155000000414e+01 7.037626288333330038e+00 +5.414959999999999912e-02 7.142443308833330207e+01 5.530319266666669620e+00 +5.867460000000000037e-02 6.747951957166669956e+01 4.803320423333330424e+00 +6.313670000000000393e-02 6.491403129500000091e+01 3.984481885000000112e+00 +6.771770000000000567e-02 5.688806046833330043e+01 3.329560004999999823e+00 +7.237100000000000477e-02 4.031379161166670144e+01 2.864987329999999943e+00 +7.692330000000000001e-02 4.363721252500000247e+01 2.591659659999999921e+00 +8.164470000000000061e-02 3.499940050499999700e+01 2.159604903333330217e+00 +8.639470000000000482e-02 3.331168452000000002e+01 1.961486934999999932e+00 +9.106759999999999855e-02 3.141853618500000067e+01 1.737193953333330043e+00 +9.573710000000000553e-02 2.514925802833329982e+01 1.580249494999999893e+00 +1.004952999999999957e-01 2.418445705499999931e+01 1.404480071666670105e+00 +1.052397000000000055e-01 2.311071394833329862e+01 1.309208076666670051e+00 +1.099886000000000058e-01 2.234431306333329914e+01 1.186371084999999992e+00 +1.148422000000000054e-01 2.015011891333330141e+01 1.077548355000000013e+00 +1.197769999999999946e-01 1.762490517499999854e+01 9.840672733333329925e-01 +1.247698000000000002e-01 1.703488854000000075e+01 9.213895800000000413e-01 +1.298370000000000080e-01 1.644401272166669870e+01 8.327218266666670532e-01 +1.349077999999999944e-01 1.614025673666670002e+01 8.110203433333329492e-01 +1.399244000000000043e-01 1.432045983666669997e+01 7.277294366666670067e-01 +1.450733999999999913e-01 1.348007003166670081e+01 6.981156733333330200e-01 +1.502658000000000049e-01 1.217057146500000009e+01 6.589065133333330548e-01 +1.556062000000000001e-01 1.311354035333330081e+01 6.087518283333329672e-01 +1.573412999999999895e-01 1.262766263333329952e+01 1.918068450000000036e-01 +1.609471000000000096e-01 1.195521582166669994e+01 5.786270049999999721e-01 +1.661874999999999880e-01 1.070419187999999977e+01 5.435432616666669992e-01 +1.707328999999999930e-01 1.060603477499999947e+01 1.596232366666670011e-01 +1.715615000000000057e-01 1.106375028666669991e+01 5.234683166666670440e-01 +1.771270000000000067e-01 9.097135046666670277e+00 4.748174949999999783e-01 +1.827315999999999940e-01 9.232441778333329907e+00 4.562185283333329844e-01 +1.843548999999999882e-01 9.582483613333330652e+00 1.386399050000000077e-01 +1.883173999999999959e-01 9.681451038333330317e+00 4.521609416666669823e-01 +1.939794999999999991e-01 8.227612430000000643e+00 4.254379616666669750e-01 +1.983621000000000134e-01 8.295318078333329126e+00 1.192443799999999970e-01 +1.997663000000000078e-01 8.131970284999999521e+00 3.988034016666670012e-01 +2.055810999999999888e-01 7.142658796666670362e+00 3.773835349999999922e-01 +2.114655000000000007e-01 7.164933761666669731e+00 3.624743966666669759e-01 +2.121402000000000010e-01 7.387669431666670228e+00 1.083523799999999981e-01 +2.161903000000000019e-01 6.864321103333329788e+00 4.553583783333329804e-01 +2.261198000000000097e-01 6.847039901666669870e+00 9.614554833333330275e-02 +2.399302000000000101e-01 6.151464958333329847e+00 8.952354166666670610e-02 +2.538638999999999757e-01 5.563245829999999614e+00 8.146051666666670465e-02 +2.677901999999999783e-01 5.162996156666669556e+00 7.501275500000000040e-02 +2.819571000000000160e-01 4.731078383333329640e+00 6.938886333333330048e-02 +2.958196000000000159e-01 4.470382223333330352e+00 6.595420000000000449e-02 +3.100928000000000018e-01 4.071269568333329758e+00 5.983178000000000107e-02 +3.242616999999999861e-01 3.818289136666670025e+00 5.835058500000000342e-02 +3.390086999999999962e-01 3.464278166666669989e+00 5.175618166666669934e-02 +3.541291999999999773e-01 3.297458013333330218e+00 5.076983000000000190e-02 +3.689332000000000167e-01 3.070901008333330129e+00 4.683460500000000137e-02 +3.842147000000000201e-01 2.769801061666670172e+00 4.388707166666670073e-02 +3.991399999999999948e-01 2.510583560000000158e+00 4.232430333333329908e-02 +4.106022999999999756e-01 2.374815773499999949e+00 4.286182233333329927e-02 +4.139030000000000209e-01 2.373871081666670158e+00 4.032223833333330176e-02 +4.293236999999999748e-01 2.231085370000000179e+00 3.713437166666670036e-02 +4.294773000000000063e-01 2.156539414333329852e+00 3.612328016666670194e-02 +4.448717999999999839e-01 1.876962131666670031e+00 3.561285166666670193e-02 +4.465942999999999996e-01 1.963582529333329996e+00 3.226501116666669750e-02 +4.606124999999999803e-01 1.886281606666670108e+00 3.367513833333329876e-02 +4.684626000000000068e-01 1.795604685000000034e+00 3.177854483333329705e-02 +4.767757000000000245e-01 1.729912848333329922e+00 3.174894833333329752e-02 +4.833245999999999931e-01 1.643333701666670033e+00 3.012282700000000132e-02 +4.926197999999999966e-01 1.601281793333330095e+00 3.152871666666669931e-02 +5.045747000000000426e-01 1.513868059666670041e+00 2.772222300000000070e-02 +5.085798000000000263e-01 1.480025179999999940e+00 2.889593166666670071e-02 +5.229823000000000111e-01 1.292098316666669966e+00 2.594417883333329997e-02 +5.252354000000000189e-01 1.389690103333329985e+00 2.712855999999999948e-02 +5.420070000000000165e-01 1.207626951666670001e+00 2.641061333333330138e-02 +5.443057999999999508e-01 1.171074812333330106e+00 2.606778300000000062e-02 +5.586463000000000401e-01 1.131968480000000055e+00 2.562754666666669859e-02 +5.611642999999999493e-01 1.119771183833329964e+00 2.388942000000000149e-02 +5.757455000000000211e-01 1.050324178333329916e+00 2.370775166666670014e-02 +5.833063000000000553e-01 9.890430198333329814e-01 2.349970633333330061e-02 +5.931003000000000247e-01 9.529422783333330038e-01 2.272385333333330065e-02 +6.004707000000000239e-01 9.087951714999999986e-01 2.229426183333330092e-02 +6.102765999999999469e-01 8.968441200000000224e-01 2.213925333333329956e-02 +6.217470000000000496e-01 8.116193323333330545e-01 2.157820200000000119e-02 +6.278818000000000454e-01 7.745292049999999984e-01 2.063141833333330052e-02 +6.410972000000000337e-01 7.304547335000000086e-01 1.926881999999999900e-02 +6.451074999999999449e-01 7.030634049999999746e-01 2.109099333333330079e-02 +6.654345000000000399e-01 6.451808323333330097e-01 1.843812350000000044e-02 +6.842517000000000182e-01 5.969281796666670026e-01 2.051784966666669874e-02 +7.056883000000000461e-01 4.875458616666670242e-01 1.721726566666669997e-02 +7.257436999999999916e-01 5.206052460000000215e-01 1.864389433333329960e-02 +7.489183000000000368e-01 4.716855048333329914e-01 1.534349166666670004e-02 +7.718749999999999778e-01 3.996116843333329949e-01 1.723059933333330115e-02 +7.919618000000000491e-01 3.949704010000000265e-01 1.521841983333330033e-02 +8.153685999999999989e-01 3.711572033333330189e-01 1.553813883333329988e-02 +8.363026999999999544e-01 3.373120053333329982e-01 1.476541233333330053e-02 +8.617884000000000100e-01 3.012111169999999727e-01 1.384248800000000001e-02 +8.844872000000000289e-01 3.093801609999999869e-01 1.462152149999999991e-02 +9.079890000000000461e-01 2.725293346666670113e-01 1.278188633333329945e-02 +9.332829999999999737e-01 2.562172424999999976e-01 1.375849099999999943e-02 +9.550178000000000278e-01 2.195073688333329942e-01 1.287299783333330054e-02 +9.811271999999999771e-01 1.993873673333330099e-01 1.203331399999999982e-02 +1.006633399999999900e+00 1.965978770000000042e-01 1.249421516666670076e-02 +1.030228200000000038e+00 1.888581198333330047e-01 1.187900783333330039e-02 +1.057601100000000072e+00 2.019180355000000093e-01 1.106158266666669963e-02 +1.084413199999999966e+00 1.710308969999999873e-01 1.133507299999999933e-02 +1.109710100000000033e+00 1.385269876666669897e-01 1.092970799999999978e-02 +1.137554500000000024e+00 1.323425321666669985e-01 1.049619949999999920e-02 +1.165834400000000048e+00 1.352444690000000060e-01 1.028587266666670073e-02 \ No newline at end of file diff --git a/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/5_75_1340_10.csv b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/5_75_1340_10.csv new file mode 100644 index 00000000..e4a5c8ff --- /dev/null +++ b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/5_75_1340_10.csv @@ -0,0 +1,105 @@ +3.623830000000000107e-02 1.262534512933330006e+02 1.649893105666669868e+01 +4.068399999999999794e-02 1.096408472683330046e+02 1.152156481833329948e+01 +4.509420000000000095e-02 6.843298548666669490e+01 8.484508865000000455e+00 +4.957310000000000189e-02 5.337196919333329959e+01 6.882409771666670117e+00 +5.414259999999999906e-02 4.824018401499999698e+01 5.433445991666670416e+00 +5.866699999999999693e-02 4.665372951666670076e+01 4.705492196666670068e+00 +6.312850000000000406e-02 5.221271013999999866e+01 3.917987768333329957e+00 +6.770890000000000242e-02 4.827582027833329903e+01 3.280544881666669799e+00 +7.236159999999999815e-02 3.618855914666669804e+01 2.837183351666670017e+00 +7.691330000000000389e-02 3.079150051333330040e+01 2.510836073333329921e+00 +8.163410000000000111e-02 3.296728457500000076e+01 2.143401925000000041e+00 +8.638339999999999907e-02 3.069841431000000043e+01 1.940150145000000048e+00 +9.105580000000000618e-02 2.710592771666669876e+01 1.703459686666670025e+00 +9.572469999999999590e-02 2.329590637833329936e+01 1.563327263333329942e+00 +1.004822999999999966e-01 2.091072460333329985e+01 1.376591528333330094e+00 +1.052260000000000001e-01 1.912562908333330114e+01 1.273402691666670083e+00 +1.099742999999999971e-01 1.787115249333330169e+01 1.146192773333329917e+00 +1.148272999999999933e-01 1.794118791666669921e+01 1.055988855000000060e+00 +1.197614000000000040e-01 1.653599519333329937e+01 9.723875616666669552e-01 +1.247536000000000062e-01 1.582743532666670028e+01 9.084356083333330334e-01 +1.298200999999999938e-01 1.478155951166669979e+01 8.152435350000000192e-01 +1.348902999999999908e-01 1.334408521333329922e+01 7.818770066666670404e-01 +1.399062000000000083e-01 1.377281605666670039e+01 7.205883533333330426e-01 +1.450545000000000029e-01 1.181691977333330001e+01 6.799812333333330461e-01 +1.502462999999999993e-01 1.213584320000000005e+01 6.572146300000000219e-01 +1.555860000000000021e-01 1.192358865166669979e+01 5.953690533333330093e-01 +1.572933999999999999e-01 1.181612013833330010e+01 1.889312700000000123e-01 +1.609261999999999915e-01 1.175466066000000076e+01 5.752044899999999572e-01 +1.661659000000000053e-01 1.042143580833329963e+01 5.392668716666669804e-01 +1.706808999999999965e-01 1.011778497999999971e+01 1.577585016666669948e-01 +1.715392000000000028e-01 9.758357795000000223e+00 5.081139666666669719e-01 +1.771040000000000114e-01 8.903400566666670457e+00 4.716962699999999731e-01 +1.827079000000000064e-01 9.178007346666669619e+00 4.546196783333329994e-01 +1.842987000000000097e-01 9.009516103333330861e+00 1.364224366666670074e-01 +1.882929000000000130e-01 8.406366826666669567e+00 4.368516150000000264e-01 +1.939542999999999962e-01 7.892936534999999587e+00 4.208100816666670019e-01 +1.983015999999999945e-01 7.968528981666669786e+00 1.178380566666669960e-01 +1.997403000000000095e-01 7.734987613333330181e+00 3.934730833333329736e-01 +2.055543999999999982e-01 7.979108915000000302e+00 3.863922049999999886e-01 +2.114380999999999899e-01 6.834691483333330098e+00 3.579354766666669740e-01 +2.120755000000000001e-01 7.473622876666669690e+00 1.084324933333330049e-01 +2.161621999999999988e-01 6.843431601666670083e+00 4.540542233333330069e-01 +2.260508999999999991e-01 6.624537448333329692e+00 9.508442500000000019e-02 +2.398570999999999898e-01 6.030806045000000282e+00 8.883706333333329930e-02 +2.537865999999999733e-01 5.548485336666669987e+00 8.121552666666670417e-02 +2.677086000000000188e-01 5.056279183333329819e+00 7.439664999999999473e-02 +2.818711999999999884e-01 4.746517688333329765e+00 6.929181333333329917e-02 +2.957293999999999756e-01 4.360488485000000303e+00 6.532353000000000465e-02 +3.099983000000000044e-01 4.042259101666670240e+00 5.956615500000000257e-02 +3.241628999999999761e-01 3.667612291666670021e+00 5.754424666666670130e-02 +3.389054000000000233e-01 3.392261260000000167e+00 5.132888666666669819e-02 +3.540212999999999832e-01 3.153369070000000107e+00 5.001504333333330055e-02 +3.688208000000000042e-01 2.957433689999999782e+00 4.622241666666670329e-02 +3.840975999999999835e-01 2.782086464999999897e+00 4.383930500000000202e-02 +3.990183999999999953e-01 2.497954614999999823e+00 4.216718166666669904e-02 +4.106379000000000001e-01 2.347253106833329994e+00 4.254085666666670290e-02 +4.137768000000000002e-01 2.313526113333329803e+00 3.995030166666670157e-02 +4.291927999999999854e-01 2.205677500000000180e+00 3.693004333333330114e-02 +4.295145000000000213e-01 2.125140473833329935e+00 3.583346700000000079e-02 +4.447362000000000259e-01 1.900926653333329996e+00 3.564656666666669860e-02 +4.466328999999999994e-01 1.939722618500000051e+00 3.202840516666669718e-02 +4.604721999999999982e-01 1.890633683333329929e+00 3.361991000000000285e-02 +4.685032000000000085e-01 1.753475375666670111e+00 3.146806816666670309e-02 +4.766304000000000096e-01 1.692544018333330014e+00 3.150160500000000197e-02 +4.833663999999999739e-01 1.571138671166669942e+00 2.967553150000000126e-02 +4.924697000000000102e-01 1.562743266666670072e+00 3.127265833333330025e-02 +5.046184000000000225e-01 1.517538195333330009e+00 2.762060549999999920e-02 +5.084248000000000101e-01 1.452519751666669912e+00 2.870172666666670133e-02 +5.230276000000000369e-01 1.280607860833330003e+00 2.579378549999999937e-02 +5.250753000000000226e-01 1.364635400000000054e+00 2.694985833333329861e-02 +5.418418000000000401e-01 1.180339848333330055e+00 2.622176999999999841e-02 +5.443529999999999758e-01 1.172893769833329936e+00 2.597214383333330129e-02 +5.584759999999999724e-01 1.116791568333330043e+00 2.550004666666669945e-02 +5.612129000000000145e-01 1.071752901666670033e+00 2.359418099999999879e-02 +5.755700000000000260e-01 1.003165499999999932e+00 2.343291166666670172e-02 +5.833568000000000087e-01 9.490407108333329678e-01 2.324037416666669895e-02 +5.929195000000000437e-01 9.286979850000000036e-01 2.255620333333329883e-02 +6.005226999999999649e-01 8.938147573333330431e-01 2.214501316666669939e-02 +6.100906000000000384e-01 8.779068549999999860e-01 2.199904499999999832e-02 +6.218008000000000424e-01 8.078191183333329750e-01 2.148254866666670163e-02 +6.276903999999999817e-01 7.890844949999999969e-01 2.066168499999999908e-02 +6.411527999999999672e-01 6.990414535000000207e-01 1.907308200000000162e-02 +6.449108999999999536e-01 7.027759316666669642e-01 2.104888333333329933e-02 +6.654921999999999782e-01 6.693195063333330364e-01 1.846815400000000051e-02 +6.843110000000000026e-01 5.654079498333329790e-01 2.030432683333329921e-02 +7.057493999999999712e-01 4.733216641666669888e-01 1.710506283333329894e-02 +7.258065000000000211e-01 4.881572298333329840e-01 1.844279849999999957e-02 +7.489831000000000127e-01 4.439720878333329734e-01 1.519038399999999948e-02 +7.719418000000000113e-01 4.063687451666669892e-01 1.720554133333329974e-02 +7.920304000000000233e-01 4.025809184999999957e-01 1.519988700000000047e-02 +8.154392000000000307e-01 3.522328571666670238e-01 1.541253149999999988e-02 +8.363751000000000380e-01 3.005755748333330257e-01 1.457862766666669953e-02 +8.618630000000000457e-01 2.801856756666670223e-01 1.372444566666669932e-02 +8.845638000000000112e-01 3.046253318333330129e-01 1.455751649999999925e-02 +9.080675999999999748e-01 2.540008221666669730e-01 1.267830250000000041e-02 +9.333637999999999657e-01 2.481020686666670083e-01 1.368678783333330054e-02 +9.551005000000000189e-01 1.938199579999999866e-01 1.274076333333330063e-02 +9.812121999999999788e-01 1.905731619999999904e-01 1.196892649999999926e-02 +1.006720599999999965e+00 1.784818836666670072e-01 1.239337400000000040e-02 +1.030317399999999939e+00 1.750911911666669929e-01 1.179671083333330012e-02 +1.057692700000000041e+00 1.705610396666669970e-01 1.092493383333329945e-02 +1.084507099999999946e+00 1.719191243333330066e-01 1.130716749999999965e-02 +1.109806099999999907e+00 1.366376699999999889e-01 1.089438483333329995e-02 +1.137653000000000025e+00 1.239800338333330032e-01 1.044124700000000072e-02 +1.165935399999999955e+00 1.310211675000000076e-01 1.024458533333330069e-02 \ No newline at end of file diff --git a/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/6_100_1340_10.csv b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/6_100_1340_10.csv new file mode 100644 index 00000000..2b03b80a --- /dev/null +++ b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/6_100_1340_10.csv @@ -0,0 +1,105 @@ +3.625239999999999713e-02 1.213206534416670053e+02 1.651067908166670151e+01 +4.070000000000000007e-02 8.158564289333330066e+01 1.146150313666669973e+01 +4.511180000000000051e-02 6.728303165833330013e+01 8.493610868333329122e+00 +4.959249999999999770e-02 5.609321657833329766e+01 6.902234358333330100e+00 +5.416379999999999806e-02 4.208391674666670212e+01 5.417946521666669568e+00 +5.868999999999999911e-02 4.572918046833329697e+01 4.708583066666670369e+00 +6.315320000000000655e-02 4.506950746166670285e+01 3.890110903333329873e+00 +6.773540000000000116e-02 3.932771859999999720e+01 3.240109296666669803e+00 +7.238989999999999314e-02 3.425092975666670014e+01 2.830867521666669884e+00 +7.694339999999999513e-02 2.785716006499999864e+01 2.497106951666669961e+00 +8.166600000000000248e-02 2.647316349333329910e+01 2.106834523333330100e+00 +8.641719999999999957e-02 2.865549820000000025e+01 1.929370503333329934e+00 +9.109140000000000292e-02 2.617781355833329826e+01 1.699746431666669944e+00 +9.576210000000000278e-02 2.262414874833330103e+01 1.560889624999999947e+00 +1.005216000000000026e-01 2.072404344833330114e+01 1.377539340000000001e+00 +1.052672000000000052e-01 1.924028949499999896e+01 1.276625569999999987e+00 +1.100174000000000013e-01 1.836748323999999855e+01 1.152518286666670111e+00 +1.148721999999999938e-01 1.699007782666669897e+01 1.049408108333329981e+00 +1.198083000000000065e-01 1.493712180833330017e+01 9.595881783333329862e-01 +1.248023999999999939e-01 1.484142020166670051e+01 9.009069050000000356e-01 +1.298709000000000113e-01 1.473895257666669956e+01 8.163899350000000110e-01 +1.349431000000000103e-01 1.308340819666669930e+01 7.807371766666669766e-01 +1.399610000000000021e-01 1.350568340499999920e+01 7.192249033333329988e-01 +1.451112999999999986e-01 1.173611615333330072e+01 6.804578466666669767e-01 +1.503050999999999970e-01 1.154060497666669960e+01 6.523238550000000080e-01 +1.556469000000000047e-01 1.191625231333330071e+01 5.964806683333330195e-01 +1.573344999999999883e-01 1.139197852166670089e+01 1.879402166666669927e-01 +1.609891999999999990e-01 1.125507998666670062e+01 5.708663616666670437e-01 +1.662309999999999899e-01 1.041113468166670053e+01 5.402433599999999503e-01 +1.707255000000000023e-01 1.013307594333329931e+01 1.580824633333330065e-01 +1.716062999999999894e-01 9.500610554999999735e+00 5.062648633333329817e-01 +1.771733000000000058e-01 8.706795813333329193e+00 4.704599616666669815e-01 +1.827794000000000085e-01 9.156035623333330875e+00 4.553047450000000107e-01 +1.843469000000000080e-01 9.118870355000000316e+00 1.370399833333330042e-01 +1.883665999999999952e-01 7.952454938333329615e+00 4.325042000000000053e-01 +1.940302000000000138e-01 8.034647969999999972e+00 4.232443883333329993e-01 +1.983534999999999882e-01 8.119139649999999264e+00 1.185909816666669975e-01 +1.998185000000000100e-01 7.527596430000000005e+00 3.919050766666670182e-01 +2.056348000000000065e-01 7.553855535000000287e+00 3.822255366666669762e-01 +2.115208000000000088e-01 6.617426820000000376e+00 3.561540533333329983e-01 +2.121309999999999862e-01 7.390867828333330003e+00 1.083323783333329932e-01 +2.162467999999999890e-01 6.635593964999999983e+00 4.517541333333329745e-01 +2.261101000000000083e-01 6.488236141666670065e+00 9.474941333333329607e-02 +2.399198999999999915e-01 5.939089416666670118e+00 8.864513000000000253e-02 +2.538528999999999924e-01 5.445706584999999933e+00 8.096326333333329905e-02 +2.677785999999999778e-01 5.020975218333330048e+00 7.440106833333330616e-02 +2.819448999999999983e-01 4.782109235000000069e+00 6.958310166666670238e-02 +2.958067999999999809e-01 4.377367741666669865e+00 6.553075166666670615e-02 +3.100794000000000050e-01 4.070373518333330054e+00 5.980910666666670178e-02 +3.242476000000000247e-01 3.688249826666670117e+00 5.775312500000000238e-02 +3.389940000000000175e-01 3.411490240000000007e+00 5.151532000000000333e-02 +3.541138999999999815e-01 3.221007965000000084e+00 5.041618500000000225e-02 +3.689172000000000007e-01 2.942401639999999929e+00 4.624927499999999941e-02 +3.841980999999999868e-01 2.767105533333329870e+00 4.386071833333329839e-02 +3.991226999999999969e-01 2.564823933333329808e+00 4.256582833333329846e-02 +4.106734000000000218e-01 2.300881685499999829e+00 4.238948299999999864e-02 +4.138850000000000029e-01 2.413576710000000070e+00 4.049426666666670199e-02 +4.293050999999999950e-01 2.233726248333329778e+00 3.713503166666669991e-02 +4.295516999999999808e-01 2.156876818999999834e+00 3.601888033333330158e-02 +4.448524999999999840e-01 1.902652508333330106e+00 3.572283666666670188e-02 +4.466716000000000020e-01 1.934442038833330102e+00 3.205653166666670023e-02 +4.605926000000000187e-01 1.874115690000000001e+00 3.360869833333329781e-02 +4.685437000000000074e-01 1.737730488500000003e+00 3.144913299999999717e-02 +4.767550999999999872e-01 1.772107871666670054e+00 3.193823833333329920e-02 +4.834083000000000130e-01 1.581873448666669901e+00 2.976716433333330067e-02 +4.925984999999999947e-01 1.570737606666670061e+00 3.137136000000000091e-02 +5.046621000000000024e-01 1.492830698499999942e+00 2.756047650000000016e-02 +5.085577999999999488e-01 1.485373084999999982e+00 2.891283166666670096e-02 +5.230728999999999518e-01 1.159546245333330061e+00 2.533816566666670031e-02 +5.252126000000000294e-01 1.354185358333330091e+00 2.695080999999999866e-02 +5.419834999999999514e-01 1.186315898333329955e+00 2.629908666666670031e-02 +5.444001000000000534e-01 1.201936935333330014e+00 2.613158683333329999e-02 +5.586221000000000103e-01 1.109243371666670086e+00 2.550917499999999852e-02 +5.612614999999999688e-01 1.098874749833330000e+00 2.373973216666670077e-02 +5.757206000000000268e-01 1.029040688333330067e+00 2.359920000000000073e-02 +5.834072999999999620e-01 9.811238644999999980e-01 2.340685516666669852e-02 +5.930746000000000073e-01 9.534806916666670462e-01 2.272023833333329870e-02 +6.005747000000000169e-01 8.956047156666669951e-01 2.218217800000000031e-02 +6.102501999999999649e-01 8.805252883333329894e-01 2.205115333333329888e-02 +6.218546000000000351e-01 8.076579554999999688e-01 2.150926133333330020e-02 +6.278546000000000404e-01 8.143317416666669972e-01 2.081983000000000097e-02 +6.412082999999999533e-01 7.185076576666670212e-01 1.917512450000000146e-02 +6.450795999999999752e-01 7.187583950000000499e-01 2.116659499999999985e-02 +6.655497999999999692e-01 6.417931828333329758e-01 1.838261283333330123e-02 +6.843702000000000396e-01 5.677104609999999996e-01 2.033867249999999835e-02 +7.058105999999999547e-01 5.146355146666670155e-01 1.728668516666670082e-02 +7.258693999999999980e-01 5.183306771666670310e-01 1.859332783333330144e-02 +7.490480000000000471e-01 4.413184218333329745e-01 1.519721516666669957e-02 +7.720086999999999922e-01 4.069291728333330194e-01 1.722663266666669968e-02 +7.920989999999999975e-01 3.912903401666669723e-01 1.517220816666670080e-02 +8.155097999999999514e-01 3.620392363333330144e-01 1.546880433333329939e-02 +8.364475000000000104e-01 3.218374210000000124e-01 1.467598383333330002e-02 +8.619377000000000288e-01 2.793489951666670024e-01 1.373524349999999915e-02 +8.846403999999999934e-01 2.729738648333330242e-01 1.444214883333330007e-02 +9.081462000000000145e-01 2.731246803333329809e-01 1.275928200000000026e-02 +9.334447000000000161e-01 2.513956430000000020e-01 1.371339566666670076e-02 +9.551832000000000100e-01 2.075616728333329886e-01 1.280443116666669934e-02 +9.812971999999999806e-01 2.026315226666670077e-01 1.202260966666669935e-02 +1.006807800000000030e+00 1.715856353333329865e-01 1.237966916666670067e-02 +1.030406600000000061e+00 1.813217336666670121e-01 1.183022066666669994e-02 +1.057784300000000011e+00 1.874246033333329953e-01 1.099227083333330010e-02 +1.084600999999999926e+00 1.682342156666669919e-01 1.130467983333329970e-02 +1.109902299999999897e+00 1.305937931666669993e-01 1.088363799999999930e-02 +1.137751600000000085e+00 1.264251891666670069e-01 1.045857849999999936e-02 +1.166036300000000026e+00 1.206125236666669986e-01 1.021913349999999977e-02 \ No newline at end of file diff --git a/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/7_200_1340_10.csv b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/7_200_1340_10.csv new file mode 100644 index 00000000..e8e9c9a0 --- /dev/null +++ b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/7_200_1340_10.csv @@ -0,0 +1,105 @@ +3.625239999999999713e-02 8.265676436333329491e+01 1.638320715333329858e+01 +4.070000000000000007e-02 5.464383480166669926e+01 1.136626790833330070e+01 +4.511180000000000051e-02 2.996549328500000087e+01 8.362137733333330658e+00 +4.959249999999999770e-02 3.902000664833330035e+01 6.829579628333330099e+00 +5.416379999999999806e-02 2.960868386499999971e+01 5.359635035000000158e+00 +5.868999999999999911e-02 2.775202533166670094e+01 4.620562245000000345e+00 +6.315320000000000655e-02 2.923420741833329828e+01 3.806268636666669813e+00 +6.773540000000000116e-02 2.925570898666670061e+01 3.181662941666670186e+00 +7.238989999999999314e-02 2.438109743166669929e+01 2.769415394999999780e+00 +7.694339999999999513e-02 2.082174377999999848e+01 2.449056793333329818e+00 +8.166600000000000248e-02 2.093264007666670068e+01 2.067833788333329981e+00 +8.641719999999999957e-02 2.065156679166669917e+01 1.869354308333329939e+00 +9.109140000000000292e-02 2.096371475999999845e+01 1.658336066666669995e+00 +9.576210000000000278e-02 1.686565956500000141e+01 1.512923419999999908e+00 +1.005216000000000026e-01 1.674424940666670025e+01 1.343149441666670052e+00 +1.052672000000000052e-01 1.453594207833329932e+01 1.233355748333329949e+00 +1.100174000000000013e-01 1.743436942499999986e+01 1.141890178333329953e+00 +1.148721999999999938e-01 1.514907325499999935e+01 1.030380964999999982e+00 +1.198083000000000065e-01 1.335097668833330076e+01 9.427780516666669497e-01 +1.248023999999999939e-01 1.334016233833330034e+01 8.846290449999999472e-01 +1.298709000000000113e-01 1.370878951499999943e+01 8.044898483333330352e-01 +1.349431000000000103e-01 1.169732300833329930e+01 7.648545733333329544e-01 +1.399610000000000021e-01 1.178831958166670013e+01 6.994071549999999471e-01 +1.451112999999999986e-01 1.065116290166669977e+01 6.676857533333330208e-01 +1.503050999999999970e-01 9.325193926666669242e+00 6.273500633333329857e-01 +1.556469000000000047e-01 1.029459589333329994e+01 5.781039766666670188e-01 +1.572456999999999883e-01 1.000640059000000015e+01 1.830944850000000013e-01 +1.609891999999999990e-01 1.045812633999999974e+01 5.605480683333330383e-01 +1.662309999999999899e-01 9.379805284999999770e+00 5.273011283333329802e-01 +1.706292000000000086e-01 9.152038171666669442e+00 1.545153300000000063e-01 +1.716062999999999894e-01 9.072271783333329509e+00 5.002084749999999858e-01 +1.771733000000000058e-01 8.299759079999999400e+00 4.647556766666670058e-01 +1.827794000000000085e-01 8.918414625000000484e+00 4.514085349999999996e-01 +1.842428999999999872e-01 8.303275895000000517e+00 1.338776349999999948e-01 +1.883665999999999952e-01 8.087114149999999668e+00 4.329608600000000029e-01 +1.940302000000000138e-01 6.940626968333329927e+00 4.097805333333329747e-01 +1.982414999999999872e-01 7.399220633333330355e+00 1.156899150000000043e-01 +1.998185000000000100e-01 7.671813116666670318e+00 3.925434549999999856e-01 +2.056348000000000065e-01 7.058416683333329722e+00 3.753756766666669908e-01 +2.115208000000000088e-01 6.186979153333330039e+00 3.502447450000000240e-01 +2.120113000000000136e-01 6.509825833333329648e+00 1.046767516666669978e-01 +2.162467999999999890e-01 6.237968744999999871e+00 4.443455150000000242e-01 +2.259823999999999999e-01 6.054194650000000344e+00 9.278840666666669790e-02 +2.397845000000000115e-01 5.564811654999999746e+00 8.685724833333340056e-02 +2.537096999999999825e-01 5.167096618333330227e+00 7.958348833333329930e-02 +2.676275000000000182e-01 4.857346653333330266e+00 7.348198000000000230e-02 +2.817857999999999752e-01 4.540460176666670122e+00 6.833893833333329337e-02 +2.956398000000000081e-01 4.237780543333330208e+00 6.471250666666669704e-02 +3.099044000000000243e-01 3.935048769999999863e+00 5.903304000000000190e-02 +3.240645999999999805e-01 3.478254921666669830e+00 5.662396666666669881e-02 +3.388027000000000122e-01 3.303115776666670111e+00 5.088800999999999741e-02 +3.539140000000000064e-01 3.111107356666670043e+00 4.976782999999999901e-02 +3.687090000000000090e-01 2.860701315000000022e+00 4.573441000000000312e-02 +3.839813000000000254e-01 2.699693565000000017e+00 4.341666500000000012e-02 +3.988975000000000160e-01 2.495346641666670084e+00 4.210437499999999944e-02 +4.106734000000000218e-01 2.304978501333330154e+00 4.227540733333329942e-02 +4.136514000000000024e-01 2.281161938333330141e+00 3.975256666666669714e-02 +4.290628000000000219e-01 2.190969609999999790e+00 3.681797500000000278e-02 +4.295516999999999808e-01 2.110397390999999789e+00 3.571657216666670326e-02 +4.446014999999999828e-01 1.879588453333330023e+00 3.550642500000000118e-02 +4.466716000000000020e-01 1.816268564666670082e+00 3.148649049999999866e-02 +4.603325999999999807e-01 1.793649588333330103e+00 3.313105499999999953e-02 +4.685437000000000074e-01 1.770170460833329962e+00 3.148902633333330175e-02 +4.764860000000000206e-01 1.682140176666669928e+00 3.141608500000000331e-02 +4.834083000000000130e-01 1.536765404999999918e+00 2.947832866666669910e-02 +4.923204999999999942e-01 1.511959243333329983e+00 3.099057499999999937e-02 +5.046621000000000024e-01 1.465441131499999994e+00 2.736933133333329868e-02 +5.082708000000000226e-01 1.365583310000000106e+00 2.825172333333330135e-02 +5.230728999999999518e-01 1.154780808166669948e+00 2.524863800000000033e-02 +5.249162000000000550e-01 1.329800611666670074e+00 2.675280666666670151e-02 +5.416775999999999813e-01 1.188958583333330044e+00 2.623523499999999911e-02 +5.444001000000000534e-01 1.154485974333330001e+00 2.585743066666670170e-02 +5.583067999999999920e-01 1.077189841666670089e+00 2.527868166666669830e-02 +5.612614999999999688e-01 1.051792144833330056e+00 2.347724083333330158e-02 +5.753956999999999544e-01 1.014095294999999952e+00 2.346036166666670003e-02 +5.834072999999999620e-01 9.588631039999999661e-01 2.325032483333330097e-02 +5.927398999999999862e-01 9.388917833333330076e-01 2.258296499999999998e-02 +6.005747000000000169e-01 8.756694673333329515e-01 2.203778849999999886e-02 +6.099058000000000535e-01 8.473022983333330371e-01 2.182166666666670099e-02 +6.218546000000000351e-01 7.881510335000000422e-01 2.137202599999999883e-02 +6.275003000000000108e-01 7.785123416666670515e-01 2.058900166666670015e-02 +6.412082999999999533e-01 7.088910750000000371e-01 1.908878433333329946e-02 +6.447154999999999969e-01 6.898300916666669780e-01 2.096122833333330035e-02 +6.655497999999999692e-01 6.325061706666670336e-01 1.830133016666669887e-02 +6.843702000000000396e-01 5.535274884999999978e-01 2.022582083333330019e-02 +7.058105999999999547e-01 4.772884753333330177e-01 1.710068249999999873e-02 +7.258693999999999980e-01 4.794482545000000040e-01 1.838371499999999839e-02 +7.490480000000000471e-01 4.303608649999999813e-01 1.512209783333329921e-02 +7.720086999999999922e-01 3.958678310000000033e-01 1.713988450000000080e-02 +7.920989999999999975e-01 3.804415833333329999e-01 1.509590366666670007e-02 +8.155097999999999514e-01 3.391658666666669819e-01 1.534219399999999997e-02 +8.364475000000000104e-01 2.920511656666670008e-01 1.453006433333330072e-02 +8.619377000000000288e-01 2.768105268333330149e-01 1.369777266666669970e-02 +8.846403999999999934e-01 2.773741731666670152e-01 1.443021766666669967e-02 +9.081462000000000145e-01 2.284839270000000033e-01 1.257401600000000036e-02 +9.334447000000000161e-01 2.262680790000000108e-01 1.358766933333330033e-02 +9.551832000000000100e-01 2.014745451666669906e-01 1.275664699999999943e-02 +9.812971999999999806e-01 1.828399243333329871e-01 1.193000216666669985e-02 +1.006807800000000030e+00 1.768480391666669982e-01 1.237525766666669989e-02 +1.030406600000000061e+00 1.607457411666670111e-01 1.173357283333329934e-02 +1.057784300000000011e+00 1.603443938333329877e-01 1.087976483333330004e-02 +1.084600999999999926e+00 1.672401265000000026e-01 1.127955949999999950e-02 +1.109902299999999897e+00 1.308651896666669923e-01 1.086452716666670010e-02 +1.137751600000000085e+00 1.208445169999999985e-01 1.042120399999999988e-02 +1.166036300000000026e+00 1.219414448333329959e-01 1.020481383333330001e-02 \ No newline at end of file diff --git a/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/8_300_1340_10.csv b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/8_300_1340_10.csv new file mode 100644 index 00000000..c576fb9b --- /dev/null +++ b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/8_300_1340_10.csv @@ -0,0 +1,105 @@ +3.625710000000000044e-02 7.586499487000000386e+01 1.637401323166670153e+01 +4.070529999999999982e-02 4.481643682666670259e+01 1.134473448500000003e+01 +4.511769999999999670e-02 3.187475403666670104e+01 8.372120233333330219e+00 +4.959899999999999726e-02 2.997679580000000144e+01 6.801308398333329563e+00 +5.417079999999999812e-02 2.394855565499999983e+01 5.340431286666669664e+00 +5.869760000000000255e-02 2.142582576833330066e+01 4.594792676666670239e+00 +6.316149999999999542e-02 3.089957102166669856e+01 3.816311058333329953e+00 +6.774429999999999341e-02 2.654007947166670078e+01 3.169185818333330129e+00 +7.239929999999999977e-02 1.943375908333329960e+01 2.742530958333329938e+00 +7.695340000000000513e-02 1.976728253666669843e+01 2.443851029999999813e+00 +8.167670000000000485e-02 1.925900297000000094e+01 2.058348746666669893e+00 +8.642850000000000532e-02 1.891829230833329945e+01 1.858069830000000033e+00 +9.110319999999999530e-02 1.827805419833330092e+01 1.639507476666669961e+00 +9.577460000000000140e-02 1.569160155166670023e+01 1.504524439999999963e+00 +1.005346000000000017e-01 1.326347007166669911e+01 1.315847523333329994e+00 +1.052808999999999967e-01 1.528716061000000082e+01 1.240626203333329958e+00 +1.100316999999999962e-01 1.357029930000000029e+01 1.108701733333329997e+00 +1.148871000000000059e-01 1.268526336333330029e+01 1.008324261666670107e+00 +1.198237999999999942e-01 1.157206131499999913e+01 9.266636050000000013e-01 +1.248187000000000046e-01 1.139260776499999928e+01 8.664271866666669597e-01 +1.298877999999999977e-01 1.288652256499999993e+01 7.969746983333330093e-01 +1.349605999999999861e-01 1.079102049499999971e+01 7.560191466666670301e-01 +1.399791999999999981e-01 1.105679143000000053e+01 6.920319183333329960e-01 +1.451301000000000119e-01 1.044385611833330074e+01 6.659680816666669889e-01 +1.503246000000000027e-01 9.389620521666669717e+00 6.284466633333329888e-01 +1.556671000000000027e-01 9.365269319999999453e+00 5.685768949999999711e-01 +1.572933999999999999e-01 9.202126339999999516e+00 1.807199866666669985e-01 +1.610100999999999893e-01 9.725111731666670423e+00 5.526473100000000027e-01 +1.662526000000000004e-01 8.453553321666669618e+00 5.170390433333329483e-01 +1.706808999999999965e-01 8.508661829999999426e+00 1.525917300000000087e-01 +1.716285999999999923e-01 8.248625778333330771e+00 4.912094450000000223e-01 +1.771963000000000010e-01 7.860151886666669974e+00 4.601271083333329792e-01 +1.828030999999999962e-01 8.179063558333330874e+00 4.431557566666670112e-01 +1.842987000000000097e-01 7.789301021666670266e+00 1.322509683333329966e-01 +1.883911000000000058e-01 7.284922560000000047e+00 4.239183200000000151e-01 +1.940553999999999890e-01 6.860865920000000173e+00 4.091373166666669725e-01 +1.983015999999999945e-01 6.984366938333329777e+00 1.143283233333329957e-01 +1.998445000000000082e-01 7.321059296666669880e+00 3.888106266666669919e-01 +2.056614999999999971e-01 6.516638536666669701e+00 3.691254600000000163e-01 +2.115482999999999947e-01 5.813378858333329902e+00 3.460794933333329881e-01 +2.120755000000000001e-01 6.410695145000000039e+00 1.044179233333329959e-01 +2.162748999999999922e-01 6.046752721666670105e+00 4.416217800000000193e-01 +2.260508999999999991e-01 5.970100884999999913e+00 9.257100833333330170e-02 +2.398570999999999898e-01 5.328183368333330172e+00 8.599138666666669706e-02 +2.537865999999999733e-01 5.025059630000000332e+00 7.909936500000000481e-02 +2.677086000000000188e-01 4.638679231666669622e+00 7.265197833333329747e-02 +2.818711999999999884e-01 4.320586221666670390e+00 6.748263000000000178e-02 +2.957293999999999756e-01 4.003143766666670267e+00 6.376038000000000538e-02 +3.099983000000000044e-01 3.820362695000000031e+00 5.861029000000000239e-02 +3.241628999999999761e-01 3.476478435000000200e+00 5.668581000000000314e-02 +3.389054000000000233e-01 3.203212216666670109e+00 5.052067666666670148e-02 +3.540212999999999832e-01 3.082675891666669887e+00 4.970334166666669912e-02 +3.688208000000000042e-01 2.852994409999999981e+00 4.575685000000000169e-02 +3.840975999999999835e-01 2.619206445000000105e+00 4.310591500000000159e-02 +3.990183999999999953e-01 2.447015261666670050e+00 4.192961333333330293e-02 +4.106022999999999756e-01 2.250148667666670210e+00 4.208387400000000028e-02 +4.137768000000000002e-01 2.199470903333330174e+00 3.941821333333329902e-02 +4.291927999999999854e-01 2.152990283333330090e+00 3.668624166666670239e-02 +4.294773000000000063e-01 2.066339865166670009e+00 3.559078633333329772e-02 +4.447362000000000259e-01 1.856158275000000080e+00 3.543832166666670280e-02 +4.465942999999999996e-01 1.863514126000000104e+00 3.172593550000000345e-02 +4.604721999999999982e-01 1.785743791666670077e+00 3.313367500000000132e-02 +4.684626000000000068e-01 1.701883971166670007e+00 3.125393900000000141e-02 +4.766304000000000096e-01 1.673134868333330028e+00 3.141082333333330284e-02 +4.833245999999999931e-01 1.561424375333329895e+00 2.963426116666669982e-02 +4.924697000000000102e-01 1.528741043333329941e+00 3.110879000000000075e-02 +5.045747000000000426e-01 1.440318964999999896e+00 2.730927600000000038e-02 +5.084248000000000101e-01 1.414442154999999923e+00 2.852017166666670142e-02 +5.229823000000000111e-01 1.195010044500000035e+00 2.544837383333330150e-02 +5.250753000000000226e-01 1.305492214999999900e+00 2.666661666666670163e-02 +5.418418000000000401e-01 1.152673440000000049e+00 2.608773333333330030e-02 +5.443057999999999508e-01 1.145610492499999911e+00 2.585580999999999990e-02 +5.584759999999999724e-01 1.067225758333329999e+00 2.525753999999999846e-02 +5.611642999999999493e-01 1.067666433166670092e+00 2.357715833333329930e-02 +5.755700000000000260e-01 1.029394661666670041e+00 2.355942499999999842e-02 +5.833063000000000553e-01 9.377488744999999959e-01 2.319254466666669998e-02 +5.929195000000000437e-01 9.657185166666669707e-01 2.274125999999999925e-02 +6.004707000000000239e-01 8.602252591666670334e-01 2.200228683333330104e-02 +6.100906000000000384e-01 8.435776883333330201e-01 2.182633499999999879e-02 +6.217470000000000496e-01 7.760975660000000165e-01 2.134885499999999992e-02 +6.276903999999999817e-01 7.741666183333330009e-01 2.058969000000000077e-02 +6.410972000000000337e-01 7.019246166666669451e-01 1.908517183333329967e-02 +6.449108999999999536e-01 6.851353900000000108e-01 2.095862666666669857e-02 +6.654345000000000399e-01 5.961630181666669470e-01 1.818150283333330036e-02 +6.842517000000000182e-01 5.507593983333329835e-01 2.023759049999999948e-02 +7.056883000000000461e-01 4.903288296666670210e-01 1.717216466666670119e-02 +7.257436999999999916e-01 4.865799499999999833e-01 1.843612033333329875e-02 +7.489183000000000368e-01 4.252237993333329857e-01 1.512044316666670031e-02 +7.718749999999999778e-01 3.910920323333330062e-01 1.713838283333329882e-02 +7.919618000000000491e-01 3.921714671666670093e-01 1.515910683333330025e-02 +8.153685999999999989e-01 3.357406613333330236e-01 1.534501599999999952e-02 +8.363026999999999544e-01 3.075498956666670169e-01 1.460560383333329992e-02 +8.617884000000000100e-01 2.857851470000000171e-01 1.374501849999999921e-02 +8.844872000000000289e-01 2.740424961666669823e-01 1.443190633333329975e-02 +9.079890000000000461e-01 2.574230581666669959e-01 1.269050000000000039e-02 +9.332829999999999737e-01 2.258197691666669893e-01 1.359980449999999980e-02 +9.550178000000000278e-01 1.934376846666669980e-01 1.273933533333329940e-02 +9.811271999999999771e-01 1.665732448333329951e-01 1.188447333333329976e-02 +1.006633399999999900e+00 1.640185443333329884e-01 1.234030849999999922e-02 +1.030228200000000038e+00 1.357939996666669979e-01 1.165456533333330061e-02 +1.057601100000000072e+00 1.607766528333330058e-01 1.089187166666670016e-02 +1.084413199999999966e+00 1.505459054999999935e-01 1.123158833333329915e-02 +1.109710100000000033e+00 1.282342724999999961e-01 1.086581349999999994e-02 +1.137554500000000024e+00 1.161345933333329944e-01 1.041549200000000015e-02 +1.165834400000000048e+00 1.239725849999999963e-01 1.022116500000000081e-02 \ No newline at end of file diff --git a/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/9_600_1340_10.csv b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/9_600_1340_10.csv new file mode 100644 index 00000000..c869ca7d --- /dev/null +++ b/test/trend_test_data/FeNiB_perpendicular_Bersweiler_et_al/9_600_1340_10.csv @@ -0,0 +1,105 @@ +3.625239999999999713e-02 5.114892415500000311e+01 1.631213251166670020e+01 +4.070000000000000007e-02 4.576787608166669941e+01 1.134772132833330005e+01 +4.511180000000000051e-02 4.331816215999999997e+01 8.407984515000000769e+00 +4.959249999999999770e-02 2.519454865000000154e+01 6.784784965000000057e+00 +5.416379999999999806e-02 1.407789967999999980e+01 5.302135776666670353e+00 +5.868999999999999911e-02 1.337527417166669963e+01 4.558895663333330184e+00 +6.315320000000000655e-02 2.088227957833329995e+01 3.767553325000000175e+00 +6.773540000000000116e-02 2.352067832833330030e+01 3.153463791666669902e+00 +7.238989999999999314e-02 1.459065788833330046e+01 2.714546644999999980e+00 +7.694339999999999513e-02 1.643097480333329941e+01 2.423213508333330157e+00 +8.166600000000000248e-02 1.564276924666670077e+01 2.035339249999999822e+00 +8.641719999999999957e-02 1.609254856500000130e+01 1.837833853333330048e+00 +9.109140000000000292e-02 1.279946007166670086e+01 1.598433549999999981e+00 +9.576210000000000278e-02 1.168004799999999932e+01 1.472505774999999906e+00 +1.005216000000000026e-01 1.239115217500000021e+01 1.308776653333330042e+00 +1.052672000000000052e-01 1.104442360666669920e+01 1.203200884999999998e+00 +1.100174000000000013e-01 1.264824788499999997e+01 1.100505863333330003e+00 +1.148721999999999938e-01 1.068150069333329988e+01 9.895295950000000396e-01 +1.198083000000000065e-01 1.062919121333329997e+01 9.177280949999999660e-01 +1.248023999999999939e-01 8.480228023333330256e+00 8.376397483333329896e-01 +1.298709000000000113e-01 1.054387282333330056e+01 7.736622133333329598e-01 +1.349431000000000103e-01 8.823326103333329229e+00 7.353996000000000421e-01 +1.399610000000000021e-01 9.107303678333330765e+00 6.707318450000000487e-01 +1.451112999999999986e-01 7.568946575000000010e+00 6.354211899999999691e-01 +1.503050999999999970e-01 7.486937655000000191e+00 6.076642716666670330e-01 +1.556469000000000047e-01 8.316071640000000542e+00 5.572019216666670438e-01 +1.572933999999999999e-01 8.082806423333330770e+00 1.772221983333329975e-01 +1.609891999999999990e-01 8.406204656666670161e+00 5.374420016666670019e-01 +1.662309999999999899e-01 7.305961626666669595e+00 5.035922400000000243e-01 +1.706808999999999965e-01 7.409980823333330413e+00 1.490785450000000067e-01 +1.716062999999999894e-01 7.126663556666669841e+00 4.782201200000000263e-01 +1.771733000000000058e-01 6.104670323333330373e+00 4.398585833333329975e-01 +1.827794000000000085e-01 6.314649313333330127e+00 4.207942599999999755e-01 +1.842987000000000097e-01 6.985669296666669581e+00 1.295300000000000062e-01 +1.883665999999999952e-01 7.196837438333330006e+00 4.229003000000000068e-01 +1.940302000000000138e-01 5.456149725000000394e+00 3.926154199999999928e-01 +1.983015999999999945e-01 6.101519538333329606e+00 1.111746183333330029e-01 +1.998185000000000100e-01 6.715507050000000255e+00 3.818104066666669905e-01 +2.056348000000000065e-01 5.954089436666669677e+00 3.622545733333329965e-01 +2.115208000000000088e-01 5.823000606666670187e+00 3.462099583333330122e-01 +2.120755000000000001e-01 5.689073146666670411e+00 1.016859966666670001e-01 +2.162467999999999890e-01 5.274567024999999632e+00 4.291691899999999782e-01 +2.260508999999999991e-01 5.306054011666669901e+00 9.002336499999999408e-02 +2.398570999999999898e-01 4.872989734999999989e+00 8.416759833333330165e-02 +2.537865999999999733e-01 4.590923198333330291e+00 7.736351666666670124e-02 +2.677086000000000188e-01 4.288802803333330083e+00 7.121471833333330170e-02 +2.818711999999999884e-01 4.093612151666669696e+00 6.655526166666669852e-02 +2.957293999999999756e-01 3.790488160000000217e+00 6.286535166666669394e-02 +3.099983000000000044e-01 3.646502630000000078e+00 5.789795166666669712e-02 +3.241628999999999761e-01 3.269809508333330061e+00 5.578990999999999811e-02 +3.389054000000000233e-01 3.057003066666669877e+00 4.992672166666670130e-02 +3.540212999999999832e-01 3.010102380000000188e+00 4.942106333333329965e-02 +3.688208000000000042e-01 2.669906414999999811e+00 4.496581333333329877e-02 +3.840975999999999835e-01 2.619258584999999862e+00 4.314465499999999704e-02 +3.990183999999999953e-01 2.335828473333330102e+00 4.143680999999999753e-02 +4.106379000000000001e-01 2.148179208166669962e+00 4.160297799999999879e-02 +4.137768000000000002e-01 2.129094906666670006e+00 3.911823833333329808e-02 +4.291927999999999854e-01 2.070782439999999891e+00 3.633175833333329718e-02 +4.295145000000000213e-01 2.006558257333329820e+00 3.534480766666669993e-02 +4.447362000000000259e-01 1.796411879999999961e+00 3.518650999999999723e-02 +4.466328999999999994e-01 1.795892682000000073e+00 3.145603500000000025e-02 +4.604721999999999982e-01 1.744876515000000072e+00 3.296921333333330262e-02 +4.685032000000000085e-01 1.640983006833329982e+00 3.099933850000000102e-02 +4.766304000000000096e-01 1.643527214999999986e+00 3.129624333333329983e-02 +4.833663999999999739e-01 1.494623838666669924e+00 2.933965966666670158e-02 +4.924697000000000102e-01 1.485128543333330109e+00 3.092004166666669981e-02 +5.046184000000000225e-01 1.428664912499999939e+00 2.726148050000000087e-02 +5.084248000000000101e-01 1.392996424999999983e+00 2.844018666666670025e-02 +5.230276000000000369e-01 1.160762609500000098e+00 2.531033383333329903e-02 +5.250753000000000226e-01 1.272159111666669951e+00 2.652694499999999969e-02 +5.418418000000000401e-01 1.157124196666670102e+00 2.613007999999999997e-02 +5.443529999999999758e-01 1.137421045666670016e+00 2.582160033333329857e-02 +5.584759999999999724e-01 1.068463686666669910e+00 2.528335666666670090e-02 +5.612129000000000145e-01 1.030207043666669930e+00 2.342105016666670009e-02 +5.755700000000000260e-01 9.590708749999999894e-01 2.323855999999999838e-02 +5.833568000000000087e-01 9.226566683333330410e-01 2.312843766666669923e-02 +5.929195000000000437e-01 9.361494049999999900e-01 2.261169999999999847e-02 +6.005226999999999649e-01 8.377321681666669573e-01 2.190618299999999921e-02 +6.100906000000000384e-01 8.074355933333330348e-01 2.165867833333329912e-02 +6.218008000000000424e-01 7.338905468333329907e-01 2.116956166666670094e-02 +6.276903999999999817e-01 7.588048750000000453e-01 2.052974999999999939e-02 +6.411527999999999672e-01 6.602236648333329461e-01 1.891721183333330142e-02 +6.449108999999999536e-01 6.976061483333330093e-01 2.103810166666670103e-02 +6.654921999999999782e-01 5.868389913333329488e-01 1.814479133333329886e-02 +6.843110000000000026e-01 5.442379206666669855e-01 2.020865366666670104e-02 +7.057493999999999712e-01 4.561023925000000090e-01 1.703726683333330050e-02 +7.258065000000000211e-01 4.680557555000000036e-01 1.835656600000000124e-02 +7.489831000000000127e-01 4.286472281666670048e-01 1.513358700000000043e-02 +7.719418000000000113e-01 3.942902281666669784e-01 1.715291783333329836e-02 +7.920304000000000233e-01 3.917498161666669865e-01 1.515774683333329965e-02 +8.154392000000000307e-01 3.437863165000000221e-01 1.537817166666670052e-02 +8.363751000000000380e-01 2.882515494999999817e-01 1.453092849999999998e-02 +8.618630000000000457e-01 2.511196196666670155e-01 1.361783299999999933e-02 +8.845638000000000112e-01 2.644882184999999830e-01 1.439263916666670001e-02 +9.080675999999999748e-01 2.444909116666670046e-01 1.264447816666670020e-02 +9.333637999999999657e-01 2.291532736666669900e-01 1.361314100000000048e-02 +9.551005000000000189e-01 1.942403736666669933e-01 1.274254833333329957e-02 +9.812121999999999788e-01 1.854263468333330056e-01 1.195107366666670057e-02 +1.006720599999999965e+00 1.833105325000000119e-01 1.241118433333330065e-02 +1.030317399999999939e+00 1.560276093333330116e-01 1.172809033333330024e-02 +1.057692700000000041e+00 1.687616888333330067e-01 1.091902633333330028e-02 +1.084507099999999946e+00 1.609880563333329906e-01 1.126864266666669986e-02 +1.109806099999999907e+00 1.237764021666669934e-01 1.085079116666669979e-02 +1.137653000000000025e+00 1.252411816666670064e-01 1.044549983333330039e-02 +1.165935399999999955e+00 1.143170270000000016e-01 1.018908599999999998e-02 \ No newline at end of file diff --git a/test/trend_test_data/Nanoperm_perpendicular_Honecker_et_al/1_33_1640_22.874115.csv b/test/trend_test_data/Nanoperm_perpendicular_Honecker_et_al/1_33_1640_22.874115.csv new file mode 100644 index 00000000..160f11da --- /dev/null +++ b/test/trend_test_data/Nanoperm_perpendicular_Honecker_et_al/1_33_1640_22.874115.csv @@ -0,0 +1,60 @@ +2.732000000000000053e-02 5.247581629999999677e+03 5.122294999999999732e+01 +3.090999999999999998e-02 3.911350339999999960e+03 4.422301999999999822e+01 +3.452999999999999819e-02 2.897911560000000009e+03 3.806515000000000271e+01 +3.812999999999999723e-02 2.151411560000000009e+03 3.279795000000000016e+01 +4.173999999999999932e-02 1.629610879999999952e+03 2.854479999999999862e+01 +4.542999999999999816e-02 1.229812930000000051e+03 2.479730999999999952e+01 +4.646000000000000130e-02 1.191418850000000020e+03 2.440716000000000108e+01 +4.909000000000000169e-02 9.198557799999999816e+02 2.144593000000000060e+01 +5.254999999999999949e-02 7.981839199999999437e+02 1.997728999999999999e+01 +5.270999999999999991e-02 7.205761899999999969e+02 1.898125999999999891e+01 +5.630999999999999894e-02 5.509914999999999736e+02 1.659806000000000026e+01 +5.870999999999999830e-02 5.012570499999999925e+02 1.583125000000000071e+01 +5.988000000000000267e-02 4.332142900000000054e+02 1.471757999999999988e+01 +6.353999999999999926e-02 3.420333299999999781e+02 1.307732999999999990e+01 +6.482999999999999874e-02 3.398080699999999865e+02 1.303472000000000008e+01 +6.716999999999999360e-02 2.726683699999999817e+02 1.167622000000000071e+01 +7.073000000000000120e-02 2.204248299999999858e+02 1.049821000000000026e+01 +7.095999999999999530e-02 2.235476199999999949e+02 1.057230999999999987e+01 +7.434999999999999942e-02 1.760958799999999940e+02 9.383390000000000342e+00 +7.724000000000000310e-02 1.542382200000000125e+02 8.781750000000000611e+00 +7.799999999999999989e-02 1.457825499999999863e+02 8.537639999999999674e+00 +8.164000000000000423e-02 1.201677600000000012e+02 7.751380000000000159e+00 +8.346000000000000640e-02 1.089934400000000068e+02 7.382189999999999586e+00 +8.525000000000000633e-02 9.836262000000000683e+01 7.012940000000000396e+00 +8.881999999999999618e-02 8.192439000000000249e+01 6.400170000000000137e+00 +8.962000000000000521e-02 7.733648999999999774e+01 6.218379999999999797e+00 +9.572999999999999565e-02 5.882012000000000285e+01 5.423099999999999810e+00 +1.018000000000000016e-01 4.406902000000000186e+01 4.694090000000000096e+00 +1.080300000000000010e-01 3.487624000000000279e+01 4.175900000000000389e+00 +1.141999999999999960e-01 2.769241999999999848e+01 3.721049999999999969e+00 +1.202399999999999997e-01 2.227824000000000026e+01 3.337530000000000108e+00 +1.263900000000000023e-01 1.858717000000000041e+01 3.048540000000000028e+00 +1.325999999999999956e-01 1.587791999999999959e+01 2.817619999999999791e+00 +1.388199999999999990e-01 1.416899000000000086e+01 2.661669999999999980e+00 +1.449499999999999955e-01 1.174597999999999942e+01 2.423430000000000195e+00 +1.509900000000000131e-01 1.047161000000000008e+01 2.288190000000000168e+00 +1.571599999999999941e-01 9.681599999999999540e+00 2.200180000000000025e+00 +1.633799999999999975e-01 8.894579999999999487e+00 2.108859999999999957e+00 +1.695599999999999885e-01 8.317479999999999762e+00 2.039299999999999891e+00 +1.756800000000000028e-01 7.913870000000000182e+00 1.989200000000000079e+00 +1.818599999999999939e-01 7.771530000000000271e+00 1.971230000000000038e+00 +1.880699999999999872e-01 7.882909999999999862e+00 1.985309999999999908e+00 +1.941799999999999915e-01 7.707720000000000127e+00 1.963130000000000042e+00 +2.002800000000000136e-01 7.676859999999999573e+00 1.959189999999999987e+00 +2.064199999999999924e-01 7.872259999999999813e+00 1.983970000000000011e+00 +2.126000000000000112e-01 7.870400000000000063e+00 1.983729999999999993e+00 +2.187500000000000000e-01 7.977940000000000254e+00 1.997239999999999904e+00 +2.248699999999999866e-01 8.152039999999999509e+00 2.018920000000000048e+00 +2.310500000000000054e-01 8.298420000000000130e+00 2.036960000000000104e+00 +2.372700000000000087e-01 8.716689999999999827e+00 2.087660000000000071e+00 +2.434399999999999897e-01 8.782600000000000406e+00 2.095540000000000180e+00 +2.495600000000000041e-01 8.933479999999999421e+00 2.113469999999999960e+00 +2.556899999999999729e-01 9.374109999999999943e+00 2.164960000000000218e+00 +2.618500000000000272e-01 9.457430000000000447e+00 2.174560000000000048e+00 +2.680500000000000105e-01 9.702469999999999928e+00 2.202550000000000008e+00 +2.742100000000000093e-01 9.923930000000000362e+00 2.227549999999999919e+00 +2.802999999999999936e-01 1.008966000000000030e+01 2.246070000000000011e+00 +2.864599999999999924e-01 1.031367999999999974e+01 2.270869999999999944e+00 +2.926699999999999857e-01 1.058960000000000079e+01 2.301039999999999974e+00 +2.988199999999999745e-01 1.062256999999999962e+01 2.304619999999999891e+00 \ No newline at end of file diff --git a/test/trend_test_data/Nanoperm_perpendicular_Honecker_et_al/2_42_1640_23.456895.csv b/test/trend_test_data/Nanoperm_perpendicular_Honecker_et_al/2_42_1640_23.456895.csv new file mode 100644 index 00000000..80562dff --- /dev/null +++ b/test/trend_test_data/Nanoperm_perpendicular_Honecker_et_al/2_42_1640_23.456895.csv @@ -0,0 +1,60 @@ +2.732000000000000053e-02 3.883615650000000187e+03 4.406595000000000084e+01 +3.090000000000000038e-02 3.072013609999999971e+03 3.919192000000000320e+01 +3.452999999999999819e-02 2.355659860000000208e+03 3.431953000000000031e+01 +3.812999999999999723e-02 1.806985030000000052e+03 3.005815000000000126e+01 +4.173999999999999932e-02 1.389839799999999968e+03 2.636132999999999882e+01 +4.542000000000000204e-02 1.072684349999999995e+03 2.315906000000000020e+01 +4.644000000000000211e-02 9.624380499999999756e+02 2.193669999999999831e+01 +4.907999999999999863e-02 8.065360500000000457e+02 2.008153000000000077e+01 +5.253000000000000030e-02 6.666923000000000457e+02 1.825777000000000072e+01 +5.270999999999999991e-02 6.432540800000000445e+02 1.793395999999999901e+01 +5.630000000000000282e-02 4.985809499999999730e+02 1.578894000000000020e+01 +5.868999999999999911e-02 4.290448299999999904e+02 1.464658000000000015e+01 +5.986999999999999961e-02 3.918163299999999936e+02 1.399671999999999983e+01 +6.353000000000000314e-02 3.098850299999999720e+02 1.244758999999999993e+01 +6.481000000000000649e-02 2.946982499999999732e+02 1.213874000000000031e+01 +6.715999999999999748e-02 2.516384400000000028e+02 1.121692000000000000e+01 +7.072000000000000508e-02 2.028407500000000141e+02 1.007076999999999956e+01 +7.094000000000000306e-02 1.948358800000000031e+02 9.870050000000000878e+00 +7.434000000000000330e-02 1.633427599999999984e+02 9.037219999999999587e+00 +7.721000000000000085e-02 1.371933899999999937e+02 8.282310000000000727e+00 +7.799000000000000377e-02 1.347819700000000012e+02 8.209199999999999164e+00 +8.162999999999999423e-02 1.123165999999999940e+02 7.493879999999999875e+00 +8.343000000000000416e-02 9.602442000000000633e+01 6.929079999999999906e+00 +8.523999999999999633e-02 9.249591999999999814e+01 6.800589999999999691e+00 +8.880000000000000393e-02 7.934887999999999408e+01 6.298759999999999692e+00 +8.959000000000000297e-02 7.016214999999999691e+01 5.922930000000000028e+00 +9.568999999999999728e-02 5.357988000000000284e+01 5.175900000000000389e+00 +1.017699999999999994e-01 4.050641000000000247e+01 4.500359999999999694e+00 +1.079900000000000027e-01 3.194395000000000095e+01 3.996500000000000163e+00 +1.141599999999999976e-01 2.563089000000000084e+01 3.579870000000000108e+00 +1.202000000000000013e-01 2.144641999999999982e+01 3.274630000000000152e+00 +1.263499999999999901e-01 1.696240999999999843e+01 2.912249999999999783e+00 +1.325600000000000112e-01 1.453672000000000075e+01 2.695990000000000109e+00 +1.387700000000000045e-01 1.265718999999999994e+01 2.515670000000000073e+00 +1.449000000000000010e-01 1.114897000000000027e+01 2.361029999999999962e+00 +1.509399999999999908e-01 1.005156999999999989e+01 2.241830000000000211e+00 +1.571099999999999997e-01 9.313710000000000377e+00 2.157970000000000166e+00 +1.633300000000000030e-01 8.668530000000000513e+00 2.081890000000000018e+00 +1.695099999999999940e-01 7.810069999999999624e+00 1.976120000000000099e+00 +1.756300000000000083e-01 7.695079999999999920e+00 1.961519999999999930e+00 +1.817999999999999894e-01 7.222220000000000084e+00 1.900290000000000035e+00 +1.880100000000000104e-01 7.386999999999999567e+00 1.921850000000000058e+00 +1.941199999999999870e-01 7.239320000000000199e+00 1.902539999999999898e+00 +2.002200000000000091e-01 7.279099999999999682e+00 1.907759999999999900e+00 +2.063500000000000056e-01 7.406340000000000146e+00 1.924360000000000070e+00 +2.125299999999999967e-01 7.439199999999999591e+00 1.928630000000000067e+00 +2.186800000000000133e-01 7.556879999999999598e+00 1.943820000000000103e+00 +2.247899999999999898e-01 7.730520000000000280e+00 1.966029999999999944e+00 +2.309799999999999909e-01 8.106099999999999639e+00 2.013220000000000010e+00 +2.371999999999999942e-01 8.398609999999999687e+00 2.049220000000000041e+00 +2.433599999999999930e-01 8.171440000000000481e+00 2.021319999999999784e+00 +2.494800000000000073e-01 8.478289999999999438e+00 2.058920000000000083e+00 +2.556100000000000039e-01 8.796279999999999433e+00 2.097170000000000201e+00 +2.617700000000000027e-01 8.973420000000000840e+00 2.118189999999999795e+00 +2.679699999999999860e-01 9.270730000000000359e+00 2.152989999999999959e+00 +2.741299999999999848e-01 9.463879999999999626e+00 2.175300000000000011e+00 +2.802100000000000146e-01 9.409150000000000347e+00 2.169000000000000039e+00 +2.863700000000000134e-01 9.709509999999999863e+00 2.203349999999999920e+00 +2.925800000000000067e-01 9.851300000000000168e+00 2.219380000000000130e+00 +2.987299999999999955e-01 9.904920000000000613e+00 2.225410000000000110e+00 \ No newline at end of file diff --git a/test/trend_test_data/Nanoperm_perpendicular_Honecker_et_al/3_61_1640_23.748285.csv b/test/trend_test_data/Nanoperm_perpendicular_Honecker_et_al/3_61_1640_23.748285.csv new file mode 100644 index 00000000..6e68726d --- /dev/null +++ b/test/trend_test_data/Nanoperm_perpendicular_Honecker_et_al/3_61_1640_23.748285.csv @@ -0,0 +1,60 @@ +2.732000000000000053e-02 2.792353740000000016e+03 3.736545000000000272e+01 +3.090000000000000038e-02 2.271702040000000125e+03 3.370239000000000118e+01 +3.452000000000000207e-02 1.788897279999999910e+03 2.990733000000000175e+01 +3.812000000000000111e-02 1.407023470000000088e+03 2.652380000000000138e+01 +4.173000000000000320e-02 1.095009520000000066e+03 2.339882000000000062e+01 +4.542000000000000204e-02 8.376734699999999521e+02 2.046549999999999869e+01 +4.644000000000000211e-02 7.836296200000000454e+02 1.979430999999999941e+01 +4.907000000000000250e-02 6.414952399999999670e+02 1.790943000000000040e+01 +5.253000000000000030e-02 5.516777200000000221e+02 1.660839999999999961e+01 +5.269999999999999685e-02 5.141714299999999866e+02 1.603388999999999953e+01 +5.628999999999999976e-02 3.988418399999999906e+02 1.412165000000000070e+01 +5.868999999999999911e-02 3.579082799999999907e+02 1.337737000000000087e+01 +5.985999999999999654e-02 3.164397999999999911e+02 1.257854999999999990e+01 +6.351999999999999313e-02 2.486343500000000120e+02 1.114976000000000056e+01 +6.481000000000000649e-02 2.482066299999999899e+02 1.114016999999999946e+01 +6.715000000000000135e-02 2.006787799999999891e+02 1.001695999999999920e+01 +7.070999999999999508e-02 1.657927200000000028e+02 9.104739999999999611e+00 +7.094000000000000306e-02 1.667760000000000105e+02 9.131700000000000372e+00 +7.431999999999999718e-02 1.326982299999999952e+02 8.145500000000000185e+00 +7.721000000000000085e-02 1.181177100000000024e+02 7.684980000000000366e+00 +7.796999999999999764e-02 1.108474499999999949e+02 7.444709999999999717e+00 +8.161999999999999811e-02 9.087302999999999997e+01 6.740660000000000096e+00 +8.343000000000000416e-02 8.290343000000000018e+01 6.438299999999999912e+00 +8.522000000000000408e-02 7.588799000000000206e+01 6.159869999999999735e+00 +8.878999999999999393e-02 6.486343999999999710e+01 5.694890000000000008e+00 +8.959000000000000297e-02 6.133471999999999724e+01 5.537810000000000343e+00 +9.568999999999999728e-02 4.711737999999999715e+01 4.853729999999999656e+00 +1.017699999999999994e-01 3.554666000000000281e+01 4.215840000000000032e+00 +1.079900000000000027e-01 2.833098000000000027e+01 3.763710000000000111e+00 +1.141599999999999976e-01 2.318925000000000125e+01 3.405089999999999950e+00 +1.202000000000000013e-01 1.876435000000000031e+01 3.063029999999999919e+00 +1.263499999999999901e-01 1.568601999999999919e+01 2.800539999999999807e+00 +1.325600000000000112e-01 1.346410000000000018e+01 2.594619999999999926e+00 +1.387700000000000045e-01 1.173840000000000039e+01 2.422639999999999905e+00 +1.449000000000000010e-01 1.054757999999999996e+01 2.296469999999999789e+00 +1.509399999999999908e-01 9.084839999999999804e+00 2.131299999999999972e+00 +1.571099999999999997e-01 8.239589999999999748e+00 2.029729999999999812e+00 +1.633300000000000030e-01 7.767660000000000231e+00 1.970739999999999936e+00 +1.695099999999999940e-01 7.631929999999999659e+00 1.953449999999999909e+00 +1.756300000000000083e-01 7.253739999999999633e+00 1.904430000000000067e+00 +1.817999999999999894e-01 6.968580000000000219e+00 1.866630000000000011e+00 +1.880100000000000104e-01 7.029829999999999579e+00 1.874810000000000088e+00 +1.941199999999999870e-01 6.805609999999999715e+00 1.844670000000000032e+00 +2.002200000000000091e-01 7.007130000000000081e+00 1.871779999999999999e+00 +2.063500000000000056e-01 7.083709999999999951e+00 1.881979999999999986e+00 +2.125299999999999967e-01 6.971890000000000143e+00 1.867070000000000007e+00 +2.186800000000000133e-01 7.549360000000000070e+00 1.942849999999999966e+00 +2.247899999999999898e-01 7.451310000000000322e+00 1.930199999999999916e+00 +2.309799999999999909e-01 7.449589999999999712e+00 1.929969999999999963e+00 +2.371999999999999942e-01 7.776720000000000077e+00 1.971889999999999921e+00 +2.433599999999999930e-01 8.157130000000000436e+00 2.019550000000000178e+00 +2.494800000000000073e-01 8.339280000000000470e+00 2.041970000000000063e+00 +2.556100000000000039e-01 8.448100000000000165e+00 2.055250000000000021e+00 +2.617700000000000027e-01 8.746420000000000528e+00 2.091219999999999857e+00 +2.679699999999999860e-01 9.040720000000000312e+00 2.126110000000000166e+00 +2.741299999999999848e-01 9.130409999999999471e+00 2.136629999999999807e+00 +2.802100000000000146e-01 9.247719999999999274e+00 2.150319999999999787e+00 +2.863700000000000134e-01 9.291150000000000020e+00 2.155359999999999943e+00 +2.925800000000000067e-01 9.640140000000000597e+00 2.195469999999999811e+00 +2.987299999999999955e-01 9.537290000000000489e+00 2.183720000000000105e+00 \ No newline at end of file diff --git a/test/trend_test_data/Nanoperm_perpendicular_Honecker_et_al/4_103_1640_24.039675.csv b/test/trend_test_data/Nanoperm_perpendicular_Honecker_et_al/4_103_1640_24.039675.csv new file mode 100644 index 00000000..c3350620 --- /dev/null +++ b/test/trend_test_data/Nanoperm_perpendicular_Honecker_et_al/4_103_1640_24.039675.csv @@ -0,0 +1,60 @@ +2.733000000000000013e-02 2.320909860000000208e+03 3.406544999999999845e+01 +3.090999999999999998e-02 1.910828909999999951e+03 3.090978000000000137e+01 +3.454000000000000126e-02 1.519815650000000005e+03 2.756643000000000043e+01 +3.814000000000000029e-02 1.188850680000000011e+03 2.438083999999999918e+01 +4.175000000000000239e-02 9.312768700000000308e+02 2.157865999999999929e+01 +4.544000000000000122e-02 7.201697299999999586e+02 1.897589999999999932e+01 +4.646000000000000130e-02 6.728012599999999566e+02 1.834122999999999948e+01 +4.909999999999999781e-02 5.453608799999999519e+02 1.651304000000000016e+01 +5.256000000000000255e-02 4.724414499999999748e+02 1.536946999999999974e+01 +5.272000000000000297e-02 4.318700699999999983e+02 1.469472999999999985e+01 +5.632000000000000201e-02 3.382353699999999890e+02 1.300453000000000081e+01 +5.870999999999999830e-02 3.073326000000000136e+02 1.239621999999999957e+01 +5.988999999999999879e-02 2.634156100000000151e+02 1.147639999999999993e+01 +6.356000000000000538e-02 2.076918699999999944e+02 1.019048000000000087e+01 +6.483999999999999486e-02 2.125336499999999944e+02 1.030857999999999919e+01 +6.718999999999999972e-02 1.688028199999999970e+02 9.187020000000000408e+00 +7.073999999999999733e-02 1.362696899999999971e+02 8.254379999999999384e+00 +7.097000000000000530e-02 1.410024300000000039e+02 8.396499999999999631e+00 +7.435999999999999555e-02 1.127603699999999947e+02 7.508670000000000400e+00 +7.724999999999999922e-02 9.965560999999999581e+01 7.058880000000000265e+00 +7.800999999999999601e-02 9.194693999999999789e+01 6.780369999999999564e+00 +8.165999999999999648e-02 7.522768999999999551e+01 6.133009999999999629e+00 +8.347000000000000253e-02 7.049939999999999429e+01 5.937149999999999928e+00 +8.526000000000000245e-02 6.411020000000000607e+01 5.661719999999999864e+00 +8.883000000000000618e-02 5.376234999999999786e+01 5.184709999999999930e+00 +8.963000000000000134e-02 5.217504000000000275e+01 5.107590000000000074e+00 +9.574000000000000565e-02 4.054399999999999693e+01 4.502439999999999998e+00 +1.018199999999999938e-01 3.096013999999999911e+01 3.934470000000000134e+00 +1.080399999999999971e-01 2.426012000000000057e+01 3.482819999999999805e+00 +1.142200000000000021e-01 1.991522000000000148e+01 3.155569999999999986e+00 +1.202600000000000058e-01 1.696463999999999928e+01 2.912440000000000140e+00 +1.264099999999999946e-01 1.406012999999999913e+01 2.651429999999999954e+00 +1.326199999999999879e-01 1.186475000000000080e+01 2.435649999999999871e+00 +1.388300000000000090e-01 1.075843000000000060e+01 2.319310000000000205e+00 +1.449699999999999878e-01 9.447770000000000223e+00 2.173449999999999882e+00 +1.510100000000000053e-01 8.521739999999999426e+00 2.064189999999999969e+00 +1.571799999999999864e-01 7.782460000000000377e+00 1.972620000000000040e+00 +1.634099999999999997e-01 7.576889999999999681e+00 1.946390000000000065e+00 +1.695800000000000085e-01 7.175270000000000259e+00 1.894109999999999960e+00 +1.756999999999999951e-01 6.609770000000000145e+00 1.817930000000000046e+00 +1.818799999999999861e-01 6.690150000000000041e+00 1.828950000000000076e+00 +1.880999999999999894e-01 6.763010000000000410e+00 1.838889999999999914e+00 +1.942000000000000115e-01 6.479930000000000412e+00 1.799989999999999979e+00 +2.003099999999999881e-01 6.931930000000000369e+00 1.861709999999999976e+00 +2.064499999999999946e-01 7.058690000000000353e+00 1.878649999999999931e+00 +2.126300000000000134e-01 6.966739999999999711e+00 1.866379999999999928e+00 +2.187699999999999922e-01 7.155350000000000321e+00 1.891469999999999985e+00 +2.248999999999999888e-01 7.328820000000000334e+00 1.914260000000000073e+00 +2.310800000000000076e-01 7.623789999999999623e+00 1.952409999999999979e+00 +2.373000000000000109e-01 7.870739999999999625e+00 1.983780000000000099e+00 +2.434699999999999920e-01 7.865639999999999965e+00 1.983130000000000059e+00 +2.495900000000000063e-01 8.081390000000000740e+00 2.010149999999999881e+00 +2.557300000000000129e-01 8.480309999999999349e+00 2.059159999999999879e+00 +2.618900000000000117e-01 8.708700000000000330e+00 2.086710000000000065e+00 +2.680899999999999950e-01 8.823320000000000718e+00 2.100389999999999979e+00 +2.742499999999999938e-01 9.030450000000000088e+00 2.124909999999999854e+00 +2.803300000000000236e-01 9.222670000000000812e+00 2.147400000000000198e+00 +2.864999999999999769e-01 9.345430000000000348e+00 2.161649999999999849e+00 +2.927100000000000257e-01 9.660539999999999239e+00 2.197789999999999910e+00 +2.988600000000000145e-01 9.559969999999999857e+00 2.186319999999999819e+00 \ No newline at end of file diff --git a/test/trend_test_data/Nanoperm_perpendicular_Honecker_et_al/5_312_1640_24.331065.csv b/test/trend_test_data/Nanoperm_perpendicular_Honecker_et_al/5_312_1640_24.331065.csv new file mode 100644 index 00000000..17968ec9 --- /dev/null +++ b/test/trend_test_data/Nanoperm_perpendicular_Honecker_et_al/5_312_1640_24.331065.csv @@ -0,0 +1,60 @@ +2.733000000000000013e-02 1.791495509999999967e+03 2.992905000000000015e+01 +3.091999999999999957e-02 1.477992469999999912e+03 2.718449000000000026e+01 +3.454000000000000126e-02 1.172791189999999915e+03 2.421561000000000163e+01 +3.814000000000000029e-02 9.196233300000000099e+02 2.144322000000000017e+01 +4.175000000000000239e-02 7.160111200000000053e+02 1.892102999999999824e+01 +4.544000000000000122e-02 5.391461900000000469e+02 1.641867999999999839e+01 +4.646000000000000130e-02 4.929036699999999769e+02 1.569877999999999929e+01 +4.909999999999999781e-02 4.129331099999999992e+02 1.436894000000000027e+01 +5.256000000000000255e-02 3.482022600000000239e+02 1.319473999999999947e+01 +5.272999999999999909e-02 3.242194700000000012e+02 1.273222999999999949e+01 +5.632000000000000201e-02 2.454095199999999863e+02 1.107722000000000051e+01 +5.870999999999999830e-02 2.218204599999999971e+02 1.053139000000000003e+01 +5.990000000000000185e-02 1.931666899999999885e+02 9.827680000000000859e+00 +6.356000000000000538e-02 1.505020900000000097e+02 8.674739999999999895e+00 +6.483999999999999486e-02 1.511605900000000133e+02 8.693690000000000140e+00 +6.718999999999999972e-02 1.202856799999999993e+02 7.755180000000000184e+00 +7.074999999999999345e-02 9.616129999999999711e+01 6.934020000000000294e+00 +7.097000000000000530e-02 9.870516999999999541e+01 7.025140000000000384e+00 +7.435999999999999555e-02 7.818730999999999653e+01 6.252489999999999881e+00 +7.724999999999999922e-02 6.837958000000000425e+01 5.847199999999999953e+00 +7.802000000000000601e-02 6.371482000000000312e+01 5.644239999999999924e+00 +8.165999999999999648e-02 5.179379999999999740e+01 5.088899999999999757e+00 +8.347000000000000253e-02 4.836007000000000033e+01 4.917320000000000135e+00 +8.526999999999999857e-02 4.457972000000000179e+01 4.721210000000000129e+00 +8.884000000000000230e-02 3.751116999999999990e+01 4.330770000000000231e+00 +8.963000000000000134e-02 3.544118999999999886e+01 4.209579999999999878e+00 +9.574000000000000565e-02 2.739192999999999856e+01 3.700810000000000155e+00 +1.018199999999999938e-01 2.084316000000000102e+01 3.228250000000000064e+00 +1.080399999999999971e-01 1.702296000000000120e+01 2.917440000000000033e+00 +1.142200000000000021e-01 1.391883999999999943e+01 2.638069999999999915e+00 +1.202600000000000058e-01 1.148202000000000034e+01 2.396040000000000170e+00 +1.264099999999999946e-01 9.887589999999999435e+00 2.223460000000000214e+00 +1.326199999999999879e-01 8.618710000000000093e+00 2.075899999999999856e+00 +1.388300000000000090e-01 7.875820000000000043e+00 1.984420000000000073e+00 +1.449699999999999878e-01 7.119869999999999699e+00 1.886779999999999902e+00 +1.510100000000000053e-01 6.691399999999999793e+00 1.829129999999999923e+00 +1.571799999999999864e-01 6.133020000000000138e+00 1.751139999999999919e+00 +1.634099999999999997e-01 5.984770000000000145e+00 1.729850000000000110e+00 +1.695800000000000085e-01 5.802669999999999995e+00 1.703330000000000011e+00 +1.756999999999999951e-01 5.772070000000000256e+00 1.698830000000000062e+00 +1.818799999999999861e-01 5.866640000000000299e+00 1.712690000000000046e+00 +1.880999999999999894e-01 5.880740000000000300e+00 1.714749999999999996e+00 +1.942000000000000115e-01 5.856810000000000294e+00 1.711260000000000003e+00 +2.003099999999999881e-01 6.138060000000000294e+00 1.751870000000000038e+00 +2.064499999999999946e-01 6.406990000000000407e+00 1.789830000000000032e+00 +2.126300000000000134e-01 6.462060000000000137e+00 1.797509999999999941e+00 +2.187699999999999922e-01 6.501079999999999970e+00 1.802929999999999922e+00 +2.248999999999999888e-01 6.991179999999999950e+00 1.869650000000000034e+00 +2.310800000000000076e-01 7.014639999999999986e+00 1.872779999999999889e+00 +2.373000000000000109e-01 7.136879999999999669e+00 1.889029999999999987e+00 +2.434699999999999920e-01 7.547889999999999766e+00 1.942660000000000053e+00 +2.495900000000000063e-01 7.608620000000000161e+00 1.950460000000000083e+00 +2.557300000000000129e-01 7.913459999999999717e+00 1.989149999999999974e+00 +2.618900000000000117e-01 8.245919999999999916e+00 2.030510000000000037e+00 +2.680899999999999950e-01 8.604509999999999437e+00 2.074190000000000200e+00 +2.742499999999999938e-01 8.711750000000000327e+00 2.087070000000000203e+00 +2.803300000000000236e-01 8.863739999999999952e+00 2.105199999999999960e+00 +2.864999999999999769e-01 8.911099999999999355e+00 2.110819999999999919e+00 +2.927100000000000257e-01 9.338430000000000675e+00 2.160839999999999872e+00 +2.988600000000000145e-01 9.385640000000000427e+00 2.166290000000000049e+00 \ No newline at end of file diff --git a/test/trend_test_data/Nanoperm_perpendicular_Honecker_et_al/6_1270_1640_24.331065.csv b/test/trend_test_data/Nanoperm_perpendicular_Honecker_et_al/6_1270_1640_24.331065.csv new file mode 100644 index 00000000..72272ed8 --- /dev/null +++ b/test/trend_test_data/Nanoperm_perpendicular_Honecker_et_al/6_1270_1640_24.331065.csv @@ -0,0 +1,60 @@ +2.732000000000000053e-02 1.517870979999999918e+03 2.754878000000000071e+01 +3.090999999999999998e-02 1.252144950000000108e+03 2.502143999999999835e+01 +3.452999999999999819e-02 9.849326300000000174e+02 2.219157999999999831e+01 +3.812999999999999723e-02 7.635778900000000249e+02 1.953941999999999979e+01 +4.173999999999999932e-02 5.910564900000000534e+02 1.719093000000000160e+01 +4.542999999999999816e-02 4.454160299999999779e+02 1.492340000000000089e+01 +4.644000000000000211e-02 4.097451399999999921e+02 1.431337000000000081e+01 +4.907999999999999863e-02 3.412573399999999992e+02 1.306249000000000038e+01 +5.254000000000000337e-02 2.833860500000000116e+02 1.190348999999999968e+01 +5.270999999999999991e-02 2.587516299999999774e+02 1.137434999999999974e+01 +5.630000000000000282e-02 1.972410900000000140e+02 9.930790000000000006e+00 +5.868999999999999911e-02 1.820288400000000024e+02 9.540150000000000574e+00 +5.988000000000000267e-02 1.548462399999999946e+02 8.799039999999999750e+00 +6.353999999999999926e-02 1.208091600000000057e+02 7.772039999999999615e+00 +6.481000000000000649e-02 1.228846300000000014e+02 7.838510000000000311e+00 +6.716999999999999360e-02 9.485747999999999536e+01 6.886849999999999916e+00 +7.072000000000000508e-02 7.616365000000000407e+01 6.171050000000000146e+00 +7.094999999999999918e-02 7.959243999999999630e+01 6.308419999999999916e+00 +7.434000000000000330e-02 6.139544999999999675e+01 5.540549999999999642e+00 +7.721999999999999698e-02 5.528307999999999822e+01 5.257520000000000415e+00 +7.799000000000000377e-02 5.105310999999999666e+01 5.052380000000000315e+00 +8.164000000000000423e-02 4.131436000000000064e+01 4.545020000000000060e+00 +8.343000000000000416e-02 3.883191999999999666e+01 4.406349999999999767e+00 +8.523999999999999633e-02 3.386838999999999800e+01 4.115120000000000111e+00 +8.881000000000000005e-02 2.873742000000000019e+01 3.790610000000000035e+00 +8.959999999999999909e-02 2.744166999999999845e+01 3.704159999999999897e+00 +9.569999999999999341e-02 2.114314999999999856e+01 3.251399999999999846e+00 +1.017799999999999955e-01 1.639198000000000022e+01 2.862859999999999960e+00 +1.079999999999999988e-01 1.302345000000000041e+01 2.551810000000000134e+00 +1.141699999999999937e-01 1.067233000000000054e+01 2.310010000000000119e+00 +1.202099999999999974e-01 8.927680000000000504e+00 2.112779999999999880e+00 +1.263600000000000001e-01 7.691300000000000026e+00 1.961030000000000051e+00 +1.325600000000000112e-01 6.633219999999999672e+00 1.821159999999999890e+00 +1.387799999999999867e-01 6.187759999999999927e+00 1.758939999999999948e+00 +1.449100000000000110e-01 5.397269999999999790e+00 1.642749999999999932e+00 +1.509500000000000008e-01 5.127299999999999969e+00 1.601140000000000008e+00 +1.571200000000000097e-01 4.947350000000000136e+00 1.572789999999999910e+00 +1.633400000000000130e-01 4.791970000000000063e+00 1.547900000000000054e+00 +1.695200000000000040e-01 4.839830000000000076e+00 1.555609999999999937e+00 +1.756399999999999906e-01 4.855599999999999916e+00 1.558140000000000081e+00 +1.818099999999999994e-01 4.971189999999999998e+00 1.576580000000000092e+00 +1.880199999999999927e-01 4.996179999999999843e+00 1.580540000000000056e+00 +1.941299999999999970e-01 5.231489999999999974e+00 1.617329999999999934e+00 +2.002299999999999913e-01 5.461660000000000181e+00 1.652519999999999989e+00 +2.063699999999999979e-01 5.566430000000000433e+00 1.668299999999999894e+00 +2.125400000000000067e-01 5.749649999999999928e+00 1.695529999999999982e+00 +2.186899999999999955e-01 5.962720000000000020e+00 1.726660000000000084e+00 +2.248100000000000098e-01 6.366220000000000212e+00 1.784129999999999994e+00 +2.309900000000000009e-01 6.576419999999999710e+00 1.813339999999999952e+00 +2.372100000000000042e-01 6.993730000000000224e+00 1.869990000000000041e+00 +2.433800000000000130e-01 7.112300000000000288e+00 1.885780000000000012e+00 +2.494999999999999996e-01 7.346860000000000390e+00 1.916619999999999990e+00 +2.556300000000000239e-01 7.629410000000000025e+00 1.953130000000000033e+00 +2.617900000000000227e-01 7.827810000000000379e+00 1.978359999999999896e+00 +2.679900000000000060e-01 8.252810000000000201e+00 2.031359999999999832e+00 +2.741399999999999948e-01 8.304840000000000444e+00 2.037749999999999950e+00 +2.802200000000000246e-01 8.705450000000000799e+00 2.086320000000000174e+00 +2.863899999999999779e-01 8.881330000000000169e+00 2.107289999999999885e+00 +2.926000000000000267e-01 9.112230000000000274e+00 2.134510000000000129e+00 +2.987500000000000155e-01 9.303969999999999629e+00 2.156849999999999934e+00 diff --git a/test/trend_test_data/NdFeB_parallel_Bick_et_al/1_8000_1600_1070.csv b/test/trend_test_data/NdFeB_parallel_Bick_et_al/1_8000_1600_1070.csv new file mode 100644 index 00000000..efaf03f7 --- /dev/null +++ b/test/trend_test_data/NdFeB_parallel_Bick_et_al/1_8000_1600_1070.csv @@ -0,0 +1,53 @@ +0.02466 11161.7 105.066 +0.02779 8582.24 78.8702 +0.03118 6929.14 63.4309 +0.03472 5567.83 52.0489 +0.03798 4896.87 48.9428 +0.04103 4301.45 44.2537 +0.04439 3931.56 35.32 +0.04789 3532.24 33.8506 +0.05145 3225.93 29.1479 +0.05487 3001.52 30.0366 +0.05809 2808.67 26.8966 +0.06147 2656.59 24.6464 +0.06487 2544.49 24.3544 +0.06823 2417.04 22.1946 +0.07159 2285.04 21.753 +0.07491 2209.24 20.4624 +0.07817 2057.47 19.703 +0.08149 2022.7 18.6311 +0.08492 1895.55 17.4353 +0.08832 1842.28 17.2068 +0.09171 1764.56 16.4013 +0.09497 1702.47 16.2564 +0.09818 1650.68 15.7993 +0.10176 1598.61 13.5448 +0.10537 1517.07 14.6455 +0.10864 1470.19 13.6872 +0.11191 1422.64 13.7771 +0.11529 1373.82 12.5952 +0.11874 1333.67 12.468 +0.12204 1309.38 12.7116 +0.1253 1256.04 11.6755 +0.12868 1224.03 11.535 +0.13213 1180.94 10.9145 +0.13557 1163.16 10.9427 +0.13878 1135.89 11.2995 +0.14204 1129.91 10.3422 +0.14544 1101.7 10.3463 +0.14888 1050.46 9.68171 +0.1523 1034.3 9.79525 +0.15558 1022.51 9.86417 +0.15895 1003.15 9.06883 +0.16231 984.02 9.61305 +0.16567 956.076 8.69603 +0.16916 941.039 8.81872 +0.17256 914.22 8.5457 +0.1759 903.557 8.49062 +0.1792 862.822 8.49027 +0.18246 857.39 8.12443 +0.18596 822.372 7.58423 +0.18945 803.539 7.77639 +0.19275 787.933 7.6279 +0.19605 759.06 7.64242 +0.1993 746.444 7.32989 diff --git a/test/trend_test_data/NdFeB_parallel_Bick_et_al/2_10000_1600_1070.csv b/test/trend_test_data/NdFeB_parallel_Bick_et_al/2_10000_1600_1070.csv new file mode 100644 index 00000000..ede4f812 --- /dev/null +++ b/test/trend_test_data/NdFeB_parallel_Bick_et_al/2_10000_1600_1070.csv @@ -0,0 +1,53 @@ +0.02466 9785.87 100.498 +0.02779 7387.61 74.8808 +0.03118 5638.9 58.6706 +0.03472 4473.77 47.7572 +0.03797 3723.19 43.7134 +0.04102 3256.4 39.3915 +0.04439 2850.7 30.7019 +0.04789 2561.59 29.3595 +0.05145 2280.01 24.9612 +0.05487 2158.85 25.8372 +0.05809 1997.24 23.0262 +0.06147 1891.61 21.0832 +0.06486 1796.03 20.6896 +0.06823 1698.02 18.8045 +0.07158 1613.84 18.4277 +0.0749 1560.07 17.3367 +0.07817 1481.58 16.8421 +0.08149 1400.5 15.6189 +0.08492 1362.71 14.8771 +0.08832 1326.21 14.6884 +0.09171 1308.1 14.1935 +0.09497 1238.48 13.9232 +0.09817 1217.12 13.6217 +0.10176 1182.01 11.7148 +0.10536 1125.29 12.6688 +0.10863 1086.39 11.8029 +0.1119 1069.27 11.9783 +0.11528 1037.53 10.9946 +0.11874 1000.25 10.8391 +0.12203 1030.21 11.3207 +0.12529 996.562 10.4091 +0.12867 937.635 10.1418 +0.13213 932.787 9.7458 +0.13557 936.093 9.83598 +0.13878 902.903 10.118 +0.14204 895.331 9.2379 +0.14544 894.523 9.3444 +0.14887 873.213 8.84172 +0.15229 870.028 8.99453 +0.15558 846.466 8.9992 +0.15895 842.358 8.32813 +0.1623 809.53 8.74144 +0.16566 822.783 8.08431 +0.16915 792.943 8.10552 +0.17255 774.308 7.87783 +0.1759 781.539 7.90816 +0.17919 756.562 7.97296 +0.18245 745.925 7.59066 +0.18596 725.982 7.13904 +0.18944 711.483 7.3287 +0.19275 709.157 7.24903 +0.19604 677.704 7.2411 +0.19929 659.648 6.90112 diff --git a/test/trend_test_data/NdFeB_parallel_Bick_et_al/3_12000_1600_1070.csv b/test/trend_test_data/NdFeB_parallel_Bick_et_al/3_12000_1600_1070.csv new file mode 100644 index 00000000..1c96f3d5 --- /dev/null +++ b/test/trend_test_data/NdFeB_parallel_Bick_et_al/3_12000_1600_1070.csv @@ -0,0 +1,53 @@ +0.02467 9119.79 98.2483 +0.02779 6716.34 72.6007 +0.03118 4992.66 56.2161 +0.03472 3831.98 45.1388 +0.03798 3145.6 40.9924 +0.04103 2635.02 36.2785 +0.0444 2353.33 28.4064 +0.04789 2051.02 26.7601 +0.05146 1820.51 22.7202 +0.05487 1702.5 23.3101 +0.0581 1582.43 20.8224 +0.06148 1455.78 18.7977 +0.06487 1396.8 18.5029 +0.06824 1347.58 16.9495 +0.07159 1258.02 16.4371 +0.07491 1231.28 15.5554 +0.07818 1164.47 15.0776 +0.0815 1146.97 14.2378 +0.08493 1109.77 13.533 +0.08833 1040.08 13.1167 +0.09172 1030.03 12.6919 +0.09498 996.502 12.5769 +0.09819 973.153 12.2625 +0.10178 933.855 10.4847 +0.10538 941.324 11.6421 +0.10865 891.14 10.747 +0.11192 890.927 10.9919 +0.1153 863.999 10.0862 +0.11876 860.652 10.1051 +0.12205 833.905 10.2392 +0.12531 831.414 9.54046 +0.12869 796.354 9.38666 +0.13215 809.376 9.11842 +0.13559 805.133 9.14629 +0.1388 791.474 9.50691 +0.14205 787.287 8.69915 +0.14546 761.22 8.65866 +0.1489 779.201 8.38098 +0.15232 771.323 8.50152 +0.1556 748.08 8.49235 +0.15897 748.238 7.8713 +0.16233 756.864 8.48551 +0.16569 723.545 7.61209 +0.16918 723.039 7.76626 +0.17258 714.249 7.58349 +0.17592 707.068 7.55031 +0.17922 695.999 7.67253 +0.18248 687.854 7.31121 +0.18599 672.503 6.89206 +0.18947 656.046 7.05985 +0.19277 644.64 6.93548 +0.19607 633.62 7.0287 +0.19932 617.369 6.7009 diff --git a/test/trend_test_data/NdFeB_parallel_Bick_et_al/4_14000_1600_1070.csv b/test/trend_test_data/NdFeB_parallel_Bick_et_al/4_14000_1600_1070.csv new file mode 100644 index 00000000..e349acef --- /dev/null +++ b/test/trend_test_data/NdFeB_parallel_Bick_et_al/4_14000_1600_1070.csv @@ -0,0 +1,53 @@ +0.02466 8578.27 96.3713 +0.02778 6201.5 70.6884 +0.03117 4666.55 54.9081 +0.03471 3532.71 43.8556 +0.03797 2779.72 39.1195 +0.04102 2401.14 35.0436 +0.04438 2010.93 26.6446 +0.04788 1789.13 25.3004 +0.05144 1629 21.7057 +0.05485 1414.39 21.5471 +0.05808 1338.47 19.3969 +0.06145 1255.55 17.6425 +0.06485 1194.78 17.2789 +0.06822 1131.88 15.6792 +0.07157 1093.29 15.4062 +0.07489 1076.75 14.6325 +0.07815 990.83 13.9891 +0.08147 993.984 13.3123 +0.0849 949.429 12.5828 +0.0883 906.082 12.3081 +0.09169 901.543 11.9196 +0.09495 883.023 11.8721 +0.09815 842.784 11.4424 +0.10174 819.526 9.8676 +0.10534 802.736 10.7893 +0.10861 795.767 10.1828 +0.11188 791.163 10.3837 +0.11526 783.07 9.63416 +0.11872 771.734 9.59199 +0.12201 755.014 9.75865 +0.12527 763.25 9.14929 +0.12865 738.983 9.05459 +0.1321 735.65 8.71334 +0.13554 713.743 8.62175 +0.13875 720.548 9.08471 +0.142 726.975 8.37203 +0.14541 705.893 8.34698 +0.14884 717.099 8.0504 +0.15226 709.825 8.16294 +0.15554 709.595 8.27884 +0.15891 699.293 7.61937 +0.16227 704.678 8.19256 +0.16563 698.926 7.48706 +0.16912 685.563 7.56396 +0.17251 673.043 7.36594 +0.17586 671.398 7.36238 +0.17915 664.082 7.50135 +0.18241 667.157 7.20373 +0.18592 646.099 6.75787 +0.1894 640.973 6.98357 +0.1927 634.489 6.88246 +0.196 608.639 6.89728 +0.19924 599.163 6.60299 diff --git a/test/trend_test_data/NdFeB_parallel_Bick_et_al/5_16000_1600_1070.csv b/test/trend_test_data/NdFeB_parallel_Bick_et_al/5_16000_1600_1070.csv new file mode 100644 index 00000000..03d157c5 --- /dev/null +++ b/test/trend_test_data/NdFeB_parallel_Bick_et_al/5_16000_1600_1070.csv @@ -0,0 +1,53 @@ +0.02466 8403.24 95.8365 +0.02779 5866.26 69.5434 +0.03118 4356.78 53.6923 +0.03472 3299.25 42.8649 +0.03798 2597.38 38.2386 +0.04103 2129.79 33.557 +0.04439 1878.69 25.9888 +0.04789 1624.61 24.3861 +0.05145 1439.52 20.6737 +0.05487 1263.65 20.5759 +0.05809 1171.99 18.386 +0.06147 1079.56 16.577 +0.06487 1041.15 16.3129 +0.06823 1001.7 14.8847 +0.07159 961.645 14.563 +0.07491 923.442 13.6706 +0.07817 896.915 13.3973 +0.08149 883.652 12.6286 +0.08492 862.753 12.0581 +0.08832 824.722 11.795 +0.09171 784.35 11.1756 +0.09497 769.294 11.1579 +0.09818 755.349 10.8901 +0.10176 749.016 9.48228 +0.10537 742.393 10.4072 +0.10864 731.812 9.80011 +0.11191 725.902 9.97489 +0.11529 687.755 9.06119 +0.11874 695.826 9.13917 +0.12204 706.826 9.47552 +0.1253 686.062 8.69528 +0.12868 672.907 8.67583 +0.13213 693.14 8.48682 +0.13557 670.728 8.38538 +0.13878 665.705 8.76139 +0.14204 671.833 8.08011 +0.14544 675.608 8.18129 +0.14888 672.094 7.81614 +0.1523 674.683 7.97469 +0.15558 656.506 7.99112 +0.15895 676.236 7.50792 +0.16231 665.98 7.99164 +0.16567 649.237 7.2359 +0.16916 663.887 7.46039 +0.17256 654.732 7.28088 +0.1759 649.138 7.26044 +0.1792 637.918 7.36805 +0.18246 634.954 7.04588 +0.18596 615.952 6.61209 +0.18945 606.075 6.80618 +0.19275 602.38 6.72418 +0.19605 583.877 6.77081 +0.1993 592.832 6.58159 diff --git a/test/trend_test_data/custom_test/1.txt b/test/trend_test_data/custom_test/1.txt new file mode 100644 index 00000000..7f82c727 --- /dev/null +++ b/test/trend_test_data/custom_test/1.txt @@ -0,0 +1,9 @@ +0.1,32 +0.2,321 +0.3,65 +0.4,32 +0.5,12 +0.6,4 +0.7,15 +0.8,85 +0.9,23 diff --git a/test/trend_test_data/custom_test/2.txt b/test/trend_test_data/custom_test/2.txt new file mode 100644 index 00000000..5a8d4fe8 --- /dev/null +++ b/test/trend_test_data/custom_test/2.txt @@ -0,0 +1,9 @@ +0.1,23 +0.2,12 +0.3,13 +0.4,122 +0.5,21 +0.6,1 +0.7,30 +0.8,1 +0.9,2 diff --git a/test/trend_test_data/custom_test/3.txt b/test/trend_test_data/custom_test/3.txt new file mode 100644 index 00000000..00e891f4 --- /dev/null +++ b/test/trend_test_data/custom_test/3.txt @@ -0,0 +1,9 @@ +0.1,1 +0.2,354 +0.3,21 +0.4,31 +0.5,11 +0.6,45 +0.7,5 +0.8,1 +0.9,44 diff --git a/test/utest_new_sasdata.py b/test/utest_new_sasdata.py new file mode 100644 index 00000000..fa99c303 --- /dev/null +++ b/test/utest_new_sasdata.py @@ -0,0 +1,47 @@ +import numpy as np + +from sasdata.data import SasData +from sasdata.dataset_types import one_dim, two_dim +from sasdata.data_backing import Group +from sasdata.quantities.quantity import Quantity +from sasdata.quantities.units import per_angstrom, per_centimeter + +def test_1d(): + q = [1, 2, 3, 4, 5] + i = [5, 4, 3, 2, 1] + + q_quantity = Quantity(np.array(q), per_angstrom) + i_quantity = Quantity(np.array(i), per_centimeter) + + data_contents = { + 'Q': q_quantity, + 'I': i_quantity + } + + data = SasData('TestData', data_contents, one_dim, Group('root', {}), True) + + assert all(data.abscissae.value == np.array(q)) + assert all(data.ordinate.value == np.array(i)) + + +def test_2d(): + # This could be autogenerated but I am hard coding to reduce the logic in + # the test. + qx = [1, 1, 1, 2, 2, 2, 3, 3, 3] + qy = [1, 2, 3, 1, 2, 3, 1, 2, 3] + i = [1, 2, 3] + + qx_quantity = Quantity(np.array(qx), per_angstrom) + qy_quantity = Quantity(np.array(qy), per_angstrom) + i_quantity = Quantity(np.array(i), per_centimeter) + + data_contents = { + 'Qx': qx_quantity, + 'Qy': qy_quantity, + 'I': i_quantity + } + + data = SasData('TestData', data_contents, two_dim, Group('root', {}), True) + + assert all(data.ordinate.value == np.array(i)) + assert (data.abscissae.value == np.array([[1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3], [3, 1], [3, 2], [3, 3]])).all().all() diff --git a/test/utest_sasdata.py b/test/utest_sasdata.py index 56dffbdc..79045700 100644 --- a/test/utest_sasdata.py +++ b/test/utest_sasdata.py @@ -9,13 +9,6 @@ logging.config.fileConfig(LOGGER_CONFIG_FILE) logger = logging.getLogger(__name__) -try: - import xmlrunner -except: - logger.error("xmlrunner needs to be installed to run these tests") - logger.error("Try easy_install unittest-xml-reporting") - sys.exit(1) - # Check whether we have matplotlib installed HAS_MPL_WX = True try: diff --git a/test/utest_temp_ascii_reader.py b/test/utest_temp_ascii_reader.py new file mode 100644 index 00000000..5ab760bb --- /dev/null +++ b/test/utest_temp_ascii_reader.py @@ -0,0 +1,153 @@ +from typing import Literal +import pytest +import os + +from sasdata.temp_ascii_reader import ( + load_data, + guess_params_from_filename, + load_data_default_params, + AsciiReaderParams, +) +from sasdata.ascii_reader_metadata import AsciiReaderMetadata, AsciiMetadataCategory +from sasdata.dataset_types import one_dim +from sasdata.quantities.units import per_angstrom, per_centimeter +from sasdata.guess import guess_columns + +# TODO: These are using the private _data_contents temporarily. Later, there will be a public way of accessing these, +# and that should be used instead. + +# TODO: Look into parameterizing this, although its not trivial due to the setup, and tests being a bit different. + + +def find(filename: str, locations: Literal["sasdataloader", "mumag"]) -> str: + # This match statement is here in case we want to pull data out of other locations. + match locations: + case "sasdataloader": + return os.path.join( + os.path.dirname(__file__), "sasdataloader", "data", filename + ) + case "mumag": + return os.path.join( + os.path.dirname(__file__), + "mumag", + "Nanoperm_perpendicular_Honecker_et_al", + filename, + ) + + +def test_ascii_1(): + filename = find("ascii_test_1.txt", "sasdataloader") + params = guess_params_from_filename(filename, one_dim) + # Need to change the columns as they won't be right. + # TODO: unitless + params.columns = [ + ("Q", per_angstrom), + ("I", per_centimeter), + ("dI", per_centimeter), + ("", None), + ("", None), + ("", None), + ] + loaded_data = load_data(params)[0] + assert len(loaded_data._data_contents) > 0 + # Check the first, and last rows to see if they are correct. + for name, datum in loaded_data._data_contents.items(): + match name: + case "Q": + assert datum.value[0] == pytest.approx(0.002618) + assert datum.value[-1] == pytest.approx(0.0497) + case "I": + assert datum.value[0] == pytest.approx(0.02198) + assert datum.value[-1] == pytest.approx(8.346) + case "dI": + assert datum.value[0] == pytest.approx(0.002704) + assert datum.value[-1] == pytest.approx(0.191) + + +def test_ascii_2(): + filename = find("test_3_columns.txt", "sasdataloader") + loaded_data = load_data_default_params(filename)[0] + assert len(loaded_data._data_contents) > 0 + for name, datum in loaded_data._data_contents.items(): + match name: + case "Q": + assert datum.value[0] == pytest.approx(0) + assert datum.value[-1] == pytest.approx(1.22449) + case "I": + assert datum.value[0] == pytest.approx(2.83954) + assert datum.value[-1] == pytest.approx(7.47487) + case "dI": + assert datum.value[0] == pytest.approx(0.6) + assert datum.value[-1] == pytest.approx(1.05918) + + +@pytest.mark.xfail( + reason="Guesses for 2D ASCII files are currently wrong, so the data loaded won't be correct." +) +def test_ascii_2d(): + filename = find("detector_rectangular.DAT", "sasdataloader") + # Make sure that the dataset type is guessed as 2D data. + loaded_data = load_data_default_params(filename)[0] + assert len(loaded_data._data_contents) > 0 + + for name, datum in loaded_data._data_contents.items(): + match name: + case "Qx": + assert datum.value[0] == pytest.approx(-0.009160664) + assert datum.value[-1] == pytest.approx(0.2908819) + case "Qy": + assert datum.value[0] == pytest.approx(-0.1683881) + assert datum.value[-1] == pytest.approx(0.1634992) + case "I": + assert datum.value[0] == pytest.approx(16806.79) + assert datum.value[-1] == pytest.approx(8147.779) + case "dI": + assert datum.value[0] == pytest.approx(0.01366757) + assert datum.value[-1] == pytest.approx(0.05458562) + + +def test_mumag_metadata(): + filenames = [ + "1_33_1640_22.874115.csv", + "1_33_1640_22.874115.csv", + "2_42_1640_23.456895.csv", + "3_61_1640_23.748285.csv", + "4_103_1640_24.039675.csv", + "5_312_1640_24.331065.csv", + "6_1270_1640_24.331065.csv", + ] + param_filenames = [] + for filename in filenames: + param_filenames.append(find(filename, "mumag")) + + metadata = AsciiReaderMetadata( + master_metadata={ + "magnetic": AsciiMetadataCategory( + values={ + "counting_index": 0, + "applied_magnetic_field": 1, + "saturation_magnetization": 2, + "demagnetizing_field": 3, + } + ), + }, + ) + params = AsciiReaderParams( + filenames=param_filenames, + columns=[(column, per_angstrom) for column in guess_columns(3, one_dim)], + separator_dict={"Comma": True}, + metadata=metadata, + ) + data = load_data(params) + for datum in data: + match datum.name: + case "1_33_1640_22.874115.csv": + assert datum.metadata.raw.filter("counting_index") == ["1"] + assert datum.metadata.raw.filter("applied_magnetic_field") == ["33"] + assert datum.metadata.raw.filter("saturation_magnetization") == ["1640"] + assert datum.metadata.raw.filter("demagnetizing_field") == ["22"] + case "6_1270_1640_24.331065.csv": + assert datum.metadata.raw.filter("counting_index") == ["6"] + assert datum.metadata.raw.filter("applied_magnetic_field") == ["1270"] + assert datum.metadata.raw.filter("saturation_magnetization") == ["1640"] + assert datum.metadata.raw.filter("demagnetizing_field") == ["24"] diff --git a/test/utest_trend.py b/test/utest_trend.py new file mode 100644 index 00000000..ed66daf4 --- /dev/null +++ b/test/utest_trend.py @@ -0,0 +1,67 @@ +import pytest +from os import path, listdir +from sasdata.ascii_reader_metadata import AsciiMetadataCategory +from sasdata.quantities.units import per_nanometer, per_angstrom +from sasdata.temp_ascii_reader import AsciiReaderParams +import sasdata.temp_ascii_reader as ascii_reader +from sasdata.trend import Trend + +mumag_test_directories = [ + 'FeNiB_perpendicular_Bersweiler_et_al', + 'Nanoperm_perpendicular_Honecker_et_al', + 'NdFeB_parallel_Bick_et_al' +] + +custom_test_directory = 'custom_test' + +def get_files_to_load(directory_name: str) -> list[str]: + load_from = path.join(path.dirname(__file__), 'trend_test_data', directory_name) + base_filenames_to_load = listdir(load_from) + files_to_load = [path.join(load_from, basename) for basename in base_filenames_to_load] + return files_to_load + +@pytest.mark.parametrize('directory_name', mumag_test_directories) +def test_trend_build_interpolate(directory_name: str): + """Try to build a trend object on the MuMag datasets""" + files_to_load = get_files_to_load(directory_name) + params = AsciiReaderParams( + filenames=files_to_load, + columns=[('Q', per_nanometer), ('I', per_nanometer), ('dI', per_nanometer)], + ) + params.separator_dict['Whitespace'] = True + params.metadata.master_metadata['magnetic'] = AsciiMetadataCategory( + values={ + 'counting_index': 0, + 'applied_magnetic_field': 1, + 'saturation_magnetization': 2, + 'demagnetizing_field': 3 + } + ) + data = ascii_reader.load_data(params) + trend = Trend( + data=data, + trend_axis=['magnetic', 'applied_magnetic_field'] + ) + # Initially, the q axes in this date don't exactly match + to_interpolate_on = 'Q' + assert not trend.all_axis_match(to_interpolate_on) + interpolated_trend = trend.interpolate(to_interpolate_on) + assert interpolated_trend.all_axis_match(to_interpolate_on) + +def test_trend_q_axis_match(): + files_to_load = get_files_to_load(custom_test_directory) + params = AsciiReaderParams( + filenames=files_to_load, + columns=[('Q', per_angstrom), ('I', per_angstrom)] + ) + params.metadata.master_metadata['magnetic'] = AsciiMetadataCategory( + values={ + 'counting_index': 0, + } + ) + data = ascii_reader.load_data(params) + trend = Trend( + data=data, + trend_axis=['magnetic', 'counting_index'] + ) + assert trend.all_axis_match('Q') diff --git a/test/utest_unit_parser.py b/test/utest_unit_parser.py new file mode 100644 index 00000000..326faed6 --- /dev/null +++ b/test/utest_unit_parser.py @@ -0,0 +1,78 @@ +from sasdata.quantities.unit_parser import parse_named_unit, parse_named_unit_from_group, parse_unit +from sasdata.quantities import units +from sasdata.quantities.units import Unit + +import pytest + +named_units_for_testing = [ + ('m', units.meters), + ('A-1', units.per_angstrom), + ('1/A', units.per_angstrom), + ('1/angstroms', units.per_angstrom), + ('micrometer', units.micrometers), + ('micron', units.micrometers), + ('kmh-2', units.kilometers_per_square_hour), + ('km/h2', units.kilometers_per_square_hour), + ('kgm/s2', units.newtons), + ('m m', units.square_meters), + ('mm', units.millimeters), + ('A^-1', units.per_angstrom), + ('V/Amps', units.ohms), + ('Ω', units.ohms), + ('Å', units.angstroms), + ('%', units.percent) +] + +latex_units_for_testing = [ + (r"\Omega", units.ohms), # Test omega is Ω + (r"\AA", units.angstroms), # Test angstrom is Å + (r"\%", units.percent), # Test percent is NOT a comment + (r"{\mu}A", units.microamperes), # Test µ with an ASCII unit + (r"{\mu}\Omega", units.microohms), # Test µ with LaTeX unit + (r"mm", units.millimeters) # Test that most units just use ASCII in LaTeX +] + + +unnamed_units_for_testing = [ + ('m13', units.meters**13), + ('kW/sr', units.kilowatts/units.stradians) +] + +@pytest.mark.parametrize("string, expected_units", named_units_for_testing) +def test_name_parse(string: str, expected_units: Unit): + """ Test basic parsing""" + assert parse_named_unit(string) == expected_units + +@pytest.mark.parametrize("string, expected_units", named_units_for_testing + unnamed_units_for_testing) +def test_equivalent(string: str, expected_units: Unit): + """ Check dimensions of parsed units""" + assert parse_unit(string).equivalent(expected_units) + + +@pytest.mark.parametrize("string, expected_units", named_units_for_testing + unnamed_units_for_testing) +def test_scale_same(string: str, expected_units: Unit): + """ Test basic parsing""" + assert parse_unit(string).scale == pytest.approx(expected_units.scale, rel=1e-14) + +@pytest.mark.parametrize("latex_string, units", latex_units_for_testing) +def test_scale_same(latex_string: str, units: Unit): + """ Test that proper LaTeX formats for units are being generated""" + assert units.latex_symbol == latex_string + + +def test_parse_from_group(): + """ Test group based disambiguation""" + parsed_metres_per_second = parse_named_unit_from_group('ms-1', units.speed) + assert parsed_metres_per_second == units.meters_per_second + + +def test_parse_errors(): + # Fails because the unit is not in that specific group. + with pytest.raises(ValueError, match='That unit cannot be parsed from the specified group.'): + parse_named_unit_from_group('km', units.speed) + # Fails because part of the unit matches but there is an unknown unit '@' + with pytest.raises(ValueError, match='unit_str contains forbidden characters.'): + parse_unit('km@-1') + # Fails because 'da' is not a unit. + with pytest.raises(ValueError, match='Unit string contains an unrecognised pattern.'): + parse_unit('mmda2')