Метод Монте-Карло (возможно, имитация отжига?) для N взаимно отталкивающихся точек на единичной сфере C++

Мне нужно создать алгоритм на C++ для моделирования взаимно отталкивающих точек на сфере с использованием метода Монте-Карло. Пока что у меня есть это:

#include <stdio.h> 
#include <string.h>
#include <math.h>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <time.h>
#include <stdlib.h>
using namespace std;

int main()
{

  int a,f,g,n,m,i,j,k,r,s;
  double p,q,Energy,energy,y[101][4],x[101][4],Length,Distance;

 clock_t t1,t2;
  t1=clock();

  /*  set the number of points */
  n=12;

  /* check that there are no more than 100 points */
  if(n>100){
    cout << n << " is too many points for me :-( \n";
    exit(0);
  }

  /* reset the random number generator */
  srand((unsigned)time(0));  

  for (i=1;i<=n;i++){
    x[i][1]=((rand()*1.0)/(1.0*RAND_MAX)-0.5)*2.0;
    x[i][2]=((rand()*1.0)/(1.0*RAND_MAX)-0.5)*2.0;
    x[i][3]=((rand()*1.0)/(1.0*RAND_MAX)-0.5)*2.0;

    Length=sqrt(pow(x[i][1],2)+pow(x[i][2],2)+pow(x[i][3],2));

    for (k=1;k<=3;k++){
      x[i][k]=x[i][k]/Length;
    }
  }

  /* calculate the energy */
  Energy=0.0;

  for(i=1;i<=n;i++){
    for(j=i+1;j<=n;j++){
      Distance=sqrt(pow(x[i][1]-x[j][1],2)+pow(x[i][2]-x[j][2],2)
                    +pow(x[i][3]-x[j][3],2));

      Energy=Energy+1.0/Distance;
    }
  }

  /* Save Original Points */
  for(i=1;i<=n;i++){
    y[i][1]=x[i][1];
    y[i][2]=x[i][2];
    y[i][3]=x[i][3];
  }

  /* Loop for random points m times*/
  m=10;

  if (m>100){
    cout << "The m="<< m << " loop is inefficient...lessen m \n";
    exit(0);
  }

  a=1;

  while(a<m){

    /* assign random points */
    for (i=1;i<=n;i++){
      x[i][1]=((rand()*1.0)/(1.0*RAND_MAX)-0.5)*2.0;
      x[i][2]=((rand()*1.0)/(1.0*RAND_MAX)-0.5)*2.0;
      x[i][3]=((rand()*1.0)/(1.0*RAND_MAX)-0.5)*2.0;

      Length=sqrt(pow(x[i][1],2)+pow(x[i][2],2)+pow(x[i][3],2));

      for (k=1;k<=3;k++){
        x[i][k]=x[i][k]/Length;
      }
    }

    /* calculate the energy */
    energy=0.0;

    for(i=1;i<=n;i++){
      for(j=i+1;j<=n;j++){
        Distance=sqrt(pow(x[i][1]-x[j][1],2)+pow(x[i][2]-x[j][2],2)
                      +pow(x[i][3]-x[j][3],2));

        energy=energy+1.0/Distance;
      }
    }

    if(energy<Energy)
      for(i=1;i<=n;i++){
        for(j=1;j<=3;j++){
          Energy=energy;
          y[i][j]=x[i][j];
        }
      }
    else
      for(i=1;i<=n;i++){
        for(j=1;j<=3;j++){
          energy=Energy;
          x[i][j]=y[i][j];
        }
      }

    a=a+1;
  }

  /* Output the best random energy */
  cout << "Energy=" << Energy << "\n";

  m=10;
  a=1;

  while(a<m){
    /* Choose random point to move */
    g=(rand() % n)+1;

    /* Choose a p small to give q in a range -p <= q <= p */
    p=0.1;

    /* q is how much I am moving the random point by */
    q=((rand()*1.0)/(1.0*RAND_MAX)-0.5)*2.0*p;

    /* Move the point by q */
    for(j=1;j<=3;j++){
      x[g][j]=((x[g][j])+q);
    }

    /* Bring it back onto sphere */
    Length=sqrt(pow(x[g][1],2)+pow(x[g][2],2)+pow(x[g][3],2));

    for (k=1;k<=3;k++){
      x[g][k]=x[g][k]/Length;
    }

    /* Calculate the new energy */
    energy=0.0;

    for(i=1;i<=n;i++){
      for(j=i+1;j<=n;j++){
        Distance=sqrt(pow(x[i][1]-x[j][1],2)+pow(x[i][2]-x[j][2],2)
                         +pow(x[i][3]-x[j][3],2));

        energy=energy+1.0/Distance;
      }
    }

    /* Choose best energy and therefore best point */
    if (energy<Energy)
      Energy=energy,x[g][1]=y[g][1],x[g][2]=y[g][2],x[g][3]=y[g][3];
    else
      energy=Energy,y[g][1]=x[g][1],y[g][2]=x[g][2],y[g][3]=x[g][3];

    a=a+1;  

  }

   /* Output the best single shift energy */
  cout << "Energy=" << Energy << "\n";

  /* Set fail count to 0 */
  s=0;
  f=0;
  r=1;
  **p=0.1;**

  /* Maximum distance to move the random point */

  while (**p>0.00001**) {

    /* Number of loops to do */

    while (**r<3000**) {

      g=(rand() % n)+1;

      /* q is how much I am moving the random point by -p<=q<=p*/
      q=((rand()*1.0)/(1.0*RAND_MAX)-0.5)*2.0*p;

      /* Move the point by q */
      for(j=1;j<=3;j++){
        x[g][j]=((x[g][j])+q);
      }

      /* Bring it back onto sphere */
      Length=sqrt(pow(x[g][1],2)+pow(x[g][2],2)+pow(x[g][3],2));

      for (k=1;k<=3;k++){
        x[g][k]=x[g][k]/Length;
      }

      /* Calculate the new energy */
      energy=0.0;

      for(i=1;i<=n;i++){
        for(j=i+1;j<=n;j++){
          Distance=sqrt(pow(y[i][1]-y[j][1],2)+pow(y[i][2]-y[j][2],2)
                        +pow(y[i][3]-y[j][3],2));
          energy=energy+1.0/Distance;
        }
      }

      /* Choose best energy and therefore best point */
      if (energy<Energy)
        Energy=energy,x[g][1]=y[g][1],x[g][2]=y[g][2],x[g][3]=y[g][3],s=s+1;
      else
        energy=Energy,y[g][1]=x[g][1],y[g][2]=x[g][2],y[g][3]=x[g][3],f=f+1;

      r=r+1;

    }

    **/* Calculate percentage fails */

    if ((100.0*(f/r))>50.0)
      p=(p-0.00001);
    else
      p=p;**

    r=0;  

  }

  cout << "Overall Success Rate = " << ((s*1.0)/((s+f)*1.0))*100 << "%" << "\n";
  cout << "Energy=" << fixed << setprecision(10) << Energy << "\n";


  ofstream Bestpointssofar ("Bestpointssofar");
  for(i=1;i<=n;i++){
    Bestpointssofar << y[i][1] << " " <<   y[i][2] << " " << y[i][3] << "\n";
  }
  Bestpointssofar.close(); 

  t2=clock();
    float diff ((float)t2-(float)t1);
    float seconds = diff / CLOCKS_PER_SEC;
    cout << fixed << setprecision(2) << "Run time: " << seconds << "(s)" << "\n";
    return 0;

}

Я думаю, что это нормально (обратите внимание, что я, по сути, пытаюсь минимизировать функцию энергии), но я хочу сделать ее более точной/заставить ее работать быстрее. Для этого, я думаю, мне следует изменить значение p, условия цикла while или как изменить p в конце кода. (Все они находятся в *... *, поскольку я пытался ободрить их, чтобы вы поняли, что я имею в виду. Примерно 3/4 пути по коду). Я сижу часами, пытаясь изменить эти условия, но ничего не работает. Для n=12 (12 точек на сфере) моя энергия должна быть равна 49,16525306, но на самом деле я могу получить ее только между 50,5 и 54,0. Я знаю, что это относительно хорошо, но я хочу, чтобы это было более точно (даже если это займет некоторое время). Я также хотел бы, чтобы уровень успеха увеличился, если это возможно (мой общий уровень успеха просто ужасен).

Если у кого есть идеи, буду очень благодарен за помощь!

Спасибо.

(Примечание: если вы хотите запустить код, вы должны удалить двойные *. Есть четыре раздела с двойными *, окружающими их).


person adrem7    schedule 29.12.2012    source источник


Ответы (1)


Во-первых, вы похожи на умного ученого/математика, пытающегося заняться программированием. Я физик, и по моему опыту из таких людей получаются одни из худших программистов; если это вообще возможно, обратитесь за помощью к опытному программисту.

Во-вторых, посмотрите на этот код (который повторяется, см. Во-первых):

/* Move the point by q */
for(j=1;j<=3;j++){
  x[g][j]=((x[g][j])+q);
}

Вы изменяете все три координаты на одинаковую величину, что означает, что вы всегда перемещаете точку по лучу (1,1,1). Результаты улучшаются, если вы изменяете одну координату за раз.

В-третьих, в последнем цикле (на который уходит больше всего времени) ваша логика немного странная — вы изменяете x, но затем вычисляете энергию, используя < эм>г. Результаты по-прежнему довольно хорошие, потому что у вас также есть перестановки x и y в конце цикла, но исправление этого повышает точность результатов.

В-четвертых, и это очень важно, когда вы возмущаете точку, а затем пересчитываете энергию, вы пересчитываете вклады всех точек; изменилась только одна точка, а это значит, что большинство пар точек не изменились и не нуждаются в пересчете. Вместо этого после того, как вы выберете точку, вы можете рассчитать вклад этой точки примерно так:

double oldEnergy = 0.0;
  for(i=1;i<=n;i++)
    {
      if(i!=g)
        {
          Distance=myDistance(x[i], x[g]);
          oldEnergy += 1.0/Distance;
        }
    }

Затем вычислите его снова после возмущения и сравните. Это переводит вычисление из O(n2) в O(n), что делает его намного быстрее.

Когда я делаю эти модификации (и заставляю p сходиться в 10 раз быстрее, потому что я не очень терпелив), моя энергия выходит на 49,1652530576.

person Beta    schedule 01.01.2013
comment
@adrem7, в своем (удаленном) ответе вы говорите, что внедрили эти изменения и получили огромные негативные энергии. Мой первый тезис таков: пытаетесь ли вы заставить этот код работать или изучаете программирование, лучший способ — получить помощь от опытного программиста. В этом случае ваша главная ошибка заключалась в том, что вы внесли много изменений, прежде чем протестировать любое из них. Также вы вычисляете вещи, которые не нужно вычислять, и сохраняете вещи, которые не нужно хранить. (Я написал класс Point, который значительно упростил код.) В частности, вы устанавливаете energy равным нулю, а затем вычитаете из него. - person Beta; 05.01.2013
comment
@adrem7: ИСПРАВЛЕНИЕ, конкретная проблема не в том, что вы вычитаете энергию, а в том, что у вас все еще есть перестановки x и y в конце последнего большого цикла. - person Beta; 06.01.2013