em dash; sentence case
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@ The family of exponential functions is used to model growth and decay. The famil
|
||||
## Exponential functions
|
||||
|
||||
|
||||
The family of exponential functions is defined by $f(x) = a^x, -\infty< x < \infty$ and $a > 0$. For $0 < a < 1$ these functions decay or decrease, for $a > 1$ the functions grow or increase, and if $a=1$ the function is constantly $1$.
|
||||
The family of exponential functions is defined by $f(x) = a^x, -\infty< x < \infty$ and $a > 0$. For $0 < a < 1$ these functions decay or decrease, for $a > 1$ these functions grow or increase, and if $a=1$ the function is constantly $1$.
|
||||
|
||||
|
||||
For a given $a$, defining $a^n$ for positive integers is straightforward, as it means multiplying $n$ copies of $a.$ From this, for *integer powers*, the key properties of exponents: $a^x \cdot a^y = a^{x+y}$, and $(a^x)^y = a^{x \cdot y}$ are immediate consequences. For example with $x=3$ and $y=2$:
|
||||
@@ -114,7 +114,7 @@ t2, t8 = 72/2, 72/8
|
||||
exp(r2*t2), exp(r8*t8)
|
||||
```
|
||||
|
||||
So fairly close - after $72/r$ years the amount is $2.05...$ times more than the initial amount.
|
||||
So fairly close---after $72/r$ years the amount is $2.05...$ times more than the initial amount.
|
||||
|
||||
|
||||
##### Example
|
||||
@@ -259,7 +259,7 @@ The inverse function will solve for $x$ in the equation $a^x = y$. The answer, f
|
||||
That is $a^{\log_a(x)} = x$ for $x > 0$ and $\log_a(a^x) = x$ for all $x$.
|
||||
|
||||
|
||||
To see how a logarithm is mathematically defined will have to wait, though the family of functions - one for each $a>0$ - are implemented in `Julia` through the function `log(a,x)`. There are special cases requiring just one argument: `log(x)` will compute the natural log, base $e$ - the inverse of $f(x) = e^x$; `log2(x)` will compute the log base $2$ - the inverse of $f(x) = 2^x$; and `log10(x)` will compute the log base $10$ - the inverse of $f(x)=10^x$. (Also `log1p` computes an accurate value of $\log(1 + p)$ when $p \approx 0$.)
|
||||
To see how a logarithm is mathematically defined will have to wait, though the family of functions---one for each $a>0$---are implemented in `Julia` through the function `log(a,x)`. There are special cases requiring just one argument: `log(x)` will compute the natural log, base $e$---the inverse of $f(x) = e^x$; `log2(x)` will compute the log base $2$---the inverse of $f(x) = 2^x$; and `log10(x)` will compute the log base $10$- the inverse of $f(x)=10^x$. (Also `log1p` computes an accurate value of $\log(1 + p)$ when $p \approx 0$.)
|
||||
|
||||
|
||||
To see this in an example, we plot for base $2$ the exponential function $f(x)=2^x$, its inverse, and the logarithm function with base $2$:
|
||||
@@ -398,7 +398,7 @@ $$
|
||||
##### Example
|
||||
|
||||
|
||||
Before the ubiquity of electronic calculating devices, the need to compute was still present. Ancient civilizations had abacuses to make addition easier. For multiplication and powers a [slide rule](https://en.wikipedia.org/wiki/Slide_rule) could be used. It is easy to represent addition physically with two straight pieces of wood - just represent a number with a distance and align the two pieces so that the distances are sequentially arranged. To multiply then was as easy: represent the logarithm of a number with a distance then add the logarithms. The sum of the logarithms is the logarithm of the *product* of the original two values. Converting back to a number answers the question. The conversion back and forth is done by simply labeling the wood using a logartithmic scale. The slide rule was [invented](http://tinyurl.com/qytxo3e) soon after Napier's initial publication on the logarithm in 1614.
|
||||
Before the ubiquity of electronic calculating devices, the need to compute was still present. Ancient civilizations had abacuses to make addition easier. For multiplication and powers a [slide rule](https://en.wikipedia.org/wiki/Slide_rule) could be used. It is easy to represent addition physically with two straight pieces of wood---just represent a number with a distance and align the two pieces so that the distances are sequentially arranged. To multiply then was as easy: represent the logarithm of a number with a distance then add the logarithms. The sum of the logarithms is the logarithm of the *product* of the original two values. Converting back to a number answers the question. The conversion back and forth is done by simply labeling the wood using a logartithmic scale. The slide rule was [invented](http://tinyurl.com/qytxo3e) soon after Napier's initial publication on the logarithm in 1614.
|
||||
|
||||
|
||||
##### Example
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 50 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 114 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 10 KiB |
@@ -240,13 +240,13 @@ f(x) = 5/9 * (x - 32)
|
||||
f(72) ## room temperature
|
||||
```
|
||||
|
||||
will create a function object with a value of `x` determined at a later time - the time the function is called. So the value of `x` defined when the function is created is not important here (as the value of `x` used by `f` is passed in as an argument).
|
||||
will create a function object with a value of `x` determined at a later time---the time the function is called. So the value of `x` defined when the function is created is not important here (as the value of `x` used by `f` is passed in as an argument).
|
||||
|
||||
|
||||
Within `Julia`, we make note of the distinction between a function object versus a function call. In the definition `f(x)=cos(x)`, the variable `f` refers to a function object, whereas the expression `f(pi)` is a function call, resulting in a value. This mirrors the math notation where an $f$ is used when properties of a function are being emphasized (such as $f \circ g$ for composition) and $f(x)$ is used when the values related to the function are being emphasized (such as saying "the plot of the equation $y=f(x)$).
|
||||
|
||||
|
||||
Distinguishing these related but different concepts --- expressions, equations, values from function calls, and function objects --- is important when modeling mathematics on the computer.
|
||||
Distinguishing these related but different concepts---expressions, equations, values from function calls, and function objects---is important when modeling mathematics on the computer.
|
||||
|
||||
::: {#fig-kidney}
|
||||
|
||||
@@ -315,7 +315,7 @@ s(x) =
|
||||
\end{cases}
|
||||
$$
|
||||
|
||||
We learn to read this as: when $x$ is less than $0$, then the answer is $-1$. If $x$ is greater than $0$ the answer is $1.$ Often--but not in this example--there is an "otherwise" case to catch those values of $x$ that are not explicitly mentioned. As there is no such "otherwise" case here, we can see that this function has no definition when $x=0$. This function is often called the "sign" function and is also defined by $\lvert x\rvert/x$. (`Julia`'s `sign` function defines `sign(0)` to be `0`.)
|
||||
We learn to read this as: when $x$ is less than $0$, then the answer is $-1$. If $x$ is greater than $0$ the answer is $1.$ Often---but not in this example---there is an "otherwise" case to catch those values of $x$ that are not explicitly mentioned. As there is no such "otherwise" case here, we can see that this function has no definition when $x=0$. This function is often called the "sign" function and is also defined by $\lvert x\rvert/x$. (`Julia`'s `sign` function defines `sign(0)` to be `0`.)
|
||||
|
||||
|
||||
How do we create conditional statements in `Julia`? Programming languages generally have "if-then-else" constructs to handle conditional evaluation. In `Julia`, the following code will handle the above condition:
|
||||
@@ -357,7 +357,7 @@ For example, here is one way to define an absolute value function:
|
||||
abs_val(x) = x >= 0 ? x : -x
|
||||
```
|
||||
|
||||
The condition is `x >= 0`--or is `x` non-negative? If so, the value `x` is used, otherwise `-x` is used.
|
||||
The condition is `x >= 0`---or is `x` non-negative? If so, the value `x` is used, otherwise `-x` is used.
|
||||
|
||||
|
||||
Here is a means to implement a function which takes the larger of `x` or `10`:
|
||||
@@ -672,7 +672,7 @@ During this call, values for `m` and `b` are found from how the function is call
|
||||
mxplusb(0; m=3, b=2)
|
||||
```
|
||||
|
||||
Keywords are used to mark the parameters whose values are to be changed from the default. Though one can use *positional arguments* for parameters--and there are good reasons to do so--using keyword arguments is a good practice if performance isn't paramount, as their usage is more explicit yet the defaults mean that a minimum amount of typing needs to be done.
|
||||
Keywords are used to mark the parameters whose values are to be changed from the default. Though one can use *positional arguments* for parameters---and there are good reasons to do so---using keyword arguments is a good practice if performance isn't paramount, as their usage is more explicit yet the defaults mean that a minimum amount of typing needs to be done.
|
||||
|
||||
|
||||
Keyword arguments are widely used with plotting commands, as there are numerous options to adjust, but typically only a handful adjusted per call. The `Plots` package whose commands we illustrate throughout these notes starting with the next section has this in its docs: `Plots.jl` follows two simple rules with data and attributes:
|
||||
@@ -726,7 +726,7 @@ The style isn't so different from using keyword arguments, save the extra step o
|
||||
v0, theta
|
||||
```
|
||||
|
||||
The *big* advantage of bundling parameters into a container is consistency – the function is always called in an identical manner regardless of the number of parameters (or variables).
|
||||
The *big* advantage of bundling parameters into a container is consistency-–-the function is always called in an identical manner regardless of the number of parameters (or variables).
|
||||
|
||||
|
||||
::: {.callout-note}
|
||||
@@ -749,13 +749,13 @@ Volume(r, h) = pi * r^2 * h # of a cylinder
|
||||
SurfaceArea(r, h) = pi * r * (r + sqrt(h^2 + r^2)) # of a right circular cone, including the base
|
||||
```
|
||||
|
||||
The right-hand sides may or may not be familiar, but it should be reasonable to believe that if push came to shove, the formulas could be looked up. However, the left-hand sides are subtly different - they have two arguments, not one. In `Julia` it is trivial to define functions with multiple arguments - we just did.
|
||||
The right-hand sides may or may not be familiar, but it should be reasonable to believe that if push came to shove, the formulas could be looked up. However, the left-hand sides are subtly different---they have two arguments, not one. In `Julia` it is trivial to define functions with multiple arguments---we just did.
|
||||
|
||||
|
||||
Earlier we saw the `log` function can use a second argument to express the base. This function is basically defined by `log(b,x)=log(x)/log(b)`. The `log(x)` value is the natural log, and this definition just uses the change-of-base formula for logarithms.
|
||||
|
||||
|
||||
But not so fast, on the left side is a function with two arguments and on the right side the functions have one argument--yet they share the same name. How does `Julia` know which to use? `Julia` uses the number, order, and *type* of the positional arguments passed to a function to determine which function definition to use. This is technically known as [multiple dispatch](http://en.wikipedia.org/wiki/Multiple_dispatch) or **polymorphism**. As a feature of the language, it can be used to greatly simplify the number of functions the user must learn. The basic idea is that many functions are "generic" in that they have methods which will work differently in different scenarios.
|
||||
But not so fast, on the left side is a function with two arguments and on the right side the functions have one argument---yet they share the same name. How does `Julia` know which to use? `Julia` uses the number, order, and *type* of the positional arguments passed to a function to determine which function definition to use. This is technically known as [multiple dispatch](http://en.wikipedia.org/wiki/Multiple_dispatch) or **polymorphism**. As a feature of the language, it can be used to greatly simplify the number of functions the user must learn. The basic idea is that many functions are "generic" in that they have methods which will work differently in different scenarios.
|
||||
|
||||
|
||||
:::{.callout-warning}
|
||||
@@ -785,7 +785,7 @@ twotox(x::Real) = (2.0)^x
|
||||
twotox(x::Complex) = (2.0 + 0.0im)^x
|
||||
```
|
||||
|
||||
This is for illustration purposes -- the latter two are actually already done through `Julia`'s *promotion* mechanism -- but we see that `twotox` will return a rational number when `x` is an integer unlike `Julia` which, when `x` is non-negative will return an integer and will otherwise will error or return a float (when `x` is a numeric literal, like `2^(-3)`).
|
||||
This is for illustration purposes---the latter two are actually already done through `Julia`'s *promotion* mechanism---but we see that `twotox` will return a rational number when `x` is an integer unlike `Julia` which, when `x` is non-negative will return an integer and will otherwise will error or return a float (when `x` is a numeric literal, like `2^(-3)`).
|
||||
|
||||
The key to reading the above is the type annotation acts like a gatekeeper allowing in only variables of that type or a subtype of that type.
|
||||
|
||||
@@ -811,7 +811,7 @@ Representing the area of a rectangle in terms of two variables is easy, as the f
|
||||
Area(w, h) = w * h
|
||||
```
|
||||
|
||||
But the other fact about this problem--that the perimeter is $20$--means that height depends on width. For this question, we can see that $P=2w + 2h$ so that--as a function--`height` depends on `w` as follows:
|
||||
But the other fact about this problem---that the perimeter is $20$---means that height depends on width. For this question, we can see that $P=2w + 2h$ so that---as a function---`height` depends on `w` as follows:
|
||||
|
||||
|
||||
```{julia}
|
||||
@@ -858,7 +858,7 @@ $$
|
||||
g(x) = f(x-c)
|
||||
$$
|
||||
|
||||
has an interpretation - the graph of $g$ will be the same as the graph of $f$ shifted to the right by $c$ units. That is $g$ is a transformation of $f$. From one perspective, the act of replacing $x$ with $x-c$ transforms a function into a new function. Mathematically, when we focus on transforming functions, the word [operator](http://en.wikipedia.org/wiki/Operator_%28mathematics%29) is sometimes used. This concept of transforming a function can be viewed as a certain type of function, in an abstract enough way. The relation would be to just pair off the functions $(f,g)$ where $g(x) = f(x-c)$.
|
||||
has an interpretation---the graph of $g$ will be the same as the graph of $f$ shifted to the right by $c$ units. That is $g$ is a transformation of $f$. From one perspective, the act of replacing $x$ with $x-c$ transforms a function into a new function. Mathematically, when we focus on transforming functions, the word [operator](http://en.wikipedia.org/wiki/Operator_%28mathematics%29) is sometimes used. This concept of transforming a function can be viewed as a certain type of function, in an abstract enough way. The relation would be to just pair off the functions $(f,g)$ where $g(x) = f(x-c)$.
|
||||
|
||||
|
||||
With `Julia` we can represent such operations. The simplest thing would be to do something like:
|
||||
@@ -881,7 +881,7 @@ function shift_right(f; c=0)
|
||||
end
|
||||
```
|
||||
|
||||
That takes some parsing. In the body of the `shift_right` is the definition of a function. But this function has no name–-it is *anonymous*. But what it does should be clear--it subtracts $c$ from $x$ and evaluates $f$ at this new value. Since the last expression creates a function, this function is returned by `shift_right`.
|
||||
That takes some parsing. In the body of the `shift_right` is the definition of a function. But this function has no name–-it is *anonymous*. But what it does should be clear---it subtracts $c$ from $x$ and evaluates $f$ at this new value. Since the last expression creates a function, this function is returned by `shift_right`.
|
||||
|
||||
|
||||
So we could have done something more complicated like:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# The Inverse of a Function
|
||||
# The inverse of a function
|
||||
|
||||
|
||||
{{< include ../_common_code.qmd >}}
|
||||
@@ -25,7 +25,7 @@ We may conceptualize such a relation in many ways:
|
||||
* through a description of what $f$ does;
|
||||
* or through a table of paired values, say.
|
||||
|
||||
For the moment, let's consider a function as a rule that takes in a value of $x$ and outputs a value $y$. If a rule is given defining the function, the computation of $y$ is straightforward. A different question is not so easy: for a given value $y$ what value--or *values*--of $x$ (if any) produce an output of $y$? That is, what $x$ value(s) satisfy $f(x)=y$?
|
||||
For the moment, let's consider a function as a rule that takes in a value of $x$ and outputs a value $y$. If a rule is given defining the function, the computation of $y$ is straightforward. A different question is not so easy: for a given value $y$ what value---or *values*---of $x$ (if any) produce an output of $y$? That is, what $x$ value(s) satisfy $f(x)=y$?
|
||||
|
||||
|
||||
*If* for each $y$ in some set of values there is just one $x$ value, then this operation associates to each value $y$ a single value $x$, so it too is a function. When that is the case we call this an *inverse* function.
|
||||
@@ -202,7 +202,7 @@ In the section on the [intermediate value theorem](../limits/intermediate_value_
|
||||
## Functions which are not always invertible
|
||||
|
||||
|
||||
Consider the function $f(x) = x^2$. The graph--a parabola--is clearly not *monotonic*. Hence no inverse function exists. Yet, we can solve equations $y=x^2$ quite easily: $y=\sqrt{x}$ *or* $y=-\sqrt{x}$. We know the square root undoes the squaring, but we need to be a little more careful to say the square root is the inverse of the squaring function.
|
||||
Consider the function $f(x) = x^2$. The graph---a parabola---is clearly not *monotonic*. Hence no inverse function exists. Yet, we can solve equations $y=x^2$ quite easily: $y=\sqrt{x}$ *or* $y=-\sqrt{x}$. We know the square root undoes the squaring, but we need to be a little more careful to say the square root is the inverse of the squaring function.
|
||||
|
||||
|
||||
The issue is there are generally *two* possible answers. To avoid this, we might choose to only take the *non-negative* answer. To make this all work as above, we restrict the domain of $f(x)$ and now consider the related function $f(x)=x^2, x \geq 0$. This is now a monotonic function, so will have an inverse function. This is clearly $f^{-1}(x) = \sqrt{x}$. (The $\sqrt{x}$ being defined as the principle square root or the unique *non-negative* answer to $u^2-x=0$.)
|
||||
@@ -287,7 +287,7 @@ plot(xs, ys; color=:blue, label="f",
|
||||
plot!(ys, xs; color=:red, label="f⁻¹") # the inverse
|
||||
```
|
||||
|
||||
By flipping around the $x$ and $y$ values in the `plot!` command, we produce the graph of the inverse function--when viewed as a function of $x$. We can see that the domain of the inverse function (in red) is clearly different from that of the function (in blue).
|
||||
By flipping around the $x$ and $y$ values in the `plot!` command, we produce the graph of the inverse function---when viewed as a function of $x$. We can see that the domain of the inverse function (in red) is clearly different from that of the function (in blue).
|
||||
|
||||
|
||||
The inverse function graph can be viewed as a symmetry of the graph of the function. Flipping the graph for $f(x)$ around the line $y=x$ will produce the graph of the inverse function: Here we see for the graph of $f(x) = x^{1/3}$ and its inverse function:
|
||||
|
||||
@@ -11,6 +11,8 @@ using CalculusWithJulia
|
||||
nothing
|
||||
```
|
||||
|
||||
|
||||
|
||||
The [`Julia`](http://www.julialang.org) programming language is well suited as a computer accompaniment while learning the concepts of calculus. The following overview covers the language-specific aspects of the pre-calculus part of the [Calculus with Julia](calculuswithjulia.github.io) notes.
|
||||
|
||||
|
||||
@@ -34,35 +36,29 @@ The [https://mybinder.org/](https://mybinder.org/) service in particular allows
|
||||
|
||||
[Google colab](https://colab.research.google.com/) offers a free service with more computing power than `binder`, though setup is a bit more fussy. To use `colab` along with these notes, you need to execute a command that downloads `Julia` and installs the `CalculusWithJulia` package and a plotting package. (Modify the `pkg"add ..."` command to add other desired packages; update the julia version as necessary):
|
||||
|
||||
```
|
||||
# Installation cell
|
||||
%%capture
|
||||
%%shell
|
||||
if ! command -v julia 3>&1 > /dev/null
|
||||
then
|
||||
wget -q 'https://julialang-s3.julialang.org/bin/linux/x64/1.10/julia-1.10.2-linux-x86_64.tar.gz' \
|
||||
-O /tmp/julia.tar.gz
|
||||
tar -x -f /tmp/julia.tar.gz -C /usr/local --strip-components 1
|
||||
rm /tmp/julia.tar.gz
|
||||
fi
|
||||
julia -e 'using Pkg; pkg"add IJulia CalculusWithJulia; precompile;"'
|
||||
julia -e 'using Pkg; Pkg.add(url="https://github.com/mth229/BinderPlots.jl")'
|
||||
echo 'Now change the runtime type'
|
||||
```
|
||||
|
||||
(The `BinderPlots` is a light-weight, barebones, plotting package that uses `PlotlyLight` to render graphics with commands mostly following those of the `Plots` package. Though suitable for most examples herein, the `Plots` package could instead be installed)
|
||||
> Go to google colab:
|
||||
|
||||
[https://colab.research.google.com/](https://colab.research.google.com/)
|
||||
|
||||
|
||||
After this executes (which can take quite some time, as in a few minutes) under the `Runtime` menu select `Change runtime type` and then select `Julia`.
|
||||
> Click on "Runtime" menu and then "Change Runtime Type"
|
||||
|
||||
After that, in a cell execute these commands to load the two installed packages:
|
||||
> Select Julia as the "Runtime Type" then save
|
||||
|
||||
|
||||
> Copy and paste then run this set of commands
|
||||
|
||||
```
|
||||
using Pkg
|
||||
Pkg.add("Plots")
|
||||
Pkg.add("CalculusWithJulia")
|
||||
using CalculusWithJulia
|
||||
using BinderPlots
|
||||
using Plots
|
||||
```
|
||||
|
||||
As mentioned, other packages can be chosen for installation.
|
||||
|
||||
This may take 2-3 minutes to load. The `plotly()` backend doesn't work out of the box. Use `gr()` to recover if that command is issued.
|
||||
|
||||
|
||||
|
||||
@@ -85,7 +81,7 @@ $ julia
|
||||
(_) | (_) (_) |
|
||||
_ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help.
|
||||
| | | | | | |/ _` | |
|
||||
| | |_| | | | (_| | | Version 1.11.1 (2024-10-16)
|
||||
| | |_| | | | (_| | | Version 1.11.6 (2025-07-09)
|
||||
_/ |\__'_|_|_|\__'_| | Official https://julialang.org/ release
|
||||
|__/ |
|
||||
|
||||
@@ -452,7 +448,7 @@ a = 4
|
||||
f(3) # now 2 * 4 + 3
|
||||
```
|
||||
|
||||
User-defined functions can have $0$, $1$ or more positional arguments:
|
||||
User-defined functions can have $0$, $1$, or more positional arguments:
|
||||
|
||||
|
||||
```{julia}
|
||||
@@ -573,7 +569,7 @@ With `Plots` loaded, we can plot a function by passing the function object by na
|
||||
|
||||
|
||||
```{julia}
|
||||
plot(sin, 0, 2pi) # plot a function - by name - over an interval [a,b]
|
||||
plot(sin, 0, 2pi)
|
||||
```
|
||||
|
||||
::: {.callout-note}
|
||||
@@ -629,7 +625,7 @@ ys = f.(xs)
|
||||
plot(f, a, b) # recipe for a function
|
||||
plot(xs, f) # alternate recipe
|
||||
plot(xs, ys) # plot coordinates as two vectors
|
||||
plot([(x,f(x)) for x in xs]) # plot a vector o points
|
||||
plot([(x,f(x)) for x in xs]) # plot a vector of points
|
||||
```
|
||||
|
||||
The choice should depend on convenience.
|
||||
@@ -637,7 +633,7 @@ The choice should depend on convenience.
|
||||
## Equations
|
||||
|
||||
|
||||
Notation for `Julia` and math is *similar* for functions - but not for equations. In math, an equation might look like:
|
||||
Notation for `Julia` and math is *similar* for functions---but not for equations. In math, an equation might look like:
|
||||
|
||||
|
||||
$$
|
||||
@@ -664,13 +660,15 @@ using SymPy
|
||||
(A macro rewrites values into other commands before they are interpreted. Macros are prefixed with the `@` sign. In this use, the "macro" `@syms` translates `x a b c` into a command involving `SymPy`s `symbols` function.)
|
||||
|
||||
|
||||
Symbolic expressions - unlike numeric expressions - are not immediately evaluated, though they may be simplified:
|
||||
Symbolic expressions---unlike numeric expressions---are not immediately evaluated, though they may be simplified:
|
||||
|
||||
|
||||
```{julia}
|
||||
p = a*x^2 + b*x + c
|
||||
```
|
||||
|
||||
The above command illustrates that the mathematical operations of `*`, `^`, and `+` work with symbolic objects. This is the case for most mathematical functions as well.
|
||||
|
||||
To substitute a value, we can use `Julia`'s `pair` notation (`variable=>value`):
|
||||
|
||||
|
||||
|
||||
@@ -1,623 +0,0 @@
|
||||
# Inequalities, Logical expressions
|
||||
|
||||
|
||||
{{< include ../_common_code.qmd >}}
|
||||
|
||||
In this section we use the following package:
|
||||
|
||||
|
||||
```{julia}
|
||||
using CalculusWithJulia # loads the `SpecialFunctions` package
|
||||
```
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
#| results: "hidden"
|
||||
using Plots
|
||||
plotly()
|
||||
nothing
|
||||
```
|
||||
|
||||
## Boolean values
|
||||
|
||||
|
||||
In mathematics it is common to test if an expression is true or false. For example, is the point $(1,2)$ inside the disc $x^2 + y^2 \leq 1$? We would check this by substituting $1$ for $x$ and $2$ for $y$, evaluating both sides of the inequality and then assessing if the relationship is true or false. In this case, we end up with a comparison of $5 \leq 1$, which we of course know is false.
|
||||
|
||||
|
||||
`Julia` provides numeric comparisons that allow this notation to be exactly mirrored:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
x, y = 1, 2
|
||||
x^2 + y^2 <= 1
|
||||
```
|
||||
|
||||
The response is `false`, as expected. `Julia` provides [Boolean](http://en.wikipedia.org/wiki/Boolean_data_type) values `true` and `false` for such questions. The same process is followed as was described mathematically.
|
||||
|
||||
|
||||
The set of numeric comparisons is nearly the same as the mathematical counterparts: `<`, `<=`, `==`, `>=`, `>`. The syntax for less than or equal can also be represented with the Unicode `≤` (generated by `\le[tab]`). Similarly, for greater than or equal, there is `\ge[tab]`.
|
||||
|
||||
|
||||
:::{.callout-warning}
|
||||
## Warning
|
||||
The use of `==` is necessary, as `=` is used for assignment and mutation.
|
||||
|
||||
:::
|
||||
|
||||
The `!` operator takes a boolean value and negates it. It uses prefix notation:
|
||||
|
||||
|
||||
```{julia}
|
||||
!true
|
||||
```
|
||||
|
||||
For convenience, `a != b` can be used in place of `!(a == b)`.
|
||||
|
||||
|
||||
## Algebra of inequalities
|
||||
|
||||
|
||||
To illustrate, let's see that the algebra of expressions works as expected.
|
||||
|
||||
|
||||
For example, if $a < b$ then for any $c$ it is also true that $a + c < b + c$.
|
||||
|
||||
|
||||
We can't "prove" this through examples, but we can investigate it by the choice of various values of $a$, $b$, and $c$. For example:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
a,b,c = 1,2,3
|
||||
a < b, a + c < b + c
|
||||
```
|
||||
|
||||
Or in reverse:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
a,b,c = 3,2,1
|
||||
a < b, a + c < b + c
|
||||
```
|
||||
|
||||
Trying other choices will show that the two answers are either both `false` or both `true`.
|
||||
|
||||
|
||||
:::{.callout-warning}
|
||||
## Warning
|
||||
Well, almost... When `Inf` or `NaN` are involved, this may not hold, for example `1 + Inf < 2 + Inf` is actually `false`. As would be `1 + (typemax(1)-1) < 2 + (typemax(1)-1)`.
|
||||
|
||||
:::
|
||||
|
||||
So adding or subtracting most any finite value from an inequality will preserve the inequality, just as it does for equations.
|
||||
|
||||
|
||||
What about multiplication?
|
||||
|
||||
|
||||
Consider the case $a < b$ and $c > 0$. Then $ca < cb$. Here we investigate using $3$ random values (which will be positive):
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
a,b,c = rand(3) # 3 random numbers in [0,1)
|
||||
a < b, c*a < c*b
|
||||
```
|
||||
|
||||
Whenever these two commands are run, the two logical values should be identical, even though the specific values of `a`, `b`, and `c` will vary.
|
||||
|
||||
|
||||
The restriction that $c > 0$ is needed. For example, if $c = -1$, then we have $a < b$ if and only if $-a > -b$. That is the inequality is "flipped."
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
a,b = rand(2)
|
||||
a < b, -a > -b
|
||||
```
|
||||
|
||||
Again, whenever this is run, the two logical values should be the same. The values $a$ and $-a$ are the same distance from $0$, but on opposite sides. Hence if $0 < a < b$, then $b$ is farther from $0$ than $a$, so $-b$ will be farther from $0$ than $-a$, which in this case says $-b < -a$, as expected.
|
||||
|
||||
|
||||
Finally, we have the case of division. The relation of $x$ and $1/x$ (for $x > 0$) is that the farther $x$ is from $0$, the closer $1/x$ is to $0$. So large values of $x$ make small values of $1/x$. This leads to this fact for $a,b > 0$: $a < b$ if and only if $1/a > 1/b$.
|
||||
|
||||
|
||||
We can check with random values again:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
a,b = rand(2)
|
||||
a < b, 1/a > 1/b
|
||||
```
|
||||
|
||||
In summary we investigated numerically that the following hold:
|
||||
|
||||
|
||||
* `a < b` if and only if `a + c < b + c` for all finite `a`, `b`, and `c`.
|
||||
* `a < b` if and only if `c*a < c*b` for all finite `a` and `b`, and finite, positive `c`.
|
||||
* `a < b` if and only if `-a > -b` for all finite `a` and `b`.
|
||||
* `a < b` if and only if `1/a > 1/b` for all finite, positive `a` and `b`.
|
||||
|
||||
|
||||
### Examples
|
||||
|
||||
|
||||
We now show some inequalities highlighted on this [Wikipedia](http://en.wikipedia.org/wiki/Inequality_%28mathematics%29) page.
|
||||
|
||||
|
||||
Numerically investigate the fact $e^x \geq 1 + x$ by showing it is true for three different values of $x$. We pick $x=-1$, $0$, and $1$:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
x = -1; exp(x) >= 1 + x
|
||||
x = 0; exp(x) >= 1 + x
|
||||
x = 1; exp(x) >= 1 + x
|
||||
```
|
||||
|
||||
Now, let's investigate that for any distinct real numbers, $a$ and $b$, that
|
||||
|
||||
|
||||
$$
|
||||
\frac{e^b - e^a}{b - a} > e^{(a+b)/2}
|
||||
$$
|
||||
|
||||
For this, we use `rand(2)` to generate two random numbers in $[0,1)$:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
a, b = rand(2)
|
||||
(exp(b) - exp(a)) / (b-a) > exp((a+b)/2)
|
||||
```
|
||||
|
||||
This should evaluate to `true` for any random choice of `a` and `b` returned by `rand(2)`.
|
||||
|
||||
|
||||
Finally, let's investigate the fact that the harmonic mean, $2/(1/a + 1/b)$ is less than or equal to the geometric mean, $\sqrt{ab}$, which is less than or equal to the quadratic mean, $\sqrt{a^2 + b^2}/\sqrt{2}$, using two randomly chosen values:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
let
|
||||
a, b = rand(2)
|
||||
h = 2 / (1/a + 1/b)
|
||||
g = (a * b) ^ (1 / 2)
|
||||
q = sqrt((a^2 + b^2) / 2)
|
||||
h <= g, g <= q
|
||||
end
|
||||
```
|
||||
|
||||
## Chaining, combining expressions: absolute values
|
||||
|
||||
|
||||
The absolute value notation can be defined through cases:
|
||||
|
||||
|
||||
$$
|
||||
\lvert x\rvert = \begin{cases}
|
||||
x & x \geq 0\\
|
||||
-x & \text{otherwise}.
|
||||
\end{cases}
|
||||
$$
|
||||
|
||||
The interpretation of $\lvert x\rvert$, as the distance on the number line of $x$ from $0$, means that many relationships are naturally expressed in terms of absolute values. For example, a simple shift: $\lvert x -c\rvert$ is related to the distance $x$ is from the number $c$. As common as they are, the concept can still be confusing when inequalities are involved.
|
||||
|
||||
|
||||
For example, the expression $\lvert x - 5\rvert < 7$ has solutions which are all values of $x$ within $7$ units of $5$. This would be the values $-2< x < 12$. If this isn't immediately intuited, then formally $\lvert x - 5\rvert <7$ is a compact representation of a chain of inequalities: $-7 < x-5 < 7$. (Which is really two combined inequalities: $-7 < x-5$ *and* $x-5 < 7$.) We can "add" $5$ to each side to get $-2 < x < 12$, using the fact that adding by a finite number does not change the inequality sign.
|
||||
|
||||
|
||||
Julia's precedence for logical expressions, allows such statements to mirror the mathematical notation:
|
||||
|
||||
|
||||
```{julia}
|
||||
x = 18
|
||||
abs(x - 5) < 7
|
||||
```
|
||||
|
||||
This is to be expected, but we could also have written:
|
||||
|
||||
|
||||
```{julia}
|
||||
-7 < x - 5 < 7
|
||||
```
|
||||
|
||||
Read aloud this would be "minus $7$ is less than $x$ minus $5$ **and** $x$ minus $5$ is less than $7$".
|
||||
|
||||
|
||||
The "and" equations can be combined as above with a natural notation. However, an equation like $\lvert x - 5\rvert > 7$ would emphasize an **or** and be "$x$ minus $5$ less than minus $7$ **or** $x$ minus $5$ greater than $7$". Expressing this requires some new notation.
|
||||
|
||||
|
||||
The *boolean shortcut operators* `&&` and `||` implement "and" and "or". (There are also *bitwise* boolean operators `&` and `|`, but we only describe the former.)
|
||||
|
||||
|
||||
Thus we could write $-7 < x-5 < 7$ as
|
||||
|
||||
|
||||
```{julia}
|
||||
(-7 < x - 5) && (x - 5 < 7)
|
||||
```
|
||||
|
||||
and could write $\lvert x-5\rvert > 7$ as
|
||||
|
||||
|
||||
```{julia}
|
||||
(x - 5 < -7) || (x - 5 > 7)
|
||||
```
|
||||
|
||||
(The first expression is false for $x=18$ and the second expression true, so the "or"ed result is `true` and the "and" result is `false`.)
|
||||
|
||||
|
||||
##### Example
|
||||
|
||||
|
||||
One of [DeMorgan's Laws](http://en.wikipedia.org/wiki/De_Morgan%27s_laws) states that "not (A and B)" is the same as "(not A) or (not B)". This is a kind of distributive law for "not", but note how the "and" changes to "or". We can verify this law systematically. For example, the following shows it true for $1$ of the $4$ possible cases for the pair `A`, `B` to take:
|
||||
|
||||
|
||||
```{julia}
|
||||
A,B = true, false ## also true, true; false, true; and false, false
|
||||
!(A && B) == !A || !B
|
||||
```
|
||||
|
||||
## Precedence
|
||||
|
||||
|
||||
The question of when parentheses are needed and when they are not is answered by the [precedence](https://docs.julialang.org/en/v1/manual/mathematical-operations/#Operator-Precedence-and-Associativity) rules implemented. Earlier, we wrote
|
||||
|
||||
|
||||
```{julia}
|
||||
(x - 5 < -7) || (x - 5 > 7)
|
||||
```
|
||||
|
||||
To represent $\lvert x-5\rvert > 7$. Were the parentheses necessary? Let's just check.
|
||||
|
||||
|
||||
```{julia}
|
||||
x - 5 < -7 || x - 5 > 7
|
||||
```
|
||||
|
||||
So no, they were not in this case.
|
||||
|
||||
|
||||
An operator (such as `<`, `>`, `||` above) has an associated associativity and precedence. The associativity is whether an expression like `a - b - c` is `(a-b) - c` or `a - (b-c)`. The former being left associative, the latter right. Of issue here is *precedence*, as in with two or more different operations, which happens first, second, $\dots$.
|
||||
|
||||
|
||||
The table in the manual on [operator precedence and associativity](https://docs.julialang.org/en/v1/manual/mathematical-operations/#Operator-Precedence-and-Associativity) shows that for these operations "control flow" (the `&&` above) is lower than "comparisons" (the `<`, `>`), which are lower than "Addition" (the `-` above). So the expression without parentheses would be equivalent to:
|
||||
|
||||
|
||||
```{julia}
|
||||
((x-5) < -7) || ((x-5) > 7)
|
||||
```
|
||||
|
||||
(This is different than the precedence of the bitwise boolean operators, which have `&` with "Multiplication" and `|` with "Addition", so `x-5 < 7 | x - 5 > 7` would need parentheses.)
|
||||
|
||||
|
||||
A thorough understanding of the precedence rules can help eliminate unnecessary parentheses, but in most cases it is easier just to put them in.
|
||||
|
||||
|
||||
## Arithmetic with
|
||||
|
||||
|
||||
For convenience, basic arithmetic can be performed with Boolean values, `false` becomes $0$ and true $1$. For example, both these expressions make sense:
|
||||
|
||||
|
||||
```{julia}
|
||||
true + true + false, false * 1000
|
||||
```
|
||||
|
||||
The first example shows a common means used to count the number of `true` values in a collection of Boolean values - just add them.
|
||||
|
||||
|
||||
This can be cleverly exploited. For example, the following expression returns `x` when it is positive and $0$ otherwise:
|
||||
|
||||
|
||||
```{julia}
|
||||
(x > 0) * x
|
||||
```
|
||||
|
||||
There is a built in function, `max` that can be used for this: `max(0, x)`.
|
||||
|
||||
|
||||
This expression returns `x` if it is between $-10$ and $10$ and otherwise $-10$ or $10$ depending on whether $x$ is negative or positive.
|
||||
|
||||
|
||||
```{julia}
|
||||
(x < -10)*(-10) + (x >= -10)*(x < 10) * x + (x>=10)*10
|
||||
```
|
||||
|
||||
The `clamp(x, a, b)` performs this task more generally, and is used as in `clamp(x, -10, 10)`.
|
||||
|
||||
|
||||
## Questions
|
||||
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Is `e^pi` or `pi^e` greater?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = [
|
||||
"`e^pi` is greater than `pi^e`",
|
||||
"`e^pi` is equal to `pi^e`",
|
||||
"`e^pi` is less than `pi^e`"
|
||||
]
|
||||
answ = 1
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Is $\sin(1000)$ positive?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = (sin(1000) > 0)
|
||||
yesnoq(answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Suppose you know $0 < a < b$. What can you say about the relationship between $-1/a$ and $-1/b$?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = [
|
||||
"``-1/a < -1/b``",
|
||||
"``-1/a > -1/b``",
|
||||
raw"``-1/a \geq -1/b``"]
|
||||
answ = 1
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Suppose you know $a < 0 < b$, is it true that $1/a > 1/b$?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = ["Yes, it is always true.",
|
||||
"It can sometimes be true, though not always.",
|
||||
L"It is never true, as $1/a$ is negative and $1/b$ is positive"]
|
||||
answ = 3
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The `airyai` [function](http://en.wikipedia.org/wiki/Airy_function) is a special function named after a British Astronomer who realized the function's value in his studies of the rainbow. The `SpecialFunctions` package must be loaded to include this function, which is done with the accompanying package `CalculusWithJulia`.
|
||||
|
||||
|
||||
```{julia}
|
||||
airyai(0)
|
||||
```
|
||||
|
||||
It is known that this function is always positive for $x > 0$, though not so for negative values of $x$. Which of these indicates the first negative value : `airyai(-1) <0`, `airyai(-2) < 0`, ..., or `airyai(-5) < 0`?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = ["`airyai($i) < 0`" for i in -1:-1:-5]
|
||||
answ = 3
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
By trying three different values of $x > 0$ which of these could possibly be always true:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = ["`x^x <= (1/e)^(1/e)`",
|
||||
"`x^x == (1/e)^(1/e)`",
|
||||
"`x^x >= (1/e)^(1/e)`"]
|
||||
answ = 3
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Student logic says $(x+y)^p = x^p + y^p$. Of course, this isn't correct for all $p$ and $x$. By trying a few points, which is true when $x,y > 0$ and $0 < p < 1$:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = ["`(x+y)^p < x^p + y^p`",
|
||||
"`(x+y)^p == x^p + y^p`",
|
||||
"`(x+y)^p > x^p + y^p`"]
|
||||
answ = 1
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
According to Wikipedia, one of the following inequalities is always true for $a, b > 0$ (as proved by I. Ilani in JSTOR, AMM, Vol.97, No.1, 1990). Which one?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = ["`a^a + b^b <= a^b + b^a`",
|
||||
"`a^a + b^b >= a^b + b^a`",
|
||||
"`a^b + b^a <= 1`"]
|
||||
answ = 2
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Is $3$ in the set $\lvert x - 2\rvert < 1/2$?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
val = abs(3-2) < 1/2
|
||||
yesnoq(val)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Which of the following is equivalent to $\lvert x - a\rvert > b$:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = [raw"``-b < x - a < b``",
|
||||
raw"`` -b < x-a \text{ and } x - a < b``",
|
||||
raw"``x - a < -b \text{ or } x - a > b``"]
|
||||
answ = 3
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
If $\lvert x - \pi\rvert < 1/10$ is $\lvert \sin(x) - \sin(\pi)\rvert < 1/10$?
|
||||
|
||||
|
||||
Guess an answer based on a few runs of
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| eval: false
|
||||
x = pi + 1/10 * (2rand()-1)
|
||||
abs(x - pi) < 1/10, abs(sin(x) - sin(pi)) < 1/10
|
||||
```
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
booleanq(true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Does `12` satisfy $\lvert x - 3\rvert + \lvert x-9\rvert > 12$?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
x = 12
|
||||
val = (abs(x -3) + abs(x-9) > 12)
|
||||
yesnoq(val)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Which of these will show DeMorgan's law holds when both values are `false`:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = ["`!(false && false) == (!false && !false)`",
|
||||
"`!(false && false) == (false || false)`",
|
||||
"`!(false && false) == (!false || !false)`"]
|
||||
answ = 3
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
For floating point numbers there are two special values `Inf` and `NaN`. For which of these is the answer always `false`:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = ["`Inf < 3.0` and `3.0 <= Inf`",
|
||||
"`NaN < 3.0` and `3.0 <= NaN`"]
|
||||
answ = 2
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The IEEE 754 standard is about floating point numbers, for which there are the special values `Inf`, `-Inf`, `NaN`, and, surprisingly, `-0.0` (as a floating point number and not `-0`, an integer). Here are 4 facts that seem reasonable:
|
||||
|
||||
|
||||
* Positive zero is equal but not greater than negative zero.
|
||||
* `Inf` is equal to itself and greater than everything else except `NaN`.
|
||||
* `-Inf` is equal to itself and less then everything else except `NaN`.
|
||||
* `NaN` is not equal to, not less than, and not greater than anything, including itself.
|
||||
|
||||
|
||||
Do all four seem to be the case within `Julia`? Find your answer by trial and error.
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
yesnoq(true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The `NaN` value is meant to signal an error in computation. `Julia` has value to indicate some data is missing or unavailable. This is `missing`. For `missing` values we have these computations:
|
||||
|
||||
|
||||
```{julia}
|
||||
true && missing, true || missing
|
||||
```
|
||||
|
||||
We see the value of `true || missing` is `true`. Why?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = ["""
|
||||
In the manual we can read that "In the expression `a || b`, the subexpression `b` is only evaluated if `a` evaluates to false." In this case `a` is `true` and so `a` is returned.
|
||||
""",
|
||||
"Since the second value is \"`missing`\", only the first is used. So `false || missing` would also be `false`"]
|
||||
answ = 1
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
The value for `true && missing` is `missing`, not a boolean value. What happens?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = ["""
|
||||
In the manual we can read that "In the expression `a && b`, the subexpression `b` is only evaluated if `a` evaluates to true." In this case, `a` is `true` so `b` is evaluated and returned. As `b` is just `missing` that is the return value.
|
||||
""",
|
||||
"Since the second value is \"`missing`\" all such answers would be missing."]
|
||||
answ = 1
|
||||
radioq(choices, answ)
|
||||
```
|
||||
@@ -2,13 +2,7 @@ module Make
|
||||
# makefile for generating typst pdfs
|
||||
# per directory usage
|
||||
dir = "precalc"
|
||||
files = ("calculator",
|
||||
"variables",
|
||||
"numbers_types",
|
||||
"logical_expressions",
|
||||
"vectors",
|
||||
"ranges",
|
||||
"functions",
|
||||
files = ("functions",
|
||||
"plotting",
|
||||
"transformations",
|
||||
"inversefunctions",
|
||||
|
||||
@@ -1,685 +0,0 @@
|
||||
# Number systems
|
||||
|
||||
|
||||
{{< include ../_common_code.qmd >}}
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
#| results: "hidden"
|
||||
using CalculusWithJulia
|
||||
|
||||
nothing
|
||||
```
|
||||
|
||||
In mathematics, there are many different number systems in common use. For example by the end of pre-calculus, all of the following have been introduced:
|
||||
|
||||
|
||||
* The integers, $\{\dots, -3, -2, -1, 0, 1, 2, 3, \dots\}$;
|
||||
* The rational numbers, $\{p/q: p, q \text{ are integers}, q \neq 0\}$;
|
||||
* The real numbers, $\{x: -\infty < x < \infty\}$;
|
||||
* The complex numbers, $\{a + bi: a,b \text{ are real numbers and } i^2=-1\}$.
|
||||
|
||||
|
||||
On top of these, we have special subsets, such as the natural numbers $\{1, 2, \dots\}$ (sometimes including $0$), the even numbers, the odd numbers, the positive numbers, the non-negative numbers, etc.
|
||||
|
||||
|
||||
Mathematically, these number systems are naturally nested within each other as integers are rational numbers which are real numbers, which can be viewed as part of the complex numbers.
|
||||
|
||||
|
||||
Calculators typically have just one type of number - floating point values. These model the real numbers. `Julia`, on the other hand, has a rich type system, and within that has several different number types. There are types that model each of the four main systems above, and within each type, specializations for how these values are stored.
|
||||
|
||||
|
||||
Most of the details will not be of interest to all, and will be described later.
|
||||
|
||||
|
||||
For now, let's consider the number $1$. It can be viewed as either an integer, rational, real, or complex number. To construct "$1$" in each type within `Julia` we have these different styles:
|
||||
|
||||
|
||||
```{julia}
|
||||
1, 1.0, 1//1, 1 + 0im
|
||||
```
|
||||
|
||||
The basic number types in `Julia` are `Int`, `Float64`, `Rational` and `Complex`, though in fact there are many more, and the last two aren't even *concrete* types. This distinction is important, as the type of number dictates how it will be stored and how precisely the stored value can be expected to be to the mathematical value it models.
|
||||
|
||||
|
||||
Though there are explicit constructors for these types, these notes avoid them unless necessary, as `Julia`'s parser can distinguish these types through an easy to understand syntax:
|
||||
|
||||
|
||||
* integers have no decimal point;
|
||||
* floating point numbers have a decimal point (or are in scientific notation);
|
||||
* rationals are constructed from integers using the double division operator, `//`; and
|
||||
* complex numbers are formed by including a term with the imaginary unit, `im`.
|
||||
|
||||
|
||||
:::{.callout-note}
|
||||
## Warning
|
||||
Heads up, the difference between `1` and `1.0` is subtle. Even more so, as `1.` will parse as `1.0`. This means some expressions, such as `2.*3`, are ambiguous, as the `.` might be part of the `2` (as in `2. * 3`) or the operation `*` (as in `2 .* 3`).
|
||||
|
||||
:::
|
||||
|
||||
Similarly, each type is printed slightly differently.
|
||||
|
||||
|
||||
The key distinction is between integers and floating points. While floating point values include integers, and so can be used exclusively on the calculator, the difference is that an integer is guaranteed to be an exact value, whereas a floating point value, while often an exact representation of a number is also often just an *approximate* value. This can be an advantage – floating point values can model a much wider range of numbers.
|
||||
|
||||
In nearly all cases the differences are not noticeable. Take for instance this simple calculation involving mixed types.
|
||||
|
||||
|
||||
```{julia}
|
||||
1 + 1.25 + 3//2
|
||||
```
|
||||
|
||||
The sum of an integer, a floating point number and rational number returns a floating point number without a complaint.
|
||||
|
||||
|
||||
This is because behind the scenes, `Julia` will often "promote" a type to match, so for example to compute `1 + 1.25` the integer `1` will be promoted to a floating point value and the two values are then added. Similarly, with `2.25 + 3//2`, where the fraction is promoted to the floating point value `1.5` and addition is carried out.
|
||||
|
||||
|
||||
As floating point numbers may be approximations, some values are not quite what they would be mathematically:
|
||||
|
||||
|
||||
```{julia}
|
||||
sqrt(2) * sqrt(2) - 2, sin(1pi), 1/10 + 1/5 - 3/10
|
||||
```
|
||||
|
||||
These values are *very* small numbers, but not exactly $0$, as they are mathematically.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
The only common issue is with powers. We saw this previously when discussing a distinction between `2^64` and `2.0^64`. `Julia` tries to keep a predictable output from the input types (not their values). Here are the two main cases that arise where this can cause unexpected results:
|
||||
|
||||
|
||||
* integer bases and integer exponents can *easily* overflow. Not only `m^n` is always an integer, it is always an integer with a fixed storage size computed from the sizes of `m` and `n`. So the powers can quickly get too big. This can be especially noticeable on older $32$-bit machines, where too big is $2^{32} = 4,294,967,296$. On $64$-bit machines, this limit is present but much bigger.
|
||||
|
||||
|
||||
Rather than give an error though, `Julia` gives seemingly arbitrary answers, as can be seen in this example on a $64$-bit machine:
|
||||
|
||||
|
||||
```{julia}
|
||||
2^62, 2^63
|
||||
```
|
||||
|
||||
(They aren't arbitrary, as explained previously.)
|
||||
|
||||
|
||||
This could be worked around, as it is with some programming languages, but it isn't, as it would slow down this basic computation. So, it is up to the user to be aware of cases where their integer values can grow to big. The suggestion is to use floating point numbers in this domain, as they have more room, at the cost of sometimes being approximate values for fairly large values.
|
||||
|
||||
|
||||
* the `sqrt` function will give a domain error for negative values:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| error: true
|
||||
sqrt(-1.0)
|
||||
```
|
||||
|
||||
This is because for real-valued inputs `Julia` expects to return a real-valued output. Of course, this is true in mathematics until the complex numbers are introduced. Similarly in `Julia` - to take square roots of negative numbers, start with complex numbers:
|
||||
|
||||
|
||||
```{julia}
|
||||
sqrt(-1.0 + 0im)
|
||||
```
|
||||
|
||||
* At one point, `Julia` had an issue with a third type of power:
|
||||
|
||||
|
||||
integer bases and negative integer exponents. For example `2^(-1)`. This is now special cased, though only for numeric literals. If `z=-1`, `2^z` will throw a `DomainError`. Historically, the desire to keep a predictable type for the output (integer) led to defining this case as a domain error, but its usefulness led to special casing.
|
||||
|
||||
|
||||
## Additional details.
|
||||
|
||||
|
||||
What follows is only needed for those seeking more background.
|
||||
|
||||
|
||||
Julia has *abstract* number types `Integer`, `Real`, and `Number`. All four types described above are of type `Number`, but `Complex` is not of type `Real`.
|
||||
|
||||
|
||||
However, a specific value is an instance of a *concrete* type. A concrete type will also include information about how the value is stored. For example, the *integer* `1` could be stored using $64$ bits as a signed integers, or, should storage be a concern, as an $8$ bits signed or even unsigned integer, etc.. If storage isn't an issue, but exactness at all scales is, then it can be stored in a manner that allows for the storage to grow using "big" numbers.
|
||||
|
||||
|
||||
These distinctions can be seen in how `Julia` parses these three values:
|
||||
|
||||
|
||||
* `1234567890` will be a $64$-bit integer (on newer machines), `Int64`
|
||||
* `12345678901234567890` will be a $128$ bit integer, `Int128`
|
||||
* `1234567890123456789012345678901234567890` will be a big integer, `BigInt`
|
||||
|
||||
|
||||
Having abstract types allows programmers to write functions that will work over a wide range of input values that are similar, but have different implementation details.
|
||||
|
||||
|
||||
### Integers
|
||||
|
||||
|
||||
Integers are often used casually, as they come about from parsing. As with a calculator, floating point numbers *could* be used for integers, but in `Julia` - and other languages - it proves useful to have numbers known to have *exact* values. In `Julia` there are built-in number types for integers stored in $8$, $16$, $32$, $64$, and $128$ bits and `BigInt`s if the previous aren't large enough. ($8$ bits can hold $8$ binary values representing $1$ of $256=2^8$ possibilities, whereas the larger $128$ bit can hold one of $2^{128}$ possibilities.) Smaller values can be more efficiently used, and this is leveraged at the system level, but not a necessary distinction with calculus where the default size along with an occasional usage of `BigInt` suffice.
|
||||
|
||||
|
||||
### Floating point numbers
|
||||
|
||||
|
||||
[Floating point](http://en.wikipedia.org/wiki/Floating_point) numbers are a computational model for the real numbers. For floating point numbers, $64$ bits are used by default for both $32$- and $64$-bit systems, though other storage sizes can be requested. This gives a large range - but still finite - set of real numbers that can be represented. However, there are infinitely many real numbers just between $0$ and $1$, so there is no chance that all can be represented exactly on the computer with a floating point value. Floating point then is *necessarily* an approximation for all but a subset of the real numbers. Floating point values can be viewed in normalized [scientific notation](http://en.wikipedia.org/wiki/Scientific_notation) as $a\cdot 2^b$ where $a$ is the *significand* and $b$ is the *exponent*. Save for special values, the significand $a$ is normalized to satisfy $1 \leq \lvert a\rvert < 2$, the exponent can be taken to be an integer, possibly negative.
|
||||
|
||||
|
||||
As per IEEE Standard 754, the `Float64` type gives 52 bits to the precision (with an additional implied one), 11 bits to the exponent and the other bit is used to represent the sign. Positive, finite, floating point numbers have a range approximately between $10^{-308}$ and $10^{308}$, as 308 is about $\log_{10} 2^{1023}$. The numbers are not evenly spread out over this range, but, rather, are much more concentrated closer to $0$.
|
||||
|
||||
The use of 32-bit floating point values is common, as some widely used computer chips expect this. These values have a narrower range of possible values.
|
||||
|
||||
:::{.callout-warning}
|
||||
## More on floating point numbers
|
||||
You can discover more about the range of floating point values provided by calling a few different functions.
|
||||
|
||||
* `typemax(0.0)` gives the largest value for the type (`Inf` in this case).
|
||||
* `prevfloat(Inf)` gives the largest finite one, in general `prevfloat` is the next smallest floating point value.
|
||||
|
||||
* `nextfloat(-Inf)`, similarly, gives the smallest finite floating point value, and in general returns the next largest floating point value.
|
||||
* `nextfloat(0.0)` gives the closest positive value to 0.
|
||||
* `eps()` gives the distance to the next floating point number bigger than `1.0`. This is sometimes referred to as machine precision.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
#### Scientific notation
|
||||
|
||||
|
||||
Floating point numbers may print in a familiar manner:
|
||||
|
||||
|
||||
```{julia}
|
||||
x = 1.23
|
||||
```
|
||||
|
||||
or may be represented in scientific notation:
|
||||
|
||||
|
||||
```{julia}
|
||||
6.022 * 10.0^23
|
||||
```
|
||||
|
||||
The special coding `aeb` (or if the exponent is negative `ae-b`) is used to represent the number $a \cdot 10^b$ ($1 \leq a < 10$). This notation can be used directly to specify a floating point value:
|
||||
|
||||
|
||||
```{julia}
|
||||
avogadro = 6.022e23
|
||||
```
|
||||
|
||||
::: {.callout-note}
|
||||
## Not `e`
|
||||
Here `e` is decidedly *not* the Euler number, rather **syntax** to separate the exponent from the mantissa.
|
||||
:::
|
||||
|
||||
The first way of representing this number required using `10.0` and not `10` as the integer power will return an integer and even for 64-bit systems is only valid up to `10^18`. Using scientific notation avoids having to concentrate on such limitations.
|
||||
|
||||
|
||||
##### Example
|
||||
|
||||
|
||||
Floating point values in scientific notation will always be normalized. This is easy for the computer to do, but tedious to do by hand. Here we see:
|
||||
|
||||
|
||||
```{julia}
|
||||
4e30 * 3e40
|
||||
```
|
||||
|
||||
```{julia}
|
||||
3e40 / 4e30
|
||||
```
|
||||
|
||||
The power in the first is $71$, not $70 = 30+40$, as the product of $3$ and $4$ as $12$ or `1.2e^1`. (We also see the artifact of `1.2` not being exactly representable in floating point.)
|
||||
|
||||
|
||||
##### Example: 32-bit floating point
|
||||
|
||||
|
||||
In some uses, such as using a GPU, $32$-bit floating point (single precision) is also common. These values may be specified with an `f` in place of the `e` in scientific notation:
|
||||
|
||||
|
||||
```{julia}
|
||||
1.23f0
|
||||
```
|
||||
|
||||
As with the use of `e`, some exponent is needed after the `f`, even if it is `0`.
|
||||
|
||||
|
||||
#### Special values: Inf, -Inf, NaN
|
||||
|
||||
|
||||
The coding of floating point numbers also allows for the special values of `Inf`, `-Inf` to represent positive and negative infinity. As well, a special value `NaN` ("not a number") is used to represent a value that arises when an operation is not closed (e.g., $0.0/0.0$ yields `NaN`). (Technically `NaN` has several possible "values," a point ignored here.) Except for negative bases, the floating point numbers with the addition of `Inf` and `NaN` are closed under the operations `+`, `-`, `*`, `/`, and `^`. Here are some computations that produce `NaN`:
|
||||
|
||||
|
||||
```{julia}
|
||||
0/0, Inf/Inf, Inf - Inf, 0 * Inf
|
||||
```
|
||||
|
||||
Whereas, these produce an infinity
|
||||
|
||||
|
||||
```{julia}
|
||||
1/0, Inf + Inf, 1 * Inf
|
||||
```
|
||||
|
||||
Finally, these are mathematically undefined, but still yield a finite value with `Julia`:
|
||||
|
||||
|
||||
```{julia}
|
||||
0^0, Inf^0
|
||||
```
|
||||
|
||||
#### Floating point numbers and real numbers
|
||||
|
||||
|
||||
Floating point numbers are an abstraction for the real numbers. For the most part this abstraction works in the background, though there are cases where one needs to have it in mind. Here are a few:
|
||||
|
||||
|
||||
* For real and rational numbers, between any two numbers $a < b$, there is another real number in between. This is not so for floating point numbers which have a finite precision. (Julia has some functions for working with this distinction.)
|
||||
* Floating point numbers are approximations for most values, even simple rational ones like $1/3$. This leads to oddities such as this value not being $0$:
|
||||
|
||||
|
||||
```{julia}
|
||||
sqrt(2)*sqrt(2) - 2
|
||||
```
|
||||
|
||||
It is no surprise that an irrational number, like $\sqrt{2}$, can't be represented **exactly** within floating point, but it is perhaps surprising that simple numbers can not be, so $1/3$, $1/5$, $\dots$ are approximated. Here is a surprising-at-first consequence:
|
||||
|
||||
|
||||
```{julia}
|
||||
1/10 + 2/10 == 3/10
|
||||
```
|
||||
|
||||
That is adding `1/10` and `2/10` is not exactly `3/10`, as expected mathematically. Such differences are usually very small and are generally attributed to rounding error. The user needs to be mindful when testing for equality, as is done above with the `==` operator.
|
||||
|
||||
|
||||
* Floating point addition is not necessarily associative, that is the property $a + (b+c) = (a+b) + c$ may not hold exactly. For example:
|
||||
|
||||
|
||||
```{julia}
|
||||
1/10 + (2/10 + 3/10) == (1/10 + 2/10) + 3/10
|
||||
```
|
||||
|
||||
* Mathematically, for real numbers, subtraction of similar-sized numbers is not exceptional, for example $1 - \cos(x)$ is positive if $0 < x < \pi/2$, say. This will not be the case for floating point values. If $x$ is close enough to $0$, then $\cos(x)$ and $1$ will be so close, that they will be represented by the same floating point value, `1.0`, so the difference will be zero:
|
||||
|
||||
|
||||
```{julia}
|
||||
1.0 - cos(1e-8)
|
||||
```
|
||||
|
||||
### Rational numbers
|
||||
|
||||
|
||||
Rational numbers can be used when the exactness of the number is more important than the speed or wider range of values offered by floating point numbers. In `Julia` a rational number is comprised of a numerator and a denominator, each an integer of the same type, and reduced to lowest terms. The operations of addition, subtraction, multiplication, and division will keep their answers as rational numbers. As well, raising a rational number to an integer value will produce a rational number.
|
||||
|
||||
|
||||
As mentioned, these are constructed using double slashes:
|
||||
|
||||
|
||||
```{julia}
|
||||
1//2, 2//1, 6//4
|
||||
```
|
||||
|
||||
Rational numbers are exact, so the following are identical to their mathematical counterparts:
|
||||
|
||||
|
||||
```{julia}
|
||||
1//10 + 2//10 == 3//10
|
||||
```
|
||||
|
||||
and associativity:
|
||||
|
||||
|
||||
```{julia}
|
||||
(1//10 + 2//10) + 3//10 == 1//10 + (2//10 + 3//10)
|
||||
```
|
||||
|
||||
Here we see that the type is preserved under the basic operations:
|
||||
|
||||
|
||||
```{julia}
|
||||
(1//2 + 1//3 * 1//4 / 1//5) ^ 6
|
||||
```
|
||||
|
||||
For powers, a non-integer exponent is converted to floating point, so this operation is defined, though will always return a floating point value:
|
||||
|
||||
|
||||
```{julia}
|
||||
(1//2)^(1//2) # the first parentheses are necessary as `^` will be evaluated before `//`.
|
||||
```
|
||||
|
||||
##### Example: different types of real numbers
|
||||
|
||||
|
||||
This table shows what attributes are implemented for the different types.
|
||||
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
using DataFrames
|
||||
attributes = ["construction", "exact", "wide range", "has infinity", "has `-0`", "fast", "closed under"]
|
||||
integer = [q"1", "true", "false", "false", "false", "true", "`+`, `-`, `*`, `^` (non-negative exponent)"]
|
||||
rational = ["`1//1`", "true", "false", "false", "false", "false", "`+`, `-`, `*`, `/` (non zero denominator),`^` (integer power)"]
|
||||
float = [q"1.0", "not usually", "true", "true", "true", "true", "`+`, `-`, `*`, `/` (possibly `NaN`, `Inf`),`^` (non-negative base)"]
|
||||
d = DataFrame(Attributes=attributes, Integer=integer, Rational=rational, FloatingPoint=float)
|
||||
table(d)
|
||||
```
|
||||
|
||||
### Complex numbers
|
||||
|
||||
|
||||
Complex numbers in `Julia` are stored as two numbers, a real and imaginary part, each some type of `Real` number. The special constant `im` is used to represent $i=\sqrt{-1}$. This makes the construction of complex numbers fairly standard:
|
||||
|
||||
|
||||
```{julia}
|
||||
1 + 2im, 3 + 4.0im
|
||||
```
|
||||
|
||||
(These two aren't exactly the same, the `3` is promoted from an integer to a float to match the `4.0`. Each of the components must be of the same type of number.)
|
||||
|
||||
|
||||
Mathematically, complex numbers are needed so that certain equations can be satisfied. For example $x^2 = -2$ has solutions $-\sqrt{2}i$ and $\sqrt{2}i$ over the complex numbers. Finding this in `Julia` requires some attention, as we have both `sqrt(-2)` and `sqrt(-2.0)` throwing a `DomainError`, as the `sqrt` function expects non-negative real arguments. However first creating a complex number does work:
|
||||
|
||||
|
||||
```{julia}
|
||||
sqrt(-2 + 0im)
|
||||
```
|
||||
|
||||
For complex arguments, the `sqrt` function will return complex values (even if the answer is a real number).
|
||||
|
||||
|
||||
This means, if you wanted to perform the quadratic equation for any real inputs, your computations might involve something like the following:
|
||||
|
||||
|
||||
```{julia}
|
||||
a,b,c = 1,2,3 ## x^2 + 2x + 3
|
||||
discr = b^2 - 4a*c
|
||||
(-b + sqrt(discr + 0im))/(2a), (-b - sqrt(discr + 0im))/(2a)
|
||||
```
|
||||
|
||||
When learning calculus, the only common usage of complex numbers arises when solving polynomial equations for roots, or zeros, though they are very important for subsequent work using the concepts of calculus.
|
||||
|
||||
|
||||
:::{.callout-note}
|
||||
## Note
|
||||
Though complex numbers are stored as pairs of numbers, the imaginary unit, `im`, is of type `Complex{Bool}`, a type that can be promoted to more specific types when `im` is used with different number types.
|
||||
|
||||
:::
|
||||
|
||||
### Strings and symbols
|
||||
|
||||
For text, `Julia` has a `String` type. When double quotes are used to specify a string, the parser creates this type:
|
||||
|
||||
```{julia}
|
||||
x = "The quick brown fox jumped over the lazy dog"
|
||||
typeof(x)
|
||||
```
|
||||
|
||||
Values can be inserted into a string through *interpolation* using a dollar sign.
|
||||
|
||||
```{julia}
|
||||
animal = "lion"
|
||||
x = "The quick brown $(animal) jumped over the lazy dog"
|
||||
```
|
||||
|
||||
The use of parentheses allows more complicated expressions; it isn't always necessary.
|
||||
|
||||
Longer strings can be produced using *triple* quotes:
|
||||
|
||||
```{julia}
|
||||
lincoln = """
|
||||
Four score and seven years ago our fathers brought forth, upon this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.
|
||||
"""
|
||||
```
|
||||
|
||||
Strings are comprised of *characters* which can be produced directly using *single* quotes:
|
||||
|
||||
```{julia}
|
||||
'c'
|
||||
```
|
||||
|
||||
We won't use these.
|
||||
|
||||
Finally, `Julia` has *symbols* which are *interned* strings which are used as identifiers. Symbols are used for advanced programming techniques; we will only see them as shortcuts to specify plotting arguments.
|
||||
|
||||
## Type stability
|
||||
|
||||
|
||||
One design priority of `Julia` is that it should be fast. How can `Julia` do this? In a simple model, `Julia` is an interface between the user and the computer's processor(s). Processors consume a set of instructions, the user issues a set of commands. `Julia` is in charge of the translation between the two. Ultimately `Julia` calls a compiler to create the instructions. A basic premise is the shorter the instructions, the faster they are to process. Shorter instructions can come about by being more explicit about what types of values the instructions concern. Explicitness means, there is no need to reason about what a value can be. When `Julia` can reason about the type of value involved without having to reason about the values themselves, it can work with the compiler to produce shorter lists of instructions.
|
||||
|
||||
|
||||
So knowing the type of the output of a function based only on the type of the inputs can be a big advantage. In `Julia` this is known as *type stability*. In the standard `Julia` library, this is a primary design consideration.
|
||||
|
||||
|
||||
##### Example: closure
|
||||
|
||||
|
||||
To motivate this a bit, we discuss how mathematics can be shaped by a desire to stick to simple ideas. A desirable algebraic property of a set of numbers and an operation is *closure*. That is, if one takes an operation like `+` and then uses it to add two numbers in a set, will that result also be in the set? If this is so for any pair of numbers, then the set is closed with respect to the operation addition.
|
||||
|
||||
|
||||
Lets suppose we start with the *natural numbers*: $1,2, \dots$. Natural, in that we can easily represent small values in terms of fingers. This set is closed under addition - as a child learns when counting using their fingers. However, if we started with the odd natural numbers, this set would *not* be closed under addition - $3+3=6$.
|
||||
|
||||
|
||||
The natural numbers are not all the numbers we need, as once a desire for subtraction is included, we find the set isn't closed. There isn't a $0$, needed as $n-n=0$ and there aren't negative numbers. The set of integers are needed for closure under addition and subtraction.
|
||||
|
||||
|
||||
The integers are also closed under multiplication, which for integer values can be seen as just regrouping into longer additions.
|
||||
|
||||
|
||||
However, the integers are not closed under division - even if you put aside the pesky issue of dividing by $0$. For that, the rational numbers must be introduced. So aside from division by $0$, the rationals are closed under addition, subtraction, multiplication, and division. There is one more fundamental operation though, powers.
|
||||
|
||||
|
||||
Powers are defined for positive integers in a simple enough manner
|
||||
|
||||
|
||||
$$
|
||||
a^n=a \cdot a \cdot a \cdots a \text{ (n times); } a, n \text{ are integers } n \text{ is positive}.
|
||||
$$
|
||||
|
||||
We can define $a^0$ to be $1$, except for the special case of $0^0$, which is left undefined mathematically (though it is also defined as `1` within `Julia`). We can extend the above to include negative values of $a$, but what about negative values of $n$? We can't say the integers are closed under powers, as the definition consistent with the rules that $a^{(-n)} = 1/a^n$ requires rational numbers to be defined.
|
||||
|
||||
|
||||
Well, in the above `a` could be a rational number, is `a^n` closed for rational numbers? No again. Though it is fine for $n$ as an integer (save the odd case of $0$, simple definitions like $2^{1/2}$ are not answered within the rationals. For this, we need to introduce the *real* numbers. It is mentioned that [Aristotle](http://tinyurl.com/bpqbkap) hinted at the irrationality of the square root of $2$. To define terms like $a^{1/n}$ for integer values $a,n > 0$ a reference to a solution to an equation $x^n-a$ is used. Such solutions require the irrational numbers to have solutions in general. Hence the need for the real numbers (well, algebraic numbers at least, though once the exponent is no longer a rational number, the full set of real numbers are needed.)
|
||||
|
||||
|
||||
So, save the pesky cases, the real numbers will be closed under addition, subtraction, multiplication, division, and powers - provided the base is non-negative.
|
||||
|
||||
|
||||
Finally for that last case, the complex numbers are introduced to give an answer to $\sqrt{-1}$.
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
How does this apply with `Julia`?
|
||||
|
||||
|
||||
The point is, if we restrict our set of inputs, we can get more precise values for the output of basic operations, but to get more general inputs we need to have bigger output sets.
|
||||
|
||||
|
||||
A similar thing happens in `Julia`. For addition say, the addition of two integers of the same type will be an integer of that type. This speed consideration is not solely for type stability, but also to avoid checking for overflow.
|
||||
|
||||
|
||||
Another example, the division of two integers will always be a number of the same type - floating point, as that is the only type that ensures the answer will always fit within. (The explicit use of rationals notwithstanding.) So even if two integers are the input and their answer *could* be an integer, in `Julia` it will be a floating point number, (cf. `2/1`).
|
||||
|
||||
|
||||
Hopefully this helps explain the subtle issues around powers: in `Julia` an integer raised to an integer should be an integer, for speed, though certain cases are special cased, like `2^(-1)`. However since a real number raised to a real number makes sense always when the base is non-negative, as long as real numbers are used as outputs, the expressions `2.0^(-1)` and `2^(-1.0)` are computed and real numbers (floating points) are returned. For type stability, even though $2.0^1$ could be an integer, a floating point answer is returned.
|
||||
|
||||
|
||||
As for negative bases, `Julia` could always return complex numbers, but in addition to this being slower, it would be irksome to users. So user's must opt in. Hence `sqrt(-1.0)` will be an error, but the more explicit - but mathematically equivalent - `sqrt(-1.0 + 0im)` will not be a domain error, but rather a complex value will be returned.
|
||||
|
||||
|
||||
## Questions
|
||||
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
choices = ["Integer", "Rational", "Floating point", "Complex", "None, an error occurs"]
|
||||
nothing
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `pi/2` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 3
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `2/2` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 3
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `2//2` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 2
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `1 + 1//2 + 1/3` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 3
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `2^3` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 1
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `sqrt(im)` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 4
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The number created by `2^(-1)` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 3
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The "number" created by `1/0` is?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
answ = 3
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Is `(2 + 6) + 7` equal to `2 + (6 + 7)`?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
yesnoq(true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Is `(2/10 + 6/10) + 7/10` equal to `2/10 + (6/10 + 7/10)`?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
yesnoq(false)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The following *should* compute `2^(-1)`, which if entered directly will return `0.5`. Does it?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| eval: false
|
||||
a, b = 2, -1
|
||||
a^b
|
||||
```
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
yesnoq(false)
|
||||
```
|
||||
|
||||
(This shows the special casing that is done when powers use literal numbers.)
|
||||
|
||||
|
||||
###### Question
|
||||
|
||||
In [NewScientist](https://www.newscientist.com/article/2112537-smallest-sliver-of-time-yet-measured-sees-electrons-fleeing-atom/) we learn "For the first time, physicists have measured changes in an atom to the level of zeptoseconds, or trillionths of a billionth of a second – the smallest division of time yet observed."
|
||||
|
||||
That is
|
||||
|
||||
```{julia}
|
||||
1e-9 / 1e12
|
||||
```
|
||||
|
||||
Finding the value through division introduces a floating point deviation. Which of the following values will directly represent a zeptosecond?
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
as = ["1/10^21", "1e-21"]
|
||||
explanation = "The scientific notation is correct. Due to integer overflow `10^21` is not the same number as `10.0^21`."
|
||||
buttonq(as, 2; explanation)
|
||||
```
|
||||
@@ -1,4 +1,4 @@
|
||||
# The Graph of a Function
|
||||
# The graph of a function
|
||||
|
||||
|
||||
{{< include ../_common_code.qmd >}}
|
||||
@@ -254,10 +254,10 @@ nothing
|
||||
|
||||
|
||||
|
||||
----
|
||||
---
|
||||
|
||||
|
||||
Making a graph with `Plots` is easy, but producing a graph that is informative can be a challenge, as the choice of a viewing window can make a big difference in what is seen. For example, trying to make a graph of $f(x) = \tan(x)$, as below, will result in a bit of a mess - the chosen viewing window crosses several places where the function blows up:
|
||||
Making a graph with `Plots` is easy, but producing a graph that is informative can be a challenge, as the choice of a viewing window can make a big difference in what is seen. For example, trying to make a graph of $f(x) = \tan(x)$, as below, will result in a bit of a mess---the chosen viewing window crosses several places where the function blows up:
|
||||
|
||||
|
||||
```{julia}
|
||||
@@ -536,16 +536,16 @@ The `Plots` package uses positional arguments for input data and keyword argumen
|
||||
The `Plots` package provides many such arguments for adjusting a graphic, here we mention just a few:
|
||||
|
||||
|
||||
* `plot(...; title="main title", xlab="x axis label", ylab="y axis label")`: add title and label information to a graphic
|
||||
* `plot(...; color="green")`: this argument can be used to adjust the color of the drawn figure (color can be a string,`"green"`, or a symbol, `:green`, among other specifications)
|
||||
* `plot(...; linewidth=5)`: this argument can be used to adjust the width of drawn lines
|
||||
* `plot(...; linestyle=:dash)`: will change the line style of the plotted lines to dashed lines. Also `:dot`, ...
|
||||
* `plot(...; title="main title", xlabel="x axis label", ylabel="y axis label")`: add title and label information to a graphic
|
||||
* `plot(...; label="a label")` the `label` attribute will show up when a legend is present. Using an empty string, `""`, will suppress add the layer to the legend.
|
||||
* `plot(...; legend=false)`: by default, different layers will be indicated with a legend, this will turn off this feature
|
||||
* `plot(...; xlims=(a,b), ylims=(c,d))`: either or both `xlims` and `ylims` can be used to control the viewing window
|
||||
* `plot(...; xticks=[xs..], yticks=[ys...]: either or both `xticks` and `yticks` can be used to specify where the tick marks are to be drawn
|
||||
* `plot(...; aspect_ratio=:equal)`: will keep $x$ and $y$ axis on same scale so that squares look square.
|
||||
* `plot(...; framestyle=:origin)`: The default `framestyle` places $x$-$y$ guides on the edges; this specification places them on the $x-y$ plane.
|
||||
* `plot(...; color="green")`: this argument can be used to adjust the color of the drawn figure (color can be a string,`"green"`, or a symbol, `:green`, among other specifications)
|
||||
* `plot(...; linewidth=5)`: this argument can be used to adjust the width of drawn lines
|
||||
* `plot(...; linestyle=:dash)`: will change the line style of the plotted lines to dashed lines. Also `:dot`, ...
|
||||
|
||||
|
||||
For plotting points with `scatter`, or `scatter!` the markers can be adjusted via
|
||||
@@ -583,7 +583,7 @@ With these assumptions, we have an initial decision to make:
|
||||
We re-express our equation $y=f(x)= mx+b$ in general form $f(x,y) = 0 = Ax + By + C$. Using the other point on the line $A=-(y_1-y_0)$, $B=(x_1-x_0)$, and $C = -x_1y_0 + x_0 y_1$. In particular, by assumption both $A$ and $B$ are positive.
|
||||
|
||||
|
||||
With this, we have $f(x_0,y_0) = 0$. But moreover, any point with $y>y_0$ will have $f(x_0,y)>0$ and if $y < y_0$ the opposite. That is this equation divides the plane into two pieces depending on whether $f$ is positive, the line is the dividing boundary.
|
||||
With this, we have $f(x_0,y_0) = 0$. But moreover, any point $(x_0,y)$ with $y>y_0$ will have $f(x_0,y)>0$ and if $y < y_0$ the opposite. That is this equation divides the plane into two pieces depending on whether $f$ is positive---the line is the dividing boundary.
|
||||
|
||||
For the algorithm, we start at $(x_0, y_0)$ and ask if the pixel $(x_0 + 1, y_0)$ or $(x_0 + 1, y_0 - 1)$ will be lit, then we continue to the right.
|
||||
|
||||
@@ -637,7 +637,7 @@ Two basic objects to graph are points and lines. Add to these polygons.
|
||||
|
||||
A point in two-dimensional space has two coordinates, often denoted by $(x,y)$. In `Julia`, the same notation produces a `tuple`. Using square brackets, as in `[x,y]`, produces a vector. Vectors are are more commonly used in these notes, as we have seen there are algebraic operations defined for them. However, tuples have other advantages and are how `Plots` designates a point.
|
||||
|
||||
The plot command `plot(xs, ys)` plots the points $(x_1,y_1), \dots, (x_n, y_n)$ and then connects adjacent points with with lines. The command `scatter(xs, ys)` just plots the points.
|
||||
The plot command `plot(xs, ys)` plots the points $(x_1,y_1), \dots, (x_n, y_n)$ and then connects adjacent points with lines. The command `scatter(xs, ys)` just plots the points.
|
||||
|
||||
However, the points might be more naturally specified as coordinate pairs. If tuples are used to pair them off, then `Plots` will plot a vector of tuples as a sequence of points through `plot([(x1,y1), (x2, y2), ..., (xn, yn)])`:
|
||||
|
||||
@@ -711,7 +711,7 @@ scatter!(f.(θs), g.(θs))
|
||||
---
|
||||
|
||||
|
||||
As with the plot of a univariate function, there is a convenience interface for these plots - just pass the two functions in:
|
||||
As with the plot of a univariate function, there is a convenience interface for these plots---just pass the two functions in:
|
||||
|
||||
|
||||
```{julia}
|
||||
@@ -777,8 +777,9 @@ Playing with the toy makes a few things become clear:
|
||||
|
||||
These all apply to parametric plots, as the Etch A Sketch trace is no more than a plot of $(f(t), g(t))$ over some range of values for $t$, where $f$ describes the movement in time of the left knob and $g$ the movement in time of the right.
|
||||
|
||||
---
|
||||
|
||||
Now, we revisit the last problem in the context of this. We saw in the last problem that the parametric graph was nearly a line - so close the eye can't really tell otherwise. That means that the growth in both $f(t) = t^3$ and $g(t)=t - \sin(t)$ for $t$ around $0$ are in a nearly fixed ratio, as otherwise the graph would have more curve in it.
|
||||
Now, we revisit the last problem in the context of this. We saw in the last problem that the parametric graph was nearly a line---so close the eye can't really tell otherwise. That means that the growth in both $f(t) = t^3$ and $g(t)=t - \sin(t)$ for $t$ around $0$ are in a nearly fixed ratio, as otherwise the graph would have more curve in it.
|
||||
|
||||
|
||||
##### Example: Spirograph
|
||||
@@ -1136,14 +1137,3 @@ choices = [
|
||||
answ = 5
|
||||
radioq(choices, answ, keep_order=true)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Technical note
|
||||
|
||||
|
||||
The slow "time to first plot" in `Julia` is a well-known hiccup that is related to how `Julia` can be so fast. Loading Plots and the making the first plot are both somewhat time consuming, though the second and subsequent plots are speedy. Why?
|
||||
|
||||
|
||||
`Julia` is an interactive language that attains its speed by compiling functions on the fly using the [llvm](llvm.org) compiler. When `Julia` encounters a new combination of a function method and argument types it will compile and cache a function for subsequent speedy execution. The first plot is slow, as there are many internal functions that get compiled. This has sped up of late, as excessive recompilations have been trimmed down, but still has a way to go. This is different from "precompilation" which also helps trim down time for initial executions. There are also some more technically challenging means to create `Julia` images for faster start up that can be pursued if needed.
|
||||
|
||||
@@ -614,7 +614,7 @@ It is easy to create a symbolic expression from a function - just evaluate the f
|
||||
f(x)
|
||||
```
|
||||
|
||||
This is easy--but can also be confusing. The function object is `f`, the expression is `f(x)`--the function evaluated on a symbolic object. Moreover, as seen, the symbolic expression can be evaluated using the same syntax as a function call:
|
||||
This is easy---but can also be confusing. The function object is `f`, the expression is `f(x)`---the function evaluated on a symbolic object. Moreover, as seen, the symbolic expression can be evaluated using the same syntax as a function call:
|
||||
|
||||
|
||||
```{julia}
|
||||
|
||||
@@ -448,12 +448,12 @@ To get the numeric approximation, we can broadcast:
|
||||
N.(solveset(p ~ 0, x))
|
||||
```
|
||||
|
||||
(There is no need to call `collect` -- though you can -- as broadcasting over a set falls back to broadcasting over the iteration of the set and in this case returns a vector.)
|
||||
(There is no need to call `collect`---though you can---as broadcasting over a set falls back to broadcasting over the iteration of the set and in this case returns a vector.)
|
||||
|
||||
## Do numeric methods matter when you can just graph?
|
||||
|
||||
|
||||
It may seem that certain practices related to roots of polynomials are unnecessary as we could just graph the equation and look for the roots. This feeling is perhaps motivated by the examples given in textbooks to be worked by hand, which necessarily focus on smallish solutions. But, in general, without some sense of where the roots are, an informative graph itself can be hard to produce. That is, technology doesn't displace thinking--it only supplements it.
|
||||
It may seem that certain practices related to roots of polynomials are unnecessary as we could just graph the equation and look for the roots. This feeling is perhaps motivated by the examples given in textbooks to be worked by hand, which necessarily focus on smallish solutions. But, in general, without some sense of where the roots are, an informative graph itself can be hard to produce. That is, technology doesn't displace thinking---it only supplements it.
|
||||
|
||||
|
||||
For another example, consider the polynomial $(x-20)^5 - (x-20) + 1$. In this form we might think the roots are near $20$. However, were we presented with this polynomial in expanded form: $x^5 - 100x^4 + 4000x^3 - 80000x^2 + 799999x - 3199979$, we might be tempted to just graph it to find roots. A naive graph might be to plot over $[-10, 10]$:
|
||||
|
||||
@@ -76,7 +76,7 @@ Polynomials may be evaluated using function notation, that is:
|
||||
p(1)
|
||||
```
|
||||
|
||||
This blurs the distinction between a polynomial expression--a formal object consisting of an indeterminate, coefficients, and the operations of addition, subtraction, multiplication, and non-negative integer powers--and a polynomial function.
|
||||
This blurs the distinction between a polynomial expression---a formal object consisting of an indeterminate, coefficients, and the operations of addition, subtraction, multiplication, and non-negative integer powers---and a polynomial function.
|
||||
|
||||
|
||||
The polynomial variable, in this case `1x`, can be returned by `variable`:
|
||||
|
||||
@@ -1,751 +0,0 @@
|
||||
# Ranges and Sets
|
||||
|
||||
|
||||
{{< include ../_common_code.qmd >}}
|
||||
|
||||
|
||||
## Arithmetic sequences
|
||||
|
||||
|
||||
Sequences of numbers are prevalent in math. A simple one is just counting by ones:
|
||||
|
||||
|
||||
$$
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, \dots
|
||||
$$
|
||||
|
||||
Or counting by sevens:
|
||||
|
||||
|
||||
$$
|
||||
7, 14, 21, 28, 35, 42, 49, \dots
|
||||
$$
|
||||
|
||||
More challenging for humans is [counting backwards](http://www.psychpage.com/learning/library/assess/mse.htm) by 7:
|
||||
|
||||
|
||||
$$
|
||||
100, 93, 86, 79, \dots
|
||||
$$
|
||||
|
||||
These are examples of [arithmetic sequences](http://en.wikipedia.org/wiki/Arithmetic_progression). The form of the first $n+1$ terms in such a sequence is:
|
||||
|
||||
|
||||
$$
|
||||
a_0, a_0 + h, a_0 + 2h, a_0 + 3h, \dots, a_0 + nh
|
||||
$$
|
||||
|
||||
The formula for the $a_n$th term can be written in terms of $a_0$, or any other $0 \leq m \leq n$ with $a_n = a_m + (n-m)\cdot h$.
|
||||
|
||||
|
||||
A typical question might be: The first term of an arithmetic sequence is equal to $200$ and the common difference is equal to $-10$. Find the value of $a_{20}$. We could find this using $a_n = a_0 + n\cdot h$:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
a0, h, n = 200, -10, 20
|
||||
a0 + n * h
|
||||
```
|
||||
|
||||
More complicated questions involve an unknown first value, as with: an arithmetic sequence has a common difference equal to $10$ and its $6$th term is equal to $52$. Find its $15$th term, $a_{15}$. Here we have to answer: $a_0 + 15 \cdot 10$. Either we could find $a_0$ (using $52 = a_0 + 6\cdot(10)$) or use the above formula
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
a6, h, m, n = 52, 10, 6, 15
|
||||
a15 = a6 + (n-m)*h
|
||||
```
|
||||
|
||||
### The colon operator
|
||||
|
||||
|
||||
Rather than express sequences by the $a_0$, $h$, and $n$, `Julia` uses the starting point (`a`), the difference (`h`) and a *suggested* stopping value (`b`). That is, we need three values to specify these ranges of numbers: a `start`, a `step`, and an `endof`. `Julia` gives a convenient syntax for this: `a:h:b`. When the difference is just $1$, all numbers between the start and end are specified by `a:b`, as in
|
||||
|
||||
|
||||
```{julia}
|
||||
1:10
|
||||
```
|
||||
|
||||
But wait, nothing different printed? This is because `1:10` is efficiently stored. Basically, a recipe to generate the next number from the previous number is created and `1:10` just stores the start and end points (the step size is implicit in how this is stored) and that recipe is used to generate the set of all values. To expand the values, you have to ask for them to be `collect`ed (though this typically isn't needed in practice, as values are usually *iterated* over):
|
||||
|
||||
|
||||
```{julia}
|
||||
collect(1:10)
|
||||
```
|
||||
|
||||
When a non-default step size is needed, it goes in the middle, as in `a:h:b`. For example, counting by sevens from $1$ to $50$ is achieved by:
|
||||
|
||||
|
||||
```{julia}
|
||||
collect(1:7:50)
|
||||
```
|
||||
|
||||
Or counting down from 100:
|
||||
|
||||
|
||||
```{julia}
|
||||
collect(100:-7:1)
|
||||
```
|
||||
|
||||
In this last example, we said end with $1$, but it ended with $2$. The ending value in the range is a suggestion to go up to, but not exceed. Negative values for `h` are used to make decreasing sequences.
|
||||
|
||||
|
||||
### The range function
|
||||
|
||||
|
||||
For generating points to make graphs, a natural set of points to specify is $n$ evenly spaced points between $a$ and $b$. We can mimic creating this set with the range operation by solving for the correct step size. We have $a_0=a$ and $a_0 + (n-1) \cdot h = b$. (Why $n-1$ and not $n$?) Solving yields $h = (b-a)/(n-1)$. To be concrete we might ask for $9$ points between $-1$ and $1$:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
a, b, n = -1, 1, 9
|
||||
h = (b-a)/(n-1)
|
||||
collect(a:h:b)
|
||||
```
|
||||
|
||||
Pretty neat. If we were doing this many times - such as once per plot - we'd want to encapsulate this into a function, for example using a comprehension:
|
||||
|
||||
|
||||
```{julia}
|
||||
function evenly_spaced(a, b, n)
|
||||
h = (b-a)/(n-1)
|
||||
[a + i*h for i in 0:n-1]
|
||||
end
|
||||
```
|
||||
|
||||
Great, let's try it out:
|
||||
|
||||
|
||||
```{julia}
|
||||
evenly_spaced(0, 2pi, 5)
|
||||
```
|
||||
|
||||
Now, our implementation was straightforward, but only because it avoids somethings. Look at something simple:
|
||||
|
||||
|
||||
```{julia}
|
||||
evenly_spaced(1/5, 3/5, 3)
|
||||
```
|
||||
|
||||
It seems to work as expected. But looking just at the algorithm it isn't quite so clear:
|
||||
|
||||
|
||||
```{julia}
|
||||
1/5 + 2*1/5 # last value if h is exactly 1/5 or 0.2
|
||||
```
|
||||
|
||||
Floating point roundoff leads to the last value *exceeding* `0.6`, so should it be included? Well, here it is pretty clear it *should* be, but better to have something programmed that hits both `a` and `b` and adjusts `h` accordingly. Something which isn't subject to the vagaries of `(3/5 - 1/5)/2` not being `0.2`.
|
||||
|
||||
|
||||
Enter the base function `range` which solves this seemingly simple - but not really - task. It can use `a`, `b`, and `n`. Like the range operation, this function returns a generator which can be collected to realize the values.
|
||||
|
||||
|
||||
The number of points is specified as a third argument (though keyword arguments can be given):
|
||||
|
||||
|
||||
```{julia}
|
||||
xs = range(1/5, 3/5, 3) |> collect
|
||||
```
|
||||
|
||||
:::{.callout-note}
|
||||
## Note
|
||||
There is also the `LinRange(a, b, n)` function which can be more performant than `range`, as it doesn't try to correct for floating point errors.
|
||||
|
||||
:::
|
||||
|
||||
## Modifying sequences
|
||||
|
||||
|
||||
Now we concentrate on some more general styles to modify a sequence to produce a new sequence.
|
||||
|
||||
|
||||
### Filtering
|
||||
|
||||
The act of throwing out elements of a collection based on some condition is called *filtering*.
|
||||
|
||||
|
||||
For example, another way to get the values between $0$ and $100$ that are multiples of $7$ is to start with all $101$ values and throw out those that don't match. To check if a number is divisible by $7$, we could use the `rem` function. It gives the remainder upon division. Multiples of `7` match `rem(m, 7) == 0`. Checking for divisibility by seven is unusual enough there is nothing built in for that, but checking for division by $2$ is common, and for that, there is a built-in function `iseven`.
|
||||
|
||||
|
||||
The `filter` function does this in `Julia`; the basic syntax being `filter(predicate_function, collection)`. The "`predicate_function`" is one that returns either `true` or `false`, such as `iseven`. The output of `filter` consists of the new collection of values - those where the predicate returns `true`.
|
||||
|
||||
|
||||
To see it used, lets start with the numbers between `0` and `25` (inclusive) and filter out those that are even:
|
||||
|
||||
|
||||
```{julia}
|
||||
filter(iseven, 0:25)
|
||||
```
|
||||
|
||||
To get the numbers between $1$ and $100$ that are divisible by $7$ requires us to write a function akin to `iseven`, which isn't hard (e.g., `is_seven(x) = x%7 == 0` or if being fancy `Base.Fix2(iszero∘rem, 7)`), but isn't something we continue with just yet.
|
||||
|
||||
|
||||
For another example, here is an inefficient way to list the prime numbers between $100$ and $200$. This uses the `isprime` function from the `Primes` package
|
||||
|
||||
|
||||
```{julia}
|
||||
using Primes
|
||||
```
|
||||
|
||||
```{julia}
|
||||
filter(isprime, 100:200)
|
||||
```
|
||||
|
||||
Illustrating `filter` at this point is mainly a motivation to illustrate that we can start with a regular set of numbers and then modify or filter them. The function takes on more value once we discuss how to write predicate functions.
|
||||
|
||||
|
||||
### Comprehensions
|
||||
|
||||
|
||||
Let's return to the case of the set of even numbers between $0$ and $100$. We have many ways to describe this set:
|
||||
|
||||
|
||||
* The collection of numbers $0, 2, 4, 6 \dots, 100$, or the arithmetic sequence with step size $2$, which is returned by `0:2:100`.
|
||||
* The numbers between $0$ and $100$ that are even, that is `filter(iseven, 0:100)`.
|
||||
* The set of numbers $\{2k: k=0, \dots, 50\}$.
|
||||
|
||||
|
||||
While `Julia` has a special type for dealing with sets, we will use a vector for such a set. (Unlike a set, vectors can have repeated values, but, as vectors are more widely used, we demonstrate them.) Vectors are described more fully in a previous section, but as a reminder, vectors are constructed using square brackets: `[]` (a special syntax for [concatenation](http://docs.julialang.org/en/latest/manual/arrays/#concatenation)). Square brackets are used in different contexts within `Julia`, in this case we use them to create a *collection*. If we separate single values in our collection by commas (or semicolons), we will create a vector:
|
||||
|
||||
|
||||
```{julia}
|
||||
x = [0, 2, 4, 6, 8, 10]
|
||||
```
|
||||
|
||||
That is of course only part of the set of even numbers we want. Creating more might be tedious were we to type them all out, as above. In such cases, it is best to *generate* the values.
|
||||
|
||||
|
||||
For this simple case, a range can be used, but more generally a [comprehension](https://docs.julialang.org/en/v1/manual/arrays/#man-comprehensions) provides this ability using a construct that closely mirrors a set definition, such as $\{2k: k=0, \dots, 50\}$. The simplest use of a comprehension takes this form (as we described in the section on vectors):
|
||||
|
||||
|
||||
`[expr for variable in collection]`
|
||||
|
||||
|
||||
The expression typically involves the variable specified after the keyword `for`. The collection can be a range, a vector, or many other items that are *iterable*. Here is how the mathematical set $\{2k: k=0, \dots, 50\}$ may be generated by a comprehension:
|
||||
|
||||
|
||||
```{julia}
|
||||
[2k for k in 0:50]
|
||||
```
|
||||
|
||||
The expression is `2k`, the variable `k`, and the collection is the range of values, `0:50`. The syntax is basically identical to how the math expression is typically read aloud.
|
||||
|
||||
|
||||
For some other examples, here is how we can create the first $10$ numbers divisible by $7$:
|
||||
|
||||
|
||||
```{julia}
|
||||
[7k for k in 1:10]
|
||||
```
|
||||
|
||||
Here is how we can square the numbers between $1$ and $10$:
|
||||
|
||||
|
||||
```{julia}
|
||||
[x^2 for x in 1:10]
|
||||
```
|
||||
|
||||
To generate other progressions, such as powers of $2$, we could do:
|
||||
|
||||
|
||||
```{julia}
|
||||
[2^i for i in 1:10]
|
||||
```
|
||||
|
||||
Here are decreasing powers of $2$:
|
||||
|
||||
|
||||
```{julia}
|
||||
[1/2^i for i in 1:10]
|
||||
```
|
||||
|
||||
Sometimes, the comprehension does not produce the type of output that may be expected. This is related to `Julia`'s more limited abilities to infer types at the command line. If the output type is important, the extra prefix of `T[]` can be used, where `T` is the desired type.
|
||||
|
||||
|
||||
### Generators
|
||||
|
||||
|
||||
A typical pattern would be to generate a collection of numbers and then apply a function to them. For example, here is one way to sum the powers of $2$:
|
||||
|
||||
|
||||
```{julia}
|
||||
sum([2^i for i in 1:10])
|
||||
```
|
||||
|
||||
Conceptually this is easy to understand: one step generates the numbers, the other adds them up. Computationally it is a bit inefficient. The generator syntax allows this type of task to be done more efficiently. To use this syntax, we just need to drop the `[]`:
|
||||
|
||||
|
||||
```{julia}
|
||||
sum(2^i for i in 1:10)
|
||||
```
|
||||
|
||||
The difference being no intermediate object is created to store the collection of all values specified by the generator. Not all functions allow generators as arguments, but most common reductions do.
|
||||
|
||||
|
||||
### Filtering generated expressions
|
||||
|
||||
|
||||
Both comprehensions and generators allow for filtering through the keyword `if`. The basic pattern is
|
||||
|
||||
`[expr for variable in collection if expr]`
|
||||
|
||||
The following shows *one* way to add the prime numbers in $[1,100]$:
|
||||
|
||||
|
||||
```{julia}
|
||||
sum(p for p in 1:100 if isprime(p))
|
||||
```
|
||||
|
||||
The value on the other side of `if` should be an expression that evaluates to either `true` or `false` for a given `p` (like a predicate function, but here specified as an expression). The value returned by `isprime(p)` is such.
|
||||
|
||||
::: {.callout-note}
|
||||
In these notes we primarily use functions rather than expressions for various actions. We will see creating a function is not much more difficult than specifying an expression, though there is additional notation necessary. Generators are one very useful means to use expressions, symbolic math will be seen as another.
|
||||
:::
|
||||
|
||||
|
||||
In this example, we use the fact that `rem(k, 7)` returns the remainder found from dividing `k` by `7`, and so is `0` when `k` is a multiple of `7`:
|
||||
|
||||
|
||||
```{julia}
|
||||
sum(k for k in 1:100 if rem(k,7) == 0) ## add multiples of 7
|
||||
```
|
||||
|
||||
The same `if` can be used in a comprehension. For example, this is an alternative to `filter` for identifying the numbers divisible by `7` in a range of numbers:
|
||||
|
||||
|
||||
```{julia}
|
||||
[k for k in 1:100 if rem(k,7) == 0]
|
||||
```
|
||||
|
||||
#### Example: Making change
|
||||
|
||||
|
||||
This example of Stefan Karpinski's comes from a [blog](http://julialang.org/blog/2016/10/julia-0.5-highlights) post highlighting changes to the `Julia` language with version `v"0.5.0"`, which added features to comprehensions that made this example possible.
|
||||
|
||||
|
||||
First, a simple question: using pennies, nickels, dimes, and quarters how many different ways can we generate one dollar? Clearly $100$ pennies, or $20$ nickels, or $10$ dimes, or $4$ quarters will do this, so the answer is at least four, but how much more than four?
|
||||
|
||||
|
||||
Well, we can use a comprehension to enumerate the possibilities. This example illustrates how comprehensions and generators can involve one or more variable for the iteration. By judiciously choosing what is iterated over, the entire set can be described.
|
||||
|
||||
|
||||
First, we either have $0,1,2,3$, or $4$ quarters, or $0$, $25$ cents, $50$ cents, $75$ cents, or a dollar's worth. If we have, say, $1$ quarter, then we need to make up $75$ cents with the rest. If we had $3$ dimes, then we need to make up $45$ cents out of nickels and pennies, if we then had $6$ nickels, we know we must need $15$ pennies.
|
||||
|
||||
|
||||
The following expression shows how counting this can be done through enumeration. Here `q` is the amount contributed by quarters, `d` the amount from dimes, `n` the amount from nickels, and `p` the amount from pennies. `q` ranges over $0, 25, 50, 75, 100$ or `0:25:100`, etc. If we know that the sum of quarters, dimes, nickels contributes a certain amount, then the number of pennies must round things up to $100$.
|
||||
|
||||
|
||||
```{julia}
|
||||
ways = [(q, d, n, p)
|
||||
for q = 0:25:100
|
||||
for d = 0:10:(100 - q)
|
||||
for n = 0:5:(100 - q - d)
|
||||
for p = (100 - q - d - n)]
|
||||
length(ways)
|
||||
```
|
||||
|
||||
There are $242$ distinct cases. The first three are:
|
||||
|
||||
|
||||
```{julia}
|
||||
ways[1:3]
|
||||
```
|
||||
|
||||
The generating expression reads naturally. It introduces the use of multiple `for` statements, each subsequent one depending on the value of the previous (working left to right).
|
||||
|
||||
The cashier might like to know the number of coins, not the dollar amount:
|
||||
|
||||
```{julia}
|
||||
[amt ./ [25, 10, 5, 1] for amt in ways[1:3]]
|
||||
```
|
||||
|
||||
There are various ways to get integer values, and not floating point values. One way is to call `round`. Here though, we use the integer division operator, `div`, through its infix operator `÷`:
|
||||
|
||||
```{julia}
|
||||
[amt .÷ [25, 10, 5, 1] for amt in ways[1:3]]
|
||||
```
|
||||
|
||||
|
||||
Now suppose, we want to ensure that the amount in pennies is less than the amount in nickels, etc. We could use `filter` somehow to do this for our last answer, but using `if` allows for filtering while the events are generating. Here our condition is simply expressed: `q > d > n > p`:
|
||||
|
||||
|
||||
```{julia}
|
||||
[(q, d, n, p)
|
||||
for q = 0:25:100
|
||||
for d = 0:10:(100 - q)
|
||||
for n = 0:5:(100 - q - d)
|
||||
for p = (100 - q - d - n)
|
||||
if q > d > n > p]
|
||||
```
|
||||
|
||||
## Random numbers
|
||||
|
||||
|
||||
We have been discussing structured sets of numbers. On the opposite end of the spectrum are random numbers. `Julia` makes them easy to generate, especially random numbers chosen uniformly from $[0,1)$.
|
||||
|
||||
|
||||
* The `rand()` function returns a randomly chosen number in $[0,1)$.
|
||||
* The `rand(n)` function returns a vector of `n` randomly chosen numbers in $[0,1)$.
|
||||
|
||||
|
||||
To illustrate, this will command return a single number
|
||||
|
||||
|
||||
```{julia}
|
||||
rand()
|
||||
```
|
||||
|
||||
If the command is run again, it is almost certain that a different value will be returned:
|
||||
|
||||
|
||||
```{julia}
|
||||
rand()
|
||||
```
|
||||
|
||||
This call will return a vector of $10$ such random numbers:
|
||||
|
||||
|
||||
```{julia}
|
||||
rand(10)
|
||||
```
|
||||
|
||||
The `rand` function is easy to use. The only common source of confusion is the subtle distinction between `rand()` and `rand(1)`, as the latter is a vector of $1$ random number and the former just $1$ random number.
|
||||
|
||||
|
||||
## Questions
|
||||
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Which of these will produce the odd numbers between $1$ and $99$?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = [
|
||||
q"1:99",
|
||||
q"1:3:99",
|
||||
q"1:2:99"
|
||||
]
|
||||
answ = 3
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Which of these will create the sequence $2, 9, 16, 23, \dots, 72$?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = [q"2:7:72", q"2:9:72", q"2:72", q"72:-7:2"]
|
||||
answ = 1
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
How many numbers are in the sequence produced by `0:19:1000`?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
val = length(collect(0:19:1000))
|
||||
numericq(val)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The range operation (`a:h:b`) can also be used to countdown. Which of these will do so, counting down from `10` to `1`? (You can call `collect` to visualize the generated numbers.)
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = [
|
||||
"`10:-1:1`",
|
||||
"`10:1`",
|
||||
"`1:-1:10`",
|
||||
"`1:10`"
|
||||
]
|
||||
answ = 1
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
What is the last number generated by `1:4:7`?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
val = (1:4:7)[end]
|
||||
numericq(val)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
While the range operation can generate vectors by collecting, do the objects themselves act like vectors?
|
||||
|
||||
|
||||
Does scalar multiplication work as expected? In particular, is the result of `2*(1:5)` *basically* the same as `2 * [1,2,3,4,5]`?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
yesnoq(true)
|
||||
```
|
||||
|
||||
Does vector addition work? as expected? In particular, is the result of `(1:4) + (2:5)` *basically* the same as `[1,2,3,4]` + `[2,3,4,5]`?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
yesnoq(true)
|
||||
```
|
||||
|
||||
What if parentheses are left off? Explain the output of `1:4 + 2:5`?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = ["It is just random",
|
||||
"Addition happens prior to the use of `:` so this is like `1:(4+2):5`",
|
||||
"It gives the correct answer, a generator for the vector `[3,5,7,9]`"
|
||||
]
|
||||
answ = 2
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
How is `a:b-1` interpreted:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = ["as `a:(b-1)`", "as `(a:b) - 1`, which is `(a-1):(b-1)`"]
|
||||
answ = 1
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
An arithmetic sequence ($a_0$, $a_1 = a_0 + h$, $a_2=a_0 + 2h, \dots,$ $a_n=a_0 + n\cdot h$) is specified with a starting point ($a_0$), a step size ($h$), and a number of points $(n+1)$. This is not the case with the colon constructor which take a starting point, a step size, and a suggested last value. This is not the case a with the default for the `range` function, with signature `range(start, stop, length)`. However, the documentation for `range` shows that indeed the three values ($a_0$, $h$, and $n$) can be passed in. Which signature (from the docs) would allow this:
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
choices = [
|
||||
"`range(start, stop, length)`",
|
||||
"`range(start, stop; length, step)`",
|
||||
"`range(start; length, stop, step)`",
|
||||
"`range(;start, length, stop, step)`"]
|
||||
answer = 3
|
||||
explanation = """
|
||||
This is a somewhat vague question, but the use of `range(a0; length=n+1, step=h)` will produce the arithmetic sequence with this parameterization.
|
||||
"""
|
||||
buttonq(choices, answer; explanation)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Create the sequence $10, 100, 1000, \dots, 1,000,000$ using a list comprehension. Which of these works?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = [q"[10^i for i in 1:6]", q"[10^i for i in [10, 100, 1000]]", q"[i^10 for i in [1:6]]"]
|
||||
answ = 1
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Create the sequence $0.1, 0.01, 0.001, \dots, 0.0000001$ using a list comprehension. Which of these will work:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = [
|
||||
q"[10^-i for i in 1:7]",
|
||||
q"[(1/10)^i for i in 1:7]",
|
||||
q"[i^(1/10) for i in 1:7]"]
|
||||
answ = 2
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Evaluate the expression $x^3 - 2x + 3$ for each of the values $-5, -4, \dots, 4, 5$ using a comprehension. Which of these will work?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = [q"[x^3 - 2x + 3 for i in -5:5]", q"[x^3 - 2x + 3 for x in -(5:5)]", q"[x^3 - 2x + 3 for x in -5:5]"]
|
||||
answ = 3
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
How many prime numbers are there between $1100$ and $1200$? (Use `filter` and `isprime`)
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
val = length(filter(isprime, 1100:1200))
|
||||
numericq(val)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Which has more prime numbers the range `1000:2000` or the range `11000:12000`?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
n1 = length(filter(isprime, 1000:2000))
|
||||
n2 = length(filter(isprime, 11_000:12_000))
|
||||
booleanq(n1 > n2, labels=[q"1000:2000", q"11000:12000"])
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
We can easily add an arithmetic progression with the `sum` function. For example, `sum(1:100)` will add the numbers $1, 2, ..., 100$.
|
||||
|
||||
|
||||
What is the sum of the odd numbers between $0$ and $100$?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
val = sum(1:2:99)
|
||||
numericq(val)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The sum of the arithmetic progression $a, a+h, \dots, a+n\cdot h$ has a simple formula. Using a few cases, can you tell if this is the correct one:
|
||||
|
||||
|
||||
$$
|
||||
(n+1)\cdot a + h \cdot n(n+1)/2
|
||||
$$
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
booleanq(true, labels=["Yes, this is true", "No, this is false"])
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
A *geometric progression* is of the form $a^0, a^1, a^2, \dots, a^n$. These are easily generated by comprehensions of the form `[a^i for i in 0:n]`. Find the sum of the geometric progression $1, 2^1, 2^2, \dots, 2^{10}$.
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
as = [2^i for i in 0:10]
|
||||
val = sum(as)
|
||||
numericq(val)
|
||||
```
|
||||
|
||||
Is your answer of the form $(1 - a^{n+1}) / (1-a)$?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
yesnoq(true)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The [product](http://en.wikipedia.org/wiki/Arithmetic_progression) of the terms in an arithmetic progression has a known formula. The product can be found by an expression of the form `prod(a:h:b)`. Find the product of the terms in the sequence $1,3,5,\dots,19$.
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
val = prod(1:2:19)
|
||||
numericq(val)
|
||||
```
|
||||
|
||||
##### Question
|
||||
|
||||
Credit card numbers have a check digit to ensure data entry of a 16-digit number is correct. How does it work? The [Luhn Algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm).
|
||||
|
||||
Let's see if `4137 8947 1175 5804` is a valid credit card number?
|
||||
|
||||
First, we enter it as a value and immediately break the number into its digits:
|
||||
|
||||
```{julia}
|
||||
x = 4137_8947_1175_5804 # _ in a number is ignored by parser
|
||||
xs = digits(x)
|
||||
```
|
||||
|
||||
We reverse the order, so the first number in digits is the largest place value in `xs`
|
||||
|
||||
```{julia}
|
||||
reverse!(xs)
|
||||
```
|
||||
|
||||
Now, the 1st, 3rd, 5th, ... digit is doubled. We do this through indexing:
|
||||
|
||||
```{julia}
|
||||
for i in 1:2:length(xs)
|
||||
xs[i] = 2 * xs[i]
|
||||
end
|
||||
```
|
||||
|
||||
Numbers greater than 9, have their digits added, then all the resulting numbers are added. This can be done with a generator:
|
||||
|
||||
|
||||
```{julia}
|
||||
z = sum(sum(digits(xi)) for xi in xs)
|
||||
```
|
||||
|
||||
If this sum has a remainder of 0 when dividing by 10, the credit card number is possibly valid, if not it is definitely invalid. (The check digit is the last number and is set so that the above applied to the first 15 digits *plus* the check digit results in a multiple of 10.)
|
||||
|
||||
```{julia}
|
||||
iszero(rem(z,10))
|
||||
```
|
||||
|
||||
Darn. A typo. is `4137 8047 1175 5804` a possible credit card number?
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
booleanq(true)
|
||||
```
|
||||
@@ -22,7 +22,7 @@ nothing
|
||||
---
|
||||
|
||||
|
||||
Thinking of functions as objects themselves that can be manipulated - rather than just blackboxes for evaluation - is a major abstraction of calculus. The main operations to come: the limit *of a function*, the derivative *of a function*, and the integral *of a function* all operate on functions. Hence the idea of an [operator](http://tinyurl.com/n5gp6mf). Here we discuss manipulations of functions from pre-calculus that have proven to be useful abstractions.
|
||||
Thinking of functions as objects themselves that can be manipulated---rather than just blackboxes for evaluation---is a major abstraction of calculus. The main operations to come: the limit *of a function*, the derivative *of a function*, and the integral *of a function* all operate on functions. Hence the idea of an [operator](http://tinyurl.com/n5gp6mf). Here we discuss manipulations of functions from pre-calculus that have proven to be useful abstractions.
|
||||
|
||||
|
||||
## The algebra of functions
|
||||
@@ -141,7 +141,7 @@ The real value of composition is to break down more complicated things into a se
|
||||
### Shifting and scaling graphs
|
||||
|
||||
|
||||
It is very useful to mentally categorize functions within families. The difference between $f(x) = \cos(x)$ and $g(x) = 12\cos(2(x - \pi/4))$ is not that much - both are cosine functions, one is just a simple enough transformation of the other. As such, we expect bounded, oscillatory behaviour with the details of how large and how fast the oscillations are to depend on the specifics of the function. Similarly, both these functions $f(x) = 2^x$ and $g(x)=e^x$ behave like exponential growth, the difference being only in the rate of growth. There are families of functions that are qualitatively similar, but quantitatively different, linked together by a few basic transformations.
|
||||
It is very useful to mentally categorize functions within families. The difference between $f(x) = \cos(x)$ and $g(x) = 12\cos(2(x - \pi/4))$ is not that much---both are cosine functions, one is just a simple enough transformation of the other. As such, we expect bounded, oscillatory behaviour with the details of how large and how fast the oscillations are to depend on the specifics of the function. Similarly, both these functions $f(x) = 2^x$ and $g(x)=e^x$ behave like exponential growth, the difference being only in the rate of growth. There are families of functions that are qualitatively similar, but quantitatively different, linked together by a few basic transformations.
|
||||
|
||||
|
||||
There is a set of operations of functions, which does not really change the type of function. Rather, it basically moves and stretches how the functions are graphed. We discuss these four main transformations of $f$:
|
||||
@@ -322,7 +322,7 @@ datetime = 12 + 10/60 + 38/60/60
|
||||
delta = (newyork(266) - datetime) * 60
|
||||
```
|
||||
|
||||
This is off by a fair amount - almost $8$ minutes. Clearly a trigonometric model, based on the assumption of circular motion of the earth around the sun, is not accurate enough for precise work, but it does help one understand how summer days are longer than winter days and how the length of a day changes fastest at the spring and fall equinoxes.
|
||||
This is off by a fair amount---almost $8$ minutes. Clearly a trigonometric model, based on the assumption of circular motion of the earth around the sun, is not accurate enough for precise work, but it does help one understand how summer days are longer than winter days and how the length of a day changes fastest at the spring and fall equinoxes.
|
||||
|
||||
|
||||
##### Example: the pipeline operator
|
||||
@@ -358,7 +358,7 @@ Suppose we have a data set like the following:^[Which comes from the "Palmer Pen
|
||||
| 48.8 | 18.4 | 3733 | male | Chinstrap |
|
||||
| 47.5 | 15.0 | 5076 | male | Gentoo |
|
||||
|
||||
We might want to plot on an $x-y$ axis flipper length versus bill length but also indicate body size with a large size marker for bigger sizes.
|
||||
We might want to plot on an $x$-$y$ axis flipper length versus bill length but also indicate body size with a large size marker for bigger sizes.
|
||||
|
||||
We could do so by transforming a marker: scaling by size, then shifting it to an `x-y` position; then plotting. Something like this:
|
||||
|
||||
@@ -473,7 +473,7 @@ S(D(f))(15), f(15) - f(0)
|
||||
That is the accumulation of differences is just the difference of the end values.
|
||||
|
||||
|
||||
These two operations are discrete versions of the two main operations of calculus - the derivative and the integral. This relationship will be known as the "fundamental theorem of calculus."
|
||||
These two operations are discrete versions of the two main operations of calculus---the derivative and the integral. This relationship will be known as the "fundamental theorem of calculus."
|
||||
|
||||
|
||||
## Questions
|
||||
|
||||
@@ -269,7 +269,7 @@ plot(sin, 0, 4pi)
|
||||
The graph shows two periods. The wavy aspect of the graph is why this function is used to model periodic motions, such as the amount of sunlight in a day, or the alternating current powering a computer.
|
||||
|
||||
|
||||
From this graph - or considering when the $y$ coordinate is $0$ - we see that the sine function has zeros at any integer multiple of $\pi$, or $k\pi$, $k$ in $\dots,-2,-1, 0, 1, 2, \dots$.
|
||||
From this graph---or considering when the $y$ coordinate is $0$---we see that the sine function has zeros at any integer multiple of $\pi$, or $k\pi$, $k$ in $\dots,-2,-1, 0, 1, 2, \dots$.
|
||||
|
||||
|
||||
The cosine function is similar, in that it has the same domain and range, but is "out of phase" with the sine curve. A graph of both shows the two are related:
|
||||
@@ -693,14 +693,144 @@ atan(y, x)
|
||||
##### Example
|
||||
|
||||
|
||||
A (white) light shining through a [prism](http://tinyurl.com/y8sczg4t) will be deflected depending on the material of the prism and the angles involved (refer to the link for a figure). The relationship can be analyzed by tracing a ray through the figure and utilizing Snell's law. If the prism has index of refraction $n$ then the ray will deflect by an amount $\delta$ that depends on the angle, $\alpha$ of the prism and the initial angle ($\theta_0$) according to:
|
||||
A (white) light shining through a [dispersive prism](https://en.wikipedia.org/wiki/Dispersive_prism) will be deflected depending on the material of the prism and the angles involved. The relationship can be analyzed by tracing a ray through the figure and utilizing Snell's law which relates the angle of incidence with the angle of refraction through two different media through:
|
||||
|
||||
|
||||
$$
|
||||
\delta = \theta_0 - \alpha + \arcsin(n \sin(\alpha - \arcsin(\frac{1}{n}\sin(\theta_0)))).
|
||||
n_0 \sin(\theta_0) = n_1 \sin(\theta_1)
|
||||
$$
|
||||
|
||||
If $n=1.5$ (glass), $\alpha = \pi/3$ and $\theta_0=\pi/6$, find the deflection (in radians).
|
||||
|
||||
:::{#fig-snells-law-prism}
|
||||
```{julia}
|
||||
#| echo: false
|
||||
p1 = let
|
||||
gr()
|
||||
|
||||
plot(; empty_style..., aspect_ratio=:equal)
|
||||
n₀,n₁,n₂ = 1,3, 1
|
||||
θ₀ = pi/7
|
||||
α = pi/8
|
||||
θ₁ = asin(n₀/n₁* θ₀)
|
||||
θ₁′ = α - θ₁
|
||||
θ₂′ = asin(n₁/n₂ * θ₁′)
|
||||
θ₂ = θ₂′ - α
|
||||
|
||||
plot!([(-1,0), (1,0)]; line=(:black, 1))
|
||||
plot!([(0,-1),(0,1)]; line=(:black, 1))
|
||||
plot!([(0,1), (2tan(α),-1)]; line=(:black, 1))
|
||||
S = Shape([(0,-1),(2tan(α),-1),(0,1)])
|
||||
plot!(S, fill=(:gray80, 0.25), line=nothing)
|
||||
|
||||
|
||||
xx = tan(α)/ (1 + tan(α)*tan(θ₁))
|
||||
sl(x) = 1 - x/tan(α)
|
||||
yy = sl(xx)
|
||||
plot!(sl, xx-1/8, xx+1/9; line=(:red,3))
|
||||
plot!(x -> yy + tan(α)*(x-xx), xx-1/4, xx+1/4; line=(:red, 3))
|
||||
|
||||
|
||||
plot!([(-1,-sin(θ₀)), (0,0)]; line=(:black, 2))
|
||||
plot!([(0,0), (xx, xx*tan(θ₁))]; line=(:black, 2))
|
||||
plot!([(xx,yy), (xx + 5/8, yy - 5/8*tan(θ₂))]; line=(:black, 2))
|
||||
|
||||
|
||||
|
||||
|
||||
annotate!([
|
||||
(-1/2,1/2*sin(pi + θ₀/2), text(L"\theta_0")),
|
||||
(1/5, 1/5*sin(θ₁/2), text(L"\theta_1")),
|
||||
(2tan(α), -0.075, text(L"\theta_2")),
|
||||
(-1/2, -3/4, text(L"n_0")),
|
||||
(2tan(α/2), -3/4, text(L"n_1")),
|
||||
(1 - (1-2tan(α))/2, -3/4, text(L"n_2"))
|
||||
|
||||
])
|
||||
current()
|
||||
end
|
||||
p2 = let
|
||||
plot(; empty_style..., aspect_ratio=:equal)
|
||||
n₀,n₁,n₂ = 1,3, 1
|
||||
θ₀ = pi/7
|
||||
α = pi/8
|
||||
θ₁ = asin(n₀/n₁* θ₀)
|
||||
θ₁′ = α - θ₁
|
||||
θ₂′ = asin(n₁/n₂ * θ₁′)
|
||||
θ₂ = θ₂′ - α
|
||||
|
||||
|
||||
|
||||
xx = tan(α)/ (1 + tan(α)*tan(θ₁))
|
||||
sl(x) = 1 - x/tan(α)
|
||||
yy = sl(xx)
|
||||
plot!(sl, xx-1/8, xx+1/9; line=(:red,3))
|
||||
plot!(x -> yy + tan(α)*(x-xx), xx-1/4, xx+1/4; line=(:red, 3))
|
||||
|
||||
S = Shape([(0, sl(xx-1/8)), (xx-1/8,sl(xx-1/8)),
|
||||
(xx+1/9, sl(xx+1/9)), (0, sl(xx+1/9))])
|
||||
plot!(S, fill=(:gray80, 0.25), line=nothing)
|
||||
|
||||
#plot!([(-1,-sin(θ₀)), (0,0)]; line=(:black, 2))
|
||||
plot!([(0,0), (xx, xx*tan(θ₁))]; line=(:black, 2))
|
||||
plot!([(xx,yy), (xx + 2/8, yy - 2/8*tan(θ₂))]; line=(:black, 2))
|
||||
|
||||
annotate!([
|
||||
(1/5, .1*sin(θ₁/2), text(L"\theta_1\prime")),
|
||||
(xx + .1, 0.06, text(L"\theta_2\prime")),
|
||||
(2tan(α/2), -1/8, text(L"n_1")),
|
||||
(17/32, -1/8, text(L"n_2"))
|
||||
])
|
||||
current()
|
||||
end
|
||||
plot(p1, p2; layout=(1,2))
|
||||
```
|
||||
|
||||
Light bending through a prism. The right graphic shows the second bending.
|
||||
:::
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
plotly()
|
||||
nothing
|
||||
```
|
||||
|
||||
Following Wikipedia, we have
|
||||
|
||||
|
||||
|
||||
$$
|
||||
\theta_1 = \sin^{-1}\left( \frac{n_0}{n_1} \sin(\theta_0) \right)
|
||||
$$
|
||||
|
||||
Both $\theta_0$ and $\theta_1$ are measured with respect to the coordinate system that looks like the $x-y$ plane. The red coordinate system is used to identify the angle of incidence for the second bending. Some right-triangle geometry relates the new angle $\theta'_1$ with $\theta_1$ through $\theta'_1 = \alpha - \theta_1$. With this new angle of incidence, the angle of refraction, $\theta'_2$, satisfies:
|
||||
|
||||
$$
|
||||
n1 \sin(\theta'_1) = n2 \sin(\theta'_2)
|
||||
$$
|
||||
|
||||
Or
|
||||
|
||||
$$
|
||||
\theta'_2 = \sin^{-1}\left(\frac{n_1}{n_2}\sin(\theta'_1) \right)
|
||||
$$
|
||||
|
||||
Finally, using right-triangle geometry, the angle $\theta_2 = \theta'_2 - \alpha$ can be identified.
|
||||
|
||||
For a prism, in air, we would have $n_0 = n_2 = 1$. Letting $n_1 = n$, and combining we get
|
||||
|
||||
$$
|
||||
\begin{align*}
|
||||
\delta &= \theta_0 + \theta_2\\
|
||||
&=\theta_0 + \sin^{-1}\left(\frac{n_1}{n_2}\sin(\theta'_1) \right)- \alpha\\
|
||||
&= \theta_0 - \alpha + \sin^{-1}\left(\frac{n}{1}\sin(\alpha -\theta_1) \right)\\
|
||||
&= \theta_0 - \alpha + \sin^{-1}\left(n\sin\left(\alpha - \sin^{-1}\left( \frac{n_0}{n_1} \sin(\theta_0) \right)\right)\right)\\
|
||||
&= \theta_0 - \alpha + \sin^{-1}\left(n\sin\left(\alpha -\sin^{-1}\left( \frac{1}{n} \sin(\theta_0) \right)\right) \right)
|
||||
\end{align*}
|
||||
$$
|
||||
|
||||
If the prism has index of refraction $n$ then the ray will deviate by this amount $\delta$ that depends on the initial incidence angle, $\alpha$ of the prism and $n$.
|
||||
|
||||
When $n=1.5$ (glass), $\alpha = \pi/3$ and $\theta_0=\pi/6$, find the deflection (in radians).
|
||||
|
||||
|
||||
We have:
|
||||
@@ -795,6 +925,8 @@ plot(abs ∘ T4, -1,1, label="|T₄|")
|
||||
plot!(abs ∘ q, -1,1, label="|q|")
|
||||
```
|
||||
|
||||
We will return to this family of polynomials in the section on Orthogonal Polynomials.
|
||||
|
||||
## Hyperbolic trigonometric functions
|
||||
|
||||
|
||||
|
||||
@@ -1,554 +0,0 @@
|
||||
# Variables
|
||||
|
||||
|
||||
{{< include ../_common_code.qmd >}}
|
||||
|
||||
## Assignment
|
||||
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
#| results: "hidden"
|
||||
using CalculusWithJulia
|
||||
|
||||
nothing
|
||||
```
|
||||
|
||||
```{julia}
|
||||
#| echo: false
|
||||
imgfile = "figures/calculator.png"
|
||||
caption = "Screenshot of a calculator provided by the Google search engine."
|
||||
# ImageFile(:precalc, imgfile, caption)
|
||||
nothing
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
||||
The Google calculator has a button `Ans` to refer to the answer to the previous evaluation. This is a form of memory. The last answer is stored in a specific place in memory for retrieval when `Ans` is used. In some calculators, more advanced memory features are possible. For some, it is possible to push values onto a stack of values for them to be referred to at a later time. This proves useful for complicated expressions, say, as the expression can be broken into smaller intermediate steps to be computed. These values can then be appropriately combined. This strategy is a good one, though the memory buttons can make its implementation a bit cumbersome.
|
||||
|
||||
|
||||
With `Julia`, as with other programming languages, it is very easy to refer to past evaluations. This is done by *assignment* whereby a computed value stored in memory is associated with a name (sometimes thought of as symbol or label). The name can be used to look up the value later. Assignment does not change the value of the object being assigned, it only introduces a reference to it.
|
||||
|
||||
|
||||
Assignment in `Julia` is handled by the equals sign and takes the general form `variable_name = value`. For example, here we assign values to the variables `x` and `y`
|
||||
|
||||
|
||||
```{julia}
|
||||
x = sqrt(2)
|
||||
y = 42
|
||||
```
|
||||
|
||||
In an assignment, the right hand side is always returned, so it appears nothing has happened. However, the values are there, as can be checked by typing their name
|
||||
|
||||
|
||||
```{julia}
|
||||
x
|
||||
```
|
||||
|
||||
Just typing a variable name (without a trailing semicolon) causes the assigned value to be displayed.
|
||||
|
||||
|
||||
Variable names can be reused (or reassigned), as here, where we redefine `x`:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
x = 2
|
||||
```
|
||||
|
||||
:::{.callout-note}
|
||||
## Note
|
||||
The `Pluto` interface for `Julia` is idiosyncratic, as variables are *reactive*. This interface allows changes to a variable `x` to propagate to all other cells referring to `x`. Consequently, the variable name can only be assigned *once* per notebook **unless** the name is in some other namespace, which can be arranged by including the assignment inside a function or a `let` block.
|
||||
|
||||
:::
|
||||
|
||||
`Julia` is referred to as a "dynamic language" which means (in most cases) that a variable can be reassigned with a value of a different type, as we did with `x` where first it was assigned to a floating point value then to an integer value. (Though we meet some cases - generic functions - where `Julia` balks at reassigning a variable if the type is different.)
|
||||
|
||||
|
||||
More importantly than displaying a value, is the use of variables to build up more complicated expressions. For example, to compute
|
||||
|
||||
|
||||
$$
|
||||
\frac{1 + 2 \cdot 3^4}{5 - 6/7}
|
||||
$$
|
||||
|
||||
we might break it into the grouped pieces implied by the mathematical notation:
|
||||
|
||||
|
||||
```{julia}
|
||||
top = 1 + 2*3^4
|
||||
bottom = 5 - 6/7
|
||||
top/bottom
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
|
||||
##### Example
|
||||
|
||||
|
||||
Imagine we have the following complicated expression related to the trajectory of a [projectile](http://www.researchgate.net/publication/230963032_On_the_trajectories_of_projectiles_depicted_in_early_ballistic_woodcuts) with wind resistance:
|
||||
|
||||
|
||||
$$
|
||||
\left(\frac{g}{k v_0\cos(\theta)} + \tan(\theta) \right) t + \frac{g}{k^2}\ln\left(1 - \frac{k}{v_0\cos(\theta)} t \right)
|
||||
$$
|
||||
|
||||
Here $g$ is the gravitational constant $9.8$ and $v_0$, $\theta$ and $k$ parameters, which we take to be $200$, $45$ degrees, and $1/2$ respectively. With these values, the above expression can be computed when $t=100$:
|
||||
|
||||
|
||||
```{julia}
|
||||
g = 9.8
|
||||
v0 = 200
|
||||
theta = 45
|
||||
k = 1/2
|
||||
t = 100
|
||||
a = v0 * cosd(theta)
|
||||
(g/(k*a) + tand(theta))* t + (g/k^2) * log(1 - (k/a)*t)
|
||||
```
|
||||
|
||||
By defining a new variable `a` to represent a value that is repeated a few times in the expression, the last command is greatly simplified. Doing so makes it much easier to check for accuracy against the expression to compute.
|
||||
|
||||
|
||||
|
||||
|
||||
##### Example
|
||||
|
||||
|
||||
A common expression in mathematics is a polynomial expression, for example $-16s^2 + 32s - 12$. Translating this to `Julia` at $s =3$ we might have:
|
||||
|
||||
|
||||
```{julia}
|
||||
s = 3
|
||||
-16*s^2 + 32*s - 12
|
||||
```
|
||||
|
||||
This looks nearly identical to the mathematical expression, but we inserted `*` to indicate multiplication between the constant and the variable. In fact, this step is not needed as Julia allows numeric literals to have an implied multiplication:
|
||||
|
||||
|
||||
```{julia}
|
||||
-16s^2 + 32s - 12
|
||||
```
|
||||
|
||||
##### Example
|
||||
|
||||
A [grass swale](https://stormwater.pca.state.mn.us/index.php?title=Design_criteria_for_dry_swale_(grass_swale)) is a design to manage surface water flow resulting from a storm. Swales detain, filter, and infiltrate runoff limiting erosion in the process.
|
||||
|
||||

|
||||
|
||||
There are a few mathematical formula that describe the characteristics of swale:
|
||||
|
||||
The area is given by:
|
||||
|
||||
$$
|
||||
A = (b + d/\tan(\theta)) d
|
||||
$$
|
||||
|
||||
The *wetted* perimeter is given by
|
||||
|
||||
$$
|
||||
P = b + 2 d/\sin(\theta)
|
||||
$$
|
||||
|
||||
The *hydraulic radius* is given by
|
||||
|
||||
$$
|
||||
R = \frac{b\cdot d \sin(\theta) + d^2 \cos(\theta)}{b\sin(\theta) + 2d}.
|
||||
$$
|
||||
|
||||
Finally, the *flow quantity* is given by *Manning's* formula:
|
||||
|
||||
$$
|
||||
Q = vA = \frac{R^{2/3} S^{1/2}}{n} A, \quad R = \frac{A}{P}.
|
||||
$$
|
||||
|
||||
With $n$ being Manning's coefficient, $v$ the velocity in meters per second, and $S$ being the slope. Velocity and slope are correlated.
|
||||
|
||||
Manning's coefficient depends on the height of the vegetation in the grass swale. It is $0.025$ when the depth of flow is similar to the vegetation height.
|
||||
|
||||
Given all this, compute the flow quantity when $S = 2/90$ and $n=0.025$ for a swale with characteristics $b=1$, $\theta=\pi/4$, $d=1$.
|
||||
|
||||
```{julia}
|
||||
b, theta, d = 1, pi/4, 1
|
||||
n, S = 0.025, 2/90
|
||||
A = (b + d/tan(theta)) * d
|
||||
P = b + 2d/sin(theta)
|
||||
R = A / P
|
||||
Q = R^(2/3) * S^(1/2) / n * A
|
||||
```
|
||||
|
||||
## Where math and computer notations diverge
|
||||
|
||||
|
||||
It is important to recognize that `=` to `Julia` is not in analogy to how $=$ is used in mathematical notation. The following `Julia` code is not an equation:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
x = 3
|
||||
x = x^2
|
||||
```
|
||||
|
||||
What happens instead? The right hand side is evaluated (`x` is squared), the result is stored and bound to the variable `x` (so that `x` will end up pointing to the new value, `9`, and not the original one, `3`); finally the value computed on the right-hand side is returned and in this case displayed, as there is no trailing semicolon to suppress the output.
|
||||
|
||||
|
||||
This is completely unlike the mathematical equation $x = x^2$ which is typically solved for values of $x$ that satisfy the equation ($0$ and $1$).
|
||||
|
||||
|
||||
##### Example
|
||||
|
||||
|
||||
Having `=` as assignment is usefully exploited when modeling sequences. For example, an application of Newton's method might end up with this mathematical expression:
|
||||
|
||||
|
||||
$$
|
||||
x_{i+1} = x_i - \frac{x_i^2 - 2}{2x_i}
|
||||
$$
|
||||
|
||||
As a mathematical expression, for each $i$ this defines a new value for $x_{i+1}$ in terms of a known value $x_i$. This can be used to recursively generate a sequence, provided some starting point is known, such as $x_0 = 2$.
|
||||
|
||||
|
||||
The above might be written instead using assignment with:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
x = 2
|
||||
x = x - (x^2 - 2) / (2x)
|
||||
x = x - (x^2 - 2) / (2x)
|
||||
```
|
||||
|
||||
Repeating this last line will generate new values of `x` based on the previous one - no need for subscripts. This is exactly what the mathematical notation indicates is to be done.
|
||||
|
||||
::: {.callout-note}
|
||||
## Use of =
|
||||
|
||||
The distinction between ``=`` versus `=` is important and one area where common math notation and common computer notation diverge. The mathematical ``=`` indicates *equality*, and is often used with equations and also for assignment. Later, when symbolic math is introduced, the `~` symbol will be used to indicate an equation, though this is by convention and not part of base `Julia`. The computer syntax use of `=` is for *assignment* and *re-assignment*. Equality is tested with `==` and `===`.
|
||||
|
||||
:::
|
||||
|
||||
## Context
|
||||
|
||||
|
||||
The binding of a value to a variable name happens within some context. When a variable is assigned or referenced, the scope of the variable -- the region of code where it is accessible -- is taken into consideration.
|
||||
|
||||
For our simple illustrations, we are assigning values, as though they were typed at the command line. This stores the binding in the `Main` module. `Julia` looks for variables in this module when it encounters an expression and the value is substituted. Other uses, such as when variables are defined within a function, involve different contexts which may not be visible within the `Main` module.
|
||||
|
||||
|
||||
:::{.callout-note}
|
||||
## Note
|
||||
The `varinfo` function will list the variables currently defined in the main workspace. There is no mechanism to delete a single variable.
|
||||
|
||||
:::
|
||||
|
||||
:::{.callout-warning}
|
||||
## Warning
|
||||
**Shooting oneselves in the foot.** `Julia` allows us to locally redefine variables that are built in, such as the value for `pi` or the function object assigned to `sin`. This is called shadowing. For example, this is a perfectly valid command `x + y = 3`. However, it doesn't specify an equation, rather it *redefines* addition. At the terminal, this binding to `+` occurs in the `Main` module. This shadows that value of `+` bound in the `Base` module. Even if redefined in `Main`, the value in base can be used by fully qualifying the name, as in `Base.:+(2, 3)`. This uses the notation `module_name.variable_name` to look up a binding in a module.
|
||||
|
||||
:::
|
||||
|
||||
## Variable names
|
||||
|
||||
|
||||
`Julia` has a very wide set of possible [names](https://docs.julialang.org/en/stable/manual/variables/#Allowed-Variable-Names-1) for variables. Variables are case sensitive and their names can include many [Unicode](http://en.wikipedia.org/wiki/List_of_Unicode_characters) characters. Names must begin with a letter or an appropriate Unicode value (but not a number). There are some reserved words, such as `try` or `else` which can not be assigned to. However, many built-in names can be locally overwritten (shadowed).
|
||||
|
||||
Conventionally, variable names are lower case. For compound names, it is not unusual to see them squished together, joined with underscores, or written in camelCase.
|
||||
|
||||
|
||||
```{julia}
|
||||
value_1 = 1
|
||||
a_long_winded_variable_name = 2
|
||||
sinOfX = sind(45)
|
||||
__private = 2 # a convention
|
||||
```
|
||||
|
||||
### Unicode names
|
||||
|
||||
|
||||
`Julia` allows variable names to use Unicode identifiers. Such names allow `julia` notation to mirror that of many mathematical texts. For example, in calculus the variable $\epsilon$ is often used to represent some small number. We can assign to a symbol that looks like $\epsilon$ using `Julia`'s LaTeX input mode. Typing `\epsilon[tab]` will replace the text with the symbol within `IJulia` or the command line.
|
||||
|
||||
|
||||
```{julia}
|
||||
ϵ = 1e-10
|
||||
```
|
||||
|
||||
Entering Unicode names follows the pattern of "backslash" + LaTeX name + `[tab]` key. Some other ones that are useful are `\delta[tab]`, `\alpha[tab]`, and `\beta[tab]`, though there are [hundreds](https://github.com/JuliaLang/julia/blob/master/stdlib/REPL/src/latex_symbols.jl) of other values defined.
|
||||
|
||||
|
||||
For example, we could have defined `theta` (`\theta[tab]`) and `v0` (`v\_0[tab]`) using Unicode to make them match more closely the typeset math:
|
||||
|
||||
|
||||
```{julia}
|
||||
θ = 45; v₀ = 200
|
||||
```
|
||||
|
||||
:::{.callout-note}
|
||||
## Emojis
|
||||
There is even support for tab-completion of [emojis](https://github.com/JuliaLang/julia/blob/master/stdlib/REPL/src/emoji_symbols.jl) such as `\:snowman:[tab]` or `\:koala:[tab]`
|
||||
|
||||
:::
|
||||
|
||||
|
||||
:::{.callout-note}
|
||||
## Unicode
|
||||
These notes often use Unicode alternatives for some variable. Originally this was to avoid a requirement of `Pluto` of a single use of assigning to a variable name in a notebook without placing the assignment in a `let` block or a function body. Now, they are just for clarity through distinction.
|
||||
|
||||
:::
|
||||
|
||||
|
||||
##### Example
|
||||
|
||||
|
||||
As mentioned the value of $e$ is bound to the Unicode value `\euler[tab]` and not the letter `e`, so Unicode entry is required to access this constant. This isn't quite true. The `MathConstants` module defines `e`, as well as a few other values accessed via Unicode. When the `CalculusWithJulia` package is loaded, as will often be done in these notes, a value of `exp(1)` is assigned to `e`.
|
||||
|
||||
|
||||
## Tuple assignment
|
||||
|
||||
|
||||
It is a common task to define more than one variable. Multiple definitions can be done in one line, using semicolons to break up the commands, as with:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
a = 1; b = 2; c=3
|
||||
```
|
||||
|
||||
For convenience, `Julia` allows an alternate means to define more than one variable at a time. The syntax is similar:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
a, b, c = 1, 2, 3
|
||||
b
|
||||
```
|
||||
|
||||
This sets `a=1`, `b=2`, and `c=3`, as suggested. This construct relies on *tuple destructuring*. The expression on the right hand side forms a tuple of values. A tuple is a container for different types of values, and in this case the tuple has 3 values. When the same number of variables match on the left-hand side as those in the container on the right, the names are assigned one by one.
|
||||
|
||||
|
||||
The value on the right hand side is evaluated, then the assignment occurs. The following exploits this to swap the values assigned to `a` and `b`:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
a, b = 1, 2
|
||||
a, b = b, a
|
||||
```
|
||||
|
||||
### Example, finding the slope
|
||||
|
||||
|
||||
Find the slope of the line connecting the points $(1,2)$ and $(4,6)$. We begin by defining the values and then applying the slope formula:
|
||||
|
||||
|
||||
```{julia}
|
||||
x0, y0 = 1, 2
|
||||
x1, y1 = 4, 6
|
||||
m = (y1 - y0) / (x1 - x0)
|
||||
```
|
||||
|
||||
Of course, this could be computed directly with `(6-2) / (4-1)`, but by using familiar names for the values we can be certain we apply the formula properly.
|
||||
|
||||
|
||||
|
||||
## Questions
|
||||
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Let $a=10$, $b=2.3$, and $c=8$. Find the value of $(a-b)/(a-c)$.
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
a,b,c = 10, 2.3, 8;
|
||||
numericq((a-b)/(a-c))
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Let `x = 4`. Compute $y=100 - 2x - x^2$. What is the value:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
x = 4
|
||||
y = 100 - 2x - x^2
|
||||
numericq(y, 0.1)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
What is the answer to this computation?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| eval: false
|
||||
a = 3.2; b=2.3
|
||||
a^b - b^a
|
||||
```
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
a = 3.2; b=2.3;
|
||||
val = a^b - b^a;
|
||||
numericq(val)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
For longer computations, it can be convenient to do them in parts, as this makes it easier to check for mistakes.
|
||||
|
||||
|
||||
For example, to compute
|
||||
|
||||
|
||||
$$
|
||||
\frac{p - q}{\sqrt{p(1-p)}}
|
||||
$$
|
||||
|
||||
for $p=0.25$ and $q=0.2$ we might do:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| eval: false
|
||||
p, q = 0.25, 0.2
|
||||
top = p - q
|
||||
bottom = sqrt(p*(1-p))
|
||||
ans = top/bottom
|
||||
```
|
||||
|
||||
What is the result of the above?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
p, q = 0.25, 0.2;
|
||||
top = p - q;
|
||||
bottom = sqrt(p*(1-p));
|
||||
answ = top/bottom;
|
||||
numericq(answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Using variables to record the top and the bottom of the expression, compute the following for $x=3$:
|
||||
|
||||
|
||||
$$
|
||||
y = \frac{x^2 - 2x - 8}{x^2 - 9x - 20}.
|
||||
$$
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
x = 3
|
||||
val = (x^2 - 2x - 8)/(x^2 - 9x - 20)
|
||||
numericq(val)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Which if these is not a valid variable name (identifier) in `Julia`:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = [
|
||||
q"5degreesbelowzero",
|
||||
q"some_really_long_name_that_is_no_fun_to_type",
|
||||
q"aMiXeDcAsEnAmE",
|
||||
q"fahrenheit451"
|
||||
]
|
||||
answ = 1
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Which of these symbols is one of `Julia`'s built-in math constants?
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = [q"pi", q"oo", q"E", q"I"]
|
||||
answ = 1
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
What key sequence will produce this assignment
|
||||
|
||||
|
||||
```{julia}
|
||||
#| eval: false
|
||||
δ = 1/10
|
||||
```
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices=[
|
||||
q"\delta[tab] = 1/10",
|
||||
q"delta[tab] = 1/10",
|
||||
q"$\\delta$ = 1/10"]
|
||||
answ = 1
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
Which of these three statements will **not** be a valid way to assign three variables at once:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = [
|
||||
q"a=1, b=2, c=3",
|
||||
q"a,b,c = 1,2,3",
|
||||
q"a=1; b=2; c=3"]
|
||||
answ = 1
|
||||
radioq(choices, answ)
|
||||
```
|
||||
|
||||
###### Question
|
||||
|
||||
|
||||
The fact that assignment *always* returns the value of the right hand side *and* the fact that the `=` sign associates from right to left means that the following idiom:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| eval: false
|
||||
x = y = z = 3
|
||||
```
|
||||
|
||||
Will always:
|
||||
|
||||
|
||||
```{julia}
|
||||
#| hold: true
|
||||
#| echo: false
|
||||
choices = ["Assign all three variables at once to a value of `3`",
|
||||
"Create ``3`` linked values that will stay synced when any value changes",
|
||||
"Throw an error"
|
||||
]
|
||||
answ = 1
|
||||
radioq(choices, answ)
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user