Лабораторная работа 3: элементарные вычисления

Кувшинов Д.Р.

2016


Общее оглавление


Взлёт и посадка: модель

Рассмотрим задачу моделирования вертикального полёта летательного аппарата с реактивным двигателем.

Предположения:

Итак, имеем следующие постоянные:

Введём следующие переменные:

Индексом 0 обозначим момент включения двигателя, индексом 1 — момент выключения двигателя. Тогда Δt = t1t0 — время работы двигателя. Аналогичный смысл обозначение Δ имеет и при применении к прочим переменным. Через Δvr обозначено изменение скорости, приобретаемое в результате действия реактивной тяги, вычисляемое по формуле Циолковского. Благодаря предположениям модели изменения скорости под действием реактивной силы и гравитации можно просто сложить:

Подставив вместо Δt переменную 0 ≤ τ ≤ Δt (t = t0 + τ), можно выразить функции изменения массы и скорости аппарата:

Продифференцировав скорость, получим ускорение w(τ). Проинтегрировав скорость, получим высоту x(τ):

Итак, пусть заданы начальные значения t0, m0, v0, x0 и целевые Δt и Δm. Тогда не составит труда вычислить конечные значения переменных:

Программная реализация

Для реализации вышеприведённых формул на языке C++ пригодится стандартный заголовочный файл cmath.

Для выполнения моделирования можно определить следующие переменные:

// Параметры модели

double g;
double I;
double empty_m;

// Переменные модели

double t0, t1;
double m0, m1;
double x0, x1;
double v0, v1;
double w0, w1;

Впрочем, здесь уместно заметить, что хотя формально всё это — числа, они имеют различный физический смысл. Для того, чтобы подчеркнуть этот факт, можно воспользоваться средствами системы типов языка программирования и определить для каждой физической величины свой тип. К сожалению, полноценная конструкция такого рода (допускающая осмысленные переходы между типами, например деление расстояния на время, дающее скорость) в C++ слишком сложна. Поэтому в данной программе мы ограничемся “симуляцией” разных типов через введение синонимов типа double.

// Вспомогательные типы

using Acceleration = double; // м/с2
using Velocity = double; // м/с
using Position = double; // м
using Time = double; // с
using Mass = double; // кг
using Direction = double; // вверх или вниз

const Direction
  upwards   = +1.0,  // Вверх
  downwards = -1.0;  // Вниз
  
// Параметры модели

Acceleration g;
Velocity I;
Mass empty_m;

// Переменные модели

Time t0, t1;
Mass m0, m1;
Position x0, x1;
Velocity v0, v1;
Acceleration w0, w1;

Один шаг работы программы состоит в вычислении очередных значений переменных t1, m1, x1, v1 (а также w0 и w1) для заданных пользователем значений Δm и Δt. Пусть этим занимается функция

void compute_step(Time delta_t, Mass delta_m, Direction dir);

Для следующего шага старые значения t1, m1, x1, v1 должны стать новыми значениями t0, m0, x0, v0: конечное состояние предыдущего шага — это начальное состояние следующего шага. Для выполнения этого действия добавим вспомогательную функцию

// "Перевернуть страницу"
void finish_step()
{
  t0 = t1;
  m0 = m1;
  x0 = x1;
  v0 = v1;
}

Впрочем, в данный момент удобно поместить сюда же вывод текстовой информации о текущем шаге:

// "Перевернуть страницу"
void finish_step()
{
  cout << '\n' << w0 << " ~ " << w1 << "m/s2;\n";
  cout << t1 << "s, " << x1 << "m: ";
  cout << (m1 - empty_m) << "kg; " << v1 << "m/s;\n\n";

  t0 = t1;
  m0 = m1;
  x0 = x1;
  v0 = v1;
  w0 = w1;
}

Один полёт состоит из цикла, на каждой итерации которого выполняется очередной шаг для очередных значений Δm и Δt, введённых пользователем. При этом для удобства будем считать, что пользователь вводит в качестве Δm неотрицательное значение, если сопло двигателя направлено вниз, и отрицательное, если сопло направлено вверх (“реверс”). Указанный цикл можно реализовать в виде следующей функции:

// Один полёт
void step_loop()
{
  cout << "Enter delta-m delta-t for each step\n";

  Time delta_t;
  Mass delta_m;
  while (cin >> delta_m >> delta_t)
  {
    compute_step(delta_t, -abs(delta_m),
      delta_m < 0 ? downwards : upwards);
    finish_step();
  }
}

Работа консольного приложения состоит из череды симуляций полётов. Каждый полёт характеризуется начальными значениями m0, x0 и v0 (t0 положим равным 0), а также собственными значениями параметров g, I и empty_m. После установки должных значений вызывается функция step_loop. После выполняем очистку потока ввода стандартными средствами.

// Консольное приложение
int main()
{
  void select_g();  // Выбор g.
  void select_I();  // Выбор I.
  void step_loop(); // Симуляция полёта.

  while (true)
  {
    select_g();
    select_I();

    t0 = 0; // Обнуляем время.
    cout << "\nEmpty mass = ";
    cin >> empty_m; // Масса пустого.
    cout << "Propellant mass = ";
    cin >> m0; // Масса рабочего тела.
    m0 += empty_m; // Полная масса.

    cout << "x0 = ";
    cin >> x0; // Начальная высота.
    cout << "v0 = ";
    cin >> v0; // Начальная скорость.

    cout << '\n';
    step_loop();

    // Сброс и очистка потока ввода.
    cin.clear();
    cin.sync();
    cin.ignore(cin.rdbuf()->in_avail());
  }

  return EXIT_SUCCESS;
}

Функции select_g и select_I устанавливают соответственно значения g и I, предлагая пользователю выбор из набора известных значений для разных безатмосферных тел и реактивных двигателей.

Процедурная декомпозиция проекта
Процедурная декомпозиция проекта

Итак, всё целиком имеет следующий вид:

#include <cstdlib>
#include <cassert>
#include <iostream>
#include <cmath>
using namespace std;

// Вспомогательные типы

using Acceleration = double; // м/с2
using Velocity = double; // м/с
using Position = double; // м
using Time = double; // с
using Mass = double; // кг
using Direction = double; // вверх или вниз

const Direction
  upwards   = +1.0,  // Вверх
  downwards = -1.0;  // Вниз

// Параметры модели

Acceleration g;
Velocity I;
Mass empty_m;

// Переменные модели

Time t0, t1;
Mass m0, m1;
Position x0, x1;
Velocity v0, v1;
Acceleration w0, w1;

// Консольное приложение
int main()
{
  void select_g();  // Выбор g.
  void select_I();  // Выбор I.
  void step_loop(); // Симуляция полёта.

  while (true)
  {
    select_g();
    select_I();

    t0 = 0; // Обнуляем время.
    cout << "\nEmpty mass = ";
    cin >> empty_m; // Масса пустого.
    cout << "Propellant mass = ";
    cin >> m0; // Масса рабочего тела.
    m0 += empty_m; // Полная масса.

    cout << "x0 = ";
    cin >> x0; // Начальная высота.
    cout << "v0 = ";
    cin >> v0; // Начальная скорость.

    cout << '\n';
    step_loop();

    // Сброс и очистка потока ввода.
    cin.clear();
    cin.sync();
    cin.ignore(cin.rdbuf()->in_avail());
  }

  return EXIT_SUCCESS;
}


// Выбор ускорения свободного падения
void select_g()
{
  const Acceleration
    g_mercury = 3.7,   // Меркурий
    g_moon    = 1.62,  // Луна
    g_europa  = 1.315, // Европа
    g_pluto   = 0.617, // Плутон
    g_ceris   = 0.27;  // Церера

  cout << "\nSelect location:\n"
    "0. No gravity\n"
    "1. Mercury\n"
    "2. Moon\n"
    "3. Europa\n"
    "4. Pluto\n"
    "5. Ceris\n";

  switch (cin.get())
  {
  case '0': g = 0.0; break;
  case '1': g = g_mercury; break;
  case '2': g = g_moon; break;
  case '3': g = g_europa; break;
  case '4': g = g_pluto; break;
  case '5': g = g_ceris; break;
  default:
    cout << " g = ";
    cin >> g;
  }

  cin.ignore();
}

// Выбор удельного импульса
void select_I()
{
  const Velocity
    I_RD_253  = 3100,   // Протон-М 1 ст.; гидразин + азотная кислота
    I_NK_33   = 3250,   // Союз-М 1 ст.; керосин + кислород
    I_J_2     = 4170,   // Saturn V, 2/3 ст.; водород + кислород
    I_RD_0120 = 4460,   // Энергия, ускоритель; водород + кислород
    I_arcjet  = 16000,  // КА, водород, проект
    I_SPD_230 = 26500,  // КА, ксенон
    I_VISTA   = 157000; // D-T ТЯРД, водород, проект

  cout << "\nSelect engine:\n"
    "1. RD-253\n"
    "2. NK-33\n"
    "3. J-2\n"
    "4. RD-0120\n"
    "5. Arcjet\n"
    "6. SPD-230\n"
    "7. VISTA\n";

  switch (cin.get())
  {
  case '1': I = I_RD_253; break;
  case '2': I = I_NK_33; break;
  case '3': I = I_J_2; break;
  case '4': I = I_RD_0120; break;
  case '5': I = I_arcjet; break;
  case '6': I = I_SPD_230; break;
  case '7': I = I_VISTA; break;
  default: 
    cout << " I = ";
    cin >> I;
  }

  cin.ignore();
}


// Один полёт
void step_loop()
{
  void compute_step(Time delta_t, Mass delta_m, Direction dir);
  void finish_step();

  cout << "Enter delta-m delta-t for each step\n";

  Time delta_t;
  Mass delta_m;
  while (cin >> delta_m >> delta_t)
  {
    compute_step(delta_t, -abs(delta_m),
      delta_m < 0 ? downwards : upwards);
    finish_step();
  }
}

// "Перевернуть страницу"
void finish_step()
{
  cout << '\n' << w0 << " ~ " << w1 << "m/s2;\n";
  cout << t1 << "s, " << x1 << "m: ";
  cout << (m1 - empty_m) << "kg; " << v1 << "m/s;\n\n";

  t0 = t1;
  m0 = m1;
  x0 = x1;
  v0 = v1;
  w0 = w1;
}

// Вычислить конечные значения переменных на очередном шаге.
void compute_step(Time delta_t, Mass delta_m, Direction dir)
{
  // dir используется как множитель I
  // t1 = ?
  // m1 = ?
  // w0 = ?
  // w1 = ?
  // v1 = ?
  // x1 = ?
}

Задача лабораторной работы состоит в написании тела функции compute_step.

Попробуйте ответить на следующие вопросы:

  1. Правильно ли моделируется ситуация, когда пользователь задал Δm = 0 (свободное падение)?
  2. Правильно ли моделируется ситуация, когда пользователь задал Δm > (m0масса-пустого)?
  3. Что произойдёт, если пользователь задаст Δt = 0?
  4. Что произойдёт, если пользователь задаст Δt < 0?
  5. Возможно ли с заданной точностью (например, до 1 метра) определить момент пересечения x = 0 (посадка или падение)?

Общее оглавление

Кувшинов Д.Р. © 2016