Pyomo: Create Abstract model and AMPL data - python

I am just starting with Pyomo and I have a big problem. I want to create an Abstract Model and use AMPL data format to feed it.
The question is a classic transportation problem. I need to find the optimal solution for cost. M means if the shipment is impossible between a given source and destination, a large cost of M is entered. I need to convert it into AMPL data. Apart from this, I do not know how to create this abstract model. The code for this table and model are shown below. Also after reading this problem, I created the mathematical model as follows.
[the mathematical model that I created][1]
[the classic transportation problem tabel][2]
from __future__ import division
from pyomo.environ import *
model = AbstractModel()
model.I = Set()
model.J = Set()
model.a = Param(model.I)
model.b = Param(model.J)
model.cost = Param(model.I,model.J)
model.supply = Var(model.I,model.J)
def obj_expression(model):
return sum(model.supply[i,j] * model.cost[i,j] for i in model.I for j in model.J)
model.OBJ = Objective(rule=obj_expression)
def ax_constraint_rule_1(model, i):
return sum(model.supply[i,j] for j in model.J )<= model.a[i]
def ax_constraint_rule_2(model, j):
return sum(model.supply[i,j] for i in model.I )>= model.b[j]
model.AxbConstraint = Constraint(model.I, rule=ax_constraint_rule_1)
model.AxbConstraint_2 = Constraint(model.J, rule=ax_constraint_rule_2)
pyomo solve --solver=glpk test.py transportation_data.dat
model.pprint()
set I := D1 D2 D3 ;
set J := S1 S2 S3 ;
param cost :=
S1 D1 3
S1 D2 1
S2 D1 4
S2 D2 2
S2 D3 4
S3 D2 3
S3 D3 3
;
param b :=
D1 7
D2 3
D3 5 ;
param a:=
S1 5
S2 7
S3 3
;
Any help with this code? Really need help with the model creation and AMPL data construction.
Thanks anyway
===============================================
The result
File "E:/pycharm_project/test.py", line 28
pyomo solve --solver=glpk test.py transportation_data.dat
^
SyntaxError: invalid syntax```
[1]: https://i.stack.imgur.com/DoWXA.png
[2]: https://i.stack.imgur.com/Fwmjb.png

Well, you are close, I think. You have a few things to clean up.
You do NOT need model.m and model.n I'm not sure what you are trying to do there.
For the sets, I and J, just list them as Set(), because you are providing values for them in your AMPL data. Like:
model.I = Set()
model.J = Set()
In your formulation you are double indexing c[i,j] but in your formulation and data, c is only indexed by model.I
Similarly, in your model you are only single-indexing a[i], but you have a double index on it in your data and formulation.
In your constraints, the definitions should only hold a variable for the "for each" part, not the variable you are summing over.
Clean that stuff up, give it a whirl, comment me back if it is still broke.
Edit: A couple more items.....
I'd recommend naming your parameters and sets intuitively, such as:
model.supply, model.cost, etc. Makes it MUCH easier to read & troubleshoot. :)
============
Edit #2: Your code cleanup is vastly improved. A couple cleanup items remain:
In your constraints, you only need to pass variable for the "for each" side of the equation if you are summing over the other variable. You are making model.I constraints here, so this is appropriate:
def ax_constraint_rule_1(model, i): # note removal of j
return sum(model.supply[i,j] for j in model.J ) <= model.a[i]
Note that the summation here is over j for each i so I changed your for loop also.
flip that around for the other constraint.
your a, b, cost, supply don't line up with your data names. Check them all. In your data, a appears to be cost[i, j]
your cost data is also missing some values!
In AMPL syntax, set is not capitalized. If you get it running, it will barf and call out that error.

Here's my cut:
from pyomo.environ import *
model = AbstractModel()
# model.m = Param(within=NonNegativeIntegers)
# model.n = Param(within=NonNegativeIntegers)
model.S = Set() # Sources
model.D = Set() # Destinations
model.cost = Param(model.S, model.D) # cost from S->D
model.supply = Param(model.S) # supply at source S
model.demand = Param(model.D) # demad at destination D
model.x = Var(model.S, model.D, domain=NonNegativeReals)
### OBJECTIVE FUNCTION ###
# calculate total cost of decisions
def obj_expression(model):
return sum(model.x[s, d] * model.cost[s, d] for s in model.S for d in model.D)
model.OBJ = Objective(rule=obj_expression)
### CONSTRAINTS ###
# ensure that supply constraint is met for each source in model.S
def supply_constraint(model, s):
return sum(model.x[s, d] for d in model.D ) <= model.supply[s]
# ensure that demand constraint is met for each destination in model.D
def demand_constraint(model, d):
return sum(model.x[s, d] for s in model.S ) >= model.demand[d]
model.sup_constraint = Constraint(model.S, rule=supply_constraint)
model.dem_constraint = Constraint(model.D, rule=demand_constraint)
model.pprint()
Data file
set D := D1 D2 D3 ;
set S := S1 S2 S3 ;
param cost :=
S1 D1 3
S1 D2 1
S1 D3 10
S2 D1 4
S2 D2 2
S2 D3 4
S3 D1 10
S3 D2 3
S3 D3 3
;
param demand :=
D1 7
D2 3
D3 5 ;
param supply :=
S1 5
S2 7
S3 3
;
Output:
% pyomo solve --solver=glpk transpo_model.py transpo.dat --summary
[ 0.00] Setting up Pyomo environment
[ 0.00] Applying Pyomo preprocessing actions
4 Set Declarations
D : Dim=0, Dimen=1, Size=0, Domain=None, Ordered=False, Bounds=None
Not constructed
S : Dim=0, Dimen=1, Size=0, Domain=None, Ordered=False, Bounds=None
Not constructed
cost_index : Dim=0, Dimen=2, Size=0, Domain=None, Ordered=False, Bounds=None
Virtual
x_index : Dim=0, Dimen=2, Size=0, Domain=None, Ordered=False, Bounds=None
Virtual
3 Param Declarations
cost : Size=0, Index=cost_index, Domain=Any, Default=None, Mutable=False
Not constructed
demand : Size=0, Index=D, Domain=Any, Default=None, Mutable=False
Not constructed
supply : Size=0, Index=S, Domain=Any, Default=None, Mutable=False
Not constructed
1 Var Declarations
x : Size=0, Index=x_index
Not constructed
1 Objective Declarations
OBJ : Size=0, Index=None, Active=True
Not constructed
2 Constraint Declarations
dem_constraint : Size=0, Index=D, Active=True
Not constructed
sup_constraint : Size=0, Index=S, Active=True
Not constructed
11 Declarations: S D cost_index cost supply demand x_index x OBJ sup_constraint dem_constraint
[ 0.29] Creating model
[ 0.32] Applying solver
[ 0.33] Processing results
Number of solutions: 1
Solution Information
Gap: 0.0
Status: feasible
Function Value: 46.0
Solver results file: results.json
==========================================================
Solution Summary
==========================================================
Model unknown
Variables:
x : Size=9, Index=x_index
Key : Lower : Value : Upper : Fixed : Stale : Domain
('S1', 'D1') : 0 : 5.0 : None : False : False : NonNegativeReals
('S1', 'D2') : 0 : 0.0 : None : False : False : NonNegativeReals
('S1', 'D3') : 0 : 0.0 : None : False : False : NonNegativeReals
('S2', 'D1') : 0 : 2.0 : None : False : False : NonNegativeReals
('S2', 'D2') : 0 : 3.0 : None : False : False : NonNegativeReals
('S2', 'D3') : 0 : 2.0 : None : False : False : NonNegativeReals
('S3', 'D1') : 0 : 0.0 : None : False : False : NonNegativeReals
('S3', 'D2') : 0 : 0.0 : None : False : False : NonNegativeReals
('S3', 'D3') : 0 : 3.0 : None : False : False : NonNegativeReals
Objectives:
OBJ : Size=1, Index=None, Active=True
Key : Active : Value
None : True : 46.0
Constraints:
sup_constraint : Size=3
Key : Lower : Body : Upper
S1 : None : 5.0 : 5.0
S2 : None : 7.0 : 7.0
S3 : None : 3.0 : 3.0
dem_constraint : Size=3
Key : Lower : Body : Upper
D1 : 7.0 : 7.0 : None
D2 : 3.0 : 3.0 : None
D3 : 5.0 : 5.0 : None
[ 0.33] Applying Pyomo postprocessing actions
[ 0.33] Pyomo Finished

Related

Create different restrictions with a single rule

I am having trouble creating multiple constraints with a single rule. The problem is the following:
I have "n" decision variables in my problem and I have "m" constraints formed by the sum of some of the decision variables, if for example (n=7) and (m=3), the constraints could be the following:
n1+n2+n3+n4=m1
n3+n4+n5=m2
n5+n6+n7=m3
The index of the decision variables I have it in a list and m value of each constraint I have it in a dictionary.
N = [1,2,3,4,5,6,7]
M = {1:20,2:12,3:18}
I define the variables
model.N = Var(N, within=NonNegativeReals)
The variables that I have to add in each constraint I have them stored in a dictionary, so that the key of each element of the dictionary represents the constraint m and the value is a list formed by the decision variables that are part of the constraint.
d = {1: [1,2,3,4], \
2: [3,4,5], \
3: [5,6,7],}
I have tried two different codes. The first one is shown below
def node_limit_rule (model,m):
# We extract the list that defines each constraint.
constraint_list = d.values()
for element in constraint_list:
ConcreteConstraint=element
return sum(model.N[cc] for cc in ConcreteConstrain) <= d[cc] for cc in ConcreteConstrains)
model.node_limit = Constraint(mydataWFLP.M, rule=node_limit_rule)
Using this code what I get is always the first constraint repeated m times
Key : Lower : Body : Upper : Active
1 : -Inf : N[1] + N[2] + N[3] + N[4] : 20.0 : True
2 : -Inf : N[1] + N[2] + N[3] + N[4] : 20.0 : True
3 : -Inf : N[1] + N[2] + N[3] + N[4] : 20.0 : True
When in fact it should be as follows
Key : Lower : Body : Upper : Active
1 : -Inf : N[1] + N[2] + N[3] + N[4] : 20.0 : True
2 : -Inf : N[3] + N[4] + N[5] : 12.0 : True
3 : -Inf : N[5] + N[6] + N[7] : 18.0 : True
What am I doing wrong? How can I fix it?
The second code I have tried is the following
constraint_list = d.values()
for element in constraint_list:
ConcreteConstraint=element
for m in M:
def node_limit_rule (model):
return sum(model.N[cc] for cc in ConcreteConstrain) <= d[cc] for cc in ConcreteConstrains)
model.node_limit = Constraint(mydataWFLP.M,rule=node_limit_rule)
But in this case I get only get one equation. Specifically the third one
Key : Lower : Body : Upper : Active
None : -Inf : N[5] + N[6] + N[7] : 18 : True
I think I should store each iteration of the for loop in a variable. Something similar to what is shown below but I don't know how to do it.
model.node_limit_1
model.node_limit_2
model.node_limit_3

How to understand this Pyomo Constraint (rewriting the constraint)

I am new to Pyomo.
I wrote this constraint in Pyomo written below as shown in this equation:
model.amount_of_energy_con = pe.ConstraintList()
for t in model.time:
for b in model.boats:
for s in model.chargers:
lhs = model.charge_energy[b, t, s]
rhs = model.c_rating[s] * model.boat_battery_capacity * model.boats_availability[b][t] * model.charging[b, t, s]
model.amount_of_energy_con.add(expr= (lhs <= rhs))
For the Constraint above, I think my constraint should be something like this in the model object
Key : Lower : Body : Upper : Active
1 : -Inf : charge_energy[1,0,SC] : 15.75*charging[1,0,SC] : True
2 : -Inf : charge_energy[1,0,FC] : 126*charging[1,0,FC] : True
But I was getting this below using model.amount_of_energy_con.pprint()
Key : Lower : Body : Upper : Active
1 : -Inf : charge_energy[1,0,SC] - 15.75*charging[1,0,SC] : 0.0 : True
2 : -Inf : charge_energy[1,0,FC] - 126*charging[1,0,FC] : 0.0 : True
Note: The 0 in the equation was added as bounds when setting up the model.charge_energy Variable model.charge_energy = pe.Var(model.boats, model.time, model.chargers, bounds=(0, None)) and I still don't understand why my Lower is -Inf.
What am I doing wrong?

Pyomo assignes floats to Integer domain

Having a simplified problem: I'd like to assign an instance ID anywhere in in the instance_map. The goal is to have the ID's uniquely distributed over the instance_map, so each ID should occur exatcly once.
Pyomo in turn raises that this task is unfeasbile and most surprisingly started assigning with floats in an integer domain. Here's the code
import pyomo.environ as pe
model = pe.ConcreteModel()
model.rows = pe.RangeSet(1, 2)
model.cols = pe.RangeSet(1, 5)
model.instances = pe.RangeSet(1, 5)
model.n_instances = pe.Var(initialize=5)
model.n_cols = pe.Var(initialize=5)
model.n_rows = pe.Var(initialize=2)
model.instances_map = pe.Var(model.rows, model.cols, within=pe.Integers, initialize=0, bounds=(0, model.n_instances.value))
def unique_instances_check(model, instance):
if instance == 0:
return sum(model.instances_map[i,j] == instance for i in model.rows for j in model.cols) >= 0
else:
return sum(model.instances_map[i,j] == instance for i in model.rows for j in model.cols) == 1
model.C1 = pe.Constraint(model.instances, rule=unique_instances_check)
def objective(model):
return sum(model.instances_map[1,j] for j in model.cols)
model.obj = pe.Objective(rule=objective, sense=pe.minimize)
opt = pe.SolverFactory("ipopt").solve(model)
model.instances_map.pprint()
When running it I get the the following output for the last code line
WARNING: Loading a SolverResults object with a warning status into
model.name="unknown";
- termination condition: infeasible
- message from solver: Ipopt 3.14.5\x3a Converged to a locally
infeasible point. Problem may be infeasible.
instances_map : Size=10, Index=instances_map_index
Key : Lower : Value : Upper : Fixed : Stale : Domain
(1, 1) : 0 : 5.000000049983252 : 5 : False : False : Integers
(1, 2) : 0 : 5.000000049983252 : 5 : False : False : Integers
(1, 3) : 0 : 5.000000049983252 : 5 : False : False : Integers
(1, 4) : 0 : 5.000000049983252 : 5 : False : False : Integers
(1, 5) : 0 : 5.000000049983252 : 5 : False : False : Integers
(2, 1) : 0 : 5.000000049983252 : 5 : False : False : Integers
(2, 2) : 0 : 5.000000049983252 : 5 : False : False : Integers
(2, 3) : 0 : 5.000000049983252 : 5 : False : False : Integers
(2, 4) : 0 : 5.000000049983252 : 5 : False : False : Integers
(2, 5) : 0 : 5.000000049983252 : 5 : False : False : Integers
I was expecting many 0 assignments but 1,2,3,4,5 only once.
I'm honestly not sure where to go from here
First, your problem is not converging to a feasible point, so there is no guarantee that the returned solution respects any constraints or bounds.
More importantly, ipopt is a continuous interior point solver and ignores discrete domains. If you look at the solver output (by adding tee=True' to the solve call), you should see:
==> Warning: Treating __ binary and __ integer variables as continous.
at the top of the solver output log.

Pyomo: TypeError: unhashable type: 'OrderedScalarSet'

Dears,
I am new on Python and Pyomo as well.
I'm creating an abstract model, below there are the sets, parameters and variables related to my error:
SETS:
SCUC.UP = Set()
SCUC.zone = Set()
from .dat file
"set UP := G1 G2 G3 G4 G5 G6;
set zone := Z1 Z2 Z3;"
PARAMETERS:
SCUC.Pmi = Param(SCUC.UP)
SCUC.zonal_UP = Param(SCUC.UP, SCUC.zone)
from .dat file
"param Pmi :=
G1 300.0
G2 200.0
G3 350.0
G4 210.0
G5 200.0
G6 240.0;
param zonal_UP :=
G1 Z1 1
G2 Z1 1
G3 Z1 0
G4 Z1 0
G5 Z1 0
G6 Z1 0
G1 Z2 0
G2 Z2 0
G3 Z2 1
G4 Z2 1
G5 Z2 0
G6 Z2 0
G1 Z3 0
G2 Z3 0
G3 Z3 0
G4 Z3 0
G5 Z3 1
G6 Z3 1;"
VARIABLES:
SCUC.UP_gz = Var(SCUC.zone)
SCUC.DPg_g = Var(SCUC.UP, within = Reals)
I'm trying to type three constraints where I need to multiply a vector of variables/parameters with a vector of parameters to obtain a scalar value.
Here the constraint formulation:
def zonal_gen_rule(SCUC,z):
return SCUC.UP_gz[z] == SCUC.Pmi[SCUC.UP] * SCUC.zonal_UP[SCUC.UP,z] + SCUC.zonal_UP[SCUC.UP,z] * SCUC.DPg_g[SCUC.UP]
SCUC.zonal_gen = Constraint(SCUC.zone, rule=zonal_gen_rule)
But when I lunch the instance construction it appears the following error:
TypeError: unhashable type: 'OrderedScalarSet'
It also tried to transpose the vectors with numpy but it didn't work.
I hope someone could help me.
You are getting that error because you are passing the entire set into your expression, instead of an element of the set. And as the whole set is "unhashable" you are getting the error
SCUC.UP_gz[z] == SCUC.Pmi[SCUC.UP] * ...
^
this is illegal... it is the whole set
It isn't clear what type of constraint you are trying to make from the context of your question... Meaning it isn't clear if you intend to sum across the members of SCUC.UP or make a constraint for each pair... So the example below shows "both ways" of doing this.
Also, a little standardization in use of caps will help you troubleshoot. Convention is that all variables are lower case, however, convention in set notation sometimes has caps for the set name and lower for members.... either way, consistency is key to troubleshooting.
from pyomo.environ import *
SCUC = ConcreteModel('example')
# SETS
SCUC.ups = Set(initialize=['G1', 'G2', 'G3'])
SCUC.zones = Set(initialize=['Z1', 'Z2'])
# PARAMS
SCUC.pmi = Param(SCUC.ups, initialize=2) # junk initialization...
SCUC.zonal_up = Param(SCUC.ups, SCUC.zones, initialize=3)
# VARIABLES
SCUC.u = Var(SCUC.zones)
SCUC.d = Var(SCUC.ups)
# CONSTRAINTS
# "for each" (up, zone) pair...
def zonal_gen_rule(SCUC, zone, up):
return SCUC.u[zone] == SCUC.pmi[up] * SCUC.zonal_up[up, zone] \
+ SCUC.zonal_up[up, zone] * SCUC.d[up]
SCUC.zonal_gen_1 = Constraint(SCUC.zones, SCUC.ups, rule=zonal_gen_rule)
# "for each zone, sum the ups"
def zonal_gen_rule_2(SCUC, zone):
return SCUC.u[zone] == sum(SCUC.pmi[up] * SCUC.zonal_up[up, zone] \
+ SCUC.zonal_up[up, zone] * SCUC.d[up] for up in SCUC.ups)
SCUC.zonal_gen_2 = Constraint(SCUC.zones, rule=zonal_gen_rule_2)
SCUC.pprint()
Yields:
...
2 Constraint Declarations
zonal_gen_1 : Size=6, Index=zonal_gen_1_index, Active=True
Key : Lower : Body : Upper : Active
('Z1', 'G1') : 0.0 : u[Z1] - (6 + 3*d[G1]) : 0.0 : True
('Z1', 'G2') : 0.0 : u[Z1] - (6 + 3*d[G2]) : 0.0 : True
('Z1', 'G3') : 0.0 : u[Z1] - (6 + 3*d[G3]) : 0.0 : True
('Z2', 'G1') : 0.0 : u[Z2] - (6 + 3*d[G1]) : 0.0 : True
('Z2', 'G2') : 0.0 : u[Z2] - (6 + 3*d[G2]) : 0.0 : True
('Z2', 'G3') : 0.0 : u[Z2] - (6 + 3*d[G3]) : 0.0 : True
zonal_gen_2 : Size=2, Index=zones, Active=True
Key : Lower : Body : Upper : Active
Z1 : 0.0 : u[Z1] - (6 + 3*d[G1] + 6 + 3*d[G2] + 6 + 3*d[G3]) : 0.0 : True
Z2 : 0.0 : u[Z2] - (6 + 3*d[G1] + 6 + 3*d[G2] + 6 + 3*d[G3]) : 0.0 : True
First reason: This type of error occurs when you try to insert a mutable type object as a key in a dict. Mutable objects are those objects whose value can be changed on run time for eg: Lists are mutable objects.
Second reason: you have implemented __eq__ method in your class because of which hashing no longer works on your class, you can fix this by implementing __hash__ method in your class.
In your case it looks like your object is unhashable. Try to implement __hash__ method in your class.
you can learn more about these methods here: https://docs.python.org/3/reference/datamodel.html

PYOMO ERROR: KeyError: "Index '0' is not valid for indexed component 'y' "

This is the hole Error:
ERROR: Rule failed when generating expression for objective FObj: KeyError:
"Index '4' is not valid for indexed component 'y'"
ERROR: Constructing component 'FObj' from data=None failed:
KeyError: "Index '4' is not valid for indexed component 'y'"
I've proved everything, checking every RangeSet and it's ok so I don't why it doesn't work well. Thanks for reading this, if anyone could help...
from pyomo.environ import *
from pyomo.opt import SolverFactory
from pyomo.core.base.PyomoModel import AbstractModel
from pyomo.core.base.constraint import Constraint
from pyomo.core.base.set import RangeSet
#import pyomo.dae
import numpy as np
import logging
logging.getLogger('pyomo.core').setLevel(logging.ERROR)
model = AbstractModel()
model.personas = RangeSet(0, 29)
model.sabados = RangeSet(0,3)
model.y = Var(model.personas,model.sabados, within = Binary)
def ObjFunction(model):
return sum(model.y[i][s] for i in model.personas for s in model.sabados)
model.FObj= Objective(rule=ObjFunction, sense = maximize)
Problem discovered. I think you must have just changed the model type to Abstract as when I change it back to Concrete the problem with y shows up.
You are indexing model.y with double indexing (Python standard). Pyomo ... for whatever reason ... uses comma separated indices for multiple indexing. Note the change in my code below. If this is a head-hurter, I've built models and put the indices in a tuple just to keep myself sane. Such as: model.y[(i, s)] which is unnecessary, but works and makes it look more distinct for pyomo.
Couple other notes...
I removed some of the unnecceary imports. One was causing some kind
of warning.
I chopped down your indices just to see a smaller printout
from pyomo.environ import *
from pyomo.opt import SolverFactory
#from pyomo.core.base.PyomoModel import AbstractModel
#from pyomo.core.base.constraint import Constraint
#from pyomo.core.base.set import RangeSet
#import pyomo.dae
import numpy as np
import logging
#logging.getLogger('pyomo.core').setLevel(logging.ERROR)
model = ConcreteModel()
model.personas = RangeSet(0, 3)
model.sabados = RangeSet(0,2)
model.y = Var(model.personas,model.sabados, within = Binary)
def ObjFunction(model):
return sum(model.y[i,s] for i in model.personas for s in model.sabados)
model.FObj= Objective(rule=ObjFunction, sense = maximize)
model.pprint()
Yields:
1 Set Declarations
y_index : Dim=0, Dimen=2, Size=12, Domain=None, Ordered=True, Bounds=None
Virtual
2 RangeSet Declarations
personas : Dim=0, Dimen=1, Size=4, Domain=Integers, Ordered=True, Bounds=(0, 3)
Virtual
sabados : Dim=0, Dimen=1, Size=3, Domain=Integers, Ordered=True, Bounds=(0, 2)
Virtual
1 Var Declarations
y : Size=12, Index=y_index
Key : Lower : Value : Upper : Fixed : Stale : Domain
(0, 0) : 0 : None : 1 : False : True : Binary
(0, 1) : 0 : None : 1 : False : True : Binary
(0, 2) : 0 : None : 1 : False : True : Binary
(1, 0) : 0 : None : 1 : False : True : Binary
(1, 1) : 0 : None : 1 : False : True : Binary
(1, 2) : 0 : None : 1 : False : True : Binary
(2, 0) : 0 : None : 1 : False : True : Binary
(2, 1) : 0 : None : 1 : False : True : Binary
(2, 2) : 0 : None : 1 : False : True : Binary
(3, 0) : 0 : None : 1 : False : True : Binary
(3, 1) : 0 : None : 1 : False : True : Binary
(3, 2) : 0 : None : 1 : False : True : Binary
1 Objective Declarations
FObj : Size=1, Index=None, Active=True
Key : Active : Sense : Expression
None : True : maximize : y[0,0] + y[0,1] + y[0,2] + y[1,0] + y[1,1] + y[1,2] + y[2,0] + y[2,1] + y[2,2] + y[3,0] + y[3,1] + y[3,2]
5 Declarations: personas sabados y_index y FObj
[Finished in 2.6s]

Categories