3 Haziran 2016 Cuma

C/C++ Programlama Dili ve ROOT



Öncelikle şunu belirtmeliyim ki C/C++ programlama dilini başlangıç seviyesinden (->Hello World!) itibaren öğrenmek için internette bir çok kaynak bulabilirsiniz. Örneğin buradaki gibi. Ve bir programlama dilini öğrenmenin en iyi yolu örnekleri anlamaktan geçer. Bu yüzden önce neyi öğrenmeliyim, nasıl başlamalıyım bu işe gibi düşünceleriniz varsa aklınızda, elbette C/C++ bilmek sizin için bir avantaj olacaktır ancak C/C++ hakkında hiç bir şey bilmeden de, root örneklerini inceleyerek bu işe vakit kaybetmeden başlayabilirsiniz.


Bu yazımda önce C/C++ ile bir kaynak kod hazırlayarak, bu kodu derlemeyi ve dışa bir çıktı kaydetmeyi göstereceğim daha sonra ROOT için bir kaynak kod hazırlayıp dışarı kaydettiğimiz dosyayı içe aktararak bunları bir grafik halinde çizdireceğiz. En son olarak ise bu iki kodu nasıl birleştireceğimizi anlatacağım.

Tahmin edebileceğiniz gibi kod yazmak için bir editöre ihtiyacınız olacak. Linux kullandığınızı düşünerek benim size tavsiye edeceğim editör emacs ya da vim olacaktır.

Emacs editörü kullanarak hazırlanmış kaynak kodlarımız.

C/C++ Kodları ile Eksponansiyel Bir Dağılım Üzerinden Random Sayı Üretme:

Başlangıç olarak basit ve konumuzla ilgili bir örnek vermek istedim. Başlık sizi korkutabilir ancak endişelenecek bir şey yok. Bir önceki yazımda rastgele sayı üretmek ile ilgili bir kaç şeyden bahsetmiştim bugün biraz daha detaylı bakacağız olaya. Amacımız burda matematik yapmak veya detaylı programlar hazırlamak olmadığı için oldukça basit bir şekilde vereceğim bu örneği size. Bu örneği yapmaktaki diğer bir amacım ise ileride veri analizi yapabilmek için size veri oluşturabilmek. Bu işi simulasyon ile veri üreterek yapabiliriz ve bu örneği simulasyona giriş olarak düşünebilirsiniz.

Kaynak kodu yazarken, yazacağınız kodun ne kadar detaylı olacağını hesaplayıp, kodlarımızı ona göre düzenlememiz gerekir. C/C++ ile çalışırken class/function (sınıf-fonksiyon)  kullanarak fonksiyonlarımızı sınıflandırabilir ve bunlar üzerinde ana programımızı  birbirleri ile ilişkilendirerek tasarlayabiliriz. Ayrıca sınıf ve fonksiyonlarımızı ayrı ayrı dosyalarda tutup kendimize bir dizin oluşturabiliriz. Zamanla bunları bir kütüphane haline bile getirebilirsiniz. Temelde ROOT'un bize sağladığı kolaylık bu kütüphanelerdir. Class/Function kullanımı gerçekten büyük kolaylık sağlamaktadır bu yüzden bunu en basit kodlarınız da dahi kullanıp alışkanlık edinmelisiniz.

Bir dağılımdan rasgele sayı üretmek için ters dönüşüm yöntemini kullanabiliriz. Bir f(x) fonksiyonunun, F(x) dağılım fonksiyonu için, U rastgele sayı olmak üzere X rastgele sayısı F(x) fonksiyonun tersi ile elde edilebilir.

f(x) = \left\{ \begin{array}{ll} e^{-x} & \mbox{if } x \geq 0 \\ 0 & \mbox{if } x < 0 \end{array} \right.
F(x) = \left\{ \begin{array}{ll} 1-e^{-x} & \mbox{if } x \geq 0 \\ 0 & \mbox{if } x < 0 \end{array} \right.
F^{-1}(U) = \left\{ \begin{array}{ll} -ln(1-U) & \mbox{if } x \geq 0 \\ 0 & \mbox{if } x < 0 \end{array} \right.

Bu bilgileri ve C/C++ 'ın matematik kütüphanesini kullanarak kolayca rastgele sayılarımızı üretebiliriz.
  
#include <fstream>
#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <math.h>

using namespace std;

const int n = 10000;

class fun //Sınıf isminiz
{

 public: //Sınıfımızın paylaşıma açık parametreleri
  
  ofstream outfile;
  
  float x[n],f[n],F[n],X[n];

  void generate();

 private: //Sınıfımızın özel parametreleri
  double U;

}fc; //Sınıf tanımı.

void fun::generate() //Fonksiyonumuz.
{
  outfile.open("output.dat", ios::out | ios::trunc );
  for (int i=0;i<n;i++)
    {
      x[i] = i*0.001+0.0005;
      f[i] = exp(-x[i]);
      F[i] = 1-exp(-x[i]);
      U = (double)rand( ) / RAND_MAX;
      X[i] = -log(1-U);
      outfile <<x[i]<<" "<<f[i]<<" "<<F[i]<<" "<<X[i]<< endl;
    }
  outfile.close();
}

int main() //Ana program.
{
  fc.generate(); // fc sınıfındaki generate fonksiyonunu "." operatörü ile çalıştırıyoruz.
}

Editörünüzü kullanarak "program_ismi.cpp" isimli bir dosya içine bu kodları kopyalayıp terminal üzerinden dosyanızın bulunduğu konumda, ("cd /dosya_konumum/" ile konumunuza gidebilirsiniz) "g++ program_ismi.cpp" komutu ile dosyanızı derleyebilir ve oluşacak 'a.out' isimli çalıştırılabilir dosyayı "./a.out" komutu ile çalıştırarak output.dat dosyası içinde değerlerinizi elde edebilirsiniz.

Peki programımız nasıl çalışıyor? 

#include <> komutu dışarıdan kütüphane çağırmamızı sağlıyor ihtiyacımız olan bazı kütüphaneleri yukarıda ekledim. Ayrıca bu komutu kullanarak kendi yardımcı dosyalarımızıda çağırabiliriz. const int n=10000; n isminde sabit bir integer tanımlamamı sağlıyor ve buna 10000 değerini atıyorum. Daha sonra class fun {}fc; komutu ile  sınıfımızı oluşturarak, "{}" arasında public: (paylaşıma açık) ve private: (özel) değişkenlerimizi tanımlıyoruz. Burada:

  • float ile real sayılardan oluşan x,f,F ve X sayı dizilerini. (Bu sayı dizilerine [n] ile 10000 tane değer alabileceklerini söylüyoruz. Sayı dizileri için atadığınız maksimum değer sabit ve tamsayı olmalıdır.) 
  • ofstream outfile; ile dışarı aktaracağımız dosyayı
  • void generate(); ile fonksiyonumuzu
  • özel olarak ise U değişkenini tanımlıyoruz.

Burada kullanacağınız verinin özelliğine göre veri tiplerini seçebilirsiniz, örneğin tamsayı için int ve ya long, reel sayı için float ya da double gibi. Daha fazlasına buradan bakabilirsiniz.

void fun::generate(){} ile fun sınıfında bulunan bir fonksiyon oluşturuyoruz. Fonksiyonun içinde üreteceğim sayıları öncelikle dışarı aktarmak için outfile.open("dosya_ismi.dat", ios::out | ios::trunc ); komutu ile dosyayı oluşturuyorum. Burada ios::out dosyanın dışa aktarılacağını, ios::trunc ise dosyanın oluşturulacağını söylüyor. Daha sonra for (int i=0;i<n;i++){} döngüsü oluşturuyorum. (c++ için döngüler). Burada for döngüsü bir i tamsayılı değişkeni için 0 değerinden başlayarak, her bir döngüde birer birer artarak (i++ komutu) i sayısı n sayısından küçük kaldığı süre boyunca işlemleri tekrar ediyor. x[i]=i*0.001+0.0005; işlemi x sıralı dizisine, i'inci değeri için 0.0005 ten başlamak üzere 0.001 adım aralığı ile değerler atamamızı sağlıyor ve bu x sayılarını f eksponansiyel fonksiyonu ve onun F dağılım fonksiyonunda yerine koyuyoruz. Yani x'e karşılık gelen y noktalarını oluşturuyoruzki bu noktaları grafik üzerinde çizdirip dağılımlarımızı görebilelim. U = (double)rand( ) / RAND_MAX; ise bize 0 ve 1 aralığında rastgele sayı üreterek U değişkenine atamamızı sağlıyor. Bu U rastgele sayılarınıda F(x) fonksiyonunun tersi için kullanarak X rastgele sayılarından oluşan sayı dizimizi elde ediyoruz. outfile <<x[i]<<" "<<f[i]<<" "<<F[i]<<" "<<X[i]<< endl; komutu ile de döngümüzün içinde bu sayı dizilerini dışardaki dosyamıza yazdırıyoruz. Döngümüz bittikten sonra outfile.close(); ile dosyamızı kapatıyoruz.

int main(){} Ana programında ise bu fonksiyonumuzu fc.generate(); şeklinde çağırıp çalıştırıyoruz.

ROOT ile Dışarıdan Dosya Okuma: Bir önceki yazımda grafik çizdirme ve histogram oluşturma gibi konulara değinmiştim. Bu yazımda bir kaynak kod yazarak yukarıda oluşturduğumuz output dosyasını okutup bu bilgileri grafik ve histograma dönüştüreceğiz. Root için kaynak kod yazarken kodlarınızı void isim(){} içeriğine yazmalısınız ve bu void'in ismi dosya isminizle aynı olmalı. Örneğin 'draw.C' dosyası oluşturalım. terminal üzerinden 'emacs draw.C' komutu ile kolayca bunu yapabilirsiniz. Daha sonra içeriğini aşağıda verdiğim kod ile doldurabilirsiniz.
  
void draw()
{
  const int n = 10000;
  int count = 0;
  
  float x[n],f[n],F[n],X[n];
  
  FILE *fr = fopen("output.dat","r"); //"r"(readable, okunabilir) ayarı ile dosya oluşturma.
  while (count<n)
    {
      fscanf(fr,"%f %f %f %f\n",&x[count],&f[count],&F[count],&X[count]); //dosyanın içeriğini okuma
      count++;
    }
  fclose(fr);

  TGraph *gf = new TGraph(n,x,f);
  gf->SetTitle("Function of e^{-x} and Cumulative Distribition");
  gf->SetLineColor(1); gf->SetLineWidth(3);
  
  TGraph *gF = new TGraph(n,x,F);
  gF->SetLineColor(9); gF->SetLineWidth(3);
  
  TH1F *h = new TH1F("h","",100,0,10);
  for (int i=0;i<n;i++)
    h->Fill(X[i]);

  TCanvas *c1 = new TCanvas("c1");
  gf->Draw();
  gF->Draw("same");

  TCanvas *c2 = new TCanvas("c2");
  h->Draw();
}

Burada FILE *fr = fopen("output.dat","r"); output.dat isimli dosyanızı tanımlayarak okunabilir olarak açmasını sağlıyor. Daha sonra başka bir örnek olması amacı ile oluştuduğum while döngüsü içerisinde kaydettiğimiz düzen ile sayı dizilerimizi fscanf() komutu ile dolduruyoruz. fclose(fr) komutu ile de açtığımız dosyayı kapatıyoruz. Bu dosyamızda bi önceki yazımdan bu yana farklı olan ilk kısım buydu ve bununla ilgili internette bir çok örnek bulabilirsiniz. Farklı olan diğer kısım ise TCanvas kavramı. TCanvas sizin grafiksel ortamlarınızı çizdirebileceğiniz, yazılar yazabileceğiniz, tablolar oluşturabileceğiniz bir arayüz, bir pencere açar. En basit kullanımı yukarıdaki gibidir. Bu örnek için c1 ve c2 isimli iki tane canvas oluşturarak ilk canvas üzerine fonksiyonları çizdiriyorum, ikinci canvas üzerine ise random sayılarımızı ve sonuç olarak çıktılarınız da aşağıdaki gibi olmalıdır:

Eksponansiyel fonksiyon ve onun dağılım fonksiyonu.

Random olarak ürettiğimiz sayıların histogramı.
Gördüğünüz gibi random olarak ürettiğimiz sayılar ile bir histogram oluşturduğumuz zaman eksponansiyel bir davranış gösteren grafik şeklinde karşımıza çıkıyor. Burda bir nefes alıp matematiğin güzelliğinden etkilenebilirsiniz. Yeterince fizik ile haşır neşir olduysanız, sadece bu örnek bile matematiğin evreni anlamakta ne kadar güçlü bir mekanizma olduğunu size göstericektir.

Konumuza dönecek olursak, önce C/C++ ile bir program tasarlayıp random sayılar ürettik (burada root ile ilgili hiç bir şey yapmadık.) Daha sonra dışarı kaydettiğimiz bu sayıları okutarak grafiksel ortamda çizdirdik (bu kısımda ise root için bir kaynak kod hazırladık.). Bunu yaptırmamın amacı c++ kodları ile root arasındaki farkı görebilmeniz. Şimdi bu iki kodu birleştirerek root için bir tane kaynak kodu hazırlayalım:

Öncelike function.h isimli header dosyası oluşturarak, fonksiyonlarımızı bu kısımda oluşturalım:

  
const int n = 10000;
float x[n],f[n],F[n],X[n];

class fun
{

 public: 
  void generate();

 private:
  double U;

}fc;

void fun::generate()
{
  for (int i=0;i<n;i++)
    {
      x[i] = i*0.001+0.0005;
      f[i] = exp(-x[i]);
      F[i] = 1-exp(-x[i]);
      U = (double)rand( ) / RAND_MAX;
      X[i] = -log(1-U);
    }
}

Burada artık dışarı yazdırma kısmını kodlarımızdan çıkardık ve sayı dizilerini fonksiyonun dışında tanımladık çünkü bu dizileri fonksiyonun içinden çağırmak yerine tüm program boyunca tanımlayarak işimi biraz kolaylaştırmak istedim. Şimdide random_generator_of_exponantiol_func.C isimli bir dosya oluşturarak içerisine aşağıdaki kodları yazalım. Burada dikkat etmeniz gereken şey header dosyanız ile kaynak kodunuz aynı klasörün içinde bulunmasıdır.

#include <fstream>
#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <math.h>

#include "function.h"

using namespace std;

void random_generator_of_exponantiol_func()
{

  fc.generate();

  TGraph *gf = new TGraph(n,x,f);
  gf->SetTitle("Function of e^{-x} and Cumulative Distribition");
  gf->SetLineColor(1); gf->SetLineWidth(3);
  
  TGraph *gF = new TGraph(n,x,F);
  gF->SetLineColor(9); gF->SetLineWidth(3);
  
  TH1F *h = new TH1F("h","",100,0,10);
  for (int i=0;i<n;i++)
    h->Fill(X[i]);

  TCanvas *c1,*c2;
  c1 = new TCanvas("c1");
  gf->Draw();
  gF->Draw("same");

  c2 = new TCanvas("c2");
  h->Draw();

}

Bu kısımda #include "function.h" kodu ile fonksiyonumuzu kaynak kodunun içerisine ekledik. Dışarı bir dosya yazdırmadan sayılarımızı fonksiyonun içerisinde elde ettiğimiz için artık dosya okutmamıza da gerek kalmadı ve sayılarımızı fc.generate(); komutu ile fonksiyonumuzu çalıştırarak elde etmiş olduk. Bu dosyayı
'root -l random_generator_of_exponantiol_func' komutu ile çalıştırdıktan sonra yukarıda elde ettiğiniz çıktıların aynısını elde edeceksiniz. Ancak histogramınız tam anlamıyla aynı olmayacaktır çünkü random şekilde ürettiğiniz sayılar farklı olacaktır. Davranış ve görünüş temel olarak aynı olacaktır ama histogramdaki her bir bin'e baktığınızda çok küçük farklılıklar göreceksiniz. Bir önceki yazımda anlattığım NTuple ortamını kullanarak bu grafikleri ayrı bir root dosyasına kaydedebilirsiniz, programı ikinci kez çalıştırdığınızda root dosya isminizi değiştirerek iki farklı dosya elde edebilir ve bu iki farklı dosyadaki grafikleri karşılaştırabilirsiniz.

Şimdilik hoşçakalın, bir sonraki yazımda görüşmek üzere...

Faydalı Linkler:

https://en.wikipedia.org/wiki/Inverse_transform_sampling
http://www.math.vt.edu/people/qlfang/class_home/Lesson2021.pdf
http://www.tutorialspoint.com/cplusplus/index.htm
https://root.cern.ch/root/html/tutorials/

Hiç yorum yok :

Yorum Gönder