This commit is contained in:
jverzani
2025-01-24 11:04:54 -05:00
parent ff0f8a060d
commit 92f4cba496
28 changed files with 1070 additions and 124 deletions

View File

@@ -15,6 +15,7 @@ using SymPy
using Roots
using QuadGK
using JSON
using ScatteredInterpolation
```
Also, these methods from the `Contour` package:
@@ -173,7 +174,7 @@ surface(xs, ys, 𝒇)
The `surface` function will generate the surface.
:::{.callout-note}
::: {.callout-note}
## Note
Using `surface` as a function name is equivalent to `plot(xs, ys, f, seriestype=:surface)`.
@@ -524,12 +525,97 @@ The filled contour layers on the contour lines to a heatmap:
```{julia}
#| hold: true
f(x,y) = exp(-(x^2 + y^2)/5) * sin(x) * cos(y)
xs= ys = range(-pi, pi, length=100)
xs = ys = range(-pi, pi, length=100)
contourf(xs, ys, f)
```
This function has a prominent peak and a prominent valley, around the middle of the viewing window. The nested contour lines indicate this, and the color key can be used to identify which is the peak and which the valley.
##### Example
The description of a function for the contour function is in terms of a grid of $x-y$ values and a function $f$ which gives the height, $z$. In other situations, it might make more sense to have a stream of $x-y-z$ values describing a surface. This might be the case, say, with trying to piece together a topography using a series of GPS track. To do so, one way is to take a regular grid of points and then *interpolate* $z$ values from the existing values.
The `ScatteredInterpolation.jl` package can be used to create a structure that can be used to interpolate points. The necessary pieces are the points, the sampled heights, and a method for the interpolation.
A simple example follows (inspired by a [discourse post](https://discourse.julialang.org/t/plots-contourf-and-plotlyjs-contour-behaviour-with-regard-to-the-x-y-z-input/122897/2)) where the true surface is known so that a comparison can be made is given. The two figures show the contour described by 4 paths through the space, is not as detailed but captures the general shape reasonably well.
```{julia}
f(x,y) = 3*(1-x)^2*exp(-(x^2) - (y+1)^2) -
10*(x/5 - x^3 - y^5)*exp(-x^2-y^2) -
1/3*exp(-(x+1)^2 - y^2)
r(t, a=2) = [a*cbrt(sinpi(t)), a * cbrt(cospi(t))]
ts = range(0, 2, 30)[1:end-1]
pts = vcat([[r(t,a) for t in ts] for a in [1/2, 1, 3/2, 2]]...)
samples = [f(pt...) for pt in pts]
first(zip(pts, samples),5)
```
```{julia}
using ScatteredInterpolation
itp = interpolate(Multiquadratic(), stack(pts), samples)
# make a grid
(xm,xM), (ym,yM) = extrema.(eachrow(stack(pts)))
n, m = 25, 40
xg, yg = range(xm,xM,n), range(ym, yM, m)
X = [s for t in yg, s in xg] #size(X) is (m,n)
Y = [t for t in yg, s in xg] # size(Y) is also (m,n)
gridP = stack(vec([[x, y] for (x,y) in zip(X, Y)]))
gridP = stack(vec([[x, y] for (x,y) in zip(X, Y)])) #2 x m*n - matrix; each column is a grid point
interpolated = evaluate(itp, gridP)
zg = reshape(interpolated, m, n)
p = Plots.contourf(xg, yg, zg; levels=6)
q = Plots.contourf(xg, yg, f)
plot(p,q)
```
##### Example
```{julia}
using Plots
```
##### Example
The arrangement of the data in a heatmap or contour plot depends on the underlying plotting package. A [discourse post](https://discourse.julialang.org/t/wrong-heatmap-orientation-with-plots-jl/124822/9) used this example to illustrate.
The data is a matrix
```{julia}
xy = [1 2; 3 4]
```
which has these colors mapped to their values:
```{julia}
cmap =[:red, :green, :blue, :orange]
cmap[xy]
```
@fig-plots-makie-heatmap shows on the left the image created by this command in `Plots`, and on the right the image created with the same command using `Makie`:
```{julia}
#| eval: false
heatmap(xy; colormap = cols, title="Plots", legend=false)
```
```{julia}
#| echo: false
#| layout-ncol: 2
#| label: fig-plots-makie-heatmap
#| fig-cap: "Orientation of heatmap may vary by plotting package."
p = heatmap(xy; colormap = cmap, title="Plots",
legend=false)
q = heatmap(xy'; colormap = cmap, title="Makie",
legend=false)
plot(p,q,layout=(1,2))
```
`Makie` uses the first dimension for `x` and the second for `y` (the first dimension is down the columns, then across); and `Plots` plots `x` values on the `x` axis etc. with values rising upwards and towards the left.
## Limits
@@ -785,7 +871,7 @@ Another alternative would be to hold one variable constant, and use the `derivat
partial_x(f, y) = x -> ForwardDiff.derivative(u -> f(u,y), x)
```
:::{.callout-note}
::: {.callout-note}
## Note
For vector-valued functions, we can override the syntax `'` using `Base.adjoint`, as `'` is treated as a postfix operator in `Julia` for the `adjoint` operation. The symbol `\\nabla` is also available in `Julia`, but it is not an operator, so can't be used as mathematically written `∇f` (this could be used as a name though). In `CalculusWithJulia` a definition is made so essentially `∇(f) = x -> ForwardDiff.gradient(f, x)`. It does require parentheses to be called, as in `∇(f)`.