<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Use of PYBIND11_MAKE_OPAQUE
pybind11 automatically converts std
Step1:  Two identical classes
Both of then creates random vectors equivalent to std
Step2:  Scenarii
Three possibilities
<ASSISTANT_TASK:>
Python Code:
from jyquickhelper import add_notebook_menu
add_notebook_menu()
%matplotlib inline
Explanation: Use of PYBIND11_MAKE_OPAQUE
pybind11 automatically converts std::vector into python list. That's convenient but not necessarily efficient depending on how it is used after that. PYBIND11_MAKE_OPAQUE is used to create a capsule to hold a pointer on the C++ object.
End of explanation
from cpyquickhelper.examples.vector_container_python import (
    RandomTensorVectorFloat, RandomTensorVectorFloat2)
rnd = RandomTensorVectorFloat(10, 10)
result = rnd.get_tensor_vector()
print(result)
result_ref = rnd.get_tensor_vector_ref()
print(result_ref)
rnd2 = RandomTensorVectorFloat2(10, 10)
result2 = rnd2.get_tensor_vector()
print(result2)
result2_ref = rnd2.get_tensor_vector_ref()
print(result2_ref)
%timeit rnd.get_tensor_vector()
%timeit rnd.get_tensor_vector_ref()
%timeit rnd2.get_tensor_vector()
%timeit rnd2.get_tensor_vector_ref()
Explanation: Two identical classes
Both of then creates random vectors equivalent to std::vector&lt;Tensor&gt;, and Tensor ~  std::vector&lt;float&gt;. The first one returns a capsule due PYBIND11_MAKE_OPAQUE(std::vector&lt;OneTensorFloat&gt;) inserted into the C++ code. The other one is returning a list.
End of explanation
import itertools
from cpyquickhelper.numbers.speed_measure import measure_time
from tqdm import tqdm
import pandas
data = []
sizes = [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 5000, 10000]
sizes = list(itertools.product(sizes, sizes))
for i, j in tqdm(sizes):
    if j >= 1000:
        if i > 1000:
            continue
    if i * j >= 1e6:
        repeat, number = 3, 3
    else:
        repeat, number = 10, 10
    rnd = RandomTensorVectorFloat(i, j)
    obs = measure_time(lambda: rnd.get_tensor_vector(), repeat=repeat, number=number, div_by_number=True)
    obs['name'] = 'capsule'
    obs['n_vectors'] = i
    obs['size'] = j
    data.append(obs)
    rnd2 = RandomTensorVectorFloat2(i, j)
    obs = measure_time(lambda: rnd2.get_tensor_vector(), repeat=repeat, number=number, div_by_number=True)
    obs['name'] = 'list'
    obs['n_vectors'] = i
    obs['size'] = j
    data.append(obs)
    obs = measure_time(lambda: rnd2.get_tensor_vector_ref(), repeat=repeat, number=number, div_by_number=True)
    obs['name'] = 'ref'
    obs['n_vectors'] = i
    obs['size'] = j
    data.append(obs)
    
df = pandas.DataFrame(data)
df.tail()
piv = pandas.pivot_table(df, index=['n_vectors', 'size'], columns=['name'], values='average')
piv['ratio'] = piv['capsule'] / piv['list']
piv.tail()
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 2, figsize=(10, 4))
piv[['capsule', 'list', 'ref']].plot(logy=True, ax=ax[0], title='Capsule (OPAQUE) / list')
piv.sort_values('ratio', ascending=False)[['ratio']].plot(ax=ax[1], title='Ratio Capsule (OPAQUE) / list');
flat = piv.reset_index(drop=False)[['n_vectors', 'size', 'ratio']]
flat_piv = flat.pivot('n_vectors', 'size', 'ratio')
flat_piv
import numpy
import seaborn
seaborn.heatmap(numpy.minimum(flat_piv.values, 1), cmap="YlGnBu",
                xticklabels=list(flat_piv.index), yticklabels=list(flat_piv.columns));
Explanation: Scenarii
Three possibilities:
list: std::vector&lt;Tensor&gt; is converted into a list of copied Tensors
capsule: std::vector&lt;Tensor&gt; is converted into a capsule on a copied std::vector&lt;Tensor&gt;, the capsule still holds the pointer and is responsible to the deletion.
ref: std::vector&lt;Tensor&gt; is just return as a pointer. The cost of getting the pointer does not depend on the content size. It is somehow the low limit.
Plots
End of explanation
<END_TASK>