A package for curve coupling analysis. This package helps finding the solution for a coupling of N curves with N-1 constraints (which gives a curve as a solution). In particular, this is used to compute the equilibrium points of a network of nonlinear compliant elements and their stability.
Made at: Soft Robotics Group @ KU Leuven
Curve Coupling is a Python package designed for analyzing and solving curve coupling problems. It provides tools for solving curve coupling problems with constraints. Applied to networks nonlinear of compliant elements, this package allows to
- Find the system constriants from its graph structure.
- Compute the equilibirum points.
- Find isolated islands.
- Find singularities and branching points.
- Compute the stability of the elements and the network
- Visualizing the results.
On GitHub
On GitLab
In the equality case, the constraint is that some dimension of the input curves must be equal. For example for a match dimension
The output for a solution point is just the average of the inputs,
In the general case, we define a constraint array
Similarly, we define the output array
In both cases, given an initial seed point, the solution is computed in the parametric space by a continuation algorithm based on computing the tangent to the solution at each step.
As an example, we consider the constraints
and output
The continuation algorithm solution process can be seen below.
Finding the equlibrium points of networks of non-linear compliant elements can be seen as a curve coupling problem. Therefore, the package provides tools to compute the network constraints and output arrays from the network graph, as well as computing the stability of the found equilibrium points from the stability of the individual elements.
To install the package, use pip:
pip install -e .This installs the package in editable mode, allowing modifications to the source code to take effect immediately.
You can generate curves using the built-in functions:
import numpy as np
from curveCoupling.curveGenerators import generate_curve_CubicSpline, generate_curve_Pchip, generate_curve_snaps
p0 = np.array([[0.0, 0.0], [0.2, 0.62], [0.35, 0.8], [0.45, 0.78], [0.45, 0.67], [
0.4, 0.52], [0.4, 0.41], [0.6, 0.44], [0.8, 0.55], [1.0, 1.0]])
data_lst = [
generate_curve_CubicSpline(p0, 200),
generate_curve_Pchip(p0, 200),
generate_curve_snaps(p0, 200)
]Comparison of generated curves:
Generate a function that interpolates and extrapolates data points as a parametric curve using the class ndcurves:
import numpy as np
from curveCoupling import ndcurve
curve = ndcurve(data)
t = np.linspace(0.0,1.0,200)
values = curve(t)
deriv = curve(t, nu=1)Resulting values:
Solve curve coupling problems efficiently (in case of equality constraints):
from curveCoupling import ndcurve, curveCouplingProblem_Equality, solveCurveCoupling_Equality, solveCurveCoupling_bruteForce_localSolve
curves = ndcurve.createList(data_lst)
match_index = 1
prob_eq = curveCouplingProblem_Equality(curves, match_index)
out, res = solveCurveCoupling_Equality(prob_eq)
out_brute, res_brute = solveCurveCoupling_bruteForce_localSolve(prob_eq, iter_points=10)Comparison with brute force results, we are missing the islands.
Find islands in curve coupling problems efficiently (in case of equality constraints):
from curveCoupling.curveCoupling_Analysis import solveCurveCoupling_Islands_Equality
out_lst, res_lst = solveCurveCoupling_Islands_Equality(prob)We now get the islands.
Deal with singularities in curve coupling problems efficiently (in case of equality constraints):
from curveCoupling.curveCoupling_Analysis import solveCurveCoupling_Singularities_Equality, findSingularities_Equality
sing_out, sing_seeds, sing_orders, sing_dirs = findSingularities_Equality(prob, tol=1e-3)
out_lst, res_lst = solveCurveCoupling_Singularities_Equality(prob, tol=1e-3)We get the different branches from the singular points.
Solve curve coupling problems efficiently (in case of general constraints):
from curveCoupling import ndcurve, curveCouplingProblem, solveCurveCoupling, solveCurveCoupling_bruteForce_localSolve
curves = ndcurve.createList(data_lst)
constraint_matrices = np.zeros((len(data_lst)-1, len(data_lst), data_lst[0].shape[1]))
output_matrices = np.zeros((data_lst[0].shape[1], len(data_lst), data_lst[0].shape[1]))
constraint_matrices[0,:,0] = np.array([1.0,-1.0,-1.0])
constraint_matrices[1,:,1] = np.array([0.0,1.0,-1.0])
output_matrices[0,:,0] = np.array([1.0,0.0,0.0])
output_matrices[1,:,1] = np.array([1.0,1.0,0.0])
prob = curveCouplingProblem(curves, constraint_matrices, output_matrices)
out, res = solveCurveCoupling(prob)
out_brute, res_brute = solveCurveCoupling_bruteForce_localSolve(prob, iter_points=10)Comparison with brute force results, we are missing the islands.
Find islands in curve coupling problems efficiently (in case of general constraints):
from curveCoupling.curveCoupling_Analysis import solveCurveCoupling_Islands
out_lst, res_lst = solveCurveCoupling_Islands(prob)We now get the islands.
Deal with singularities in curve coupling problems efficiently (in case of general constraints):
from curveCoupling.curveCoupling_Analysis import solveCurveCoupling_Singularities, findSingularities
sing_outs, sing_seeds, sing_orders, sing_dirs = findSingularities(prob, 10, tol=1e-3)
out_lst, res_lst = solveCurveCoupling_Singularities(prob, tol=1e-3)We get the different branches from the singular points.
In the case of separable problems, the problem can be inverted to find the input that would generate a given output. For this we need that:
- Each contraint applies to only one dimension of the curves (the same for all curves):
- The output is of the same dimension as the inputs and each component is a linear combination of the input components:
- The system must sufficiently constraint the input to compute. For example, if the direct problem is independent of one component of the curve to compute, then the inverse proble will be undetermined since this component cuold take any value.
Although not all problems are separable, most physical systems arising from interactions satisfy these conditions since. For example, in compliant elements networks, forces are compared to forces and displacements to displacements.
from curveCoupling import ndcurve, curveCouplingProblem_Split
# Transfrom in split problem
prob_split = prob.to_Split()
# Solve direct problem
out, res = solveCurveCoupling(prob_split)
# Invert problem for curve 0
solve_for_idx = 0
prob_inverse = prob_split.invert(solve_for_idx, ndcurve(out))
# Solve inverse problem
out_inverse, res_inverse = solveCurveCoupling(prob_inverse)The direct problem results:
Then, we invert the problem to compute the necessary curve to get the computed output. For this, we invert he equations and the roles of the output and input curve. This works to recompute any of the desired curves.
Visualize curve coupling results with matplotlib:
import matplotlib.pyplot as plt
from matplotlib import gridspec
fig = plt.figure()
gs = gridspec.GridSpec(2, 2 * len(data_lst))
axs = []
for i in range(0, len(data_lst)):
axs.append(fig.add_subplot(gs[0, 2 * i:2 * (i + 1)]))
axs.append(fig.add_subplot(gs[1, len(data_lst):]))
axs.append(fig.add_subplot(gs[1, :len(data_lst)], projection='3d'))
for i, d in enumerate(data_lst):
axs[i].plot(d[:, 0], d[:, 1])
for res in res_lst:
axs[-1].plot(res[:, 0], res[:, 1], res[:, 2])
axs[-1].scatter(res_brute[:, 0], res_brute[:, 1], res_brute[:, 2],
color='r', marker ='.',alpha=0.1)
for out in out_lst:
axs[-2].plot(out[:, 0], out[:, 1])
axs[-2].scatter(out_brute[:, 0], out_brute[:, 1],
color='r', marker ='.',alpha=0.1)
plt.show()Alternatively, use the provided default plot:
from curveCoupling.utils.defaultPlots import plotResults
fig = plt.figure()
axs = plotResults(fig, data_lst, out_lst, res_lst, out_brute, res_brute)
plt.show(block=False)Define a graph of the compliant elemnets network and compute the constraints and output matrices.
from curveCoupling.compliantElements import generate_network_equations, force_disp_to_matrices
edges = [
('Start', 'A'),
('A', 'B'),
('B', 'End'),
('Start', 'B'),
('A', 'End'),
]
disp_constr, force_constr, disp_out, force_out = generate_network_equations(edges)
ConstraintMatrices, OutputMatrices = generate_network_equations(
edges, return_in_joint_matrices=True)In the example, the graph is:
Start
|
-----
| |
| 1
| |
4 A----
| | |
| 2 |
| | |
----B 5
| |
3 |
| |
|----
|
End
The output is:
>>> disp_constr
[[-1. -1. 0. 1. 0.]
[ 0. -1. -1. 0. 1.]]
>>> force_constr
[[ 0. -1. 1. -1. 0.]
[-1. 1. 0. 0. 1.]]
>>> disp_out
[0. 0. 1. 1. 0.]
>>> force_out
[1. 0. 0. 1. 0.]
>>> ConstraintMatrices
[[[-1. 0.]
[-1. 0.]
[ 0. 0.]
[ 1. 0.]
[ 0. 0.]]
[[ 0. 0.]
[-1. 0.]
[-1. 0.]
[ 0. 0.]
[ 1. 0.]]
[[ 0. 0.]
[ 0. -1.]
[ 0. 1.]
[ 0. -1.]
[ 0. 0.]]
[[ 0. -1.]
[ 0. 1.]
[ 0. 0.]
[ 0. 0.]
[ 0. 1.]]]
>>> OutputMatrices
[[[0. 0.]
[0. 0.]
[1. 0.]
[1. 0.]
[0. 0.]]
[[0. 1.]
[0. 0.]
[0. 0.]
[0. 1.]
[0. 0.]]]Compute stability of an element from its force-displacement curve:
import numpy as np
from curveCoupling.compliantElements import getEigenVals, eigen2stability
eigen = getEigenVals(data)
stability = eigen2stability(eigen)We get the input stability evolution along the curve, assuming the initial point is stable.
Compute stability of a network from components (in case of equality constraints):
from curveCoupling.compliantElements import getEigenVals, getEigen_coupling_analytic_Equality, eigen2stability
eigen_input_lst = [getEigenVals(d) for d in data_lst]
stability_input_lst = [eigen2stability(e) for e in eigen_input_lst]]
eigen_analytic_lst = [getEigen_coupling_analytic_Equality(prob, r) for r in res_lst]
stability_analytic_lst = [eigen2stability(e) for e in eigen_analytic_lst]We get the input and output stabilities, including islands.
Compute stability of a network from components (in case of general constraints):
from curveCoupling.compliantElements import getEigenVals, getEigen_coupling_analytic, eigen2stability
eigen_input_lst = [getEigenVals(d) for d in data_lst]
stability_input_lst = [eigen2stability(e) for e in eigen_input_lst]
eigen_analytic_lst = [getEigen_coupling_analytic(prob, r) for r in res_lst]
stability_analytic_lst = [eigen2stability(e) for e in eigen_analytic_lst]We get the input and output stabilities, including islands.
Visualize curve coupling stability results with plot_stability to plot the stability of each curve:
import matplotlib.pyplot as plt
from matplotlib import gridspec
from curveCoupling.utils.defaultPlots import plot_stability
fig = plt.figure()
gs = gridspec.GridSpec(2, 2 * len(data_lst))
axs = []
for i in range(0, len(data_lst)):
axs.append(fig.add_subplot(gs[0, 2 * i:2 * (i + 1)]))
axs.append(fig.add_subplot(gs[1, len(data_lst):]))
axs.append(fig.add_subplot(gs[1, :len(data_lst)], projection='3d'))
for i, (d, s) in enumerate(zip(data_lst, stability_input_lst)):
plot_stability(axs[i], s, d[:, 0], d[:, 1])
for res, s in zip(res_lst, stability_analytic_lst):
plot_stability(axs[-1], s, res[:, 0], res[:, 1], res[:, 2])
for out, s in zip(out_lst, stability_analytic_lst):
plot_stability(axs[-2], s, out[:, 0], out[:, 1])
plt.show()Alternatively, use the provided default plot for a network:
from curveCoupling.utils.defaultPlots import plotResults_stability
fig = plt.figure()
plotResults_stability(fig, data_lst, stability_input_lst,
out_lst, res_lst, stability_analytic_lst)
plt.show()This project is licensed under the GNU GENERAL PUBLIC LICENSE. See the LICENSE file for details.
Contributions are welcome! Feel free to open an issue or submit a pull request on GitHub.
For questions or inquiries, please contact Franco N. Pinan Basualdo.















