Skip to content

Commit

Permalink
Merge branch 'refactor_24' into unit_parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
jamescrake-merani authored Oct 1, 2024
2 parents 5f00767 + 3d62f9d commit 544f131
Show file tree
Hide file tree
Showing 18 changed files with 858 additions and 143 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
python-version: ['3.10', '3.11']
python-version: ['3.12']
fail-fast: false

steps:
Expand Down
2 changes: 1 addition & 1 deletion sasdata/data.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from sasdata.quantities.quantity import BaseQuantity, NamedQuantity
from sasdata.quantities.quantity import Quantity, NamedQuantity
from sasdata.metadata import Metadata

import numpy as np
Expand Down
8 changes: 4 additions & 4 deletions sasdata/quantities/_accessor_base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import TypeVar, Sequence

from sasdata.quantities.quantity import BaseQuantity
from sasdata.quantities.quantity import Quantity
import sasdata.quantities.units as units
from sasdata.quantities.units import Dimensions, Unit

Expand Down Expand Up @@ -33,7 +33,7 @@ def value(self) -> float | None:



class QuantityAccessor[DataType](Accessor[DataType, BaseQuantity[DataType]]):
class QuantityAccessor[DataType](Accessor[DataType, Quantity[DataType]]):
""" Base class for accessors that work with quantities that have units """
def __init__(self, target_object, value_target: str, unit_target: str, default_unit=None):
super().__init__(target_object, value_target)
Expand All @@ -54,8 +54,8 @@ def unit(self) -> Unit:
return Unit.parse(self._unit_part())

@property
def value(self) -> BaseQuantity[DataType] | None:
def value(self) -> Quantity[DataType] | None:
if self._unit_part() is not None and self._numerical_part() is not None:
return BaseQuantity(self._numerical_part(), self.unit)
return Quantity(self._numerical_part(), self.unit)


3 changes: 2 additions & 1 deletion sasdata/quantities/_build_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@
"Ang": ["A", "Å"],
"au": ["a.u.", "amu"],
"percent": ["%"],
"deg": ["degr"],
"deg": ["degr", "Deg", "degrees", "Degrees"],
"none": ["Counts", "counts", "cnts", "Cnts"]
}


Expand Down
91 changes: 79 additions & 12 deletions sasdata/quantities/_units_base.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from dataclasses import dataclass
from typing import Sequence, Self, TypeVar
from fractions import Fraction

import numpy as np

from sasdata.quantities.unicode_superscript import int_as_unicode_superscript

class DimensionError(Exception):
pass

class Dimensions:
"""
Expand Down Expand Up @@ -65,19 +69,46 @@ def __truediv__(self: Self, other: Self):
self.moles_hint - other.moles_hint,
self.angle_hint - other.angle_hint)

def __pow__(self, power: int):
def __pow__(self, power: int | float):

if not isinstance(power, int):
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 * power,
self.time * power,
self.mass * power,
self.current * power,
self.temperature * power,
self.moles_hint * power,
self.angle_hint * power)
(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):
Expand Down Expand Up @@ -140,6 +171,36 @@ def __repr__(self):

return s

def si_repr(self):
s = ""
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:
s += f"{name}"
else:
s += f"{name}{int_as_unicode_superscript(size)}"

match self.angle_hint:
case 0:
pass
case 2:
s += "sr"
case -2:
s += "sr" + int_as_unicode_superscript(-1)
case _:
s += "rad" + int_as_unicode_superscript(self.angle_hint)

return s


class Unit:
def __init__(self,
si_scaling_factor: float,
Expand Down Expand Up @@ -171,12 +232,13 @@ def __rtruediv__(self: Self, other: "Unit"):
else:
return NotImplemented

def __pow__(self, power: int):
if not isinstance(power, int):
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

Expand All @@ -192,7 +254,12 @@ def _format_unit(self, format_process: list["UnitFormatProcessor"]):
pass

def __repr__(self):
return f"Unit[{self.scale}, {self.dimensions}]"
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":
Expand Down
6 changes: 3 additions & 3 deletions sasdata/quantities/absolute_temperature.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from typing import TypeVar

from quantities.quantity import BaseQuantity
from 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) -> BaseQuantity[DataType] | None:
def value(self) -> Quantity[DataType] | None:
if self._numerical_part() is None:
return None
else:
return BaseQuantity.parse(self._numerical_part(), self._unit_part(), absolute_temperature=True)
return Quantity.parse(self._numerical_part(), self._unit_part(), absolute_temperature=True)
8 changes: 4 additions & 4 deletions sasdata/quantities/accessors.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@

from typing import TypeVar, Sequence

from sasdata.quantities.quantity import BaseQuantity
from sasdata.quantities.quantity import Quantity
import sasdata.quantities.units as units
from sasdata.quantities.units import Dimensions, Unit

Expand Down Expand Up @@ -113,7 +113,7 @@ def value(self) -> float | None:



class QuantityAccessor[DataType](Accessor[DataType, BaseQuantity[DataType]]):
class QuantityAccessor[DataType](Accessor[DataType, Quantity[DataType]]):
""" Base class for accessors that work with quantities that have units """
def __init__(self, target_object, value_target: str, unit_target: str, default_unit=None):
super().__init__(target_object, value_target)
Expand All @@ -134,9 +134,9 @@ def unit(self) -> Unit:
return Unit.parse(self._unit_part())

@property
def value(self) -> BaseQuantity[DataType] | None:
def value(self) -> Quantity[DataType] | None:
if self._unit_part() is not None and self._numerical_part() is not None:
return BaseQuantity(self._numerical_part(), self.unit)
return Quantity(self._numerical_part(), self.unit)



Expand Down
23 changes: 23 additions & 0 deletions sasdata/quantities/notes.rst
Original file line number Diff line number Diff line change
@@ -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.
22 changes: 10 additions & 12 deletions sasdata/quantities/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import json

# from sasdata.quantities.quantity import BaseQuantity

T = TypeVar("T")

def hash_and_name(hash_or_name: int | str):
Expand Down Expand Up @@ -75,21 +73,21 @@ def derivative(self, variable: Union[str, int, "Variable"], simplify=True):

derivative_string = derivative.serialise()

print("---------------")
print("Base")
print("---------------")
print(derivative.summary())
# 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("-------------------")
#
# print("-------------------")
# print("Iteration", i+1)
# print("-------------------")
# print(derivative.summary())
# print("-------------------")

new_derivative_string = derivative.serialise()

Expand Down
Loading

0 comments on commit 544f131

Please sign in to comment.