code: def build_constrained_seasonal_transition_noise(
      drift_scale, num_seasons, is_last_day_of_season
  ):
      """Build transition noise distribution for a ConstrainedSeasonalSSM."""
  
      # Conceptually, this method takes the noise covariance on effects L @ L'
      # computed by `build_seasonal_transition_noise`, with scale factor
      #       L = [ 0, 0, ..., 0
      #             ...
      #             0, 0, ..., drift_scale],
      # and transforms it to act on the constrained-residual representation.
      #
      # The resulting noise covariance M @ M' is equivalent to
      #    M @ M' = effects_to_residuals @ LL' @ residuals_to_effects
      # where `@` is matrix multiplication. However because this matrix is
      # rank-deficient, we can't take its Cholesky decomposition directly, so we'll
      # construct its lower-triangular scale factor `M` by hand instead.
      #
      # Concretely, let `M = P @ R @ L` be the scale factor in the
      # transformed space, with matrices `R`, `P` applying the reparameterization
      # and zero-mean constraint respectively as defined in the
      # "Mathematical Details" section of `ConstrainedSeasonalStateSpaceModel`. It's
      # easy to see (*) that the implied covariance
      # `M @ M' = P @ R @ L @ L' @ R' @ P'` is just the constant matrix
      #  `M @ M' = [ 1, 1, ..., 1, 0
      #              1, 1, ..., 1, 0
      #              ...
      #              1, 1, ..., 1, 0
      #              0, 0, ..., 0, 0] * (drift_scale / num_seasons)**2`
      # with zeros in the final row and column. So we can directly construct
      # the lower-triangular factor
      #  `Q = [ 1, 0, ...  0
      #         1, 0, ..., 0
      #         ...
      #         1, 0, ..., 0
      #         0, 0, ..., 0 ] * drift_scale/num_seasons`
      # such that Q @ Q' = M @ M'. In practice, we don't reify the final row and
      # column full of zeroes, i.e., we construct
      # `Q[:num_seasons-1, :num_seasons-1]` as the scale-TriL covariance factor.
      #
      # (*) Argument: `L` is zero everywhere but the last column, so `R @ L` will be
      # too. Since the last column of `R` is the constant `-1/num_seasons`, `R @ L`
      # is simply the matrix with constant `-drift_scale/num_seasons` in the final
      # column (except the final row, which is negated) and zero in all other
      # columns, and `M = P @ R @ L` additionally zeroes out the final row. Then
      # M @ M' is just the outer product of that final column with itself (since all
      # other columns are zero), which gives the matrix shown above.
  
      drift_scale_tril_nonzeros = tf.concat(
          [
              tf.ones([num_seasons - 1, 1], dtype=drift_scale.dtype),
              tf.zeros(
                  [num_seasons - 1, num_seasons - 2],
                  dtype=drift_scale.dtype,
              ),
          ],
          axis=-1,
      )
      drift_scale_tril = (
          drift_scale_tril_nonzeros
          * drift_scale[..., tf.newaxis, tf.newaxis]
          / num_seasons
      )
  
      # Inject transition noise iff it is the last day of the season.
      def seasonal_transition_noise(t):
          noise_scale_tril = dist_util.pick_scalar_condition(
              is_last_day_of_season(t),
              drift_scale_tril,
              tf.zeros_like(drift_scale_tril),
          )
          return mvn_tril.MultivariateNormalTriL(
              loc=tf.zeros(num_seasons - 1, dtype=drift_scale.dtype),
              scale_tril=noise_scale_tril,
          )
  
      return seasonal_transition_noise
  
imports: import numpy
  import tensorflow.compat.v2
  from tensorflow_probability.python.bijectors import chain
  from tensorflow_probability.python.bijectors import invert
  from tensorflow_probability.python.bijectors import scale
  from tensorflow_probability.python.bijectors import softplus
  from tensorflow_probability.python.bijectors import square
  from tensorflow_probability.python.distributions import inverse_gamma
  from tensorflow_probability.python.distributions import linear_gaussian_ssm
  from tensorflow_probability.python.distributions import lognormal
  from tensorflow_probability.python.distributions import mvn_diag
  from tensorflow_probability.python.distributions import mvn_tril
  from tensorflow_probability.python.distributions import normal
  from tensorflow_probability.python.distributions import transformed_distribution
  from tensorflow_probability.python.experimental.sts_gibbs import sample_parameters
  from tensorflow_probability.python.internal import distribution_util
  from tensorflow_probability.python.internal import docstring_util
  from tensorflow_probability.python.internal import dtype_util
  from tensorflow_probability.python.internal import prefer_static
  from tensorflow_probability.python.sts.internal import util
  from tensorflow_probability.python.sts.structural_time_series import Parameter
  from tensorflow_probability.python.sts.structural_time_series import StructuralTimeSeries
label_desc: Use image node_id 5 for calling a global function with example usage: build_constrained_seasonal_transition_noise(drift_scale, num_seasons, is_last_day_of_season) and returns: seasonal_transition_noise, mvn_tril