Welcome to Cadabra Q&A, where you can ask questions and receive answers from other members of the community.
0 votes

Hi Folks,

I've been playing with reading Cadabra expressions from Python dictionaries and I've run into a weird error. I'm using the following code

{a,b,c,d,e,f#}::Indices(position=independent).

\partial_{#}::PartialDerivative.

def get_item (key_name):
  import cadabra2

  data = {"term01": "A_{a b}",
          "term02": "\\partial_{b}(B_{a})",
          "term03": "A_{a b} + \\partial_{b}(B_{a})"}

  return cadabra2.Ex (data[key_name])

import foolib

pqr = foolib.get_item ('term01');
abc =        get_item ('term01');

The Python module, foolib, contains a single function that is an exact copy of the local function get_item.

The intention is to create the Cadabra expressions pqr and abc with values drawn from the dictionary data. I get the correct results when I choose the entries term01 or term02, but when I choose term03 the function foolib.get_item fails with Cadabra reporting that the "Free indices in different terms in a sum do not match". The local function get_item works fine. Yet both functions are identical to each other, one just happens to be in a Python module.

Am I doing something wrong? Any suggestions would be appreciated.

One clue that I noticed is that this problem only occurs in a sum that contains partial derivatives. Simple expressions like A_{a b} + B_{a b} work fine.

BTW, this code is part of a slightly large library that I'm using to read/write Cadabra expressions from/to plain text files. The idea is to use this library to share Cadabra expressions between notebooks.

Cheers, Leo

in General questions by (1.6k points)

1 Answer

0 votes
 
Best answer

You analysed that correctly: the reason why there is a problem when get_item is in a separate module is that that module does not see your

\partial{#}::PartialDerivative.

declaration. A quick hack around that is to stick such a declaration line in the module as well, but that does not scale very well because you may be overlooking other properties. Instead, to make the module have access to the property information of the global scope, put a line

__cdbkernel__ = cadabra2.__cdbkernel__

in the module (after import cadabra2 obviously).

Support for modules could definitely be improved; one thing that you already ran into is that you need to write proper Python in a module; you cannot use Cadabra notation except in strings. To help us design this, please do share your experience with this.

by (80.3k points)
selected by

Just in case it wasn't clear already: if you do not have that \partial{#}::PartialDerivative declaration, the expression

 \partial_{b}( B_{a} )

will be seen as a tensor with just a single b index (no a), as by default any indices that appear inside a normal 'functional' argument are eaten.

Hi Kasper,

Many thanks for the quick reply. Yes, that did the trick.

Here is a short example of what I'm using these dict's for. I have two Cadabra programs, foo.cdb,

{a,b,c,d,e,f#}::Indices.
\partial{#}::PartialDerivative.

obj01 := A_{a b};
obj02 := \partial_{b}{B_{a}};
obj03 := A_{a b} + \partial_{b}{B_{a}};

import cdblib

cdblib.create ('my-lib.json')

cdblib.put ('key01',obj01,'my-lib.json')
cdblib.put ('key02',obj02,'my-lib.json')
cdblib.put ('key03',obj03,'my-lib.json')

and bah.cdb,

{a,b,c,d,e,f#}::Indices.
\partial{#}::PartialDerivative.

import cdblib

new01 = cdblib.get ('key01','my-lib.json');
new02 = cdblib.get ('key02','my-lib.json');
new03 = cdblib.get ('key03','my-lib.json');

The foo.cdb code creates three objects and saves them to a dict. The bah.cdb codes reads that dict to create three new objects identical to the first three.

Here is the code that defines the Python/Cadabra module, cdblib.py

def create (file_name):
  import json, io, os, errno

  try:
      os.remove(file_name)                # delete the file if it exsists
      with open(file_name, 'w'): pass     # create an empty file
  except OSError as e:
      if e.errno == errno.ENOENT:         # if the file doesn't exit then
         with open(file_name, 'w'): pass  # create an empty file
      else:                               # otherwise
          raise                           # report an exception

  # Create and save an empty dict
  data_out = {}
  with io.open(file_name, 'w', encoding='utf-8') as out_file:
      out_file.write(json.dumps(data_out,
                                indent=4,
                                sort_keys=True,
                                separators=(',', ': '),
                                ensure_ascii=False)+'\n')

def put (key_name,object,file_name):
  import json, io, os
  import cadabra2
  __cdbkernel__ = cadabra2.__cdbkernel__

  # Read the current dict
  with io.open(file_name) as inp_file:
      data_out = json.load(inp_file)

  # Add a new entry to the dict
  data_out[key_name] = str(object)
  # data_out[key_name] = object.input_form()

  # Save the updated dict
  with io.open(file_name, 'w', encoding='utf-8') as out_file:
      out_file.write(json.dumps(data_out,
                                indent=4,
                                sort_keys=True,
                                separators=(',', ': '),
                                ensure_ascii=False)+'\n')

def get (key_name,file_name):
  import json, io, os
  import cadabra2
  __cdbkernel__ = cadabra2.__cdbkernel__

  # Read the current dict
  with io.open(file_name) as inp_file:
      data_inp = json.load(inp_file)

  # Return one entry from the dict
  return cadabra2.Ex (data_inp[key_name])

There is now some support to import one notebook directly into another one using the import statement. See https://cadabra.science/qa/776/cadabra-2-syntax-for for an example.

...