Example #1
0
    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)
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
    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)
Example #5
0
    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)
Example #6
0
    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)
Example #7
0
    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)
Example #8
0
    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)
Example #9
0
    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)
Example #10
0
    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)
Example #11
0
    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,
        ]