ERGO-Code / HiGHS

Linear optimization software
MIT License
957 stars 177 forks source link

addVar in highs.py doesn't add columns to the model #1681

Closed jajhall closed 5 months ago

jajhall commented 7 months ago

Hence, if highspy calls are made to change model data for columns, they fail needs self.update() call?

See mats.py

mathgeekcoder commented 7 months ago

Correct, this was by design so that we can efficiently add a batch of variables. It's not ideal for people to add many variables one-by-one - but obviously it's still possible for them to do so.

For the mats.py example, instead of:

for i in range(nvars):
   h.addVar(0, inf)
   h.changeColCost(i, 1)

we can simply use:

for i in range(nvars):
   h.addVar(0, inf, 1)

h.update()  # needed since calling low-level addRow afterwards (instead of addConstr)

or (horribly):

for i in range(nvars):
   h.addVar(0, inf)
   h.update()
   h.changeColCost(i, 1)

That said, this also connects to #1679, and so it depends if we want the default API to use the new highspy interface, or directly access the base HiGHS class.

mathgeekcoder commented 7 months ago

BTW: I believe with the new highspy, mats.py can be rewritten from:

for i in range(nvars):
   h.addVar(0, inf)
   h.changeColCost(i, 1)
row_nnz = 2
index = np.array([0, 1])
value = np.array([1, 1], dtype=np.double)
for i in range(nvars - 1):
    index[0] = i
    index[1] = i+1
    h.addRow(1, inf, row_nnz, index, value)
h.run()

to:

X = [h.addVar(0, inf, 1)             for _ in range(nvars)]
C = [h.addConstr(X[i] + X[i+1] >= 1) for i in range(nvars - 1)]    
h.run()
jajhall commented 7 months ago

mats.py was actually my attempt to re-write

https://github.com/ERGO-Code/HiGHS/blob/highspy-modelling/examples/scip.py

when we lost the ability to build highspy with your modelling language.

Your version is even more succinct, although (keeping within the modelling language) it would work with

X = [h.addVar(obj=1) for _ in range(nvars)] C = [h.addConstr(X[i] + X[i+1] >= 1) for i in range(nvars - 1)]
h.run()

mathgeekcoder commented 7 months ago

Good point. Referring to comment in #1679, if we also support addVars/addConstrs in highspy, we could simplify even further:

X = h.addVars(nvars, obj=1)
C = h.addConstrs(X[i] + X[i+1] >= 1 for i in range(nvars - 1))
h.run()
mathgeekcoder commented 7 months ago

That highspy code would look something like the following:

    def addVars(self, nvars, lb = 0, ub = kHighsInf, obj = 0, type=HighsVarType.kContinuous, name_prefix = None):
        return [self.addVar(lb, ub, obj, type, None if name_prefix == None else name_prefix + str(_)) for _ in range(nvars)]

    def addConstrs(self, generator, name_prefix=None):
        self.update()

        lower = []
        upper = []
        starts = [0]
        indices = []
        values = []
        nnz = 0;

        for cons in generator:
            # if we have duplicate variables, add the vals together
            vars,vals = zip(*[(var, sum(v[1] for v in Vals)) for var, Vals in groupby(sorted(zip(cons.vars, cons.vals)), key=itemgetter(0))])

            lower.append(cons.LHS - cons.constant)
            upper.append(cons.RHS - cons.constant)
            indices.extend(vars)
            values.extend(vals)

            nnz += len(vars)
            starts.append(nnz)

        new_rows = len(lower)
        super().addRows(new_rows, lower, upper, nnz, starts, indices, values);

        cons = [highs_cons(self.numConstrs - new_rows + n, self, None if name_prefix == None else name_prefix + str(n)) for n in range(new_rows)]
        self._cons.extend(cons)
        return cons
jajhall commented 5 months ago

Closed by #1753