Math Homework #1

Objective

Using 1 Dimensional Vector math, given a function definiton 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 documentatios 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
45from pytest import approx
46
47from modelviewprojection.mathutils import InvertibleFunction, compose, inverse
48from modelviewprojection.mathutils1d import Vector1D, translate, uniform_scale
49

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

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

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

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

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

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

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

assignments/demo02/vec1.py
71-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 contant \(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
83fn: InvertibleFunction[Vector1D] = translate(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
92assert fn(Vector1D(0)) == Vector1D(2.0)
93assert fn(Vector1D(1)) == Vector1D(3.0)
94assert fn(Vector1D(5)) == 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
100assert inverse(fn)(Vector1D(2)) == Vector1D(0.0)
101assert inverse(fn)(Vector1D(3)) == Vector1D(1.0)
102assert inverse(fn)(Vector1D(7)) == Vector1D(5.0)

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 invertable BlackBox.

Definition (White Box)

src/modelviewprojection/mathutils1d.py
153def translate(translate_amount: Vector1D) -> InvertibleFunction[Vector1D]:
src/modelviewprojection/mathutils1d.py
168    def f(vector: Vector1D) -> Vector1D:
169        return vector + translate_amount
170
171    def f_inv(vector: Vector1D) -> Vector1D:
172        return vector - translate_amount
173
174    return InvertibleFunction[Vector1D](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
107m: float = 5.0
108b: float = 2.0
109fn: InvertibleFunction[Vector1D] = compose(
110    translate(Vector1D(b)), uniform_scale(m)
111)
112print(fn(Vector1D(0.0)))
113print(fn(Vector1D(1.0)))
114
115assert fn(Vector1D(0.0)) == Vector1D(2.0)
116assert fn(Vector1D(1.0)) == 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
131celsius_to_kelvin: InvertibleFunction[Vector1D] = translate(Vector1D(273.15))
132assert celsius_to_kelvin(Vector1D(0.0)) == Vector1D(approx(273.15))
133assert celsius_to_kelvin(Vector1D(100.0)) == Vector1D(approx(373.15))
134
135
136fahrenheit_to_celsius: InvertibleFunction[Vector1D] = compose(
137    uniform_scale(5.0 / 9.0), translate(Vector1D(-32.0))
138)
139assert fahrenheit_to_celsius(Vector1D(32.0)) == Vector1D(approx(0.0))
140assert fahrenheit_to_celsius(Vector1D(212.0)) == Vector1D(approx(100.0))
141
142
143kelvin_to_celsius: InvertibleFunction[Vector1D] = inverse(celsius_to_kelvin)
144assert kelvin_to_celsius(Vector1D(273.15)) == Vector1D(approx(0.0))
145assert kelvin_to_celsius(Vector1D(373.15)) == Vector1D(approx(100.0))

Functions to implement

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

assignments/demo02/vec1.py
158fahrenheit_to_kelvin: InvertibleFunction[Vector1D] = translate(Vector1D(0.0))
159assert fahrenheit_to_kelvin(Vector1D(32.0)) == Vector1D(approx(273.15))
160assert fahrenheit_to_kelvin(Vector1D(212.0)) == Vector1D(approx(373.15))
161
162celsius_to_fahrenheit: InvertibleFunction[Vector1D] = translate(Vector1D(0.0))
163assert celsius_to_fahrenheit(Vector1D(0.0)) == Vector1D(approx(32.0))
164assert celsius_to_fahrenheit(Vector1D(100.0)) == Vector1D(approx(212.0))
165
166
167kelvin_to_fahrenheit: InvertibleFunction[Vector1D] = translate(Vector1D(0.0))
168assert kelvin_to_fahrenheit(Vector1D(273.15)) == Vector1D(approx(32.0))
169assert kelvin_to_fahrenheit(Vector1D(373.15)) == Vector1D(approx(212.0))