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

Switched to Cython memoryviews #170

Merged
merged 6 commits into from
Jun 23, 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]
- I : car yaw moment of inertia, [kg m^2]
- Izz : 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 @@ -40,7 +40,7 @@

# %%
# Generate the nonholonomic equations of motion of the system.
m, I, a, b = sm.symbols('m, I, a, b', real=True)
m, Izz, a, b = sm.symbols('m, Izz, 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 @@ -76,9 +76,9 @@
Pf.vel(N).dot(B.y),
]

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

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

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

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

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

# %%
# Specify the known system parameters.
par_map = {
I: 1.0,
Izz: 1.0,
m: 1.0,
g: 9.81,
d: 1.0,
Expand All @@ -51,7 +51,8 @@
obj, obj_grad = create_objective_function(obj_func, state_symbols,
specified_symbols, tuple(),
num_nodes,
node_time_interval=interval_value)
node_time_interval=interval_value,
time_symbol=t)

# %%
# Specify the symbolic instance constraints, i.e. initial and end conditions,
Expand All @@ -71,11 +72,11 @@

# %%
# Create an optimization problem.
prob = Problem(obj, obj_grad, eom, state_symbols,
num_nodes, interval_value,
prob = Problem(obj, obj_grad, eom, state_symbols, num_nodes, interval_value,
known_parameter_map=par_map,
instance_constraints=instance_constraints,
bounds=bounds)
bounds=bounds,
time_symbol=t)

# %%
# Use a random positive initial guess.
Expand Down
4 changes: 2 additions & 2 deletions opty/tests/test_direct_collocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def test_pendulum():
interval_value = duration / (num_nodes - 1)

# Symbolic equations of motion
I, m, g, d, t = sym.symbols('I, m, g, d, t')
I, m, g, d, t = sym.symbols('Ix, m, g, d, t')
theta, omega, T = sym.symbols('theta, omega, T', cls=sym.Function)

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

def setup_method(self):

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

Expand Down
6 changes: 3 additions & 3 deletions opty/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def setup_method(self):
self.q = len(self.input_symbols)
self.r = len(self.unknown_symbols)
self.N = 20

self.x_vals = np.random.random(self.N)
self.v_vals = np.random.random(self.N)
self.f1_vals = np.random.random(self.N)
Expand Down Expand Up @@ -109,7 +109,7 @@ def test_backward_all(self):
obj_expr = (
sym.Integral(self.x ** 2 + self.m ** 2, self.t) +
sym.Integral(self.c ** 2 * self.f2 ** 2 , self.t) +
sym.sin(self.k) ** 2
sym.sin(self.k) ** 2
)
obj, obj_grad = utils.create_objective_function(
obj_expr, self.state_symbols, self.input_symbols,
Expand Down Expand Up @@ -163,7 +163,7 @@ def test_midpoint_all(self):
obj_expr = (
sym.Integral(self.x ** 2 + self.m ** 2, self.t) +
sym.Integral(self.c ** 2 * self.f2 ** 2 , self.t) +
sym.sin(self.k) ** 2
sym.sin(self.k) ** 2
)
x_mid = (self.x_vals[1:] + self.x_vals[:-1]) / 2
f2_mid = (self.f2_vals[1:] + self.f2_vals[:-1]) / 2
Expand Down
49 changes: 34 additions & 15 deletions opty/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,10 @@ def parse_free(free, n, q, N, variable_duration=False):
return free_states, free_specified, free_constants


def create_objective_function(
objective, state_symbols, input_symbols, unknown_symbols,
N, node_time_interval=1.0, integration_method="backward euler"):
def create_objective_function(objective, state_symbols, input_symbols,
unknown_symbols, N, node_time_interval=1.0,
integration_method="backward euler",
time_symbol=me.dynamicsymbols._t):
"""Creates function to evaluate the objective and objective gradient.

Parameters
Expand All @@ -278,6 +279,8 @@ def create_objective_function(
integration_method : str, optional
The method used to integrate the system. The default is "backward
euler".
time_symbol : sympy.Symbol
If not supplied, ``sympy.physics.mechanics.dynamicsymbols._t`` is used.

"""
def lambdify_function(expr, multiplication_array, take_sum):
Expand All @@ -298,7 +301,7 @@ def parse_expr(expr, in_integral=False):
if isinstance(expr, sm.Integral):
if in_integral:
raise NotImplementedError("Nested integrals are not supported.")
if expr.limits != ((me.dynamicsymbols._t,),):
if expr.limits != ((time_symbol,),):
raise NotImplementedError(
"Only indefinite integrals of time are supported.")
return int_placeholder(parse_expr(expr.function, True))
Expand Down Expand Up @@ -413,18 +416,24 @@ def sort_sympy(seq):
cimport cython

cdef extern from "{file_prefix}_h.h"{head_gil}:
void {routine_name}(double matrix[{matrix_output_size}], {input_args})
void {routine_name}(double matrix[{matrix_output_size}],
{input_args})

@cython.boundscheck(False)
@cython.wraparound(False)
def {routine_name}_loop(np.ndarray[np.double_t, ndim=2] matrix, {numpy_typed_input_args}):
def {routine_name}_loop(matrix,
{numpy_typed_input_args}):

cdef double[:, ::1] matrix_memview = matrix
{memory_views}

cdef int n = matrix.shape[0]

cdef int i

for i in {loop_sig}:
{routine_name}(&matrix[i, 0], {indexed_input_args})
{routine_name}(&matrix_memview[i, 0],
{indexed_input_args})

return matrix.reshape(n, {num_rows}, {num_cols})
"""
Expand Down Expand Up @@ -604,31 +613,41 @@ def ufuncify_matrix(args, expr, const=None, tmp_dir=None, parallel=False,
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]
d['input_args'] = c_arg_spacer.join(input_args)
d['input_args'] = ' '*c_indent + c_arg_spacer.join(input_args)

cython_input_args = []
indexed_input_args = []
memory_views = []
for a in args:
if const is not None and a in const:
typ = 'double'
idexy = '{}'
cython_input_args.append('{} {}'.format(typ, sm.ccode(a)))
else:
typ = 'np.ndarray[np.double_t, ndim=1]'
idexy = '{}[i]'
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)))

cython_input_args.append('{} {}'.format(typ, sm.ccode(a)))
indexed_input_args.append(idexy.format(sm.ccode(a)))

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

loop_indent = len(' {routine_name}('.format(**d))
loop_spacer = ',\n' + ' '*loop_indent

d['numpy_typed_input_args'] = (' '*cython_indent +
cython_arg_spacer.join(cython_input_args))

d['numpy_typed_input_args'] = cython_arg_spacer.join(cython_input_args)
d['indexed_input_args'] = (' '*loop_indent +
loop_spacer.join(indexed_input_args))

d['indexed_input_args'] = ',\n'.join(indexed_input_args)
d['memory_views'] = '\n '.join(memory_views)

files = {}
files[d['file_prefix'] + '_c.c'] = _c_template.format(**d)
Expand Down