Skip to content

Commit 1278269

Browse files
committed
fix unit tests within test_levels.py in relation to PR #267
1 parent 7209969 commit 1278269

1 file changed

Lines changed: 142 additions & 107 deletions

File tree

tests/test_CodeEntropy/test_levels.py

Lines changed: 142 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import numpy as np
44

5+
from CodeEntropy.axes import AxesManager
56
from CodeEntropy.levels import LevelManager
67
from CodeEntropy.mda_universe_operations import UniverseOperations
78
from tests.test_CodeEntropy.test_base import BaseTestCase
@@ -383,48 +384,47 @@ def test_get_matrices_united_atom_customised_axes(self):
383384
axes.get_UA_axes.assert_called_once()
384385
assert axes.get_residue_axes.call_count == 0
385386

386-
def test_get_matrices_non_customised_axes_path(self):
387+
def test_get_matrices_non_customised_axes_path_atomic(self):
387388
"""
388-
Test that: customised_axes=False triggers the else axes path.
389-
Covers:
390-
trans_axes = data_container.atoms.principal_axes()
391-
rot_axes = real(bead.principal_axes())
392-
eigenvalues, _ = np.linalg.eig(bead.moment_of_inertia())
393-
moment_of_inertia sorted(...)
394-
center = bead.center_of_mass()
389+
Tests that `customised_axes=False` triggers the non-customised axes path.
390+
391+
Verifies that:
392+
- translational axes are taken from `data_container.atoms.principal_axes()`
393+
- rotational axes are taken from `bead.principal_axes()` (real-valued)
394+
- bead moment of inertia and center of mass are queried
395+
- force and torque matrices are assembled with size (3N, 3N) for N beads
395396
"""
396397
universe_operations = UniverseOperations()
397398
level_manager = LevelManager(universe_operations)
398399

399-
bead1 = MagicMock()
400-
bead2 = MagicMock()
401-
402-
bead1.principal_axes.return_value = np.eye(3)
403-
bead2.principal_axes.return_value = np.eye(3)
404-
400+
bead1, bead2 = MagicMock(), MagicMock()
401+
bead1.principal_axes.return_value = np.eye(3) * (1 + 2j)
402+
bead2.principal_axes.return_value = np.eye(3) * (1 + 2j)
405403
bead1.center_of_mass.return_value = np.zeros(3)
406404
bead2.center_of_mass.return_value = np.zeros(3)
407-
408405
bead1.moment_of_inertia.return_value = np.eye(3)
409406
bead2.moment_of_inertia.return_value = np.eye(3)
410407

411408
level_manager.get_beads = MagicMock(return_value=[bead1, bead2])
412-
413409
level_manager.get_weighted_forces = MagicMock(
414410
return_value=np.array([1.0, 2.0, 3.0])
415411
)
416412
level_manager.get_weighted_torques = MagicMock(
417413
return_value=np.array([0.5, 1.5, 2.5])
418414
)
419-
level_manager.create_submatrix = MagicMock(return_value=np.identity(3))
415+
level_manager.create_submatrix = MagicMock(return_value=np.eye(3))
420416

421417
data_container = MagicMock()
422418
data_container.atoms = MagicMock()
423419
data_container.atoms.principal_axes.return_value = np.eye(3)
424420

425-
with patch("CodeEntropy.levels.np.linalg.eig") as eig_mock:
426-
eig_mock.return_value = (np.array([3.0, 2.0, 1.0]), None)
427-
421+
with (
422+
patch("CodeEntropy.levels.make_whole", autospec=True),
423+
patch(
424+
"CodeEntropy.levels.np.linalg.eig",
425+
return_value=(np.array([1.0, 3.0, 2.0]), None),
426+
),
427+
):
428428
force_matrix, torque_matrix = level_manager.get_matrices(
429429
data_container=data_container,
430430
level="polymer",
@@ -435,15 +435,17 @@ def test_get_matrices_non_customised_axes_path(self):
435435
customised_axes=False,
436436
)
437437

438+
data_container.atoms.principal_axes.assert_called()
439+
bead1.principal_axes.assert_called()
440+
bead2.principal_axes.assert_called()
441+
bead1.center_of_mass.assert_called()
442+
bead2.center_of_mass.assert_called()
443+
bead1.moment_of_inertia.assert_called()
444+
bead2.moment_of_inertia.assert_called()
445+
438446
assert force_matrix.shape == (6, 6)
439447
assert torque_matrix.shape == (6, 6)
440448

441-
data_container.atoms.principal_axes.assert_called()
442-
assert bead1.principal_axes.called and bead2.principal_axes.called
443-
assert bead1.center_of_mass.called and bead2.center_of_mass.called
444-
assert bead1.moment_of_inertia.called and bead2.moment_of_inertia.called
445-
assert eig_mock.call_count == 2
446-
447449
def test_get_matrices_accepts_existing_same_shape(self):
448450
"""
449451
Test that: if force_matrix and torque_matrix are provided with correct shape,
@@ -558,43 +560,54 @@ def test_get_combined_forcetorque_matrices_residue_customised_init(self):
558560
def test_get_combined_forcetorque_matrices_noncustomised_axes_path(self):
559561
"""
560562
Test that: customised_axes=False forces else-path:
561-
trans_axes = data_container.atoms.principal_axes()
562-
rot_axes = real(bead.principal_axes())
563-
eig(bead.moment_of_inertia()) called
564-
center_of_mass called
563+
- make_whole(data_container.atoms) and make_whole(bead) called
564+
- trans_axes = data_container.atoms.principal_axes()
565+
- rot_axes, moment_of_inertia = AxesManager.get_vanilla_axes(bead)
566+
- center = bead.center_of_mass(unwrap=True)
567+
- FT block matrix assembled via create_FTsubmatrix and np.block
565568
"""
566569
universe_operations = UniverseOperations()
567570
level_manager = LevelManager(universe_operations)
568571

569-
bead1 = MagicMock()
570-
bead2 = MagicMock()
571-
572-
bead1.principal_axes.return_value = np.eye(3)
573-
bead2.principal_axes.return_value = np.eye(3)
572+
bead1 = MagicMock(name="bead1")
573+
bead2 = MagicMock(name="bead2")
574+
beads = [bead1, bead2]
574575

575-
bead1.moment_of_inertia.return_value = np.eye(3)
576-
bead2.moment_of_inertia.return_value = np.eye(3)
577-
578-
bead1.center_of_mass.return_value = np.zeros(3)
579-
bead2.center_of_mass.return_value = np.zeros(3)
576+
level_manager.get_beads = MagicMock(return_value=beads)
580577

581-
level_manager.get_beads = MagicMock(return_value=[bead1, bead2])
578+
data_container = MagicMock(name="data_container")
579+
data_container.atoms = MagicMock(name="atoms")
580+
data_container.atoms.principal_axes.return_value = np.eye(3)
582581

582+
# Forces/torques are 3-vectors -> concatenated to length 6
583583
level_manager.get_weighted_forces = MagicMock(
584-
return_value=np.array([1.0, 2.0, 3.0])
584+
side_effect=[
585+
np.array([1.0, 2.0, 3.0]),
586+
np.array([1.1, 2.1, 3.1]),
587+
]
585588
)
586589
level_manager.get_weighted_torques = MagicMock(
587-
return_value=np.array([4.0, 5.0, 6.0])
590+
side_effect=[
591+
np.array([4.0, 5.0, 6.0]),
592+
np.array([4.1, 5.1, 6.1]),
593+
]
588594
)
589595

590596
level_manager.create_FTsubmatrix = MagicMock(return_value=np.identity(6))
591597

592-
data_container = MagicMock()
593-
data_container.atoms = MagicMock()
594-
data_container.atoms.principal_axes.return_value = np.eye(3)
598+
rot_axes_expected = np.eye(3)
599+
moi_expected = np.array([3.0, 2.0, 1.0])
595600

596-
with patch("CodeEntropy.levels.np.linalg.eig") as eig_mock:
597-
eig_mock.return_value = (np.array([3.0, 2.0, 1.0]), None)
601+
with (
602+
patch("CodeEntropy.levels.make_whole", autospec=True) as mw_mock,
603+
patch(
604+
"CodeEntropy.axes.AxesManager.get_vanilla_axes",
605+
autospec=True,
606+
return_value=(rot_axes_expected, moi_expected),
607+
) as vanilla_mock,
608+
):
609+
bead1.center_of_mass.return_value = np.zeros(3)
610+
bead2.center_of_mass.return_value = np.zeros(3)
598611

599612
ft_matrix = level_manager.get_combined_forcetorque_matrices(
600613
data_container=data_container,
@@ -605,13 +618,20 @@ def test_get_combined_forcetorque_matrices_noncustomised_axes_path(self):
605618
customised_axes=False,
606619
)
607620

608-
assert ft_matrix.shape == (12, 12)
609-
610621
data_container.atoms.principal_axes.assert_called()
611-
assert bead1.principal_axes.called and bead2.principal_axes.called
612-
assert bead1.moment_of_inertia.called and bead2.moment_of_inertia.called
613-
assert bead1.center_of_mass.called and bead2.center_of_mass.called
614-
assert eig_mock.call_count == 2
622+
bead1.center_of_mass.assert_called_with(unwrap=True)
623+
bead2.center_of_mass.assert_called_with(unwrap=True)
624+
625+
assert vanilla_mock.call_count == 2 # once per bead
626+
627+
# make_whole is called twice per bead: on data_container.atoms and on bead
628+
assert mw_mock.call_count == 4
629+
mw_mock.assert_any_call(data_container.atoms)
630+
mw_mock.assert_any_call(bead1)
631+
mw_mock.assert_any_call(bead2)
632+
633+
# result shape: (6N, 6N) with N=2
634+
assert ft_matrix.shape == (12, 12)
615635

616636
def test_get_combined_forcetorque_matrices_shape_mismatch_raises(self):
617637
"""
@@ -924,61 +944,75 @@ def test_get_weighted_forces_negative_mass_raises_value_error(self):
924944

925945
def test_get_weighted_torques_weighted_torque_basic(self):
926946
"""
927-
Test basic torque calculation with non-zero moment of inertia and torques.
947+
Test basic weighted torque calculation for a single-atom bead.
948+
949+
Setup:
950+
r = [1, 0, 0], F = [0, 1, 0] => r x F = [0, 0, 1]
951+
With force_partitioning=0.5, rot_axes=I, MOI=[1,1,1],
952+
expected weighted torque is [0, 0, 0.5].
928953
"""
929954
universe_operations = UniverseOperations()
930955
level_manager = LevelManager(universe_operations)
956+
axes_manager = AxesManager()
931957

932-
# Bead with one "atom"
933958
bead = MagicMock()
934-
bead.positions = np.array([[1.0, 0.0, 0.0]]) # r
935-
bead.forces = np.array([[0.0, 1.0, 0.0]]) # F
959+
bead.positions = np.array([[1.0, 0.0, 0.0]])
960+
bead.forces = np.array([[0.0, 1.0, 0.0]])
961+
bead.dimensions = np.array([10.0, 10.0, 10.0])
936962

937-
rot_axes = np.identity(3)
938-
center = np.array([0.0, 0.0, 0.0])
963+
rot_axes = np.eye(3)
964+
center = np.zeros(3)
939965
force_partitioning = 0.5
940966
moment_of_inertia = np.array([1.0, 1.0, 1.0])
941967

942-
result = level_manager.get_weighted_torques(
943-
bead=bead,
944-
rot_axes=rot_axes,
945-
center=center,
946-
force_partitioning=force_partitioning,
947-
moment_of_inertia=moment_of_inertia,
948-
)
968+
with patch.object(
969+
AxesManager, "get_vector", return_value=bead.positions - center
970+
) as gv_mock:
971+
result = level_manager.get_weighted_torques(
972+
bead=bead,
973+
rot_axes=rot_axes,
974+
center=center,
975+
force_partitioning=force_partitioning,
976+
moment_of_inertia=moment_of_inertia,
977+
axes_manager=axes_manager,
978+
)
979+
980+
gv_mock.assert_called()
949981

950982
expected = np.array([0.0, 0.0, 0.5])
951-
np.testing.assert_allclose(result, expected, rtol=0, atol=1e-12)
983+
np.testing.assert_allclose(result, expected)
952984

953985
def test_get_weighted_torques_zero_torque_skips_division(self):
954986
"""
955987
Test that zero torque components skip division and remain zero.
956988
"""
957989
universe_operations = UniverseOperations()
958990
level_manager = LevelManager(universe_operations)
991+
axes_manager = AxesManager()
959992

960993
bead = MagicMock()
961-
# All zeros => r x F = 0
962994
bead.positions = np.array([[0.0, 0.0, 0.0]])
963995
bead.forces = np.array([[0.0, 0.0, 0.0]])
996+
bead.dimensions = np.array([10.0, 10.0, 10.0])
964997

965998
rot_axes = np.identity(3)
966999
center = np.array([0.0, 0.0, 0.0])
9671000
force_partitioning = 0.5
968-
969-
# Use non-zero MOI so that "skip division" is only due to zero torque
9701001
moment_of_inertia = np.array([1.0, 2.0, 3.0])
9711002

972-
result = level_manager.get_weighted_torques(
973-
bead=bead,
974-
rot_axes=rot_axes,
975-
center=center,
976-
force_partitioning=force_partitioning,
977-
moment_of_inertia=moment_of_inertia,
978-
)
1003+
with patch.object(
1004+
AxesManager, "get_vector", return_value=bead.positions - center
1005+
):
1006+
result = level_manager.get_weighted_torques(
1007+
bead=bead,
1008+
rot_axes=rot_axes,
1009+
center=center,
1010+
force_partitioning=force_partitioning,
1011+
moment_of_inertia=moment_of_inertia,
1012+
axes_manager=axes_manager,
1013+
)
9791014

980-
expected = np.zeros(3)
981-
np.testing.assert_array_equal(result, expected)
1015+
np.testing.assert_array_equal(result, np.zeros(3))
9821016

9831017
def test_get_weighted_torques_zero_moi(self):
9841018
"""
@@ -987,31 +1021,31 @@ def test_get_weighted_torques_zero_moi(self):
9871021
"""
9881022
universe_operations = UniverseOperations()
9891023
level_manager = LevelManager(universe_operations)
1024+
axes_manager = AxesManager()
9901025

9911026
bead = MagicMock()
992-
# r = (1,0,0), F = (0,1,0) => torque = (0,0,1)
9931027
bead.positions = np.array([[1.0, 0.0, 0.0]])
9941028
bead.forces = np.array([[0.0, 1.0, 0.0]])
1029+
bead.dimensions = np.array([10.0, 10.0, 10.0])
9951030

9961031
rot_axes = np.identity(3)
9971032
center = np.array([0.0, 0.0, 0.0])
9981033
force_partitioning = 0.5
999-
1000-
# MOI is zero in z dimension (index 2)
10011034
moment_of_inertia = np.array([1.0, 1.0, 0.0])
10021035

1003-
torque = level_manager.get_weighted_torques(
1004-
bead=bead,
1005-
rot_axes=rot_axes,
1006-
center=center,
1007-
force_partitioning=force_partitioning,
1008-
moment_of_inertia=moment_of_inertia,
1009-
)
1036+
with patch.object(
1037+
AxesManager, "get_vector", return_value=bead.positions - center
1038+
):
1039+
torque = level_manager.get_weighted_torques(
1040+
bead=bead,
1041+
rot_axes=rot_axes,
1042+
center=center,
1043+
force_partitioning=force_partitioning,
1044+
moment_of_inertia=moment_of_inertia,
1045+
axes_manager=axes_manager,
1046+
)
10101047

1011-
# x and y torques are zero; z torque is non-zero
1012-
# but MOI_z==0 => weighted z should be 0
1013-
expected = np.array([0.0, 0.0, 0.0])
1014-
np.testing.assert_array_equal(torque, expected)
1048+
np.testing.assert_array_equal(torque, np.zeros(3))
10151049

10161050
def test_get_weighted_torques_negative_moi_sets_zero(self):
10171051
"""
@@ -1020,30 +1054,31 @@ def test_get_weighted_torques_negative_moi_sets_zero(self):
10201054
"""
10211055
universe_operations = UniverseOperations()
10221056
level_manager = LevelManager(universe_operations)
1057+
axes_manager = AxesManager()
10231058

10241059
bead = MagicMock()
1025-
# r=(1,0,0), F=(0,1,0) => raw torque in z is non-zero
10261060
bead.positions = np.array([[1.0, 0.0, 0.0]])
10271061
bead.forces = np.array([[0.0, 1.0, 0.0]])
1062+
bead.dimensions = np.array([10.0, 10.0, 10.0])
10281063

10291064
rot_axes = np.identity(3)
10301065
center = np.array([0.0, 0.0, 0.0])
10311066
force_partitioning = 0.5
1032-
1033-
# Negative MOI in z dimension
10341067
moment_of_inertia = np.array([1.0, 1.0, -1.0])
10351068

1036-
result = level_manager.get_weighted_torques(
1037-
bead=bead,
1038-
rot_axes=rot_axes,
1039-
center=center,
1040-
force_partitioning=force_partitioning,
1041-
moment_of_inertia=moment_of_inertia,
1042-
)
1069+
with patch.object(
1070+
AxesManager, "get_vector", return_value=bead.positions - center
1071+
):
1072+
result = level_manager.get_weighted_torques(
1073+
bead=bead,
1074+
rot_axes=rot_axes,
1075+
center=center,
1076+
force_partitioning=force_partitioning,
1077+
moment_of_inertia=moment_of_inertia,
1078+
axes_manager=axes_manager,
1079+
)
10431080

1044-
# z torque would be non-zero, but negative MOI => z component forced to 0
1045-
expected = np.array([0.0, 0.0, 0.0])
1046-
np.testing.assert_array_equal(result, expected)
1081+
np.testing.assert_array_equal(result, np.zeros(3))
10471082

10481083
def test_create_submatrix_basic_outer_product(self):
10491084
"""

0 commit comments

Comments
 (0)