Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

519

520

521

522

523

524

525

526

527

528

529

530

531

532

533

534

535

536

537

538

539

540

541

542

543

544

545

546

547

548

549

550

551

552

553

554

555

556

557

558

559

560

561

562

563

564

565

566

567

568

569

570

571

572

573

574

575

576

577

578

579

580

581

582

583

584

585

586

587

588

589

590

591

592

593

594

595

596

597

598

599

600

601

602

603

604

605

606

607

608

609

610

611

612

613

614

615

616

617

618

619

620

621

622

623

624

625

626

627

628

629

630

631

632

633

634

635

636

637

638

639

640

641

642

643

644

645

646

647

648

649

650

651

652

653

654

655

656

657

658

659

660

661

662

663

664

665

666

667

668

669

670

671

672

673

674

675

676

677

678

679

680

681

682

683

684

685

686

687

688

689

690

691

692

693

694

695

696

697

698

699

700

701

702

703

704

705

706

707

708

709

710

711

712

713

714

715

716

717

718

719

720

721

722

723

724

725

726

727

728

729

730

731

732

733

734

735

736

737

738

739

740

741

742

743

744

745

746

747

748

749

750

751

752

753

754

755

756

757

758

759

760

761

762

763

764

765

766

767

768

769

770

771

772

773

774

775

776

777

778

779

780

781

782

783

784

785

786

787

788

789

790

791

792

793

794

795

796

797

798

799

800

801

802

803

804

805

806

807

808

809

810

811

812

813

814

815

816

817

818

819

820

821

822

823

824

825

826

827

828

829

830

831

832

833

834

835

836

837

838

839

840

841

842

843

844

845

846

847

848

849

850

851

852

853

854

855

856

857

858

859

860

861

862

863

864

865

866

867

868

869

870

871

872

873

874

875

876

877

878

879

880

881

882

883

884

885

886

887

888

889

890

891

892

893

894

895

896

897

898

899

900

901

902

903

904

905

906

907

908

909

910

911

912

913

914

915

916

917

918

919

920

921

922

923

924

925

926

927

928

929

930

931

932

933

934

935

936

937

938

939

940

941

942

943

944

945

946

947

948

949

950

951

952

953

954

955

956

957

958

959

960

961

962

963

964

965

966

967

968

969

970

971

972

973

974

975

976

977

978

979

980

981

982

983

984

985

986

987

988

989

990

991

992

993

994

995

996

997

998

999

1000

1001

1002

1003

1004

1005

1006

1007

1008

1009

1010

1011

1012

1013

1014

# coding: utf-8 

# Copyright (c) Pymatgen Development Team. 

# Distributed under the terms of the MIT License. 

 

from __future__ import division, unicode_literals 

 

""" 

This module contains the class describing the coordination geometries that can exist in a given structure. These 

"model" coordination geometries are described in the following articles : 

- Pure Appl. Chem., Vol. 79, No. 10, pp. 1779--1799, 2007. 

- Acta Cryst. A, Vol. 46, No. 1, pp. 1--11, 1990. 

The module also contains descriptors of part of these geometries (plane of separation, ...) that are used in the 

identification algorithms. 

""" 

 

__author__ = "David Waroquiers" 

__copyright__ = "Copyright 2012, The Materials Project" 

__credits__ = "Geoffroy Hautier" 

__version__ = "2.0" 

__maintainer__ = "David Waroquiers" 

__email__ = "david.waroquiers@gmail.com" 

__date__ = "Feb 20, 2016" 

 

import numpy as np 

from scipy.misc import factorial 

import itertools 

import abc 

from monty.json import MSONable, MontyDecoder 

import json 

import os 

from six import with_metaclass 

 

module_dir = os.path.dirname(os.path.abspath(__file__)) 

 

UNKNOWN_ENVIRONMENT_SYMBOL = 'UNKNOWN' 

UNCLEAR_ENVIRONMENT_SYMBOL = 'UNCLEAR' 

EXPLICIT_PERMUTATIONS = 'EXPLICIT_PERMUTATIONS' 

SEPARATION_PLANE = 'SEPARATION_PLANE' 

 

 

class AbstractChemenvAlgorithm(with_metaclass(abc.ABCMeta, MSONable)): 

""" 

Class used to define a Chemenv strategy for the neighbors and coordination environment to be applied to a 

StructureEnvironments object 

""" 

 

def __init__(self, algorithm_type): 

self._algorithm_type = algorithm_type 

 

@abc.abstractmethod 

def as_dict(self): 

""" 

A JSON serializable dict representation of the algorithm 

""" 

pass 

 

@property 

def algorithm_type(self): 

return self._algorithm_type 

 

@abc.abstractmethod 

def __str__(self): 

return 

 

 

class ExplicitPermutationsAlgorithm(AbstractChemenvAlgorithm): 

def __init__(self, permutations): 

""" 

Initializes a separation plane for a given perfect coordination geometry 

""" 

super(ExplicitPermutationsAlgorithm, self).__init__( 

algorithm_type=EXPLICIT_PERMUTATIONS) 

self._permutations = permutations 

 

def __str__(self): 

return self.algorithm_type 

 

@property 

def permutations(self): 

return self._permutations 

 

@property 

def as_dict(self): 

return {"@module": self.__class__.__module__, 

"@class": self.__class__.__name__, 

"permutations": self._permutations} 

 

@classmethod 

def from_dict(cls, dd): 

return cls(dd['permutations']) 

 

 

class SeparationPlane(AbstractChemenvAlgorithm): 

def __init__(self, plane_points, mirror_plane=False, ordered_plane=False, 

point_groups=None, 

ordered_point_groups=None, # include_inverted_plane=False, 

point_groups_permutations=None, 

# do_inverse_pt_gp_permutations=False, plane_type='MIRROR', 

explicit_permutations=None, minimum_number_of_points=None, 

explicit_optimized_permutations=None, 

multiplicity=None, 

other_plane_points=None): # , plane_safe_permutations=False): 

""" 

Initializes a separation plane for a given perfect coordination geometry 

 

:param mirror_plane: True if the separation plane is a mirror plane, in which case there is a correspondence 

of the points in each point_group (can reduce the number of permutations) 

:param ordered_plane : True if the order of the points in the plane can be taken into account to reduce the 

number of permutations 

:param plane_points: Indices of the points that are in the plane in the perfect structure (and should be 

found in the defective one as well) 

:param point_groups: The two groups of points separated by the plane 

:param plane_type: can be "MIRROR", if the plane is a mirror plane going through the central site, 

'BASAL_THROUGH_CENTER', if the plane is a basal plane (no point on the "left" side) going through the central 

site, 'BASAL', if the is a basal plane not going through the central site, 'UNEQUILIBRATED_THROUGH_CENTER', if 

the plane cuts the geometry in two groups of points with different numbers of points on each side, and is going 

through the centre, 'UNEQUILIBRATED', if the plane cuts the geometry in two groups of points with different 

numbers of points on each side, and is not going through the centre, 'EQUILIBRATED_THROUGH_CENTER', if the 

plane cuts the geometry in two groups of points of the same size, is going through the centre but is not a 

mirror plane, 'EQUILIBRATED', if the plane cuts the geometry in two groups of points of the same size, is not 

going through the centre but is not a mirror plane. 

""" 

super(SeparationPlane, self).__init__(algorithm_type=SEPARATION_PLANE) 

self.mirror_plane = mirror_plane 

self.plane_points = plane_points 

self.point_groups = point_groups 

if len(point_groups[0]) > len(point_groups[1]): 

raise RuntimeError( 

"The number of points in the first group should be\n" 

"less than or equal to the number of points in the second group") 

self._hash = 10000 * len(plane_points) + 100 * len( 

point_groups[0]) + len(point_groups[1]) 

self.ordered_plane = ordered_plane 

self.ordered_point_groups = [False, 

False] if ordered_point_groups is None else ordered_point_groups 

self._ordered_indices = list(point_groups[0]) 

self._ordered_indices.extend(plane_points) 

self._ordered_indices.extend(point_groups[1]) 

self._inv_ordered_indices = np.argsort(self._ordered_indices) 

self._point_groups_permutations = point_groups_permutations 

self.explicit_permutations = explicit_permutations 

self.explicit_optimized_permutations = explicit_optimized_permutations 

self._safe_permutations = None 

if self.explicit_optimized_permutations is not None: 

self._permutations = self.explicit_optimized_permutations 

elif self.explicit_permutations is not None: 

self._permutations = self.explicit_permutations 

self.multiplicity = multiplicity 

self.other_plane_points = other_plane_points 

self.minimum_number_of_points = minimum_number_of_points 

self.maximum_number_of_points = len(self.plane_points) 

self._ref_separation_perm = list(self.point_groups[0]) 

self._ref_separation_perm.extend(list(self.plane_points)) 

self._ref_separation_perm.extend(list(self.point_groups[1])) 

self._argsorted_ref_separation_perm = list( 

np.argsort(self._ref_separation_perm)) 

 

@property 

def ordered_indices(self): 

return self._ordered_indices 

 

@property 

def inv_ordered_indices(self): 

return self._inv_ordered_indices 

 

@property 

def permutations(self): 

return self._permutations 

 

@property 

def ref_separation_perm(self): 

return self._ref_separation_perm 

 

@property 

def argsorted_ref_separation_perm(self): 

return self._argsorted_ref_separation_perm 

 

def safe_plane_permutations(self, ordered_plane=False, 

ordered_point_groups=None): 

ordered_point_groups = [False, 

False] if ordered_point_groups is None else ordered_point_groups 

rotate = lambda s, n: s[-n:] + s[:-n] 

if ordered_plane and self.ordered_plane: 

plane_perms = [rotate(self.plane_points, ii) for ii in 

range(len(self.plane_points))] 

invplanepoints = self.plane_points[::-1] 

plane_perms.extend([rotate(invplanepoints, ii) for ii in 

range(len(self.plane_points) - 1, -1, -1)]) 

else: 

plane_perms = list(itertools.permutations(self.plane_points)) 

if ordered_point_groups[0] and self.ordered_point_groups[0]: 

s0_perms = [rotate(self.point_groups[0], ii) for ii in 

range(len(self.point_groups[0]))] 

invpg0 = self.point_groups[0][::-1] 

s0_perms.extend([rotate(invpg0, ii) for ii in range(len(invpg0))]) 

else: 

s0_perms = list(itertools.permutations(self.point_groups[0])) 

if ordered_point_groups[1] and self.ordered_point_groups[1]: 

s2_perms = [rotate(self.point_groups[1], ii) for ii in 

range(len(self.point_groups[1]))] 

invpg2 = self.point_groups[1][::-1] 

s2_perms.extend([rotate(invpg2, ii) for ii in range(len(invpg2))]) 

else: 

s2_perms = list(itertools.permutations(self.point_groups[1])) 

add_opposite = False 

if self._safe_permutations is None: 

self._safe_permutations = [] 

for perm_side1 in s0_perms: 

for perm_sep_plane in plane_perms: 

for perm_side2 in s2_perms: 

perm = list(perm_side1) 

perm.extend(list(perm_sep_plane)) 

perm.extend(list(perm_side2)) 

self._safe_permutations.append(perm) 

if add_opposite: 

perm = list(perm_side2) 

perm.extend(list(perm_sep_plane)) 

perm.extend(list(perm_side1)) 

self._safe_permutations.append(perm) 

return self._safe_permutations 

 

def safe_separation_permutations(self, ordered_plane=False, 

ordered_point_groups=None, 

add_opposite=False): 

s0 = range(len(self.point_groups[0])) 

plane = range(len(self.point_groups[0]), 

len(self.point_groups[0]) + len(self.plane_points)) 

s1 = range(len(self.point_groups[0]) + len(self.plane_points), 

len(self.point_groups[0]) + len(self.plane_points) + len( 

self.point_groups[1])) 

ordered_point_groups = [False, 

False] if ordered_point_groups is None else ordered_point_groups 

rotate = lambda s, n: s[-n:] + s[:-n] 

if ordered_plane and self.ordered_plane: 

plane_perms = [rotate(plane, ii) for ii in range(len(plane))] 

inv_plane = plane[::-1] 

plane_perms.extend( 

[rotate(inv_plane, ii) for ii in range(len(inv_plane))]) 

else: 

plane_perms = list(itertools.permutations(plane)) 

if ordered_point_groups[0] and self.ordered_point_groups[0]: 

s0_perms = [rotate(s0, ii) for ii in range(len(s0))] 

inv_s0 = s0[::-1] 

s0_perms.extend([rotate(inv_s0, ii) for ii in range(len(inv_s0))]) 

else: 

s0_perms = list(itertools.permutations(s0)) 

if ordered_point_groups[1] and self.ordered_point_groups[1]: 

s1_perms = [rotate(s1, ii) for ii in range(len(s1))] 

inv_s1 = s1[::-1] 

s1_perms.extend([rotate(inv_s1, ii) for ii in range(len(inv_s1))]) 

else: 

s1_perms = list(itertools.permutations(s1)) 

if self._safe_permutations is None: 

self._safe_permutations = [] 

for perm_side1 in s0_perms: 

for perm_sep_plane in plane_perms: 

for perm_side2 in s1_perms: 

perm = list(perm_side1) 

perm.extend(list(perm_sep_plane)) 

perm.extend(list(perm_side2)) 

self._safe_permutations.append(perm) 

if add_opposite: 

perm = list(perm_side2) 

perm.extend(list(perm_sep_plane)) 

perm.extend(list(perm_side1)) 

self._safe_permutations.append(perm) 

return self._safe_permutations 

 

@property 

def as_dict(self): 

return {"@module": self.__class__.__module__, 

"@class": self.__class__.__name__, 

"plane_points": self.plane_points, 

"mirror_plane": self.mirror_plane, 

"ordered_plane": self.ordered_plane, 

"point_groups": self.point_groups, 

"ordered_point_groups": self.ordered_point_groups, 

"point_groups_permutations": self._point_groups_permutations, 

"explicit_permutations": self.explicit_permutations, 

"explicit_optimized_permutations": self.explicit_optimized_permutations, 

"multiplicity": self.multiplicity, 

"other_plane_points": self.other_plane_points, 

"minimum_number_of_points": self.minimum_number_of_points} 

 

@classmethod 

def from_dict(cls, dd): 

eop = dd[ 

'explicit_optimized_permutations'] if 'explicit_optimized_permutations' in dd else None 

return cls(plane_points=dd['plane_points'], 

mirror_plane=dd['mirror_plane'], 

ordered_plane=dd['ordered_plane'], 

point_groups=dd['point_groups'], 

ordered_point_groups=dd['ordered_point_groups'], 

point_groups_permutations=dd['point_groups_permutations'], 

explicit_permutations=dd['explicit_permutations'], 

explicit_optimized_permutations=eop, 

multiplicity=dd[ 

'multiplicity'] if 'multiplicity' in dd else None, 

other_plane_points=dd[ 

'other_plane_points'] if 'other_plane_points' in dd else None, 

minimum_number_of_points=dd['minimum_number_of_points']) 

 

def __str__(self): 

out = 'Separation plane algorithm with the following reference separation :\n' 

out += '[{}] | [{}] | [{}]'.format( 

'-'.join(str(pp) for pp in [self.point_groups[0]]), 

'-'.join(str(pp) for pp in [self.plane_points]), 

'-'.join(str(pp) for pp in [self.point_groups[1]]), 

) 

return out 

 

 

class CoordinationGeometry(object): 

""" 

Class used to store the ideal representation of a chemical environment or "coordination geometry" 

""" 

 

def __init__(self, mp_symbol, name, alternative_names=None, 

IUPAC_symbol=None, IUCr_symbol=None, coordination=None, 

central_site=np.zeros(3), points=None, solid_angles=None, 

permutations_safe_override=False, 

plane_ordering_override=True, deactivate=False, faces=None, 

edges=None, 

plane_safe_permutations=False, algorithms=None, 

equivalent_indices=None): 

""" 

Initializes one "coordination geometry" according to [Pure Appl. Chem., Vol. 79, No. 10, pp. 1779--1799, 2007] 

and [Acta Cryst. A, Vol. 46, No. 1, pp. 1--11, 1990]. 

:param mp_symbol: Symbol used internally for the coordination geometry. 

:param name: Name of the coordination geometry. 

:param alternative_names: Alternative names for this coordination geometry. 

:param IUPAC_symbol: The IUPAC symbol of this coordination geometry. 

:param IUCr_symbol: The IUCr symbol of this coordination geometry. 

:param coordination: The coordination number of this coordination geometry (number of neighboring atoms). 

:param central_site: The coordinates of the central site of this coordination geometry. 

:param points: The list of the coordinates of all the points of this coordination geometry. 

:param separation_planes: List of separation planes to help set up the permutations 

:param permutation_safe_override: Computes all the permutations if set to True (overrides the plane separation 

algorithms or any other algorithm, for testing purposes) 

:param plane_ordering_override: Computes all the permutations of the plane separation algorithm if set to False 

otherwise, uses the anticlockwise ordering of the separation planes (for testing purposes) 

:param deactivate: deactivates this coordination geometry in the search 

:param faces : list of the faces with their vertices given in a clockwise or anticlockwise order, for drawing 

purposes 

:param : list of edges, for drawing purposes 

""" 

self._mp_symbol = mp_symbol 

self.name = name 

self.alternative_names = alternative_names if alternative_names is not None else [] 

self.IUPACsymbol = IUPAC_symbol 

self.IUCrsymbol = IUCr_symbol 

self.coordination = coordination 

self.central_site = np.array(central_site) 

self.points = points 

self._solid_angles = solid_angles 

self.permutations_safe_override = permutations_safe_override 

self.plane_ordering_override = plane_ordering_override 

self.plane_safe_permutations = plane_safe_permutations 

# self.setup_permutations(permutations) 

self.deactivate = deactivate 

self._faces = faces 

self._edges = edges 

self._algorithms = algorithms 

if points is not None: 

self.centroid = np.mean(np.array(points), axis=0) 

else: 

self.centroid = None 

self.equivalent_indices = equivalent_indices 

 

def as_dict(self): 

return {'mp_symbol': self._mp_symbol, 

'name': self.name, 

'alternative_names': self.alternative_names, 

'IUPAC_symbol': self.IUPACsymbol, 

'IUCr_symbol': self.IUCrsymbol, 

'coordination': self.coordination, 

'central_site': [float(xx) for xx in self.central_site], 

'points': [[float(xx) for xx in pp] for pp in 

self.points] if self.points is not None else None, 

'solid_angles': [float(ang) for ang in 

self._solid_angles] if self._solid_angles is not None else None, 

'deactivate': self.deactivate, 

'_faces': self._faces, 

'_edges': self._edges, 

'_algorithms': [algo.as_dict for algo in 

self._algorithms] if self._algorithms is not None else None, 

'equivalent_indices': self.equivalent_indices} 

 

@classmethod 

def from_dict(cls, dd): 

dec = MontyDecoder() 

return cls(mp_symbol=dd['mp_symbol'], 

name=dd['name'], 

alternative_names=dd['alternative_names'], 

IUPAC_symbol=dd['IUPAC_symbol'], 

IUCr_symbol=dd['IUCr_symbol'], 

coordination=dd['coordination'], 

central_site=dd['central_site'], 

points=dd['points'], 

solid_angles=(dd['solid_angles'] if 'solid_angles' in dd 

else [4.0 * np.pi / dd['coordination']] * dd[ 

'coordination']), 

deactivate=dd['deactivate'], 

faces=dd['_faces'], 

edges=dd['_edges'], 

algorithms=[dec.process_decoded(algo_d) 

for algo_d in dd['_algorithms']] if dd[ 

'_algorithms'] is not None else None, 

equivalent_indices=dd[ 

'equivalent_indices'] if 'equivalent_indices' in dd else None) 

 

def __str__(self): 

symbol = '' 

if self.IUPAC_symbol is not None: 

symbol += ' (IUPAC: {s}'.format(s=self.IUPAC_symbol) 

if self.IUCr_symbol is not None: 

symbol += ' || IUCr: {s})'.format(s=self.IUCr_symbol) 

else: 

symbol += ')' 

elif self.IUCr_symbol is not None: 

symbol += ' (IUCr: {s})'.format(s=self.IUCr_symbol) 

outs = ['Coordination geometry type : {n}{s}\n'.format(n=self.name, 

s=symbol), 

' - coordination number : {c}'.format(c=self.coordination)] 

if self.points is None: 

outs.append('... not yet implemented') 

else: 

outs.append(' - list of points :') 

for pp in self.points: 

outs.append(' - {p}'.format(p=pp)) 

outs.append( 

'------------------------------------------------------------') 

outs.append('') 

 

return '\n'.join(outs) 

 

def __repr__(self): 

symbol = '' 

if self.IUPAC_symbol is not None: 

symbol += ' (IUPAC: {s}'.format(s=self.IUPAC_symbol) 

if self.IUCr_symbol is not None: 

symbol += ' || IUCr: {s})'.format(s=self.IUCr_symbol) 

else: 

symbol += ')' 

elif self.IUCr_symbol is not None: 

symbol += ' (IUCr: {s})'.format(s=self.IUCr_symbol) 

outs = ['Coordination geometry type : {n}{s}\n'.format(n=self.name, 

s=symbol), 

' - coordination number : {c}'.format(c=self.coordination)] 

outs.append( 

'------------------------------------------------------------') 

outs.append('') 

return '\n'.join(outs) 

 

def __len__(self): 

return self.coordination 

 

def set_permutations_safe_override(self, permutations_safe_override): 

self.permutations_safe_override = permutations_safe_override 

# self.setup_permutations() 

 

@property 

def coordination_number(self): 

""" 

Returns the coordination number of this coordination geometry. 

""" 

return self.coordination 

 

@property 

def mp_symbol(self): 

""" 

Returns the MP symbol of this coordination geometry. 

""" 

return self._mp_symbol 

 

def get_coordination_number(self): 

""" 

Returns the coordination number of this coordination geometry. 

""" 

return self.coordination 

 

def is_implemented(self): 

""" 

Returns True if this coordination geometry is implemented. 

""" 

return bool(self.points) 

 

def get_name(self): 

""" 

Returns the name of this coordination geometry. 

""" 

return self.name 

 

@property 

def IUPAC_symbol(self): 

""" 

Returns the IUPAC symbol of this coordination geometry. 

""" 

return self.IUPACsymbol 

 

@property 

def IUPAC_symbol_str(self): 

""" 

Returns a string representation of the IUPAC symbol of this coordination geometry. 

""" 

return str(self.IUPACsymbol) 

 

@property 

def IUCr_symbol(self): 

""" 

Returns the IUCr symbol of this coordination geometry. 

""" 

return self.IUCrsymbol 

 

@property 

def IUCr_symbol_str(self): 

""" 

Returns a string representation of the IUCr symbol of this coordination geometry. 

""" 

return str(self.IUCrsymbol) 

 

@property 

def number_of_permutations(self): 

""" 

Returns the number of permutations of this coordination geometry. 

""" 

if self.permutations_safe_override: 

return factorial(self.coordination) 

elif self.permutations is None: 

return factorial(self.coordination) 

return len(self.permutations) 

 

def ref_permutation(self, permutation): 

perms = [] 

for eqv_indices in self.equivalent_indices: 

perms.append(tuple([permutation[ii] for ii in eqv_indices])) 

perms.sort() 

return perms[0] 

 

@property 

def algorithms(self): 

""" 

Returns the list of algorithms that are used to identify this coordination geometry. 

""" 

return self._algorithms 

 

def get_central_site(self): 

""" 

Returns the central site of this coordination geometry. 

""" 

return self.central_site 

 

def faces(self, sites, permutation=None): 

""" 

Returns the list of faces of this coordination geometry. Each face is given as a 

list of its vertices coordinates. 

""" 

if permutation is None: 

coords = [site.coords for site in sites] 

else: 

coords = [sites[ii].coords for ii in permutation] 

return [[coords[ii] for ii in f] for f in self._faces] 

 

def edges(self, sites, permutation=None): 

""" 

Returns the list of edges of this coordination geometry. Each edge is given as a 

list of its end vertices coordinates. 

""" 

if permutation is None: 

coords = [site.coords for site in sites] 

else: 

coords = [sites[ii].coords for ii in permutation] 

return [[coords[ii] for ii in e] for e in self._edges] 

 

def solid_angles(self, permutation=None): 

""" 

Returns the list of "perfect" solid angles Each edge is given as a 

list of its end vertices coordinates. 

""" 

if permutation is None: 

return self._solid_angles 

else: 

return [self._solid_angles[ii] for ii in permutation] 

 

def get_pmeshes(self, sites, permutation=None): 

""" 

Returns the pmesh strings used for jmol to show this geometry. 

""" 

pmeshes = [] 

# _vertices = [site.coords for site in sites] 

if permutation is None: 

_vertices = [site.coords for site in sites] 

else: 

_vertices = [sites[ii].coords for ii in permutation] 

_face_centers = [] 

number_of_faces = 0 

for face in self._faces: 

if len(face) in [3, 4]: 

number_of_faces += 1 

else: 

number_of_faces += len(face) 

 

_face_centers.append(np.array([np.mean([_vertices[face_vertex][ii] 

for face_vertex in face]) 

for ii in range(3)])) 

 

out = '{}\n'.format(len(_vertices) + len(_face_centers)) 

for vv in _vertices: 

out += '{:15.8f} {:15.8f} {:15.8f}\n'.format(vv[0], vv[1], vv[2]) 

for fc in _face_centers: 

out += '{:15.8f} {:15.8f} {:15.8f}\n'.format(fc[0], fc[1], fc[2]) 

out += '{:d}\n'.format(number_of_faces) 

for iface, face in enumerate(self._faces): 

if len(face) == 3: 

out += '4\n' 

elif len(face) == 4: 

out += '5\n' 

else: 

for ii in range(len(face)): 

out += '4\n' 

out += '{:d}\n'.format(len(_vertices) + iface) 

out += '{:d}\n'.format(face[ii]) 

out += '{:d}\n'.format(face[np.mod(ii + 1, len(face))]) 

out += '{:d}\n'.format(len(_vertices) + iface) 

if len(face) in [3, 4]: 

for face_vertex in face: 

out += '{:d}\n'.format(face_vertex) 

out += '{:d}\n'.format(face[0]) 

pmeshes.append({"pmesh_string": out}) 

return pmeshes 

 

def get_pmeshes_test(self, sites, permutation=None): 

""" 

Returns the pmesh strings used for jmol to show this geometry. 

""" 

pmeshes = [] 

_vertices = [site.coords for site in sites] 

# if permutation is None: 

# _vertices = [site.coords for site in sites] 

# else: 

# _vertices = [sites[ii].coords for ii in permutation] 

_face_centers = [] 

number_of_faces = 0 

for face in self._faces: 

if len(face) in [3, 4]: 

number_of_faces += 1 

else: 

number_of_faces += len(face) 

 

_face_centers.append(np.array([np.mean([_vertices[face_vertex][ii] 

for face_vertex in face]) 

for ii in range(3)])) 

 

out = '{}\n'.format(len(_vertices) + len(_face_centers)) 

for vv in _vertices: 

out += '{:15.8f} {:15.8f} {:15.8f}\n'.format(vv[0], vv[1], vv[2]) 

for fc in _face_centers: 

out += '{:15.8f} {:15.8f} {:15.8f}\n'.format(fc[0], fc[1], fc[2]) 

out += '{:d}\n'.format(number_of_faces) 

for iface, face in enumerate(self._faces): 

if len(face) == 3: 

out += '4\n' 

elif len(face) == 4: 

out += '5\n' 

else: 

for ii in range(len(face)): 

out += '4\n' 

out += '{:d}\n'.format(len(_vertices) + iface) 

out += '{:d}\n'.format(permutation[face[ii]]) 

out += '{:d}\n'.format( 

permutation[face[np.mod(ii + 1, len(face))]]) 

out += '{:d}\n'.format(len(_vertices) + iface) 

if len(face) in [3, 4]: 

for face_vertex in face: 

out += '{:d}\n'.format(permutation[face_vertex]) 

out += '{:d}\n'.format(permutation[face[0]]) 

pmeshes.append({"pmesh_string": out}) 

return pmeshes 

 

 

class AllCoordinationGeometries(dict): 

""" 

Class used to store all the reference "coordination geometries" (list with instances of the CoordinationGeometry 

classes) 

""" 

 

def __init__(self, permutations_safe_override=False, only_symbols=None): 

""" 

Initializes the list of Coordination Geometries 

:param permutations_safe_override: 

:param only_symbols: 

""" 

dict.__init__(self) 

self.cg_list = list() 

if only_symbols is None: 

f = open( 

'{}/coordination_geometries_files/allcg.txt'.format(module_dir), 

'r') 

data = f.readlines() 

f.close() 

for line in data: 

cg_file = '{}/{}'.format(module_dir, line.strip()) 

f = open(cg_file, 'r') 

dd = json.load(f) 

f.close() 

self.cg_list.append(CoordinationGeometry.from_dict(dd)) 

else: 

for symbol in only_symbols: 

cg_file = '{}/coordination_geometries_files/{}.json'.format( 

module_dir, symbol) 

f = open(cg_file, 'r') 

dd = json.load(f) 

f.close() 

self.cg_list.append(CoordinationGeometry.from_dict(dd)) 

 

self.cg_list.append(CoordinationGeometry(UNKNOWN_ENVIRONMENT_SYMBOL, 

"Unknown environment", 

deactivate=True)) 

self.cg_list.append(CoordinationGeometry(UNCLEAR_ENVIRONMENT_SYMBOL, 

"Unclear environment", 

deactivate=True)) 

if permutations_safe_override: 

for cg in self.cg_list: 

cg.set_permutations_safe_override(True) 

 

def __getitem__(self, key): 

return self.get_geometry_from_mp_symbol(key) 

 

def __repr__(self): 

""" 

Returns a string with the list of coordination geometries. 

""" 

outs = ['', '#=================================#', 

'# List of coordination geometries #', 

'#=================================#', ''] 

for cg in self.cg_list: 

outs.append(repr(cg)) 

 

return '\n'.join(outs) 

 

def __str__(self): 

""" 

Returns a string with the list of coordination geometries that are implemented. 

""" 

outs = ['', '#=======================================================#', 

'# List of coordination geometries currently implemented #', 

'#=======================================================#', ''] 

for cg in self.cg_list: 

if cg.is_implemented(): 

outs.append(str(cg)) 

 

return '\n'.join(outs) 

 

def get_geometries(self, coordination=None, returned='cg'): 

""" 

Returns a list of coordination geometries with the given coordination number. 

:param coordination: The coordination number of which the list of coordination geometries are returned. 

""" 

geom = list() 

if coordination is None: 

for gg in self.cg_list: 

if returned == 'cg': 

geom.append(gg) 

elif returned == 'mp_symbol': 

geom.append(gg.mp_symbol) 

else: 

for gg in self.cg_list: 

if gg.get_coordination_number() == coordination: 

if returned == 'cg': 

geom.append(gg) 

elif returned == 'mp_symbol': 

geom.append(gg.mp_symbol) 

return geom 

 

def get_symbol_name_mapping(self, coordination=None): 

geom = {} 

if coordination is None: 

for gg in self.cg_list: 

geom[gg.mp_symbol] = gg.name 

else: 

for gg in self.cg_list: 

if gg.get_coordination_number() == coordination: 

geom[gg.mp_symbol] = gg.name 

return geom 

 

def get_symbol_cn_mapping(self, coordination=None): 

geom = {} 

if coordination is None: 

for gg in self.cg_list: 

geom[gg.mp_symbol] = gg.coordination_number 

else: 

for gg in self.cg_list: 

if gg.get_coordination_number() == coordination: 

geom[gg.mp_symbol] = gg.coordination_number 

return geom 

 

def get_implemented_geometries(self, coordination=None, returned='cg', 

include_deactivated=False): 

""" 

Returns a list of the implemented coordination geometries with the given coordination number. 

:param coordination: The coordination number of which the list of implemented coordination geometries 

are returned. 

""" 

geom = list() 

if coordination is None: 

for gg in self.cg_list: 

if gg.points is not None and ( 

(not gg.deactivate) or include_deactivated): 

if returned == 'cg': 

geom.append(gg) 

elif returned == 'mp_symbol': 

geom.append(gg.mp_symbol) 

else: 

for gg in self.cg_list: 

if gg.get_coordination_number() == coordination and gg.points is not None and \ 

((not gg.deactivate) or include_deactivated): 

if returned == 'cg': 

geom.append(gg) 

elif returned == 'mp_symbol': 

geom.append(gg.mp_symbol) 

return geom 

 

def get_not_implemented_geometries(self, coordination=None, 

returned='mp_symbol'): 

""" 

Returns a list of the implemented coordination geometries with the given coordination number. 

:param coordination: The coordination number of which the list of implemented coordination geometries 

are returned. 

""" 

geom = list() 

if coordination is None: 

for gg in self.cg_list: 

if gg.points is None: 

if returned == 'cg': 

geom.append(gg) 

elif returned == 'mp_symbol': 

geom.append(gg.mp_symbol) 

else: 

for gg in self.cg_list: 

if gg.get_coordination_number() == coordination and gg.points is None: 

if returned == 'cg': 

geom.append(gg) 

elif returned == 'mp_symbol': 

geom.append(gg.mp_symbol) 

return geom 

 

def get_geometry_from_name(self, name): 

""" 

Returns the coordination geometry of the given name. 

:param name: The name of the coordination geometry. 

""" 

for gg in self.cg_list: 

if gg.name == name or name in gg.alternative_names: 

return gg 

raise LookupError( 

'No coordination geometry found with name "{name}"'.format( 

name=name)) 

 

def get_geometry_from_IUPAC_symbol(self, IUPAC_symbol): 

""" 

Returns the coordination geometry of the given IUPAC symbol. 

:param IUPAC_symbol: The IUPAC symbol of the coordination geometry. 

""" 

for gg in self.cg_list: 

if gg.IUPAC_symbol == IUPAC_symbol: 

return gg 

raise LookupError( 

'No coordination geometry found with IUPAC symbol "{symbol}"'.format( 

symbol=IUPAC_symbol)) 

 

def get_geometry_from_IUCr_symbol(self, IUCr_symbol): 

""" 

Returns the coordination geometry of the given IUCr symbol. 

:param IUCr_symbol: The IUCr symbol of the coordination geometry. 

""" 

for gg in self.cg_list: 

if gg.IUCr_symbol == IUCr_symbol: 

return gg 

raise LookupError( 

'No coordination geometry found with IUCr symbol "{symbol}"'.format( 

symbol=IUCr_symbol)) 

 

def get_geometry_from_mp_symbol(self, mp_symbol): 

""" 

Returns the coordination geometry of the given mp_symbol. 

:param mp_symbol: The mp_symbol of the coordination geometry. 

""" 

for gg in self.cg_list: 

if gg.mp_symbol == mp_symbol: 

return gg 

raise LookupError( 

'No coordination geometry found with mp_symbol "{symbol}"'.format( 

symbol=mp_symbol)) 

 

def is_a_valid_coordination_geometry(self, mp_symbol=None, 

IUPAC_symbol=None, IUCr_symbol=None, 

name=None, cn=None): 

""" 

Checks whether a given coordination geometry is valid (exists) and whether the parameters are coherent with 

each other. 

:param IUPAC_symbol: 

:param IUCr_symbol: 

:param name: 

:param cn: 

:param mp_symbol: The mp_symbol of the coordination geometry. 

""" 

if name is not None: 

raise NotImplementedError( 

'is_a_valid_coordination_geometry not implemented for the name') 

if mp_symbol is None and IUPAC_symbol is None and IUCr_symbol is None: 

raise SyntaxError( 

'missing argument for is_a_valid_coordination_geometry : at least one of mp_symbol, ' 

'IUPAC_symbol and IUCr_symbol must be passed to the function') 

if mp_symbol is not None: 

try: 

cg = self.get_geometry_from_mp_symbol(mp_symbol) 

if IUPAC_symbol is not None: 

if IUPAC_symbol != cg.IUPAC_symbol: 

return False 

if IUCr_symbol is not None: 

if IUCr_symbol != cg.IUCr_symbol: 

return False 

if cn is not None: 

if int(cn) != int(cg.coordination_number): 

return False 

return True 

except LookupError: 

return False 

elif IUPAC_symbol is not None: 

try: 

cg = self.get_geometry_from_IUPAC_symbol(IUPAC_symbol) 

if IUCr_symbol is not None: 

if IUCr_symbol != cg.IUCr_symbol: 

return False 

if cn is not None: 

if cn != cg.coordination_number: 

return False 

return True 

except LookupError: 

return False 

elif IUCr_symbol is not None: 

try: 

cg = self.get_geometry_from_IUCr_symbol(IUCr_symbol) 

if cn is not None: 

if cn != cg.coordination_number: 

return False 

return True 

except LookupError: 

return True 

raise Exception('Should not be here !') 

 

def pretty_print(self, type='implemented_geometries', maxcn=8): 

if type == 'all_geometries_latex_images': 

mystring = '' 

for cn in range(1, maxcn + 1): 

mystring += '\section*{{Coordination {cn}}}\n\n'.format(cn=cn) 

for cg in self.get_implemented_geometries(coordination=cn, 

returned='cg'): 

mystring += '\subsubsection*{{{mp} : {name}}}\n\n'.format( 

mp=cg.mp_symbol, name=cg.get_name()) 

mystring += 'IUPAC : {iupac}\n\nIUCr : {iucr}\n\n'.format( 

iupac=cg.IUPAC_symbol, iucr=cg.IUCr_symbol) 

mystring += '\\begin{center}\n' 

mystring += '\\includegraphics[scale=0.15]{{images/{let}_{cif}.png}}\n'.format( 

let=cg.mp_symbol.split(':')[0], 

cif=cg.mp_symbol.split(':')[1]) 

mystring += '\\end{center}\n\n' 

for cg in self.get_not_implemented_geometries(cn, 

returned='cg'): 

mystring += '\subsubsection*{{{mp} : {name}}}\n\n'.format( 

mp=cg.mp_symbol, name=cg.get_name()) 

mystring += 'IUPAC : {iupac}\n\nIUCr : {iucr}\n\n'.format( 

iupac=cg.IUPAC_symbol, iucr=cg.IUCr_symbol) 

elif type == 'all_geometries_latex': 

mystring = '' 

for cn in range(1, maxcn + 1): 

mystring += '\subsection*{{Coordination {cn}}}\n\n'.format( 

cn=cn) 

mystring += '\\begin{itemize}\n' 

for cg in self.get_implemented_geometries(coordination=cn, 

returned='cg'): 

mystring += '\\item {mp} $\\rightarrow$ {name} '.format( 

mp=cg.mp_symbol.replace('_', 

'\\_'), 

name=cg.get_name()) 

mystring += '(IUPAC : {iupac} - IUCr : {iucr})\n'.format( 

iupac=cg.IUPAC_symbol_str, 

iucr=cg.IUCr_symbol_str.replace('[', '$[$').replace(']', 

'$]$')) 

for cg in self.get_not_implemented_geometries(cn, 

returned='cg'): 

mystring += '\\item {mp} $\\rightarrow$ {name} '.format( 

mp=cg.mp_symbol.replace('_', 

'\\_'), 

name=cg.get_name()) 

mystring += '(IUPAC : {iupac} - IUCr : {iucr})\n'.format( 

iupac=cg.IUPAC_symbol_str, 

iucr=cg.IUCr_symbol_str.replace('[', '$[$').replace(']', 

'$]$')) 

mystring += '\\end{itemize}\n\n' 

else: 

mystring = '+-------------------------+\n| Coordination geometries |\n+-------------------------+\n\n' 

for cn in range(1, maxcn + 1): 

mystring += '==>> CN = {cn} <<==\n'.format(cn=cn) 

if type == 'implemented_geometries': 

for cg in self.get_implemented_geometries(coordination=cn): 

mystring += ' - {mp} : {name}\n'.format(mp=cg.mp_symbol, 

name=cg.get_name()) 

elif type == 'all_geometries': 

for cg in self.get_geometries(coordination=cn): 

mystring += ' - {mp} : {name}\n'.format(mp=cg.mp_symbol, 

name=cg.get_name()) 

mystring += '\n' 

return mystring