Closing: GNU Radio 3.9.3.0 Python basic block - issues with, forecast and produce

2022-01-27 Thread Patric Müller

Hello Johannes and hello Marcus,

thank you both for the suggestions and explanations, it really helps a 
lot and is much appreciated!
With your explanations, I was able to produce my minimal working 
example, which I have linked below.



The best place to check for specifics about Python blocks is here:
https://github.com/gnuradio/gnuradio/blob/main/gnuradio-runtime/python/gnuradio/gr/gateway.py

This is the interface that is implemented. The `forecast` method is
implemented in L153ff. It is different from the C++ version.
Thanks for this hint, I've taken a look at the implementation. Also 
thanks for the suggestion with `bpython3` it is a really nice tool. For 
now my block works without the `forecast` method so I won't bother 
implementing it for now. I think it makes more sense to implement 
`forecast` if I should decide to work with a C++ block.




The mechanism is that the scheduler asks forecast what it needs to produce the 
largest
amount of output that the output buffer can deal with, then it halves the 
amount until it
hits the minimum number. If none of these requests yield a fulfillable input 
requirement,
the block is permanently input-blocked.

Also thanks for clearing this up and explaining it Marcus!



First off, the `consume_each` call needs to go behind any read on the
input buffer. You really tell the system at this point: I'm finally done
with these items, do whatever. Since GR is a multi-threaded system, this
may cause trouble because the samples you want to read are already
overwritten.


So let's comment on your `general_work`.

  > def general_work(self, input_items, output_items):
  >  # Firstly check, if input_items has a sufficient amount of items
  >  if len(input_items[0]) >= self.buffer_len:
  >  # Then consume exactly self.buffer_len items
  >  self.consume_each(self.buffer_len)
`consume_each` should go after the last use of `input_items`.
The comment, together with your explanation makes perfect sense, I have 
changed it accordingly.




  >  # Now only output a fraction of the input items, say the first
  > self.out_items, on output port[0]
  >  output_items[0]  = input_items[0][:self.out_items]
This is probably the line causing issues. Try
`output_items[0][0:self.out_items] = input_items[0][0:self.out_items]`
The difference is the left hand indexing. Now, you write items to
positions in an array. Previously, you overwrote the array and replaced
it with a new one. Thus, the output buffer was still full of zeros.


Changing this line was the last missing piece, the minimal example now 
works! I have posted my working minimal example here:

https://pastebin.com/qxgnPcpL


And with that I would like to close this problem, thanks again for the 
feedback!



Kind regards,
Patric




Re: GNU Radio 3.9.3.0 Python basic block - issues with forecast and produce

2022-01-25 Thread Marcus Müller

Hi Johannes, hi Patric,

On 22.01.22 12:25, Johannes Demel wrote:

The `forecast` method expects estimates. The scheduler will call `general_work` anyways at 
some point, if the system is unable to fulfill your forecast requirement. 


No, that is not correct; at least in the 3.8-style scheduler you can deadlock the whole 
flowgraph by letting the forecast method tell the scheduling algorithm that it needs more 
input to produce the minimum possible number of output (==1 or output_multiple) than the 
input could ever offer.


The mechanism is that the scheduler asks forecast what it needs to produce the largest 
amount of output that the output buffer can deal with, then it halves the amount until it 
hits the minimum number. If none of these requests yield a fulfillable input requirement, 
the block is permanently input-blocked.


Best regards,
Marcus



Re: GNU Radio 3.9.3.0 Python basic block - issues with forecast and produce

2022-01-24 Thread Johannes Demel

Hi Patric,

first off, the `consume_each` call needs to go behind any read on the 
input buffer. You really tell the system at this point: I'm finally done 
with these items, do whatever. Since GR is a multi-threaded system, this 
may cause trouble because the samples you want to read are already 
overwritten.


The best place to check for specifics about Python blocks is here:
https://github.com/gnuradio/gnuradio/blob/main/gnuradio-runtime/python/gnuradio/gr/gateway.py

This is the interface that is implemented. The `forecast` method is 
implemented in L153ff. It is different from the C++ version.


I guess Python blocks (and embedded Python blocks even more) are 
considered simple blocks for beginners. Unfortunately, this is not how 
they're treated by developers. Also, they are leaky abstractions over 
their C++ originals.


To rephrase the new issue:
- You insert a known pattern and expect corresponding output.
- Actual output is all zeros. (The screenshot is missing).

So let's comment on your `general_work`.

> def general_work(self, input_items, output_items):
>  # Firstly check, if input_items has a sufficient amount of items
>  if len(input_items[0]) >= self.buffer_len:
>  # Then consume exactly self.buffer_len items
>  self.consume_each(self.buffer_len)
`consume_each` should go after the last use of `input_items`.

>  # Now only output a fraction of the input items, say the first
> self.out_items, on output port[0]
>  output_items[0]  = input_items[0][:self.out_items]
This is probably the line causing issues. Try
`output_items[0][0:self.out_items] = input_items[0][0:self.out_items]`
The difference is the left hand indexing. Now, you write items to 
positions in an array. Previously, you overwrote the array and replaced 
it with a new one. Thus, the output buffer was still full of zeros.


This behavior is very unpythonic. It makes perfect sense though from a 
C++ block perspective. You pass pointers or references to a data 
structure and write data to it in C++. You wouldn't normally do that in 
Python.


Cheers
Johannes

On 23.01.22 19:27, Patric Müller wrote:

Hello everyone and hello Johannes,

first of all, a huge thanks for the quick and detailed response, it is 
really helpful. After implementing your suggested changes and additional 
tinkering, I am still facing two issues:



The `forecast` method expects estimates. The scheduler will call
`general_work` anyways at some point, if the system is unable to fulfill
your forecast requirement. `set_output_multiple` is much more strict.
In your case I'd start with:
`ninput_items_required[0] = noutput_items`


I've changed the `forecast`method accordingly, however I still get the 
same errors. If I use

`ninput_items_required[0] = noutput_items`
then I receive:
`TypeError: 'int' object does not support item assignment`
I've checked the type and value of `ninput_items_required` and sure 
enough, it is of `` and has a value of `1`.


My next thought was that it may not be a , since I only 
have one input port, so I also tried:

`ninput_items_required = noutput_items`
This yields me with:
`Unable to cast Python instance to C++ type (compile in debug mode for 
details)`


For now and since it isn't strictly necessary, I've commented the 
`forecast` method.




At the end of `general_work` report these values to the system:
- consumed items via `consume_each`
- produced items via `return integer_with_number_of_consumed_items`.


I have changed my code accordingly. My `general_work` function now looks 
like this:


def general_work(self, input_items, output_items):
     # Firstly check, if input_items has a sufficient amount of items
     if len(input_items[0]) >= self.buffer_len:
     # Then consume exactly self.buffer_len items
     self.consume_each(self.buffer_len)
     # Now only output a fraction of the input items, say the first 
self.out_items, on output port[0]

     output_items[0]  = input_items[0][:self.out_items]

     else:
     # if we do not have enough input_items, set empty output
     output_items[0] = []

     # finally, return len(output_items[0]), which is either equal to 
`out_items` or `0`

     return len(output_items[0])


It runs and I did some tests. I have created a signal of length 1024, 
which is composed of a random sequence with length 312 and a cosine 
signal with a length of 712 both muxed together. Next follows the python 
block and the signal is compared before and after by using time sinks.

Screenshot of the flowgraph:



I would expect that I should only observe the random sequence (first 312 
samples) in the `processed signal` time sink. However the time sink only 
shows zeros, which is probably due to a coding error.
In order to investigate, I took a look at the content of 
`output_items[0]` and also at the length. The length is 312, which seems 
fine, since I specified it with `self.out_items`. The contents also seem 
fine, it is definitely not z

Re: Re: GNU Radio 3.9.3.0 Python basic block - issues with forecast and produce

2022-01-23 Thread Patric Müller

Hello everyone and hello Johannes,

first of all, a huge thanks for the quick and detailed response, it is 
really helpful. After implementing your suggested changes and additional 
tinkering, I am still facing two issues:



The `forecast` method expects estimates. The scheduler will call
`general_work` anyways at some point, if the system is unable to fulfill
your forecast requirement. `set_output_multiple` is much more strict.
In your case I'd start with:
`ninput_items_required[0] = noutput_items`


I've changed the `forecast`method accordingly, however I still get the 
same errors. If I use

`ninput_items_required[0] = noutput_items`
then I receive:
`TypeError: 'int' object does not support item assignment`
I've checked the type and value of `ninput_items_required` and sure 
enough, it is of `` and has a value of `1`.


My next thought was that it may not be a , since I only 
have one input port, so I also tried:

`ninput_items_required = noutput_items`
This yields me with:
`Unable to cast Python instance to C++ type (compile in debug mode for 
details)`


For now and since it isn't strictly necessary, I've commented the 
`forecast` method.




At the end of `general_work` report these values to the system:
- consumed items via `consume_each`
- produced items via `return integer_with_number_of_consumed_items`.


I have changed my code accordingly. My `general_work` function now looks 
like this:


def general_work(self, input_items, output_items):
    # Firstly check, if input_items has a sufficient amount of items
    if len(input_items[0]) >= self.buffer_len:
    # Then consume exactly self.buffer_len items
    self.consume_each(self.buffer_len)
    # Now only output a fraction of the input items, say the first 
self.out_items, on output port[0]

    output_items[0]  = input_items[0][:self.out_items]

    else:
    # if we do not have enough input_items, set empty output
    output_items[0] = []

    # finally, return len(output_items[0]), which is either equal to 
`out_items` or `0`

    return len(output_items[0])


It runs and I did some tests. I have created a signal of length 1024, 
which is composed of a random sequence with length 312 and a cosine 
signal with a length of 712 both muxed together. Next follows the python 
block and the signal is compared before and after by using time sinks.

Screenshot of the flowgraph:



I would expect that I should only observe the random sequence (first 312 
samples) in the `processed signal` time sink. However the time sink only 
shows zeros, which is probably due to a coding error.
In order to investigate, I took a look at the content of 
`output_items[0]` and also at the length. The length is 312, which seems 
fine, since I specified it with `self.out_items`. The contents also seem 
fine, it is definitely not zero and matches the content of 
`input_items[0]`.


Since everything "seems" fine, I am not sure on how I could debug this 
further. My complete code, including the Debug statements, can be found 
here:

https://pastebin.com/HRMzp0zA

Thanks for taking your time and looking into this!
Kind regards,
Patric




Re: GNU Radio 3.9.3.0 Python basic block - issues with forecast and produce

2022-01-22 Thread Johannes Demel

Hi Patric,

since your goal is to produce a variable number of output items, your 
`general_work` approach seems to be the correct one.
I mean "variable number of output items" in the sense that there is no 
fixed relation between the number of input items to the number of output 
items. Just for reference.


`WORK_CALLED_PRODUCE` is one of those "magic values" that are defined 
somewhere in the system. I've never personally used it in any block.


It is documented here:
https://www.gnuradio.org/doc/doxygen/classgr_1_1block.html

The function is found here:
https://www.gnuradio.org/doc/doxygen/classgr_1_1block.html#aa5581727d057bdd8113f8b2a3fc5bd66

I use `bpython3` to explore Python methods etc. In your case, I expect 
that `self.WORK_CALLED_PRODUCE` should be available.


BUT: Don't use `produce`! In your case use:
`return self.buffer_len`

Why? Because the all the buffer pointer updates will be handled after 
you return from `general_work`. The `produce` method exists for cases 
where you have multiple output buffers and want to produce different 
numbers of output items. e.g 5 samples on `output_items[0]` and 25 
samples on `output_items[1]`.


In your case, you `return 0` which tells the scheduler that you did not 
produce any items.


More generally, try to minimize calls to `consume`/`produce`. Keep track 
of the number of samples your `general_work` consumes and how many 
samples you've written to your output buffer. At the end of 
`general_work` report these values to the system:

- consumed items via `consume_each`
- produced items via `return integer_with_number_of_consumed_items`.

The `forecast` method expects estimates. The scheduler will call 
`general_work` anyways at some point, if the system is unable to fulfill 
your forecast requirement. `set_output_multiple` is much more strict.

In your case I'd start with:
`ninput_items_required[0] = noutput_items`
which might be inefficient. But you can update it with better forecasts 
later. Talking about efficiency: I'd only worry about efficiency in C++ 
blocks. Python blocks incur quite a penalty.


Cheers
Johannes

On 21.01.22 15:14, Patric Müller wrote:

Hello everyone,

I am currently trying to create a basic block similar to this issue:
https://stackoverflow.com/questions/68289222/gnu-radio-buffer-size

Some software and hardware info:
GNU Radio version: 3.9.3.0
Python version: 3.9.7
OS: Ubuntu 20.04

I always want to work on the same number of items, say 1024. Now I would 
like to use up all of the items, but I want to produce something between 
say 500 - 1500 items.
For now, I would like to create a simple block with one input and one 
output, which consumes a set number of items and outputs only a fraction 
of those incoming items in order to understand it better.


Therefore I started with a general block and wanted to change 
ninput_items_required[0].
However, ninput_items_required is an integer. Trying to directly change 
this integer values results in:


Unable to cast Python instance to C++ type (compile in debug mode for 
details)



My second question is about the produce() function. If it is used, 
general_work() should return

WORK_CALLED_PRODUCE
but how exactly? After calling produce() and telling the scheduler how 
many items are produced, can I then change output_items[0]?


My attempt at the forecast() function and general_work() is provided 
below. General suggestions/hints or a basic example would be helpful and 
immensely appreciated.
If additional background is required, let me know and I will gladly 
provide more information.



     def forecast(self, noutput_items, ninput_items_required):
     # Estimate, that we will always need self.buffer_len items on 
input port [0]

     ninput_items_required[0] = self.buffer_len


     def general_work(self, input_items, output_items):
         # Firstly check, if input_items has a sufficient amount of items
     if len(input_items[0]) >= self.buffer_len:
     # Then consume exactly self.buffer_len items on input_port[0]
     self.consume(0, self.buffer_len)
     # Now I would like to only output a fraction of the input 
items

             # say the first self.out_items, on output port[0]
     self.produce(0, self.out_items)
     # Do I set my output_items[0] here?
     # Now I only need to return WORK_CALLED_PRODUCE, but how exactly?
     return 0


Kind regards,
Patric Müller







GNU Radio 3.9.3.0 Python basic block - issues with forecast and produce

2022-01-21 Thread Patric Müller

Hello everyone,

I am currently trying to create a basic block similar to this issue:
https://stackoverflow.com/questions/68289222/gnu-radio-buffer-size

Some software and hardware info:
GNU Radio version: 3.9.3.0
Python version: 3.9.7
OS: Ubuntu 20.04

I always want to work on the same number of items, say 1024. Now I would 
like to use up all of the items, but I want to produce something between 
say 500 - 1500 items.
For now, I would like to create a simple block with one input and one 
output, which consumes a set number of items and outputs only a fraction 
of those incoming items in order to understand it better.


Therefore I started with a general block and wanted to change 
ninput_items_required[0].
However, ninput_items_required is an integer. Trying to directly change 
this integer values results in:


Unable to cast Python instance to C++ type (compile in debug mode for 
details)



My second question is about the produce() function. If it is used, 
general_work() should return

WORK_CALLED_PRODUCE
but how exactly? After calling produce() and telling the scheduler how 
many items are produced, can I then change output_items[0]?


My attempt at the forecast() function and general_work() is provided 
below. General suggestions/hints or a basic example would be helpful and 
immensely appreciated.
If additional background is required, let me know and I will gladly 
provide more information.



    def forecast(self, noutput_items, ninput_items_required):
    # Estimate, that we will always need self.buffer_len items on 
input port [0]

    ninput_items_required[0] = self.buffer_len


    def general_work(self, input_items, output_items):
        # Firstly check, if input_items has a sufficient amount of items
    if len(input_items[0]) >= self.buffer_len:
    # Then consume exactly self.buffer_len items on input_port[0]
    self.consume(0, self.buffer_len)
    # Now I would like to only output a fraction of the input items
            # say the first self.out_items, on output port[0]
    self.produce(0, self.out_items)
    # Do I set my output_items[0] here?
    # Now I only need to return WORK_CALLED_PRODUCE, but how exactly?
    return 0


Kind regards,
Patric Müller