MultiOptPy 1.20.2__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/__init__.py +0 -0
- multioptpy/Calculator/ase_calculation_tools.py +424 -0
- multioptpy/Calculator/ase_tools/__init__.py +0 -0
- multioptpy/Calculator/ase_tools/fairchem.py +28 -0
- multioptpy/Calculator/ase_tools/gamess.py +19 -0
- multioptpy/Calculator/ase_tools/gaussian.py +165 -0
- multioptpy/Calculator/ase_tools/mace.py +28 -0
- multioptpy/Calculator/ase_tools/mopac.py +19 -0
- multioptpy/Calculator/ase_tools/nwchem.py +31 -0
- multioptpy/Calculator/ase_tools/orca.py +22 -0
- multioptpy/Calculator/ase_tools/pygfn0.py +37 -0
- multioptpy/Calculator/dxtb_calculation_tools.py +344 -0
- multioptpy/Calculator/emt_calculation_tools.py +458 -0
- multioptpy/Calculator/gpaw_calculation_tools.py +183 -0
- multioptpy/Calculator/lj_calculation_tools.py +314 -0
- multioptpy/Calculator/psi4_calculation_tools.py +334 -0
- multioptpy/Calculator/pwscf_calculation_tools.py +189 -0
- multioptpy/Calculator/pyscf_calculation_tools.py +327 -0
- multioptpy/Calculator/sqm1_calculation_tools.py +611 -0
- multioptpy/Calculator/sqm2_calculation_tools.py +376 -0
- multioptpy/Calculator/tblite_calculation_tools.py +352 -0
- multioptpy/Calculator/tersoff_calculation_tools.py +818 -0
- multioptpy/Constraint/__init__.py +0 -0
- multioptpy/Constraint/constraint_condition.py +834 -0
- multioptpy/Coordinate/__init__.py +0 -0
- multioptpy/Coordinate/polar_coordinate.py +199 -0
- multioptpy/Coordinate/redundant_coordinate.py +638 -0
- multioptpy/IRC/__init__.py +0 -0
- multioptpy/IRC/converge_criteria.py +28 -0
- multioptpy/IRC/dvv.py +544 -0
- multioptpy/IRC/euler.py +439 -0
- multioptpy/IRC/hpc.py +564 -0
- multioptpy/IRC/lqa.py +540 -0
- multioptpy/IRC/modekill.py +662 -0
- multioptpy/IRC/rk4.py +579 -0
- multioptpy/Interpolation/__init__.py +0 -0
- multioptpy/Interpolation/adaptive_interpolation.py +283 -0
- multioptpy/Interpolation/binomial_interpolation.py +179 -0
- multioptpy/Interpolation/geodesic_interpolation.py +785 -0
- multioptpy/Interpolation/interpolation.py +156 -0
- multioptpy/Interpolation/linear_interpolation.py +473 -0
- multioptpy/Interpolation/savitzky_golay_interpolation.py +252 -0
- multioptpy/Interpolation/spline_interpolation.py +353 -0
- multioptpy/MD/__init__.py +0 -0
- multioptpy/MD/thermostat.py +185 -0
- multioptpy/MEP/__init__.py +0 -0
- multioptpy/MEP/pathopt_bneb_force.py +443 -0
- multioptpy/MEP/pathopt_dmf_force.py +448 -0
- multioptpy/MEP/pathopt_dneb_force.py +130 -0
- multioptpy/MEP/pathopt_ewbneb_force.py +207 -0
- multioptpy/MEP/pathopt_gpneb_force.py +512 -0
- multioptpy/MEP/pathopt_lup_force.py +113 -0
- multioptpy/MEP/pathopt_neb_force.py +225 -0
- multioptpy/MEP/pathopt_nesb_force.py +205 -0
- multioptpy/MEP/pathopt_om_force.py +153 -0
- multioptpy/MEP/pathopt_qsm_force.py +174 -0
- multioptpy/MEP/pathopt_qsmv2_force.py +304 -0
- multioptpy/ModelFunction/__init__.py +7 -0
- multioptpy/ModelFunction/avoiding_model_function.py +29 -0
- multioptpy/ModelFunction/binary_image_ts_search_model_function.py +47 -0
- multioptpy/ModelFunction/conical_model_function.py +26 -0
- multioptpy/ModelFunction/opt_meci.py +50 -0
- multioptpy/ModelFunction/opt_mesx.py +47 -0
- multioptpy/ModelFunction/opt_mesx_2.py +49 -0
- multioptpy/ModelFunction/seam_model_function.py +27 -0
- multioptpy/ModelHessian/__init__.py +0 -0
- multioptpy/ModelHessian/approx_hessian.py +147 -0
- multioptpy/ModelHessian/calc_params.py +227 -0
- multioptpy/ModelHessian/fischer.py +236 -0
- multioptpy/ModelHessian/fischerd3.py +360 -0
- multioptpy/ModelHessian/fischerd4.py +398 -0
- multioptpy/ModelHessian/gfn0xtb.py +633 -0
- multioptpy/ModelHessian/gfnff.py +709 -0
- multioptpy/ModelHessian/lindh.py +165 -0
- multioptpy/ModelHessian/lindh2007d2.py +707 -0
- multioptpy/ModelHessian/lindh2007d3.py +822 -0
- multioptpy/ModelHessian/lindh2007d4.py +1030 -0
- multioptpy/ModelHessian/morse.py +106 -0
- multioptpy/ModelHessian/schlegel.py +144 -0
- multioptpy/ModelHessian/schlegeld3.py +322 -0
- multioptpy/ModelHessian/schlegeld4.py +559 -0
- multioptpy/ModelHessian/shortrange.py +346 -0
- multioptpy/ModelHessian/swartd2.py +496 -0
- multioptpy/ModelHessian/swartd3.py +706 -0
- multioptpy/ModelHessian/swartd4.py +918 -0
- multioptpy/ModelHessian/tshess.py +40 -0
- multioptpy/Optimizer/QHAdam.py +61 -0
- multioptpy/Optimizer/__init__.py +0 -0
- multioptpy/Optimizer/abc_fire.py +83 -0
- multioptpy/Optimizer/adabelief.py +58 -0
- multioptpy/Optimizer/adabound.py +68 -0
- multioptpy/Optimizer/adadelta.py +65 -0
- multioptpy/Optimizer/adaderivative.py +56 -0
- multioptpy/Optimizer/adadiff.py +68 -0
- multioptpy/Optimizer/adafactor.py +70 -0
- multioptpy/Optimizer/adam.py +65 -0
- multioptpy/Optimizer/adamax.py +62 -0
- multioptpy/Optimizer/adamod.py +83 -0
- multioptpy/Optimizer/adamw.py +65 -0
- multioptpy/Optimizer/adiis.py +523 -0
- multioptpy/Optimizer/afire_neb.py +282 -0
- multioptpy/Optimizer/block_hessian_update.py +709 -0
- multioptpy/Optimizer/c2diis.py +491 -0
- multioptpy/Optimizer/component_wise_scaling.py +405 -0
- multioptpy/Optimizer/conjugate_gradient.py +82 -0
- multioptpy/Optimizer/conjugate_gradient_neb.py +345 -0
- multioptpy/Optimizer/coordinate_locking.py +405 -0
- multioptpy/Optimizer/dic_rsirfo.py +1015 -0
- multioptpy/Optimizer/ediis.py +417 -0
- multioptpy/Optimizer/eve.py +76 -0
- multioptpy/Optimizer/fastadabelief.py +61 -0
- multioptpy/Optimizer/fire.py +77 -0
- multioptpy/Optimizer/fire2.py +249 -0
- multioptpy/Optimizer/fire_neb.py +92 -0
- multioptpy/Optimizer/gan_step.py +486 -0
- multioptpy/Optimizer/gdiis.py +609 -0
- multioptpy/Optimizer/gediis.py +203 -0
- multioptpy/Optimizer/geodesic_step.py +433 -0
- multioptpy/Optimizer/gpmin.py +633 -0
- multioptpy/Optimizer/gpr_step.py +364 -0
- multioptpy/Optimizer/gradientdescent.py +78 -0
- multioptpy/Optimizer/gradientdescent_neb.py +52 -0
- multioptpy/Optimizer/hessian_update.py +433 -0
- multioptpy/Optimizer/hybrid_rfo.py +998 -0
- multioptpy/Optimizer/kdiis.py +625 -0
- multioptpy/Optimizer/lars.py +21 -0
- multioptpy/Optimizer/lbfgs.py +253 -0
- multioptpy/Optimizer/lbfgs_neb.py +355 -0
- multioptpy/Optimizer/linesearch.py +236 -0
- multioptpy/Optimizer/lookahead.py +40 -0
- multioptpy/Optimizer/nadam.py +64 -0
- multioptpy/Optimizer/newton.py +200 -0
- multioptpy/Optimizer/prodigy.py +70 -0
- multioptpy/Optimizer/purtubation.py +16 -0
- multioptpy/Optimizer/quickmin_neb.py +245 -0
- multioptpy/Optimizer/radam.py +75 -0
- multioptpy/Optimizer/rfo_neb.py +302 -0
- multioptpy/Optimizer/ric_rfo.py +842 -0
- multioptpy/Optimizer/rl_step.py +627 -0
- multioptpy/Optimizer/rmspropgrave.py +65 -0
- multioptpy/Optimizer/rsirfo.py +1647 -0
- multioptpy/Optimizer/rsprfo.py +1056 -0
- multioptpy/Optimizer/sadam.py +60 -0
- multioptpy/Optimizer/samsgrad.py +63 -0
- multioptpy/Optimizer/tr_lbfgs.py +678 -0
- multioptpy/Optimizer/trim.py +273 -0
- multioptpy/Optimizer/trust_radius.py +207 -0
- multioptpy/Optimizer/trust_radius_neb.py +121 -0
- multioptpy/Optimizer/yogi.py +60 -0
- multioptpy/OtherMethod/__init__.py +0 -0
- multioptpy/OtherMethod/addf.py +1150 -0
- multioptpy/OtherMethod/dimer.py +895 -0
- multioptpy/OtherMethod/elastic_image_pair.py +629 -0
- multioptpy/OtherMethod/modelfunction.py +456 -0
- multioptpy/OtherMethod/newton_traj.py +454 -0
- multioptpy/OtherMethod/twopshs.py +1095 -0
- multioptpy/PESAnalyzer/__init__.py +0 -0
- multioptpy/PESAnalyzer/calc_irc_curvature.py +125 -0
- multioptpy/PESAnalyzer/cmds_analysis.py +152 -0
- multioptpy/PESAnalyzer/koopman_analysis.py +268 -0
- multioptpy/PESAnalyzer/pca_analysis.py +314 -0
- multioptpy/Parameters/__init__.py +0 -0
- multioptpy/Parameters/atomic_mass.py +20 -0
- multioptpy/Parameters/atomic_number.py +22 -0
- multioptpy/Parameters/covalent_radii.py +44 -0
- multioptpy/Parameters/d2.py +61 -0
- multioptpy/Parameters/d3.py +63 -0
- multioptpy/Parameters/d4.py +103 -0
- multioptpy/Parameters/dreiding.py +34 -0
- multioptpy/Parameters/gfn0xtb_param.py +137 -0
- multioptpy/Parameters/gfnff_param.py +315 -0
- multioptpy/Parameters/gnb.py +104 -0
- multioptpy/Parameters/parameter.py +22 -0
- multioptpy/Parameters/uff.py +72 -0
- multioptpy/Parameters/unit_values.py +20 -0
- multioptpy/Potential/AFIR_potential.py +55 -0
- multioptpy/Potential/LJ_repulsive_potential.py +345 -0
- multioptpy/Potential/__init__.py +0 -0
- multioptpy/Potential/anharmonic_keep_potential.py +28 -0
- multioptpy/Potential/asym_elllipsoidal_potential.py +718 -0
- multioptpy/Potential/electrostatic_potential.py +69 -0
- multioptpy/Potential/flux_potential.py +30 -0
- multioptpy/Potential/gaussian_potential.py +101 -0
- multioptpy/Potential/idpp.py +516 -0
- multioptpy/Potential/keep_angle_potential.py +146 -0
- multioptpy/Potential/keep_dihedral_angle_potential.py +105 -0
- multioptpy/Potential/keep_outofplain_angle_potential.py +70 -0
- multioptpy/Potential/keep_potential.py +99 -0
- multioptpy/Potential/mechano_force_potential.py +74 -0
- multioptpy/Potential/nanoreactor_potential.py +52 -0
- multioptpy/Potential/potential.py +896 -0
- multioptpy/Potential/spacer_model_potential.py +221 -0
- multioptpy/Potential/switching_potential.py +258 -0
- multioptpy/Potential/universal_potential.py +34 -0
- multioptpy/Potential/value_range_potential.py +36 -0
- multioptpy/Potential/void_point_potential.py +25 -0
- multioptpy/SQM/__init__.py +0 -0
- multioptpy/SQM/sqm1/__init__.py +0 -0
- multioptpy/SQM/sqm1/sqm1_core.py +1792 -0
- multioptpy/SQM/sqm2/__init__.py +0 -0
- multioptpy/SQM/sqm2/calc_tools.py +95 -0
- multioptpy/SQM/sqm2/sqm2_basis.py +850 -0
- multioptpy/SQM/sqm2/sqm2_bond.py +119 -0
- multioptpy/SQM/sqm2/sqm2_core.py +303 -0
- multioptpy/SQM/sqm2/sqm2_data.py +1229 -0
- multioptpy/SQM/sqm2/sqm2_disp.py +65 -0
- multioptpy/SQM/sqm2/sqm2_eeq.py +243 -0
- multioptpy/SQM/sqm2/sqm2_overlapint.py +704 -0
- multioptpy/SQM/sqm2/sqm2_qm.py +578 -0
- multioptpy/SQM/sqm2/sqm2_rep.py +66 -0
- multioptpy/SQM/sqm2/sqm2_srb.py +70 -0
- multioptpy/Thermo/__init__.py +0 -0
- multioptpy/Thermo/normal_mode_analyzer.py +865 -0
- multioptpy/Utils/__init__.py +0 -0
- multioptpy/Utils/bond_connectivity.py +264 -0
- multioptpy/Utils/calc_tools.py +884 -0
- multioptpy/Utils/oniom.py +96 -0
- multioptpy/Utils/pbc.py +48 -0
- multioptpy/Utils/riemann_curvature.py +208 -0
- multioptpy/Utils/symmetry_analyzer.py +482 -0
- multioptpy/Visualization/__init__.py +0 -0
- multioptpy/Visualization/visualization.py +156 -0
- multioptpy/WFAnalyzer/MO_analysis.py +104 -0
- multioptpy/WFAnalyzer/__init__.py +0 -0
- multioptpy/Wrapper/__init__.py +0 -0
- multioptpy/Wrapper/autots.py +1239 -0
- multioptpy/Wrapper/ieip_wrapper.py +93 -0
- multioptpy/Wrapper/md_wrapper.py +92 -0
- multioptpy/Wrapper/neb_wrapper.py +94 -0
- multioptpy/Wrapper/optimize_wrapper.py +76 -0
- multioptpy/__init__.py +5 -0
- multioptpy/entrypoints.py +916 -0
- multioptpy/fileio.py +660 -0
- multioptpy/ieip.py +340 -0
- multioptpy/interface.py +1086 -0
- multioptpy/irc.py +529 -0
- multioptpy/moleculardynamics.py +432 -0
- multioptpy/neb.py +1267 -0
- multioptpy/optimization.py +1553 -0
- multioptpy/optimizer.py +709 -0
- multioptpy-1.20.2.dist-info/METADATA +438 -0
- multioptpy-1.20.2.dist-info/RECORD +246 -0
- multioptpy-1.20.2.dist-info/WHEEL +5 -0
- multioptpy-1.20.2.dist-info/entry_points.txt +9 -0
- multioptpy-1.20.2.dist-info/licenses/LICENSE +674 -0
- multioptpy-1.20.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.signal import savgol_filter
|
|
3
|
+
from scipy.interpolate import interp1d
|
|
4
|
+
|
|
5
|
+
from multioptpy.Utils.calc_tools import calc_path_length_list
|
|
6
|
+
|
|
7
|
+
def savitzky_golay_interpolation(structures, n_points=20, window_length=5, polyorder=2):
|
|
8
|
+
"""
|
|
9
|
+
Interpolate between arbitrary number of structures using Savitzky-Golay filtering.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
structures: list of np.ndarray, each of shape (n_atoms, 3)
|
|
13
|
+
n_points: int, number of points to interpolate
|
|
14
|
+
window_length: int, the length of the filter window (must be odd and greater than polyorder)
|
|
15
|
+
polyorder: int, the order of the polynomial used to fit the samples
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
path: np.ndarray of shape (n_points, n_atoms, 3)
|
|
19
|
+
"""
|
|
20
|
+
print("Using Savitzky-Golay filter interpolation.")
|
|
21
|
+
N = len(structures)
|
|
22
|
+
if N < window_length:
|
|
23
|
+
print("Not enough points for Savitzky-Golay filtering, falling back to linear interpolation.")
|
|
24
|
+
# Fallback to linear interpolation if not enough points
|
|
25
|
+
t_original = np.linspace(0, 1, N)
|
|
26
|
+
t_interp = np.linspace(0, 1, n_points)
|
|
27
|
+
structures = np.array(structures)
|
|
28
|
+
path = []
|
|
29
|
+
for atom_idx in range(structures.shape[1]):
|
|
30
|
+
for coord in range(3):
|
|
31
|
+
interp_func = interp1d(t_original, structures[:, atom_idx, coord], kind='linear')
|
|
32
|
+
path.append(interp_func(t_interp))
|
|
33
|
+
path = np.array(path).reshape(n_points, structures.shape[1], 3)
|
|
34
|
+
return path
|
|
35
|
+
|
|
36
|
+
structures = np.array(structures)
|
|
37
|
+
# Apply Savitzky-Golay filter along the structure sequence
|
|
38
|
+
smoothed_structures = np.zeros_like(structures)
|
|
39
|
+
for atom_idx in range(structures.shape[1]):
|
|
40
|
+
for coord in range(3):
|
|
41
|
+
smoothed_structures[:, atom_idx, coord] = savgol_filter(structures[:, atom_idx, coord], window_length, polyorder)
|
|
42
|
+
|
|
43
|
+
# Interpolate to n_points
|
|
44
|
+
t_original = np.linspace(0, 1, N)
|
|
45
|
+
t_interp = np.linspace(0, 1, n_points)
|
|
46
|
+
path = []
|
|
47
|
+
for atom_idx in range(structures.shape[1]):
|
|
48
|
+
for coord in range(3):
|
|
49
|
+
interp_func = interp1d(t_original, smoothed_structures[:, atom_idx, coord], kind='linear')
|
|
50
|
+
path.append(interp_func(t_interp))
|
|
51
|
+
path = np.array(path).reshape(n_points, structures.shape[1], 3)
|
|
52
|
+
return path
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def distribute_geometry_by_length_savgol(geometry_list, angstrom_spacing, window_length=5, polyorder=2):
|
|
56
|
+
"""
|
|
57
|
+
Distribute geometries by specified distance spacing using Savitzky-Golay filtering.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
geometry_list: list of np.ndarray, each of shape (n_atoms, 3)
|
|
61
|
+
angstrom_spacing: float, desired spacing
|
|
62
|
+
window_length: int, the length of the filter window (must be odd and greater than polyorder)
|
|
63
|
+
polyorder: int, the order of the polynomial used to fit the samples
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
new_geometry_list: list of np.ndarray, each of shape (n_atoms, 3)
|
|
67
|
+
"""
|
|
68
|
+
print("Distributing geometries using Savitzky-Golay filtering.")
|
|
69
|
+
path_length_list = calc_path_length_list(geometry_list)
|
|
70
|
+
interpolate_dist_list = np.arange(0, path_length_list[-1], angstrom_spacing)
|
|
71
|
+
interpolate_dist_list = np.append(interpolate_dist_list, path_length_list[-1])
|
|
72
|
+
t_values = interpolate_dist_list / path_length_list[-1]
|
|
73
|
+
|
|
74
|
+
N = len(geometry_list)
|
|
75
|
+
if N < window_length:
|
|
76
|
+
print("Not enough points for Savitzky-Golay filtering, falling back to linear interpolation.")
|
|
77
|
+
# Fallback to linear interpolation if not enough points
|
|
78
|
+
t_original = np.linspace(0, 1, N)
|
|
79
|
+
geometry_list = np.array(geometry_list)
|
|
80
|
+
new_geometry_list = []
|
|
81
|
+
for t in t_values:
|
|
82
|
+
interp_point = []
|
|
83
|
+
for atom_idx in range(geometry_list.shape[1]):
|
|
84
|
+
for coord in range(3):
|
|
85
|
+
interp_func = interp1d(t_original, geometry_list[:, atom_idx, coord], kind='linear')
|
|
86
|
+
interp_point.append(interp_func(t))
|
|
87
|
+
new_geometry_list.append(np.array(interp_point).reshape(geometry_list.shape[1], 3))
|
|
88
|
+
return np.array(new_geometry_list)
|
|
89
|
+
|
|
90
|
+
geometry_list = np.array(geometry_list)
|
|
91
|
+
# Apply Savitzky-Golay filter along the structure sequence
|
|
92
|
+
smoothed_geometries = np.zeros_like(geometry_list)
|
|
93
|
+
for atom_idx in range(geometry_list.shape[1]):
|
|
94
|
+
for coord in range(3):
|
|
95
|
+
smoothed_geometries[:, atom_idx, coord] = savgol_filter(geometry_list[:, atom_idx, coord], window_length, polyorder)
|
|
96
|
+
|
|
97
|
+
# Interpolate to desired spacing
|
|
98
|
+
t_original = np.linspace(0, 1, N)
|
|
99
|
+
new_geometry_list = []
|
|
100
|
+
for t in t_values:
|
|
101
|
+
interp_point = []
|
|
102
|
+
for atom_idx in range(geometry_list.shape[1]):
|
|
103
|
+
for coord in range(3):
|
|
104
|
+
interp_func = interp1d(t_original, smoothed_geometries[:, atom_idx, coord], kind='linear')
|
|
105
|
+
interp_point.append(interp_func(t))
|
|
106
|
+
new_geometry_list.append(np.array(interp_point).reshape(geometry_list.shape[1], 3))
|
|
107
|
+
return np.array(new_geometry_list)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def savitzky_golay_interpolation_with_derivatives(structures, n_points=20, window_length=5, polyorder=2, deriv_order=1):
|
|
111
|
+
"""
|
|
112
|
+
Interpolate between arbitrary number of structures using Savitzky-Golay filtering and compute derivatives.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
structures: list of np.ndarray, each of shape (n_atoms, 3)
|
|
116
|
+
n_points: int, number of points to interpolate
|
|
117
|
+
window_length: int, the length of the filter window (must be odd and greater than polyorder)
|
|
118
|
+
polyorder: int, the order of the polynomial used to fit the samples
|
|
119
|
+
deriv_order: int, the order of the derivative to compute (0 for smoothed, 1 for first derivative, etc.)
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
path: np.ndarray of shape (n_points, n_atoms, 3) if deriv_order == 0, or derivative array if deriv_order > 0
|
|
123
|
+
derivatives: dict containing derivative arrays for each derivative order up to deriv_order
|
|
124
|
+
"""
|
|
125
|
+
print(f"Using Savitzky-Golay filter interpolation with derivatives up to order {deriv_order}.")
|
|
126
|
+
N = len(structures)
|
|
127
|
+
if N < window_length:
|
|
128
|
+
# Fallback to linear interpolation if not enough points
|
|
129
|
+
t_original = np.linspace(0, 1, N)
|
|
130
|
+
t_interp = np.linspace(0, 1, n_points)
|
|
131
|
+
structures = np.array(structures)
|
|
132
|
+
path = []
|
|
133
|
+
derivatives = {}
|
|
134
|
+
for d in range(deriv_order + 1):
|
|
135
|
+
deriv_path = []
|
|
136
|
+
for atom_idx in range(structures.shape[1]):
|
|
137
|
+
for coord in range(3):
|
|
138
|
+
interp_func = interp1d(t_original, structures[:, atom_idx, coord], kind='linear')
|
|
139
|
+
if d == 0:
|
|
140
|
+
deriv_path.append(interp_func(t_interp))
|
|
141
|
+
else:
|
|
142
|
+
# Simple finite difference for derivatives in fallback
|
|
143
|
+
deriv_values = np.gradient(interp_func(t_interp), t_interp[1] - t_interp[0], edge_order=2)
|
|
144
|
+
for _ in range(d - 1):
|
|
145
|
+
deriv_values = np.gradient(deriv_values, t_interp[1] - t_interp[0], edge_order=2)
|
|
146
|
+
deriv_path.append(deriv_values)
|
|
147
|
+
derivatives[f'deriv_{d}'] = np.array(deriv_path).reshape(n_points, structures.shape[1], 3)
|
|
148
|
+
if d == 0:
|
|
149
|
+
path = derivatives[f'deriv_{d}']
|
|
150
|
+
return path, derivatives
|
|
151
|
+
|
|
152
|
+
structures = np.array(structures)
|
|
153
|
+
derivatives = {}
|
|
154
|
+
for d in range(deriv_order + 1):
|
|
155
|
+
deriv_path = []
|
|
156
|
+
for atom_idx in range(structures.shape[1]):
|
|
157
|
+
for coord in range(3):
|
|
158
|
+
smoothed = savgol_filter(structures[:, atom_idx, coord], window_length, polyorder, deriv=d)
|
|
159
|
+
deriv_path.append(smoothed)
|
|
160
|
+
derivatives[f'deriv_{d}'] = np.array(deriv_path).reshape(N, structures.shape[1], 3)
|
|
161
|
+
|
|
162
|
+
# Interpolate to n_points for each derivative
|
|
163
|
+
t_original = np.linspace(0, 1, N)
|
|
164
|
+
t_interp = np.linspace(0, 1, n_points)
|
|
165
|
+
for d in range(deriv_order + 1):
|
|
166
|
+
deriv_interp = []
|
|
167
|
+
for atom_idx in range(structures.shape[1]):
|
|
168
|
+
for coord in range(3):
|
|
169
|
+
interp_func = interp1d(t_original, derivatives[f'deriv_{d}'][:, atom_idx, coord], kind='linear')
|
|
170
|
+
deriv_interp.append(interp_func(t_interp))
|
|
171
|
+
derivatives[f'deriv_{d}'] = np.array(deriv_interp).reshape(n_points, structures.shape[1], 3)
|
|
172
|
+
|
|
173
|
+
path = derivatives['deriv_0']
|
|
174
|
+
return path, derivatives
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def distribute_geometry_by_length_savgol_with_derivatives(geometry_list, angstrom_spacing, window_length=5, polyorder=2, deriv_order=1):
|
|
178
|
+
"""
|
|
179
|
+
Distribute geometries by specified distance spacing using Savitzky-Golay filtering and compute derivatives.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
geometry_list: list of np.ndarray, each of shape (n_atoms, 3)
|
|
183
|
+
angstrom_spacing: float, desired spacing
|
|
184
|
+
window_length: int, the length of the filter window (must be odd and greater than polyorder)
|
|
185
|
+
polyorder: int, the order of the polynomial used to fit the samples
|
|
186
|
+
deriv_order: int, the order of the derivative to compute (0 for smoothed, 1 for first derivative, etc.)
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
new_geometry_list: list of np.ndarray, each of shape (n_atoms, 3)
|
|
190
|
+
derivatives: dict containing derivative arrays for each derivative order up to deriv_order
|
|
191
|
+
"""
|
|
192
|
+
print(f"Distributing geometries using Savitzky-Golay filtering with derivatives up to order {deriv_order}.")
|
|
193
|
+
path_length_list = calc_path_length_list(geometry_list)
|
|
194
|
+
interpolate_dist_list = np.arange(0, path_length_list[-1], angstrom_spacing)
|
|
195
|
+
interpolate_dist_list = np.append(interpolate_dist_list, path_length_list[-1])
|
|
196
|
+
t_values = interpolate_dist_list / path_length_list[-1]
|
|
197
|
+
|
|
198
|
+
N = len(geometry_list)
|
|
199
|
+
if N < window_length:
|
|
200
|
+
# Fallback to linear interpolation if not enough points
|
|
201
|
+
t_original = np.linspace(0, 1, N)
|
|
202
|
+
geometry_list = np.array(geometry_list)
|
|
203
|
+
new_geometry_list = []
|
|
204
|
+
derivatives = {}
|
|
205
|
+
for d in range(deriv_order + 1):
|
|
206
|
+
deriv_list = []
|
|
207
|
+
for t in t_values:
|
|
208
|
+
interp_point = []
|
|
209
|
+
for atom_idx in range(geometry_list.shape[1]):
|
|
210
|
+
for coord in range(3):
|
|
211
|
+
interp_func = interp1d(t_original, geometry_list[:, atom_idx, coord], kind='linear')
|
|
212
|
+
if d == 0:
|
|
213
|
+
interp_point.append(interp_func(t))
|
|
214
|
+
else:
|
|
215
|
+
# Simple finite difference for derivatives in fallback
|
|
216
|
+
values = interp_func(t_original)
|
|
217
|
+
deriv_values = np.gradient(values, t_original[1] - t_original[0], edge_order=2)
|
|
218
|
+
for _ in range(d - 1):
|
|
219
|
+
deriv_values = np.gradient(deriv_values, t_original[1] - t_original[0], edge_order=2)
|
|
220
|
+
interp_func_deriv = interp1d(t_original, deriv_values, kind='linear')
|
|
221
|
+
interp_point.append(interp_func_deriv(t))
|
|
222
|
+
deriv_list.append(np.array(interp_point).reshape(geometry_list.shape[1], 3))
|
|
223
|
+
derivatives[f'deriv_{d}'] = np.array(deriv_list)
|
|
224
|
+
if d == 0:
|
|
225
|
+
new_geometry_list = derivatives[f'deriv_{d}']
|
|
226
|
+
return new_geometry_list, derivatives
|
|
227
|
+
|
|
228
|
+
geometry_list = np.array(geometry_list)
|
|
229
|
+
derivatives = {}
|
|
230
|
+
for d in range(deriv_order + 1):
|
|
231
|
+
deriv_path = []
|
|
232
|
+
for atom_idx in range(geometry_list.shape[1]):
|
|
233
|
+
for coord in range(3):
|
|
234
|
+
smoothed = savgol_filter(geometry_list[:, atom_idx, coord], window_length, polyorder, deriv=d)
|
|
235
|
+
deriv_path.append(smoothed)
|
|
236
|
+
derivatives[f'deriv_{d}'] = np.array(deriv_path).reshape(N, geometry_list.shape[1], 3)
|
|
237
|
+
|
|
238
|
+
# Interpolate to desired spacing for each derivative
|
|
239
|
+
t_original = np.linspace(0, 1, N)
|
|
240
|
+
for d in range(deriv_order + 1):
|
|
241
|
+
deriv_interp = []
|
|
242
|
+
for t in t_values:
|
|
243
|
+
interp_point = []
|
|
244
|
+
for atom_idx in range(geometry_list.shape[1]):
|
|
245
|
+
for coord in range(3):
|
|
246
|
+
interp_func = interp1d(t_original, derivatives[f'deriv_{d}'][:, atom_idx, coord], kind='linear')
|
|
247
|
+
interp_point.append(interp_func(t))
|
|
248
|
+
deriv_interp.append(np.array(interp_point).reshape(geometry_list.shape[1], 3))
|
|
249
|
+
derivatives[f'deriv_{d}'] = np.array(deriv_interp)
|
|
250
|
+
|
|
251
|
+
new_geometry_list = derivatives['deriv_0']
|
|
252
|
+
return new_geometry_list, derivatives
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.interpolate import CubicSpline, make_interp_spline, PchipInterpolator
|
|
3
|
+
|
|
4
|
+
from multioptpy.Utils.calc_tools import calc_path_length_list
|
|
5
|
+
from multioptpy.Interpolation.linear_interpolation import distribute_geometry_by_length, distribute_geometry
|
|
6
|
+
|
|
7
|
+
def spline_interpolation(
|
|
8
|
+
structures,
|
|
9
|
+
n_points=20,
|
|
10
|
+
method='hermite', # Options: 'linear', 'quadratic', 'cubic', 'b-spline', 'hermite'
|
|
11
|
+
bc_type='natural',
|
|
12
|
+
spline_degree=5,
|
|
13
|
+
window=None
|
|
14
|
+
):
|
|
15
|
+
"""
|
|
16
|
+
Interpolates between atomic structures using various spline interpolation methods.
|
|
17
|
+
Supports global and local (windowed) interpolation. For local interpolation,
|
|
18
|
+
only the nearest 'window' structures before and after each node are used.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
structures: list of np.ndarray, each of shape (n_atoms, 3)
|
|
22
|
+
n_points: int, number of interpolated structures to generate
|
|
23
|
+
method: str, interpolation method to use
|
|
24
|
+
Options:
|
|
25
|
+
'linear' : Piecewise linear interpolation
|
|
26
|
+
'quadratic': Quadratic spline (degree 2, via make_interp_spline)
|
|
27
|
+
'cubic' : Cubic spline (via CubicSpline)
|
|
28
|
+
'b-spline' : B-spline of arbitrary degree (use spline_degree)
|
|
29
|
+
'hermite' : Hermite spline (PCHIP monotonic cubic)
|
|
30
|
+
bc_type: str or tuple, boundary condition type for cubic spline
|
|
31
|
+
spline_degree: int or None, degree for B-spline interpolation
|
|
32
|
+
window: int or None, number of structures before and after each node to use for local interpolation.
|
|
33
|
+
If None, uses global interpolation.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
path: np.ndarray of shape (n_points, n_atoms, 3)
|
|
37
|
+
"""
|
|
38
|
+
structures = np.array(structures) # (n_structures, n_atoms, 3)
|
|
39
|
+
n_structures = structures.shape[0]
|
|
40
|
+
n_atoms = structures.shape[1]
|
|
41
|
+
|
|
42
|
+
if window is None:
|
|
43
|
+
# Global interpolation
|
|
44
|
+
x = np.linspace(0, 1, n_structures)
|
|
45
|
+
t_values = np.linspace(0, 1, n_points)
|
|
46
|
+
path = np.zeros((n_points, n_atoms, 3))
|
|
47
|
+
|
|
48
|
+
for atom_idx in range(n_atoms):
|
|
49
|
+
for coord_idx in range(3):
|
|
50
|
+
y = structures[:, atom_idx, coord_idx]
|
|
51
|
+
if method == 'linear':
|
|
52
|
+
path[:, atom_idx, coord_idx] = np.interp(t_values, x, y)
|
|
53
|
+
elif method == 'quadratic':
|
|
54
|
+
spline = make_interp_spline(x, y, k=2)
|
|
55
|
+
path[:, atom_idx, coord_idx] = spline(t_values)
|
|
56
|
+
elif method == 'cubic':
|
|
57
|
+
spline = CubicSpline(x, y, bc_type=bc_type)
|
|
58
|
+
path[:, atom_idx, coord_idx] = spline(t_values)
|
|
59
|
+
elif method == 'b-spline':
|
|
60
|
+
deg = spline_degree if spline_degree is not None else 3
|
|
61
|
+
spline = make_interp_spline(x, y, k=deg)
|
|
62
|
+
path[:, atom_idx, coord_idx] = spline(t_values)
|
|
63
|
+
elif method == 'hermite':
|
|
64
|
+
interpolator = PchipInterpolator(x, y)
|
|
65
|
+
path[:, atom_idx, coord_idx] = interpolator(t_values)
|
|
66
|
+
else:
|
|
67
|
+
raise ValueError(
|
|
68
|
+
f"Unknown method '{method}'. Supported methods are: 'linear', 'quadratic', 'cubic', 'b-spline', 'hermite'."
|
|
69
|
+
)
|
|
70
|
+
print(f"Using global '{method}' spline interpolation"
|
|
71
|
+
f"{' degree ' + str(spline_degree) if method == 'b-spline' else ''}"
|
|
72
|
+
f"{' and bc_type ' + str(bc_type) if method == 'cubic' else ''}.")
|
|
73
|
+
return path
|
|
74
|
+
|
|
75
|
+
# Local (windowed) interpolation
|
|
76
|
+
segments = []
|
|
77
|
+
for idx in range(n_structures - 1):
|
|
78
|
+
start = max(0, idx - window)
|
|
79
|
+
end = min(n_structures, idx + window + 2) # +2 to include idx+1
|
|
80
|
+
local_structures = structures[start:end]
|
|
81
|
+
local_n_structures = local_structures.shape[0]
|
|
82
|
+
local_x = np.linspace(0, 1, local_n_structures)
|
|
83
|
+
if idx - window >= 0:
|
|
84
|
+
t0 = local_x[window]
|
|
85
|
+
t1 = local_x[window + 1]
|
|
86
|
+
else:
|
|
87
|
+
t0 = local_x[idx]
|
|
88
|
+
t1 = local_x[idx + 1]
|
|
89
|
+
t_values = np.linspace(t0, t1, n_points)
|
|
90
|
+
|
|
91
|
+
local_path = np.zeros((n_points, n_atoms, 3))
|
|
92
|
+
for atom_idx in range(n_atoms):
|
|
93
|
+
for coord_idx in range(3):
|
|
94
|
+
y = local_structures[:, atom_idx, coord_idx]
|
|
95
|
+
if method == 'linear':
|
|
96
|
+
local_path[:, atom_idx, coord_idx] = np.interp(t_values, local_x, y)
|
|
97
|
+
elif method == 'quadratic':
|
|
98
|
+
spline = make_interp_spline(local_x, y, k=2)
|
|
99
|
+
local_path[:, atom_idx, coord_idx] = spline(t_values)
|
|
100
|
+
elif method == 'cubic':
|
|
101
|
+
spline = CubicSpline(local_x, y, bc_type=bc_type)
|
|
102
|
+
local_path[:, atom_idx, coord_idx] = spline(t_values)
|
|
103
|
+
elif method == 'b-spline':
|
|
104
|
+
deg = spline_degree if spline_degree is not None else 3
|
|
105
|
+
spline = make_interp_spline(local_x, y, k=deg)
|
|
106
|
+
local_path[:, atom_idx, coord_idx] = spline(t_values)
|
|
107
|
+
elif method == 'hermite':
|
|
108
|
+
interpolator = PchipInterpolator(local_x, y)
|
|
109
|
+
local_path[:, atom_idx, coord_idx] = interpolator(t_values)
|
|
110
|
+
else:
|
|
111
|
+
raise ValueError(
|
|
112
|
+
f"Unknown method '{method}'. Supported methods are: 'linear', 'quadratic', 'cubic', 'b-spline', 'hermite'."
|
|
113
|
+
)
|
|
114
|
+
segments.append(local_path)
|
|
115
|
+
|
|
116
|
+
# Concatenate all segments, removing duplicate points at boundaries
|
|
117
|
+
result_path = [segments[0][0]]
|
|
118
|
+
for seg in segments:
|
|
119
|
+
result_path.extend(seg[1:])
|
|
120
|
+
result_path = np.array(result_path)
|
|
121
|
+
|
|
122
|
+
print(f"Using local '{method}' spline interpolation with window={window},"
|
|
123
|
+
f"{' degree ' + str(spline_degree) if method == 'b-spline' else ''}"
|
|
124
|
+
f"{' and bc_type ' + str(bc_type) if method == 'cubic' else ''}.")
|
|
125
|
+
|
|
126
|
+
# Resample to exactly n_points along the full path
|
|
127
|
+
result_path = resample_path(result_path, n_points)
|
|
128
|
+
return result_path
|
|
129
|
+
|
|
130
|
+
def resample_path(path, n_points):
|
|
131
|
+
"""
|
|
132
|
+
Resamples a path to have exactly n_points structures.
|
|
133
|
+
path: np.ndarray of shape (N, n_atoms, 3)
|
|
134
|
+
n_points: int, desired number of output structures
|
|
135
|
+
Returns: np.ndarray of shape (n_points, n_atoms, 3)
|
|
136
|
+
"""
|
|
137
|
+
N = path.shape[0]
|
|
138
|
+
indices = np.linspace(0, N-1, n_points)
|
|
139
|
+
resampled = np.array([path[int(round(idx))] for idx in indices])
|
|
140
|
+
return resampled
|
|
141
|
+
|
|
142
|
+
# Example usage:
|
|
143
|
+
# S0, S1, S2, S3, S4, S5 = np.array(...), np.array(...), np.array(...), np.array(...), np.array(...), np.array(...)
|
|
144
|
+
# path_global_cubic = spline_interpolation([S0, S1, S2, S3, S4, S5], n_points=20, method='cubic')
|
|
145
|
+
# path_local_bspline = spline_interpolation([S0, S1, S2, S3, S4, S5], n_points=10, method='b-spline', spline_degree=5, window=3)
|
|
146
|
+
# path_local_hermite = spline_interpolation([S0, S1, S2, S3, S4, S5], n_points=10, method='hermite', window=3)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def distribute_geometry_by_length_spline(geometry_list, angstrom_spacing, spline_degree=3):
|
|
152
|
+
"""
|
|
153
|
+
Distribute geometries by specified distance spacing using B-spline interpolation
|
|
154
|
+
|
|
155
|
+
Parameters:
|
|
156
|
+
-----------
|
|
157
|
+
geometry_list : list
|
|
158
|
+
List of geometry arrays/objects
|
|
159
|
+
angstrom_spacing : float
|
|
160
|
+
Desired spacing in Angstroms between distributed geometries
|
|
161
|
+
spline_degree : int, optional
|
|
162
|
+
Degree of the spline interpolation (default=3 for cubic splines)
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
--------
|
|
166
|
+
list
|
|
167
|
+
New list of geometries distributed at regular intervals using spline interpolation
|
|
168
|
+
"""
|
|
169
|
+
# Handle edge cases
|
|
170
|
+
if len(geometry_list) <= 1:
|
|
171
|
+
return geometry_list.copy()
|
|
172
|
+
|
|
173
|
+
# Calculate path lengths
|
|
174
|
+
path_length_list = calc_path_length_list(geometry_list)
|
|
175
|
+
total_length = path_length_list[-1]
|
|
176
|
+
|
|
177
|
+
# Handle case with extremely short paths
|
|
178
|
+
if total_length < angstrom_spacing:
|
|
179
|
+
return [geometry_list[0], geometry_list[-1]]
|
|
180
|
+
|
|
181
|
+
# Ensure spline degree is not greater than number of points minus 1
|
|
182
|
+
k = min(spline_degree, len(geometry_list) - 1)
|
|
183
|
+
|
|
184
|
+
# Check for duplicate path lengths and handle them
|
|
185
|
+
# First create a list of unique indices to keep
|
|
186
|
+
unique_indices = []
|
|
187
|
+
unique_path_lengths = []
|
|
188
|
+
for i, length in enumerate(path_length_list):
|
|
189
|
+
if i == 0 or abs(length - path_length_list[i-1]) > 1e-10: # Use small epsilon for floating-point comparison
|
|
190
|
+
unique_indices.append(i)
|
|
191
|
+
unique_path_lengths.append(length)
|
|
192
|
+
|
|
193
|
+
# If we have duplicates, filter the geometry list and path lengths
|
|
194
|
+
if len(unique_indices) < len(geometry_list):
|
|
195
|
+
print(f"Warning: Removed {len(geometry_list) - len(unique_indices)} duplicate geometries from the path")
|
|
196
|
+
unique_geometries = [geometry_list[i] for i in unique_indices]
|
|
197
|
+
path_length_list = unique_path_lengths
|
|
198
|
+
geometry_list = unique_geometries
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# If after removing duplicates we have too few points for the requested spline degree,
|
|
202
|
+
# reduce the degree accordingly
|
|
203
|
+
k = min(k, len(geometry_list) - 1)
|
|
204
|
+
|
|
205
|
+
# If we have too few points for splines, fall back to linear interpolation
|
|
206
|
+
if len(geometry_list) <= 2 or k < 1:
|
|
207
|
+
print("Warning: Insufficient points for spline interpolation. Falling back to linear interpolation.")
|
|
208
|
+
return distribute_geometry_by_length(geometry_list, angstrom_spacing)
|
|
209
|
+
|
|
210
|
+
# Convert geometries to numpy arrays
|
|
211
|
+
geom_arrays = [np.asarray(geom) for geom in geometry_list]
|
|
212
|
+
original_shape = geom_arrays[0].shape
|
|
213
|
+
flattened_geoms = [g.flatten() for g in geom_arrays]
|
|
214
|
+
|
|
215
|
+
# Determine the number of coordinates to interpolate
|
|
216
|
+
n_coords = len(flattened_geoms[0])
|
|
217
|
+
|
|
218
|
+
# Create new geometry list with the first point
|
|
219
|
+
new_geometry_list = []
|
|
220
|
+
|
|
221
|
+
try:
|
|
222
|
+
# Create splines for each coordinate
|
|
223
|
+
splines = []
|
|
224
|
+
for i in range(n_coords):
|
|
225
|
+
# Extract the i-th coordinate from each geometry
|
|
226
|
+
coord_values = [g[i] for g in flattened_geoms]
|
|
227
|
+
# Create a spline for this coordinate
|
|
228
|
+
spline = make_interp_spline(path_length_list, coord_values, k=k)
|
|
229
|
+
splines.append(spline)
|
|
230
|
+
|
|
231
|
+
# Determine sample points along the path including endpoints
|
|
232
|
+
num_points = max(2, int(np.ceil(total_length / angstrom_spacing)) + 1)
|
|
233
|
+
sample_distances = np.linspace(0, total_length, num_points)
|
|
234
|
+
|
|
235
|
+
# Generate new geometries
|
|
236
|
+
for dist in sample_distances:
|
|
237
|
+
# Evaluate all splines at this distance
|
|
238
|
+
interpolated_coords = [spline(dist) for spline in splines]
|
|
239
|
+
# Reshape back to original geometry shape
|
|
240
|
+
new_geom = np.array(interpolated_coords).reshape(original_shape)
|
|
241
|
+
new_geometry_list.append(new_geom)
|
|
242
|
+
|
|
243
|
+
except Exception as e:
|
|
244
|
+
print(f"Warning: Spline interpolation failed with error: {e}. Falling back to linear interpolation.")
|
|
245
|
+
return distribute_geometry_by_length(geometry_list, angstrom_spacing)
|
|
246
|
+
|
|
247
|
+
# Replace first and last points with original endpoints to guarantee exact endpoints
|
|
248
|
+
new_geometry_list[0] = geom_arrays[0]
|
|
249
|
+
new_geometry_list[-1] = geom_arrays[-1]
|
|
250
|
+
new_geometry_list.append(geometry_list[-1])
|
|
251
|
+
|
|
252
|
+
return new_geometry_list
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def distribute_geometry_spline(geometry_list, spline_degree=3):
|
|
257
|
+
"""
|
|
258
|
+
Distribute geometries evenly along the path using spline interpolation
|
|
259
|
+
|
|
260
|
+
Parameters:
|
|
261
|
+
-----------
|
|
262
|
+
geometry_list : list
|
|
263
|
+
List of geometry arrays/objects
|
|
264
|
+
spline_degree : int, optional
|
|
265
|
+
Degree of the spline interpolation (default=3 for cubic splines)
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
--------
|
|
269
|
+
list
|
|
270
|
+
New list of geometries distributed at regular intervals using spline interpolation
|
|
271
|
+
"""
|
|
272
|
+
nnode = len(geometry_list)
|
|
273
|
+
|
|
274
|
+
# Handle edge cases
|
|
275
|
+
if nnode <= 2:
|
|
276
|
+
return geometry_list.copy()
|
|
277
|
+
|
|
278
|
+
path_length_list = calc_path_length_list(geometry_list)
|
|
279
|
+
total_length = path_length_list[-1]
|
|
280
|
+
node_dist = total_length / (nnode-1)
|
|
281
|
+
|
|
282
|
+
# Ensure spline degree is not greater than number of points minus 1
|
|
283
|
+
k = min(spline_degree, nnode - 1)
|
|
284
|
+
|
|
285
|
+
# Check for duplicate path lengths and handle them
|
|
286
|
+
unique_indices = []
|
|
287
|
+
unique_path_lengths = []
|
|
288
|
+
for i, length in enumerate(path_length_list):
|
|
289
|
+
if i == 0 or abs(length - path_length_list[i-1]) > 1e-10:
|
|
290
|
+
unique_indices.append(i)
|
|
291
|
+
unique_path_lengths.append(length)
|
|
292
|
+
|
|
293
|
+
# If we have duplicates, filter the geometry list and path lengths
|
|
294
|
+
if len(unique_indices) < nnode:
|
|
295
|
+
filtered_geometries = [geometry_list[i] for i in unique_indices]
|
|
296
|
+
unique_path_lengths_array = np.array(unique_path_lengths)
|
|
297
|
+
|
|
298
|
+
# If after filtering we have too few points for spline, fall back to linear
|
|
299
|
+
if len(filtered_geometries) <= k:
|
|
300
|
+
return distribute_geometry(geometry_list)
|
|
301
|
+
|
|
302
|
+
# Convert geometries to arrays for interpolation
|
|
303
|
+
geom_arrays = [np.asarray(geom) for geom in filtered_geometries]
|
|
304
|
+
|
|
305
|
+
else:
|
|
306
|
+
# No duplicates found
|
|
307
|
+
geom_arrays = [np.asarray(geom) for geom in geometry_list]
|
|
308
|
+
unique_path_lengths_array = np.array(path_length_list)
|
|
309
|
+
|
|
310
|
+
# Determine shape of geometries
|
|
311
|
+
original_shape = geom_arrays[0].shape
|
|
312
|
+
flattened_geoms = [g.flatten() for g in geom_arrays]
|
|
313
|
+
n_coords = len(flattened_geoms[0])
|
|
314
|
+
|
|
315
|
+
# Create new geometry list with the first point
|
|
316
|
+
new_geometry_list = [geometry_list[0]]
|
|
317
|
+
|
|
318
|
+
try:
|
|
319
|
+
# Create splines for each coordinate
|
|
320
|
+
splines = []
|
|
321
|
+
for i in range(n_coords):
|
|
322
|
+
# Extract the i-th coordinate from each geometry
|
|
323
|
+
coord_values = [g[i] for g in flattened_geoms]
|
|
324
|
+
# Create a spline for this coordinate
|
|
325
|
+
spline = make_interp_spline(unique_path_lengths_array, coord_values, k=k)
|
|
326
|
+
splines.append(spline)
|
|
327
|
+
|
|
328
|
+
# Generate new geometries at evenly spaced positions
|
|
329
|
+
for i in range(1, nnode-1):
|
|
330
|
+
dist = i * node_dist
|
|
331
|
+
|
|
332
|
+
# Evaluate all splines at this distance
|
|
333
|
+
interpolated_coords = [spline(dist) for spline in splines]
|
|
334
|
+
# Reshape back to original geometry shape
|
|
335
|
+
new_geom = np.array(interpolated_coords).reshape(original_shape)
|
|
336
|
+
new_geometry_list.append(new_geom)
|
|
337
|
+
|
|
338
|
+
except Exception as e:
|
|
339
|
+
# Fall back to linear interpolation if spline fails
|
|
340
|
+
print(f"Warning: Spline interpolation failed with error: {e}. Falling back to linear interpolation.")
|
|
341
|
+
return distribute_geometry(geometry_list)
|
|
342
|
+
|
|
343
|
+
# Always include the last geometry
|
|
344
|
+
new_geometry_list.append(geometry_list[-1])
|
|
345
|
+
|
|
346
|
+
return new_geometry_list
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
|
|
File without changes
|