I’m looking around to see if there is somewhere in the ecosystem a package that would allow me to define structs with arbitrary constraints in them, through something like
@probably_a_macro struct MyStruct{T}
a::T # a must be positive
b::Vector{T} # b must belong to the simplex
C::Matrix{T} # must be a correlation matrix
end
I am looking for this to provide :
1° A unique, strict constructor that will enforce these constraints at construction
2° A (at least surjective, bijective when possible) mapping from a vector x \in R^p to my struct, with potentially the associated jacobian.
Something like that should be produced by the macro:
struct MyStruct{T}
a::T
b::Vector{T}
C::Matrix{T}
function MyStruct(a::T,b::Vector{T},c::Matrix{T}) where T
@assert is_positive(a) && is_in_simplex(b) && is_a_corr(c)
return new{T}(a,b,c)
end
end
function intrisic_dimension(::MyStruct)
return #whatever
end
function constraint(x)
@assert length(x) == intrinsic_dimension(::MyStruct)
a,b,c, = automatic_mappings(x)
return MyStruct(a,b,c)
end
others tools such as unconstrain(::Mystruct) would be nice too.
I have seen TransformVariables.jl and looked at a few others but nothing seemed to do exactly what i wanted. Is there something close to this already existing, or will i have to roll my own ?
Of course, the capacity to then construct another one :
@same_macro struct MySecond{T}
x::MyStruct{T}
a::T # shoudl be negative
end
would enlarge the intrisic dimension and enforce the conditions of the first one…
I had my clanker help me define structs with tagged fields (using empty macros) for similar things. If you don’t have access to one I can lend a hand this week.
I think that if you want to automatically enforce a natural-language constraint like this then you are not talking about a programming language or something that would be implemented by a macro.
You are talking about an AI. While it is technically possible to prompt an AI during macro execution and inject its output into the code, it seems highly inadvisable. At the very least you would want to inspect and test the AI output (and, once the code is tested, no longer prompt the AI).
Maybe your code comments were supposed to be a stand-in for some future syntax to be determined. But in that case I think you are putting the cart before the horse: if you want a programming construct (as opposed to an AI prompt), you need to precisely define the problem you are solving before you worry about syntax or implementation.
I think the question was about what could be accomplished with a language construct like a macro. There is no need for an AI agent to be used to create the constraints that the OP describes. This can be done using a macro definition and some supporting types and functions.
I didn’t find something that directly implemented your idea, although there are packages like the one you mentioned as well as Bijectors.jl which capture some of the semantics.
Feel free to take anything from this if you find it useful. Perhaps there is a package waiting to be written for this purpose. I’m not going to tackle it (at least not now) but perhaps you or someone else might take these ideas and construct a package around them. If so, feel free to use it. If it’s not accomplishing what you wanted, then I apologize if I misinterpreted your request.
My point is that the key challenge here is not the macro syntax or the code development, but on how you precisely describe the constraints of interest. The post described the constraints using natural language, and to me this punts on the core of the problem.
I interpreted those as placeholders to be implemented, because they seemed like common terms most of us would agree upon already. So I interpreted the question to be about the scaffolding which would be needed around them in order to achieve the desired syntax and semantics.
If you’d like, check out the sample notebook and see if that implementation does a good job with some concrete constraint definitions.
To me this is the interesting part. What kinds constraints do you want to implement, and why?
In particular,
A (at least surjective, bijective when possible) mapping from a vector x \in R^p to my struct, with potentially the associated jacobian.
sounds like @lrnv has a concrete application in mind that should inform the design. Are they thinking of doing optimization or something, and that’s why you want a differentiable mapping from parameter vectors to structs?
If it is for optimization, you probably need to think much more carefully about the constraint manifold and how it is expressed. For example, if you are optimizing over the set of n \times n “correlation matrices”, can you work with covariance C matrices instead, which are simply SPSD (and from which the corresponding correlation matrix can be computed via diagonal scaling, at least if it is SPD)? In that case, there are lots of approaches, like explicit positive-definite constraints in e.g. JuMP or ManOpt.jl or implicit embeddings C = A^T A in spaces of unconstrained matrices A.
And if you are optimizing over a simplex, you really want an optimizer that is aware of this feasible set (and its non-smoothness at corners)…
The comments about wanting to track the “intrinsic dimension” reinforce my impression that there is something specialized going on here regarding manifolds or optimization or something.
Worrying about syntax seems grossly premature before establishing what sort of constraints you want and for what purpose you need them, and hence what semantics need to be captured.
In you want to know what I had in mind, I ended up rolling my own sketch. Code underlying is probably not pretty, but it works. Not sure I am going to use it, but it will clearly settle your argument
Looking at your notebook right now, and yes it think we went in similar ways. I’ll read it and see if you found clever tricks that I did not
But my version is a bit more complete, permissive in terms of what you can do with it – that’s normal, you had only my discourse sketch why I had all the true application behind laying at hand.
Yes, I meant to provide just an outline/sketch of the general direction, assuming that you could fill in the gaps.
The fact that the two solutions are somewhat aligned seems like a good thing – at least at face value. A registered package that goes the extra distance and allows for very flexible constraint implementations also seems like it could be useful for others, if you end up refining it bit, perhaps you can register it and write up an announcement for the package in the future.
I am not sure that this is a well-specified problem. If you have a T <: AbstractFloat, verifying eg that something is a correlation matrix becomes tricky because of floating point error.
TransformVariables.jl can indeed provide the mapping for you. I can imagine a wrapper like this:
import TransformVariables as TV
struct Constrained{T,N<:NamedTuple}
transform::T
variables::N
function Constrained(transformation, x::AbstractVector)
variables = TV.transform(transformation, x)
new{typeof(transfrom),typeof(variables)}(transform, variables)
end
end
Base.getproperty(C::Constrained, key) = getproperty(getfield(C, :variables), key)
intrinsic_dimension(C::Constrained) = TV.dimension(getfield(C, :transformation))
unconstrain(C::Constrained) = TV.inverse(getfield(C, :transformation), getfield(C, :variables))
and so on.
I am not sure what, if anything, you would gain from a macro.
It may make sense to store the original vector x to, depending on the application and how often you need it.