Авторизация



Счетчики

Обмен ссылками

Блог программиста
Металлические конструкции - проектирование металлоконструкций в Новосибирске и пригороде на заказ.

Пульты для телевизоров

Главная Borland C++ Builder Броуновское движение
Броуновское движение PDF Печать E-mail
Автор: Андрей   
31.03.2009 16:49

Описание программы
Расположение компонентов на форме
Свойства компонент
Код программы
Код с подробными комментариями
Советы по улучшению и расширению программы

Описание программы
Эта программа имитирует броуновское движение частиц в закрытом сосуде. В данном случае сосуд прямоугольный, а частицы - двумерные круги. При столкновении частицы со стенкой сосуда проекция ее скорости вдоль линии стенки не меняется, а проекция, перпендикулярная линии, меняется на противоположную. При столкновении же двух частиц друг с другом каждая из них приобретает скорость, которой обладала другая до столкновения. При этом считается, что массы частиц одинаковы, а геометрия их не учитывается. Для частиц определяется специальный класс, в самом простом случае содержащий координаты частиц, проекции их скорости и функцию, рисующую частицу. При нажатии на кнопку "Старт" случайным образом происходит определение координат и скоростей каждой частицы, после чего запускается таймер, при каждом срабатывании которого происходит рассчет - частицы перемещаются, после чего происходит проверка, не находятся ли какие-нибудь частицы ближе, чем на расстоянии суммы радиусов (не произошло ли их столкновение), а также не столкнулись ли какие-то частицы со стенками сосуда, после чего происходит соответствующее изменение скоростей таких частиц. Также каждые десять срабатываний таймера происходит отрисовка частиц. Почему не каждый раз при срабатывании таймера? Ответ на этот вопрос связан с проблемой чисто механического подхода к столкновениям частиц. Реальные столкновения обусловлены деформацией электронных оболочек соприкасающихся атомов - при достаточном их сближении сила отталкивания резко возрастает, в результате чего скорости движения меняются соответствующим образом. Если же одновременно сталкивается более двух частиц, то силы, действующие на каждую из них вследствие отталкивания в каждый момент времени, складываются. В нашей же модели столкновения обрабатываются попарно, из-за чего скорость каждой частицы определяется только последним обработанным за один проход столкновением. Поэтому наиболее корректная картина будет в том случае, если каждая частица одновременно будет сталкиваться не более чем с одной из других. Для этого скорости частиц должны быть достаточно низкими при не слишком большой концентрации, а соответственно, слишком частая прорисовка не будет добавлять новой визуальной информации, а будет только понапрасну загружать систему.

Используются следующие компоненты: Button, PaintBox, Timer.

Расположение компонентов на форме

Расположение компонентов на форме программы

Свойства компонент, измененные по сравнению со стандартными
Button1:
Caption: "Старт"
Button2:
Caption: "Выход"
PaintBox1:
Height: 385
Width: 641
Timer1:
Enabled: false
Interval: 1

Код программы

const int N = 300;
const int A = 641;
const int B = 385;
const int R = 10;

int n = 0;

class MoleculaClass
 {
 public:
 void Draw();
 double X, Y;
 double Vx, Vy;
 };
void MoleculaClass::Draw()
 {
 Form1 -> PaintBox1 -> Canvas -> Ellipse(X - R, Y - R, X + R, Y + R);
 }

MoleculaClass Molecula[N];
//--------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
bool Ok;
Molecula[0].X = R + random(A - 2 * R);
Molecula[0].Y = R + random(B - 2 * R);
for (int i = 1; i <= N - 1; i++)
 {
 Ok = false;
 while (Ok == false)
  {
  Ok = true;
  Molecula[i].X = R + random(A - 2 * R);
  Molecula[i].Y = R + random(B - 2 * R);
  for (int j = 0; j <= i - 1; j++)
   if (((Molecula[i].X - Molecula[j].X) * (Molecula[i].X -
  Molecula[j].X) + (Molecula[i].Y - Molecula[j].Y) *
  (Molecula[i].Y - Molecula[j].Y)) < 4 * R * R)
    Ok = false; continue;
  }
 }
for (int i = 0; i <= N - 1; i++)
 {
 Molecula[i].Vx = 0.002 * random(100) - 0.1;
 Molecula[i].Vy = 0.002 * random(100) - 0.1;
 }
Timer1 -> Enabled = true;
}
//--------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
exit(0);
}
//--------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
for (int i = 0; i <= N - 1; i++)
 {
 Molecula[i].X += Molecula[i].Vx;
 Molecula[i].Y += Molecula[i].Vy;
 }
double vx, vy;
for (int i = 0; i <= N - 2; i++)
 for (int j = i + 1; j <= N - 1; j++)
  if (((Molecula[i].X - Molecula[j].X) * (Molecula[i].X -
  Molecula[j].X) + (Molecula[i].Y - Molecula[j].Y) *
  (Molecula[i].Y - Molecula[j].Y)) < 4 * R * R)
   {
   vx = Molecula[i].Vx;
   vy = Molecula[i].Vy;
   Molecula[i].Vx = Molecula[j].Vx;
Molecula[i].Vy = Molecula[j].Vy;
Molecula[j].Vx = vx;
Molecula[j].Vy = vy;
   }
for (int i = 0; i <= N - 1; i++)
 {
 if (Molecula[i].X A - R)
  Molecula[i].Vx *= -1;
 if (Molecula[i].Y B - R)
  Molecula[i].Vy *= -1;
 }
if (n == 0)
 {
 PaintBox1 -> Repaint();
 for (int i = 0; i <= N - 1; i++)
  Molecula[i].Draw();
 }
n++;
if (n == 10) n = 0;
}

Код программы с комментариями

const int N = 300; //Число молекул в сосуде
const int A = 641; //Ширина сосуда - равна ширине PaintBox1
const int B = 385; //Высота сосуда - равна высоте PaintBox1
const int R = 10; //Радиус молекул

int n = 0; //Счетчик, обеспечивающий прорисовку
  //раз в десять срабатываний таймера

class MoleculaClass //Класс для молекул
 {
 public:
 void Draw(); //Функция рисования молекулы
 double X, Y; //Координаты молекулы
 double Vx, Vy; //Проекции скорости молекулы на оси X и Y
 };
void MoleculaClass::Draw()
 {
 Form1 -> PaintBox1 -> Canvas -> Ellipse(X - R, Y - R, X + R, Y + R); //Рисуем окружность с центром в (X, Y) и радиусом R
 }

MoleculaClass Molecula[N]; //Массив из N молекул
//--------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
bool Ok; //Флаг, сигнализирующем об успешном задании
  //координат молекулы
Molecula[0].X = R + random(A - 2 * R); //Определяем случайным образом
  //координаты первой частицы
Molecula[0].Y = R + random(B - 2 * R); //При этом учитываем ее размеры
for (int i = 1; i <= N - 1; i++) //Определяем координаты оставшихся
  //N-1 молекул
 {
 Ok = false; //Устанавливаем, что координаты еще не заданы
 while (Ok == false) //До тех пор, пока не будет получен
  //сигнал об успешном задании, пытаемся их определить
  {
  Ok = true; //Если в дальнейшем значение флага не поменяется -
  //значит координаты определены успешно
  Molecula[i].X = R + random(A - 2 * R); //Случайным образом
  //"на пробу" определяем координаты i-ой молекулы
  Molecula[i].Y = R + random(B - 2 * R);
  for (int j = 0; j <= i - 1; j++) //Проверяем, как соотносятся
  //эти координаты с координатами предыдущих молекул
   if (((Molecula[i].X - Molecula[j].X) * (Molecula[i].X -
  Molecula[j].X) + (Molecula[i].Y - Molecula[j].Y) *
  (Molecula[i].Y - Molecula[j].Y)) < 4 * R * R) //Если расстояние
  //между текущей и какой-то j-ой молекулой меньше двух радиусов...
    Ok = false; continue; //...то устанавливаем, что координаты
  //заданы неудачно и досрочно прерываем цикл
  }
 }
for (int i = 0; i <= N - 1; i++) //Для всех N молекул
  //случайным образом определяем скорости
 {
 Molecula[i].Vx = 0.002 * random(100) - 0.1; //Скорости могут лежать
  //в интервале
 Molecula[i].Vy = 0.002 * random(100) - 0.1; //от -0.100 до +0.100
 }
Timer1 -> Enabled = true; //Запускаем таймер
}
//--------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
exit(0); //Выход из приложения
}
//--------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
for (int i = 0; i <= N - 1; i++) //Перемещаем все молекулы
 {
 Molecula[i].X += Molecula[i].Vx;
 Molecula[i].Y += Molecula[i].Vy;
 }
double vx, vy; //Вспомогательные переменные,
  //необходимые для реализации обмена скоростями двух молекул
for (int i = 0; i <= N - 2; i++) //Для всех частиц, кроме последней...
 for (int j = i + 1; j <= N - 1; j++) //...проверяем расстояние
  //между ней и теми, которые еще не проверены
  if (((Molecula[i].X - Molecula[j].X) * (Molecula[i].X -
  Molecula[j].X) + (Molecula[i].Y - Molecula[j].Y) *
  (Molecula[i].Y - Molecula[j].Y)) < 4 * R * R)
   {
   vx = Molecula[i].Vx; //Если молекулы слишком близко -
  //т.е. столкнулись,
   vy = Molecula[i].Vy; //то осуществляем обмен их скоростями
   Molecula[i].Vx = Molecula[j].Vx;
Molecula[i].Vy = Molecula[j].Vy;
Molecula[j].Vx = vx;
Molecula[j].Vy = vy;
   }
for (int i = 0; i <= N - 1; i++) //Проверяем, не столкнулась ли
  //молекула со стенками сосуда
 {
 if (Molecula[i].X A - R) //С вертикальными
  Molecula[i].Vx *= -1; //Меняем X-проекцию на противоположную
 if (Molecula[i].Y B - R) //С горизонтальными
  Molecula[i].Vy *= -1; //Меняем Y-проекцию на противоположную
 }
if (n == 0) //Каждые 10 срабатываний таймера отрисовываем
 {
 PaintBox1 -> Repaint(); //Очищаем экран
 for (int i = 0; i <= N - 1; i++)
  Molecula[i].Draw(); //Рисуем каждую молекулу
 }
n++; //Увеличиваем счетчик срабатываний на единицу
if (n == 10) n = 0; //Обеспечиваем цикличность счетчика
}

Советы по улучшению и расширению программы
Здесь у нас все молекулы полагались одинаковыми как по размеру, так и по массе, однако для каждой из них можно задать свой собственный радиус и свою собственную массу (например, случайным образом). В этом случае необходимо будет заменить все выражения вида "R * R" на "Molecula[i].R * Molecula[j].R", а формулу рассчета скоростей после столкновения двух молекул взять
V'_i=(2 * m_j * V_j + (m_i - m_j) * V_i) / (m_i + m_j)
и, соответственно
V'_j=(2 * m_i * V_i + (m_j - m_i) * V_j) / (m_i + m_j)
как для X-овой, так и для Y-овой компоненты.
Однако, и в этом приближении удар считается центральным. Случай нецентрального удара гораздо более нетривиален, однако более реалистичен. Точные формулы здесь приводиться не будут, однако вооружившись поисковиком вы при желании вполне можете найти подходящий материал, либо же можете просчитать этот процесс сами. Как говорится, реализация нецентрального удара оставляется читателю в качестве несложного домашнего упражнения!;)
Также для большей реалистичности вы можете определить скорости молекул (в случае, если массы всех молекул одинаковы) не совершенно случайным образом, а в соответствии с функцией распределения Максвелла:
f_v(v_x) = sqrt(m / (2 * pi * k * T)) * exp(-m * v_x^2 / (2 * k * T)). Или же, введя для краткости константу C = m / (2 * k * T):
f_v(v_x) = sqrt(C / pi) * exp(- C * v_x^2).

Обновлено 04.06.2009 14:03