Skip to content

Commit

Permalink
Merge branch 'refactor_24' into 48-fractional-binning
Browse files Browse the repository at this point in the history
  • Loading branch information
lucas-wilkins authored Oct 15, 2024
2 parents 628e56c + df6d045 commit 6df7930
Show file tree
Hide file tree
Showing 58 changed files with 112,205 additions and 182 deletions.
25 changes: 7 additions & 18 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,22 @@ jobs:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
python-version: ['3.9', '3.10', '3.11']
python-version: ['3.12']
fail-fast: false

steps:

- name: Obtain SasData source from git
uses: actions/checkout@v1
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

### Caching of pip downloads and local wheel builds
- name: Get pip cache dir
id: pip-cache
run: |
echo "name={$(pip cache dir)}" >> $GITHUB_OUTPUT
- name: Obtain pip cache (Linux)
uses: actions/cache@v2
with:
path: ${{ steps.pip-cache.outputs.name }}
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('**/test.yml') }}
restore-keys: |
${{ runner.os }}-pip-${{ matrix.python-version }}-
${{ runner.os }}-pip-
cache: 'pip'
cache-dependency-path: |
**/test.yml
**/requirements*.txt
### Installation of build-dependencies

Expand Down
3 changes: 1 addition & 2 deletions LICENSE.TXT
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
Copyright (c) 2009-2022, SasView Developers

Copyright (c) 2009-2024, SasView Developers

All rights reserved.

Expand Down
23 changes: 15 additions & 8 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
# Minimal makefile for Sphinx documentation
#

# Environment variables
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SPHINXAPIDOC ?= sphinx-apidoc
APIOPTS = -d 8 -H SasData
DOCSDIR = docs
SOURCEDIR = source
BUILDDIR = build
CD = cd
DEVDIR = dev
LIBDIR = lib
GENDIR = generated
UP = ..

ifdef ComSpec
RMDIR = rmdir /s/q
Expand All @@ -20,27 +23,31 @@ else
PATHSEP = $(strip /)
endif

LIBDIR = $(BUILDDIR)$(PATHSEP)lib
SASDATABUILD = $(UP)$(PATHSEP)$(BUILDDIR)$(PATHSEP)$(LIBDIR)
DOCSSRC = $(SOURCEDIR)
DEV = $(DOCSSRC)$(PATHSEP)$(DEVDIR)
DEVGEN = $(DEV)$(PATHSEP)$(GENDIR)
DOCSBUILD = $(DOCSSRC)$(PATHSEP)$(BUILDDIR)

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

clean:
-$(RMDIR) "$(SOURCEDIR)$(PATHSEP)dev$(PATHSEP)generated/" "$(BUILDDIR)"
-$(RMDIR) "$(DEVGEN)" "$(DOCSBUILD)"

dir:
-$(MKDIR) "$(BUILDDIR)"
-$(MKDIR) "$(LIBDIR)"
-$(MKDIR) "$(DEVGEN)"
-$(MKDIR) "$(DOCSBUILD)"

.PHONY: help Makefile

# Generate the api docs
api:
$(SPHINXAPIDOC) -o "$(SOURCEDIR)$(PATHSEP)dev$(PATHSEP)generated/" $(APIOPTS) "$(LIBDIR)"
$(SPHINXAPIDOC) -o "$(DEVGEN)" $(APIOPTS) "$(SASDATABUILD)"

html: dir api
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@$(SPHINXBUILD) -M $@ "$(DOCSSRC)" "$(DOCSBUILD)" $(SPHINXOPTS) $(O)

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
Expand Down
4 changes: 2 additions & 2 deletions docs/source/dev/dev.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ Developer Documentation
=======================

.. toctree::
:maxdepth: 1
:maxdepth: 8

SasData <generated/modules>
SasData <generated/sasdata>
3 changes: 2 additions & 1 deletion docs/source/rst_prolog
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.. Set up some substitutions to make life easier...

.. |Ang| unicode:: U+212B
.. |Ang^-1| replace:: |Ang|\ :sup:`-1`
.. |Ang^-1| replace:: |Ang|\ :sup:`-1`
.. |delta| unicode:: U+03B4
37 changes: 37 additions & 0 deletions docs/source/user/RELEASE.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,43 @@ Features
========
Wheel, egg, and tar.gz files are available on `pypi <https://pypi.org/project/sasdata/>`_.

New in Version 0.9.0
--------------------
This is an enhancement release with updates to the unit conversion routines, the ability to load data from URIs, the
addition of a wedge slicer and other slicer enhancements.

What's Changed
^^^^^^^^^^^^^^

Feature Enhancements
____________________
* Refactor nxsunit by @krzywon in https://github.com/SasView/sasdata/pull/13
* Enable the sector slicing to allow both sides independantly by @butlerpd in https://github.com/SasView/sasdata/pull/36
* Load data from URIs by @krzywon in https://github.com/SasView/sasdata/pull/37
* SasData counterpart to SasView SlicerExtension_1344 by @jack-rooks in https://github.com/SasView/sasdata/pull/61

Bug Fixes
_________
* Fixing Issue #40 (but properly this time) by @ehewins in https://github.com/SasView/sasdata/pull/42
* changed xaxis label for updated SESANS nomenclature from z to delta by @caitwolf in https://github.com/SasView/sasdata/pull/60
* Fix delta in sesans docs by @caitwolf in https://github.com/SasView/sasdata/pull/65

Documentation Changes
_____________________
* Rework readme by @krzywon in https://github.com/SasView/sasdata/pull/15
* Building sasdata documentation by @krzywon in https://github.com/SasView/sasdata/pull/53
* Generate Developer Docs by @krzywon in https://github.com/SasView/sasdata/pull/56

Infrastructure Changes
______________________
* Remove entry_point from setup.py by @krzywon in https://github.com/SasView/sasdata/pull/2
* Dependency cleanup by @krzywon in https://github.com/SasView/sasdata/pull/33
* Move example data to sasdata by @krzywon in https://github.com/SasView/sasdata/pull/49
* CI updates by @krzywon in https://github.com/SasView/sasdata/pull/50
* Restrict lxml to versions less than 5.0 by @krzywon in https://github.com/SasView/sasdata/pull/63
* Update example data by @smk78 in https://github.com/SasView/sasdata/pull/58
* Fix broken unit test(s) by @krzywon in https://github.com/SasView/sasdata/pull/68

New in Version 0.8.1
--------------------
This is a point release to fix a build issue. The `sasdata.data_utils` package was omitted from setup.py causing an
Expand Down
6 changes: 3 additions & 3 deletions docs/source/user/data/data_formats_help.rst
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,10 @@ between users.
Following the header are up to 8 space-delimited columns of experimental
variables of which the first 4 columns are required. In order, these are:

- Spin-echo length (z, in Angstroms)
- Depolarization (:math:`log(P/P_0)/(lambda^2 * thickness)`, in Angstrom :sup:`-1` cm :sup:`-1`\ )
- Spin-echo length (:math:`\delta`, in Angstroms)
- Depolarization (:math:`log(P/P_0)/(\lambda^2 * thickness)`, in Angstrom :sup:`-1` cm :sup:`-1`\ )
- Depolarization error (also in in Angstrom :sup:`-1` cm :sup:`-1`\ ) (i.e. the measurement error)
- Spin-echo length error (:math:`\Delta`\ z, in Angstroms) (i.e. the experimental resolution)
- Spin-echo length error (:math:`\Delta \delta`, in Angstroms) (i.e. the experimental resolution)
- Neutron wavelength (:math:`\lambda`, in Angstroms)
- Neutron wavelength error (:math:`\Delta \lambda`, in Angstroms)
- Normalized polarization (:math:`P/P_0`, unitless)
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ h5py
lxml

# Calculation
numpy
numpy==1.*
scipy

# Unit testing
Expand Down
2 changes: 1 addition & 1 deletion sasdata/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
from pathlib import Path

__version__ = "0.8.1"
__version__ = "0.9.0"

# An importable path to the example data to
data_path: Path = Path(os.path.join(Path(os.path.dirname(__file__)), 'example_data'))
3 changes: 3 additions & 0 deletions sasdata/checklist.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Things to check once everything is in place:

1) Do any centigrade fields read in incorrectly?
42 changes: 42 additions & 0 deletions sasdata/data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from enum import Enum
from typing import TypeVar, Any, Self
from dataclasses import dataclass

from quantities.quantity import NamedQuantity
from sasdata.metadata import Metadata
from sasdata.quantities.accessors import AccessorTarget
from sasdata.data_backing import Group, key_tree


class SasData:
def __init__(self, name: str, data_contents: list[NamedQuantity], raw_metadata: Group, verbose: bool=False):
self.name = name
self._data_contents = data_contents
self._raw_metadata = raw_metadata
self._verbose = verbose

self.metadata = Metadata(AccessorTarget(raw_metadata, verbose=verbose))

# TO IMPLEMENT

# abscissae: list[NamedQuantity[np.ndarray]]
# ordinate: NamedQuantity[np.ndarray]
# other: list[NamedQuantity[np.ndarray]]
#
# metadata: Metadata
# model_requirements: ModellingRequirements

def summary(self, indent = " ", include_raw=False):
s = f"{self.name}\n"

for data in self._data_contents:
s += f"{indent}{data}\n"

s += f"Metadata:\n"
s += "\n"
s += self.metadata.summary()

if include_raw:
s += key_tree(self._raw_metadata)

return s
126 changes: 126 additions & 0 deletions sasdata/data_backing.py
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 6df7930

Please sign in to comment.