def system(self, gui=True): """View the entire molecular system. Parameters ---------- gui : bool Whether to display the gui. """ # Make sure we're running inside a Jupyter notebook. if not _is_notebook(): return None # Get the latest system from the process. if self._is_process: system = self._handle.getSystem() # No system. if system is None: return else: system = system._getSireSystem() else: system = self._handle # Create and return the view. return self._create_view(system, gui=gui)
def savePDB(self, file, index=None): """Save a specific view as a PDB file. Parameters ---------- file : str The name of the file to write to. index : int The view index. """ # Make sure we're running inside a Jupyter notebook. if not _is_notebook(): return None # Default to the most recent view. if index is None: index = self._num_views - 1 # Check that the index is an integer. elif type(index) is not int: raise TypeError("'index' must be of type 'int'") # Make sure the view index is valid. if index < 0 or index >= self._num_views: raise ValueError("View index (%d) is out of range: [0-%d]" % (index, self._num_views-1)) # Copy the file to the chosen location. _shutil.copyfile("%s/view_%04d.pdb" % (self._work_dir, index), file)
def reload(self, index=None, gui=True): """Reload a particular view. Parameters ---------- index : int The view index. gui : bool Whether to display the gui. """ # Make sure we're running inside a Jupyter notebook. if not _is_notebook(): return None # Return if there are no views. if self._num_views == 0: return # Default to the most recent view. if index is None: index = self._num_views - 1 # Check that the index is an integer. elif type(index) is not int: raise TypeError("'index' must be of type 'int'") # Make sure the view index is valid. if index < 0 or index >= self._num_views: raise ValueError("View index (%d) is out of range: [0-%d]" % (index, self._num_views-1)) # Create and return the view. return self._create_view(view=index, gui=gui)
def getOutput(self, name=None, block="AUTO", file_link=False): """Return a link to a zip file of the working directory. Parameters ---------- name : str The name of the zip file. block : bool Whether to block until the process has finished running. file_link : bool Whether to return a FileLink when working in Jupyter. Returns ------- ouput : str, IPython.display.FileLink A path, or file link, to an archive of the process output. """ if name is None: name = self._name else: if type(name) is not str: raise TypeError("'name' must be of type 'str'") # Wait for the process to finish. if block is True: self.wait() elif block == "AUTO" and self._is_blocked: self.wait() # Generate the zip file name. zipname = "%s.zip" % name # Glob all of the output files. output = _glob.glob("%s/*" % self._work_dir) with _zipfile.ZipFile(zipname, "w") as zip: # Loop over all of the file outputs. for file in output: zip.write(file, arcname=_os.path.basename(file)) # Return a link to the archive. if _is_notebook(): if file_link: return _FileLink(zipname) else: return zipname # Return the path to the archive. else: return zipname
def getOutput(self, filename=None, file_link=False): """Return a zip file containing the output from the parameterisation process. Parameters ---------- filename : str The name to write the output to. file_link : bool Whether to return a FileLink when working in Jupyter. Returns ------- file_link : str, IPython.lib.display.FileLink The name of, or link to, a zipfile containing the output. """ if self._zipfile is None or filename is not None: if filename is not None: zipname = "%s.zip" % filename # If the user has passed a directory, make sure that is exists. if _os.path.basename(filename) != filename: dirname = _os.path.dirname(filename) # Create the directory if it doesn't already exist. if not _os.path.isdir(dirname): _os.makedirs(dirname, exist_ok=True) else: zipname = "%s.zip" % self._hash # Append the files to the archive. with _zipfile.ZipFile(zipname, "w") as zip: # Loop over all of the output files. for file in _glob.glob("%s/*" % self._work_dir): zip.write(file, arcname=_os.path.basename(file)) # Store the location of the zip file. Only do so if the # parameterisation has finished. if not self._is_finished: self._zipfile = zipname # Return a link to the archive. if _is_notebook(): if file_link: return _FileLink(self._zipfile) else: return self._zipfile else: return self._zipfile
def molecule(self, index=0, gui=True): """View a specific molecule. Parameters ---------- index : int The molecule index. gui : bool Whether to display the gui. """ # Make sure we're running inside a Jupyter notebook. if not _is_notebook(): return None # Check that the index is an integer. if type(index) is not int: raise TypeError("'index' must be of type 'int'") # Get the latest system from the process. if self._is_process: system = self._handle.getSystem()._getSireSystem() # No system. if system is None: return else: system = self._handle # Extract the molecule numbers. molnums = system.molNums() # Make sure the index is valid. if index < 0 or index > len(molnums): raise ValueError("Molecule index is out of range!") # Create a new system and add a single molecule. s = _Sire.System.System("BioSimSpace System") m = _Sire.Mol.MoleculeGroup("all") m.add(system[molnums[index]]) s.add(m) # Create and return the view. return self._create_view(s, gui=gui)
def __init__(self, handle): """Constructor. Parameters ---------- handle : :class:`Process <BioSimSpace.Process>`, \ :class:`System <BioSimSpace._SireWrappers.System>` A handle to a process or system. """ # Make sure we're running inside a Jupyter notebook. if not _is_notebook(): _warnings.warn("You can only use BioSimSpace.Notebook.View from within a Jupyter notebook.") return None # Check the handle. # BioSimSpace process. if isinstance(handle, _Process): self._handle = handle self._is_process = True # BioSimSpace system. elif type(handle) is _System: self._handle = handle._getSireSystem() self._is_process = False else: raise TypeError("The handle must be of type 'BioSimSpace.Process' or 'BioSimSpace._SireWrappers.System'.") # Create a temporary workspace for the view object. self._tmp_dir = _tempfile.TemporaryDirectory() self._work_dir = self._tmp_dir.name # Zero the number of views. self._num_views = 0
import timeit as _timeit import warnings as _warnings import tempfile as _tempfile import zipfile as _zipfile import Sire.Mol as _SireMol from BioSimSpace import _is_interactive, _is_notebook from ..Protocol._protocol import Protocol as _Protocol from .._SireWrappers import System as _System import BioSimSpace.Types._type as _Type import BioSimSpace.Units as _Units if _is_notebook(): from IPython.display import FileLink as _FileLink __author__ = "Lester Hedges" __email_ = "*****@*****.**" __all__ = ["Process"] class _MultiDict(dict): """A multi-valued dictionary.""" def __setitem__(self, key, value): """Add the given value to the list of values for this key.""" self.setdefault(key, []).append(value) class Process(): """Base class for running different biomolecular simulation processes."""
class Node(): """A class for interfacing with BioSimSpace nodes. Nodes are used to collect and validate user input, document the intentions of the workflow component, track and report errors, and validate output. Once written, a node can be run from within Jupyter, from the command-line, or plugged into a workflow engine, such as Knime. Example ------- A generic energy minimisation node: >>> import BioSimSpace as BSS >>> node = BSS.Gateway.Node("Perform energy minimisation") >>> node.addAuthor(name="Lester Hedges", email="*****@*****.**", affiliation="University of Bristol") >>> node.setLicence("GPLv3") >>> node.addInput("files", BSS.Gateway.FileSet(help="A set of molecular input files.")) >>> node.addInput("steps", BSS.Gateway.Integer(help="The number of minimisation steps.", minimum=0, maximum=100000, default=10000)) >>> node.addOutput("minimised", BSS.Gateway.FileSet(help="The minimised molecular system.")) >>> node.showControls() >>> system = BSS.IO.readMolecules(node.getInput("files")) >>> protocol = BSS.Protocol.Minimisation(steps=node.getInput("steps")) >>> process = BSS.MD.run(system, protocol) >>> node.setOutput("minimised", BSS.IO.saveMolecules("minimised", process.getSystem(block=True), system.fileFormat())) >>> node.validate() """ # Whether the node is run from Knime. _is_knime = False # Whether the node is run from a Jupyter notebook. _is_notebook = _is_notebook() def __init__(self, description, name=None): """Constructor. Parameters ---------- description : str A description of the node. name : str The name of the node. """ if type(description) is not str: raise TypeError("The 'description' keyword must be of type 'str'.") # Set the node name. if name is None: try: self._name = _os.path.basename(__main__.__file__) except: self._name = None else: if type(name) is not str: raise TypeError("The 'name' keyword must be of type 'str'.") self._name = name # Set the node description string. self._description = description # Initalise the authors. self._authors = None # Initalise the license. self._license = None # Initialise dictionaries for the inputs/outputs. self._inputs = _collections.OrderedDict() self._outputs = _collections.OrderedDict() # A dictionary of Jupyter widgets. self._widgets = _collections.OrderedDict() # Whether the input is valid. self._is_valid_input = False # Whether the output has been validated. self._is_output_validated = False # A list of user error messages. self._errors = [] # Initialise the parser. self._parser = None self._required = None # Intialise the Jupyter input panel. self._control_panel = None # Running from the command-line. if not self._is_knime and not self._is_notebook: # Generate the node help description. description = self._generate_description() # Create the parser. self._parser = _argparse.ArgumentParser( description=description, formatter_class=_argparse.RawTextHelpFormatter, add_help=False, config_file_parser_class=_argparse.YAMLConfigFileParser, add_config_file_help=False) # Add argument groups. self._required = self._parser.add_argument_group( "Required arguments") self._optional = self._parser.add_argument_group( "Optional arguments") self._optional.add_argument( "-h", "--help", action="help", help="Show this help message and exit.") self._optional.add_argument("-c", "--config", is_config_file=True, help="Path to configuration file.") def __del__(self): """Destructor.""" # Validate the node if the user hasn't already done so. if self._is_valid_input: if not self._is_output_validated: self.validate() def addInput(self, name, input): """Add an input requirement. Parameters ---------- name : str The name of the input. input : :class:`Requirement <BioSimSpace.Gateway._requirement.Requirement>` The input requirement object. """ if type(name) is not str: raise TypeError("'name' must be of type 'str'.") if not isinstance(input, _Requirement): raise TypeError("'input' must be of type 'Requirement'.") # We already have an input with this name. reset = False if name in self._inputs: if self._is_notebook: _warnings.warn("Duplicate input requirement '%s'" % name) reset = True else: raise ValueError("Duplicate input requirement '%s'" % name) # Add the input to the dictionary. self._inputs[name] = input # Create a Knime GUI widget. if self._is_knime: self._addInputKnime(name, input) # Create a Jupyter GUI widget. elif self._is_notebook: return self._addInputJupyter(name, input, reset) # Command-line argparse ArgumentParser. else: self._addInputCommandLine(name, input) def _addInputCommandLine(self, name, input): """Add an input requirement for the command-line. Parameters ---------- name : str The name of the input. input : :class:`Requirement <BioSimSpace.Gateway._requirement.Requirement>` The input requirement object. """ # Append long-form argument name if not present. if (len(name) > 2): if name[0:2] != '--': name = '--' + name else: name = '--' + name if input.isOptional(): if input.getDefault() is not None: if input.isMulti() is not False: self._optional.add_argument( name, type=input.getArgType(), nargs='+', help=self._create_help_string(input), default=input.getDefault()) else: if input.getArgType() is bool: self._optional.add_argument( name, type=_str2bool, nargs='?', const=True, default=input.getDefault(), help=self._create_help_string(input)) else: if input.getAllowedValues() is not None: self._optional.add_argument( name, type=input.getArgType(), help=self._create_help_string(input), default=input.getDefault(), choices=input.getAllowedValues()) else: self._optional.add_argument( name, type=input.getArgType(), help=self._create_help_string(input), default=input.getDefault()) else: if input.isMulti() is not False: self._optional.add_argument( name, type=input.getArgType(), nargs='+', help=self._create_help_string(input)) else: self._optional.add_argument( name, type=input.getArgType(), help=self._create_help_string(input)) else: if input.isMulti() is not False: self._required.add_argument( name, type=input.getArgType(), nargs='+', help=self._create_help_string(input), required=True) else: if input.getAllowedValues() is not None: self._required.add_argument( name, type=input.getArgType(), help=self._create_help_string(input), required=True, choices=input.getAllowedValues()) else: if input.getArgType() is bool: self._required.add_argument( name, type=_str2bool, nargs='?', const=True, help=self._create_help_string(input)) else: self._required.add_argument( name, type=input.getArgType(), help=self._create_help_string(input), required=True) def _addInputKnime(self, name, input): """Add an input requirement for Knime. Parameters ---------- name : str The name of the input. input : :class:`Requirement <BioSimSpace.Gateway._requirement.Requirement>` The input requirement object. """ return None def _addInputJupyter(self, name, input, reset=False): """Add an input requirement for Jupyter. Parameters ---------- name : str The name of the input. input : :class:`Requirement <BioSimSpace.Gateway._requirement.Requirement>` The input requirement object. reset : bool Whether to reset the widget data. """ # Create a widget button to indicate whether the requirement value # has been set. button = _widgets.Button( tooltip="The input requirement is unset.", button_style="warning", icon="fa-exclamation-triangle", layout=_widgets.Layout(flex="1 1 auto", width="auto"), disabled=False, ) # Add a Jupyter widget for each of the supported requirement types. # Boolean. if type(input) is _Boolean: # Create a Jupyter toggle button. widget = _widgets.ToggleButton(value=False, description=name, tooltip=input.getHelp(), button_style="", icon="check", disabled=False) # Add the 'set' indicator button to the widget. widget._button = button # Flag that the widget is unset. widget._is_set = False # Get the default value. default = input.getDefault() if default is not None: widget.value = default widget._is_set = True widget._button.tooltip = "The input requirement is set." widget._button.button_style = "success" widget._button.icon = "fa-check" # Store the requirement name. widget._name = name # Bind the callback function. widget.observe(_on_value_change, names="value") # Store the widget. self._widgets[name] = widget # Integer. elif type(input) is _Integer: # Get the list of allowed values. allowed = input.getAllowedValues() # Get the default value. default = input.getDefault() if allowed is not None: # Set the default. if default is None: default = allowed[0] # Create a dropdown for the list of allowed values. widget = _widgets.Dropdown(options=allowed, value=default, description=name, tooltip=input.getHelp(), disabled=False) else: # Get the range of the input. _min = input.getMin() _max = input.getMax() # Whether the integer is unbounded. is_unbounded = True if _min is not None: # Set the default. if default is None: default = _min # Bounded integer. if _max is not None: step = int((_max - _min) / 100) # Create an int slider widget. widget = _widgets.IntSlider(value=default, min=_min, max=_max, step=step, description=name, tooltip=input.getHelp(), continuous_update=False, orientation="horizontal", readout=True, readout_format="d", disabled=False) # Flag that the integer is bounded. is_unbounded = False # Unbounded integer. if is_unbounded: # Create an integer widget. widget = _widgets.IntText(value=default, description=name, tooltip=input.getHelp(), disabled=False) # Add the 'set' indicator button to the widget. widget._button = button # Flag that the widget is unset. widget._is_set = False # Add an attribute to flag whether the widget value has # been set by the user. if input.getDefault() is not None: widget._is_set = True widget._button.tooltip = "The input requirement is set." widget._button.button_style = "success" widget._button.icon = "fa-check" # Store the requirement name. widget._name = name # Bind the callback function. widget.observe(_on_value_change, names="value") # Store the widget. self._widgets[name] = widget # Float types (including those with units). elif type(input) in _float_types: # Get the list of allowed values. allowed = input.getAllowedValues() # Get the default value. default = input.getDefault() # Get the magnitude of types with units. if isinstance(default, _Type.Type): default = default.magnitude() if allowed is not None: # Set the default. if default is None: default = allowed[0] # Get the magnitude of types with units. if isinstance(default, _Type.Type): default = default.magnitude() # Create a dropdown for the list of allowed values. widget = _widgets.Dropdown(options=allowed, value=default, description=name, tooltip=input.getHelp(), disabled=False) else: # Get the range of the input. _min = input.getMin() _max = input.getMax() # Get the magnitude of types with units. if isinstance(_min, _Type.Type): _min = _min.magnitude() if isinstance(_max, _Type.Type): _max = _max.magnitude() # Whether the float is unbounded. is_unbounded = True if _min is not None: # Set the default. if default is None: default = _min # Bounded float. if _max is not None: step = (_max - _min) / 100 # Create a float slider widget. widget = _widgets.FloatSlider(value=default, min=_min, max=_max, step=step, description=name, tooltip=input.getHelp(), continuous_update=False, orientation="horizontal", readout=True, readout_format=".1f", disabled=False) # Flag that the float is bounded. is_unbounded = False # Unbounded float. if is_unbounded: # Create a float widget. widget = _widgets.FloatText(value=default, description=name, tooltip=input.getHelp(), disabled=False) # Add the 'set' indicator button to the widget. widget._button = button # Flag that the widget is unset. widget._is_set = False # Add an attribute to flag whether the widget value has # been set by the user. if input.getDefault() is not None: widget._is_set = True widget._button.tooltip = "The input requirement is set." widget._button.button_style = "success" widget._button.icon = "fa-check" # Store the requirement name. widget._name = name # Bind the callback function. widget.observe(_on_value_change, names="value") # Store the widget. self._widgets[name] = widget # String. elif type(input) is _String: # Get the list of allowed values. allowed = input.getAllowedValues() # Get the default value. default = input.getDefault() if allowed is not None: # Set the default. if default is None: default = allowed[0] # Create a dropdown for the list of allowed values. widget = _widgets.Dropdown(options=allowed, value=default, description=name, tooltip=input.getHelp(), disabled=False) else: if default is None: # Create a text widget without a default. widget = _widgets.Text(placeholder="Type something", description=name, tooltip=input.getHelp(), disabled=False) else: # Create a text widget. widget = _widgets.Text(value=default, placeholder="Type something", description=name, tooltip=input.getHelp(), disabled=False) # Add the 'set' indicator button to the widget. widget._button = button # Flag that the widget is unset. widget._is_set = False # Add an attribute to flag whether the widget value has # been set by the user. if input.getDefault() is not None: widget._is_set = True widget._button.tooltip = "The input requirement is set." widget._button.button_style = "success" widget._button.icon = "fa-check" # Store the requirement name. widget._name = name # Bind the callback function. widget.observe(_on_value_change, names="value") # Store the widget. self._widgets[name] = widget # File. elif type(input) is _File: # Create a fileupload widget. widget = _fileupload.FileUploadWidget() # Add the 'set' indicator button to the widget. widget._button = button # Flag that the widget is unset. widget._is_set = False # Flag that this is just a single file upload. widget._is_multi = False # Set the value to None. widget.value = None # Store the requirement name. widget._name = name # Bind the callback function. widget.observe(_on_file_upload, names="data") # Store the widget. self._widgets[name] = widget # File set. elif type(input) is _FileSet: # Create a fileupload widget. widget = _fileupload.FileUploadWidget() # Add the 'set' indicator button to the widget. widget._button = button # Flag that the widget is unset. widget._is_set = False # Flag that this is is a set of files. widget._is_multi = True # Set the value to None. widget.value = None # Store a reference to the node. widget._node = self # Store the requirement name. widget._name = name # Store the requirement. widget._input = input # Bind the callback function. widget.observe(_on_file_upload, names="data") # This is a new widget. if not name in self._widgets or reset: self._widgets[name] = [widget] else: self._widgets[name].append(widget) # Unsupported input. else: raise ValueError("Unsupported requirement type '%s'" % type(input)) def addOutput(self, name, output): """Add an output requirement. Parameters ---------- name : str The name of the output. output : :class:`Requirement <BioSimSpace.Gateway._requirement.Requirement>` The output requirement object. """ if type(name) is not str: raise TypeError("'name' must be of type 'str'.") if not isinstance(output, _Requirement): raise TypeError("'output' must be of type 'Requirement'.") # We already have an ouput requirement with this name. if name in self._outputs: _warnings.warn( "Duplicate input requirement. Overwriting existing value!") # Add the output to the dictionary. self._outputs[name] = output # Update the parser description. if not self._is_notebook: self._parser.description = self._generate_description() def setOutput(self, name, value): """Set the value of an output. Parameters ---------- name : str The name of the output. value : The value of the output. """ try: self._outputs[name].setValue(value, name=name) except KeyError: raise def getInput(self, name): """Get the value of the named input. Parameters ---------- name : str The name of the input requirement. Returns ------- input : The value of the named input requirement. """ if type(name) is not str: raise TypeError("The name must be of type 'str'") # Validate the inputs. self._is_valid_input = self._validateInput() try: value = self._inputs[name].getValue() if type(value) is list: return value.copy() else: return value except KeyError: raise def getInputs(self): """Get all of the input requirements. Returns ------- inputs : { str : :class:`Requirement <BioSimSpace.Gateway._requirement.Requirement>` } The dictionary of input requirements. """ # Validate the inputs. self._is_valid_input = self._validateInput() return self._inputs.copy() def addError(self, error): """Add an error message. Parameters ---------- error : str The error message. """ if type(error) is not str: raise TypeError("The error message must be of type 'str'") else: self._errors.append(error) def addAuthor(self, name=None, email=None, affiliation=None): """Add an author for the node. Parameters ---------- name : str The author's name. email : str The author's email address. affiliation : str The author's affiliation. """ if name is None: raise ValueError("Missing required 'name' keyword argument.") if type(name) is not str: raise TypeError("'name' must be of type 'str'") if email is not None and type(email) is not str: raise TypeError("'email' must be of type 'str'") if affiliation is not None and type(affiliation) is not str: raise TypeError("'affiliation' must be of type 'str'") if self._authors is None: self._authors = [{ "name": name, "email": email, "affiliation": affiliation }] else: author = {"name": name, "email": email, "affiliation": affiliation} if not author in self._authors: self._authors.append(author) def getAuthors(self): """Return the list of authors. Returns ------- authors : [dict] A list of author dictionaries. """ return self._authors.copy() def setLicense(self, license): """Set the license for the node. Parameters ---------- license : str The license type. """ if type(license) is not str: raise TypeError("The license must be of type 'str'") else: self._license = license def getLicense(self): """Return the license. Returns ------- license : str The license of the node. """ return self._license def showControls(self): """Show the Jupyter widget GUI to allow the user to enter input. Returns ------- controls : ipywidgets.form A gui control panel for setting input requirements. """ if not self._is_notebook: return # Create the layout object. layout = _widgets.Layout(display="flex", flex_flow="row", justify_content="space-between") # Initialise the list of form items. requirements = [] # Initialise the list of input requirement 'set' indicators. indicators = [] # Loop over all of the widgets. for name, widget in self._widgets.items(): # Credate the label string. string = "%s: %s" % (name, self._inputs[name].getHelp()) # Add the unit information. unit = self._inputs[name].getUnit() if unit is not None: string += " (%s)" % self._inputs[name]._print_unit # Create the widget label. label = _widgets.Label(value=string) # This is a FileSet requirement with multiple widgets. if type(widget) is list: items = [label] + widget indicator = widget[0]._button else: items = [label, widget] indicator = widget._button # Create a box for the widget. box = _widgets.Box(items, layout=layout) # Add the widget box to the list. requirements.append(box) # Add the indicator to the list. indicators.append(indicator) # Create the widget form. form1 = _widgets.Box(requirements, layout=_widgets.Layout(display="flex", flex_flow="column", border="solid 2px", align_items="stretch", width="100%")) # Create the indicator form. form2 = _widgets.VBox(indicators, layout=_widgets.Layout(display="flex", flex_flow="column", align_items="stretch", width="5%")) # Combine the two forms. form = _widgets.Box([form1, form2], layout=layout) # Store the form. self._control_panel = form return form def _validateInput(self): """Validate the parsed inputs.""" # Knime. if self._is_knime: pass # Jupyter. elif self._is_notebook: # Loop over the widgets and set the input values. for key, widget in self._widgets.items(): # Use the widget value if it has been set, otherwise, set the value to None. # This ensures that the user actually sets a value. # This is a FileSet requirement with multiple widgets. if type(widget) is list: value = [] # Loop over all of the widgets. for w in widget: if w._is_set: value.append(w.value) # If there are no values, set to None. if len(value) == 0: value = None # Single widget. else: if widget._is_set: value = widget.value else: value = None self._inputs[key].setValue(value, name=key) # Command-line. else: # Parse the arguments into a dictionary. args = vars(self._parser.parse_known_args()[0]) # Now loop over the arguments and set the input values. for key, value in args.items(): if key is not "config": self._inputs[key].setValue(value, name=key) def validate(self, file_prefix="output"): """Whether the output requirements are satisfied. Parameters ---------- file_prefix : str The prefix of the output file name. Returns ------- output : IPython.lib.display.FileLink, str If running interatvely: A link to a zipfile containing the validated output, else the name of a YAML file containing the node output. """ if type(file_prefix) is not str: raise TypeError("The 'file_prefix' keyword must be of type 'str'.") # Flag that we have validated output. self._is_output_validated = True # A list of File and FileSet outputs. file_outputs = [] # Check no outputs are None. for name, output in self._outputs.items(): if output.getValue() is None: self._errors.append("Missing output for requirement '%s'" % name) else: if type(output) is _File or type(output) is _FileSet: file_outputs.append(output) # Node failed. if len(self._errors) > 0: for error in self._errors: print("%s" % error, file=_sys.stderr) if self._name is not None: raise SystemExit("Node '%s' failed!" % self._name) else: raise SystemExit("Node failed!") # Create a compressed archive containing all file output for the node. if self._is_notebook: # There are files. if len(file_outputs) > 0: # Create the archive name. zipname = "%s.zip" % file_prefix # Append the files to the archive. with _zipfile.ZipFile(zipname, "w") as zip: # Loop over all of the file outputs. for output in file_outputs: if type(output) is _File: file = output.getValue() zip.write(file, arcname=_os.path.basename(file)) else: for file in output.getValue(): zip.write(file, arcname=_os.path.basename(file)) # Return a link to the archive. return _FileLink(zipname) else: # Initialise an empty dictionary to store the output data. data = {} # Create the YAML file name. yamlname = "%s.yaml" % file_prefix # Populate the dictionary. for name, output in self._outputs.items(): data[name] = output.getValue() # Write the outputs to a YAML file. with open(yamlname, "w") as file: _yaml.dump(data, file, default_flow_style=False) return yamlname def _create_help_string(self, input): """Create a nicely formatted argparse help string. Parameters ---------- input : :class:`Requirement <BioSimSpace.Gateway.Requirement>` The input requirement. Returns ------- help : str The formatted help string. """ # Initialise the help string. help = "\n".join(_textwrap.wrap(input.getHelp(), 56)) # Get the unit. units = input.getUnit() # Add the units to the help string. if units is not None: help += "\n units=%s" % (units[0] + units[1:].lower()) # Get the default value. default = input.getDefault() # Add default value to help string. if default is not None: help += "\n default=%s" % default # Get the min/max allowed values. minimum = input.getMin() maximum = input.getMax() # Add min/max values to help string. if minimum is not None: if maximum is not None: help += "\n min=%s, max=%s" % (minimum, maximum) else: help += "\n min=%s" % minimum else: if maximum is not None: help += "\n max=%s" % maxmimum return help def _generate_description(self): """Generate a formatted output section for the argparse help description. Returns ------- output : str A string listing the output requirements. """ string = "\n".join(_textwrap.wrap(self._description, 80)) yaml_help = ( "Args that start with '--' (e.g. --arg) can also be set " "in a config file (specified via -c). The config file uses " "YAML syntax and must represent a YAML 'mapping' " "(for details, see http://learn.getgrav.org/advanced/yaml). " "If an arg is specified in more than one place, then " "commandline values override config file values which " "override defaults.") string += "\n\n" + "\n".join(_textwrap.wrap(yaml_help, 80)) # Initialise the output string. string += "\n\nOutput:\n" # Add documentation for each output. for name, output in self._outputs.items(): num_whitespace = 19 - len(output.__class__.__name__) - len(name) s = " %s: %s " % (name, output.__class__.__name__) for i, line in enumerate(_textwrap.wrap(output.getHelp(), 56)): if i == 0: for _ in range(0, num_whitespace): s += " " else: for _ in range(0, 24): s += " " s += line + "\n" string += s return string
def molecules(self, indices=None, gui=True): """View specific molecules. Parameters ---------- indices : [int], range A list of molecule indices. gui : bool Whether to display the gui. """ # Make sure we're running inside a Jupyter notebook. if not _is_notebook(): return None # Return a view of the entire system. if indices is None: return self.system(gui=gui) # Convert single indices to a list. if isinstance(indices, range): indices = list(indices) elif type(indices) is not list: indices = [indices] # Check that the indices is a list of integers. if not all(isinstance(x, int) for x in indices): raise TypeError("'indices' must be a 'list' of type 'int'") # Get the latest system from the process. if self._is_process: system = self._handle.getSystem()._getSireSystem() # No system. if system is None: return else: system = self._handle # Extract the molecule numbers. molnums = system.molNums() # Create a new system. s = _Sire.System.System("BioSimSpace System") m = _Sire.Mol.MoleculeGroup("all") # Loop over all of the indices. for index in indices: if index < 0 or index > len(molnums): raise ValueError("Molecule index is out of range!") # Add the molecule. m.add(system[molnums[index]]) # Add all of the molecules to the system. s.add(m) # Create and return the view. return self._create_view(s, gui=gui)