From f0ad0602069b562c2818b60201111383f329b844 Mon Sep 17 00:00:00 2001 From: Wojtek Potrzebowski Date: Tue, 12 May 2026 12:23:51 +0200 Subject: [PATCH 1/5] Migrate from PyStan 2 API to PyStan 3 (import stan) - Add stan_run.build_and_sample mirroring default warmup/sampling split - Replace StanModel/sampling/extract/summary/plot with stan.build, sample, fit[param], and matplotlib marginals - Rewrite stan_utility diagnostics for CmdStan-style __ sampler outputs - Update statistics mean_for_weights and me_log_lik for PyStan 3 fits - fullBayesianTR: rebuild model per frame; drop unused qvector from data - Pin pystan>=3.9,<4 in bioce.yml; register stan_run in setup.py Co-authored-by: Cursor --- bioce.yml | 2 +- fullBayesian.py | 142 ++++++++++++++------------------- fullBayesianTR.py | 60 +++++--------- psisloo.py | 6 +- setup.py | 2 +- stan_run.py | 93 ++++++++++++++++++++++ stan_utility.py | 198 +++++++++++++++++++++------------------------- statistics.py | 16 ++-- 8 files changed, 275 insertions(+), 244 deletions(-) create mode 100644 stan_run.py diff --git a/bioce.yml b/bioce.yml index 7aa1a41..0b0db3e 100644 --- a/bioce.yml +++ b/bioce.yml @@ -11,6 +11,6 @@ dependencies: - swig - gsl - pip: - - pystan + - "pystan>=3.9,<4" - pandas - git+https://github.com/emblsaxs/sasciftools.git diff --git a/fullBayesian.py b/fullBayesian.py index 24d0934..6680247 100755 --- a/fullBayesian.py +++ b/fullBayesian.py @@ -11,10 +11,9 @@ import optparse import numpy as np -import pystan import psisloo -import matplotlib.pyplot as plt import stan_utility +import stan_run from statistics import calculateChiCrysol, calculateChemShiftsChi, JensenShannonDiv, waic, mean_for_weights, me_log_lik from stan_models import stan_code, stan_code_CS, stan_code_EP, stan_code_EP_CS, \ @@ -39,23 +38,20 @@ def execute_stan(experimental, simulated, priors, iterations, chains, njobs): "n_structures" : np.shape(simulated)[1], "priors":priors} - #sm = pystan.StanModel(model_code=stan_code+psisloo_quanities) - sm = pystan.StanModel(model_code=stan_code) - fit = sm.sampling(data=stan_dat, iter=iterations, chains=chains, - n_jobs=njobs, sample_file="saved_samples.txt") + fit = stan_run.build_and_sample( + stan_code, + stan_dat, + iterations, + chains, + njobs=njobs, + ) - #initial_values = [{"weight[0]":0.05, "weight[1]":0.1, "weight[2]":0.15, + # initial_values = [{"weight[0]":0.05, "weight[1]":0.1, "weight[2]":0.15, # "weight[3]":0.3, "weight[4]":0.4, "scale":1}] - #fit = sm.optimizing(data=stan_dat, init=initial_values, algorithm="BFGS") + # PyStan 3 does not expose optimizing(); use CmdStanPy if needed. - fig = fit.plot(pars="weights") - #ax.set_color_cycle(['red', 'black', 'yellow', 'green', 'blue']) - fig.subplots_adjust(wspace=0.8) - fig.savefig("stan_weights.png", dpi=300) - - fig = fit.plot(pars="scale") - fig.subplots_adjust(wspace=0.8) - fig.savefig("stan_scale.png", dpi=300) + stan_run.plot_parameter_marginals(fit, "weights", "stan_weights.png", dpi=300) + stan_run.plot_parameter_marginals(fit, "scale", "stan_scale.png", dpi=300) #np.savetxt("target_curve_full.csv", fit.summary()['summary'][-869:-1][:,:2]) return fit @@ -79,20 +75,15 @@ def execute_stan_EP(experimental, simulated, priors, iterations, chains, njobs): "n_measures" : np.shape(experimental)[0], "n_structures" : np.shape(simulated)[1], "energy_priors":priors} - sm = pystan.StanModel(model_code=stan_code_EP) - fit = sm.sampling(data=stan_dat, iter=iterations, chains=chains, n_jobs=njobs, sample_file="saved_samples.txt") - - fig = fit.plot(pars="weights") - fig.subplots_adjust(wspace=0.8) - fig.savefig("stan_weights_EP.png", dpi=300) - - fig = fit.plot(pars="boltzmann_shift") - fig.subplots_adjust(wspace=0.8) - fig.savefig("stan_boltzmann_EP.png", dpi=300) + fit = stan_run.build_and_sample( + stan_code_EP, stan_dat, iterations, chains, njobs=njobs + ) - fig = fit.plot(pars="scale") - fig.subplots_adjust(wspace=0.8) - fig.savefig("stan_scale_EP.png", dpi=300) + stan_run.plot_parameter_marginals(fit, "weights", "stan_weights_EP.png", dpi=300) + stan_run.plot_parameter_marginals( + fit, "boltzmann_shift", "stan_boltzmann_EP.png", dpi=300 + ) + stan_run.plot_parameter_marginals(fit, "scale", "stan_scale_EP.png", dpi=300) return fit @@ -126,20 +117,15 @@ def execute_stan_EP_CS(experimental, simulated, priors, "n_structures" : np.shape(simulated)[1], "energy_priors":priors} - sm = pystan.StanModel(model_code=stan_code_EP_CS) - fit = sm.sampling(data=stan_dat, iter=iterations, chains=chains, n_jobs=njobs, sample_file="saved_samples.txt") - - fig = fit.plot(pars="weights") - fig.subplots_adjust(wspace=0.8) - fig.savefig("stan_weights_EP_CS.png", dpi=300) - - fig = fit.plot(pars="boltzmann_shift") - fig.subplots_adjust(wspace=0.8) - fig.savefig("stan_boltzmann_EP_CS.png", dpi=300) + fit = stan_run.build_and_sample( + stan_code_EP_CS, stan_dat, iterations, chains, njobs=njobs + ) - fig = fit.plot(pars="scale") - fig.subplots_adjust(wspace=0.8) - fig.savefig("stan_scale_EP_CS.png", dpi=300) + stan_run.plot_parameter_marginals(fit, "weights", "stan_weights_EP_CS.png", dpi=300) + stan_run.plot_parameter_marginals( + fit, "boltzmann_shift", "stan_boltzmann_EP_CS.png", dpi=300 + ) + stan_run.plot_parameter_marginals(fit, "scale", "stan_scale_EP_CS.png", dpi=300) return fit @@ -172,16 +158,12 @@ def execute_stan_CS(experimental, simulated, priors, "n_structures" : np.shape(simulated)[1], "priors":priors} - sm = pystan.StanModel(model_code=stan_code_CS) - fit = sm.sampling(data=stan_dat, iter=iterations, chains=chains, n_jobs=njobs, sample_file="saved_samples.txt") + fit = stan_run.build_and_sample( + stan_code_CS, stan_dat, iterations, chains, njobs=njobs + ) - fig = fit.plot(pars="weights") - fig.subplots_adjust(wspace=0.8) - fig.savefig("stan_weights_CS.png", dpi=300) - - fig = fit.plot(pars="scale") - fig.subplots_adjust(wspace=0.8) - fig.savefig("stan_scale.png", dpi=300) + stan_run.plot_parameter_marginals(fit, "weights", "stan_weights_CS.png", dpi=300) + stan_run.plot_parameter_marginals(fit, "scale", "stan_scale.png", dpi=300) return fit def execute_bws(experimental, simulated, priors, file_names, threshold, @@ -215,7 +197,7 @@ def execute_bws(experimental, simulated, priors, file_names, threshold, last_loo = None last_waic = None model_comp_diff = 1 - sm = pystan.StanModel(model_code=stan_code+psisloo_quanities) + model_code_bws = stan_code + psisloo_quanities iteration = 0 repeat_iteration = 0 @@ -234,15 +216,16 @@ def execute_bws(experimental, simulated, priors, file_names, threshold, "n_structures" : n_structures, "priors" : alphas} - fit = sm.sampling(data=stan_dat, iter=iterations, chains=chains, - n_jobs=njobs) + fit = stan_run.build_and_sample( + model_code_bws, stan_dat, iterations, chains, njobs=njobs + ) #Calculating psis loo - stan_chain=fit.extract() + loglikes = np.asarray(fit["loglikes"]).T #If less than 100 models start psisloo otherwise waic if n_structures < structure_cutoff: - current_loo = psisloo.psisloo(stan_chain['loglikes']) + current_loo = psisloo.psisloo(loglikes) log_file.write("greater than 0.5 ") log_file.write(str(current_loo.print_summary()[0])+"\n") log_file.write("greater than 1 ") @@ -254,7 +237,7 @@ def execute_bws(experimental, simulated, priors, file_names, threshold, log_file.write(str(model_comp['diff'])+"\n") log_file.write(str(model_comp['se_diff'])+"\n") else: - current_waic = waic(stan_chain['loglikes']) + current_waic = waic(loglikes) if last_waic: model_comp_diff = current_waic - last_waic log_file.write("WAIC model comparison: \n") @@ -279,15 +262,15 @@ def execute_bws(experimental, simulated, priors, file_names, threshold, else: last_waic = current_waic repeat_iteration = 0 - current_weights = fit.summary()['summary'][:,0][:n_structures] + current_weights = np.asarray(fit["weights"]).mean(axis=1) sim_curves = sim_curves[:,current_weights>threshold] alphas = alphas[current_weights>threshold] n_structures = np.shape(sim_curves)[1] file_names = file_names[current_weights>threshold] #np.savetxt("fit.txt",fit.summary()['summary'][:,0],delimiter=" ") - fig = fit.plot() - fig.subplots_adjust(wspace=0.8) - fig.savefig("stan_fit_"+str(iteration)+".png", dpi=300) + stan_run.plot_parameter_marginals( + fit, "weights", "stan_fit_" + str(iteration) + ".png", dpi=300 + ) bayesian_weights, jsd, crysol_chi2 = calculate_stats(fit, experimental, simulated) log_file.write("JSD: "+str(jsd)) @@ -329,32 +312,23 @@ def execute_bws(experimental, simulated, priors, file_names, threshold, def calculate_stats(fit, experimental, simulated, cs_simulated=None, cs_rms=None, cs_experimental=None): """ - Calculates statistics based on stan model + Calculates statistics based on stan model (PyStan 3 ``stan`` fit). :param fit: :return: """ - #la = fit.extract(permuted=True) # return a dictionary of arrays - #mu = la['weights'] - - ## return an array of three dimensions: iterations, chains, parameters - results_array = fit.extract(permuted=False, inc_warmup=False) + weights_draws = np.asarray(fit["weights"]) + if weights_draws.ndim == 1: + weights_draws = weights_draws.reshape(1, -1) + n_struct = np.shape(simulated)[1] + weights_draws = weights_draws[:n_struct, :] + n_draws = weights_draws.shape[1] + bayesian_weights = weights_draws.mean(axis=1) - nsamples = 0 jsd_sum = 0.0 - bayesian_weights = np.zeros(np.shape(simulated)[1]) - for iteration in results_array: - for parameters in iteration: - current_weights = parameters[:np.shape(simulated)[1]] - bayesian_weights+=current_weights - nsamples+=1 - bayesian_weights=bayesian_weights/nsamples - - for iteration in results_array: - for parameters in iteration: - current_weights = parameters[:np.shape(simulated)[1]] - jsd_sum+=JensenShannonDiv(current_weights, bayesian_weights) - jsd = (np.sqrt(jsd_sum/nsamples)) - + for j in range(n_draws): + current_weights = weights_draws[:, j] + jsd_sum += JensenShannonDiv(current_weights, bayesian_weights) + jsd = np.sqrt(jsd_sum / n_draws) crysol_chi2 = calculateChiCrysol(np.dot(bayesian_weights, np.transpose(simulated)), experimental[:,1], @@ -365,8 +339,8 @@ def calculate_stats(fit, experimental, simulated, cs_simulated=None, except: chemical_shifts_on = False - scale = fit.summary(pars='scale')['summary'][0][0] - print("Scale from summary", scale) + scale = float(np.mean(np.asarray(fit["scale"]))) + print("Scale from posterior mean", scale) combine_curve(experimental, simulated, bayesian_weights, scale) if chemical_shifts_on: diff --git a/fullBayesianTR.py b/fullBayesianTR.py index a1f12c4..b7bc150 100644 --- a/fullBayesianTR.py +++ b/fullBayesianTR.py @@ -9,13 +9,11 @@ import os import optparse -import pickle import numpy as np -import pystan import psisloo -import matplotlib.pyplot as plt import stan_utility +import stan_run from statistics import ( calculateChiCrysol, @@ -164,14 +162,8 @@ def execute_stan(experimental, simulated, priors, iterations, chains, njobs): "priors": priors, } - # sm = pystan.StanModel(model_code=stan_code+psisloo_quanities) - sm = pystan.StanModel(model_code=stan_code) - fit = sm.sampling( - data=stan_dat, - iter=iterations, - chains=chains, - n_jobs=njobs, - sample_file="saved_samples.txt", + fit = stan_run.build_and_sample( + stan_code, stan_dat, iterations, chains, njobs=njobs ) # initial_values = [{"weight[0]":0.05, "weight[1]":0.1, "weight[2]":0.15, @@ -202,24 +194,19 @@ def calculate_stats( # la = fit.extract(permuted=True) # return a dictionary of arrays # mu = la['weights'] - ## return an array of three dimensions: iterations, chains, parameters - results_array = fit.extract(permuted=False, inc_warmup=False) + weights_draws = np.asarray(fit["weights"]) + if weights_draws.ndim == 1: + weights_draws = weights_draws.reshape(1, -1) + n_struct = np.shape(simulated)[1] + weights_draws = weights_draws[:n_struct, :] + n_draws = weights_draws.shape[1] + bayesian_weights = weights_draws.mean(axis=1) - nsamples = 0 jsd_sum = 0.0 - bayesian_weights = np.zeros(np.shape(simulated)[1]) - for iteration in results_array: - for parameters in iteration: - current_weights = parameters[: np.shape(simulated)[1]] - bayesian_weights += current_weights - nsamples += 1 - bayesian_weights = bayesian_weights / nsamples - - for iteration in results_array: - for parameters in iteration: - current_weights = parameters[: np.shape(simulated)[1]] - jsd_sum += JensenShannonDiv(current_weights, bayesian_weights) - jsd = np.sqrt(jsd_sum / nsamples) + for j in range(n_draws): + current_weights = weights_draws[:, j] + jsd_sum += JensenShannonDiv(current_weights, bayesian_weights) + jsd = np.sqrt(jsd_sum / n_draws) crysol_chi2 = calculateChiCrysol( np.dot(bayesian_weights, np.transpose(simulated)), @@ -388,12 +375,6 @@ def calculatePostChi(): # Even distribution of priors priors = np.ones(number_of_states + 1) * 1.0 / (number_of_states + 1) file_names = np.append(file_names, "diff") - # Pickling for teh first time compilation - sm = pystan.StanModel(model_code=stan_code) - # with open('model.pkl', 'wb') as f: - # pickle.dump(sm, f) - # sm = pickle.load(open('model.pkl', 'rb')) - iteration_folder = "iteration_" + str(options.iteration) # previous_iteration_folder = 'iteration_' + str(options.iteration-1) if os.path.exists(iteration_folder) == False: @@ -404,8 +385,6 @@ def calculatePostChi(): # fout.write('jsd, chi2\n') # List of experiemntal files to scan over - stan_initialized = False - # TODO: For each frame load diff file and combine it with simulated # What to do with CTD simulated? # make if statement for non-diff cases @@ -452,8 +431,6 @@ def calculatePostChi(): # TODO: Priors should be defined as a matrix and defined for specific matrix stan_dat = { "sim_curves": full_simulated, - # "ctd_curves" : ctd_simulated, - "qvector": qvector, "target_curve": experimental[:, 1], "target_errors": target_errors, "n_measures": np.shape(experimental)[0], @@ -461,8 +438,13 @@ def calculatePostChi(): "priors": priors_i, } - fit = sm.sampling( - data=stan_dat, iter=iterations, chains=chains, n_jobs=njobs, refresh=-1 + fit = stan_run.build_and_sample( + stan_code, + stan_dat, + iterations, + chains, + njobs=njobs, + show_progress=False, ) print(fit) diff --git a/psisloo.py b/psisloo.py index 8bbe0fc..6a8d001 100644 --- a/psisloo.py +++ b/psisloo.py @@ -80,11 +80,11 @@ def psisloo(log_likelihood): and approximate Leave-One-Out cross-validation (LOO). Takes as input an ndarray of posterior log likelihood terms [ p( y_i | theta^s ) ] - per observation unit. + per observation unit (rows = posterior draws, cols = observations). - e.x. if using pystan: + Example with PyStan 3 (``import stan``):: - loosummary = stanity.psisloo(stan_fit.extract()['log_lik']) + loosummary = psisloo.psisloo(stan_fit["loglikes"].T) Returns a Psisloo object. Useful methods such as print_summary() & plot(). diff --git a/setup.py b/setup.py index 2c97c02..c5155a8 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,7 @@ url='https://www.andrelab.org', ext_package='vbwSC', ext_modules=extensions, - py_modules=['psis','psisloo','stan_models','stan_utility', + py_modules=['psis','psisloo','stan_models','stan_utility','stan_run', 'statistics','variationalBayesian','fullBayesian', 'prepareBayesian', 'prepareChemicalShifts'], package_data={'bioce': ['bioce.yml']}, diff --git a/stan_run.py b/stan_run.py new file mode 100644 index 0000000..df95402 --- /dev/null +++ b/stan_run.py @@ -0,0 +1,93 @@ +""" +PyStan 3 (import stan) helpers mirroring PyStan 2 sampling defaults. + +PyStan 2 used ``iter`` total iterations per chain with the first half as warmup +by default. CmdStan / PyStan 3 split ``num_warmup`` and ``num_samples`` explicitly. +""" +from __future__ import annotations + +from typing import Any, Optional, Tuple + +import numpy as np + +import stan + + +def iterations_to_warmup_and_samples(iterations: int) -> Tuple[int, int]: + """Match PyStan 2 default split: ``warmup = iter // 2``, sampling = remainder.""" + iterations = int(iterations) + num_warmup = max(1, iterations // 2) + num_samples = max(1, iterations - num_warmup) + return num_warmup, num_samples + + +def build_and_sample( + model_code: str, + data: dict, + iterations: int, + chains: int, + njobs: Optional[int] = None, + random_seed: Optional[int] = None, + show_progress: bool = True, +) -> Any: + """ + Compile with ``stan.build`` and draw posterior samples. + + Parameters + ---------- + njobs + Upper bound on parallel chains (passed as ``parallel_chains`` when supported). + show_progress + If False, attempts to silence progress output (CmdStan ``show_progress``). + """ + num_warmup, num_samples = iterations_to_warmup_and_samples(iterations) + posterior = stan.build( + model_code, data=data, random_seed=random_seed + ) + kwargs: dict = { + "num_chains": int(chains), + "num_warmup": num_warmup, + "num_samples": num_samples, + } + if njobs is not None and njobs > 0: + kwargs["parallel_chains"] = min(int(chains), int(njobs)) + if not show_progress: + kwargs["show_progress"] = False + try: + return posterior.sample(**kwargs) + except TypeError: + # Older CmdStan backends may not support all kwargs. + kwargs.pop("parallel_chains", None) + kwargs.pop("show_progress", None) + return posterior.sample(**kwargs) + + +def plot_parameter_marginals(fit, par: str, filename: str, dpi: int = 300) -> None: + """Histogram of each scalar component of ``par`` (similar spirit to PyStan 2 ``fit.plot``).""" + import matplotlib.pyplot as plt + + arr = np.asarray(fit[par]) + if arr.ndim == 1: + arr = arr.reshape(1, -1) + n_par = arr.shape[0] + ncols = min(5, max(1, n_par)) + nrows = (n_par + ncols - 1) // ncols + fig, axes = plt.subplots( + nrows, + ncols, + figsize=(3.2 * ncols, 2.4 * nrows), + squeeze=False, + ) + idx = 0 + for r in range(nrows): + for c in range(ncols): + ax = axes[r][c] + if idx < n_par: + ax.hist(arr[idx, :].ravel(), bins=40, density=True, color="steelblue") + ax.set_title("{}[{}]".format(par, idx + 1) if n_par > 1 else par) + else: + ax.axis("off") + idx += 1 + fig.subplots_adjust(wspace=0.35, hspace=0.45) + fig.savefig(filename, dpi=dpi) + plt.close(fig) diff --git a/stan_utility.py b/stan_utility.py index d728e4b..a6ac5b4 100644 --- a/stan_utility.py +++ b/stan_utility.py @@ -1,142 +1,128 @@ -import pystan -import pickle +""" +Diagnostics and helpers for PyStan 3 (``import stan``) fits. + +Sampler diagnostics use CmdStan-style names ending in ``__``, indexed like +``fit["divergent__"]``. See https://pystan.readthedocs.io/en/latest/upgrading.html +""" +from __future__ import print_function + import numpy +import stan + + +def _sampler_as_draws_by_chain(fit, name): + """Return array shape (num_saved_draws, num_chains) for a scalar sampler diagnostic.""" + x = numpy.asarray(fit[name]) + x = x.reshape(-1) + num_saved = fit._draws.shape[1] + num_chains = fit.num_chains + return x.reshape(num_saved, num_chains, order="F") + + def check_div(fit): - """Check transitions that ended with a divergence""" - sampler_params = fit.get_sampler_params(inc_warmup=False) - divergent = [x for y in sampler_params for x in y['divergent__']] - n = sum(divergent) - N = len(divergent) - print('{} of {} iterations ended with a divergence ({}%)'.format(n, N, - 100 * n / N)) + """Check transitions that ended with a divergence.""" + d = numpy.asarray(fit["divergent__"]).ravel() + n = int(d.sum()) + N = len(d) + print( + "{} of {} iterations ended with a divergence ({}%)".format( + n, N, 100 * n / N if N else 0 + ) + ) if n > 0: - print(' Try running with larger adapt_delta to remove the divergences') + print(" Try running with larger adapt_delta to remove the divergences") + -def check_treedepth(fit, max_depth = 10): - """Check transitions that ended prematurely due to maximum tree depth limit""" - sampler_params = fit.get_sampler_params(inc_warmup=False) - depths = [x for y in sampler_params for x in y['treedepth__']] +def check_treedepth(fit, max_depth=10): + """Check transitions that hit the maximum tree depth.""" + mat = _sampler_as_draws_by_chain(fit, "treedepth__") + depths = mat.ravel(order="C") n = sum(1 for x in depths if x == max_depth) N = len(depths) - print(('{} of {} iterations saturated the maximum tree depth of {}' - + ' ({}%)').format(n, N, max_depth, 100 * n / N)) + print( + ("{} of {} iterations saturated the maximum tree depth of {} ({}%)").format( + n, N, max_depth, 100 * n / N if N else 0 + ) + ) if n > 0: - print(' Run again with max_depth set to a larger value to avoid saturation') + print( + " Run again with max_depth passed through to sample() " + "to avoid saturation (CmdStan: max_depth keyword)." + ) + def check_energy(fit): - """Checks the energy Bayesian fraction of missing information (E-BFMI)""" - sampler_params = fit.get_sampler_params(inc_warmup=False) + """E-BFMI from energy__ within each chain.""" + mat = _sampler_as_draws_by_chain(fit, "energy__") no_warning = True - for chain_num, s in enumerate(sampler_params): - energies = s['energy__'] - numer = sum((energies[i] - energies[i - 1])**2 for i in range(1, len(energies))) / len(energies) + for chain_num in range(mat.shape[1]): + energies = mat[:, chain_num] + if len(energies) < 2: + continue + numer = ( + sum((energies[i] - energies[i - 1]) ** 2 for i in range(1, len(energies))) + / len(energies) + ) denom = numpy.var(energies) - if numer / denom < 0.2: - print('Chain {}: E-BFMI = {}'.format(chain_num, numer / denom)) + if denom == 0: + continue + ratio = numer / denom + if ratio < 0.2: + print("Chain {}: E-BFMI = {}".format(chain_num, ratio)) no_warning = False if no_warning: - print('E-BFMI indicated no pathological behavior') + print("E-BFMI indicated no pathological behavior") else: - print(' E-BFMI below 0.2 indicates you may need to reparameterize your model') + print( + " E-BFMI below 0.2 indicates you may need to reparameterize your model" + ) + def check_n_eff(fit): - """Checks the effective sample size per iteration""" - fit_summary = fit.summary(probs=[0.5]) - n_effs = [x[4] for x in fit_summary['summary']] - names = fit_summary['summary_rownames'] - n_iter = len(fit.extract()['lp__']) + """Not supported for PyStan 3 fits (no ``fit.summary``); use ArviZ on draws.""" + print( + "check_n_eff: skipped under PyStan 3 (no built-in summary table). " + "Use ArviZ or manual ESS from ``fit.to_frame()``." + ) - no_warning = True - for n_eff, name in zip(n_effs, names): - ratio = n_eff / n_iter - if (ratio < 0.001): - print('n_eff / iter for parameter {} is {}!'.format(name, ratio)) - print('E-BFMI below 0.2 indicates you may need to reparameterize your model') - no_warning = False - if no_warning: - print('n_eff / iter looks reasonable for all parameters') - else: - print(' n_eff / iter below 0.001 indicates that the effective sample size has likely been overestimated') def check_rhat(fit): - """Checks the potential scale reduction factors""" - from math import isnan - from math import isinf + """Not supported for PyStan 3 fits; use ArviZ on draws.""" + print( + "check_rhat: skipped under PyStan 3 (no built-in summary table). " + "Use ArviZ or manual R-hat from chain-split draws." + ) - fit_summary = fit.summary(probs=[0.5]) - rhats = [x[5] for x in fit_summary['summary']] - names = fit_summary['summary_rownames'] - - no_warning = True - for rhat, name in zip(rhats, names): - if (rhat > 1.1 or isnan(rhat) or isinf(rhat)): - print('Rhat for parameter {} is {}!'.format(name, rhat)) - no_warning = False - if no_warning: - print('Rhat looks reasonable for all parameters') - else: - print(' Rhat above 1.1 indicates that the chains very likely have not mixed') def check_all_diagnostics(fit): - """Checks all MCMC diagnostics""" + """Checks all MCMC diagnostics implemented for PyStan 3.""" check_n_eff(fit) check_rhat(fit) check_div(fit) check_treedepth(fit) check_energy(fit) -def _by_chain(unpermuted_extraction): - num_chains = len(unpermuted_extraction[0]) - result = [[] for _ in range(num_chains)] - for c in range(num_chains): - for i in range(len(unpermuted_extraction)): - result[c].append(unpermuted_extraction[i][c]) - return numpy.array(result) - -def _shaped_ordered_params(fit): - ef = fit.extract(permuted=False, inc_warmup=False) # flattened, unpermuted, by (iteration, chain) - ef = _by_chain(ef) - ef = ef.reshape(-1, len(ef[0][0])) - ef = ef[:, 0:len(fit.flatnames)] # drop lp__ - shaped = {} - idx = 0 - for dim, param_name in zip(fit.par_dims, fit.extract().keys()): - length = int(numpy.prod(dim)) - shaped[param_name] = ef[:,idx:idx + length] - shaped[param_name].reshape(*([-1] + dim)) - idx += length - return shaped def partition_div(fit): - """ Returns parameter arrays separated into divergent and non-divergent transitions""" - sampler_params = fit.get_sampler_params(inc_warmup=False) - div = numpy.concatenate([x['divergent__'] for x in sampler_params]).astype('int') - params = _shaped_ordered_params(fit) - nondiv_params = dict((key, params[key][div == 0]) for key in params) - div_params = dict((key, params[key][div == 1]) for key in params) - return nondiv_params, div_params + """Not ported for PyStan 3 (requires ordered unconstrained draws per transition).""" + raise NotImplementedError( + "partition_div is not implemented for PyStan 3 fits. " + "Use ArviZ or CmdStanPy if you need divergent/non-divergent draw splits." + ) + -def compile_model(filename, model_name=None, **kwargs): - """This will automatically cache models - great if you're just running a - script on the command line. +def compile_model(filename, model_name=None, data=None, random_seed=None, **kwargs): + """Compile a Stan program with ``stan.build`` (PyStan 3). - See http://pystan.readthedocs.io/en/latest/avoiding_recompilation.html""" - from hashlib import md5 + Unlike PyStan 2, a program with a nonempty ``data`` block must receive matching + ``data`` here. Optional disk caching of compiled models is not supported. + See https://pystan.readthedocs.io/en/latest/upgrading.html + """ with open(filename) as f: model_code = f.read() - code_hash = md5(model_code.encode('ascii')).hexdigest() - if model_name is None: - cache_fn = 'cached-model-{}.pkl'.format(code_hash) - else: - cache_fn = 'cached-{}-{}.pkl'.format(model_name, code_hash) - try: - sm = pickle.load(open(cache_fn, 'rb')) - except: - sm = pystan.StanModel(model_code=model_code) - with open(cache_fn, 'wb') as f: - pickle.dump(sm, f) - else: - print("Using cached StanModel") - return sm + payload = {} if data is None else data + return stan.build( + model_code, data=payload, random_seed=random_seed + ) diff --git a/statistics.py b/statistics.py index c6ccfd9..05e2487 100644 --- a/statistics.py +++ b/statistics.py @@ -64,17 +64,14 @@ def JensenShannonDiv(weights_a, weights_b): def mean_for_weights(fit): """ - Calculation mean weight from stan fitting object + Calculation mean weight from stan fitting object (PyStan 3 ``stan`` fit). :param fit: stan fit model :return: """ - stan_chain = fit.extract() - weights = stan_chain["weights"] - vlen = np.shape(weights) - mean_weights = [] - for ind in range(vlen[1]): - mean_weights.append(np.mean(weights[:,ind])) - return mean_weights + weights = np.asarray(fit["weights"]) + if weights.ndim == 1: + weights = weights.reshape(1, -1) + return list(np.mean(weights, axis=1)) def me_log_lik(fit): """ @@ -82,8 +79,7 @@ def me_log_lik(fit): :param log_lik: :return: """ - stan_chain = fit.extract() - log_lik = stan_chain["loglikes"] + log_lik = np.asarray(fit["loglikes"]).T return np.sum(np.mean(log_lik, axis=1)) From f5cb34663de90c080d5f51e12f48f874f614ac63 Mon Sep 17 00:00:00 2001 From: Wojtek Potrzebowski Date: Tue, 12 May 2026 12:37:20 +0200 Subject: [PATCH 2/5] Add README PyStan section, pytest smoke tests, lazy stan import - Document PyStan 3, platforms, and pytest smoke test in README - tests/test_pystan_smoke.py: iteration split always runs; sampling tests skip without stan - pytest.ini: pythonpath for local imports - stan_run: lazy-import stan in build_and_sample; numpy only in plotting Co-authored-by: Cursor --- README.md | 19 ++++++++++ pytest.ini | 3 ++ stan_run.py | 7 ++-- tests/test_pystan_smoke.py | 78 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 pytest.ini create mode 100644 tests/test_pystan_smoke.py diff --git a/README.md b/README.md index acbba55..087e397 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,25 @@ python variationalBayesian.py –-help ``` If you see no errors but options menu pops up, you are good to go. +### PyStan 3 (full Bayesian inference) + +Complete Bayesian sampling (`fullBayesian.py`, `fullBayesianTR.py`) uses **PyStan 3**: +install the **`pystan`** package from pip (inside the activated conda env the `bioce.yml` `pip` section already requests `pystan>=3.9,<4`). In code this appears as **`import stan`** (not `import pystan`). See the [PyStan upgrading guide](https://pystan.readthedocs.io/en/latest/upgrading.html). + +**Platforms:** PyStan 3 is supported on **Linux and macOS** only (not Windows). + +**Smoke test** (checks that `stan` compiles and samples, and that `stan_run` / `stan_utility` run): + +```bash +pip install pytest +cd /path/to/bioce +python -m pytest tests/test_pystan_smoke.py -q +``` + +The first test only checks **warmup vs sampling split** logic and passes without PyStan. The other two tests **compile and sample** a tiny model; if `pystan` is missing they are **skipped** (pytest exit code 0). With PyStan installed, all three tests should pass. + +`pytest.ini` in the repo root sets `pythonpath = .` so `stan_run` imports work without installing the package. + ## Problems with installation on OSX 10.14 (Mojave) There is a known issue with xcode installation on OSX 10.14 (Mojave) If you see the following error: diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..c7b23ec --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +pythonpath = . +testpaths = tests diff --git a/stan_run.py b/stan_run.py index df95402..6cf0dda 100644 --- a/stan_run.py +++ b/stan_run.py @@ -8,10 +8,6 @@ from typing import Any, Optional, Tuple -import numpy as np - -import stan - def iterations_to_warmup_and_samples(iterations: int) -> Tuple[int, int]: """Match PyStan 2 default split: ``warmup = iter // 2``, sampling = remainder.""" @@ -40,6 +36,8 @@ def build_and_sample( show_progress If False, attempts to silence progress output (CmdStan ``show_progress``). """ + import stan + num_warmup, num_samples = iterations_to_warmup_and_samples(iterations) posterior = stan.build( model_code, data=data, random_seed=random_seed @@ -65,6 +63,7 @@ def build_and_sample( def plot_parameter_marginals(fit, par: str, filename: str, dpi: int = 300) -> None: """Histogram of each scalar component of ``par`` (similar spirit to PyStan 2 ``fit.plot``).""" import matplotlib.pyplot as plt + import numpy as np arr = np.asarray(fit[par]) if arr.ndim == 1: diff --git a/tests/test_pystan_smoke.py b/tests/test_pystan_smoke.py new file mode 100644 index 0000000..d23ff04 --- /dev/null +++ b/tests/test_pystan_smoke.py @@ -0,0 +1,78 @@ +""" +Smoke tests for PyStan 3 (``import stan``) and the bioce ``stan_run`` helpers. + +Requires: pip install pystan pytest +Stan-dependent tests skip automatically if ``stan`` is not installed. +""" +from __future__ import annotations + +import pytest + + +MINIMAL_STAN = """ +data { + int n; + array[n] real y; +} +parameters { + real mu; +} +model { + y ~ normal(mu, 1); +} +""" + + +def test_iterations_to_warmup_and_samples_matches_pystan2_style(): + from stan_run import iterations_to_warmup_and_samples + + w, s = iterations_to_warmup_and_samples(2000) + assert w == 1000 + assert s == 1000 + assert w + s == 2000 + + w2, s2 = iterations_to_warmup_and_samples(3) + assert w2 >= 1 and s2 >= 1 + assert w2 + s2 == 3 + + +def test_build_and_sample_minimal_model(): + """Compile and draw a few samples (exercises httpstan/CmdStan bridge).""" + pytest.importorskip("stan", reason="pystan not installed (pip install pystan)") + from stan_run import build_and_sample + + data = {"n": 4, "y": [0.0, 0.1, -0.05, 0.02]} + fit = build_and_sample( + MINIMAL_STAN, + data, + iterations=32, + chains=2, + njobs=2, + random_seed=12345, + show_progress=False, + ) + assert "mu" in fit + mu = fit["mu"] + assert mu.size >= 1 + # PyStan 3: scalar params often shaped (1, num_draws) + assert mu.ravel().shape[0] >= 4 + + +def test_stan_utility_diagnostics_do_not_crash(): + pytest.importorskip("stan", reason="pystan not installed (pip install pystan)") + from stan_run import build_and_sample + import stan_utility + + data = {"n": 3, "y": [0.0, 0.0, 0.0]} + fit = build_and_sample( + MINIMAL_STAN, + data, + iterations=24, + chains=2, + njobs=2, + random_seed=99, + show_progress=False, + ) + stan_utility.check_div(fit) + stan_utility.check_treedepth(fit, max_depth=10) + stan_utility.check_energy(fit) From 48daa159949450509ad543822de339aedc68e516 Mon Sep 17 00:00:00 2001 From: Wojtek Potrzebowski Date: Tue, 12 May 2026 12:41:57 +0200 Subject: [PATCH 3/5] stan_run: retry sample() without parallel_chains/show_progress on ValueError httpstan rejects these fields with ValueError (not TypeError); TAPE_ERROR from bad JSON can follow invalid kwargs. Pytest filters httpstan/importlib DeprecationWarnings; drop broken NotAppKeyWarning filter. Co-authored-by: Cursor --- README.md | 2 ++ pytest.ini | 3 +++ stan_run.py | 32 +++++++++++++++++++++----------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 087e397..4d1ab2b 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,8 @@ python -m pytest tests/test_pystan_smoke.py -q The first test only checks **warmup vs sampling split** logic and passes without PyStan. The other two tests **compile and sample** a tiny model; if `pystan` is missing they are **skipped** (pytest exit code 0). With PyStan installed, all three tests should pass. +Some httpstan builds reject sampler options such as ``parallel_chains`` or ``show_progress``; ``stan_run.build_and_sample`` then retries with only ``num_chains``, ``num_warmup``, and ``num_samples`` (parallelism and progress follow backend defaults). + `pytest.ini` in the repo root sets `pythonpath = .` so `stan_run` imports work without installing the package. ## Problems with installation on OSX 10.14 (Mojave) diff --git a/pytest.ini b/pytest.ini index c7b23ec..3082da9 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,6 @@ [pytest] pythonpath = . testpaths = tests +filterwarnings = + ignore::DeprecationWarning:httpstan + ignore::DeprecationWarning:importlib.resources._legacy diff --git a/stan_run.py b/stan_run.py index 6cf0dda..521716e 100644 --- a/stan_run.py +++ b/stan_run.py @@ -32,9 +32,10 @@ def build_and_sample( Parameters ---------- njobs - Upper bound on parallel chains (passed as ``parallel_chains`` when supported). + When the backend supports it, may map to parallel chain execution; ignored if + the installed httpstan rejects ``parallel_chains`` (falls back to defaults). show_progress - If False, attempts to silence progress output (CmdStan ``show_progress``). + When supported, passed to the sampler; ignored if the backend has no such field. """ import stan @@ -42,22 +43,31 @@ def build_and_sample( posterior = stan.build( model_code, data=data, random_seed=random_seed ) - kwargs: dict = { + base_kwargs = { "num_chains": int(chains), "num_warmup": num_warmup, "num_samples": num_samples, } - if njobs is not None and njobs > 0: - kwargs["parallel_chains"] = min(int(chains), int(njobs)) + optional = {} + if njobs is not None and int(njobs) > 0: + optional["parallel_chains"] = min(int(chains), int(njobs)) if not show_progress: - kwargs["show_progress"] = False + optional["show_progress"] = False + + kwargs = {**base_kwargs, **optional} try: return posterior.sample(**kwargs) - except TypeError: - # Older CmdStan backends may not support all kwargs. - kwargs.pop("parallel_chains", None) - kwargs.pop("show_progress", None) - return posterior.sample(**kwargs) + except (TypeError, ValueError) as exc: + err = str(exc).lower() + # Newer httpstan / PyStan backends reject some CmdStan kwargs (ValueError) or + # raise TypeError for unexpected keywords. + if optional and ( + "unknown field" in err + or "unexpected keyword" in err + or "got an unexpected keyword" in err + ): + return posterior.sample(**base_kwargs) + raise def plot_parameter_marginals(fit, par: str, filename: str, dpi: int = 300) -> None: From c9aac7fa87b993385a57787e24147d0f956fd20c Mon Sep 17 00:00:00 2001 From: Wojtek Potrzebowski Date: Tue, 19 May 2026 12:55:50 +0200 Subject: [PATCH 4/5] Streamlit app added --- .streamlit/config.toml | 14 + MANIFEST | 9 +- README.md | 50 ++ app.py | 242 ++++++++ bioce.yml | 3 + bioce_pipeline.py | 262 ++++++++ statistics.py => bioce_statistics.py | 0 data/bayesianEstimateCurve.txt | 868 +++++++++++++++++++++++++++ data/stan_scale.png | Bin 0 -> 17239 bytes data/stan_weights.png | Bin 55562 -> 63760 bytes fullBayesian.py | 2 +- fullBayesianMultimodal.py | 342 +++++++++++ fullBayesianTR.py | 2 +- hdx_weights.png | Bin 0 -> 40449 bytes multimodal_io.py | 87 +++ prepareHDX.py | 90 +++ prepareXLMS.py | 87 +++ scilifelab_theme.py | 149 +++++ serve/Dockerfile | 25 + serve/SERVE.md | 26 + serve/environment.yml | 16 + serve/examples/hdx_exp.txt | 2 + serve/examples/hdx_peptides.txt | 3 + serve/examples/xl_restraints.txt | 3 + setup.py | 7 +- stan_models.py | 221 +++++++ structure_observables.py | 351 +++++++++++ tests/fixtures/two_chain_mini_A.pdb | 24 + tests/fixtures/two_chain_mini_B.pdb | 24 + tests/test_multimodal_stan.py | 140 +++++ tests/test_pipeline.py | 71 +++ 31 files changed, 3115 insertions(+), 5 deletions(-) create mode 100644 .streamlit/config.toml create mode 100644 app.py create mode 100644 bioce_pipeline.py rename statistics.py => bioce_statistics.py (100%) create mode 100644 data/bayesianEstimateCurve.txt create mode 100644 data/stan_scale.png create mode 100644 fullBayesianMultimodal.py create mode 100644 hdx_weights.png create mode 100644 multimodal_io.py create mode 100644 prepareHDX.py create mode 100644 prepareXLMS.py create mode 100644 scilifelab_theme.py create mode 100644 serve/Dockerfile create mode 100644 serve/SERVE.md create mode 100644 serve/environment.yml create mode 100644 serve/examples/hdx_exp.txt create mode 100644 serve/examples/hdx_peptides.txt create mode 100644 serve/examples/xl_restraints.txt create mode 100644 structure_observables.py create mode 100644 tests/fixtures/two_chain_mini_A.pdb create mode 100644 tests/fixtures/two_chain_mini_B.pdb create mode 100644 tests/test_multimodal_stan.py create mode 100644 tests/test_pipeline.py diff --git a/.streamlit/config.toml b/.streamlit/config.toml new file mode 100644 index 0000000..988efa0 --- /dev/null +++ b/.streamlit/config.toml @@ -0,0 +1,14 @@ +[server] +headless = true +enableCORS = false +enableXsrfProtection = true + +[browser] +gatherUsageStats = false + +[theme] +primaryColor = "#045C64" +backgroundColor = "#FFFFFF" +secondaryBackgroundColor = "#F4F6F4" +textColor = "#2D3436" +font = "sans serif" diff --git a/MANIFEST b/MANIFEST index 7bc0d92..47f6b22 100644 --- a/MANIFEST +++ b/MANIFEST @@ -7,6 +7,13 @@ psisloo.py setup.py stan_models.py stan_utility.py -statistics.py +bioce_statistics.py variationalBayesian.py +fullBayesianMultimodal.py +multimodal_io.py +prepareHDX.py +prepareXLMS.py +bioce_pipeline.py +structure_observables.py +app.py vbw_sc.i diff --git a/README.md b/README.md index 4d1ab2b..8a642a7 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,56 @@ which graphically ilustrated distribution of population weights (shown below) an 3. Script also returns text file containing Q vector, experimental intensity, model intensity and experimental error. +### Streamlit web app (SciLifeLab Serve) + +A browser UI runs the full PDB → observables → Stan pipeline: + +```bash +pip install streamlit biopython "pystan>=3.9,<4" +streamlit run app.py +``` + +Upload PDBs (or zip), experimental SAXS / HDX / XL files, and run inference. See `serve/SERVE.md` for Docker deployment on [SciLifeLab Serve](https://serve.scilifelab.se/docs/). + +SAXS from PDB requires **FoXS** or **Pepsi-SAXS** on `PATH`. HDX and XL use **BioPython** (peptide SASA proxy and Cα distances). + +### HDX-MS and XL-MS (multimodal inference) + +Ensemble-averaged HDX uptake and probabilistic XL-MS distance restraints can be combined with SAXS (or used alone) via `fullBayesianMultimodal.py`. Per-conformer observables must be precomputed (e.g. protection factors or Cα distances); Stan multiplies independent likelihoods over a shared `simplex` weight vector. + +**Prepare inputs** + +```bash +# One file per conformer in a directory (same row order: flattened peptide×time) +python prepareHDX.py -d hdx_predictions/ -o SimulatedHDX.txt + +# One distance file per conformer (rows = crosslinks, same order as xl_restraints.dat) +python prepareXLMS.py -d xl_distances/ -o SimulatedXLdistances.txt +``` + +`hdx_exp.dat`: two columns `uptake sigma` per observation. +`xl_restraints.dat`: `res_i res_j z [d_max] [tau]` (`z=1` observed link; default `d_max=30`, `tau=3` Å). + +**Run inference** + +```bash +# SAXS + HDX + XL +python fullBayesianMultimodal.py \ + -p weights.txt -f structures.txt \ + -e simulated.dat -s SimulatedIntensities.txt \ + -H hdx_exp.dat -S SimulatedHDX.txt \ + -X xl_restraints.dat -D SimulatedXLdistances.txt \ + -i 2000 -c 4 -j 4 + +# HDX only, or XL only (same -p priors) +python fullBayesianMultimodal.py -p weights.txt -H hdx_exp.dat -S SimulatedHDX.txt +python fullBayesianMultimodal.py -p weights.txt -X xl_restraints.dat -D SimulatedXLdistances.txt +python fullBayesianMultimodal.py -p weights.txt -X xl_restraints.dat -D SimulatedXLdistances.txt --xl-fp-mixture +``` + +XL satisfaction uses a soft logistic in linker distance: ensemble probability +`p_sat = sum_k w_k * sigmoid((d_max - d_k) / tau)` with `z_l ~ Bernoulli(p_sat)`. + ### Using chemical shift data 1. In order to use chemical shift data, one needs to install SHIFTX2. This can be done by following instructions at: [SHIFTX2](http://www.shiftx2.ca/download.html). This requires running python 2.6 or later, which won't work with diff --git a/app.py b/app.py new file mode 100644 index 0000000..e40e693 --- /dev/null +++ b/app.py @@ -0,0 +1,242 @@ +""" +Bioce Streamlit app: upload PDB ensemble + SAXS / HDX / XL data → Bayesian weights. + +Run locally: + streamlit run app.py +""" +from __future__ import annotations + +import tempfile +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +import streamlit as st + +from bioce_pipeline import PipelineConfig, run_full_pipeline +from scilifelab_theme import ( + AQUA, + GRAPE, + LIME, + TEAL, + apply_matplotlib_theme, + inject_streamlit_theme, + render_footer, + render_header, +) +from structure_observables import _foxs_on_path, _HAS_BIOPYTHON, _pepsi_on_path + +st.set_page_config( + page_title="Bioce | SciLifeLab", + layout="wide", + initial_sidebar_state="expanded", +) +inject_streamlit_theme() +apply_matplotlib_theme() +render_header( + "Bioce — Bayesian ensemble inference", + "Upload PDB conformers and SAXS, HDX-MS, and/or XL-MS data to infer posterior ensemble weights.", +) + +with st.sidebar: + st.markdown( + '

' + "SciLifeLab · Bioce

", + unsafe_allow_html=True, + ) + st.header("Sampling") + iterations = st.slider("Stan iterations (total per chain)", 100, 3000, 400, 100) + chains = st.slider("Chains", 1, 8, 2) + njobs = st.slider("Parallel chains", 1, 8, 2) + st.header("Modalities") + use_saxs = st.checkbox("SAXS", value=True) + use_hdx = st.checkbox("HDX-MS", value=False) + use_xl = st.checkbox("XL-MS", value=False) + xl_fp = st.checkbox("XL false-positive mixture", value=False, disabled=not use_xl) + st.divider() + st.markdown("**Dependencies**") + st.write("FoXS:", "yes" if _foxs_on_path() else "no") + st.write("Pepsi-SAXS:", "yes" if _pepsi_on_path() else "no") + st.write("BioPython:", "yes" if _HAS_BIOPYTHON else "no") + +st.subheader("Structure library") +pdb_uploads = st.file_uploader( + "PDB files or .zip archive", + type=["pdb", "zip"], + accept_multiple_files=True, + help="One file per conformer in the ensemble.", +) + +col1, col2 = st.columns(2) + +with col1: + st.subheader("SAXS") + saxs_file = st.file_uploader( + "Experimental SAXS (.dat)", + type=["dat", "txt", "csv"], + disabled=not use_saxs, + help="Columns: q, I, sigma (whitespace-separated).", + ) + +with col2: + st.subheader("HDX-MS") + hdx_peptides = st.file_uploader( + "Peptide definitions", + type=["txt", "dat", "csv"], + disabled=not use_hdx, + help="One peptide per line: chain start_res end_res (e.g. A 10 25).", + ) + hdx_exp = st.file_uploader( + "Experimental uptake", + type=["txt", "dat", "csv"], + disabled=not use_hdx, + help="Lines: uptake sigma — or pep_idx time uptake sigma for kinetics.", + ) + +st.subheader("XL-MS") +xl_file = st.file_uploader( + "Crosslink restraints", + type=["txt", "dat"], + disabled=not use_xl, + help="res_i res_j z [d_max] [tau] — or chain_i res_i chain_j res_j z [d_max] [tau].", +) + +with st.expander("File format help"): + st.markdown( + """ +- **SAXS**: three columns `q`, `I`, `error` (same as bioce `fullBayesian.py`). +- **HDX peptides**: `chain start end` per line (1-based residue numbers). +- **HDX data**: `uptake sigma` per peptide, or `pep_idx time uptake sigma` for time series. +- **XL**: `z=1` if the crosslink was observed. Distances are computed as Cα–Cα from each PDB. +- **HDX prediction** uses mean peptide SASA (Shrake–Rupley) as a solvent-exposure proxy—not full HDX kinetics. + """ + ) + +run = st.button("Run inference", type="primary", use_container_width=True) + +if run: + if not pdb_uploads: + st.error("Upload at least one PDB file or a .zip archive.") + st.stop() + if use_saxs and not saxs_file: + st.error("SAXS enabled: upload an experimental curve.") + st.stop() + if use_hdx and (not hdx_peptides or not hdx_exp): + st.error("HDX enabled: upload peptide definitions and experimental uptake.") + st.stop() + if use_xl and not xl_file: + st.error("XL enabled: upload crosslink restraints.") + st.stop() + if use_saxs and not (_foxs_on_path() or _pepsi_on_path()): + st.error("Install FoXS or Pepsi-SAXS on PATH for SAXS simulation from PDBs.") + st.stop() + if (use_hdx or use_xl) and not _HAS_BIOPYTHON: + st.error("Install BioPython for HDX/XL from PDB (pip install biopython).") + st.stop() + if not (use_saxs or use_hdx or use_xl): + st.error("Enable at least one modality.") + st.stop() + + with tempfile.TemporaryDirectory(prefix="bioce_") as tmp: + work = Path(tmp) + config = PipelineConfig( + work_dir=work, + use_saxs=use_saxs, + use_hdx=use_hdx, + use_xl=use_xl, + iterations=iterations, + chains=chains, + njobs=njobs, + xl_fp_mixture=xl_fp, + ) + if use_saxs: + saxs_path = work / "experimental_saxs.dat" + saxs_path.write_bytes(saxs_file.getvalue()) + config.saxs_experimental = saxs_path + if use_hdx: + config.hdx_peptide_lines = hdx_peptides.getvalue().decode().splitlines() + config.hdx_exp_lines = hdx_exp.getvalue().decode().splitlines() + if use_xl: + config.xl_restraint_lines = xl_file.getvalue().decode().splitlines() + + with st.status("Running pipeline…", expanded=True) as status: + try: + st.write("Extracting PDBs and computing observables…") + result = run_full_pipeline(pdb_uploads, config) + status.update(label="Done", state="complete") + except Exception as exc: + status.update(label="Failed", state="error") + st.exception(exc) + st.stop() + + m1, m2, m3, m4 = st.columns(4) + if result.saxs_chi2 is not None: + m1.metric("SAXS χ²", f"{result.saxs_chi2:.3f}") + if result.jsd is not None: + m2.metric("JSD", f"{result.jsd:.4f}") + if result.hdx_rmse is not None: + m3.metric("HDX RMSE", f"{result.hdx_rmse:.4f}") + if result.xl_mean_psat is not None: + m4.metric("Mean XL p(sat)", f"{result.xl_mean_psat:.3f}") + + st.subheader("Posterior mean weights") + df = pd.DataFrame(result.tables["weights"]) + st.dataframe(df, use_container_width=True) + fig, ax = plt.subplots(figsize=(8, 3)) + colors = [TEAL if w == df["weight"].max() else LIME for w in df["weight"]] + ax.bar(df["structure"], df["weight"], color=colors, edgecolor=TEAL, linewidth=0.6) + ax.set_ylabel("Weight") + ax.set_facecolor("#FAFCFA") + ax.grid(axis="y", linestyle="--", alpha=0.5) + ax.tick_params(axis="x", rotation=45) + plt.tight_layout() + st.pyplot(fig) + plt.close(fig) + + if "saxs_fit" in result.plots: + st.subheader("SAXS fit") + curve = np.genfromtxt(result.plots["saxs_fit"]) + fig2, ax2 = plt.subplots(figsize=(7, 4)) + ax2.errorbar( + curve[:, 0], curve[:, 1], yerr=curve[:, 3], + fmt="o", ms=3, color=GRAPE, ecolor=AQUA, label="Experiment", alpha=0.85, + ) + ax2.plot( + curve[:, 0], curve[:, 2], "-", color=TEAL, + label="Weighted model", lw=2, + ) + ax2.set_xlabel("q (Å⁻¹)") + ax2.set_ylabel("I(q)") + ax2.legend(frameon=True) + ax2.set_facecolor("#FAFCFA") + ax2.set_yscale("log") + st.pyplot(fig2) + plt.close(fig2) + + for key, path in result.plots.items(): + if path.suffix == ".png": + st.image(str(path), caption=key) + + if "xl_psat" in result.tables: + st.subheader("XL satisfaction probability (posterior mean)") + psat = result.tables["xl_psat"] + fig_xl, ax_xl = plt.subplots(figsize=(7, 2.8)) + ax_xl.plot(psat, color=TEAL, marker="o", markersize=5, markerfacecolor=LIME) + ax_xl.fill_between(range(len(psat)), psat, alpha=0.2, color=AQUA) + ax_xl.set_ylim(0, 1) + ax_xl.set_xlabel("Crosslink index") + ax_xl.set_ylabel("p(satisfied)") + ax_xl.set_facecolor("#FAFCFA") + ax_xl.grid(axis="y", linestyle="--", alpha=0.5) + st.pyplot(fig_xl) + plt.close(fig_xl) + + st.download_button( + "Download weights CSV", + df.to_csv(index=False).encode(), + file_name="bayesian_weights.csv", + mime="text/csv", + ) + +render_footer() diff --git a/bioce.yml b/bioce.yml index 0b0db3e..2809bce 100644 --- a/bioce.yml +++ b/bioce.yml @@ -6,6 +6,9 @@ channels: - intel dependencies: - seaborn +- biopython +- pandas +- streamlit - clang - openmp - swig diff --git a/bioce_pipeline.py b/bioce_pipeline.py new file mode 100644 index 0000000..908e7a6 --- /dev/null +++ b/bioce_pipeline.py @@ -0,0 +1,262 @@ +""" +End-to-end pipeline: PDBs + experimental data → observables → Stan inference. +""" +from __future__ import annotations + +import os +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any, Dict, List, Optional, Sequence + +import numpy as np + +from bioce_statistics import calculateChiCrysol, JensenShannonDiv, mean_for_weights +from fullBayesian import calculate_stats, combine_curve, execute_stan +from fullBayesianMultimodal import ( + execute_stan_hdx, + execute_stan_hdx_xl, + execute_stan_saxs_hdx, + execute_stan_saxs_hdx_xl, + execute_stan_saxs_xl, + execute_stan_xl, +) +from structure_observables import ( + extract_pdbs, + generate_hdx_matrix, + generate_saxs_matrix, + generate_xl_distance_matrix, + parse_hdx_experimental, + parse_hdx_peptides, + parse_xl_restraints, + write_hdx_experimental, +) + + +@dataclass +class PipelineConfig: + work_dir: Path + use_saxs: bool = True + use_hdx: bool = False + use_xl: bool = False + iterations: int = 400 + chains: int = 2 + njobs: int = 2 + saxs_experimental: Optional[Path] = None + hdx_peptide_lines: Optional[List[str]] = None + hdx_exp_lines: Optional[List[str]] = None + xl_restraint_lines: Optional[List[str]] = None + xl_fp_mixture: bool = False + + +@dataclass +class PipelineResult: + fit: Any + work_dir: Path + structure_names: List[str] + bayesian_weights: np.ndarray + saxs_chi2: Optional[float] = None + jsd: Optional[float] = None + hdx_rmse: Optional[float] = None + xl_mean_psat: Optional[float] = None + plots: Dict[str, Path] = field(default_factory=dict) + tables: Dict[str, Any] = field(default_factory=dict) + + +def _read_experimental_saxs(path: Path) -> np.ndarray: + return np.genfromtxt(path, dtype=np.float64) + + +def _uniform_priors(n: int) -> np.ndarray: + return np.full(n, 1.0 / n) + + +def prepare_from_pdbs( + pdb_uploads, + config: PipelineConfig, +) -> Dict[str, Path]: + """Extract PDBs and write simulated observable matrices into work_dir.""" + config.work_dir.mkdir(parents=True, exist_ok=True) + pdb_dir = config.work_dir / "pdbs" + paths = extract_pdbs(pdb_uploads, pdb_dir) + n = len(paths) + names = [p.stem for p in paths] + artifacts: Dict[str, Path] = {"pdb_dir": pdb_dir} + + priors = _uniform_priors(n) + priors_path = config.work_dir / "weights.txt" + np.savetxt(priors_path, priors, fmt="%.8g") + names_path = config.work_dir / "structures.txt" + with open(names_path, "w") as handle: + handle.write(" ".join(names) + "\n") + artifacts["priors"] = priors_path + artifacts["names"] = names_path + + if config.use_saxs: + if not config.saxs_experimental: + raise ValueError("SAXS experimental file required.") + sim_p, w_p, n_p, _ = generate_saxs_matrix( + paths, config.saxs_experimental, config.work_dir / "saxs" + ) + artifacts["sim_saxs"] = sim_p + artifacts["priors"] = w_p + artifacts["names"] = n_p + shutil_copy_exp = config.work_dir / "saxs" / config.saxs_experimental.name + artifacts["exp_saxs"] = shutil_copy_exp + + if config.use_xl: + if not config.xl_restraint_lines: + raise ValueError("XL restraints required.") + records = parse_xl_restraints(config.xl_restraint_lines) + xl_p, sim_xl, _ = generate_xl_distance_matrix( + paths, records, config.work_dir / "xl" + ) + artifacts["xl_restraints"] = xl_p + artifacts["sim_xl"] = sim_xl + + if config.use_hdx: + if not config.hdx_peptide_lines or not config.hdx_exp_lines: + raise ValueError("HDX peptides and experimental data required.") + peptides = parse_hdx_peptides(config.hdx_peptide_lines) + uptake, sigma, pep_idx = parse_hdx_experimental( + config.hdx_exp_lines, len(peptides) + ) + hdx_exp_path = config.work_dir / "hdx" / "hdx_exp.dat" + hdx_exp_path.parent.mkdir(parents=True, exist_ok=True) + write_hdx_experimental(uptake, sigma, hdx_exp_path) + sim_h, _ = generate_hdx_matrix( + paths, peptides, config.work_dir / "hdx", pep_indices=pep_idx + ) + artifacts["hdx_exp"] = hdx_exp_path + artifacts["sim_hdx"] = sim_h + + return artifacts + + +def run_inference( + config: PipelineConfig, + artifacts: Dict[str, Path], +) -> PipelineResult: + """Run Stan after prepare_from_pdbs.""" + work = config.work_dir + old_cwd = os.getcwd() + os.chdir(work) + try: + priors = np.genfromtxt(artifacts["priors"]) + names = open(artifacts["names"]).read().split() + + has_saxs = config.use_saxs and "sim_saxs" in artifacts + has_hdx = config.use_hdx and "sim_hdx" in artifacts + has_xl = config.use_xl and "sim_xl" in artifacts + + it, ch, nj = config.iterations, config.chains, config.njobs + + if has_saxs and has_hdx and has_xl: + exp = _read_experimental_saxs(artifacts["exp_saxs"]) + sim = np.genfromtxt(artifacts["sim_saxs"]) + fit = execute_stan_saxs_hdx_xl( + exp, sim, + str(artifacts["hdx_exp"]), str(artifacts["sim_hdx"]), + str(artifacts["xl_restraints"]), str(artifacts["sim_xl"]), + priors, it, ch, nj, + ) + prefix = "saxs_hdx_xl" + elif has_saxs and has_hdx: + exp = _read_experimental_saxs(artifacts["exp_saxs"]) + sim = np.genfromtxt(artifacts["sim_saxs"]) + fit = execute_stan_saxs_hdx( + exp, sim, + str(artifacts["hdx_exp"]), str(artifacts["sim_hdx"]), + priors, it, ch, nj, + ) + prefix = "saxs_hdx" + elif has_saxs and has_xl: + exp = _read_experimental_saxs(artifacts["exp_saxs"]) + sim = np.genfromtxt(artifacts["sim_saxs"]) + fit = execute_stan_saxs_xl( + exp, sim, + str(artifacts["xl_restraints"]), str(artifacts["sim_xl"]), + priors, it, ch, nj, + ) + prefix = "saxs_xl" + elif has_hdx and has_xl: + fit = execute_stan_hdx_xl( + str(artifacts["hdx_exp"]), str(artifacts["sim_hdx"]), + str(artifacts["xl_restraints"]), str(artifacts["sim_xl"]), + priors, it, ch, nj, + ) + prefix = "hdx_xl" + elif has_saxs: + exp = _read_experimental_saxs(artifacts["exp_saxs"]) + sim = np.genfromtxt(artifacts["sim_saxs"]) + fit = execute_stan(exp, sim, priors, it, ch, nj) + prefix = "saxs" + elif has_hdx: + fit = execute_stan_hdx( + str(artifacts["hdx_exp"]), str(artifacts["sim_hdx"]), + priors, it, ch, nj, + ) + prefix = "hdx" + elif has_xl: + fit = execute_stan_xl( + str(artifacts["xl_restraints"]), str(artifacts["sim_xl"]), + priors, it, ch, nj, use_fp=config.xl_fp_mixture, + ) + prefix = "xl" + else: + raise ValueError("No modality enabled.") + + weights = np.asarray(mean_for_weights(fit)) + result = PipelineResult( + fit=fit, + work_dir=work, + structure_names=names, + bayesian_weights=weights, + ) + + plots = {} + for name in ( + "{}_weights.png".format(prefix), + "stan_weights.png", + "{}_scale.png".format(prefix), + "stan_scale.png", + ): + p = work / name + if p.exists(): + plots["weights" if "weight" in name else "scale"] = p + + if has_saxs: + exp = _read_experimental_saxs(artifacts["exp_saxs"]) + sim = np.genfromtxt(artifacts["sim_saxs"]) + bw, jsd, chi2 = calculate_stats(fit, exp, sim) + result.bayesian_weights = bw + result.jsd = float(jsd) + result.saxs_chi2 = float(chi2) + curve_path = work / "bayesianEstimateCurve.txt" + if curve_path.exists(): + plots["saxs_fit"] = curve_path + + if has_hdx: + from multimodal_io import read_hdx_experimental + tu, _ = read_hdx_experimental(str(artifacts["hdx_exp"])) + sim_h = np.genfromtxt(artifacts["sim_hdx"]) + pred = sim_h.T @ result.bayesian_weights + result.hdx_rmse = float(np.sqrt(np.mean((tu - pred) ** 2))) + + if has_xl and "p_sat" in fit: + psat = np.asarray(fit["p_sat"]) + result.xl_mean_psat = float(np.mean(psat)) + result.tables["xl_psat"] = np.mean(psat, axis=1) + + result.plots = plots + result.tables["weights"] = { + "structure": names, + "weight": result.bayesian_weights, + } + return result + finally: + os.chdir(old_cwd) + + +def run_full_pipeline(pdb_uploads, config: PipelineConfig) -> PipelineResult: + artifacts = prepare_from_pdbs(pdb_uploads, config) + return run_inference(config, artifacts) diff --git a/statistics.py b/bioce_statistics.py similarity index 100% rename from statistics.py rename to bioce_statistics.py diff --git a/data/bayesianEstimateCurve.txt b/data/bayesianEstimateCurve.txt new file mode 100644 index 0000000..f453f9f --- /dev/null +++ b/data/bayesianEstimateCurve.txt @@ -0,0 +1,868 @@ +5.658709999999999955e-03 2.605899000000000063e-02 2.616062206258566031e-02 6.538312999999999590e-03 +6.048959999999999927e-03 2.604476999999999834e-02 2.614642553244841960e-02 2.102154000000000175e-03 +6.439219999999999840e-03 2.602932999999999983e-02 2.613093788388811539e-02 1.494653000000000084e-03 +6.829469999999999812e-03 2.601389000000000132e-02 2.611545728295707608e-02 9.480715999999999710e-04 +7.219729999999999724e-03 2.599862000000000006e-02 2.610009012072817686e-02 8.027344999999999967e-04 +7.609979999999999696e-03 2.598347000000000157e-02 2.608481282367591803e-02 6.931519000000000135e-04 +8.000240000000000476e-03 2.596833000000000127e-02 2.606953600827629711e-02 6.532776999999999811e-04 +8.390490000000000448e-03 2.595048000000000146e-02 2.605169141831172389e-02 6.185780999999999788e-04 +8.780750000000000360e-03 2.593263000000000165e-02 2.603384489006434904e-02 5.481044000000000237e-04 +9.171000000000000332e-03 2.591364999999999988e-02 2.601488655363431990e-02 4.967650000000000321e-04 +9.561260000000000245e-03 2.589324000000000139e-02 2.599452072152295609e-02 4.533153000000000269e-04 +9.951510000000000217e-03 2.587282999999999944e-02 2.597415471944051341e-02 4.240569999999999821e-04 +1.034179999999999995e-02 2.585194000000000034e-02 2.595310307588851589e-02 4.169377000000000044e-04 +1.073200000000000022e-02 2.583097999999999991e-02 2.593195733719368470e-02 3.988217999999999971e-04 +1.112229999999999989e-02 2.580864000000000075e-02 2.590946588873705922e-02 3.705421999999999774e-04 +1.151250000000000016e-02 2.578327000000000049e-02 2.588401921184589616e-02 3.640655000000000128e-04 +1.190279999999999984e-02 2.575790999999999845e-02 2.585857098873837329e-02 3.507869999999999738e-04 +1.229300000000000011e-02 2.573218999999999854e-02 2.583275907384328246e-02 3.442941999999999943e-04 +1.268329999999999978e-02 2.570634999999999934e-02 2.580682189420015898e-02 3.282116999999999869e-04 +1.307350000000000005e-02 2.568010000000000084e-02 2.578047611010549772e-02 3.200821999999999760e-04 +1.346379999999999973e-02 2.565202000000000107e-02 2.575234086343195297e-02 3.101631999999999783e-04 +1.385409999999999940e-02 2.562394999999999951e-02 2.572420620700981500e-02 3.058952999999999781e-04 +1.424429999999999967e-02 2.559552000000000008e-02 2.569570709559651153e-02 2.950391999999999923e-04 +1.463459999999999935e-02 2.556686999999999849e-02 2.566696773545071308e-02 3.078153999999999938e-04 +1.502479999999999961e-02 2.553804000000000143e-02 2.563803874317140477e-02 3.012835000000000166e-04 +1.541509999999999929e-02 2.550625000000000114e-02 2.560611445868011052e-02 2.733028000000000085e-04 +1.580530000000000129e-02 2.547446999999999906e-02 2.557420230719907248e-02 2.734953000000000037e-04 +1.619560000000000097e-02 2.544131999999999991e-02 2.554096425242296034e-02 2.639868000000000184e-04 +1.658580000000000124e-02 2.540685000000000165e-02 2.550644328570561212e-02 2.644279000000000030e-04 +1.697610000000000091e-02 2.537236000000000005e-02 2.547190510867966898e-02 2.621254999999999961e-04 +1.736630000000000118e-02 2.533708999999999961e-02 2.543644993791249753e-02 2.570121000000000189e-04 +1.775660000000000086e-02 2.530175999999999953e-02 2.540092452031132708e-02 2.476986999999999939e-04 +1.814680000000000112e-02 2.526528999999999928e-02 2.536432687947702200e-02 2.443009000000000109e-04 +1.853710000000000080e-02 2.522695000000000146e-02 2.532594727996597198e-02 2.489613000000000112e-04 +1.892730000000000107e-02 2.518861000000000017e-02 2.528757043286513836e-02 2.444742000000000182e-04 +1.931760000000000074e-02 2.514983999999999970e-02 2.524865907802703938e-02 2.372960999999999929e-04 +1.970780000000000101e-02 2.511098000000000149e-02 2.520964089121384422e-02 2.312833000000000087e-04 +2.009810000000000069e-02 2.507155000000000147e-02 2.517006786087300566e-02 2.374785999999999933e-04 +2.048830000000000096e-02 2.503052000000000055e-02 2.512892251980788916e-02 2.293792000000000010e-04 +2.087860000000000063e-02 2.498946999999999974e-02 2.508775786016333512e-02 2.149956999999999921e-04 +2.126880000000000090e-02 2.494715000000000127e-02 2.504529666142804936e-02 2.118533000000000135e-04 +2.165910000000000057e-02 2.490424999999999930e-02 2.500223509887694942e-02 2.192177999999999868e-04 +2.204930000000000084e-02 2.486106999999999900e-02 2.495890729806614361e-02 2.140549999999999961e-04 +2.243960000000000052e-02 2.481596999999999970e-02 2.491366631818287686e-02 2.131601000000000058e-04 +2.282980000000000079e-02 2.477087000000000039e-02 2.486843542063147533e-02 2.061498000000000096e-04 +2.322010000000000046e-02 2.472517000000000117e-02 2.482258766903275987e-02 2.121330999999999900e-04 +2.361030000000000073e-02 2.467901000000000122e-02 2.477627286130649337e-02 2.020128000000000049e-04 +2.400060000000000041e-02 2.463283999999999960e-02 2.472994973412406122e-02 2.001299999999999965e-04 +2.439080000000000067e-02 2.458416000000000073e-02 2.468109980321592234e-02 1.995239999999999958e-04 +2.478110000000000035e-02 2.453547999999999840e-02 2.463223546107055212e-02 1.976458000000000110e-04 +2.517130000000000062e-02 2.448625999999999928e-02 2.458284903446922298e-02 2.016204000000000119e-04 +2.556160000000000029e-02 2.443632999999999916e-02 2.453275775761378646e-02 2.024715000000000011e-04 +2.595180000000000056e-02 2.438641000000000073e-02 2.448268596933279515e-02 1.909114999999999966e-04 +2.634210000000000024e-02 2.433557000000000081e-02 2.443166877408394985e-02 2.043170000000000023e-04 +2.673230000000000051e-02 2.428461000000000161e-02 2.438053628808508830e-02 1.936863999999999893e-04 +2.712260000000000018e-02 2.423283999999999855e-02 2.432858389157794773e-02 1.903317000000000127e-04 +2.751280000000000045e-02 2.417932000000000067e-02 2.427488591406776197e-02 2.043404999999999928e-04 +2.790310000000000012e-02 2.412577999999999945e-02 2.422117591591303049e-02 1.948816000000000079e-04 +2.829330000000000039e-02 2.407114999999999949e-02 2.416634125602292946e-02 1.970847999999999871e-04 +2.868360000000000007e-02 2.401613999999999832e-02 2.411112750156875831e-02 1.975338000000000094e-04 +2.907380000000000034e-02 2.396099999999999966e-02 2.405578088241197565e-02 2.035050000000000108e-04 +2.946400000000000061e-02 2.390524000000000121e-02 2.399986446249081223e-02 2.001492000000000115e-04 +2.985430000000000028e-02 2.384947000000000109e-02 2.394393501132088473e-02 1.964019000000000008e-04 +3.024450000000000055e-02 2.379231000000000054e-02 2.388658683189068960e-02 1.985501000000000084e-04 +3.063480000000000023e-02 2.373429000000000164e-02 2.382837053411693967e-02 1.955279999999999887e-04 +3.102500000000000049e-02 2.367622000000000129e-02 2.377009792278205608e-02 1.928091999999999903e-04 +3.141530000000000017e-02 2.361707999999999863e-02 2.371078538673483324e-02 1.953644999999999893e-04 +3.180550000000000044e-02 2.355795000000000111e-02 2.365148633163891012e-02 1.973626999999999933e-04 +3.219579999999999664e-02 2.349814999999999890e-02 2.359147076443030705e-02 1.887171000000000096e-04 +3.258599999999999691e-02 2.343773000000000037e-02 2.353078441378800637e-02 1.907273999999999927e-04 +3.297619999999999718e-02 2.337728999999999849e-02 2.347009045720021767e-02 1.884219000000000128e-04 +3.336650000000000033e-02 2.331555999999999906e-02 2.340816778604896606e-02 1.905653999999999898e-04 +3.375670000000000059e-02 2.325375999999999832e-02 2.334618798816043284e-02 1.929118000000000120e-04 +3.414699999999999680e-02 2.319146999999999875e-02 2.328369695884240087e-02 1.909141999999999957e-04 +3.453719999999999707e-02 2.312840999999999855e-02 2.322042092153627293e-02 1.899299000000000126e-04 +3.492750000000000021e-02 2.306534000000000015e-02 2.315712794439268515e-02 1.871816000000000105e-04 +3.531770000000000048e-02 2.300048999999999844e-02 2.309206454526258404e-02 1.875427999999999893e-04 +3.570790000000000075e-02 2.293525000000000078e-02 2.302660681167787229e-02 1.775640000000000096e-04 +3.609819999999999696e-02 2.286989000000000036e-02 2.296102696796075565e-02 1.800720000000000066e-04 +3.648839999999999723e-02 2.280425000000000160e-02 2.289514687198180784e-02 1.738188000000000103e-04 +3.687870000000000037e-02 2.273860000000000117e-02 2.282925837235896122e-02 1.691143999999999948e-04 +3.726890000000000064e-02 2.267243999999999857e-02 2.276287633017694267e-02 1.711170000000000085e-04 +3.765910000000000091e-02 2.260602999999999918e-02 2.269626427694436052e-02 1.703656000000000007e-04 +3.804939999999999711e-02 2.253940000000000110e-02 2.262940981175838931e-02 1.651758999999999985e-04 +3.843959999999999738e-02 2.247125999999999985e-02 2.256096766360838474e-02 1.651951000000000135e-04 +3.882979999999999765e-02 2.240311999999999859e-02 2.249253149108529359e-02 1.570115000000000128e-04 +3.922010000000000080e-02 2.233434000000000114e-02 2.242349985648887239e-02 1.585086000000000090e-04 +3.961030000000000106e-02 2.226507999999999959e-02 2.235403099202725591e-02 1.607230000000000128e-04 +4.000059999999999727e-02 2.219580000000000164e-02 2.228453954574925733e-02 1.557550999999999955e-04 +4.039079999999999754e-02 2.212518999999999944e-02 2.221370317630664420e-02 1.543247000000000032e-04 +4.078099999999999781e-02 2.205458999999999892e-02 2.214287531483003640e-02 1.524874000000000064e-04 +4.117130000000000095e-02 2.198353999999999933e-02 2.207156502516190513e-02 1.512198999999999987e-04 +4.156150000000000122e-02 2.191194000000000128e-02 2.199968055860630420e-02 1.568401000000000029e-04 +4.195170000000000149e-02 2.184033999999999975e-02 2.192779741440599103e-02 1.512809000000000105e-04 +4.234199999999999769e-02 2.176865999999999871e-02 2.185582541119675953e-02 1.485494999999999980e-04 +4.273219999999999796e-02 2.169698000000000113e-02 2.178386728115323800e-02 1.494238999999999908e-04 +4.312239999999999823e-02 2.162477999999999970e-02 2.171138286684865318e-02 1.502061000000000119e-04 +4.351270000000000138e-02 2.155142000000000169e-02 2.163775730809103237e-02 1.504898999999999972e-04 +4.390290000000000165e-02 2.147806000000000021e-02 2.156414080090841498e-02 1.444441000000000084e-04 +4.429310000000000191e-02 2.140384999999999857e-02 2.148966756723826727e-02 1.433128999999999946e-04 +4.468339999999999812e-02 2.132933999999999872e-02 2.141489893531567726e-02 1.426393999999999882e-04 +4.507359999999999839e-02 2.125476999999999922e-02 2.134006930622955184e-02 1.384957000000000028e-04 +4.546379999999999866e-02 2.117990000000000150e-02 2.126494343211247223e-02 1.401301999999999886e-04 +4.585410000000000180e-02 2.110501999999999864e-02 2.118979691702054316e-02 1.368936000000000068e-04 +4.624430000000000207e-02 2.102901000000000076e-02 2.111348932754061880e-02 1.376049000000000011e-04 +4.663450000000000234e-02 2.095233000000000165e-02 2.103648214514772935e-02 1.410387999999999887e-04 +4.702479999999999855e-02 2.087566000000000074e-02 2.095949130355157300e-02 1.375681000000000018e-04 +4.741499999999999881e-02 2.079947999999999866e-02 2.088303758512468494e-02 1.362973000000000071e-04 +4.780519999999999908e-02 2.072330000000000005e-02 2.080657668109291494e-02 1.386550000000000065e-04 +4.819539999999999935e-02 2.064625999999999961e-02 2.072925708084319071e-02 1.386187999999999949e-04 +4.858570000000000250e-02 2.056830999999999937e-02 2.065105059226504322e-02 1.403200000000000118e-04 +4.897590000000000277e-02 2.049039000000000069e-02 2.057286657319130560e-02 1.400907999999999971e-04 +4.936610000000000303e-02 2.041286000000000142e-02 2.049503381128415722e-02 1.407901999999999992e-04 +4.975639999999999924e-02 2.033532999999999868e-02 2.041720223102738013e-02 1.419589999999999871e-04 +5.014659999999999951e-02 2.025754999999999917e-02 2.033911230906939455e-02 1.429395000000000025e-04 +5.053679999999999978e-02 2.017930999999999891e-02 2.026056093967545588e-02 1.444802000000000130e-04 +5.092700000000000005e-02 2.010106999999999866e-02 2.018200928790250467e-02 1.396145999999999965e-04 +5.131730000000000319e-02 2.002090999999999940e-02 2.010155013096629265e-02 1.403297000000000127e-04 +5.170750000000000346e-02 1.994032999999999917e-02 2.002067715298700562e-02 1.399915999999999964e-04 +5.209769999999999679e-02 1.985984999999999834e-02 1.993989845014924878e-02 1.370638999999999939e-04 +5.248789999999999706e-02 1.977968000000000087e-02 1.985942781678529112e-02 1.392661999999999915e-04 +5.287809999999999733e-02 1.969950999999999994e-02 1.977894999785135763e-02 1.382088999999999974e-04 +5.326840000000000047e-02 1.961970000000000033e-02 1.969884494753471743e-02 1.374512000000000097e-04 +5.365860000000000074e-02 1.954010999999999942e-02 1.961894403765851447e-02 1.363897999999999998e-04 +5.404880000000000101e-02 1.946029000000000161e-02 1.953881890439435748e-02 1.397796999999999993e-04 +5.443900000000000128e-02 1.937890999999999919e-02 1.945712248120418097e-02 1.365279000000000113e-04 +5.482929999999999748e-02 1.929749999999999868e-02 1.937539567089395162e-02 1.324386999999999895e-04 +5.521949999999999775e-02 1.921609999999999985e-02 1.929367191118193728e-02 1.323242000000000027e-04 +5.560969999999999802e-02 1.913466999999999946e-02 1.921191688852882593e-02 1.303377000000000040e-04 +5.599989999999999829e-02 1.905325000000000074e-02 1.913016916381843346e-02 1.282578000000000038e-04 +5.639009999999999856e-02 1.897110999999999936e-02 1.904770657616991839e-02 1.292793999999999942e-04 +5.678029999999999883e-02 1.888897000000000145e-02 1.896524398853013937e-02 1.298956000000000036e-04 +5.717060000000000197e-02 1.880679999999999852e-02 1.888275375382969523e-02 1.257393000000000041e-04 +5.756080000000000224e-02 1.872464000000000073e-02 1.880027243959095373e-02 1.250178000000000010e-04 +5.795100000000000251e-02 1.864247999999999947e-02 1.871779171569387321e-02 1.245061999999999905e-04 +5.834120000000000278e-02 1.855971999999999830e-02 1.863470501784624325e-02 1.245713999999999979e-04 +5.873140000000000305e-02 1.847688999999999929e-02 1.855153903662197859e-02 1.268389000000000029e-04 +5.912160000000000332e-02 1.839404999999999860e-02 1.846836326163447603e-02 1.221787999999999965e-04 +5.951189999999999952e-02 1.831115999999999994e-02 1.838514979396218066e-02 1.202135999999999971e-04 +5.990209999999999979e-02 1.822829000000000116e-02 1.830195129957841102e-02 1.205508000000000046e-04 +6.029230000000000006e-02 1.814539000000000082e-02 1.821869290184508633e-02 1.199057999999999998e-04 +6.068250000000000033e-02 1.806249000000000049e-02 1.813541556231786436e-02 1.196926000000000066e-04 +6.107270000000000060e-02 1.797939999999999955e-02 1.805196406641292745e-02 1.215309000000000056e-04 +6.146290000000000087e-02 1.789556000000000133e-02 1.796779688773924438e-02 1.190594999999999940e-04 +6.185310000000000114e-02 1.781171000000000143e-02 1.788362838675769395e-02 1.213999000000000030e-04 +6.224330000000000140e-02 1.772804000000000046e-02 1.779963720853651801e-02 1.203586999999999967e-04 +6.263349999999999473e-02 1.764446999999999890e-02 1.771575186057049861e-02 1.177072999999999970e-04 +6.302380000000000482e-02 1.756090000000000081e-02 1.763187033451096147e-02 1.180958999999999953e-04 +6.341399999999999815e-02 1.747786999999999952e-02 1.754845157169272404e-02 1.175629999999999991e-04 +6.380420000000000536e-02 1.739484000000000169e-02 1.746504173716099256e-02 1.154626999999999948e-04 +6.419439999999999868e-02 1.731098999999999832e-02 1.738082843969512153e-02 1.158778999999999972e-04 +6.458460000000000589e-02 1.722631000000000162e-02 1.729581794735354089e-02 1.142516000000000023e-04 +6.497479999999999922e-02 1.714165000000000133e-02 1.721081475300145422e-02 1.179614000000000053e-04 +6.536500000000000643e-02 1.705813000000000121e-02 1.712698946708405071e-02 1.134273000000000041e-04 +6.575519999999999976e-02 1.697469999999999882e-02 1.704324910729296955e-02 1.138225000000000033e-04 +6.614539999999999309e-02 1.689097000000000168e-02 1.695918753140932336e-02 1.141267999999999997e-04 +6.653560000000000030e-02 1.680672000000000069e-02 1.687458106879960104e-02 1.139221999999999983e-04 +6.692579999999999363e-02 1.672246000000000149e-02 1.678996713824609799e-02 1.118457999999999990e-04 +6.731600000000000084e-02 1.663859000000000171e-02 1.670571967990813755e-02 1.111126999999999975e-04 +6.770619999999999417e-02 1.655480999999999966e-02 1.662155061615788049e-02 1.102739999999999949e-04 +6.809640000000000137e-02 1.647097000000000144e-02 1.653734311087814296e-02 1.108059999999999959e-04 +6.848659999999999470e-02 1.638695999999999903e-02 1.645301678229664152e-02 1.078567000000000002e-04 +6.887680000000000191e-02 1.630293000000000020e-02 1.636868222549060087e-02 1.079058999999999997e-04 +6.926699999999999524e-02 1.621883000000000005e-02 1.628425341034724777e-02 1.088324000000000051e-04 +6.965720000000000245e-02 1.613469000000000014e-02 1.619977826552451011e-02 1.054888000000000055e-04 +7.004739999999999578e-02 1.605060999999999988e-02 1.611537158983979146e-02 1.065766000000000055e-04 +7.043760000000000299e-02 1.596699999999999856e-02 1.603144840729229387e-02 1.057296000000000050e-04 +7.082779999999999632e-02 1.588339999999999891e-02 1.594752536272911123e-02 1.054971000000000034e-04 +7.121800000000000352e-02 1.579985999999999891e-02 1.586363310298559079e-02 1.064822999999999952e-04 +7.160819999999999685e-02 1.571635999999999866e-02 1.577975762019730860e-02 1.057505999999999968e-04 +7.199840000000000406e-02 1.563287000000000010e-02 1.569588376776566083e-02 1.060603000000000050e-04 +7.238859999999999739e-02 1.554901000000000026e-02 1.561168883189871688e-02 1.041179000000000014e-04 +7.277880000000000460e-02 1.546516000000000036e-02 1.552749659743643887e-02 1.048078999999999965e-04 +7.316899999999999793e-02 1.538139999999999993e-02 1.544340075760752481e-02 1.042733999999999968e-04 +7.355920000000000514e-02 1.529776000000000052e-02 1.535942896224578094e-02 1.006476000000000018e-04 +7.394939999999999847e-02 1.521410999999999944e-02 1.527544981134015649e-02 9.874159999999999665e-05 +7.433950000000000280e-02 1.513095000000000065e-02 1.519191771079473653e-02 9.938579999999999973e-05 +7.472969999999999613e-02 1.504783999999999983e-02 1.510843537956833502e-02 1.014812000000000000e-04 +7.511990000000000334e-02 1.496485999999999997e-02 1.502507671237522484e-02 1.014849999999999948e-04 +7.551009999999999667e-02 1.488215000000000024e-02 1.494201267182827461e-02 9.770799999999999353e-05 +7.590030000000000387e-02 1.479944000000000051e-02 1.485894279364298638e-02 9.800800000000000083e-05 +7.629049999999999720e-02 1.471698999999999924e-02 1.477614466245610186e-02 9.677200000000000114e-05 +7.668070000000000441e-02 1.463461999999999923e-02 1.469343688698711639e-02 9.775429999999999341e-05 +7.707089999999999774e-02 1.455221999999999939e-02 1.461068916925587283e-02 9.549610000000000206e-05 +7.746100000000000207e-02 1.446968000000000039e-02 1.452781386919502378e-02 9.513919999999999875e-05 +7.785119999999999540e-02 1.438712999999999971e-02 1.444491660580846104e-02 9.770649999999999702e-05 +7.824140000000000261e-02 1.430488000000000072e-02 1.436233045946878721e-02 9.529270000000000329e-05 +7.863159999999999594e-02 1.422283000000000054e-02 1.427993459091195065e-02 9.643389999999999840e-05 +7.902180000000000315e-02 1.414078000000000036e-02 1.419753652017903545e-02 9.496639999999999932e-05 +7.941199999999999648e-02 1.405880999999999971e-02 1.411522168003312601e-02 9.281660000000000171e-05 +7.980210000000000081e-02 1.397687000000000061e-02 1.403292925548272306e-02 9.309979999999999927e-05 +8.019229999999999414e-02 1.389486000000000020e-02 1.395056495690346829e-02 9.322759999999999489e-05 +8.058250000000000135e-02 1.381279000000000014e-02 1.386815161594708544e-02 9.204930000000000653e-05 +8.097269999999999468e-02 1.373072000000000008e-02 1.378573869526804677e-02 9.424679999999999354e-05 +8.136290000000000189e-02 1.364999000000000073e-02 1.370465831832146444e-02 9.418200000000000053e-05 +8.175300000000000622e-02 1.356939000000000062e-02 1.362370536413650204e-02 9.375499999999999936e-05 +8.214319999999999955e-02 1.348890999999999979e-02 1.354288902835608377e-02 9.299580000000000162e-05 +8.253340000000000676e-02 1.340868999999999915e-02 1.346234826006290942e-02 9.085749999999999534e-05 +8.292360000000000009e-02 1.332848000000000019e-02 1.338181495972442722e-02 9.101269999999999684e-05 +8.331370000000000442e-02 1.324830999999999925e-02 1.330131282203845904e-02 9.108979999999999649e-05 +8.370389999999999775e-02 1.316813000000000011e-02 1.322079307304159512e-02 8.960849999999999425e-05 +8.409410000000000496e-02 1.308808000000000020e-02 1.314039956958151616e-02 8.983630000000000586e-05 +8.448419999999999541e-02 1.300845999999999947e-02 1.306043089402079624e-02 8.921549999999999988e-05 +8.487440000000000262e-02 1.292882000000000059e-02 1.298044935338811892e-02 9.003020000000000413e-05 +8.526459999999999595e-02 1.284920999999999980e-02 1.290050074779767499e-02 8.868019999999999842e-05 +8.565480000000000316e-02 1.276963000000000056e-02 1.282057691750264697e-02 8.695680000000000065e-05 +8.604489999999999361e-02 1.269016000000000068e-02 1.274075932068990946e-02 8.962229999999999741e-05 +8.643510000000000082e-02 1.261128000000000075e-02 1.266155427410518668e-02 8.753039999999999801e-05 +8.682529999999999415e-02 1.253241000000000077e-02 1.258234905755320661e-02 8.837510000000000027e-05 +8.721539999999999848e-02 1.245405999999999944e-02 1.250367044019390361e-02 8.655230000000000139e-05 +8.760560000000000569e-02 1.237609999999999925e-02 1.242538581230035431e-02 8.655489999999999354e-05 +8.799579999999999902e-02 1.229815000000000075e-02 1.234710660171956485e-02 8.638530000000000113e-05 +8.838590000000000335e-02 1.222026000000000015e-02 1.226889996120519334e-02 8.725110000000000222e-05 +8.877609999999999668e-02 1.214234999999999967e-02 1.219067611926958825e-02 8.563389999999999338e-05 +8.916620000000000101e-02 1.206469999999999938e-02 1.211270039273322670e-02 8.539790000000000445e-05 +8.955639999999999434e-02 1.198735000000000078e-02 1.203501427124121712e-02 8.543029999999999418e-05 +8.994660000000000155e-02 1.191000000000000045e-02 1.195732803739646584e-02 8.535040000000000194e-05 +9.033670000000000588e-02 1.183300999999999971e-02 1.188002885239964126e-02 8.536150000000000595e-05 +9.072689999999999921e-02 1.175604000000000059e-02 1.180276148483007728e-02 8.610179999999999613e-05 +9.111700000000000355e-02 1.167938999999999956e-02 1.172579702615247216e-02 8.831479999999999680e-05 +9.150719999999999688e-02 1.160340999999999977e-02 1.164949131458491334e-02 8.455170000000000567e-05 +9.189730000000000121e-02 1.152744999999999985e-02 1.157319563153584932e-02 8.551700000000000133e-05 +9.228749999999999454e-02 1.145181999999999971e-02 1.149722739159510232e-02 8.333830000000000133e-05 +9.267770000000000175e-02 1.137631000000000059e-02 1.142138567875185425e-02 8.406990000000000632e-05 +9.306780000000000608e-02 1.130088999999999920e-02 1.134563681146314937e-02 8.385449999999999480e-05 +9.345799999999999941e-02 1.122578999999999938e-02 1.127024298802847997e-02 8.294990000000000349e-05 +9.384810000000000374e-02 1.115070999999999944e-02 1.119487558927098093e-02 8.268419999999999687e-05 +9.423829999999999707e-02 1.107604000000000054e-02 1.111988931460827952e-02 8.292019999999999935e-05 +9.462840000000000140e-02 1.100166999999999985e-02 1.104519137259795357e-02 8.294990000000000349e-05 +9.501859999999999473e-02 1.092732000000000078e-02 1.097051175932093128e-02 8.221800000000000462e-05 +9.540869999999999906e-02 1.085367999999999923e-02 1.089657276738932069e-02 8.236279999999999686e-05 +9.579890000000000627e-02 1.078001999999999953e-02 1.082260749246298157e-02 8.193070000000000485e-05 +9.618899999999999673e-02 1.070659000000000020e-02 1.074888658763396669e-02 8.178810000000000387e-05 +9.657920000000000393e-02 1.063335999999999969e-02 1.067537224943441651e-02 8.181649999999999839e-05 +9.696929999999999439e-02 1.056015000000000079e-02 1.060188360391375699e-02 8.147700000000000612e-05 +9.735939999999999872e-02 1.048799000000000085e-02 1.052939438393979237e-02 8.278839999999999496e-05 +9.774960000000000593e-02 1.041589000000000056e-02 1.045696799431052211e-02 8.139279999999999768e-05 +9.813969999999999638e-02 1.034379000000000028e-02 1.038454969622005369e-02 8.078309999999999571e-05 +9.852990000000000359e-02 1.027162000000000040e-02 1.031209247583799934e-02 8.075579999999999683e-05 +9.891999999999999404e-02 1.019948000000000035e-02 1.023965541846634693e-02 8.166849999999999912e-05 +9.931009999999999838e-02 1.012806999999999943e-02 1.016795739511015123e-02 8.070110000000000564e-05 +9.970030000000000558e-02 1.005682999999999924e-02 1.009642937686532418e-02 8.032129999999999955e-05 +1.000899999999999984e-01 9.985924999999999663e-03 1.002523421813927840e-02 7.978789999999999548e-05 +1.004810000000000009e-01 9.915571999999999234e-03 9.954599718145421341e-03 8.159719999999999864e-05 +1.008710000000000023e-01 9.845411999999999567e-03 9.884159526810371368e-03 7.986729999999999340e-05 +1.012610000000000038e-01 9.775653999999999941e-03 9.814106784750434298e-03 7.986729999999999340e-05 +1.016510000000000052e-01 9.706091000000000024e-03 9.744242462897068080e-03 7.918749999999999357e-05 +1.020410000000000067e-01 9.636580999999999966e-03 9.674432580379145954e-03 7.900750000000000546e-05 +1.024309999999999943e-01 9.567463999999999483e-03 9.605033723073736970e-03 8.018249999999999336e-05 +1.028209999999999957e-01 9.498345000000000052e-03 9.535632953108497123e-03 7.885409999999999436e-05 +1.032109999999999972e-01 9.429552000000000767e-03 9.466559960887240693e-03 7.842439999999999405e-05 +1.036019999999999996e-01 9.360867999999999620e-03 9.397596168922404958e-03 7.870160000000000556e-05 +1.039920000000000011e-01 9.292359000000000036e-03 9.328807703208418303e-03 7.832429999999999818e-05 +1.043820000000000026e-01 9.225139999999999729e-03 9.261315498934339155e-03 7.877779999999999646e-05 +1.047720000000000040e-01 9.157956000000000082e-03 9.193857792025946288e-03 7.797689999999999537e-05 +1.051620000000000055e-01 9.090878999999999627e-03 9.126505445784194315e-03 7.807569999999999517e-05 +1.055520000000000069e-01 9.023957000000000853e-03 9.059303304327512296e-03 7.775609999999999911e-05 +1.059419999999999945e-01 8.957042000000000129e-03 8.992108492832507835e-03 7.758559999999999795e-05 +1.063319999999999960e-01 8.890629000000000587e-03 8.925430038632565719e-03 7.768290000000000123e-05 +1.067229999999999984e-01 8.824138999999999941e-03 8.858676837224304865e-03 7.763419999999999609e-05 +1.071129999999999999e-01 8.758095999999999937e-03 8.792371405876674498e-03 7.712839999999999833e-05 +1.075030000000000013e-01 8.692715999999999638e-03 8.726727138619471899e-03 7.724800000000000308e-05 +1.078930000000000028e-01 8.627343000000000858e-03 8.661089610923416310e-03 7.665580000000000560e-05 +1.082830000000000042e-01 8.562518999999999339e-03 8.596002692194178868e-03 7.663239999999999494e-05 +1.086730000000000057e-01 8.497892999999999419e-03 8.531113124543358156e-03 7.804370000000000632e-05 +1.090629999999999933e-01 8.433404000000000247e-03 8.466362483832982205e-03 7.651570000000000333e-05 +1.094529999999999947e-01 8.369599999999999609e-03 8.402303593678224491e-03 7.670269999999999324e-05 +1.098439999999999972e-01 8.305626999999999455e-03 8.338075339093179231e-03 7.598570000000000671e-05 +1.102339999999999987e-01 8.242275000000000296e-03 8.274459938656674590e-03 7.580390000000000110e-05 +1.106240000000000001e-01 8.179225000000000037e-03 8.211141233379041712e-03 7.644600000000000635e-05 +1.110140000000000016e-01 8.116197000000000342e-03 8.147845584243957814e-03 7.546660000000000011e-05 +1.114040000000000030e-01 8.053737999999999522e-03 8.085144385881561785e-03 7.610000000000000661e-05 +1.117940000000000045e-01 7.991279999999999911e-03 8.022444817826972913e-03 7.703320000000000642e-05 +1.121840000000000059e-01 7.929313000000000333e-03 7.960237384892022983e-03 7.653899999999999343e-05 +1.125739999999999935e-01 7.867884000000000336e-03 7.898568572810989522e-03 7.756130000000000565e-05 +1.129639999999999950e-01 7.806457999999999627e-03 7.836901811329656520e-03 7.686739999999999524e-05 +1.133549999999999974e-01 7.745417000000000100e-03 7.775603703224632027e-03 7.765850000000000194e-05 +1.137449999999999989e-01 7.684589999999999824e-03 7.714518792126701108e-03 7.802629999999999527e-05 +1.141350000000000003e-01 7.624022000000000264e-03 7.653699613847527415e-03 7.729590000000000646e-05 +1.145250000000000018e-01 7.563936999999999744e-03 7.593375507885479631e-03 7.768290000000000123e-05 +1.149150000000000033e-01 7.503852000000000091e-03 7.533051992232098377e-03 7.729590000000000646e-05 +1.153050000000000047e-01 7.444553999999999928e-03 7.473524223322518083e-03 7.751289999999999439e-05 +1.156950000000000062e-01 7.385476999999999667e-03 7.414221451600616630e-03 7.842439999999999405e-05 +1.160849999999999937e-01 7.326397999999999591e-03 7.354915841988299459e-03 7.736799999999999515e-05 +1.164749999999999952e-01 7.267306999999999760e-03 7.295595498027451081e-03 7.770719999999999353e-05 +1.168649999999999967e-01 7.208209000000000144e-03 7.236268106446680157e-03 7.758559999999999795e-05 +1.172549999999999981e-01 7.150253999999999985e-03 7.178079956390027473e-03 7.703649999999999333e-05 +1.176460000000000006e-01 7.092756999999999881e-03 7.120347429028719630e-03 7.822460000000000318e-05 +1.180360000000000020e-01 7.035422000000000377e-03 7.062779703614394979e-03 7.773169999999999982e-05 +1.184260000000000035e-01 6.978250000000000078e-03 7.005383064547699226e-03 7.727630000000000414e-05 +1.188160000000000049e-01 6.921072000000000335e-03 6.947981178150877506e-03 7.815000000000000223e-05 +1.192060000000000064e-01 6.864224999999999841e-03 6.890917802534510077e-03 7.760990000000000380e-05 +1.195959999999999940e-01 6.807675000000000011e-03 6.834157822465209212e-03 7.805100000000000200e-05 +1.199859999999999954e-01 6.751117000000000055e-03 6.777390964746413289e-03 7.734399999999999674e-05 +1.203759999999999969e-01 6.695979000000000340e-03 6.722025499520075005e-03 7.768290000000000123e-05 +1.207659999999999983e-01 6.640886999999999832e-03 6.666705536198270013e-03 7.815000000000000223e-05 +1.211559999999999998e-01 6.586002999999999996e-03 6.611601297508906336e-03 7.724800000000000308e-05 +1.215460000000000013e-01 6.531437000000000180e-03 6.556825657495615745e-03 7.800160000000000210e-05 +1.219370000000000037e-01 6.476736000000000298e-03 6.501915450311195922e-03 7.715230000000000330e-05 +1.223270000000000052e-01 6.422755999999999951e-03 6.447712721281343221e-03 7.758559999999999795e-05 +1.227170000000000066e-01 6.368890999999999789e-03 6.393623928437020161e-03 7.797689999999999537e-05 +1.231069999999999942e-01 6.315285000000000343e-03 6.339801163925852870e-03 7.736799999999999515e-05 +1.234969999999999957e-01 6.262353000000000017e-03 6.286673901904199328e-03 7.753709999999999325e-05 +1.238869999999999971e-01 6.209428000000000343e-03 6.233553799906901317e-03 7.729590000000000646e-05 +1.242769999999999986e-01 6.156970000000000325e-03 6.180889476861710884e-03 7.729590000000000646e-05 +1.246670000000000000e-01 6.104708999999999830e-03 6.128417887546812673e-03 7.763419999999999609e-05 +1.250570000000000015e-01 6.052553999999999920e-03 6.076055362815024029e-03 7.689040000000000501e-05 +1.254470000000000030e-01 6.000977999999999694e-03 6.024284341728588545e-03 7.717819999999999910e-05 +1.258370000000000044e-01 5.949409000000000121e-03 5.972520926627121647e-03 7.727189999999999450e-05 +1.262270000000000059e-01 5.898373999999999631e-03 5.921288942881298457e-03 7.682040000000000060e-05 +1.266170000000000073e-01 5.847736000000000080e-03 5.870453452929540530e-03 7.729590000000000646e-05 +1.270070000000000088e-01 5.797102999999999631e-03 5.819622487404882936e-03 7.667920000000000270e-05 +1.273980000000000112e-01 5.747145999999999852e-03 5.769479612444817183e-03 7.751289999999999439e-05 +1.277880000000000127e-01 5.697312000000000383e-03 5.719460098137257864e-03 7.756130000000000565e-05 +1.281779999999999864e-01 5.647738000000000237e-03 5.669694945821524695e-03 7.642279999999999614e-05 +1.285679999999999878e-01 5.598477999999999614e-03 5.620236180117848396e-03 7.672409999999999950e-05 +1.289579999999999893e-01 5.549219999999999674e-03 5.570779635037102426e-03 7.677369999999999984e-05 +1.293479999999999908e-01 5.500625000000000306e-03 5.521996757653743933e-03 7.691469999999999731e-05 +1.297379999999999922e-01 5.452114000000000092e-03 5.473299762841953714e-03 7.660759999999999478e-05 +1.301279999999999937e-01 5.403907999999999802e-03 5.424907309546476639e-03 7.627970000000000085e-05 +1.305179999999999951e-01 5.356323999999999981e-03 5.377137328982770428e-03 7.783710000000000054e-05 +1.309079999999999966e-01 5.308740000000000160e-03 5.329367768746692824e-03 7.771550000000000495e-05 +1.312979999999999980e-01 5.261789000000000223e-03 5.282231884939614722e-03 7.905880000000000275e-05 +1.316879999999999995e-01 5.215028000000000025e-03 5.235286909652696503e-03 7.852990000000000176e-05 +1.320780000000000010e-01 5.168378999999999855e-03 5.188456452459017446e-03 7.853420000000000442e-05 +1.324680000000000024e-01 5.122149999999999793e-03 5.142055266766147777e-03 7.963070000000000315e-05 +1.328580000000000039e-01 5.075912000000000132e-03 5.095645714863236524e-03 8.081219999999999853e-05 +1.332480000000000053e-01 5.030459000000000146e-03 5.050013739781279536e-03 7.934110000000000511e-05 +1.336380000000000068e-01 4.985449999999999778e-03 5.004820076744203976e-03 7.866659999999999658e-05 +1.340280000000000082e-01 4.940476000000000069e-03 4.959663593969062742e-03 7.825859999999999642e-05 +1.344180000000000097e-01 4.895980999999999771e-03 4.915012916732218635e-03 7.892710000000000535e-05 +1.348080000000000112e-01 4.851486000000000340e-03 4.870362377487781960e-03 7.965670000000000595e-05 +1.351990000000000136e-01 4.807284999999999683e-03 4.825995781660621237e-03 7.846529999999999564e-05 +1.355889999999999873e-01 4.763584999999999868e-03 4.782119847129753971e-03 7.989869999999999449e-05 +1.359789999999999888e-01 4.719883999999999712e-03 4.738244220564536216e-03 8.006769999999999914e-05 +1.363689999999999902e-01 4.676684000000000396e-03 4.694881402238172165e-03 7.941140000000000340e-05 +1.367589999999999917e-01 4.633507000000000250e-03 4.651541923787324770e-03 7.987600000000000570e-05 +1.371489999999999931e-01 4.590636999999999843e-03 4.608508992499429739e-03 7.922749999999999996e-05 +1.375389999999999946e-01 4.548243999999999898e-03 4.565950688357889829e-03 7.928880000000000563e-05 +1.379289999999999961e-01 4.505850999999999953e-03 4.523392384216710742e-03 7.876470000000000162e-05 +1.383189999999999975e-01 4.464336000000000172e-03 4.481712575352084227e-03 7.825370000000000600e-05 +1.387089999999999990e-01 4.423024999999999700e-03 4.440235406367055299e-03 7.813860000000000434e-05 +1.390990000000000004e-01 4.381847000000000346e-03 4.398893938955567018e-03 7.822839999999999796e-05 +1.394890000000000019e-01 4.341051999999999758e-03 4.357940383301032658e-03 7.697190000000000076e-05 +1.398790000000000033e-01 4.300258000000000379e-03 4.316988287989037033e-03 7.687109999999999657e-05 +1.402690000000000048e-01 4.259889000000000210e-03 4.276468154694128450e-03 7.571339999999999917e-05 +1.406590000000000062e-01 4.219719000000000247e-03 4.236148722935041168e-03 7.414069999999999981e-05 +1.410490000000000077e-01 4.179676999999999698e-03 4.195957146193621604e-03 7.480040000000000300e-05 +1.414390000000000092e-01 4.140493999999999772e-03 4.156619114688175042e-03 7.588220000000000338e-05 +1.418290000000000106e-01 4.101312000000000187e-03 4.117283021458397786e-03 7.531849999999999385e-05 +1.422190000000000121e-01 4.062477999999999576e-03 4.078298015633048841e-03 7.607619999999999509e-05 +1.426090000000000135e-01 4.023918999999999933e-03 4.039590265035316631e-03 7.624829999999999976e-05 +1.429989999999999872e-01 3.985362000000000106e-03 4.000884735052255732e-03 7.414020000000000549e-05 +1.433889999999999887e-01 3.947797000000000285e-03 3.963170753358553726e-03 7.331370000000000248e-05 +1.437789999999999901e-01 3.910231999999999597e-03 3.925457022029048108e-03 7.485140000000000641e-05 +1.441689999999999916e-01 3.872865999999999982e-03 3.887943785065704327e-03 7.537839999999999645e-05 +1.445589999999999931e-01 3.835751000000000091e-03 3.850681680258943255e-03 7.395579999999999418e-05 +1.449489999999999945e-01 3.798642000000000078e-03 3.813427181385629482e-03 7.325290000000000469e-05 +1.453389999999999960e-01 3.762179000000000044e-03 3.776822312248529542e-03 7.403250000000000650e-05 +1.457289999999999974e-01 3.725818000000000099e-03 3.740321278818892030e-03 7.324929999999999679e-05 +1.461189999999999989e-01 3.689692999999999966e-03 3.704052713725200681e-03 7.281560000000000127e-05 +1.465090000000000003e-01 3.654090000000000029e-03 3.668303033334851301e-03 7.394060000000000151e-05 +1.468990000000000018e-01 3.618497000000000033e-03 3.632561889120675819e-03 7.294230000000000126e-05 +1.472890000000000033e-01 3.583296000000000137e-03 3.597226939049489453e-03 7.582549999999999425e-05 +1.476790000000000047e-01 3.548233000000000029e-03 3.562034062721425656e-03 7.260239999999999457e-05 +1.480690000000000062e-01 3.513329999999999839e-03 3.526999471655489250e-03 7.098020000000000186e-05 +1.484590000000000076e-01 3.479165000000000209e-03 3.492691353754984532e-03 7.115269999999999386e-05 +1.488490000000000091e-01 3.444998999999999804e-03 3.458383713795992569e-03 7.203430000000000250e-05 +1.492390000000000105e-01 3.411118999999999783e-03 3.424373427789681370e-03 7.151860000000000335e-05 +1.496290000000000120e-01 3.377404999999999990e-03 3.390536762628489856e-03 6.975539999999999963e-05 +1.500190000000000135e-01 3.343750999999999841e-03 3.356759908741979194e-03 7.207830000000000411e-05 +1.504089999999999872e-01 3.311071999999999974e-03 3.323938945049851115e-03 7.000219999999999869e-05 +1.507989999999999886e-01 3.278390999999999857e-03 3.291116828980062293e-03 6.909410000000000649e-05 +1.511889999999999901e-01 3.245917999999999980e-03 3.258518306957129598e-03 7.074379999999999850e-05 +1.515789999999999915e-01 3.213656999999999972e-03 3.226147669106086465e-03 6.995620000000000625e-05 +1.519689999999999930e-01 3.181401999999999841e-03 3.193782586540288605e-03 6.967729999999999779e-05 +1.523589999999999944e-01 3.149945999999999822e-03 3.162195504152960288e-03 7.165079999999999507e-05 +1.527489999999999959e-01 3.118567999999999948e-03 3.130683740703821619e-03 7.121440000000000040e-05 +1.531389999999999973e-01 3.087377000000000090e-03 3.099362059268761377e-03 7.058950000000000576e-05 +1.535289999999999988e-01 3.056539000000000044e-03 3.068397982216640105e-03 7.117140000000000098e-05 +1.539190000000000003e-01 3.025705999999999968e-03 3.037440050754521004e-03 6.978990000000000074e-05 +1.543090000000000017e-01 2.995299999999999786e-03 3.006917707629095521e-03 7.064020000000000173e-05 +1.546990000000000032e-01 2.965016000000000006e-03 2.976521192391699128e-03 7.175539999999999404e-05 +1.550890000000000046e-01 2.934939000000000124e-03 2.946330042971181343e-03 7.090020000000000263e-05 +1.554790000000000061e-01 2.905603999999999999e-03 2.916875599319826602e-03 6.824010000000000416e-05 +1.558690000000000075e-01 2.876259999999999841e-03 2.887412789456781422e-03 7.015070000000000582e-05 +1.562590000000000090e-01 2.847224000000000196e-03 2.858261355699302873e-03 7.053629999999999753e-05 +1.566490000000000105e-01 2.818324999999999997e-03 2.829248270715712353e-03 6.910960000000000659e-05 +1.570390000000000119e-01 2.789516000000000131e-03 2.800324278336249317e-03 7.000039999999999474e-05 +1.574290000000000134e-01 2.761415000000000137e-03 2.772100292214679969e-03 6.826470000000000389e-05 +1.578189999999999871e-01 2.733315000000000050e-03 2.743876476065622814e-03 6.868950000000000023e-05 +1.582089999999999885e-01 2.705531000000000168e-03 2.715979443758927483e-03 6.784339999999999490e-05 +1.585979999999999890e-01 2.678091000000000065e-03 2.688434681094967280e-03 6.768259999999999468e-05 +1.589879999999999904e-01 2.650581999999999851e-03 2.660821708235847852e-03 7.048529999999999412e-05 +1.593779999999999919e-01 2.623696999999999922e-03 2.633825199887842184e-03 6.678670000000000212e-05 +1.597679999999999934e-01 2.596838000000000184e-03 2.606854667905693888e-03 6.806260000000000120e-05 +1.601579999999999948e-01 2.570183000000000190e-03 2.580092981264940372e-03 6.871039999999999863e-05 +1.605479999999999963e-01 2.543807999999999833e-03 2.553619222140252835e-03 6.722320000000000378e-05 +1.609379999999999977e-01 2.517438999999999788e-03 2.527151890940786474e-03 6.860290000000000008e-05 +1.613279999999999992e-01 2.491541999999999802e-03 2.501157969061430005e-03 6.642819999999999530e-05 +1.617180000000000006e-01 2.465730000000000179e-03 2.475251014726331193e-03 6.829539999999999667e-05 +1.621080000000000021e-01 2.440117999999999801e-03 2.449537867086657578e-03 6.741309999999999328e-05 +1.624980000000000036e-01 2.414995000000000076e-03 2.424297177188450281e-03 6.718890000000000311e-05 +1.628880000000000050e-01 2.389871999999999917e-03 2.399057527302398806e-03 6.741690000000000161e-05 +1.632780000000000065e-01 2.365219999999999909e-03 2.374304457557258833e-03 6.624600000000000237e-05 +1.636680000000000079e-01 2.340750999999999856e-03 2.349741823667695923e-03 6.692020000000000347e-05 +1.640580000000000094e-01 2.316367000000000165e-03 2.325261849698087840e-03 6.688960000000000413e-05 +1.644480000000000108e-01 2.292428000000000000e-03 2.301221692539575595e-03 6.762290000000000607e-05 +1.648380000000000123e-01 2.268488999999999835e-03 2.277181057447325192e-03 6.835449999999999751e-05 +1.652280000000000137e-01 2.244897999999999946e-03 2.253496645638895877e-03 6.869079999999999631e-05 +1.656169999999999864e-01 2.221619000000000198e-03 2.230129570371323054e-03 6.746150000000000454e-05 +1.660069999999999879e-01 2.198294999999999850e-03 2.206717817169590208e-03 6.907780000000000463e-05 +1.663969999999999894e-01 2.175713000000000126e-03 2.184036898316521740e-03 6.889120000000000204e-05 +1.667869999999999908e-01 2.153129000000000154e-03 2.161355251162463010e-03 6.772520000000000677e-05 +1.671769999999999923e-01 2.130687000000000032e-03 2.138817610188813428e-03 7.153609999999999429e-05 +1.675669999999999937e-01 2.108416000000000110e-03 2.116453762942960889e-03 6.937949999999999532e-05 +1.679569999999999952e-01 2.086145000000000187e-03 2.094090393634430881e-03 6.916460000000000522e-05 +1.683469999999999966e-01 2.064514999999999840e-03 2.072371495021373220e-03 6.855229999999999755e-05 +1.687369999999999981e-01 2.042971000000000197e-03 2.050739599191951038e-03 6.994760000000000094e-05 +1.691269999999999996e-01 2.021560999999999844e-03 2.029243498964222823e-03 6.755549999999999381e-05 +1.695170000000000010e-01 2.000431999999999905e-03 2.008034416458182297e-03 6.868370000000000107e-05 +1.699070000000000025e-01 1.979303999999999873e-03 1.986825924251870573e-03 6.795630000000000529e-05 +1.702970000000000039e-01 1.958590999999999978e-03 1.966031270102142937e-03 6.902040000000000074e-05 +1.706860000000000044e-01 1.938052000000000004e-03 1.945411555716657342e-03 6.863630000000000555e-05 +1.710760000000000058e-01 1.917571999999999983e-03 1.924849603339735555e-03 6.846359999999999957e-05 +1.714660000000000073e-01 1.897524000000000042e-03 1.904718635712856394e-03 6.896460000000000036e-05 +1.718560000000000088e-01 1.877467000000000068e-03 1.884578877799051580e-03 6.800080000000000122e-05 +1.722460000000000102e-01 1.857825000000000015e-03 1.864854490435936644e-03 6.760270000000000244e-05 +1.726360000000000117e-01 1.838407000000000019e-03 1.845355983491966381e-03 6.726500000000000057e-05 +1.730260000000000131e-01 1.819022000000000000e-03 1.825890163223910885e-03 6.831339999999999548e-05 +1.734159999999999868e-01 1.799957999999999940e-03 1.806752033628942307e-03 6.840200000000000002e-05 +1.738059999999999883e-01 1.780894000000000097e-03 1.787613313724997332e-03 6.856929999999999417e-05 +1.741959999999999897e-01 1.762116999999999893e-03 1.768766170387312507e-03 6.971699999999999675e-05 +1.745849999999999902e-01 1.743671000000000023e-03 1.750251885518847462e-03 6.820779999999999432e-05 +1.749749999999999917e-01 1.725175000000000016e-03 1.731688090524433543e-03 6.736290000000000517e-05 +1.753649999999999931e-01 1.707289000000000072e-03 1.713723014906770809e-03 6.912970000000000323e-05 +1.757549999999999946e-01 1.689441999999999984e-03 1.695796694041889997e-03 6.874500000000000673e-05 +1.761449999999999960e-01 1.671713999999999926e-03 1.677992205595691842e-03 6.779379999999999456e-05 +1.765349999999999975e-01 1.654202999999999923e-03 1.660411151334141057e-03 6.832499999999999381e-05 +1.769249999999999989e-01 1.636686000000000044e-03 1.642822937097313321e-03 6.812130000000000117e-05 +1.773150000000000004e-01 1.619659999999999980e-03 1.625720564719550188e-03 6.800459999999999600e-05 +1.777050000000000018e-01 1.602738999999999943e-03 1.608721818498840559e-03 6.811849999999999503e-05 +1.780940000000000023e-01 1.585943999999999939e-03 1.591854310222239359e-03 6.869170000000000506e-05 +1.784840000000000038e-01 1.569340000000000014e-03 1.575195460186004041e-03 6.833049999999999909e-05 +1.788740000000000052e-01 1.552743999999999999e-03 1.558543965727642294e-03 6.938700000000000499e-05 +1.792640000000000067e-01 1.536533000000000083e-03 1.542272193801553278e-03 6.898549999999999875e-05 +1.796540000000000081e-01 1.520524000000000094e-03 1.526199178039503064e-03 6.900180000000000061e-05 +1.800440000000000096e-01 1.504544000000000020e-03 1.510156012833899653e-03 6.884539999999999649e-05 +1.804340000000000110e-01 1.488862000000000085e-03 1.494410031161222960e-03 6.918419999999999399e-05 +1.808240000000000125e-01 1.473188999999999966e-03 1.478673147738473375e-03 6.781660000000000390e-05 +1.812130000000000130e-01 1.457792000000000072e-03 1.463214792436034768e-03 6.849660000000000416e-05 +1.816029999999999867e-01 1.442556999999999911e-03 1.447921733548443793e-03 6.983220000000000540e-05 +1.819929999999999881e-01 1.427321000000000058e-03 1.432627606424102243e-03 6.843779999999999720e-05 +1.823829999999999896e-01 1.412658999999999920e-03 1.417905559205587989e-03 6.880020000000000580e-05 +1.827729999999999910e-01 1.398004999999999907e-03 1.403192696987993300e-03 6.995859999999999796e-05 +1.831629999999999925e-01 1.383488999999999899e-03 1.388617841926898617e-03 6.783569999999999834e-05 +1.835529999999999939e-01 1.369161999999999940e-03 1.374233844136508618e-03 6.942659999999999695e-05 +1.839419999999999944e-01 1.354876999999999991e-03 1.359891272603341384e-03 6.821979999999999353e-05 +1.843319999999999959e-01 1.340961000000000106e-03 1.345919825199748052e-03 6.796620000000000667e-05 +1.847219999999999973e-01 1.327122999999999931e-03 1.332026637582098489e-03 6.796980000000000101e-05 +1.851119999999999988e-01 1.313382000000000091e-03 1.318232867819654807e-03 6.891140000000000568e-05 +1.855020000000000002e-01 1.299879000000000095e-03 1.304681860970273184e-03 6.910809999999999653e-05 +1.858920000000000017e-01 1.286381999999999977e-03 1.291138014099025167e-03 6.863720000000000075e-05 +1.862820000000000031e-01 1.273152000000000051e-03 1.277861459579325261e-03 6.880290000000000494e-05 +1.866710000000000036e-01 1.260058000000000098e-03 1.264720936661419860e-03 6.981570000000000310e-05 +1.870610000000000051e-01 1.246998000000000030e-03 1.251613997147216738e-03 6.799179999999999504e-05 +1.874510000000000065e-01 1.234293999999999929e-03 1.238858164487802302e-03 6.890579999999999340e-05 +1.878410000000000080e-01 1.221597999999999955e-03 1.226111462067930357e-03 6.919630000000000019e-05 +1.882310000000000094e-01 1.209161999999999954e-03 1.213626342147920874e-03 6.872659999999999349e-05 +1.886210000000000109e-01 1.196896999999999935e-03 1.201313942123454848e-03 6.908550000000000118e-05 +1.890100000000000113e-01 1.184680000000000021e-03 1.189048940457348991e-03 6.897539999999999694e-05 +1.894000000000000128e-01 1.172826999999999984e-03 1.177152247689267637e-03 6.886089999999999659e-05 +1.897899999999999865e-01 1.160982999999999980e-03 1.165264483203993368e-03 7.001879999999999443e-05 +1.901799999999999879e-01 1.149257000000000100e-03 1.153495245985217696e-03 6.856119999999999673e-05 +1.905699999999999894e-01 1.137664999999999944e-03 1.141860735426192134e-03 7.087970000000000511e-05 +1.909599999999999909e-01 1.126073000000000005e-03 1.130226224863439734e-03 6.940489999999999680e-05 +1.913489999999999913e-01 1.114895000000000079e-03 1.119007555244638666e-03 6.862689999999999849e-05 +1.917389999999999928e-01 1.103739000000000066e-03 1.107810294446727288e-03 6.882030000000000244e-05 +1.921289999999999942e-01 1.092667000000000074e-03 1.096697523321008266e-03 6.936189999999999739e-05 +1.925189999999999957e-01 1.081785999999999946e-03 1.085779477513103969e-03 6.912439999999999839e-05 +1.929089999999999971e-01 1.070898999999999940e-03 1.074854246105282291e-03 6.976779999999999972e-05 +1.932979999999999976e-01 1.060366999999999994e-03 1.064284449820336338e-03 7.027020000000000358e-05 +1.936879999999999991e-01 1.049908999999999912e-03 1.053787282946540369e-03 7.000329999999999433e-05 +1.940780000000000005e-01 1.039502999999999998e-03 1.043343662972487200e-03 6.885150000000000309e-05 +1.944680000000000020e-01 1.029313000000000016e-03 1.033115555796461837e-03 6.894200000000000501e-05 +1.948580000000000034e-01 1.019123999999999941e-03 1.022888658603138181e-03 7.096749999999999435e-05 +1.952470000000000039e-01 1.009179000000000022e-03 1.012909300069499835e-03 6.915410000000000253e-05 +1.956370000000000053e-01 9.993420000000000688e-04 1.003038293878567248e-03 6.918809999999999576e-05 +1.960270000000000068e-01 9.895248999999999625e-04 9.931879096075624247e-04 6.942610000000000263e-05 +1.964170000000000083e-01 9.800364000000000211e-04 9.836718300761993806e-04 6.848079999999999662e-05 +1.968070000000000097e-01 9.705494999999999747e-04 9.741573808560532012e-04 6.902649999999999379e-05 +1.971960000000000102e-01 9.612031000000000264e-04 9.647777774550493363e-04 6.887369999999999755e-05 +1.975860000000000116e-01 9.519647999999999695e-04 9.555009113983180041e-04 6.977089999999999974e-05 +1.979760000000000131e-01 9.427205999999999607e-04 9.462180377396460160e-04 7.010900000000000248e-05 +1.983659999999999868e-01 9.338194999999999656e-04 9.372868192542283244e-04 7.041399999999999363e-05 +1.987559999999999882e-01 9.249433000000000047e-04 9.283810851779537073e-04 6.937639999999999530e-05 +1.991449999999999887e-01 9.161593999999999684e-04 9.195695964686032052e-04 6.894009999999999407e-05 +1.995349999999999902e-01 9.074693000000000429e-04 9.108550750942090825e-04 6.926260000000000327e-05 +1.999249999999999916e-01 8.987793999999999688e-04 9.021406917109429241e-04 6.999849999999999735e-05 +2.003149999999999931e-01 8.904117000000000455e-04 8.937485997501862364e-04 6.912399999999999751e-05 +2.007049999999999945e-01 8.821229000000000309e-04 8.854354138798793249e-04 7.009270000000000062e-05 +2.010939999999999950e-01 8.739231000000000082e-04 8.772095543280502552e-04 6.913019999999999755e-05 +2.014839999999999964e-01 8.659477999999999967e-04 8.692034264066068883e-04 6.921679999999999771e-05 +2.018739999999999979e-01 8.579660999999999711e-04 8.611907288178628989e-04 6.942480000000000656e-05 +2.022639999999999993e-01 8.501022000000000006e-04 8.533009988635987999e-04 7.077370000000000308e-05 +2.026529999999999998e-01 8.423226999999999711e-04 8.454982493515854694e-04 6.875199999999999497e-05 +2.030430000000000013e-01 8.345451000000000475e-04 8.376976063981077117e-04 6.847060000000000136e-05 +2.034330000000000027e-01 8.269878000000000038e-04 8.301202155110144710e-04 6.866130000000000616e-05 +2.038230000000000042e-01 8.194364000000000205e-04 8.225486622492074983e-04 6.901299999999999807e-05 +2.042120000000000046e-01 8.120967999999999460e-04 8.151883666480172876e-04 6.848079999999999662e-05 +2.046020000000000061e-01 8.048926999999999991e-04 8.079628113859400581e-04 6.878680000000000352e-05 +2.049920000000000075e-01 7.976956000000000541e-04 8.007444161019963268e-04 6.940600000000000599e-05 +2.053820000000000090e-01 7.906798000000000255e-04 7.937056473873297432e-04 6.942829999999999390e-05 +2.057710000000000095e-01 7.836861000000000521e-04 7.866890986928393040e-04 6.880680000000000671e-05 +2.061610000000000109e-01 7.767628000000000163e-04 7.797459928857612211e-04 6.865040000000000259e-05 +2.065510000000000124e-01 7.699818999999999674e-04 7.729502003901422011e-04 6.917379999999999829e-05 +2.069410000000000138e-01 7.631924000000000216e-04 7.661458717190005631e-04 6.926610000000000416e-05 +2.073299999999999865e-01 7.566879000000000096e-04 7.596194446442036240e-04 6.823499999999999976e-05 +2.077199999999999880e-01 7.502236000000000451e-04 7.531319456828068941e-04 6.853699999999999788e-05 +2.081099999999999894e-01 7.437897000000000388e-04 7.466770908252821437e-04 6.796980000000000101e-05 +2.084999999999999909e-01 7.374404999999999676e-04 7.403128179979843668e-04 6.834640000000000008e-05 +2.088889999999999914e-01 7.311061999999999899e-04 7.339634608725748239e-04 6.839109999999999645e-05 +2.092789999999999928e-01 7.249501999999999899e-04 7.277898952841397456e-04 6.781660000000000390e-05 +2.096689999999999943e-01 7.188729999999999730e-04 7.216941500352032747e-04 6.851600000000000604e-05 +2.100589999999999957e-01 7.128269000000000445e-04 7.156302823263786155e-04 6.842769999999999539e-05 +2.104479999999999962e-01 7.069889999999999898e-04 7.097783818959492105e-04 6.902180000000000381e-05 +2.108379999999999976e-01 7.011352999999999683e-04 7.039106355198940625e-04 6.835980000000000235e-05 +2.112279999999999991e-01 6.953802999999999937e-04 6.981409677519885516e-04 6.792310000000000025e-05 +2.116180000000000005e-01 6.896969999999999663e-04 6.924425967012601615e-04 6.844879999999999422e-05 +2.120070000000000010e-01 6.840299000000000422e-04 6.867603795611481547e-04 6.762110000000000213e-05 +2.123970000000000025e-01 6.785852999999999894e-04 6.813038585791437269e-04 6.755829999999999995e-05 +2.127870000000000039e-01 6.731420999999999802e-04 6.758486855729307257e-04 6.856259999999999980e-05 +2.131760000000000044e-01 6.677984999999999997e-04 6.704913894465928687e-04 6.912399999999999751e-05 +2.135660000000000058e-01 6.625446000000000330e-04 6.652216110399162760e-04 7.012630000000000653e-05 +2.139560000000000073e-01 6.572852999999999595e-04 6.599464729458228178e-04 6.977480000000000151e-05 +2.143460000000000087e-01 6.521788999999999841e-04 6.548279369688987054e-04 7.041510000000000282e-05 +2.147350000000000092e-01 6.471002000000000221e-04 6.497376769277467701e-04 7.062169999999999505e-05 +2.151250000000000107e-01 6.420698000000000507e-04 6.446946792448021322e-04 7.086940000000000286e-05 +2.155150000000000121e-01 6.371779000000000375e-04 6.397881268960462636e-04 7.109459999999999521e-05 +2.159040000000000126e-01 6.322927999999999578e-04 6.348883222406692716e-04 7.168090000000000009e-05 +2.162939999999999863e-01 6.275971999999999888e-04 6.301800963007824939e-04 7.212059999999999521e-05 +2.166839999999999877e-01 6.229798000000000150e-04 6.255508677629553500e-04 7.188699999999999799e-05 +2.170739999999999892e-01 6.183621999999999730e-04 6.209211039055799681e-04 7.209579999999999505e-05 +2.174629999999999896e-01 6.137747000000000038e-04 6.163210494368342616e-04 7.233290000000000672e-05 +2.178529999999999911e-01 6.091799999999999646e-04 6.117138551753275276e-04 7.376390000000000030e-05 +2.182429999999999926e-01 6.047643000000000333e-04 6.072841543603626352e-04 7.447619999999999685e-05 +2.186319999999999930e-01 6.004547999999999960e-04 6.029600849150960461e-04 7.445059999999999493e-05 +2.190219999999999945e-01 5.961450999999999990e-04 5.986359846397048710e-04 7.423320000000000612e-05 +2.194119999999999959e-01 5.919172999999999579e-04 5.944001196198046497e-04 7.694409999999999401e-05 +2.198009999999999964e-01 5.876931999999999859e-04 5.901680213972076462e-04 7.600549999999999592e-05 +2.201909999999999978e-01 5.835492000000000066e-04 5.860143891473464442e-04 7.501700000000000360e-05 +2.205809999999999993e-01 5.794997000000000516e-04 5.819536888610457506e-04 7.525920000000000613e-05 +2.209699999999999998e-01 5.754609999999999847e-04 5.779037453747489558e-04 7.535879999999999413e-05 +2.213600000000000012e-01 5.716140000000000197e-04 5.740447495814852150e-04 7.507610000000000444e-05 +2.217500000000000027e-01 5.677743000000000503e-04 5.701929119472412110e-04 7.616279999999999524e-05 +2.221390000000000031e-01 5.639681000000000121e-04 5.663751303869838001e-04 7.717400000000000345e-05 +2.225290000000000046e-01 5.601752999999999897e-04 5.625715545116477447e-04 7.489909999999999580e-05 +2.229190000000000060e-01 5.563828999999999953e-04 5.587683989680501803e-04 7.528420000000000673e-05 +2.233080000000000065e-01 5.527140999999999738e-04 5.550917580136095243e-04 7.629890000000000229e-05 +2.236980000000000079e-01 5.490538999999999576e-04 5.514245115115100262e-04 7.543390000000000295e-05 +2.240880000000000094e-01 5.454408999999999907e-04 5.478033392683911854e-04 7.638869999999999591e-05 +2.244770000000000099e-01 5.419897000000000126e-04 5.443404896498550954e-04 7.586849999999999367e-05 +2.248670000000000113e-01 5.385267000000000222e-04 5.408656732480038613e-04 7.697740000000000604e-05 +2.252570000000000128e-01 5.351541999999999931e-04 5.374823751808695954e-04 7.633019999999999638e-05 +2.256460000000000132e-01 5.318344000000000456e-04 5.341521670767570293e-04 7.570559999999999562e-05 +2.260359999999999869e-01 5.285119000000000448e-04 5.308194525007712021e-04 7.635630000000000618e-05 +2.264259999999999884e-01 5.252755000000000228e-04 5.275739897428699489e-04 7.607249999999999375e-05 +2.268149999999999888e-01 5.220592999999999501e-04 5.243488599752575653e-04 7.625800000000000070e-05 +2.272049999999999903e-01 5.188963000000000212e-04 5.211747521796385695e-04 7.635129999999999521e-05 +2.275949999999999918e-01 5.157939999999999747e-04 5.180595612221928180e-04 7.641360000000000307e-05 +2.279839999999999922e-01 5.127041999999999624e-04 5.149569273508962765e-04 7.657019999999999408e-05 +2.283739999999999937e-01 5.096855000000000179e-04 5.119318132104029627e-04 7.675929999999999537e-05 +2.287629999999999941e-01 5.066858000000000473e-04 5.089259154116908095e-04 7.713689999999999664e-05 +2.291529999999999956e-01 5.037321000000000420e-04 5.059626831677892581e-04 7.749009999999999861e-05 +2.295429999999999970e-01 5.008699000000000137e-04 5.030857209643141946e-04 8.049690000000000512e-05 +2.299319999999999975e-01 4.980200999999999855e-04 5.002211458746868081e-04 7.742350000000000165e-05 +2.303219999999999990e-01 4.952927999999999888e-04 4.974829497830962925e-04 7.756269999999999517e-05 +2.307120000000000004e-01 4.925853999999999476e-04 4.947654876549569805e-04 7.768140000000000472e-05 +2.311010000000000009e-01 4.898656000000000147e-04 4.920368189838526893e-04 7.824030000000000372e-05 +2.314910000000000023e-01 4.870828000000000115e-04 4.892485885268587189e-04 7.785819999999999937e-05 +2.318810000000000038e-01 4.842986999999999986e-04 4.864590357192099916e-04 7.792439999999999545e-05 +2.322700000000000042e-01 4.817013000000000089e-04 4.838507201814716660e-04 7.842570000000000368e-05 +2.326600000000000057e-01 4.791815000000000267e-04 4.813176280713186267e-04 7.779229999999999717e-05 +2.330490000000000061e-01 4.766713999999999912e-04 4.787944541501742242e-04 7.904830000000000005e-05 +2.334390000000000076e-01 4.742080999999999770e-04 4.763197785544048151e-04 7.839200000000000432e-05 +2.338290000000000091e-01 4.717464000000000206e-04 4.738467332739635112e-04 7.914910000000000424e-05 +2.342180000000000095e-01 4.693370999999999893e-04 4.714269911357107717e-04 7.886809999999999795e-05 +2.346080000000000110e-01 4.669499000000000132e-04 4.690299703306956064e-04 7.852709999999999562e-05 +2.349970000000000114e-01 4.645712999999999883e-04 4.666416556780947247e-04 7.977469999999999364e-05 +2.353870000000000129e-01 4.622499000000000074e-04 4.643079802055170261e-04 7.883379999999999728e-05 +2.357769999999999866e-01 4.599267999999999889e-04 4.619726744175325093e-04 8.025940000000000613e-05 +2.361659999999999870e-01 4.576649000000000233e-04 4.596989765219442283e-04 7.984699999999999632e-05 +2.365559999999999885e-01 4.554652000000000180e-04 4.574881665785909628e-04 7.910940000000000528e-05 +2.369449999999999890e-01 4.532679000000000178e-04 4.552797472259941902e-04 7.998979999999999773e-05 +2.373349999999999904e-01 4.511566999999999964e-04 4.531568947683767648e-04 7.928319999999999335e-05 +2.377249999999999919e-01 4.490598000000000267e-04 4.510483630473043049e-04 7.963420000000000405e-05 +2.381139999999999923e-01 4.469772999999999802e-04 4.489540424166306985e-04 8.045939999999999744e-05 +2.385039999999999938e-01 4.449292999999999890e-04 4.468936883618616169e-04 8.020559999999999658e-05 +2.388929999999999942e-01 4.428782000000000248e-04 4.448303652101485354e-04 8.114180000000000296e-05 +2.392829999999999957e-01 4.408912000000000182e-04 4.428314666446142556e-04 7.988270000000000006e-05 +2.396719999999999962e-01 4.389266000000000066e-04 4.408551351836859479e-04 8.049590000000000293e-05 +2.400619999999999976e-01 4.369667000000000256e-04 4.388833855576689544e-04 8.121580000000000259e-05 +2.404519999999999991e-01 4.350612000000000012e-04 4.369652799777722367e-04 8.075249999999999637e-05 +2.408409999999999995e-01 4.331595000000000258e-04 4.350510253312271799e-04 8.170340000000000111e-05 +2.412310000000000010e-01 4.313143999999999984e-04 4.331928183481683595e-04 8.053239999999999487e-05 +2.416200000000000014e-01 4.295174000000000019e-04 4.313821914650272206e-04 8.138619999999999676e-05 +2.420100000000000029e-01 4.277177000000000062e-04 4.295688660260522126e-04 8.119829999999999810e-05 +2.423990000000000034e-01 4.259333000000000238e-04 4.277723940753346845e-04 8.108620000000000302e-05 +2.427890000000000048e-01 4.241458999999999941e-04 4.259728032374355427e-04 8.161339999999999350e-05 +2.431790000000000063e-01 4.224106000000000041e-04 4.242241106834040748e-04 8.191940000000000040e-05 +2.435680000000000067e-01 4.207327000000000180e-04 4.225314046490879160e-04 8.168959999999999796e-05 +2.439580000000000082e-01 4.190514000000000109e-04 4.208352680127957048e-04 8.168959999999999796e-05 +2.443470000000000086e-01 4.173979999999999769e-04 4.191706055024867596e-04 8.222880000000000120e-05 +2.447370000000000101e-01 4.157496000000000217e-04 4.175113447655430131e-04 8.191940000000000040e-05 +2.451260000000000105e-01 4.141207999999999738e-04 4.158710927291010937e-04 8.226769999999999840e-05 +2.455160000000000120e-01 4.125228000000000206e-04 4.142605567231768984e-04 8.273919999999999550e-05 +2.459050000000000125e-01 4.109351000000000019e-04 4.126601872126887342e-04 8.188090000000000407e-05 +2.462949999999999862e-01 4.093753999999999904e-04 4.110863739720261581e-04 8.285830000000000593e-05 +2.466839999999999866e-01 4.078436999999999861e-04 4.095400122767430570e-04 8.365229999999999866e-05 +2.470739999999999881e-01 4.063197999999999853e-04 4.080012848605372978e-04 8.262060000000000649e-05 +2.474629999999999885e-01 4.048394999999999988e-04 4.065053693058205556e-04 8.242399999999999553e-05 +2.478529999999999900e-01 4.033589999999999984e-04 4.050091457871405633e-04 8.258109999999999442e-05 +2.482429999999999914e-01 4.019059000000000173e-04 4.035418672309337576e-04 8.293799999999999773e-05 +2.486319999999999919e-01 4.004659000000000040e-04 4.020884662091811132e-04 8.199640000000000661e-05 +2.490219999999999934e-01 3.990258999999999907e-04 4.006350075816027603e-04 8.281849999999999997e-05 +2.494109999999999938e-01 3.976412000000000241e-04 3.992355094572715710e-04 8.285799999999999849e-05 +2.498009999999999953e-01 3.962619000000000015e-04 3.978413710208203100e-04 8.437110000000000269e-05 +2.501900000000000235e-01 3.949418000000000004e-04 3.965056445224996741e-04 8.325919999999999729e-05 +2.505800000000000249e-01 3.936823999999999901e-04 3.952296165820911363e-04 8.188090000000000407e-05 +2.509689999999999976e-01 3.924250999999999911e-04 3.939556968932646447e-04 8.313829999999999647e-05 +2.513589999999999991e-01 3.910785000000000064e-04 3.925955111759000886e-04 8.250240000000000481e-05 +2.517480000000000273e-01 3.897311000000000200e-04 3.912347717919792997e-04 8.285830000000000593e-05 +2.521379999999999733e-01 3.884205999999999857e-04 3.899102961176904121e-04 8.301799999999999696e-05 +2.525270000000000015e-01 3.871979000000000219e-04 3.886728415363535226e-04 8.230670000000000260e-05 +2.529170000000000029e-01 3.859741000000000083e-04 3.874341769715277956e-04 8.350249999999999546e-05 +2.533059999999999756e-01 3.847324000000000165e-04 3.861768757179676612e-04 8.207370000000000670e-05 +2.536959999999999771e-01 3.834845000000000246e-04 3.849129516077297937e-04 8.289809999999999833e-05 +2.540850000000000053e-01 3.822664999999999830e-04 3.836790206950234914e-04 8.329960000000000456e-05 +2.544750000000000068e-01 3.811107000000000101e-04 3.825071998295300683e-04 8.269960000000000353e-05 +2.548639999999999795e-01 3.799572999999999883e-04 3.813377695555121810e-04 8.317859999999999674e-05 +2.552539999999999809e-01 3.788126000000000058e-04 3.801785214937243476e-04 8.258109999999999442e-05 +2.556430000000000091e-01 3.776833999999999964e-04 3.790356802368470535e-04 8.254179999999999634e-05 +2.560330000000000106e-01 3.765493999999999764e-04 3.778877002759607902e-04 8.269960000000000353e-05 +2.564219999999999833e-01 3.754230000000000002e-04 3.767455282664382418e-04 8.273919999999999550e-05 +2.568110000000000115e-01 3.742891000000000143e-04 3.755957183447737992e-04 8.285830000000000593e-05 +2.572010000000000129e-01 3.732103999999999869e-04 3.745010042164901242e-04 8.184259999999999463e-05 +2.575899999999999856e-01 3.721775999999999991e-04 3.734519514324489520e-04 8.281849999999999997e-05 +2.579799999999999871e-01 3.711429000000000139e-04 3.724009859942455591e-04 8.207370000000000670e-05 +2.583690000000000153e-01 3.700840999999999961e-04 3.713283543449538574e-04 8.254179999999999634e-05 +2.587590000000000168e-01 3.690182999999999766e-04 3.702488488100828725e-04 8.238480000000000445e-05 +2.591479999999999895e-01 3.679803000000000045e-04 3.691954650533356896e-04 8.218990000000000399e-05 +2.595379999999999909e-01 3.669831000000000135e-04 3.681800772745679991e-04 8.258109999999999442e-05 +2.599270000000000191e-01 3.659876000000000058e-04 3.671664897833429937e-04 8.203499999999999638e-05 +2.603170000000000206e-01 3.650066999999999895e-04 3.661697568317291880e-04 8.262060000000000649e-05 +2.607059999999999933e-01 3.640278000000000047e-04 3.651756072583566904e-04 8.218990000000000399e-05 +2.610959999999999948e-01 3.630583000000000269e-04 3.641912245035230683e-04 8.250240000000000481e-05 +2.614850000000000230e-01 3.620726000000000001e-04 3.631919706771840435e-04 8.269960000000000353e-05 +2.618739999999999957e-01 3.610870000000000074e-04 3.621928868228600513e-04 8.191940000000000040e-05 +2.622639999999999971e-01 3.601376999999999790e-04 3.612282867518446048e-04 8.289809999999999833e-05 +2.626530000000000253e-01 3.592089999999999823e-04 3.602834142411698824e-04 8.262060000000000649e-05 +2.630430000000000268e-01 3.582801000000000258e-04 3.593384622506609334e-04 8.234569999999999325e-05 +2.634319999999999995e-01 3.573768999999999969e-04 3.584197742395971425e-04 8.285830000000000593e-05 +2.638220000000000010e-01 3.564659000000000187e-04 3.574932783443720880e-04 8.211240000000000346e-05 +2.642109999999999737e-01 3.555758999999999916e-04 3.565886486139654022e-04 8.297800000000000412e-05 +2.646000000000000019e-01 3.546822999999999838e-04 3.556811108155387099e-04 8.234569999999999325e-05 +2.649900000000000033e-01 3.537950999999999900e-04 3.547800009455959929e-04 8.277880000000000101e-05 +2.653789999999999760e-01 3.529100999999999874e-04 3.538787492590604643e-04 8.289860000000000620e-05 +2.657689999999999775e-01 3.520199999999999805e-04 3.529722182722326530e-04 8.273919999999999550e-05 +2.661580000000000057e-01 3.511594999999999842e-04 3.520969858921923590e-04 8.289809999999999833e-05 +2.665480000000000071e-01 3.503359999999999743e-04 3.512608985775068850e-04 8.234569999999999325e-05 +2.669369999999999798e-01 3.495048999999999747e-04 3.504172053304884546e-04 8.277880000000000101e-05 +2.673260000000000081e-01 3.486849000000000198e-04 3.495811798816702274e-04 8.258109999999999442e-05 +2.677160000000000095e-01 3.478703999999999889e-04 3.487500496461259621e-04 8.305800000000000335e-05 +2.681049999999999822e-01 3.470608999999999825e-04 3.479246821251523675e-04 8.297800000000000412e-05 +2.684949999999999837e-01 3.462585000000000120e-04 3.471087628822637060e-04 8.242399999999999553e-05 +2.688840000000000119e-01 3.454654000000000145e-04 3.463021118659109494e-04 8.265999999999999802e-05 +2.692729999999999846e-01 3.446712000000000213e-04 3.454942881872301113e-04 8.246320000000000017e-05 +2.696629999999999860e-01 3.438798000000000072e-04 3.446891478714741454e-04 8.285830000000000593e-05 +2.700520000000000143e-01 3.430975999999999861e-04 3.438934777345286492e-04 8.254179999999999634e-05 +2.704420000000000157e-01 3.422990999999999903e-04 3.430824890824102922e-04 8.218990000000000399e-05 +2.708309999999999884e-01 3.415086000000000120e-04 3.422795586736045235e-04 8.285830000000000593e-05 +2.712200000000000166e-01 3.407489999999999998e-04 3.415058869621151135e-04 8.184259999999999463e-05 +2.716100000000000181e-01 3.400189000000000184e-04 3.407605521124414962e-04 8.254179999999999634e-05 +2.719989999999999908e-01 3.392892000000000108e-04 3.400154996013372865e-04 8.180420000000000530e-05 +2.723880000000000190e-01 3.385379999999999900e-04 3.392519909690060675e-04 8.215110000000000023e-05 +2.727780000000000205e-01 3.377866999999999892e-04 3.384883123643084800e-04 8.246320000000000017e-05 +2.731669999999999932e-01 3.370491999999999781e-04 3.377384225828707932e-04 8.157540000000000505e-05 +2.735569999999999946e-01 3.363228000000000116e-04 3.369993744033151443e-04 8.222880000000000120e-05 +2.739460000000000228e-01 3.355961999999999769e-04 3.362602138563092983e-04 8.161339999999999350e-05 +2.743349999999999955e-01 3.348962999999999940e-04 3.355484832650906172e-04 8.222880000000000120e-05 +2.747249999999999970e-01 3.342042000000000146e-04 3.348447535514761497e-04 8.195789999999999673e-05 +2.751140000000000252e-01 3.335179999999999872e-04 3.341463513027778383e-04 8.165150000000000251e-05 +2.755029999999999979e-01 3.328184999999999781e-04 3.334330144753725107e-04 8.218990000000000399e-05 +2.758929999999999993e-01 3.321258999999999908e-04 3.327265552837520160e-04 8.184259999999999463e-05 +2.762820000000000276e-01 3.314357000000000089e-04 3.320250677655894249e-04 8.203499999999999638e-05 +2.766710000000000003e-01 3.307432000000000015e-04 3.313223192860144536e-04 8.203499999999999638e-05 +2.770610000000000017e-01 3.300472999999999732e-04 3.306159145585010656e-04 8.207370000000000670e-05 +2.774499999999999744e-01 3.293694000000000114e-04 3.299267388617606453e-04 8.246320000000000017e-05 +2.778390000000000026e-01 3.286914999999999954e-04 3.292375631648275080e-04 8.168959999999999796e-05 +2.782290000000000041e-01 3.280377000000000119e-04 3.285727736867610988e-04 8.234569999999999325e-05 +2.786179999999999768e-01 3.274161000000000042e-04 3.279403822881075091e-04 8.165150000000000251e-05 +2.790079999999999782e-01 3.267888999999999842e-04 3.273023232365097337e-04 8.211240000000000346e-05 +2.793970000000000065e-01 3.261428000000000244e-04 3.266454499426300570e-04 8.289809999999999833e-05 +2.797859999999999792e-01 3.254962000000000025e-04 3.259879863446824922e-04 8.153749999999999648e-05 +2.801750000000000074e-01 3.248607000000000252e-04 3.253424293529359659e-04 8.246320000000000017e-05 +2.805650000000000088e-01 3.242284999999999807e-04 3.247012024111746746e-04 8.188090000000000407e-05 +2.809539999999999815e-01 3.236021999999999965e-04 3.240658130936892832e-04 8.270060000000000573e-05 +2.813430000000000097e-01 3.229795999999999731e-04 3.234336121555275809e-04 8.218990000000000399e-05 +2.817330000000000112e-01 3.223511999999999775e-04 3.227955159873011331e-04 8.222880000000000120e-05 +2.821219999999999839e-01 3.217324000000000030e-04 3.221673814313388731e-04 8.250240000000000481e-05 +2.825110000000000121e-01 3.211372999999999788e-04 3.215636167837490991e-04 8.222880000000000120e-05 +2.829010000000000136e-01 3.205344999999999852e-04 3.209522142245878839e-04 8.226769999999999840e-05 +2.832899999999999863e-01 3.199211999999999889e-04 3.203307399967297636e-04 8.199640000000000661e-05 +2.836790000000000145e-01 3.193045000000000258e-04 3.197059901843678654e-04 8.234569999999999325e-05 +2.840690000000000159e-01 3.186921999999999910e-04 3.190856144799374583e-04 8.269960000000000353e-05 +2.844579999999999886e-01 3.181235000000000247e-04 3.185084241350567814e-04 8.188090000000000407e-05 +2.848470000000000169e-01 3.175531000000000209e-04 3.179296034750427220e-04 8.281849999999999997e-05 +2.852359999999999896e-01 3.169784999999999942e-04 3.173476001360786862e-04 8.180420000000000530e-05 +2.856259999999999910e-01 3.163948999999999885e-04 3.167573903861529592e-04 8.246320000000000017e-05 +2.860150000000000192e-01 3.158189000000000265e-04 3.161747609431665089e-04 8.265999999999999802e-05 +2.864039999999999919e-01 3.152512000000000218e-04 3.156000446020477230e-04 8.238480000000000445e-05 +2.867939999999999934e-01 3.146771999999999829e-04 3.150190383251282684e-04 8.242399999999999553e-05 +2.871830000000000216e-01 3.141202999999999747e-04 3.144554989098492489e-04 8.218990000000000399e-05 +2.875719999999999943e-01 3.135693000000000269e-04 3.138983131810690322e-04 8.281849999999999997e-05 +2.879610000000000225e-01 3.130173000000000092e-04 3.133400874412470651e-04 8.188090000000000407e-05 +2.883510000000000240e-01 3.124709999999999836e-04 3.127877833527109575e-04 8.254179999999999634e-05 +2.887399999999999967e-01 3.119258999999999877e-04 3.122365987546242474e-04 8.281849999999999997e-05 +2.891290000000000249e-01 3.113915999999999885e-04 3.116958610556516554e-04 8.234569999999999325e-05 +2.895190000000000263e-01 3.108664999999999822e-04 3.111639123794826631e-04 8.305800000000000335e-05 +2.899079999999999990e-01 3.103345999999999882e-04 3.106250860680211741e-04 8.289809999999999833e-05 +2.902970000000000272e-01 3.098102000000000039e-04 3.100968226842441269e-04 8.329960000000000456e-05 +2.906859999999999999e-01 3.092856000000000056e-04 3.095692622435483187e-04 8.407869999999999851e-05 +2.910760000000000014e-01 3.087618999999999889e-04 3.090422500213310989e-04 8.313829999999999647e-05 +2.914649999999999741e-01 3.082444000000000264e-04 3.085199056372787030e-04 8.496570000000000544e-05 +2.918540000000000023e-01 3.077266999999999958e-04 3.079974232607622263e-04 8.358409999999999820e-05 +2.922429999999999750e-01 3.072207999999999775e-04 3.074874499239215841e-04 8.470940000000000587e-05 +2.926329999999999765e-01 3.067187000000000082e-04 3.069816375423161476e-04 8.445540000000000458e-05 +2.930220000000000047e-01 3.062264000000000197e-04 3.064856836911707930e-04 8.479460000000000296e-05 +2.934109999999999774e-01 3.057250000000000180e-04 3.059810751879292164e-04 8.548539999999999980e-05 +2.938000000000000056e-01 3.052234999999999822e-04 3.054763286922513687e-04 8.479460000000000296e-05 +2.941889999999999783e-01 3.047333999999999850e-04 3.049831701519195625e-04 8.592580000000000324e-05 +2.945789999999999798e-01 3.042586000000000010e-04 3.045056688852436333e-04 8.535459999999999760e-05 +2.949680000000000080e-01 3.037785999999999785e-04 3.040229202980747640e-04 8.592580000000000324e-05 +2.953569999999999807e-01 3.032835999999999909e-04 3.035251375254263413e-04 8.610389999999999396e-05 +2.957460000000000089e-01 3.027831000000000251e-04 3.030218250930092130e-04 8.916239999999999864e-05 +2.961360000000000103e-01 3.023052999999999938e-04 3.025416491063127834e-04 9.207629999999999797e-05 +2.965249999999999830e-01 3.018715000000000048e-04 3.021063378256179681e-04 9.246090000000000103e-05 +2.969140000000000112e-01 3.014297999999999782e-04 3.016631382737857336e-04 9.416600000000000610e-05 +2.973029999999999839e-01 3.009938999999999779e-04 3.012255466088361732e-04 9.615489999999999649e-05 +2.976920000000000122e-01 3.005495999999999862e-04 3.007794956393054645e-04 9.703769999999999421e-05 +2.980820000000000136e-01 3.001017999999999936e-04 3.003300250798394836e-04 9.915039999999999856e-05 +2.984709999999999863e-01 2.996515000000000159e-04 2.998785606043726068e-04 1.008286000000000056e-04 +2.988600000000000145e-01 2.992000999999999884e-04 2.994261978608201650e-04 1.023691999999999956e-04 +2.992489999999999872e-01 2.987414000000000193e-04 2.989671735280575635e-04 1.031280000000000060e-04 +2.996380000000000154e-01 2.982851000000000014e-04 2.985110828290101458e-04 1.052644000000000013e-04 +3.000280000000000169e-01 2.978276000000000079e-04 2.980536954049686359e-04 1.071930000000000018e-04 +3.004169999999999896e-01 2.974362999999999833e-04 2.976622144129323659e-04 1.076264000000000035e-04 +3.008060000000000178e-01 2.970434999999999894e-04 2.972692730780028265e-04 1.099684000000000024e-04 +3.011949999999999905e-01 2.966461999999999788e-04 2.968716871976620399e-04 1.111651999999999974e-04 +3.015840000000000187e-01 2.962471999999999848e-04 2.964723252996577518e-04 1.130720999999999978e-04 +3.019729999999999914e-01 2.958465000000000075e-04 2.960711631146576502e-04 1.151507999999999953e-04 +3.023629999999999929e-01 2.954343000000000117e-04 2.956596889000617074e-04 1.155804999999999956e-04 +3.027520000000000211e-01 2.950240000000000132e-04 2.952502947077579741e-04 1.179139999999999962e-04 +3.031409999999999938e-01 2.946258000000000210e-04 2.948528261236499728e-04 1.201248999999999991e-04 +3.035300000000000220e-01 2.942433000000000158e-04 2.944709295258800047e-04 1.223413000000000072e-04 +3.039189999999999947e-01 2.938530999999999869e-04 2.940812570235265587e-04 1.231830999999999963e-04 +3.043080000000000229e-01 2.934821999999999800e-04 2.937116032257194777e-04 1.248210000000000031e-04 +3.046980000000000244e-01 2.931133000000000046e-04 2.933442057771573738e-04 1.263055999999999923e-04 +3.050869999999999971e-01 2.927405000000000003e-04 2.929730133024328062e-04 1.301193000000000130e-04 +3.054760000000000253e-01 2.923455999999999950e-04 2.925798728506316757e-04 1.311964000000000099e-04 +3.058649999999999980e-01 2.919530999999999949e-04 2.921891229900896742e-04 1.326278999999999978e-04 +3.062540000000000262e-01 2.915967999999999794e-04 2.918346506990721371e-04 1.327878999999999963e-04 +3.066429999999999989e-01 2.912583000000000164e-04 2.914980668315784724e-04 1.359770999999999961e-04 +3.070330000000000004e-01 2.909181999999999957e-04 2.911598013983691426e-04 1.359777000000000110e-04 +3.074219999999999731e-01 2.905863999999999865e-04 2.908298558052878455e-04 1.388615000000000053e-04 +3.078110000000000013e-01 2.902456999999999780e-04 2.904909819297539974e-04 1.386927999999999946e-04 +3.081999999999999740e-01 2.899120000000000256e-04 2.901592602158933323e-04 1.372109000000000046e-04 +3.085890000000000022e-01 2.895684999999999839e-04 2.898179864775426041e-04 1.372097000000000019e-04 +3.089779999999999749e-01 2.892239000000000008e-04 2.894755603607760937e-04 1.373872000000000049e-04 +3.093670000000000031e-01 2.889093999999999820e-04 2.891639687079120588e-04 1.404807000000000050e-04 +3.097559999999999758e-01 2.885952999999999912e-04 2.888528549917964154e-04 1.388170999999999893e-04 +3.101459999999999773e-01 2.882841999999999935e-04 2.885446645662028027e-04 1.393598000000000070e-04 +3.105350000000000055e-01 2.879916000000000160e-04 2.882546986399607655e-04 1.387952000000000023e-04 +3.109239999999999782e-01 2.876926000000000244e-04 2.879584747578470185e-04 1.381104999999999985e-04 +3.113130000000000064e-01 2.873911999999999734e-04 2.876596672921420661e-04 1.395496000000000030e-04 +3.117019999999999791e-01 2.870918000000000081e-04 2.873629424524224091e-04 1.421229999999999943e-04 +3.120910000000000073e-01 2.867985999999999886e-04 2.870725305695521383e-04 1.431030000000000019e-04 +3.124799999999999800e-01 2.865276999999999841e-04 2.868047998748426714e-04 1.397399999999999868e-04 +3.128690000000000082e-01 2.862494999999999840e-04 2.865297136077542078e-04 1.424918999999999969e-04 +3.132579999999999809e-01 2.859730000000000215e-04 2.862566201767633291e-04 1.447575000000000044e-04 +3.136470000000000091e-01 2.856897999999999968e-04 2.859768865536277253e-04 1.437219999999999904e-04 +3.140370000000000106e-01 2.854147000000000238e-04 2.857053785418446016e-04 1.503511999999999980e-04 +3.144259999999999833e-01 2.851482000000000018e-04 2.854426330389806194e-04 1.491061999999999921e-04 +3.148150000000000115e-01 2.848902999999999852e-04 2.851884237164071427e-04 1.485733999999999894e-04 +3.152039999999999842e-01 2.846341000000000062e-04 2.849362166330986776e-04 1.487511000000000063e-04 +3.155930000000000124e-01 2.843874000000000141e-04 2.846936177211068300e-04 1.462811000000000113e-04 +3.159819999999999851e-01 2.841335999999999862e-04 2.844440031813924488e-04 1.491451000000000029e-04 +3.163710000000000133e-01 2.839121000000000223e-04 2.842258852343302125e-04 1.484608000000000000e-04 +3.167599999999999860e-01 2.836825000000000067e-04 2.839995390720561480e-04 1.529481000000000070e-04 +3.171490000000000142e-01 2.834544999999999947e-04 2.837750277808021807e-04 1.570753000000000036e-04 +3.175379999999999869e-01 2.832267999999999765e-04 2.835510327617293607e-04 1.478384999999999975e-04 +3.179270000000000151e-01 2.830067000000000021e-04 2.833346756543718556e-04 1.539524000000000068e-04 +3.183159999999999878e-01 2.828026999999999885e-04 2.831343504477663907e-04 1.523301999999999870e-04 +3.187050000000000161e-01 2.825972000000000054e-04 2.829325136488639015e-04 1.544651999999999928e-04 +3.190939999999999888e-01 2.823982999999999962e-04 2.827374075339390228e-04 1.538359999999999955e-04 +3.194830000000000170e-01 2.821968000000000219e-04 2.825397433566017506e-04 1.548649999999999886e-04 +3.198719999999999897e-01 2.819898000000000153e-04 2.823365495193574828e-04 1.572579000000000110e-04 +3.202619999999999911e-01 2.817887000000000148e-04 2.821397133289695589e-04 1.556504999999999965e-04 +3.206510000000000193e-01 2.815925999999999846e-04 2.819480184465777799e-04 1.583218000000000060e-04 +3.210399999999999920e-01 2.813914000000000042e-04 2.817512142358586606e-04 1.589162000000000083e-04 +3.214290000000000203e-01 2.812264999999999882e-04 2.815899062007329464e-04 1.612678999999999946e-04 +3.218179999999999930e-01 2.810627000000000221e-04 2.814298081491948948e-04 1.659742999999999874e-04 +3.222070000000000212e-01 2.808965999999999763e-04 2.812677553906732736e-04 1.615507999999999983e-04 +3.225959999999999939e-01 2.807290999999999953e-04 2.811046755693940571e-04 1.621755000000000061e-04 +3.229850000000000221e-01 2.805689999999999898e-04 2.809490636878003776e-04 1.639381000000000085e-04 +3.233739999999999948e-01 2.804433999999999855e-04 2.808265596311325298e-04 1.633284999999999999e-04 +3.237630000000000230e-01 2.803101999999999916e-04 2.806964176623623613e-04 1.670692000000000097e-04 +3.241519999999999957e-01 2.801767999999999837e-04 2.805663875138520043e-04 1.623965999999999962e-04 +3.245410000000000239e-01 2.800222999999999905e-04 2.804158037509697017e-04 1.691728999999999943e-04 +3.249299999999999966e-01 2.798744000000000253e-04 2.802717859079632877e-04 1.690384999999999978e-04 +3.253190000000000248e-01 2.797472999999999974e-04 2.801487714205305648e-04 1.698040999999999960e-04 +3.257079999999999975e-01 2.796283000000000210e-04 2.800338434058708662e-04 1.683768000000000038e-04 +3.260970000000000257e-01 2.795105000000000202e-04 2.799199516517741492e-04 1.717026999999999985e-04 +3.264859999999999984e-01 2.794000999999999950e-04 2.798130599727718243e-04 1.714243000000000114e-04 +3.268750000000000266e-01 2.792884000000000143e-04 2.797048459433649223e-04 1.809872999999999875e-04 +3.272639999999999993e-01 2.791758999999999777e-04 2.795964360080406097e-04 1.710141999999999999e-04 +3.276530000000000276e-01 2.790673000000000242e-04 2.794922665078223754e-04 1.745460000000000056e-04 +3.280420000000000003e-01 2.789588999999999762e-04 2.793880676323133670e-04 1.790577000000000119e-04 +3.284309999999999730e-01 2.788690999999999826e-04 2.793018478638439406e-04 1.755163999999999921e-04 +3.288200000000000012e-01 2.787795000000000030e-04 2.792157980678862199e-04 1.770495000000000131e-04 +3.292089999999999739e-01 2.787062999999999780e-04 2.791455373694078264e-04 1.819953000000000022e-04 +3.295980000000000021e-01 2.786501999999999838e-04 2.790919715385649586e-04 1.798648000000000130e-04 +3.299869999999999748e-01 2.785921000000000122e-04 2.790362974550876095e-04 1.806505999999999878e-04 +3.303760000000000030e-01 2.785096999999999942e-04 2.789575760155024189e-04 1.929718000000000080e-04 +3.307649999999999757e-01 2.784263999999999945e-04 2.788778987026790815e-04 1.865740000000000063e-04 +3.311529999999999752e-01 2.783627999999999905e-04 2.788176080128882978e-04 1.816882000000000132e-04 +3.315420000000000034e-01 2.783212999999999876e-04 2.787786984474626599e-04 1.850993000000000050e-04 +3.319309999999999761e-01 2.782808000000000005e-04 2.787408288929876994e-04 1.885498999999999953e-04 +3.323200000000000043e-01 2.782226999999999747e-04 2.786856020376570105e-04 1.943861000000000124e-04 +3.327089999999999770e-01 2.781713999999999910e-04 2.786373945610160570e-04 1.922694000000000128e-04 +3.330980000000000052e-01 2.781282999999999846e-04 2.785972991827907405e-04 1.936216000000000098e-04 +3.334869999999999779e-01 2.781045000000000001e-04 2.785762619442790902e-04 1.895111999999999958e-04 +3.338760000000000061e-01 2.780904999999999965e-04 2.785650512567331653e-04 1.995795000000000023e-04 +3.342649999999999788e-01 2.780640999999999929e-04 2.785415119668670550e-04 1.914451000000000011e-04 +3.346540000000000070e-01 2.780376000000000093e-04 2.785177770799753894e-04 2.080398999999999866e-04 +3.350429999999999797e-01 2.780125000000000153e-04 2.784953901687556711e-04 1.967505999999999997e-04 +3.354320000000000079e-01 2.779993999999999933e-04 2.784851218586190352e-04 2.038550999999999992e-04 +3.358209999999999806e-01 2.779861000000000116e-04 2.784745455842722222e-04 2.015398999999999912e-04 +3.362100000000000088e-01 2.779875000000000011e-04 2.784781080324947060e-04 2.103934999999999974e-04 +3.365989999999999815e-01 2.779834999999999923e-04 2.784757101450725000e-04 2.026726000000000016e-04 +3.369869999999999810e-01 2.779880000000000090e-04 2.784819582003916641e-04 2.038153000000000068e-04 +3.373760000000000092e-01 2.780122000000000214e-04 2.785084795041505349e-04 2.283696000000000098e-04 +3.377649999999999819e-01 2.780378000000000233e-04 2.785364931303875081e-04 2.369937999999999873e-04 +3.381540000000000101e-01 2.780599000000000243e-04 2.785606129473946254e-04 2.330691000000000078e-04 +3.385429999999999828e-01 2.780775000000000087e-04 2.785794535527421202e-04 2.513144999999999941e-04 +3.389320000000000110e-01 2.780867000000000018e-04 2.785899279491779322e-04 2.499806000000000033e-04 +3.393209999999999837e-01 2.781112999999999879e-04 2.786163928818992993e-04 2.681560000000000259e-04 +3.397100000000000120e-01 2.781419999999999943e-04 2.786490929886539488e-04 2.847969999999999907e-04 +3.400989999999999847e-01 2.781673000000000023e-04 2.786761153378254988e-04 2.799604000000000242e-04 +3.404869999999999841e-01 2.782223000000000009e-04 2.787325158340676944e-04 2.986880000000000242e-04 +3.408760000000000123e-01 2.782697000000000100e-04 2.787813103991318257e-04 3.000426000000000265e-04 +3.412649999999999850e-01 2.783198000000000182e-04 2.788325749176380356e-04 3.488532999999999825e-04 +3.416540000000000132e-01 2.783662999999999914e-04 2.788801017760343367e-04 3.978211000000000052e-04 +3.420429999999999859e-01 2.784075000000000005e-04 2.789222689472517546e-04 4.270850999999999879e-04 +3.424320000000000142e-01 2.784590999999999781e-04 2.789749536591799753e-04 5.026348000000000144e-04 +3.428209999999999869e-01 2.785028000000000265e-04 2.790197500992105343e-04 6.213787999999999603e-04 +3.432100000000000151e-01 2.785644999999999788e-04 2.790825270358666043e-04 7.163046999999999860e-04 +3.435980000000000145e-01 2.786366999999999880e-04 2.791557882985907439e-04 9.452773000000000035e-04 diff --git a/data/stan_scale.png b/data/stan_scale.png new file mode 100644 index 0000000000000000000000000000000000000000..e8d4ac6ab6ab8c5e81cd5e73d8ac51fb4eabdec0 GIT binary patch literal 17239 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 b6GmGkl+vmTfsmB?>rt~_zixc zyp~ng#sEM57*-MBHKv=A{%Z)rH%I(~l}HuagEvJzM#oXtJloLoJe z>@66)tY5vhcXeUl=jP+)89CyD9RLpMX0e7eoubN`plq)zsg z#OSdvijIyB0U;rw<|OadBE$rKIJwc_a^OcN9L9k7;G;RD1YT-eQ$R%EwGQR~4?fmu zqQBizBtT&-zQ)7H|MNo!EWj05ha|T1h1d9Z7rj#3i#SY7c}Wz|i|)qTv-#EIORPXT z#L8%MtGp40myD^f605MbOhiUR2o6CBd*57_@ni@NiluHjnw)p0DhNgq^wadGR9 z1|GWqo8iLYQxj`M4Nu{uVBLl{PkA%-=nk@qc5{gV4 zxB>=or(l0yc!j+=oQ_^L8TIX(kpJCrcJDXm`I>;fh=_<+9ngCZ@2kJX`9FS8y0+eW z`OJ&&Hz9|%!{epyDAbCHp&WLsCBOYAT>3RA(80gj(Y2#N9>suwCwtB3CN~G~q`LMN z8{JpmlIPXd3j7t-`(hMndUupk|F34yZNK?Euc!zO%*?mrwz6?gtlo&*X{HJ`ZR|~( z6?kR8;5lu)I-ebIrvI`+7x92m$!o4G@bc!m^qIDiC}r2Y&QMHSQEDLxi~B8vsUq1 zTQD*NkLGC+**m17rY6qX*&5I1rN9xnyI9ZvmhH0^iMcOeJCaedT&$9Y1Xa!NDvzQ< z=ZoI-y5%o}XKQUO$MfV7` H`6|P$15M1)5>A6%P1nM*$;=w_YX2bf<+ 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ÕT2WkB2nA9cPW}>xK0y;z!z;pR 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 diff --git a/fullBayesian.py b/fullBayesian.py index 6680247..57574f4 100755 --- a/fullBayesian.py +++ b/fullBayesian.py @@ -15,7 +15,7 @@ import stan_utility import stan_run -from statistics import calculateChiCrysol, calculateChemShiftsChi, JensenShannonDiv, waic, mean_for_weights, me_log_lik +from bioce_statistics import calculateChiCrysol, calculateChemShiftsChi, JensenShannonDiv, waic, mean_for_weights, me_log_lik from stan_models import stan_code, stan_code_CS, stan_code_EP, stan_code_EP_CS, \ psisloo_quanities, posterior_predict_quanities diff --git a/fullBayesianMultimodal.py b/fullBayesianMultimodal.py new file mode 100644 index 0000000..1c773c3 --- /dev/null +++ b/fullBayesianMultimodal.py @@ -0,0 +1,342 @@ +""" +Bayesian ensemble inference with HDX-MS and/or XL-MS likelihoods (PyStan 3). + +Combines with existing SAXS inputs using the same file layouts as fullBayesian.py. +""" +from __future__ import print_function + +import optparse +import os + +import numpy as np +import stan_run +import stan_utility +from bioce_statistics import JensenShannonDiv, mean_for_weights +from multimodal_io import ( + check_sim_matrix, + read_hdx_experimental, + read_matrix, + read_xl_restraints, +) +from stan_models import ( + stan_code_HDX, + stan_code_HDX_XL, + stan_code_SAXS_HDX, + stan_code_SAXS_HDX_XL, + stan_code_SAXS_XL, + stan_code_XL, + stan_code_XL_FP, + xl_posterior_predict, +) + + +def _plot_weights(fit, prefix="stan"): + stan_run.plot_parameter_marginals(fit, "weights", "{}_weights.png".format(prefix), dpi=300) + + +def execute_stan_hdx(hdx_exp, sim_hdx, priors, iterations, chains, njobs): + target_hdx, target_hdxerr = read_hdx_experimental(hdx_exp) + sim = read_matrix(sim_hdx) + n_structures = check_sim_matrix(sim, target_hdx.size, "HDX") + stan_dat = { + "n_obs": int(target_hdx.size), + "n_structures": int(n_structures), + "target_hdx": target_hdx, + "target_hdxerr": target_hdxerr, + "sim_hdx": sim, + "priors": priors, + } + print("Starting HDX-only inference ({} obs, {} structures)".format( + stan_dat["n_obs"], n_structures + )) + fit = stan_run.build_and_sample( + stan_code_HDX, stan_dat, iterations, chains, njobs=njobs + ) + _plot_weights(fit, "hdx") + return fit + + +def execute_stan_xl(xl_restraints, sim_xl, priors, iterations, chains, njobs, use_fp=False): + xl_obs, d_max, tau = read_xl_restraints(xl_restraints) + sim = read_matrix(sim_xl) + n_structures = check_sim_matrix(sim, xl_obs.size, "XL") + stan_dat = { + "n_xl": int(xl_obs.size), + "n_structures": int(n_structures), + "xl_obs": xl_obs, + "d_max": d_max, + "tau": tau, + "xl_distances": sim, + "priors": priors, + } + code = stan_code_XL_FP if use_fp else stan_code_XL + xl_posterior_predict + print("Starting XL-only inference ({} links, {} structures)".format( + stan_dat["n_xl"], n_structures + )) + fit = stan_run.build_and_sample(code, stan_dat, iterations, chains, njobs=njobs) + _plot_weights(fit, "xl") + return fit + + +def execute_stan_saxs_hdx( + experimental, simulated, hdx_exp, sim_hdx, priors, iterations, chains, njobs +): + target_hdx, target_hdxerr = read_hdx_experimental(hdx_exp) + sim_h = read_matrix(sim_hdx) + check_sim_matrix(sim_h, target_hdx.size, "HDX") + stan_dat = { + "n_measures": int(experimental.shape[0]), + "n_hdx": int(target_hdx.size), + "n_structures": int(simulated.shape[1]), + "target_saxs": experimental[:, 1], + "target_saxserr": experimental[:, 2], + "sim_saxs": simulated, + "target_hdx": target_hdx, + "target_hdxerr": target_hdxerr, + "sim_hdx": sim_h, + "priors": priors, + } + if sim_h.shape[1] != stan_dat["n_structures"]: + raise ValueError("SAXS and HDX simulated matrices have different structure counts") + print("Starting SAXS+HDX inference") + fit = stan_run.build_and_sample( + stan_code_SAXS_HDX, stan_dat, iterations, chains, njobs=njobs + ) + _plot_weights(fit, "saxs_hdx") + stan_run.plot_parameter_marginals(fit, "scale", "saxs_hdx_scale.png", dpi=300) + return fit + + +def execute_stan_saxs_xl( + experimental, + simulated, + xl_restraints, + sim_xl, + priors, + iterations, + chains, + njobs, +): + xl_obs, d_max, tau = read_xl_restraints(xl_restraints) + sim_x = read_matrix(sim_xl) + check_sim_matrix(sim_x, xl_obs.size, "XL") + stan_dat = { + "n_measures": int(experimental.shape[0]), + "n_xl": int(xl_obs.size), + "n_structures": int(simulated.shape[1]), + "target_saxs": experimental[:, 1], + "target_saxserr": experimental[:, 2], + "sim_saxs": simulated, + "xl_obs": xl_obs, + "d_max": d_max, + "tau": tau, + "xl_distances": sim_x, + "priors": priors, + } + if sim_x.shape[1] != stan_dat["n_structures"]: + raise ValueError("SAXS and XL simulated matrices have different structure counts") + print("Starting SAXS+XL inference") + code = stan_code_SAXS_XL + xl_posterior_predict + fit = stan_run.build_and_sample(code, stan_dat, iterations, chains, njobs=njobs) + _plot_weights(fit, "saxs_xl") + stan_run.plot_parameter_marginals(fit, "scale", "saxs_xl_scale.png", dpi=300) + return fit + + +def execute_stan_saxs_hdx_xl( + experimental, + simulated, + hdx_exp, + sim_hdx, + xl_restraints, + sim_xl, + priors, + iterations, + chains, + njobs, +): + target_hdx, target_hdxerr = read_hdx_experimental(hdx_exp) + xl_obs, d_max, tau = read_xl_restraints(xl_restraints) + sim_h = read_matrix(sim_hdx) + sim_x = read_matrix(sim_xl) + n_structures = int(simulated.shape[1]) + check_sim_matrix(sim_h, target_hdx.size, "HDX") + check_sim_matrix(sim_x, xl_obs.size, "XL") + if sim_h.shape[1] != n_structures or sim_x.shape[1] != n_structures: + raise ValueError("All simulated matrices must have the same number of columns") + stan_dat = { + "n_measures": int(experimental.shape[0]), + "n_hdx": int(target_hdx.size), + "n_xl": int(xl_obs.size), + "n_structures": n_structures, + "target_saxs": experimental[:, 1], + "target_saxserr": experimental[:, 2], + "sim_saxs": simulated, + "target_hdx": target_hdx, + "target_hdxerr": target_hdxerr, + "sim_hdx": sim_h, + "xl_obs": xl_obs, + "d_max": d_max, + "tau": tau, + "xl_distances": sim_x, + "priors": priors, + } + print("Starting SAXS+HDX+XL inference") + code = stan_code_SAXS_HDX_XL + xl_posterior_predict + fit = stan_run.build_and_sample(code, stan_dat, iterations, chains, njobs=njobs) + _plot_weights(fit, "saxs_hdx_xl") + stan_run.plot_parameter_marginals(fit, "scale", "saxs_hdx_xl_scale.png", dpi=300) + return fit + + +def execute_stan_hdx_xl(hdx_exp, sim_hdx, xl_restraints, sim_xl, priors, iterations, chains, njobs): + target_hdx, target_hdxerr = read_hdx_experimental(hdx_exp) + xl_obs, d_max, tau = read_xl_restraints(xl_restraints) + sim_h = read_matrix(sim_hdx) + sim_x = read_matrix(sim_xl) + n_structures = check_sim_matrix(sim_h, target_hdx.size, "HDX") + if sim_x.shape[1] != n_structures: + raise ValueError("HDX and XL simulated matrices have different structure counts") + check_sim_matrix(sim_x, xl_obs.size, "XL") + stan_dat = { + "n_hdx": int(target_hdx.size), + "n_xl": int(xl_obs.size), + "n_structures": int(n_structures), + "target_hdx": target_hdx, + "target_hdxerr": target_hdxerr, + "sim_hdx": sim_h, + "xl_obs": xl_obs, + "d_max": d_max, + "tau": tau, + "xl_distances": sim_x, + "priors": priors, + } + print("Starting HDX+XL inference") + code = stan_code_HDX_XL + xl_posterior_predict + fit = stan_run.build_and_sample(code, stan_dat, iterations, chains, njobs=njobs) + _plot_weights(fit, "hdx_xl") + return fit + + +def print_weight_summary(fit, file_names=None): + weights = mean_for_weights(fit) + if file_names is not None: + for index, fname in enumerate(file_names): + print(fname, weights[index]) + else: + print("Posterior mean weights:", weights) + return weights + + +def read_file_safe(filename, dtype="float64"): + try: + return np.genfromtxt(filename, dtype=dtype) + except IOError as err: + print(os.strerror(err.errno)) + raise + + +if __name__ == "__main__": + doc = """ + Bayesian inference with HDX-MS and/or XL-MS (optionally with SAXS). + Usage: python fullBayesianMultimodal.py --help + """ + print(doc) + usage = "usage: %prog [options]" + parser = optparse.OptionParser(usage=usage, version="0.1") + + parser.add_option("-p", "--priors", dest="priors_file", default=None, + help="Dirichlet prior weights (one per structure)") + parser.add_option("-f", "--file_names", dest="names_file", default=None, + help="Structure names (optional, for printed output)") + parser.add_option("-i", "--iterations", dest="iterations", default=2000, type="int") + parser.add_option("-j", "--jobs", dest="njobs", default=4, type="int") + parser.add_option("-c", "--chains", dest="chains", default=4, type="int") + + parser.add_option("-e", "--experimental", dest="experimental_file", default=None, + help="Experimental SAXS (q, I, sigma) — required for SAXS modes") + parser.add_option("-s", "--simulated", dest="simulated_file", default=None, + help="Simulated SAXS matrix") + + parser.add_option("-H", "--hdx_experimental", dest="hdx_exp", default=None, + help="HDX uptake file (uptake sigma per line)") + parser.add_option("-S", "--hdx_simulated", dest="hdx_sim", default=None, + help="Simulated HDX matrix") + + parser.add_option("-X", "--xl_restraints", dest="xl_restraints", default=None, + help="Crosslink restraint list (res_i res_j z [d_max] [tau])") + parser.add_option("-D", "--xl_distances", dest="xl_sim", default=None, + help="Simulated XL distance matrix") + parser.add_option("--xl-fp-mixture", dest="xl_fp", action="store_true", default=False, + help="XL-only: use false-positive mixture model") + + options, _ = parser.parse_args() + + if not options.priors_file: + parser.error("-p/--priors is required") + + priors = read_file_safe(options.priors_file) + file_names = None + if options.names_file: + file_names = read_file_safe(options.names_file, dtype="unicode") + + has_saxs = options.experimental_file and options.simulated_file + has_hdx = options.hdx_exp and options.hdx_sim + has_xl = options.xl_restraints and options.xl_sim + + if not (has_hdx or has_xl): + parser.error("Provide HDX (-H/-S) and/or XL (-X/-D) inputs") + + iterations = options.iterations + chains = options.chains + njobs = options.njobs + + if has_saxs and has_hdx and has_xl: + experimental = read_file_safe(options.experimental_file) + simulated = read_file_safe(options.simulated_file) + fit = execute_stan_saxs_hdx_xl( + experimental, simulated, + options.hdx_exp, options.hdx_sim, + options.xl_restraints, options.xl_sim, + priors, iterations, chains, njobs, + ) + elif has_saxs and has_hdx: + experimental = read_file_safe(options.experimental_file) + simulated = read_file_safe(options.simulated_file) + fit = execute_stan_saxs_hdx( + experimental, simulated, + options.hdx_exp, options.hdx_sim, + priors, iterations, chains, njobs, + ) + elif has_saxs and has_xl: + experimental = read_file_safe(options.experimental_file) + simulated = read_file_safe(options.simulated_file) + fit = execute_stan_saxs_xl( + experimental, simulated, + options.xl_restraints, options.xl_sim, + priors, iterations, chains, njobs, + ) + elif has_hdx and has_xl: + fit = execute_stan_hdx_xl( + options.hdx_exp, options.hdx_sim, + options.xl_restraints, options.xl_sim, + priors, iterations, chains, njobs, + ) + elif has_hdx: + fit = execute_stan_hdx( + options.hdx_exp, options.hdx_sim, + priors, iterations, chains, njobs, + ) + elif has_xl: + fit = execute_stan_xl( + options.xl_restraints, options.xl_sim, + priors, iterations, chains, njobs, + use_fp=options.xl_fp, + ) + else: + parser.error("Unrecognized modality combination") + + print_weight_summary(fit, file_names) + stan_utility.check_treedepth(fit) + stan_utility.check_energy(fit) + stan_utility.check_div(fit) diff --git a/fullBayesianTR.py b/fullBayesianTR.py index b7bc150..082e20d 100644 --- a/fullBayesianTR.py +++ b/fullBayesianTR.py @@ -15,7 +15,7 @@ import stan_utility import stan_run -from statistics import ( +from bioce_statistics import ( calculateChiCrysol, calculateChemShiftsChi, JensenShannonDiv, diff --git a/hdx_weights.png b/hdx_weights.png new file mode 100644 index 0000000000000000000000000000000000000000..2e0a245a3a1fb0de884ccbec9974f64c0c701b64 GIT binary patch literal 40449 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 literal 0 HcmV?d00001 diff --git a/multimodal_io.py b/multimodal_io.py new file mode 100644 index 0000000..5d7e941 --- /dev/null +++ b/multimodal_io.py @@ -0,0 +1,87 @@ +""" +I/O helpers for HDX-MS and XL-MS inputs to multimodal Stan models. + +File formats (same column layout as SAXS simulated matrices: rows = observations, +columns = conformers): + + HDX experimental (hdx_exp.dat): uptake sigma [per flattened peptide×time obs] + HDX simulated (SimulatedHDX.txt): one row per observation, space-separated columns + + XL restraints (xl_restraints.dat): + res_i res_j z [d_max] [tau] + z = 1 observed crosslink, 0 absent / decoy; d_max and tau optional (defaults 30, 3 Å). + + XL distances (SimulatedXLdistances.txt): rows = crosslinks, cols = conformers (Cα or + linker-compatible distance in Å). +""" +from __future__ import annotations + +import numpy as np + +DEFAULT_XL_DMAX = 30.0 +DEFAULT_XL_TAU = 3.0 + + +def read_matrix(path: str, dtype="float64") -> np.ndarray: + return np.genfromtxt(path, dtype=dtype) + + +def read_hdx_experimental(path: str) -> tuple[np.ndarray, np.ndarray]: + """Return (uptake, sigma) vectors.""" + data = read_matrix(path) + if data.ndim == 1: + if data.size % 2 != 0: + raise ValueError("HDX experimental file must have uptake and sigma columns") + data = data.reshape(-1, 2) + if data.shape[1] < 2: + raise ValueError("HDX experimental file needs at least two columns: uptake sigma") + return data[:, 0], data[:, 1] + + +def read_xl_restraints(path: str) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + """ + Parse xl_restraints.dat. + + Returns (xl_obs, d_max, tau) as 1-D float/int arrays of length n_xl. + """ + rows = [] + with open(path) as handle: + for line in handle: + line = line.strip() + if not line or line.startswith("#"): + continue + rows.append(line.split()) + if not rows: + raise ValueError("No crosslink restraints found in {}".format(path)) + + xl_obs = [] + d_max = [] + tau = [] + for parts in rows: + if len(parts) < 3: + raise ValueError( + "Each XL restraint line needs: res_i res_j z [d_max] [tau]: {}".format( + parts + ) + ) + xl_obs.append(int(float(parts[2]))) + d_max.append(float(parts[3]) if len(parts) > 3 else DEFAULT_XL_DMAX) + tau.append(float(parts[4]) if len(parts) > 4 else DEFAULT_XL_TAU) + return ( + np.asarray(xl_obs, dtype=np.int32), + np.asarray(d_max, dtype=np.float64), + np.asarray(tau, dtype=np.float64), + ) + + +def check_sim_matrix(sim: np.ndarray, n_obs: int, label: str) -> int: + """Ensure simulated matrix row count matches observations; return n_structures.""" + if sim.ndim != 2: + raise ValueError("{} simulated matrix must be 2-D".format(label)) + if sim.shape[0] != n_obs: + raise ValueError( + "{} simulated rows ({}) != {} observations ({})".format( + label, sim.shape[0], label, n_obs + ) + ) + return sim.shape[1] diff --git a/prepareHDX.py b/prepareHDX.py new file mode 100644 index 0000000..48b8e17 --- /dev/null +++ b/prepareHDX.py @@ -0,0 +1,90 @@ +#! /usr/bin/env python +""" +Build HDX simulated matrix for multimodal Bayesian inference. + +Expected per-structure uptake files in a directory (one file per conformer), each with +the same number of lines (flattened peptide×time observations). Lines are either +``uptake`` or ``time uptake``. + +Usage: python prepareHDX.py --help +""" +from __future__ import print_function + +import os +import optparse + +import numpy as np + + +def _read_uptake_column(path): + data = np.genfromtxt(path, dtype=np.float64) + if data.ndim == 1: + return data + if data.shape[1] == 1: + return data[:, 0] + if data.shape[1] >= 2: + return data[:, 1] + raise ValueError("Cannot parse uptake from {}".format(path)) + + +def build_hdx_matrix(dir_name, suffix=".hdx", out_sim="SimulatedHDX.txt"): + files = sorted( + f + for f in os.listdir(dir_name) + if f.endswith(suffix) or (suffix == ".hdx" and f.endswith(".txt")) + ) + if not files: + raise RuntimeError( + "No *{} files in {}; use --suffix to match your naming".format( + suffix, dir_name + ) + ) + + columns = [] + n_obs = None + for fname in files: + col = _read_uptake_column(os.path.join(dir_name, fname)) + if n_obs is None: + n_obs = col.size + elif col.size != n_obs: + raise ValueError( + "Inconsistent observation count in {} ({} vs {})".format( + fname, col.size, n_obs + ) + ) + columns.append(col) + + sim = np.column_stack(columns) + np.savetxt(out_sim, sim, fmt="%.8g") + print("Wrote {} ({} observations × {} structures)".format( + out_sim, sim.shape[0], sim.shape[1] + )) + return out_sim + + +if __name__ == "__main__": + usage = "usage: %prog [options]" + parser = optparse.OptionParser(usage=usage, version="0.1") + parser.add_option( + "-d", + "--hdx_dir", + dest="dir_name", + help="Directory with per-structure predicted HDX uptake files", + ) + parser.add_option( + "-o", + "--output", + dest="output", + default="SimulatedHDX.txt", + help="Output simulated matrix [default: SimulatedHDX.txt]", + ) + parser.add_option( + "--suffix", + dest="suffix", + default=".hdx", + help="Filename suffix for structure files [default: .hdx]", + ) + options, _ = parser.parse_args() + if not options.dir_name: + parser.error("-d/--hdx_dir is required") + build_hdx_matrix(options.dir_name, suffix=options.suffix, out_sim=options.output) diff --git a/prepareXLMS.py b/prepareXLMS.py new file mode 100644 index 0000000..ab8de96 --- /dev/null +++ b/prepareXLMS.py @@ -0,0 +1,87 @@ +#! /usr/bin/env python +""" +Build XL-MS distance matrix for multimodal Bayesian inference. + +Each per-structure file lists one distance per crosslink (same order as +xl_restraints.dat). Lines: ``distance`` or ``res_i res_j distance``. + +Usage: python prepareXLMS.py --help +""" +from __future__ import print_function + +import os +import optparse + +import numpy as np + + +def _read_distance_column(path): + data = np.genfromtxt(path, dtype=np.float64) + if data.ndim == 0: + return np.asarray([float(data)]) + if data.ndim == 1: + return data + return data[:, -1] + + +def build_xl_matrix(dir_name, suffix=".xldist", out_sim="SimulatedXLdistances.txt"): + files = sorted( + f + for f in os.listdir(dir_name) + if f.endswith(suffix) or (suffix == ".xldist" and f.endswith(".txt")) + ) + if not files: + raise RuntimeError( + "No *{} files in {}; use --suffix to match your naming".format( + suffix, dir_name + ) + ) + + columns = [] + n_xl = None + for fname in files: + col = _read_distance_column(os.path.join(dir_name, fname)) + if n_xl is None: + n_xl = col.size + elif col.size != n_xl: + raise ValueError( + "Inconsistent crosslink count in {} ({} vs {})".format( + fname, col.size, n_xl + ) + ) + columns.append(col) + + sim = np.column_stack(columns) + np.savetxt(out_sim, sim, fmt="%.8g") + print("Wrote {} ({} crosslinks × {} structures)".format( + out_sim, sim.shape[0], sim.shape[1] + )) + return out_sim + + +if __name__ == "__main__": + usage = "usage: %prog [options]" + parser = optparse.OptionParser(usage=usage, version="0.1") + parser.add_option( + "-d", + "--xl_dir", + dest="dir_name", + help="Directory with per-structure crosslink distance files", + ) + parser.add_option( + "-o", + "--output", + dest="output", + default="SimulatedXLdistances.txt", + help="Output distance matrix [default: SimulatedXLdistances.txt]", + ) + parser.add_option( + "--suffix", + dest="suffix", + default=".xldist", + help="Filename suffix [default: .xldist]", + ) + options, _ = parser.parse_args() + if not options.dir_name: + parser.error("-d/--xl_dir is required") + build_xl_matrix(options.dir_name, suffix=options.suffix, out_sim=options.output) diff --git a/scilifelab_theme.py b/scilifelab_theme.py new file mode 100644 index 0000000..1095f5a --- /dev/null +++ b/scilifelab_theme.py @@ -0,0 +1,149 @@ +""" +SciLifeLab visual identity colors and UI helpers. + +Brand palette (https://www.scilifelab.se/visual-identity): + Lime #A7C947 — main + Teal #045C64 + Aqua #4C979F + Grape #491F53 — accent (use sparingly) +""" +from __future__ import annotations + +import matplotlib as mpl + +LIME = "#A7C947" +TEAL = "#045C64" +AQUA = "#4C979F" +GRAPE = "#491F53" +WHITE = "#FFFFFF" +GRAY_LIGHT = "#F4F6F4" +GRAY_MID = "#6B7280" +GRAY_DARK = "#2D3436" + +PALETTE = [TEAL, LIME, AQUA, GRAPE, "#7BA428", "#2E7D84"] + +STREAMLIT_CSS = """ + +""" + + +def _build_streamlit_css() -> str: + return ( + STREAMLIT_CSS.replace("__TEAL__", TEAL) + .replace("__AQUA__", AQUA) + .replace("__LIME__", LIME) + .replace("__WHITE__", WHITE) + .replace("__GRAY__", GRAY_MID) + .replace("__GRAY_BG__", GRAY_LIGHT) + ) + + +def inject_streamlit_theme() -> None: + import streamlit as st + + st.markdown(_build_streamlit_css(), unsafe_allow_html=True) + + +def render_header(title: str, subtitle: str) -> None: + import streamlit as st + + st.markdown( + '

{}

{}

'.format(title, subtitle), + unsafe_allow_html=True, + ) + + +def render_footer() -> None: + import streamlit as st + + st.markdown( + ''.format( + teal=TEAL + ), + unsafe_allow_html=True, + ) + + +def apply_matplotlib_theme() -> None: + mpl.rcParams.update({ + "axes.prop_cycle": mpl.cycler(color=PALETTE), + "axes.edgecolor": GRAY_MID, + "axes.labelcolor": GRAY_DARK, + "axes.titlecolor": TEAL, + "axes.titlesize": 12, + "axes.titleweight": "bold", + "figure.facecolor": WHITE, + "axes.facecolor": WHITE, + "grid.color": "#E5EBE5", + "grid.alpha": 0.7, + "font.family": "sans-serif", + "font.sans-serif": ["Lato", "DejaVu Sans", "Arial"], + "legend.framealpha": 0.9, + }) diff --git a/serve/Dockerfile b/serve/Dockerfile new file mode 100644 index 0000000..fd7c79f --- /dev/null +++ b/serve/Dockerfile @@ -0,0 +1,25 @@ +# Bioce Streamlit app for SciLifeLab Serve (or local Docker) +FROM condaforge/mambaforge:24.7.1-0 + +WORKDIR /app +COPY . /app + +RUN mamba env create -f serve/environment.yml && mamba clean -ay +SHELL ["mamba", "run", "-n", "bioce-serve", "/bin/bash", "-c"] + +# Optional: install ATSAS FoXS for SAXS (uncomment if your image may fetch ATSAS) +# RUN apt-get update && apt-get install -y wget && \ +# wget -q https://www.bioisis.net/download/ATSAS/atsas-3.2.1-1_amd64.deb && \ +# apt-get install -y ./atsas-3.2.1-1_amd64.deb || true + +ENV PATH="/opt/conda/envs/bioce-serve/bin:$PATH" +ENV PYTHONPATH=/app + +EXPOSE 8501 +HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health || exit 1 + +CMD ["streamlit", "run", "app.py", \ + "--server.port=8501", \ + "--server.address=0.0.0.0", \ + "--server.headless=true", \ + "--browser.gatherUsageStats=false"] diff --git a/serve/SERVE.md b/serve/SERVE.md new file mode 100644 index 0000000..ed0a2a1 --- /dev/null +++ b/serve/SERVE.md @@ -0,0 +1,26 @@ +# Deploying Bioce on SciLifeLab Serve + +1. Register at [SciLifeLab Serve](https://serve.scilifelab.se/) and create a **Project**. +2. Build and push the Docker image (or let Serve build from this repo): + +```bash +docker build -f serve/Dockerfile -t bioce-app . +docker run -p 8501:8501 bioce-app +``` + +3. In Serve, create an app of type **Streamlit**, point to this image, port **8501**. + +## FoXS / SAXS + +SAXS simulation from PDBs requires **FoXS** (ATSAS) or **Pepsi-SAXS** on `PATH`. The default Dockerfile does not install ATSAS; add it to the image or run **HDX + XL only** until FoXS is available. + +## Example uploads + +See `serve/examples/` for HDX peptide list, uptake, and XL restraint templates. Use your own PDB ensemble and SAXS `.dat` file (same format as bioce `data/16_extrap_5concs-10p.dat`). + +## Resource hints + +- Limit conformers (e.g. ≤ 30) and use moderate Stan settings in the sidebar. +- First run compiles Stan models (several minutes). + +Documentation: [Serve user guide](https://serve.scilifelab.se/docs/) diff --git a/serve/environment.yml b/serve/environment.yml new file mode 100644 index 0000000..e92202a --- /dev/null +++ b/serve/environment.yml @@ -0,0 +1,16 @@ +name: bioce-serve +channels: + - conda-forge +dependencies: + - python=3.11 + - numpy + - matplotlib + - pandas + - biopython + - gsl + - clang + - cxx-compiler + - pip + - pip: + - "pystan>=3.9,<4" + - streamlit>=1.28 diff --git a/serve/examples/hdx_exp.txt b/serve/examples/hdx_exp.txt new file mode 100644 index 0000000..714e4c3 --- /dev/null +++ b/serve/examples/hdx_exp.txt @@ -0,0 +1,2 @@ +0.35 0.05 +0.62 0.06 diff --git a/serve/examples/hdx_peptides.txt b/serve/examples/hdx_peptides.txt new file mode 100644 index 0000000..ac06384 --- /dev/null +++ b/serve/examples/hdx_peptides.txt @@ -0,0 +1,3 @@ +# chain start end +A 2 10 +A 15 25 diff --git a/serve/examples/xl_restraints.txt b/serve/examples/xl_restraints.txt new file mode 100644 index 0000000..edb3167 --- /dev/null +++ b/serve/examples/xl_restraints.txt @@ -0,0 +1,3 @@ +# res_i res_j z d_max tau +12 48 1 30 3 +20 55 1 30 3 diff --git a/setup.py b/setup.py index c5155a8..186ffd8 100644 --- a/setup.py +++ b/setup.py @@ -43,8 +43,11 @@ ext_package='vbwSC', ext_modules=extensions, py_modules=['psis','psisloo','stan_models','stan_utility','stan_run', - 'statistics','variationalBayesian','fullBayesian', - 'prepareBayesian', 'prepareChemicalShifts'], + 'bioce_statistics','variationalBayesian','fullBayesian', + 'fullBayesianMultimodal','multimodal_io','bioce_pipeline', + 'structure_observables', 'scilifelab_theme', + 'prepareBayesian', 'prepareChemicalShifts', + 'prepareHDX', 'prepareXLMS'], package_data={'bioce': ['bioce.yml']}, include_package_data=True, ) diff --git a/stan_models.py b/stan_models.py index f8c8919..ff29461 100644 --- a/stan_models.py +++ b/stan_models.py @@ -145,4 +145,225 @@ loglikes[i] = normal_lpdf(target_curve[i] | pred_curve[i], target_errors[i]); } } +""" + +# --- HDX-MS ensemble-averaged uptake (precomputed per conformer) --- +stan_code_HDX = """ +data { + int n_obs; + int n_structures; + vector[n_obs] target_hdx; + vector[n_obs] target_hdxerr; + matrix[n_obs, n_structures] sim_hdx; + vector[n_structures] priors; +} + +parameters { + simplex[n_structures] weights; +} + +model { + vector[n_obs] pred_hdx; + weights ~ dirichlet(priors); + pred_hdx = sim_hdx * weights; + target_hdx ~ normal(pred_hdx, target_hdxerr); +} +""" + +# --- XL-MS probabilistic distance restraints (soft satisfaction) --- +stan_code_XL = """ +data { + int n_xl; + int n_structures; + array[n_xl] int xl_obs; + vector[n_xl] d_max; + vector[n_xl] tau; + matrix[n_xl, n_structures] xl_distances; + vector[n_structures] priors; +} + +parameters { + simplex[n_structures] weights; +} + +model { + weights ~ dirichlet(priors); + for (l in 1:n_xl) { + xl_obs[l] ~ bernoulli( + dot_product(weights, inv_logit((d_max[l] - xl_distances[l]) / tau[l])) + ); + } +} +""" + +# False-positive mixture: P(z=1) = (1-pi_fp)*p_sat + pi_fp*(1-p_sat) +stan_code_XL_FP = """ +data { + int n_xl; + int n_structures; + array[n_xl] int xl_obs; + vector[n_xl] d_max; + vector[n_xl] tau; + matrix[n_xl, n_structures] xl_distances; + vector[n_structures] priors; +} + +parameters { + simplex[n_structures] weights; + real pi_fp; +} + +model { + real p_sat; + pi_fp ~ beta(1, 9); + weights ~ dirichlet(priors); + for (l in 1:n_xl) { + p_sat = dot_product(weights, inv_logit((d_max[l] - xl_distances[l]) / tau[l])); + xl_obs[l] ~ bernoulli((1 - pi_fp) * p_sat + pi_fp * (1 - p_sat)); + } +} +""" + +stan_code_SAXS_HDX = """ +data { + int n_measures; + int n_hdx; + int n_structures; + vector[n_measures] target_saxs; + vector[n_measures] target_saxserr; + matrix[n_measures, n_structures] sim_saxs; + vector[n_hdx] target_hdx; + vector[n_hdx] target_hdxerr; + matrix[n_hdx, n_structures] sim_hdx; + vector[n_structures] priors; +} + +parameters { + simplex[n_structures] weights; + real scale; +} + +model { + vector[n_measures] pred_saxs; + vector[n_hdx] pred_hdx; + weights ~ dirichlet(priors); + pred_saxs = sim_saxs * weights * scale; + pred_hdx = sim_hdx * weights; + target_saxs ~ normal(pred_saxs, target_saxserr); + target_hdx ~ normal(pred_hdx, target_hdxerr); +} +""" + +stan_code_SAXS_XL = """ +data { + int n_measures; + int n_xl; + int n_structures; + vector[n_measures] target_saxs; + vector[n_measures] target_saxserr; + matrix[n_measures, n_structures] sim_saxs; + array[n_xl] int xl_obs; + vector[n_xl] d_max; + vector[n_xl] tau; + matrix[n_xl, n_structures] xl_distances; + vector[n_structures] priors; +} + +parameters { + simplex[n_structures] weights; + real scale; +} + +model { + vector[n_measures] pred_saxs; + real p_sat; + weights ~ dirichlet(priors); + pred_saxs = sim_saxs * weights * scale; + target_saxs ~ normal(pred_saxs, target_saxserr); + for (l in 1:n_xl) { + p_sat = dot_product(weights, inv_logit((d_max[l] - xl_distances[l]) / tau[l])); + xl_obs[l] ~ bernoulli(p_sat); + } +} +""" + +stan_code_SAXS_HDX_XL = """ +data { + int n_measures; + int n_hdx; + int n_xl; + int n_structures; + vector[n_measures] target_saxs; + vector[n_measures] target_saxserr; + matrix[n_measures, n_structures] sim_saxs; + vector[n_hdx] target_hdx; + vector[n_hdx] target_hdxerr; + matrix[n_hdx, n_structures] sim_hdx; + array[n_xl] int xl_obs; + vector[n_xl] d_max; + vector[n_xl] tau; + matrix[n_xl, n_structures] xl_distances; + vector[n_structures] priors; +} + +parameters { + simplex[n_structures] weights; + real scale; +} + +model { + vector[n_measures] pred_saxs; + vector[n_hdx] pred_hdx; + real p_sat; + weights ~ dirichlet(priors); + pred_saxs = sim_saxs * weights * scale; + pred_hdx = sim_hdx * weights; + target_saxs ~ normal(pred_saxs, target_saxserr); + target_hdx ~ normal(pred_hdx, target_hdxerr); + for (l in 1:n_xl) { + p_sat = dot_product(weights, inv_logit((d_max[l] - xl_distances[l]) / tau[l])); + xl_obs[l] ~ bernoulli(p_sat); + } +} +""" + +stan_code_HDX_XL = """ +data { + int n_hdx; + int n_xl; + int n_structures; + vector[n_hdx] target_hdx; + vector[n_hdx] target_hdxerr; + matrix[n_hdx, n_structures] sim_hdx; + array[n_xl] int xl_obs; + vector[n_xl] d_max; + vector[n_xl] tau; + matrix[n_xl, n_structures] xl_distances; + vector[n_structures] priors; +} + +parameters { + simplex[n_structures] weights; +} + +model { + vector[n_hdx] pred_hdx; + real p_sat; + weights ~ dirichlet(priors); + pred_hdx = sim_hdx * weights; + target_hdx ~ normal(pred_hdx, target_hdxerr); + for (l in 1:n_xl) { + p_sat = dot_product(weights, inv_logit((d_max[l] - xl_distances[l]) / tau[l])); + xl_obs[l] ~ bernoulli(p_sat); + } +} +""" + +xl_posterior_predict = """ +generated quantities { + vector[n_xl] p_sat; + for (l in 1:n_xl) { + p_sat[l] = dot_product(weights, inv_logit((d_max[l] - xl_distances[l]) / tau[l])); + } +} """ \ No newline at end of file diff --git a/structure_observables.py b/structure_observables.py new file mode 100644 index 0000000..cd47ec4 --- /dev/null +++ b/structure_observables.py @@ -0,0 +1,351 @@ +""" +Compute SAXS, HDX, and XL-MS observables from PDB conformers. + +SAXS: FoXS (preferred) or Pepsi-SAXS if available on PATH. +HDX: peptide mean relative SASA (Shrake–Rupley) as a protection proxy. +XL: Cα–Cα distances for residue pairs in the restraint list. +""" +from __future__ import annotations + +import os +import shlex +import shutil +import zipfile +from pathlib import Path +from subprocess import PIPE, Popen +from typing import List, Optional, Sequence, Tuple + +import numpy as np + +try: + from Bio.PDB import PDBParser, ShrakeRupley + from Bio.PDB.Polypeptide import is_aa + _HAS_BIOPYTHON = True +except ImportError: + _HAS_BIOPYTHON = False + +PDBParser_instance = PDBParser(QUIET=True) if _HAS_BIOPYTHON else None + + +def _foxs_on_path() -> bool: + return shutil.which("foxs") is not None + + +def _pepsi_on_path() -> bool: + return shutil.which("Pepsi-SAXS") is not None + + +def extract_pdbs(uploaded, dest_dir: Path) -> List[Path]: + """Save uploaded Streamlit files or a zip archive into dest_dir; return .pdb paths.""" + dest_dir.mkdir(parents=True, exist_ok=True) + paths: List[Path] = [] + for item in uploaded: + name = getattr(item, "name", str(item)) + if name.lower().endswith(".zip"): + with zipfile.ZipFile(item) as zf: + zf.extractall(dest_dir) + else: + data = item.getvalue() if hasattr(item, "getvalue") else Path(item).read_bytes() + out = dest_dir / Path(name).name + out.write_bytes(data) + for pdb in sorted(dest_dir.rglob("*.pdb")): + paths.append(pdb) + for pdb in sorted(dest_dir.rglob("*.PDB")): + if pdb not in paths: + paths.append(pdb) + if not paths: + raise ValueError("No PDB files found in upload.") + return paths + + +def _run_foxs(pdb_path: Path, exp_path: Path, work_dir: Path) -> List[float]: + cmd = "foxs {} {}".format(pdb_path, exp_path) + proc = Popen(shlex.split(cmd), stdout=PIPE, cwd=str(work_dir), shell=False) + proc.wait() + out_name = pdb_path.stem + "_" + exp_path.name + foxs_file = work_dir / out_name + if not foxs_file.exists(): + foxs_file = pdb_path.parent / out_name + if not foxs_file.exists(): + raise RuntimeError("FoXS did not produce {}".format(out_name)) + lines = foxs_file.read_text().splitlines()[1:] + norm_c = 1e8 + scaling_c = norm_c * float(lines[0].split(",")[1][12:]) + intensities = [] + for line in lines[2:]: + intensities.append(float(line.split()[2]) / scaling_c) + return intensities + + +def _run_pepsi(pdb_path: Path, exp_path: Path) -> np.ndarray: + cmd = ( + "Pepsi-SAXS --dp_min 0 --dp_max 0 --dp_N 1 --r0_min_factor 1 " + "--r0_max_factor 1 --r0_N 1 {} {}".format(pdb_path, exp_path) + ) + proc = Popen(shlex.split(cmd), stdout=PIPE, shell=False) + proc.wait() + fit_file = Path(str(pdb_path)[:-4] + "-" + str(exp_path)[:-3] + "fit") + if not fit_file.exists(): + fit_file = pdb_path.parent / fit_file.name + data = np.genfromtxt(fit_file, skip_header=6) + intensity = data[:, 3] + log_path = Path(str(pdb_path)[:-4] + "-" + str(exp_path)[:-3] + "log") + log_line = log_path.read_text().splitlines()[62] + if not log_line.startswith("Scaling"): + raise RuntimeError("Pepsi-SAXS scaling line not found in log") + scale = float(log_line.split(":")[1]) + return intensity / scale + + +def generate_saxs_matrix( + pdb_paths: Sequence[Path], + experimental_path: Path, + out_dir: Path, +) -> Tuple[Path, Path, Path, np.ndarray]: + """ + Write SimulatedIntensities.txt, weights.txt, structures.txt; return paths + matrix. + """ + out_dir.mkdir(parents=True, exist_ok=True) + exp_copy = out_dir / experimental_path.name + shutil.copy(experimental_path, exp_copy) + + if _foxs_on_path(): + backend = "FoXS" + columns = [] + for pdb in pdb_paths: + columns.append(_run_foxs(pdb, exp_copy, pdb.parent)) + elif _pepsi_on_path(): + backend = "Pepsi-SAXS" + columns = [_run_pepsi(pdb, exp_copy) for pdb in pdb_paths] + else: + raise RuntimeError( + "Neither FoXS nor Pepsi-SAXS found on PATH. " + "Install ATSAS FoXS or Pepsi-SAXS for SAXS simulation." + ) + + sim = np.column_stack(columns) + names = [p.stem for p in pdb_paths] + n = len(names) + priors = np.full(n, 1.0 / n) + + sim_path = out_dir / "SimulatedIntensities.txt" + weights_path = out_dir / "weights.txt" + names_path = out_dir / "structures.txt" + np.savetxt(sim_path, sim, fmt="%.8g") + np.savetxt(weights_path, priors, fmt="%.8g") + with open(names_path, "w") as handle: + handle.write(" ".join(names) + "\n") + + (out_dir / "saxs_backend.txt").write_text(backend) + return sim_path, weights_path, names_path, sim + + +def _get_ca_coord(structure, chain_id: str, resseq: int): + model = structure[0] + chain = model[chain_id] if chain_id in [c.id for c in model] else model.child_list[0] + res = chain[resseq] + for atom_name in ("CA", "CB", "N"): + if atom_name in res: + return res[atom_name].coord + raise KeyError("No CA/CB/N for {}:{}".format(chain_id, resseq)) + + +def ca_distance(pdb_path: Path, chain_i: str, res_i: int, chain_j: str, res_j: int) -> float: + if not _HAS_BIOPYTHON: + raise RuntimeError("BioPython is required for XL/HDX from PDB (pip install biopython).") + structure = PDBParser_instance.get_structure("bioce", str(pdb_path)) + ci = _get_ca_coord(structure, chain_i, res_i) + cj = _get_ca_coord(structure, chain_j, res_j) + return float(np.linalg.norm(ci - cj)) + + +def parse_xl_restraints(lines: List[str]) -> List[dict]: + """ + Parse crosslink lines. Supported formats per line: + res_i res_j z [d_max] [tau] + chain_i res_i chain_j res_j z [d_max] [tau] + """ + records = [] + for raw in lines: + line = raw.strip() + if not line or line.startswith("#"): + continue + parts = line.split() + if len(parts) < 3: + continue + if len(parts) in (3, 4, 5): + res_i, res_j, z = int(parts[0]), int(parts[1]), int(float(parts[2])) + d_max = float(parts[3]) if len(parts) > 3 else 30.0 + tau = float(parts[4]) if len(parts) > 4 else 3.0 + records.append({ + "chain_i": "A", "res_i": res_i, "chain_j": "A", "res_j": res_j, + "z": z, "d_max": d_max, "tau": tau, + }) + else: + chain_i, res_i = parts[0], int(parts[1]) + chain_j, res_j = parts[2], int(parts[3]) + z = int(float(parts[4])) + d_max = float(parts[5]) if len(parts) > 5 else 30.0 + tau = float(parts[6]) if len(parts) > 6 else 3.0 + records.append({ + "chain_i": chain_i, "res_i": res_i, "chain_j": chain_j, "res_j": res_j, + "z": z, "d_max": d_max, "tau": tau, + }) + if not records: + raise ValueError("No XL restraints parsed.") + return records + + +def write_xl_restraints_std(records: List[dict], path: Path) -> None: + with open(path, "w") as handle: + for r in records: + handle.write( + "{} {} {} {} {} {} {}\n".format( + r["chain_i"], r["res_i"], r["chain_j"], r["res_j"], + r["z"], r["d_max"], r["tau"], + ) + ) + + +def generate_xl_distance_matrix( + pdb_paths: Sequence[Path], + records: List[dict], + out_dir: Path, +) -> Tuple[Path, Path, np.ndarray]: + out_dir.mkdir(parents=True, exist_ok=True) + n_xl = len(records) + n_struct = len(pdb_paths) + dist = np.zeros((n_xl, n_struct)) + for j, pdb in enumerate(pdb_paths): + for i, rec in enumerate(records): + try: + dist[i, j] = ca_distance( + pdb, rec["chain_i"], rec["res_i"], rec["chain_j"], rec["res_j"] + ) + except (KeyError, IndexError) as exc: + dist[i, j] = 999.0 + xl_path = out_dir / "xl_restraints.dat" + write_xl_restraints_for_stan(records, xl_path) + write_xl_restraints_std(records, out_dir / "xl_restraints_full.dat") + sim_path = out_dir / "SimulatedXLdistances.txt" + np.savetxt(sim_path, dist, fmt="%.4f") + return xl_path, sim_path, dist + + +def parse_hdx_peptides(lines: List[str]) -> List[dict]: + """Each line: chain start_res end_res (1-based inclusive).""" + peptides = [] + for raw in lines: + line = raw.strip() + if not line or line.startswith("#"): + continue + parts = line.split() + if len(parts) < 3: + continue + peptides.append({ + "chain": parts[0], + "start": int(parts[1]), + "end": int(parts[2]), + }) + if not peptides: + raise ValueError("No HDX peptides defined.") + return peptides + + +def parse_hdx_experimental( + lines: List[str], n_peptides: int +) -> Tuple[np.ndarray, np.ndarray, Optional[np.ndarray]]: + """ + Formats: + uptake sigma (n_peptides lines, single time point) + pep_idx time uptake sigma (multiple time points; pep_idx 0-based) + Returns (uptake, sigma, pep_indices) where pep_indices is None for single-time. + """ + rows = [] + for raw in lines: + line = raw.strip() + if not line or line.startswith("#"): + continue + rows.append([float(x) for x in line.split()]) + if not rows: + raise ValueError("No HDX experimental data.") + arr = np.asarray(rows) + if arr.shape[1] == 2: + if arr.shape[0] != n_peptides: + raise ValueError( + "Expected {} peptide uptake rows, got {}".format(n_peptides, arr.shape[0]) + ) + return arr[:, 0], arr[:, 1], None + if arr.shape[1] >= 4: + order = np.lexsort((arr[:, 1], arr[:, 0])) + arr = arr[order] + pep_idx = arr[:, 0].astype(int) + return arr[:, 2], arr[:, 3], pep_idx + raise ValueError("HDX experimental file: use 'uptake sigma' or 'pep_idx time uptake sigma'.") + + +def peptide_mean_relative_sasa(pdb_path: Path, chain_id: str, start: int, end: int) -> float: + if not _HAS_BIOPYTHON: + raise RuntimeError("BioPython is required for HDX from PDB.") + structure = PDBParser_instance.get_structure("bioce", str(pdb_path)) + sr = ShrakeRupley() + sr.compute(structure, level="R") + model = structure[0] + chain = model[chain_id] if chain_id in [c.id for c in model] else model.child_list[0] + values = [] + for res in chain: + if not is_aa(res, standard=True): + continue + if res.id[1] < start or res.id[1] > end: + continue + sasa = res.sasa if hasattr(res, "sasa") else 0.0 + values.append(sasa) + if not values: + return 0.5 + mean_sasa = float(np.mean(values)) + return min(1.0, mean_sasa / 200.0) + + +def generate_hdx_matrix( + pdb_paths: Sequence[Path], + peptides: List[dict], + out_dir: Path, + pep_indices: Optional[np.ndarray] = None, +) -> Tuple[Path, np.ndarray]: + """Build simulated HDX matrix; rows follow pep_indices or one row per peptide.""" + out_dir.mkdir(parents=True, exist_ok=True) + if pep_indices is None: + obs_pep = list(range(len(peptides))) + else: + obs_pep = [int(i) for i in pep_indices] + n_obs = len(obs_pep) + n_struct = len(pdb_paths) + sim = np.zeros((n_obs, n_struct)) + cache = {} + for j, pdb in enumerate(pdb_paths): + for row_i, pidx in enumerate(obs_pep): + key = (pidx, j) + if key not in cache: + pep = peptides[pidx] + cache[key] = peptide_mean_relative_sasa( + pdb, pep["chain"], pep["start"], pep["end"] + ) + sim[row_i, j] = cache[key] + sim_path = out_dir / "SimulatedHDX.txt" + np.savetxt(sim_path, sim, fmt="%.6f") + return sim_path, sim + + +def write_xl_restraints_for_stan(records: List[dict], path: Path) -> None: + """Stan reader format: res_i res_j z [d_max] [tau].""" + with open(path, "w") as handle: + for r in records: + handle.write( + "{} {} {} {} {}\n".format( + r["res_i"], r["res_j"], r["z"], r["d_max"], r["tau"] + ) + ) + + +def write_hdx_experimental(uptake: np.ndarray, sigma: np.ndarray, path: Path) -> None: + np.savetxt(path, np.column_stack([uptake, sigma]), fmt="%.6f") diff --git a/tests/fixtures/two_chain_mini_A.pdb b/tests/fixtures/two_chain_mini_A.pdb new file mode 100644 index 0000000..205353c --- /dev/null +++ b/tests/fixtures/two_chain_mini_A.pdb @@ -0,0 +1,24 @@ +ATOM 1 N ALA A 2 0.000 0.000 0.000 1.00 0.00 N +ATOM 2 CA ALA A 2 1.458 0.000 0.000 1.00 0.00 C +ATOM 3 C ALA A 2 2.009 1.420 0.000 1.00 0.00 C +ATOM 4 O ALA A 2 1.251 2.390 0.000 1.00 0.00 O +ATOM 5 CB ALA A 2 1.958 -0.770 1.210 1.00 0.00 C +ATOM 6 N GLY A 3 3.300 1.520 0.000 1.00 0.00 N +ATOM 7 CA GLY A 3 4.000 2.800 0.000 1.00 0.00 C +ATOM 8 C GLY A 3 5.500 2.600 0.000 1.00 0.00 C +ATOM 9 O GLY A 3 6.100 1.550 0.000 1.00 0.00 O +ATOM 10 N ALA A 10 10.000 0.000 0.000 1.00 0.00 N +ATOM 11 CA ALA A 10 11.458 0.000 0.000 1.00 0.00 C +ATOM 12 C ALA A 10 12.009 1.420 0.000 1.00 0.00 C +ATOM 13 O ALA A 10 11.251 2.390 0.000 1.00 0.00 O +ATOM 14 CB ALA A 10 11.958 -0.770 1.210 1.00 0.00 C +ATOM 15 N GLY A 15 20.000 0.000 0.000 1.00 0.00 N +ATOM 16 CA GLY A 15 21.458 0.000 0.000 1.00 0.00 C +ATOM 17 C GLY A 15 22.009 1.420 0.000 1.00 0.00 C +ATOM 18 O GLY A 15 21.251 2.390 0.000 1.00 0.00 O +ATOM 19 N ALA A 25 40.000 0.000 0.000 1.00 0.00 N +ATOM 20 CA ALA A 25 41.458 0.000 0.000 1.00 0.00 C +ATOM 21 C ALA A 25 42.009 1.420 0.000 1.00 0.00 C +ATOM 22 O ALA A 25 41.251 2.390 0.000 1.00 0.00 O +ATOM 23 CB ALA A 25 41.958 -0.770 1.210 1.00 0.00 C +END diff --git a/tests/fixtures/two_chain_mini_B.pdb b/tests/fixtures/two_chain_mini_B.pdb new file mode 100644 index 0000000..c95ffac --- /dev/null +++ b/tests/fixtures/two_chain_mini_B.pdb @@ -0,0 +1,24 @@ +ATOM 1 N ALA A 2 0.000 0.000 0.000 1.00 0.00 N +ATOM 2 CA ALA A 2 1.458 0.000 0.000 1.00 0.00 C +ATOM 3 C ALA A 2 2.009 1.420 0.000 1.00 0.00 C +ATOM 4 O ALA A 2 1.251 2.390 0.000 1.00 0.00 O +ATOM 5 CB ALA A 2 1.958 -0.770 1.210 1.00 0.00 C +ATOM 6 N GLY A 3 3.300 1.520 0.000 1.00 0.00 N +ATOM 7 CA GLY A 3 4.000 2.800 0.000 1.00 0.00 C +ATOM 8 C GLY A 3 5.500 2.600 0.000 1.00 0.00 C +ATOM 9 O GLY A 3 6.100 1.550 0.000 1.00 0.00 O +ATOM 10 N ALA A 10 12.000 0.000 0.000 1.00 0.00 N +ATOM 11 CA ALA A 10 13.458 0.000 0.000 1.00 0.00 C +ATOM 12 C ALA A 10 14.009 1.420 0.000 1.00 0.00 C +ATOM 13 O ALA A 10 13.251 2.390 0.000 1.00 0.00 O +ATOM 14 CB ALA A 10 13.958 -0.770 1.210 1.00 0.00 C +ATOM 15 N GLY A 15 18.000 0.000 0.000 1.00 0.00 N +ATOM 16 CA GLY A 15 19.458 0.000 0.000 1.00 0.00 C +ATOM 17 C GLY A 15 20.009 1.420 0.000 1.00 0.00 C +ATOM 18 O GLY A 15 19.251 2.390 0.000 1.00 0.00 O +ATOM 19 N ALA A 25 35.000 0.000 0.000 1.00 0.00 N +ATOM 20 CA ALA A 25 36.458 0.000 0.000 1.00 0.00 C +ATOM 21 C ALA A 25 37.009 1.420 0.000 1.00 0.00 C +ATOM 22 O ALA A 25 36.251 2.390 0.000 1.00 0.00 O +ATOM 23 CB ALA A 25 36.958 -0.770 1.210 1.00 0.00 C +END diff --git a/tests/test_multimodal_stan.py b/tests/test_multimodal_stan.py new file mode 100644 index 0000000..2fca66e --- /dev/null +++ b/tests/test_multimodal_stan.py @@ -0,0 +1,140 @@ +""" +Compile/sample tests for HDX and XL-MS Stan models (skip if pystan missing). +""" +from __future__ import annotations + +import tempfile +from pathlib import Path + +import numpy as np +import pytest + + +@pytest.fixture +def priors(): + return np.array([1.0, 1.0, 1.0]) + + +@pytest.fixture +def tmp_dir(): + with tempfile.TemporaryDirectory() as d: + yield Path(d) + + +def test_read_xl_restraints_defaults(): + from multimodal_io import read_xl_restraints, DEFAULT_XL_DMAX, DEFAULT_XL_TAU + + with tempfile.NamedTemporaryFile(mode="w", suffix=".dat", delete=False) as f: + f.write("10 20 1\n") + f.write("30 40 0\n") + path = f.name + obs, dmax, tau = read_xl_restraints(path) + assert list(obs) == [1, 0] + assert dmax[0] == DEFAULT_XL_DMAX + assert tau[1] == DEFAULT_XL_TAU + + +def test_stan_hdx_only(priors): + pytest.importorskip("stan") + from stan_run import build_and_sample + from stan_models import stan_code_HDX + + n_obs, n_structures = 6, 3 + rng = np.random.default_rng(0) + sim = rng.uniform(0, 1, (n_obs, n_structures)) + w_true = np.array([0.5, 0.3, 0.2]) + target = sim @ w_true + stan_dat = { + "n_obs": n_obs, + "n_structures": n_structures, + "target_hdx": target, + "target_hdxerr": np.full(n_obs, 0.05), + "sim_hdx": sim, + "priors": priors, + } + fit = build_and_sample( + stan_code_HDX, stan_dat, iterations=40, chains=2, njobs=2, show_progress=False + ) + assert "weights" in fit + + +def test_stan_xl_only(priors): + pytest.importorskip("stan") + from stan_run import build_and_sample + from stan_models import stan_code_XL + + n_xl, n_structures = 4, 3 + distances = np.array([ + [10.0, 25.0, 28.0], + [12.0, 11.0, 35.0], + [30.0, 32.0, 31.0], + [15.0, 16.0, 14.0], + ]) + stan_dat = { + "n_xl": n_xl, + "n_structures": n_structures, + "xl_obs": np.array([1, 1, 0, 1], dtype=np.int32), + "d_max": np.full(n_xl, 30.0), + "tau": np.full(n_xl, 3.0), + "xl_distances": distances, + "priors": priors, + } + fit = build_and_sample( + stan_code_XL, stan_dat, iterations=40, chains=2, njobs=2, show_progress=False + ) + assert "weights" in fit + + +def test_stan_saxs_hdx_xl(priors): + pytest.importorskip("stan") + from stan_run import build_and_sample + from stan_models import stan_code_SAXS_HDX_XL, xl_posterior_predict + + n_measures, n_hdx, n_xl, n_structures = 5, 4, 3, 3 + rng = np.random.default_rng(1) + sim_saxs = rng.uniform(0.01, 0.05, (n_measures, n_structures)) + sim_hdx = rng.uniform(0, 1, (n_hdx, n_structures)) + xl_distances = rng.uniform(8, 35, (n_xl, n_structures)) + stan_dat = { + "n_measures": n_measures, + "n_hdx": n_hdx, + "n_xl": n_xl, + "n_structures": n_structures, + "target_saxs": sim_saxs.mean(axis=1), + "target_saxserr": np.full(n_measures, 0.001), + "sim_saxs": sim_saxs, + "target_hdx": sim_hdx.mean(axis=1), + "target_hdxerr": np.full(n_hdx, 0.05), + "sim_hdx": sim_hdx, + "xl_obs": np.array([1, 1, 0], dtype=np.int32), + "d_max": np.full(n_xl, 30.0), + "tau": np.full(n_xl, 3.0), + "xl_distances": xl_distances, + "priors": priors, + } + fit = build_and_sample( + stan_code_SAXS_HDX_XL + xl_posterior_predict, + stan_dat, + iterations=40, + chains=2, + njobs=2, + show_progress=False, + ) + assert "weights" in fit + assert "p_sat" in fit + + +def test_fullBayesianMultimodal_hdx_cli(tmp_dir, priors): + pytest.importorskip("stan") + from fullBayesianMultimodal import execute_stan_hdx + + n_obs, n_structures = 5, 3 + sim = np.ones((n_obs, n_structures)) * np.linspace(0.2, 0.8, n_structures) + exp_path = tmp_dir / "hdx_exp.dat" + sim_path = tmp_dir / "SimulatedHDX.txt" + np.savetxt(exp_path, np.column_stack([sim.mean(axis=1), np.full(n_obs, 0.1)])) + np.savetxt(sim_path, sim) + fit = execute_stan_hdx( + str(exp_path), str(sim_path), priors, iterations=32, chains=2, njobs=2 + ) + assert "weights" in fit diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py new file mode 100644 index 0000000..554869d --- /dev/null +++ b/tests/test_pipeline.py @@ -0,0 +1,71 @@ +"""Pipeline tests (HDX+XL from PDB; SAXS if FoXS/Pepsi available).""" +from __future__ import annotations + +from pathlib import Path + +import numpy as np +import pytest + +FIXTURES = Path(__file__).parent / "fixtures" + + +class _FakeUpload: + def __init__(self, path: Path): + self.name = path.name + self._path = path + + def getvalue(self): + return self._path.read_bytes() + + +@pytest.fixture +def mini_pdbs(): + return [ + _FakeUpload(FIXTURES / "two_chain_mini_A.pdb"), + _FakeUpload(FIXTURES / "two_chain_mini_B.pdb"), + ] + + +def test_observables_xl_hdx(mini_pdbs, tmp_path): + pytest.importorskip("Bio.PDB") + from structure_observables import ( + extract_pdbs, + generate_hdx_matrix, + generate_xl_distance_matrix, + parse_hdx_peptides, + parse_xl_restraints, + ) + + pdb_dir = tmp_path / "pdbs" + paths = extract_pdbs(mini_pdbs, pdb_dir) + xl_lines = ["2 25 1 30 3", "10 15 0 30 3"] + records = parse_xl_restraints(xl_lines) + _, sim_xl, dist = generate_xl_distance_matrix(paths, records, tmp_path / "xl") + assert dist.shape == (2, 2) + assert dist[0, 0] < dist[0, 1] + + peptides = parse_hdx_peptides(["A 2 10", "A 15 25"]) + _, sim_h = generate_hdx_matrix(paths, peptides, tmp_path / "hdx") + assert sim_h.shape == (2, 2) + + +def test_pipeline_hdx_xl_inference(mini_pdbs, tmp_path): + pytest.importorskip("stan") + pytest.importorskip("Bio.PDB") + from bioce_pipeline import PipelineConfig, run_full_pipeline + + config = PipelineConfig( + work_dir=tmp_path / "run", + use_saxs=False, + use_hdx=True, + use_xl=True, + iterations=60, + chains=2, + njobs=2, + hdx_peptide_lines=["A 2 10", "A 15 25"], + hdx_exp_lines=["0.3 0.05", "0.55 0.05"], + xl_restraint_lines=["2 25 1", "10 15 0"], + ) + result = run_full_pipeline(mini_pdbs, config) + assert result.bayesian_weights.shape[0] == 2 + assert np.isclose(result.bayesian_weights.sum(), 1.0, atol=0.02) From 90b281472a1358aa154f62b7ea9b5c87f540c85f Mon Sep 17 00:00:00 2001 From: Wojtek Potrzebowski Date: Fri, 22 May 2026 09:33:08 +0200 Subject: [PATCH 5/5] adding --- docs/METHODS_HDX_XL.md | 137 ++++++++++++++++++++++++++++++++ docs/WORKFLOW.md | 174 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 docs/METHODS_HDX_XL.md create mode 100644 docs/WORKFLOW.md diff --git a/docs/METHODS_HDX_XL.md b/docs/METHODS_HDX_XL.md new file mode 100644 index 0000000..fe0eed8 --- /dev/null +++ b/docs/METHODS_HDX_XL.md @@ -0,0 +1,137 @@ +# Materials and Methods: HDX-MS and XL-MS extensions in bioce + +This section describes the hydrogen–deuterium exchange mass spectrometry (HDX-MS) and cross-linking mass spectrometry (XL-MS) likelihoods added to the bioce Bayesian ensemble framework. Small-angle X-ray scattering (SAXS) inference in bioce is described in Potrzebowski et al. (PLOS Computational Biology, 2018); here we assume the same conformational library and population-weight formalism, and document only the new modalities and their integration. + +**Workflow diagram:** [WORKFLOW.md](WORKFLOW.md) + +--- + +## Overview + +We extend bioce to combine HDX-MS and XL-MS data with a shared discrete ensemble of *K* structural conformers {*S*₁,…,*S*ₖ}. Each conformer has a non-negative population weight *w*ₖ with Σₖ *w*ₖ = 1. Experimental observables are predicted as ensemble averages over conformers, and independent likelihoods for each modality are multiplied in a joint Stan model (`fullBayesianMultimodal.py`, `stan_models.py`). When SAXS is included in the same run, it shares the same weight vector; the SAXS scale factor applies only to the scattering term and is not part of the HDX or XL blocks. + +--- + +## Structural library and per-conformer observables + +The input structural library is the same as in the original bioce workflow: one three-dimensional model per ensemble member (PDB format). For each modality we precompute a matrix of predicted observables with rows indexed by experimental observations (HDX peptides or time points; XL restraints) and columns indexed by conformers. + +### HDX-MS predicted uptake + +For each peptide *i*, defined by chain identifier, and inclusive residue range [*start*ᵢ, *end*ᵢ], we compute a structure-based solvent-exposure proxy from conformer *S*ₖ. Residue solvent-accessible surface area (SASA) is calculated with the Shrake–Rupley algorithm (BioPython `ShrakeRupley`, residue-level). The mean SASA over standard amino-acid residues in the peptide range is divided by 200 Ų and capped at 1: + +$$D_{\mathrm{pred},i}(S_k) = \min\!\left(1,\; \frac{1}{|P_i|}\sum_{r \in P_i} \frac{\mathrm{SASA}_r(S_k)}{200}\right)$$ + +where *P*ᵢ is the set of residues in peptide *i* and |*P*ᵢ| is the number of residues contributing. Higher values correspond to greater apparent solvent exposure (a simple proxy for deuterium uptake). This implementation does not fit exchange kinetics (e.g. logistic or Weibull curves); users may supply externally computed per-conformer uptake matrices via `prepareHDX.py` instead of the built-in SASA proxy (`structure_observables.py`). + +### XL-MS predicted distances + +For each crosslink restraint *l* between residues (*chain*ᵢ, *res*ᵢ) and (*chain*ⱼ, *res*ⱼ), we compute the Euclidean distance between preferred backbone atoms in conformer *S*ₖ, using Cα when present, otherwise Cβ or N. The distance *d*ₗ(*S*ₖ) is stored in Å. If atoms or residues are missing, a penalty distance of 999 Å is assigned so the restraint is effectively unsatisfied in that conformer. + +--- + +## HDX-MS likelihood + +Experimental HDX data are reduced to peptide-level deuterium uptake values *y*ᵢ with associated uncertainties *σ*ᵢ. Observations may represent a single time point per peptide (one row per peptide) or multiple time points encoded as flattened rows with peptide index and time (`multimodal_io.py`). + +The ensemble-averaged predicted uptake is + +$$\hat{D}_i = \sum_{k=1}^{K} w_k\, D_{\mathrm{pred},i}(S_k).$$ + +Each observation is modeled with a Gaussian likelihood: + +$$y_i \sim \mathcal{N}(\hat{D}_i,\, \sigma_i^2).$$ + +In Stan (`stan_code_HDX`, and jointly in `stan_code_SAXS_HDX`, `stan_code_HDX_XL`, `stan_code_SAXS_HDX_XL`), the predicted vector is formed as **sim_hdx** × **w**, where **sim_hdx** is the *n*ₒᵦₛ × *K* matrix of *D*ₚᵣₑ𝒹,ᵢ(*S*ₖ). + +--- + +## XL-MS likelihood + +Crosslinks are treated as probabilistic distance restraints rather than hard cutoffs. For restraint *l*, let *z*ₗ ∈ {0,1} indicate whether the crosslink was reported (*z*ₗ = 1) or listed as absent/decoy (*z*ₗ = 0). User-specified or default parameters are the effective maximum linker distance *d*ₘₐₓ,ₗ (default 30 Å) and softness *τ*ₗ (default 3 Å), which absorb linker length, flexibility, and assignment uncertainty. + +Per conformer, a soft satisfaction score is + +$$s_{l,k} = \mathrm{logit}^{-1}\!\left(\frac{d_{\max,l} - d_l(S_k)}{\tau_l}\right) = \frac{1}{1 + \exp\!\left(\frac{d_l(S_k) - d_{\max,l}}{\tau_l}\right)}.$$ + +The ensemble probability that the restraint is satisfied is + +$$p_{\mathrm{sat},l} = \sum_{k=1}^{K} w_k\, s_{l,k}.$$ + +Observed crosslinks are modeled with a Bernoulli likelihood: + +$$z_l \sim \mathrm{Bernoulli}(p_{\mathrm{sat},l}).$$ + +An optional false-positive mixture (`stan_code_XL_FP`, CLI flag `--xl-fp-mixture`) introduces a global false-positive rate *π*ₑₚ with prior *π*ₑₚ ~ Beta(1, 9): + +$$P(z_l = 1) = (1 - \pi_{\mathrm{fp}})\, p_{\mathrm{sat},l} + \pi_{\mathrm{fp}}\, (1 - p_{\mathrm{sat},l}).$$ + +**Non-observed crosslinks** (links not reported in the restraint list) are not scored as negatives, avoiding penalty for conformers when detectability, digestion, or sampling limits crosslink identification. + +Posterior satisfaction probabilities *p*ₛₐₜ,ₗ are computed in generated quantities (`xl_posterior_predict`) for each MCMC draw and summarized (e.g. posterior mean per restraint). + +--- + +## Priors and Bayesian inference + +Population weights are constrained to the simplex with a Dirichlet prior, + +$$\mathbf{w} \sim \mathrm{Dirichlet}(\boldsymbol{\alpha}),$$ + +where **α** is read from a user file (e.g. uniform αₖ = 1, or energy-informed priors when combined with the SAXS+energy models in the original bioce release). HDX and XL modules use the same **w** as SAXS when run jointly. + +Inference uses Hamiltonian Monte Carlo with the No-U-Turn Sampler (NUTS) via PyStan 3 (`stan_run.build_and_sample`). Total iterations per chain are split equally into warmup and sampling (PyStan 2–compatible default: half warmup, half samples). Typical CLI defaults are 2000 iterations and 4 chains (`fullBayesianMultimodal.py`); the Streamlit app uses lower defaults for interactive use. + +Software: Python 3, NumPy, Stan/PyStan 3 (httpstan backend), BioPython for PDB parsing and SASA when using the automated pipeline (`bioce_pipeline.py`, `app.py`). + +--- + +## Data preparation and software + +**Command-line workflow** + +| Step | Script | Output | +|------|--------|--------| +| HDX simulated matrix | `prepareHDX.py` | `SimulatedHDX.txt` (rows = observations, columns = conformers) | +| XL distance matrix | `prepareXLMS.py` | `SimulatedXLdistances.txt` | +| Inference | `fullBayesianMultimodal.py` | Posterior samples, weight plots | + +**Experimental file formats** + +- `hdx_exp.dat`: columns `uptake` `sigma`, or `peptide_index` `time` `uptake` `sigma` for time series. +- `hdx_peptides.txt` (pipeline/app): `chain` `start` `end` per line. +- `xl_restraints.dat`: `res_i` `res_j` `z` [`d_max`] [`tau`], or `chain_i` `res_i` `chain_j` `res_j` `z` [`d_max`] [`tau`]. + +**Web interface** + +The Streamlit application (`app.py`) runs the same Stan models: users upload PDB conformers and experimental files; the app builds simulated matrices (SASA-based HDX proxy, Cα distances for XL) and calls `bioce_pipeline.run_full_pipeline`. + +--- + +## Post hoc validation + +- **HDX:** root-mean-square error between observed uptake and posterior mean ensemble prediction *D̂* (`bioce_pipeline.py`). +- **XL:** posterior mean of *p*ₛₐₜ,ₗ per restraint from generated quantities. + +These metrics are diagnostic summaries; formal model comparison (e.g. Bayes factors, LOO) follows the original bioce SAXS workflow where implemented. + +--- + +## Limitations + +1. **HDX:** The default structure-to-uptake mapping uses mean peptide SASA, not hydrogen-exchange kinetics, LC-MS peak integration, or condition-specific Weibull/logistic models (cf. Crook et al., J. Proteome Res. 2023). Users requiring kinetic HDX models should supply precomputed `SimulatedHDX.txt` from external tools. +2. **XL:** Distances use Cα (or fallback) pairs, not full linker chemistry or atom-specific crosslinker geometry. There is no explicit model for undetected crosslinks beyond omitting them from the restraint list. +3. **Ensemble selection:** Variational Bayesian model selection (`variationalBayesian.py`) has not been extended to multimodal HDX/XL likelihoods; users may pre-filter conformers with SAXS-only VB before multimodal inference. + +--- + +## Key references + +1. Potrzebowski W., Trewhella J., Andre I. Bayesian inference of protein conformational ensembles from limited structural data. *PLOS Computational Biology* **14**, e1006641 (2018). +2. Crook O. M., Gittens N., Chung C.-w., Deane C. M. A Functional Bayesian Model for Hydrogen-Deuterium Exchange Mass Spectrometry. *Journal of Proteome Research* **22**, 2959–2972 (2023). *(Related methodology; kinetic HDX layer not implemented in bioce.)* +3. Stan Development Team. Stan modeling language users guide and reference manual (versions cited with PyStan 3 install). +4. BioPython project: PDB parsing and Shrake–Rupley SASA (Cock et al., *Bioinformatics* 2009). + +--- + +*Document version: bioce multimodal extension (HDX/XL). Implementation: `stan_models.py`, `structure_observables.py`, `fullBayesianMultimodal.py`, `multimodal_io.py`.* diff --git a/docs/WORKFLOW.md b/docs/WORKFLOW.md new file mode 100644 index 0000000..b8653cf --- /dev/null +++ b/docs/WORKFLOW.md @@ -0,0 +1,174 @@ +# Bioce multimodal workflow + +End-to-end flow for Bayesian ensemble inference with SAXS, HDX-MS, and/or XL-MS. The same logic runs via **CLI** (`fullBayesianMultimodal.py`) or the **Streamlit app** (`app.py` → `bioce_pipeline.py`). + +See also: [METHODS_HDX_XL.md](METHODS_HDX_XL.md) for equations and [README.md](../README.md) for usage commands. + +--- + +## Overview + +```mermaid +flowchart TB + subgraph inputs [User inputs] + PDB["PDB ensemble\n(zip or files)"] + SAXSexp["SAXS curve\nq, I, sigma"] + HDXpep["HDX peptides\nchain start end"] + HDXexp["HDX uptake\nuptake sigma"] + XLrest["XL restraints\nres_i res_j z"] + end + + subgraph prep [Per-conformer observables] + FoXS["FoXS / Pepsi-SAXS"] + SASA["Peptide SASA\nShrake-Rupley"] + CAdist["C-alpha distances"] + SimSAXS["SimulatedIntensities.txt"] + SimHDX["SimulatedHDX.txt"] + SimXL["SimulatedXLdistances.txt"] + end + + subgraph stan [Stan NUTS inference] + W["Population weights w\nDirichlet prior"] + LikSAXS["SAXS: Normal likelihood"] + LikHDX["HDX: Normal likelihood"] + LikXL["XL: Bernoulli likelihood"] + Joint["Joint posterior\nshared w"] + end + + subgraph outputs [Outputs] + Weights["Posterior mean weights"] + Chi2["SAXS chi-squared"] + RMSE["HDX RMSE"] + Psat["XL p_sat per link"] + Plots["Fit plots and CSV"] + end + + PDB --> FoXS + PDB --> SASA + PDB --> CAdist + SAXSexp --> FoXS + FoXS --> SimSAXS + HDXpep --> SASA + HDXexp --> SimHDX + SASA --> SimHDX + XLrest --> CAdist + CAdist --> SimXL + + SimSAXS --> LikSAXS + SimHDX --> LikHDX + SimXL --> LikXL + SAXSexp --> LikSAXS + HDXexp --> LikHDX + XLrest --> LikXL + + W --> LikSAXS + W --> LikHDX + W --> LikXL + LikSAXS --> Joint + LikHDX --> Joint + LikXL --> Joint + + Joint --> Weights + Joint --> Chi2 + Joint --> RMSE + Joint --> Psat + Joint --> Plots +``` + +--- + +## Modality selection + +Any non-empty subset of SAXS, HDX, and XL is supported. Stan model choice is automatic: + +```mermaid +flowchart LR + subgraph mods [Enabled modalities] + S[SAXS] + H[HDX] + X[XL] + end + + S --> M1[stan_code] + H --> M2[stan_code_HDX] + X --> M3[stan_code_XL] + + S --> SH[stan_code_SAXS_HDX] + H --> SH + S --> SX[stan_code_SAXS_XL] + X --> SX + H --> HX[stan_code_HDX_XL] + X --> HX + S --> SHX[stan_code_SAXS_HDX_XL] + H --> SHX + X --> SHX +``` + +| SAXS | HDX | XL | Script / model | +|:----:|:---:|:--:|----------------| +| | ✓ | | `stan_code_HDX` | +| | | ✓ | `stan_code_XL` | +| ✓ | | | `stan_code` (SAXS-only) | +| ✓ | ✓ | | `stan_code_SAXS_HDX` | +| ✓ | | ✓ | `stan_code_SAXS_XL` | +| | ✓ | ✓ | `stan_code_HDX_XL` | +| ✓ | ✓ | ✓ | `stan_code_SAXS_HDX_XL` | + +--- + +## CLI vs web app + +```mermaid +flowchart TB + subgraph cli [Command line] + P1[prepareBayesian.py / prepareHDX.py / prepareXLMS.py] + P2[fullBayesianMultimodal.py] + P1 --> P2 + end + + subgraph web [Streamlit app.py] + U[Upload PDBs and data files] + BP[bioce_pipeline.run_full_pipeline] + U --> BP + end + + subgraph core [Shared core] + SO[structure_observables.py] + SM[stan_models.py] + SR[stan_run.build_and_sample] + SO --> SM --> SR + end + + P2 --> core + BP --> SO + BP --> SM +``` + +--- + +## HDX and XL at a glance + +```mermaid +flowchart LR + subgraph hdx [HDX-MS] + Dpred["D_pred,i(S_k)\nmean peptide SASA / 200"] + Dhat["D_hat_i = sum w_k D_pred"] + Y["y_i ~ Normal(D_hat, sigma)"] + Dpred --> Dhat --> Y + end + + subgraph xl [XL-MS] + dlk["d_l(S_k)\nCA distance"] + sat["s_l,k = sigmoid((d_max - d)/tau)"] + psat["p_sat = sum w_k s_l,k"] + Z["z_l ~ Bernoulli(p_sat)"] + dlk --> sat --> psat --> Z + end + + w["weights w"] --> Dhat + w --> psat +``` + +--- + +*Render these diagrams in GitHub, VS Code (Mermaid extension), or [mermaid.live](https://mermaid.live).*