MetroloPy¶

tools for dealing with physical quantities: uncertainty propagation and unit conversion


Table of Contents¶

  • getting started
  • tutorial
    • expressing physical quantities
    • propagating uncertainty
    • Monte-Carlo uncertainty propagation
    • expanded uncertainty
    • an uncertainty budget
    • units and mathematical operations
    • built-in and user defined units
    • physical constants
    • curve fitting
    • applying numerical functions to gummys
    • functions with multiple inputs and outputs
    • creating correlated gummys
    • complex numbers with uncertainty
    • formatting

getting started¶

MetroloPy is a pure python package and requires Python 3.6 or later and the SciPy stack (NumPy, SciPy and Pandas). It looks best in a Jupyter Notebook like the one used to create this tutorial.

Install MetroloPy with pip:

$ pip install metrolopy

or with conda from conda-forge:

$ conda install -c conda-forge metrolopy

See the MetroloPy home page for the full documentation.


tutorial¶

expressing physical quantities¶

A gummy object represents a physical quantity with an uncertainty and (or) a unit:

In [1]:
import metrolopy as uc
In [2]:
distance = uc.gummy(342.195,u=0.023,unit='m')
time = uc.gummy(1.00235,u=0.00044,unit='s')

distance/time
Out[2]:
341.39(15) m/s

The uncertainty in the velocity is automatically calculated using first order uncertainty propagation. By default, concise notation is used when a gummy is printed, where the numbers in parentheses represent the uncertainty in the last digits of the value. There are many other ways that the uncertainty can be notated, here is another example:

In [3]:
side_1 = uc.gummy(1.23456,u=0.00234,unit='cm',utype='B')
side_1
Out[3]:
1.2346(23) cm
In [4]:
side_1.style='+-'
side_1
Out[4]:
(1.2346 ± 0.0023) cm

We can express the uncertainty in different units:

In [5]:
side_1.uunit = 'um' # the uunit property sets the units on the uncertainty
side_1
Out[5]:
1.2346 cm ± 23 μm

or express the uncertainty as a relative uncertainty:

In [6]:
side_1.uunit = '%'
side_1
Out[6]:
1.2346 cm ± 0.19%
In [7]:
side_1.uunit = 'mm/m'
side_1
Out[7]:
1.2346 cm ± 1.9 mm/m

The gummy side_1 was defined with a utype of 'B'. This is just an arbitrary string that we can attach to gummys to classify the uncertainty type (how you want classify uncertainties or the labels that you use is up to you).

Gummy instances can also track degrees of freedom and propagate them using the Welch-Satterthwaite approximation.

Say we measured side 2 six times. We can create a gummy instance and use the dof keywork (e.g. uc.gummy(3.059,u=0.020,dof=5,unit='mm') but here we will use the MetroloPy mean function:

In [8]:
measurements = [3.011,3.125,2.995,3.055,3.067,3.101]
side_2 = uc.mean(measurements,unit='mm',utype='A')
side_2
Out[8]:
3.059(20) mm with ν = 5

Up to this point we have been dealing with standard uncertainties (sometimes referred to roughly as 1-sigma uncertaities). But often uncertainties are expressed as expanded uncertainties. For example, if we want the uncerainty interval at a 95% level of confidence we can set the gummy.p property to 0.95 then the value and expanded uncertainty, calculated using a Student's t distribution with 5 degrees of freedom is:

In [9]:
side_2.p = 0.95
side_2.show_k = True # lets print the k value as well
side_2
Out[9]:
3.059(53) mm with k = 2.6, a 95% level of confidence and ν = 5

We can also create a gummy based on an expanded uncertainty and/or relative uncertainty by using k, p and uunit keywords in the initializer:

In [10]:
g = uc.gummy(3.4457995,u=1.24,dof=7,p=0.95,unit='kg',uunit='ppm')
g
Out[10]:
3.445 799 5 kg ± 1.2 ppm with a 95% level of confidence and ν = 7

Even when .p or .k are set .u property gives the standard uncertainty in kg:

In [11]:
g.u # this is equal to g.x*g.U*1e-6/g.k
Out[11]:
1.8069642046180897e-06

propagating uncertainty¶

Many numpy functions will work with gummys:

In [12]:
import numpy as np
In [13]:
angle = np.arctan(side_2/side_1)
angle
Out[13]:
0.2429(16) with ν = 5.8
In [14]:
angle.convert('degree')
Out[14]:
13.916(93)° with ν = 5.8

Or another example:

The gummys keep track of any correlations between values for example if we define:

In [15]:
area = side_1*side_2
area
Out[15]:
0.3777(26) cm2 with ν = 5.8

then there is a correlation between the area and the angle:

In [16]:
area.correlation(angle)
Out[16]:
0.851780140482482

which can also be expressed as a covariance:

In [17]:
area.covariance(angle)
Out[17]:
3.6403257452426167e-06
In [18]:
uc.gummy.correlation_matrix([angle,area])
Out[18]:
array([[1.        , 0.85178014],
       [0.85178014, 1.        ]])
In [19]:
uc.gummy.covariance_matrix([angle,area])
Out[19]:
array([[2.64186856e-06, 3.64032575e-06],
       [3.64032575e-06, 6.91375948e-06]])

MetroloPy takes into account these correlations when propagating uncertainties.

Up to now we have assumed that the uncertainties are best modeled with a Normal probability distribution, but we can define a gummy with, say, a uniform probability distribution:

In [20]:
force = uc.gummy(uc.UniformDist(center=0.934566,half_width=0.0096),unit='N',utype='B')
force

# We used the `UniformDist` class above but gummy's can also take any of the continuous or
# discrete distribution defined in the scipy.stats package, e.g. we could have equivalently
# force as:

# from scipy.stats import uniform
# force = uc.gummy(uniform(loc = 0.924966, scale = 0.192))
Out[20]:
0.9346(55) N

And now we can calculate:

In [21]:
pressure = force/area
pressure
Out[21]:
2.475(23) N/cm2
In [22]:
pressure.unit = 'kPa'
pressure
Out[22]:
24.75(23) kPa

Note that by default, the degrees of freedom are only displayed for gummys with $ \nu < 10 $. See also the .show_dof property.

In [23]:
pressure.dof
Out[23]:
17.366358852939854

We can get the uncertainty contribution from specific sources. Here is the combined standard uncertainty from the type B uncertainty sources:

In [24]:
pressure.ufrom('B')
Out[24]:
0.1540769887232122

or we can seperate the type A and type B uncertainties when the gummy is printed:

In [25]:
pressure.ubreakdown = ['A','B']
pressure
Out[25]:
24.75(17)(15) kPa

ufrom and ubreakdown can also take gummys or lists of gummys as parameters. There is also a .doffrom method.

Monte-Carlo uncertainty propagation¶

Above, gummy just took the standard deviation of the force probability distribution and then forgot about the shape and propagated the uncertainties assuming everything was Gaussian. However we can also do a Monte-Carlo simulation using the actual probability distributions:

In [26]:
%matplotlib inline
uc.gummy.simulate([area,force,pressure])  # generate Monte-Carlo data, default is 1e6 samples for each gummy

area.name = 'Area'  #give the variables names for the plot
area.p = 0.95  # put the reference lines in the histograms at a 95% confidence interval
area.hist()  # create the histogram for a (we could have used a.hist(p=0.95) instead of a.p = 0.95 above)

force.name = 'Force'
force.p = 0.95
force.cimethod = 'symmetric'  # The default method for calculating confidence intervals is 
                          # cimethod = 'shortest', the shortest interval with the desired 
                          # confidence level.  But this doesn't work well with a uniform distribution.
force.hist()

pressure.name = 'Pressure'
pressure.ubreakdown = None # ubreakdown was set previously, clear it now
pressure.p = 0.95
pressure.name = 'P'
pressure.hist();
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

The Monte-Carlo uncertainty above is pretty close (though not exactly the same as) the result from the first order uncertainty propagation:

In [27]:
pressure
Out[27]:
P = 24.75(48) kPa with a 95% level of confidence

There are a number of gummy properties summarize that the Monte-Carlo results:

In [28]:
pressure.usim  # The standard deviation of the simulated data
Out[28]:
0.26368349566226656
In [29]:
pressure.xsim  # The mean of the simulated data
Out[29]:
24.74984352923561
In [30]:
# The confidence interval based on the level of confidence that
# we set with the pressure.p property:
pressure.cisim
Out[30]:
[24.244978661695534, 25.260973116993707]

By default the the confidence interval is calculated as the shortest inverval corresponding to the desired level of confidence. However by setting the pressure.cimethod from the string "shortest" to "symmetric", the interval can be changed to one that leaves and equal fraction of simulated values below and above the interval (i.e. the interval between the (1-p)/2 and (1+p)/2 quantiles).

In [31]:
pressure.Usim  # this is [pressure.cisim[1] - pressure.xsim,pressure.xsim - pressure.cisim[0]]
Out[31]:
(0.5111295877580986, 0.5048648675400749)
In [32]:
# the standard deviation of the Monte-Carlo data if we hold all but the
# type B variables constant.
pressure.ufromsim('B')  
Out[32]:
0.15424864727396206

The full Monte-Carlo data can be accessed with the pressure.simdata and pressure.simsorted properties. Note that we could generate Monte-Carlo data for pressure only by calling pressure.sim(). However when ever the .sim or gummy.simulate methods are called, all previous Monte-Carlo data for all gummys is erased, so use the gummy.simulate([list of guumys]) method when you want data from more than one gummy available at one time.

an uncertainty budget¶

Let's summarize the uncertainty budget in a table:

In [33]:
side_1.name = 'Side 1' # define names for the table listing
side_2.name = 'Side 2'
pressure.budget([side_1,side_2,force])
Out[33]:
Component
Unit
Value
u
νeff
Type
|∂y/∂x|
s
Side 2
mm
3.059
0.020
5
A
8.1
0.54
Force
N
0.9346
0.0055
∞
B
26
0.42
Side 1
cm
1.2346
0.0023
∞
B
20
0.04
uc type A
kPa
0.17
5.0
A
0.54
uc type B
kPa
0.15
∞
B
0.46
P
kPa
24.75
0.23
17.4
Uc at k = 2.1
kPa
0.48

By default the table is ordered with the most significant contributors to the uncertainty at the top.

Maybe you prefer relative uncertainties in the table:

In [34]:
pressure.budget([side_1,side_2,force],uunit='%')
Out[34]:
Component
Unit
Value
u / %
νeff
Type
|∂y/∂x|
s
Side 2
mm
3.059
0.67
5
A
8.1
0.54
Force
N
0.9346
0.59
∞
B
26
0.42
Side 1
cm
1.2346
0.19
∞
B
20
0.04
uc type A
kPa
0.67
5.0
A
0.54
uc type B
kPa
0.62
∞
B
0.46
P
kPa
24.75
0.23
17.4
Uc at k = 2.1
kPa
1.9

units and mathematical operations¶

During mathematical operations, gummys will automatically convert units if necessary:

In [35]:
x = uc.gummy(1.2,unit='cm')
y = uc.gummy(2.1,unit='in')
x + y
Out[35]:
6.534 cm
In [36]:
x*y
Out[36]:
6.4008 cm2

Use the c flag to control the unit conversion. Put the .c property on the unit that you want converted:

In [37]:
x.c + y
Out[37]:
2.572 440 944 881 89 in
In [38]:
x.c*y
Out[38]:
0.992 125 984 251 969 in2

Nonlinear units (e.g. the decibel) or units with a offset origin may affect the way gummys behave under mathematical operations:

In [39]:
t1 = uc.gummy(27,unit='degC')
t2 = uc.gummy(19,unit='degC')
difference = t1 - t2  # t1 + t2 will raise an exception
difference
Out[39]:
8 °C

The difference looks like it has units degree Celsius, however gummy understands that it represents a temperature interval:

In [40]:
difference.unit.name
Out[40]:
'degree Celsius interval'
In [41]:
difference.convert('K')
Out[41]:
8 K

This is different from, say t1 defined above:

In [42]:
t1.unit.name
Out[42]:
'degree Celsius'
In [43]:
t1.convert('K')
Out[43]:
300.15 K

built-in and user defined units¶

A number of units are built into gummy. Use the search_units function to search them:

In [44]:
uc.search_units('length',fmt='html')
# uc.search_units() with no argument displays all loaded units
Out[44]:
  • angstrom, 1 Å = 1 × 10-10 m, alias: Å
  • astronomical unit, 1 au = 1.495 978 707 × 1011 m, aliases: au, ua
  • bohr, 1 a0 = 5.291 772 105 41(80) × 10-11, aliases: a0, a(0)
  • cable, 1 cb = 120 ftm, alias: cb
  • chain, 1 ch = 4 rd, alias: ch
  • fathom, 1 ftm = 2 yd, alias: ftm
  • foot, 1 ft = 12 in, alias: ft
  • furlong, 1 fur = 10 ch, alias: fur
  • hand, 1 hand = 4 in
  • inch (1 prefix), 1 in = 0.0254 m, alias: in
  • league, 1 lea = 3 mi, alias: lea
  • light hour, 1 light-hour = 1 c h, alias: light-hour
  • light minute, 1 light-minute = 1 c min, alias: light-minute
  • light second, 1 light-second = 1 c s, alias: light-second
  • light year, 1 ly = 1 c a, alias: ly
  • link, 1 li = 0.66 ft, alias: li
  • metre (20 prefixes), symbol: m, aliases: m, meter
  • mile, 1 mi = 1760 yd, alias: mi
  • nautical mile, 1 M = 1852 m, aliases: M, Nm, NM, nmi
  • parsec (3 prefixes), 1 pc = 206 264.806 247 096 363 2(97), alias: pc
  • pica, 1 P/ = 1/6 in, alias: P/
  • Planck length, 1 lP = 1.616 255(18) × 10-35, alias: l(P)
  • point, 1 p = 1/12 P/, alias: p
  • rack unit, 1 U = 1.75 in
  • reduced Compton wavelength, 1 ƛC = 1 ℏ/(me c), aliases: lambda(C), ƛ(C)
  • rod, 1 rd = 25 ft, alias: rd
  • siriometer, 1 Sm = 1 000 000 au
  • survey foot, 1 ft = 1200/3937 m
  • survey mile, 1 mi = 8 fur, alias: statute mile
  • thousandth of an inch, 1 mil = 0.001 in, aliases: mil, thou, thousandth
  • yard, 1 yd = 3 ft, alias: yd

You can also create custom units. Creating an instance of the Unit class automatically loads the unit definition into the unit library so it can be accessed by its string name. The Unit class has two required parameters, the unit name and the unit symbol.

In [45]:
uc.Unit('weird meter','wm',conversion=uc.Conversion('m',0.9144),add_symbol=True)
Out[45]:
wm

The optional conversion defines a unit conversion, in this case 1 wm = 0.9144 m. Because add_symbol was set to True when creating the unit, we can access the unit using its symbol 'wm' as well as by its name '[weird meter]' ( the brackets are required because there is a space in the name):

In [46]:
w = uc.gummy(1,unit='wm')
w
Out[46]:
1 wm
In [47]:
w.convert('m')
Out[47]:
0.9144 m

physical constants¶

MetroloPy includes some built-in constants, for example:

In [48]:
uc.constant('G')
Out[48]:
G = 6.674 30(15) × 10-11 m3/(kg s2)
In [49]:
uc.constant('G').description
Out[49]:
'Newtonian constant of gravitation, CODATA 2022'

Correlations between constants are correctly handled. For example the proton and electron masses are know to about 0.3 ppb, however the ratio of the masses has a smaller uncertainty.

In [50]:
me = uc.constant('m(e)')
me.uunit = 'ppb'
me
Out[50]:
me = 9.109 383 714 0 × 10-31 kg ± 0.30 ppb
In [51]:
mp = uc.constant('m(p)')
mp.uunit = 'ppb'
mp
Out[51]:
mp = 1.672 621 925 97 × 10-27 kg ± 0.30 ppb
In [52]:
r = mp/me
r.uunit = 'ppb'
r
Out[52]:
1 836.152 673 426 ± 0.017 ppb

Constants can be found with search_constants function:

In [53]:
uc.search_constants('electron')
Out[53]:
  • classical electron radius rr = 2.817 940 320 4(13) × 10-15 m, alias: r(e)
  • Compton wavelength λC = 2.426 310 235 35(73) × 10-12 m, aliases: lambda(C), λ(C)
  • electron g factor ge- = -2.002 319 304 360 92(36), alias: g(e-)
  • electron gyromagnetic ratio γe = 1.760 859 627 82(53) × 1011 1/(T s), aliases: gamma(e), γ(e)
  • electron magnetic moment μe = -9.284 764 691 6(28) × 10-24 J/T, aliases: mu(e), μ(e)
  • electron magnetic moment anomaly ae = 0.001 159 652 180 46(18), alias: a(e)
  • electron mass me = 9.109 383 714 0 × 10-31 kg ± 0.30 ppb, alias: m(e)

The constants can be referenced by name or alias. To list all constants, call the search_constants function with no arguments.

Users can also define constants to be added to the constant library:

In [54]:
uc.GummyConstant(1.75,unit='in',name='rack constant',symbol='rk',description='see also "rack unit"',add_symbol=True)
uc.constant('rk')
Out[54]:
rk = 1.75 in

curve fitting¶

The gummy package also includes several classes for fitting functions, for example:

In [55]:
y0 = uc.gummy(0.11,2.2,unit='m')
y1 = uc.gummy(2.12,1.2,unit='m')
y2 = uc.gummy(3.02,1.3,unit='m')
y3 = uc.gummy(5.55,2.3,unit='m')
y4 = uc.gummy(16.22,1.2,unit='m')

fit = uc.PolyFit([0,1,2,3,4],[y0,y1,y2,y3,y4],deg=2,xunit='s')
fit
Out[55]:
y = p1 + p2 x + p3 x2

best fit parameters:
p1 = 1.7(19) m
p2 = -1.9(20) m/s
p3 = 1.37(43) m/s2

We can plot the fit along with the standard uncertainty in the fit at any point:

In [56]:
fit.plot(cik=1,xlabel='time',ylabel='distance');
# cik is the coverage factor for the fit uncertainty band,
# alterately the cip parameter can be set to give a
# probability level for the band
No description has been provided for this image

There are several other fit classes:

In [57]:
x = np.linspace(0,10,100)
y = np.sin(x + 0.88) + 1.3 + np.random.normal(scale=0.23,size=100)

sinfit = uc.SinFit(x,y,xunit='t',yunit='m')
sinfit
Out[57]:
y = p1 sin(p2 x + p3) + p4

best fit parameters:
p1 = 0.957(30) m
p2 = 0.982(12) 1/t
p3 = 0.951(69)
p4 = 1.348(22) m
In [58]:
sinfit.plot(cip=0.95,clp=0.95);
# cip is the level of confidence for the fit uncertainty band in green

# clp (or clk) sets control limits in red, the region where the data points
# arelikely to lie
No description has been provided for this image

The predicted value is a gummy (and is correlated with the fit parameter gummys):

In [59]:
sinfit.ypred(5.2)
Out[59]:
1.134(43) m

User defined functions can be fit with the Fit class, either by passing the function to the constructor of Fit or by sub-classing Fit.

applying numerical functions to gummys¶

A number of mathematical functions that can be used with gummys are included with the gummy package:

In [60]:
a = uc.gummy(1.233,0.022)
uc.sin(a)
Out[60]:
0.9435(73)

Many NumPy functions can also be used with gummys:

In [61]:
import numpy as np
np.sin(a)
Out[61]:
0.9435(73)

The apply static method can also be used to apply a arbitrary numerical function to a gummy or several gummys. The apply method takes as its first parameter the function, which must take one or more float parameters and return a float or list or numpy.ndarray of floats. The second parameter is another function which gives the derivative of the first function. The remaining parameters are the gummy(s) or float(s) to which the function will be applied. We also demonstrate here that gummy can be used with the mpmath package to work with extended precision floating point types:

In [62]:
# the mpmath package can be installed with pip or conda
from mpmath import sin, cos, mpf, mp

mp.dps = 50
# set mpmath to a precision of 50 digits

uc.gummy.max_digits = 50
# by default gummy doesn't display more than 20 digits;
# this option does not affect the working precision, only 
# the display

a = uc.gummy(mpf('1/3'),mpf('2.2e-45'))

uc.gummy.apply(sin,cos,a)
Out[62]:
0.327 194 696 796 152 244 173 344 085 267 620 606 064 301 406 9(21)

The napply static method is similar to the apply method except that the derivatives are calculated numerically and do not need to be applied:

In [63]:
uc.gummy.napply(sin,a)
Out[63]:
0.327 194 696 796 152 244 173 344 085 267 620 606 064 301 406 9(21)
In [64]:
#set the max digits back to its original value
uc.gummy.max_digits = 20

functions with multiple inputs and outputs¶

The gummy.apply and gummy.napply methods work with functions that have a list or array output, returning a list of correlated gummys:

In [65]:
def f(x,y):
    return [x + y, x - y, x*y]

x = uc.gummy(3.664,u=0.012)
y = uc.gummy(2.229,u=0.022)

z = uc.gummy.napply(f,x,y)
z
Out[65]:
array([5.893(25), 1.435(25), 8.167(85)], dtype=object)
In [66]:
uc.gummy.correlation_matrix(z) # or uc.gummy.covariance_matrix(z)
Out[66]:
array([[ 1.        , -0.54140127,  0.98403087],
       [-0.54140127,  1.        , -0.68240985],
       [ 0.98403087, -0.68240985,  1.        ]])

creating correlated gummys¶

Use the create static method to generate a list of correlated gummys:

In [67]:
g = uc.gummy.create([1.1,2.2,3.3],u=[0.3,0.1,0.4],correlation_matrix=[[1,0,0.5],[0,1,0],[0.5,0,1]])
g
Out[67]:
[1.10(30), 2.20(10), 3.30(40)]
In [68]:
g[0].covariance(g[2])
Out[68]:
0.05999999999999998

The covariance_matrix key word can be used in the create method in place of u and correlation_matrix.

Note that mathematical operations between gummys will also create correlations between the input and output gummys.

complex numbers with uncertainty¶

The jummy class can be used to represent complex numbers with uncertainties:

In [69]:
c = uc.jummy(real=1.334,imag=0.2345,cov=[[0.0127,0.0055],[0.0055,0.0334]])
c
Out[69]:
1.33(11) + j 0.23(18)

Polar coordinates can also be used:

In [70]:
c.style='polar'
c
Out[70]:
1.35(12) · e j 0.17(13)
In [71]:
d = uc.jummy(r=2.445,phi=4.556,cov=[[0.022,0],[0,0.044]])
d
Out[71]:
-0.38(51) - j 2.42(17)

The .real, .imag, .r and .phi properties return gummy values:

In [72]:
e = c + d**2
e
Out[72]:
-4.4(10) + j 2.1(24)
In [73]:
e.real
Out[73]:
-4.4(10)
In [74]:
e.imag
Out[74]:
2.1(24)
In [75]:
e.r
Out[75]:
4.82(80)
In [76]:
e.phi
Out[76]:
2.70(52)

formatting¶

By default in a Jupyter notebook, gummy output is rendered using HTML:

In [77]:
acceleration = fit.p[2]
acceleration
Out[77]:
1.37(43) m/s2
In [78]:
f'the acceleration is {acceleration:1x}'
Out[78]:
'the acceleration is 1.4 m/s²'

But they can also be displayed using LaTeX:

In [79]:
acceleration.latex()
$\displaystyle 1.37(43)\:\mathrm{m}/\mathrm{s}^{2} $

The latex() method is like a print command but rendering the output with LaTeX. There is also a html() method. Output can also be printed using only ASCII or unicode characters:

In [80]:
acceleration.ascii()
1.37(43) m/s**2
In [81]:
acceleration.unicode()
1.37(43) m/s²

Use the tolatex() or tohtml() methods to get a string with the LaTeX or HTML encoding for the gummy.

In [82]:
acceleration.tolatex()
Out[82]:
'1.37(43)\\:\\mathrm{m}/\\mathrm{s}^{2}'
In [83]:
acceleration.tohtml()
Out[83]:
'<span>1.37(43)&nbsp;m/s<sup>2</sup></span>'

It is also possible to change the default output to LaTeX:

In [84]:
uc.gummy.printer = 'latex'
acceleration
Out[84]:

$ 1.37(43)\:\mathrm{m}/\mathrm{s}^{2} $

or unicode:

In [85]:
uc.gummy.printer = 'unicode'
acceleration
Out[85]:
1.37(43) m/s²

or output using only ASCII characters:

In [86]:
uc.gummy.printer = 'ascii'
acceleration
Out[86]:
1.37(43) m/s**2

or back to HTML:

In [87]:
uc.gummy.printer = 'html'
acceleration
Out[87]:
1.37(43) m/s2

Uncertainty budgets and fits can also be rendered using LaTeX:

In [88]:
budget = pressure.budget([side_1,side_2,force],uunit='%')
budget.latex()
$\displaystyle \begin{array}{ c c c c c c c c } \text{Component} & \text{Unit} & \text{Value} & u\,/\,\mathrm{\%} & \nu_{eff} & \text{Type} & \left\lvert\frac{\partial y}{\partial x} \right\rvert & s \\ \hline \text{Side 2} & \mathrm{mm} & 3.059 & 0.67 & 5 & \text{A} & 8.1 & 0.54 \\ \text{Force} & \mathrm{N} & 0.9346 & 0.59 & \infty & \text{B} & 26 & 0.42 \\ \text{Side 1} & \mathrm{cm} & 1.2346 & 0.19 & \infty & \text{B} & 20 & 0.04 \\ \hline u_{c} \text{ type A} & \mathrm{kPa} & & 0.67 & 5.0 & \text{A} & & 0.54 \\ u_{c} \text{ type B} & \mathrm{kPa} & & 0.62 & \infty & \text{B} & & 0.46 \\ \hline P & \mathrm{kPa} & 24.75 & 0.23 & 17.4 & \text{} & & \\ \hline U_c \text{ at } k = 2.1 & \mathrm{kPa} & & 1.9 & & \text{} & & \\ \end{array} $
In [89]:
budget.tolatex()
Out[89]:
'\\begin{array}{ c c c c c c c c }\n\\text{Component} & \\text{Unit} & \\text{Value} & u\\,/\\,\\mathrm{\\%} & \\nu_{eff} & \\text{Type} & \\left\\lvert\\frac{\\partial y}{\\partial x} \\right\\rvert & s \\\\\n\\hline\n\\text{Side 2} & \\mathrm{mm} & 3.059 & 0.67 & 5 & \\text{A} & 8.1 & 0.54 \\\\\n\\text{Force} & \\mathrm{N} & 0.9346 & 0.59 & \\infty & \\text{B} & 26 & 0.42 \\\\\n\\text{Side 1} & \\mathrm{cm} & 1.2346 & 0.19 & \\infty & \\text{B} & 20 & 0.04 \\\\\n\\hline\nu_{c} \\text{ type A} & \\mathrm{kPa} &  & 0.67 & 5.0 & \\text{A} &  & 0.54 \\\\\nu_{c} \\text{ type B} & \\mathrm{kPa} &  & 0.62 & \\infty & \\text{B} &  & 0.46 \\\\\n\\hline\nP & \\mathrm{kPa} & 24.75 & 0.23 & 17.4 & \\text{} &  &  \\\\\n\\hline\nU_c \\text{ at } k = 2.1 & \\mathrm{kPa} &  & 1.9 &  & \\text{} &  &  \\\\\n\\end{array}'

As can the ouput of the fit from the curve fitting section:

In [90]:
fit.latex()
$\displaystyle y = p_{1} + p_{2} x + p_{3} x^{2} \\ [10pt]\text{best fit parameters:} \\ \begin{align}p_{1} &= 1.7(19)\:\mathrm{m}\\p_{2} &= -1.9(20)\:\mathrm{m}/\mathrm{s}\\p_{3} &= 1.37(43)\:\mathrm{m}/\mathrm{s}^{2}\\\end{align}$
In [ ]:
 
© Copyright 2026, National Research Council Canada