GAMS-dev / gamspy

Python-based algebraic modeling interface to GAMS
Other
35 stars 2 forks source link

Variable bound is not applied/overwritten in certain case #4

Closed grecht closed 3 months ago

grecht commented 3 months ago

I encountered a bug: A static bound on a variable x[s], e.g. x[s].lo = 0, is not applied or rather overwritten when the statement is executed before the records of set s have been defined via s.setRecords(). I provide a small example below, where only the line x.lo = 0 has to be moved after the s.setRecords() call to make the model feasible.

This is especially relevant when wanting to do the "instantiation" of the model (i.e. setting all records) after its abstract definition. Static variable bounds might still be set before the instantiation.

"InfeasibleNoSolution" when x.lo = 0 is executed before s.setRecords()

from gamspy import Container, Sum, Sense

c = Container()
s = c.addSet("s")
x = c.addVariable("x", type="free", domain=s)

m = c.addModel(
    problem="LP",
    name="test",
    sense=Sense.MIN,
    objective=Sum(s, 1 * x[s]))

x.lo = 0
s.setRecords(["a"])

m.solve()

"OptimalGlobal" when x.lo = 0 is executed after s.setRecords()

from gamspy import Container, Sum, Sense

c = Container()
s = c.addSet("s")
x = c.addVariable("x", type="free", domain=s)

m = c.addModel(
    problem="LP",
    name="test",
    sense=Sense.MIN,
    objective=Sum(s, 1 * x[s]))

s.setRecords(["a"])
x.lo = 0

m.solve()
mabdullahsoyturk commented 3 months ago

x.lo is not a declaration but an execution statement (similar to Python interpreter running line by line). Hence, when you execute in the following order:

x.lo = 0
s.setRecords(["a"])

you are basically indicating that you want to set the lower bound of elements in the set so far (since set s is empty at the moment, you do not set the lower bound of anything). But when you change the order:

s.setRecords(["a"])
x.lo = 0

Set s is not empty anymore. Therefore, you assign the lower bound for a.

Because of this, static variable bounds should be declared after the instantiation of the model to achieve the desired result.

grecht commented 3 months ago

Okay, that makes sense, thanks for the explanation. My first thought was that if the statement has no effect, it should not be possible to execute it or this should at least be communicated to the user. However, if a set is left empty on purpose, e.g. due to flexibility in the model definition, this makes no sense. So FYI, my current workaround is to define a function for each variable which adds its bounds to the Container and execute it after the data has been added to the Container (if the respective variable is contained in it).

I can see this is due to GAMS' underlying set philosophy and not specific to GAMSPy. However, it sort of goes against the "Model Instances vs. Mathematical Models" proposition of GAMSPy's landing page, which is IMO not implemented consistently (or at least it is not clear where the "model world" ends and the "instance world" begins): I am able to define equations using variables and parameters defined over empty sets, as this only creates "symbolic" relationships. This is really nice and appealing. But then it is confusing that it does not work for static variable bounds, as they are also equations from a mathematical perspective. Also, defining the complementary set of an empty subset of an empty set does not work. Moreover, from a modeling perspective a mathematical model is not totally devoid of data: In many cases, variable bounds are known beforehand as they are dictated by the problem at hand (e.g. the voltage angle limits in the optimal power flow problem). And from a software development perspective, I want to make the model building flexible using functions which add sets, parameters, variables and equations to the Container. If during definition of a variable I already know its static bounds, it makes sense to put them right after its definition.