Skip to content

← Back to SubSysCouplingParametersBasis documentation

SubSysCouplingParametersBasis - Source Code

File: Distributed_Design_Optimizer/subsystem/couplingparameters/SubSysCouplingParametersBasis.py

# Copyright (C) The DistributedDesignOptimizer Contributors
# Licensed under the GNU General Public License v3.0. See LICENSE file for details.
"""Subsystem coupling parameters basis module.

This module provides the base class for subsystem-level coupling
parameters in distributed optimization.
"""

import copy
from typing import List
import numpy as np
from Distributed_Design_Optimizer.postprocess.terminal_print_tools import DDO_Color, Reset, ddo_print
from Distributed_Design_Optimizer.subsystem.couplingparameters import CouplingParametersBasis
from Distributed_Design_Optimizer.subsystem.tools import update_state_listprimitive


class SubSysCouplingParametersBasis(CouplingParametersBasis):
    """Base class for subsystem-level coupling parameters between local subsystems.

    Stores the coupling variables, mapped responses, shared design variables,
    their copies from the neighboring subsystem, associated Jacobians, and
    index mappings into the subsystem's design variable vector. All scaled
    quantities use [0, 1] scaling.

    The storage is defined as follows::

                          subsystem 0
                      //                 \\

        mapped response 0 -> 1         coupling variable 1 -> 0


        coupling variable 0 -> 1         mapped response 1 -> 0

                      \\                  //
                          subsystem 1

    Consider that the responses are stored locally for subsystem 0. Then:
        - mapping responses from  0 -> 1 is called mappedresponses
        - coupling variables from 1 -> 0 is called couplingvariable
        - coupling responses from 0 -> 1 is called copy_couplingvariable
        - mapping responses from  1 -> 0 is called copy_mappedresponses
    """

    def __init__(self, id: str) -> None:
        """Initialize coupling parameters for a subsystem.

        Args:
            id: Identifier of the neighboring subsystem.
        """
        super().__init__(id)

        self._couplingvariable: List[float] | None = None  # scaled01 values
        self._mappedresponses: List[float] | None = None  # scaled01 values
        self._couplingvariable_unscaled: List[float] | None = None  # unscaled values
        self._mappedresponses_unscaled: List[float] | None = None  # unscaled values
        self._copy_couplingvariable: List[float] | None = None  # scaled01 values
        self._copy_mappedresponses: List[float] | None = None  # scaled01 values

        # Jacobian of the mapped response
        self._jacobian_mappedresponse: List[List[float | None]] | None = None
        self._copy_jacobian_mappedresponse: List[List[float | None]] | None = None

        self._shareddesignvariables: List[float] | None = None  # scaled01 values
        self._shareddesignvariables_unscaled: List[float] | None = None  # unscaled values
        self._copy_shareddesignvariables: List[float] | None = None  # scaled01 values
        self._targetshareddesignvariables: List[float] | None = None  # scaled01 values
        self._targetshareddesignvariables_unscaled: List[float] | None = None  # unscaled values
        self._copy_targetshareddesignvariables: List[float] | None = None  # scaled01 values

        self._couplingstrength: float | None = None

        # Indices of the design variables vector of the subsystem i that stores this object
        # that correspond to the coupling, shared and target shared variables of the 
        # i <-> j coupling
        self._indices_couplingvariables_in_designvariables: List[int] | None = None
        self._indices_shareddesignvariables_in_designvariables: List[int] | None = None
        self._indices_targetshareddesignvariables_in_designvariables: List[int] | None = None

        # Warning flags for out-of-range values (one-time warnings)
        self._mappedresponses_lower_scaler_bound_warning_raised: bool = False
        self._mappedresponses_upper_scaler_bound_warning_raised: bool = False
        self._couplingvariable_lower_scaler_bound_warning_raised: bool = False
        self._couplingvariable_upper_scaler_bound_warning_raised: bool = False
        self._shareddesignvariables_lower_scaler_bound_warning_raised: bool = False
        self._shareddesignvariables_upper_scaler_bound_warning_raised: bool = False
        self._targetshareddesignvariables_lower_scaler_bound_warning_raised: bool = False
        self._targetshareddesignvariables_upper_scaler_bound_warning_raised: bool = False

    def get_CouplingVariable(self) -> List[float] | None:
        """Returns the coupling variables prescribed by the subsystem for a single neighboring subsystem.

        Returns:
            Vector of coupling variables as scaled01 values.
        """
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        return copy.copy(self._couplingvariable)

    def set_CouplingVariable(self, couplingvariable: List[float]) -> None:
        """Stores a new value of the coupling variables.

        Args:
            couplingvariable: Coupling variables as scaled01 values.
        """
        cv_array = np.array(couplingvariable)
        # Check lower bound
        if not np.all((cv_array >= 0.0) | np.isclose(cv_array, 0.0, atol=1e-8, rtol=0)):
            if not self._couplingvariable_lower_scaler_bound_warning_raised:
                self._couplingvariable_lower_scaler_bound_warning_raised = True
                ddo_print("WARNING: All couplingvariable must be within the range [0.0, 1.0] (with tolerance 1e-8)")
        # Check upper bound
        if not np.all((cv_array <= 1.0) | np.isclose(cv_array, 1.0, atol=1e-8, rtol=0)):
            if not self._couplingvariable_upper_scaler_bound_warning_raised:
                self._couplingvariable_upper_scaler_bound_warning_raised = True
                ddo_print("WARNING: All couplingvariable must be within the range [0.0, 1.0] (with tolerance 1e-8)")
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        self._couplingvariable = copy.copy(couplingvariable)

    def get_MappedResponses(self) -> List[float] | None:
        """Returns the physically mapped responses from the subsystem onto the neighboring subsystem.

        Returns:
            Mapped responses as scaled01 values.
        """
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        return copy.copy(self._mappedresponses)

    def set_MappedResponses(self, varin: List[float]) -> None:
        """Stores the mapped responses from the subsystem onto the neighboring subsystem.

        Args:
            varin: Mapped responses as scaled01 values.
        """
        mr_array = np.array(varin)
        # Check lower bound
        if not np.all((mr_array >= 0.0) | np.isclose(mr_array, 0.0, atol=1e-8, rtol=0)):
            if not self._mappedresponses_lower_scaler_bound_warning_raised:
                self._mappedresponses_lower_scaler_bound_warning_raised = True
                ddo_print("WARNING: All mappedresponses must be within the range [0.0, 1.0] (with tolerance 1e-8)")
        # Check upper bound
        if not np.all((mr_array <= 1.0) | np.isclose(mr_array, 1.0, atol=1e-8, rtol=0)):
            if not self._mappedresponses_upper_scaler_bound_warning_raised:
                self._mappedresponses_upper_scaler_bound_warning_raised = True
                ddo_print("WARNING: All mappedresponses must be within the range [0.0, 1.0] (with tolerance 1e-8)")
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        self._mappedresponses = copy.copy(varin)

    def set_CouplingVariable_Unscaled(self, couplingvariable: List[float]) -> None:
        """Stores the unscaled coupling variables.

        Note: No getter method is provided for unscaled values to prevent coordination
        methods from using unscaled values for coordination. Coordination should always
        use scaled [0,1] values to ensure numerical stability and consistency.

        Args:
            couplingvariable: Coupling variables as unscaled values.
        """
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        self._couplingvariable_unscaled = copy.copy(couplingvariable)    

    def set_MappedResponses_Unscaled(self, varin: List[float]) -> None:
        """Stores the unscaled mapped responses.

        Note: No getter method is provided for unscaled values to prevent coordination
        methods from using unscaled values for coordination. Coordination should always
        use scaled [0,1] values to ensure numerical stability and consistency.

        Args:
            varin: Mapped responses as unscaled values.
        """
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        self._mappedresponses_unscaled = copy.copy(varin)

    def get_Copy_CouplingVariable(self) -> List[float] | None:
        """Gets the stored copy of the coupling variable of a neighboring subsystem.

        Returns:
            Copy of the coupling variable as scaled01 values.
        """
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        return copy.copy(self._copy_couplingvariable)

    def set_Copy_CouplingVariable(self, varin: List[float]) -> None:
        """Stores a copy of the coupling variable representing the physical coupling required by a neighboring subsystem.

        Args:
            varin: Copy of coupling variable as scaled01 values.
        """
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        self._copy_couplingvariable = copy.copy(varin)

    def get_Copy_MappedResponses(self) -> List[float] | None:
        """Gets the stored copy of the physical responses mapped onto this subsystem.

        Returns:
            Copy of mapped responses as scaled01 values.
        """
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        return copy.copy(self._copy_mappedresponses)

    def set_Copy_MappedResponses(self, varin: List[float]) -> None:
        """Stores a copy of the mapped physical responses of a neighboring subsystem onto this subsystem.

        Args:
            varin: Copy of mapped responses as scaled01 values.
        """
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        self._copy_mappedresponses = copy.copy(varin)

    def get_Jacobian_MappedResponse(self) -> List[List[float | None]] | None:
        """Returns the stored Jacobian of the mapped responses, possibly with missing values.

        Returns:
            The Jacobian matrix, or None if not set.
        """
        # copy.deepcopy() used - List[List[float | None]] is mutable (nested list). This prevents modifications
        # in the caller from being reflected back to the class attribute.
        return copy.deepcopy(self._jacobian_mappedresponse)

    def set_Jacobian_MappedResponse(self, jacobian_mappedresponse_in: List[List[float] | None]) -> None:
        """Sets the Jacobian of the mapped response from this subsystem's coupling.

        The Jacobian should have no missing values
        (or does not exist, if there are no mapped responses).

        Args:
            jacobian_mappedresponse_in: The Jacobian matrix to set.
        """

        # Check if there exist mapped responses in this coupling
        if self.get_MappedResponses() is None:

            # Since there are no mapped responses, there should also be no Jacobian
            raise ValueError(f"{DDO_Color}There exist no mapped responses in the coupling associated with ID {self.get_ID()} that you provided; hence, no Jacobian of the mapped responses{Reset}")

        # copy.deepcopy() used - List[List[float]] is mutable (nested list). This prevents modifications
        # in the caller from being reflected back to the class attribute.
        self._jacobian_mappedresponse = copy.deepcopy(jacobian_mappedresponse_in)

    def set_Jacobian_MappedResponse_at_Position(self, i: int, j: int, jacobian_mappedresponse_point: float) -> None:
        """Sets value 'jacobian_mappedresponse_point' to the Jacobian at index (i,j).

        Args:
            i: Row index of the Jacobian matrix.
            j: Column index of the Jacobian matrix.
            jacobian_mappedresponse_point: The value to set at position (i,j).
        """
        # No copy.copy() needed - float is a primitive/immutable type.
        # Assigning the input value directly is safe because any subsequent changes
        # to the caller's variable cannot affect this class's attribute.
        self._jacobian_mappedresponse[i][j] = jacobian_mappedresponse_point

    def get_Copy_Jacobian_MappedResponse(self) -> List[List[float]] | None:
        """Returns the Jacobian of the mapped response from the other subsystem's coupling.

        The Jacobian should have no missing values
        (or does not exist, if there are no mapped responses).

        Returns:
            The copy Jacobian matrix, or None if not set.
        """
        # copy.deepcopy() used - List[List[float]] is mutable (nested list). This prevents modifications
        # in the caller from being reflected back to the class attribute.
        return copy.deepcopy(self._copy_jacobian_mappedresponse)

    def set_Copy_Jacobian_MappedResponse(self, copy_jacobian_mappedresponse_in: List[List[float] | None]) -> None:
        """Sets the Jacobian of the mapped response from the neighboring subsystem's coupling.

        The Jacobian should have no missing values
        (or does not exist, if there are no mapped responses).

        Args:
            copy_jacobian_mappedresponse_in: The copy Jacobian matrix to set.
        """

        # Check if there exist copy mapped responses in this coupling
        if self.get_Copy_MappedResponses() is None:

            # Since there are no copy mapped responses, there should also be no Jacobian
            raise ValueError(f"{DDO_Color}There exist no copy mapped responses in the coupling associated with ID {self.get_ID()} that you provided; hence, no Jacobian of the mapped responses{Reset}")

        # copy.deepcopy() used - List[List[float]] is mutable (nested list). This prevents modifications
        # in the caller from being reflected back to the class attribute.
        self._copy_jacobian_mappedresponse = copy.deepcopy(copy_jacobian_mappedresponse_in)

    def set_SharedDesignVariables(self, variablesin: List[float]) -> None:
        """Sets the shared design variables which are shared with the neighboring subsystem.

        Args:
            variablesin: Shared design variables as scaled01 values.
        """
        sdv_array = np.array(variablesin)
        # Check lower bound
        if not np.all((sdv_array >= 0.0) | np.isclose(sdv_array, 0.0, atol=1e-8, rtol=0)):
            if not self._shareddesignvariables_lower_scaler_bound_warning_raised:
                self._shareddesignvariables_lower_scaler_bound_warning_raised = True
                ddo_print("WARNING: All shareddesignvariables must be within the range [0.0, 1.0] (with tolerance 1e-8)")
        # Check upper bound
        if not np.all((sdv_array <= 1.0) | np.isclose(sdv_array, 1.0, atol=1e-8, rtol=0)):
            if not self._shareddesignvariables_upper_scaler_bound_warning_raised:
                self._shareddesignvariables_upper_scaler_bound_warning_raised = True
                ddo_print("WARNING: All shareddesignvariables must be within the range [0.0, 1.0] (with tolerance 1e-8)")
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        self._shareddesignvariables = copy.copy(variablesin)

    def get_SharedDesignVariables(self) -> List[float] | None:
        """Gets the shared design variables.

        Returns:
            Shared design variables as scaled01 values.
        """
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        return copy.copy(self._shareddesignvariables)

    def set_SharedDesignVariables_Unscaled(self, variablesin: List[float]) -> None:
        """Stores the unscaled shared design variables.

        Note: No getter method is provided for unscaled values to prevent coordination
        methods from using unscaled values for coordination. Coordination should always
        use scaled [0,1] values to ensure numerical stability and consistency.

        Args:
            variablesin: Shared design variables as unscaled values.
        """
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        self._shareddesignvariables_unscaled = copy.copy(variablesin)

    def set_Copy_SharedDesignVariables(self, copy_ofvariablesin: List[float]) -> None:
        """Sets a copy of the shared design variable value of the neighboring subsystem.

        Args:
            copy_ofvariablesin: Copy of shared design variables as scaled01 values.
        """
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        self._copy_shareddesignvariables = copy.copy(copy_ofvariablesin)

    def get_Copy_SharedDesignVariables(self) -> List[float] | None:
        """Gets the copy of the shared design variable of the neighboring subsystem.

        Returns:
            Copy of shared design variables as scaled01 values.
        """
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        return copy.copy(self._copy_shareddesignvariables)

    def set_TargetSharedDesignVariables(self, variablesin: List[float]) -> None:
        """Sets the target shared design variables which are shared with the neighboring subsystem.

        Args:
            variablesin: Target shared design variables as scaled01 values.
        """
        tsdv_array = np.array(variablesin)
        # Check lower bound
        if not np.all((tsdv_array >= 0.0) | np.isclose(tsdv_array, 0.0, atol=1e-8, rtol=0)):
            if not self._targetshareddesignvariables_lower_scaler_bound_warning_raised:
                self._targetshareddesignvariables_lower_scaler_bound_warning_raised = True
                ddo_print("WARNING: All targetshareddesignvariables must be within the range [0.0, 1.0] (with tolerance 1e-8)")
        # Check upper bound
        if not np.all((tsdv_array <= 1.0) | np.isclose(tsdv_array, 1.0, atol=1e-8, rtol=0)):
            if not self._targetshareddesignvariables_upper_scaler_bound_warning_raised:
                self._targetshareddesignvariables_upper_scaler_bound_warning_raised = True
                ddo_print("WARNING: All targetshareddesignvariables must be within the range [0.0, 1.0] (with tolerance 1e-8)")
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        self._targetshareddesignvariables = copy.copy(variablesin)

    def get_TargetSharedDesignVariables(self) -> List[float] | None:
        """Gets the target shared design variables.

        Returns:
            Target shared design variables as scaled01 values.
        """
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        return copy.copy(self._targetshareddesignvariables)

    def set_TargetSharedDesignVariables_Unscaled(self, variablesin: List[float]) -> None:
        """Stores the unscaled target shared design variables.

        Note: No getter method is provided for unscaled values to prevent coordination
        methods from using unscaled values for coordination. Coordination should always
        use scaled [0,1] values to ensure numerical stability and consistency.

        Args:
            variablesin: Target shared design variables as unscaled values.
        """
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        self._targetshareddesignvariables_unscaled = copy.copy(variablesin)

    def set_Copy_TargetSharedDesignVariables(self, copy_ofvariablesin: List[float]) -> None:
        """Sets a copy of the shared design variable value of the neighboring subsystem.

        Args:
            copy_ofvariablesin: Copy of target shared design variables as scaled01 values.
        """
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        self._copy_targetshareddesignvariables = copy.copy(copy_ofvariablesin)

    def get_Copy_TargetSharedDesignVariables(self) -> List[float] | None:
        """Gets the copy of the shared design variable of the neighboring subsystem.

        Returns:
            Copy of target shared design variables as scaled01 values.
        """
        # copy.copy() used - List[float] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        return copy.copy(self._copy_targetshareddesignvariables)

    def set_CouplingStrength(self, couplingstrengthIn: float) -> None:
        """Sets the coupling strength value.

        Args:
            couplingstrengthIn: The coupling strength value to set.
        """
        # No copy.copy() needed - float is a primitive/immutable type.
        # Assigning the input value directly is safe because any subsequent changes
        # to the caller's variable cannot affect this class's attribute.
        self._couplingstrength = couplingstrengthIn

    def get_CouplingStrength(self) -> float | None:
        """Gets the coupling strength value.

        Returns:
            The coupling strength value, or None if not set.
        """
        # No copy.copy() needed - float is a primitive/immutable type.
        # Assigning this return value to a variable in the caller creates a new binding;
        # modifications there won't affect this class's attribute.
        return self._couplingstrength

    def set_Indices_CouplingVariables_In_DesignVariables(self, indices_couplingvariables_in_designvariables_in: List[int]) -> None:
        """Sets the indices of coupling variables within the design variables vector.

        Args:
            indices_couplingvariables_in_designvariables_in: Indices of the coupling variables
                in the designvariables of subsystem i.
        """
        # copy.copy() used - List[int] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        self._indices_couplingvariables_in_designvariables = copy.copy(indices_couplingvariables_in_designvariables_in)

    def get_Indices_CouplingVariables_In_DesignVariables(self) -> List[int] | None:
        """Gets the indices of coupling variables within the design variables vector.

        Returns:
            Indices of the coupling variables in the designvariables of subsystem i,
            or None if not set.
        """
        # copy.copy() used - List[int] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        return copy.copy(self._indices_couplingvariables_in_designvariables)

    def set_Indices_SharedDesignVariables_In_DesignVariables(self, indices_shareddesignvariables_in_designvariables_in: List[int]) -> None:
        """Sets the indices of shared design variables within the design variables vector.

        Args:
            indices_shareddesignvariables_in_designvariables_in: Indices of the shared design
                variables in the designvariables of subsystem i.
        """
        # copy.copy() used - List[int] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        self._indices_shareddesignvariables_in_designvariables = copy.copy(indices_shareddesignvariables_in_designvariables_in)

    def get_Indices_SharedDesignVariables_In_DesignVariables(self) -> List[int] | None:
        """Gets the indices of shared design variables within the design variables vector.

        Returns:
            Indices of the shared design variables in the designvariables of subsystem i,
            or None if not set.
        """
        # copy.copy() used - List[int] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        return copy.copy(self._indices_shareddesignvariables_in_designvariables)

    def set_Indices_TargetSharedDesignVariables_In_DesignVariables(self, indices_targetshareddesignvariables_in_designvariables_in: List[int]) -> None:
        """Sets the indices of target shared design variables within the design variables vector.

        Args:
            indices_targetshareddesignvariables_in_designvariables_in: Indices of the target shared
                design variables in the designvariables of subsystem i.
        """
        # copy.copy() used - List[int] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        self._indices_targetshareddesignvariables_in_designvariables = copy.copy(indices_targetshareddesignvariables_in_designvariables_in)

    def get_Indices_TargetSharedDesignVariables_In_DesignVariables(self) -> List[int] | None:
        """Gets the indices of target shared design variables within the design variables vector.

        Returns:
            Indices of the target shared design variables in the designvariables
            of subsystem i, or None if not set.
        """
        # copy.copy() used - List[int] is mutable. This prevents modifications
        # in the caller from being reflected back to the class attribute.
        return copy.copy(self._indices_targetshareddesignvariables_in_designvariables)   

    def update_state(self, other_coupling: 'SubSysCouplingParametersBasis') -> None:
        """Update the state of this SubSysCouplingParametersBasis with values from another instance.

        This method is necessary for multiprocessing. When subsystems are executed in parallel
        using multiprocessing.Pool, they are serialized and deserialized, creating new objects
        in separate memory spaces. After parallel execution completes, this method updates
        the original object's attribute values while preserving their memory addresses.

        The update preserves memory addresses by modifying list contents in-place rather than
        reassigning references. This is essential for maintaining object identity across the
        multiprocessing boundary.

        Args:
            other_coupling: The source SubSysCouplingParametersBasis containing updated values
                from parallel execution.
        """
        # Call the base class update_state to handle inherited attributes (_id from CouplingParametersBasis)
        super().update_state(other_coupling=other_coupling)

        # Coupling variables and mapped responses (scaled01)
        self._couplingvariable: List[float] | None = update_state_listprimitive(self._couplingvariable, other_coupling.get_CouplingVariable())
        self._mappedresponses: List[float] | None = update_state_listprimitive(self._mappedresponses, other_coupling.get_MappedResponses())

        # Coupling variables and mapped responses (unscaled)
        # Note: Since we dont define getter methods, we have to directly access the private variables from the other couplings
        self._couplingvariable_unscaled: List[float] | None = update_state_listprimitive(self._couplingvariable_unscaled, other_coupling._couplingvariable_unscaled)
        self._mappedresponses_unscaled: List[float] | None = update_state_listprimitive(self._mappedresponses_unscaled, other_coupling._mappedresponses_unscaled)

        # Copy coupling variables and mapped responses
        self._copy_couplingvariable: List[float] | None = update_state_listprimitive(self._copy_couplingvariable, other_coupling.get_Copy_CouplingVariable())
        self._copy_mappedresponses: List[float] | None = update_state_listprimitive(self._copy_mappedresponses, other_coupling.get_Copy_MappedResponses())

        # Handle List[List[float]] attributes - need to iterate and update each inner list
        other_jacobian_mappedresponse: List[List[float | None]] | None = (other_coupling.get_Jacobian_MappedResponse())
        if other_jacobian_mappedresponse is None:
            self._jacobian_mappedresponse: List[List[float | None]] | None = None
        else:
            if self._jacobian_mappedresponse is None:
                self._jacobian_mappedresponse = other_jacobian_mappedresponse
            else:
                for i in range(len(other_jacobian_mappedresponse)):
                    self._jacobian_mappedresponse[i] = update_state_listprimitive(self._jacobian_mappedresponse[i], other_jacobian_mappedresponse[i])

        other_copy_jacobian_mappedresponse: List[List[float | None]] | None = (other_coupling.get_Copy_Jacobian_MappedResponse())
        if other_copy_jacobian_mappedresponse is None:
            self._copy_jacobian_mappedresponse: List[List[float | None]] | None = None
        else:
            if self._copy_jacobian_mappedresponse is None:
                self._copy_jacobian_mappedresponse = other_copy_jacobian_mappedresponse
            else:
                for i in range(len(other_copy_jacobian_mappedresponse)):
                    self._copy_jacobian_mappedresponse[i] = update_state_listprimitive(self._copy_jacobian_mappedresponse[i], other_copy_jacobian_mappedresponse[i])

        # Shared design variables (scaled01, unscaled, copy)
        self._shareddesignvariables: List[float] | None = update_state_listprimitive(self._shareddesignvariables, other_coupling.get_SharedDesignVariables())
        self._shareddesignvariables_unscaled = update_state_listprimitive(self._shareddesignvariables_unscaled, other_coupling._shareddesignvariables_unscaled)
        self._copy_shareddesignvariables: List[float] | None = update_state_listprimitive(self._copy_shareddesignvariables, other_coupling.get_Copy_SharedDesignVariables())

        # Target shared design variables (scaled01, unscaled, copy)
        self._targetshareddesignvariables: List[float] | None = update_state_listprimitive(self._targetshareddesignvariables, other_coupling.get_TargetSharedDesignVariables())
        self._targetshareddesignvariables_unscaled = update_state_listprimitive(self._targetshareddesignvariables_unscaled, other_coupling._targetshareddesignvariables_unscaled)
        self._copy_targetshareddesignvariables: List[float] | None = update_state_listprimitive(self._copy_targetshareddesignvariables, other_coupling.get_Copy_TargetSharedDesignVariables())

        # Coupling strength (float - primitive, direct assignment)
        self._couplingstrength: float | None = other_coupling.get_CouplingStrength()

        # Indices
        self._indices_couplingvariables_in_designvariables: List[int] | None = update_state_listprimitive(self._indices_couplingvariables_in_designvariables, 
                                                                                                          other_coupling.get_Indices_CouplingVariables_In_DesignVariables())
        self._indices_shareddesignvariables_in_designvariables: List[int] | None = update_state_listprimitive(self._indices_shareddesignvariables_in_designvariables, 
                                                                                                              other_coupling.get_Indices_SharedDesignVariables_In_DesignVariables())
        self._indices_targetshareddesignvariables_in_designvariables: List[int] | None = update_state_listprimitive(self._indices_targetshareddesignvariables_in_designvariables, 
                                                                                                                    other_coupling.get_Indices_TargetSharedDesignVariables_In_DesignVariables())

        # Warning flags (bool primitives - direct assignment)
        self._mappedresponses_lower_scaler_bound_warning_raised: bool = other_coupling._mappedresponses_lower_scaler_bound_warning_raised
        self._mappedresponses_upper_scaler_bound_warning_raised: bool = other_coupling._mappedresponses_upper_scaler_bound_warning_raised
        self._couplingvariable_lower_scaler_bound_warning_raised: bool = other_coupling._couplingvariable_lower_scaler_bound_warning_raised
        self._couplingvariable_upper_scaler_bound_warning_raised: bool = other_coupling._couplingvariable_upper_scaler_bound_warning_raised
        self._shareddesignvariables_lower_scaler_bound_warning_raised: bool = other_coupling._shareddesignvariables_lower_scaler_bound_warning_raised
        self._shareddesignvariables_upper_scaler_bound_warning_raised: bool = other_coupling._shareddesignvariables_upper_scaler_bound_warning_raised
        self._targetshareddesignvariables_lower_scaler_bound_warning_raised: bool = other_coupling._targetshareddesignvariables_lower_scaler_bound_warning_raised
        self._targetshareddesignvariables_upper_scaler_bound_warning_raised: bool = other_coupling._targetshareddesignvariables_upper_scaler_bound_warning_raised