├── REQUIRE ├── docs ├── index.md └── Domains.md ├── test ├── runtests.jl ├── lazybox.jl ├── hyperbox.jl ├── bool.jl └── interval.jl ├── src ├── common.jl ├── domains │ ├── relational.jl │ ├── boxes │ │ ├── hyperbox.jl │ │ └── lazybox.jl │ ├── boxes.jl │ ├── simpledisjunctive.jl │ ├── bool.jl │ ├── envvar.jl │ └── interval.jl ├── domains.jl ├── polyhedra.jl └── AbstractDomains.jl ├── .travis.yml ├── LICENSE.md └── README.md /REQUIRE: -------------------------------------------------------------------------------- 1 | julia 0.3 2 | Docile 3 | Iterators 4 | Compat 5 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # AbstractDomains 2 | 3 | *AbstractDomains.jl* is a [Julia](http://www.julialang.org) package for representing and computing with sets of valuess. 4 | It's useful for static analysis, bug checking, and inference. -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- 1 | using AbstractDomains 2 | 3 | tests = ["bool", 4 | "interval", 5 | "hyperbox", 6 | "lazybox"] 7 | 8 | println("Running tests:") 9 | 10 | for t in tests 11 | test_fn = "$t.jl" 12 | println(" * $test_fn") 13 | include(test_fn) 14 | end 15 | -------------------------------------------------------------------------------- /src/common.jl: -------------------------------------------------------------------------------- 1 | @doc "All the abstract domains" -> 2 | all_domains() = Set(Interval,AbstractBool,HyperBox) 3 | 4 | @doc "Return a type Union of all the abstract domains which can abstract a type T" -> 5 | abstractdomains(CT::DataType) = Union(filter(AT->isabstract(CT,AT),all_domains())...) 6 | 7 | ## Random Number Generation 8 | ## ========================= 9 | @doc "Random number between `a` and `b`" -> 10 | rand_interval(a::Float64, b::Float64) = a + (b - a) * rand() -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | compiler: 3 | - clang 4 | notifications: 5 | email: false 6 | env: 7 | matrix: 8 | - JULIAVERSION="juliareleases" 9 | - JULIAVERSION="julianightlies" 10 | before_install: 11 | - sudo add-apt-repository ppa:staticfloat/julia-deps -y 12 | - sudo add-apt-repository ppa:staticfloat/${JULIAVERSION} -y 13 | - sudo apt-get update -qq -y 14 | - sudo apt-get install libpcre3-dev julia -y 15 | - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi 16 | script: 17 | - julia -e 'Pkg.init(); Pkg.clone(pwd()); Pkg.test("AbstractDomains")' 18 | -------------------------------------------------------------------------------- /test/lazybox.jl: -------------------------------------------------------------------------------- 1 | using Base.Test 2 | using Compat 3 | X = Interval(0,1) 4 | @compat ll = LazyBox(Dict(1=>Interval(0,1))) 5 | ll[3] 6 | @test domaineq(ll[3],Interval(0,1)) 7 | @test ndims(ll) == 2 8 | ll[5] 9 | @test ndims(ll) == 3 10 | ll[5] 11 | @test ndims(ll) == 3 12 | 13 | @test length(convert(Vector{Interval},ll)) == ndims(ll) 14 | @test length(convert(Vector{Interval},ll,[1,2])) == 2 15 | 16 | l1 = LazyBox(Float64) 17 | l1[1] 18 | l1[2] = Interval(0.0,10.0) 19 | rand(l1) 20 | l2 = LazyBox(Int64) 21 | l2[1] 22 | l2[2] = Interval(2,5) 23 | rand(l2) 24 | 25 | @test 0 <= mid(l1)[2] <= 10 26 | @test 2 <= mid(l2)[2] <= 5 27 | 28 | # Splitting 29 | l1split = mid_split(l1) 30 | @test length(l1split) == 4 31 | # @compat partial_split_box(l2,Dict(2=>3)) Need to fix splitting intervals 32 | @compat l1split = partial_split_box(l1,Dict(1=>0.5)) 33 | @test length(l1split) == 2 -------------------------------------------------------------------------------- /src/domains/relational.jl: -------------------------------------------------------------------------------- 1 | ## Relational Disjunctive Domain A1 ∨ A2 ∨ A3 ∨ ... ∨ An 2 | ## ================================================================= 3 | 4 | # This is a simple non-relational domain of disjunctive domains (paramaterised by T) 5 | immutable Relational{T} <: Domain{T} 6 | values::Set{T} 7 | end 8 | 9 | # This domain is relational, duh 10 | isrelational(::Union(Relational, Type{Relational})) = true 11 | 12 | ## Set Operations 13 | ## ============== 14 | issubset(x::SimpleDisjunctive, y::SimpleDisjunctive) = issubset(x.values, y.values) 15 | isintersect(x::SimpleDisjunctive, y::SimpleDisjunctive) = intersect(x.values, y.values) 16 | domaineq(x::SimpleDisjunctive, y::SimpleDisjunctive) = x.values == y.values 17 | ⊔{T}(x::SimpleDisjunctive{T},y::T) = push!(x.values, y) 18 | 19 | # Apply f to every abstract element in disjunction 20 | function setmap{T}(f::Function, s::Set{T}) 21 | out = Set{T}() 22 | for x in s 23 | push!(out,f(x)) 24 | end 25 | out 26 | end 27 | -------------------------------------------------------------------------------- /src/domains.jl: -------------------------------------------------------------------------------- 1 | @doc "Abstract domains represent sets of finite values 2 | They are paramaterise by T which determines the type of values it represents" -> 3 | abstract Domain{T} 4 | 5 | # # Does the abstract domain represent a single variable or a set of variables 6 | # abstract VariateForm 7 | # type Univariate <: VariateForm end 8 | # type Multivariate <: VariateForm end 9 | 10 | # # Is it disjunctive or not 11 | # # Is it relational or not 12 | 13 | # abstract ValueSupport 14 | # type Discrete <: ValueSupport end 15 | # type Continuous <: ValueSupport end 16 | 17 | 18 | 19 | for finame in ["bool.jl", 20 | "interval.jl", 21 | "boxes.jl"] 22 | include(joinpath("domains", finame)) 23 | end 24 | 25 | ## Domain General Doc 26 | ## ================== 27 | @doc doc"Can an abstract domain represent relationships between variables?" -> isrelational 28 | @doc doc"Is domain `y` a subset of domain `x`" -> subsumes 29 | @doc doc"Is the intersection of two domains non-empty" -> isintersect 30 | @doc doc"Does domain `x` and domain `y` represent the same set of points" -> domaineq 31 | @doc doc"Unit interval [0,1]" -> unit 32 | @doc doc"Find midpoint of numerical domain " -> mid 33 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The AbstractDomains.jl package is licensed under the MIT "Expat" License: 2 | 3 | > Copyright (c) 2015: Zenna Tavares. 4 | > 5 | > Permission is hereby granted, free of charge, to any person obtaining 6 | > a copy of this software and associated documentation files (the 7 | > "Software"), to deal in the Software without restriction, including 8 | > without limitation the rights to use, copy, modify, merge, publish, 9 | > distribute, sublicense, and/or sell copies of the Software, and to 10 | > permit persons to whom the Software is furnished to do so, subject to 11 | > the following conditions: 12 | > 13 | > The above copyright notice and this permission notice shall be 14 | > included in all copies or substantial portions of the Software. 15 | > 16 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /src/polyhedra.jl: -------------------------------------------------------------------------------- 1 | ## Convex Polyhedra 2 | ## ================ 3 | convert( ::Type{Ptr{Ptr{UInt8}}}, s::Array{ASCIIString,1} ) = map(pointer ,s) 4 | 5 | pplisinitailsed = false 6 | 7 | function isppl_initialzied() 8 | global pplisinitailsed 9 | pplisinitailsed 10 | end 11 | 12 | function ppl_initialize() 13 | @assert !isppl_initialzied() 14 | ccall( (:ppl_initialize, "libppl_c"), Int32, ()) 15 | global pplisinitailsed = true 16 | end 17 | 18 | function ppl_finalize() 19 | @assert isppl_initialzied() 20 | ccall( (:ppl_finalize, "libppl_c"), Int32, ()) 21 | global pplisinitailsed = false 22 | end 23 | 24 | function ppl_version_vec() 25 | @assert isppl_initialzied() 26 | Int[ccall((:ppl_version_major, "libppl_c"), Int32, ()), 27 | ccall((:ppl_version_minor, "libppl_c"), Int32, ()), 28 | ccall((:ppl_version_revision, "libppl_c"), Int32, ()), 29 | ccall((:ppl_version_beta, "libppl_c"), Int32, ())] 30 | end 31 | 32 | function ppl_version() 33 | @assert isppl_initialzied() 34 | # versionnum = Array(UInt8, 20) 35 | versionnum = convert(Ptr{Ptr{Uint8}},["H", "E","L","L","O"]) 36 | res = ccall((:ppl_version, "libppl_c"), Int, (Ptr{Ptr{Uint8}},), versionnum) 37 | bytestring(convert(Ptr{Uint8}, versionnum[1])) 38 | end 39 | -------------------------------------------------------------------------------- /test/hyperbox.jl: -------------------------------------------------------------------------------- 1 | using AbstractDomains 2 | using Base.Test 3 | using Compat 4 | 5 | a = HyperBox([0.0 0.0 0.0 6 | 1.0 1.0 1.0]) 7 | b = HyperBox([0.0 0.0 0.0 0.0 8 | 1.0 1.0 1.0 1.0]) 9 | @test mid(a) == [0.5,0.5,0.5] 10 | @test ndims(a) == 3 11 | @test a != b 12 | @test isrelational(HyperBox) == false 13 | 14 | # Mid 15 | c = HyperBox([0.0 0.0 16 | 1.0 1.0]) 17 | prev = prevfloat(0.5) 18 | next = nextfloat(0.5) 19 | c1 = HyperBox([0.0 0.0 20 | 0.5 0.5]) 21 | c2 = HyperBox([next 0.0 22 | 1.0 0.5]) 23 | c3 = HyperBox([0.0 next 24 | 0.5 1.0]) 25 | c4 = HyperBox([next next 26 | 1.0 1.0]) 27 | q = mid_split(c) 28 | 29 | # Partial Split 30 | d = HyperBox([0.0 0.0 0.0 31 | 10.0 20.0 1.0]) 32 | @compat split_points = Dict(1 => 5.0, 3 => 0.5) 33 | @test length(partial_split_box(d,split_points)) == 4 34 | @test length(mid_partial_split(d,[1,2,3])) == 8 35 | @compat split_points2 = Dict(1 => 5.0, 2=> 0.1, 3 => 0.5) 36 | @test length(partial_split_box(d,split_points2)) == 8 37 | 38 | 39 | @test isequal(q[1],c1) 40 | @test isequal(q[2],c2) 41 | @test isequal(q[3],c3) 42 | @test isequal(q[4],c4) 43 | 44 | @test all(Bool[0 <= rb <= 1 for rb in rand(b)]) 45 | -------------------------------------------------------------------------------- /src/AbstractDomains.jl: -------------------------------------------------------------------------------- 1 | module AbstractDomains 2 | 3 | import Base: convert, promote_rule 4 | import Base: string, print, show, showcompact, rand 5 | import Base: abs, zero, one, in, inv, ndims, issubset, union, intersect, isequal, ifelse 6 | 7 | import Base: ! 8 | import Base: == 9 | import Base: == 10 | import Base: == 11 | import Base: == 12 | import Base: | 13 | import Base: & 14 | import Base: == 15 | import Base: != 16 | import Base: == 17 | import Base: == 18 | import Base: > 19 | import Base: < 20 | import Base: < 21 | import Base: < 22 | import Base: <= 23 | import Base: >= 24 | import Base: + 25 | import Base: - 26 | import Base: * 27 | import Base: / 28 | import Base: // 29 | import Base: getindex 30 | import Base: setindex! 31 | 32 | using Iterators 33 | using Compat 34 | VERSION < v"0.4-" && using Docile 35 | 36 | export Interval, 37 | Domain, 38 | AbstractBool, 39 | Boxes, HyperBox,LazyBox, 40 | LazyRandomVector, 41 | t,f,tf, 42 | ⊔, ⊓, subsumes, isintersect, intersect, isrelational, domaineq, 43 | ndims, 44 | getindex, 45 | mid, mid_split, partial_split_box, mid_partial_split, 46 | unit, 47 | dims, 48 | hasdim, 49 | isrelational 50 | 51 | include("common.jl") 52 | include("domains.jl") 53 | 54 | end 55 | -------------------------------------------------------------------------------- /test/bool.jl: -------------------------------------------------------------------------------- 1 | using AbstractDomains 2 | using Base.Test 3 | import AbstractDomains: isintersect, subsumes, ⊔ 4 | import AbstractDomains: t, f, tf 5 | 6 | @test t & f === f 7 | @test tf & f === f 8 | @test t & t === t 9 | @test tf & tf === tf 10 | @test !t === f 11 | @test !tf === tf 12 | @test !f === t 13 | @test tf | t === t 14 | @test tf | f === tf 15 | @test f | t === t 16 | @test t | tf === t 17 | 18 | # Lifted equality tests 19 | @test (t == t) === t 20 | @test (t == tf) === tf 21 | @test (tf == t) === tf 22 | @test (f == f) === t 23 | @test (t == f) === f 24 | @test (f == t) === f 25 | 26 | # isintersect 27 | @test isintersect(t,f) == false 28 | @test isintersect(f,t) == false 29 | @test isintersect(t,t) == true 30 | @test isintersect(f,f) == true 31 | @test isintersect(tf,t) == true 32 | @test isintersect(t,tf) == true 33 | @test isintersect(tf,f) == true 34 | @test isintersect(f,tf) == true 35 | @test isintersect(tf,tf) == true 36 | 37 | # Subsumes 38 | @test subsumes(tf,f) == true 39 | @test subsumes(tf,t) == true 40 | @test subsumes(tf,tf) == true 41 | @test subsumes(f,f) == subsumes(t,t) == true 42 | @test subsumes(f,t) == subsumes(t,f) == false 43 | @test subsumes(f,tf) == subsumes(t,tf) == false 44 | 45 | # Join 46 | @test ⊔(t,f) === ⊔(f,t) === tf 47 | @test ⊔(t,t) === t 48 | @test ⊔(f,f) === f 49 | @test ⊔(tf,t) === ⊔(t,tf) === tf 50 | @test ⊔(tf,f) === ⊔(f,tf) === tf 51 | @test ⊔(tf,tf) === tf 52 | 53 | #ifelse 54 | @test ifelse(tf,f,t) === tf 55 | @test ifelse(t,f,t) === f 56 | @test ifelse(f,f,t) === t -------------------------------------------------------------------------------- /src/domains/boxes/hyperbox.jl: -------------------------------------------------------------------------------- 1 | HyperBox{T}(a::Array{T,2}) = HyperBox{T}([Interval(a[1,i],a[2,i]) for i=1:size(a,2)]) 2 | 3 | ## Access 4 | ## ====== 5 | getindex(b::HyperBox, i::Int) = b.intervals[i] 6 | setindex!{T}(b::HyperBox{T}, i::Int, v::Interval{T}) = b.intervals[i] = v 7 | ndims(b::HyperBox) = length(b.intervals) 8 | dims(b::HyperBox) = collect(1:ndims(b)) # make this iterable? 9 | hasdim(b::HyperBox, i::Int) = i <= ndims(b) 10 | 11 | ## Splitting 12 | ## ========= 13 | mid{T}(b::HyperBox{T}) = T[mid(b[dim]) for dim in dims(b)] 14 | 15 | @doc "Split box into 2^d equally sized boxes by cutting down middle of each axis" -> 16 | function split_box{T}(b::HyperBox{T}, split_point::Vector{T}) 17 | @assert(length(split_point) == ndims(b)) 18 | splits = [split_box(b[i],split_point[i]) for i = 1:ndims(b)] # Split intervals 19 | intervals_set = prodsubboxes(splits) 20 | HyperBox[HyperBox([intervals...]) for intervals in intervals_set] 21 | end 22 | 23 | @doc doc"""Split a box along not all dimensions. 24 | `split_point` maps integer dimensions to point in that dimension to split""" -> 25 | function partial_split_box{T}(b::HyperBox{T}, split_point::Dict{Int, T}) 26 | @assert(length(keys(split_point)) <= ndims(b)) 27 | intervals_set = product_boxes(b, split_point) 28 | HyperBox[HyperBox([intervals...]) for intervals in intervals_set] 29 | end 30 | 31 | ## Print 32 | ## ===== 33 | string(b::HyperBox) = b.intervals 34 | print(io::IO, b::HyperBox) = print(io, string(b)) 35 | show(io::IO, b::HyperBox) = print(io, string(b)) 36 | showcompact(io::IO, b::HyperBox) = print(io, string(b)) 37 | -------------------------------------------------------------------------------- /src/domains/boxes.jl: -------------------------------------------------------------------------------- 1 | @doc """ 2 | An axis aligned hyperbox - a set of intervals 3 | """ -> 4 | type HyperBox{T} <: Domain{T} 5 | intervals::Vector{Interval{T}} 6 | end 7 | 8 | @doc doc"""A LazyBox is a hyperbox whose dimensions can be 9 | created dynamically and do not have to be contiguous""" -> 10 | immutable LazyBox{T} <: Domain{T} 11 | intervals::Dict{Int64,Interval{T}} 12 | end 13 | 14 | @doc "Boxes are abstractions of Vector{T<:Real}" -> 15 | typealias Boxes{T} Union(LazyBox{T},HyperBox{T}) 16 | 17 | ## Domain Operations 18 | ## ================= 19 | isrelational{T<:Boxes}(::Type{T}) = false 20 | domaineq(x::Boxes,y::Boxes) = isequal(x,y) 21 | function isequal(x::Boxes,y::Boxes) 22 | for dim in union(dims(x),dims(y)) 23 | if !(hasdim(x,dim) && hasdim(y,dim) && isequal(x[dim],y[dim])) 24 | return false 25 | end 26 | end 27 | true 28 | end 29 | 30 | ## Box Domain General functions 31 | ## ============================ 32 | function product_boxes{T}(b::Boxes, split_point::Dict{Int, T}) 33 | @assert(length(keys(split_point)) <= ndims(b)) 34 | 35 | splits = Vector{Interval{T}}[] 36 | for dim in dims(b) 37 | if haskey(split_point, dim) 38 | push!(splits, split_box(b[dim],split_point[dim])) 39 | else # Don't split along this dimension 40 | push!(splits, [b[dim]]) 41 | end 42 | end 43 | prodsubboxes(splits) 44 | end 45 | 46 | # Find cartesian product of a bunch of subboxes (vector of intervals) 47 | function prodsubboxes{T}(splits::Vector{Vector{Interval{T}}}) 48 | @compat boxes = Tuple{Vararg{Interval}}[] 49 | for subbox in product(splits...) 50 | push!(boxes, subbox) # product returns tuple 51 | end 52 | return boxes 53 | end 54 | 55 | mid{T}(b::Boxes{T}) = T[mid(b[dim]) for dim in dims(b)] 56 | 57 | @doc "Split box into 2^d equally sized boxes by cutting down middle of each axis" -> 58 | mid_split(b::Boxes) = split_box(b, mid(b)) 59 | 60 | @doc "Do a partial split at the midpoints of dimensions `dims`" -> 61 | mid_partial_split(b::Boxes, partial_dims::Vector{Int}) = 62 | @compat partial_split_box(b,Dict([dim => mid(b[dim]) for dim in partial_dims])) 63 | 64 | ## Sampling 65 | ## ======== 66 | rand(b::Boxes) = [rand(b[dim]) for dim in dims(b)] 67 | 68 | include("boxes/hyperbox.jl") 69 | include("boxes/lazybox.jl") 70 | -------------------------------------------------------------------------------- /src/domains/simpledisjunctive.jl: -------------------------------------------------------------------------------- 1 | ## Disjuncive Domain A1 ∨ A2 ∨ A3 ∨ ... ∨ An 2 | ## ================================================================= 3 | 4 | # This is a simple non-relational domain of disjunctive domains (paramaterised by T) 5 | 6 | immutable SimpleDisjunctive{T} <: Domain{T} 7 | values::Set{T} 8 | end 9 | 10 | isrelational(::Union(SimpleDisjunctive, Type{SimpleDisjunctive})) = false 11 | 12 | ## Set Operations 13 | ## ============== 14 | issubset(x::SimpleDisjunctive, y::SimpleDisjunctive) = issubset(x.values, y.values) 15 | isintersect(x::SimpleDisjunctive, y::SimpleDisjunctive) = intersect(x.values, y.values) 16 | domaineq(x::SimpleDisjunctive, y::SimpleDisjunctive) = x.values == y.values 17 | ⊔{T}(x::SimpleDisjunctive{T},y::T) = push!(x.values, y) 18 | 19 | # Apply f to every abstract element in disjunction 20 | function setmap{T}(f::Function, s::Set{T}) 21 | out = Set{T}() 22 | for x in s 23 | push!(out,f(x)) 24 | end 25 | out 26 | end 27 | 28 | # Operations on Disjunctive Domains (to be sound) do a cartesian product 29 | # Real × Real -> Rea 30 | for op = (:+, :-, :*, :/) 31 | @eval begin 32 | function ($op){T}(x::SimpleDisjunctive{T}, y::SimpleDisjunctive{T}) 33 | let op = $op 34 | fcartproduct($op,T,x,y) 35 | end 36 | end 37 | end 38 | end 39 | 40 | # Boolean Valued Functions 41 | # Real × Real -> Rea 42 | for op = (:>, :>=, :<, :<=, :&, :|) 43 | @eval begin 44 | function ($op){T}(x::SimpleDisjunctive{T}, y::SimpleDisjunctive{T}) 45 | let op = $op 46 | fcartproduct($op,Bool,x,y) 47 | end 48 | end 49 | end 50 | end 51 | 52 | # Real -> Rea 53 | for op = (:sqr,) 54 | @eval begin 55 | function ($op){T}(x::SimpleDisjunctive{T}) 56 | let op = $op 57 | SimpleDisjunctive{T}(setmap(op,x.values)) 58 | end 59 | end 60 | end 61 | end 62 | 63 | function !(x::SimpleDisjunctive{Bool}) 64 | if x.values == Set{Bool}(true) SimpleDisjunctive{Bool}(Set(false)) 65 | elseif x.values == Set{Bool}(false) SimpleDisjunctive{Bool}(Set(true)) 66 | else x end 67 | end 68 | 69 | # Apply f to the cartesian product of values in x and y 70 | function fcartproduct(f::Function, T::DataType, 71 | x::SimpleDisjunctive, y::SimpleDisjunctive) 72 | result = SimpleDisjunctive{T}(Set{T}()) 73 | for args in Iterators.product(x.values,y.values) 74 | ⊔(result, f(args...)) 75 | end 76 | result 77 | end 78 | -------------------------------------------------------------------------------- /src/domains/boxes/lazybox.jl: -------------------------------------------------------------------------------- 1 | @compat LazyBox{T<:Real}(T2::Type{T}) = LazyBox{T2}(Dict()) 2 | 3 | @doc "Gets nth dim of box, creates it as unit-interval if it doesn't exist" -> 4 | function getindex{D}(o::LazyBox{D}, key::Int) 5 | if haskey(o.intervals,key) 6 | o.intervals[key] 7 | else 8 | o.intervals[key] = unit(Interval{D}) 9 | end 10 | end 11 | 12 | setindex!{T}(b::LazyBox{T}, val::Interval{T}, key::Int) = b.intervals[key] = val 13 | ndims(b::LazyBox) = length(keys(b.intervals)) 14 | dims(b::LazyBox) = keys(b.intervals) 15 | hasdim(b::LazyBox, i::Int) = haskey(b.intervals,i) 16 | 17 | ## Conversion 18 | ## ========== 19 | convert{T}(::Type{Vector{Interval{T}}}, b::LazyBox{T}) = collect(values(b.intervals)) 20 | convert{T}(::Type{Vector{Interval}}, b::LazyBox{T}) = collect(values(b.intervals)) 21 | convert{T}(::Type{Vector{Interval{T}}}, b::LazyBox{T}, dims::Vector) = Interval{T}[b[d] for d in dims] 22 | convert{T}(::Type{Vector{Interval}}, b::LazyBox{T}, dims::Vector) = Interval{T}[b[d] for d in dims] 23 | convert(::Type{HyperBox}, l::LazyBox) = HyperBox(convert(Vector{Interval},l)) 24 | 25 | # ## Splitting 26 | # ## ========= 27 | mid{T}(b::LazyBox{T}) = 28 | Dict([dim => mid(interval) for (dim,interval) in b.intervals]) 29 | 30 | function split_box{T}(b::LazyBox{T}, split_point::Dict{Int,T}) 31 | ks = collect(keys(b.intervals)) 32 | intervals_set = product_boxes(b,split_point) 33 | @compat [LazyBox(Dict{Int,Interval{T}}(zip(ks,intervals))) for intervals in intervals_set] 34 | end 35 | 36 | partial_split_box{T}(b::LazyBox{T}, split_point::Dict{Int,T}) = 37 | split_box(b,split_point) 38 | 39 | ## Rand 40 | @doc doc"""A Vector of whose values are sampled uniformly from [0,1], but are not 41 | created until accessed (hence Lazy).""" -> 42 | immutable LazyRandomVector{T<:Real} 43 | samples::Dict{Int64,T} 44 | end 45 | LazyRandomVector{T<:Real}(T1::Type{T}) = LazyRandomVector(Dict{Int64,T1}()) 46 | 47 | function getindex{T}(o::LazyRandomVector{T}, key::Int) 48 | if haskey(o.samples,key) 49 | o.samples[key] 50 | else 51 | i = rand(T) 52 | o.samples[key] = i 53 | i 54 | end 55 | end 56 | 57 | function setindex!{T}(o::LazyRandomVector{T}, val::T, key::Int) 58 | o.samples[key] = val 59 | end 60 | 61 | function rand{T<:Real}(b::LazyBox{T}) 62 | l = LazyRandomVector(T) 63 | for (dim,interval) in b.intervals 64 | l[dim] = rand(interval) 65 | end 66 | l 67 | end 68 | 69 | ## Print 70 | ## ===== 71 | string(b::LazyBox) = b.intervals 72 | print(io::IO, b::LazyBox) = print(io, string(b)) 73 | show(io::IO, b::LazyBox) = print(io, string(b)) 74 | showcompact(io::IO, b::LazyBox) = print(io, string(b)) -------------------------------------------------------------------------------- /test/interval.jl: -------------------------------------------------------------------------------- 1 | using AbstractDomains 2 | import AbstractDomains: subsumes, isintersect, ⊔, sqr, makepos 3 | using Base.Test 4 | 5 | # Concrete Arithmetic Examples 6 | @test Interval(3,4) + Interval(9,10) === Interval(12,14) 7 | @test Interval(3,4) * Interval(1,2) === Interval(3,8) 8 | 9 | 10 | @test subsumes(Interval(-5,5), Interval(-3,3)) 11 | @test subsumes(Interval(-5,5), Interval(-5,5)) 12 | @test !subsumes(Interval(-5,5), Interval(-3,10)) 13 | @test !subsumes(Interval(-5,5), Interval(10,20)) 14 | 15 | @test isintersect(Interval(-5,5), Interval(-3,3)) 16 | @test isintersect(Interval(-5,5), Interval(-3,10)) 17 | @test isintersect(Interval(0,5), Interval(5,10)) 18 | @test !isintersect(Interval(-5,5), Interval(10,20)) 19 | @test !isintersect(Interval(10,20), Interval(5,-5)) 20 | 21 | @test (Interval(5,5) > Interval(5,5)) === f 22 | @test (Interval(0,5) > Interval(5,5)) === f 23 | @test (Interval(5,5) > Interval(0,5)) === tf 24 | 25 | @test (Interval(0,1) > Interval(-2,-1)) === t 26 | @test (Interval(0,1) > Interval(1,2)) === f 27 | @test (Interval(0,1) > Interval(-1,0)) === tf 28 | @test (Interval(0,1) > Interval(1.5,2.5)) === f 29 | 30 | @test (Interval(-9,-3) < Interval(0,3)) === t 31 | @test (Interval(0,1) < Interval(1,2)) === tf 32 | @test (Interval(0,1) < Interval(-1,0)) === f 33 | @test (Interval(1.5,2.5) < Interval(0,1)) === f 34 | 35 | @test (Interval(0,1) >= Interval(0,0)) === t 36 | @test (Interval(0,1) <= Interval(1,1)) === t 37 | 38 | @test (Interval(0,1) == Interval(0,1)) === tf 39 | 40 | #abs 41 | @test abs(Interval(-3,-1)) === Interval(1,3) 42 | @test abs(Interval(-9,0)) === Interval(0,9) 43 | @test abs(Interval(-10,5)) === Interval(0,10) 44 | @test abs(Interval(0,7)) === Interval(0,7) 45 | @test abs(Interval(0,0)) === Interval(0,0) 46 | @test abs(Interval(2,5)) === Interval(2,5) 47 | @test sqr(Interval(-4,4)) === Interval(0,16) 48 | 49 | # unit 50 | @test unit(Interval{Float64}) === Interval(0.0,1.0) 51 | @test unit(Interval{Int}) === Interval(0,1) 52 | 53 | # Division 54 | @test Interval(9,18) / Interval(2,3) === Interval(3.0,9.0) 55 | @test makepos(Interval(-2,5)) === Interval(0,5) 56 | 57 | # ⊔ 58 | @test ⊔(Interval(-3,2), Interval(10,12)) === Interval(-3,12) 59 | @test Interval(0,0) ⊔ 1 === Interval(0,1) 60 | 61 | # Conversion Tests 62 | @test convert(Interval, 3) === Interval{Int}(3,3) 63 | @test convert(Interval{Float64}, 3) === Interval{Float64}(3.0,3.0) 64 | @test convert(Interval{Float64}, Interval(1,2)) === Interval(1.0,2.0) 65 | @test (promote(Interval(3,5),Interval(10.0,20.0))) === (Interval(3.0,5.0),Interval(10.0,20.0)) 66 | 67 | # rand 68 | @test all(i->-100<=i<=100,rand(Interval(-100,100),100)) 69 | @test all(i->-100.<=i<=100.,rand(Interval(-100.,100.),100)) 70 | -------------------------------------------------------------------------------- /docs/Domains.md: -------------------------------------------------------------------------------- 1 | # AbstractDomains 2 | 3 | ## Exported 4 | --- 5 | 6 | ### isintersect 7 | Is the intersection of two domains non-empty 8 | 9 | *source:* 10 | [AbstractDomains/src/domains.jl:14](file:///home/zenna/.julia/v0.3/AbstractDomains/src/domains.jl) 11 | 12 | --- 13 | 14 | ### isrelational 15 | Can an abstract domain represent relationships between variables? 16 | 17 | *source:* 18 | [AbstractDomains/src/domains.jl:12](file:///home/zenna/.julia/v0.3/AbstractDomains/src/domains.jl) 19 | 20 | --- 21 | 22 | ### subsumes 23 | Is domain `y` a subset of domain `x` 24 | 25 | *source:* 26 | [AbstractDomains/src/domains.jl:13](file:///home/zenna/.julia/v0.3/AbstractDomains/src/domains.jl) 27 | 28 | --- 29 | 30 | ### intersect{T}(x::Interval{T}, y::Interval{T}) 31 | Construct interval which is intersection of two intervals 32 | 33 | *source:* 34 | [AbstractDomains/src/domains/interval.jl:48](file:///home/zenna/.julia/v0.3/AbstractDomains/src/domains/interval.jl) 35 | 36 | --- 37 | 38 | ### HyperBox{T} 39 | An axis aligned hyperbox - a set of intervals 40 | 41 | 42 | *source:* 43 | [AbstractDomains/src/domains/hyperbox.jl:3](file:///home/zenna/.julia/v0.3/AbstractDomains/src/domains/hyperbox.jl) 44 | 45 | --- 46 | 47 | ### Interval{T<:Real} 48 | An Interval of type 'T' between 'a' and 'b' represents all the values 49 | of type 'T' between 'a' and 'b'. 50 | 51 | 52 | *source:* 53 | [AbstractDomains/src/domains/interval.jl:3](file:///home/zenna/.julia/v0.3/AbstractDomains/src/domains/interval.jl) 54 | 55 | ## Internal 56 | --- 57 | 58 | ### domaineq 59 | Does domain `x` and domain `y` represent the same set of points 60 | 61 | *source:* 62 | [AbstractDomains/src/domains.jl:15](file:///home/zenna/.julia/v0.3/AbstractDomains/src/domains.jl) 63 | 64 | --- 65 | 66 | ### findproduct(splits::Array{Array{Array{Float64, 1}, 1}, 1}, b::HyperBox{T}) 67 | Splits a box at a split-point along all its dimensions into n^d boxes 68 | 69 | *source:* 70 | [AbstractDomains/src/domains/hyperbox.jl:37](file:///home/zenna/.julia/v0.3/AbstractDomains/src/domains/hyperbox.jl) 71 | 72 | --- 73 | 74 | ### mid_partial_split(b::HyperBox{T}, dims::Array{Int64, 1}) 75 | Do a partial split at the midpoints of dimensions `dims` 76 | 77 | *source:* 78 | [AbstractDomains/src/domains/hyperbox.jl:76](file:///home/zenna/.julia/v0.3/AbstractDomains/src/domains/hyperbox.jl) 79 | 80 | --- 81 | 82 | ### mid_split(b::HyperBox{T}) 83 | Split box into 2^d equally sized boxes by cutting down middle of each axis 84 | 85 | *source:* 86 | [AbstractDomains/src/domains/hyperbox.jl:73](file:///home/zenna/.julia/v0.3/AbstractDomains/src/domains/hyperbox.jl) 87 | 88 | --- 89 | 90 | ### rand_interval(a::Float64, b::Float64) 91 | Random number between `a` and `b` 92 | 93 | *source:* 94 | [AbstractDomains/src/common.jl:1](file:///home/zenna/.julia/v0.3/AbstractDomains/src/common.jl) 95 | 96 | -------------------------------------------------------------------------------- /src/domains/bool.jl: -------------------------------------------------------------------------------- 1 | @doc doc"Abstract Boolean Types: {{true},{false}.{true,false}}" -> 2 | immutable AbstractBool <: Domain{Bool} 3 | v::Uint8 4 | AbstractBool(v::Uint8) = (@assert v == 0x1 || v == 0x2 || v== 0x3; new(v)) 5 | end 6 | 7 | const t = AbstractBool(0x1) 8 | const f = AbstractBool(0x2) 9 | const tf = AbstractBool(0x3) 10 | 11 | promote_rule(::Type{Bool}, ::Type{AbstractBool}) = AbstractBool 12 | convert(::Type{AbstractBool}, b::Bool) = if b t else f end 13 | 14 | 15 | ## AbstractBool Set Operations 16 | ## =========================== 17 | subsumes(x::AbstractBool, y::AbstractBool) = x === tf || x === y 18 | subsumes(x::AbstractBool, y::Bool) = subsumes(x,convert(AbstractBool, y)) 19 | 20 | isintersect(x::AbstractBool, y::AbstractBool) = !((x === t && y === f) || (x === f && y === t)) 21 | isintersect(x::AbstractBool, y::Bool) = isintersect(x,convert(AbstractBool, y)) 22 | isintersect(x::Bool, y::AbstractBool) = isintersect(convert(AbstractBool, x),y) 23 | 24 | isrelational(::Type{AbstractBool}) = false 25 | isabstract(c::Type{Bool}, a::Type{AbstractBool}) = true 26 | 27 | isequal(x::AbstractBool, y::AbstractBool) = x.v == y.v 28 | domaineq(x::AbstractBool, y::AbstractBool) = x.v == y.v 29 | 30 | ⊔(a::AbstractBool) = a 31 | ⊔(a::AbstractBool, b::AbstractBool) = a === b ? a : tf 32 | ⊔(a::Bool, b::AbstractBool) = ⊔(convert(AbstractBool,a),b) 33 | ⊔(a::AbstractBool, b::Bool) = ⊔(a,convert(AbstractBool,b)) 34 | ⊔(a::Bool, b::Bool) = a === b ? convert(AbstractBool,a) : tf 35 | 36 | 37 | ## ========================= 38 | ## Lifted Boolean Arithmetic 39 | 40 | function !(b::AbstractBool) 41 | if b === t 42 | f 43 | elseif b === f 44 | t 45 | elseif b === tf 46 | tf 47 | end 48 | end 49 | 50 | (==)(x::AbstractBool, y::AbstractBool) = 51 | x === tf || y === tf ? tf : x === t && y === t || x === f && y === f 52 | 53 | function (==)(x::AbstractBool, y::AbstractBool) 54 | if x === tf || y === tf tf 55 | elseif x === t && y === t t 56 | elseif x === f && y === f t 57 | else f 58 | end 59 | end 60 | (==)(x::AbstractBool, y::Bool) = (==)(promote(x,y)...) 61 | (==)(y::Bool,x::AbstractBool) = (==)(promote(y,x)...) 62 | 63 | function (|)(x::AbstractBool, y::AbstractBool) 64 | if x === t || y === t t 65 | elseif x === tf || y === tf tf 66 | else f 67 | end 68 | end 69 | |(x::AbstractBool, y::Bool) = |(x,convert(AbstractBool,y)) 70 | |(y::Bool, x::AbstractBool) = |(convert(AbstractBool,y), x) 71 | 72 | function (&)(x::AbstractBool, y::AbstractBool) 73 | if x === f || y === f f 74 | elseif x === tf || y === tf tf 75 | else t 76 | end 77 | end 78 | 79 | (&)(x::AbstractBool, y::Bool) = x & convert(AbstractBool, y) 80 | (&)(y::Bool, x::AbstractBool) = convert(AbstractBool, y) & x 81 | 82 | # When condition is TF we need to evaluate both branches 83 | # and merge with ⊔ 84 | function ifelse(c::AbstractBool, x, y) 85 | if c === t 86 | x 87 | elseif c === f 88 | y 89 | elseif c === tf 90 | ⊔(x,y) 91 | end 92 | end 93 | 94 | ## Printing 95 | ## ======== 96 | string(x::AbstractBool) = ["{true}","{false}","{true,false}"][x.v] 97 | print(io::IO, x::AbstractBool) = print(io, string(x)) 98 | show(io::IO, x::AbstractBool) = print(io, string(x)) 99 | showcompact(io::IO, x::AbstractBool) = print(io, string(x)) 100 | -------------------------------------------------------------------------------- /src/domains/envvar.jl: -------------------------------------------------------------------------------- 1 | # Variables which can be multiple values 2 | NotEnv = Union(Float64, Bool, AbstractBool, Array, Int64, Interval) 3 | immutable EnvVar{K,V} 4 | worlds::Dict{K,NotEnv} 5 | 6 | EnvVar() = new(Dict{K,V}()) 7 | EnvVar(worlds::Dict{K,V}) = new(worlds) 8 | end 9 | EnvVar() = EnvVar{Any,Any}() 10 | singleton{T}(x::T) = (s = Set{T}();push!(s,x);s) 11 | const noconstraints = Set{Symbol}() 12 | 13 | typealias IntervalEnvDict Dict{Set{Symbol},Interval} 14 | typealias IntervalEnvVar EnvVar{Set{Symbol}, Interval} 15 | 16 | function intervalenvvar{T<:Real}(x::T,y::T) 17 | EnvVar{Set{Symbol},Interval}(IntervalEnvDict([noconstraints => Interval(x,y)])) 18 | end 19 | 20 | unitinterval(::Type{EnvVar}) = intervalenvvar(0.,1.) 21 | 22 | function getindex(e::EnvVar, i::Int64) 23 | ret = EnvVar() 24 | for world in e.worlds 25 | val = world[2][i] 26 | if isa(val, EnvVar) 27 | for valworld in val.worlds 28 | ret.worlds[valworld[1]] = valworld[2] 29 | end 30 | else 31 | ret.worlds[world[1]] = world[2][i] 32 | end 33 | end 34 | ret 35 | end 36 | 37 | function getindex(e::EnvVar, i::Int64, j::Int64) 38 | ret = EnvVar() 39 | for world in e.worlds 40 | val = world[2][i,j] 41 | if isa(val, EnvVar) 42 | for valworld in val.worlds 43 | ret.worlds[valworld[1]] = valworld[2] 44 | end 45 | else 46 | ret.worlds[world[1]] = world[2][i,j] 47 | end 48 | end 49 | ret 50 | end 51 | 52 | 53 | # function getindex(e::EnvVar, i::Int64, j::Int64) 54 | # ret = EnvVar() 55 | # for world in e.worlds 56 | # ret.worlds[world[1]] = world[2][i,j] 57 | # end 58 | # ret 59 | # end 60 | 61 | # Set functions 62 | function subsumes(a::AbstractBool,e::EnvVar) 63 | doessubsume = true 64 | for world in e.worlds 65 | doessubsume = doessubsume & subsumes(a,world[2]) 66 | end 67 | doessubsume 68 | end 69 | 70 | function convert(::Type{Vector{EnvVar}}, b::HyperBox) 71 | x = [intervalenvvar(b.intervals[1,i],b.intervals[2,i]) for i = 1:num_dims(b)] 72 | end 73 | 74 | function isintersect(e::EnvVar, a::AbstractBool) 75 | doesisintersect = false # only has to isintersect with one 76 | for world in e.worlds 77 | doesisintersect = doesisintersect | isintersect(a,world[2]) 78 | end 79 | doesisintersect 80 | end 81 | 82 | function isintersect(a::AbstractBool, e::EnvVar) 83 | doesisintersect = false # only has to isintersect with one 84 | for world in e.worlds 85 | doesisintersect = doesisintersect | isintersect(a,world[2]) 86 | end 87 | doesisintersect 88 | end 89 | 90 | ConcreteValue = Union(Float64, Int64, Bool) 91 | 92 | #FIXME, IVE GOT IN and SIZE AS ASSOCIATE BUT ITS NOT 93 | for op = (:+, :-, :*, :>, :>=, :<=, :<, :&, :|, :in, :/, :size) 94 | @eval begin 95 | function ($op)(x::EnvVar, y::EnvVar) 96 | ret = EnvVar() #FIXME: MAKE TYPE STABLE 97 | for xworld in x.worlds 98 | xworldid = xworld[1] 99 | if haskey(y.worlds,xworldid) # If we share the same world 100 | ret.worlds[xworldid] = ($op)(xworld[2],y.worlds[xworldid]) 101 | else 102 | for yworld in y.worlds 103 | conjworld = union(xworldid,yworld[1]) 104 | ret.worlds[conjworld] = ($op)(xworld[2],yworld[2]) 105 | end 106 | end 107 | end 108 | ret 109 | end 110 | end 111 | 112 | @eval begin 113 | function ($op)(x::EnvVar, y::ConcreteValue) 114 | ret = EnvVar() #FIXME: MAKE TYPE STABLE 115 | for xworld in x.worlds 116 | xworldid = xworld[1] 117 | ret.worlds[xworldid] = ($op)(xworld[2],y) 118 | end 119 | ret 120 | end 121 | end 122 | 123 | @eval begin 124 | function ($op)(y::ConcreteValue, x::EnvVar) 125 | ret = EnvVar() #FIXME: MAKE TYPE STABLE 126 | for xworld in x.worlds 127 | xworldid = xworld[1] 128 | ret.worlds[xworldid] = ($op)(y,xworld[2]) 129 | end 130 | ret 131 | end 132 | end 133 | end 134 | 135 | for op = (:sqr, :sqrt, :inv) 136 | @eval begin 137 | function ($op)(x::EnvVar) 138 | ret = EnvVar() #FIXME: MAKE TYPE STABLE 139 | for xworld in x.worlds 140 | xworldid = xworld[1] 141 | ret.worlds[xworldid] = ($op)(xworld[2]) 142 | end 143 | ret 144 | end 145 | end 146 | end 147 | 148 | function update_ret!(a::EnvVar,ret::EnvVar, constraints) 149 | for aworld in a.worlds 150 | ret.worlds[union(aworld[1],constraints)] = aworld[2] 151 | end 152 | end 153 | 154 | function update_ret!(a::ConcreteValue, ret::EnvVar, constraints) 155 | ret.worlds[constraints] = a 156 | end 157 | 158 | function add_path_constraints(e::EnvVar, constraints) 159 | ret = EnvVar() 160 | for world in e.worlds 161 | ret.worlds[union(world[1],constraints)] = world[2] 162 | end 163 | ret 164 | end 165 | 166 | add_path_constraints(e::ConcreteValue, constraints) = e 167 | 168 | function update_ret!(a::Array, ret::EnvVar, constraints) 169 | amap = map(x->add_path_constraints(x,constraints),a) 170 | ret.worlds[constraints] = amap 171 | end 172 | 173 | macro Iff(condition, conseq, alt) 174 | local idtrue = singleton(gensym()) 175 | local idfalse = singleton(gensym()) 176 | q = 177 | quote 178 | c = $(esc(condition)); 179 | local ret 180 | if isa(c, EnvVar) 181 | ret = EnvVar() 182 | @show world[2] 183 | for world in c.worlds 184 | if world[2] === T || world[2] === true 185 | ret.worlds[world[1]] = $(esc(conseq)) 186 | elseif world[2] === F || world[2] === false 187 | ret.worlds[world[1]] = $(esc(alt)) 188 | elseif world[2] === TF 189 | a = $(esc(conseq)) 190 | constraintstrue = union(world[1],$idtrue) 191 | update_ret!(a,ret, constraintstrue) 192 | 193 | b = $(esc(alt)) 194 | constraintsfalse = union(world[1],$idfalse) 195 | update_ret!(b,ret, constraintsfalse) 196 | 197 | else 198 | println("error:", world[2]) 199 | throw(DomainError()) 200 | end 201 | end 202 | end 203 | ret 204 | end 205 | return q 206 | end 207 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AbstractDomains.jl 2 | 3 | This Julia package allows you to run Julia programs with (possibly infinite) sets of values. These sets are called abstract domains, and computing with them is called [abstract interpretation](http://en.wikipedia.org/wiki/Abstract_interpretation). Computing with sets of values is useful for static analysis and verification (e.g. to know for *sure* that some bug won't occur). But it is also useful for reasoning and inference. 4 | 5 | [![Build Status](https://travis-ci.org/zenna/AbstractDomains.jl.svg?branch=master)](https://travis-ci.org/zenna/AbstractDomains.jl) 6 | 7 | [__Documentation__ can be found here](http://abstractdomainsjl.readthedocs.org) 8 | 9 | # Domains 10 | 11 | An abstract domain is used to represent a large or infinite set in a finite amount of space. For instance we can use intervals `[a, b]` to represent all the floating point numbers between `a` and `b`. AbstractDomains.jl then provides functions for computing with abstract values, for instance we can add or multiple two intervals. 12 | 13 | Currently AbstractDomains.jl supports only two abstract domains: `Interval` and `AbstractBool`. For every primitive functions such as `+`, `-`, `&`, `ifelse`, defined on concrete values such as `Float64` or `Bool`, there is a corresponding *lifted* method defined on its abstract counterpart: `Interval` and `AbstractBool`. 14 | 15 | The meaning of these *lifted* functions is [*pointwise*](http://en.wikipedia.org/wiki/Pointwise). That is, if we do `C = f(A,B)` where `A` and `B` are elements belonging to an abstract domain, then `C` should contain the result of applying `f` to __all possible combinations__ of elements in `A` and `B`. 16 | 17 | # Example 18 | 19 | If only using Intervals, AbstractDomains.jl is basically an interval arithmetic package, e.g.: 20 | 21 | ```julia 22 | julia> A = Interval(0,1) 23 | [0.0 1.0] 24 | 25 | julia> B = Interval(1,2) 26 | [1.0 2.0] 27 | 28 | julia> C = A + B 29 | [1.0 3.0] 30 | ``` 31 | 32 | `C` should be an interval which represents all the values if we took every value in the interval `A`. The following code represents what this means (__note:__ it is not valid code): 33 | 34 | ```julia 35 | # What C = A + B is doing (conceptually!) 36 | c = Set(Float64) 37 | for a in A 38 | for b in B 39 | push!(c, a + b) 40 | end 41 | end 42 | ``` 43 | 44 | Functions involving intervals and normal values are also defined in AbstractDomains.jl: 45 | 46 | ```julia 47 | julia> C * 3 48 | [3.0 9.0] 49 | ``` 50 | 51 | ## Boolean Functions and AbstractBools 52 | 53 | If we apply boolean functions (`>` `>=` `<` `<=` `ifelse`) to intervals, we don't get back a `Bool`, we get back an `AbstractBool`. For example: 54 | 55 | ```julia 56 | julia> A = Interval(1,2) 57 | [1.0 2.0] 58 | 59 | julia> A > 0 60 | {true} 61 | 62 | julia> A > 3 63 | {false} 64 | ``` 65 | 66 | This means that for all the elements in `A` (e.g., 1.0, 1.000001, 1.00002,...), *all* of them are greater than 0. Similarly _none_ of `A` is greater than 3. But why has it returned these strange value `{true}` and `{false}` instead of `true` and `false`? The next example should help illustrate why: 67 | 68 | ```julia 69 | julia> A = Interval(1,2) 70 | [1.0 2.0] 71 | 72 | julia> A > 1.5 73 | {true,false} 74 | ``` 75 | 76 | The result `{true,false}` means that there are some values in `A` which are greater than 1.5 and some that are not greater than 1.5. 77 | 78 | The values `{true}`, `{false}` and `{true,false}` fully represent the `AbstractBool` domain. These values are exported by AbstractDomains.jl as `t`, `f` and `tf` respectively, and primitive boolean operations are defined on them, e.g: 79 | 80 | ```julia 81 | julia> t & t 82 | {true} 83 | 84 | julia> t & f 85 | {false} 86 | 87 | julia> tf & t 88 | {true,false} 89 | 90 | julia> tf & f 91 | {false} 92 | 93 | julia> tf | f 94 | {true,false} 95 | ``` 96 | 97 | Some of these may require thinking about. But they are all consistent with the semantics of functions on abstract operations being defined pointwise, as described above. 98 | 99 | ## Equality 100 | Perhaps surprisingly, there are many meaningful definitions of equality. In AbstractDomains.jl equality `==` is, like the other functions we've seen before, defined pointwise. This can lead to some unexpected results if you're not careful, e.g. 101 | 102 | ```julia 103 | julia> tf == tf 104 | {true,false} 105 | 106 | ``` 107 | 108 | The answer is `{true,false}` because `true == true` is `true`, but `false == true` is `false` (to take just two of four possible combinations). Similarly: 109 | 110 | ```julia 111 | julia> Interval(0,1) == Interval(0,1) 112 | {true,false} 113 | ``` 114 | 115 | The answer is `{true,false}` because there are points in the first interval which equal to points in the second interval, but there are also points in the first interval which are not equal to point in the second interval. 116 | 117 | # Non-pointwise functions on abstract domains 118 | If we really want to test the identity of abstract objects we would use `isequal, e.g. 119 | 120 | ```julia 121 | julia> isequal(Interval(0,1), Interval(0,1)) 122 | true 123 | 124 | julia> isequal(tf,f) 125 | false 126 | ``` 127 | 128 | 129 | `isequal ` is an example of a function on abstract values which is not defined pointwise. There are many useful others. Some of which we call *domain functions*: 130 | 131 | ```julia 132 | julia> subsumes(Interval(0,1),Interval(0.4,0.5)) 133 | true 134 | 135 | julia> isintersect(Interval(0,1),Interval(3,6)) 136 | false 137 | 138 | julia> isintersect(t,f) 139 | false 140 | 141 | julia> isintersect(t,tf) 142 | true 143 | 144 | # ⊔ (\sqcup) is a lub 145 | julia> Interval(0,1) ⊔ Interval(10,3) 146 | [0.0 10.0] 147 | ``` 148 | 149 | # Imprecision 150 | 151 | Abstractions are *sound* but can be *imprecise*. Imprecision means that the result of a function application may contain values which are not possible in reality. E.g.: 152 | 153 | ```julia 154 | julia> A = Interval(1,2) 155 | [1.0 2.0] 156 | 157 | julia> A / A 158 | [0.5 2.0] 159 | ``` 160 | 161 | The precise answer should be `[1.0, 1.0]`. This imprecision comes because we do not recognise that the `A` in the denominator is the same `A` as in the numerator. That is, we have ignored the dependencies. 162 | 163 | Imprecision is undesirable. We can take some comfort in knowing that we'll always be sound, which this example also demonstrates. Soundness means that the true answer `[1.0, 1.0]` must be a subset of the answer AbstractDomains.jl gives us. For instance, the following would __never__ happen 164 | 165 | ```julia 166 | julia> A = Interval(1,2) 167 | [1.0 2.0] 168 | 169 | # Unsound result: this can *NEVER* happen 170 | julia> A / A 171 | [2.0 3.0] 172 | ``` -------------------------------------------------------------------------------- /src/domains/interval.jl: -------------------------------------------------------------------------------- 1 | @doc doc"""An Interval of type 'T' between 'a' and 'b' represents all the values 2 | of type 'T' between 'a' and 'b'. 3 | """ -> 4 | immutable Interval{T<:Real} <: Domain{T} 5 | l::T 6 | u::T 7 | Interval(l,u) = if u > l new(l, u) else new(u,l) end 8 | end 9 | 10 | Interval{T<:Real}(x::T) = Interval{T}(x,x) 11 | Interval{T<:Real}(v::Vector{T}) = Interval(v[1],v[2]) 12 | Interval{T<:Real}(x::T,y::T) = Interval{T}(x,y) 13 | Interval{T1<:Real, T2<:Real}(x::T1,y::T2) = Interval{promote_type(T1,T2)}(promote(x,y)...) 14 | 15 | 16 | ## Conversions and Promotion 17 | ## ========================= 18 | 19 | # A concrete number can be coerced into an interval with no width 20 | function convert{T1<:Real, T2<:Real}(::Type{Interval{T1}}, x::Interval{T2}) 21 | T = promote_type(T1,T2) 22 | Interval{T}(convert(T,x.l),convert(T,x.u)) 23 | end 24 | convert{T<:Real}(::Type{Interval}, c::T) = Interval{T}(c,c) 25 | convert{T<:Real}(::Type{Interval{T}}, c::T) = Interval{T}(c,c) 26 | convert{T1<:Real, T2<:Real}(::Type{Interval{T1}}, c::T2) = Interval{T1}(c,c) 27 | 28 | promote_rule{T1<:Real, T2<:Real}(::Type{Interval{T1}}, ::Type{T2}) = Interval{T1} 29 | promote_rule{T1<:Real, T2<:Real}(::Type{Interval{T1}}, ::Type{Interval{T2}}) = Interval{promote_type(T1,T2)} 30 | 31 | ## Domain operations 32 | ## ================= 33 | ndims(i::Interval) = 1 34 | subsumes(x::Interval, y::Interval) = y.l >= x.l && y.u <= x.u 35 | issubset(x::Interval, y::Interval) = x.l >= y.l && x.u <= y.u 36 | ⊑ = issubset 37 | 38 | isintersect(x::Interval, y::Interval) = y.l <= x.u && x.l <= y.u 39 | domaineq(x::Interval, y::Interval) = x.u == y.u && x.l == y.l 40 | 41 | @doc "Construct interval which is intersection of two intervals" -> 42 | intersect{T}(x::Interval{T}, y::Interval{T}) = Interval(max(x.l, y.l), min(x.u, y.u)) 43 | intersect{T,S}(a::Interval{T}, b::Interval{S}) = intersect(promote(a,b)...) 44 | ⊓ = intersect 45 | 46 | ## Union/Join 47 | union(a::Interval, b::Interval) = ⊔(a, b) 48 | function ⊔(a::Interval, b::Interval) 49 | l = min(a.l,b.l) 50 | u = max(a.u, b.u) 51 | Interval(l,u) 52 | end 53 | 54 | ⊔(a::Interval, b::Real) = ⊔(promote(a,b)...) 55 | ⊔(b::Real, a::Interval) = ⊔(promote(b,a)...) 56 | ⊔(a::Interval) = a 57 | ⊔(a::Vector{Interval}) = reduce(⊔,a) 58 | 59 | isequal(x::Interval,y::Interval) = domaineq(x,y) 60 | isrelational(::Type{Interval}) = false 61 | 62 | isabstract{T<:Real}(c::Type{T}, a::Type{Interval{T}}) = true 63 | 64 | ## Interval Arithmetic and Inequalities 65 | ## ==================================== 66 | 67 | # ==, != return values in AbstractBool 68 | function ==(x::Interval, y::Interval) 69 | if x.u == y.u == x.l == y.l t 70 | elseif isintersect(x,y) tf 71 | else f end 72 | end 73 | 74 | !=(x::Interval,y::Interval) = !(==(x,y)) 75 | 76 | ==(x::Interval,y::Real) = ==(promote(x,y)...) 77 | ==(y::Real,x::Interval) = ==(promote(y,x)...) 78 | 79 | !=(x::Interval, y::Real) = !==(x,y) 80 | !=(y::Real, x::Interval) = !==(y,x) 81 | 82 | >(x::Interval, y::Interval) = if x.l > y.u t elseif x.u <= y.l f else tf end 83 | >(x::Interval, y::Real) = if x.l > y t elseif x.u <= y f else tf end 84 | >(y::Real, x::Interval) = if y > x.u t elseif y <= x.l f else tf end 85 | 86 | <(x::Interval, y::Interval) = y > x 87 | <(x::Interval, y::Real) = y > x 88 | <(y::Real, x::Interval) = x > y 89 | 90 | <=(x::Interval, y::Interval) = !(x > y) 91 | >=(x::Interval, y::Interval) = !(x < y) 92 | <=(x::Interval, y::Real) = !(x > y) 93 | <=(y::Real, x::Interval) = !(y > x) 94 | 95 | >=(x::Interval, y::Real) = !(x < y) 96 | >=(y::Real, x::Interval) = !(y < x) 97 | 98 | +(x::Interval, y::Interval) = Interval(x.l + y.l, x.u + y.u) 99 | -(x::Interval, y::Interval) = Interval(x.l - y.u, x.u - y.l) 100 | +(x::Interval, y::Real) = Interval(x.l + y, x.u + y) 101 | +(y::Real, x::Interval) = x + y 102 | +(x::Interval) = x 103 | -(x::Interval, y::Real) = Interval(x.l - y, x.u - y) 104 | -(y::Real, x::Interval) = Interval(y - x.l, y - x.u) 105 | -{T}(x::Interval{T}) = zero{T} - x 106 | *(x::Interval, y::Real) = Interval(x.l * y, x.u * y) 107 | *(y::Real, x::Interval) = x * y 108 | 109 | sqrt(x::Interval) = Interval(sqrt(x.l), sqrt(x.u)) 110 | 111 | # CODEREVIEW: Generalise to even powers 112 | function sqr(x::Interval) 113 | a,b,c,d = x.l * x.l, x.l * x.u, x.u * x.l, x.u * x.u 114 | Interval(max(min(a,b,c,d),0),max(a,b,c,d,0)) 115 | end 116 | 117 | function *(x::Interval, y::Interval) 118 | a,b,c,d = x.l * y.l, x.l * y.u, x.u * y.l, x.u * y.u 119 | Interval(min(a,b,c,d),max(a,b,c,d)) 120 | end 121 | 122 | # is c inside the interval 123 | # CODREVIEW: TESTME 124 | in(c::Real, y::Interval) = y.l <= c <= y.u 125 | 126 | # CODREVIEW: TESTME 127 | inv(x::Interval) = Interval(1/x.u,1/x.l) 128 | 129 | # Ratz Interval Division 130 | # CODREVIEW: TESTME 131 | function /(x::Interval, y::Interval) 132 | a,b,c,d = x.l,x.u,y.l,y.u 133 | if !(0 ∈ y) 134 | x * inv(y) 135 | elseif (0 ∈ x) 136 | Interval(-Inf,Inf) 137 | elseif b < 0 && c < d == 0 138 | Interval(b/c,Inf) 139 | elseif b < 0 && c < 0 < d 140 | Interval(-Inf,Inf) 141 | elseif b < 0 && 0 == c < d 142 | Interval(-Inf,b/d) 143 | elseif 0 < a && c < d == 0 144 | Interval(-Inf,a/c) 145 | elseif 0 < a && c < 0 < d 146 | Interval(-Inf,Inf) 147 | elseif 0 < a && 0 == c < d 148 | Interval(a/d, Inf) 149 | else 150 | Inf 151 | end 152 | end 153 | 154 | /(c::Real, x::Interval) = convert(Interval,c) / x 155 | /(x::Interval, c::Real) = x / convert(Interval,c) 156 | ## Rationals 157 | //(x::Interval, y::Interval) = x / y 158 | //(x::Interval, c::Real) = x / c 159 | //(c::Real, x::Interval) = c / x 160 | 161 | ## Functions on Interval type 162 | ## ========================== 163 | 164 | unit{T}(::Type{Interval{T}}) = Interval{T}(zero(T), one(T)) 165 | unit{T}(::Interval{T}) = Interval{T}(zero(T), one(T)) 166 | 167 | ## It's all Ones and Zeros 168 | zero(::Type{Interval}) = Interval(0.0,0.0) 169 | one{T}(::Type{Interval{T}}) = Interval(one(T)) 170 | one{T}(::Interval{T}) = Interval(one(T)) 171 | zero{T}(::Type{Interval{T}}) = Interval(zero(T)) 172 | zero{T}(::Interval{T}) = Interval(zero(T)) 173 | 174 | ## Functions on interval abstraction itself 175 | ## ======================================= 176 | reflect(x::Interval) = Interval(-x.l,-x.u) 177 | makepos(x::Interval) = Interval(max(x.l,0), max(x.u,0)) 178 | mid(x::Interval) = (x.u - x.l) / 2 + x.l 179 | 180 | ## Non primitive functions 181 | ## ======================= 182 | function abs(x::Interval) 183 | if x.l >= 0.0 && x.u >= 0.0 x 184 | elseif x.u >= 0.0 Interval(0,max(abs(x.l), abs(x.u))) 185 | else makepos(reflect(x)) 186 | end 187 | end 188 | 189 | round(x::Interval) = Interval(round(x.l), round(x.u)) 190 | 191 | function isinf(x::Interval) 192 | if isinf(x.l) || isinf(x.u) 193 | x.u == x.l ? T : t 194 | else 195 | F 196 | end 197 | end 198 | 199 | function isapprox(x::Interval, y::Interval; epsilon::Real = 1E-5) 200 | ifelse(isinf(x) | isinf(y), x == y, abs(x - y) <= epsilon) 201 | end 202 | 203 | isapprox(x::Interval, y::Real) = isapprox(promote(x,y)...) 204 | isapprox(x::Real, y::Interval) = isapprox(promote(x,y)...) 205 | 206 | ## Vector Interop 207 | ## ============== 208 | l{T<:Real}(v::Vector{T}) = v[1] 209 | u{T<:Real}(v::Vector{T}) = v[2] 210 | l(x::Interval) = x.l 211 | u(x::Interval) = x.u 212 | pair{T}(::Type{Interval{T}},low,up) = Interval(low,up) 213 | pair(::Type{Vector{Float64}},low,up) = [low,up] 214 | Pair = Union(Vector{Float64},Interval) 215 | 216 | ## Splitting 217 | ## ========= 218 | function split_box{P<:Pair}(i::P, split_point::Float64) 219 | @assert l(i) <= split_point <= u(i) "Split point must be within interval" 220 | # @assert l(i) != u(i) "Can't split a single point interval into disjoint sets" 221 | 222 | if l(i) == u(i) #Degenrate case 223 | P[pair(P, l(i), u(i)), pair(P, l(i), u(i))] 224 | elseif split_point < u(i) 225 | P[pair(P, l(i), split_point), pair(P, nextfloat(split_point), u(i))] 226 | else 227 | P[pair(P, l(i), prevfloat(split_point)), pair(P, split_point, u(i))] 228 | end 229 | end 230 | 231 | mid_split(i::Interval) = split_box(i,mid(i)) 232 | 233 | # Split along the middle n times 234 | function mid_split(i::Interval, n::Int64) 235 | A = [i] 236 | for i = 1:n 237 | res = Interval[] 238 | for a in A 239 | splitted = mid_split(a) 240 | push!(res,splitted[1],splitted[2]) 241 | end 242 | A = res 243 | end 244 | A 245 | end 246 | 247 | ## Sampling 248 | ## ======== 249 | rand{T<:FloatingPoint}(x::Interval{T}) = x.l + (x.u - x.l) * rand(T) 250 | rand{T<:Integer}(x::Interval{T}) = rand(UnitRange(x.l,x.u)) 251 | rand{T<:Real}(x::Interval{T},n::Int) = T[rand(x) for i = 1:n] 252 | 253 | ## Print 254 | ## ===== 255 | string(x::Interval) = "[$(x.l) $(x.u)]" 256 | print(io::IO, x::Interval) = print(io, string(x)) 257 | show(io::IO, x::Interval) = print(io, string(x)) 258 | showcompact(io::IO, x::Interval) = print(io, string(x)) 259 | --------------------------------------------------------------------------------