@@ -317,5 +317,58 @@ def test_calc_principal_layers_disp_vec():
317317# assert na == 4
318318# assert hamiltonian.device_norbs==device_norbs_standard
319319
320-
320+ def test_sort_device_lexico_preserves_species ():
321+ """Regression test for the device-sort species desync bug.
322+
323+ The block-tridiagonal device sort must permute the whole atoms slice so
324+ that atomic species stay attached to their coordinates. A previous version
325+ permuted only ``positions``, which for a non-pre-sorted, multi-species
326+ device relocated species onto the wrong sites and corrupted the
327+ Hamiltonian.
328+ """
329+ from ase import Atoms
330+ from dpnegf .negf .negf_hamiltonian_init import NEGFHamiltonianInit
331+ from dpnegf .negf .sort_btd import sort_lexico
332+
333+ # leads: 2 C atoms each; device: mixed C/H that is NOT lexicographically
334+ # ordered along z, so a correct sort must actually permute it.
335+ # idx: 0 1 | 2 3 4 5 | 6 7
336+ # sym: C C | C H C H | C C
337+ # z : 0 1 | 5 3 4 2 | 8 9
338+ symbols = ["C" , "C" , "C" , "H" , "C" , "H" , "C" , "C" ]
339+ zs = [0.0 , 1.0 , 5.0 , 3.0 , 4.0 , 2.0 , 8.0 , 9.0 ]
340+ positions = [[0.0 , 0.0 , z ] for z in zs ]
341+ atoms = Atoms (symbols = symbols , positions = positions ,
342+ cell = [10.0 , 10.0 , 10.0 ], pbc = [True , True , False ])
343+ device_id = [2 , 6 ]
344+
345+ # ground-truth mapping computed independently
346+ dev_pos = np .array (positions )[device_id [0 ]:device_id [1 ]]
347+ perm = sort_lexico (dev_pos )
348+ expected_dev_sym = np .array (symbols [device_id [0 ]:device_id [1 ]])[perm ].tolist ()
349+ expected_dev_z = dev_pos [perm ][:, 2 ].tolist ()
350+
351+ sorted_atoms = NEGFHamiltonianInit ._sort_device_lexico (atoms , device_id )
352+ got_sym = list (sorted_atoms .get_chemical_symbols ())
353+ got_z = sorted_atoms .positions [:, 2 ].tolist ()
354+
355+ # device region: species stay attached to their (now sorted) coordinates
356+ assert got_sym [device_id [0 ]:device_id [1 ]] == expected_dev_sym
357+ assert np .allclose (got_z [device_id [0 ]:device_id [1 ]], expected_dev_z )
358+ # device coordinates are actually sorted ascending in z
359+ assert got_z [device_id [0 ]:device_id [1 ]] == sorted (got_z [device_id [0 ]:device_id [1 ]])
360+ # H atoms remain H at the correct sorted coordinates (z=2 and z=3)
361+ dev_pairs = list (zip (got_sym [device_id [0 ]:device_id [1 ]],
362+ got_z [device_id [0 ]:device_id [1 ]]))
363+ assert ("H" , 2.0 ) in dev_pairs and ("H" , 3.0 ) in dev_pairs
364+
365+ # leads are untouched (verbatim)
366+ assert got_sym [:device_id [0 ]] == symbols [:device_id [0 ]]
367+ assert got_sym [device_id [1 ]:] == symbols [device_id [1 ]:]
368+ assert np .allclose (got_z [:device_id [0 ]], zs [:device_id [0 ]])
369+ assert np .allclose (got_z [device_id [1 ]:], zs [device_id [1 ]:])
370+
371+ # cell and pbc preserved
372+ assert np .allclose (sorted_atoms .cell , atoms .cell )
373+ assert list (sorted_atoms .pbc ) == list (atoms .pbc )
321374
0 commit comments