Skip to content

← Back to LocalObjective1 documentation

LocalObjective1 - Source Code

File: userfiles/SpeedReducer/subsystem1/LocalObjective1.py

# Copyright (C) The DistributedDesignOptimizer Contributors
# Licensed under the GNU General Public License v3.0. See LICENSE file for details.
"""Local objective module for Speed Reducer subsystem 1.

Defines the local objective function contribution from shaft 1
to the total weight objective.
"""
from typing import List
from Distributed_Design_Optimizer.subsystem.optimization.designproblem import LocalObjectiveInterface
from Distributed_Design_Optimizer.subsystem import LocalSubSystemBasis
from Distributed_Design_Optimizer.subsystem.tools import ScalerBasis, ScalerZeroOne


class LocalObjective1(LocalObjectiveInterface):
    """Local objective class for Speed Reducer subsystem 1.

    Evaluates the shaft 1 weight contribution to the total reducer weight.

    Attributes:
        None specific to this class; inherits from LocalObjectiveInterface.
    """

    def __init__(self) -> None:
        """Initialize LocalObjective1 instance."""
        pass

    def evaluateLocalObjective(self, subsystem: LocalSubSystemBasis) -> None:
        """Evaluate the local objective function for subsystem 1.

        Computes the shaft 1 weight contribution to total reducer weight.

        Args:
            subsystem: The local subsystem basis containing state information.
        """
        responses: List[float] = subsystem.get_Responses_Unscaled()  # unscaled values
        scalers: List[ScalerBasis] = subsystem.get_Scalers()
        ################################################################
        ###          USER CODE: Local objective                      ###
        ################################################################
        # compute local objective, if no local objective simply set localobjective = 0.0

        localobjective_unscaled = -1.5079 * responses[0] * (responses[4]**2) + 7.477 * (responses[4]**3) \
                                    + 0.7854 * responses[3] * (responses[4]**2)

        # scale the local objective function evaluation
        localobjective = scalers[5].transform(localobjective_unscaled)

        # localobjective needs to be a scaled01 quantity
        ################################################################
        ###          END USER CODE                                   ###
        ################################################################        
        subsystem.set_LocalObjectiveValue(localobjective)

    def evaluate_Gradient_LocalObjective(self, subsystem: LocalSubSystemBasis) -> None:
        """Evaluate the gradient of the local objective function.

        Args:
            subsystem: The local subsystem basis containing state information.
        """
        # === Tutorial: scaled <-> unscaled gradients (chain rule) ==================
        # The optimizer works in SCALED [0, 1] design-variable space, so this gradient
        # must be d(scaled objective) / d(scaled design variables). Every affine scaler
        # has a constant slope get_scale() = d(scaled)/d(unscaled). The objective is
        # written in RESPONSES r = [x1, x2, x3, x4, x6], but the design variables are
        # ordered [x4, x6, x1, x2, x3] (see Analysis1), so we differentiate w.r.t. the
        # responses, gather into design-variable order via dv_to_resp, then convert:
        #     df_s/ds_j = get_scale(obj) * df/dx_u[j] / get_scale(dv_scaler_j)
        # Return None instead to let the framework fall back to finite differences.
        # ===========================================================================

        responses: List[float] = subsystem.get_Responses_Unscaled()  # unscaled values
        scalers: List[ScalerBasis] = subsystem.get_Scalers()
        des_var: List[float] = subsystem.get_DesignVariables_Unscaled()  # unscaled values
        ################################################################
        ###          USER CODE: Gradient of local objective          ###
        ################################################################
        # Local objective (unscaled): f = -1.5079*x1*x6^2 + 7.477*x6^3 + 0.7854*x4*x6^2
        # Responses r = [x1, x2, x3, x4, x6]; design variables [x4, x6, x1, x2, x3].
        dv_to_resp: List[int] = [3, 4, 0, 1, 2]
        r0, r3, r4 = responses[0], responses[3], responses[4]  # x1, x4, x6

        c1, c2, c3 = -1.5079, 7.477, 0.7854
        grad_resp: List[float] = [
            c1 * r4**2,                                     # df/dx1
            0.0,                                            # df/dx2
            0.0,                                            # df/dx3
            c3 * r4**2,                                     # df/dx4
            2.0 * c1 * r0 * r4 + 3.0 * c2 * r4**2 + 2.0 * c3 * r3 * r4,  # df/dx6
        ]

        # Gather into DESIGN-VARIABLE order, then chain-rule into SCALED space:
        scale_obj = scalers[5].get_scale()
        n_dv = len(des_var)
        gradient: List[float] = [scale_obj * grad_resp[dv_to_resp[j]] / scalers[j].get_scale()
                                 for j in range(n_dv)]

        # gradient must be a scaled01 quantity (w.r.t. scaled01 design variables)
        ################################################################
        ###          END USER CODE                                   ###
        ################################################################
        return gradient

    def evaluate_Hessian_LocalObjective(self, subsystem: LocalSubSystemBasis) -> None:
        """Evaluate the Hessian of the local objective function.

        Args:
            subsystem: The local subsystem basis containing state information.
        """
        # === Tutorial: scaled <-> unscaled Hessian (chain rule, 2nd order) =========
        # The optimizer works in SCALED [0, 1] space, so this Hessian must be
        # d^2(scaled objective) / d(scaled design vars)^2. Each affine scaler has a
        # constant slope get_scale() = d(scaled)/d(unscaled). We build the Hessian in
        # RESPONSE space, gather into design-variable order, then convert element-wise:
        #     d2f_s/ds_a ds_b = get_scale(obj) * d2f/dx_a dx_b
        #                       / (get_scale(x_a) * get_scale(x_b))
        # Return None instead to let the framework fall back to finite differences.
        # ===========================================================================

        responses: List[float] = subsystem.get_Responses_Unscaled()  # unscaled values
        scalers: List[ScalerBasis] = subsystem.get_Scalers()
        des_var: List[float] = subsystem.get_DesignVariables_Unscaled()  # unscaled values
        ################################################################
        ###          USER CODE: Hessian of local objective           ###
        ################################################################
        # f = -1.5079*x1*x6^2 + 7.477*x6^3 + 0.7854*x4*x6^2; r = [x1, x2, x3, x4, x6].
        dv_to_resp: List[int] = [3, 4, 0, 1, 2]
        r0, r3, r4 = responses[0], responses[3], responses[4]  # x1, x4, x6

        c1, c2, c3 = -1.5079, 7.477, 0.7854
        n_r = 5
        Hr: List[List[float]] = [[0.0 for _ in range(n_r)] for _ in range(n_r)]
        Hr[0][4] = Hr[4][0] = 2.0 * c1 * r4                       # d2f/dx1 dx6
        Hr[3][4] = Hr[4][3] = 2.0 * c3 * r4                       # d2f/dx4 dx6
        Hr[4][4] = 2.0 * c1 * r0 + 6.0 * c2 * r4 + 2.0 * c3 * r3  # d2f/dx6^2

        # Gather into DESIGN-VARIABLE order, then chain-rule into SCALED space:
        scale_obj = scalers[5].get_scale()
        n_dv = len(des_var)
        hessian: List[List[float]] = [
            [scale_obj * Hr[dv_to_resp[a]][dv_to_resp[b]] / (scalers[a].get_scale() * scalers[b].get_scale())
             for b in range(n_dv)]
            for a in range(n_dv)
        ]

        # hessian must be a scaled01 quantity (w.r.t. scaled01 design variables)
        ################################################################
        ###          END USER CODE                                   ###
        ################################################################
        return hessian