Source code for pydae.core.builder.parser
# pydae/core/builder/parser.py
import logging
import sympy as sym
[docs]
def check_system(sys):
"""
Validates the user-provided system dictionary.
Adds dummy equations if dynamic or algebraic equations are missing,
checks for duplicates, and determines if a separate initialization
run ('inirun') is required.
Parameters
----------
sys : dict
The raw system dictionary provided by the user.
Returns
-------
sys : dict
The validated and potentially patched system dictionary.
inirun : bool
Flag indicating if the initialization and run stages are different.
"""
logging.debug('Checking system dictionary structure...')
inirun = True
# 1. Check for dynamic equations
if len(sys.get('f_list', [])) == 0:
logging.warning('System without dynamic equations. Adding dummy dynamic equation.')
x_dummy, u_dummy = sym.symbols('x_dummy, u_dummy')
sys.setdefault('x_list', []).append('x_dummy')
sys['f_list'] = [u_dummy - x_dummy]
sys.setdefault('u_ini_dict', {})['u_dummy'] = 1.0
sys.setdefault('u_run_dict', {})['u_dummy'] = 1.0
# 2. Check for algebraic equations
if len(sys.get('g_list', [])) == 0:
logging.warning('System without algebraic equations. Adding dummy algebraic equations.')
y_dummy, u_dummy = sym.symbols('y_dummy, u_dummy')
y_dummy2, u_dummy2 = sym.symbols('y_dummy2, u_dummy2')
sys.setdefault('g_list', []).extend([u_dummy - y_dummy, u_dummy2 - y_dummy2])
sys.setdefault('y_ini_list', []).extend(['y_dummy', 'y_dummy2'])
sys.setdefault('y_run_list', []).extend(['y_dummy', 'y_dummy2'])
sys.setdefault('u_ini_dict', {}).update({'u_dummy': 1.0, 'u_dummy2': 1.0})
sys.setdefault('u_run_dict', {}).update({'u_dummy': 1.0, 'u_dummy2': 1.0})
# 3. Check for duplicated variables
if len(sys['y_ini_list']) != len(set(sys['y_ini_list'])):
logging.error('Error: y_ini_list contains duplicate variables.')
if len(sys['y_run_list']) != len(set(sys['y_run_list'])):
logging.error('Error: y_run_list contains duplicate variables.')
# 4. Check if initialization variables are identical to run variables
if sys['y_run_list'] == sys['y_ini_list']:
inirun = False
return sys, inirun
[docs]
def process_system_dict(sys):
"""
Parses the raw user dictionary. It handles cases where the variables
in the lists are either strings OR already defined SymPy symbolic objects.
It extracts the EXACT SymPy symbols from the expressions to ensure
derivatives compute correctly.
"""
logging.debug('Parsing dictionary: converting lists to SymPy matrices and vectors')
# 1. Collect all equations to extract their original symbols
all_exprs = sys.get('f_list', []) + sys.get('g_list', [])
if 'h_dict' in sys:
all_exprs += list(sys['h_dict'].values())
elif 'h_list' in sys:
all_exprs += sys['h_list']
# Dictionary with the exact symbols used in the expressions (keyed by their string name)
exact_symbols = {}
for expr in all_exprs:
if hasattr(expr, 'free_symbols'):
for s in expr.free_symbols:
exact_symbols[s.name] = s
def get_sym(item):
"""
If the item is already a SymPy Symbol, return it directly.
If it is a string, retrieve the exact symbol from the equations,
or create a new real symbol if it doesn't exist.
"""
if isinstance(item, sym.Symbol):
return item
elif isinstance(item, str):
return exact_symbols.get(item, sym.Symbol(item, real=True))
else:
return sym.Symbol(str(item), real=True)
# 2. Convert equations to row matrices (.T)
sys['f'] = sym.Matrix(sys['f_list']).T
sys['g'] = sym.Matrix(sys['g_list']).T
# 3. Build state and input vectors dynamically
sys['x'] = sym.Matrix([get_sym(item) for item in sys['x_list']]).T
sys['y_ini'] = sym.Matrix([get_sym(item) for item in sys['y_ini_list']]).T
sys['y_run'] = sym.Matrix([get_sym(item) for item in sys['y_run_list']]).T
sys['u_ini'] = sym.Matrix([get_sym(item) for item in sys['u_ini_dict'].keys()]).T
sys['u_run'] = sym.Matrix([get_sym(item) for item in sys['u_run_dict'].keys()]).T
# 4. Outputs (h)
if 'h_dict' in sys:
sys['h'] = sym.Matrix(list(sys['h_dict'].values())).T
elif 'h_list' in sys:
sys['h'] = sym.Matrix(sys['h_list']).T
else:
sys['h'] = sym.Matrix([get_sym(item) for item in sys['y_run_list']]).T
# 5. Store dimensions
sys['N_x'] = len(sys['x'])
sys['N_y'] = len(sys['y_run'])
sys['N_u'] = len(sys['u_run'])
sys['N_z'] = len(sys['h'])
return sys