A Solver for Non-Linear Optimization

Operations Research has been a passion of mine for years. There's something deeply satisfying about taking a real-world problem, modeling it mathematically, and watching an algorithm find the optimal solution. This led me to build an open-source OR library that handles linear programming via Google's solver.

But LP has limits. Many real problems are non-linear, and that's where things get interesting.

The Mesh Solver

I've been developing a new solver that operates in continuous space using iterative refinement. It evaluates candidate solutions directly, perturbs the best ones, and converges toward the optimum. This approach handles non-linear objectives and constraints naturally—no linearization tricks required. The output is a list of best candidates as well as timing information.

The solver works well for problems with a modest number of variables where traditional LP struggles. It's now hosted as a service that I use for my own personal optimization work & experiments.

Example: Pressure Vessel Design

Consider designing a cylindrical pressure vessel with hemispherical heads. We want to minimize manufacturing cost while meeting structural requirements.

Variables:

- Shell thickness (Ts) — discrete increments of 0.0625"
- Head thickness (Th) — discrete increments of 0.0625"
- Inner radius (R)
- Cylinder length (L)

Objective: Minimize total cost (material + welding + overhead)

Constraints: Minimum thickness for pressure rating, minimum volume capacity, maximum length

The cost function is non-linear (products of variables, squared terms), and the thickness variables are discrete. This is exactly where the mesh solver excels.

fsharp
// Objective with penalty constraints
// Note: Using integer indices for thickness because manufacturing constraints
// require discrete thickness values (multiples of 0.0625 inches)
let pressureVessel =
    <@
        fun (v: Vector) ->
            // Round to nearest integer to ensure discrete thickness multiples
            let tsIdx = round v.[0]
            let thIdx = round v.[1]
            let ts = tsIdx * 0.0625 // Shell thickness (inches) from index
            let th = thIdx * 0.0625 // Head thickness (inches) from index
            let r = v.[2] // Inner radius (inches)
            let l = v.[3] // Cylindrical length (inches)

// Cost function components
let materialCost = 0.6224 ts r * l // Shell material
let headCost = 1.7781 th r * r // Hemispherical heads
let weldingCost = 3.1661 ts ts * l // Welding seams
let fixedCost = 19.84 ts ts * r // Manufacturing overhead

let cost = materialCost + headCost + weldingCost + fixedCost

// Constraints in form g(x) <= 0
let g1 = -ts + 0.0193 * r // Min shell thickness for pressure
let g2 = -th + 0.00954 * r // Min head thickness for pressure
let g3 = -3.14159 r r l - 4.18879 r r r + 1296000.0 // Min volume
let g4 = l - 240.0 // Max length constraint

let penalty = 1e6

let violation =
(if g1 > 0.0 then g1 * g1 else 0.0)
+ (if g2 > 0.0 then g2 * g2 else 0.0)
+ (if g3 > 0.0 then g3 * g3 else 0.0)
+ (if g4 > 0.0 then g4 * g4 else 0.0)

cost + penalty * violation
@>

let meshParams =
{ Channels = 4096
MaxIterations = 8192
Order = SortOrder.Minimum
Tolerance = 0.001
Bounds =
[ "TsIdx", [ 1.0; 99.0 ] // TsIdx: thickness index (1-99)
"ThIdx", [ 1.0; 99.0 ] // ThIdx: thickness index (1-99)
"R", [ 10.0; 200.0 ] // R: practical radius range
"L", [ 10.0; 200.0 ] ] // L: practical length range
}

// Run mesh solver
let response = Cruiser.solveMesh meshParams pressureVessel client
printfn "ACCEPTED: %b" (response.Message.Value = "accepted")
printfn "CorrelationID: %s" response.CorrelationID.Value

This sends a problem to the service and solves the given problem asynchronously. With the correlationID, you can retrieve results when you need them.

fsharp
let result: MeshResponse = Cruiser.fetchMeshResult id (CancellationToken()) client

// get the best answer
let vars, cost = result.Results.Value[0]

// Convert indices to actual thickness for comparison
let actualTs = round vars[0] * 0.0625
let actualTh = round vars[1] * 0.0625

printfn "cost: %f " cost
printfn "Ts: %f [index: %f]" actualTs (round vars[0])
printfn "Th: %f [index: %f]" actualTh (round vars[1])
printfn "R: %f " vars[2]
printfn "L: %f " vars[3]

client |> Cruiser.shutdown

Architecture

The solver is built on an Akka.NET actor system, extending my existing OR library with a distributed, fault-tolerant architecture. For standard LP problems, the library still delegates to Google's solver. But now it can also dispatch non-linear problems to remote actors running the mesh solver and Nelder-Mead implementations.

The actor model provides natural concurrency—each solver instance runs independently, processing multiple optimization problems in parallel. Actor supervision hierarchies handle failures gracefully, and the routing pools scale computation across available resources. Client applications communicate with the remote solver service via Akka.NET remoting, submitting problems asynchronously and retrieving results when ready.

This separation keeps the core library lightweight while enabling heavier computation server-side. The same modeling interface now covers a broader problem space—from linear programs to complex non-linear optimizations—all transparently distributed across the actor system.

Try It?

I host this primarily to solve my own problems—scheduling, resource allocation, engineering design. But if you're working on something interesting and want to experiment, reach out. Contact information is available on this site.

The mesh solver won't replace commercial tools for large-scale LP. But for non-linear problems with moderate dimensionality, it's a capable alternative that I'm continuing to refine.