IsApprox implements an interface for applying different definitions of "approximate" in tests for approximate (or exact) equality.
It is also fun and hip.
Design requirements of IsApprox are:
-
It should provide a drop-in replacement for (and extend)
isapproxas well as several application functions, such asisoneandissymmetric. In particular, many functions that currently check for a property exactly (to machine precision) will instead useIsApproxto implement both exact and approximate comparison. -
Replacements of existing methods (eg.
isone(::Float64)) must incur no run-time penalty. In practice, this means specifying the notion of "approximate" via types, egEqualandApproxso that the compiler inlines the comparison code.
See this Jupyter notebook for examples. See also the test suite.
For some applications, LinearAlgebra wants to know if a matrix is exactly
Hermitian. Quantum information packages, on the other hand, might want to know
if a matrix is approximately (or exactly) Hermitian. Furthermore, many functions that check
whether a property (approximately) holds are interdependent. For example
isdiag calls functions that eventually call iszero. And isposdef calls
ishermitian. Furthermore again, one might want to check approximate equality
in norm; or elementwise. One might want to specify a tolerance and have it
propagate. In practice, packages
tend to reimplement tests in ways that do not satisfy all these criteria,
and fail to be composable.
Such packages include QuantumInformation(
code example)
,
QuantumInfo(
code example),
and
Yao(
code example).
Clearly, a general interface for approximate
equality is needed.
IsApprox allows users to specify different definitions of
closeness, via a zero-cost abstraction. That is, specifying the definition of closeness
need not incur a run-time cost. The code that implements tests for properties such as
symmetry or positivity may then be somewhat decoupled from the specification of
closeness. Furthermore, a simple, small, collection of closeness measures should be
adequate for the vast majority of use cases.
Four subtypes of AbstractApprox are included, Equal, Approx, EachApprox, and UpToPhase.
IsApprox implements the interface at least partially for each of: isone, iszero, ishermitian, issymmetric,
isreal, isinteger, istriu, istril, isbanded, isdiag, isposdef,
ispossemidef, isunitary, isinvolution, isnormalized, isprobdist.
Consider ishermitian.
-
ishermitian(A)or equivalentlyishermitian(A, Equal())demands exact equality. This implementation and the function of the same name inLinearAlgebralower to the same code. That is, theIsApproxinterface adds no performance penalty. -
ishermitian(A, Approx(kws...))has the same semantics asBase.isapprox. In this case, we test thatAis close to Hermitian in some norm. In this case, a separate code path is required, namely
ishermitian(A::AbstractMatrix, approx::Approx) = isapprox(approx, A, adjoint(A))ishermitian(A, EachApprox(kws...)).EachApproxspecifies element-wise closeness. IfAis not close to Hermitian, this test is much faster thanApproxbecause only order1elements must be tested. This implementation shares a code path with that forEqual.
AbstractApprox, Equal, Approx, UpToPhase, and EachApprox are exported.
This extends Base.isapprox with methods that take an initial argument of type AbstractApprox.
The application functions below take an optional argument of type AbstractApprox in the final
position and (may) forward this argument to isapprox.
These are not exported, and do not extend the Base and LinearAlgebra functions of the same names.
They take an optional final argument of type AbstractApprox. They are not exported because they
would overwrite existing definitions. However, the AbstractApprox interface could be moved into
Base.
There are also functions, which are exported, that are in neither Base nor the standard library, such
as IsApprox.isunitary. These follow the parameter ordering and calling conventions
as IsApprox.isone, etc.
This package will probably try to follow the Blue Style Guide.
An important rule is broken immediately: predicates are written isprop rather than is_prop.
And ispropmod1mod2 rather than is_prop_mod1_mod2. The main reason is that some of these
functions exist by the same name in Base. And some are very closely related.