JuliaApproximation / ContinuumArrays.jl

A package for representing quasi arrays with continuous indices
MIT License
27 stars 6 forks source link

Design of package #1

Closed dlfivefifty closed 5 years ago

dlfivefifty commented 5 years ago

Isn’t a FunctionSpace (almost by definition) just a Space with evaluation defined (that respects the linearity of the space)?

It almost feels like Fun is misnomer, as the space could be something other than a FunctionSpace. Perhaps its more general to do the following:

# In FunctionSpaces.jl
struct Vec{S<:Space, T, VT}
   space::S
   coefficients::VT
end

# In ApproxFun.jl
const Fun{S, T, VT} = Vec{S<:FunctionSpace, T, VT}
MikaelSlevinsky commented 5 years ago

Why not start with

abstract type Space{T} end
abstract type VectorSpace{T} <: Space{T} end
abstract type NormedVectorSpace{T} <: VectorSpace{T} end
abstract type HilbertSpace{T} <: NormedVectorSpace{T} end

Then, the vector spaces are all linear, and a space may provide evaluation, a norm, and an inner product and bilinear form.

The catch is what to do about finite-dimensional vector spaces (e.g. AbstractArrays). Traits would include them wrapper-free.

Also, how would this framework deal with isometries (between \ell2(\N) and 2-normalized Legendre coefficients, say)?

MikaelSlevinsky commented 5 years ago

Also, https://github.com/eschnett/VectorSpaces.jl

dlfivefifty commented 5 years ago

Good point: these are traits, not inheritance: A space can either be a normed space (NormStyle), a function space (EvaluationStyle) or both.

I think this is the key: adjectives are traits, nouns are types. So Cat() isa Mammal while AppearanceStyle(Cat()) == UglyStyle()

MikaelSlevinsky commented 5 years ago

That's true: the inheritance I described is just one possibility (that works for complete orthonormal bases).

dlfivefifty commented 5 years ago

I was thinking LinearSpaces.jl might be another name, though it’s probably too generic.

PS here’s another one https://github.com/anj1/AffineSpaces.jl/blob/master/README.md

dlfivefifty commented 5 years ago

Also, how would this framework deal with isometries (between \ell2(\N) and 2-normalized Legendre coefficients, say)?

I don't think that makes sense: they are just different spaces.

daanhb commented 5 years ago

What is the T in Space{T}? Functions involve two types, since each element of a function space presumably maps something of type S to something of type T. I'd go for FunctionSpace{S,T}. One could have S = SVector{3,Float64} and T=Float64, for example.

Also, ideally, I'd like to distinguish between a space and the span of some basis for that space. By analogy to DomainSets.jl, where domains are equal if they describe the same set, functions are equal if they have the same outcome for the same input. Hence, the function x::S -> zero(T) is the zero element of FunctionSpace{S,T}, always. Bases, along with finite or infinite vectors of coefficients, are one step further down the road. A space could be defined by the span of some basis, for example.

Disclaimer, that is exactly what I did here in BasisFunctions.jl. I haven't really used these spaces though.

daanhb commented 5 years ago

I guess it depends on what the aim of the package is, of course. In a package called FunctionSpaces.jl, I would expect to see L^2(Omega) where Omega is some domain.

dlfivefifty commented 5 years ago

Here's a crazy thought: a basis is an operator from (possibly infinite) vectors (EuclideanSpace?) to a space:

struct ChebyshevBasis{T,D} <: Operator{T} 
    d::D
end
domainspace(::ChebyshevBasis{T}) where T = EuclideanSpace{T}(∞)
rangespace(b::ChebyshevBasis) = Chebyshev(b.d)

Then functions are lazy multiplication of this operator:

f = Mul(ChebyshevBasis(), Vcat([1,2,3], Zeros(∞)))
dlfivefifty commented 5 years ago

I would expect to see L^2(Omega) where Omega is some domain

Chebyshev() could be code for 1/sqrt(1-x^2) weighted L^2 on [-1,1]. Though in terms of the basis, it all depends on the decay: if the coefficients are finite vectors, then the space is really PolynomialSpace(), if the coefficient tail is (1:∞).^(-1) which are in l^2, then the space is L^2...

daanhb commented 5 years ago

Bases as operators are not so crazy: it is the synthesis operator! I have an example too in BasisFunctions here. Again, experimental code, rarely ever used so far. It was meant to bring the code closer to a formal mathematical description of sampling.

daanhb commented 5 years ago

For completeness, in my experience, the spaces I mentioned don't do much. It is somewhat nice to have a notion of L^2. For example, if FunctionSpace{S,T} has complex types, then this conveys some information: you can preallocate complex floats here and there. But the real work is of course indeed in the bases, the associated vector spaces, and the operators that act on them. That might as well be the goal here, else the package will be fairly empty.

MikaelSlevinsky commented 5 years ago

This is also a good time to rethink norm(f::Fun{Chebyshev}) as implementing the L2([-1,1], dx) norm with Lebesgue measure. Arguably, orthogonal polynomial spaces should implement the norm with the orthogonality measure.

With finite-dimensional coefficients, this doesn't matter. But with InfiniteArrays.jl, there is the possibility of summing sequences of coefficients whose weighted squares have a convergence trait, say the p-test.

dlfivefifty commented 5 years ago

That ones harder than it sounds. if I were to type

f = Fun(x -> exp(x), 0..1)

I'd expect norm(f) to return Lebesgue over 0..1. If I were to type

g = Fun(x -> exp(x), Chebyshev(0..1))

then maybe it would be fine for norm(g) to give the weighted norm. But what about norm(f+g)???

MikaelSlevinsky commented 5 years ago

Maybe that's your expectation, but I'd expect the norm to depend on the basis. It's a design choice that the default constructor is Chebyshev.

With the proper help text, norm can be consistent, and the user can do norm(Fun(f, Legendre(0..1))) or even norm(Fun(f, Fourier(PeriodicInterval(0..1))))!

MikaelSlevinsky commented 5 years ago

We could always change the default segment constructor to Legendre :trollface:.

MikaelSlevinsky commented 5 years ago

This is also complicated by considering other p-norms. Should the norm(::Fun{Chebyshev}, Inf) be a weighted infinity-norm?

dlfivefifty commented 5 years ago

That’s what I suggested in my draft post...

The issue is that Space and Basis are really two different things. For finite coefficients the space is really PolynomialSpace regardless of the basis. So using the basis to determine the norm is wrong.

I don’t know the right way to set it up, but to infer a space requires both knowledge of the basis and the (tail of the) coefficients.

To me for norm the following makes sense:

norm(f, Chebyshev()) # weighted norm, independent of the basis for f
norm(f) # either errors, or is Lebesgue on domain(f). 

PS some history: ApproxFun.jl was originally named Funs.jl, until when trying to register Stefan suggested calling it ChebyshevFunctions.jl or ChebFun.jl 🤢. I was on a flight and quickly added support for Fourier to avoid those suggested names. I don’t remember why I used space instead of basis, probably was just because it sounded better.

MikaelSlevinsky commented 5 years ago

The idea of distinguishing space from basis is growing on me. (Being used to the ApproxFun ecosystem, I was accustomed to the idea of a "Chebyshev" approximation space, but even that's not clear: with one summability condition it could represent continuous functions, and another condition leads to weighted square integrable. None of this mattered for finite-dimensional coefficients.) I suppose this repository would be for things like Lebesgue, Sobolev/BesselPotential, and Continuous{n} as examples of normed vector spaces along with bases (or perhaps bases should be in Bases.jl or Daan's BasisFunctions.jl)

Cheers,

Mikael

On Oct 20, 2018, at 1:54 PM, Sheehan Olver notifications@github.com<mailto:notifications@github.com> wrote:

That’s what I suggested in my draft post...

The issue is that Space and Basis are really two different things. For finite coefficients the space is really PolynomialSpace regardless of the basis. So using the basis to determine the norm is wrong.

I don’t know the right way to set it up, but to infer a space requires both knowledge of the basis and the (tail of the) coefficients.

To me for norm the following makes sense:

norm(f, Chebyshev()) # weighted norm, independent of the basis for f norm(f) # either errors, or is Lebesgue on domain(f).

PS some history: ApproxFun.jl was originally named Funs.jl, until when trying to register Stefan suggested calling it ChebyshevFunctions.jl or ChebFun.jl 🤢. I was on a flight and quickly added support for Fourier to avoid those suggested names. I don’t remember why I used space instead of basis, probably was just because it sounded better.

— You are receiving this because you commented. Reply to this email directly, view it on GitHubhttps://github.com/JuliaApproximation/FunctionSpaces.jl/issues/1#issuecomment-431608811, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AHzBpZmRm1_DrrGyITGF2AwtnWonl0gkks5um3F9gaJpZM4Xx3yp.

MikaelSlevinsky commented 5 years ago

Here are two options:

  1. A Fun consists of coefficients in a basis (two fields). Functions like norm and bilinearform dispatch based on typeof(coefficients), typeof(basis) and a user-supplied Space, e.g. L2 with the usual measure.

  2. A Fun in an approximation space consists of coefficients in a basis (three fields). Functions like norm and bilinearform dispatch completely based on typeof(f).

Other questions linger:

MikaelSlevinsky commented 5 years ago

Another big question: What is a Fun?

Why is the current Fun all the way down the hierarchy of islinear-hasnorm-hasbasis?

A Fun could conceivably hold more generic fields to describe the approximation data, like rational or exponential models. In this context, RatFun for example is really just an alias.

dlfivefifty commented 5 years ago

There's nothing forcing the expansion to be a basis, really it's just a quasi-matrix multiplication.

A Fun is an expansion in a linear space. Getting rid of this results in a type with very little commonality.

Having slept on it I think the first version should just be a cleaned up version of what's already in ApproxFun. We can refactor later, but there are too many possibilities, and very little improvement results from these changes.

One change I might go through with: instead of storing coefficients as a vector [1,2,3], they would be stored in infinite-dimensional form:

Vcat([1,2,3], Zeros(∞))

This has no increased storage and can still be used as before via: instead of cfs = coefficients(f) one would use

cfs,_ = coefficients(f).arrays

But it gets rid of pretty much all k ≤ ncoefficients(f) ? ... : ....

daanhb commented 5 years ago

Infinite arrays are a big assumption to make on the space, though it indeed looks like a neat way to express the algorithms of ApproxFun. It makes sense in a setting where there is a strictly nested sequence of subspaces, where in addition there is an infinite basis that can be truncated to yield a basis for any subspace. This is true in the setting of spectral methods.

More generally, for function approximation problems, there is a sequence of finite-dimensional subspaces that are ultimately dense in an infinite-dimensional space. Each of those subspaces has its own basis, and there are linear operators to convert (approximately) between the corresponding expansions. Think of finite elements, for example, where the conversions could be prolongation and restriction, or refinement and coarsening. Ideally, we can support this more general setting too.

daanhb commented 5 years ago

From our discussion, it currently seems like a Fun has three properties: a set of coefficients, a linear space which it is an element of, and a "basis" (which we now think of as a map from the coefficients to the actual element of that space, and it needs a different name). The Fun doesn't necessarily store these three explicitly, but you can ask about them. Makes sense? Sounds much like @MikaelSlevinsky's option 2 above.

If a Fun does become part of this package, what might be neat and useful is a way to simply make linear combinations of Fun's with some sanity checks: you can add Fun's only if they are in the same space, for example, or if they can easily be converted. Anybody out there who has ever overloaded + and * to essentially add vectors hidden inside some expansion type could benefit from the framework.

dlfivefifty commented 5 years ago

what might be neat and useful is a way to simply make linear combinations of Fun's with some sanity checks: you can add Fun's only if they are in the same space

You mean exactly like what's in ApproxFun already?

Infinite arrays are a big assumption to make on the space

Sorry, I meant pad(cfs, dimension(space)) . This would return Vcat(cfs, Zeros(∞)) if dimension(space) == ∞, otherwise it would just pad to the dimension.

Note ApproxFun already supports finite dimensional spaces, and includes splines and a few others. What would be needed for FEM would be a family of spaces, this fits better as something on top of spaces.

daanhb commented 5 years ago

what might be neat and useful is a way to simply make linear combinations of Fun's with some sanity checks: you can add Fun's only if they are in the same space

You mean exactly like what's in ApproxFun already?

For the most part, yes, like spacescompatible. If you can separate the descriptive part of spaces and expansions from the approximation algorithms in ApproxFun, I think it could be useful for others. I'd use it.

I still think of L^2 when I hear FunctionSpaces.jl, and perhaps a way to declare that one space is embedded into another one. Throw in an abstract Fun object and I hear ApproximationSpaces.jl.

MikaelSlevinsky commented 5 years ago

To continue refining the ideas above, one option is to rename the package VectorSpaces.jl and define:

struct Vec{VSet, VSpace, T, VType}
    set::VSet
    space::VSpace
    coefficients::VType
end

There would be a reasonable alias for a Fun based on FunctionSet <: VectorSet and FunctionSpace <: VectorSpace.

Two more traits to consider are iscomplete and isspannedby. For example,

iscomplete(::Lebesgue{1}) = true # Lebesgue{1} is printed as a BanachSpace rather than an NormedVectorSpace

and

isspannedby(L::Lebesgue{2,D<:Domain}, set::Legendre{D<:Domain) where D = true
dlfivefifty commented 5 years ago

With trait-like design it's no longer the case that everything needs to be a templated out: the space can be inferred from set and coefficients.

I would design it like this, where a function set is represented by a quasi-matrix, that is, an operator, and expansions in a basis are just lazy multiplication. Here I show how it would work even for infinite vectors of coefficients:

abstract type AbstractArrayFunction{T,N} end # represents array-valued functions
AbstractVectorFunction{T} = AbstractArrayFunction{T,1}
struct Legendre{T} <: VectorFunction{T} end # Legendre()(0.3) is equal to the infinite-vector `[P_0(x), P_1(x), …]
length(::Legendre) = ∞

const Fun{BASIS, T, VT} = Mul{T, BasisStyle, <:Any, Adjoint{T,BASIS <: AbstractVectorFunction}, VT<:AbstractVector}

struct AlgebraicGrowth{BASIS} <: FunctionSpace
    vset::BASIS
    rate::Float64
end
struct UnknownGrowth{BASIS} <: FunctionSpace
   vset::BASIS
end

const L2 = Sobolev{2,0}

issubset(sp::AlgebraicGrowth{<:Legendre}, ::L2) = sp.rate < -0.5
issubset(sp::AlgebraicGrowth{<:Legendre}, ::Sobolev{2,R}) where R = sp.rate < -0.5-R

coefficients(f::Fun) = f.B
space(f::Fun) = _space(f.A', f.B)
_space(sp::Legendre, cfs::Vcat) = _space(sp, last(cfs.arrays)) # the tail determines everything

_space(sp::Legendre, ::Zeros) = PolynomialSpace(ChebyshevInterval())
_space(sp::Legendre, ::AbstractFill) = AlgebraicGrowth(sp, 0)
_space(sp::Legendre, ::InfRange) = AlgebraicGrowth(sp, 1)
function _space(sp::Legendre, cfs::Broadcasted{<:Any,<:Any,typeof(^),Tuple{<:InfRange,<:Integer}) 
    _, p = cfs.args
    AlgebraicGrowth(sp, p)
end
_space(sp::Legendre, _) = UnknownGrowth(sp)

## Tests
x = 0.3
P = Legendre()
@test isinf(P)
@test isinf(P(x)) # lazily evaluated
@test P(x)[1:3] == legendrep.(1:3, x)

f = Mul(P', Vcat([1,2,3], Zeros(∞))) # represent P_0(x) + 2P_1(x) + 3P_2(x)
@test f(x) == P(x)[1:3]'*coefficients(f)[1:3]
@test space(f) == PolynomialSpace(ChebyshevInterval())

g = Mul(P',(1:∞).^(-1))
@test space(g) ⊆ L2(ChebyshevInterval())
@test norm(g) == sqrt(π^2/6)
@test_throws InexactError g(0.1) # Evaluation undefined

h = Mul(P',(1:∞).^(-2))
@test space(h) ⊆ L2(ChebyshevInterval())
@test space(h) ⊆ H1(ChebyshevInterval())
@test h(0.1) == ? # requires infinite-dimensional Clenshaw with the prescribed tail (Too Easy.)
MikaelSlevinsky commented 5 years ago

There are other uses for a function space other than looking at coefficient tails. Ex: the constructor (even if the result is finite-dimensional). In this sense, the space could give you historical information about the Fun. I don't know if this merits a field in the type, but something to think about.

MikaelSlevinsky commented 5 years ago

Another way to store a Fun (with some acknowledgeable disadvantages) is through its domain of definition, and equations it satisfies. For example:

f = Fun(exp, -1..1)
f.domain == -1..1
f.equations == [Evaluation(Point(1.0)) - exp(1.0); Derivative(-1..1) - I]
dlfivefifty commented 5 years ago

There are other uses for a function space … Ex: the constructor

This is really a property of the basis, and the finite constructor could be written as:

x = ChebyshevGrid(-1..1) # lazy chebyshev points
V = P[1:n].(x) # Vandermonde matrix, where the rules of broadcast for `VectorFunction`s are defined to return an operator. So this would be an operator from expansions in P[1:n] to functions evaluated on the Chebyshev grid

f = V \ exp.(x) # returns Mul(P[1:n]', AbstractMatrix(V) \ Vector(exp.(x)))

Another way to store a Fun (with some acknowledgeable disadvantages) is through its domain of definition, and equations it satisfies

Indeed, which can be done with lazy Ldiv:

f = Ldiv([Evaluation(Point(1.0)); Derivative(-1..1) - I], [exp(1.0); 0])
MikaelSlevinsky commented 5 years ago

If the function space is dictated by traits acting on the coefficients (software-wise), then how will this framework be wide enough to include the FrameFun package? In many of the same function spaces philosophically shared with ApproxFun, the generated expansions in certain function sets in FrameFun may not have any decay at all.

Even in ApproxFun, this limits the use of function sets, e.g. two orthonormal bases spanning the same Hilbert space, to direct-sum construction (via or ).

dlfivefifty commented 5 years ago

In FrameFun they are finite dimensional, so the space would be Span(basis)

dlfivefifty commented 5 years ago

Actually functions don’t have spaces, anymore than the number 0.3 has a domain. Functions are in spaces, and can be in multiple spaces.

MikaelSlevinsky commented 5 years ago

Good point.

daanhb commented 5 years ago

Right, and verifying membership numerically will be difficult in general, even if coefficients do decay - which in the case of frames they might not even do.

At one point in our code we tried to make everything revolve around spaces rather than bases (we only used the span of a truncated basis as a space), but that was a disaster. It led to repeated wrapping and unwrapping of the basis: the basis really is the workhorse that captures the semantics of the coefficients.

dlfivefifty commented 5 years ago

I think functional analysis is not the right language for concrete realisations on a computer. Spaces and operators in the mathematical sense are not actually useful.

I think the right notions are quasi-matrices (to represent bases), which multiply vectors and return functions. Essentially analogous to the old RowVector.

We may also need adjoint of functions (bra-ket) to express analysis and synthesis, and thereby conversion, but I’ll have to think some more.

dlfivefifty commented 5 years ago

Right, and verifying membership numerically will be difficult in general, even if coefficients do decay

Not true: using interval arithmetic and automatic differentiation (or evaluation in the complex plane) one can rigorously and automatically determine decay rates of the tail.

MikaelSlevinsky commented 5 years ago
g = Mul(P',(1:∞).^(-1))
@test space(g) ⊆ L2(ChebyshevInterval())
@test norm(g) == sqrt(π^2/6)

Imagine dispatching to zeta, hehe

dlfivefifty commented 5 years ago

Let's say we thought in terms of Bra-Ket notation, so that functions are kets and functionals are bras. Then we have that quasi-matrices are row-vectors of kets. Their adjoint is a column-vector of bras.

Then the following could represent conversion:

abstract type QuasiVector{T} end # Vector of bras
const QuasiMatrix{T} = Transpose{T, <:QuasiVector{T}} # Quasi-matrices are represented by transpose

# adjoint(::QuasiMatrix) gives a row-vector of bras

struct Jacobi{T} <: QuasiVector{T} 
   a::T
   b::T
end

 struct Legendre{T} <: QuasiVector{T} end
S = transpose(Legendre()) # The synthesis operator, row vector of kets
A = adjoint.(Legendre()) # The analysis operator, Column vector bras

@test A*S == Eye(∞)
@test S*A == IdentityOperator()

f =  S * Vcat([1,2,3],Zeros(∞)) # in practice a lazy Mul

@test A * f == Vcat([1,2,3],Zeros(∞))

M = adjoint.(Jacobi(1,1)) * S  # Conversion matrix from Legendre to Jacobi coefficients, whose entries are inner products. In practice, represented lazily with entries determined by exact formula
@test M isa AbstractMatrix
@test size(M) == (∞,∞)
C = transpose(Jacobi(1,1)) * M * A # Conversion operator

@test (C*f)(0.1) == f(0.1)

EDIT: Actually, just make bases quasi matrices:

abstract type QuasiMatrix{T} end # row vector of bras
# adjoint(::QuasiMatrix) gives column vector of kets

struct Jacobi{T} <: QuasiMatrix{T} 
   a::T
   b::T
end

 struct Legendre{T} <: QuasiVector{T} end
S = Legendre() # The synthesis operator, row vector of kets
A = adjoint(Legendre()) # The analysis operator, Column vector bras

@test A*S == Eye(∞)
@test S*A == IdentityOperator()

f =  S * Vcat([1,2,3],Zeros(∞)) # in practice a lazy Mul

@test A * f == Vcat([1,2,3],Zeros(∞))

M = adjoint(Jacobi(1,1)) * S  # Conversion matrix from Legendre to Jacobi coefficients, whose entries are inner products. In practice, represented lazily with entries determined by exact formula
@test M isa AbstractMatrix
@test size(M) == (∞,∞)
C =Jacobi(1,1) * M * A # Conversion operator

@test (C*f)(0.1) == f(0.1)
daanhb commented 5 years ago

I think functional analysis is not the right language for concrete realisations on a computer. Spaces and operators in the mathematical sense are not actually useful.

What did you originally have in mind for this FunctionSpaces.jl package? A way to formally describe spaces, or a generic way to represent function approximations like we are discussing now? (Either way, whatever the outcome here, by January I'll start integrating more with ApproxFun or with this package.)

daanhb commented 5 years ago

I was going to say "let's not invent new terminology" but a QuasiMatrix (or QuasiArray?) is starting to make sense. One (or more?) of the dimensions is simply continuous. There is no assumption in the terminology whatsoever on the continuous dimension being a basis, a frame or a "dictionary". A function can be a linear combination over the discrete dimensions of the QuasiArray.

The analogy with row and column vectors is lost if both the function set and the coefficients have tensor product structure. An expansion is like a dot-product of the quasi-matrix and the coefficient matrix, and then a sum over them. What kind of operation is that? And is it fair to say that a QuasiMatrix has as many continuous dimensions as it has discrete ones? That is the case at least with a tensor product basis: indices i and j and variables x and y, say.

dlfivefifty commented 5 years ago

Quasi-matrix is an established Trefethenism, not a new term

marcusdavidwebb commented 5 years ago

I like the idea that a basis is simply a QuasiMatrix or QuasiArray. One thing that bugs me is that a function space does not depend on the ordering of the basis, but a QuasiVector has a built in ordering. Is the function space of a polynomial basis whose indices go 0,1,2,.. as usual the same as that with the first two basis elements switched? More pertinently, the coefficients of a bivariate function can be indexed in an array in several ways, or made into a one-dimensional vector in many ways. Are all of these function spaces different?

daanhb commented 5 years ago

It would have to be the case that the iteration over a quasi matrix happens in the same order as the iteration over any set of coefficients.

The span of two permuted bases is the same and we should treat it as such. However, the expansion in a particular basis is specific to the ordering of that basis. The fact that the spaces are the same only means that the expansion in one basis can be converted to the other basis. In this case, the conversion would be a permutation? Is that satisfactory?

daanhb commented 5 years ago

Quasi-matrix is an established Trefethenism, not a new yerm

For reference quoting the link here to quasi-matrices in Chebfun. Nick Trefethen also cites Stewart for the name. The name didn't really catch on so far, but the concept really seems to makes sense as an implementation strategy in Julia.

daanhb commented 5 years ago

An AbstractArray{T,N} has two type parameters: the dimension and the element type. This implies in the case where N=2 that a[i,j] has type T. In particular, the array does not prescribe the type of the indices.

Does this mean that a possible full signature is QuasiArray{T,M,N}? It means in case M=3 and N=2 that a[i,j](x,y,z) has type T. In other words, T is the type of the codomain of the functions, and the type of the domain is not part of the signature. And the type of a[:,2](:,2.0,:) is QuasiArray{T,1,2}?

dlfivefifty commented 5 years ago

Suppose Vec is a type that denotes a linear vector space: this could be functions or anything else. Suppose their shape in Julia is scalar (like functions). Then a quasi-matrix is just an Adjoint{Vec, <:AbstractVector{Vec}} and LinearAlgebra.jl takes care of the rest.

Now Function is not a linear space, and in this setting we want multiplication to be lazy as "expansion in a basis" just means lazy multiplication of a quasi-matrix times a vector. And there's a bit of a chicken-and-egg situation: we want to represent functions as expansions in a basis, but then the elements of the basis is itself a function. So there's some more thought to be had.

In terms of span, do we need this? I think for matrices one would do a qr and check that the Qs are the same.

daanhb commented 5 years ago

As a sidenote, I'm not convinced we should aim for general vector spaces. AbstractArray itself already does this very well, I don't see what we have to offer here over Point{T} <: AbstractVector{T}. Or am I missing something?

If the multiplication of a QuasiVector with a vector of coefficients results in a Fun, that is precisely lazy multiplication: it is just grouping two objects together. Any concrete subtype of QuasiVector should know how to evaluate an entry i in a given point x. If you have a QuasiVector that represents a basis, that amounts to evaluating the basis function. One could make a concrete QuasiVector that contains a bunch of Fun's (which would correspond more closely to a quasimatrix as in Chebfun I think), then evaluation is implemented in terms of evaluating the corresponding Fun?

daanhb commented 5 years ago

@dlfivefifty Sorry, I misread your post. Your type Vec is a scalar element that possibly represents a function -- okay. You need something like that if you inherit from AbstractArray in order to say what its element type is. But we can't do that, unless Vec is something like Function (which would be abstract and slow) or if Vec itself encodes the basis. I think we don't have that problem if QuasiVector does not inherit from AbstractArray, but then the methods of LinearAlgebra.jl won't apply. Perhaps a QuasiVector can be a matrix, where the second dimension is continuous, and then we only have to tell AbstractArray what the type is of any function evaluation.

Perhaps some experimenting is called for.