Giter VIP home page Giter VIP logo

qnet's People

Contributors

bitdeli-chef avatar chronum94 avatar danielwe avatar gitter-badger avatar goerz avatar ntezak avatar rhamerly avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

qnet's Issues

Add bi-directional ports to QHDL syntax/parser

Currently, a given port of a component specified in QHDL has a unique direction (in/out). It would be helpful to model the time-reversed modes as well and explicitly allow for backreflections (under the constraint that the linear part of most basic circuit models will satisfy Lorentz-reciprocity relations)

Labels should always be strings, never ints

Using integers as labels e.g. for BasisKet should not be allowed (or rather, an integer label should be converted via str in the __init__ routine). Allowing integers makes it tempting to do arithmetic on the labels. However, any code that does this will fail if the labels are actually strings! Instead, numerical indices should be obtained via an index method / property

Avoid using class decorators for algebraic properties

Currently, we are using class decorator to indicate the algebraic properties that should be applied to the arguments of the create method of an Operation. This has some drawbacks:

  • It is very difficult to follow the instantiation of an Expression in a debugger, as the algebraic transformations are nested, and one cannot easily step over them
  • It makes profiling diagrams more complicated, especially because the actual class decorators are anonymous (generated with preprocess_create_with), so they don't show up in the profiler with useful names.
  • There is a non-negligible overhead in the nested function calls of a decorator

Since >99% of the numerical resources are spent in the create routines (mostly in the pattern matching), it is important that it is easy to profile this part of the code, in order to have useful benchmarks for possible improvements.

Instead of nested decorators, I would propose a simple "serial" approach:

Each Expression gets a _simplifications class attribute, containing a list of callables. Each of these callables is given both the positional (args) and the keyword arguments (kwargs) of a create. It should return a modified args and kwargs (applying e.g. reordering or associativity). The modified args and kwargs become the input for the next simplification in the list. Alternatively, the simplification may also return a fully instantiated Expression (this is what the match_replace and match_replace_binary functions do for the most part). In this case, the expression is returned directly. This is the only way in which create returns an object of a different type than was originally requested.

The relevant code would be as follows:

      for simplification in cls._simplifications:
          simplified = simplification(cls, args, kwargs)
          try:
              args, kwargs = simplified
          except (TypeError, ValueError):
              # We assume that if the simplification didn't return a tuple,
              # the result is a fully instantiated object
              return simplified

Replace PyX dependency by matplotlib

We should render our circuit diagrams with matplotlib as this dependency is much easier to install and does not require LaTeX to be installed.

Add Commutator, AntiCommutator Operations

The operator algebra should include operations Commutator and AntiCommutator, although we probably don't want to automatically create them automatically from operator sums and products. Converting from an "expanded" to a "commutator" representation and vice versa is probably best done using the capabilities of #43 and #42.

The commutator and anticommutator routines in the superoperator algebra should return Commutator and AntiCommutator objects, unless expand is given as True.

Cache output parameters in Expression.create

If we can verify that Expression.create is "idempotent", i.e. expr.create(*expr.args, **expr.kwargs) == expr, we can cache not just the mapping of input parameters to the create constructor, but also the ultimately resulting instantiation (such that the above idempotency rule is directly returned from cache).

Switch documentation theme to RTD default

We might consider switching the theme of the documentation to the default ReadTheDocs theme (see e.g. http://clusterjob.readthedocs.io). The benefits over the "classic" theme would be:

  • Reactive design (i.e. the RTD theme works better on mobile devices)
  • Better navigation toolbar
  • The default theme does not have a strong visual marker between classes/routines, making it hard to visually parse the documentation
  • It arguably looks nicer (but then, blue is my favorite color)

The better navigation would only work if we change the setting for the automatically generated API to one page per per module, not per package. I find this to be easier to deal with in any case. The one page per package leads just leads to extremely long pages.

Qutip conversion does not handle TrivialSpace objects well

As reported by James Driver, QNET does not handle the conversion to qutip of objects in TrivialSpace very well:

  • converting operators in TrivialSpace does not give a useful error:

    >>> from qnet.convert.to_qutip import convert_to_qutip
    >>> from qnet.algebra.operator_algebra import ZeroOperator
    >>> convert_to_qutip(ZeroOperator)
    Traceback (most recent call last):
    File "<ipython-input-4-6143b5660dbf>", line 1, in <module>
        convert_to_qutip(ZeroOperator)
    File "/Users/goerz/Documents/Programming/github/QNET/qnet/convert/to_qutip.py", line 80, in convert_to_qutip
        for s in full_space.local_factors]
    File "/Users/goerz/anaconda3/lib/python3.5/site-packages/qutip/tensor.py", line 82, in tensor
        raise TypeError("Requires at least one input argument")
    TypeError: Requires at least one input argument
    

    Note that the only way to do this conversion is to explicitly supply a
    proper Hilbert space

    >>> from qnet.algebra.hilbert_space_algebra import LocalSpace
    >>> O = convert_to_qutip(ZeroOperator, full_space=LocalSpace(0, dimension=10))
    
  • converting SLH objects has the same problem:

    >>> from qnet.circuit_components import mach_zehnder_cc
    >>> from qnet.convert.to_qutip import SLH_to_qutip
    >>> mz = mach_zehnder_cc.MachZehnder('Zender', alpha=1 , phi=0)
    >>> slh = mz.toSLH()
    >>> SLH_to_qutip(slh)
    Traceback (most recent call last):
    File "<ipython-input-12-d6fb7ebbfefa>", line 1, in <module>
        SLH_to_qutip(slh)
    File "/Users/goerz/Documents/Programming/github/QNET/qnet/convert/to_qutip.py", line 139, in SLH_to_qutip
        H = convert_to_qutip(slh.H, full_space)
    File "/Users/goerz/Documents/Programming/github/QNET/qnet/convert/to_qutip.py", line 80, in convert_to_qutip
        for s in full_space.local_factors]
    File "/Users/goerz/anaconda3/lib/python3.5/site-packages/qutip/tensor.py", line 82, in tensor
        raise TypeError("Requires at least one input argument")
    TypeError: Requires at least one input argument
    

In both cases, the problem should be caught early, and the resulting error message should identify the problem clearly, recommending the use of an explicit full_space

Add simplify method / routine

Akin to substitute, there should be a routine simplify (and a method to Expression of the same name) of the form simplify(expr, rules), where rules is a list of (Pattern, callable) tuples. Note that unlike in the _rules defined for Operations, the Pattern will generally have a well-defined head.

The simplify routine should re-create all expressions from the bottom up (using create), and apply all rules again all expressions encountered in the process. Note that this is also easily combined with the context managers from #42 (and using them may often been better than supplying rules to simplify

Allow to give fixed names for expression in Printers

The printing system (qnet.printing.Printer) should have the capability to "register" arbitrary expressions and associate them with fixed string. Then, whenever that expression is rendered (as part of a another expression), that string will be used directly. This paves the way to using the printing system as part of code generation, where sub-expressions would often be saved as variables, and the variable name should be used whenever that expression occurs elsewhere.

Extend SLH class to allow for description of port names and optionally keep track of eliminated internal modes

It would be quite useful to store more metadata with an SLH object, such as the labels of inputs and outputs as well as any internal signals that were eliminated through feedforward/feedback. Ideally we would also store scattering matrices and L operators for the internal modes, which can then be used to compute transfer functions from external input to some internal mode, and to give the expected mode amplitudes for internal modes. The QHDLJ package I wrote supports this through the ABCD formalism as well. We still need to figure out if this can be generalized from this almost linear case.

match_replace_binary reduces expressions incompletely

The current implementation of match_replace_binary does not fully reduce all expression. For example, the following fails:

x, y, z, alpha = sympy.symbols('x y z alpha')
ops = [LocalSigma(LocalSpace('f'), 0, 0),
       Displace(LocalSpace('f'), -alpha),
       Displace(LocalSpace('f'), alpha),
       LocalSigma(LocalSpace('f'), 0, 0)]
res = OperatorTimes.create(*ops)
assert res == LocalSigma(LocalSpace('f'), 0, 0)

At the very least, we must immediately drop neutral elements. In the above example, the two Displace reduce to the identity, but then LocalSigma, Identity, LocalSigma does not match any rules.

Exploit immutability of Expressions by caching object creation

If Expressions are guaranteed to be immutable, we can gain efficiency by caching all created objects. Trying to instantiate an Expression with the same arguments would return a previously cached object directly.

In fact (assuming Expressions are generally not deleted), this may not only save significant CPU power, but also memory, as "equal" expressions are now the identical object, instead of independent instantiations that just compare equally, as is the case now.

This depends on #26

Include doctests in test suite

A number of docstrings in QNET contain code examples. We should automatically verify that the example are up-to-date by running them as doctests

Drop Python 2 support

Is there any reason not to drop Python 2 support? That is, is anyone using QDYN at the moment in a Python 2 environment (and is waiting for some new features)?

I'm especially interested in using "keyword-only" arguments, e.g.

def simplify(*args, rules=None, check_signature=False, **kwargs):
    ...
def __init__(self, *args, hs=None):
    ...
def __init__(self, name, *, hs=None):
   ...

This would allow for more consistency in the interfaces, make signatures more explicit, and eliminate code that would locally process **kwargs in Python 2.

Also, I think type annotations could be used for signature checking, in conjunction with the enforce package

Hash collision in Expression caching

After caching of Expression objects was introduced by closing #26, new Expression objects to be created are first checked against Expression._instances via a dictionary lookup. However, it turns out there are two often-used expressions with the same hash:

a = LocalOperator(hs="0")
hash(-a) == hash(-2*a) # True

This seems to occur for any instance a of LocalOperator (including Destroy, etc.), and it also occurs regardless of a.space. Changing either of these two things changes the hash, but not the fact that they collide.

A minimal working example of a bug caused by this issue:

a-a # 0
a-2*a # 0

This is because

hash((OperatorPlus, a, -a)) == hash((OperatorPlus, a, -2*a)) # True

evaluates as True, causing Expression.create to construct the wrong expression for a-2*a.

A more significant example is

import sympy as sp
a = Destroy(hs="1")
SLH([[1]], [sp.sqrt(2)*a], 0).symbolic_heisenberg_eom(a)

which produces an incorrect equation of motion for the empty cavity with unit field decay rate, affecting any device model with linear loss (and is closer to how this bug was discovered in the first place).

The straightforward workaround would be setting Expression.instance_caching = False, at the expense of losing the caching feature. Any ideas for a fix instead?

Add a find_all routine

Add a routine find_all(pattern, expr) that returns a list of MatchDict instances for each sub-expression of expr that matches pattern

Clarify superoperator ordering

The SuperOperatorOrderKey contains code that explicitly commutes SPre and SPost superoperators, even if they operate in the same Hilbert space.

I'm not sure I follow the reasoning behind this.

If I define SPre = [A, .], SPost = [., C], and operators A, B, C (all in the same Hilbert space), then

SPost * SPre * B = [[A, B], C]
SPre * SPost * B = [A, [B, C]]

I don't see how these are equal in general. According to Jacobi's identity,

[A, [B, C]] = [[A, B], C] + [B, [A, C]]

but since A, B, C are arbitrary operators, so the second term doesn't vanish. Yet, it looks like the code was added very deliberately (and there's even a specific test for it). @ntezak, could you clarify the reasoning behind this?

Make Hilbert spaces immutable

We could increase efficiency my making all Expressions fully immutable. At the moment, Hilbert spaces are still a major "mutable" component, as we can change their basis dynamically. It would be better to fix the basis (or leave an explicitly "undefined" basis) at the time a Hilbert space is instantiated. Changing the basis later on, or attaching a defined basis to a Hilbert space that previously had and undefined basis can be done via substitute

Improve pattern matching

More than 90% of the numerical effort is spent in pattern matching.

We should rewrite the pattern matching with the following improvements:

  • use lazy evaluation (i.e. generators) to extract sub-pattern from a Pattern used to match arguments/operands of and Expression
  • Restrict patterns such that no backtracking is required to match them. This means that wildcards that match more than one object must only occur at the beginning or the end of a pattern. In the former case, we can match sub-patterns from back to front, in the latter front to back. In either case, we can assume that all wildcards except that last one considered matches exactly one object
  • Don't modify patterns to account for the same wildcard occurring multiple times. Currently, when we find a match for a wildcard, we modify the remaining pattern to replace any wildcard of the same name with the found expressions. This is relatively expensive. Instead, we could simply check that any time a wildcard with the same name matches, it matched expression is the same (by using a dict that throws an exception when a value is overwritten for a known key)
  • Don't use Expressions as patterns. Expressions come with a lot of overhead (i.e. their algebraic properties). Instead, we can have a dedicated and simple Pattern object

We could have a more performant pattern matching with the following implementation:

  • Define a Pattern class to represent arbitrary patterns. The Pattern has the attributes head (class of Expression to be matched), and args (positional arguments of Expression to be matched). It may also associate a wildcard name (wc_name) with the matched Expression. The elements of args, respectively the values in kwargs should themselves either be Patterns (to be matched recursively), or expressions to be matched exactly. Note that nesting is not achieved by instantiating Operations with Pattern instances as operands. When matched recursively, a Pattern can define a mode to indicate that it should be applied more than once. Lastly, a Pattern may define a number of additional conditions that an expression must fulfill in order to be matched successfully.

  • pattern.match(expr) produces a MatchDict instance that maps wildcard names to matched expressions. This is a subclass of dict with the following properties:

    • once a value is set for a key, trying to set a different value for the same key later on will raise a KeyError.
    • in a boolean context, the MatchDict evaluates to True iff the match was successful (even if the dictionary is empty because the pattern did not contain any wildcard names)

In order to match an uninstantiated expression to a Pattern (e.g., inside create via the match_replace function), we create a temporary ProtoExpression object and a Pattern with an undefined head to match it. For convenience, and to allow to write algebraic rules concisely, we may define several helper routines to build Pattern instances: pattern to mach a fully instantiated expression, pattern_head to match a ProtoExpression, and wc to define named wildcards.

Testing the approach outlined above on the following code shows an improvement in runtime by about a factor of 2:

def components():
    """Return all components"""
    n = 8
    nodes = [CircuitSymbol("Cc%d" % (i+1), 1) for i in range(n)]
    scatterer = CircuitSymbol("S", n)
    bs = CircuitSymbol("bs", 2)
    controller = connect([scatterer, bs, scatterer],
                        [((0,0), (1,1)), ((1,1), (2,0))],
                        force_SLH=False)
    network_components = [controller] + nodes
    network_connections = [((1+nid,0),(0,nid)) for nid in range(n)]
    network_connections +=[((0,n+nid),(1+nid,0)) for nid in range(n)]
    return network_components, network_connections


def test_instantiate():
    network_components, network_connections = components()
    circuit = connect(network_components, network_connections, force_SLH=False)
    diagram = CircuitDiagram(circuit)
    print("\n" + str(diagram))

Allow to omit Hilbert space labels from textual expressions

Especially in cases where the is only one Hilbert space, having a Hilbert space label for all operators, states, and superoperators is a bit verbose. The init_printing routine should have a show_hs parameter that modifies the default printers not to show such labels

QSD exporter

Let's write an exporter script that sets up a QSD simulation as a cpp file

qnet.algebra.operator_algebra.OperatorOrderKey.__str__ missing return string initialization

Hi,

I just cloned the repo and ran the Jaynes-Cummings Hamiltonian example from the documentation. I received an error message ending in:

/home/dcriger/Documents/Github/QNET/qnet/algebra/operator_algebra.py in     __str__(self)
    973                 ret += r" ({}) ".format(str(o))
    974             else:
--> 975                 ret += " {}".format(str(o))
    976 
    977         return ret

UnboundLocalError: local variable 'ret' referenced before assignment 

I think ret needs to be initialized to an empty string or something, but I thought I'd submit this as an issue, since I'm not that familiar with the code yet.

Cheers,

Ben

Allow to temporarily modify rules

Added context managers extra_rules(cls, rules), extra_binary_rules(cls, rules), no_rules(cls) that temporarily modify cls._rules, respectively cls._binary_rules

Introduce mechanism to annotate "LocalSpace" objects with more details

Right now every Space is treated as equivalent and only once a basis has been set (with some labels and ordering of states) one can distinguish between different kinds of degrees of freedom such as oscillators (->Fock space), spins (-> angular momentum space, with some J,M=-J,...J) and generalized N-level systems.

We should either allow annotating LocalSpace objects with further data or introduce subclasses for the above cases.

Substitute on expression with Singletons crashes

Calling the substitute method on any Singleton object should always return the
same object. E.g. for the IdentityOperator (II), we would like

assert II.substitute({}) is II

However, since II._class is not II, the call to substitute fails:

self = IdentityOperator, var_map = {}

    def _substitute(self, var_map):
        if self in var_map:
            return var_map[self]
        #if isinstance(self.__class__, Singleton):
            #return self
        new_args = [substitute(arg, var_map) for arg in self.args]
        new_kwargs = {key: substitute(val, var_map)
                    for (key, val) in self.kwargs.items()}
>       return self.__class__.create(*new_args, **new_kwargs)
E       TypeError: create() missing 1 required positional argument: 'self'

qnet/algebra/abstract_algebra.py:280: TypeError

Add symbolic angular momentum operators

It would be useful for some problems to have symbolic operators describing general spin degrees of freedom of a fixed main quantum number J (automatically inferred from dimension d of Hilbert space J = (d-1)/2.
Like for harmonic oscillators, the best basis should probably not be in terms of Jx,Jy because those are less sparse and both couple to increasing and decreasing m_J states. Instead, it makes sense to use as a basis J_z and the ladder operators J_+/-.

connect: connections should not refer to components by index

For the connections argument of the connect routine, it's very cumbersome to have to refer to components by their index in the components list. Instead, we should be able to use the component directly. That is, instead of

connect(components=[A, B, C],  connections=[((0, 0), (2, 0)), ((1, 0), (2, 1))])

we should be able to write

connect(components=[A, B, C],  connections=[((A, 0), (C, 0)), ((B, 0), (C, 1))])

Singletons don't work as expected

Our Singleton objects don't behave the way they're supposed to: "instantiating" (i.e., calling) seems to return a new instance:

>>> from qnet.algebra.operator_algebra import IdentityOperator
>>> IdentityOperator is IdentityOperator()
False

Currently, our singletons are implemented as

_singletons = {}
def _get_singleton(name):
    """Retrieve singletons by name."""
    return _singletons[name]

def singleton(cls):
    """Singleton class decorator."""
    class S(cls):
        __instance = None
        def __hash__(self):
            return hash(cls)
        def _symbols(self):
            return set(())
        def __repr__(self):
            return cls.__name__
        def __call__(self):
            return self.__instance
        def __reduce__(self):
            return (_get_singleton, (cls.__name__,))

    S.__name__ = cls.__name__
    s = S()
    S.__instance = s
    _singletons[S.__name__] = s
    return s

I have absolutely no idea why this fails. Both from reading the source code and stepping through it with a debugger the existing implementation seems like it should work.

However, playing around with it a bit more, I found that using a combination of a metaclass and a class decorator produces the desired behavior:

def singleton_object(cls):
    """Class decorator that replaces a class definition  with the actual singleton object."""
    assert isinstance(cls, Singleton), \
        cls.__name__ + " must use Singleton metaclass"
    def self_instantiate(self):
        return self
    cls.__call__ = self_instantiate
    cls.__hash__ = lambda self: hash(cls)
    cls.__repr__ = lambda self: cls.__name__
    cls.__reduce__ = lambda self: cls.__name__
    cls.__str__ = cls.__repr__
    return cls()


class Singleton(type):
    """Metaclass for singletons"""
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

This would be used as e.g.

@singleton_object
class IdentityOperator(Expression, metaclass=Singleton):
     pass

It separates the two aspects of the previous singleton decorator: the metaclass produces the singleton behavior that all instantiations yield the exact same instance, and the decorator ensures that we don't have to instantiate the singleton into an object (i.e., we can directly use IdentityOperator as opposed to IdentityOperator(), although both are equivalent)

Add support for time-dependent operators

QNET allows to drive the numerical simulation of a symbolic master equation through several backends (qutip and QSD at the moment). We may wish to allow for time-dependent operators in such a numerical simulation. For example, we consider the Hamiltonian for a driven two-level system, H = H_0 + eps(t) * H_1, where H_0 = -(Delta/2)*SigmaZ, H_1 = SigmaX, and eps(t) is a time-dependent pulse, e.g. a Gaussian. In general, eps(t) may be a real or a complex function.

It is important to note that for the derivation of the master equation (QNET's main task), eps(t) is purely symbolic, and can be treated the same way as a numerical constant. Only at the level of the numerical simulation, eps(t) needs to be associated with its actual time-dependent definition. There are several possibilities to address time-dependent operators. For example,

  1. Support for time-dependent operators could be pushed into the QNET modules that serve as drivers for the numerical backends. At the level of the QNET data structures, there would be no support for time-dependent pulses. In any case, when setting up the numerical simulation, the user has to map any symbolic constant to a specific numerical value. Support for time-dependent pulses would be added at this level: instead of mapping a symbol to a numerical value, it would be mapped to a time-dependent function. This would be backend-specific. For QSD, it would have to be mapped to a C++ snippet implementing the desired function. For qutip, the symbol would be mapped to a Python function eps(t). The specific time-dependence (i.e, that Gaussian shape, in our example) would be decided by the user when setting up the simulation.
  2. QNET could recognize the occurrence of a time parameter (t) in the names of symbols. In the driver modules for the numerical backends, this would be taken as an indication that the symbol has to be mapped to a function instead of a numerical value. The workflow would otherwise follow the same procedure as above (i.e., the specific shape of the pulse would be decided in a backend-specific way by the user when setting up the simulation)
  3. For either of the above two cases, as an alternative to leaving the pulse shape definition up to the user when setting up the simulation, a symbolic formula for the time dependence associated with eps(t) could be stored in the QNET data structures (but separate from the symbol eps(t)). Besides having to decide where in the data structure this extra information should be stored, this would then pose the problem that we need to translate the formula into backend-specific code (a Python function for qutip and a C++ code snippet defining a function for QSD), a task that does not appear completely trivial, although doable.
  4. We could recognize only t as a symbol representing time, and fully write out eps(t) at the level of the QNET symbolic expression (i.e., instead of eps(t), the formula for the Gaussian would appear). In the backend, the only time-dependent function required would be f(t) = t in that case, which is easy to hard-code. However, the backend would then have to algebraically re-evalate the function at every application, which adds significant overhead and would unnecessarily slow down the simulation.

Cache Pattern matching

Since Pattern matching is the most expensive operation QNET performs, we might gain significant efficiency by caching the results of Pattern.match

Add OperatorPlusMinusCC

Add an Operation OperatorPlusMinusCC(operand, sign=+1) that signifies an operator plus (or minus) its complex conjugate. This is primarily to make the structure of an expression clear. Like in #44, the operation should only be created or expanded "on demand".

Migrate docstrings to Google format

The standard ReStructuredText format for docstring, e. g.

:param circuit: the circuit to operator on
:type circuit: qnet.algebra.SLH

is both difficult to write and difficult to read outside of the Sphinx-Generated documentation. An alternative, much more flexible format is given in the Google style guidelines. See https://pypi.python.org/pypi/sphinxcontrib-napoleon for a rationale of why to use Google format over Restructured text. Sphinx includes the napoleon plugin (see above) to understand docstrings in the Google format. Modules can contain docstrings in a mixed format. Thus, we can gradually migrate to the new format (although eventually, all docstrings should be in Google format). PyCharm can be configured to use the Google format as well.

A comment on class documentation:
The __init__ method should have no docstring, but the class docstring should document both arguments to the __init__ constructor (as as section Arguments), as well as the class and instance attributes (in section Attributes for the latter). By using the configuration detailed in http://michaelgoerz.net/notes/extending-sphinx-napoleon-docstring-sections.html, we may also use a section Class Attributes to separate attributes from class attributes. See https://github.com/goerz/clusterjob/blob/develop/clusterjob/__init__.py for an example of a very complex class docstring

Utilize printing system in code generation

Significant parts of the QSD code converter could be rewritten to make use of the printing system. Specifically, generating valid variable names from labels of "atomic" Expression, and generating infix expressions for operations can be handled through a Printer.

It might be useful to add a _code_ method to Expression (akin to _ascii_, _tex_, etc.) that does some generic code generation (under the assumption that relevant languages such as C/C++, Fortran, Julia, are all pretty similar in terms of what acceptable variable names are and how expressions look.

This depends on #35

symbol names in QSD code generator are not sanitized

In symbolic expressions, it is often desirable to use LaTeX strings as a symbol name, so that the symbols are rendered nicely in a notebook. For example,

k = symbols("\kappa", positive=True)

When such a symbol is defined and translated to QSD code, there is a resulting C++ line

double \kappa = 2;

which is not valid C++ code. All symbol names must be sanitized to contain only word characters (alphanumeric ASCII symbols and underscores)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.