Math Homework #1

Objective

Using 1 Dimensional Vector math, given a function definition in Python for celsius to kelvin and for fahrenheit to celsius, implement in Python

  • fahrenheit to kelvin

  • celsius to fahrenheit

  • kelvin to fahrenheit

This book provides a math library in Python. We import them here.

We’ll use pytest’s approx method to test if two floating point values are close enough to the same value. We import a type modelviewprojection.mathutils.InvertibleFunction, modelviewprojection.mathutils.compose(), modelviewprojection.mathutils.inverse()

Important note. The links in the previous paragraphs are links to API documentation. API documentation is like a guidebook that explains how to use a library. It tells you what functions, classes, and modules are available, what inputs they require, what they return, and examples of how to use them correctly. Instead of guessing or relying on scattered internet posts, the API gives you the most accurate and up to date information straight from the source. For your assignments (and your future work), clicking on the API docs will save you time, help you avoid mistakes and show you features that you might not realize exists.

assignments/demo02/vec1.py
45import warnings
46
47import modelviewprojection.mathutils1d as mu1d
48
49# turn warnings into exceptions
50warnings.filterwarnings("error", category=RuntimeWarning)
51

We can do addition on modelviewprojection.mathutils1d.Vector1D using “+”, modelviewprojection.mathutils1d.Vector1D.__add__()

assignments/demo02/vec1.py
58mu1d.Vector1D(x=1.0) + mu1d.Vector1D(x=3.0)

We can do subtraction on modelviewprojection.mathutils1d.Vector1D using “-”, modelviewprojection.mathutils1d.Vector1D.__sub__()

assignments/demo02/vec1.py
63mu1d.Vector1D(x=5.0) - mu1d.Vector1D(x=1.0)

We can do multiply a scalar by modelviewprojection.mathutils1d.Vector1D using “*”, modelviewprojection.mathutils1d.Vector1D.__mul__()

assignments/demo02/vec1.py
684.0 * mu1d.Vector1D(x=2.0)

We can do negate a modelviewprojection.mathutils1d.Vector1D using “-”, modelviewprojection.mathutils1d.Vector1D.__neg__()

assignments/demo02/vec1.py
73-mu1d.Vector1D(x=2.0)

Translate Implementation

Next we have a very import function, modelviewprojection.mathutils.translate(). Read the API documentation in the link, it’s a very important function.

Translate is a function which partially binds a constant Vector1D to one of the arguments of Vector1D.__add__(), thus creating a new function of one argument.

In high school math, you’d learn about classes of functions, such as affine functions that follow the pattern \(f(x) = m \times x + b\). You were told that \(m\) and \(b\) were constant.

You could recognize \(f(x) = 2 \times x + 3\) as being an affine function where \(m=2\) and \(b=3\). You could recognize \(f(x) = 5 \times x + 0\) as being an affine function where \(m=5\) and \(b=0\). You could recognize \(f(x) = x \times x\) as not being affine, although it’s implicit that \(b=0\), there is no constant times \(x\) But could you generate a new function for a given \(m\) and given \(b\)?

Perhaps you could use notation such as \(f_{m=2,b=3}(x)\) to be a function \(2 \times x + 3\), or \(f(x; m=2,b=3)\) to be a function \(2 \times x + 3\).

We will use the folliwng notation for translate, \(T_{b}(x) = x + b\), where if we specify a constant \(b\), it will be notated as \(T_{b=3}\) equals an expression \(x + 3\).

Here, we call the translate function to create a new function, named “fn”, notated \(T_{b=2}\), which is a function of a variable \(x\), and a constant 2, \(T_{b=2}(x) = x + 2\).

Usage (Black Box)

assignments/demo02/vec1.py
85fn: mu1d.InvertibleFunction = mu1d.translate(mu1d.Vector1D(2.0))

Now that we’ve generated a function using translate, we can now apply it, 0, 1, or many times.

assignments/demo02/vec1.py
94assert fn(mu1d.Vector1D(0)) == mu1d.Vector1D(2.0)
95assert fn(mu1d.Vector1D(1)) == mu1d.Vector1D(3.0)
96assert fn(mu1d.Vector1D(5)) == mu1d.Vector1D(7.0)

Inverting such a function is done by negating \(b\), so (\({T_{b=2}}^{-1} \circ {T_{b=2}}) (x) = ({T_{b=-2}} \circ {T_{b=2}}) (x) = x\)

To get the inverse in Python, we can call the modelviewprojection.mathutils.inverse() function on our function, without having to worry about how it’s implemented.

assignments/demo02/vec1.py
102assert mu1d.inverse(fn)(mu1d.Vector1D(2)) == mu1d.Vector1D(0.0)
103assert mu1d.inverse(fn)(mu1d.Vector1D(3)) == mu1d.Vector1D(1.0)
104assert mu1d.inverse(fn)(mu1d.Vector1D(7)) == mu1d.Vector1D(5.0)

What’s nice about that is we can look at the implementation of modelviewprojection.mathutils.translate() once, understand how it works internally, and then forget those details and treat it as an invertible BlackBox.

Definition (White Box)

src/modelviewprojection/mathutils.py
407def translate(b: Vector) -> InvertibleFunction:
408    def f(vector: Vector) -> Vector:
409        return vector + b
410
411    def f_inv(vector: Vector) -> Vector:
412        return vector - b
413
414    return InvertibleFunction(f, f_inv)

Function Composition

Similarly to how we defined \(T_{b}(x) = x + b\) for adding a constant \(b\), we can define a “scaling” function \(S_{m}(x) = m \times x\). We can use function composition of a partially bound \(S\) and partially bound \(T\) to generate new instances of \(f(x) = m \times x + b\)

\(f(x) = {m}{x} + b = T_{b=2} \circ S_{m=5}\)

assignments/demo02/vec1.py
109m: float = 5.0
110b: float = 2.0
111fn: mu1d.InvertibleFunction = mu1d.compose(
112    [mu1d.translate(mu1d.Vector1D(b)), mu1d.uniform_scale(m)]
113)
114print(fn(mu1d.Vector1D(0.0)))
115print(fn(mu1d.Vector1D(1.0)))
116
117assert fn(mu1d.Vector1D(0.0)) == mu1d.Vector1D(2.0)
118assert fn(mu1d.Vector1D(1.0)) == mu1d.Vector1D(7.0)

Assignment

Provided functions

In Temperature Conversion you were provided definitions of functions to convert between fahrenheit, celsius, and kelvin. Provided to you are Python implementations of three of those functions

assignments/demo02/vec1.py
133celsius_to_kelvin: mu1d.InvertibleFunction = mu1d.translate(
134    mu1d.Vector1D(273.15)
135)
136assert celsius_to_kelvin(mu1d.Vector1D(0.0)).isclose(mu1d.Vector1D(273.15))
137
138assert celsius_to_kelvin(mu1d.Vector1D(100.0)).isclose(mu1d.Vector1D(373.15))
139
140
141fahrenheit_to_celsius: mu1d.InvertibleFunction = mu1d.compose(
142    [mu1d.uniform_scale(5.0 / 9.0), mu1d.translate(mu1d.Vector1D(-32.0))]
143)
144assert fahrenheit_to_celsius(mu1d.Vector1D(32.0)).isclose(mu1d.Vector1D(0.0))
145
146assert fahrenheit_to_celsius(mu1d.Vector1D(212.0)).isclose(mu1d.Vector1D(100.0))
147
148
149kelvin_to_celsius: mu1d.InvertibleFunction = mu1d.inverse(celsius_to_kelvin)
150assert kelvin_to_celsius(mu1d.Vector1D(273.15)).isclose(mu1d.Vector1D(0.0))
151assert kelvin_to_celsius(mu1d.Vector1D(373.15)).isclose(mu1d.Vector1D(100.0))

Functions to implement

Your task is to modify the three functions below so that the asserts all pass

assignments/demo02/vec1.py
164fahrenheit_to_kelvin: mu1d.InvertibleFunction = mu1d.translate(
165    mu1d.Vector1D(0.0)
166)
167assert fahrenheit_to_kelvin(mu1d.Vector1D(32.0)).isclose(mu1d.Vector1D(273.15))
168assert fahrenheit_to_kelvin(mu1d.Vector1D(212.0)).isclose(mu1d.Vector1D(373.15))
169
170celsius_to_fahrenheit: mu1d.InvertibleFunction = mu1d.translate(
171    mu1d.Vector1D(0.0)
172)
173assert celsius_to_fahrenheit(mu1d.Vector1D(0.0)).isclose(mu1d.Vector1D(32.0))
174
175assert celsius_to_fahrenheit(mu1d.Vector1D(100.0)).isclose(mu1d.Vector1D(212.0))
176
177
178kelvin_to_fahrenheit: mu1d.InvertibleFunction = mu1d.translate(
179    mu1d.Vector1D(0.0)
180)
181assert kelvin_to_fahrenheit(mu1d.Vector1D(273.15)).isclose(mu1d.Vector1D(32.0))
182assert kelvin_to_fahrenheit(mu1d.Vector1D(373.15)).isclose(mu1d.Vector1D(212.0))