# Basic Usage

This section seeks to provide a simple example of how to interact with the reservoir as well as some background on other basic functionality provided for interacting with the device.

## Sine Wave

To illustrate a basic usage pattern, sequential observations from the sine wave function will be modelled by the reservoir. The first step will be to create the sine wave data for our example.

    import numpy as np
    import matplotlib.pyplot as plt

    # Define the x-values for the domain of the sine function input
    x = np.linspace(0, 6 * np.pi, 500)
    # Calculate the y-values using the sine function and probability function
    y = np.sin(x)

    # Plot the oscillator with probability-based amplitudes
    plt.plot(x, y)
    plt.xlabel('x')
    plt.ylabel('y')
    plt.title('Sine Wave')
    plt.grid(True)
    plt.show()

In the below code block make sure to replace both IP_ADDR and PORT with your specific network location for your EmuCore device

    from emucore_direct.client import EmuCoreClient
    from time import time

    IP_ADDR = "YOUR DEVICE IP ADDRESS"
    PORT = "YOUR DEVICE PORT"
    # Instantiate an EmuCore instance
    ec_client = EmuCoreClient(ip_addr=IP_ADDR, port = PORT)

Reservoir systems require state while processing so in order to prevent collisions a locking mechanism on the device is available. To acquire the execution lock a user must run:

    lock_id, _, _ = ec_client.wait_for_lock()

This function waits for the execution lock indefinitely until it becomes available.

The basic recipe for modeling using EmuCore is to sequentially feed data to the reservoir. This process creates an expanded representation based on the current configuration of the reservoir model. Subsequently, a model is applied to the augmented data from the reservoir. This expanded representation should be easier to model and may require less complicated models to achieve good prediction results for a given series. Before running another dataset, it is important to reset the reservoir; otherwise, it will retain information from previous data that was modeled. Next, the reservoir model must be configured. There are a variety of parameters that can be tuned to obtain better results from predictions. Here is an example of a possible configuration:

    vbias=0.3
    gain=0.5
    num_nodes=800
    num_taps=400
    input_scaling=0.33
    density=0.5

    ec_client.reservoir_reset(lock_id=lock_id)
    print("Reservoir config")
    # Configure
    ec_client.rc_config(
        lock_id=lock_id,
        vbias=vbias,
        gain=gain,
        num_nodes=num_nodes,
        num_taps=num_taps,
    )

    Reservoir config

    {'status': 0, 'message': 'Success'}

Now we begin modeling the data with the reservoir:

    y_resp, max_scale_val, weights = ec_client.process_all_data(
        input_data=y.reshape((-1,1)),
        num_nodes=num_nodes,
        density=density,
        feature_scaling=input_scaling,
        lock_id=lock_id,
        max_scale_val=None)

After processing it is important to release the device execution lock to free up the device for other usage:

    # releasing lock after finished with device execution
    ec_client.release_lock(lock_id=lock_id)

    {'status': 0, 'message': 'Success'}

Below, the output of the reservoir is visualized, with the coloring representing the value for each combination of a given node and a given value in our sequence from the sine function. Since the sine function generates a repeating sequence, observe how the values across nodes repeat for this particular series due to the cyclic nature of the underlying function. However, with more complex inputs, this representation can become much more varied.

    plt.imshow(y_resp, cmap="nipy_spectral")
    plt.colorbar()
    plt.xlabel("Node")
    plt.ylabel("Observation number (x)")
    plt.show()

Having acquired the data any number of models can be fit to predict future outcomes using the dynamics of the reservoir model.

## System Information

In order to obtain system information the package provides the method `emucore_direct.client.EmuCoreClient.system_info()`. An example of how to call this function is provided below:

    from emucore_direct.client import EmuCoreClient
    # replace with your network location
    IP_ADDR = "YOUR DEVICE IP ADDRESS"
    PORT = "YOUR DEVICE PORT"
    # Instantiate an EmuCore instance
    ec_client = EmuCoreClient(ip_addr=IP_ADDR, port = PORT)
    ec_client.system_info()
    # Example output:
    # {'system_name': 'Bumblebee', 'system_version': 'v0.1-10-ga4a5c1f'}

## Device Lock Basics

Device locking ensures that only one process is running on the device at a time. In order to ensure this functionality, there are a few useful functions:

> - `emucore_direct.client.EmuCoreClient.acquire_lock()`—makes a single attempt to acquire the device lock

> - `emucore_direct.client.EmuCoreClient.release_lock()`—releases lock if lock currently held by the user.

In order to ensure access to users there is a timer on the server that if a user is inactive for more than one minute and another user wishes to use the device they will be able to acquire the device lock and the previous idle user will lose it’s lock on the device. In order to help ease the process of acquiring the lock it’s recommended to use `emucore_direct.client.EmuCoreClient.wait_for_lock()`. In order to ensure maximum device availability it is a good practice to release the lock if taking time between runs on the device. Here is a basic example of how to use `emucore_direct.client.EmuCoreClient.wait_for_lock()` and `emucore_direct.client.EmuCoreClient.release_lock()`:

    from emucore_direct.client import EmuCoreClient
    # replace with your network location
    IP_ADDR = "YOUR DEVICE IP ADDRESS"
    PORT = "YOUR DEVICE PORT"
    # Instantiate an EmuCore instance
    ec_client = EmuCoreClient(ip_addr=IP_ADDR, port = PORT)
    lock_id, _, _ = ec_client.wait_for_lock()
    ec_client.release_lock(lock_id=lock_id)