ion-CSP 2.2.1__tar.gz → 2.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. {ion_csp-2.2.1 → ion_csp-2.2.3}/.gitignore +1 -0
  2. {ion_csp-2.2.1 → ion_csp-2.2.3}/CHANGELOG.md +24 -0
  3. {ion_csp-2.2.1 → ion_csp-2.2.3}/PKG-INFO +1 -1
  4. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/__init__.py +2 -2
  5. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/gen_opt.py +6 -6
  6. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/mlp_opt.py +2 -6
  7. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/read_mlp_density.py +5 -1
  8. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/task_manager.py +1 -1
  9. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP.egg-info/PKG-INFO +1 -1
  10. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP.egg-info/SOURCES.txt +1 -0
  11. ion_csp-2.2.3/tests/test_identify_molecules.py +171 -0
  12. {ion_csp-2.2.1 → ion_csp-2.2.3}/.dockerignore +0 -0
  13. {ion_csp-2.2.1 → ion_csp-2.2.3}/.github/workflows/conda-env-build.yml +0 -0
  14. {ion_csp-2.2.1 → ion_csp-2.2.3}/.github/workflows/pypi-publish.yml +0 -0
  15. {ion_csp-2.2.1 → ion_csp-2.2.3}/.vscode/settings.json +0 -0
  16. {ion_csp-2.2.1 → ion_csp-2.2.3}/CODE_OF_CONDUCT.md +0 -0
  17. {ion_csp-2.2.1 → ion_csp-2.2.3}/CONTRIBUTING.md +0 -0
  18. {ion_csp-2.2.1 → ion_csp-2.2.3}/Dockerfile +0 -0
  19. {ion_csp-2.2.1 → ion_csp-2.2.3}/LICENSE +0 -0
  20. {ion_csp-2.2.1 → ion_csp-2.2.3}/MANIFEST.in +0 -0
  21. {ion_csp-2.2.1 → ion_csp-2.2.3}/README.md +0 -0
  22. {ion_csp-2.2.1 → ion_csp-2.2.3}/SECURITY.md +0 -0
  23. {ion_csp-2.2.1 → ion_csp-2.2.3}/config/complete_config.yaml +0 -0
  24. {ion_csp-2.2.1 → ion_csp-2.2.3}/config/simple_config.yaml +0 -0
  25. {ion_csp-2.2.1 → ion_csp-2.2.3}/config/usage.md +0 -0
  26. {ion_csp-2.2.1 → ion_csp-2.2.3}/docs/example_usage_CSP.py +0 -0
  27. {ion_csp-2.2.1 → ion_csp-2.2.3}/docs/example_usage_EE.py +0 -0
  28. {ion_csp-2.2.1 → ion_csp-2.2.3}/docs/index.md +0 -0
  29. {ion_csp-2.2.1 → ion_csp-2.2.3}/docs/usage.md +0 -0
  30. {ion_csp-2.2.1 → ion_csp-2.2.3}/environment.yml +0 -0
  31. {ion_csp-2.2.1 → ion_csp-2.2.3}/examples/example_1/config.yaml +0 -0
  32. {ion_csp-2.2.1 → ion_csp-2.2.3}/examples/example_1/ions.csv +0 -0
  33. {ion_csp-2.2.1 → ion_csp-2.2.3}/examples/example_2/combo_1/ACEGUL.gjf +0 -0
  34. {ion_csp-2.2.1 → ion_csp-2.2.3}/examples/example_2/combo_1/AGIDOM.gjf +0 -0
  35. {ion_csp-2.2.1 → ion_csp-2.2.3}/examples/example_2/combo_1/config.yaml +0 -0
  36. {ion_csp-2.2.1 → ion_csp-2.2.3}/examples/example_usage.py +0 -0
  37. {ion_csp-2.2.1 → ion_csp-2.2.3}/examples/server/example_local_machine.yaml +0 -0
  38. {ion_csp-2.2.1 → ion_csp-2.2.3}/examples/server/example_local_resources.yaml +0 -0
  39. {ion_csp-2.2.1 → ion_csp-2.2.3}/examples/server/example_remote_machine.yaml +0 -0
  40. {ion_csp-2.2.1 → ion_csp-2.2.3}/examples/server/example_remote_resources.yaml +0 -0
  41. {ion_csp-2.2.1 → ion_csp-2.2.3}/makefile +0 -0
  42. {ion_csp-2.2.1 → ion_csp-2.2.3}/model/model.pt +0 -0
  43. {ion_csp-2.2.1 → ion_csp-2.2.3}/model/options/README.md +0 -0
  44. {ion_csp-2.2.1 → ion_csp-2.2.3}/model/options/model.ckpt-4000000.pt +0 -0
  45. {ion_csp-2.2.1 → ion_csp-2.2.3}/param/INCAR_0 +0 -0
  46. {ion_csp-2.2.1 → ion_csp-2.2.3}/param/INCAR_1 +0 -0
  47. {ion_csp-2.2.1 → ion_csp-2.2.3}/param/INCAR_2 +0 -0
  48. {ion_csp-2.2.1 → ion_csp-2.2.3}/param/INCAR_3 +0 -0
  49. {ion_csp-2.2.1 → ion_csp-2.2.3}/param/POTCAR_C +0 -0
  50. {ion_csp-2.2.1 → ion_csp-2.2.3}/param/POTCAR_H +0 -0
  51. {ion_csp-2.2.1 → ion_csp-2.2.3}/param/POTCAR_N +0 -0
  52. {ion_csp-2.2.1 → ion_csp-2.2.3}/param/POTCAR_O +0 -0
  53. {ion_csp-2.2.1 → ion_csp-2.2.3}/param/g16_sub.sh +0 -0
  54. {ion_csp-2.2.1 → ion_csp-2.2.3}/param/sub_final.sh +0 -0
  55. {ion_csp-2.2.1 → ion_csp-2.2.3}/param/sub_ori.sh +0 -0
  56. {ion_csp-2.2.1 → ion_csp-2.2.3}/param/sub_supple.sh +0 -0
  57. {ion_csp-2.2.1 → ion_csp-2.2.3}/pyproject.toml +0 -0
  58. {ion_csp-2.2.1 → ion_csp-2.2.3}/requirements.txt +0 -0
  59. {ion_csp-2.2.1 → ion_csp-2.2.3}/scripts/CLI.py +0 -0
  60. {ion_csp-2.2.1 → ion_csp-2.2.3}/scripts/CLI.sh +0 -0
  61. {ion_csp-2.2.1 → ion_csp-2.2.3}/scripts/main_CSP.sh +0 -0
  62. {ion_csp-2.2.1 → ion_csp-2.2.3}/scripts/main_EE.sh +0 -0
  63. {ion_csp-2.2.1 → ion_csp-2.2.3}/scripts/update_changelog.py +0 -0
  64. {ion_csp-2.2.1 → ion_csp-2.2.3}/setup.cfg +0 -0
  65. {ion_csp-2.2.1 → ion_csp-2.2.3}/setup.py +0 -0
  66. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/__init__.py +0 -0
  67. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/__main__.py +0 -0
  68. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/convert_SMILES.py +0 -0
  69. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/empirical_estimate.py +0 -0
  70. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/identify_molecules.py +0 -0
  71. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/log_and_time.py +0 -0
  72. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/model/model.pt +0 -0
  73. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/model/options/README.md +0 -0
  74. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/model/options/model.ckpt-4000000.pt +0 -0
  75. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/param/INCAR_0 +0 -0
  76. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/param/INCAR_1 +0 -0
  77. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/param/INCAR_2 +0 -0
  78. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/param/INCAR_3 +0 -0
  79. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/param/POTCAR_C +0 -0
  80. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/param/POTCAR_H +0 -0
  81. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/param/POTCAR_N +0 -0
  82. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/param/POTCAR_O +0 -0
  83. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/param/g16_sub.sh +0 -0
  84. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/param/sub_final.sh +0 -0
  85. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/param/sub_ori.sh +0 -0
  86. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/param/sub_supple.sh +0 -0
  87. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/run/__init__.py +0 -0
  88. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/run/main_CSP.py +0 -0
  89. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/run/main_EE.py +0 -0
  90. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/run/run_convert_SMILES.py +0 -0
  91. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/run/run_empirical_estimate.py +0 -0
  92. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/run/run_gen_opt.py +0 -0
  93. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/run/run_read_mlp_density.py +0 -0
  94. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/run/run_upload_download.py +0 -0
  95. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/run/run_vasp_processing.py +0 -0
  96. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/steps_opt_monitor.sh +0 -0
  97. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/upload_download.py +0 -0
  98. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP/vasp_processing.py +0 -0
  99. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP.egg-info/dependency_links.txt +0 -0
  100. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP.egg-info/entry_points.txt +0 -0
  101. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP.egg-info/requires.txt +0 -0
  102. {ion_csp-2.2.1 → ion_csp-2.2.3}/src/ion_CSP.egg-info/top_level.txt +0 -0
  103. {ion_csp-2.2.1 → ion_csp-2.2.3}/tests/__init__.py +0 -0
  104. {ion_csp-2.2.1 → ion_csp-2.2.3}/tests/test_log_and_time.py +0 -0
  105. {ion_csp-2.2.1 → ion_csp-2.2.3}/tests/test_task_manager.py +0 -0
  106. {ion_csp-2.2.1 → ion_csp-2.2.3}/uv.lock +0 -0
@@ -5,6 +5,7 @@ venv/
5
5
  # 编译文件
6
6
  __pycache__/
7
7
  *.pyc
8
+ *.egg-info/
8
9
 
9
10
  # IDE 配置
10
11
  .idea/
@@ -2,6 +2,30 @@
2
2
 
3
3
  ## Latest Changes
4
4
 
5
+ ### 12e7aa8 (2025-07-10)
6
+
7
+ Update version number to 2.2.3, remove redundant egg info files, and add ignore for *. egg info.
8
+
9
+ ### 42fad92 (2025-07-10)
10
+
11
+ Stabilized version functionality, restored import of cell filters, and resumed use of UnitCellFilter to optimize atomic structure pressure constraints.
12
+
13
+ ## V2.2.2
14
+
15
+ ### 19a1b2d (2025-07-04)
16
+
17
+ Adjust the sleep time in task_manager and add molecular recognition test cases.
18
+
19
+ ### d914d90 (2025-07-03)
20
+
21
+ Update the version to 2.2.2. The redundant output content of the console is greatly reduced to control the size of the console. log file. Specifically, the redundant log records are reduced in gen_opt when exceptions occur in the structure generation process, and the redundant log records in gen_opt and read_mlp_density are reduced when calling the phonopy toolkit.
22
+
23
+ ## V2.2.1
24
+
25
+ ### 0a77f51 (2025-07-01)
26
+
27
+ Due to the unsatisfactory performance of Mattersim in testing, we switched back to fine-tuning the machine learning potential of DP. The performance of ExpCellFilter in testing is easier to identify experimental structures compared to UnitCellFilter, so it is changed to call ExpCellFilter in mlp_opt.py.
28
+
5
29
  ### 1754cc7 (2025-06-30)
6
30
 
7
31
  Update version number to 2.2.1, fix file copying logic in main_EE workflow, update mlp_opt.py to use MatterSimCalculator to develop calculation efficiency.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ion_CSP
3
- Version: 2.2.1
3
+ Version: 2.2.3
4
4
  Summary: Crystal Structure Design Software Based on Molecular/Ionic Configuration.
5
5
  Home-page: https://github.com/bagabaga007/ion_CSP
6
6
  Author: Ze Yang
@@ -1,8 +1,8 @@
1
1
  __author__ = "Ze Yang"
2
2
  __contact__ = "yangze1995007@163.com"
3
3
  __license__ = "MIT"
4
- __version__ = "2.2.1"
5
- __date__ = "2025-06-30"
4
+ __version__ = "2.2.3"
5
+ __date__ = "2025-07-10"
6
6
 
7
7
 
8
8
  try:
@@ -116,9 +116,8 @@ class CrystalGenerator:
116
116
  pyxtal_structure.to_file(POSCAR_path, fmt="poscar")
117
117
  total_count += 1
118
118
  group_count += 1
119
+ # 捕获对于某一空间群生成结构的运行时间过长、组成兼容性错误、对称性兼容性错误等异常,使结构生成能够完全进行而不中断
119
120
  except (RuntimeError, Comp_CompatibilityError, Symm_CompatibilityError) as e:
120
- # 捕获对于某一空间群生成结构的运行时间过长、组成兼容性错误、对称性兼容性错误等异常,使结构生成能够完全进行而不中断
121
- logging.error(f"Generating structure error: {e}")
122
121
  # 记录异常类型并跳出当前空间群的生成循环
123
122
  exception_message = type(e).__name__
124
123
  break
@@ -154,7 +153,11 @@ class CrystalGenerator:
154
153
  # 按顺序处理POSCAR文件,首先复制一份无数字后缀的POSCAR文件
155
154
  shutil.copy(f"{self.POSCAR_dir}/{filename}", f"{self.POSCAR_dir}/POSCAR")
156
155
  try:
157
- subprocess.run(["nohup", "phonopy", "--symmetry", "POSCAR"], check=True)
156
+ subprocess.run(
157
+ ["nohup", "phonopy", "--symmetry", "POSCAR"],
158
+ check=True,
159
+ stdout=subprocess.DEVNULL
160
+ )
158
161
  except subprocess.CalledProcessError as e:
159
162
  # 新增:捕获phonopy执行错误
160
163
  logging.error(f"Phonopy execution failed for {filename}: {str(e)}")
@@ -168,9 +171,6 @@ class CrystalGenerator:
168
171
 
169
172
  # 检查生成的POSCAR中的原子数,如果不匹配则删除该POSCAR并在日志中记录
170
173
  if cell_atoms != self.cell_atoms:
171
- error_message = f"Atom number mismatch ({cell_atoms} vs {self.cell_atoms})"
172
- print(f"{filename} - {error_message}")
173
-
174
174
  # 新增:回溯空间群归属
175
175
  poscar_index = int(filename.split('_')[1]) # 提取POSCAR编号
176
176
  space_group = self._find_space_group(poscar_index)
@@ -10,17 +10,14 @@ import numpy as np
10
10
  import multiprocessing
11
11
  from ase.io.vasp import read_vasp
12
12
  from ase.optimize import LBFGS
13
- # from ase.constraints import UnitCellFilter
14
- from ase.filters import ExpCellFilter
13
+ from ase.constraints import UnitCellFilter
15
14
  from deepmd.calculator import DP
16
- # from mattersim.forcefield import MatterSimCalculator
17
15
 
18
16
  # 根据脚本位置确定model.pt文件的位置, 减少错误发生
19
17
  base_dir = os.path.dirname(__file__)
20
18
  relative_path = './model.pt'
21
19
  file_path = os.path.join(base_dir, relative_path)
22
20
  calc = DP(file_path)
23
- # calc = MatterSimCalculator(device="cuda")
24
21
 
25
22
 
26
23
  def get_element_num(elements):
@@ -162,8 +159,7 @@ def run_opt(index: int):
162
159
  aim_stress = 1.0 * pstress * 0.01 * 0.6242 / 10.0
163
160
  atoms = read_vasp('POSCAR_'+str(index))
164
161
  atoms.calc = calc
165
- # ucf = UnitCellFilter(atoms, scalar_pressure=aim_stress)
166
- ucf = ExpCellFilter(atoms, scalar_pressure=aim_stress)
162
+ ucf = UnitCellFilter(atoms, scalar_pressure=aim_stress)
167
163
  # optimization
168
164
  opt = LBFGS(ucf)
169
165
  opt.run(fmax=fmax,steps=Opt_Step)
@@ -174,7 +174,11 @@ class ReadMlpDensity:
174
174
  # 使用phonopy模块处理POSCAR结构文件,获取对称化的原胞和常规胞。
175
175
  # 应用晶体的对称操作优化后的原胞可以最好地符合晶体的对称性,减少后续优化计算的复杂性。
176
176
  log.write(f'\nProcessing file: {new_CONTCAR_filename}\n')
177
- result = subprocess.run(['nohup', 'phonopy', '--symmetry', 'POSCAR'], stderr=subprocess.STDOUT)
177
+ result = subprocess.run(
178
+ ["nohup", "phonopy", "--symmetry", "POSCAR"],
179
+ check=True,
180
+ stdout=subprocess.DEVNULL
181
+ )
178
182
  log.write(f'Finished processing file: {new_CONTCAR_filename} with return code: {result.returncode}\n')
179
183
  # 将phonopy生成的PPOSCAR(对称化原胞)和BPOSCAR(对称化常规胞)放到对应的文件夹中,并将文件名改回POSCAR_index
180
184
  shutil.move(f'{self.phonopy_dir}/PPOSCAR', f'{self.primitive_cell_dir}/{new_CONTCAR_filename}')
@@ -264,7 +264,7 @@ class TaskManager:
264
264
  std_log.symlink_to(output_log)
265
265
 
266
266
  print('Starting task ......')
267
- time.sleep(6)
267
+ time.sleep(3)
268
268
  logging.info(f"Started {module} module (PID: {process.pid})")
269
269
  print(f"Task started (PID: {process.pid})")
270
270
  print(f"Normalized log file: {std_log}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ion_CSP
3
- Version: 2.2.1
3
+ Version: 2.2.3
4
4
  Summary: Crystal Structure Design Software Based on Molecular/Ionic Configuration.
5
5
  Home-page: https://github.com/bagabaga007/ion_CSP
6
6
  Author: Ze Yang
@@ -100,5 +100,6 @@ src/ion_CSP/run/run_read_mlp_density.py
100
100
  src/ion_CSP/run/run_upload_download.py
101
101
  src/ion_CSP/run/run_vasp_processing.py
102
102
  tests/__init__.py
103
+ tests/test_identify_molecules.py
103
104
  tests/test_log_and_time.py
104
105
  tests/test_task_manager.py
@@ -0,0 +1,171 @@
1
+ import os
2
+ import logging
3
+ import pytest
4
+ from ase import Atoms
5
+ from ion_CSP.identify_molecules import identify_molecules, molecules_information
6
+
7
+ # 配置日志
8
+ logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
9
+
10
+
11
+ def create_gjf_file(path: str, filename: str, atoms: Atoms):
12
+ """手动创建临时的GJF文件"""
13
+ filepath = os.path.join(path, filename)
14
+ with open(filepath, "w") as f:
15
+ f.write("#N\n\n") # Gaussian 文件头
16
+ f.write("Generated by ASE test\n\n")
17
+ f.write("0 1\n")
18
+ for atom in atoms:
19
+ f.write(f"{atom.symbol} {atom.x} {atom.y} {atom.z}\n")
20
+ f.write("\n\n") # 文件结束
21
+ return filepath
22
+
23
+
24
+ @pytest.fixture
25
+ def setup_test_environment(tmp_path):
26
+ """设置测试环境,创建临时目录并切换工作目录"""
27
+ os.chdir(tmp_path)
28
+ return tmp_path
29
+
30
+
31
+ def test_single_water_molecule(setup_test_environment):
32
+ """测试单个水分子"""
33
+ # 创建初始GJF文件
34
+ water = Atoms("OH2", positions=[[0, 0, 0], [1, 0, 0], [0, 1, 0]])
35
+ create_gjf_file(setup_test_environment, "water.gjf", water)
36
+
37
+ # 运行识别函数
38
+ merged_molecules, molecules_flag, initial_info = identify_molecules(water)
39
+
40
+ # 验证结果
41
+ expected_molecule = frozenset([("O", 1), ("H", 2)])
42
+ assert len(merged_molecules) == 1
43
+ assert expected_molecule in merged_molecules
44
+ assert merged_molecules[expected_molecule] == 1
45
+ assert molecules_flag is True
46
+ assert initial_info == [{"O": 1, "H": 2}]
47
+
48
+
49
+ def test_two_isolated_water_molecules(setup_test_environment):
50
+ """测试两个独立的水分子"""
51
+ # 创建两个初始GJF文件
52
+ water1 = Atoms("OH2", positions=[[0, 0, 0], [1, 0, 0], [0, 1, 0]])
53
+ water2 = Atoms("OH2", positions=[[10, 0, 0], [11, 0, 0], [10, 1, 0]])
54
+ create_gjf_file(setup_test_environment, "water1.gjf", water1)
55
+ create_gjf_file(setup_test_environment, "water2.gjf", water2)
56
+
57
+ # 合并两个分子
58
+ combined = water1 + water2
59
+
60
+ # 运行识别函数
61
+ merged_molecules, molecules_flag, initial_info = identify_molecules(combined)
62
+
63
+ # 验证结果
64
+ expected_molecule = frozenset([("O", 1), ("H", 2)])
65
+ assert len(merged_molecules) == 1
66
+ assert expected_molecule in merged_molecules
67
+ assert merged_molecules[expected_molecule] == 2
68
+ assert molecules_flag is True
69
+
70
+
71
+ def test_water_and_methane_molecules(setup_test_environment):
72
+ """测试水分子和甲烷分子"""
73
+ # 创建初始GJF文件
74
+ water = Atoms("OH2", positions=[[0, 0, 0], [1, 0, 0], [0, 1, 0]])
75
+ methane = Atoms(
76
+ "CH4", positions=[[10, 0, 0], [11, 0, 0], [10, 1, 0], [10, 0, 1], [9, 0, 0]]
77
+ )
78
+ create_gjf_file(setup_test_environment, "water.gjf", water)
79
+ create_gjf_file(setup_test_environment, "methane.gjf", methane)
80
+
81
+ # 合并两个分子
82
+ combined = water + methane
83
+
84
+ # 运行识别函数
85
+ merged_molecules, molecules_flag, initial_info = identify_molecules(combined)
86
+
87
+ # 验证结果
88
+ water_mol = frozenset([("O", 1), ("H", 2)])
89
+ methane_mol = frozenset([("C", 1), ("H", 4)])
90
+
91
+ assert len(merged_molecules) == 2
92
+ assert water_mol in merged_molecules
93
+ assert methane_mol in merged_molecules
94
+ assert merged_molecules[water_mol] == 1
95
+ assert merged_molecules[methane_mol] == 1
96
+ assert molecules_flag is True
97
+ assert sorted(initial_info, key=lambda x: sorted(x.items())) == sorted(
98
+ [{"O": 1, "H": 2}, {"C": 1, "H": 4}], key=lambda x: sorted(x.items())
99
+ )
100
+
101
+
102
+ def test_mismatched_molecules(setup_test_environment):
103
+ """测试分子不匹配的情况"""
104
+ # 只创建一个初始GJF文件
105
+ water = Atoms("OH2", positions=[[0, 0, 0], [1, 0, 0], [0, 1, 0]])
106
+ create_gjf_file(setup_test_environment, "water.gjf", water)
107
+
108
+ # 创建两个独立的水分子
109
+ water1 = Atoms("OH", positions=[[0, 0, 0], [1, 0, 0]])
110
+ water2 = Atoms("OH3", positions=[[10, 0, 0], [11, 0, 0], [10, 1, 0], [10, 1, 1]])
111
+ combined = water1 + water2
112
+
113
+ # 运行识别函数
114
+ merged_molecules, molecules_flag, initial_info = identify_molecules(combined)
115
+
116
+ # 验证结果
117
+ expected_molecule = frozenset([("O", 1), ("H", 2)])
118
+ assert len(merged_molecules) == 2
119
+ assert expected_molecule not in merged_molecules
120
+ assert merged_molecules[expected_molecule] == 0
121
+ assert molecules_flag is False
122
+ assert initial_info == [{"O": 1, "H": 2}]
123
+
124
+
125
+ def test_no_gjf_files(setup_test_environment):
126
+ """测试没有GJF文件的情况"""
127
+ water = Atoms("OH2", positions=[[0, 0, 0], [1, 0, 0], [0, 1, 0]])
128
+
129
+ # 运行识别函数
130
+ merged_molecules, molecules_flag, initial_info = identify_molecules(water)
131
+
132
+ # 验证结果
133
+ expected_molecule = frozenset([("O", 1), ("H", 2)])
134
+ assert len(merged_molecules) == 1
135
+ assert expected_molecule in merged_molecules
136
+ assert merged_molecules[expected_molecule] == 1
137
+ assert molecules_flag is False # 没有初始GJF文件
138
+ assert initial_info == []
139
+
140
+
141
+ def test_molecules_information_logging(caplog):
142
+ """测试分子信息日志输出"""
143
+ # 准备测试数据
144
+ molecules = {frozenset([("C", 1), ("H", 4)]): 1, frozenset([("O", 1), ("H", 2)]): 2}
145
+ molecules_flag = True
146
+ initial_info = [{"C": 1, "H": 4}, {"O": 1, "H": 2}]
147
+
148
+ # 运行信息输出函数
149
+ with caplog.at_level(logging.INFO):
150
+ molecules_information(molecules, molecules_flag, initial_info)
151
+
152
+ # 验证日志输出
153
+ assert "Initial molecules: [{'C': 1, 'H': 4}, {'O': 1, 'H': 2}]" in caplog.text
154
+ assert "Molecule 1 (Total Atoms: 5, Count: 1): C1H4" in caplog.text
155
+ assert "Molecule 2 (Total Atoms: 3, Count: 2): O1H2" in caplog.text
156
+ assert "Molecular Comparison Successful" in caplog.text
157
+
158
+
159
+ def test_molecules_information_failed_logging(caplog):
160
+ """测试分子比较失败的日志输出"""
161
+ # 准备测试数据
162
+ molecules = {frozenset([("C", 1), ("H", 4)]): 1, frozenset([("O", 1), ("H", 2)]): 2}
163
+ molecules_flag = False
164
+ initial_info = [{"C": 1, "H": 4}, {"O": 1, "H": 2}]
165
+
166
+ # 运行信息输出函数
167
+ with caplog.at_level(logging.INFO):
168
+ molecules_information(molecules, molecules_flag, initial_info)
169
+
170
+ # 验证日志输出
171
+ assert "Molecular Comparison Failed" in caplog.text
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes