/*****************************************************************************

    TRAVIS - Trajectory Analyzer and Visualizer

    http://www.travis-analyzer.de/

    Copyright (c) 2009-2026 Martin Brehm
                  2012-2017 Martin Thomas
                  2016-2020 Sascha Gehrke

    Please cite:  J. Chem. Phys. 2020, 152 (16), 164105.         (DOI 10.1063/5.0005078 )
                  J. Chem. Inf. Model. 2011, 51 (8), 2007-2023.  (DOI 10.1021/ci200217w )

    This file was written by Martin Brehm.

    ---------------------------------------------------------------------------

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*****************************************************************************/


// This must always be the first include directive
#include "config.h"

#include "microhet2.h"
#include "globalvar.h"
#include "maintools.h"
#include "conversion.h"



const char *GetRevisionInfo_microhet2(unsigned int len) {
	static char buf[256];
	GET_REVISION_INFO( buf, len );
	return buf;
}


const char *GetSourceVersion_microhet2() {
	static char buf[256];
	GET_SOURCE_VERSION( buf );
	return buf;
}



void CMicroHet2Observation::Parse() {

	int z, z2, z3, z4;
	CMolecule *m;
	CSingleMolecule *sm;
	CAtomGroup ag;
	CxString buf;
	std::vector<int> ml;
	double mass, radius;
	bool custom;


	mprintf("\n");
	mprintf(YELLOW,"    >>> Observation %d >>>\n",m_iID+1);

	m_iaAtoms.clear();
	m_faAtomMasses.clear();
	m_faAtomRadii.clear();

	for (z=0;z<g_oaMolecules.GetSize();z++) {

		mprintf("\n");

		m = (CMolecule*)g_oaMolecules[z];

		if (!AskYesNo("        Use atoms from molecule %d (%s) for this observation (y/n)? [yes] ",true,z+1,m->m_sName))
			continue;

		if (m->m_iAtomGesNoVirt == 1) {
			mprintf("          %s is only one atom, there is no choice.\n",m->m_sName);
			ag.Reset();
			ag.m_pMolecule = m;
			ag.AddAtom(0,0,false);
			ag.SortAtoms();
			ag.BuildName();
		} else {
_as:
			ag.Reset();
			AskString("          Which atom(s) to take from %s (e.g. \"C1,C3-5,H\", \"*\"=all)? [all] ", &buf, "*", m->m_sName );
			if (!ag.ParseAtoms(m,buf))
				goto _as;
		}

		m_sName += "_";
		m_sName += m->m_sName;
		if (m->m_iAtomGesNoVirt > 1) {
			m_sName += "_";
			m_sName += ag.m_sName;
		}

_ms:
		ml.clear();

		if (m->m_laSingleMolIndex.GetSize() > 1) {

			AskString("          Which molecules of %s to use (1-%d, e.g. \"1,3-7\")? [all] ",&buf,"",m->m_sName,m->m_laSingleMolIndex.GetSize());

			if (buf.GetLength() == 0) {
				for (z2=1;z2<=m->m_laSingleMolIndex.GetSize();z2++)
					ml.push_back(z2);
			} else if (!ParseIntList((const char*)buf,ml)) {
				eprintf("Invalid input.\n");
				goto _ms;
			}

			for (z2=0;z2<(int)ml.size();z2++) {
				if ((ml[z2] < 1) || (ml[z2] > m->m_laSingleMolIndex.GetSize())) {
					eprintf("Error: Molecule index %d out of range (allowed: 1 ... %d).\n",ml[z2],m->m_laSingleMolIndex.GetSize());
					goto _ms;
				}
				ml[z2]--;
			}

		} else
			ml.push_back(0);

		if (m_bMassDensity)
			custom = AskYesNo( "          Use custom atom masses / radii (y/n)? [no] ",false);
		else
			custom = AskYesNo( "          Use custom atom radii (y/n)? [no] ",false);


		for (z2=0;z2<ag.m_oaAtoms.GetSize();z2++) {

			for (z3=0;z3<((CxIntArray*)ag.m_oaAtoms[z2])->GetSize();z3++) {

				if (ag.m_baRealAtomType[z2] == g_iVirtAtomType) {
					radius = AskFloat( "          Enter radius for atom %s%d (in pm): [%.2f] ",
						300.0,
						(const char*)((CAtom*)g_oaAtoms[ag.m_baRealAtomType[z2]])->m_sName,
						((CxIntArray*)ag.m_oaAtoms[z2])->GetAt(z3)+1,
						300.0
					);
					if (m_bMassDensity)
						mass = AskFloat( "          Enter mass for atom %s%d (in a.m.u.): [%.4f] ",
							m->m_fMass,
							(const char*)((CAtom*)g_oaAtoms[ag.m_baRealAtomType[z2]])->m_sName,
							((CxIntArray*)ag.m_oaAtoms[z2])->GetAt(z3)+1,
							m->m_fMass
						);
				} else if (custom) {
					radius = AskFloat( "          Enter radius for atom %s%d (in pm): [%.2f] ",
						((CAtom*)g_oaAtoms[ag.m_baRealAtomType[z2]])->m_pElement->m_fVdWRadius,
						(const char*)((CAtom*)g_oaAtoms[ag.m_baRealAtomType[z2]])->m_sName,
						((CxIntArray*)ag.m_oaAtoms[z2])->GetAt(z3)+1,
						((CAtom*)g_oaAtoms[ag.m_baRealAtomType[z2]])->m_pElement->m_fVdWRadius
					);
					if (m_bMassDensity)
						mass = AskFloat( "          Enter mass for atom %s%d (in a.m.u.): [%.4f] ",
							((CAtom*)g_oaAtoms[ag.m_baRealAtomType[z2]])->m_pElement->m_fMass,
							(const char*)((CAtom*)g_oaAtoms[ag.m_baRealAtomType[z2]])->m_sName,
							((CxIntArray*)ag.m_oaAtoms[z2])->GetAt(z3)+1,
							((CAtom*)g_oaAtoms[ag.m_baRealAtomType[z2]])->m_pElement->m_fMass
						);
				} else {
					radius = ((CAtom*)g_oaAtoms[ag.m_baRealAtomType[z2]])->m_pElement->m_fVdWRadius;
					mass = ((CAtom*)g_oaAtoms[ag.m_baRealAtomType[z2]])->m_pElement->m_fMass;
				}

				for (z4=0;z4<(int)ml.size();z4++) {

					sm = (CSingleMolecule*)g_oaSingleMolecules[m->m_laSingleMolIndex[ml[z4]]];

					m_iaAtoms.push_back( ((CxIntArray*)sm->m_oaAtomOffset[ag.m_baAtomType[z2]])->GetAt(((CxIntArray*)ag.m_oaAtoms[z2])->GetAt(z3)) );
					if (m_bMassDensity)
						m_faAtomMasses.push_back( mass );
					else
						m_faAtomMasses.push_back( 1.0 );
					m_faAtomRadii.push_back( radius );
				}
			}
		}
	}

	mprintf("\n        This observation contains %lu atoms.\n\n",(unsigned long)m_iaAtoms.size());
	if (m_iaAtoms.size() == 0) {
		eprintf("Error: Empty observation is not allowed.\n");
		abort();
	}

	m_fUniformDensity = 0;
	for (z=0;z<(int)m_iaAtoms.size();z++)
		m_fUniformDensity += m_faAtomMasses[z];

	if (m_bMassDensity)
		mprintf("        Total mass:       %10.4f a.m.u.\n",m_fUniformDensity);

	m_fUniformDensity /= 6.02214076E-7 * g_fBoxX * g_fBoxY * g_fBoxZ;

	if (m_bMassDensity)
		mprintf("        Uniform density:  %10.4f g cm^-3\n",m_fUniformDensity);

	mprintf("\n");

	mprintf("        Observation name: %s\n",m_sName.c_str());

	mprintf("\n");
	mprintf(YELLOW,"    <<< Observation %d <<<\n\n",m_iID+1);
}



void CMicroHet2::Parse() {

	int z, z2;
	double tf;
	CMicroHet2Observation *obs;


	mprintf("\n");
	mprintf("\n");
	mprintf(WHITE,"    ########################################################\n");
	mprintf(WHITE,"    ####                                                ####\n");
	mprintf(WHITE,"    ####    Microheterogeneity Analysis - Revision 2    ####\n");
	mprintf(WHITE,"    ####         (c) Martin Brehm, 2023 - 2026          ####\n");
	mprintf(WHITE,"    ####           https://brehm-research.de            ####\n");
	mprintf(WHITE,"    ####                                                ####\n");
	mprintf(WHITE,"    ########################################################\n");
	mprintf("\n");
	mprintf("\n");


	m_fRadiusMin = AskFloat("    Min Radius (in pm): [100.0] ",100.0);
	tf = (double)HalfBox();
	if (fmod(tf,100.0) != 0)
		tf -= fmod(tf,100.0);
	m_fRadiusMax = AskFloat("    Max Radius (in pm): [%.1f] ",tf,tf);
	z = (int)floor((m_fRadiusMax-m_fRadiusMin)/20.0+0.5)+1;
	m_iRadiusCount = AskUnsignedInteger("    Radius count: [%d] ",z,z);

	mprintf("\n");

	m_iSampling = AskUnsignedInteger("    Sampling points per direction: [100] ",100);

	mprintf("\n");

	m_bMassDensity = AskYesNo("    Use mass density (y) or particle density (n)? [yes] ",true);

	if (!m_bMassDensity)
		mprintf("\n    Setting all masses to 1.0.\n");

	while (true) {

		obs = new CMicroHet2Observation();

		obs->m_iID = (int)m_oaObservations.size();
		obs->m_bMassDensity = m_bMassDensity;

		m_oaObservations.push_back( obs );

		obs->Parse();

		obs->m_pCDF = new C2DF();

		obs->m_pCDF->m_iRes[0] = m_iRadiusCount;
		obs->m_pCDF->m_iRes[1] = 400;
		obs->m_pCDF->m_fMinVal[0] = m_fRadiusMin;
		obs->m_pCDF->m_fMaxVal[0] = m_fRadiusMax;
		obs->m_pCDF->m_fMinVal[1] = 0;
		obs->m_pCDF->m_fMaxVal[1] = 10.0;
		obs->m_pCDF->Create();
		obs->m_pCDF->m_bPixel = true;
		obs->m_pCDF->m_bContourLines = false;
		obs->m_pCDF->m_fPlotExp = 0.25;
		obs->m_pCDF->m_bDrawMesh = false;

		obs->m_pCDFCount = new C2DF();

		obs->m_pCDFCount->m_iRes[0] = m_iRadiusCount;
		obs->m_pCDFCount->m_iRes[1] = (int)obs->m_iaAtoms.size()+1;
		obs->m_pCDFCount->m_fMinVal[0] = m_fRadiusMin;
		obs->m_pCDFCount->m_fMaxVal[0] = m_fRadiusMax;
		obs->m_pCDFCount->m_fMinVal[1] = 0;
		obs->m_pCDFCount->m_fMaxVal[1] = (double)(obs->m_iaAtoms.size()+1);
		obs->m_pCDFCount->Create();
		obs->m_pCDFCount->m_bPixel = true;
		obs->m_pCDFCount->m_bContourLines = false;
		obs->m_pCDFCount->m_fPlotExp = 0.25;
		obs->m_pCDFCount->m_bDrawMesh = false;

		obs->m_faDFEmpty.resize(m_iRadiusCount);
		for (z=0;z<m_iRadiusCount;z++)
			obs->m_faDFEmpty[z] = 0;

		obs->m_fDFEmptyCounter = 0;

		if (!AskYesNo("    Add another observation (y/n)? [no] ",false))
			break;
	}

	mprintf("\n");

	mprintf("    Added %lu observations.\n\n",m_oaObservations.size());

	mprintf("    Using the following atoms, masses, and radii:\n" );

	for (z=0;z<(int)m_oaObservations.size();z++) {
		mprintf(WHITE,"\n    (*) Observation %d:\n",z+1);
		obs = m_oaObservations[z];
		for (z2=0;z2<(int)obs->m_iaAtoms.size();z2++) {
			if (g_laAtomSMLocalIndex[ obs->m_iaAtoms[z2] ] != 0)
				continue;
			mprintf("%20s  %2s%-3d   m=%9.4f   r=%6.1f\n",
				(const char*)((CMolecule*)g_oaMolecules[g_waAtomMolIndex[ obs->m_iaAtoms[z2] ]])->m_sName,
				(const char*)((CAtom*)g_oaAtoms[ g_waAtomRealElement[ obs->m_iaAtoms[z2] ] ])->m_sName,
				g_waAtomMolNumber[ obs->m_iaAtoms[z2] ]+1,
				obs->m_faAtomMasses[z2],
				obs->m_faAtomRadii[z2]
			);
		}
	}
	mprintf("\n");


	if (m_oaObservations.size() >= 2) {

		for (z=0;z<m_iRadiusCount;z++) {

			m_paCrossCDF.push_back( new C2DF() );

			m_paCrossCDF.back()->m_iRes[0] = 200;
			m_paCrossCDF.back()->m_iRes[1] = 200;
			m_paCrossCDF.back()->m_fMinVal[0] = 0;
			m_paCrossCDF.back()->m_fMaxVal[0] = 4.0;
			m_paCrossCDF.back()->m_fMinVal[1] = 0;
			m_paCrossCDF.back()->m_fMaxVal[1] = 4.0;
			m_paCrossCDF.back()->Create();
			m_paCrossCDF.back()->m_bPixel = true;
			m_paCrossCDF.back()->m_bContourLines = false;
			m_paCrossCDF.back()->m_fPlotExp = 0.5;
			m_paCrossCDF.back()->m_bDrawMesh = false;
		}
	}

	mprintf("\n");

	#ifdef MICROHET_PARALLEL
		m_iThreads = AskUnsignedInteger( "    How many threads to use in parallel? [4] ", 4 );
	#else
		mprintf( "    Parallel execution not available on this platform.\n" );
	#endif

	mprintf("\n");
	mprintf("\n");
	mprintf(WHITE,"    ######################################################\n");
	mprintf(WHITE,"    ####    Microheterogeneity Analysis Input Done    ####\n");
	mprintf(WHITE,"    ######################################################\n");
	mprintf("\n");
}



void CMicroHet2::Create() {

	CMicroHet2Thread *thr;
	int z2;


	#ifdef MICROHET_PARALLEL

		int z;

		mprintf("      Creating %d threads...\n",m_iThreads);

		for (z=0;z<m_iThreads;z++) {
			thr = new CMicroHet2Thread();
			thr->m_iID = z;
			thr->m_faaTemp.resize( m_oaObservations.size() );
			thr->m_faaTempCount.resize( m_oaObservations.size() );
			for (z2=0;z2<(int)m_oaObservations.size();z2++) {
				thr->m_faaTemp[z2].resize( m_iRadiusCount );
				thr->m_faaTempCount[z2].resize( m_iRadiusCount );
			}
			m_oaThreads.push_back( thr );
			thr->m_pThread = new std::thread( &CMicroHet2::ThreadMain, this, thr );
		}

		std::this_thread::sleep_for( std::chrono::milliseconds(100) );

	#else

		thr = new CMicroHet2Thread();
		thr->m_iID = 0;
		thr->m_faaTemp.resize( m_oaObservations.size() );
		thr->m_faaTempCount.resize( m_oaObservations.size() );
		for (z2=0;z2<(int)m_oaObservations.size();z2++) {
			thr->m_faaTemp[z2].resize( m_iRadiusCount );
			thr->m_faaTempCount[z2].resize( m_iRadiusCount );
		}
		m_oaThreads.push_back( thr );

	#endif

	mprintf("      Done.\n");
}



#ifdef MICROHET_PARALLEL


	bool CMicroHet2Thread::DequeqeTimeStep( const CTimeStep **ts ) {

		std::unique_lock<std::mutex> lock(m_oThreadMutex);

		while (m_qTimeSteps.empty() /*&& !m_bThreadsFinished*/)
			// release lock as long as the wait and reacquire it afterwards.
			m_oCondition.wait(lock);

		if (m_qTimeSteps.empty() /*&& m_bThreadsFinished*/)
			return false;

		*ts = m_qTimeSteps.front();
		m_qTimeSteps.pop();

		return true;
	}



	void CMicroHet2::ThreadMain( CMicroHet2Thread *thr ) {

		const CTimeStep *ts = NULL;

		#ifdef MICROHET_PARALLEL
			std::unique_lock<std::mutex> lock( thr->m_oThreadMutex, std::defer_lock );
		#endif


		mprintf("        Microheterogeneity thread %3d is online and waiting.\n",thr->m_iID+1);

		while (!m_bThreadsFinished) {

			#ifdef MICROHET_PARALLEL
				lock.lock();
			#endif

			thr->m_bWaiting = true;
			thr->m_oCondition.notify_all();

			#ifdef MICROHET_PARALLEL
				lock.unlock();
			#endif

			if (!thr->DequeqeTimeStep( &ts ))
				break;

			#ifdef MICROHET_PARALLEL
				lock.lock();
			#endif

			thr->m_bWaiting = false;

			#ifdef MICROHET_PARALLEL
				lock.unlock();
			#endif

			ThreadWork( thr, ts );
		}

		#ifdef MICROHET_PARALLEL
			lock.lock();
		#endif

		thr->m_bWaiting = false;

		#ifdef MICROHET_PARALLEL
			lock.unlock();
		#endif

		mprintf("        Microheterogeneity thread %3d is leaving.\n",thr->m_iID+1);
	}



#endif



void CMicroHet2::ThreadWork( CMicroHet2Thread *thr, const CTimeStep *ts ) {

	int ix, iy, iz, z, z2, z3;
	CMicroHet2Observation *o;
	double px, py, pz, rfac;
	CxDVector3 vec;


	#ifdef MICROHET_PARALLEL
		std::unique_lock<std::mutex> lock( m_oGlobalMutex, std::defer_lock );
	#endif

	rfac = (m_fRadiusMax - m_fRadiusMin) / (m_iRadiusCount-1);

	if (thr->m_iID == 0)
		mprintf("[");

	for (iz=0;iz<m_iSampling;iz++) {

		pz = ((double)iz)/m_iSampling * g_fBoxZ;

		for (iy=0;iy<m_iSampling;iy++) {

			#ifdef MICROHET_PARALLEL
				if (m_iThreads > 1)
					if (((iz*m_iSampling+iy) % m_iThreads) != thr->m_iID)
						continue;
			#endif

			py = ((double)iy)/m_iSampling * g_fBoxY;
	
			for (ix=0;ix<m_iSampling;ix++) {

				px = ((double)ix)/m_iSampling * g_fBoxX;

				vec[0] = px;
				vec[1] = py;
				vec[2] = pz;

				for (z=0;z<(int)m_oaObservations.size();z++) {

					o = m_oaObservations[z];

					for (z2=0;z2<m_iRadiusCount;z2++) {
						thr->m_faaTemp[z][z2] = 0;
						thr->m_faaTempCount[z][z2] = 0;
					}

					for (z2=0;z2<(int)o->m_iaAtoms.size();z2++) {

						int i1;

						const double arad = o->m_faAtomRadii[ z2 ];

						const double dist = FoldedLength( ts->m_vaCoords[ o->m_iaAtoms[z2] ] - vec );

						const double mass = o->m_faAtomMasses[ z2 ];

						if (dist >= arad) {

							i1 = imax( (int)ceil( (dist - arad - m_fRadiusMin) / rfac ), 0 );

							if (i1 >= m_iRadiusCount)
								continue;

							// 0 .. (i1-1): disjunct

						} else { // dist < arad

							i1 = imin( imax( (int)ceil( (arad - dist - m_fRadiusMin) / rfac ), 0 ), m_iRadiusCount );

							// 0 .. (i1-1): completely reverse contained (sphere inside atom)
							for (z3=0;z3<i1;z3++) {
								const double sprad = m_fRadiusMin + rfac * (double)z3;
								double tf = pow3( sprad/arad );
							//	if (tf > 1.0)
							//		mprintf("$$ %f\n",tf);
								if (tf < 0)
									tf = 0;
								if (tf > 1.0)
									tf = 1.0;
								thr->m_faaTemp[z][z3] += mass * tf;
								thr->m_faaTempCount[z][z3] += tf;
							}
						}

						const int i2 = imin( imax( (int)ceil( (arad + dist - m_fRadiusMin) / rfac ), 0 ), m_iRadiusCount );

						// i1 .. (i2-1): partial
						for (z3=i1;z3<i2;z3++) {
							const double sprad = m_fRadiusMin + rfac * (double)z3;
							double tf = 1.0 / (16.0 * dist * pow3(arad))
								* pow2( arad + sprad - dist ) * (pow2(dist) + 2.0*dist*(arad+sprad)
									- 3.0 * pow2( arad - sprad ) );
/*							if ((tf < 0) || (tf > 1.0)) {
								mprintf(CYAN,"##################\n");
								mprintf(CYAN,"arad = %f\n",arad);
								mprintf(CYAN,"sprad = %f\n",sprad);
								mprintf(CYAN,"dist = %f\n",dist);
								mprintf(CYAN,"tf = %f\n",tf);
							}*/
							if (tf < 0)
								tf = 0;
							if (tf > 1.0)
								tf = 1.0;
							thr->m_faaTemp[z][z3] += mass * tf;
							thr->m_faaTempCount[z][z3] += tf;
						}

						// i2 .. m_iRadiusCount: completely contained
						for (;z3<m_iRadiusCount;z3++) {
							thr->m_faaTemp[z][z3] += mass;
							thr->m_faaTempCount[z][z3]++;
						}

					//	mprintf("#### arad=%6.2f, dist=%6.2f\n",arad,dist);
					//	for (z3=0;z3<m_iRadiusCount;z3++)
					//		mprintf("sprad=%6.2f, tf=%8.6f\n",m_fRadiusMin + rfac * (double)z3,thr->m_faaTempCount[z][z3]);



				/*		mass = m_faAtomMasses[ o->m_iaAtoms[z2] ];

						for (z3=0;z3<m_iRadiusCount;z3++) {

							//sprad = m_fRadiusMin + (m_fRadiusMax - m_fRadiusMin) * (double)z3 / (m_iRadiusCount-1);
							sprad = m_fRadiusMin + rfac * (double)z3;

							// Spheres are disjunct
							if (dist > sprad + arad)
								continue;

							tf2 = mass;

							// Spheres partially overlap
							if (dist > sprad - arad)
								tf2 *= 1.0 / (16.0 * dist * pow3(arad))
									* pow2( arad + sprad - dist ) * (pow2(dist) + 2.0*dist*(arad+sprad)
									  - 3.0 * pow2( arad - sprad ) );

							thr->m_faaTemp[z][z3] += tf2;
						}*/

					} // END for z2

					for (z3=0;z3<m_iRadiusCount;z3++) {
						thr->m_faaTemp[z][z3] *= CONST_ATOMIC_MASS_UNIT * 1.0e33 / ( 4.0/3.0 * Pi * pow3(m_fRadiusMin + rfac * (double)z3) );
						thr->m_faaTemp[z][z3] /= o->m_fUniformDensity;
					}

					#ifdef MICROHET_PARALLEL
						lock.lock();
					#endif

					for (z3=0;z3<m_iRadiusCount;z3++) {
						o->m_pCDF->AddToBin_IntX_fast( z3, thr->m_faaTemp[z][z3] );
						//o->m_pCDFCount->AddToBin_IntX_fast( z3, thr->m_faaTempCount[z][z3] );
						if ((int)floor(thr->m_faaTempCount[z][z3]+0.5) >= o->m_pCDFCount->m_iRes[1]) {
							eprintf("CMicroHet2::ThreadWork(): Error: %d >= %d (z=%d, z3=%d)\n",
								(int)floor(thr->m_faaTempCount[z][z3]+0.5), o->m_pCDFCount->m_iRes[1],z,z3);
							abort();
						}
						o->m_pCDFCount->AddToBin_IntXY_fast( z3, (int)floor(thr->m_faaTempCount[z][z3]+0.5) );
						if (thr->m_faaTempCount[z][z3] == 0)
							o->m_faDFEmpty[z3]++;
					}

					o->m_fDFEmptyCounter++;

					#ifdef MICROHET_PARALLEL
						lock.unlock();
					#endif

				} // END for observation

				#ifdef MICROHET_PARALLEL
					lock.lock();
				#endif

				if (m_oaObservations.size() >= 2) {
					for (z3=0;z3<m_iRadiusCount;z3++)
						m_paCrossCDF[z3]->AddToBin(
							thr->m_faaTemp[0][z3],
							thr->m_faaTemp[1][z3]
						);
				}

				#ifdef MICROHET_PARALLEL
					lock.unlock();
				#endif

			} // END for ix

		} // END for iy

		if (thr->m_iID == 0)
			mprintf("#");

	} // END for iz

	if (thr->m_iID == 0)
		mprintf("]\n");
}



void CMicroHet2::ProcessStep( CTimeStep *ts ) {

	#ifdef MICROHET_PARALLEL

		int z;

		for (z=0;z<(int)m_oaThreads.size();z++) {
			m_oaThreads[z]->m_oLock.lock();
			m_oaThreads[z]->m_qTimeSteps.push( ts );
			m_oaThreads[z]->m_oCondition.notify_all();
			m_oaThreads[z]->m_oLock.unlock();
		}

		std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );

		for (z=0;z<(int)m_oaThreads.size();z++) {
			m_oaThreads[z]->m_oLock.lock();
			while (!m_oaThreads[z]->m_bWaiting)
				m_oaThreads[z]->m_oCondition.wait( m_oaThreads[z]->m_oLock );
			m_oaThreads[z]->m_oLock.unlock();
		}

	#else

		ThreadWork( m_oaThreads[0], ts );

	#endif
}



static double Normal( double x, double mu, double sig ) {

	return 1.0 / (sig * sqrt( 2.0 * Pi ) ) * exp( -0.5 * pow( (x - mu) / sig, 2 ) );
}



/*
unsigned long binom( unsigned long n, unsigned long k ) {

    return
      (        k> n  )? 0 :          // out of range
      (k==0 || k==n  )? 1 :          // edge
      (k==1 || k==n-1)? n :          // first
      (     k+k < n  )?              // recursive:
      (binom(n-1,k-1) * n)/k :       //  path to k=1   is faster
      (binom(n-1,k) * n)/(n-k);      //  path to k=n-1 is faster
}
*/



void CMicroHet2::Finish() {

	int z, z2, z3;
	CMicroHet2Observation *o;
	char buf[256];
	FILE *a;
	double tp, tn, rad, mu, tf, sig, ixmu, ixsig;
	std::vector<double> tfa, tfa2, tfa3;


	system( "mkdir microhet" );

	#ifdef TARGET_WINDOWS
		system( "mkdir microhet\\slices" );
	#else
		system( "mkdir microhet/slices" );
	#endif

	if (m_oaObservations.size() >= 2) {

		#ifdef TARGET_WINDOWS
			system( "mkdir microhet\\cross" );
		#else
			system( "mkdir microhet/cross" );
		#endif

		for (z=0;z<m_iRadiusCount;z++) {
			sprintf(buf,"microhet/cross/microhet_cross_obs1_obs2_i%03d_rad%.1f",z+1,m_fRadiusMin + (m_fRadiusMax - m_fRadiusMin) / (m_iRadiusCount-1) * (double)z);
			m_paCrossCDF[z]->WriteGnuplotInput( "", buf, "", false );
		}
	}

	for (z=0;z<(int)m_oaObservations.size();z++) {

		o = m_oaObservations[z];

		sprintf(buf,"microhet/microhet_obs%d%s",z+1,o->m_sName.c_str());
		o->m_pCDF->WriteGnuplotInput( "", buf, "", false );
		sprintf(buf,"microhet/microhet_obs%d%s_count",z+1,o->m_sName.c_str());
		o->m_pCDFCount->WriteGnuplotInput( "", buf, "", false );


		sprintf(buf,"microhet/microhet_obs%d%s_empty.csv",z+1,o->m_sName.c_str());
		a = fopen( buf, "wb" );
		fprintf(a,"#Radius; Empty Sphere Fraction/%%\n");
		for (z2=0;z2<m_iRadiusCount;z2++)
			fprintf(a,"%f;  %.10f\n",
				m_fRadiusMin + (m_fRadiusMax - m_fRadiusMin) / (m_iRadiusCount-1) * (double)z2,
				o->m_faDFEmpty[z2]/o->m_fDFEmptyCounter*100.0
			);
		fclose(a);	


		sprintf(buf,"microhet/microhet_obs%d%s_mean_sd.csv",z+1,o->m_sName.c_str());
		a = fopen( buf, "wb" );

		fprintf(a,"#Radius;  Obs.Mu;  Obs.Sigma;  Ideal_Mu;  Ideal_Sigma;  Ratio;  Ideal_Mu_2;  Ideal_Sigma_2;  Ratio_2\n" );

		tfa.resize( o->m_pCDFCount->m_iRes[1] );

		for (z2=0;z2<m_iRadiusCount;z2++) {

			rad = m_fRadiusMin + (m_fRadiusMax - m_fRadiusMin) / (m_iRadiusCount-1) * (double)z2;
			tn = (double)o->m_iaAtoms.size();
			tp = 4.0/3.0 * Pi * pow( rad, 3 ) / g_fBoxX / g_fBoxY / g_fBoxZ;

			mu = 0;
			tf = 0;
			for (z3=0;z3<o->m_pCDFCount->m_iRes[1];z3++) {
				mu += (double)z3 * o->m_pCDFCount->m_pBin[ z3*o->m_pCDFCount->m_iRes[0]+z2 ];
				tf += o->m_pCDFCount->m_pBin[ z3*o->m_pCDFCount->m_iRes[0]+z2 ];
			}
			mu /= tf;

			sig = 0;
			for (z3=0;z3<o->m_pCDFCount->m_iRes[1];z3++)
				sig += o->m_pCDFCount->m_pBin[ z3*o->m_pCDFCount->m_iRes[0]+z2 ] * pow( (double)z3 - mu, 2 );
			sig /= tf;
			sig = sqrt( sig );

			//for (z3=0;z3<o->m_pCDFCount->m_iRes[1];z3++)
			//	tfa[z3] = Normal( (double)z3, tn*tp, sqrt( tn*tp*(1.0-tp) ) );

			for (z3=0;z3<o->m_pCDFCount->m_iRes[1];z3++)
				tfa[z3] = BinomialCoeff( (int)o->m_iaAtoms.size(), z3 ) * pow(tp,z3) * pow(1.0-tp,o->m_iaAtoms.size()-z3);

			ixmu = 0;
			tf = 0;
			for (z3=0;z3<o->m_pCDFCount->m_iRes[1];z3++) {
				ixmu += (double)z3 * tfa[ z3 ];
				tf += tfa[ z3 ];
			}
			ixmu /= tf;

			ixsig = 0;
			for (z3=0;z3<o->m_pCDFCount->m_iRes[1];z3++)
				ixsig += tfa[ z3 ] * pow( (double)z3 - ixmu, 2 );
			ixsig /= tf;
			ixsig = sqrt( ixsig );

			fprintf(a,"%f;  %f;  %f;  %f;  %f;  %f;  %f;  %f;  %f\n",
				rad,
				mu,
				sig,
				tn*tp,
				sqrt( tn*tp*(1.0-tp) ),
				sig / sqrt( tn*tp*(1.0-tp) ),
				ixmu,
				ixsig,
				sig / ixsig
			);
		}

		fclose(a);


		for (z3=0;z3<m_iRadiusCount;z3++) {

			rad = m_fRadiusMin + (m_fRadiusMax - m_fRadiusMin) / (m_iRadiusCount-1) * (double)z3;
		
			tfa.resize( o->m_pCDFCount->m_iRes[1] );
			tfa2.resize( o->m_pCDFCount->m_iRes[1] );
			tfa3.resize( o->m_pCDFCount->m_iRes[1] );

			tn = (double)o->m_iaAtoms.size();
			tp = 4.0/3.0 * Pi * pow( rad, 3 ) / g_fBoxX / g_fBoxY / g_fBoxZ;


			///////////////////////////////////
			tf = 0;
			for (z2=0;z2<o->m_pCDFCount->m_iRes[1];z2++) {
				tfa[z2] = o->m_pCDFCount->m_pBin[ z2*o->m_pCDFCount->m_iRes[0]+z3 ];
				tf += tfa[z2];
			}
			for (z2=0;z2<o->m_pCDFCount->m_iRes[1];z2++)
				tfa[z2] *= 1000.0 / tf;


			///////////////////////////////////
			for (z2=0;z2<o->m_pCDFCount->m_iRes[1];z2++)
				tfa2[z2] = BinomialCoeff( (int)o->m_iaAtoms.size(), z2 ) * pow(tp,z2) * pow(1.0-tp,o->m_iaAtoms.size()-z2);

			tf = 0;
			for (z2=0;z2<o->m_pCDFCount->m_iRes[1];z2++)
				tf += tfa2[z2];
			for (z2=0;z2<o->m_pCDFCount->m_iRes[1];z2++)
				tfa2[z2] *= 1000.0 / tf;


			///////////////////////////////////
			for (z2=0;z2<o->m_pCDFCount->m_iRes[1];z2++)
				tfa3[z2] = Normal( (double)z2, tn*tp, sqrt( tn*tp*(1.0-tp) ) );

			tf = 0;
			for (z2=0;z2<o->m_pCDFCount->m_iRes[1];z2++)
				tf += tfa3[z2];
			for (z2=0;z2<o->m_pCDFCount->m_iRes[1];z2++)
				tfa3[z2] *= 1000.0 / tf;

			sprintf(buf,"microhet/slices/microhet_obs%d%s_slice_i%03d_rad%.1f.csv",z+1,o->m_sName.c_str(),z3,rad);
			a = fopen( buf, "wb" );
			fprintf(a,"#Value;  Obs;  Ideal(Bin);  Ideal(Norm)\n");
			for (z2=0;z2<o->m_pCDFCount->m_iRes[1];z2++)
				fprintf(a,"%f;  %f;  %f;  %f\n",
					o->m_pCDFCount->m_fMinVal[1]+(z2+0.5)*(o->m_pCDFCount->m_fMaxVal[1]-o->m_pCDFCount->m_fMinVal[1])/o->m_pCDFCount->m_iRes[1],
					tfa[z2],
					tfa2[z2],
					tfa3[z2]
				);

			fclose(a);
		}
	}
}








