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.
- multioptpy/Calculator/ase_calculation_tools.py +13 -0
- multioptpy/Calculator/ase_tools/fairchem.py +12 -7
- multioptpy/Constraint/constraint_condition.py +208 -245
- multioptpy/ModelFunction/binary_image_ts_search_model_function.py +111 -18
- multioptpy/ModelFunction/opt_meci.py +94 -27
- multioptpy/ModelFunction/opt_mesx.py +47 -15
- multioptpy/ModelFunction/opt_mesx_2.py +35 -18
- multioptpy/Optimizer/crsirfo.py +182 -0
- multioptpy/Optimizer/mf_rsirfo.py +266 -0
- multioptpy/Optimizer/mode_following.py +273 -0
- multioptpy/Utils/calc_tools.py +1 -0
- multioptpy/fileio.py +13 -6
- multioptpy/interface.py +3 -2
- multioptpy/optimization.py +2139 -1259
- multioptpy/optimizer.py +158 -6
- {multioptpy-1.20.2.dist-info → multioptpy-1.20.3.dist-info}/METADATA +497 -438
- {multioptpy-1.20.2.dist-info → multioptpy-1.20.3.dist-info}/RECORD +21 -18
- {multioptpy-1.20.2.dist-info → multioptpy-1.20.3.dist-info}/WHEEL +0 -0
- {multioptpy-1.20.2.dist-info → multioptpy-1.20.3.dist-info}/entry_points.txt +0 -0
- {multioptpy-1.20.2.dist-info → multioptpy-1.20.3.dist-info}/licenses/LICENSE +0 -0
- {multioptpy-1.20.2.dist-info → multioptpy-1.20.3.dist-info}/top_level.txt +0 -0
|
@@ -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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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]
|
|
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]
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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]
|
|
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
|
|
557
|
-
|
|
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
|
-
|
|
570
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
606
|
-
|
|
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
|
-
|
|
615
|
-
|
|
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
|
-
|
|
622
|
-
|
|
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
|
-
|
|
627
|
-
|
|
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
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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)
|
|
638
|
-
|
|
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
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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
|
-
|
|
657
|
-
|
|
658
|
-
|
|
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
|
|
654
|
+
return grad_proj.reshape(natom, 3)
|
|
664
655
|
|
|
665
|
-
def calc_project_out_hess(self, coord, grad, hessian)
|
|
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
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
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
|
-
|
|
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
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
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
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
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
|
|