diff --git a/mcstasscript/configuration.yaml b/mcstasscript/configuration.yaml index 008534d1..d6f9f8c3 100644 --- a/mcstasscript/configuration.yaml +++ b/mcstasscript/configuration.yaml @@ -1,7 +1,7 @@ other: characters_per_line: 85 paths: - mcrun_path: /Applications/McStas-2.7.1.app/Contents/Resources/mcstas/2.7.1/bin/ - mcstas_path: /Applications/McStas-2.7.1.app/Contents/Resources/mcstas/2.7.1/ + mcrun_path: /usr/mcstas/3.3//bin/ + mcstas_path: /usr/mcstas/3.3/ mcxtrace_path: /Applications/McXtrace-1.5.app/Contents/Resources/mcxtrace/1.5/ mxrun_path: /Applications/McXtrace-1.5.app/Contents/Resources/mcxtrace/1.5/bin/ diff --git a/mcstasscript/helper/component_reader.py b/mcstasscript/helper/component_reader.py index fbe64d62..6b81ad01 100644 --- a/mcstasscript/helper/component_reader.py +++ b/mcstasscript/helper/component_reader.py @@ -152,6 +152,55 @@ def __init__(self, mcstas_path, input_path="."): print("These definitions will be used instead of the installed " + "versions.") + def add_custom_component_dir(self, input_path=".", category="custom"): + """ + Method to add further directories containing custom components + """ + # Will overwrite McStas components with definitions in input_folder + current_directory = os.getcwd() + + # Set up absolute input_path + if os.path.isabs(input_path): + input_directory = input_path + else: + if input_path == ".": + # Default case, avoid having /./ in absolute path + input_directory = current_directory + else: + input_directory = os.path.join(current_directory, input_path) + + if not os.path.isdir(input_directory): + print("input_path: ", input_directory) + raise ValueError("Can't find given input_path," + " directory must exist.") + """ + If components are present both in the McStas install and the + work directory, the version in the work directory is used. The user + is informed of this behavior when the instrument object is created. + """ + overwritten_components = [] + for file in os.listdir(input_directory): + if file.endswith(".comp"): + abs_path = os.path.join(input_directory, file) + component_name = os.path.split(abs_path)[1].split(".")[-2] + + if component_name in self.component_path: + overwritten_components.append(file) + + self.component_path[component_name] = abs_path + self.component_category[component_name] = category + + # Report components found in the work directory and install to the user + if len(overwritten_components) > 0: + print( + "The following components are found in the work_directory" + + " / input_path:" + ) + for name in overwritten_components: + print(" ", name) + + print( + "These definitions will be used instead of the installed " + "versions." + ) def show_categories(self): """ diff --git a/mcstasscript/helper/managed_mcrun.py b/mcstasscript/helper/managed_mcrun.py index 112f87fe..0d5000dd 100644 --- a/mcstasscript/helper/managed_mcrun.py +++ b/mcstasscript/helper/managed_mcrun.py @@ -83,17 +83,19 @@ def __init__(self, instr_name, **kwargs): If True, automatically appends output_path to make it unique force_compile : bool, default True If True, forces compile. If False no new instrument is written - run_folder : str + run_path : str Path to folder in which to run McStas openacc : bool, default False If True, adds the --openacc flag to mcrun call NeXus : bool, default False If True, adds the --format=NeXus to mcrun call - + component_dirs: list + Sets non standard paths to search for component definitions + """ self.name_of_instrumentfile = instr_name - + self.component_dirs=[] self.data_folder_name = "" self.ncount = int(1E6) self.mpi = None @@ -110,6 +112,9 @@ def __init__(self, instr_name, **kwargs): self.seed = None self.suppress_output = False + if "component_dirs" in kwargs: + self.component_dirs = kwargs["component_dirs"] + # executable_path always in kwargs if "executable_path" in kwargs: self.executable_path = kwargs["executable_path"] @@ -279,10 +284,15 @@ def run_simulation(self): self.executable) mcrun_full_path = '"' + mcrun_full_path + '"' # Path in quotes to allow spaces + add_component_dirs="" + for d in self.component_dirs: + add_component_dirs="-I "+d + # Run the mcrun command on the system full_command = (mcrun_full_path + " " + option_string + " " + self.custom_flags + " " + + add_component_dirs + " " + self.name_of_instrumentfile + parameter_string) @@ -842,4 +852,4 @@ def findall(s, p): i = s.lower().find(p) while i != -1: yield i - i = s.lower().find(p, i+1) \ No newline at end of file + i = s.lower().find(p, i+1) diff --git a/mcstasscript/instrument_diagnostics/beam_diagnostics.py b/mcstasscript/instrument_diagnostics/beam_diagnostics.py index 52cebc21..4f42ae6f 100644 --- a/mcstasscript/instrument_diagnostics/beam_diagnostics.py +++ b/mcstasscript/instrument_diagnostics/beam_diagnostics.py @@ -413,5 +413,5 @@ def plot(self): print("No data to plot! Use the run method to generate data.") overview = PlotOverview(self.event_plotters, self.views) - overview.plot_all() + return overview.plot_all() diff --git a/mcstasscript/instrument_diagnostics/plot_overview.py b/mcstasscript/instrument_diagnostics/plot_overview.py index 308e394a..2ba4bdb8 100644 --- a/mcstasscript/instrument_diagnostics/plot_overview.py +++ b/mcstasscript/instrument_diagnostics/plot_overview.py @@ -62,6 +62,7 @@ def plot_all(self, figsize=None, same_scale=True): major_label_set = True fig.tight_layout() + return fig def set_same_scale(self): """ @@ -90,4 +91,4 @@ def set_same_scale(self): view.set_axis1_limits(np.nanmin(axis1_mins), np.nanmax(axis1_maxs)) if view.axis2 is not None: - view.set_axis2_limits(np.nanmin(axis2_mins), np.nanmax(axis2_maxs)) \ No newline at end of file + view.set_axis2_limits(np.nanmin(axis2_mins), np.nanmax(axis2_maxs)) diff --git a/mcstasscript/instrument_diagram/canvas.py b/mcstasscript/instrument_diagram/canvas.py index a2d5a391..9749b1e3 100644 --- a/mcstasscript/instrument_diagram/canvas.py +++ b/mcstasscript/instrument_diagram/canvas.py @@ -455,4 +455,6 @@ def hover(event): fig.canvas.mpl_connect("motion_notify_event", hover) # Show the figure - plt.show() \ No newline at end of file + plt.show() + + return fig diff --git a/mcstasscript/instrument_diagram/make_diagram.py b/mcstasscript/instrument_diagram/make_diagram.py index 4925bb3a..457835f8 100644 --- a/mcstasscript/instrument_diagram/make_diagram.py +++ b/mcstasscript/instrument_diagram/make_diagram.py @@ -91,4 +91,5 @@ def instrument_diagram(instrument, analysis=False, variable=None, limits=None): limits=limits) # Plot diagram - canvas.plot() \ No newline at end of file + return canvas.plot() + diff --git a/mcstasscript/interface/instr.py b/mcstasscript/interface/instr.py index fd28ca4f..ec37b897 100644 --- a/mcstasscript/interface/instr.py +++ b/mcstasscript/interface/instr.py @@ -6,6 +6,8 @@ import subprocess import copy import warnings +import io +import hashlib from IPython.display import IFrame @@ -149,6 +151,9 @@ class McCode_instr(BaseCalculator): component_help(name) Shows help on component of given name + add_component_dir(path) + Add path to the search dir for components + add_component(instance_name, component_name, **kwargs) Add a component to the instrument file @@ -341,6 +346,8 @@ def __init__(self, name, parameters=None, author=None, self._run_settings = {} # Settings for running simulation + self._run_settings["component_dirs"] = [] + # Sets max_line_length and adds paths to run_settings self._read_calibration() @@ -1193,6 +1200,19 @@ def _create_component_instance(self, name, component_name, **kwargs): return self.component_class_lib[component_name](name, component_name, **kwargs) + def add_component_dir(self, path=".", category="custom"): + """ + Method for adding a directory to the list of directories where to search for components" + """ + self.component_reader.add_custom_component_dir(path, category) + + current_directory = os.getcwd() + + if not os.path.isabs(path): + path = os.path.join(current_directory, path) + + self._run_settings["component_dirs"].append(path) + def add_component(self, name, component_name=None, *, before=None, after=None, AT=None, AT_RELATIVE=None, ROTATED=None, ROTATED_RELATIVE=None, RELATIVE=None, WHEN=None, @@ -2027,7 +2047,15 @@ def show_instrument_file(self, line_numbers=False): full_line = line_number + line print(full_line.replace("\n", "")) - def write_full_instrument(self): + + def __hash__(self): + """ Define a hashing specifically for this calculator + The hash method in the BaseCalculator would pack also the internals of this class and it changes when calling the backengine + """ + contents = self.write_full_instrument(False) + return int.from_bytes(hashlib.sha256(contents.encode()).digest(), "big") + + def write_full_instrument(self, do_write=True) -> str: """ Method for writing full instrument file to disk @@ -2041,8 +2069,12 @@ def write_full_instrument(self): self.check_for_errors() # Create file identifier - fo = open(os.path.join(self.input_path, self.name + ".instr"), "w") - + run_path = self._run_settings["run_path"] + fo = None + if do_write: + fo = open(os.path.join(run_path, self.name + ".instr"), "w") + else: + fo = io.StringIO() # Write quick doc start fo.write("/" + 80*"*" + "\n") fo.write("* \n") @@ -2062,7 +2094,8 @@ def write_full_instrument(self): fo.write("* %Identification\n") # Could allow the user to insert this fo.write("* Written by: %s\n" % self.author) t_format = "%H:%M:%S on %B %d, %Y" - fo.write("* Date: %s\n" % datetime.datetime.now().strftime(t_format)) + if do_write: + fo.write("* Date: %s\n" % datetime.datetime.now().strftime(t_format)) fo.write("* Origin: %s\n" % self.origin) fo.write("* %INSTRUMENT_SITE: Generated_instruments\n") fo.write("* \n") @@ -2075,14 +2108,18 @@ def write_full_instrument(self): fo.write("\n") fo.write("DEFINE INSTRUMENT %s (" % self.name) fo.write("\n") - # Insert parameters - parameter_list = list(self.parameters) - end_chars = [", "]*len(parameter_list) - if len(end_chars) >= 1: - end_chars[-1] = " " - for variable, end_char in zip(parameter_list, end_chars): - write_parameter(fo, variable, end_char) - fo.write(")\n") + + # remove parameters when calculating hash because don't want + # variations in the parameter values to trigger a new hash + if do_write: + # Insert parameters + parameter_list = list(self.parameters) + end_chars = [", "]*len(parameter_list) + if len(end_chars) >= 1: + end_chars[-1] = " " + for variable, end_char in zip(parameter_list, end_chars): + write_parameter(fo, variable, end_char) + fo.write(")\n") if self.dependency_statement != "": fo.write("DEPENDENCY " + str(self.dependency_statement) + "\n") self.search_statement_list.write(fo) @@ -2133,7 +2170,11 @@ def write_full_instrument(self): # End instrument file fo.write("\nEND\n") + instrument_file_as_string = "" + if do_write is False: + instrument_file_as_string = fo.getvalue() fo.close() + return instrument_file_as_string def get_component_subset_index_range(self, start_ref=None, end_ref=None): """ @@ -2275,7 +2316,7 @@ def settings(self, ncount=None, mpi="not_set", seed=None, increment_folder_name=None, custom_flags=None, executable=None, executable_path=None, suppress_output=None, gravity=None, checks=None, - openacc=None, NeXus=None): + openacc=None, NeXus=None, component_dirs=None, run_path=None): """ Sets settings for McStas run performed with backengine @@ -2312,6 +2353,10 @@ def settings(self, ncount=None, mpi="not_set", seed=None, If True, adds --openacc to mcrun call NeXus : bool If True, adds --format=NeXus to mcrun call + component_dirs : list + Additional directories where to find components (use absolute paths) + run_path : str + Change the directory where the code is compiled and run """ settings = {} @@ -2373,6 +2418,15 @@ def settings(self, ncount=None, mpi="not_set", seed=None, if NeXus is not None: settings["NeXus"] = bool(NeXus) + if component_dirs is not None: + if len(component_dirs) > 0: + settings["component_dirs"].append( component_dirs) + else: + component_dirs = [] + + if run_path is not None: + settings["run_path"] = os.path.abspath(run_path) + self._run_settings.update(settings) def settings_string(self): @@ -2467,7 +2521,11 @@ def backengine(self): self.__add_input_to_mcpl() instrument_path = os.path.join(self.input_path, self.name + ".instr") + + run_path = self._run_settings["run_path"] + instrument_path = os.path.join(run_path, self.name + ".instr") if not os.path.exists(instrument_path) or self._run_settings["force_compile"]: + print("Instrument file not found: ", instrument_path, os.path.exists(instrument_path), self._run_settings["force_compile"]) self.write_full_instrument() parameters = {} @@ -2483,7 +2541,7 @@ def backengine(self): options["output_path"] = self.output_path # Set up the simulation - simulation = ManagedMcrun(self.name + ".instr", **options) + simulation = ManagedMcrun(instrument_path, **options) #self.name + ".instr", **options) # Run the simulation and return data simulation.run_simulation() @@ -2728,15 +2786,17 @@ def show_diagram(self, analysis=False, variable=None, limits=None): if variable is not None: analysis = True - instrument_diagram(self, analysis=analysis, variable=variable, limits=limits) + fig = instrument_diagram(self, analysis=analysis, variable=variable, limits=limits) if self._run_settings["checks"]: self.check_for_errors() + return fig + def show_analysis(self, variable=None): beam_diag = IntensityDiagnostics(self) beam_diag.run_general(variable=variable) - beam_diag.plot() + return beam_diag.plot() def saveH5(self, filename: str, openpmd: bool = True): """