Add new file hierarchy

This commit is contained in:
Sylvain Tricot 2019-11-15 11:16:06 +01:00
parent bb3a861ba2
commit eee2516a02
136 changed files with 65383 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
__pycache__
*.py[cod]
*.so
*.bak

20
Makefile Normal file
View File

@ -0,0 +1,20 @@
MAKESELF:=makeself
LICENSE:=$(shell cat ./license.txt)
VERSION:=$(shell cd src && python -c "import msspec; print(msspec.__version__)")
SETUPFILE:=pymsspec-$(VERSION).setup
.PHONY: clean
clean:
@rm -rf *.setup
purge: clean
@echo "Purging all..."
@find ./src -type f -name '*.pyc' -exec rm {} +
@find ./src -type d -name '__pycache__' -exec rm -r {} +
+$(MAKE) -C src/ clean
selfex: purge
@echo "Creating the self-extractible setup program... "
$(MAKESELF) --license "$(LICENSE)" ./src $(SETUPFILE) "Python MsSpec" ./install.sh

8
README.md Normal file
View File

@ -0,0 +1,8 @@
To install
==========
```Bash
make selfex
./pymsspec-###.setup
```

1
license.txt Normal file
View File

@ -0,0 +1 @@
Realeased under the GNU General Public License <http://www.gnu.org/licenses/>.

2
src/MANIFEST.in Normal file
View File

@ -0,0 +1,2 @@
include msspec/spec/*.so
include msspec/phagen/*.so

18
src/Makefile Normal file
View File

@ -0,0 +1,18 @@
VERSION:=$(shell python -c "import msspec; print(msspec.__version__)")
install: sdist
@pip install dist/msspec-$(VERSION).tar.gz
sdist: pybinding
@python setup.py sdist
pybinding:
+$(MAKE) -C msspec/spec/fortran pybinding
+$(MAKE) -C msspec/phagen/fortran pybinding
clean:
+$(MAKE) -C msspec/spec/fortran clean
+$(MAKE) -C msspec/phagen/fortran clean
@rm -rf dist
@rm -rf *.egg*

3
src/install.sh Normal file
View File

@ -0,0 +1,3 @@
#!/bin/bash
make install

1
src/msspec/__init__.py Normal file
View File

@ -0,0 +1 @@
__version__ = '1.2rc3.post152'

957
src/msspec/calcio.py Normal file
View File

@ -0,0 +1,957 @@
# coding: utf-8
"""
Module calcio
=============
"""
import numpy as np
from datetime import datetime
import ase
import re
from msspec.misc import UREG, LOGGER
class PhagenIO(object):
def __init__(self, phagen_parameters, malloc_parameters):
self.parameters = phagen_parameters
self.malloc_parameters = malloc_parameters
self.tl = None
self.nat = None
self.nateqm = None
self.ne = None
self.ipot = None
self.lmax_mode = None
self.nlmax = None
self.energies = None
def write_input_file(self, filename='input.ms'):
# in input folder
atoms = self.parameters.atoms
if not atoms.has('mt_radii'):
for a in atoms:
a.set('mt_radius', 0.)
if not atoms.has('mt_radii_scale'):
for a in atoms:
a.set('mt_radius_scale', 1.)
radii = atoms.get_array('mt_radii')
radii_scale = atoms.get_array('mt_radii_scale')
if np.all(radii == 0) and np.all(radii_scale == 1):
self.parameters.norman = 'stdcrm'
elif np.all(radii == 0) and np.any(radii_scale != 1):
self.parameters.norman = 'scaled'
radii = radii_scale
else:
self.parameters.norman = 'extrad'
radii *= radii_scale
parameters = []
for parameter in self.parameters:
name = parameter.name
if name in ('ionicity', 'atoms'):
# skip ionicity and atoms parameters as they are treated in
# other sections
continue
value = parameter.value
if isinstance(value, str) and not re.match('\..*\.', value):
s = ' {}=\'{:s}\''.format(name, str(parameter))
else:
s = ' {}={:s}'.format(name, str(parameter))
parameters.append(s)
header = " &job\n"
header += ',\n'.join(parameters) + "\n &end\n"
header += (' c Auto-generated file on {}\n Computes the T-matrix and '
'radial matrix elements\n\n').format(
datetime.ctime(datetime.now()))
# cluster section
try:
absorber = atoms.absorber
except AttributeError as err:
print(err)
absorber = 0
cluster_section = ''
absorber_line = ''
line_format = '{:>10s}{:4d}{:11.6f}{:14.6f}{:14.6f}{:13.4f}\n'
for iat, atom in enumerate(atoms):
symbol = atom.symbol
if symbol == 'X':
symbol = 'ES'
number = atom.number
x, y, z = atom.position
r = radii[iat]
cluster_line = line_format.format(symbol, number, x, y, z, r)
# store absober line
if atom.index == absorber:
absorber_line = cluster_line
else:
cluster_section += cluster_line
cluster_section = absorber_line + cluster_section
cluster_section += '{:10d}{:4d}{:10.0f}{:9.0f}{:9.0f}{:8.0f}\n'.format(
-1, -1, 0, 0, 0, 0)
# Ionicity section
ionicity = self.parameters.ionicity
ionicity_section = ''
ionicity_format = '{:8d}{:9.2f}\n'
symbols = set(atoms.get_chemical_symbols())
for symbol in symbols:
try:
charge = ionicity[symbol]
except KeyError:
charge = 0.
ionicity_section += ionicity_format.format(
ase.data.atomic_numbers[symbol],
charge)
ionicity_section += '{:8d}\n'.format(-1)
content = header + cluster_section + ionicity_section
# Write the content to filename
try:
with open(filename, 'r') as fd:
old_content = fd.read()
except IOError:
old_content = ''
pat = re.compile(r' c .*\n')
modified = False
if pat.sub('', content) != pat.sub('', old_content):
with open(filename, 'w') as fd:
fd.write(content)
modified = True
return modified
def write_include_file(self, filename='msxas3.inc'):
# read the whole include file content
with open(filename, 'r') as fd:
content = fd.read()
# backup the content in memory
old_content = content
# replace the content
for p in self.malloc_parameters:
content = re.sub(r'({:s}\s*=\s*)\d+'.format(p.name),
r'\g<1>{:d}'.format(p.value), content)
# actually write to the file only if different from the previous file
modified = False
if content != old_content:
with open(filename, 'w') as fd:
fd.write(content)
modified = True
return modified
def load_tl_file(self, filename='tmatrix.tl'):
atom_data = []
# load all the file in the string 'content'
with open(filename, 'r') as fd:
content = fd.read()
# convert the file to a (nat x ne) array
#
# first, split the content in a list for each atom
pattern = re.compile(r'-+\s*ATOM.*-+')
lines = pattern.split(content)
# get the number of atoms (nat) and the number of energy points (ne)
nat, ne, _, ipot, lmax_mode = list(map(int, content.split('\n')[0].split()))
self.nat = nat
self.ne = ne
self.ipot = ipot
self.lmax_mode = lmax_mode
# extract atom data
for line in lines:
numbers_str = ''.join(line.strip().split('\n')).split()
numbers = []
for n in numbers_str:
if not re.match(r'^\d+$', n):
numbers.append(float(n))
if len(numbers) > 0:
array = np.array(numbers).reshape((-1, 4)) # pylint: disable=no-member
atom_data.append(array)
# construct the data array
data = []
for i in range(nat):
data.append([])
for j in range(ne):
data[i].append(atom_data[j * nat + i])
self.tl = data
return data
def write_tl_file(self, filename='tmatrix.tl'):
def get_lmaxs(ie):
lmaxs = np.zeros(int(4 * np.ceil(self.nat / 4.)), dtype=int)
for a in range(self.nat):
lmaxs[a] = len(self.tl[a][ie]) - 1
lmaxs = lmaxs.reshape((-1, 4))
return lmaxs
def get_energies(unit='eV'):
emin = self.parameters.emin
emax = self.parameters.emax
delta = self.parameters.delta
energies = np.arange(emin, emax, delta)
if len(energies) == 0:
energies = np.array([emin])
if energies[-1] + delta / 2 < emax:
energies = np.append(energies, energies[-1] + delta)
# conversion in eV
if unit == 'eV':
energies = (energies * UREG.Ry).to('eV')
return energies
def custom_strfloat(f):
mantissa, exponent = '{:.10E}'.format(f).split('E')
m = format(float(mantissa) / 10, '.6f').replace('-0', '-')
e = format(int(exponent) + 1, '+03d')
return ' {}E{}'.format(m, e)
with open(filename, 'w') as fd:
fd.write('{:>9}{:>9}{:>9}{:>9}{:>9}\n'.format(self.nat, self.ne, 1,
self.ipot,
self.lmax_mode))
nlmax = 0
for ie in range(self.ne):
# write all lmaxs for each energy set
lmaxs = get_lmaxs(ie)
nlmax = int(max(nlmax, np.max(lmaxs)))
fmt1 = '{:>9}' * 4 + '\n'
fmt2 = '{:12.4f}{:10.4f}'
for _ in lmaxs:
fd.write(fmt1.format(*_))
for ia in range(self.nat):
# write the atom header line
fd.write('{}ATOM{:>4}{}\n'.format('-' * 26 + ' ', ia + 1,
' ' + '-' * 23))
for _a, _b, _c, _d in self.tl[ia][ie]:
fd.write(fmt2.format(_a, _b))
fd.write(custom_strfloat(_c))
fd.write(custom_strfloat(_d))
fd.write('\n')
self.nlmax = nlmax
self.energies = get_energies()
def load_cluster_file(self, filename='cluster.clu'):
data = np.loadtxt(filename, skiprows=1, usecols=(0, 2, 3, 4, 5, 6))
atoms = self.parameters.atoms
absorber_position = atoms[atoms.absorber].position
positions = data[:, 2:5] + absorber_position
proto_indices = []
for atom in atoms:
i = np.argmin(np.linalg.norm(positions - atom.position, axis=1))
proto_index = int(data[i, -1])
proto_indices.append(proto_index)
atoms.set_array('proto_indices', np.array(proto_indices))
self.nateqm = int(np.max([len(np.where(data[:,-1]==i)[0]) for i in range(
1, self.nat + 1)]))
def load_potential_file(self, filename='plot_vc.dat'):
a_index = 0
pot_data = []
with open(filename, 'r') as fd:
data = fd.readlines()
for d in data:
if d[1] == 'a':
a_index += 1
d = d.split()
a = {'Symbol': d[1], 'distance': float(d[4]),
'coord': np.array([float(d[7]), float(d[8]), float(d[9])]),
'index': int(a_index), 'values': []}
pot_data.append(a)
else:
pot_data[a_index - 1]['values'].append(tuple(float(_) for _ in d.split()))
# convert the values list to a numpy array
for _pot_data in pot_data:
v = _pot_data['values']
_pot_data['values'] = np.asarray(v)
return pot_data
class SpecIO(object):
def __init__(self, parameters, malloc_parameters, phagenio):
self.parameters = parameters
self.malloc_parameters = malloc_parameters
self.phagenio = phagenio
def write_input_file(self, filename='spec.dat'):
def title(t, shift=4, width=79, center=True):
if center:
s = ('{}*{:^%ds}*\n' % (width - shift - 2)).format(' ' * shift, t)
else:
s = ('{}*{:%ds}*\n' % (width - shift - 2)).format(' ' * shift, t)
return s
def rule(tabs=(5, 10, 10, 10, 10), symbol='=', shift=4, width=79):
s = ' ' * shift + '*' + symbol * (width - shift - 2) + '*\n'
t = np.cumsum(tabs) + shift
l = list(s)
for i in t:
l[i] = '+'
return ''.join(l)
def fillstr(a, b, index, justify='left'):
alist = list(a)
if justify == 'left':
offset = -len(b) + 1
elif justify == 'center':
offset = (-len(b) + 1) / 2
elif justify == 'decimal':
try:
offset = -(b.index('.') - 1)
except ValueError:
offset = 0
else:
offset = 0
for i, _ in enumerate(b):
alist[int(index + offset + i)] = _
return ''.join(alist)
def create_line(legend='', index=49, dots=False):
s = ' ' * 79 + '\n'
if dots:
s = fillstr(s, "..", 6, justify='right')
s = fillstr(s, "*", 4)
s = fillstr(s, "*", 78)
s = fillstr(s, legend, index, justify='right')
return s
p = self.parameters
content = rule(tabs=(), symbol='*')
content += title('spec input file')
content += rule(tabs=(), symbol='*')
content += rule(tabs=(), symbol='=')
content += title('CRYSTAL STRUCTURE :')
content += rule()
line = create_line("CRIST,CENTR,IBAS,NAT")
line = fillstr(line, "CUB", 9, 'left')
line = fillstr(line, "P", 19, 'left')
line = fillstr(line, format(0, 'd'), 29, 'left')
line = fillstr(line, str(p.get_parameter('extra_nat')), 39, 'left')
content += line
line = create_line("A,BSURA,CSURA,UNIT")
line = fillstr(line, format(1., '.4f'), 9, 'decimal')
line = fillstr(line, format(1., '.3f'), 19, 'decimal')
line = fillstr(line, format(1., '.3f'), 29, 'decimal')
line = fillstr(line, "ATU", 39, 'left')
content += line
line = create_line("ALPHAD,BETAD,GAMMAD")
line = fillstr(line, format(90., '.2f'), 9, 'decimal')
line = fillstr(line, format(90., '.2f'), 19, 'decimal')
line = fillstr(line, format(90., '.2f'), 29, 'decimal')
content += line
line = create_line("H,K,I,L")
line = fillstr(line, format(0, 'd'), 9, 'left')
line = fillstr(line, format(0, 'd'), 19, 'left')
line = fillstr(line, format(0, 'd'), 29, 'left')
line = fillstr(line, format(1, 'd'), 39, 'left')
content += line
line = create_line("NIV,COUPUR,ITEST,IESURF")
line = fillstr(line, format(8, 'd'), 9, 'left')
line = fillstr(line, format(1.4, '.2f'), 19, 'decimal')
line = fillstr(line, format(0, 'd'), 29, 'left')
line = fillstr(line, format(1, 'd'), 39, 'left')
content += line
line = create_line("ATBAS,CHEM(NAT),NZAT(NAT)")
line = fillstr(line, format(0., '.6f'), 9, 'decimal')
line = fillstr(line, format(0., '.6f'), 19, 'decimal')
line = fillstr(line, format(0., '.6f'), 29, 'decimal')
line = fillstr(line, format("Mg", '>2s'), 39, 'right')
line = fillstr(line, format(12, '>2d'), 43, 'right')
content += line
line = create_line("ATBAS,CHEM(NAT),NZAT(NAT)")
line = fillstr(line, format(0., '.6f'), 9, 'decimal')
line = fillstr(line, format(0.5, '.6f'), 19, 'decimal')
line = fillstr(line, format(0., '.6f'), 29, 'decimal')
line = fillstr(line, format("O", '>2s'), 39, 'right')
line = fillstr(line, format(8, '>2d'), 43, 'right')
content += line
line = create_line("VECBAS")
line = fillstr(line, format(1., '.6f'), 9, 'decimal')
line = fillstr(line, format(0., '.6f'), 19, 'decimal')
line = fillstr(line, format(0., '.6f'), 29, 'decimal')
content += line
line = create_line("VECBAS")
line = fillstr(line, format(0., '.6f'), 9, 'decimal')
line = fillstr(line, format(1., '.6f'), 19, 'decimal')
line = fillstr(line, format(0., '.6f'), 29, 'decimal')
content += line
line = create_line("VECBAS")
line = fillstr(line, format(0., '.6f'), 9, 'decimal')
line = fillstr(line, format(0., '.6f'), 19, 'decimal')
line = fillstr(line, format(1., '.6f'), 29, 'decimal')
content += line
line = create_line("IREL,NREL,PCREL(NREL)")
line = fillstr(line, format(0, 'd'), 9, 'left')
line = fillstr(line, format(0, 'd'), 19, 'left')
line = fillstr(line, format(0., '.1f'), 29, 'decimal')
line = fillstr(line, format(0., '.1f'), 39, 'decimal')
content += line
line = create_line("OMEGA1,OMEGA2,IADS")
line = fillstr(line, format(28., '.2f'), 9, 'decimal')
line = fillstr(line, format(0., '.2f'), 19, 'decimal')
line = fillstr(line, format(1, 'd'), 29, 'left')
content += line
content += rule()
content += title('TYPE OF CALCULATION :')
content += rule()
line = create_line("SPECTRO,ISPIN,IDICHR,IPOL")
line = fillstr(line, str(p.calctype_spectro), 9, 'left')
line = fillstr(line, str(p.get_parameter('calctype_ispin')), 19, 'left')
line = fillstr(line, str(p.calctype_idichr), 29, 'left')
line = fillstr(line, str(p.calctype_ipol), 39, 'left')
content += line
line = create_line("I_AMP")
line = fillstr(line, str(p.calctype_iamp), 9, 'left')
content += line
content += rule()
content += title('PhD EXPERIMENTAL PARAMETERS :')
content += rule()
line = create_line("LI,S-O,INITL,I_SO")
line = fillstr(line, str(p.ped_li), 9, 'left')
line = fillstr(line, str(p.ped_so), 19, 'center')
line = fillstr(line, str(p.ped_initl), 29, 'left')
line = fillstr(line, str(p.ped_iso), 39, 'left')
content += line
line = create_line("IPHI,ITHETA,IE,IFTHET")
line = fillstr(line, str(p.ped_iphi), 9, 'left')
line = fillstr(line, str(p.ped_itheta), 19, 'left')
line = fillstr(line, str(p.ped_ie), 29, 'left')
line = fillstr(line, str(p.ped_ifthet), 39, 'left')
content += line
line = create_line("NPHI,NTHETA,NE,NFTHET")
line = fillstr(line, str(p.ped_nphi), 9, 'left')
line = fillstr(line, str(p.ped_ntheta), 19, 'left')
line = fillstr(line, str(p.ped_ne), 29, 'left')
line = fillstr(line, str(p.ped_nfthet), 39, 'left')
content += line
line = create_line("PHI0,THETA0,E0,R0")
line = fillstr(line, str(p.get_parameter('ped_phi0')), 9, 'decimal')
line = fillstr(line, str(p.get_parameter('ped_theta0')), 19, 'decimal')
line = fillstr(line, str(p.get_parameter('ped_e0')), 29, 'decimal')
line = fillstr(line, str(p.get_parameter('ped_r0')), 39, 'decimal')
content += line
line = create_line("PHI1,THETA1,E1,R1")
line = fillstr(line, str(p.get_parameter('ped_phi1')), 9, 'decimal')
line = fillstr(line, str(p.get_parameter('ped_theta1')), 19, 'decimal')
line = fillstr(line, str(p.get_parameter('ped_e1')), 29, 'decimal')
line = fillstr(line, str(p.get_parameter('ped_r1')), 39, 'decimal')
content += line
line = create_line("THLUM,PHILUM,ELUM")
line = fillstr(line, str(p.get_parameter('ped_thlum')), 9, 'decimal')
line = fillstr(line, str(p.get_parameter('ped_philum')), 19, 'decimal')
line = fillstr(line, str(p.get_parameter('ped_elum')), 29, 'decimal')
content += line
line = create_line("IMOD,IMOY,ACCEPT,ICHKDIR")
line = fillstr(line, str(p.get_parameter('ped_imod')), 9, 'decimal')
line = fillstr(line, str(p.get_parameter('ped_imoy')), 19, 'decimal')
line = fillstr(line, str(p.get_parameter('ped_accept')), 29, 'decimal')
line = fillstr(line, str(p.get_parameter('ped_ichkdir')), 39, 'decimal')
content += line
content += rule()
content += title(' ' * 22 + 'LEED EXPERIMENTAL PARAMETERS :', center=False)
content += rule()
line = create_line("IPHI,ITHETA,IE,IFTHET")
line = fillstr(line, str(p.get_parameter('leed_iphi')), 9, 'left')
line = fillstr(line, str(p.get_parameter('leed_itheta')), 19, 'left')
line = fillstr(line, str(p.get_parameter('leed_ie')), 29, 'left')
line = fillstr(line, str(p.get_parameter('leed_ifthet')), 39, 'left')
content += line
line = create_line("NPHI,NTHETA,NE,NFTHET")
line = fillstr(line, str(p.get_parameter('leed_nphi')), 9, 'left')
line = fillstr(line, str(p.get_parameter('leed_ntheta')), 19, 'left')
line = fillstr(line, str(p.get_parameter('leed_ne')), 29, 'left')
line = fillstr(line, str(p.get_parameter('leed_nfthet')), 39, 'left')
content += line
line = create_line("PHI0,THETA0,E0,R0")
line = fillstr(line, str(p.get_parameter('leed_phi0')), 9, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_theta0')), 19, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_e0')), 29, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_r0')), 39, 'decimal')
content += line
line = create_line("PHI1,THETA1,E1,R1")
line = fillstr(line, str(p.get_parameter('leed_phi1')), 9, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_theta1')), 19, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_e1')), 29, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_r1')), 39, 'decimal')
content += line
line = create_line("TH_INI,PHI_INI")
line = fillstr(line, str(p.get_parameter('leed_thini')), 9, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_phiini')), 19, 'decimal')
content += line
line = create_line("IMOD,IMOY,ACCEPT,ICHKDIR")
line = fillstr(line, str(p.get_parameter('leed_imod')), 9, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_imoy')), 19, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_accept')), 29, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_ichkdir')), 39,
'decimal')
content += line
content += rule()
content += title(' ' * 21 + 'EXAFS EXPERIMENTAL PARAMETERS :', center=False)
content += rule()
line = create_line("EDGE,INITL,THLUM,PHILUM")
line = fillstr(line, str(p.get_parameter('exafs_edge')), 9, 'left')
line = fillstr(line, str(p.get_parameter('exafs_initl')), 19, 'left')
line = fillstr(line, str(p.get_parameter('exafs_thlum')), 29, 'decimal')
line = fillstr(line, str(p.get_parameter('exafs_philum')), 39,
'decimal')
content += line
line = create_line("NE,EK_INI,EK_FIN,EPH_INI")
line = fillstr(line, str(p.get_parameter('exafs_ne')), 9, 'left')
line = fillstr(line, str(p.get_parameter('exafs_ekini')), 19, 'decimal')
line = fillstr(line, str(p.get_parameter('exafs_ekfin')), 29, 'decimal')
line = fillstr(line, str(p.get_parameter('exafs_ephini')), 39,
'decimal')
content += line
content += rule()
content += title(' ' * 22 + 'AED EXPERIMENTAL PARAMETERS :', center=False)
content += rule()
line = create_line("EDGE_C,EDGE_I,EDGE_A")
line = fillstr(line, str(p.get_parameter('aed_edgec')), 9, 'left')
line = fillstr(line, str(p.get_parameter('aed_edgei')), 19, 'left')
line = fillstr(line, str(p.get_parameter('aed_edgea')), 29, 'left')
content += line
line = create_line("I_MULT,MULT")
line = fillstr(line, str(p.get_parameter('aed_imult')), 9, 'left')
line = fillstr(line, str(p.get_parameter('aed_mult')), 19, 'center')
content += line
line = create_line("IPHI,ITHETA,IFTHET,I_INT")
line = fillstr(line, str(p.get_parameter('aed_iphi')), 9, 'left')
line = fillstr(line, str(p.get_parameter('aed_itheta')), 19, 'left')
line = fillstr(line, str(p.get_parameter('aed_ifthet')), 29, 'left')
line = fillstr(line, str(p.get_parameter('aed_iint')), 39, 'left')
content += line
line = create_line("NPHI,NTHETA,NFTHET")
line = fillstr(line, str(p.get_parameter('aed_nphi')), 9, 'left')
line = fillstr(line, str(p.get_parameter('aed_ntheta')), 19, 'left')
line = fillstr(line, str(p.get_parameter('aed_nfthet')), 29, 'left')
content += line
line = create_line("PHI0,THETA0,R0")
line = fillstr(line, str(p.get_parameter('aed_phi0')), 9, 'decimal')
line = fillstr(line, str(p.get_parameter('aed_theta0')), 19, 'decimal')
line = fillstr(line, str(p.get_parameter('aed_r0')), 29, 'decimal')
content += line
line = create_line("PHI1,THETA1,R1")
line = fillstr(line, str(p.get_parameter('aed_phi1')), 9, 'decimal')
line = fillstr(line, str(p.get_parameter('aed_theta1')), 19, 'decimal')
line = fillstr(line, str(p.get_parameter('aed_r1')), 29, 'decimal')
content += line
line = create_line("IMOD,IMOY,ACCEPT,ICHKDIR")
line = fillstr(line, str(p.get_parameter('aed_imod')), 9, 'left')
line = fillstr(line, str(p.get_parameter('aed_imoy')), 19, 'left')
line = fillstr(line, str(p.get_parameter('aed_accept')), 29, 'decimal')
line = fillstr(line, str(p.get_parameter('aed_ichkdir')), 39, 'left')
content += line
content += rule()
content += title(' ' * 19 + 'EIGENVALUE CALCULATION PARAMETERS :',
center=False)
content += rule()
line = create_line("NE,EK_INI,EK_FIN,I_DAMP")
line = fillstr(line, str(p.get_parameter('eigval_ne')), 9, 'left')
line = fillstr(line, str(p.get_parameter('eigval_ekini')), 19,
'decimal')
line = fillstr(line, str(p.get_parameter('eigval_ekfin')), 29,
'decimal')
line = fillstr(line, str(p.get_parameter('eigval_idamp')), 39, 'left')
content += line
if p.get_parameter('calctype_spectro') == "EIG":
nlines = int(np.ceil(p.eigval_ne / 4.))
else:
nlines = 1
table = np.chararray((nlines, 4), unicode=True)
table[:] = str(p.get_parameter('eigval_ispectrum_ne').default)
for i in range(nlines):
line = create_line("I_SPECTRUM(NE)")
for j, o in enumerate((9, 19, 29, 39)):
line = fillstr(line, table[i, j], o, 'left')
content += line
line = create_line("I_PWM,METHOD,ACC,EXPO")
line = fillstr(line, str(p.get_parameter('eigval_ipwm')), 9, 'left')
line = fillstr(line, str(p.get_parameter('eigval_method')), 19, 'left')
line = fillstr(line, str(p.get_parameter('eigval_acc')), 29, 'decimal')
line = fillstr(line, str(p.get_parameter('eigval_expo')), 39, 'decimal')
content += line
line = create_line("N_MAX,N_ITER,N_TABLE,SHIFT")
line = fillstr(line, str(p.get_parameter('eigval_nmax')), 9, 'left')
line = fillstr(line, str(p.get_parameter('eigval_niter')), 19, 'left')
line = fillstr(line, str(p.get_parameter('eigval_ntable')), 29, 'left')
line = fillstr(line, str(p.get_parameter('eigval_shift')), 39,
'decimal')
content += line
line = create_line("I_XN,I_VA,I_GN,I_WN")
line = fillstr(line, str(p.get_parameter('eigval_ixn')), 9, 'left')
line = fillstr(line, str(p.get_parameter('eigval_iva')), 19, 'left')
line = fillstr(line, str(p.get_parameter('eigval_ign')), 29, 'left')
line = fillstr(line, str(p.get_parameter('eigval_iwn')), 39, 'left')
content += line
line = create_line("L,ALPHA,BETA")
line = fillstr(line, str(p.get_parameter('eigval_l')), 9, 'left')
line = fillstr(line, str(p.get_parameter('eigval_alpha')), 19,
'decimal')
line = fillstr(line, str(p.get_parameter('eigval_beta')), 29,
'decimal')
content += line
content += rule()
content += title(' ' * 24 + 'CALCULATION PARAMETERS :', center=False)
content += rule()
line = create_line("NO,NDIF,ISPHER,I_GR")
line = fillstr(line, str(p.get_parameter('calc_no')), 9, 'left')
line = fillstr(line, str(p.get_parameter('calc_ndif')), 19, 'left')
line = fillstr(line, str(p.get_parameter('calc_ispher')), 29, 'left')
line = fillstr(line, str(p.get_parameter('calc_igr')), 39, 'left')
content += line
line = create_line("ISFLIP,IR_DIA,ITRTL,I_TEST")
line = fillstr(line, str(p.get_parameter('calc_isflip')), 9, 'left')
line = fillstr(line, str(p.get_parameter('calc_irdia')), 19, 'left')
line = fillstr(line, str(p.get_parameter('calc_itrtl')), 29, 'left')
line = fillstr(line, str(p.get_parameter('calc_itest')), 39, 'left')
content += line
line = create_line("NEMET,IEMET(NEMET)")
line = fillstr(line, format(1, 'd'), 9, 'left')
line = fillstr(line, format(1, 'd'), 19, 'left')
line = fillstr(line, format(0, 'd'), 29, 'left')
line = fillstr(line, format(0, 'd'), 39, 'left')
content += line
line = create_line("ISOM,NONVOL,NPATH,VINT")
line = fillstr(line, str(p.get_parameter('calc_isom')), 9, 'left')
line = fillstr(line, str(p.get_parameter('calc_nonvol')), 19, 'left')
line = fillstr(line, str(p.get_parameter('calc_npath')), 29, 'left')
line = fillstr(line, str(p.get_parameter('calc_vint')), 39, 'decimal')
content += line
line = create_line("IFWD,NTHOUT,I_NO,I_RA")
line = fillstr(line, str(p.get_parameter('calc_ifwd')), 9, 'left')
line = fillstr(line, str(p.get_parameter('calc_nthout')), 19, 'left')
line = fillstr(line, str(p.get_parameter('calc_ino')), 29, 'left')
line = fillstr(line, str(p.get_parameter('calc_ira')), 39, 'left')
content += line
nat = p.extra_nat
nra_arr = np.ones((nat), dtype=np.int)
thfwd_arr = np.ones((nat))
path_filtering = p.extra_parameters['calculation'].get_parameter(
'path_filtering').value
if path_filtering != None and 'backward_scattering' in path_filtering:
ibwd_arr = np.ones((nat), dtype=np.int)
else:
ibwd_arr = np.zeros((nat), dtype=np.int)
thbwd_arr = np.ones((nat))
for at in p.extra_atoms:
i = at.get('proto_index') - 1
thfwd_arr[i] = at.get('forward_angle')
thbwd_arr[i] = at.get('backward_angle')
nra_arr[i] = at.get('RA_cut_off')
for i in range(p.extra_nat):
line = create_line("N_RA,THFWD,IBWD,THBWD(NAT)", dots=True)
line = fillstr(line, format(nra_arr[i], 'd'), 9, 'left')
line = fillstr(line, format(thfwd_arr[i], '.2f'), 19, 'decimal')
line = fillstr(line, format(ibwd_arr[i], 'd'), 29, 'left')
line = fillstr(line, format(thbwd_arr[i], '.2f'), 39, 'decimal')
content += line
line = create_line("IPW,NCUT,PCTINT,IPP")
line = fillstr(line, str(p.get_parameter('calc_ipw')), 9, 'left')
line = fillstr(line, str(p.get_parameter('calc_ncut')), 19, 'left')
line = fillstr(line, str(p.get_parameter('calc_pctint')), 29, 'decimal')
line = fillstr(line, str(p.get_parameter('calc_ipp')), 39, 'left')
content += line
line = create_line("ILENGTH,RLENGTH,UNLENGTH")
line = fillstr(line, str(p.get_parameter('calc_ilength')), 9, 'left')
line = fillstr(line, str(p.get_parameter('calc_rlength')), 19,
'decimal')
line = fillstr(line, str(p.get_parameter('calc_unlength')), 29, 'left')
content += line
line = create_line("IDWSPH,ISPEED,IATT,IPRINT")
# Here, if 'vibrational_damping' is None, use the 'debye_waller'
# approach and the debye model for the mean square displacements
# and set the temeprature to 0K and the Debye temeparture to 500K.
calc_param = p.extra_parameters['calculation']
if calc_param.vibrational_damping is None:
idwsph = format(0, 'd')
idcm = format(2, 'd')
temp = format(0., '.2f')
td = format(500., '.2f')
LOGGER.warning('Vibrational damping is disabled for this calculation.')
else:
idwsph = str(p.get_parameter('calc_idwsph'))
idcm = str(p.get_parameter('calc_idcm'))
temp = str(p.get_parameter('calc_t'))
td = str(p.get_parameter('calc_td'))
ispeed = str(p.get_parameter('calc_ispeed'))
line = fillstr(line, idwsph, 9, 'left')
line = fillstr(line, ispeed, 19, 'left')
line = fillstr(line, str(p.get_parameter('calc_iatt')), 29, 'left')
line = fillstr(line, str(p.get_parameter('calc_iprint')), 39, 'left')
content += line
line = create_line("IDCM,TD,T,RSJ")
line = fillstr(line, idcm, 9, 'left')
line = fillstr(line, td, 19, 'decimal')
line = fillstr(line, temp, 29, 'decimal')
line = fillstr(line, str(p.get_parameter('calc_rsj')), 39, 'decimal')
content += line
line = create_line("ILPM,XLPM0")
line = fillstr(line, str(p.get_parameter('calc_ilpm')), 9, 'left')
line = fillstr(line, str(p.get_parameter('calc_xlpm0')), 19, 'decimal')
content += line
nat = p.extra_nat
nlines = int(np.ceil(nat / 4.))
uj2_array = np.zeros((4 * nlines))
# Now, for each atom in the cluster, get the mean_square_vibration and
# store it in the index corresponding to the prototypical index
for at in p.extra_atoms:
i = at.get('proto_index') - 1
msq_vib = at.get('mean_square_vibration')
uj2_array[i] = msq_vib
uj2_array = uj2_array.reshape((nlines, 4))
for i in range(nlines):
line = create_line("UJ2(NAT)", dots=True)
for j, o in enumerate((9, 19, 29, 39)):
line = fillstr(line, format(uj2_array[i, j], '.5f'), o,
'decimal')
content += line
content += rule()
content += title(' ' * 17 + 'INPUT FILES (PHD, EXAFS, LEED, AED, '
'APECS) :', center=False)
content += rule(tabs=(), symbol='-')
content += title(' ' * 8 + 'NAME' + ' ' * 20 + 'UNIT' + ' ' * 16 + 'TYPE',
center=False)
content += rule(tabs=(5, 23, 7, 10))
line = create_line("DATA FILE,UNIT")
line = fillstr(line, str(p.get_parameter('input_data')), 9, 'right')
line = fillstr(line, str(p.get_parameter('input_unit00')), 39, 'left')
content += line
line = create_line("PHASE SHIFTS/TL FILE,UNIT")
line = fillstr(line, str(p.get_parameter('input_tl')), 9, 'right')
line = fillstr(line, str(p.get_parameter('input_unit01')), 39, 'left')
content += line
line = create_line("RADIAL MATRIX ELTS FILE,UNIT")
line = fillstr(line, str(p.get_parameter('input_rad')), 9, 'right')
line = fillstr(line, str(p.get_parameter('input_unit02')), 39, 'left')
content += line
line = create_line("CLUSTER FILE,UNIT")
line = fillstr(line, str(p.get_parameter('input_cluster')), 9, 'right')
line = fillstr(line, str(p.get_parameter('input_unit03')), 39, 'left')
content += line
line = create_line("ADSORBATE FILE,UNIT")
line = fillstr(line, str(p.get_parameter('input_adsorbate')), 9,
'right')
line = fillstr(line, str(p.get_parameter('input_unit04')), 39, 'left')
content += line
line = create_line("K DIRECTIONS FILE,UNIT")
line = fillstr(line, str(p.get_parameter('input_kdirs')), 9, 'right')
line = fillstr(line, str(p.get_parameter('input_unit05')), 39, 'left')
content += line
content += rule(tabs=(5, 23, 7, 10))
content += title(' ' * 21 + 'ADDITIONAL INPUT FILES (APECS) :',
center=False)
content += title(' ' * 28 + '(AUGER ELECTRON)', center=False)
content += rule(tabs=(), symbol='-')
content += title(' ' * 8 + 'NAME' + ' ' * 20 + 'UNIT' + ' ' * 16 + 'TYPE',
center=False)
content += rule(tabs=(5, 23, 7, 10))
line = create_line("PHASE SHIFTS/TL FILE,UNIT")
line = fillstr(line, str(p.get_parameter('input2_tl')), 9, 'right')
line = fillstr(line, str(p.get_parameter('input2_unit06')), 39, 'left')
content += line
line = create_line("RADIAL MATRIX ELTS FILE,UNIT")
line = fillstr(line, str(p.get_parameter('input2_rad')), 9, 'right')
line = fillstr(line, str(p.get_parameter('input2_unit07')), 39, 'left')
content += line
line = create_line("K DIRECTIONS FILE,UNIT")
line = fillstr(line, str(p.get_parameter('input2_kdirs')), 9, 'right')
line = fillstr(line, str(p.get_parameter('input2_unit08')), 39, 'left')
content += line
content += rule(tabs=(5, 23, 7, 10))
content += title(' ' * 29 + 'OUTPUT FILES :', center=False)
content += rule(tabs=(), symbol='-')
content += title(' ' * 8 + 'NAME' + ' ' * 20 + 'UNIT' + ' ' * 16 + 'TYPE',
center=False)
content += rule(tabs=(5, 23, 7, 10))
line = create_line("CONTROL FILE,UNIT")
line = fillstr(line, str(p.get_parameter('output_log')), 9, 'right')
line = fillstr(line, str(p.get_parameter('output_unit09')), 39, 'left')
content += line
line = create_line("RESULT FILE,UNIT")
line = fillstr(line, str(p.get_parameter('output_res')), 9, 'right')
line = fillstr(line, str(p.get_parameter('output_unit10')), 39, 'left')
content += line
line = create_line("SCATTERING FACTOR FILE,UNIT")
line = fillstr(line, str(p.get_parameter('output_sf')), 9, 'right')
line = fillstr(line, str(p.get_parameter('output_unit11')), 39, 'left')
content += line
line = create_line("AUGMENTED CLUSTER FILE,UNIT")
line = fillstr(line, str(p.get_parameter('output_augclus')), 9, 'right')
line = fillstr(line, str(p.get_parameter('output_unit12')), 39, 'left')
content += line
content += rule(tabs=(5, 23, 7, 10))
content += title(' ' * 26 + 'END OF THE DATA FILE', center=False)
content += rule(tabs=())
content += rule(tabs=(), symbol='*')
try:
with open(filename, 'r') as fd:
old_content = fd.read()
except IOError:
old_content = ''
modified = False
if content != old_content:
with open(filename, 'w') as fd:
fd.write(content)
modified = True
return modified
def write_include_file(self, filename='spec.inc'):
def get_li(level):
orbitals = 'spdfghi'
m = re.match(r'\d(?P<l>[%s])(\d/2)?' % orbitals, level)
return orbitals.index(m.group('l'))
requirements = {
'NATP_M': self.phagenio.nat,
'NATCLU_M': len(self.parameters.extra_atoms),
'NAT_EQ_M': self.phagenio.nateqm,
'N_CL_L_M': 0,
'NE_M': self.phagenio.ne,
'NL_M': self.phagenio.nlmax + 1,
'LI_M': get_li(self.parameters.extra_level),
'NEMET_M': 1,
'NO_ST_M': self.parameters.calc_no,
}
# read the include file
with open(filename, 'r') as fd:
content = fd.read()
# backup the content in memory
old_content = content
"""
for key in ('NATP_M', 'NATCLU_M', 'NE_M', 'NEMET_M', 'LI_M', 'NL_M',
'NO_ST_M'):
required = requirements[key]
limit = self.malloc_parameters.get_parameter(key).value
value = required if required > limit else limit
content = re.sub(r'({:s}\s*=\s*)\d+'.format(key),
r'\g<1>{:d}'.format(value), content)
"""
for key in ('NAT_EQ_M', 'N_CL_N_M', 'NDIF_M', 'NSO_M', 'NTEMP_M',
'NODES_EX_M', 'NSPIN_M', 'NTH_M', 'NPH_M', 'NDIM_M',
'N_TILT_M', 'N_ORD_M', 'NPATH_M', 'NGR_M'):
value = self.malloc_parameters.get_parameter(key).value
content = re.sub(r'({:s}\s*=\s*)\d+'.format(key),
r'\g<1>{:d}'.format(value), content)
for key, value in list(requirements.items()):
content = re.sub(r'({:s}\s*=\s*)\d+'.format(key),
r'\g<1>{:d}'.format(value), content)
modified = False
if content != old_content:
with open(filename, 'w') as fd:
fd.write(content)
modified = True
return modified
def write_kdirs_file(self, filename='kdirs.dat'):
fwhm = 1.
all_theta = self.parameters.extra_parameters['scan'].theta
all_phi = self.parameters.extra_parameters['scan'].phi
f = '{:7}{:4}{:6}\n'
old_content = None
try:
with open(filename, 'r') as fd:
old_content = fd.read()
except IOError:
pass
content = ''
content += f.format(2, 1, len(all_theta) * len(all_phi))
content += f.format(1, len(all_phi), len(all_theta))
for iphi, phi in enumerate(all_phi):
for itheta, theta in enumerate(all_theta):
s = '{:5}{:5}{:13.3f}{:11.3f}{:15e}\n'
s = s.format(iphi + 1, itheta + 1, theta, phi, fwhm)
content += s
modified = False
if content != old_content:
with open(filename, 'w') as fd:
fd.write(content)
modified = True
return modified
def load_results(self, filename='results.dat'):
rows2skip = {
'PED': 27,
'AED': 27,
'EXAFS': 27,
'LEED': 26,
'EIG': 0
}
spectro = self.parameters.extra_parameters['global'].spectroscopy
skip = rows2skip[spectro]
data = np.loadtxt(filename, skiprows=skip, unpack=True)
if len(data.shape) <= 1:
data = data.reshape((1, data.shape[0]))
return data
def load_facdif(self, filename='facdif1.dat'):
data = np.loadtxt(filename, skiprows=1)
return data
def load_log(self, filename='spec.log'):
pat = re.compile(r'ORDER\s+(\d+)\s+TOTAL NUMBER OF PATHS\s+:\s+(\d+)')
with open(filename, 'r') as fd:
content = fd.read()
#return pat.findall(content.replace('\n', '__cr__'))
return pat.findall(content)

905
src/msspec/calculator.py Normal file
View File

@ -0,0 +1,905 @@
# coding: utf-8
# vim: set et sw=4 ts=4 sts=4 nu ai cc=+0 fdm=indent mouse=a:
"""
Module calculator
=================
This module contains different classes used to define a new calculator for
specific spectroscopies understood by MsSpec.
These spectroscopies are listed :ref:`here <globalparameters-spectroscopy>`.
There is one *calculator* class for each spectroscopy. The class name is based
on the spectroscopy name. For instance, the class for PhotoElectron Diffraction
is called :py:class:`_PED`.
The helper function :py:func:`calculator.MSSPEC` is used to create objects from
these classes by passing the kind of spectroscopy as a keyword argument.
For more information on MsSpec, follow this
`link <https://ipr.univ-rennes1.fr/msspec>`__
"""
import os
import sys
import re
import inspect
from subprocess import Popen, PIPE
from shutil import copyfile, rmtree
from datetime import datetime
import time
from io import StringIO
from collections import OrderedDict
from ase.calculators.calculator import Calculator
import ase.data
import ase.atom
import ase.atoms
import numpy as np
from msspec import iodata
from msspec.data import electron_be
from msspec.config import Config
from msspec.misc import (UREG, LOGGER, get_call_info, get_level_from_electron_configuration,
XRaySource, set_log_output, log_process_output)
from msspec.utils import get_atom_index
from msspec.parameters import (PhagenParameters, PhagenMallocParameters,
SpecParameters, SpecMallocParameters,
GlobalParameters, MuffintinParameters,
TMatrixParameters, SourceParameters,
DetectorParameters, ScanParameters,
CalculationParameters,
PEDParameters, EIGParameters)
from msspec.calcio import PhagenIO, SpecIO
from msspec.phagen.libphagen import main as do_phagen
from msspec.spec.libspec import run as do_spec
from terminaltables.ascii_table import AsciiTable
try:
MSSPEC_ROOT = os.environ['MSSPEC_ROOT']
except KeyError:
cfg = Config()
MSSPEC_ROOT = cfg.get('path')
if MSSPEC_ROOT == str(None):
raise NameError('No path to the MsSpec distribution found !!')
def init_msspec():
LOGGER.debug('Initialization of the msspec module')
ase.atom.names['mt_radius'] = ('mt_radii', 0.)
ase.atom.names['mt_radius_scale'] = ('mt_radii_scale', 1.)
ase.atom.names['proto_index'] = ('proto_indices', 1)
ase.atom.names['mean_square_vibration'] = ('mean_square_vibrations', 0.)
ase.atom.names['forward_angle'] = ('forward_angles', 20.)
ase.atom.names['backward_angle'] = ('backward_angles', 20.)
ase.atom.names['RA_cut_off'] = ('RA_cuts_off', 1)
ase.atoms.Atoms.absorber = None
init_msspec()
class _MSCALCULATOR(Calculator):
"""
This class defines an ASE calculator for doing Multiple scattering
calculations.
"""
implemented_properties = ['', ]
__data = {}
def __init__(self, spectroscopy='PED', algorithm='expansion',
polarization=None, dichroism=None, spinpol=False,
folder='./calc', txt='-', **kwargs):
stdout = sys.stdout
if isinstance(txt, str) and txt != '-':
stdout = open(txt, 'w')
#elif isinstance(txt, buffer):
# stdout = txt
elif txt == None:
stdout = open('/dev/null', 'a')
#set_log_output(stdout)
########################################################################
LOGGER.debug('Initialization of %s', self.__class__.__name__)
LOGGER.debug(get_call_info(inspect.currentframe()))
########################################################################
# Init the upper class
Calculator.__init__(self, **kwargs)
########################################################################
LOGGER.debug(' create low level parameters')
########################################################################
self.phagen_parameters = PhagenParameters()
self.phagen_malloc_parameters = PhagenMallocParameters()
self.spec_parameters = SpecParameters()
self.spec_malloc_parameters = SpecMallocParameters()
########################################################################
LOGGER.debug(' create higher level parameters')
########################################################################
self.tmatrix_parameters = TMatrixParameters(self.phagen_parameters)
self.muffintin_parameters = MuffintinParameters(self.phagen_parameters,
self.spec_parameters)
self.global_parameters = GlobalParameters(self.phagen_parameters,
self.spec_parameters)
if spectroscopy == 'PED':
self.spectroscopy_parameters = PEDParameters(self.phagen_parameters,
self.spec_parameters)
elif spectroscopy == 'EIG':
self.spectroscopy_parameters = EIGParameters(self.phagen_parameters,
self.spec_parameters)
#pass
else:
raise NameError('No such spectrosopy')
self.source_parameters = SourceParameters(self.global_parameters,
self.phagen_parameters,
self.spec_parameters)
self.detector_parameters = DetectorParameters(self.global_parameters,
self.phagen_parameters,
self.spec_parameters)
self.scan_parameters = ScanParameters(self.global_parameters,
self.phagen_parameters,
self.spec_parameters)
self.calculation_parameters = CalculationParameters(
self.global_parameters, self.phagen_parameters, self.spec_parameters)
# updated global parameters with provided keywords
self.global_parameters.spectroscopy = spectroscopy
self.global_parameters.algorithm = algorithm
self.global_parameters.polarization = polarization
self.global_parameters.dichroism = dichroism
self.global_parameters.spinpol = spinpol
self.global_parameters.folder = folder
self.phagenio = PhagenIO(self.phagen_parameters,
self.phagen_malloc_parameters)
self.specio = SpecIO(self.spec_parameters,
self.spec_malloc_parameters,
self.phagenio)
########################################################################
LOGGER.debug(' create a space dedicated to the calculation')
########################################################################
self.init_folder = os.getcwd()
self.msspec_folder = os.path.join(MSSPEC_ROOT)
self.tmp_folder = os.path.abspath(folder)
LOGGER.debug(' folder: \'%s\'', self.tmp_folder)
if not os.path.exists(self.tmp_folder):
os.makedirs(self.tmp_folder)
os.makedirs(os.path.join(self.tmp_folder, 'input'))
os.makedirs(os.path.join(self.tmp_folder, 'output'))
#copyfile(os.path.join(self.msspec_folder, 'ase', 'Makefile'),
# os.path.join(self.tmp_folder, 'Makefile'))
os.chdir(self.tmp_folder)
inv = cor = 'NO'
if algorithm == 'expansion':
pass
elif algorithm == 'inversion':
inv = 'YES'
elif algorithm == 'correlation':
cor = 'YES'
# spin orbit resolved (not yet)
sorb = 'NO'
# spin resolved
dichro_spinpol = False
if dichroism in ('sum_over_spin', 'spin_resolved'):
dichro_spinpol = True
spin = 'NO'
if spinpol or dichro_spinpol:
spin = 'YES'
if spin == 'YES':
LOGGER.error('Option not implemented!')
raise NotImplementedError(
'Spin polarization is not implemeted yet!')
calctype_spectro = self.spec_parameters.get_parameter('calctype_spectro')
calctype_spectro = calctype_spectro.value
self._make_opts = (MSSPEC_ROOT, calctype_spectro, inv, cor,
spin, sorb, self.tmp_folder)
# Initialize the working environment
#self._make('init')
self.modified = False
self.resources = {}
########################################################################
LOGGER.debug(' initialization done.\n')
########################################################################
def _make(self, target):
LOGGER.debug(get_call_info(inspect.currentframe()))
os.chdir(self.tmp_folder)
cmd = ("make__SPACE__ROOT_FOLDER=\"{}\"__SPACE__SPEC=\"{}\"__SPACE__INV=\"{}\"__SPACE__COR=\"{"
"}\"__SPACE__"
"SPIN=\"{}\"__SPACE__SO=\"{}\"__SPACE__CALC_FOLDER=\"{}\"__SPACE__{}").format(*(self._make_opts + (
target,))).split('__SPACE__')
#cmd = cmd.replace(' ', '\ ')
#cmd = cmd.split('__SPACE__')
LOGGER.debug(' the full command is: %s', cmd)
child = Popen(cmd,stdout=PIPE, stderr=PIPE)
logger_name = LOGGER.name
if target == 'tmatrix':
logger_name = 'Phagen'
elif target == 'compute':
logger_name = 'Spec'
log_process_output(child, logger=logger_name)
os.chdir(self.init_folder)
if child.returncode != 0:
LOGGER.error("Unable to successfully run the target: {}".format(target))
sys.exit(1)
def _guess_ke(self, level):
""" Try to guess the kinetic energy based on the level and
the source energy. If the kinetic energy cannot be infered
because the level is not reported in the database, the returned
value is None.
"""
try:
state = get_level_from_electron_configuration(level)
absorber_atomic_number = self.atoms[self.atoms.absorber].number
lines = electron_be[absorber_atomic_number]
binding_energy = lines[state]
except KeyError:
# unable to find a binding energy in the database
return None
# let's assume work function energy (in eV)
wf = 4.5
source_energy = self.source_parameters.get_parameter('energy').value
ke = source_energy - binding_energy - wf
#return np.array(ke, dtype=np.float).flatten()
return ke
def run_phagen(self):
#include_fname = os.path.join(self.tmp_folder, 'src/msxas3.inc')
input_fname = os.path.join(self.tmp_folder, 'input/input.ms')
#mod0 = self.phagenio.write_include_file(filename=include_fname)
mod1 = self.phagenio.write_input_file(filename=input_fname)
self.modified = self.modified or mod1 #or mod0 or mod1
if self.modified:
# run phagen
#self._make('tmatrix')
os.chdir(os.path.join(self.tmp_folder, 'output'))
do_phagen()
# rename some output files to be more explicit
os.rename('fort.10', 'cluster.clu')
os.rename('fort.35', 'tmatrix.tl')
os.rename('fort.55', 'tmatrix.rad')
def run_spec(self):
def get_li(level):
orbitals = 'spdfghi'
m = re.match(r'\d(?P<l>[%s])(\d/2)?' % orbitals, level)
return orbitals.index(m.group('l'))
#include_fname = os.path.join(self.tmp_folder, 'src/spec.inc')
input_fname = os.path.join(self.tmp_folder, 'input/spec.dat')
kdirs_fname = os.path.join(self.tmp_folder, 'input/kdirs.dat')
mod0 = self.specio.write_input_file(filename=input_fname)
#mod1 = self.specio.write_include_file(filename=include_fname)
mod2 = self.specio.write_kdirs_file(filename=kdirs_fname)
#self.modified = self.modified or mod0 or mod1 or mod2
self.modified = self.modified or mod0 or mod2
#self._make('tmatrix')
#self._make('bin/spec')
#t0 = time.time()
#self._make('compute')
#t1 = time.time()
#self.resources['spec_time'] = t1 - t0
if self.modified:
#self.get_tmatrix()
t0 = time.time()
os.chdir(os.path.join(self.tmp_folder, 'output'))
# set/get the dimension values
requirements = OrderedDict({
'NATP_M' : self.phagenio.nat,
'NATCLU_M' : len(self.atoms),
'NAT_EQ_M' : self.phagenio.nateqm,
'N_CL_L_M' : 1,
'NE_M' : self.phagenio.ne,
'NL_M' : self.phagenio.nlmax + 1,
'LI_M' : get_li(self.spec_parameters.extra_level),
'NEMET_M' : 1,
'NO_ST_M' : self.spec_parameters.calc_no,
'NDIF_M' : 10,
'NSO_M' : 2,
'NTEMP_M' : 1,
'NODES_EX_M' : 3,
'NSPIN_M' : 1, # to change for spin dependent
'NTH_M' : 2000,
'NPH_M' : 2000,
'NDIM_M' : 100000,
'N_TILT_M' : 11, # to change see extdir.f
'N_ORD_M' : 200,
'NPATH_M' : 500,
'NGR_M' : 10,})
for key, value in requirements.items():
setattr(self.spec_malloc_parameters, key, value)
do_spec(*requirements.values())
t1 = time.time()
self.resources['spec_time'] = t1 - t0
def get_tmatrix(self):
LOGGER.info("Getting the TMatrix...")
LOGGER.debug(get_call_info(inspect.currentframe()))
self.run_phagen()
filename = os.path.join(self.tmp_folder, 'output/tmatrix.tl')
tl = self.phagenio.load_tl_file(filename)
filename = os.path.join(self.tmp_folder, 'output/cluster.clu')
self.phagenio.load_cluster_file(filename)
tl_threshold = self.tmatrix_parameters.get_parameter('tl_threshold')
if tl_threshold.value != None:
LOGGER.debug(" applying tl_threshold to %s...",
tl_threshold.value)
go_on = True
while go_on:
go_on = False
for ia in range(self.phagenio.nat):
for ie in range(self.phagenio.ne):
last_tl = tl[ia][ie][-1, -2:]
# convert to complex
last_tl = last_tl[0] + last_tl[1] * 1j
if np.abs(last_tl) < tl_threshold.value:
# remove last line of tl
tl[ia][ie] = tl[ia][ie][:-1, :]
go_on = True
max_tl = self.tmatrix_parameters.get_parameter('max_tl').value
cluster = self.phagen_parameters.get_parameter('atoms').value
proto_indices = cluster.get_array('proto_indices')
if max_tl != None:
LOGGER.debug(" applying max_tl: %s", max_tl)
for ia in range(self.phagenio.nat):
for ie in range(self.phagenio.ne):
try:
# for each set of tl:
# 1. get the symbol of the prototipical atom
j = np.where(proto_indices == ia+1)
symbol = cluster[j][0].symbol
# 2. get the number of max tl allowed
ntl = max_tl[symbol]
# 3. reshape the tl set accordingly
tl[ia][ie] = tl[ia][ie][:ntl, :]
except KeyError:
pass
self.phagenio.write_tl_file(
os.path.join(self.tmp_folder, 'output/tmatrix.tl'))
# update spec extra parameters here
self.spec_parameters.set_parameter('extra_nat', self.phagenio.nat)
self.spec_parameters.set_parameter('extra_nlmax', self.phagenio.nlmax)
def set_atoms(self, atoms):
"""Defines the cluster on which the calculator will work.
:param atoms: The cluster to attach the calculator to.
:type atoms: :py:class:`ase.Atoms`
"""
if atoms.absorber == None:
LOGGER.error("You must define the absorber before setting the atoms to the"
"calculator.")
self.atoms = atoms
self.phagen_parameters.set_parameter('atoms', atoms)
self.spec_parameters.set_parameter('extra_atoms', atoms)
def get_parameters(self):
"""Get all the defined parameters in the calculator.
:return: A list of all parameters objects.
:rtype: List of :py:class:`parameters.Parameter`
"""
_ = []
for section in ('global', 'muffintin', 'tmatrix', 'spectroscopy',
'source', 'detector', 'scan', 'calculation'):
parameters = getattr(self, section + '_parameters')
for p in parameters:
_.append(p)
return _
def shutdown(self):
"""Removes the temporary folder and all its content.
The user may whish to keep the calculation folder (see :ref:`globalparameters-folder`) so it is not removed
at the end of the calculation. The calculation folder contains raw results from *Phagen* and *Spec* programs as
well as their input files and configuration. It allows the program to save some time by not repeating some
tasks (such as the Fortran code generation, building the binaries, computing things that did not changed
between runs...).
Calling this function at the end of the calculation will erase this calculation folder.
.. warning::
Calling this function will erase the produced data without prompting you for confirmation,
so take care of explicitly saving your results in your script, by using the
:py:func:`iodata.Data.save` method for example.
"""
LOGGER.info('Deleting temporary files...')
rmtree(self.tmp_folder)
class _PED(_MSCALCULATOR):
"""This class creates a calculator object for PhotoElectron DIffraction
spectroscopy.
:param algorithm: The algorithm to use for the computation. See
:ref:`globalparameters-algorithm` for more details about the allowed
values and the type.
:param polarization: The incoming light polarization (see
:ref:`globalparameters-polarization`)
:param dichroism: Wether to enable or not the dichroism (see
:ref:`globalparameters-dichroism`)
:param spinpol: Enable or disable the spin polarization in the calculation
(see :ref:`globalparameters-spinpol`)
:param folder: The path to the temporary folder for the calculations. See
:ref:`globalparameters-folder`
:param txt: The name of a file where to redirect standard output. The string
'-' will redirect the standard output to the screen (default).
:type txt: str
.. note::
This class constructor is not meant to be called directly by the user.
Use the :py:func:`MSSPEC` to instanciate any calculator.
"""
def __init__(self, algorithm='expansion', polarization=None, dichroism=None,
spinpol=False, folder='./calc', txt='-'):
_MSCALCULATOR.__init__(self, spectroscopy='PED', algorithm=algorithm,
polarization=polarization, dichroism=dichroism,
spinpol=spinpol, folder=folder, txt=txt)
self.iodata = iodata.Data('PED Simulation')
def _get_scan(self, scan_type='theta', phi=0,
theta=np.linspace(-70, 70, 141), level=None,
kinetic_energy=None, data=None):
LOGGER.info("Computting the %s scan...", scan_type)
if data:
self.iodata = data
if kinetic_energy is None:
# try to guess the kinetic energy
kinetic_energy = self._guess_ke(level)
# if still None...
if kinetic_energy is None:
LOGGER.error('Unable to guess the kinetic energy!')
raise ValueError('You must define a kinetic_energy value.')
# update the parameters
self.scan_parameters.set_parameter('kinetic_energy', kinetic_energy)
all_ke = self.scan_parameters.get_parameter('ke_array')
if np.any(all_ke.value < 0):
LOGGER.error('Source energy is not high enough or level too deep!')
raise ValueError('Kinetic energy is < 0! ({})'.format(
kinetic_energy))
self.scan_parameters.set_parameter('type', scan_type)
# make sure there is only one energy point in scatf scan
if scan_type == 'scatf':
assert len(all_ke) == 1, ('kinetic_energy should not be an array '
'in scatf scan')
if scan_type != 'scatf':
self.scan_parameters.set_parameter('phi', phi)
self.scan_parameters.set_parameter('theta', theta)
self.spectroscopy_parameters.set_parameter('level', level)
self.get_tmatrix()
self.run_spec()
# Now load the data
ndset = len(self.iodata)
dset = self.iodata.add_dset('{} scan [{:d}]'.format(scan_type, ndset))
for p in self.get_parameters():
bundle = {'group': str(p.group),
'name': str(p.name),
'value': str(p.value),
'unit': '' if p.unit is None else str(p.unit)}
dset.add_parameter(**bundle)
if scan_type in ('theta', 'phi', 'energy'):
results_fname = os.path.join(self.tmp_folder, 'output/results.dat')
data = self.specio.load_results(results_fname)
for _plane, _theta, _phi, _energy, _dirsig, _cs in data.T:
if _plane == -1:
dset.add_row(theta=_theta, phi=_phi, energy=_energy, cross_section=_cs, direct_signal=_dirsig)
elif scan_type in ('scatf',):
results_fname = os.path.join(self.tmp_folder, 'output/facdif1.dat')
data = self.specio.load_facdif(results_fname)
data = data[:, [1, 4, 5, 6, 8]].T
_proto, _sf_real, _sf_imag, _theta, _energy = data
_sf = _sf_real + _sf_imag * 1j
dset.add_columns(proto_index=_proto, sf_real=np.real(_sf),
sf_imag=np.imag(_sf), sf_module=np.abs(_sf),
theta=_theta, energy=_energy)
elif scan_type in ('theta_phi',):
results_fname = os.path.join(self.tmp_folder, 'output/results.dat')
data = self.specio.load_results(results_fname)
#theta_c, phi_c = data[[2, 3], :]
#xsec_c = data[-1, :]
#dirsig_c = data[-2, :]
#dset.add_columns(theta=theta_c)
#dset.add_columns(phi=phi_c)
#dset.add_columns(cross_section=xsec_c)
#dset.add_columns(direct_signal=dirsig_c)
for _plane, _theta, _phi, _energy, _dirsig, _cs in data.T:
if _plane == -1:
dset.add_row(theta=_theta, phi=_phi, energy=_energy, cross_section=_cs,
direct_signal=_dirsig)
# create a view
title = ''
for ke in all_ke.value:
if scan_type == 'theta':
absorber_symbol = self.atoms[self.atoms.absorber].symbol
title = 'Polar scan of {}({}) at {:.2f} eV'.format(
absorber_symbol, level, ke)
xlabel = r'Angle $\theta$($\degree$)'
ylabel = r'Signal (a. u.)'
view = dset.add_view("E = {:.2f} eV".format(ke), title=title,
xlabel=xlabel, ylabel=ylabel)
for angle_phi in self.scan_parameters.get_parameter(
'phi').value:
where = ("energy=={:.2f} and phi=={:.2f}"
"").format(ke, angle_phi)
legend = r'$\phi$ = {:.1f} $\degree$'.format(angle_phi)
view.select('theta', 'cross_section', where=where,
legend=legend)
if scan_type == 'phi':
absorber_symbol = self.atoms[self.atoms.absorber].symbol
title = 'Azimuthal scan of {}({}) at {:.2f} eV'.format(
absorber_symbol, level, ke)
xlabel = r'Angle $\phi$($\degree$)'
ylabel = r'Signal (a. u.)'
view = dset.add_view("E = {:.2f} eV".format(ke), title=title,
xlabel=xlabel, ylabel=ylabel)
for angle_theta in self.scan_parameters.get_parameter(
'theta').value:
where = ("energy=={:.2f} and theta=={:.2f}"
"").format(ke, angle_theta)
legend = r'$\theta$ = {:.1f} $\degree$'.format(angle_theta)
view.select('phi', 'cross_section', where=where,
legend=legend)
if scan_type == 'theta_phi':
absorber_symbol = self.atoms[self.atoms.absorber].symbol
title = ('Stereographic projection of {}({}) at {:.2f} eV'
'').format(absorber_symbol, level, ke)
xlabel = r'Angle $\phi$($\degree$)'
ylabel = r'Signal (a. u.)'
view = dset.add_view("E = {:.2f} eV".format(ke), title=title,
xlabel=xlabel, ylabel=ylabel,
projection='stereo', colorbar=True, autoscale=True)
view.select('theta', 'phi', 'cross_section')
if scan_type == 'scatf':
for i in range(self.phagenio.nat):
proto_index = i+1
title = 'Scattering factor at {:.3f} eV'.format(kinetic_energy)
view = dset.add_view("Proto. atom #{:d}".format(proto_index),
title=title, projection='polar')
where = "proto_index=={:d}".format(proto_index)
view.select('theta', 'sf_module', where=where,
legend=r'$|f(\theta)|$')
view.select('theta', 'sf_real', where=where,
legend=r'$\Im(f(\theta))$')
view.select('theta', 'sf_imag', where=where,
legend=r'$\Re(f(\theta))$')
# save the cluster
clusbuf = StringIO()
self.atoms.info['absorber'] = self.atoms.absorber
self.atoms.write(clusbuf, format='xyz')
dset.add_parameter(group='Cluster', name='cluster', value=clusbuf.getvalue(), hidden="True")
LOGGER.info('%s scan computing done!', scan_type)
return self.iodata
def get_potential(self, atom_index=None, data=None, units={'energy': 'eV', 'space': 'angstrom'}):
"""Computes the coulombic part of the atomic potential.
:param atom_index: The atom indices to get the potential of, either as a list or as a single integer
:param data: The data object to store the results to
:param units: The units to be used. A dictionary with the keys 'energy' and 'space'
:return: A Data object
"""
LOGGER.info("Getting the Potential...")
LOGGER.debug(get_call_info(inspect.currentframe()))
_units = {'energy': 'eV', 'space': 'angstrom'}
_units.update(units)
if data:
self.iodata = data
self.run_phagen()
filename = os.path.join(self.tmp_folder, 'output/tmatrix.tl')
tl = self.phagenio.load_tl_file(filename)
filename = os.path.join(self.tmp_folder, 'output/cluster.clu')
self.phagenio.load_cluster_file(filename)
filename = os.path.join(self.tmp_folder, 'bin/plot/plot_vc.dat')
pot_data = self.phagenio.load_potential_file(filename)
cluster = self.phagen_parameters.get_parameter('atoms').value
dset = self.iodata.add_dset('Potential [{:d}]'.format(len(self.iodata)))
r = []
v = []
index = np.empty((0,1), dtype=int)
absorber_position = cluster[cluster.absorber].position
for _pot_data in pot_data:
# find the proto index of these data
at_position = (_pot_data['coord'] * UREG.bohr_radius).to('angstrom').magnitude + absorber_position
at_index = get_atom_index(cluster, *at_position)
at_proto_index = cluster[at_index].get('proto_index')
#values = np.asarray(_pot_data['values'])
values = _pot_data['values']
index = np.append(index, np.ones(values.shape[0], dtype=int) * at_proto_index)
r = np.append(r, (values[:, 0] * UREG.bohr_radius).to(_units['space']).magnitude)
v = np.append(v, (values[:, 1] * UREG.rydberg).to(_units['energy']).magnitude)
dset.add_columns(distance=r, potential=v, index=index)
view = dset.add_view('potential data', title='Potential energy of atoms',
xlabel='distance from atomic center [{:s}]'.format(_units['space']),
ylabel='energy [{:s}]'.format(_units['energy']), scale='linear',
autoscale=True)
if atom_index == None:
for i in range(pot_data[len(pot_data) - 1]['index']):
view.select('distance', 'potential', where="index=={:d}".format(i),
legend="Atom index #{:d}".format(i + 1))
else:
for i in atom_index:
view.select('distance', 'potential', where="index=={:d}".format(cluster[i].get('proto_index') - 1),
legend="Atom index #{:d}".format(i))
return self.iodata
def get_scattering_factors(self, level='1s', kinetic_energy=None,
data=None):
"""Computes the scattering factors of all prototypical atoms in the
cluster.
This function computes the real and imaginery parts of the scattering
factor as well as its modulus for each non symetrically equivalent atom
in the cluster. The results are stored in the *data* object if provided
as a parameter.
:param level: The electronic level. See :ref:`pedparameters-level`.
:param kinetic_energy: see :ref:`scanparameters-kinetic_energy`.
:param data: a :py:class:`iodata.Data` object to append the results to
or None.
:returns: The modified :py:class:`iodata.Data` object passed as an
argument or a new :py:class:`iodata.Data` object.
"""
data = self._get_scan(scan_type='scatf', level=level, data=data,
kinetic_energy=kinetic_energy)
return data
def get_theta_scan(self, phi=0, theta=np.linspace(-70, 70, 141),
level=None, kinetic_energy=None, data=None):
"""Computes a polar scan of the emitted photoelectrons.
:param phi: The azimuthal angle in degrees. See
:ref:`scanparameters-phi`.
:param theta: All the values of the polar angle to be computed. See
:ref:`scanparameters-theta`.
:param level: The electronic level. See :ref:`pedparameters-level`.
:param kinetic_energy: see :ref:`scanparameters-kinetic_energy`.
:param data: a :py:class:`iodata.Data` object to append the results to
or None.
:returns: The modified :py:class:`iodata.Data` object passed as an
argument or a new :py:class:`iodata.Data` object.
"""
data = self._get_scan(scan_type='theta', level=level, theta=theta,
phi=phi, kinetic_energy=kinetic_energy, data=data)
return data
def get_phi_scan(self, phi=np.linspace(0, 359, 359), theta=0,
level=None, kinetic_energy=None, data=None):
"""Computes an azimuthal scan of the emitted photoelectrons.
:param phi: All the values of the azimuthal angle to be computed. See
:ref:`scanparameters-phi`.
:param theta: The polar angle in degrees. See
:ref:`scanparameters-theta`.
:param level: The electronic level. See :ref:`pedparameters-level`.
:param kinetic_energy: see :ref:`scanparameters-kinetic_energy`.
:param data: a :py:class:`iodata.Data` object to append the results to
or None.
:returns: The modified :py:class:`iodata.Data` object passed as an
argument or a new :py:class:`iodata.Data` object.
"""
data = self._get_scan(scan_type='phi', level=level, theta=theta,
phi=phi, kinetic_energy=kinetic_energy, data=data)
return data
def get_theta_phi_scan(self, phi=np.linspace(0, 360),
theta=np.linspace(0, 90, 45), level=None,
kinetic_energy=None, data=None):
"""Computes a stereographic scan of the emitted photoelectrons.
The azimuth ranges from 0 to 360° and the polar angle ranges from 0 to
90°.
:param level: The electronic level. See :ref:`pedparameters-level`.
:param kinetic_energy: see :ref:`scanparameters-kinetic_energy`.
:param data: a :py:class:`iodata.Data` object to append the results to
or None.
:returns: The modified :py:class:`iodata.Data` object passed as an
argument or a new :py:class:`iodata.Data` object.
"""
self.spec_malloc_parameters.NPH_M = 8000
data = self._get_scan(scan_type='theta_phi', level=level, theta=theta,
phi=phi, kinetic_energy=kinetic_energy, data=data)
return data
class _EIG(_MSCALCULATOR):
"""
.. note::
This class constructor is not meant to be called directly by the user.
Use the :py:func:`MSSPEC` to instanciate any calculator.
"""
def __init__(self, algorithm='inversion', polarization=None, dichroism=None,
spinpol=False, folder='./calc', txt='-'):
_MSCALCULATOR.__init__(self, spectroscopy='EIG', algorithm=algorithm,
polarization=polarization, dichroism=dichroism,
spinpol=spinpol, folder=folder, txt=txt)
if algorithm not in ('inversion', 'power'):
LOGGER.error("Only the 'inversion' or the 'power' algorithms "
"are supported in EIG spectroscopy mode")
exit(1)
self.iodata = iodata.Data('EIG Simulation')
def get_eigen_values(self, level=None, kinetic_energy=None, data=None):
LOGGER.info("Computting the eigen values...")
if data:
self.iodata = data
if kinetic_energy is None:
# try to guess the kinetic energy
kinetic_energy = self._guess_ke(level)
# if still None...
if kinetic_energy is None:
LOGGER.error('Unable to guess the kinetic energy!')
raise ValueError('You must define a kinetic_energy value.')
# update the parameters
self.scan_parameters.set_parameter('kinetic_energy', kinetic_energy)
all_ke = self.scan_parameters.get_parameter('ke_array')
if np.any(all_ke < 0):
LOGGER.error('Source energy is not high enough or level too deep!')
raise ValueError('Kinetic energy is < 0! ({})'.format(
kinetic_energy))
self.spectroscopy_parameters.set_parameter('level', level)
self.get_tmatrix()
self.run_spec()
# Now load the data
ndset = len(self.iodata)
dset = self.iodata.add_dset('Eigen values calculation [{:d}]'.format(ndset))
for p in self.get_parameters():
bundle = {'group': str(p.group),
'name': str(p.name),
'value': str(p.value),
'unit': '' if p.unit is None else str(p.unit)}
dset.add_parameter(**bundle)
results_fname = os.path.join(self.tmp_folder, 'output/results.dat')
data = self.specio.load_results(results_fname)
dset.add_columns(energy=data[:,0], eigen_value=data[:,1])
return self.iodata
def MSSPEC(spectroscopy='PED', **kwargs):
""" The MsSpec calculator constructor.
Instanciates a calculator for the given spectroscopy.
:param spectroscopy: See :ref:`globalparameters-spectroscopy`.
:param kwargs: Other keywords are passed to the spectroscopy-specific
constructor.
:returns: A :py:class:`calculator._MSCALCULATOR` object.
"""
module = sys.modules[__name__]
cls = getattr(module, '_' + spectroscopy)
return cls(**kwargs)
if __name__ == "__main__":
pass

118
src/msspec/config.py Normal file
View File

@ -0,0 +1,118 @@
# coding: utf-8
"""
Module config
=============
"""
from configparser import NoSectionError
from configparser import SafeConfigParser as ConfigParser
import os
import sys
import fnmatch
from msspec.misc import LOGGER
class NoConfigFile(Exception): pass
class Config(object):
def __init__(self, **kwargs):
self.fname = os.path.join(os.environ['HOME'], '.config/msspec/pymsspec.cfg')
self.version = sys.modules['msspec'].__version__
self.config = ConfigParser()
self.defaults = {'path': 'None'}
self.defaults.update(kwargs)
# try to load the config file, create one with defaults if none is found
try:
self.options = self.read()
self.options.update(kwargs)
self.set(**self.options)
self.write()
except NoConfigFile:
# write a file with default options
self.write(defaults=True)
except NoSectionError:
# the file exists but has no options for this version of pymsspec
self.config.add_section(self.version)
self.set(**self.defaults)
self.write()
def read(self):
fp = self.config.read(self.fname)
if len(fp) == 0:
raise NoConfigFile
self.version = self.config.get('general', 'version')
return dict(self.config.items(self.version))
def set(self, **kwargs):
if not(self.config.has_section(self.version)):
self.config.add_section(self.version)
for k, v in list(kwargs.items()):
self.config.set(self.version, k, v)
def get(self, key):
return self.config.get(self.version, key)
def choose_msspec_folder(self, *folders):
print("Several folders containing the appropriate version of MsSpec were found.")
print("Please choose one tu use:")
s = ""
for i, f in enumerate(folders):
s += '{:d}) {:s}\n'.format(i, f)
print(s)
return(folders[int(input('Your choice: '))])
def find_msspec_folders(self):
version = sys.modules['msspec'].__version__
folders = []
i = 0
prompt = 'Please wait while scanning the filesystem (%3d found) '
sys.stdout.write(prompt % len(folders))
sys.stdout.write('\033[s')
for root, dirnames, filenames in os.walk('/home/stricot'):
sys.stdout.write('\033[u\033[k')
sys.stdout.write('%d folders scanned' % i)
i += 1
for fn in fnmatch.filter(filenames, 'VERSION'):
with open(os.path.join(root, fn), 'r') as fd:
try:
line = fd.readline()
if line.strip() == version:
folders.append(root)
sys.stdout.write('\033[2000D\033[k')
sys.stdout.write(prompt % len(folders))
except:
pass
sys.stdout.write('\033[u\033[k\n')
print('Done.')
return(folders)
def write(self, defaults=False):
if defaults:
self.set(**self.defaults)
with open(self.fname, 'w') as fd:
self.config.write(fd)
LOGGER.info("{} file written".format(self.fname))
def set_mode(self, mode="pymsspec"):
if not(self.config.has_section("general")):
self.config.add_section("general")
self.config.set("general", "mode", str(mode))
def get_mode(self):
return self.config.get("general", "mode")
def set_version(self, version=""):
if not(self.config.has_section("general")):
self.config.add_section("general")
self.config.set("general", "version", version)
def remove_version(self, version):
pass

View File

@ -0,0 +1,5 @@
# -*- encoding: utf-8 -*-
# vim: set fdm=indent ts=4 sw=4 sts=4 et ai tw=80 cc=+0 mouse=a nu : #
from .electron_be import electron_be

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
# coding: utf-8
import unittest
import numpy
import delaunay.core as delc
from ase import Atoms
def Define_Domain(set,ncover,ratio) :
#defines the sphere englobing the molecule, such as we will have at least ncover empty spheres around the molecule,
#and this empty sphere have a size around ratio x {global size of muffin tin atom size}
def Distance(A,B)
#calculate distance between A and B
try :
assert len(A) == len(B)
d=0
for l in range(0,len(A))
d+=(B[l]-A[l])**2
d=sqrt(d)
except AssertionError :
print("Error : distance calculated from two points in different spaces")

View File

@ -0,0 +1,9 @@
# coding: utf-8
import unittest
import numpy
import delaunay.core as delc
from ase import Atoms
#=============================================

View File

@ -0,0 +1,2 @@
# coding: utf-8

469
src/msspec/es/README Normal file
View File

@ -0,0 +1,469 @@
# 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)
==============================================================

Binary file not shown.

View File

@ -0,0 +1,11 @@
PARAMETER(NATCLU_M=700,NLINE_M=10000,NP_M=50)
PARAMETER(NATP_M=700,NAT_EQ_M=64)
C
C NATCLU_M : MAXIMUM NUMBER OF ATOMS IN THE CLUSTER FILE
C NLINE_M : MAXIMUM NUMBER OF LINES IN THE CLUSTER FILE
C NP_M : MAXIMUM NUMBER OF PREAMBLE LINES IN THE
C phagen_scf. f INPUT FILE (BEFORE THE CLUSTER
C SECTION)
C NATP_M : MAXIMUM NUMBER OF PROTOTYPICAL ATOMS
C NAT_EQ_M : MAXIMUM NUMBER OF EQUIVALENT ATOMS
C

View File

@ -0,0 +1,73 @@
#! /bin/bash -f
clear
echo " "
echo " "
echo "****************************************************"
echo "* *"
echo "* CLUSTER GEOMETRY ANALYSIS CODE *"
echo "* *"
echo "****************************************************"
echo " "
echo " "
#
time -p ./clus_geom <<Fin >& error.dat
Test.xyz # Input cluster file
1 # Tetrahedra detection
1 # Octahedra detection
1 # Cube detection
1 # Hollow molecules detection
1 # Nanotube detection
1 # Regular polygons detection
1 # Iregular polygons detection
1 # Symmetries detection
Fin
#
# Checking for errors in the execution
#
cat error.dat | sed -e '1,35d' \
-e '/real/,/ /d' > error.txt
#
# Checking for a blend of dialog
#
DIAL=`which dialog | cut -d: -f2 | grep -c 'dialog'`
XDIA=`which Xdialog | cut -d: -f2 | grep -c 'Xdialog'`
KDIA=`which kdialog | cut -d: -f2 | grep -c 'kdialog'`
ZENI=`which zenity | cut -d: -f2 | grep -c 'zenity'`
#
if [ "$ZENI" -ne "0" ]; then
DIALOG=zenity
else
if [ "$XDIA" -ne "0" ]; then
DIALOG=Xdialog
else
if [ "$KDIA" -ne "0" ]; then
DIALOG=kdialog
else
if [ "$DIAL" -ne "0" ]; then
DIALOG=dialog
else
DIALOG=none
fi
fi
fi
fi
#
FILE=`ls -at | grep .lis | awk '{print $1}'`
tail --lines=10 $FILE | grep '<<<<' | sed 's/<</ /g' | sed 's/>>/ /g' >> run.txt
cat run.txt >> error.txt
ERR=`cat error.txt`
NLINE=`cat error.txt | wc -l`
#
if [ $NLINE != 0 ]; then
if [ "$DIALOG" = "zenity" ]; then
zenity --width 400 --height 180 \
--title "MsSpec-1.1 runtime error" \
--info --text "The code has stopped with the message : \n \n \n $ERR" \
--timeout 5
fi
fi
#
rm -f error.dat error.txt run.txt
#
exit

View File

View File

@ -0,0 +1,62 @@
60
C -0.70401 0.00110 3.57345
C 0.70083 0.00133 3.57327
C 3.57574 -0.70246 0.00129
C 3.57685 0.70239 0.00110
C 0.00074 3.57570 0.70219
C 0.00060 3.57460 -0.70265
C 0.70410 -0.00010 -3.57531
C -0.70080 -0.00043 -3.57472
C -3.57649 0.70168 -0.00040
C -3.57650 -0.70328 -0.00080
C -0.00022 -3.57603 -0.70253
C -0.00065 -3.57534 0.70260
C 1.45527 1.22059 3.10904
C 1.22007 3.11053 1.45642
C 3.11158 1.45695 1.22069
C -1.45773 1.22048 3.10909
C -3.11002 1.45543 1.21878
C -1.21916 3.10985 1.45535
C -1.45798 -1.21865 3.10901
C -3.11094 -1.45629 1.21895
C -1.22020 -3.10925 1.45579
C 1.45456 -1.21797 3.10872
C 1.21832 -3.10945 1.45706
C 3.10958 -1.45572 1.22084
C -1.45534 -1.22011 -3.11047
C -1.21944 -3.11066 -1.45668
C -3.10954 -1.45689 -1.22018
C 1.45725 -1.21982 -3.10992
C 3.10891 -1.45572 -1.21802
C 1.21907 -3.10943 -1.45581
C 1.45804 1.21938 -3.10976
C 3.11051 1.45522 -1.21855
C 1.22021 3.10873 -1.45574
C -1.45417 1.21854 -3.10850
C -1.21859 3.10885 -1.45655
C -3.10979 1.45514 -1.21977
C 0.75317 2.35757 2.67585
C 2.67579 0.75479 2.35684
C 2.35709 2.67579 0.75442
C -2.67816 0.75415 2.35674
C -0.75451 2.35720 2.67539
C -2.35486 2.67378 0.75265
C -2.67807 -0.75360 2.35625
C -0.75519 -2.35526 2.67525
C -2.35673 -2.67557 0.75321
C 0.75208 -2.35504 2.67576
C 2.35468 -2.67447 0.75491
C 2.67533 -0.75265 2.35696
C -0.75325 -2.35679 -2.67619
C -2.67475 -0.75438 -2.35653
C -2.35590 -2.67596 -0.75411
C 2.67648 -0.75411 -2.35574
C 0.75400 -2.35630 -2.67588
C 2.35436 -2.67411 -0.75219
C 2.67749 0.75328 -2.35614
C 0.75540 2.35626 -2.67613
C 2.35610 2.67388 -0.75278
C -0.75218 2.35599 -2.67618
C -2.35517 2.67402 -0.75419
C -2.67390 0.75268 -2.35549

View File

@ -0,0 +1,7 @@
5
Properties=species:S:1:pos:R:3:Z:I:1 pbc="F F F"
Ge 0.0000 0.0000 0.0000 32
Cl 1.2100 1.2100 1.2100 17
Cl 1.2100 -1.2100 -1.2100 17
Cl -1.2100 1.2100 -1.2100 17
Cl -1.2100 -1.2100 1.2100 17

View File

@ -0,0 +1,5 @@
3
Properties=species:S:1:pos:R:3:Z:I:1 pbc="F F F"
O 0.00000000 0.00000000 0.11926200 8
H 0.00000000 0.76323900 -0.47704700 1
H 0.00000000 -0.76323900 -0.47704700 1

View File

@ -0,0 +1,6 @@
4
Ammonia
N 0.257 -0.363 0.000
H 0.257 0.727 0.000
H 0.771 -0.727 0.890
H 0.771 -0.727 -0.890

View File

@ -0,0 +1,16 @@
14
Properties=species:S:1:pos:R:3:Z:I:1 pbc="F F F"
C 0.00000000 -1.00000000 0.00000000 6
C 0.00000000 1.00000000 0.00000000 6
C 0.00000000 0.00000000 1.00000000 6
C 0.00000000 0.00000000 -1.00000000 6
C 1.00000000 0.00000000 0.00000000 6
C -1.00000000 0.00000000 0.00000000 6
C -1.00000000 -1.00000000 -1.00000000 6
C 1.00000000 -1.00000000 -1.00000000 6
C -1.00000000 1.00000000 -1.00000000 6
C 1.00000000 1.00000000 -1.00000000 6
C -1.00000000 -1.00000000 1.00000000 6
C 1.00000000 -1.00000000 1.00000000 6
C -1.00000000 1.00000000 1.00000000 6
C 1.00000000 1.00000000 1.00000000 6

View File

@ -0,0 +1,85 @@
83
Lattice="36.0 0.0 0.0 0.0 36.0 0.0 0.0 0.0 36.0" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T"
Cu -5.40000000 -3.60000000 -1.80000000 29
Cu -5.40000000 -1.80000000 -3.60000000 29
Cu -5.40000000 -1.80000000 0.00000000 29
Cu -5.40000000 0.00000000 -1.80000000 29
Cu -5.40000000 1.80000000 -3.60000000 29
Cu -7.20000000 0.00000000 0.00000000 29
Cu -5.40000000 1.80000000 0.00000000 29
Cu -5.40000000 3.60000000 -1.80000000 29
Cu -3.60000000 -5.40000000 -1.80000000 29
Cu -1.80000000 -5.40000000 -3.60000000 29
Cu -1.80000000 -5.40000000 0.00000000 29
Cu -3.60000000 -1.80000000 -5.40000000 29
Cu -1.80000000 -3.60000000 -5.40000000 29
Cu -3.60000000 -3.60000000 -3.60000000 29
Cu -3.60000000 -1.80000000 -1.80000000 29
Cu -1.80000000 -3.60000000 -1.80000000 29
Cu -1.80000000 -1.80000000 -3.60000000 29
Cu -3.60000000 -3.60000000 0.00000000 29
Cu -1.80000000 -1.80000000 0.00000000 29
Cu -3.60000000 1.80000000 -5.40000000 29
Cu -1.80000000 0.00000000 -5.40000000 29
Cu -3.60000000 0.00000000 -3.60000000 29
Cu -3.60000000 1.80000000 -1.80000000 29
Cu -1.80000000 0.00000000 -1.80000000 29
Cu -1.80000000 1.80000000 -3.60000000 29
Cu -3.60000000 0.00000000 0.00000000 29
Cu -1.80000000 1.80000000 0.00000000 29
Cu -1.80000000 3.60000000 -5.40000000 29
Cu -3.60000000 3.60000000 -3.60000000 29
Cu -3.60000000 5.40000000 -1.80000000 29
Cu -1.80000000 3.60000000 -1.80000000 29
Cu -1.80000000 5.40000000 -3.60000000 29
Cu -3.60000000 3.60000000 0.00000000 29
Cu -1.80000000 5.40000000 0.00000000 29
Cu 0.00000000 -5.40000000 -1.80000000 29
Cu 1.80000000 -5.40000000 -3.60000000 29
Cu 0.00000000 -7.20000000 0.00000000 29
Cu 1.80000000 -5.40000000 0.00000000 29
Cu 0.00000000 -1.80000000 -5.40000000 29
Cu 1.80000000 -3.60000000 -5.40000000 29
Cu 0.00000000 -3.60000000 -3.60000000 29
Cu 0.00000000 -1.80000000 -1.80000000 29
Cu 1.80000000 -3.60000000 -1.80000000 29
Cu 1.80000000 -1.80000000 -3.60000000 29
Cu 0.00000000 -3.60000000 0.00000000 29
Cu 1.80000000 -1.80000000 0.00000000 29
Cu 0.00000000 0.00000000 -7.20000000 29
Cu 0.00000000 1.80000000 -5.40000000 29
Cu 1.80000000 0.00000000 -5.40000000 29
Cu 0.00000000 0.00000000 -3.60000000 29
Cu 0.00000000 1.80000000 -1.80000000 29
Cu 1.80000000 0.00000000 -1.80000000 29
Cu 1.80000000 1.80000000 -3.60000000 29
Cu 0.00000000 0.00000000 0.00000000 29
Cu 1.80000000 1.80000000 0.00000000 29
Cu 1.80000000 3.60000000 -5.40000000 29
Cu 0.00000000 3.60000000 -3.60000000 29
Cu 0.00000000 5.40000000 -1.80000000 29
Cu 1.80000000 3.60000000 -1.80000000 29
Cu 1.80000000 5.40000000 -3.60000000 29
Cu 0.00000000 3.60000000 0.00000000 29
Cu 1.80000000 5.40000000 0.00000000 29
Cu 0.00000000 7.20000000 0.00000000 29
Cu 3.60000000 -5.40000000 -1.80000000 29
Cu 3.60000000 -1.80000000 -5.40000000 29
Cu 3.60000000 -3.60000000 -3.60000000 29
Cu 3.60000000 -1.80000000 -1.80000000 29
Cu 5.40000000 -3.60000000 -1.80000000 29
Cu 5.40000000 -1.80000000 -3.60000000 29
Cu 3.60000000 -3.60000000 0.00000000 29
Cu 5.40000000 -1.80000000 0.00000000 29
Cu 3.60000000 1.80000000 -5.40000000 29
Cu 3.60000000 0.00000000 -3.60000000 29
Cu 3.60000000 1.80000000 -1.80000000 29
Cu 5.40000000 0.00000000 -1.80000000 29
Cu 5.40000000 1.80000000 -3.60000000 29
Cu 3.60000000 0.00000000 0.00000000 29
Cu 5.40000000 1.80000000 0.00000000 29
Cu 3.60000000 3.60000000 -3.60000000 29
Cu 3.60000000 5.40000000 -1.80000000 29
Cu 5.40000000 3.60000000 -1.80000000 29
Cu 3.60000000 3.60000000 0.00000000 29
Cu 7.20000000 0.00000000 0.00000000 29

View File

@ -0,0 +1,146 @@
144
Lattice="28.700000000000003 0.0 0.0 0.0 28.700000000000003 0.0 0.0 0.0 28.700000000000003" Properties=species:S:1:pos:R:3:Z:I:1 pbc="T T T"
Fe -7.17500000 -4.30500000 -1.43500000 26
Fe -7.17500000 -1.43500000 -4.30500000 26
Fe -7.17500000 -1.43500000 -1.43500000 26
Fe -7.17500000 1.43500000 -4.30500000 26
Fe -7.17500000 1.43500000 -1.43500000 26
Fe -8.61000000 0.00000000 0.00000000 26
Fe -7.17500000 4.30500000 -1.43500000 26
Fe -4.30500000 -7.17500000 -1.43500000 26
Fe -4.30500000 -4.30500000 -4.30500000 26
Fe -5.74000000 -5.74000000 -2.87000000 26
Fe -4.30500000 -4.30500000 -1.43500000 26
Fe -5.74000000 -5.74000000 0.00000000 26
Fe -4.30500000 -1.43500000 -7.17500000 26
Fe -5.74000000 -2.87000000 -5.74000000 26
Fe -4.30500000 -1.43500000 -4.30500000 26
Fe -5.74000000 -2.87000000 -2.87000000 26
Fe -4.30500000 -1.43500000 -1.43500000 26
Fe -5.74000000 -2.87000000 0.00000000 26
Fe -4.30500000 1.43500000 -7.17500000 26
Fe -5.74000000 0.00000000 -5.74000000 26
Fe -4.30500000 1.43500000 -4.30500000 26
Fe -5.74000000 0.00000000 -2.87000000 26
Fe -4.30500000 1.43500000 -1.43500000 26
Fe -5.74000000 0.00000000 0.00000000 26
Fe -5.74000000 2.87000000 -5.74000000 26
Fe -4.30500000 4.30500000 -4.30500000 26
Fe -5.74000000 2.87000000 -2.87000000 26
Fe -4.30500000 4.30500000 -1.43500000 26
Fe -5.74000000 2.87000000 0.00000000 26
Fe -5.74000000 5.74000000 -2.87000000 26
Fe -4.30500000 7.17500000 -1.43500000 26
Fe -5.74000000 5.74000000 0.00000000 26
Fe -1.43500000 -7.17500000 -4.30500000 26
Fe -1.43500000 -7.17500000 -1.43500000 26
Fe -1.43500000 -4.30500000 -7.17500000 26
Fe -2.87000000 -5.74000000 -5.74000000 26
Fe -1.43500000 -4.30500000 -4.30500000 26
Fe -2.87000000 -5.74000000 -2.87000000 26
Fe -1.43500000 -4.30500000 -1.43500000 26
Fe -2.87000000 -5.74000000 0.00000000 26
Fe -1.43500000 -1.43500000 -7.17500000 26
Fe -2.87000000 -2.87000000 -5.74000000 26
Fe -1.43500000 -1.43500000 -4.30500000 26
Fe -2.87000000 -2.87000000 -2.87000000 26
Fe -1.43500000 -1.43500000 -1.43500000 26
Fe -2.87000000 -2.87000000 0.00000000 26
Fe -1.43500000 1.43500000 -7.17500000 26
Fe -2.87000000 0.00000000 -5.74000000 26
Fe -1.43500000 1.43500000 -4.30500000 26
Fe -2.87000000 0.00000000 -2.87000000 26
Fe -1.43500000 1.43500000 -1.43500000 26
Fe -2.87000000 0.00000000 0.00000000 26
Fe -1.43500000 4.30500000 -7.17500000 26
Fe -2.87000000 2.87000000 -5.74000000 26
Fe -1.43500000 4.30500000 -4.30500000 26
Fe -2.87000000 2.87000000 -2.87000000 26
Fe -1.43500000 4.30500000 -1.43500000 26
Fe -2.87000000 2.87000000 0.00000000 26
Fe -2.87000000 5.74000000 -5.74000000 26
Fe -1.43500000 7.17500000 -4.30500000 26
Fe -2.87000000 5.74000000 -2.87000000 26
Fe -1.43500000 7.17500000 -1.43500000 26
Fe -2.87000000 5.74000000 0.00000000 26
Fe 1.43500000 -7.17500000 -4.30500000 26
Fe 1.43500000 -7.17500000 -1.43500000 26
Fe 0.00000000 -8.61000000 0.00000000 26
Fe 1.43500000 -4.30500000 -7.17500000 26
Fe 0.00000000 -5.74000000 -5.74000000 26
Fe 1.43500000 -4.30500000 -4.30500000 26
Fe 0.00000000 -5.74000000 -2.87000000 26
Fe 1.43500000 -4.30500000 -1.43500000 26
Fe 0.00000000 -5.74000000 0.00000000 26
Fe 1.43500000 -1.43500000 -7.17500000 26
Fe 0.00000000 -2.87000000 -5.74000000 26
Fe 1.43500000 -1.43500000 -4.30500000 26
Fe 0.00000000 -2.87000000 -2.87000000 26
Fe 1.43500000 -1.43500000 -1.43500000 26
Fe 0.00000000 -2.87000000 0.00000000 26
Fe 0.00000000 0.00000000 -8.61000000 26
Fe 1.43500000 1.43500000 -7.17500000 26
Fe 0.00000000 0.00000000 -5.74000000 26
Fe 1.43500000 1.43500000 -4.30500000 26
Fe 0.00000000 0.00000000 -2.87000000 26
Fe 1.43500000 1.43500000 -1.43500000 26
Fe 0.00000000 0.00000000 0.00000000 26
Fe 1.43500000 4.30500000 -7.17500000 26
Fe 0.00000000 2.87000000 -5.74000000 26
Fe 1.43500000 4.30500000 -4.30500000 26
Fe 0.00000000 2.87000000 -2.87000000 26
Fe 1.43500000 4.30500000 -1.43500000 26
Fe 0.00000000 2.87000000 0.00000000 26
Fe 0.00000000 5.74000000 -5.74000000 26
Fe 1.43500000 7.17500000 -4.30500000 26
Fe 0.00000000 5.74000000 -2.87000000 26
Fe 1.43500000 7.17500000 -1.43500000 26
Fe 0.00000000 5.74000000 0.00000000 26
Fe 0.00000000 8.61000000 0.00000000 26
Fe 4.30500000 -7.17500000 -1.43500000 26
Fe 2.87000000 -5.74000000 -5.74000000 26
Fe 4.30500000 -4.30500000 -4.30500000 26
Fe 2.87000000 -5.74000000 -2.87000000 26
Fe 4.30500000 -4.30500000 -1.43500000 26
Fe 2.87000000 -5.74000000 0.00000000 26
Fe 4.30500000 -1.43500000 -7.17500000 26
Fe 2.87000000 -2.87000000 -5.74000000 26
Fe 4.30500000 -1.43500000 -4.30500000 26
Fe 2.87000000 -2.87000000 -2.87000000 26
Fe 4.30500000 -1.43500000 -1.43500000 26
Fe 2.87000000 -2.87000000 0.00000000 26
Fe 4.30500000 1.43500000 -7.17500000 26
Fe 2.87000000 0.00000000 -5.74000000 26
Fe 4.30500000 1.43500000 -4.30500000 26
Fe 2.87000000 0.00000000 -2.87000000 26
Fe 4.30500000 1.43500000 -1.43500000 26
Fe 2.87000000 0.00000000 0.00000000 26
Fe 2.87000000 2.87000000 -5.74000000 26
Fe 4.30500000 4.30500000 -4.30500000 26
Fe 2.87000000 2.87000000 -2.87000000 26
Fe 4.30500000 4.30500000 -1.43500000 26
Fe 2.87000000 2.87000000 0.00000000 26
Fe 2.87000000 5.74000000 -5.74000000 26
Fe 2.87000000 5.74000000 -2.87000000 26
Fe 4.30500000 7.17500000 -1.43500000 26
Fe 2.87000000 5.74000000 0.00000000 26
Fe 5.74000000 -5.74000000 -2.87000000 26
Fe 7.17500000 -4.30500000 -1.43500000 26
Fe 5.74000000 -5.74000000 0.00000000 26
Fe 5.74000000 -2.87000000 -5.74000000 26
Fe 7.17500000 -1.43500000 -4.30500000 26
Fe 5.74000000 -2.87000000 -2.87000000 26
Fe 7.17500000 -1.43500000 -1.43500000 26
Fe 5.74000000 -2.87000000 0.00000000 26
Fe 5.74000000 0.00000000 -5.74000000 26
Fe 7.17500000 1.43500000 -4.30500000 26
Fe 5.74000000 0.00000000 -2.87000000 26
Fe 7.17500000 1.43500000 -1.43500000 26
Fe 5.74000000 0.00000000 0.00000000 26
Fe 5.74000000 2.87000000 -5.74000000 26
Fe 5.74000000 2.87000000 -2.87000000 26
Fe 7.17500000 4.30500000 -1.43500000 26
Fe 5.74000000 2.87000000 0.00000000 26
Fe 5.74000000 5.74000000 -2.87000000 26
Fe 5.74000000 5.74000000 0.00000000 26
Fe 8.61000000 0.00000000 0.00000000 26

View File

@ -0,0 +1,786 @@
784
Lattice="4.21 0.0 0.0 0.0 4.21 0.0 0.0 0.0 4.21" Properties=species:S:1:pos:R:3:Z:I:1:tags:I:1 pbc="T T T"
O -12.63000000 -4.21000000 -4.21000000 8 0
Mg -12.63000000 -6.31500000 -4.21000000 12 1
Mg -12.63000000 -4.21000000 -6.31500000 12 1
O -12.63000000 -6.31500000 -2.10500000 8 0
O -12.63000000 -4.21000000 0.00000000 8 0
Mg -12.63000000 -6.31500000 0.00000000 12 1
Mg -12.63000000 -4.21000000 -2.10500000 12 1
O -12.63000000 -2.10500000 -6.31500000 8 0
O -12.63000000 0.00000000 -4.21000000 8 0
Mg -12.63000000 -2.10500000 -4.21000000 12 1
Mg -12.63000000 0.00000000 -6.31500000 12 1
O -12.63000000 -2.10500000 -2.10500000 8 0
Mg -14.73500000 0.00000000 0.00000000 12 1
O -12.63000000 0.00000000 0.00000000 8 0
Mg -12.63000000 -2.10500000 0.00000000 12 1
Mg -12.63000000 0.00000000 -2.10500000 12 1
O -12.63000000 2.10500000 -6.31500000 8 0
O -12.63000000 4.21000000 -4.21000000 8 0
Mg -12.63000000 2.10500000 -4.21000000 12 1
Mg -12.63000000 4.21000000 -6.31500000 12 1
O -12.63000000 2.10500000 -2.10500000 8 0
O -12.63000000 4.21000000 0.00000000 8 0
Mg -12.63000000 2.10500000 0.00000000 12 1
Mg -12.63000000 4.21000000 -2.10500000 12 1
Mg -12.63000000 6.31500000 -4.21000000 12 1
O -12.63000000 6.31500000 -2.10500000 8 0
Mg -12.63000000 6.31500000 0.00000000 12 1
O -8.42000000 -8.42000000 -8.42000000 8 0
Mg -10.52500000 -8.42000000 -4.21000000 12 1
O -8.42000000 -8.42000000 -4.21000000 8 0
Mg -8.42000000 -10.52500000 -4.21000000 12 1
Mg -8.42000000 -8.42000000 -6.31500000 12 1
O -8.42000000 -10.52500000 -2.10500000 8 0
Mg -10.52500000 -8.42000000 0.00000000 12 1
O -8.42000000 -8.42000000 0.00000000 8 0
Mg -8.42000000 -10.52500000 0.00000000 12 1
Mg -8.42000000 -8.42000000 -2.10500000 12 1
O -10.52500000 -8.42000000 -2.10500000 8 0
Mg -10.52500000 -4.21000000 -8.42000000 12 1
O -8.42000000 -4.21000000 -8.42000000 8 0
Mg -8.42000000 -6.31500000 -8.42000000 12 1
Mg -8.42000000 -4.21000000 -10.52500000 12 1
Mg -10.52500000 -6.31500000 -6.31500000 12 1
O -8.42000000 -6.31500000 -6.31500000 8 0
Mg -10.52500000 -4.21000000 -4.21000000 12 1
O -8.42000000 -4.21000000 -4.21000000 8 0
Mg -8.42000000 -6.31500000 -4.21000000 12 1
O -10.52500000 -6.31500000 -4.21000000 8 0
Mg -8.42000000 -4.21000000 -6.31500000 12 1
O -10.52500000 -4.21000000 -6.31500000 8 0
Mg -10.52500000 -6.31500000 -2.10500000 12 1
O -8.42000000 -6.31500000 -2.10500000 8 0
Mg -10.52500000 -4.21000000 0.00000000 12 1
O -8.42000000 -4.21000000 0.00000000 8 0
Mg -8.42000000 -6.31500000 0.00000000 12 1
O -10.52500000 -6.31500000 0.00000000 8 0
Mg -8.42000000 -4.21000000 -2.10500000 12 1
O -10.52500000 -4.21000000 -2.10500000 8 0
O -8.42000000 -2.10500000 -10.52500000 8 0
Mg -10.52500000 0.00000000 -8.42000000 12 1
O -8.42000000 0.00000000 -8.42000000 8 0
Mg -8.42000000 -2.10500000 -8.42000000 12 1
O -10.52500000 -2.10500000 -8.42000000 8 0
Mg -8.42000000 0.00000000 -10.52500000 12 1
Mg -10.52500000 -2.10500000 -6.31500000 12 1
O -8.42000000 -2.10500000 -6.31500000 8 0
Mg -10.52500000 0.00000000 -4.21000000 12 1
O -8.42000000 0.00000000 -4.21000000 8 0
Mg -8.42000000 -2.10500000 -4.21000000 12 1
O -10.52500000 -2.10500000 -4.21000000 8 0
Mg -8.42000000 0.00000000 -6.31500000 12 1
O -10.52500000 0.00000000 -6.31500000 8 0
Mg -10.52500000 -2.10500000 -2.10500000 12 1
O -8.42000000 -2.10500000 -2.10500000 8 0
Mg -10.52500000 0.00000000 0.00000000 12 1
O -8.42000000 0.00000000 0.00000000 8 0
Mg -8.42000000 -2.10500000 0.00000000 12 1
O -10.52500000 -2.10500000 0.00000000 8 0
Mg -8.42000000 0.00000000 -2.10500000 12 1
O -10.52500000 0.00000000 -2.10500000 8 0
O -8.42000000 2.10500000 -10.52500000 8 0
Mg -10.52500000 4.21000000 -8.42000000 12 1
O -8.42000000 4.21000000 -8.42000000 8 0
Mg -8.42000000 2.10500000 -8.42000000 12 1
O -10.52500000 2.10500000 -8.42000000 8 0
Mg -8.42000000 4.21000000 -10.52500000 12 1
Mg -10.52500000 2.10500000 -6.31500000 12 1
O -8.42000000 2.10500000 -6.31500000 8 0
Mg -10.52500000 4.21000000 -4.21000000 12 1
O -8.42000000 4.21000000 -4.21000000 8 0
Mg -8.42000000 2.10500000 -4.21000000 12 1
O -10.52500000 2.10500000 -4.21000000 8 0
Mg -8.42000000 4.21000000 -6.31500000 12 1
O -10.52500000 4.21000000 -6.31500000 8 0
Mg -10.52500000 2.10500000 -2.10500000 12 1
O -8.42000000 2.10500000 -2.10500000 8 0
Mg -10.52500000 4.21000000 0.00000000 12 1
O -8.42000000 4.21000000 0.00000000 8 0
Mg -8.42000000 2.10500000 0.00000000 12 1
O -10.52500000 2.10500000 0.00000000 8 0
Mg -8.42000000 4.21000000 -2.10500000 12 1
O -10.52500000 4.21000000 -2.10500000 8 0
O -8.42000000 8.42000000 -8.42000000 8 0
Mg -8.42000000 6.31500000 -8.42000000 12 1
Mg -10.52500000 6.31500000 -6.31500000 12 1
O -8.42000000 6.31500000 -6.31500000 8 0
Mg -10.52500000 8.42000000 -4.21000000 12 1
O -8.42000000 8.42000000 -4.21000000 8 0
Mg -8.42000000 6.31500000 -4.21000000 12 1
O -10.52500000 6.31500000 -4.21000000 8 0
Mg -8.42000000 8.42000000 -6.31500000 12 1
Mg -10.52500000 6.31500000 -2.10500000 12 1
O -8.42000000 6.31500000 -2.10500000 8 0
Mg -10.52500000 8.42000000 0.00000000 12 1
O -8.42000000 8.42000000 0.00000000 8 0
Mg -8.42000000 6.31500000 0.00000000 12 1
O -10.52500000 6.31500000 0.00000000 8 0
Mg -8.42000000 8.42000000 -2.10500000 12 1
O -10.52500000 8.42000000 -2.10500000 8 0
Mg -8.42000000 10.52500000 -4.21000000 12 1
O -8.42000000 10.52500000 -2.10500000 8 0
Mg -8.42000000 10.52500000 0.00000000 12 1
Mg -6.31500000 -12.63000000 -4.21000000 12 1
O -4.21000000 -12.63000000 -4.21000000 8 0
Mg -4.21000000 -12.63000000 -6.31500000 12 1
Mg -6.31500000 -12.63000000 0.00000000 12 1
O -4.21000000 -12.63000000 0.00000000 8 0
Mg -4.21000000 -12.63000000 -2.10500000 12 1
O -6.31500000 -12.63000000 -2.10500000 8 0
Mg -6.31500000 -8.42000000 -8.42000000 12 1
O -4.21000000 -8.42000000 -8.42000000 8 0
Mg -4.21000000 -10.52500000 -8.42000000 12 1
Mg -4.21000000 -8.42000000 -10.52500000 12 1
Mg -6.31500000 -10.52500000 -6.31500000 12 1
O -4.21000000 -10.52500000 -6.31500000 8 0
Mg -6.31500000 -8.42000000 -4.21000000 12 1
O -4.21000000 -8.42000000 -4.21000000 8 0
Mg -4.21000000 -10.52500000 -4.21000000 12 1
O -6.31500000 -10.52500000 -4.21000000 8 0
Mg -4.21000000 -8.42000000 -6.31500000 12 1
O -6.31500000 -8.42000000 -6.31500000 8 0
Mg -6.31500000 -10.52500000 -2.10500000 12 1
O -4.21000000 -10.52500000 -2.10500000 8 0
Mg -6.31500000 -8.42000000 0.00000000 12 1
O -4.21000000 -8.42000000 0.00000000 8 0
Mg -4.21000000 -10.52500000 0.00000000 12 1
O -6.31500000 -10.52500000 0.00000000 8 0
Mg -4.21000000 -8.42000000 -2.10500000 12 1
O -6.31500000 -8.42000000 -2.10500000 8 0
Mg -6.31500000 -4.21000000 -12.63000000 12 1
O -4.21000000 -4.21000000 -12.63000000 8 0
Mg -4.21000000 -6.31500000 -12.63000000 12 1
Mg -6.31500000 -6.31500000 -10.52500000 12 1
O -4.21000000 -6.31500000 -10.52500000 8 0
Mg -6.31500000 -4.21000000 -8.42000000 12 1
O -4.21000000 -4.21000000 -8.42000000 8 0
Mg -4.21000000 -6.31500000 -8.42000000 12 1
O -6.31500000 -6.31500000 -8.42000000 8 0
Mg -4.21000000 -4.21000000 -10.52500000 12 1
O -6.31500000 -4.21000000 -10.52500000 8 0
Mg -6.31500000 -6.31500000 -6.31500000 12 1
O -4.21000000 -6.31500000 -6.31500000 8 0
Mg -6.31500000 -4.21000000 -4.21000000 12 1
O -4.21000000 -4.21000000 -4.21000000 8 0
Mg -4.21000000 -6.31500000 -4.21000000 12 1
O -6.31500000 -6.31500000 -4.21000000 8 0
Mg -4.21000000 -4.21000000 -6.31500000 12 1
O -6.31500000 -4.21000000 -6.31500000 8 0
Mg -6.31500000 -6.31500000 -2.10500000 12 1
O -4.21000000 -6.31500000 -2.10500000 8 0
Mg -6.31500000 -4.21000000 0.00000000 12 1
O -4.21000000 -4.21000000 0.00000000 8 0
Mg -4.21000000 -6.31500000 0.00000000 12 1
O -6.31500000 -6.31500000 0.00000000 8 0
Mg -4.21000000 -4.21000000 -2.10500000 12 1
O -6.31500000 -4.21000000 -2.10500000 8 0
Mg -6.31500000 0.00000000 -12.63000000 12 1
O -4.21000000 0.00000000 -12.63000000 8 0
Mg -4.21000000 -2.10500000 -12.63000000 12 1
O -6.31500000 -2.10500000 -12.63000000 8 0
Mg -6.31500000 -2.10500000 -10.52500000 12 1
O -4.21000000 -2.10500000 -10.52500000 8 0
Mg -6.31500000 0.00000000 -8.42000000 12 1
O -4.21000000 0.00000000 -8.42000000 8 0
Mg -4.21000000 -2.10500000 -8.42000000 12 1
O -6.31500000 -2.10500000 -8.42000000 8 0
Mg -4.21000000 0.00000000 -10.52500000 12 1
O -6.31500000 0.00000000 -10.52500000 8 0
Mg -6.31500000 -2.10500000 -6.31500000 12 1
O -4.21000000 -2.10500000 -6.31500000 8 0
Mg -6.31500000 0.00000000 -4.21000000 12 1
O -4.21000000 0.00000000 -4.21000000 8 0
Mg -4.21000000 -2.10500000 -4.21000000 12 1
O -6.31500000 -2.10500000 -4.21000000 8 0
Mg -4.21000000 0.00000000 -6.31500000 12 1
O -6.31500000 0.00000000 -6.31500000 8 0
Mg -6.31500000 -2.10500000 -2.10500000 12 1
O -4.21000000 -2.10500000 -2.10500000 8 0
Mg -6.31500000 0.00000000 0.00000000 12 1
O -4.21000000 0.00000000 0.00000000 8 0
Mg -4.21000000 -2.10500000 0.00000000 12 1
O -6.31500000 -2.10500000 0.00000000 8 0
Mg -4.21000000 0.00000000 -2.10500000 12 1
O -6.31500000 0.00000000 -2.10500000 8 0
Mg -6.31500000 4.21000000 -12.63000000 12 1
O -4.21000000 4.21000000 -12.63000000 8 0
Mg -4.21000000 2.10500000 -12.63000000 12 1
O -6.31500000 2.10500000 -12.63000000 8 0
Mg -6.31500000 2.10500000 -10.52500000 12 1
O -4.21000000 2.10500000 -10.52500000 8 0
Mg -6.31500000 4.21000000 -8.42000000 12 1
O -4.21000000 4.21000000 -8.42000000 8 0
Mg -4.21000000 2.10500000 -8.42000000 12 1
O -6.31500000 2.10500000 -8.42000000 8 0
Mg -4.21000000 4.21000000 -10.52500000 12 1
O -6.31500000 4.21000000 -10.52500000 8 0
Mg -6.31500000 2.10500000 -6.31500000 12 1
O -4.21000000 2.10500000 -6.31500000 8 0
Mg -6.31500000 4.21000000 -4.21000000 12 1
O -4.21000000 4.21000000 -4.21000000 8 0
Mg -4.21000000 2.10500000 -4.21000000 12 1
O -6.31500000 2.10500000 -4.21000000 8 0
Mg -4.21000000 4.21000000 -6.31500000 12 1
O -6.31500000 4.21000000 -6.31500000 8 0
Mg -6.31500000 2.10500000 -2.10500000 12 1
O -4.21000000 2.10500000 -2.10500000 8 0
Mg -6.31500000 4.21000000 0.00000000 12 1
O -4.21000000 4.21000000 0.00000000 8 0
Mg -4.21000000 2.10500000 0.00000000 12 1
O -6.31500000 2.10500000 0.00000000 8 0
Mg -4.21000000 4.21000000 -2.10500000 12 1
O -6.31500000 4.21000000 -2.10500000 8 0
Mg -4.21000000 6.31500000 -12.63000000 12 1
Mg -6.31500000 6.31500000 -10.52500000 12 1
O -4.21000000 6.31500000 -10.52500000 8 0
Mg -6.31500000 8.42000000 -8.42000000 12 1
O -4.21000000 8.42000000 -8.42000000 8 0
Mg -4.21000000 6.31500000 -8.42000000 12 1
O -6.31500000 6.31500000 -8.42000000 8 0
Mg -4.21000000 8.42000000 -10.52500000 12 1
Mg -6.31500000 6.31500000 -6.31500000 12 1
O -4.21000000 6.31500000 -6.31500000 8 0
Mg -6.31500000 8.42000000 -4.21000000 12 1
O -4.21000000 8.42000000 -4.21000000 8 0
Mg -4.21000000 6.31500000 -4.21000000 12 1
O -6.31500000 6.31500000 -4.21000000 8 0
Mg -4.21000000 8.42000000 -6.31500000 12 1
O -6.31500000 8.42000000 -6.31500000 8 0
Mg -6.31500000 6.31500000 -2.10500000 12 1
O -4.21000000 6.31500000 -2.10500000 8 0
Mg -6.31500000 8.42000000 0.00000000 12 1
O -4.21000000 8.42000000 0.00000000 8 0
Mg -4.21000000 6.31500000 0.00000000 12 1
O -6.31500000 6.31500000 0.00000000 8 0
Mg -4.21000000 8.42000000 -2.10500000 12 1
O -6.31500000 8.42000000 -2.10500000 8 0
Mg -4.21000000 10.52500000 -8.42000000 12 1
Mg -6.31500000 10.52500000 -6.31500000 12 1
O -4.21000000 10.52500000 -6.31500000 8 0
Mg -6.31500000 12.63000000 -4.21000000 12 1
O -4.21000000 12.63000000 -4.21000000 8 0
Mg -4.21000000 10.52500000 -4.21000000 12 1
O -6.31500000 10.52500000 -4.21000000 8 0
Mg -4.21000000 12.63000000 -6.31500000 12 1
Mg -6.31500000 10.52500000 -2.10500000 12 1
O -4.21000000 10.52500000 -2.10500000 8 0
Mg -6.31500000 12.63000000 0.00000000 12 1
O -4.21000000 12.63000000 0.00000000 8 0
Mg -4.21000000 10.52500000 0.00000000 12 1
O -6.31500000 10.52500000 0.00000000 8 0
Mg -4.21000000 12.63000000 -2.10500000 12 1
O -6.31500000 12.63000000 -2.10500000 8 0
Mg -2.10500000 -12.63000000 -4.21000000 12 1
O 0.00000000 -12.63000000 -4.21000000 8 0
Mg 0.00000000 -12.63000000 -6.31500000 12 1
O -2.10500000 -12.63000000 -6.31500000 8 0
Mg -2.10500000 -12.63000000 0.00000000 12 1
O 0.00000000 -12.63000000 0.00000000 8 0
Mg 0.00000000 -14.73500000 0.00000000 12 1
Mg 0.00000000 -12.63000000 -2.10500000 12 1
O -2.10500000 -12.63000000 -2.10500000 8 0
Mg -2.10500000 -8.42000000 -8.42000000 12 1
O 0.00000000 -8.42000000 -8.42000000 8 0
Mg 0.00000000 -10.52500000 -8.42000000 12 1
O -2.10500000 -10.52500000 -8.42000000 8 0
Mg 0.00000000 -8.42000000 -10.52500000 12 1
O -2.10500000 -8.42000000 -10.52500000 8 0
Mg -2.10500000 -10.52500000 -6.31500000 12 1
O 0.00000000 -10.52500000 -6.31500000 8 0
Mg -2.10500000 -8.42000000 -4.21000000 12 1
O 0.00000000 -8.42000000 -4.21000000 8 0
Mg 0.00000000 -10.52500000 -4.21000000 12 1
O -2.10500000 -10.52500000 -4.21000000 8 0
Mg 0.00000000 -8.42000000 -6.31500000 12 1
O -2.10500000 -8.42000000 -6.31500000 8 0
Mg -2.10500000 -10.52500000 -2.10500000 12 1
O 0.00000000 -10.52500000 -2.10500000 8 0
Mg -2.10500000 -8.42000000 0.00000000 12 1
O 0.00000000 -8.42000000 0.00000000 8 0
Mg 0.00000000 -10.52500000 0.00000000 12 1
O -2.10500000 -10.52500000 0.00000000 8 0
Mg 0.00000000 -8.42000000 -2.10500000 12 1
O -2.10500000 -8.42000000 -2.10500000 8 0
Mg -2.10500000 -4.21000000 -12.63000000 12 1
O 0.00000000 -4.21000000 -12.63000000 8 0
Mg 0.00000000 -6.31500000 -12.63000000 12 1
O -2.10500000 -6.31500000 -12.63000000 8 0
Mg -2.10500000 -6.31500000 -10.52500000 12 1
O 0.00000000 -6.31500000 -10.52500000 8 0
Mg -2.10500000 -4.21000000 -8.42000000 12 1
O 0.00000000 -4.21000000 -8.42000000 8 0
Mg 0.00000000 -6.31500000 -8.42000000 12 1
O -2.10500000 -6.31500000 -8.42000000 8 0
Mg 0.00000000 -4.21000000 -10.52500000 12 1
O -2.10500000 -4.21000000 -10.52500000 8 0
Mg -2.10500000 -6.31500000 -6.31500000 12 1
O 0.00000000 -6.31500000 -6.31500000 8 0
Mg -2.10500000 -4.21000000 -4.21000000 12 1
O 0.00000000 -4.21000000 -4.21000000 8 0
Mg 0.00000000 -6.31500000 -4.21000000 12 1
O -2.10500000 -6.31500000 -4.21000000 8 0
Mg 0.00000000 -4.21000000 -6.31500000 12 1
O -2.10500000 -4.21000000 -6.31500000 8 0
Mg -2.10500000 -6.31500000 -2.10500000 12 1
O 0.00000000 -6.31500000 -2.10500000 8 0
Mg -2.10500000 -4.21000000 0.00000000 12 1
O 0.00000000 -4.21000000 0.00000000 8 0
Mg 0.00000000 -6.31500000 0.00000000 12 1
O -2.10500000 -6.31500000 0.00000000 8 0
Mg 0.00000000 -4.21000000 -2.10500000 12 1
O -2.10500000 -4.21000000 -2.10500000 8 0
Mg -2.10500000 0.00000000 -12.63000000 12 1
O 0.00000000 0.00000000 -12.63000000 8 0
Mg 0.00000000 -2.10500000 -12.63000000 12 1
O -2.10500000 -2.10500000 -12.63000000 8 0
Mg 0.00000000 0.00000000 -14.73500000 12 1
Mg -2.10500000 -2.10500000 -10.52500000 12 1
O 0.00000000 -2.10500000 -10.52500000 8 0
Mg -2.10500000 0.00000000 -8.42000000 12 1
O 0.00000000 0.00000000 -8.42000000 8 0
Mg 0.00000000 -2.10500000 -8.42000000 12 1
O -2.10500000 -2.10500000 -8.42000000 8 0
Mg 0.00000000 0.00000000 -10.52500000 12 1
O -2.10500000 0.00000000 -10.52500000 8 0
Mg -2.10500000 -2.10500000 -6.31500000 12 1
O 0.00000000 -2.10500000 -6.31500000 8 0
Mg -2.10500000 0.00000000 -4.21000000 12 1
O 0.00000000 0.00000000 -4.21000000 8 0
Mg 0.00000000 -2.10500000 -4.21000000 12 1
O -2.10500000 -2.10500000 -4.21000000 8 0
Mg 0.00000000 0.00000000 -6.31500000 12 1
O -2.10500000 0.00000000 -6.31500000 8 0
Mg -2.10500000 -2.10500000 -2.10500000 12 1
O 0.00000000 -2.10500000 -2.10500000 8 0
Mg -2.10500000 0.00000000 0.00000000 12 1
O 0.00000000 0.00000000 0.00000000 8 0
Mg 0.00000000 -2.10500000 0.00000000 12 1
O -2.10500000 -2.10500000 0.00000000 8 0
Mg 0.00000000 0.00000000 -2.10500000 12 1
O -2.10500000 0.00000000 -2.10500000 8 0
Mg -2.10500000 4.21000000 -12.63000000 12 1
O 0.00000000 4.21000000 -12.63000000 8 0
Mg 0.00000000 2.10500000 -12.63000000 12 1
O -2.10500000 2.10500000 -12.63000000 8 0
Mg -2.10500000 2.10500000 -10.52500000 12 1
O 0.00000000 2.10500000 -10.52500000 8 0
Mg -2.10500000 4.21000000 -8.42000000 12 1
O 0.00000000 4.21000000 -8.42000000 8 0
Mg 0.00000000 2.10500000 -8.42000000 12 1
O -2.10500000 2.10500000 -8.42000000 8 0
Mg 0.00000000 4.21000000 -10.52500000 12 1
O -2.10500000 4.21000000 -10.52500000 8 0
Mg -2.10500000 2.10500000 -6.31500000 12 1
O 0.00000000 2.10500000 -6.31500000 8 0
Mg -2.10500000 4.21000000 -4.21000000 12 1
O 0.00000000 4.21000000 -4.21000000 8 0
Mg 0.00000000 2.10500000 -4.21000000 12 1
O -2.10500000 2.10500000 -4.21000000 8 0
Mg 0.00000000 4.21000000 -6.31500000 12 1
O -2.10500000 4.21000000 -6.31500000 8 0
Mg -2.10500000 2.10500000 -2.10500000 12 1
O 0.00000000 2.10500000 -2.10500000 8 0
Mg -2.10500000 4.21000000 0.00000000 12 1
O 0.00000000 4.21000000 0.00000000 8 0
Mg 0.00000000 2.10500000 0.00000000 12 1
O -2.10500000 2.10500000 0.00000000 8 0
Mg 0.00000000 4.21000000 -2.10500000 12 1
O -2.10500000 4.21000000 -2.10500000 8 0
Mg 0.00000000 6.31500000 -12.63000000 12 1
O -2.10500000 6.31500000 -12.63000000 8 0
Mg -2.10500000 6.31500000 -10.52500000 12 1
O 0.00000000 6.31500000 -10.52500000 8 0
Mg -2.10500000 8.42000000 -8.42000000 12 1
O 0.00000000 8.42000000 -8.42000000 8 0
Mg 0.00000000 6.31500000 -8.42000000 12 1
O -2.10500000 6.31500000 -8.42000000 8 0
Mg 0.00000000 8.42000000 -10.52500000 12 1
O -2.10500000 8.42000000 -10.52500000 8 0
Mg -2.10500000 6.31500000 -6.31500000 12 1
O 0.00000000 6.31500000 -6.31500000 8 0
Mg -2.10500000 8.42000000 -4.21000000 12 1
O 0.00000000 8.42000000 -4.21000000 8 0
Mg 0.00000000 6.31500000 -4.21000000 12 1
O -2.10500000 6.31500000 -4.21000000 8 0
Mg 0.00000000 8.42000000 -6.31500000 12 1
O -2.10500000 8.42000000 -6.31500000 8 0
Mg -2.10500000 6.31500000 -2.10500000 12 1
O 0.00000000 6.31500000 -2.10500000 8 0
Mg -2.10500000 8.42000000 0.00000000 12 1
O 0.00000000 8.42000000 0.00000000 8 0
Mg 0.00000000 6.31500000 0.00000000 12 1
O -2.10500000 6.31500000 0.00000000 8 0
Mg 0.00000000 8.42000000 -2.10500000 12 1
O -2.10500000 8.42000000 -2.10500000 8 0
Mg 0.00000000 10.52500000 -8.42000000 12 1
O -2.10500000 10.52500000 -8.42000000 8 0
Mg -2.10500000 10.52500000 -6.31500000 12 1
O 0.00000000 10.52500000 -6.31500000 8 0
Mg -2.10500000 12.63000000 -4.21000000 12 1
O 0.00000000 12.63000000 -4.21000000 8 0
Mg 0.00000000 10.52500000 -4.21000000 12 1
O -2.10500000 10.52500000 -4.21000000 8 0
Mg 0.00000000 12.63000000 -6.31500000 12 1
O -2.10500000 12.63000000 -6.31500000 8 0
Mg -2.10500000 10.52500000 -2.10500000 12 1
O 0.00000000 10.52500000 -2.10500000 8 0
Mg -2.10500000 12.63000000 0.00000000 12 1
O 0.00000000 12.63000000 0.00000000 8 0
Mg 0.00000000 10.52500000 0.00000000 12 1
O -2.10500000 10.52500000 0.00000000 8 0
Mg 0.00000000 12.63000000 -2.10500000 12 1
O -2.10500000 12.63000000 -2.10500000 8 0
Mg 0.00000000 14.73500000 0.00000000 12 1
Mg 2.10500000 -12.63000000 -4.21000000 12 1
O 4.21000000 -12.63000000 -4.21000000 8 0
Mg 4.21000000 -12.63000000 -6.31500000 12 1
O 2.10500000 -12.63000000 -6.31500000 8 0
Mg 2.10500000 -12.63000000 0.00000000 12 1
O 4.21000000 -12.63000000 0.00000000 8 0
Mg 4.21000000 -12.63000000 -2.10500000 12 1
O 2.10500000 -12.63000000 -2.10500000 8 0
Mg 2.10500000 -8.42000000 -8.42000000 12 1
O 4.21000000 -8.42000000 -8.42000000 8 0
Mg 4.21000000 -10.52500000 -8.42000000 12 1
O 2.10500000 -10.52500000 -8.42000000 8 0
Mg 4.21000000 -8.42000000 -10.52500000 12 1
O 2.10500000 -8.42000000 -10.52500000 8 0
Mg 2.10500000 -10.52500000 -6.31500000 12 1
O 4.21000000 -10.52500000 -6.31500000 8 0
Mg 2.10500000 -8.42000000 -4.21000000 12 1
O 4.21000000 -8.42000000 -4.21000000 8 0
Mg 4.21000000 -10.52500000 -4.21000000 12 1
O 2.10500000 -10.52500000 -4.21000000 8 0
Mg 4.21000000 -8.42000000 -6.31500000 12 1
O 2.10500000 -8.42000000 -6.31500000 8 0
Mg 2.10500000 -10.52500000 -2.10500000 12 1
O 4.21000000 -10.52500000 -2.10500000 8 0
Mg 2.10500000 -8.42000000 0.00000000 12 1
O 4.21000000 -8.42000000 0.00000000 8 0
Mg 4.21000000 -10.52500000 0.00000000 12 1
O 2.10500000 -10.52500000 0.00000000 8 0
Mg 4.21000000 -8.42000000 -2.10500000 12 1
O 2.10500000 -8.42000000 -2.10500000 8 0
Mg 2.10500000 -4.21000000 -12.63000000 12 1
O 4.21000000 -4.21000000 -12.63000000 8 0
Mg 4.21000000 -6.31500000 -12.63000000 12 1
O 2.10500000 -6.31500000 -12.63000000 8 0
Mg 2.10500000 -6.31500000 -10.52500000 12 1
O 4.21000000 -6.31500000 -10.52500000 8 0
Mg 2.10500000 -4.21000000 -8.42000000 12 1
O 4.21000000 -4.21000000 -8.42000000 8 0
Mg 4.21000000 -6.31500000 -8.42000000 12 1
O 2.10500000 -6.31500000 -8.42000000 8 0
Mg 4.21000000 -4.21000000 -10.52500000 12 1
O 2.10500000 -4.21000000 -10.52500000 8 0
Mg 2.10500000 -6.31500000 -6.31500000 12 1
O 4.21000000 -6.31500000 -6.31500000 8 0
Mg 2.10500000 -4.21000000 -4.21000000 12 1
O 4.21000000 -4.21000000 -4.21000000 8 0
Mg 4.21000000 -6.31500000 -4.21000000 12 1
O 2.10500000 -6.31500000 -4.21000000 8 0
Mg 4.21000000 -4.21000000 -6.31500000 12 1
O 2.10500000 -4.21000000 -6.31500000 8 0
Mg 2.10500000 -6.31500000 -2.10500000 12 1
O 4.21000000 -6.31500000 -2.10500000 8 0
Mg 2.10500000 -4.21000000 0.00000000 12 1
O 4.21000000 -4.21000000 0.00000000 8 0
Mg 4.21000000 -6.31500000 0.00000000 12 1
O 2.10500000 -6.31500000 0.00000000 8 0
Mg 4.21000000 -4.21000000 -2.10500000 12 1
O 2.10500000 -4.21000000 -2.10500000 8 0
Mg 2.10500000 0.00000000 -12.63000000 12 1
O 4.21000000 0.00000000 -12.63000000 8 0
Mg 4.21000000 -2.10500000 -12.63000000 12 1
O 2.10500000 -2.10500000 -12.63000000 8 0
Mg 2.10500000 -2.10500000 -10.52500000 12 1
O 4.21000000 -2.10500000 -10.52500000 8 0
Mg 2.10500000 0.00000000 -8.42000000 12 1
O 4.21000000 0.00000000 -8.42000000 8 0
Mg 4.21000000 -2.10500000 -8.42000000 12 1
O 2.10500000 -2.10500000 -8.42000000 8 0
Mg 4.21000000 0.00000000 -10.52500000 12 1
O 2.10500000 0.00000000 -10.52500000 8 0
Mg 2.10500000 -2.10500000 -6.31500000 12 1
O 4.21000000 -2.10500000 -6.31500000 8 0
Mg 2.10500000 0.00000000 -4.21000000 12 1
O 4.21000000 0.00000000 -4.21000000 8 0
Mg 4.21000000 -2.10500000 -4.21000000 12 1
O 2.10500000 -2.10500000 -4.21000000 8 0
Mg 4.21000000 0.00000000 -6.31500000 12 1
O 2.10500000 0.00000000 -6.31500000 8 0
Mg 2.10500000 -2.10500000 -2.10500000 12 1
O 4.21000000 -2.10500000 -2.10500000 8 0
Mg 2.10500000 0.00000000 0.00000000 12 1
O 4.21000000 0.00000000 0.00000000 8 0
Mg 4.21000000 -2.10500000 0.00000000 12 1
O 2.10500000 -2.10500000 0.00000000 8 0
Mg 4.21000000 0.00000000 -2.10500000 12 1
O 2.10500000 0.00000000 -2.10500000 8 0
Mg 2.10500000 4.21000000 -12.63000000 12 1
O 4.21000000 4.21000000 -12.63000000 8 0
Mg 4.21000000 2.10500000 -12.63000000 12 1
O 2.10500000 2.10500000 -12.63000000 8 0
Mg 2.10500000 2.10500000 -10.52500000 12 1
O 4.21000000 2.10500000 -10.52500000 8 0
Mg 2.10500000 4.21000000 -8.42000000 12 1
O 4.21000000 4.21000000 -8.42000000 8 0
Mg 4.21000000 2.10500000 -8.42000000 12 1
O 2.10500000 2.10500000 -8.42000000 8 0
Mg 4.21000000 4.21000000 -10.52500000 12 1
O 2.10500000 4.21000000 -10.52500000 8 0
Mg 2.10500000 2.10500000 -6.31500000 12 1
O 4.21000000 2.10500000 -6.31500000 8 0
Mg 2.10500000 4.21000000 -4.21000000 12 1
O 4.21000000 4.21000000 -4.21000000 8 0
Mg 4.21000000 2.10500000 -4.21000000 12 1
O 2.10500000 2.10500000 -4.21000000 8 0
Mg 4.21000000 4.21000000 -6.31500000 12 1
O 2.10500000 4.21000000 -6.31500000 8 0
Mg 2.10500000 2.10500000 -2.10500000 12 1
O 4.21000000 2.10500000 -2.10500000 8 0
Mg 2.10500000 4.21000000 0.00000000 12 1
O 4.21000000 4.21000000 0.00000000 8 0
Mg 4.21000000 2.10500000 0.00000000 12 1
O 2.10500000 2.10500000 0.00000000 8 0
Mg 4.21000000 4.21000000 -2.10500000 12 1
O 2.10500000 4.21000000 -2.10500000 8 0
Mg 4.21000000 6.31500000 -12.63000000 12 1
O 2.10500000 6.31500000 -12.63000000 8 0
Mg 2.10500000 6.31500000 -10.52500000 12 1
O 4.21000000 6.31500000 -10.52500000 8 0
Mg 2.10500000 8.42000000 -8.42000000 12 1
O 4.21000000 8.42000000 -8.42000000 8 0
Mg 4.21000000 6.31500000 -8.42000000 12 1
O 2.10500000 6.31500000 -8.42000000 8 0
Mg 4.21000000 8.42000000 -10.52500000 12 1
O 2.10500000 8.42000000 -10.52500000 8 0
Mg 2.10500000 6.31500000 -6.31500000 12 1
O 4.21000000 6.31500000 -6.31500000 8 0
Mg 2.10500000 8.42000000 -4.21000000 12 1
O 4.21000000 8.42000000 -4.21000000 8 0
Mg 4.21000000 6.31500000 -4.21000000 12 1
O 2.10500000 6.31500000 -4.21000000 8 0
Mg 4.21000000 8.42000000 -6.31500000 12 1
O 2.10500000 8.42000000 -6.31500000 8 0
Mg 2.10500000 6.31500000 -2.10500000 12 1
O 4.21000000 6.31500000 -2.10500000 8 0
Mg 2.10500000 8.42000000 0.00000000 12 1
O 4.21000000 8.42000000 0.00000000 8 0
Mg 4.21000000 6.31500000 0.00000000 12 1
O 2.10500000 6.31500000 0.00000000 8 0
Mg 4.21000000 8.42000000 -2.10500000 12 1
O 2.10500000 8.42000000 -2.10500000 8 0
Mg 4.21000000 10.52500000 -8.42000000 12 1
O 2.10500000 10.52500000 -8.42000000 8 0
Mg 2.10500000 10.52500000 -6.31500000 12 1
O 4.21000000 10.52500000 -6.31500000 8 0
Mg 2.10500000 12.63000000 -4.21000000 12 1
O 4.21000000 12.63000000 -4.21000000 8 0
Mg 4.21000000 10.52500000 -4.21000000 12 1
O 2.10500000 10.52500000 -4.21000000 8 0
Mg 4.21000000 12.63000000 -6.31500000 12 1
O 2.10500000 12.63000000 -6.31500000 8 0
Mg 2.10500000 10.52500000 -2.10500000 12 1
O 4.21000000 10.52500000 -2.10500000 8 0
Mg 2.10500000 12.63000000 0.00000000 12 1
O 4.21000000 12.63000000 0.00000000 8 0
Mg 4.21000000 10.52500000 0.00000000 12 1
O 2.10500000 10.52500000 0.00000000 8 0
Mg 4.21000000 12.63000000 -2.10500000 12 1
O 2.10500000 12.63000000 -2.10500000 8 0
Mg 6.31500000 -12.63000000 -4.21000000 12 1
Mg 6.31500000 -12.63000000 0.00000000 12 1
O 6.31500000 -12.63000000 -2.10500000 8 0
Mg 6.31500000 -8.42000000 -8.42000000 12 1
O 8.42000000 -8.42000000 -8.42000000 8 0
Mg 6.31500000 -10.52500000 -6.31500000 12 1
Mg 6.31500000 -8.42000000 -4.21000000 12 1
O 8.42000000 -8.42000000 -4.21000000 8 0
Mg 8.42000000 -10.52500000 -4.21000000 12 1
O 6.31500000 -10.52500000 -4.21000000 8 0
Mg 8.42000000 -8.42000000 -6.31500000 12 1
O 6.31500000 -8.42000000 -6.31500000 8 0
Mg 6.31500000 -10.52500000 -2.10500000 12 1
O 8.42000000 -10.52500000 -2.10500000 8 0
Mg 6.31500000 -8.42000000 0.00000000 12 1
O 8.42000000 -8.42000000 0.00000000 8 0
Mg 8.42000000 -10.52500000 0.00000000 12 1
O 6.31500000 -10.52500000 0.00000000 8 0
Mg 8.42000000 -8.42000000 -2.10500000 12 1
O 6.31500000 -8.42000000 -2.10500000 8 0
Mg 6.31500000 -4.21000000 -12.63000000 12 1
Mg 6.31500000 -6.31500000 -10.52500000 12 1
Mg 6.31500000 -4.21000000 -8.42000000 12 1
O 8.42000000 -4.21000000 -8.42000000 8 0
Mg 8.42000000 -6.31500000 -8.42000000 12 1
O 6.31500000 -6.31500000 -8.42000000 8 0
Mg 8.42000000 -4.21000000 -10.52500000 12 1
O 6.31500000 -4.21000000 -10.52500000 8 0
Mg 6.31500000 -6.31500000 -6.31500000 12 1
O 8.42000000 -6.31500000 -6.31500000 8 0
Mg 6.31500000 -4.21000000 -4.21000000 12 1
O 8.42000000 -4.21000000 -4.21000000 8 0
Mg 8.42000000 -6.31500000 -4.21000000 12 1
O 6.31500000 -6.31500000 -4.21000000 8 0
Mg 8.42000000 -4.21000000 -6.31500000 12 1
O 6.31500000 -4.21000000 -6.31500000 8 0
Mg 6.31500000 -6.31500000 -2.10500000 12 1
O 8.42000000 -6.31500000 -2.10500000 8 0
Mg 6.31500000 -4.21000000 0.00000000 12 1
O 8.42000000 -4.21000000 0.00000000 8 0
Mg 8.42000000 -6.31500000 0.00000000 12 1
O 6.31500000 -6.31500000 0.00000000 8 0
Mg 8.42000000 -4.21000000 -2.10500000 12 1
O 6.31500000 -4.21000000 -2.10500000 8 0
Mg 6.31500000 0.00000000 -12.63000000 12 1
O 6.31500000 -2.10500000 -12.63000000 8 0
Mg 6.31500000 -2.10500000 -10.52500000 12 1
O 8.42000000 -2.10500000 -10.52500000 8 0
Mg 6.31500000 0.00000000 -8.42000000 12 1
O 8.42000000 0.00000000 -8.42000000 8 0
Mg 8.42000000 -2.10500000 -8.42000000 12 1
O 6.31500000 -2.10500000 -8.42000000 8 0
Mg 8.42000000 0.00000000 -10.52500000 12 1
O 6.31500000 0.00000000 -10.52500000 8 0
Mg 6.31500000 -2.10500000 -6.31500000 12 1
O 8.42000000 -2.10500000 -6.31500000 8 0
Mg 6.31500000 0.00000000 -4.21000000 12 1
O 8.42000000 0.00000000 -4.21000000 8 0
Mg 8.42000000 -2.10500000 -4.21000000 12 1
O 6.31500000 -2.10500000 -4.21000000 8 0
Mg 8.42000000 0.00000000 -6.31500000 12 1
O 6.31500000 0.00000000 -6.31500000 8 0
Mg 6.31500000 -2.10500000 -2.10500000 12 1
O 8.42000000 -2.10500000 -2.10500000 8 0
Mg 6.31500000 0.00000000 0.00000000 12 1
O 8.42000000 0.00000000 0.00000000 8 0
Mg 8.42000000 -2.10500000 0.00000000 12 1
O 6.31500000 -2.10500000 0.00000000 8 0
Mg 8.42000000 0.00000000 -2.10500000 12 1
O 6.31500000 0.00000000 -2.10500000 8 0
Mg 6.31500000 4.21000000 -12.63000000 12 1
O 6.31500000 2.10500000 -12.63000000 8 0
Mg 6.31500000 2.10500000 -10.52500000 12 1
O 8.42000000 2.10500000 -10.52500000 8 0
Mg 6.31500000 4.21000000 -8.42000000 12 1
O 8.42000000 4.21000000 -8.42000000 8 0
Mg 8.42000000 2.10500000 -8.42000000 12 1
O 6.31500000 2.10500000 -8.42000000 8 0
Mg 8.42000000 4.21000000 -10.52500000 12 1
O 6.31500000 4.21000000 -10.52500000 8 0
Mg 6.31500000 2.10500000 -6.31500000 12 1
O 8.42000000 2.10500000 -6.31500000 8 0
Mg 6.31500000 4.21000000 -4.21000000 12 1
O 8.42000000 4.21000000 -4.21000000 8 0
Mg 8.42000000 2.10500000 -4.21000000 12 1
O 6.31500000 2.10500000 -4.21000000 8 0
Mg 8.42000000 4.21000000 -6.31500000 12 1
O 6.31500000 4.21000000 -6.31500000 8 0
Mg 6.31500000 2.10500000 -2.10500000 12 1
O 8.42000000 2.10500000 -2.10500000 8 0
Mg 6.31500000 4.21000000 0.00000000 12 1
O 8.42000000 4.21000000 0.00000000 8 0
Mg 8.42000000 2.10500000 0.00000000 12 1
O 6.31500000 2.10500000 0.00000000 8 0
Mg 8.42000000 4.21000000 -2.10500000 12 1
O 6.31500000 4.21000000 -2.10500000 8 0
Mg 6.31500000 6.31500000 -10.52500000 12 1
Mg 6.31500000 8.42000000 -8.42000000 12 1
O 8.42000000 8.42000000 -8.42000000 8 0
Mg 8.42000000 6.31500000 -8.42000000 12 1
O 6.31500000 6.31500000 -8.42000000 8 0
Mg 6.31500000 6.31500000 -6.31500000 12 1
O 8.42000000 6.31500000 -6.31500000 8 0
Mg 6.31500000 8.42000000 -4.21000000 12 1
O 8.42000000 8.42000000 -4.21000000 8 0
Mg 8.42000000 6.31500000 -4.21000000 12 1
O 6.31500000 6.31500000 -4.21000000 8 0
Mg 8.42000000 8.42000000 -6.31500000 12 1
O 6.31500000 8.42000000 -6.31500000 8 0
Mg 6.31500000 6.31500000 -2.10500000 12 1
O 8.42000000 6.31500000 -2.10500000 8 0
Mg 6.31500000 8.42000000 0.00000000 12 1
O 8.42000000 8.42000000 0.00000000 8 0
Mg 8.42000000 6.31500000 0.00000000 12 1
O 6.31500000 6.31500000 0.00000000 8 0
Mg 8.42000000 8.42000000 -2.10500000 12 1
O 6.31500000 8.42000000 -2.10500000 8 0
Mg 6.31500000 10.52500000 -6.31500000 12 1
Mg 6.31500000 12.63000000 -4.21000000 12 1
Mg 8.42000000 10.52500000 -4.21000000 12 1
O 6.31500000 10.52500000 -4.21000000 8 0
Mg 6.31500000 10.52500000 -2.10500000 12 1
O 8.42000000 10.52500000 -2.10500000 8 0
Mg 6.31500000 12.63000000 0.00000000 12 1
Mg 8.42000000 10.52500000 0.00000000 12 1
O 6.31500000 10.52500000 0.00000000 8 0
O 6.31500000 12.63000000 -2.10500000 8 0
Mg 10.52500000 -8.42000000 -4.21000000 12 1
Mg 10.52500000 -8.42000000 0.00000000 12 1
O 10.52500000 -8.42000000 -2.10500000 8 0
Mg 10.52500000 -4.21000000 -8.42000000 12 1
Mg 10.52500000 -6.31500000 -6.31500000 12 1
Mg 10.52500000 -4.21000000 -4.21000000 12 1
O 12.63000000 -4.21000000 -4.21000000 8 0
Mg 12.63000000 -6.31500000 -4.21000000 12 1
O 10.52500000 -6.31500000 -4.21000000 8 0
Mg 12.63000000 -4.21000000 -6.31500000 12 1
O 10.52500000 -4.21000000 -6.31500000 8 0
Mg 10.52500000 -6.31500000 -2.10500000 12 1
O 12.63000000 -6.31500000 -2.10500000 8 0
Mg 10.52500000 -4.21000000 0.00000000 12 1
O 12.63000000 -4.21000000 0.00000000 8 0
Mg 12.63000000 -6.31500000 0.00000000 12 1
O 10.52500000 -6.31500000 0.00000000 8 0
Mg 12.63000000 -4.21000000 -2.10500000 12 1
O 10.52500000 -4.21000000 -2.10500000 8 0
Mg 10.52500000 0.00000000 -8.42000000 12 1
O 10.52500000 -2.10500000 -8.42000000 8 0
Mg 10.52500000 -2.10500000 -6.31500000 12 1
O 12.63000000 -2.10500000 -6.31500000 8 0
Mg 10.52500000 0.00000000 -4.21000000 12 1
O 12.63000000 0.00000000 -4.21000000 8 0
Mg 12.63000000 -2.10500000 -4.21000000 12 1
O 10.52500000 -2.10500000 -4.21000000 8 0
Mg 12.63000000 0.00000000 -6.31500000 12 1
O 10.52500000 0.00000000 -6.31500000 8 0
Mg 10.52500000 -2.10500000 -2.10500000 12 1
O 12.63000000 -2.10500000 -2.10500000 8 0
Mg 10.52500000 0.00000000 0.00000000 12 1
O 12.63000000 0.00000000 0.00000000 8 0
Mg 12.63000000 -2.10500000 0.00000000 12 1
O 10.52500000 -2.10500000 0.00000000 8 0
Mg 12.63000000 0.00000000 -2.10500000 12 1
O 10.52500000 0.00000000 -2.10500000 8 0
Mg 10.52500000 4.21000000 -8.42000000 12 1
O 10.52500000 2.10500000 -8.42000000 8 0
Mg 10.52500000 2.10500000 -6.31500000 12 1
O 12.63000000 2.10500000 -6.31500000 8 0
Mg 10.52500000 4.21000000 -4.21000000 12 1
O 12.63000000 4.21000000 -4.21000000 8 0
Mg 12.63000000 2.10500000 -4.21000000 12 1
O 10.52500000 2.10500000 -4.21000000 8 0
Mg 12.63000000 4.21000000 -6.31500000 12 1
O 10.52500000 4.21000000 -6.31500000 8 0
Mg 10.52500000 2.10500000 -2.10500000 12 1
O 12.63000000 2.10500000 -2.10500000 8 0
Mg 10.52500000 4.21000000 0.00000000 12 1
O 12.63000000 4.21000000 0.00000000 8 0
Mg 12.63000000 2.10500000 0.00000000 12 1
O 10.52500000 2.10500000 0.00000000 8 0
Mg 12.63000000 4.21000000 -2.10500000 12 1
O 10.52500000 4.21000000 -2.10500000 8 0
Mg 10.52500000 6.31500000 -6.31500000 12 1
Mg 10.52500000 8.42000000 -4.21000000 12 1
Mg 12.63000000 6.31500000 -4.21000000 12 1
O 10.52500000 6.31500000 -4.21000000 8 0
Mg 10.52500000 6.31500000 -2.10500000 12 1
O 12.63000000 6.31500000 -2.10500000 8 0
Mg 10.52500000 8.42000000 0.00000000 12 1
Mg 12.63000000 6.31500000 0.00000000 12 1
O 10.52500000 6.31500000 0.00000000 8 0
O 10.52500000 8.42000000 -2.10500000 8 0
Mg 14.73500000 0.00000000 0.00000000 12 1

View File

@ -0,0 +1,59 @@
57
Phtalocyanine
Ti 0.0000 0.0000 0.0000
N 1.9944 0.0000 -0.6270
N 0.0000 1.9944 -0.6270
N -1.9944 0.0000 -0.6270
N 0.0000 -1.9944 -0.6270
N 2.3811 -2.3811 -0.7747
N 2.3811 2.3811 -0.7747
N -2.3811 2.3811 -0.7747
N -2.3811 -2.3811 -0.7747
C 2.7862 -1.1243 -0.7216
C 1.1243 2.7862 -0.7216
C -2.7862 1.1243 -0.7216
C -1.1243 2.7862 -0.7216
C -2.7862 -1.1243 -0.7216
C 1.1243 -2.7862 -0.7216
C 2.7862 1.1243 -0.7216
C -1.1243 -2.7862 -0.7216
C 4.1814 -0.7098 -0.8274
C 0.7098 4.1814 -0.8274
C -4.1814 0.7098 -0.8274
C -0.7098 4.1814 -0.8274
C -4.1814 -0.7098 -0.8274
C 0.7098 -4.1814 -0.8274
C 4.1814 0.7098 -0.8274
C -0.7098 -4.1814 -0.8274
C 5.3704 -1.4277 -0.9433
C 1.4277 5.3704 -0.9433
C -5.3704 1.4277 -0.9433
C -1.4277 5.3704 -0.9433
C -5.3704 -1.4277 -0.9433
C 1.4277 -5.3704 -0.9433
C 5.3704 1.4277 -0.9433
C -1.4277 -5.3704 -0.9433
C 6.5622 -0.7166 -1.0358
C 0.7166 6.5622 -1.0358
C -6.5622 0.7166 -1.0358
C -0.7166 6.5622 -1.0358
C -6.5622 -0.7166 -1.0358
C 0.7166 -6.5622 -1.0358
C 6.5622 0.7166 -1.0358
C -0.7166 -6.5622 -1.0358
H 5.4029 -2.4925 -0.9609
H 2.4925 5.4029 -0.9609
H -5.4029 2.4925 -0.9609
H -2.4925 5.4029 -0.9609
H -5.4029 -2.4925 -0.9609
H 2.4925 -5.4029 -0.9609
H 5.4029 2.4925 -0.9609
H -2.4925 -5.4029 -0.9609
H 7.5337 -1.1483 -1.1224
H 1.1483 7.5337 -1.1224
H -7.5337 1.1483 -1.1224
H -1.1483 7.5337 -1.1224
H -7.5337 -1.1483 -1.1224
H 1.1483 -7.5337 -1.1224
H 7.5337 1.1483 -1.1224
H -1.1483 -7.5337 -1.1224

View File

@ -0,0 +1,312 @@
310
#A (5,5) Tube material is: C , radius is: 3.43 bond legth used: 1.42
C 3.427256 .000000 .000000
C 3.351267 -.717699 1.222920
C 1.059080 -3.259514 .000000
C .353026 -3.409025 1.222920
C -2.772708 -2.014490 .000000
C -3.133085 -1.389195 1.222920
C -2.772708 2.014491 .000000
C -2.289379 2.550456 1.222920
C 1.059081 3.259514 .000000
C 1.718171 2.965462 1.222920
C 2.772708 2.014490 1.222921
C 3.133085 1.389195 2.445841
C 2.772708 -2.014490 1.222921
C 2.289379 -2.550456 2.445841
C -1.059080 -3.259514 1.222921
C -1.718170 -2.965463 2.445841
C -3.427256 .000000 1.222921
C -3.351267 .717699 2.445841
C -1.059081 3.259514 1.222921
C -.353026 3.409025 2.445841
C 1.059080 3.259514 2.445841
C 1.718170 2.965463 3.668761
C 3.427256 .000000 2.445841
C 3.351267 -.717699 3.668761
C 1.059080 -3.259514 2.445841
C .353026 -3.409025 3.668761
C -2.772708 -2.014490 2.445841
C -3.133085 -1.389195 3.668761
C -2.772708 2.014491 2.445841
C -2.289379 2.550456 3.668761
C -1.059080 3.259514 3.668762
C -.353027 3.409025 4.891682
C 2.772708 2.014490 3.668762
C 3.133085 1.389195 4.891682
C 2.772708 -2.014490 3.668762
C 2.289379 -2.550456 4.891682
C -1.059080 -3.259514 3.668762
C -1.718170 -2.965463 4.891682
C -3.427256 .000000 3.668762
C -3.351267 .717699 4.891682
C -2.772708 2.014490 4.891682
C -2.289379 2.550456 6.114603
C 1.059080 3.259514 4.891682
C 1.718170 2.965463 6.114603
C 3.427256 .000000 4.891682
C 3.351267 -.717699 6.114603
C 1.059080 -3.259514 4.891682
C .353026 -3.409025 6.114603
C -2.772708 -2.014490 4.891682
C -3.133085 -1.389195 6.114603
C -3.427256 -.000000 6.114603
C -3.351267 .717698 7.337523
C -1.059080 3.259514 6.114603
C -.353027 3.409025 7.337523
C 2.772708 2.014490 6.114603
C 3.133085 1.389195 7.337523
C 2.772708 -2.014490 6.114603
C 2.289379 -2.550456 7.337523
C -1.059080 -3.259514 6.114603
C -1.718170 -2.965463 7.337523
C -2.772708 -2.014491 7.337523
C -3.133084 -1.389195 8.560444
C -2.772708 2.014490 7.337523
C -2.289379 2.550456 8.560444
C 1.059080 3.259514 7.337523
C 1.718170 2.965463 8.560444
C 3.427256 .000000 7.337523
C 3.351267 -.717699 8.560444
C 1.059080 -3.259514 7.337523
C .353026 -3.409025 8.560444
C -1.059081 -3.259514 8.560444
C -1.718171 -2.965462 9.783364
C -3.427256 .000001 8.560444
C -3.351267 .717699 9.783364
C -1.059080 3.259514 8.560444
C -.353026 3.409025 9.783364
C 2.772708 2.014490 8.560444
C 3.133085 1.389194 9.783364
C 2.772707 -2.014491 8.560444
C 2.289378 -2.550456 9.783364
C 1.059081 -3.259514 9.783364
C .353026 -3.409025 11.006285
C -2.772708 -2.014491 9.783364
C -3.133084 -1.389195 11.006285
C -2.772708 2.014490 9.783364
C -2.289379 2.550456 11.006285
C 1.059080 3.259514 9.783364
C 1.718170 2.965463 11.006285
C 3.427256 .000000 9.783364
C 3.351267 -.717699 11.006285
C 2.772709 -2.014489 11.006285
C 2.289380 -2.550455 12.229205
C -1.059079 -3.259514 11.006285
C -1.718169 -2.965464 12.229205
C -3.427256 -.000001 11.006285
C -3.351267 .717698 12.229205
C -1.059081 3.259513 11.006285
C -.353027 3.409025 12.229205
C 2.772707 2.014491 11.006285
C 3.133084 1.389196 12.229205
C 3.427256 .000001 12.229205
C 3.351267 -.717697 13.452126
C 1.059081 -3.259514 12.229205
C .353026 -3.409025 13.452126
C -2.772708 -2.014491 12.229205
C -3.133084 -1.389195 13.452126
C -2.772708 2.014490 12.229205
C -2.289379 2.550456 13.452126
C 1.059080 3.259514 12.229205
C 1.718170 2.965463 13.452126
C 2.772708 2.014490 13.452126
C 3.133085 1.389194 14.675046
C 2.772708 -2.014491 13.452126
C 2.289380 -2.550455 14.675046
C -1.059081 -3.259514 13.452126
C -1.718171 -2.965462 14.675046
C -3.427256 .000001 13.452126
C -3.351267 .717699 14.675046
C -1.059080 3.259514 13.452126
C -.353026 3.409025 14.675046
C 1.059080 3.259514 14.675047
C 1.718170 2.965463 15.897967
C 3.427256 .000001 14.675047
C 3.351267 -.717697 15.897967
C 1.059081 -3.259514 14.675047
C .353026 -3.409025 15.897967
C -2.772708 -2.014491 14.675047
C -3.133084 -1.389195 15.897967
C -2.772708 2.014490 14.675047
C -2.289379 2.550456 15.897967
C -1.059082 3.259513 15.897967
C -.353027 3.409025 17.120888
C 2.772707 2.014492 15.897967
C 3.133084 1.389197 17.120888
C 2.772709 -2.014489 15.897967
C 2.289380 -2.550455 17.120888
C -1.059079 -3.259514 15.897967
C -1.718169 -2.965464 17.120888
C -3.427256 -.000001 15.897967
C -3.351267 .717698 17.120888
C -2.772708 2.014491 17.120888
C -2.289378 2.550457 18.343807
C 1.059081 3.259513 17.120888
C 1.718170 2.965463 18.343807
C 3.427256 -.000001 17.120888
C 3.351267 -.717700 18.343807
C 1.059079 -3.259514 17.120888
C .353026 -3.409025 18.343807
C -2.772709 -2.014489 17.120888
C -3.133085 -1.389194 18.343807
C -3.427256 -.000000 18.343807
C -3.351267 .717699 19.566727
C -1.059079 3.259514 18.343807
C -.353024 3.409025 19.566727
C 2.772708 2.014490 18.343807
C 3.133085 1.389194 19.566727
C 2.772708 -2.014491 18.343807
C 2.289380 -2.550455 19.566727
C -1.059081 -3.259514 18.343807
C -1.718171 -2.965462 19.566727
C -2.772707 -2.014491 19.566729
C -3.133085 -1.389195 20.789650
C -2.772708 2.014491 19.566729
C -2.289378 2.550457 20.789650
C 1.059080 3.259514 19.566729
C 1.718170 2.965463 20.789650
C 3.427256 .000001 19.566729
C 3.351267 -.717697 20.789650
C 1.059081 -3.259514 19.566729
C .353026 -3.409025 20.789650
C -1.059079 -3.259514 20.789650
C -1.718169 -2.965464 22.012569
C -3.427256 -.000000 20.789650
C -3.351267 .717699 22.012569
C -1.059082 3.259513 20.789650
C -.353027 3.409025 22.012569
C 2.772707 2.014492 20.789650
C 3.133084 1.389197 22.012569
C 2.772709 -2.014489 20.789650
C 2.289380 -2.550455 22.012569
C 1.059083 -3.259513 22.012569
C .353028 -3.409025 23.235489
C -2.772707 -2.014491 22.012569
C -3.133085 -1.389195 23.235489
C -2.772710 2.014488 22.012569
C -2.289381 2.550455 23.235489
C 1.059078 3.259515 22.012569
C 1.718168 2.965465 23.235489
C 3.427256 .000002 22.012569
C 3.351267 -.717697 23.235489
C 2.772708 -2.014490 23.235491
C 2.289379 -2.550456 24.458412
C -1.059079 -3.259514 23.235491
C -1.718169 -2.965464 24.458412
C -3.427256 -.000000 23.235491
C -3.351267 .717699 24.458412
C -1.059079 3.259514 23.235491
C -.353024 3.409025 24.458412
C 2.772708 2.014490 23.235491
C 3.133085 1.389194 24.458412
C 3.427256 .000001 24.458410
C 3.351267 -.717698 25.681332
C 1.059083 -3.259513 24.458410
C .353028 -3.409025 25.681332
C -2.772707 -2.014491 24.458410
C -3.133085 -1.389195 25.681332
C -2.772708 2.014491 24.458410
C -2.289378 2.550457 25.681332
C 1.059080 3.259514 24.458410
C 1.718170 2.965463 25.681332
C 2.772707 2.014492 25.681332
C 3.133084 1.389196 26.904251
C 2.772710 -2.014488 25.681332
C 2.289381 -2.550454 26.904251
C -1.059079 -3.259514 25.681332
C -1.718169 -2.965464 26.904251
C -3.427256 -.000000 25.681332
C -3.351267 .717699 26.904251
C -1.059082 3.259513 25.681332
C -.353027 3.409025 26.904251
C 1.059081 3.259514 26.904251
C 1.718171 2.965462 28.127171
C 3.427256 -.000002 26.904251
C 3.351266 -.717701 28.127171
C 1.059080 -3.259514 26.904251
C .353025 -3.409025 28.127171
C -2.772707 -2.014491 26.904251
C -3.133085 -1.389195 28.127171
C -2.772708 2.014491 26.904251
C -2.289378 2.550457 28.127171
C -1.059081 3.259514 28.127172
C -.353026 3.409025 29.350094
C 2.772709 2.014489 28.127172
C 3.133085 1.389193 29.350094
C 2.772708 -2.014490 28.127172
C 2.289379 -2.550456 29.350094
C -1.059079 -3.259514 28.127172
C -1.718169 -2.965464 29.350094
C -3.427256 -.000000 28.127172
C -3.351267 .717699 29.350094
C -2.772709 2.014489 29.350094
C -2.289380 2.550455 30.573013
C 1.059081 3.259514 29.350094
C 1.718171 2.965462 30.573013
C 3.427256 .000001 29.350094
C 3.351267 -.717698 30.573013
C 1.059083 -3.259513 29.350094
C .353028 -3.409025 30.573013
C -2.772707 -2.014491 29.350094
C -3.133085 -1.389195 30.573013
C -3.427256 -.000002 30.573013
C -3.351267 .717697 31.795933
C -1.059081 3.259514 30.573013
C -.353026 3.409025 31.795933
C 2.772707 2.014492 30.573013
C 3.133084 1.389196 31.795933
C 2.772710 -2.014488 30.573013
C 2.289381 -2.550454 31.795933
C -1.059079 -3.259514 30.573013
C -1.718169 -2.965464 31.795933
C -2.772706 -2.014493 31.795935
C -3.133082 -1.389200 33.018856
C -2.772709 2.014489 31.795935
C -2.289380 2.550455 33.018856
C 1.059077 3.259515 31.795935
C 1.718169 2.965464 33.018856
C 3.427256 .000004 31.795935
C 3.351268 -.717695 33.018856
C 1.059083 -3.259513 31.795935
C .353028 -3.409025 33.018856
C -1.059076 -3.259515 33.018856
C -1.718165 -2.965466 34.241776
C -3.427256 -.000002 33.018856
C -3.351267 .717697 34.241776
C -1.059084 3.259513 33.018856
C -.353030 3.409025 34.241776
C 2.772705 2.014495 33.018856
C 3.133083 1.389199 34.241776
C 2.772710 -2.014488 33.018856
C 2.289381 -2.550454 34.241776
C 1.059079 -3.259514 34.241776
C .353027 -3.409025 35.464695
C -2.772710 -2.014488 34.241776
C -3.133085 -1.389194 35.464695
C -2.772707 2.014492 34.241776
C -2.289377 2.550457 35.464695
C 1.059081 3.259514 34.241776
C 1.718171 2.965462 35.464695
C 3.427256 -.000002 34.241776
C 3.351266 -.717701 35.464695
C 2.772708 -2.014491 35.464695
C 2.289381 -2.550455 36.687614
C -1.059083 -3.259513 35.464695
C -1.718170 -2.965463 36.687614
C -3.427256 .000001 35.464695
C -3.351267 .717700 36.687614
C -1.059081 3.259514 35.464695
C -.353026 3.409025 36.687614
C 2.772709 2.014489 35.464695
C 3.133085 1.389193 36.687614
C 3.427256 .000000 36.687614
C 3.351267 -.717696 37.910534
C 1.059079 -3.259514 36.687614
C .353027 -3.409025 37.910534
C -2.772710 -2.014488 36.687614
C -3.133085 -1.389194 37.910534
C -2.772709 2.014489 36.687614
C -2.289380 2.550455 37.910534
C 1.059081 3.259514 36.687614
C 1.718171 2.965462 37.910534

Binary file not shown.

View File

@ -0,0 +1,72 @@
#! /bin/bash -f
echo " "
echo " "
echo "****************************************************"
echo "* *"
echo "* CLUSTER GEOMETRY ANALYSIS CODE *"
echo "* *"
echo "****************************************************"
echo " "
echo " "
#
time -p ./es_mod/Sym_Analys/clus_sym <<Fin >& error.dat
Cluster.xyz # Input cluster file
0 # Tetrahedra detection
0 # Octahedra detection
0 # Cube detection
0 # Hollow molecules detection
0 # Nanotube detection
0 # Regular polygons detection
0 # Iregular polygons detection
1 # Symmetries detection
Fin
#
# Checking for errors in the execution
#
cat error.dat | sed -e '1,35d' \
-e '/real/,/ /d' > error.txt
#
# Checking for a blend of dialog
#
DIAL=`which dialog | cut -d: -f2 | grep -c 'dialog'`
XDIA=`which Xdialog | cut -d: -f2 | grep -c 'Xdialog'`
KDIA=`which kdialog | cut -d: -f2 | grep -c 'kdialog'`
ZENI=`which zenity | cut -d: -f2 | grep -c 'zenity'`
#
if [ "$ZENI" -ne "0" ]; then
DIALOG=zenity
else
if [ "$XDIA" -ne "0" ]; then
DIALOG=Xdialog
else
if [ "$KDIA" -ne "0" ]; then
DIALOG=kdialog
else
if [ "$DIAL" -ne "0" ]; then
DIALOG=dialog
else
DIALOG=none
fi
fi
fi
fi
#
FILE=`ls -at | grep .lis | awk '{print $1}'`
tail --lines=10 $FILE | grep '<<<<' | sed 's/<</ /g' | sed 's/>>/ /g' >> run.txt
cat run.txt >> error.txt
ERR=`cat error.txt`
NLINE=`cat error.txt | wc -l`
#
if [ $NLINE != 0 ]; then
if [ "$DIALOG" = "zenity" ]; then
zenity --width 400 --height 180 \
--title "MsSpec-1.1 runtime error" \
--info --text "The code has stopped with the message : \n \n \n $ERR" \
--timeout 5
fi
fi
#
rm -f error.dat error.txt run.txt
#
exit

View File

@ -0,0 +1 @@
__all__ = ["Delaunay_Intersphere","Convex_Hull_Cover"]

View File

@ -0,0 +1,712 @@
# coding: utf-8
import unittest
import math
import numpy as np
import delaunay.core as delc
import es_sym_analys as essyma
import es_tools as tool
import es_clustering as esclus
from scipy.spatial import ConvexHull, Voronoi
# ===========
from ase import Atoms
from ase.visualize import view
from ase.data import covalent_radii
# ===================================================================
# List of routines :
"""
=================
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]) : 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_AllRadius = [] # Global Variable taking all empty spheres radius
# ===================================================================
def ES_AllRadius_Updater(NewES, Structure, list=1):
# Update ES_AllRadius global variable with new radius of empty spheres given as NewES
global ES_AllRadius
StrPos = np.ndarray.tolist(Structure.positions)
for ES in np.ndarray.tolist(NewES.positions):
# print("ES = {}".format(ES))
Tempo = []
for A in StrPos:
# print("A = {}".format(A))
d = tool.distance(ES, A)
Tempo.append(d - Atom_Radius(Structure, StrPos.index(A), list))
ES_AllRadius.append(min(Tempo))
return ES_AllRadius
# ===================================================================
def Voronoi_Vertex(Structure):
# Uses Delaunay triangulation to create a tetrahedral mesh from the set of
# atoms-centers coordinates, then computes each tetrahedron's inerty center
# Returns list of tetrahedrons-centers coordinates
# Be careful on tetrahedralisation done : the cube for example will not be correctly tesselated (6tetrahedrons in)
"""
Triag=delc.Triangulation(set)
tetracenters=[]
for tetra in Triag.indices:
x = y = z = 0.0
for vertex in tetra:
x += set[vertex][0] / 4.0
y += set[vertex][1] / 4.0
z += set[vertex][2] / 4.0
tetracenters.append((x, y, z))
"""
struct = np.ndarray.tolist(Structure.positions)
Vor = Voronoi(struct)
return np.ndarray.tolist(Vor.vertices)
# ===================================================================
def Delaunay_Tetrahedral_ES(Structure, minsize=0, maxsize=999, tol=0.6):
# Uses Delaunay triangulation to create a tetrahedral mesh from the Structure of
# atoms-centers coordinates, then adds empty sphere in each tetrahedron, equidistant to all Atoms
es_data = []
allradius = []
set = Structure.positions
All_Spheres_Data = esclus.Spheres_Data_Structure_Extractor(Structure, 1)
# print All_Spheres_Data
Triag = delc.Triangulation(set)
for tetra in Triag.indices:
Data = []
for vertex in tetra:
Data.append(All_Spheres_Data[vertex])
tetra_es = esclus.Tetrahedron_ES(Data)
if tetra_es == 999:
EV = []
for vertex in tetra:
EV.append(vertex)
print(
"Error by computing tangent solution to tetrahedron {} : delta < 0 for radius determination ".format(EV))
elif tetra_es != 999: # 999 is returned when the problem has no solution due to singular matrix
if tetra_es[1] <= maxsize:
if tetra_es[1] >= minsize:
es_data.append(tetra_es)
# output.append(tetra_es[0])
"""Verify result : ________________
print("E_S created at position {}, in center of tetrahedron {}".format(output[0],tetra))
viewer = [output[-1]]
for vertex in tetra :
d=tool.distance(set[vertex],tetra_es[0])
print("Distance with vertex {} : {}\nAtom radius = {}, ES radius = {}, so the sum is : {}".format(vertex,d,All_Spheres_Data[vertex][1],tetra_es[1],All_Spheres_Data[vertex][1]+tetra_es[1]))
#viewer.append(set[vertex])
#View=Atoms("XC4",positions = viewer)
#view(View)
#raw_input("\nPress Enter to continue ...\n")
#""" # _______________________________
# print "allradius : \n",allradius
output = Fusion_Overlap(es_data, tol)
return output
# ===================================================================
def Convex_Hull_Cover(Structure, radius_es=0, tol=0.6, missing=False, Int_Ext=0):
# Uses ConvexHull to find the triangular Hull from the set, then generate a covering by adding
# an empty sphere on each hull triangle.
# Default tol value is used for Fusion Overlap
# Returns list of cover coordinates
if Int_Ext == 0:
print("Select wich covering you wish :\n0: Internal Cover\n1: External Cover\n2: Both Cover")
CovChoice = input()
Cover_Data = []
set = np.ndarray.tolist(Structure.positions)
if radius_es == 0:
radius_es = input("Please select the radius of empty spheres you desire : ")
hull = ConvexHull(set)
xc = yc = zc = 0
lh = len(hull.vertices)
for hpt in hull.vertices:
xc += set[hpt][0] / lh
yc += set[hpt][1] / lh
zc += set[hpt][2] / lh
Centroid = [xc, yc, zc]
if missing == False: # It means we don't care if some hull points are not implemented because they are in facet
# Computes the centroïd of the hull
AllData = esclus.Spheres_Data_Structure_Extractor(Structure, 1)
for facet in hull.simplices: # hull.simplices contains index of simplices vertex, grouped by 3.
Data = []
for vertex in facet:
Data.append(AllData[vertex])
ES1, ES2 = esclus.Triangle_ES(Data, radius_es)
if ES1 == 666:
print("For radius {}, no solution of tangent to triangle {} ".format(radius_es, facet))
if ES1 != 666:
ES = Select_Int_Ext(Centroid, ES1, ES2, 1) # Last parameter != 0 => external cover
if tool.distance(Centroid, ES) < 1000000000:
Cover_Data.append([ES, radius_es])
elif missing == True:
Data = esclus.Spheres_Data_Structure_Extractor(Structure, 1)
HullPlane = tool.cleanlist(np.ndarray.tolist(hull.equations))
for HP in HullPlane:
a, b, c, d = HP
HPList = []
HPIndex = []
for pt in set:
x, y, z = pt
if abs(a * x + b * y + c * z + d) < 0.01: # Then pt is in Plane HP
HPList.append(pt)
HPIndex.append(set.index(pt))
Tria = Plane_Triangulation(HPList, HP)
for tria in Tria.indices:
Spheres_data = [Data[HPIndex[tria[0]]], Data[HPIndex[tria[1]]], Data[HPIndex[tria[2]]]]
ES1, ES2 = esclus.Triangle_ES(Spheres_data, radius_es)
if ES1 == 666:
print("For radius {}, no solution of tangent to triangle {} ".format(radius_es, [HPIndex[tria[0]],
HPIndex[tria[1]],
HPIndex[tria[2]]]))
if ES1 != 666:
# ES = Select_Int_Ext(Centroid, ES1, ES2, 1) # Last parameter != 1 => external cover
# Cover_Data.append([ES, radius_es])
if CovChoice > 1:
Cover_Data.append([ES1, radius_es])
Cover_Data.append([ES2, radius_es])
else:
ES = Select_Int_Ext(Centroid, ES1, ES2, CovChoice) # 1 : external; 0 : internal
Cover_Data.append([ES, radius_es])
"""Verify result : ________________
print("E_S created at position {}, defined on triangle {}".format(ES, facet))
viewer = [ES,ES2]
for vertex in facet:
d = tool.distance(set[vertex], ES)
print("Distance with vertex {} : {}\nAtom radius = {}, ES radius = {}, so the sum is : {}".format(vertex, d,AllData[vertex][1],radius_es,AllData[vertex][1]+radius_es))
viewer.append(set[vertex])
View=Atoms("XH4",positions = viewer)
view(View)
# raw_input("\nPress Enter to continue ...\n")
# """ # _______________________________
# Fusion overlapping spheres
Output = Fusion_Overlap(Cover_Data, tol) # tol at 1% : means fusion if d < (r1 + r2) * 0.01
return Output
# ===================================================================
def Select_Int_Ext(Centroid, E1, E2, IE):
# Returns only internal or external part of cover, using the fact hull is convex : so using his centroid
# IE = 0 : take internal part, IE != 0 : take external part
d1 = tool.distance(Centroid, E1)
d2 = tool.distance(Centroid, E2)
if IE == 0: # Internal part : the closest to centroid
if d1 < d2:
return E1
else: # External part : the farest from centroïd
if d2 < d1:
return E1
# Excepted this 2 double conditions : we have to take E2
return E2
# ===================================================================
def Internal_Hull_Cover(Structure, radius_es=0, tol=0.6, missing=False):
# Uses ConvexHull to find the triangular Hull from the set, then generate a covering by adding
# an empty sphere on each hull triangle.
# Default tol value is used for Fusion Overlap
# Returns list of cover coordinates
Cover_Data = []
set = np.ndarray.tolist(Structure.positions)
if radius_es == 0:
radius_es = input("Please select the radius of empty spheres you desire : ")
hull = ConvexHull(set)
xc = yc = zc = 0
lh = len(hull.vertices)
for hpt in hull.vertices:
xc += set[hpt][0] / lh
yc += set[hpt][1] / lh
zc += set[hpt][2] / lh
Centroid = [xc, yc, zc]
invset = tool.Invert_Coord(set, Centroid, 10)
hull = ConvexHull(invset)
if missing == False: # It means we don't care if some hull points are not implemented because they are in facet
# Computes the centroïd of the hull
AllData = esclus.Spheres_Data_Structure_Extractor(Structure, 1)
for facet in hull.simplices: # hull.simplices contains index of simplices vertex, grouped by 3.
Data = []
for vertex in facet:
Data.append(AllData[vertex])
ES1, ES2 = esclus.Triangle_ES(Data, radius_es)
if ES1 == 666:
print("For radius {}, no solution of tangent to triangle {} ".format(radius_es, facet))
if ES1 != 666:
ES = Select_Int_Ext(Centroid, ES1, ES2, 0) # Last parameter = 0 => internal cover
Cover_Data.append([ES, radius_es])
elif missing == True:
Data = esclus.Spheres_Data_Structure_Extractor(Structure, 1)
HullPlane = tool.cleanlist(np.ndarray.tolist(hull.equations))
for HP in HullPlane:
a, b, c, d = HP
HPList = []
HPIndex = []
for pt in set:
x, y, z = pt
if abs(a * x + b * y + c * z + d) < 0.01: # Then pt is in Plane HP
HPList.append(pt)
HPIndex.append(set.index(pt))
Tria = Plane_Triangulation(HPList, HP)
for tria in Tria.indices:
Spheres_data = [Data[HPIndex[tria[0]]], Data[HPIndex[tria[1]]], Data[HPIndex[tria[2]]]]
ES1, ES2 = esclus.Triangle_ES(Spheres_data, radius_es)
if ES1 == 666:
print("For radius {}, no solution of tangent to triangle {} ".format(radius_es, [HPIndex[tria[0]],
HPIndex[tria[1]],
HPIndex[tria[2]]]))
if ES1 != 666:
ES = Select_Int_Ext(Centroid, ES1, ES2, 0) # Last parameter != 0 => internal cover
Cover_Data.append([ES, radius_es])
"""Verify result : ________________
print("E_S created at position {}, defined on triangle {}".format(ES, facet))
viewer = [ES,ES2]
for vertex in facet:
d = tool.distance(set[vertex], ES)
print("Distance with vertex {} : {}\nAtom radius = {}, ES radius = {}, so the sum is : {}".format(vertex, d,AllData[vertex][1],radius_es,AllData[vertex][1]+radius_es))
viewer.append(set[vertex])
View=Atoms("XH4",positions = viewer)
view(View)
# raw_input("\nPress Enter to continue ...\n")
# """ # _______________________________
# Fusion overlapping spheres
Output = Fusion_Overlap(Cover_Data, tol) # tol at 1% : means fusion if d < (r1 + r2) * 0.01
return Output
# ===================================================================
def Internal_Hull_Centers(set):
# Uses ConvexHull to find the intern triangular Hull from the set, then computes all centers of simplices
# Returns list of centers coordinates
invset = tool.Invert_Coord(set, [0, 0, 0], 10)
hull = ConvexHull(invset)
output = []
for facet in hull.simplices:
x = y = z = 0.0
for vertex in facet:
x += set[vertex][0] / 3.0
y += set[vertex][1] / 3.0
z += set[vertex][2] / 3.0
facet_center = [x, y, z] # Center of the triangular facet
output.append(facet_center)
output = np.array(output).tolist()
return output
# ===================================================================
# ===================================================================
# ===================================================================
def ES_Fusion(set, structure, size=0):
# study the given set, and fusion some empty spheres to create a better set. Size is used for the partitionnal
# clustering, and structure assures the clustering will not reduce the distances. It it set basically to the min distance in the set.
if size == 0:
size = tool.shortest_dist(set)
# print("initial size :", size)
size = size * 11. / 10 # we add 10%, to include the very similar cases
# print("with error correction size :", size)
fusion_set = [] # output : defined like set
dmin = tool.set_set_proximity(set, structure)
hull = ConvexHull(structure)
# simplice_centers=Convex_Hull_Centers(structure+set)
while len(set) > 0:
# Define a new cluster, add as much empty spheres as possible, and regroup around the centroid
cluster = [set[0]]
centroid = cluster[0] # Initialisation of the next cluster (it may rest one element, to progress until set=void
set.pop(0)
reroll = 1
while reroll == 1:
d0_error = 0
reroll = 0 # We must scan the set everytime we change centroid, or we could miss some ES in set
for ES in set:
# Other possible condition : tool.point_set_proximity(ES, cluster)<=size
if tool.distance(ES, centroid) < size: # We can fusion to the cluster
reroll = 1 # Centroid will be updated, so reroll the scan of set
print("Fusionned a sphere to the cluster")
cluster.append(ES)
set.remove(ES) # It is in the cluster, so remove from set : we studied it
centroid = tool.Isobarycenter(cluster)
if tool.point_set_proximity(centroid,
structure) < dmin: # We have to put centroid farer to balance fusion
Nearest = tool.search_nearest(centroid, structure,
tool.point_set_proximity(centroid, structure))
V = np.ndarray.tolist(
np.array(centroid) - np.array(Nearest)) # So we need nearest structure point
d = tool.distance(centroid, Nearest)
if d == 0: # it means the centroid came right on existing structure
print("Cluster centered exactly in the structure. Size must be revised : cluster cancelled")
d0_error = 1
fusion_set += cluster
reroll = 0
break
else:
V = np.multiply(V, 1. / d) # set V as norm 1
V = np.multiply(V, dmin)
#
print("\n\n We put away form:\n{}\ndmin={}\n".format(tool.vector_norm(V), dmin))
#
centroid = np.ndarray.tolist(
np.array(Nearest) + V) # get centroid away from Nearest to dmin
#
#
#
if d0_error == 0:
fusion_set.append(centroid)
#
#
return fusion_set
# ===================================================================
def Fusion_Overlap(Spheres_Data, tol):
# Find Spheres touching each other, and fusions them. Don't return radius : only final coordinates
Output = []
ls = len(Spheres_Data)
Index = range(ls)
for iS in range(ls):
if iS in Index: # The we have to treat this case
FusionIndex = [iS]
for iOS in range(iS + 1, ls):
if iOS in Index:
S = Spheres_Data[iS][0]
OS = Spheres_Data[iOS][0]
rS = Spheres_Data[iS][1]
rOS = Spheres_Data[iOS][1]
# print("S : {}\nOS : {}".format(S,OS))
if tool.distance(S, OS) < (rS + rOS) * tol:
# print("Overlap detected : d= {}, r1 ={}, r2 = {}".format(tool.distance(S, OS),rS,rOS))
Index.remove(iOS) # S and OS are same coord or almost : we remove the last : OS
FusionIndex.append(iOS)
lf = len(FusionIndex)
x = y = z = 0
for i in FusionIndex:
x += Spheres_Data[i][0][0] / lf
y += Spheres_Data[i][0][1] / lf
z += Spheres_Data[i][0][2] / lf
Output.append([x, y, z])
# else : iS correspond to coord already fusionned
return Output
# ===================================================================
# ===================================================================
# ===================================================================
# ===================================================================
# ===================================================================
# ===================================================================
def Flat_Covering(Structure, R=0, tol=0.6, Curved=False):
# Designed for quite flat set : Searchs major plane, triangulates it, and cover both sides with empty spheres wich radius=size.
if R != 0:
NoAsk = 1
else:
NoAsk = 0
if Curved == False:
flatness = input("Please describe cluster :\nOnly one major plane : Enter 0\nMore major planes : Enter 1\n")
set = np.ndarray.tolist(Structure.positions)
struct = set
FlatCover = []
# Search major plane(s)
if flatness != 0:
AllPlanes = essyma.major_plane(struct, multiple=True)
else:
[Plane3D, Plane_eq] = essyma.major_plane(struct)
AllPlanes = [[Plane3D, Plane_eq]]
"""
for P in AllPlanes :
PlaneView = Atoms(positions=P[0])
#view(Structure+PlaneView)
view(PlaneView)
print("Plane n°{} : \nContains : {}\n PlaneEq = {}".format(AllPlanes.index(P)+1,P[0],P[1]))
#"""
# Build empty spheres for all major planes :
for AP in AllPlanes:
Plane3D, Plane_eq = AP
if NoAsk == 0:
Pview = Atoms(positions=Plane3D)
view(Pview)
print("Please select the radius of empty spheres you desire : ")
R = input("(See the view of current plane to get help)\n")
Index = []
for Ppt in Plane3D:
Index.append(struct.index(Ppt))
""" Show Details on Plane
#print("Plane : Equation is {}x+{}y+{}z+{}=0\n Plane norm is {}".format(Plane_eq[0],Plane_eq[1],Plane_eq[2],Plane_eq[3],Norm))
Lset = len(set)
name = "C" + str(Lset)
Structure = Atoms(name, positions=struct)
Plane3DView = Atoms(positions=Plane3D)
view(Structure + Plane3DView)
#""" # =====
Triang = Plane_Triangulation(Plane3D, Plane_eq)
# print("Triangulation 2D : {}".format(Triang.indices))
# Extract DataforTangent_Fourth_Sphere fromStructure
Data = esclus.Spheres_Data_Structure_Extractor(Structure, 1)
simplicenters = []
for tria in Triang.indices:
Spheres_data = [Data[Index[tria[0]]], Data[Index[tria[1]]], Data[Index[tria[2]]]]
"""
h = (Spheres_data[0][1] + Spheres_data[1][1] + Spheres_data[2][1]) #h is set as 3 times the average covalent radii of atoms
xc = 1. / 3 * (Spheres_data[0][0][0] + Spheres_data[1][0][0] + Spheres_data[2][0][0])
yc = 1. / 3 * (Spheres_data[0][0][1] + Spheres_data[1][0][1] + Spheres_data[2][0][1])
zc = 1. / 3 * (Spheres_data[0][0][2] + Spheres_data[1][0][2] + Spheres_data[2][0][2])
Addpoint = np.ndarray.tolist(np.array([xc,yc,zc])+np.multiply(Norm,h))
Spheres_data.append([Addpoint,1]) # We add one sphere with radius=0, that must be tangent to solution
"""
P1, P2 = esclus.Triangle_ES(Spheres_data, R)
if P1 == 666:
print("For radius {}, no solution of tangent to triangle {} ".format(R, [Index[tria[0]],
Index[tria[1]],
Index[tria[2]]]))
if P1 != 666:
FlatCover.append([P1, R])
FlatCover.append([P2, R])
"""
ViewPos=[Spheres_data[0][0],Spheres_data[1][0],Spheres_data[2][0],Addpoint,Cov1[0]]
print "preview :",ViewPos
Viewer=Atoms("H2OClX",positions=[Spheres_data[0][0],Spheres_data[1][0],Spheres_data[2][0],Addpoint,Cov1[0]])
view(Viewer)
Spheres_data.remove([Addpoint, 1])
Addpoint = np.ndarray.tolist(np.array([xc, yc, zc]) - np.multiply(Norm, h))
Spheres_data.append([Addpoint, 1]) # Our
print Spheres_data
Cov2 = esclus.Tetrahedron_ES (Spheres_data)
ViewPos = [Spheres_data[0][0], Spheres_data[1][0], Spheres_data[2][0], Addpoint, Cov2]
print "preview :", ViewPos
Viewer = Atoms("H2OClX", positions=[Spheres_data[0][0], Spheres_data[1][0], Spheres_data[2][0], Addpoint, Cov2[0]])
view(Viewer)
FlatCover.append(Cov1[0])
FlatCover.append(Cov2[0])
"""
"""
for tria in Triang.indices: #compute classic simplice center :
x=y=z=0
for vertex in tria:
x += set[vertex][0] / 3.0
y += set[vertex][1] / 3.0
z += set[vertex][2] / 3.0
simcen=[x,y,z]
for simcen in simplicenters :
C1=np.ndarray.tolist(np.array(simcen) + np.array(Norm))
C2=np.ndarray.tolist(np.array(simcen) - np.array(Norm))
FlatCover.append(C1)
FlatCover.append(C2)
#""" #
elif Curved == True: # With curve : Complicated to define planes... So we need to do as if it was pure 2D.
# In Case of Curved routine, ALL spheres will be included in Cover iteration.
FlatCover = []
Plane3D = Structure.positions
Plane2D = []
Projection_selected = 0
while Projection_selected == 0:
PProj = input(
"To create mesh, we need to project on a plane : please select him.\n1 for x=0\n2 for y=0\n3 for z=0\n")
if PProj == 1:
for pt in Plane3D:
Plane2D.append(pt[1:])
Projection_selected = 1
elif PProj == 3:
for pt in Plane3D:
Plane2D.append(pt[:2])
Projection_selected = 1
elif PProj == 2:
for pt in Plane3D:
Plane2D.append([pt[0], pt[2]])
Projection_selected = 1
Triang = delc.Triangulation(Plane2D)
if R == 0:
R = input("Please select the radius of empty spheres you desire : ")
Data = esclus.Spheres_Data_Structure_Extractor(Structure, 1)
simplicenters = []
for tria in Triang.indices:
Spheres_data = [Data[tria[0]], Data[tria[1]], Data[tria[2]]]
P1, P2 = esclus.Triangle_ES(Spheres_data, R)
if P1 == 666:
print("For radius {}, no solution of tangent to triangle {} ".format(R, [tria[0], tria[1],
tria[2]]))
if P1 != 666:
FlatCover.append([P1, R])
FlatCover.append([P2, R])
FlatCover = Fusion_Overlap(FlatCover, tol)
return FlatCover
# ====================================================================
def Plane_Triangulation(Plane3D, Plane_eq):
# Transform plane into same z
Norm = Plane_eq[:3]
a, b, c, d = Plane_eq
Plane2D = []
NormZ0 = [0, 0, 1]
Alpha = tool.angle_vector(Norm, NormZ0) # Angle of rotation
if Alpha % math.pi == 0: # Plane already at z=K : no rotation needed
for pt in Plane3D:
Plane2D.append(pt[:2])
else: # We must see the axis of rotation, then rotate all points :
if a == 0:
u = [1, 0, 0]
M = [-d / b, 0, 0]
else:
u = [-b / a, 1, 0]
M = [-d / a, 0, 0]
# print("Norm of Plane = {}, so : \nAlpha = {}° \nu={} and M = {}".format(Norm,Alpha * 180 / math.pi, u,M))
# Plane def by ax + by + cz + d = 0. Intercect z=0 at array ax + by + d = 0, so vector directing intercection is (-b,a,0)
nu = tool.vector_norm(u)
u = np.ndarray.tolist(np.multiply(u, 1. / nu))
# print("Intercection is line defined by vector u={} and point p={}\nAngle of rotation will be {}°".format(u,M,Alpha*180/math.pi))
# Now rotate all points with rotation angled Alpha round u.
Rview = []
for pt in Plane3D: # Translate point until axis of rotation include origin : for easier rotation
x, y, z = pt
Mm = [-M[0], -M[1], -M[2]]
pt = tool.vector_trslt(pt, Mm)
rpt = tool.rot3D(pt, Alpha, u)
rpt = tool.vector_trslt(rpt, M)
# print("pt = {}, rotate to rpt = {} (Verify same z !)".format(pt,rpt))
Rview.append(rpt)
Plane2D.append(rpt[:2])
# Triangulate with Delny : indices will be the same as original Plane : no need to invert transformation
Triang = delc.Triangulation(Plane2D)
return Triang
# ====================================================================
# ====================================================================
def Atom_Radius(Structure, n, list):
# Returns the radius of the n°th atom in Structure. 0 are placed to keep information. Unit = Angstrom
# List variable determines wich information we need
global ES_AllRadius
if 0 in Structure.numbers:
FirstES = np.ndarray.tolist(Structure.numbers).index(0) # Number of the first Empty_Spheres
N = Structure.numbers[n]
if N == 0: # Atom is X : empty sphere
Atom_Radius = ES_AllRadius[n - FirstES]
if list == 1: # Covalent Radii
Atom_Radius_List = covalent_radii
else:
print("No list found, verify list variable. Routine returns 0")
return 0
if Atom_Radius_List[N] == 0:
print ("No information on this atom, or false n° given. Routine returns 0")
return Atom_Radius_List[N]
# ===================================================================
def Convex_Hull_InterCover(set):
# Uses ConvexHull to find the triangular Hull from the set, then generate a covering by adding
# an empty sphere on each hull triangle.
# Returns list of cover coordinates
reverset = tool.Invert_Coord(set, [0, 0, 0], 2)
hull = ConvexHull(reverset)
cover_coord = []
counter = 0
for facet in hull.simplices: # hull.simplices contains index of simplices vertex, grouped by 3.
x = y = z = 0.0
for vertex in facet:
x += reverset[vertex][0] / 3.0
y += reverset[vertex][1] / 3.0
z += reverset[vertex][2] / 3.0
facet_center = [x, y, z] # Center of the triangular facet
normal_facet = hull.equations[counter][:3] # The exterior normal of the facet
addpoint = facet_center + normal_facet
cover_coord.append(addpoint)
counter += 1
cover_coord = np.array(cover_coord).tolist()
cover_coord = tool.Invert_Coord(cover_coord, [0, 0, 0], 2)
return cover_coord
# ===================================================================
def lookhull(Structure, hull):
# view the structure and his hull
hullview = []
for i in hull.vertices:
hullview.append(Structure.positions[i])
L = len(hullview)
Lookhull = Atoms(positions=hullview)
view(Lookhull)
view(Structure + Lookhull)
return 0

View File

@ -0,0 +1,61 @@
# coding: utf-8
import unittest
from subprocess import call
import numpy as np
from ase import Atoms
from ase.io import read,write
import empty_spheres as esph
import es_tools as tool
#===================================================================
def Apollonius_CCC(circles_data) :
# From the circle_data list defined like : [[list of centers coordinates 3D],[list of radius]]
# defines the Apollonius point, equidistant to the 3 circles
#___________________________________________________________
# Regroup data, and define the case
O1=circles_data[0][0]
O2=circles_data[0][1]
O3=circles_data[0][2]
r1 = circles_data[1][0]
r2 = circles_data[1][1]
r3 = circles_data[1][2]
rmin=min(circles_data[1])
nbmin=circles_data[1].count(rmin)#So it is the number of circles with the smallest radius
if nbmin==1 : #then we have 1 little circle, we go to Apollonius CCP
elif nbmin==2: #then we have 2 little circles, we go to Apollonius CPP
if r1!=rmin :
data=[[O1,r1],O2,O3]
elif r2!=rmin :
data = [[O2, r2], O1, O3]
elif r3!=rmin :
data = [[O3, r3], O1, O2]
Apollo=Apollonius_CPP(data)
elif nbmin==3 :#then the 3 circles have the same radius, so we search simply the centroid, form Apollonius PPP
data=[O1,O2,O3]
Apollo=tool.Isobarycenter(data)
return Apollo
# ===================================================================
def Apollonius_CPP(set) :
#From a set define like this : [[Coordinates of circle center,Circle radius],Coord P1, Coord P2]
# define the point equidistant to P1, P2 and the circle. We will use circular inversion method
Apollo=0
return Apollo
# ===================================================================
def Circular_Inversion (P,O,R) :
#Computes P' the image of circular inversion of P by the circle (O,R). It means OP*OP'=R²
OP=tool.vector_def(O,P)
nOP=tool.vector_norm(OP)
OPprim = (OP * R ** 2) / (nOP ** 2)
Apollo = np.ndarray.tolist(np.array(OPprim)-np.array(O))
return Apollo
# ===================================================================

View File

@ -0,0 +1,209 @@
# coding: utf-8
import unittest
from subprocess import call
import numpy as np
from numpy import sqrt, dot, cross
from numpy.linalg import norm
from ase import Atoms
from ase.io import read, write
from ase.visualize import view
import empty_spheres as esph
import es_tools as tool
# ===================================================================
# List of routines :
"""
=================
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.
=================
"""
# ===================================================================
def Tangent_Fourth_Sphere(Spheres_data, r, inout=1):
# From Spheres_data build like : [[S1],[S2],[S3]] containing spheres informations Si=[[xi,yi,zi],ri], the wanted radius r,
# and with the inout variable (equal to 1 or -1, depends on the facet, to build empty sphere in or out the hull)
# computes the center of the fourth sphere tangent to S1, S2 and S3, and with radius r
# WARNING : Require S1, S2 and S3 are tangent each to another !
# ================
# Read information
S1, S2, S3 = Spheres_data
d1 = S1[1] + r
d2 = S2[1] + r
d3 = S3[1] + r
# Solve the problem in the right space : using u,v,t as base to solution
u = tool.vector_def(S2[0], S1[0])
v = tool.vector_def(S3[0], S1[0])
unorm = tool.vector_norm(u)
vnorm = tool.vector_norm(v)
u = np.multiply(u, 1. / unorm)
v = np.multiply(v, 1. / vnorm)
w = np.multiply(S1[0], -2)
# =====
a = (d2 ** 2 - d1 ** 2 + S1[0][0] ** 2 - S2[0][0] ** 2 + S1[0][1] ** 2 - S2[0][1] ** 2 + S1[0][2] ** 2 -
S2[0][2] ** 2) / (2 * unorm)
b = (d3 ** 2 - d1 ** 2 + S1[0][0] ** 2 - S3[0][0] ** 2 + S1[0][1] ** 2 - S3[0][1] ** 2 + S1[0][2] ** 2 -
S3[0][2] ** 2) / (2 * vnorm)
c = d1 ** 2 - S1[0][0] ** 2 - S1[0][1] ** 2 - S1[0][2] ** 2
# =====
t = np.ndarray.tolist(np.cross(u, v))
tnorm = tool.vector_norm(t)
t = np.multiply(t, 1 / tnorm)
#
alpha = (a - b * np.dot(u, v)) / (1 - np.dot(u, v) ** 2)
beta = (b - a * np.dot(u, v)) / (1 - np.dot(u, v) ** 2)
d = alpha ** 2 + beta ** 2 + 2 * alpha * beta * np.dot(u, v) + alpha * np.dot(u, w) + beta * np.dot(v, w) - c
theta = (-1 * np.dot(w, t) + inout * np.sqrt(np.dot(w, t) ** 2 - 4 * d)) / 2
#
solution = np.ndarray.tolist(np.multiply(u, alpha) + np.multiply(v, beta) + np.multiply(t, theta))
return solution
# ===================================================================
def 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.
# list determines wich radius we take : 1 for covalent
set_pos = np.ndarray.tolist(Structure.positions)
set_nb = np.ndarray.tolist(Structure.numbers)
data = []
for i in range(0, len(set_nb)):
data.append([set_pos[i], esph.Atom_Radius(Structure, i, list)])
return data
# ===================================================================
def 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.
# list determines wich radius we take : 1 for covalent
set = read(name)
set_pos = np.ndarray.tolist(set.positions)
set_nb = np.ndarray.tolist(set.numbers)
data = []
for i in range(0, len(set_nb)):
data.append([set_pos[i], esph.Atom_Radius(set, i, list)])
return data
# ===================================================================
def Tetrahedron_ES(Spheres_data):
# print "Spheres datas :",Spheres_data
S1, S2, S3, S4 = Spheres_data
x1, y1, z1 = S1[0]
r1 = S1[1]
x2, y2, z2 = S2[0]
r2 = S2[1]
x3, y3, z3 = S3[0]
r3 = S3[1]
x4, y4, z4 = S4[0]
r4 = S4[1]
# Solve this problem equals to found V=[x,y,z] and r as : MV = S + r P with :
M = np.array([[x1 - x2, y1 - y2, z1 - z2], [x2 - x3, y2 - y3, z2 - z3], [x3 - x4, y3 - y4, z3 - z4]])
P = np.array([r2 - r1, r3 - r2, r4 - r3])
S = np.array([(r1 ** 2 - r2 ** 2) + (x2 ** 2 - x1 ** 2) + (y2 ** 2 - y1 ** 2) + (z2 ** 2 - z1 ** 2),
(r2 ** 2 - r3 ** 2) + (x3 ** 2 - x2 ** 2) + (y3 ** 2 - y2 ** 2) + (z3 ** 2 - z2 ** 2),
(r3 ** 2 - r4 ** 2) + (x4 ** 2 - x3 ** 2) + (y4 ** 2 - y3 ** 2) + (z4 ** 2 - z3 ** 2)])
S = np.multiply(S, 0.5)
"""
print("M= {}".format(M))
print("P= {}".format(P))
print("S= {}".format(S))
#"""
# MV = S + r P <=> V = Minv S + r MinvP, rewrite as V = Mbar + r Pbar
if np.linalg.det(M) == 0:
print("Singular matrix on the Tetrahedron Fourth SPhere Problem : So we cancel routine")
return 999
Minv = np.linalg.inv(M)
Sbar = np.matmul(Minv, S)
Pbar = np.matmul(Minv, P)
"""
print("Minv= {}\n So verify inversion : Minv * M = \n{}".format(Minv,np.matmul(M,Minv)))
print("Pbar= {}".format(Pbar))
print("Sbar= {}".format(Sbar))
#"""
# V = [x,y,z] depends on r : We need to solve r first : as a root of a 2 degree polygon
Vi = np.array(S1[0])
ri = np.array(S1[1])
D = Sbar - Vi
a = np.linalg.norm(Pbar) ** 2 - 1
b = 2 * (np.dot(D, P) - ri)
c = np.linalg.norm(Sbar) ** 2 - 2 * np.dot(Vi, Sbar) - ri ** 2
delta = b ** 2 - 4 * a * c # Theorically delta >=0
if delta < 0:
return 999
rsol = (-b - np.sqrt(delta)) / (2 * a) # radius of sphere internally tangent
# print("rsol found : {}".format(r))
# print("Verify solution : ar² + br + c = {}".format(a * rsol**2 + b * rsol + c))
# Now we can compute the solution V = x,y,z
V = -1 * (Sbar + np.multiply(Pbar, rsol))
r = tool.distance(V, Vi) - ri
# print("r that should really be : {}".format(r))
# print("Verify solution : ar² + br + c = {}".format(a * r ** 2 + b * r + c))
return [np.ndarray.tolist(V), r]
# ===================================================================
def Triangle_ES(Spheres_data, R):
# Routine posted by Andrew Wagner, Thanks to him.
# Implementaton based on Wikipedia Trilateration article, about intercection of 3 spheres
# This problem solves sphere tangent to 3 spheres : and returns the 2 solutions
P1 = np.array(Spheres_data[0][0])
r1 = Spheres_data[0][1] + R
P2 = np.array(Spheres_data[1][0])
r2 = Spheres_data[1][1] + R
P3 = np.array(Spheres_data[2][0])
r3 = Spheres_data[2][1] + R
temp1 = P2 - P1
if norm(temp1) == 0:
return 666, 666
e_x = temp1 / norm(temp1)
temp2 = P3 - P1
i = dot(e_x, temp2)
temp3 = temp2 - i * e_x
if norm(temp3) == 0:
return 666, 666
e_y = temp3 / norm(temp3)
e_z = cross(e_x, e_y)
d = norm(P2 - P1)
j = dot(e_y, temp2)
x = (r1 * r1 - r2 * r2 + d * d) / (2 * d)
y = (r1 * r1 - r3 * r3 - 2 * i * x + i * i + j * j) / (2 * j)
temp4 = r1 * r1 - x * x - y * y
if temp4 < 0:
# print("The three spheres do not intersect!")
return 666, 666
else:
z = sqrt(temp4)
p_12_a = P1 + x * e_x + y * e_y + z * e_z
p_12_b = P1 + x * e_x + y * e_y - z * e_z
p_12_a = np.ndarray.tolist(p_12_a)
p_12_b = np.ndarray.tolist(p_12_b)
return p_12_a, p_12_b
# ===================================================================

View File

@ -0,0 +1,553 @@
# coding: utf-8
import unittest
import delaunay.core as delc
from subprocess import call
import numpy as np
from scipy.spatial import ConvexHull
from ase import Atoms
from ase.io import read,write
from ase.visualize import view
import empty_spheres as esph
import es_tools as tool
import math
#===================================================================
# List of routines :
"""
=================
sym_analyse(Cluster) : Convert set into xyz folder, then finds all symetries using. Uses compare_sym and read_sym_file
major_plane(set,[multiple]) : Search the major(s) plane with max nb of points in set and returns all of his points, and his equation
if multiple = True, returns all Plane equations of planes containing enough points
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)
=================
"""
#===================================================================
def sym_analyse(Cluster) :
#This function convert the given set into a xyz folder, then uses modified clus_geom executable to find
# the set symmetries. Then, it computes and returns the list of symmetries the list into "sym"
#Sym begins with the integer number of symetries, then with all symmetry-names on strings
sym=[]
write('Cluster.xyz',Cluster) #Sym_Analys folder contains actually the cluster_geom executables
call(["./es_mod/Sym_Analys/proc_geom"])
sym=read_sym_file('sym_analysis.lis')
#For the moment, the file isn't removed...
return sym
# ===================================================================
def compare_sym(Nsym,Osym):
# Compare the new list of symmetries Nsym to the old one (Osym).
# If the lists are not the same, it tells user to fusion more empty-spheres, to keep sym unchanged
# Situation will be defined with output : 1 if symmetry conserved, 0 if not.
# If there was no symmetry except identity : (-1). And if symmetries have been gained : 2.
N=Nsym[0]
O=Osym[0]
print("Old number of sym :",O)
print("New number of sym :", N)
if N>O :
output=2
print "Symmetries gained. Symmetry-list updated"
elif O==1 :
output=-1
print "No symmetry existing to help at decision"
elif N == O:
output=1
print "Symmetry has been conserved."
elif N<O :
output=0
print "Symmetries Lost: Fusion-Operation on last empty-spheres advised"
return output
# ===================================================================
def read_sym_file(symfile) :
#The function reads the .lis file. The first argument is a 2space-int, after this,
# we have 5 space-string. Each time, we have to delete the \n readed
sym = []
file=open(symfile,"r")
NbSym=int(file.read(2))
sym.append(NbSym)
delete=file.read(1)#to delete the \n
for i in range(0,NbSym) :
Namesym=str(file.read(5))#read the 5 sym-name char, + the 2 empty spaces as char
delete=file.read(1)#to delete the \n
sym.append(Namesym)
file.close()
return sym
# ===================================================================
def major_plane(set,multiple=False) :
#This function search the major plane where we can find the maximum of set-points.
#In case of a draw, it takes the plane where the points are the closest (to be conform to molecules planes)
l=len(set)
PlanesLen=[]
#We will use this lists :
AllPlanes=[]
Plane_list=[]#List of all planes, defined by every points in this plane
Plane_eq=[] #List of 4-uplet corresponding to plane-equation for each Plane. Linked to Plane_list
#
#Regroup every unordered combinaisons of 3-uplet from set :
Pts_Combi=combinaison_3_noorder_norepeat(set)
#print("Pts_Combi={}".format(Pts_Combi))
#For every Plane in Pts_Combi, define plane equation from the 3 pts defining P
for P in Pts_Combi :
#print("3 points Combi studied : {}".format(P))
#Check caracteristics of the plane P, then update Plane_list and Plane_eq
v1=tool.vector_def(P[0],P[1])
n1=tool.vector_norm(v1)
if n1 == 0 :
print("n1 = 0 on this Pt Combi :{}\nVerify no double coordinates given".format(P))
u1=np.ndarray.tolist(np.multiply(v1,1./n1))
v2 = tool.vector_def(P[0],P[2])
n2 = tool.vector_norm(v2)
if n2 == 0 :
print("n2 = 0 on this Pt Combi :{}\nVerify no double coordinates given".format(P))
u2 = np.ndarray.tolist(np.multiply(v2, 1. / n2))
#print("The 2vectors for the potential plane : \nv1={}, of norm{}, normed to {}\nv2={},of norm{}, normed to {}".format(v1,n1,u1,v2,n2,u2))
if tool.ColinearTest(u1,u2) != 1 :#IE the 3 points are not aligned
norm=np.cross(v1,v2) #normal vector to the plane
#print("u1 :{}\nu2 :{}\n So norm is {} ".format(u1,u2,norm))
a, b, c = norm
d=np.dot(norm,P[2])
Equation=[a,b,c,d]
"""print("Equation of plane found : {}\nFrom Combi {}\n".format(Equation,P))
if abs(a)+abs(b)+abs(c)==0 :
print("More Details :\n\nv1 = {}\nv2={}\nSo norm ={}\n\n".format(v1,v2,norm))
#"""
# Add points on Plane list
if Equation not in Plane_eq :
Plane_pts=[]
Plane_eq.append(Equation)
for pt in set :
x, y, z = pt
#print("Pt : {}, Eq : {}\nSo ax +by + cz +d = {}".format(pt, Equation, a * x + b * y + c * z + d))
if abs(a * x + b * y + c * z + d) < 0.02:
Plane_pts.append(pt)
Plane_list.append(Plane_pts)
PlanesLen.append(len(Plane_pts))
#Else : Case already found
#Find now the plane with the most points in :
#print("Plane_list = {}".format(Plane_list))
if multiple == False :
Plane = Plane_list[0]
for P in Plane_list[1:]:
if len(P) > len(Plane):
Plane = P
elif len(P) == len(Plane):
if tool.longest_dist(P) < tool.longest_dist(Plane):
Plane = P
# Else, Plane remains the one with the most and the nearest points
#
index = Plane_list.index(Plane)
Plane_eq = Plane_eq[index]
return [Plane, Plane_eq]
elif multiple == True :
#print("We have Planelist at {} element and Planeeq at {} elements".format(len(Plane_list),len(Plane_eq)))
CleanLen=tool.cleanlist(PlanesLen)
print("Planes include number of spheres in {}".format(sorted(CleanLen)))
planesize = input("Please select minimal size of plane we should take : ")
AllPlanes = []
for P in Plane_list :
if len(P) >= planesize :
index=Plane_list.index(P)
PEQ=Plane_eq[index]
#AllPlanes.append(PEQ)
AllPlanes.append([P,PEQ])
"""
print("In total we have {} planes".format(len(AllPlanes)))
for P in AllPlanes:
print("P n°{} : Equation : {}\nPoints : {}\n".format(AllPlanes.index(P) + 1, P[1],P[0]))
#"""
#Clean Double Planes : ax + by + cz + d = 0 <=> -ax -by -cz -d =0
AllPlanesIndex = range(len(AllPlanes))
Output = []
for iP in range(len(AllPlanes)):
if iP in AllPlanesIndex:
for iOP in range(iP + 1, len(AllPlanes)):
Plist = AllPlanes[iP][0]
OPlist = AllPlanes[iOP][0]
if Plist == OPlist:
AllPlanesIndex.remove(iOP) # P and OP are same Planes as they include same points
for I in AllPlanesIndex:
Output.append(AllPlanes[I])
return Output
# ===================================================================
def combinaison_3_noorder_norepeat(set) :
#returns every 3-uplet combinaison of the set, with no repetition, and no order variation (ie :{1,2,3}={1,3,2})
l=len(set)
Combi = []
for i in range(0, l - 2):
for j in range(i + 1, l - 1):
for k in range(j + 1, l):
Combi.append([set[i], set[j], set[k]])
return Combi
# ===================================================================
def Cluster_flatness_informations(set) :
#returns the total volume and area of the set's hull, then
hull= ConvexHull(set)
set_area=hull.area
set_volume=hull.volume
return[set_volume,set_area]
# ===================================================================
def Cluster_search_hollow(set,tol) :
#Returns the hollow datas : hollow=[[[list],center,volume]...] where list is hollow vertices, and center and volume respectively
#his center and volume.'Tol' can be 2 (big) or sqrt(3) to consider cubes as hollow, or even sqrt(2) to consider terahedrons as hollow
#Notice that no hollows are detected fo a pentagone, due to his particular alignment of vertices...
#Compute Centroid of Cluster :
dmin = tool.shortest_dist(set)
#Search for hollows
hollow = []
L=len(set)
for i in range(0,L-1):
for j in range(i+1,L) :
#Data print (debug) :
P1=set[i]
P2=set[j]
print("i,j : {},{}\nP1 :{}\nP2 :{}\ndistance(P1,P2):{}\n dmin={} so tol becomes {}\n".format(i+1,j+1,P1,P2,tool.distance(P1,P2),dmin,tol*0.99*dmin))
if tool.distance(P1,P2) > 0.99*tol * dmin :#Then we may have find a hole center between P1 and P2
hcenter=tool.Midpoint(P1,P2)
d = tool.distance(P1,P2)/2
print("hcenter is distant form set to {}...\nMust be > {}".format(tool.point_set_proximity(hcenter, set),0.98*d))
if tool.point_set_proximity(hcenter, set) > 0.98*d :#We have a hollow there :
hollow_list=[]
hollowinvset=tool.Invert_Coord(set,hcenter,10)#10 is just decent : the volumewill be multiplied by 1000
hollowhull=ConvexHull(hollowinvset)
hollow_index =np.ndarray.tolist(hollowhull.vertices)
hollow_volume=hollowhull.volume/1000
hollow.append([hollow_index,hcenter,hollow_volume])
print("Hollow founded :{}\n".format(hcenter))
#
#else : no hollow finally
# else : P1 and P2 pretty neighboors
#
#
return hollow
# ===================================================================
def Cluster_emptyness_informations(Structure) :
#returns the % of volume of hull occupied by his spheres
Allproportions=[]
set=Structure.positions
hull= ConvexHull(set)
#esph.lookhull(Structure,hull)
set_volume=hull.volume
spheres_volume=0
for pt in range(0,len(set)) : #Study all set point from index.
print("Pt n°{}".format(pt))
if pt in hull.vertices :
#pt is a boundary point. We need to know the proportion of the sphere in the hull.
proportion=Vertice_Sphere_Proportion(pt,hull,Structure)
else : #We must verify sphere is completely is the cluster or not : it can have a cap out :
NeibPt=Neighbors_List(pt,Structure)
NeibInHull=tool.commonlist(NeibPt,hull.vertices)
if len(NeibInHull) != 0 :#We must search the cap cutting the sphere.
cap=facet_cap(pt,Structure,hull)
proportion = 1 - cap
else :
proportion = 1
previous = spheres_volume
spheres_volume += proportion * (4 * math.pi / 3) * (esph.Atom_Radius(Structure,pt,1)**3)
print("So sphere n°{} is at proportion {} in hull : We add {} to total sphere volume, being at {} now".format(pt,proportion,spheres_volume-previous,spheres_volume))
raw_input("\nPress Enter to continue ...\n")
Allproportions.append(proportion)
print "AllProportions : ",Allproportions
print("Hull volume = {}".format(set_volume))
return spheres_volume / set_volume * 100
# ===================================================================
# ===================================================================
def Vertice_Sphere_Proportion(O,hull,Structure) :
#Returns the proportion of the sphere centered in O in the hull (proportion in ]O,1]).
#O is the index of the center in hull vertices list.
R = esph.Atom_Radius(Structure, O, 1) # Radius of O
Vertices_list = np.ndarray.tolist(hull.vertices)
Simplices_list = np.ndarray.tolist(hull.simplices)
Point_list = np.ndarray.tolist(hull.points)
Norm_list = np.ndarray.tolist(hull.equations)
#print("Vertices : {}\nSimplices :{}\nPoints : {}\nO :{}".format(Vertices_list,Simplices_list,Point_list,O))
Proportion = 0 #initialisation
Neighbors=Hull_Neighbors_List(O,Structure,hull)
print("Neighbors :{}".format(Neighbors))
#Neighbors=tool.commonlist(Neighbors,Vertices_list)#We delete all neighbors not included in the hull
Neighbors.insert(0,O)
#print("Neighbors in hull :{}".format(Neighbors))
if len(Neighbors) == 3 : #O is on a ridge
Pt=[]
for pt in Neighbors :
Pt.append(hull.points[pt])
Ox = tool.vector_def(Pt[0],Pt[1])
Oy = tool.vector_def(Pt[0], Pt[2])
Alpha=tool.angle_vector(Ox,Oy)
Proportion=Alpha / (2 * math.pi)
else :
if len(Neighbors) < 3 : #There is an error here
print("Hull is strangely defined : you can check on view")
#Test if O is in a big facet :
norms=[]
for Sim in Simplices_list :
if O in Sim :
indx=Simplices_list.index(Sim)
norm=Norm_list[indx][:3]
if norm not in norms :
norms.append(norm)
if len(norms)==1 : #Only one norm for all facets containing O : O in center of a big facet
Proportion = 0.5
else : #Here the work begins : O top of a pyramid
Triag = delc.Triangulation(Structure.positions)
for tetra in Triag.indices :
if O in tetra: #A tetrahedron containing O : O will be counted as top
print("Tetraedron found with {} in : {}".format(O,tetra))
tetra.remove(O)
P1 = Point_list[tetra[0]]
P2 = Point_list[tetra[1]]
P3 = Point_list[tetra[2]]
#View tetrahedron :
Tetraview=Atoms("XH3",positions=[Point_list[O],P1,P2,P3])
view(Structure+Tetraview)
V1 = tool.vector_def(Point_list[O], P1)
V2 = tool.vector_def(Point_list[O], P2)
V3 = tool.vector_def(Point_list[O], P3)
print "Vectors from top of pyramid to base points" , [V1, V2, V3]
a = tool.angle_vector(V1, V2)
b = tool.angle_vector(V2, V3)
c = tool.angle_vector(V3, V1)
# """____________________________________________________________
# L'Huilier Theorem :
p = (a + b + c) / 2
#print("p = {} , or {}° , Angles : {}, {}, and {}".format(p,p*180/math.pi,a*180/math.pi,b*180/math.pi,c*180/math.pi))
S = 4 * np.arctan(
np.sqrt(np.tan(p / 2) * np.tan((p - a) / 2) * np.tan((p - b) / 2) * np.tan((p - c) / 2)))
#print("L'huilier pocess :\nS =4arctan( Sqrt( tan(p/2)*tan(p-a/2)* tan(p-b/2) * tan(p-c/2) ) )\n =4arctan(sqrt(tan({})*tan({})*tan({})*tan({})))\n =4arctan(sqrt({}*{}*{}*{}))\n =4arctan({})\n = {} \n\n Sphere Volume = {}, so proportion = {}".format(p/2,(p-a)/2,(p-b)/2,(p-c)/2,np.tan(p/2),np.tan((p-a)/2),np.tan((p-b)/2),np.tan((p-c)/2),np.tan(p/2)*np.tan((p-a)/2)*np.tan((p-b)/2)*np.tan((p-c)/2),S,(4 * math.pi),S / (4 * math.pi)))
print("S with Huilier = {}".format(S))
# """____________________________________________________________
# """____________________________________________________________
# Spherical area computing : Sinus formula + Girard's formula
cosT = (np.cos(c) - np.cos(a)*np.cos(b)) / (np.sin(a)*np.sin(b))
Theta = np.arccos(cosT)
sinT = np.sin(Theta)
sinA = np.sin(a) * sinT / np.sin(c)
sinB = np.sin(b) * sinT / np.sin(c)
print("sinB detail : b = {}, sin(b) = {}, sinT = {},c = {} sin(c) = {}\So : sinB = {}".format(b,np.sin(b),sinT,c,np.sin(c),sinB))
if sinA >=1 :
Alpha=math.pi/2
else :
Alpha = np.arcsin(sinA)
if sinB >=1 :
Beta=math.pi/2
else :
Beta = np.arcsin(sinB)
S = (Alpha + Beta + Theta - math.pi)
print("S with sinus and Girard's formulaes = {}".format(S))
#"""____________________________________________________________
Proportion += S / (4 * math.pi)
print("We had {} to actual {} Proportion, being now at {}".format(S / (4*math.pi), Proportion - S / (4 * math.pi), Proportion))
#else the tetraedron in disconnected to the center of sphere : no need to study him
print("proportion : {}".format(Proportion, 1. / Proportion))
"""Little thing to see exactly what happens
print("proportion : {} , or 1/{}".format(Proportion, 1./Proportion))
Lset=len(Neighbors)-1
set = []
Lhul=len(np.ndarray.tolist(hull.points))
namehull= "C" +str(Lhul)
HullView=Atoms(namehull,positions=np.ndarray.tolist(hull.points))
for pt in Neighbors:
set.append(Point_list[pt])
set.remove(Point_list[O])
nameset= "H" + str(Lset)
ViewNeig=Atoms(nameset,positions=set)
Ostr=Atoms(positions=[Point_list[O]])
view(HullView+ViewNeig+Ostr)
#"""
return Proportion
# ===================================================================
def hull_search_neighbors(O,Simplices) :
#Returns the list including O in fist position and all of his neighbors (list contains only index, no coordinates)
Neil=[O]
print("O in the search neighbors routine :",O)
for facet in Simplices :
if O in facet :
for i in facet :
if i not in Neil :
Neil.append(i)
#else : i still in neighbors list
return Neil
# ===================================================================
def convex_base(Neil,Simplices) :
#Order the neighbors list by listing them so as each consecutives elements in the list are themselves neighbors.
#So it returns all Neil[0] neighbors so as ConvBase is the base of pyramid from top Neil[0]
ConvBase=[]
ConvBase.append(Neil[1])
Rest=Neil[2:]
print("Initial Neil : ",Neil)
while len(Rest)>0 :
for i in Rest :
print("Convbase :{}\nRest :{}\nactual i:{}".format(ConvBase, Rest,i))
Last=ConvBase[-1]
print(Last)
Neighborsi=hull_search_neighbors(i,Simplices)
print ("The last elements :{}\nHis neighbors : {}".format(ConvBase[-1],Neighborsi))
if ConvBase[-1] in Neighborsi :
ConvBase.append(i)
Rest.remove(i)
return ConvBase
# ===================================================================
def Neighbors_List(numAtom,Structure) :
# Returns the list of all atom's indexes in Structure wich are neighbors of Atom indexed numAtom (numAtom must be int, 0 included)
set=np.ndarray.tolist(Structure.positions)
radAtom=esph.Atom_Radius(Structure,numAtom,1)
posAtom=set[numAtom]
Neilist=[]
for i in range(0,len(set)) :
int(i)
radi=esph.Atom_Radius(Structure,i,1)
D = tool.distance(set[i],posAtom) - radi - radAtom
if D < 0.1 :
Neilist.append(i)
Neilist.remove(numAtom)
return Neilist
# ===================================================================
def Hull_Neighbors_List(O,Structure,hull) :
# Returns the list of all atom's indexes in hull wich are neighbors of Atom indexed numAtom (numAtom must be int, 0 included)
Allfacets = np.ndarray.tolist(hull.simplices)
Hull_Neilist = []
print Allfacets
for facet in Allfacets :
if O in facet :
for index in facet :
if index not in Hull_Neilist :
Hull_Neilist.append(index)
Hull_Neilist.remove(O)
return Hull_Neilist
# ===================================================================
def facet_cap(O,Structure,hull) :
#From the hull, the Structure and the index of the sphere center O (int), return the proportion of sphere cuted by hull facets
#(ie the caps out of the hull). cap is returned proportionnal to total sphere volume (ie cap in [0,1[)
cap = 0
Center = Structure.positions[O]
Radius = esph.Atom_Radius(Structure,O,1)
AllFacets = np.ndarray.tolist(hull.equations)
hullplan=tool.cleanlist(AllFacets)
cutplan = [] # List of all cuting plan equations
cuthigh = [] # List of cuting high, corelated to cutplan
cutpoint = [] #List containing for each plan one point of the hull included in this plan (used for overlap routine)
for facet_plan in AllFacets:
d=tool.dist_point_plan(Center,facet_plan)
if d<Radius : #Then we can have a cap out : but first verify that the facet isn't to far away
plan_index = AllFacets.index(facet_plan)
FacetPtsIndex = np.ndarray.tolist(hull.simplices[plan_index])
FacetPtsCoord=[]
for facetpt in FacetPtsIndex :
FacetPtsCoord.append(np.ndarray.tolist(hull.points[facetpt]))
Facetnorm=facet_plan[:3]
GoOnPlan=np.multiply(Facetnorm,d/tool.vector_norm(Facetnorm))
OrthoProjO = np.ndarray.tolist(hull.points[O] + GoOnPlan) #OrthoProjO is the orthogonal projection of O on facet_plan
# Sphere cut by facet only if OrtoProjO is in the facet : sum of angles with facet points must be 2*pi
V0 = tool.vector_def(OrthoProjO, FacetPtsCoord[0])
V1 = tool.vector_def(OrthoProjO, FacetPtsCoord[1])
V2 = tool.vector_def(OrthoProjO, FacetPtsCoord[2])
SumAngle= np.abs(tool.angle_vector(V0,V1))+np.abs(tool.angle_vector(V0,V2))+np.abs(tool.angle_vector(V1,V2))
print("Sum of angle : {} = {}°".format(SumAngle,SumAngle*180/math.pi))
if SumAngle > 1.99 * math.pi :#considering a little error here from 1% : because soon OrthoProjO is in a facet ridge
if facet_plan not in cutplan :#If OrthoProjO in a ridge, the plan can be counted twice or more...
h=Radius-d
cutplan.append(facet_plan)
cuthigh.append(h)
cutpoint.append(FacetPtsCoord[0]) # Just one point is enough :
# Draw to see better :===================
Draw = FacetPtsCoord
Draw.append(np.ndarray.tolist(hull.points[O]))
Draw.append(OrthoProjO)
DrawView = Atoms("X3CH", positions=Draw)
view(DrawView)
# =======================================
"""________________________________________________________________
# Little thing to see exactly what happens
hullview = []
for i in hull.vertices:
hullview.append(Structure.positions[i])
L = len(hullview)
Lookhull = Atoms(positions=hullview)
set = []
for ffp in AllFacets :
if ffp == cutplan :
FacetPts = np.ndarray.tolist(hull.simplices[plan_index])
for pt in FacetPts:
if pt not in set :
set.append(np.ndarray.tolist(hull.points[pt]))
nameset = "H" + str(len(set))
ViewFacet = Atoms(nameset, positions=set)
Ostr = Atoms("C", positions=[np.ndarray.tolist(hull.points[O])])
view(Lookhull + ViewFacet + Ostr)
# __________________________________________________________________"""
if len(cutplan) <1 :
print("Finally no plan cutting our sphere...\n\n" )
if len(cutplan) == 1 :#Only one plan cuting sphere : One cap to be calculated :
h = cuthigh[0]
print("Sphere (radius {}) is cuted by one plan at cap from high {}\n Plan equation :{} ".format(Radius, h,
facet_plan))
Vtot = 4 * math.pi * (Radius ** 3) / 3
Vcap = math.pi * (h ** 2) / 3 * (3 * Radius - h)
cap += Vcap / Vtot
if len(cutplan) > 1:
print("We have {} plans cutting the sphere.... We have to calculate overlap... ".format(len(cutplan)))
return cap

View File

@ -0,0 +1,278 @@
# coding: utf-8
from ase import Atoms
from ase.visualize import view
import numpy as np
import math
# List of tools :
"""
=================Vector tools==================
vector_def(A,B) : returns simply the vector translating A to B
vector_norm(V) : return euclidian norm of a vector
vector_trslt(P,V) : return image of translation of P from vector V
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)
"""
# ========================Vector Tools=============================
def vector_def(A, B):
# returns simply the vector translating A to B
l = len(A)
if l != len(B):
print("Major Error : The 2 given points aren't same dimensioned :\nA = {}\nB={}".format(A, B))
V = []
for i in range(0, l):
V.append(B[i] - A[i])
return V
# ==========================================
def vector_norm(V):
# return euclidian norm of a vector
l = (len(V))
Origin = np.ndarray.tolist(np.zeros(l))
return distance(V, Origin)
# ==========================================
def vector_trslt(P, V):
x, y, z = P
a, b, c = V
return [x + a, y + b, z + c]
# ==========================================
def 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
OP = tool.vector_def(O, P)
OP = np.ndarray.tolist(np.multiply(OP, 1. / tool.vector_norm(OP)))
output = np.ndarray.tolist(np.multiply(OP, d))
return output
# ==========================================
def angle_vector(u, v):
# returns the value in radian of the convex angle defined by vectors u and v.
U = vector_norm(u)
V = vector_norm(v)
UV = np.dot(u, v)
# print("U : {}\nV : {}\n u.v : {}".format(U,U,UV,))
if UV == 0:
# print("u.v = 0, on a donc un angle de {}, soit {}°".format(math.pi/2,math.pi/2*180/math.pi))
return math.pi / 2
else:
# print("L'angle est donc donc arccos({}) = {} = {}°".format(UV / (U * V),np.arccos(UV/(U*V)),np.arccos(UV/(U*V))*180/math.pi))
return np.arccos(UV / (U * V))
# ==========================================
def ColinearTest(u, v):
# Tests if u and v colinear
L = len(u)
k = 999999999999 # initial value, to be sure first ktest will become k
if L != len(v):
print(
"Error : u and v ant same dimension : \nu={}\nv={}\nSo we return u and v not aligned... but verify consequences".format(
u, v))
else:
for i in range(0, L):
if u[i] == 0 or v[i] == 0:
if u[i] != 0 or v[i] != 0:
return 1
else:
ktest = u[i] / v[i]
if k == 999999999999:
k = ktest
elif np.abs(k - ktest) < 0.0000001: # We accept almost colinearité at 1/10^6
return 1
return 0
# ==========================================
# ==========================================
# ==========================================
# ==============Distance and Proximity Tools=======================
def distance(a, b):
# Calculate distance between 2 points
d = 0.0
dim = len(a)
for i in range(0, dim):
d += (b[i] - a[i]) ** 2
d = np.sqrt(d)
return d
# ==========================================
def 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
for p in set:
if distance(point, p) == d:
return p
# ==========================================
# ==========================================
def point_set_proximity(point, set):
# returns the min distance between the point and the set of points
d = distance(point, set[0])
for p in set[1:]:
d = min(d, distance(point, p))
return d
# ==========================================
def set_set_proximity(S1, S2):
# returns minimal distance between each points of each sets.
d = point_set_proximity(S1[0], S2)
for s in S1[1:]:
d = min(d, point_set_proximity(s, S2))
return d
# ==========================================
def longest_dist(set):
# returns the longest distance between the points in the set
L = len(set)
if L < 2:
print("Major ERROR : the set has become a 1-UPLET")
quit()
else:
d = distance(set[L - 2], set[L - 1]) # The last ones
for i in range(0, L - 2):
for j in range(i + 1, L - 2):
d = max(d, distance(set[i], set[j]))
return d
# ==========================================
def shortest_dist(set):
# returns the shortest distance between the points in the set
L = len(set)
if L < 2:
print("Major ERROR : the set has become a 1-UPLET")
quit()
else:
d = distance(set[L - 2], set[L - 1]) # The last ones
for i in range(0, L - 2):
for j in range(i + 1, L - 2):
d = min(d, distance(set[i], set[j]))
return d
# ==========================================
def dist_point_plan(Pt, Plan):
# From Pt=[x,y,z] and Plan=[a,b,c,d] corresponding to plan's equation ax + by + cz + d = 0, give the distance beetween Pt and the Plan
x, y, z = Pt
a, b, c, d = Plan
dist = np.abs(a * x + b * y + c * z + d) / np.sqrt(a ** 2 + b ** 2 + c ** 2)
return dist
# ======================Construction tools=========================
def Isobarycenter(set):
# Calculate isobarycenter of a set of points. Returns his coordinates
x = y = z = 0.0
l = len(set)
for point in set:
x += point[0] / l
y += point[1] / l
z += point[2] / l
# print("New Centroid of cluter:",[x,y,z])
return [x, y, z]
# ==========================================
def Invert_Coord(set, O, r):
# Apply circular inversion to every point in the set, excepted for origin, remaining origin.
output = []
for point in set:
if point == O:
output.append(point)
else:
D = distance(O, point)
OP = vector_def(O, point)
OP = np.multiply(OP, 1. / D) # set OP to unity vector
add = np.ndarray.tolist(np.array(O) + np.multiply(OP, r ** 2 / D))
output.append(add)
return output
# ==========================================
def Midpoint(P1, P2):
# From points coordinates P1 and P2 : construct the middle point of [P1,P2]
output = np.ndarray.tolist(np.multiply((np.array(P1) + np.array(P2)), 0.5))
return output
# ==========================================
def rot3D(P, A, u):
# Verify first if u is unity vector :
if vector_norm(u) != 1:
u = np.ndarray.tolist(np.multiply(u, 1. / vector_norm(u)))
# From P in R3, A in R and u in R3, returns the image from P's rotation around u from angle A
x, y, z = P
ux, uy, uz = u
c = np.cos(A)
s = np.sin(A)
# We compute directly the result : see the 3D rotation matrix
X = x * (ux ** 2 * (1 - c) + c) + y * (ux * uy * (1 - c) - uz * s) + z * (ux * uz * (1 - c) + uy * s)
Y = x * (ux * uy * (1 - c) + uz * s) + y * (uy ** 2 * (1 - c) + c) + z * (uy * uz * (1 - c) - ux * s)
Z = x * (ux * uz * (1 - c) - uy * s) + y * (uy * uz * (1 - c) + ux * s) + z * (uz ** 2 * (1 - c) + c)
return [X, Y, Z]
# =========================Data Tools==============================
def commonlist(L1, L2):
# Returns a list of elements common to L1 and L2, ie output is L1 intercection with L2
output = []
for i in L1:
if i in L2:
output.append(i)
return output
# ==========================================
def cleanlist(list):
# Returns a list without repeated elements (each element in cleaned list appears only once)
ls = len(list)
Index = range(ls)
Output = []
for iS in range(ls):
if iS in Index:
for iOS in range(iS + 1, ls):
S = list[iS]
OS = list[iOS]
if S == OS:
Index.remove(iOS) # S and OS are same coord or almost : we remove the last : OS
# else : iS correspond to coord needed to be deleted, so we don't add it
for I in Index:
Output.append(list[I])
return Output
# ==========================================

53
src/msspec/es/main.py Normal file
View File

@ -0,0 +1,53 @@
# coding: utf-8
from ase import Atoms
from ase.io import write,read
from ase.visualize import view
from scipy.spatial import ConvexHull
from es_mod import empty_spheres as esph
"""=============Generate empty spheres in copper cluster
Structure = read('cluster_examples/copper.xyz')
struct = np.ndarray.tolist(Structure.positions)
set = esph.Delaunay_Intersphere(struct)
Set=Atoms(positions=set)
view(Structure+Set)
view(Set)
#"""#====================================================
from msspec.calculator import MSSPEC
from msspec.utils import *
#"""=============Use Python MsSpec
cluster = read('cluster_examples/GeCl4.xyz')
# Set the absorber (the deepest atom centered in the xy-plane)
cluster.absorber = 0
# Create a calculator for the PhotoElectron Diffration
calc = MSSPEC(spectroscopy='PED')
# Set the cluster to use for the calculation
calc.set_atoms(cluster)
# Run the calculation
data = calc.get_theta_scan(level='2p3/2', kinetic_energy=[320,325,5])
# Show the results
data.view()
#"""#===============================
#"""===================MsSpec on ClusterC Test :
cluster = read('ClusterFinal.xyz')
# Set the absorber (the deepest atom centered in the xy-plane)
cluster.absorber = 0
# Create a calculator for the PhotoElectron Diffration
calc = MSSPEC(spectroscopy='PED')
# Set the cluster to use for the calculation
calc.set_atoms(cluster)
# Run the calculation
data = calc.get_theta_scan(level='2p3/2', kinetic_energy=[320,325,5])
# Show the results
data.view()
#"""#===============================

1188
src/msspec/iodata.py Normal file

File diff suppressed because it is too large Load Diff

86
src/msspec/misc.py Normal file
View File

@ -0,0 +1,86 @@
# coding: utf-8
"""
Module misc
===========
"""
import logging
from pint import UnitRegistry
import numpy as np
import inspect
import re
class XRaySource(object):
MG_KALPHA = 1253.6
AL_KALPHA = 1486.6
def __init__(self):
pass
UREG = UnitRegistry()
UREG.define('rydberg = c * h * rydberg_constant = Ry')
UREG.define('bohr_radius = 4 * pi * epsilon_0 * hbar**2 / electron_mass / e**2 = a0')
logging.basicConfig(level=logging.INFO)
LOGGER = logging.getLogger('msspec')
np.set_printoptions(formatter={'float': lambda x:'%.2f' % x}, threshold=5)
def set_log_level(level):
lvl = getattr(logging, level.upper())
LOGGER.setLevel(lvl)
def set_log_output(stream):
LOGGER.parent.handlers[0].stream = stream
def get_call_info(frame):
args, varargs, varkw, loc = inspect.getargvalues(frame)
_, _, function, _, _ = inspect.getframeinfo(frame)
s = '%s called with:\n' % function
for kws in (args, varargs, varkw):
if kws != None:
if isinstance(kws, (tuple, list)):
for kw in kws:
s += '\t\t%s = %r\n' % (kw, loc[kw])
else:
s += '\t\t%s = %r\n' % (kws, loc[kws])
return s.strip()
def log_process_output(process, logger=None, severity=logging.INFO):
if logger == None:
logger = logging
else:
logger = logging.getLogger(logger)
logger.setLevel(LOGGER.getEffectiveLevel())
for line in iter(process.stdout.readline, b''):
logger.log(severity, line.rstrip('\n'))
process.stdout.close()
process.wait()
def get_level_from_electron_configuration(notation):
l_letters = 'spdfghiklmnoqrtuv'
n_letters = 'klmnopqrstuv'
pattern = re.compile(r'(?P<n>\d+)(?P<l>[%s%s])'
r'((?P<j>\d)/2)?' % (l_letters, l_letters.upper()))
m = pattern.match(notation)
assert m, 'Not a valid notation'
n, l, _, j = m.groups()
n = int(n)
l = l_letters.index(l.lower())
assert (l < n), 'Invalid l'
j1 = abs(l - 0.5)
j2 = abs(l + 0.5)
if j:
j = int(j) / 2.
else:
j = j1
assert j in (j1, j2), 'Invalid j'
letter = n_letters[n - 1]
subscript = str(int((2 * (l + j) + 1) / 2))
if letter == 'k':
subscript = ''
return '{}{}'.format(letter, subscript)

1
src/msspec/msspecgui/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/contribs/

View File

@ -0,0 +1 @@
__version__ = '1.2rc3.post152'

View File

@ -0,0 +1,8 @@
# from __future__ import absolute_import
from .operator import Operator
from .ioperatorcreator import IOperatorCreator
from .idataflowserializer import IDataflowSerializer
from msspecgui.dataflow.dataflow import DataFlow
from .idatatype import IDataType
from .plug import Plug
from .wire import Wire

View File

@ -0,0 +1,24 @@
class Attribute(object):
def __init__(self, attr_name, attr_type_name, attr_desc, is_pluggable=True):
"""
:param attr_name: the name of this attribute, eg 'file_name'
:type attr_name: str
:param attr_type_name: the id of the type of this attribute (eg 'string'). Note that this type of attribute should have been registered in the dataflow first
:type attr_type_name: str
:param attr_desc: a string describing the role of this attribute
:type attr_desc: str
"""
self._attr_name = attr_name
self._attr_type_name = attr_type_name
self._attr_desc = attr_desc
self.is_pluggable = is_pluggable # indicates that this attribute can be plugged to another one using a wire. If not, then the value of this attribute is set using other means (eg graphical user interface)
@property
def name(self):
return self._attr_name
@property
def type_name(self):
return self._attr_type_name

View File

@ -0,0 +1,229 @@
# from __future__ import absolute_import
# from .plug import Plug
from .wire import Wire
class DataFlow(object):
''' a flow of operators, each of them having inputs and outputs that are connected together
'''
class IDataFlowEventsHandler(object):
"""an abstract class that listens and responds to events affecting a flow of operators
"""
def on_added_operator(self, operator):
"""this method is invoked whenever an operator is added to the flow
:param dataflow.Operator operator: the operator that has just been added to the flow
"""
pass
def on_deleted_operator(self, operator):
"""this method is invoked whenever an operator is deleted from the data flow
:param dataflow.Operator operator: the operator that has just been deleted
"""
pass
def on_modified_operator(self, operator):
"""this method is invoked whenever an operator is modified in the flow
:param operator: the operator that has just been modified
:type operator: dataflow.Operator
"""
pass
def on_added_wire(self, wire):
"""this method is invoked whenever a wire is added to the flow
:param wire: the wire that has just been added to the flow
:type wire: dataflow.Wire
"""
pass
def on_deleted_wire(self, wire):
"""this method is invoked whenever a wire is deleted from the flow
:param dataflow.Wire wire: the wire that has just been deleted
"""
pass
def __init__(self):
self._last_created_operator_id = 0 # each operator (node) has a uniqueid in the flow
self._operators = {} #: :type self._operators: dict[int, msspec.dataflow.Operator]
self._registered_creators = {} # This factory lists and creates cluster creator instances
self._registered_data_types = {} #: :type self._registered_data_types: dict(str, msspecgui.dataflow.IDataType) ; the list of known data types (for links between nodes) in this dataflow
self._dataflow_events_handlers = []
self._wires = []
@property
def last_created_operator_id(self):
return self._last_created_operator_id
@last_created_operator_id.setter
def last_created_operator_id(self, operator_id):
"""initializes the operator id generator
useful for restoring the dataflow state, eg after loading
:param int operator_id:
"""
self._last_created_operator_id = operator_id
def add_dataflow_events_handler(self, dataflow_events_handler):
"""adds a dataflow events handler to the list of dataflow events handler
:type dataflow_events_handler: IDataFlowEventsHandler
"""
self._dataflow_events_handlers.append(dataflow_events_handler)
def remove_dataflow_events_handler(self, dataflow_events_handler):
"""removes a dataflow events handler to the list of dataflow events handler
:type dataflow_events_handler: IDataFlowEventsHandler
"""
self._dataflow_events_handlers.remove(dataflow_events_handler)
def add_operator(self, operator):
"""
:type operator: msspec.dataflow.Operator
"""
self._operators[operator.id] = operator
for dataflow_events_handler in self._dataflow_events_handlers:
dataflow_events_handler.on_added_operator(operator)
def delete_operator(self, operator):
"""
:param msspec.dataflow.Operator operator:
"""
data_flow = operator.data_flow
# delete incoming wires
for plug in operator.get_input_plugs():
if plug.is_connected():
wire = plug.incoming_wire
data_flow.delete_wire(wire)
# delete outgoing wires
for plug in operator.get_output_plugs(): #: :type plug: Plug
if plug.is_connected():
for wire in plug.outgoing_wires:
data_flow.delete_wire(wire)
self._operators.pop(operator.id)
for dataflow_events_handler in self._dataflow_events_handlers:
dataflow_events_handler.on_deleted_operator(operator)
def on_modified_operator(self, operator):
"""tells the dataflow that the given operator has been modified (eg its paraeters have been changed)
:type operator: Operator
"""
# print("on_modified_operator : operator %d" % operator.id)
operator.set_dirty()
for deh in self._dataflow_events_handlers:
deh.on_modified_operator(operator)
def get_operator(self, operator_id):
"""
:param int operator_id:
:rtype: msspec.dataflow.Operator
"""
return self._operators[operator_id]
def find_operator(self, operator_name):
"""
:param str operator_name:
:rtype: msspec.dataflow.Operator
"""
for op in self.operators:
if op.name == operator_name:
return op
return None
@property
def operators(self):
"""
:rtype: list(Operator)
"""
return list(self._operators.itervalues())
def get_new_operator_id(self):
"""
:rtype: int
"""
self._last_created_operator_id += 1
return self._last_created_operator_id
def register_operator_creator(self, operator_creator):
'''
:param operator_creator:
:type operator_creator: derived from IOperatorCreator
'''
self._registered_creators[operator_creator.get_operator_type_id()] = operator_creator
def register_data_type(self, data_type):
'''
:param data_type: the type of this argument is expected to be derived from IDataType
:type data_type: derived from IDataType
'''
self._registered_data_types[data_type.get_type_id()] = data_type
def get_data_type(self, data_type_id):
"""
:return msspecggui.dataflow.IDataType:
"""
return self._registered_data_types[data_type_id]
def get_operator_creators(self):
"""
:rtype: list(dataflow.ioperatorcreator.IOperatorCreator)
"""
return list(self._registered_creators.itervalues())
@property
def wires(self):
"""
:rtype: list(msspec.dataflow.Wire)
"""
return self._wires
def create_operator(self, operator_type_id):
""" creates an operator of the given type and adds it to this flow
:param operator_type_id: the type of operator to create. This type must be one of the allowed types for this flow (in other words, this type must have been registered to this flow)
"""
creator = self._registered_creators[operator_type_id]
return creator.create_operator(self)
def create_wire(self, input_plug, output_plug):
"""
:param Plug input_plug: the input plug
:param Plug output_plug:
:return dataflow.Wire:
"""
wire = Wire(input_plug, output_plug)
input_plug.add_outgoing_wire(wire)
output_plug.incoming_wire = wire
self._wires.append(wire)
for dataflow_events_handler in self._dataflow_events_handlers:
dataflow_events_handler.on_added_wire(wire)
return wire
def delete_wire(self, wire):
"""
:param Wire wire: the wire that needs to be destroyed
"""
wire.input_plug.detach_wire(wire)
wire.output_plug.detach_wire(wire)
self._wires.remove(wire)
for dataflow_events_handler in self._dataflow_events_handlers:
dataflow_events_handler.on_deleted_wire(wire)
return wire

View File

@ -0,0 +1,69 @@
from .idatatype import IDataType
class StringDataType(IDataType):
def __init__(self):
IDataType.__init__(self)
def get_type_id(self):
"""
Returns a string uniquely identifying this data type
"""
return 'string'
def get_python_class(self): # pylint: disable=no-self-use
"""
see IDataType.get_python_class
"""
return str
class FloatDataType(IDataType):
def __init__(self):
IDataType.__init__(self)
def get_type_id(self):
"""
Returns a string uniquely identifying this data type
"""
return 'float'
def get_python_class(self): # pylint: disable=no-self-use
"""
see IDataType.get_python_class
"""
return float
class BoolDataType(IDataType):
def __init__(self):
IDataType.__init__(self)
def get_type_id(self):
"""
Returns a string uniquely identifying this data type
"""
return 'bool'
def get_python_class(self): # pylint: disable=no-self-use
"""
see IDataType.get_python_class
"""
return bool
class IntDataType(IDataType):
def __init__(self):
IDataType.__init__(self)
def get_type_id(self):
"""
Returns a string uniquely identifying this data type
"""
return 'int'
def get_python_class(self): # pylint: disable=no-self-use
"""
see IDataType.get_python_class
"""
return int

View File

@ -0,0 +1,26 @@
import abc
class IDataflowSerializer(object):
@abc.abstractmethod
def save_dataflow(self, dataflow, filepath):
""" saves the given dataflow at the given file location
:param dataflow: the dataflow to save
:type dataflow: msspecgui.dataflow.DataFlow
:param file_path: the path of the file that will store this dataflow in serialized form
:type file_path: str
"""
pass
@abc.abstractmethod
def load_dataflow(self, file_path, dataflow):
"""loads the dataflow from the given serialized dataflow file
:param str file_path: the xml file describng the dataflow to load
:param msspecgui.dataflow.DataFlow dataflow: an empty dataflow that will be filled
:rtype: DataFlow
"""
return None

View File

@ -0,0 +1,59 @@
import abc
class IDataType(object):
class IAction(object):
"""an abstract class that represents an action that can be performed on a data of the given data type
for example, the export cluster is an action (with a gui that allows the user to select a file path) that can be performed on a data of type physics.atomscluster. This mechanism is used in the dataflow editor : when the user right-clicks on a wire, a list of actions related to the wire's datatype is offered to the user.
"""
def __init__(self, datatype):
"""
:param msspecgui.dataflow.IDataType datatype: the datatype on which this action is expected to perform
"""
self.datatype = datatype
@abc.abstractmethod
def get_name(self):
""" returns a name that uniquely identifies the action amongst actions associated with a given IDataType
"""
assert False
@abc.abstractmethod
def execute_on_data(self, data):
"""executes this action on the given data
:param data: the data on which this action is performed
"""
assert False
def __init__(self):
self._actions = {} #: :type self._actions: dict(str, IDataType.IAction)
def get_type_id(self): # pylint: disable=no-self-use
"""
Returns a string iuniquely identifying this data type
:rtype: str
"""
assert False # this method is expected to be defined in a derived class
def get_python_class(self): # pylint: disable=no-self-use
"""
returns the python class associated with this data type
"""
assert False # this method is expected to be defined in a derived class
def register_datatype_action(self, action):
"""
:type action: IDataType.IAction
"""
assert action.datatype == self
self._actions[action.get_name()] = action
def get_actions(self):
"""
:rtype: list(IDataType.IAction)
"""
return list(self._actions.itervalues())

View File

@ -0,0 +1,101 @@
from .attribute import Attribute
import abc
class IOperatorCreator(object):
'''abstract base class that allows registration of operator creators
'''
class IAction(object):
"""an abstract class that represents an action that can be performed on an operator type
for example, the ase.lattice.bulk operator's gui (property settings) is an action that can be performed on an operator of type ase.lattice.bulk
"""
def __init__(self):
pass
@abc.abstractmethod
def get_name(self):
""" returns a name that uniquely identifies the action amongst actions associated with a given IOperatorCreator
"""
assert False
@abc.abstractmethod
def execute_on_operator(self, operator):
"""executes this action on the given operator
:param operator: the operator on which this action is performed
:type operator: dataflow.Operator
"""
assert False
def __init__(self):
self._input_attrs = {}
self._output_attrs = {}
self._actions = {}
@abc.abstractmethod
def get_operator_type_id(self): # pylint: disable=no-self-use
'''returns the unique id of the type of the operator that this creator creates (eg ipr.msspec.simpleclustercreator)
'''
assert False # this method should be implemented in a derived class
@abc.abstractmethod
def create_operator(self, dflow): # pylint: disable=no-self-use
'''creates an application specific operator in the given Dataflow dflow
:type dflow: DataFlow
'''
assert False # this method should be implemented in a derived class
def register_operator_action(self, action):
"""
:type action: dataflow.ioperatorcreator.IOperatorCreator.IAction
"""
self._actions[action.get_name()] = action
def get_actions(self):
"""
:rtype: list(dataflow.ioperatorcreator.IOperatorCreator.IAction)
"""
return list(self._actions.itervalues())
def add_input_attribute(self, attr_name, attr_type_name, attr_desc, is_pluggable=True):
"""Declares a new input attribute for the related operator
:param attr_name: the name of the attribute
:type attr_name: str
:param attr_type_name: the name of the type of the attribute (eg 'math.position3')
:type attr_type_name: str
:param attr_desc: the description of the attribute
:type attr_desc: str
:rtype: Attribute
"""
attr = Attribute(attr_name, attr_type_name, attr_desc, is_pluggable=is_pluggable)
self._input_attrs[attr_name] = attr
return attr
def add_output_attribute(self, attr_name, attr_type_name, attr_desc, is_pluggable=True):
"""Declares a new output attribute for the related operator
:param attr_name: the name of the attribute
:type attr_name: str
:param attr_type_name: the name of the type of the attribute (eg 'math.position3')
:type attr_type_name: str
:param attr_desc: the description of the attribute
:type attr_desc: str
:rtype: Attribute
"""
attr = Attribute(attr_name, attr_type_name, attr_desc, is_pluggable=is_pluggable)
self._output_attrs[attr_name] = attr
return attr
def get_input_attributes(self):
return self._input_attrs.itervalues()
def get_output_attributes(self):
return self._output_attrs.itervalues()

View File

@ -0,0 +1,120 @@
# import plug
# from .dflow import DataFlow
from .plug import Plug
class Operator(object):
'''
an abstract operator that generates outputs from its inputs
'''
def __init__(self, data_flow, creator):
'''
Constructor
:param data_flow: the data_flow that will contain this operator
:type data_flow: DataFlow
:param creator: an instance of IOperatorCreator, the creator of this object (consider it as an object that stores informations that are common to all operators of this type).
:type creator: dataflow.ioperatorcreator.IOperatorCreator
'''
self._data_flow = data_flow
self._creator = creator
self._id = data_flow.get_new_operator_id()
self._input_plugs = {}
self._output_plugs = {}
for attr in self._creator.get_input_attributes():
data_type = self._data_flow.get_data_type(attr.type_name)
assert data_type is not None
p = Plug(attr, data_type, Plug.PlugSide.INPUT, self)
self._input_plugs[p.name] = p
for attr in self._creator.get_output_attributes():
data_type = self._data_flow.get_data_type(attr.type_name)
assert data_type is not None
p = Plug(attr, data_type, Plug.PlugSide.OUTPUT, self)
self._output_plugs[p.name] = p
@property
def name(self):
return '%s_%d' % (self._creator.get_operator_type_id(), self._id)
def get_input_plugs(self):
"""
:rtype: list(msspecgui.dataflow.Plug)
"""
return list(self._input_plugs.itervalues())
def get_output_plugs(self):
"""
:rtype: list(msspecgui.dataflow.Plug)
"""
return list(self._output_plugs.itervalues())
def get_plug(self, plug_name):
"""
:type plug_name: str
:rtype: msspecgui.dataflow.Plug
"""
if plug_name in self._input_plugs:
return self._input_plugs[plug_name]
if plug_name in self._output_plugs:
return self._output_plugs[plug_name]
@property
def wires(self):
"""
:return list(msspecgui.dataflow.Wire)
"""
wires = []
for p in self._input_plugs.itervalues():
if p.is_connected():
wires.append(p.incoming_wire)
for p in self._output_plugs.itervalues():
if p.is_connected():
for wire in p.outgoing_wires:
wires.append(wire)
return wires
def set_dirty(self):
"""
indicates that the output values of this operator are obsolete and need to be recomputed
"""
print("setting operator %d as dirty" % self.id)
for plug in self.get_output_plugs():
plug.set_dirty()
def update(self):
"""
compute this operator's output values from its input values
this method is supposed to be overriden in derived classes
"""
assert False # should be implemented in derived classes
def all_inputs_are_available(self):
for input_plug in self.get_input_plugs():
if not input_plug.value_is_available():
return False
return True
@property
def id(self):
"""
:rtype: int
"""
return self._id
@id.setter
def id(self, operator_id):
"""changes the unique identifier of this operator (use at your own risk)
:param int operator_id: the new unique identifier for this operator
"""
self._id = operator_id
@property
def creator(self):
return self._creator
@property
def data_flow(self):
return self._data_flow

View File

@ -0,0 +1,219 @@
# from __builtin__ import False
class Plug(object):
"""
A Plug represents an input or output attribute on a dataflow operator
:param __operator: the operator that contains this plug
:type __operator: dataflow.Operator
"""
class PlugSide(object):
INPUT = 1
OUTPUT = 2
def __init__(self, attr, data_type, plug_side, operator):
"""Constructor
:param attr: an instance of Attribute
:type attr: msspec.dataflow.Attribute
:param data_type: the type of the attribute
:type data_type: IDataType
:param plug_side: tells if this plug is an input plug or an output plug
:type plug_side: Plug.PlugSide enum
:param operator: the operator that contains this plug
:type operator: msspec.dataflow.Operator
"""
self._attr = attr
self._data_type = data_type
self._plug_side = plug_side
self._operator = operator
self._outgoing_wires = []
self._incoming_wire = None
self._value = None
@property
def name(self):
"""
:rtype: str
"""
return self._attr.name
@property
def id(self):
"""
:rtype: str
"""
return '%s.%s' % (self.operator.name, self.name)
@property
def data_type(self):
return self._data_type
@property
def operator(self):
"""
:rtype: msspec.dataflow.Operator
"""
return self._operator
@property
def is_pluggable(self):
return self._attr.is_pluggable
def get_value(self):
print("getting value of plug %s" % self.id)
assert self.value_is_available()
if self.is_dirty():
self._update_value()
assert not self.is_dirty()
return self._value
def set_value(self, value):
"""
:type value: any type of data that is compatible with this plug's datatype
"""
assert isinstance(value, self._data_type.get_python_class())
if self.is_pluggable:
assert self.is_dirty() # we expect to only have to use this method when this plug is dirty
self._value = value
def value_is_available(self):
"""
indicates if the value of this plug can be computed
"""
if self.is_destination():
if not self.is_pluggable:
return True # if this plug is not pluggable, then its value is available since it doesn't depend on the result of other operators
# print('value_is_available: self.is_connected() = %s' % str(self.is_connected()))
if self.is_connected():
src_plug = self.get_incoming_plug()
return src_plug.value_is_available()
else:
return False
else:
# this is an output plug; its value is available if all the operators's input plugs are available
return self.operator.all_inputs_are_available()
def set_dirty(self):
"""
indicates that the value of this plug is obsolete and needs to be recomputed
"""
print("setting plug %s as dirty" % self.id)
self._value = None
if self.is_output_plug():
if self.is_connected():
for connected_plug in self.get_outgoing_plugs():
connected_plug.set_dirty()
else:
# this is an operator's input plug. Propagate the dirtiness to its out plugs
self.operator.set_dirty()
def is_dirty(self):
"""
indicates if the value needs to be computed
"""
assert self.value_is_available()
if not self.is_pluggable:
return False # a non-pluggable plug is never dirty, it always has a valid value
return self._value is None
def _update_value(self):
print("updating value of plug %s" % self.id)
assert self.value_is_available()
assert self.is_dirty()
if self.is_destination():
if self.is_connected():
self._value = self.get_incoming_plug().get_value()
else:
assert False # if we are in this case, this would mean that the value is not available
else:
print("updating operator %s" % self._operator.name)
self._operator.update()
assert not self.is_dirty()
def is_connected(self):
if self.is_source():
return len(self._outgoing_wires) != 0
else:
return self._incoming_wire is not None
def is_source(self):
return self._plug_side == Plug.PlugSide.OUTPUT
def is_destination(self):
return self._plug_side == Plug.PlugSide.INPUT
def is_output_plug(self):
return self._plug_side == Plug.PlugSide.OUTPUT
def is_input_plug(self):
return self._plug_side == Plug.PlugSide.INPUT
def connect_to(self, other_plug):
"""
:type other_plug: dataflow.Plug
:rtype: dataflow.Wire
"""
return self._operator.data_flow.create_wire(self, other_plug)
def add_outgoing_wire(self, wire):
"""
:type wire: dataflow.Wire
"""
assert self.is_source()
self._outgoing_wires.append(wire)
@property
def incoming_wire(self):
assert self.is_destination()
assert self.is_connected()
return self._incoming_wire
@incoming_wire.setter
def incoming_wire(self, wire):
"""
:type wire: dataflow.Wire
"""
assert self.is_destination()
self._incoming_wire = wire
self.operator.set_dirty() # one of the operator's input plugs has changed, therefore its output plugs values are obsolete
@property
def outgoing_wires(self):
"""
:return list(dataflow.Wire):
"""
assert self.is_source()
assert self.is_connected()
return self._outgoing_wires
def detach_wire(self, wire):
"""
:param msspec.dataflow.Wire wire:
"""
assert self.is_connected()
if self.is_destination():
assert self._incoming_wire == wire
self.incoming_wire = None
self.set_dirty() # the data on this plug is no longer available
else:
assert self.is_source()
self._outgoing_wires.remove(wire)
def get_incoming_plug(self):
assert self.is_destination()
assert self.is_connected()
return self.incoming_wire.input_plug # (scenegraph_group.setter seen as a method, see https://github.com/PyCQA/pylint/issues/870) pylint: disable=no-member
def get_outgoing_plugs(self):
"""
:rtype: list(dataflow.Plug)
"""
assert self.is_source()
assert self.is_connected()
outgoing_plugs = []
for wire in self._outgoing_wires:
outgoing_plugs.append(wire.output_plug)
return outgoing_plugs

View File

@ -0,0 +1,33 @@
'''
Created on Jul 1, 2016
@author: graffy
'''
class Wire(object):
"""
a wire connects an input plug to an output plug
"""
def __init__(self, input_plug, output_plug):
"""
:type input_plug: dataflow.Plug
:type output_plug: dataflow.Plug
"""
self.input_plug = input_plug
self.output_plug = output_plug
@property
def data_type(self):
"""
:return IDataType: the datatype of this wire
"""
return self.input_plug.data_type
@property
def data_flow(self):
"""
:return msspecgui.dataflow.DataFlow
"""
return self.input_plug.operator.data_flow

View File

@ -0,0 +1,6 @@
from .dataflowview import DataflowView
from .operatorwidget import OperatorWidget
from .plugwidget import PlugWidget
from .wirewidget import WireWidget
from .operatorgui import OperatorGui
from msspecgui.datafloweditor.dotlayoutmanager import DotLayoutManager

View File

@ -0,0 +1,342 @@
"""
a graphical editor for dataflow
"""
from __future__ import print_function
# import wx
import wx.lib.wxcairo
import cairo
# from wx.lib.scrolledpanel import ScrolledPanel
# import scenegraph2d.xml
import msspecgui.scenegraph2d.cairo
# import dataflow
import msspecgui.dataflow as dataflow
# import msspecgui.datafloweditor as datafloweditor
from .plugwidget import PlugWidget
from .wirewidget import WireWidget
from msspecgui.datafloweditor.dotlayoutmanager import DotLayoutManager
# class DataflowView(wx.ScrolledWindow):
class DataflowView(wx.Panel):
'''
a dataflow Graphical User Interface
:type self.wire_being_created_widget: WireWidget
'''
# see https://wiki.wxwidgets.org/Scrolling
class DataflowEventsHandler(dataflow.DataFlow.IDataFlowEventsHandler):
"""
handles dataflow events
"""
def __init__(self, data_flow_view):
"""
:param msspec.datafloweditor.DataflowView data_flow_view:
"""
super(DataflowView.DataflowEventsHandler, self).__init__()
self.data_flow_view = data_flow_view
def on_added_operator(self, operator):
super(DataflowView.DataflowEventsHandler, self).on_added_operator(operator)
# a new operator has just been added to the dataflow
# create a new widget to graphically manipulate the added operator
self.data_flow_view.add_operator_widget(msspecgui.datafloweditor.OperatorWidget(operator, self.data_flow_view))
def on_deleted_operator(self, operator):
super(DataflowView.DataflowEventsHandler, self).on_deleted_operator(operator)
self.data_flow_view.delete_widget_for_operator(operator)
self.data_flow_view.update_appearance() # possibly update the appearance of the plugs that have now become available
def on_added_wire(self, wire):
super(DataflowView.DataflowEventsHandler, self).on_added_wire(wire)
# create a new widget to graphically represent and manipulate the added wire
wire_widget = self.data_flow_view.add_wire_widget(wire) # @UnusedVariable
self.data_flow_view.update_appearance() # possibly update the appearance of the plugs that have now become available
def on_deleted_wire(self, wire):
super(DataflowView.DataflowEventsHandler, self).on_deleted_wire(wire)
self.data_flow_view.delete_widget_for_wire(wire)
self.data_flow_view.update_appearance() # possibly update the appearance of the plugs that have now become available
def __init__(self, parent, data_flow, log):
"""
:type parent: wx.Window
:type data_flow: DataFlow
"""
wx.Panel.__init__(self, parent, -1)
self.scenegraph_group_to_widget = {} # this array associates a datafloweditor.Widget instance to a scenegraph group that represents this widget
self.layout_is_enabled = True
self.log = log
self.layout_manager = DotLayoutManager()
self.scene = None # the 2d scenegraph that describes the graphical aspect of the dataflow
self.scene = msspecgui.scenegraph2d.Group()
# with open('/Users/graffy/ownCloud/ipr/msspec/rectangle.svg') as f:
# self.scene = scenegraph2d.xml.parse(f.read())
self.operator_to_widget = {}
self.wire_to_widget = {}
self.dataflow = data_flow
# self.selected_widget = None
self.hovered_widget = None # the widget hovered on by the mouse pointer
self.plug_being_connected = None # when the user creates a wire, memorizes the first selected plug
self.wire_being_created_widget = None # while the use creates a wire, the widget that is used to represent it
self.is_left_down = False
self.Bind(wx.EVT_PAINT, self.on_paint)
self.Bind(wx.EVT_LEFT_DOWN, self.on_left_down)
self.Bind(wx.EVT_LEFT_UP, self.on_left_up)
self.Bind(wx.EVT_MOTION, self.on_move)
self.Bind(wx.EVT_CONTEXT_MENU, self.on_context_menu)
self.data_flow_view_updater = DataflowView.DataflowEventsHandler(self)
self.layout_is_enabled = False # temporarily disable the layout manager for efficiency (prevent complete layout computation for each operator in the data_flow)
data_flow.add_dataflow_events_handler(self.data_flow_view_updater)
# initialize the widgets to reflect the state of the cluster flow
for node in self.dataflow.operators:
self.data_flow_view_updater.on_added_operator(node)
for wire in self.dataflow.wires:
self.data_flow_view_updater.on_added_wire(wire)
self.layout_is_enabled = True
self.update_operators_position()
def on_paint(self, evt): # IGNORE:unused-argument
"""handler for the wx.EVT_PAINT event
"""
if self.IsDoubleBuffered():
display_context = wx.PaintDC(self)
else:
display_context = wx.BufferedPaintDC(self)
display_context.SetBackground(wx.Brush('white'))
display_context.Clear()
self.render(display_context)
def update_appearance(self):
for operator_widget in self.operator_to_widget.itervalues():
operator_widget.update_appearance()
def update_operators_position(self):
if self.layout_is_enabled:
op_pos = self.layout_manager.compute_operators_position(self.dataflow)
dodgy_offset = (20.0, 20.0) # TODO: make it better
dodgy_scale = 1.5
for op, pos in op_pos.iteritems():
x, y = pos
print("update_operators_position : %f %f" % (x, y))
if op in self.operator_to_widget: # while loading the dataflow, there are operators that don't have widgets yet
op_widget = self.operator_to_widget[op]
op_widget.set_position(x * dodgy_scale + dodgy_offset[0], y * dodgy_scale + dodgy_offset[1])
def add_operator_widget(self, operator_widget):
"""
:type operator_widget: an instance of OperatorWidget
"""
self.operator_to_widget[operator_widget.operator] = operator_widget
widget_root = msspecgui.scenegraph2d.Group()
self.scene.add_child(widget_root)
# widget_root.transform = [msspecgui.scenegraph2d.Translate(operator_widget.get_id() * 100.0 + 50.0, 60.0)]
operator_widget.render_to_scene_graph(widget_root)
self.update_operators_position()
# self.UpdateWindowUI(wx.UPDATE_UI_RECURSE)
# print("self.UpdateWindowUI has executed")
# self.Refresh()
def delete_widget_for_operator(self, operator):
"""
:param Operator operator: the operator for which we want to delete the widget
"""
op_widget = self.get_operator_widget(operator)
op_widget.remove_from_scene_graph()
self.operator_to_widget.pop(operator)
def add_wire_widget(self, wire):
"""
creates a wire widget and adds it to this dataflow view
:param wire: the wire represented by the widget to create
:type wire: Wire
:rtype: WireWidget
"""
wire_widget = WireWidget(self)
wire_widget.source_plug_widget = self.get_plug_widget(wire.input_plug)
wire_widget.dest_plug_widget = self.get_plug_widget(wire.output_plug)
self.wire_to_widget[wire] = wire_widget
widget_root = msspecgui.scenegraph2d.Group()
self.scene.add_child(widget_root)
wire_widget.render_to_scene_graph(widget_root)
self.update_operators_position()
return wire_widget
def delete_widget_for_wire(self, wire):
"""
:param Wire wire: the wire for which we want to delete the widget
"""
wire_widget = self.get_wire_widget(wire)
wire_widget.remove_from_scene_graph()
# widget_root = wire_widget.parent()
# self.scene.remove_child(widget_root)
self.wire_to_widget.pop(wire)
self.update_operators_position()
def get_wire_widget(self, wire):
"""
Returns the widget associated with the given wire
:param Wire wire:
:return WireWidget:
"""
return self.wire_to_widget[wire]
def get_operator_widget(self, operator):
"""
Returns the operator widget associated with the given operator
:param Operator operator:
:return OperatorWidget:
"""
return self.operator_to_widget[operator]
def get_plug_widget(self, plug):
"""
Returns the plug widget associated with the given plug
:type plug: Plug
:rtype: PlugWidget
"""
operator_widget = self.get_operator_widget(plug.operator)
plug_widget = operator_widget.get_plug_widget(plug)
return plug_widget
def render(self, display_context):
"""
renders this dataflow view in the given drawing context
:param display_context: the drawing context in which to render the dataflow
:type display_context: a wx context
"""
#cairo_context = wx.lib.wxcairo.ContextFromDC(display_context)
w, h = self.GetClientSize()
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
cairo_context = cairo.Context(surface)
msspecgui.scenegraph2d.cairo.render_scene(self.scene, cairo_context)
bitmap = wx.lib.wxcairo.BitmapFromImageSurface(surface)
display_context.DrawBitmap(bitmap, 0, 0)
def get_pointed_widget(self, pointer_position):
"""
returns the widget that is at the position pointer_position
"""
hits = self.scene.pick(pointer_position[0], pointer_position[1])
if len(hits) > 0:
svg_path, local_pos = hits[-1] # @UnusedVariable
# print("hit svg node stack : %s" % str(svg_path))
for svg_node in reversed(svg_path):
if svg_node in self.scenegraph_group_to_widget:
return self.scenegraph_group_to_widget[svg_node]
# for widget in self.operator_to_widget.itervalues():
# if widget.get_bounding_box().Contains(pointer_position):
# return widget
return None
# def select_widget(self, widget):
# if self.selected_widget is not None:
# self.selected_widget.set_selected_state(False)
# self.selected_widget = widget
# self.selected_widget.set_selected_state(True)
def on_left_down(self, event):
pos = event.GetPositionTuple()
display_context = wx.ClientDC(self)
display_context.DrawCircle(pos[0], pos[1], 5)
widget = self.get_pointed_widget(pos)
if widget is not None:
# self.select_widget(widget)
if isinstance(widget, PlugWidget):
plug_widget = widget
if plug_widget.is_connectable():
self.plug_being_connected = plug_widget
wire_widget = WireWidget(self)
if plug_widget.plug.is_source():
wire_widget.source_plug_widget = plug_widget
else:
wire_widget.dest_plug_widget = plug_widget
self.wire_being_created_widget = wire_widget
self.wire_being_created_widget.render_to_scene_graph(self.scene)
self.is_left_down = True
self.Refresh()
def on_left_up(self, event):
self.is_left_down = False
self.plug_being_connected = None
if self.wire_being_created_widget is not None:
pos = event.GetPositionTuple()
pointed_widget = self.get_pointed_widget(pos)
if pointed_widget is not None and isinstance(pointed_widget, PlugWidget):
if self.wire_being_created_widget.is_valid_final_plug_widget(pointed_widget):
self.wire_being_created_widget.set_final_plug_widget(pointed_widget)
self.dataflow.create_wire(self.wire_being_created_widget.source_plug_widget.plug, self.wire_being_created_widget.dest_plug_widget.plug)
self.wire_being_created_widget.remove_from_scene_graph()
self.wire_being_created_widget = None
self.Refresh()
def on_move(self, event):
pos = event.GetPositionTuple()
if self.wire_being_created_widget is not None:
self.wire_being_created_widget.set_pointer_pos(pos)
widget = self.get_pointed_widget(pos)
# print('widget at (%d,%d) : %s\n' % (pos[0], pos[1], widget))
if self.hovered_widget is not None:
if self.hovered_widget != widget:
self.hovered_widget.on_hover(False)
self.hovered_widget = None
if widget is not None:
if self.hovered_widget is None:
widget.on_hover(True)
self.hovered_widget = widget
if self.is_left_down:
display_context = wx.ClientDC(self)
display_context.DrawCircle(pos[0], pos[1], 3)
self.Refresh()
def on_context_menu(self, event):
pos = self.ScreenToClient(event.GetPosition())
for wire_widget in self.wire_to_widget.itervalues():
if wire_widget.get_bounding_box(border=5).Contains(pos):
self.on_wire_context_menu(event, wire_widget.wire)
return
for operator_widget in self.operator_to_widget.itervalues():
if operator_widget.get_bounding_box().Contains(pos):
self.on_operator_context_menu(event, operator_widget.operator)
return
self.on_background_context_menu(event)
def on_operator_context_menu(self, event, operator):
'''
called whenever the user right-clicks in an operator of the dataflow
'''
pass # possibly implemented in derived classes
def on_wire_context_menu(self, event, wire):
'''
called whenever the user right-clicks in an operator of the dataflow
:param msspec.dataflow.Wire wire: the wire on which the context menu is supposed to act
'''
pass # possibly implemented in derived classes
def on_background_context_menu(self, event):
'''
called whenever the user right-clicks in the background of the dataflow
'''
pass # possibly implemented in derived classes

View File

@ -0,0 +1,193 @@
from __future__ import print_function
import json
import tempfile
# from pprint import pprint
import pydot
from .ilayoutmanager import ILayoutManager
# def pydot_sample():
# import pydot # import pydot or you're not going to get anywhere my friend :D
#
# # first you create a new graph, you do that with pydot.Dot()
# graph = pydot.Dot(graph_type='graph')
#
# # the idea here is not to cover how to represent the hierarchical data
# # but rather how to graph it, so I'm not going to work on some fancy
# # recursive function to traverse a multidimensional array...
# # I'm going to hardcode stuff... sorry if that offends you
#
# # let's add the relationship between the king and vassals
# for i in range(3):
# # we can get right into action by "drawing" edges between the nodes in our graph
# # we do not need to CREATE nodes, but if you want to give them some custom style
# # then I would recomend you to do so... let's cover that later
# # the pydot.Edge() constructor receives two parameters, a source node and a destination
# # node, they are just strings like you can see
# edge = pydot.Edge("king", "lord%d" % i)
# # and we obviosuly need to add the edge to our graph
# graph.add_edge(edge)
#
# # now let us add some vassals
# vassal_num = 0
# for i in range(3):
# # we create new edges, now between our previous lords and the new vassals
# # let us create two vassals for each lord
# for j in range(2):
# edge = pydot.Edge("lord%d" % i, "vassal%d" % vassal_num)
# graph.add_edge(edge)
# vassal_num += 1
#
# # ok, we are set, let's save our graph into a file
# graph.write_png('/tmp/example1.png') # .write_png('example1_graph.png')
#
# # and we are done!
# def graphviz_sample():
# from graphviz import Digraph
#
# dot = Digraph(comment='The Round Table')
# dot.node('A', 'King Arthur')
# dot.node('B', 'Sir Bedevere the Wise')
# dot.node('L', 'Sir Lancelot the Brave')
#
# dot.edges(['AB', 'AL'])
# dot.edge('B', 'L', constraint='false')
#
# # print(dot.source)
# # // The Round Table
# # digraph {
# # A [label="King Arthur"]
# # B [label="Sir Bedevere the Wise"]
# # L [label="Sir Lancelot the Brave"]
# # A -> B
# # A -> L
# # B -> L [constraint=false]
# # }
#
# dot.render('/tmp/round-table.png', view=True)
class DotLayoutManager(ILayoutManager):
"""A layout manager that uses graphviz's dot
"""
def __init__(self):
super(DotLayoutManager, self).__init__()
self._method = 'plain' # at the moment we use the plain method instead of the more robust json method because json method is too recent to be supported on all platforms (on windows, graphviz 2.38 doesn't yet supports json output format)
self._tmp_file_path = tempfile.mktemp(suffix='.%s' % self._method, prefix='msspec_layout_')
print('DotLayoutManager.__init__ : self._tmp_file_path = %s' % self._tmp_file_path)
def compute_operators_position(self, data_flow):
"""
:param DataFlow data_flow:
"""
# pydot_sample()
graph = pydot.Dot(graph_type='graph')
# node [shape = record,height=.1];
# attr_to_label = {}
graph.set_node_defaults(shape='record')
for op in data_flow.operators:
node = pydot.Node(op.name)
label = '"'
plug_index = 0
for plug in op.get_input_plugs() + op.get_output_plugs():
if plug.is_pluggable:
if len(label) > 1:
label += '|'
# label += "<%s> %s" % (plug.name, plug.name)
label += "<%s> " % (plug.name)
plug_index += 1
label += '"'
node.set('label', label)
graph.add_node(node)
for wire in data_flow.wires:
#: :type wire: Wire
src_plug = wire.input_plug
dst_plug = wire.output_plug
src_op = src_plug.operator
dst_op = dst_plug.operator
# print("src_op.name = %s" % src_op.name)
# print("dst_op.name = %s" % dst_op.name)
edge = pydot.Edge('"%s":%s' % (src_op.name, src_plug.name), '"%s":%s' % (dst_op.name, dst_plug.name))
graph.add_edge(edge)
# print(graph.to_string())
# graph.write('/tmp/toto.pdf', format='pdf')
func = {'plain': self._graph_to_pos_plain, 'json': self._graph_to_pos_json}[self._method]
return func(graph, data_flow)
def _graph_to_pos_plain(self, graph, data_flow):
"""extracts operator positions from the pydot graph using dot's plain output format
"""
class GraphField(object):
X_SIZE = 2
Y_SIZE = 3
class NodeField(object):
NAME = 1
X = 2
Y = 3
graph.write(self._tmp_file_path, format='plain')
# example output
# graph 1 1.9444 2.5417
# node "ase.lattice.bulk_1" 0.375 2.2847 0.75 0.51389 "<output_cluster> " solid record black lightgrey
# node "ase.repeat_2" 0.56944 1.2708 0.75 0.51389 "<input_cluster> |<output_cluster> " solid record black lightgrey
# node "ase.lattice.bulk_3" 1.5694 1.2708 0.75 0.51389 "<output_cluster> " solid record black lightgrey
# node "ase.build.add_adsorbate_4" 1.3056 0.25694 0.83333 0.51389 "<slab> |<adsorbate> |<output_cluster> " solid record black lightgrey
# edge "ase.lattice.bulk_1" "ase.repeat_2" 4 0.375 2.0301 0.375 1.8816 0.375 1.6906 0.375 1.5208 solid black
# edge "ase.repeat_2" "ase.build.add_adsorbate_4" 4 0.76389 1.0208 0.76389 0.76408 1.0278 0.76369 1.0278 0.50694 solid black
# edge "ase.lattice.bulk_3" "ase.build.add_adsorbate_4" 4 1.4344 1.0167 1.3695 0.87155 1.3056 0.68373 1.3056 0.50694 solid black
# stop
positions = {} #: :type positions: dict(str, (float,float))
scale = 100.0
y_max = None
with open(self._tmp_file_path) as data_file:
for line in data_file.readlines():
# print(line)
tokens = line.split(' ')
if tokens[0] == 'graph':
y_max = float(tokens[GraphField.Y_SIZE])
elif tokens[0] == 'node':
op_name = tokens[NodeField.NAME][1:-1]
x = float(tokens[NodeField.X])
y = float(tokens[NodeField.Y])
# print( op_name, x, y)
positions[data_flow.find_operator(op_name)] = ((y_max - y) * scale, x * scale) # graphviz' dot lays th flow of arcs in the down direction by default, and we want it on the right
elif tokens[0] == 'edge':
break # for efficiency reason, stop parsing as we're not interested in the rest of the file
return positions
def _graph_to_pos_json(self, graph, data_flow):
"""extracts operator positions from the pydot graph using dot's json output format
"""
graph.write('toto.json', format='json')
with open('toto.json') as data_file:
data = json.load(data_file)
# pprint(data)
y_max = float(data["bb"].split(",")[3])
positions = {} #: :type positions: dict(str, (float,float))
if "objects" in data:
for op_node in data["objects"]:
op_name = op_node["name"]
# print(op_node["name"])
# print(op_node["pos"])
coords = op_node["pos"].split(',')
x = float(coords[0])
y = float(coords[1])
# print( op_name, x, y)
positions[data_flow.find_operator(op_name)] = (y_max - y, x) # graphviz' dot lays th flow of arcs in the down direction by default, and we want it on the right
return positions

View File

@ -0,0 +1,12 @@
import abc
class ILayoutManager(object):
@abc.abstractmethod
def compute_operators_position(self, data_flow):
"""
:param DataFlow data_flow: the data flow that we want to compute the layout for
:return dict(msspecgui.dataflow.Operator, (int, int)): a (x, y) position for each operator
"""
return {}

View File

@ -0,0 +1,142 @@
import wx
from wx.lib.agw import floatspin as fs
from msspecgui.dataflow import IOperatorCreator
# bug 778 on https://www.brainwy.com/tracker/PyDev/#
# autocomplete (code completion) doesn't work for list items inside self members
# Hi, The code below shows a case of code completion that doesn't work although it works on very close cases (see below)
#
# class Toto(object):
# def dummy(self):
# pass
#
#
# class Titi(object):
# def __init__(self, l, s):
# """
# :param list(Toto) l:
# :param str s:
# """
# self._l = l
# self._s = s
#
# s.capitalize() # <-- autocomplete works on s
# self._s.capitalize() # <-- autocomplete works on self._s
#
# l.pop() # <-- autocomplete works on l
# for p1 in l:
# p1.dummy() # <-- autocomplete works on p1
#
# self._l.pop() # <-- autocomplete works on self._l
# for p2 in self._l:
# p2.dummy() # <-- autocomplete doesn't work on p2
class PlugsGuiFrame(wx.Dialog):
""" The frame containing the graphical user interface for the given plugs
"""
def __init__(self, parent, window_title, plugs):
"""
:param wx.Widget parent: window that contains this window
:param str window_title: window title
:param list(msspecgui.dataflow.Plug) plugs: the plugs that this gui allows the user to modify
:param msspecgui.dataflow.Plug plug: the plugs that this gui allows the user to modify
"""
super(PlugsGuiFrame, self).__init__(parent, title=window_title)
self._plugs = plugs
self._widgets = {} #: :type self._widgets: dict(str, wx.Control)
self.initui()
self.Centre()
self.Show()
def initui(self):
"""
the constructor of the interface
"""
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.panel = wx.Panel(parent=self, id=1)
main_params_sizer = wx.FlexGridSizer(0, 2, 10, 10)
# print("PlugsGuiFrame.initui len(self._plugs) = %d" % len(list(self._plugs)))
for plug in self._plugs: #: :type plug: msspecgui.dataflow.Plug
if not plug.is_pluggable:
label = wx.StaticText(self.panel, label=plug.name)
main_params_sizer.Add(label)
widget = None
# print("plug.data_type.get_type_id() = %s" % plug.data_type.get_type_id())
if plug.data_type.get_type_id() == 'int':
widget = wx.SpinCtrl(self.panel)
widget.SetValue(plug.get_value())
elif plug.data_type.get_type_id() == 'float':
widget = fs.FloatSpin(self.panel,
value=plug.get_value(), size=(180, -1),
min_val=0.1, max_val=10.00,
increment=0.01,
style=fs.FS_CENTRE)
widget.SetFormat("%f")
widget.SetDigits(6)
else:
raise NotImplementedError
main_params_sizer.Add(widget)
self._widgets[plug.name] = widget
ok_cancel_reset_sizer = wx.BoxSizer(wx.HORIZONTAL)
# reset_btn = wx.Button(self.panel, 2, label="Reset")
# reset_btn.Bind(wx.EVT_BUTTON, self.on_reset)
cancel_btn = wx.Button(self.panel, wx.ID_CANCEL, label="Cancel")
cancel_btn.Bind(wx.EVT_BUTTON, self.on_close, id=wx.ID_CANCEL)
self.ok_btn = wx.Button(self.panel, wx.ID_OK, label="OK")
# self.ok_btn.Bind(wx.EVT_BUTTON, self.on_ok, id=wx.ID_OK)
self.ok_btn.Bind(wx.EVT_BUTTON, self.on_ok, id=wx.ID_OK)
# ok_cancel_reset_sizer.Add(reset_btn, flag=wx.ALL, border=5)
ok_cancel_reset_sizer.Add(cancel_btn)
ok_cancel_reset_sizer.Add(self.ok_btn, flag=wx.LEFT, border=5)
self.vbox.Add(main_params_sizer, proportion=2, flag=wx.ALL | wx.EXPAND, border=15)
self.vbox.Add(ok_cancel_reset_sizer, flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
self.panel.SetSizer(self.vbox)
self.panel.Fit()
self.Fit()
def on_reset(self, event):
pass
def on_close(self, event):
event.Skip() # propagate the event so that the dialog closes
def on_ok(self, event):
# print("PlugsGuiFrame.on_ok len(self._plugs) = %d" % len(list(self._plugs)))
for plug in self._plugs: #: :type plug: msspecgui.dataflow.Plug
# print("plug.data_type.get_type_id() = %s" % plug.data_type.get_type_id())
if not plug.is_pluggable:
new_value = None
widget = self._widgets[plug.name]
if plug.data_type.get_type_id() == 'int':
new_value = widget.GetValue()
# print("PlugsGuiFrame.on_ok : new_value (int) = %d" % new_value)
elif plug.data_type.get_type_id() == 'float':
new_value = widget.GetValue()
# print("PlugsGuiFrame.on_ok : new_value (float) = %f" % new_value)
plug.set_value(new_value)
event.Skip() # propagate the event so that the dialog closes
class OperatorGui(IOperatorCreator.IAction):
"""a graphic user interface to allow the user to modify an operator's parameters
"""
def get_name(self):
return 'properties via gui'
def execute_on_operator(self, operator):
"""
:param dataflow.operator.Operator operator: the operator related to this action
"""
dialog = PlugsGuiFrame(None, window_title='operator %s' % operator.name, plugs=operator.get_input_plugs())
result = dialog.ShowModal()
if result == wx.ID_OK:
operator.data_flow.on_modified_operator(operator)
dialog.Destroy()

View File

@ -0,0 +1,119 @@
# import wx.lib.wxcairo
# import math
import msspecgui
from msspecgui import scenegraph2d
from .widget import Widget
# from msspecgui import datafloweditor
# from numpy.distutils.misc_util import cxx_ext_match
class OperatorWidget(Widget):
'''
The widget representing a node in the dataflow
'''
_g_last_id = 0
def __init__(self, operator, data_flow_view):
"""
:param operator: the dataflow operator that this widget graphically represents
:type operator: msspecgui.dataflow.Operator
:param data_flow_view: the dataflowview to which this operator belongs
:type data_flow_view: msspecgui.datafloweditor.DataflowView
"""
super(OperatorWidget, self).__init__(data_flow_view)
self._operator = operator
self._id = OperatorWidget.get_new_id()
self.is_selected = False
self.widget_background = None
self.main_shape_node = scenegraph2d.Group()
self.plug_to_widget = {} # an associative array that gives the widget associated to each plug name
for side in ['input', 'output']:
plugs = {'input': self._operator.get_input_plugs(),
'output': self._operator.get_output_plugs()}[side]
# plug_index = 0
for plug in plugs:
if plug.is_pluggable:
plug_widget = msspecgui.datafloweditor.PlugWidget(plug, self, data_flow_view)
self.plug_to_widget[plug.name] = plug_widget
# plug_index += 1
@classmethod
def get_new_id(cls):
OperatorWidget._g_last_id += 1
return OperatorWidget._g_last_id
def get_id(self):
return self._id
@property
def operator(self):
return self._operator
def update_appearance(self):
for plug_widget in self.plug_to_widget.itervalues():
plug_widget.update_appearance(mouse_is_above=False) # FIXME : handle the case where the mouse is over the plug widget
def get_plug_widget(self, plug):
"""
Returns the plug widget associated with the given plug
:type plug: Plug
:rtype: PlugWidget
"""
plug_widget = self.plug_to_widget[plug.name]
return plug_widget
def set_selected_state(self, is_selected):
self.is_selected = is_selected
self.widget_background.fill = {False: scenegraph2d.Color(128, 128, 128), True: scenegraph2d.Color(192, 192, 128)}[self.is_selected]
def set_position(self, x, y):
self.main_shape_node.parent.transform = [msspecgui.scenegraph2d.Translate(x, y)]
assert self.operator is not None
for wire in self.operator.wires:
if wire in self._data_flow_view.wire_to_widget: # while load the dataflow, wires are not guaranteed to have a widget yet
wire_widget = self._data_flow_view.wire_to_widget[wire]
wire_widget.update_position()
def render_to_scene_graph(self, scenegraph_group):
"""
:param scenegraph_group: the group node that contains the drawing of this element
:type scenegraph_group: scenegraph.Group
"""
rect = scenegraph2d.Rectangle()
rect.width = 70.0
rect.height = 70.0
rect.x = -35.0
rect.y = -35.0
rect.fill = scenegraph2d.Color(128, 128, 128)
self.widget_background = rect
self.main_shape_node.add_child(rect)
title = "%s (%d)" % (self._operator.creator.get_operator_type_id().split('.')[-1], self.get_id())
# import my.source.module
# c = my.source.module.Color()
title_text = scenegraph2d.Text(title)
title_text.fill = scenegraph2d.Color.black
title_text.text_anchor = "middle"
self.main_shape_node.add_child(title_text)
for side in ['input', 'output']:
(cx, plugs) = {'input': (-25.0, self._operator.get_input_plugs()),
'output': (25.0, self._operator.get_output_plugs())}[side]
plug_index = 0
for p in plugs:
if p.is_pluggable:
plug_group = scenegraph2d.Group()
plug_group.x = cx
plug_group.y = -25.0 + 10.0 * plug_index
self.main_shape_node.add_child(plug_group)
plug_widget = self.plug_to_widget[p.name]
plug_widget.render_to_scene_graph(plug_group)
plug_index += 1
self.scenegraph_group = scenegraph_group
self.scenegraph_group.add_child(self.main_shape_node)
def remove_from_scene_graph(self):
parent = self.main_shape_node.parent
parent.remove_child(self.main_shape_node)

View File

@ -0,0 +1,83 @@
from msspecgui import scenegraph2d
from .widget import Widget
# import dataflow
class PlugWidget(Widget):
'''
The widget representing a plug (input or output) of an operator in the dataflow
'''
DATA_IS_UNAVAILABLE_COLOR = scenegraph2d.Color(128, 64, 64)
DATA_IS_AVAILABLE_COLOR = scenegraph2d.Color(64, 128, 64)
HIGHLITED_COLOR = scenegraph2d.Color(128, 255, 128)
def __init__(self, plug, operator_widget, data_flow_view):
"""
:param plug: the dataflow plug that this widget graphically represents
:type plug: dataflow.Plug
@type plug: dataflow.Plug
:param operator_widget: the operator widget that this plug widget is attached to
:type operator_widget: datafloweditor.OperatorWidget
:param data_flow_view: the dataflowview to which this operator belongs
:type data_flow_view: datafloweditor.DataflowView
"""
super(PlugWidget, self).__init__(data_flow_view)
self.operator_widget = operator_widget
self._plug = plug
self.main_shape_node = None
@property
def plug(self):
"""
:rtype: dataflow.plug.Plug
"""
# plug = dataflow.plug.Plug
return self._plug
@property
def centre_pos(self):
""" Returns the position of the centre of this plug widget
"""
# print('PlugWidget.centre_pos : local_to_parent_matrix = %s' % str(self.scenegraph_group.local_to_parent_matrix()))
# defs = []
# print('PlugWidget.centre_pos : self.scenegraph_group = %s' % str(self.scenegraph_group._xml(defs)))
# defs = []
# print('PlugWidget.centre_pos : self.scenegraph_group.parent = %s' % str(self.scenegraph_group.parent._xml(defs)))
return self.scenegraph_group.local_to_world_matrix().project(x=0, y=0)
def is_connectable(self):
if self._plug.is_connected():
return False
if not self._plug.value_is_available():
return False
return True
def update_appearance(self, mouse_is_above):
color = {False: self.DATA_IS_UNAVAILABLE_COLOR,
True: self.DATA_IS_AVAILABLE_COLOR}[self._plug.value_is_available()]
if mouse_is_above and self.is_connectable():
color = self.HIGHLITED_COLOR
self.main_shape_node.fill = color
def render_to_scene_graph(self, scenegraph_group):
"""
:param scenegraph_group: the group node that contains the drawing of this element
:type scenegraph_group: scenegraph.Group
"""
self.scenegraph_group = scenegraph_group
circle_node = scenegraph2d.Circle()
circle_node.cx = 0.0
circle_node.cy = 0.0
circle_node.r = 7.0
self.main_shape_node = circle_node
scenegraph_group.add_child(circle_node)
self.update_appearance(mouse_is_above=False)
def on_hover(self, is_entering):
self.update_appearance(mouse_is_above=is_entering)

View File

@ -0,0 +1,51 @@
'''
Created on May 20, 2016
@author: graffy
'''
import wx
class Widget(object):
'''
The widget representing an interactive object in the dataflow (an operator, a plug, etc.)
'''
def __init__(self, data_flow_view):
"""
:param data_flow_view: the dataflowview to which this operator belongs
:type data_flow_view: datafloweditor.DataflowView
"""
self._container_group = None # the 2d scene graph group node representing this widget
self._data_flow_view = data_flow_view
@property
def scenegraph_group(self):
"""
:rtype: scenegraph.Group
"""
return self._container_group
@scenegraph_group.setter
def scenegraph_group(self, scenegraph_group):
"""
:type scenegraph_group: scenegraph.Group
"""
print('Widget.scenegraph_group.setter : scenegraph_group=%s' % str(scenegraph_group))
self._container_group = scenegraph_group
self._data_flow_view.scenegraph_group_to_widget[scenegraph_group] = self
def get_bounding_box(self, border=0):
"""
:param int border: the width of the border surrounding the box
:return wx.Rect: the smallest box containing this widget
"""
(x_min, y_min), (x_max, y_max) = self.scenegraph_group.aabbox() # (scenegraph_group.setter seen as a method, see https://github.com/PyCQA/pylint/issues/870) pylint: disable=no-member
return wx.Rect(x_min - border, y_min - border, x_max - x_min + border * 2, y_max - y_min + border * 2)
def on_hover(self, is_entering):
'''
:param is_entering: True is the mouse pointer enters this widget, False if it leaves the widget
'''
pass

View File

@ -0,0 +1,111 @@
import msspecgui
from msspecgui import scenegraph2d
# import msspecgui.datafloweditor as datafloweditor
import msspecgui.dataflow as dataflow
import widget
class WireWidget(widget.Widget):
'''
The widget representing a wire (a link connectiong two plugs)
'''
def __init__(self, data_flow_view):
"""
:param data_flow_view: the dataflowview to which this operator belongs
:type data_flow_view: datafloweditor.DataflowView
"""
super(WireWidget, self).__init__(data_flow_view)
# print('WireWidget constructor')
self.main_shape_node = None
self._source_plug_widget = None #: :type self._source_plug_widget: msspecgui.datafloweditor.PlugWidget
self._dest_plug_widget = None
self.main_shape_node = scenegraph2d.Line()
self.main_shape_node.fill = scenegraph2d.Color(0, 255, 255)
@property
def wire(self):
return self.dest_plug_widget.plug.incoming_wire
@property
def source_plug_widget(self):
return self._source_plug_widget
@source_plug_widget.setter
def source_plug_widget(self, plug_widget):
self._source_plug_widget = plug_widget
self._set_from_pos(plug_widget.centre_pos)
@property
def dest_plug_widget(self):
return self._dest_plug_widget
@dest_plug_widget.setter
def dest_plug_widget(self, plug_widget):
self._dest_plug_widget = plug_widget
self._set_to_pos(plug_widget.centre_pos)
def _set_from_pos(self, pos):
(self.main_shape_node.x1, self.main_shape_node.y1) = pos
def _set_to_pos(self, pos):
(self.main_shape_node.x2, self.main_shape_node.y2) = pos
def render_to_scene_graph(self, scenegraph_group):
"""
:param scenegraph_group: the group node that contains the drawing of this element
:type scenegraph_group: scenegraph.Group
"""
self.scenegraph_group = scenegraph_group
scenegraph_group.add_child(self.main_shape_node)
def update_position(self):
self._set_from_pos(self.source_plug_widget.centre_pos)
self._set_to_pos(self.dest_plug_widget.centre_pos)
def remove_from_scene_graph(self):
parent = self.main_shape_node.parent
parent.remove_child(self.main_shape_node)
def is_construction_wire(self):
""" Tells if this widget represents a wire being built interactively by the user
"""
return self.source_plug_widget is None or self.dest_plug_widget is None
def set_pointer_pos(self, pos):
"""
:type pos: a tuple (x,y)
"""
assert(self.is_construction_wire()) # this call only makes sens if this wire widget represents a wire in construction
if self.source_plug_widget is None:
self._set_from_pos(pos)
else:
self._set_to_pos(pos)
def is_valid_final_plug_widget(self, plug_widget):
"""
checks whether plug_widget is a valid plug to complete this connection
:type plug_widget: datafloweditor.PlugWidget
"""
assert(isinstance(plug_widget, msspecgui.datafloweditor.PlugWidget))
assert(self.is_construction_wire())
if self.source_plug_widget is None:
return plug_widget.plug.is_output_plug()
else:
return plug_widget.plug.is_input_plug()
def set_final_plug_widget(self, plug_widget):
"""
completes this wire with the given plug_widget
:type plug_widget: datafloweditor.PlugWidget
"""
assert(isinstance(plug_widget, msspecgui.datafloweditor.PlugWidget))
assert(self.is_construction_wire())
assert(self.is_valid_final_plug_widget(plug_widget))
if self.source_plug_widget is None:
self.source_plug_widget = plug_widget
else:
self.dest_plug_widget = plug_widget

View File

@ -0,0 +1 @@
from dataflowxmlserializer import DataflowSerializer

View File

@ -0,0 +1,213 @@
import abc
from xml.dom import minidom
# from msspecgui.dataflow import Operator
from msspecgui.dataflow import IDataflowSerializer
from msspecgui.dataflow.datatypes import StringDataType
from msspecgui.dataflow.datatypes import FloatDataType
from msspecgui.dataflow.datatypes import BoolDataType
from msspecgui.dataflow.datatypes import IntDataType
class IDataTypeSerializer(object):
@abc.abstractmethod
def get_data_type(self):
"""
:return msspecgui.dataflow.IDatatype: the id of the type this seralize deals with
"""
return None
@abc.abstractmethod
def value_to_xml(self, value, xml_node):
"""
:param minidom.Element xml_node: the container node of the serialized value
"""
pass
@abc.abstractmethod
def xml_to_value(self, xml_node):
"""
:param minidom.Element xml_node: the container node of the serialized value
"""
pass
class StringSerializer(IDataTypeSerializer):
def get_data_type(self):
return StringDataType()
def value_to_xml(self, value, xml_node):
xml_node.setAttribute('value', '%s' % value)
def xml_to_value(self, xml_node):
return xml_node.GetAttribute('value')
class FloatSerializer(IDataTypeSerializer):
def get_data_type(self):
return FloatDataType()
def value_to_xml(self, value, xml_node):
xml_node.setAttribute('value', '%f' % value)
def xml_to_value(self, xml_node):
return float(xml_node.GetAttribute('value'))
class BoolSerializer(IDataTypeSerializer):
def get_data_type(self):
return BoolDataType()
def value_to_xml(self, value, xml_node):
xml_node.setAttribute('value', {False: 'false', True: 'true'}[value])
def xml_to_value(self, xml_node):
value_as_str = xml_node.GetAttribute('value')
return {'false': False, 'true': True}[value_as_str]
class IntSerializer(IDataTypeSerializer):
def get_data_type(self):
return IntDataType()
def value_to_xml(self, value, xml_node):
xml_node.setAttribute('value', '%d' % value)
def xml_to_value(self, xml_node):
return int(xml_node.GetAttribute('value'))
class DataflowSerializer(IDataflowSerializer):
FORMAT_VERSION = 1
def __init__(self):
super(DataflowSerializer, self).__init__()
self._data_type_serializers = {} #: :type self._data_type_serializers: dict[str, IDataTypeSerializer]
self._register_data_type_serializer(StringSerializer())
self._register_data_type_serializer(FloatSerializer())
self._register_data_type_serializer(BoolSerializer())
self._register_data_type_serializer(IntSerializer())
def _register_data_type_serializer(self, data_type_serializer):
"""
:param IDataTypeSerializer data_type_serializer: the datatype serialize that needs to be registered
"""
self._data_type_serializers[data_type_serializer.get_data_type().get_type_id()] = data_type_serializer
def _get_datatype_serializer(self, data_type_id):
"""
:param str data_type_id:
"""
return self._data_type_serializers[data_type_id]
def _operator_as_xml(self, operator, xml_doc):
"""creates the xml representation of the given operator
:param msspecgui.dataflow.Operator operator: the operator
:param minidom.Document xml_doc: the xml document that be used to create the xml node
:return minidom.Element: the xml representation of the operator
"""
op_xml_node = xml_doc.createElement('operator')
op_xml_node.setAttribute('nodeId', '%d' % operator.id)
op_xml_node.setAttribute('operatorTypeId', '%s' % operator.creator.get_operator_type_id())
for plug in operator.get_input_plugs():
if not plug.is_pluggable:
plug_node = xml_doc.createElement(plug.name)
data_type_id = plug.data_type.get_type_id()
# print('data_type_id = %s' % data_type_id)
data_type_serializer = self._get_datatype_serializer(data_type_id)
data_type_serializer.value_to_xml(plug.get_value(), plug_node)
op_xml_node.appendChild(plug_node)
return op_xml_node
def _create_operator_from_xml(self, op_xml_node, dataflow):
"""
:param minidom.Element op_xml_node: the xml node describing the operator and its data
:param msspec.dataflow.IDataFlow dataflow: the dataflow that contains the resulting operator
:return msspec.dataflow.Operator: the created operator
"""
operator_type_id = str(op_xml_node.getAttribute('operatorTypeId'))
operator = dataflow.create_operator(operator_type_id)
operator.id = int(op_xml_node.getAttribute('nodeId'))
dataflow.add_operator(operator)
return operator
def save_dataflow(self, dataflow, file_path):
"""
:type dataflow: msspecgui.dataflow.DataFlow
"""
xml_doc = minidom.Document()
root_xml_node = xml_doc.createElement('dataflow')
xml_doc.appendChild(root_xml_node)
# store a format version so that if the format needs changing, then it will be possible to detect and support old file formats
format_version_xml_node = xml_doc.createElement('formatVersion')
format_version_xml_node.setAttribute('value', '%d' % self.FORMAT_VERSION)
root_xml_node.appendChild(format_version_xml_node)
last_create_op_id_xml_node = xml_doc.createElement('lastCreatedOperatorId')
last_create_op_id_xml_node.setAttribute('value', '%d' % dataflow.last_created_operator_id)
root_xml_node.appendChild(last_create_op_id_xml_node)
operators_xml_node = xml_doc.createElement('operators')
root_xml_node.appendChild(operators_xml_node)
for op in dataflow.operators:
op_xml_node = self._operator_as_xml(op, xml_doc)
operators_xml_node.appendChild(op_xml_node)
wires_xml_node = xml_doc.createElement('wires')
root_xml_node.appendChild(wires_xml_node)
for wire in dataflow.wires: #: :type wire: msspec.dataflow.Wire
wire_xml_node = xml_doc.createElement('wire')
wire_xml_node.setAttribute('fromOperator', '%d' % wire.input_plug.operator.id)
wire_xml_node.setAttribute('fromAttr', wire.input_plug.name)
wire_xml_node.setAttribute('toOperator', '%d' % wire.output_plug.operator.id)
wire_xml_node.setAttribute('toAttr', wire.output_plug.name)
wires_xml_node.appendChild(wire_xml_node)
print('save_dataflow : saving to %s\n' % file_path)
with open(file_path, 'w') as f:
f.write(xml_doc.toprettyxml())
def load_dataflow(self, file_path, dataflow):
"""
:param msspecgui.dataflow.DataFlow dataflow: an empty dataflow that will be filled
"""
xml_doc = minidom.parse(file_path)
root_xml_node = xml_doc.documentElement
last_create_op_id_xml_node = root_xml_node.getElementsByTagName('lastCreatedOperatorId')[0]
last_created_operator_id = int(last_create_op_id_xml_node.getAttribute('value'))
dataflow.last_created_operator_id = last_created_operator_id
operators_xml_node = root_xml_node.getElementsByTagName('operators')[0]
for op_xml_node in operators_xml_node.getElementsByTagName('operator'):
# print('load_dataflow : creating operator')
self._create_operator_from_xml(op_xml_node, dataflow)
wires_xml_node = root_xml_node.getElementsByTagName('wires')[0]
for wire_xml_node in wires_xml_node.getElementsByTagName('wire'):
from_op_id = int(wire_xml_node.getAttribute('fromOperator'))
from_attr = wire_xml_node.getAttribute('fromAttr')
to_op_id = int(wire_xml_node.getAttribute('toOperator'))
to_attr = wire_xml_node.getAttribute('toAttr')
from_plug = dataflow.get_operator(from_op_id).get_plug(from_attr)
to_plug = dataflow.get_operator(to_op_id).get_plug(to_attr)
# print('load_dataflow : creating wire')
dataflow.create_wire(from_plug, to_plug)
return dataflow

View File

View File

@ -0,0 +1 @@
from .clusterflow import ClusterFlow

View File

@ -0,0 +1,34 @@
'''
Created on Jan 18, 2016
@author: graffy
'''
from msspecgui import dataflow
from msspecgui.dataflow.datatypes import StringDataType
from msspecgui.dataflow.datatypes import FloatDataType
from msspecgui.dataflow.datatypes import BoolDataType
from msspecgui.dataflow.datatypes import IntDataType
from .datatypes import ClusterDataType
from .generators import Bulk
from .generators import Repeat
from .generators import AddAdsorbate
class ClusterFlow(dataflow.DataFlow):
'''
a dataflow of cluster generator nodes
'''
def __init__(self):
'''
Constructor
'''
super(ClusterFlow, self).__init__()
self.register_operator_creator(Bulk.Creator())
self.register_operator_creator(Repeat.Creator())
self.register_operator_creator(AddAdsorbate.Creator())
self.register_data_type(ClusterDataType())
self.register_data_type(StringDataType())
self.register_data_type(FloatDataType())
self.register_data_type(BoolDataType())
self.register_data_type(IntDataType())

View File

@ -0,0 +1,19 @@
from msspecgui.dataflow import IDataType
from ase import Atoms
class ClusterDataType(IDataType):
def __init__(self):
IDataType.__init__(self)
def get_type_id(self):
"""
Returns a string uniquely identifying this data type
"""
return 'physics.atomscluster'
def get_python_class(self): # pylint: disable=no-self-use
"""
see IDataType.get_python_class
"""
return Atoms

View File

@ -0,0 +1,3 @@
from msspecgui.msspec.cluster.generators.repeat import Repeat
from msspecgui.msspec.cluster.generators.bulk import Bulk
from msspecgui.msspec.cluster.generators.addadsorbate import AddAdsorbate

View File

@ -0,0 +1,43 @@
from msspecgui import dataflow
from ase.build import add_adsorbate
class AddAdsorbate(dataflow.Operator):
"""
an operator for ase.build.add_absorbate
"""
class Creator(dataflow.IOperatorCreator):
def __init__(self):
dataflow.IOperatorCreator.__init__(self)
self.add_input_attribute('slab', 'physics.atomscluster', 'the surface onto which the adsorbate should be added')
self.add_input_attribute('adsorbate', 'physics.atomscluster', 'the adsorbate')
self.add_input_attribute('height', 'float', 'height above the surface.', is_pluggable=False)
self.add_output_attribute('output_cluster', 'physics.atomscluster', 'the resulting cluster')
def get_operator_type_id(self):
return 'ase.build.add_adsorbate'
def create_operator(self, dflow):
return AddAdsorbate(dflow, self)
def __init__(self, data_flow, creator):
'''
Constructor
:param data_flow: the dataflow that will contain this operator
'''
dataflow.Operator.__init__(self, data_flow, creator)
self.get_plug('height').set_value(0.0)
def update(self):
"""
see dataflow.Operator.update
"""
slab = self.get_plug('slab').get_value()
adsorbate = self.get_plug('adsorbate').get_value()
height = self.get_plug('height').get_value()
output_cluster = slab.copy()
add_adsorbate(output_cluster, adsorbate, height)
self.get_plug('output_cluster').set_value(output_cluster)

View File

@ -0,0 +1,131 @@
from ase.lattice import bulk
from msspecgui import dataflow
class Bulk(dataflow.Operator):
'''
a cluster creator that performs what ase.lattice.bulk performs
'''
class Creator(dataflow.IOperatorCreator):
def __init__(self):
dataflow.IOperatorCreator.__init__(self)
self.add_output_attribute('output_cluster', 'physics.atomscluster', 'the resulting cluster')
# the following attributes are set via a graphical user interface, hence not pluggable
self.add_input_attribute('atoms', 'string', 'the atoms that constitute this cluster (eg "MgO")', is_pluggable=False)
self.add_input_attribute('structure', 'string', 'the lattice structure (eg "rocksalt")', is_pluggable=False)
self.add_input_attribute('a', 'float', 'the lattice constant a', is_pluggable=False)
self.add_input_attribute('c', 'float', 'the lattice constant c', is_pluggable=False)
self.add_input_attribute('u', 'float', 'the lattice constant u', is_pluggable=False)
self.add_input_attribute('is_cubic', 'bool', 'is the lattice cubic', is_pluggable=False)
self.add_input_attribute('is_orthorombic', 'bool', 'is the lattice orthorhombic', is_pluggable=False)
def get_operator_type_id(self):
return 'ase.lattice.bulk'
def create_operator(self, dflow):
return Bulk(dflow, self)
def __init__(self, data_flow, creator):
'''
Constructor
:param data_flow: the dataflow that will contain this operator
'''
dataflow.Operator.__init__(self, data_flow, creator)
self.atoms = 'MgO'
self.structure = 'rocksalt'
self.a = 4.219
self.c = 0.0
self.u = 0.0
self.is_cubic = True
self.is_orthorombic = False
@property
def atoms(self):
return self.get_plug('atoms').get_value()
@atoms.setter
def atoms(self, atoms):
"""
:type atoms: str
"""
self.get_plug('atoms').set_value(atoms)
@property
def structure(self):
return self.get_plug('structure').get_value()
@structure.setter
def structure(self, structure):
"""
:type structure: str
"""
self.get_plug('structure').set_value(structure)
@property
def a(self):
return self.get_plug('a').get_value()
@a.setter
def a(self, a):
"""
:type a: float
"""
self.get_plug('a').set_value(a)
@property
def c(self):
return self.get_plug('c').get_value()
@c.setter
def c(self, c):
"""
:type c: float
"""
self.get_plug('c').set_value(c)
@property
def u(self):
return self.get_plug('u').get_value()
@u.setter
def u(self, u):
"""
:type u: float
"""
self.get_plug('u').set_value(u)
@property
def is_cubic(self):
return self.get_plug('is_cubic').get_value()
@is_cubic.setter
def is_cubic(self, is_cubic):
"""
:type is_cubic: bool
"""
self.get_plug('is_cubic').set_value(is_cubic)
@property
def is_orthorombic(self):
return self.get_plug('is_orthorombic').get_value()
@is_orthorombic.setter
def is_orthorombic(self, is_orthorombic):
"""
:type is_orthorombic: bool
"""
self.get_plug('is_orthorombic').set_value(is_orthorombic)
def update(self):
"""
see dataflow.Operator.update
"""
# >>> ase.lattice.bulk('MgO', crystalstructure='rocksalt', a=4.219, c=0.1, u=0.1, cubic=True, orthorhombic=False)
# Atoms(symbols='MgOMgOMgOMgO', positions=..., cell=[4.219, 4.219, 4.219], pbc=[True, True, True])
print('self.atoms = %s (type = %s)' % (self.atoms, type(self.atoms)))
cluster = bulk(self.atoms, crystalstructure=self.structure, a=self.a, c=self.c, u=self.u, cubic=self.is_cubic, orthorhombic=self.is_orthorombic)
self.get_plug('output_cluster').set_value(cluster)

View File

@ -0,0 +1,48 @@
from msspecgui import dataflow
class Repeat(dataflow.Operator):
"""
an operator that repeats the input cluser a given number of times along each axis
"""
# an operator that removes the atoms of the cluster that are outside the given sphere
class Creator(dataflow.IOperatorCreator):
def __init__(self):
dataflow.IOperatorCreator.__init__(self)
self.add_input_attribute('input_cluster', 'physics.atomscluster', 'the cluster from which we want to keep the atoms that are inside the given sphere')
self.add_input_attribute('repeat_x', 'int', 'the number of repetitions along x axis', is_pluggable=False)
self.add_input_attribute('repeat_y', 'int', 'the number of repetitions along y axis', is_pluggable=False)
self.add_input_attribute('repeat_z', 'int', 'the number of repetitions along z axis', is_pluggable=False)
# self.add_input_attribute('inputSphere.center', 'position3', 'the position of the center of the sphere')
# self.add_input_attribute('inputSphere.radius', 'float', 'the radius of the center of the sphere')
self.add_output_attribute('output_cluster', 'physics.atomscluster', 'the resulting cluster')
def get_operator_type_id(self):
return 'ase.repeat'
def create_operator(self, dflow):
return Repeat(dflow, self)
def __init__(self, data_flow, creator):
'''
Constructor
:param data_flow: the dataflow that will contain this operator
'''
dataflow.Operator.__init__(self, data_flow, creator)
self.get_plug('repeat_x').set_value(2)
self.get_plug('repeat_y').set_value(3)
self.get_plug('repeat_z').set_value(4)
def update(self):
"""
see dataflow.Operator.update
"""
input_cluster = self.get_plug('input_cluster').get_value()
rx = self.get_plug('repeat_x').get_value()
ry = self.get_plug('repeat_y').get_value()
rz = self.get_plug('repeat_z').get_value()
output_cluster = input_cluster.repeat((rx, ry, rz))
self.get_plug('output_cluster').set_value(output_cluster)

View File

@ -0,0 +1,435 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import wx
from wx.lib.agw import floatspin as fs
"""
bulk interface that the user use to create a single cell crystal
"""
class StructureDef(object):
# enabled widgets
class EnabledWidgets(object):
covera_flg = 0b00001
u_flg = 0b00010
c_flg = 0b00100
orth_flg = 0b01000
cubic_flg = 0b10000
def __init__(self, ase_name, long_name, min_atoms, max_atoms, default_atoms, enabled_widgets, default_a, default_c=0.1):
"""
:param ase_name: crystal structure identifier used by ase
:param long_name: crystal long name
:param min_atoms: minimal number of atoms allowed in crystal
:param max_atoms: minimal number of atoms allowed in crystal
"""
self.long_name = long_name
self.ase_name = ase_name
self.min_atoms = min_atoms
self.max_atoms = max_atoms
self.default_atoms = default_atoms
self.enabled_widgets = enabled_widgets
self.default_a = default_a
self.default_c = default_c
class StructureDefs(object):
def __init__(self):
ew = StructureDef.EnabledWidgets
self._long_name_to_structure_def = {}
self._ase_name_to_structure_def = {}
self._add_structure_def(StructureDef('sc', 'Simple Cubic', -1, 1, 'Po', ew.cubic_flg | ew.orth_flg, 3.80))
self._add_structure_def(StructureDef('fcc', 'Face Centered Cubic', -1, 1, 'Cu', ew.cubic_flg | ew.orth_flg, 3.615))
self._add_structure_def(StructureDef('bcc', 'Body Centered Cubic', -1, 1, 'Fe', ew.cubic_flg | ew.orth_flg, 2.866))
self._add_structure_def(StructureDef('hcp', 'Hexagonal Close-Pack', 2, -1, 'Mg', ew.cubic_flg | ew.c_flg | ew.covera_flg, 3.209, 5.210))
self._add_structure_def(StructureDef('diamond', 'Diamond', 2, -1, 'C', ew.cubic_flg | ew.orth_flg, 3.5668))
self._add_structure_def(StructureDef('zincblende', 'Zincblende', 2, -1, 'ZnS', ew.cubic_flg | ew.orth_flg, 5.420))
self._add_structure_def(StructureDef('rocksalt', 'Rocksalt', 2, -1, 'NaCl', ew.cubic_flg | ew.orth_flg, 5.630))
self._add_structure_def(StructureDef('cesiumchloride', 'Cesiumchloride', -1, 2, 'CsCl', ew.cubic_flg, 4.110))
self._add_structure_def(StructureDef('fluorite', 'Fluorite', -1, 1, 'CaFF', ew.orth_flg | ew.c_flg | ew.covera_flg, 5.463))
self._add_structure_def(StructureDef('wurtzite', 'Wurtzite', -1, 1, 'ZnS', ew.cubic_flg | ew.c_flg | ew.u_flg | ew.covera_flg, 3.820, 6.260))
def _add_structure_def(self, structure_def):
"""
:type structure_def: StructureDef
"""
self._long_name_to_structure_def[structure_def.long_name] = structure_def
self._ase_name_to_structure_def[structure_def.ase_name] = structure_def
def get_structure_def(self, long_name=None, ase_name=None):
"""
:rtype: StructureDef
"""
assert (long_name is None) ^ (ase_name is None)
if long_name is not None:
return self._long_name_to_structure_def[long_name]
if ase_name is not None:
return self._ase_name_to_structure_def[ase_name]
class BulkFrame(wx.Dialog):
"""
:param structure_defs: list of crystal structures
:type structure_defs: dictionary
:param selected_crystal_str: the structure that will be
used to create the cluster
"""
structure_defs = StructureDefs()
@classmethod
def get_structure_defs(cls):
"""
"""
return cls.structure_defs
@classmethod
def get_structure_id(cls, structure_name):
"""
:type structure_name: str
:rtype: str
"""
return cls.structure_defs.get_structure_def(long_name=structure_name).ase_name
@classmethod
def get_structure_name(cls, structure_id):
"""
:type structure_id: str
:rtype: str
"""
return cls.structure_defs.get_structure_def(ase_name=structure_id).long_name
def get_crystal_def(self, long_name):
"""
:param long_name : the structure long name (eg 'Face Centered Cubic'
:type long_name : str
:rtype: StructureDef
"""
return BulkFrame.get_structure_defs().get_structure_def(long_name=long_name)
selected_crystal_str = ""
def __init__(self, parent, title, bulk_operator):
"""
:param parent: window that contains this window
:type parent: wx.Widget
:param bulk_operator: the bulk_operator that this gui allows the user to modify
:type bulk_operator: msspec.cluster.generators.Bulk
"""
self._bulk_operator = bulk_operator
super(BulkFrame, self).__init__(parent, title=title)
self.initui()
self.Centre()
self.Show()
# --------------------------------------------------------------------------
def initui(self):
"""
the constructor of the interface
"""
self.vbox = wx.BoxSizer(wx.VERTICAL)
self.panel = wx.Panel(parent=self, id=1)
main_params_sizer = wx.GridBagSizer()
# all crystal structures that can be selected to create the cluster
self.crystal_structures = ['Simple Cubic',
'Face Centered Cubic',
'Body Centered Cubic',
'Hexagonal Close-Pack',
'Diamond',
'Zincblende',
'Rocksalt',
'Cesiumchloride',
'Wurtzite']
# crystal structure
crystalstructure_txt = wx.StaticText(self.panel, label="Crystal structure : ")
main_params_sizer.Add(crystalstructure_txt, pos=(1, 0), flag=wx.LEFT | wx.TOP, border=10)
# list of crystal structures
self.crystr_cbx = wx.ComboBox(self.panel, choices=self.crystal_structures)
main_params_sizer.Add(self.crystr_cbx, pos=(1, 1), border=10)
crystal_structure_name = BulkFrame.get_structure_name(self._bulk_operator.structure)
self.crystr_cbx.Select(self.crystal_structures.index(crystal_structure_name))
# a ToolTip associated to the combobox
self.crystr_cbx.SetToolTip(wx.ToolTip("choose a crystal structure"))
self.crystr_cbx.Bind(wx.EVT_COMBOBOX, self.on_crystal_select)
# the atoms that constitute the crystal
self.atoms_txt = wx.StaticText(self.panel, label="Atoms : ")
main_params_sizer.Add(self.atoms_txt, pos=(3, 0), flag=wx.LEFT, border=10)
# to insert the name of the cluster
self.atoms_txc = wx.TextCtrl(self.panel)
main_params_sizer.Add(self.atoms_txc, pos=(3, 1), flag=wx.EXPAND, border=10)
# default value is 'MgO'
self.atoms_txc.SetLabel(self._bulk_operator.atoms)
self.atoms_txc.SetToolTip(wx.ToolTip("the atoms that form the crystal (eg MgO or NaCl)"))
self.atoms_txc.Bind(wx.EVT_TEXT, self.alert_crystal_name)
self.numatoms_txt = wx.StaticText(self.panel, label="this crystal is composed of two atoms")
main_params_sizer.Add(self.numatoms_txt, pos=(4, 1), flag=wx.LEFT, border=10)
# is cubic
self.is_cubic_txt = wx.StaticText(self.panel, label="Cubic : ")
main_params_sizer.Add(self.is_cubic_txt, pos=(5, 0), flag=wx.TOP | wx.LEFT, border=10)
self.is_cubic_chk = wx.CheckBox(self.panel)
main_params_sizer.Add(self.is_cubic_chk, pos=(5, 1), flag=wx.TOP | wx.EXPAND, border=10)
self.is_cubic_chk.SetValue(self._bulk_operator.is_cubic)
# is othorhombic
self.is_orth_txt = wx.StaticText(self.panel, label="Orthorhombic : ")
main_params_sizer.Add(self.is_orth_txt, pos=(7, 0), flag=wx.TOP | wx.LEFT, border=10)
self.is_orth_chk = wx.CheckBox(self.panel)
main_params_sizer.Add(self.is_orth_chk, pos=(7, 1), flag=wx.TOP | wx.EXPAND, border=10)
self.is_orth_chk.SetValue(self._bulk_operator.is_orthorombic)
# lattice parameters
lattice_params_sizer = wx.BoxSizer(wx.VERTICAL)
self.lattice_params_stbox = wx.StaticBox(self.panel, label='Lattice Parameters')
lattice_params_subsizer = wx.StaticBoxSizer(self.lattice_params_stbox, wx.VERTICAL)
lattice_params_subsizer.AddSpacer(3)
# lattice parameter a
a_sizer = wx.BoxSizer(wx.HORIZONTAL)
a_txt = wx.StaticText(self.panel, label=" a = ")
self.a_fspin = fs.FloatSpin(self.panel,
value=self._bulk_operator.a, size=(180, -1),
min_val=0.1, max_val=10.00,
increment=0.01,
style=fs.FS_CENTRE)
self.a_fspin.SetFormat("%f")
self.a_fspin.SetDigits(6)
self.a_fspin.Bind(fs.EVT_FLOATSPIN, self.alert_a)
self.a_fspin.Bind(wx.EVT_SPINCTRL, self.on_a_changed)
a_sizer.Add(a_txt)
a_sizer.Add(self.a_fspin)
lattice_params_subsizer.AddSpacer(10)
# lattice parameter c
c_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.c_txt = wx.StaticText(self.panel, 6, label=" c = ")
self.c_txt.Disable()
self.c_fspin = fs.FloatSpin(self.panel,
value=self._bulk_operator.c, size=(160, -1),
min_val=0.1, max_val=10.00, increment=0.01)
self.c_fspin.SetFormat("%f")
self.c_fspin.SetDigits(6)
self.c_fspin.Bind(wx.EVT_SPINCTRL, self.on_c_changed)
self.c_fspin.Disable()
c_sizer.Add(self.c_txt)
c_sizer.Add(self.c_fspin)
# lattice parameter u
u_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.u_txt = wx.StaticText(self.panel, 7, label=" u = ")
self.u_txt.Disable()
self.u_fspin = fs.FloatSpin(self.panel,
value=self._bulk_operator.u, size=(160, -1),
min_val=0.1, max_val=10.00, increment=0.01)
self.u_fspin.SetFormat("%f")
self.u_fspin.SetDigits(6)
self.u_fspin.Disable()
u_sizer.Add(self.u_txt)
u_sizer.Add(self.u_fspin)
# lattice parameter c/a
covera_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.covera_txt = wx.StaticText(self.panel, label="c/a ratio : ")
self.covera_txt.Disable()
self.covera_fspin = fs.FloatSpin(self.panel,
value='0.0', size=(160, -1),
min_val=0.1, max_val=10.00, increment=0.01)
self.covera_fspin.SetFormat("%f")
self.covera_fspin.SetDigits(6)
self.covera_fspin.Bind(wx.EVT_SPINCTRL, self.on_covera_changed)
self.covera_fspin.Disable()
covera_sizer.Add(self.covera_txt)
covera_sizer.Add(self.covera_fspin)
lattice_params_subsizer.Add(a_sizer)
lattice_params_subsizer.AddSpacer(10)
lattice_params_subsizer.Add(c_sizer)
lattice_params_subsizer.AddSpacer(10)
lattice_params_subsizer.Add(u_sizer)
lattice_params_subsizer.AddSpacer(10)
lattice_params_subsizer.Add(covera_sizer)
lattice_params_subsizer.AddSpacer(10)
lattice_params_sizer.Add(lattice_params_subsizer, flag=wx.ALL | wx.EXPAND, border=5)
ok_cancel_reset_sizer = wx.BoxSizer(wx.HORIZONTAL)
reset_btn = wx.Button(self.panel, 2, label="Reset")
reset_btn.Bind(wx.EVT_BUTTON, self.on_reset_crystal_params)
cancel_btn = wx.Button(self.panel, wx.ID_CANCEL, label="Cancel")
cancel_btn.Bind(wx.EVT_BUTTON, self.on_close, id=wx.ID_CANCEL)
self.ok_btn = wx.Button(self.panel, wx.ID_OK, label="OK")
self.ok_btn.Bind(wx.EVT_BUTTON, self.on_ok, id=wx.ID_OK)
ok_cancel_reset_sizer.Add(reset_btn)
ok_cancel_reset_sizer.Add(cancel_btn)
ok_cancel_reset_sizer.Add(self.ok_btn)
self.vbox.Add(main_params_sizer)
self.vbox.Add(lattice_params_sizer, proportion=1, flag=wx.LEFT | wx.EXPAND, border=10)
self.vbox.Add(ok_cancel_reset_sizer, flag=wx.ALIGN_RIGHT, border=5)
self.panel.SetSizer(self.vbox)
self.panel.Fit()
self.Fit()
def on_reset_crystal_params(self, event):
self.on_crystal_select(event)
def on_close(self, event):
event.Skip() # propagate the event so that the dialog closes
def on_ok(self, event):
a_val = str(self.a_fspin.GetValue())
c_val = str(self.c_fspin.GetValue())
u_val = str(self.u_fspin.GetValue())
covera_val = str(self.covera_fspin.GetValue())
print('bulk = (%s,' % self.atoms_txc.GetValue() +
' %s,' % self.crystr_cbx.GetValue() + ' a= %s,' % a_val +
' c= %s,' % c_val +
' u= %s,' % u_val +
' c/a ratio= %s,' % covera_val +
' cubic= %s,' % str(self.is_cubic_chk.GetValue()) +
' orthorombic= %s)' % str(self.is_orth_chk.GetValue()))
self._bulk_operator.atoms = self.atoms_txc.GetValue().encode('ascii', 'ignore')
self._bulk_operator.structure = BulkFrame.get_structure_id(self.crystr_cbx.GetValue())
self._bulk_operator.a = float(a_val)
self._bulk_operator.c = float(c_val)
self._bulk_operator.u = float(u_val)
self._bulk_operator.is_cubic = self.is_cubic_chk.GetValue()
self._bulk_operator.is_orthorombic = self.is_orth_chk.GetValue()
event.Skip() # propagate the event so that the dialog closes
def alert_a(self, event):
if self.a_fspin.GetValue() == 0.00:
wx.MessageBox("Please enter the parameter a",
style=wx.OK | wx.ICON_MASK)
def alert_crystal_name(self, event):
if self.atoms_txc.GetValue() == '':
wx.MessageBox("Please enter the Atoms that\ncomposed the crystal ",
style=wx.CANCEL | wx.ICON_MASK)
def on_a_changed(self, event):
"""
change values of c and covera when the user insert the value of the parameter a
"""
self.covera_fspin.SetValue(self.c_fspin.GetValue() / self.a_fspin.GetValue())
def on_c_changed(self, event):
"""
change values of a and covera when the user insert the value of the parameter c
"""
self.covera_fspin.SetValue(self.c_fspin.GetValue() / self.a_fspin.GetValue())
"""
def change_covera_val(self):
print ("---_cov_---")
# if self.c_fspin.GetValue() != 0.00000:
self.covera_fspin.SetValue(
self.c_fspin.GetValue() / self.a_fspin.GetValue())
print ("---***_cov_***---")
"""
def on_covera_changed(self, event):
"""
change values of c and a when the user
insert the value of the parameter covera
"""
self.change_covera_val()
def change_covera_val(self):
self.c_fspin.SetValue(self.covera_fspin.GetValue() * self.a_fspin.GetValue())
def get_atoms_constraint_msg(self, num_atoms_min, num_atoms_max):
"""
return the constraint of atoms number
"""
message = ''
if num_atoms_min != -1:
message += 'minimum %d atoms' % num_atoms_min
if num_atoms_max != -1:
if len(message) > 0:
message += ', '
message += 'maximum %d atoms' % num_atoms_max
return message
def update_widgets(self, enabled_widgets):
"""
manage the widgets
"""
ew = StructureDef.EnabledWidgets
if enabled_widgets & ew.cubic_flg:
self.is_cubic_txt.Enable()
self.is_cubic_chk.Enable()
self.is_cubic_chk.SetValue(False)
else:
self.is_cubic_chk.SetValue(False)
self.is_cubic_txt.Disable()
self.is_cubic_chk.Disable()
if enabled_widgets & ew.orth_flg:
self.is_orth_txt.Enable()
self.is_orth_chk.Enable()
self.is_orth_chk.SetValue(False)
else:
self.is_orth_chk.SetValue(False)
self.is_orth_txt.Disable()
self.is_orth_chk.Disable()
if enabled_widgets & ew.c_flg:
self.c_txt.Enable()
self.c_fspin.Enable()
else:
self.c_txt.Disable()
self.c_fspin.Disable()
if enabled_widgets & ew.u_flg:
self.u_txt.Enable()
self.u_fspin.Enable()
else:
self.u_txt.Disable()
self.u_fspin.Disable()
if enabled_widgets & ew.covera_flg:
self.covera_txt.Enable()
self.covera_fspin.Enable()
else:
self.covera_txt.Disable()
self.covera_fspin.Disable()
def on_crystal_select(self, event):
"""
when the user choose one structure this function will be triggered
"""
self.selected_crystal_str = self.crystr_cbx.GetValue()
# print self.selected_crystal_str
crystal_def = self.get_crystal_def(self.selected_crystal_str)
# print ('values : %s' % crystal_def)
self.numatoms_txt.SetLabel(str(self.get_atoms_constraint_msg(crystal_def.min_atoms, crystal_def.max_atoms)))
self.update_widgets(crystal_def.enabled_widgets)
self.atoms_txc.SetLabel(crystal_def.default_atoms)
self.a_fspin.SetValue(crystal_def.default_a)
self.c_fspin.SetValue(crystal_def.default_c)
self.covera_fspin.SetValue(crystal_def.default_c / crystal_def.default_a)
self.a_fspin.Bind(fs.EVT_FLOATSPIN, self.alert_a)
self.lattice_params_stbox.Refresh()
self.vbox.RecalcSizes()
self.panel.SetSizer(self.vbox)
if __name__ == '__main__':
app = wx.App()
BulkFrame(None, title="create a single cell", bulk_operator=None)
app.MainLoop()

View File

@ -0,0 +1,294 @@
'''
Created on Jan 18, 2016
@author: graffy
'''
# standard libraries
import os
import functools
# import math
# import sys
# from fileinput import close
import ase.io
# 3rd party libraries
import wx
# import cairo
# import wx.lib.wxcairo
# local libraries
import msspecgui.datafloweditor as datafloweditor
import msspecgui.dataflow as dataflow
from msspecgui.dataflow import IDataType
from msspecgui.msspec.gui.bulk_interface import BulkFrame
from msspecgui.msspec.gui.viewmanager import IViewCreator
from msspecgui.datafloweditor import OperatorGui
# based on example https://github.com/wxWidgets/wxPython/blob/master/demo/Cairo.py
class BackgroundContextMenu(wx.Menu):
'''
the context menu that the user sees when right-clicking on the background of the dataflow
'''
def __init__(self, panel):
"""
:type panel: msspecgui.msspec.gui.ClusterEditor
"""
wx.Menu.__init__(self)
self.m_panel = panel
# wx.ID_ABOUT and wx.ID_EXIT are standard IDs provided by wxWidgets.
# self.add_cluster_modifier_menu_item = self.Append(wx.ID_ANY,"Add Cluster Modifier"," adds a cluster modifier in the cluster editor")
self.add_cluster_modifier_menu = wx.Menu()
self.add_cluster_modifier_menu_item = self.AppendMenu(wx.ID_ANY, "Add Cluster Modifier...", self.add_cluster_modifier_menu)
# self.m_panel.Bind(wx.EVT_MENU, self.on_add_cluster_modifier, self.add_cluster_modifier_menu_item)
data_flow = self.m_panel.get_cluster_flow()
for creator in data_flow.get_operator_creators():
operator_type_id = creator.get_operator_type_id()
menu_item = self.add_cluster_modifier_menu.Append(wx.ID_ANY, operator_type_id, " adds a cluster modifier in the cluster editor")
# self.m_panel.Bind(wx.EVT_MENU, functools.partial( creator.create_operator, dflow = data_flow), menu_item)
self.m_panel.Bind(wx.EVT_MENU, functools.partial(self.on_add_cluster_modifier, operator_type_id=operator_type_id), menu_item)
def on_add_cluster_modifier(self, event, operator_type_id):
"""Callback that is invoked when the user chooses to add a specific cluster modifier using the context menu
:param operator_type_id: the type of cluster modifier to add (eg 'ipr.msspec.cutsphere')
"""
self.m_panel.create_operator(operator_type_id)
class OperatorContextMenu(wx.Menu):
'''
the context menu that the user sees when right-clicking on an operator of the dataflow
'''
def __init__(self, panel, operator):
"""
:type panel: msspecgui.msspec.gui.ClusterEditor
:type operator: dataflow.operator.Operator
"""
wx.Menu.__init__(self)
self.m_panel = panel
menu_item = self.Append(wx.ID_ANY, 'delete', "deletes this operator")
self.m_panel.Bind(wx.EVT_MENU, functools.partial(self.on_delete_operator, operator=operator), menu_item)
for action in operator.creator.get_actions():
menu_item = self.Append(wx.ID_ANY, action.get_name(), " performs this action on the operator")
# self.m_panel.Bind(wx.EVT_MENU, functools.partial( creator.create_operator, dflow = data_flow), menu_item)
self.m_panel.Bind(wx.EVT_MENU, functools.partial(self.on_perform_operator_action, action=action, operator=operator), menu_item)
def on_perform_operator_action(self, event, action, operator):
"""Callback that is invoked when the user chooses to add a specific cluster modifier using the context menu
:param operator_type_id: the type of cluster modifier to add (eg 'ipr.msspec.cutsphere')
:type action: dataflow.ioperatorcreator.IOperatorCreator.IAction
:type operator: dataflow.operator.Operator
"""
action.execute_on_operator(operator)
def on_delete_operator(self, event, operator):
"""Callback that is invoked when the user chooses to delete an operator
:param dataflow.Operator operator: the operator that needs to be deleted
"""
data_flow = operator.data_flow
data_flow.delete_operator(operator)
class WireContextMenu(wx.Menu):
'''
the context menu that the user sees when right-clicking on a wire of the dataflow
'''
def __init__(self, panel, wire):
"""
:param msspecgui.msspec.gui.ClusterEditor panel:
:param dataflow.Wire wire: the wire for which this context menu is created
"""
wx.Menu.__init__(self)
self.m_panel = panel
# # create a menu item for each wire action
# for wire_action in self.m_panel.get_cluster_flow().wire_actions:
# menu_item = self.Append(wx.ID_ANY, data_action.get_name(), " performs this action on the operator")
# self.m_panel.Bind(wx.EVT_MENU, functools.partial(self.on_perform_data_action, data_action=data_action, data=wire.input_plug.get_value()), menu_item)
menu_item = self.Append(wx.ID_ANY, 'delete', "deletes this wire")
self.m_panel.Bind(wx.EVT_MENU, functools.partial(self.on_delete_wire, wire=wire), menu_item)
for data_action in wire.data_type.get_actions():
menu_item = self.Append(wx.ID_ANY, data_action.get_name(), " performs this action on the operator")
self.m_panel.Bind(wx.EVT_MENU, functools.partial(self.on_perform_data_action, data_action=data_action, data=wire.input_plug.get_value()), menu_item)
# def on_perform_wire_action(self, event, wire_action, wire):
# """Callback that is invoked when the user chooses to add a specific cluster modifier using the context menu
#
# :param dataflow.Wire.IAction wire_action: the action to perform
# :param dataflow.Wire wire: the wire on which to perform the action
# """
# wire_action.execute_on_wire(wire)
def on_perform_data_action(self, event, data_action, data):
"""Callback that is invoked when the user chooses to add a specific data action using the context menu
:param dataflow.IDataType.IAction data_action: the action to perform on the given data
:param data: the data on which to perform the action
"""
assert isinstance(data, data_action.datatype.get_python_class())
data_action.execute_on_data(data)
def on_delete_wire(self, event, wire):
"""Callback that is invoked when the user chooses to delete a wire using the context menu
:param dataflow.Wire wire: the wire that needs to be deleted
"""
data_flow = wire.data_flow
data_flow.delete_wire(wire)
def opj(path):
"""Convert paths to the platform-specific separator"""
platform_path = apply(os.path.join, tuple(path.split('/')))
# HACK: on Linux, a leading / gets lost...
if path.startswith('/'):
platform_path = '/' + platform_path
return platform_path
class BulkGui(dataflow.IOperatorCreator.IAction):
def get_name(self):
return 'properties via gui'
def execute_on_operator(self, operator):
"""
:type operator: dataflow.operator.Operator
"""
dialog = BulkFrame(None, title="create a single cell", bulk_operator=operator)
print("execute_on_operator : before MainLoop")
result = dialog.ShowModal()
if result == wx.ID_OK:
print("execute_on_operator : signaling operator %d as modified" % operator.id)
operator.data_flow.on_modified_operator(operator)
print "OK"
else:
print "Cancel"
dialog.Destroy()
class ExportCluster(IDataType.IAction):
def __init__(self, cluster_flow):
"""
:param msspecgui.cluster.ClusterFlow cluster_flow:
"""
super(ExportCluster, self).__init__(cluster_flow.get_data_type('physics.atomscluster'))
def get_name(self):
return 'export cluster'
def execute_on_data(self, data):
"""
:param data: the data on which this action needs to be performed
"""
assert isinstance(data, self.datatype.get_python_class())
dlg = wx.FileDialog(None, message="Choose a file to store this cluster (the format of the file is defined by the file's extension)", defaultDir='', defaultFile='', style=wx.FD_SAVE)
if dlg.ShowModal() == wx.ID_OK:
cluster_file_path = dlg.GetPath()
ase.io.write(cluster_file_path, data)
dlg.Destroy()
class ClusterEditor(datafloweditor.DataflowView):
class Creator(IViewCreator):
VIEW_TYPE_NAME = 'cluster editor'
def __init__(self, workspace):
"""
:param msspecgui.msspec.Workspace workspace: the workspace that is associated with the cluster editors created by this creator
"""
self._workspace = workspace
@property
def view_type_name(self):
"""
:return str:
"""
return self.VIEW_TYPE_NAME
def create_view(self, parent):
"""
:param wx.Window parent: the wx.Window that owns the view
:return wx.Panel:
"""
return ClusterEditor(parent, self._workspace.get_cluster_flow())
def __init__(self, parent, cluster_flow):
"""
:param cluster_flow: the dataflow that this editor manipulates
:type cluster_flow: msspec.cluster.clusterflow.ClusterFlow
"""
super(ClusterEditor, self).__init__(parent, cluster_flow, -1)
for operator_creator in cluster_flow.get_operator_creators():
if operator_creator.get_operator_type_id() == 'ase.lattice.bulk':
operator_creator.register_operator_action(BulkGui())
else:
operator_creator.register_operator_action(OperatorGui())
datatype = cluster_flow.get_data_type('physics.atomscluster')
datatype.register_datatype_action(ExportCluster(cluster_flow))
def create_operator(self, operator_type_id):
operator = self.get_cluster_flow().create_operator(operator_type_id)
self.get_cluster_flow().add_operator(operator)
def get_cluster_flow(self):
"""
:rtype: msspec.cluster.clusterflow.ClusterFlow
"""
return self.dataflow
def on_background_context_menu(self, event):
'''
called whenever the user right-clicks in the background of the dataflow
'''
pos = event.GetPosition()
# print(pos)
pos = self.ScreenToClient(pos)
self.PopupMenu(BackgroundContextMenu(self), pos)
def on_operator_context_menu(self, event, operator):
'''
called whenever the user right-clicks in an operator of the dataflow
:type event: wx.Event
:type operator: dataflow.operator.Operator
'''
pos = event.GetPosition()
# print(pos)
pos = self.ScreenToClient(pos)
self.PopupMenu(OperatorContextMenu(self, operator), pos)
def on_wire_context_menu(self, event, wire):
'''
called whenever the user right-clicks in a wire of the dataflow
:type event: wx.Event
:param dataflow.Wire wire:
'''
pos = event.GetPosition()
# print(pos)
pos = self.ScreenToClient(pos)
self.PopupMenu(WireContextMenu(self, wire), pos)

View File

@ -0,0 +1,201 @@
import wx
from msspecgui.msspec.gui.clusterviewer import ClusterViewer
from msspecgui.dataflow import DataFlow
from msspecgui.msspec.gui.viewmanager import IViewCreator
class ClusterView(wx.Window):
"""
a view that allows the user to view a cluster and also to select which cluster is viewed
"""
class Creator(IViewCreator):
VIEW_TYPE_NAME = '3d cluster viewer'
def __init__(self, cluster_flow):
"""
:param msspecgui.msspec.cluster.clusterflow.ClusterFlow cluster_flow: the cluster flow that is associated with the cluster viewers created with this creator
"""
self._cluster_flow = cluster_flow
@property
def view_type_name(self):
"""
:return str:
"""
return self.VIEW_TYPE_NAME
def create_view(self, parent):
"""
:param wx.Window parent: the wx.Window that owns the view
:return wx.Panel:
"""
return ClusterView(self._cluster_flow, parent)
class ClusterFlowEventsHandler(DataFlow.IDataFlowEventsHandler):
def __init__(self, cluster_view):
"""
:type cluster_view: ClusterView
"""
super(ClusterView.ClusterFlowEventsHandler, self).__init__()
self._cluster_view = cluster_view
# self._selected_cluster_chc = selected_cluster_chc
# self.__dataflow = dataflow
def on_added_operator(self, operator):
"""
:type operator: Operator
"""
super(ClusterView.ClusterFlowEventsHandler, self).on_added_operator(operator)
self._cluster_view.update_cluster_cbx()
def on_deleted_operator(self, operator):
"""
:param Operator operator:
"""
super(ClusterView.ClusterFlowEventsHandler, self).on_deleted_operator(operator)
# the available clusters might have changed
self._cluster_view.update_cluster_cbx()
# force an update of the displayed atoms
if self._cluster_view.selected_cluster is not None:
self._cluster_view.selected_cluster = self._cluster_view.selected_cluster
def on_modified_operator(self, operator):
"""
:type operator: Operator
"""
super(ClusterView.ClusterFlowEventsHandler, self).on_modified_operator(operator)
self._cluster_view.update_cluster_cbx()
# force an update of the displayed atoms
if self._cluster_view.selected_cluster is not None:
self._cluster_view.selected_cluster = self._cluster_view.selected_cluster
def on_added_wire(self, wire):
super(ClusterView.ClusterFlowEventsHandler, self).on_added_wire(wire)
self._cluster_view.update_cluster_cbx()
def on_deleted_wire(self, wire):
super(ClusterView.ClusterFlowEventsHandler, self).on_deleted_wire(wire)
self._cluster_view.update_cluster_cbx()
# force an update of the displayed atoms
if self._cluster_view.selected_cluster is not None:
self._cluster_view.selected_cluster = self._cluster_view.selected_cluster
def __init__(self, cluster_dataflow, *args, **kwargs):
"""
:param msspecgui.msspec.cluster.clusterflow.ClusterFlow cluster_dataflow: the cluster flow to which this view is attached
"""
super(ClusterView, self).__init__(*args, **kwargs)
self._cluster_dataflow = cluster_dataflow
self._available_clusters = None
self._selected_cluster = None
main_box = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(main_box)
# add the choice widget that allows the user to select the cluster he wants to view
widgets_border = 1 # number of pixels used as a border between widgets
widgets_spacing = 3 # size of the spacings separating widgets, in pixels
selected_cluster_box = wx.BoxSizer(wx.HORIZONTAL)
selected_cluster_lbl = wx.StaticText(self, id=-1, label=u'Viewed cluster')
selected_cluster_box.Add(selected_cluster_lbl)
selected_cluster_box.AddSpacer(widgets_spacing)
self._selected_cluster_chc = wx.Choice(self, size=wx.Size(400, -1))
selected_cluster_box.Add(self._selected_cluster_chc)
main_box.Add(selected_cluster_box, proportion=0, flag=wx.TOP | wx.BOTTOM, border=widgets_border)
self._selected_cluster_chc.Bind(wx.EVT_CHOICE, self.on_cluster_selection_changed)
main_box.AddSpacer(widgets_spacing)
self._cluster_viewer = ClusterViewer(self)
#self._cluster_viewer.light_mode_threshold = 2
main_box.Add(self._cluster_viewer, proportion=1, flag=wx.EXPAND | wx.TOP | wx.BOTTOM, border=widgets_border)
self._clusterflow_events_handler = ClusterView.ClusterFlowEventsHandler(self)
self._cluster_dataflow.add_dataflow_events_handler(self._clusterflow_events_handler)
self._clusterflow_events_handler.on_added_operator(operator=None)
self.Bind(wx.EVT_CLOSE, self.on_close)
def on_close(self, event):
print("ClusterView.on_close")
self._cluster_dataflow.remove_dataflow_events_handler(self._clusterflow_events_handler)
self.Close(True)
@property
def cluster_viewer(self):
return self._cluster_viewer
@classmethod
def _get_cluster_id_string(cls, plug):
"""
:type plug: Plug
:rtype: str
"""
op = plug.operator
cluster_id = '%s(%d).%s' % (op.creator.get_operator_type_id(), op.id, plug.name)
return cluster_id
def update_cluster_cbx(self):
"""update the widget that allows the user to choose the viewed cluster
"""
print('update_cluster_cbx')
available_clusters = []
for op in self._cluster_dataflow.operators:
# cluster_id = '%s (%d)' % (op.creator.get_operator_type_id(), op.id)
for plug in op.get_output_plugs():
# cluster_id += '.' + plug.name
# available_clusters.append(self._get_cluster_id_string(plug))
if plug.value_is_available(): # only propose the clusters that can be computed
available_clusters.append(plug)
# if len(available_clusters) == 0:
# available_clusters.append('no cluster available')
self._selected_cluster_chc.SetItems([self._get_cluster_id_string(plug) for plug in available_clusters])
self._available_clusters = dict(enumerate(available_clusters))
# keep the selected cluster in the choices, if possible
if self._selected_cluster is not None:
selected_cluster_indices = [k for k, plug in self._available_clusters.items() if plug == self._selected_cluster]
if len(selected_cluster_indices) != 0:
selected_cluster_index = selected_cluster_indices[0]
self._selected_cluster_chc.SetSelection(selected_cluster_index)
else:
# the selected cluster is no longer available (for example because its operator has been deleted)
self._selected_cluster_chc.SetSelection(wx.NOT_FOUND)
self.selected_cluster = None
@property
def selected_cluster(self):
"""
:rtype: dataflow.Plug
"""
return self._selected_cluster
@selected_cluster.setter
def selected_cluster(self, selected_cluster):
"""
:param dataflow.Plug selected_cluster:
"""
self._selected_cluster = selected_cluster
if selected_cluster is not None:
# print('ClusterView.selected_cluster : setting self._selected_cluster to %s' % selected_cluster.name)
# print("ClusterView.selected_cluster : updating viewed atoms")
self._cluster_viewer.set_atoms(selected_cluster.get_value(), rescale=True)
else:
self._cluster_viewer.set_atoms(None, rescale=True)
def on_cluster_selection_changed(self, event):
"""
callback when the user changes the selected cluster in the choice widget
:type event: wx.CommandEvent
"""
if len(self._available_clusters) != 0:
new_selected_cluster = self._available_clusters[event.GetInt()]
else:
new_selected_cluster = None
self.selected_cluster = new_selected_cluster

View File

@ -0,0 +1,897 @@
# -*- encoding: utf-8 -*-
# vim: set fdm=indent ts=2 sw=2 sts=2 et tw=80 cc=+1 mouse=a nu : #
# import wx
import numpy as np
# from time import clock
# import copy
import cairo
import wx.lib.wxcairo
# import ase
from ase.data import covalent_radii
from ase.data.colors import jmol_colors
class ClusterViewer(wx.Window):
"""
:param mx: last mouse position in x
:param my: last mouse position in y
"""
MODE_NONE = 0b0000000
MODE_SELECTION = 0b0000001
MODE_SELECTION_BOX = 0b0000010
MODE_SELECTION_APPEND = 0b0000100
MODE_SELECTION_TOGGLE = 0b0001000
MODE_TRANSLATION = 0b0010000
MODE_ROTATION = 0b0100000
def __init__(self, *args, **kwargs):
kwargs['style'] = wx.NO_FULL_REPAINT_ON_RESIZE | wx.CLIP_CHILDREN
wx.Window.__init__(self, *args, **kwargs)
self.ox = self.oy = 0 # offset in x and y
self.im_ox = self.im_oy = 0 # image offset in x and y
self.last_mouse_move_x = self.last_mouse_move_y = 0 # last mouse move
self.mx = self.my = 0 # last mouse position
self.theta = self.phi = 0
self.scale = self.scale0 = 100
self.im_factor = self.im_scale = 1
self.atoms = None
# self.do_rescale = False
# self.do_center = False
self.atoms_center_of_mass = np.zeros(3)
self.atoms_largest_dimension = 1.0 # float, in angstrom
self.selection = []
self.selection_box = None
self.__outer_margin = 0
self.surface = None
self.busy = False
self.refresh_delay = 200
self.back_buffer = None
self.screenshot = None
self.atom_numbers = None
self.atom_surfaces = None
self.atoms_sprite = None
self.background_sprite = None
self.mode = self.MODE_NONE
self.colors = {
'selection_box': (0.0, 0.4, 1.0),
'boulding_box_line': (0.0, 0.4, 1.0, 1.0),
'boulding_box_fill': (0.0, 0.4, 1.0, 0.3),
}
self.sprites_opts = {'alpha': 1, 'glow': True}
self.light_mode = False
self.light_mode_threshold = 2000
self.rotation_matrix = np.identity(4)
self.scale_matrix = np.identity(4)
self.translation_matrix = np.identity(4)
self.model_matrix = np.identity(4)
self.projection_matrix = np.identity(4)
# model to world matrix
self.m2w_matrix = np.identity(4)
# world to view matrix
self.w2v_matrix = np.identity(4)
# view to projection matrix
viewport = (-1., 1., -1., 1., -1., 1.)
self.v2p_matrix = self.create_v2p_matrix(*viewport)
self.projections = None
self.timer = wx.Timer(self)
self.Bind(wx.EVT_PAINT, self.__evt_paint_cb)
self.Bind(wx.EVT_SIZE, self.__evt_size_cb)
self.Bind(wx.EVT_MOUSEWHEEL, self.__evt_mousewheel_cb)
self.Bind(wx.EVT_MOTION, self.__evt_motion_cb)
self.Bind(wx.EVT_LEFT_DOWN, self.__evt_left_down_cb)
self.Bind(wx.EVT_LEFT_UP, self.__evt_left_up_cb)
self.Bind(wx.EVT_RIGHT_UP, self.__evt_right_up_cb)
self.Bind(wx.EVT_TIMER, self.__evt_timer_cb, self.timer)
def show_emitter(self, show=True):
_opts = self.sprites_opts.copy()
if show:
self.sprites_opts['alpha'] = 0.25
self.sprites_opts['glow'] = False
else:
self.sprites_opts = _opts.copy()
def set_atoms(self, atoms, rescale=False, center=True):
"""
Attach an Atoms object to the view.
This will translate the model to the center of mass, move the model center
to the center of screen and adjust the scale to the largest dimension of the
model
:param rescale: if True, the zoom is computed to view the atoms; if False, a fixed zoom value is used
"""
if atoms is None:
self.light_mode = False
self.atoms_center_of_mass = np.zeros(3)
self.atoms_largest_dimension = 1.0
self.atom_numbers = None
self.atom_surfaces = None
self.atoms_sprite = None
self.projections = None
else:
# Set the light mode according to the number of atoms
if len(atoms) > self.light_mode_threshold: # pylint: disable=simplifiable-if-statement
self.light_mode = True
else:
self.light_mode = False
# get the center of mass
self.atoms_center_of_mass = atoms.get_center_of_mass()
# get the largest dimension
p = atoms.get_positions()
self.atoms_largest_dimension = np.max(np.amax(p, axis=0) - np.amin(p, axis=0))
if self.atoms_largest_dimension == 0:
self.atoms_largest_dimension = 1.0
# make atoms a class attribute
self.atoms = atoms
# self.do_rescale = rescale
# self.do_center = center
self.update_camera(center=center, rescale=rescale)
# create the textures
self.create_atom_sprites()
# finally update the view
self.update_drawing()
def rotate_atoms(self, dtheta, dphi):
self.theta += dtheta
self.phi += dphi
tx, ty = (self.theta, self.phi)
m_mat = np.zeros((4, 4))
m_mat[0, 0] = m_mat[3, 3] = 1
m_mat[1, 1] = m_mat[2, 2] = np.cos(np.radians(tx))
m_mat[2, 1] = -np.sin(np.radians(tx))
m_mat[1, 2] = np.sin(np.radians(tx))
n_mat = np.zeros((4, 4))
n_mat[1, 1] = n_mat[3, 3] = 1
n_mat[0, 0] = n_mat[2, 2] = np.cos(np.radians(ty))
n_mat[0, 2] = -np.sin(np.radians(ty))
n_mat[2, 0] = np.sin(np.radians(ty))
self.rotation_matrix = np.dot(m_mat, n_mat)
self.update_model_matrix()
self.scale_atoms(self.scale)
def scale_atoms(self, factor):
self.scale = factor
self.scale_matrix[(0, 1, 2), (0, 1, 2)] = factor
self.create_atom_sprites()
self.update_projection_matrix()
def translate_atoms(self, x, y):
"""
sets the translation of the atoms
"""
# print('translate_atoms : x=%f, y=%f' % (x, y))
self.ox = x
self.oy = y
self.im_ox += self.last_mouse_move_x
self.im_oy += self.last_mouse_move_y
self.last_mouse_move_x = self.last_mouse_move_y = 0
self.translation_matrix[-1, (0, 1)] = (x, y)
self.update_projection_matrix()
# print('translate_atoms : self.projection_matrix=%s' % str(self.projection_matrix))
def select_atoms(self, x, y, w=None, h=None, append=False,
toggle=False):
selection = np.array([])
if w is None and h is None:
# get the projections
p = self.projections.copy()
# translate to the event point
p[:, :2] -= (x, y)
# compute the norm and the radius for each projected atom
norm = np.linalg.norm(p[:, :2], axis=1)
radii = covalent_radii[p[:, 4].astype(int)] * self.scale
# search where the norm is inside an atom
i = np.where(norm < radii)
# pick up the atom index of the one with the z min
try:
selection = np.array([int(p[i][np.argmin(p[i, 2]), 5])])
# self.selection = np.array([selection])
except:
pass
else:
if w < 0:
x += w
w = abs(w)
if h < 0:
y += h
h = abs(h)
p = self.projections.copy()
p = p[np.where(p[:, 0] > x)]
p = p[np.where(p[:, 0] < x + w)]
p = p[np.where(p[:, 1] > y)]
p = p[np.where(p[:, 1] < y + h)]
selection = p[:, -1].astype(int)
if toggle:
print(self.selection)
# whether atoms in the current selection were previously selected
i = np.in1d(self.selection, selection)
print(i)
self.selection = self.selection[np.invert(i)]
if append:
self.selection = np.append(self.selection, selection)
self.selection = np.unique(self.selection)
else:
self.selection = selection
def __evt_paint_cb(self, event):
self.swap_buffers()
def __evt_size_cb(self, event):
self.timer.Stop()
self.timer.Start(self.refresh_delay)
size = self.GetClientSize()
self.back_buffer = cairo.ImageSurface(cairo.FORMAT_RGB24, *size)
self.create_background_sprite(*size)
self.update_drawing()
def __evt_timer_cb(self, event):
self.update_drawing(light=False)
self.timer.Stop()
def __evt_left_down_cb(self, event):
self.mx = event.GetX()
self.my = event.GetY()
self.capture_screen()
if event.ControlDown():
self.mode |= self.MODE_SELECTION
if event.ShiftDown():
self.mode |= self.MODE_SELECTION_APPEND
if event.AltDown():
self.mode |= self.MODE_SELECTION_TOGGLE
def __evt_left_up_cb(self, event):
if self.mode & self.MODE_SELECTION:
self.mode ^= self.MODE_SELECTION
# search for atoms in the selection box
x, y = event.GetPosition()
w = h = None
if self.mode & self.MODE_SELECTION_BOX:
self.mode ^= self.MODE_SELECTION_BOX
x, y, w, h = self.selection_box
append = False
if self.mode & self.MODE_SELECTION_APPEND:
self.mode ^= self.MODE_SELECTION_APPEND
append = True
toggle = False
if self.mode & self.MODE_SELECTION_TOGGLE:
self.mode ^= self.MODE_SELECTION_TOGGLE
toggle = True
self.select_atoms(x, y, w, h, append=append, toggle=toggle)
if self.mode == self.MODE_TRANSLATION:
self.mode ^= self.MODE_TRANSLATION
self.update_drawing(light=False)
def __evt_right_up_cb(self, event):
if self.mode & self.MODE_ROTATION:
self.mode ^= self.MODE_ROTATION
self.update_drawing(light=False)
def __evt_motion_cb(self, event):
self.timer.Stop()
self.timer.Start(self.refresh_delay)
if event.LeftIsDown():
mx, my = event.GetPosition()
dx, dy = (mx - self.mx, my - self.my)
# if event.ControlDown():
if self.mode & self.MODE_SELECTION:
self.mode |= self.MODE_SELECTION_BOX
# if event.ShiftDown():
# self.mode |= self.MODE_SELECTION_APPEND
self.selection_box = [self.mx, self.my, dx, dy]
else:
self.mode = self.MODE_TRANSLATION
self.mx, self.my = (mx, my)
self.last_mouse_move_x = int(dx)
self.last_mouse_move_y = int(dy)
self.ox = int(self.ox + dx)
self.oy = int(self.oy + dy)
self.translate_atoms(self.ox, self.oy)
self.update_drawing()
elif event.RightIsDown():
self.mode = self.MODE_ROTATION
theta = 2. * (float(self.scale0) / self.scale)
theta = max(1., theta)
mx, my = event.GetPosition()
dx, dy = (mx - self.mx, my - self.my)
self.mx, self.my = (mx, my)
tx = theta * np.sign(dy)
ty = theta * np.sign(dx)
self.rotate_atoms(tx, ty)
self.update_drawing()
def __evt_mousewheel_cb(self, event):
rot = event.GetWheelRotation()
self.timer.Stop()
self.timer.Start(self.refresh_delay)
if rot > 0:
factor = self.scale * 1.1
im_factor = 1 * 1.1
elif rot < 0:
factor = self.scale / 1.1
im_factor = 1 / 1.1
self.im_factor = im_factor
self.scale_atoms(factor)
self.update_drawing()
def capture_screen(self):
# get size of screen
w, h = self.GetClientSize()
# create a cairo surface and context
surface = cairo.ImageSurface(cairo.FORMAT_RGB24, w, h)
ctx = cairo.Context(surface)
# trick here: blit the last back_buffer onto the newly created surface
ctx.set_source_surface(self.back_buffer)
ctx.paint()
# store it as an attribute
self.screenshot = surface
def create_atom_sprites(self):
"""
This function creates a list of cairo surfaces for each kind
of atoms
"""
# Get out if there are no atoms
if not self.atoms:
return
# First get an array of all atoms numbers
atom_numbers = np.unique(self.atoms.numbers)
# Now, for each kind of atoms create a surface in memory
atom_surfaces = np.empty((2, len(atom_numbers)), dtype=object)
self.__outer_margin = 0
def create_surface(atom_number, alpha=1, glow=True):
#global margin
# get the radius, and the color
radius = int(covalent_radii[atom_number] * 1. * self.scale)
r, g, b = jmol_colors[atom_number]
# actually create the surface
size = 2 * radius #+ 4
self.__outer_margin = np.maximum(self.__outer_margin, size / 2.)
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size)
# draw the ball
ctx = cairo.Context(surface)
# ctx.set_antialias(cairo.ANTIALIAS_NONE)
ctx.set_line_width(1.)
ctx.set_source_rgba(r, g, b, alpha)
ctx.arc(radius, radius, radius - 0.5, 0, 2 * np.pi)
ctx.fill_preserve()
if glow:
gradient = cairo.RadialGradient(radius, radius, radius / 2,
radius, radius, radius)
gradient.add_color_stop_rgba(0., 1., 1., 1., .5)
gradient.add_color_stop_rgba(0.5, 1., 1., 1., 0)
gradient.add_color_stop_rgba(1., 1., 1., 1., 0.)
ctx.set_source(gradient)
ctx.fill_preserve()
ctx.set_source_rgba(0., 0., 0., alpha)
ctx.stroke()
# Create the overlay for selection
overlay = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size)
# draw the circle
ctx = cairo.Context(overlay)
ctx.set_source_surface(surface)
ctx.paint()
ctx.set_line_width(2.)
ctx.set_source_rgb(1 - r, 1 - g, 1 - b)
ctx.arc(radius, radius, radius - 2., 0, 2 * np.pi)
ctx.stroke()
return surface, overlay
for i, a in enumerate(atom_numbers):
surface, overlay = create_surface(a, alpha=self.sprites_opts['alpha'],
glow=self.sprites_opts['glow'])
atom_surfaces[0, i] = surface
atom_surfaces[1, i] = overlay
"""
# get the radius, and the color
radius = int(covalent_radii[a] * 1. * self.scale)
# b, g, r = jmol_colors[a]
r, g, b = jmol_colors[a]
# actually create the surface
size = 2 * radius + 4
margin = np.maximum(margin, size / 2.)
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size)
# draw the ball
ctx = cairo.Context(surface)
# ctx.set_antialias(cairo.ANTIALIAS_NONE)
ctx.set_line_width(1.)
ctx.set_source_rgba(r, g, b, self.sprites_opts['alpha'])
ctx.arc(radius, radius, radius - 0.5, 0, 2 * np.pi)
ctx.fill_preserve()
if self.sprites_opts['glow']:
gradient = cairo.RadialGradient(radius, radius, radius / 2,
radius, radius, radius)
gradient.add_color_stop_rgba(0., 1., 1., 1., .5)
gradient.add_color_stop_rgba(0.5, 1., 1., 1., 0)
gradient.add_color_stop_rgba(1., 1., 1., 1., 0.)
ctx.set_source(gradient)
ctx.fill_preserve()
ctx.set_source_rgba(0., 0., 0., self.sprites_opts['alpha'])
ctx.stroke()
# store it
atom_surfaces[0, i] = surface
# Create the overlay for selection
overlay = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size)
# draw the circle
ctx = cairo.Context(overlay)
ctx.set_source_surface(surface)
ctx.paint()
ctx.set_line_width(2.)
ctx.set_source_rgb(1 - r, 1 - g, 1 - b)
ctx.arc(radius, radius, radius - 2., 0, 2 * np.pi)
ctx.stroke()
atom_surfaces[1, i] = overlay
"""
self.atom_numbers = atom_numbers
self.atom_surfaces = atom_surfaces
try:
absorber_number = self.atoms[self.atoms.info['absorber']].number
self.absorber_surface = create_surface(absorber_number, alpha=1, glow=True)
except:
self.atoms.info['absorber'] = -1
self.__outer_margin *= 1.1
def create_background_sprite(self, w, h):
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
ctx = cairo.Context(surface)
if True: # pylint: disable=using-constant-test
g = cairo.LinearGradient(0, 0, 0, h)
g.add_color_stop_rgba(0.0, 1.0, 1.0, 1.0, 1.0)
g.add_color_stop_rgba(0.7, 1.0, 1.0, 1.0, 1.0)
g.add_color_stop_rgba(1.0, 0.5, 0.5, 0.5, 1.0)
ctx.set_source(g)
ctx.rectangle(0, 0, w, h)
ctx.fill()
g = cairo.LinearGradient(0, 0, 0, h)
#g.add_color_stop_rgba(0., 1., 1., 1., 1.)
#g.add_color_stop_rgba(2 / 3., 0.5, 0.5, 0.5, 1)
#g.add_color_stop_rgba(0.0, 1.0, 1.0, 1.0, 1.0)
#g.add_color_stop_rgba(0.9, 0.8, 0.8, 0.8, 1.0)
#g.add_color_stop_rgba(1.0, 0.2, 0.2, 0.2, 1.0)
#ctx.set_source(g)
ctx.set_source_rgb(1, 1, 1)
ctx.rectangle(0, 0, w, h)
ctx.fill()
ctx.save()
if False:
ctx.set_source_rgb(0.8, 0.8, 0.8)
rect = (0, 2 * h / 3, w, h / 3)
ctx.rectangle(*rect)
ctx.clip()
ctx.paint()
ctx.set_line_width(1.)
for i in np.arange(0, 2 * np.pi, np.pi / 30):
ctx.move_to(w / 2, 2 * h / 3)
x1 = w * np.cos(i)
y1 = w * np.sin(i)
ctx.rel_line_to(x1, y1)
for i in np.arange(2 * h / 3, h, 10):
ctx.move_to(0, i)
ctx.line_to(w, i)
ctx.set_source_rgb(0.7, 0.7, 0.7)
ctx.stroke()
ctx.restore()
self.background_sprite = surface
@classmethod
def create_v2p_matrix(cls, left, right, bottom, top, near, far):
"""
creates the matrix that transforms coordinates from view space (space defined by the bounding box passed as argument) to projection space
this transformation is a scale and offset that maps [left; right], [bottom; top], [near; far] to [-1;1], [-1;1], [0;1]
"""
v2p_matrix = np.eye(4)
v2p_matrix[0, 0] = 2. / (right - left)
v2p_matrix[1, 1] = 2. / (right - left)
v2p_matrix[2, 2] = 1. / (near - far)
v2p_matrix[3, 0] = (left + right) / (left - right)
v2p_matrix[3, 1] = (top + bottom) / (bottom - top)
v2p_matrix[3, 2] = near / (near - far)
return v2p_matrix
def update_projection_matrix(self):
# print('update_projection_matrix : self.v2p_matrix=%s' % str(self.v2p_matrix))
# print('update_projection_matrix : self.translation_matrix=%s' % str(self.translation_matrix))
m_matrix = np.dot(self.v2p_matrix, self.scale_matrix)
m_matrix = np.dot(m_matrix, self.translation_matrix)
self.projection_matrix = m_matrix
# print('update_projection_matrix : self.projection_matrix=%s' % str(self.projection_matrix))
def update_model_matrix(self):
m_matrix = np.dot(self.m2w_matrix, self.rotation_matrix)
self.model_matrix = m_matrix
def get_projections(self, points, save=False):
m_matrix = np.dot(self.model_matrix, self.projection_matrix)
# print('get_projections : self.model_matrix = %s' % str(self.model_matrix))
# print('get_projections : self.projection_matrix = %s' % str(self.projection_matrix))
# print('get_projections : m_matrix = %s' % str(m_matrix))
p = points[:, :4]
v = np.dot(p, m_matrix)
v = v / v[:, -1, None]
# add the other columns
v = np.c_[v, points[:, 4:]]
# and sort by Z
# v = v[v[:,2].argsort()[::-1]]
if save:
self.projections = v
return v
def filter_projections(self, projections, w, h):
try:
# filtering
margin = self.__outer_margin
projections = projections[projections[:, 0] >= -1 * margin, :]
projections = projections[projections[:, 0] <= w + margin, :]
projections = projections[projections[:, 1] >= -1 * margin, :]
projections = projections[projections[:, 1] <= h + margin, :]
return projections
except:
pass
def render_background(self, ctx):
surface = self.background_sprite
ctx.set_source_surface(surface, 0, 0)
ctx.paint()
def render_scalebar(self, ctx):
x, y, w, h = ctx.clip_extents() # @UnusedVariable
scalebar_bb_width = 200
scalebar_bb_height = 20
ctx.set_source_rgba(0., 0., 0., 0.7)
ctx.rectangle(x + w - scalebar_bb_width - 6, h - scalebar_bb_height - 6, scalebar_bb_width, scalebar_bb_height)
ctx.fill()
ctx.set_source_rgb(1, 1, 1)
ctx.rectangle(x + w - scalebar_bb_width, h - scalebar_bb_height, 100, scalebar_bb_height - 12)
ctx.fill()
ctx.move_to(x + w - scalebar_bb_width / 2 + 6, h - 9)
ctx.set_source_rgb(1, 1, 1)
ctx.set_font_size(16)
ctx.show_text("%.2f \xc5" % (100. / self.scale))
def render_axes(self, ctx):
_, _, w, h = ctx.clip_extents() # @UnusedVariable
m_matrix = np.dot(self.rotation_matrix, self.v2p_matrix)
d = 20
offset = 12
origin = np.array([0, 0, 0, 1]) # @UnusedVariable
x_axis = np.array([d, 0, 0, 1])
y_axis = np.array([0, d, 0, 1])
z_axis = np.array([0, 0, d, 1])
# translation = np.array([[1, 0, 0, d + offset], # @UnusedVariable
# [0, 1, 0, h - offset],
# [0, 0, 1, 1 ],
# [0, 0, 0, 1 ]])
red = (1, 0, 0)
green = (0, 0.7, 0)
blue = (0, 0, 1)
# draw a white circle
so = np.array([d + offset, h - d - offset, 0])
ctx.move_to(d + offset, h - d - offset)
ctx.arc(d + offset, h - d - offset, d + offset - 2, 0, 2 * np.pi)
#ctx.set_source_rgb(1, 1, 1)
ctx.set_source_rgba(0., 0., 0., 0.7)
ctx.set_line_width(1)
#ctx.stroke_preserve()
ctx.set_source_rgba(0.95, 0.95, 0.95, 1)
ctx.fill()
for axis, color, label in ((x_axis, red, 'X'),
(y_axis, green, 'Y'),
(z_axis, blue, 'Z')):
axis = np.dot(axis, m_matrix)
axis /= axis[-1]
ctx.move_to(*so[:2])
ctx.rel_line_to(*axis[:2])
ctx.set_source_rgb(*color)
ctx.set_line_width(2)
ctx.set_font_size(10)
ctx.show_text(label)
ctx.stroke()
def render_atoms(self, ctx):
try:
atoms = self.atoms
except:
return
# create a points matrix with homogeneous coordinates
# x,y,z,w and the atom number and index
points = atoms.get_positions()
points = np.c_[points, np.ones(len(points))]
points = np.c_[points, atoms.numbers]
points = np.c_[points, np.arange(len(atoms))]
# Get the points array projected to the screen display
projections = self.get_projections(points, save=True)
# print('render_atoms : len(projections) = %d' % len(projections))
# print('render_atoms : projections = %s' % str(projections))
# Reduce the number of atoms to be drawn if outside the viewport
w, h = self.GetClientSize()
# print('render_atoms : w = %f, h = %f' % (w, h))
projections = self.filter_projections(projections, w, h)
# self.projections = projections
if len(projections) == 0:
print('render_atoms : no atom is visible')
return # no atom is visible from the camera
# sort by z
projections = projections[projections[:, 2].argsort()[::-1]]
margin = self.__outer_margin
xmin, ymin = np.min(projections[:, :2], axis=0)
xmax, ymax = np.max(projections[:, :2], axis=0)
sw = xmax - xmin + 2 * margin
sh = ymax - ymin + 2 * margin
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(sw), int(sh))
surface_ctx = cairo.Context(surface)
# set the local references
set_source_surface = surface_ctx.set_source_surface
paint = surface_ctx.paint
selection = self.selection
atom_numbers = self.atom_numbers
atom_surfaces = self.atom_surfaces
self.im_ox = int(xmin - margin)
self.im_oy = int(ymin - margin)
surface_ctx.translate(-self.im_ox, -self.im_oy)
# surface_ctx.set_source_rgb(1,1,0)
# surface_ctx.set_line_width(2.)
# surface_ctx.rectangle(self.im_ox, self.im_oy, int(sw), int(sh))
# surface_ctx.stroke()
for i, p in enumerate(projections): # @UnusedVariable
x, y, z, w, n, index = p # @UnusedVariable
# load the surface
if index in selection:
if index == self.atoms.info['absorber']:
sprite = self.absorber_surface[1]
else:
sprite = atom_surfaces[1, np.where(atom_numbers == n)[0][0]]
else:
if index == self.atoms.info['absorber']:
sprite = self.absorber_surface[0]
else:
sprite = atom_surfaces[0, np.where(atom_numbers == n)[0][0]]
sx = x - sprite.get_width() / 2.
sy = y - sprite.get_height() / 2.
set_source_surface(sprite, int(sx), int(sy))
paint()
if False: # pylint: disable=using-constant-test
ctx.set_source_rgb(0., 0., 0.)
r = sprite.get_width() / 2
ctx.arc(x, y, r, 0, 2 * np.pi)
ctx.stroke_preserve()
ctx.set_source_rgb(0.8, 0.8, 0.8)
ctx.fill()
ctx.move_to(x, y)
ctx.set_source_rgb(0, 0, 0)
ctx.set_font_size(14)
ctx.show_text("%d" % index)
# save the rendering
self.atoms_sprite = surface
ctx.set_source_surface(surface, self.im_ox, self.im_oy)
ctx.paint()
def render_selection_box(self, ctx):
r, g, b = self.colors['selection_box']
ctx.set_source_surface(self.screenshot, 0, 0)
ctx.paint()
ctx.set_source_rgba(r, g, b, 0.3)
ctx.rectangle(*self.selection_box)
ctx.fill_preserve()
ctx.set_source_rgb(r, g, b)
ctx.stroke()
def render_boundingbox(self, ctx):
# print('render_boundingbox : start')
try:
atoms = self.atoms
except:
return
# create a points matrix with homogeneous coordinates
# x,y,z,w for atoms extrema
points = atoms.get_positions()
margin = self.__outer_margin / float(self.scale)
xmin, ymin, zmin = np.min(points, axis=0) - margin
xmax, ymax, zmax = np.max(points, axis=0) + margin
points = np.array([
[xmax, ymax, zmax, 1],
[xmax, ymin, zmax, 1],
[xmin, ymax, zmax, 1],
[xmin, ymin, zmax, 1],
[xmax, ymax, zmin, 1],
[xmax, ymin, zmin, 1],
[xmin, ymax, zmin, 1],
[xmin, ymin, zmin, 1]])
# Get the points array projected to the screen display
projections = self.get_projections(points)
x0, y0 = (np.min(projections[:, :2], axis=0)).astype(int)
x1, y1 = (np.max(projections[:, :2], axis=0)).astype(int)
# Declare the 6 faces with their vertex index
# the order of the numbers define if the normal plane points outward or not
faces = np.array([
[6, 7, 5, 4],
[2, 3, 7, 6],
[3, 1, 5, 7],
[2, 6, 4, 0],
[0, 4, 5, 1],
[2, 0, 1, 3]])
# kind of backface culling
ind = []
for i, f in enumerate(faces):
# Get 2 vectors of the plane
v1 = projections[f[1], :3] - projections[f[0], :3]
v2 = projections[f[3], :3] - projections[f[0], :3]
# cross multiply them to get the normal
n = np.cross(v2, v1)
# If the normal z coordinate is <0, the plane is not visible, so, draw it
# first, otherwise draw it last
if n[-1] > 0:
ind.append(i)
else:
ind.insert(0, i)
# faces are now re-ordered
faces = faces[ind]
# plane transparency and color
color_plane = self.colors['boulding_box_fill']
color_line = self.colors['boulding_box_line']
ctx.save()
#ctx.set_source_rgb(1, 0, 0)
#ctx.rectangle(x0, y0, x1 - x0, y1 - y0)
#ctx.stroke()
ctx.set_fill_rule(cairo.FILL_RULE_EVEN_ODD)
ctx.set_line_join(cairo.LINE_JOIN_ROUND)
ctx.set_line_width(1.)
ctx.set_dash([8., 8.])
for i, p in enumerate(faces):
# remove dash for front faces
if i > 2:
ctx.set_dash([])
if i == 3 and not(self.mode & self.MODE_ROTATION): # pylint: disable=superfluous-parens
try:
f = self.scale / self.scale0
# sprite_w = self.atoms_sprite.get_width()
# sprite_h = self.atoms_sprite.get_height()
# m = self.__outer_margin
# sw = float(sprite_w) / (x1 - x0)
# sh = float(sprite_h) / (y1 - y0)
ctx.save()
# ctx.translate((x0 + x1 - sprite_w)/2. ,
# (y0 + y1 - sprite_h)/2.)
ctx.set_source_surface(self.atoms_sprite, self.im_ox, self.im_oy)
ctx.paint()
ctx.restore()
except:
pass
ctx.set_source_rgba(*color_plane)
# get the projected points
p0, p1, p2, p3 = projections[p, :2]
# move to the first
ctx.move_to(*p0)
# line to the others
list(map(lambda _: ctx.line_to(*_), (p1, p2, p3)))
# close the polygon
ctx.close_path()
# fill it and stroke the path
ctx.fill_preserve()
ctx.set_source_rgba(*color_line)
ctx.stroke()
ctx.restore()
def update_camera(self, rescale=False, center=False):
# update the scale
w, h = self.GetClientSize()
l = min(w, h)
# print('set_atoms : w=%d h = %d l = %f' % (w, h, l))
self.scale0 = int(.5 * l / self.atoms_largest_dimension) + 1
# move the model to the center of mass
t_matrix = np.eye(4)
t_matrix[-1, :-1] = -1 * self.atoms_center_of_mass
self.m2w_matrix = t_matrix # self.m2w_matrix.dot(t_matrix)
self.update_model_matrix()
if rescale:
assert self.scale0 > 0.0
self.scale = self.scale0
#print "scale = ", self.scale, "scale0 = ", self.scale0
#self.scale_atoms(1.)
if center:
self.translate_atoms(w / 2, h / 2)
def update_drawing(self, light=None):
# print('update_drawing : light=%s' % str(light))
try:
ctx = cairo.Context(self.back_buffer)
except:
#self.scale = self.scale0 = 10
return
light_mode = self.light_mode if light is None else light
if self.mode & self.MODE_SELECTION:
self.render_selection_box(ctx)
else:
self.render_background(ctx)
if self.atoms:
if light_mode:
self.render_boundingbox(ctx)
else:
self.render_atoms(ctx)
self.render_scalebar(ctx)
self.render_axes(ctx)
self.Refresh(eraseBackground=False)
def swap_buffers(self):
if self.back_buffer:
back_buffer = self.back_buffer
# w = back_buffer.get_width()
# h = back_buffer.get_height()
bitmap = wx.lib.wxcairo.BitmapFromImageSurface(back_buffer)
dc = wx.PaintDC(self)
dc.DrawBitmap(bitmap, 0, 0)

View File

@ -0,0 +1,286 @@
import wx
from wx import glcanvas
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
import sys
from ase import Atom, Atoms
from ase.data import covalent_radii
from ase.data.colors import jmol_colors
from ase.build import molecule
import numpy as np
class GLPanel(wx.Panel):
def __init__(self, *args, **kwargs):
#style = wx.DEFAULT_FRAME_STYLE | wx.NO_FULL_REPAINT_ON_RESIZE
wx.Panel.__init__(self, *args, **kwargs)
self.GLinitialized = False
attribList = (glcanvas.WX_GL_RGBA, # RGBA
glcanvas.WX_GL_DOUBLEBUFFER, # Double Buffered
glcanvas.WX_GL_DEPTH_SIZE, 24) # 24 bit
# Create the canvas
self.canvas = glcanvas.GLCanvas(self, attribList=attribList)
self.glcontext = glcanvas.GLContext(self.canvas)
# Set the event handlers.
self.canvas.Bind(wx.EVT_SIZE, self.onSizeEvent)
self.canvas.Bind(wx.EVT_PAINT, self.onPaintEvent)
szr = wx.BoxSizer(wx.HORIZONTAL)
szr.Add(self.canvas, 1, wx.EXPAND|wx.ALL, 0)
self.SetSizer(szr)
self.Bind(wx.EVT_MOUSEWHEEL, self.onMouseWheelEvent)
self.canvas.Bind(wx.EVT_MOTION, self.onMotionEvent)
self.canvas.Bind(wx.EVT_LEFT_DOWN, self.onLeftDownEvent)
#self.Bind(wx.EVT_LEFT_UP, self.__evt_left_up_cb)
#self.Bind(wx.EVT_RIGHT_UP, self.__evt_right_up_cb)
self.canvas.Bind(wx.EVT_RIGHT_DOWN, self.onRightDownEvent)
self.atoms = None
self.scale = 1.
self.theta = self.phi = 0.
self.tx = self.ty = 0
self.tx0 = self.ty0 = 0.
self.znear = 1.
self.zfar = -1.
self.coords = [0., 0., 0.]
self.translation = [0., 0., 0]
def set_atoms(self, atoms):
self.atoms = atoms
self.com = atoms.get_center_of_mass()
self.sorted_indices = np.argsort(atoms.numbers)
self.theta = 45
self.phi = -45
def onMouseWheelEvent(self, event):
rotation = event.GetWheelRotation()
if rotation > 0:
self.scale *= 1.1
else:
self.scale /= 1.1
#self._gl_scale()
print self.scale
w, h = self.canvas.GetClientSize()
self._gl_init_view(w, h)
self._gl_draw()
def onRightDownEvent(self, event):
self.mx0, self.my0 = event.GetPosition()
def onLeftDownEvent(self, event):
mvmatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
projmatrix = glGetDoublev(GL_PROJECTION_MATRIX)
viewport = glGetIntegerv(GL_VIEWPORT)
winx, winy = event.GetPosition()
winy = viewport[3] - winy
winz = glReadPixels(winx, winy, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
coords = gluUnProject(winx, winy, winz, mvmatrix, projmatrix, viewport)
self.coords0 = coords
print "coords0 = ", coords
def onMotionEvent(self, event):
w, h = self.canvas.GetClientSize()
if event.RightIsDown():
mx, my = event.GetPosition()
dy = my - self.my0
dx = mx - self.mx0
self.my0 = my
self.mx0 = mx
self.theta += dx
self.phi += dy
elif event.LeftIsDown():
mvmatrix = glGetDoublev(GL_MODELVIEW_MATRIX)
projmatrix = glGetDoublev(GL_PROJECTION_MATRIX)
viewport = glGetIntegerv(GL_VIEWPORT)
# get x0, y0 and z0
#x0, y0, z0 = gluUnProject(self.tx0, self.ty0, self.tz0, mvmatrix, projmatrix, viewport)
winx, winy = event.GetPosition()
winy = viewport[3] - winy
winz = glReadPixels(winx, winy, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)[0][0]
x, y, z = gluUnProject(winx, winy, winz, mvmatrix, projmatrix, viewport)
#translation = [x - x0, y - y0, 0]
translation = [x - self.coords0[0], y - self.coords0[1], 0]
#print x, y, z#translation
if winx < w and winx > 0 and winy < h and winy > 0:
print 'here'
glTranslatef(*translation)
#self.translation = translation
print translation
self._gl_draw()
def onSizeEvent(self, event):
"""Process the resize event."""
width, height = self.canvas.GetClientSize()
self._gl_resize(width, height)
self.canvas.Refresh(False)
event.Skip()
def onPaintEvent(self, event):
"""Process the drawing event."""
if not self.GLinitialized:
w, h = self.canvas.GetClientSize()
self._gl_init(w, h)
self._gl_draw()
event.Skip()
# GLFrame OpenGL Event Handlers
def _gl_init(self, width, height):
"""Initialize OpenGL for use in the window."""
self.canvas.SetCurrent(self.glcontext)
self.quadric = gluNewQuadric()
gluQuadricNormals(self.quadric, GLU_SMOOTH)
glClearColor(1., 1., 1., 1.)
glClearDepth(1.)
glDepthFunc(GL_LESS)
glEnable(GL_DEPTH_TEST)
glEnable(GL_MULTISAMPLE)
glShadeModel(GL_SMOOTH)
#glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
#glLightfv(GL_LIGHT0, GL_AMBIENT, (0.5, 0.5, 0.5, 1.0))
a = 0.4
d = 0.4
s = 1.
glLightfv(GL_LIGHT0, GL_AMBIENT, (a, a, a, 1.0))
glLightfv(GL_LIGHT0, GL_DIFFUSE, (d, d, d, 1.0))
glLightfv(GL_LIGHT0, GL_SPECULAR, (s, s, s, 1.0))
glLightfv(GL_LIGHT0, GL_POSITION, (0.0, 100.0, 10., 1.0))
glEnable(GL_LIGHT0)
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
#glLightfv(GL_LIGHT1, GL_AMBIENT, (0.3, 0.3, 0.3, 1.0))
#glLightfv(GL_LIGHT1, GL_DIFFUSE, (.2, .2, .2, 1.0))
#glLightfv(GL_LIGHT1, GL_POSITION, (0.0, -10.0, 0., 1.0))
#glEnable(GL_LIGHT1)
glEnable(GL_LIGHTING)
glEnable(GL_COLOR_MATERIAL)
self._gl_init_view(width, height)
self._gl_resize(width, height)
self.GLinitialized = True
def _gl_init_view(self, width, height):
glViewport(0, 0, width, height)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(45., float(width) / float(height), self.znear, self.zfar)
# find the largest cluster dimension
max_d = np.max(np.linalg.norm(self.atoms.get_positions(), axis=1))
# set the scale accordingly
gluLookAt(0, 0, 5*max_d*self.scale, 0, 0, 0, 0, 1, 0)
#gluLookAt(0, 0, self.scale, 0, 0, 0, 0, 1, 0)
glMatrixMode(GL_MODELVIEW)
def _gl_resize(self, width, height):
"""Reshape the OpenGL viewport based on the dimensions of the window."""
if height == 0:
height = 1
self._gl_init_view(width, height)
def _gl_draw(self, *args, **kwargs):
"Draw the window."
#glClear(GL_COLOR_BUFFER_BIT)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
# Drawing an example triangle in the middle of the screen
#glBegin(GL_TRIANGLES)
#glColor(0, 0, 0)
#color = [1.0, 0.0, 0.0, 1.0]
#glMaterialfv(GL_FRONT, GL_DIFFUSE, color)
#glVertex3f(-.25, -.25, 0.)
#glVertex3f(.25, -.25, 0.)
#glVertex3f(0., .25, 0.)
##glVertex(-.25, -.25)
##glVertex(.25, -.25)
##glVertex(0., .25)
#glEnd()
def draw_circle(radius, n):
theta = 2*np.pi/float(n)
c = np.cos(theta)
s = np.sin(theta)
x = radius
y = 0
glBegin(GL_LINE_LOOP)
for i in range(n):
glVertex2f(x , y )
t = x
x = c * x - s * y
y = s * t + c * y
glEnd()
def render_atom(atom):
glPushMatrix()
glRotate(self.theta, 0, 1, 0)
glRotate(self.phi, 1, 0, 0)
glTranslatef(-self.com[0], -self.com[1], -self.com[2])
glTranslatef(*atom.position)
#r, g, b = jmol_colors[atom.number]
radius = covalent_radii[atom.number]
#color = [r, g, b, 1.0]
#glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, (.1, .1, .1, 1))
#glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, (0, 0, 0, 1))
#glColor3f(r, g, b)
resolution = 16
gluSphere(self.quadric, radius, resolution, resolution)
#draw_circle(radius, 32)
glPopMatrix()
def set_material(number):
r, g, b = jmol_colors[number]
color = [r, g, b, 1.0]
s = 0.7
glMaterialfv(GL_FRONT, GL_SPECULAR, (s, s, s, 1))
glMaterialfv(GL_FRONT, GL_EMISSION, (0., 0., 0., 1))
glMateriali(GL_FRONT, GL_SHININESS, 30)
glColor4f(r, g, b, 0.5)
last_number = -1
#glPushMatrix()
#glLoadIdentity()
#glTranslatef(*self.translation)
#glRotate(self.theta, 0, 1, 0)
#glRotate(self.phi, 1, 0, 0)
#glTranslatef(-self.com[0], -self.com[1], -self.com[2])
#glPopMatrix()
for i in self.sorted_indices:
atom = self.atoms[i]
number = atom.number
if number != last_number:
set_material(number)
render_atom(atom)
self.canvas.SwapBuffers()
def _gl_scale(self):
scale = self.scale
glScale(scale, scale, scale)
self._gl_draw()

View File

@ -0,0 +1,30 @@
# coding: utf-8
import wx
from GLPanel import GLPanel
from msspec.utils import hemispherical_cluster
from ase.build import bulk
class GLFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.pnl = GLPanel(self)
def set_atoms(self, atoms):
self.pnl.set_atoms(atoms)
if __name__ == "__main__":
lattice = bulk('MgO', crystalstructure='rocksalt', a=4.21, cubic=True)
cluster = hemispherical_cluster(lattice, 0, 0, diameter=20.)
app = wx.App(False)
frame = GLFrame(None, -1, 'GL Window', size=(640, 480))
frame.set_atoms(cluster)
frame.Show()
app.MainLoop()
app.Destroy()

View File

@ -0,0 +1,38 @@
# -*- encoding: utf-8 -*-
# vim: set fdm=indent ts=2 sw=2 sts=2 et tw=80 cc=+1 mouse=a nu : #
# import sys
# sys.path.append('/home/sylvain/dev/msspec/trunk/src/python/MsSpecGui/')
import wx
from clusterviewer import ClusterViewer
from ase.lattice import bulk
class MyFrame(wx.Frame):
def __init__(self, **kwargs):
p = {}
p.setdefault('parent', None)
p.setdefault('title', 'Test Frame')
p.setdefault('size', (640, 480))
p.update(kwargs)
wx.Frame.__init__(self, **p)
self.Window = ClusterViewer(self)
self.Show()
# display an MgO cell
MgO = bulk('MgO', crystalstructure='rocksalt', orthorhombic=True, a=4.21)
MgO = MgO.repeat((20, 20, 30))
app = wx.App(False)
frame = MyFrame(title='MsSpec Viewer')
frame.Window.light_mode_threshold = 2
frame.Window.set_atoms(MgO, rescale=True)
frame.Show()
app.MainLoop()
app.Destroy()

View File

@ -0,0 +1,88 @@
'''
Created on Jan 4, 2016
@author: graffy
'''
import wx
import msspecgui.msspec.workspace
class FileMenu(wx.Menu):
def __init__(self, main_frame):
wx.Menu.__init__(self)
self._main_frame = main_frame
# wx.ID_ABOUT and wx.ID_EXIT are standard IDs provided by wxWidgets.
self._new_workspace_menu_item = self.Append(wx.ID_ANY, "N&ew workspace", " create a new workspace")
self._open_workspace_menu_item = self.Append(wx.ID_ANY, "O&pen workspace", " load an existing workspace")
self.AppendSeparator()
self._save_workspace_menu_item = self.Append(wx.ID_ANY, "S&ave workspace", " save the current workspace")
self._close_workspace_menu_item = self.Append(wx.ID_ANY, "C&lose workspace", " close the current workspace")
self.AppendSeparator()
self._about_menu_item = self.Append(wx.ID_ABOUT, "&About", " Information about this program")
self.AppendSeparator()
self._exit_menu_item = self.Append(wx.ID_EXIT, "E&xit", " Terminate the program")
self._main_frame.Bind(wx.EVT_MENU, self.on_about, self._about_menu_item)
self._main_frame.Bind(wx.EVT_MENU, self.on_exit, self._exit_menu_item)
self._main_frame.Bind(wx.EVT_MENU, self.on_new_workspace, self._new_workspace_menu_item)
self._main_frame.Bind(wx.EVT_MENU, self.on_open_workspace, self._open_workspace_menu_item)
self._main_frame.Bind(wx.EVT_MENU, self.on_save_workspace, self._save_workspace_menu_item)
self._main_frame.Bind(wx.EVT_MENU, self.on_close_workspace, self._close_workspace_menu_item)
self.update()
def update(self):
self._close_workspace_menu_item.Enable(self._main_frame.m_workspace is not None)
self._save_workspace_menu_item.Enable(self._main_frame.m_workspace is not None)
def on_new_workspace(self, event):
if self.on_close_workspace(event=None):
default_path = ''
dlg = wx.DirDialog(self._main_frame, "Choose location to store this new workspace", default_path)
if dlg.ShowModal() == wx.ID_OK:
workspace_path = dlg.GetPath()
self._main_frame.set_workspace(msspecgui.msspec.workspace.Workspace(workspace_path, is_new_workspace=True))
# f = open(, 'r')
# self.control.SetValue(f.read())
# f.close()
dlg.Destroy()
self.update()
def on_open_workspace(self, event):
if self.on_close_workspace(event=None):
default_path = ''
dlg = wx.DirDialog(self._main_frame, "Choose the workspace location", default_path)
if dlg.ShowModal() == wx.ID_OK:
workspace_path = dlg.GetPath()
workspace = msspecgui.msspec.workspace.Workspace(workspace_path, is_new_workspace=False)
self._main_frame.set_workspace(workspace)
# f = open(, 'r')
# self.control.SetValue(f.read())
# f.close()
dlg.Destroy()
self.update()
def on_save_workspace(self, event):
print('FileMenu.on_save_workspace')
self._main_frame.save_workspace()
self.update()
def on_close_workspace(self, event):
self._main_frame.close_workspace()
self.update()
return True
def on_about(self, event):
# A message dialog box with an OK button. wx.OK is a standard ID in wxWidgets.
dlg = wx.MessageDialog(self._main_frame, "A multiple-scattering package for spectroscopies using electrons to probe materials", "MsSpec 1.1", wx.OK)
dlg.ShowModal() # Show it
dlg.Destroy() # finally destroy it when finished.
def on_exit(self, event):
self.on_close_workspace(event=event)
self._main_frame.Close(True) # Close the frame.

View File

@ -0,0 +1,70 @@
'''
Created on Dec 23, 2015
@author: graffy
how to install ase on osx macports
[OK]graffy@pr079234:~/ownCloud/ipr/msspec/toto[12:50:42]>export ASE_TAGS=https://svn.fysik.dtu.dk/projects/ase/tags/
[OK]graffy@pr079234:~/ownCloud/ipr/msspec/toto[12:50:57]>svn co -r 4567 $ASE_TAGS/3.9.1 ase-3.9.1
[OK]graffy@pr079234:~[12:52:41]>cd ownCloud/ipr/msspec/toto/ase-3.9.1
sudo /opt/local/bin/python setup.py install
'''
import sys
# import re
# sudo port install py27-wxpython-3.0
# import os
sys.path.append('../../')
import wx # @IgnorePep8
import ase # @IgnorePep8
# import msspec
import msspecgui.msspec.gui.mainwindow as mainwindow # @IgnorePep8
# import cairosvg
def view_atoms(atoms):
'''Displays the atoms.
This is the equivalent of ase.visualize.view(molecule), but this function doesn't make use of the external python program ase-3.9.1/tools/ase-gui. It was built by looking at what ase.visualize.view does.
'''
import ase.gui.gui # IGNORE:redefined-outer-name
# import ase.gui.ag
import ase.io
# import ase.gui.gtkexcepthook
# from ase.visualize import view
# from ase.io import write
# from ase.gui.ag import main
# http://wiki.wxpython.org/Getting%20Started
import ase.gui.images
file_format = 'traj'
file_path = '/tmp/toto.%s' % file_format
# ase.io.write(file_path, molecule, file_format)
ase.io.write(file_path, images=atoms, format=file_format)
images = ase.gui.images.Images()
images.read([file_path], ase.io.string2index(':'))
gui = ase.gui.gui.GUI(images, '', 1, False)
gui.run(None)
def test_ase():
d = 1.10
molecule = ase.Atoms('2N', positions=[(0., 0., 0.), (0., 0., d)])
view_atoms(molecule)
def main():
print('hello, world')
app = wx.App(False) # Create a new app, don't redirect stdout/stderr to a window.
app.SetAppName('MsSpec')
# app.SetAppDisplayName('MsSpec')
frame = mainwindow.MainWindow(None, 'MsSpec') # A Frame is a top-level window. @UnusedVariable
# test_ase()
app.MainLoop()
if __name__ == '__main__':
main()

View File

@ -0,0 +1,83 @@
'''
Created on Jan 4, 2016
@author: graffy
'''
import wx
from .filemenu import FileMenu
# import clustermenu
from msspecgui.msspec.gui.viewmanager import ViewFactory
from msspecgui.msspec.gui.viewmanager import ViewManager
from msspecgui.msspec.gui.clustereditor import ClusterEditor
from msspecgui.msspec.gui.clusterview import ClusterView
class MainWindow(wx.Frame):
def __init__(self, parent, title):
wx.Frame.__init__(self, parent, title=title, size=(300, 300))
# self.c ontrol = wx.TextCtrl(self, style=wx.TE_MULTILINE)
self.CreateStatusBar() # A Statusbar in the bottom of the window
self.view_manager = None
self.set_workspace(None)
self._file_menu = FileMenu(self)
# self.m_clusterMenu = clustermenu.ClusterMenu(self)
# Creating the menubar.
menu_bar = wx.MenuBar()
menu_bar.Append(self._file_menu, "&File") # Adding the "fileMenu" to the MenuBar
# menu_bar.Append(self.m_clusterMenu,"&Cluster") # Adding the "fileMenu" to the MenuBar
self.SetMenuBar(menu_bar) # Adding the MenuBar to the Frame content.
self.Bind(wx.EVT_CLOSE, self.on_close)
self.Show(True) # Show the frame.
def set_workspace(self, workspace):
"""
:type workspace: Workspace
"""
self.m_workspace = workspace
window_title = 'MsSpecGui'
if workspace is not None:
window_title += ' - ' + workspace.workspace_path
self.SetTitle(window_title)
if workspace is None:
if self.view_manager is not None:
self.view_manager.Destroy()
self.view_manager = None
else:
view_factory = ViewFactory()
view_factory.register_view_creator(ClusterEditor.Creator(self.get_workspace()))
view_factory.register_view_creator(ClusterView.Creator(self.get_workspace().get_cluster_flow()))
self.view_manager = ViewManager(self, view_factory)
self.view_manager.views[0].set_view_type(ClusterEditor.Creator.VIEW_TYPE_NAME)
view2 = self.view_manager.add_view()
view2.set_view_type(ClusterView.Creator.VIEW_TYPE_NAME)
self.Hide() # without this call, I couldn't make the workspace window updated (at least on osx); as a result, I had to drag the corner of the window to force a refresh
self.Show()
def get_workspace(self):
return self.m_workspace
def close_workspace(self, ask_confirmation=True):
if self.m_workspace is not None:
dlg = wx.MessageDialog(self, message="Do you want to save your workspace before closing it ?",
caption="Save current workspace ?",
style=wx.YES_NO | wx.CANCEL | wx.YES_DEFAULT | wx.ICON_QUESTION)
clicked_button_id = dlg.ShowModal() # Show it
dlg.Destroy() # finally destroy it when finished.
if clicked_button_id == wx.ID_YES:
self.save_workspace()
elif clicked_button_id == wx.ID_CANCEL:
return False
self.set_workspace(None)
def save_workspace(self):
self.m_workspace.save()
def on_close(self, event):
print("MainWindow.on_close")
self.close_workspace(ask_confirmation=True)
event.Skip() # propagate the event so that the dialog closes

View File

@ -0,0 +1,51 @@
import wx
import wx.lib.scrolledpanel as scrolled
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial", size=(200,500))
# Add a panel so it looks the correct on all platforms
self.panel = wx.Panel(self, wx.ID_ANY)
# --------------------
# Scrolled panel stuff
self.scrolled_panel = scrolled.ScrolledPanel(self.panel, -1,
style = wx.TAB_TRAVERSAL|wx.SUNKEN_BORDER, name="panel1")
self.scrolled_panel.SetAutoLayout(1)
self.scrolled_panel.SetupScrolling()
words = "A Quick Brown Insane Fox Jumped Over the Fence and Ziplined to Cover".split()
self.spSizer = wx.BoxSizer(wx.VERTICAL)
for word in words:
text = wx.TextCtrl(self.scrolled_panel, value=word)
self.spSizer.Add(text)
self.scrolled_panel.SetSizer(self.spSizer)
# --------------------
btn = wx.Button(self.panel, label="Add Widget")
btn.Bind(wx.EVT_BUTTON, self.onAdd)
panelSizer = wx.BoxSizer(wx.VERTICAL)
panelSizer.AddSpacer(50)
panelSizer.Add(self.scrolled_panel, 1, wx.EXPAND)
panelSizer.Add(btn)
self.panel.SetSizer(panelSizer)
#----------------------------------------------------------------------
def onAdd(self, event):
""""""
print "in onAdd"
new_text = wx.TextCtrl(self.scrolled_panel, value="New Text")
self.spSizer.Add(new_text)
self.scrolled_panel.Layout()
self.scrolled_panel.SetupScrolling()
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm().Show()
app.MainLoop()

View File

@ -0,0 +1,103 @@
import wx
class ScrolledImageComponent(wx.ScrolledWindow):
def __init__(self, parent, id, image_path):
wx.ScrolledWindow.__init__(self, parent, id)
image = wx.Image(image_path)
if not image.IsOk():
wx.MessageBox("there was an error loading the image")
return
self.w = image.GetWidth()
self.h = image.GetHeight()
# init scrolled area size, scrolling speed, etc. */
self.SetScrollbars(1,1, self.w, self.h, 0, 0)
self.bitmap = wx.Bitmap(image_path)
def OnDraw(self, dc):
"""
render the image - in a real app, if your scrolled area
is somewhat big, you will want to draw only visible parts,
not everything like below
"""
dc.DrawBitmap(self.bitmap, 0, 0, False)
#// also check wxScrolledWindow::PrepareDC
# Run the program
if __name__ == "__main__":
app = wx.App(False)
wx.InitAllImageHandlers();
frame = wx.Frame(None)
sizer = wx.BoxSizer(wx.HORIZONTAL)
my_image = ScrolledImageComponent(frame, wx.ID_ANY, "/Users/graffy/data/Perso/mms_img1041085029.jpg")
sizer.Add(my_image, 1, wx.ALL | wx.EXPAND, 120)
frame.SetSizer(sizer);
frame.Show()
app.MainLoop()
"""
class ScrolledImageComponent : public wxScrolledWindow
{
wxBitmap* bitmap;
int w,h;
public:
ScrolledImageComponent(wxWindow* parent, wxWindowID id, wxString image_path) : wxScrolledWindow(parent, id)
{
wxImage image(image_path);
if(!image.IsOk())
{
wxMessageBox(wxT("there was an error loading the image"));
return;
}
w = image.GetWidth();
h = image.GetHeight();
/* init scrolled area size, scrolling speed, etc. */
SetScrollbars(1,1, w, h, 0, 0);
bitmap = new wxBitmap( image );
}
~ScrolledImageComponent()
{
delete bitmap;
}
void OnDraw(wxDC& dc)
{
/* render the image - in a real app, if your scrolled area
is somewhat big, you will want to draw only visible parts,
not everything like below */
dc.DrawBitmap(*bitmap, 0, 0, false);
// also check wxScrolledWindow::PrepareDC
}
};
class MyApp: public wxApp
{
wxFrame *frame;
public:
bool OnInit()
{
wxInitAllImageHandlers();
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
frame = new wxFrame((wxFrame *)NULL, -1, wxT("Scrolling an Image"), wxPoint(50,50), wxSize(650,650));
ScrolledImageComponent* my_image = new ScrolledImageComponent(frame, wxID_ANY, wxT("my_image.jpg") );
sizer->Add(my_image, 1, wxALL | wxEXPAND, 120);
frame->SetSizer(sizer);
frame->Show();
return true;
}
};
"""

View File

@ -0,0 +1,246 @@
from __future__ import print_function
import abc
import functools
import wx.lib.agw.aui as aui
import wx
class IViewCreator(object):
@abc.abstractproperty
def view_type_name(self):
"""
:return str:
"""
pass
@abc.abstractmethod
def create_view(self, parent):
"""
:param wx.Window parent: the wx.Window that owns the view
:return wx.Panel:
"""
pass
class ViewFactory(object):
def __init__(self):
self._view_creators = {} #: :type self._view_creators: dict(str, IViewCreator)
def register_view_creator(self, view_creator):
"""
:param IViewCreator view_creator:
"""
self._view_creators[view_creator.view_type_name] = view_creator
def get_view_type_names(self):
return list(self._view_creators.iterkeys())
def create_view(self, view_type_name, container_window):
"""
:param str view_type_name: the type of view to create
:param wx.Window container_window: the parent wx window
"""
return self._view_creators[view_type_name].create_view(container_window)
class View(wx.Panel):
"""
panel who will be used to create the interface
"""
def __init__(self, view_manager, view_id):
"""
:param ViewManager view_manager: the manager that manages this view
:param int id: a number uniquely identifying this view in its view manager
"""
wx.Panel.__init__(self, view_manager, -1)
self.view_manager = view_manager
self.id = view_id
vbox = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(vbox)
self.panel = None
@property
def view_factory(self):
return self.view_manager.view_factory
def set_view_type(self, view_type):
"""
:param str view_type:
"""
self.on_view_set(None, view_type)
def on_view_set(self, evt, view_type_name):
"""
called when the menu item that assigns a view type to this view is clicked by the user
:param str view_type_name: the type of view that the user just chose for this view
"""
pane = self.view_manager.aui_manager.GetPaneByWidget(self)
index = self.view_manager.get_free_pane_index(view_type_name)
pane_name = view_type_name + ("" if index == 0 else " " + str(index))
pane.caption = pane_name
if self.panel is not None:
self.panel.Destroy()
self.panel = None
self.panel = self.view_factory.create_view(view_type_name, self)
self.GetSizer().Add(self.panel, proportion=1, flag=wx.EXPAND) # the new panel is the only item in the vbox sizer that is allowed to change its size, so any value other than 0 will do
# self.Update()
# self.Refresh()
# self.view_manager.Update()
# self.view_manager.Refresh()
self.view_manager.aui_manager.Update()
self.view_manager.update_view_menu()
# print self.selected_crystal_str
class ViewManager(wx.Panel):
"""a user interface that contains views
"""
def __init__(self, parent, view_factory):
"""
:param ViewFactory view_factory: the factory that is used to create views
"""
super(ViewManager, self).__init__(parent)
self.view_factory = view_factory
self.parent = parent
# manages the panes associated with it for a particular wxFrame
self.aui_manager = aui.AuiManager(self)
self.last_created_view_id = 0
self.views = []
self.view_set_menu = None # :param wxMenu self.view_set_menu: the menu View->Set that allows the user to assign a view type to a view
view = self.add_view(False, False) # @UnusedVariable the 1st view is special : it's not closable nor movable (to avoid holes)
self.aui_manager.Update()
self.update_view_menu()
def pane_exists(self, panes, pane_name_prefix, pane_index):
for pane_info in panes:
if pane_info.caption == "%s %d" % (pane_name_prefix, pane_index):
return True
return False
def get_free_pane_index(self, pane_name_prefix):
"""
finds a non-used integer to uniquely identify a pane that we want to name "<pane_name_prefix> <i>"
:param str pane_name_prefix: the pane name prefix (usually the view type name)
"""
index = 1
while self.pane_exists(self.aui_manager.GetAllPanes(), pane_name_prefix, index):
index += 1
return index
def add_view(self, movable=True, enable_close_button=True):
"""
:param bool movable: if True, the view is movable
:param bool enable_close_button: if True a close button is added to the view
:return View:
"""
self.last_created_view_id += 1
view = View(self, self.last_created_view_id)
index = self.get_free_pane_index("Empty")
caption = 'Empty %d' % (index)
pane_info = aui.AuiPaneInfo().Movable(movable).Floatable(False).CloseButton(enable_close_button).Caption(caption).DestroyOnClose(True)
if view.id % 2 == 1:
pane_info = pane_info.Center()
else:
pane_info = pane_info.Right()
pane_info = pane_info.Layer(1)
self.aui_manager.AddPane(view, pane_info)
self.aui_manager.Bind(aui.EVT_AUI_PANE_CLOSE, self.on_close_pane)
self.aui_manager.OnFloatingPaneResized(view, None)
self.aui_manager.Update()
self.views.append(view)
self.update_view_menu()
return view
def on_close_pane(self, event):
"""
:param wx.Event event:
"""
pane = event.GetPane()
self.views.remove(pane.window)
items = self.view_set_menu.GetMenuItems()
for i in items:
if i.GetText() == pane.caption:
self.view_set_menu.DestroyItem(i)
def update_view_menu(self):
"""
builds and updates the view menu to reflect the views in the view manager
"""
def populate_view_set_submenu(view_set_submenu, views, aui_manager, view_type_names, window_receiving_events):
"""
populate the View/Set menu
:param wxMenu view_set_submenu: the menu View/Set
:param list(View) views: the list of existing views in the view manager
:param wxAuiManager aui_manager: the aui manager that manages the views (which are widgets)
:param list(str) view_type_names: the list of available view types
:param wxWindow window_receiving_events: the window that receives the menu events
"""
for view in views:
view_item = wx.Menu()
view_set_submenu.AppendSubMenu(view_item, aui_manager.GetPaneByWidget(view).caption)
for view_type_name in view_type_names:
menu_item_id = wx.NewId()
view_type_item = wx.MenuItem(view_item, menu_item_id, view_type_name)
view_item.AppendItem(view_type_item)
window_receiving_events.Bind(wx.EVT_MENU, functools.partial(view.on_view_set, view_type_name=view_type_name), view_type_item)
# create the menu bar if it doesn't exist yet
if self.parent.GetMenuBar():
menu_bar = self.parent.GetMenuBar()
else:
menu_bar = wx.MenuBar()
# delete the View menu if it already exists, as we are going to re-create it
i = 0
while i < menu_bar.GetMenuCount():
if menu_bar.GetMenuLabelText(i) == "View":
menu_bar.Remove(i)
i += 1
# create the view menu
menu_view = wx.Menu()
self.view_set_menu = wx.Menu()
# create the menu item that allows the user to add a view to the view manager
add_view_menu_item = wx.MenuItem(menu_view, wx.NewId(), "Add")
menu_view.AppendItem(add_view_menu_item)
# create the sub menu that allows the user to assign a view type to a view
menu_view.AppendSubMenu(self.view_set_menu, "Set")
menu_bar.Append(menu_view, "View")
self.parent.Bind(wx.EVT_MENU, self.add_view, add_view_menu_item)
self.parent.SetMenuBar(menu_bar)
populate_view_set_submenu(self.view_set_menu, self.views, self.aui_manager, self.view_factory.get_view_type_names(), self.parent)
"""
def close_win(self, event):
self.Close()
def maximize(self, event):
self.Maximize(True)
def minimize(self, event):
self.Maximize(False)
"""

View File

@ -0,0 +1,54 @@
'''
Created on Dec 23, 2015
@author: graffy
'''
import msspecgui.msspec.cluster.clusterflow
# import sys
import msspecgui.dataflowxmlserializer
class Workspace(object):
'''
classdocs
'''
SETTINGS_FILE_NAME = 'settings.xml'
def __init__(self, workspace_path, is_new_workspace):
'''Constructor
:param workspace_path: the file path to the workspace directory
:type workspace_path: str
:param is_new_workspace: if True, the workspace is created, otherwise the workspace is loaded from it path
'''
self._workspace_path = workspace_path
if not is_new_workspace:
self.load()
else:
self._cluster_flow = msspecgui.msspec.cluster.clusterflow.ClusterFlow()
debugging = False
if debugging is True:
serializer = msspecgui.dataflowxmlserializer.DataflowSerializer()
serializer.save_dataflow(self._cluster_flow, '%s/%s' % (self._workspace_path, self.SETTINGS_FILE_NAME))
def load(self):
'''Loads the workspace
'''
print('Workspace.load')
serializer = msspecgui.dataflowxmlserializer.DataflowSerializer()
self._cluster_flow = msspecgui.msspec.cluster.clusterflow.ClusterFlow()
serializer.load_dataflow('%s/%s' % (self._workspace_path, self.SETTINGS_FILE_NAME), self._cluster_flow)
def save(self):
serializer = msspecgui.dataflowxmlserializer.DataflowSerializer()
serializer.save_dataflow(self._cluster_flow, '%s/%s' % (self._workspace_path, self.SETTINGS_FILE_NAME))
def get_cluster_flow(self):
return self._cluster_flow
@property
def workspace_path(self):
return self._workspace_path

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
"""seagull module"""
from scenegraph import *

View File

@ -0,0 +1 @@
from render import render_scene

View File

@ -0,0 +1,347 @@
# encoding:utf-8
'''
Created on Feb 19, 2016
@author: graffy
'''
import cairo
import math
import re
from ..scenegraph.element._path import _quadric
def scenegraph_matrix_to_cairo_matrix(sg_matrix):
"""
:type sg_matrix: scenegraph._Transform
"""
return cairo.Matrix(*sg_matrix.abcdef)
# from logging import log
def render_group(group, cairo_context):
""" renders the scenegraph group 'group' in the cairo drawing context 'cairo_context'
:param group: the scenegraph group to render
:type group: scenegraph2d.Group
:param cairo_context: the cairo drawing context
:type cairo_context: cairo.Context
"""
# print('rendering group')
cairo_context.transform(scenegraph_matrix_to_cairo_matrix(group.local_to_parent_matrix()))
for child in group.children:
render_node(child, cairo_context)
def render_rectangle(rect, cairo_context):
""" renders the scenegraph rectangle 'rect' in the cairo drawing context 'cairo_context'
:param rect: the scenegraph rectangle to render
:type rect: scenegraph2d.Rectangle
:param cairo_context: the cairo drawing context
"""
# cairo_context.set_source_rgb(0.5, 0.5, 0.5)
cairo_context.rectangle(rect.x, rect.y, rect.width, rect.height)
cairo_context.fill()
# print('rendering rectangle')
def render_path(path, cairo_context):
""" renders the scenegraph path 'path' in the cairo drawing context 'cairo_context'
:param path: the scenegraph path to render
:type path: scenegraph2d.Path
:param cairo_context: the cairo drawing context
:type cairo_context: cairo.Context
"""
color = path.fill
cairo_context.set_source_rgb(*color.rgb)
path_as_svg_code = path.d
# print('render_path : rendering path %s' % str(path_as_svg_code))
paths = []
path = []
joins = []
du2 = 1.
path_data_iter = iter(path_as_svg_code)
def next_d():
return next(path_data_iter)
def findCircleCenterPassingThroughPoints(p1, p2, r):
"""
the set of points (x,y) that are at a distance r from p1=(x1,y1) satisfy :
(x-x1)^2 + (y-y1)^2 = r^2
the set of points (x,y) that are at a distance r from p2=(x2,y2) satisfy :
(x-x2)^2 + (y-y2)^2 = r^2
So here's another idea for solving the problem. It should lead to the
same answer and the algebra will be less tedious. This method,
however, uses some fancy ideas from "vector theory", which are
probably strange and unfamiliar to you.
Think of the geometry. You know that for any two points there's a
"mirror line" that goes halfway between them. Technically, the line
consists of the locus of all points that are equidistant from your two
circle points; you can think of the line as a mirror where each of
your two points appears as a reflection of the other.
Well, this line will help us a lot in constructing our center, because
we know that the center is on the line AND because we can use
Pythagoras to tell us where on the line the point is. Here's how we
can do all that with algebra.
First, find the distance between points 1 and 2. We'll call that q,
and it's given by sqrt((x2-x1)^2 + (y2-y1)^2).
Second, find the point halfway between your two points. We'll call it
(x3, y3). x3 = (x1+x2)/2 and y3 = (y1+y2)/2.
Now find the direction of the line between point 1 and point 2. That
direction is (x1-x2, y1-y2).
What we really want to know is the direction of the mirror line, which
is perpendicular to the line between point 1 and point 2.
Here's a crucial trick: you can find the direction of a perpendicular
to a line just by swapping x with y and changing the sign of one. In
other words, if the direction of the joining line was (x1-x2, y1-y2)
then the direction of the mirror line is (y1-y2,x2-x1).
It will be helpful to "normalize" our direction which means to make
the length of the line equal to 1. You do this just by dividing both
the (y1-y2) and the (x2-x1) by q.
The two circle centers will both be on the mirror line, and we can use
geometry to find how far they are from the point (x3,y3): First
notice that the distance from point (x3,y3) to either point 1 or point
2 is just half of q. Then the distance to move along the mirror line
is:
sqrt(r^2-(q/2)^2)
Move up the mirror line by adding a multiple of the direction line to
the point (x3,y3) or move down the mirror line by subtracting the same
multiple.
One answer will be:
x = x3 + sqrt(r^2-(q/2)^2)*(y1-y2)/q
y = y3 + sqrt(r^2-(q/2)^2)*(x2-x1)/q
The other will be:
x = x3 - sqrt(r^2-(q/2)^2)*(y1-y2)/q
y = y3 - sqrt(r^2-(q/2)^2)*(x2-x1)/q
"""
#
q = math.sqrt((p2[1] - p1[1]) ** 2 + (p2[0] - p1[0]) ** 2) # @UnusedVariable
# let p3 be the midpoint between p1 and p2
p3 = ((p1[0] + p2[0]) * 0.5, (p1[1] + p2[1]) * 0.5) # @UnusedVariable
assert(False) # todo : finish this implementation
pn = p0 = (0., 0.)
cn = None
for c in path_data_iter:
x0, y0 = p0
xn, yn = pn
if c.islower(): # coordinates are then relative coordinates
def next_p():
dx, dy = next_d()
return (x0 + dx, y0 + dy)
def next_x():
dx = next_d()
return x0 + dx
def next_y():
dy = next_d()
return y0 + dy
c = c.upper()
else:
next_x = next_y = next_p = next_d
if c == 'M': # Moveto
p1 = next_p()
# if path:
# paths.append((path, False, joins))
# path = [p1]
# joins = []
cairo_context.move_to(*p1)
pn, p0 = p0, p1
elif c in "LHV":
if c == 'L': # Lineto
p1 = next_p()
elif c == 'H': # Horizontal Lineto
p1 = (next_x(), y0)
elif c == 'V': # Vertical Lineto
p1 = (x0, next_y())
cairo_context.line_to(*p1)
# path.append(p1)
pn, p0 = p0, p1
elif c in "CS": # cubic bezier curve
if c == 'C':
p1 = next_p()
else: # 'S'
p1 = (2. * x0 - xn, 2 * y0 - yn) if cn in "CS" else p0
p2, p3 = next_p(), next_p()
# path += _cubic(p0, p1, p2, p3, du2)
cairo_context.rel_curve_to(p1, p2, p3)
pn, p0 = p2, p3
elif c in 'QT': # quadratic bezier curve
if c == 'Q':
p1 = next_p()
else: # 'T'
p1 = (2. * x0 - xn, 2 * y0 - yn) if cn in "QT" else p0
p2 = next_p()
path += _quadric(p0, p1, p2, du2)
pn, p0 = p1, p2
elif c == 'A': # Arcto
rs, phi, flags = next_d(), next_d(), next_d() # @UnusedVariable
# rs = (rx, ry) : radius in each direction
# phi = rotation of the axis of the ellipse
# flags = (large-arc-flag, sweep-flag)
# large-arc-flag, indique si on doit afficher larc dont la mesure fait plus de la moitié du périmètre de lellipse (dans ce cas, la valeur est 1), ou larc dont la mesure fait moins de la moitié du périmètre (valeur: 0).
# sweep-flag, indique quant à lui si larc doit être dessiné dans la direction négative des angles (dans lequel cas sa valeur est 0) ou dans la direction positive des angles (valeur: 1)
p1 = next_p()
# p1 : end point
# path += _arc(p0, rs, phi, flags, p1, du2)
assert(False) # incomplete implementation
# cairo_context.rel_curve_to
pn, p0 = p0, p1
elif c == 'Z': # Closepath
x1, y1 = p1 = path[0]
dx, dy = x1 - x0, y1 - y0
if (dx * dx + dy * dy) * du2 > 1.:
path.append(p1)
paths.append((path, True, joins))
path = []
joins = []
pn, p0 = p0, p1
cn = c
joins.append(len(path) - 1)
if path:
paths.append((path, False, joins))
cairo_context.stroke()
cairo_context.fill()
# print('rendering path')
def render_circle(circle_node, cairo_context):
""" renders the scenegraph circle 'circle_node' in the cairo drawing context 'cairo_context'
:param circle_node: the scenegraph text to render
:type circle_node: scenegraph2d.Circle
:param cairo_context: the cairo drawing context
"""
color = circle_node.fill
cairo_context.set_source_rgb(*color.rgb)
cairo_context.arc(circle_node.cx, circle_node.cy, circle_node.r, 0.0, math.pi * 2.0)
cairo_context.fill()
# print('render_circle : rendering circle (%f, %f, %f)' % (circle_node.cx, circle_node.cy, circle_node.r))
def render_text(text_node, cairo_context):
""" renders the scenegraph text 'text' in the cairo drawing context 'cairo_context'
:param text_node: the scenegraph text to render
:type text_node: scenegraph2d.Text
:param cairo_context: the cairo drawing context
"""
# face = wx.lib.wxcairo.FontFaceFromFont(wx.FFont(10, wx.SWISS, wx.FONTFLAG_BOLD))
# ctx.set_font_face(face)
cairo_context.set_font_size(10)
(x, y, width, height, dx, dy) = cairo_context.text_extents(text_node.text) # @UnusedVariable
# ctx.move_to(*center.Get())
# cairo_context.move_to(center.x - width/2, center.y + height/2)
# cairo_context.set_source_rgb(0, 0, 0)
cairo_context.move_to(text_node._anchor(), 0.0)
cairo_context.show_text(text_node.text)
# print('rendering text')
def render_node(node, cairo_context):
""" renders the scenegraph node 'node' in the cairo drawing context 'cairo_context'
:param node: the scenegraph node to render
:type node: scenegraph2d.Element
:param cairo_context: the cairo drawing context
:type cairo_context: cairo.Context
"""
# print node.tag
try:
handler = {
# "svg": render_group,
"g": render_group,
# "symbol": render_group,
# "a": render_group,
# "defs": render_group,
# "clipPath": render_group,
# "mask": render_group,
# "path": sg.Path,
"rect": render_rectangle,
"circle": render_circle,
# "ellipse": sg.Ellipse,
"line": render_path,
# "polyline": sg.Polyline,
# "polygon": sg.Polygon,
"text": render_text,
}[node.tag]
except KeyError:
# log.warning("unhandled tag : %s" % node.tag)
print("unhandled tag : %s" % node.tag)
return
# save the parent transform matrix
# parent_matrix = cairo_context.get_matrix()
cairo_context.save()
if(hasattr(node, 'fill')):
# print("node.fill = %s" % str(node.fill))
if (isinstance(node.fill, str)):
assert(False) # this shouldn't happen now that fill attribute is set to a Color node
match = re.match('^rgb\((?P<r>[0-9]+),(?P<g>[0-9]+),(?P<b>[0-9]+)\)', node.fill)
if match:
r = float(match.group('r')) / 255.0
g = float(match.group('g')) / 255.0
b = float(match.group('b')) / 255.0
else:
assert(False)
else:
r, g, b = node.fill.get_rgb()
cairo_context.set_source_rgb(r, g, b)
# render the node
handler(node, cairo_context)
cairo_context.restore()
# restore the parent transform matrix (so that brother nodes car render relative to their parent)
# cairo_context.set_matrix(parent_matrix)
def render_scene(scene, cairo_context):
""" renders the scenegraph 'scene' in the cairo drawing context 'cairo_context'
:param scene: the scenegraph to render
:type scene: scenegraph2d.Group
:param cairo_context: the cairo drawing context
:type cairo_context: cairo.Context
"""
render_node(scene, cairo_context)

View File

@ -0,0 +1,163 @@
# -*- coding: utf-8 -*-
"""font abstraction"""
# imports ####################################################################
from itertools import chain
from ctypes import byref, string_at
from .utils import get_font
from . import freetype2 as _ft2
_FT = _ft2.FT
_library = _ft2._library
# globals ####################################################################
def _on(tag):
return bool(tag & 1)
def _cubic(tag):
return bool(tag & 2)
# face #######################################################################
class Face(object):
def __init__(self, font_families, font_weight, font_style, px=10):
font_name, index = get_font(font_families, font_weight, font_style)
self.face = _ft2.Face()
self.pen = _ft2.Vector()
print('_library = %s' % str(_library))
print('font_name = %s' % str(font_name))
print('font_name.encode() = %s' % str(font_name.encode()))
if _FT.New_Face(_library, font_name.encode(), index, byref(self.face)) != 0:
raise ValueError("unable to create '%s' face" % font_name)
_FT.Select_Charmap(self.face, _ft2.ENCODING_UNICODE)
if px is not None:
self.set_size(px)
self._FT = _FT # keep a ref for finalizer
def __del__(self):
try:
self._FT.Done_Face(self.face)
except AttributeError:
pass
def set_size(self, px):
_FT.Set_Pixel_Sizes(self.face, 0, px)
def set_transform(self, a=1., b=0., c=0., d=1., e=0., f=0.):
matrix = _ft2.Matrix()
matrix.xx, matrix.xy = int(a * 0x10000), int(b * 0x10000)
matrix.yx, matrix.yy = int(c * 0x10000), int(d * 0x10000)
self.pen.x = int(e * 64)
self.pen.y = int(-f * 64)
_FT.Set_Transform(self.face, byref(matrix), byref(self.pen))
def _glyph(self, uc):
glyph_index = _FT.Get_Char_Index(self.face, ord(uc))
_FT.Load_Glyph(self.face, glyph_index, _ft2.LOAD_DEFAULT)
return self.face.contents.glyph.contents
def get_hkerning(self, ucl, ucr):
if ucl is None:
return 0.
left_glyph = _FT.Get_Char_Index(self.face, ord(ucl))
right_glyph = _FT.Get_Char_Index(self.face, ord(ucr))
kerning = _ft2.Vector()
_FT.Get_Kerning(self.face, left_glyph, right_glyph,
_ft2.KERNING_DEFAULT, byref(kerning))
return kerning.x / 64.
def get_bbox(self, text):
width = 0
top, bottom = 0, 0
up = None
glyph = None
for uc in text:
width += self.get_hkerning(up, uc)
up = uc
glyph = self._glyph(uc)
width += glyph.metrics.horiAdvance / 64.
top = max(top, glyph.metrics.horiBearingY / 64.)
bottom = min(bottom, (glyph.metrics.horiBearingY -
glyph.metrics.height) / 64.)
if glyph:
width += (glyph.metrics.horiBearingX + glyph.metrics.width -
glyph.metrics.horiAdvance) / 64.
return (0., -top), (width, top - bottom)
def bitmap(self, uc):
glyph = self._glyph(uc)
_FT.Render_Glyph(byref(glyph), _ft2.RENDER_MODE_NORMAL)
origin = glyph.bitmap_left, -glyph.bitmap_top
bitmap = glyph.bitmap
assert bitmap.pixel_mode == _ft2.PIXEL_MODE_GRAY, bitmap.pixel_mode
rows, columns = bitmap.rows, bitmap.pitch
size = columns, rows
offset = glyph.advance.x / 64., -glyph.advance.y / 64.
data = string_at(bitmap.buffer, rows * columns)
data = bytes(chain(*([255, 255, 255, c] for c in data)))
return origin, size, offset, data
def outline(self, uc):
glyph = self._glyph(uc)
outline = glyph.outline
data = []
b = 0
for c in range(outline.n_contours):
e = outline.contours[c]
# ensure we start with an 'on' point
for s in range(b, e + 1):
if _on(outline.tags[s]):
break
# generate path data
contour = []
command, offs = 'M', []
for i in chain(range(s, e + 1), range(b, s + 1)):
point, tag = outline.points[i], outline.tags[i]
point = (point.x / 64., -point.y / 64.)
if _on(tag): # 'on' point
contour.append(command)
if command is 'Q' and len(offs) >= 2:
(x0, y0) = offs[0]
for (x1, y1) in offs[1:]:
contour += [(x0, y0), ((x0 + x1) / 2, (y0 + y1) / 2), 'Q']
x0, y0 = x1, y1
contour.append((x0, y0))
else: # 'off' point
contour += offs
contour.append(point)
command, offs = 'L', []
else:
offs.append(point)
command = 'C' if _cubic(tag) else 'Q'
if contour:
contour.append('Z')
data += contour
b = e + 1
# bbox
bbox = _ft2.BBox()
_FT.Outline_Get_BBox(byref(outline), byref(bbox))
xmin, xmax = bbox.xMin / 64., bbox.xMax / 64.
ymin, ymax = bbox.yMin / 64., bbox.yMax / 64.
origin = xmin, -ymax
size = xmax - xmin, ymax - ymin
offset = glyph.advance.x / 64., -glyph.advance.y / 64.
return origin, size, offset, data

View File

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
"""Cocoa implementation of get_font"""
# imports ####################################################################
from CoreText import ( # pylint: disable=import-error
CTFontCollectionCreateFromAvailableFonts, # @UnresolvedImport
CTFontCollectionCreateMatchingFontDescriptors, # @UnresolvedImport
kCTFontFamilyNameAttribute, # @UnresolvedImport
kCTFontTraitsAttribute, # @UnresolvedImport
kCTFontURLAttribute, # @UnresolvedImport
kCTFontSymbolicTrait, # @UnresolvedImport
kCTFontItalicTrait, # @UnresolvedImport
kCTFontBoldTrait, # @UnresolvedImport
)
# fonts ######################################################################
_font_collection = CTFontCollectionCreateFromAvailableFonts({})
_font_descriptors = CTFontCollectionCreateMatchingFontDescriptors(_font_collection)
_FONTS = {}
_FONT_NAMES = {}
for _font in _font_descriptors:
family = _font[kCTFontFamilyNameAttribute]
_fonts = _FONTS.get(family, {})
_traits = _font[kCTFontTraitsAttribute]
_bold = bool(_traits[kCTFontSymbolicTrait] & kCTFontBoldTrait)
_italic = bool(_traits[kCTFontSymbolicTrait] & kCTFontItalicTrait)
_font_name = _font[kCTFontURLAttribute].path()
_key = _italic, _bold
_fonts[_key] = _font_name
_names = _FONT_NAMES.get(_font_name, [])
_names.append(_key) # TODO: no idea how to retreive fonst index in dfont file
_names.sort() # so rely on arbitrary assunption
_FONT_NAMES[_font_name] = _names
_FONTS[family] = _fonts
# utils ######################################################################
def _get_font(family, bold, italic):
key = italic, bold
font_name = _FONTS[family][key]
return font_name, _FONT_NAMES[font_name].index(key)

View File

@ -0,0 +1,304 @@
# -*- coding: utf-8 -*-
"""freetype2."""
# imports ####################################################################
from ctypes import cdll, c_int, c_short, c_long, c_uint, c_ushort, c_char, c_byte, c_ubyte, c_void_p, Structure, POINTER, CFUNCTYPE, byref
from ctypes.util import find_library
# platform libraries #########################################################
from sys import platform as _platform
if _platform == "darwin":
# patching find_library to look into X11 libs
X11lib = "/usr/X11/lib"
from os import environ
try:
DYLD_LIBRARY_PATH = environ["DYLD_LIBRARY_PATH"]
except KeyError:
environ["DYLD_LIBRARY_PATH"] = X11lib
else:
environ["DYLD_LIBRARY_PATH"] = ":".join([DYLD_LIBRARY_PATH,
X11lib])
class FreetypeWrapper(object):
def __init__(self, ft):
self.ft = ft
def __getattr__(self, name):
return getattr(self.ft, "FT_%s" % name)
FT = FreetypeWrapper(cdll.LoadLibrary(find_library("freetype")))
if _platform == "darwin":
try:
environ["DYLD_LIBRARY_PATH"] = DYLD_LIBRARY_PATH
del DYLD_LIBRARY_PATH
except NameError:
del environ["DYLD_LIBRARY_PATH"]
# types ######################################################################
Int = c_int
Short = c_short
Long = c_long
UInt = c_uint
UShort = c_ushort
String = c_char
Fixed = c_long
Pos = c_long
class Vector(Structure):
_fields_ = [("x", Pos),
("y", Pos)]
class Matrix(Structure):
_fields_ = [("xx", Fixed),
("xy", Fixed),
("yx", Fixed),
("yy", Fixed)]
class Bitmap(Structure):
_fields_ = [("rows", c_int),
("width", c_int),
("pitch", c_int),
("buffer", POINTER(c_ubyte)), # c_void_p),
("num_grays", c_short),
("pixel_mode", c_char),
("palette_mode", c_char),
("palette", c_void_p)]
class Outline(Structure):
_fields_ = [("n_contours", c_short),
("n_points", c_short),
("points", POINTER(Vector)),
("tags", POINTER(c_byte)),
("contours", POINTER(c_short)),
("flags", c_int)]
class Size_Metrics(Structure):
_fields_ = [("x_ppem", UShort),
("y_ppem", UShort),
("x_scale", Fixed),
("y_scale", Fixed),
("ascender", Pos),
("descender", Pos),
("height", Pos),
("max_advance", Pos)]
class Bitmap_Size(Structure):
_fields_ = [("height", Short),
("width", Short),
("size", Pos),
("x_ppem", Pos),
("y_ppem", Pos)]
class BBox(Structure):
_fields_ = [("xMin", Pos),
("yMin", Pos),
("xMax", Pos),
("yMax", Pos)]
class Glyph_Metrics(Structure):
_fields_ = [("width", Pos),
("height", Pos),
("horiBearingX", Pos),
("horiBearingY", Pos),
("horiAdvance", Pos),
("vertBearingX", Pos),
("vertBearingY", Pos),
("vertAdvance", Pos)]
Generic_Finalizer = CFUNCTYPE(c_void_p, c_void_p)
class Generic(Structure):
_fields_ = [("data", c_void_p),
("finalizer", Generic_Finalizer)]
class Glyph_Format(c_int):
def __repr__(self):
v = self.value
return "".join(chr((v >> 8 * i) & 255) for i in reversed(range(4)))
Encoding = c_int # enum
SubGlyph = c_void_p # POINTER(SubGlyphRec)
Slot_Internal = c_void_p # POINTER(Slot_InternalRec)
Size_Internal = c_void_p # POINTER(Size_InternalRec)
class CharMapRec(Structure):
pass
CharMap = POINTER(CharMapRec)
class GlyphSlotRec(Structure):
pass
GlyphSlot = POINTER(GlyphSlotRec)
class SizeRec(Structure):
pass
Size = POINTER(SizeRec)
class FaceRec(Structure):
pass
Face = POINTER(FaceRec)
Library = c_void_p
CharMapRec._fields_ = [
("face", Face),
("encoding", Encoding),
("platform_id", UShort),
("encoding_id", UShort)
]
GlyphSlotRec._fields_ = [
("library", Library),
("face", Face),
("next", GlyphSlot),
("reserved", UInt),
("generic", Generic),
("metrics", Glyph_Metrics),
("linearHoriAdvance", Fixed),
("linearVertAdvance", Fixed),
("advance", Vector),
("format", Glyph_Format),
("bitmap", Bitmap),
("bitmap_left", Int),
("bitmap_top", Int),
("outline", Outline),
("num_subglyphs", UInt),
("subglyphs", SubGlyph),
("control_data", c_void_p),
("control_len", c_long),
("lsb_delta", Pos),
("rsb_delta", Pos),
("other", c_void_p),
("internal", Slot_Internal),
]
SizeRec._fields_ = [
("face", Face),
("generic", Generic),
("metrics", Size_Metrics),
("internal", Size_Internal),
]
FaceRec._fields_ = [
("num_faces", Long),
("face_index", Long),
("face_flags", Long),
("style_flags", Long),
("num_glyphs", Long),
("family_name", POINTER(String)),
("style_name", POINTER(String)),
("num_fixed_sizes", Int),
("available_sizes", POINTER(Bitmap_Size)),
("num_charmaps", Int),
("charmaps", POINTER(CharMap)),
("generic", Generic),
("bbox", BBox),
("unit_per_EM", UShort),
("ascender", Short),
("descender", Short),
("height", Short),
("max_advance_width", Short),
("max_advance_height", Short),
("underline_position", Short),
("underline_thickness", Short),
("glyph", GlyphSlot),
("size", Size),
("charmap", CharMap),
]
# constants ##################################################################
LOAD_DEFAULT = 0x0 # @IgnorePep8
LOAD_NO_SCALE = 0x1 # @IgnorePep8
LOAD_NO_HINTING = 0x2 # @IgnorePep8
LOAD_RENDER = 0x4 # @IgnorePep8
LOAD_NO_BITMAP = 0x8 # @IgnorePep8
LOAD_VERTICAL_LAYOUT = 0x10 # @IgnorePep8
LOAD_FORCE_AUTOHINT = 0x20 # @IgnorePep8
LOAD_CROP_BITMAP = 0x40 # @IgnorePep8
LOAD_PEDANTIC = 0x80 # @IgnorePep8
LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 0x200
LOAD_NO_RECURSE = 0x400 # @IgnorePep8
LOAD_IGNORE_TRANSFORM = 0x800 # @IgnorePep8
LOAD_MONOCHROME = 0x1000 # @IgnorePep8
LOAD_LINEAR_DESIGN = 0x2000 # @IgnorePep8
[
PIXEL_MODE_NONE,
PIXEL_MODE_MONO,
PIXEL_MODE_GRAY,
PIXEL_MODE_GRAY2,
PIXEL_MODE_GRAY4,
PIXEL_MODE_LCD,
PIXEL_MODE_LCD_V,
PIXEL_MODE_MAX
] = [bytes([i]) for i in range(8)]
[
RENDER_MODE_NORMAL,
RENDER_MODE_LIGHT,
RENDER_MODE_MONO,
RENDER_MODE_LCD,
RENDER_MODE_LCD_V,
RENDER_MODE_MAX
] = range(6)
[
KERNING_DEFAULT,
KERNING_UNFITTED,
KERNING_UNSCALED,
] = range(3)
def LOAD_TARGET_(x):
return ((x) & 15) << 16
LOAD_TARGET_NORMAL = LOAD_TARGET_(RENDER_MODE_NORMAL ) # @IgnorePep8
LOAD_TARGET_LIGHT = LOAD_TARGET_(RENDER_MODE_LIGHT ) # @IgnorePep8
LOAD_TARGET_MONO = LOAD_TARGET_(RENDER_MODE_MONO ) # @IgnorePep8
LOAD_TARGET_LCD = LOAD_TARGET_(RENDER_MODE_LCD ) # @IgnorePep8
LOAD_TARGET_LCD_V = LOAD_TARGET_(RENDER_MODE_LCD_V ) # @IgnorePep8
def ENC_TAG(s):
a, b, c, d = s
return (ord(a) << 24 |
ord(b) << 16 |
ord(c) << 8 | # @IgnorePep8
ord(d))
ENCODING_UNICODE = ENC_TAG("unic")
# initialisation #############################################################
_library = Library()
FT.Init_FreeType(byref(_library))

View File

@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
# imports ####################################################################
import os
from sys import platform as _platform
if _platform == "darwin":
try:
from ._cocoa import _get_font
except ImportError:
pass
try:
_get_font
except NameError:
def _get_font(family, bold, italic): # pylint: disable=function-redefined
raise LookupError
# constants ##################################################################
_CONTRIBS_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__),
"..", "..",
"contribs", "freefont-20120503"))
_FALLBACK_FONTS = {
"sans-serif": {
(False, False): os.path.join(_CONTRIBS_PATH, "FreeSans.otf"),
(False, True): os.path.join(_CONTRIBS_PATH, "FreeSansOblique.otf"),
(True, False): os.path.join(_CONTRIBS_PATH, "FreeSansBold.otf"),
(True, True): os.path.join(_CONTRIBS_PATH, "FreeSansBoldOblique.otf"),
},
"serif": {
(False, False): os.path.join(_CONTRIBS_PATH, "FreeSerif.otf"),
(False, True): os.path.join(_CONTRIBS_PATH, "FreeSerifItalic.otf"),
(True, False): os.path.join(_CONTRIBS_PATH, "FreeSerifBold.otf"),
(True, True): os.path.join(_CONTRIBS_PATH, "FreeSerifBoldItalic.otf"),
},
"mono": {
(False, False): os.path.join(_CONTRIBS_PATH, "FreeMono.otf"),
(False, True): os.path.join(_CONTRIBS_PATH, "FreeMonoOblique.otf"),
(True, False): os.path.join(_CONTRIBS_PATH, "FreeMonoBold.otf"),
(True, True): os.path.join(_CONTRIBS_PATH, "FreeMonoBoldOblique.otf"),
},
}
# font lookup ################################################################
def _get_fallback_font(family, bold=False, italic=False):
return _FALLBACK_FONTS[family][bold, italic], 0
def get_font(families, weight="normal", style="normal"):
bold = weight in ["bold", "bolder", "600", "700", "800", "900"]
italic = style in ["italic", "oblique"]
families = [family.strip() for family in families.split(',')] + ["sans-serif"]
font_name, index = None, 0
for font_getter in [_get_font, _get_fallback_font]:
for family in families:
try:
font_name, index = font_getter(family, bold, italic)
except LookupError:
continue
break
else:
continue
break
return font_name, index
__all__ = [
"get_font",
]

View File

@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
"""seagull.scenegraph module"""
from .paint import Color, LinearGradient, RadialGradient, Pattern
from .transform import Translate, Scale, Rotate, SkewX, SkewY, Matrix
from .element import (Use, Group, Path,
Rectangle, Circle, Ellipse,
Line, Polyline, Polygon,
Text, Image)

View File

@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
"""
misc utility functions and classes
"""
# utils ######################################################################
def _indent(s, level=1, tab="\t"):
"""indent blocks"""
indent = tab * level
return "\n".join("%s%s" % (indent, line) for line in s.split("\n"))
def _u(v, encoding="utf8"):
"""provides a unicode string from anything."""
if isinstance(v, str):
return v
elif isinstance(v, (list, tuple)):
return " ".join(_u(vi, encoding) for vi in v)
elif v is None:
return "none"
else:
return str(v)
# base classes ###############################################################
class _Base(object):
"""equality based on state rather than id"""
_state_attributes = []
def _state(self):
return {name: getattr(self, name)
for name in self._state_attributes}
def __eq__(self, other):
try:
return other._state() == self._state()
except AttributeError:
return False
def __ne__(self, other):
return not self.__eq__(other)
# def __hash__(self): return hash(self._state())
def __hash__(self):
raise RuntimeError("state is not hashable")
class _Element(_Base):
"""element with xml serialization support"""
_state_attributes = ["tag"]
attributes = []
def __init__(self):
super(_Element, self).__init__()
self.id = None
@property
def tag(self):
"""the svg tag that represents this element
"""
raise NotImplementedError # this tag is suppsed to be defined in derived classes
def _xml(self, defs):
"""xml serialization"""
u = "<%s %s" % (self.tag, self._xml_attributes(defs))
content = self._xml_content(defs)
if content.strip():
u += ">\n" + \
_indent(content) + "\n" + \
"</%s>" % self.tag
else:
u += "/>"
return u
def _xml_content(self, defs):
"""xml serialization of content"""
return ""
def _xml_attributes(self, defs):
"""xml serialization of attributes"""
return " ".join(self._xml_attribute(name, defs) for name in self.attributes)
def _xml_attribute(self, name, defs):
"""unicode serialization of attribute/value pair"""
attribute = getattr(self, name)
if name == "href":
name = "xlink:href"
try:
href = "#%s" % attribute.id
except:
pass
else:
defs.append(attribute)
attribute = href
try:
u = attribute._xml_attr(defs)
except AttributeError:
u = _u(attribute)
return "%s='%s'" % (name.replace("_", "-"), u) if u else ""
def _xml_attr(self, defs):
defs.append(self)
return "url(#%s)" % self.id

View File

@ -0,0 +1,294 @@
# -*- coding: utf-8 -*-
"""
scenegraph.element
"""
# imports ####################################################################
from weakref import WeakValueDictionary as _weakdict
# from ...opengl.utils import OffscreenContext
from .._common import _Element
from ..paint import Color, _Texture, _MaskContext
from ..transform import Matrix, Translate, stretch, product
# element ####################################################################
_elements_by_id = _weakdict()
def _id(element):
element_id = getattr(element, "_id", "%X" % id(element))
_elements_by_id[element_id] = element
return element_id
def get_element_by_id(element_id):
return _elements_by_id[element_id]
"""
List of all possible attributes for svg nodes
"""
_ATTRIBUTES = [
"id", "href",
"x", "y",
"width", "height",
"r", "rx", "ry",
"cx", "cy",
"points",
"x1", "x2", "y1", "y2",
"opacity", "color",
"fill", "fill_opacity", "fill_rule",
"stroke", "stroke_opacity", "stroke_width",
"stroke_linecap", "stroke_linejoin", "stroke_miterlimit",
"stroke_dasharray", "stroke_dashoffset",
"font_family", "font_weight", "font_style", "font_size",
"text_anchor",
"transform",
"clip_path",
"mask",
"d",
]
_INHERITEDS = {
"color": Color.black,
"fill": Color.black,
"fill_opacity": 1.,
"fill_rule": 'nonzero',
"stroke": Color.none,
"stroke_opacity": 1.,
"stroke_width": 1,
"stroke_linecap": 'butt',
"stroke_linejoin": 'miter',
"stroke_miterlimit": 4.,
"stroke_dasharray": None,
"stroke_dashoffset": 0.,
"font_family": 'sans-serif',
"font_weight": 'normal',
"font_style": 'normal',
"font_size": 10,
"text_anchor": 'start',
}
class Element(_Element):
""" A node in the svg tree
"""
x, y = 0, 0
transform = None
opacity = 1.
clip_path = None
mask = None
_state_attributes = _Element._state_attributes + list(_INHERITEDS) + [
"x", "y", "transform",
"opacity", "clip_path", "mask"
]
def __init__(self, **attributes):
"""
:param attributes: svg attributes (eg for a circle element : { 'cx':'5.0', 'cy':'10.0', 'r':'6.0', 'transform':'translate(30,40) rotate(45)' })
"""
self._attributes = set()
self._inheriteds = _INHERITEDS
for attribute in attributes:
setattr(self, attribute, attributes[attribute])
# the transform attribute contains a list of transformations associated to this Element. It doesn't take into account its parents transforms.
if self.transform is None:
# empty list of transforms if the transform svg attribute is not present in the svg node
self.transform = []
self._parent = None # the svg group containing this element
def __setattr__(self, attribute, value):
if attribute in _ATTRIBUTES:
self._attributes.add(attribute)
super(Element, self).__setattr__(attribute, value)
def __delattr__(self, attribute):
super(Element, self).__delattr__(attribute)
if attribute in _ATTRIBUTES:
self._attributes.remove(attribute)
def __getattr__(self, attribute):
if attribute in _INHERITEDS:
return self._inheriteds[attribute]
try:
return super(Element, self).__getattr__(attribute)
except AttributeError:
return super(Element, self).__getattribute__(attribute)
def _inherit(self, inheriteds):
self._inheriteds = inheriteds
return {attr: getattr(self, attr) for attr in _INHERITEDS}
@property
def id(self):
self._attributes.add("id")
return _id(self)
@property
def attributes(self):
return (name for name in _ATTRIBUTES if name in self._attributes)
def __hash__(self):
return id(self) # hash((self.name, self.location))
def __eq__(self, other):
return id(self) == id(other)
def __ne__(self, other):
# Not strictly necessary, but to avoid having both x==y and x!=y
# True at the same time
return not(self == other)
@property
def parent(self):
"""
:rtype: scenegraph.Element
"""
return self._parent
@parent.setter
def parent(self, parent_group):
self._parent = parent_group
# transformations
def local_to_parent_matrix(self):
"""returns the matrix that converts coordinates in this node's space to its parent's space
:rtype: scenegraph.Matrix
"""
return product(*self.transform + [Translate(self.x, self.y)])
def parent_to_world_matrix(self):
"""returns the matrix that converts coordinates in this node's parent's space to the world space
:rtype: scenegraph.Matrix
"""
if self.parent is None:
return Matrix()
else:
return self.parent.parent_to_world_matrix() * self.parent.local_to_parent_matrix() # pylint: disable=no-member
def local_to_world_matrix(self):
"""returns the matrix that converts coordinates in this node's space to the world space
:rtype: scenegraph.Matrix
"""
return self.parent_to_world_matrix() * self.local_to_parent_matrix() # pylint: disable=no-member
# axis-aligned bounding box
def aabbox(self, transform=Matrix(), inheriteds=_INHERITEDS):
"""returns the axis-aligned bounding box of this xml element
"""
inheriteds = self._inherit(inheriteds)
return self._aabbox(transform * self.local_to_parent_matrix(), inheriteds)
def _aabbox(self, transform, inheriteds):
raise NotImplementedError
def _units(self, elem, attr, default="userSpaceOnUse"):
units = getattr(elem, attr, default)
if units == "userSpaceOnUse":
transform = Matrix()
elif units == "objectBoundingBox":
(x_min, y_min), (x_max, y_max) = self.aabbox()
transform = stretch(x_min, y_min, x_max - x_min, y_max - y_min)
else:
raise ValueError("unknown units %s" % units)
return product(*self.transform) * transform
# rendering
def _color(self, color):
if color == Color.current:
return self.color
return color
def render(self, transform=Matrix(), inheriteds=_INHERITEDS, context=None,
clipping=True, masking=True, opacity=True):
inheriteds = self._inherit(inheriteds)
if context is None:
# context = OffscreenContext()
assert False
if (clipping and self.clip_path) or (masking and self.mask):
if clipping and self.clip_path:
clipping = False
mask, units = self.clip_path, "clipPathUnits"
else:
masking = False
mask, units = self.mask, "maskContentUnits"
mask_transform = self._units(mask, units)
with context(mask.aabbox(transform * mask_transform),
(0., 0., 0., 0.)) as ((x, y), (width, height),
mask_texture_id):
if not mask_texture_id:
return
mask.render(transform * mask_transform, context=context)
with _MaskContext((x, y), (width, height), mask_texture_id):
self.render(transform, inheriteds, context,
clipping=clipping, masking=masking, opacity=opacity)
elif opacity and self.opacity < 1.:
with context(self.aabbox(transform, inheriteds)) as \
((x, y), (width, height), elem_texture_id):
if not elem_texture_id:
return
self.render(transform, inheriteds, context,
clipping=clipping, masking=masking, opacity=False)
Rectangle(x=x, y=y, width=width, height=height,
fill=_Texture(elem_texture_id),
fill_opacity=self.opacity).render(context=context)
else:
self._render(transform * self.local_to_parent_matrix(), inheriteds, context)
def _render(self, transform, inheriteds, context):
raise NotImplementedError
# picking
def _hit_test(self, x, y, transform):
"""tests if the position (x,y) collides with this shape (not its children)
"""
return []
def pick(self, x=0, y=0, parent_to_world=Matrix()):
"""
returns the list of svg nodes that are hit when picking at position x,y
:param parent_to_world: the transformation matrix that converts coordinates from this element's parent space to the scene's world space (the space in which x and y coordinates are expressed)
"""
parent_to_world = parent_to_world * self.local_to_parent_matrix()
hits = self._hit_test(x, y, parent_to_world)
hits += [([self] + e, p) for e, p in self._pick_content(x, y, parent_to_world)]
return hits
def _pick_content(self, x, y, transform):
"""tests if the position (x,y) collides with the children shapes of this shape
"""
return []
# elements ###################################################################
from .use import Use # @IgnorePep8
from .group import Group # @IgnorePep8
from .rectangle import Rectangle # @IgnorePep8
from .circle import Circle # @IgnorePep8
from .ellipse import Ellipse # @IgnorePep8
from .line import Line # @IgnorePep8
from .polyline import Polyline # @IgnorePep8
from .polygon import Polygon # @IgnorePep8
from .path import Path # @IgnorePep8
from .text import Text # @IgnorePep8
from .image import Image # @IgnorePep8

Some files were not shown because too many files have changed in this diff Show More