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

[Refactor] Implement derived value with error propagation #73

Merged
merged 25 commits into from
Apr 22, 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
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ exclude_also =
; Don't complain if tests don't hit defensive assertion code:
raise AssertionError
raise NotImplementedError
return NotImplemented

; Don't complain about abstract methods, they aren't run:
@(abc\.)?abstractmethod
3 changes: 3 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,7 @@ jobs:
- uses: Queens-Physics/coverage@main
with:
coverageFile: ./coverage.xml
thresholdAll: 0.99
thresholdNew: 1
thresholdModified: 1
token: ${{ secrets.GITHUB_TOKEN }}
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
* Users can now define a custom alias for a compound unit. e.g., define `"N"` as `"kg*m/s^2"`, and
it will be treated as its expanded form during calculations.

* Common physical constants are added and can be access from `qexpy`.
```pycon
>>> q.hbar
(1.05 +/- 0.00) * 10^-34 [J⋅s]
```

### Deprecations

* Support for Python 3.5, 3.6, 3.7, 3.8 are discontinued.
Expand All @@ -18,3 +24,15 @@

* Measurements are now semi-immutable. Users will no longer be able to override the value or
uncertainty of a recorded measurement.

* Mathematical functions are removed from QExPy. Instead, experimental values and arrays defined
in QExPy are now compatible with standard `numpy` functions.
```pycon
>>> m = q.Measurement(5, 0.1)
>>> q.sqrt(m) # this has now been removed
```
Instead, simply use functions from `numpy`.
```pycon
>>> np.sqrt(m)
2.00 +/- 0.02
```
62 changes: 62 additions & 0 deletions docs/source/api-reference/constants.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
.. _api.constants:

Constants
=========

QExPy makes available the following list of physical constants:

.. list-table::
:widths: 20 80
:header-rows: 1

* - Name
- Description
* - ``qexpy.e``
- `Elementary charge <https://en.wikipedia.org/wiki/Elementary_charge>`_ (electron charge)
* - ``qexpy.G``
- `Gravitational constant <https://en.wikipedia.org/wiki/Gravitational_constant>`_
* - ``qexpy.me``
- `Electron mass <https://en.wikipedia.org/wiki/Electron>`_
* - ``qexpy.c``
- `Speed of light in vacuum <https://en.wikipedia.org/wiki/Speed_of_light>`_
* - ``qexpy.eps0``
- `Vacuum electric permittivity <https://en.wikipedia.org/wiki/Vacuum_permittivity>`_ (permittivity of free space) in :math:`\text{F}\cdot\text{m}^{-1}`
* - ``qexpy.mu0``
- `Vacuum magnetic permeability <https://en.wikipedia.org/wiki/Vacuum_permeability>`_ (permeability of free space) in :math:`\text{N}\cdot\text{A}^{-2}`
* - ``qexpy.h``
- `Planck constant <https://en.wikipedia.org/wiki/Planck_constant>`_ in :math:`\text{J}\cdot \text{Hz}^{-1}`
* - ``qexpy.hbar``
- `Reduced Planck constant <https://en.wikipedia.org/wiki/Planck_constant>`_ in :math:`\text{J}\cdot \text{s}`
* - ``qexpy.kb``
- `Boltzmann constant <https://en.wikipedia.org/wiki/Boltzmann_constant>`_ in :math:`\text{J}\cdot \text{K}^{-1}`
* - ``qexpy.pi``
- The ratio of a circle's circumference to its diameter, written as :math:`\pi`

Compound Units
~~~~~~~~~~~~~~

Note that some of the constants are defined in terms of compound units such as :math:`\text{N}`,
:math:`\text{J}`, and :math:`\text{F}`. By default, they are not expanded into their full form
consisting only of base units. Depending on the use case, you might wish to unpack these compound
units differently. For example, :math:`\text{F}` can be expressed in many different ways. You may
choose to express it in terms of :math:`\text{N}`, :math:`\text{m}`, and :math:`\text{V}`:

.. ipython:: python

import qexpy as q
q.options.format.style.value = "scientific"
q.define_unit("F", "N*m/V^2")
q.eps0
res = q.eps0 * q.Measurement(4, 0.1, unit="V") ** 2
res

Define your own constants
~~~~~~~~~~~~~~~~~~~~~~~~~

QExPy only pre-defines some of the most common physical constants. You can easily define
a constant for yourself if needed:

.. ipython:: python

mp = q.Constant(1.67262192369e-27, 0.00000000051e-27, unit="kg")
mp
25 changes: 25 additions & 0 deletions docs/source/api-reference/core.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,28 @@ case, a subclass of :py:class:`Measurement` is created, that is the :py:class:`R

Measurement
RepeatedMeasurement

.. _api.core.derived_value:

DerivedValue
------------

The result of all calculations performed with :py:class:`ExperimentalValue` objects are wrapped in
this class. The DerivedValue supports different methods of error propagation.

.. autosummary::
:nosignatures:
:toctree: api/

DerivedValue

.. _api.core.constants:

Constants
---------

.. autosummary::
:nosignatures:
:toctree: api/

Constant
1 change: 1 addition & 0 deletions docs/source/api-reference/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ plotting: ``qexpy.plotting``, which can be imported separately if needed.
:caption: General Functions

options
constants

.. toctree::
:maxdepth: 1
Expand Down
36 changes: 36 additions & 0 deletions qexpy/core/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,42 @@
"""Defines the core data structures of QExPy"""

from .array import pack_data_arrays
from .constants import (
Constant,
e,
G,
me,
c,
eps0,
mu0,
h,
hbar,
kb,
pi,
)
from .derived_value import DerivedValue
from .experimental_value import ExperimentalValue
from .functions import correlation, covariance
from .measurement import Measurement, RepeatedMeasurement

__functions__ = [
"Measurement",
"Constant",
"correlation",
"covariance",
]

__constants__ = [
"e",
"G",
"me",
"c",
"eps0",
"mu0",
"h",
"hbar",
"kb",
"pi",
]

__all__ = __functions__ + __constants__
44 changes: 44 additions & 0 deletions qexpy/core/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Defines constants"""

from numbers import Real

from .experimental_value import ExperimentalValue
from .formula import _Formula


class Constant(ExperimentalValue, _Formula):
"""A numerical value."""

def __new__(cls, value: Real, error: Real = 0, name: str = "", unit: str = ""):
return object.__new__(Constant)

def __init__(self, value: Real, error: Real = 0, name: str = "", unit: str = ""):
self._value = float(value)
self._error = float(error)
super().__init__(name, unit)

@property
def value(self) -> float:
return self._value

@property
def error(self) -> float:
return self._error

def _derivative(self, x: _Formula) -> float:
return 0


# Physical constants
e = Constant(1.602176634e-19, unit="C") # elementary charge (electron charge)
G = Constant(6.6743e-11, 0.00015e-11, unit="m^3*kg^-1*s^-2") # gravitational constant
me = Constant(9.1093837015e-31, 0.0000000028e-31, unit="kg") # mass of electron
c = Constant(299792458, unit="m*s^-1") # speed of light
eps0 = Constant(8.8541878128e-12, 0.0000000013e-12, unit="F*m^-1") # electric permittivity
mu0 = Constant(1.25663706212e-6, 0.00000000019e-6, unit="N*A^-2") # magnetic permeability
h = Constant(6.62607015e-34, unit="J*Hz^-1") # Planck's constant
hbar = Constant(1.05457181e-34, unit="J*s") # reduced Planck's constant
kb = Constant(1.380649e-23, unit="J*K^-1") # Boltzmann's constant

# Mathematical constants
pi = 3.141592653589793
62 changes: 62 additions & 0 deletions qexpy/core/derived_value.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Defines the DerivedValue class"""

from functools import cached_property

from qexpy.core.experimental_value import ExperimentalValue
from qexpy.core.formula import _Formula
from qexpy.utils import Unit


class DerivedValue(ExperimentalValue):
"""A calculated value with a propagated uncertainty

When :py:class:`~qexpy.core.ExperimentalValue` objects are used in calculations, the results
are wrapped in instances of this class. Internally, the ``DerivedValue`` stores the expression
tree for how it was calculated, the leaf nodes of which are the constants and measurements.
This allows the value and uncertainty to be calculated more flexibly. Different error methods
can be chosen to propagate the uncertainty.

Attributes
----------

value
error
relative_error
name
unit

"""

def __init__(self, formula: _Formula):
self._formula = formula
super().__init__("", None)

def __copy__(self):
obj = object.__new__(DerivedValue)
obj._formula = self._formula
obj._name = self._name
return obj

@property
def value(self) -> float:
return self._value

@property
def error(self) -> float:
return self._error

@property
def unit(self) -> Unit:
return self._unit

@cached_property
def _value(self):
return self._formula.value

@cached_property
def _error(self):
return self._formula.error

@cached_property
def _unit(self): # pylint: disable=method-hidden
return self._formula.unit
Loading
Loading