// NOTE: CPLEX does not provide a function to directly get the dual // multipliers for second order cone constraint. // Example QCPDual.cs illustrates how the dual multipliers for a // quadratic constraint can be computed from that constraint's // slack. // However, for SOCP we can do something simpler: we can read those // multipliers directly from the dual slacks for the // cone head variables. For a second order cone constraint // x[1] >= |(x[2], ..., x[n])| // the dual multiplier is the dual slack value for x[1]. /// <summary> /// Compute dual multipliers for second order cone constraints. /// Returns a dictionary with a dual multiplier for each second order cone /// constraint. /// </summary> /// <remarks> /// <c>cplex</c> is the Cplex instance with the optimal solution. /// <c>vars</c> is a collection with <em>all</em> variables in the model. /// <c>rngs</c> is a collection with <em>all</em> constraints in the /// model. /// <c>dslack</c> is a dictionary in which the function will store the full /// dual slack, provided the argument is not <c>null</c>. /// </remarks> private static IDictionary <IRange, System.Double> Getsocpconstrmultipliers(Cplex cplex, ICollection <INumVar> vars, ICollection <IRange> rngs, IDictionary <INumVar, System.Double> dslack) { // Compute full dual slack. IDictionary <INumVar, System.Double> dense = new SortedDictionary <INumVar, System.Double>(NumVarComparator); foreach (INumVar v in vars) { dense[v] = cplex.GetReducedCost(v); } foreach (IRange r in rngs) { INumExpr e = r.Expr; if ((e is IQuadNumExpr) && ((IQuadNumExpr)e).GetQuadEnumerator().MoveNext()) { // Quadratic constraint: pick up dual slack vector for (ILinearNumExprEnumerator it = cplex.GetQCDSlack(r).GetLinearEnumerator(); it.MoveNext(); /* nothing */) { dense[it.NumVar] = dense[it.NumVar] + it.Value; } } } // Find the cone head variables and pick up the dual slacks for them. IDictionary <IRange, System.Double> socppi = new SortedDictionary <IRange, System.Double>(RangeComparator); foreach (IRange r in rngs) { INumExpr e = r.Expr; if ((e is IQuadNumExpr) && ((IQuadNumExpr)e).GetQuadEnumerator().MoveNext()) { // Quadratic constraint: pick up dual slack vector for (IQuadNumExprEnumerator it = ((IQuadNumExpr)e).GetQuadEnumerator(); it.MoveNext(); /* nothing */) { if (it.Value < 0) { socppi[r] = dense[it.NumVar1]; break; } } } } // Fill in the dense slack if the user asked for it. if (dslack != null) { dslack.Clear(); foreach (INumVar v in dense.Keys) { dslack[v] = dense[v]; } } return(socppi); }
// Print out the objective function. // Note that the quadratic expression in the objective // is normalized: i.E., for all i != j, terms // c(i,j)*x[i]*x[j] + c(j,i)*x[j]*x[i] is normalized as // (c(i,j) + c(j,i)) * x[i]*x[j], or // (c(i,j) + c(j,i)) * x[j]*x[i]. internal static void PrintObjective(IObjective obj) { System.Console.WriteLine("obj: " + obj); // Count the number of linear terms // in the objective function. int nlinterms = 0; ILinearNumExprEnumerator len = ((ILQNumExpr)obj.Expr).GetLinearEnumerator(); while (len.MoveNext()) { ++nlinterms; } // Count the number of quadratic terms // in the objective function. int nquadterms = 0; int nquaddiag = 0; IQuadNumExprEnumerator qen = ((ILQNumExpr)obj.Expr).GetQuadEnumerator(); while (qen.MoveNext()) { ++nquadterms; INumVar var1 = qen.GetNumVar1(); INumVar var2 = qen.GetNumVar2(); if (var1.Equals(var2)) { ++nquaddiag; } } System.Console.WriteLine("number of linear terms in the objective : " + nlinterms); System.Console.WriteLine("number of quadratic terms in the objective : " + nquadterms); System.Console.WriteLine("number of diagonal quadratic terms in the objective : " + nquaddiag); System.Console.WriteLine(); }
/* Test KKT conditions on the solution. * The function returns true if the tested KKT conditions are satisfied * and false otherwise. */ private static bool Checkkkt(Cplex cplex, IObjective obj, ICollection <INumVar> vars, ICollection <IRange> rngs, IDictionary <INumVar, IRange> cone, double tol) { System.IO.TextWriter error = cplex.Output(); System.IO.TextWriter output = cplex.Output(); IDictionary <INumVar, System.Double> dslack = new SortedDictionary <INumVar, System.Double>(NumVarComparator); IDictionary <INumVar, System.Double> x = new SortedDictionary <INumVar, System.Double>(NumVarComparator); IDictionary <IRange, System.Double> pi = new SortedDictionary <IRange, System.Double>(RangeComparator); IDictionary <IRange, System.Double> slack = new SortedDictionary <IRange, System.Double>(RangeComparator); // Read primal and dual solution information. foreach (INumVar v in vars) { x[v] = cplex.GetValue(v); } pi = Getsocpconstrmultipliers(cplex, vars, rngs, dslack); foreach (IRange r in rngs) { slack[r] = cplex.GetSlack(r); INumExpr e = r.Expr; if (!(e is IQuadNumExpr) || !((IQuadNumExpr)e).GetQuadEnumerator().MoveNext()) { // Linear constraint: get the dual value pi[r] = cplex.GetDual(r); } } // Print out the data we just fetched. output.Write("x = ["); foreach (INumVar v in vars) { output.Write(" {0,7:0.000}", x[v]); } output.WriteLine(" ]"); output.Write("dslack = ["); foreach (INumVar v in vars) { output.Write(" {0,7:0.000}", dslack[v]); } output.WriteLine(" ]"); output.Write("pi = ["); foreach (IRange r in rngs) { output.Write(" {0,7:0.000}", pi[r]); } output.WriteLine(" ]"); output.Write("slack = ["); foreach (IRange r in rngs) { output.Write(" {0,7:0.000}", slack[r]); } output.WriteLine(" ]"); // Test primal feasibility. // This example illustrates the use of dual vectors returned by CPLEX // to verify dual feasibility, so we do not test primal feasibility // here. // Test dual feasibility. // We must have // - for all <= constraints the respective pi value is non-negative, // - for all >= constraints the respective pi value is non-positive, // - the dslack value for all non-cone variables must be non-negative. // Note that we do not support ranged constraints here. foreach (INumVar v in vars) { if (cone[v] == NOT_IN_CONE && dslack[v] < -tol) { error.WriteLine("Dual multiplier for " + v + " is not feasible: " + dslack[v]); return(false); } } foreach (IRange r in rngs) { if (System.Math.Abs(r.LB - r.UB) <= tol) { // Nothing to check for equality constraints. } else if (r.LB > -System.Double.MaxValue && pi[r] > tol) { error.WriteLine("Dual multiplier " + pi[r] + " for >= constraint"); error.WriteLine(" " + r); error.WriteLine("not feasible."); return(false); } else if (r.UB < System.Double.MaxValue && pi[r] < -tol) { error.WriteLine("Dual multiplier " + pi[r] + " for <= constraint"); error.WriteLine(" " + r); error.WriteLine("not feasible."); return(false); } } // Test complementary slackness. // For each constraint either the constraint must have zero slack or // the dual multiplier for the constraint must be 0. We must also // consider the special case in which a variable is not explicitly // contained in a second order cone constraint. foreach (INumVar v in vars) { if (cone[v] == NOT_IN_CONE) { if (System.Math.Abs(x[v]) > tol && dslack[v] > tol) { error.WriteLine("Invalid complementary slackness for " + v + ":"); error.WriteLine(" " + x[v] + " and " + dslack[v]); return(false); } } } foreach (IRange r in rngs) { if (System.Math.Abs(slack[r]) > tol && System.Math.Abs(pi[r]) > tol) { error.WriteLine("Invalid complementary slackness for"); error.WriteLine(" " + r); error.WriteLine(" " + slack[r] + " and " + pi[r]); return(false); } } // Test stationarity. // We must have // c - g[i]'(X)*pi[i] = 0 // where c is the objective function, g[i] is the i-th constraint of the // problem, g[i]'(x) is the derivate of g[i] with respect to x and X is the // optimal solution. // We need to distinguish the following cases: // - linear constraints g(x) = ax - b. The derivative of such a // constraint is g'(x) = a. // - second order constraints g(x[1],...,x[n]) = -x[1] + |(x[2],...,x[n])| // the derivative of such a constraint is // g'(x) = (-1, x[2]/|(x[2],...,x[n])|, ..., x[n]/|(x[2],...,x[n])| // (here |.| denotes the Euclidean norm). // - bound constraints g(x) = -x for variables that are not explicitly // contained in any second order cone constraint. The derivative for // such a constraint is g'(x) = -1. // Note that it may happen that the derivative of a second order cone // constraint is not defined at the optimal solution X (this happens if // X=0). In this case we just skip the stationarity test. IDictionary <INumVar, System.Double> sum = new SortedDictionary <INumVar, System.Double>(NumVarComparator); foreach (INumVar v in vars) { sum[v] = 0.0; } for (ILinearNumExprEnumerator it = ((ILinearNumExpr)cplex.GetObjective().Expr).GetLinearEnumerator(); it.MoveNext(); /* nothing */) { sum[it.NumVar] = it.Value; } foreach (INumVar v in vars) { if (cone[v] == NOT_IN_CONE) { sum[v] = sum[v] - dslack[v]; } } foreach (IRange r in rngs) { INumExpr e = r.Expr; if ((e is IQuadNumExpr) && ((IQuadNumExpr)e).GetQuadEnumerator().MoveNext()) { double norm = 0.0; for (IQuadNumExprEnumerator q = ((IQuadNumExpr)e).GetQuadEnumerator(); q.MoveNext(); /* nothing */) { if (q.Value > 0) { norm += x[q.NumVar1] * x[q.NumVar1]; } } norm = System.Math.Sqrt(norm); if (System.Math.Abs(norm) <= tol) { cplex.Warning().WriteLine("Cannot check stationarity at non-differentiable point"); return(true); } for (IQuadNumExprEnumerator q = ((IQuadNumExpr)e).GetQuadEnumerator(); q.MoveNext(); /* nothing */) { INumVar v = q.NumVar1; if (q.Value < 0) { sum[v] = sum[v] - pi[r]; } else if (q.Value > 0) { sum[v] = sum[v] + pi[r] * x[v] / norm; } } } else if (e is ILinearNumExpr) { for (ILinearNumExprEnumerator it = ((ILinearNumExpr)e).GetLinearEnumerator(); it.MoveNext(); /* nothing */) { INumVar v = it.NumVar; sum[v] = sum[v] - pi[r] * it.Value; } } } // Now test that all elements in sum[] are 0. foreach (INumVar v in vars) { if (System.Math.Abs(sum[v]) > tol) { error.WriteLine("Invalid stationarity " + sum[v] + " for " + v); return(false); } } return(true); }
/* ***************************************************************** * * * * C A L C U L A T E D U A L S F O R Q U A D S * * * * CPLEX does not give us the dual multipliers for quadratic * * constraints directly. This is because they may not be properly * * defined at the cone top and deciding whether we are at the cone * * top or not involves (problem specific) tolerance issues. CPLEX * * instead gives us all the values we need in order to compute the * * dual multipliers if we are not at the cone top. * * * * ***************************************************************** */ /// <summary> /// Calculate dual multipliers for quadratic constraints from dual /// slack vectors and optimal solutions. /// The dual multiplier is essentially the dual slack divided /// by the derivative evaluated at the optimal solution. If the optimal /// solution is 0 then the derivative at this point is not defined (we are /// at the cone top) and we cannot compute a dual multiplier. /// </summary> /// <remarks> /// <c>cplex</c> is the Cplex instance that holds the optimal /// solution. /// <c>xval</c> is the optimal solution vector. /// <c>tol</c> is the tolerance used to decide whether we are at /// the cone /// <c>x</c> is the array of variables in the model. /// <c>qs</c> is the array of quadratic constraints for which duals /// shall be calculated. /// </remarks> private static double[] getqconstrmultipliers(Cplex cplex, double[] xval, double tol, INumVar[] x, IRange[] qs) { double[] qpi = new double[qs.Length]; // Store solution in map so that we can look up by variable. IDictionary <INumVar, System.Double> sol = new Dictionary <INumVar, System.Double>(); for (int j = 0; j < x.Length; ++j) { sol.Add(x[j], xval[j]); } for (int i = 0; i < qs.Length; ++i) { IDictionary <INumVar, System.Double> dslack = new Dictionary <INumVar, System.Double>(); for (ILinearNumExprEnumerator it = cplex.GetQCDSlack(qs[i]).GetLinearEnumerator(); it.MoveNext(); /* nothing */) { dslack[it.NumVar] = it.Value; } IDictionary <INumVar, System.Double> grad = new Dictionary <INumVar, System.Double>(); bool conetop = true; for (IQuadNumExprEnumerator it = ((ILQNumExpr)qs[i].Expr).GetQuadEnumerator(); it.MoveNext(); /* nothing */) { INumVar x1 = it.NumVar1; INumVar x2 = it.NumVar2; if (sol[x1] > tol || sol[x2] > tol) { conetop = false; } System.Double old; if (!grad.TryGetValue(x1, out old)) { old = 0.0; } grad[x1] = old + sol[x2] * it.Value; if (!grad.TryGetValue(x2, out old)) { old = 0.0; } grad[x2] = old + sol[x1] * it.Value; } if (conetop) { throw new System.SystemException("Cannot compute dual multiplier at cone top!"); } // Compute qpi[i] as slack/gradient. // We may have several indices to choose from and use the one // with largest absolute value in the denominator. bool ok = false; double maxabs = -1.0; foreach (INumVar v in x) { System.Double g; if (grad.TryGetValue(v, out g)) { if (System.Math.Abs(g) > tol) { if (System.Math.Abs(g) > maxabs) { System.Double d; qpi[i] = (dslack.TryGetValue(v, out d) ? d : 0.0) / g; maxabs = System.Math.Abs(g); } ok = true; } } } if (!ok) { // Dual slack is all 0. qpi[i] can be anything, just set to 0. qpi[i] = 0; } } return(qpi); }
/// <summary> /// The example's main function. /// </summary> public static void Main(string[] args) { int retval = 0; Cplex cplex = null; try { cplex = new Cplex(); /* ***************************************************************** * * * * S E T U P P R O B L E M * * * * We create the following problem: * * Minimize * * obj: 3x1 - x2 + 3x3 + 2x4 + x5 + 2x6 + 4x7 * * Subject To * * c1: x1 + x2 = 4 * * c2: x1 + x3 >= 3 * * c3: x6 + x7 <= 5 * * c4: -x1 + x7 >= -2 * * q1: [ -x1^2 + x2^2 ] <= 0 * * q2: [ 4.25x3^2 -2x3*x4 + 4.25x4^2 - 2x4*x5 + 4x5^2 ] + 2 x1 <= 9.0 * q3: [ x6^2 - x7^2 ] >= 4 * * Bounds * * 0 <= x1 <= 3 * * x2 Free * * 0 <= x3 <= 0.5 * * x4 Free * * x5 Free * * x7 Free * * End * * * * ***************************************************************** */ INumVar[] x = new INumVar[7]; x[0] = cplex.NumVar(0, 3, "x1"); x[1] = cplex.NumVar(System.Double.NegativeInfinity, System.Double.PositiveInfinity, "x2"); x[2] = cplex.NumVar(0, 0.5, "x3"); x[3] = cplex.NumVar(System.Double.NegativeInfinity, System.Double.PositiveInfinity, "x4"); x[4] = cplex.NumVar(System.Double.NegativeInfinity, System.Double.PositiveInfinity, "x5"); x[5] = cplex.NumVar(0, System.Double.PositiveInfinity, "x6"); x[6] = cplex.NumVar(System.Double.NegativeInfinity, System.Double.PositiveInfinity, "x7"); IRange[] linear = new IRange[4]; linear[0] = cplex.AddEq(cplex.Sum(x[0], x[1]), 4.0, "c1"); linear[1] = cplex.AddGe(cplex.Sum(x[0], x[2]), 3.0, "c2"); linear[2] = cplex.AddLe(cplex.Sum(x[5], x[6]), 5.0, "c3"); linear[3] = cplex.AddGe(cplex.Diff(x[6], x[0]), -2.0, "c4"); IRange[] quad = new IRange[3]; quad[0] = cplex.AddLe(cplex.Sum(cplex.Prod(-1, x[0], x[0]), cplex.Prod(x[1], x[1])), 0.0, "q1"); quad[1] = cplex.AddLe(cplex.Sum(cplex.Prod(4.25, x[2], x[2]), cplex.Prod(-2, x[2], x[3]), cplex.Prod(4.25, x[3], x[3]), cplex.Prod(-2, x[3], x[4]), cplex.Prod(4, x[4], x[4]), cplex.Prod(2, x[0])), 9.0, "q2"); quad[2] = cplex.AddGe(cplex.Sum(cplex.Prod(x[5], x[5]), cplex.Prod(-1, x[6], x[6])), 4.0, "q3"); cplex.AddMinimize(cplex.Sum(cplex.Prod(3.0, x[0]), cplex.Prod(-1.0, x[1]), cplex.Prod(3.0, x[2]), cplex.Prod(2.0, x[3]), cplex.Prod(1.0, x[4]), cplex.Prod(2.0, x[5]), cplex.Prod(4.0, x[6])), "obj"); /* ***************************************************************** * * * * O P T I M I Z E P R O B L E M * * * * ***************************************************************** */ cplex.SetParam(Cplex.Param.Barrier.QCPConvergeTol, 1e-10); cplex.Solve(); /* ***************************************************************** * * * * Q U E R Y S O L U T I O N * * * * ***************************************************************** */ double[] xval = cplex.GetValues(x); double[] slack = cplex.GetSlacks(linear); double[] qslack = cplex.GetSlacks(quad); double[] cpi = cplex.GetReducedCosts(x); double[] rpi = cplex.GetDuals(linear); double[] qpi = getqconstrmultipliers(cplex, xval, ZEROTOL, x, quad); // Also store solution in a dictionary so that we can look up // values by variable and not only by index. IDictionary <INumVar, System.Double> xmap = new Dictionary <INumVar, System.Double>(); for (int j = 0; j < x.Length; ++j) { xmap.Add(x[j], xval[j]); } /* ***************************************************************** * * * * C H E C K K K T C O N D I T I O N S * * * * Here we verify that the optimal solution computed by CPLEX * * (and the qpi[] values computed above) satisfy the KKT * * conditions. * * * * ***************************************************************** */ // Primal feasibility: This example is about duals so we skip this test. // Dual feasibility: We must verify // - for <= constraints (linear or quadratic) the dual // multiplier is non-positive. // - for >= constraints (linear or quadratic) the dual // multiplier is non-negative. for (int i = 0; i < linear.Length; ++i) { if (linear[i].LB <= System.Double.NegativeInfinity) { // <= constraint if (rpi[i] > ZEROTOL) { throw new System.SystemException("Dual feasibility test failed for row " + linear[i] + ": " + rpi[i]); } } else if (linear[i].UB >= System.Double.PositiveInfinity) { // >= constraint if (rpi[i] < -ZEROTOL) { throw new System.SystemException("Dual feasibility test failed for row " + linear[i] + ": " + rpi[i]); } } else { // nothing to do for equality constraints } } for (int i = 0; i < quad.Length; ++i) { if (quad[i].LB <= System.Double.NegativeInfinity) { // <= constraint if (qpi[i] > ZEROTOL) { throw new System.SystemException("Dual feasibility test failed for quad " + quad[i] + ": " + qpi[i]); } } else if (quad[i].UB >= System.Double.PositiveInfinity) { // >= constraint if (qpi[i] < -ZEROTOL) { throw new System.SystemException("Dual feasibility test failed for quad " + quad[i] + ": " + qpi[i]); } } else { // nothing to do for equality constraints } } // Complementary slackness. // For any constraint the product of primal slack and dual multiplier // must be 0. for (int i = 0; i < linear.Length; ++i) { if (System.Math.Abs(linear[i].UB - linear[i].LB) > ZEROTOL && System.Math.Abs(slack[i] * rpi[i]) > ZEROTOL) { throw new System.SystemException("Complementary slackness test failed for row " + linear[i] + ": " + System.Math.Abs(slack[i] * rpi[i])); } } for (int i = 0; i < quad.Length; ++i) { if (System.Math.Abs(quad[i].UB - quad[i].LB) > ZEROTOL && System.Math.Abs(qslack[i] * qpi[i]) > ZEROTOL) { throw new System.SystemException("Complementary slackness test failed for quad " + quad[i] + ": " + System.Math.Abs(qslack[i] * qpi[i])); } } for (int j = 0; j < x.Length; ++j) { if (x[j].UB < System.Double.PositiveInfinity) { double slk = x[j].UB - xval[j]; double dual = cpi[j] < -ZEROTOL ? cpi[j] : 0.0; if (System.Math.Abs(slk * dual) > ZEROTOL) { throw new System.SystemException("Complementary slackness test failed for column " + x[j] + ": " + System.Math.Abs(slk * dual)); } } if (x[j].LB > System.Double.NegativeInfinity) { double slk = xval[j] - x[j].LB; double dual = cpi[j] > ZEROTOL ? cpi[j] : 0.0; if (System.Math.Abs(slk * dual) > ZEROTOL) { throw new System.SystemException("Complementary slackness test failed for column " + x[j] + ": " + System.Math.Abs(slk * dual)); } } } // Stationarity. // The difference between objective function and gradient at optimal // solution multiplied by dual multipliers must be 0, i.E., for the // optimal solution x // 0 == c // - sum(r in rows) r'(x)*rpi[r] // - sum(q in quads) q'(x)*qpi[q] // - sum(c in cols) b'(x)*cpi[c] // where r' and q' are the derivatives of a row or quadratic constraint, // x is the optimal solution and rpi[r] and qpi[q] are the dual // multipliers for row r and quadratic constraint q. // b' is the derivative of a bound constraint and cpi[c] the dual bound // multiplier for column c. IDictionary <INumVar, System.Double> kktsum = new Dictionary <INumVar, System.Double>(); for (int j = 0; j < x.Length; ++j) { kktsum.Add(x[j], 0.0); } // Objective function. for (ILinearNumExprEnumerator it = ((ILinearNumExpr)cplex.GetObjective().Expr).GetLinearEnumerator(); it.MoveNext(); /* nothing */) { kktsum[it.NumVar] = it.Value; } // Linear constraints. // The derivative of a linear constraint ax - b (<)= 0 is just a. for (int i = 0; i < linear.Length; ++i) { for (ILinearNumExprEnumerator it = ((ILinearNumExpr)linear[i].Expr).GetLinearEnumerator(); it.MoveNext(); /* nothing */) { kktsum[it.NumVar] = kktsum[it.NumVar] - rpi[i] * it.Value; } } // Quadratic constraints. // The derivative of a constraint xQx + ax - b <= 0 is // Qx + Q'x + a. for (int i = 0; i < quad.Length; ++i) { for (ILinearNumExprEnumerator it = ((ILinearNumExpr)quad[i].Expr).GetLinearEnumerator(); it.MoveNext(); /* nothing */) { kktsum[it.NumVar] = kktsum[it.NumVar] - qpi[i] * it.Value; } for (IQuadNumExprEnumerator it = ((IQuadNumExpr)quad[i].Expr).GetQuadEnumerator(); it.MoveNext(); /* nothing */) { INumVar v1 = it.NumVar1; INumVar v2 = it.NumVar2; kktsum[v1] = kktsum[v1] - qpi[i] * xmap[v2] * it.Value; kktsum[v2] = kktsum[v2] - qpi[i] * xmap[v1] * it.Value; } } // Bounds. // The derivative for lower bounds is -1 and that for upper bounds // is 1. // CPLEX already returns dj with the appropriate sign so there is // no need to distinguish between different bound types here. for (int j = 0; j < x.Length; ++j) { kktsum[x[j]] = kktsum[x[j]] - cpi[j]; } foreach (INumVar v in x) { if (System.Math.Abs(kktsum[v]) > ZEROTOL) { throw new System.SystemException("Stationarity test failed at " + v + ": " + System.Math.Abs(kktsum[v])); } } // KKT conditions satisfied. Dump out the optimal solutions and // the dual values. System.Console.WriteLine("Optimal solution satisfies KKT conditions."); System.Console.WriteLine(" x[] = " + arrayToString(xval)); System.Console.WriteLine(" cpi[] = " + arrayToString(cpi)); System.Console.WriteLine(" rpi[] = " + arrayToString(rpi)); System.Console.WriteLine(" qpi[] = " + arrayToString(qpi)); } catch (ILOG.Concert.Exception e) { System.Console.WriteLine("IloException: " + e.Message); System.Console.WriteLine(e.StackTrace); retval = -1; } finally { if (cplex != null) { cplex.End(); } } System.Environment.Exit(retval); }