Unable to use the setAttr() method in gurobipy
AnsweredHere is the code (1D cutting stock problem):
#!/usr/bin/env python3
# * coding: utf8 *
"""
Created on Tue May 22 22:02:13 2018 by: Fangzhou Sun
https://github.com/fzsun/cutstockgurobi
This code solves the following cutting stock model:
Master problem:
min \sum_{p in P} x_p
s.t. \sum_{p in P} patterns_{ip} * x_p ≥ d_i, for i in I
x_p ≥ 0 and integer, for p in P
Subproblem:
min 1  \sum_{i in I} price_i * use_i
s.t. \sum_{i in I} w_i * use_i ≤ W_roll
use_i ≥ 0 and integer, for i in I
x_p: number of times pattern p is used
price_i: dual of constraint i in the master problem
use_i: number of item i's in a new pattern
Modifications by: V. Stokes (vs@it.uu.se) 2020.08.31
"""
import numpy as np
import logging
from itertools import count
import sys
'''
The following path should be adjusted to where gurobipy.py is located on
your system for the specific version of python that you will be using
'''
sys.path.insert(1,'C:/gurobi902/win64/python36/lib')
from gurobipy import *
def keyboard_terminate(model, where): # Enable pause m.optimize() by 'ctrl + c'
try:
pass
except KeyboardInterrupt:
model.terminate()
logger = logging.getLogger(__name__) # Set up logger
if not logger.hasHandlers():
logger.addHandler(logging.StreamHandler())
file_handler = logging.FileHandler('RunLog.log')
formatter = logging.Formatter(
fmt='%(asctime)s %(filename)s:%(levelname)s  %(message)s',
datefmt='%Y%m%d %H:%M:%S')
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.setLevel(logging.DEBUG)
logger.info('Begin')
# =========================== Parameters ======================================
#np.random.seed(1)
np.random.seed(143) # 40th prime
TOL = 1e6
W_roll = 100 # roll width
I = list(range(5)) # item set
w = np.random.randint(1, 50, len(I)).tolist() # width of each item
d = np.random.randint(1, 50, len(I)).tolist() # demand of each item
patterns = np.diag([W_roll // w[i] for i in I]).tolist() # initial patterns
print('stock width: %3d'%W_roll)
print('item width demand (width x demand)')
total_demand_wid = 0
for k,(wid,dmd) in enumerate(zip(w,d)):
total_wid = wid*dmd
print('%3d %3d %3d %7d'%(k,wid,dmd,total_wid))
total_demand_wid += total_wid
print(30*(''))
print('Total demand width: %9d '%total_demand_wid)
# ========================= Master Problem ====================================
m = Model('cutstock')
m.ModelSense = GRB.MINIMIZE
x = m.addVars(len(patterns), obj=1, vtype='C', name='x')
c1 = m.addConstrs((patterns[i][i] * x[i] >= d[i] for i in I), name='c1')
# ======================= Subproblem and Iteration ============================
for iter_count in count():
m.write('master_problem.lp')
m.optimize(keyboard_terminate)
price = [c1[i].pi for i in I]
print(f'Price = {price}')
sp = Model('subproblem') # Subproblem
sp.ModelSense = GRB.MAXIMIZE
use = sp.addVars(I, obj=price, vtype='I', name='use')
c2 = sp.addConstr(quicksum(w[i]*use[i] for i in I) <= W_roll)
sp.write('subproblem.lp')
sp.optimize(keyboard_terminate)
min_rc = 1  sp.objVal
if min_rc < TOL:
patterns.append([int(use[i].x) for i in I])
logger.info(f'min reduced cost = {min_rc:.4f};' f' new pattern: {patterns[1]}')
x[iter_count+len(I)] = m.addVar(obj=1, vtype='C', column=Column(patterns[1], c1.values()))
else:
break
# ====================== Relaxed Model Result =================================
logger.info(f'min reduced cost = {min_rc:.4f} ≥ 0')
relaxed_result = [f'{v.x:.4f} * {patterns[p]}' for p, v in enumerate(m.getVars()) if v.x > TOL]
relaxed_result.insert(0, f'Relaxed result = {m.objVal:.4f} rolls')
logger.info('\n\t'.join(relaxed_result))
# ====================== Integer Model Result =================================
m.setAttr('VType', x.values(), 'I'*len(x))
m.write('master_problem.lp')
m.optimize(keyboard_terminate)
integer_result = [f'{int(v.x)} * {patterns[p]}' for p, v in enumerate(m.getVars()) if v.x > TOL]
integer_result.insert(0, f'Integer result = {int(m.objVal)} rolls')
logger.info('\n\t'.join(integer_result))
When I try to execute this code on a Windows 10 platform, in Python 3.6.5 the following error message is returned when the 6th line from the last line is executed:
File "C:\PythonTestCode\LinearProgramming\Gurobi\StockCutting_01A.py", line 105, in <module>
m.setAttr('VType', x.values(), 'I'*len(x))
File "C:\gurobi902\win64\python36\lib\gurobipy\gurobipy.pyd", line 1869, in gurobipy.Model.setAttr
File "C:\gurobi902\win64\python36\lib\gurobipy\gurobipy.pyd", line 281, in gurobipy.__settypedattrlist
builtins.ValueError: only single character unicode strings can be converted to Py_UCS4, got length 13
Why do I get the error and how can it be fixed?

Note, when I selected code and then did a copy an paste of the actual code, the indented spaces were ignored :( Please look a the link https://github.com/fzsun/cutstockgurobi
0 
Hi,
Line \(\texttt{m.setAttr('VType', x.values(), 'I'*len(x))}\) actually expands to \(\texttt{m.setAttr('VType', x.values(), 'IIIIIIIIIIIII')}\), since \(\texttt{len(x)}\) equals 13 resulting in the error you see.
You can achieve setting the type of all \(x\) variables to integer by executing \(\texttt{m.setAttr('VType', x.values(), 'I')}\) or
\(\texttt{m.setAttr('VType', x.values(), ['I']*len(x))}\).
Please note that in the first option, all variables types are going to be set to integer as exactly one character \(\texttt{'I'}\) is used for all variables \(x\). In the second option a list \(\texttt{['I', 'I', 'I', 'I', 'I', 'I', 'I', 'I', 'I', 'I', 'I', 'I', 'I']}\) is constructed and passed to the \(\texttt{setAttr()}\) function, setting the type of each of the \(x\) variables individually to integer.Best regards,
Jaromił1 
Very good Jaromil,
Thank you for your prompt reply to my question. Indeed this solves the problem with setAttr() and gives an answer to the 1D cutting stock problem. However, when I examine the results obtained, I am a little botherd by the trim loss (the demands for the widths of 18 and 34 are exceeded by 2 and 1 respectfully). I would be interested in your thoughts on the results obtained, Jaromil. Perhaps the formulation of the problem by Fangzhou Sun could be improved.
0 
Hi Virgil,
The exceeded amounts arise because the optimizer tries to reduce the waste of the roll. E.g., it would suffice to use
1 * [0, 0, 3, 0, 0]
instead of
1 * [0, 0, 5, 0, 0]
but the cutstock waste would be larger, namely 44 instead of 10. Thus, the solution with an exceed of 2 for the demand of 18 width is "better" in terms of "waste reduction".
It would also be possible to slightly variate the patterns but, the optimal solution of 38 rolls could not be lowered anyway.
Best regards,
Jaromił0
Please sign in to leave a comment.
Comments
4 comments