diff --git a/smartsim/_core/generation/generator.py b/smartsim/_core/generation/generator.py index 86c96f4e9..b80e4b448 100644 --- a/smartsim/_core/generation/generator.py +++ b/smartsim/_core/generation/generator.py @@ -28,8 +28,12 @@ import shutil import typing as t +from datetime import datetime from distutils import dir_util # pylint: disable=deprecated-module +from logging import INFO, DEBUG from os import mkdir, path, symlink +from os.path import join, relpath +from tabulate import tabulate from ...entity import Model, TaggedFilesHierarchy from ...log import get_logger @@ -49,7 +53,9 @@ class Generator: and writing into configuration files as well. """ - def __init__(self, gen_path: str, overwrite: bool = False) -> None: + def __init__( + self, gen_path: str, overwrite: bool = False, verbose: bool = True + ) -> None: """Initialize a generator object if overwrite is true, replace any existing @@ -59,12 +65,28 @@ def __init__(self, gen_path: str, overwrite: bool = False) -> None: is false, raises EntityExistsError when there is a name collision between entities. + :param gen_path: Path in which files need to be generated + :type gen_path: str :param overwrite: toggle entity replacement, defaults to False :type overwrite: bool, optional + :param verbose: Whether generation information should be logged to std out + :type verbose: bool, optional """ self._writer = ModelWriter() self.gen_path = gen_path self.overwrite = overwrite + self.log_level = DEBUG if not verbose else INFO + + @property + def log_file(self) -> str: + """Returns the location of the file + summarizing the parameters used for the last generation + of all generated entities. + + :returns: path to file with parameter settings + :rtype: str + """ + return join(self.gen_path, "smartsim_params.txt") def generate_experiment(self, *args: t.Any) -> None: """Run ensemble and experiment file structure generation @@ -129,7 +151,17 @@ def _gen_exp_dir(self) -> None: # keep exists ok for race conditions on NFS pathlib.Path(self.gen_path).mkdir(exist_ok=True) else: - logger.info("Working in previously created experiment") + logger.log( + level=self.log_level, msg="Working in previously created experiment" + ) + + # The log_file only keeps track of the last generation + # this is to avoid gigantic files in case the user repeats + # generation several times. The information is anyhow + # redundant, as it is also written in each entity's dir + with open(self.log_file, mode= 'w', encoding='utf-8') as log_file: + dt_string = datetime.now().strftime("%d/%m/%Y %H:%M:%S") + log_file.write(f"Generation start date and time: {dt_string}\n") def _gen_orc_dir(self, orchestrator: t.Optional[Orchestrator]) -> None: """Create the directory that will hold the error, output and @@ -249,10 +281,56 @@ def _build_tagged_files(tagged: TaggedFilesHierarchy) -> None: # write in changes to configurations if isinstance(entity, Model): - logger.debug( - f"Configuring model {entity.name} with params {entity.params}" + files_to_params = self._writer.configure_tagged_model_files( + to_write, entity.params + ) + self._log_params(entity, files_to_params) + + def _log_params( + self, entity: Model, files_to_params: t.Dict[str, t.Dict[str, str]] + ) -> None: + """Log which files were modified during generation + + and what values were set to the parameters + + :param entity: the model being generated + :type entity: Model + :param files_to_params: a dict connecting each file to its parameter settings + :type files_to_params: t.Dict[str, t.Dict[str, str]] + """ + used_params: t.Dict[str, str] = {} + file_to_tables: t.Dict[str, str] = {} + for file, params in files_to_params.items(): + used_params.update(params) + table = tabulate(params.items(), + headers=["Name", "Value"]) + file_to_tables[relpath(file, self.gen_path)] = table + + if used_params: + used_params_str = ", ".join( + [f"{name}={value}" for name, value in used_params.items()] + ) + logger.log( + level=self.log_level, + msg=f"Configured model {entity.name} with params {used_params_str}", + ) + file_table = tabulate( + file_to_tables.items(), + headers=["File name", "Parameters"], ) - self._writer.configure_tagged_model_files(to_write, entity.params) + log_entry = f"Model name: {entity.name}\n{file_table}\n\n" + with open(self.log_file, mode="a", encoding="utf-8") as logfile: + logfile.write(log_entry) + with open(join(entity.path, "smartsim_params.txt"), + mode="w", + encoding="utf-8") as local_logfile: + local_logfile.write(log_entry) + + else: + logger.log( + level=self.log_level, + msg=f"Configured model {entity.name} with no parameters", + ) @staticmethod def _copy_entity_files(entity: Model) -> None: diff --git a/smartsim/_core/generation/modelwriter.py b/smartsim/_core/generation/modelwriter.py index eb0e389aa..0cf071082 100644 --- a/smartsim/_core/generation/modelwriter.py +++ b/smartsim/_core/generation/modelwriter.py @@ -63,7 +63,7 @@ def configure_tagged_model_files( tagged_files: t.List[str], params: t.Dict[str, str], make_missing_tags_fatal: bool = False, - ) -> None: + ) -> t.Dict[str, t.Dict[str, str]]: """Read, write and configure tagged files attached to a Model instance. @@ -71,13 +71,19 @@ def configure_tagged_model_files( :type model: list[str] :param params: model parameters :type params: dict[str, str] - :param make_missing_tags_fatal: blow up if a tag is missing + :param make_missing_tags_fatal: raise an error if a tag is missing :type make_missing_tags_fatal: bool + :returns: A dict connecting each file to its parameter settings + :rtype: dict[str,dict[str,str]] """ + files_to_tags: t.Dict[str, t.Dict[str, str]] = {} for tagged_file in tagged_files: self._set_lines(tagged_file) - self._replace_tags(params, make_missing_tags_fatal) + used_tags = self._replace_tags(params, make_missing_tags_fatal) self._write_changes(tagged_file) + files_to_tags[tagged_file] = used_tags + + return files_to_tags def _set_lines(self, file_path: str) -> None: """Set the lines for the modelwrtter to iterate over @@ -104,8 +110,10 @@ def _write_changes(self, file_path: str) -> None: except (IOError, OSError) as e: raise ParameterWriterError(file_path, read=False) from e - def _replace_tags(self, params: t.Dict[str, str], make_fatal: bool = False) -> None: - """Replace the tagged within the tagged file attached to this + def _replace_tags( + self, params: t.Dict[str, str], make_fatal: bool = False + ) -> t.Dict[str, str]: + """Replace the tagged parameters within the file attached to this model. The tag defaults to ";" :param model: The model instance @@ -113,9 +121,12 @@ def _replace_tags(self, params: t.Dict[str, str], make_fatal: bool = False) -> N :param make_fatal: (Optional) Set to True to force a fatal error if a tag is not matched :type make_fatal: bool + :returns: A dict of parameter names and values set for the file + :rtype: dict[str,str] """ edited = [] unused_tags: t.Dict[str, t.List[int]] = {} + used_params: t.Dict[str, str] = {} for i, line in enumerate(self.lines): search = re.search(self.regex, line) if search: @@ -126,6 +137,7 @@ def _replace_tags(self, params: t.Dict[str, str], make_fatal: bool = False) -> N new_val = str(params[previous_value]) new_line = re.sub(self.regex, new_val, line, 1) search = re.search(self.regex, new_line) + used_params[previous_value] = new_val if not search: edited.append(new_line) else: @@ -143,13 +155,12 @@ def _replace_tags(self, params: t.Dict[str, str], make_fatal: bool = False) -> N else: edited.append(line) for tag, value in unused_tags.items(): - missing_tag_message = ( - f"Unused tag {tag} on line(s): {str(value)}" - ) + missing_tag_message = f"Unused tag {tag} on line(s): {str(value)}" if make_fatal: raise SmartSimError(missing_tag_message) logger.warning(missing_tag_message) self.lines = edited + return used_params def _is_ensemble_spec( self, tagged_line: str, model_params: t.Dict[str, str] diff --git a/smartsim/entity/model.py b/smartsim/entity/model.py index a86be546b..35690c526 100644 --- a/smartsim/entity/model.py +++ b/smartsim/entity/model.py @@ -30,18 +30,19 @@ import sys import typing as t import warnings +from os import path as osp from .._core.utils.helpers import cat_arg_and_value, init_default from ..error import EntityExistsError, SSUnsupportedError +from ..log import get_logger +from ..settings.base import BatchSettings, RunSettings from .dbobject import DBModel, DBScript from .entity import SmartSimEntity from .files import EntityFiles -from ..settings.base import BatchSettings, RunSettings -from ..log import get_logger - logger = get_logger(__name__) + class Model(SmartSimEntity): def __init__( self, @@ -163,6 +164,20 @@ def attach_generator_files( to_copy = init_default([], to_copy, (list, str)) to_symlink = init_default([], to_symlink, (list, str)) to_configure = init_default([], to_configure, (list, str)) + + # Check that no file collides with the parameter file written + # by Generator. We check the basename, even though it is more + # restrictive than what we need (but it avoids relative path issues) + for strategy in [to_copy, to_symlink, to_configure]: + if strategy is not None and any( + osp.basename(filename) == "smartsim_params.txt" + for filename in strategy + ): + raise ValueError( + "`smartsim_params.txt` is a file automatically " + + "generated by SmartSim and cannot be ovewritten." + ) + self.files = EntityFiles(to_configure, to_copy, to_symlink) @property @@ -177,8 +192,7 @@ def attached_files_table(self) -> str: return str(self.files) def print_attached_files(self) -> None: - """Print a table of the attached files on std out - """ + """Print a table of the attached files on std out""" print(self.attached_files_table) def colocate_db(self, *args: t.Any, **kwargs: t.Any) -> None: @@ -187,7 +201,8 @@ def colocate_db(self, *args: t.Any, **kwargs: t.Any) -> None: ( "`colocate_db` has been deprecated and will be removed in a \n" "future release. Please use `colocate_db_tcp` or `colocate_db_uds`." - ), FutureWarning + ), + FutureWarning, ) self.colocate_db_tcp(*args, **kwargs) @@ -333,8 +348,7 @@ def _set_colocated_db_settings( # TODO list which db settings can be extras common_options["custom_pinning"] = self._create_pinning_string( - common_options["custom_pinning"], - common_options["cpus"] + common_options["custom_pinning"], common_options["cpus"] ) colo_db_config = {} @@ -358,13 +372,13 @@ def _set_colocated_db_settings( @staticmethod def _create_pinning_string( - pin_ids: t.Optional[t.Iterable[t.Union[int, t.Iterable[int]]]], - cpus: int - ) -> t.Optional[str]: + pin_ids: t.Optional[t.Iterable[t.Union[int, t.Iterable[int]]]], cpus: int + ) -> t.Optional[str]: """Create a comma-separated string CPU ids. By default, None returns 0,1,...,cpus-1; an empty iterable will disable pinning altogether, and an iterable constructs a comma separate string (e.g. 0,2,5) """ + def _stringify_id(_id: int) -> str: """Return the cPU id as a string if an int, otherwise raise a ValueError""" if isinstance(_id, int): @@ -389,14 +403,14 @@ def _stringify_id(_id: int) -> str: warnings.warn( "CPU pinning is not supported on MacOSX. Ignoring pinning " "specification.", - RuntimeWarning + RuntimeWarning, ) return None raise TypeError(_invalid_input_message) # Flatten the iterable into a list and check to make sure that the resulting # elements are all ints if pin_ids is None: - return ','.join(_stringify_id(i) for i in range(cpus)) + return ",".join(_stringify_id(i) for i in range(cpus)) if not pin_ids: return None if isinstance(pin_ids, collections.abc.Iterable): @@ -406,7 +420,7 @@ def _stringify_id(_id: int) -> str: pin_list.extend([_stringify_id(j) for j in pin_id]) else: pin_list.append(_stringify_id(pin_id)) - return ','.join(sorted(set(pin_list))) + return ",".join(sorted(set(pin_list))) raise TypeError(_invalid_input_message) def params_to_args(self) -> None: @@ -433,7 +447,7 @@ def add_ml_model( backend: str, model: t.Optional[str] = None, model_path: t.Optional[str] = None, - device: t.Literal["CPU","GPU"] = "CPU", + device: t.Literal["CPU", "GPU"] = "CPU", devices_per_node: int = 1, batch_size: int = 0, min_batch_size: int = 0, @@ -495,7 +509,7 @@ def add_script( name: str, script: t.Optional[str] = None, script_path: t.Optional[str] = None, - device: t.Literal["CPU","GPU"] = "CPU", + device: t.Literal["CPU", "GPU"] = "CPU", devices_per_node: int = 1, ) -> None: """TorchScript to launch with this Model instance @@ -539,7 +553,7 @@ def add_function( self, name: str, function: t.Optional[str] = None, - device: t.Literal["CPU","GPU"] = "CPU", + device: t.Literal["CPU", "GPU"] = "CPU", devices_per_node: int = 1, ) -> None: """TorchScript function to launch with this Model instance diff --git a/smartsim/experiment.py b/smartsim/experiment.py index 639d0e7c9..6c6b1ba34 100644 --- a/smartsim/experiment.py +++ b/smartsim/experiment.py @@ -231,7 +231,11 @@ def stop(self, *args: t.Any) -> None: raise def generate( - self, *args: t.Any, tag: t.Optional[str] = None, overwrite: bool = False + self, + *args: t.Any, + tag: t.Optional[str] = None, + overwrite: bool = False, + verbose: bool = False, ) -> None: """Generate the file structure for an ``Experiment`` @@ -251,9 +255,11 @@ def generate( :param overwrite: overwrite existing folders and contents, defaults to False :type overwrite: bool, optional + :param verbose: log parameter settings to std out + :type verbose: bool """ try: - generator = Generator(self.exp_path, overwrite=overwrite) + generator = Generator(self.exp_path, overwrite=overwrite, verbose=verbose) if tag: generator.set_tag(tag) generator.generate_experiment(*args) diff --git a/tests/test_configs/h2o.inp b/tests/test_configs/generator_files/circular_config/h2o.inp similarity index 100% rename from tests/test_configs/h2o.inp rename to tests/test_configs/generator_files/circular_config/h2o.inp diff --git a/tests/test_configs/circular_config/sub_dir/circle b/tests/test_configs/generator_files/circular_config/sub_dir/circle similarity index 100% rename from tests/test_configs/circular_config/sub_dir/circle rename to tests/test_configs/generator_files/circular_config/sub_dir/circle diff --git a/tests/test_configs/circular_config/sub_dir/hello.sh b/tests/test_configs/generator_files/circular_config/sub_dir/hello.sh similarity index 100% rename from tests/test_configs/circular_config/sub_dir/hello.sh rename to tests/test_configs/generator_files/circular_config/sub_dir/hello.sh diff --git a/tests/test_configs/easy/correct/MOM_input b/tests/test_configs/generator_files/easy/correct/MOM_input similarity index 100% rename from tests/test_configs/easy/correct/MOM_input rename to tests/test_configs/generator_files/easy/correct/MOM_input diff --git a/tests/test_configs/easy/correct/example_input.i b/tests/test_configs/generator_files/easy/correct/example_input.i similarity index 100% rename from tests/test_configs/easy/correct/example_input.i rename to tests/test_configs/generator_files/easy/correct/example_input.i diff --git a/tests/test_configs/easy/correct/in.airebo b/tests/test_configs/generator_files/easy/correct/in.airebo similarity index 100% rename from tests/test_configs/easy/correct/in.airebo rename to tests/test_configs/generator_files/easy/correct/in.airebo diff --git a/tests/test_configs/easy/correct/in.atm b/tests/test_configs/generator_files/easy/correct/in.atm similarity index 100% rename from tests/test_configs/easy/correct/in.atm rename to tests/test_configs/generator_files/easy/correct/in.atm diff --git a/tests/test_configs/easy/correct/in.crack b/tests/test_configs/generator_files/easy/correct/in.crack similarity index 100% rename from tests/test_configs/easy/correct/in.crack rename to tests/test_configs/generator_files/easy/correct/in.crack diff --git a/tests/test_configs/easy/correct/in.ellipse.gayberne b/tests/test_configs/generator_files/easy/correct/in.ellipse.gayberne similarity index 100% rename from tests/test_configs/easy/correct/in.ellipse.gayberne rename to tests/test_configs/generator_files/easy/correct/in.ellipse.gayberne diff --git a/tests/test_configs/easy/correct/input-file.inp b/tests/test_configs/generator_files/easy/correct/input-file.inp similarity index 100% rename from tests/test_configs/easy/correct/input-file.inp rename to tests/test_configs/generator_files/easy/correct/input-file.inp diff --git a/tests/test_configs/easy/correct/input.nml b/tests/test_configs/generator_files/easy/correct/input.nml similarity index 100% rename from tests/test_configs/easy/correct/input.nml rename to tests/test_configs/generator_files/easy/correct/input.nml diff --git a/tests/test_configs/easy/correct/simple-H20.xml b/tests/test_configs/generator_files/easy/correct/simple-H20.xml similarity index 100% rename from tests/test_configs/easy/correct/simple-H20.xml rename to tests/test_configs/generator_files/easy/correct/simple-H20.xml diff --git a/tests/test_configs/easy/marked/MOM_input b/tests/test_configs/generator_files/easy/marked/MOM_input similarity index 100% rename from tests/test_configs/easy/marked/MOM_input rename to tests/test_configs/generator_files/easy/marked/MOM_input diff --git a/tests/test_configs/easy/marked/example_input.i b/tests/test_configs/generator_files/easy/marked/example_input.i similarity index 100% rename from tests/test_configs/easy/marked/example_input.i rename to tests/test_configs/generator_files/easy/marked/example_input.i diff --git a/tests/test_configs/easy/marked/in.airebo b/tests/test_configs/generator_files/easy/marked/in.airebo similarity index 100% rename from tests/test_configs/easy/marked/in.airebo rename to tests/test_configs/generator_files/easy/marked/in.airebo diff --git a/tests/test_configs/easy/marked/in.atm b/tests/test_configs/generator_files/easy/marked/in.atm similarity index 100% rename from tests/test_configs/easy/marked/in.atm rename to tests/test_configs/generator_files/easy/marked/in.atm diff --git a/tests/test_configs/easy/marked/in.crack b/tests/test_configs/generator_files/easy/marked/in.crack similarity index 100% rename from tests/test_configs/easy/marked/in.crack rename to tests/test_configs/generator_files/easy/marked/in.crack diff --git a/tests/test_configs/easy/marked/in.ellipse.gayberne b/tests/test_configs/generator_files/easy/marked/in.ellipse.gayberne similarity index 100% rename from tests/test_configs/easy/marked/in.ellipse.gayberne rename to tests/test_configs/generator_files/easy/marked/in.ellipse.gayberne diff --git a/tests/test_configs/easy/marked/input-file.inp b/tests/test_configs/generator_files/easy/marked/input-file.inp similarity index 100% rename from tests/test_configs/easy/marked/input-file.inp rename to tests/test_configs/generator_files/easy/marked/input-file.inp diff --git a/tests/test_configs/easy/marked/input.nml b/tests/test_configs/generator_files/easy/marked/input.nml similarity index 100% rename from tests/test_configs/easy/marked/input.nml rename to tests/test_configs/generator_files/easy/marked/input.nml diff --git a/tests/test_configs/easy/marked/simple-H20.xml b/tests/test_configs/generator_files/easy/marked/simple-H20.xml similarity index 100% rename from tests/test_configs/easy/marked/simple-H20.xml rename to tests/test_configs/generator_files/easy/marked/simple-H20.xml diff --git a/tests/test_configs/in.atm b/tests/test_configs/generator_files/in.atm similarity index 100% rename from tests/test_configs/in.atm rename to tests/test_configs/generator_files/in.atm diff --git a/tests/test_configs/generator_files/log_params/dir_test/dir_test_0/smartsim_params.txt b/tests/test_configs/generator_files/log_params/dir_test/dir_test_0/smartsim_params.txt new file mode 100644 index 000000000..373cec87e --- /dev/null +++ b/tests/test_configs/generator_files/log_params/dir_test/dir_test_0/smartsim_params.txt @@ -0,0 +1,8 @@ +Model name: dir_test_0 +File name Parameters +-------------------------- --------------- +dir_test/dir_test_0/in.atm Name Value + ------ ------- + THERMO 10 + STEPS 10 + diff --git a/tests/test_configs/generator_files/log_params/dir_test/dir_test_1/smartsim_params.txt b/tests/test_configs/generator_files/log_params/dir_test/dir_test_1/smartsim_params.txt new file mode 100644 index 000000000..e45ebb6bf --- /dev/null +++ b/tests/test_configs/generator_files/log_params/dir_test/dir_test_1/smartsim_params.txt @@ -0,0 +1,8 @@ +Model name: dir_test_1 +File name Parameters +-------------------------- --------------- +dir_test/dir_test_1/in.atm Name Value + ------ ------- + THERMO 10 + STEPS 20 + diff --git a/tests/test_configs/generator_files/log_params/dir_test/dir_test_2/smartsim_params.txt b/tests/test_configs/generator_files/log_params/dir_test/dir_test_2/smartsim_params.txt new file mode 100644 index 000000000..081dc56c6 --- /dev/null +++ b/tests/test_configs/generator_files/log_params/dir_test/dir_test_2/smartsim_params.txt @@ -0,0 +1,8 @@ +Model name: dir_test_2 +File name Parameters +-------------------------- --------------- +dir_test/dir_test_2/in.atm Name Value + ------ ------- + THERMO 20 + STEPS 10 + diff --git a/tests/test_configs/generator_files/log_params/dir_test/dir_test_3/smartsim_params.txt b/tests/test_configs/generator_files/log_params/dir_test/dir_test_3/smartsim_params.txt new file mode 100644 index 000000000..3403f7c71 --- /dev/null +++ b/tests/test_configs/generator_files/log_params/dir_test/dir_test_3/smartsim_params.txt @@ -0,0 +1,8 @@ +Model name: dir_test_3 +File name Parameters +-------------------------- --------------- +dir_test/dir_test_3/in.atm Name Value + ------ ------- + THERMO 20 + STEPS 20 + diff --git a/tests/test_configs/generator_files/log_params/smartsim_params.txt b/tests/test_configs/generator_files/log_params/smartsim_params.txt new file mode 100644 index 000000000..6ac92049f --- /dev/null +++ b/tests/test_configs/generator_files/log_params/smartsim_params.txt @@ -0,0 +1,32 @@ +Generation start date and time: 08/09/2023 18:22:44 +Model name: dir_test_0 +File name Parameters +-------------------------- --------------- +dir_test/dir_test_0/in.atm Name Value + ------ ------- + THERMO 10 + STEPS 10 + +Model name: dir_test_1 +File name Parameters +-------------------------- --------------- +dir_test/dir_test_1/in.atm Name Value + ------ ------- + THERMO 10 + STEPS 20 + +Model name: dir_test_2 +File name Parameters +-------------------------- --------------- +dir_test/dir_test_2/in.atm Name Value + ------ ------- + THERMO 20 + STEPS 10 + +Model name: dir_test_3 +File name Parameters +-------------------------- --------------- +dir_test/dir_test_3/in.atm Name Value + ------ ------- + THERMO 20 + STEPS 20 diff --git a/tests/test_configs/med/correct/MOM_input b/tests/test_configs/generator_files/med/correct/MOM_input similarity index 100% rename from tests/test_configs/med/correct/MOM_input rename to tests/test_configs/generator_files/med/correct/MOM_input diff --git a/tests/test_configs/med/correct/diag_table b/tests/test_configs/generator_files/med/correct/diag_table similarity index 100% rename from tests/test_configs/med/correct/diag_table rename to tests/test_configs/generator_files/med/correct/diag_table diff --git a/tests/test_configs/med/correct/example_input.i b/tests/test_configs/generator_files/med/correct/example_input.i similarity index 100% rename from tests/test_configs/med/correct/example_input.i rename to tests/test_configs/generator_files/med/correct/example_input.i diff --git a/tests/test_configs/med/correct/in.airebo b/tests/test_configs/generator_files/med/correct/in.airebo similarity index 100% rename from tests/test_configs/med/correct/in.airebo rename to tests/test_configs/generator_files/med/correct/in.airebo diff --git a/tests/test_configs/med/correct/in.atm b/tests/test_configs/generator_files/med/correct/in.atm similarity index 100% rename from tests/test_configs/med/correct/in.atm rename to tests/test_configs/generator_files/med/correct/in.atm diff --git a/tests/test_configs/med/correct/in.crack b/tests/test_configs/generator_files/med/correct/in.crack similarity index 100% rename from tests/test_configs/med/correct/in.crack rename to tests/test_configs/generator_files/med/correct/in.crack diff --git a/tests/test_configs/med/correct/in.ellipse.gayberne b/tests/test_configs/generator_files/med/correct/in.ellipse.gayberne similarity index 100% rename from tests/test_configs/med/correct/in.ellipse.gayberne rename to tests/test_configs/generator_files/med/correct/in.ellipse.gayberne diff --git a/tests/test_configs/med/correct/input-file.inp b/tests/test_configs/generator_files/med/correct/input-file.inp similarity index 100% rename from tests/test_configs/med/correct/input-file.inp rename to tests/test_configs/generator_files/med/correct/input-file.inp diff --git a/tests/test_configs/med/correct/input.nml b/tests/test_configs/generator_files/med/correct/input.nml similarity index 100% rename from tests/test_configs/med/correct/input.nml rename to tests/test_configs/generator_files/med/correct/input.nml diff --git a/tests/test_configs/med/correct/simple-H20.xml b/tests/test_configs/generator_files/med/correct/simple-H20.xml similarity index 100% rename from tests/test_configs/med/correct/simple-H20.xml rename to tests/test_configs/generator_files/med/correct/simple-H20.xml diff --git a/tests/test_configs/med/marked/MOM_input b/tests/test_configs/generator_files/med/marked/MOM_input similarity index 100% rename from tests/test_configs/med/marked/MOM_input rename to tests/test_configs/generator_files/med/marked/MOM_input diff --git a/tests/test_configs/med/marked/diag_table b/tests/test_configs/generator_files/med/marked/diag_table similarity index 100% rename from tests/test_configs/med/marked/diag_table rename to tests/test_configs/generator_files/med/marked/diag_table diff --git a/tests/test_configs/med/marked/example_input.i b/tests/test_configs/generator_files/med/marked/example_input.i similarity index 100% rename from tests/test_configs/med/marked/example_input.i rename to tests/test_configs/generator_files/med/marked/example_input.i diff --git a/tests/test_configs/med/marked/in.airebo b/tests/test_configs/generator_files/med/marked/in.airebo similarity index 100% rename from tests/test_configs/med/marked/in.airebo rename to tests/test_configs/generator_files/med/marked/in.airebo diff --git a/tests/test_configs/med/marked/in.atm b/tests/test_configs/generator_files/med/marked/in.atm similarity index 100% rename from tests/test_configs/med/marked/in.atm rename to tests/test_configs/generator_files/med/marked/in.atm diff --git a/tests/test_configs/med/marked/in.crack b/tests/test_configs/generator_files/med/marked/in.crack similarity index 100% rename from tests/test_configs/med/marked/in.crack rename to tests/test_configs/generator_files/med/marked/in.crack diff --git a/tests/test_configs/med/marked/in.ellipse.gayberne b/tests/test_configs/generator_files/med/marked/in.ellipse.gayberne similarity index 100% rename from tests/test_configs/med/marked/in.ellipse.gayberne rename to tests/test_configs/generator_files/med/marked/in.ellipse.gayberne diff --git a/tests/test_configs/med/marked/input-file.inp b/tests/test_configs/generator_files/med/marked/input-file.inp similarity index 100% rename from tests/test_configs/med/marked/input-file.inp rename to tests/test_configs/generator_files/med/marked/input-file.inp diff --git a/tests/test_configs/med/marked/input.nml b/tests/test_configs/generator_files/med/marked/input.nml similarity index 100% rename from tests/test_configs/med/marked/input.nml rename to tests/test_configs/generator_files/med/marked/input.nml diff --git a/tests/test_configs/med/marked/simple-H20.xml b/tests/test_configs/generator_files/med/marked/simple-H20.xml similarity index 100% rename from tests/test_configs/med/marked/simple-H20.xml rename to tests/test_configs/generator_files/med/marked/simple-H20.xml diff --git a/tests/test_configs/multi_tags_template.sh b/tests/test_configs/generator_files/multi_tags_template.sh similarity index 100% rename from tests/test_configs/multi_tags_template.sh rename to tests/test_configs/generator_files/multi_tags_template.sh diff --git a/tests/test_configs/new-tag/correct/MOM_input b/tests/test_configs/generator_files/new-tag/correct/MOM_input similarity index 100% rename from tests/test_configs/new-tag/correct/MOM_input rename to tests/test_configs/generator_files/new-tag/correct/MOM_input diff --git a/tests/test_configs/new-tag/correct/diag_table b/tests/test_configs/generator_files/new-tag/correct/diag_table similarity index 100% rename from tests/test_configs/new-tag/correct/diag_table rename to tests/test_configs/generator_files/new-tag/correct/diag_table diff --git a/tests/test_configs/new-tag/correct/example_input.i b/tests/test_configs/generator_files/new-tag/correct/example_input.i similarity index 100% rename from tests/test_configs/new-tag/correct/example_input.i rename to tests/test_configs/generator_files/new-tag/correct/example_input.i diff --git a/tests/test_configs/new-tag/correct/in.airebo b/tests/test_configs/generator_files/new-tag/correct/in.airebo similarity index 100% rename from tests/test_configs/new-tag/correct/in.airebo rename to tests/test_configs/generator_files/new-tag/correct/in.airebo diff --git a/tests/test_configs/new-tag/correct/in.atm b/tests/test_configs/generator_files/new-tag/correct/in.atm similarity index 100% rename from tests/test_configs/new-tag/correct/in.atm rename to tests/test_configs/generator_files/new-tag/correct/in.atm diff --git a/tests/test_configs/new-tag/correct/in.crack b/tests/test_configs/generator_files/new-tag/correct/in.crack similarity index 100% rename from tests/test_configs/new-tag/correct/in.crack rename to tests/test_configs/generator_files/new-tag/correct/in.crack diff --git a/tests/test_configs/new-tag/correct/in.ellipse.gayberne b/tests/test_configs/generator_files/new-tag/correct/in.ellipse.gayberne similarity index 100% rename from tests/test_configs/new-tag/correct/in.ellipse.gayberne rename to tests/test_configs/generator_files/new-tag/correct/in.ellipse.gayberne diff --git a/tests/test_configs/new-tag/correct/input-file.inp b/tests/test_configs/generator_files/new-tag/correct/input-file.inp similarity index 100% rename from tests/test_configs/new-tag/correct/input-file.inp rename to tests/test_configs/generator_files/new-tag/correct/input-file.inp diff --git a/tests/test_configs/new-tag/correct/input.nml b/tests/test_configs/generator_files/new-tag/correct/input.nml similarity index 100% rename from tests/test_configs/new-tag/correct/input.nml rename to tests/test_configs/generator_files/new-tag/correct/input.nml diff --git a/tests/test_configs/new-tag/correct/simple-H20.xml b/tests/test_configs/generator_files/new-tag/correct/simple-H20.xml similarity index 100% rename from tests/test_configs/new-tag/correct/simple-H20.xml rename to tests/test_configs/generator_files/new-tag/correct/simple-H20.xml diff --git a/tests/test_configs/new-tag/marked/MOM_input b/tests/test_configs/generator_files/new-tag/marked/MOM_input similarity index 100% rename from tests/test_configs/new-tag/marked/MOM_input rename to tests/test_configs/generator_files/new-tag/marked/MOM_input diff --git a/tests/test_configs/new-tag/marked/diag_table b/tests/test_configs/generator_files/new-tag/marked/diag_table similarity index 100% rename from tests/test_configs/new-tag/marked/diag_table rename to tests/test_configs/generator_files/new-tag/marked/diag_table diff --git a/tests/test_configs/new-tag/marked/example_input.i b/tests/test_configs/generator_files/new-tag/marked/example_input.i similarity index 100% rename from tests/test_configs/new-tag/marked/example_input.i rename to tests/test_configs/generator_files/new-tag/marked/example_input.i diff --git a/tests/test_configs/new-tag/marked/in.airebo b/tests/test_configs/generator_files/new-tag/marked/in.airebo similarity index 100% rename from tests/test_configs/new-tag/marked/in.airebo rename to tests/test_configs/generator_files/new-tag/marked/in.airebo diff --git a/tests/test_configs/new-tag/marked/in.atm b/tests/test_configs/generator_files/new-tag/marked/in.atm similarity index 100% rename from tests/test_configs/new-tag/marked/in.atm rename to tests/test_configs/generator_files/new-tag/marked/in.atm diff --git a/tests/test_configs/new-tag/marked/in.crack b/tests/test_configs/generator_files/new-tag/marked/in.crack similarity index 100% rename from tests/test_configs/new-tag/marked/in.crack rename to tests/test_configs/generator_files/new-tag/marked/in.crack diff --git a/tests/test_configs/new-tag/marked/in.ellipse.gayberne b/tests/test_configs/generator_files/new-tag/marked/in.ellipse.gayberne similarity index 100% rename from tests/test_configs/new-tag/marked/in.ellipse.gayberne rename to tests/test_configs/generator_files/new-tag/marked/in.ellipse.gayberne diff --git a/tests/test_configs/new-tag/marked/input-file.inp b/tests/test_configs/generator_files/new-tag/marked/input-file.inp similarity index 100% rename from tests/test_configs/new-tag/marked/input-file.inp rename to tests/test_configs/generator_files/new-tag/marked/input-file.inp diff --git a/tests/test_configs/new-tag/marked/input.nml b/tests/test_configs/generator_files/new-tag/marked/input.nml similarity index 100% rename from tests/test_configs/new-tag/marked/input.nml rename to tests/test_configs/generator_files/new-tag/marked/input.nml diff --git a/tests/test_configs/new-tag/marked/simple-H20.xml b/tests/test_configs/generator_files/new-tag/marked/simple-H20.xml similarity index 100% rename from tests/test_configs/new-tag/marked/simple-H20.xml rename to tests/test_configs/generator_files/new-tag/marked/simple-H20.xml diff --git a/tests/test_configs/tag_dir_template/nested_0/tagged_0.sh b/tests/test_configs/generator_files/tag_dir_template/nested_0/tagged_0.sh similarity index 100% rename from tests/test_configs/tag_dir_template/nested_0/tagged_0.sh rename to tests/test_configs/generator_files/tag_dir_template/nested_0/tagged_0.sh diff --git a/tests/test_configs/tag_dir_template/nested_1/tagged_1.sh b/tests/test_configs/generator_files/tag_dir_template/nested_1/tagged_1.sh similarity index 100% rename from tests/test_configs/tag_dir_template/nested_1/tagged_1.sh rename to tests/test_configs/generator_files/tag_dir_template/nested_1/tagged_1.sh diff --git a/tests/test_configs/test_dir/test.py b/tests/test_configs/generator_files/test_dir/test.py similarity index 97% rename from tests/test_configs/test_dir/test.py rename to tests/test_configs/generator_files/test_dir/test.py index bf6fd954c..8a0a76ee2 100644 --- a/tests/test_configs/test_dir/test.py +++ b/tests/test_configs/generator_files/test_dir/test.py @@ -23,3 +23,6 @@ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +thermo = @THERMO@ +steps = @STEPS@ \ No newline at end of file diff --git a/tests/test_configs/test_dir/test_dir_1/config.txt b/tests/test_configs/generator_files/test_dir/test_dir_1/config.txt similarity index 100% rename from tests/test_configs/test_dir/test_dir_1/config.txt rename to tests/test_configs/generator_files/test_dir/test_dir_1/config.txt diff --git a/tests/test_configs/to_copy_dir/mock.txt b/tests/test_configs/generator_files/to_copy_dir/mock.txt similarity index 100% rename from tests/test_configs/to_copy_dir/mock.txt rename to tests/test_configs/generator_files/to_copy_dir/mock.txt diff --git a/tests/test_configs/to_symlink_dir/mock2.txt b/tests/test_configs/generator_files/to_symlink_dir/mock2.txt similarity index 100% rename from tests/test_configs/to_symlink_dir/mock2.txt rename to tests/test_configs/generator_files/to_symlink_dir/mock2.txt diff --git a/tests/test_generator.py b/tests/test_generator.py index 4307b2c2c..938d15599 100644 --- a/tests/test_generator.py +++ b/tests/test_generator.py @@ -24,6 +24,7 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import filecmp from os import path as osp import pytest @@ -34,8 +35,6 @@ from smartsim.settings import RunSettings from tabulate import tabulate -from smartsim.settings import SbatchSettings - rs = RunSettings("python", exe_args="sleep.py") @@ -50,15 +49,18 @@ """ +def get_gen_file(fileutils, filename): + return fileutils.get_test_conf_path(osp.join("generator_files", filename)) + + def test_ensemble(fileutils): exp = Experiment("gen-test", launcher="local") test_dir = fileutils.get_test_dir() gen = Generator(test_dir) - params = {"THERMO": [10, 20, 30], "STEPS": [10, 20, 30]} ensemble = exp.create_ensemble("test", params=params, run_settings=rs) - config = fileutils.get_test_conf_path("in.atm") + config = get_gen_file(fileutils, "in.atm") ensemble.attach_generator_files(to_configure=config) gen.generate_experiment(ensemble) @@ -76,12 +78,12 @@ def test_ensemble_overwrite(fileutils): params = {"THERMO": [10, 20, 30], "STEPS": [10, 20, 30]} ensemble = exp.create_ensemble("test", params=params, run_settings=rs) - config = fileutils.get_test_conf_path("in.atm") + config = get_gen_file(fileutils, "in.atm") ensemble.attach_generator_files(to_configure=[config]) gen.generate_experiment(ensemble) # re generate without overwrite - config = fileutils.get_test_conf_path("in.atm") + config = get_gen_file(fileutils, "in.atm") ensemble.attach_generator_files(to_configure=[config]) gen.generate_experiment(ensemble) @@ -99,12 +101,12 @@ def test_ensemble_overwrite_error(fileutils): params = {"THERMO": [10, 20, 30], "STEPS": [10, 20, 30]} ensemble = exp.create_ensemble("test", params=params, run_settings=rs) - config = fileutils.get_test_conf_path("in.atm") + config = get_gen_file(fileutils, "in.atm") ensemble.attach_generator_files(to_configure=[config]) gen.generate_experiment(ensemble) # re generate without overwrite - config = fileutils.get_test_conf_path("in.atm") + config = get_gen_file(fileutils, "in.atm") ensemble.attach_generator_files(to_configure=[config]) with pytest.raises(FileExistsError): gen.generate_experiment(ensemble) @@ -122,7 +124,7 @@ def test_full_exp(fileutils, wlmutils): params = {"THERMO": [10, 20, 30], "STEPS": [10, 20, 30]} ensemble = exp.create_ensemble("test_ens", params=params, run_settings=rs) - config = fileutils.get_test_conf_path("in.atm") + config = get_gen_file(fileutils, "in.atm") ensemble.attach_generator_files(to_configure=config) exp.generate(orc, ensemble, model) @@ -149,10 +151,10 @@ def test_dir_files(fileutils): params = {"THERMO": [10, 20, 30], "STEPS": [10, 20, 30]} ensemble = exp.create_ensemble("dir_test", params=params, run_settings=rs) - conf_dir = fileutils.get_test_dir_path("test_dir") + conf_dir = get_gen_file(fileutils, "test_dir") ensemble.attach_generator_files(to_configure=conf_dir) - exp.generate(ensemble) + exp.generate(ensemble, tag="@") assert osp.isdir(osp.join(test_dir, "dir_test/")) for i in range(9): @@ -177,9 +179,9 @@ def test_print_files(fileutils, capsys): params = {"THERMO": [10, 20], "STEPS": [20, 30]} ensemble = exp.create_ensemble("dir_test", params=params, run_settings=rs) - gen_dir = fileutils.get_test_dir_path("test_dir") - symlink_dir = fileutils.get_test_dir_path("to_symlink_dir") - copy_dir = fileutils.get_test_dir_path("to_copy_dir") + gen_dir = get_gen_file(fileutils, "test_dir") + symlink_dir = get_gen_file(fileutils, "to_symlink_dir") + copy_dir = get_gen_file(fileutils, "to_copy_dir") ensemble.print_attached_files() captured = capsys.readouterr() @@ -253,7 +255,7 @@ def test_multiple_tags(fileutils): parameterized_model = exp.create_model( "multi-tags", run_settings=model_settings, params=model_params ) - config = fileutils.get_test_conf_path("multi_tags_template.sh") + config = get_gen_file(fileutils, "multi_tags_template.sh") parameterized_model.attach_generator_files(to_configure=[config]) exp.generate(parameterized_model, overwrite=True) exp.start(parameterized_model, block=True) @@ -265,6 +267,41 @@ def test_multiple_tags(fileutils): ) +def test_generation_log(fileutils): + """Test that an error is issued when a tag is unused and make_fatal is True""" + + test_dir = fileutils.make_test_dir() + exp = Experiment("gen-log-test", test_dir, launcher="local") + + params = {"THERMO": [10, 20], "STEPS": [10, 20]} + ensemble = exp.create_ensemble("dir_test", params=params, run_settings=rs) + conf_file = get_gen_file(fileutils, "in.atm") + ensemble.attach_generator_files(to_configure=conf_file) + + def not_header(line): + """you can add other general checks in here""" + return not line.startswith("Generation start date and time:") + + exp.generate(ensemble, verbose=True) + + log_file = osp.join(test_dir, "smartsim_params.txt") + ground_truth = get_gen_file(fileutils, osp.join("log_params", "smartsim_params.txt")) + + with open(log_file) as f1, open(ground_truth) as f2: + assert(not not_header(f1.readline())) + f1 = filter(not_header, f1) + f2 = filter(not_header, f2) + assert all(x == y for x, y in zip(f1, f2)) + + for entity in ensemble: + assert filecmp.cmp( + osp.join(entity.path, "smartsim_params.txt"), + get_gen_file( + fileutils, + osp.join("log_params", "dir_test", entity.name, "smartsim_params.txt"), + ), + ) + def test_config_dir(fileutils): """Test the generation and configuration of models with tagged files that are directories with subdirectories and files @@ -276,7 +313,7 @@ def test_config_dir(fileutils): params = {"PARAM0": [0, 1], "PARAM1": [2, 3]} ensemble = exp.create_ensemble("test", params=params, run_settings=rs) - config = fileutils.get_test_conf_path("tag_dir_template") + config = get_gen_file(fileutils, "tag_dir_template") ensemble.attach_generator_files(to_configure=config) gen.generate_experiment(ensemble) @@ -308,7 +345,7 @@ def test_no_gen_if_file_not_exist(fileutils): """ exp = Experiment("file-not-found", launcher="local") ensemble = exp.create_ensemble("test", params={"P": [0, 1]}, run_settings=rs) - config = fileutils.get_test_conf_path("path_not_exist") + config = get_gen_file(fileutils, "path_not_exist") with pytest.raises(FileNotFoundError): ensemble.attach_generator_files(to_configure=config) @@ -320,6 +357,17 @@ def test_no_gen_if_symlink_to_dir(fileutils): """ exp = Experiment("circular-config-files", launcher="local") ensemble = exp.create_ensemble("test", params={"P": [0, 1]}, run_settings=rs) - config = fileutils.get_test_conf_path("circular_config") + config = get_gen_file(fileutils, "circular_config") with pytest.raises(ValueError): ensemble.attach_generator_files(to_configure=config) + + +def test_no_file_overwrite(): + exp = Experiment("test_no_file_overwrite", launcher="local") + ensemble = exp.create_ensemble("test", params={"P": [0, 1]}, run_settings=rs) + with pytest.raises(ValueError): + ensemble.attach_generator_files(to_configure=["/normal/file.txt", "/path/to/smartsim_params.txt"]) + with pytest.raises(ValueError): + ensemble.attach_generator_files(to_symlink=["/normal/file.txt", "/path/to/smartsim_params.txt"]) + with pytest.raises(ValueError): + ensemble.attach_generator_files(to_copy=["/normal/file.txt", "/path/to/smartsim_params.txt"]) \ No newline at end of file diff --git a/tests/test_modelwriter.py b/tests/test_modelwriter.py index ba5393c4f..11d887e2d 100644 --- a/tests/test_modelwriter.py +++ b/tests/test_modelwriter.py @@ -32,14 +32,15 @@ import pytest from smartsim._core.generation.modelwriter import ModelWriter -from smartsim.error.errors import ParameterWriterError +from smartsim.error.errors import ParameterWriterError, SmartSimError from smartsim.settings import RunSettings mw_run_settings = RunSettings("python", exe_args="sleep.py") +def get_gen_file(fileutils, filename): + return fileutils.get_test_conf_path(path.join("generator_files", filename)) def test_write_easy_configs(fileutils): - test_dir = fileutils.make_test_dir() param_dict = { @@ -51,8 +52,8 @@ def test_write_easy_configs(fileutils): "1200": "120", # input.nml } - conf_path = fileutils.get_test_dir_path("easy/marked/") - correct_path = fileutils.get_test_dir_path("easy/correct/") + conf_path = get_gen_file(fileutils, "easy/marked/") + correct_path = get_gen_file(fileutils, "easy/correct/") # copy confs to gen directory dir_util.copy_tree(conf_path, test_dir) assert path.isdir(test_dir) @@ -69,7 +70,6 @@ def test_write_easy_configs(fileutils): def test_write_med_configs(fileutils): - test_dir = fileutils.make_test_dir() param_dict = { @@ -81,8 +81,8 @@ def test_write_med_configs(fileutils): "3*12.0": "3*14.0", # MOM_input } - conf_path = fileutils.get_test_dir_path("med/marked/") - correct_path = fileutils.get_test_dir_path("med/correct/") + conf_path = get_gen_file(fileutils, "med/marked/") + correct_path = get_gen_file(fileutils, "med/correct/") # copy confs to gen directory dir_util.copy_tree(conf_path, test_dir) @@ -115,8 +115,8 @@ def test_write_new_tag_configs(fileutils): "3*12.0": "3*14.0", # MOM_input } - conf_path = fileutils.get_test_dir_path("new-tag/marked/") - correct_path = fileutils.get_test_dir_path("new-tag/correct/") + conf_path = get_gen_file(fileutils, "new-tag/marked/") + correct_path = get_gen_file(fileutils, "new-tag/correct/") # copy confs to gen directory dir_util.copy_tree(conf_path, test_dir) @@ -144,3 +144,24 @@ def test_mw_error_2(): writer = ModelWriter() with pytest.raises(ParameterWriterError): writer._write_changes("[not/a/path]") + + +def test_write_mw_error_3(fileutils): + test_dir = fileutils.make_test_dir() + + param_dict = { + "5": 10, # MOM_input + } + + conf_path = get_gen_file(fileutils, "easy/marked/") + + # copy confs to gen directory + dir_util.copy_tree(conf_path, test_dir) + assert path.isdir(test_dir) + + # init modelwriter + writer = ModelWriter() + with pytest.raises(SmartSimError): + writer.configure_tagged_model_files( + glob(test_dir + "/*"), param_dict, make_missing_tags_fatal=True + )