Relationship between variables

In an optimisation problem, I have some relationships between variables i.e. they should take same values. I define a dataframe (called Master) that contains all the data and another dataframe (called Relationship) that in each row contains ID for variables that are related. For example, first row contains A1, A5, A3 and A9, so based on indexing in Master a constraint should ensure that x[1] = x[5] = x[3] = x[9]. This is explained in more detail in the MWE in comments. I thought it will be more a DataFrames related issue but I have not been able to get my head around this specific problem.

How could I define this constraint?

A not so nice way of applying the constraint could be to arbitrary change the IDs in the Master where they are related, i.e. replace with same ID. And then use the unique function of DataFrames to find the corresponding index and set them equal, but a more elegant solution should be possible.

using JuMP, Clp
function get_data()
    Relationship = DataFrame(ID = ["A1","A2","A4"], LINK_1 = ["A5","A6","A10"],
    LINK_2 = ["A3","A7",""], LINK_3 = ["A9","A11","A12"])
    ID_t =  collect(1:15)
    ID = Vector{String}(undef,15)
    for i in 1:15
        ID[i]="A".*string(ID_t[i])
    end

    PRICE = rand(10.0:100.0,15)
    Master = DataFrame(ID = ID,PRICE = PRICE)
    return Relationship, Master
end


Relationship, Master = get_data()
Price = Master.PRICE
model = Model(Clp.Optimizer)
@variable(model,0<=x[i=1:length(Price)] <=1)
@constraint(model,con, sum(Price[i]*x[i] for i in 1:length(Price)) <=300)
@constraint(model,con2, sum(Price[i]*x[i] for i in 1:length(Price)) >=200)


# Create a dummy variable so that all elements in same rows in the Relationship dataframe
# take same values (in optimisation)
@variable(model,t[1:length(Relationship.ID)])

# Define the constraint that ensures that 
#x[1] = x[5] = x[3] = x[9]    = t[1]
#x[2] = x[6] = x[7] = x[11]   = t[2]
#x[4] = x[10] = x[12]         = t[3]

# Create a dictionary based on the Master dataframe 
d = Dict([Master.ID[i] => Master.PRICE[i] for i in 1:nrow(Master)])
# For all rows in Relationship get references to the corresponding entries 
# in Master to define the above constraint

 # Warning some of the rows may have some blanks entries as in the example!

You want something like

model = Model()
@variable(model, x[Master.ID])
@variable(model,t[1:length(Relationship.ID)])
for (i, row) in enumerate(eachrow(Relationship))
    for key in values(row)
        if !isempty(key)
            @constraint(model, t[i] == x[key])
        end
    end
end
1 Like

Thank you. I get “KeyError: key 1 not found” . This arises when it gets to constraints and probably because of the JuMP container DenseAxisArray.

@constraint(model,con, sum(Price[i]*x[i] for i in 1:length(Price)) <=300)
For this specific example, I can use @constraint(model,Price'*x <=300) but I lose flexibility if I need to use a comprehension, nested comprehension or loop for a more complex constraint.
I also tried this but it is not equivalent to the constraint in question: @constraint(model,sum(Price[j] *x[i] for i in keys(x), j in 1:length(Price))<=300)

Please could you advise on use of comprehension and/or loop with this container type (as in the constraint)? I have given the code that reproduces this error:

using JuMP, Clp, DataFrames
function get_data()
    Relationship = DataFrame(ID = ["A1","A2","A4"], LINK_1 = ["A5","A6","A10"],
    LINK_2 = ["A3","A7",""], LINK_3 = ["A9","A11","A12"])
    ID_t =  collect(1:15)
    ID = Vector{String}(undef,15)
    for i in 1:15
        ID[i]="A".*string(ID_t[i])
    end

    PRICE = rand(10.0:100.0,15)
    Master = DataFrame(ID = ID,PRICE = PRICE)
    return Relationship, Master
end


Relationship, Master = get_data()
Price = Master.PRICE
model = Model(Clp.Optimizer)

@variable(model, 0<=x[Master.ID]<=1)
@variable(model,t[1:length(Relationship.ID)])
for (i, row) in enumerate(eachrow(Relationship))
    for key in values(row)
        if !isempty(key)
            @constraint(model, t[i] == x[key])
        end
    end
end
@constraint(model,con, sum(Price[i]*x[i] for i in 1:length(Price)) <=300)
@constraint(model,con2, sum(Price[i]*x[i] for i in 1:length(Price)) >=200)

optimize!(model)

I suggest you step back and think about what’s going on here.

@variable(model, 0<=x[Master.ID]<=1)

means that the keys of x are now x["A1"], x["A2"], etc, whereas in the constraint, you are trying to index x[1], x[2], etc.

Rather than looping through i in 1:length(Price), you should change the loop to go through the keys of x. (Hint: you could use for (id, price) in zip(Master.ID, Master.PRICE).)

Or, you should change the definition of x back to what you had originally, and change the t[i] == x[key] constraint to look up the integer index from the string key.

3 Likes

Thank you. I have tried to educate myself on dictionaries in Julia over the weekend, and now I have hit a dead end. I was under the impression that I have picked up fair bit of Julia, but struggling with such a basic manipulation has made me to think otherwise.

I have unsuccessfully tried different combinations with for (id,price) in zip(Master.ID,Master.PRICE)

I know it is bound to fail if I define the constraint the following way:

@constraint(model,con,sum(keys(x)*keys(price) for (id,price) in zip(Master.ID,Master.PRICE)<=300)

@constraint(model,con,sum(x*price for (id,price) in zip(Master.ID,Master.PRICE)<=300)

This would be my preference but I am not sure how to do it.

Any tips on what else I can try?

@constraint(
    model, 
    sum(price * x[id] for (id, price) in zip(Master.ID, Master.PRICE)) <= 300
)
1 Like

Thanks a lot!

1 Like