Hi @Huy !
Ok, thanks.
As of 3DEC versions 7.00.161 and 9.00.167, setting a Fish list from a Python’s one still raises a ValueError: unknown type in conversion from PyObject to QVariant tuple
.
In the meantime, I wrote Python functions that I give at the end of this post in case it could be of any help to some users. They deal with the following Python to Fish conversions:
list
/tuple
to list
dict
to map
numpy.ndarray
to array
Notice it is only a temporary solution until your native implementation since it is underoptimized (e.g., 1 s for instantiating à 100,000 elements array from Python to Fish versus 0.5 ms in pure Python). Besides, since it uses a bypass through strings, the float format precision might falsify the values in Fish.
Cheers!
Regards
Théophile
PS: the code
"""
Temporary solution to pass containers to Fish
Warning
-------
Just a workaround until container conversion is natively dealt with through
Itasca's Python API. This workaround is underoptimized, e.g., a random
(100, 1000)-shaped array takes 0.5ms to run in Python console, and
conversion to Fish takes ~1s (depends of course on the machine it's running on,
but it's the 2000x ratio that matters).
"""
import itasca as it
import numpy as np
def format_fish_list(iterator, transformers={}):
"""
Formatting a Python iterator to a Fish list
Parameters
----------
iterator: iterator (preferably list or tuple)
The sequence to convert to Fish list
Note
----
Nested iterators are supported
transformers: dict
Custom type-to-string formatters with (key, val) = (type to format,
formatting function), see examples
Returns
-------
out: str
The formatted iterator as a Fish list constructor
Examples
--------
>>> test = (1, 'foo', 'bar', ('nested', True), 3.2548e-10)
>>> format_fish_list(test)
'list.seq(1, "foo", "bar", list.seq("nested", true), 3.2548e-10)'
Custom converters
>>> test = (99, {'a': 1, 'b': 2})
>>> format_fish_list(test)
KeyError: <class 'dict'>
>>> def my_dict_conv(d):
... return format_fish_list(d.values())
>>> format_fish_list(test, {dict: my_dict_conv})
>>> test = (99, {'a': 1, 'b': 2})
'list.seq(99, list.seq(1, 2))'
"""
transform = {
bool: lambda x: str(x).lower(),
int: lambda x: str(x),
float: lambda x: str(x),
list: format_fish_list,
str: lambda x: f'"{x}"',
tuple: format_fish_list,
}
transform.update(transformers)
items = ', '.join([
transform[type(item)](item)
for item in iterator
])
return f'list.seq({items})'
def format_fish_array(arr):
"""
Formatting a numpy array to a Fish array
Parameters
----------
arr: np.ndarray
The array to format
Returns
-------
out: str
The formatted array as a Fish array constructor
Examples
--------
>>> import numpy as np
>>> test = np.array(((1,2,3), (4,5,6)))
>>> format_fish_array(test)
'array(list.seq(1, 4, 2, 5, 3, 6), 2, 3)'
"""
items = arr.flatten('F').tolist()
shape = ', '.join([str(n) for n in arr.shape])
return f'array({format_fish_list(items)}, {shape})'
def format_fish_map(dictionary):
"""
Formatting a dictionary a Fish map
Parameters
----------
dictionary: dict
The dictionary to format
Returns
-------
out: str
The formatted dict as a Fish map constructor
Examples
--------
>>> test = {'hello': 'world', -62: 1.e10, True: False}
>>> format_fish_map(test)
'map(list.seq("hello", -62, true), list.seq("world", 10000000000.0, false))'
"""
keys, values = zip(*dictionary.items())
return f'map({format_fish_list(keys)}, {format_fish_list(values)})'
def format_fish_container(
container, var_name=None, instantiate=False, transformers={}
):
"""
Formatting a Python container to a Fish constructor
Parameters
----------
container: list/tuple/dict/np.ndarray
The container to set in Fish
var_name: str
The name of the Fish variable for storing the container.
If None, the formatted container has no associated variable.
instantiate: bool
Whether the container should be instantiated on the fly in Fish
(var_name must not be None).
transformers: dict
Custom type-to-string formatters with (key, val) = (type to format,
formatting function), see doc from format_fish_list()
Examples
--------
List/tuple
>>> test = (1, -15., 'foo', ('nested', True), 3.2548e-10)
>>> format_fish_container(test)
'list.seq(1, -15.0, "foo", list.seq("nested", true), 3.2548e-10)'
>>> format_fish_container(demo_seq, 'test')
'test=list.seq(1, -15.0, "foo", list.seq("nested", true), 3.2548e-10)'
Setting variable on-the-fly
>>> format_fish_container(demo_seq, 'test', instantiate=True)
'test=list.seq(1, -15.0, "foo", list.seq("nested", true), 3.2548e-10)'
>>> it.fish.get('test')
(1, -15.0, 'foo', ('nested', True), 3.2548e-10)
Array
>>> test_arr = np.array(((1.,2.,3.),(4.,5.,6.)))
>>> format_fish_container(test_arr, 'test_arr', instantiate=True)
'test_arr=array(list.seq(1.0, 4.0, 2.0, 5.0, 3.0, 6.0), 2, 3)'
Dict
>>> test_dict = {'hey': 'yo', -62: 1.e3, True: False}
>>> format_fish_container(test_dict, 'test_dict', instantiate=True)
'test_dict=map(list.seq("hey", -62, true), list.seq("yo", 1000.0, false))'
"""
if isinstance(container, np.ndarray):
items = format_fish_array(container)
elif isinstance(container, dict):
items = format_fish_map(container)
else:
items = format_fish_list(container, transformers)
if var_name is not None:
items = f'{var_name}={items}'
if instantiate:
it.command(f'[{items}]')
else:
assert not instantiate, 'Cannot instantiate with no var_name'
return items
if __name__ == '__main__':
# List/tuple
test = (1, -15., 'foo', ('nested', True), 3.2548e-10)
print(format_fish_container(test))
print(format_fish_container(test, 'test'))
assert not it.fish.has('test')
format_fish_container(test, 'test', instantiate=True)
assert it.fish.has('test')
assert test==it.fish.get('test')
# Array: test with a silly formatting
test_arr = np.array((((1,),(2,),(3,)),((4,),(5,),(6,))))
format_fish_container(test_arr, 'test_arr', instantiate=True)
assert it.fish.has('test_arr')
# Dict
test_dict = {'hey': 'yo', -62: 1.e3, True: False}
format_fish_container(test_dict, 'test_dict', instantiate=True)
assert test_dict==it.fish.get('test_dict')