maptor.solution#

Classes for working with optimization results.

class maptor.solution.Solution(raw_solution, problem, auto_summary=True)[source]#

Bases: object

Optimal control solution with comprehensive data access and analysis capabilities.

Provides unified interface for accessing optimization results, trajectories, solver diagnostics, mesh information, and adaptive refinement data. Supports both single-phase and multiphase problems with automatic data concatenation.

Data Access Patterns:

Mission-wide access (concatenates all phases): - solution[“variable_name”] - Variable across all phases - solution[“time_states”] - State time points across all phases - solution[“time_controls”] - Control time points across all phases

Phase-specific access: - solution[(phase_id, “variable_name”)] - Variable in specific phase - solution[(phase_id, “time_states”)] - State times in specific phase - solution[(phase_id, “time_controls”)] - Control times in specific phase

Existence checking: - “variable_name” in solution - Check mission-wide variable - (phase_id, “variable”) in solution - Check phase-specific variable

Examples:

Basic solution workflow:

>>> solution = mtor.solve_adaptive(problem)
>>> if solution.status["success"]:
...     print(f"Objective: {solution.status['objective']:.6f}")
...     solution.plot()

Mission-wide data access:

>>> altitude_all = solution["altitude"]       # All phases concatenated
>>> velocity_all = solution["velocity"]       # All phases concatenated
>>> state_times_all = solution["time_states"] # All phase state times

Phase-specific data access:

>>> altitude_p1 = solution[(1, "altitude")]   # Phase 1 only
>>> velocity_p2 = solution[(2, "velocity")]   # Phase 2 only
>>> state_times_p1 = solution[(1, "time_states")]

Data extraction patterns:

>>> # Final/initial values
>>> final_altitude = solution["altitude"][-1]
>>> initial_velocity = solution["velocity"][0]
>>> final_mass_p1 = solution[(1, "mass")][-1]
>>>
>>> # Extrema
>>> max_altitude = max(solution["altitude"])
>>> min_thrust_p2 = min(solution[(2, "thrust")])

Variable existence checking:

>>> if "altitude" in solution:
...     altitude_data = solution["altitude"]
>>> if (2, "thrust") in solution:
...     thrust_p2 = solution[(2, "thrust")]

Phase information access:

>>> for phase_id, phase_data in solution.phases.items():
...     duration = phase_data["times"]["duration"]
...     state_names = phase_data["variables"]["state_names"]

Solution validation:

>>> status = solution.status
>>> if status["success"]:
...     objective = status["objective"]
...     mission_time = status["total_mission_time"]
... else:
...     print(f"Failed: {status['message']}")
Parameters:
  • raw_solution (OptimalControlSolution | None)

  • problem (ProblemProtocol | None)

  • auto_summary (bool)

__init__(raw_solution, problem, auto_summary=True)[source]#

Initialize solution wrapper from raw multiphase optimization results.

Args:

raw_solution: Raw optimization results from solver problem: Problem protocol instance auto_summary: Whether to automatically display comprehensive summary (default: True)

Parameters:
  • raw_solution (OptimalControlSolution | None)

  • problem (ProblemProtocol | None)

  • auto_summary (bool)

Return type:

None

property status: dict[str, Any]#

Complete solution status and optimization results.

Provides comprehensive optimization outcome information including success status, objective value, and mission timing. Essential for solution validation and performance assessment.

Returns:

Dictionary containing complete status information:

  • success (bool): Optimization success status

  • message (str): Detailed solver status message

  • objective (float): Final objective function value

  • total_mission_time (float): Sum of all phase durations

Examples:

Success checking:

>>> if solution.status["success"]:
...     print("Optimization successful")

Objective extraction:

>>> objective = solution.status["objective"]
>>> mission_time = solution.status["total_mission_time"]

Error handling:

>>> status = solution.status
>>> if not status["success"]:
...     print(f"Failed: {status['message']}")
...     print(f"Objective: {status['objective']}")  # May be NaN

Status inspection:

>>> print(f"Success: {solution.status['success']}")
>>> print(f"Message: {solution.status['message']}")
>>> print(f"Objective: {solution.status['objective']:.6e}")
>>> print(f"Mission time: {solution.status['total_mission_time']:.3f}")
property phases: dict[int, dict[str, Any]]#

Comprehensive phase information and data organization.

Provides detailed data for each phase including timing, variables, mesh configuration, and trajectory arrays. Essential for understanding multiphase structure and accessing phase-specific information.

Returns:

Dictionary mapping phase IDs to phase data:

Phase data structure:

  • times (dict): Phase timing
    • initial (float): Phase start time

    • final (float): Phase end time

    • duration (float): Phase duration

  • variables (dict): Variable information
    • state_names (list): State variable names

    • control_names (list): Control variable names

    • num_states (int): Number of states

    • num_controls (int): Number of controls

  • mesh (dict): Mesh configuration
    • polynomial_degrees (list): Polynomial degree per interval

    • mesh_nodes (FloatArray): Mesh node locations

    • num_intervals (int): Total intervals

  • time_arrays (dict): Time coordinates
    • states (FloatArray): State time points

    • controls (FloatArray): Control time points

  • integrals (float | FloatArray | None): Integral values

Examples:

Phase iteration:

>>> for phase_id, phase_data in solution.phases.items():
...     print(f"Phase {phase_id}")

Timing information:

>>> phase_1 = solution.phases[1]
>>> duration = phase_1["times"]["duration"]
>>> start_time = phase_1["times"]["initial"]
>>> end_time = phase_1["times"]["final"]

Variable information:

>>> variables = solution.phases[1]["variables"]
>>> state_names = variables["state_names"]     # ["x", "y", "vx", "vy"]
>>> control_names = variables["control_names"] # ["thrust_x", "thrust_y"]
>>> num_states = variables["num_states"]       # 4
>>> num_controls = variables["num_controls"]   # 2

Mesh information:

>>> mesh = solution.phases[1]["mesh"]
>>> degrees = mesh["polynomial_degrees"]       # [6, 8, 6]
>>> intervals = mesh["num_intervals"]          # 3
>>> nodes = mesh["mesh_nodes"]                 # [-1, -0.5, 0.5, 1]

Time arrays:

>>> time_arrays = solution.phases[1]["time_arrays"]
>>> state_times = time_arrays["states"]        # State time coordinates
>>> control_times = time_arrays["controls"]    # Control time coordinates

Integral values:

>>> integrals = solution.phases[1]["integrals"]
>>> if isinstance(integrals, float):
...     single_integral = integrals             # Single integral
>>> else:
...     multiple_integrals = integrals          # Array of integrals
property parameters: dict[str, Any]#

Static parameter optimization results and information.

Always returns valid parameter dictionary with empty structure if no parameters were defined

Returns:

Parameter information dictionary:

  • values (FloatArray): Optimized parameter values (empty array if no parameters)

  • names (list[str] | None): Parameter names if available

  • count (int): Number of static parameters (0 if no parameters)

Examples:

Parameter existence check:

>>> if solution.parameters["count"] > 0:
...     print("Problem has static parameters")

Parameter access:

>>> params = solution.parameters
>>> if params["count"] > 0:
...     values = params["values"]        # [500.0, 1500.0, 0.1]
...     count = params["count"]          # 3
...     names = params["names"]          # ["mass", "thrust", "drag"] or None

Named parameter access:

>>> params = solution.parameters
>>> if params["names"] and params["count"] > 0:
...     for name, value in zip(params["names"], params["values"]):
...         print(f"{name}: {value:.6f}")

Unnamed parameter access:

>>> params = solution.parameters
>>> for i in range(params["count"]):
...     value = params["values"][i]
...     print(f"Parameter {i}: {value:.6f}")

No parameters case:

>>> if solution.parameters["count"] == 0:
...     print("No static parameters in problem")
property adaptive: dict[str, Any]#

Comprehensive adaptive algorithm performance data and benchmarking metrics.

Provides complete adaptive mesh refinement data including convergence status, error estimates, refinement statistics, and iteration-by-iteration benchmarking arrays. Only available for adaptive solver solutions.

Returns:

Adaptive algorithm data dictionary with comprehensive benchmarking arrays:

Algorithm Status: - converged (bool): Algorithm convergence status - iterations (int): Total refinement iterations performed - target_tolerance (float): Target error tolerance - phase_converged (dict): Per-phase convergence status - final_errors (dict): Final error estimates per phase per interval - gamma_factors (dict): Normalization factors per phase

Complete Benchmarking Data: - iteration_history (dict): Raw per-iteration algorithm state - benchmark (dict): Processed mission-wide benchmark arrays - phase_benchmarks (dict): Per-phase benchmark arrays

Benchmark Array Structure (both mission-wide and per-phase): - mesh_iteration (list[int]): Iteration numbers [0, 1, 2, …] - estimated_error (list[float]): Error estimates [1e-2, 1e-3, 1e-5, …] - collocation_points (list[int]): Total collocation points [50, 75, 100, …] - mesh_intervals (list[int]): Total mesh intervals [10, 15, 20, …] - polynomial_degrees (list[list[int]]): Polynomial degrees per interval - refinement_strategy (list[dict]): Refinement actions per iteration

Raises:
RuntimeError: If no adaptive data available. This typically means

solve_fixed_mesh() was used instead of solve_adaptive().

Examples:

Safe adaptive data access:

>>> try:
...     adaptive = solution.adaptive
...     converged = adaptive["converged"]
...     iterations = adaptive["iterations"]
... except RuntimeError:
...     print("Fixed mesh solution - no adaptive data available")

Complete benchmark arrays:

>>> adaptive = solution.adaptive  # May raise RuntimeError
>>> benchmark = adaptive["benchmark"]
>>> iterations = benchmark["mesh_iteration"]         # [0, 1, 2, 3]
>>> errors = benchmark["estimated_error"]            # [1e-2, 1e-3, 1e-5, 1e-7]
>>> points = benchmark["collocation_points"]         # [50, 75, 100, 150]

Phase-specific benchmark data:

>>> phase_benchmarks = solution.adaptive["phase_benchmarks"]
>>> phase1_data = phase_benchmarks[1]
>>> phase1_errors = phase1_data["estimated_error"]

Built-in analysis methods:

>>> try:
...     solution.print_benchmark_summary()
...     solution.plot_refinement_history(phase_id=1)
... except RuntimeError:
...     print("No adaptive data for analysis")
plot(phase_id=None, *variable_names, figsize=(12.0, 8.0), show_phase_boundaries=True)[source]#

Plot solution trajectories with comprehensive customization options.

Creates trajectory plots with automatic formatting, phase boundaries, and flexible variable selection. Supports both single-phase and multiphase visualization with professional styling.

Return type:

None

Parameters:
Args:
phase_id: Phase selection:
  • None: Plot all phases (default)

  • int: Plot specific phase only

variable_names: Variable selection:
  • Empty: Plot all variables

  • Specified: Plot only named variables

figsize: Figure size tuple (width, height)

show_phase_boundaries: Display vertical lines at phase transitions

Examples:

Basic plotting:

>>> solution.plot()  # All variables, all phases

Specific phase:

>>> solution.plot(phase_id=1)  # Phase 1 only

Selected variables:

>>> solution.plot(phase_id=None, "altitude", "velocity", "thrust")

Custom formatting:

>>> solution.plot(
...     figsize=(16, 10),
...     show_phase_boundaries=True
... )

Phase-specific variables:

>>> solution.plot(1, "x_position", "y_position")  # Phase 1 positions

No phase boundaries:

>>> solution.plot(show_phase_boundaries=False)
summary(comprehensive=True)[source]#

Display solution summary with comprehensive details and diagnostics.

Prints detailed overview including solver status, phase information, mesh details, and adaptive algorithm results. Essential for solution validation and performance analysis.

Return type:

None

Parameters:

comprehensive (bool)

Args:
comprehensive: Summary detail level:
  • True: Full detailed summary (default)

  • False: Concise key information only

Examples:

Full summary:

>>> solution.summary()  # Comprehensive details

Concise summary:

>>> solution.summary(comprehensive=False)  # Key information only

Manual summary control:

>>> # Solve without automatic summary
>>> solution = mtor.solve_adaptive(problem, show_summary=False)
>>> # Display summary when needed
>>> solution.summary()

Conditional summary:

>>> if solution.status["success"]:
...     solution.summary()
... else:
...     solution.summary(comprehensive=False)  # Brief failure info
plot_refinement_history(phase_id, figsize=(12, 6), transform_domain=None)[source]#

Visualize adaptive mesh refinement history for research analysis.

Creates mesh point distribution evolution plot showing both mesh interval boundaries (red circles) and interior collocation points (black dots).

Return type:

None

Parameters:
Args:

phase_id: Phase to visualize figsize: Figure dimensions (width, height) transform_domain: Transform from [-1,1] to physical domain (min, max)

print_benchmark_summary()[source]#

Display professional adaptive mesh refinement benchmark analysis.

Provides comprehensive performance metrics, convergence analysis, refinement strategies, and research integrity verification suitable for academic comparison with established pseudospectral optimal control methods.

Return type:

None

Examples:

Complete benchmark analysis:

>>> solution = mtor.solve_adaptive(problem)
>>> solution.print_benchmark_summary()

Access raw benchmark data:

>>> benchmark_data = solution.adaptive["benchmark"]
>>> iterations = benchmark_data["mesh_iteration"]
>>> errors = benchmark_data["estimated_error"]

Phase-specific analysis:

>>> phase_data = solution.adaptive["phase_benchmarks"][1]
>>> solution.plot_refinement_history(phase_id=1)