https://github.com/jfozard/hei10_zyp1
Revision d3d884a236e33e87868cb5926b43c3b6c3630756 authored by foz on 28 April 2022, 10:14:36 UTC, committed by foz on 28 April 2022, 10:14:36 UTC
1 parent 02fb9c1
Raw File
Tip revision: d3d884a236e33e87868cb5926b43c3b6c3630756 authored by foz on 28 April 2022, 10:14:36 UTC
Small bug fixes
Tip revision: d3d884a
zyp_output.jl

using ArgParse
using DifferentialEquations
using DiffEqCallbacks
using LinearAlgebra
using Random
using SparseArrays
using StatsBase
using NLopt
using StatsPlots
using FiniteDiff
using Plots
using Distributions
using GLM


# Piecewise linear functions

# Piecewise linear interpolation at multiple positions (specialized for Float64)
function interp1d(x::Array{Float64,1}, x0::Array{Float64,1}, y0::Array{Float64,1})
    # x - position to evaluate function
    # x0 - positions of discontinuities in function
    # y0 - values of function on each region: y = y0[i] for x0[i] < x < x0[i+1]


    y = zeros(Float64, size(x))
    jj = 1
    for ii in 1:length(y)
        if x0[1]<=x[ii]<x0[end]
            jj = findnext(val->val>x[ii],x0,jj)-1
            y[ii] = y0[jj] + (y0[jj+1]-y0[jj])/(x0[jj+1]-x0[jj])*(x[ii]-x0[jj])
        end
    end
    return y
end

# Piecewise linear interpolation at a single position
function interp1d(x::Number, x0::Array, y0::Array)
    # x - position to evaluate function
    # x0 - positions of discontinuities in function slope
    # y0 - values of function at each discontinuity. Function takes the value 0 outside the range [x0[1], x0[length(x0)]]

    jj = 1
    y = 0.0
    if x0[1]<=x<x0[end]
        jj = findnext(val->val>x,x0,jj)-1
        y = y0[jj] + (y0[jj+1]-y0[jj])/(x0[jj+1]-x0[jj])*(x-x0[jj])
    end
    return y
end


# Piecewise constant interpolation
function piecewise(x::Number, x0::Array, y0::Array)
    # x - position to evaluate function
    # x0 - positions of discontinuities in function
    # y0 - values of function on each region: y = y0[i] for x0[i] < x < x0[i+1]

    jj = 1
    y = 0.0
    if x0[1]<=x<x0[end]
        jj = findnext(val->val>x,x0,jj)-1
        y = y0[jj]
    end
    return y
end


# Inverse of a piecewise constant density function
function piecewise_density_inv(x0::Array{T,1}, y0::Array{T,1}) where {T <: Number}
    csum = zeros(T, size(x0))
    for i in 2:length(x0)
        csum[i] = (x0[i] - x0[i-1])*y0[i-1] + csum[i-1]
    end
    csum = csum / csum[end]
    csum, x0
end


# Inverse of a piecewise linear density function
function linear_density_inverse(x0::Array{T,1}, y0::Array{T,1}) where {T <: Number}
    csum = zeros(T, size(x0))
    for	i in 2:length(x0)
    	csum[i] = (x0[i] - x0[i-1])*(y0[i-1] + y0[i])/2	+ csum[i-1]
    end
    csum_t = csum[end]
    function inverse(x::T)
        z = x*csum_t
        jj = findnext(val->val>z, csum, 1)-1
        z = z - csum[jj]
        y = y0[jj]
        dy = (y0[jj+1] - y0[jj])/(x0[jj+1]-x0[jj])
        if abs(dy)>1e-12
            return x0[jj]+((sqrt(y*y+2*z*dy)-y)/dy)
        elseif abs(y)>1e-12
            return x0[jj]+z/y
        else
            return x0[jj]
        end
    end
    inverse
end

# RI HEI10 escape rate function (\beta(C)*C in equation (2) )
function betaC(C, K, n_c)
    C ./ (1 .+ (C / K) .^ n_c)
end

# RI HEI10 escape rate function \beta(C)*C (evaluated in place)
function betaC!(dC, C, K, n_c)
    @. dC = C ./ (1 .+ (C ./ K) .^ n_c)
end

# Derivative of (\beta(C)*C) wrt C
function d_betaC(C::Float64, K::Float64, n_c::Float64)::Float64
    1 ./ (1.0 .+ (C ./ K) .^ n_c) .-
    C ./ (1.0 .+ (C ./ K) .^ n_c) .^ 2 .* (n_c ./ K) .* (C ./ K) .^ (n_c - 1)
end

# Derivative of (\beta(C)*C) wrt C (evaluated in place)
function d_betaC!(dbC, C, K, n_c)
    @. dbC = 1 ./ (1.0 .+ (C ./ K) .^ n_c) .-
    C ./ (1.0 .+ (C ./ K) .^ n_c) .^ 2 .* (n_c ./ K) .* (C ./ K) .^ (n_c - 1)
end

### Distributions of chromosome lengths

Lm = Vector{Float64}([37.5, 40.4, 45.7, 53.8, 59.8])
Ls = Vector{Float64}([4.93, 5.06, 5.86, 7.18, 7.13])


function basic_ode!(dyy::Array{Float64,1}, yy::Array{Float64,1}, p, t::Float64)
    N_array,           # Number of RIs (array)
    Q,           # Number of SCs
    K,           # Hill threshold
    a_nodes, # Rate of HEI10 absorption from pool to nodes
    betaC!,      # beta(C)*C function (passed to ODE for efficiency)
    d_betaC,     # derivative of beta(C)*C function wrt C
    gamma,         # Hill coefficient (\gamma)
    beta_2,    # RI HEI10 escape rate into pool (C-dependent)
    beta_3,    # RI HEI10 escape rate into pool (const) - This is zero in all the simulations in the paper
    bC  = p        # Placeholder to re-use vector

    @inbounds pool = yy[end]

    @inbounds dyy[end] = 0

    offset = 0
    for q = 1:Q

        @inbounds N = N_array[q]

        # Extract variables from ODE State vector

        @inbounds C = @view yy[offset+1:offset+N] # HEI10 amounts at the RH (C_j 1<=j<=N)
        @inbounds dC = @view dyy[offset+1:offset+N]

        # Calculate escape of HEI10 into nucleoplasm

        bbC = @view bC[1:N]
        betaC!(bbC, max.(C,0.0), K, gamma)

        # Rate of change of HEI10 amounts at the RIs
        @. dC = - beta_2 .* bC[1:N] .- beta_3 .* C + a_nodes*pool

        # Rate of change of HEI10 in nuceloplasmic pool

        @inbounds dyy[end] = dyy[end] -  a_nodes*pool*N + beta_2 *sum(bC[1:N]) + beta_3 *sum(C)
        offset += (N)
    end
end


function jac!(J, yy, p, t)
    N_array,           # Number of RIs (array)
    Q,           # Number of SCs
    K,           # Hill threshold
    a_nodes,     # From pool onto RI
    betaC!,      # beta(C)*C function (passed to ODE for efficiency)
    d_betaC,     # derivative of beta(C)*C function wrt C
    gamma,         # Hill coefficient (\gamma)
    beta_2,    # RI HEI10 escape rate into pool (C-dependent)
    beta_3,    # RI HEI10 escape rate into pool (const)
    bC = p        # Placeholder to re-use vector

    J[end, end] = 0
    offset = 0

    for q in 1:Q
        N = N_array[q]
        C = @view yy[offset+1:offset+N]

        dbC::Float64 = 0.0
        idx::Int64 = 0
        @inbounds for j = 1:N
            dbC = d_betaC(max(0, C[j]), K, gamma)
            J[offset+j, offset+j] = -(beta_2) * dbC - beta_3
            J[offset+j, end] = a_nodes
            J[end, offset+j] = beta_2*dbC + beta_3
        end
        J[end, end] = J[end, end] - N*a_nodes
        offset += N
    end
end


function simulate_multi(idx, args, Lm, Ls)

    # Initialize random number generator with seed for this simulation

    L_array = Vector{Float64}()
    x_array = Vector{Vector{Float64}}()
    N_array = Vector{Int64}()

    y0 = Vector{Float64}()

    Q = size(Lm, 1)

    M_start = 0.0
    mt = MersenneTwister(idx)

    # Multiplicative noise in the overall level of HEI10 (unused)
    ratio_hei10 = exp(args["sd_hei10"]*randn(mt))

    use_poisson = Bool(args["use_poisson"])

    for q = 1:Q

        # Generate SC length, sampled from Normal distribution clipped to +/- 3 S.D.
        L::Float64 = Float64(Lm[q])+Float64(Ls[q])*clamp(randn(mt), -3, 3)
        push!(L_array, L)

	N::Int64 = 0
        # Calculate appropriate number of RIs for this SC length
	if use_poisson
	    N = rand(mt, Poisson(L*args["density"]))
	else
            N = floor(L*args["density"])
        end
        push!(N_array, N)



        x::Array{Float64,1} = L * sort(rand(mt, N))
        push!(x_array, x)

        # Initialize truncated normal distribution for RI HEI10 loading
        # Mean C0_noise (C_0)
        # Standard deviation C0_noise (\sigma)
        # Truncated at +/- 3 s.d.
        d = Truncated(Normal(Float64(args["C0"]), Float64(args["C0_noise"])), max(0, Float64(args["C0"])-3*Float64(args["C0_noise"])), Float64(args["C0"])+3*Float64(args["C0_noise"]))


        # Generate initial RI HEI10 amounts
        C0::Array{Float64,1} = rand(mt, d, N)
        C0[C0.<0] .= 0.0 # Double-check that initial RI HEI10 concentrations are non-negative

        C0_t = 1.0
        C0_t2::Float64 = Float64(args["t_C0_ratio2"]) # ($f_e$)
        t_L::Float64 = Float64(args["t_L"])


        for i in 1:N
    	    p = x[i]/L
            if p < t_L
               r = p/t_L
               C0[i] *= C0_t*(r) + C0_t2*(1-r)
            elseif p> 1-t_L
               r = (1-p)/t_L
               C0[i] *= C0_t*(r) + C0_t2*(1-r)
       	     end
	    end

        C0 *= ratio_hei10

        append!(y0, C0)
        M_start += sum(C0)
    end

    gamma::Float64 = Float64(args["gamma"]) # Hill coefficient (gamma)
    K::Float64 = Float64(args["K"]) # Hill threshold (K_C)

    a_nodes::Float64 = Float64(args["a_nodes"])
    beta_2 = Float64(args["beta_2"]) # RI escape rate (loss to nuceloplasm) - not used here
    beta_3 = Float64(args["beta_3"]) # RI constant escape rate (loss to nuceloplasm) - not used here
    bC = zeros(Float64, sum(N_array)) # Buffer to store values of \beta(C)*C in ODE calculation avoiding allocating memory

    pool0 = Float64(args["pool0"])*ratio_hei10

    M_start += pool0

    # Construct initial ODE state vector
    push!(y0, pool0)

    # ODE system parameters
    p = (
        N_array,
        Q,
        K,
        a_nodes,
        betaC!,
        d_betaC,
        gamma,
        beta_2,
        beta_3,
        bC,
    )

    # Initialize ODE problem with jacobian

    @show(y0)

    yy0 = zero(y0)
    basic_ode!(yy0, y0, p, 0.0)

    @show(yy0)

    # Initialize ODE problem with jacobian
    j = spzeros(sum(N_array) + 1, sum(N_array) + 1)
    g2 = ODEFunction(basic_ode!; jac=jac!, jac_prototype = j)
    prob_g = ODEProblem(g2, y0, (0.0, Float64(args["n_ts"]) * Float64(args["dt"])), p)
    cb = PositiveDomain(g2) # Additional constraint to ensure non-negativity of concentration values during integration

    # Solve ODE system using implicit Rodas5 solver, with tight relative error tolerance
    sol = solve(prob_g,Rodas5(autodiff=:false), cb=cb, save_everystep=false)

    offset = 0
    u_array = Vector{Vector{Float64}}()
    u0_array = Vector{Vector{Float64}}()

    u_end = sol.u[end]
    for q in 1:Q
        z = u_end[offset+1:offset+N_array[q]]
        push!(u_array, z)

        z = y0[offset+1:offset+N_array[q]]
        push!(u0_array, z)

        offset += N_array[q]
    end

    (L_array, x_array, u_array, u0_array)
end


function hist_hard(x::Vector{Float64}, n::Int64)
    h = zeros(Float64, n)
    for p in x
        h[min(max(1, Int(ceil(p * n))), n)] += 1
    end
    h
end


function get_counts(d, low, high)
    counts(d, low:high)
end

struct SimResult
    x_array::Vector{Vector{Float64}}
    u_array::Vector{Vector{Float64}}
    L_array::Vector{Float64}
    n_seed::Int
end

function simulate_all(args, io)

    nb = 9
    nh = 20

    Q = Int64(args["Q"])

    prefix = args["prefix"]

    hist_nn_t = zeros(Float64, Threads.nthreads(), Q, nh, nb)

    start::Int64 = Int64(args["start"])
    n::Int64 = Int64(args["n"])
    @show(n)
    n_args = copy(args)

    solution_output = Vector{Vector{SimResult}}()
    for i in 1:Threads.nthreads()
        push!(solution_output, Vector{SimResult}())
    end

    Threads.@threads for i in start:start+(n-1)
        L_array, x_array, u_array, u0_array = simulate_multi(i, n_args, Lm[1:Q], Ls[1:Q])
        s = SimResult(x_array, u_array, L_array, i)
        push!(solution_output[Threads.threadid()], s)
    end

    solution_output = vcat(solution_output...)

    for i in 1:length(solution_output)
        x_array = solution_output[i].x_array
        u_array = solution_output[i].u_array
        L_array = solution_output[i].L_array

        print(io, join(map(string, L_array), ',') * '\n')
        for q in 1:Q
            print(io, join(map(string, x_array[q]), ',') * '\n')
            print(io, join(map(string, u_array[q]), ',') * '\n')
        end
    end


end


function parse_commandline()

# Parse command line options and parameters

    s = ArgParseSettings()

    @add_arg_table! s begin
        "--filename_base"
            default="test"
            help="base filename"

        "--start"
            arg_type=Int
            default=1
            help="first seed to use for the simulation"

        "--n"
            arg_type=Int
            default=100 # (No unit)
            help="number of seeds" # Number of different SCs to simulate, using a different RNG seed for each one

        "--n_ts"
            arg_type=Int
            default=100
            help="number of timesteps"

        "--dt"
     	    arg_type=Float64
            default=360.0 # (s)
            help="time interval" # Length of each integration period (timestep is shorter, controlled automatically by error estimate)

        "--K"
     	    arg_type=Float64
            default=1.0 # (a.u.)
            help="threshold for RI unbinding" # (K_C) Hill function thresold for escape of HEI10 from RI

        "--gamma"
    	    arg_type=Float64
    	    default=1.25 # (no units)
            help="Hill coefficient for RI unbinding" # (\gamma) Hill coefficient for escape of HEI10 from RI

        "--pool0"
            arg_type=Float64
            default=280.0 # (a.u.)
            help="initial pool HEI10 concentration" # (c_0) HEI10 initial loading on SC

        "--C0"
            arg_type=Float64
            default=6.8 # (a.u.)
            help="initial RI HEI10" # (C_0) HEI10 initial RI loading

        "--C0_noise"
            arg_type=Float64
            default=2.2 # (a.u.)
            help="initial RI HEI10 noise" # (\sigma) Noise in HEI10 initial RI loading

        "--t_C0_ratio2"
            arg_type=Float64
            default=1.25 # (no units)
            help="initial hei10 ratio for RIs in telomeres"  # (f_e) Increased RI loading at SC telomeres

        "--t_L"
            arg_type=Float64
            default=0.3 # (no units)
            help="telomere rel length" # (x_e) Telomere length as fraction of bivalent

        "--density"
            arg_type=Float64
            default=0.5         # (um^{-1})
            help="RI density"  # (\rho) SC RI density

        "--a_nodes"
            arg_type=Float64   # (s^{-1})
            default=0.17       # Rate of HEI10 absorption from pool to RIs

         "--beta_2"
            arg_type=Float64   # (s^{-1})
            default=0.04        # RI HEI10 escape rate into pool (C-dependent)

        "--beta_3"
            arg_type=Float64   # (s^{-1})
            default=0.0        # RI HEI10 escape rate into pool (const) - This is zero in all the simulations in the paper

        "--Q"
            arg_type=Int64  
            default=5          # Number of chromosomes

        "--sd_hei10"
            arg_type=Float64   # (a.u.)
            default=0.0        # Variation in total HEI10 amounts

        "--use_poisson"
            arg_type=Bool      # Whether to distribute RIs at a constant rate per uniform length
            default=true      # (rather than a number proportional to chromosome length)
    end
    return parse_args(s)
end


function plot_obj(filename, p; n=100)
    args = parse_commandline()
    println("Parsed args:")

    # Update simulation parameters from function call parameters
    n_args = copy(args)
    n_args["n"] = n
    n_args["C0"] = p[1]
    n_args["C0_noise"] = p[2]
    n_args["pool0"] = p[3]
    n_args["t_C0_ratio2"] = p[4]
    n_args["t_L"] = p[5]
    n_args["n_c"] = p[6]
    n_args["a_nodes"] = p[7]
    n_args["beta_2"] = p[8]
    n_args["sd_hei10"] = p[9]
    n_args["use_poisson"] = p[10]
    n_args["prefix"] = p[11]
    for (arg,val) in n_args
        println("$arg=$val")
    end
    println("filename ", filename)
    open(filename,"w") do io
        # Write parameter values as first line of output file
        println(io, "#Namespace(" * join(map(x-> string(x[1]) *"="*string(x[2]), collect(n_args)), ",") * ")")

        # Run (group of) simulations
        simulate_all(n_args, io)
    end
    println("done")
end



@show("done load ends")

total_SC_L = sum(Lm)

@show(total_SC_L)


# Simulations with / without poisson RI initialization
plot_obj("out-var-0.0.dat", [6.8, 2.2, 280, 1.25, 0.3, 1.25, 0.17, 0.04, 0.0, false, "var-0.0-"]; n=10000)
plot_obj("out-poisson-0.0.dat",[6.8, 2.2, 280, 1.25, 0.3, 1.25, 0.17, 0.04, 0.0, true, "poisson-0.0-"]; n=10000)

back to top