diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index c1f6550f..9704582f 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v2 with: - version: '1.9' + version: '1.10' - uses: julia-actions/cache@v2 - name: Install dependencies run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' diff --git a/docs/Project.toml b/docs/Project.toml index 8a11b5a1..36ddf560 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,12 +1,19 @@ [deps] BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" +Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326" MeshIntegrals = "dadec2fd-bbe0-4da4-9dbe-476c782c8e47" Meshes = "eacbb407-ea5a-433e-ab97-5258b1ca43fa" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [compat] BenchmarkTools = "1" +CairoMakie = "0.15" +Colors = "0.13" +Distributions = "0.25" Documenter = "1" Meshes = "0.53, 0.54" Unitful = "1.19" diff --git a/docs/make.jl b/docs/make.jl index 0cb480e7..fd2311ba 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -16,6 +16,9 @@ makedocs( "Support Status" => "support.md", "Tips" => "tips.md" ], + "Examples" => [ + "Darts Strategy Analysis" => "examples/darts.md" + ], "Developer Notes" => [ "Changelog" => "developer/CHANGELOG.md", "How it Works" => "developer/how_it_works.md", diff --git a/docs/src/examples/Project.toml b/docs/src/examples/Project.toml new file mode 100644 index 00000000..e6c9f5b9 --- /dev/null +++ b/docs/src/examples/Project.toml @@ -0,0 +1,19 @@ +[deps] +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" +Colors = "5ae59095-9a9b-59fe-a467-6f913c188581" +Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +MeshIntegrals = "dadec2fd-bbe0-4da4-9dbe-476c782c8e47" +Meshes = "eacbb407-ea5a-433e-ab97-5258b1ca43fa" +Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" + +[compat] +BenchmarkTools = "1" +CairoMakie = "0.15" +Colors = "0.13" +Distributions = "0.25" +Documenter = "1" +Meshes = "0.53, 0.54" +Unitful = "1.19" +julia = "1.9" diff --git a/docs/src/examples/darts.jl b/docs/src/examples/darts.jl new file mode 100644 index 00000000..a164779a --- /dev/null +++ b/docs/src/examples/darts.jl @@ -0,0 +1,109 @@ +using CairoMakie +using Colors +using Distributions +using Meshes +using MeshIntegrals +using Unitful +using Unitful.DefaultSymbols: mm, m + + +################################################################################ +# Geometry Utils +################################################################################ + +# Define a plane (wall) on which the dartboard rests +dartboard_center = Meshes.Point(0m, 0m, 1.5m) +dartboard_plane = Plane(dartboard_center, Meshes.Vec(1, 0, 0)) +point(t, ϕ) = dartboard_plane(t * sin(ϕ), t * cos(ϕ)) +point(r::Unitful.Length, ϕ) = point(ustrip(u"m", r), ϕ) + +# Score-earning areas where darts can land +struct ScoredRegion{G, C} + geometry::G + points::Int64 + color::C +end + +# A sectoral geometry bounded by constant r and ϕ limits +struct Sector{L <: Unitful.Length, A} + r_inner::L + r_outer::L + ϕ_a::A + ϕ_b::A +end + +# Sector -> Ngon +function _Ngon(sector::Sector; N=32) + ϕs = range(sector.ϕ_a, sector.ϕ_b, length=N) + arc_o = [point(sector.r_outer, ϕ) for ϕ in ϕs] + arc_i = [point(sector.r_inner, ϕ) for ϕ in reverse(ϕs)] + return Meshes.Ngon(arc_o..., arc_i...) +end + + +################################################################################ +# Construct Dartboard ScoredRegions +################################################################################ + +# Sectorize the board +# scores +ring_pts = [20, 1, 18, 4, 13, 6, 10, 15, 2, 17, 3, 19, 7, 16, 8, 11, 14, 9, 12, 5] +board_points = hcat(ring_pts, (3 .* ring_pts), ring_pts, (2 .* ring_pts)) +# colors +ring_c1 = repeat([colorant"black", colorant"white"], 10) +ring_c2 = repeat([colorant"red", colorant"green"], 10) +board_colors = hcat(ring_c1, ring_c2, ring_c1, ring_c2) +# geometries +Δϕ = 2π/20 +ϕas = range(0, 2π - Δϕ, length=20) .- (Δϕ / 2) +ϕbs = range(0, 2π - Δϕ, length=20) .+ (Δϕ / 2) +ϕs = Iterators.zip(ϕas, ϕbs) +rs = [ (16mm, 99mm), (99mm, 107mm), (107mm, 162mm), (162mm, 170mm) ] +board_ngons = map(((ϕs, rs),) -> _Ngon(Sector(rs..., ϕs...)), Iterators.product(ϕs, rs)) + +# Consolidate the Sectors +sector_data = Iterators.zip(board_ngons, board_points, board_colors) +board_regions = map(args -> ScoredRegion(args...), sector_data) + +# Center region +bullseye_inner = ScoredRegion(Meshes.Circle(dartboard_plane, (6.35e-3)m), 50, colorant"red") +bullseye_outer = ScoredRegion(_Ngon(Sector(6.35mm, 16.0mm, 0.0, 2π)), 25, colorant"green") + +# Get set of all regions +all_regions = vcat(vec(board_regions), bullseye_inner, bullseye_outer) + + +################################################################################ +# Makie Utils +################################################################################ + +# To Makie-compatible GeometryBasics types +_Point2f(p::Meshes.Point) = Point2f(ustrip.(u"m", (p.coords.y, p.coords.z))...) +_Point3f(p::Meshes.Point) = Point3f(ustrip.(u"m", (p.coords.x, p.coords.y, p.coords.z))...) + +# To Makie-compatible polygons +_poly(circle::Meshes.Circle; N=32) = [(_Point3f(circle(t)) for t in range(0, 1, length=N))...] +_poly(ngon::Meshes.Ngon) = [(_Point3f(pt) for pt in ngon.vertices)...] +_poly2d(circle::Meshes.Circle; N=32) = [(_Point2f(circle(t)) for t in range(0, 1, length=N))...] +_poly2d(ngon::Meshes.Ngon) = [(_Point2f(pt) for pt in ngon.vertices)...] + + +################################################################################ +# Figure - Dartboard +################################################################################ + +# Illustrate the dartboard +fig = Figure() +ax = Axis(fig[1, 1], xlabel="y [m]", ylabel="z [m]") +ax.aspect = DataAspect() +for region in all_regions + poly!(ax, _poly2d(region.geometry), color=region.color) + + # Write score label on geometry + centerPt = centroid(region.geometry) + center = ustrip.(u"m", [centerPt.coords.y, centerPt.coords.z]) + text!(ax, string(region.points), position=Point2f(center...), align=(:center,:center), color=:blue, fontsize=10) +end + +fig +save("dartboard.png", fig) diff --git a/docs/src/examples/darts.md b/docs/src/examples/darts.md new file mode 100644 index 00000000..1b3efa5f --- /dev/null +++ b/docs/src/examples/darts.md @@ -0,0 +1,147 @@ +# Darts (Draft) + +Steps +- Construct a set of geometries representing a dartboard with individual sector scores +- Develop a model of the dart trajectory with probability density distribution +- Use integration over each geometry to determine the probabilities of particular outcomes +- Calculate expected value for the throw, repeat for other distributions to compare strategies + +```@example darts + +``` + +## Modeling the Dartboard + +Model the dartboard +```@example darts +using CairoMakie +using Colors +using Distributions +using Meshes +using MeshIntegrals +using Unitful +using Unitful.DefaultSymbols: mm, m + + +################################################################################ +# Geometry Utils +################################################################################ + +# Define a plane (wall) on which the dartboard rests +dartboard_center = Meshes.Point(0m, 0m, 1.5m) +dartboard_plane = Plane(dartboard_center, Meshes.Vec(1, 0, 0)) +point(t, ϕ) = dartboard_plane(t * sin(ϕ), t * cos(ϕ)) +point(r::Unitful.Length, ϕ) = point(ustrip(u"m", r), ϕ) + +# Score-earning areas where darts can land +struct ScoredRegion{G, C} + geometry::G + points::Int64 + color::C +end + +# A sectoral geometry bounded by constant r and ϕ limits +struct Sector{L <: Unitful.Length, A} + r_inner::L + r_outer::L + ϕ_a::A + ϕ_b::A +end + +# Sector -> Ngon +function _Ngon(sector::Sector; N=32) + ϕs = range(sector.ϕ_a, sector.ϕ_b, length=N) + arc_o = [point(sector.r_outer, ϕ) for ϕ in ϕs] + arc_i = [point(sector.r_inner, ϕ) for ϕ in reverse(ϕs)] + return Meshes.Ngon(arc_o..., arc_i...) +end + + +################################################################################ +# Construct Dartboard ScoredRegions +################################################################################ + +# Sectorize the board +# scores +ring_pts = [20, 1, 18, 4, 13, 6, 10, 15, 2, 17, 3, 19, 7, 16, 8, 11, 14, 9, 12, 5] +board_points = hcat(ring_pts, (3 .* ring_pts), ring_pts, (2 .* ring_pts)) +# colors +ring_c1 = repeat([colorant"black", colorant"white"], 10) +ring_c2 = repeat([colorant"red", colorant"green"], 10) +board_colors = hcat(ring_c1, ring_c2, ring_c1, ring_c2) +# geometries +Δϕ = 2π/20 +ϕas = range(0, 2π - Δϕ, length=20) .- (Δϕ / 2) +ϕbs = range(0, 2π - Δϕ, length=20) .+ (Δϕ / 2) +ϕs = Iterators.zip(ϕas, ϕbs) +rs = [ (16mm, 99mm), (99mm, 107mm), (107mm, 162mm), (162mm, 170mm) ] +board_ngons = map(((ϕs, rs),) -> _Ngon(Sector(rs..., ϕs...)), Iterators.product(ϕs, rs)) + +# Consolidate the Sectors +sector_data = Iterators.zip(board_ngons, board_points, board_colors) +board_regions = map(args -> ScoredRegion(args...), sector_data) + +# Center region +bullseye_inner = ScoredRegion(Meshes.Circle(dartboard_plane, (6.35e-3)m), 50, colorant"red") +bullseye_outer = ScoredRegion(_Ngon(Sector(6.35mm, 16.0mm, 0.0, 2π)), 25, colorant"green") + +# Get set of all regions +all_regions = vcat(vec(board_regions), bullseye_inner, bullseye_outer) + + +################################################################################ +# Makie Utils +################################################################################ + +# To Makie-compatible GeometryBasics types +_Point2f(p::Meshes.Point) = Point2f(ustrip.(u"m", (p.coords.y, p.coords.z))...) +_Point3f(p::Meshes.Point) = Point3f(ustrip.(u"m", (p.coords.x, p.coords.y, p.coords.z))...) + +# To Makie-compatible polygons +_poly(circle::Meshes.Circle; N=32) = [(_Point3f(circle(t)) for t in range(0, 1, length=N))...] +_poly(ngon::Meshes.Ngon) = [(_Point3f(pt) for pt in ngon.vertices)...] +_poly2d(circle::Meshes.Circle; N=32) = [(_Point2f(circle(t)) for t in range(0, 1, length=N))...] +_poly2d(ngon::Meshes.Ngon) = [(_Point2f(pt) for pt in ngon.vertices)...] + + +################################################################################ +# Figure - Dartboard +################################################################################ + +# Illustrate the dartboard +fig = Figure() +ax = Axis(fig[1, 1], xlabel="y [m]", ylabel="z [m]") +ax.aspect = DataAspect() +for region in all_regions + poly!(ax, _poly2d(region.geometry), color=region.color) + + # Write score label on geometry + centerPt = centroid(region.geometry) + center = ustrip.(u"m", [centerPt.coords.y, centerPt.coords.z]) + text!(ax, string(region.points), position=Point2f(center...), align=(:center,:center), color=:blue, fontsize=10) +end +fig +``` + +## Modeling the Dart Trajectory + +Define a probability distribution for where the dart will land +``` +# TODO +dist = MvNormal(μs, σs) +``` + +Integrand function is the distribution's PDF value at any particular point +``` +# TODO +function integrand(p::Point) + v_error = dist_center - p + pdf(dist, v_error) +end +``` + +Example image of trajectory probability distribution on board + +## Strategy Evaluation + +Use these tools to evaluate different aiming/throwing parameters and their impact on expected scores.