An interface package for working with domains as continuous sets of elements.
Examples of domains may be geometric sets such as intervals and triangles,
or the complex plane without the negative real line. However, domains are not
limited to geometry. A domain could also be a collection of vectors or arrays,
such as the set of all orthogonal 3x3
matrices.
Existing types may add the interpretation of being a domain by implementing the domain interface. They gain the ability to interact with other domains.
A domain is a set of elements that is possibly continuous. Continuous sets are
defined mathematically, not by an exhaustive list of their elements. In practice membership of the set is defined by the implementation of in
. The function
call in(x, domain)
evaluates to true if the domain contains an element y
such that y == x
.
In principle, the function in(x, domain)
should not throw an exception even
if the types seem mathematically nonsensical. In that case, the correct return
value is false
. This mimicks the behaviour of in
for finite sets in Julia:
julia> in(rand(3,3), 1:3)
false
Indeed, a 3x3
matrix is not equal to any of the numbers 1
, 2
or 3
.
The defining mathematical condition of a continuous set might be satisfied by
variables of different types. Still, the interface defines the domaineltype
of a domain. It is a valid type for elements of the set.
Functions that generate elements of the domain should generate elements
of that type. As a consequence, for finite sets such as an AbstractArray
or AbstractSet
, the domaineltype
agrees with the eltype
of that set. For
intervals on the real line, the domaineltype
might be Float64
. When there is
no clear candidate the domaineltype
might simply be Any
.
The domain interface is formally summarised in the following table:
Required methods | Brief description |
---|---|
in(x, d) |
Returns true when x is an element of the domain, false otherwise |
DomainStyle(d) |
Returns IsDomain() if d implements this interface |
Optional methods include:
Important optional methods | Default definition | Brief description |
---|---|---|
domaineltype(d) |
eltype(d) |
Returns a valid type for elements of the domain |
Several extensions of this minimal interface are defined in the DomainSets.jl package.
A domain behaves as much as possible like the mathematical set it represents, irrespective of its type. Thus, for example, two domains should be considered equal if their membership functions agree.
It is not always possible to realize this intended behaviour in practice. Indeed it may be difficult to discover automatically whether two domains are equal, especially when their types are different. Still, the principle serves as a design goal.
This package defines the abstract type Domain{T}
of continuous sets with
domaineltype
equal to T
. No concrete domain types are defined in this
package.
The package also defines the trait DomainStyle
. Any type can declare to
implement the domain interface by defining
DomainSetsCore.DomainStyle(d::MyDomain) = IsDomain()
Objects of type Number
, AbstractArray
and AbstractSet
are declared to be
domains in this package.
With the exception of subtypes of Domain
, the set of types implementing the
domain interface is not based on a common abstract supertype. Functions that are
intended to manipulate domains may simply omit the type of the domain in the
function signature. However, functions defined in other packages can not be
extended to work for domains, as there is no common type to dispatch on.
For that reason this package defines the DomainRef
reference.
In the absence of function ownership, the practice of domain references requires
active intent both by users and by developers. For a user, passing DomainRef(d)
to a function indicates that d
is to be treated as a domain. For a developer,
the implementation foo(d::DomainRef)
may be used to extend the functionality
of foo
to domains.
The reference DomainRef(d)
is not in itself a domain, it is merely a reference.
The developer of foo(d::DomainRef)
may use domain(d)
to access the domain
object. More generally, one can implement foo(d::AnyDomain)
and use
domain(d)
in the function definition. In that case the user may invoke foo
both indirectly with a domain reference and directly with a concrete subtype of
Domain
.
An example of this practice is the functionality of union
in the
DomainSets.jl package.
First, the package defines a generic function uniondomain(d1,d2)
with no
restrictions on the types of d1
and d2
. The function returns an object that
behaves as the mathematical union of the two arguments. It always interprets the arguments as domains, regardless of their types.
A user wanting to use the standard ∪
syntax for this purpose has to make this intention explicit by writing DomainRef(d1) ∪ DomainRef(d2)
. The outcome is
equivalent to uniondomain(d1, d2)
, and could be different from what d1 ∪ d2
means in other contexts for the combination of types of d1
and d2
. That
original behaviour of ∪
, quite possibly an error in fact, is not changed.
A developer may make the ∪
syntax more accessible to users as follows. As soon
as one of d1
or d2
is a Domain
, or a reference to a domain, the call to
union
can safely be interpreted as the union of domains. Thus, a developer may
write:
union(d1::Anydomain, d2) = uniondomain(domain(d1), d2)
union(d1, d2::AnyDomain) = uniondomain(d1, domain(d2))
union(d1::AnyDomain, d2::AnyDomain) = uniondomain(domain(d1),domain(d2))
These definitions are safe in the sense that there is no ambiguity and no
possible clash with any existing definition of union(d1,d2)
in other packages.
The goal of the domain interface is to make objects from different packages interoperable with minimal interaction between packages and, thus, maximal independence in their development.
Say package A and package B both define a type that can be interpreted as a
domain, respectively objectA
and objectB
. Package C may define the domain
interface for objectA
using the DomainStyle
trait. Package D may similarly
define the domain interface for objectB
. Package E could load DomainSets
and packages C and D. A user of package E can type uniondomain(objectA,objectB)
.
This construction requires no active collaboration between the developers of
packages A, B, C, D and E. They can all be developed independently. Package C
relies on package A and on DomainSetsCore
. Package D relies on package B and
on DomainSetsCore
. Package E relies on packages C, D and DomainSets
.
Packages C, D and E could be developed as package extensions.
Unions and intersections of domains, as well as many other set operations, are implemented generically in the DomainSets.jl package.
Intervals inheriting from the Domain
supertype are implemented in
IntervalSets.jl.