Skip to content

Commit

Permalink
Merge pull request #78 from SasView/76-basic-outline-of-new-data-types
Browse files Browse the repository at this point in the history
76 basic outline of new data types
  • Loading branch information
lucas-wilkins authored Aug 6, 2024
2 parents a113ad6 + 41dc519 commit fc433c3
Show file tree
Hide file tree
Showing 7 changed files with 356 additions and 0 deletions.
18 changes: 18 additions & 0 deletions sasdata/data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from dataclasses import dataclass
from units_temp import Quantity, NamedQuantity

import numpy as np

from sasdata.model_requirements import ModellingRequirements




@dataclass
class SASData:
abscissae: list[NamedQuantity[np.ndarray]]
ordinate: NamedQuantity[np.ndarray]
other: list[NamedQuantity[np.ndarray]]

metadata: MetaData
model_requirements: ModellingRequirements
76 changes: 76 additions & 0 deletions sasdata/dataset_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
""" Information used for providing guesses about what text based files contain """

from dataclasses import dataclass

#
# 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", "shadow"],
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", "shadow"],
expected_orders=[
["Qx", "Qy", "I"],
["Qx", "Qy", "I", "dI"],
["Qx", "Qy", "dQx", "dQy", "I", "dI"]])

sesans = DatasetType(
name="SESANS",
required=["z", "G"],
optional=["stuff", "other stuff", "more stuff"],
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
#
default_units = {
"Q": "1/A",
"I": "1/cm",
"Qx": "1/A",
"Qy": "1/A",
"Qz": "1/A",
"dI": "1/A",
"dQ": "1/A",
"dQx": "1/A",
"dQy": "1/A",
"dQz": "1/A",
"z": "A",
"G": "<none>",
"shaddow": "<none>",
"temperature": "K",
"magnetic field": "T"
}

#
# Other possible fields. Ultimately, these should come out of the metadata structure
#

metadata_fields = [
"temperature",
"magnetic field",
]



11 changes: 11 additions & 0 deletions sasdata/distributions.py
Original file line number Diff line number Diff line change
@@ -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")
64 changes: 64 additions & 0 deletions sasdata/metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from typing import Generic, TypeVar

from numpy._typing import ArrayLike

from sasdata.quantities.quantities import Unit, Quantity


class RawMetaData:
pass

class MetaData:
pass


FieldDataType = TypeVar("FieldDataType")
OutputDataType = TypeVar("OutputDataType")

class Accessor(Generic[FieldDataType, OutputDataType]):
def __init__(self, target_field: str):
self._target_field = target_field

def _raw_values(self) -> FieldDataType:
raise NotImplementedError("not implemented in base class")

@property
def value(self) -> OutputDataType:
raise NotImplementedError("value not implemented in base class")



class QuantityAccessor(Accessor[ArrayLike, Quantity[ArrayLike]]):
def __init__(self, target_field: str, units_field: str | None = None):
super().__init__(target_field)
self._units_field = units_field

def _get_units(self) -> Unit:
pass

def _raw_values(self) -> ArrayLike:
pass


class StringAccessor(Accessor[str]):
@property
def value(self) -> str:
return self._raw_values()


class LengthAccessor(QuantityAccessor):
@property
def m(self):
return self.value.in_units_of("m")


class TimeAccessor(QuantityAccessor):
pass


class TemperatureAccessor(QuantityAccessor):
pass


class AbsoluteTemperatureAccessor(QuantityAccessor):
pass
22 changes: 22 additions & 0 deletions sasdata/model_requirements.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from dataclasses import dataclass

import numpy as np

from transforms.operation import Operation


@dataclass
class ModellingRequirements:
""" Requirements that need to be passed to any modelling step """
dimensionality: int
operation: Operation


def from_qi_transformation(self, data: np.ndarray) -> np.ndaarray:
pass




def guess_requirements(abscissae, ordinate) -> ModellingRequirements:
""" Use names of axes and units to guess what kind of processing needs to be done """
146 changes: 146 additions & 0 deletions sasdata/quantities/quantities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
from typing import Collection, Sequence, TypeVar, Generic
from dataclasses import dataclass

class Dimensions:
"""
Note that some SI Base units are
For example, moles and angular measures are dimensionless from this perspective, and candelas are
"""
def __init__(self,
length: int = 0,
time: int = 0,
mass: int = 0,
current: int = 0,
temperature: int = 0):

self.length = length
self.time = time
self.mass = mass
self.current = current
self.temperature = temperature

def __mul__(self, other: "Dimensions"):

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)

def __truediv__(self, other: "Dimensions"):

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)

def __pow__(self, power: int):

if not isinstance(power, int):
return NotImplemented

return Dimensions(
self.length * power,
self.time * power,
self.mass * power,
self.current * power,
self.temperature * power)


@dataclass
class UnitName:
ascii_name: str
unicode_name: str | None = None

@property
def best_name(self):
if self.unicode_name is None:
return self.ascii_name
else:
return self.unicode_name

class Unit:
def __init__(self,
si_scaling_factor: float,
dimensions: Dimensions,
name: UnitName | None = None):

self.scale = si_scaling_factor
self.dimensions = dimensions
self.name = name

def _components(self, tokens: Sequence["UnitToken"]):
pass

def __mul__(self, other: "Unit"):
if not isinstance(other, Unit):
return NotImplemented

return Unit(self.scale * other.scale, self.dimensions * other.dimensions)

def __truediv__(self, other: "Unit"):
if not isinstance(other, Unit):
return NotImplemented

return Unit(self.scale / other.scale, self.dimensions / other.dimensions)

def __pow__(self, power: int):
if not isinstance(power, int):
return NotImplemented

return Unit(self.scale**power, self.dimensions**power)


QuantityType = TypeVar("QuantityType")
class Quantity(Generic[QuantityType]):
def __init__(self, value: QuantityType, units: Unit):
self.value = value
self.units = units

def in_units_of(self, units: Unit) -> QuantityType:
pass

class ExpressionMethod:
pass


class SetExpressionMethod(ExpressionMethod):
pass


class AnyExpressionMethod(ExpressionMethod):
pass


class ForceExpressionMethod(ExpressionMethod):
pass


class UnitToken:
def __init__(self, unit: Collection[NamedUnit], method: ExpressionMethod):
pass

unit_dictionary = {
"Amps": Unit(1, Dimensions(current=1), UnitName("A")),
"Coulombs": Unit(1, Dimensions(current=1, time=1), UnitName("C"))
}

@dataclass
class Disambiguator:
A: Unit = unit_dictionary["Amps"]
C: Unit = unit_dictionary["Coulombs"]

def parse_units(unit_string: str, disambiguator: Disambiguator = Disambiguator()) -> Unit:
pass
19 changes: 19 additions & 0 deletions sasdata/transforms/operation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import numpy as np
from sasdata.quantities.quantities import Quantity

class Operation:
""" Sketch of what model post-processing classes might look like """

children: list["Operation"]
named_children: dict[str, "Operation"]

@property
def name(self) -> str:
raise NotImplementedError("No name for transform")

def evaluate(self) -> Quantity[np.ndarray]:
pass

def __call__(self, *children, **named_children):
self.children = children
self.named_children = named_children

0 comments on commit fc433c3

Please sign in to comment.