# coding: utf-8

 ============================================================
|                   Empty_Spheres Modules                    |
 ============================================================

Made by : Ulysse DUPONT
Collaboration with : Didier SEBILLEAU, Sylvain TRICOT
Helped by : Floris Bruynooghe (Delny) and the team from scipy.spatial (qhull/Voronoi)

Path : msspec/src/python/pymsspec/msspec/es
Language : Python
Important Global Variable : ES_AllRadius

                     ============================================================
                    |                     About ES_AllRadius                     |
                     ============================================================

For now : Empty spheres can be created from a cluster of Atoms.
The missing part is the definition of the Empty-Sphere Python Class : they are considered as "X" Atoms.
It means that the radius of empty spheres are not directly linked to them :
The routine empty-spheres.Atom_Radius(Structure,n,list) will recognize empty spheres because their atom number is 0.
So we created ES_AllRadius global variable, wich is updated after each EmptySphere generation with function
"ES_AllRadius_Updater(NewES,Structure)". The new radius are calculated brutally by taking the max, without touching
Structure molecules

To use this global variable, it will be necessary to delete no empty spheres (or be sure to delete the
correspondant radius in ES_AllRadius).

An example of ES_AllRadius use is given afterward : in the example "Covering GeCl4 using ES_AllRadius"

In the future, the informations about each empty-spheres radius can be taken directly in construction routines, and linked
to the empty-sphere python object.


                     ============================================================
                    |                        Objectives                          |
                     ============================================================

The empty_spheres modules are routines made to add empty spheres in clusters for diffusion computations.
The routines used are based on tetrahedralisation, convex-hull research and triangularisation to create adapted mesh,
and uses tangent-spheres resolutions to compute adapted coordinates and radius of empty-spheres


                     ============================================================
                    |                       How to use ?                         |
                     ============================================================

Before adding empty-spheres, you need a cluster, or list of positions.
The argument name "Structure" designs the Atoms type in pymsspec. The coordinates of the set of points is given
by the command : numpy.ndarray.tolist(Structure.positions)

For the moment, empty-sphere type isn't defined, only coordinates are computed.

To see how the routines work : We will show few examples : You just need to adjust the folder where the xyz files will be


__________________Internal/External ConvexHull Cover in C60_____________________________________________________________

=================================Commands :
from ase import Atoms
from ase.io import write,read
from ase.visualize import view
from es_mod import empty_spheres as esph

Structure = read('cluster_examples/C60.xyz')
ExtCover = esph.Convex_Hull_Cover(Structure,radius_es=1,tol=1)
1
Result = Atoms(positions = ExtCover)
view(Structure + Result)
IntCover1 = esph.Internal_Hull_Cover(Structure,radius_es=0.84,tol=0.6)
l=len(IntCover1)
wcolor = "B"+str(l)
Result1 = Atoms(wcolor,positions = IntCover1)
view(Structure + Result1)
IntCover2 = esph.Internal_Hull_Cover(Structure + Result1)

Result2 = Atoms(positions = IntCover2)
view(Result1 + Result2)
view(Structure + Result1 + Result2)
=================================Comments :

The view will allow you to see the added spheres. For the moment, no empty spheres objects have been created,
so we defin special radii, from covalent radii table.

You can change IntCover1, for example taking radius_es = 0.71 and wcolor with "N" letter,
or es_radius = 0.76 and C letter,etc... The letter allow view function to set correct radii.
The tol parameter influences Fusion_Overlap. Spheres are fusionned if the distance beetween centers is less than
the sum of the two spheres raddi, multiplied by tol parameter. So tol=1 is used to completely avoid spheres to touch
themselves. Default value of tol has been subjectively settled at 0.6

ICover2 will ask for radius_es : it is not given in the function call.
If you put radius = 1.3, for example, you will obtain the centroid of the hull after fusionning the spheres.
Try very big or smaller radius to understand how fusion works.
____________________________________________________



__________________Delaunay TetraHedral with a cube face-centered________________________________________________________
=================================Commands :
from ase import Atoms
from ase.io import write,read
from ase.visualize import view
from es_mod import empty_spheres as esph


struct = struct = [[0,-1,0],[0,1,0],[0,0,1],[0,0,-1],[1,0,0],[-1,0,0],[-1,-1,-1],[1,-1,-1],[-1,1,-1],[1,1,-1],[-1,-1,1],[1,-1,1],[-1,1,1],[1,1,1]]
Structure = Atoms("C14",positions=struct)
set1 = esph.Delaunay_Tetrahedral_ES(Structure)
Set1 = Atoms (positions = set1)
view(Set1)
view(Structure + Set1)

set2 = esph.Delaunay_Tetrahedral_ES(Structure+Set1)
Set2 = Atoms (positions = set2)
view(Set2)
view(Structure + Set1 + Set2)
=================================Comments :
First iteration as you can see is given with np minsize and maxsize : This parameters are otionnaly set both at 0 and 999.
The second iteration will show you an actual problem :
The size of empty spheres must be saved to be used once more.
If free space is to small, the routine will obtain some unsolvable problems. This issue is showed in your board with
singular matrix
____________________________________________________




__________________Delaunay TetraHedral with a copper sample_____________________________________________________________
=================================Commands :
from ase import Atoms
from ase.io import write,read
from ase.visualize import view
from es_mod import empty_spheres as esph

Structure = read('cluster_examples/copper.xyz')
set1 = esph.Delaunay_Tetrahedral_ES(Structure)
Set1 = Atoms (positions = set1)
view(Set1)
view(Structure + Set1)

=================================Comments :
First iteration as you can see is given with np minsize and maxsize : This parameters are otionnaly set both at 0 and 999.
If you want to delete the empty-spheres added out of convexhull, you can add the parameter maxsize=2
No second iteration is done : see the centered cube example for explanations.
____________________________________________________




__________________Experiment problem of missing spheres in cover________________________________________________________
=================================Commands :
from ase import Atoms
from ase.io import write,read
from ase.visualize import view
from es_mod import empty_spheres as esph

struct = struct = [[0,-1,0],[0,1,0],[0,0,1],[0,0,-1],[1,0,0],[-1,0,0],[-1,-1,-1],[1,-1,-1],[-1,1,-1],[1,1,-1],[-1,-1,1],[1,-1,1],[-1,1,1],[1,1,1]]
Structure = Atoms("C14",positions=struct)

ExtCover = esph.Convex_Hull_Cover(Structure,radius_es=1,missing=True)
1
Result = Atoms(positions = ExtCover)
view(Structure + Result)
ExtCover = esph.Convex_Hull_Cover(Structure,radius_es=1)
1
Result = Atoms(positions = ExtCover)
view(Structure + Result)
=================================Comments :
As you will see : The Second Result show only 6 empty spheres, and this spheres could maybe overlap the centers of facets.
Classic ConveHull routine returns only boundary points of facets : if points are included in this facets, they miss...
To solve this problem : put the optionnal parameter "missing" at "True". Do it when your cluster as some huge facets with
some atoms included in (exactly like this cube facets, wich are centered).
The first result shows the "good way" : it solves the problem of atoms forgotten by ConvexHull routine.
____________________________________________________



__________________Covering Nanotube_____________________________________________________________________________________
=================================Commands :
from ase import Atoms
from ase.io import write,read
from ase.visualize import view
from es_mod import empty_spheres as esph


Structure = read('cluster_examples/tubes.xyz')
#set = np.ndarray.tolist(Structure.positions)
#hull=ConvexHull(set)
Cover = esph.Convex_Hull_Cover(Structure,radius_es=1,missing=True)

Result = Atoms(positions = Cover)
view(Structure + Result)

=================================Comments :
Computing time here is bigger, as you will see (Operation is done twice, if you need to go faster, you can do internal and
external cover in one same routine, by not using Select_Int_Ext function.
You can test routine with no missing parameter (missing=False by default) : You will feel good the problem of ConvexHull.

____________________________________________________


__________________Covering little NH3___________________________________________________________________________________
=================================Commands :
from ase import Atoms
from ase.io import write,read
from ase.visualize import view
from es_mod import empty_spheres as esph

Structure = read('cluster_examples/NH3.xyz')


FCover = esph.Flat_Covering(Structure)
1
3
Result = Atoms(positions = FCover)
view(Structure + Result)

Cover = esph.Convex_Hull_Cover(Structure)
1
1
Result = Atoms(positions = Cover)
view(Structure + Result)

TCover = esph.Delaunay_Tetrahedral_ES(Structure)
Result = Atoms(positions = TCover)
view(Structure + Result)
=================================Comments :
Using Flat Covening in caseof little molecule can let some problems : the "biggest plane" selected isn't H3, but an NH2
face. The results you would prefer are obtained in Convex_Hull_Cover : with a small radius (1 for example), overlap will
happen and routine computes only the empty sphere on the other side of H3 face. With bigger radius (try 2), you will
obtain 3 empty spheres.
TCover uses Tetrahedral resolution. But this problem set a radius solution of polygon with delta < 0, ie no solution is
computed



__________________Covering GeCl4 using ES_AllRadius global variable_____________________________________________________
=================================Commands :
from ase import Atoms
from ase.io import write,read
from ase.visualize import view
from es_mod import empty_spheres as esph

Structure = read('cluster_examples/GeCl4.xyz')
Cover = esph.Convex_Hull_Cover(Structure,radius_es=2)
1
Result = Atoms(positions = Cover)
view(Structure+Result)

ESAR1=esph.ES_AllRadius_Updater(Result,Structure,list=1)
print("Actual ES radius list (ESAR1) :\n{}".format(ESAR1))
Structure=Structure+Result

Cover = esph.Convex_Hull_Cover(Structure,radius_es=3.5,tol=0)
1
Result = Atoms(positions = Cover)
view(Structure+Result)

ESAR2=esph.ES_AllRadius_Updater(Result,Structure,list=1)
print("Actual ES radius list (ESAR2) :\n{}".format(ESAR2))
comp1=ESAR2[-1]
Cover = esph.Convex_Hull_Cover(Structure,radius_es=3.5)
1
Result = Atoms(positions = Cover)
view(Structure+Result)

ESAR2bis=esph.ES_AllRadius_Updater(Result,Structure,list=1)
comp2=ESAR2bis[-1]
print("\n\nWithout fusioning 2nd Cover, empty spheres are sized {} , after fusion there radius is {}".format(comp1,comp2))


=================================Comments :
ESAR1 and ESAR2 are just copy in time of the global variable "ES_AllRadius". Indeed : this global variable is used in
empty_spheres.py, not in main. So ES_AllRadius_Updater returns the global variable in the time it is called.

We also use this example to show tol use : in the second covering, we need a big radius to touch the 3 bounds of triangle
vertices, so afterward the empty spheres are overlapping. So without tol parameter (tol=0.6 by default), the empty-spheres
fusion, and the second cover is aligned with Cl atoms.
With tol = 0 , no fusion is done, so we have more empty spheres in cover.
Comparing the size of spheres with and without fusionning, you can see that fusion isn't always a good solution.



__________________Covering molecule Phtalocyanine_______________________________________________________________________
=================================Commands :
from ase import Atoms
from ase.io import write,read
from ase.visualize import view
from es_mod import empty_spheres as esph
from scipy.spatial import ConvexHull

Structure = read('cluster_examples/phtalocyanine.xyz')
view(Structure)
FCover = esph.Flat_Covering(Structure,R=2)

Result = Atoms(positions = FCover)
view(Structure + Result)
FCover = esph.Flat_Covering(Structure,Curved=True)

Result = Atoms(positions = FCover)
view(Structure + Result)
=================================Comments :
The first FCover is using classic "FlatCovering" : the routine search for every plane, then triangulate and add ES.
The problem here is that phtalocyanine is curved : so the polygons we see are not in same plane !
As command : Enter 1 to tell there are a lot of different planes.
To see the planes constructed, remove the R=2 parameter. Don't be afraid about time taken in this routine (5min possible)

To solve the problem : we add the "Curved=True" : as the cluster is close to z=0 plane (but curved), we project him as
2D plane with z=0. So we can triangulate correctly. Be careful with this method : all spheres are taken in the mesh !
As radius, you should select 1.5, and do again with 2 : The results are pretty different because of the overlap : it can
be interesting to compare the results.



__________________Computin Voronoi Vertices of a Structure (face-centered cube)_________________________________________
=================================Commands :
from ase import Atoms
from ase.io import write,read
from ase.visualize import view
from es_mod import empty_spheres as esph
Structure = read('cluster_examples/centeredcube.xyz')
Vor=esph.Voronoi_Vertex(Structure)
Result=Atoms(positions=Vor)
view(Structure+Result)
Structure = read('cluster_examples/GeCl4.xyz')
Vor=esph.Voronoi_Vertex(Structure)
Result=Atoms(positions=Vor)
view(Structure+Result)
Structure = read('cluster_examples/tubes.xyz')
Vor=esph.Voronoi_Vertex(Structure)
Result=Atoms(positions=Vor)
view(Structure)
view(Structure+Result)
=================================Commands for complete Voronoi informations (ridges, regions/polyhedrons) :
from ase import Atoms
from ase.io import write,read
import numpy as np
from scipy.spatial import Voronoi

Structure = read('cluster_examples/centeredcube.xyz')
Str=np.ndarray.tolist(Structure.positions)
Vor = Voronoi(Str)

Vor.                            [Then click on Tab]
=================================Comments :
The Voronoi_Vertex routine uses scipy.spatial.Voronoi :
https://docs.scipy.org/doc/scipy-0.19.1/reference/generated/scipy.spatial.Voronoi.html

You can use commands for complete Voronoi information to get voronoi ridges, regions, etc (see details after clicking Tab)

As you can see, the Voronoi research is very similar to the research of empty spheres. But the common errors solved
before will happen here to (see the 2 last figures : example with nanotube)

Other Voronoi Tesselations can be found under C++ opensource Voro++ : http://math.lbl.gov/voro++/about.html

========================================================================================================================
========================================================================================================================


                     ============================================================
                    |                         Routines                           |
                     ============================================================

The Empty_Spheres routines are agenced in different folders :

=====================empty_spheres.py=========================

 ES_AllRadius_Updater(NewES,Structure,[list])  :   Update ES_AllRadius global variable with new radius of empty spheres
                                                   given as NewES

 Voronoi_Vertex(Structure)          :   Computes Voronoi Vertices of Structure

 Delaunay_Tetrahedral_ES(Structure,[minsize],[maxsize],[tol]) :   Creates a tetrehedral mesh from the structure,
                                                            then returns for each center the perfect sphere going in.

 Convex_Hull_Cover(Structure,[es_radius],[tol],[missing],[Int_Ext]) :   Finds the exterior Hull from the set, create triangular
                                                                  mesh then returns cover coordinates. tol=0 => no fusion

 Select_Int_Ext(Centroid,E1,E2,IE)  :   Clean the Cover, taking only internal or external

 Internal_Hull_Cover(Structure,[es_radius],[tol],[missing])   :   Finds the interior Hull from the set, create triangular
                                                                  mesh then returns cover coordinates

 Internal_Hull_Centers(set)         :   Finds the interior Hull from the set, create triangular mesh then returns centers coordinates

 ES_Fusion(set, structure, size)    :   Change the set by clustering spheres near from size to each other. No size given => take shortest
                                        Maintain distances with structure, compared with the ancient set.

 Fusion_Overlap(Spheres_Data,tol)   :   Find Spheres touching each other, and fusions them. Don't return  radius : only final coordinates

 Flat_Covering(Structure,[R],[tol],[Curved])    :   For flat (or almost) set : Searchs major plane, triangulates,
                                                    and cover the 2 sides.

 Plane_Triangulation(Plane3D,Plane_eq): Return triangulation of a 3D Plane (convert into 2D, uses Delny)

 Atom_Radius(Structure,n,list)      :   Returns radius of n°th atom in Structure (Angstrom). Regroup different radius lists.

 Convex_Hull_InterCover(set)        :   Return list of internal cover using ConvexHull : Different from Delaunay_Intersphere :
                                        made for empty clusters

==============================================================
=====================es_clustering.py=========================
Tangent_Fourth_Sphere(Spheres_data, r, inout=1)         :   From 3 tangent spheres, returns the fourth, tangent to others, with radius r.
                                                            inout determines the side of the coordinate.
Spheres_Data_Structure_Extractor (Structure,list)       :   From Structure, returns data as [[S1][S2]...] where Si = [[xi,yi,zi],ri]
                                                            used in Tangent_Fourth routine. List determines the radius we will use.

Spheres_Data_XYZ_Extractor (name,list)                  :   From name, given as "_____.xyz", returns data as [[S1][S2]...] where Si = [[xi,yi,zi],ri]
                                                            used in Tangent_Fourth routine. List determines the radius we will use.

Tetrahedron_ES (Spheres_data)                           :   From 4 spheres forming a tetrahedron,returns the sphere tangent to others, with radius r.

Triangle_ES (Spheres_data,R)                            :   Returns the 2 solutions of tangent of 3 spheres with radius R.
==============================================================
========================es_tools.py===========================
=================Vector tools==================
vector_def(A,B)     :   returns simply the vector translating A to B
vector_norm(V)      :   return euclidian norm of a vector
throw_away (P,O,d)  :   returns P' so as O,P and P' are aligned, and OP'=d. So it translate P from O with distance d to direction OP
angle_vector(u,v)   :   returns the value of the convex angle defined by vectors u and v, in radians.
ColinearTest(u,v)   :   returns 1 if u and v colinear, and 0 if not.

===========Distance and Proximity tools========
distance(a,b)                   :   calculate distance between 2 points
search_nearest(point,set,d)     :   search the point in set wich is the nearest from point. We must know that the min distance is d
point_set_proximity(point, set) :   returns the min distance between the point and the set of points
set_set_proximity(S1,S2)        :   returns minimal distance between each points of each sets.
longest_dist(set)               :   returns the longest distance between the points in the set
shortest_dist(set)              :   returns the shortest distance between the points in the set
dist_point_plan(Pt,Plan)        :   From Pt=[x,y,z] and Plan=[a,b,c,d], return distance beetween Pt and Plan

===============Construction tools===============
Isobarycenter(set)          :    Calculate isobarycenter of a set of points. Returns his coordinates
Invert_Coord(set,O,r)       :    Apply circular inversion to every point in the set, excepted for origin, remaining origin.
Midpoint(P1,P2)             :    Computes the middle point of P1 and P2
rot3D(P,A,u)                :    Returns P' image of P by rotation from angle A around unity vector u
===================Data tools===================
commonlist(L1,L2)           :    Returns a list of elements common to L1 and L2, ie output is L1 intercection with L2
cleanlist(list)             :    Returns a list without repeated elements (each element in cleaned list appears only once)
=====================es_sym_analys.py=========================
sym_analyse(Cluster)                        :    Convert set into xyz folder, then finds all symetries using. Uses compare_sym and read_sym_file

major_plane(set)                            :    Search the major plane with max nb of points in set and returns all of his points, and his equation

Cluster_flatness_informations(set)          :    Returns set's hull's total volume and area

Cluster_search_hollow(set,tol)              :    Returns the hollow datas : hollow=[[list,center,volume]...] where list is hollow' vertice's

Cluster_emptyness_informations(structure)   :    Returns the % of volume of hull occupied by his spheres index,[center,volume] his hollow's center and volume. tol defines hollow's diagonale

Vertice_Sphere_Proportion(O,hull)           :    Returns the proportion of the sphere centered in Oth pt in the hull (proportion in ]O,1])

hull_search_neighbors(O,Simplices)          :    Returns list including O and neighbors (list contains only index, no coordinates)

convex_base(Neil,Simplices)                 :    Returns all Neil[0] neighbors so as ConvBase is the base of pyramid from top Neil[0]

Neighbors_List(numAtom,Structure)           :    Returns index list in Structure of neighbors of Atom indexed numAtom. numatom not included

Hull_Tetracut(O,Structure,hull)             :    Returns index list in Structure of centers of spheres defining the terahedron cuting the vertice O

facet_cap(O,Structure,hull)                 :    Return the proportion of sphere centered in O out from the hull(ie return the cap proportion of the sphere defined by cuting sphere with hull facets)
==============================================================






