Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Append an underscore to all Opty generated C variable names to avoid variable name clashes in included code. #200

Merged
merged 3 commits into from
Aug 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions examples-gallery/plot_parallel_park.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
**Constants**

- m : car mass, [kg]
- Izz : car yaw moment of inertia, [kg m^2]
- I : car yaw moment of inertia, [kg m^2]
- a : distance from front axle to mass center, [m]
- b : distance from rear axle to mass center, [m]

Expand Down Expand Up @@ -39,7 +39,7 @@

# %%
# Generate the nonholonomic equations of motion of the system.
m, Izz, a, b = sm.symbols('m, Izz, a, b', real=True)
m, I, a, b = sm.symbols('m, I, a, b', real=True)
x, y, vx, vy = me.dynamicsymbols('x, y, v_x, v_y', real=True)
theta, omega = me.dynamicsymbols('theta, omega', real=True)
delta, beta = me.dynamicsymbols('delta, beta', real=True)
Expand Down Expand Up @@ -75,9 +75,9 @@
Pf.vel(N).dot(B.y),
]

IA = me.inertia(A, 0, 0, Izz)
IA = me.inertia(A, 0, 0, I)
car = me.RigidBody('A', Ao, A, m, (IA, Ao))
IB = me.inertia(B, 0, 0, Izz/32)
IB = me.inertia(B, 0, 0, I/32)
wheel = me.RigidBody('B', Pf, B, m/6, (IB, Pf))

propulsion = (Pr, F*A.x)
Expand Down Expand Up @@ -111,7 +111,7 @@
# %%
# Provide some reasonably realistic values for the constants.
par_map = {
Izz: 1/12*1200*(2**2 + 3**2),
I: 1/12*1200*(2**2 + 3**2),
m: 1200,
a: 1.5,
b: 1.5,
Expand Down
8 changes: 4 additions & 4 deletions examples-gallery/plot_pendulum_swing_up_fixed_duration.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,21 @@

# %%
# Specify the symbolic equations of motion.
Izz, m, g, d, t = sm.symbols('Izz, m, g, d, t')
I, m, g, d, t = sm.symbols('I, m, g, d, t')
theta, omega, T = sm.symbols('theta, omega, T', cls=sm.Function)

state_symbols = (theta(t), omega(t))
constant_symbols = (Izz, m, g, d)
constant_symbols = (I, m, g, d)
specified_symbols = (T(t),)

eom = sm.Matrix([theta(t).diff() - omega(t),
Izz*omega(t).diff() + m*g*d*sm.sin(theta(t)) - T(t)])
I*omega(t).diff() + m*g*d*sm.sin(theta(t)) - T(t)])
sm.pprint(eom)

# %%
# Specify the known system parameters.
par_map = {
Izz: 1.0,
I: 1.0,
m: 1.0,
g: 9.81,
d: 1.0,
Expand Down
5 changes: 2 additions & 3 deletions opty/tests/test_direct_collocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ def test_pendulum():
# Symbolic equations of motion
# NOTE : h, real=True is used as a regression test for
# https://github.com/csu-hmc/opty/issues/162
# NOTE : Ix is used because NumPy 2.0 uses I in the C API.
I, m, g, h, t = sym.symbols('Ix, m, g, h, t', real=True)
I, m, g, h, t = sym.symbols('I, m, g, h, t', real=True)
theta, omega, T = sym.symbols('theta, omega, T', cls=sym.Function)

state_symbols = (theta(t), omega(t))
Expand Down Expand Up @@ -825,7 +824,7 @@ class TestConstraintCollocatorInstanceConstraints():

def setup_method(self):

I, m, g, d, t = sym.symbols('Ix, m, g, d, t')
I, m, g, d, t = sym.symbols('I, m, g, d, t')
theta, omega, T = [f(t) for f in sym.symbols('theta, omega, T',
cls=sym.Function)]

Expand Down
27 changes: 25 additions & 2 deletions opty/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,16 @@ def test_parse_free():

def test_ufuncify_matrix():

# NOTE: `if` is a reserved C keyword, this should not cause a compile
# NOTE : `if` is a reserved C keyword, this should not cause a compile
# error.
a, b, c, d = sym.symbols('a, b, if, d_{badsym}')
# NumPy 2 includes complex.h which defines the C variable "I". If we
# use "I" compilation would fail if opty does not handle the variable name
# appropriately.
# d_{badsym} is simply an invalid C variable name and compilation will
# fail, opty does not sanitize the variable names for invalid characters
# (although it could).

a, b, c, d, I, i = sym.symbols('a, b, if, d_{badsym}, I, i')

expr_00 = a**2 * sym.cos(b)**c
expr_01 = sym.tan(b) / sym.sin(a + b) + c**4
Expand Down Expand Up @@ -284,6 +291,22 @@ def eval_matrix_loop_numpy(a_vals, b_vals, c_vals):
testing.assert_allclose(f(result, a_vals, b_vals, c_val),
eval_matrix_loop_numpy(a_vals, b_vals, c_val))

# NOTE : Test "I" as a variable name, which fails compiling with NumPy 2.
f = utils.ufuncify_matrix((a, b, I), sym_mat.xreplace({c: I}))

result = np.empty((n, 4))

testing.assert_allclose(f(result, a_vals, b_vals, c_vals),
eval_matrix_loop_numpy(a_vals, b_vals, c_vals))

# NOTE : Test "i" as a variable name, which creates "in".
f = utils.ufuncify_matrix((a, b, i), sym_mat.xreplace({c: i}))

result = np.empty((n, 4))

testing.assert_allclose(f(result, a_vals, b_vals, c_vals),
eval_matrix_loop_numpy(a_vals, b_vals, c_vals))

# NOTE : Will not compile due to d_{badsym} being an invalid C variable
# name.
with pytest.raises(ImportError) as error:
Expand Down
43 changes: 33 additions & 10 deletions opty/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import sympy as sm
import sympy.physics.mechanics as me
from sympy.utilities.iterables import numbered_symbols
from sympy.printing.c import C99CodePrinter
try:
plt = sm.external.import_module('matplotlib.pyplot',
__import__kwargs={'fromlist': ['']},
Expand All @@ -35,6 +36,27 @@
]


class OptyC99CodePrinter(C99CodePrinter):
"""Printer that appends an underscore to all C variable names, to minimize
clashes with variables declared in headers we link against."""

# TODO : Add sanitizer to remove invalid characters in symbol names, for
# example deal with latex symbol names e.g. I_{zz}.

def _print_Symbol(self, expr):
name = super()._print_Symbol(expr)
return name + '_'

def _print_Function(self, expr):
name = super()._print_Function(expr)
return name + '_'


def ccode(expr, assign_to=None, standard='c99', **settings):
"""Mimics SymPy's ccode, but uses our printer."""
return OptyC99CodePrinter(settings).doprint(expr, assign_to)


def _forward_jacobian(expr, wrt):

def add_to_cache(node):
Expand Down Expand Up @@ -72,7 +94,7 @@ def add_to_cache(node):
raise NotImplementedError

replacement_symbols = numbered_symbols(
prefix='_z',
prefix='z',
cls=sm.Symbol,
exclude=expr.free_symbols,
)
Expand Down Expand Up @@ -405,7 +427,8 @@ def sort_sympy(seq):
#include <math.h>
#include "{file_prefix}_h.h"

void {routine_name}(double matrix[{matrix_output_size}], {input_args})
void {routine_name}(double matrix[{matrix_output_size}],
{input_args})
{{
{eval_code}
}}
Expand Down Expand Up @@ -611,18 +634,18 @@ def ufuncify_matrix(args, expr, const=None, tmp_dir=None, parallel=False,
else:
sub_exprs, simple_mat = sm.cse(expr, sm.numbered_symbols('z_'))

sub_expr_code = '\n'.join(['double ' + sm.ccode(sub_expr[1], sub_expr[0])
sub_expr_code = '\n'.join(['double ' + ccode(sub_expr[1], sub_expr[0])
for sub_expr in sub_exprs])

matrix_code = sm.ccode(simple_mat[0], matrix_sym)
matrix_code = ccode(simple_mat[0], matrix_sym)

d['eval_code'] = ' ' + '\n '.join((sub_expr_code + '\n' +
matrix_code).split('\n'))

c_indent = len(' void {routine_name}('.format(**d))
c_indent = len('void {routine_name}('.format(**d))
c_arg_spacer = ',\n' + ' ' * c_indent

input_args = ['double {}'.format(sm.ccode(a)) for a in args]
input_args = ['double {}'.format(ccode(a)) for a in args]
d['input_args'] = ' '*c_indent + c_arg_spacer.join(input_args)

cython_input_args = []
Expand All @@ -632,14 +655,14 @@ def ufuncify_matrix(args, expr, const=None, tmp_dir=None, parallel=False,
if const is not None and a in const:
typ = 'double'
idexy = '{}'
cython_input_args.append('{} {}'.format(typ, sm.ccode(a)))
cython_input_args.append('{} {}'.format(typ, ccode(a)))
else:
idexy = '{}_memview[i]'
memview = 'cdef double[::1] {}_memview = {}'
memory_views.append(memview.format(sm.ccode(a), sm.ccode(a)))
cython_input_args.append('{}'.format(sm.ccode(a)))
memory_views.append(memview.format(ccode(a), ccode(a)))
cython_input_args.append('{}'.format(ccode(a)))

indexed_input_args.append(idexy.format(sm.ccode(a)))
indexed_input_args.append(idexy.format(ccode(a)))

cython_indent = len('def {routine_name}_loop('.format(**d))
cython_arg_spacer = ',\n' + ' '*cython_indent
Expand Down
Loading