def test_serialization(self): BaTiO3_se_fpath = os.path.join( self.TEST_FILES_DIR, "chemenv", "structure_environments_files", "se_mp-5020.json", ) with open(BaTiO3_se_fpath) as f: dd = json.load(f) se = StructureEnvironments.from_dict(dd) lse = LightStructureEnvironments.from_structure_environments( strategy=SimplestChemenvStrategy(), structure_environments=se ) cf = ConnectivityFinder() sc = cf.get_structure_connectivity(light_structure_environments=lse) sc_from_dict = StructureConnectivity.from_dict(sc.as_dict()) assert sc.light_structure_environments == sc_from_dict.light_structure_environments assert set(sc._graph.nodes()) == set(sc_from_dict._graph.nodes()) assert set(sc._graph.edges()) == set(sc_from_dict._graph.edges()) sc_from_json = StructureConnectivity.from_dict(json.loads(json.dumps(sc.as_dict()))) assert sc.light_structure_environments == sc_from_json.light_structure_environments assert set(sc._graph.nodes()) == set(sc_from_json._graph.nodes()) assert set(sc._graph.edges()) == set(sc_from_json._graph.edges()) if bson is not None: bson_data = bson.BSON.encode(sc.as_dict()) sc_from_bson = StructureConnectivity.from_dict(bson_data.decode()) assert sc.light_structure_environments == sc_from_bson.light_structure_environments assert set(sc._graph.nodes()) == set(sc_from_bson._graph.nodes()) assert set(sc._graph.edges()) == set(sc_from_bson._graph.edges())
def setUp(self): self.lgf = LocalGeometryFinder() self.lgf.setup_parameters(centering_type='standard') self.strategies = [ SimplestChemenvStrategy(), SimpleAbundanceChemenvStrategy() ]
def getSimplestChemenvStrategy(struct): # Setup the local geometry finder lgf = LocalGeometryFinder() lgf.setup_parameters(centering_type='centroid', include_central_site_in_centroid=True) #you can also save the logging to a file, just remove the comment # logging.basicConfig(#filename='chemenv_structure_environments.log', # format='%(levelname)s:%(module)s:%(funcName)s:%(message)s', # level=logging.DEBUG) lgf.setup_structure(structure=struct) se = lgf.compute_structure_environments(maximum_distance_factor=1.41, only_cations=False) strategy = SimplestChemenvStrategy(distance_cutoff=1.4, angle_cutoff=0.3) lse = LightStructureEnvironments.from_structure_environments( strategy=strategy, structure_environments=se) return lse.coordination_environments
def test_read_write_structure_environments(self): f = open( "{}/{}".format(json_files_dir, "test_T--4_FePO4_icsd_4266.json"), "r") dd = json.load(f) f.close() atom_indices = dd["atom_indices"] struct = Structure.from_dict(dd["structure"]) self.lgf.setup_structure(struct) se = self.lgf.compute_structure_environments( only_indices=atom_indices, maximum_distance_factor=2.25, get_from_hints=True) f = open("tmp_dir/se.json", "w") json.dump(se.as_dict(), f) f.close() f = open("tmp_dir/se.json", "r") dd = json.load(f) f.close() se2 = StructureEnvironments.from_dict(dd) self.assertEqual(se, se2) strategy = SimplestChemenvStrategy() lse = LightStructureEnvironments.from_structure_environments( structure_environments=se, strategy=strategy, valences="undefined") f = open("tmp_dir/lse.json", "w") json.dump(lse.as_dict(), f) f.close() f = open("tmp_dir/lse.json", "r") dd = json.load(f) f.close() lse2 = LightStructureEnvironments.from_dict(dd) self.assertEqual(lse, lse2)
def test_read_structure_environments(self): f = open("{}/{}".format(json_files_dir, 'test_T--4_FePO4_icsd_4266.json'), 'r') dd = json.load(f) f.close() atom_indices = dd['atom_indices'] struct = Structure.from_dict(dd['structure']) self.lgf.setup_structure(struct) se = self.lgf.compute_structure_environments_detailed_voronoi(only_indices=atom_indices, maximum_distance_factor=2.25) f = open('tmp_dir/se.json', 'w') json.dump(se.as_dict(), f) f.close() f = open('tmp_dir/se.json', 'r') dd = json.load(f) f.close() se2 = StructureEnvironments.from_dict(dd) self.assertEqual(se, se2) _strategy = SimplestChemenvStrategy() light_se = LightStructureEnvironments(_strategy, se) f = open('tmp_dir/light_se.json', 'w') json.dump(light_se.as_dict(), f) f.close() f = open('tmp_dir/light_se.json', 'r') dd = json.load(f) f.close() light_se2 = LightStructureEnvironments.from_dict(dd) self.assertEqual(light_se._strategy, light_se2._strategy) self.assertEqual(light_se._structure, light_se2._structure) self.assertEqual(light_se._bva_valences, light_se2._bva_valences) self.assertEqual(light_se._coordination_environments, light_se2._coordination_environments) self.assertEqual(light_se._neighbors, light_se2._neighbors) self.assertEqual(light_se, light_se2)
def test_read_write_structure_environments(self): f = open( "{}/{}".format(json_files_dir, 'test_T--4_FePO4_icsd_4266.json'), 'r') dd = json.load(f) f.close() atom_indices = dd['atom_indices'] struct = Structure.from_dict(dd['structure']) self.lgf.setup_structure(struct) se = self.lgf.compute_structure_environments( only_indices=atom_indices, maximum_distance_factor=2.25) f = open('tmp_dir/se.json', 'w') json.dump(se.as_dict(), f) f.close() f = open('tmp_dir/se.json', 'r') dd = json.load(f) f.close() se2 = StructureEnvironments.from_dict(dd) self.assertEqual(se, se2) strategy = SimplestChemenvStrategy() lse = LightStructureEnvironments.from_structure_environments( structure_environments=se, strategy=strategy, valences='undefined') f = open('tmp_dir/lse.json', 'w') json.dump(lse.as_dict(), f) f.close() f = open('tmp_dir/lse.json', 'r') dd = json.load(f) f.close() lse2 = LightStructureEnvironments.from_dict(dd) self.assertEqual(lse, lse2)
def from_preset(preset): """ Use a standard collection of CE types and choose your ChemEnv neighbor-finding strategy. Args: preset (str): preset types ("simple" or "multi_weights"). Returns: ChemEnvSiteFingerprint object from a preset. """ cetypes = [ 'S:1', 'L:2', 'A:2', 'TL:3', 'TY:3', 'TS:3', 'T:4', 'S:4', 'SY:4', 'SS:4', 'PP:5', 'S:5', 'T:5', 'O:6', 'T:6', 'PP:6', 'PB:7', 'ST:7', 'ET:7', 'FO:7', 'C:8', 'SA:8', 'SBT:8', 'TBT:8', 'DD:8', 'DDPN:8', 'HB:8', 'BO_1:8', 'BO_2:8', 'BO_3:8', 'TC:9', 'TT_1:9', 'TT_2:9', 'TT_3:9', 'HD:9', 'TI:9', 'SMA:9', 'SS:9', 'TO_1:9', 'TO_2:9', 'TO_3:9', 'PP:10', 'PA:10', 'SBSA:10', 'MI:10', 'S:10', 'H:10', 'BS_1:10', 'BS_2:10', 'TBSA:10', 'PCPA:11', 'H:11', 'SH:11', 'CO:11', 'DI:11', 'I:12', 'PBP:12', 'TT:12', 'C:12', 'AC:12', 'SC:12', 'S:12', 'HP:12', 'HA:12', 'SH:13', 'DD:20' ] lgf = LocalGeometryFinder() lgf.setup_parameters( centering_type='centroid', include_central_site_in_centroid=True, structure_refinement=lgf.STRUCTURE_REFINEMENT_NONE) if preset == "simple": return ChemEnvSiteFingerprint( cetypes, SimplestChemenvStrategy(distance_cutoff=1.4, angle_cutoff=0.3), lgf) elif preset == "multi_weights": return ChemEnvSiteFingerprint( cetypes, MultiWeightsChemenvStrategy.stats_article_weights_parameters(), lgf) else: raise RuntimeError('unknown neighbor-finding strategy preset.')
def test_strategies(self): simplest_strategy_1 = SimplestChemenvStrategy() simplest_strategy_2 = SimplestChemenvStrategy(distance_cutoff=1.5, angle_cutoff=0.5) self.assertFalse(simplest_strategy_1 == simplest_strategy_2) simplest_strategy_1_from_dict = SimplestChemenvStrategy.from_dict( simplest_strategy_1.as_dict()) self.assertTrue(simplest_strategy_1, simplest_strategy_1_from_dict) effective_csm_estimator = { "function": "power2_inverse_decreasing", "options": { "max_csm": 8.0 }, } self_csm_weight = SelfCSMNbSetWeight() surface_definition = { "type": "standard_elliptic", "distance_bounds": { "lower": 1.1, "upper": 1.9 }, "angle_bounds": { "lower": 0.1, "upper": 0.9 }, } surface_definition_2 = { "type": "standard_elliptic", "distance_bounds": { "lower": 1.1, "upper": 1.9 }, "angle_bounds": { "lower": 0.1, "upper": 0.95 }, } da_area_weight = DistanceAngleAreaNbSetWeight( weight_type="has_intersection", surface_definition=surface_definition, nb_sets_from_hints="fallback_to_source", other_nb_sets="0_weight", additional_condition=DistanceAngleAreaNbSetWeight.AC.ONLY_ACB, ) da_area_weight_2 = DistanceAngleAreaNbSetWeight( weight_type="has_intersection", surface_definition=surface_definition_2, nb_sets_from_hints="fallback_to_source", other_nb_sets="0_weight", additional_condition=DistanceAngleAreaNbSetWeight.AC.ONLY_ACB, ) weight_estimator = { "function": "smootherstep", "options": { "delta_csm_min": 0.5, "delta_csm_max": 3.0 }, } symmetry_measure_type = "csm_wcs_ctwcc" delta_weight = DeltaCSMNbSetWeight( effective_csm_estimator=effective_csm_estimator, weight_estimator=weight_estimator, symmetry_measure_type=symmetry_measure_type, ) bias_weight = CNBiasNbSetWeight.linearly_equidistant(weight_cn1=1.0, weight_cn13=4.0) bias_weight_2 = CNBiasNbSetWeight.linearly_equidistant(weight_cn1=1.0, weight_cn13=5.0) angle_weight = AngleNbSetWeight() nad_weight = NormalizedAngleDistanceNbSetWeight( average_type="geometric", aa=1, bb=1) multi_weights_strategy_1 = MultiWeightsChemenvStrategy( dist_ang_area_weight=da_area_weight, self_csm_weight=self_csm_weight, delta_csm_weight=delta_weight, cn_bias_weight=bias_weight, angle_weight=angle_weight, normalized_angle_distance_weight=nad_weight, symmetry_measure_type=symmetry_measure_type, ) multi_weights_strategy_2 = MultiWeightsChemenvStrategy( dist_ang_area_weight=da_area_weight, self_csm_weight=self_csm_weight, delta_csm_weight=delta_weight, cn_bias_weight=bias_weight_2, angle_weight=angle_weight, normalized_angle_distance_weight=nad_weight, symmetry_measure_type=symmetry_measure_type, ) multi_weights_strategy_3 = MultiWeightsChemenvStrategy( dist_ang_area_weight=da_area_weight_2, self_csm_weight=self_csm_weight, delta_csm_weight=delta_weight, cn_bias_weight=bias_weight, angle_weight=angle_weight, normalized_angle_distance_weight=nad_weight, symmetry_measure_type=symmetry_measure_type, ) multi_weights_strategy_1_from_dict = MultiWeightsChemenvStrategy.from_dict( multi_weights_strategy_1.as_dict()) self.assertTrue( multi_weights_strategy_1 == multi_weights_strategy_1_from_dict) self.assertFalse(simplest_strategy_1 == multi_weights_strategy_1) self.assertFalse(multi_weights_strategy_1 == multi_weights_strategy_2) self.assertFalse(multi_weights_strategy_1 == multi_weights_strategy_3) self.assertFalse(multi_weights_strategy_2 == multi_weights_strategy_3)
def test_light_structure_environments(self): with ScratchDir("."): f = open("{}/{}".format(se_files_dir, 'se_mp-7000.json'), 'r') dd = json.load(f) f.close() se = StructureEnvironments.from_dict(dd) strategy = SimplestChemenvStrategy() lse = LightStructureEnvironments.from_structure_environments( structure_environments=se, strategy=strategy, valences='undefined') isite = 6 nb_set = lse.neighbors_sets[isite][0] neighb_coords = [ np.array([0.2443798, 1.80409653, -1.13218359]), np.array([1.44020353, 1.11368738, 1.13218359]), np.array([2.75513098, 2.54465207, -0.70467298]), np.array([0.82616785, 3.65833945, 0.70467298]) ] neighb_indices = [0, 3, 5, 1] neighb_images = [[0, 0, -1], [0, 0, 0], [0, 0, -1], [0, 0, 0]] np.testing.assert_array_almost_equal(neighb_coords, nb_set.neighb_coords) np.testing.assert_array_almost_equal( neighb_coords, [s.coords for s in nb_set.neighb_sites]) nb_sai = nb_set.neighb_sites_and_indices np.testing.assert_array_almost_equal( neighb_coords, [sai['site'].coords for sai in nb_sai]) np.testing.assert_array_almost_equal( neighb_indices, [sai['index'] for sai in nb_sai]) nb_iai = nb_set.neighb_indices_and_images np.testing.assert_array_almost_equal( neighb_indices, [iai['index'] for iai in nb_iai]) np.testing.assert_array_equal( neighb_images, [iai['image_cell'] for iai in nb_iai]) self.assertEqual(nb_set.__len__(), 4) self.assertEqual(nb_set.__hash__(), 4) self.assertFalse(nb_set.__ne__(nb_set)) self.assertEqual( nb_set.__str__(), 'Neighbors Set for site #6 :\n' ' - Coordination number : 4\n' ' - Neighbors sites indices : 0, 1, 2, 3\n') stats = lse.get_statistics() neighbors = lse.strategy.get_site_neighbors( site=lse.structure[isite]) self.assertArrayAlmostEqual( neighbors[0].coords, np.array([0.2443798, 1.80409653, -1.13218359])) self.assertArrayAlmostEqual( neighbors[1].coords, np.array([1.44020353, 1.11368738, 1.13218359])) self.assertArrayAlmostEqual( neighbors[2].coords, np.array([2.75513098, 2.54465207, -0.70467298])) self.assertArrayAlmostEqual( neighbors[3].coords, np.array([0.82616785, 3.65833945, 0.70467298])) equiv_site_index_and_transform = lse.strategy.equivalent_site_index_and_transform( neighbors[0]) self.assertEqual(equiv_site_index_and_transform[0], 0) self.assertArrayAlmostEqual(equiv_site_index_and_transform[1], [0.0, 0.0, 0.0]) self.assertArrayAlmostEqual(equiv_site_index_and_transform[2], [0.0, 0.0, -1.0]) equiv_site_index_and_transform = lse.strategy.equivalent_site_index_and_transform( neighbors[1]) self.assertEqual(equiv_site_index_and_transform[0], 3) self.assertArrayAlmostEqual(equiv_site_index_and_transform[1], [0.0, 0.0, 0.0]) self.assertArrayAlmostEqual(equiv_site_index_and_transform[2], [0.0, 0.0, 0.0]) self.assertEqual(stats['atom_coordination_environments_present'], {'Si': { 'T:4': 3.0 }}) self.assertEqual(stats['coordination_environments_atom_present'], {'T:4': { 'Si': 3.0 }}) self.assertEqual( stats['fraction_atom_coordination_environments_present'], {'Si': { 'T:4': 1.0 }}) site_info_ce = lse.get_site_info_for_specie_ce(specie=Species( 'Si', 4), ce_symbol='T:4') np.testing.assert_array_almost_equal(site_info_ce['fractions'], [1.0, 1.0, 1.0]) np.testing.assert_array_almost_equal(site_info_ce['csms'], [ 0.009887784240541068, 0.009887786546730826, 0.009887787384385317 ]) self.assertEqual(site_info_ce['isites'], [6, 7, 8]) site_info_allces = lse.get_site_info_for_specie_allces( specie=Species('Si', 4)) self.assertEqual(site_info_allces['T:4'], site_info_ce) self.assertFalse(lse.contains_only_one_anion('I-')) self.assertFalse(lse.contains_only_one_anion_atom('I')) self.assertTrue( lse.site_contains_environment(isite=isite, ce_symbol='T:4')) self.assertFalse( lse.site_contains_environment(isite=isite, ce_symbol='S:4')) self.assertFalse( lse.structure_contains_atom_environment(atom_symbol='Si', ce_symbol='S:4')) self.assertTrue( lse.structure_contains_atom_environment(atom_symbol='Si', ce_symbol='T:4')) self.assertFalse( lse.structure_contains_atom_environment(atom_symbol='O', ce_symbol='T:4')) self.assertTrue(lse.uniquely_determines_coordination_environments) self.assertFalse(lse.__ne__(lse)) envs = lse.strategy.get_site_coordination_environments( lse.structure[6]) self.assertEqual(len(envs), 1) self.assertEqual(envs[0][0], 'T:4') multi_strategy = MultiWeightsChemenvStrategy.stats_article_weights_parameters( ) lse_multi = LightStructureEnvironments.from_structure_environments( strategy=multi_strategy, structure_environments=se, valences='undefined') self.assertAlmostEqual( lse_multi.coordination_environments[isite][0]['csm'], 0.009887784240541068) self.assertAlmostEqual( lse_multi.coordination_environments[isite][0]['ce_fraction'], 1.0) self.assertEqual( lse_multi.coordination_environments[isite][0]['ce_symbol'], 'T:4')
def test_coordination_sequences(self): BaTiO3_se_fpath = os.path.join( self.TEST_FILES_DIR, "chemenv", "structure_environments_files", "se_mp-5020.json", ) with open(BaTiO3_se_fpath, "r") as f: dd = json.load(f) se = StructureEnvironments.from_dict(dd) lse = LightStructureEnvironments.from_structure_environments( strategy=SimplestChemenvStrategy(), structure_environments=se) cf = ConnectivityFinder() sc = cf.get_structure_connectivity(light_structure_environments=lse) ccs_oct = sc.get_connected_components(environments_symbols=["O:6"]) ccs_all = sc.get_connected_components( environments_symbols=["O:6", "C:12"]) assert len(ccs_oct) == 1 assert len(ccs_all) == 1 cc_oct = ccs_oct[0] cc_all = ccs_all[0] cc_oct_node = list(cc_oct.graph.nodes())[0] cseq = cc_oct.coordination_sequence(source_node=cc_oct_node, path_size=6) assert cseq == {1: 6, 2: 18, 3: 38, 4: 66, 5: 102, 6: 146} cc_all_oct_node = next(n for n in cc_all.graph.nodes() if n.coordination_environment == "O:6") cc_all_cuboct_node = next(n for n in cc_all.graph.nodes() if n.coordination_environment == "C:12") cseq = cc_all.coordination_sequence(source_node=cc_all_oct_node, path_size=6) assert cseq == {1: 14, 2: 74, 3: 218, 4: 442, 5: 746, 6: 1130} cseq = cc_all.coordination_sequence(source_node=cc_all_cuboct_node, path_size=6) assert cseq == {1: 26, 2: 122, 3: 298, 4: 554, 5: 890, 6: 1306} cseq = cc_all.coordination_sequence(source_node=cc_all_cuboct_node, path_size=6, include_source=True) assert cseq == {0: 1, 1: 26, 2: 122, 3: 298, 4: 554, 5: 890, 6: 1306} cseq = cc_all.coordination_sequence(source_node=cc_all_oct_node, path_size=4, coordination="env:number") assert cseq == { 1: { "O:6": 6, "C:12": 8 }, 2: { "O:6": 26, "C:12": 48 }, 3: { "O:6": 90, "C:12": 128 }, 4: { "O:6": 194, "C:12": 248 }, } cseq = cc_all.coordination_sequence(source_node=cc_all_cuboct_node, path_size=4, coordination="env:number") assert cseq == { 1: { "O:6": 8, "C:12": 18 }, 2: { "O:6": 48, "C:12": 74 }, 3: { "O:6": 128, "C:12": 170 }, 4: { "O:6": 248, "C:12": 306 }, } cseq = cc_all.coordination_sequence( source_node=cc_all_cuboct_node, path_size=4, coordination="env:number", include_source=True, ) assert cseq == { 0: { "C:12": 1 }, 1: { "O:6": 8, "C:12": 18 }, 2: { "O:6": 48, "C:12": 74 }, 3: { "O:6": 128, "C:12": 170 }, 4: { "O:6": 248, "C:12": 306 }, }
def test_light_structure_environments(self): with ScratchDir("."): with open(f"{se_files_dir}/se_mp-7000.json") as f: dd = json.load(f) se = StructureEnvironments.from_dict(dd) strategy = SimplestChemenvStrategy() lse = LightStructureEnvironments.from_structure_environments( structure_environments=se, strategy=strategy, valences="undefined") isite = 6 nb_set = lse.neighbors_sets[isite][0] neighb_coords = [ np.array([0.2443798, 1.80409653, -1.13218359]), np.array([1.44020353, 1.11368738, 1.13218359]), np.array([2.75513098, 2.54465207, -0.70467298]), np.array([0.82616785, 3.65833945, 0.70467298]), ] neighb_indices = [0, 3, 5, 1] neighb_images = [[0, 0, -1], [0, 0, 0], [0, 0, -1], [0, 0, 0]] np.testing.assert_array_almost_equal(neighb_coords, nb_set.neighb_coords) np.testing.assert_array_almost_equal( neighb_coords, [s.coords for s in nb_set.neighb_sites]) nb_sai = nb_set.neighb_sites_and_indices np.testing.assert_array_almost_equal( neighb_coords, [sai["site"].coords for sai in nb_sai]) np.testing.assert_array_almost_equal( neighb_indices, [sai["index"] for sai in nb_sai]) nb_iai = nb_set.neighb_indices_and_images np.testing.assert_array_almost_equal( neighb_indices, [iai["index"] for iai in nb_iai]) np.testing.assert_array_equal( neighb_images, [iai["image_cell"] for iai in nb_iai]) self.assertEqual(nb_set.__len__(), 4) self.assertEqual(nb_set.__hash__(), 4) self.assertFalse(nb_set.__ne__(nb_set)) self.assertEqual( nb_set.__str__(), "Neighbors Set for site #6 :\n" " - Coordination number : 4\n" " - Neighbors sites indices : 0, 1, 2, 3\n", ) stats = lse.get_statistics() neighbors = lse.strategy.get_site_neighbors( site=lse.structure[isite]) self.assertArrayAlmostEqual( neighbors[0].coords, np.array([0.2443798, 1.80409653, -1.13218359])) self.assertArrayAlmostEqual( neighbors[1].coords, np.array([1.44020353, 1.11368738, 1.13218359])) self.assertArrayAlmostEqual( neighbors[2].coords, np.array([2.75513098, 2.54465207, -0.70467298])) self.assertArrayAlmostEqual( neighbors[3].coords, np.array([0.82616785, 3.65833945, 0.70467298])) equiv_site_index_and_transform = lse.strategy.equivalent_site_index_and_transform( neighbors[0]) self.assertEqual(equiv_site_index_and_transform[0], 0) self.assertArrayAlmostEqual(equiv_site_index_and_transform[1], [0.0, 0.0, 0.0]) self.assertArrayAlmostEqual(equiv_site_index_and_transform[2], [0.0, 0.0, -1.0]) equiv_site_index_and_transform = lse.strategy.equivalent_site_index_and_transform( neighbors[1]) self.assertEqual(equiv_site_index_and_transform[0], 3) self.assertArrayAlmostEqual(equiv_site_index_and_transform[1], [0.0, 0.0, 0.0]) self.assertArrayAlmostEqual(equiv_site_index_and_transform[2], [0.0, 0.0, 0.0]) self.assertEqual(stats["atom_coordination_environments_present"], {"Si": { "T:4": 3.0 }}) self.assertEqual(stats["coordination_environments_atom_present"], {"T:4": { "Si": 3.0 }}) self.assertEqual( stats["fraction_atom_coordination_environments_present"], {"Si": { "T:4": 1.0 }}, ) site_info_ce = lse.get_site_info_for_specie_ce(specie=Species( "Si", 4), ce_symbol="T:4") np.testing.assert_array_almost_equal(site_info_ce["fractions"], [1.0, 1.0, 1.0]) np.testing.assert_array_almost_equal( site_info_ce["csms"], [ 0.009887784240541068, 0.009887786546730826, 0.009887787384385317 ], ) self.assertEqual(site_info_ce["isites"], [6, 7, 8]) site_info_allces = lse.get_site_info_for_specie_allces( specie=Species("Si", 4)) self.assertEqual(site_info_allces["T:4"], site_info_ce) self.assertFalse(lse.contains_only_one_anion("I-")) self.assertFalse(lse.contains_only_one_anion_atom("I")) self.assertTrue( lse.site_contains_environment(isite=isite, ce_symbol="T:4")) self.assertFalse( lse.site_contains_environment(isite=isite, ce_symbol="S:4")) self.assertFalse( lse.structure_contains_atom_environment(atom_symbol="Si", ce_symbol="S:4")) self.assertTrue( lse.structure_contains_atom_environment(atom_symbol="Si", ce_symbol="T:4")) self.assertFalse( lse.structure_contains_atom_environment(atom_symbol="O", ce_symbol="T:4")) self.assertTrue(lse.uniquely_determines_coordination_environments) self.assertFalse(lse.__ne__(lse)) envs = lse.strategy.get_site_coordination_environments( lse.structure[6]) self.assertEqual(len(envs), 1) self.assertEqual(envs[0][0], "T:4") multi_strategy = MultiWeightsChemenvStrategy.stats_article_weights_parameters( ) lse_multi = LightStructureEnvironments.from_structure_environments( strategy=multi_strategy, structure_environments=se, valences="undefined") self.assertAlmostEqual( lse_multi.coordination_environments[isite][0]["csm"], 0.009887784240541068, ) self.assertAlmostEqual( lse_multi.coordination_environments[isite][0]["ce_fraction"], 1.0) self.assertEqual( lse_multi.coordination_environments[isite][0]["ce_symbol"], "T:4")
def test_strategies(self): simplest_strategy_1 = SimplestChemenvStrategy() simplest_strategy_2 = SimplestChemenvStrategy(distance_cutoff=1.5, angle_cutoff=0.5) self.assertFalse(simplest_strategy_1 == simplest_strategy_2) simplest_strategy_1_from_dict = SimplestChemenvStrategy.from_dict(simplest_strategy_1.as_dict()) self.assertTrue(simplest_strategy_1, simplest_strategy_1_from_dict) effective_csm_estimator = {'function': 'power2_inverse_decreasing', 'options': {'max_csm': 8.0}} self_csm_weight = SelfCSMNbSetWeight() surface_definition = {'type': 'standard_elliptic', 'distance_bounds': {'lower': 1.1, 'upper': 1.9}, 'angle_bounds': {'lower': 0.1, 'upper': 0.9}} surface_definition_2 = {'type': 'standard_elliptic', 'distance_bounds': {'lower': 1.1, 'upper': 1.9}, 'angle_bounds': {'lower': 0.1, 'upper': 0.95}} da_area_weight = DistanceAngleAreaNbSetWeight(weight_type='has_intersection', surface_definition=surface_definition, nb_sets_from_hints='fallback_to_source', other_nb_sets='0_weight', additional_condition=DistanceAngleAreaNbSetWeight.AC.ONLY_ACB) da_area_weight_2 = DistanceAngleAreaNbSetWeight(weight_type='has_intersection', surface_definition=surface_definition_2, nb_sets_from_hints='fallback_to_source', other_nb_sets='0_weight', additional_condition=DistanceAngleAreaNbSetWeight.AC.ONLY_ACB) weight_estimator = {'function': 'smootherstep', 'options': {'delta_csm_min': 0.5, 'delta_csm_max': 3.0}} symmetry_measure_type = 'csm_wcs_ctwcc' delta_weight = DeltaCSMNbSetWeight(effective_csm_estimator=effective_csm_estimator, weight_estimator=weight_estimator, symmetry_measure_type=symmetry_measure_type) bias_weight = CNBiasNbSetWeight.linearly_equidistant(weight_cn1=1.0, weight_cn13=4.0) bias_weight_2 = CNBiasNbSetWeight.linearly_equidistant(weight_cn1=1.0, weight_cn13=5.0) angle_weight = AngleNbSetWeight() nad_weight = NormalizedAngleDistanceNbSetWeight(average_type='geometric', aa=1, bb=1) multi_weights_strategy_1 = MultiWeightsChemenvStrategy(dist_ang_area_weight=da_area_weight, self_csm_weight=self_csm_weight, delta_csm_weight=delta_weight, cn_bias_weight=bias_weight, angle_weight=angle_weight, normalized_angle_distance_weight=nad_weight, symmetry_measure_type=symmetry_measure_type) multi_weights_strategy_2 = MultiWeightsChemenvStrategy(dist_ang_area_weight=da_area_weight, self_csm_weight=self_csm_weight, delta_csm_weight=delta_weight, cn_bias_weight=bias_weight_2, angle_weight=angle_weight, normalized_angle_distance_weight=nad_weight, symmetry_measure_type=symmetry_measure_type) multi_weights_strategy_3 = MultiWeightsChemenvStrategy(dist_ang_area_weight=da_area_weight_2, self_csm_weight=self_csm_weight, delta_csm_weight=delta_weight, cn_bias_weight=bias_weight, angle_weight=angle_weight, normalized_angle_distance_weight=nad_weight, symmetry_measure_type=symmetry_measure_type) multi_weights_strategy_1_from_dict = MultiWeightsChemenvStrategy.from_dict(multi_weights_strategy_1.as_dict()) self.assertTrue(multi_weights_strategy_1 == multi_weights_strategy_1_from_dict) self.assertFalse(simplest_strategy_1 == multi_weights_strategy_1) self.assertFalse(multi_weights_strategy_1 == multi_weights_strategy_2) self.assertFalse(multi_weights_strategy_1 == multi_weights_strategy_3) self.assertFalse(multi_weights_strategy_2 == multi_weights_strategy_3)
def test_strategies(self): simplest_strategy_1 = SimplestChemenvStrategy() simplest_strategy_2 = SimplestChemenvStrategy(distance_cutoff=1.5, angle_cutoff=0.5) self.assertFalse(simplest_strategy_1 == simplest_strategy_2) simplest_strategy_1_from_dict = SimplestChemenvStrategy.from_dict( simplest_strategy_1.as_dict()) self.assertTrue(simplest_strategy_1, simplest_strategy_1_from_dict) effective_csm_estimator = { 'function': 'power2_inverse_decreasing', 'options': { 'max_csm': 8.0 } } self_csm_weight = SelfCSMNbSetWeight() surface_definition = { 'type': 'standard_elliptic', 'distance_bounds': { 'lower': 1.1, 'upper': 1.9 }, 'angle_bounds': { 'lower': 0.1, 'upper': 0.9 } } surface_definition_2 = { 'type': 'standard_elliptic', 'distance_bounds': { 'lower': 1.1, 'upper': 1.9 }, 'angle_bounds': { 'lower': 0.1, 'upper': 0.95 } } da_area_weight = DistanceAngleAreaNbSetWeight( weight_type='has_intersection', surface_definition=surface_definition, nb_sets_from_hints='fallback_to_source', other_nb_sets='0_weight', additional_condition=DistanceAngleAreaNbSetWeight.AC.ONLY_ACB) da_area_weight_2 = DistanceAngleAreaNbSetWeight( weight_type='has_intersection', surface_definition=surface_definition_2, nb_sets_from_hints='fallback_to_source', other_nb_sets='0_weight', additional_condition=DistanceAngleAreaNbSetWeight.AC.ONLY_ACB) weight_estimator = { 'function': 'smootherstep', 'options': { 'delta_csm_min': 0.5, 'delta_csm_max': 3.0 } } symmetry_measure_type = 'csm_wcs_ctwcc' delta_weight = DeltaCSMNbSetWeight( effective_csm_estimator=effective_csm_estimator, weight_estimator=weight_estimator, symmetry_measure_type=symmetry_measure_type) bias_weight = CNBiasNbSetWeight.linearly_equidistant(weight_cn1=1.0, weight_cn13=4.0) bias_weight_2 = CNBiasNbSetWeight.linearly_equidistant(weight_cn1=1.0, weight_cn13=5.0) angle_weight = AngleNbSetWeight() nad_weight = NormalizedAngleDistanceNbSetWeight( average_type='geometric', aa=1, bb=1) multi_weights_strategy_1 = MultiWeightsChemenvStrategy( dist_ang_area_weight=da_area_weight, self_csm_weight=self_csm_weight, delta_csm_weight=delta_weight, cn_bias_weight=bias_weight, angle_weight=angle_weight, normalized_angle_distance_weight=nad_weight, symmetry_measure_type=symmetry_measure_type) multi_weights_strategy_2 = MultiWeightsChemenvStrategy( dist_ang_area_weight=da_area_weight, self_csm_weight=self_csm_weight, delta_csm_weight=delta_weight, cn_bias_weight=bias_weight_2, angle_weight=angle_weight, normalized_angle_distance_weight=nad_weight, symmetry_measure_type=symmetry_measure_type) multi_weights_strategy_3 = MultiWeightsChemenvStrategy( dist_ang_area_weight=da_area_weight_2, self_csm_weight=self_csm_weight, delta_csm_weight=delta_weight, cn_bias_weight=bias_weight, angle_weight=angle_weight, normalized_angle_distance_weight=nad_weight, symmetry_measure_type=symmetry_measure_type) multi_weights_strategy_1_from_dict = MultiWeightsChemenvStrategy.from_dict( multi_weights_strategy_1.as_dict()) self.assertTrue( multi_weights_strategy_1 == multi_weights_strategy_1_from_dict) self.assertFalse(simplest_strategy_1 == multi_weights_strategy_1) self.assertFalse(multi_weights_strategy_1 == multi_weights_strategy_2) self.assertFalse(multi_weights_strategy_1 == multi_weights_strategy_3) self.assertFalse(multi_weights_strategy_2 == multi_weights_strategy_3)
def get_chemenv_analysis(struct, distance_cutoff, angle_cutoff): if not struct: raise PreventUpdate struct = self.from_data(struct) distance_cutoff = float(distance_cutoff) angle_cutoff = float(angle_cutoff) # decide which indices to present to user sga = SpacegroupAnalyzer(struct) symm_struct = sga.get_symmetrized_structure() inequivalent_indices = [ indices[0] for indices in symm_struct.equivalent_indices ] wyckoffs = symm_struct.wyckoff_symbols lgf = LocalGeometryFinder() lgf.setup_structure(structure=struct) se = lgf.compute_structure_environments( maximum_distance_factor=distance_cutoff + 0.01, only_indices=inequivalent_indices, ) strategy = SimplestChemenvStrategy(distance_cutoff=distance_cutoff, angle_cutoff=angle_cutoff) lse = LightStructureEnvironments.from_structure_environments( strategy=strategy, structure_environments=se) all_ce = AllCoordinationGeometries() envs = [] unknown_sites = [] for index, wyckoff in zip(inequivalent_indices, wyckoffs): datalist = { "Site": struct[index].species_string, "Wyckoff Label": wyckoff, } if not lse.neighbors_sets[index]: unknown_sites.append( f"{struct[index].species_string} ({wyckoff})") continue # represent the local environment as a molecule mol = Molecule.from_sites( [struct[index]] + lse.neighbors_sets[index][0].neighb_sites) mol = mol.get_centered_molecule() mg = MoleculeGraph.with_empty_graph(molecule=mol) for i in range(1, len(mol)): mg.add_edge(0, i) view = html.Div( [ StructureMoleculeComponent( struct_or_mol=mg, static=True, id= f"{struct.composition.reduced_formula}_site_{index}", scene_settings={ "enableZoom": False, "defaultZoom": 0.6 }, ).all_layouts["struct"] ], style={ "width": "300px", "height": "300px" }, ) env = lse.coordination_environments[index] co = all_ce.get_geometry_from_mp_symbol(env[0]["ce_symbol"]) name = co.name if co.alternative_names: name += f" (also known as {', '.join(co.alternative_names)})" datalist.update({ "Environment": name, "IUPAC Symbol": co.IUPAC_symbol_str, get_tooltip( "CSM", '"Continuous Symmetry Measure," a measure of how symmetrical a ' "local environment is from most symmetrical at 0% to least " "symmetrical at 100%", ): f"{env[0]['csm']:.2f}%", "Interactive View": view, }) envs.append(get_data_list(datalist)) # TODO: switch to tiles? envs_grouped = [envs[i:i + 2] for i in range(0, len(envs), 2)] analysis_contents = [] for env_group in envs_grouped: analysis_contents.append( Columns([Column(e, size=6) for e in env_group])) if unknown_sites: unknown_sites = html.Strong( f"The following sites were not identified: {', '.join(unknown_sites)}. " f"Please try changing the distance or angle cut-offs to identify these sites." ) else: unknown_sites = html.Span() return html.Div( [html.Div(analysis_contents), html.Br(), unknown_sites])
def get_chemenv_analysis(struct, distance_cutoff, angle_cutoff): if not struct: raise PreventUpdate struct = self.from_data(struct) kwargs = self.reconstruct_kwargs_from_state( callback_context.inputs) distance_cutoff = kwargs["distance_cutoff"] angle_cutoff = kwargs["angle_cutoff"] # TODO: remove these brittle guard statements, figure out more robust way to handle multiple input types if isinstance(struct, StructureGraph): struct = struct.structure def get_valences(struct): valences = [ getattr(site.specie, "oxi_state", None) for site in struct ] valences = [v for v in valences if v is not None] if len(valences) == len(struct): return valences else: return "undefined" # decide which indices to present to user sga = SpacegroupAnalyzer(struct) symm_struct = sga.get_symmetrized_structure() inequivalent_indices = [ indices[0] for indices in symm_struct.equivalent_indices ] wyckoffs = symm_struct.wyckoff_symbols lgf = LocalGeometryFinder() lgf.setup_structure(structure=struct) se = lgf.compute_structure_environments( maximum_distance_factor=distance_cutoff + 0.01, only_indices=inequivalent_indices, valences=get_valences(struct), ) strategy = SimplestChemenvStrategy(distance_cutoff=distance_cutoff, angle_cutoff=angle_cutoff) lse = LightStructureEnvironments.from_structure_environments( strategy=strategy, structure_environments=se) all_ce = AllCoordinationGeometries() envs = [] unknown_sites = [] for index, wyckoff in zip(inequivalent_indices, wyckoffs): datalist = { "Site": unicodeify_species(struct[index].species_string), "Wyckoff Label": wyckoff, } if not lse.neighbors_sets[index]: unknown_sites.append( f"{struct[index].species_string} ({wyckoff})") continue # represent the local environment as a molecule mol = Molecule.from_sites( [struct[index]] + lse.neighbors_sets[index][0].neighb_sites) mol = mol.get_centered_molecule() mg = MoleculeGraph.with_empty_graph(molecule=mol) for i in range(1, len(mol)): mg.add_edge(0, i) view = html.Div( [ StructureMoleculeComponent( struct_or_mol=mg, disable_callbacks=True, id= f"{struct.composition.reduced_formula}_site_{index}", scene_settings={ "enableZoom": False, "defaultZoom": 0.6 }, )._sub_layouts["struct"] ], style={ "width": "300px", "height": "300px" }, ) env = lse.coordination_environments[index] co = all_ce.get_geometry_from_mp_symbol(env[0]["ce_symbol"]) name = co.name if co.alternative_names: name += f" (also known as {', '.join(co.alternative_names)})" datalist.update({ "Environment": name, "IUPAC Symbol": co.IUPAC_symbol_str, get_tooltip( "CSM", "The continuous symmetry measure (CSM) describes the similarity to an " "ideal coordination environment. It can be understood as a 'distance' to " "a shape and ranges from 0 to 100 in which 0 corresponds to a " "coordination environment that is exactly identical to the ideal one. A " "CSM larger than 5.0 already indicates a relatively strong distortion of " "the investigated coordination environment.", ): f"{env[0]['csm']:.2f}", "Interactive View": view, }) envs.append(get_data_list(datalist)) # TODO: switch to tiles? envs_grouped = [envs[i:i + 2] for i in range(0, len(envs), 2)] analysis_contents = [] for env_group in envs_grouped: analysis_contents.append( Columns([Column(e, size=6) for e in env_group])) if unknown_sites: unknown_sites = html.Strong( f"The following sites were not identified: {', '.join(unknown_sites)}. " f"Please try changing the distance or angle cut-offs to identify these sites, " f"or try an alternative algorithm such as LocalEnv.") else: unknown_sites = html.Span() return html.Div( [html.Div(analysis_contents), html.Br(), unknown_sites])
def test_strategies(self): simplest_strategy = SimplestChemenvStrategy() self.assertTrue(simplest_strategy.uniquely_determines_coordination_environments) self.assertAlmostEqual( simplest_strategy.continuous_symmetry_measure_cutoff, 10.0 ) self.assertAlmostEqual(simplest_strategy.distance_cutoff, 1.4) self.assertAlmostEqual(simplest_strategy.angle_cutoff, 0.3) simplest_strategy = SimplestChemenvStrategy( distance_cutoff=1.3, angle_cutoff=0.45, continuous_symmetry_measure_cutoff=8.26, ) self.assertAlmostEqual( simplest_strategy.continuous_symmetry_measure_cutoff, 8.26 ) self.assertAlmostEqual(simplest_strategy.distance_cutoff, 1.3) self.assertAlmostEqual(simplest_strategy.angle_cutoff, 0.45) simplest_strategy.set_option("distance_cutoff", 1.5) self.assertAlmostEqual(simplest_strategy.distance_cutoff, 1.5) with self.assertRaises(ValueError) as cm: simplest_strategy.set_option("distance_cutoff", 0.5) self.assertEqual( str(cm.exception), "Distance cutoff should be between 1.0 and +infinity" ) simplest_strategy.set_option("angle_cutoff", 0.2) self.assertAlmostEqual(simplest_strategy.angle_cutoff, 0.2) with self.assertRaises(ValueError) as cm: simplest_strategy.set_option("angle_cutoff", 1.5) self.assertEqual( str(cm.exception), "Angle cutoff should be between 0.0 and 1.0" ) simplest_strategy.setup_options( { "distance_cutoff": 1.4, "additional_condition": 3, "continuous_symmetry_measure_cutoff": 8.5, } ) self.assertAlmostEqual(simplest_strategy.distance_cutoff, 1.4) self.assertAlmostEqual( simplest_strategy.continuous_symmetry_measure_cutoff, 8.5 ) self.assertEqual(simplest_strategy.additional_condition, 3) with self.assertRaises(ValueError) as cm: simplest_strategy.setup_options( {"continuous_symmetry_measure_cutoff": -0.1} ) self.assertEqual( str(cm.exception), "Continuous symmetry measure limits should be between 0.0 and 100.0", ) with self.assertRaises(ValueError) as cm: simplest_strategy.setup_options( {"continuous_symmetry_measure_cutoff": 100.1} ) self.assertEqual( str(cm.exception), "Continuous symmetry measure limits should be between 0.0 and 100.0", )
def test_real_systems(self): # Initialize geometry and connectivity finders strat = SimplestChemenvStrategy() lgf = LocalGeometryFinder() cf = ConnectivityFinder() # Connectivity of LiFePO4 struct = self.get_structure("LiFePO4") lgf.setup_structure(structure=struct) se = lgf.compute_structure_environments(only_atoms=["Li", "Fe", "P"], maximum_distance_factor=1.2) lse = LightStructureEnvironments.from_structure_environments( strategy=strat, structure_environments=se) # Make sure the initial structure and environments are correct for isite in range(0, 4): assert lse.structure[isite].specie.symbol == "Li" assert lse.coordination_environments[isite][0][ "ce_symbol"] == "O:6" for isite in range(4, 8): assert lse.structure[isite].specie.symbol == "Fe" assert lse.coordination_environments[isite][0][ "ce_symbol"] == "O:6" for isite in range(8, 12): assert lse.structure[isite].specie.symbol == "P" assert lse.coordination_environments[isite][0][ "ce_symbol"] == "T:4" # Get the connectivity including all environments and check results sc = cf.get_structure_connectivity(lse) assert len(sc.environment_subgraphs ) == 0 # Connected component not computed by default ccs = sc.get_connected_components( ) # by default, will use all the environments (O:6 and T:4 here) assert list(sc.environment_subgraphs.keys()) == [ "O:6-T:4" ] # Now the default components are there assert len(sc.environment_subgraphs) == 1 assert len(ccs) == 1 cc = ccs[0] assert len(cc) == 12 assert cc.periodicity == "3D" assert ( cc.description() == """Connected component with environment nodes : Node #0 Li (O:6) Node #1 Li (O:6) Node #2 Li (O:6) Node #3 Li (O:6) Node #4 Fe (O:6) Node #5 Fe (O:6) Node #6 Fe (O:6) Node #7 Fe (O:6) Node #8 P (T:4) Node #9 P (T:4) Node #10 P (T:4) Node #11 P (T:4)""") assert (cc.description( full=True) == """Connected component with environment nodes : Node #0 Li (O:6), connected to : - Node #1 Li (O:6) with delta image cells (-1 0 1) (-1 1 1) - Node #4 Fe (O:6) with delta image cells (-1 1 1) (0 1 1) - Node #5 Fe (O:6) with delta image cells (0 0 1) - Node #6 Fe (O:6) with delta image cells (-1 1 0) - Node #7 Fe (O:6) with delta image cells (-1 0 0) (0 0 0) - Node #8 P (T:4) with delta image cells (-1 0 1) (0 0 1) - Node #11 P (T:4) with delta image cells (-1 1 0) (0 1 0) Node #1 Li (O:6), connected to : - Node #0 Li (O:6) with delta image cells (1 -1 -1) (1 0 -1) - Node #4 Fe (O:6) with delta image cells (0 0 0) (1 0 0) - Node #5 Fe (O:6) with delta image cells (1 0 0) - Node #6 Fe (O:6) with delta image cells (0 0 -1) - Node #7 Fe (O:6) with delta image cells (0 0 -1) (1 0 -1) - Node #8 P (T:4) with delta image cells (0 0 0) (1 0 0) - Node #11 P (T:4) with delta image cells (0 0 -1) (1 0 -1) Node #2 Li (O:6), connected to : - Node #3 Li (O:6) with delta image cells (0 0 0) (0 1 0) - Node #4 Fe (O:6) with delta image cells (0 1 0) - Node #5 Fe (O:6) with delta image cells (0 0 0) (1 0 0) - Node #6 Fe (O:6) with delta image cells (-1 1 0) (0 1 0) - Node #7 Fe (O:6) with delta image cells (0 0 0) - Node #9 P (T:4) with delta image cells (0 1 0) (1 1 0) - Node #10 P (T:4) with delta image cells (-1 0 0) (0 0 0) Node #3 Li (O:6), connected to : - Node #2 Li (O:6) with delta image cells (0 -1 0) (0 0 0) - Node #4 Fe (O:6) with delta image cells (0 0 0) - Node #5 Fe (O:6) with delta image cells (0 0 0) (1 0 0) - Node #6 Fe (O:6) with delta image cells (-1 0 0) (0 0 0) - Node #7 Fe (O:6) with delta image cells (0 0 0) - Node #9 P (T:4) with delta image cells (0 0 0) (1 0 0) - Node #10 P (T:4) with delta image cells (-1 0 0) (0 0 0) Node #4 Fe (O:6), connected to : - Node #0 Li (O:6) with delta image cells (0 -1 -1) (1 -1 -1) - Node #1 Li (O:6) with delta image cells (-1 0 0) (0 0 0) - Node #2 Li (O:6) with delta image cells (0 -1 0) - Node #3 Li (O:6) with delta image cells (0 0 0) - Node #5 Fe (O:6) with delta image cells (0 -1 0) (0 0 0) (1 -1 0) (1 0 0) - Node #8 P (T:4) with delta image cells (0 -1 0) (0 0 0) - Node #9 P (T:4) with delta image cells (0 0 0) (1 0 0) - Node #11 P (T:4) with delta image cells (0 0 -1) Node #5 Fe (O:6), connected to : - Node #0 Li (O:6) with delta image cells (0 0 -1) - Node #1 Li (O:6) with delta image cells (-1 0 0) - Node #2 Li (O:6) with delta image cells (-1 0 0) (0 0 0) - Node #3 Li (O:6) with delta image cells (-1 0 0) (0 0 0) - Node #4 Fe (O:6) with delta image cells (-1 0 0) (-1 1 0) (0 0 0) (0 1 0) - Node #8 P (T:4) with delta image cells (-1 0 0) (0 0 0) - Node #9 P (T:4) with delta image cells (0 0 0) (0 1 0) - Node #10 P (T:4) with delta image cells (-1 0 0) Node #6 Fe (O:6), connected to : - Node #0 Li (O:6) with delta image cells (1 -1 0) - Node #1 Li (O:6) with delta image cells (0 0 1) - Node #2 Li (O:6) with delta image cells (0 -1 0) (1 -1 0) - Node #3 Li (O:6) with delta image cells (0 0 0) (1 0 0) - Node #7 Fe (O:6) with delta image cells (0 -1 0) (0 0 0) (1 -1 0) (1 0 0) - Node #9 P (T:4) with delta image cells (1 0 0) - Node #10 P (T:4) with delta image cells (0 -1 0) (0 0 0) - Node #11 P (T:4) with delta image cells (0 0 0) (1 0 0) Node #7 Fe (O:6), connected to : - Node #0 Li (O:6) with delta image cells (0 0 0) (1 0 0) - Node #1 Li (O:6) with delta image cells (-1 0 1) (0 0 1) - Node #2 Li (O:6) with delta image cells (0 0 0) - Node #3 Li (O:6) with delta image cells (0 0 0) - Node #6 Fe (O:6) with delta image cells (-1 0 0) (-1 1 0) (0 0 0) (0 1 0) - Node #8 P (T:4) with delta image cells (0 0 1) - Node #10 P (T:4) with delta image cells (-1 0 0) (0 0 0) - Node #11 P (T:4) with delta image cells (0 0 0) (0 1 0) Node #8 P (T:4), connected to : - Node #0 Li (O:6) with delta image cells (0 0 -1) (1 0 -1) - Node #1 Li (O:6) with delta image cells (-1 0 0) (0 0 0) - Node #4 Fe (O:6) with delta image cells (0 0 0) (0 1 0) - Node #5 Fe (O:6) with delta image cells (0 0 0) (1 0 0) - Node #7 Fe (O:6) with delta image cells (0 0 -1) Node #9 P (T:4), connected to : - Node #2 Li (O:6) with delta image cells (-1 -1 0) (0 -1 0) - Node #3 Li (O:6) with delta image cells (-1 0 0) (0 0 0) - Node #4 Fe (O:6) with delta image cells (-1 0 0) (0 0 0) - Node #5 Fe (O:6) with delta image cells (0 -1 0) (0 0 0) - Node #6 Fe (O:6) with delta image cells (-1 0 0) Node #10 P (T:4), connected to : - Node #2 Li (O:6) with delta image cells (0 0 0) (1 0 0) - Node #3 Li (O:6) with delta image cells (0 0 0) (1 0 0) - Node #5 Fe (O:6) with delta image cells (1 0 0) - Node #6 Fe (O:6) with delta image cells (0 0 0) (0 1 0) - Node #7 Fe (O:6) with delta image cells (0 0 0) (1 0 0) Node #11 P (T:4), connected to : - Node #0 Li (O:6) with delta image cells (0 -1 0) (1 -1 0) - Node #1 Li (O:6) with delta image cells (-1 0 1) (0 0 1) - Node #4 Fe (O:6) with delta image cells (0 0 1) - Node #6 Fe (O:6) with delta image cells (-1 0 0) (0 0 0) - Node #7 Fe (O:6) with delta image cells (0 -1 0) (0 0 0)""") # Get the connectivity for T:4 and O:6 separately and check results # Only tetrahedral sc.setup_environment_subgraph(environments_symbols=["T:4"]) assert list(sc.environment_subgraphs.keys()) == ["O:6-T:4", "T:4"] ccs = sc.get_connected_components() assert len(ccs) == 4 for cc in ccs: assert cc.periodicity == "0D" # Only octahedral sc.setup_environment_subgraph(environments_symbols=["O:6"]) assert list( sc.environment_subgraphs.keys()) == ["O:6-T:4", "T:4", "O:6"] ccs = sc.get_connected_components() assert len(ccs) == 1 cc = ccs[0] assert cc.periodicity == "3D" # Only Manganese octahedral sc.setup_environment_subgraph(environments_symbols=["O:6"], only_atoms=["Fe"]) assert list(sc.environment_subgraphs.keys()) == [ "O:6-T:4", "T:4", "O:6", "O:6#Fe", ] ccs = sc.get_connected_components() assert len(ccs) == 2 for cc in ccs: assert cc.periodicity == "2D" # Connectivity of Li4Fe3Mn1(PO4)4 struct = Structure.from_file( os.path.join(self.TEST_FILES_DIR, "Li4Fe3Mn1(PO4)4.cif")) lgf.setup_structure(structure=struct) se = lgf.compute_structure_environments( only_atoms=["Li", "Fe", "Mn", "P"], maximum_distance_factor=1.2) lse = LightStructureEnvironments.from_structure_environments( strategy=strat, structure_environments=se) # Make sure the initial structure and environments are correct for isite in range(0, 4): assert lse.structure[isite].specie.symbol == "Li" assert lse.coordination_environments[isite][0][ "ce_symbol"] == "O:6" for isite in range(4, 5): assert lse.structure[isite].specie.symbol == "Mn" assert lse.coordination_environments[isite][0][ "ce_symbol"] == "O:6" for isite in range(5, 8): assert lse.structure[isite].specie.symbol == "Fe" assert lse.coordination_environments[isite][0][ "ce_symbol"] == "O:6" for isite in range(8, 12): assert lse.structure[isite].specie.symbol == "P" assert lse.coordination_environments[isite][0][ "ce_symbol"] == "T:4" # Get the connectivity including all environments and check results sc = cf.get_structure_connectivity(lse) assert len(sc.environment_subgraphs ) == 0 # Connected component not computed by default ccs = sc.get_connected_components( ) # by default, will use all the environments (O:6 and T:4 here) # Now connected components for the defaults are there : assert list(sc.environment_subgraphs.keys()) == ["O:6-T:4"] assert len(sc.environment_subgraphs) == 1 assert len(ccs) == 1 cc = ccs[0] assert cc.periodicity == "3D" # Get the connectivity for Li octahedral only ccs = sc.get_connected_components(environments_symbols=["O:6"], only_atoms=["Li"]) assert list(sc.environment_subgraphs.keys()) == ["O:6-T:4", "O:6#Li"] assert len(ccs) == 2 for cc in ccs: assert cc.periodicity == "1D" assert len(cc) == 2 # Sort connected components as they might # come in a different order depending on # the algorithm used to get them. sorted_ccs = sorted(ccs, key=lambda x: sorted(x.graph.nodes())[0]) assert (sorted_ccs[0].description( full=True) == """Connected component with environment nodes : Node #0 Li (O:6), connected to : - Node #1 Li (O:6) with delta image cells (1 -1 1) (1 0 1) Node #1 Li (O:6), connected to : - Node #0 Li (O:6) with delta image cells (-1 0 -1) (-1 1 -1)""") assert (sorted_ccs[1].description( full=True) == """Connected component with environment nodes : Node #2 Li (O:6), connected to : - Node #3 Li (O:6) with delta image cells (0 -1 0) (0 0 0) Node #3 Li (O:6), connected to : - Node #2 Li (O:6) with delta image cells (0 0 0) (0 1 0)""") # Get the connectivity for Mn octahedral only ccs = sc.get_connected_components(environments_symbols=["O:6"], only_atoms=["Mn"]) assert list(sc.environment_subgraphs.keys()) == [ "O:6-T:4", "O:6#Li", "O:6#Mn" ] assert len(ccs) == 1 assert ccs[0].periodicity == "0D" # Get the connectivity for Fe octahedral only ccs = sc.get_connected_components(environments_symbols=["O:6"], only_atoms=["Fe"]) assert list(sc.environment_subgraphs.keys()) == [ "O:6-T:4", "O:6#Li", "O:6#Mn", "O:6#Fe", ] assert len(ccs) == 2 ccs_periodicities = set(cc.periodicity for cc in ccs) assert ccs_periodicities == {"0D", "2D"}