@@ -513,6 +513,183 @@ def test_run_entropy_workflow_missing_selection_string(self):
513513 with self .assertRaisesRegex (ValueError , "Missing 'selection_string' argument." ):
514514 run_manager .run_entropy_workflow ()
515515
516+ @patch ("CodeEntropy.run.EntropyManager" )
517+ @patch ("CodeEntropy.run.GroupMolecules" )
518+ @patch ("CodeEntropy.run.LevelManager" )
519+ @patch ("CodeEntropy.run.AnalysisFromFunction" )
520+ @patch ("CodeEntropy.run.mda.Merge" )
521+ @patch ("CodeEntropy.run.mda.Universe" )
522+ def test_merges_forcefile_correctly (
523+ self , MockUniverse , MockMerge , MockAnalysis , * _mocks
524+ ):
525+ """
526+ Ensure that coordinates and forces are merged correctly
527+ when a force file is provided.
528+ """
529+ run_manager = RunManager ("mock/job001" )
530+ mock_logger = MagicMock ()
531+ run_manager ._logging_config = MagicMock (
532+ setup_logging = MagicMock (return_value = mock_logger )
533+ )
534+ run_manager ._config_manager = MagicMock ()
535+ run_manager ._data_logger = MagicMock ()
536+ run_manager .show_splash = MagicMock ()
537+
538+ args = MagicMock (
539+ top_traj_file = ["topol.tpr" , "traj.xtc" ],
540+ force_file = "forces.xtc" ,
541+ file_format = "xtc" ,
542+ selection_string = "all" ,
543+ verbose = False ,
544+ output_file = "output.json" ,
545+ kcal_force_units = False ,
546+ )
547+ run_manager ._config_manager .load_config .return_value = {"run1" : {}}
548+ parser = run_manager ._config_manager .setup_argparse .return_value
549+ parser .parse_known_args .return_value = (args , [])
550+ run_manager ._config_manager .merge_configs .return_value = args
551+ run_manager ._config_manager .input_parameters_validation = MagicMock ()
552+
553+ mock_u , mock_u_force = MagicMock (), MagicMock ()
554+ MockUniverse .side_effect = [mock_u , mock_u_force ]
555+ mock_atoms , mock_atoms_force = MagicMock (), MagicMock ()
556+ mock_u .select_atoms .return_value = mock_atoms
557+ mock_u_force .select_atoms .return_value = mock_atoms_force
558+
559+ coords , forces = np .random .rand (2 , 3 , 3 ), np .random .rand (2 , 3 , 3 )
560+ MockAnalysis .side_effect = [
561+ MagicMock (
562+ run = MagicMock (return_value = MagicMock (results = {"timeseries" : coords }))
563+ ),
564+ MagicMock (
565+ run = MagicMock (return_value = MagicMock (results = {"timeseries" : forces }))
566+ ),
567+ ]
568+ mock_merge = MagicMock ()
569+ MockMerge .return_value = mock_merge
570+
571+ run_manager .run_entropy_workflow ()
572+
573+ MockUniverse .assert_any_call ("topol.tpr" , ["traj.xtc" ], format = "xtc" )
574+ MockUniverse .assert_any_call ("topol.tpr" , "forces.xtc" , format = "xtc" )
575+ MockMerge .assert_called_once_with (mock_atoms )
576+ mock_merge .load_new .assert_called_once_with (coords , forces = forces )
577+ self .assertEqual (mock_logger .debug .call_count , 3 )
578+
579+ @patch ("CodeEntropy.run.EntropyManager" )
580+ @patch ("CodeEntropy.run.GroupMolecules" )
581+ @patch ("CodeEntropy.run.LevelManager" )
582+ @patch ("CodeEntropy.run.AnalysisFromFunction" )
583+ @patch ("CodeEntropy.run.mda.Merge" )
584+ @patch ("CodeEntropy.run.mda.Universe" )
585+ def test_converts_kcal_to_kj (self , MockUniverse , MockMerge , MockAnalysis , * _mocks ):
586+ """
587+ Ensure that forces are scaled by 4.184 when kcal_force_units=True.
588+ """
589+ run_manager = RunManager ("mock/job001" )
590+ mock_logger = MagicMock ()
591+ run_manager ._logging_config = MagicMock (
592+ setup_logging = MagicMock (return_value = mock_logger )
593+ )
594+ run_manager ._config_manager = MagicMock ()
595+ run_manager ._data_logger = MagicMock ()
596+ run_manager .show_splash = MagicMock ()
597+
598+ args = MagicMock (
599+ top_traj_file = ["topol.tpr" , "traj.xtc" ],
600+ force_file = "forces.xtc" ,
601+ file_format = "xtc" ,
602+ selection_string = "all" ,
603+ verbose = False ,
604+ output_file = "output.json" ,
605+ kcal_force_units = True ,
606+ )
607+ run_manager ._config_manager .load_config .return_value = {"run1" : {}}
608+ parser = run_manager ._config_manager .setup_argparse .return_value
609+ parser .parse_known_args .return_value = (args , [])
610+ run_manager ._config_manager .merge_configs .return_value = args
611+ run_manager ._config_manager .input_parameters_validation = MagicMock ()
612+
613+ mock_u , mock_u_force = MagicMock (), MagicMock ()
614+ MockUniverse .side_effect = [mock_u , mock_u_force ]
615+ mock_atoms , mock_atoms_force = MagicMock (), MagicMock ()
616+ mock_u .select_atoms .return_value = mock_atoms
617+ mock_u_force .select_atoms .return_value = mock_atoms_force
618+
619+ coords = np .random .rand (2 , 3 , 3 )
620+ forces = np .random .rand (2 , 3 , 3 )
621+ forces_orig = forces .copy ()
622+ MockAnalysis .side_effect = [
623+ MagicMock (
624+ run = MagicMock (return_value = MagicMock (results = {"timeseries" : coords }))
625+ ),
626+ MagicMock (
627+ run = MagicMock (return_value = MagicMock (results = {"timeseries" : forces }))
628+ ),
629+ ]
630+ mock_merge = MagicMock ()
631+ MockMerge .return_value = mock_merge
632+
633+ run_manager .run_entropy_workflow ()
634+ _ , kwargs = mock_merge .load_new .call_args
635+ np .testing .assert_allclose (kwargs ["forces" ], forces_orig * 4.184 )
636+
637+ @patch ("CodeEntropy.run.EntropyManager" )
638+ @patch ("CodeEntropy.run.GroupMolecules" )
639+ @patch ("CodeEntropy.run.LevelManager" )
640+ @patch ("CodeEntropy.run.AnalysisFromFunction" )
641+ @patch ("CodeEntropy.run.mda.Merge" )
642+ @patch ("CodeEntropy.run.mda.Universe" )
643+ def test_logs_debug_messages (self , MockUniverse , MockMerge , MockAnalysis , * _mocks ):
644+ """
645+ Ensure that loading and merging steps produce debug logs.
646+ """
647+ run_manager = RunManager ("mock/job001" )
648+ mock_logger = MagicMock ()
649+ run_manager ._logging_config = MagicMock (
650+ setup_logging = MagicMock (return_value = mock_logger )
651+ )
652+ run_manager ._config_manager = MagicMock ()
653+ run_manager ._data_logger = MagicMock ()
654+ run_manager .show_splash = MagicMock ()
655+
656+ args = MagicMock (
657+ top_traj_file = ["topol.tpr" , "traj.xtc" ],
658+ force_file = "forces.xtc" ,
659+ file_format = "xtc" ,
660+ selection_string = "all" ,
661+ verbose = False ,
662+ output_file = "output.json" ,
663+ kcal_force_units = False ,
664+ )
665+ run_manager ._config_manager .load_config .return_value = {"run1" : {}}
666+ parser = run_manager ._config_manager .setup_argparse .return_value
667+ parser .parse_known_args .return_value = (args , [])
668+ run_manager ._config_manager .merge_configs .return_value = args
669+ run_manager ._config_manager .input_parameters_validation = MagicMock ()
670+
671+ mock_u , mock_u_force = MagicMock (), MagicMock ()
672+ MockUniverse .side_effect = [mock_u , mock_u_force ]
673+ mock_atoms , mock_atoms_force = MagicMock (), MagicMock ()
674+ mock_u .select_atoms .return_value = mock_atoms
675+ mock_u_force .select_atoms .return_value = mock_atoms_force
676+
677+ arr = np .random .rand (2 , 3 , 3 )
678+ MockAnalysis .side_effect = [
679+ MagicMock (
680+ run = MagicMock (return_value = MagicMock (results = {"timeseries" : arr }))
681+ ),
682+ MagicMock (
683+ run = MagicMock (return_value = MagicMock (results = {"timeseries" : arr }))
684+ ),
685+ ]
686+ MockMerge .return_value = MagicMock ()
687+
688+ run_manager .run_entropy_workflow ()
689+ debug_msgs = [c [0 ][0 ] for c in mock_logger .debug .call_args_list ]
690+ self .assertTrue (any ("forces.xtc" in m for m in debug_msgs ))
691+ self .assertTrue (any ("Merging forces" in m for m in debug_msgs ))
692+
516693 @patch ("CodeEntropy.run.AnalysisFromFunction" )
517694 @patch ("CodeEntropy.run.mda.Merge" )
518695 def test_new_U_select_frame (self , MockMerge , MockAnalysisFromFunction ):
0 commit comments