Forwarding a macro that returns a variable?

I am trying to forward the @variable macro from JuMP, so that instead of passing a model to it, I pass my custom struct that has a model as a field. I was able to do it when the new macro is declared locally:

using JuMP

struct Player
    model::Model
end

macro pvar(player, args...)  # forwards JuMP.@variable to Player.model
    return :(JuMP.@variable($(esc(player)).model, $(args...)))
end

p = Player(Model())

@pvar(p, y >= 1)  # works!

However, as soon as I move the code to my module, it breaks. I believe my problem is in trying to return a local variable (the one created inside JuMP.@variable). MWE:

using JuMP

module MyModule
    using JuMP

    struct Player
        model::Model
    end

    macro pvar(player, args...)  # forwards JuMP.@variable to Player.model
        return :(JuMP.@variable($(esc(player)).model, $(args...)))
    end
end

p = MyModule.Player(Model())

MyModule.@pvar(p, y >= 1)  # fails :(

The error is:

ERROR: LoadError: Global MyModule.y does not exist and cannot be assigned. Declare it using `global` before attempting assignment.

I tried prepending a global to the inner macro call and escaping the whole thing. I am really new to using macros and I have a hard time understanding the order of interpolations and executions.

Hi @brunompacheco,

Before telling you how to do this, why do you want to do this? What is the higher-level goal you are trying to achieve?

I would strongly encourage you not to define new macros. As you have seen, they can be tricky to get correct. Is there some other way that you can achieve what you are trying to do?

You may want to take a read of Extensions · JuMP

Hi @odow,

Each Player has a feasible space and an objective function, but I will manipulate the objective function (change parameters, explore separability), so I think I cannot just define it as a Model. At first, I thought about extending Model, but I got discouraged by Defining new JuMP models. So now, my idea is to have a Model as a field of Player, and forward the @variable and @constraint macros, so that the user can easily define the feasible space. As an alternative, I thought about forwarding add_variable and add_constraint, but that is also discouraged.

My last resort is for the user to build a Model with the feasible region and pass that when declaring the Player. But I would then make a deepcopy of the model to avoid counterintuitive interferences with the algorithm.

Feel free to let me know what you think of it. Your suggestions are highly appreciated.

What is your ideal syntax for the smallest example? In other words, what would be the first example in the README or documentation?

Probably something in the lines of

p1 = Player(QuadraticPayoff(0, [2, 1]))
@add_variable(p1, x1 >= 0)


p2 = Player(QuadraticPayoff(0, [1, 2]))
@add_variable(p2, x2 >= 0)

P = [p1, p2]

x_init = find_feasible(P)

x_opt = solve(P)

For context, QuadraticPayoff is used in solve both by itself and to define the (parameterized) objective of the Model.

Just make the syntax:

p1 = Player(QuadraticPayoff(0, [2, 1]))
@variable(p1.model, x1 >= 0)

p2 = Player(QuadraticPayoff(0, [1, 2]))
@variable(p2.model, x2 >= 0)
P = [p1, p2]
x_init = find_feasible(P)
x_opt = solve(P)

Defining a new macro is should be a last resort. JuMP uses macros so that we can intercept x >= 0 as bound and constraints instead of comparisons, and so we can build linear expressions more efficiently.

If you tell people “just write p.model, then you can use all of JuMP” it is much simpler than opting into a subset of the JuMP features or defining new macros.

1 Like