Skip to content

Commit 0e152b8

Browse files
authored
Merge pull request #59 from StevenBtw/dev
Dev
2 parents 290727a + c3db6b5 commit 0e152b8

5 files changed

Lines changed: 90 additions & 18 deletions

File tree

docs/algorithms/graph/shortest-paths.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Algorithms for finding shortest paths in weighted graphs. For unweighted graphs,
44

55
## dijkstra
66

7-
[Dijkstra's algorithm](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm) for non-negative edge weights. Greedily expands the closest unvisited node.
7+
[Dijkstra's algorithm](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm) for non-negative edge weights. The classic greedy shortest path algorithm.
88

99
```python
1010
from solvor import dijkstra
@@ -21,7 +21,11 @@ print(result.solution) # ['A', 'B', 'C', 'D']
2121
print(result.objective) # 4
2222
```
2323

24-
**Complexity:** O((V + E) log V)
24+
**How it works:** Maintain a priority queue of nodes by tentative distance. Always expand the closest unvisited node—with non-negative weights, this node's distance is final. Update neighbors and repeat until you reach the goal.
25+
26+
The key insight: if all edges are non-negative, the shortest path to the closest unexplored node can't possibly go through unexplored territory (that would only add distance). So we can "lock in" each node's distance as we visit it.
27+
28+
**Complexity:** O((V + E) log V) with a binary heap, where V = nodes, E = edges
2529
**Guarantees:** Optimal for non-negative weights
2630

2731
## astar
@@ -72,7 +76,13 @@ result = bellman_ford(n_nodes=4, edges=edges, start=0)
7276
print(result.solution) # Distances from node 0
7377
```
7478

75-
**Complexity:** O(VE)
79+
**How it works:** Relax all edges V−1 times (where V is the number of nodes). "Relaxing" an edge means: if going through this edge gives a shorter path, update the distance.
80+
81+
Each pass guarantees we've found shortest paths using at most k edges. After V−1 passes, we've found all shortest paths—because a simple path visits each node at most once, so it uses at most V−1 edges.
82+
83+
To detect negative cycles: do one more pass. If any distance still improves, there's a negative cycle—you can keep going around it forever, reducing the distance infinitely.
84+
85+
**Complexity:** O(V × E) where V = nodes, E = edges
7686
**Guarantees:** Optimal, detects negative cycles
7787

7888
## floyd_warshall

docs/algorithms/linear-programming/solve-lp-interior.md

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,34 @@ print(result.objective) # 12.0 approximately
4747

4848
## How It Works
4949

50-
Interior point methods maintain a point strictly inside the feasible region and move toward optimality while staying interior. This implementation uses:
50+
While simplex hops between vertices, interior point methods stay strictly inside the feasible region and approach optimality along a curved path called the *central path*.
5151

52-
1. **Primal-dual formulation** — Solves primal and dual problems simultaneously
53-
2. **Mehrotra predictor-corrector** — Two-step Newton method for faster convergence
54-
3. **Normal equations** — Reduces the KKT system to a smaller symmetric system
55-
4. **Cholesky decomposition** — Efficiently solves the symmetric linear system
52+
**The barrier idea:** Add a logarithmic penalty for approaching boundaries:
5653

57-
Each iteration requires O(n³) work for the linear solve, but converges in O(√n) iterations.
54+
```text
55+
minimize c·x - μ Σ log(xᵢ)
56+
```
57+
58+
As μ→0, the solution approaches the true optimum. But we don't solve this directly.
59+
60+
**Primal-dual formulation:** Instead of just the primal problem, we solve primal and dual simultaneously. The optimality conditions (KKT) are:
61+
62+
```text
63+
Ax = b (primal feasibility)
64+
A'y + z = c (dual feasibility)
65+
xᵢzᵢ = 0 (complementarity)
66+
x, z ≥ 0
67+
```
68+
69+
We relax complementarity to xᵢzᵢ = μ and drive μ→0.
70+
71+
**Newton's method:** Each iteration solves a linear system (the KKT system) to find a direction, then takes a step while staying positive. The magic is that convergence is polynomial—typically 20-50 iterations regardless of problem size.
72+
73+
**Mehrotra predictor-corrector:** Two Newton steps per iteration. First a "predictor" step toward the boundary, then a "corrector" step that recenters. Nearly doubles the practical speed.
74+
75+
**Normal equations:** The 3×3 block KKT system reduces to a smaller m×m system (A D A'), solved via Cholesky decomposition.
76+
77+
For the theory, see [Interior Point Methods on Wikipedia](https://en.wikipedia.org/wiki/Interior-point_method) or Nocedal & Wright's *Numerical Optimization*.
5878

5979
## Simplex vs Interior Point
6080

docs/algorithms/linear-programming/solve-lp.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,39 @@ result = solve_lp(c, [[-1, -1]], [-4])
6060
result = solve_lp(c, [[1, 1], [-1, -1]], [4, -4])
6161
```
6262

63+
## How It Works
64+
65+
The simplex algorithm exploits a key insight: the optimal solution to a linear program always occurs at a vertex (corner) of the feasible region. Instead of searching the entire space, we hop from vertex to vertex, always improving the objective.
66+
67+
**The geometry:** Your constraints define a convex polytope in n-dimensional space. Each vertex is where n constraints meet. The objective function defines a direction—we're looking for the vertex furthest in that direction.
68+
69+
**The algebra:** We convert to standard form with slack variables:
70+
71+
```text
72+
minimize c·x
73+
subject to Ax + s = b, x ≥ 0, s ≥ 0
74+
```
75+
76+
Each vertex corresponds to a *basic feasible solution*—setting n variables to zero and solving for the rest. The algorithm:
77+
78+
1. Start at a vertex (basic feasible solution)
79+
2. Look at adjacent vertices (one pivot away)
80+
3. Move to a neighbor with better objective value
81+
4. Repeat until no improvement possible → optimal
82+
83+
**The pivot:** Each iteration picks an entering variable (improves objective) and leaving variable (maintains feasibility), then updates the tableau. This is the "walking along edges" part.
84+
85+
**Two-phase method:** If the origin isn't feasible (some constraints violated), Phase I finds a feasible starting vertex using artificial variables. Phase II then optimizes.
86+
87+
**Bland's rule:** Prevents cycling (revisiting the same vertex) by always picking the smallest index when ties occur.
88+
89+
For the full algorithm, see [Linear Programming on Wikipedia](https://en.wikipedia.org/wiki/Simplex_algorithm) or the classic textbook by Chvátal.
90+
6391
## Complexity
6492

65-
- **Time:** O(exponential worst case, polynomial average case)
66-
- **Guarantees:** Finds the exact optimum for LP problems
93+
- **Time:** O(2^n) worst case, but O(n²m) average on random instances
94+
- **Space:** O(nm) for the tableau
95+
- **Guarantees:** Finds the exact global optimum (not approximate)
6796

6897
## Tips
6998

docs/algorithms/metaheuristics/anneal.md

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,25 @@ result = anneal(initial, obj, neighbors, cooling=logarithmic_cooling(c=1.0))
7676

7777
## How It Works
7878

79-
1. Start with initial solution at high temperature
80-
2. Generate random neighbor
81-
3. If neighbor is better, accept it
82-
4. If neighbor is worse, accept with probability exp(-delta/T)
83-
5. Reduce temperature according to cooling schedule
84-
6. Stop when temperature drops below `min_temp` or `max_iter` reached
79+
**The metallurgy analogy:** In real annealing, you heat metal until atoms move freely, then cool slowly so atoms settle into a low-energy crystal structure. Cool too fast and you get brittle metal with defects (stuck in local minimum). The algorithm mimics this.
80+
81+
**The math:** Accept worse solutions with probability:
82+
83+
```text
84+
P(accept) = exp(-Δcost / temperature)
85+
```
86+
87+
At high temperature, this is near 1—accept almost anything. At low temperature, this approaches 0—only accept improvements. The exponential form comes from statistical mechanics (Boltzmann distribution).
88+
89+
**Why it works:** Early on, high temperature lets you escape local optima by accepting worse moves. As you cool, you become more selective, converging toward a good solution. The probability of accepting a bad move depends on *how bad*—small backward steps are more likely than large ones.
90+
91+
**The algorithm:**
92+
93+
1. Start at high temperature with initial solution
94+
2. Pick a random neighbor
95+
3. If better, accept. If worse, accept with probability exp(-Δ/T)
96+
4. Cool down: T ← T × cooling_rate
97+
5. Repeat until cold or max iterations
8598

8699
## Reproducibility
87100

solvor/dijkstra.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
1717
For negative edges use bellman_ford, Dijkstra's negativity was legendary,
1818
just not in his algorithm. For unweighted graphs use bfs (simpler).
19-
With a good distance estimate, use astar.
19+
With a good distance estimate, use a_star.
2020
"""
2121

2222
from collections.abc import Callable, Iterable

0 commit comments

Comments
 (0)