Why simulink s-function return status 4

Interface: Matlab

Goal: Generate S-function for 4-wheel vehicle MPC that works in Simulink for real-time control

Problem: M-code solver works perfectly (status=0), but S-function returns status=4 (infeasible) in Simulink with identical setup and parameters

What I tried: Used identical model, constraints, and solver settings for both M-code and S-function generation. The M-code version solves successfully, but when I use make_sfun to generate S-function for Simulink, it immediately returns status=4.

Vehicle Model
function [ocp_solver, sim_solver] = setup_fourwheel_ocp_solver(model, T_horizon, N, dt_sim)
    % SETUP_FOURWHEEL_OCP_SOLVER Creates and configures OCP solver and plant simulator
    % for 4-wheel drive vehicle model with individual wheel torques and front/rear steering
    % Inputs:
    %   model: AcadosModel for 4-wheel vehicle
    %   T_horizon: prediction horizon time [s]
    %   N: prediction horizon steps
    %   dt_sim: simulation timestep [s]
    % Outputs:
    %   ocp_solver: AcadosOcpSolver object
    %   sim_solver: AcadosSimSolver object
    
    %% OCP SETUP - Optimized for speed
    ocp = AcadosOcp();
    
    ocp.solver_options.tf = T_horizon;
    ocp.solver_options.N_horizon = N;
    ocp.solver_options.nlp_solver_type = 'SQP_RTI';
    ocp.solver_options.qp_solver = 'PARTIAL_CONDENSING_HPIPM';
    
    % RTI-specific settings for stability
    ocp.solver_options.hessian_approx = 'GAUSS_NEWTON';

    ocp.solver_options.print_level = 0;
        
    % OCP model integrator
    ocp.solver_options.sim_method_num_stages = 4;
    ocp.solver_options.sim_method_num_steps = 1;
    ocp.solver_options.integrator_type = 'ERK';
    
    %% PLANT SIMULATOR SETUP
    sim = AcadosSim();
    
    % Plant integrator
    sim.solver_options.num_stages = 4;
    sim.solver_options.num_steps = 1;
    sim.solver_options.Tsim = dt_sim;
    sim.solver_options.integrator_type = 'ERK';
    
    %% ASSIGN MODEL
    ocp.model = model;
    sim.model = model;
    
    %% COST FUNCTION - External cost (defined in model)
    ocp.cost.cost_type = 'EXTERNAL';
    ocp.cost.cost_type_0 = 'EXTERNAL';
    ocp.cost.cost_type_e = 'EXTERNAL';
    
    %% CONSTRAINTS
    % Wheel torque limits
    T_max = 3000;                    % max wheel torque [Nm]
    T_min = -3000;                   % min wheel torque [Nm] (negative for braking/regen)
    
    % Steering angle limits
    delta_f_max = deg2rad(30);       % front steering limit [rad]
    delta_r_max = deg2rad(5);        % rear steering limit [rad] (smaller than front)
    
    % Control input constraints
    ocp.constraints.lbu = [T_min; T_min; T_min; T_min; -delta_f_max; -delta_r_max];
    ocp.constraints.ubu = [T_max; T_max; T_max; T_max; delta_f_max; delta_r_max];
    ocp.constraints.idxbu = [0; 1; 2; 3; 4; 5];
    
    % State constraints
    vx_max = 50.0; vx_min = 0.1;     % longitudinal velocity limits [m/s]
    vy_max = 20.0;                   % lateral velocity limits [m/s]
    psi_dot_max = deg2rad(180);      % yaw rate limits [rad/s]
    
    % State bounds [vx, vy, psi_dot]
    ocp.constraints.lbx = [vx_min; -vy_max; -psi_dot_max]; 
    ocp.constraints.ubx = [vx_max; vy_max; psi_dot_max];  
    ocp.constraints.idxbx = [3; 4; 5];  % indices for vx, vy, psi_dot
    
    % Initial state constraint setup (will be set dynamically)
    ocp.constraints.x0 = zeros(6, 1); % placeholder for 6-dimensional state

    ocp.simulink_opts = get_acados_simulink_opts;
    
    %% CREATE SOLVERS
    ocp_solver = AcadosOcpSolver(ocp);
    sim_solver = AcadosSimSolver(sim);

end
function model = get_fourwheel_vehicle_model(varargin)
    % GET_FOURWHEEL_VEHICLE_MODEL returns AcadosModel for 4-wheel drive vehicle
    % States: [x, y, psi, vx, vy, psi_dot] 
    %         - position, heading, velocities, yaw rate
    % Controls: [T_fl, T_fr, T_rl, T_rr, delta_f, delta_r] 
    %          - 4 wheel torques, front and rear steering angles
    
    import casadi.*
    
    %% System dimensions
    nx = 6; % states: [x, y, psi, vx, vy, psi_dot]
    nu = 6; % controls: [T_fl, T_fr, T_rl, T_rr, delta_f, delta_r]
    
    %% Vehicle parameters
    m = 1500;       % vehicle mass [kg]
    I_z = 2500;     % moment of inertia around z-axis [kg⋅m²]
    l_f = 1.3;      % distance from CG to front axle [m]
    l_r = 1.2;      % distance from CG to rear axle [m]
    t_f = 1.6;      % front track width [m]
    t_r = 1.6;      % rear track width [m]
    
    % Wheel parameters
    R_w = 0.32;     % wheel radius [m]
    
    % Tire parameters
    C_f = 80000;    % front tire cornering stiffness [N/rad]
    C_r = 80000;    % rear tire cornering stiffness [N/rad]

    %% Named symbolic variables
    % States
    x_pos = SX.sym('x');        % global x position [m]
    y_pos = SX.sym('y');        % global y position [m]  
    psi = SX.sym('psi');        % heading angle [rad]
    vx = SX.sym('vx');          % velocity in x direction [m/s]
    vy = SX.sym('vy');          % velocity in y direction [m/s]
    psi_dot = SX.sym('psi_dot'); % yaw rate [rad/s]
    
    % Controls
    T_fl = SX.sym('T_fl');       % front left wheel torque [Nm]
    T_fr = SX.sym('T_fr');       % front right wheel torque [Nm]
    T_rl = SX.sym('T_rl');       % rear left wheel torque [Nm]
    T_rr = SX.sym('T_rr');       % rear right wheel torque [Nm]
    delta_f = SX.sym('delta_f'); % front steering angle [rad]
    delta_r = SX.sym('delta_r'); % rear steering angle [rad]
    
    %% State and control vectors
    x = vertcat(x_pos, y_pos, psi, vx, vy, psi_dot);
    u = vertcat(T_fl, T_fr, T_rl, T_rr, delta_f, delta_r);
    xdot = SX.sym('xdot', nx, 1);
    
    %% Wheel velocities at contact points
    % Front left wheel
    vx_fl = vx - t_f/2 * psi_dot;
    vy_fl = vy + l_f * psi_dot;
    
    % Front right wheel  
    vx_fr = vx + t_f/2 * psi_dot;
    vy_fr = vy + l_f * psi_dot;
    
    % Rear left wheel
    vx_rl = vx - t_r/2 * psi_dot;
    vy_rl = vy - l_r * psi_dot;
    
    % Rear right wheel
    vx_rr = vx + t_r/2 * psi_dot;
    vy_rr = vy - l_r * psi_dot;
    
    %% Tire slip calculations
    
    % Slip angles (considering steering)
    alpha_fl = delta_f - atan2(vy_fl, abs(vx_fl));
    alpha_fr = delta_f - atan2(vy_fr, abs(vx_fr));
    alpha_rl = delta_r - atan2(vy_rl, abs(vx_rl));
    alpha_rr = delta_r - atan2(vy_rr, abs(vx_rr));
    
    %% Tire forces (simplified linear model)
    % Longitudinal forces: simply torque divided by wheel radius
    F_xfl = T_fl / R_w;
    F_xfr = T_fr / R_w; 
    F_xrl = T_rl / R_w;
    F_xrr = T_rr / R_w;
    
    % Lateral forces
    F_yfl = C_f * alpha_fl;
    F_yfr = C_f * alpha_fr;
    F_yrl = C_r * alpha_rl;
    F_yrr = C_r * alpha_rr;
    
    % Transform forces to vehicle coordinate system
    % Front wheels (with steering)
    F_xfl_body = F_xfl * cos(delta_f) - F_yfl * sin(delta_f);
    F_yfl_body = F_xfl * sin(delta_f) + F_yfl * cos(delta_f);
    F_xfr_body = F_xfr * cos(delta_f) - F_yfr * sin(delta_f);
    F_yfr_body = F_xfr * sin(delta_f) + F_yfr * cos(delta_f);
    
    % Rear wheels (with steering)
    F_xrl_body = F_xrl * cos(delta_r) - F_yrl * sin(delta_r);
    F_yrl_body = F_xrl * sin(delta_r) + F_yrl * cos(delta_r);
    F_xrr_body = F_xrr * cos(delta_r) - F_yrr * sin(delta_r);
    F_yrr_body = F_xrr * sin(delta_r) + F_yrr * cos(delta_r);
    
    % Total forces
    F_x_total = F_xfl_body + F_xfr_body + F_xrl_body + F_xrr_body;
    F_y_total = F_yfl_body + F_yfr_body + F_yrl_body + F_yrr_body;
    
    %% Vehicle dynamics equations
    % Position dynamics (global frame)
    x_dot = vx * cos(psi) - vy * sin(psi);
    y_dot = vx * sin(psi) + vy * cos(psi);
    psi_dot_eq = psi_dot;
    
    % Body frame accelerations
    vx_dot = F_x_total / m + vy * psi_dot;
    vy_dot = F_y_total / m - vx * psi_dot;
    
    % Yaw dynamics (moments about CG)
    M_z = (F_yfl_body + F_yfr_body) * l_f - (F_yrl_body + F_yrr_body) * l_r + ...
          (F_xfr_body - F_xfl_body) * t_f/2 + (F_xrr_body - F_xrl_body) * t_r/2;
    psi_dot_dot = M_z / I_z;
    
    
    %% System dynamics
    f_expl_expr = vertcat(x_dot, ...
                          y_dot, ...
                          psi_dot_eq, ...
                          vx_dot, ...
                          vy_dot, ...
                          psi_dot_dot);
    
    f_impl_expr = f_expl_expr - xdot;
    
    % Discrete dynamics
    if nargin > 0
        delta_t = varargin{1};
        disc_dyn_expr = x + delta_t * f_expl_expr; % explicit Euler
    else
        disc_dyn_expr = [];
    end
    
    %% Cost function setup (for S-function compatibility)
    % Reference parameters
    x_ref = SX.sym('x_ref');
    y_ref = SX.sym('y_ref');

    % Cost weights
    Q_pos = 100;         % position tracking weight
    Q_vy = 2;            % lateral velocity penalty (stability)
    Q_psi_dot = 2;       % yaw rate penalty (stability)

    % Control effort weights
    R_torque = 1e-6;     % wheel torque penalty
    R_steer_f = 10;      % front steering penalty
    R_steer_r = 20;      % rear steering penalty (higher to prefer front steering)

    % Stage cost (tracking + stability + control effort)
    stage_cost = Q_pos * ((x_pos - x_ref)^2 + (y_pos - y_ref)^2) + ...
                 Q_vy * vy^2 + ...
                 Q_psi_dot * psi_dot^2 + ...
                 R_torque * (T_fl^2 + T_fr^2 + T_rl^2 + T_rr^2) + ...
                 R_steer_f * delta_f^2 + ...
                 R_steer_r * delta_r^2;

    % Initial stage cost (stability + control effort)
    initial_cost = Q_vy * vy^2 + ...
                   Q_psi_dot * psi_dot^2 + ...
                   R_torque * (T_fl^2 + T_fr^2 + T_rl^2 + T_rr^2) + ...
                   R_steer_f * delta_f^2 + ...
                   R_steer_r * delta_r^2;

    % Terminal cost (tracking + stability only)
    terminal_cost = Q_pos * ((x_pos - x_ref)^2 + (y_pos - y_ref)^2) + ...
                    Q_vy * vy^2 + ...
                    Q_psi_dot * psi_dot^2;

    %% Populate AcadosModel
    model = AcadosModel();
    model.x = x;
    model.xdot = xdot;
    model.u = u;
    model.p = vertcat(x_ref, y_ref); % parameters for reference
    model.f_expl_expr = f_expl_expr;
    model.f_impl_expr = f_impl_expr;
    model.disc_dyn_expr = disc_dyn_expr;
    model.cost_expr_ext_cost = stage_cost;
    model.cost_expr_ext_cost_0 = initial_cost;
    model.cost_expr_ext_cost_e = terminal_cost;
    model.name = 'fourwheel_vehicle';

end
clear all; close all; clc;

% Add parent directory to path to access acados setup
addpath('..');
acados_env_variables_windows();

%% SIMULATION PARAMETERS
T_sim = 40.0;        % full simulation time [s] (for reference generation)
dt_sim = 0.05;       % simulation timestep [s] 
N_sim = round(T_sim/dt_sim);

%% OCP SETUP - 3 second horizon optimization
dt_ocp = 0.05;                              % match simulation timestep
T_horizon = 3.0;                            % 3 second prediction horizon
N = round(T_horizon/dt_ocp);                % prediction horizon steps = 60

%% GIF Recording Parameters
record_gif = false;    % Disabled for single optimization
gif_filename = '4wheel_lemniscate_3sec_horizon.gif';

%% 4-WHEEL VEHICLE MODEL
model = get_fourwheel_vehicle_model();
nx = 6; % [x, y, psi, vx, vy, psi_dot]
nu = 6; % [T_fl, T_fr, T_rl, T_rr, delta_f, delta_r]

%% LEMNISCATE REFERENCE PATH - Figure-8 trajectory
t_sim = (0:N_sim) * dt_sim;

% Lemniscate parameters
scale = 200;  % size of the figure-8
omega = 0.2; % frequency (slower for better tracking)

% Parametric lemniscate equations
t_param = omega * t_sim;

% Lemniscate position
denominator = 1 + sin(t_param).^2;
x_ref_sim = scale * cos(t_param) ./ denominator;
y_ref_sim = scale * sin(t_param) .* cos(t_param) ./ denominator;

% Calculate heading angle from velocity direction
dx_dt = gradient(x_ref_sim, dt_sim);
dy_dt = gradient(y_ref_sim, dt_sim);
psi_ref_sim = atan2(dy_dt, dx_dt);

% Calculate reference velocity (speed along path)
v_ref_sim = sqrt(dx_dt.^2 + dy_dt.^2);
v_ref_sim = max(v_ref_sim, 5); % minimum speed of 5 m/s

%% EXTRACT 3-SECOND REFERENCE TRAJECTORY
N_3sec = N + 1;  % Number of points for 3 seconds (including t=0)
t_3sec = (0:N) * dt_sim;  % Time vector for 3 seconds

% Extract first 3 seconds of reference
x_ref_3sec = x_ref_sim(1:N_3sec);
y_ref_3sec = y_ref_sim(1:N_3sec);
psi_ref_3sec = psi_ref_sim(1:N_3sec);
v_ref_3sec = v_ref_sim(1:N_3sec);

%% CREATE SOLVERS
fprintf('Creating 4-wheel vehicle solvers with 3-second horizon (N=%d)...\n', N);
tic;
[ocp_solver, sim_solver] = setup_fourwheel_ocp_solver(model, T_horizon, N, dt_sim);
solver_creation_time = toc;
fprintf('Solvers created in %.3f seconds\n', solver_creation_time);

%% Initial state
% [x, y, psi, vx, vy, psi_dot]
R_w = 0.32; % wheel radius [m]
vx0 = v_ref_3sec(1);
vy0 = 0;

x0 = [x_ref_3sec(1); y_ref_3sec(1); psi_ref_3sec(1); vx0; vy0; 0];

%% SETUP VISUALIZATION
% Get screen size and center figure
screenSize = get(0, 'ScreenSize');
screenWidth = screenSize(3);
screenHeight = screenSize(4);

figWidth = 1800;
figHeight = 1000;

xPos = (screenWidth - figWidth) / 2;
yPos = (screenHeight - figHeight) / 2;

fig = figure('Position', [xPos, yPos, figWidth, figHeight]);

% Main trajectory plot
subplot(2,3,[1,4]);
h_ref_full = plot(x_ref_sim, y_ref_sim, 'k:', 'LineWidth', 1); hold on;  % Full reference (light)
h_ref_3sec = plot(x_ref_3sec, y_ref_3sec, 'k--', 'LineWidth', 2);        % 3-second reference
h_optimal = plot(NaN, NaN, 'b-', 'LineWidth', 3);
h_start = plot(x0(1), x0(2), 'go', 'MarkerSize', 10, 'MarkerFaceColor', 'g');
h_end_3sec = plot(x_ref_3sec(end), y_ref_3sec(end), 'ro', 'MarkerSize', 10, 'MarkerFaceColor', 'r');
xlabel('X [m]'); ylabel('Y [m]'); 
title('3-Second Horizon Lemniscate Trajectory Optimization');
legend('Full Reference', '3-sec Reference', 'Optimal Path', 'Start', '3-sec End', 'Location', 'best');
grid on; axis equal;

% Error plot placeholder
subplot(2,3,2);
title('Position Error (will be computed after optimization)');
xlabel('Time [s]'); ylabel('Error [m]'); grid on;

% Velocity plot placeholder
subplot(2,3,3);
title('Velocities (will be computed after optimization)');
xlabel('Time [s]'); ylabel('Velocity [m/s]'); grid on;

% Control plots placeholders
subplot(2,3,5);
title('Wheel Torques (will be computed after optimization)');
xlabel('Time [s]'); ylabel('Torque [Nm]'); grid on;

subplot(2,3,6);
title('Steering Angles (will be computed after optimization)');
xlabel('Time [s]'); ylabel('Steering Angle [deg]'); grid on;

drawnow;

%% SINGLE 3-SECOND HORIZON OPTIMIZATION
fprintf('Setting up 3-second horizon optimization...\n');

% Set initial state
ocp_solver.set('constr_x0', x0);

% Set reference parameters for 3-second trajectory
fprintf('Setting reference trajectory for %d stages (3 seconds)...\n', N+1);
for j = 0:N
    ref_params = [x_ref_3sec(j+1); y_ref_3sec(j+1)];
    ocp_solver.set('p', ref_params, j);
    
    % Initialize with reference trajectory as initial guess
    x_init = [x_ref_3sec(j+1); y_ref_3sec(j+1); psi_ref_3sec(j+1); 
              v_ref_3sec(j+1); 0; 0];
    ocp_solver.set('x', x_init, j);
    
    if j < N
        u_init = [50; 50; 50; 50; 0; 0]; % small torques, zero steering
        ocp_solver.set('u', u_init, j);
    end
end

fprintf('Starting 3-second horizon optimization...\n');
optimization_tic = tic;

% Solve the 3-second trajectory at once
ocp_solver.solve();
total_optimization_time = toc(optimization_tic);

status = ocp_solver.get('status');
fprintf('Optimization completed!\n');
fprintf('Status: %d\n', status);
fprintf('Total optimization time: %.3f seconds\n', total_optimization_time);

if status ~= 0
    fprintf('Warning: Optimization did not converge to optimal solution\n');
else
    fprintf('Optimization successful!\n');
end

%% EXTRACT OPTIMAL SOLUTION
fprintf('Extracting optimal solution...\n');

% Extract optimal states and controls for 3-second horizon
x_opt = zeros(nx, N+1);
u_opt = zeros(nu, N);

for j = 0:N
    x_opt(:, j+1) = ocp_solver.get('x', j);
    if j < N
        u_opt(:, j+1) = ocp_solver.get('u', j);
    end
end

%% COMPUTE PERFORMANCE METRICS (for 3-second trajectory)
pos_error = sqrt((x_opt(1,:) - x_ref_3sec).^2 + (x_opt(2,:) - y_ref_3sec).^2);
total_force = (u_opt(1,:) + u_opt(2,:) + u_opt(3,:) + u_opt(4,:)) / R_w;
total_torque = u_opt(1,:) + u_opt(2,:) + u_opt(3,:) + u_opt(4,:);

%% UPDATE PLOTS WITH OPTIMAL SOLUTION
% Update main trajectory plot
subplot(2,3,[1,4]);
set(h_optimal, 'XData', x_opt(1,:), 'YData', x_opt(2,:));

% Error plot
subplot(2,3,2);
plot(t_3sec, pos_error, 'r-', 'LineWidth', 2);
xlabel('Time [s]'); ylabel('Position Error [m]');
title('Tracking Error (3 seconds)'); grid on;
xlim([0, 3]);

% Velocity plots
subplot(2,3,3);
plot(t_3sec, x_opt(4,:), 'b-', 'LineWidth', 2); hold on;
plot(t_3sec, x_opt(5,:), 'r-', 'LineWidth', 2);
plot(t_3sec, v_ref_3sec, 'k--', 'LineWidth', 2);
xlabel('Time [s]'); ylabel('Velocity [m/s]');
title('Velocities (3 seconds)'); legend('vx', 'vy', 'v_{ref}'); grid on;
xlim([0, 3]);

% Wheel torques
subplot(2,3,5);
plot(t_3sec(1:end-1), u_opt(1,:), 'r-', 'LineWidth', 1.5); hold on;
plot(t_3sec(1:end-1), u_opt(2,:), 'g-', 'LineWidth', 1.5);
plot(t_3sec(1:end-1), u_opt(3,:), 'b-', 'LineWidth', 1.5);
plot(t_3sec(1:end-1), u_opt(4,:), 'm-', 'LineWidth', 1.5);
xlabel('Time [s]'); ylabel('Wheel Torque [Nm]');
title('Wheel Torques (3 seconds)'); 
legend('FL', 'FR', 'RL', 'RR', 'Location', 'best'); grid on;
xlim([0, 3]);

% Steering angles
subplot(2,3,6);
plot(t_3sec(1:end-1), rad2deg(u_opt(5,:)), 'b-', 'LineWidth', 2); hold on;
plot(t_3sec(1:end-1), rad2deg(u_opt(6,:)), 'r-', 'LineWidth', 2);
xlabel('Time [s]'); ylabel('Steering Angle [deg]');
title('Steering Angles (3 seconds)'); 
legend('Front', 'Rear', 'Location', 'best'); grid on;
xlim([0, 3]);

drawnow;

%% PERFORMANCE METRICS (for 3-second trajectory)
rmse_pos = sqrt(mean(pos_error.^2));
max_pos_error = max(pos_error);
max_vy = max(abs(x_opt(5,:)));
max_yaw_rate = max(abs(rad2deg(x_opt(6,:))));
avg_speed = mean(sqrt(x_opt(4,:).^2 + x_opt(5,:).^2));
max_torque = max(max(abs(u_opt(1:4,:))));
avg_total_torque = mean(abs(total_torque));

fprintf('\n=== 3-SECOND HORIZON OPTIMIZATION RESULTS ===\n');
fprintf('Optimization time: %.3f seconds\n', total_optimization_time);
fprintf('Solver status: %d\n', status);
fprintf('Horizon length: %.1f seconds (%d steps)\n', T_horizon, N);
fprintf('\nPerformance Metrics (first 3 seconds):\n');
fprintf('RMSE Position Error: %.3f m\n', rmse_pos);
fprintf('Max Position Error: %.3f m\n', max_pos_error);
fprintf('Max Lateral Velocity: %.2f m/s\n', max_vy);
fprintf('Max Yaw Rate: %.2f deg/s\n', max_yaw_rate);
fprintf('Average Speed: %.2f m/s\n', avg_speed);
fprintf('Max Wheel Torque: %.0f Nm\n', max_torque);
fprintf('Average Total Torque: %.0f Nm\n', avg_total_torque);

Question: Why does the same NMPC problem become infeasible when compiled to S-function? Is there a difference in constraint handling, memory initialization, or parameter parsing between MATLAB interface and S-function compilation process?

Environment: Windows 11, MATLAB R2022b,2024b, MinGW-w64, ACADOS latest

Hi :waving_hand:

differences between the behaviour in MATLAB and Simulink are typically due to different initializations. Please check how the iterates are initialized in Matlab and Simulink.

As a next step, you could compile acados with the flag ACADOS_DEBUG_SQP_PRINT_QPS_TO_FILE. With this flag, the QPs as well as the iterates are saved to file and you can compare when and where the differences between Simulink and Matlab occur.

Hope this is helpful!
Katrin