@@ -351,29 +351,40 @@ def generate_components_symmetry_box(self,
351351 ads_components_counts : list ,
352352 bulk_components_smiles : list ,
353353 bulk_components_counts : list ,
354- ads_limit = 0.5
354+ ads_limit = 0.5 ,
355+ bulk_dw = None ,
356+ bulk_up = None
355357 ):
356358 """
357- Generate water + ion/organic components box in a slab-aligned box
359+ Generate a slab-aligned box with symmetric distribution of water, adsorbed molecules, and bulk components.
360+
358361 Args:
359362 water_box_len (float): Height of the water box along z-direction.
360363 ads_components_smiles (list): SMILES list of adsorbed components.
361364 ads_components_counts (list): Corresponding counts of adsorbed components.
362365 bulk_components_smiles (list): SMILES list of bulk components.
363366 bulk_components_counts (list): Corresponding counts of bulk components.
364- ads_limit (float): Maximum z-direction distance for adsorbates from the surface.
365- For large molecules, this value may need to be increased. (default = 0.5 Å)
367+ ads_limit (float): Vertical range (in Å) above and below the surface for placing adsorbed species. Default = 0.5 Å.
368+ bulk_dw (float, optional): Lower boundary (Å) of the symmetric bulk region (below box center).
369+ bulk_up (float, optional): Upper boundary (Å) of the symmetric bulk region (below box center).
370+ Must satisfy: 0 < bulk_dw < bulk_up < water_box_len / 2.
371+ If set, creates symmetric regions for bulk placement.
372+
366373
367374 Returns:
368375 None. Generates a Packmol input file and runs Packmol to generate `watbox.xyz`.
369376
370377 Example:
371378 >>> from ectoolkits.structures.tools import generate_components_symmetry_box
372379 >>> generate_components_symmetry_box(
380+ ... water_box_len = 20
373381 ... ads_components_smiles=["[Cl-]"],
374382 ... ads_components_counts=[2],
375383 ... bulk_components_smiles=["[K+]", "[Cl-]"],
376- ... bulk_components_counts=[3, 3]
384+ ... bulk_components_counts=[3, 3],
385+ ... ads_limit=0.5,
386+ ... bulk_dw = 3,
387+ ... bulk_up = 6
377388 )
378389
379390
@@ -383,31 +394,40 @@ def generate_components_symmetry_box(self,
383394
384395 assert len (ads_components_smiles ) == len (ads_components_counts ), "Length mismatch in adsorbed components"
385396 assert len (bulk_components_smiles ) == len (bulk_components_counts ), "Length mismatch in bulk components"
397+ custom_bulk = False
398+ if bulk_dw is not None and bulk_up is not None :
399+ if bulk_dw > 0 and bulk_up < water_box_len / 2 :
400+ custom_bulk = True
401+ else :
402+ raise ValueError (f"Invalid custom bulk region: bulk_dw={ bulk_dw } , bulk_up={ bulk_up } . "
403+ f"Ensure 0 < bulk_dw < bulk_up < water_box_len/2." )
404+
386405 cell = self .get_cell ()
387406 cell_a = cell [0 ]
388407 cell_b = cell [1 ]
408+
409+ # === Water molecule estimation ===
389410 header = "-"
390411 logger .info (header * 50 )
391412 logger .info ("Now Generate Water Box" )
392413 space_per_water = 9.86 ** 3 / 32
393- init_wat_num = (np .linalg .norm (np .cross (cell_a , cell_b ))
394- * water_box_len ) / space_per_water
395- init_wat_num = int (init_wat_num )
414+ init_wat_num = int ((np .linalg .norm (np .cross (cell_a , cell_b ))
415+ * water_box_len ) / space_per_water )
396416
397417 exclude_water = get_exclude_water_count (
398418 ads_components_smiles , ads_components_counts ,
399419 bulk_components_smiles , bulk_components_counts
400420 )
401421
402422 wat_num = max (0 , init_wat_num - 2 * exclude_water )
403- wat_num = int (wat_num )
404423 logger .info (f"final number: { wat_num } " )
405424 logger .info ("Read Water Box Length: {0:03f} A" .format (water_box_len ))
406425 logger .info ("Predict Water Number: {0}" .format (wat_num ))
407426
408427 n_vec_a , d1_a , d2_a , n_vec_b , d1_b , d2_b = get_plane_eq (cell_a , cell_b )
409428 logger .info ("Calculate Plane Equation" )
410429
430+ # === Create directory and input ===
411431 if os .path .exists ('gen_water' ):
412432 logger .info ("found gen_water direcotry, now remove it" )
413433 shutil .rmtree ('gen_water' )
@@ -422,24 +442,46 @@ def generate_components_symmetry_box(self,
422442 "filetype xyz" ,
423443 "output watbox.xyz\n "
424444 ]
445+ # === Water ===
425446 water_xyz = os .path .join ("gen_water" , "water.xyz" )
426447 smiles2xyz ('O' ,water_xyz )
427448 lines .append (get_packmol_inp ("water.xyz" , wat_num , n_vec_a , n_vec_b , d1_a , d2_a , d1_b , d2_b , 0 , water_box_len , radius = 1 ))
428449
450+ # === Adsorbates ===
429451 for i , (smiles , count ) in enumerate (zip (ads_components_smiles , ads_components_counts )):
430452 xyz_file = f"ads_component_{ i } .xyz"
431453 xyz_path = os .path .join ("gen_water" , xyz_file )
432454 smiles2xyz (smiles , xyz_path )
455+ # bottom surface
433456 lines .append (get_packmol_inp (xyz_file , count , n_vec_a , n_vec_b , d1_a , d2_a , d1_b , d2_b , 0 , ads_limit , radius = 2 ))
457+ # top surface
434458 lines .append (get_packmol_inp (xyz_file , count , n_vec_a , n_vec_b , d1_a , d2_a , d1_b , d2_b , water_box_len - ads_limit , water_box_len , radius = 2 ))
435459
460+ # === Bulk Components ===
436461 for i , (smiles , count ) in enumerate (zip (bulk_components_smiles , bulk_components_counts )):
437462 xyz_file = f"bulk_component_{ i } .xyz"
438463 xyz_path = os .path .join ("gen_water" , xyz_file )
439464 smiles2xyz (smiles , xyz_path )
440- lines .append (get_packmol_inp (xyz_file , count , n_vec_a , n_vec_b , d1_a , d2_a , d1_b , d2_b , water_box_len / 4 , water_box_len / 2 , radius = 2 ))
441- lines .append (get_packmol_inp (xyz_file , count , n_vec_a , n_vec_b , d1_a , d2_a , d1_b , d2_b , water_box_len / 2 , 3 * water_box_len / 4 , radius = 2 ))
465+ if custom_bulk :
466+ # lower layer
467+ lines .append (get_packmol_inp (xyz_file , count , n_vec_a , n_vec_b ,
468+ d1_a , d2_a , d1_b , d2_b ,
469+ bulk_dw , bulk_up , radius = 2 ))
470+ # symmetric upper layer
471+ lines .append (get_packmol_inp (xyz_file , count , n_vec_a , n_vec_b ,
472+ d1_a , d2_a , d1_b , d2_b ,
473+ water_box_len - bulk_up , water_box_len - bulk_dw , radius = 2 ))
474+ else :
475+ # default symmetric placement in central box
476+ lines .append (get_packmol_inp (xyz_file , count , n_vec_a , n_vec_b ,
477+ d1_a , d2_a , d1_b , d2_b ,
478+ water_box_len / 4 , water_box_len / 2 , radius = 2 ))
479+ lines .append (get_packmol_inp (xyz_file , count , n_vec_a , n_vec_b ,
480+ d1_a , d2_a , d1_b , d2_b ,
481+ water_box_len / 2 , 3 * water_box_len / 4 , radius = 2 ))
482+
442483 f .write ("\n " .join (lines ))
484+ # === Run Packmol ===
443485 os .chdir ("./gen_water" )
444486 os .system ("packmol < gen_wat_box.inp" )
445487 os .chdir ("../" )
0 commit comments