C++QEDCore  v2 Milestone 10
a framework for simulating open quantum dynamics – core
Rationales
Abstract
Failure to supply contemporaneous rationale for design decisions is a major defect in many software projects. Lack of accurate rationale causes issues to be revisited endlessly, causes maintenance bugs when a maintainer changes something without realizing it was done a certain way for some purpose, and shortens the useful lifetime of software. Rationale is fairly easy to provide at the time decisions are made, but very hard to accurately recover even a short time later.

Design rationales

  • For polymorphic classes, use the non-virtual-interface idiom. Down the hierarchy, virtual functions should almost always be privateprotected only in the case when a derived class uses the class's implementation.
  • All information available at compile time (in particular, the complete layout of the simulated system) should be processed as such, with the help of template metaprogramming.
    • Note that whether the system is Hamiltonian, Liouvillean, etc. is generally not known at compile time since it depends on parameters. (E.g. if there is pumping, Mode is Hamiltonian, but otherwise not.)
    • The fact whether the system is NonOrthogonal, however, is known at compile time, so this can be treated e.g. in trajectories with the help of a boolean template parameter.
  • Interactions should not bother with shifting frequencies as in C++QEDv1, because these frequencies appear at so many places that it is hardly possible to track them all. Instead, the shift of frequencies, when required at all, should be performed on the highest level, that is, in scripts like 1particle1mode.
  • The basic data structures should be built on Blitz++ — memory block of doubles, state vectors, complex matrices, etc.
    Todo:
    Consider switching to Boost.MultiArray. This would also provide const views which are not present in Blitz++.
  • All non-trivial numerics should be performed by GSL with bindings developed in utils.

    Todo:
    Alternative implementations are possible, consider in particular Boost.Math.

    Basic linear algebra should be tackled using the Blitz++ operators, while more involved ones using FLENS/LAPACK.

    Todo:
    Consider switching to Eigen.
  • Rely heavily on STL and Boost both in design and implementation, however, use Boost instead of TR1: TR1 support for gcc appears scarce, and what is implemented doesn't interoperate well with other parts of Boost. (E.g. boost::lambda and tr1::function)
    Todo:
    Revisit this issue (might not exist anymore in C++11)
  • Elements should declare their parameter structs in their own namespaces, and split them into separate header & implementation files. Eg. ParsParticle -> particle::Pars.
  • In object hierarchies make the objects noncopyable, but allow them to be cloneable. Then, they can be conveniently manipulated by Boost.PointerContainers.

Future

  • Implement a more elaborate set of trajectories like ExactTrajectory, HamiltonianTrajectory and MCWF_Trajectory. Then, a maker function can decide on the basis of the dynamical characteristics of the quantum system which one to use.
    Todo:
    Implement this

Implementation rationales

General robust style + Effective C++ + Exceptional C++

  • When refering to names in templatized base classes, prefer the this-> style instead of the using style (cf. Item 43 in [meyers]) – use the latter only when the name in question is needed many times (introduce the using declaration to the same access level as the original)
  • Be aware that assert works only in debug mode (probably a macro switches it off in NDEBUG). It will not even evaluate its argument in NDEBUG mode!
  • Note that function template partial specializations are not allowed, but the same effect can almost always be achieved by function template overloads.
    • Be aware of the possibility of “partial template-argument-inference” of trailing parameters, as exempified e.g. by
      • quantumoperator::Tridiagonal::doApply, where only the REMAINING argument is inferred
      • evolve, where only the RANK paramter is inferred
      • quantumdata::partialTrace, where the arguments V and T are not inferred, etc.
  • When using BOOST_PP_ITERATE to generate code, use the same file for reentering with the include guard BOOST_PP_IS_ITERATING outside the normal include guards (if a header file in is question, but a .cc file can be reentered like this as well). Cf. the example at Boost.Preprocessor.
    See also
    Composite.h, Sigma.cc, Tridiagonal.tcc, BlitzArraySliceIterator.tcc, etc.
    The advantage of this technique is that the generating pattern gets placed in the same file where it belongs.
  • Note that declaring RANK template arguments as unsigned does not work because of template argument deduction problems. Unfortunately, Blitz++ declares them as int…
  • Try to separate template declaration from definition as much as possible (without exporting, of course). One technique is explicit instatiation, but this can be used only when the class is not very deeply templated (e.g. when there is only a single RANK argument). For this, it is very important to factor out parameter-independent code from templates as much as possible. Explicit instatiation will be essential for a Python interface.
  • When using containers of pointers to abstract base together with bind expessions always use boost::bind instead of boost::lambda::bind! See c++essays/lambda_smartPtr for a demonstration. Be extremely careful that Boost.Bind introduces the placeholders into the global namespace, so all kinds of clashes with boost::lambda::bind are easily created. In particular

    bind(foo,_1) 
    

    is correct, but

    bind(foo,bll::_1) 
    

    will never compile because bind is taken from boost as this is searched before boost::lambda.

    bll::bind(foo,bll::_1) 
    

    must be used instead!!!

    Rule is: use lambda when really needed, but bind in all other cases.

    From the above it also follows that using namespace boost::lambda should never really occur, use the namespace alias bll instead.

    Note that Boost.Bind takes bound arguments by value, and unbound arguments (_n) by non-const reference (same for Boost.Lambda).

    Todo:
    Merge Boost.Bind and Boost.Lambda into Boost.Phoenix
  • Templates taking int RANK arguments may implement specializations, when needed, in a brute-force way, when the number of needed specializations depend linearly on RANK, just as in Blitz++. Examples:

    A notable exception is blitzplusplus::basi::Iterator itself because here the number of possibilities goes as RANK choose the size of V. Here obviously one has to resort to more sophisticated TMP techniques.

    Such brute force implementations should still rely on Boost.Preprocessor.

  • All offset (indexing) arguments should be of type std::size_t (std::ptrdiff_t) and all the extent arguments of type std::size_t. In template parameters use only int.
  • Avoid hand-crafted loops whenever practical with the help of STL.Algorithm & Boost.Functional & Boost.Phoenix.
    • Use Boost.Range whenever possible, together with range adaptors to avoid nested bind statements
      Todo:
      Switch to range adaptors in existing codebase
  • Consider design alternatives to dynamic_cast
  • Plain pointers should practically never be used, but replaced with a smart pointer. Newed objects directly to shared_ptr in class factories. Use the dynamic_pointer_cast etc. templates.
  • Avoid newed arrays, use std::vector instead together with &v[0]
  • Effective C++ item 37!!!
  • Do not inherit from STL containers.
  • Prefer preincrements over postincrements, using the latter only when the original value is needed.
  • If xns is a namespace, prefer the syntax xns:: instead of re-opening the namespace when defining a function declared in that namespace. Cf. Stroustrup 8.2.9.3
  • In return statements, be aware that the semantics of value return is the same as those of initialization.
  • Use unnamed namespaces instead of details namespaces in .cc files.
  • Classes not meant to be used from the outside should have protected constructors. Then, it's more convenient to use virtual bases which can have trivial protected constructors. These (if not really meant to be used), can emit a runtime error, when called. Cf. c++_essays/basics/virtualBaseSolution.cc
  • Identify uses of boost::assign::repeat_fun().
  • Note the possibility of manual overload resolution by statically casting to the necessary function signature. This may enable the use of boost::function and boost::bind even in situations when they could not resolve the overload by themselves.
  • In the factory idiom, encapsulate derived classes in makers (even maker functions).
    See also
    evolved::MakerGSL::_
  • Watch out when to use boost::mpl::if_ and boost::mpl::eval_if
    Todo:
    clarify their use throughout
  • Concept checking for template parameters that accept very restricted set of types. Where templates are such that they accept only a very restricted set of types, this should be checked for. Examples are blitzplusplus::basi::Iterator's V parameter or Composite's VA. An implementation could be that these types are derived from some tag class and then a static assertion with the help of is_base_of could be performed. Another solution can be Boost.ConceptChecking

C++11 features currently used

  • variadic templates – variadic parameter lists
  • template aliases
  • new function declaration syntax
  • auto keyword => new for syntax
  • rvalue references
  • new initialization syntax + initialization lists
  • delegating constructors
Note
This sets the compiler requirement to g++ >= 4.7 ; clang++ >= 3.1
  • To be adopted
    • inherited constructors 4.8, 3.3
    • initialization of static constants within the class
    • forward-declared enums 4.6, 3.1
    • Non-static data member initializers
    • lambda 4.5, 3.1
    • decltype 4.8, 2.9

Coding-style conventions

  1. Reserve struct for TMP (traits classes, metafunctions, etc.) and stateless classes, otherwise always use class
  2. Names representing types must be in mixed case starting with upper case
  3. Variable names must be in mixed case starting with lower case
  4. Private class variables must have underscore suffix (this makes them easier to spot than the underscore prefix.)
  5. Named constants (including enumeration values, and template parameters of integral const type) must be all uppercase using underscore to separate words — Enumeration constants must be prefixed by a common type name
  6. Names representing methods or functions should be verbs and written in mixed case starting with lower case — They are distinguished from variable names anyway by their special syntax
  7. Names representing template metafunctions must be named as if they were functions, but in mixed case starting with upper case as if they were types
  8. Names representing namespaces should be all lowercase
  9. Names representing template types should be a single uppercase letter
  10. Generic variables should have the same name as their type — Non-generic variables have a role: these variables can often be named by combining role and type
  11. Variables with a large scope should have long names, variables with a small scope can have short names
  12. The name of the object is implicit, and should be avoided in a method name
  13. The terms get/set must be used where an attribute is accessed directly — This, together with items 2, 3, 4, 6, and 10, allows for very convenient naming, eg::
    class Foo
    {
    public:
      Foo(Bar bar) : bar_(bar) {}  
    
      bar  getBar(       ) const {return bar_;}
      void setBar(Bar bar)       {bar_=bar   ;}
    private:
      Bar bar_;
    };
    
  14. Plural form should be used on names representing a collection of objects
  15. The prefix n should be used for variables representing a number of objects
  16. The suffix No should be used for variables representing an entity number
  17. Iterator variables should be called i, j, k, etc.
  18. The prefix is should be used for boolean variables and methods
  19. Naming pointers specifically should be avoided
  20. Negated boolean variable names must be avoided
  21. Exception classes should be suffixed with Exception
  22. Functions (methods returning something) should be named after what they return, procedures (void methods) after what they do
  23. C++ header files should have the extension .h, source files can have the extension .cpp or .cc
  24. Header files must contain an include guard. The guard macro name should be the all-uppercase filename including the path relative to the project root with . and / replaced by _ and with the suffix _INCLUDED. It must not start with an underscore.
  25. Include statements should be sorted and grouped. Sorted by their hierarchical position in the system with high level files included first. Leave an empty line between groups of include statements. Cf. below in code organization
  26. Include statements must be located at the top of a file only
  27. The parts of a class must be sorted public, protected, and private. All sections must be identified explicitly. Not applicable sections should be left out.
  28. Abbreviations and acronyms must not be uppercase when used as name
  29. Complement names must be used for complement operations
  30. C++ pointers and references should have their reference symbol next to the type rather than to the name
  31. The nominal case should be put in the if-part and the exception in the else-part of an if statement
  32. Use alignment wherever it enhances readability
  33. Use // for all comments, including multi-line comments — then any larger passage can be commented out with /* … */
  34. The function return type can be put in the left column immediately above the function name
  35. Consider using a beautifier (bcpp?)

Code organization

  • Organize headers around a single concept: either a class (e.g. Evolved.h) or a coherent set of services (e.g. BlitzArrayExtensions.h).
  • For headers that declare classes provide also a forward-declaring header with suffix Fwd. (Never manually forward declare anything, because this hardwires the forward declaration at several places, which is then hard to change if we want to migrate e.g. from a class to a template with default arguments.)
  • Keep headers minimal but idempotent (apply include guards, etc.).
  • Source files first include the corresponding header, next any headers developed for the project, then any other third party headers, and finally standard and system headers. This ensures that headers are genuinely self-contained, not accidentally relying on features they do not themselves include. Within each of the groupings use alphabetical ordering.
  • Rely on forward declarations whenever possible.

    Some cases when it is not enough: base classes, data members stored by value, throw expressions, etc.

    typedefs and enums may not be forward declared!

  • A header should always include the corresponding Fwd header if it exists, because this helps to keep the two in synchron.
  • Avoid the use of inline functions out of laziness.
  • Template definitions must of course also be in header files with .tcc extension.

    The following is the preferred method to include implementation headers, because it reduces compilation-dependencies and enables IDEs to parse the headers correctly:

    • The .tcc header includes the corresponding header (also in the case of non-classes to keep the two in synchron) and further headers needed for the implementation.
    • The .tcc header must then be included at the point where the templates are actually instantiated, typically in .cc files. In this case it is indicated for .tcc headers to include further .tcc headers for classes that are subsequently also instantiated. This is for convenience of use. However: script developers should not be required to include <some-implementation>.tcc files. Instead, high level include files should be provided which present a specific concept towards the user (e.g. Evolution.h, MCWF.h etc.). Each element Element with template classes and/or a corresponding ParsElement class should be defined in an Element_.h and provide a high level Element.h which bundles Element.tcc (or Element_.h for non-template classes) and ParsElement.h.

Namespaces

  • The content of utils is treated as a library, so that here everything should be wrapped in namespaces
    • well-defined bundles in their own namespaces (this results in names like trajectory::Trajectory, but so be it)
    • all the rest in namespace cpputils
  • Higher-level components of the framework can come in the global namespace, while their helpers come in namespaces named after their module (like Composite, but composite::Base).

Miscellaneous rationales, ideas, notes

  • Instead of the current utils/Blitz2FLENS.h Blitz and FLENS could be better integrated by making a GeneralStorageView of a blitz::Array and passing it to GEMatrix as template parameter.
  • Transposition of blitz::Array only permutes strides, does not touch storage, as we very well know. What is maybe not so clear is that this remains the case after deep copying as well, since also in this case the strides are just copied so that the result does not have obvious ordering. To get an array with some conventional storage order, the way to go is to first create an array with the desired ordering and then assign to it from the original array.

    A more general solution would be to implement an Array class, which is noncopyable and inherits publicly from blitz::Array. Then, in “copy-like” constructors and transposition, one must in each and every case specify whether deep or shallow semantics is meant. In this case, since no real copy constructor is available, one should resort to another way to return these arrays from functions: A straightforward solution is to return the underlying blitz::Array, and reconstruct the Array from this. It should also provide something like a “cloning” member, which allows for dressing up a chunk of memory with the very same Array interface, with the correct storage layout, etc. Clearly, this new Array class obsoletes blitzplusplus::TinyOfArrays.

    The class could leave all work to the underlying blitz::Array storage, but solve this silly problem of size_t, ptrdiff_t, int conversions. The difficulty is the construction, because blitz::Array provides an immense number of constructors, but with the constructor inheritance of C++11 it should be possible.

  • Passing dcomp by reference is slightly faster if no optimizaton is used, but if the function is inlined, there is no difference.
  • In structure::Interaction, it would be an interesting possibility to supply the type of the base class plugs as a compile-time vector (e.g. JaynesCummings could be derived from structure::Interaction<tt><mpl::vector<QbitBase,ModeBase> >). Then, Composite could do much more static sanity checks. The problem with this is that in this case these would appear in composite::_ as well and creep into the user interface, but this could be perhaps avoided somehow?