Skip to content

Commit 1d2e04b

Browse files
authored
Merge pull request #31 from JuliaHomotopyContinuation/custom-homotopy-guide
add new guide
2 parents 013d7cc + d21c81a commit 1d2e04b

File tree

2 files changed

+368
-0
lines changed

2 files changed

+368
-0
lines changed

content/guides/custom-homotopy.md

Lines changed: 368 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,368 @@
1+
+++
2+
title = "Implement a custom homotopy"
3+
description = "How to set up customized homotopies"
4+
weight = 100
5+
draft = false
6+
toc = true
7+
bref = "How to set up a custom homotopy"
8+
group = "feature-guide"
9+
+++
10+
11+
12+
This guide explains how to implement a homotopy of the form
13+
$$H(x,t).$$
14+
In `HomotopyContinuation.jl` a homotopy is a `struct` for which one must implement a few (in-place) functions, namely
15+
16+
```julia
17+
ModelKit.evaluate!
18+
ModelKit.evaluate_and_jacobian!
19+
ModelKit.taylor!
20+
```
21+
22+
As the names suggest, these functions
23+
24+
* evaluate $H(x,t)$ at $(x,t)$,
25+
* evaluate $H(x,t)$ and compute the jacobian at $(x,t)$, and
26+
* compute the taylor expansion of $H(x,t)$ with respect to $t$ at $(x,t)$.
27+
28+
In fact, $H(x,t)$ does not even need to be a polynomial function, neither in $x$ nor $t$. The algorithm only depends the implementation of these three functions and tracks $t$ from $t=1$ to $t=0$.
29+
30+
<h3 class="section-head" id="homotopies"><a href="#homotopies">Implementing an example homotopy</a></h3>
31+
32+
Let us illustrate how this works in an example. We want to implement a homotopy that, given systems of polynomials $F(x)$ and $G(x)$, is given by
33+
34+
$$H(x,t) = \gamma\cdot \sin(t\cdot \pi/2)\cdot G(x) + \cos(t\cdot \pi/2) \cdot F(x), \quad 0\leq t\leq 1,$$
35+
36+
where $\gamma \in\mathbb C$.
37+
38+
In this case, we have $H(x,1)=G(x)$ and $H(x,0)=F(x)$. That is, $G(x)$ is our start system and $F(x)$ our target system.
39+
40+
This is similar to a [straight line homotopy](https://www.juliahomotopycontinuation.org/HomotopyContinuation.jl/stable/homotopies/#StraightLineHomotopy) $H(x,t) = \gamma \cdot t \cdot G(x) + (1-t)\cdot F(x)$.
41+
We copy the [code](https://github.com/JuliaHomotopyContinuation/HomotopyContinuation.jl/blob/main/src/homotopies/straight_line_homotopy.jl) for a `StraightLineHomotopy`, adapt it to our setting and call the result `TrigonometricHomotopy`.
42+
43+
In the following, we explain some of the implemented functions, but not all. The complete code is at the end of this guide.
44+
45+
The `struct` that defines our `TrigonometricHomotopy` could look like this:
46+
47+
```julia
48+
using HomotopyContinuation
49+
50+
struct TrigonometricHomotopy{S<:AbstractSystem, T<:AbstractSystem} <: AbstractHomotopy
51+
start::S
52+
target::T
53+
γ::ComplexF64
54+
half_pi::Float64
55+
56+
u::Vector{ComplexF64}
57+
::Vector{ComplexF64}
58+
::Vector{ComplexF64}
59+
U::Matrix{ComplexF64}
60+
61+
dv_start::TaylorVector{5,ComplexF64}
62+
dv_target::TaylorVector{5,ComplexF64}
63+
end
64+
```
65+
Here, `start` and `target` are start and target system, respectively. We also save $\pi/24 in `half_pi` so that we don't need to compute it all the time. The remaining entries are for caching.
66+
67+
68+
The `evaluate!` function comes next:
69+
```julia
70+
function ModelKit.evaluate!(
71+
u,
72+
H::TrigonometricHomotopy,
73+
x::Vector{ComplexF64},
74+
t,
75+
p = nothing,
76+
)
77+
evaluate!(H.v̄, H.start, x)
78+
evaluate!(H.ū, H.target, x)
79+
80+
t0 = H.half_pi * t
81+
ts, tt = H.γ .* sin(t0), cos(t0)
82+
for i = 1:size(H, 1)
83+
@inbounds u[i] = ts * H.v̄[i] + tt * H.ū[i]
84+
end
85+
end
86+
```
87+
Here, `ts` and `tt` are the coefficients at $t$ of $G$ and $F$, respectively. Notice that the coefficient of $G$ is indeed $\gamma \cdot \sin(t \cdot \pi/2)$ and the coefficient of $F$ is $\cos(t \cdot \pi/2)$.
88+
89+
The `evaluate_and_jacobian!` is similar, but includes the Jacobian:
90+
```julia
91+
function ModelKit.evaluate_and_jacobian!(
92+
u,
93+
U,
94+
H::TrigonometricHomotopy,
95+
x::AbstractVector{T},
96+
t,
97+
p = nothing,
98+
) where {T}
99+
evaluate_and_jacobian!(u, U, H.start, x)
100+
evaluate_and_jacobian!(H.u, H.U, H.target, x)
101+
102+
t0 = H.half_pi * t
103+
ts, tt = H.γ .* sin(t0), cos(t0)
104+
for i = 1:size(H, 1)
105+
@inbounds u[i] = ts * u[i] + tt * H.u[i]
106+
end
107+
for j = 1:size(H, 2), i = 1:size(H, 1)
108+
@inbounds U[i, j] = ts * U[i, j] + tt * H.U[i, j]
109+
end
110+
nothing
111+
end
112+
```
113+
114+
Finally, we implement `ModelKit.taylor!`:
115+
```julia
116+
function ModelKit.taylor!(u, ::Val{1}, H::TrigonometricHomotopy, x, t)
117+
evaluate!(u, H.start, x)
118+
evaluate!(H.u, H.target, x)
119+
120+
t0 = H.half_pi * t
121+
ts, tt = H.γ * cos(t0) * H.half_pi, -sin(t0) * H.half_pi
122+
for i = 1:size(H, 1)
123+
@inbounds u[i] = ts * u[i] + tt * H.u[i]
124+
end
125+
u
126+
end
127+
```
128+
Here, the coefficients are given by differentiating $\gamma \sin(t\cdot \pi/2)$ and $\cos(t\cdot \pi/2)$ at $t$. The input `::Val{1}` indicated that this function returns the first derivative with respect to $t$. We also have to implement higher order derivatives (see the complete code at the end of this guide).
129+
130+
<h3 class="section-head" id="homotopies"><a href="#homotopies">Running our custom homotopy</a></h3>
131+
132+
Let us use our homotopy on the following data:
133+
134+
$$G(x) = \begin{pmatrix} x^2 - 1\\\ y^2 - 1\end{pmatrix}, \\quad F(x) = \begin{pmatrix} y^2 + xy + 1\\\ -x^2 + x + 2y - 2\end{pmatrix}$$
135+
136+
choosing $\gamma\in\mathbb C$ at random.
137+
138+
```julia
139+
@var x y
140+
G = System([x^2 - 1; y^2 - 1])
141+
F = System([y^2 + x * y + 1; -x^2 + x + 2y - 2])
142+
143+
γ = exp(rand() * 2 * pi * im)
144+
H = TrigonometricHomotopy(G, F; γ = γ)
145+
```
146+
147+
Now, `H` is a `TrigonometricHomotopy` and we can track the zeros of $G$ from $t=1$ to $t=0$ along this homotopy.
148+
149+
```julia
150+
julia> starts = [[1,1], [1,-1], [-1,1], [-1,-1]]
151+
julia> S = solve(H, starts)
152+
Result with 4 solutions
153+
=======================
154+
4 paths tracked
155+
4 non-singular solutions (0 real)
156+
• random_seed: nothing
157+
```
158+
159+
The computation was successful and all $4$ zeros of $F$ were computed!
160+
161+
162+
<h3 class="section-head" id="homotopies"><a href="#homotopies">Comparing with a straight line homotopy</a></h3>
163+
164+
Let us compare our newly implemented homotopy with a `StraightLineHomotopy`.
165+
```
166+
H_SL = StraightLineHomotopy(G, F; γ = γ)
167+
```
168+
169+
We wish to plot every single point tracked along the way. For this, we can use the following code:
170+
171+
```
172+
T = Tracker(H)
173+
T_SL = Tracker(H_SL)
174+
175+
pts = Vector{ComplexF64}()
176+
for (x, t) in iterator(T, starts[1], 1.0, 0.0)
177+
push!(pts, x[1])
178+
end
179+
180+
pts_SL = Vector{ComplexF64}()
181+
for (x, t) in iterator(T_SL, starts[1], 1.0, 0.0)
182+
push!(pts_SL, x[1])
183+
end
184+
```
185+
186+
Now, `pts` contains the list of points tracked along our `TrigonometricHomotopy` when starting at `starts[1] = [1,1]`. Similarly, `pts_SL` contains the points tracked along a `StraightLineHomotopy`.
187+
188+
We plot the result:
189+
190+
```
191+
using Plots
192+
193+
scatter(real(pts), imag(pts), markercolor = :steelblue, markersize = 10, legend = false)
194+
scatter!(real(pts_SL), imag(pts_SL), markercolor = :indianred, markersize = 10)
195+
for i in 2:length(pts)
196+
v = pts[i] - pts[i-1]
197+
a = pts[i-1:i] + [0.25 .* v; -0.25 .* v]
198+
plot!(real(a) , imag(a) , linecolor = :black, linewidth = 2, arrow=true, opacity = 0.75)
199+
end
200+
plot!()
201+
```
202+
203+
<p style="text-align:center;"><img src="/images/tracking.png" width="500px"/></p>
204+
205+
The blue points have been tracked by our `TrigonometricHomotopy` while the red points have been tracked by the `StraightLineHomotopy`.
206+
As we can see, both homotopies start and arrive at the same point, but the intermediate points are different.
207+
208+
209+
210+
<h3 class="section-head" id="homotopies"><a href="#homotopies">The complete code for our example</a></h3>
211+
212+
213+
```julia
214+
struct TrigonometricHomotopy{S<:AbstractSystem, T<:AbstractSystem} <: AbstractHomotopy
215+
start::S
216+
target::T
217+
γ::ComplexF64
218+
half_pi::Float64
219+
220+
u::Vector{ComplexF64}
221+
::Vector{ComplexF64}
222+
::Vector{ComplexF64}
223+
U::Matrix{ComplexF64}
224+
225+
dv_start::TaylorVector{5,ComplexF64}
226+
dv_target::TaylorVector{5,ComplexF64}
227+
end
228+
229+
function TrigonometricHomotopy(
230+
start::System,
231+
target::System;
232+
kwargs...,
233+
)
234+
TrigonometricHomotopy(
235+
fixed(start),
236+
fixed(target);
237+
kwargs...,
238+
)
239+
end
240+
function TrigonometricHomotopy(
241+
start::AbstractSystem,
242+
target::AbstractSystem;
243+
γ = 1.0,
244+
gamma = γ,
245+
)
246+
size(start) == size(target) || throw(
247+
ArgumentError(
248+
"Start and target do not have the same size, got $(size(start)) and $(size(target))",
249+
),
250+
)
251+
252+
m, n = size(start)
253+
u = zeros(ComplexF64, m)
254+
= zeros(ComplexF64, m)
255+
= zeros(ComplexF64, m)
256+
U = zeros(ComplexF64, m, n)
257+
half_pi = π / 2
258+
259+
dv_start = TaylorVector{5}(ComplexF64, m)
260+
dv_target = TaylorVector{5}(ComplexF64, m)
261+
262+
TrigonometricHomotopy(
263+
start,
264+
target,
265+
ComplexF64(gamma),
266+
half_pi,
267+
u,
268+
ū,
269+
v̄,
270+
U,
271+
dv_start,
272+
dv_target,
273+
)
274+
end
275+
276+
Base.size(H::TrigonometricHomotopy) = size(H.start)
277+
278+
function Base.show(io::IO, mime::MIME"text/plain", H::TrigonometricHomotopy)
279+
println(io, typeof(H), ":")
280+
println(io, "γ: ", H.γ)
281+
println(io, "\nG: ")
282+
show(io, mime, H.start)
283+
println(io, "\n\nF:")
284+
show(io, mime, H.target)
285+
end
286+
287+
function ModelKit.evaluate!(
288+
u,
289+
H::TrigonometricHomotopy,
290+
x::Vector{ComplexF64},
291+
t,
292+
p = nothing,
293+
)
294+
evaluate!(H.v̄, H.start, x)
295+
evaluate!(H.ū, H.target, x)
296+
297+
t0 = H.half_pi * t
298+
ts, tt = H.γ .* sin(t0), cos(t0)
299+
for i = 1:size(H, 1)
300+
@inbounds u[i] = ts * H.v̄[i] + tt * H.ū[i]
301+
end
302+
end
303+
304+
function ModelKit.evaluate!(u, H::TrigonometricHomotopy, x, t, p = nothing)
305+
evaluate!(u, H.start, x)
306+
evaluate!(H.u, H.target, x)
307+
308+
t0 = H.half_pi * t
309+
ts, tt = H.γ .* sin(t0), cos(t0)
310+
for i = 1:size(H, 1)
311+
@inbounds u[i] = ts * u[i] + tt * H.u[i]
312+
end
313+
u
314+
end
315+
316+
function ModelKit.evaluate_and_jacobian!(
317+
u,
318+
U,
319+
H::TrigonometricHomotopy,
320+
x::AbstractVector{T},
321+
t,
322+
p = nothing,
323+
) where {T}
324+
evaluate_and_jacobian!(u, U, H.start, x)
325+
evaluate_and_jacobian!(H.u, H.U, H.target, x)
326+
327+
t0 = H.half_pi * t
328+
ts, tt = H.γ .* sin(t0), cos(t0)
329+
for i = 1:size(H, 1)
330+
@inbounds u[i] = ts * u[i] + tt * H.u[i]
331+
end
332+
for j = 1:size(H, 2), i = 1:size(H, 1)
333+
@inbounds U[i, j] = ts * U[i, j] + tt * H.U[i, j]
334+
end
335+
nothing
336+
end
337+
338+
function ModelKit.taylor!(u, ::Val{1}, H::TrigonometricHomotopy, x, t)
339+
evaluate!(u, H.start, x)
340+
evaluate!(H.u, H.target, x)
341+
342+
t0 = H.half_pi * t
343+
ts, tt = H.γ * cos(t0) * H.half_pi, -sin(t0) * H.half_pi
344+
for i = 1:size(H, 1)
345+
@inbounds u[i] = ts * u[i] + tt * H.u[i]
346+
end
347+
u
348+
end
349+
350+
function ModelKit.taylor!(
351+
u,
352+
v::Val{K},
353+
H::TrigonometricHomotopy,
354+
tx::TaylorVector,
355+
t,
356+
) where {K}
357+
taylor!(H.dv_start, v, H.start, tx)
358+
taylor!(H.dv_target, v, H.target, tx)
359+
360+
t0 = H.half_pi * t
361+
for i = 1:size(H, 1)
362+
start = H.γ * (cos(t0) * H.half_pi * H.dv_start[i, K] + sin(t0) * H.dv_start[i, K+1])
363+
target = cos(t0) * H.dv_target[i, K+1] - sin(t0) * H.half_pi * H.dv_target[i, K]
364+
u[i] = start + target
365+
end
366+
u
367+
end
368+
```

static/images/tracking.png

164 KB
Loading

0 commit comments

Comments
 (0)