# Attributes

Both `Places` and `Agents` can have a set of attributes attached. However, we cannot do that by adding member variables directly into the `Place` and `Agent` class since that will result in severe performance degradation due to uncoalesced memory access. As a result, special APIs are provided for defining and accessing attributes. 

## Define Attributes

The `setAttribute` function is used to define attributes, which takes in a template type argument for the type of that attribute, and together with 4 normal arguments: 

1. `tag`: An integer tag for identifying the attribute
2. `elementCount`: Number of elements for the attribute. Value larger than 1 mean that this is a 2D attribute (array attribute). Value should not be smaller than 1. 
3. `initialValue`: The initial value of the attribute, should have the same type as the template type argument provided. This argument is optional, but it is recommended to provide it, otherwise it will be uninitialed random bytes.

For `Places`, there is one additional parameter `permernentlyExcludedFromSyncBorder`, which is a boolean showing whether this attribute should be permanently excluded during `syncPlaceBorder` call. This argument is optional with default value of false. It is mainly designed for internal uses and is usually not necessary to be modified. Only set it to true when you are 100% sure what you are doing. 

Normally, before actually creating the attributes, it is recommended to define the tags of all the required attributes as an enum in the custom `Place` or `Agent` class. 

```cpp
class MyPlace final : public mass::Place {
public:
    __universal__ explicit MyPlace(const mass::Index localIndex) : mass::Place(localIndex) {}

    enum Attribute {
        INT_ATTR,
        DOUBLE_ATTR,
        FLOAT_ARR_ATTR
    };

    __device__ void callMethod(const mass::FunctionId functionId, void*const args) {
        // ...
    }

    // Other member methods...
};
```

Then, we can define the attributes through the created `Places` or `Agents` instance with the tags defined in the enum. After all the attributes being defined, call `finalizeAttributes` to commit the definition. 

```cpp
const auto places = mass::Mass::createPlaces<MyPlace>(/* ... */);

// an int attribute with inital value 0
places->setAttribute<int>(MyPlace::Attribute::INT_ATTR, 1, 0);
// a double attribute with no initial value
places->setAttribute<double>(MyPlace::Attribute::DOUBLE_ATTR, 1);
// a float 2D attribute (array attribute) with length of 4 
// and inital value 2.0f for each element. 
places->setAttribute<float>(MyPlace::Attribute::FLOAT_ARR_ATTR, 4, 2.0f);

places->finalizeAttributes();
```


## Access Attributes

Once the attributes are defined with `setAttribute` and committed with `finalizeAttributes`, they are ready to be accessed within member methods of the custom `Place` or `Agent` class. To access the value of an attribute, use the `getAttribute` and `getArrAttribute` methods, where both of them takes in the type of the attribute as a template argument and the tag of the attribute as a normal argument. 

The return values of these two methods are pointer-like types pointing to the attribute value, and we can access the value using `*` or `->` operator. For `getAttribute`, it is designed for 1D attributes (non-array attributes) whose value is a single value, so the return type does not implement the subscript operator `[]`. On the contrary, `getArrAttribute` is designed for 2D attributes (array attributes), so the return type implements the subscript operator `[]` for accessing each element. 

Continue with the `MyPlace` class above, we can add a member method and access the attributes as follows.

```cpp
class MyPlace final : public mass::Place {
public:
    __universal__ explicit MyPlace(const mass::Index localIndex) : mass::Place(localIndex) {}

    enum Attribute {
        INT_ATTR,
        DOUBLE_ATTR,
        FLOAT_ARR_ATTR
    };

    enum Function {
        FUNC1
    }

    __device__ void callMethod(const mass::FunctionId functionId, void*const args) {
        switch (functionId) {
        case FUNC1: func1(); break;
        default:             break;
        }
    }

    __device__ void func1() {
        // get pointers to the attributes
        const auto intAttr = getAttribute<int>(Attribute::INT_ATTR);
        const auto doubleAttr = getAttribute<double>(Attribute::DOUBLE_ATTR);
        const auto floatArrAttr = getArrAttribute<float>(Attribute::FLOAT_ARR_ATTR);

        // read attribute values and do some computation
        const auto v = *intAttr + *doubleAttr * 2;
        
        for (int i = 0; i < 4; i++) {
            // read & write to 2D attribute
            floatArrAttr[i] += static_cast<float>(v + i);
        }
    }
};
```


## Shared Attributes

Normal attributes have individual values for each `Place` or `Agent` instance. However, sometimes we might want to store some constants shared by all instances. We can still use normal attributes for that, but it wastes memory since we are storing the same thing repeatedly. For this use case, it is recommended to use the Shared Attributes. 

To define a shared attribute, the process is very similar to normal attributes, where we first define the tags for all the shared attribute using enum.

```cpp
class MyPlace final : public mass::Place {
public:
    __universal__ explicit MyPlace(const mass::Index localIndex) : mass::Place(localIndex) {}

    enum SharedAttribute {
        INT_ATTR,
        FLOAT_ARR_ATTR
    };

    // Other member methods...
};
```

And then use the `addSharedAttribute` method to define the shared attributes, which takes in the same arguments as `setAttribute` method with just 2 exceptions. First, the `initialValue` argument is not optional and must be provided. Second, the `permernentlyExcludedFromSyncBorder` argument is not available. 

```cpp
const auto places = mass::Mass::createPlaces<MyPlace>(/* ... */);

// an int shared attribute with inital value 0
places->addSharedAttribute<int>(MyPlace::SharedAttribute::INT_ATTR, 1, 0);
// a float 2D shared attribute (array attribute) with length of 4 
// and inital value 2.0f for each element. 
places->addSharedAttribute<float>(MyPlace::SharedAttribute::FLOAT_ARR_ATTR, 4, 2.0f);

places->finalizeAttributes();
```

With that, we can then access these shared attributes within the member methods of the custom `Place` or `Agent` class using the `getSharedAttribute` and `getSharedArrAttribute` methods, which is again very similar to the ones for accessing normal attributes. 

The return types of `getSharedAttribute` and `getSharedArrAttribute` are the same pointer-like types as the normal attributes, but they are "pointer-to-constant", meaning that they cannot be used to modify the attribute values. This is by design, since shared attributes are intended to store constants and should not be modified once initialized. 

```cpp
class MyPlace final : public mass::Place {
public:
    __universal__ explicit MyPlace(const mass::Index localIndex) : mass::Place(localIndex) {}

    enum SharedAttribute {
        INT_ATTR,
        FLOAT_ARR_ATTR
    };

    enum Function {
        FUNC1
    }

    __device__ void callMethod(const mass::FunctionId functionId, void*const args) {
        switch (functionId) {
        case FUNC1: func1(); break;
        default:             break;
        }
    }

    __device__ void func1() {
        // get pointers to the shared attributes
        const auto intAttr = getSharedAttribute<int>(SharedAttribute::INT_ATTR);
        const auto floatArrAttr = getSharedArrAttribute<float>(SharedAttribute::FLOAT_ARR_ATTR);

        // read attribute values and do some computation
        const auto v = *intAttr + floatArrAttr[0] + floatArrAttr[2] * 3;

        // ERROR: cannot modify shared attributes
        // floatArrAttr[3] = v;
    }
};
```


## Pre-Defined Attributes

Besides the custom attributes we defined during simulations, MASS CUDA also provides some pre-defined attributes for both `Place` and `Agent`. The value of these attributes can be accessed through provided getters in `Place` and `Agent` classes. 

For `Place`, the following pre-defined attributes are provided: 

| Getter | Description | 
| --- | --- |
| `getIndex()` | get the flattened global index of the current `Place` instance |
| `getLocalIndex()` | get the flattened index of the current `Place` instance within the current block (including Ghost Regions) |
| `getFlattenBlockIndex()` | get the flattened index of the current block |
| `getNumPlaces()` | get the total number of `Place` instances within the `Places` grid | 
| `getNeighborsPtr()` | get array of pointers to all the neighboring instances of the current `Place` instance| 
| `getAgents()` | get the array of pointers to all the `Agent` instances residing on the current `Place` instance |
| `getAgentPopulation()` | get the number of `Agent` instances residing on the current `Place` instance |
| `getPotentialAgents()` | get the array of `Agent` instances trying to migrate into the current `Place`, but haven't been moved yet | 
| `getPotentialPopulation()` | get the number of `Agent` instances trying to migrate into the current `Place`, but haven't been moved yet |

For `Agent`, the following pre-defined attributes are provided: 

| Getter | Description |
| --- | --- |
| `getId()` | get the unique ID of the current `Agent` instance | 
| `getLocalIndex()` | get the flattened index of the current `Agent` instance on the current device |
| `getIndex()` | Same as `getLocalIndex()` | 
| `getPlace()` | get pointer to the `Place` instance where the current `Agent` instance resides |
| `getPlaceIndex()` | get the flattened index of the `Place` instance where the current `Agent` instance resides. Same as `getPlace()->getIndex()` |
