Building a Flexible Optimizer Framework with Micronaut
Combinatorial optimization problems are fundamental challenges in modern computing, particularly in logistics, network design, and operations research. The Facility Location Problem (FLP) and Traveling Salesman Problem (TSP) represent prime examples of these challenges. In this post, we’ll explore the development of a sophisticated optimizer framework using Micronaut, featuring real-time progress tracking and a modular, extensible architecture.
With the exciting announcement of the free plan for Github Copilot, we’ll leverage AI-assisted development to create our Micronaut Optimizer framework. This powerful combination of tools will help us tackle complex combinatorial optimization problems efficiently.
Background and Motivation
Building on our previous exploration of Optimizing TSP with Genetic Algorithms, this post delves deeper into creating a versatile optimization framework. Our focus will be on the architectural decisions and implementation details that make this framework both powerful and extensible.
Architecture Overview
The architecture of our Micronaut Optimizer framework is designed with modularity and scalability in mind. Below is a high-level architectural diagram that illustrates the key components and their interactions:
Core Components
The framework’s architecture is built around several key components, each serving a specific purpose while maintaining loose coupling for maximum flexibility:
Controller Layer
The controller layer serves as the entry point for HTTP requests, implementing a clean REST API interface. Here’s an example of our FLPController
:
// filepath: src/main/java/io/github/seehiong/controller/FLPController.java
package io.github.seehiong.controller;
@Controller("/flp")
@RequiredArgsConstructor
public class FLPController {
private final FLPService flpService;
@Post(value = "/uploadSolve", produces = MediaType.TEXT_EVENT_STREAM, consumes = MediaType.MULTIPART_FORM_DATA)
public Flux<Object> uploadSolve(@Body FLPInput input, CompletedFileUpload file) throws IOException {
return flpService.uploadSolve(input, file);
}
}
Service Layer
The service layer encapsulates our business logic and orchestrates the optimization process. The FLPService
demonstrates this orchestration:
// filepath: src/main/java/io/github/seehiong/service/FLPService.java
package io.github.seehiong.service;
@Singleton
@RequiredArgsConstructor
public class FLPService {
public Flux<Object> uploadSolve(FLPInput input, CompletedFileUpload file) {
Solver<FLPInput, FLPOutput> solver = (Solver<FLPInput, FLPOutput>) solverFactory.getSolver(input.getProblemType());
return solver.solve(input, (PublishSubject<FLPOutput>) progressSubject);
}
}
Factory Pattern Implementation
Our SolverFactory
implements a clean factory pattern to instantiate appropriate solvers based on the problem type:
// filepath: src/main/java/io/github/seehiong/factory/SolverFactory.java
package io.github.seehiong.factory;
@Singleton
public class SolverFactory {
public Solver<?, ?> getSolver(ProblemType problemType) {
return switch (problemType) {
case TSP -> new TSPSolver();
case TSP_GA -> new TSPGaSolver();
case FLP -> new FLPSolver();
default -> throw new IllegalArgumentException("Unknown problem type: " + problemType);
};
}
}
Solver Interface
The Solver
interface defines the contract that all optimization implementations must fulfill:
// filepath: src/main/java/io/github/seehiong/solver/Solver.java
package io.github.seehiong.solver;
public interface Solver<I extends Input, O extends Output> {
Flux<Object> solve(I input, PublishSubject<O> progressSubject);
}
Reactive Programming Integration
Our framework leverages reactive programming through Project Reactor’s Flux
and RxJava’s PublishSubject
. This enables:
- Real-time progress updates to clients
- Non-blocking execution of optimization algorithms
- Efficient handling of long-running computations
The FLPSolver
implementation showcases this reactive approach:
// filepath: src/main/java/io/github/seehiong/solver/FLPSolver.java
package io.github.seehiong.solver;
public class FLPSolver implements Solver<FLPInput, FLPOutput> {
@Override
public Flux<Object> solve(FLPInput input, PublishSubject<FLPOutput> progressSubject) {
// Optimization logic with reactive progress updates
}
}
Real-Time Visualization
A key feature of our framework is its ability to visualize optimization progress in real-time, providing valuable insights into the solver’s behavior.
Server-Sent Events Implementation
We implement Server-Sent Events (SSE) to stream optimization progress to clients:
// filepath: src/main/java/io/github/seehiong/controller/FLPController.java
package io.github.seehiong.controller;
public class FLPController {
@Get(value = "/progress/{solverId}", produces = MediaType.TEXT_EVENT_STREAM)
public Flowable<Event<FLPOutput>> streamProgress(String solverId) {
return flpService.streamProgress(solverId);
}
}
Interactive Visualization Interface
Our front-end implementation provides real-time visualization of the optimization process:
<!-- filepath: src/main/resources/static/flp-progress.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>FLP Optimization Progress</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<h1>FLP Optimization Progress</h1>
<canvas id="flpChart" width="800" height="600"></canvas>
<script>
const eventSource = new EventSource(`/flp/progress/${solverId}`);
eventSource.onmessage = function (event) {
const data = JSON.parse(event.data);
// Visualization logic
};
</script>
</body>
</html>
Framework in Action
Let’s examine how our framework handles different optimization problems:
Facility Location Problem (FLP)
Consider this sample FLP input:
3 8
100 100 1065.0 1065.0
100 100 1062.0 1062.0
100 500 0.0 0.0
50 1397.0 1397.0
50 1398.0 1398.0
75 1399.0 1399.0
75 586.0 586.0
80 900.0 900.0
80 910.0 910.0
90 1200.0 1200.0
90 1210.0 1210.0
The visualization of the solved FLP problem:
Traveling Salesman Problem (TSP)
For the TSP implementation, we can process complex distance matrices:
{
"problemType": "TSP",
"distanceMatrixConstraint": {
"distanceMatrix": [
[0, 10, 15, 20, 25, 30, 35, 40],
[10, 0, 35, 25, 20, 45, 50, 55],
[15, 35, 0, 30, 10, 40, 45, 60],
[20, 25, 30, 0, 15, 50, 55, 70],
[25, 20, 10, 15, 0, 35, 40, 65],
[30, 45, 40, 50, 35, 0, 25, 70],
[35, 50, 45, 55, 40, 25, 0, 75],
[40, 55, 60, 70, 65, 70, 75, 0]
]
}
}
The optimization result:
Genetic Algorithm TSP Implementation
For larger problems, our genetic algorithm implementation shows impressive results:
The final optimization result:
Future Directions and Conclusion
The Micronaut Optimizer Framework represents a powerful, flexible solution for tackling complex optimization problems. Its reactive architecture and real-time visualization capabilities make it an excellent choice for both research and production environments.
Planned Enhancements
-
Additional Solver Integration
- Support for Vehicle Routing Problems (VRP)
- Implementation of advanced scheduling algorithms
- Integration with quantum computing solvers
-
Performance Optimizations
- Distributed computing support
- GPU acceleration for applicable algorithms
- Advanced caching strategies
-
Visualization Improvements
- Interactive 3D visualizations
- Advanced analytics dashboards
- Real-time performance metrics
-
Architecture Extensions
- Implementation of CP-SAT callbacks
- Enhanced error handling and recovery
- Expanded API documentation
Get Involved
The complete implementation is available on GitHub: Micronaut-Optimizer. We welcome contributions and feedback from the community!