[Resolved] Julia, Cantera, PyCall


This is a bit of a stupid question because I don’t know the Python language.
How to convert these instructions to Julia?

>>> II = [i for i,r in enumerate(g.reactions())
...       if 'CO' in r.reactants and 'CO2' in r.products]
>>> for i in II:
...     print(g.reaction(i).equation)

Indeed, as I learn to use Cantera with PyCall, I would like to use the equivalent of II (class ‘list’) in a statement like:

julia> g.net_rates_of_progress[II]

Thank you in advance for your suggestions and explanations about II = …

I would make this more explicit personally than a list comprehension. Here is the conversion for Julia:

II = []

for (i, r) in enumerate(g.reactions())
    if in("CO", r.reactants) && in("CO2", r.products)
        push!(II, i)

for i in II

For your second question on g.net_rates_of_progress[II], I think syntax like this should work: g.net_rates_of_progress[II...] although it’s a bit hard to tell without knowing what the data types are.

Hope that helps, let me know if you have any questions!

P.S. Also, not a stupid question at all! List comprehensions can be hard to read which is why I expanded it in my conversion above.

1 Like

Doesn’t this syntax literally work in Julia if you put parens around (i,r), change single quotes to double quotes, and change and to &&? i.e.

II = [i for (i,r) in enumerate(g.reactions())
      if "CO" in r.reactants && "CO2" in r.products]

is valid Julia syntax for a comprehension. (And in is a valid infix operator in Julia too!)

I just noticed that PyCall does not currently overload in for Python objects, which I should probably fix. In the meantime, you can call r.reactants.__contains__("CO") to call the underlying Python __contains__ method (equivalent to Python’s in operator).

1 Like

TheCedarPrince and stevengj,

Thank you very much for your replies.

The first formulation (TheCedarPrince) is very understandable for me because I am familiar with the separation between the creation of an empty table and the instructions for filling it.

@stevengj: thank you in particular for the last part of your reply which helps me a lot.

1 Like

Note also that PythonCall.jl (a more modern rewrite of PyCall.jl) already handles this.

I am going to look at PythonCall.jl
Many thanks :+1:

This syntax:

II = [i for (i,r) in enumerate(g.reactions())
      if "CO" in r.reactants && "CO2" in r.products]


ERROR: AbstractDict collections only contain Pairs;
Either look for e.g. A=>B instead, or use the `keys` or `values`
function if you are looking for a key or value respectively.
 [1] error(s::String)
   @ Base ./error.jl:35
 [2] in(p::String, a::Dict{Any, Any})
   @ Base ./abstractdict.jl:28
 [3] (::var"#8#10")(::Tuple{Int64, PyObject})
   @ Main ./none:0
 [4] iterate
   @ ./iterators.jl:472 [inlined]
 [5] iterate
   @ ./generator.jl:44 [inlined]
 [6] grow_to!(dest::Vector{Int64}, itr::Base.Generator{Base.Iterators.Filter{var"#8#10", Base.Iterators.Enumerate{Vector{PyObject}}}, var"#7#9"})
   @ Base ./array.jl:860
 [7] collect(itr::Base.Generator{Base.Iterators.Filter{var"#8#10", Base.Iterators.Enumerate{Vector{PyObject}}}, var"#7#9"})
   @ Base ./array.jl:784
 [8] top-level scope
   @ REPL[6]:1

If I just do:

II = [i for (i,r) in enumerate(g.reactions())]

The vector is constructed. Since I use gri30.yaml from cantera, there is 325 elements (what is right).
The part:

if "CO" in r.reactants && "CO2" in r.products

appears to introduce a problem. Why?

I still can’t extract the reactions with CO and CO2 (all the reactions of gri30.yaml are displayed), there is a small modification to make on the indices because in Julia we start at 1:

for i in II

What are the types of r.reactants and r.products?

r.reactants and r.products are Dict{}.

For exemple, if I consider the first reaction in gri30.yaml:

using PyCall
ct = pyimport("cantera")
g = ct.Solution("gri30.yaml")
r = g.reaction(1)
PyObject H + O + M <=> OH + M    <ThreeBodyReaction(Arrhenius)>
Dict{Any, Any} with 2 entries:
  "H" => 1.0
  "O" => 1.0

And, of course, H and O are the keys of the Dict{}

Ah thanks for sharing the data type. This should work better:

II = []

for (i, r) in enumerate(g.reactions())
    if in("CO", keys(r.reactants)) && in("CO2", keys(r.products))
        push!(II, i)

for i in II
    println(g.reaction(i - 1).equation)

Does that solve the problems you were running into?

This is indeed the solution. :+1: :+1: :+1:
I tried to use keys(r.reactants) on the half of the code, i.e. just with “CO” but I missed the push! to fill table II.
Now, everything is clear.

Many thanks to you both, TheCedarPrice and stevengj.

Just to complete our discussion. I tried this (for fun):

II = []

for (i, r) in enumerate(g.reactions())
    if "CO" ∈ keys(r.reactants) && "CO2" ∈ keys(r.products)
        push!(II, i)

for i in II
    println(g.reaction(i - 1).equation)

With the same (correct) results.

1 Like