Skip to content

Commit 7acc5b5

Browse files
committed
core submodule update, major warnings and bug fixing
1 parent ef32b1c commit 7acc5b5

File tree

7 files changed

+54
-69
lines changed

7 files changed

+54
-69
lines changed

fdaPDE/core

Submodule core updated 41 files

fdaPDE/src/distributions.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ constexpr double adaptive_simpson_integrate(FunctorT&& f, double a, double b, do
6868
}
6969

7070
// computes the lower incomplete gamma function gamma(a, x) = \int_0^x (t^{a-1} * exp(-t))dt
71-
constexpr double lower_incomplete_gamma(double a, double x) {
71+
inline double lower_incomplete_gamma(double a, double x) {
7272
if (almost_zero(x)) { return 0.0; }
7373
// lower incomplete gamma integrand
7474
auto f = [a](double t) {
@@ -82,9 +82,9 @@ constexpr double lower_incomplete_gamma(double a, double x) {
8282
return adaptive_simpson_integrate(f, 0.0, x, 1e-10); // integral approximation by adaptive Simpson rule
8383
}
8484
// normalized lower incomplete gamma function
85-
constexpr double gamma_p(double a, double x) { return lower_incomplete_gamma(a, x) / std::tgamma(a); }
85+
inline double gamma_p(double a, double x) { return lower_incomplete_gamma(a, x) / std::tgamma(a); }
8686
// inverse error function (based on Newton-Rapson root finder)
87-
constexpr double inverse_erf(double x, double eps = 1e-10) {
87+
inline double inverse_erf(double x, double eps = 1e-10) {
8888
double y = 0.0;
8989
double delta;
9090
do {
@@ -188,7 +188,7 @@ struct rademacher_distribution : public internals::distribution_base<std::bernou
188188
template <typename InputType> constexpr result_type pdf(InputType x) const {
189189
return (x == 1 || x == -1) ? 0.5 : 0.0;
190190
}
191-
constexpr result_type cdf(double x) const { return x < -1 ? 0 : ((-1 <= x < 1) ? 0.5 : 1.0); }
191+
constexpr result_type cdf(double x) const { return x < -1 ? 0 : ((-1 <= x && x < 1) ? 0.5 : 1.0); }
192192
constexpr result_type mean() const { return 0.0; }
193193
constexpr result_type variance() const { return 1.0; }
194194
// random sampling
@@ -389,14 +389,14 @@ class normal_distribution : public internals::distribution_base<std::normal_dist
389389
constexpr normal_distribution() noexcept = default;
390390
constexpr normal_distribution(param_type mu, param_type sigma) : Base(mu, sigma), mu_(mu), sigma_(sigma) { }
391391
// density function
392-
constexpr result_type pdf(double x) const {
392+
result_type pdf(double x) const {
393393
constexpr double pi = std::numbers::pi;
394394
return 1.0 / (std::sqrt(2 * pi) * sigma_) * std::exp(-std::pow(x - mu_, 2) / (2 * std::pow(sigma_, 2)));
395395
}
396-
constexpr result_type cdf(double x) const { return 0.5 * (1 + std::erf((x - mu_) / (sigma_ * std::sqrt(2)))); }
396+
result_type cdf(double x) const { return 0.5 * (1 + std::erf((x - mu_) / (sigma_ * std::sqrt(2)))); }
397397
constexpr param_type mean() const { return mu_; }
398398
constexpr param_type variance() const { return sigma_ * sigma_; }
399-
constexpr double quantile(double alpha) const { return std::sqrt(2.0) * internals::inverse_erf(2.0 * alpha - 1.0); }
399+
double quantile(double alpha) const { return std::sqrt(2.0) * internals::inverse_erf(2.0 * alpha - 1.0); }
400400
// random sampling
401401
template <typename RandomNumberGenerator> result_type operator()(RandomNumberGenerator& rng) { return distr_(rng); }
402402

fdaPDE/src/models/gsr.h

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,15 @@ template <typename VariationalSolver, typename Distribution> class GSRPDE {
5151
// Functional penalized iterative reweighted least squares
5252
template <typename... Args> auto fit(Args&&... args) {
5353
vector_t lambda(n_lambda);
54-
internals::for_each_index_and_args<sizeof...(Args)>([&]<int Ns_, typename Ts_>(const Ts_& ts) {
55-
if (Ns_ < n_lambda) {
56-
fdapde_static_assert(std::is_convertible_v<Ts_ FDAPDE_COMMA double>, INVALID_SMOOTHING_PARAMETER_TYPE);
57-
lambda[Ns_] = ts;
58-
}
59-
});
54+
internals::for_each_index_and_args<sizeof...(Args)>(
55+
[&]<int Ns_, typename Ts_>(const Ts_& ts) {
56+
if (Ns_ < n_lambda) {
57+
fdapde_static_assert(
58+
std::is_convertible_v<Ts_ FDAPDE_COMMA double>, INVALID_SMOOTHING_PARAMETER_TYPE);
59+
lambda[Ns_] = ts;
60+
}
61+
},
62+
args...);
6063
// initialize mean vector
6164
vector_t y = y_;
6265
solver_.update_response_and_weights(y, vector_t::Ones(n_obs_).asDiagonal()); // restore solver state

fdaPDE/src/solvers/fe_ls_elliptic.h

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ struct fe_ls_elliptic {
3232
using diag_matrix_t = Eigen::DiagonalMatrix<double, Dynamic, Dynamic>;
3333
using sparse_solver_t = eigen_sparse_solver_movable_wrap<Eigen::SparseLU<sparse_matrix_t>>;
3434
using dense_solver_t = Eigen::PartialPivLU<matrix_t>;
35+
using size_t = std::size_t;
3536
template <typename DataLocs>
3637
static constexpr bool is_valid_data_locs_descriptor_v =
3738
std::is_same_v<DataLocs, matrix_t> || std::is_same_v<DataLocs, binary_t>;
@@ -80,7 +81,7 @@ struct fe_ls_elliptic {
8081
}
8182
void enforce_lhs_dirichlet_bc_(SparseBlockMatrix<double, 2, 2>& A) {
8283
if (dirichlet_dofs_.size() == 0) { return; }
83-
for (int i = 0; i < dirichlet_dofs_.size(); ++i) {
84+
for (size_t i = 0; i < dirichlet_dofs_.size(); ++i) {
8485
// zero out row and column in correspondance of Dirichlet-type dofs
8586
A.row(dirichlet_dofs_[i]) *= 0;
8687
A.col(dirichlet_dofs_[i]) *= 0;
@@ -206,7 +207,7 @@ struct fe_ls_elliptic {
206207
for (const std::string& token : formula_.rhs()) {
207208
if (gf.contains(token)) { covs.push_back(token); }
208209
}
209-
bool require_woodbury_realloc = n_covs_ != covs.size();
210+
bool require_woodbury_realloc = std::cmp_not_equal(n_covs_, covs.size());
210211
n_covs_ = covs.size();
211212
const auto& y_data = gf[0].data().template col<double>(formula_.lhs());
212213
y_.resize(n_locs_, y_data.blk_sz());
@@ -238,7 +239,9 @@ struct fe_ls_elliptic {
238239
if (old_n_obs != n_obs_) { W_ *= (double)old_n_obs / n_obs_; }
239240
b_.block(0, 0, n_dofs_, 1) = -PsiNA().transpose() * D_ * W_ * y_;
240241
// enforce dirichlet bc, if any
241-
for (int i = 0; i < dirichlet_dofs_.size(); ++i) { b_.row(dirichlet_dofs_[i]).setConstant(dirichlet_vals_[i]); }
242+
for (size_t i = 0; i < dirichlet_dofs_.size(); ++i) {
243+
b_.row(dirichlet_dofs_[i]).setConstant(dirichlet_vals_[i]);
244+
}
242245
return;
243246
}
244247
template <typename WeightMatrix> void update_weights(const WeightMatrix& W) {
@@ -257,7 +260,9 @@ struct fe_ls_elliptic {
257260
b_.block(0, 0, n_dofs_, 1) = -PsiNA().transpose() * D_ * internals::lmbQ(W_, X_, invXtWX_, y_);
258261
}
259262
// enforce dirichlet bc, if any
260-
for (int i = 0; i < dirichlet_dofs_.size(); ++i) { b_.row(dirichlet_dofs_[i]).setConstant(dirichlet_vals_[i]); }
263+
for (size_t i = 0; i < dirichlet_dofs_.size(); ++i) {
264+
b_.row(dirichlet_dofs_[i]).setConstant(dirichlet_vals_[i]);
265+
}
261266
W_changed_ = true;
262267
return;
263268
}
@@ -290,7 +295,7 @@ struct fe_ls_elliptic {
290295
if (lambda_saved_.value() != lambda) {
291296
// update linear system rhs
292297
b_.block(n_dofs_, 0, n_dofs_, 1) = lambda * u_;
293-
for (int i = 0; i < dirichlet_dofs_.size(); ++i) { b_.row(n_dofs_ + dirichlet_dofs_[i]).setZero(); }
298+
for (size_t i = 0; i < dirichlet_dofs_.size(); ++i) { b_.row(n_dofs_ + dirichlet_dofs_[i]).setZero(); }
294299
}
295300
lambda_saved_ = lambda;
296301
vector_t x;
@@ -325,7 +330,7 @@ struct fe_ls_elliptic {
325330
if (n_covs_ == 0) { // equivalent to calling fit(lambda)
326331
if (lambda_saved_.value() != lambda) {
327332
b_.block(n_dofs_, 0, n_dofs_, 1) = lambda * u_;
328-
for (int i = 0; i < dirichlet_dofs_.size(); ++i) { b_.row(n_dofs_ + dirichlet_dofs_[i]).setZero(); }
333+
for (size_t i = 0; i < dirichlet_dofs_.size(); ++i) { b_.row(n_dofs_ + dirichlet_dofs_[i]).setZero(); }
329334
}
330335
x = invA_.solve(b_);
331336
} else {
@@ -334,7 +339,7 @@ struct fe_ls_elliptic {
334339
b.block(0, 0, n_dofs_, 1) = -PsiNA().transpose() * D_ * W_ * y_;
335340
b.block(n_dofs_, 0, n_dofs_, 1) = lambda * u_;
336341
// enforce Dirichlet BCs, if any
337-
for (int i = 0; i < dirichlet_dofs_.size(); ++i) {
342+
for (size_t i = 0; i < dirichlet_dofs_.size(); ++i) {
338343
b_.row(dirichlet_dofs_[i]).setConstant(dirichlet_vals_[i]);
339344
b_.row(n_dofs_ + dirichlet_dofs_[i]).setZero();
340345
}
@@ -366,7 +371,7 @@ struct fe_ls_elliptic {
366371
Bs_->topRows(n_dofs_) = -PsiNA().transpose() * D_ * internals::lmbQ(W_, X_, invXtWX_, *Us_);
367372
}
368373
// enforce Dirichlet BCs, if any
369-
for (int i = 0; i < dirichlet_dofs_.size(); ++i) {
374+
for (size_t i = 0; i < dirichlet_dofs_.size(); ++i) {
370375
Bs_->row(dirichlet_dofs_[i]).setConstant(dirichlet_vals_[i]);
371376
}
372377
matrix_t x = n_covs_ == 0 ? invA_.solve(*Bs_) : woodbury_system_solve(invA_, U_, XtWX_, V_, *Bs_);

fdaPDE/src/solvers/fe_ls_parabolic.h

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ class fe_ls_parabolic_mono {
140140
// construct with no data
141141
template <typename GeoFrame, typename WeightMatrix, typename InfoT>
142142
requires(is_valid_info_t<InfoT>::value)
143-
fe_ls_parabolic_mono(const GeoFrame& gf, InfoT&& info, const WeightMatrix& W) : s_(info.ic) {
143+
fe_ls_parabolic_mono(const GeoFrame& gf, InfoT&& info, const WeightMatrix& W) : W_(W), s_(info.ic) {
144144
fdapde_static_assert(GeoFrame::Order == 2, THIS_CLASS_IS_FOR_ORDER_TWO_GEOFRAMES_ONLY);
145145
fdapde_assert(gf.n_layers() == 1);
146146
n_obs_ = gf[0].rows();
@@ -165,7 +165,7 @@ class fe_ls_parabolic_mono {
165165
template <typename GeoFrame, typename InfoT>
166166
requires(is_valid_info_t<InfoT>::value)
167167
fe_ls_parabolic_mono(const GeoFrame& gf, InfoT&& info) :
168-
fe_ls_parabolic_mono(gf, info, vector_t::Ones(n_locs_).asDiagonal()) { }
168+
fe_ls_parabolic_mono(gf, info, vector_t::Ones(gf[0].rows()).asDiagonal()) { }
169169

170170
template <typename Penalty> void discretize(Penalty&& penalty) {
171171
fdapde_static_assert(internals::is_valid_penalty_pair_v<Penalty>, INVALID_PENALTY_DESCRIPTION);
@@ -226,7 +226,7 @@ class fe_ls_parabolic_mono {
226226

227227
n_obs_ = y.rows();
228228
n_locs_ = n_obs_;
229-
bool require_woodbury_realloc = n_covs_ != X.cols();
229+
bool require_woodbury_realloc = std::cmp_not_equal(n_covs_, X.cols());
230230
n_covs_ = X.cols();
231231
eval_basis_at_(locs1); // update \Psi matrix
232232

@@ -254,7 +254,7 @@ class fe_ls_parabolic_mono {
254254
// extract temporal mesh
255255
const auto& time_index = geo_index_cast<1, POINT>(gf[0]);
256256
const auto& time_coords = time_index.coordinates();
257-
bool require_full_tensorization = m_ != time_coords.rows();
257+
bool require_full_tensorization = std::cmp_not_equal(m_, time_coords.rows());
258258
m_ = time_coords.rows();
259259
fdapde_assert(m_ > 0 && time_coords.cols() == 1);
260260
DeltaT_ = time_coords(1, 0) - time_coords(0, 0);
@@ -277,7 +277,7 @@ class fe_ls_parabolic_mono {
277277
for (const std::string& token : formula_.rhs()) {
278278
if (gf.contains(token)) { covs.push_back(token); }
279279
}
280-
bool require_woodbury_realloc = n_covs_ != covs.size();
280+
bool require_woodbury_realloc = std::cmp_not_equal(n_covs_, covs.size());
281281
n_covs_ = covs.size();
282282
const auto& y_data = gf[0].data().template col<double>(formula_.lhs());
283283
y_.resize(n_locs_, y_data.blk_sz());
@@ -599,10 +599,10 @@ struct fe_ls_parabolic_ieul {
599599
}
600600
block_map_t(const block_map_t& other) :
601601
rows_(other.rows_), cols_(other.cols_), blk_rows_(other.blk_rows_), blk_cols_(other.blk_cols_) {
602-
for (int i = 0; i < data_.size(); ++i) { data_.data()[i] = other.data_.data()[i]; }
602+
for (std::size_t i = 0; i < data_.size(); ++i) { data_.data()[i] = other.data_.data()[i]; }
603603
}
604604
block_map_t& operator=(const block_map_t& other) {
605-
for (int i = 0; i < data_.size(); ++i) { data_.data()[i] = other.data_.data()[i]; }
605+
for (std::size_t i = 0; i < data_.size(); ++i) { data_.data()[i] = other.data_.data()[i]; }
606606
rows_ = other.rows_;
607607
cols_ = other.cols_;
608608
blk_rows_ = other.blk_rows_;
@@ -696,7 +696,7 @@ struct fe_ls_parabolic_ieul {
696696
template <typename GeoFrame, typename InfoT, typename WeightMatrix>
697697
requires(is_valid_info_t<InfoT>::value)
698698
fe_ls_parabolic_ieul(const GeoFrame& gf, InfoT&& info, const WeightMatrix& W) :
699-
s_(info.ic), tol_(info.tol), max_iter_(info.max_iter) {
699+
s_(info.ic), W_(W), tol_(info.tol), max_iter_(info.max_iter) {
700700
fdapde_static_assert(GeoFrame::Order == 2, THIS_CLASS_IS_FOR_ORDER_TWO_GEOFRAMES_ONLY);
701701
fdapde_assert(gf.n_layers() == 1);
702702
n_obs_ = gf[0].rows();
@@ -977,8 +977,8 @@ struct fe_ls_parabolic_ieul {
977977
sparse_matrix_t W_; // n_obs x n_obs matrix of observation weights
978978
bool W_changed_, W_const_; // W_const_ == true \iff W_ is time-wise constant
979979

980-
int max_iter_; // maximum number of iterations
981980
double tol_; // convergence tolerance
981+
int max_iter_; // maximum number of iterations
982982
double DeltaT_; // time step
983983
};
984984

fdaPDE/src/solvers/fe_ls_separable.h

Lines changed: 14 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ class fe_ls_separable_mono {
110110
return;
111111
}
112112
// unrolls the penalty tuple and injects them into discretize()
113-
template <typename GeoFrame, typename Penalty> void discretize_loop_(const GeoFrame& gf, Penalty&& penalty) {
113+
template <typename Penalty> void discretize_loop_(Penalty&& penalty) {
114114
using Penalty_ = std::decay_t<Penalty>;
115115
internals::apply_index_pack<n_lambda>(
116116
[&]<int... Ns_>() { discretize([&]() { return std::get<Ns_>(penalty); }()...); });
@@ -127,7 +127,7 @@ class fe_ls_separable_mono {
127127
n_obs_ = gf[0].rows();
128128
n_locs_ = n_obs_;
129129

130-
discretize_loop_(gf, info.penalty);
130+
discretize_loop_(info.penalty);
131131
analyze_data(formula, gf, W);
132132
}
133133
template <typename GeoFrame, typename InfoT>
@@ -143,7 +143,7 @@ class fe_ls_separable_mono {
143143
n_obs_ = gf[0].rows();
144144
n_locs_ = n_obs_;
145145

146-
discretize_loop_(gf, info.penalty);
146+
discretize_loop_(info.penalty);
147147
eval_basis_at_(gf);
148148
}
149149
template <typename GeoFrame, typename InfoT>
@@ -244,7 +244,7 @@ class fe_ls_separable_mono {
244244
X.rows() == locs1.rows() * locs2.rows() && W.rows() == locs1.rows() * locs2.rows() && W.rows() == W.cols());
245245
n_obs_ = y.rows();
246246
n_locs_ = n_obs_;
247-
bool require_woodbury_realloc = n_covs_ != X.cols();
247+
bool require_woodbury_realloc = std::cmp_not_equal(n_covs_, X.cols());
248248
n_covs_ = X.cols();
249249
eval_basis_at_(locs1, locs2); // update \Psi matrix
250250
if (require_woodbury_realloc) { U_ = matrix_t::Zero(2 * n_dofs_, n_covs_); }
@@ -267,7 +267,7 @@ class fe_ls_separable_mono {
267267
for (const std::string& token : formula_.rhs()) {
268268
if (gf.contains(token)) { covs.push_back(token); }
269269
}
270-
bool require_woodbury_realloc = n_covs_ != covs.size();
270+
bool require_woodbury_realloc = std::cmp_not_equal(n_covs_, covs.size());
271271
n_covs_ = covs.size();
272272
const auto& y_data = gf[0].data().template col<double>(formula_.lhs());
273273
y_.resize(n_locs_, y_data.blk_sz());
@@ -606,10 +606,10 @@ class fe_ls_separable_cdti {
606606
}
607607
block_map_t(const block_map_t& other) :
608608
rows_(other.rows_), cols_(other.cols_), blk_rows_(other.blk_rows_), blk_cols_(other.blk_cols_) {
609-
for (int i = 0; i < data_.size(); ++i) { data_.data()[i] = other.data_.data()[i]; }
609+
for (size_t i = 0; i < data_.size(); ++i) { data_.data()[i] = other.data_.data()[i]; }
610610
}
611611
block_map_t& operator=(const block_map_t& other) {
612-
for (int i = 0; i < data_.size(); ++i) { data_.data()[i] = other.data_.data()[i]; }
612+
for (size_t i = 0; i < data_.size(); ++i) { data_.data()[i] = other.data_.data()[i]; }
613613
rows_ = other.rows_;
614614
cols_ = other.cols_;
615615
blk_rows_ = other.blk_rows_;
@@ -692,24 +692,14 @@ class fe_ls_separable_cdti {
692692
fe_ls_separable_cdti() noexcept = default;
693693
template <typename GeoFrame, typename InfoT, typename WeightMatrix>
694694
requires(is_valid_info_t<InfoT>::value)
695-
fe_ls_separable_cdti(
696-
const std::string& formula, const GeoFrame& gf, InfoT&& info, const WeightMatrix& W) :
697-
tol_(info.tol), max_iter_(info.max_iter) {
695+
fe_ls_separable_cdti(const std::string& formula, const GeoFrame& gf, InfoT&& info, const WeightMatrix& W) :
696+
max_iter_(info.max_iter), tol_(info.tol) {
698697
fdapde_static_assert(GeoFrame::Order == 2, THIS_CLASS_IS_FOR_ORDER_TWO_GEOFRAMES_ONLY);
699698
fdapde_assert(gf.n_layers() == 1);
700699
n_obs_ = gf[0].rows();
701700
n_locs_ = n_obs_;
702701

703-
using T = std::tuple_element_t<0, std::decay_t<decltype(info.penalty)>>;
704-
if constexpr (requires(T t) { t.get(); }) {
705-
discretize(std::get<0>(info.penalty).get());
706-
} else {
707-
if constexpr (internals::is_pair_v<T>) {
708-
discretize(std::get<0>(info.penalty)); // user supplied penalty pair
709-
} else {
710-
discretize(std::get<0>(info.penalty)(gf.template triangulation<0>()).get());
711-
}
712-
}
702+
discretize(info.penalty);
713703
analyze_data(formula, gf, W);
714704
}
715705
template <typename GeoFrame, typename InfoT>
@@ -719,9 +709,8 @@ class fe_ls_separable_cdti {
719709
// construct with no data
720710
template <typename GeoFrame, typename InfoT, typename WeightMatrix>
721711
requires(is_valid_info_t<InfoT>::value)
722-
fe_ls_separable_cdti(
723-
const GeoFrame& gf, InfoT&& info, const WeightMatrix& W) :
724-
tol_(info.tol), max_iter_(info.max_iter) {
712+
fe_ls_separable_cdti(const GeoFrame& gf, InfoT&& info, const WeightMatrix& W) :
713+
W_(W), max_iter_(info.max_iter), tol_(info.tol) {
725714
fdapde_static_assert(GeoFrame::Order == 2, THIS_CLASS_IS_FOR_ORDER_TWO_GEOFRAMES_ONLY);
726715
fdapde_assert(gf.n_layers() == 1);
727716
n_obs_ = gf[0].rows();
@@ -738,16 +727,7 @@ class fe_ls_separable_cdti {
738727
fdapde_assert(DeltaT_ > 0 && lag_i > 0 && almost_equal(DeltaT_ FDAPDE_COMMA lag_i));
739728
}
740729

741-
using T = std::tuple_element_t<0, std::decay_t<decltype(info.penalty)>>;
742-
if constexpr (requires(T t) { t.get(); }) {
743-
discretize(std::get<0>(info.penalty).get());
744-
} else {
745-
if constexpr (internals::is_pair_v<T>) {
746-
discretize(std::get<0>(info.penalty)); // user supplied penalty pair
747-
} else {
748-
discretize(std::get<0>(info.penalty)(gf.template triangulation<0>()).get());
749-
}
750-
}
730+
discretize(info.penalty);
751731
u_.resize(n_dofs_ * m_);
752732
for (int i = 0; i < m_; ++i) { u_.segment(i * n_dofs_, n_dofs_) = u_space_; }
753733
eval_spatial_basis_at_(gf);
@@ -815,7 +795,7 @@ class fe_ls_separable_cdti {
815795
}
816796
// fit from formula
817797
template <typename GeoFrame, typename WeightMatrix>
818-
void analyze_data(const std::string& formula, const GeoFrame& gf, const WeightMatrix& W) {
798+
void analyze_data(const std::string& formula, const GeoFrame& gf, const WeightMatrix&) {
819799
fdapde_static_assert(GeoFrame::Order == 2, THIS_CLASS_IS_FOR_ORDER_ONE_GEOFRAMES_ONLY);
820800
fdapde_assert(gf.n_layers() == 1 && gf[0].category()[1] == ltype::point);
821801
n_obs_ = gf[0].rows();

0 commit comments

Comments
 (0)