Skip to content

ToolComponent

mdo_framework.core.components.ToolComponent

Bases: Discipline

Generic GEMSEO discipline that wraps a Python function.

Source code in src/mdo_framework/core/components.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
class ToolComponent(Discipline):
    """Generic GEMSEO discipline that wraps a Python function."""

    def __init__(
        self,
        name: str,
        func: Callable,
        inputs: list[str],
        outputs: list[str],
        derivatives: bool = False,
    ):
        """Initializes the generic GEMSEO tool component.

        Args:
            name: The name of the discipline.
            func: The Python callable executing the tool logic.
            inputs: List of input variable names.
            outputs: List of output variable names.
            derivatives: Whether the function provides analytical derivatives (default False).
        """
        super().__init__(name=name)
        self.func = func
        self._inputs_list = inputs
        self._outputs_list = outputs
        self._derivatives = derivatives

        # GEMSEO Grammars require us to define input/output names
        self.input_grammar.update_from_names(self._inputs_list)
        self.output_grammar.update_from_names(self._outputs_list)

        # GEMSEO expects default values to be set in default_inputs if they exist
        self.default_inputs = {
            in_name: np.array([0.0]) for in_name in self._inputs_list
        }

    def _run(self, **kwargs) -> None:
        """Executes the wrapped function using data from self.local_data and stores results.

        Expects the wrapped function to return a dictionary mapping output names
        to their computed values, or a single value for single outputs, or a tuple.
        """
        # Prepare inputs as scalars: GEMSEO stores np.ndarray([v]) in local_data,
        # but wrapped functions typically expect plain floats.
        input_vals = {}
        for name in self._inputs_list:
            val = self.local_data[name]
            input_vals[name] = (
                val.item() if isinstance(val, np.ndarray) and val.size == 1 else val
            )

        # Always use keyword arguments to guarantee correct mapping
        # regardless of the order in _inputs_list.
        result = self.func(**input_vals)

        # Map results to outputs inside self.local_data
        if len(self._outputs_list) == 1:
            output_name = self._outputs_list[0]
            self.local_data[output_name] = np.atleast_1d(result)
        elif isinstance(result, dict):
            for name in self._outputs_list:
                self.local_data[name] = np.atleast_1d(result[name])
        else:
            # If result is a tuple/list, assume order matches outputs
            for i, name in enumerate(self._outputs_list):
                self.local_data[name] = np.atleast_1d(result[i])

    def _compute_jacobian(
        self, inputs: list[str] = None, outputs: list[str] = None
    ) -> None:
        """Computes the analytical derivatives if provided."""
        if self._derivatives:
            # Placeholder for exact jacobian
            pass
        else:
            # GEMSEO handles finite differences automatically if we call self.set_jacobian_approximation()
            # which is typically done outside or at initialization.
            pass

__init__(name, func, inputs, outputs, derivatives=False)

Initializes the generic GEMSEO tool component.

Parameters:

Name Type Description Default
name str

The name of the discipline.

required
func Callable

The Python callable executing the tool logic.

required
inputs list[str]

List of input variable names.

required
outputs list[str]

List of output variable names.

required
derivatives bool

Whether the function provides analytical derivatives (default False).

False
Source code in src/mdo_framework/core/components.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def __init__(
    self,
    name: str,
    func: Callable,
    inputs: list[str],
    outputs: list[str],
    derivatives: bool = False,
):
    """Initializes the generic GEMSEO tool component.

    Args:
        name: The name of the discipline.
        func: The Python callable executing the tool logic.
        inputs: List of input variable names.
        outputs: List of output variable names.
        derivatives: Whether the function provides analytical derivatives (default False).
    """
    super().__init__(name=name)
    self.func = func
    self._inputs_list = inputs
    self._outputs_list = outputs
    self._derivatives = derivatives

    # GEMSEO Grammars require us to define input/output names
    self.input_grammar.update_from_names(self._inputs_list)
    self.output_grammar.update_from_names(self._outputs_list)

    # GEMSEO expects default values to be set in default_inputs if they exist
    self.default_inputs = {
        in_name: np.array([0.0]) for in_name in self._inputs_list
    }