UP | HOME

Context

Table of Contents

1 Context handling

The context variable is a handle for the state of the library, and is stored in a data structure which can't be seen outside of the library. To simplify compatibility with other languages, the pointer to the internal data structure is converted into a 64-bit signed integer, defined in the qmckl_context type. A value of QMCKL_NULL_CONTEXT for the context is equivalent to a NULL pointer.

typedef int64_t qmckl_context ;
#define QMCKL_NULL_CONTEXT (qmckl_context) 0

An immutable context would have required to implement a garbage collector. To keep the library simple, we have chosen to implement the context as a mutable data structure, so it has to be handled with care.

By convention, in this file context is a qmckl_context variable and ctx is a qmckl_context_struct* pointer.

1.1 Data structure

The context keeps a ``date'' that allows to check which data needs to be recomputed. The date is incremented when the electron coordinates are updated.

When a new element is added to the context, the functions qmcklcontextcreate, qmcklcontextdestroy and qmcklcontextcopy should be updated inorder to make deep copies.

A tag is used internally to check if the memory domain pointed by a pointer is a valid context. This allows to check that even if the pointer associated with a context is non-null, we can still verify that it points to the expected data structure.

#define VALID_TAG   0xBEEFFACE
#define INVALID_TAG 0xDEADBEEF

The qmckl_context_check function checks if the domain pointed by the pointer is a valid context. It returns the input qmckl_context if the context is valid, QMCKL_NULL_CONTEXT otherwise.

qmckl_context qmckl_context_check(const qmckl_context context) ;
qmckl_context qmckl_context_check(const qmckl_context context) {

  if (context == QMCKL_NULL_CONTEXT)
    return QMCKL_NULL_CONTEXT;

  const qmckl_context_struct* const ctx = (const qmckl_context_struct* const) context;

  /* Try to access memory */
  if (ctx->tag != VALID_TAG) {
      return QMCKL_NULL_CONTEXT;
  }

  return context;
}

1.2 Creation

To create a new context, qmckl_context_create() should be used.

  • Upon success, it returns a pointer to a new context with the qmckl_context type
  • It returns QMCKL_NULL_CONTEXT upon failure to allocate the internal data structure
  • A new context always has all its members initialized with a NULL value
qmckl_context qmckl_context_create() {

  qmckl_context_struct* const ctx =
    (qmckl_context_struct* const) malloc (sizeof(qmckl_context_struct));

  if (ctx == NULL) {
    return QMCKL_NULL_CONTEXT;
  }

  /* Set all pointers and values to NULL */
  {
    memset(ctx, 0, sizeof(qmckl_context_struct));
  }

  /* Initialize lock */
  {
    pthread_mutexattr_t attr;
    int rc;

    rc = pthread_mutexattr_init(&attr);
    assert (rc == 0);

    (void) pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

    rc = pthread_mutex_init ( &(ctx->mutex), &attr);
    assert (rc == 0);

    (void) pthread_mutexattr_destroy(&attr);
  }

  /* Initialize data */
  {
    ctx->tag = VALID_TAG;

    const qmckl_context context = (const qmckl_context) ctx;
    assert ( qmckl_context_check(context) != QMCKL_NULL_CONTEXT );

    qmckl_exit_code rc;

    ctx->numprec.precision = QMCKL_DEFAULT_PRECISION;
    ctx->numprec.range = QMCKL_DEFAULT_RANGE;

    rc = qmckl_init_electron(context);
    assert (rc == QMCKL_SUCCESS);

    rc = qmckl_init_nucleus(context);
    assert (rc == QMCKL_SUCCESS);

    rc = qmckl_init_ao_basis(context);
    assert (rc == QMCKL_SUCCESS);
  }

  /* Allocate qmckl_memory_struct */
  {
    const size_t size = 128L;
    qmckl_memory_info_struct * new_array = calloc(size, sizeof(qmckl_memory_info_struct));
    if (new_array == NULL) {
      free(ctx);
      return QMCKL_NULL_CONTEXT;
    }
    memset( &(new_array[0]), 0, size * sizeof(qmckl_memory_info_struct) );

    ctx->memory.element = new_array;
    ctx->memory.array_size = size;
    ctx->memory.n_allocated = (size_t) 0;
  }

  return (qmckl_context) ctx;
}

1.3 Locking

For thread safety, the context may be locked/unlocked. The lock is initialized with the PTHREAD_MUTEX_RECURSIVE attribute, and the number of times the thread has locked it is saved in the lock_count attribute.

void qmckl_lock(qmckl_context context) {
  if (context == QMCKL_NULL_CONTEXT)
    return ;
  qmckl_context_struct* const ctx = (qmckl_context_struct* const) context;
  errno = 0;
  int rc = pthread_mutex_lock( &(ctx->mutex) );
  if (rc != 0) {
    fprintf(stderr, "DEBUG qmckl_lock:%s\n", strerror(rc) );
    fflush(stderr);
  }
  assert (rc == 0);
  ctx->lock_count += 1;
/*
  printf("  lock : %d\n", ctx->lock_count);
*/
}

void qmckl_unlock(const qmckl_context context) {
  qmckl_context_struct* const ctx = (qmckl_context_struct* const) context;
  int rc = pthread_mutex_unlock( &(ctx->mutex) );
  if (rc != 0) {
    fprintf(stderr, "DEBUG qmckl_unlock:%s\n", strerror(rc) );
    fflush(stderr);
  }
  assert (rc == 0);
  ctx->lock_count -= 1;
/*
  printf("unlock : %d\n", ctx->lock_count);
*/
}

1.4 TODO Copy

qmckl_context_copy makes a deep copy of a context. It returns QMCKL_NULL_CONTEXT upon failure.

qmckl_context qmckl_context_copy(const qmckl_context context) {

  const qmckl_context checked_context = qmckl_context_check(context);

  if (checked_context == QMCKL_NULL_CONTEXT) {
    return QMCKL_NULL_CONTEXT;
  }

  /*
  qmckl_lock(context);
  {

    const qmckl_context_struct* const old_ctx =
      (qmckl_context_struct* const) checked_context;

    qmckl_context_struct* const new_ctx =
      (qmckl_context_struct* const) malloc (context, sizeof(qmckl_context_struct));

    if (new_ctx == NULL) {
      qmckl_unlock(context);
      return QMCKL_NULL_CONTEXT;
    }

     * Copy the old context on the new one *
     * TODO Deep copies should be done here *
    memcpy(new_ctx, old_ctx, sizeof(qmckl_context_struct));

    qmckl_unlock( (qmckl_context) new_ctx );

    return (qmckl_context) new_ctx;
  }
  qmckl_unlock(context);
*/
    return QMCKL_NULL_CONTEXT;
}

1.5 Destroy

The context is destroyed with qmckl_context_destroy, leaving the ancestors untouched. It frees the context, and returns the previous context.

qmckl_exit_code qmckl_context_destroy(const qmckl_context context) {

  const qmckl_context checked_context = qmckl_context_check(context);
  if (checked_context == QMCKL_NULL_CONTEXT) return QMCKL_INVALID_CONTEXT;

  qmckl_context_struct* const ctx = (qmckl_context_struct* const) context;
  assert (ctx != NULL);  /* Shouldn't be possible because the context is valid */

  qmckl_lock(context);
  {
    /* Memory: Remove all allocated data */
    for (size_t pos = (size_t) 0 ; pos < ctx->memory.array_size ; ++pos) {
      if (ctx->memory.element[pos].pointer != NULL) {
        free(ctx->memory.element[pos].pointer);
        memset( &(ctx->memory.element[pos]), 0, sizeof(qmckl_memory_info_struct) );
        ctx->memory.n_allocated -= 1;
      }
    }
    assert (ctx->memory.n_allocated == (size_t) 0);
    free(ctx->memory.element);
    ctx->memory.element = NULL;
    ctx->memory.array_size = (size_t) 0;
  }
  qmckl_unlock(context);

  ctx->tag = INVALID_TAG;

  const int rc_destroy = pthread_mutex_destroy( &(ctx->mutex) );
  if (rc_destroy != 0) {
/* DEBUG */
     fprintf(stderr, "qmckl_context_destroy: %s (count = %d)\n", strerror(rc_destroy), ctx->lock_count);
     abort();
  }

  free(ctx);

  return QMCKL_SUCCESS;
}

Author: TREX CoE

Created: 2021-09-27 Mon 09:32

Validate