Skip to content

Commit 85e1665

Browse files
Merge pull request #894 from AayushSabharwal/as/hc
feat: add `HomotopyContinuationProblem`
2 parents 6c6925b + 3e94722 commit 85e1665

File tree

3 files changed

+166
-3
lines changed

3 files changed

+166
-3
lines changed

src/SciMLBase.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -838,7 +838,8 @@ export remake
838838

839839
export ODEFunction, DiscreteFunction, ImplicitDiscreteFunction, SplitFunction, DAEFunction,
840840
DDEFunction, SDEFunction, SplitSDEFunction, RODEFunction, SDDEFunction,
841-
IncrementingODEFunction, NonlinearFunction, IntervalNonlinearFunction, BVPFunction,
841+
IncrementingODEFunction, NonlinearFunction, HomotopyNonlinearFunction,
842+
IntervalNonlinearFunction, BVPFunction,
842843
DynamicalBVPFunction, IntegralFunction, BatchIntegralFunction
843844

844845
export OptimizationFunction, MultiObjectiveOptimizationFunction

src/scimlfunctions.jl

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,14 @@ function DEFAULT_OBSERVED_NO_TIME(sym, u, p)
177177
error("Indexing symbol $sym is unknown.")
178178
end
179179

180+
function DEFAULT_POLYNOMIALIZE(u, p)
181+
return u
182+
end
183+
184+
function DEFAULT_UNPOLYNOMIALIZE(u, p)
185+
return (u,)
186+
end
187+
180188
function Base.summary(io::IO, prob::AbstractSciMLFunction)
181189
type_color, no_color = get_colorizers(io)
182190
print(io,
@@ -1741,6 +1749,108 @@ struct NonlinearFunction{iip, specialize, F, TMM, Ta, Tt, TJ, JVP, VJP, JP, SP,
17411749
initialization_data::ID
17421750
end
17431751

1752+
"""
1753+
$(TYPEDEF)
1754+
1755+
A representation of a polynomial nonlinear system of equations given by
1756+
1757+
```math
1758+
0 = f(polynomialize(u, p), p)
1759+
```
1760+
1761+
Designed to be used for solving with HomotopyContinuation.
1762+
1763+
## Constructor
1764+
1765+
$(TYPEDSIGNATURES)
1766+
1767+
Note that only the function `f` is required. This function should be given as `f!(du,u,p)`
1768+
or `du = f(u,p)`. See the section on `iip` for more details on in-place vs out-of-place
1769+
handling.
1770+
1771+
To provide the Jacobian function etc, `f` must be a [`NonlinearFunction`](@ref) with the
1772+
required fields populated.
1773+
1774+
## iip: In-Place vs Out-Of-Place
1775+
1776+
For more details on this argument, see the ODEFunction documentation.
1777+
1778+
## specialize: Controlling Compilation and Specialization
1779+
1780+
For more details on this argument, see the ODEFunction documentation.
1781+
1782+
## Fields
1783+
1784+
The fields of the `HomotopyNonlinearFunction` type directly match the names of the inputs.
1785+
1786+
$(FIELDS)
1787+
"""
1788+
struct HomotopyNonlinearFunction{iip, specialize, F, P, Q, D} <:
1789+
SciMLBase.AbstractSciMLFunction{iip}
1790+
"""
1791+
The polynomial function `f`. Stored as a `NonlinearFunction{iip, specialize}`. If not
1792+
provided to the constructor as a `NonlinearFunction`, it will be appropriately wrapped.
1793+
"""
1794+
f::F
1795+
"""
1796+
The nonlinear system may be a polynomial of non-polynomial functions. For example,
1797+
```math
1798+
sin^2(x^2) + 2sin(x^2) + 1 = 0
1799+
log(x + y) ^ 3 = 0.5
1800+
```
1801+
1802+
is a polynomial in `[sin(x^2), log(x + y)]`. To allow for such systems, `polynomialize`
1803+
converts the state vector of the original system (termed as an "original space" vector)
1804+
to the state vector of the polynomial system (termed as a "polynomial space" vector).
1805+
In the above example, it will be the function:
1806+
1807+
```julia
1808+
functon polynomialize(u, p)
1809+
x, y = u
1810+
return [sin(x^2), log(x + y)]
1811+
end
1812+
```
1813+
1814+
We refer to `[x, y]` as being in "original space" and `[sin(x^2), log(x + y)]` as being
1815+
in "polynomial space".
1816+
1817+
This defaults to a function which returns `u` as-is.
1818+
"""
1819+
polynomialize::P
1820+
"""
1821+
The inverse operation of `polynomialize`.
1822+
1823+
This function takes as input a vector `u′` in "polynomial space" and returns a collection of
1824+
vectors `uᵢ ∀ i ∈ {1..n}` in "original space" such that `polynomialize(uᵢ, p) == u′ ∀ i`.
1825+
A collection is returned since the mapping may not be bijective. For periodic functions
1826+
which have infinite such vectors `uᵢ`, it is up to the user to choose which ones to return.
1827+
1828+
In the aforementioned example in `polynomialize`, this function will be:
1829+
1830+
```julia
1831+
function unpolynomialize(u, p)
1832+
a, b = u
1833+
return [
1834+
[sqrt(asin(a)), exp(b) - sqrt(asin(a))],
1835+
[-sqrt(asin(a)), exp(b) + sqrt(asin(a))],
1836+
]
1837+
end
1838+
```
1839+
1840+
There are of course an infinite number of such functions due to the presence of `sin`.
1841+
This example chooses to return the roots in the interval `[-π/2, π/2]`.
1842+
"""
1843+
unpolynomialize::Q
1844+
"""
1845+
A function of the form `denominator(u, p) -> denoms` where `denoms` is an array of
1846+
denominator terms which cannot be zero. This is useful when `f` represents the
1847+
numerator of a rational function. Roots of `f` which cause any of the values in
1848+
`denoms` to be below a threshold will be excluded. Note that here `u` must be in
1849+
"polynomial space".
1850+
"""
1851+
denominator::D
1852+
end
1853+
17441854
"""
17451855
$(TYPEDEF)
17461856
"""
@@ -3899,6 +4009,33 @@ function NonlinearFunction(f; kwargs...)
38994009
end
39004010
NonlinearFunction(f::NonlinearFunction; kwargs...) = f
39014011

4012+
function HomotopyNonlinearFunction{iip, specialize}(f;
4013+
polynomialize = __has_polynomialize(f) ? f.polynomialize : DEFAULT_POLYNOMIALIZE,
4014+
unpolynomialize = __has_unpolynomialize(f) ? f.unpolynomialize :
4015+
DEFAULT_UNPOLYNOMIALIZE,
4016+
denominator = __has_denominator(f) ? f.denominator : Returns(())
4017+
) where {iip, specialize}
4018+
f = f isa NonlinearFunction ? f : NonlinearFunction{iip, specialize}(f)
4019+
4020+
if specialize === NoSpecialize
4021+
HomotopyNonlinearFunction{iip, specialize, Any, Any, Any, Any}(
4022+
f, polynomialize, unpolynomialize, denominator)
4023+
else
4024+
HomotopyNonlinearFunction{iip, specialize, typeof(f), typeof(polynomialize),
4025+
typeof(unpolynomialize), typeof(denominator)}(
4026+
f, polynomialize, unpolynomialize, denominator)
4027+
end
4028+
end
4029+
4030+
function HomotopyNonlinearFunction{iip}(f; kwargs...) where {iip}
4031+
HomotopyNonlinearFunction{iip, FullSpecialize}(f; kwargs...)
4032+
end
4033+
HomotopyNonlinearFunction{iip}(f::HomotopyNonlinearFunction; kwargs...) where {iip} = f
4034+
function HomotopyNonlinearFunction(f; kwargs...)
4035+
HomotopyNonlinearFunction{isinplace(f, 3), FullSpecialize}(f; kwargs...)
4036+
end
4037+
HomotopyNonlinearFunction(f::HomotopyNonlinearFunction; kwargs...) = f
4038+
39024039
function IntervalNonlinearFunction{iip, specialize}(f;
39034040
analytic = __has_analytic(f) ?
39044041
f.analytic :
@@ -4500,6 +4637,9 @@ function __has_initializeprobpmap(f)
45004637
has_initialization_data(f) && isdefined(f.initialization_data, :initializeprobpmap)
45014638
end
45024639
__has_initialization_data(f) = isdefined(f, :initialization_data)
4640+
__has_polynomialize(f) = isdefined(f, :polynomialize)
4641+
__has_unpolynomialize(f) = isdefined(f, :unpolynomialize)
4642+
__has_denominator(f) = isdefined(f, :denominator)
45034643

45044644
# compatibility
45054645
has_invW(f::AbstractSciMLFunction) = false
@@ -4528,6 +4668,9 @@ end
45284668
function has_initialization_data(f)
45294669
__has_initialization_data(f) && f.initialization_data !== nothing
45304670
end
4671+
has_polynomialize(f) = __has_polynomialize(f) && f.polynomialize !== nothing
4672+
has_unpolynomialize(f) = __has_unpolynomialize(f) && f.unpolynomialize !== nothing
4673+
has_denominator(f) = __has_denominator(f) && f.denominator !== nothing
45314674

45324675
function has_syms(f::AbstractSciMLFunction)
45334676
if __has_syms(f)
@@ -4601,6 +4744,13 @@ has_Wfact_t(f::JacobianWrapper) = has_Wfact_t(f.f)
46014744
has_paramjac(f::JacobianWrapper) = has_paramjac(f.f)
46024745
has_colorvec(f::JacobianWrapper) = has_colorvec(f.f)
46034746

4747+
for _fieldname in fieldnames(NonlinearFunction)
4748+
fnname = Symbol(:has_, _fieldname)
4749+
@eval function $(fnname)(f::HomotopyNonlinearFunction)
4750+
$(fnname)(f.f)
4751+
end
4752+
end
4753+
46044754
is_split_function(x) = is_split_function(typeof(x))
46054755
is_split_function(::Type) = false
46064756
function is_split_function(::Type{T}) where {T <: Union{
@@ -4692,6 +4842,17 @@ end
46924842

46934843
SymbolicIndexingInterface.constant_structure(::AbstractSciMLFunction) = true
46944844

4845+
SymbolicIndexingInterface.symbolic_container(fn::HomotopyNonlinearFunction) = fn.f
4846+
function SymbolicIndexingInterface.is_observed(fn::HomotopyNonlinearFunction, sym)
4847+
is_observed(symbolic_container(fn), sym)
4848+
end
4849+
function SymbolicIndexingInterface.observed(fn::HomotopyNonlinearFunction, sym)
4850+
SymbolicIndexingInterface.observed(symbolic_container(fn), sym)
4851+
end
4852+
function SymbolicIndexingInterface.observed(fn::HomotopyNonlinearFunction, sym::Symbol)
4853+
SymbolicIndexingInterface.observed(symbolic_container(fn), sym)
4854+
end
4855+
46954856
function Base.getproperty(x::AbstractSciMLFunction, sym::Symbol)
46964857
if __has_initialization_data(x) &&
46974858
(sym == :initializeprob || sym == :update_initializeprob! ||

test/downstream/comprehensive_indexing.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,11 @@ begin
6262
dprob = DiscreteProblem(jsys, u0_vals, tspan, p_vals)
6363
jprob = JumpProblem(jsys, deepcopy(dprob), Direct(); rng)
6464
nprob = NonlinearProblem(nsys, u0_vals, p_vals)
65+
hcprob = NonlinearProblem(HomotopyNonlinearFunction(nprob.f), nprob.u0, nprob.p)
6566
ssprob = SteadyStateProblem(osys, u0_vals, p_vals)
6667
optprob = OptimizationProblem(optsys, u0_vals, p_vals, grad = true, hess = true)
67-
problems = [oprob, sprob, dprob, jprob, nprob, ssprob, optprob]
68-
systems = [osys, ssys, jsys, jsys, nsys, osys, optsys]
68+
problems = [oprob, sprob, dprob, jprob, nprob, hcprob, ssprob, optprob]
69+
systems = [osys, ssys, jsys, jsys, nsys, nsys, osys, optsys]
6970

7071
# Creates an `EnsembleProblem` for each problem.
7172
eoprob = EnsembleProblem(oprob)

0 commit comments

Comments
 (0)