Skip to content

Commit d2985a2

Browse files
Complete finite difference jacobian
1 parent f048daa commit d2985a2

File tree

3 files changed

+120
-13
lines changed

3 files changed

+120
-13
lines changed

include/cantera/zeroD/ReactorNet.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,45 @@ class ReactorNet : public FuncEval
299299
//! @param settings the settings map propagated to all reactors and kinetics objects
300300
virtual void setDerivativeSettings(AnyMap& settings);
301301

302+
//! Calculate the system Jacobian using a finite difference method.
303+
//!
304+
//! This method is used only for informational purposes. Jacobian calculations
305+
//! for the full reactor system are handled internally by CVODES.
306+
//!
307+
//! @warning This method is an experimental part of the %Cantera
308+
//! API and may be changed or removed without notice.
309+
virtual Eigen::SparseMatrix<double> finiteDifferenceJacobian();
310+
311+
//! A wrapper method to calculate the system jacobian as Eigen::SparseMatrix<double>
312+
//! @warning Depending on the particular implementation, this may return an
313+
//! approximate Jacobian intended only for use in forming a preconditioner for
314+
//! iterative solvers.
315+
//!
316+
//! @warning This method is an experimental part of the %Cantera
317+
//! API and may be changed or removed without notice.
318+
virtual Eigen::SparseMatrix<double> jacobian() {
319+
vector<Eigen::Triplet<double>> jac_trips;
320+
// Add before, during, after evals
321+
buildJacobian(jac_trips);
322+
// construct jacobian from vector
323+
Eigen::SparseMatrix<double> jac(m_nv, m_nv);
324+
jac.setFromTriplets(jac_trips.begin(), jac_trips.end());
325+
return jac;
326+
}
327+
302328
protected:
329+
//! Calculate the Jacobian of the entire reactor network.
330+
//! @param jac_vector vector where jacobian triplets are added
331+
//! @param offset offset added to the row and col indices of the elements
332+
//! @warning Depending on the particular implementation, this may return an
333+
//! approximate Jacobian intended only for use in forming a preconditioner for
334+
//! iterative solvers.
335+
//! @ingroup derivGroup
336+
//!
337+
//! @warning This method is an experimental part of the %Cantera
338+
//! API and may be changed or removed without notice.
339+
virtual void buildJacobian(vector<Eigen::Triplet<double>>& jacVector);
340+
303341
//! Check that preconditioning is supported by all reactors in the network
304342
virtual void checkPreconditionerSupported() const;
305343

src/zeroD/ReactorNet.cpp

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -545,20 +545,10 @@ void ReactorNet::preconditionerSetup(double t, double* y, double gamma)
545545
updateState(yCopy.data());
546546
// Create jacobian triplet vector
547547
vector<Eigen::Triplet<double>> jac_vector;
548-
vector<size_t> jstarts;
549-
// Get jacobians and give elements to preconditioners
550-
jstarts.push_back(jac_vector.size());
551-
for (size_t i = 0; i < m_reactors.size(); i++) {
552-
m_reactors[i]->buildJacobian(jac_vector);
553-
jstarts.push_back(jac_vector.size());
554-
}
548+
buildJacobian(jac_vector);
555549
// Add to preconditioner with offset
556-
for (size_t i=0; i < m_reactors.size(); i++) {
557-
for (size_t j = jstarts[i]; j < jstarts[i+1]; j++) {
558-
auto it = jac_vector[j];
559-
precon->setValue(it.row() + m_start[i], it.col() + m_start[i],
560-
it.value());
561-
}
550+
for (auto it : jac_vector) {
551+
precon->setValue(it.row(), it.col(), it.value());
562552
}
563553
// post reactor setup operations
564554
precon->setup();
@@ -587,4 +577,72 @@ void ReactorNet::checkPreconditionerSupported() const {
587577
}
588578
}
589579

580+
void ReactorNet::buildJacobian(vector<Eigen::Triplet<double>>& jacVector)
581+
{
582+
// network must be initialized for the jacobian
583+
if (! m_init) {
584+
initialize();
585+
}
586+
// Create jacobian triplet vector
587+
vector<size_t> jstarts;
588+
// Get jacobians and give elements to preconditioners
589+
jstarts.push_back(jacVector.size());
590+
for (size_t i = 0; i < m_reactors.size(); i++) {
591+
m_reactors[i]->buildJacobian(jacVector);
592+
jstarts.push_back(jacVector.size());
593+
}
594+
// Add to preconditioner with offset
595+
for (size_t i=0; i < m_reactors.size(); i++) {
596+
for (size_t j = jstarts[i]; j < jstarts[i+1]; j++) {
597+
auto it = jacVector[j];
598+
auto newTrip = Eigen::Triplet<double>(it.row() + m_start[i], it.col()
599+
+ m_start[i], it.value());
600+
jacVector[j] = newTrip;
601+
}
602+
}
603+
}
604+
605+
Eigen::SparseMatrix<double> ReactorNet::finiteDifferenceJacobian()
606+
{
607+
// network must be initialized for the jacobian
608+
if (! m_init) {
609+
initialize();
610+
}
611+
612+
// clear former jacobian elements
613+
vector<Eigen::Triplet<double>> jac_trips;
614+
615+
// Get the current state
616+
Eigen::ArrayXd yCurrent(m_nv);
617+
getState(yCurrent.data());
618+
619+
Eigen::ArrayXd yPerturbed = yCurrent;
620+
Eigen::ArrayXd ydotCurrent(m_nv), ydotPerturbed(m_nv);
621+
622+
eval(m_time, yCurrent.data(), ydotCurrent.data(), m_sens_params.data());
623+
double rel_perturb = std::sqrt(std::numeric_limits<double>::epsilon());
624+
625+
for (size_t j = 0; j < m_nv; j++) {
626+
yPerturbed = yCurrent;
627+
double delta_y = std::max(std::abs(yCurrent[j]), 1000 * m_atols) * rel_perturb;
628+
yPerturbed[j] += delta_y;
629+
ydotPerturbed = 0;
630+
eval(m_time, yPerturbed.data(), ydotPerturbed.data(), m_sens_params.data());
631+
// d ydot_i/dy_j
632+
for (size_t i = 0; i < m_nv; i++) {
633+
if (ydotCurrent[i] != ydotPerturbed[i]) {
634+
jac_trips.emplace_back(
635+
static_cast<int>(i), static_cast<int>(j),
636+
(ydotPerturbed[i] - ydotCurrent[i]) / delta_y);
637+
}
638+
}
639+
}
640+
updateState(yCurrent.data());
641+
642+
Eigen::SparseMatrix<double> jac(m_nv, m_nv);
643+
jac.setFromTriplets(jac_trips.begin(), jac_trips.end());
644+
return jac;
645+
}
646+
647+
590648
}

test/python/test_reactor.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,6 +1200,17 @@ class TestIdealGasMoleReactor(TestMoleReactor):
12001200
reactorClass = ct.IdealGasMoleReactor
12011201
test_preconditioner_unsupported = None
12021202

1203+
@pytest.mark.diagnose
1204+
def test_heat_transfer_network(self):
1205+
# Result should be the same if (m * cp) / (U * A) is held constant
1206+
self.make_reactors(T1=300, T2=1000)
1207+
self.add_wall(U=200, A=1.0)
1208+
self.net.preconditioner = ct.AdaptivePreconditioner()
1209+
print(self.net.finite_difference_jacobian)
1210+
# self.net.advance(1.0)
1211+
# T1a = self.r1.T
1212+
# T2a = self.r2.T
1213+
12031214
def test_adaptive_precon_integration(self):
12041215
# Network one with non-mole reactor
12051216
net1 = ct.ReactorNet()

0 commit comments

Comments
 (0)