SolverStudio & PuLP

PuLP in SolverStudio
PuLP is a modelling environment for building linear and integer programmes within Python. SolverStudio can run PuLP both using the built-in IronPython (via the SolverStudio language “PuLP (IronPython)” — this is the recommended choice) or using your own external (i.e. manually installed outside SolverStudio) Python installation (via the SolverStudio language “Python (external)”. (Read more about external Python in SolverStudio.)

PuLP comes by default with SolverStudio – you do not need to install it.

Using PuLP with IronPython
Please see the example below, and the “PuLP Examples.xlsx” example spreadsheet, for samples that illustrate using PuLP. Please also see this SolverStudio IronPython page for more details of IronPython’s ability to access the spreadsheet using the standard Excel objects Application, ActiveSheet and ActiveWorkbook.

Using PuLP with External Python
If you run PuLP using Python (External), you need to start your file with:
from SolverStudio import *
which (1) gives you read and write access to the SovlerStudio Data Items on the sheet, (2) gives you access to the standard Excel objects  Application, ActiveSheet and ActiveWorkbook, and (3) adds SolverStudio’s PuLP files the external Python’s path, allowing you to then say:
from pulp import *
in the normal way. PuLP works with Python 2.7 and 3.x. Don’t forget that you need to install Python, but not PuLP. (When using SolverStudio, your external Python installation will use the PuLP included in SolverStudio.)

PuLP Solvers under both IronPython and Python (external)
PuLP supports a range of solvers. Of these, you can use the “_CMD” solvers under both the “PuLP (IronPython)” and “Python (external) languages, including CBC (i.e. COIN_CMD()), Gurobi (GUROBI_CMD()) and Cplex (CPLEX_CMD()). The other solvers, such as GUROBI(), are only available under “Python(external).”

The default PuLP solver is CBC, which is included with SolverStudio. You can solve using CBC with any of the following commands:

Other options for CBC include COIN_CMD(path=None, keepFiles=0, mip=1, msg=0, cuts=None, presolve=None, dual=None, strong=None, options=[], fracGap=None, maxSeconds=None, threads=None)

If you have installed the Gurobi or Cplex solver, PuLP can use it (under the “PuLP (IronPython)” language) using one of

The “PuLP Examples.xlsx” workbook (included in the SolverStudio download) has code on the “IronPython Solvers” sheet to list the available solvers.

PuLP Solvers under Python (External)
If you run PuLP using the “Python (External)” language (i.e. CPython), you can use any of the installed solvers. The “PuLP Examples.xlsx” workbook (included in the SolverStudio download) has code on the “Python (external) Solvers” sheet to list the available solvers.

SolverStudio with Cut/Column Generation using DIPPY under Python (External)
SolverStudio can work with Dippy, the ‘big brother’ of PuLP that allows column and cut generation. Be sure to run under the Python (External) language.

Advanced SolverStudio Usage
SolverStudio can work with Dippy, the ‘big brother’ of PuLP that allows column and cut generation. Dippy is a joint collaboration between Ted Ralphs and Michael O’Sullivan. Ted has some excellent SolverStudio examples using Dippy on his COIN-Fest 2015 page. Be sure to run under the Python (External) language.

COIN Fest 2015: Workshop on Modeling and the COIN-OR Optimization Suite

PuLP Resources
To work with PuLP in SolverStudio, you need some understanding of Python.

The PuLP developer, Stu Mitchell, has some useful resources including:
PuLP on PyPi – the definitive site for installing PuLP
PuLP Package Documentation from PyPi – the definitive documentation
A comprehensive introduction to PuLP;
Tips for efficient Python modelling using PuLP
;
Pulp a linear programming interpreter for Python
;
PuLP: A Linear Programming Toolkit for Python (PDF paper);
An Introduction to PuLP for Python Programmers
(PDF).

See also Pulp and GLPK.

The latest version of PuLP is available on PyPi.

If you have any PuLP questions, please direct them to the PuLP Google Forum.

PuLP Example using IronPython

Note: To run this under an external CPython, please add “from SolverStudio import *” before the “from pulp import *” line.

58 thoughts on “SolverStudio & PuLP

  1. I am using PULP and a loop to repeatedly add cuts and resolve. I don’t go out of my way to reset/re-declare the variable array before resolving, so it has the previous solution in there when the solver is called again. Does that mean it uses this as an the starting solution or is there a special command you have to use during the call to solver that tells it to? Thanks

    • Linear Solvers like CBC and Gurobi do not normally use an existing solution as a starting point. If they are solving an integer programme, then an existing solution may be used to help the search if it is feasible, but I doubt that PuLP takes advantage of this. Hope this helps. Andrew

  2. Hi Andrew,

    is there a limit on variables in SolverStudio used with PuLP? My model is working correctly with around 28000 variables, but as soon as they are increased to around 42000, I immediately receive a KeyError pointing to my objective function.

    • There is no limit that SolverStudio imposes; I suggest you loop thru printing the values and their indices and see when it goes wrong. Andrew

  3. Solve(CPLEX) returning infeasible solution as optimal

    Anyone ever have any issues with external solvers like CPLEX returning an infeasible solution as “optimal” *sometimes* and other times returning the proper feasible solution? I have a PULP procedure that solves and generates a new constraint and resolves. About 75% of the time I run the program the CPLEX solver will ignore the new constraint and return a solution that violates it. The COIN_CMD solve always works fine and honours the new constraint.

    I even generated the .lp and .mps files directly before solving step and visually inspected to verify that the new constraint did indeed make it to the problem – and yep, it is there, yet CPLEX returns a solution that blatantly violates the new constraint about 75% of the time (seemingly random).

        • It would be unusual for CPLEX to have an error. However, errors do occur; my SolverStudio developer, Oscar Dowson, recently found a bug in Gurobi that silently changed models. If you can reproduce the error, and ideally do so without relying on PuLP, then I am sure that CPLEX would like to know about this. Andrew

  4. Thank-you all for your work on Solver Studio, and for allowing me to use it.
    In the included examples, “Common Formulations.xlsx” the Kanpsack-PuLP spreadsheet, the relevant equations to feed Solver Studio are given.
    Considering an identical problem, what if one more limit [constraint?] is required? How would I write the code to limit the total number of unique items to 4 or 5?
    I’m NOT stating I want the sum of the solution to equal 4 or 5, I’m saying I want the solution to be comprised of only 4 or 5 unique items from the Item list.
    I appreciate you patience and help – afraid I’ve been retired a little too long and my background with visual basic for macros is of little use to me at this point.

  5. Hi, also new to all of this (Solverstudio+python), have been using open solver so far, but got stuck due to too many variables (around 50,000…)

    I tried learning from one of your examples the syntax, but I keep getting an error saying that instead of an expected index, a float was found

    The problematic line of the code was:
    prob += lpSum([vars2[p]* price[p] -vars[p]* price[p] for p in price]),”sum of revenue”

    after defining vars and vars2 like this:
    vars = LpVariable.dicts(“Route”,price,0,maxcharge,LpContinuous)
    vars2 = LpVariable.dicts(“Route”,price,0,maxdischarge,LpContinuous)

    price is a set of 50,000 real positive values (representing prices in a market), and the variables are charge and discharge rates of a battery, constrained by:
    vars = LpVariable.dicts(“Route”,price,0,maxcharge,LpContinuous)
    vars2 = LpVariable.dicts(“Route”,price,0,maxdischarge,LpContinuous)

    #creates the upper and lower bounds for the variables
    for a in vars:
    vars[a].bounds(0, maxcharge)
    vars2[a].bounds(0, maxdischarge)

    Can you tell what the problem is with my code?

    • I don’t think you mean “price[p] for p in price]” as I assume “price” is indexed by integers, not by the actual prices. Andrew

  6. HI,
    I am new to solver studio and python programming language. I have a few questions to ask:
    1. can one do multiple optimizations using a for loop in iron python. For example, in inventory management after solving for the first horizon and setting the decision variables for the first peeriod and moving on to the next horizon and doing the same and keep looping until end of all the periods.Kindly explain!

    • Yes. Most modelling languages will let you loop, and all programming languages have this as standard. Python and PuLP will do what you want, for example. Andrew

  7. What’s the best strategy for handling “output” Data that has dynamic dimensions?

    Here’s an example: I have “input” Data that defines tuple sets IE and SE, and then my PuLP(IronPython) model has a decision variable that’s defined for all combinations SEI=[(s,e,i) for (i,e) in IE for (s,e) in SE]. My model is able to read IE and SE, create the model, solve the model, but then crashes when writing the results. The issue is that I don’t know beforehand the combinations in SEI, and so the output Data in the Excel worksheet isn’t yet defined with all the keys. SolverStudio then often throws an error when trying to write new keys to the output Data that weren’t already in the Excel worksheet, and skips rows (without deleting the old contents) for rows defined in Excel worksheet that are in the current (s,e,i) key set.

    What I have done to get around this is very manual. I expanded my PuLP(IronPython) to:

    1. clear the old contents of the output Data in the Excel worksheet, including keys, using
    Application.Worksheets(‘Opt_Production’).Range(‘Table_Opt_Production’).ClearContents()

    2. write the desired keys to the Excel worksheet, using
    row = 0
    rg = Application.Worksheets(‘Opt_Production’).Range(‘Table_Opt_Production’)
    for (s,e,i) in SEI:
    row += 1
    rg.Cells(row,1).Value = s
    rg.Cells(row,2).Value = e
    rg.Cells(row,3).Value = i

    3. create and solve the PuLP model

    4. assign values to the output Data, using
    for (s,e,i) in SEI:
    output_production[s,e,i] = x[site,e,i].varValue
    # SolverStudio’s internal way of writing to Excel ranges is faster than using rg.Cells(row,4).Value

    The downsides of this impementation are that
    – a lot of lines need to be added to the PuLP(IronPython) script
    – the script contains hard-coded references to specific names in the Excel file (easily broken)
    – when new keys are added to SEI, it seems like you need to run the model twice for it to really display the correct solution. When you run it only once, all the keys are shown in the output Data in the Excel worksheet, but none of the values.

    There has to be a simpler way to do this. Do you have any suggestions?

    • One idea: is there a SolverStudio function available in PuLP(IronPython) for “write Data now” or “update keys for Data now”?

    • One idea is to write VBA macros for “clear output tables” and “write keys for output tables”, and attached those macros to buttons for the user, but this wouldn’t cover the instance where I only want the optimized decision variable values displayed that are non-zero (i.e. the keys to be displayed are determined after the solution is obtained).

      • For those interested, this is the approach I ended up using. I defined my output tables in Excel as tables, so that they automatically resize when new rows are added. I defined data variable in SolverStudio for each output table, which will also expand if new rows are added to the tables, because of how SolverStudio handles references. I then wrote a VBA macro that clears the output tables, including values and keys, and writes the correct keys (thus resizing the table and the SolverStudio data variable references). The user runs the VBA macro with a button and then solves the SolverStudio model.

        • Alex: Would you be happy to share your workbook with us? I’d like to understand more about what you’ve done. Also, I have a long term plan to expand data item ranges automatically when the model is run (or the Data Items editor invoked) so that they include newly added rows/columns. I assume this is would meet your needs? Andrew

    • A quick answer before I rush out of the office; if the input indices change, then I assume the output indices should also change (and ideally be the same as the input indices?). If so, then could you index the output data over the ranges used for the input data? Then all output items will exists in thr output data item, perhaps as None/Nothing if they are blank. But, because they exist, they can be updated. Andrew

      • I think this only works if the index of the output data can be known and entered into the Excel worksheet before the SolverStudio model script is ran. Is there a way to edit the index of a SolverStudio Data variable within the model script, and have that updated in the worksheet too (indexes added/deleted in the model script -> rows added/deleted from the worksheet)?

    • Another option: is there a way to write a table of data with IronPython to a worksheet, in a way that is as quick as how SolverStudio read/writes Data variables? I find that for-looping over the indexes and writing each cell one at a time using
      Application.Worksheets(‘Opt_Production’).Range(x).Value = y
      is pretty slow

      • Accessing cells individually is slow. SolverStudio works hard to this fast. You can use arrays of variants to do the same whee you pass an entire array in one go. You can also have a big table indexed from 1, 2, … and write to the data and a nice ‘index’ for the user (but not SolverStudio). The new version of SolverStudio will make this a bit easier. Andrew

  8. Hello,
    I am trying to get PuLP/CBC to solve a quadratic problem. I will have X1*X2, etc in the objective function (Xi is binary). So I thought about creating a dummy variable X12 = X1*X2, where X1 + X2 < X12 + 1, I am minimizing so X12 will be 1 only if both X1 and X2 are 1, all variables being binary.
    (Wonder if PuLP/CBC would let themselves be tricked… but I haven't gotten that far.)
    I am having a PuLP/Python hurdle:
    To create the X12 I think I need a list of lists, instead of tuples (this may be the key to my problem), so I am trying:

    # this would be the list of Xi referenced above
    Allocated = [(p,l) for (p,l) in allocations]

    #this would be the list of Xij
    Allocated_pair = []
    for (p,l) in allocations:
    for (p2,l2) in allocations:
    Allocated_pair += [[Allocated[(p,l)],Allocated[(p2,l2)]] for (p,l) in allocations for (p2,l2) in allocations ]

    but I get error "expected index value, got tuple" (google returns nothing for this error)
    This is somewhat related (analogous) to the problem I had two years ago (above in this page).

    Then after some time I figured a bit of the difference between tuples and lists (I assume a tuple cannot be an index and a list can) so removed the parenthesis from the last line and get this:

    Allocated_pair += [[Allocated[p,l],Allocated[p2,l2]] for (p,l) in allocations for (p2,l2) in allocations ]

    and the error message becomes "…takes exactly 1 argument (2 given)"

    Hopefully my logic is not totally flawed and one comma in the right place will get me going.
    Can anyone see an obvious/easy fix here?
    Thank you

    • Well this got me going:

      Allocated_pair += [[[p,l],[p2,l2]] for (p,l) in allocations for (p2,l2) in allocations ]

      …now I am running out of memory.

      But I found the PuLP Google forum, a better place for my type of questions.

      Thank you

      • Pleased you got it going. Stu Mitchell said you were using Allocated, which is a list, as if it were a dictionary. Your memory error is not surprising as you are taking a cross product of 2 large sets. Hope you can get it to work. Andrew

        • Yes, thank you for checking that for me. The problem will be even bigger with the real data so I think I will find a non-linear option. I’ve used GAMS before so that will be my next try.
          Thanks again and best regards

          • You can try a GAMS model via NEOS, or think about more efficient ways of modelling the problem. If you need a non-linear objective, you could try Pyomo. Julia/JuMP is also worth a look on account of its speed at building the model. Please let us know how you get on. Andrew. PS: What is the problem… why are you getting so many variables?

            • Yes! I was using GAMS with NEOS before I found OpenSolver and SolverStudio. I am trying to model and solve a warehouse slotting problem. Since the problem will be solved once (or once in a while) I think that NEOS will work well, timeliness not being an issue. And I look forward to having the communication with NEOS taken care of.
              Thanks again.

  9. Hi

    I’m building a Network optimization tool using solverstudio and pulp. I’m building a constraint for supply in the network and have 3 types to model – fixed, min and max.

    I’m wondering if there is a way to model of all these types of supply together in 1 equation where in the sign of the constraint is dependent on the supply type, i.e. “=” if it is fixed, “>=” if it is min and “<=" if it is max.

    Otherwise I will have to break the data in to three sets and write three equations.

    Thanks!

    • You will be adding these as separate equations in Pull so you could let the user enter a < , > or = on the sheet and then use this to add the right equation in Pulp. Andrew

  10. Hi everyone!

    Firstly I would like to thank you if you can help me ^^.

    We are actually working on a LP problem for a student project at the univerity.
    We have had a lot of errors but we still have one unsolved and honestly we don’t understand why…^^

    The message error is:”Error execting model code: name’k’is not defined.
    Traceback (most recent call last): File””, line…, in NameError: name ‘k’ is not defined

    Our variables are declared like that:
    Deltavars = LpVariable.dicts(“Delta”,(Produits,Jours),0,1,LpInteger)

    And it is more or less the same declaration for our data (^^)

    And our unsolved error is for this constraint:

    # largeur de gamme
    for t in Jours:
    for s in SousFamille:
    prob += lpSum([Deltavars[p][j]*Esp[p,k] for p in Produits if (t==j and s==k)]) >= LGmin[s]*lpSum([Esp[p,k] for p in Produits if (s==k)]), “largeur_de _gamme_%s_%s”%(t,s)

    Thanks a lot!!
    Cheers

    • You have no value(s) specified for the variable (i.e. index) ‘k’ which is needed to look up Esp[p,k]. You may need to loop over something like “for k in XX”. By the way, is this part of a course where you are taught SolverStudio, or did you find SolverStudio yourself? Andrew

  11. Hi, me again 😉

    thanks für the answer to me problem realy helped me out.

    But now I have a different problem. I use this code to simulate a heat storage for my heating grid

    var_WS_load_unload = LpVariable.dicts (“heat storage load or unload”,Zeit, -WS_unload_max,WS_load_max,LpContinuous)
    WS_load = {}
    WS_load_now = WS_load_at_start

    for i in Time
    WS_load[i] = WS_load_now + var_WS_load_unload[i]
    WS_load_now = WS_load[i] * WS_loss
    prob += WS_load[i] = WS_load_min

    The Code just takes the actual load of the heating storage adds or subtracts the new amount of heat subtracts the losses and that is the load for the next time step

    This works good if my model dosent have more then 2500 time steps.
    If I go over that i geht the message “system out of memory exception” and the solving process stops.

    Is there anything i can do or a other way to simulat that problem ?
    Thanks in advace for your help

    • It looks like you have a big optimisation problem to solve. I would try a machine with more memory (and a 64 bit OS), or another modelling language, or shorter (or no) text descriptions in your model.

      • Hi yes it is a big problem going on a 64 bit os is ok but is it important that excel is a 64 bit version too or is it ok to have the 32 bit version running on a 64 bit os ?

        Which modeling language would you suggest ?

        Thanks for your help

        • PuLP will only get access to a a full 64-bit worth of memory if Excel is a 64 bit version. Alternative4ly, install your own 64 bit version of Python (from python.org), and change the Python (External) as your language (after adding “from SolverStudio import *” as shown in the examples). Hope this helps.

          • Hi amas008,

            thanks for your help. I installed CPython and Pulp as 64 bit version on my computer solver studio is working with the external Python and starts creating the data file solverstudio.py and the model file model.py

            but i get the error invalid syntax if I try to import the data file solverstudio.py with the line
            “from SolverStudio import *”

            This ist what the output shows me:

            ## Executing C:\Program Files (x86)\SolverStudio_00_06_03_00 20140829\SolverStudio\SolverStudio\CPython\RunCPython.py
            ## Building CPython input file, module ‘SolverStudio.py’, for 139 data items
            ## Writing data items
            ## Running CPython (c:\Python34\python.exe)
            ## with file: C:\Users\Patrick_Netbook\AppData\Local\Temp\SolverStudio gffhkjxz\model.py

            Traceback (most recent call last):
            File “C:\Users\Patrick_Netbook\AppData\Local\Temp\SolverStudio gffhkjxz\model.py”, line 28, in
            from SolverStudio import *
            File “C:\Users\Patrick_Netbook\AppData\Local\Temp\SolverStudio gffhkjxz\SolverStudio.py”, line 648
            except Exception, ex:
            ^
            SyntaxError: invalid syntax

            ## CPython did not complete; no solution is available.
            ## Done

            did not finde any solution yet maybe you have a hint for me.
            Thanks in advance

            • This appears to be a Python 3 issue; I have sent you a patch for testing, and will include this in the next release. Thanks for letting me know about this. Andrew

  12. Hi I have a Problem with a constrain i am trying to add to my Problem.

    prob = LpProblem (“Heating Costs”,LpMinimize)

    var_plant1_th = LpVariable.dicts (“heating power plant1”,Time,0,32,LpContinuous)
    var_plant2_th = LpVariable.dicts (“heating power plant2″,Time,5,90,LpContinuous)

    prob += lpSum([var_plant1_th[i]*Costs_plant1 + var_plant2_th[i]*Costs_plant2 for i in Zeit]),”Total Costs for heating”

    prob += [var_plant1_th[i]+ var_plant2_th[i] == demand_heating[i] for i in Zeit]), “Sum of power for each plant = demand of heating”

    the last constrain allways givs me the error ” Need more then 1 value to unpack”
    I tried so many things but could not finde any solution
    I hope someone could help me

    • Stu Mitchel says:
      You need an explicit loop to add the constraints
      for i in Zeit:
      prob += var_plant1_th[i]+ var_plant2_th[i] == demand_heating[i] for i in Zeit, “Sum of power for plant %s = demand of heating” % i

  13. Hi,

    I modeled an MIP with PuLP through SolverStudio and solved with cbc.
    Because the model is hard to solve I use a time limit to stop optimization.

    I want to see how good is my solution by checking the LowerBound but I don’t know how to get it.
    So here my questions :
    – What is the function to get back the LowerBound ? To get the objective the function to use is “prob.objective”, is there an equivalent function to get the lowerBound ?
    – Is it possible to save the trace/log file after an optimization ? When I solve the MIP by calling the solver like this prob.solve(COIN_CMD(msg=1,maxSeconds=120)), I can see the log but at the end of the optimization the log is quiting and not saving.

    Thank you for your help

  14. Hi,

    I modeled an LP with COIN.
    Because of the complexity of this model, i can’t get the optimal solution in a reasonnable amount of time.
    As a result I use the MaxSeconds option when I call the solver : prob.solve(COIN_CMD(msg=1,maxSeconds=30))

    At the end of the resolution I would like to get the best bound to be aware of the solution’s quality.
    Unfortunately, I don’t know how to get this information. I can look at the trace but it’s not really convenient cause the window close really fast after optimisation.
    So here my question :
    – Is it possible to get the best bound with a function like we can get objective with the function prob.objective ?
    – Is it possible to save the trace in a log file after an optimisation ?

    Best regards

    • Hi this is Stuart Mitchell here maintainer of PuLP, Unfortunately Pulp does not record the bound gap when the problem is terminated early. You can however use the fracGap argument to the solver so that it terminates once the bound gap is small enough (i.e. fracGap = 0.1 terminates within 10% of the optimal solution)

      Stu

  15. Hi,
    How can I choose which language to use. My problem is linear and has over Million Variables and Constraints. So I probably should stay away from the trials like CPLEX and GUROBI. but how do I choose if its better to code with PUPL or COOPR/PYOMO ? Which one is faster ? which one is easier to use? ,…
    Thank you for your help

    • I think both PuLP and Pyomo will struggle with your problem. PuLP is perhaps easier to learn as it avoids the idea of abstract models; I’d start with that on a scaled down test problem. Let me know how you get on. Andrew

      • Thank you Andrew. What if we decide to get the licence for CPLEX, GUROBI, or other not free ones. Would they be able to solve a problem with this size?

    • aux is the second variable I want to create (the first one is vars)
      so
      print aux[t][d]
      gives
      Auxiliar_tool1_day1
      and
      print vars[(i,j,k),d)]
      gives
      Variable_((‘material1′,_’tool1′,_qty1),_’day1’)
      Not sure why vars has quotes and aux doesn’t or if that’s part of the problem.
      Thank you

  16. I am not sure if I have a SolverStudio or a PuLP problem. I am trying to create a second set of variables.
    This is the code I have:

    vars = LpVariable.dicts(“Variable”,Sched,0,1,LpInteger)
    aux = LpVariable.dicts(“Auxiliar”,(tools,workdays),0,1,LpInteger)

    Everything related to vars works. Sched, tools and workdays are being used in other portions of the code and are correctly defined (I think).

    But this doesn’t work:
    for d in workdays:
    prob += 25 >= lpSum([aux[(t,d)] for t in tools])

    The error points to the line above and says:
    KeyError: (‘tool1’, ‘day1’)
    where tool1 is the first t in tools and day1 is the first d in workdays.

    If I replace (tools,workdays) above with a tuple (like I did when creating vars) it tells me the list is unhashable.

    What am I doing wrong? This is probably very silly… Thanks

    • I’ve realized this lets me print the variables (I wasn’t able before):

      for t in tools:
      for d in workdays:
      print aux[t][d]

      but still this doesn’t work:

      for d in workdays:
      prob += 25 >= lpSum([aux[t][d] for t in tools])

      the error is:
      TypeError: expected index value, got str

      I assume it is bad Python syntax but couldn’t find anything similar online.
      Any hints?
      Thank you

      • I fixed my problem:
        #Create a second tuple
        AuxVars = [((t),d) for (t) in tools for d in turndays]
        #Created the variable with the tuple
        aux = LpVariable.dicts(“Auxiliar”,AuxVars,0,1,LpInteger)
        #Create the cosntraint
        for d in turndays:
        prob += 25 >= lpSum([aux[((t),d)] for t in tools]), “Tool repeat limits for %s” %d
        I was also missing an index in the data items editor.
        Thank you

Leave a Reply

Your email address will not be published. Required fields are marked *