Function of ALG Language

Table of contents

Syntax Basics

The initiative of proposing a new programming language for algorithm implementation is based on the multi-backend design of mmCEsim. The language is specially designed so that it can be exported to C++ (with Armadillo), Python (with NumPy) and Matlab/Octave easily.

Every line of ALG language calls a function. Let’s first have a look at its basic structure before we cover its details.

ret1::type1 ret2 = FUNC param1 param2::type2 key1=value1 key2=value2::type3 # comments

It may look like an assembly language at the first glance, due to all parameters are separated by space. But it is actually much more convenient. Here are some basic rules:

  • All tokens are separated by space.
  • Function names are in all upper cases, like CALC, WHILE.
  • Indentation does not matter. Blocks are ended with END.
  • The function line is mainly composed of three parts: return values, function name and parameters, in the left to right direction.
  • Some functions may not have return values, and you may also omit the return values. If there are return values, there is a = between return values and function names.
  • Function name is the first word on the right of = (if there are return values) or the first word of line (if there is no return value).
  • Like Python, parameters can be passed in by two ways:
    1. value in position: Like param1 and param2 in the above example. Parameters in different positions correspond to different usages in the function. This is the only way in C++.
    2. key and value: Parameters can also be specified using key and its corresponding value. value1 and value2 are passed in using this method. It should be noted that there should be no space around the = between key and value.

    There are some special cases that parameters are viewed as a whole, for example COMMENT and CALC.

  • If a parameter contains space or special characters, you need to use the double quotes like "param with space" and escape special characters as in C++ and Python.
  • You may optionally specify the type of return value and parameters with :: after the value. For example, in the above example dtype1, dtype2 and dtype3 are type specifications for ret1, param2 and value2, respectively. For more information about data type, please refer to data type of ALG language.
  • Like Python, the backslash (\) at the end of the line can be used for continuing the function on next line.
  • Comments start with the hash (#) like Python.

There should be no space around the = between key and value for parameters. For example, key=val is valid while key = val is forbidden.

Special rules may be applied for different functions. Please refer to the specific documentation for each function.


BRANCH

Declare start of the scope of job algorithms.

Explanations

This is useful in estimation. Contents between BRANCH and MERGE will be repeated for different algorithms. So you need to place compressed sensing estimation ESTIMATE and RECOVER inside.

Example

Example of OFDM OMP.


BREAK

Break from a block (for FOR, FOREVER, LOOP, WHILE).

Explanations

The same as break in C++, Python and Matlab/Octave. This function takes no parameter.

Example

Example with FOREVER.


CALC

Make arithmetic calculations.

Explanations

There are two kinds of CALC usage: inline and standalone.

  • inline: The contents to be calculated are placed in a set of dollar signs, like LaTeX syntax: $some operations to be calculated$.
  • standalone: This is like a normal function, with function name as CALC. You may also omit the function name CALC since it is the default function name if nothing is specified. Therefore, result = CALC your expression is equivalent to result = your expression.

For more information about the CALC syntax, please refer to CALC details.

In the current version, all spaces inside CALC are eliminated, so string operations should be avoided.

For safety, you should not use anything other than ANSI characters in CALC functions. Otherwise, there can be undefined behaviour.

If you want the calculation result to be a new variable, you may use function NEW.

Example

C++

a = b + 2;
a = arma::sin(b) * c;
a = b.t() + c.i();
c = b(2, 3);
c = arma::abs(b(arma::span::all, 3)) + arma::pow(b, 2);
arma::exp2(a + c % d) / e.st() - f(arma::span::all, 3, arma::span(1, index));
a = CALC b + 2 # explicit CALC function
a = \sin(b) @ c # implicit CALC function
a = b^H + c^{-1} # conjugate transpose and inverse
c = b_{2, 3} # get element of a matrix
c = \abs{b_{:, 3}} + \pow(b_{}, 2) # use : in subscript & use {} for function
\exp2(a + c .* d) ./ e^T -f_{:,3,1:index} # element-wise operator and subscript : range

CALL

Call a custom function defined by FUNCTION.


COMMENT

Place a line of comment in the exported code.

Explanations

All contents after the function keyword COMMENT are considered as comments.

In the current version, all spaces in the comment between words will be changed into one whitespace. In future releases, comment text will remain its original style.

Example

C++

// Hi, this is a comment!

Python

# Hi, this is a comment!

Matlab/Octave

% Hi, this is a comment!
COMMENT Hi, this is a comment!

CPP

Write standard C++ contents.

Explanations

All contents after the CPP keywords are copied to exported codes. For backend other than C++, this function is ignored.

Example

C++

std::cout << "Standard C++ Language!" << std::endl;

Python/Matlab/Octave

                                                      <--- (Nothing)
CPP std::cout << "Standard C++ Language!" << std::endl;

ELIF

Alias for the continuous ELSE and IF.

Explanations

The parameter is the same as IF.

Example

Example with IF.


ELSE

Used in IF block.

Explanations

This function implements as else in C++, Python and Matlab/Octave. There is no parameter for the ELSE function.

Example

Example with IF.


END

End of a block (for ELSE, ELIF, FOR, FOREVER, IF, LOOP, WHILE).

Explanations

In C++, this functions as }, in Python it is the indentation goes back for one block. In Matlab/Octave, it is the end specification.

Example

Example with FOR, FOREVER, IF, LOOP, WHILE.


ESTIMATE

CALL standard ALG functions to estimate the sparse channel with compressed sensing (CS).

Explanations

There are four parameters.

Position Parameter Key Descriptions
1 Q The sensing matrix.
2 y The received signal.
3 nonzero The indices vector of non-zero elements.
4 init Whether to initialize a variable (boolean).

Example

Estimation for MIMO (with no RIS):

VNt::m = NEW `DICTIONARY.T`
VNr::m = NEW `DICTIONARY.R`
lambda_hat = INIT `GRID.*`
Q = INIT `MEASUREMENT` `GRID.*`
i::u0 = LOOP 0 `PILOT`/`BEAM.T`
  F_t::m = NEW F_{:,:,i}
  W_t::m = NEW W_{:,:,i}
  Q_{i*`BEAM.*`:(i+1)*`BEAM.*`-1,:} = \kron(F_t^T, W_t^H) @ \kron(VNt^*, VNr) # the sensing matrix
END
BRANCH
lambda_hat = ESTIMATE Q y
RECOVER $VNr @ \reshape(lambda_hat, `GRID.R`, `GRID.T`) @ VNt^H$
MERGE

Example for Single RIS assisted MIMO:

COMMENT STEP 1: Load Dictionary
VNt::m = NEW `DICTIONARY.T`
VNr::m = NEW `DICTIONARY.R`
V_M::m = NEW `DICTIONARY[RIS]`
V_N::m = NEW \kron(VNt^*, VNr)
D::m   = INIT `GRID[RIS]` `SIZE[RIS]`
LOOP 0 `SIZE[RIS]`
  tmp::v = NEW \kron(V_M_{i,:}^T, V_M_{i,:}^H)
  D_{:,i} = tmp_{0:`SIZE[RIS]`-1}
END

COMMENT STEP 2: Compressed Sensing Formulation
Q::m = INIT `MEASUREMENT` $`GRID.*` * `GRID[RIS]`$ # sensing matrix
K = NEW `GRID[RIS]` # number of blocks
T = NEW `PILOT`/`BEAM.T`/K # number of sounding times
k::u0 = LOOP 0 K
  t::u0 = LOOP 0 T
    i::u0 = NEW k * T + t
    psi::v = NEW Psi_{:,i} # one reflection vector
    F_t::m = NEW F_{:,:,i}
    W_t::m = NEW W_{:,:,i}
    Z_T::m = NEW \kron(F_t^T, W_t^H)
    first_index::u0 = NEW (k * T + t) * `BEAM.*`
    last_index::u0  = NEW first_index + `BEAM.*` - 1
    Q_{first_index:last_index,:} = \kron((D @ psi)^T, Z_T @ V_N)
  END
END

COMMENT STEP 3: Calculate Ground Truth
G_ang::m = NEW VNr^H @ G @ V_M * `GRID.R` * `GRID[RIS]` / `SIZE.R` / `SIZE[RIS]`
R_ang::m = NEW V_M^H @ R @ VNt * `GRID.T` * `GRID[RIS]` / `SIZE.T` / `SIZE[RIS]`
J::m = NEW \kron(R_ang^T, G_ang)
Lambda::m = CALL mergeToLambda J init=true

COMMENT STEP 4: Apply Compressed Sensing Algorithms
lambda_hat = INIT $`GRID.*` * `GRID[RIS]`$
BRANCH
lambda_hat = ESTIMATE Q y
Lambda_hat::m = NEW \reshape(lambda_hat, `GRID.*`, `GRID[RIS]`)
RECOVER Lambda_hat Lambda
MERGE

FOR

Start a for loop.

Explanations

The parameters are similar to C++.

Position Parameter Key Descriptions
1 init Initialization before entering the loop.
2 cond Condition to continue into the loop.
3 oper Operation after each iteration.

If there is = or other special characters inside your parameter or there exists space, do remember to place them inside double quotes (").

Example

C++

for (uword i = 0; i != 10; i = i + 2) {
    // Do something here in the for loop.
}
FOR "i::u0 = INIT 0" "i != 10" "i=i+2" # a for loop taking three parameters
  COMMENT "Do something here in the for loop."
END

FOREVER

Repeat in the block until BREAK.

Example

C++

while (1) {
    break;
}
FOREVER # takes no param
  BREAK # Wow, nothing is done when I just break here [Lol]
END

FUNCTION

Start a function definition.

Explanations

The function requires an END to mark the end of function.


IF

Conditional statement.

Explanations

This works the same as if in C++, Python, Matlab/Octave. All contents after the IF keyword are part of the condition. If you insist using the key value style, the key is cond.

Example

C++

if (arma::accu(arma::pow(arma::abs(A), 2)) > 0.1 * threshold) {
    if (b < 0) {
        b = 0;
    } else if (b > 100) {
        b = 100;
    } else {
        b = -b;
    }
} else {
    if (c == d) {
        A = A * 0.1;
    }
}
IF \accu(\pow(\abs(A), 2)) > 0.1 * threshold
  IF b < 0
    b = 0
  ELIF b > 100
    b = 100
  ELSE
    b = -b
  END
ELSE
  IF cond="c == d" # use key value style if you insist
    A = A * 0.1
  END
END

INIT

Initialize a variable.

Explanations

This function can initialize a scalar, a vector, a matrix and a tensor. The initialization target can be specified in two ways:

  • return value type specification: You can specify the type of the variable to be initialized by ::.
  • parameters: Parameter dtype is used for element type, and dim1, dim2, dim3 are used for dimension specification.

Please be consistent! The current implementation of the function is fragile and can be fooled by any inconsistent actions. While we are trying to enhance the error detection, you are advised to use the correct dimension.

However, there are also a few exceptions for user’s convenience. Though row vector (r) is regarded as a matrix, you can still specify its dimension with only one parameter on dim1. For a scalar initialization, the value can directly follow =.

Position Parameter Key Descriptions
1 dim1 Size of the first dimension (for vector).
2 dim2 Size of the second dimension (for vector and matrix).
3 dim3 Size of the third dimension (for vector, matrix and tensor).
4 fill Element filling mode. randn for standard normal distribution \(\mathcal{N}(0, 1)\), randu for standard uniform distribution \(\mathcal{U}(0, 1)\), zeros for filling as 0, ones for filling as 1. Default option is zeros.
5 scale Scale of the value. This works like multiplying a value after the initialization by fill.
6 dtype Element data type. This is the one character prefix like c, i. The default value is complex (c).

For initialization of a row vector (r), you may just use one dimension. For initialization of a scalar (dimension as 0), you can specify the value directly after =, but if you want to use fill and scale, you must specify the parameter key.

Since the development of ALG concentrates on matrix operations, the initialization also performs in a matrix-oriented way. Please refer to data type of ALG language if you are not sure how the data type works.

Example

C++

cx_mat a = (4) * arma::ones<cx_mat>(4, 3);
cx_mat b = (-1 + i) * arma::zeros<cx_mat>(1, 10);
const double pi = 3.1415926;
std::complex<double> random_number = (-2) * arma::randn<std::complex<double>>();
a = INIT 4 3 fill=ones scale=4 dtype=c # a matrix
b::r = INIT 10 scale="-1+i" # row vector (viewed as a matrix)
pi::f0c = INIT 3.1415926 # a const float
random_number = INIT fill=randn scale=-2

LOG

Write to log file.

Example

Log file: mmcesim.log

[INFO] $ LOG [INFO] A user-defined message.
[INFO] * FUNCTION: LOG
[INFO] * PARAMS:
[INFO]    > {}={[INFO]}::{}
[INFO]    > {}={A}::{}
[INFO]    > {}={user-defined}::{}
[INFO]    > {}={message.}::{}
[INFO] A user-defined LOG message.
LOG [INFO] A user-defined message.

LOOP

Loop with iteration counter.

Explanations

The LOOP function uses an iteration counter to control the loop. The parameters are shown as below.

Position Parameter Key Descriptions
1 begin The starting iteration counter.
2 end The end iteration counter (not included).
3 step Iteration counter step.
4 from The starting iteration counter.
5 to The last interaction counter (included if step walks into it).

Normally, we use the begin + end format. You may also use from + to format, but the two formats cannot be used together.

The return value is the iteration counter. If it is not specified, the default one is i. You may also use :: to specify the iteration counter type.

Example

C++

for (auto i = 0; i < 10; ++i) {
    // 0, 1, 2, ..., 9
    for (auto j = 10; j >= 0; --j) {
        // 10, 9, 8, ..., 0
    }
    for (uword k = 0; k < 10; k += 2) {
        // 0, 2, 4, 6, 8
    }
}
LOOP 0 10 # implicit counter name as 'i'
  COMMENT 0, 1, 2, ..., 9
  j = LOOP from=10 to=0 step=-1
    COMMENT 10, 9, 8, ..., 0
  END
  k::u0 = LOOP begin=0 end=10 step=2 # specify counter type
    COMMENT 0, 2, 4, 6, 8
  END
END

PRINT

Print the contents.

Explanations

The function takes a list of parameters, and they can be matrices or scalar values. They are printed out from left to right.

Examples

C++

std::cout << 1 << '\t' << H << '\n';
PRINT 1 '\t' H '\n'

RECOVER

Declare the recovered channel.

Explanations

The NMSE/BER performance is evaluated with the recovered channel specified here.

Position Parameter Key Descriptions
1 est The estimated (recovered) channel.
2 real The real (ground-truth) channel.
3 num The total number of RECOVER statements.

The second parameter (real) is default as the cascaded channel (of size \(N_r\times N_t\)). Therefore, it is necessary for the system with RIS to specify the real channel (which may be of other definitions).

The third parameter (num) is used to specify the total number of RECOVER statements. Therefore, the NMSE result can be scaled correctly.

Examples

See examples of ESTIMATE.

Example of the num key.

C++

NMSE0(ii, 0) += mmce::nmse(H, H_cascaded) / K;
RECOVER H_hat num=K

MERGE

Declare end the scope of job algorithms.

Explanations

This works with BRANCH. View documentation of BRANCH for details.

If you do not specify MERGE, it is assumed to be end of estimation by default.

It is advised to always specify MERGE so that the range between BRANCH and MERGE is clearer and can be extended by other users more easily.

Examples

Example of OFDM OMP.


NEW

Create a new variable from CALC result.

Explanations

The parameters are the same as CALC, except for the return value type is required.

Examples

C++

cx_vec y = H * x + n;
y::v = NEW H @ x + n

WHILE

Repeat while the condition satisfies.

Explanations

This is the same as C++, Python and Matlab/Octave. The function takes only one parameter. (If you do need a key value style, it has key cond.)

Since only one parameter is needed, all contents after the WHILE keyword is viewed as the stop condition. So there is no need to quote the condition which is required in FOR.

Example

C++

while (i != 100 && result == false) {
    // Do something in the while loop.
}
WHILE i != 100 && result == false # no quote is needed because there will be only one param
  COMMENT "Do something in the while loop."
END