Tetrapod Project
miqp_quadruped.cpp
Go to the documentation of this file.
2 
3 #include "ros/console.h"
4 
5 #include<cmath>
6 
8 
9 #define _USE_MATH_DEFINES
10 
11 namespace miqp{
12 namespace quadruped{
13 
14 using drake::solvers::MatrixXDecisionVariable;
15 
16 using drake::symbolic::sin;
17 
18 using drake::symbolic::cos;
19 
20 using Eigen::atan2;
21 
22 DecVars add_decision_variables(drake::solvers::MathematicalProgram &prog, Terrain &terrain, int n_steps, int n_legs)
23 {
24  DecVars decision_variables;
25 
26  int n_stones = terrain.getSteppingStones().rows();
27 
28  decision_variables.position = prog.NewContinuousVariables(n_steps, 3, "position");
29 
30  decision_variables.theta = prog.NewContinuousVariables(n_steps/n_legs + 1, 1, "theta");
31 
32  decision_variables.lin_sin = prog.NewContinuousVariables(n_steps/n_legs + 1, 1, "sin_theta");
33 
34  decision_variables.lin_cos = prog.NewContinuousVariables(n_steps/n_legs + 1, 1, "cos_theta");
35 
36  decision_variables.bin_sin = prog.NewBinaryVariables(n_steps/n_legs + 1, 5, "sin_selection");
37 
38  decision_variables.bin_cos = prog.NewBinaryVariables(n_steps/n_legs + 1, 5, "cos_selection");
39 
40  decision_variables.stone = prog.NewBinaryVariables(n_steps, n_stones, "stone");
41 
42  //decision_variables.sequence_offset = prog.NewBinaryVariables(4, 1, "sequence offset");
43 
44  return decision_variables;
45 }
46 
47 void set_initial_and_goal_position(drake::solvers::MathematicalProgram &prog, int n_steps, double length_legs, Leg step_sequence[], Terrain &terrain, DecVars &decision_variables, bool enforce_goal_hard)
48 {
49  MatrixXDecisionVariable &position = decision_variables.position;
50 
51  MatrixXDecisionVariable &theta = decision_variables.theta;
52 
53  //MatrixXDecisionVariable &sequence_offset = decision_variables.sequence_offset;
54  Eigen::Vector4d sequence_offset(1, 0, 0, 0);
55 
56  int n_legs = 4;
57 
58  double angle_divided = 2*M_PI/n_legs;
59 
60  double phis[] = {0.5*angle_divided, -0.5*angle_divided, 1.5*angle_divided, -1.5*angle_divided};
61 
62  double theta_0 = 0;
63 
64  Eigen::Vector3d center(terrain.getStoneByName("initial").getCenter());
65 
66  double angle_front_left = theta_0 + phis[front_left];
67 
68  double angle_front_right = theta_0 + phis[front_right];
69 
70  double angle_rear_left = theta_0 + phis[rear_left];
71 
72  double angle_rear_right = theta_0 + phis[rear_right];
73 
74  Eigen::Vector3d initial_position_front_left = center + length_legs*Eigen::Vector3d(std::cos(angle_front_left), std::sin(angle_front_left), 0);
75 
76  Eigen::Vector3d initial_position_rear_left = center + length_legs*Eigen::Vector3d(std::cos(angle_rear_left), std::sin(angle_rear_left), 0);
77 
78  Eigen::Vector3d initial_position_front_right = center + length_legs*Eigen::Vector3d(std::cos(angle_front_right), std::sin(angle_front_right), 0);
79 
80  Eigen::Vector3d initial_position_rear_right = center + length_legs*Eigen::Vector3d(std::cos(angle_rear_right), std::sin(angle_rear_right), 0);
81 
82  center = terrain.getStoneByName("goal").getCenter();
83 
84  //Enforce initial positions and goal positions
85  Eigen::Vector3d goal_position_front_left = center + length_legs*Eigen::Vector3d(std::cos(angle_front_left), std::sin(angle_front_left), 0);
86 
87  Eigen::Vector3d goal_position_rear_left = center + length_legs*Eigen::Vector3d(std::cos(angle_rear_left), std::sin(angle_rear_left), 0);
88 
89  Eigen::Vector3d goal_position_front_right = center + length_legs*Eigen::Vector3d(std::cos(angle_front_right), std::sin(angle_front_right), 0);
90 
91  Eigen::Vector3d goal_position_rear_right = center + length_legs*Eigen::Vector3d(std::cos(angle_rear_right), std::sin(angle_rear_right), 0);
92 
93  Eigen::Vector3d initial_positions[] = {initial_position_front_left, initial_position_front_right, initial_position_rear_left, initial_position_rear_right};
94 
95  Eigen::Vector3d goal_positions[] = {goal_position_front_left, goal_position_front_right, goal_position_rear_left, goal_position_rear_right};
96 
97  Eigen::Matrix<drake::symbolic::Expression, 3, 1> init_pos_i;
98 
99  Eigen::Matrix<drake::symbolic::Expression, 3, 1> goal_pos_i;
100 
101  for (int i = 0; i < n_legs; ++i)
102  {
103  init_pos_i = sequence_offset(0)*initial_positions[step_sequence[i]]
104  + sequence_offset(1)*initial_positions[step_sequence[(i + 1) % n_legs]]
105  + sequence_offset(2)*initial_positions[step_sequence[(i + 2) % n_legs]]
106  + sequence_offset(3)*initial_positions[(i + 3) % n_legs];
107 
108  auto constr_init = prog.AddLinearConstraint(position.row(i).transpose() == init_pos_i);
109 
110  if (enforce_goal_hard)
111  {
112  goal_pos_i = sequence_offset(0)*goal_positions[step_sequence[(n_steps + i) % n_legs]]
113  + sequence_offset(1)*goal_positions[step_sequence[(n_steps + i + 1) % n_legs]]
114  + sequence_offset(2)*goal_positions[step_sequence[(n_steps + i + 2) % n_legs]]
115  + sequence_offset(3)*goal_positions[(n_steps + i + 3) % n_legs];
116 
117  auto constr_goal = prog.AddLinearConstraint(position.row(n_steps - n_legs + i).transpose() == goal_pos_i);
118  }
119  }
120 
121  //Enforce initial and goal orientations
122 
123  prog.AddLinearConstraint(theta(0) == theta_0);
124 
125  prog.AddLinearConstraint(theta(theta.rows()-1) == theta_0);
126 
127  //Enforce exactly one offset variable is 1
128 
129  //prog.AddLinearConstraint((Eigen::Vector4d::Ones().transpose()*sequence_offset)(0) == 1);
130 }
131 
132 void theta_limits(drake::solvers::MathematicalProgram &prog, int n_steps, int n_legs, DecVars &decision_variables)
133 {
134  MatrixXDecisionVariable &theta = decision_variables.theta;
135 
136  MatrixXDecisionVariable &lin_sin = decision_variables.lin_sin;
137 
138  MatrixXDecisionVariable &lin_cos = decision_variables.lin_cos;
139 
140  MatrixXDecisionVariable &bin_sin = decision_variables.bin_sin;
141 
142  MatrixXDecisionVariable &bin_cos = decision_variables.bin_cos;
143 
144  double big_M = 2*M_PI;
145 
146  double sin_dividers[] = {0, 1, M_PI - 1, M_PI + 1, 2*M_PI - 1, 2*M_PI};
147 
148  double cos_dividers[] = {0, M_PI/2 - 1, M_PI/2 + 1, 3*M_PI/2 - 1, 3*M_PI/2 + 1, 2*M_PI};
149 
150  Eigen::Matrix<double, 5, 2> sin_coeffs = get_sin_coeffs();
151 
152  Eigen::Matrix<double, 5, 2> cos_coeffs = get_cos_coeffs();
153 
154 
155  for (int i = 0; i < n_steps/n_legs + 1; ++i)
156  {
157  // enforce theta to be within first cycle
158  prog.AddLinearConstraint(theta(i) >= 0);
159 
160  prog.AddLinearConstraint(theta(i) <= 2*M_PI);
161 
162  // enforce relation between theta and binary variables
163  for (int l = 0; l < 5; ++l)
164  {
165  prog.AddLinearConstraint(theta(i) >= sin_dividers[l] - (1 - bin_sin(i, l))*big_M);
166 
167  prog.AddLinearConstraint(theta(i) <= sin_dividers[l + 1] + (1 - bin_sin(i, l))*big_M);
168 
169  prog.AddLinearConstraint(theta(i) >= cos_dividers[l] - (1 - bin_cos(i, l))*big_M);
170 
171  prog.AddLinearConstraint(theta(i) <= cos_dividers[l + 1] + (1 - bin_cos(i, l))*big_M);
172  }
173  // enforce that exactly one line segment must be active at a time
174  auto sin_const = prog.AddLinearConstraint((bin_sin.row(i)*Eigen::VectorXd::Ones(5))(0) == 1);
175 
176  auto cos_const = prog.AddLinearConstraint((bin_cos.row(i)*Eigen::VectorXd::Ones(5))(0) == 1);
177 
178  // enforce linearized sin_theta according to which binary selection variable is active
179 
180  auto sin_upper = prog.AddLinearConstraint(lin_sin(i)*Eigen::VectorXd::Ones(5) - (sin_coeffs.col(0) + sin_coeffs.col(1)*theta(i)) <= (Eigen::VectorXd::Ones(5) - bin_sin.row(i).transpose())*big_M);
181 
182  auto sin_lower = prog.AddLinearConstraint(lin_sin(i)*Eigen::VectorXd::Ones(5) - (sin_coeffs.col(0) + sin_coeffs.col(1)*theta(i)) >= -(Eigen::VectorXd::Ones(5) - bin_sin.row(i).transpose())*big_M);
183 
184  auto cos_upper = prog.AddLinearConstraint(lin_cos(i)*Eigen::VectorXd::Ones(5) - (cos_coeffs.col(0) + cos_coeffs.col(1)*theta(i)) <= (Eigen::VectorXd::Ones(5) - bin_cos.row(i).transpose())*big_M);
185 
186  auto cos_lower = prog.AddLinearConstraint(lin_cos(i)*Eigen::VectorXd::Ones(5) - (cos_coeffs.col(0) + cos_coeffs.col(1)*theta(i)) >= -(Eigen::VectorXd::Ones(5) - bin_cos.row(i).transpose())*big_M);
187  }
188 }
189 
190 void geometry_limits(drake::solvers::MathematicalProgram &prog, int n_steps, int n_legs, double length_legs, Leg step_sequence[], Terrain &terrain, double bbox_len, DecVars &decision_variables)
191 {
192  MatrixXDecisionVariable &position = decision_variables.position;
193 
194  MatrixXDecisionVariable &lin_sin = decision_variables.lin_sin;
195 
196  MatrixXDecisionVariable &lin_cos = decision_variables.lin_cos;
197 
198  //MatrixXDecisionVariable &sequence_offset = decision_variables.sequence_offset;
199 
200  Eigen::Vector4d sequence_offset(1, 0, 0, 0);
201 
202  double angle_divided = 2*M_PI/n_legs;
203 
204  double phis[] = {0.5*angle_divided, -0.5*angle_divided, 1.5*angle_divided, -1.5*angle_divided};
205 
206  double phis_ordered[] = {phis[step_sequence[0]], phis[step_sequence[1]], phis[step_sequence[2]], phis[step_sequence[3]]};
207 
208  Eigen::Matrix<drake::symbolic::Expression, 2, 1> p_i;
209 
210  Eigen::Matrix<drake::symbolic::Expression, 2, 1> leg_offset;
211 
212  Eigen::Vector4d M = get_big_M(terrain);
213 
214  //Eigen::Matrix<drake::symbolic::Expression, 2, 1> M_conditional;
215 
216  for (int i = n_legs; i < n_steps; ++i)
217  {
218  p_i = (Eigen::VectorXd::Ones(n_legs).transpose()*position.block(i - n_legs, 0, n_legs, 2)/n_legs).transpose();
219 
220  leg_offset << length_legs*(lin_cos(i/n_legs)*cos(phis_ordered[i % n_legs]) - lin_sin(i/n_legs)*sin(phis_ordered[i % n_legs])),
221 
222  length_legs*(lin_sin(i/n_legs)*cos(phis_ordered[i % n_legs]) + lin_cos(i/n_legs)*sin(phis_ordered[i % n_legs]));
223 
224  prog.AddLinearConstraint(position.block(i, 0, 1, 2).transpose() - (p_i + leg_offset) <= Eigen::Vector2d::Constant(bbox_len/2));
225 
226  prog.AddLinearConstraint(position.block(i, 0, 1, 2).transpose() - (p_i + leg_offset) >= Eigen::Vector2d::Constant(-bbox_len/2));
227  }
228 }
229 
230 void relative_position_limits(drake::solvers::MathematicalProgram &prog, int n_steps, int n_legs, double step_span, double step_height, DecVars &decision_variables)
231 {
232  MatrixXDecisionVariable &position = decision_variables.position;
233 
234  for (int i = n_legs; i < n_steps; ++i)
235  {
236  prog.AddLinearConstraint(position(i, 0) - position(i - n_legs, 0) <= step_span/2);
237 
238  prog.AddLinearConstraint(position(i, 0) - position(i - n_legs, 0) >= -step_span/2);
239 
240  prog.AddLinearConstraint(position(i, 1) - position(i - n_legs, 1) <= step_span/2);
241 
242  prog.AddLinearConstraint(position(i, 1) - position(i - n_legs, 1) >= -step_span/2);
243 
244  prog.AddLinearConstraint(position(i, 2) - position(i - n_legs, 2) <= step_height);
245 
246  prog.AddLinearConstraint(position(i, 2) - position(i - n_legs, 2) >= -step_height);
247  }
248 }
249 
250 void one_stone_per_foot(drake::solvers::MathematicalProgram &prog, int n_steps, DecVars &decision_variables)
251 {
252  MatrixXDecisionVariable &stone = decision_variables.stone;
253 
254  auto stone_const = prog.AddLinearConstraint(stone*Eigen::VectorXd::Ones(stone.cols()) == Eigen::VectorXd::Ones(stone.rows()));
255 }
256 
257 Eigen::Vector4d get_big_M(Terrain terrain)
258 {
259  SteppingStone initial = terrain.getStoneByName("initial");
260 
261  SteppingStone goal = terrain.getStoneByName("goal");
262 
263  SteppingStone lateral = terrain.getStoneByName("lateral");
264 
265  Eigen::Vector2d M(goal.getCenter()(0) - initial.getCenter()(0), lateral.getTopRight()(1) - initial.getCenter()(1));
266 
267  Eigen::Vector4d res;
268 
269  res << 5, 5, 5, 5;
270 
271  return res;
272 }
273 
274 void foot_in_stepping_stone(drake::solvers::MathematicalProgram &prog, Terrain &terrain, int n_steps, DecVars &decision_variables)
275 {
276  MatrixXDecisionVariable &position = decision_variables.position;
277 
278  MatrixXDecisionVariable &stone = decision_variables.stone;
279 
280  Eigen::Vector4d M = get_big_M(terrain);
281 
282  Eigen::Matrix<double, 1, 1> M_height = Eigen::Matrix<double, 1, 1>::Constant(10);
283 
284  int n_stones = terrain.getSteppingStones().rows();
285 
286  Eigen::MatrixXd A_ineq, b_ineq, A_eq, b_eq;
287  for (int i = 0; i < n_steps; ++i)
288  {
289  for (int l = 0; l < n_stones ; ++l)
290  {
291  A_ineq = terrain.getSteppingStones()(l).getAIneq();
292 
293  b_ineq = terrain.getSteppingStones()(l).getBIneq();
294 
295  A_eq = terrain.getSteppingStones()(l).getAEq();
296 
297  b_eq = terrain.getSteppingStones()(l).getBEq();
298 
299  prog.AddLinearConstraint(A_ineq*position.row(i).transpose() <= b_ineq + (1 - stone(i, l))*M);
300 
301  prog.AddLinearConstraint(A_eq*position.row(i).transpose() <= b_eq + (1 - stone(i, l))*M_height);
302 
303  prog.AddLinearConstraint(A_eq*position.row(i).transpose() >= b_eq - (1 - stone(i, l))*M_height);
304  }
305  }
306 }
307 
308 void minimize_step_length(drake::solvers::MathematicalProgram &prog, int n_steps, int n_legs, DecVars &decision_variables)
309 {
310  MatrixXDecisionVariable &position = decision_variables.position;
311 
312  Eigen::Matrix<drake::symbolic::Expression, 2, 1> dist;
313 
314  for (int i = n_legs; i < n_steps; ++i)
315  {
316  dist = (position.row(i) - position.row(i - n_legs)).transpose();
317 
318  prog.AddQuadraticCost((dist.transpose()*dist)(0));
319  }
320 }
321 
322 void minimize_remaining_length(drake::solvers::MathematicalProgram &prog, Terrain &terrain, int n_steps, int n_legs, DecVars &decision_variables)
323 {
324  MatrixXDecisionVariable &position = decision_variables.position;
325 
326  Eigen::Matrix<drake::symbolic::Expression, 3, 1> center;
327 
328  Eigen::Matrix<drake::symbolic::Expression, 3, 1> dist;
329 
330  center = position.block(n_steps - n_legs, 0, n_legs, 3).transpose()*Eigen::Vector4d::Ones()/n_legs;
331 
332  dist = terrain.getStoneByName("goal").getCenter() - center;
333 
334  prog.AddQuadraticCost(10*(dist.transpose()*dist)(0));
335 }
336 
337 Eigen::Array<Eigen::MatrixXd, Eigen::Dynamic, 1> get_uncompressed_arrays(Eigen::MatrixXd &arr, Eigen::MatrixXd &sequence_offset, int n_steps, int n_legs, Leg step_sequence[])
338 {
339  int offset;
340 
341  for (int l = 0; l < n_legs; ++l)
342  {
343  if (bool(std::round(sequence_offset(l))))
344  {
345  offset = l;
346 
347  break;
348  }
349  }
350 
351  Eigen::Array<Eigen::MatrixXd, Eigen::Dynamic, 1> res;
352 
353  res.conservativeResize(n_legs);
354 
355  int ind;
356 
357  for (int l = 0; l < n_legs; ++l)
358  {
359  ind = step_sequence[(l + offset) % n_legs];
360 
361  res(ind) = Eigen::MatrixXd();
362 
363  res(ind).conservativeResize(n_steps - n_legs + 1, arr.cols());
364 
365  res(ind).row(0) << arr.row(l);
366 
367  for (int i = 1; i < res(ind).rows(); ++i)
368  {
369  if (((i + l - 1) % n_legs) == 0) //if the current step is being taken by this leg
370  {
371  res(ind).row(i) << arr.row(n_legs + i - 1);
372  }
373  else
374  {
375  res(ind).row(i) << res(ind).row(i - 1);
376  }
377  }
378  }
379  return res;
380 }
381 
382 DecVars_res get_decvars_res(DecVars_res_raw &decision_variables_raw, int n_steps, int n_legs, Leg step_sequence[])
383 {
384  Eigen::Array<Eigen::MatrixXd, Eigen::Dynamic, 1> positions = get_uncompressed_arrays(decision_variables_raw.position, decision_variables_raw.sequence_offset, n_steps, n_legs, step_sequence);
385 
386  Eigen::Array<Eigen::MatrixXd, Eigen::Dynamic, 1> stones = get_uncompressed_arrays(decision_variables_raw.stone, decision_variables_raw.sequence_offset, n_steps, n_legs, step_sequence);
387 
388  DecVars_res res;
389 
390  res.position_front_left = positions(0);
391 
392  res.position_front_right = positions(1);
393 
394  res.position_rear_left = positions(2);
395 
396  res.position_rear_right = positions(3);
397 
398  res.stone_front_left = stones(0);
399 
400  res.stone_front_right = stones(1);
401 
402  res.stone_rear_left = stones(2);
403 
404  res.stone_rear_right = stones(3);
405 
406  res.cost = decision_variables_raw.cost;
407 
408  res.position_ts = decision_variables_raw.position;
409 
410  res.success = decision_variables_raw.success;
411 
412  return res;
413 }
414 
415 void writeMatToFile(Eigen::MatrixXd &mat, std::string filename)
416 {
417  std::ofstream file(filename);
418 
419  if (file.is_open())
420  {
421  file.clear();
422 
423  file << mat;
424 
425  ROS_INFO("writeMatToFile: Successfully wrote to file");
426  } else
427  {
428  ROS_ERROR("could not open file");
429 
430  throw "could not open file " + filename;
431  }
432 
433  file.close();
434 }
435 
436 void writeDecVarsToFile(DecVars_res &decision_variables, std::string base_name)
437 {
438  std::string fend = ".csv";
439 
440  writeMatToFile(decision_variables.position_front_left, base_name + "_position_front_left" + fend);
441 
442  writeMatToFile(decision_variables.position_front_right, base_name + "_position_front_right" + fend);
443 
444  writeMatToFile(decision_variables.position_rear_left, base_name + "_position_rear_left" + fend);
445 
446  writeMatToFile(decision_variables.position_rear_right, base_name + "_position_rear_right" + fend);
447 
448  writeMatToFile(decision_variables.stone_front_left, base_name + "_stone_front_left" + fend);
449 
450  writeMatToFile(decision_variables.stone_front_right, base_name + "_stone_front_right" + fend);
451 
452  writeMatToFile(decision_variables.stone_rear_left, base_name + "_stone_rear_left" + fend);
453 
454  writeMatToFile(decision_variables.stone_rear_right, base_name + "_stone_rear_right" + fend);
455 
456  writeMatToFile(decision_variables.position_ts, base_name + "_position_ts" + fend);
457 }
458 
459 }
460 }
const Eigen::Vector3d & getCenter() const
const Eigen::Vector3d & getTopRight() const
const SteppingStone & getStoneByName(std::string name) const
Definition: terrain.cpp:95
const Eigen::Array< SteppingStone, Eigen::Dynamic, 1 > & getSteppingStones() const
Definition: terrain.h:23
Eigen::Vector3d Vector3d
Definition: kinematics.h:49
void step_sequence(drake::solvers::MathematicalProgram &prog, int n_steps, double step_span, DecVars &decision_variables)
Definition: miqp_biped.cpp:93
void theta_limits(drake::solvers::MathematicalProgram &prog, int n_steps, int n_legs, DecVars &decision_variables)
DecVars_res get_decvars_res(DecVars_res_raw &decision_variables_raw, int n_steps, int n_legs, Leg step_sequence[])
Eigen::Vector4d get_big_M(Terrain terrain)
void one_stone_per_foot(drake::solvers::MathematicalProgram &prog, int n_steps, DecVars &decision_variables)
void minimize_step_length(drake::solvers::MathematicalProgram &prog, int n_steps, int n_legs, DecVars &decision_variables)
void relative_position_limits(drake::solvers::MathematicalProgram &prog, int n_steps, int n_legs, double step_span, double step_height, DecVars &decision_variables)
void foot_in_stepping_stone(drake::solvers::MathematicalProgram &prog, Terrain &terrain, int n_steps, DecVars &decision_variables)
DecVars add_decision_variables(drake::solvers::MathematicalProgram &prog, Terrain &terrain, int n_steps, int n_legs)
Eigen::Array< Eigen::MatrixXd, Eigen::Dynamic, 1 > get_uncompressed_arrays(Eigen::MatrixXd &arr, Eigen::MatrixXd &sequence_offset, int n_steps, int n_legs, Leg step_sequence[])
void set_initial_and_goal_position(drake::solvers::MathematicalProgram &prog, int n_steps, double length_legs, Leg step_sequence[], Terrain &terrain, DecVars &decision_variables, bool enforce_goal_hard=true)
void writeMatToFile(Eigen::MatrixXd &mat, std::string filename)
void geometry_limits(drake::solvers::MathematicalProgram &prog, int n_steps, int n_legs, double length_legs, Leg step_sequence[], Terrain &terrain, double bbox_len, DecVars &decision_variables)
void writeDecVarsToFile(DecVars_res &decision_variables, std::string base_name="footstep_planner")
void minimize_remaining_length(drake::solvers::MathematicalProgram &prog, Terrain &terrain, int n_steps, int n_legs, DecVars &decision_variables)
int step_span
Definition: visualize.py:14
Eigen::MatrixXd stone_rear_right
Eigen::MatrixXd position_rear_left
Eigen::MatrixXd position_rear_right
Eigen::MatrixXd position_front_right
Eigen::MatrixXd stone_front_right
Eigen::MatrixXd stone_rear_left
Eigen::MatrixXd stone_front_left
Eigen::MatrixXd position_front_left
MatrixXDecisionVariable position
MatrixXDecisionVariable bin_cos
MatrixXDecisionVariable stone
MatrixXDecisionVariable bin_sin
MatrixXDecisionVariable lin_sin
MatrixXDecisionVariable theta
MatrixXDecisionVariable lin_cos
Eigen::MatrixXd get_cos_coeffs()
Definition: utils.cpp:40
Eigen::MatrixXd get_sin_coeffs()
Definition: utils.cpp:28