Arcane  v3.14.10.0
Documentation développeur
Chargement...
Recherche...
Aucune correspondance
AlephTrilinos.cc
1// -*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-
2//-----------------------------------------------------------------------------
3// Copyright 2000-2023 CEA (www.cea.fr) IFPEN (www.ifpenergiesnouvelles.com)
4// See the top-level COPYRIGHT file for details.
5// SPDX-License-Identifier: Apache-2.0
6//-----------------------------------------------------------------------------
7/*****************************************************************************
8 * IAlephTrilinos.h (C) 2010-2023 *
9 * constants for output types
10 #define AZ_all -4 Print out everything including matrix
11 #define AZ_none 0 Print out no results (not even warnings)
12 #define AZ_last -1 Print out final residual and warnings
13 #define AZ_summary -2 Print out summary, final residual and warnings
14 #define AZ_warnings -3 Print out only warning messages
15 *****************************************************************************/
16
17#include "arcane/aleph/AlephArcane.h"
18
19#include "Epetra_config.h"
20#include "Epetra_Vector.h"
21#include "Epetra_MpiComm.h"
22#include "Epetra_Map.h"
23#include "Epetra_CrsMatrix.h"
24#include "Epetra_LinearProblem.h"
25#include "AztecOO.h"
26#include "ml_MultiLevelPreconditioner.h"
27#include "Ifpack_IC.h"
28
29/*---------------------------------------------------------------------------*/
30/*---------------------------------------------------------------------------*/
31
32namespace Arcane
33{
34
35/*---------------------------------------------------------------------------*/
36/*---------------------------------------------------------------------------*/
37
38/******************************************************************************
39 AlephVectorTrilinos
40*****************************************************************************/
42{
43 public:
44 /******************************************************************************
45 * AlephVectorTrilinos
46 *****************************************************************************/
48 AlephKernel* kernel,
49 Integer index)
50 : IAlephVector(tm, kernel, index)
51 , m_trilinos_vector(NULL)
52 {
53 debug() << "\t\t[AlephVectorTrilinos::AlephVectorTrilinos] new SolverVectorTrilinos";
54#ifdef HAVE_MPI
55 m_trilinos_Comm = new Epetra_MpiComm(*(MPI_Comm*)(m_kernel->subParallelMng(m_index)->getMPICommunicator()));
56#else
57 m_trilinos_Comm = new Epetra_SerialComm();
58#endif // HAVE_MPI
59 }
60
61 /******************************************************************************
62 * AlephVectorCreate
63 *****************************************************************************/
64 void AlephVectorCreate(void)
65 {
66 debug() << "\t\t[AlephVectorTrilinos::AlephVectorCreate] TRILINOS VectorCreate";
67 Integer jlower = -1;
68 Integer jupper = 0;
69 for (int iCpu = 0; iCpu < m_kernel->size(); ++iCpu) {
70 if (m_kernel->rank() != m_kernel->solverRanks(m_index)[iCpu])
71 continue;
72 if (jlower == -1)
73 jlower = m_kernel->topology()->gathered_nb_row(iCpu);
74 jupper = m_kernel->topology()->gathered_nb_row(iCpu + 1) - 1;
75 }
76 Integer size = jupper - jlower + 1;
77 debug() << "\t\t[AlephVectorTrilinos::AlephVectorCreate] jlower=" << jlower << ", jupper=" << jupper;
78 Epetra_Map trilinos_Map = Epetra_Map(m_kernel->topology()->nb_row_size(),
79 size,
80 0,
81 *m_trilinos_Comm);
82 m_trilinos_vector = new Epetra_Vector(trilinos_Map);
83 debug() << "\t\t[AlephVectorTrilinos::AlephVectorCreate] done";
84 }
85
86 /******************************************************************************
87 * AlephVectorSet
88 *****************************************************************************/
89 void AlephVectorSet(const double* bfr_val, const int* bfr_idx, Integer size)
90 {
91 debug() << "\t\t[AlephVectorTrilinos::AlephVectorSet]";
92 m_trilinos_vector->ReplaceGlobalValues(size, bfr_val, bfr_idx);
93 /*#warning DUMPING trilinos
94 for(int i=0; i<size;++i){
95 debug()<<"\t\t[AlephVectorTrilinos::AlephVectorSet] @["<<bfr_idx[i]<<"]="<<bfr_val[i];
96 }*/
97 }
98
99 int AlephVectorAssemble(void)
100 {
101 //m_trilinos_vector->FillComplete();
102 return 0;
103 }
104
105 /******************************************************************************
106 * AlephVectorGet
107 *****************************************************************************/
108 void AlephVectorGet(double* bfr_val, const int* bfr_idx, Integer size)
109 {
110 ARCANE_UNUSED(bfr_idx);
111 debug() << "\t\t[AlephVectorTrilinos::AlephVectorGet]";
112 for (int i = 0; i < size; ++i) {
113 // bfr_val[i]=(*m_trilinos_vector)[bfr_idx[i]];
114 bfr_val[i] = (*m_trilinos_vector)[i];
115 /*#warning DUMPING trilinos
116 debug()<<"\t\t[AlephVectorTrilinos::AlephVectorGet] @["<<i<<"]="<<bfr_val[i];
117*/
118 }
119 }
120
121 /******************************************************************************
122 * writeToFile
123 *****************************************************************************/
124 void writeToFile(const String filename)
125 {
126 ARCANE_UNUSED(filename);
127 debug() << "\t\t[AlephVectorTrilinos::writeToFile]";
128 }
129
130 /******************************************************************************
131 * LinftyNorm
132 * int NormInf (double * Result) const;
133 *****************************************************************************/
134 Real LinftyNorm(void)
135 {
136 Real Result;
137 if (m_trilinos_vector->NormInf(&Result) != 0)
138 throw Exception("LinftyNorm", "NormInf error");
139 return Result;
140 }
141
142 /******************************************************************************
143 * LinftyNorm
144 *****************************************************************************/
145 void fill(Real value)
146 {
147 m_trilinos_vector->PutScalar(value);
148 }
149
150 public:
151 Epetra_Vector* m_trilinos_vector;
152 Epetra_Comm* m_trilinos_Comm;
153};
154
155/******************************************************************************
156 AlephMatrixTrilinos
157*****************************************************************************/
159{
160 public:
161 /******************************************************************************
162 AlephMatrixTrilinos
163*****************************************************************************/
165 AlephKernel* kernel,
166 Integer index)
167 : IAlephMatrix(tm, kernel, index)
168 , m_trilinos_matrix(NULL)
169 {
170 debug() << "\t\t[AlephMatrixTrilinos::AlephMatrixTrilinos] new AlephMatrixTrilinos";
171#ifdef HAVE_MPI
172 m_trilinos_Comm = new Epetra_MpiComm(*(MPI_Comm*)(m_kernel->subParallelMng(m_index)->getMPICommunicator()));
173#else
174 m_trilinos_Comm = new Epetra_SerialComm();
175#endif // HAVE_MPI
176 }
177
178 /******************************************************************************
179 * AlephMatrixCreate
180 *****************************************************************************/
181 void AlephMatrixCreate(void)
182 {
183 debug() << "\t\t[AlephMatrixTrilinos::AlephMatrixCreate] TRILINOS MatrixCreate idx:" << m_index;
184 Integer ilower = -1;
185 Integer iupper = 0;
186 for ( int iCpu = 0; iCpu < m_kernel->size(); ++iCpu) {
187 if (m_kernel->rank() != m_kernel->solverRanks(m_index)[iCpu])
188 continue;
189 if (ilower == -1)
190 ilower = m_kernel->topology()->gathered_nb_row(iCpu);
191 iupper = m_kernel->topology()->gathered_nb_row(iCpu + 1) - 1;
192 }
193 Integer size = iupper - ilower + 1;
194
195 debug() << "\t\t[AlephMatrixTrilinos::AlephMatrixCreate] ilower=" << ilower << ", iupper=" << iupper;
196 Integer jlower = ilower;
197 Integer jupper = iupper;
198
199 debug() << "\t\t[AlephMatrixTrilinos::AlephMatrixCreate] jlower=" << jlower << ", jupper=" << jupper;
200 debug() << "\t\t[AlephMatrixTrilinos::AlephMatrixCreate] global=" << m_kernel->topology()->nb_row_size();
201 debug() << "\t\t[AlephMatrixTrilinos::AlephMatrixCreate] size=" << size;
202
203 Epetra_Map trilinos_Map = Epetra_Map(m_kernel->topology()->nb_row_size(),
204 size,
205 0,
206 *m_trilinos_Comm);
207 m_trilinos_matrix = new Epetra_CrsMatrix(Copy,
209 m_kernel->topology()->gathered_nb_row_elements().subView(ilower, size).unguardedBasePointer(),
210 false);
211 }
212
213 /******************************************************************************
214 * AlephMatrixSetFilled
215 *****************************************************************************/
216 void AlephMatrixSetFilled(bool) {}
217
218 /******************************************************************************
219 * AlephMatrixConfigure
220 *****************************************************************************/
221 int AlephMatrixAssemble(void)
222 {
223 debug() << "\t\t[AlephMatrixTrilinos::AlephMatrixAssemble] AlephMatrixAssemble";
224 m_trilinos_matrix->FillComplete();
225 return true;
226 }
227
228 /******************************************************************************
229 * AlephMatrixFill
230 *****************************************************************************/
231 void AlephMatrixFill(int size, int* rows, int* cols, double* values)
232 {
233 [[maybe_unused]] int rtn = 0;
234 for (int i = 0; i < size; i++) {
235 // int InsertGlobalValues(int GlobalRow, int NumEntries, double* Values, int* Indices);
236 /*#warning TRILINOS DUMP
237 debug()<<"\t\t[AlephMatrixTrilinos::AlephMatrixFill] A["<<rows[i]<<"]["<<cols[i]<<"]="<<values[i];
238*/
239 rtn += m_trilinos_matrix->InsertGlobalValues(rows[i], 1, &values[i], &cols[i]);
240 }
241
242 debug() << "\t\t[AlephMatrixTrilinos::AlephMatrixFill] done";
243 }
244
245 /******************************************************************************
246 * LinftyNormVectorProductAndSub
247 *****************************************************************************/
248 Real LinftyNormVectorProductAndSub(AlephVector* x,
249 AlephVector* b)
250 {
251 ARCANE_UNUSED(x);
252 ARCANE_UNUSED(b);
253 throw FatalErrorException("LinftyNormVectorProductAndSub", "error");
254 }
255
256 /******************************************************************************
257 * isAlreadySolved
258 *****************************************************************************/
259 bool isAlreadySolved(AlephVectorTrilinos* x,
262 Real* residual_norm,
263 AlephParams* params)
264 {
265 const bool convergence_analyse = true; //params->convergenceAnalyse();
266
267 // test le second membre du système linéaire
268 const Real res0 = b->LinftyNorm();
269
271 debug() << "analyse convergence : norme max du second membre res0 : " << res0;
272
273 const Real considered_as_null = params->minRHSNorm();
274 if (res0 < considered_as_null) {
275 x->fill(Real(0.0));
276 residual_norm[0] = res0;
278 debug() << "analyse convergence : le second membre du système linéaire est inférieur à : " << considered_as_null;
279 return true;
280 }
281
282 if (params->xoUser()) {
283 // on test si b est déjà solution du système à epsilon près
284 //matrix->vectorProduct(b, tmp_vector); tmp_vector->sub(x);
285 m_trilinos_matrix->Multiply(false,
286 *x->m_trilinos_vector,
287 *tmp->m_trilinos_vector); // tmp=A*x
288 tmp->m_trilinos_vector->Update(-1.0,
289 *b->m_trilinos_vector,
290 1.0); // tmp=A*x-b
291 const Real residu = tmp->LinftyNorm();
292 //debug() << "[IAlephTrilinos::isAlreadySolved] residu="<<residu;
293
296 debug() << "analyse convergence : |Ax0-b| est inférieur à " << considered_as_null;
297 debug() << "analyse convergence : x0 est déjà solution du système.";
298 }
300 return true;
301 }
302
303 const Real relative_error = residu / res0;
305 debug() << "analyse convergence : résidu initial : " << residu
306 << " --- residu relatif initial (residu/res0) : " << residu / res0;
307
308 if (relative_error < (params->epsilon())) {
310 debug() << "analyse convergence : X est déjà solution du système";
312 return true;
313 }
314 }
316 debug() << "analyse convergence : return false";
317 return false;
318 }
319
320 /******************************************************************************
321 * AlephMatrixSolve
322 *****************************************************************************/
323 int AlephMatrixSolve(AlephVector* x,
324 AlephVector* b,
325 AlephVector* t,
326 Integer& nb_iteration,
327 Real* residual_norm,
329 {
330 Ifpack_IC* ICPrecond = nullptr;
331 Teuchos::ParameterList* MLList = nullptr;
332 ML_Epetra::MultiLevelPreconditioner* MLPrecond = nullptr;
333 const String func_name("SolverMatrixTrilinos::solve");
334
335 AlephVectorTrilinos* x_tri = dynamic_cast<AlephVectorTrilinos*>(x->implementation());
336 AlephVectorTrilinos* b_tri = dynamic_cast<AlephVectorTrilinos*>(b->implementation());
337 AlephVectorTrilinos* t_tri = dynamic_cast<AlephVectorTrilinos*>(t->implementation());
338
342
343 if (isAlreadySolved(x_tri, b_tri, t_tri, residual_norm, solver_param)) {
344 ItacRegion(isAlreadySolved, AlephMatrixSloop);
345 debug() << "\t[AlephMatrixSloop::AlephMatrixSolve] isAlreadySolved !";
346 nb_iteration = 0;
347 return 0;
348 }
349
350 Epetra_Vector* X = x_tri->m_trilinos_vector;
351 Epetra_Vector* B = b_tri->m_trilinos_vector;
352
353 if (!solver_param->xoUser())
354 x_tri->fill(0.0);
355
356 // Create Linear Problem
357 debug() << "\t\t[AlephMatrixTrilinos::AlephMatrixSolve] Create Linear Problem";
358 Epetra_LinearProblem problem(m_trilinos_matrix, X, B);
359
360 // Create AztecOO instance
361 debug() << "\t\t[AlephMatrixTrilinos::AlephMatrixSolve] Create AztecOO instance";
363 // Options can be: AZ_none AZ_last AZ_summary AZ_warnings
364 solver.SetAztecOption(AZ_output, solver_param->getOutputLevel());
365
366 debug() << "\t\t[AlephMatrixTrilinos::AlephMatrixSolve] Setting options";
367 switch (solver_param->method()) {
368 case TypesSolver::PCG:
369 solver.SetAztecOption(AZ_solver, AZ_cg);
370 break;
371 case TypesSolver::BiCGStab:
372 solver.SetAztecOption(AZ_solver, AZ_bicgstab);
373 break;
374 case TypesSolver::BiCGStab2:
375 solver.SetAztecOption(AZ_solver, AZ_bicgstab);
376 break;
377 case TypesSolver::GMRES:
378 solver.SetAztecOption(AZ_solver, AZ_gmres);
379 break;
380 case TypesSolver::SuperLU:
381 solver.SetAztecOption(AZ_solver, AZ_slu);
382 break;
383 default:
384 throw ArgumentException(func_name, "solveur inconnu");
385 }
386
387 switch (solver_param->precond()) {
388 case TypesSolver::NONE:
389 solver.SetAztecOption(AZ_precond, AZ_none);
390 break;
391 case TypesSolver::DIAGONAL:
392 solver.SetAztecOption(AZ_precond, AZ_Jacobi);
393 break;
394 case TypesSolver::ILU: {
395 solver.SetAztecOption(AZ_precond, AZ_dom_decomp);
396 solver.SetAztecOption(AZ_subdomain_solve, AZ_ilu);
397 break;
398 }
399 case TypesSolver::ILUp:
400 solver.SetAztecOption(AZ_precond, AZ_bilu);
401 break;
402 case TypesSolver::POLY:
403 solver.SetAztecOption(AZ_precond, AZ_Neumann);
404 break;
405 case TypesSolver::AMG: { // Taken from trilinos-x.y.z-Source/packages/ml/examples/BasicExamples/*
406 MLList = new Teuchos::ParameterList();
407 /* Setting parameter for aggregation-based preconditioners:
408 - "SA" : classical smoothed aggregation preconditioners;
409 - "NSSA" : default values for Petrov-Galerkin preconditioner for nonsymmetric systems
410 - "maxwell" : default values for aggregation preconditioner for eddy current systems
411 - "DD" : defaults for 2-level domain decomposition preconditioners based on aggregation;
412 - "DD-LU" : Like "DD", but use exact LU decompositions on each subdomain;
413 - "DD-ML" : 3-level domain decomposition preconditioners, with coarser spaces defined by aggregation;
414 - "DD-ML-LU" : Like "DD-ML", but with LU decompositions on each subdomain.
415 */
416 ML_Epetra::SetDefaults("SA", *MLList);
417 //MLList->set("ML output", 10); // output level, 0 being silent and 10 verbose
418 //MLList->set("max levels",16); // maximum number of levels
419 //MLList->set("increasing or decreasing","increasing"); // set finest level to 0
420 MLList->set("cycle applications", solver_param->getAmgSolverIter());
421 debug() << "\t\t[AlephMatrixTrilinos::AlephMatrixSolve] Setting cycle application=" << solver_param->getAmgSolverIter();
422 //MLList->set("aggregation: type", "MIS"); // use MIS scheme to create the aggregate
423 //MLList->set("smoother: type","Chebyshev"); // smoother is Chebyshev
424 MLList->set("smoother: sweeps", solver_param->getAmgSmootherIter());
425 debug() << "\t\t[AlephMatrixTrilinos::AlephMatrixSolve] Setting smoother: sweeps=" << solver_param->getAmgSmootherIter();
426 //MLList->set("smoother: pre or post", "both"); // use both pre and post smoothing
427 //MLList->set("coarse: type","Amesos-KLU"); // solve with serial direct solver KLU
428 //MLList->set("coarse: max size",32); // maximum allowed coarse size
429 MLList->set("ML debug mode", false);
430
431 MLPrecond = new ML_Epetra::MultiLevelPreconditioner(*m_trilinos_matrix, *MLList);
432 //MLPrecond->PrintUnused(0); // verify unused parameters on process 0 (put -1 to print on all processes)
433 //MLPrecond->AnalyzeHierarchy(true,1,1,1);
434 // MLPrec->ReComputePreconditioner(); // Cheap recompute the preconditioner
435 // It is supposed that the linear system matrix has different values, but
436 // **exactly** the same structure and layout. The code re-built the
437 // hierarchy and re-setup the smoothers and the coarse solver using
438 // already available information on the hierarchy. A particular
439 // care is required to use ReComputePreconditioner() with nonzero threshold.
440 // tell AztecOO to use the ML preconditioner
441 solver.SetPrecOperator(MLPrecond);
442 break;
443 }
444 case TypesSolver::IC: {
445 ICPrecond = new Ifpack_IC(m_trilinos_matrix);
446 IFPACK_CHK_ERR(ICPrecond->Compute());
447 solver.SetPrecOperator(ICPrecond);
448 break;
449 }
450 case TypesSolver::SPAIstat:
451 throw ArgumentException(func_name, "preconditionnement AztecOO::SPAIstat indisponible");
452 case TypesSolver::AINV:
453 throw ArgumentException(func_name, "preconditionnement AztecOO::AINV indisponible");
454 case TypesSolver::SPAIdyn:
455 throw ArgumentException(func_name, "preconditionnement AztecOO::SPAIdyn indisponible");
456 default:
457 throw ArgumentException(func_name, "preconditionnement inconnu");
458 }
459
460 // Déclenchement du solver
461 // Iterates on the current problem until MaxIters or Tolerance is reached.
462 solver.Iterate(solver_param->maxIter(), solver_param->epsilon());
463
464 double norm[1];
465 solver.GetLHS()->Norm2(norm);
466 double real_residual;
467 X->Norm2(&real_residual);
468
469 debug() << "[AlephMatrixTrilinos::AlephMatrixSolve]"
470 // Returns the total number of iterations performed on this problem
471 << "\n\t\tNumIters=" << solver.NumIters()
472 // Returns the true unscaled residual for this problem
473 << "\n\t\tTrueResidual=" << solver.TrueResidual()
474 // Returns the true scaled residual for this problem
475 << "\n\t\tScaledResidual=" << solver.ScaledResidual()
476 // Returns the recursive residual for this problem
477 << "\n\t\tRecursiveResidual=" << solver.RecursiveResidual()
478 << "\n\t\tnorm=" << norm[0]
479 << "\n\t\tRealResidual=" << real_residual;
480
481 nb_iteration = static_cast<Integer>(solver.NumIters());
482 residual_norm[0] = static_cast<Real>(solver.TrueResidual()); // vs ScaledResidual ?
483
484 if (solver_param->maxIter() <= nb_iteration)
485 throw Exception("Nombre max d'itérations du solveur atteint!",
486 "AlephMatrixTrilinos::AlephMatrixSolve");
487 /*if (solver_param->epsilon()<residual_norm[0])
488 throw Exception("Convergence non atteinte!", "AlephMatrixTrilinos::AlephMatrixSolve");
489 */
490
491 if (MLList != NULL)
492 delete MLList;
493 if (MLPrecond != NULL)
494 delete MLPrecond;
495 if (ICPrecond != NULL)
496 delete ICPrecond;
497
498 debug() << "\t\t[AlephMatrixTrilinos::AlephMatrixSolve] done";
499 return nb_iteration;
500 }
501
502 /******************************************************************************
503 * writeToFile
504 *****************************************************************************/
505 void writeToFile(const String filename)
506 {
507 ARCANE_UNUSED(filename);
508 }
509
510 private:
511 Epetra_CrsMatrix* m_trilinos_matrix;
512 Epetra_Comm* m_trilinos_Comm;
513};
514
515/*---------------------------------------------------------------------------*/
516/*---------------------------------------------------------------------------*/
517
519, public IAlephFactoryImpl
520{
521 public:
524 , m_IAlephVectors(0)
525 , m_IAlephMatrixs(0)
526 {}
528 {
529 for ( auto* v : m_IAlephVectors )
530 delete v;
531 for ( auto* v : m_IAlephMatrixs )
532 delete v;
533 }
534
535 public:
536 virtual void initialize() {}
537 virtual IAlephTopology* createTopology(ITraceMng* tm,
538 AlephKernel* kernel,
539 Integer index,
540 Integer nb_row_size)
541 {
542 ARCANE_UNUSED(tm);
543 ARCANE_UNUSED(kernel);
544 ARCANE_UNUSED(index);
545 ARCANE_UNUSED(nb_row_size);
546 return nullptr;
547 }
548 virtual IAlephVector* createVector(ITraceMng* tm,
549 AlephKernel* kernel,
550 Integer index)
551 {
552 IAlephVector* new_vector = new AlephVectorTrilinos(tm, kernel, index);
553 m_IAlephVectors.add(new_vector);
554 return new_vector;
555 }
556
557 virtual IAlephMatrix* createMatrix(ITraceMng* tm,
558 AlephKernel* kernel,
559 Integer index)
560 {
561 IAlephMatrix* new_matrix = new AlephMatrixTrilinos(tm, kernel, index);
562 m_IAlephMatrixs.add(new_matrix);
563 return new_matrix;
564 }
565
566 private:
567 UniqueArray<IAlephVector*> m_IAlephVectors;
568 UniqueArray<IAlephMatrix*> m_IAlephMatrixs;
569};
570
571/*---------------------------------------------------------------------------*/
572/*---------------------------------------------------------------------------*/
573
575
576/*---------------------------------------------------------------------------*/
577/*---------------------------------------------------------------------------*/
578
579} // End namespace Arcane
580
581/*---------------------------------------------------------------------------*/
582/*---------------------------------------------------------------------------*/
#define ARCANE_CHECK_POINTER(ptr)
Macro retournant le pointeur ptr s'il est non nul ou lancant une exception s'il est nul.
#define ARCANE_REGISTER_APPLICATION_FACTORY(aclass, ainterface, aname)
Enregistre un service de fabrique pour la classe aclass.
Classe de base d'un service.
Paramètres d'un système linéraire.
Definition AlephParams.h:34
Vecteur d'un système linéaire.
Definition AlephVector.h:33
Interface d'une fabrique d'implémentation pour Aleph.
virtual void * getMPICommunicator()=0
Adresse du communicateur MPI associé à ce gestionnaire.
Lecteur des fichiers de maillage via la bibliothèque LIMA.
Definition Lima.cc:120
Structure contenant les informations pour créer un service.
Exception lorsqu'un argument est invalide.
Classe de base d'une exception.
Exception lorsqu'une erreur fatale est survenue.
Interface du gestionnaire de traces.
Chaîne de caractères unicode.
TraceMessageDbg debug(Trace::eDebugLevel=Trace::Medium) const
Flot pour un message de debug.
-*- tab-width: 2; indent-tabs-mode: nil; coding: utf-8-with-signature -*-