Commit cf0c8132 authored by Gaetano Scandariato's avatar Gaetano Scandariato
Browse files

Refactor GP kernel parameters from amplitude to standard deviation

- Updated kernel parameter names from `log_gp_amp` to `log_gp_sigma` across the codebase to reflect the change in terminology.
- Adjusted documentation to clarify that the first parameter for all kernels is now the standard deviation (sigma) instead of amplitude.
- Modified configuration examples and tests to use the new parameter naming convention.
- Ensured consistency in the handling of spectral mixture components and their associated parameters.
parent fd1f3f43
Loading
Loading
Loading
Loading
Loading
+95 −68
Changes for PyAETNA/kernels/factory.py: 95 added lines, 68 removed lines.
Original line number Diff line number Diff line
@@ -17,7 +17,7 @@ def build_single_kernel(kernel_type, param_values, subspace_idx=None, use_jax=Tr
    """
    Build a single tinygp kernel term from parameter values.
    
    All kernels require amplitude as the first parameter.
    All kernels require the standard deviation (sigma) as the first parameter.
    Parameters are passed in LINEAR scale (already exponentiated from log samples).
    
    Args:
@@ -31,63 +31,86 @@ def build_single_kernel(kernel_type, param_values, subspace_idx=None, use_jax=Tr
        tinygp kernel object.
    
    Supported kernel types:
        - 'ExpSquared' / 'RBF': Squared exponential, params: [amp, scale]
        - 'Matern32': Matern kernel with nu=3/2, params: [amp, scale]
        - 'Matern52': Matern kernel with nu=5/2, params: [amp, scale]
        - 'ExpSineSquared': Periodic kernel, params: [amp, freq, gamma]
        - 'ExpSquared' / 'RBF': Squared exponential, params: [sigma, scale]

        - 'Matern32': Matern kernel with nu=3/2, params: [sigma, scale]

        - 'Matern52': Matern kernel with nu=5/2, params: [sigma, scale]

        - 'ExpSineSquared': Periodic kernel, params: [sigma, freq, gamma]
                             freq is linear frequency, internally converted to period = 1/freq

        - 'Constant': Constant kernel, params: [const]
        - 'QuasiPeriodic': ExpSquared * ExpSineSquared, params: [amp, se_scale, freq, gamma]

        - 'QuasiPeriodic': ExpSquared * ExpSineSquared, params: [sigma, se_scale, freq, gamma]

        - 'SHO': Damped simple harmonic oscillator, params: [sigma, freq, lengthscale]
                 omega = 2π*freq, Q = omega*lengthscale/2
                 lengthscale is the damping timescale τ
                 
        - 'SHO_Quality': SHO with direct Q parametrization, params: [sigma, freq, quality]

        Note: The "sigma" parameter represents standard deviation (sigma), not amplitude.
                It is internally squared as sigma^2 for the kernel variance.

            """
    xp = jnp if use_jax else np
    kernel_type_lower = kernel_type.lower()
    kernel = None
    
    if kernel_type_lower in ('expsquared', 'rbf'):
        # params: [amp, scale]
        amp = param_values[0] if len(param_values) > 0 else 1.0
        # params: [sigma, scale]
        # Note: The parameter represents standard deviation (sigma), not amplitude.
        # It is internally squared as sigma^2 for the kernel variance.
        sigma = param_values[0] if len(param_values) > 0 else 1.0
        scale = param_values[1] if len(param_values) > 1 else 1.0
        kernel = amp**2 * kernels.ExpSquared(scale=scale)
        kernel = sigma**2 * kernels.ExpSquared(scale=scale)
    
    elif kernel_type_lower == 'matern32':
        # params: [amp, scale]
        # params: [sigma, scale]
        # Uses standard (dense) Matern32 for better GPU performance
        # For quasisep version (memory-efficient for large 1D), use 'Matern32_quasisep'
        amp = param_values[0] if len(param_values) > 0 else 1.0
        # Note: The parameter represents standard deviation (sigma), not amplitude.
        # It is internally squared as sigma^2 for the kernel variance.
        sigma = param_values[0] if len(param_values) > 0 else 1.0
        scale = param_values[1] if len(param_values) > 1 else 1.0
        kernel = amp**2 * kernels.Matern32(scale=scale)
        kernel = sigma**2 * kernels.Matern32(scale=scale)
    
    elif kernel_type_lower == 'matern32_quasisep':
        # params: [amp, scale]
        # params: [sigma, scale]
        # Quasiseparable version: memory-efficient for large 1D time series
        # May be slower on GPU than standard Matern32
        amp = param_values[0] if len(param_values) > 0 else 1.0
        # Note: The parameter represents standard deviation (sigma), not amplitude.
        # It is internally squared as sigma^2 for the kernel variance.
        sigma = param_values[0] if len(param_values) > 0 else 1.0
        scale = param_values[1] if len(param_values) > 1 else 1.0
        kernel = amp**2 * kernels.quasisep.Matern32(scale=scale)
        kernel = sigma**2 * kernels.quasisep.Matern32(scale=scale)

    elif kernel_type_lower == 'matern52':
        # params: [amp, scale]
        # params: [sigma, scale]
        # Uses standard (dense) Matern52 for better GPU performance
        # For quasisep version, use 'Matern52_quasisep'
        amp = param_values[0] if len(param_values) > 0 else 1.0
        # Note: The parameter represents standard deviation (sigma), not amplitude.
        # It is internally squared as sigma^2 for the kernel variance.
        sigma = param_values[0] if len(param_values) > 0 else 1.0
        scale = param_values[1] if len(param_values) > 1 else 1.0
        kernel = amp**2 * kernels.Matern52(scale=scale)
        kernel = sigma**2 * kernels.Matern52(scale=scale)
    
    elif kernel_type_lower == 'matern52_quasisep':
        # params: [amp, scale]
        # params: [sigma, scale]
        # Quasiseparable version: memory-efficient for large 1D time series
        amp = param_values[0] if len(param_values) > 0 else 1.0
        # Note: The parameter represents standard deviation (sigma), not amplitude.
        # It is internally squared as sigma^2 for the kernel variance.
        sigma = param_values[0] if len(param_values) > 0 else 1.0
        scale = param_values[1] if len(param_values) > 1 else 1.0
        kernel = amp**2 * kernels.quasisep.Matern52(scale=scale)
        kernel = sigma**2 * kernels.quasisep.Matern52(scale=scale)
    
    elif kernel_type_lower == 'expsinesquared':
        # params: [amp, freq, gamma]
        # params: [sigma, freq, gamma]
        # freq is linear frequency, kernel uses period = 1/freq
        amp = param_values[0] if len(param_values) > 0 else 1.0
        # Note: The parameter represents standard deviation (sigma), not amplitude.
        # It is internally squared as sigma^2 for the kernel variance.
        sigma = param_values[0] if len(param_values) > 0 else 1.0
        freq = param_values[1] if len(param_values) > 1 else 1.0
        gamma = param_values[2] if len(param_values) > 2 else 0.5
        period = 1.0 / freq
@@ -140,31 +163,35 @@ def build_single_kernel(kernel_type, param_values, subspace_idx=None, use_jax=Tr
        kernel = kernels.Constant(const_val)
    
    elif kernel_type_lower == 'quasiperiodic':
        # params: [amp, se_scale, freq, gamma]
        # params: [sigma, se_scale, freq, gamma]
        # freq is linear frequency, kernel uses period = 1/freq
        amp = param_values[0] if len(param_values) > 0 else 1.0
        # Note: The parameter represents standard deviation (sigma), not amplitude.
        # It is internally squared as sigma^2 for the kernel variance.
        sigma = param_values[0] if len(param_values) > 0 else 1.0
        se_scale = param_values[1] if len(param_values) > 1 else 1.0
        freq = param_values[2] if len(param_values) > 2 else 1.0
        gamma = param_values[3] if len(param_values) > 3 else 0.5
        period = 1.0 / freq
        kernel = amp**2 * kernels.ExpSquared(scale=se_scale) * kernels.ExpSineSquared(scale=period, gamma=gamma)
        kernel = sigma**2 * kernels.ExpSquared(scale=se_scale) * kernels.ExpSineSquared(scale=period, gamma=gamma)
    
    elif kernel_type_lower == 'spectralmixture':
        # For SpectralMixture, expect parameters to be provided in blocks:
        # [amp_0, ls_0, f_0, amp_1, ls_1, f_1, ..., amp_N, ls_N, f_N]
        # [sigma_0, ls_0, f_0, sigma_1, ls_1, f_1, ..., sigma_N, ls_N, f_N]
        # This is what PyAETNA's parameter expansion produces for lists
        # Note: Each sigma represents standard deviation (sigma), not amplitude.
        # It is internally squared as sigma^2 for the kernel variance.
        n_total = len(param_values)
        
        if n_total < 3:
            raise ValueError(
                f"SpectralMixture requires at least 3 parameters (amp, ls, freq). "
                f"SpectralMixture requires at least 3 parameters (sigma, ls, freq). "
                f"Received {n_total} parameter values."
            )
        
        # Check that we have a valid number of parameters for triplets
        if (n_total % 3) != 0:
            raise ValueError(
                f"SpectralMixture requires parameters in sets of three: [amp1, ls1, freq1, amp2, ls2, freq2, ...]. \n"
                f"SpectralMixture requires parameters in sets of three: [sigma1, ls1, freq1, sigma2, ls2, freq2, ...]. \n"
                f"Got {n_total} total parameter values. Cannot divide evenly into triplets."
            )
        
@@ -172,7 +199,7 @@ def build_single_kernel(kernel_type, param_values, subspace_idx=None, use_jax=Tr
        n_components = n_total // 3
        
        # Extract parameters by blocks (this now assumes all three parts are complete)
        amplitudes = xp.asarray(param_values[0::3], dtype=jnp.float32)
        sigmas = xp.asarray(param_values[0::3], dtype=jnp.float32)
        lengthscales = xp.asarray(param_values[1::3], dtype=jnp.float32)
        frequencies = xp.asarray(param_values[2::3], dtype=jnp.float32)

@@ -182,24 +209,24 @@ def build_single_kernel(kernel_type, param_values, subspace_idx=None, use_jax=Tr
        # to construct a spectral mixture kernel, which is both performant and compatible
        # with JAX tracing in MCMC contexts.

        n_components = len(amplitudes)
        n_components = len(sigmas)
        
        # Build first component - note: We're constructing a sum of products, 
        # not using kernel composition directly as in the tinygp approach.
        # The correct mathematical form needs to be:  sum_i (amp_i^2 * ExpSquared(scale=ls_i) * Cosine(scale=1/f_i))
        amp_0 = amplitudes[0]
        # The correct mathematical form needs to be:  sum_i (sigma_i^2 * ExpSquared(scale=ls_i) * Cosine(scale=1/f_i))
        sigma_0 = sigmas[0]
        ls_0 = lengthscales[0] 
        f_0 = frequencies[0]
        
        # Use simple approach first - sum of kernel products
        kernel = amp_0**2 * kernels.ExpSquared(scale=ls_0) * kernels.Cosine(scale=1.0/f_0)
        kernel = sigma_0**2 * kernels.ExpSquared(scale=ls_0) * kernels.Cosine(scale=1.0/f_0)
        
        # Add additional components (sum of all components)
        for i in range(1, n_components):
            amp_i = amplitudes[i]
            sigma_i = sigmas[i]
            ls_i = lengthscales[i]
            f_i = frequencies[i]
            kernel += amp_i**2 * kernels.ExpSquared(scale=ls_i) * kernels.Cosine(scale=1.0/f_i)
            kernel += sigma_i**2 * kernels.ExpSquared(scale=ls_i) * kernels.Cosine(scale=1.0/f_i)

    else:
        supported = "ExpSquared, RBF, Matern32, Matern32_quasisep, Matern52, Matern52_quasisep, ExpSineSquared, SHO, SHO_Quality, Constant, QuasiPeriodic, SpectralMixture"
@@ -368,17 +395,17 @@ def _build_spectral_mixture_params(params, spec, inst_suffix, section_name, per_
    param_names = spec.get('params', [])
    fixed_params = spec.get('fixed_params', {})
    
    # For SpectralMixture: identify components based on amplitude parameter expansion
    # For SpectralMixture: identify components based on sigma parameter expansion
    # Use cached component count if available (avoiding expensive recomputation)
    component_count = spec['cached_component_count']

    # Find amplitude and lengthscale parameters that define the number of components
    amp_param_names = []
    # Find sigma and lengthscale parameters that define the number of components
    sigma_param_names = []
    ls_param_names = []
    
    for p in param_names:
        if re.match(r'(log_)?(gp_)?amp', p) or re.match(r'(log_)?amp', p):
            amp_param_names.append(p)
        if re.match(r'(log_)?(gp_)?sigma', p) or re.match(r'(log_)?sigma', p):
            sigma_param_names.append(p)
        elif re.match(r'(log_)?(gp_)?lengthscale', p) or re.match(r'(log_)?lengthscale', p):
            ls_param_names.append(p)

@@ -436,13 +463,13 @@ def _build_spectral_mixture_params(params, spec, inst_suffix, section_name, per_
            param_structure[pname] = [val]
    
    # Now validate and build the structure properly for SpectralMixture
    # We expect amplitude and lengthscale parameters to be handled, frequency parameter may be scalar
    # We expect sigma and lengthscale parameters to be handled, frequency parameter may be scalar
    
    # Extract amplitudes 
    amplitudes = []
    for amp_name in amp_param_names:
        if amp_name in param_structure:
            amplitudes.extend(param_structure[amp_name])
    # Extract sigmas 
    sigmas = []
    for sigma_name in sigma_param_names:
        if sigma_name in param_structure:
            sigmas.extend(param_structure[sigma_name])
    
    # Get other parameter values
    # For lengthscale - handle broadcasting if needed to match component count
@@ -473,11 +500,11 @@ def _build_spectral_mixture_params(params, spec, inst_suffix, section_name, per_
            else:
                frequencies.extend(freq_vals)
    
    # Build final list: [amp0, ls0, f0, amp1, ls1, f1, ...]
    # Build final list: [sigma0, ls0, f0, sigma1, ls1, f1, ...]
    final_param_values = []
    for i in range(component_count):
        if amplitudes:
            final_param_values.append(amplitudes[i % len(amplitudes)])
        if sigmas:
            final_param_values.append(sigmas[i % len(sigmas)])
        if lengthscales:
            final_param_values.append(lengthscales[i % len(lengthscales)])
        if frequencies:
@@ -502,24 +529,24 @@ def _count_spectral_mixture_components(params, spec, inst_suffix, section_name,
    Returns:
        int: Number of components in the SpectralMixture kernel
    """
    # Find amplitude and lengthscale parameters that define the number of components
    amp_param_names = []
    # Find sigma and lengthscale parameters that define the number of components
    sigma_param_names = []
    ls_param_names = []
    
    param_names = spec.get('params', [])
    for p in param_names:
        if re.match(r'(log_)?(gp_)?amp', p) or re.match(r'(log_)?amp', p):
            amp_param_names.append(p)
        if re.match(r'(log_)?(gp_)?sigma', p) or re.match(r'(log_)?sigma', p):
            sigma_param_names.append(p)
        elif re.match(r'(log_)?(gp_)?lengthscale', p) or re.match(r'(log_)?lengthscale', p):
            ls_param_names.append(p)
    
    # Count amplitudes and lengthscales in the params dict
    amp_counts = []
    # Count sigmas and lengthscales in the params dict
    sigma_counts = []
    ls_counts = []
    
    for amp_name in amp_param_names:
    for sigma_name in sigma_param_names:
        # Determine suffix for this parameter  
        pname_scope = per_param_scopes.get(amp_name, default_scope)
        pname_scope = per_param_scopes.get(sigma_name, default_scope)
        if pname_scope == 'global':
            param_suffix = f'_{section_name}' if section_name else ''
        elif inst_suffix is not None:
@@ -527,9 +554,9 @@ def _count_spectral_mixture_components(params, spec, inst_suffix, section_name,
        else:
            param_suffix = f'_{section_name}' if section_name else ''
            
        full_pname = amp_name + param_suffix
        full_pname = sigma_name + param_suffix
        
        # Count indexed parameters for this amplitude
        # Count indexed parameters for this sigma
        i = 0
        while True:
            expanded_key = f"{full_pname}_{i}"
@@ -537,7 +564,7 @@ def _count_spectral_mixture_components(params, spec, inst_suffix, section_name,
                i += 1
            else:
                break
        amp_counts.append(i if i > 0 else 1)
        sigma_counts.append(i if i > 0 else 1)
    
    for ls_name in ls_param_names:
        # Determine suffix for this parameter  
@@ -562,24 +589,24 @@ def _count_spectral_mixture_components(params, spec, inst_suffix, section_name,
        ls_counts.append(i if i > 0 else 1)
    
    # Apply the specified logic for component count determination:
    amp_count = sum(amp_counts) 
    sigma_count = sum(sigma_counts) 
    ls_count = sum(ls_counts)
    
    if amp_count > 1 and ls_count > 1 and amp_count != ls_count:
    if sigma_count > 1 and ls_count > 1 and sigma_count != ls_count:
        # If both counts are greater than 1 but not equal, log an error message for a length mismatch
        error_msg = f"ERROR: SpectralMixture parameter length mismatch. Found {amp_count} amplitude parameters " \
        error_msg = f"ERROR: SpectralMixture parameter length mismatch. Found {sigma_count} sigma parameters " \
                   f"but {ls_count} lengthscale parameters."
        if logger is not None:
            logger(error_msg)
        else:
            print(error_msg)
        raise ValueError(error_msg)
    elif (amp_count == 1 and ls_count >= 1) or (ls_count == 1 and amp_count >= 1):
    elif (sigma_count == 1 and ls_count >= 1) or (ls_count == 1 and sigma_count >= 1):
        # If one is exactly 1 and the other is 1 or more, set component_count to the maximum
        component_count = max(amp_count, ls_count)
    elif amp_count > 0 and ls_count > 0:
        component_count = max(sigma_count, ls_count)
    elif sigma_count > 0 and ls_count > 0:
        # Both are greater than 0 but not meeting the previous conditions (will be equal if they were properly aligned)
        component_count = max(amp_count, ls_count)
        component_count = max(sigma_count, ls_count)
    else:
        # Default behavior
        component_count = 1
+19 −21
Changes for PyAETNA/sampler.py: 19 added lines, 21 removed lines.
Original line number Diff line number Diff line
@@ -1764,15 +1764,15 @@ def _get_default_param_order(kernel_type):
    The kernel internally converts: period = 1/freq.
    """
    kernel_param_orders = {
        'ExpSquared': ['log_gp_amp', 'log_gp_scale'],
        'RBF': ['log_gp_amp', 'log_gp_scale'],
        'Matern32': ['log_gp_amp', 'log_gp_scale'],
        'Matern32_quasisep': ['log_gp_amp', 'log_gp_scale'],
        'Matern52': ['log_gp_amp', 'log_gp_scale'],
        'Matern52_quasisep': ['log_gp_amp', 'log_gp_scale'],
        'ExpSineSquared': ['log_gp_amp', 'gp_freq', 'log_gp_gamma'],
        'ExpSquared': ['log_gp_sigma', 'log_gp_scale'],
        'RBF': ['log_gp_sigma', 'log_gp_scale'],
        'Matern32': ['log_gp_sigma', 'log_gp_scale'],
        'Matern32_quasisep': ['log_gp_sigma', 'log_gp_scale'],
        'Matern52': ['log_gp_sigma', 'log_gp_scale'],
        'Matern52_quasisep': ['log_gp_sigma', 'log_gp_scale'],
        'ExpSineSquared': ['log_gp_sigma', 'gp_freq', 'log_gp_gamma'],
        'Constant': ['log_gp_const'],
        'QuasiPeriodic': ['log_gp_amp', 'log_gp_se_scale', 'gp_freq', 'log_gp_gamma'],
        'QuasiPeriodic': ['log_gp_sigma', 'log_gp_se_scale', 'gp_freq', 'log_gp_gamma'],
        # Simple Harmonic Oscillator (damped SHO kernel):
        # - SHO: Sample damping timescale τ in log space, convert to Q = ω₀*τ/2
        # - SHO_Quality: Sample Q directly (linear or log scale as desired)
@@ -1780,10 +1780,10 @@ def _get_default_param_order(kernel_type):
        'SHO': ['log_gp_sigma', 'gp_freq', 'log_gp_lengthscale'],
        'SHO_Quality': ['log_gp_sigma', 'gp_freq', 'gp_quality'],
        # Spectral Mixture kernel: multiple oscillatory components
        # - log_gp_amp: (array) per-harmonic amplitudes
        # - log_gp_asigma: (array) per-harmonic amplitudes
        # - log_gp_lengthscale: (array or scalar) per-harmonic damping scales
        # - gp_freq: (scalar) fundamental frequency for generating harmonics
        'SpectralMixture': ['log_gp_amp', 'log_gp_lengthscale', 'gp_freq'],
        'SpectralMixture': ['log_gp_sigma', 'log_gp_lengthscale', 'gp_freq'],
    }
    return kernel_param_orders.get(kernel_type, [])

@@ -2683,10 +2683,10 @@ def _parse_yaml_config(config, plModel, plData, log_func=None):
    # Within a GP section, each hyperparameter uses the standard
    # scoped-parameter syntax:
    #
    #   log_gp_amp: -6.0                                        # fixed globally
    #   log_gp_amp: [uniform, -8, -5]                           # implicit global prior
    #   log_gp_amp: [global, uniform, -8, -5]                   # explicit global prior
    #   log_gp_amp: [per_instrument, [uniform,-8,-5], [uniform,-6,-3]]
    #   log_gp_sigma: -6.0                                        # fixed globally
    #   log_gp_sigma: [uniform, -8, -5]                           # implicit global prior
    #   log_gp_sigma: [global, uniform, -8, -5]                   # explicit global prior
    #   log_gp_sigma: [per_instrument, [uniform,-8,-5], [uniform,-6,-3]]
    #                                                              # one prior per listed instrument
    #
    # For 'per_instrument', the number of entries must match
@@ -2699,7 +2699,7 @@ def _parse_yaml_config(config, plModel, plData, log_func=None):
    #       instruments: [CHEOPS]
    #       kernel: Matern32
    #       x_variable: BJD
    #       log_gp_amp: [uniform, -10, -4]
    #       log_gp_sigma: [uniform, -10, -4]
    #       log_gp_scale: [uniform, -5, 0]
    #     GP_time_rv:
    #       instruments: [HARPS]
@@ -2812,11 +2812,9 @@ def _parse_yaml_config(config, plModel, plData, log_func=None):
                continue
            raw_value = gp_cfg[param_key]

            # Special handling for array parameters (e.g., log_gp_amp with 3 harmonics)
            # Array parameters are specified as lists of prior specs:
            #   log_gp_amp: [['global', 'uniform', -3, 1], ['global', 'uniform', -4, 1], ...]
            # These get expanded into indexed free parameters:
            #   log_gp_amp_0, log_gp_amp_1, log_gp_amp_2, ...
            # Special handling for array parameters (e.g., log_gp_sigma with 3 harmonics)
            #   log_gp_sigma: [['global', 'uniform', -3, 1], ['global', 'uniform', -4, 1], ...]
            #   log_gp_sigma_0, log_gp_sigma_1, log_gp_sigma_2, ...
            is_array_param = (isinstance(raw_value, list) and len(raw_value) > 0 and
                    all(isinstance(item, list) for item in raw_value))
            if is_array_param:
@@ -4450,7 +4448,7 @@ def runSampler_NumPyro(configFile, dataFile, logFile, num_chains=None, outDir='.
                        kernel: Matern32
                        x_variable: ROLL_ANGLE
                        per_visit: true
                        log_gp_amp: [uniform, -8, -4]
                        log_gp_sigma: [uniform, -8, -4]
                        log_gp_scale: [uniform, 1, 8]

            log_jitter: [per_visit, uniform, -9.5, -8.5]
+7 −7
Changes for docs/config-generator.html: 7 added lines, 7 removed lines.
Original line number Diff line number Diff line
@@ -982,20 +982,20 @@ var DISTS = [
];

var KERN = {
  Matern32:          [{n:'log_gp_amp',  d:'uniform',p:'-8, -4',   h:'Log amplitude (controls GP variance)'},
  Matern32:          [{n:'log_gp_sigma',  d:'uniform',p:'-8, -4',   h:'Log amplitude (controls GP variance)'},
                      {n:'log_gp_scale', d:'uniform',p:'1, 8',     h:'Log length-scale (correlation length)'}],
  Matern52:          [{n:'log_gp_amp',  d:'uniform',p:'-8, -4',   h:'Log amplitude'},
  Matern52:          [{n:'log_gp_sigma',  d:'uniform',p:'-8, -4',   h:'Log amplitude'},
                      {n:'log_gp_scale', d:'uniform',p:'1, 8',     h:'Log length-scale'}],
  Matern32_quasisep: [{n:'log_gp_amp',  d:'uniform',p:'-8, -4',   h:'Log amplitude (O(N) scalable)'},
  Matern32_quasisep: [{n:'log_gp_sigma',  d:'uniform',p:'-8, -4',   h:'Log amplitude (O(N) scalable)'},
                      {n:'log_gp_scale', d:'uniform',p:'1, 8',     h:'Log length-scale'}],
  Matern52_quasisep: [{n:'log_gp_amp',  d:'uniform',p:'-8, -4',   h:'Log amplitude (O(N) scalable)'},
  Matern52_quasisep: [{n:'log_gp_sigma',  d:'uniform',p:'-8, -4',   h:'Log amplitude (O(N) scalable)'},
                      {n:'log_gp_scale', d:'uniform',p:'1, 8',     h:'Log length-scale'}],
  ExpSquared:        [{n:'log_gp_amp',  d:'uniform',p:'-8, -4',   h:'Log amplitude (RBF/SE kernel)'},
  ExpSquared:        [{n:'log_gp_sigma',  d:'uniform',p:'-8, -4',   h:'Log amplitude (RBF/SE kernel)'},
                      {n:'log_gp_scale', d:'uniform',p:'1, 8',     h:'Log length-scale'}],
  ExpSineSquared:    [{n:'log_gp_amp',  d:'uniform',p:'-8, -5',   h:'Log amplitude'},
  ExpSineSquared:    [{n:'log_gp_sigma',  d:'uniform',p:'-8, -5',   h:'Log amplitude'},
                      {n:'gp_freq',     d:'fixed',  p:'0.5/jnp.pi',h:'Frequency (linear, NOT log-scale)'},
                      {n:'log_gp_gamma',d:'uniform',p:'-5, 5',    h:'Log gamma (damping)'}],
  QuasiPeriodic:     [{n:'log_gp_amp',     d:'uniform',p:'-8, -5',h:'Log amplitude'},
  QuasiPeriodic:     [{n:'log_gp_sigma',     d:'uniform',p:'-8, -5',h:'Log amplitude'},
                      {n:'log_gp_se_scale',d:'uniform',p:'6, 10', h:'Log SE envelope scale'},
                      {n:'gp_freq',        d:'fixed',p:'0.5/jnp.pi',h:'Frequency (linear)'},
                      {n:'log_gp_gamma',   d:'uniform',p:'-5, 5', h:'Log gamma (damping)'}],
+77 −16

File changed.

Preview size limit exceeded, changes collapsed.

+8 −7
Changes for docs/configuration_keys.md: 8 added lines, 7 removed lines.
Original line number Diff line number Diff line
@@ -252,20 +252,21 @@ Each named subsection defines a GP kernel targeting specific instruments.

| Kernel | Required hyperparameters (in order) |
|--------|--------------------------------------|
| `ExpSquared` / `RBF` | `log_gp_amp`, `log_gp_scale` |
| `Matern32` / `Matern32_quasisep` | `log_gp_amp`, `log_gp_scale` |
| `Matern52` / `Matern52_quasisep` | `log_gp_amp`, `log_gp_scale` |
| `ExpSineSquared` | `log_gp_amp`, `gp_freq`, `log_gp_gamma` |
| `QuasiPeriodic` | `log_gp_amp`, `log_gp_se_scale`, `gp_freq`, `log_gp_gamma` |
| `ExpSquared` / `RBF` | `log_gp_sigma`, `log_gp_scale` |
| `Matern32` / `Matern32_quasisep` | `log_gp_sigma`, `log_gp_scale` |
| `Matern52` / `Matern52_quasisep` | `log_gp_sigma`, `log_gp_scale` |
| `ExpSineSquared` | `log_gp_sigma`, `gp_freq`, `log_gp_gamma` |
| `QuasiPeriodic` | `log_gp_sigma`, `log_gp_se_scale`, `gp_freq`, `log_gp_gamma` |
| `SHO` | `log_gp_sigma`, `gp_freq`, `log_gp_lengthscale` |
| `SHO_Quality` | `log_gp_sigma`, `gp_freq`, `gp_quality` |
| `SpectralMixture` | `log_gp_sigma`, `log_gp_lengthscale`, `gp_freq` |
| `Constant` | `log_gp_const` |

Each GP hyperparameter uses **scoped syntax** with allowed scopes `global` and `per_instrument`:

| Key pattern | Mandatory | Default | Accepted values |
|-------------|-----------|---------|-----------------|
| `detrending.GP.<name>.log_gp_amp` | depends on kernel | — | Scoped parameter. A bare list like `[uniform, -8, -5]` is treated as implicit global. |
| `detrending.GP.<name>.log_gp_sigma` | depends on kernel | — | Scoped parameter. A bare list like `[uniform, -8, -5]` is treated as implicit global. |
| `detrending.GP.<name>.log_gp_scale` | depends on kernel | — | Scoped parameter |
| `detrending.GP.<name>.log_gp_se_scale` | QuasiPeriodic only | — | Scoped parameter (SE envelope scale) |
| `detrending.GP.<name>.gp_freq` | periodic kernels | — | Scoped parameter (linear frequency, **NOT** log-scale) |
@@ -277,7 +278,7 @@ Each GP hyperparameter uses **scoped syntax** with allowed scopes `global` and `

**GP per_instrument scope example:**
```yaml
log_gp_amp: [per_instrument, [uniform, -8, -5], [uniform, -6, -3]]
log_gp_sigma: [per_instrument, [uniform, -8, -5], [uniform, -6, -3]]
```
The number of entries after `per_instrument` must equal the length of the GP section's `instruments` list (not the global total).

Loading