[ANN] Armington.jl Constant Elasticity of Substitution aggregators in Julia

Hi all,

Announcing Armington.jl, a small package for building and evaluating arbitrarily-nested CES aggregators, trees where each internal node combines its children with a constant elasticity of substitution. See Constant elasticity of substitution - Wikipedia.

Design goals:

  • Zero allocations on evaluation. Tree topology is encoded in the type parameters, so the recursion unrolls at compile time via tuple peeling.
  • AD-friendly. T<:Real throughout, so ForwardDiff duals pass through cleanly.

Quick example:

using Armington

# Two-level Armington: domestic vs. imports (US, EU)
imports = CESNode(4.0, (0.6, 0.4), (CESLeaf(:US), CESLeaf(:EU)); name=:imports)
tree = CESNode(1.5, (0.7, 0.3), (CESLeaf(:dom), imports); name=:total)

leaf_names(tree)            # [:dom, :US, :EU]
aggregate(tree, [10, 5, 8]) # CES composite quantity
show_tree(tree)             # print nesting structure

total: CESNode(σ=1.5, α=(0.7, 0.3))
  └ dom
  └ imports: CESNode(σ=4.0, α=(0.6, 0.4))
    └ US
    └ EU

Repo: GitHub - molivov/Armington.jl: Constant Elasticity of Substitution aggregators in Julia · GitHub

Feedback is welcome.

Cheers

What’s the function of the wrapper type CESLeaf? Couldn’t the constructors just take a Symbol?

Just to keep things consistent: objects are either CESLeaf or CESNode. I guess I could add a method for a tuple of Symbol.

That sounds like exposing an implementation detail; my impression is that for the user, CESLeaf has no purpose, and CESNode could be shortened to CES, leading to an API not unlike

imports = CES(4.0, (0.6, 0.4), (:US, :EU); name = :imports)
tree = CES(1.5, (0.7, 0.3), (:dom, imports); name = :total)

This looks much cleaner. However, using Symbols instead of CESLeaf requires that anonymous leaves are not allowed, and that is something I want to keep.

CESNode to CES is harmless, though.

Ok, I managed to clean the API, now your examples work. Thank you for the suggestion.

Why not use something like nothing for anonymous leafs?

You mean instead of Symbol()? I could do that and use Union{Symbol, Nothing} as the type of name. What would be the gain?

No, I meant drop the leaf type entirely, use ::Symbol for named nodes, and nothing for anonymous ones.

(Just a thought. I use CES utility in my work so I might end up using your package. I have a non-homothetic CES implementation myself.)

I have plans for developing packages for other kinds of utility functions, perhaps we can coordinate to have consistent APIs.

First of all, thank you for providing the package. Given the ubiquity of the CES aggregators in economic modelling, I see that a respective package should be useful.

Looking at your package, I feel it would be useful if it could also return the analytical derivatives of the nested aggregators w.r.t. its inputs, given that these appear very often in model FOCs. Since these FOCs tend to have a special structure at least for standard CES aggregators, that should presumably be possible? I understand that one can ForwardDiff the aggregators, but being able to evaluate the derivates directly would seem simpler and more convenient.

Yes, I thought about that as well. To be honest, I thought I would do derivatives with respect to parameters, since I am more interested in estimating those. In any case, I think the easiest way would be to define some rules for an AD engine?