Working with Solution Data#
This comprehensive tutorial covers all of MAPTOR’s solution data extraction capabilities. After completing this guide, you’ll understand how to access any optimization result using MAPTOR’s unified solution interface.
Prerequisites: Complete the Getting Started guide first to understand the basic problem-solving workflow.
Scope: This tutorial covers the complete solution access API, from basic trajectory extraction to comprehensive adaptive algorithm diagnostics.
Solution Access Framework#
MAPTOR provides a unified solution interface for accessing optimization results across all problem types. You systematically extract trajectories, metadata, and diagnostics using consistent access patterns that work for both single-phase and multiphase problems.
import maptor as mtor
# Solve any optimal control problem
solution = mtor.solve_adaptive(problem)
# Access results through unified interface
objective = solution.status["objective"]
trajectory = solution["position"]
final_value = trajectory[-1]
A solution contains all optimization results with automatic data organization, trajectory concatenation, and comprehensive metadata for analysis and visualization.
Status Validation#
Every solution provides complete optimization outcome information through the status property:
Basic Status Access:
# Essential validation
success = solution.status["success"]
objective = solution.status["objective"]
message = solution.status["message"]
Complete Status Information:
# All status components
status = solution.status
success = status["success"] # bool: Optimization success
objective = status["objective"] # float: Final objective value
total_time = status["total_mission_time"] # float: Complete mission duration
message = status["message"] # str: Detailed solver message
Status Validation Patterns:
# Success checking
if solution.status["success"]:
print("Optimization succeeded")
# Objective extraction
optimal_cost = solution.status["objective"]
# Mission timing
duration = solution.status["total_mission_time"]
# Solver diagnostics
solver_info = solution.status["message"]
The status property provides immediate access to optimization outcomes and is essential for determining whether to proceed with data extraction.
Mission-Wide Data Access#
Use string keys to automatically access complete mission trajectories with automatic phase concatenation:
Basic Trajectory Access:
# Complete mission trajectories
time_states = solution["time_states"] # All state time points
time_controls = solution["time_controls"] # All control time points
position = solution["position"] # Complete position trajectory
velocity = solution["velocity"] # Complete velocity trajectory
thrust = solution["thrust"] # Complete control trajectory
Time Coordinate Access:
# State and control time arrays
state_times = solution["time_states"]
control_times = solution["time_controls"]
# Time span analysis
mission_start = state_times[0]
mission_end = state_times[-1]
total_duration = mission_end - mission_start
Variable Trajectory Access:
# State trajectories
altitude = solution["altitude"]
mass = solution["mass"]
angle = solution["angle"]
# Control trajectories
throttle = solution["throttle"]
steering = solution["steering"]
power = solution["power"]
Final Value Extraction:
# Mission endpoints
final_position = solution["position"][-1]
final_velocity = solution["velocity"][-1]
initial_mass = solution["mass"][0]
final_mass = solution["mass"][-1]
String key access automatically concatenates data from all phases containing the specified variable, providing seamless mission-wide analysis.
Phase-Specific Data Access#
Use tuple keys for granular control over individual phase data:
Single Phase Access:
# Phase-specific trajectories
phase1_position = solution[(1, "position")]
phase1_velocity = solution[(1, "velocity")]
phase1_thrust = solution[(1, "thrust")]
# Phase-specific time coordinates
phase1_state_times = solution[(1, "time_states")]
phase1_control_times = solution[(1, "time_controls")]
Multi-Phase Access:
# Access each phase individually
ascent_altitude = solution[(1, "altitude")]
coast_altitude = solution[(2, "altitude")]
descent_altitude = solution[(3, "altitude")]
# Phase-specific controls
ascent_thrust = solution[(1, "thrust")]
coast_thrust = solution[(2, "thrust")] # May be zero
descent_thrust = solution[(3, "thrust")]
Phase Boundary Analysis:
# Phase transition values
phase1_final_mass = solution[(1, "mass")][-1]
phase2_initial_mass = solution[(2, "mass")][0]
# Continuity verification
altitude_transition = solution[(1, "altitude")][-1]
altitude_continuation = solution[(2, "altitude")][0]
Phase Comparison:
# Compare phase characteristics
phase1_duration = len(solution[(1, "time_states")])
phase2_duration = len(solution[(2, "time_states")])
# Phase-specific extrema
max_thrust_p1 = max(solution[(1, "thrust")])
max_thrust_p2 = max(solution[(2, "thrust")])
Tuple access pattern (phase_id, variable_name)
provides complete control over which phase data to extract and enables detailed phase-specific analysis.
Variable Existence Validation#
Safely validate variable availability before accessing solution data:
Basic Existence Checking:
# String key validation
if "altitude" in solution:
altitude_data = solution["altitude"]
# Tuple key validation
if (1, "thrust") in solution:
thrust_data = solution[(1, "thrust")]
Multiple Variable Validation:
# Check multiple variables
required_vars = ["position", "velocity", "thrust"]
available_vars = [var for var in required_vars if var in solution]
# Conditional access
if "fuel_mass" in solution:
fuel_trajectory = solution["fuel_mass"]
else:
print("Fuel mass not tracked in this problem")
Phase-Specific Validation:
# Phase variable existence
if (2, "steering") in solution:
steering_profile = solution[(2, "steering")]
# Multi-phase validation
phases_with_thrust = []
for phase_id in [1, 2, 3]:
if (phase_id, "thrust") in solution:
phases_with_thrust.append(phase_id)
Safe Access Patterns:
# Conditional trajectory extraction
trajectories = {}
for var_name in ["x", "y", "z", "vx", "vy", "vz"]:
if var_name in solution:
trajectories[var_name] = solution[var_name]
# Phase-conditional access
phase_data = {}
for phase_id in range(1, 4):
if (phase_id, "altitude") in solution:
phase_data[phase_id] = solution[(phase_id, "altitude")]
The in
operator works with both string and tuple keys, enabling robust solution processing workflows.
Phase Information Analysis#
The phases
property provides comprehensive metadata for detailed mission analysis:
Basic Phase Information:
# Available phases
phase_ids = list(solution.phases.keys())
num_phases = len(solution.phases)
# Single phase data
phase_data = solution.phases[1]
Timing Information:
# Phase timing
for phase_id, phase_data in solution.phases.items():
times = phase_data["times"]
initial_time = times["initial"]
final_time = times["final"]
duration = times["duration"]
Variable Information:
# Phase variables
for phase_id, phase_data in solution.phases.items():
variables = phase_data["variables"]
state_names = variables["state_names"]
control_names = variables["control_names"]
num_states = variables["num_states"]
num_controls = variables["num_controls"]
Mesh Configuration:
# Mesh details
for phase_id, phase_data in solution.phases.items():
mesh = phase_data["mesh"]
polynomial_degrees = mesh["polynomial_degrees"]
mesh_nodes = mesh["mesh_nodes"]
num_intervals = mesh["num_intervals"]
Time Array Access:
# Direct time array access
for phase_id, phase_data in solution.phases.items():
time_arrays = phase_data["time_arrays"]
state_times = time_arrays["states"]
control_times = time_arrays["controls"]
Integral Values:
# Phase integral extraction
for phase_id, phase_data in solution.phases.items():
integrals = phase_data["integrals"]
if integrals is not None:
if isinstance(integrals, float):
single_integral = integrals
else:
multiple_integrals = integrals
Each phase provides complete timing, variable, mesh, and integral information for comprehensive mission analysis.
Static Parameter Access#
Extract optimized design parameters that remain constant throughout the mission:
Parameter Availability:
# Check parameter existence
if solution.parameters["count"] > 0:
print("Problem includes static parameters")
else:
print("No static parameters")
Basic Parameter Access:
# Parameter extraction - always available
params = solution.parameters
param_values = params["values"] # Always valid (empty array if none)
param_count = params["count"] # Always valid (0 if none)
param_names = params["names"] # Always valid (None if none)
Named Parameter Access:
# With parameter names
params = solution.parameters
if params["names"] and params["count"] > 0:
for name, value in zip(params["names"], params["values"]):
print(f"{name}: {value}")
Unnamed Parameter Access:
# Without parameter names
params = solution.parameters
for i in range(params["count"]):
value = params["values"][i]
print(f"Parameter {i}: {value}")
Parameter Value Extraction:
# Direct value access (when parameters exist)
params = solution.parameters
if params["count"] >= 3:
optimized_mass = params["values"][0]
optimized_thrust = params["values"][1]
design_parameter = params["values"][2]
Static parameters represent optimization variables that remain constant throughout the mission but are determined by the solver.
Adaptive Algorithm Data Access#
Access comprehensive adaptive mesh refinement performance data and benchmarking metrics for adaptive solutions:
Algorithm Status:
# Adaptive solution check
try:
adaptive = solution.adaptive
print("Adaptive solution available")
except RuntimeError:
print("Fixed mesh solution")
Basic Algorithm Information:
# Algorithm status data
try:
adaptive = solution.adaptive
converged = adaptive["converged"]
iterations = adaptive["iterations"]
tolerance = adaptive["target_tolerance"]
except RuntimeError:
print("No adaptive data available")
Per-Phase Convergence Status:
# Phase convergence information
try:
adaptive = solution.adaptive
phase_converged = adaptive["phase_converged"]
for phase_id, status in phase_converged.items():
print(f"Phase {phase_id}: {'Converged' if status else 'Not converged'}")
except RuntimeError:
print("No adaptive data available")
Final Error Estimates:
# Final error estimates per phase per interval
try:
adaptive = solution.adaptive
final_errors = adaptive["final_errors"]
for phase_id, errors in final_errors.items():
for interval_idx, error in enumerate(errors):
print(f"Phase {phase_id} interval {interval_idx}: {error:.2e}")
except RuntimeError:
print("No adaptive data available")
Gamma Normalization Factors:
# Algorithm normalization factors
try:
adaptive = solution.adaptive
gamma_factors = adaptive["gamma_factors"]
for phase_id, factors in gamma_factors.items():
if factors is not None:
print(f"Phase {phase_id} gamma factors available")
except RuntimeError:
print("No adaptive data available")
Benchmark Array Data Access#
Access structured benchmark arrays for performance analysis and research comparison:
Mission-Wide Benchmark Arrays:
# Complete benchmark data structure
try:
adaptive = solution.adaptive
benchmark = adaptive["benchmark"]
# All six benchmark arrays
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, ...]
intervals = benchmark["mesh_intervals"] # [10, 15, 20, 30, ...]
degrees = benchmark["polynomial_degrees"] # [[4,4,4], [4,6,4], [6,6,6], ...]
strategies = benchmark["refinement_strategy"] # [{0:'p', 1:'h'}, {2:'p'}, ...]
except RuntimeError:
print("Fixed mesh solution - no benchmark data available")
Phase-Specific Benchmark Arrays:
# Individual phase benchmark data
try:
adaptive = solution.adaptive
phase_benchmarks = adaptive["phase_benchmarks"]
# Access specific phase data
phase1_data = phase_benchmarks[1]
phase1_iterations = phase1_data["mesh_iteration"] # [0, 1, 2, ...]
phase1_errors = phase1_data["estimated_error"] # [1e-2, 1e-4, 1e-6, ...]
phase1_points = phase1_data["collocation_points"] # [25, 35, 50, ...]
phase1_intervals = phase1_data["mesh_intervals"] # [5, 7, 10, ...]
phase1_degrees = phase1_data["polynomial_degrees"] # [[4,4], [4,6], [6,6], ...]
phase1_strategies = phase1_data["refinement_strategy"] # [{0:'p'}, {1:'h'}, ...]
except RuntimeError:
print("Fixed mesh solution - no benchmark data available")
Benchmark Array Definitions:
# Array content definitions
try:
adaptive = solution.adaptive
benchmark = adaptive["benchmark"]
# mesh_iteration: Iteration sequence numbers starting from 0
# estimated_error: Maximum error estimate across all intervals
# collocation_points: Total collocation points used in mesh
# mesh_intervals: Total number of mesh intervals
# polynomial_degrees: List of polynomial degrees for each interval per iteration
# refinement_strategy: Dictionary mapping interval index to strategy ('p' or 'h')
except RuntimeError:
print("Fixed mesh solution - no benchmark data available")
Benchmark Data Iteration:
# Process all benchmark iterations
try:
adaptive = solution.adaptive
benchmark = adaptive["benchmark"]
for i in range(len(benchmark["mesh_iteration"])):
iteration = benchmark["mesh_iteration"][i]
error = benchmark["estimated_error"][i]
points = benchmark["collocation_points"][i]
intervals = benchmark["mesh_intervals"][i]
degrees = benchmark["polynomial_degrees"][i]
strategy = benchmark["refinement_strategy"][i]
print(f"Iteration {iteration}: Error={error:.2e}, Points={points}")
except RuntimeError:
print("Fixed mesh solution - no benchmark data available")
Multi-Phase Benchmark Comparison:
# Compare benchmark data across phases
try:
adaptive = solution.adaptive
phase_benchmarks = adaptive["phase_benchmarks"]
# Compare final iteration across phases
for phase_id, phase_data in phase_benchmarks.items():
final_error = phase_data["estimated_error"][-1]
final_points = phase_data["collocation_points"][-1]
print(f"Phase {phase_id}: Final error={final_error:.2e}, Points={final_points}")
except RuntimeError:
print("Fixed mesh solution - no benchmark data available")
Raw Iteration History Access#
Access complete algorithm state for each refinement iteration:
Iteration History Structure:
# Complete iteration-by-iteration algorithm state
try:
adaptive = solution.adaptive
history = adaptive["iteration_history"]
# Access specific iteration data
iteration_data = history[2] # Third iteration (0-indexed)
# All available fields per iteration
iteration_num = iteration_data["iteration"]
phase_errors = iteration_data["phase_error_estimates"]
total_points = iteration_data["total_collocation_points"]
phase_points = iteration_data["phase_collocation_points"]
phase_intervals = iteration_data["phase_mesh_intervals"]
phase_degrees = iteration_data["phase_polynomial_degrees"]
mesh_nodes = iteration_data["phase_mesh_nodes"]
refinement_actions = iteration_data["refinement_strategy"]
max_error = iteration_data["max_error_all_phases"]
convergence_status = iteration_data["convergence_status"]
except RuntimeError:
print("Fixed mesh solution - no iteration history available")
Iteration History Processing:
# Process all iterations sequentially
try:
adaptive = solution.adaptive
history = adaptive["iteration_history"]
for iteration in sorted(history.keys()):
data = history[iteration]
print(f"Iteration {iteration}:")
print(f" Total points: {data['total_collocation_points']}")
print(f" Max error: {data['max_error_all_phases']:.2e}")
# Phase-specific data for this iteration
for phase_id in data["phase_error_estimates"].keys():
phase_errors = data["phase_error_estimates"][phase_id]
phase_points = data["phase_collocation_points"][phase_id]
print(f" Phase {phase_id}: {len(phase_errors)} intervals, {phase_points} points")
except RuntimeError:
print("Fixed mesh solution - no iteration history available")
Mesh Evolution Tracking:
# Track mesh node evolution
try:
adaptive = solution.adaptive
history = adaptive["iteration_history"]
for iteration in sorted(history.keys()):
data = history[iteration]
for phase_id, mesh_nodes in data["phase_mesh_nodes"].items():
degrees = data["phase_polynomial_degrees"][phase_id]
print(f"Iteration {iteration}, Phase {phase_id}:")
print(f" Mesh nodes: {len(mesh_nodes)} points")
print(f" Polynomial degrees: {degrees}")
except RuntimeError:
print("Fixed mesh solution - no mesh evolution data")
Built-in Analysis Methods#
Access professional analysis and visualization capabilities:
Comprehensive Benchmark Summary:
# Professional benchmark analysis
try:
solution.print_benchmark_summary()
except RuntimeError:
print("No adaptive data for benchmark summary")
# Concise benchmark information
try:
solution.print_benchmark_summary()
except RuntimeError:
print("No adaptive data for benchmark summary")
Mesh Refinement Visualization:
# Basic mesh evolution plot
try:
solution.plot_refinement_history(phase_id=1)
except RuntimeError:
print("No adaptive data for mesh refinement plot")
# Custom mesh visualization
try:
solution.plot_refinement_history(
phase_id=1,
figsize=(16, 10),
transform_domain=(0.0, 100.0) # Transform from [-1,1] to [0,100]
)
except RuntimeError:
print("No adaptive data for mesh refinement plot")
Method Parameters:
# plot_refinement_history parameters:
# - phase_id: Phase to visualize (required)
# - figsize: Figure dimensions (width, height), default (12, 6)
# - transform_domain: Transform from [-1,1] to physical domain (min, max), default None
# print_benchmark_summary parameters:
# - comprehensive: Detail level (bool), default True
Detecting Solution Type:
# Handle solutions without adaptive data
try:
adaptive = solution.adaptive
benchmark_available = "benchmark" in adaptive
print(f"Adaptive solution - benchmark arrays available: {benchmark_available}")
except RuntimeError:
print("Fixed mesh solution - no benchmark data available")
Data Export Patterns#
Extract benchmark data for external analysis and research comparison:
CSV Export Pattern:
# Export benchmark data to CSV format
try:
adaptive = solution.adaptive
benchmark = adaptive["benchmark"]
print("iteration,error,points,intervals")
for i in range(len(benchmark["mesh_iteration"])):
iteration = benchmark["mesh_iteration"][i]
error = benchmark["estimated_error"][i]
points = benchmark["collocation_points"][i]
intervals = benchmark["mesh_intervals"][i]
error_str = "NaN" if np.isnan(error) else f"{error:.6e}"
print(f"{iteration},{error_str},{points},{intervals}")
except RuntimeError:
print("Fixed mesh solution - no benchmark data to export")
NumPy Array Conversion:
# Convert to numpy arrays for analysis
try:
adaptive = solution.adaptive
benchmark = adaptive["benchmark"]
import numpy as np
points_array = np.array(benchmark["collocation_points"])
error_array = np.array(benchmark["estimated_error"])
# Filter valid errors for analysis
valid_errors = error_array[~np.isnan(error_array)]
except RuntimeError:
print("Fixed mesh solution - no benchmark data for conversion")
Research Data Package:
# Extract complete research dataset
try:
adaptive = solution.adaptive
research_data = {
"algorithm_status": {
"converged": adaptive["converged"],
"iterations": adaptive["iterations"],
"tolerance": adaptive["target_tolerance"]
},
"mission_benchmark": adaptive["benchmark"],
"phase_benchmarks": adaptive["phase_benchmarks"],
"convergence_history": adaptive["iteration_history"]
}
except RuntimeError:
print("Fixed mesh solution - no research data available")
Next Steps#
Problem Definition: Study Complete Problem Definition Guide to understand how solution structure relates to problem formulation
Complete Examples: Explore Examples Gallery for solution analysis in context of specific problems
API Reference: Use API Reference for detailed method signatures and advanced options