```json
{
  "sym_variables": [
    ("x0", "candles"),
    ("x1", "dish soap bottles"),
    ("x2", "bottles of ibuprofen"),
    ("x3", "toilet paper rolls")
  ],
  "objective_function": "5.11 * x0 + 5.61 * x1 + 1.51 * x2 + 8.4 * x3",
  "constraints": [
    "21 * x0 + 10 * x2 >= 12",
    "21 * x0 + 16 * x1 + 6 * x3 >= 22",
    "21 * x0 + 16 * x1 <= 88",
    "21 * x0 + 6 * x3 <= 129",
    "16 * x1 + 10 * x2 <= 129",
    "10 * x2 + 6 * x3 <= 116",
    "21 * x0 + 16 * x1 + 6 * x3 <= 140",
    "21 * x0 + 16 * x1 + 10 * x2 + 6 * x3 <= 140",
    "5 * x0 + 7 * x1 <= 126",
    "5 * x0 + 15 * x3 <= 95",
    "5 * x0 + 22 * x2 + 15 * x3 <= 79",
    "5 * x0 + 7 * x1 + 22 * x2 + 15 * x3 <= 79",
    "x0 >= 0",
    "x1 >= 0",
    "x2 >= 0",
    "x3 >= 0"
  ]
}
```

```python
import gurobipy as gp

try:
    # Create a new model
    m = gp.Model("optimization_problem")

    # Create variables
    candles = m.addVar(vtype=gp.GRB.INTEGER, name="candles")
    dish_soap = m.addVar(vtype=gp.GRB.INTEGER, name="dish_soap")
    ibuprofen = m.addVar(vtype=gp.GRB.INTEGER, name="ibuprofen")
    toilet_paper = m.addVar(vtype=gp.GRB.INTEGER, name="toilet_paper")

    # Set objective function
    m.setObjective(5.11 * candles + 5.61 * dish_soap + 1.51 * ibuprofen + 8.4 * toilet_paper, gp.GRB.MAXIMIZE)

    # Add constraints
    m.addConstr(21 * candles + 10 * ibuprofen >= 12)
    m.addConstr(21 * candles + 16 * dish_soap + 6 * toilet_paper >= 22)
    m.addConstr(21 * candles + 16 * dish_soap <= 88)
    m.addConstr(21 * candles + 6 * toilet_paper <= 129)
    m.addConstr(16 * dish_soap + 10 * ibuprofen <= 129)
    m.addConstr(10 * ibuprofen + 6 * toilet_paper <= 116)
    m.addConstr(21 * candles + 16 * dish_soap + 6 * toilet_paper <= 140)
    m.addConstr(21 * candles + 16 * dish_soap + 10 * ibuprofen + 6 * toilet_paper <= 140)
    m.addConstr(5 * candles + 7 * dish_soap <= 126)
    m.addConstr(5 * candles + 15 * toilet_paper <= 95)
    m.addConstr(5 * candles + 22 * ibuprofen + 15 * toilet_paper <= 79)
    m.addConstr(5 * candles + 7 * dish_soap + 22 * ibuprofen + 15 * toilet_paper <= 79)


    # Optimize model
    m.optimize()

    if m.status == gp.GRB.OPTIMAL:
        print('Obj: %g' % m.objVal)
        print('candles:', candles.x)
        print('dish_soap:', dish_soap.x)
        print('ibuprofen:', ibuprofen.x)
        print('toilet_paper:', toilet_paper.x)
    elif m.status == gp.GRB.INFEASIBLE:
        print('The model is infeasible.')
    else:
        print('Optimization ended with status %d' % m.status)


except gp.GurobiError as e:
    print('Error code ' + str(e.errno) + ': ' + str(e))

except AttributeError:
    print('Encountered an attribute error')
```