Skip to content

FORM Cookbook

Ben Ruijl edited this page Apr 14, 2023 · 4 revisions

In the FORM Cookbook we will go through several common patterns and use-cases in FORM and provide ways to work around certain shortcomings.

Matching symbols and functions

The FORM pattern matcher currently cannot go through all possible options when matching symbols and function arguments. For example the following pattern may not match:

id p1?.p2?*f(p1?.p2?) = 1;

As a workaround, put all symbols into functions first.

Auto V p;
CF dot(s), f;

L F = p1.p2*f(p1.p2);

id p1?.p2? = dot(p1,p2);
id dot(p1?,p2?)*f(p1?.p2?) = 1;

Converting a function to a tensor

A function containing sums of vectors cannot be converted to a tensor:

I mudummy;
V p,p1,p2;
CF gamma;
T gammat;

L F = gamma(p1+p2);
id gamma(?a) = gammat(?a);

will crash with Illegal substitution of argument field in tensor.

With the following id statement we can linearize the gamma:

repeat id once gamma(?a,p?!vector_,?b) = p(mudummy)*gamma(?a,mudummy,?b);

which exploits the normalization order. FORM first creates two terms and then it contracts with gamma, yielding gamma(p1)+gamma(p2).

Nested matching

When matching repeated wildcards that occur in functions nested in functions, the pattern matcher may sometimes fail:

id f(?a,f(?b,?a,?c),?d) = f(?a,f(?b,?c),?d);

may not work. Flatten the function first:

S split;
CF f;

id f(?a,f(?b),c) = f(?a,split,f,?b,split,c);
id f(?a,split,f,?b,?a,?c,split,?d) = f(?a,split,f,?b,?c,split,?d);
id f(?a,split,f,?b,split,?c) = f(?a,f(?b),?c);

The split variable is used as a unique identifier to signal the split.

Labeling each function uniquely

To label each function f uniquely per term, use a counter and repeat id once:

Auto S x;
CF f, counter;
L F = f(x1)*f(x2)*f(x3)*f(x4);

Multiply counter(1);
repeat id once f(x1?)*counter(x2?) = f(x2,x1)*counter(x2+1);
id counter(x?) = 1;

Labeling each term uniquely

Using a $-variable one can give a unique label to each term. We disable parallelisation explicitly.

Auto S x;
CF f, counter;
L F = f(x1)+f(x2)+f(x3)+f(x4);

#$counter = 0;
Multiply counter($counter);
$counter = $counter + 1;

Moduleoption noparallel;
.sort

Maximum element in a function

To find the maximum element in a function, we perform a repeated id statement that consumes the first two elements:

CF f;
L F = f(1,2,3,7,4,5);
repeat id f(x1?,x2?,?a) = f(max_(x1,x2),?a);

Graph manipulation

Graphs can be represented in FORM as a multiplication of vertices:

Auto V p,Q;
CF vx,vxs(s);

L F = vxs(Q1,p1,p6)*vxs(p1,p2,p7)*vxs(p2,p3,p8)*vxs(p3,p4,Q2)*vxs(p4,p5,p7)*vxs(p5,p6,p8);

The pattern matcher does not allow matching with ranged wildcards inside symmetric functions, so we first convert the function to a non-symmetric one:

Multiply replace_(vxs,vx);

Now we use the fact that $L=E-V+C$, relating the number of loops $L$ to the number of edges and vertices and connected components and $2 E = \sum_i N_i i$ relating the number of edges to the number of vertices of rank $i$ to obtain information about the graph. Below we obtain the number of vertices and edges:

Auto S n;
CF nv,ne,nl;

id vx(?a) = nv*ne^nargs_(?a)*vx(?a);
id ne^n? = ne^(n/2);

To get the number of connected components we shrink every edge of the graph, fusing the two vertex endpoints. The number of remaining vertices is the number of connected components. After, we obtain the total number of loops.

CF nc;

repeat id vx(?a,p?,?b)*vx(?c,-p?,?d) = vx(?a,?b,?c,?d);
id vx(?a) = nc;

id nv^n1?*ne^n2?*nc^n3? = nl^(n2 - n1 + n3)*nv^n1*ne^2*nc^n3;

Checking if a vector is positive

Using the vector_ set, one can distinguish a -p from a p.

Auto V p;
CF f;

L F = f(-p1,p2);
repeat id f(?a,-p?vector_,?b) = -f(?a,p,?b);

To distinguish a symbol from a number, one can use number_.

Loops with sorts inside

A common pattern is to keep on applying a certain set of transformations until it is no longer possible to apply them. A repeat block can be used, but it does not allow for placing any .sort inside them. Thus, when terms are expected to cancel, using repeat is highly inefficient. A solution is to use the preprocessor to create a loop whose counter keeps getting reset when a match is still possible for any of the terms in the active expressions:

S x;
CF f;
Local F = f(30);
#do i = 1,1
    id f(x?{>1}) = f(x - 1) + f(x - 2);

    if ( match(f(x?{>1})) ) redefine i "0";
    .sort
#enddo
Print +s;
.end

Split off unique functions

To get all unique function arguments in an expression, use extrasymbols.

Auto S x;
CF f;

L F = f(x1)*f(x2)*f(x1*x2);

#define start "{`extrasymbols_'+1}"
argtoextrasymbol tonumber, f;
.sort:collect;

This gives:

F = f(1)*f(2)*f(3);

Now we use the preprocessor to iterate over all new 'extra' symbols and for example split them into a new expression:

#define end "`extrasymbols_'"
#do i=`start`,`end'
    L F`i' = extrasymbol_(`i');
#enddo

This is a convenient way to perform a symbolic manipulation of a function that will grow very rapidly in size. For example when computing Feynman diagrams, the same diagram could appear with many different numerators:

(c1+c2+...)*f(graph1) + (c3+c4+...)*f(graph2) 

Applying the Feynman rules inside f will quickly go beyond the maximum allowed term size. Using the above technique, f(graphx) can be split off into a new function and can grow as large as needed. When the computation is done, the result of the computation could be substituted back in the original expression F:

Drop F`start`,...,F`end';
#do i=`start`,`end'
    id f(`i') = F`i';
#enddo

Collecting bracket content in new expression

When the content of a bracket fits inside a function, one could use the Collect statement:

Auto S x;
CF f, f1;

L F = x * (x1 + x2 + x3)^10;
B x;
.sort
Collect f;

However, when the content gets too large, one may want to split off the bracket into a new expression instead:

Auto S x;
CF f, f1;

L F = x * (x1 + x2 + x3)^200;
B x;
.sort
L F1 = F[x];

In some cases, one does not know the bracketed factors in advance. For example, when bracketing in a function that can have arguments that are only known at runtime. In that case, we can use the trick used to split off unique functions:

Auto S x;
CF f, f1;

L F = f(x1) * (x1 + x2 + x3)^100 + f(x2) * (x1 + x2 + x3)^200;
B f;
.sort
Keep brackets;

id f(?a) = f(f(?a));

#define start "{`extrasymbols_'+1}"
argtoextrasymbol tonumber, f;

B f;
.sort;

#define end "`extrasymbols_'"
#do i=`start',`end'
    L F`i' = F[f(`i')];
#enddo

L F = <extrasymbol_(`start') * f1(`start')>+...+<extrasymbol_(`end') * f1(`end')>;

Print +s;
.end

We first bracketed in f and turned the bracket into an extrasymbol (wrapping it in another f first so that the first argument of the f is the entire bracketed function). Now we know that we can index the expression F using F[f(1)], F[f(2)], etc. Then we construct the new expressions and overwrite the original one to reinstate the bracketed function and create a function f1 whose argument has the same index as the newly created expressions.

We get:

   F1 =
       + x3^100
       + 100*x2*x3^99
       + 4950*x2^2*x3^98
       + ...

   F2 =
       + x3^200
       + 200*x2*x3^199
       + 19900*x2^2*x3^198
       + ...

   F =
       + f(x1)*f1(1)
       + f(x2)*f1(2)
      ;