def __init__(self, prefix='refData'): df = pd.read_csv(os.path.join(prefix, 'TxnCost.txt'), sep=' ', header=None, names=['Code', 'BuyCost', 'SellCost']) df['Code'] = df['Code'].astype(str).str.zfill(6) #print buyCosts, sellCosts self._txnCosts = dict() for fundCode, buyCost, sellCost in df.itertuples(index=False): self._txnCosts[ fundCode] = lambda _x, b=buyCost, s=sellCost: cvx.scalene( _x, b / NORMAL_FACTOR, s / NORMAL_FACTOR)
])), (lambda x: cp.power(x, -1), tuple(), [7.45], Constant([0.1342281879194631 ])), (lambda x: cp.power(x, -.7), tuple(), [7.45], Constant([0.24518314363015764])), (lambda x: cp.power(x, -1.34), tuple(), [7.45], Constant([0.06781263100321579])), (lambda x: cp.power(x, 1.34), tuple(), [7.45], Constant([14.746515290825071])), (cp.quad_over_lin, tuple(), [[[-1, 2, -2], [-1, 2, -2]], 2], Constant([2 * 4.5])), (cp.quad_over_lin, tuple(), [v_np, 2], Constant([4.5])), (lambda x: cp.norm(x, 2), tuple(), [[[2, 0], [0, 1]]], Constant([2])), (lambda x: cp.norm(x, 2), tuple(), [[[3, 4, 5], [6, 7, 8], [9, 10, 11]]], Constant([22.368559552680377])), (lambda x: cp.scalene(x, 2, 3), (2, 2), [[[-5, 2], [-3, 1]]], Constant([[15, 4], [9, 2]])), (cp.square, (2, 2), [[[-5, 2], [-3, 1]]], Constant([[25, 4], [9, 1]])), (cp.sum, tuple(), [[[-5, 2], [-3, 1]]], Constant(-5)), (lambda x: cp.sum(x, axis=0), (2, ), [[[-5, 2], [-3, 1]]], Constant([-3, -2])), (lambda x: cp.sum(x, axis=1), (2, ), [[[-5, 2], [-3, 1]]], Constant([-8, 3])), (lambda x: (x + Constant(0))**2, (2, 2), [[[-5, 2], [-3, 1]]], Constant([[25, 4], [9, 1]])), (lambda x: cp.sum_largest(x, 3), tuple(), [[1, 2, 3, 4, 5]], Constant([5 + 4 + 3])), (lambda x: cp.sum_largest(x, 3), tuple(), [[[3, 4, 5], [6, 7, 8], [9, 10, 11]]], Constant([9 + 10 + 11])),
def main(datafile=default_data_file, namesfile=default_volunteers_file, min_staff=5, alpha=1.0, beta=0.7, gamma=0.28, verbose=1, solver_options=None): '''Notação: M = min_staff: número mínimo de voluntários por turno. i = número (código) da pessoa. vai de 1 a N (talvez 28?) j = número (código) do turno. vai de 1 a T (provavelmente 14) A_ij = available[i, j] == True quando pessoa i está disponível no turno j P_ij = preference[i, j] == True quando pessoa i tem preferência pelo turno j w_i = prev_week[i] == número de turnos (almoços OU jantas) alocados à pessoa i na semana anterior Variável a ser otimizada: Z[i, j] == True quando pessoa i é alocada para colaborar no turno j. Variável auxiliar: L_i = load[i] == sum_j Z[i, j] == (soma da linha i de Z) == número de turnos alocados pra pessoa i. OBJETIVO: minimizar o máximo dos load[i], conforme disponibilidade das pessoas, orientando-se também pelas preferências. * Por enquanto, não estamos usando `preference` e `prev_week`, já que poucos usaram esses recursos na planilha. ''' table_string = read_data(datafile) names_string = read_data(namesfile) available, preference, prev_week, managers = data_to_array( table_string, names_string) N, T = available.shape Z = cp.Variable((N, T), name="Z", boolean=True) if verbose >= 1: print( "# Programa de otimização linear com variáveis inteiras (MIP)\n\nDados de Entrada:" ) print("\n[Array: AVAILABLE]", 0 + available, sep="\n", end="\n\n") print("\n[Array: PREFERENCE]", 0 + preference, sep="\n", end="\n\n") # load[i] == 'número de turnos alocados para a pessoa i' == sum(Z[i,j] para cada j) load = cp.sum(Z, axis=1) # Variável para induzir a preferência de soluções sem muita gente contribuindo em um só turno do dia: same_day = cp.Variable((N, T // 2)) constraints = [ # Sempre que A_ij==0, impõe-se Z_ij==0: Z <= available, # Somatório em i (somatório de cada coluna) deve ser, no mínimo, min_staff: cp.sum(Z, axis=0) == min_staff, # Restrição para garantir a presença de no mínimo um dos `responsáveis` (managers) a cada turno: cp.sum(Z[managers, :], axis=0) >= 1, # Forma matricial das desigualdades same_day[:, j//2] <= min(Z[:, j], Z[:, j+1]), com j em (0, 2, 4, ..., T-2): same_day <= Z[:, 0::2], same_day <= Z[:, 1::2] ] # Expressão para a carga média por pessoa, usando a igualdade cp.sum(load) == cp.sum(Z): mean_load = cp.sum(Z) / N # Função Objetivo (função a ser MINIMIZADA): load_cost = cp.norm_inf(load) # máximo das entradas do vetor `load` # PENALTY_1 DESATIVADO (substituído por zero), vide restrição `Z <= available` acima. penalty_1 = cp.Constant( 0 ) # alpha * cp.sum(cp.pos(Z - available)) # penalizava-se alpha para cada entrada Z[i,j] > available[i,j]. penalty_2 = beta * cp.sum( cp.scalene(load - mean_load, 1.8, 1.0) ) # penalizar cada load[i] longe da média (especialmente acima da média) same_day_bonus = gamma * cp.sum(same_day) objective = cp.Minimize( load_cost + penalty_1 + penalty_2 - same_day_bonus ) # minimize ('máximo das entradas' do vetor load) + penalidades problem = cp.Problem( objective, constraints) # <= problema de otimização sujeito às restrições acima. if verbose >= 1: print("# Solucionando problema de otimização.") if solver_options is None: solver_options = {"verbose": (verbose >= 2)} elif "TimeLimit" in solver_options: # GAMBIARRA: "cvxpy throws error when Gurobi solver encounters time limit" cp.settings.ERROR = [cp.settings.USER_LIMIT] cp.settings.SOLUTION_PRESENT = [ cp.settings.OPTIMAL, cp.settings.OPTIMAL_INACCURATE, cp.settings.SOLVER_ERROR ] # CONFERIR: https://github.com/cvxgrp/cvxpy/issues/735 if verbose >= 2: print("\n", "# # # # # " * 9, "\n", sep="") problem.solve(solver=cp.GUROBI, **solver_options) if verbose >= 2: print("\n", "# # # # # " * 9, "\n", sep="") results_dict = { "alpha": alpha, "beta": beta, "gamma": gamma, "Z": Z, "Z_array": None, "objective": objective, "constraints": constraints, "problem": problem, "load": load, "load_cost": load_cost, "penalty_1": penalty_1, "penalty_2": penalty_2, "same_day_bonus": same_day_bonus } if results_dict["Z"] is not None: results_dict["Z_array"] = np.asarray(Z.value + 0.1, dtype=int) if problem.status != cp.OPTIMAL: print( "# WARNING. Talvez tenha atingido o tempo limite sem otimalidade?" ) print("Status do problema: {}".format(problem.status)) if verbose: show_results(results_dict) else: print("# ERRO: o solver não encontrou uma solução!") print("Status do problema: {}".format(problem.status)) print("# Para as disponibilidades dadas, talvez não seja") print("# possível obter `min_staff` pessoas por turno?") return results_dict
(cp.neg, (2,), [[-3, 3]], Constant([3, 0])), (lambda x: cp.power(x, 1), tuple(), [7.45], Constant([7.45])), (lambda x: cp.power(x, 2), tuple(), [7.45], Constant([55.502500000000005])), (lambda x: cp.power(x, -1), tuple(), [7.45], Constant([0.1342281879194631])), (lambda x: cp.power(x, -.7), tuple(), [7.45], Constant([0.24518314363015764])), (lambda x: cp.power(x, -1.34), tuple(), [7.45], Constant([0.06781263100321579])), (lambda x: cp.power(x, 1.34), tuple(), [7.45], Constant([14.746515290825071])), (cp.quad_over_lin, tuple(), [[[-1, 2, -2], [-1, 2, -2]], 2], Constant([2 * 4.5])), (cp.quad_over_lin, tuple(), [v_np, 2], Constant([4.5])), (lambda x: cp.norm(x, 2), tuple(), [[[2, 0], [0, 1]]], Constant([2])), (lambda x: cp.norm(x, 2), tuple(), [[[3, 4, 5], [6, 7, 8], [9, 10, 11]]], Constant([22.368559552680377])), (lambda x: cp.scalene(x, 2, 3), (2, 2), [[[-5, 2], [-3, 1]]], Constant([[15, 4], [9, 2]])), (cp.square, (2, 2), [[[-5, 2], [-3, 1]]], Constant([[25, 4], [9, 1]])), (cp.sum, tuple(), [[[-5, 2], [-3, 1]]], Constant([-5])), (lambda x: cp.sum(x, axis=0), (2,), [[[-5, 2], [-3, 1]]], Constant([-3, -2])), (lambda x: cp.sum(x, axis=1), (2,), [[[-5, 2], [-3, 1]]], Constant([-8, 3])), (lambda x: (x + Constant(0))**2, (2, 2), [[[-5, 2], [-3, 1]]], Constant([[25, 4], [9, 1]])), (lambda x: cp.sum_largest(x, 3), tuple(), [[1, 2, 3, 4, 5]], Constant([5 + 4 + 3])), (lambda x: cp.sum_largest(x, 3), tuple(), [[[3, 4, 5], [6, 7, 8], [9, 10, 11]]], Constant([9 + 10 + 11])), (cp.sum_squares, tuple(), [[[-1, 2], [3, -4]]], Constant([30])), (cp.trace, tuple(), [[[3, 4, 5], [6, 7, 8], [9, 10, 11]]], Constant([3 + 7 + 11])), (cp.trace, tuple(), [[[-5, 2], [-3, 1]]], Constant([-5 + 1])), (cp.tv, tuple(), [[1, -1, 2]], Constant([5])), (cp.tv, tuple(), [[1, -1, 2]], Constant([5])), (cp.tv, tuple(), [[[-5, 2], [-3, 1]]], Constant([math.sqrt(53)])), (cp.tv, tuple(), [[[-5, 2], [-3, 1]], [[6, 5], [-4, 3]], [[8, 0], [15, 9]]],
import os.path import pandas as pd import cvxpy as cvx __all__ = ['fundTransactionCost'] DEFAULT_BUYCOST = 0.12 DEFAULT_SELLCOST = 0.5 NORMAL_FACTOR = 100. defaultTxnCost = lambda _x: cvx.scalene(_x, DEFAULT_BUYCOST / NORMAL_FACTOR, DEFAULT_SELLCOST / NORMAL_FACTOR) class FundTransactionCostLoader(object): def __init__(self, prefix='refData'): df = pd.read_csv(os.path.join(prefix, 'TxnCost.txt'), sep=' ', header=None, names=['Code', 'BuyCost', 'SellCost']) df['Code'] = df['Code'].astype(str).str.zfill(6) #print buyCosts, sellCosts self._txnCosts = dict() for fundCode, buyCost, sellCost in df.itertuples(index=False): self._txnCosts[ fundCode] = lambda _x, b=buyCost, s=sellCost: cvx.scalene( _x, b / NORMAL_FACTOR, s / NORMAL_FACTOR)