code: def make_ica_funs(observed_dimension, latent_dimension):
      
  
      def sample(weights, n_samples, noise_std, rs):
          latents = rs.randn(latent_dimension, n_samples)
          latents = np.array(sorted(latents.T, key=lambda a_entry: a_entry[0])).T
          noise = rs.randn(n_samples, observed_dimension) * noise_std
          observed = predict(weights, latents) + noise
          return latents, observed
  
      def predict(weights, latents):
          return np.dot(weights, latents).T
  
      def logprob(weights, latents, noise_std, observed):
          preds = predict(weights, latents)
          log_lik = np.sum(t.logpdf(preds, 2.4, observed, noise_std))
          return log_lik
  
      num_weights = observed_dimension * latent_dimension
  
      def unpack_weights(weights):
          return np.reshape(weights, (observed_dimension, latent_dimension))
  
      return num_weights, sample, logprob, unpack_weights
docs: These functions implement independent component analysis.
  
      The model is:
      latents are drawn i.i.d. for each data point from a product of student-ts.
      weights are the same across all datapoints.
      each data = latents * weghts + noise.