support arrays as parameters

This PR adds support for arrays as parameters to `Model` (#2049) and restores the original functionality:

Examples:

Create one model with 2 parameters: an array and a scalar. (param_dim=1 by default)

```
model = MyModel(param1 = np.array(...), param2 = 3)
```

The equivalent model with  multiple parameter set will be created only if param_dim is passed
to the model, no assumptions based on the size of the parameters. 

```
model = MyModel(param1 = [np.array(...), np.array(...)], param2 =[1, 3], param_dim=2)
```

This is different from the current behavior where the first example creates one model with `n` parameter sets, where n is the size of the array and `param2` is repeated n times.

The internal changes are:

- The `Parameter` class initializer takes a new boolean argument `optional`. It indicates that the parameter is not used internally by the model and exists only for the user API. For an example of its use see `Matrix2DRotation`, where `angle` is an optional parameter but internally the model works with the rotation matrix. This argument was added to avoid errors when validating parameters.
- Pulled the construction of `param_metrics` from `ParametricModel._initialize_parameters` into a separate method. Also moved it from `ParametricModel` to `Model` so that non-fittable models also have this attribute now. This allows to do parameter validation for all models based on `param_metrics` and not using `Parameter.shape` as is currently done.
- Removed `Parameter.shape` since it's unclear how it's defined when parameters are class variables, i.e. it persists between instances.
- `custom_model_1d` was changed to require parameter values when initialized.

Notes:

- After discussions here the `optional` argument was removed. `Matrix2DRotation` was changed
to work only with angles and was renamed to `RotateByAngle2D`.