teqp 0.22.0
Loading...
Searching...
No Matches
iteration.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <optional>
4
6#include "teqp/cpp/derivs.hpp"
7#include "teqp/exceptions.hpp"
8
9#include <boost/multiprecision/cpp_bin_float.hpp>
10
11namespace teqp {
12namespace iteration {
13
14using namespace cppinterface;
15
17
24 const int N;
25 const Eigen::Array2d& x;
26 const Eigen::Array2d& dx;
27 const Eigen::Array2d& r;
28 const std::vector<int>& nonconstant_indices;
29};
30
38public:
40 virtual std::string desc() = 0;
41};
42
44private:
45 double threshold;
46public:
47 MaxAbsErrorCondition(double threshold) : threshold(threshold) {};
50 return (data.r.abs().maxCoeff() < threshold) ? s::success : s::keep_going;
51 };
52 std::string desc() override{
53 return "MaxAbsErrorCondition";
54 };
55};
56
58private:
59 double threshold;
60public:
61 MinRelStepsizeCondition(double threshold) : threshold(threshold) {};
62
68 double minval = 1e99;
69 for (auto idx : data.nonconstant_indices){
70 double val = std::abs(data.dx(idx)/data.x(idx));
71 minval = std::min(val, minval);
72 }
73 return (minval < threshold) ? s::success : s::keep_going;
74 };
75 std::string desc() override{
76 return "MinRelStepsizeCondition";
77 };
78};
79
81public:
87 bool allfinite = true;
88 for (auto idx : data.nonconstant_indices){
89 if (!std::isfinite(data.x(idx)) || !std::isfinite(data.dx(idx))){
90 allfinite = false; break;
91 }
92 }
93 return (!allfinite) ? s::fatal : s::keep_going;
94 };
95 std::string desc() override{
96 return "NanXDXErrorCondition";
97 };
98};
99
101public:
103 using s = StoppingConditionReason;
104 return ((data.x(data.nonconstant_indices) < 0).any()) ? s::fatal : s::keep_going;
105 };
106 std::string desc() override{
107 return "NegativeXErrorCondition";
108 };
109};
110
112private:
113 const std::size_t Nmax;
114public:
115 StepCountErrorCondition(int Nmax) : Nmax(Nmax){}
117 using s = StoppingConditionReason;
118 return (data.N >= Nmax) ? s::fatal : s::keep_going;
119 };
120 std::string desc() override{
121 return "StepCountErrorCondition";
122 };
123};
124
125
126
127
129public:
130 std::shared_ptr<AbstractModel> model_ideal_gas, model_residual;
131
132 template<typename Z>
133 auto get_R(const Z& z) const{
134 return model_residual->get_R(z);
135 }
136
137 auto get_R(const std::vector<double>& z) const{
138 const auto zz = Eigen::Map<const Eigen::ArrayXd>(&z[0], z.size());
139 return model_residual->get_R(zz);
140 }
141
142 template<typename Z>
143 Eigen::Array33d get_deriv_mat2(double T, double rho, const Z& z) const{
144 return model_ideal_gas->get_deriv_mat2(T, rho, z) + model_residual->get_deriv_mat2(T, rho, z);
145 }
146
147 Eigen::Array33d get_deriv_mat2(double T, double rho, const std::vector<double>& z) const{
148 const auto zz = Eigen::Map<const Eigen::ArrayXd>(&z[0], z.size());
149 return model_ideal_gas->get_deriv_mat2(T, rho, zz) + model_residual->get_deriv_mat2(T, rho, zz);
150 }
151
152 template<typename Z>
153 auto get_A00A10A01(double T, double rho, const Z& z) const{
154 auto A10 = model_ideal_gas->get_Ar10(T, rho, z) + model_residual->get_Ar10(T, rho, z);
155 Eigen::Array2d A00A01 = model_ideal_gas->get_Ar01n(T, rho, z) + model_residual->get_Ar01n(T, rho, z); // This gives [A00, A01] at the same time
156 return std::make_tuple(A00A01[0], A10, A00A01[1]);
157 }
158
169 template<typename Array>
170 auto get_vals(const std::vector<char>& vars, const double R, const double T, const double rho, const Array &z) const{
171
172 Array v(2);
173 auto Trecip = 1.0/T;
174 auto dTrecipdT = -Trecip*Trecip;
175
176 // Define some lambda functions for things that *might* be needed
177 // The lambdas are used to get a sort of lazy evaluation. The lambdas are
178 // only evaluated on an as-needed basis. If the lambda is not called,
179 // no cost is incurred.
180 //
181 // Probably the compiler will inline these functions anyhow.
182 //
183 auto [A00, A10, A01] = get_A00A10A01(T, rho, z);
184 // Derivatives of total alpha(Trecip, rho)
185 auto alpha = [&](){ return A00; };
186 auto dalphadTrecip = [&](){ return A10/Trecip; };
187 auto dalphadrho = [&](){ return A01/rho; };
188 // Derivatives of total Helmholtz energy a = alpha*R/Trecip in
189 // terms of derivatives of alpha(Trecip, rho)
190 auto a = [&](){ return alpha()*R*T; };
191 auto dadTrecip = [&](){ return R/(Trecip*Trecip)*(Trecip*dalphadTrecip()-alpha()); };
192 auto dadrho = [&](){ return R/Trecip*(dalphadrho()); };
193
194 for (auto i = 0; i < vars.size(); ++i){
195 switch(vars[i]){
196 case 'T':
197 v(i) = T; break;
198 case 'D':
199 v(i) = rho; break;
200 case 'P':
201 v(i) = rho*rho*dadrho(); break;
202 case 'S':
203 v(i) = Trecip*Trecip*dadTrecip(); break;
204 case 'H':
205 v(i) = a() + Trecip*dadTrecip() + rho*dadrho(); break;
206 case 'U':
207 v(i) = a() + Trecip*dadTrecip(); break;
208 default:
209 throw std::invalid_argument("bad var: " + std::to_string(vars[i]));
210 }
211 }
212 return v;
213 }
214};
215static_assert(std::is_copy_constructible_v<AlphaModel>);
216
220 std::string msg;
221 Eigen::Array2d Trho;
222};
223
229private:
230 const AlphaModel alphamodel;
231 const std::vector<char> vars;
232 const Eigen::Array2d vals;
233
234 const Eigen::ArrayXd z;
235 Eigen::Array2d Trho;
236 double R;
237
238 std::tuple<bool, bool> relative_error;
239 const std::vector<std::shared_ptr<StoppingCondition>> stopping_conditions;
240
241 int step_counter = 0;
242 std::string msg;
243 std::vector<int> nonconstant_indices;
244 bool isTD;
245
246public:
247 NRIterator(const AlphaModel& alphamodel, const std::vector<char>& vars, const Eigen::Array2d& vals, double T, double rho, const Eigen::Ref<const Eigen::ArrayXd>& z, const std::tuple<bool, bool> &relative_error, const std::vector<std::shared_ptr<StoppingCondition>> stopping_conditions) : alphamodel(alphamodel), vars(vars), vals(vals), Trho((Eigen::Array2d() << T,rho).finished()), z(z), R(alphamodel.get_R(z)), relative_error(relative_error), stopping_conditions(stopping_conditions) {
248 if(!(vars[0] == 'T' || vars[1] == 'T')){ nonconstant_indices.push_back(0); }
249 if(!(vars[0] == 'D' || vars[1] == 'D')){ nonconstant_indices.push_back(1); }
250 isTD = (nonconstant_indices.size() == 0);
251 }
252 bool verbose = false;
253
255 std::vector<char> get_vars() const { return vars; }
257 Eigen::Array2d get_vals() const { return vals; }
259 auto get_T() const { return Trho(0); }
261 auto get_rho() const { return Trho(1); }
263 Eigen::ArrayXd get_molefrac() const { return z; }
265 int get_step_count() const { return step_counter; }
267 std::string get_message() const { return msg; }
269 auto get_nonconstant_indices() const { return nonconstant_indices; }
270
271 void reset(double T, double rho){
272 Trho = (Eigen::Array2d() << T, rho).finished();
273 step_counter = 0;
274 }
275
281 auto calc_matrices(double T, double rho) const{
282 auto A = alphamodel.get_deriv_mat2(T, rho, z);
283 return build_iteration_Jv(vars, A, R, T, rho, z);
284 }
285 auto calc_step(double T, double rho) const{
286 auto im = calc_matrices(T, rho);
287 return std::make_tuple((im.J.matrix().colPivHouseholderQr().solve((-(im.v-vals)).matrix())).eval(), im);
288 }
289 auto calc_just_step(double T, double rho) const {
290 return std::get<0>(calc_step(T, rho));
291 }
292 auto calc_vals(double T, double rho) const{
293 return calc_matrices(T, rho).v;
294 }
295 auto calc_r(double T, double rho) const{
296 auto im = calc_matrices(T, rho);
297 Eigen::Array2d r = im.v-vals;
298 if (std::get<0>(relative_error)){ r(0) /= vals(0);}
299 if (std::get<1>(relative_error)){ r(1) /= vals(1);}
300 return r;
301 }
302 auto calc_J(double T, double rho) const{
303 auto im = calc_matrices(T, rho);
304 if (std::get<0>(relative_error)){ im.J.row(0) /= vals(0);}
305 if (std::get<1>(relative_error)){ im.J.row(1) /= vals(1);}
306 return im.J;
307 }
309 auto calc_maxabsr(double T, double rho) const{
310 Eigen::Array2d r = alphamodel.get_vals(vars, R, T, rho, z)-vals;
311 if (std::get<0>(relative_error)){ r(0) /= vals(0);}
312 if (std::get<1>(relative_error)){ r(1) /= vals(1);}
313 return r.abs().maxCoeff();
314 }
315
319 auto take_steps(int N){
320 if (N <= 0){
321 throw teqp::InvalidArgument("N must be greater than 0");
322 }
323 auto tic = std::chrono::steady_clock::now();
325 for (auto K = 0; K < N; ++K){
326 auto im = calc_matrices(Trho(0), Trho(1));
327 Eigen::Array2d r = im.v-vals;
328
329 if (std::get<0>(relative_error)){ r(0) /= vals(0); im.J.row(0) /= vals(0); }
330 if (std::get<1>(relative_error)){ r(1) /= vals(1); im.J.row(1) /= vals(1); }
331
332 Eigen::Array2d dTrho = im.J.matrix().fullPivLu().solve((-r).matrix());
333
334 if(isTD){
335 Trho += dTrho;
336 step_counter++;
337 reason = StoppingConditionReason::success; msg = "Only one step is needed for DT inputs";
338 break;
339 }
340
341 // Check whether a stopping condition (either good[complete] or bad[error])
342 bool stop = false;
343 const StoppingData data{N, Trho, dTrho, r, nonconstant_indices};
344 for (auto& condition : stopping_conditions){
345 using s = StoppingConditionReason;
346 auto this_reason = condition->stop(data);
347 if (this_reason != s::keep_going){
348 stop = true; reason = this_reason; msg = condition->desc(); break;
349 }
350 }
351
352 Trho += dTrho;
353 step_counter++;
354 if (stop){
355 break;
356 }
357 }
358 auto toc = std::chrono::steady_clock::now();
359// std::cout << "Time difference = " << std::chrono::duration_cast<std::chrono::nanoseconds>(toc - tic).count()/1e3/step_counter << "[µs/call]" << std::endl;
360 return reason;
361 }
362
363
367 auto take_steps_logrho(int N){
368 if (N <= 0){
369 throw teqp::InvalidArgument("N must be greater than 0");
370 }
371 auto tic = std::chrono::steady_clock::now();
373
374 for (auto K = 0; K < N; ++K){
375 auto im = calc_matrices(Trho(0), Trho(1));
376 Eigen::Array2d r = im.v-vals;
377
378 im.J.col(1) *= Trho(1); // This will make the step be [dT, dln(rho)] instead of [dT, drho]
379 if (std::get<0>(relative_error)){ r(0) /= vals(0); im.J.row(0) /= vals(0); }
380 if (std::get<1>(relative_error)){ r(1) /= vals(1); im.J.row(1) /= vals(1); }
381
382// Eigen::JacobiSVD<Eigen::Matrix2d> svd(im.J);
383// double cond = svd.singularValues()(0) / svd.singularValues()(svd.singularValues().size()-1);
384
385 Eigen::Array2d step;
386 if (false){//cond > 1){
387 // The idea in this block was to use extended precision to obtain the step
388 // but it seems to make no difference at all, because the condition number is a
389 // function of the problem, but the loss in precision is only meaningful relative to
390 // the number of digits of working precision available via the lost digits approximated
391 // by log10(kappa), where kappa is the condition number
392 using my_float = boost::multiprecision::number<boost::multiprecision::cpp_bin_float<164U>>;
393 Eigen::Matrix<my_float, 2, 2> Jmp = im.J.cast<my_float>();
394 Eigen::Vector<my_float, 2> rmp = r.cast<my_float>();
395 step = Jmp.fullPivLu().solve(-rmp).cast<double>();
396 Eigen::Array<my_float, 2, -1> newvals = (Trho.cast<my_float>() + Jmp.fullPivLu().solve(-rmp).array());
397
398 Eigen::Array2d new_double = (Trho + im.J.matrix().fullPivLu().solve((-r).matrix()).array());
399 std::cout << "-- " << ((newvals-new_double.cast<my_float>())/Trho.cast<my_float>()).cast<double>() << std::endl;
400 }
401 else{
402 step = im.J.matrix().fullPivLu().solve((-r).matrix());
403 }
404 Eigen::Array2d dTrho = (Eigen::Array2d() << step(0), Trho(1)*(exp(step(1))-1)).finished();
405
406 // Check whether a stopping condition (either good[complete] or bad[error])
407 bool stop = false;
408 const StoppingData data{N, Trho, dTrho, r, nonconstant_indices};
409 for (auto& condition : stopping_conditions){
410 using s = StoppingConditionReason;
411 auto this_reason = condition->stop(data);
412 if (this_reason != s::keep_going){
413 stop = true; reason = this_reason; msg = condition->desc(); break;
414 }
415 }
416
417 Trho += dTrho;
418 step_counter++;
419
420 if(nonconstant_indices.size() == 0){
421 stop = true; reason = StoppingConditionReason::success; msg = "Only one step is needed for DT inputs";
422 }
423// if(nonconstant_indices.size() == 2){
424// // If the step size gets too small on a relative basis then you
425// // need to stop
426// // There is a loss in precision caused by the Jacobian having a large condition number
427// // and use that to determine whether you need to stop stepping
428// //
429// // The condition number only is meaningful for 2x2 systems where both variables
430// // are being iterated for.
431// if ((dTrho/Trho)(nonconstant_indices).abs().minCoeff() < 1e-15*cond){
432// std::stringstream ss; ss << im.J;
433// stop = true; reason = StoppingConditionReason::success; msg = "MinRelStepSize ~= cond of " + std::to_string(cond) + ": J" + ss.str();
434// }
435// }
436 if (verbose){
437// std::cout << step_counter << "," << r.abs().maxCoeff() << "," << Trho << "|" << (dTrho/Trho).abs().minCoeff() << "cond: " << cond << std::endl;
438// std::cout << step_counter << "," << r.abs().maxCoeff() << "," << Trho << "|" << (dTrho/Trho).abs().minCoeff() << im.J.matrix() << im.v-vals << "cond: " << cond << std::endl;
439 }
440 if (stop){
441 break;
442 }
443 }
444 auto toc = std::chrono::steady_clock::now();
445// std::cout << "Time difference = " << std::chrono::duration_cast<std::chrono::nanoseconds>(toc - tic).count()/1e3/step_counter << "[µs/call]" << std::endl;
446 return reason;
447 }
448 auto path_integration(double T, double rho, std::size_t N){
449 // Start at the given value of T, rho
450
451 // Calculate the given variables
452 auto im = calc_matrices(T, rho);
453
454 Eigen::Array2d vals_current = im.v;
455 double x_current = vals_current(0);
456 double y_current = vals_current(1);
457
458 // Assume (for now) a linear path between starting point and the target point
459 double x_target = vals(0);
460 double y_target = vals(1);
461 auto dxdt = (x_target-x_current)/(1.0-0.0); // The 1.0 to make clear what is going on; we are integrating from 0 -> 1
462 auto dydt = (y_target-y_current)/(1.0-0.0);
463 double dt = 1.0/N;
464
465 // Determine the value of the residual function at the end point
466 for (auto i = 0U; i < N; ++i){
467
468 auto dxdT__rho = im.J(0, 0);
469 auto dxdrho__T = im.J(0, 1);
470 auto dydT__rho = im.J(1, 0);
471 auto dydrho__T = im.J(1, 1);
472
473 auto dxdT__y = dxdT__rho - dxdrho__T*dydT__rho/dydrho__T;
474 auto dxdrho__y = dxdrho__T - dxdT__rho*dydrho__T/dydT__rho;
475
476 auto dydT__x = dydT__rho - dydrho__T*dxdT__rho/dxdrho__T;
477 auto dydrho__x = dydrho__T - dydT__rho*dxdrho__T/dxdT__rho;
478
479 // Calculate the increment in temperature and density along the integration path (needs to be adaptive eventually)
480 double dTdt = dxdt/dxdT__y + dydt/dydT__x;
481 double drhodt = dxdt/dxdrho__y + dydt/dydrho__x;
482
483 T += dTdt*dt;
484 rho += drhodt*dt;
485 im = calc_matrices(T, rho);
486 if (verbose){
487 std::cout << i << "," << (im.v-vals).abs().maxCoeff() << std::endl;
488 }
489 }
490 return std::make_tuple(T, rho, im.v(0), im.v(1));
491 }
492};
493
494
495}
496}
std::shared_ptr< AbstractModel > model_residual
auto get_A00A10A01(double T, double rho, const Z &z) const
std::shared_ptr< AbstractModel > model_ideal_gas
auto get_R(const Z &z) const
auto get_R(const std::vector< double > &z) const
auto get_vals(const std::vector< char > &vars, const double R, const double T, const double rho, const Array &z) const
A convenience function for calculation of Jacobian terms of the form and where is one of the therm...
Eigen::Array33d get_deriv_mat2(double T, double rho, const std::vector< double > &z) const
Eigen::Array33d get_deriv_mat2(double T, double rho, const Z &z) const
StoppingConditionReason stop(const StoppingData &data) override
Definition iteration.hpp:48
StoppingConditionReason stop(const StoppingData &data) override
Definition iteration.hpp:66
Eigen::ArrayXd get_molefrac() const
Return the current mole fractions.
auto calc_maxabsr(double T, double rho) const
Calculate the maximum absolute value of the values in r.
std::vector< char > get_vars() const
Return the variables that are being used in the iteration.
auto calc_J(double T, double rho) const
auto get_T() const
Return the current temperature.
auto path_integration(double T, double rho, std::size_t N)
auto get_rho() const
Return the current molar density.
auto calc_vals(double T, double rho) const
std::string get_message() const
Return the current message relaying information about success or failure of the iteration.
auto calc_just_step(double T, double rho) const
auto get_nonconstant_indices() const
Get the indices of the nonconstant variables.
auto calc_step(double T, double rho) const
Eigen::Array2d get_vals() const
Return the target values to be obtained.
auto calc_matrices(double T, double rho) const
void reset(double T, double rho)
NRIterator(const AlphaModel &alphamodel, const std::vector< char > &vars, const Eigen::Array2d &vals, double T, double rho, const Eigen::Ref< const Eigen::ArrayXd > &z, const std::tuple< bool, bool > &relative_error, const std::vector< std::shared_ptr< StoppingCondition > > stopping_conditions)
int get_step_count() const
Return the current step counter.
auto calc_r(double T, double rho) const
StoppingConditionReason stop(const StoppingData &data) override
Definition iteration.hpp:85
StoppingConditionReason stop(const StoppingData &data) override
StoppingConditionReason stop(const StoppingData &data) override
virtual StoppingConditionReason stop(const StoppingData &)=0
virtual std::string desc()=0
auto build_iteration_Jv(const std::vector< char > &vars, const Eigen::Array< double, 3, 3 > &A, const double R, const double T, const double rho, const Array &z)
A convenience function for calculation of Jacobian terms of the form and where is one of the therm...
Definition derivs.hpp:26
StoppingConditionReason reason
const Eigen::Array2d & r
The set of residual functions.
Definition iteration.hpp:27
const std::vector< int > & nonconstant_indices
The indices where the independent variable can be varied (not T or rho)
Definition iteration.hpp:28
const Eigen::Array2d & dx
The complete set of steps in independent variables.
Definition iteration.hpp:26
const int N
The number of steps that have been taken so far.
Definition iteration.hpp:24
const Eigen::Array2d & x
The complete set of independent variables.
Definition iteration.hpp:25