def get_bool_input( self, kwarg_label: str, state: Optional[dict] = None, label: Optional[str] = None, help_str: str = None, ): """ For Python classes which take boolean values as inputs, this will generate a corresponding Dash input layout. :param kwarg_label: The name of the corresponding Python input, this is used to name the component. :param label: A description for this input. :param state: Used to set state for this input, dict with arg name or kwarg name as key :param help_str: Text for a tooltip when hovering over label. :return: a Dash layout """ default = state.get(kwarg_label) or False self._kwarg_type_hints[kwarg_label] = "bool" bool_input = dcc.Checklist( id=self.id(kwarg_label, is_kwarg=True), style={"width": "5rem"}, options=[{ "label": "", "value": "enabled" }], value=["enabled"] if default else [], persistence=True, ) return add_label_help(bool_input, label, help_str)
def get_dict_input( self, kwarg_label: str, state: Optional[dict] = None, label: Optional[str] = None, help_str: str = None, key_name: str = "key", value_name: str = "value", ): default = state.get(kwarg_label) or {} self._kwarg_type_hints[kwarg_label] = "dict" dict_input = dt.DataTable( id=self.id(kwarg_label), columns=[ { "id": "key", "name": key_name }, { "id": "value", "name": value_name }, ], data=[{ "key": k, "value": v } for k, v in default.items()], editable=True, persistence=True, ) return add_label_help(dict_input, label, help_str)
def get_bool_input( self, kwarg_label: str, default: Optional[bool] = None, state: Optional[dict] = None, label: Optional[str] = None, help_str: str = None, **kwargs, ): """ For Python classes which take boolean values as inputs, this will generate a corresponding Dash input layout. :param kwarg_label: The name of the corresponding Python input, this is used to name the component. :param label: A description for this input. :param default: A default value for this input. :param state: Used to set default state for this input, use a dict with the kwarg_label as a key and the default value as a value. Ignored if `default` is set. It can be useful to use `state` if you want to set defaults for multiple inputs from a single dictionary. :param help_str: Text for a tooltip when hovering over label. :return: a Dash layout """ state = state or {} default = default or state.get(kwarg_label) or False bool_input = mpc.Switch( id=self.id(kwarg_label, is_kwarg=True, hint="bool"), value=True if default else False, hasLabel=True, **kwargs, ) return add_label_help(bool_input, label, help_str)
def get_slider_input( self, kwarg_label: str, state: Optional[dict] = None, label: Optional[str] = None, help_str: str = None, multiple: bool = False, **kwargs, ): default = state.get(kwarg_label) or False self._kwarg_type_hints[kwarg_label] = "slider" if multiple: slider_input = dcc.RangeSlider( id=self.id(kwarg_label, is_kwarg=True), tooltip={"placement": "bottom"}, value=default, persistence=True, **kwargs, ) else: slider_input = dcc.Slider( id=self.id(kwarg_label, is_kwarg=True), tooltip={"placement": "bottom"}, value=default, persistence=True, **kwargs, ) return add_label_help(slider_input, label, help_str)
def get_slider_input( self, kwarg_label: str, default: Optional[Any] = None, state: Dict = None, label: Optional[str] = None, help_str: str = None, multiple: bool = False, **kwargs, ): state = state or {} default = default or state.get(kwarg_label) # mpc.RangeSlider requires a domain to be specified slider_kwargs = {"domain": [0, default * 2]} slider_kwargs.update(**kwargs) if multiple: slider_input = mpc.DualRangeSlider( id=self.id(kwarg_label, is_kwarg=True, hint="slider"), value=default, **slider_kwargs, ) else: slider_input = mpc.RangeSlider( id=self.id(kwarg_label, is_kwarg=True, hint="slider"), value=default, **slider_kwargs, ) return add_label_help(slider_input, label, help_str)
def get_choice_input( self, kwarg_label: str, state: Optional[dict] = None, label: Optional[str] = None, help_str: str = None, options: Optional[List[Dict]] = None, **kwargs, ): """ For Python classes which take floats as inputs, this will generate a corresponding Dash input layout. :param kwarg_label: The name of the corresponding Python input, this is used to name the component. :param label: A description for this input. :param state: Used to set state for this input, dict with arg name or kwarg name as key :param help_str: Text for a tooltip when hovering over label. :param options: Options to choose from, as per dcc.Dropdown :return: a Dash layout """ default = state.get(kwarg_label) option_input = dcc.Dropdown( id=self.id(kwarg_label, is_kwarg=True, hint="literal"), options=options if options else [], value=default, persistence=True, clearable=False, **kwargs, ) return add_label_help(option_input, label, help_str)
def get_dict_input( self, kwarg_label: str, default: Optional[Any] = None, state: Optional[dict] = None, label: Optional[str] = None, help_str: str = None, key_name: str = "key", value_name: str = "value", ): """ :param kwarg_label: :param default: :param state: :param label: :param help_str: :param key_name: :param value_name: :return: """ state = state or {} default = default or state.get(kwarg_label) or {} dict_input = dt.DataTable( id=self.id(kwarg_label, is_kwarg=True, hint="dict"), columns=[ { "id": "key", "name": key_name }, { "id": "value", "name": value_name }, ], data=[{ "key": k, "value": v } for k, v in default.items()], editable=True, persistence=False, ) return add_label_help(dict_input, label, help_str)
def get_choice_input( self, kwarg_label: str, default: Optional[str] = None, state: Optional[dict] = None, label: Optional[str] = None, help_str: str = None, options: Optional[List[Dict]] = None, **kwargs, ): """ For Python classes which take pre-defined values as inputs, this will generate a corresponding input layout using mpc.Select. :param kwarg_label: The name of the corresponding Python input, this is used to name the component. :param label: A description for this input. :param default: A default value for this input. :param state: Used to set default state for this input, use a dict with the kwarg_label as a key and the default value as a value. Ignored if `default` is set. It can be useful to use `state` if you want to set defaults for multiple inputs from a single dictionary. :param help_str: Text for a tooltip when hovering over label. :param options: Options to choose from, as per dcc.Dropdown :return: a Dash layout """ state = state or {} default = default or state.get(kwarg_label) option_input = mpc.Select( id=self.id(kwarg_label, is_kwarg=True, hint="literal"), options=options if options else [], value=default, isClearable=False, arbitraryProps={**kwargs}, ) return add_label_help(option_input, label, help_str)
def get_numerical_input( self, kwarg_label: str, state: Optional[dict] = None, label: Optional[str] = None, help_str: str = None, is_int: bool = False, shape: Tuple[int, ...] = (), **kwargs, ): """ For Python classes which take matrices as inputs, this will generate a corresponding Dash input layout. :param kwarg_label: The name of the corresponding Python input, this is used to name the component. :param label: A description for this input. :param state: Used to set state for this input, dict with arg name or kwarg name as key :param help_str: Text for a tooltip when hovering over label. :param is_int: if True, will use a numeric input :param shape: (3, 3) for matrix, (1, 3) for vector, (1, 1) for scalar :return: a Dash layout """ default = np.full(shape, state.get(kwarg_label)) default = np.reshape(default, shape) self._kwarg_type_hints[kwarg_label] = shape def matrix_element(idx, value=0): # TODO: maybe move element out of the name mid = self.id(kwarg_label, is_kwarg=True, idx=idx) if not is_int: return dcc.Input( id=mid, inputMode="numeric", className="input", style={ "textAlign": "center", # shorter default width if matrix or vector "width": "2.5rem" if (shape == (3, 3)) or (shape == (3, )) else "5rem", "marginRight": "0.2rem", "marginBottom": "0.2rem", "height": "1.5rem", }, value=value, persistence=True, **kwargs, ) else: return daq.NumericInput(id=mid, value=value, **kwargs) # dict of row indices, column indices to element matrix_contents = defaultdict(dict) # determine what individual input boxes we need # note that shape = () for floats, shape = (3,) for vectors # but we may also need to accept input for e.g. (3, 1) it = np.nditer(default, flags=["multi_index", "refs_ok"]) while not it.finished: idx = it.multi_index row = (idx[1] if len(idx) > 1 else 0, ) column = idx[0] if len(idx) > 0 else 0 matrix_contents[row][column] = matrix_element(idx, value=it[0]) it.iternext() # arrange the input boxes in two dimensions (rows, columns) matrix_div_contents = [] for row_idx, columns in sorted(matrix_contents.items()): row = [] for column_idx, element in sorted(columns.items()): row.append(element) matrix_div_contents.append(html.Div(row)) matrix = html.Div(matrix_div_contents) return add_label_help(matrix, label, help_str)
def get_numerical_input( self, kwarg_label: str, default: Optional[Union[int, float, List]] = None, state: Optional[dict] = None, label: Optional[str] = None, help_str: str = None, is_int: bool = False, shape: Tuple[int, ...] = (), **kwargs, ): """ For Python classes which take matrices as inputs, this will generate a corresponding Dash input layout. :param kwarg_label: The name of the corresponding Python input, this is used to name the component. :param label: A description for this input. :param default: A default value for this input. :param state: Used to set default state for this input, use a dict with the kwarg_label as a key and the default value as a value. Ignored if `default` is set. It can be useful to use `state` if you want to set defaults for multiple inputs from a single dictionary. :param help_str: Text for a tooltip when hovering over label. :param is_int: if True, will use a numeric input :param shape: (3, 3) for matrix, (1, 3) for vector, (1, 1) for scalar :return: a Dash layout """ state = state or {} default = np.full(shape, default or state.get(kwarg_label)) default = np.reshape(default, shape) style = { "textAlign": "center", # shorter default width if matrix or vector "width": "5rem", "marginRight": "0.2rem", "marginBottom": "0.2rem", "height": "36px", } if "style" in kwargs: style.update(kwargs["style"]) del kwargs["style"] def matrix_element(idx, value=0): # TODO: maybe move element out of the name mid = self.id(kwarg_label, is_kwarg=True, idx=idx, hint=shape) if isinstance(value, np.ndarray): value = value.item() if not is_int: return dcc.Input( id=mid, inputMode="numeric", debounce=True, className="input", style=style, value=float(value) if value is not None else None, persistence=True, type="number", **kwargs, ) else: return dcc.Input( id=mid, inputMode="numeric", debounce=True, className="input", style=style, value=int(value) if value is not None else None, persistence=True, type="number", step=1, **kwargs, ) # dict of row indices, column indices to element matrix_contents = defaultdict(dict) # determine what individual input boxes we need # note that shape = () for floats, shape = (3,) for vectors # but we may also need to accept input for e.g. (3, 1) it = np.nditer(default, flags=["multi_index", "refs_ok"]) while not it.finished: idx = it.multi_index row = (idx[1] if len(idx) > 1 else 0, ) column = idx[0] if len(idx) > 0 else 0 matrix_contents[row][column] = matrix_element(idx, value=it[0]) it.iternext() # arrange the input boxes in two dimensions (rows, columns) matrix_div_contents = [] for row_idx, columns in sorted(matrix_contents.items()): row = [] for column_idx, element in sorted(columns.items()): row.append(element) matrix_div_contents.append(html.Div(row)) matrix = html.Div(matrix_div_contents) return add_label_help(matrix, label, help_str)
def options_layouts(self, state=None, structure=None): state = state or { "rotation_axis": [0, 0, 1], "rotation_angle": None, "expand_times": 2, "vacuum_thickness": 0, "ab_shift": [0, 0], "normal": False, "ratio": None, "plane": None, "max_search": 20, "tol_coi": 1e-8, "rm_ratio": 0.7, "quick_gen": False, } rotation_axis = self.get_numerical_input( label="Rotation axis", kwarg_label="rotation_axis", state=state, help_str="""Maximum number of atoms allowed in the supercell.""", shape=(3,), ) rotation_angle = self.get_choice_input( label="Rotation angle", kwarg_label="rotation_angle", state=state, help_str="""Rotation angle to generate grain boundary. Options determined by your choice of Σ.""", ) # sigma isn't a direct input into the transformation, but has # to be calculated from the rotation_axis and structure _, sigma_options, _ = self._get_sigmas_options_and_ratio( structure, state.get("rotation_axis") ) sigma = dcc.Dropdown( id=self.id("sigma"), style={"width": "5rem"}, options=sigma_options, value=sigma_options[0]["value"] if sigma_options else None, ) sigma = add_label_help( sigma, "Sigma", "The unit cell volume of the coincidence site lattice relative to " "input unit cell is denoted by sigma.", ) expand_times = self.get_numerical_input( label="Expand times", kwarg_label="expand_times", state=state, help_str="""The multiple number of times to expand one unit grain into a larger grain. This is useful to avoid self-interaction issues when using the grain boundary as an input to further simulations.""", is_int=True, shape=(), min=1, max=6, ) vacuum_thickness = self.get_numerical_input( label="Vacuum thickness /Å", kwarg_label="vacuum_thickness", state=state, help_str="""The thickness of vacuum that you want to insert between the two grains.""", shape=(), ) ab_shift = self.get_numerical_input( label="In-plane shift", kwarg_label="ab_shift", state=state, help_str="""In-plane shift of the two grains given in units of the **a** and **b** vectors of the grain boundary.""", shape=(2,), ) normal = self.get_bool_input( label="Set normal direction", kwarg_label="normal", state=state, help_str="Enable to require the **c** axis of the top grain to be perpendicular to the surface.", ) plane = self.get_numerical_input( label="Grain boundary plane", kwarg_label="plane", state=state, help_str="""Grain boundary plane in the form of a list of integers. If not set, grain boundary will be a twist grain boundary. The plane will be perpendicular to the rotation axis.""", shape=(3,), ) tol_coi = self.get_numerical_input( label="Coincidence Site Tolerance", kwarg_label="tol_coi", state=state, help_str="""Tolerance to find the coincidence sites. To check the number of coincidence sites are correct or not, you can compare the generated grain boundary's sigma with expected number.""", shape=(), ) rm_ratio = self.get_numerical_input( label="Site Merging Tolerance", kwarg_label="rm_ratio", state=state, help_str="""The criteria to remove the atoms which are too close with each other relative to the bond length in the bulk system.""", shape=(), ) return [ rotation_axis, sigma, rotation_angle, expand_times, vacuum_thickness, ab_shift, normal, plane, tol_coi, rm_ratio, ]