Project: s41467-025-60878-z | Function: solve_homogeneous_from_second_order (python)

system: You are an expert Python programmer implementing algorithms from scientific papers.
Complete the masked function based on the algorithm description.
Follow Python best practices and maintain consistent code style.
Return **only** the complete Python function code.

user: Complete this Python function:

**DESCRIPTION:** Solves the second-order damped oscillator system by converting it to first-order form and computing eigenvalues/eigenvectors. This implements the mathematical transformation from Mx'' + Dx' + Kx = 0 to state-space form for analyzing oscillatory systems.

**KEY TERMS:** {
  "M": "mass matrix",
  "K": "stiffness matrix",
  "D": "damping matrix",
  "inv_A_B": "state matrix for eigenvalue analysis",
  "d": "eigenvalues",
  "T": "eigenvectors"
}

**CONTEXT CODE:**         x, _ = compute_batched_system(self.eigen_val, self.eigen_vec, self.init_positions[index][np.newaxis, ...], self.t_array)
        # get real values
        x = np.real(x[0])
        inputs = x[:-1]
        targets = x[1:]
        mask_idx = math.floor(inputs.shape[0]*self.mask_first_n_percent)
        block_idx = torch.ones((inputs.shape[0],), dtype=torch.int16)
        block_idx[:mask_idx] = int(0)
        
        if self.transform is not None:
            inputs = self.transform(inputs)
        if self.target_transform is not None:
            targets = self.target_transform(targets)
        if self.get_metadata:
            return inputs, targets, block_idx, {}
        else:
            return inputs, targets, block_idx
        
    def __len__(self):
        return self.num_sample

def create_damp_oscilator_system(
    mass_coef: np.ndarray, spring_coef: np.ndarray, damping_coef: np.ndarray
):
    # pre-cond: spring_coef and damping_coef should be equal in size
    n_dof = len(mass_coef)
    n_spring = len(spring_coef)
    n_damping = len(damping_coef)
    if n_spring != n_damping:
        raise AttributeError(
            "The number of springs should be the same as the number of dampers"
        )
    if n_dof >= n_spring:
        raise AttributeError(
            f"not enougth spring for {n_dof}-DOF, if you don't want the system to be right attached add a spring with 0 coefficient"
        )
    M = np.eye(n_dof, n_dof) * mass_coef

    K_ii = np.eye(n_dof, n_dof) * (spring_coef[:-1] + spring_coef[1:])
    D_ii = np.eye(n_dof, n_dof) * (damping_coef[:-1] + damping_coef[1:])
    K_ld = -np.eye(n_dof, n_dof, -1) * spring_coef[:-1][..., np.newaxis]
    K_ud = -np.eye(n_dof, n_dof, 1) * spring_coef[1:][..., np.newaxis]
    D_ld = -np.eye(n_dof, n_dof, -1) * damping_coef[:-1][..., np.newaxis]
    D_ud = -np.eye(n_dof, n_dof, 1) * damping_coef[1:][..., np.newaxis]

    K = K_ld + K_ii + K_ud
    D = D_ld + D_ii + D_ud
    return M, K, D


def solve_homogeneous_from_second_order(M: np.ndarray, K: np.ndarray, D: np.ndarray):
    """
    Solves the second-order damped oscillator system by converting it to first-order form and
    computing eigenvalues/eigenvectors. This implements the mathematical transformation from
    Mx'' + Dx' + Kx = 0 to state-space form for analyzing oscillatory systems.
    """
    # MASKED: Solves second-order damped oscillator system by converting to first-order state-space form and computing eigenvalues/eigenvectors for mathematical transformation from Mx'' + Dx' + Kx = 0 for oscillatory system analysis implementation
    raise NotImplementedError("This function is masked for testing purposes.")


def compute_batched_system(
    d: np.ndarray, T: np.ndarray, y_0: np.ndarray, t_array: np.ndarray
):
    # (n, 1)
    inv_T = np.linalg.inv(T)

    b = y_0 @ inv_T
    b = np.expand_dims(b, 1)
    t_array = np.expand_dims(t_array, (0, 2))
    # row            col
    # d = (1, n) * (T, 1)
    # (T, n) * ()

    exp_d_t = np.exp(d * t_array)
    exp_d_t_b = exp_d_t * b

    y = exp_d_t_b @ T.T
    n_ddof = len(d) // 2
    x, v = y[..., :n_ddof], y[..., n_ddof:]
    return x, v

class OscilatorTask(pl.LightningDataModule):
    """
    Create data module for the damped oscilator system,
    the parameter of the system are selected randomly 
    but fixed for the trial.
    The objective is to predict the next position.
    The generalization is determined by correct prediction from
    unseen initial conditions.
    max_freq determined the maximum frequency that you want to represent
    sensible value would max_freq > 2*pi*sqrt(max(K)/min(M)), 
    the maximal possible harmonic frequency of one spring-mass system in isolation.
    where K and M are the spring and mass coefficients.
    
    
    """
    def __init__(self,
                 n_dof: int,
                 mass_range: tuple[int, int],
                 spring_range: tuple[int, int],
                 damping_range: tuple[int, int],
                 max_freq: float,
                 duration: float,
                 num_sample: int,
                 batch_size: int = 32,
                 num_workers: int = 1,
                 fits_into_ram: bool = False,
                 name: str = None, # for hydra
                 required_model_size: str=None, # for hydra
                 num_classes: int = 0,
                 random_initial_velocity: bool = False,
                 mask_first_n_percent: float = 0.0,
                 ) -> None:
        super().__init__()
        self.n_dof = n_dof
        self.batch_size = batch_size
        self.num_workers = num_workers
        self.fits_into_ram = fits_into_ram
        self.collate_fn = PadTensors()
        # used for the random position signal task
        # the transformation will add zeros at the right side
        # of a length based on the original size of the data-sample
        
        def to_tensor(x):
            return torch.from_numpy(x).float()
        
        transform_list = [to_tensor,]
        
        self.static_data_transform = tonic.transforms.Compose(
            transform_list)  # noqa
        self.gen_dataset = DampedOscilatorWithFixedParameters(
            n_dof=n_dof,
            mass_coeff_range=mass_range,
            spring_coeff_range=spring_range,
            damping_coeff_range=damping_range,
            max_freq=max_freq,
            duration=duration,
            num_sample=num_sample,
            transform=self.static_data_transform,
            target_transform=self.static_data_transform,
            random_initial_velocity=random_initial_velocity,
            mask_first_n_percent=mask_first_n_percent
        )
        self.train_dataset_, self.valid_dataset_, self.test_dataset_ = torch.utils.data.random_split(
            self.gen_dataset,
            [
                0.8,
                0.1,
                0.1,                   
            ],


Complete the function implementation replacing "raise NotImplementedError".