em dash; sentence case

This commit is contained in:
jverzani
2025-07-27 15:26:00 -04:00
parent c3b221cd29
commit 33c6e62d68
59 changed files with 385 additions and 243 deletions

View File

@@ -0,0 +1,15 @@
[deps]
CalculusWithJulia = "a2e0e22d-7d4c-5312-9169-8b992201a882"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
Measures = "442fdcdd-2543-5da2-b0f3-8c86c306513e"
Mustache = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70"
PlotlyBase = "a03496cd-edff-5a9b-9e67-9cda94a718b5"
PlotlyKaleido = "f2990250-8cf9-495f-b13a-cce12b45703c"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
Primes = "27ebfcd6-29c5-5fa9-bf4b-fb8fc14df3ae"
QuizQuestions = "612c44de-1021-4a21-84fb-7261cf5eb2d4"
SymPy = "24249f21-da20-56a4-8eb1-6a02cf4ae2e6"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
TextWrap = "b718987f-49a8-5099-9789-dcd902bef87d"

View File

@@ -0,0 +1 @@
../basics.qmd

1211
quarto/basics/calculator.qmd Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,623 @@
# 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)
```

16
quarto/basics/make_pdf.jl Normal file
View File

@@ -0,0 +1,16 @@
module Make
# makefile for generating typst pdfs
# per directory usage
dir = "basics"
files = ("calculator",
"variables",
"numbers_types",
"logical_expressions",
"vectors",
"ranges",
)
include("../_make_pdf.jl")
main()
end

View File

@@ -0,0 +1,685 @@
# 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)
```

751
quarto/basics/ranges.qmd Normal file
View File

@@ -0,0 +1,751 @@
# 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)
```

554
quarto/basics/variables.qmd Normal file
View File

@@ -0,0 +1,554 @@
# 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
```
![Screenshot of a calculator provided by the Google search engine.](figures/calculator.png)
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.
![Swale cross section](figures/swale.png)
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)
```

1244
quarto/basics/vectors.qmd Normal file

File diff suppressed because it is too large Load Diff