├── REQUIRE ├── .gitignore ├── .github └── workflows │ └── TagBot.yml ├── .travis.yml ├── src ├── cheb.jl ├── lin.jl ├── CompEcon.jl ├── spli.jl ├── compat.jl └── core.jl ├── test ├── runtests.jl └── types.jl ├── LICENSE.md ├── demo ├── demapp00.jl ├── dierckxcompare_2d.jl ├── dierckxcompare_1d.jl └── demapp01.jl └── README.md /REQUIRE: -------------------------------------------------------------------------------- 1 | julia 0.7 2 2 | QuantEcon 3 | BasisMatrices 0.3 4 | Reexport 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.jl.cov 2 | *.jl.mem 3 | .ipynb_checkpoints/ 4 | *tags* 5 | *.cov 6 | CompEcon2014/ 7 | -------------------------------------------------------------------------------- /.github/workflows/TagBot.yml: -------------------------------------------------------------------------------- 1 | name: TagBot 2 | on: 3 | schedule: 4 | - cron: 0 * * * * 5 | jobs: 6 | TagBot: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: JuliaRegistries/TagBot@v1 10 | with: 11 | token: ${{ secrets.GITHUB_TOKEN }} 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: julia 2 | julia: 3 | - 1.0 4 | - nightly 5 | matrix: 6 | allow_failures: 7 | - julia: nightly 8 | notifications: 9 | email: false 10 | after_success: 11 | - julia -e 'cd(Pkg.dir("CompEcon")); Pkg.add("Coverage"); using Coverage; proc = process_folder(); Coveralls.submit(proc); Codecov.submit(proc)' 12 | -------------------------------------------------------------------------------- /src/cheb.jl: -------------------------------------------------------------------------------- 1 | # ------------ # 2 | # Original API # 3 | # ------------ # 4 | 5 | # chebdef.m -- DONE 6 | function chebdef(n::Int, a::Real, b::Real) 7 | p = ChebParams(n, a, b) 8 | return (p.n, p.a, p.b, Any[p.n, p.a, p.b], p) 9 | end 10 | 11 | 12 | # chebnode.m -- DONE 13 | chebnode(n::Int, a::Real, b::Real, nodetype=0) = 14 | nodes(ChebParams(n, a, b), nodetype) 15 | 16 | # chebdop.m -- DONE 17 | function chebdop(n, a, b, order=1) 18 | D, p = derivative_op(ChebParams(n, a, b), order) 19 | D, p.n-order, p.a, p.b, Any[p.n-order, p.a, p.b] 20 | end 21 | 22 | 23 | # chebbase.m -- DONE 24 | function chebbase(n, a, b, x=chebnode(n, a, b, 1), order=0, nodetype=1) 25 | B = evalbase(ChebParams(n, a, b), x, order, nodetype) 26 | B, x 27 | end 28 | 29 | # chebbasex.m -- DONE 30 | chebbasex(n, a, b, x) = evalbasex(ChebParams(n, a, b), x) 31 | -------------------------------------------------------------------------------- /src/lin.jl: -------------------------------------------------------------------------------- 1 | # ------------ # 2 | # Original API # 3 | # ------------ # 4 | 5 | # lindef.m -- DONE 6 | function lindef(breaks::AbstractVector, evennum::Int=0) 7 | p = LinParams(breaks, evennum) 8 | return ( 9 | length(p.breaks), 10 | minimum(p.breaks), 11 | maximum(p.breaks), 12 | Any[p.breaks, evennum], 13 | p 14 | ) 15 | end 16 | 17 | # linnode.m -- DONE 18 | linnode(breaks, evennum) = nodes(LinParams(breaks, evennum)) 19 | 20 | # lindop.m 21 | function lindop(breaks, evennum, order) 22 | D, params = derivative_op(LinParams(breaks, evennum), order) 23 | n, a, b = length(params.breaks), params.breaks[1], params.breaks[end] 24 | D, n, a, b, params 25 | end 26 | 27 | # linbase.m -- DONE 28 | function linbase(breaks, evennum=0, x=breaks, order=0) 29 | B = evalbase(LinParams(breaks, evennum), x, order) 30 | B, x 31 | end 32 | -------------------------------------------------------------------------------- /src/CompEcon.jl: -------------------------------------------------------------------------------- 1 | module CompEcon 2 | 3 | using Reexport 4 | 5 | using QuantEcon: gridmake, gridmake!, ckron, fix, fix!, qnwlege, qnwcheb, 6 | qnwsimp, qnwtrap, qnwbeta, qnwgamma, qnwequi, qnwnorm, 7 | qnwunif, qnwlogn, quadrect, do_quad 8 | 9 | @reexport using BasisMatrices 10 | 11 | import BasisMatrices: funeval, funfitxy, funfitf 12 | 13 | # old API only 14 | export fundef, fundefn, funnode, funbase, funbasex, funeval, funbconv, 15 |   chebdef, chebnode, chebbase, chebbasex, chebdop, 16 | splidef, splinode, splibase, splibasex, splidop, 17 | lindef, linnode, linbase, lindop 18 | 19 | # quad names 20 | export qnwlege, qnwcheb, qnwsimp, qnwtrap, qnwbeta, qnwgamma, qnwequi, qnwnorm, 21 | qnwunif, qnwlogn, quadrect, do_quad 22 | 23 | include("core.jl") 24 | include("cheb.jl") 25 | include("spli.jl") 26 | include("lin.jl") 27 | include("compat.jl") 28 | 29 | end # module 30 | -------------------------------------------------------------------------------- /src/spli.jl: -------------------------------------------------------------------------------- 1 | # ------------ # 2 | # Original API # 3 | # ------------ # 4 | 5 | # splidef.m -- DONE 6 | function splidef(breaks, evennum=0, k::Int=3) 7 | p = SplineParams(breaks, evennum, k) 8 | return ( 9 | length(p.breaks), 10 | minimum(p.breaks), 11 | maximum(p.breaks), 12 | Any[p.breaks, 0, k], 13 | p 14 | ) 15 | end 16 | 17 | # splinode.m -- DONE 18 | splinode(breaks::AbstractVector, evennum::Int, k::Int=3) = 19 | nodes(SplineParams(breaks, evennum, k)) 20 | 21 | function splidop(breaks, evennum=0, k=3, order=1) 22 | D, p = derivative_op(SplineParams(breaks, evennum, k), order) 23 | n = length(breaks) + k - 1 24 | 25 | D, n-order, breaks[1], breaks[end], Any[breaks, evennum, k-order] 26 | end 27 | 28 | # splibas.m -- DONE 29 | function splibase(breaks::AbstractVector, evennum, k=3, x=splinode(breaks, evennum, k), 30 | order=0) 31 | B = evalbase(SplineParams(breaks, evennum, k), x, order) 32 | B, x 33 | end 34 | -------------------------------------------------------------------------------- /test/runtests.jl: -------------------------------------------------------------------------------- 1 | module CompEconTests 2 | 3 | using CompEcon 4 | 5 | using Test 6 | 7 | include("types.jl") 8 | 9 | # correctness is checked in BasisMatrices. Here we just check that all 10 | # functions run without error 11 | b1 = fundef((:spli, range(0, stop = 1, length = 10), 0, 3)) 12 | b2 = fundef((:lin, range(0, stop = 1, length = 10), 0)) 13 | b3 = fundef((:cheb, 10, 0.0, 3.0)) 14 | b4 = fundefn(:spli, [10, 10], [-2.0, -1.0], [1.0, 2.0], 3) 15 | b5 = fundefn(:lin, [10, 10], [-2.0, -1.0], [1.0, 2.0]) 16 | b6 = fundefn(:cheb, [10, 10], [-2.0, -1.0], [1.0, 2.0]) 17 | f1(X) = exp.(X) 18 | f2(X) = sin.(X[:, 1] + X[:, 2]) 19 | 20 | i = 0 21 | for b in (b1, b2, b3, b4, b5, b6) 22 | global i 23 | @show i += 1 24 | X, x = funnode(b) 25 | funbase(b) 26 | funbasex(b) 27 | 28 | f = b[:d] == 1 ? f1 : f2 29 | funfitf(b, f) 30 | y = f(X) 31 | c, B = funfitxy(b, X, y) 32 | funeval(c, b, x) 33 | funeval(c, b, X) 34 | funeval(c, b, B) 35 | funbconv(B, B[:order], :expanded) 36 | end 37 | 38 | end # module 39 | -------------------------------------------------------------------------------- /test/types.jl: -------------------------------------------------------------------------------- 1 | sp = SplineParams(range(0, stop = 5, length = 10), 0, 3) 2 | cp = ChebParams(6, 2.0, 5.0) 3 | lp = LinParams(range(0, stop = 5, length = 10), 0) 4 | 5 | @testset "Test original API compat" begin 6 | 7 | s = Spline() 8 | c = Cheb() 9 | l = Lin() 10 | 11 | @testset "old_name for basis family" begin 12 | @test CompEcon.old_name(s) == :spli 13 | @test CompEcon.old_name(c) == :cheb 14 | @test CompEcon.old_name(l) == :lin 15 | end 16 | 17 | @testset "old_name for params" begin 18 | @test CompEcon.old_name(sp) == :spli 19 | @test CompEcon.old_name(cp) == :cheb 20 | @test CompEcon.old_name(lp) == :lin 21 | end 22 | 23 | @testset "old_params" begin 24 | @test CompEcon.old_params(sp) == Any[sp.breaks, sp.evennum, sp.k] 25 | @test CompEcon.old_params(cp) == Any[cp.n, cp.a, cp.b] 26 | @test CompEcon.old_params(lp) == Any[lp.breaks, lp.evennum] 27 | end 28 | 29 | @testset "convert(Basis, Dict) and revert(Basis) --> Dict" begin 30 | b = Basis(sp, cp) 31 | d = CompEcon.revert(b) 32 | @test convert(Basis, d) == b 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2015, 2016 Spencer Lyon & QuantEcon: BSD-3 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 25 | OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 26 | AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 28 | WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /demo/demapp00.jl: -------------------------------------------------------------------------------- 1 | import CompEcon 2 | using PyPlot 3 | using PyCall 4 | 5 | function main() 6 | f1(x) = exp(-2x) 7 | g1(x) = -2exp(-2x) 8 | 9 | function univariate_interp(f, g) 10 | # fit approximant 11 | n = 10; a = -1; b = 1 12 | basis = CompEcon.fundefn(:cheb, n, a, b) 13 | c = CompEcon.funfitf(basis, f1) 14 | 15 | # graph approximation error for function and derivative 16 | x = CompEcon.nodeunif(1001, a, b)[1] 17 | yact = f(x) 18 | dact = g(x) 19 | yfit = CompEcon.funeval(c, basis, x)[1] 20 | dfit = CompEcon.funeval(c, basis, x, 1)[1] 21 | 22 | 23 | # Nice plot of function approximation error 24 | fig, ax = plt.subplots() 25 | ax[:plot](x, yfit-yact) 26 | ax[:plot](x, 0*x, "k--", linewidth=2) 27 | ax[:set_xlabel]("x") 28 | ax[:set_ylabel]("Error") 29 | ax[:set_title](L"Error approximating function using Chebyshev") 30 | 31 | # Nice plot of derivative approximation error 32 | fig, ax = plt.subplots() 33 | ax[:plot](x, dfit-dact); 34 | ax[:plot](x, 0*x, "k--", linewidth=2) 35 | ax[:set_xlabel]("x") 36 | ax[:set_ylabel]("Error") 37 | ax[:set_title](L"Error approximating derivative using Chebyshev") 38 | end 39 | 40 | 41 | f2(x) = cos(x[:, 1]) ./ exp(x[:, 2]) 42 | function bivaraite_interp(f) 43 | n = [7 7] 44 | a = [0 0] 45 | b = [1 1] 46 | basis = CompEcon.fundefn(:cheb, n, a, b) 47 | c = CompEcon.funfitf(basis, f2) 48 | 49 | fig = plt.figure() 50 | nplot = [101 101] 51 | x, xcoord = CompEcon.nodeunif(nplot, a, b) 52 | yfit = CompEcon.funeval(c, basis, x)[1] 53 | error = reshape(yfit - f2(x), nplot...) 54 | poly3d = surf(xcoord[1], xcoord[2], error) 55 | ax = poly3d[:get_axes]() 56 | ax[:set_title]("Chebyshev approximation error") 57 | 58 | end 59 | end 60 | 61 | main() 62 | -------------------------------------------------------------------------------- /src/compat.jl: -------------------------------------------------------------------------------- 1 | # Stuff We need to maintain compatibility with the matlab api 2 | 3 | ## BasisFamily stuff 4 | 5 | # needed for the `convert(::Basis, ::Dict)` method below 6 | Base.convert(::Type{BasisFamily}, s::Symbol) = 7 | s == :spli ? Spline() : 8 | s == :cheb ? Cheb() : 9 | s == :lin ? Lin() : 10 | error("Unknown basis type") 11 | 12 | old_name(::Lin) = :lin 13 | old_name(::Cheb) = :cheb 14 | old_name(::Spline) = :spli 15 | 16 | ## BasisParams stuff 17 | 18 | # needed for the `convert(::Basis, ::Dict)` method below 19 | Base.convert(::Type{BasisParams}, p::Vector{Any}) = 20 | length(p) == 3 && isa(p[1], Number) ? ChebParams(p...) : 21 | length(p) == 3 ? SplineParams(p...) : 22 | length(p) == 2 ? LinParams(p...) : 23 | error("Unknown parameter type") 24 | 25 | old_name(::LinParams) = :lin 26 | old_name(::ChebParams) = :cheb 27 | old_name(::SplineParams) = :spli 28 | 29 | old_params(p::LinParams) = Any[p.breaks, p.evennum] 30 | old_params(p::ChebParams) = Any[p.n, p.a, p.b] 31 | old_params(p::SplineParams) = Any[p.breaks, p.evennum, p.k] 32 | 33 | ## Basis stuff 34 | 35 | # convert old API to new API 36 | function Base.convert(::Type{Basis}, b::Dict{Symbol, Any}) 37 | btype = map(x->convert(BasisFamily, x), b[:basetype]) 38 | param = BasisParams[convert(BasisParams, x) for x in b[:params]] 39 | Basis(param...) 40 | end 41 | 42 | # convert new API to old API 43 | function revert(b::Basis) 44 | B = Dict{Symbol, Any}() 45 | B[:d] = ndims(b) 46 | the_nodes = nodes.(b.params) 47 | B[:n] = [length(vec) for vec in the_nodes] 48 | B[:a] = [minimum(vec) for vec in the_nodes] 49 | B[:b] = [maximum(vec) for vec in the_nodes] 50 | B[:basetype] = Symbol[old_name(bt) for bt in b.params] 51 | B[:params] = Any[old_params(p) for p in b.params] 52 | B 53 | end 54 | 55 | # add method to funbasex that creates a BasisStructure 56 | function funbasex(basis::Basis, x=nodes(basis)[1], order=0, 57 | bformat::BasisMatrices.ABSR=Direct()) 58 | BasisMatrix(Nothing, basis, bformat, x, order) 59 | end 60 | 61 | funbase(basis::Basis, x=nodes(basis)[1], order=fill(0, 1, ndims(basis))) = 62 | funbasex(basis, x, order, Expanded()).vals[1] 63 | -------------------------------------------------------------------------------- /demo/dierckxcompare_2d.jl: -------------------------------------------------------------------------------- 1 | using CompEcon 2 | using Dierckx 3 | 4 | 5 | # Create functions to test against 6 | f1(x, y) = sin(x).*cos(y) 7 | f2(x, y) = exp(x) .* cos(y) 8 | f3(x, y) = 1.5 + log(x .* y) 9 | fs = [f1, f2, f3] 10 | nf = length(fs) 11 | 12 | x = collect(range(.5, stop=2, length=35)) 13 | y = collect(range(.2, stop=4, length=35)) 14 | 15 | function compare_D_CE_levels(f::Function, x::Array{Float64, 1}, y::Array{Float64, 1}, order::Int) 16 | 17 | # Create Dierckx Spline (and data) 18 | n = length(x) 19 | z = f(x, y') 20 | dspl = Spline2D(x, y, z; s=0., kx=order, ky=order) 21 | 22 | # Create CompEcon Spline (and data) 23 | cebasis_x = Basis(SplineParams(x, 0, order)) 24 | cebasis_y = Basis(SplineParams(y, 0, order)) 25 | cebasis = Basis(cebasis_x, cebasis_y) 26 | xx = nodes(cebasis)[1] 27 | yy = f(xx[:, 1], xx[:, 2]) 28 | c, bs = funfitxy(cebasis, xx, yy) 29 | 30 | # Evaluate Splines on finer grid 31 | xfine = collect(range(x[1], stop=x[end], length=2*n + 1)) 32 | yfine = collect(range(y[1], stop=y[end], length=2*n + 1)) 33 | zfine = f(xfine, yfine') 34 | 35 | deval = Dierckx.evalgrid(dspl, xfine, yfine) 36 | derr = abs(zfine - deval) 37 | 38 | ceeval = CompEcon.funeval(c, cebasis, gridmake(xfine, yfine)) 39 | ceerr = abs(zfine - reshape(ceeval, 2*n+1, 2*n+1)) 40 | 41 | return derr, ceerr 42 | end 43 | 44 | 45 | for i=1:nf 46 | 47 | curr_f = fs[i] 48 | 49 | derr1_levels, ceerr1_levels = compare_D_CE_levels(curr_f, x, y, 1) 50 | derr2_levels, ceerr2_levels = compare_D_CE_levels(curr_f, x, y, 2) 51 | derr3_levels, ceerr3_levels = compare_D_CE_levels(curr_f, x, y, 3) 52 | 53 | println("Linear Approximation to Function") 54 | println("Dierckx Level Errors \n\t Max: $(maximum(derr1_levels)) \n\t Min: $(minimum(derr1_levels)) \n\t Mean: $(mean(derr1_levels))") 55 | println("CompEcon Level Errors \n\t Max: $(maximum(ceerr1_levels)) \n\t Min: $(minimum(ceerr1_levels)) \n\t Mean: $(mean(ceerr1_levels))") 56 | 57 | println("Quadratic Approximation to Function") 58 | println("Dierckx Level Errors \n\t Max: $(maximum(derr2_levels)) \n\t Min: $(minimum(derr2_levels)) \n\t Mean: $(mean(derr2_levels))") 59 | println("CompEcon Level Errors \n\t Max: $(maximum(ceerr2_levels)) \n\t Min: $(minimum(ceerr2_levels)) \n\t Mean: $(mean(ceerr2_levels))") 60 | 61 | println("Cubic Approximation to Function") 62 | println("Dierckx Level Errors \n\t Max: $(maximum(derr3_levels)) \n\t Min: $(minimum(derr3_levels)) \n\t Mean: $(mean(derr3_levels))") 63 | println("CompEcon Level Errors \n\t Max: $(maximum(ceerr3_levels)) \n\t Min: $(minimum(ceerr3_levels)) \n\t Mean: $(mean(ceerr3_levels))") 64 | 65 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/QuantEcon/CompEcon.jl.svg?branch=master)](https://travis-ci.org/QuantEcon/CompEcon.jl) [![codecov.io](http://codecov.io/github/QuantEcon/CompEcon.jl/coverage.svg?branch=master)](http://codecov.io/github/QuantEcon/CompEcon.jl?branch=master) [![Coverage Status](https://coveralls.io/repos/QuantEcon/CompEcon.jl/badge.svg?branch=master&service=github)](https://coveralls.io/github/QuantEcon/CompEcon.jl?branch=master) 2 | 3 | This package is a Julia implementation of the routines originally contained in the [CompEcon Matlab toolbox](http://www4.ncsu.edu/~pfackler/compecon/toolbox.html) by Paul Fackler and Mario Miranda. The original Matlab code was written to accompany the publication 4 | 5 | > Miranda, Mario J., and Paul L. Fackler. Applied computational economics and finance. MIT press, 2004. 6 | 7 | This work is derivative of their work and has been licensed with their permission. 8 | 9 | # CompEcon 10 | 11 | This package is a wrapper around [BasisMatrices.jl](https://github.com/QuantEcon/BasisMatrices.jl) and provides an API similar to the original [CompEcon matlab library](http://www4.ncsu.edu/~pfackler/compecon/toolbox.html) by Miranda and Fackler. For best use of the underlying routines, we recommend using the BasisMatrices.jl API. 12 | 13 | The Matlab style API here is as close to the original library as possible (differences are based mostly on syntax). To see what this means, consider the following Matlab example (taken from `demapp01.m`): 14 | 15 | ```matlab 16 | % function to approximate 17 | f = @(x) exp(-x) 18 | 19 | % Set the endpoints of approximation interval: 20 | a = -1; % left endpoint 21 | b = 1; % right endpoint 22 | 23 | % Choose an approximation scheme. In this case, let us use an order 10 24 | % Chebychev approximation scheme: 25 | n = 10; % order of approximation 26 | basis = fundefn('cheb',n,a,b); % define basis 27 | 28 | % Compute the basis coefficients c. There are various way to do this: 29 | % One may use funfitf: 30 | c = funfitf(basis,@f); 31 | 32 | % ... or one may compute the standard approximation nodes x and corresponding 33 | % function values y and use funfitxy: 34 | x = funnode(basis); 35 | y = f(x); 36 | c = funfitxy(basis,x,y); 37 | 38 | % ... or one compute the standard approximation nodes x, corresponding 39 | % function values y, and the interpolation matrix phi, and solve the 40 | % interpolation equation directly using the backslash operator: 41 | x = funnode(basis); 42 | y = f(x); 43 | phi = funbase(basis); 44 | c = phi\y; 45 | 46 | % Having computed the basis coefficients, one may now evaluate the 47 | % approximant at any point x using funeval: 48 | x = 0; 49 | y = funeval(c,basis,x); 50 | ``` 51 | 52 | 53 | The corresponding Julia code is 54 | 55 | ```julia 56 | using CompEcon 57 | # function to approximate 58 | f(x) = exp.(-x) 59 | 60 | # Set the endpoints of approximation interval: 61 | a = -1 # left endpoint 62 | b = 1 # right endpoint 63 | 64 | # Choose an approximation scheme. In this case, let us use an order 10 65 | # Chebychev approximation scheme: 66 | n = 10 # order of approximation 67 | basis = fundefn(:cheb, n, a, b) # define basis 68 | 69 | # Compute the basis coefficients c. There are various way to do this: 70 | # One may use funfitf: 71 | c = funfitf(basis, f) 72 | 73 | # ... or one may compute the standard approximation nodes x and corresponding 74 | # function values y and use funfitxy: 75 | x = funnode(basis)[1] 76 | y = f(x) 77 | c = funfitxy(basis, x, y)[1] 78 | 79 | # ... or one compute the standard approximation nodes x, corresponding 80 | # function values y, and the interpolation matrix phi, and solve the 81 | # interpolation equation directly using the backslash operator: 82 | x = funnode(basis)[1] 83 | y = f(x) 84 | phi = funbase(basis) 85 | c = phi\y 86 | 87 | # Having computed the basis coefficients, one may now evaluate the 88 | # approximant at any point x using funeval: 89 | x = [0.0] 90 | y = funeval(c, basis, x)[1] 91 | ``` 92 | 93 | 94 | The main differences are: 95 | 96 | - The Julia code uses symbols instead of strings to specify basis functions and refer to objects in the basis structure. The Matlab uses string (we see this above with use of `'cheb'` in Matlab and `:cheb` in Julia) 97 | - The Matlab code relies heavily on the use of `varargout` to only return some objects. The Julia code always returns all objects the Matlab ones might ever return, so we need to be careful about keeping only some of the return arguments. (notice in the calls to `funnode` and `funeval` we just keep the first output in Julia). 98 | -------------------------------------------------------------------------------- /demo/dierckxcompare_1d.jl: -------------------------------------------------------------------------------- 1 | using CompEcon 2 | using Dierckx 3 | 4 | 5 | # Create functions to test against 6 | f1(x) = sin(x) 7 | fp1(x) = cos(x) 8 | f2(x) = exp(x) 9 | fp2(x) = exp(x) 10 | f3(x) = 1.5 + log(x) 11 | fp3(x) = 1./x 12 | fs = [f1, f2, f3] 13 | fps = [fp1, fp2, fp3] 14 | nf = length(fs) 15 | 16 | x = collect(range(.5, stop=2, length=35)) 17 | finex = collect(range(1e-2, stop=2*pi - 1e-2, length=150)) 18 | ys = map(f->f(x), fs) 19 | finey = map(f->f(finex), fs) 20 | 21 | function compare_D_CE_levels(f::Function, x::Array{Float64, 1}, order::Int) 22 | 23 | # Create Dierckx Spline (and data) 24 | n = length(x) 25 | y = f(x) 26 | dspl = Spline1D(x, y; s=0., k=order) 27 | 28 | # Create CompEcon Spline (and data) 29 | cebasis = Basis(SplineParams(x, 0, order)) 30 | xx = nodes(cebasis)[1] 31 | yy = f(xx) 32 | c, bs = funfitxy(cebasis, xx, yy) 33 | 34 | # Evaluate Splines on finer grid 35 | xfine = collect(range(x[1], stop=x[end], length=2*n + 1)) 36 | yfine = f(xfine) 37 | 38 | deval = Dierckx.evaluate(dspl, xfine) 39 | derr = abs(yfine - deval) 40 | 41 | ceeval = CompEcon.funeval(c, cebasis, xfine) 42 | ceerr = abs(yfine - ceeval) 43 | 44 | # # Evaluate Derivative 45 | # dderiv1 = Dierckx.derivative(dspl1, finex) 46 | # cederiv1 = CompEcon.evaluate(ceinterp1, finex, order=1) 47 | 48 | return derr, ceerr 49 | end 50 | 51 | function compare_D_CE_deriv(f::Function, fp::Function, x::Array{Float64, 1}, order::Int) 52 | 53 | # Create Dierckx Spline (and data) 54 | n = length(x) 55 | y = f(x) 56 | dspl = Spline1D(x, y; s=0., k=order) 57 | 58 | # Create CompEcon Spline (and data) 59 | cebasis = Basis(SplineParams(x, 0, order)) 60 | xx = nodes(cebasis)[1] 61 | yy = f(xx) 62 | c, bs = funfitxy(cebasis, xx, yy) 63 | 64 | # Evaluate Splines on finer grid 65 | xfine = collect(range(x[1], stop=x[end], length=2*n + 1)) 66 | ypfine = fp(xfine) 67 | 68 | devalderiv = Dierckx.derivative(dspl, xfine) 69 | derr = abs(ypfine - devalderiv) 70 | 71 | ceevalderiv = CompEcon.funeval(c, cebasis, xfine, 1) 72 | ceerr = abs(ypfine - ceevalderiv) 73 | 74 | return derr, ceerr 75 | end 76 | 77 | 78 | for i=1:nf 79 | 80 | curr_f, curr_fp = fs[i], fps[i] 81 | 82 | derr1_levels, ceerr1_levels = compare_D_CE_levels(curr_f, x, 1) 83 | derr2_levels, ceerr2_levels = compare_D_CE_levels(curr_f, x, 2) 84 | derr3_levels, ceerr3_levels = compare_D_CE_levels(curr_f, x, 3) 85 | 86 | # derr1_deriv, ceerr1_deriv = compare_D_CE_deriv(curr_f, curr_fp, x, 1) 87 | derr2_deriv, ceerr2_deriv = compare_D_CE_deriv(curr_f, curr_fp, x, 2) 88 | derr3_deriv, ceerr3_deriv = compare_D_CE_deriv(curr_f, curr_fp, x, 3) 89 | 90 | println("Linear Approximation to Function") 91 | println("Dierckx Level Errors \n\t Max: $(maximum(derr1_levels)) \n\t Min: $(minimum(derr1_levels)) \n\t Mean: $(mean(derr1_levels))") 92 | # println("Dierckx Deriv Errors \n\t Max: $(maximum(derr1_deriv)) \n\t Min: $(minimum(derr1_deriv)) \n\t Mean: $(mean(derr1_deriv))") 93 | println("CompEcon Level Errors \n\t Max: $(maximum(ceerr1_levels)) \n\t Min: $(minimum(ceerr1_levels)) \n\t Mean: $(mean(ceerr1_levels))") 94 | # println("CompEcon Deriv Errors \n\t Max: $(maximum(ceerr1_deriv)) \n\t Min: $(minimum(ceerr1_deriv)) \n\t Mean: $(mean(ceerr1_deriv))") 95 | 96 | println("Quadratic Approximation to Function") 97 | println("Dierckx Level Errors \n\t Max: $(maximum(derr2_levels)) \n\t Min: $(minimum(derr2_levels)) \n\t Mean: $(mean(derr2_levels))") 98 | println("Dierckx Deriv Errors \n\t Max: $(maximum(derr2_deriv)) \n\t Min: $(minimum(derr2_deriv)) \n\t Mean: $(mean(derr2_deriv))") 99 | println("CompEcon Level Errors \n\t Max: $(maximum(ceerr2_levels)) \n\t Min: $(minimum(ceerr2_levels)) \n\t Mean: $(mean(ceerr2_levels))") 100 | println("CompEcon Deriv Errors \n\t Max: $(maximum(ceerr2_deriv)) \n\t Min: $(minimum(ceerr2_deriv)) \n\t Mean: $(mean(ceerr2_deriv))") 101 | 102 | println("Cubic Approximation to Function") 103 | println("Dierckx Level Errors \n\t Max: $(maximum(derr3_levels)) \n\t Min: $(minimum(derr3_levels)) \n\t Mean: $(mean(derr3_levels))") 104 | println("Dierckx Deriv Errors \n\t Max: $(maximum(derr3_deriv)) \n\t Min: $(minimum(derr3_deriv)) \n\t Mean: $(mean(derr3_deriv))") 105 | println("CompEcon Level Errors \n\t Max: $(maximum(ceerr3_levels)) \n\t Min: $(minimum(ceerr3_levels)) \n\t Mean: $(mean(ceerr3_levels))") 106 | println("CompEcon Deriv Errors \n\t Max: $(maximum(ceerr3_deriv)) \n\t Min: $(minimum(ceerr3_deriv)) \n\t Mean: $(mean(ceerr3_deriv))") 107 | 108 | 109 | end -------------------------------------------------------------------------------- /demo/demapp01.jl: -------------------------------------------------------------------------------- 1 | import CompEcon 2 | using PyPlot 3 | using PyCall 4 | 5 | reload("CompEcon") 6 | ## DEMAPP01 Approximating functions on R 7 | 8 | # This m-file illustrates how to use CompEcon Toolbox routines to construct 9 | # and operate with an approximant for a function defined on an interval of 10 | # the real line. 11 | 12 | # In particular, we construct an approximant for f(x)=exp(-x) on the 13 | # interval [-1,1]. The function used in this illustration posseses a 14 | # closed-form, which will allow us to measure approximation error precisely. 15 | # Of course, in practical applications, the function to be approximated 16 | # will not possess a known closed-form. 17 | 18 | # In order to carry out the exercise, one must first code the function to 19 | # be approximated at arbitrary points. The required code is presented at 20 | # the end of this m-file (see below). Let's begin: 21 | 22 | # Preliminary tasks 23 | function main() 24 | f(x) = exp(-x) 25 | 26 | # Set the endpoints of approximation interval: 27 | a = -1 # left endpoint 28 | b = 1 # right endpoint 29 | 30 | # Choose an approximation scheme. In this case, let us use an order 10 31 | # Chebychev approximation scheme: 32 | n = 10 # order of approximation 33 | basis = CompEcon.fundefn(:cheb, n, a, b) # define basis 34 | 35 | # Compute the basis coefficients c. There are various way to do this: 36 | # One may use funfitf: 37 | c = CompEcon.funfitf(basis, f) 38 | 39 | # ... or one may compute the standard approximation nodes x and 40 | # corresponding function values y and use funfitxy: 41 | x = CompEcon.funnode(basis)[1] 42 | y = f(x) 43 | c = CompEcon.funfitxy(basis, x, y) 44 | 45 | # ... or one compute the standard approximation nodes x, corresponding 46 | # function values y, and the interpolation matrix phi, and solve the 47 | # interpolation equation directly using the backslash operator: 48 | x = CompEcon.funnode(basis)[1] 49 | y = f(x) 50 | phi = CompEcon.funbase(basis) 51 | c = phi\y 52 | 53 | # Having computed the basis coefficients, one may now evaluate the 54 | # approximant at any point x using funeval: 55 | x = [0.0] 56 | y = CompEcon.funeval(c, basis, x)[1] 57 | println("The approximate value of exp(-x) at x=0 is $y") 58 | println("The ''exact'' value of exp(-x) at x=0 is 1") 59 | 60 | # ... one may also evaluate the approximant's first and second derivatives 61 | # at x: 62 | d1 = CompEcon.funeval(c, basis, x, 1)[1][1] 63 | d2 = CompEcon.funeval(c, basis, x, 2)[1][1] 64 | println("The approximate first derivative of exp(-x) at x=0 is $d1") 65 | println("The ''exact'' first derivative of exp(-x) at x=0 is -1") 66 | println("The approximate second derivative of exp(-x) at x=0 is $d2") 67 | println("The ''exact'' second derivative of exp(-x) at x=0 is 1") 68 | 69 | # ... and one may even evaluate the approximant's definite integral between 70 | # the left endpoint a and x: 71 | int = CompEcon.funeval(c, basis, x, -1)[1] 72 | println("The approximate integral of exp(-x) between x=-1 and x=0 is $int") 73 | println("The `exact` integral of exp(-x) between x ∈[-1, 0] is $(exp(1)-1)") 74 | 75 | # One may evaluate the accuracy of the Chebychev polynomial approximant by 76 | # computing the approximation error on a highly refined grid of points: 77 | ngrid = 5001 # number of grid nodes 78 | xgrid = range(a, stop=b, length=ngrid) # generate refined grid for plotting 79 | 80 | 81 | function plot_approx(f::Function, basistype::Symbol, n, a, b, xgrid, k=0) 82 | basis = basistype == :spli ? CompEcon.fundefn(:spli, n, a, b, k) : 83 | basistype == :cheb ? CompEcon.fundefn(:cheb, n, a, b) : 84 | error("Not doing this") 85 | 86 | # compute approximation 87 | c = CompEcon.funfitf(basis, f) 88 | yapp = CompEcon.funeval(c, basis, xgrid)[1] 89 | 90 | # compute exact 91 | yact = f(xgrid) 92 | 93 | # plot error 94 | fig, ax = plt.subplots() 95 | ax[:plot](xgrid,yapp-yact) 96 | ax[:plot](xgrid, zeros(ngrid), "k--", linewidth=2) 97 | ax[:set_xlabel]("x") 98 | ax[:set_ylabel]("Error") 99 | nm = basistype == :spli ? "Degree $k spline" : "Degree $n Chebyshev" 100 | ax[:set_title]("$nm Approximation Error for exp(-x)") 101 | end 102 | 103 | plot_approx(f, :cheb, n, a, b, xgrid) 104 | plot_approx(f, :spli, 21, a, b, xgrid, 3) 105 | # plot_approx(f, :cheb, 31, a, b, xgrid, 1) 106 | end 107 | 108 | main() 109 | -------------------------------------------------------------------------------- /src/core.jl: -------------------------------------------------------------------------------- 1 | # -------------------- # 2 | # Dispatcher functions # 3 | # -------------------- # 4 | 5 | const BASE_TYPES = [:spli, :cheb, :lin] 6 | const ABSR_MAP = Dict( 7 | :none => Direct(), 8 | :direct => Direct(), 9 | :tensor => Tensor(), 10 | :expanded => Expanded(), 11 | ) 12 | get_bformat(b::T) where T<:BasisMatrix{Direct} = :direct 13 | get_bformat(b::T) where T<:BasisMatrix{Expanded} = :expanded 14 | get_bformat(b::T) where T<:BasisMatrix{Tensor} = :tensor 15 | 16 | function to_dict(bm::BasisMatrix) 17 | B = Dict{Symbol, Any}() 18 | B[:order] = bm.order 19 | B[:format] = get_bformat(bm) 20 | B[:vals] = bm.vals 21 | B 22 | end 23 | 24 | function bm_from_dict(B::Dict) 25 | arr_type = eltype(B[:vals]) 26 | bm = BasisMatrix{typeof(ABSR_MAP[B[:format]]),arr_type}(B[:order], B[:vals]) 27 | bm 28 | end 29 | 30 | base_exists(s::Symbol) = s in BASE_TYPES 31 | 32 | basedef(s::Symbol, args...) = 33 | s == :spli ? splidef(args...) : 34 | s == :cheb ? chebdef(args...) : 35 | s == :lin ? lindef(args...) : 36 | error("somehow you snuck through here you 👺") 37 | 38 | basenode(s::Symbol, args...) = 39 | s == :spli ? splinode(args...) : 40 | s == :cheb ? chebnode(args...) : 41 | s == :lin ? linnode(args...) : 42 | error("somehow you snuck through here you 👺") 43 | 44 | BasisMatrices.evalbase(s::Symbol, args...) = 45 | s == :spli ? splibase(args...) : 46 | s == :cheb ? chebbase(args...) : 47 | s == :lin ? linbase(args...) : 48 | error("somehow you snuck through here you 👺") 49 | 50 | # Helper function 51 | function squeeze_trail(x::AbstractArray) 52 | sz = size(x) 53 | squeezers = Int[] 54 | n = length(sz) 55 | for i=n:-1:1 56 | if sz[i] == 1 57 | push!(squeezers, i) 58 | else 59 | break 60 | end 61 | end 62 | squeeze(x, tuple(squeezers...)) 63 | end 64 | 65 | 66 | # ---------------------------- # 67 | # Generic translated functions # 68 | # ---------------------------- # 69 | 70 | # from fundef.m -- DONE 71 | function fundef(foo...) 72 | d = length(foo) # 89 73 | n = zeros(Int, d) # 93 74 | b = zeros(d) # 94 75 | a = zeros(d) # 95 76 | p = Array{Any}(undef, d) # 96 77 | _params = Array{BasisMatrices.BasisParams}(undef, d) 78 | 79 | basetype = Array{Symbol}(undef, d) 80 | for j=1:d 81 | basetype[j] = foo[j][1] # 99 82 | !(base_exists(basetype[j])) && error("Unknown basis $(foo[j][1])") 83 | n[j], a[j], b[j], p[j], _params[j] = basedef(basetype[j], foo[j][2:end]...) # 124 84 | end 85 | 86 | # package output. Lines 143-150 87 | g = Dict{Symbol, Any}() 88 | g[:d] = d 89 | g[:n] = n 90 | g[:a] = a 91 | g[:b] = b 92 | g[:basetype] = basetype 93 | g[:params] = p 94 | g[:_basis_params] = _params 95 | g[:_basis] = Basis(_params...) 96 | g 97 | end 98 | 99 | # fundefn.m 100 | function fundefn(basistype::Symbol, n, a, b, order=3) 101 | d = length(n) 102 | length(a) != d && error("a must be same dimension as n") 103 | length(b) != d && error("b must be same dimension as n") 104 | any(a .> b) && error("left endpoints must be less than right endpoints") 105 | any(n .< 2) && error("n(i) must be greater than 1") 106 | 107 | params = Array{Any}(undef, 1, d) 108 | if basistype == :cheb 109 | for i=1:d params[i] = Any[:cheb, n[i], a[i], b[i]] end 110 | elseif basistype == :spli 111 | for i=1:d params[i] = Any[:spli, [a[i], b[i]], n[i]-order+1, order] end 112 | elseif basistype == :lin 113 | for i=1:d params[i] = Any[:lin, [a[i], b[i]], n[i]] end 114 | end 115 | 116 | fundef(params...) 117 | end 118 | 119 | # funnode.m -- DONE 120 | funnode(basis::Dict) = nodes(basis[:_basis]) 121 | 122 | # funbase.m -- DONE 123 | function funbase(basis::Dict, x=funnode(basis)[1], order=fill(0, 1, basis[:d])) 124 | BasisMatrix(basis[:_basis], Expanded(), x, order).vals[1] 125 | end 126 | 127 | # funbasex.m -- DONE 128 | function funbasex(basis::Dict{Symbol}, x=funnode(basis)[1], order=0, 129 | bformat::Symbol=:none) 130 | to_dict(BasisMatrix(basis[:_basis], ABSR_MAP[bformat], x, order)) 131 | end 132 | 133 | # funfitf.m -- DONE 134 | funfitf(basis, f::Function, args...) = funfitf(basis[:_basis], f, args...) 135 | 136 | # funfitxy.m -- DONE 137 | function funfitxy(basis, x, y) 138 | c, bm = funfitxy(basis[:_basis], x, y) 139 | return c, to_dict(bm) 140 | end 141 | 142 | # funeval.m -- DONE 143 | function funeval(c, basis::Dict, B, _order=0) 144 | isempty(c) && error("missing basis coefficients") 145 | order = BasisMatrices._check_order(basis[:d], _order) 146 | 147 | if isa(B, Dict) # B is a basis structure 148 | bm = bm_from_dict(B) 149 | y = funeval(c, bm, order) 150 | return y, B 151 | else 152 | bm = BasisMatrix(basis[:_basis], B, order) 153 | y = funeval(c, bm, bm.order) 154 | return y, to_dict(bm) 155 | end 156 | end 157 | 158 | # fund.m 159 | function fund(c, basis, x, hess_opt) 160 | # TODO: come back when I need this. I think I should probably do something 161 | # like what optim does and write functions `f`, `fg!` and `fgh!` to 162 | # replicate this for the type of basis instead of this function 163 | nothing 164 | end 165 | 166 | # funbconv.m -- DONE 167 | function funbconv(b::Dict, order=fill(0, 1, size(b[:order], 2)), 168 | format::Symbol=:expanded) 169 | bm = bm_from_dict(b) 170 | new_bm = convert(typeof(ABSR_MAP[format]), bm, order) 171 | to_dict(new_bm) 172 | end 173 | --------------------------------------------------------------------------------