Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

76 basic outline of new data types #80

Merged
merged 4 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion sasdata/data.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from dataclasses import dataclass
from units_temp import Quantity, NamedQuantity
from quantities.quantities import Quantity, NamedQuantity
from sasdata.metadata import MetaData

import numpy as np

Expand Down
36 changes: 28 additions & 8 deletions sasdata/metadata.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Generic, TypeVar
from typing import TypeVar

from numpy._typing import ArrayLike

Expand All @@ -8,14 +8,11 @@
class RawMetaData:
pass

class MetaData:
pass


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

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

Expand All @@ -33,18 +30,29 @@ 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:
def _units(self) -> Unit:
pass

def _raw_values(self) -> ArrayLike:
pass

@property
def value(self) -> Quantity[ArrayLike]:
return Quantity(self._raw_values(), self._units())


class StringAccessor(Accessor[str, str]):

def _raw_values(self) -> str:
pass

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

#
# Quantity specific accessors, provides helper methods for quantities with known dimensionality
#

class LengthAccessor(QuantityAccessor):
@property
Expand All @@ -61,4 +69,16 @@ class TemperatureAccessor(QuantityAccessor):


class AbsoluteTemperatureAccessor(QuantityAccessor):
pass
pass


#
# Main metadata object
#


class MetaData:
def __init__(self, raw: RawMetaData):
self._raw = raw

# Put the structure of the metadata that should be exposed to a power-user / developer in here
168 changes: 168 additions & 0 deletions sasdata/quantities/_units_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
from dataclasses import dataclass
from typing import Sequence, Self, TypeVar

import numpy as np

from sasdata.quantities.unicode_superscript import int_as_unicode_superscript


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: 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)

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)

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)

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)

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

return 2**two_powers * 3**abs(self.length) * 5**abs(self.time) * \
7**abs(self.mass) * 11**abs(self.current) * 13**abs(self.temperature)

def __repr__(self):
s = ""
for name, size in [
("L", self.length),
("T", self.time),
("M", self.mass),
("C", self.current),
("K", self.temperature)]:

if size == 0:
pass
elif size == 1:
s += f"{name}"
else:
s += f"{name}{int_as_unicode_superscript(size)}"

return s

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

self.scale = si_scaling_factor
self.dimensions = dimensions
self.name = name
self.ascii_symbol = ascii_symbol
self.symbol = symbol

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

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

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

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

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

def __rtruediv__(self: Self, other: Self):
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):
if not isinstance(power, int):
return NotImplemented

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

def equivalent(self: Self, other: Self):
return self.dimensions == other.dimensions

def __eq__(self: Self, other: Self):
return self.equivalent(other) and np.abs(np.log(self.scale/other.scale)) < 1e-5

class UnitGroup:
def __init__(self, name: str, units: list[Unit]):
self.name = name
self.units = sorted(units, key=lambda unit: unit.scale)
Loading