16 #include "runge_kutta.h"
17 #include "discrete_problem.h"
18 #include "projections/ogprojection.h"
19 #include "projections/localprojection.h"
20 #include "weakform_library/weakforms_hcurl.h"
25 template<
typename Scalar>
26 RungeKutta<Scalar>::RungeKutta(
const WeakForm<Scalar>* wf, Hermes::vector<
const Space<Scalar> *> spaces, ButcherTable* bt)
27 : wf(wf), bt(bt), num_stages(bt->get_size()), stage_wf_right(bt->get_size() * spaces.size()),
28 stage_wf_left(spaces.size()), start_from_zero_K_vector(false), block_diagonal_jacobian(false), residual_as_vector(true), iteration(0),
29 freeze_jacobian(false), newton_tol(1e-6), newton_max_iter(20), newton_damping_coeff(1.0), newton_max_allowed_residual_norm(1e10)
31 for(
unsigned int i = 0; i < spaces.size(); i++)
33 this->spaces.push_back(spaces.at(i));
34 this->spaces_seqs.push_back(spaces.at(i)->get_seq());
36 for(
unsigned int i = 0; i < spaces.size(); i++)
37 this->spaces_mutable.push_back(const_cast<Space<Scalar>*>(spaces.at(i)));
40 throw Exceptions::NullException(2);
42 do_global_projections =
true;
44 matrix_right = create_matrix<Scalar>();
45 matrix_left = create_matrix<Scalar>();
46 vector_right = create_vector<Scalar>();
48 solver = create_linear_solver(matrix_right, vector_right);
60 this->stage_dp_left = NULL;
61 this->stage_dp_right = NULL;
64 template<
typename Scalar>
65 RungeKutta<Scalar>::RungeKutta(
const WeakForm<Scalar>* wf,
const Space<Scalar>* space, ButcherTable* bt)
66 : wf(wf), bt(bt), num_stages(bt->get_size()), stage_wf_right(bt->get_size() * 1),
67 stage_wf_left(1), start_from_zero_K_vector(false), block_diagonal_jacobian(false), residual_as_vector(true), iteration(0),
68 freeze_jacobian(false), newton_tol(1e-6), newton_max_iter(20), newton_damping_coeff(1.0), newton_max_allowed_residual_norm(1e10)
70 this->spaces.push_back(space);
71 this->spaces_seqs.push_back(space->get_seq());
72 this->spaces_mutable.push_back(
const_cast<Space<Scalar>*
>(space));
74 if(bt==NULL)
throw Exceptions::NullException(2);
76 do_global_projections =
true;
78 matrix_right = create_matrix<Scalar>();
79 matrix_left = create_matrix<Scalar>();
80 vector_right = create_vector<Scalar>();
82 solver = create_linear_solver(matrix_right, vector_right);
94 this->stage_dp_left = NULL;
95 this->stage_dp_right = NULL;
98 template<
typename Scalar>
99 void RungeKutta<Scalar>::set_spaces(Hermes::vector<
const Space<Scalar>*> spaces)
101 bool delete_K_vector =
false;
102 for(
unsigned int i = 0; i < spaces.size(); i++)
104 if(spaces[i]->get_seq() != this->spaces_seqs[i])
105 delete_K_vector =
true;
108 this->spaces = spaces;
109 this->spaces_seqs.clear();
110 for(
unsigned int i = 0; i < spaces.size(); i++)
111 this->spaces_seqs.push_back(spaces.at(i)->get_seq());
112 this->spaces_mutable.clear();
113 for(
unsigned int i = 0; i < this->spaces.size(); i++)
114 this->spaces_mutable.push_back(const_cast<Space<Scalar>*>(this->spaces.at(i)));
120 this->info(
"\tRunge-Kutta: K vectors are being set to zero, as the spaces changed during computation.");
121 memset(K_vector, 0, num_stages * Space<Scalar>::get_num_dofs(this->spaces) *
sizeof(Scalar));
125 delete [] vector_left;
128 if(this->stage_dp_left != NULL)
129 static_cast<DiscreteProblem<Scalar>*
>(this->stage_dp_left)->set_spaces(this->spaces);
132 template<
typename Scalar>
133 void RungeKutta<Scalar>::set_space(
const Space<Scalar>* space)
135 bool delete_K_vector =
false;
136 if(space->get_seq() != this->spaces_seqs[0])
137 delete_K_vector =
true;
139 this->spaces.clear();
140 this->spaces.push_back(space);
141 this->spaces_seqs.clear();
142 this->spaces_seqs.push_back(space->get_seq());
143 this->spaces_mutable.clear();
144 this->spaces_mutable.push_back(
const_cast<Space<Scalar>*
>(space));
150 this->info(
"\tRunge-Kutta: K vector is being set to zero, as the spaces changed during computation.");
151 memset(K_vector, 0, num_stages * Space<Scalar>::get_num_dofs(this->spaces) *
sizeof(Scalar));
155 delete [] vector_left;
158 if(this->stage_dp_left != NULL)
159 static_cast<DiscreteProblem<Scalar>*
>(this->stage_dp_left)->set_space(space);
162 template<
typename Scalar>
163 Hermes::vector<const Space<Scalar>*> RungeKutta<Scalar>::get_spaces()
const
168 template<
typename Scalar>
169 void RungeKutta<Scalar>::init()
171 this->create_stage_wf(spaces.size(), block_diagonal_jacobian);
173 if(this->get_verbose_output())
175 this->stage_wf_left.set_verbose_output(
true);
176 this->stage_wf_right.set_verbose_output(
true);
180 this->stage_wf_left.set_verbose_output(
false);
181 this->stage_wf_right.set_verbose_output(
false);
191 this->stage_dp_left =
new DiscreteProblem<Scalar>(&stage_wf_left, spaces);
194 Hermes::vector<const Space<Scalar>*> stage_spaces_vector;
198 for (
unsigned int i = 0; i < num_stages; i++)
199 for(
unsigned int space_i = 0; space_i < spaces.size(); space_i++)
200 stage_spaces_vector.push_back(spaces[space_i]);
202 this->stage_dp_right =
new DiscreteProblem<Scalar>(&stage_wf_right, stage_spaces_vector);
204 stage_dp_right->set_RK(spaces.size());
207 if(!residual_as_vector)
208 for (
unsigned int i = 0; i < num_stages; i++)
209 for(
unsigned int sln_i = 0; sln_i < spaces.size(); sln_i++)
210 residuals_vector.push_back(
new Solution<Scalar>(spaces[sln_i]->get_mesh()));
213 template<
typename Scalar>
214 void RungeKutta<Scalar>::set_start_from_zero_K_vector()
216 this->start_from_zero_K_vector =
true;
219 template<
typename Scalar>
220 void RungeKutta<Scalar>::set_residual_as_solutions()
222 this->residual_as_vector =
false;
225 template<
typename Scalar>
226 void RungeKutta<Scalar>::set_block_diagonal_jacobian()
228 this->block_diagonal_jacobian =
true;
231 template<
typename Scalar>
232 void RungeKutta<Scalar>::set_freeze_jacobian()
234 this->freeze_jacobian =
true;
236 template<
typename Scalar>
237 void RungeKutta<Scalar>::set_newton_tol(
double newton_tol)
239 this->newton_tol = newton_tol;
241 template<
typename Scalar>
242 void RungeKutta<Scalar>::set_newton_max_iter(
int newton_max_iter)
244 this->newton_max_iter = newton_max_iter;
246 template<
typename Scalar>
247 void RungeKutta<Scalar>::set_newton_damping_coeff(
double newton_damping_coeff)
249 this->newton_damping_coeff = newton_damping_coeff;
251 template<
typename Scalar>
252 void RungeKutta<Scalar>::set_newton_max_allowed_residual_norm(
double newton_max_allowed_residual_norm)
254 this->newton_max_allowed_residual_norm = newton_max_allowed_residual_norm;
257 template<
typename Scalar>
258 RungeKutta<Scalar>::~RungeKutta()
260 if(stage_dp_left != NULL)
261 delete stage_dp_left;
262 if(stage_dp_right != NULL)
263 delete stage_dp_right;
270 delete [] vector_left;
273 template<
typename Scalar>
274 void RungeKutta<Scalar>::use_local_projections()
276 do_global_projections =
false;
279 template<
typename Scalar>
280 void RungeKutta<Scalar>::multiply_as_diagonal_block_matrix(SparseMatrix<Scalar>* matrix,
int num_blocks,
281 Scalar* source_vec, Scalar* target_vec)
283 int size = matrix->get_size();
284 for (
int i = 0; i < num_blocks; i++)
286 matrix->multiply_with_vector(source_vec + i*size, target_vec + i*size);
290 template<
typename Scalar>
291 void RungeKutta<Scalar>::rk_time_step_newton(Solution<Scalar>* sln_time_prev,
292 Solution<Scalar>* sln_time_new, Solution<Scalar>* error_fn)
294 Hermes::vector<Solution<Scalar>*> slns_time_prev = Hermes::vector<Solution<Scalar>*>();
295 slns_time_prev.push_back(sln_time_prev);
296 Hermes::vector<Solution<Scalar>*> slns_time_new = Hermes::vector<Solution<Scalar>*>();
297 slns_time_new.push_back(sln_time_new);
298 Hermes::vector<Solution<Scalar>*> error_fns = Hermes::vector<Solution<Scalar>*>();
299 error_fns.push_back(error_fn);
300 return rk_time_step_newton(slns_time_prev, slns_time_new,
304 template<
typename Scalar>
305 void RungeKutta<Scalar>::rk_time_step_newton(Hermes::vector<Solution<Scalar>*> slns_time_prev,
306 Hermes::vector<Solution<Scalar>*> slns_time_new,
307 Hermes::vector<Solution<Scalar>*> error_fns)
313 if(this->stage_dp_left == NULL)
317 update_stage_wf(slns_time_prev);
320 if(error_fns != Hermes::vector<Solution<Scalar>*>() && bt->is_embedded() ==
false)
321 throw Hermes::Exceptions::Exception(
"rk_time_step_newton(): R-K method must be embedded if temporal error estimate is requested.");
323 info(
"\tRunge-Kutta: time step, time: %f, time step: %f", this->
time, this->time_step);
326 for (
unsigned int stage_i = 0; stage_i < num_stages; stage_i++)
327 Space<Scalar>::update_essential_bc_values(spaces_mutable, this->
time + bt->get_C(stage_i)*this->time_step);
330 Hermes::vector<const Space<Scalar>*> stage_spaces_vector;
334 for (
unsigned int i = 0; i < num_stages; i++)
335 for(
unsigned int space_i = 0; space_i < spaces.size(); space_i++)
337 typename Space<Scalar>::ReferenceSpaceCreator ref_space_creator(spaces[space_i], spaces[space_i]->get_mesh(), 0);
338 stage_spaces_vector.push_back(ref_space_creator.create_ref_space());
341 this->stage_dp_right->set_spaces(stage_spaces_vector);
344 if(start_from_zero_K_vector || !iteration)
345 memset(K_vector, 0, num_stages * ndof *
sizeof(Scalar));
346 memset(u_ext_vec, 0, num_stages * ndof *
sizeof(Scalar));
347 memset(vector_left, 0, num_stages * ndof *
sizeof(Scalar));
353 stage_dp_left->assemble(matrix_left, NULL);
356 double residual_norm;
364 if(this->filters_to_reinit.size() > 0)
368 for(
unsigned int filters_i = 0; filters_i < this->filters_to_reinit.size(); filters_i++)
369 filters_to_reinit.at(filters_i)->reinit();
373 multiply_as_diagonal_block_matrix(matrix_left, num_stages, K_vector, vector_left);
377 bool force_diagonal_blocks =
true;
378 stage_dp_right->assemble(u_ext_vec, NULL, vector_right, force_diagonal_blocks);
381 vector_right->add_vector(vector_left);
385 vector_right->change_sign();
386 if(this->output_rhsOn && (this->output_rhsIterations == -1 || this->output_rhsIterations >= it))
388 char* fileName =
new char[this->RhsFilename.length() + 5];
389 if(this->RhsFormat == Hermes::Algebra::DF_MATLAB_SPARSE)
390 sprintf(fileName,
"%s%i.m", this->RhsFilename.c_str(), it);
392 sprintf(fileName,
"%s%i", this->RhsFilename.c_str(), it);
393 FILE* rhs_file = fopen(fileName,
"w+");
394 vector_right->dump(rhs_file, this->RhsVarname.c_str(), this->RhsFormat, this->rhs_number_format);
399 if(residual_as_vector)
401 residual_norm = Global<Scalar>::get_l2_norm(vector_right);
405 Hermes::vector<bool> add_dir_lift_vector;
406 add_dir_lift_vector.reserve(1);
407 add_dir_lift_vector.push_back(
false);
409 residuals_vector,
false);
415 this->info(
"\tRunge-Kutta: Newton initial residual norm: %g", residual_norm);
417 this->info(
"\tRunge-Kutta: Newton iteration %d, residual norm: %g", it-1, residual_norm);
420 if(residual_norm > newton_max_allowed_residual_norm)
422 throw Exceptions::ValueException(
"residual norm", residual_norm, newton_max_allowed_residual_norm);
427 if((residual_norm < newton_tol || it > newton_max_iter) && it > 1)
430 bool rhs_only = (freeze_jacobian && it > 1);
436 stage_dp_right->assemble(u_ext_vec, matrix_right, NULL, force_diagonal_blocks);
440 matrix_right->add_sparse_to_diagonal_blocks(num_stages, matrix_left);
442 if(this->output_matrixOn && (this->output_matrixIterations == -1 || this->output_matrixIterations >= it))
444 char* fileName =
new char[this->matrixFilename.length() + 5];
445 if(this->matrixFormat == Hermes::Algebra::DF_MATLAB_SPARSE)
446 sprintf(fileName,
"%s%i.m", this->matrixFilename.c_str(), it);
448 sprintf(fileName,
"%s%i", this->matrixFilename.c_str(), it);
449 FILE* matrix_file = fopen(fileName,
"w+");
451 matrix_right->dump(matrix_file, this->matrixVarname.c_str(), this->matrixFormat, this->matrix_number_format);
455 matrix_right->finish();
458 solver->set_factorization_scheme(HERMES_REUSE_FACTORIZATION_COMPLETELY);
462 throw Exceptions::LinearMatrixSolverException();
465 for (
unsigned int i = 0; i < num_stages*ndof; i++)
466 K_vector[i] += newton_damping_coeff * solver->get_sln_vector()[i];
473 if(it >= newton_max_iter)
476 this->info(
"\tRunge-Kutta: time step duration: %f s.\n", this->last());
477 throw Exceptions::ValueException(
"Newton iterations", it, newton_max_iter);
485 Scalar* coeff_vec =
new Scalar[ndof];
486 if(do_global_projections)
488 OGProjection<Scalar> ogProjection;
489 ogProjection.project_global(spaces, slns_time_prev, coeff_vec);
493 LocalProjection<Scalar> ogProjection;
494 ogProjection.project_local(spaces, slns_time_prev, coeff_vec);
497 if(do_global_projections)
499 OGProjection<Scalar> ogProjection;
500 ogProjection.project_global(spaces, slns_time_prev, coeff_vec);
504 LocalProjection<Scalar> ogProjection;
505 ogProjection.project_local(spaces, slns_time_prev, coeff_vec);
509 for (
int i = 0; i < ndof; i++)
510 for (
unsigned int j = 0; j < num_stages; j++)
511 coeff_vec[i] += this->time_step * bt->get_B(j) * K_vector[j * ndof + i];
517 if(error_fns != Hermes::vector<Solution<Scalar>*>())
519 for (
int i = 0; i < ndof; i++)
522 for (
unsigned int j = 0; j < num_stages; j++)
523 coeff_vec[i] += (bt->get_B(j) - bt->get_B2(j)) * K_vector[j * ndof + i];
524 coeff_vec[i] *= this->time_step;
526 Solution<Scalar>::vector_to_solutions_common_dir_lift(coeff_vec, spaces, error_fns);
530 for (
unsigned int i = 0; i < num_stages * spaces.size(); i++)
531 delete stage_spaces_vector[i];
534 if(!residual_as_vector)
535 for (
unsigned int i = 0; i < num_stages; i++)
536 delete residuals_vector[i];
543 this->info(
"\tRunge-Kutta: time step duration: %f s.\n", this->last());
546 template<
typename Scalar>
547 void RungeKutta<Scalar>::rk_time_step_newton(Hermes::vector<Solution<Scalar>*> slns_time_prev,
548 Hermes::vector<Solution<Scalar>*> slns_time_new)
550 return rk_time_step_newton(slns_time_prev, slns_time_new,
551 Hermes::vector<Solution<Scalar>*>());
554 template<
typename Scalar>
555 void RungeKutta<Scalar>::rk_time_step_newton(Solution<Scalar>* sln_time_prev, Solution<Scalar>* sln_time_new)
557 Hermes::vector<Solution<Scalar>*> slns_time_prev = Hermes::vector<Solution<Scalar>*>();
558 slns_time_prev.push_back(sln_time_prev);
559 Hermes::vector<Solution<Scalar>*> slns_time_new = Hermes::vector<Solution<Scalar>*>();
560 slns_time_new.push_back(sln_time_new);
561 Hermes::vector<Solution<Scalar>*> error_fns = Hermes::vector<Solution<Scalar>*>();
562 return rk_time_step_newton(slns_time_prev, slns_time_new,
566 template<
typename Scalar>
567 void RungeKutta<Scalar>::set_filters_to_reinit(Hermes::vector<Filter<Scalar>*> filters_to_reinit)
569 for(
int i = 0; i < filters_to_reinit.size(); i++)
570 this->filters_to_reinit.push_back(filters_to_reinit.at(i));
573 template<
typename Scalar>
574 void RungeKutta<Scalar>::create_stage_wf(
unsigned int size,
bool block_diagonal_jacobian)
577 stage_wf_left.delete_all();
578 stage_wf_right.delete_all();
581 for(
unsigned int component_i = 0; component_i < size; component_i++)
583 if(spaces[component_i]->get_type() == HERMES_H1_SPACE
584 || spaces[component_i]->get_type() == HERMES_L2_SPACE)
586 MatrixFormVolL2<Scalar>* proj_form =
new MatrixFormVolL2<Scalar>(component_i, component_i);
587 proj_form->areas.push_back(HERMES_ANY);
588 proj_form->scaling_factor = 1.0;
589 proj_form->u_ext_offset = 0;
590 stage_wf_left.add_matrix_form(proj_form);
592 if(spaces[component_i]->get_type() == HERMES_HDIV_SPACE
593 || spaces[component_i]->get_type() == HERMES_HCURL_SPACE)
595 MatrixFormVolHCurl<Scalar>* proj_form =
new MatrixFormVolHCurl<Scalar>(component_i, component_i);
596 proj_form->areas.push_back(HERMES_ANY);
597 proj_form->scaling_factor = 1.0;
598 proj_form->u_ext_offset = 0;
599 stage_wf_left.add_matrix_form(proj_form);
610 Hermes::vector<MatrixFormVol<Scalar> *> mfvol_base = wf->mfvol;
611 Hermes::vector<MatrixFormSurf<Scalar> *> mfsurf_base = wf->mfsurf;
612 Hermes::vector<VectorFormVol<Scalar> *> vfvol_base = wf->vfvol;
613 Hermes::vector<VectorFormSurf<Scalar> *> vfsurf_base = wf->vfsurf;
620 for (
unsigned int m = 0; m < mfvol_base.size(); m++)
622 for (
unsigned int i = 0; i < num_stages; i++)
624 for (
unsigned int j = 0; j < num_stages; j++)
626 if(block_diagonal_jacobian && i != j)
continue;
628 MatrixFormVol<Scalar>* mfv_ij = mfvol_base[m]->clone();
630 mfv_ij->i = mfv_ij->i + i * spaces.size();
631 mfv_ij->j = mfv_ij->j + j * spaces.size();
633 mfv_ij->u_ext_offset = i * spaces.size();
637 stage_wf_right.add_matrix_form(mfv_ij);
645 for (
unsigned int m = 0; m < mfsurf_base.size(); m++)
647 for (
unsigned int i = 0; i < num_stages; i++)
649 for (
unsigned int j = 0; j < num_stages; j++)
651 if(block_diagonal_jacobian && i != j)
continue;
653 MatrixFormSurf<Scalar>* mfs_ij = mfsurf_base[m]->clone();
655 mfs_ij->i = mfs_ij->i + i * spaces.size();
656 mfs_ij->j = mfs_ij->j + j * spaces.size();
658 mfs_ij->u_ext_offset = i * spaces.size();
662 stage_wf_right.add_matrix_form_surf(mfs_ij);
670 for (
unsigned int m = 0; m < vfvol_base.size(); m++)
672 for (
unsigned int i = 0; i < num_stages; i++)
674 VectorFormVol<Scalar>* vfv_i = vfvol_base[m]->clone();
676 vfv_i->i = vfv_i->i + i * spaces.size();
678 vfv_i->scaling_factor = -1.0;
679 vfv_i->u_ext_offset = i * spaces.size();
683 stage_wf_right.add_vector_form(vfv_i);
690 for (
unsigned int m = 0; m < vfsurf_base.size(); m++)
692 for (
unsigned int i = 0; i < num_stages; i++)
694 VectorFormSurf<Scalar>* vfs_i = vfsurf_base[m]->clone();
696 vfs_i->i = vfs_i->i + i * spaces.size();
698 vfs_i->scaling_factor = -1.0;
699 vfs_i->u_ext_offset = i * spaces.size();
703 stage_wf_right.add_vector_form_surf(vfs_i);
708 template<
typename Scalar>
709 void RungeKutta<Scalar>::update_stage_wf(Hermes::vector<Solution<Scalar>*> slns_time_prev)
711 if(this->wf->global_integration_order_set)
713 this->stage_wf_left.set_global_integration_order(this->wf->global_integration_order);
714 this->stage_wf_right.set_global_integration_order(this->wf->global_integration_order);
719 Hermes::vector<MatrixFormVol<Scalar> *> mfvol = stage_wf_right.mfvol;
720 Hermes::vector<MatrixFormSurf<Scalar> *> mfsurf = stage_wf_right.mfsurf;
721 Hermes::vector<VectorFormVol<Scalar> *> vfvol = stage_wf_right.vfvol;
722 Hermes::vector<VectorFormSurf<Scalar> *> vfsurf = stage_wf_right.vfsurf;
724 for(
unsigned int slns_time_prev_i = 0; slns_time_prev_i < slns_time_prev.size(); slns_time_prev_i++)
725 stage_wf_right.ext.push_back(slns_time_prev[slns_time_prev_i]);
732 for (
unsigned int m = 0; m < mfvol.size(); m++)
734 MatrixFormVol<Scalar> *mfv_ij = mfvol[m];
735 mfv_ij->scaling_factor = -this->time_step * bt->get_A(mfv_ij->i / spaces.size(), mfv_ij->j / spaces.size());
736 mfv_ij->set_current_stage_time(this->
time + bt->get_C(mfv_ij->i / spaces.size()) * this->time_step);
742 for (
unsigned int m = 0; m < mfsurf.size(); m++)
744 MatrixFormSurf<Scalar> *mfs_ij = mfsurf[m];
745 mfs_ij->scaling_factor = -this->time_step * bt->get_A(mfs_ij->i / spaces.size(), mfs_ij->j / spaces.size());
746 mfs_ij->set_current_stage_time(this->
time + bt->get_C(mfs_ij->i / spaces.size()) * this->time_step);
752 for (
unsigned int m = 0; m < vfvol.size(); m++)
754 VectorFormVol<Scalar>* vfv_i = vfvol[m];
755 vfv_i->set_current_stage_time(this->
time + bt->get_C(vfv_i->i / spaces.size())*this->time_step);
761 for (
unsigned int m = 0; m < vfsurf.size(); m++)
763 VectorFormSurf<Scalar>* vfs_i = vfsurf[m];
764 vfs_i->set_current_stage_time(this->
time + bt->get_C(vfs_i->i / spaces.size())*this->time_step);
768 template<
typename Scalar>
769 void RungeKutta<Scalar>::prepare_u_ext_vec()
772 for (
unsigned int stage_i = 0; stage_i < num_stages; stage_i++)
774 unsigned int running_space_ndofs = 0;
775 for(
unsigned int space_i = 0; space_i < spaces.size(); space_i++)
777 for (
int idx = 0; idx < spaces[space_i]->get_num_dofs(); idx++)
779 Scalar increment = 0;
780 for (
unsigned int stage_j = 0; stage_j < num_stages; stage_j++)
781 increment += bt->get_A(stage_i, stage_j) * K_vector[stage_j * ndof + running_space_ndofs + idx];
782 u_ext_vec[stage_i * ndof + running_space_ndofs + idx] = this->time_step * increment;
784 running_space_ndofs += spaces[space_i]->get_num_dofs();
788 template class HERMES_API RungeKutta<double>;
789 template class HERMES_API RungeKutta<std::complex<double> >;