Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Micro-simulator in C #37

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions crates/c/Justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
prefix := `realpath prefix`

export LD_LIBRARY_PATH := env("LD_LIBRARY_PATH", "") + ":" + prefix + "/lib/"
export LD_DYLD_PATH:= env("LD_DYLD_PATH", "") + ":" + prefix + "/lib/"

install:
cargo cinstall --prefix {{prefix}}

example-build name:
cd examples/ && make {{name}}

example name: (example-build name)
cd examples/ && ./{{name}}
24 changes: 22 additions & 2 deletions crates/c/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ but you can change this value with any other valid path).

Now move into the `examples/` folder in here.

To compile an example, than use:
To compile an example, just run:

```sh
# from inside `examples/`
PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$(realpath ../prefix/lib/pkgconfig) make my-example
make my-example
```

and then run the compiled example with the following:
Expand All @@ -43,6 +43,26 @@ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$(realpath ../prefix/lib/) ./my-example
LD_DYLD_PATH=$LD_DYLD_PATH:$(realpath ../prefix/lib/) ./my-example
```

#### `pkg-config`

To use a custom prefix, change your `PKG_CONFIG_PATH` variable, appending the your
prefix location:

```sh
# from inside `examples/`
PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$(realpath ../prefix/lib/pkgconfig) make my-example
```

and remove the line exporting this variable in the `Makefile`.

#### Justfile

For people making use of [`just`](https://just.systems/), a `Justfile` is provided. Examples could be run using:

```sh
just example my-example
```

## What's next

We are working to provide reliable internal dependency, and a suitable package for
Expand Down
5 changes: 4 additions & 1 deletion crates/c/examples/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
circuit
draw

libmicrosim.so
execute
15 changes: 15 additions & 0 deletions crates/c/examples/Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
PREFIX=$(shell realpath ../prefix/)
export PKG_CONFIG_PATH := $(PREFIX)/lib/pkgconfig

CFLAGS=-g -Wall -Wextra $(shell pkg-config --cflags qibo_core_c)
LDFLAGS=$(shell pkg-config --libs qibo_core_c)

SOURCES=$(wildcard *.c)
EXECUTABLES=$(patsubst %.c,%,$(SOURCES))


libmicrosim.so: microsimulator/microsim.c microsimulator/microsim.h
@cp ./microsimulator/microsim.h $(PREFIX)/include/
$(CC) $(CFLAGS) -I$(PREFIX)/include $(LDFLAGS) microsimulator/microsim.c -shared -o libmicrosim.so
cp libmicrosim.so $(PREFIX)/lib/

CMICROSIM=-I$(PREFIX)/include
LDMICROSIM=-L$(PREFIX)/lib -lmicrosim

execute: libmicrosim.so execute.c
$(CC) $(CFLAGS) $(CMICROSIM) $(LDFLAGS) $(LDMICROSIM) execute.c -o execute

all: $(EXECUTABLES)
15 changes: 0 additions & 15 deletions crates/c/examples/circuit.c

This file was deleted.

18 changes: 18 additions & 0 deletions crates/c/examples/draw.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include <stdio.h>
#include <complex.h>
#include "qibo_core_c.h"


int main(int, char *[])
{
qibo_core_circuit *c = qibo_core_circuit_new(5);
qibo_core_circuit_add(c, "H", (size_t[]) {0}, 1);
qibo_core_circuit_add(c, "X", (size_t[]) {2}, 1);
qibo_core_circuit_add(c, "H", (size_t[]) {2}, 1);
qibo_core_circuit_add(c, "X", (size_t[]) {3}, 1);
qibo_core_circuit_add(c, "CNOT", (size_t[]) {0, 3}, 2);

printf("%s\n", qibo_core_circuit_draw(c));

return 0;
}
37 changes: 37 additions & 0 deletions crates/c/examples/execute.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include <stdio.h>
#include <complex.h>
#include "qibo_core_c.h"
#include "microsim.h"


void print_state(complex double *state, const size_t size) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be moved in the qibo_core library (even the main Rust one), start making a state object (that could simply wrap an array of double).

//const size_t size = sizeof(state) / sizeof(state[0]);
for (size_t i=0; i < size; i++) {
printf("%ld: %.4f, %.4f\n", i, creal(state[i]), cimag(state[i]));
}
}


int main(int, char *[])
{
qibo_core_circuit *c = qibo_core_circuit_new(5);
qibo_core_circuit_add(c, "H", (size_t[]) {0}, 1);
qibo_core_circuit_add(c, "X", (size_t[]) {2}, 1);
qibo_core_circuit_add(c, "H", (size_t[]) {2}, 1);
qibo_core_circuit_add(c, "X", (size_t[]) {3}, 1);
qibo_core_circuit_add(c, "CNOT", (size_t[]) {0, 3}, 2);

printf("%s\n\n", qibo_core_circuit_draw(c));

// Initialize 5-qubit state vector
complex double state[32] = { 0 };
state[0] = 1;

// Execute circuit
execute_circuit(c, state);

// Print state vector
print_state(state, 32);

return 0;
}
112 changes: 112 additions & 0 deletions crates/c/examples/microsimulator/microsim.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#include <stdio.h>
#include <complex.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include "qibo_core_c.h"
#include "microsim.h"

// normalization factor
double const H = 1.0 / sqrt(2);
// matrices implementing gates
struct {
complex double h[4];
complex double x[4];
complex double y[4];
complex double z[4];
} const MATRICES = {
{H, H, H, -H},
{0, 1, 1, 0},
{0, -I, I, 0},
{1, 0, 0, -1}
};

// get matrix corresponding to the gate
complex double const* matrix(const char* gate) {
if (strcmp(gate, "H") == 0) {
return MATRICES.h;
}
if (strcmp(gate, "Y") == 0) {
return MATRICES.y;
}
if (strcmp(gate, "Z") == 0) {
return MATRICES.z;
}
return MATRICES.x;
}

// integer comparison, compatible with qsort
int compare(const void* a, const void* b) {
return (*(int*)a - *(int*)b);
}

// TODO: description missing
size_t control_index(size_t const g, size_t const* qubits, size_t const nqubits) {
size_t i = g;
for (size_t j = 0; j < nqubits; j++) {
size_t const n = qubits[j];
size_t const k = 1 << n;
i = ((i >> n) << (n + 1)) + (i & (k - 1)) + k;
}
return i;
}

// TODO: description missing
void apply_controlled_gate(
complex double* state,
complex double const* gate,
size_t const* qubits,
size_t const ncontrols,
size_t const nqubits
) {
size_t target = qubits[0];

size_t sorted_qubits[ncontrols + 1];
memcpy(sorted_qubits, qubits, (ncontrols + 1) * sizeof(size_t));
for (size_t i = 0; i < ncontrols + 1; i++) {
sorted_qubits[i] = nqubits - sorted_qubits[i] - 1;
}
qsort(sorted_qubits, ncontrols + 1, sizeof(size_t), compare);

size_t const nstates = 1 << (nqubits - ncontrols - 1);
size_t const tk = 1 << (nqubits - target - 1);
// TODO: This can be parallelized for large number of qubits
for (size_t g = 0; g < nstates; g++) {
size_t const i2 = control_index(g, sorted_qubits, ncontrols + 1);
size_t const i1 = i2 - tk;
complex double const state1 = state[i1];
complex double const state2 = state[i2];
state[i1] = gate[0] * state1 + gate[1] * state2;
state[i2] = gate[2] * state1 + gate[3] * state2;
}
}

// calculate number of control qubits
// maybe can be improved if we expose gate n_elements to C API
// but this will change anyway when we implement ``controlled_by``
size_t n_controls(const char* gate) {
if (strcmp(gate, "CNOT") == 0) {
return 1;
}
return 0;
}

// execute a qibo-core circuit, mutating the state in place
void execute_circuit(qibo_core_circuit* circuit, complex double* state) {
size_t const n_elements = qibo_core_circuit_n_elements(circuit);
size_t const n_gates = qibo_core_circuit_n_gates(circuit);

for (size_t gid = 0; gid < n_gates; gid++) {
char const* gate = qibo_core_circuit_gate(circuit, gid);

size_t const* elements;
size_t n_gate_elements;
qibo_core_circuit_elements(circuit, gid, &elements, &n_gate_elements);

complex double const* matrix_ = matrix(gate);
size_t n_controls_ = n_controls(gate);
apply_controlled_gate(state, matrix_, elements, n_controls_, n_elements);

qibo_core_circuit_free_elements(elements, n_gate_elements);
}
}
8 changes: 8 additions & 0 deletions crates/c/examples/microsimulator/microsim.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#ifndef MICROSIM_H
#define MICROSIM_H

#include "qibo_core_c.h"

void execute_circuit(qibo_core_circuit *circuit, complex double *state);

#endif
42 changes: 42 additions & 0 deletions crates/c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::slice;

use qibo_core::prelude::*;


#[no_mangle]
pub extern "C" fn qibo_core_circuit_new(n_elements: usize) -> Box<Circuit> {
Box::new(Circuit::new(n_elements))
Expand Down Expand Up @@ -36,6 +37,47 @@ pub extern "C" fn qibo_core_circuit_n_elements(circuit: &Circuit) -> usize {
circuit.n_elements()
}

#[no_mangle]
pub extern "C" fn qibo_core_circuit_n_gates(circuit: &Circuit) -> usize {
circuit.n_gates()
}

#[no_mangle]
pub extern "C" fn qibo_core_circuit_gate(circuit: &Circuit, gid: usize) -> *const c_char {
let gate = circuit.gate(gid);
let name = match gate {
Gate::One(One::H) => "H",
Gate::One(One::X) => "X",
Gate::One(One::Y) => "Y",
Gate::One(One::Z) => "Z",
Gate::Two(Two::CNOT) => "CNOT",
_ => todo!()
};

CString::new(name).unwrap().into_raw()
}

#[no_mangle]
pub extern "C" fn qibo_core_circuit_elements(circuit: &Circuit, gid: usize, ptr: *mut *const usize, len: *mut usize) {
let elements = circuit.elements(gid);
// Complaints about what follows are to be directed to ChatGPT
let boxed_slice = elements.clone().into_boxed_slice();
unsafe {
*ptr = boxed_slice.as_ptr();
*len = elements.len();
}
std::mem::forget(boxed_slice);
}
Comment on lines +60 to +70
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, you're avoiding returning in this function, resorting to a more complex solution (with mutable inputs, used to effectively return).

I will fix it, since this should be a very simple function.

Copy link
Member Author

@stavros11 stavros11 Jun 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, did not want to avoid returning and I would prefer something simpler. I was just not sure how to return a Rust vector to C, so I used chatGPT (as written in the comment), probably not with the best prompt, to get something working. Same for your next comment (about _free_elements), it came together with this.

If you are planning to fix, great! Otherwise I can also have another look. Note, that C does not need to manipulate the vector of elements, just read the values. I am not sure if that simplifies it in any way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm planning to improve over this.

I'd like to return a structured object (i.e. essentially a smart pointer, like std::vector), possibly defined within the lib.rs itself.
But I'm not sure if I will be able to access its fields from C (I should try).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, and most likely, we should focus on the C++ API rather than the pure C, encapsulating objects in std::array, std::vector and smart pointers. I don't believe we will see interest from collaborators to have a pure C backend soon.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I perfectly agree.

That's why I was also interested in direct usage of free() (cf. #37 (comment)), to limit the overhead in the C API definition, and postpone the proper memory handling to the C++ library.

I will try to have another go at packaging it properly, with a more advanced build tool, since now we'll need the C++ applications (including examples) to depend on the C++ library, that in turn will depend on the C library (whose dependency on qibo-core main library is handled by Cargo).


#[no_mangle]
pub extern "C" fn qibo_core_circuit_free_elements(ptr: *const usize, len: usize) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a possible way for freeing the elements.

However, once you passed the pointer down to C, you should be able to call free(that_pointer).
Notice that the call does not contain the number of elements (the size). I don't know how the internals of the allocator, so I can just make assumptions about how it works, but I believe it keeps track of the whole block of memory requested just by the address it returned during allocation. I should check this later on.

However, the proposal would be to directly use free() in C, without reimplementing a function to free every pointer, if the returned object is just a dynamic array of non-referencing elements.
For those objects holding references, we should resort to the implementation of a dedicated destructor. This class will mainly consist of objects owning vectors or hash-maps as attributes.

Copy link
Member

@alecandido alecandido Jun 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the time being, the proposal is to consciously leak memory, and fix this consistently at a later stage.

Cf. #38 #39

if !ptr.is_null() {
unsafe {
let _ = Vec::from_raw_parts(ptr as *mut usize, len, len);
}
}
}
alecandido marked this conversation as resolved.
Show resolved Hide resolved

#[no_mangle]
pub extern "C" fn qibo_core_circuit_draw(circuit: &Circuit) -> *mut c_char {
let repr = circuit.draw();
Expand Down