#
# Copyright 2019 Gianluca Frison, Dimitris Kouzoupis, Robin Verschueren,
# Andrea Zanelli, Niels van Duijkeren, Jonathan Frey, Tommaso Sartor,
# Branimir Novoselnik, Rien Quirynen, Rezart Qelibari, Dang Doan,
# Jonas Koenemann, Yutao Chen, Tobias Schöls, Jonas Schlagenhauf, Moritz Diehl
#
# This file is part of acados.
#
# The 2-Clause BSD License
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.;
#
import os , sys , json
import urllib . request
import shutil
import numpy as np
from casadi import SX , MX , DM , Function , CasadiMeta
ALLOWED_CASADI_VERSIONS = ( ' 3.5.5 ' , ' 3.5.4 ' , ' 3.5.3 ' , ' 3.5.2 ' , ' 3.5.1 ' , ' 3.4.5 ' , ' 3.4.0 ' )
TERA_VERSION = " 0.0.34 "
def get_acados_path ( ) :
ACADOS_PATH = os . environ . get ( ' ACADOS_SOURCE_DIR ' )
if not ACADOS_PATH :
acados_template_path = os . path . dirname ( os . path . abspath ( __file__ ) )
acados_path = os . path . join ( acados_template_path , ' .. ' , ' .. ' , ' .. ' )
ACADOS_PATH = os . path . realpath ( acados_path )
msg = ' Warning: Did not find environment variable ACADOS_SOURCE_DIR, '
msg + = ' guessed ACADOS_PATH to be {} . \n ' . format ( ACADOS_PATH )
msg + = ' Please export ACADOS_SOURCE_DIR to not avoid this warning. '
print ( msg )
return ACADOS_PATH
def get_python_interface_path ( ) :
ACADOS_PYTHON_INTERFACE_PATH = os . environ . get ( ' ACADOS_PYTHON_INTERFACE_PATH ' )
if not ACADOS_PYTHON_INTERFACE_PATH :
acados_path = get_acados_path ( )
ACADOS_PYTHON_INTERFACE_PATH = os . path . join ( acados_path , ' interfaces ' , ' acados_template ' , ' acados_template ' )
return ACADOS_PYTHON_INTERFACE_PATH
def get_tera_exec_path ( ) :
TERA_PATH = os . environ . get ( ' TERA_PATH ' )
if not TERA_PATH :
TERA_PATH = os . path . join ( get_acados_path ( ) , ' bin ' , ' t_renderer ' )
if os . name == ' nt ' :
TERA_PATH + = ' .exe '
return TERA_PATH
platform2tera = {
" linux " : " linux " ,
" darwin " : " osx " ,
" win32 " : " window.exe "
}
def casadi_version_warning ( casadi_version ) :
msg = ' Warning: Please note that the following versions of CasADi are '
msg + = ' officially supported: {} . \n ' . format ( " or " . join ( ALLOWED_CASADI_VERSIONS ) )
msg + = ' If there is an incompatibility with the CasADi generated code, '
msg + = ' please consider changing your CasADi version. \n '
msg + = ' Version {} currently in use. ' . format ( casadi_version )
print ( msg )
def is_column ( x ) :
if isinstance ( x , np . ndarray ) :
if x . ndim == 1 :
return True
elif x . ndim == 2 and x . shape [ 1 ] == 1 :
return True
else :
return False
elif isinstance ( x , ( MX , SX , DM ) ) :
if x . shape [ 1 ] == 1 :
return True
elif x . shape [ 0 ] == 0 and x . shape [ 1 ] == 0 :
return True
else :
return False
elif x == None or x == [ ] :
return False
else :
raise Exception ( " is_column expects one of the following types: np.ndarray, casadi.MX, casadi.SX. "
+ " Got: " + str ( type ( x ) ) )
def is_empty ( x ) :
if isinstance ( x , ( MX , SX , DM ) ) :
return x . is_empty ( )
elif isinstance ( x , np . ndarray ) :
if np . prod ( x . shape ) == 0 :
return True
else :
return False
elif x == None or x == [ ] :
return True
else :
raise Exception ( " is_empty expects one of the following types: casadi.MX, casadi.SX, "
+ " None, numpy array empty list. Got: " + str ( type ( x ) ) )
def casadi_length ( x ) :
if isinstance ( x , ( MX , SX , DM ) ) :
return int ( np . prod ( x . shape ) )
else :
raise Exception ( " casadi_length expects one of the following types: casadi.MX, casadi.SX. "
+ " Got: " + str ( type ( x ) ) )
def make_model_consistent ( model ) :
x = model . x
xdot = model . xdot
u = model . u
z = model . z
p = model . p
if isinstance ( x , MX ) :
symbol = MX . sym
elif isinstance ( x , SX ) :
symbol = SX . sym
else :
raise Exception ( " model.x must be casadi.SX or casadi.MX, got {} " . format ( type ( x ) ) )
if is_empty ( p ) :
model . p = symbol ( ' p ' , 0 , 0 )
if is_empty ( z ) :
model . z = symbol ( ' z ' , 0 , 0 )
return model
def get_tera ( ) :
tera_path = get_tera_exec_path ( )
acados_path = get_acados_path ( )
if os . path . exists ( tera_path ) and os . access ( tera_path , os . X_OK ) :
return tera_path
repo_url = " https://github.com/acados/tera_renderer/releases "
url = " {} /download/v {} /t_renderer-v {} - {} " . format (
repo_url , TERA_VERSION , TERA_VERSION , platform2tera [ sys . platform ] )
manual_install = ' For manual installation follow these instructions: \n '
manual_install + = ' 1 Download binaries from {} \n ' . format ( url )
manual_install + = ' 2 Copy them in {} /bin \n ' . format ( acados_path )
manual_install + = ' 3 Strip the version and platform from the binaries: '
manual_install + = ' as t_renderer-v0.0.34-X -> t_renderer) \n '
manual_install + = ' 4 Enable execution privilege on the file " t_renderer " with: \n '
manual_install + = ' " chmod +x {} " \n \n ' . format ( tera_path )
msg = " \n "
msg + = ' Tera template render executable not found, '
msg + = ' while looking in path: \n {} \n ' . format ( tera_path )
msg + = ' In order to be able to render the templates, '
msg + = ' you need to download the tera renderer binaries from: \n '
msg + = ' {} \n \n ' . format ( repo_url )
msg + = ' Do you wish to set up Tera renderer automatically? \n '
msg + = ' y/N? (press y to download tera or any key for manual installation) \n '
if input ( msg ) == ' y ' :
print ( " Dowloading {} " . format ( url ) )
with urllib . request . urlopen ( url ) as response , open ( tera_path , ' wb ' ) as out_file :
shutil . copyfileobj ( response , out_file )
print ( " Successfully downloaded t_renderer. " )
os . chmod ( tera_path , 0o755 )
return tera_path
msg_cancel = " \n You cancelled automatic download. \n \n "
msg_cancel + = manual_install
msg_cancel + = " Once installed re-run your script. \n \n "
print ( msg_cancel )
sys . exit ( 1 )
def render_template ( in_file , out_file , template_dir , json_path ) :
cwd = os . getcwd ( )
if not os . path . exists ( template_dir ) :
os . mkdir ( template_dir )
os . chdir ( template_dir )
tera_path = get_tera ( )
# setting up loader and environment
acados_path = os . path . dirname ( os . path . abspath ( __file__ ) )
template_glob = os . path . join ( acados_path , ' c_templates_tera ' , ' * ' )
# call tera as system cmd
os_cmd = " {tera_path} ' {template_glob} ' ' {in_file} ' ' {json_path} ' ' {out_file} ' " . format (
tera_path = tera_path ,
template_glob = template_glob ,
json_path = json_path ,
in_file = in_file ,
out_file = out_file
)
status = os . system ( os_cmd )
if ( status != 0 ) :
raise Exception ( ' Rendering of {} failed! \n \n Attempted to execute OS command: \n {} \n \n Exiting. \n ' . format ( in_file , os_cmd ) )
os . chdir ( cwd )
## Conversion functions
def np_array_to_list ( np_array ) :
if isinstance ( np_array , ( np . ndarray ) ) :
return np_array . tolist ( )
elif isinstance ( np_array , ( SX ) ) :
return DM ( np_array ) . full ( )
elif isinstance ( np_array , ( DM ) ) :
return np_array . full ( )
else :
raise ( Exception (
" Cannot convert to list type {} " . format ( type ( np_array ) )
) )
def format_class_dict ( d ) :
"""
removes the __ artifact from class to dict conversion
"""
out = { }
for k , v in d . items ( ) :
if isinstance ( v , dict ) :
v = format_class_dict ( v )
out_key = k . split ( ' __ ' , 1 ) [ - 1 ]
out [ k . replace ( k , out_key ) ] = v
return out
def get_ocp_nlp_layout ( ) :
python_interface_path = get_python_interface_path ( )
abs_path = os . path . join ( python_interface_path , ' acados_layout.json ' )
with open ( abs_path , ' r ' ) as f :
ocp_nlp_layout = json . load ( f )
return ocp_nlp_layout
def ocp_check_against_layout ( ocp_nlp , ocp_dims ) :
"""
Check dimensions against layout
Parameters
- - - - - - - - -
ocp_nlp : dict
dictionary loaded from JSON to be post - processed .
ocp_dims : instance of AcadosOcpDims
"""
ocp_nlp_layout = get_ocp_nlp_layout ( )
ocp_check_against_layout_recursion ( ocp_nlp , ocp_dims , ocp_nlp_layout )
return
def ocp_check_against_layout_recursion ( ocp_nlp , ocp_dims , layout ) :
for key , item in ocp_nlp . items ( ) :
try :
layout_of_key = layout [ key ]
except KeyError :
raise Exception ( " ocp_check_against_layout_recursion: field " \
" ' {0} ' is not in layout but in OCP description. " . format ( key ) )
if isinstance ( item , dict ) :
ocp_check_against_layout_recursion ( item , ocp_dims , layout_of_key )
if ' ndarray ' in layout_of_key :
if isinstance ( item , int ) or isinstance ( item , float ) :
item = np . array ( [ item ] )
if isinstance ( item , ( list , np . ndarray ) ) and ( layout_of_key [ 0 ] != ' str ' ) :
dim_layout = [ ]
dim_names = layout_of_key [ 1 ]
for dim_name in dim_names :
dim_layout . append ( ocp_dims [ dim_name ] )
dims = tuple ( dim_layout )
item = np . array ( item )
item_dims = item . shape
if len ( item_dims ) != len ( dims ) :
raise Exception ( ' Mismatching dimensions for field {0} . ' \
' Expected {1} dimensional array, got {2} dimensional array. ' \
. format ( key , len ( dims ) , len ( item_dims ) ) )
if np . prod ( item_dims ) != 0 or np . prod ( dims ) != 0 :
if dims != item_dims :
raise Exception ( ' acados -- mismatching dimensions for field {0} . ' \
' Provided data has dimensions {1} , ' \
' while associated dimensions {2} are {3} ' \
. format ( key , item_dims , dim_names , dims ) )
return
def J_to_idx ( J ) :
nrows = J . shape [ 0 ]
idx = np . zeros ( ( nrows , ) )
for i in range ( nrows ) :
this_idx = np . nonzero ( J [ i , : ] ) [ 0 ]
if len ( this_idx ) != 1 :
raise Exception ( ' Invalid J matrix structure detected, ' \
' must contain one nonzero element per row. Exiting. ' )
if this_idx . size > 0 and J [ i , this_idx [ 0 ] ] != 1 :
raise Exception ( ' J matrices can only contain 1s. Exiting. ' )
idx [ i ] = this_idx [ 0 ]
return idx
def J_to_idx_slack ( J ) :
nrows = J . shape [ 0 ]
ncol = J . shape [ 1 ]
idx = np . zeros ( ( ncol , ) )
i_idx = 0
for i in range ( nrows ) :
this_idx = np . nonzero ( J [ i , : ] ) [ 0 ]
if len ( this_idx ) == 1 :
idx [ i_idx ] = i
i_idx = i_idx + 1
elif len ( this_idx ) > 1 :
raise Exception ( ' J_to_idx_slack: Invalid J matrix. Exiting. ' \
' Found more than one nonzero in row ' + str ( i ) )
if this_idx . size > 0 and J [ i , this_idx [ 0 ] ] != 1 :
raise Exception ( ' J_to_idx_slack: J matrices can only contain 1s, ' \
' got J( ' + str ( i ) + ' , ' + str ( this_idx [ 0 ] ) + ' ) = ' + str ( J [ i , this_idx [ 0 ] ] ) )
if not i_idx == ncol :
raise Exception ( ' J_to_idx_slack: J must contain a 1 in every column! ' )
return idx
def acados_dae_model_json_dump ( model ) :
# load model
x = model . x
xdot = model . xdot
u = model . u
z = model . z
p = model . p
f_impl = model . f_impl_expr
model_name = model . name
# create struct with impl_dae_fun, casadi_version
fun_name = model_name + ' _impl_dae_fun '
impl_dae_fun = Function ( fun_name , [ x , xdot , u , z , p ] , [ f_impl ] )
casadi_version = CasadiMeta . version ( )
str_impl_dae_fun = impl_dae_fun . serialize ( )
dae_dict = { " str_impl_dae_fun " : str_impl_dae_fun , " casadi_version " : casadi_version }
# dump
json_file = model_name + ' _acados_dae.json '
with open ( json_file , ' w ' ) as f :
json . dump ( dae_dict , f , default = np_array_to_list , indent = 4 , sort_keys = True )
print ( " dumped " , model_name , " dae to file: " , json_file , " \n " )
def set_up_imported_gnsf_model ( acados_formulation ) :
gnsf = acados_formulation . gnsf_model
# check CasADi version
# dump_casadi_version = gnsf['casadi_version']
# casadi_version = CasadiMeta.version()
# if not casadi_version == dump_casadi_version:
# print("WARNING: GNSF model was dumped with another CasADi version.\n"
# + "This might yield errors. Please use the same version for compatibility, serialize version: "
# + dump_casadi_version + " current Python CasADi verison: " + casadi_version)
# input("Press any key to attempt to continue...")
# load model
phi_fun = Function . deserialize ( gnsf [ ' phi_fun ' ] )
phi_fun_jac_y = Function . deserialize ( gnsf [ ' phi_fun_jac_y ' ] )
phi_jac_y_uhat = Function . deserialize ( gnsf [ ' phi_jac_y_uhat ' ] )
get_matrices_fun = Function . deserialize ( gnsf [ ' get_matrices_fun ' ] )
# obtain gnsf dimensions
size_gnsf_A = get_matrices_fun . size_out ( 0 )
acados_formulation . dims . gnsf_nx1 = size_gnsf_A [ 1 ]
acados_formulation . dims . gnsf_nz1 = size_gnsf_A [ 0 ] - size_gnsf_A [ 1 ]
acados_formulation . dims . gnsf_nuhat = max ( phi_fun . size_in ( 1 ) )
acados_formulation . dims . gnsf_ny = max ( phi_fun . size_in ( 0 ) )
acados_formulation . dims . gnsf_nout = max ( phi_fun . size_out ( 0 ) )
# save gnsf functions in model
acados_formulation . model . phi_fun = phi_fun
acados_formulation . model . phi_fun_jac_y = phi_fun_jac_y
acados_formulation . model . phi_jac_y_uhat = phi_jac_y_uhat
acados_formulation . model . get_matrices_fun = get_matrices_fun
# get_matrices_fun = Function([model_name,'_gnsf_get_matrices_fun'], {dummy},...
# {A, B, C, E, L_x, L_xdot, L_z, L_u, A_LO, c, E_LO, B_LO,...
# nontrivial_f_LO, purely_linear, ipiv_x, ipiv_z, c_LO});
get_matrices_out = get_matrices_fun ( 0 )
acados_formulation . model . gnsf [ ' nontrivial_f_LO ' ] = int ( get_matrices_out [ 12 ] )
acados_formulation . model . gnsf [ ' purely_linear ' ] = int ( get_matrices_out [ 13 ] )
if " f_lo_fun_jac_x1k1uz " in gnsf :
f_lo_fun_jac_x1k1uz = Function . deserialize ( gnsf [ ' f_lo_fun_jac_x1k1uz ' ] )
acados_formulation . model . f_lo_fun_jac_x1k1uz = f_lo_fun_jac_x1k1uz
else :
dummy_var_x1 = SX . sym ( ' dummy_var_x1 ' , acados_formulation . dims . gnsf_nx1 )
dummy_var_x1dot = SX . sym ( ' dummy_var_x1dot ' , acados_formulation . dims . gnsf_nx1 )
dummy_var_z1 = SX . sym ( ' dummy_var_z1 ' , acados_formulation . dims . gnsf_nz1 )
dummy_var_u = SX . sym ( ' dummy_var_z1 ' , acados_formulation . dims . nu )
dummy_var_p = SX . sym ( ' dummy_var_z1 ' , acados_formulation . dims . np )
empty_var = SX . sym ( ' empty_var ' , 0 , 0 )
empty_fun = Function ( ' empty_fun ' , \
[ dummy_var_x1 , dummy_var_x1dot , dummy_var_z1 , dummy_var_u , dummy_var_p ] ,
[ empty_var ] )
acados_formulation . model . f_lo_fun_jac_x1k1uz = empty_fun
del acados_formulation . gnsf_model