diff --git a/.github/workflows/Documentation.yml b/.github/workflows/Documentation.yml new file mode 100644 index 00000000..3abfa993 --- /dev/null +++ b/.github/workflows/Documentation.yml @@ -0,0 +1,32 @@ +name: Documentation + +on: + push: + branches: + - develop + tags: '*' + pull_request: + +jobs: + build: + name: build on Julia ${{ matrix.julia-version }} + runs-on: ubuntu-latest + strategy: + matrix: + julia-version: ['1.0', '1.1', '1.2', '1.3', 'nightly'] + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@v1 + with: + version: ${{ matrix.julia-version }} + - name: Install dependencies + run: julia --project=docs -e 'using Pkg; Pkg.instantiate()' + - name: Build and deploy + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # For authentication with GitHub Actions token + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # For authentication with SSH deploy key + run: julia --project=docs/ docs/make.jl + - uses: actions/upload-artifact@v1 + with: + name: docs + path: docs/build diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml new file mode 100644 index 00000000..d77d3a0c --- /dev/null +++ b/.github/workflows/TagBot.yml @@ -0,0 +1,11 @@ +name: TagBot +on: + schedule: + - cron: 0 * * * * +jobs: + TagBot: + runs-on: ubuntu-latest + steps: + - uses: JuliaRegistries/TagBot@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..ffd5ecf5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI + +on: + push: + branches: + - develop + - master + tags: '*' + pull_request: + +jobs: + test: + name: Test on Julia ${{ matrix.julia-version }} - ${{ matrix.os }} - ${{ matrix.julia-arch }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + julia-version: ['1.0', '1.1', '1.2', '1.3', 'nightly'] + julia-arch: [x64, x86] + os: [ubuntu-latest, windows-latest, macOS-latest] + exclude: + - os: macOS-latest + julia-arch: x86 + steps: + - uses: actions/checkout@v1.0.0 + - uses: julia-actions/setup-julia@v1.0.1 + with: + version: ${{ matrix.julia-version }} + arch: ${{ matrix.julia-arch }} + - uses: julia-actions/julia-runtest@v0.1.0 + - uses: julia-actions/julia-uploadcodecov@v0.1.0 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + - uses: julia-actions/julia-uploadcoveralls@v0.1.0 + env: + COVERALLS_TOKEN: ${{ secrets.COVERALLS_TOKEN }} diff --git a/.github/workflows/compathelper.yml b/.github/workflows/compathelper.yml new file mode 100644 index 00000000..cd20a0d8 --- /dev/null +++ b/.github/workflows/compathelper.yml @@ -0,0 +1,19 @@ +name: CompatHelper + +on: + schedule: + - cron: '0 0 * * *' + +jobs: + CompatHelper: + runs-on: ubuntu-latest + steps: + - uses: julia-actions/setup-julia@v1 + with: + version: 1.3 + - name: Pkg.add("CompatHelper") + run: julia -e 'using Pkg; Pkg.add("CompatHelper")' + - name: CompatHelper.main() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: julia -e 'using CompatHelper; CompatHelper.main()' diff --git a/.travis.yml b/.travis.yml index a8e1d98f..64cbabe4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,19 +15,3 @@ notifications: on_start: never codecov: true coveralls: true -jobs: - include: - - &docs - stage: "Documentation" - julia: 1.0 - script: - - julia --project=docs/ -e 'using Pkg; Pkg.instantiate(); Pkg.develop(PackageSpec(path=pwd()))' - - julia --project=docs/ docs/make.jl - - <<: *docs - julia: 1.1 - - <<: *docs - julia: 1.2 - - <<: *docs - julia: 1.3 - - <<: *docs - julia: nightly diff --git a/Project.toml b/Project.toml index 85396f2c..a0a4b4a5 100644 --- a/Project.toml +++ b/Project.toml @@ -1,8 +1,9 @@ name = "ACME" uuid = "ca8b7239-ccd3-5cce-807f-2072f3f0d108" -version = "0.9.2" +version = "0.9.3" [deps] +Compat = "34da2185-b29b-5c13-b0c7-acf172513d20" IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" @@ -13,6 +14,7 @@ StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" [compat] +Compat = "3.7" IterTools = "1" OrderedCollections = "1" ProgressMeter = "0.6, 0.7, 0.8, 0.9, 1" diff --git a/README.md b/README.md index 38f352de..eaa9dc43 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # ACME.jl - Analog Circuit Modeling and Emulation for Julia [![Join the chat at https://gitter.im/HSU-ANT/ACME.jl](https://badges.gitter.im/HSU-ANT/ACME.jl.svg)](https://gitter.im/HSU-ANT/ACME.jl?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Documentation](https://img.shields.io/badge/docs-v0.9.2-blue.svg)](https://hsu-ant.github.io/ACME.jl/v0.9.2/) +[![Documentation](https://img.shields.io/badge/docs-v0.9.3-blue.svg)](https://hsu-ant.github.io/ACME.jl/v0.9.3/) [![DOI](https://zenodo.org/badge/48224425.svg)](https://zenodo.org/badge/latestdoi/48224425) ACME is a [Julia](http://julialang.org/) package for the simulation of @@ -18,7 +18,7 @@ Schematics"](http://www.eurasip.org/Proceedings/Eusipco/Eusipco2015/papers/15701 ## Installation If you have not done so already, [download and install -Julia](http://julialang.org/downloads/). (Any version starting with 0.6 should +Julia](http://julialang.org/downloads/). (Any version starting with 1.0 should be fine; earlier ACME versions support Julia starting with version 0.3.) To install ACME, start Julia and run: @@ -104,7 +104,7 @@ row per input (just one in the example) and one column per sample. So for a sinusoid at 1 kHz lasting one second, we do ```Julia -y = run!(model, sin(2π*1000/44100*(0:44099).')) +y = run!(model, [sin(2π*1000/44100*n) for c in 1:1, n in 0:44099]) ``` The output `y` now likewise is a matrix with one row for the one probe we have @@ -123,7 +123,7 @@ fail to run altogether. ## Moving on -There is some [documentation](https://hsu-ant.github.io/ACME.jl/v0.9.2/) +There is some [documentation](https://hsu-ant.github.io/ACME.jl/v0.9.3/) available for how to use ACME. Additionally, you can take a look at the examples that can be found in the `examples` directory below `Pkg.dir("ACME")`. diff --git a/docs/Manifest.toml b/docs/Manifest.toml index bfdcca24..00da65c6 100644 --- a/docs/Manifest.toml +++ b/docs/Manifest.toml @@ -1,35 +1,50 @@ +[[ACME]] +deps = ["Compat", "IterTools", "LinearAlgebra", "Markdown", "OrderedCollections", "ProgressMeter", "SparseArrays", "StaticArrays", "Statistics"] +path = ".." +uuid = "ca8b7239-ccd3-5cce-807f-2072f3f0d108" + [[Base64]] uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +[[Compat]] +deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"] +git-tree-sha1 = "2e23d71ad695ec28ca58ddd44869f07afa33cc76" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "3.7.0" + [[Dates]] deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" +[[DelimitedFiles]] +deps = ["Mmap"] +uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" + [[Distributed]] deps = ["LinearAlgebra", "Random", "Serialization", "Sockets"] uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" [[DocStringExtensions]] -git-tree-sha1 = "0513f1a8991e9d83255e0140aace0d0fc4486600" +deps = ["LibGit2", "Markdown", "Pkg", "Test"] +git-tree-sha1 = "88bb0edb352b16608036faadcc071adda068582a" uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" -version = "0.8.0" - - [DocStringExtensions.deps] - LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433" - Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" - Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" - Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +version = "0.8.1" [[Documenter]] -deps = ["Base64", "DocStringExtensions", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"] -git-tree-sha1 = "1b6ae3796f60311e39cd1770566140d2c056e87f" +deps = ["Base64", "Dates", "DocStringExtensions", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "REPL", "Test", "Unicode"] +git-tree-sha1 = "885467cebde4639a3d81953652cc53ff5a73cb87" uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -version = "0.23.3" +version = "0.24.3" [[InteractiveUtils]] deps = ["LinearAlgebra", "Markdown"] uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +[[IterTools]] +git-tree-sha1 = "05110a2ab1fc5f932622ffea2a003221f4782c18" +uuid = "c8e1da08-722c-5040-9ed9-7db0dc04731e" +version = "1.3.0" + [[JSON]] deps = ["Dates", "Mmap", "Parsers", "Unicode"] git-tree-sha1 = "b34d7cef7b337321e97d22242c3c2b91f476748e" @@ -56,11 +71,17 @@ uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" [[Mmap]] uuid = "a63ad114-7e13-5084-954f-fe012c677804" +[[OrderedCollections]] +deps = ["Random", "Serialization", "Test"] +git-tree-sha1 = "c4c13474d23c60d20a67b217f1d7f22a40edf8f1" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.1.0" + [[Parsers]] deps = ["Dates", "Test"] -git-tree-sha1 = "ef0af6c8601db18c282d092ccbd2f01f3f0cd70b" +git-tree-sha1 = "0139ba59ce9bc680e2925aec5b7db79065d60556" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "0.3.7" +version = "0.3.10" [[Pkg]] deps = ["Dates", "LibGit2", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"] @@ -70,6 +91,12 @@ uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" deps = ["Unicode"] uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" +[[ProgressMeter]] +deps = ["Distributed", "Printf"] +git-tree-sha1 = "ea1f4fa0ff5e8b771bf130d87af5b7ef400760bd" +uuid = "92933f4c-e287-5a05-a399-4b506db050ca" +version = "1.2.0" + [[REPL]] deps = ["InteractiveUtils", "Markdown", "Sockets"] uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" @@ -84,9 +111,27 @@ uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" [[Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" +[[SharedArrays]] +deps = ["Distributed", "Mmap", "Random", "Serialization"] +uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" + [[Sockets]] uuid = "6462fe0b-24de-5631-8697-dd941f90decc" +[[SparseArrays]] +deps = ["LinearAlgebra", "Random"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + +[[StaticArrays]] +deps = ["LinearAlgebra", "Random", "Statistics"] +git-tree-sha1 = "5a3bcb6233adabde68ebc97be66e95dcb787424c" +uuid = "90137ffa-7385-5640-81b9-e52037218182" +version = "0.12.1" + +[[Statistics]] +deps = ["LinearAlgebra", "SparseArrays"] +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + [[Test]] deps = ["Distributed", "InteractiveUtils", "Logging", "Random"] uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/docs/Project.toml b/docs/Project.toml index 1c87e8fc..029216c1 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,5 +1,6 @@ [deps] +ACME = "ca8b7239-ccd3-5cce-807f-2072f3f0d108" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" [compat] -Documenter = "=0.23.3" +Documenter = "=0.24.3" diff --git a/docs/src/gettingstarted.md b/docs/src/gettingstarted.md index 8fc198ef..0c6417d3 100644 --- a/docs/src/gettingstarted.md +++ b/docs/src/gettingstarted.md @@ -3,7 +3,7 @@ ## Installation If you have not done so already, [download and install -Julia](http://julialang.org/downloads/). (Any version starting with 0.6 should +Julia](http://julialang.org/downloads/). (Any version starting with 1.0 should be fine; earlier ACME versions also support Julia 0.3 and later.) To install ACME, start Julia and run: diff --git a/src/ACME.jl b/src/ACME.jl index 112e25b9..cb701d89 100644 --- a/src/ACME.jl +++ b/src/ACME.jl @@ -1,10 +1,11 @@ -# Copyright 2015, 2016, 2017, 2018, 2019 Martin Holters +# Copyright 2015, 2016, 2017, 2018, 2019, 2020 Martin Holters # See accompanying license file. module ACME export DiscreteModel, run!, steadystate, steadystate!, linearize, ModelRunner +using Compat: evalpoly using SparseArrays: SparseMatrixCSC, blockdiag, dropzeros!, findnz, nonzeros, sparse, spzeros using LinearAlgebra: BLAS, I, axpy!, lu, rmul! diff --git a/src/elements.jl b/src/elements.jl index df9b42a4..597fbded 100644 --- a/src/elements.jl +++ b/src/elements.jl @@ -1,4 +1,4 @@ -# Copyright 2015, 2016, 2017, 2018, 2019 Martin Holters +# Copyright 2015, 2016, 2017, 2018, 2019, 2020 Martin Holters # See accompanying license file. export resistor, potentiometer, capacitor, inductor, transformer, @@ -394,23 +394,31 @@ Pins: `base`, `emitter`, `collector` end @doc doc""" - mosfet(typ; vt=0.7, α=2e-5) + mosfet(typ; vt=0.7, α=2e-5, λ=0) Creates a MOSFET transistor with the simple model $i_D=\begin{cases} 0 & \text{if } v_{GS} \le v_T \\ \alpha \cdot (v_{GS} - v_T - \tfrac{1}{2}v_{DS})\cdot v_{DS} + \cdot (1 + \lambda v_{DS}) & \text{if } v_{DS} \le v_{GS} - v_T \cap v_{GS} > v_T \\ - \frac{\alpha}{2} \cdot (v_{GS} - v_T)^2 & \text{otherwise.} + \frac{\alpha}{2} \cdot (v_{GS} - v_T)^2 \cdot (1 + \lambda v_{DS}) + & \text{otherwise.} \end{cases}$ The `typ` parameter chooses between NMOS (`:n`) and PMOS (`:p`). The threshold -voltage `vt` is given in Volt, `α` (in A/V²) in a constant depending on the -physics and dimensions of the device. +voltage `vt` is given in Volt, `α` (in A/V²) is a constant depending on the +physics and dimensions of the device, and `λ` (in V⁻¹) controls the channel +length modulation. + +Optionally, it is possible to specify tuples of coefficients for `vt` and `α`. +These will be used as polynomials in $v_{GS}$ to determine $v_T$ and $\alpha$, +respectively. E.g. with `vt=(0.7, 0.1, 0.02)`, the $v_{GS}$-dpendent threshold +voltage $v_T = 0.7 + 0.1\cdot v_{GS} + 0.02\cdot v_{GS}^2$ will be used. Pins: `gate`, `source`, `drain` -""" function mosfet(typ; vt=0.7, α=2e-5) +""" function mosfet(typ; vt=0.7, α=2e-5, λ=0) if typ == :n polarity = 1 elseif typ == :p @@ -418,25 +426,43 @@ Pins: `gate`, `source`, `drain` else throw(ArgumentError("Unknown mosfet type $(typ), must be :n or :p")) end - return Element(mv=[-1 0; 0 -1; 0 0; 0 0], - mi=[0 0; 0 0; 0 -1; 1 0], - mq=polarity*[1 0 0; 0 1 0; 0 0 1; 0 0 0], - u0=polarity*[-vt; 0; 0; 0], - ports=[:gate => :source, :drain => :source], - nonlinear_eq = @inline function (q) - vg, vds, id=q # vg = vgs-vt - if vg <= 0 - res = @SVector [-id] - J = @SMatrix [0.0 0.0 -1.0] - elseif vds <= vg - res = @SVector [α * (vg-0.5*vds)*vds - id] - J = @SMatrix [α*vds α*(vg-vds) -1.0] - else # 0 < vg < vds - res = @SVector [(α/2) * vg^2 - id] - J = @SMatrix [α*vg 0.0 -1.0] - end - return (res, J) - end) + vt = (vt...,) + α = (α...,) + dvt = vt[2:end] .* (1:length(vt)-1...,) + dα = α[2:end] .* (1:length(α)-1...,) + let polarity = polarity, α = α, vt = vt + return Element(mv=[-1 0; 0 -1; 0 0; 0 0], + mi=[0 0; 0 0; 0 -1; 1 0], + mq=polarity*[1 0 0; 0 1 0; 0 0 1; 0 0 0], + ports=[:gate => :source, :drain => :source], + nonlinear_eq = @inline function (q) + vgs, vds, id = q + α´ = evalpoly(polarity*vgs, α) + if !isempty(dα) + dα´_dvgs = evalpoly(polarity*vgs, dα) + else + dα´_dvgs = 0 + end + vt´ = evalpoly(polarity*vgs, vt) + if !isempty(dvt) + dvt´_dvgs = evalpoly(polarity*vgs, dvt) + else + dvt´_dvgs = 0 + end + λ´ = vds ≥ 0 ? λ : zero(λ) + if vgs <= vt´ + res = @SVector [-id] + J = @SMatrix [0.0 0.0 -1.0] + elseif vds <= vgs - vt´ # && vgs > vt´ + res = @SVector [α´ * (vgs-vt´-0.5*vds)*vds*(1+λ´*vds) - id] + J = @SMatrix [α´*(1-dvt´_dvgs)*vds*(1+λ´*vds) + dα´_dvgs * (vgs-vt´-0.5*vds)*vds*(1+λ´*vds) α´*(vgs-vt´+vds*(2*λ´*(vgs-vt´-0.75*vds)-1)) -1.0] + else # 0 < vgs - vt´ < vds + res = @SVector [(α´/2) * (vgs-vt´)^2*(1+λ´*vds) - id] + J = @SMatrix [α´*(vgs-vt´)*(1-dvt´_dvgs)*(1+λ´*vds) + dα´_dvgs/2 * (vgs-vt´)^2*(1+λ´*vds) λ´*α´/2*(vgs-vt´)^2 -1.0] + end + return (res, J) + end) + end end @doc doc""" diff --git a/test/runtests.jl b/test/runtests.jl index 1c269ad4..b5748896 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,9 +1,10 @@ -# Copyright 2015, 2016, 2017, 2018, 2019 Martin Holters +# Copyright 2015, 2016, 2017, 2018, 2019, 2020 Martin Holters # See accompanying license file. include("checklic.jl") using ACME +using Compat: evalpoly, only using Test: @test, @test_broken, @test_logs, @test_throws, @testset using FFTW: rfft using ProgressMeter @@ -518,6 +519,28 @@ end y = run!(model, pol*[0 1 2 2 2; 5 5 0.5 1 1.5]) @test y == pol*[0 0 1e-4*(1-0.5/2)*0.5 1e-4*(1-1/2)*1 1e-4/2*1^2] end + for (typ, pol) in ((:n, 1), (:p, -1)), α in (1e-4, (0.0205, -0.0017)), + vt in (1, (1.2078, 0.3238), (-1.2454, -0.199, -0.0483)) + circ = @circuit begin + vgs = voltagesource(), [-] == gnd + vds = voltagesource(), [-] == gnd + J = mosfet(typ, vt=vt, α=α, λ=0.05), [gate] == vgs[+], [drain] == vds[+] + out = currentprobe(), [+] == J[source], [-] == gnd + end + model = DiscreteModel(circ, 1); + for vgs in range(0, stop=5, length=10), vds in range(0, stop=5, length=10) + y = only(run!(model, pol*hcat([vgs; vds]))) + α´ = evalpoly(pol*vgs, (α...,)) + vt´ = evalpoly(pol*vgs, (vt...,)) + if vgs ≤ vt´ + @test y == 0 + elseif vds ≤ vgs - vt´ + @test y ≈ pol * α´ * (vgs - vt´ - vds / 2) * vds * (1 + 0.05 * vds) + else + @test y ≈ pol * α´ / 2 * (vgs - vt´)^2 * (1 + 0.05 * vds) + end + end + end end @testset "op amp" begin