Skip to content

Commit

Permalink
v0.4.1
Browse files Browse the repository at this point in the history
  • Loading branch information
CianLM committed Aug 25, 2022
1 parent e66691f commit 9bf5a1b
Show file tree
Hide file tree
Showing 14 changed files with 120 additions and 48 deletions.
9 changes: 7 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@

# Looking Forward

- Computer Modern Plot font
- CSV data import workflow
- Numpy input integration
- Interface with statistical packages for non-linear regression
- Printing latex for the error propogation done
- Printing latex for the error propagation done
- Documentation

# v0.4.1
- Fixed exponential error propogation
- Documentation/guides coming very soon
- Fixed exponential and inverse tangent error propagation
- Fixed other minor bugs
# v0.4.0

New Features:
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ Alternatively if an `upright` table is preferred, this may be specified through
doc.table(
nameandsymbol = ["Voltage, V", "Temperature, T"],
data = [voltages,temperatures**2],
caption = "Voltage and Temperature Sqaured Correlation",
caption = "Voltage and Temperature Squared Correlation",
style = "upright"
)
```
Expand All @@ -130,7 +130,7 @@ Once again this inserts the following into the `doc` instance.
```latex
\begin{table}[ht]
\centering
\caption{Voltage and Temperature Sqaured Correlation}
\caption{Voltage and Temperature Squared Correlation}
\label{tab:2}
\begin{tabular}{*{2}c}
\toprule
Expand Down Expand Up @@ -166,7 +166,7 @@ This generates the graph below and saves it to `figures/graph1.png`. If you want

![](https://github.com/CianLM/labtex/raw/master/figures/graph1.png)

Once you have added all your tables and graphs to the `doc` object, you may save this file as shown below. The default write directory is `tex/` relative to root. This directory is also customisable with `Document.texfolder`.
Once you have added all your tables and graphs to the `doc` object, you may save this file as shown below. The default write directory is `tex/` relative to root. This directory is also customizable with `Document.texfolder`.

```python
doc.save("test")
Expand Down
Binary file modified figures/graph1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified figures/graph2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified figures/readmetable2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion labtex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
from labtex.document import Document


__version__ = '0.4.1-alpha'
__version__ = '0.4.1'
6 changes: 3 additions & 3 deletions labtex/linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ def __init__(self,x : MeasurementList, y : MeasurementList):
def __repr__(self):
return f"m = {self.lobf['m']}\nc = {self.lobf['c']}"

def plot(self, title: str = "", xname : str = "", yname: str = "", showline : bool = True, graphnumber : int = 0):
def plot(self, title: str = "", xnameandsymbol : str = "", ynameandsymbol: str = "", showline : bool = True, graphnumber : int = 0):
plt.figure(graphnumber)
plt.errorbar(self.x.values(),self.y.values(), yerr = self.y.uncertainties(),fmt='o')
if showline:
plt.plot(self.x.values(), (self.x * self.lobf["m"].value + self.lobf["c"].value).values() )
plt.title(title)
plt.xlabel(xname + f"{', ($' + str(self.x.unit) + '$)' if self.x.unit != '' else ''}")
plt.xlabel(yname + f"{', ($' + str(self.y.unit) + '$)' if self.y.unit != '' else ''}")
plt.xlabel(xnameandsymbol + f"{', ($ ' + str(self.x.unit) + '$)' if self.x.unit != '' else ''}")
plt.ylabel(ynameandsymbol + f"{', ($ ' + str(self.y.unit) + '$)' if self.y.unit != '' else ''}")
return plt
40 changes: 18 additions & 22 deletions labtex/measurement.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
from numbers import Number
from typing import Union
from labtex.unit import Unit
from labtex.unit import factorandbasedims

class Measurement:
"Base Class for all single valued measurements with uncertainties and SI units."
def __init__(self, value: float, uncertainty: float = 0, unit: Union[Unit,str] = ""):
"Create a measurement with a value, uncertainty and SI Unit."
self.value = value
self.uncertainty = uncertainty
self.relativeuncertainty = self.uncertainty / self.value
self.relativeuncertainty = self.uncertainty / self.value if self.value != 0 else 0

self.unit = unit if (isinstance(unit,Unit)) else Unit(unit)

Expand Down Expand Up @@ -151,10 +153,21 @@ def __rtruediv__(self,obj):

def __pow__(self,obj):
"Raising a measurement to a constant power."
if(isinstance(obj,Measurement)):
if(Unit.unitless(obj.unit)):
return Measurement(
self.value ** obj.value,
abs(self.value ** obj.value) * math.sqrt(
(obj.value * self.relativeuncertainty)**2 + (math.log(obj.value) * obj.uncertainty)**2
),
self.unit ** obj.value
)
else:
raise Exception("Cannot raise a constant to a dimensional quantity. Units: " + str(self.unit))
if(isinstance(obj,Number)):
return Measurement(
self.value ** obj,
(self.value ** obj) * abs(obj) * self.relativeuncertainty,
abs(self.value ** obj * obj) * self.relativeuncertainty,
self.unit ** obj
)
else:
Expand Down Expand Up @@ -206,7 +219,7 @@ def tan(x):
if(Unit.unitless(x.unit)):
return Measurement(
math.tan(x.value),
math.fabs( 1 / math.cos(x.value)**2 ) * x.uncertainty,
x.uncertainty / math.cos(x.value)**2 ,
""
)
else:
Expand Down Expand Up @@ -254,7 +267,7 @@ def atan(x):
if(Unit.unitless(x.unit)):
return Measurement(
math.atan(x.value),
x.uncertainty / (math.sqrt(1 - x.value**2)),
x.uncertainty / (math.sqrt(1 + x.value**2)),
""
)
else:
Expand All @@ -277,23 +290,6 @@ def to(self, unit : Union[str,Unit]):
raise Exception(f"Dimension Error: Cannot convert from {self.unit} to {unit} because they have different dimensions.")

class M(Measurement):
"Base Class for all single valued measurements with uncertainties and SI units."
pass

def factorandbasedims(unit : Unit):
factor = 1
basedims = { unit : 0 for unit in Unit.baseUnits }
for dim in unit.units:
if unit.units[dim]['power'] != 0 and dim in Unit.derivedUnits:
if len(Unit.derivedUnits[dim]) == 2:
factor *= Unit.derivedUnits[dim][1] ** unit.units[dim]['power']
# print('Derived unit: ' + str(dim))
equivalentunits = Unit(Unit.derivedUnits[dim][0]).units
for baseUnit in Unit.baseUnits: # as all equivalent units are base units
# print('Base unit: ' + str(baseUnit) + 'power: ' + str(equivalentunits[baseUnit]['power']))
basedims[baseUnit] += equivalentunits[baseUnit]['power'] * unit.units[dim]['power']
factor *= Unit.prefixes[equivalentunits[baseUnit]['prefix']]**(equivalentunits[baseUnit]['power'] * unit.units[dim]['power'])
elif dim in Unit.baseUnits:
# print('Base unit: ' + str(dim))
basedims[dim] += unit.units[dim]['power']
factor *= Unit.prefixes[unit.units[dim]['prefix']]**unit.units[dim]['power']
return factor, basedims
38 changes: 33 additions & 5 deletions labtex/measurementlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ class MeasurementList:
`>>> MeasurementList(
... [Measurement(1,0.1,"m"),Measurement(2,0.2,"m"),Measurement(3,0.3,"m")]
... )`
"""
def __init__(self,measurements: Union[List[Number],List[Measurement]], uncertainty: Union[Number,List] = math.nan, unit: Union[Unit,str] = ""):

Expand Down Expand Up @@ -79,15 +78,23 @@ def values(self):
def uncertainties(self):
return [measurement.uncertainty for measurement in self]

def append(self,obj):
# mutating
def concat(self,obj):
"Non-mutating concatenation of a Measurement/MeasurementList to the current MeasurementList."
if(isinstance(obj,MeasurementList)):
return MeasurementList([*[measurement for measurement in self], *[measurement for measurement in obj]])
if(self.unit == obj.unit):
return MeasurementList([*[measurement for measurement in self], *[measurement for measurement in obj]])
else:
raise Exception("MeasurementList Error: Cannot append two MeasurementLists with different units.")
elif(isinstance(obj,Measurement)):
return MeasurementList([*[measurement for measurement in self], obj])
if(self.unit == obj.unit):
return MeasurementList([*[measurement for measurement in self], obj])
else:
raise Exception("MeasurementList Error: Cannot append a MeasurementList and a Measurement with different units.")
else:
raise Exception(f"Object of type {type(obj)} cannot be appended to a MeasurementList. Try a MeasurementList or a Measurement.")


def __add__(self,obj):
"Elementwise addition of two MeasurementLists. If a Measurement is added, it is added to all Measurements in the list."
if(isinstance(obj,MeasurementList)):
Expand Down Expand Up @@ -163,6 +170,11 @@ def __rpow__(self,obj):
[obj ** measurement for measurement in self]
)

def to(self,unit : str):
return MeasurementList(
[measurement.to(unit) for measurement in self]
)

@staticmethod
def sin(x):
return MeasurementList(
Expand Down Expand Up @@ -206,4 +218,20 @@ def atan(x):
)

class ML(MeasurementList):
"""An extension of the measurement class to take list values. Can be instantiated in a number of ways:
- Using lists for the values and the uncertainty
`>>> MeasurementList([1,2,3],[0.1,0.2,0.3],"m")`
- Using lists for the values and a single uncertainty for all measurements
`>>> MeasurementList([1,2,3],0.1,"m")`
- Using A list of `Measurement` instances
`>>> MeasurementList(
... [Measurement(1,0.1,"m"),Measurement(2,0.2,"m"),Measurement(3,0.3,"m")]
... )`
"""
pass
57 changes: 48 additions & 9 deletions labtex/unit.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
from numbers import Number
from typing import Union
import math

Expand Down Expand Up @@ -31,7 +32,8 @@ class Unit:
# 'f':1e-15,
'p':1e-12,
'n':1e-9,
'u':1e-6,
'u':1e-6, # u \approx µ for usability
'µ':1e-6,
'm':1e-3,
'c':1e-2,
'' :1,
Expand Down Expand Up @@ -75,6 +77,7 @@ def parse(self,unitString):
# Match a known unit
# Compiles to '([JVNWTmgsACK]|(?:Pa)|(?:Hz))' for default (base + derived) units
unit = re.compile(f"([{''.join([ unit_str if len(unit_str) == 1 else '' for unit_str in Unit.knownUnits])}]|{'|'.join([ ('(?:' + unit_str + ')') for unit_str in filter(lambda x: len(x) > 1, Unit.knownUnits) ])})")
# print(unit)
# Match a '^' followed optionally by '-' and then any number of digits
power = re.compile('(\^)(\-?)(\d+)')
# power = re.compile(r'\^(?:-?\d+)')
Expand Down Expand Up @@ -129,12 +132,25 @@ def singularunit(self):
else:
return False

@staticmethod
def addUnit(symbol : str, SI_equivalent : str, constant_factor : float = 1):
# inefficient but functional
Unit.derivedUnits[symbol] = [SI_equivalent, constant_factor]
Unit.knownUnits += [symbol]



# def __eq__(self,obj):
# "Check if two Units are the same."
# if (isinstance(obj,Unit)):
# return all(self.units[unit] == obj.units[unit] for unit in Unit.knownUnits)
# return False

def __eq__(self,obj):
def __eq__(self, obj):
"Check if two Units are the same."
if (isinstance(obj,Unit)):
return all(self.units[unit] == obj.units[unit] for unit in Unit.knownUnits)
return False
obj = obj if isinstance(obj,Unit) else Unit(obj)
return factorandbasedims(self) == factorandbasedims(obj) if isinstance(obj,Unit) else False


def __mul__(self,obj):
"Multiply two Units."
Expand All @@ -153,10 +169,12 @@ def __mul__(self,obj):
"power": self.units[unit]["power"] + obj.units[unit]["power"]
}
else:
raise Exception("Units have different prefixes. Multiplication not supported.")
raise Exception("Units have different prefixes. Multiplication not supported as constant factors arise..")
return Unit(newunits)
else:
elif(isinstance(obj,Number)):
return self
else:
return NotImplemented

def __rmul__(self,obj):
return self.__mul__(obj)
Expand All @@ -177,7 +195,7 @@ def __truediv__(self,obj):
"power": self.units[unit]["power"] - obj.units[unit]["power"]
}
else:
raise Exception("Measurements have different prefixes. Division not supported.")
raise Exception("Measurements have different prefixes. Division not supported as constant factors arise.")
return Unit(newunits)
else:
return self.__mul__(1/obj)
Expand All @@ -203,4 +221,25 @@ def __pow__(self,obj):
return Unit(newunits)

class U(Unit):
pass
"SI Unit taking in a string."
pass

def factorandbasedims(unit):
factor = 1
basedims = { unit : 0 for unit in Unit.baseUnits }
for dim in unit.units:
if unit.units[dim]['power'] != 0 and dim in Unit.derivedUnits:
if len(Unit.derivedUnits[dim]) == 2:
factor *= (Unit.derivedUnits[dim][1]) ** unit.units[dim]['power']
# print('Derived unit: ' + str(dim))
factor *= Unit.prefixes[unit.units[dim]['prefix']] ** unit.units[dim]['power']
equivalentunits = Unit(Unit.derivedUnits[dim][0]).units
for baseUnit in Unit.baseUnits: # as all equivalent units are base units
# print('Base unit: ' + str(baseUnit) + 'power: ' + str(equivalentunits[baseUnit]['power']))
basedims[baseUnit] += equivalentunits[baseUnit]['power'] * unit.units[dim]['power']
factor *= Unit.prefixes[equivalentunits[baseUnit]['prefix']]**(equivalentunits[baseUnit]['power'] * unit.units[dim]['power'])
elif dim in Unit.baseUnits:
# print('Base unit: ' + str(dim))
basedims[dim] += unit.units[dim]['power']
factor *= Unit.prefixes[unit.units[dim]['prefix']]**unit.units[dim]['power']
return factor, basedims
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
DIR = pathlib.Path(__file__).parent
README = (DIR / 'README.md').read_text()

VERSION = '0.4.1-alpha'
VERSION = '0.4.1'
DESCRIPTION = 'Lab report data analysis and LaTeX file generation'

setuptools.setup(
Expand Down
4 changes: 4 additions & 0 deletions test/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# To run tests, if you are in the root of the project:
# source test/venv/bin/activate
# python3 -m unittest
# These commands activate a virtual environment and run the tests respectively.
2 changes: 1 addition & 1 deletion test/test_measurement.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def test_functions(self):
self.assertEqual( repr(Measurement.log(x/y)), "-0.7 ± 0.3 ")
self.assertEqual( repr(Measurement.asin(x/y)), "0.5 ± 0.2 ")
self.assertEqual( repr(Measurement.acos(x/y)), "1.1 ± 0.2 ")
self.assertEqual( repr(Measurement.atan(x/y)), "0.5 ± 0.2 ")
self.assertEqual( repr(Measurement.atan(x/y)), "0.5 ± 0.1 ")

def test_conversion(self):
self.assertEqual( repr(Measurement(2,1,'cm^3').to('m^3')), "(2 ± 1) × 10^{-6} m^3")
Expand Down
2 changes: 1 addition & 1 deletion test/test_measurementlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def test_functions(self):
repr(MeasurementList.acos(heights/maxheight)), "[0.39 ± 0.09, 0.42 ± 0.08, 0.43 ± 0.08, 0.2 ± 0.2, 0.4 ± 0.1, 0.5 ± 0.1] "
)
self.assertEqual(
repr(MeasurementList.atan(heights/maxheight)), "[0.75 ± 0.09, 0.74 ± 0.08, 0.74 ± 0.08, 0.8 ± 0.2, 0.7 ± 0.1, 0.7 ± 0.1] "
repr(MeasurementList.atan(heights/maxheight)), "[0.75 ± 0.03, 0.74 ± 0.02, 0.74 ± 0.03, 0.77 ± 0.03, 0.74 ± 0.03, 0.72 ± 0.04] "
)
def test_exceptions(self):
with self.assertRaises(Exception):
Expand Down

0 comments on commit 9bf5a1b

Please sign in to comment.