Pyomo - Mindtpy: Why does reformulating nonlinear exponential objective as constraints not work? - pyomo

The question is why Pyomo - MindtPy is unable to get an optimal solution with nonlinear exponential function is built in constraints, while being able to get an optimal solution with nonlinear exponential function is built in objective?
The nonlinear optimization problem is
Max Total_Reach_Freq
Constraints are:
Total_Reach_Freq =[1-exp( -2.4208*x[0])] +[1-exp(-1.6278*x[1])]
8000 <= 266*x[0]+494*x[1] <= 12000, 0 <=x[0] <=1755, 0 <=x[1]<=2873, x[0] and x[1] are integers
where exp is the exponential function.
The Python code is below
import pandas as pd
import numpy as np
import pyomo.environ as pe
from pyomo.opt import SolverStatus, TerminationCondition
from pyomo.util.infeasible import log_infeasible_constraints
model = pe.ConcreteModel(name="Pyomo Test Optimization")
########### Define integer variables x[0], x[1]
### such that 0 <= x[0] <= 1755
### 0 <= x[1] <= 2873
cap_list=[1755, 2873]
def cap_rule( model, i):
return (0, cap_list[i])
model.x = pe.Var( [0, 1], within=pe.NonNegativeIntegers, bounds= cap_rule )
C=[-2.4208, -1.6278]
##### Define objective variable Total_Reach_Freq >=0
### Total_Reach_Freq =[1-pe.exp( -2.4208*x[0])] +[1-pe.exp(-1.6278*x[1])]
model.Total_Reach_Freq = pe.Var( within=pe.NonNegativeReals, bounds=(0, None) )
model.Constraint_Define_Objective= pe.Constraint(expr= (1-pe.exp(C[0]*model.x[0])) +(1-pe.exp(C[1]*model.x[1])) == model.Total_Reach_Freq )
model.objective = pe.Objective(expr=model.Total_Reach_Freq, sense= pe.maximize)
#model.objective = pe.Objective(expr=(1-pe.exp(C[0]*model.x[0])) +(1-pe.exp(C[1]*model.x[1])), sense= pe.maximize)
##### Define constraints
#### 8000 <= 266*x[0]+494*x[1] <= 12000
D=[266, 494]
model.Constraint_Budget_lb= pe.Constraint(expr= D[0]*model.x[0]+D[1]*model.x[1] >= 8000 )
model.Constraint_Budget_ub= pe.Constraint(expr= D[0]*model.x[0]+D[1]*model.x[1] <= 12000 )
model.pprint()
results=pe.SolverFactory('mindtpy').solve(model, tee=True)
log_infeasible_constraints(model)
#model.load(results)
#model.solutions.load_from(results)
print()
print("Results=", results)
status= results.solver.termination_condition
print("############################## Status ############################")
print()
print( "results.solver.status =", results.solver.status)
print("results.solver.termination_condition =", results.solver.termination_condition )
print("SolverStatus.warning =", SolverStatus.warning)
print()
print("Solution:")
print()
print( "pe.value(model.objective)=", pe.value(model.objective) )
print( "Decision variable x =", [model.x[0].value, model.x[1].value ] )
print("################################################################")
R_var= [ np.round_( 1-pe.exp( C[0]*model.x[0].value ), decimals=4), np.round_( 1-pe.exp(C[1]*model.x[1].value), decimals=4) ]
print()
print("R_i", R_var)
print()
print("sum (R_i)=", np.round_(sum(R_var), decimals=2))
This problem has optimal solution. However, the output shows a feasible solution
1 Set Declarations
x_index : Size=1, Index=None, Ordered=Insertion
Key : Dimen : Domain : Size : Members
None : 1 : Any : 2 : {0, 1}
2 Var Declarations
Total_Reach_Freq : Size=1, Index=None
Key : Lower : Value : Upper : Fixed : Stale : Domain
None : 0 : None : None : False : True : NonNegativeReals
x : Size=2, Index=x_index
Key : Lower : Value : Upper : Fixed : Stale : Domain
0 : 0 : None : 1755 : False : True : NonNegativeIntegers
1 : 0 : None : 2873 : False : True : NonNegativeIntegers
1 Objective Declarations
objective : Size=1, Index=None, Active=True
Key : Active : Sense : Expression
None : True : maximize : Total_Reach_Freq
3 Constraint Declarations
Constraint_Budget_lb : Size=1, Index=None, Active=True
Key : Lower : Body : Upper : Active
None : 8000.0 : 266*x[0] + 494*x[1] : +Inf : True
Constraint_Budget_ub : Size=1, Index=None, Active=True
Key : Lower : Body : Upper : Active
None : -Inf : 266*x[0] + 494*x[1] : 12000.0 : True
Constraint_Define_Objective : Size=1, Index=None, Active=True
Key : Lower : Body : Upper : Active
None : 0.0 : 1 - exp(-2.4208*x[0]) + 1 - exp(-1.6278*x[1]) - Total_Reach_Freq : 0.0 : True
7 Declarations: x_index x Total_Reach_Freq Constraint_Define_Objective objective Constraint_Budget_lb Constraint_Budget_ub
---------------------------------------------------------------------------------------------
Mixed-Integer Nonlinear Decomposition Toolbox in Pyomo (MindtPy)
---------------------------------------------------------------------------------------------
For more information, please visit https://pyomo.readthedocs.io/en/stable/contributed_packages/mindtpy.html
Original model has 3 constraints (1 nonlinear) and 0 disjunctions, with 3 variables, of which 0 are binary, 2 are integer, and 1 are continuous.
rNLP is the initial strategy being used.
===============================================================================================
Iteration | Subproblem Type | Objective Value | Primal Bound | Dual Bound | Gap | Time(s)
- Relaxed NLP 2 -inf 2 nan% 0.05
WARNING: DEPRECATED: The 'ignore_integrality' argument no longer has any
functionality. (deprecated in 6.4.2) (called from
1 MILP 2 -inf 2 nan% 0.17
* 1 Fixed NLP 1 1 2 100.00% 0.21
MILP main problem is infeasible. Problem may have no more feasible binary configurations.
MindtPy exiting due to MILP main problem infeasibility.
===============================================================================================
Primal integral : 0.0000
Dual integral : 0.0000
Primal-dual gap integral : 0.0000
Results=
Problem:
- Name: Pyomo Test Optimization
Lower bound: 1.0
Upper bound: 2.0000000000256444
Number of objectives: 1
Number of constraints: 3
Number of variables: 3
Number of binary variables: 0
Number of integer variables: 2
Number of continuous variables: 1
Number of nonzeros: None
Sense: maximize
Number of disjunctions: 0
Solver:
- Name: MindtPyOA
Status: ok
Message: None
User time: None
System time: None
Wallclock time: None
Termination condition: feasible
Termination message: None
Timing: Call after main solve: 1.1296011507511139e-05
Call after subproblem solve: 1.0070973075926304e-05
OA cut generation: 0.0011551158968359232
fixed subproblem: 0.028742596972733736
initialization: 0.1482729569543153
main loop: 0.07431022403761744
main: 0.0369135340442881
main_timer_start_time: 804870.380321695
total: 0.22866498795337975
Iterations: 2
Num infeasible nlp subproblem: 0
Best solution found time: 0.20502531598322093
Primal integral: 0.0
Dual integral: 0.0
Primal dual gap integral: 0.0
############################## Status ############################
results.solver.status = ok
results.solver.termination_condition = feasible
SolverStatus.warning = warning
Solution:
pe.value(model.objective)= 1.0
Decision variable x = [45.0, 0.0]
################################################################
R_i [1.0, 0.0]
sum (R_i)= 1.0
I eliminated the nonlinear constraint and instead define it in the objective function. Pyomo - Mindtpy is able to get the optimal solution. The Python code is below.
import pandas as pd
import numpy as np
import pyomo.environ as pe
from pyomo.opt import SolverStatus, TerminationCondition
from pyomo.util.infeasible import log_infeasible_constraints
model = pe.ConcreteModel(name="Pyomo Test Optimization")
########### Define integer variables x[0], x[1]
### such that 0 <= x[0] <= 1755
### 0 <= x[1] <= 2873
cap_list=[1755, 2873]
def cap_rule( model, i):
return (0, cap_list[i])
model.x = pe.Var( [0, 1], within=pe.NonNegativeIntegers, bounds= cap_rule )
C=[-2.4208, -1.6278]
##### Define objective variable Total_Reach_Freq >=0
### Total_Reach_Freq =[1-pe.exp( -2.4208*x[0])] +[1-pe.exp(-1.6278*x[1])]
# model.Total_Reach_Freq = pe.Var( within=pe.NonNegativeReals, bounds=(0, None) )
# model.Constraint_Define_Objective= pe.Constraint(expr= (1-pe.exp(C[0]*model.x[0])) +(1-pe.exp(C[1]*model.x[1])) == model.Total_Reach_Freq )
# model.objective = pe.Objective(expr=model.Total_Reach_Freq, sense= pe.maximize)
model.objective = pe.Objective(expr=(1-pe.exp(C[0]*model.x[0])) +(1-pe.exp(C[1]*model.x[1])), sense= pe.maximize)
##### Define constraints
#### 8000 <= 266*x[0]+494*x[1] <= 12000
D=[266, 494]
model.Constraint_Budget_lb= pe.Constraint(expr= D[0]*model.x[0]+D[1]*model.x[1] >= 8000 )
model.Constraint_Budget_ub= pe.Constraint(expr= D[0]*model.x[0]+D[1]*model.x[1] <= 12000 )
model.pprint()
results=pe.SolverFactory('mindtpy').solve(model, tee=True)
log_infeasible_constraints(model)
#model.load(results)
#model.solutions.load_from(results)
print()
print("Results=", results)
status= results.solver.termination_condition
print("############################## Status ############################")
print()
print( "results.solver.status =", results.solver.status)
print("results.solver.termination_condition =", results.solver.termination_condition )
print("SolverStatus.warning =", SolverStatus.warning)
print()
print("Solution:")
print()
print( "pe.value(model.objective)=", pe.value(model.objective) )
print( "Decision variable x =", [model.x[0].value, model.x[1].value ] )
print("################################################################")
R_var= [ np.round_( 1-pe.exp( C[0]*model.x[0].value ), decimals=4), np.round_( 1-pe.exp(C[1]*model.x[1].value), decimals=4) ]
print()
print("R_i", R_var)
print()
print("sum (R_i)=", np.round_(sum(R_var), decimals=2))
The output is below
1 Set Declarations
x_index : Size=1, Index=None, Ordered=Insertion
Key : Dimen : Domain : Size : Members
None : 1 : Any : 2 : {0, 1}
1 Var Declarations
x : Size=2, Index=x_index
Key : Lower : Value : Upper : Fixed : Stale : Domain
0 : 0 : None : 1755 : False : True : NonNegativeIntegers
1 : 0 : None : 2873 : False : True : NonNegativeIntegers
1 Objective Declarations
objective : Size=1, Index=None, Active=True
Key : Active : Sense : Expression
None : True : maximize : 1 - exp(-2.4208*x[0]) + 1 - exp(-1.6278*x[1])
2 Constraint Declarations
Constraint_Budget_lb : Size=1, Index=None, Active=True
Key : Lower : Body : Upper : Active
None : 8000.0 : 266*x[0] + 494*x[1] : +Inf : True
Constraint_Budget_ub : Size=1, Index=None, Active=True
Key : Lower : Body : Upper : Active
None : -Inf : 266*x[0] + 494*x[1] : 12000.0 : True
5 Declarations: x_index x objective Constraint_Budget_lb Constraint_Budget_ub
---------------------------------------------------------------------------------------------
Mixed-Integer Nonlinear Decomposition Toolbox in Pyomo (MindtPy)
---------------------------------------------------------------------------------------------
For more information, please visit https://pyomo.readthedocs.io/en/stable/contributed_packages/mindtpy.html
Original model has 2 constraints (0 nonlinear) and 0 disjunctions, with 2 variables, of which 0 are binary, 2 are integer, and 0 are continuous.
Objective is nonlinear. Moving it to constraint set.
rNLP is the initial strategy being used.
===============================================================================================
Iteration | Subproblem Type | Objective Value | Primal Bound | Dual Bound | Gap | Time(s)
- Relaxed NLP 2 -inf 2 nan% 0.06
WARNING: DEPRECATED: The 'ignore_integrality' argument no longer has any
functionality. (deprecated in 6.4.2) ()
1 MILP 2 -inf 2 nan% 0.18
* 1 Fixed NLP 1.99243 1.99243 2 0.38% 0.22
2 MILP 2 1.99243 2 0.38% 0.23
* 2 Fixed NLP 1.99851 1.99851 2 0.07% 0.27
MindtPy exiting on bound convergence. |Primal Bound: 1.998513324083709 - Dual Bound: 2.0| / (1e-10 + |Primal Bound|:1.998513324083709) <= relative tolerance: 0.001
===============================================================================================
Primal integral : 0.0013
Dual integral : 0.0000
Primal-dual gap integral : 0.0013
Results=
Problem:
- Name: Pyomo Test Optimization
Lower bound: 1.998513324083709
Upper bound: 2.0
Number of objectives: 1
Number of constraints: 2
Number of variables: 2
Number of binary variables: 0
Number of integer variables: 2
Number of continuous variables: 0
Number of nonzeros: None
Sense: maximize
Number of disjunctions: 0
Solver:
- Name: MindtPyOA
Status: ok
Message: None
User time: None
System time: None
Wallclock time: None
Termination condition: optimal
Termination message: None
Timing: Call after main solve: 2.035603392869234e-05
Call after subproblem solve: 1.256109680980444e-05
OA cut generation: 0.0016140680527314544
fixed subproblem: 0.06024683394934982
initialization: 0.1535441749729216
main loop: 0.10393345193006098
main: 0.02929913904517889
main_timer_start_time: 805180.471824162
total: 0.27106013789307326
Iterations: 2
Num infeasible nlp subproblem: 0
Best solution found time: 0.2668719069333747
Primal integral: 0.0013113116202245812
Dual integral: 1.0499250925580773e-09
Primal dual gap integral: 0.0013113126701496738
############################## Status ############################
results.solver.status = ok
results.solver.termination_condition = optimal
SolverStatus.warning = warning
Solution:
pe.value(model.objective)= 1.9985133053813313
Decision variable x = [23.0, 4.0]
################################################################
R_i [1.0, 0.9985]
sum (R_i)= 2.0

Related

Error handling in pyomo - division by zero

I am working on a linear optimization problem where I have a set of cities and a set of powerplants. The cities have an electricity demand that needs to be satisfied. However, in the context of my problem, in certain time periods, the cities have no electricity demand (from the power plants because they can produce some of their own). I do not think the specific details are very important so below is my best description of the issue.
The objective function contains the following term:
Term in objective function
I created the appropriate city and month sets and set up my objective function as:
sum(sum(1/model.monthly_demand[c,t]*model.theta[c] for c in model.cities) for t in model.months)
The issue clearly arises when monthly_demand[c,t] = 0 as i get a division by zero error. And I am not sure how to deal with this. Ideally I would like theta[c] to be set to zero in that case but I am unsure how to do this. I tried adding some if/else statements in the sum() function but this is not possible as far as I understand.
I think I can also define a function that is passed into the pyomo objective, so my idea was to try something like an if statement that sets theta[c] to zero when the monthly demand is zero, but this was not successful.
Another idea was to set the demands to something like 0.000001 but I would like this to be a last resort solution because I think it will cause issues.
You should just make a subset wherever convenient of the non-zero elements of demand. I am assuming demand is a parameter which would make sense, but you didn't specify. Then use that subset as the basis of summation. This will avoid including the empty/zero elements.
Note the elements of the objective in the printout of the model.
Code:
import pyomo.environ as pyo
demand_data = { ('LA', 0) : 20,
('LA', 1) : 0,
('SF', 1) : 15,
('NY', 0) : 20,
('NY', 1) : 30}
m = pyo.ConcreteModel('electricity')
m.C = pyo.Set(initialize=['LA', 'SF', 'NY'])
m.T = pyo.Set(initialize=list(range(2)))
# VARS
m.x = pyo.Var(m.C, m.T, domain=pyo.NonNegativeReals)
# PARAMS
m.demand = pyo.Param(m.C, m.T, initialize=demand_data, default=0)
# CONSTRAINTS
# ...
# OBJ
# make a subset of the non-zero demand periods
nonzero_demand_domain = {(c,t) for c in m.C for t in m.T if m.demand[c, t] > 0}
# use that in your objective...
m.obj = pyo.Objective(expr=sum(m.x[c, t] for c, t in nonzero_demand_domain))
m.pprint()
Output:
4 Set Declarations
C : Size=1, Index=None, Ordered=Insertion
Key : Dimen : Domain : Size : Members
None : 1 : Any : 3 : {'LA', 'SF', 'NY'}
T : Size=1, Index=None, Ordered=Insertion
Key : Dimen : Domain : Size : Members
None : 1 : Any : 2 : {0, 1}
demand_index : Size=1, Index=None, Ordered=True
Key : Dimen : Domain : Size : Members
None : 2 : C*T : 6 : {('LA', 0), ('LA', 1), ('SF', 0), ('SF', 1), ('NY', 0), ('NY', 1)}
x_index : Size=1, Index=None, Ordered=True
Key : Dimen : Domain : Size : Members
None : 2 : C*T : 6 : {('LA', 0), ('LA', 1), ('SF', 0), ('SF', 1), ('NY', 0), ('NY', 1)}
1 Param Declarations
demand : Size=6, Index=demand_index, Domain=Any, Default=0, Mutable=False
Key : Value
('LA', 0) : 20
('LA', 1) : 0
('NY', 0) : 20
('NY', 1) : 30
('SF', 1) : 15
1 Var Declarations
x : Size=6, Index=x_index
Key : Lower : Value : Upper : Fixed : Stale : Domain
('LA', 0) : 0 : None : None : False : True : NonNegativeReals
('LA', 1) : 0 : None : None : False : True : NonNegativeReals
('NY', 0) : 0 : None : None : False : True : NonNegativeReals
('NY', 1) : 0 : None : None : False : True : NonNegativeReals
('SF', 0) : 0 : None : None : False : True : NonNegativeReals
('SF', 1) : 0 : None : None : False : True : NonNegativeReals
1 Objective Declarations
obj : Size=1, Index=None, Active=True
Key : Active : Sense : Expression
None : True : minimize : x[NY,0] + x[NY,1] + x[LA,0] + x[SF,1]
7 Declarations: C T x_index x demand_index demand obj

pyomo: an indexed variable should be linear or integer depending on the variable index

I have a indexed variable New_UnitsBuilt[p] and this variabele should be integer for the index "GasPowerplant"
but linear for the index "batterystorage".
new_units_built_set = pyo.Set(initialize=list(params.Installable_units))
model.New_UnitsBuilt = pyo.Var(new_units_built_set, domain=(pyo.NonNegativeIntegers if p="GasPowerplant" else NonNegativeReals)
Please help me how to do this in pyomo.
I am new in pyomo
Best Greetings
Gerhard
There are a couple ways you can accomplish this. For the following, I am assuming that your params.Installable_units = ["GasPowerplant", "batterystorage"]:
If the number of elements in new_units_built_set is small, then you can use a dictionary:
model.new_units_built_set = pyo.Set(initialize=list(params.Installable_units))
model.New_UnitsBuilt = pyo.Var(model.new_units_built_set,
domain={"GasPowerplant": pyo.NonNegativeIntegers, "batterystorage": pyo.NonNegativeReals})
Or if there are a lot – or there is a simple formula to get the return value – you can use a function (rule):
model.new_units_built_set = pyo.Set(initialize=list(params.Installable_units))
def _new_unitsbuilt_domain(m, p):
return pyo.NonNegativeIntegers if p=="GasPowerplant" else pyo.NonNegativeReals
model.New_UnitsBuilt = pyo.Var(model.new_units_built_set, domain=_new_unitsbuilt_domain)
Or you can just set everything to one value and override later (assuming you are using a ConcreteModel):
model.new_units_built_set = pyo.Set(initialize=list(params.Installable_units))
model.New_UnitsBuilt = pyo.Var(model.new_units_built_set, domain=pyo.NonNegativeReals)
model.New_UnitsBuilt["GasPowerplant"].domain = pyo.NonNegativeIntegers
All of these will produce:
>>> model.pprint()
1 Set Declarations
new_units_built_set : Size=1, Index=None, Ordered=Insertion
Key : Dimen : Domain : Size : Members
None : 1 : Any : 2 : {'GasPowerplant', 'batterystorage'}
1 Var Declarations
New_UnitsBuilt : Size=2, Index=new_units_built_set
Key : Lower : Value : Upper : Fixed : Stale : Domain
GasPowerplant : 0 : None : None : False : True : NonNegativeIntegers
batterystorage : 0 : None : None : False : True : NonNegativeReals
2 Declarations: new_units_built_set New_UnitsBuilt

Pyomo: ERROR: evaluating expression: No value for uninitialized NumericValue object

I am working on an Pyomo optimization script. It is moderately complex with indexed parameters and variables and 8 constraints. If I leave the decision variables with no initial value, I get "ERROR: evaluating expression: No value for uninitialized NumericValue object" errors for constraints involving a decision variable. If I initialize the decision variables to 1 or 0 the solver(glpk) returns Status: optimal and objective function value = 0. All decision variables are also set to 0. I am looking for ideas about how to troubleshoot this. Below is relevant code (w/o data) and the output from the solver and model.display, which has been edited to make it more compact.
#Sets
#clinics
model.C = Set(initialize=list(clinics.keys()))
#clients
model.B = Set(initialize=list(client_blocks.keys()))
#scalar params
model.clt_stf_max = 10
model.tt_max = 45
model.selected_clinic_max = 4
model.stf_max = sum(staff_cap.values()) #total available staff is sum of clinic new staff capacity
model.z_M = 5000 #used in clients at selected clinics constraint
model.e_M = 500 #used in staff at selected clinics constraint
#indexed params
model.cnc_stf_cap = Param(C, initialize=staff_cap)
model.clt_blk = Param(B, initialize=client_blocks)
model.trav_time = Param(T, initialize=trav_time)
#decision vars
#x new staff at each clinic
model.new_stf = Var(model.C, domain=NonNegativeIntegers, initialize=0)
#y new clients at each clinic/block combo
model.new_clt = Var(model.C, model.B, domain=NonNegativeIntegers, initialize=0)
#z client at selected clinics only
model.z = Var(model.C, model.B, domain=Binary, initialize=0)
#e staff at selected clinics only
model.e = Var(model.C, domain=Binary, initialize=0)
#objective function
def o_min_tt_rule(model):
return sum(model.trav_time[c,b]*model.new_clt[c,b] for c in model.C for b in model.B)
model.o_min_tt = Objective(rule=o_min_tt_rule, sense=minimize)
#constraints
# limit new clients at clinic to staff capacity
def limit_clients_to_clinic_staff_cap_rule(model, c):
return sum(model.new_clt[c,b] for c in model.C for b in model.B) <= (model.cnc_stf_cap[c] * model.clt_stf_max)
model.limit_clients_to_clinic_staff_cap = Constraint(model.C, rule=limit_clients_to_clinic_staff_cap_rule)
#total of new clients served in block should not exceed the number new clients in the block
def limit_newclient_block_rule(model, b):
return sum(model.new_clt[c,b] for c in model.C for b in model.B) <= (model.clt_blk[b])
model.limit_newclient_block = Constraint(model.B, rule=limit_newclient_block_rule)
#limit new clients to selected clinics
def client_to_selected_clinic_rule(model, c, b):
return model.new_clt[c,b] <= model.z[c,b] * model.z_M
model.client_to_selected_clinic = Constraint(model.C, model.B, rule=client_to_selected_clinic_rule)
#limit single client travel time to max travel time minutes
def limit_client_travtime_rule(model, c, b):
return (model.trav_time[c,b] * model.z[c,b]) <= model.tt_max
model.limit_client_travtime = Constraint(model.C, model.B, rule=limit_client_travtime_rule)
#limit selected clinics to max number
def limit_selected_clinic_to_max_rule(model):
return summation(model.e) <= model.selected_clinic_max
model.limit_selected_clinic_to_max = Constraint(rule=limit_selected_clinic_to_max_rule,)
#limit new staff to selected clinics
def staff_to_selected_clinic_rule(model, c):
return model.new_stf[c] <= model.e[c] * model.e_M
model.staff_to_selected_clinic = Constraint(model.C, rule=staff_to_selected_clinic_rule)
#limit new staff at clinic to clinic capacity
def limit_staff_to_clnic_cap_rule(model, c):
return model.new_stf[c] <= model.cnc_stf_cap[c]
model.limit_staff_to_clnic_cap = Constraint(model.C, rule=limit_staff_to_clnic_cap_rule)
#limit total new staff to staff max
def limit_tot_staff_rule(model):
return summation(model.new_stf) <= model.stf_max
model.limit_tot_staff = Constraint(rule=limit_tot_staff_rule)
# solve the model
solver = SolverFactory("glpk", tee=True, warmstart=True, symbolic_solver_labels=True)
OUTPUT from solver and model.display()
SOLVER:
Problem:
- Name: unknown
Lower bound: 0.0
Upper bound: 0.0
Number of objectives: 1
Number of constraints: 753
Number of variables: 665
Number of nonzeros: 29213
Sense: minimize
Solver:
- Status: ok
Termination condition: optimal
Statistics:
Branch and bound:
Number of bounded subproblems: 1
Number of created subproblems: 1
Error rc: 0
Time: 0.10507559776306152
Solution:
- number of solutions: 1
number of solutions displayed: 1
- Gap: 0.0
Status: optimal
Message: None
Objective:
o_min_tt:
Value: 0
Variable: No nonzero values
Constraint: No values
MODEL:
Model (Staff_Allocation)
Variables:
new_stf : Size=4, Index=Clinics
Key : Lower : Value : Upper : Fixed : Stale : Domain
FEDCAP Counseling Center - 01004 : 0 : 0 : None : False : False : NonNegativeIntegers
The Salvation Army Belmont Center - 04002 : 0 : 0 : None : False : False : NonNegativeIntegers
VIP Wellness Center - 12000 : 0 : 0 : None : False : False : NonNegativeIntegers
Westchester Center of Excellence - 11006 : 0 : 0 : None : False : False : NonNegativeIntegers
new_clt : Size=392, Index=new_clt_index
Key : Lower : Value : Upper : Fixed : Stale : Domain
('FEDCAP Counseling Center - 01004', 360050019001022) : 0 : 0 : None : False : False : NonNegativeIntegers
z : Size=392, Index=z_index
Key : Lower : Value : Upper : Fixed : Stale : Domain
('FEDCAP Counseling Center - 01004', 360050019001022) : 0 : 0 : 1 : False : False : Binary
e : Size=4, Index=Clinics
Key : Lower : Value : Upper : Fixed : Stale : Domain
FEDCAP Counseling Center - 01004 : 0 : 0 : 1 : False : False : Binary
The Salvation Army Belmont Center - 04002 : 0 : 0 : 1 : False : False : Binary
VIP Wellness Center - 12000 : 0 : 0 : 1 : False : False : Binary
Westchester Center of Excellence - 11006 : 0 : 0 : 1 : False : False : Binary
Objectives:
o_min_tt : Size=1, Index=None, Active=True
Key : Active : Value
None : True : 0
Constraints:
limit_clients_to_clinic_staff_cap : Size=4
Key : Lower : Body : Upper
FEDCAP Counseling Center - 01004 : None : 0 : 200.0
The Salvation Army Belmont Center - 04002 : None : 0 : 150.0
VIP Wellness Center - 12000 : None : 0 : 150.0
Westchester Center of Excellence - 11006 : None : 0 : 250.0
limit_newclient_block : Size=98
Key : Lower : Body : Upper
360050019001022 : None : 0 : 1.0
.... 97 more like this
client_to_selected_clinic : Size=392
Key : Lower : Body : Upper
('FEDCAP Counseling Center - 01004', 360050019001022) : None : 0 : 0.0
.... 391 more like this
limit_client_travtime : Size=392
Key : Lower : Body : Upper
('FEDCAP Counseling Center - 01004', 360050019001022) : None : 0 : 75.0
.... 391 more like this
limit_selected_clinic_to_max : Size=1
Key : Lower : Body : Upper
None : None : 0 : 4.0
staff_to_selected_clinic : Size=4
Key : Lower : Body : Upper
FEDCAP Counseling Center - 01004 : None : 0 : 0.0
The Salvation Army Belmont Center - 04002 : None : 0 : 0.0
VIP Wellness Center - 12000 : None : 0 : 0.0
Westchester Center of Excellence - 11006 : None : 0 : 0.0
limit_staff_to_clnic_cap : Size=4
Key : Lower : Body : Upper
FEDCAP Counseling Center - 01004 : None : 0 : 4.0
The Salvation Army Belmont Center - 04002 : None : 0 : 3.0
VIP Wellness Center - 12000 : None : 0 : 3.0
Westchester Center of Excellence - 11006 : None : 0 : 5.0
limit_tot_staff : Size=1
Key : Lower : Body : Upper
None : None : 0 : 15.0

Find position of first non-zero decimal

Suppose I have the following local macro:
loc a = 12.000923
I would like to get the decimal position of the first non-zero decimal (4 in this example).
There are many ways to achieve this. One is to treat a as a string and to find the position of .:
loc a = 12.000923
loc b = strpos(string(`a'), ".")
di "`b'"
From here one could further loop through the decimals and count since I get the first non-zero element. Of course this doesn't seem to be a very elegant approach.
Can you suggest a better way to deal with this? Regular expressions perhaps?
Well, I don't know Stata, but according to the documentation, \.(0+)? is suported and it shouldn't be hard to convert this 2 lines JavaScript function in Stata.
It returns the position of the first nonzero decimal or -1 if there is no decimal.
function getNonZeroDecimalPosition(v) {
var v2 = v.replace(/\.(0+)?/, "")
return v2.length !== v.length ? v.length - v2.length : -1
}
Explanation
We remove from input string a dot followed by optional consecutive zeros.
The difference between the lengths of original input string and this new string gives the position of the first nonzero decimal
Demo
Sample Snippet
function getNonZeroDecimalPosition(v) {
var v2 = v.replace(/\.(0+)?/, "")
return v2.length !== v.length ? v.length - v2.length : -1
}
var samples = [
"loc a = 12.00012",
"loc b = 12",
"loc c = 12.012",
"loc d = 1.000012",
"loc e = -10.00012",
"loc f = -10.05012",
"loc g = 0.0012"
]
samples.forEach(function(sample) {
console.log(getNonZeroDecimalPosition(sample))
})
You can do this in mata in one line and without using regular expressions:
foreach x in 124.000923 65.020923 1.000022030 0.0090843 .00000425 {
mata: selectindex(tokens(tokens(st_local("x"), ".")[selectindex(tokens(st_local("x"), ".") :== ".") + 1], "0") :!= "0")[1]
}
4
2
5
3
6
Below, you can see the steps in detail:
. local x = 124.000823
. mata:
: /* Step 1: break Stata's local macro x in tokens using . as a parsing char */
: a = tokens(st_local("x"), ".")
: a
1 2 3
+----------------------------+
1 | 124 . 000823 |
+----------------------------+
: /* Step 2: tokenize the string in a[1,3] using 0 as a parsing char */
: b = tokens(a[3], "0")
: b
1 2 3 4
+-------------------------+
1 | 0 0 0 823 |
+-------------------------+
: /* Step 3: find which values are different from zero */
: c = b :!= "0"
: c
1 2 3 4
+-----------------+
1 | 0 0 0 1 |
+-----------------+
: /* Step 4: find the first index position where this is true */
: d = selectindex(c :!= 0)[1]
: d
4
: end
You can also find the position of the string of interest in Step 2 using the
same logic.
This is the index value after the one for .:
. mata:
: k = selectindex(a :== ".") + 1
: k
3
: end
In which case, Step 2 becomes:
. mata:
:
: b = tokens(a[k], "0")
: b
1 2 3 4
+-------------------------+
1 | 0 0 0 823 |
+-------------------------+
: end
For unexpected cases without decimal:
foreach x in 124.000923 65.020923 1.000022030 12 0.0090843 .00000425 {
if strmatch("`x'", "*.*") mata: selectindex(tokens(tokens(st_local("x"), ".")[selectindex(tokens(st_local("x"), ".") :== ".") + 1], "0") :!= "0")[1]
else display " 0"
}
4
2
5
0
3
6
A straighforward answer uses regular expressions and commands to work with strings.
One can select all decimals, find the first non 0 decimal, and finally find its position:
loc v = "123.000923"
loc v2 = regexr("`v'", "^[0-9]*[/.]", "") // 000923
loc v3 = regexr("`v'", "^[0-9]*[/.][0]*", "") // 923
loc first = substr("`v3'", 1, 1) // 9
loc first_pos = strpos("`v2'", "`first'") // 4: position of 9 in 000923
di "`v2'"
di "`v3'"
di "`first'"
di "`first_pos'"
Which in one step is equivalent to:
loc first_pos2 = strpos(regexr("`v'", "^[0-9]*[/.]", ""), substr(regexr("`v'", "^[0-9]*[/.][0]*", ""), 1, 1))
di "`first_pos2'"
An alternative suggested in another answer is to compare the lenght of the decimals block cleaned from the 0s with that not cleaned.
In one step this is:
loc first_pos3 = strlen(regexr("`v'", "^[0-9]*[/.]", "")) - strlen(regexr("`v'", "^[0-9]*[/.][0]*", "")) + 1
di "`first_pos3'"
Not using regex but log10 instead (which treats a number like a number), this function will:
For numbers >= 1 or numbers <= -1, return with a positive number the number of digits to the left of the decimal.
Or (and more specifically to what you were asking), for numbers between 1 and -1, return with a negative number the number of digits to the right of the decimal where the first non-zero number occurs.
digitsFromDecimal = (n) => {
dFD = Math.log10(Math.abs(n)) | 0;
if (n >= 1 || n <= -1) { dFD++; }
return dFD;
}
var x = [118.8161330, 11.10501660, 9.254180571, -1.245501523, 1, 0, 0.864931613, 0.097007836, -0.010880074, 0.009066729];
x.forEach(element => {
console.log(`${element}, Digits from Decimal: ${digitsFromDecimal(element)}`);
});
// Output
// 118.816133, Digits from Decimal: 3
// 11.1050166, Digits from Decimal: 2
// 9.254180571, Digits from Decimal: 1
// -1.245501523, Digits from Decimal: 1
// 1, Digits from Decimal: 1
// 0, Digits from Decimal: 0
// 0.864931613, Digits from Decimal: 0
// 0.097007836, Digits from Decimal: -1
// -0.010880074, Digits from Decimal: -1
// 0.009066729, Digits from Decimal: -2
Mata solution of Pearly is very likable, but notice should be paid for "unexpected" cases of "no decimal at all".
Besides, the regular expression is not a too bad choice when it could be made in a memorable 1-line.
loc v = "123.000923"
capture local x = regexm("`v'","(\.0*)")*length(regexs(0))
Below code tests with more values of v.
foreach v in 124.000923 605.20923 1.10022030 0.0090843 .00000425 12 .000125 {
capture local x = regexm("`v'","(\.0*)")*length(regexs(0))
di "`v': The wanted number = `x'"
}

Strange behaviour when adding columns

I'm using Python 2.7.8 |Anaconda 2.1.0. I'm wondering why the strange behavior below occurs
I create a pandas dataframe with two columns, then add a third column by summing the first two columns
x = pd.DataFrame(np.random.randn(5, 2), columns = ['a', 'b'])
x['c'] = x[['a', 'b']].sum(axis = 1) #or x['c'] = x['a'] + x['b']
Out[7]:
a b c
0 -1.644246 0.851602 -0.792644
1 -0.129092 0.237140 0.108049
2 0.623160 0.105494 0.728654
3 0.737803 -1.612189 -0.874386
4 0.340671 -0.113334 0.227337
All good so far. Now I want to set the values of column c to zero if they are negative
x[x['c']<0] = 0
Out[9]:
a b c
0 0.000000 0.000000 0.000000
1 -0.129092 0.237140 0.108049
2 0.623160 0.105494 0.728654
3 0.000000 0.000000 0.000000
4 0.340671 -0.113334 0.227337
This gives the desired result in column 'c', but for some reason columns 'a' and 'b' have been modified - i don't want this to happen. I was wondering why this is happening and how I can fix this behavior?
You have to specify you only want the 'c' column:
x.loc[x['c']<0, 'c'] = 0
When you just index with a boolean array/series, this will select full rows, as you can see in this example:
In [46]: x['c']<0
Out[46]:
0 True
1 False
2 False
3 True
4 False
Name: c, dtype: bool
In [47]: x[x['c']<0]
Out[47]:
a b c
0 -0.444493 -0.592318 -1.036811
3 -1.363727 -1.572558 -2.936285
Because you are setting to zero for all the columns. You should set it only for column c
x['c'][x['c']<0] = 0