// (c) 2023-2026 Fair Isaac Corporation
using System;
using Optimizer;
using Optimizer.Objects;
using static Optimizer.Objects.Utils;
namespace XPRSexamples
{
/// Maximize the total horizontal distance a hang glider flies.
///
/// Configure the wind conditions by setting the parameter wind:
/// wind = 0 : no wind
/// wind = 1 : thermal uplift 250m ahead (positive in a neighborhood of 250m
/// but drops to zero exponentially away from this point)
/// wind = 2 : wind shear (a rapidly diminishing head wind that may cause
/// the glider to stall)
///
/// Trapezoidal discretization.
///
/// Based on AMPL models hang_midpt.mod, shear_midpt.mod, stableair_midpt.mod
/// Source:
/// Reference: R. Bulirsch, E. Nerz, H.J. Pesch, and O. von Stryk, "Combining Direct and
/// Indirect Methods in Optimal Control: Range Maximization of a Hang Glider", in
/// "Optimal Control: Calculus of Variations, Optimal Control Theory and
/// Numerical Methods", ed. by R. Bulirsch, A. Miele, J. Stoer, and K.H. Well
/// (1993) Birkhauser
///
/// *** This model cannot be run with a Community Licence for the provided data
/// instance ***
///
internal class Glidert
{
/// Wind conditions; possible values: 0, 1, 2 (see description at the beginning of the file).
static readonly int wind = 1;
/// Number of discretization points.
const int n = 150;
/// Initial horizontal position.
const double x0 = 0.0;
/// Initial vertical position.
const double y0 = 1000.0;
/// Initial horizontal velocity.
const double vx0 = 13.2275675;
/// Initial vertical velocity.
const double vy0 = -1.28750052;
/// Final vertical position.
const double yN = 900.0;
/// Final horizontal velocity.
const double vxN = 13.2275675;
/// Final vertical velocity.
const double vyN = -1.28750052;
/// Upward wind-velocity coefficient (m/sec).
const double um = 2.5;
/// Upward wind-velocity ratio (m).
const double r = 100.0;
/// First drag coefficient constant.
const double c0 = 0.034;
/// Second drag coefficient constant.
const double k = 0.069662;
/// Mass of glider & pilot.
const double mass = 100.0;
/// Wing area.
const double span = 14.0;
/// Air density.
const double rho = 1.13;
/// Acceleration due to gravity.
const double grav = 9.80665;
/// Weight of glider & pilot (= m*g).
const double weight = mass * grav;
/// Minimum lift coefficient.
const double clMin = 0.0;
/// Maximum lift coefficient.
const double clMax = 1.4;
/// Minimum velocity.
const double velMin = -100.0;
/// Maximum velocity.
const double velMax = 100.0;
/// Minimum acceleration.
const double accMin = -3.0;
/// Maximum acceleration.
const double accMax = 3.0;
/// Minimum duration.
const double durMin = 10.0;
/// Maximum duration.
const double durMax = 200.0;
public static void Main(string[] args)
{
using (XpressProblem prob = new XpressProblem())
{
// Output all messages.
prob.callbacks.AddMessageCallback(DefaultMessageListener.Console);
/**** VARIABLES ****/
// Total duration of the flight
Variable dur = prob.AddVariable(durMin, durMax, ColumnType.Continuous, "dur");
// Horizontal position at time step t
Variable[] x = prob.AddVariables(n + 1).WithName(t => $"x_{t}").ToArray();
// Vertical position at time step t
Variable[] y = prob.AddVariables(n + 1).WithName(t => $"y_{t}").ToArray();
// Horizontal velocity at time step t
Variable[] vx = prob.AddVariables(n + 1).WithName(t => $"vx_{t}")
.WithLB(velMin).WithUB(velMax).ToArray();
// Vertical velocity at time step t
Variable[] vy = prob.AddVariables(n + 1).WithName(t => $"vy_{t}")
.WithLB(velMin).WithUB(velMax).ToArray();
// Derivative of vx at time step t
Variable[] vxDot = prob.AddVariables(n + 1).WithName(t => $"vxDot_{t}")
.WithLB(accMin).WithUB(accMax).ToArray();
// Derivative of vy at time step t
Variable[] vyDot = prob.AddVariables(n + 1).WithName(t => $"vyDot_{t}")
.WithLB(accMin).WithUB(accMax).ToArray();
// Lift coefficient (control variable) at time step t
Variable[] cLift = prob.AddVariables(n + 1).WithName(t => $"cLift_{t}")
.WithLB(clMin).WithUB(clMax).ToArray();
// Objective is to maximize the horizontal position in the final time step
prob.SetObjective(x[n], ObjSense.Maximize);
// Initial and final states
prob.AddConstraint(x[0] == x0);
prob.AddConstraint(y[0] == y0);
if (wind == 2)
prob.AddConstraint(vx[0] == vx0 - 2.5 * (1 + Math.Atan((y0 - 30) / 10)));
else
prob.AddConstraint(vx[0] == vx0);
prob.AddConstraint(vy[0] == vy0);
prob.AddConstraint(y[n] == yN);
prob.AddConstraint(vx[n] == vxN);
prob.AddConstraint(vy[n] == vyN);
// Monotonicity
prob.AddConstraints(n, i => x[i + 1] >= x[i]);
// Trapezoidal discretization
// x[i+1] = x[i] + 0.5*(dur/n)*(vx[i+1] + vx[i])
prob.AddConstraints(n, i => x[i + 1] == x[i] + 0.5 * dur / n * (vx[i + 1] + vx[i]));
// y[i+1] = y[i] + 0.5*(dur/n)*(vy[i+1] + vy[i])
prob.AddConstraints(n, i => y[i + 1] == y[i] + 0.5 * dur / n * (vy[i + 1] + vy[i]));
// vx[i+1] = vx[i] + 0.5*(dur/n)*(vxDot[i+1] + vxDot[i])
prob.AddConstraints(n, i => vx[i + 1] == vx[i] + 0.5 * dur / n * (vxDot[i + 1] + vxDot[i]));
// vy[i+1] = vy[i] + 0.5*(dur/n)*(vyDot[i+1] + vyDot[i])
prob.AddConstraints(n, i => vy[i + 1] == vy[i] + 0.5 * dur / n * (vyDot[i + 1] + vyDot[i]));
// Adjust velocity for the effects of the wind
Expression[] vxWindAdjusted = new Expression[n + 1];
Expression[] vyWindAdjusted = new Expression[n + 1];
Expression[] drag = new Expression[n + 1];
Expression[] lift = new Expression[n + 1];
Expression[] sinEta = new Expression[n + 1];
Expression[] cosEta = new Expression[n + 1];
for (int i = 0; i <= n; i++)
{
if (wind == 2)
// vxAdj = vx + 5*(1 + arctan((y - 30) / 10))
vxWindAdjusted[i] = vx[i] + 5.0 * (1.0 + Arctan((y[i] - 30.0) / 10.0));
else
vxWindAdjusted[i] = vx[i];
if (wind == 1)
{
// upWindVel = (x[i]/r - 2.5)^2
Expression upWindVel = Pow(x[i] / r - 2.5, 2.0);
// vyAdj = vy - um * (1 - upWindVel) * e^(-upWindVel)
vyWindAdjusted[i] = vy[i] - um * (1.0 - upWindVel) * Exp(-1.0 * upWindVel);
}
else
vyWindAdjusted[i] = vy[i];
// vr = sqrt(vx^2 + vy^2)
Expression vr = Sqrt(Pow(vxWindAdjusted[i], 2.0) + Pow(vyWindAdjusted[i], 2.0));
// dragCoef = c0 + k * cLift^2
Expression dragCoef = c0 + k * Pow(cLift[i], 2.0);
// drag = 0.5 * rho * span * dragCoef * vr^2
drag[i] = 0.5 * rho * span * dragCoef * Pow(vr, 2.0);
// lift = 0.5 * rho * span * cLift * vr^2
lift[i] = 0.5 * rho * span * cLift[i] * Pow(vr, 2.0);
// sinEta = vy / vr
sinEta[i] = vyWindAdjusted[i] / vr;
// cosEta = vx / vr
cosEta[i] = vxWindAdjusted[i] / vr;
}
// Equations of motion: F = m * a
// vxDot = (-Lift * sinEta - Drag * cosEta) / mass
prob.AddConstraints(n, i => vxDot[i + 1] ==
(-1.0 * lift[i + 1] * sinEta[i + 1] - drag[i + 1] * cosEta[i + 1]) / mass);
// vyDot = (Lift * cosEta - Drag * sinEta - weight) / mass
prob.AddConstraints(n, i => vyDot[i + 1] ==
(lift[i + 1] * cosEta[i + 1] - drag[i + 1] * sinEta[i + 1] - weight) / mass);
// Dump the problem to disk so that we can inspect it.
prob.WriteProb("glidert.lp", "l");
// Solve to local optimality since solving to global optimality takes some time
prob.NLPSolver = Optimizer.Constants.NLPSOLVER_LOCAL;
// LocalSolver defaults to automatic selection, which picks Knitro for
// this problem. Uncomment the line below to use SLP instead.
// prob.LocalSolver = Optimizer.Constants.LOCALSOLVER_XSLP;
// Set initial values for the local solve
double[] xInitial = new double[n + 1];
double[] yInitial = new double[n + 1];
double[] vxInitial = new double[n + 1];
double[] vyInitial = new double[n + 1];
double[] vxDotInitial = new double[n + 1];
double[] vyDotInitial = new double[n + 1];
double[] cLiftInitial = new double[n + 1];
for (int i = 0; i <= n; i++)
{
xInitial[i] = 5000.0 * i / n;
yInitial[i] = -100.0 * i / n + 1000.0;
vxInitial[i] = vx0;
vyInitial[i] = vy0;
vxDotInitial[i] = 0.0;
vyDotInitial[i] = 0.0;
cLiftInitial[i] = 0.0;
}
prob.NlpSetInitVal(x, xInitial);
prob.NlpSetInitVal(y, yInitial);
prob.NlpSetInitVal(vx, vxInitial);
prob.NlpSetInitVal(vy, vyInitial);
prob.NlpSetInitVal(vxDot, vxDotInitial);
prob.NlpSetInitVal(vyDot, vyDotInitial);
prob.NlpSetInitVal(cLift, cLiftInitial);
// Solve
prob.Optimize();
if (prob.SolStatus != SolStatus.Optimal && prob.SolStatus != SolStatus.Feasible)
throw new Exception($"optimization failed with status {prob.SolStatus}");
double[] sol = prob.GetSolution();
Console.WriteLine($"Flight distance {x[n].GetValue(sol)} over a time of {dur.GetValue(sol)}.");
}
}
}
}