Phase Unwrapping function question

Hello,
I’m trying to make sense of this phase wrapping code I found by @ssfrr. What is the whole inplace business about? I couldn’t find anything about it in the documentation. In particular, the inplace ? yphase : copy(yphase) is confusing to me.

Thanks,
Nakul

function unwrap(yphase, inplace=false)
    # currently assuming an array
    unwrapped = inplace ? yphase : copy(yphase)
    for i in 2:length(yphase)
      while unwrapped[i] - unwrapped[i-1] >= pi
        unwrapped[i] -= 2pi
      end
      while unwrapped[i] - unwrapped[i-1] <= -pi
        unwrapped[i] += 2pi
      end
    end
    return unwrapped
  end

I don’t know which code you are referring to, but my guess would be that there is an according unwrap!() function (notice the exclamation mark) which calls this function with inplace = true.

The inplace param tells the function that the provided data should be modified inplace as compared to creating and returning a copy.

Here you find more information on that convention:
https://docs.julialang.org/en/v1/manual/style-guide/#Append-!-to-names-of-functions-that-modify-their-arguments-1

2 Likes

condition ? iftrue : iffalse
is equal to

if condition
    iftrue
else
    iffalse
end

It’s called the ternary operator
https://docs.julialang.org/en/v1/manual/control-flow/#man-conditional-evaluation-2

3 Likes

The interface of that function seems to be a bit non-standard. But the meaning is as follows:

  • If you want unwrap to work directly on the input array and modify that, we say that it is operating ‘in-place’.
  • If you don’t want the original array to be modified, you create a copy of that array and modify that instead. That way, the original inpu array is untouched.

Perhaps the example you showed is very old code, but today one would normally define two functions, unwrap! and unwrap, where the first modify the input array in-place, and the second does not. The first function, unwrap!, would then do all the work, and the second would just create a copy for unwrap! to work on:

unwrap(yphase) = unwrap!(copy(yphase))

This is a very common pattern in Julia, so it’s a bit odd that you couldn’t find any info about it. Did you search the documentation?

BTW: The ! at the end of unwrap! is a convention, and signals that the function will modify (or mutate) some of its input arguments.

3 Likes

Thank you! I did search the documentation but I could not find anything that explicitly describes “in-place.” The only context I could find it in was described in conjunction with broadcast.

I’m still struggling to wrap my head around the differences. I wanted to clarify a few things. Is broadcasting (using the dot) syntax is the same as operating in-place (using the !) syntax? For some reason I had thought they were different because of a thread I made a while back. On that thread, I was advised not to use push! but to use broadcast operators for the mathematics I was doing. If I properly understand, they are both operating in-place. If that is indeed the case, push! was not recommended for different reasons not to do with operating-in-place.

Also, is inplace just a dated way of using an exclamation point? The original code was written 5 years ago.

If that’s the case, just to be clear, you are saying that these two lines are equivalent:

unwrapped = inplace ? yphase : copy(yphase)
unwrap(yphase) = unwrap!(copy(yphase))

Thanks a bunch!

You are mixing up broadcasting and the ! function naming convention, although they have nothing to do with each other.

The exclamation mark ! in a function name is just a convention to tell the caller of the function that the arguments of the function are going to be mutated (= adjusted “inplace”). e.g:

julia> a = [3,1,2];
julia> sort!(a);
julia> a # the array that binds to variable `a` which went into sort! is now sorted
3-element Array{Int64,1}:
 1
 2
 3

The same function without the ! behaves differently and doesn’t change the provided array:

julia> a = [3,1,2];
julia> b = sort(a);
julia> a # the array that binds to variable `a` is untouched
3-element Array{Int64,1}:
 3
 1
 2

You use sort without the exclamation mark to get a sorted copy of a instead (in this example bound to variable b).

This manual section explains what broadcasting is:
https://docs.julialang.org/en/v1/manual/arrays/#Broadcasting-1

3 Likes

As @laborg says, ! and broadcast . have nothing to do with each other.

The exclamation mark is not syntax, and doesn’t do anything, so if you create a function foo, and then try to add an exclamation mark, you get

julia> foo!(1)
ERROR: UndefVarError: foo! not defined
Stacktrace:
 [1] top-level scope at none:0

So foo! does not exist, you have to explicitly define it. The ! is just an ordinary character that you can put into a name. Defining, for example hello!world = 4, creates a variable with the name hello!world. But everyone has just agreed that if your function modifies any of its input arguments, then the name of that function should end with ! to help the user see what’s going on.

The broadcast . is very different. If you add a . to a function or an operator it changes how it works. If you take your function foo and add a dot to it, it will work:

julia> foo.(1:3)
3-element Array{Float64,1}:
  1.3817732906760363
  0.4931505902785393
 -0.8488724885405782

(I have actually defined f(x) = sin(x) + cos(x)). You don’t need to define the function foo., instead, . is a syntax that changes the behaviour of foo (unlike ! !!)

The dot broadcast operator in its simplest sense changes your functions to work elementwise, so foo.(1:3) will loop over the input 1:3 and produce an output vector.

As for the in-place behaviour, you get that when assigning. If you already have a length 3 vector called y lying around, you can do this

y .= foo.(1:3)

Then y will be modified in-place, in stead of having to define a new output variable for foo..

Dot broadcasting can do a lot more, this was just the simplest possible example. Take a look here for a much more educational introduction: https://julialang.org/blog/2017/01/moredots

4 Likes

No, no. They are not the same. The last line is a function definition. If you have an already existing funtion unwrap! that modifies its input, yphase, then you can create a non-mutating version of that function in a very simple way, namely by defining a new function unwrap, and then creating a copy of yphase which you give to the unwrap! function to work on. That way, you leave the original yphase unchanged.

The first line is not a function definition. In stead it creates a new variable unwrapped. It uses the ternary operator (Control Flow · The Julia Language). We can rewrite that line like this:

if inplace  # this is a boolean that is true or false
    unwrapped = yphase  # if inplace is true, then work directly on yphase and modify it in-place
else
    unwrapped = copy(yphase) # if inplace is false, create a copy of yphase and work on that in stead
end
# ...
# down here we do the actual work, modifying unwrapped

We use the ternary operator because it’s shorter and faster to write.

3 Likes

Ah, you are wonderful teachers. Everything makes a whole lot more sense now. To summarize what I’ve learned:

  • Executing in place refers to using an existing array to copy into without allocating extraneous copies.

  • ! is a function naming convention, not an actual operation, but conventionally refers to functions that mutate their input

  • The dot operator in its most general sense changes functions to work element-wise.

  • The dot operator allows for execution in-place when used with an assignment operator, but that is not the entire breadth of the dot operator’s capabilities.

  • Ternary syntax condenses if-then expressions

  • Creating a non-mutating version of a function is as simple as modifying a mutating function to work on a copy of its function input instead of the original input.

Thanks for the help and the links as well!

6 Likes

Note that these points are all documented in the excellent manual:

https://docs.julialang.org/en/stable/

It is fine to ask questions, but perhaps reading through the manual first is useful.

1 Like

@Tamas_Papp , with all due respect I already know that the manual is excellent. In the case of this post and in my replies to it I indeed mentioned the relevant documentation that I read from the manual but I still ended up grossly misinterpreting that information. My problem is that I am trying to learn programming and signal processing at the same time. The result is that my mind becomes saturated quite quickly (often with misinformation).

The findings I’ve posted are indeed trivial, but I wanted to post them to acknowledge my understanding to those who who took the time to help me out.

1 Like