# Creating regions

* [Creating regions](/labpal-user-manual/regions.md#creating)
* [Iterating over regions](/labpal-user-manual/regions.md#iterating)
* [Irregular regions](/labpal-user-manual/regions.md#irregular)
* [Regions are objects](/labpal-user-manual/regions.md#regions-are-objects)
* [Filtering experiments](/labpal-user-manual/regions.md#filtering)
* [Counting points](/labpal-user-manual/regions.md#counting)

Very often, the principle behind our set of experiments is to execute the same basic process multiple times, and varying the input parameters each time we do it. For example, suppose an experiment `Foo` has two integer input parameters, A and B. In a lab, we might want to run `Foo` for all combinations of A and B between 1 and 10. Adding these experiments to the lab is straightforward, using two nested `for` loops:

```java
for (int a = 1; a <= 10; a++)
  for (int b = 1; b <= 10; b++)
    add(new Foo(a, b));
```

This works well for simple iterations including all combinations of all parameters between their respective bounds. Graphically, such combinations of *n* parameters form an *n*-dimensional "rectangle"; all the discrete *n*-dimensional points within its boundaries represents a valid parameter assignment.

The situation becomes more complicated if one wants to consider combinations of parameters that do not form a rectangle. To simplify this task, LabPal provides an object called a `Region`.

## Creating regions <a href="#creating" id="creating"></a>

A region is just that: a representation of a set of parameter assignments. Defining a region is done by instantiating an empty `Region` object:

```java
Region region = new Region();
```

Each distinct parameter in a region is called a **dimension**. For each dimension, the set of possible values for that dimension must be specified. One possible way is through method `addRange`:

```java
region.addRange("A", 1, 10).addRange("B", 1, 10);
```

This adds two dimensions to the region, A and B, each ranging between 1 and 10 by increments of 1. This can be represented by a two-dimensional graph such as this one:

![Region](/files/-LJTd9oxz4EeEyBDE6BK)

Alternately, one can create dimensions using the `add` method, which takes a dimension name, followed by any number of values. Values for a dimension can be numbers, character strings, or any object of type `JsonElement` (including lists and maps).

## Iterating on regions <a href="#iterating" id="iterating"></a>

Iterating over all points of a region can be done with method `all()`, which enumerates them. Each point is another `Region` object, this time with a single value for each dimension. This value can be obtained with a method called `get()`; casts to type XXX can be obtained with `getXXX`. For example, to create the same set of experiments as in our very first code sample, we can write:

```java
for (Region r : region.all())
  add(new Foo(r.getInt("A"), r.getInt("B")));
```

By default, method `all` enumerates points in lexicographical order. This order is defined by the order in which the dimensions have been added to the region, and the order in which values have been added to each dimension. In our example, A is the first dimension to be added, and its values are 1 to 10 in increasing order. The region iterator will start by setting A to 1, and then enumerate all regions where B=1, B=2, etc. Then it will set A to 2, and enumerate all regions where B=1, B=2, and so on.

The order in which dimensions are used can be changed by specifying them as arguments in `all`. For example, writing `region.all("B", "A")` will enumerate regions by first fixing B=1 and enumerating the As, then B=2, etc.

The arguments to `all` do not have to list all the dimensions. When fewer dimensions are specified, the resulting enumeration will consist of regions where some dimensions have a single value, and others are still ranges of values. For example, `region.all("A")` will first enumerate the region where A=1 and B is the range 1-10, then the region where A=2 and B is the range 1-10, and so on. Since the elements of this enumeration are themselves regions, one can still iterate over them using `all` to get regions of even smaller size.

## Irregular regions <a href="#irregular" id="irregular"></a>

So far, regions do not bring such a big advantage over our nested `for` loops (actually one: they are [objects instead of instructions](/labpal-user-manual/regions.md#regions-are-objects)). Things get different when considering regions of irregular shapes.

For example, suppose you'd like to keep in the region only elements where A is smaller than B. Graphically, this means that the region is no longer a rectangle, but rather a triangle like this:

![Region](/files/-LJTd9p0JfKMRS6e3l1Y)

To create such a region, we can "filter" points from an existing region using method `where`. This method expects a `Condition`: an object with a single method, `in`, that should return `true` if a given point belongs to the target region. The triangle above can be used by cutting out a portion of the original rectangle as follows:

```
Region triangle = region.where(new Condition() {
  boolean in(Region r) {return r.getInt("A") < r.getInt("B");}
});
```

Note how an anonymous `Condition` object is passed to the `where` method, whose `in` method checks that A < B.

These conditions can be chained, and progressively cut out pieces of an original region in increasingly irregular shapes. For example, to obtain the region corresponding to the following picture:

![Region](/files/-LJTd9p2jW-W5aZJ-I28)

one can write:

```
Region weird = region.where(new Condition() {
  boolean in(Region r) {return r.getInt("A") >= 2 * r.getInt("B");}
}).where(new Condition() {
  boolean in(Region r) {return r.getInt("A") + r.getInt("B") != 5;}
});
```

(Of course here, the two conditions could have been put into a single `Condition` object combined with `&&`, but the point here is that using `where`, you can cut any given region without knowing how it was made.)

Method `where` can accept multiple conditions; in such a case, it creates the region made of points that satisfy *all* of them. Therefore, an alternate syntax for the above is:

```
Region weird = region.where(new Condition() {
  boolean in(Region r) {return r.getInt("A") >= 2 * r.getInt("B");}
}, new Condition() {
  boolean in(Region r) {return r.getInt("A") + r.getInt("B") != 5;}
});
```

The dual of `where` is `or`. This is a method that accepts multiple conditions, and will create the region made of points that satisfy *either* condition. The following region:

![Region](/files/-LJTd9p4Vb_05a3isvZp)

can be created by the union of two subregions as follows:

```
Region or_region = region.or(new Condition() {
  boolean in(Region r) {return r.getInt("A") <= 3;}
}, new Condition() {
  boolean in(Region r) {return r.getInt("A") == 5 && r.getInt("B") == 5;}
});
```

## Regions are objects <a href="#regions-are-objects" id="regions-are-objects"></a>

This may seem obvious, but the fact that regions are *objects*, rather than *instructions*, has interesting side effects. Let us illustrate this with an example.

Suppose that you created a method `addToTable` that is expected to create a table out of a set of experiments. More precisely, it loops over all values of parameter A, and creates one table each for all experiments with the same value of A. If the combinations of parameters forms a square region, this can easily be achieved by providing ranges for A and B to the method:

```java
public void addToTable(int min_a, int max_a, int min_b, int max_b) {
  for (int a = min_a; a <= max_a; a++) {
    ExperimentTable t = new ExperimentTable("A", "B");
    for (int b = min_b; b <= max_b; b++) {
      t.add(new Foo(a, b));
    }
    add(t);
  }
}
```

But what if the region is not a square, but is rather commposed only of the points where A < 2B? This could be worked around:

```java
public void addToTable(int min_a, int max_a, int min_b, int max_b) {
  for (int a = min_a; a <= max_a; a++) {
    ExperimentTable t = new ExperimentTable("A", "B");
    for (int b = min_b; b <= max_b; b++) {
      if (a < 2b)
        t.add(new Foo(a, b));
    }
    if (!t.isEmpty())
      add(t);
  }
}
```

But now the shape of the region becomes hard-coded into the method; for a different shape, you would need a different version of `addToTable`. Notice also that we have to check if we added any experiments to a table, as there are values of A for which no B fulfills the condition.

Another workaround would be to create the list of experiments outside of `addToTable`, and to give this method only the collection of experiments:

```java
public void addToTable(Collection<Foo> col, int min_a, int max_a, int min_b, int max_b) {
  for (int a = min_a; a <= max_a; a++) {
    ExperimentTable t = new ExperimentTable("A", "B");
    for (int b = min_b; b <= max_b; b++) {
      for (Foo f : col) {
        if (f.readInt("B") == b)
          t.add(new Foo(a, b));
    }
    if (!t.isEmpty())
      add(t);
}
```

But now we run into another problem. We have to iterate first on all values of A, and then find all experiments in the collection that have this value, before adding them to the corresponding table. A reverse solution would be to create tables for all values of A first, iterate through the collection and add the experiment to the right table --but then again, this is starting to look like a hack. Our method, instead of getting simpler, is actually becoming more and more complicated.

But since regions are objects, this means they can be passed directly as an argument to a method, which simply iterates over points of the region without caring about how they are computed. A much more reusable version of `addToTable` would hence be:

```java
public void addToTable(Region region) {
  for (Region r_a : region.all("A") {
    ExperimentTable t = new ExperimentTable("A", "B");
    for (Region r : r_a.all()) {
      t.add(new Foo(r.getInt("A"), r.getInt("B")));
    }
    add(t);
  }
}
```

This method is much simpler, its meaning is easy to grasp, and it can work with whatever set of points one can build.

## Filtering experiments <a href="#filtering" id="filtering"></a>

Among the various uses of regions, one is to filter sets of experiments in a lab. The `Laboratory` class provides a method `filter` that takes as input an arbitrary region, and returns the set of experiments in the lab that lie within that region. For example, to get all experiments where A is between 1 and 5:

```java
Region region = new Region().addRange("A", 1, 5);
Collection<Experiment> exps = my_lab.filter(region);
```

Note that if an experiment has other parameters than A, their value is ignored. For an experiment to lie within a region, it must be such that if it has a parameter that is one of the regions' dimensions, its value must be one of those specified in that region's dimension.

Coupled with the possiblity of shaping complex regions, this makes it possible to filter experiments in a lab in a very flexible way.

## Counting points <a href="#counting" id="counting"></a>

An interesting perk of using regions is that counting points inside is easy, through method `size()`. This can also be used to count points that satisfy a condition, without having to iterate over them; it suffices to insert a `where` call before `size`:

```
Region region = ...;
int count = region.where(new Condition() {
  boolean in(Region r) {return getInt("A") < 2 * getInt("B");}}).size();
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://liflab.gitbook.io/labpal-user-manual/regions.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
