Replies: 2 comments
-
|
@jbcaillau @PierreMartinon @joseph-gergaud Un peu de lecture. |
Beta Was this translation helpful? Give feedback.
0 replies
-
|
@ocots remember that for control-toolbox/CTParser.jl#209 |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
DOCP Architecture Audit Report
Date: 2026-01-27
Author: CTModels Analysis Team
Status: 📊 Deep Analysis
Scope:
DiscretizedOptimalControlProblemand its integration with CTDirectTable of Contents
Executive Summary
This audit analyzes the current DOCP (Discretized Optimal Control Problem) architecture in CTModels and its usage in CTDirect. The architecture implements a Builder Pattern where discretization produces a DOCP containing 4 encapsulated builders (ADNLP/Exa model/solution builders).
Key Findings
Current Architecture
Pipeline Overview
Current DOCP Structure & Relationships
This diagram illustrates how the
DOCPstruct acts as a container for backend-specific builders, maintaining the link back to the originalOCP.erDiagram OCP ||--|| DOCP : "discretized into" DOCP ||--|| ADNLPModelBuilder : "contains" DOCP ||--|| ExaModelBuilder : "contains" DOCP ||--|| ADNLPSolutionBuilder : "contains" DOCP ||--|| ExaSolutionBuilder : "contains" ADNLPModeler ||--|| ADNLPModelBuilder : "uses" ADNLPModelBuilder ||--|| ADNLPModel : "produces" Solver ||--|| ADNLPModel : "solves" Solver ||--|| NLPSolution : "returns" ADNLPSolutionBuilder ||--|| NLPSolution : "consumes" ADNLPSolutionBuilder ||--|| OptimalControlSolution : "reconstructs" OCP { AbstractOptimalControlProblem type } DOCP { OCP optimal_control_problem ADNLPModelBuilder adnlp_model_builder ExaModelBuilder exa_model_builder ADNLPSolutionBuilder adnlp_solution_builder ExaSolutionBuilder exa_solution_builder } ADNLPModelBuilder { Function closure } ExaModelBuilder { Function closure } ADNLPSolutionBuilder { Function closure } ExaSolutionBuilder { Function closure }Code Detail:
DiscretizedOptimalControlProblemBuilder Pattern
The builders are callable wrappers around closures:
CTDirect Implementation (Collocation)
In CTDirect, the
Collocationdiscretizer defines 4 internal functions that become the builders:Modeler Flow
Evaluation against Development Standards
SOLID Principles Assessment
Type Stability Assessment
ADNLPModelBuilderExaModelBuilderget_*_builderDocumentation Assessment
Strengths Analysis
1. Signature Encapsulation ✅
The builder pattern successfully hides complex function signatures:
Benefit: CTModels doesn't need to know about backend-specific options.
2. Pre-computation Caching ✅
Discretization data is computed once and captured in closures:
Benefit: Efficiency when calling the same builder multiple times with different initial guesses.
3. Type Parametric Builders ✅
Builders are parametric on the wrapped function:
Benefit: Compiler can specialize on specific closure types.
4. Clear Contract Interface ✅
The
AbstractOptimizationProblemcontract is well-defined:Benefit: Any problem type implementing these methods works with modelers.
5. Decoupled Solve API ✅
CTSolvers provides a clean, high-level API:
Benefit: User doesn't need to understand the internal plumbing.
Weaknesses Analysis
1. Hard-coded Backend Proliferation ❌
The DOCP struct has fixed fields for exactly 2 backends:
Problem: Adding a third backend (e.g., JuMP, Symbolics-based) requires:
Severity: 🔴 High - Violates Open/Closed principle
2. Discretizer Complexity ❌
CTDirect's Collocation discretizer must define 4 internal functions:
Problem:
Severity: 🟠 Medium-High
3. Mutable State in Discretizer⚠️
The discretizer stores mutable state:
Problem:
Severity: 🟠 Medium
4. Model/Solution Builder Coupling⚠️
Model builder and solution builder are conceptually paired but stored separately:
Problem: Easy to mix incompatible builders.
Comment from project.md:
Severity: 🟡 Low-Medium
5. Closure Opacity⚠️
Builders wrap opaque closures:
Problem:
Severity: 🟡 Low-Medium
6. Redundant Re-computation for Exa⚠️
From CTDirect code:
Problem: Exa model building duplicates some computation.
Severity: 🟡 Low
Alternative Architectures
Alternative A: Minimal DOCP with External Dispatch
Concept: DOCP stores only OCP + Discretizer. Backend selection happens at modeler level via multiple dispatch.
Advantages:
Disadvantages:
erDiagram OCP ||--|| DOCP : "discretized into" DOCP ||--|| Discretizer : "references" Discretizer ||--o{ ADNLPModel : "builds via dispatch" Discretizer ||--o{ ExaModel : "builds via dispatch" ADNLPModeler ||--|| Discretizer : "dispatches on" Solver ||--|| ADNLPModel : "solves" Solver ||--|| NLPSolution : "returns" OCP { AbstractOptimalControlProblem type } DOCP { OCP optimal_control_problem Discretizer discretizer } Discretizer { Symbol method Any options }Alternative B: Registry-based Builder Selection
Concept: DOCP stores builders in a Dict/NamedTuple by backend ID.
Advantages:
Strategies.id(typeof(modeler))Disadvantages:
id(already the case for ADNLPModeler)erDiagram OCP ||--|| DOCP : "discretized into" DOCP ||--|| BuilderRegistry : "contains" BuilderRegistry ||--o{ BackendPair : "stores" BackendPair ||--|| ModelBuilder : "has" BackendPair ||--|| SolutionBuilder : "has" ADNLPModeler ||--|| BuilderRegistry : "queries by :adnlp" ModelBuilder ||--|| ADNLPModel : "produces" SolutionBuilder ||--|| OptimalControlSolution : "reconstructs" OCP { AbstractOptimalControlProblem type } DOCP { OCP optimal_control_problem NamedTuple builders } BuilderRegistry { BackendPair adnlp BackendPair exa BackendPair any_new_backend } BackendPair { ModelBuilder model SolutionBuilder solution }Alternative C: Strategy Pattern for Backend Selection
Concept: DOCP stores a single "backend strategy" that handles both model and solution building.
Advantages:
Disadvantages:
erDiagram OCP ||--|| DOCP : "discretized into" DOCP ||--|| DiscretizationData : "contains" DOCP ||--o{ AbstractNLPBackend : "stores tuple of" AbstractNLPBackend ||--|| ADNLPBackend : "subtype" AbstractNLPBackend ||--|| ExaBackend : "subtype" ADNLPBackend ||--|| ModelBuilder : "has" ADNLPBackend ||--|| SolutionBuilder : "has" ADNLPModeler ||--|| ADNLPBackend : "finds by type" ModelBuilder ||--|| ADNLPModel : "produces" OCP { AbstractOptimalControlProblem type } DOCP { OCP optimal_control_problem DiscretizationData discretization_data Tuple backends } DiscretizationData { Symbol scheme Int grid_size Vector time_grid Bounds bounds } ADNLPBackend { ModelBuilder model_builder SolutionBuilder solution_builder } ExaBackend { ModelBuilder model_builder SolutionBuilder solution_builder }Alternative D: Lazy Builder Construction
Concept: DOCP stores only OCP + discretization data. Builders are constructed on-demand.
Advantages:
Disadvantages:
erDiagram OCP ||--|| DOCP : "discretized into" DOCP ||--|| DiscretizationData : "contains" ADNLPModeler ||..|| BuilderFactory : "calls" BuilderFactory ||--|| ModelBuilder : "creates on-demand" ModelBuilder ||--|| DiscretizationData : "uses" ModelBuilder ||--|| ADNLPModel : "produces" Solver ||--|| ADNLPModel : "solves" SolutionFactory ||--|| OptimalControlSolution : "reconstructs" OCP { AbstractOptimalControlProblem type } DOCP { OCP optimal_control_problem DiscretizationData discretization_data } DiscretizationData { Symbol scheme Int grid_size Vector time_grid Bounds bounds Flags flags } BuilderFactory { Function make_model_builder Function make_solution_builder }Alternative E: Hybrid Approach (Best of Both Worlds)
Concept: DOCP stores minimal discretization data + optional cached builders.
Advantages:
Disadvantages:
erDiagram OCP ||--|| DOCP : "discretized into" DOCP ||--|| DiscretizationData : "contains" DOCP ||--o| BuilderCache : "optionally has" BuilderCache ||--o{ CachedBuilder : "stores" ADNLPModeler ||--|| DOCP : "queries" DOCP ||..|| BuilderFactory : "calls if not cached" BuilderFactory ||--|| ModelBuilder : "creates" ModelBuilder ||--|| ADNLPModel : "produces" CachedBuilder ||--|| ModelBuilder : "wraps" OCP { AbstractOptimalControlProblem type } DOCP { OCP optimal_control_problem DiscretizationData discretization_data Union_Nothing_NamedTuple builder_cache } DiscretizationData { Symbol scheme Int grid_size Vector time_grid Bounds bounds Flags flags } BuilderCache { ModelBuilder adnlp_model SolutionBuilder adnlp_solution ModelBuilder exa_model SolutionBuilder exa_solution }Comparative Evaluation
Evaluation Matrix
Score Summary (1-5, higher is better)
Recommendations
Short-term Recommendations (Low Effort)
Medium-term Recommendations (Medium Effort)
Important
Recommended: Migrate to Alternative B (Registry-based)
Rationale:
Migration path:
DiscretizedOptimalControlProblemV2with registry approachDiscretizedOptimalControlProblemLong-term Vision: Unified Extensible Architecture
The long-term vision synthesizes the best elements from all proposed alternatives into a coherent, extensible architecture. This "Alternative F" represents the ideal target state combining:
ADNLPModeler)Core Architecture
erDiagram OCP ||--|| DOCP : "discretized into" DOCP ||--|| DiscretizationData : "contains shared data" DOCP ||--|| BuilderRegistry : "has backends by id" BuilderRegistry ||--o{ BackendEntry : "stores" BackendEntry ||--|| BackendId : "keyed by" BackendEntry ||--o| BuilderPair : "cached (optional)" BackendEntry ||--|| BuilderFactory : "lazily creates" AbstractModeler ||--|| BackendId : "has id()" AbstractModeler ||--|| DOCP : "queries" BuilderFactory ||--|| BuilderPair : "produces" BuilderPair ||--|| ModelBuilder : "has" BuilderPair ||--|| SolutionBuilder : "has" ModelBuilder ||--|| NLPModel : "creates" SolutionBuilder ||--|| OptimalControlSolution : "reconstructs" OCP { AbstractOptimalControlProblem type } DOCP { OCP optimal_control_problem DiscretizationData data BuilderRegistry registry } DiscretizationData { Symbol scheme Int grid_size Vector time_grid Bounds bounds Flags flags Any precomputed_matrices } BackendEntry { Symbol id Union_Nothing_BuilderPair cached BuilderFactory factory } BuilderPair { ModelBuilder model SolutionBuilder solution }Key Design Principles
Separation of Concerns
DiscretizationData: Pure data, no closures. Inspectable, serializable.BuilderFactory: Logic for constructing builders from data.BuilderPair: Paired model + solution builders (cannot mismatch).Extensibility via Registration
BuilderFactorymethod for the backendStrategies.idfor the corresponding modelerDOCPstruct is required.Lazy Construction with Optional Caching
get_builderis called.Type-Safe Lookup via
Strategies.idImplementation Sketch
Handling Different Builder Signatures
A key design challenge is that different backends have different call signatures:
ADNLPModelBuilderbuilder(initial_guess; kwargs...)ExaModelBuilderbuilder(BaseType, initial_guess; kwargs...)Current Implementation: The modeler knows the builder signature:
Long-term Solution: The modeler remains responsible for knowing how to invoke its builder. The key insight is that:
Here's the complete modeler implementation for both backends:
Key Design Decisions:
No Unified Builder Interface: We don't force all builders to have the same signature. This would require awkward workarounds for ExaModeler's
BaseType.Modeler Owns Invocation Logic: Each modeler knows exactly how to call its builder. This is cleaner than trying to abstract away the differences.
Registry is Signature-Agnostic: The registry just stores and retrieves builders. It doesn't care about their call signatures.
Type Safety via NamedTuple Keys: The
Strategies.idmechanism ensures the correct builder is retrieved for each modeler type.sequenceDiagram participant User participant ADNLPModeler participant ExaModeler participant DOCP participant Registry participant Builder User->>ADNLPModeler: modeler(prob, x0) ADNLPModeler->>DOCP: get_model_builder(prob, modeler) DOCP->>Registry: registry[:adnlp].model Registry-->>ADNLPModeler: ADNLPModelBuilder ADNLPModeler->>Builder: builder(x0, opts) Builder-->>User: ADNLPModel User->>ExaModeler: modeler(prob, x0) ExaModeler->>DOCP: get_model_builder(prob, modeler) DOCP->>Registry: registry[:exa].model Registry-->>ExaModeler: ExaModelBuilder ExaModeler->>Builder: builder(Float32, x0, opts) Builder-->>User: ExaModel_Float32Migration Strategy
DiscretizationDatatype alongside current closuresBuilderRegistrywith wrapper for old APIadnlp_model_builderfield accessDiscretizationData+ factoriesBenefits Summary
Appendix: Code Sketches
Alternative B Implementation Sketch
End of Audit Report
Beta Was this translation helpful? Give feedback.
All reactions