MultiOptPy 1.20.2__py3-none-any.whl → 1.20.3__py3-none-any.whl

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.
@@ -1,6 +1,7 @@
1
1
  import numpy as np
2
2
  import copy
3
3
  import torch
4
+ from collections import deque
4
5
 
5
6
  from multioptpy.Parameters.parameter import atomic_mass, UnitValueLib
6
7
  from multioptpy.Utils.calc_tools import (calc_bond_length_from_vec,
@@ -353,19 +354,34 @@ class ProjectOutConstrain:
353
354
  self.spring_const = 0.0
354
355
  self.projection_vec = None
355
356
  self.arbitrary_proj_vec = None
357
+
358
+ # --- Advanced Adaptive Stiffness Parameters ---
359
+ self.reference_scale = None
360
+ self.alpha_smoothing = 0.7
361
+ self.current_step = 0
362
+
363
+ # --- Multi-Secant Like History Storage ---
364
+ # Store past Q vectors to approximate constraint curvature
365
+ self.history_size = 5
366
+ self.q_history = deque(maxlen=self.history_size)
367
+
368
+ # Feedback parameter
369
+ self.last_shake_iter = 0
370
+
356
371
  return
357
372
 
358
- def initialize(self, geom_num_list, **kwargs):#Bohr
373
+ def initialize(self, geom_num_list, **kwargs):
374
+ # ... [Initialize logic remains the same] ...
359
375
  tmp_init_constraint = []
360
376
  tmp_projection_vec = []
361
377
  tmp_arbitrary_proj_vec = []
378
+
362
379
  for i in range(len(self.constraint_name)):
363
380
  if self.constraint_name[i] == "bond":
364
381
  vec_1 = geom_num_list[self.constraint_atoms_list[i][0] - 1]
365
382
  vec_2 = geom_num_list[self.constraint_atoms_list[i][1] - 1]
366
383
  init_bond_dist = calc_bond_length_from_vec(vec_1, vec_2)
367
384
  tmp_init_constraint.append(init_bond_dist)
368
-
369
385
  elif self.constraint_name[i] == "fbond":
370
386
  divide_index = self.constraint_atoms_list[i][-1]
371
387
  fragm_1 = np.array(self.constraint_atoms_list[i][:divide_index], dtype=np.int32) - 1
@@ -374,33 +390,26 @@ class ProjectOutConstrain:
374
390
  vec_2 = np.mean(geom_num_list[fragm_2], axis=0)
375
391
  init_bond_dist = calc_bond_length_from_vec(vec_1, vec_2)
376
392
  tmp_init_constraint.append(init_bond_dist)
377
-
378
393
  elif self.constraint_name[i] == "angle":
379
394
  vec_1 = geom_num_list[self.constraint_atoms_list[i][0] - 1] - geom_num_list[self.constraint_atoms_list[i][1] - 1]
380
395
  vec_2 = geom_num_list[self.constraint_atoms_list[i][2] - 1] - geom_num_list[self.constraint_atoms_list[i][1] - 1]
381
396
  init_angle = calc_angle_from_vec(vec_1, vec_2)
382
397
  tmp_init_constraint.append(init_angle)
383
-
384
398
  elif self.constraint_name[i] == "dihedral":
385
399
  vec_1 = geom_num_list[self.constraint_atoms_list[i][0] - 1] - geom_num_list[self.constraint_atoms_list[i][1] - 1]
386
400
  vec_2 = geom_num_list[self.constraint_atoms_list[i][1] - 1] - geom_num_list[self.constraint_atoms_list[i][2] - 1]
387
401
  vec_3 = geom_num_list[self.constraint_atoms_list[i][2] - 1] - geom_num_list[self.constraint_atoms_list[i][3] - 1]
388
402
  init_dihedral = calc_dihedral_angle_from_vec(vec_1, vec_2, vec_3)
389
403
  tmp_init_constraint.append(init_dihedral)
390
-
391
404
  elif self.constraint_name[i] == "x":
392
405
  tmp_init_constraint.append(geom_num_list[self.constraint_atoms_list[i][0] - 1][0])
393
-
394
406
  elif self.constraint_name[i] == "y":
395
407
  tmp_init_constraint.append(geom_num_list[self.constraint_atoms_list[i][0] - 1][1])
396
-
397
408
  elif self.constraint_name[i] == "z":
398
409
  tmp_init_constraint.append(geom_num_list[self.constraint_atoms_list[i][0] - 1][2])
399
-
400
410
  elif self.constraint_name[i] == "rot":
401
411
  tmp_init_constraint.append(geom_num_list)
402
-
403
- elif self.constraint_name[i] == "eigvec":#This implementation is only available for "optmain.py (optimization.py)"
412
+ elif self.constraint_name[i] == "eigvec":
404
413
  mode_index = int(self.constraint_atoms_list[i][0])
405
414
  if "hessian" in kwargs:
406
415
  hessian = copy.copy(kwargs["hessian"])
@@ -411,32 +420,23 @@ class ProjectOutConstrain:
411
420
  init_eigvec = eigvecs[:, target_mode]
412
421
  tmp_init_constraint.append(geom_num_list)
413
422
  tmp_projection_vec.append(init_eigvec)
414
-
415
423
  else:
416
- print("error")
417
- raise "error (Hessian is required for eigvec constraint)"
418
-
424
+ raise Exception("error (Hessian is required for eigvec constraint)")
419
425
  elif self.constraint_name[i] == "atoms_pair":
420
426
  atom_label_1 = self.constraint_atoms_list[i][0] - 1
421
427
  atom_label_2 = self.constraint_atoms_list[i][1] - 1
422
-
423
428
  vec = np.zeros_like(geom_num_list)
424
429
  vec[atom_label_1] = -geom_num_list[atom_label_1] + geom_num_list[atom_label_2]
425
430
  vec[atom_label_2] = -geom_num_list[atom_label_2] + geom_num_list[atom_label_1]
426
-
427
431
  norm_vec = np.linalg.norm(vec)
428
432
  if norm_vec < 1.0e-10:
429
- print("error")
430
- raise "error (the distance between the pair atoms is too small)"
433
+ raise Exception("error (the distance between the pair atoms is too small)")
431
434
  unit_vec = vec / norm_vec
432
435
  unit_vec = unit_vec.reshape(-1, 1)
433
436
  tmp_arbitrary_proj_vec.append(unit_vec)
434
437
  tmp_init_constraint.append(geom_num_list)
435
-
436
-
437
438
  else:
438
- print("error")
439
- raise "error (invaild input of constraint conditions)"
439
+ raise Exception("error (invaild input of constraint conditions)")
440
440
 
441
441
  self.projection_vec = tmp_projection_vec
442
442
 
@@ -452,39 +452,35 @@ class ProjectOutConstrain:
452
452
  return ortho
453
453
 
454
454
  self.arbitrary_proj_vec = gram_schmidt(tmp_arbitrary_proj_vec)
455
+
455
456
  if self.init_tag:
456
457
  if len(self.constraint_constant) == 0:
457
458
  self.init_constraint = tmp_init_constraint
458
459
  else:
459
460
  self.init_constraint = []
460
461
  for i in range(len(self.constraint_constant)):
461
- if self.constraint_name[i] == "bond" or self.constraint_name[i] == "fbond" or self.constraint_name[i] == "x" or self.constraint_name[i] == "y" or self.constraint_name[i] == "z":
462
+ if self.constraint_name[i] in ["bond", "fbond", "x", "y", "z"]:
462
463
  self.init_constraint.append(self.constraint_constant[i] / UnitValueLib().bohr2angstroms)
463
- elif self.constraint_name[i] == "angle" or self.constraint_name[i] == "dihedral":
464
+ elif self.constraint_name[i] in ["angle", "dihedral"]:
464
465
  self.init_constraint.append(np.deg2rad(self.constraint_constant[i]))
465
-
466
- elif self.constraint_name[i] == "rot":
467
- self.init_constraint.append(geom_num_list)
468
- elif self.constraint_name[i] == "eigvec":
469
- self.init_constraint.append(geom_num_list)
470
- elif self.constraint_name[i] == "atoms_pair":
466
+ elif self.constraint_name[i] in ["rot", "eigvec", "atoms_pair"]:
471
467
  self.init_constraint.append(geom_num_list)
472
468
  else:
473
- print("error")
474
- raise "error (invaild input of constraint conditions)"
469
+ raise Exception("error (invaild input of constraint conditions)")
475
470
 
476
471
  self.init_tag = False
477
472
 
478
473
  return tmp_init_constraint
479
474
 
480
- def adjust_init_coord(self, coord, hessian=None):#coord:Bohr
475
+ def adjust_init_coord(self, coord, hessian=None):
476
+ if self.init_tag or not hasattr(self, 'init_constraint'):
477
+ self.initialize(coord, hessian=hessian)
481
478
  print("Adjusting initial coordinates... (SHAKE-like method) ")
482
479
  jiter = 10000
483
480
  shake_like_method_threshold = 1.0e-10
484
481
 
485
-
482
+ # ... [Special constraint projection] ...
486
483
  for i_constrain in range(len(self.constraint_name)):
487
-
488
484
  if self.constraint_name[i_constrain] == "rot":
489
485
  print("fix fragment rotation... (Experimental Implementation)")
490
486
  atom_label = self.constraint_atoms_list[i_constrain]
@@ -499,306 +495,272 @@ class ProjectOutConstrain:
499
495
  init_coord = self.init_constraint[i_constrain]
500
496
  coord, _ = Calculationtools().kabsch_algorithm(coord, init_coord)
501
497
 
502
- for jter in range(jiter): # SHAKE-like algorithm
498
+ final_iter_count = 0
499
+ for jter in range(jiter):
500
+ final_iter_count = jter
503
501
  for i_constrain in range(len(self.constraint_name)):
504
502
  if self.constraint_name[i_constrain] == "bond":
505
503
  atom_label_1 = self.constraint_atoms_list[i_constrain][0] - 1
506
504
  atom_label_2 = self.constraint_atoms_list[i_constrain][1] - 1
507
505
  coord = change_atom_distance_both_side(coord, atom_label_1, atom_label_2, self.init_constraint[i_constrain])
508
-
509
506
  elif self.constraint_name[i_constrain] == "fbond":
510
507
  divide_index = self.constraint_atoms_list[i_constrain][-1]
511
508
  fragm_1 = np.array(self.constraint_atoms_list[i_constrain][:divide_index], dtype=np.int32) - 1
512
509
  fragm_2 = np.array(self.constraint_atoms_list[i_constrain][divide_index:], dtype=np.int32) - 1
513
510
  coord = change_fragm_distance_both_side(coord, fragm_1, fragm_2, self.init_constraint[i_constrain])
514
-
515
511
  elif self.constraint_name[i_constrain] == "angle":
516
512
  atom_label_1 = self.constraint_atoms_list[i_constrain][0] - 1
517
513
  atom_label_2 = self.constraint_atoms_list[i_constrain][1] - 1
518
514
  atom_label_3 = self.constraint_atoms_list[i_constrain][2] - 1
519
515
  coord = change_bond_angle_both_side(coord, atom_label_1, atom_label_2, atom_label_3, self.init_constraint[i_constrain])
520
-
521
516
  elif self.constraint_name[i_constrain] == "dihedral":
522
517
  atom_label_1 = self.constraint_atoms_list[i_constrain][0] - 1
523
518
  atom_label_2 = self.constraint_atoms_list[i_constrain][1] - 1
524
519
  atom_label_3 = self.constraint_atoms_list[i_constrain][2] - 1
525
520
  atom_label_4 = self.constraint_atoms_list[i_constrain][3] - 1
526
521
  coord = change_torsion_angle_both_side(coord, atom_label_1, atom_label_2, atom_label_3, atom_label_4, self.init_constraint[i_constrain])
527
-
528
522
  elif self.constraint_name[i_constrain] == "x":
529
523
  atom_label = self.constraint_atoms_list[i_constrain][0] - 1
530
524
  coord[atom_label][0] = self.init_constraint[i_constrain]
531
-
532
-
533
525
  elif self.constraint_name[i_constrain] == "y":
534
526
  atom_label = self.constraint_atoms_list[i_constrain][0] - 1
535
527
  coord[atom_label][1] = self.init_constraint[i_constrain]
536
-
537
528
  elif self.constraint_name[i_constrain] == "z":
538
529
  atom_label = self.constraint_atoms_list[i_constrain][0] - 1
539
530
  coord[atom_label][2] = self.init_constraint[i_constrain]
540
-
541
-
542
- else:
543
- pass
544
531
 
545
532
  tmp_current_coord = self.initialize(coord, hessian=hessian)
546
533
  current_coord = []
547
534
  tmp_init_constraint = []
548
535
  for i_constrain in range(len(self.constraint_name)):
549
- if self.constraint_name[i_constrain] != "rot" and self.constraint_name[i_constrain] != "eigvec" and self.constraint_name[i_constrain] != "atoms_pair":
536
+ if self.constraint_name[i_constrain] not in ["rot", "eigvec", "atoms_pair"]:
550
537
  current_coord.append(tmp_current_coord[i_constrain])
551
538
  tmp_init_constraint.append(self.init_constraint[i_constrain])
552
-
553
539
 
554
540
  current_coord = np.array(current_coord)
555
541
  tmp_init_constraint = np.array(tmp_init_constraint)
556
- if np.linalg.norm(current_coord - tmp_init_constraint) < shake_like_method_threshold:
557
- print("Adjusted!!! : ITR. ", jter)
542
+ if len(current_coord) > 0:
543
+ if np.linalg.norm(current_coord - tmp_init_constraint) < shake_like_method_threshold:
544
+ print(f"Adjusted!!! : ITR. {jter}")
545
+ break
546
+ else:
558
547
  break
559
548
 
560
-
561
-
549
+ self.last_shake_iter = final_iter_count
562
550
  return coord
563
551
 
564
-
565
-
566
-
567
- def calc_project_out_grad(self, coord, grad):# grad: (3N, 1), geom_num_list: (N, 3)
552
+ def _get_all_constraint_vectors(self, coord):
568
553
  natom = len(coord)
569
- tmp_grad = copy.copy(grad)
570
- tmp_b_mat = None
571
- B_mat = None
554
+ B_vectors = []
555
+
572
556
  projection_vec_count = 0
573
557
  arbitrary_vec_count = 0
558
+
574
559
  for i_constrain in range(len(self.constraint_name)):
560
+ vec = None
575
561
  if self.constraint_name[i_constrain] == "bond":
576
- print("Projecting out bond... ")
577
562
  atom_label = [self.constraint_atoms_list[i_constrain][0], self.constraint_atoms_list[i_constrain][1]]
578
- print("atom_label:", atom_label)
579
- tmp_b_mat = torch_B_matrix(torch.tensor(coord, dtype=torch.float64), atom_label, torch_calc_distance).detach().numpy().reshape(1, -1)
563
+ vec = torch_B_matrix(torch.tensor(coord, dtype=torch.float64), atom_label, torch_calc_distance).detach().numpy().reshape(-1)
580
564
 
581
565
  elif self.constraint_name[i_constrain] == "fbond":
582
- print("Projecting out fragment bond... (Experimental Implementation)")
583
566
  divide_index = self.constraint_atoms_list[i_constrain][-1]
584
567
  fragm_1 = torch.tensor(self.constraint_atoms_list[i_constrain][:divide_index], dtype=torch.int64)
585
568
  fragm_2 = torch.tensor(self.constraint_atoms_list[i_constrain][divide_index:], dtype=torch.int64)
586
569
  atom_label = [fragm_1, fragm_2]
587
- print("atom_label:", atom_label)
588
- tmp_b_mat = torch_B_matrix(torch.tensor(coord, dtype=torch.float64), atom_label, torch_calc_fragm_distance).detach().numpy().reshape(1, -1)
570
+ vec = torch_B_matrix(torch.tensor(coord, dtype=torch.float64), atom_label, torch_calc_fragm_distance).detach().numpy().reshape(-1)
589
571
 
590
572
  elif self.constraint_name[i_constrain] == "angle":
591
- print("Projecting out bond angle... ")
592
573
  atom_label = [self.constraint_atoms_list[i_constrain][0], self.constraint_atoms_list[i_constrain][1], self.constraint_atoms_list[i_constrain][2]]
593
- print("atom_label:", atom_label)
594
- tmp_b_mat = torch_B_matrix(torch.tensor(coord, dtype=torch.float64), atom_label, torch_calc_angle).detach().numpy().reshape(1, -1)
574
+ vec = torch_B_matrix(torch.tensor(coord, dtype=torch.float64), atom_label, torch_calc_angle).detach().numpy().reshape(-1)
595
575
 
596
576
  elif self.constraint_name[i_constrain] == "dihedral":
597
- print("Projecting out dihedral angle... ")
598
577
  atom_label = [self.constraint_atoms_list[i_constrain][0], self.constraint_atoms_list[i_constrain][1], self.constraint_atoms_list[i_constrain][2], self.constraint_atoms_list[i_constrain][3]]
599
- print("atom_label:", atom_label)
600
- tmp_b_mat = torch_B_matrix(torch.tensor(coord, dtype=torch.float64), atom_label, torch_calc_dihedral_angle).detach().numpy().reshape(1, -1)
578
+ vec = torch_B_matrix(torch.tensor(coord, dtype=torch.float64), atom_label, torch_calc_dihedral_angle).detach().numpy().reshape(-1)
601
579
 
602
580
  elif self.constraint_name[i_constrain] == "x":
603
- print("Projecting out x coordinate... ")
604
581
  atom_label = self.constraint_atoms_list[i_constrain][0]
605
- print("atom_label:", atom_label)
606
- tmp_b_mat = torch.zeros(1, 3*natom)
607
- tmp_b_mat[0][3*(atom_label - 1) + 0] = 1.0
582
+ vec = np.zeros(3*natom)
583
+ vec[3*(atom_label - 1) + 0] = 1.0
608
584
 
609
585
  elif self.constraint_name[i_constrain] == "y":
610
- print("Projecting out y coordinate... ")
611
- tmp_b_mat = torch.zeros(1, 3*natom)
612
-
613
586
  atom_label = self.constraint_atoms_list[i_constrain][0]
614
- print("atom_label:", atom_label)
615
- tmp_b_mat[0][3*(atom_label - 1) + 1] = 1.0
587
+ vec = np.zeros(3*natom)
588
+ vec[3*(atom_label - 1) + 1] = 1.0
616
589
 
617
590
  elif self.constraint_name[i_constrain] == "z":
618
- print("Projecting out z coordinate... ")
619
- tmp_b_mat = torch.zeros(1, 3*natom)
620
591
  atom_label = self.constraint_atoms_list[i_constrain][0]
621
- print("atom_label:", atom_label)
622
- tmp_b_mat[0][3*(atom_label - 1) + 2] = 1.0
592
+ vec = np.zeros(3*natom)
593
+ vec[3*(atom_label - 1) + 2] = 1.0
594
+
623
595
  elif self.constraint_name[i_constrain] == "rot":
624
- print("Projecting out fragment rotation... (Experimental Implementation)")
625
596
  atom_label = self.constraint_atoms_list[i_constrain]
626
- print("atom_label:", atom_label)
627
- tmp_b_mat = constract_partial_rot_B_mat(coord, atom_label)
628
-
597
+ rot_B = constract_partial_rot_B_mat(coord, atom_label)
598
+ for row in rot_B:
599
+ B_vectors.append(row)
600
+ vec = None
601
+
629
602
  elif self.constraint_name[i_constrain] == "eigvec":
630
- print("Projecting out eigenvector... (Experimental Implementation)")
631
- tmp_proj_vec = self.projection_vec[projection_vec_count]
632
- print("mode index:", self.constraint_atoms_list[i_constrain][0])
633
- projection_vec_count += 1
634
- tmp_grad = np.dot((np.eye(len(tmp_proj_vec)) - np.outer(tmp_proj_vec, tmp_proj_vec)), tmp_grad.reshape(3*natom, 1))
635
- tmp_b_mat = None
603
+ if projection_vec_count < len(self.projection_vec):
604
+ vec = self.projection_vec[projection_vec_count]
605
+ projection_vec_count += 1
606
+
636
607
  elif self.constraint_name[i_constrain] == "atoms_pair":
637
- if len(self.arbitrary_proj_vec) < arbitrary_vec_count + 1:
638
- pass
639
- else:
640
- print("Projecting out translation along the vector between the pair atoms... (Experimental Implementation)")
641
- tmp_arbitrary_proj_vec = self.arbitrary_proj_vec[arbitrary_vec_count]
642
- print("atom labels:", self.constraint_atoms_list[i_constrain])
608
+ if arbitrary_vec_count < len(self.arbitrary_proj_vec):
609
+ vec = self.arbitrary_proj_vec[arbitrary_vec_count].reshape(-1)
643
610
  arbitrary_vec_count += 1
644
- tmp_grad = np.dot((np.eye(len(tmp_arbitrary_proj_vec)) - np.outer(tmp_arbitrary_proj_vec, tmp_arbitrary_proj_vec)), tmp_grad.reshape(3*natom, 1))
645
- tmp_b_mat = None
646
- else:
647
- print("error")
648
- raise "error (invaild input of constraint conditions)"
611
+
612
+ if vec is not None:
613
+ B_vectors.append(vec)
649
614
 
650
- if tmp_b_mat is not None:
651
- if 'B_mat' not in locals() or B_mat is None:
652
- B_mat = tmp_b_mat
653
- else:
654
- B_mat = np.vstack((B_mat, tmp_b_mat))
615
+ if len(B_vectors) == 0:
616
+ return None
617
+
618
+ return np.array(B_vectors)
619
+
620
+ def _get_orthonormal_basis(self, coord):
621
+ """
622
+ Uses SVD to find the orthonormal basis Q for constraints.
623
+ """
624
+ B_mat = self._get_all_constraint_vectors(coord)
625
+ if B_mat is None: return None
626
+ try:
627
+ U, S, Vt = np.linalg.svd(B_mat.T, full_matrices=False)
628
+ except np.linalg.LinAlgError:
629
+ return None
630
+ threshold = 1e-6
631
+ rank = np.sum(S > threshold)
632
+ if rank == 0: return None
633
+ Q = U[:, :rank]
634
+ return Q
635
+
636
+ def calc_project_out_grad(self, coord, grad):
637
+ """
638
+ Projects gradient + Re-orthogonalization (Purification)
639
+ """
640
+ natom = len(coord)
641
+ grad_vec = grad.reshape(3*natom, 1)
642
+
643
+ Q = self._get_orthonormal_basis(coord)
644
+ if Q is None: return grad
645
+
646
+ # P = I - Q Q^T
647
+ qt_g = np.dot(Q.T, grad_vec)
648
+ grad_proj = grad_vec - np.dot(Q, qt_g)
655
649
 
656
- if B_mat is None:
657
- return tmp_grad.reshape(natom, 3)
658
- int_grad = calc_int_grad_from_pBmat(tmp_grad.reshape(3*natom, 1), B_mat)
659
- projection_grad = calc_cart_grad_from_pBmat(-1*int_grad, B_mat)
660
- proj_grad = tmp_grad.reshape(3*natom, 1) + projection_grad
661
- proj_grad = proj_grad.reshape(natom, 3)
650
+ # Purification
651
+ qt_g_residual = np.dot(Q.T, grad_proj)
652
+ grad_proj = grad_proj - np.dot(Q, qt_g_residual)
662
653
 
663
- return proj_grad
654
+ return grad_proj.reshape(natom, 3)
664
655
 
665
- def calc_project_out_hess(self, coord, grad, hessian):# hessian:(3N, 3N), B_g: (3N, 1), geom_num_list: (N, 3)
656
+ def calc_project_out_hess(self, coord, grad, hessian):
657
+ """
658
+ Multi-Secant Like Subspace Projection Strategy.
659
+ Constructs a stiffness wall using:
660
+ 1. Current Q (Hard Wall)
661
+ 2. Orthogonalized History of Qs (Curvature Guiding Wall)
662
+ """
666
663
  natom = len(coord)
667
- tmp_grad = copy.copy(grad)
668
- tmp_hessian = copy.copy(hessian)
669
- tmp_b_mat = None
670
- tmp_b_mat_1st_derivative = None
671
- projection_vec_count = 0
672
- arbitrary_vec_count = 0
673
-
674
- # --- FIX: Calculate grad_rms from the ORIGINAL gradient ---
675
- grad_rms = np.sqrt(np.mean(grad**2))
676
- grad_rms_threshold = 1.0e-3
677
- # ---------------------------------------------------------
678
-
679
- for i_constrain in range(len(self.constraint_name)):
680
- if self.constraint_name[i_constrain] == "bond":
681
- atom_label = [self.constraint_atoms_list[i_constrain][0], self.constraint_atoms_list[i_constrain][1]]
682
- tmp_b_mat = torch_B_matrix(torch.tensor(coord, dtype=torch.float64), atom_label, torch_calc_distance).detach().numpy().reshape(1, -1)
683
- tmp_b_mat_1st_derivative = torch_B_matrix_derivative(torch.tensor(coord, dtype=torch.float64), atom_label, torch_calc_distance).detach().numpy()
684
-
685
-
686
- elif self.constraint_name[i_constrain] == "fbond":
687
- divide_index = self.constraint_atoms_list[i_constrain][-1]
688
- fragm_1 = torch.tensor(self.constraint_atoms_list[i_constrain][:divide_index], dtype=torch.int64)
689
- fragm_2 = torch.tensor(self.constraint_atoms_list[i_constrain][divide_index:], dtype=torch.int64)
690
- atom_label = [fragm_1, fragm_2]
691
- tmp_b_mat = torch_B_matrix(torch.tensor(coord, dtype=torch.float64), atom_label, torch_calc_fragm_distance).detach().numpy().reshape(1, -1)
692
- tmp_b_mat_1st_derivative = torch_B_matrix_derivative(torch.tensor(coord, dtype=torch.float64), atom_label, torch_calc_fragm_distance).detach().numpy()
693
-
694
-
695
- elif self.constraint_name[i_constrain] == "angle":
696
- atom_label = [self.constraint_atoms_list[i_constrain][0], self.constraint_atoms_list[i_constrain][1], self.constraint_atoms_list[i_constrain][2]]
697
- tmp_b_mat = torch_B_matrix(torch.tensor(coord, dtype=torch.float64), atom_label, torch_calc_angle).detach().numpy().reshape(1, -1)
698
- tmp_b_mat_1st_derivative = torch_B_matrix_derivative(torch.tensor(coord, dtype=torch.float64), atom_label, torch_calc_angle).detach().numpy()
699
-
700
- elif self.constraint_name[i_constrain] == "dihedral":
701
- atom_label = [self.constraint_atoms_list[i_constrain][0], self.constraint_atoms_list[i_constrain][1], self.constraint_atoms_list[i_constrain][2], self.constraint_atoms_list[i_constrain][3]]
702
- tmp_b_mat = torch_B_matrix(torch.tensor(coord, dtype=torch.float64), atom_label, torch_calc_dihedral_angle).detach().numpy().reshape(1, -1)
703
- tmp_b_mat_1st_derivative = torch_B_matrix_derivative(torch.tensor(coord, dtype=torch.float64), atom_label, torch_calc_dihedral_angle).detach().numpy()
704
-
705
- elif self.constraint_name[i_constrain] == "x":
706
- atom_label = self.constraint_atoms_list[i_constrain][0]
707
- tmp_b_mat = torch.zeros(1, 3*natom)
708
- tmp_b_mat[0][3*(atom_label - 1) + 0] = 1.0
709
- tmp_b_mat_1st_derivative = torch.zeros_like(torch_B_matrix_derivative(torch.tensor(coord, dtype=torch.float64), [atom_label,atom_label], torch_calc_distance)).detach().numpy()
710
-
711
- elif self.constraint_name[i_constrain] == "y":
712
- atom_label = self.constraint_atoms_list[i_constrain][0]
713
- tmp_b_mat = torch.zeros(1, 3*natom)
714
- tmp_b_mat[0][3*(atom_label - 1) + 1] = 1.0
715
- tmp_b_mat_1st_derivative = torch.zeros_like(torch_B_matrix_derivative(torch.tensor(coord, dtype=torch.float64), [atom_label,atom_label], torch_calc_distance)).detach().numpy()
716
-
717
- elif self.constraint_name[i_constrain] == "z":
718
- atom_label = self.constraint_atoms_list[i_constrain][0]
719
- tmp_b_mat = torch.zeros(1, 3*natom)
720
- tmp_b_mat[0][3*(atom_label - 1) + 2] = 1.0
721
- tmp_b_mat_1st_derivative = torch.zeros_like(torch_B_matrix_derivative(torch.tensor(coord, dtype=torch.float64), [atom_label,atom_label], torch_calc_distance)).detach().numpy()
722
-
723
- elif self.constraint_name[i_constrain] == "rot":
724
- tmp_b_mat_1st_derivative = None
725
- ## atom_label = self.constraint_atoms_list[i_constrain]
726
- # tmp_b_mat = constract_partial_rot_B_mat(coord, atom_label)
727
- # tmp_b_mat_1st_derivative = torch.zeros_like(torch_B_matrix_derivative(torch.tensor(coord, dtype=torch.float64), [atom_label[0],atom_label[0]], torch_calc_distance)).detach().numpy() #TODO: implement this function
728
-
729
- elif self.constraint_name[i_constrain] == "eigvec":
730
- tmp_proj_vec = self.projection_vec[projection_vec_count]
731
- projection_vec_count += 1
732
- tmp_grad = np.dot((np.eye(len(tmp_proj_vec)) - np.outer(tmp_proj_vec, tmp_proj_vec)), tmp_grad.reshape(3*natom, 1))
733
- tmp_hessian = np.dot((np.eye(len(tmp_proj_vec)) - np.outer(tmp_proj_vec, tmp_proj_vec)), tmp_hessian)
734
- tmp_b_mat_1st_derivative = None
735
- elif self.constraint_name[i_constrain] == "atoms_pair":
736
- tmp_arbitrary_proj_vec = self.arbitrary_proj_vec[arbitrary_vec_count]
737
- arbitrary_vec_count += 1
738
- tmp_grad = np.dot((np.eye(len(tmp_arbitrary_proj_vec)) - np.outer(tmp_arbitrary_proj_vec, tmp_arbitrary_proj_vec)), tmp_grad.reshape(3*natom, 1))
739
- tmp_hessian = np.dot(np.dot((np.eye(len(tmp_arbitrary_proj_vec)) - np.outer(tmp_arbitrary_proj_vec, tmp_arbitrary_proj_vec)), tmp_hessian), (np.eye(len(tmp_arbitrary_proj_vec)) - np.outer(tmp_arbitrary_proj_vec, tmp_arbitrary_proj_vec)).T)
740
- tmp_b_mat_1st_derivative = None
664
+ Q = self._get_orthonormal_basis(coord)
665
+
666
+ if Q is None:
667
+ return hessian
741
668
 
742
- else:
743
- print("error")
744
- raise "error (invaild input of constraint conditions)"
669
+ # --- 1. Update History ---
670
+ # Add current Q to history for future steps
671
+ self.q_history.append(Q)
672
+
673
+ # --- 2. Construct Augmented Projector ---
674
+ # Start with current Q (Basis of current constraints)
675
+ # We need to find "Where the constraints WERE but ARE NOT ANYMORE".
676
+ # This difference represents the curvature of the constraint surface.
677
+
678
+ # List of basis vectors to exclude (Current + History Residuals)
679
+ exclusion_vectors = []
680
+
681
+ # Add current basis vectors
682
+ for i in range(Q.shape[1]):
683
+ exclusion_vectors.append(Q[:, i])
745
684
 
746
-
747
- if i_constrain == 0:
748
- B_mat = tmp_b_mat
749
- B_mat_1st_derivative = tmp_b_mat_1st_derivative
750
- else:
751
- # FIX: Handle vstack/concatenate when B_mat or B_mat_1st_derivative is None
752
- # (e.g., if 'eigvec' is the first constraint)
753
- if B_mat is None:
754
- B_mat = tmp_b_mat
755
- elif tmp_b_mat is not None:
756
- B_mat = np.vstack((B_mat, tmp_b_mat))
685
+ # Add orthogonal components from history (Gram-Schmidt style)
686
+ for Q_hist in self.q_history:
687
+ # We treat Q_hist as a block of vectors
688
+ for i in range(Q_hist.shape[1]):
689
+ vec = Q_hist[:, i]
757
690
 
758
- if B_mat_1st_derivative is None:
759
- B_mat_1st_derivative = tmp_b_mat_1st_derivative
760
- elif tmp_b_mat_1st_derivative is not None:
761
- B_mat_1st_derivative = np.concatenate((B_mat_1st_derivative, tmp_b_mat_1st_derivative), axis=2)
691
+ # Orthogonalize against all vectors currently in exclusion list
692
+ for basis_vec in exclusion_vectors:
693
+ proj = np.dot(vec, basis_vec)
694
+ vec = vec - proj * basis_vec
762
695
 
763
-
764
- # --- FIX: Main projection logic based on RMS and B_mat existence ---
696
+ # If residual is significant, it's a new direction (curvature)
697
+ norm = np.linalg.norm(vec)
698
+ if norm > 0.1: # Threshold to ignore noise/duplicates
699
+ vec = vec / norm
700
+ exclusion_vectors.append(vec)
701
+
702
+ # Now exclusion_vectors contains [Q_current, Q_history_perp, ...]
703
+ # We build two projectors:
704
+ # P_hard: Defined by Q_current (Stiffness: High)
705
+ # P_soft: Defined by Q_history_perp (Stiffness: Medium)
706
+
707
+ num_hard = Q.shape[1]
765
708
 
766
- # Case 1: No B-matrix constraints were added (e.g., only 'eigvec'/'atoms_pair')
767
- if B_mat is None:
768
- proj_hess = tmp_hessian
709
+ P_hard = np.zeros_like(hessian)
710
+ P_soft = np.zeros_like(hessian)
711
+
712
+ for idx, vec in enumerate(exclusion_vectors):
713
+ outer_prod = np.outer(vec, vec)
714
+ if idx < num_hard:
715
+ P_hard += outer_prod
716
+ else:
717
+ P_soft += outer_prod
718
+
719
+ # --- 3. Physical Projection ---
720
+ # We strictly remove curvature along CURRENT constraints
721
+ # P_hard acts as the Projector P = Q Q^T
722
+ Proj_H = np.dot(P_hard, hessian)
723
+ H_Proj = np.dot(hessian, P_hard)
724
+ Proj_H_Proj = np.dot(P_hard, H_Proj)
769
725
 
770
- # Case 2: B-matrix exists, but the *last* constraint had no B'' (e.g., 'rot'/'eigvec' was last)
771
- elif tmp_b_mat_1st_derivative is None:
772
- proj_hess = tmp_hessian
726
+ PHP = hessian - Proj_H - H_Proj + Proj_H_Proj
773
727
 
774
- # Case 3: B-matrix and B'' (for the last constraint) exist
728
+ # --- 4. Adaptive Stiffness Calculation ---
729
+ self.current_step += 1
730
+ current_max_diag = np.max(np.abs(np.diag(hessian)))
731
+ current_scale = max(current_max_diag, 0.5)
732
+
733
+ if self.reference_scale is None:
734
+ self.reference_scale = current_scale
775
735
  else:
776
- # Case 3a: Gradient RMS is low (Stationary Point)
777
- if grad_rms < grad_rms_threshold:
778
- print(f"Gradient RMS ({grad_rms:.2e}) < {grad_rms_threshold:.2e}. Using stationary projection logic (fallback).")
779
- # Fallback to the 'eigvec'-projected Hessian, per "mix-in" logic
780
- proj_hess = tmp_hessian
736
+ self.reference_scale = (self.alpha_smoothing * self.reference_scale +
737
+ (1.0 - self.alpha_smoothing) * current_scale)
738
+
739
+ # Multipliers
740
+
741
+ hard_mult = 100.0
742
+
743
+ # Soft wall is weaker (e.g., 20% of hard wall)
744
+ # This guides the optimizer without locking it up
745
+ soft_mult = hard_mult * 0.2
781
746
 
782
- # Case 3b: Gradient RMS is high (Non-Stationary Point)
783
- else:
784
- print(f"Gradient RMS ({grad_rms:.2e}) >= {grad_rms_threshold:.2e}. Using non-stationary projection.")
785
- # Use the 'eigvec'-projected tmp_grad for int_grad calculation
786
- int_grad = calc_int_grad_from_pBmat(tmp_grad.reshape(3*natom, 1), B_mat)
787
- # Use the 'eigvec'-projected tmp_hessian for non-stationary calculation
788
- proj_hess = tmp_hessian
789
- int_hess = calc_int_hess_from_pBmat_for_non_stationary_point(tmp_hessian, B_mat, B_mat_1st_derivative, int_grad)
790
- couple_hess = calc_int_cart_coupling_hess_from_pBmat_for_non_stationary_point(tmp_hessian, B_mat, B_mat_1st_derivative, int_grad)
791
- #hess_x = calc_cart_hess_from_pBmat_for_non_stationary_point(tmp_hessian, B_mat, B_mat_1st_derivative, int_grad)
792
- try:
793
- int_hess_inv = np.linalg.pinv(int_hess)
794
- except np.linalg.LinAlgError:
795
- int_hess = int_hess + np.eye(len(int_hess)) * 1e-10
796
- int_hess_inv = np.linalg.pinv(int_hess)
797
- eff_hess = np.dot(couple_hess.T, np.dot(int_hess_inv, couple_hess))
798
- proj_hess = proj_hess - eff_hess
799
-
800
- return proj_hess
747
+ stiffness_hard = self.reference_scale * hard_mult
748
+ stiffness_soft = self.reference_scale * soft_mult
749
+
750
+ # --- 5. Final Effective Hessian ---
751
+ # H_eff = PHP + k_hard * P_hard + k_soft * P_soft
752
+ H_eff = PHP + stiffness_hard * P_hard + stiffness_soft * P_soft
753
+
754
+ return H_eff
801
755
 
756
+ def reset_stiffness(self):
757
+ self.reference_scale = None
758
+ self.current_step = 0
759
+ self.q_history.clear()
760
+ self.last_shake_iter = 0
761
+
762
+
763
+
802
764
  def constract_partial_rot_B_mat(geom_num_list, target_atoms_list):#1-based index
803
765
  target_atoms_list = np.array(target_atoms_list, dtype=np.int32) - 1
804
766
  center = np.mean(geom_num_list[target_atoms_list], axis=0)
@@ -818,7 +780,8 @@ def constract_partial_rot_B_mat(geom_num_list, target_atoms_list):#1-based index
818
780
  B_mat[3*j+2][3*i+2] = 0.0
819
781
 
820
782
  return B_mat
821
-
783
+
784
+
822
785
  def constract_partial_rot_B_mat_1st_derivative(geom_num_list, target_atoms_list):#1-based index
823
786
  return
824
787