Converting Models to Computation Graphs

using Pkg Pkg.status() Pkg.activate(".")

Pkg.instantiate()

Pkg.develop(path="../../") Pkg.instantiate()

using AlgebraicPetri
using AlgebraicPetri.Epidemiology
using AlgebraicPetri.BilayerNetworks

using Catlab
using Catlab.CategoricalAlgebra
using Catlab.Graphics
import Catlab.CategoricalAlgebra: migrate!
using Catlab.WiringDiagrams
using Catlab.Programs.RelationalPrograms
display_uwd(ex) = to_graphviz(ex, box_labels=:name, junction_labels=:variable, edge_attrs=Dict(:len=>".75"));

using PrettyTables
function printsoln(bn::AbstractLabelledBilayerNetwork, soln::Vector)
    pretty_table(soln)
end
printsoln (generic function with 1 method)

SIR Model:

define model

sir = @relation (s,i,r) begin
    infection(s,i)
    recovery(i,r)
end
display_uwd(sir)

Extract the Petri network form of the model.

psir = apex(oapply_epi(sir))
psir
to_graphviz(psir)

Convert the Petri network into a bilayer network and draw it. This model uses a computation graph to express the computation of the vector field of the Petri net.

bnsir = BilayerNetwork()
migrate!(bnsir, psir)
bnsir
to_graphviz(bnsir)

We can hand code a Bilayer netowork using the @acset macro provided by Catlab. As you can see from the code, there is a lot of typing to specify the incidence of all these wires. The Petri Net is more compact. This notion of Bilayer network comes from the definition of mass action kinetics for reaction networks.

hand coded Bilayer network

bnsir_test = @acset BilayerNetwork begin
    Qin = 3
    Qout = 3
    Win = 3
    Wa = 3
    Wn = 3
    Box = 2
    arg = [1,2,2]
    call = [1,1,2]
    efflux = [1,1,2]
    effusion = [1,2,2]
    influx = [1,1,2]
    infusion = [2,2,3]
end

@assert bnsir == bnsir_test

function roundtrip(pn::AbstractPetriNet, bn::AbstractBilayerNetwork)
    roundtrippetri = PetriNet()
    migrate!(roundtrippetri, bn)
    pn_structure = PetriNet()
    copy_parts!(pn_structure, pn)
    return roundtrippetri, pn_structure
end
roundtrip (generic function with 1 method)

We can specify the SEIR model with the relation macro and functorial semantics as usual.

seir = @relation (s,e,i,r) begin
    exposure(s,i,e)
    illness(e,i)
    recovery(i,r)
end

pseir = apex(oapply_epi(seir))
LabelledPetriNet {T:3, S:4, I:4, O:4, Name:0}
T tname
1 exp
2 ill
3 rec
S sname
1 S
2 I
3 E
4 R
I it is
1 1 1
2 1 2
3 2 3
4 3 2
O ot os
1 1 3
2 1 2
3 2 2
4 3 4

By converting this to a Bilayer network, we are able to visualize differences in the computational pattern of data flow between different reaction network models.

bnseir = BilayerNetwork()
migrate!(bnseir, pseir)

bnrt,pnstr = roundtrip(pseir, bnseir)

display_uwd(sir)

to_graphviz(psir)
to_graphviz(bnsir)
to_graphviz(bnseir)
to_graphviz(bnrt)
qm = @relation (s,q) begin
    exposure(s,i,e)
    illness(e,i)
    recovery(i,r)
    quarexp(e,q)
    quarinf(i,q)
end
display_uwd(qm)
import AlgebraicPetri.Epidemiology: exposure_petri, spontaneous_petri
semantics = Dict(
    :infection => exposure_petri(:S, :I, :I, :inf),
    :exposure  => exposure_petri(:S, :I, :E, :exp),
    :illness   => spontaneous_petri(:E,:I,:ill),
    :recovery  => spontaneous_petri(:I,:R,:rec),
    :death     => spontaneous_petri(:I,:D,:death),
    :quarexp   => spontaneous_petri(:E, :Q, :qe),
    :quarinf   => spontaneous_petri(:I, :Q, :qi),
    :quarrec   => spontaneous_petri(:Q, :R, :qr)
)
pn_quar = oapply(qm, semantics)  |> apex
to_graphviz(pn_quar)
bnquar = LabelledBilayerNetwork()
migrate!(bnquar, pn_quar)
to_graphviz(bnquar)

qm = @relation (s,i,q) begin
    exposure(s,i,e)
    illness(e,i)
    recovery(i,r)
    quarexp(e,q)
    quarinf(i,q)
    quarrec(q,r)
end
display_uwd(qm)
pn_quar = oapply(qm, semantics)  |> apex
to_graphviz(pn_quar)
Example block output
bnquar = LabelledBilayerNetwork()
migrate!(bnquar, pn_quar)
to_graphviz(bnquar)
Example block output
quarrt = LabelledPetriNet()
migrate!(quarrt, bnquar) |> to_graphviz

bnquar
AlgebraicPetri.BilayerNetworks.LabelledBilayerNetwork {Qin:5, Qout:5, Win:7, Wn:7, Wa:7, Box:6, Name:0}
Qin variable
1 S
2 I
3 E
4 R
5 Q
Qout tanvar
1 S
2 I
3 E
4 R
5 Q
Win arg call
1 1 1
2 2 1
3 3 2
4 2 3
5 3 4
6 2 5
7 5 6
Wn efflux effusion
1 1 1
2 1 2
3 2 3
4 3 2
5 4 3
6 5 2
7 6 5
Wa influx infusion
1 1 3
2 1 2
3 2 2
4 3 4
5 4 5
6 5 5
7 6 4
Box parameter
1 exp
2 ill
3 rec
4 qe
5 qi
6 qr
import AlgebraicPetri.BilayerNetworks: evaluate!, evaluate

bnsir = LabelledBilayerNetwork()
migrate!(bnsir, psir)

evaluate(bnsir, [10.0, 1, 0.0], inf=0.1, rec=0.3)

evaluate(bnquar, [10.0, 1, 0, 0,0,0], exp=0.1, rec=0.3, qi=0.2, ill=0.7, qe=0.23, qr=0.3)

function euler(bn::AbstractLabelledBilayerNetwork, state, nsteps::Integer, stepsize::Real; params...)
    #preallocate storage so that each step is nonallocating
    #create a storage space for steps of euler's method store intermediate
    #states as named tuples so that you can integrate with julia Tables.jl data  analysis ecosystem
    du = zeros(length(state))
    ϕ = ones(nparts(bn, :Box))
    u = tuple(state...)
    results = Vector{NamedTuple{tuple(bn[:,:variable]...)}}()
    for i in 1:nsteps
        u = u .+ stepsize.*evaluate!(du, ϕ, bn, u; params...)
        push!(results, NamedTuple{tuple(bn[:,:variable]...)}(u))
    end
    return results
end

soln = euler(bnquar, (S=10.0, I=1, E=0, R=0, Q=0), 30, 0.15, exp=0.1, rec=0.03, qi=0.37, ill=0.7, qe=0.23, qr=0.03)
printsoln(bnquar, soln)
┌─────────┬──────────┬──────────┬────────────┬──────────┐
│       S │        I │        E │          R │        Q │
├─────────┼──────────┼──────────┼────────────┼──────────┤
│    9.85 │     0.94 │     0.15 │     0.0045 │   0.0555 │
│ 9.71111 │  0.89935 │  0.26796 │ 0.00897975 │ 0.112595 │
│ 9.58011 │ 0.873525 │ 0.361585 │  0.0135335 │ 0.171247 │
│ 9.45458 │  0.85908 │ 0.436671 │   0.018235 │ 0.231432 │
│ 9.33275 │ 0.853385 │ 0.497589 │  0.0231423 │ 0.293134 │
│ 9.21328 │ 0.854429 │ 0.547642 │  0.0283016 │ 0.356345 │
│  9.0952 │ 0.860666 │ 0.589327 │  0.0337501 │ 0.421056 │
│ 8.97778 │ 0.870905 │ 0.624535 │  0.0395178 │  0.48726 │
│  8.8605 │ 0.884227 │ 0.654694 │  0.0456296 │ 0.554949 │
│ 8.74298 │ 0.899916 │ 0.680885 │  0.0521059 │ 0.624113 │
│ 8.62496 │ 0.917414 │ 0.703921 │   0.058964 │ 0.694741 │
│ 8.50627 │ 0.936281 │ 0.724414 │  0.0662187 │ 0.766816 │
│ 8.38681 │ 0.956168 │ 0.742822 │  0.0738826 │ 0.840321 │
│ 8.26652 │ 0.976794 │ 0.759486 │  0.0819668 │ 0.915234 │
│  8.1454 │ 0.997932 │ 0.774658 │   0.090481 │  0.99153 │
│ 8.02347 │   1.0194 │ 0.788522 │  0.0994336 │  1.06918 │
│ 7.90078 │  1.04103 │ 0.801209 │   0.108832 │  1.14815 │
│ 7.77741 │  1.06269 │ 0.812814 │   0.118683 │   1.2284 │
│ 7.65344 │  1.08428 │ 0.823402 │   0.128993 │  1.30989 │
│ 7.52896 │  1.10568 │ 0.833014 │   0.139767 │  1.39258 │
│ 7.40409 │   1.1268 │ 0.841677 │   0.151009 │  1.47642 │
│ 7.27895 │  1.14757 │ 0.849407 │   0.162724 │  1.56135 │
│ 7.15365 │   1.1679 │ 0.856211 │   0.174914 │  1.64732 │
│ 7.02833 │  1.18773 │ 0.862092 │   0.187582 │  1.73427 │
│ 6.90311 │  1.20699 │ 0.867046 │   0.200731 │  1.82212 │
│ 6.77813 │  1.22561 │ 0.871073 │   0.214362 │  1.91083 │
│ 6.65352 │  1.24353 │ 0.874168 │   0.228476 │   2.0003 │
│ 6.52941 │  1.26071 │  0.87633 │   0.243074 │  2.09047 │
│ 6.40594 │  1.27708 │ 0.877557 │   0.258154 │  2.18127 │
│ 6.28322 │   1.2926 │ 0.877852 │   0.273717 │  2.27261 │
└─────────┴──────────┴──────────┴────────────┴──────────┘
import AlgebraicPetri.BilayerNetworks: compile
compile(bnquar, :du, :ϕ, :u, :p)
:(f!(du, ϕ, u, p, t) = begin
          #= /home/runner/work/AlgebraicPetri.jl/AlgebraicPetri.jl/src/BilayerNetworks.jl:281 =#
          begin
              #= /home/runner/work/AlgebraicPetri.jl/AlgebraicPetri.jl/src/BilayerNetworks.jl:287 =#
              du .= 0.0
              #= /home/runner/work/AlgebraicPetri.jl/AlgebraicPetri.jl/src/BilayerNetworks.jl:288 =#
              ϕ .= 1.0
              ϕ[1] *= u[1]
              ϕ[1] *= u[2]
              ϕ[2] *= u[3]
              ϕ[3] *= u[2]
              ϕ[4] *= u[3]
              ϕ[5] *= u[2]
              ϕ[6] *= u[5]
              ϕ[1] *= p[:exp]
              ϕ[2] *= p[:ill]
              ϕ[3] *= p[:rec]
              ϕ[4] *= p[:qe]
              ϕ[5] *= p[:qi]
              ϕ[6] *= p[:qr]
              du[1] -= ϕ[1]
              du[2] -= ϕ[1]
              du[3] -= ϕ[2]
              du[2] -= ϕ[3]
              du[3] -= ϕ[4]
              du[2] -= ϕ[5]
              du[5] -= ϕ[6]
              du[3] += ϕ[1]
              du[2] += ϕ[1]
              du[2] += ϕ[2]
              du[4] += ϕ[3]
              du[5] += ϕ[4]
              du[5] += ϕ[5]
              du[4] += ϕ[6]
              return du
          end
      end)
compile(bnquar, :du, :ϕ, :u, exp=0.1, rec=0.03, qi=0.37, ill=0.7, qe=0.23, qr=0.03)
:(f!(du, ϕ, u, t) = begin
          #= /home/runner/work/AlgebraicPetri.jl/AlgebraicPetri.jl/src/BilayerNetworks.jl:282 =#
          begin
              #= /home/runner/work/AlgebraicPetri.jl/AlgebraicPetri.jl/src/BilayerNetworks.jl:287 =#
              du .= 0.0
              #= /home/runner/work/AlgebraicPetri.jl/AlgebraicPetri.jl/src/BilayerNetworks.jl:288 =#
              ϕ .= 1.0
              ϕ[1] *= u[1]
              ϕ[1] *= u[2]
              ϕ[2] *= u[3]
              ϕ[3] *= u[2]
              ϕ[4] *= u[3]
              ϕ[5] *= u[2]
              ϕ[6] *= u[5]
              ϕ[1] *= 0.1
              ϕ[2] *= 0.7
              ϕ[3] *= 0.03
              ϕ[4] *= 0.23
              ϕ[5] *= 0.37
              ϕ[6] *= 0.03
              du[1] -= ϕ[1]
              du[2] -= ϕ[1]
              du[3] -= ϕ[2]
              du[2] -= ϕ[3]
              du[3] -= ϕ[4]
              du[2] -= ϕ[5]
              du[5] -= ϕ[6]
              du[3] += ϕ[1]
              du[2] += ϕ[1]
              du[2] += ϕ[2]
              du[4] += ϕ[3]
              du[5] += ϕ[4]
              du[5] += ϕ[5]
              du[4] += ϕ[6]
              return du
          end
      end)
function eulers(bn::AbstractLabelledBilayerNetwork, funcname::Symbol; params...)
    f = compile(bn, :du, :ϕ, :u; params...)
    varnames = tuple(bn[:,:variable]...)
    nϕ = nparts(bn, :Box)
    quote
    function $funcname(state, nsteps::Integer, stepsize::Real)
        $f
        #preallocate storage so that each step is nonallocating
        du = zeros(length(state))
        ϕ = ones($nϕ)
        u = tuple(state...)
        #create a storage space for steps of euler's method
        #store intermediate states as named tuples so that you can integrate
        #with julia Tables.jl data analysis ecosystem
        results = Vector{NamedTuple{$varnames}}()
        for i in 1:nsteps
            Δ = f!(du, ϕ, u, 0)
            u = u .+ stepsize.*Δ
            push!(results, NamedTuple{$varnames}(u))
        end
        return results
    end
    end
end

eulers(bnsir, :eulsir, inf=0.3, rec=0.2)
quote
    #= bilayerconversion.md:243 =#
    function eulsir(state, nsteps::Integer, stepsize::Real)
        #= bilayerconversion.md:243 =#
        #= bilayerconversion.md:244 =#
        f!(du, ϕ, u, t) = begin
                #= /home/runner/work/AlgebraicPetri.jl/AlgebraicPetri.jl/src/BilayerNetworks.jl:282 =#
                begin
                    #= /home/runner/work/AlgebraicPetri.jl/AlgebraicPetri.jl/src/BilayerNetworks.jl:287 =#
                    du .= 0.0
                    #= /home/runner/work/AlgebraicPetri.jl/AlgebraicPetri.jl/src/BilayerNetworks.jl:288 =#
                    ϕ .= 1.0
                    ϕ[1] *= u[1]
                    ϕ[1] *= u[2]
                    ϕ[2] *= u[2]
                    ϕ[1] *= 0.3
                    ϕ[2] *= 0.2
                    du[1] -= ϕ[1]
                    du[2] -= ϕ[1]
                    du[2] -= ϕ[2]
                    du[2] += ϕ[1]
                    du[2] += ϕ[1]
                    du[3] += ϕ[2]
                    return du
                end
            end
        #= bilayerconversion.md:246 =#
        du = zeros(length(state))
        #= bilayerconversion.md:247 =#
        ϕ = ones(2)
        #= bilayerconversion.md:248 =#
        u = tuple(state...)
        #= bilayerconversion.md:252 =#
        results = Vector{NamedTuple{(:S, :I, :R)}}()
        #= bilayerconversion.md:253 =#
        for i = 1:nsteps
            #= bilayerconversion.md:254 =#
            Δ = f!(du, ϕ, u, 0)
            #= bilayerconversion.md:255 =#
            u = u .+ stepsize .* Δ
            #= bilayerconversion.md:256 =#
            push!(results, NamedTuple{(:S, :I, :R)}(u))
            #= bilayerconversion.md:257 =#
        end
        #= bilayerconversion.md:258 =#
        return results
    end
end
eulseirqexp = eulers(bnquar, :eulseirq, exp=0.1, rec=0.03, qi=0.37, ill=0.7, qe=0.23, qr=0.03)
quote
    #= bilayerconversion.md:243 =#
    function eulseirq(state, nsteps::Integer, stepsize::Real)
        #= bilayerconversion.md:243 =#
        #= bilayerconversion.md:244 =#
        f!(du, ϕ, u, t) = begin
                #= /home/runner/work/AlgebraicPetri.jl/AlgebraicPetri.jl/src/BilayerNetworks.jl:282 =#
                begin
                    #= /home/runner/work/AlgebraicPetri.jl/AlgebraicPetri.jl/src/BilayerNetworks.jl:287 =#
                    du .= 0.0
                    #= /home/runner/work/AlgebraicPetri.jl/AlgebraicPetri.jl/src/BilayerNetworks.jl:288 =#
                    ϕ .= 1.0
                    ϕ[1] *= u[1]
                    ϕ[1] *= u[2]
                    ϕ[2] *= u[3]
                    ϕ[3] *= u[2]
                    ϕ[4] *= u[3]
                    ϕ[5] *= u[2]
                    ϕ[6] *= u[5]
                    ϕ[1] *= 0.1
                    ϕ[2] *= 0.7
                    ϕ[3] *= 0.03
                    ϕ[4] *= 0.23
                    ϕ[5] *= 0.37
                    ϕ[6] *= 0.03
                    du[1] -= ϕ[1]
                    du[2] -= ϕ[1]
                    du[3] -= ϕ[2]
                    du[2] -= ϕ[3]
                    du[3] -= ϕ[4]
                    du[2] -= ϕ[5]
                    du[5] -= ϕ[6]
                    du[3] += ϕ[1]
                    du[2] += ϕ[1]
                    du[2] += ϕ[2]
                    du[4] += ϕ[3]
                    du[5] += ϕ[4]
                    du[5] += ϕ[5]
                    du[4] += ϕ[6]
                    return du
                end
            end
        #= bilayerconversion.md:246 =#
        du = zeros(length(state))
        #= bilayerconversion.md:247 =#
        ϕ = ones(6)
        #= bilayerconversion.md:248 =#
        u = tuple(state...)
        #= bilayerconversion.md:252 =#
        results = Vector{NamedTuple{(:S, :I, :E, :R, :Q)}}()
        #= bilayerconversion.md:253 =#
        for i = 1:nsteps
            #= bilayerconversion.md:254 =#
            Δ = f!(du, ϕ, u, 0)
            #= bilayerconversion.md:255 =#
            u = u .+ stepsize .* Δ
            #= bilayerconversion.md:256 =#
            push!(results, NamedTuple{(:S, :I, :E, :R, :Q)}(u))
            #= bilayerconversion.md:257 =#
        end
        #= bilayerconversion.md:258 =#
        return results
    end
end
eulsirexp = eulers(bnsir, :eulsir, inf=0.3, rec=0.2)
quote
    #= bilayerconversion.md:243 =#
    function eulsir(state, nsteps::Integer, stepsize::Real)
        #= bilayerconversion.md:243 =#
        #= bilayerconversion.md:244 =#
        f!(du, ϕ, u, t) = begin
                #= /home/runner/work/AlgebraicPetri.jl/AlgebraicPetri.jl/src/BilayerNetworks.jl:282 =#
                begin
                    #= /home/runner/work/AlgebraicPetri.jl/AlgebraicPetri.jl/src/BilayerNetworks.jl:287 =#
                    du .= 0.0
                    #= /home/runner/work/AlgebraicPetri.jl/AlgebraicPetri.jl/src/BilayerNetworks.jl:288 =#
                    ϕ .= 1.0
                    ϕ[1] *= u[1]
                    ϕ[1] *= u[2]
                    ϕ[2] *= u[2]
                    ϕ[1] *= 0.3
                    ϕ[2] *= 0.2
                    du[1] -= ϕ[1]
                    du[2] -= ϕ[1]
                    du[2] -= ϕ[2]
                    du[2] += ϕ[1]
                    du[2] += ϕ[1]
                    du[3] += ϕ[2]
                    return du
                end
            end
        #= bilayerconversion.md:246 =#
        du = zeros(length(state))
        #= bilayerconversion.md:247 =#
        ϕ = ones(2)
        #= bilayerconversion.md:248 =#
        u = tuple(state...)
        #= bilayerconversion.md:252 =#
        results = Vector{NamedTuple{(:S, :I, :R)}}()
        #= bilayerconversion.md:253 =#
        for i = 1:nsteps
            #= bilayerconversion.md:254 =#
            Δ = f!(du, ϕ, u, 0)
            #= bilayerconversion.md:255 =#
            u = u .+ stepsize .* Δ
            #= bilayerconversion.md:256 =#
            push!(results, NamedTuple{(:S, :I, :R)}(u))
            #= bilayerconversion.md:257 =#
        end
        #= bilayerconversion.md:258 =#
        return results
    end
end
eval(eulsirexp)
soln_codegen = eulsir((S=10.0, I=1, R=0), 30, 0.15)
pretty_table(soln_codegen)
┌─────────────┬─────────┬──────────┐
│           S │       I │        R │
├─────────────┼─────────┼──────────┤
│        9.55 │    1.42 │     0.03 │
│     8.93976 │ 1.98764 │   0.0726 │
│     8.14015 │ 2.72762 │ 0.132229 │
│       7.141 │ 3.64494 │ 0.214058 │
│     5.96972 │ 4.70688 │ 0.323406 │
│     4.70527 │ 5.83011 │ 0.464613 │
│     3.47082 │ 6.88966 │ 0.639516 │
│     2.39475 │ 7.75905 │ 0.846206 │
│      1.5586 │ 8.36242 │  1.07898 │
│    0.972087 │ 8.69806 │  1.32985 │
│      0.5916 │ 8.81761 │  1.59079 │
│    0.356858 │ 8.78782 │  1.85532 │
│    0.215737 │ 8.66531 │  2.11895 │
│    0.131613 │ 8.48947 │  2.37891 │
│   0.0813334 │ 8.28507 │   2.6336 │
│     0.05101 │ 8.06684 │  2.88215 │
│    0.032493 │ 7.84335 │  3.12416 │
│   0.0210246 │ 7.61952 │  3.35946 │
│   0.0138157 │ 7.39814 │  3.58804 │
│  0.00921622 │  7.1808 │  3.80999 │
│  0.00623813 │ 6.96835 │  4.02541 │
│    0.004282 │ 6.76126 │  4.23446 │
│  0.00297918 │ 6.55972 │   4.4373 │
│  0.00209976 │ 6.36381 │  4.63409 │
│  0.00149845 │  6.1735 │    4.825 │
│  0.00108217 │ 5.98871 │  5.01021 │
│ 0.000790533 │ 5.80934 │  5.18987 │
│ 0.000583872 │ 5.63527 │  5.36415 │
│ 0.000435809 │ 5.46636 │  5.53321 │
│ 0.000328606 │ 5.30247 │   5.6972 │
└─────────────┴─────────┴──────────┘
eval(eulseirqexp)
soln_codegen = eulseirq((S=10.0, I=1, E=0, R=0, Q=0), 30, 0.15)
pretty_table(soln_codegen)
┌─────────┬──────────┬──────────┬────────────┬──────────┐
│       S │        I │        E │          R │        Q │
├─────────┼──────────┼──────────┼────────────┼──────────┤
│    9.85 │     0.94 │     0.15 │     0.0045 │   0.0555 │
│ 9.71111 │  0.89935 │  0.26796 │ 0.00897975 │ 0.112595 │
│ 9.58011 │ 0.873525 │ 0.361585 │  0.0135335 │ 0.171247 │
│ 9.45458 │  0.85908 │ 0.436671 │   0.018235 │ 0.231432 │
│ 9.33275 │ 0.853385 │ 0.497589 │  0.0231423 │ 0.293134 │
│ 9.21328 │ 0.854429 │ 0.547642 │  0.0283016 │ 0.356345 │
│  9.0952 │ 0.860666 │ 0.589327 │  0.0337501 │ 0.421056 │
│ 8.97778 │ 0.870905 │ 0.624535 │  0.0395178 │  0.48726 │
│  8.8605 │ 0.884227 │ 0.654694 │  0.0456296 │ 0.554949 │
│ 8.74298 │ 0.899916 │ 0.680885 │  0.0521059 │ 0.624113 │
│ 8.62496 │ 0.917414 │ 0.703921 │   0.058964 │ 0.694741 │
│ 8.50627 │ 0.936281 │ 0.724414 │  0.0662187 │ 0.766816 │
│ 8.38681 │ 0.956168 │ 0.742822 │  0.0738826 │ 0.840321 │
│ 8.26652 │ 0.976794 │ 0.759486 │  0.0819668 │ 0.915234 │
│  8.1454 │ 0.997932 │ 0.774658 │   0.090481 │  0.99153 │
│ 8.02347 │   1.0194 │ 0.788522 │  0.0994336 │  1.06918 │
│ 7.90078 │  1.04103 │ 0.801209 │   0.108832 │  1.14815 │
│ 7.77741 │  1.06269 │ 0.812814 │   0.118683 │   1.2284 │
│ 7.65344 │  1.08428 │ 0.823402 │   0.128993 │  1.30989 │
│ 7.52896 │  1.10568 │ 0.833014 │   0.139767 │  1.39258 │
│ 7.40409 │   1.1268 │ 0.841677 │   0.151009 │  1.47642 │
│ 7.27895 │  1.14757 │ 0.849407 │   0.162724 │  1.56135 │
│ 7.15365 │   1.1679 │ 0.856211 │   0.174914 │  1.64732 │
│ 7.02833 │  1.18773 │ 0.862092 │   0.187582 │  1.73427 │
│ 6.90311 │  1.20699 │ 0.867046 │   0.200731 │  1.82212 │
│ 6.77813 │  1.22561 │ 0.871073 │   0.214362 │  1.91083 │
│ 6.65352 │  1.24353 │ 0.874168 │   0.228476 │   2.0003 │
│ 6.52941 │  1.26071 │  0.87633 │   0.243074 │  2.09047 │
│ 6.40594 │  1.27708 │ 0.877557 │   0.258154 │  2.18127 │
│ 6.28322 │   1.2926 │ 0.877852 │   0.273717 │  2.27261 │
└─────────┴──────────┴──────────┴────────────┴──────────┘