├── .gitignore ├── docs ├── src │ └── index.md ├── Project.toml └── make.jl ├── test ├── test_canonical.jl ├── runtests.jl ├── test_readme.jl ├── test_applications.jl ├── test_common.jl ├── test_domain_simplex.jl ├── test_generic_domain.jl └── test_setoperations.jl ├── .github └── workflows │ ├── TagBot.yml │ ├── CompatHelper.yml │ ├── ci.yml │ └── docs.yml ├── Project.toml ├── src ├── applications │ ├── coordinates.jl │ ├── random.jl │ └── rotation.jl ├── maps │ ├── lazy.jl │ ├── inverse.jl │ ├── arithmetics.jl │ ├── isomorphism.jl │ ├── map.jl │ ├── basic.jl │ ├── jacobian.jl │ ├── product.jl │ └── composite.jl ├── domains │ ├── numbers.jl │ ├── point.jl │ ├── boundingbox.jl │ ├── indicator.jl │ ├── trivial.jl │ ├── levelset.jl │ ├── simplex.jl │ └── cube.jl ├── generic │ ├── broadcast.jl │ ├── canonical.jl │ ├── lazy.jl │ ├── domain.jl │ ├── mapped.jl │ ├── productdomain.jl │ └── setoperations.jl ├── DomainSets.jl └── util │ └── common.jl ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Manifest.toml 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /docs/src/index.md: -------------------------------------------------------------------------------- 1 | # DomainSets.jl Documentation 2 | 3 | This is only a temporary document for now. 4 | -------------------------------------------------------------------------------- /docs/Project.toml: -------------------------------------------------------------------------------- 1 | [deps] 2 | Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" 3 | DomainSets = "5b8099bc-c8ec-5219-889f-1d9e522a28bf" 4 | -------------------------------------------------------------------------------- /test/test_canonical.jl: -------------------------------------------------------------------------------- 1 | @testset "canonical domains" begin 2 | @test canonicaldomain(2..3) isa ChebyshevInterval 3 | @test canonicaldomain(2..3) == ChebyshevInterval{Float64}() 4 | @test mapto(UnitInterval(), UnitInterval()) isa IdentityMap 5 | @test mapto(UnitInterval(), ChebyshevInterval()) isa AffineMap 6 | end 7 | -------------------------------------------------------------------------------- /docs/make.jl: -------------------------------------------------------------------------------- 1 | using Documenter, DomainSets 2 | 3 | makedocs( 4 | doctest = false, 5 | clean = true, 6 | format = Documenter.HTML(), 7 | sitename = "DomainSets.jl", 8 | authors = "volunteers wanted", 9 | pages = Any[ 10 | "Home" => "index.md" 11 | ] 12 | ) 13 | 14 | 15 | deploydocs( 16 | repo = "github.com/JuliaApproximation/DomainSets.jl.git" 17 | ) 18 | -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- 1 | using Test, LinearAlgebra, StaticArrays, Random, StableRNGs 2 | 3 | using DomainSets 4 | using CompositeTypes.Indexing 5 | 6 | include("test_common.jl") 7 | include("test_maps.jl") 8 | include("test_generic_domain.jl") 9 | include("test_specific_domains.jl") 10 | include("test_canonical.jl") 11 | include("test_setoperations.jl") 12 | include("test_applications.jl") 13 | include("test_readme.jl") 14 | -------------------------------------------------------------------------------- /.github/workflows/TagBot.yml: -------------------------------------------------------------------------------- 1 | name: TagBot 2 | on: 3 | issue_comment: # THIS BIT IS NEW 4 | types: 5 | - created 6 | workflow_dispatch: 7 | jobs: 8 | TagBot: 9 | # THIS 'if' LINE IS NEW 10 | if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' 11 | # NOTHING BELOW HAS CHANGED 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: JuliaRegistries/TagBot@v1 15 | with: 16 | token: ${{ secrets.GITHUB_TOKEN }} 17 | ssh: ${{ secrets.DOCUMENTER_KEY }} 18 | -------------------------------------------------------------------------------- /Project.toml: -------------------------------------------------------------------------------- 1 | name = "DomainSets" 2 | uuid = "5b8099bc-c8ec-5219-889f-1d9e522a28bf" 3 | version = "0.5.13" 4 | 5 | [deps] 6 | CompositeTypes = "b152e2b5-7a66-4b01-a709-34e65c35f657" 7 | IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" 8 | LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" 9 | Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" 10 | StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" 11 | Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" 12 | 13 | [compat] 14 | CompositeTypes = "0.1.2" 15 | IntervalSets = "0.5, 0.6, 0.7" 16 | StaticArrays = "0.12.2, 1" 17 | StableRNGs = "1" 18 | julia = "1.6" 19 | 20 | [extras] 21 | Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" 22 | StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" 23 | 24 | [targets] 25 | test = ["Test", "StableRNGs"] 26 | -------------------------------------------------------------------------------- /.github/workflows/CompatHelper.yml: -------------------------------------------------------------------------------- 1 | name: CompatHelper 2 | 3 | on: 4 | schedule: 5 | - cron: '00 * * * *' 6 | 7 | jobs: 8 | build: 9 | runs-on: ${{ matrix.os }} 10 | strategy: 11 | matrix: 12 | julia-version: [1.2.0] 13 | julia-arch: [x86] 14 | os: [ubuntu-latest] 15 | steps: 16 | - uses: julia-actions/setup-julia@latest 17 | with: 18 | version: ${{ matrix.julia-version }} 19 | - name: Install dependencies 20 | run: julia -e 'using Pkg; Pkg.add(Pkg.PackageSpec(name = "CompatHelper", url = "https://github.com/bcbi/CompatHelper.jl.git"))' 21 | - name: CompatHelper.main 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | JULIA_DEBUG: CompatHelper 25 | run: julia -e 'using CompatHelper; CompatHelper.main()' 26 | -------------------------------------------------------------------------------- /src/applications/coordinates.jl: -------------------------------------------------------------------------------- 1 | 2 | "A `DomainPoint` is a point which is an element of a domain by construction." 3 | abstract type DomainPoint{T} end 4 | 5 | in(p::DomainPoint, d::Domain) = domain(p) == d || in(point(p), d) 6 | 7 | 8 | ## Points on a sphere 9 | 10 | "A point on the unit sphere." 11 | abstract type SpherePoint{T} <: DomainPoint{T} end 12 | 13 | domain(p::SpherePoint{T}) where {T<:StaticTypes} = UnitSphere{T}() 14 | domain(p::SpherePoint{T}) where {T<:AbstractVector} = UnitSphere{T}(length(point(p))) 15 | 16 | "A point on the unit sphere represented by a standard Euclidean vector." 17 | struct EuclideanSpherePoint{T} <: SpherePoint{T} 18 | x :: T 19 | end 20 | point(p::EuclideanSpherePoint) = p.x 21 | 22 | 23 | "A point on the unit sphere represented in spherical coordinates." 24 | struct SphericalCoordinate{T} <: SpherePoint{SVector{3,T}} 25 | θ :: T # inclination or polar angle 26 | ϕ :: T # azimuthal angle 27 | end 28 | 29 | point(p::SphericalCoordinate) = SVector(sin(p.θ)*cos(p.ϕ), sin(p.θ)*sin(p.ϕ), cos(p.θ)) 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Daan Huybrechs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | - push 4 | - pull_request 5 | jobs: 6 | test: 7 | name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | version: 13 | - '1.6' 14 | - '1' 15 | os: 16 | - ubuntu-latest 17 | - macOS-latest 18 | - windows-latest 19 | arch: 20 | - x64 21 | steps: 22 | - uses: actions/checkout@v2 23 | - uses: julia-actions/setup-julia@v1 24 | with: 25 | version: ${{ matrix.version }} 26 | arch: ${{ matrix.arch }} 27 | - uses: actions/cache@v1 28 | env: 29 | cache-name: cache-artifacts 30 | with: 31 | path: ~/.julia/artifacts 32 | key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} 33 | restore-keys: | 34 | ${{ runner.os }}-test-${{ env.cache-name }}- 35 | ${{ runner.os }}-test- 36 | ${{ runner.os }}- 37 | - uses: julia-actions/julia-buildpkg@v1 38 | - uses: julia-actions/julia-runtest@v1 39 | - uses: julia-actions/julia-processcoverage@v1 40 | - uses: codecov/codecov-action@v1 41 | with: 42 | file: lcov.info 43 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - 'master' 8 | tags: '*' 9 | release: 10 | types: [published] 11 | 12 | jobs: 13 | build: 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | julia-version: [1] 18 | os: [ubuntu-latest] 19 | steps: 20 | - uses: actions/checkout@v2 21 | - uses: julia-actions/setup-julia@latest 22 | with: 23 | version: ${{ matrix.julia-version }} 24 | - name: Cache artifacts 25 | uses: actions/cache@v1 26 | env: 27 | cache-name: cache-artifacts 28 | with: 29 | path: ~/.julia/artifacts 30 | key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} 31 | restore-keys: | 32 | ${{ runner.os }}-test-${{ env.cache-name }}- 33 | ${{ runner.os }}-test- 34 | ${{ runner.os }}- 35 | - name: Install dependencies 36 | run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' 37 | - name: Build and deploy 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # If authenticating with SSH deploy key 41 | run: julia --project=docs/ docs/make.jl 42 | -------------------------------------------------------------------------------- /test/test_readme.jl: -------------------------------------------------------------------------------- 1 | # Make sure the examples in the README continue to function 2 | 3 | @testset "examples" begin 4 | using DomainSets, StaticArrays 5 | @test repr(UnitInterval()) == "0.0..1.0 (Unit)" 6 | @test repr(ChebyshevInterval()) == "-1.0..1.0 (Chebyshev)" 7 | @test repr(HalfLine()) == "0.0..Inf (closed–open) (HalfLine)" 8 | 9 | using DomainSets: × 10 | @test repr((-1..1) × (0..3) × (4.0..5.0)) == "(-1.0..1.0) × (0.0..3.0) × (4.0..5.0)" 11 | @test SVector(1,2) in (-1..1) × (0..3) 12 | 13 | @test SVector(0,0,1.0) in UnitSphere(Val(3)) 14 | @test [0.0,1.0,0.0,0.0] in UnitSphere(4) 15 | @test SVector(1,0) in UnitCircle() 16 | 17 | @test SVector(0.1,0.2,0.3) in UnitBall(Val(3)) 18 | @test [0.1,0.2,0.3,-0.1] in UnitBall(4) 19 | @test SVector(0.1,0.2) in UnitDisk() 20 | 21 | @test 1:5 in ProductDomain([0..i for i in 1:5]) 22 | @test ("a", 0.4) ∈ ProductDomain(["a","b"], 0..1) 23 | 24 | d = UnitCircle() ∪ 2UnitCircle() 25 | @test in.([SVector(1,0),SVector(0,2), SVector(1.5,1.5)], Ref(d)) == [1,1,0] 26 | d = UnitCircle() ∩ (2UnitCircle() .+ SVector(1.0,0.0)) 27 | @test !(SVector(1,0) in d) 28 | @test SVector(-1,0) in d 29 | 30 | d = LevelSet{SVector{2,Float64}}(prod, 1.0) 31 | @test [0.5,2] ∈ d 32 | 33 | d = IndicatorFunction{Float64}( t -> cos(t) > 0) 34 | @test (0.5 ∈ d, 3.1 ∈ d) == (true, false) 35 | 36 | d = Domain(x>0 for x in -1..1) 37 | @test (0.5 ∈ d, -0.5 ∈ d) == (true, false) 38 | 39 | d = Domain( x*y > 0 for (x,y) in UnitDisk()) 40 | @test ([0.2, 0.3] ∈ d, [0.2, -0.3] ∈ d) == (true, false) 41 | 42 | d = Domain( x+y+z > 0 for (x,y,z) in ProductDomain(UnitDisk(), 0..1)) 43 | @test [0.3,0.2,0.5] ∈ d 44 | end 45 | -------------------------------------------------------------------------------- /src/applications/random.jl: -------------------------------------------------------------------------------- 1 | Random.gentype(::Type{<:Domain{T}}) where T = T 2 | 3 | Base.rand(rng::AbstractRNG, s::Random.SamplerTrivial{<:ProductDomain}) = toexternalpoint(s[], map(i->(rand(rng, i)), factors(s[]))) 4 | 5 | Base.rand(rng::AbstractRNG, s::Random.SamplerTrivial{<:SimpleLazyDomain}) = toexternalpoint(s[], rand(rng, superdomain(s[]))) 6 | 7 | function Base.rand(rng::AbstractRNG, s::Random.SamplerTrivial{<:Ball}) 8 | # Technical details: http://extremelearning.com.au/how-to-generate-uniformly-random-points-on-n-spheres-and-n-balls/ 9 | 10 | b = s[] 11 | 12 | # for low-dimensional balls, use rejection sampling - acceptance rate is at least 52% 13 | if dimension(b) <= 3 14 | bb = boundingbox(b) 15 | while true 16 | r = rand(rng, bb) 17 | if r in b 18 | return r 19 | end 20 | end 21 | 22 | # for higher dimensional balls, use the "Mueller" method 23 | else 24 | u = randn_dimension(rng, eltype(b), dimension(b)) 25 | r = radius(b)*rand(rng)^(1/dimension(b)) 26 | return (r/norm(u))*u + center(b) 27 | end 28 | end 29 | 30 | randn_dimension(rng::AbstractRNG, t::Type{<:StaticVector}, d) = randn(rng, t) 31 | randn_dimension(rng::AbstractRNG, t::Type{<:Vector}, d) = randn(rng, eltype(t), d) 32 | 33 | # Implementation notes 34 | # ==================== 35 | # 36 | # The methods implemented above are the easy ones 37 | # Unions and intersections could be implemented with rejection sampling, but it might be inefficient 38 | # Sphere will require some decisions because `rand(sphere) in sphere` will usually only be approximately satisfied 39 | # Maps may be difficult because the map could distort the distribution so that it is not uniform. 40 | -------------------------------------------------------------------------------- /src/maps/lazy.jl: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | A lazy map has an action that is defined in terms of other maps. Those maps are 4 | stored internally, and the action of the lazy map is computed on-the-fly and only 5 | when invoked. 6 | """ 7 | abstract type LazyMap{T} <: Map{T} end 8 | 9 | 10 | "A composite lazy map is defined in terms of several other maps." 11 | abstract type CompositeLazyMap{T} <: LazyMap{T} end 12 | "A simple lazy map derives from a single other map." 13 | abstract type SimpleLazyMap{T} <: LazyMap{T} end 14 | 15 | supermap(m::SimpleLazyMap) = m.map 16 | components(m::CompositeLazyMap) = m.maps 17 | 18 | Base.getindex(m::CompositeLazyMap, I::ComponentIndex...) = component(m, map(Indexing.to_index, I)...) 19 | 20 | isreal(m::SimpleLazyMap) = isreal(supermap(m)) 21 | isreal(m::CompositeLazyMap) = all(map(isreal, components(m))) 22 | 23 | "A `DerivedMap` inherits all of its properties from another map, but has its own type." 24 | abstract type DerivedMap{T} <: SimpleLazyMap{T} end 25 | 26 | applymap(m::DerivedMap, x) = supermap(m)(x) 27 | appymap!(y, m::DerivedMap, x) = applymap!(y, supermap(m), x) 28 | 29 | jacobian(m::DerivedMap) = jacobian(supermap(m)) 30 | 31 | 32 | "A `WrappedMap{T}` takes any object and turns it into a `Map{T}`." 33 | struct WrappedMap{T,M} <: DerivedMap{T} 34 | map :: M 35 | end 36 | WrappedMap{T}(map) where {T} = WrappedMap{T,typeof(map)}(map) 37 | WrappedMap(map) = WrappedMap{Float64}(map) 38 | 39 | similarmap(m::WrappedMap, ::Type{T}) where {T} = WrappedMap{T}(m) 40 | 41 | convert(::Type{Map}, m::Map) = m 42 | convert(::Type{Map}, m) = WrappedMap(m) 43 | convert(::Type{Map{T}}, m) where {T} = WrappedMap{T}(m) 44 | 45 | ==(m1::WrappedMap, m2::Function) = m1.map == m2 46 | ==(m1::Function, m2::WrappedMap) = m1 == m2.map 47 | 48 | Display.displaystencil(m::WrappedMap{T}) where {T} = 49 | ["WrappedMap{$T}(", supermap(m), ")"] 50 | show(io::IO, mime::MIME"text/plain", m::WrappedMap) = composite_show(io, mime, m) 51 | -------------------------------------------------------------------------------- /src/maps/inverse.jl: -------------------------------------------------------------------------------- 1 | 2 | # We no longer use the syntax inv(m), because `inv` should be a multiplicative 3 | # inverse, and we are interested in the inverse of the map as a function. 4 | import Base: inv 5 | @deprecate inv(m::AbstractMap) inverse(m) 6 | 7 | 8 | "A lazy inverse stores a map `m` and returns `inverse(m, x)`." 9 | struct LazyInverse{T,M} <: SimpleLazyMap{T} 10 | map :: M 11 | end 12 | 13 | LazyInverse(m::AbstractMap) = LazyInverse{codomaintype(m)}(m) 14 | LazyInverse(m) = LazyInverse{Float64}(m) 15 | LazyInverse{T}(m) where {T} = LazyInverse{T,typeof(m)}(m) 16 | 17 | applymap(m::LazyInverse, x) = inverse(supermap(m), x) 18 | 19 | Display.displaystencil(m::LazyInverse) = ["LazyInverse(", supermap(m), ")"] 20 | show(io::IO, mime::MIME"text/plain", m::LazyInverse) = composite_show(io, mime, m) 21 | 22 | 23 | """ 24 | inverse(m[, x]) 25 | 26 | Return the inverse of `m`. The two-argument function evaluates the inverse 27 | at the point `x`. 28 | """ 29 | inverse(m) = LazyInverse(m) 30 | inverse(m::LazyInverse) = supermap(m) 31 | # Concrete maps should implement inverse(m, x) 32 | 33 | (\)(m::AbstractMap, x) = inverse(m, x) 34 | 35 | implements_inverse(m) = !(inverse(m) isa LazyInverse) 36 | 37 | """ 38 | leftinverse(m[, x]) 39 | 40 | Return a left inverse of the given map. This left inverse `mli` is not unique, 41 | but in any case it is such that `(mli ∘ m) * x = x` for each `x` in the domain 42 | of `m`. 43 | 44 | The two-argument function applies the left inverse to the point `x`. 45 | """ 46 | leftinverse(m) = inverse(m) 47 | leftinverse(m, x) = inverse(m, x) 48 | 49 | """ 50 | rightinverse(m[, x]) 51 | 52 | Return a right inverse of the given map. This right inverse `mri` is not unique, 53 | but in any case it is such that `(m ∘ mri) * y = y` for each `y` in the range 54 | of `m`. 55 | 56 | The two-argument function applies the right inverse to the point `x`. 57 | """ 58 | rightinverse(m) = inverse(m) 59 | rightinverse(m, x) = inverse(m, x) 60 | -------------------------------------------------------------------------------- /src/domains/numbers.jl: -------------------------------------------------------------------------------- 1 | 2 | "The set of all natural numbers." 3 | struct NaturalNumbers <: Domain{Int} 4 | end 5 | 6 | in(x, d::NaturalNumbers) = isinteger(x) && (x >= 0) 7 | in(x::AbstractArray, d::NaturalNumbers) = false 8 | ==(::NaturalNumbers, ::NaturalNumbers) = true 9 | 10 | approx_in(x::Real, d::NaturalNumbers, tol) = 11 | (abs(x-round(x)) < tol) && (round(Int, x) ∈ d) 12 | 13 | 14 | "The set of all integers." 15 | struct Integers <: Domain{Int} 16 | end 17 | 18 | in(x, d::Integers) = isinteger(x) 19 | in(x::AbstractArray, d::Integers) = false 20 | ==(::Integers, ::Integers) = true 21 | 22 | approx_in(x::Real, d::Integers, tol) = 23 | (abs(x-round(x)) < tol) && (round(Int, x) ∈ d) 24 | 25 | 26 | "The set of all real numbers." 27 | struct RealNumbers <: Domain{Float64} 28 | end 29 | 30 | in(x::Number, d::RealNumbers) = isreal(x) 31 | # isreal also allows real arrays, we want to disallow that here: 32 | in(x::AbstractArray, d::RealNumbers) = false 33 | 34 | approx_in(x::Complex, d::RealNumbers, tol) = (imag(x) < tol) && (real(x) ∈ d) 35 | ==(::RealNumbers, ::RealNumbers) = true 36 | 37 | "The set of all rationals." 38 | struct Rationals <: Domain{Rational{Int}} 39 | end 40 | 41 | in(x, d::Rationals) = x ∈ Integers() 42 | in(x::Rational, d::Rationals) = true 43 | ==(::Rationals, ::Rationals) = true 44 | 45 | "The set of all complex numbers whose real and imaginary parts are real numbers." 46 | struct ComplexNumbers <: Domain{Complex{Float64}} 47 | end 48 | 49 | in(x::Complex{T}, d::ComplexNumbers) where {T} = 50 | isreal(real(x)) && isreal(imag(x)) 51 | in(x::Complex{T}, d::ComplexNumbers) where {T<:Real} = true 52 | in(x, d::ComplexNumbers) = x ∈ RealNumbers() 53 | ==(::ComplexNumbers, ::ComplexNumbers) = true 54 | 55 | 56 | "The set of natural numbers." 57 | const ℕ = NaturalNumbers() 58 | "The set of integers." 59 | const ℤ = Integers() 60 | "The set of rational numbers." 61 | const ℚ = Rationals() 62 | "The set of real numbers." 63 | const ℝ = RealNumbers() 64 | "The set of complex numbers." 65 | const ℂ = ComplexNumbers() 66 | 67 | "The space ℝ^1." 68 | const ℝ1 = VcatDomain(ℝ) 69 | "The space ℝ^2." 70 | const ℝ2 = VcatDomain(ℝ, ℝ) 71 | "The space ℝ^3." 72 | const ℝ3 = VcatDomain(ℝ, ℝ, ℝ) 73 | "The space ℝ^4." 74 | const ℝ4 = VcatDomain(ℝ, ℝ, ℝ, ℝ) 75 | -------------------------------------------------------------------------------- /src/domains/point.jl: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Point(x) 4 | 5 | represents a single point at `x`. 6 | """ 7 | struct Point{T} <: Domain{T} 8 | x::T 9 | end 10 | 11 | similardomain(d::Point, ::Type{T}) where {T} = Point{T}(d.x) 12 | 13 | convert(::Type{Number}, d::Point{<:Number}) = d.x 14 | convert(::Type{N}, d::Point{<:Number}) where N<:Number = convert(N, convert(Number, d.x)) 15 | Number(d::Point) = convert(Number, d) 16 | 17 | convert(::Type{Domain}, c::Number) = Point(c) 18 | convert(::Type{Domain{T}}, c::Number) where T = Point{T}(c) 19 | 20 | ==(d1::Point,d2::Point) = d1.x == d2.x 21 | hash(d::Point, h::UInt) = hashrec("Point", d.x, h) 22 | 23 | indomain(x, d::Point) = x == d.x 24 | isempty(::Point) = false 25 | 26 | approx_indomain(x, d::Point, tolerance) = norm(x-d.x) <= tolerance 27 | 28 | dimension(d::Point{Vector{T}}) where {T} = length(d.x) 29 | 30 | canonicaldomain(d::Point{T}) where {T<:StaticTypes} = Point(zero(T)) 31 | canonicaldomain(d::Point{T}) where {T<:AbstractVector} = 32 | Point(zeros(eltype(T),dimension(d))) 33 | 34 | mapfrom_canonical(d::Point) = Translation(d.x) 35 | 36 | isopenset(d::Point) = false 37 | isclosedset(d::Point) = true 38 | 39 | boundary(d::Point) = d 40 | boundingbox(d::Point) = d.x..d.x 41 | 42 | infimum(d::Point) = d.x 43 | supremum(d::Point) = d.x 44 | 45 | interior(d::Point{T}) where {T} = EmptySpace{T}() 46 | closure(d::Point) = d 47 | 48 | point_in_domain(d::Point) = d.x 49 | 50 | distance_to(d::Point, x) = norm(x-d.x) 51 | 52 | mapped_domain(invmap, p::Point) = Point(inverse(invmap, p.x)) 53 | map_domain(map, p::Point) = Point(applymap(map, p.x)) 54 | parametric_domain(map, p::Point) = Point(applymap(map, p.x)) 55 | 56 | for op in (:+,:-) 57 | @eval $op(a::Point, b::Point) = Point($op(a.x,b.x)) 58 | end 59 | 60 | # Interval minus a point: 61 | setdiffdomain(d::Interval, x::Number) = setdiffdomain(d, Point(x)) 62 | setdiffdomain(d::Interval, p::Point) = setdiffdomain(promote_domains((d,p))...) 63 | function setdiffdomain(d::Interval{L,R,T}, p::Point{T}) where {L,R,T} 64 | a = leftendpoint(d) 65 | b = rightendpoint(d) 66 | x = p.x 67 | 68 | a == x && return Interval{:open,R,T}(a,b) 69 | a < x < b && return UnionDomain(Interval{L,:open,T}(a,p.x), Interval{:open,R,T}(p.x,b)) 70 | b == x && return Interval{L,:open,T}(a,b) 71 | return d 72 | end 73 | 74 | issubset1(d1::Point, d2) = d1.x ∈ d2 75 | 76 | setdiffdomain1(p::Point, d2) = issubset(p, d2) ? EmptySpace{eltype(p)}() : p 77 | 78 | intersectdomain(d1::Point, d2::Point) = d1.x ∈ d2 ? d1 : EmptySpace{eltype(d1)}() 79 | 80 | show(io::IO,d::Point) = print(io,"Point(", d.x, ")") 81 | -------------------------------------------------------------------------------- /src/maps/arithmetics.jl: -------------------------------------------------------------------------------- 1 | 2 | # Simplifications go here 3 | 4 | # Basic maps 5 | 6 | composedmap1(m1::IdentityMap, m2) = m2 7 | composedmap1(m1::IdentityMap{T}, m2::Map{T}) where {T} = m2 8 | composedmap1(m1::IdentityMap{T}, m2::Map{S}) where {S,T} = convert(Map{T}, m2) 9 | 10 | composedmap2(m1, m2::IdentityMap) = m1 11 | composedmap2(m1::Map{T}, m2::IdentityMap{T}) where {T} = m1 12 | composedmap2(m1::Map{S}, m2::IdentityMap{T}) where {S,T} = convert(Map{T}, m1) 13 | 14 | composedmap2(m1, m2::ConstantMap) = m2 15 | composedmap2(m1::Map{T}, m2::ConstantMap) where {T} = ConstantMap{T}(constant(m2)) 16 | composedmap1(m1::ConstantMap{T}, m2) where {T} = ConstantMap{T}(m2(constant(m1))) 17 | 18 | composedmap2(m1, m2::ZeroMap) = m2 19 | composedmap2(m1::Map{T}, m2::ZeroMap{S,U}) where {S,T,U} = ZeroMap{T,U}() 20 | composedmap1(m1::ConstantMap{T}, m2::ZeroMap{S,U}) where {S,T,U} = ZeroMap{T,U}() 21 | 22 | multiply_map1(m1::ZeroMap, m2) = m1 23 | multiply_map2(m1, m2::ZeroMap) = m2 24 | multiply_map2(m1::ConstantMap{T}, m2::ConstantMap{S}) where {S,T} = 25 | ConstantMap{promote_type(S,T)}(constant(m1)*constant(m2)) 26 | 27 | sum_map1(m1::ZeroMap, m2) = m2 28 | sum_map2(m1, m2::ZeroMap) = m1 29 | sum_map2(m1::ConstantMap{T}, m2::ConstantMap{S}) where {S,T} = 30 | ConstantMap{promote_type(S,T)}(constant(m1)+constant(m2)) 31 | 32 | ## Affine maps 33 | 34 | composedmap(m1::AbstractAffineMap, m2::AbstractAffineMap) = affine_composition(m1, m2) 35 | 36 | """ 37 | Compute the affine map that represents map2 after map1, that is: 38 | `y = a2*(a1*x+b1)+b2 = a2*a1*x + a2*b1 + b2`. 39 | """ 40 | affine_composition(map1::AbstractAffineMap, map2::AbstractAffineMap) = 41 | AffineMap(matrix(map2) * matrix(map1), matrix(map2)*vector(map1) + vector(map2)) 42 | 43 | affine_composition(map1::AffineMap, map2::AffineMap) = 44 | AffineMap(unsafe_matrix(map2) * unsafe_matrix(map1), unsafe_matrix(map2)*unsafe_vector(map1) + unsafe_vector(map2)) 45 | 46 | affine_composition(map1::LinearMap, map2::LinearMap) = 47 | LinearMap(unsafe_matrix(map2) * unsafe_matrix(map1)) 48 | 49 | affine_composition(map1::LinearMap, map2::AffineMap) = 50 | AffineMap(unsafe_matrix(map2) * unsafe_matrix(map1), unsafe_vector(map2)) 51 | 52 | affine_composition(map1::AffineMap, map2::LinearMap) = 53 | AffineMap(unsafe_matrix(map2) * unsafe_matrix(map1), unsafe_matrix(map2)*unsafe_vector(map1)) 54 | 55 | affine_composition(map1::Translation, map2::Translation) = 56 | Translation(unsafe_vector(map2) + unsafe_vector(map1)) 57 | 58 | affine_composition(map1::Translation, map2::LinearMap) = 59 | AffineMap(unsafe_matrix(map2), unsafe_matrix(map2)*unsafe_vector(map1)) 60 | 61 | affine_composition(map1::LinearMap, map2::Translation) = 62 | AffineMap(unsafe_matrix(map1), unsafe_vector(map2)) 63 | 64 | # The sum of two affine maps is again an affine map 65 | mapsum(map1::AbstractAffineMap, map2::AbstractAffineMap) = 66 | AffineMap(matrix(map1)+matrix(map2), vector(map1)+vector(map2)) 67 | 68 | 69 | ==(m1::ProductMap, m2::IdentityMap) = all(map(isidentity, components(m1))) 70 | ==(m1::IdentityMap, m2::ProductMap) = m2 == m1 71 | -------------------------------------------------------------------------------- /src/generic/broadcast.jl: -------------------------------------------------------------------------------- 1 | 2 | # We define a new broadcast style for domains, because they may 3 | # represent continuous sets 4 | 5 | "The broadcast style associated with domains" 6 | struct DomainSetStyle <: Base.Broadcast.BroadcastStyle end 7 | 8 | Base.BroadcastStyle(::Type{<:Domain}) = DomainSetStyle() 9 | 10 | Base.broadcastable(d::Domain) = d 11 | 12 | # DomainSetStyle doesn't mix with ArrayStyle 13 | Base.BroadcastStyle(::DomainSetStyle, ::Base.Broadcast.AbstractArrayStyle) = DomainSetStyle() 14 | Base.BroadcastStyle(::Base.Broadcast.AbstractArrayStyle, ::DomainSetStyle) = DomainSetStyle() 15 | 16 | import Base.Broadcast: broadcasted 17 | 18 | broadcasted(::DomainSetStyle, ::typeof(+), a::Union{Number,AbstractArray}, d::Domain) = 19 | map_domain(Translation(a), d) 20 | broadcasted(::DomainSetStyle, ::typeof(+), d::Domain, a::Union{Number,AbstractArray}) = 21 | map_domain(Translation(a), d) 22 | 23 | broadcasted(::DomainSetStyle, ::typeof(-), a::Union{Number,AbstractArray}, d::Domain) = 24 | map_domain(AffineMap(-1, a), d) 25 | broadcasted(::DomainSetStyle, ::typeof(-), d::Domain, a::Union{Number,AbstractArray}) = 26 | map_domain(Translation(-a), d) 27 | broadcasted(::DomainSetStyle, ::typeof(-), d::Domain{T}) where {T} = 28 | map_domain(LinearMap{T}(-1), d) 29 | 30 | broadcasted(::DomainSetStyle, ::typeof(*), a::Number, d::Domain{T}) where {T} = 31 | map_domain(LinearMap{T}(a), d) 32 | broadcasted(::DomainSetStyle, ::typeof(*), d::Domain{T}, a::Number) where {T} = 33 | map_domain(LinearMap{T}(a), d) 34 | 35 | 36 | broadcasted(::DomainSetStyle, ::typeof(/), d::Domain, a::Number) = 37 | mapped_domain(LinearMap(a), d) 38 | 39 | broadcasted(::DomainSetStyle, ::typeof(\), a::Number, d::Domain) = 40 | mapped_domain(LinearMap(a), d) 41 | 42 | broadcasted(::DomainSetStyle, m::AbstractMap, d::Domain) = map_domain(m, d) 43 | 44 | broadcasted(::DomainSetStyle, fun::Function, d::Domain{T}) where {T} = 45 | convert(Map{T}, fun).(d) 46 | 47 | # Intercept broadcast applied to `in`, e.g. in.(A, d). 48 | # This gives domains an opportunity to provide a more efficient implementation 49 | # when invoked with a set of points `A` at once, especially if `A` 50 | # has particular structure. A common case would be a raster of points, 51 | # for plotting purposes. 52 | # This call can be avoided by typing in.(A, Ref(d)) instead. 53 | broadcasted(::DomainSetStyle, ::typeof(in), A, d::Domain) = broadcast_in(A, d) 54 | broadcasted(::DomainSetStyle, ::typeof(approx_in), A, d::Domain, tol) = broadcast_approx_in(A, d, tol) 55 | 56 | "Vectorized version of `in`: apply `x ∈ d` to all elements of `A`." 57 | broadcast_in(A, d::Domain) = in.(A, Ref(d)) 58 | 59 | "Vectorized version of `approx_in`: apply `x ∈ d` to all elements of `A`." 60 | broadcast_approx_in(A, d::Domain, tol) = approx_in.(A, Ref(d), tol) 61 | 62 | 63 | ## Some arithmetics 64 | 65 | # Allow unary minus, but use broadcast for the implementation 66 | -(d::Domain) = (-).(d) 67 | 68 | # Allow multiplication and division by numbers, like for vectors 69 | *(a::Number, domain::Domain) = a .* domain 70 | *(domain::Domain, a::Number) = domain .* a 71 | /(domain::Domain, a::Number) = domain ./ a 72 | \(a::Number, domain::Domain) = a .\ domain 73 | -------------------------------------------------------------------------------- /src/domains/boundingbox.jl: -------------------------------------------------------------------------------- 1 | 2 | boundingbox(d::Vector{T}) where {T <: Number} = minimum(d)..maximum(d) 3 | boundingbox(d::Set{T}) where {T<:Number} = minimum(d)..maximum(d) 4 | 5 | "Return the bounding box of the union of two or more bounding boxes." 6 | unionbox(d::Domain) = d 7 | unionbox(d1::Domain, d2::Domain) = unionbox(promote_domains(d1, d2)...) 8 | unionbox(d1::Domain, d2::Domain, domains...) = 9 | unionbox(unionbox(d1,d2), domains...) 10 | 11 | unionbox(d1::Domain{T}, d2::Domain{T}) where {T} = unionbox1(d1, d2) 12 | unionbox1(d1, d2) = unionbox2(d1, d2) 13 | unionbox2(d1, d2) = FullSpace{eltype(d1)}() 14 | unionbox1(d1::EmptySpace, d2) = d2 15 | unionbox1(d1::FullSpace, d2) = d1 16 | unionbox2(d1, d2::EmptySpace) = d1 17 | unionbox2(d1, d2::FullSpace) = d2 18 | 19 | unionbox(d1::D, d2::D) where {D<:FixedInterval} = d1 20 | 21 | function unionbox(d1::AbstractInterval{T}, d2::AbstractInterval{T}) where {T} 22 | a, b = endpoints(d1) 23 | c, d = endpoints(d2) 24 | A = min(a, c) 25 | B = max(b, d) 26 | isinf(A) && isinf(B) ? FullSpace{T}() : A..B 27 | end 28 | 29 | unionbox(d1::HyperRectangle{T}, d2::HyperRectangle{T}) where {T} = 30 | Rectangle{T}(map(unionbox, components(d1), components(d2))) 31 | 32 | "Return the bounding box of the intersection of two or more bounding boxes." 33 | intersectbox(d::Domain) = d 34 | intersectbox(d1::Domain, d2::Domain) = intersectbox(promote_domains(d1, d2)...) 35 | intersectbox(d1::Domain, d2::Domain, domains...) = 36 | intersectbox(intersectbox(d1,d2), domains...) 37 | 38 | intersectbox(d1::Domain{T}, d2::Domain{T}) where {T} = intersectbox1(d1, d2) 39 | intersectbox1(d1, d2) = intersectbox2(d1, d2) 40 | intersectbox2(d1, d2) = FullSpace{eltype(d1)}() 41 | intersectbox1(d1::EmptySpace, d2) = d1 42 | intersectbox1(d1::FullSpace, d2) = d2 43 | intersectbox2(d1, d2::EmptySpace) = d2 44 | intersectbox2(d1, d2::FullSpace) = d1 45 | 46 | intersectbox(d1::D, d2::D) where {D<:FixedInterval} = d1 47 | 48 | intersectbox(d1::AbstractInterval{T}, d2::AbstractInterval{T}) where {T} = 49 | intersectdomain(d1, d2) 50 | 51 | function intersectbox(d1::HyperRectangle{T}, d2::HyperRectangle{T}) where {T} 52 | d = Rectangle{T}(map(intersectbox, components(d1), components(d2))) 53 | isempty(d) ? EmptySpace{T}() : d 54 | end 55 | 56 | boundingbox(d::AbstractMappedDomain) = map_boundingbox(boundingbox(superdomain(d)), forward_map(d)) 57 | 58 | function map_boundingbox(box::AbstractInterval, fmap) 59 | l,r = (leftendpoint(box),rightendpoint(box)) 60 | ml = fmap(l); mr = fmap(r) 61 | min(ml,mr)..max(ml,mr) 62 | end 63 | 64 | # This is a best effort implementation, it could be wrong for some maps, 65 | # because we only map the corners. Hence we restrict to affine maps here. 66 | map_boundingbox(box::HyperRectangle, fmap::AbstractAffineMap) = 67 | map_boundingbox_generic(box, fmap) 68 | 69 | # This is a best effort implementation. It could be wrong for some maps, 70 | # because we only map the corners. 71 | function map_boundingbox_generic(box::HyperRectangle{T}, fmap) where {T} 72 | mapped_corners = map(fmap, corners(box)) 73 | left = [minimum(x[j] for x in mapped_corners) for j in 1:dimension(box)] 74 | right = [maximum(x[j] for x in mapped_corners) for j in 1:dimension(box)] 75 | Rectangle{T}(left, right) 76 | end 77 | -------------------------------------------------------------------------------- /src/maps/isomorphism.jl: -------------------------------------------------------------------------------- 1 | 2 | "An isomorphism is a bijection between types that preserves norms." 3 | abstract type Isomorphism{T,U} <: TypedMap{T,U} end 4 | 5 | show(io::IO, m::Isomorphism{T,U}) where {T,U} = print(io, "x : $(T) -> x : $(U)") 6 | Display.object_parentheses(m::Isomorphism) = true 7 | 8 | "Map a length 1 vector `x` to `x[1]`." 9 | struct VectorToNumber{T} <: Isomorphism{SVector{1,T},T} 10 | end 11 | "Map a number `x` to a length 1 vector `[x]`." 12 | struct NumberToVector{T} <: Isomorphism{T,SVector{1,T}} 13 | end 14 | 15 | mapsize(::VectorToNumber) = (1,1) 16 | mapsize(::NumberToVector) = (1,) 17 | 18 | inverse(::VectorToNumber{T}) where {T} = NumberToVector{T}() 19 | inverse(::NumberToVector{T}) where {T} = VectorToNumber{T}() 20 | inverse(m::VectorToNumber, x) = inverse(m)(x) 21 | inverse(m::NumberToVector, x) = inverse(m)(x) 22 | 23 | applymap(::VectorToNumber, x) = x[1] 24 | applymap(::NumberToVector, x) = SVector(x) 25 | 26 | jacobian(::VectorToNumber{T}, x) where {T} = transpose(SVector(one(T))) 27 | jacobian(::VectorToNumber{T}) where {T} = ConstantMap{SVector{1,T}}(transpose(SVector(one(T)))) 28 | 29 | jacobian(::NumberToVector{T}, x) where {T} = SVector(one(T)) 30 | jacobian(::NumberToVector{T}) where {T} = ConstantMap{T}(SVector(one(T))) 31 | 32 | 33 | "Map a length 2 vector `x` to `x[1] + im*x[2]`." 34 | struct VectorToComplex{T} <: Isomorphism{SVector{2,T},Complex{T}} 35 | end 36 | "Map a complex number `x` to the length 2 vector `[real(x); imag(x)]`." 37 | struct ComplexToVector{T} <: Isomorphism{Complex{T},SVector{2,T}} 38 | end 39 | 40 | mapsize(::VectorToComplex) = (1,2) 41 | mapsize(::ComplexToVector) = (2,) 42 | 43 | applymap(::VectorToComplex, x) = x[1] + im*x[2] 44 | applymap(::ComplexToVector, x) = SVector(real(x), imag(x)) 45 | 46 | inverse(::VectorToComplex{T}) where {T} = ComplexToVector{T}() 47 | inverse(::ComplexToVector{T}) where {T} = VectorToComplex{T}() 48 | inverse(m::VectorToComplex, x) = inverse(m)(x) 49 | inverse(m::ComplexToVector, x) = inverse(m)(x) 50 | 51 | jacobian(::VectorToComplex{T}, x) where {T} = transpose(SVector(one(T),one(T)*im)) 52 | jacobian(::VectorToComplex{T}) where {T} = ConstantMap{SVector{2,T}}(transpose(SVector(one(T),one(T)*im))) 53 | 54 | 55 | "Map a static vector to a tuple." 56 | struct VectorToTuple{N,T} <: Isomorphism{SVector{N,T},NTuple{N,T}} 57 | end 58 | "Map a tuple to a static vector." 59 | struct TupleToVector{N,T} <: Isomorphism{NTuple{N,T},SVector{N,T}} 60 | end 61 | 62 | inverse(::VectorToTuple{N,T}) where {N,T} = TupleToVector{N,T}() 63 | inverse(::TupleToVector{N,T}) where {N,T} = VectorToTuple{N,T}() 64 | inverse(m::VectorToTuple, x) = inverse(m)(x) 65 | inverse(m::TupleToVector, x) = inverse(m)(x) 66 | 67 | applymap(::VectorToTuple, x) = tuple(x...) 68 | applymap(::TupleToVector, x) = SVector(x) 69 | 70 | 71 | "Map a nested vector or tuple to a flat vector." 72 | struct NestedToFlat{N,T,U,DIM} <: Isomorphism{U,SVector{N,T}} 73 | end 74 | "Map a flattened vector to a nested one." 75 | struct FlatToNested{N,T,U,DIM} <: Isomorphism{SVector{N,T},U} 76 | end 77 | 78 | inverse(::NestedToFlat{N,T,U,DIM}) where {N,T,U,DIM} = FlatToNested{N,T,U,DIM}() 79 | inverse(::FlatToNested{N,T,U,DIM}) where {N,T,U,DIM} = NestedToFlat{N,T,U,DIM}() 80 | inverse(m::NestedToFlat, x) = inverse(m)(x) 81 | inverse(m::FlatToNested, x) = inverse(m)(x) 82 | 83 | applymap(::NestedToFlat{N,T,U,DIM}, x) where {N,T,U,DIM} = convert_tocartesian(x, Val{DIM}()) 84 | applymap(::FlatToNested{N,T,U,DIM}, x) where {N,T,U,DIM} = convert_fromcartesian(x, Val{DIM}()) 85 | -------------------------------------------------------------------------------- /src/applications/rotation.jl: -------------------------------------------------------------------------------- 1 | # Rotation around the origin 2 | rotate(d::EuclideanDomain{2}, θ) = rotation_map(θ).(d) 3 | 4 | rotate(d::EuclideanDomain{3}, phi, theta, psi) = rotation_map(phi,theta,psi).(d) 5 | # Rotation around a fixed center. 6 | rotate(d::EuclideanDomain{2}, θ, center::SVector{T}) where {T} = (Translation(center) ∘ rotation_map(θ) ∘ Translation(-center)).(d) 7 | 8 | rotate(d::EuclideanDomain{3}, phi, theta, psi, center::SVector{T}) where {T} = (Translation(center) ∘ rotation_map(phi,theta,psi) ∘ Translation(-center)).(d) 9 | 10 | # Maps having to do with coordinate transforms. 11 | 12 | """ 13 | A Cartesion to Polar map. First dimension is interpreted as radial distance, 14 | second as an angle. 15 | The unit circle is mapped to the square `[-1,1]x[-1,1]`. 16 | """ 17 | struct CartToPolarMap{T} <: Map{SVector{2,T}} 18 | end 19 | 20 | CartToPolarMap() = CartToPolarMap{Float64}() 21 | 22 | mapsize(m::CartToPolarMap) = (2,2) 23 | 24 | applymap(map::CartToPolarMap{T}, x) where {T} = 25 | SVector{2,T}(sqrt(x[1]^2+x[2]^2)*2-1, atan(x[2],x[1])/pi) 26 | 27 | function jacobian(m::CartToPolarMap{T}, x) where {T} 28 | d = sqrt(x[1]^2+x[2]^2) 29 | r = 1/(1+(x[2]/x[1])^2) 30 | SMatrix{2,2,T}(2*x[1]/d, 1/pi*r*(-x[2]/x[1]^2), 2*x[2]/d, 1/pi*r*1/x[1]) 31 | end 32 | 33 | inverse(m::CartToPolarMap{T}) where {T} = PolarToCartMap{T}() 34 | inverse(m::CartToPolarMap, x) = inverse(m)(x) 35 | 36 | isreal(m::CartToPolarMap) = true 37 | 38 | convert(::Type{Map{SVector{2,T}}}, ::CartToPolarMap) where {T} = CartToPolarMap{T}() 39 | 40 | ==(m1::CartToPolarMap, m2::CartToPolarMap) = true 41 | 42 | 43 | """ 44 | A Polar to Cartesian map. The angle is mapped to the second dimension, 45 | radius to the first. 46 | The square `[-1,1]x[-1,1]` is mapped to the unit circle. 47 | """ 48 | struct PolarToCartMap{T} <: Map{SVector{2,T}} 49 | end 50 | 51 | PolarToCartMap() = PolarToCartMap{Float64}() 52 | 53 | mapsize(m::PolarToCartMap) = (2,2) 54 | 55 | applymap(map::PolarToCartMap{T}, x) where {T} = SVector{2,T}((x[1]+1)/2*cos(pi*x[2]), (x[1]+1)/2*sin(pi*x[2])) 56 | 57 | jacobian(m::PolarToCartMap{T}, x) where {T} = 58 | SMatrix{2,2,T}(cos(pi*x[2])/2, sin(pi*x[2])/2, -pi*(x[1]+1)/2*sin(pi*x[2]), pi*(x[1]+1)/2*cos(pi*x[2])) 59 | 60 | inverse(m::PolarToCartMap{T}) where {T} = CartToPolarMap{T}() 61 | inverse(m::PolarToCartMap, x) = inverse(m)(x) 62 | 63 | isreal(m::PolarToCartMap) = true 64 | 65 | convert(::Type{Map{SVector{2,T}}}, ::PolarToCartMap) where {T} = PolarToCartMap{T}() 66 | 67 | ==(m1::PolarToCartMap, m2::PolarToCartMap) = true 68 | 69 | 70 | ############################# 71 | # Rotations around the origin 72 | ############################# 73 | 74 | # Rotation in positive (counterclockwise) direction 75 | # (Note: the SMatrix constructor expects the arguments column-first) 76 | rotationmatrix(theta) = SMatrix{2,2}(cos(theta), sin(theta), -sin(theta), cos(theta)) 77 | 78 | # Rotation about X-axis (phi), Y-axis (theta) and Z-axis (psi) 79 | # As above, the matrix is given column-by-column 80 | rotationmatrix(phi,theta,psi) = 81 | SMatrix{3,3}(cos(theta)*cos(psi), cos(theta)*sin(psi), -sin(theta), 82 | -cos(phi)*sin(psi)+sin(phi)*sin(theta)*cos(psi), cos(phi)*cos(psi)+sin(phi)*sin(theta)*sin(psi), sin(phi)*cos(theta), 83 | sin(phi)*sin(psi)+cos(phi)*sin(theta)*cos(psi), -sin(phi)*cos(psi)+cos(phi)*sin(theta)*sin(psi), cos(phi)*cos(theta)) 84 | 85 | rotation_map(theta) = LinearMap(rotationmatrix(theta)) 86 | 87 | rotation_map(phi, theta, psi) = LinearMap(rotationmatrix(phi,theta,psi)) 88 | -------------------------------------------------------------------------------- /src/domains/indicator.jl: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | Supertype of domains that are defined by an indicator function. 4 | 5 | An indicator function is a function `f : S -> [0,1]` that indicates membership 6 | of `x` to a domain `D` with `D ⊂ S`. The indicator function corresponds exactly 7 | to the `in` function of a domain: `f(x) = x ∈ D`. 8 | 9 | Concrete subtypes of `AbstractIndicatorFunction` store a representation of this 10 | indicator function and implement `in` using that representation, rather than 11 | implementing `in` directly. 12 | """ 13 | abstract type AbstractIndicatorFunction{T} <: Domain{T} end 14 | 15 | "The indicator function of a domain is the function `f(x) = x ∈ D`." 16 | indicatorfunction(d::Domain) = x -> x ∈ d 17 | 18 | indomain(x, d::AbstractIndicatorFunction) = _indomain(x, d, indicatorfunction(d)) 19 | _indomain(x, d::AbstractIndicatorFunction, f) = f(x) 20 | 21 | show(io::IO, d::AbstractIndicatorFunction) = 22 | print(io, "indicator domain defined by function f = $(indicatorfunction(d))") 23 | 24 | 25 | "An `IndicatorFunction` is a domain that implements `f(x) = x ∈ D` by storing `f`." 26 | struct IndicatorFunction{T,F} <: AbstractIndicatorFunction{T} 27 | f :: F 28 | end 29 | 30 | IndicatorFunction(f) = IndicatorFunction{Float64}(f) 31 | IndicatorFunction{T}(f::F) where {T,F} = IndicatorFunction{T,F}(f) 32 | 33 | indicatorfunction(d::IndicatorFunction) = d.f 34 | 35 | similardomain(d::IndicatorFunction, ::Type{T}) where {T} = IndicatorFunction{T}(d.f) 36 | 37 | convert(::Type{IndicatorFunction}, d::AbstractIndicatorFunction) = d 38 | convert(::Type{IndicatorFunction}, d::Domain{T}) where {T} = 39 | IndicatorFunction{T}(indicatorfunction(d)) 40 | 41 | ==(d1::IndicatorFunction, d2::IndicatorFunction) = indicatorfunction(d1)==indicatorfunction(d2) 42 | 43 | intersectdomain1(d1::IndicatorFunction, d2) = BoundedIndicatorFunction(d1.f, d2) 44 | intersectdomain2(d1, d2::IndicatorFunction) = BoundedIndicatorFunction(d2.f, d1) 45 | 46 | "An indicator function with a known bounding domain." 47 | struct BoundedIndicatorFunction{F,D,T} <: AbstractIndicatorFunction{T} 48 | f :: F 49 | domain :: D 50 | end 51 | 52 | BoundedIndicatorFunction(f::F, domain::D) where {F,T,D<:Domain{T}} = 53 | BoundedIndicatorFunction{F,D,T}(f, domain) 54 | 55 | indicatorfunction(d::BoundedIndicatorFunction) = d.f 56 | 57 | boundingdomain(d::BoundedIndicatorFunction) = d.domain 58 | 59 | indomain(x, d::BoundedIndicatorFunction) = in(x, boundingdomain(d)) && d.f(x) 60 | 61 | ==(d1::BoundedIndicatorFunction, d2::BoundedIndicatorFunction) = 62 | indicatorfunction(d1)==indicatorfunction(d2) && boundingdomain(d1)==boundingdomain(d2) 63 | hash(d::BoundedIndicatorFunction, h::UInt) = 64 | hashrec(indicatorfunction(d), boundingdomain(d), h) 65 | 66 | similardomain(d::BoundedIndicatorFunction, ::Type{T}) where {T} = 67 | BoundedIndicatorFunction(d.f, convert(Domain{T}, d.domain)) 68 | 69 | Domain(gen::Base.Generator) = generator_domain(gen) 70 | 71 | generator_domain(gen::Base.Generator{<:Domain}) = BoundedIndicatorFunction(gen.f, gen.iter) 72 | generator_domain(gen::Base.Generator{<:Base.Iterators.ProductIterator}) = 73 | productgenerator_domain(gen, gen.iter.iterators) 74 | 75 | function productgenerator_domain(gen, domains::Tuple{Vararg{Domain,N} where N}) 76 | domain = TupleProductDomain(gen.iter.iterators) 77 | BoundedIndicatorFunction(gen.f, domain) 78 | end 79 | 80 | boundingbox(d::BoundedIndicatorFunction) = boundingbox(boundingdomain(d)) 81 | 82 | function show(io::IO, d::BoundedIndicatorFunction) 83 | print(io, "indicator function bounded by: ") 84 | show(io, boundingdomain(d)) 85 | end 86 | -------------------------------------------------------------------------------- /test/test_applications.jl: -------------------------------------------------------------------------------- 1 | 2 | function test_rotation_map(T) 3 | ϕ = T(pi)/4 4 | m = rotation_map(ϕ) 5 | x = [one(T), zero(T)] 6 | y = m(x) 7 | @test y[1] ≈ sqrt(T(2))/2 8 | @test y[2] ≈ sqrt(T(2))/2 9 | 10 | ϕ = T(pi)/4 11 | m = rotation_map(ϕ, 0, 0) 12 | x = [zero(T), one(T), zero(T)] 13 | y = m(x) 14 | @test y[1] ≈ 0 15 | @test y[2] ≈ sqrt(T(2))/2 16 | @test y[3] ≈ sqrt(T(2))/2 17 | 18 | # TODO: add more tests for a 3D rotation 19 | 20 | theta = T(rand()) 21 | phi = T(rand()) 22 | psi = T(rand()) 23 | m2 = rotation_map(theta) 24 | test_generic_map(m2) 25 | m3 = rotation_map(phi, theta, psi) 26 | test_generic_map(m3) 27 | 28 | r = suitable_point_to_map(m2) 29 | @test norm(m2(r))≈norm(r) 30 | 31 | r = suitable_point_to_map(m3) 32 | @test norm(m3(r))≈norm(r) 33 | @test islinear(m3) 34 | end 35 | 36 | function test_cart_polar_map(T) 37 | m1 = CartToPolarMap{T}() 38 | test_generic_map(m1) 39 | @test !islinear(m1) 40 | @test isreal(m1) 41 | 42 | m2 = PolarToCartMap{T}() 43 | test_generic_map(m2) 44 | @test !islinear(m2) 45 | @test isreal(m2) 46 | 47 | @test inverse(m1) == m2 48 | @test inverse(m2) == m1 49 | end 50 | 51 | function test_rand(T) 52 | r = Rectangle(T[-1, 2], T[3, 4]) 53 | @test @inferred(Random.gentype(r)) == Vector{T} 54 | @test typeof(rand(r)) == Random.gentype(r) 55 | @test @inferred(rand(r)) in r 56 | 57 | r = Rectangle(SA[T(-1), T(2)], SA[T(3), T(4)]) 58 | @test @inferred(Random.gentype(r)) == SVector{2, T} 59 | @test typeof(rand(r)) == Random.gentype(r) 60 | @test @inferred(rand(r)) in r 61 | 62 | hybrid_product = ProductDomain(["a", "b"], T(1.0)..T(2.0)) 63 | @test @inferred(Random.gentype(hybrid_product)) == Tuple{String, T} 64 | @test typeof(rand(hybrid_product)) == Random.gentype(hybrid_product) 65 | @test @inferred(rand(hybrid_product)) in hybrid_product 66 | 67 | b = Ball(2.0, SA[T(1.0), T(2.0)]) 68 | @test @inferred(Random.gentype(b)) == SVector{2, T} 69 | @test typeof(rand(b)) == Random.gentype(b) 70 | @test @inferred(rand(b)) in b 71 | @test all(p in b for p in rand(b, 100)) 72 | test_rng_consistency(b) 73 | 74 | b = Ball(2.0, [T(1.0), T(2.0)]) 75 | @test @inferred(Random.gentype(b)) == Vector{T} 76 | @test typeof(rand(b)) == Random.gentype(b) 77 | @test @inferred(rand(b)) in b 78 | @test all(p in b for p in rand(b, 100)) 79 | test_rng_consistency(b) 80 | 81 | b = Ball(2.0, T(1.0)) 82 | @test @inferred(Random.gentype(b)) == T 83 | @test typeof(rand(b)) == Random.gentype(b) 84 | @test @inferred(rand(b)) in b 85 | test_rng_consistency(b) 86 | 87 | # Higher dimension 88 | # Only works for Float64 since there is no randn(BigFloat) 89 | if T == Float64 90 | b = Ball(1.5, SA[1.0, -1.0, 2.0, -3.0]) 91 | @test @inferred(Random.gentype(b)) == SVector{4, T} 92 | @test typeof(rand(b)) == Random.gentype(b) 93 | @test @inferred(rand(b)) in b 94 | @test all(p in b for p in rand(b, 100)) 95 | test_rng_consistency(b) 96 | 97 | # Test numerical accuracy - two rectangles of the same size should have the same number of points 98 | rng = StableRNG(1) 99 | n = 1_000_000 100 | region_1 = Rectangle([0.0, -0.3, 0.0, -0.3], [0.3, 0.0, 0.3, 0.0]) .+ center(b) 101 | lower_corner = radius(b)*fill(-0.5, 4) 102 | region_2 = Rectangle(lower_corner, lower_corner.+0.3) .+ center(b) 103 | rs = rand(rng, b, n) 104 | n_1 = sum(r in region_1 for r in rs) 105 | n_2 = sum(r in region_2 for r in rs) 106 | @test isapprox(n_1, n_2, rtol=0.1) 107 | end 108 | end 109 | 110 | function test_rng_consistency(set) 111 | rng1 = Random.MersenneTwister(1) 112 | rng2 = Random.MersenneTwister(1) 113 | @test rand(rng1, set) == rand(rng2, set) 114 | end 115 | 116 | function test_applications(T) 117 | test_rotation_map(T) 118 | test_cart_polar_map(T) 119 | test_rand(T) 120 | end 121 | 122 | @testset "applications" begin 123 | test_applications(Float64) 124 | test_applications(BigFloat) 125 | end 126 | -------------------------------------------------------------------------------- /test/test_common.jl: -------------------------------------------------------------------------------- 1 | 2 | using DomainSets: convert_numtype, convert_prectype, 3 | promote_numtype, promote_prectype 4 | 5 | function test_dimension() 6 | @test DomainSets.euclideandimension(Int) == 1 7 | @test DomainSets.euclideandimension(Float64) == 1 8 | @test DomainSets.euclideandimension(ComplexF64) == 1 9 | @test DomainSets.euclideandimension(SVector{2,Float64}) == 2 10 | @test DomainSets.euclideandimension(MVector{2,Float64}) == 2 11 | @test DomainSets.euclideandimension(Tuple{Int,Int}) == 2 12 | @test DomainSets.euclideandimension(Tuple{Int,Float64}) == 2 13 | @test_throws MethodError DomainSets.euclideandimension(Vector{Float64}) 14 | end 15 | 16 | function test_components() 17 | @test components([1,2,3]) == () 18 | d = UnionDomain(Point(1),Point(2)) 19 | @test iscomposite(d) 20 | @test ncomponents(d) == length(components(d)) 21 | end 22 | 23 | function test_prectype() 24 | @test prectype(1.0) == Float64 25 | @test prectype(big(1.0)) == BigFloat 26 | @test prectype(1) == typeof(float(1)) 27 | @test prectype(SVector(1,2)) == typeof(float(1)) 28 | @test prectype(SVector(1,big(2))) == typeof(float(big(2))) 29 | @test prectype(1.0+2.0im) == Float64 30 | @test prectype([1.0+2.0im, 3.0]) == Float64 31 | @test prectype(NTuple{2,Int}) == Float64 32 | @test prectype((1.0,)) == Float64 33 | @test prectype((1.0, 2.0)) == Float64 34 | @test prectype((1.0, 2.0, 3.0)) == Float64 35 | @test prectype((1.0, big(2.0), 3.0+im)) == BigFloat 36 | @test prectype(NTuple{4,Int}) == Float64 37 | @test @inferred(prectype(1, 2.0)) == Float64 38 | @test @inferred(prectype((1, 2.0, 3, 40+im))) == Float64 39 | 40 | @test convert_prectype(2, Float64) == 2 41 | @test convert_prectype(2, Float64) isa Float64 42 | @test convert_prectype(1.0+im, BigFloat) == 1+im 43 | @test convert_prectype(1.0+im, BigFloat) isa Complex{BigFloat} 44 | @test convert_prectype(SA[1,2], Float64) == SA[1.0,2.0] 45 | @test convert_prectype(SA[1,2], Float64) isa SVector{2,Float64} 46 | @test convert_prectype(SA[1,2+im], BigFloat) isa SVector{2,Complex{BigFloat}} 47 | @test_throws ErrorException convert_prectype("a", BigFloat) 48 | 49 | @test promote_prectype(2) == 2 50 | @test promote_prectype(2, 3.0) isa Tuple{Float64,Float64} 51 | @test promote_prectype(2, 3.0+im, big(4)) isa Tuple{BigFloat,Complex{BigFloat},BigFloat} 52 | end 53 | 54 | 55 | function test_numtype() 56 | @test numtype(1.0) == Float64 57 | @test numtype(big(1.0)) == BigFloat 58 | @test numtype(1) == Int 59 | @test numtype([1,2,3], [4,5,6]) == Int 60 | @test numtype(SVector(1,2)) == Int 61 | @test numtype(SVector(1,big(2))) == BigInt 62 | @test numtype(Array{Float64,2}) == Float64 63 | @test numtype(1.0+2.0im) == Complex{Float64} 64 | @test numtype([1.0+2.0im, 3.0]) == Complex{Float64} 65 | @test numtype(NTuple{2,Complex{Int}}) == Complex{Int} 66 | @test numtype((1.0,)) == Float64 67 | @test numtype((1.0, 2.0)) == Float64 68 | @test numtype((1.0, 2.0, 3.0)) == Float64 69 | @test numtype((1.0, 2.0, 3.0, 4.0)) == Float64 70 | @test numtype(1.0, big(2.0), 3.0+im) == Complex{BigFloat} 71 | @test numtype((1.0, big(2.0), 3.0+im)) == Complex{BigFloat} 72 | @test @inferred(numtype(1, 2.0)) == Float64 73 | @test @inferred(numtype((1, 2.0, 3, 40+im))) == Complex{Float64} 74 | 75 | @test convert_numtype(2, Float64) == 2 76 | @test convert_numtype(2, Float64) isa Float64 77 | @test convert_numtype(SA[1,2], Float64) == SA[1,2] 78 | @test convert_numtype(SA[1,2], Float64) isa SVector{2,Float64} 79 | @test_throws ErrorException convert_numtype("a", BigFloat) 80 | 81 | @test promote_numtype(2) == 2 82 | @test promote_numtype(2, 3.0) isa Tuple{Float64,Float64} 83 | @test promote_numtype(2, 3.0+im, big(4)) isa Tuple{Complex{BigFloat},Complex{BigFloat},Complex{BigFloat}} 84 | end 85 | 86 | @testset "common functionality" begin 87 | @testset "dimension" begin 88 | test_dimension() 89 | end 90 | @testset "elements" begin 91 | test_components() 92 | end 93 | @testset "prectype" begin 94 | test_prectype() 95 | end 96 | @testset "numtype" begin 97 | test_numtype() 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /test/test_domain_simplex.jl: -------------------------------------------------------------------------------- 1 | function test_simplex() 2 | @test UnitSimplex(2) isa VectorUnitSimplex{Float64} 3 | @test UnitSimplex(Val(2)) isa EuclideanUnitSimplex{2,Float64} 4 | @test UnitSimplex{Float64}() isa StaticUnitSimplex{Float64} 5 | @test UnitSimplex{Float64}(1) isa StaticUnitSimplex{Float64} 6 | @test_throws AssertionError UnitSimplex{Float64}(2) 7 | @test UnitSimplex{SVector{2,Float64}}(Val(2)) isa EuclideanUnitSimplex{2,Float64} 8 | @test_throws AssertionError UnitSimplex{SVector{2,Float64}}(Val(3)) 9 | @test UnitSimplex{SVector{2,Float64}}(2) isa EuclideanUnitSimplex{2,Float64} 10 | @test_throws AssertionError UnitSimplex{SVector{2,Float64}}(3) 11 | @test UnitSimplex{SVector{2,Float64}}() isa EuclideanUnitSimplex{2,Float64} 12 | @test UnitSimplex{Vector{Float64}}(2) isa VectorUnitSimplex{Float64} 13 | @test_throws MethodError UnitSimplex{Vector{Float64}}() 14 | 15 | @test UnitSimplex{Float64,:open}() isa StaticUnitSimplex{Float64,:open} 16 | @test UnitSimplex{Float64,:closed}(1) isa StaticUnitSimplex{Float64,:closed} 17 | @test_throws AssertionError UnitSimplex{Float64,:closed}(2) 18 | @test UnitSimplex{SVector{2,Float64},:open}(Val(2)) isa EuclideanUnitSimplex{2,Float64,:open} 19 | @test_throws AssertionError UnitSimplex{SVector{2,Float64},:closed}(Val(3)) 20 | @test UnitSimplex{SVector{2,Float64},:open}(2) isa EuclideanUnitSimplex{2,Float64,:open} 21 | @test_throws AssertionError UnitSimplex{SVector{2,Float64},:closed}(3) 22 | @test UnitSimplex{SVector{2,Float64},:open}() isa EuclideanUnitSimplex{2,Float64,:open} 23 | @test UnitSimplex{Vector{Float64},:closed}(2) isa VectorUnitSimplex{Float64,:closed} 24 | @test_throws MethodError UnitSimplex{Vector{Float64},:open}() 25 | 26 | @test StaticUnitSimplex(Val(3)) isa StaticUnitSimplex{SVector{3,Float64}} 27 | @test DynamicUnitSimplex{Float64}(1) isa DynamicUnitSimplex{Float64} 28 | @test_throws AssertionError DynamicUnitSimplex{Float64}(2) 29 | 30 | @test repr(UnitSimplex(Val(2))) == "UnitSimplex(Val(2))" 31 | @test repr(UnitSimplex(3)) == "UnitSimplex(3)" 32 | 33 | d = UnitSimplex(Val(2)) 34 | # We test a point in the interior, a point on each of the boundaries and 35 | # all corners. 36 | @test SA[0.2,0.2] ∈ d 37 | @test SA[0.0,0.2] ∈ d 38 | @test SA[0.2,0.0] ∈ d 39 | @test SA[0.5,0.5] ∈ d 40 | @test SA[0.0,0.0] ∈ d 41 | @test SA[1.0,0.0] ∈ d 42 | @test SA[0.0,1.0] ∈ d 43 | # And then some points outside 44 | @test SA[0.6,0.5] ∉ d 45 | @test SA[0.5,0.6] ∉ d 46 | @test SA[-0.2,0.2] ∉ d 47 | @test SA[0.2,-0.2] ∉ d 48 | @test boundingbox(d) == UnitCube{SVector{2,Float64}}() 49 | 50 | # issue #102 51 | @test !([0.3,0.4,0.2] ∈ UnitSimplex(2)) 52 | z1 = @test_logs (:warn, "`in`: incompatible combination of vector with length 3 and domain 'UnitSimplex(Val(2))' with dimension 2. Returning false.") !([0.3,0.4,0.2] ∈ UnitSimplex(Val(2))) 53 | @test z1 54 | z2 = @test_logs (:warn, "`in`: incompatible combination of vector with length 3 and domain 'UnitSimplex(Val(2))' with dimension 2. Returning false.") !(SVector(0.3,0.4,0.2) ∈ UnitSimplex(Val(2))) 55 | @test z2 56 | 57 | @test approx_in(SA[-0.1,-0.1], d, 0.1) 58 | @test !approx_in(SA[-0.1,-0.1], d, 0.09) 59 | 60 | @test corners(d) == [ SA[0.0,0.0], SA[1.0,0.0], SA[0.0,1.0]] 61 | 62 | @test convert(Domain{SVector{2,BigFloat}}, d) == EuclideanUnitSimplex{2,BigFloat}() 63 | 64 | @test isclosedset(d) 65 | @test !isopenset(d) 66 | @test isopenset(interior(d)) 67 | @test closure(d) == d 68 | @test point_in_domain(d) ∈ d 69 | 70 | # open/closed 71 | d2 = EuclideanUnitSimplex{2,Float64,:open}() 72 | @test !isclosedset(d2) 73 | @test isopenset(d2) 74 | @test SA[0.3,0.1] ∈ d2 75 | @test SA[0.0,0.1] ∉ d2 76 | @test SA[0.3,0.0] ∉ d2 77 | @test approx_in(SA[-0.01,0.0], d2, 0.1) 78 | @test !approx_in(SA[-0.01,0.0], d2, 0.001) 79 | 80 | d3 = EuclideanUnitSimplex{3,BigFloat}() 81 | @test point_in_domain(d3) ∈ d3 82 | x0 = big(0.0) 83 | x1 = big(1.0) 84 | x2 = big(0.3) 85 | @test SA[x0,x0,x0] ∈ d3 86 | @test SA[x1,x0,x0] ∈ d3 87 | @test SA[x0,x1,x0] ∈ d3 88 | @test SA[x0,x0,x1] ∈ d3 89 | @test SA[x2,x0,x0] ∈ d3 90 | @test SA[x0,x2,x0] ∈ d3 91 | @test SA[x0,x0,x2] ∈ d3 92 | @test SA[x2,x2,x2] ∈ d3 93 | @test SA[-x2,x2,x2] ∉ d3 94 | @test SA[x2,-x2,x2] ∉ d3 95 | @test SA[x2,x2,-x2] ∉ d3 96 | @test SA[x1,x1,x1] ∉ d3 97 | 98 | D = VectorUnitSimplex(2) 99 | @test isopenset(interior(D)) 100 | @test closure(D) == D 101 | @test SA[0.2,0.2] ∈ D 102 | @test SA[0.0,0.2] ∈ D 103 | @test SA[0.2,0.0] ∈ D 104 | @test SA[0.5,0.5] ∈ D 105 | @test SA[0.0,0.0] ∈ D 106 | @test SA[1.0,0.0] ∈ D 107 | @test SA[0.0,1.0] ∈ D 108 | # And then some points outside 109 | @test SA[0.6,0.5] ∉ D 110 | @test SA[0.5,0.6] ∉ D 111 | @test SA[-0.2,0.2] ∉ D 112 | @test SA[0.2,-0.2] ∉ D 113 | @test convert(Domain{Vector{BigFloat}}, D) == VectorUnitSimplex{BigFloat}(2) 114 | @test corners(D) == [ [0.0,0.0], [1.0,0.0], [0.0,1.0]] 115 | @test boundingbox(D) == UnitCube(4) 116 | end 117 | -------------------------------------------------------------------------------- /src/generic/canonical.jl: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | canonicaldomain([ctype::CanonicalType, ]d::Domain) 4 | 5 | Return an associated canonical domain, if any, of the given domain. 6 | 7 | For example, the canonical domain of an Interval `[a,b]` is the interval `[-1,1]`. 8 | 9 | Optionally, a canonical type argument may specify an alternative canonical domain. 10 | Canonical domains help with establishing equality between domains, with finding 11 | maps between domains and with finding parameterizations. 12 | 13 | If a domain implements a canonical domain, it should also implement 14 | `mapfrom_canonical` and `mapto_canonical`. 15 | """ 16 | canonicaldomain(d) = d 17 | 18 | "Does the domain have a canonical domain?" 19 | hascanonicaldomain(d) = !(d === canonicaldomain(d)) 20 | 21 | identitymap(d) = IdentityMap{eltype(d)}(dimension(d)) 22 | 23 | "Return a map to a domain from its canonical domain." 24 | mapfrom_canonical(d) = identitymap(d) 25 | mapfrom_canonical(d, x) = mapfrom_canonical(d)(x) 26 | 27 | "Return a map from the domain to its canonical domain." 28 | mapto_canonical(d) = leftinverse(mapfrom_canonical(d)) 29 | mapto_canonical(d, x) = mapto_canonical(d)(x) 30 | 31 | 32 | "Supertype of kinds of canonical domains." 33 | abstract type CanonicalType end 34 | 35 | canonicaldomain(ctype::CanonicalType, d) = d 36 | hascanonicaldomain(ctype::CanonicalType, d) = !(d === canonicaldomain(ctype, d)) 37 | 38 | mapfrom_canonical(ctype::CanonicalType, d, x) = mapfrom_canonical(ctype, d)(x) 39 | mapto_canonical(ctype::CanonicalType, d, x) = mapto_canonical(ctype, d)(x) 40 | 41 | 42 | "A canonical domain that is equal but simpler (e.g. a 1-dimensional ball is an interval)." 43 | struct Equal <: CanonicalType end 44 | 45 | canonicaldomain(::Equal, d) = d 46 | mapfrom_canonical(::Equal, d) = identitymap(d) 47 | mapto_canonical(::Equal, d) = leftinverse(mapto_canonical(Equal(), d)) 48 | 49 | simplify(d) = canonicaldomain(Equal(), d) 50 | simplifies(d) = hascanonicaldomain(Equal(), d) 51 | 52 | "A canonical domain that is isomorphic but may have different element type." 53 | struct Isomorphic <: CanonicalType end 54 | 55 | canonicaldomain(::Isomorphic, d) = canonicaldomain(Equal(), d) 56 | mapfrom_canonical(::Isomorphic, d) = mapfrom_canonical(Equal(), d) 57 | mapto_canonical(::Isomorphic, d) = leftinverse(mapto_canonical(Isomorphic(), d)) 58 | 59 | canonicaldomain(::Isomorphic, d::Domain{SVector{1,T}}) where {T} = 60 | convert(Domain{T}, d) 61 | mapfrom_canonical(::Isomorphic, d::Domain{SVector{1,T}}) where {T} = 62 | NumberToVector{T}() 63 | 64 | canonicaldomain(::Isomorphic, d::Domain{NTuple{N,T}}) where {N,T} = 65 | convert(Domain{SVector{N,T}}, d) 66 | mapfrom_canonical(::Isomorphic, d::Domain{NTuple{N,T}}) where {N,T} = 67 | VectorToTuple{N,T}() 68 | 69 | 70 | "A parameter domain that can be mapped to the domain." 71 | struct Parameterization <: CanonicalType end 72 | 73 | canonicaldomain(ctype::Parameterization, d) = 74 | hascanonicaldomain(d) ? canonicaldomain(ctype, canonicaldomain(d)) : d 75 | mapfrom_canonical(ctype::Parameterization, d) = 76 | hascanonicaldomain(d) ? mapfrom_canonical(d) ∘ mapfrom_canonical(ctype, canonicaldomain(d)) : mapfrom_canonical(d) 77 | mapto_canonical(ctype::Parameterization, d) = leftinverse(mapfrom_canonical(ctype, d)) 78 | 79 | # We define some convenience functions: 80 | "Return a parameter domain which supports a `parameterization`." 81 | parameterdomain(d::Domain) = canonicaldomain(Parameterization(), d) 82 | 83 | "Return a parameterization of the given domain." 84 | parameterization(d::Domain) = mapfrom_canonical(Parameterization(), d) 85 | 86 | "Does the domain have a parameterization?" 87 | hasparameterization(d) = hascanonicaldomain(Parameterization(), d) 88 | 89 | mapfrom_parameterdomain(d::Domain) = mapfrom_canonical(Parameterization(), d) 90 | mapto_parameterdomain(d::Domain) = mapto_canonical(Parameterization(), d) 91 | mapfrom_parameterdomain(d::Domain, x) = mapfrom_canonical(Parameterization(), d, x) 92 | mapto_parameterdomain(d::Domain, x) = mapto_canonical(Parameterization(), d, x) 93 | 94 | 95 | "Return a map from domain `d1` to domain `d2`." 96 | mapto(d1, d2) = mapto1(d1, d2) 97 | mapto(d1::D, d2::D) where {D} = d1 == d2 ? identitymap(d1) : mapto1(d1,d2) 98 | 99 | # simplify the first argument 100 | mapto1(d1, d2) = 101 | hasparameterization(d1) ? mapto(parameterdomain(d1), d2) ∘ mapto_parameterdomain(d1) : mapto2(d1, d2) 102 | # simplify the second argument 103 | mapto2(d1, d2) = 104 | hasparameterization(d2) ? mapfrom_parameterdomain(d2) ∘ mapto(d1, parameterdomain(d2)) : no_known_mapto(d1,d2) 105 | 106 | no_known_mapto(d1, d2) = d1 == d2 ? identitymap(d1) : error("No map known between $(d1) and $(d2).") 107 | 108 | 109 | 110 | ## Equality for domains 111 | 112 | ## Note: here we generically define the equality of Domains, using the framework 113 | # of canonical domains above. It has the benefit of automatically recognizing 114 | # equality between some domains, but a side-effect is that the default 115 | # (implicitly defined) equality for a concrete domain type is overruled. 116 | # To be safe, concrete domains should specialize `==`. 117 | ==(d1::Domain, d2::Domain) = isequal1(d1, d2) 118 | # simplify the first argument 119 | isequal1(d1, d2) = simplifies(d1) ? simplify(d1)==d2 : isequal2(d1, d2) 120 | # simplify the second argument 121 | isequal2(d1, d2) = simplifies(d2) ? d1==simplify(d2) : d1===d2 122 | -------------------------------------------------------------------------------- /src/maps/map.jl: -------------------------------------------------------------------------------- 1 | 2 | "An `AbstractMap` represents a function `y=f(x)` of a single variable." 3 | abstract type AbstractMap end 4 | 5 | "A `Map{T}` is a map of a single variable of type `T`." 6 | abstract type Map{T} <: AbstractMap end 7 | 8 | Map(m) = convert(Map, m) 9 | Map{T}(m) where {T} = convert(Map{T}, m) 10 | 11 | "A `TypedMap{T,U}` maps a variable of type `T` to a variable of type `U`." 12 | abstract type TypedMap{T,U} <: Map{T} end 13 | 14 | const EuclideanMap{N,T} = Map{<:StaticVector{N,T}} 15 | const VectorMap{T} = Map{Vector{T}} 16 | 17 | CompositeTypes.Display.displaysymbol(m::Map) = 'F' 18 | 19 | "What is the expected type of a point in the domain of the function map `m`?" 20 | domaintype(m) = domaintype(typeof(m)) 21 | domaintype(::Type{M}) where {M} = Any 22 | domaintype(::Type{<:Map{T}}) where {T} = T 23 | 24 | """ 25 | codomaintype(m[, S]) 26 | 27 | What is the codomain type of the function map `m`, given that `S` is its domain type? 28 | """ 29 | codomaintype(m) = codomaintype(m, domaintype(m)) 30 | codomaintype(m, ::Type{T}) where {T} = codomaintype(typeof(m), T) 31 | 32 | codomaintype(::Type{M}, ::Type{T}) where {M,T} = Any 33 | codomaintype(M::Type{<:AbstractMap}, ::Type{T}) where {T} = Base.promote_op(applymap, M, T) 34 | codomaintype(M::Type{<:TypedMap{T,U}}, ::Type{T}) where {T,U} = U 35 | 36 | isreal(m::AbstractMap) = isreal(domaintype(m)) && isreal(codomaintype(m)) 37 | isreal(::UniformScaling{T}) where {T} = isreal(T) 38 | isreal(::Type{UniformScaling{T}}) where {T} = isreal(T) 39 | 40 | numtype(::Type{<:Map{T}}) where {T} = numtype(T) 41 | prectype(::Type{<:Map{T}}) where {T} = prectype(T) 42 | 43 | convert(::Type{AbstractMap}, m::AbstractMap) = m 44 | convert(::Type{Map{T}}, m::Map{T}) where {T} = m 45 | convert(::Type{Map{T}}, m::Map{S}) where {S,T} = similarmap(m, T) 46 | convert(::Type{TypedMap{T,U}}, m::TypedMap{T,U}) where {T,U} = m 47 | convert(::Type{TypedMap{T,U}}, m::TypedMap) where {T,U} = similarmap(m, T, U) 48 | 49 | convert_numtype(map::Map{T}, ::Type{U}) where {T,U} = convert(Map{to_numtype(T,U)}, map) 50 | convert_prectype(map::Map{T}, ::Type{U}) where {T,U} = convert(Map{to_prectype(T,U)}, map) 51 | 52 | # Users may call a map, concrete subtypes specialize the `applymap` function 53 | (m::AbstractMap)(x) = applymap(m, x) 54 | 55 | # For Map{T}, we allow invocation with multiple arguments by conversion to T 56 | (m::Map{T})(x) where {T} = promote_and_apply(m, x) 57 | (m::Map{T})(x...) where {T} = promote_and_apply(m, convert(T, x)) 58 | 59 | "Promote map and point to compatible types." 60 | promote_map_point_pair(m, x) = (m, x) 61 | promote_map_point_pair(m::Map, x) = _promote_map_point_pair(m, x, promote_type(domaintype(m), typeof(x))) 62 | # This is the line where we promote both the map and the point: 63 | _promote_map_point_pair(m, x, ::Type{T}) where {T} = 64 | convert(Map{T}, m), convert(T, x) 65 | _promote_map_point_pair(m, x, ::Type{Any}) = m, x 66 | 67 | # Some exceptions: 68 | # - types match, do nothing 69 | promote_map_point_pair(m::Map{T}, x::T) where {T} = m, x 70 | # - an Any map, do nothing 71 | promote_map_point_pair(m::Map{Any}, x) = m, x 72 | # - tuples: these are typically composite maps, promotion may happen later 73 | promote_map_point_pair(m::Map{<:Tuple}, x::Tuple) = m, x 74 | # - abstract vectors: promotion may be expensive 75 | promote_map_point_pair(m::Map{<:AbstractVector}, x::AbstractVector) = m, x 76 | # - SVector: promotion is likely cheap 77 | promote_map_point_pair(m::EuclideanMap{N,T}, x::AbstractVector{S}) where {N,S,T} = 78 | _promote_map_point_pair(m, x, SVector{N,promote_type(S,T)}) 79 | 80 | # For maps of type Map{T}, we call promote_map_point_pair and then applymap 81 | promote_and_apply(m::Map, x) = applymap(promote_map_point_pair(m,x)...) 82 | 83 | applymap!(y, m, x) = y .= m(x) 84 | 85 | # Fallback for functions that are not of type AbstractMap 86 | applymap(m, x) = m(x) 87 | applymap(m::AbstractMap, x) = error("Please implement applymap for map $(m)") 88 | 89 | isvectorvalued_type(::Type{T}) where {T<:Number} = true 90 | isvectorvalued_type(::Type{T}) where {T<:AbstractVector} = true 91 | isvectorvalued_type(::Type{T}) where {T} = false 92 | 93 | "Is the map a vector-valued function, i.e., a function from Rn to Rm?" 94 | isvectorvalued(m) = 95 | isvectorvalued_type(domaintype(m)) && isvectorvalued_type(codomaintype(m)) 96 | 97 | import Base: size 98 | @deprecate size(m::AbstractMap) mapsize(m) 99 | @deprecate size(m::AbstractMap, i) mapsize(m, i) 100 | @deprecate issquare(m::AbstractMap) issquaremap(m) 101 | 102 | # mapsize should be defined for vector valued maps 103 | # The size of a map equals the size of its jacobian 104 | # The jacobian can be a number, a vector, an adjoint vector, or a matrix 105 | mapsize(m, i) = _mapsize(m, i, mapsize(m)) 106 | _mapsize(m, i, size::Tuple{Int,Int}) = i <= 2 ? size[i] : 1 107 | _mapsize(m, i, size::Tuple{Int}) = i <= 1 ? size[i] : 1 108 | _mapsize(m, i, size::Tuple{}) = 1 109 | 110 | "Is the given map a square map?" 111 | issquaremap(m) = isvectorvalued(m) && (mapsize(m,1) == mapsize(m,2)) 112 | 113 | isoverdetermined(m) = mapsize(m,1) >= mapsize(m,2) 114 | isunderdetermined(m) = mapsize(m,1) <= mapsize(m,2) 115 | 116 | is_scalar_to_vector(m) = mapsize(m) isa Tuple{Int} 117 | is_vector_to_scalar(m) = mapsize(m) isa Tuple{Int,Int} && codomaintype(m)<:Number 118 | is_scalar_to_scalar(m) = mapsize(m) == () 119 | is_vector_to_vector(m) = mapsize(m) isa Tuple{Int,Int} && !is_vector_to_scalar(m) 120 | 121 | 122 | # Display routines 123 | map_stencil(m::AbstractMap, x) = [Display.SymbolObject(m), '(', x, ')'] 124 | map_stencil_broadcast(m::AbstractMap, x) = [Display.SymbolObject(m), ".(", x, ')'] 125 | -------------------------------------------------------------------------------- /src/domains/trivial.jl: -------------------------------------------------------------------------------- 1 | 2 | # The trivial domains include the empty space and the full space. 3 | 4 | "The empty space." 5 | struct EmptySpace{T} <: Domain{T} 6 | end 7 | 8 | const AnyEmptySpace = EmptySpace{Any} 9 | 10 | EmptySpace() = EmptySpace{Float64}() 11 | EmptySpace(::Type{T}) where {T} = EmptySpace{T}() 12 | 13 | similardomain(::EmptySpace, ::Type{T}) where {T} = EmptySpace{T}() 14 | 15 | "Return the empty space with the same element type as the given domain." 16 | emptyspace(d) = emptyspace(eltype(d)) 17 | emptyspace(::Type{T}) where {T} = EmptySpace{T}() 18 | 19 | indomain(x::T, d::EmptySpace{T}) where {T} = false 20 | approx_indomain(x, d::EmptySpace, tolerance) = in(x, d) 21 | 22 | show(io::IO, d::EmptySpace) = print(io, "{} (empty domain)") 23 | 24 | isempty(d::EmptySpace) = true 25 | 26 | isopenset(d::EmptySpace) = true 27 | isclosedset(d::EmptySpace) = true 28 | 29 | boundary(d::EmptySpace) = d 30 | interior(d::EmptySpace) = d 31 | closure(d::EmptySpace) = d 32 | boundingbox(d::EmptySpace) = d 33 | 34 | distance_to(d::EmptySpace, x) = convert(prectype(d), Inf) 35 | 36 | # Arithmetic operations 37 | 38 | issubset1(d1::EmptySpace, d2) = true 39 | issubset2(d1, d2::EmptySpace) = isempty(d1) 40 | 41 | # setdiffdomain2(d1, d2::EmptySpace) = d2 42 | 43 | map_domain(map::Map{T}, d::EmptySpace{T}) where {T} = d 44 | mapped_domain(map::Map, d::EmptySpace) = EmptySpace{codomaintype(map)}() 45 | 46 | ==(d1::EmptySpace, d2::EmptySpace) = true 47 | isequal1(d1::EmptySpace, d2) = isempty(d2) 48 | isequal2(d1, d2::EmptySpace) = isempty(d1) 49 | hash(d::EmptySpace, h::UInt) = hash("EmptySpace", h) 50 | 51 | 52 | """ 53 | A domain that represents the full space. 54 | 55 | The element type `T` in `FullSpace{T}` should only be seen as an indication of 56 | the expected types of the elements in the context where the domain is intended 57 | to be used. Due to the default, loose interpretation of `T`, any `FullSpace{T}` 58 | actually contains any `x` regardless of the type of `x`. For a strict domain 59 | of all elements of type `T`, or elements convertible exactly to `T`, use 60 | `TypeDomain{T}`. 61 | """ 62 | struct FullSpace{T} <: Domain{T} end 63 | 64 | const AnyFullSpace = FullSpace{Any} 65 | 66 | FullSpace() = FullSpace{Float64}() 67 | FullSpace(d) = FullSpace{eltype(d)}() 68 | 69 | "Return the full space with the same element type as the given domain." 70 | fullspace(d) = fullspace(eltype(d)) 71 | fullspace(::Type{T}) where {T} = FullSpace{T}() 72 | 73 | isfullspace(d::FullSpace) = true 74 | isfullspace(d::Domain) = false 75 | 76 | similardomain(::FullSpace, ::Type{T}) where {T} = FullSpace{T}() 77 | 78 | euclideanspace(n::Val{N}) where {N} = euclideanspace(n, Float64) 79 | euclideanspace(::Val{N}, ::Type{T}) where {N,T} = FullSpace{SVector{N,T}}() 80 | 81 | indomain(x::T, d::FullSpace{T}) where {T} = true 82 | approx_indomain(x, d::FullSpace, tolerance) = in(x, d) 83 | 84 | show(io::IO, d::FullSpace) = print(io, "{x} (full space)") 85 | 86 | # We choose the origin as a point in the full space 87 | point_in_domain(d::FullSpace) = zero(eltype(d)) 88 | 89 | isempty(::FullSpace) = false 90 | 91 | isopenset(d::FullSpace) = true 92 | isclosedset(d::FullSpace) = true 93 | 94 | boundary(d::FullSpace{T}) where {T} = EmptySpace{T}() 95 | interior(d::FullSpace) = d 96 | closure(d::FullSpace) = d 97 | 98 | distance_to(d::FullSpace, x) = zero(prectype(d)) 99 | 100 | # Arithmetic operations 101 | 102 | issubset1(d1::FullSpace, d2) = isfullspace(d2) 103 | issubset2(d1, d2::FullSpace) = true 104 | 105 | map_domain(m::AbstractAffineMap{T}, d::FullSpace{T}) where {T} = d 106 | 107 | ==(d1::FullSpace, d2::FullSpace) = true 108 | isequal1(d1::FullSpace, d2) = isfullspace(d2) 109 | isequal2(d1, d2::FullSpace) = isfullspace(d1) 110 | hash(d::FullSpace, h::UInt) = hash("FullSpace", h) 111 | 112 | 113 | convert(::Type{Domain}, ::Type{T}) where T = FullSpace{T}() 114 | convert(::Type{Domain{S}}, ::Type{T}) where {T,S} = convert(Domain{S}, convert(Domain, T)) 115 | 116 | infimum(d::FullSpace{T}) where {T} = typemin(T) 117 | supremum(d::FullSpace{T}) where {T} = typemax(T) 118 | 119 | 120 | 121 | """ 122 | The domain of all objects of type `T` and all objects convertible exactly to 123 | type `T`. 124 | """ 125 | struct TypeDomain{T} <: Domain{T} end 126 | 127 | "Return the domain for the element type of the given domain." 128 | typedomain(d) = typedomain(eltype(d)) 129 | typedomain(::Type{T}) where {T} = TypeDomain{T}() 130 | 131 | iscompatiblepair(x::T, d::TypeDomain{T}) where {T} = true 132 | iscompatiblepair(x::T, d::TypeDomain{Any}) where {T} = true 133 | function iscompatiblepair(x::S, d::TypeDomain{T}) where {S,T} 134 | # There is no generic function to check whether x can be converted to T. 135 | # A try-catch is not optimal, but it is a failsafe backup. 136 | # We can at least efficiently check promotion: if x can be converted to 137 | # T, then S and T must have a joined supertype. 138 | # For specific T and S, it would be better to specialize this function. 139 | promote_type(S,T) != Any && 140 | try 141 | convert(T, x) == x 142 | catch 143 | false 144 | end 145 | end 146 | 147 | # Suppress the warning about incompatibility because it is not likely to be 148 | # relevant for this domain 149 | compatible_or_false(x, domain::TypeDomain) = iscompatiblepair(x, domain) 150 | 151 | indomain(x::T, d::TypeDomain{T}) where {T} = true 152 | approx_indomain(x, d::TypeDomain, tolerance) = in(x, d) 153 | 154 | ==(d1::TypeDomain{S}, d2::TypeDomain{T}) where {S,T} = S==T 155 | 156 | 157 | # some special cases 158 | iscompatiblepair(x::Irrational, d::TypeDomain{<:Real}) = true 159 | -------------------------------------------------------------------------------- /test/test_generic_domain.jl: -------------------------------------------------------------------------------- 1 | import DomainSets: factors, nfactors, factor 2 | 3 | 4 | widen_eltype(::Type{T}) where {T<:Number} = widen(T) 5 | widen_eltype(::Type{SVector{N,T}}) where {N,T<:Number} = SVector{N,widen(T)} 6 | widen_eltype(::Type{Vector{T}}) where {T<:Number} = Vector{widen(T)} 7 | 8 | 9 | # We test the generic functionality of a domain. 10 | # These tests check whether the given domain correctly implements the 11 | # interface of a domain. 12 | function test_generic_domain(d::Domain) 13 | @test isreal(d) == isreal(eltype(d)) 14 | @test isreal(d) == isreal(numtype(d)) 15 | 16 | @test convert(Domain{eltype(d)}, d) == d 17 | @test convert(Domain{widen_eltype(eltype(d))}, d) == d 18 | @test prectype(convert_prectype(d, BigFloat)) == BigFloat 19 | 20 | if !isempty(d) 21 | x = point_in_domain(d) 22 | @test x ∈ d 23 | @test approx_in(x, d, 0.01) 24 | @test_throws ErrorException approx_in(x, d, -1) 25 | @test in.([x,x], d) == in.([x,x], Ref(d)) 26 | @test approx_in.([x,x], d, 0.01) == approx_in.([x,x], Ref(d), 0.01) 27 | else 28 | try 29 | x = point_in_domain(d) 30 | @test false 31 | catch 32 | end 33 | end 34 | @test canonicaldomain(DomainSets.Equal(), d) == d 35 | if hascanonicaldomain(d) 36 | cd = canonicaldomain(d) 37 | @test mapfrom_canonical(d) == mapto(cd, d) 38 | @test mapto_canonical(d) == mapto(d, cd) 39 | x1 = point_in_domain(cd) 40 | @test mapfrom_canonical(d, x1) ∈ d 41 | @test mapto_canonical(d, x) ∈ cd 42 | else 43 | @test mapto_canonical(d) == IdentityMap{eltype(d)}(dimension(d)) 44 | @test mapfrom_canonical(d) == IdentityMap{eltype(d)}(dimension(d)) 45 | end 46 | if hasparameterization(d) 47 | par = parameterdomain(d) 48 | @test mapfrom_parameterdomain(d) == mapto(par, d) 49 | xp = point_in_domain(par) 50 | @test approx_in(mapfrom_parameterdomain(d, xp), d) 51 | end 52 | if iscomposite(d) 53 | @test ncomponents(d) == length(components(d)) 54 | els = components(d) 55 | @test all([component(d,i) == els[i] for i in 1:ncomponents(d)]) 56 | if d isa ProductDomain 57 | @test factors(d) == components(d) 58 | @test nfactors(d) == ncomponents(d) 59 | if nfactors(d) > 0 60 | @test factor(d, 1) == component(d, 1) 61 | end 62 | end 63 | end 64 | end 65 | 66 | @testset "generic domain interface" begin 67 | domains = [ 68 | 0..1, 69 | UnitInterval(), 70 | ChebyshevInterval(), 71 | HalfLine(), 72 | NegativeHalfLine(), 73 | UnitInterval()^3, 74 | (0..1) × (2.0..3) × (3..4), 75 | UnitBall(), 76 | VectorUnitBall(), 77 | VectorUnitBall(8), 78 | UnitDisk(), 79 | VectorUnitDisk(), 80 | UnitCircle(), 81 | VectorUnitCircle(), 82 | UnitSphere(), 83 | VectorUnitSphere(), 84 | UnitSimplex(Val(2)), 85 | UnitSimplex(2), 86 | DomainSets.WrappedDomain(0..2.0) 87 | ] 88 | 89 | @testset "generic domains" begin 90 | for domain in domains 91 | test_generic_domain(domain) 92 | end 93 | end 94 | 95 | @testset "generic functionality" begin 96 | struct SomeDomain <: Domain{Float64} 97 | end 98 | @test_throws MethodError 0.5 ∈ SomeDomain() 99 | @test_throws MethodError approx_in(0.5, SomeDomain()) 100 | 101 | if VERSION < v"1.6-" 102 | @test_logs (:warn, "`in`: incompatible combination of point: Tuple{Float64,Float64} and domain eltype: SArray{Tuple{2},Float64,1,2}. Returning false.") (0.5,0.2) ∈ UnitCircle() 103 | @test_logs (:warn, "`in`: incompatible combination of point: Tuple{Float64,Float64} and domain eltype: SArray{Tuple{2},Float64,1,2}. Returning false.") approx_in((0.5,0.2), UnitCircle()) 104 | else 105 | @test_logs (:warn, "`in`: incompatible combination of point: Tuple{Float64, Float64} and domain eltype: SVector{2, Float64}. Returning false.") (0.5,0.2) ∈ UnitCircle() 106 | @test_logs (:warn, "`in`: incompatible combination of point: Tuple{Float64, Float64} and domain eltype: SVector{2, Float64}. Returning false.") approx_in((0.5,0.2), UnitCircle()) 107 | end 108 | 109 | # some functionality in broadcast 110 | @test 2 * (1..2) == 2 .* (1..2) 111 | @test (1..2) * 2 == (1..2) .* 2 112 | @test (1..2) / 2 ≈ (0.5..1) 113 | @test 2 \ (1..2) ≈ (0.5..1) 114 | @test_throws MethodError (0..1) + 0.4 115 | 116 | # promotion 117 | @test DomainSets.promote_domains() == () 118 | s1 = Set([0..1,2..3,3..4.0]) 119 | @test s1 isa Set{<:Domain{Float64}} 120 | @test DomainSets.promote_domains(s1) == s1 121 | s2 = Set([0..1,2..3,3..4.0, Point(2)]) 122 | @test s2 isa Set{Domain} 123 | @test DomainSets.promote_domains(s2) isa Set{<:Domain{Float64}} 124 | @test DomainSets.promote(0..1.0, [1,2,3]) isa Tuple{Interval,Vector{Float64}} 125 | @test DomainSets.promote([1,2,3], 0..1.0) isa Tuple{Vector{Float64},Interval} 126 | 127 | # compatible point-domain pairs 128 | @test DomainSets.iscompatiblepair(0.5, 0..1) 129 | @test DomainSets.iscompatiblepair(0.5, 0..1.0) 130 | @test !DomainSets.iscompatiblepair(0.5, UnitBall()) 131 | @test DomainSets.iscompatiblepair(0.5, [0.3]) 132 | @test DomainSets.iscompatiblepair(0, [0.3]) 133 | @test DomainSets.iscompatiblepair(0.5, [1, 2, 3]) 134 | @test DomainSets.iscompatiblepair(0, Set([1, 2, 3])) 135 | @test DomainSets.iscompatiblepair(0.5, Set([1, 2, 3])) 136 | @test DomainSets.iscompatiblepair(0, Set([1.0, 2, 3])) 137 | 138 | @test DomainSets.convert_eltype(Float64, Set([1,2])) isa Set{Float64} 139 | @test_throws ErrorException DomainSets.convert_eltype(Float64, (1,2)) isa NTuple{2,Float64} 140 | @test DomainSets.convert_eltype(Int, (1,2)) == (1,2) 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /src/maps/basic.jl: -------------------------------------------------------------------------------- 1 | 2 | "Supertype of identity maps." 3 | abstract type IdentityMap{T} <: Map{T} end 4 | 5 | IdentityMap(n::Int) = DynamicIdentityMap(n) 6 | IdentityMap() = StaticIdentityMap() 7 | IdentityMap(::Val{N}) where {N} = StaticIdentityMap(Val(N)) 8 | 9 | IdentityMap{T}(n::Int) where {T} = DynamicIdentityMap{T}(n) 10 | IdentityMap{T}(n::Int) where {T<:StaticTypes} = StaticIdentityMap{T}() 11 | IdentityMap{T}(::Val{N}) where {N,T} = StaticIdentityMap{T}(Val(N)) 12 | IdentityMap{T}() where {T} = StaticIdentityMap{T}() 13 | 14 | applymap(map::IdentityMap, x) = x 15 | applymap!(y, map::IdentityMap, x) = y .= x 16 | 17 | inverse(m::IdentityMap) = m 18 | inverse(m::IdentityMap, x) = x 19 | 20 | islinear(::IdentityMap) = true 21 | isreal(::IdentityMap{T}) where {T} = isreal(T) 22 | 23 | isidentity(::IdentityMap) = true 24 | isidentity(m::Map{T}) where {T} = m == StaticIdentityMap{T}() 25 | 26 | mapsize(m::IdentityMap{T}) where {T<:Number} = () 27 | mapsize(m::IdentityMap{T}) where {T} = (euclideandimension(T),euclideandimension(T)) 28 | 29 | matrix(m::IdentityMap) = identitymatrix(m) 30 | vector(m::IdentityMap) = zerovector(m) 31 | 32 | jacobian(m::IdentityMap) = ConstantMap(matrix(m)) 33 | jacobian(m::IdentityMap, x) = matrix(m) 34 | 35 | jacdet(m::IdentityMap, x) = 1 36 | 37 | determinantmap(m::IdentityMap{T}) where {T} = UnityMap{T,prectype(T)}() 38 | 39 | mapcompose(m1::IdentityMap) = m1 40 | mapcompose(m1::IdentityMap, maps...) = mapcompose(maps...) 41 | mapcompose2(m1, m2::IdentityMap, maps...) = mapcompose(m1, maps...) 42 | 43 | show(io::IO, m::IdentityMap{T}) where {T} = print(io, "x -> x") 44 | Display.object_parentheses(m::IdentityMap) = true 45 | 46 | "The identity map for variables of type `T`." 47 | struct StaticIdentityMap{T} <: IdentityMap{T} 48 | end 49 | 50 | StaticIdentityMap() = StaticIdentityMap{Float64}() 51 | StaticIdentityMap(::Val{N}) where {N} = StaticIdentityMap{SVector{N,Float64}}() 52 | 53 | StaticIdentityMap{T}(n::Int) where {T} = 54 | (@assert n == euclideandimension(T); StaticIdentityMap{T}()) 55 | StaticIdentityMap{T}(::Val{N}) where {N,T} = 56 | (@assert N == euclideandimension(T); StaticIdentityMap{T}()) 57 | 58 | similarmap(m::StaticIdentityMap, ::Type{T}) where {T<:StaticTypes} = StaticIdentityMap{T}() 59 | similarmap(m::StaticIdentityMap, ::Type{T}) where {T} = 60 | DynamicIdentityMap{T}(euclideandimension(T)) 61 | 62 | convert(::Type{StaticIdentityMap{T}}, ::StaticIdentityMap) where {T} = StaticIdentityMap{T}() 63 | 64 | ==(m1::StaticIdentityMap, m2::StaticIdentityMap) = true 65 | hash(m::StaticIdentityMap, h::UInt) = hash("StaticIdentityMap", h) 66 | 67 | "Identity map with dynamic size determined by a dimension field." 68 | struct DynamicIdentityMap{T} <: IdentityMap{T} 69 | dimension :: Int 70 | end 71 | 72 | const EuclideanIdentityMap{N,T} = StaticIdentityMap{SVector{N,T}} 73 | const VectorIdentityMap{T} = DynamicIdentityMap{Vector{T}} 74 | 75 | DynamicIdentityMap(dimension::Int) = VectorIdentityMap(dimension) 76 | VectorIdentityMap(dimension::Int) = VectorIdentityMap{Float64}(dimension) 77 | 78 | mapsize(m::DynamicIdentityMap) = (m.dimension, m.dimension) 79 | 80 | similarmap(m::DynamicIdentityMap, ::Type{T}) where {T} = 81 | DynamicIdentityMap{T}(m.dimension) 82 | similarmap(m::DynamicIdentityMap, ::Type{T}) where {T<:StaticTypes} = 83 | StaticIdentityMap{T}() 84 | 85 | ==(m1::DynamicIdentityMap, m2::DynamicIdentityMap) = m1.dimension == m2.dimension 86 | hash(m::DynamicIdentityMap, h::UInt) = hashrec("DynamicIdentityMap", m.dimension, h) 87 | 88 | 89 | "The supertype of constant maps from `T` to `U`." 90 | abstract type ConstantMap{T,U} <: TypedMap{T,U} end 91 | 92 | applymap(m::ConstantMap, x) = constant(m) 93 | 94 | isconstant(m::AbstractMap) = false 95 | isconstant(m::ConstantMap) = true 96 | 97 | isreal(m::ConstantMap{T,U}) where {T,U} = 98 | isreal(T) && isreal(U) && isreal(constant(m)) 99 | 100 | mapsize(m::ConstantMap) = _constant_mapsize(m, constant(m)) 101 | _constant_mapsize(m::ConstantMap{T,U}, c) where {T<:Number,U<:Number} = () 102 | _constant_mapsize(m::ConstantMap{T,U}, c) where {T<:Number,U} = (length(c),) 103 | _constant_mapsize(m::ConstantMap{T,U}, c) where {T,U<:Number} = (1,euclideandimension(T)) 104 | _constant_mapsize(m::ConstantMap{T,U}, c) where {T,U} = (length(c), euclideandimension(T)) 105 | 106 | matrix(m::ConstantMap) = zeromatrix(m) 107 | vector(m::ConstantMap) = constant(m) 108 | 109 | jacobian(m::ConstantMap{T}) where {T} = ConstantMap{T}(matrix(m)) 110 | jacobian(m::ConstantMap, x) = matrix(m) 111 | 112 | jacdet(::ConstantMap, x) = 0 113 | 114 | determinantmap(m::ConstantMap{T}) where {T} = ConstantMap{T}(det(constant(m))) 115 | absmap(m::ConstantMap{T}) where {T} = ConstantMap{T}(abs(constant(m))) 116 | 117 | ==(m1::ConstantMap, m2::ConstantMap) = constant(m1)==constant(m2) 118 | hash(m::ConstantMap, h::UInt) = hashrec("ConstantMap", constant(m), h) 119 | 120 | similarmap(m::ConstantMap, ::Type{T}) where {T} = ConstantMap{T}(constant(m)) 121 | similarmap(m::ConstantMap, ::Type{T}, ::Type{U}) where {T,U} = ConstantMap{T,U}(m.c) 122 | 123 | ConstantMap() = ConstantMap{Float64}() 124 | ConstantMap(c) = FixedConstantMap(c) 125 | ConstantMap{T}() where {T} = UnityMap{T}() 126 | ConstantMap{T}(c) where {T} = FixedConstantMap{T}(c) 127 | ConstantMap{T,U}() where {T,U} = UnityMap{T,U}() 128 | ConstantMap{T,U}(c) where {T,U} = FixedConstantMap{T,U}(c) 129 | 130 | show(io::IO, m::ConstantMap{T}) where {T} = print(io, "x -> $(constant(m))") 131 | Display.object_parentheses(m::ConstantMap) = true 132 | 133 | 134 | "The zero map `f(x) = 0`." 135 | struct ZeroMap{T,U} <: ConstantMap{T,U} 136 | end 137 | ZeroMap{T}() where {T} = ZeroMap{T,T}() 138 | constant(m::ZeroMap{T,U}) where {T,U} = zero(U) 139 | similarmap(m::ZeroMap{S,U}, ::Type{T}) where {T,S,U} = ZeroMap{T,U}() 140 | similarmap(m::ZeroMap, ::Type{T}, ::Type{U}) where {T,U} = ZeroMap{T,U}() 141 | 142 | 143 | "The unity map `f(x) = 1`." 144 | struct UnityMap{T,U} <: ConstantMap{T,U} 145 | end 146 | UnityMap{T}() where {T} = UnityMap{T,real(numtype(T))}() 147 | constant(m::UnityMap{T,U}) where {T,U} = one(U) 148 | similarmap(m::UnityMap{S,U}, ::Type{T}) where {T,S,U} = UnityMap{T,U}() 149 | similarmap(m::UnityMap, ::Type{T}, ::Type{U}) where {T,U} = UnityMap{T,U}() 150 | 151 | 152 | "The constant map `f(x) = c`." 153 | struct FixedConstantMap{T,U} <: ConstantMap{T,U} 154 | c :: U 155 | end 156 | FixedConstantMap{T}(c::U) where {T,U} = FixedConstantMap{T,U}(c) 157 | FixedConstantMap(c::T) where {T} = FixedConstantMap{T}(c) 158 | constant(m::FixedConstantMap) = m.c 159 | -------------------------------------------------------------------------------- /src/generic/lazy.jl: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | A lazy domain evaluates its membership function on the fly in terms of that of 4 | other domains. 5 | 6 | The `in(x, domain::LazyDomain)` applies three types of transformations: 7 | 1. Point mapping: `y = tointernalpoint(domain, x)` 8 | 2. Distribution of `y` over member domains given by `components(domain)` 9 | 3. Combination of the outputs into a single boolean result. 10 | 11 | The distribution step is determined by the result of `composition(domain)`, 12 | see `composition`. The combination is performed by `combine`. Mapping between 13 | points of the lazy domain and points of its member domains is described by 14 | `y = tointernalpoint(domain, x)` and `x = toexternalpoint(domain, y)`. 15 | """ 16 | abstract type LazyDomain{T} <: Domain{T} end 17 | 18 | "Translate a point of the lazy domain to a point (or points) of the composing domain." 19 | tointernalpoint(d::LazyDomain, x) = x 20 | "Inverse of `tointernalpoint`." 21 | toexternalpoint(d::LazyDomain{T}, y) where {T} = T(y) 22 | 23 | Base.getindex(d::LazyDomain, I::ComponentIndex...) = 24 | component(d, map(Indexing.to_index, I)...) 25 | 26 | """ 27 | A single lazy domain is defined in terms of a single domain. 28 | 29 | It has no composition and no combination of its `in` function. 30 | """ 31 | abstract type SimpleLazyDomain{T} <: LazyDomain{T} end 32 | 33 | superdomain(d::SimpleLazyDomain) = d.domain 34 | 35 | components(d::SimpleLazyDomain) = (superdomain(d),) 36 | 37 | indomain(x, d::SimpleLazyDomain) = in(tointernalpoint(d, x), superdomain(d)) 38 | approx_indomain(x, d::SimpleLazyDomain, tolerance) = approx_in(tointernalpoint(d, x), superdomain(d), tolerance) 39 | 40 | "A composite lazy domain is defined in terms of multiple domains." 41 | abstract type CompositeDomain{T} <: LazyDomain{T} end 42 | 43 | components(d::CompositeDomain) = d.domains 44 | 45 | # (daanhb) Note: upon revisiting this code, abstracting away the nature of 46 | # the composition may have been a step too far - it makes the concrete composite 47 | # domains harder to understand 48 | 49 | """ 50 | Supertype of all compositions of a lazy domain. The composition determines how 51 | the point `x` is distributed to the member domains of a lazy domain. 52 | 53 | Three compositions implemented in the package are: 54 | - `NoComposedMap`: the lazy domain encapsulates a single domain and `x` is passed 55 | through unaltered 56 | - `Combination`: the lazy domain has several members and `x` is passed to the `in` 57 | method of all members 58 | - `Product`: the lazy domain has several members and the components of `x` are 59 | passed to the components of the lazy domain 60 | """ 61 | abstract type LazyComposedMap end 62 | 63 | struct NoComposedMap <: LazyComposedMap end 64 | struct Combination <: LazyComposedMap end 65 | struct Product <: LazyComposedMap end 66 | 67 | composition(d::CompositeDomain) = NoComposedMap() 68 | 69 | indomain(x, d::CompositeDomain) = _indomain(tointernalpoint(d, x), d, composition(d), components(d)) 70 | _indomain(x, d, ::NoComposedMap, domains) = in(x, domains[1]) 71 | _indomain(x, d, ::Combination, domains) = combine(d, map(d->in(x, d), domains)) 72 | _indomain(x, d, ::Product, domains) = mapreduce(in, &, x, domains) 73 | 74 | approx_indomain(x, d::CompositeDomain, tolerance) = 75 | _approx_indomain(tointernalpoint(d, x), d, tolerance, composition(d), components(d)) 76 | 77 | _approx_indomain(x, d, tolerance, ::NoComposedMap, domains) = 78 | approx_in(x, domains[1], tolerance) 79 | _approx_indomain(x, d, tolerance, ::Combination, domains) = 80 | combine(d, map(d -> approx_in(x, d, tolerance), domains)) 81 | _approx_indomain(x, d, tolerance, ::Product, domains) = 82 | mapreduce((u,v)->approx_in(u, v, tolerance), &, x, domains) 83 | 84 | point_in_domain(d::SimpleLazyDomain) = toexternalpoint(d, point_in_domain(superdomain(d))) 85 | point_in_domain(d::CompositeDomain) = toexternalpoint(d, map(point_in_domain, components(d))) 86 | 87 | ==(a::D, b::D) where {D<:CompositeDomain} = components(a) == components(b) 88 | 89 | 90 | dimension(d::SimpleLazyDomain{Vector{T}}) where {T} = dimension(superdomain(d)) 91 | function dimension(d::CompositeDomain{Vector{T}}) where {T} 92 | dim = dimension(component(d,1)) 93 | @assert all(isequal(dim), map(dimension, components(d))) 94 | dim 95 | end 96 | 97 | """ 98 | Combine the outputs of `in` of member domains into a single output of the lazy 99 | domain. 100 | """ 101 | combine 102 | 103 | 104 | "Abstract supertype for domains that wrap another domain." 105 | abstract type DerivedDomain{T} <: SimpleLazyDomain{T} end 106 | 107 | isempty(d::DerivedDomain) = isempty(superdomain(d)) 108 | 109 | # We assume the derived domain and the superdomain are equal 110 | canonicaldomain(d::DerivedDomain) = canonicaldomain(superdomain(d)) 111 | canonicaldomain(::Equal, d::DerivedDomain) = canonicaldomain(Equal(), superdomain(d)) 112 | canonicaldomain(::Isomorphic, d::DerivedDomain) = canonicaldomain(Isomorphic(), superdomain(d)) 113 | canonicaldomain(::Parameterization, d::DerivedDomain) = canonicaldomain(Parameterization(), superdomain(d)) 114 | 115 | boundingbox(d::DerivedDomain) = boundingbox(superdomain(d)) 116 | interior(d::DerivedDomain) = interior(superdomain(d)) 117 | closure(d::DerivedDomain) = closure(superdomain(d)) 118 | 119 | """ 120 | A `WrappedDomain` is a wrapper around an object that implements the domain 121 | interface, and that is itself a domain. 122 | """ 123 | struct WrappedDomain{T,D} <: DerivedDomain{T} 124 | domain :: D 125 | end 126 | 127 | WrappedDomain(domain::Domain{T}) where {T} = WrappedDomain{T}(domain) 128 | WrappedDomain(domain) = WrappedDomain{eltype(domain)}(domain) 129 | 130 | WrappedDomain{T}(domain::D) where {T,D<:Domain{T}} = WrappedDomain{T,D}(domain) 131 | WrappedDomain{T}(domain::Domain) where {T} = WrappedDomain{T}(convert(Domain{T}, domain)) 132 | WrappedDomain{T}(domain) where {T} = WrappedDomain{T,typeof(domain)}(domain) 133 | 134 | similardomain(d::WrappedDomain, ::Type{T}) where {T} = WrappedDomain{T}(d.domain) 135 | 136 | # Anything can be converted to a domain by wrapping it. An error will be thrown 137 | # if the object does not support `eltype`. 138 | convert(::Type{Domain}, v::Domain) = v 139 | convert(::Type{Domain}, v) = WrappedDomain(v) 140 | convert(::Type{Domain{T}}, v) where {T} = WrappedDomain{T}(v) 141 | 142 | ==(d1::WrappedDomain, d2::WrappedDomain) = superdomain(d1)==superdomain(d2) 143 | 144 | "Example of a domain that wraps another domain and thus obtains its own type." 145 | struct ExampleNamedDomain{T,D} <: DerivedDomain{T} 146 | domain :: D 147 | end 148 | ExampleNamedDomain(domain::D) where {T,D<:Domain{T}} = ExampleNamedDomain{T,D}(domain) 149 | -------------------------------------------------------------------------------- /src/DomainSets.jl: -------------------------------------------------------------------------------- 1 | module DomainSets 2 | 3 | using StaticArrays 4 | using LinearAlgebra, Statistics 5 | import LinearAlgebra: cross, ×, pinv 6 | import Random 7 | using Random: AbstractRNG 8 | 9 | using IntervalSets 10 | using CompositeTypes, CompositeTypes.Display, CompositeTypes.Indexing 11 | 12 | # deprecations in v0.5 13 | @deprecate IntersectionDomain IntersectDomain 14 | @deprecate DifferenceDomain SetdiffDomain 15 | @deprecate FlexibleUnitCube DynamicUnitCube 16 | @deprecate FlexibleUnitSphere DynamicUnitSphere 17 | @deprecate FlexibleUnitBall DynamicUnitBall 18 | @deprecate Composition ComposedMap 19 | 20 | @deprecate element component 21 | @deprecate elements components 22 | @deprecate numelements ncomponents 23 | 24 | 25 | ################################ 26 | ## Exhaustive list of imports 27 | ################################ 28 | 29 | # Generated functions 30 | import Base: @ncall 31 | 32 | # Operator symbols 33 | import Base: *, +, -, /, \, ^, 34 | |, &, 35 | ∪, ∩, 36 | ==, isapprox, 37 | ∘, 38 | # Set operations 39 | intersect, union, setdiff, in, isempty, minimum, maximum, 40 | issubset, 41 | # Arrays 42 | eltype, hash, isreal, 43 | # Types, promotions and conversions 44 | convert, promote, 45 | # Display 46 | show 47 | 48 | # IntervalSets 49 | import IntervalSets: (..), endpoints, Domain, AbstractInterval, TypedEndpointsInterval, 50 | leftendpoint, rightendpoint, isleftopen, isleftclosed, 51 | isrightopen, isrightclosed, isopenset, isclosedset, 52 | infimum, supremum 53 | export .. 54 | 55 | 56 | import CompositeTypes: component, components 57 | 58 | 59 | ################################ 60 | ## Exhaustive list of exports 61 | ################################ 62 | 63 | ## Utils 64 | 65 | # from util/common.jl 66 | export prectype, numtype, 67 | convert_numtype, promote_numtype, 68 | convert_prectype, promote_prectype, 69 | iscomposite, component, components, ncomponents 70 | 71 | ## Maps 72 | 73 | # from maps/map.jl 74 | export AbstractMap, Map, TypedMap, 75 | applymap, 76 | domaintype, codomaintype, 77 | inverse, leftinverse, rightinverse, 78 | mapsize, jacobian, jacdet, diffvolume 79 | # from maps/composite.jl 80 | export ComposedMap, composedmap, ∘ 81 | # from maps/product.jl 82 | export ProductMap, productmap 83 | # from maps/basic.jl 84 | export IdentityMap, 85 | StaticIdentityMap, VectorIdentityMap, 86 | ZeroMap, UnityMap, ConstantMap, 87 | isconstant, constant 88 | # from maps/affine.jl 89 | export AffineMap, Translation, LinearMap, 90 | matrix, vector, 91 | islinear, isaffine 92 | 93 | 94 | ## Generic domains 95 | 96 | # from generic/domain.jl 97 | export Domain, EuclideanDomain, VectorDomain, 98 | dimension, 99 | approx_in, 100 | isopenset, isclosedset, iscompact, 101 | boundary, ∂, 102 | boundingbox, 103 | interior, closure, 104 | volume, 105 | point_in_domain, 106 | normal, tangents, distance_to, 107 | canonicaldomain, mapto_canonical, mapfrom_canonical, hascanonicaldomain, 108 | mapto, 109 | parameterdomain, parameterization, hasparameterization, 110 | mapfrom_parameterdomain, mapto_parameterdomain 111 | 112 | # from generic/lazy.jl 113 | export superdomain 114 | 115 | # from generic/productdomain.jl 116 | export ProductDomain, productdomain, 117 | VcatDomain, ArrayProductDomain, TupleProductDomain 118 | 119 | # from generic/mapped.jl 120 | export MappedDomain, 121 | map_domain, 122 | mapped_domain, 123 | forward_map, 124 | inverse_map 125 | 126 | # from generic/setoperations.jl 127 | export UnionDomain, uniondomain, 128 | IntersectDomain, intersectdomain, 129 | SetdiffDomain, setdiffdomain 130 | 131 | # from applications/rotations.jl 132 | export rotate 133 | 134 | export infimum, supremum 135 | 136 | 137 | ## Specific domains 138 | 139 | # from domains/trivial.jl 140 | export EmptySpace, FullSpace, TypeDomain, 141 | emptyspace, fullspace, typedomain 142 | # from domains/numbers.jl 143 | export Integers, RealNumbers, Rationals, ComplexNumbers, 144 | ℕ, ℤ, ℚ, ℝ, ℂ, ℝ1, ℝ2, ℝ3, ℝ4 145 | # from domains/interval.jl 146 | export AbstractInterval, Interval, UnitInterval, ChebyshevInterval, 147 | OpenInterval, ClosedInterval, 148 | leftendpoint, rightendpoint, isleftopen, isrightopen, 149 | HalfLine, NegativeHalfLine 150 | # from domains/simplex.jl 151 | export UnitSimplex, 152 | StaticUnitSimplex, DynamicUnitSimplex, 153 | EuclideanUnitSimplex, VectorUnitSimplex, 154 | corners 155 | # from domains/point.jl 156 | export Point 157 | # from domains/ball.jl 158 | export Ball, UnitBall, 159 | center, radius, 160 | StaticUnitBall, DynamicUnitBall, 161 | EuclideanUnitBall, VectorUnitBall, 162 | Sphere, UnitSphere, 163 | StaticUnitSphere, DynamicUnitSphere, 164 | VectorUnitSphere, EuclideanUnitSphere, 165 | Disk, UnitDisk, VectorUnitDisk, 166 | UnitCircle, VectorUnitCircle, 167 | ComplexUnitCircle, ComplexUnitDisk, 168 | ellipse, ellipse_shape, cylinder 169 | # from domains/cube.jl 170 | export UnitCube, 171 | StaticUnitCube, DynamicUnitCube, 172 | EuclideanUnitCube, VectorUnitCube, 173 | UnitSquare, UnitCube, 174 | Rectangle 175 | # from domain/levelset.jl 176 | export LevelSet, ZeroSet, 177 | SublevelSet, SubzeroSet, 178 | SuperlevelSet, SuperzeroSet, 179 | pseudolevel 180 | # from domain/indicator.jl 181 | export IndicatorFunction 182 | 183 | ## Applications 184 | # from applications/rotation.jl 185 | export rotation_map, 186 | CartToPolarMap, PolarToCartMap 187 | 188 | include("util/common.jl") 189 | 190 | include("maps/map.jl") 191 | include("maps/lazy.jl") 192 | include("maps/inverse.jl") 193 | include("maps/jacobian.jl") 194 | include("maps/composite.jl") 195 | include("maps/product.jl") 196 | include("maps/isomorphism.jl") 197 | include("maps/basic.jl") 198 | include("maps/affine.jl") 199 | include("maps/arithmetics.jl") 200 | 201 | include("generic/domain.jl") 202 | include("generic/canonical.jl") 203 | include("generic/lazy.jl") 204 | include("generic/productdomain.jl") 205 | include("generic/setoperations.jl") 206 | include("generic/mapped.jl") 207 | include("generic/broadcast.jl") 208 | 209 | include("domains/trivial.jl") 210 | include("domains/numbers.jl") 211 | include("domains/levelset.jl") 212 | include("domains/point.jl") 213 | include("domains/interval.jl") 214 | include("domains/simplex.jl") 215 | include("domains/ball.jl") 216 | include("domains/cube.jl") 217 | include("domains/indicator.jl") 218 | include("domains/boundingbox.jl") 219 | 220 | include("applications/coordinates.jl") 221 | include("applications/random.jl") 222 | include("applications/rotation.jl") 223 | 224 | end # module 225 | -------------------------------------------------------------------------------- /src/util/common.jl: -------------------------------------------------------------------------------- 1 | 2 | 3 | isreal(::Type{<:Real}) = true 4 | isreal(::Type{<:Complex}) = false 5 | isreal(::Type{T}) where {T} = isreal(eltype(T)) 6 | 7 | const StaticTypes = Union{Number,<:StaticVector{N} where N,<:NTuple{N,Any} where N} 8 | 9 | "What is the euclidean dimension of the given type (if applicable)?" 10 | euclideandimension(::Type{T}) where {T <: Number} = 1 11 | euclideandimension(::Type{T}) where {N,T <: StaticVector{N}} = N 12 | euclideandimension(::Type{T}) where {N,T <: NTuple{N,Any}} = N 13 | # Does not apply to Vector{T}: we don't know its dimension 14 | 15 | unitvector(d::Domain{T}, dim) where {N,S,T<:SVector{N,S}} = SVector{N,S}(ntuple(i -> i==dim, N)) 16 | function unitvector(d::Domain{T}, dim) where {T<:AbstractVector} 17 | p = zeros(eltype(T), dimension(d)) 18 | p[dim] = 1 19 | p 20 | end 21 | unitvector(d::Domain{T}, dim) where {T<:Number} = (@assert dim==1; one(T)) 22 | 23 | origin(d::Domain{T}) where {T <: StaticTypes} = zero(T) 24 | function origin(d::Domain{T}) where {T <: AbstractVector} 25 | p = similar(point_in_domain(d)) 26 | fill!(p, 0) 27 | convert(T, p) 28 | end 29 | 30 | "Apply the `hash` function recursively to the given arguments." 31 | hashrec(x) = hash(x) 32 | hashrec(x, args...) = hash(x, hashrec(args...)) 33 | 34 | # Workaround for #88, manually compute the hash of an array using all its elements 35 | hashrec() = zero(UInt) 36 | function hashrec(A::AbstractArray, args...) 37 | h = hash(size(A)) 38 | for x in A 39 | h = hash(x, h) 40 | end 41 | hash(h, hashrec(args...)) 42 | end 43 | 44 | ################# 45 | # Precision type 46 | ################# 47 | 48 | "The floating point precision type associated with the argument." 49 | prectype(x) = prectype(typeof(x)) 50 | prectype(::Type{<:Complex{T}}) where {T} = prectype(T) 51 | prectype(::Type{<:AbstractArray{T}}) where {T} = prectype(T) 52 | prectype(::Type{NTuple{N,T}}) where {N,T} = prectype(T) 53 | prectype(::Type{Tuple{A}}) where {A} = prectype(A) 54 | prectype(::Type{Tuple{A,B}}) where {A,B} = prectype(A,B) 55 | prectype(::Type{Tuple{A,B,C}}) where {A,B,C} = prectype(A,B,C) 56 | @generated function prectype(T::Type{<:Tuple{Vararg}}) 57 | quote $(promote_type(map(prectype, T.parameters[1].parameters)...)) end 58 | end 59 | prectype(::Type{T}) where {T<:AbstractFloat} = T 60 | prectype(::Type{T}) where {T<:Number} = prectype(float(T)) 61 | 62 | prectype(a...) = promote_type(map(prectype, a)...) 63 | 64 | "Convert `x` such that its `prectype` equals `U`." 65 | convert_prectype(x, ::Type{U}) where {U} = convert(to_prectype(typeof(x),U), x) 66 | 67 | # function convert_prectype(x, ::Type{U}) where {U} 68 | # @warn "The order of arguments of convert_prectype has changed." 69 | # prectype(U, x) 70 | # end 71 | 72 | "Return the type to which `U` can be converted, such that the `prectype` becomes `T`." 73 | to_prectype(::Type{T}, ::Type{U}) where {T,U} = error("Don't know how to convert the numtype of $(T) to $(U).") 74 | to_prectype(::Type{T}, ::Type{U}) where {T <: Real,U <: Real} = U 75 | to_prectype(::Type{Complex{T}}, ::Type{U}) where {T <: Real,U <: Real} = Complex{U} 76 | to_prectype(::Type{SVector{N,T}}, ::Type{U}) where {N,T,U} = SVector{N,to_prectype(T,U)} 77 | to_prectype(::Type{Vector{T}}, ::Type{U}) where {T,U} = Vector{to_prectype(T,U)} 78 | 79 | "Promote the precision types of the arguments to a joined supertype." 80 | promote_prectype(a) = a 81 | promote_prectype(a, b) = _promote_prectype(prectype(a,b), a, b) 82 | promote_prectype(a, b, c...) = _promote_prectype(prectype(a,b,c...), a, b, c...) 83 | _promote_prectype(U, a) = convert_prectype(a, U) 84 | _promote_prectype(U, a, b) = convert_prectype(a, U), convert_prectype(b, U) 85 | _promote_prectype(U, a, b, c...) = 86 | (convert_prectype(a, U), convert_prectype(b, U), _promote_prectype(U, c...)...) 87 | 88 | ################# 89 | # Numeric type 90 | ################# 91 | 92 | "The numeric element type of x in a Euclidean space." 93 | numtype(x) = numtype(typeof(x)) 94 | numtype(::Type{T}) where {T<:Number} = T 95 | numtype(::Type{T}) where {T} = eltype(T) 96 | numtype(::Type{NTuple{N,T}}) where {N,T} = T 97 | numtype(::Type{Tuple{A,B}}) where {A,B} = promote_type(numtype(A), numtype(B)) 98 | numtype(::Type{Tuple{A,B,C}}) where {A,B,C} = promote_type(numtype(A), numtype(B), numtype(C)) 99 | numtype(::Type{Tuple{A,B,C,D}}) where {A,B,C,D} = promote_type(numtype(A), numtype(B), numtype(C), numtype(D)) 100 | @generated function numtype(T::Type{<:Tuple{Vararg}}) 101 | quote $(promote_type(T.parameters[1].parameters...)) end 102 | end 103 | 104 | numtype(a...) = promote_type(map(numtype, a)...) 105 | 106 | "Convert `x` such that its `numtype` equals `U`." 107 | convert_numtype(x, ::Type{U}) where {U} = convert(to_numtype(typeof(x),U), x) 108 | 109 | to_numtype(::Type{T}, ::Type{U}) where {T,U} = error("Don't know how to convert the numtype of $(T) to $(U).") 110 | to_numtype(::Type{T}, ::Type{U}) where {T <: Number,U <: Number} = U 111 | to_numtype(::Type{SVector{N,T}}, ::Type{U}) where {N,T,U} = SVector{N,to_numtype(T,U)} 112 | to_numtype(::Type{Vector{T}}, ::Type{U}) where {T,U} = Vector{to_numtype(T,U)} 113 | 114 | "Promote the numeric types of the arguments to a joined supertype." 115 | promote_numtype(a) = a 116 | promote_numtype(a, b) = _promote_numtype(numtype(a,b), a, b) 117 | promote_numtype(a, b, c...) = _promote_numtype(numtype(a,b,c...), a, b, c...) 118 | _promote_numtype(U, a) = convert_numtype(a, U) 119 | _promote_numtype(U, a, b) = convert_numtype(a, U), convert_numtype(b, U) 120 | _promote_numtype(U, a, b, c...) = 121 | (convert_numtype(a, U), convert_numtype(b, U), _promote_numtype(U, c...)...) 122 | 123 | 124 | 125 | ## Conversion from nested vectors to flat vectors and back 126 | 127 | """ 128 | Convert a vector from a cartesian format to a nested tuple according to the 129 | given dimensions. 130 | 131 | For example: 132 | `convert_fromcartesian([1,2,3,4,5], Val{(2,2,1)}()) -> ([1,2],[3,4],5)` 133 | """ 134 | @generated function convert_fromcartesian(x::AbstractVector, ::Val{DIM}) where {DIM} 135 | dimsum = [0; cumsum([d for d in DIM])] 136 | E = Expr(:tuple, [ (dimsum[i+1]-dimsum[i] > 1 ? Expr(:call, :SVector, [:(x[$j]) for j = dimsum[i]+1:dimsum[i+1]]...) : :(x[$(dimsum[i+1])])) for i in 1:length(DIM)]...) 137 | return quote $(E) end 138 | end 139 | 140 | "The inverse function of `convert_fromcartesian`." 141 | @generated function convert_tocartesian(x, ::Val{DIM}) where {DIM} 142 | dimsum = [0; cumsum([d for d in DIM])] 143 | E = vcat([[:(x[$i][$j]) for j in 1:DIM[i]] for i in 1:length(DIM)]...) 144 | quote SVector($(E...)) end 145 | end 146 | 147 | 148 | # we use matrix_pinv rather than pinv to preserve static matrices 149 | matrix_pinv(A) = pinv(A) 150 | matrix_pinv(A::SMatrix{M,N}) where {M,N} = SMatrix{N,M}(pinv(A)) 151 | matrix_pinv(A::SVector{N}) where {N} = convert(Transpose{Float64, SVector{N,Float64}}, pinv(A)) 152 | -------------------------------------------------------------------------------- /src/domains/levelset.jl: -------------------------------------------------------------------------------- 1 | 2 | "A `FunctionLevelSet` is a set that derives from the levels of a function." 3 | abstract type FunctionLevelSet{T} <: Domain{T} end 4 | 5 | # Convenience: assume the function and the level are stored fields 6 | levelfun(d::FunctionLevelSet) = d.f 7 | levelfun(d::FunctionLevelSet, x) = d.f(x) 8 | level(d::FunctionLevelSet) = d.level 9 | 10 | 11 | "Supertype of level set domains of the form `f(x)=C`." 12 | abstract type AbstractLevelSet{T} <: FunctionLevelSet{T} end 13 | 14 | indomain(x, d::AbstractLevelSet) = levelfun(d, x) == level(d) 15 | 16 | show(io::IO, d::AbstractLevelSet) = 17 | print(io, "level set f(x) = $(level(d)) with f = $(levelfun(d))") 18 | 19 | ==(d1::AbstractLevelSet, d2::AbstractLevelSet) = levelfun(d1)==levelfun(d2) && 20 | level(d1)==level(d2) 21 | hash(d::AbstractLevelSet, h::UInt) = hashrec("AbstractLevelSet", levelfun(d), level(d), h) 22 | 23 | "The domain defined by `f(x)=0` for a given function `f`." 24 | struct ZeroSet{T,F} <: AbstractLevelSet{T} 25 | f :: F 26 | end 27 | 28 | ZeroSet(f) = ZeroSet{Float64}(f) 29 | ZeroSet{T}(f::F) where {T,F} = ZeroSet{T,F}(f) 30 | 31 | level(d::ZeroSet) = 0 32 | 33 | similardomain(d::ZeroSet, ::Type{T}) where {T} = ZeroSet{T}(levelfun(d)) 34 | 35 | 36 | "The domain defined by `f(x)=C` for a given function `f` and constant `C`." 37 | struct LevelSet{T,F,S} <: AbstractLevelSet{T} 38 | f :: F 39 | level :: S 40 | end 41 | 42 | LevelSet(f, level) = LevelSet{typeof(level)}(f, level) 43 | LevelSet{T}(f::F, level::S) where {T,F,S} = LevelSet{T,F,S}(f, level) 44 | 45 | similardomain(d::LevelSet, ::Type{T}) where {T} = LevelSet{T}(levelfun(d), level(d)) 46 | 47 | convert(::Type{LevelSet}, d::ZeroSet{T}) where {T} = LevelSet{T}(levelfun(d), level(d)) 48 | convert(::Type{LevelSet{T}}, d::ZeroSet) where {T} = LevelSet{T}(levelfun(d), level(d)) 49 | 50 | 51 | "Supertype of sublevel set domains." 52 | abstract type AbstractSublevelSet{T,C} <: FunctionLevelSet{T} end 53 | 54 | indomain(x, d::AbstractSublevelSet{T,:closed}) where {T} = levelfun(d, x) <= level(d) 55 | indomain(x, d::AbstractSublevelSet{T,:open}) where {T} = levelfun(d, x) < level(d) 56 | 57 | show(io::IO, d::AbstractSublevelSet{T,:closed}) where {T} = 58 | print(io, "sublevel set f(x) <= $(level(d)) with f = $(levelfun(d))") 59 | show(io::IO, d::AbstractSublevelSet{T,:open}) where {T} = 60 | print(io, "sublevel set f(x) < $(level(d)) with f = $(levelfun(d))") 61 | 62 | ==(d1::AbstractSublevelSet, d2::AbstractSublevelSet) = levelfun(d1)==levelfun(d2) && 63 | level(d1)==level(d2) 64 | hash(d::AbstractSublevelSet, h::UInt) = 65 | hashrec("AbstractSublevelSet", levelfun(d), level(d), h) 66 | 67 | "The domain where `f(x) <= 0` (or `f(x) < 0`)." 68 | struct SubzeroSet{T,C,F} <: AbstractSublevelSet{T,C} 69 | f :: F 70 | end 71 | 72 | SubzeroSet(f) = SubzeroSet{Float64}(f) 73 | SubzeroSet{T}(f) where {T} = SubzeroSet{T,:closed}(f) 74 | SubzeroSet{T,C}(f::F) where {T,C,F} = SubzeroSet{T,C,F}(f) 75 | 76 | level(d::SubzeroSet) = 0 77 | 78 | similardomain(d::SubzeroSet{S,C}, ::Type{T}) where {S,C,T} = 79 | SubzeroSet{T,C}(levelfun(d)) 80 | 81 | interior(d::SubzeroSet{T}) where {T} = SubzeroSet{T,:open}(d.f) 82 | closure(d::SubzeroSet{T}) where {T} = SubzeroSet{T,:closed}(d.f) 83 | 84 | boundary(d::SubzeroSet{T}) where {T} = ZeroSet{T}(d.f) 85 | 86 | "The domain defined by `f(x) <= C` (or `f(x) < C`) for a given function `f` and constant `C`." 87 | struct SublevelSet{T,C,F,S} <: AbstractSublevelSet{T,C} 88 | f :: F 89 | level :: S 90 | end 91 | 92 | SublevelSet(f, level) = SublevelSet{typeof(level)}(f, level) 93 | SublevelSet{T}(f, level) where {T} = SublevelSet{T,:closed}(f, level) 94 | SublevelSet{T,C}(f::F, level::S) where {T,C,F,S} = SublevelSet{T,C,F,S}(f, level) 95 | 96 | similardomain(d::SublevelSet{S,C}, ::Type{T}) where {S,C,T} = 97 | SublevelSet{T,C}(levelfun(d), level(d)) 98 | 99 | interior(d::SublevelSet{T}) where {T} = SublevelSet{T,:open}(d.f, d.level) 100 | closure(d::SublevelSet{T}) where {T} = SublevelSet{T,:closed}(d.f, d.level) 101 | 102 | boundary(d::SublevelSet{T}) where {T} = LevelSet{T}(d.f, d.level) 103 | 104 | 105 | "Supertype of superlevel set domains." 106 | abstract type AbstractSuperlevelSet{T,C} <: FunctionLevelSet{T} end 107 | 108 | indomain(x, d::AbstractSuperlevelSet{T,:closed}) where {T} = levelfun(d, x) >= level(d) 109 | indomain(x, d::AbstractSuperlevelSet{T,:open}) where {T} = levelfun(d, x) > level(d) 110 | 111 | show(io::IO, d::AbstractSuperlevelSet{T,:closed}) where {T} = 112 | print(io, "superlevel set f(x) >= $(level(d)) with f = $(levelfun(d))") 113 | show(io::IO, d::AbstractSuperlevelSet{T,:open}) where {T} = 114 | print(io, "superlevel set f(x) > $(level(d)) with f = $(levelfun(d))") 115 | 116 | ==(d1::AbstractSuperlevelSet, d2::AbstractSuperlevelSet) = 117 | levelfun(d1)==levelfun(d2) && level(d1)==level(d2) 118 | hash(d::AbstractSuperlevelSet, h::UInt) = 119 | hashrec("AbstractSuperlevelSet", levelfun(d), level(d), h) 120 | 121 | 122 | "The domain where `f(x) >= 0` (or `f(x) > 0`)." 123 | struct SuperzeroSet{T,C,F} <: AbstractSuperlevelSet{T,C} 124 | f :: F 125 | end 126 | 127 | SuperzeroSet(f) = SuperzeroSet{Float64}(f) 128 | SuperzeroSet{T}(f) where {T} = SuperzeroSet{T,:closed}(f) 129 | SuperzeroSet{T,C}(f::F) where {T,C,F} = SuperzeroSet{T,C,F}(f) 130 | 131 | level(d::SuperzeroSet) = 0 132 | 133 | similardomain(d::SuperzeroSet{S,C}, ::Type{T}) where {S,C,T} = 134 | SuperzeroSet{T,C}(levelfun(d)) 135 | 136 | interior(d::SuperzeroSet{T}) where {T} = SuperzeroSet{T,:open}(d.f) 137 | closure(d::SuperzeroSet{T}) where {T} = SuperzeroSet{T,:closed}(d.f) 138 | 139 | boundary(d::SuperzeroSet{T}) where {T} = ZeroSet{T}(d.f) 140 | 141 | 142 | "The domain defined by `f(x) >= C` (or `f(x) > C`) for a given function `f` and constant `C`." 143 | struct SuperlevelSet{T,C,F,S} <: AbstractSuperlevelSet{T,C} 144 | f :: F 145 | level :: S 146 | end 147 | 148 | SuperlevelSet(f, level) = SuperlevelSet{typeof(level)}(f, level) 149 | SuperlevelSet{T}(f, level) where {T} = SuperlevelSet{T,:closed}(f, level) 150 | SuperlevelSet{T,C}(f::F, level::S) where {T,C,F,S} = SuperlevelSet{T,C,F,S}(f, level) 151 | 152 | similardomain(d::SuperlevelSet{S,C}, ::Type{T}) where {S,C,T} = 153 | SuperlevelSet{T,C}(levelfun(d), level(d)) 154 | 155 | interior(d::SuperlevelSet{T}) where {T} = SuperlevelSet{T,:open}(d.f, d.level) 156 | closure(d::SuperlevelSet{T}) where {T} = SuperlevelSet{T,:closed}(d.f, d.level) 157 | 158 | boundary(d::SuperlevelSet{T}) where {T} = LevelSet{T}(d.f, d.level) 159 | 160 | ## Additional functionality 161 | 162 | pseudolevel(d::AbstractLevelSet, epsilon) = 163 | _pseudolevel(d, epsilon, levelfun(d), level(d)) 164 | _pseudolevel(d::AbstractLevelSet{T}, epsilon, fun, C) where {T} = 165 | SublevelSet{T,:open}(x -> norm(fun(x)-C), epsilon) 166 | 167 | pseudolevel(d::Domain, epsilon) = pseudolevel(convert(LevelSet, d), epsilon) 168 | -------------------------------------------------------------------------------- /src/maps/jacobian.jl: -------------------------------------------------------------------------------- 1 | 2 | "A lazy Jacobian `J` stores a map `m` and returns `J(x) = jacobian(m, x)`." 3 | struct LazyJacobian{T,M} <: SimpleLazyMap{T} 4 | map :: M 5 | end 6 | 7 | LazyJacobian(m) = LazyJacobian{domaintype(m),typeof(m)}(m) 8 | applymap(m::LazyJacobian, x) = jacobian(supermap(m), x) 9 | 10 | mapsize(m::LazyJacobian) = mapsize(supermap(m)) 11 | 12 | Display.displaystencil(m::LazyJacobian) = ["LazyJacobian(", supermap(m), ")"] 13 | show(io::IO, mime::MIME"text/plain", m::LazyJacobian) = composite_show(io, mime, m) 14 | 15 | 16 | """ 17 | jacobian(m::AbstractMap[, x]) 18 | 19 | Return the jacobian map. The two-argument version evaluates the jacobian 20 | at a point `x`. 21 | """ 22 | jacobian(m::AbstractMap) = LazyJacobian(m) 23 | jacobian!(y, m::AbstractMap, x) = y .= jacobian(m, x) 24 | 25 | 26 | "A `DeterminantMap` returns the determinant of the result of a given map." 27 | struct DeterminantMap{T,M} <: SimpleLazyMap{T} 28 | map :: M 29 | end 30 | 31 | DeterminantMap(m) = DeterminantMap{domaintype(m)}(m) 32 | DeterminantMap{T}(m) where {T} = DeterminantMap{T,typeof(m)}(m) 33 | DeterminantMap{T}(m::Map{T}) where {T} = DeterminantMap{T,typeof(m)}(m) 34 | DeterminantMap{T}(m::Map{S}) where {S,T} = DeterminantMap{T}(convert(Map{T}, m)) 35 | 36 | determinantmap(m::AbstractMap) = DeterminantMap(m) 37 | 38 | applymap(m::DeterminantMap, x) = det(supermap(m)(x)) 39 | 40 | 41 | """ 42 | jacdet(m::AbstractMap[, x]) 43 | 44 | Return the determinant of the jacobian as a map. The two-argument version 45 | evaluates the jacobian determinant at a point `x`. 46 | """ 47 | jacdet(m::AbstractMap) = determinantmap(jacobian(m)) 48 | jacdet(m::AbstractMap, x) = det(jacobian(m, x)) 49 | 50 | 51 | "An `AbsMap` returns the absolute value of the result of a given map." 52 | struct AbsMap{T,M} <: SimpleLazyMap{T} 53 | map :: M 54 | end 55 | 56 | AbsMap(m) = AbsMap{domaintype(m)}(m) 57 | AbsMap{T}(m) where {T} = AbsMap{T,typeof(m)}(m) 58 | AbsMap{T}(m::Map{T}) where {T} = AbsMap{T,typeof(m)}(m) 59 | AbsMap{T}(m::Map{S}) where {S,T} = AbsMap{T}(convert(Map{T}, m)) 60 | 61 | absmap(m::AbstractMap) = AbsMap(m) 62 | 63 | applymap(m::AbsMap, x) = abs(supermap(m)(x)) 64 | 65 | 66 | "A lazy volume element evaluates to `diffvolume(m, x)` on the fly." 67 | struct LazyDiffVolume{T,M} <: SimpleLazyMap{T} 68 | map :: M 69 | end 70 | 71 | LazyDiffVolume(m) = LazyDiffVolume{domaintype(m)}(m) 72 | LazyDiffVolume{T}(m) where {T} = LazyDiffVolume{T,typeof(m)}(m) 73 | LazyDiffVolume{T}(m::Map{T}) where {T} = LazyDiffVolume{T,typeof(m)}(m) 74 | LazyDiffVolume{T}(m::Map{S}) where {S,T} = LazyDiffVolume{T}(convert(Map{T}, m)) 75 | 76 | applymap(m::LazyDiffVolume, x) = diffvolume(supermap(m), x) 77 | 78 | Display.displaystencil(m::LazyDiffVolume) = ["LazyDiffVolume(", supermap(m), ")"] 79 | show(io::IO, mime::MIME"text/plain", m::LazyDiffVolume) = composite_show(io, mime, m) 80 | 81 | """ 82 | diffvolume(m[, x]) 83 | 84 | Compute the differential volume (at a point `x`). If `J` is the Jacobian matrix, 85 | possibly rectangular, then the differential volume is `sqrt(det(J'*J))`. 86 | 87 | If the map is square, then the differential volume is the absolute value of the 88 | Jacobian determinant. 89 | """ 90 | function diffvolume(m) 91 | if issquaremap(m) 92 | if mapsize(m,1) > 1 93 | absmap(jacdet(m)) 94 | else 95 | jacdet(m) 96 | end 97 | else 98 | LazyDiffVolume(m) 99 | end 100 | end 101 | 102 | function diffvolume(m, x) 103 | if issquaremap(m) 104 | if mapsize(m,1) > 1 105 | abs(jacdet(m, x)) 106 | else 107 | jacdet(m, x) 108 | end 109 | else 110 | jac = jacobian(m, x) 111 | sqrt(det(adjoint(jac)*jac)) 112 | end 113 | end 114 | 115 | 116 | 117 | const NumberLike = Union{Number,UniformScaling} 118 | 119 | """ 120 | to_matrix(::Type{T}, A[, b]) 121 | 122 | Convert the `A` in the affine map `A*x` or `A*x+b` with domaintype `T` to a matrix. 123 | """ 124 | to_matrix(::Type{T}, A) where {T} = A 125 | to_matrix(::Type{T}, A::AbstractMatrix) where {T} = A 126 | to_matrix(::Type{T}, A::NumberLike) where {T<:Number} = A 127 | to_matrix(::Type{SVector{N,T}}, A::Number) where {N,T} = A * one(SMatrix{N,N,T}) 128 | to_matrix(::Type{SVector{N,T}}, A::UniformScaling) where {N,T} = A.λ * one(SMatrix{N,N,T}) 129 | to_matrix(::Type{T}, A::Number) where {T<:AbstractVector} = A * I 130 | to_matrix(::Type{T}, A::UniformScaling) where {T<:Number} = one(T) 131 | to_matrix(::Type{T}, A::UniformScaling) where {T<:AbstractVector} = A 132 | 133 | to_matrix(::Type{T}, A, b) where {T} = A 134 | to_matrix(::Type{T}, A::AbstractMatrix, b) where {T} = A 135 | to_matrix(::Type{T}, A::Number, b::Number) where {T<:Number} = A 136 | to_matrix(::Type{T}, A::UniformScaling, b::Number) where {T<:Number} = A.λ 137 | to_matrix(::Type{SVector{N,T}}, A::NumberLike, b::SVector{N,T}) where {N,T} = A * one(SMatrix{N,N,T}) 138 | to_matrix(::Type{T}, A::NumberLike, b::AbstractVector) where {S,T<:AbstractVector{S}} = 139 | A * Array{S,2}(I, length(b), length(b)) 140 | 141 | """ 142 | to_vector(::Type{T}, A[, b]) 143 | 144 | Convert the `b` in the affine map `A*x` or `A*x+b` with domaintype `T` to a vector. 145 | """ 146 | to_vector(::Type{T}, A) where {T} = zero(T) 147 | to_vector(::Type{T}, A::SVector{M,S}) where {T,M,S} = zero(SVector{M,S}) 148 | to_vector(::Type{T}, A::SMatrix{M,N,S}) where {T<:AbstractVector,M,N,S} = zero(SVector{M,S}) 149 | to_vector(::Type{T}, A::AbstractArray) where {T<:AbstractVector} = zeros(eltype(T),size(A,1)) 150 | to_vector(::Type{T}, A, b) where {T} = b 151 | 152 | 153 | "Return a zero matrix of the same size as the map." 154 | zeromatrix(m::AbstractMap) = zeromatrix(m, domaintype(m), codomaintype(m)) 155 | zeromatrix(m, ::Type{T}, ::Type{U}) where {T,U} = zeros(numtype(T),mapsize(m)) 156 | zeromatrix(m, ::Type{T}, ::Type{U}) where {T<:Number,U<:Number} = zero(promote_type(T,U)) 157 | zeromatrix(m, ::Type{T}, ::Type{U}) where {N,T<:StaticVector{N},U<:Number} = 158 | transpose(zero(SVector{N,eltype(T)})) 159 | zeromatrix(m, ::Type{T}, ::Type{U}) where {N,M,T<:StaticVector{N},U<:StaticVector{M}} = 160 | zero(SMatrix{M,N,promote_type(eltype(T),eltype(U))}) 161 | zeromatrix(m, ::Type{T}, ::Type{U}) where {T<:Number,M,U<:StaticVector{M}} = 162 | zero(SVector{M,promote_type(T,eltype(U))}) 163 | zeromatrix(m, ::Type{T}, ::Type{U}) where {T<:AbstractVector,U<:Number} = 164 | transpose(zeros(promote_type(eltype(T),U), mapsize(m,1))) 165 | 166 | 167 | "Return a zero vector of the same size as the codomain of the map." 168 | zerovector(m::AbstractMap) = zerovector(m, codomaintype(m)) 169 | zerovector(m::AbstractMap, ::Type{U}) where {U} = zero(U) 170 | zerovector(m::AbstractMap, ::Type{StaticVector{M,T}}) where {M,T} = zero(SVector{M,T}) 171 | # If the output type is a vector, the map itself should store the size information. 172 | zerovector(m::AbstractMap, ::Type{<:AbstractVector{T}}) where {T} = zeros(T, mapsize(m,1)) 173 | 174 | "Return an identity matrix with the same size as the map." 175 | identitymatrix(m::AbstractMap) = identitymatrix(m, codomaintype(m)) 176 | identitymatrix(m::AbstractMap, ::Type{T}) where {T} = one(T) 177 | identitymatrix(m::AbstractMap, ::Type{<:StaticVector{N,T}}) where {N,T} = one(SMatrix{N,N,T}) 178 | identitymatrix(m::AbstractMap, ::Type{<:AbstractVector{T}}) where {T} = Diagonal{T}(ones(mapsize(m,1))) 179 | -------------------------------------------------------------------------------- /src/maps/product.jl: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | A product map is diagonal and acts on each of the components of x separately: 4 | `y = f(x)` becomes `y_i = f_i(x_i)`. 5 | """ 6 | abstract type ProductMap{T} <: CompositeLazyMap{T} end 7 | 8 | components(m::ProductMap) = m.maps 9 | 10 | VcatMapElement = Union{Map{<:SVector},Map{<:Number}} 11 | 12 | ProductMap(maps::Tuple) = ProductMap(maps...) 13 | ProductMap(maps::SVector) = ProductMap(maps...) 14 | ProductMap(maps...) = TupleProductMap(maps...) 15 | ProductMap(maps::VcatMapElement...) = VcatMap(maps...) 16 | ProductMap(maps::AbstractVector) = VectorProductMap(maps) 17 | 18 | ProductMap{T}(maps...) where {T} = _TypedProductMap(T, maps...) 19 | _TypedProductMap(::Type{T}, maps...) where {T<:Tuple} = TupleProductMap(maps...) 20 | _TypedProductMap(::Type{SVector{N,T}}, maps...) where {N,T} = VcatMap{N,T}(maps...) 21 | _TypedProductMap(::Type{T}, maps...) where {T<:AbstractVector} = VectorProductMap{T}(maps...) 22 | 23 | compatibleproductdims(d1::ProductMap, d2::ProductMap) = 24 | mapsize(d1) == mapsize(d2) && 25 | all(map(==, map(mapsize, components(d1)), map(mapsize, components(d2)))) 26 | 27 | isconstant(m::ProductMap) = mapreduce(isconstant, &, components(m)) 28 | islinear(m::ProductMap) = mapreduce(islinear, &, components(m)) 29 | isaffine(m::ProductMap) = mapreduce(isaffine, &, components(m)) 30 | 31 | matrix(m::ProductMap) = toexternalmatrix(m, map(matrix, components(m))) 32 | vector(m::ProductMap) = toexternalpoint(m, map(vector, components(m))) 33 | constant(m::ProductMap) = toexternalpoint(m, map(constant, components(m))) 34 | 35 | jacobian(m::ProductMap, x) = 36 | toexternalmatrix(m, map(jacobian, components(m), tointernalpoint(m, x))) 37 | function jacobian(m::ProductMap{T}) where {T} 38 | if isaffine(m) 39 | ConstantMap{T}(matrix(m)) 40 | else 41 | ProductMap(map(jacobian, components(m))) 42 | end 43 | end 44 | # function jacdet(m::ProductMap, x) 45 | 46 | similarmap(m::ProductMap, ::Type{T}) where {T} = ProductMap{T}(components(m)) 47 | 48 | tointernalpoint(m::ProductMap, x) = x 49 | toexternalpoint(m::ProductMap, y) = y 50 | 51 | applymap(m::ProductMap, x) = 52 | toexternalpoint(m, map(applymap, components(m), tointernalpoint(m, x))) 53 | 54 | productmap(map1, map2) = productmap1(map1, map2) 55 | productmap1(map1, map2) = productmap2(map1, map2) 56 | productmap2(map1, map2) = ProductMap(map1, map2) 57 | productmap(map1::ProductMap, map2::ProductMap) = 58 | ProductMap(components(map1)..., components(map2)...) 59 | productmap1(map1::ProductMap, map2) = ProductMap(components(map1)..., map2) 60 | productmap2(map1, map2::ProductMap) = ProductMap(map1, components(map2)...) 61 | 62 | for op in (:inverse, :leftinverse, :rightinverse) 63 | @eval $op(m::ProductMap) = ProductMap(map($op, components(m))) 64 | @eval $op(m::ProductMap, x) = toexternalpoint(m, map($op, components(m), tointernalpoint(m, x))) 65 | end 66 | 67 | function composedmap(m1::ProductMap, m2::ProductMap) 68 | if compatibleproductdims(m1, m2) 69 | ProductMap(map(composedmap, components(m1), components(m2))) 70 | else 71 | ComposedMap(m1,m2) 72 | end 73 | end 74 | 75 | mapsize(m::ProductMap) = (sum(t->mapsize(t,1), components(m)), sum(t->mapsize(t,2), components(m))) 76 | 77 | ==(m1::ProductMap, m2::ProductMap) = all(map(isequal, components(m1), components(m2))) 78 | hash(m::ProductMap, h::UInt) = hashrec("ProductMap", collect(components(m)), h) 79 | 80 | Display.combinationsymbol(m::ProductMap) = Display.Symbol('⊗') 81 | Display.displaystencil(m::ProductMap) = composite_displaystencil(m) 82 | show(io::IO, mime::MIME"text/plain", m::ProductMap) = composite_show(io, mime, m) 83 | show(io::IO, m::ProductMap) = composite_show_compact(io, m) 84 | 85 | """ 86 | A `VcatMap` is a product map with domain and codomain vectors 87 | concatenated (`vcat`) into a single vector. 88 | """ 89 | struct VcatMap{N,T,DIM,MAPS} <: ProductMap{SVector{N,T}} 90 | maps :: MAPS 91 | end 92 | 93 | VcatMap(maps::Union{Tuple,Vector}) = VcatMap(maps...) 94 | function VcatMap(maps...) 95 | T = numtype(maps...) 96 | M = sum(t->mapsize(t,1), maps) 97 | N = sum(t->mapsize(t,2), maps) 98 | # M,N = reduce((x,y) -> (x[1]+y[1],x[2]+y[2]), map(mapsize,maps)) 99 | @assert M==N 100 | VcatMap{N,T}(maps...) 101 | end 102 | 103 | mapdim(map) = mapsize(map,2) 104 | 105 | VcatMap{N,T}(maps::Union{Tuple,Vector}) where {N,T} = VcatMap{N,T}(maps...) 106 | function VcatMap{N,T}(maps...) where {N,T} 107 | DIM = map(mapdim,maps) 108 | VcatMap{N,T,DIM}(convert_numtype.(maps, Ref(T))...) 109 | end 110 | 111 | VcatMap{N,T,DIM}(maps...) where {N,T,DIM} = VcatMap{N,T,DIM,typeof(maps)}(maps) 112 | 113 | mapsize(m::VcatMap{N}) where {N} = (N,N) 114 | 115 | tointernalpoint(m::VcatMap{N,T,DIM}, x) where {N,T,DIM} = 116 | convert_fromcartesian(x, Val{DIM}()) 117 | toexternalpoint(m::VcatMap{N,T,DIM}, y) where {N,T,DIM} = 118 | convert_tocartesian(y, Val{DIM}()) 119 | 120 | size_as_matrix(A::AbstractArray) = size(A) 121 | size_as_matrix(A::Number) = (1,1) 122 | 123 | # The Jacobian is block-diagonal 124 | function toexternalmatrix(m::VcatMap{N,T}, matrices) where {N,T} 125 | A = zeros(T, N, N) 126 | l = 0 127 | for el in matrices 128 | m,n = size_as_matrix(el) 129 | @assert m==n 130 | A[l+1:l+m,l+1:l+n] .= el 131 | l += n 132 | end 133 | SMatrix{N,N}(A) 134 | end 135 | 136 | 137 | """ 138 | A `VectorProductMap` is a product map where all components are univariate maps, 139 | with inputs and outputs collected into a `Vector`. 140 | """ 141 | struct VectorProductMap{T<:AbstractVector,M} <: ProductMap{T} 142 | maps :: Vector{M} 143 | end 144 | 145 | VectorProductMap(maps::AbstractMap...) = VectorProductMap(maps) 146 | VectorProductMap(maps) = VectorProductMap(collect(maps)) 147 | function VectorProductMap(maps::Vector) 148 | T = mapreduce(numtype, promote_type, maps) 149 | VectorProductMap{Vector{T}}(maps) 150 | end 151 | 152 | VectorProductMap{T}(maps::AbstractMap...) where {T} = VectorProductMap{T}(maps) 153 | VectorProductMap{T}(maps) where {T} = VectorProductMap{T}(collect(maps)) 154 | function VectorProductMap{T}(maps::Vector) where {T} 155 | Tmaps = convert.(Map{eltype(T)}, maps) 156 | VectorProductMap{T,eltype(Tmaps)}(Tmaps) 157 | end 158 | 159 | # the Jacobian is a diagonal matrix 160 | toexternalmatrix(m::VectorProductMap, matrices) = Diagonal(matrices) 161 | 162 | mapsize(m::VectorProductMap) = (length(m.maps), length(m.maps)) 163 | 164 | """ 165 | A `TupleProductMap` is a product map with all components collected in a tuple. 166 | There is no vector-valued function associated with this map. 167 | """ 168 | struct TupleProductMap{T,MM} <: ProductMap{T} 169 | maps :: MM 170 | end 171 | 172 | TupleProductMap(maps::Vector) = TupleProductMap(maps...) 173 | TupleProductMap(maps...) = TupleProductMap(maps) 174 | function TupleProductMap(maps::Tuple) 175 | T = Tuple{map(eltype, maps)...} 176 | TupleProductMap{T}(maps) 177 | end 178 | 179 | TupleProductMap{T}(maps::Vector) where {T} = TupleProductMap{T}(maps...) 180 | TupleProductMap{T}(maps...) where {T} = TupleProductMap{T}(maps) 181 | function TupleProductMap{T}(maps::NTuple{N,<:AbstractMap}) where {N,T <: Tuple} 182 | Tmaps = map((t,d) -> convert(Map{t},d), tuple(T.parameters...), maps) 183 | TupleProductMap{T,typeof(Tmaps)}(Tmaps) 184 | end 185 | TupleProductMap{T}(maps) where {T <: Tuple} = TupleProductMap{T,typeof(maps)}(maps) 186 | -------------------------------------------------------------------------------- /src/domains/simplex.jl: -------------------------------------------------------------------------------- 1 | 2 | ########################### 3 | # An n-dimensional simplex 4 | ########################### 5 | 6 | "Supertype of an N-dimensional simplex." 7 | abstract type Simplex{T,C} <: Domain{T} end 8 | 9 | isclosedset(::Simplex{T,:closed}) where {T} = true 10 | isclosedset(::Simplex{T,:open}) where {T} = false 11 | 12 | isopenset(d::Simplex) = !isclosedset(d) 13 | 14 | "The unit simplex is a polytope with the origin and all unit vectors as vertices." 15 | abstract type UnitSimplex{T,C} <: Simplex{T,C} end 16 | 17 | const ClosedUnitSimplex{T} = UnitSimplex{T,:closed} 18 | const OpenUnitSimplex{T} = UnitSimplex{T,:open} 19 | 20 | UnitSimplex(n::Int) = DynamicUnitSimplex(n) 21 | UnitSimplex(::Val{N}) where {N} = EuclideanUnitSimplex{N}() 22 | 23 | UnitSimplex{T}(n::Int) where {T <: StaticTypes} = StaticUnitSimplex{T}(n) 24 | UnitSimplex{T}(::Val{N}) where {N,T} = StaticUnitSimplex{T}(Val(N)) 25 | UnitSimplex{T}() where {T <: StaticTypes} = StaticUnitSimplex{T}() 26 | UnitSimplex{T}(n::Int) where {T} = DynamicUnitSimplex{T}(n) 27 | 28 | UnitSimplex{T,C}(n::Int) where {T <: StaticTypes,C} = StaticUnitSimplex{T,C}(n) 29 | UnitSimplex{T,C}(::Val{N}) where {N,T,C} = StaticUnitSimplex{T,C}(Val(N)) 30 | UnitSimplex{T,C}() where {T <: StaticTypes,C} = StaticUnitSimplex{T,C}() 31 | UnitSimplex{T,C}(n::Int) where {T,C} = DynamicUnitSimplex{T,C}(n) 32 | 33 | insimplex_closed(x) = mapreduce( t-> t >= 0, &, x) && norm(x,1) <= 1 34 | insimplex_open(x) = mapreduce( t-> t > 0, &, x) && norm(x,1) < 1 35 | insimplex_closed(x, tol) = mapreduce( t-> t >= -tol, &, x) && norm(x,1) <= 1+tol 36 | insimplex_open(x, tol) = mapreduce( t-> t > -tol, &, x) && norm(x,1) < 1+tol 37 | 38 | indomain(x, d::ClosedUnitSimplex) = length(x)==dimension(d) && insimplex_closed(x) 39 | indomain(x, d::OpenUnitSimplex) = length(x)==dimension(d) && insimplex_open(x) 40 | approx_indomain(x, d::ClosedUnitSimplex, tolerance) = length(x)==dimension(d) && insimplex_closed(x, tolerance) 41 | approx_indomain(x, d::OpenUnitSimplex, tolerance) = length(x)==dimension(d) && insimplex_open(x, tolerance) 42 | 43 | isempty(::UnitSimplex) = false 44 | 45 | ==(d1::UnitSimplex, d2::UnitSimplex) = 46 | isclosedset(d1)==isclosedset(d2) && dimension(d1)==dimension(d2) 47 | hash(d::UnitSimplex, h::UInt) = hashrec("UnitSimplex", isclosedset(d), dimension(d), h) 48 | 49 | boundingbox(d::UnitSimplex{T}) where {T} = UnitCube{T}(dimension(d)) 50 | 51 | boundary(d::UnitSimplex{T,:open}) where {T} = EmptySpace{T}() 52 | 53 | 54 | distance_to(d::UnitSimplex, x) = x ∈ d ? zero(prectype(d)) : minimum(distance_to(el, x) for el in components(boundary(d))) 55 | 56 | function normal(d::UnitSimplex, x) 57 | z = similar(x) 58 | fill!(z, 0) 59 | if sum(x) ≈ 1 60 | fill!(z, one(eltype(z))/sqrt(length(z))) 61 | else 62 | index = findmin(x)[2] 63 | z[index] = -1 64 | end 65 | return convert(eltype(d), z/norm(z)) 66 | end 67 | 68 | 69 | "A unit simplex whose dimension is determined by its element type." 70 | struct StaticUnitSimplex{T,C} <: UnitSimplex{T,C} 71 | end 72 | 73 | StaticUnitSimplex(::Val{N}) where {N} = StaticUnitSimplex{SVector{N,Float64}}() 74 | 75 | StaticUnitSimplex{T}() where {T} = StaticUnitSimplex{T,:closed}() 76 | 77 | StaticUnitSimplex{T}(n::Int) where {T} = 78 | (@assert n == euclideandimension(T); StaticUnitSimplex{T}()) 79 | StaticUnitSimplex{T}(::Val{N}) where {N,T} = 80 | (@assert N == euclideandimension(T); StaticUnitSimplex{T}()) 81 | 82 | StaticUnitSimplex{T,C}(n::Int) where {T,C} = 83 | (@assert n == euclideandimension(T); StaticUnitSimplex{T,C}()) 84 | StaticUnitSimplex{T,C}(::Val{N}) where {N,T,C} = 85 | (@assert N == euclideandimension(T); StaticUnitSimplex{T,C}()) 86 | 87 | const EuclideanUnitSimplex{N,T,C} = StaticUnitSimplex{SVector{N,T},C} 88 | 89 | EuclideanUnitSimplex{N}() where {N} = EuclideanUnitSimplex{N,Float64}() 90 | 91 | ## A StaticUnitSimplex{<:Number} equals the interval [0,1] (open or closed) 92 | convert(::Type{Interval}, d::StaticUnitSimplex{T,:closed}) where {T <: Number} = 93 | UnitInterval{T}() 94 | convert(::Type{Interval}, d::StaticUnitSimplex{T,:open}) where {T <: Number} = 95 | OpenInterval{T}(0, 1) 96 | 97 | canonicaldomain(::Equal, d::StaticUnitSimplex{T}) where {T<:Number} = convert(Interval, d) 98 | 99 | boundary(d::StaticUnitSimplex{T}) where {T<:Number} = boundary(convert(Interval, d)) 100 | 101 | 102 | "A unit simplex with vector elements with variable dimension determined by a field." 103 | struct DynamicUnitSimplex{T,C} <: UnitSimplex{T,C} 104 | dimension :: Int 105 | 106 | DynamicUnitSimplex{T,C}(n::Int) where {T,C} = new(n) 107 | DynamicUnitSimplex{T,C}(n::Int) where {T<:StaticTypes,C} = 108 | (@assert n == euclideandimension(T); new(n)) 109 | end 110 | 111 | DynamicUnitSimplex(n::Int) = DynamicUnitSimplex{Vector{Float64}}(n) 112 | DynamicUnitSimplex{T}(n::Int) where {T} = DynamicUnitSimplex{T,:closed}(n) 113 | 114 | dimension(d::DynamicUnitSimplex) = d.dimension 115 | 116 | const VectorUnitSimplex{T,C} = DynamicUnitSimplex{Vector{T},C} 117 | 118 | VectorUnitSimplex(dimension) = VectorUnitSimplex{Float64}(dimension) 119 | 120 | show(io::IO, d::EuclideanUnitSimplex{N,Float64,:closed}) where {N} = print(io, "UnitSimplex(Val($(N)))") 121 | show(io::IO, d::VectorUnitSimplex{Float64,:closed}) = print(io, "UnitSimplex($(dimension(d)))") 122 | 123 | 124 | center(d::EuclideanUnitSimplex{N,T}) where {N,T} = ones(SVector{N,T})/N 125 | center(d::VectorUnitSimplex{T}) where {T} = ones(T, dimension(d))/dimension(d) 126 | 127 | corners(d::UnitSimplex) = vcat([origin(d)], [ unitvector(d, i) for i in 1:dimension(d)]) 128 | 129 | interior(d::EuclideanUnitSimplex{N,T}) where {N,T} = EuclideanUnitSimplex{N,T,:open}() 130 | closure(d::EuclideanUnitSimplex{N,T}) where {N,T} = EuclideanUnitSimplex{N,T,:closed}() 131 | interior(d::VectorUnitSimplex{T}) where {T} = VectorUnitSimplex{T,:open}(dimension(d)) 132 | closure(d::VectorUnitSimplex{T}) where {T} = VectorUnitSimplex{T,:closed}(dimension(d)) 133 | 134 | 135 | # We pick the center point, because it belongs to the domain regardless of 136 | # whether it is open or closed. 137 | point_in_domain(d::UnitSimplex) = center(d) 138 | 139 | similardomain(d::StaticUnitSimplex{S,C}, ::Type{T}) where {S,T,C} = 140 | StaticUnitSimplex{T,C}() 141 | similardomain(d::DynamicUnitSimplex{S,C}, ::Type{T}) where {S,T,C} = 142 | DynamicUnitSimplex{T,C}(d.dimension) 143 | 144 | 145 | simplex_face_map(a::Number, b::Number, c::SVector{2}, d::SVector{2}) = 146 | AffineMap((d-c)/(b-a), c - (d-c)/(b-a)*a) 147 | function simplex_face_map(a::Number, b::Number, c::Vector, d::Vector) 148 | @assert length(c) == length(d) == 2 149 | AffineMap((d-c)/(b-a), c - (d-c)/(b-a)*a) 150 | end 151 | 152 | function boundary(d::StaticUnitSimplex{SVector{2,T},:closed}) where {T} 153 | d0 = UnitInterval{T}() 154 | T0 = zero(T) 155 | T1 = one(T) 156 | maps = [ 157 | simplex_face_map(T0, T1, SVector(T0,T0), SVector(T1,T0)), 158 | simplex_face_map(T0, T1, SVector(T1,T0), SVector(T0,T1)), 159 | simplex_face_map(T0, T1, SVector(T0,T1), SVector(T0,T0)) 160 | ] 161 | faces = map(m -> ParametricDomain(m, d0), maps) 162 | UnionDomain(faces) 163 | end 164 | 165 | # function boundary(d::StaticUnitSimplex{SVector{N,T},:closed}) where {N,T} 166 | # left2 = infimum(d) 167 | # right2 = supremum(d) 168 | # d0 = UnitSimplex{SVector{N-1,T},:closed}() 169 | # T0 = zero(T) 170 | # T1 = one(T) 171 | # 172 | # map1 = cube_face_map(left1, right1, left2, right2, 1, left2[1]) 173 | # MAP = typeof(map1) 174 | # maps = MAP[] 175 | # for dim in 1:N 176 | # push!(maps, cube_face_map(left1, right1, left2, right2, dim, left2[dim])) 177 | # push!(maps, cube_face_map(left1, right1, left2, right2, dim, right2[dim])) 178 | # end 179 | # faces = map(m -> ParametricDomain(m, d_unit), maps) 180 | # UnionDomain(faces) 181 | # end 182 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DomainSets.jl 2 | 3 | [![](https://img.shields.io/badge/docs-stable-blue.svg)](https://JuliaApproximation.github.io/DomainSets.jl/dev) 4 | [![Build Status](https://github.com/JuliaApproximation/DomainSets.jl/workflows/CI/badge.svg)](https://github.com/JuliaApproximation/DomainSets.jl/actions) 5 | [![Coverage Status](https://codecov.io/gh/JuliaApproximation/DomainSets.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/JuliaApproximation/DomainSets.jl) 6 | 7 | 8 | DomainSets.jl is a package designed to represent simple infinite sets. The package makes it easy to represent sets, verify membership of the set, compare sets and construct new sets from existing ones. Domains are considered equivalent if they describe the same set, regardless of their type. 9 | 10 | ## Examples 11 | 12 | ### Intervals 13 | 14 | DomainSets.jl uses [IntervalSets.jl](https://github.com/JuliaMath/IntervalSets.jl) for closed and open intervals. In addition, it defines a few standard intervals. 15 | 16 | ```julia 17 | julia> using DomainSets, StaticArrays 18 | 19 | julia> UnitInterval() 20 | 0.0..1.0 (Unit) 21 | 22 | julia> ChebyshevInterval() 23 | -1.0..1.0 (Chebyshev) 24 | 25 | julia> HalfLine() 26 | 0.0..Inf (closed–open) (HalfLine) 27 | ``` 28 | 29 | ### Rectangles 30 | 31 | Rectangles can be constructed as a product of intervals, where the elements of the domain 32 | are `SVector{2}`: 33 | 34 | ```julia 35 | julia> using DomainSets: × 36 | 37 | julia> (-1..1) × (0..3) × (4.0..5.0) 38 | (-1.0..1.0) × (0.0..3.0) × (4.0..5.0) 39 | 40 | julia> SVector(1,2) in (-1..1) × (0..3) 41 | true 42 | 43 | julia> UnitInterval()^3 44 | UnitCube() 45 | ``` 46 | 47 | ### Circles and Spheres 48 | 49 | A `UnitSphere` contains `x` if `norm(x) == 1`. The unit sphere is N-dimensional, 50 | and its dimension is specified with the constructor. The element types are 51 | `SVector{N,T}` when the dimension is specified as `Val(3)`, and they 52 | are `Vector{T}` when the dimension is specified by an integer value instead: 53 | ```julia 54 | julia> SVector(0,0,1.0) in UnitSphere(Val(3)) 55 | true 56 | 57 | julia> [0.0,1.0,0.0,0.0] in UnitSphere(4) 58 | true 59 | ``` 60 | `UnitSphere` itself is an abstract type, hence the examples above return 61 | concrete types `<:UnitSphere`. The intended element type can also be explicitly 62 | specified with the `UnitSphere{T}` constructor: 63 | ```julia 64 | julia> typeof(UnitSphere{SVector{3,BigFloat}}()) 65 | EuclideanUnitSphere{3, BigFloat} (alias for StaticUnitSphere{SArray{Tuple{3}, BigFloat, 1, 3}}) 66 | 67 | julia> typeof(UnitSphere{Vector{Float32}}(6)) 68 | VectorUnitSphere{Float32} (alias for DynamicUnitSphere{Array{Float32, 1}}) 69 | ``` 70 | 71 | Without arguments, `UnitSphere()` defaults to a 3D domain with `SVector{3,Float64}` 72 | elements. Similarly, there is a special case `UnitCircle` in 2D: 73 | ```julia 74 | julia> SVector(1,0) in UnitCircle() 75 | true 76 | ``` 77 | 78 | 79 | 80 | ### Disks and Balls 81 | 82 | A `UnitBall` contains `x` if `norm(x) ≤ 1`. As with `UnitSphere`, the dimension 83 | is specified via the constructor by type or by value: 84 | ```julia 85 | julia> SVector(0.1,0.2,0.3) in UnitBall(Val(3)) 86 | true 87 | 88 | julia> [0.1,0.2,0.3,-0.1] in UnitBall(4) 89 | true 90 | ``` 91 | By default `N=3`, but `UnitDisk` is a special case in 2D, and so are `ComplexUnitDisk` and `ComplexUnitCircle` in the complex plane: 92 | ```julia 93 | julia> SVector(0.1,0.2) in UnitDisk() 94 | true 95 | 96 | julia> 0.5+0.2im ∈ ComplexUnitDisk() 97 | true 98 | ``` 99 | 100 | `UnitBall` itself is an abstract type, hence the examples above return 101 | concrete types `<:UnitBall`. The types are similar to those associated with 102 | `UnitSphere`. Like intervals, balls can also be open or closed: 103 | ```julia 104 | julia> EuclideanUnitBall{3,Float64,:open}() 105 | the 3-dimensional open unit ball 106 | ``` 107 | 108 | 109 | ### Product domains 110 | 111 | The cartesian product of domains is constructed with the `ProductDomain` or 112 | `ProductDomain{T}` constructor. This abstract constructor returns concrete types 113 | best adapted to the arguments given. 114 | 115 | If `T` is not given, `ProductDomain` makes a suitable choice based on the 116 | arguments. If all arguments are Euclidean, i.e., their element types are numbers 117 | or static vectors, then the product is a Euclidean domain as well: 118 | ```julia 119 | julia> ProductDomain(0..2, UnitCircle()) 120 | 0.0..2.0 x the unit circle 121 | 122 | julia> eltype(ans) 123 | SVector{3, Float64} (alias for SArray{Tuple{3}, Float64, 1, 3}) 124 | ``` 125 | The elements of the interval and the unit circle are flattened into a single 126 | vector, much like the `vcat` function. The result is a `VcatDomain`. 127 | 128 | If a `Vector` of domains is given, the element type is a `Vector` as well: 129 | ```julia 130 | julia> 1:5 in ProductDomain([0..i for i in 1:5]) 131 | true 132 | ``` 133 | In other cases, the points are concatenated into a tuple and membership is 134 | evaluated element-wise: 135 | ```julia 136 | julia> ("a", 0.4) ∈ ProductDomain(["a","b"], 0..1) 137 | true 138 | ``` 139 | 140 | Some arguments are recognized and return a more specialized product domain. 141 | Examples are the unit box and more general hyperrectangles: 142 | ```julia 143 | julia> ProductDomain(UnitInterval(), UnitInterval()) 144 | 0.0..1.0 (Unit) x 0.0..1.0 (Unit) 145 | 146 | julia> ProductDomain(0..2, 4..5, 6..7.0) 147 | 0.0..2.0 x 4.0..5.0 x 6.0..7.0 148 | 149 | julia> typeof(ans) 150 | Rectangle{SVector{3, Float64}} 151 | ``` 152 | 153 | 154 | ### Union, intersection, and setdiff of domains 155 | 156 | Domains can be unioned and intersected together: 157 | ```julia 158 | julia> d = UnitCircle() ∪ 2UnitCircle(); 159 | 160 | julia> in.([SVector(1,0),SVector(0,2), SVector(1.5,1.5)], d) 161 | 3-element BitArray{1}: 162 | 1 163 | 1 164 | 0 165 | 166 | julia> d = UnitCircle() ∩ (2UnitCircle() .+ SVector(1.0,0.0)) 167 | the intersection of 2 domains: 168 | 1. : the unit circle 169 | 2. : A mapped domain based on the unit circle 170 | 171 | julia> SVector(1,0) in d 172 | false 173 | 174 | julia> SVector(-1,0) in d 175 | true 176 | ``` 177 | 178 | 179 | ### Level sets 180 | 181 | A domain can be defined by the level sets of a function. The domains of all 182 | points `[x,y]` for which `x*y = 1` or `x*y >= 1` are represented as follows: 183 | ```julia 184 | julia> d = LevelSet{SVector{2,Float64}}(prod, 1.0) 185 | level set f(x) = 1.0 with f = prod 186 | 187 | julia> [0.5,2] ∈ d 188 | true 189 | 190 | julia> SuperlevelSet{SVector{2,Float64}}(prod, 1.0) 191 | superlevel set f(x) >= 1.0 with f = prod 192 | ``` 193 | There is also `SublevelSet`, and there are the special cases `ZeroSet`, 194 | `SubzeroSet` and `SuperzeroSet`. 195 | 196 | ### Indicator functions 197 | 198 | A domain can be defined by an indicator function or a characteristic function. 199 | This is a function `f(x)` which evaluates to true or false, depending on whether or 200 | not the point `x` belongs to the domain. 201 | ```julia 202 | julia> d = IndicatorFunction{Float64}( t -> cos(t) > 0) 203 | indicator domain defined by function f = #5 204 | 205 | julia> 0.5 ∈ d, 3.1 ∈ d 206 | (true, false) 207 | ``` 208 | This enables generator syntax to define domains: 209 | ```julia 210 | julia> d = Domain(x>0 for x in -1..1) 211 | indicator function bounded by: -1..1 212 | 213 | julia> 0.5 ∈ d, -0.5 ∈ d 214 | (true, false) 215 | 216 | julia> d = Domain( x*y > 0 for (x,y) in UnitDisk()) 217 | indicator function bounded by: the 2-dimensional closed unit ball 218 | 219 | julia> [0.2, 0.3] ∈ d, [0.2, -0.3] ∈ d 220 | (true, false) 221 | 222 | julia> d = Domain( x+y+z > 0 for (x,y,z) in ProductDomain(UnitDisk(), 0..1)) 223 | indicator function bounded by: the 2-dimensional closed unit ball x 0..1 224 | 225 | julia> [0.3,0.2,0.5] ∈ d 226 | true 227 | ``` 228 | 229 | ### The domain interface 230 | 231 | A domain is any type that implements the functions `eltype` and `in`. If 232 | `d` is an instance of a type that implements the domain interface, then 233 | the domain consists of all `x` that is an `eltype(d)` such that `x in d` 234 | returns true. 235 | 236 | Domains often represent continuous mathematical domains, for example, a domain 237 | `d` representing the interval `[0,1]` would have `eltype(d) == Int` but still 238 | have `0.2 in d` return true. 239 | 240 | ### The `Domain` type 241 | 242 | DomainSets.jl contains an abstract type `Domain{T}`. All subtypes of `Domain{T}` 243 | must implement the domain interface, and in addition support `convert(Domain{T}, d)`. 244 | -------------------------------------------------------------------------------- /src/maps/composite.jl: -------------------------------------------------------------------------------- 1 | 2 | "The composition of several maps." 3 | struct ComposedMap{T,MAPS} <: CompositeLazyMap{T} 4 | maps :: MAPS 5 | end 6 | 7 | ComposedMap(maps...) = ComposedMap{domaintype(maps[1])}(maps...) 8 | ComposedMap{T}(maps...) where {T} = ComposedMap{T,typeof(maps)}(maps) 9 | 10 | # TODO: make proper conversion 11 | similarmap(m::ComposedMap, ::Type{T}) where {T} = ComposedMap{T}(m.maps...) 12 | 13 | codomaintype(m::ComposedMap) = codomaintype(m.maps[end]) 14 | 15 | # Maps are applied in the order that they appear in m.maps 16 | applymap(m::ComposedMap, x) = applymap_rec(x, m.maps...) 17 | applymap_rec(x) = x 18 | applymap_rec(x, map1, maps...) = applymap_rec(map1(x), maps...) 19 | 20 | # The size of a composite map depends on the first and the last map to be applied 21 | # We check whether they are scalar_to_vector, vector_to_vector, etcetera 22 | mapsize(m::ComposedMap) = _composed_mapsize(m, m.maps[end], m.maps[1], mapsize(m.maps[end]), mapsize(m.maps[1])) 23 | _composed_mapsize(m, m_end, m1, S_end::Tuple{Int,Int}, S1::Tuple{Int,Int}) = (S_end[1],S1[2]) 24 | _composed_mapsize(m, m_end, m1, S_end::Tuple{Int,Int}, S1::Tuple{Int}) = 25 | is_vector_to_scalar(m_end) ? () : (S_end[1],) 26 | _composed_mapsize(m, m_end, m1, S_end::Tuple{Int,Int}, S1::Tuple{}) = 27 | is_vector_to_scalar(m_end) ? () : (S_end[1],) 28 | _composed_mapsize(m, m_end, m1, S_end::Tuple{Int}, S1::Tuple{Int,Int}) = (S_end[1],S1[2]) 29 | _composed_mapsize(m, m_end, m1, S_end::Tuple{Int}, S1::Tuple{Int}) = (S_end[1],) 30 | _composed_mapsize(m, m_end, m1, S_end::Tuple{Int}, S1::Tuple{}) = (S_end[1],) 31 | _composed_mapsize(m, m_end, m1, S_end::Tuple{}, S1::Tuple{Int,Int}) = (1,S1[2]) 32 | _composed_mapsize(m, m_end, m1, S_end::Tuple{}, S1::Tuple{Int}) = () 33 | _composed_mapsize(m, m_end, m1, S_end::Tuple{}, S1::Tuple{}) = () 34 | 35 | function jacobian(m::ComposedMap, x) 36 | f, fd = backpropagate(x, reverse(components(m))...) 37 | fd 38 | end 39 | backpropagate(x, m1) = (m1(x), jacobian(m1, x)) 40 | function backpropagate(x, m2, ms...) 41 | f, fd = backpropagate(x, ms...) 42 | m2(f), jacobian(m2, f) * fd 43 | end 44 | 45 | for op in (:inverse, :leftinverse, :rightinverse) 46 | @eval $op(cmap::ComposedMap) = ComposedMap(reverse(map($op, components(cmap)))...) 47 | end 48 | 49 | inverse(m::ComposedMap, x) = inverse_rec(x, reverse(components(m))...) 50 | inverse_rec(x) = x 51 | inverse_rec(x, map1, maps...) = inverse_rec(inverse(map1, x), maps...) 52 | 53 | leftinverse(m::ComposedMap, x) = leftinverse_rec(x, reverse(components(m))...) 54 | leftinverse_rec(x) = x 55 | leftinverse_rec(x, map1, maps...) = leftinverse_rec(leftinverse(map1, x), maps...) 56 | 57 | rightinverse(m::ComposedMap, x) = rightinverse_rec(x, reverse(components(m))...) 58 | rightinverse_rec(x) = x 59 | rightinverse_rec(x, map1, maps...) = rightinverse_rec(rightinverse(map1, x), maps...) 60 | 61 | composedmap() = () 62 | composedmap(m) = m 63 | composedmap(m1, m2) = composedmap1(m1, m2) 64 | composedmap1(m1, m2) = composedmap2(m1, m2) 65 | composedmap2(m1, m2) = ComposedMap(m1, m2) 66 | 67 | composedmap(m1, m2, maps...) = composedmap(composedmap(m1, m2), maps...) 68 | 69 | composedmap(m1::ComposedMap, m2::ComposedMap) = 70 | ComposedMap(components(m1)..., components(m2)...) 71 | composedmap1(m1::ComposedMap, m2) = ComposedMap(components(m1)..., m2) 72 | composedmap2(m1, m2::ComposedMap) = ComposedMap(m1, components(m2)...) 73 | 74 | # Arguments to ∘ should be reversed before passing on to mapcompose 75 | (∘)(map1::AbstractMap, map2::AbstractMap) = composedmap(map2, map1) 76 | 77 | 78 | ==(m1::ComposedMap, m2::ComposedMap) = 79 | ncomponents(m1) == ncomponents(m2) && all(map(isequal, components(m1), components(m2))) 80 | hash(m::ComposedMap, h::UInt) = hashrec("ComposedMap", collect(components(m)), h) 81 | 82 | Display.combinationsymbol(m::ComposedMap) = Display.Symbol('∘') 83 | Display.displaystencil(m::ComposedMap) = 84 | composite_displaystencil(m; reversecomponents=true) 85 | show(io::IO, mime::MIME"text/plain", m::ComposedMap) = composite_show(io, mime, m) 86 | show(io::IO, m::ComposedMap) = composite_show_compact(io, m) 87 | 88 | ## Lazy multiplication 89 | 90 | "The lazy multiplication of one or more maps." 91 | struct MulMap{T,MAPS} <: CompositeLazyMap{T} 92 | maps :: MAPS 93 | end 94 | 95 | MulMap(maps::Map{T}...) where {T} = MulMap{T}(maps...) 96 | MulMap{T}(maps::Map{T}...) where {T} = MulMap{T,typeof(maps)}(maps) 97 | MulMap{T}(maps...) where {T} = _mulmap(T, convert.(Map{T}, maps)...) 98 | _mulmap(::Type{T}, maps...) where {T} = MulMap{T,typeof(maps)}(maps) 99 | 100 | similarmap(m::MulMap, ::Type{T}) where {T} = MulMap{T}(m.maps...) 101 | 102 | applymap(m::MulMap, x) = reduce(*, applymap.(components(m), Ref(x))) 103 | 104 | multiply_map() = () 105 | multiply_map(m) = m 106 | multiply_map(m1, m2) = multiply_map1(m1, m2) 107 | multiply_map1(m1, m2) = multiply_map2(m1, m2) 108 | multiply_map2(m1, m2) = MulMap(m1, m2) 109 | 110 | multiply_map(m1, m2, maps...) = multiply_map(multiply_map(m1, m2), maps...) 111 | 112 | multiply_map(m1::MulMap, m2::MulMap) = 113 | MulMap(components(m1)..., components(m2)...) 114 | multiply_map1(m1::MulMap, m2) = MulMap(components(m1)..., m2) 115 | multiply_map2(m1, m2::MulMap) = MulMap(m1, components(m2)...) 116 | 117 | function Display.displaystencil(m::MulMap) 118 | A = Any[] 119 | list = components(m) 120 | push!(A, "x -> ") 121 | push!(A, Display.SymbolObject(list[1])) 122 | push!(A, "(x)") 123 | for i in 2:length(list) 124 | push!(A, " * ") 125 | push!(A, Display.SymbolObject(list[i])) 126 | push!(A, "(x)") 127 | end 128 | A 129 | end 130 | show(io::IO, mime::MIME"text/plain", m::MulMap) = composite_show(io, mime, m) 131 | 132 | 133 | ## Lazy sum 134 | 135 | "The lazy sum of one or more maps." 136 | struct SumMap{T,MAPS} <: CompositeLazyMap{T} 137 | maps :: MAPS 138 | end 139 | 140 | SumMap(maps::Map{T}...) where {T} = SumMap{T}(maps...) 141 | SumMap{T}(maps::Map{T}...) where {T} = SumMap{T,typeof(maps)}(maps) 142 | SumMap{T}(maps...) where {T} = _summap(T, convert.(Map{T}, maps)...) 143 | _summap(::Type{T}, maps...) where {T} = SumMap{T,typeof(maps)}(maps) 144 | 145 | similarmap(m::SumMap, ::Type{T}) where {T} = SumMap{T}(m.maps...) 146 | 147 | applymap(m::SumMap, x) = reduce(+, applymap.(components(m), Ref(x))) 148 | 149 | sum_map() = () 150 | sum_map(m) = m 151 | sum_map(m1, m2) = sum_map1(m1, m2) 152 | sum_map1(m1, m2) = sum_map2(m1, m2) 153 | sum_map2(m1, m2) = SumMap(m1, m2) 154 | 155 | sum_map(m1, m2, maps...) = sum_map(sum_map(m1, m2), maps...) 156 | 157 | sum_map(m1::SumMap, m2::SumMap) = 158 | SumMap(components(m1)..., components(m2)...) 159 | sum_map1(m1::SumMap, m2) = SumMap(components(m1)..., m2) 160 | sum_map2(m1, m2::SumMap) = SumMap(m1, components(m2)...) 161 | 162 | function Display.displaystencil(m::SumMap) 163 | A = Any[] 164 | list = components(m) 165 | push!(A, "x -> ") 166 | push!(A, Display.SymbolObject(list[1])) 167 | push!(A, "(x)") 168 | for i in 2:length(list) 169 | push!(A, " + ") 170 | push!(A, Display.SymbolObject(list[i])) 171 | push!(A, "(x)") 172 | end 173 | A 174 | end 175 | show(io::IO, mime::MIME"text/plain", m::SumMap) = composite_show(io, mime, m) 176 | 177 | 178 | # Define the jacobian of a composite map 179 | jacobian(m::ComposedMap) = composite_jacobian(reverse(components(m))...) 180 | composite_jacobian(map1) = jacobian(map1) 181 | composite_jacobian(map1, map2) = multiply_map(jacobian(map1) ∘ map2, jacobian(map2)) 182 | function composite_jacobian(map1, map2, maps...) 183 | rest = ComposedMap(reverse(maps)..., map2) 184 | f1 = jacobian(map1) ∘ rest 185 | f2 = composite_jacobian(map2, maps...) 186 | multiply_map(f1, f2) 187 | end 188 | 189 | jacobian(m::MulMap) = mul_jacobian(components(m)...) 190 | mul_jacobian() = () 191 | mul_jacobian(map1) = jacobian(map1) 192 | mul_jacobian(map1, map2) = sum_map(multiply_map(jacobian(map1), map2), multiply_map(map1, jacobian(map2))) 193 | function mul_jacobian(map1, map2, maps...) 194 | rest = multiply_map(map2, maps...) 195 | mul_jacobian(map1, rest) 196 | end 197 | function jacobian(m::MulMap, x) 198 | z = map(t -> applymap(t,x), components(m)) 199 | zd = map(t -> jacobian(t, x), components(m)) 200 | sum(prod(z[1:i-1]) * zd[i] * prod(z[i+1:end]) for i in 1:ncomponents(m)) 201 | end 202 | 203 | 204 | jacobian(m::SumMap) = sum_jacobian(components(m)...) 205 | sum_jacobian() = () 206 | sum_jacobian(map1) = jacobian(map1) 207 | sum_jacobian(maps...) = sum_map(map(jacobian, maps)...) 208 | 209 | jacobian(m::SumMap, x) = sum(jacobian(mc, x) for mc in components(m)) 210 | -------------------------------------------------------------------------------- /src/generic/domain.jl: -------------------------------------------------------------------------------- 1 | # Definition of the abstract Domain type and its interface 2 | 3 | # The type Domain{T} is defined in IntervalSets.jl 4 | 5 | eltype(::Type{<:Domain{T}}) where {T} = T 6 | prectype(::Type{<:Domain{T}}) where {T} = prectype(T) 7 | numtype(::Type{<:Domain{T}}) where {T} = numtype(T) 8 | 9 | convert_numtype(d::Domain{T}, ::Type{U}) where {T,U} = convert(Domain{to_numtype(T, U)}, d) 10 | convert_prectype(d::Domain{T}, ::Type{U}) where {T,U} = convert(Domain{to_prectype(T, U)}, d) 11 | 12 | Domain(d) = convert(Domain, d) 13 | 14 | # Concrete types can implement similardomain(d, ::Type{T}) where {T} 15 | # to support convert(Domain{T}, d) functionality. 16 | convert(::Type{Domain{T}}, d::Domain{T}) where {T} = d 17 | convert(::Type{Domain{T}}, d::Domain{S}) where {S,T} = similardomain(d, T) 18 | 19 | "Can the domains be promoted without throwing an error?" 20 | promotable_domains(domains...) = promotable_eltypes(map(eltype, domains)...) 21 | promotable_eltypes(types...) = isconcretetype(promote_type(types...)) 22 | promotable_eltypes(::Type{S}, ::Type{T}) where {S<:AbstractVector,T<:AbstractVector} = 23 | promotable_eltypes(eltype(S), eltype(T)) 24 | 25 | "Promote the given domains to have a common element type." 26 | promote_domains() = () 27 | promote_domains(domains...) = promote_domains(domains) 28 | promote_domains(domains) = convert_eltype.(mapreduce(eltype, promote_type, domains), domains) 29 | 30 | promote_domains(domains::AbstractSet{<:Domain{T}}) where {T} = domains 31 | promote_domains(domains::AbstractSet{<:Domain}) = Set(promote_domains(collect(domains))) 32 | 33 | convert_eltype(::Type{T}, d::Domain) where {T} = convert(Domain{T}, d) 34 | convert_eltype(::Type{T}, d) where {T} = _convert_eltype(T, d, eltype(d)) 35 | _convert_eltype(::Type{T}, d, ::Type{T}) where {T} = d 36 | _convert_eltype(::Type{T}, d, ::Type{S}) where {S,T} = 37 | error("Don't know how to convert the `eltype` of $(d).") 38 | # Some standard cases 39 | convert_eltype(::Type{T}, d::AbstractArray) where {T} = convert(AbstractArray{T}, d) 40 | convert_eltype(::Type{T}, d::AbstractRange) where {T} = map(T, d) 41 | convert_eltype(::Type{T}, d::Set) where {T} = convert(Set{T}, d) 42 | 43 | promote(d1::Domain, d2::Domain) = promote_domains((d1, d2)) 44 | promote(d1::Domain, d2) = promote_domains((d1, d2)) 45 | promote(d1, d2::Domain) = promote_domains((d1, d2)) 46 | 47 | "A `EuclideanDomain` is any domain whose eltype is `<:StaticVector{N,T}`." 48 | const EuclideanDomain{N,T} = Domain{<:StaticVector{N,T}} 49 | 50 | "A `VectorDomain` is any domain whose eltype is `Vector{T}`." 51 | const VectorDomain{T} = Domain{Vector{T}} 52 | 53 | const AbstractArrayDomain{T} = Domain{<:AbstractArray{T}} 54 | 55 | CompositeTypes.Display.displaysymbol(d::Domain) = 'D' 56 | 57 | "What is the Euclidean dimension of the domain?" 58 | dimension(::Domain{T}) where {T} = euclideandimension(T) 59 | 60 | "Is the given combination of point and domain compatible?" 61 | iscompatiblepair(x, d) = _iscompatiblepair(x, d, typeof(x), eltype(d)) 62 | _iscompatiblepair(x, d, ::Type{S}, ::Type{T}) where {S,T} = 63 | _iscompatiblepair(x, d, S, T, promote_type(S, T)) 64 | _iscompatiblepair(x, d, ::Type{S}, ::Type{T}, ::Type{U}) where {S,T,U} = true 65 | _iscompatiblepair(x, d, ::Type{S}, ::Type{T}, ::Type{Any}) where {S,T} = false 66 | _iscompatiblepair(x, d, ::Type{S}, ::Type{Any}, ::Type{Any}) where {S} = true 67 | 68 | # Some generic cases where we can be sure: 69 | iscompatiblepair(x::SVector{N}, ::EuclideanDomain{N}) where {N} = true 70 | iscompatiblepair(x::SVector{N}, ::EuclideanDomain{M}) where {N,M} = false 71 | iscompatiblepair(x::AbstractVector, ::EuclideanDomain{N}) where {N} = length(x) == N 72 | 73 | # Note: there are cases where this warning reveals a bug, and cases where it is 74 | # annoying. In cases where it is annoying, the domain may want to specialize `in`. 75 | compatible_or_false(x, domain) = 76 | iscompatiblepair(x, domain) ? true : (@warn "`in`: incompatible combination of point: $(typeof(x)) and domain eltype: $(eltype(domain)). Returning false."; false) 77 | 78 | compatible_or_false(x::AbstractArray, domain::AbstractArrayDomain) = 79 | iscompatiblepair(x, domain) ? true : (@warn "`in`: incompatible combination of vector with length $(length(x)) and domain '$(domain)' with dimension $(dimension(domain)). Returning false."; false) 80 | 81 | 82 | "Promote point and domain to compatible types." 83 | promote_pair(x, d) = _promote_pair(x, d, promote_type(typeof(x), eltype(d))) 84 | _promote_pair(x, d, ::Type{T}) where {T} = convert(T, x), convert(Domain{T}, d) 85 | _promote_pair(x, d, ::Type{Any}) = x, d 86 | # Some exceptions: 87 | # - matching types: avoid promotion just in case it is expensive 88 | promote_pair(x::T, d::Domain{T}) where {T} = x, d 89 | # - `Any` domain 90 | promote_pair(x, d::Domain{Any}) = x, d 91 | # - tuples: these are typically composite domains and the elements may be promoted later on 92 | promote_pair(x::Tuple, d::Domain{<:Tuple}) = x, d 93 | # - abstract vectors: promotion may be expensive 94 | promote_pair(x::AbstractArray, d::AbstractArrayDomain) = x, d 95 | # - SVector: promotion is likely cheap 96 | promote_pair(x::AbstractVector{S}, d::EuclideanDomain{N,T}) where {N,S,T} = 97 | _promote_pair(x, d, SVector{N,promote_type(S, T)}) 98 | 99 | 100 | # At the level of Domain we attempt to promote the arguments to compatible 101 | # types, then we invoke indomain. Concrete subtypes should implement 102 | # indomain. They may also implement `in` in order to accept more types. 103 | # 104 | # Note that if the type of x and the element type of d don't match, then 105 | # both x and the domain may be promoted (using convert(Domain{T}, d)) syntax). 106 | in(x, d::Domain) = compatible_or_false(x, d) && indomain(promote_pair(x, d)...) 107 | 108 | 109 | """ 110 | Return a suitable tolerance to use for verifying whether a point is close to 111 | a domain. Typically, the tolerance is close to the precision limit of the numeric 112 | type associated with the domain. 113 | """ 114 | default_tolerance(d::Domain) = default_tolerance(prectype(d)) 115 | default_tolerance(::Type{T}) where {T<:AbstractFloat} = 100eps(T) 116 | 117 | 118 | """ 119 | `approx_in(x, domain::Domain [, tolerance])` 120 | 121 | Verify whether a point lies in the given domain with a certain tolerance. 122 | 123 | The tolerance has to be positive. The meaning of the tolerance, in relation 124 | to the possible distance of the point to the domain, is domain-dependent. 125 | Usually, if the outcome is true, it means that the distance of the point to 126 | the domain is smaller than a constant times the tolerance. That constant may 127 | depend on the domain. 128 | 129 | Up to inexact computations due to floating point numbers, it should also be 130 | the case that `approx_in(x, d, 0) == in(x,d)`. This implies that `approx_in` 131 | reflects whether a domain is open or closed. 132 | """ 133 | approx_in(x, d::Domain) = approx_in(x, d, default_tolerance(d)) 134 | 135 | function compatible_or_false(x, d, tol) 136 | tol >= 0 || error("Tolerance has to be positive in `approx_in`.") 137 | compatible_or_false(x, d) 138 | end 139 | 140 | approx_in(x, d::Domain, tol) = 141 | compatible_or_false(x, d, tol) && approx_indomain(promote_pair(x, d)..., tol) 142 | 143 | # Fallback to `in` 144 | approx_indomain(x, d::Domain, tol) = in(x, d) 145 | 146 | 147 | isapprox(d1::Domain, d2::Domain; kwds...) = d1 == d2 148 | 149 | isreal(d::Domain) = isreal(eltype(d)) 150 | 151 | infimum(d::Domain) = minimum(d) # if the minimum exists, then it is also the infimum 152 | supremum(d::Domain) = maximum(d) # if the maximum exists, then it is also the supremum 153 | 154 | 155 | """ 156 | Return a bounding box of the given domain. 157 | 158 | A bounding box is an interval, a hyperrectangle or the full space. It is such that 159 | each point in the domain also lies in the bounding box. 160 | """ 161 | boundingbox(d) = FullSpace{eltype(d)}() 162 | 163 | "Return the boundary of the given domain as a domain." 164 | function boundary end 165 | const ∂ = boundary 166 | 167 | """ 168 | Return the normal of the domain at the point `x`. 169 | 170 | It is assumed that `x` is a point on the boundary of the domain. 171 | """ 172 | function normal end 173 | 174 | """ 175 | Return the tangents of the domain at the point `x`. The tangents form a 176 | basis for the tangent plane, perpendicular to the normal direction at `x`. 177 | """ 178 | function tangents end 179 | 180 | # "Lazy representation of the boundary of a domain." 181 | # struct Boundary{T,D} <: Domain{T} 182 | # domain :: D 183 | # end 184 | # 185 | # domain(d::Boundary) = d.domain 186 | # 187 | # Boundary(domain) = Boundary{eltype(domain)}(domain) 188 | # Boundary{T}(domain) where {T} = Boundary{T,typeof(domain)}(domain) 189 | # Boundary{T}(domain::Domain{T}) where {T} = Boundary{T,typeof(domain)}(domain) 190 | # Boundary{T}(domain::Domain{S}) where {S,T} = Boundary{T}(convert(Domain{T}, domain)) 191 | # 192 | # indomain(x, d::Boundary) = in(x, boundary(domain(d))) 193 | -------------------------------------------------------------------------------- /src/generic/mapped.jl: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | A `MappedDomain` represents the mapping of a domain. 4 | 5 | The map of a domain `d` under the mapping `y=f(x)` consists of all points `f(x)` 6 | with `x ∈ d`. The characteristic function of a mapped domain is defined in 7 | terms of the inverse map `g = inverse(f)`: 8 | ``` 9 | x ∈ m ⟺ g(x) ∈ d 10 | ``` 11 | """ 12 | abstract type AbstractMappedDomain{T} <: SimpleLazyDomain{T} end 13 | 14 | superdomain(d::AbstractMappedDomain) = d.domain 15 | 16 | const MappedVectorDomain{T} = AbstractMappedDomain{Vector{T}} 17 | 18 | tointernalpoint(d::AbstractMappedDomain, x) = inverse_map(d, x) 19 | toexternalpoint(d::AbstractMappedDomain, y) = forward_map(d, y) 20 | 21 | canonicaldomain(d::AbstractMappedDomain) = canonicaldomain(superdomain(d)) 22 | mapfrom_canonical(d::AbstractMappedDomain) = forward_map(d) ∘ mapfrom_canonical(superdomain(d)) 23 | mapto_canonical(d::AbstractMappedDomain) = mapto_canonical(superdomain(d)) ∘ inverse_map(d) 24 | 25 | 26 | # TODO: check whether the map alters the dimension 27 | dimension(d::MappedVectorDomain) = dimension(superdomain(d)) 28 | 29 | # TODO: check whether the map affects these properties 30 | isempty(d::AbstractMappedDomain) = isempty(superdomain(d)) 31 | isopenset(d::AbstractMappedDomain) = isopenset(superdomain(d)) 32 | isclosedset(d::AbstractMappedDomain) = isclosedset(superdomain(d)) 33 | 34 | corners(d::AbstractMappedDomain) = [forward_map(d, x) for x in corners(superdomain(d))] 35 | 36 | ## I/O functionality 37 | 38 | show(io::IO, mime::MIME"text/plain", d::AbstractMappedDomain) = composite_show(io, mime, d) 39 | Display.displaystencil(d::AbstractMappedDomain) = 40 | map_stencil_broadcast(forward_map(d), superdomain(d)) 41 | Display.object_parentheses(d::AbstractMappedDomain) = 42 | Display.object_parentheses(forward_map(d)) 43 | Display.stencil_parentheses(d::AbstractMappedDomain) = 44 | Display.stencil_parentheses(forward_map(d)) 45 | 46 | 47 | 48 | "A `MappedDomain` stores the inverse map of a mapped domain." 49 | struct MappedDomain{T,F,D} <: AbstractMappedDomain{T} 50 | invmap :: F 51 | domain :: D 52 | end 53 | 54 | # In the constructor, we have to decide which T to use for the MappedDomain. 55 | # - we don't know anything about invmap: deduce T from the given domain 56 | MappedDomain(invmap, domain::Domain{T}) where {T} = MappedDomain{T}(invmap, domain) 57 | # - if the map is a Map{T}, use that T for the MappedDomain 58 | MappedDomain(invmap::Map{T}, domain::Domain{S}) where {S,T} = MappedDomain{T}(invmap, domain) 59 | # - if T is given in the constructor, by all means we use that 60 | MappedDomain{T}(invmap, domain::Domain) where {T} = 61 | MappedDomain{T,typeof(invmap),typeof(domain)}(invmap, domain) 62 | # - in that case, if the map is a Map{S}, make sure that S matches T 63 | MappedDomain{T}(invmap::Map{T}, domain::Domain) where {T} = 64 | MappedDomain{T,typeof(invmap),typeof(domain)}(invmap, domain) 65 | MappedDomain{T}(invmap::Map{S}, domain::Domain) where {S,T} = 66 | MappedDomain{T}(convert(Map{T}, invmap), domain) 67 | 68 | similardomain(d::MappedDomain, ::Type{T}) where {T} = 69 | MappedDomain{T}(d.invmap, d.domain) 70 | 71 | forward_map(d::MappedDomain) = rightinverse(d.invmap) 72 | forward_map(d::MappedDomain, x) = rightinverse(d.invmap, x) 73 | 74 | inverse_map(d::MappedDomain) = d.invmap 75 | inverse_map(d::MappedDomain, y) = d.invmap(y) 76 | 77 | "Map a domain with the inverse of the given map" 78 | map_domain(map, domain::Domain) = _map_domain(map, domain) 79 | 80 | # Fallback: we don't know anything about map, just try to invert 81 | _map_domain(map, domain) = mapped_domain(inverse(map), domain) 82 | _map_domain(map::Map{T}, domain::Domain{T}) where {T} = 83 | mapped_domain(inverse(map), domain) 84 | # If map is a Map{T}, then verify and if necessary update T 85 | function _map_domain(map::Map, domain) 86 | U = codomaintype(map, eltype(domain)) 87 | if U == Union{} 88 | error("incompatible types of $(map) and $(domain)") 89 | end 90 | mapped_domain(inverse(convert(Map{U}, map)), convert(Domain{U}, domain)) 91 | end 92 | 93 | ==(a::MappedDomain, b::MappedDomain) = (a.invmap == b.invmap) && (superdomain(a) == superdomain(b)) 94 | 95 | 96 | "Make a mapped domain with the given inverse map" 97 | mapped_domain(invmap, domain::Domain) = _mapped_domain(invmap, domain) 98 | 99 | # We face the same task as in the constructor: attempt to identify T 100 | # Here, we are more flexible, and attempt to do more conversions. We assume 101 | # that users invoking the MappedDomain constructor know what they are doing, 102 | # but users invoking mapped_domain just expect it to work. 103 | 104 | # - we don't know anything about invmap, just pass it on 105 | _mapped_domain(invmap, domain) = MappedDomain(invmap, domain) 106 | # - invmap is a Map{T}: its codomaintype should match the eltype of the domain 107 | # -- first, update the numtype 108 | _mapped_domain(invmap::Map, domain) = 109 | _mapped_domain(invmap, domain, promote_type(numtype(invmap),numtype(domain))) 110 | _mapped_domain(invmap::Map{T}, domain::Domain{S}, ::Type{U}) where {S,T,U} = 111 | _mapped_domain2(convert_numtype(invmap,U), convert_numtype(domain,U)) 112 | # -- then, ensure the codomaintype of the map equals the element type of the domain 113 | _mapped_domain2(invmap, domain) = _mapped_domain2(invmap, domain, codomaintype(invmap), eltype(domain)) 114 | # --- it's okay 115 | _mapped_domain2(invmap, domain, ::Type{T}, ::Type{T}) where {T} = 116 | MappedDomain(invmap, domain) 117 | # --- it's not okay: attempt to convert the map (triggers e.g. when combining a scalar 118 | # LinearMap with a vector domain) 119 | _mapped_domain2(invmap, domain, ::Type{S}, ::Type{T}) where {S,T} = 120 | MappedDomain(convert(Map{T}, invmap), domain) 121 | 122 | # TODO: deprecate this syntax, it is confusing 123 | (∘)(domain::Domain, invmap::Function) = mapped_domain(invmap, domain) 124 | (∘)(domain::Domain, invmap::AbstractMap) = mapped_domain(invmap, domain) 125 | 126 | # Avoid nested mapping domains, construct a composite map instead 127 | # This assumes that the map types can be combined using \circ 128 | mapped_domain(invmap, d::MappedDomain) = mapped_domain(inverse_map(d) ∘ invmap, superdomain(d)) 129 | 130 | boundary(d::MappedDomain) = _boundary(d, boundary(superdomain(d)), inverse_map(d)) 131 | _boundary(d::MappedDomain, superbnd, invmap) = MappedDomain(invmap, superbnd) 132 | _boundary(d::MappedDomain, superbnd::UnionDomain, invmap) = 133 | UnionDomain(map(t->mapped_domain(invmap, t), components(superbnd))) 134 | 135 | interior(d::MappedDomain) = _interior(d, superdomain(d), inverse_map(d)) 136 | _interior(d::MappedDomain, superdomain, invmap) = MappedDomain(invmap, interior(superdomain)) 137 | closure(d::MappedDomain) = _closure(d, superdomain(d), inverse_map(d)) 138 | _closure(d::MappedDomain, superdomain, invmap) = MappedDomain(invmap, closure(superdomain)) 139 | 140 | convert(::Type{MappedDomain}, d::Domain{T}) where {T} = 141 | MappedDomain{T}(mapto_canonical(d), canonicaldomain(d)) 142 | convert(::Type{MappedDomain{T}}, d::Domain) where {T} = 143 | MappedDomain{T}(mapto_canonical(d), canonicaldomain(d)) 144 | 145 | 146 | "A `ParametricDomain` stores the forward map of a mapped domain." 147 | struct ParametricDomain{T,F,D} <: AbstractMappedDomain{T} 148 | fmap :: F 149 | domain :: D 150 | end 151 | 152 | ParametricDomain(fmap, domain::Domain) = ParametricDomain{codomaintype(fmap)}(fmap, domain) 153 | ParametricDomain{T}(fmap, domain::Domain) where {T} = 154 | ParametricDomain{T,typeof(fmap),typeof(domain)}(fmap, domain) 155 | 156 | similardomain(d::ParametricDomain, ::Type{T}) where {T} = 157 | ParametricDomain{T}(d.fmap, d.domain) 158 | 159 | forward_map(d::ParametricDomain) = d.fmap 160 | forward_map(d::ParametricDomain, x) = d.fmap(x) 161 | 162 | function indomain(x, d::ParametricDomain) 163 | # To check for membership, we can't use the inverse map because it may not exist 164 | # We assume a left inverse exists, but the left inverse may be many-to-one. 165 | # So we also have to check whether the left-inverse-point maps back to x 166 | y = leftinverse(d.fmap, x) 167 | x2 = forward_map(d, y) 168 | isapprox(x, x2) 169 | end 170 | 171 | ==(d1::ParametricDomain, d2::ParametricDomain) = 172 | forward_map(d1) == forward_map(d2) && superdomain(d1) == superdomain(d2) 173 | hash(d::ParametricDomain, h::UInt) = hashrec(forward_map(d), superdomain(d), h) 174 | 175 | "Return the domain that results from mapping the given domain." 176 | parametric_domain(fmap, domain::Domain) = ParametricDomain(fmap, domain) 177 | parametric_domain(fmap, domain::ParametricDomain) = 178 | parametric_domain(fmap ∘ forward_map(domain), superdomain(domain)) 179 | 180 | boundary(d::ParametricDomain) = _boundary(d, boundary(superdomain(d)), forward_map(d)) 181 | _boundary(d::ParametricDomain, superbnd, fmap) = ParametricDomain(fmap, superbnd) 182 | _boundary(d::ParametricDomain, superbnd::UnionDomain, fmap) = 183 | UnionDomain(map(t -> parametric_domain(fmap, t), components(superbnd))) 184 | 185 | interior(d::ParametricDomain) = _interior(d, superdomain(d), forward_map(d)) 186 | _interior(d::ParametricDomain, superdomain, fmap) = ParametricDomain(fmap, interior(superdomain)) 187 | closure(d::ParametricDomain) = _closure(d, superdomain(d), forward_map(d)) 188 | _closure(d::ParametricDomain, superdomain, fmap) = ParametricDomain(fmap, closure(superdomain)) 189 | -------------------------------------------------------------------------------- /src/generic/productdomain.jl: -------------------------------------------------------------------------------- 1 | 2 | "A `ProductDomain` represents the cartesian product of other domains." 3 | abstract type ProductDomain{T} <: CompositeDomain{T} end 4 | 5 | composition(d::ProductDomain) = Product() 6 | 7 | components(d::ProductDomain) = d.domains 8 | 9 | "The factors of a product domain (equivalent to `components(d)`)." 10 | factors(d::ProductDomain) = components(d) 11 | "The number of factors of a product domain." 12 | nfactors(d) = length(factors(d)) 13 | "Factor `I...` of a domain." 14 | factor(d, I...) = getindex(factors(d), I...) 15 | 16 | ==(d1::ProductDomain, d2::ProductDomain) = mapreduce(==, &, components(d1), components(d2)) 17 | hash(d::ProductDomain, h::UInt) = hashrec("ProductDomain", collect(components(d)), h) 18 | 19 | isempty(d::ProductDomain) = any(isempty, components(d)) 20 | isclosedset(d::ProductDomain) = all(isclosedset, components(d)) 21 | isopenset(d::ProductDomain) = all(isopenset, components(d)) 22 | 23 | issubset(d1::ProductDomain, d2::ProductDomain) = 24 | compatibleproductdims(d1, d2) && all(map(issubset, components(d1), components(d2))) 25 | 26 | volume(d::ProductDomain) = prod(map(volume, components(d))) 27 | 28 | distance_to(d::ProductDomain, x) = sqrt(sum(distance_to(component(d, i), x[i])^2 for i in 1:ncomponents(d))) 29 | 30 | compatibleproductdims(d1::ProductDomain, d2::ProductDomain) = 31 | dimension(d1) == dimension(d2) && 32 | all(map(==, map(dimension, components(d1)), map(dimension, components(d2)))) 33 | 34 | Display.combinationsymbol(d::ProductDomain) = Display.Times() 35 | Display.displaystencil(d::ProductDomain) = composite_displaystencil(d) 36 | show(io::IO, mime::MIME"text/plain", d::ProductDomain) = composite_show(io, mime, d) 37 | show(io::IO, d::ProductDomain) = composite_show_compact(io, d) 38 | 39 | boundary_part(d::ProductDomain{T}, domains, i) where {T} = 40 | ProductDomain{T}(domains[1:i-1]..., boundary(domains[i]), domains[i+1:end]...) 41 | 42 | boundary(d::ProductDomain) = productboundary(d) 43 | productboundary(d) = productboundary(d, factors(d)) 44 | productboundary(d, domains) = 45 | UnionDomain(boundary_part(d, domains, i) for i in 1:length(domains)) 46 | productboundary(d, domains::Tuple) = 47 | UnionDomain(tuple((boundary_part(d, domains, i) for i in 1:length(domains))...)) 48 | 49 | boundingbox(d::ProductDomain{T}) where {T} = ProductDomain{T}(map(boundingbox, components(d))) 50 | 51 | infimum(d::ProductDomain) = toexternalpoint(d, map(infimum, components(d))) 52 | supremum(d::ProductDomain) = toexternalpoint(d, map(supremum, components(d))) 53 | leftendpoint(d::ProductDomain) = toexternalpoint(d, map(leftendpoint, components(d))) 54 | rightendpoint(d::ProductDomain) = toexternalpoint(d, map(rightendpoint, components(d))) 55 | 56 | interior(d::ProductDomain) = ProductDomain(map(interior, components(d))) 57 | closure(d::ProductDomain) = ProductDomain(map(closure, components(d))) 58 | 59 | 60 | VcatDomainElement = Union{Domain{<:Number},EuclideanDomain} 61 | 62 | ProductDomain(domains...) = _ProductDomain(map(Domain, domains)...) 63 | _ProductDomain(domains...) = TupleProductDomain(domains...) 64 | _ProductDomain(domains::VcatDomainElement...) = VcatDomain(domains...) 65 | ProductDomain(domains::AbstractVector) = ArrayProductDomain(domains) 66 | # To create a tuple product domain, invoke ProductDomain{T}. Here, we splat 67 | # and this may end up creating a VcatDomain instead. 68 | ProductDomain(domains::Tuple) = ProductDomain(domains...) 69 | 70 | ProductDomain{T}(domains::Tuple) where {T} = ProductDomain{T}(domains...) 71 | ProductDomain{T}(domains...) where {T} = _TypedProductDomain(T, domains...) 72 | _TypedProductDomain(::Type{SVector{N,T}}, domains...) where {N,T} = VcatDomain{N,T}(domains...) 73 | _TypedProductDomain(::Type{T}, domains...) where {T<:Vector} = ArrayProductDomain{T}(domains...) 74 | _TypedProductDomain(::Type{T}, domains...) where {T<:Tuple} = TupleProductDomain{T}(domains...) 75 | _TypedProductDomain(::Type{T}, domains...) where {T} = TupleProductDomain{T}(domains...) 76 | 77 | productdomain() = () 78 | productdomain(d) = d 79 | productdomain(d1, d2, d3...) = productdomain(productdomain(d1, d2), d3...) 80 | 81 | productdomain(d1, d2) = productdomain1(d1, d2) 82 | productdomain1(d1, d2) = productdomain2(d1, d2) 83 | productdomain2(d1, d2) = ProductDomain(d1, d2) 84 | 85 | productdomain(d1::ProductDomain, d2::ProductDomain) = 86 | ProductDomain(factors(d1)..., factors(d2)...) 87 | productdomain1(d1::ProductDomain, d2) = ProductDomain(factors(d1)..., d2) 88 | productdomain2(d1, d2::ProductDomain) = ProductDomain(d1, factors(d2)...) 89 | 90 | # Only override cross for variables of type Domain, it may have a different 91 | # meaning for other variables (like the vector cross product) 92 | cross(x::Domain...) = productdomain(x...) 93 | 94 | ^(d::Domain, n::Int) = productdomain(ntuple(i -> d, n)...) 95 | 96 | similardomain(d::ProductDomain, ::Type{T}) where {T} = ProductDomain{T}(components(d)) 97 | 98 | canonicaldomain(d::ProductDomain) = any(map(hascanonicaldomain, factors(d))) ? 99 | ProductDomain(map(canonicaldomain, components(d))) : d 100 | 101 | mapto_canonical(d::ProductDomain) = ProductMap(map(mapto_canonical, components(d))) 102 | mapfrom_canonical(d::ProductDomain) = ProductMap(map(mapfrom_canonical, components(d))) 103 | 104 | for CTYPE in (Parameterization, Equal) 105 | @eval canonicaldomain(ctype::$CTYPE, d::ProductDomain) = 106 | any(hascanonicaldomain.(Ref(ctype), factors(d))) ? 107 | ProductDomain(canonicaldomain.(Ref(ctype), factors(d))) : d 108 | @eval mapto_canonical(ctype::$CTYPE, d::ProductDomain) = 109 | ProductMap(mapto_canonical.(Ref(ctype), factors(d))) 110 | @eval mapfrom_canonical(ctype::$CTYPE, d::ProductDomain) = 111 | ProductMap(mapfrom_canonical.(Ref(ctype), factors(d))) 112 | end 113 | 114 | 115 | """ 116 | A `VcatDomain` concatenates the element types of its member domains in a single 117 | static vector. 118 | """ 119 | struct VcatDomain{N,T,DIM,DD} <: ProductDomain{SVector{N,T}} 120 | domains::DD 121 | end 122 | 123 | VcatDomain(domains::Union{Vector,Tuple}) = VcatDomain(domains...) 124 | function VcatDomain(domains...) 125 | T = numtype(domains...) 126 | N = sum(map(dimension, domains)) 127 | VcatDomain{N,T}(domains...) 128 | end 129 | 130 | VcatDomain{N,T}(domains::Union{AbstractVector,Tuple}) where {N,T} = VcatDomain{N,T}(domains...) 131 | function VcatDomain{N,T}(domains...) where {N,T} 132 | DIM = map(dimension, domains) 133 | VcatDomain{N,T,DIM}(convert_numtype.(domains, T)...) 134 | end 135 | 136 | VcatDomain{N,T,DIM}(domains...) where {N,T,DIM} = 137 | VcatDomain{N,T,DIM,typeof(domains)}(domains) 138 | 139 | tointernalpoint(d::VcatDomain{N,T,DIM}, x) where {N,T,DIM} = 140 | convert_fromcartesian(x, Val{DIM}()) 141 | toexternalpoint(d::VcatDomain{N,T,DIM}, y) where {N,T,DIM} = 142 | convert_tocartesian(y, Val{DIM}()) 143 | 144 | """ 145 | A `ArrayProductDomain` is a product domain of arbitrary dimension where the 146 | element type is a vector, and all member domains have the same element type. 147 | """ 148 | struct ArrayProductDomain{V<:AbstractArray,DD<:AbstractArray} <: ProductDomain{V} 149 | domains::DD 150 | 151 | function ArrayProductDomain{V,DD}(domains::DD) where {V,DD} 152 | @assert eltype(eltype(domains)) == eltype(V) 153 | new(domains) 154 | end 155 | end 156 | 157 | ArrayProductDomain(domains::AbstractArray) = 158 | ArrayProductDomain{Array{eltype(eltype(domains)),ndims(domains)}}(domains) 159 | 160 | ArrayProductDomain{V}(domains::AbstractArray{<:Domain{T}}) where {T,V<:AbstractArray{T}} = 161 | ArrayProductDomain{V,typeof(domains)}(domains) 162 | function ArrayProductDomain{V}(domains::AbstractArray) where {T,V<:AbstractArray{T}} 163 | Tdomains = convert.(Domain{T}, domains) 164 | ArrayProductDomain{V}(Tdomains) 165 | end 166 | 167 | # Convenience: allow constructor to be called with multiple arguments, or with 168 | # a container that is not a vector 169 | ArrayProductDomain(domains::Domain...) = ArrayProductDomain(domains) 170 | ArrayProductDomain(domains) = ArrayProductDomain(collect(domains)) 171 | ArrayProductDomain{V}(domains::Domain...) where {V} = ArrayProductDomain{V}(domains) 172 | ArrayProductDomain{V}(domains) where {V} = ArrayProductDomain{V}(collect(domains)) 173 | 174 | # the dimension equals the number of composite elements 175 | dimension(d::ArrayProductDomain) = ncomponents(d) 176 | 177 | tointernalpoint(d::ArrayProductDomain, x) = 178 | (@assert length(x) == dimension(d); x) 179 | toexternalpoint(d::ArrayProductDomain, y) = 180 | (@assert length(y) == dimension(d); y) 181 | 182 | 183 | 184 | 185 | 186 | """ 187 | A `TupleProductDomain` is a product domain that concatenates the elements of 188 | its member domains in a tuple. 189 | """ 190 | struct TupleProductDomain{T,DD} <: ProductDomain{T} 191 | domains::DD 192 | end 193 | 194 | TupleProductDomain(domains::Vector) = TupleProductDomain(domains...) 195 | TupleProductDomain(domains::Domain...) = TupleProductDomain(domains) 196 | TupleProductDomain(domains...) = TupleProductDomain(map(Domain, domains)...) 197 | function TupleProductDomain(domains::Tuple) 198 | T = Tuple{map(eltype, domains)...} 199 | TupleProductDomain{T}(domains) 200 | end 201 | 202 | TupleProductDomain{T}(domains::Vector) where {T} = TupleProductDomain{T}(domains...) 203 | TupleProductDomain{T}(domains...) where {T} = TupleProductDomain{T}(domains) 204 | function TupleProductDomain{T}(domains::Tuple) where {T<:Tuple} 205 | Tdomains = map((t, d) -> convert(Domain{t}, d), tuple(T.parameters...), domains) 206 | TupleProductDomain{T,typeof(Tdomains)}(Tdomains) 207 | end 208 | TupleProductDomain{T}(domains::Tuple) where {T} = 209 | TupleProductDomain{T,typeof(domains)}(domains) 210 | -------------------------------------------------------------------------------- /test/test_setoperations.jl: -------------------------------------------------------------------------------- 1 | @testset "set operations" begin 2 | @testset "union" begin 3 | d1 = UnitDisk() 4 | d2 = (-.9..0.9)^2 5 | d3 = ProductDomain(-.5 .. -.1, ChebyshevInterval()) 6 | d4 = (0.0..1.5) 7 | d5 = [1.0,3.0] 8 | 9 | @test convert(Domain{SVector{2,Float64}}, d3) isa Domain{SVector{2,Float64}} 10 | 11 | u1 = UnitDisk() ∪ (-.9..0.9)^2 12 | u2 = u1 ∪ d3 13 | @test dimension(u1) == 2 14 | @test dimension(u2) == 2 15 | @test boundingbox(u1) == ChebyshevInterval()^2 16 | 17 | u3 = d3 ∪ u1 18 | u4 = u1 ∪ u2 19 | @test SVector(0.,.15) ∈ u3 20 | @test SVector(0.,.15) ∈ u4 21 | @test SVector(1.1,.75) ∉ u3 22 | @test SVector(1.1,.75) ∉ u4 23 | 24 | ũ1 = UnionDomain(d1,d2) 25 | @test u1 == ũ1 26 | @test UnionDomain{SVector{2,Float64}}(d1,d2) == ũ1 27 | ũ1 = UnionDomain((d1,d2)) 28 | @test u1 == ũ1 29 | ũ2 = UnionDomain([d1,d2]) 30 | @test ũ2 == ũ2 31 | @test u1 == ũ2 32 | @test UnionDomain{SVector{2,Float64}}(d1) isa UnionDomain 33 | 34 | # Don't create a union with two identical elements 35 | @test UnitDisk() ∪ UnitDisk() isa UnitDisk 36 | 37 | # union with non-Domain type that implements domain interface 38 | u45 = (0.0..1.5) ∪ [1.0,3.0] 39 | @test u45 isa Domain{Float64} 40 | @test u45 isa UnionDomain 41 | @test eltype(component(u45,1)) == Float64 42 | @test eltype(component(u45,2)) == Float64 43 | @test 0.2 ∈ u45 44 | @test 1.2 ∈ u45 45 | @test 3 ∈ u45 46 | @test -1.2 ∉ u45 47 | @test convert(Domain{BigFloat}, u45) isa Domain{BigFloat} 48 | 49 | u45b = (0.0..1.5) ∪ [1,3] 50 | @test u45b isa Domain{Float64} 51 | @test component(u45b,2) isa AbstractArray{Float64} 52 | @test [1,3] ∪ (0.0..1.5) isa Domain{Float64} 53 | 54 | @test issubset([0,1], 0..1) 55 | @test !issubset([0,1,2], 0..1) 56 | @test issubset(Set([0,1]), 0..1) 57 | @test !issubset(Set([0,2]), 0..1) 58 | 59 | @test uniondomain() == EmptySpace{Any}() 60 | @test uniondomain(0..1) == 0..1 61 | @test uniondomain(0..1, [0,1]) == 0..1 62 | @test uniondomain([0,1], 0..1) == 0..1 63 | 64 | @test uniondomain([0,1], [0.0,1.0]) == [0,1] 65 | @test uniondomain([0,2], [0.0,1.0]) isa UnionDomain 66 | 67 | # larger union expressions 68 | @test uniondomain(0..1, 1..3, Point(0.4), 2..5, FullSpace(), Point(-0.2)) isa FullSpace 69 | @test uniondomain(0..1, 1..3, Point(0.4)) == 0..3 70 | @test uniondomain(0..1, Point(0.4), 1..3) == 0..3 71 | @test uniondomain(Point(0.4), 0..1, 1..3) == 0..3 72 | 73 | # ordering doesn't matter 74 | @test UnionDomain(d1,d2) == UnionDomain(d2,d1) 75 | 76 | @test UnionDomain((d1,d2)) == UnionDomain(d1,d2) 77 | @test UnionDomain(d1) isa UnionDomain 78 | @test UnionDomain(UnionDomain(d1,d2),d3) == UnionDomain(d3,UnionDomain(d1,d2)) 79 | 80 | @test convert(Domain, [0..1,2..3,3..4]) isa UnionDomain{Int} 81 | @test convert(Domain{Float64}, [0..1,2..3,3..4]) isa UnionDomain{Float64} 82 | @test convert(Domain, Set([0..1,2..3,3..4])) isa UnionDomain{Int} 83 | @test convert(Domain{Float64}, Set([0..1,2..3,3..4])) isa UnionDomain{Float64} 84 | @test convert(Domain, Set([1,2,3])) isa UnionDomain{Int} 85 | @test 2 ∈ convert(Domain, Set([1,2,3])) 86 | @test 2 ∈ convert(Domain{Float64}, Set([1,2,3])) 87 | @test 4 ∉ convert(Domain, Set([1,2,3])) 88 | 89 | @test interior(uniondomain(0..1, 2..3)) == uniondomain(OpenInterval(0,1),OpenInterval(2,3)) 90 | @test closure(uniondomain(OpenInterval(0,1),OpenInterval(2,3))) == uniondomain(0..1, 2..3) 91 | 92 | @test uniondomain(0..1, 2..3) \ (0.5..2.5) == uniondomain(0..0.5, 2.5..3.0) 93 | @test uniondomain(0..1, 2..3) \ uniondomain(0.5..2.5, -2..(-1)) == uniondomain(0..0.5, 2.5..3.0) 94 | 95 | @test !isempty(u1) 96 | show(io, textmime, u1) 97 | @test String(take!(io)) == "UnitDisk() ∪ ((-0.9..0.9) × (-0.9..0.9))" 98 | 99 | # repeated union 100 | @test ncomponents(uniondomain(UnitBall{Float64}(), UnitInterval(), UnitInterval())) == 2 101 | @test ncomponents(uniondomain(UnitInterval(), UnitBall{Float64}(), UnitInterval())) == 2 102 | @test ncomponents(uniondomain(UnitInterval(), UnitInterval(), UnitBall{Float64}())) == 2 103 | end 104 | 105 | @testset "intersect" begin 106 | @test IntersectDomain(0..1) == IntersectDomain((0..1)) 107 | @test IntersectDomain{Float64}(0..1) == IntersectDomain(0.0..1.0) 108 | @test IntersectDomain{Float64}(0..1, 1..2) == IntersectDomain((0..1, 1..2)) 109 | @test intersectdomain(0..1, 1..2) == Point(1) 110 | 111 | @test intersectdomain(0..1, 0.5..1.5) == (0..1) & (0.5..1.5) 112 | 113 | # intersection of productdomains 114 | i1 = intersectdomain((-.4..0.4)^2, (-.5 .. 0.5) × (-.1.. 0.1)) 115 | @test i1 == productdomain(-0.4..0.4, -0.1..0.1) 116 | show(io,i1) 117 | @test String(take!(io)) == "(-0.4..0.4) × (-0.1..0.1)" 118 | @test intersectdomain(productdomain(UnitDisk(),-1..1), productdomain(-1..1, UnitDisk())) isa IntersectDomain 119 | i2 = UnitDisk() & (-.4..0.4)^2 120 | show(io, textmime, i2) 121 | @test String(take!(io)) == "UnitDisk() ∩ ((-0.4..0.4) × (-0.4..0.4))" 122 | @test dimension(i1) == 2 123 | @test dimension(i2) == 2 124 | 125 | i3 = ((-.5 .. 0.5) × (-.1.. 0.1)) & i2 126 | i4 = i2 & ((-.5 .. 0.5) × (-.1.. 0.1)) 127 | i5 = i3 & i2 128 | @test SVector(0.,.05) ∈ i3 129 | @test SVector(0.,.05) ∈ i4 130 | @test SVector(0.,.75) ∉ i3 131 | @test SVector(0.,.75) ∉ i4 132 | 133 | d45 = IntersectDomain(0.0..1.5, [1.0,3.0]) 134 | @test d45 isa Domain{Float64} 135 | @test 1.0 ∈ d45 136 | @test 1.1 ∉ d45 137 | @test convert(Domain{BigFloat}, d45) isa Domain{BigFloat} 138 | 139 | @test intersectdomain() == EmptySpace{Any}() 140 | @test intersectdomain(UnitDisk()) == UnitDisk() 141 | 142 | @test (0..1) ∩ [1.5] isa IntersectDomain{Float64} 143 | @test [0.5] ∩ (1..2) isa IntersectDomain{Float64} 144 | 145 | @test intersectdomain(0..1, [0,1]) == [0,1] 146 | @test intersectdomain([0,1], 0..1) == [0,1] 147 | @test intersectdomain([0,1], [0.0,1.0]) == [0,1] 148 | @test intersectdomain([0,2], [0.0,1.0]) isa IntersectDomain 149 | 150 | @test IntersectDomain(UnitDisk(), UnitSquare()) == IntersectDomain(UnitSquare(), UnitDisk()) 151 | 152 | @test intersectdomain(uniondomain(0..1, 2..3), uniondomain(0..0.5, 3.5..4.5)) == 0..0.5 153 | @test intersectdomain(uniondomain(0..1, 2..3), 2..4) == 2..3 154 | @test intersectdomain(2..4, uniondomain(0..1, 2..3)) == 2..3 155 | 156 | # repeated intersection 157 | @test ncomponents(intersectdomain(UnitBall{Float64}(), UnitInterval(), UnitInterval())) == 2 158 | @test ncomponents(intersectdomain(UnitInterval(), UnitBall{Float64}(), UnitInterval())) == 2 159 | @test ncomponents(intersectdomain(UnitInterval(), UnitInterval(), UnitBall{Float64}())) == 2 160 | # larger intersection expressions 161 | @test intersectdomain(0..1, 1..3, Point(0.4), 2..5, FullSpace(), Point(-0.2)) isa EmptySpace 162 | @test intersectdomain(0..1, 1..3, Point(1.0)) == Point(1.0) 163 | @test intersectdomain(0..1, Point(1.0), 1..3) == Point(1.0) 164 | @test intersectdomain(Point(1.0), 0..1, 1..3) == Point(1.0) 165 | 166 | @test boundingbox(UnitSphere() ∩ 2UnitBall()) == (-1..1)^3 167 | end 168 | 169 | @testset "setdiff" begin 170 | d1 = UnitDisk() \ ProductDomain(-.5..0.5, -.1..0.1) 171 | @test dimension(d1) == 2 172 | @test SVector(0.,.74) ∈ d1 173 | @test SVector(0.,.25) ∈ d1 174 | @test SVector(0.,-.05) ∉ d1 175 | @test SVector(0.5, 0.1) ∉ d1 176 | @test SVector(1.01, 0.1) ∉ d1 177 | @test approx_in(SVector(1.01, 0.1), d1, 0.1) 178 | show(io, textmime, d1) 179 | @test String(take!(io)) == "UnitDisk() \\ ((-0.5..0.5) × (-0.1..0.1))" 180 | @test setdiff(d1, ProductDomain(-0.5..0.5, -.1..0.1)) == d1 181 | 182 | d2 = SetdiffDomain(0.0..3.0, [1.0, 2.5]) 183 | @test d2 isa Domain{Float64} 184 | @test d2 isa SetdiffDomain 185 | @test 0.99 ∈ d2 186 | @test_throws MethodError approx_in(3.01, d2, 0.1) 187 | @test 1.0 ∉ d2 188 | @test convert(Domain{BigFloat}, d2) isa Domain{BigFloat} 189 | 190 | @test (0..1) \ [0.5] isa SetdiffDomain{Float64} 191 | d3 = [0,5] \ (0..3) 192 | @test d3 isa SetdiffDomain{Int} 193 | @test 0 ∉ d3 194 | @test 5 ∈ d3 195 | 196 | @test setdiff(0..1, 2..3) == setdiffdomain(0..1, 2..3) 197 | @test setdiff(0..1, 0.5) == setdiffdomain(0..1, 0.5) 198 | @test setdiff(0.5, 0..1) == setdiffdomain(0.5, 0..1) 199 | 200 | @test setdiff(0..1, EmptySpace()) == 0..1 201 | @test setdiff(0..1, 0.0..1.0) == EmptySpace() 202 | 203 | @test (0..1)^2 \ UnitCircle() == UnitInterval()^2 \ UnitCircle() 204 | end 205 | 206 | @testset "arithmetic" begin 207 | d1 = (0..1) 208 | d2 = (2..3) 209 | d = UnionDomain(d1) ∪ UnionDomain(d2) 210 | 211 | @test d .+ 1 == UnionDomain(d1 .+ 1) ∪ (d2 .+ 1) 212 | @test d .- 1 == UnionDomain(d1 .- 1) ∪ (d2 .- 1) 213 | @test 2 * d == UnionDomain(2 * d1) ∪ (2 * d2) 214 | @test d * 2 == UnionDomain(d1 * 2) ∪ (d2 * 2) 215 | @test d / 2 == UnionDomain(d1 / 2) ∪ (d2 / 2) 216 | @test 2 \ d == UnionDomain(2 \ d1) ∪ (2 \ d2) 217 | 218 | @test infimum(d) == minimum(d) == 0 219 | @test supremum(d) == maximum(d) == 3 220 | end 221 | 222 | @testset "different types" begin 223 | d̃1 = (0..1) 224 | d1 = (0f0.. 1f0) 225 | d2 = (2..3) 226 | 227 | @test UnionDomain(d1) ∪ d2 == UnionDomain(d̃1) ∪ d2 228 | end 229 | 230 | @testset "disk × interval" begin 231 | d = (0.0..1) × UnitDisk() 232 | @test SVector(0.1, 0.2, 0.3) ∈ d 233 | 234 | d = UnitDisk() × (0.0..1) 235 | @test SVector(0.1, 0.2, 0.3) ∈ d 236 | end 237 | 238 | @testset "Wrapped domain" begin 239 | d = 0..1.0 240 | w = DomainSets.WrappedDomain(d) 241 | @test w isa Domain 242 | @test 0.5 ∈ w 243 | 244 | d1 = DomainSets.WrappedDomain{Any}([0,5]) 245 | @test 0 ∈ d1 246 | @test 1 ∉ d1 247 | 248 | @test convert(Domain{Float64}, [0.4,0.5]) isa Domain{Float64} 249 | end 250 | 251 | @testset "more set operations" begin 252 | D = UnitInterval()^3 253 | S = 2 * UnitBall() 254 | @testset "joint domain" begin 255 | DS = D ∪ S 256 | @test SA[0.0, 0.6, 0.0] ∈ DS 257 | @test SA[0.9, 0.6,-2.5] ∉ DS 258 | end 259 | 260 | @testset "domain intersection" begin 261 | DS = D ∩ S 262 | @test SA[0.1, 0.1, 0.1] ∈ DS 263 | @test SA[0.1, -0.1, 0.1] ∉ DS 264 | end 265 | @testset "domain difference" begin 266 | DS = D\S 267 | @test SA[0.1, 0.1, 0.1] ∉ DS 268 | 269 | D1 = 2 * D 270 | D2 = D * 2 271 | D3 = D / 2 272 | 273 | @test SA[2., 2., 2.] ∈ D1 274 | @test SA[0.9, 0.6,-2.5] ∉ D1 275 | @test SA[2., 2., 2.] ∈ D2 276 | @test SA[0.9, 0.6,-2.5] ∉ D2 277 | @test SA[.5, .4, .45] ∈ D3 278 | @test SA[.3, 0.6,-.2] ∉ D3 279 | end 280 | end 281 | end 282 | -------------------------------------------------------------------------------- /src/domains/cube.jl: -------------------------------------------------------------------------------- 1 | 2 | "A `HyperRectangle` is the cartesian product of intervals." 3 | abstract type HyperRectangle{T} <: ProductDomain{T} end 4 | 5 | convert(::Type{HyperRectangle}, d::ProductDomain) = 6 | ProductDomain(map(t->convert(AbstractInterval, t), components(d))) 7 | 8 | boundingbox(d::HyperRectangle) = d 9 | 10 | "Compute all corners of the hyperrectangle." 11 | function corners(d::HyperRectangle) 12 | left = leftendpoint(d) 13 | right = rightendpoint(d) 14 | N = length(left) 15 | corners = [zeros(numtype(d),dimension(d)) for i in 1:2^N] 16 | # All possible permutations of the corners 17 | for i=1:2^length(left) 18 | for j=1:N 19 | corners[i][j] = ((i>>(j-1))%2==0) ? left[j] : right[j] 20 | end 21 | end 22 | [convert(eltype(d), p) for p in corners] 23 | end 24 | 25 | # map the interval [a,b] to a cube defined by c (bottom-left) and d (top-right) 26 | cube_face_map(a::Number, b::Number, c::SVector{2}, d::SVector{2}) = AffineMap((d-c)/(b-a), c - (d-c)/(b-a)*a) 27 | function cube_face_map(a::Number, b::Number, c::Vector, d::Vector) 28 | @assert length(c) == length(d) == 2 29 | AffineMap((d-c)/(b-a), c - (d-c)/(b-a)*a) 30 | end 31 | 32 | # map the cube defined by a and b, to the cube defined by c and d, 33 | # where the dimension of c and d is one larger, and one of the coordinates (dim) is fixed to dimval. 34 | function cube_face_map(a::SVector{M}, b::SVector{M}, c::SVector{N}, d::SVector{N}, dim, dimval) where {N,M} 35 | @assert N == M+1 36 | T = promote_type(eltype(a),eltype(c)) 37 | A = MMatrix{N,M,T}(undef) 38 | B = MVector{N,T}(undef) 39 | fill!(A, 0) 40 | fill!(B, 0) 41 | B[dim] = dimval 42 | for m = 1:dim-1 43 | # scalar map along the dimension "m" 44 | mapdim = interval_map(a[m], b[m], c[m], d[m]) 45 | A[m,m] = mapdim.A 46 | B[m] = mapdim.b 47 | end 48 | for m = dim+1:N 49 | mapdim = interval_map(a[m-1], b[m-1], c[m], d[m]) 50 | A[m,m-1] = mapdim.A 51 | B[m] = mapdim.b 52 | end 53 | AffineMap(SMatrix{N,M}(A), SVector{N}(B)) 54 | end 55 | 56 | function cube_face_map(a::Vector, b::Vector, c::Vector, d::Vector, dim, dimval) 57 | M = length(a) 58 | N = length(c) 59 | @assert length(a)==length(b) 60 | @assert length(c)==length(d) 61 | @assert N == M+1 62 | T = promote_type(eltype(a),eltype(c)) 63 | A = Matrix{T}(undef, N, M) 64 | B = Vector{T}(undef, N) 65 | fill!(A, 0) 66 | fill!(B, 0) 67 | B[dim] = dimval 68 | for m = 1:dim-1 69 | # scalar map along the dimension "m" 70 | mapdim = interval_map(a[m], b[m], c[m], d[m]) 71 | A[m,m] = mapdim.A 72 | B[m] = mapdim.b 73 | end 74 | for m = dim+1:N 75 | mapdim = interval_map(a[m-1], b[m-1], c[m], d[m]) 76 | A[m,m-1] = mapdim.A 77 | B[m] = mapdim.b 78 | end 79 | AffineMap(A, B) 80 | end 81 | 82 | # The boundary of a rectangle is a collection of mapped lower-dimensional rectangles. 83 | # Dimension 2 is a special case, because the lower dimension is 1 where we use 84 | # scalars instead of vectors 85 | function boundary(d::HyperRectangle{SVector{2,T}}) where {T} 86 | left = leftendpoint(d) 87 | right = rightendpoint(d) 88 | x1 = left[1]; y1 = left[2]; x2 = right[1]; y2 = right[2] 89 | d_unit = UnitInterval{T}() 90 | maps = [ 91 | cube_face_map(zero(T), one(T), SVector(x1,y1), SVector(x2,y1)), 92 | cube_face_map(zero(T), one(T), SVector(x2,y1), SVector(x2,y2)), 93 | cube_face_map(zero(T), one(T), SVector(x2,y2), SVector(x1,y2)), 94 | cube_face_map(zero(T), one(T), SVector(x1,y2), SVector(x1,y1)) 95 | ] 96 | faces = map(m -> ParametricDomain(m, d_unit), maps) 97 | UnionDomain(faces) 98 | end 99 | 100 | function boundary(d::HyperRectangle{SVector{N,T}}) where {N,T} 101 | left2 = leftendpoint(d) 102 | right2 = rightendpoint(d) 103 | d_unit = UnitCube{SVector{N-1,T}}() 104 | left1 = leftendpoint(d_unit) 105 | right1 = rightendpoint(d_unit) 106 | 107 | map1 = cube_face_map(left1, right1, left2, right2, 1, left2[1]) 108 | MAP = typeof(map1) 109 | maps = MAP[] 110 | for dim in 1:N 111 | push!(maps, cube_face_map(left1, right1, left2, right2, dim, left2[dim])) 112 | push!(maps, cube_face_map(left1, right1, left2, right2, dim, right2[dim])) 113 | end 114 | faces = map(m -> ParametricDomain(m, d_unit), maps) 115 | UnionDomain(faces) 116 | end 117 | 118 | function boundary(d::HyperRectangle{Vector{T}}) where {T} 119 | if dimension(d) == 2 120 | left = leftendpoint(d) 121 | right = rightendpoint(d) 122 | x1 = left[1]; y1 = left[2]; x2 = right[1]; y2 = right[2] 123 | d_unit = UnitInterval{T}() 124 | maps = [ 125 | cube_face_map(zero(T), one(T), [x1,y1], [x2,y1]), 126 | cube_face_map(zero(T), one(T), [x2,y1], [x2,y2]), 127 | cube_face_map(zero(T), one(T), [x2,y2], [x1,y2]), 128 | cube_face_map(zero(T), one(T), [x1,y2], [x1,y1]) 129 | ] 130 | else 131 | left2 = leftendpoint(d) 132 | right2 = rightendpoint(d) 133 | d_unit = UnitCube(dimension(d)-1) 134 | left1 = leftendpoint(d_unit) 135 | right1 = rightendpoint(d_unit) 136 | 137 | map1 = cube_face_map(left1, right1, left2, right2, 1, left2[1]) 138 | MAP = typeof(map1) 139 | maps = MAP[] 140 | for dim in 1:dimension(d) 141 | push!(maps, cube_face_map(left1, right1, left2, right2, dim, left2[dim])) 142 | push!(maps, cube_face_map(left1, right1, left2, right2, dim, right2[dim])) 143 | end 144 | end 145 | faces = map(m -> ParametricDomain(m, d_unit), maps) 146 | UnionDomain(faces) 147 | end 148 | 149 | 150 | "A `Cube` is a hyperrectangle with equal side lengths in each dimension." 151 | abstract type Cube{T} <: HyperRectangle{T} end 152 | 153 | "A cube in a fixed N-dimensional Euclidean space." 154 | const EuclideanCube{N,T} = Cube{SVector{N,T}} 155 | 156 | "A cube with vector elements of variable length." 157 | const VectorCube{T} = Cube{Vector{T}} 158 | 159 | # This will cause a warning if vectors of different length are used with cubes 160 | iscompatiblepair(x::AbstractVector, d::VectorCube) = length(x) == dimension(d) 161 | 162 | 163 | "The unit cube is the domain `[0,1]^d`." 164 | abstract type UnitCube{T} <: Cube{T} end 165 | 166 | component(d::UnitCube, i::Int) = 167 | (1 <= i <= dimension(d) || throw(BoundsError); UnitInterval{numtype(d)}()) 168 | 169 | volume(d::UnitCube) = 1 170 | 171 | 172 | "A unit cube that is specified by the element type `T`." 173 | struct StaticUnitCube{T} <: UnitCube{T} 174 | end 175 | 176 | StaticUnitCube() = StaticUnitCube{SVector{3,Float64}}() 177 | StaticUnitCube(::Val{N}) where {N} = StaticUnitCube{SVector{N,Float64}}() 178 | 179 | StaticUnitCube{T}(n::Int) where {T} = 180 | (@assert n == euclideandimension(T); StaticUnitCube{T}()) 181 | StaticUnitCube{T}(::Val{N}) where {N,T} = 182 | (@assert N == euclideandimension(T); StaticUnitCube{T}()) 183 | 184 | similardomain(d::StaticUnitCube, ::Type{T}) where {T<:StaticTypes} = 185 | StaticUnitCube{T}() 186 | similardomain(d::StaticUnitCube, ::Type{T}) where {T} = 187 | DynamicUnitCube{T}(dimension(d)) 188 | 189 | components(d::StaticUnitCube{SVector{N,T}}) where {N,T} = 190 | ntuple(x->UnitInterval{T}(), Val(N)) 191 | 192 | "The unit cube in a fixed N-dimensional space." 193 | const EuclideanUnitCube{N,T} = StaticUnitCube{SVector{N,T}} 194 | 195 | EuclideanUnitCube{N}() where {N} = EuclideanUnitCube{N,Float64}() 196 | 197 | const UnitSquare{T} = EuclideanUnitCube{2,T} 198 | 199 | 200 | "A unit cube whose dimension is specified by a field." 201 | struct DynamicUnitCube{T} <: UnitCube{T} 202 | dimension :: Int 203 | 204 | DynamicUnitCube{T}(n::Int) where {T} = new(n) 205 | DynamicUnitCube{T}(n::Int) where {T<:StaticTypes} = 206 | (@assert n == euclideandimension(T); new(n)) 207 | end 208 | 209 | DynamicUnitCube(n::Int) = DynamicUnitCube{Vector{Float64}}(n) 210 | 211 | dimension(d::DynamicUnitCube) = d.dimension 212 | 213 | components(d::DynamicUnitCube) = map(x->UnitInterval{numtype(d)}(), 1:dimension(d)) 214 | 215 | similardomain(d::DynamicUnitCube, ::Type{T}) where {T} = 216 | DynamicUnitCube{T}(d.dimension) 217 | similardomain(d::DynamicUnitCube, ::Type{T}) where {T <: StaticTypes} = 218 | StaticUnitCube{T}() 219 | 220 | "The unit cube with vector elements of a given dimension." 221 | const VectorUnitCube{T} = DynamicUnitCube{Vector{T}} 222 | 223 | VectorUnitCube(n::Int = 3) = VectorUnitCube{Float64}(n) 224 | VectorUnitSquare() = VectorUnitCube(2) 225 | 226 | 227 | UnitCube(n::Int) = DynamicUnitCube(n) 228 | UnitCube(::Val{N} = Val(3)) where {N} = EuclideanUnitCube{N}() 229 | 230 | UnitCube{T}(n::Int) where {T <: StaticTypes} = StaticUnitCube{T}(n) 231 | UnitCube{T}(::Val{N}) where {N,T} = StaticUnitCube{T}(Val(N)) 232 | UnitCube{T}() where {T <: StaticTypes} = StaticUnitCube{T}() 233 | UnitCube{T}(n::Int) where {T} = DynamicUnitCube{T}(n) 234 | 235 | UnitCube(domains::UnitInterval...) = UnitCube(domains) 236 | UnitCube(domains::NTuple{N,UnitInterval{T}}) where {N,T} = 237 | UnitCube{SVector{N,T}}(domains) 238 | UnitCube(domains::SVector{N,UnitInterval{T}}) where {N,T} = 239 | UnitCube{SVector{N,T}}(domains) 240 | 241 | UnitCube{T}(domains::UnitInterval...) where {T} = UnitCube{T}(domains) 242 | UnitCube{T}(domain::NTuple{N,<:UnitInterval}) where {N,T<:SVector{N}} = 243 | StaticUnitCube{T}() 244 | UnitCube{T}(domain::SVector{N,<:UnitInterval}) where {N,T<:SVector{N}} = 245 | StaticUnitCube{T}() 246 | 247 | # Constructor: careful about ambiguities with FixedInterval arguments below 248 | ProductDomain(domains::UnitInterval...) = UnitCube(domains...) 249 | ProductDomain(domains::NTuple{N,<:UnitInterval}) where {N} = UnitCube(domains) 250 | ProductDomain(domains::SVector{N,<:UnitInterval}) where {N} = UnitCube(domains) 251 | ProductDomain(domains::AbstractVector{<:UnitInterval{T}}) where {T} = 252 | VectorUnitCube{T}(length(domains)) 253 | ProductDomain{T}(domains::UnitInterval...) where {N,S,T<:SVector{N,S}} = 254 | UnitCube{T}(domains...) 255 | ProductDomain{T}(domains::NTuple{N,<:UnitInterval}) where {N,S,T<:SVector{N,S}} = 256 | UnitCube{T}(domains) 257 | ProductDomain{T}(domains::SVector{N,<:UnitInterval}) where {N,S,T<:SVector{N,S}} = 258 | UnitCube{T}(domains) 259 | ProductDomain{T}(domains::AbstractVector{<:UnitInterval}) where {S,T<:Vector{S}} = 260 | VectorUnitCube{S}(length(domains)) 261 | 262 | ## Display: 263 | show(io::IO, d::EuclideanUnitCube{3,Float64}) = print(io, "UnitCube()") 264 | show(io::IO, d::EuclideanUnitCube{N,Float64}) where {N} = print(io, "UnitCube(Val($(N)))") 265 | show(io::IO, d::UnitSquare{Float64}) = print(io, "UnitSquare()") 266 | show(io::IO, d::UnitSquare{T}) where {T} = print(io, "UnitSquare{$(T)}()") 267 | show(io::IO, d::VectorUnitCube{Float64}) = print(io, "UnitCube($(dimension(d)))") 268 | 269 | # set the display stencils to [] to opt-out of composite display for the types above 270 | Display.displaystencil(d::EuclideanUnitCube{N,Float64}) where {N} = [] 271 | Display.displaystencil(d::UnitSquare) = [] 272 | Display.displaystencil(d::VectorUnitCube{Float64}) = [] 273 | Display.object_parentheses(d::EuclideanUnitCube{N,Float64}) where {N} = false 274 | Display.object_parentheses(d::UnitSquare) = false 275 | Display.object_parentheses(d::VectorUnitCube{Float64}) = false 276 | 277 | 278 | 279 | "An N-dimensional rectangle is the cartesian product of closed intervals." 280 | struct Rectangle{T} <: HyperRectangle{T} 281 | a :: T 282 | b :: T 283 | 284 | function Rectangle{T}(a::S,b::S) where {S,T} 285 | @assert length(a)==length(b) 286 | new(a,b) 287 | end 288 | end 289 | 290 | dimension(d::Rectangle) = length(d.a) 291 | component(d::Rectangle, i::Int) = ClosedInterval(d.a[i],d.b[i]) 292 | components(d::Rectangle) = map(ClosedInterval, d.a, d.b) 293 | 294 | Rectangle(a, b) = Rectangle(promote(a,b)...) 295 | Rectangle(a::T, b::T) where {T} = Rectangle{T}(a, b) 296 | Rectangle(a::NTuple{N,T}, b::NTuple{N,T}) where {N,T} = 297 | Rectangle(SVector{N,T}(a), SVector{N,T}(b)) 298 | Rectangle(a::T, b::T) where {T<:Number} = 299 | error("Rectangles have to be constructed from vectors or tuples, not numbers.") 300 | 301 | Rectangle(domains::Tuple) = Rectangle(domains...) 302 | Rectangle(domains::ClosedInterval...) = Rectangle(promote_domains(domains)...) 303 | Rectangle(domains::ClosedInterval{T}...) where {T} = 304 | Rectangle(map(leftendpoint, domains), map(rightendpoint, domains)) 305 | Rectangle(domains::AbstractVector{<:ClosedInterval}) = 306 | Rectangle(map(leftendpoint, domains), map(rightendpoint, domains)) 307 | Rectangle(domains::Domain...) = 308 | error("The Rectangle constructor expects two points or a list of intervals (closed).") 309 | 310 | Rectangle{T}(domains::Tuple) where {T} = Rectangle{T}(domains...) 311 | Rectangle{T}(domains::ClosedInterval...) where {T} = 312 | Rectangle{T}(map(leftendpoint, domains), map(rightendpoint, domains)) 313 | Rectangle{T}(domains::AbstractVector{<:ClosedInterval}) where {T} = 314 | Rectangle{T}(map(leftendpoint, domains), map(rightendpoint, domains)) 315 | Rectangle{T}(domains::Domain...) where {T} = 316 | error("The Rectangle constructor expects two points or a list of intervals (closed).") 317 | 318 | ProductDomain(domains::ClosedInterval{<:Number}...) = Rectangle(domains...) 319 | ProductDomain(domains::AbstractVector{<:ClosedInterval{<:Number}}) = 320 | Rectangle(map(leftendpoint, domains), map(rightendpoint, domains)) 321 | ProductDomain{T}(domains::ClosedInterval...) where {T} = Rectangle{T}(domains...) 322 | ProductDomain{T}(domains::AbstractVector{<:ClosedInterval}) where {T} = 323 | Rectangle{T}(map(leftendpoint, domains), map(rightendpoint, domains)) 324 | 325 | 326 | 327 | "The N-fold cartesian product of a fixed interval." 328 | struct FixedIntervalProduct{N,T,D} <: Cube{SVector{N,T}} 329 | end 330 | 331 | component(d::FixedIntervalProduct{N,T,D}, i::Int) where {N,T,D} = 332 | (1 <= i <= dimension(d) || throw(BoundsError); D()) 333 | components(d::FixedIntervalProduct{N,T,D}) where {N,T,D} = 334 | ntuple(x->D(), Val(N)) 335 | 336 | volume(d::FixedIntervalProduct{N,T,D}) where {N,T,D} = volume(D())^N 337 | 338 | FixedIntervalProduct(domains::NTuple{N,D}) where {N,D <: FixedInterval} = 339 | FixedIntervalProduct{N,eltype(D),D}() 340 | FixedIntervalProduct(domains::SVector{N,D}) where {N,D <: FixedInterval} = 341 | FixedIntervalProduct{N,eltype(D),D}() 342 | 343 | const ChebyshevProductDomain{N,T} = FixedIntervalProduct{N,T,ChebyshevInterval{T}} 344 | ChebyshevProductDomain(::Val{N}) where {N} = ChebyshevProductDomain{N}() 345 | ChebyshevProductDomain{N}() where {N} = ChebyshevProductDomain{N,Float64}() 346 | 347 | ProductDomain(domains::D...) where {D <: FixedInterval} = 348 | FixedIntervalProduct(domains) 349 | ProductDomain(domains::NTuple{N,D}) where {N,D <: FixedInterval} = 350 | FixedIntervalProduct(domains) 351 | ProductDomain(domains::SVector{N,<:FixedInterval}) where {N} = 352 | FixedIntervalProduct(domains) 353 | ProductDomain{T}(domains::D...) where {N,S,T<:SVector{N,S},D <: FixedInterval} = 354 | FixedIntervalProduct(convert.(Ref(Domain{S}), domains)) 355 | ProductDomain{T}(domains::NTuple{N,D}) where {N,S,T<:SVector{N,S},D <: FixedInterval} = 356 | FixedIntervalProduct(convert.(Ref(Domain{S}), domains)) 357 | ProductDomain{T}(domains::SVector{N,<:FixedInterval}) where {N,T<:SVector{N}} = 358 | FixedIntervalProduct(domains) 359 | -------------------------------------------------------------------------------- /src/generic/setoperations.jl: -------------------------------------------------------------------------------- 1 | # The union, intersection and difference of domains are represented with lazy domains. 2 | 3 | issubset(d1::Domain, d2::Domain) = promotable_domains(d1, d2) && issubset1(promote_domains(d1, d2)...) 4 | issubset(d1, d2::Domain) = issubset1(promote_domains(d1, d2)...) 5 | issubset1(d1, d2) = issubset2(d1, d2) 6 | issubset2(d1, d2) = d1 == d2 7 | # this last fallback is only an approximation of the truth: if d1 equals d2, then 8 | # d1 is a subset of d2, but the reverse is not true. So we might be returning false 9 | # even when the correct mathematical answer is true. 10 | # What `issubset` means for the optimizations below is: 11 | # - if true: we are sure that d1 is a subset of d2 12 | # - if false: either it really is false, or we don't really know 13 | # On top of that, we only invoke issubset(d1,d2) when the arguments are both 14 | # Domains, or when d2 is a Domain, anticipating that issubset(d1::Domain,d2) 15 | # is often harder to implement. 16 | 17 | issubset(d1::AbstractArray, d2::Domain) = all(in.(d1, Ref(d2))) 18 | issubset(d1::AbstractSet, d2::Domain) = all(in.(d1, Ref(d2))) 19 | 20 | ############################ 21 | # The union of two domains 22 | ############################ 23 | 24 | """ 25 | ```using DomainSets``` 26 | 27 | A `UnionDomain` represents the union of a set of domains. 28 | """ 29 | struct UnionDomain{T,DD} <: CompositeDomain{T} 30 | domains :: DD 31 | end 32 | 33 | """ 34 | The `UnionDomain` and `UnionDomain{T}` constructors can be invoked in three ways: 35 | - with a list of arguments: UnionDomain(d1, d2, ...) 36 | - with a single domain: UnionDomain(d::Domain) 37 | - or with any iterable list of domains: UnionDomain(domains) 38 | """ 39 | UnionDomain(domains...) = UnionDomain(domains) 40 | UnionDomain(d::Domain) = UnionDomain((d,)) 41 | UnionDomain(domains) = _UnionDomain(promote_domains(domains)) 42 | _UnionDomain(domains) = _UnionDomain(eltype(first(domains)), domains) 43 | 44 | UnionDomain{T}(domains...) where {T} = UnionDomain{T}(domains) 45 | UnionDomain{T}(d::Domain) where {T} = UnionDomain{T}((d,)) 46 | UnionDomain{T}(domains) where {T} = _UnionDomain(T, convert_eltype.(T, domains)) 47 | _UnionDomain(::Type{T}, domains) where {T} = UnionDomain{T,typeof(domains)}(domains) 48 | 49 | # The union of domains corresponds to a logical OR of their characteristic functions 50 | composition(d::UnionDomain) = Combination() 51 | combine(d::UnionDomain, results) = reduce(|, results) 52 | 53 | # Make d1 ∪ d2 invoke `uniondomain` if one of the first two arguments is a Domain 54 | union(d1::Domain, d2::Domain, domains...) = uniondomain(d1, d2, domains...) 55 | union(d1::Domain, d2, domains...) = uniondomain(d1, d2, domains...) 56 | union(d1, d2::Domain, domains...) = uniondomain(d1, d2, domains...) 57 | 58 | uniondomain() = EmptySpace{Any}() 59 | uniondomain(d1) = d1 60 | uniondomain(d1, d2) = uniondomain1(promote_domains(d1, d2)...) 61 | 62 | # simplification: in case the domains are equal, we don't create a union 63 | uniondomain1(d1, d2) = d1 == d2 ? d1 : uniondomain2(d1, d2) 64 | # if d1 is a Domain, we go one step further and invoke `issubset` 65 | uniondomain1(d1::Domain, d2) = issubset(d2, d1) ? d1 : uniondomain2(d1, d2) 66 | uniondomain2(d1, d2) = UnionDomain(d1, d2) 67 | uniondomain2(d1, d2::Domain) = issubset(d1, d2) ? d2 : UnionDomain(d1, d2) 68 | 69 | uniondomain(d1, d2, d3) = _ud3(promote_domains(d1, d2, d3)...) 70 | _ud3(d1, d2, d3) = 71 | _ud3(d1, d2, d3, uniondomain(d1, d2), uniondomain(d2, d3), uniondomain(d1, d3)) 72 | _ud3(d1, d2, d3, d12::UnionDomain, d23::UnionDomain, d13::UnionDomain) = 73 | uniondomain(d12, d3) 74 | _ud3(d1, d2, d3, d12, d23::UnionDomain, d13::UnionDomain) = 75 | uniondomain(d12, d3) 76 | _ud3(d1, d2, d3, d12::UnionDomain, d23, d13::UnionDomain) = 77 | uniondomain(d23, d1) 78 | _ud3(d1, d2, d3, d12::UnionDomain, d23::UnionDomain, d13) = 79 | uniondomain(d13, d2) 80 | _ud3(d1, d2, d3, d12::UnionDomain, d23, d13) = 81 | uniondomain(d23, d1) 82 | _ud3(d1, d2, d3, d12, d23::UnionDomain, d13) = 83 | uniondomain(d12, d3) 84 | _ud3(d1, d2, d3, d12, d23, d13::UnionDomain) = 85 | uniondomain(d12, d3) 86 | _ud3(d1, d2, d3, d12, d23, d13) = 87 | uniondomain(d12, d3) 88 | 89 | uniondomain(d1, d2, d3, domains...) = _ud(promote_domains(d1, d2, d3, domains...)...) 90 | _ud(d1, d2, d3, domains...) = uniondomain(uniondomain(d1, d2, d3), domains...) 91 | 92 | # avoid nested union domains 93 | uniondomain(d1::UnionDomain, d2::UnionDomain) = 94 | d1 == d2 ? d1 : UnionDomain(collect(Set(components(d1)) ∪ Set(components(d2)))) 95 | uniondomain1(d1::UnionDomain, d2) = UnionDomain(components(d1)..., d2) 96 | uniondomain2(d1, d2::UnionDomain) = UnionDomain(d1, components(d2)...) 97 | 98 | ==(a::UnionDomain, b::UnionDomain) = Set(components(a)) == Set(components(b)) 99 | hash(d::UnionDomain, h::UInt) = hashrec("UnionDomain", Set(d.domains), h) 100 | 101 | 102 | convert(::Type{Domain}, v::AbstractVector{<:Domain}) = UnionDomain(v) 103 | convert(::Type{Domain}, v::AbstractSet{<:Domain}) = UnionDomain(v) 104 | convert(::Type{Domain}, s::AbstractSet) = UnionDomain(map(Point,collect(s))) 105 | convert(::Type{Domain{T}}, v::AbstractVector{<:Domain}) where {T} = UnionDomain{T}(v) 106 | convert(::Type{Domain{T}}, v::AbstractSet{<:Domain}) where {T} = UnionDomain{T}(v) 107 | convert(::Type{Domain{T}}, s::AbstractSet) where {T} = UnionDomain{T}(map(Point,collect(s))) 108 | 109 | similardomain(d::UnionDomain, ::Type{T}) where {T} = 110 | UnionDomain(convert.(Domain{T}, components(d))) 111 | 112 | 113 | 114 | point_in_domain(d::UnionDomain) = convert(eltype(d), point_in_domain(component(d,1))) 115 | 116 | isempty(d::UnionDomain) = all(isempty, d.domains) 117 | 118 | interior(d::UnionDomain) = UnionDomain(map(interior, components(d))) 119 | closure(d::UnionDomain) = UnionDomain(map(closure, components(d))) 120 | 121 | boundingbox(d::UnionDomain) = unionbox(map(boundingbox, components(d))...) 122 | 123 | boundary(d::UnionDomain) = uniondomain(map(boundary, components(d))...) 124 | 125 | Display.combinationsymbol(d::UnionDomain) = Display.Symbol('∪') 126 | Display.displaystencil(d::UnionDomain) = composite_displaystencil(d) 127 | show(io::IO, mime::MIME"text/plain", d::UnionDomain) = Display.composite_show(io, mime, d) 128 | show(io::IO, d::UnionDomain) = Display.composite_show_compact(io, d) 129 | 130 | ## ualgebra 131 | 132 | # preserve the uniondomain when mapping 133 | map_domain(map, domain::UnionDomain) = UnionDomain(map_domain.(Ref(map), components(domain))) 134 | mapped_domain(map, domain::UnionDomain) = UnionDomain(mapped_domain.(Ref(map), components(domain))) 135 | 136 | 137 | # TODO: what is the correct semantics for these functions? Should we have them? 138 | for (op, mop) in ((:minimum, :min), (:maximum, :max), (:infimum, :min), (:supremum, :max)) 139 | @eval $op(d::UnionDomain) = mapreduce($op, $mop, components(d)) 140 | end 141 | 142 | 143 | setdiffdomain(d1::UnionDomain, d2::UnionDomain) = 144 | UnionDomain(setdiffdomain.(components(d1), Ref(d2))) 145 | 146 | function setdiffdomain1(d1::UnionDomain, d2) 147 | s = Set(components(d1)) 148 | # check if any element is in d1 and just remove 149 | s2 = Set(setdiff(s, tuple(d2))) 150 | s2 ≠ s && return UnionDomain(s2) 151 | UnionDomain(setdiffdomain.(components(d1), Ref(d2))) 152 | end 153 | 154 | function setdiffdomain2(d1, d2::UnionDomain) 155 | result = d1 156 | for d in components(d2) 157 | result = setdiffdomain(result, d) 158 | end 159 | result 160 | end 161 | 162 | 163 | ############################## 164 | # The intersection of domains 165 | ############################## 166 | 167 | 168 | """ 169 | An `IntersectDomain` represents the intersection of a set of domains. 170 | """ 171 | struct IntersectDomain{T,DD} <: CompositeDomain{T} 172 | domains :: DD 173 | end 174 | 175 | """ 176 | The `IntersectDomain` constructor can be invoked in one of three ways: 177 | - with a list of arguments: IntersectDomain(d1, d2, ...) 178 | - with a single domain: IntersectDomain(d::Domain) 179 | - or with any iterable list of domains: IntersectDomain(domains) 180 | """ 181 | IntersectDomain(domains...) = IntersectDomain(domains) 182 | IntersectDomain(d::Domain) = IntersectDomain((d,)) 183 | IntersectDomain(domains) = _IntersectDomain(promote_domains(domains)) 184 | _IntersectDomain(domains) = IntersectDomain{eltype(first(domains))}(domains) 185 | 186 | IntersectDomain{T}(domains...) where {T} = IntersectDomain{T}(domains) 187 | IntersectDomain{T}(d::Domain) where {T} = IntersectDomain{T}((d,)) 188 | IntersectDomain{T}(domains) where {T} = _IntersectDomain(T, convert_eltype.(T, domains)) 189 | _IntersectDomain(::Type{T}, domains) where {T} = IntersectDomain{T,typeof(domains)}(domains) 190 | 191 | # The intersection of domains corresponds to a logical AND of their characteristic functions 192 | composition(d::IntersectDomain) = Combination() 193 | combine(d::IntersectDomain, results) = reduce(&, results) 194 | 195 | 196 | # Make d1 ∩ d2 invoke `intersectdomain` if one of the first two arguments is a Domain 197 | intersect(d1::Domain, d2::Domain, domains...) = intersectdomain(d1, d2, domains...) 198 | intersect(d1::Domain, d2, domains...) = intersectdomain(d1, d2, domains...) 199 | intersect(d1, d2::Domain, domains...) = intersectdomain(d1, d2, domains...) 200 | 201 | intersectdomain() = EmptySpace{Any}() 202 | intersectdomain(d1) = d1 203 | intersectdomain(d1, d2) = intersectdomain1(promote_domains(d1, d2)...) 204 | 205 | intersectdomain1(d1, d2) = d1 == d2 ? d1 : intersectdomain2(d1, d2) 206 | intersectdomain1(d1::Domain, d2) = issubset(d2, d1) ? d2 : intersectdomain2(d1, d2) 207 | intersectdomain2(d1, d2) = IntersectDomain(d1, d2) 208 | intersectdomain2(d1, d2::Domain) = issubset(d1, d2) ? d1 : IntersectDomain(d1, d2) 209 | 210 | intersectdomain(d1, d2, d3) = _id3(promote_domains(d1, d2, d3)...) 211 | _id3(d1, d2, d3) = 212 | _id3(d1, d2, d3, intersectdomain(d1, d2), intersectdomain(d2, d3), intersectdomain(d1, d3)) 213 | _id3(d1, d2, d3, d12::IntersectDomain, d23::IntersectDomain, d13::IntersectDomain) = 214 | intersectdomain(d12, d3) 215 | _id3(d1, d2, d3, d12, d23::IntersectDomain, d13::IntersectDomain) = 216 | intersectdomain(d12, d3) 217 | _id3(d1, d2, d3, d12::IntersectDomain, d23, d13::IntersectDomain) = 218 | intersectdomain(d23, d1) 219 | _id3(d1, d2, d3, d12::IntersectDomain, d23::IntersectDomain, d13) = 220 | intersectdomain(d13, d2) 221 | _id3(d1, d2, d3, d12::IntersectDomain, d23, d13) = 222 | intersectdomain(d23, d1) 223 | _id3(d1, d2, d3, d12, d23::IntersectDomain, d13) = 224 | intersectdomain(d12, d3) 225 | _id3(d1, d2, d3, d12, d23, d13::IntersectDomain) = 226 | intersectdomain(d12, d3) 227 | _id3(d1, d2, d3, d12, d23, d13) = 228 | intersectdomain(d12, d3) 229 | 230 | intersectdomain(d1, d2, d3, domains...) = _id(promote_domains(d1, d2, d3, domains...)...) 231 | _id(d1, d2, d3, domains...) = intersectdomain(intersectdomain(d1, d2, d3), domains...) 232 | 233 | # avoid nested intersect domains 234 | intersectdomain(d1::IntersectDomain, d2::IntersectDomain) = 235 | d1 == d2 ? d1 : IntersectDomain(components(d1)..., components(d2)...) 236 | intersectdomain1(d1::IntersectDomain, d2) = IntersectDomain(components(d1)..., d2) 237 | intersectdomain2(d1, d2::IntersectDomain) = IntersectDomain(d1, components(d2)...) 238 | 239 | 240 | function intersectdomain(d1::UnionDomain, d2::UnionDomain) 241 | d1 == d2 && return d1 242 | uniondomain(intersectdomain.(Ref(d1), components(d2))...) 243 | end 244 | intersectdomain(d1::UnionDomain, d2::Domain) = uniondomain(intersectdomain.(d1.domains, Ref(d2))...) 245 | intersectdomain(d1::Domain, d2::UnionDomain) = uniondomain(intersectdomain.(Ref(d1), d2.domains)...) 246 | 247 | (&)(d1::Domain, d2::Domain) = intersectdomain(d1,d2) 248 | 249 | function intersectdomain(d1::ProductDomain, d2::ProductDomain) 250 | if compatibleproductdims(d1, d2) 251 | ProductDomain(map(intersectdomain, components(d1), components(d2))) 252 | else 253 | IntersectDomain(d1, d2) 254 | end 255 | end 256 | 257 | similardomain(d::IntersectDomain, ::Type{T}) where {T} = 258 | IntersectDomain(convert.(Domain{T}, components(d))) 259 | 260 | ==(a::IntersectDomain, b::IntersectDomain) = Set(components(a)) == Set(components(b)) 261 | hash(d::IntersectDomain, h::UInt) = hashrec("IntersectDomain", Set(components(d)), h) 262 | 263 | boundingbox(d::IntersectDomain) = intersectbox(map(boundingbox, components(d))...) 264 | 265 | Display.combinationsymbol(d::IntersectDomain) = Display.Symbol('∩') 266 | Display.displaystencil(d::IntersectDomain) = composite_displaystencil(d) 267 | show(io::IO, mime::MIME"text/plain", d::IntersectDomain) = Display.composite_show(io, mime, d) 268 | show(io::IO, d::IntersectDomain) = Display.composite_show_compact(io, d) 269 | 270 | 271 | ######################################### 272 | ### The difference between two domains 273 | ######################################### 274 | 275 | 276 | "A `SetdiffDomain` represents the difference between two domains." 277 | struct SetdiffDomain{T,DD} <: CompositeDomain{T} 278 | domains :: DD 279 | function SetdiffDomain{T,DD}(domains::DD) where {T,DD} 280 | @assert length(domains) == 2 281 | new(domains) 282 | end 283 | end 284 | 285 | SetdiffDomain(d1, d2) = _SetdiffDomain(promote_domains((d1, d2))...) 286 | _SetdiffDomain(d1, d2) = SetdiffDomain{eltype(d1)}((d1,d2)) 287 | SetdiffDomain{T}(domains) where {T} = SetdiffDomain{T,typeof(domains)}(domains) 288 | 289 | # The difference between two domains corresponds to a logical AND NOT of their characteristic functions 290 | composition(d::SetdiffDomain) = Combination() 291 | combine(d::SetdiffDomain, results) = results[1] & !results[2] 292 | 293 | # It is difficult to calculate approximate membership exactly, but we can at 294 | # least not enlarge the subtracted domain by invoking in rather than approx_in on it. 295 | _approx_indomain(x, d::SetdiffDomain, comp::Combination, domains, tolerance) = 296 | approx_in(x, domains[1], tolerance) & !in(x, domains[2]) 297 | 298 | similardomain(d::SetdiffDomain, ::Type{T}) where {T} = 299 | SetdiffDomain(convert(Domain{T}, d.domains[1]), convert(Domain{T}, d.domains[2])) 300 | 301 | # use \ as a synomym for setdiff, in the context of domains (though, generically, 302 | # \ means left division in Julia) 303 | \(d1::Domain, d2::Domain) = setdiffdomain(d1, d2) 304 | \(d1::Domain, d2) = setdiffdomain(d1, d2) 305 | \(d1, d2::Domain) = setdiffdomain(d1, d2) 306 | 307 | # Make setdiff invoke `setdiffdomain` if one of the arguments is a Domain 308 | setdiff(d1::Domain, d2::Domain) = setdiffdomain(d1, d2) 309 | setdiff(d1::Domain, d2) = setdiffdomain(d1, d2) 310 | setdiff(d1, d2::Domain) = setdiffdomain(d1, d2) 311 | 312 | setdiffdomain(d1, d2) = setdiffdomain1(promote_domains(d1, d2)...) 313 | setdiffdomain1(d1, d2) = setdiffdomain2(d1, d2) 314 | 315 | function setdiffdomain2(d1, d2) 316 | if isempty(d2) 317 | d1 318 | elseif d1 == d2 319 | EmptySpace{eltype(d1)}() 320 | else 321 | SetdiffDomain(d1, d2) 322 | end 323 | end 324 | # similar to uniondomain, if d2 is a Domain we use `issubset` as an optimization 325 | function setdiffdomain2(d1, d2::Domain) 326 | if isempty(d2) 327 | d1 328 | elseif issubset(d1,d2) 329 | EmptySpace{eltype(d1)}() 330 | else 331 | SetdiffDomain(d1, d2) 332 | end 333 | end 334 | 335 | # avoid nested difference domains 336 | setdiffdomain1(d1::SetdiffDomain, d2) = setdiffdomain(d1.domains[1], uniondomain(d2, d1.domains[2])) 337 | 338 | ==(a::SetdiffDomain, b::SetdiffDomain) = a.domains == b.domains 339 | 340 | boundingbox(d::SetdiffDomain) = boundingbox(d.domains[1]) 341 | 342 | Display.combinationsymbol(d::SetdiffDomain) = Display.Symbol('\\') 343 | Display.displaystencil(d::SetdiffDomain) = composite_displaystencil(d) 344 | show(io::IO, mime::MIME"text/plain", d::SetdiffDomain) = Display.composite_show(io, mime, d) 345 | show(io::IO, d::SetdiffDomain) = Display.composite_show_compact(io, d) 346 | --------------------------------------------------------------------------------