{"id":32153936,"url":"https://github.com/jbshannon/multibisect.jl","last_synced_at":"2026-06-22T16:31:21.779Z","repository":{"id":200010856,"uuid":"704090908","full_name":"jbshannon/MultiBisect.jl","owner":"jbshannon","description":"Lightweight multidimensional root-finding in Julia","archived":false,"fork":false,"pushed_at":"2024-04-01T20:29:19.000Z","size":365,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-22T09:39:12.231Z","etag":null,"topics":["bisection","julia","optimization"],"latest_commit_sha":null,"homepage":"","language":"Julia","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jbshannon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2023-10-12T14:13:07.000Z","updated_at":"2024-04-01T14:32:53.000Z","dependencies_parsed_at":"2023-10-15T07:52:18.788Z","dependency_job_id":"61fc069e-c54c-48b9-a96f-1406cb2551b6","html_url":"https://github.com/jbshannon/MultiBisect.jl","commit_stats":null,"previous_names":["jbshannon/multibisect.jl"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/jbshannon/MultiBisect.jl","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbshannon%2FMultiBisect.jl","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbshannon%2FMultiBisect.jl/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbshannon%2FMultiBisect.jl/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbshannon%2FMultiBisect.jl/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jbshannon","download_url":"https://codeload.github.com/jbshannon/MultiBisect.jl/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbshannon%2FMultiBisect.jl/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280256125,"owners_count":26299341,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-21T02:00:06.614Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bisection","julia","optimization"],"created_at":"2025-10-21T11:47:58.259Z","updated_at":"2026-06-22T16:31:21.771Z","avatar_url":"https://github.com/jbshannon.png","language":"Julia","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MultiBisect.jl\n\n[![Build Status](https://github.com/jbshannon/MultiBisect.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/jbshannon/MultiBisect.jl/actions/workflows/CI.yml?query=branch%3Amain)\n[![](https://img.shields.io/badge/docs-stable-blue.svg)](https://jbshannon.github.io/MultiBisect.jl/stable)\n[![](https://img.shields.io/badge/docs-dev-blue.svg)](https://jbshannon.github.io/MultiBisect.jl/dev)\n\n\nThis Julia package provides a lightweight, idiomatic implementation of the [bisection method](https://en.wikipedia.org/wiki/Bisection_method) of root-finding in an arbitrary number of dimensions, which can be used to find level curves or other higher-dimensional isosurfaces of a function with fewer evaluations.\n\nThis package exports the `BisectionGrid` type, which tracks an `N`-dimensional bisection with `Array{Tuple{Bool, Bool}, N}` for sign tracking, three `Array{Bool, N}`s for state management, and one `NTuple{N, R \u003c: AbstractRange}`. The sign array uses a 4-state tuple system: `(true, true)` for unevaluated points, `(true, false)` for positive values, `(false, true)` for negative values, and `(false, false)` for zero values. This enables precise detection of level curves where function evaluations are exactly zero.\n\n\u003e [!IMPORTANT]\n\u003e This package, while functional, is still experimental and subject to change. For a more mature package with similar functionality, see [MDBM.jl](https://github.com/bachrathyd/MDBM.jl)\n\n## Installation\n\nThis package is now registered, so installation is available from the `pkg\u003e` prompt:\n\n```julia\npkg\u003e add MultiBisect\n```\n\nor in a script:\n\n```julia\nimport Pkg; Pkg.add(\"MultiBisect\")\n```\n\n## Overview\n\nThe task of finding the root of a continuous function on an interval where its sign is known to change is a relatively well-known and simple problem, but extending the method to handle two or more dimensions adds complexity. For example, consider the unit circle on the domain $[0,1]^2$.\n\n```julia\nf(z) = 1 - sum(abs2, z)\ncircle = map(t -\u003e (cos(t), sin(t)), 0:0.01:π/2)\n\nusing CairoMakie\nfig = Figure(resolution = (500, 500))\nax = Axis(fig[1, 1])\nlines!(ax, circle; color=:black)\nfig\n```\n![](docs/src/images/README/circle1.svg)\n\n### The Problem\n\nThe naive, brute-force approach would be to evaluate the function on a grid of points, but many of these evaluations would be far away from the root (in two dimensions, a zero level curve) and not provide any information about the shape of the level curve.\n\n```julia\nxs = range(0, 1, length=65)\nXS = Iterators.product(xs, xs) |\u003e collect\nF = f.(XS)\n\nscatter!(ax, XS[F .\u003e 0]; markersize=3)\nscatter!(ax, XS[F .≤ 0]; markersize=3)\nfig\n```\n![](docs/src/images/README/circle2.svg)\n\nThe level curve must lie between the positive points and the negative points. Two adjacent points that share the same sign do not tell us anything about the level curve, so we could improve our efficiency if we avoided evaluating the function in regions where the sign does not appear to change.\n\n### Bisection\n\nUsing the bisection method, we can reduce the number of function evaluations by increasing the fineness of the grid only in places we know the function must change sign. This package provides the function `bisect(f, grid)`, which applies the algorithm iteratively to the `grid` using the function `f`:\n\n```julia\nusing MultiBisect\nBG = bisect(f, (0.0:1.0, 0.0:1.0); iterations=7)\nposx, negx = splitsign(BG)\n\nfig = Figure(resolution = (500, 500))\nax = Axis(fig[1, 1])\nlines!(ax, circle; color=:black)\nscatter!(ax, posx; markersize=3)\nscatter!(ax, negx; markersize=3)\nfig\n```\n![](docs/src/images/README/circle3.svg)\n\nThe resulting grid is the same size and identifies the exact same sign changes as before, but with far fewer function evaluations – nearly 90% fewer!\n\n```julia\njulia\u003e BG\nBisectionGrid{Float64, 2}\n       Domain: (0.0:0.015625:1.0, 0.0:0.015625:1.0)\n  Grid points: 4225\n  Evaluations: 490\n\n\njulia\u003e (xs, xs) == domain(BG)\ntrue\n\njulia\u003e efficiency(BG) # percentage of gridpoints not evaluated\n0.8840236686390532\n```\n\n### Zero Detection\n\nA key feature of this implementation is precise zero detection. The sign array tracks four distinct states using `Tuple{Bool, Bool}`:\n\n```julia\nusing MultiBisect\n\n# Helper functions to check sign states\nispositivesign(sign::Tuple{Bool, Bool}) # Returns true for (true, false)\nisnegativesign(sign::Tuple{Bool, Bool}) # Returns true for (false, true)  \niszerosign(sign::Tuple{Bool, Bool})   # Returns true for (false, false)\nisevaluated(sign::Tuple{Bool, Bool}) # Returns true for anything except (true, true)\n```\n\nThis enables the algorithm to detect when function evaluations are exactly zero, which is crucial for accurate level curve detection. Zero-adjacent edges are properly identified in the edge detection process, ensuring that points exactly on the level curve are included in the interpolation step.\n\nThis efficiency gain is possible because we know that if a square in the grid has vertices that are not all the same sign, the function must change sign somewhere within the square (this is of course only a necessary and not a sufficient condition). The method proceeds by dividing an initial grid \"in half\" (in the multidimensional sense) at each stage. Since we are working in two dimensions, we break each square into four smaller squares. Before evaluating the function, we check the sign of the function at the vertices of the larger square. If the function does not change sign, there is no need to evaluate the function within the square. By discarding squares whose vertices all share the same sign at each iteration, we avoid uninformative function evaluations. Here is a step-by-step view of the algorithm:\n\nhttps://github.com/jbshannon/MultiBisect.jl/assets/46204520/f4fa8893-f073-40f7-8907-84308b91cc65\n\n### Interpolation\n\nOnce we're satisfied we have enough information about where the function changes signs, we need to convert that information into actual points along the curve. This package examines the *edges* of the grid: two adjacent points with opposite signs. \n\n```julia\njulia\u003e edges(BG)\n128-element Vector{Tuple{Tuple{Float64, Float64}, Tuple{Float64, Float64}}}:\n ((0.984375, 0.0), (1.0, 0.0))\n ((0.984375, 0.015625), (1.0, 0.015625))\n ((0.984375, 0.03125), (1.0, 0.03125))\n ((0.984375, 0.046875), (1.0, 0.046875))\n ((0.984375, 0.0625), (1.0, 0.0625))\n ⋮\n ((0.125, 0.984375), (0.125, 1.0))\n ((0.140625, 0.984375), (0.140625, 1.0))\n ((0.15625, 0.984375), (0.15625, 1.0))\n ((0.171875, 0.984375), (0.1875, 0.984375))\n ((0.171875, 0.984375), (0.171875, 1.0))\n```\n\nSince an edge is one-dimensional, we can convert the multidimensional root-finding problem to a series of one-dimensional problems. This packages provides the function `interpolate(rootfinder, BG)`, which takes a function `rootfinder` – taking an edge as input and returning a root as output – and applies it to each edge in the grid `BG`. This package currently provides three root-finders for convenience:\n\n- `edgeroot`: solve for the root on the edge with a call to `Roots.find_zero`\n- `linearroot`: create a linear interpolation of the function through the two edge points and compute its root\n- `marchingsquares`: return the midpoint of the edge\n\nHere is a visual comparison of these different interpolation methods:\n\n![](docs/src/images/README/interpolations.svg)\n\nWhen function evaluations are very expensive, `interpolate` can be called without a `rootfinder` argument to use the linear approximation method with saved function evaluations from the bisection process.\n\n### More dimensions!\n\nSince Julia has the magical `CartesianIndices` iterator, translating this process out of two dimensions is as simple as changing the dimension of the initial evaluation grid. Here is an example finding roots of the 5D unit hypersphere over the 5D unit hypercube:\n\n```julia\njulia\u003e grid = ntuple(i -\u003e (0.0:1.0), 5)\n(0.0:1.0:1.0, 0.0:1.0:1.0, 0.0:1.0:1.0, 0.0:1.0:1.0, 0.0:1.0:1.0)\n\njulia\u003e BG5 = bisect(f, grid)\nBisectionGrid{Float64, 5}\n       Domain: (0.0:0.0625:1.0, 0.0:0.0625:1.0, 0.0:0.0625:1.0, 0.0:0.0625:1.0, 0.0:0.0625:1.0)\n  Grid points: 1419857\n  Evaluations: 396246\n\njulia\u003e interpolate(linearroot(f), BG5)\n124020-element Vector{NTuple{5, Float64}}:\n (1.0, 0.0, 0.0, 0.0, 0.0)\n (0.9979838709677419, 0.0625, 0.0, 0.0, 0.0)\n (0.9919354838709677, 0.125, 0.0, 0.0, 0.0)\n (0.9818548387096774, 0.1875, 0.0, 0.0, 0.0)\n (0.967741935483871, 0.25, 0.0, 0.0, 0.0)\n ⋮\n (0.0625, 0.0625, 0.125, 0.3125, 0.9375)\n (0.0, 0.08333333333333333, 0.125, 0.3125, 0.9375)\n (0.0, 0.0625, 0.1375, 0.3125, 0.9375)\n (0.0, 0.0625, 0.125, 0.3181818181818182, 0.9375)\n (0.0, 0.0625, 0.125, 0.3125, 0.9395161290322581)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjbshannon%2Fmultibisect.jl","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjbshannon%2Fmultibisect.jl","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjbshannon%2Fmultibisect.jl/lists"}