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 pytest
46
47import modelviewprojection.mathutils as mu
48import modelviewprojection.mathutils1d as mu1d
49import warnings
50
51# turn warnings into exceptions
52warnings.filterwarnings("error", category=RuntimeWarning)
53

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

assignments/demo02/vec1.py
60mu1d.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
65mu1d.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
704.0 * mu1d.Vector1D(x=2.0)

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

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

Translate Implementation

Next we have a very import function, modelviewprojection.mathutils1d.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
87fn: mu.InvertibleFunction[mu1d.Vector1D] = mu.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
96assert fn(mu1d.Vector1D(0)) == mu1d.Vector1D(2.0)
97assert fn(mu1d.Vector1D(1)) == mu1d.Vector1D(3.0)
98assert 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.

What’s nice about that is we can look at the implementation of modelviewprojection.mathutils1d.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
246def translate(b: T) -> InvertibleFunction[T]:
247    def f(vector: T) -> T:
248        return vector + b
249
250    def f_inv(vector: T) -> T:
251        return vector - b
252
253    return InvertibleFunction[T](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
111m: float = 5.0
112b: float = 2.0
113fn: mu.InvertibleFunction[mu1d.Vector1D] = mu.compose(
114    [mu.translate(mu1d.Vector1D(b)), mu.uniform_scale(m)]
115)
116print(fn(mu1d.Vector1D(0.0)))
117print(fn(mu1d.Vector1D(1.0)))
118
119assert fn(mu1d.Vector1D(0.0)) == mu1d.Vector1D(2.0)
120assert 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
135celsius_to_kelvin: mu.InvertibleFunction[mu1d.Vector1D] = mu.translate(
136    mu1d.Vector1D(273.15)
137)
138assert celsius_to_kelvin(mu1d.Vector1D(0.0)) == mu1d.Vector1D(
139    pytest.approx(273.15)
140)
141assert celsius_to_kelvin(mu1d.Vector1D(100.0)) == mu1d.Vector1D(
142    pytest.approx(373.15)
143)
144
145
146fahrenheit_to_celsius: mu.InvertibleFunction[mu1d.Vector1D] = mu.compose(
147    [mu.uniform_scale(5.0 / 9.0), mu.translate(mu1d.Vector1D(-32.0))]
148)
149assert fahrenheit_to_celsius(mu1d.Vector1D(32.0)) == mu1d.Vector1D(
150    pytest.approx(0.0)
151)
152assert fahrenheit_to_celsius(mu1d.Vector1D(212.0)) == mu1d.Vector1D(
153    pytest.approx(100.0)
154)
155
156
157kelvin_to_celsius: mu.InvertibleFunction[mu1d.Vector1D] = mu.inverse(
158    celsius_to_kelvin
159)
160assert kelvin_to_celsius(mu1d.Vector1D(273.15)) == mu1d.Vector1D(
161    pytest.approx(0.0)
162)
163assert kelvin_to_celsius(mu1d.Vector1D(373.15)) == mu1d.Vector1D(
164    pytest.approx(100.0)
165)

Functions to implement

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

assignments/demo02/vec1.py
178fahrenheit_to_kelvin: mu.InvertibleFunction[mu1d.Vector1D] = mu.translate(
179    mu1d.Vector1D(0.0)
180)
181assert fahrenheit_to_kelvin(mu1d.Vector1D(32.0)) == mu1d.Vector1D(
182    pytest.approx(273.15)
183)
184assert fahrenheit_to_kelvin(mu1d.Vector1D(212.0)) == mu1d.Vector1D(
185    pytest.approx(373.15)
186)
187
188celsius_to_fahrenheit: mu.InvertibleFunction[mu1d.Vector1D] = mu.translate(
189    mu1d.Vector1D(0.0)
190)
191assert celsius_to_fahrenheit(mu1d.Vector1D(0.0)) == mu1d.Vector1D(
192    pytest.approx(32.0)
193)
194assert celsius_to_fahrenheit(mu1d.Vector1D(100.0)) == mu1d.Vector1D(
195    pytest.approx(212.0)
196)
197
198
199kelvin_to_fahrenheit: mu.InvertibleFunction[mu1d.Vector1D] = mu.translate(
200    mu1d.Vector1D(0.0)
201)
202assert kelvin_to_fahrenheit(mu1d.Vector1D(273.15)) == mu1d.Vector1D(
203    pytest.approx(32.0)
204)
205assert kelvin_to_fahrenheit(mu1d.Vector1D(373.15)) == mu1d.Vector1D(
206    pytest.approx(212.0)
207)