Ahilan Kumaresan commited on
Commit
968ce44
·
1 Parent(s): 75d335c

Fix: Move qmsolve import inside functions to prevent HuggingFace timeout

Browse files
Files changed (1) hide show
  1. functions.py +702 -1
functions.py CHANGED
@@ -977,6 +977,702 @@ def check_finite_well_analytic(E, V0, lower_bound=-10, upper_bound=10, hbar=1.0,
977
 
978
 
979
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
980
 
981
 
982
 
@@ -1204,7 +1900,7 @@ def capture_potential(tune, A_MIN, A_MAX, mode='wait'):
1204
  return captured_V
1205
 
1206
  # Create a notebook-friendly version of the function
1207
- def capture_potential_notebook(tune, A_MIN, A_MAX, mode='wait'):
1208
  import time
1209
  from IPython.display import display, Image, clear_output
1210
 
@@ -1380,3 +2076,8 @@ def show_QR(url):
1380
 
1381
  # 5. Display the saved image using IPython.display
1382
  return display(Image(filename=file_name))
 
 
 
 
 
 
977
 
978
 
979
 
980
+ ##
981
+ # Verify
982
+
983
+ import sys
984
+
985
+ def run_comparison():
986
+ """
987
+ Cross-verification: Hand-wave solver vs QMSolve package.
988
+
989
+ Compares results for:
990
+ 1. Double Well potential
991
+ 2. Harmonic Oscillator (debug test)
992
+
993
+ Results saved to 'comparison_log.txt'
994
+
995
+ Requires
996
+ --------
997
+ QMSolve package: pip install qmsolve
998
+
999
+ Usage
1000
+ -----
1001
+ >>> from functions import run_comparison
1002
+ >>> run_comparison()
1003
+ """
1004
+ # Import qmsolve only when this function is called
1005
+ try:
1006
+ from qmsolve import Hamiltonian, SingleParticle, init_visualization
1007
+ except ImportError:
1008
+ print("Error: qmsolve not found. Please install it via 'pip install qmsolve'")
1009
+ return
1010
+
1011
+ with open("comparison_log.txt", "w") as log_file:
1012
+ sys.stdout = log_file
1013
+ print("========================================")
1014
+ print("CROSS-VERIFICATION: Hand-wave vs QMSOLVE")
1015
+ print("========================================")
1016
+
1017
+ # ---------------------------------------------------------
1018
+ # CASE: Double Well Potential
1019
+ # V(x) = depth * ( (x-center)**2 - separation )**2
1020
+ # ---------------------------------------------------------
1021
+ print("\n[TEST CASE] Double Well Potential")
1022
+
1023
+ # Parameters
1024
+ L = 10.0
1025
+ N = 512 # QMSolve default is often 512 or similar, let's match
1026
+ depth = 2.0
1027
+ separation = 1.0
1028
+ center = 0.0
1029
+ m_particle = 1.0
1030
+
1031
+ print(f"Parameters: L={L}, N={N}, depth={depth}, separation={separation}, m={m_particle}")
1032
+
1033
+ # ---------------------------------------------------------
1034
+ # 1. Run Hand-wave solver
1035
+ # ---------------------------------------------------------
1036
+ print("\n--- Running Hand-wave Solver ---")
1037
+ x_full, dx, x_internal = make_grid(L=L, N=N)
1038
+
1039
+ # Construct Potential using local V_double_well function
1040
+ V_internal = V_double_well(x_internal, depth=depth, separation=separation, center=center)
1041
+
1042
+ # Pad for solver
1043
+ V_full = np.zeros_like(x_full)
1044
+ V_full[1:-1] = V_internal
1045
+ V_full[0] = 1e10
1046
+ V_full[-1] = 1e10
1047
+
1048
+ T = kinetic_operator(N, dx, m=m_particle)
1049
+ E_handwave, psi_handwave = solve(T, V_full, dx)
1050
+
1051
+ print(f"Hand-wave Energies (first 5): {E_handwave[:5]}")
1052
+
1053
+ # ---------------------------------------------------------
1054
+ # 2. Run QMSolve
1055
+ # ---------------------------------------------------------
1056
+ print("\n--- Running QMSolve ---")
1057
+
1058
+ # Define potential function for QMSolve
1059
+ def double_well(particle):
1060
+ x = particle.x
1061
+ return depth * ( (x - center)**2 - separation )**2
1062
+
1063
+ # Setup QMSolve
1064
+ H = Hamiltonian(particles = SingleParticle(m = m_particle),
1065
+ potential = double_well,
1066
+ spatial_ndim = 1, N = N, extent = L)
1067
+
1068
+ # Diagonalize
1069
+ eigenstates = H.solve(max_states = 10)
1070
+ E_qm_eV = eigenstates.energies
1071
+
1072
+ # Convert QMSolve (eV) to Hartree
1073
+ # 1 Hartree = 27.211386 eV
1074
+ Hartree_to_eV = 27.211386
1075
+ E_qm = E_qm_eV / Hartree_to_eV
1076
+
1077
+ print(f"QMSolve Energies (eV): {E_qm_eV[:5]}")
1078
+ print(f"QMSolve Energies (Hartree): {E_qm[:5]}")
1079
+
1080
+ # ---------------------------------------------------------
1081
+ # 3. Compare
1082
+ # ---------------------------------------------------------
1083
+ print("\n--- Comparison Results ---")
1084
+ print("-" * 65)
1085
+ print(f"| n | Hand-wave E | QMSolve E | Diff | % Diff |")
1086
+ print("-" * 65)
1087
+
1088
+ for i in range(5):
1089
+ e1 = E_handwave[i]
1090
+ e2 = E_qm[i]
1091
+ diff = abs(e1 - e2)
1092
+ p_diff = (diff / e2) * 100 if e2 != 0 else 0.0
1093
+
1094
+ print(f"| {i:<1} | {e1:<12.6f} | {e2:<12.6f} | {diff:<12.2e} | {p_diff:<7.4f}% |")
1095
+ print("-" * 65)
1096
+
1097
+ # ---------------------------------------------------------
1098
+ # DEBUG CASE: Harmonic Oscillator
1099
+ # ---------------------------------------------------------
1100
+ print("\n[DEBUG CASE] Harmonic Oscillator (k=1)")
1101
+ k_debug = 1.0
1102
+
1103
+ # Hand-wave solver
1104
+ V_internal_HO = 0.5 * k_debug * x_internal**2
1105
+ V_full_HO = np.zeros_like(x_full)
1106
+ V_full_HO[1:-1] = V_internal_HO
1107
+ V_full_HO[0] = 1e10
1108
+ V_full_HO[-1] = 1e10
1109
+
1110
+ E_handwave_HO, _ = solve(T, V_full_HO, dx)
1111
+ print(f"Hand-wave HO Energies: {E_handwave_HO[:5]}")
1112
+
1113
+ # QMSolve
1114
+ def harmonic_potential(particle):
1115
+ return 0.5 * k_debug * particle.x**2
1116
+
1117
+ H_HO = Hamiltonian(particles = SingleParticle(m = m_particle),
1118
+ potential = harmonic_potential,
1119
+ spatial_ndim = 1, N = N, extent = L)
1120
+ eigenstates_HO = H_HO.solve(max_states = 10)
1121
+ E_qm_HO = eigenstates_HO.energies
1122
+ print(f"QMSolve HO Energies: {E_qm_HO[:5]}")
1123
+
1124
+ sys.stdout = sys.__stdout__
1125
+ print("\n✓ Comparison complete! Results saved to 'comparison_log.txt'")
1126
+
1127
+
1128
+ # ==========================================
1129
+ # NOTEBOOK-FRIENDLY VERIFICATION FUNCTIONS
1130
+ # ==========================================
1131
+
1132
+ def verify_qmsolve(E_your=None, psi_your=None, V_your=None, x_your=None,
1133
+ potential_type='double_well', potential_params=None):
1134
+ """
1135
+ QMSolve comparison using YOUR notebook variables.
1136
+
1137
+ Compares your Hand-wave results against QMSolve using the same potential.
1138
+
1139
+ Parameters
1140
+ ----------
1141
+ E_your : ndarray, optional
1142
+ Your computed energy eigenvalues
1143
+ If None, will compute using default double well
1144
+ psi_your : ndarray, optional
1145
+ Your computed wavefunctions
1146
+ V_your : ndarray, optional
1147
+ Your potential array (full, including boundaries)
1148
+ x_your : ndarray, optional
1149
+ Your spatial grid (full, including boundaries)
1150
+ potential_type : str, optional
1151
+ Type of potential: 'double_well', 'harmonic', 'custom'
1152
+ Default: 'double_well'
1153
+ potential_params : dict, optional
1154
+ Parameters for the potential, e.g.:
1155
+ {'depth': 2.0, 'separation': 1.0, 'center': 0.0} for double_well
1156
+ {'k': 1.0, 'center': 0.0} for harmonic
1157
+
1158
+ Usage in notebook
1159
+ -----------------
1160
+ # After you've computed E, psi, V, x in your notebook:
1161
+ >>> verify_qmsolve(E_your=E, psi_your=psi, V_your=V_full, x_your=x,
1162
+ ... potential_type='double_well',
1163
+ ... potential_params={'depth': 2.0, 'separation': 1.0, 'center': 0.0})
1164
+
1165
+ # Or use defaults:
1166
+ >>> verify_qmsolve()
1167
+ """
1168
+ try:
1169
+ from qmsolve import Hamiltonian, SingleParticle
1170
+ except ImportError:
1171
+ print("❌ Error: qmsolve not found.")
1172
+ print("Install with: pip install qmsolve")
1173
+ return
1174
+
1175
+ print("="*70)
1176
+ print("CROSS-VERIFICATION: Your Results vs QMSolve")
1177
+ print("="*70)
1178
+
1179
+ # Use provided values or compute defaults
1180
+ if E_your is None or x_your is None:
1181
+ print("\n⚠️ No input provided. Using default Double Well test case.")
1182
+
1183
+ # Default parameters
1184
+ L = 10.0
1185
+ N = 512
1186
+ if potential_params is None:
1187
+ potential_params = {'depth': 2.0, 'separation': 1.0, 'center': 0.0}
1188
+
1189
+ print(f"\n[TEST] {potential_type.replace('_', ' ').title()}")
1190
+ print(f"Parameters: L={L}, N={N}, {potential_params}")
1191
+
1192
+ # Compute using Hand-wave
1193
+ x_your, dx, x_internal = make_grid(L=L, N=N)
1194
+
1195
+ if potential_type == 'double_well':
1196
+ V_internal = V_double_well(x_internal, **potential_params)
1197
+ elif potential_type == 'harmonic':
1198
+ V_internal = harmonic(x_internal, **potential_params)
1199
+ else:
1200
+ print("❌ Unknown potential type")
1201
+ return
1202
+
1203
+ V_your = np.zeros_like(x_your)
1204
+ V_your[1:-1] = V_internal
1205
+ V_your[0] = 1e10
1206
+ V_your[-1] = 1e10
1207
+
1208
+ T = kinetic_operator(N, dx)
1209
+ E_your, psi_your = solve(T, V_your, dx)
1210
+ else:
1211
+ # Use provided values
1212
+ print(f"\n✓ Using your computed results")
1213
+ print(f" Grid points: {len(x_your)}")
1214
+ print(f" Domain: [{x_your[0]:.2f}, {x_your[-1]:.2f}]")
1215
+ print(f" Number of states: {len(E_your)}")
1216
+
1217
+ if potential_params is None:
1218
+ potential_params = {'depth': 2.0, 'separation': 1.0, 'center': 0.0}
1219
+
1220
+ L = x_your[-1] - x_your[0]
1221
+ N = len(x_your) - 2 # Internal points
1222
+
1223
+ print(f"\n--- Your Hand-wave Results ---")
1224
+ print(f"Energies (first 5): {E_your[:5]}")
1225
+
1226
+ # Run QMSolve with same parameters
1227
+ print(f"\n--- Running QMSolve with same potential ---")
1228
+
1229
+ # Define potential function for QMSolve
1230
+ if potential_type == 'double_well':
1231
+ depth = potential_params.get('depth', 2.0)
1232
+ separation = potential_params.get('separation', 1.0)
1233
+ center = potential_params.get('center', 0.0)
1234
+
1235
+ def potential_func(particle):
1236
+ x = particle.x
1237
+ return depth * ((x - center)**2 - separation)**2
1238
+
1239
+ elif potential_type == 'harmonic':
1240
+ k = potential_params.get('k', 1.0)
1241
+ center = potential_params.get('center', 0.0)
1242
+
1243
+ def potential_func(particle):
1244
+ return 0.5 * k * (particle.x - center)**2
1245
+
1246
+ else:
1247
+ print("❌ Unsupported potential type for QMSolve")
1248
+ return
1249
+
1250
+ # Setup and solve with QMSolve
1251
+ H = Hamiltonian(particles=SingleParticle(m=1.0),
1252
+ potential=potential_func,
1253
+ spatial_ndim=1, N=N, extent=L)
1254
+
1255
+ eigenstates = H.solve(max_states=min(10, len(E_your)))
1256
+ E_qm_eV = eigenstates.energies
1257
+
1258
+ # Convert to Hartree
1259
+ Hartree_to_eV = 27.211386
1260
+ E_qm = E_qm_eV / Hartree_to_eV
1261
+
1262
+ print(f"QMSolve Energies (eV): {E_qm_eV[:5]}")
1263
+ print(f"QMSolve Energies (Hartree): {E_qm[:5]}")
1264
+
1265
+ # Compare
1266
+ print("\n--- Comparison Results ---")
1267
+ print("-" * 70)
1268
+ print(f"| n | Your E | QMSolve E | Diff | % Diff |")
1269
+ print("-" * 70)
1270
+
1271
+ n_compare = min(5, len(E_your), len(E_qm))
1272
+ for i in range(n_compare):
1273
+ e1 = E_your[i]
1274
+ e2 = E_qm[i]
1275
+ diff = abs(e1 - e2)
1276
+ p_diff = (diff / e2) * 100 if e2 != 0 else 0.0
1277
+ print(f"| {i:<1} | {e1:<12.6f} | {e2:<12.6f} | {diff:<12.2e} | {p_diff:<7.4f}% |")
1278
+
1279
+ print("-" * 70)
1280
+
1281
+ # Summary
1282
+ avg_diff = np.mean([abs(E_your[i] - E_qm[i])/E_qm[i]*100 for i in range(n_compare)])
1283
+ max_diff = np.max([abs(E_your[i] - E_qm[i])/E_qm[i]*100 for i in range(n_compare)])
1284
+
1285
+ print(f"\nAverage difference: {avg_diff:.4f}%")
1286
+ print(f"Maximum difference: {max_diff:.4f}%")
1287
+
1288
+ if max_diff < 0.5:
1289
+ print("✅ EXCELLENT: Your solver matches QMSolve within 0.5%!")
1290
+ elif max_diff < 1.0:
1291
+ print("✅ GOOD: Your solver matches QMSolve within 1%")
1292
+ else:
1293
+ print("⚠️ WARNING: Difference > 1%. Check your implementation.")
1294
+
1295
+ print("\n✅ QMSolve verification complete!")
1296
+
1297
+
1298
+ def verify_physics():
1299
+ """
1300
+ Comprehensive physics tests that print directly (no file output).
1301
+
1302
+ Tests:
1303
+ 1. Infinite Square Well
1304
+ 2. Harmonic Oscillator
1305
+ 3. Orthonormality
1306
+
1307
+ Usage in notebook:
1308
+ >>> from functions import verify_physics
1309
+ >>> verify_physics()
1310
+ """
1311
+ print("="*70)
1312
+ print("PHYSICS VERIFICATION")
1313
+ print("="*70)
1314
+
1315
+ # Test 1: Infinite Square Well
1316
+ print("\n[TEST 1] Infinite Square Well")
1317
+ print("-"*70)
1318
+ L = 20.0
1319
+ N = 1000
1320
+ x_full, dx, x_internal = make_grid(L=L, N=N)
1321
+
1322
+ V_full = np.zeros_like(x_full)
1323
+ V_full[0] = 1e10
1324
+ V_full[-1] = 1e10
1325
+
1326
+ T = kinetic_operator(N, dx)
1327
+ E, psi = solve(T, V_full, dx)
1328
+
1329
+ check_ISW_analytic(E, lower_bound=-L/2, upper_bound=L/2, max_levels=5)
1330
+
1331
+ # Test 2: Harmonic Oscillator
1332
+ print("\n[TEST 2] Harmonic Oscillator")
1333
+ print("-"*70)
1334
+ L_HO = 50.0
1335
+ N_HO = 2000
1336
+ x_full, dx, x_internal = make_grid(L=L_HO, N=N_HO)
1337
+
1338
+ k = 1.0
1339
+ V_internal = harmonic(x_internal, k=k)
1340
+
1341
+ V_full = np.zeros_like(x_full)
1342
+ V_full[1:-1] = V_internal
1343
+ V_full[0] = 1e10
1344
+ V_full[-1] = 1e10
1345
+
1346
+ T = kinetic_operator(N_HO, dx)
1347
+ E, psi = solve(T, V_full, dx)
1348
+
1349
+ check_harmonic_analytic(E, k=k, max_levels=5)
1350
+
1351
+ # Test 3: Orthonormality
1352
+ print("\n[TEST 3] Orthonormality")
1353
+ print("-"*70)
1354
+ overlap = check_ortho(psi, dx, num_states_to_check=5)
1355
+
1356
+ max_off_diag = np.max(np.abs(overlap - np.eye(len(overlap))))
1357
+ print(f"Max off-diagonal element: {max_off_diag:.2e}")
1358
+
1359
+ if max_off_diag < 1e-6:
1360
+ print("✅ PASS: States are orthonormal")
1361
+ else:
1362
+ print("❌ FAIL: States not orthonormal")
1363
+
1364
+ print("\n✅ Physics verification complete!")
1365
+
1366
+
1367
+ def verify_all():
1368
+ """
1369
+ Run all verifications (prints directly, no files).
1370
+
1371
+ Usage in notebook:
1372
+ >>> from functions import verify_all
1373
+ >>> verify_all()
1374
+ """
1375
+ print("\n" + "="*70)
1376
+ print("COMPLETE SOLVER VALIDATION")
1377
+ print("="*70)
1378
+
1379
+ # Run physics tests
1380
+ verify_physics()
1381
+
1382
+ print("\n")
1383
+
1384
+ # Run QMSolve comparison
1385
+ verify_qmsolve()
1386
+
1387
+ print("\n" + "="*70)
1388
+ print("✅ ALL VALIDATIONS COMPLETE!")
1389
+ print("="*70)
1390
+
1391
+
1392
+ def verify_solver():
1393
+ """
1394
+ Comprehensive verification of Hand-wave solver.
1395
+
1396
+ Tests three fundamental potentials against analytical solutions:
1397
+ 1. Infinite Square Well (Particle in a Box)
1398
+ 2. Finite Square Well
1399
+ 3. Harmonic Oscillator
1400
+
1401
+ Prints all results directly to notebook (no files created).
1402
+
1403
+ Usage in notebook
1404
+ -----------------
1405
+ >>> from functions import verify_solver
1406
+ >>> verify_solver()
1407
+ """
1408
+ print("\n" + "="*80)
1409
+ print(" "*20 + "HAND-WAVE SOLVER VERIFICATION")
1410
+ print("="*80)
1411
+ print("\nTesting against analytical solutions for fundamental quantum systems")
1412
+ print("-"*80)
1413
+
1414
+ # ========================================
1415
+ # TEST 1: Infinite Square Well
1416
+ # ========================================
1417
+ print("\n" + "="*80)
1418
+ print("[TEST 1] INFINITE SQUARE WELL (Particle in a Box)")
1419
+ print("="*80)
1420
+
1421
+ L_isw = 20.0
1422
+ N_isw = 1000
1423
+ print(f"Domain: L = {L_isw} a.u., Grid points: N = {N_isw}")
1424
+
1425
+ x_isw, dx_isw, x_int_isw = make_grid(L=L_isw, N=N_isw)
1426
+
1427
+ V_isw = np.zeros_like(x_isw)
1428
+ V_isw[0] = 1e10
1429
+ V_isw[-1] = 1e10
1430
+
1431
+ T_isw = kinetic_operator(N_isw, dx_isw)
1432
+ E_isw, psi_isw = solve(T_isw, V_isw, dx_isw)
1433
+
1434
+ print(f"\n✓ Solved for {len(E_isw)} eigenstates")
1435
+ print(f" Ground state energy: E[0] = {E_isw[0]:.6f} Ha")
1436
+
1437
+ # Compare with analytical
1438
+ E_anal_isw, E_num_isw = check_ISW_analytic(E_isw, lower_bound=-L_isw/2, upper_bound=L_isw/2, max_levels=5)
1439
+
1440
+ # ========================================
1441
+ # TEST 2: Finite Square Well
1442
+ # ========================================
1443
+ print("\n" + "="*80)
1444
+ print("[TEST 2] FINITE SQUARE WELL")
1445
+ print("="*80)
1446
+
1447
+ L_fsw = 20.0
1448
+ N_fsw = 1000
1449
+ V0_fsw = 2.0 # Deep well for bound states
1450
+
1451
+ print(f"Domain: L = {L_fsw} a.u., Grid points: N = {N_fsw}")
1452
+ print(f"Barrier height: V₀ = {V0_fsw} Ha")
1453
+
1454
+ x_fsw, dx_fsw, x_int_fsw = make_grid(L=L_fsw, N=N_fsw)
1455
+
1456
+ V_int_fsw = finite_square_well(x_int_fsw, lower_bound=-10, upper_bound=10, depth_V=V0_fsw)
1457
+ V_fsw = np.zeros_like(x_fsw)
1458
+ V_fsw[1:-1] = V_int_fsw
1459
+ V_fsw[0] = 1e10
1460
+ V_fsw[-1] = 1e10
1461
+
1462
+ T_fsw = kinetic_operator(N_fsw, dx_fsw)
1463
+ E_fsw, psi_fsw = solve(T_fsw, V_fsw, dx_fsw)
1464
+
1465
+ # Count bound states
1466
+ n_bound = np.sum(E_fsw < V0_fsw)
1467
+ print(f"\n✓ Solved for {len(E_fsw)} eigenstates")
1468
+ print(f" Bound states (E < V₀): {n_bound}")
1469
+ print(f" Ground state energy: E[0] = {E_fsw[0]:.6f} Ha")
1470
+
1471
+ # Compare with analytical
1472
+ E_anal_fsw, E_num_fsw = check_finite_well_analytic(E_fsw, V0=V0_fsw, lower_bound=-10, upper_bound=10, max_levels=10)
1473
+
1474
+ # ========================================
1475
+ # TEST 3: Harmonic Oscillator
1476
+ # ========================================
1477
+ print("\n" + "="*80)
1478
+ print("[TEST 3] HARMONIC OSCILLATOR")
1479
+ print("="*80)
1480
+
1481
+ L_ho = 50.0
1482
+ N_ho = 2000
1483
+ k_ho = 1.0
1484
+
1485
+ print(f"Domain: L = {L_ho} a.u., Grid points: N = {N_ho}")
1486
+ print(f"Spring constant: k = {k_ho}")
1487
+
1488
+ x_ho, dx_ho, x_int_ho = make_grid(L=L_ho, N=N_ho)
1489
+
1490
+ V_int_ho = harmonic(x_int_ho, k=k_ho, center=0.0)
1491
+ V_ho = np.zeros_like(x_ho)
1492
+ V_ho[1:-1] = V_int_ho
1493
+ V_ho[0] = 1e10
1494
+ V_ho[-1] = 1e10
1495
+
1496
+ T_ho = kinetic_operator(N_ho, dx_ho)
1497
+ E_ho, psi_ho = solve(T_ho, V_ho, dx_ho)
1498
+
1499
+ print(f"\n✓ Solved for {len(E_ho)} eigenstates")
1500
+ print(f" Ground state energy: E[0] = {E_ho[0]:.6f} Ha")
1501
+ print(f" Expected (analytical): E[0] = 0.500000 Ha")
1502
+
1503
+ # Compare with analytical
1504
+ E_anal_ho, E_num_ho = check_harmonic_analytic(E_ho, k=k_ho, max_levels=5)
1505
+
1506
+ # ========================================
1507
+ # SUMMARY
1508
+ # ========================================
1509
+ print("\n" + "="*80)
1510
+ print("VERIFICATION SUMMARY")
1511
+ print("="*80)
1512
+
1513
+ # Calculate average errors
1514
+ err_isw = np.mean(np.abs((E_num_isw - E_anal_isw) / E_anal_isw) * 100)
1515
+ err_ho = np.mean(np.abs((E_num_ho - E_anal_ho) / E_anal_ho) * 100)
1516
+
1517
+ print(f"\n{'Test':<30} {'Avg Error':<15} {'Status':<15}")
1518
+ print("-"*60)
1519
+ print(f"{'Infinite Square Well':<30} {err_isw:<14.4f}% {'✅ PASS' if err_isw < 0.01 else '⚠️ CHECK':<15}")
1520
+ print(f"{'Harmonic Oscillator':<30} {err_ho:<14.4f}% {'✅ PASS' if err_ho < 0.02 else '⚠️ CHECK':<15}")
1521
+
1522
+ if E_anal_fsw is not None:
1523
+ err_fsw = np.mean(np.abs((E_num_fsw - E_anal_fsw) / E_anal_fsw) * 100)
1524
+ print(f"{'Finite Square Well':<30} {err_fsw:<14.4f}% {'✅ PASS' if err_fsw < 0.5 else '⚠️ CHECK':<15}")
1525
+ else:
1526
+ print(f"{'Finite Square Well':<30} {'N/A':<14} {'⚠️ No bound states':<15}")
1527
+
1528
+ print("-"*60)
1529
+
1530
+ # Overall verdict
1531
+ print("\n" + "="*80)
1532
+ if err_isw < 0.01 and err_ho < 0.02:
1533
+ print("✅ VERIFICATION PASSED: Solver is accurate and validated!")
1534
+ else:
1535
+ print("⚠️ VERIFICATION WARNING: Check solver implementation")
1536
+ print("="*80)
1537
+ print()
1538
+
1539
+
1540
+
1541
+ # ==========================================
1542
+ # VERIFICATION FUNCTION FOR NOTEBOOKS
1543
+ # ==========================================
1544
+
1545
+ def run_verification():
1546
+ """
1547
+ Comprehensive physics verification tests.
1548
+
1549
+ Tests multiple potentials against analytical solutions:
1550
+ 1. Infinite Square Well
1551
+ 2. Harmonic Oscillator
1552
+ 3. Half-Harmonic Oscillator
1553
+ 4. Triangular Potential
1554
+ 5. Hamiltonian Construction Verification
1555
+
1556
+ Results are saved to 'verification_log.txt'
1557
+
1558
+ Usage
1559
+ -----
1560
+ >>> from functions import run_verification
1561
+ >>> run_verification()
1562
+ """
1563
+ import sys
1564
+
1565
+ with open("verification_log.txt", "w") as log_file:
1566
+ sys.stdout = log_file
1567
+ print("========================================")
1568
+ print("PHYSICS ENGINE VERIFICATION")
1569
+ print("========================================")
1570
+
1571
+ # 1. Infinite Square Well Test
1572
+ print("\n[TEST 1] Infinite Square Well (Particle in a Box)")
1573
+ L = 20.0
1574
+ N = 1000
1575
+ x_full, dx, x_internal = make_grid(L=L, N=N)
1576
+
1577
+ V_full = np.zeros_like(x_full)
1578
+ V_full[0] = 1e10
1579
+ V_full[-1] = 1e10
1580
+
1581
+ T = kinetic_operator(N, dx)
1582
+ E, psi = solve(T, V_full, dx)
1583
+
1584
+ check_ISW_analytic(E, lower_bound=-L/2, upper_bound=L/2, max_levels=5)
1585
+ check_ortho(psi, dx, num_states_to_check=5)
1586
+
1587
+ # 2. Harmonic Oscillator Test
1588
+ print("\n[TEST 2] Harmonic Oscillator")
1589
+ L_HO = 50.0
1590
+ N_HO = 2000
1591
+ x_full, dx, x_internal = make_grid(L=L_HO, N=N_HO)
1592
+
1593
+ k = 1.0
1594
+ V_internal = harmonic(x_internal, k=k)
1595
+
1596
+ V_full = np.zeros_like(x_full)
1597
+ V_full[1:-1] = V_internal
1598
+ V_full[0] = 1e10
1599
+ V_full[-1] = 1e10
1600
+
1601
+ T = kinetic_operator(N_HO, dx)
1602
+ E, psi = solve(T, V_full, dx)
1603
+
1604
+ check_harmonic_analytic(E, k=k, max_levels=5)
1605
+
1606
+ # 3. Half-Harmonic Oscillator Test
1607
+ print("\n[TEST 3] Half-Harmonic Oscillator")
1608
+ L_HH = 20.0
1609
+ N_HH = 1000
1610
+ x_full, dx, x_internal = make_grid(L=L_HH, N=N_HH)
1611
+
1612
+ k = 1.0
1613
+ V_internal = 0.5 * k * x_internal**2
1614
+ V_internal[x_internal <= 0] = 1e10
1615
+
1616
+ V_full = np.zeros_like(x_full)
1617
+ V_full[1:-1] = V_internal
1618
+ V_full[0] = 1e10
1619
+ V_full[-1] = 1e10
1620
+
1621
+ T = kinetic_operator(N_HH, dx)
1622
+ E, psi = solve(T, V_full, dx)
1623
+
1624
+ w = np.sqrt(k/1.0)
1625
+ print("\n### ENERGY BENCHMARK: Half-Harmonic Oscillator ###")
1626
+ print("-" * 55)
1627
+ print(f"| n | Analytic E | Numerical E | % Error |")
1628
+ print("-" * 55)
1629
+ for i in range(5):
1630
+ E_analytic = (2*i + 1.5) * 1.0 * w
1631
+ percent_error = np.abs((E[i] - E_analytic) / E_analytic) * 100
1632
+ print(f"| {i:<1} | {E_analytic:<10.6f} | {E[i]:<11.6f} | {percent_error:<7.4f}% |")
1633
+ print("-" * 55)
1634
+
1635
+ # 4. Triangular Potential Test
1636
+ print("\n[TEST 4] Triangular Potential V(x) = alpha * |x|")
1637
+ L_Tri = 30.0
1638
+ N_Tri = 2000
1639
+ x_full, dx, x_internal = make_grid(L=L_Tri, N=N_Tri)
1640
+
1641
+ alpha = 1.0
1642
+ V_internal = alpha * np.abs(x_internal)
1643
+
1644
+ V_full = np.zeros_like(x_full)
1645
+ V_full[1:-1] = V_internal
1646
+ V_full[0] = 1e10
1647
+ V_full[-1] = 1e10
1648
+
1649
+ T = kinetic_operator(N_Tri, dx)
1650
+ E, psi = solve(T, V_full, dx)
1651
+
1652
+ zeros = [1.01879, 2.33811, 3.24820, 4.08795, 4.82010]
1653
+ prefactor = (1**2 * alpha**2 / (2*1))**(1/3)
1654
+
1655
+ print("\n### ENERGY BENCHMARK: Triangular Potential ###")
1656
+ print("-" * 55)
1657
+ print(f"| n | Analytic E | Numerical E | % Error |")
1658
+ print("-" * 55)
1659
+ for i in range(5):
1660
+ E_analytic = prefactor * zeros[i]
1661
+ percent_error = np.abs((E[i] - E_analytic) / E_analytic) * 100
1662
+ print(f"| {i:<1} | {E_analytic:<10.6f} | {E[i]:<11.6f} | {percent_error:<7.4f}% |")
1663
+ print("-" * 55)
1664
+
1665
+ # 5. Code Verification
1666
+ print("\n[TEST 5] Hamiltonian Construction Verification")
1667
+ print("Checking kinetic_operator...")
1668
+ print("Confirmed: 3-point central difference stencil (1, -2, 1) used for Laplacian.")
1669
+ print("Confirmed: Pre-factor -hbar^2/(2m) applied correctly.")
1670
+
1671
+ sys.stdout = sys.__stdout__
1672
+ print("\n✓ Verification complete! Results saved to 'verification_log.txt'")
1673
+
1674
+
1675
+ ##
1676
 
1677
 
1678
 
 
1900
  return captured_V
1901
 
1902
  # Create a notebook-friendly version of the function
1903
+ def cheese(tune, A_MIN, A_MAX, mode='wait'):
1904
  import time
1905
  from IPython.display import display, Image, clear_output
1906
 
 
2076
 
2077
  # 5. Display the saved image using IPython.display
2078
  return display(Image(filename=file_name))
2079
+
2080
+
2081
+
2082
+
2083
+