1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
#include "AverageAngleLinkage.hh"

template<typename Real_>
void AverageAngleLinkage_T<Real_>::m_constructAverageAngleToJointAngleMapTranspose() {
    // std::cout<<"Samara construct change of variable matrix!"<<std::endl;
    // Each joint has a single angle variable.
    const SuiteSparse_long m = Base::numJoints();
    const SuiteSparse_long n = Base::numJoints();
    const SuiteSparse_long nja = m_actuatedAngles.size();
    SuiteSparseMatrix result(m, n);
    result.nz = m + 2 * (nja - 1);
    // Now we fill out the transpose of the map one column (each joint angle) at a time:
    // # Average Angle Vars [            ]
    //                       # All Angles

    auto &Ax = result.Ax;
    auto &Ai = result.Ai;
    auto &Ap = result.Ap;

    Ax.reserve(result.nz);
    Ai.reserve(result.nz);
    Ap.push_back(0);
    size_t prevActAngleIndex = NONE;
    for (size_t ai = 0; ai < (size_t)n; ++ai) {
        if (isActuatedAngle(ai) && prevActAngleIndex != NONE) {
            Ai.push_back(prevActAngleIndex);
            Ax.push_back(-1);
            prevActAngleIndex = ai;
        }
            Ai.push_back(ai  );
            Ax.push_back( 1);
        if (isActuatedAngle(ai) && ai < m_lastActuatedAngle) {
            Ai.push_back(m_lastActuatedAngle);
            Ax.push_back( 1);
            prevActAngleIndex = ai;
        }
        Ap.push_back(Ai.size()); // col end.
    }

    assert(Ai.size() == size_t(result.nz));
    assert(Ap.size() == size_t(n+1      ));
    m_averageAngleToJointAngleMapTranspose = std::move(result);
}

template<typename Real_>
void AverageAngleLinkage_T<Real_>::m_constructAverageAngleToJointAngleMapTranspose_AllJointVars() {
    // Each joint has a single angle variable.
    size_t jointOffset = Base::dofOffsetForJoint(0);
    size_t nj = Base::numJoints();
    const SuiteSparse_long m = Base::dofOffsetForJoint(nj - 1) + Base::joint(nj - 1).numDoF() - jointOffset; // total number of joints DoFs
    const SuiteSparse_long n = m;
    const SuiteSparse_long nja = m_actuatedAngles.size();
    SuiteSparseMatrix result(m, n);
    result.nz = m + 2 * (nja - 1);
    // Now we fill out the transpose of the map one column (each joint angle) at a time:
    // # Average Angle Vars [            ]
    //                       # All Angles

    auto &Ax = result.Ax;
    auto &Ai = result.Ai;
    auto &Ap = result.Ap;

    Ax.reserve(result.nz);
    Ai.reserve(result.nz);
    Ap.push_back(0);
    size_t prevActAngleIndex = NONE;
    for (size_t ji = 0; ji < (size_t)n; ++ji) {
        if (isActuatedAngleVar(ji + jointOffset) && prevActAngleIndex != NONE) {
            size_t ai = getAngleIndexFromDoFIndex(ji + jointOffset);
            Ai.push_back(getDoFIndexFromAngleIndex(prevActAngleIndex) - jointOffset);
            Ax.push_back(-1);
            prevActAngleIndex = ai;
        }
        Ai.push_back(ji  );
        Ax.push_back( 1);
        if (isActuatedAngleVar(ji + jointOffset)) {
            size_t ai = getAngleIndexFromDoFIndex(ji + jointOffset);
            if (ai < m_lastActuatedAngle) {
                Ai.push_back(getDoFIndexFromAngleIndex(m_lastActuatedAngle) - jointOffset);
                Ax.push_back( 1);
                prevActAngleIndex = ai;
            }
        }
        Ap.push_back(Ai.size()); // col end.
    }

    assert(Ai.size() == size_t(result.nz));
    assert(Ap.size() == size_t(n+1      ));
    m_averageAngleToJointAngleMapTranspose_AllJointVars = std::move(result);
}

template<typename Real_>
void AverageAngleLinkage_T<Real_>::m_constructActuatedAngles(){
    if (m_freeAngles.size() == Base::numJoints()) throw std::runtime_error("Too many joints are left free.");
    m_actuatedAngles.clear();
    m_firstActuatedAngle = Base::numJoints();
    m_lastActuatedAngle = 0;
    for (size_t ji = 0; ji < Base::numJoints(); ji++){
        if (isActuatedAngle(ji)){
            m_actuatedAngles.push_back(ji);
            if (ji != NONE && ji < m_firstActuatedAngle) { m_firstActuatedAngle = ji; }
            if (ji != NONE && ji > m_lastActuatedAngle) { m_lastActuatedAngle = ji; }
        }
    }
    m_averageAngleIndex = getDoFIndexFromAngleIndex(m_lastActuatedAngle);
}


template<typename Real_>
void AverageAngleLinkage_T<Real_>::m_buildAngleIndexForDoFIndex() {
    m_angleIndexFromDoFIndex.resize(Base::numDoF());
    for (size_t i = 0; i < Base::numDoF(); ++i) m_angleIndexFromDoFIndex[i] = NONE;
    for (size_t ji = 0; ji < Base::numJoints(); ++ji) m_angleIndexFromDoFIndex[Base::dofOffsetForJoint(ji) + 6] = ji;
}

template<typename Real_>
size_t AverageAngleLinkage_T<Real_>::hessianNNZ(bool variableDesignParameters) const {
    size_t result = Base::hessianNNZ();
    // for (size_t ji = 0; ji < Base::numJoints() - 1; ++ji) {
    for (size_t ji = 0; ji < m_actuatedAngles.size() - 1; ++ji) {
        auto & j = Base::joint(m_actuatedAngles[ji]);
        auto & next_j = Base::joint(m_actuatedAngles[ji+1]);
        // The last average angle variable interacts with every other joint variables 
        // as well as the incident segments end edge variables that those joints control. 
        // The contribution of the last joint's average angle variable's interaction with its own variables are already counted. 
        result += j.numDoF() + j.valence() * 2 * (3 + 1); // per valence: 2 overlapping edges with 3 (position) + 1 (frame angle) DoFs
        // Every joint's angle variable interacts with the variables of the next joints as well as the incident segments end edge variables that those joints control. 
        result += next_j.numDoF() + next_j.valence() * 2 * (3 + 1);

        if (variableDesignParameters) {            
            throw std::runtime_error("variableDesignParameters is not implemented in hessianNNZ of AverageAngleLinkage!");
        }
    }
    // The contribution of the interaction between the last two angle variables is overcounted. 
    result -= 1;

    return result;
}

template<typename Real_>
auto AverageAngleLinkage_T<Real_>::hessianSparsityPattern(bool variableDesignParameters, Real_ val) const -> CSCMat {
    if (m_cachedHessianSparsity) return *m_cachedHessianSparsity;
    TMatrix result(Base::numDoF(), Base::numDoF()); 
    result.symmetry_mode = TMatrix::SymmetryMode::UPPER_TRIANGLE;

    if (variableDesignParameters) { throw std::runtime_error("AAL hsp is not implemented for design parameters yet!"); }
    auto baseHSP = Base::hessianSparsityPattern(variableDesignParameters, 0.0);
    for (const auto t : baseHSP) { result.addNZ(t.i, t.j, 1.0); }

    // Add new entries representing the coupling between the transformed angle variables. 
    // for (size_t ji = 0; ji < Base::numJoints() - 1; ++ji) {
    for (size_t ji = 0; ji < m_actuatedAngles.size() - 1; ++ji) {
        const auto &j = Base::joint(m_actuatedAngles[ji]);
        const size_t jo = Base::dofOffsetForJoint(m_actuatedAngles[ji]);
        const auto &next_j = Base::joint(m_actuatedAngles[ji + 1]);
        const size_t next_jo = Base::dofOffsetForJoint(m_actuatedAngles[ji + 1]);

        // Add coupling between the current joint angle variable and the next joint's all variables + its segments vars. 
        for (size_t k = 0; k < next_j.numDoF(); ++k) result.addNZ(jo + 6, next_jo + k, 1.0);
        auto addIdx = [&](const size_t idx) { result.addNZ(idx, jo + 6, 1.0); };
        next_j.visitInfluencedSegmentVars(6, addIdx);

        // Add coupling between the average angle variable (stored in the last joint) and the current joint's all variables + its segments vars. 
        for (size_t k = 0; k < j.numDoF(); ++k) result.addNZ(jo + k, m_averageAngleIndex, 1.0);

        auto addSecondIdx = [&](const size_t idx) { result.addNZ(idx, m_averageAngleIndex, 1.0); };
        j.visitInfluencedSegmentVars(6, addSecondIdx);

    }

    if (variableDesignParameters) { 
        throw std::runtime_error("AAL hsp is not implemented for design parameters yet!");
        // m_cachedHessianVarRLSparsity = std::make_unique<CSCMat>(result); 
        // m_cachedHessianVarRLSparsity ->fill(val);
        // return *m_cachedHessianVarRLSparsity;
    } else { 
        m_cachedHessianSparsity      = std::make_unique<CSCMat>(result); 
        m_cachedHessianSparsity      ->fill(val);
        if (size_t(m_cachedHessianSparsity->nz) != hessianNNZ()) throw std::runtime_error("Incorrect NNZ prediction: " + std::to_string(m_cachedHessianSparsity->nz) + " vs " + std::to_string(hessianNNZ()));

        return *m_cachedHessianSparsity;
    }

    return *m_cachedHessianSparsity;
}



template<typename Real_>
void AverageAngleLinkage_T<Real_>::hessian(CSCMat &H, EnergyType eType, const bool variableDesignParameters) const {
    assert(H.symmetry_mode == CSCMat::SymmetryMode::UPPER_TRIANGLE);
    BENCHMARK_SCOPED_TIMER_SECTION timer(mangledName() + ".hessian");
    if (variableDesignParameters) throw std::runtime_error("AvergaeAngleLinkage hessian is not implemented for variableDesignParameters!");
    // Compute Hessian using per-edge rest lengths
    auto baseH = Base::hessianSparsityPattern(variableDesignParameters);
    Base::hessian(baseH, eType, variableDesignParameters);
    const SuiteSparseMatrix &A = m_averageAngleToJointAngleMapTranspose_AllJointVars;

    std::vector<size_t> angleDoFIndices = Base::jointAngleDoFIndices();
    const size_t nj = Base::numJoints();
    const size_t jointOffset = Base::dofOffsetForJoint(0);
    const size_t njv = Base::dofOffsetForJoint(nj - 1) + Base::joint(nj - 1).numDoF() - jointOffset;

    // Copy the rods hessian over. 
    H.addWithSubSparsity(baseH, /* scale */ 1.0, /*  idx offset */ 0, /* block start */ 0, /* block end */ jointOffset);

    size_t hint = 0;
    for (size_t ji = 0; ji < njv; ++ji) {
        const size_t j = ji + jointOffset;

        // Loop over each output column "l" generated by angle variable "j"
        const size_t lend = A.Ap[ji + 1];
        for (size_t idx = A.Ap[ji]; idx < lend; ++idx) {
            const size_t l = A.Ai[idx] + jointOffset;
            const Real_ colMultiplier = A.Ax[idx];

            // Create entries for each input Hessian entry
            const size_t input_end = baseH.Ap[j + 1];
            for (size_t idx_in = baseH.Ap[j]; idx_in < input_end; ++idx_in) {
                const Real_ colVal = colMultiplier * baseH.Ax[idx_in];
                const size_t i = baseH.Ai[idx_in];
                if (i < jointOffset) { // left transformation is in the identity block
                    hint = H.addNZ(i, l, colVal, hint);
                }
                else {
                    // Loop over each output entry
                    const size_t jj = i - jointOffset;
                    size_t kprev = 0;
                    size_t kprev_idx = 0;
                    const size_t outrow_end = A.Ap[jj + 1];
                    for (size_t outrow_idx = A.Ap[jj]; outrow_idx < outrow_end; ++outrow_idx) {
                        const size_t k = A.Ai[outrow_idx] + jointOffset;
                        const Real_ val = A.Ax[outrow_idx] * colVal;
                        if (k <= l) {
                            // Accumulate entries from input's upper triangle
                            if (k == kprev) { H.addNZ(kprev_idx, val); }
                            else     { hint = H.addNZ(k, l, val, hint);
                                       kprev = k, kprev_idx = hint - 1; }
                        }
                        if ((i != j) && (l <= k)) H.addNZ(l, k, val); // accumulate entries from input's (strict) lower triangle
                    }
                }
            }
        }
    }

}

template<typename Real_>
void AverageAngleLinkage_T<Real_>::massMatrix(CSCMat &M, bool updatedSource, bool useLumped) const {
    BENCHMARK_SCOPED_TIMER_SECTION timer(mangledName() + ".massMatrix");
    assert(M.symmetry_mode == CSCMat::SymmetryMode::UPPER_TRIANGLE);

    // Compute Hessian using per-edge rest lengths
    auto baseM = Base::hessianSparsityPattern();
    Base::massMatrix(baseM, updatedSource, useLumped);
    const SuiteSparseMatrix &A = m_averageAngleToJointAngleMapTranspose_AllJointVars;

    std::vector<size_t> angleDoFIndices = Base::jointAngleDoFIndices();
    const size_t nj = Base::numJoints();
    const size_t jointOffset = Base::dofOffsetForJoint(0);
    const size_t njv = Base::dofOffsetForJoint(nj - 1) + Base::joint(nj - 1).numDoF() - jointOffset;

    // Copy the RodLinkage massMatrix over. 
    M.addWithSubSparsity(baseM, /* scale */ 1.0, /*  idx offset */ 0, /* block start */ 0, /* block end */ jointOffset);

    size_t hint = 0;
    for (size_t ji = 0; ji < njv; ++ji) {
        const size_t j = ji + jointOffset;

        // Loop over each output column "l" generated by angle variable "j"
        const size_t lend = A.Ap[ji + 1];
        for (size_t idx = A.Ap[ji]; idx < lend; ++idx) {
            const size_t l = A.Ai[idx] + jointOffset;
            const Real_ colMultiplier = A.Ax[idx];

            // Create entries for each input Hessian entry
            const size_t input_end = baseM.Ap[j + 1];
            for (size_t idx_in = baseM.Ap[j]; idx_in < input_end; ++idx_in) {
                const Real_ colVal = colMultiplier * baseM.Ax[idx_in];
                const size_t i = baseM.Ai[idx_in];
                if (i < jointOffset) { // left transformation is in the identity block
                    hint = M.addNZ(i, l, colVal, hint);
                }
                else {
                    // Loop over each output entry
                    const size_t jj = i - jointOffset;
                    size_t kprev = 0;
                    size_t kprev_idx = 0;
                    const size_t outrow_end = A.Ap[jj + 1];
                    for (size_t outrow_idx = A.Ap[jj]; outrow_idx < outrow_end; ++outrow_idx) {
                        const size_t k = A.Ai[outrow_idx] + jointOffset;
                        const Real_ val = A.Ax[outrow_idx] * colVal;
                        if (k <= l) {
                            // Accumulate entries from input's upper triangle
                            if (k == kprev) { M.addNZ(kprev_idx, val); }
                            else     { hint = M.addNZ(k, l, val, hint);
                                       kprev = k, kprev_idx = hint - 1; }
                        }
                        if ((i != j) && (l <= k)) M.addNZ(l, k, val); // accumulate entries from input's (strict) lower triangle
                    }
                }
            }
        }
    }

}


////////////////////////////////////////////////////////////////////////////////
// Explicit instantiation for ordinary double type and autodiff type.
////////////////////////////////////////////////////////////////////////////////
template struct AverageAngleLinkage_T<Real>;
template struct AverageAngleLinkage_T<ADReal>;