Skip to content
Open
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
**/build
**/__pycache__/
**/*.pyc
69 changes: 69 additions & 0 deletions prototypes/fortran-cpp-python/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
cmake_minimum_required(VERSION 3.15)
project(F90_CPP_Python_Prototype LANGUAGES C CXX Fortran)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Find Python3
find_package(Python3 REQUIRED COMPONENTS Interpreter Development)

message(STATUS "Python3 executable: ${Python3_EXECUTABLE}")
message(STATUS "Python3 include dirs: ${Python3_INCLUDE_DIRS}")
message(STATUS "Python3 libraries: ${Python3_LIBRARIES}")

# Find pybind11
# First try to find pybind11 using CMake config
find_package(pybind11 CONFIG QUIET)

if(NOT pybind11_FOUND)
# If not found, try using Python to locate pybind11
message(STATUS "pybind11 not found via CMake, trying Python...")
execute_process(
COMMAND ${Python3_EXECUTABLE} -c "import pybind11; print(pybind11.get_cmake_dir())"
OUTPUT_VARIABLE PYBIND11_CMAKE_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE PYBIND11_PYTHON_RESULT
)

if(PYBIND11_PYTHON_RESULT EQUAL 0)
message(STATUS "Found pybind11 via Python at: ${PYBIND11_CMAKE_DIR}")
set(pybind11_DIR ${PYBIND11_CMAKE_DIR})
find_package(pybind11 CONFIG REQUIRED)
else()
message(FATAL_ERROR "pybind11 not found. Please install it: pip install pybind11")
endif()
endif()

message(STATUS "pybind11 version: ${pybind11_VERSION}")
message(STATUS "pybind11 includes: ${pybind11_INCLUDE_DIRS}")

# Add include directories
include_directories(${CMAKE_SOURCE_DIR}/src/cpp)

# ============================================
# C++ Library (Python bridge)
# ============================================
add_library(python_bridge STATIC
src/cpp/python_bridge.cpp
src/cpp/python_bridge.hpp
)

target_include_directories(python_bridge PUBLIC
${CMAKE_SOURCE_DIR}/src/cpp
)

target_link_libraries(python_bridge
pybind11::embed
)

# ============================================
# Fortran Main Executable
# ============================================
add_executable(fortran_main
src/fortran/fortran_main.f90
)

target_link_libraries(fortran_main
python_bridge
pybind11::embed
)
73 changes: 73 additions & 0 deletions prototypes/fortran-cpp-python/src/cpp/python_bridge.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include "python_bridge.hpp"
#include <pybind11/pybind11.h>
#include <pybind11/embed.h>
#include <pybind11/numpy.h>
#include <iostream>

namespace py = pybind11;

// Single global: the Python interpreter
static std::unique_ptr<py::scoped_interpreter> interpreter;

extern "C" {

int initialize_python() {
std::cout << "[C++] Initializing Python interpreter..." << std::endl;

try {
interpreter = std::make_unique<py::scoped_interpreter>();

py::module_ sys = py::module_::import("sys");

std::string source_file = __FILE__;
auto this_dir = source_file.substr(0, source_file.find_last_of("/\\"));

std::cout << "[C++] Adding to sys.path: " << this_dir + "/../python" << std::endl;
sys.attr("path").attr("insert")(0, this_dir + "/../python");

py::module_::import("python_main").attr("initialize")();

std::cout << "[C++] Python initialized" << std::endl;
return 0;

} catch (const std::exception& e) {
std::cerr << "[C++] Error: " << e.what() << std::endl;
return -1;
}
}

int call_main_python(const float* data, int size, float* result) {
std::cout << "[C++] Calling call_main_python..." << std::endl;

Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The 'data' pointer is not validated for null before being passed to py::array_t constructor. Add a null check to prevent potential crashes or undefined behavior.

Suggested change
if (data == nullptr) {
std::cerr << "[C++] Error: data pointer is null." << std::endl;
return -1;
}

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The 'result' pointer is not validated for null before dereferencing at line 56. Add a null check to prevent potential crashes.

Suggested change
if (result == nullptr) {
std::cerr << "[C++] Error: result pointer is null." << std::endl;
return -2;
}

Copilot uses AI. Check for mistakes.
try {
py::module_ main = py::module_::import("python_main");

// Create pybind11 array_t from C array (buffer protocol compatible)
// This will be accessible as numpy array, torch tensor, etc. in Python
py::array_t<float> array(
{size}, // shape
{sizeof(float)}, // strides
data, // data pointer
py::cast(nullptr) // owner (nullptr = not owned by Python)
);
Comment on lines +47 to +52
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

No validation is performed on the 'size' parameter before creating the array. If a negative or excessively large size is passed, it could lead to undefined behavior or security issues. Add validation to ensure size is positive and within reasonable bounds.

Copilot uses AI. Check for mistakes.

// Call the function - python_main will dispatch to implementation
py::object py_result = main.attr("call_main_python")(array, size);
*result = py_result.cast<float>();

std::cout << "[C++] Result: " << *result << std::endl;
return 0;

} catch (const std::exception& e) {
std::cerr << "[C++] Error: " << e.what() << std::endl;
return -1;
}
}

void finalize_python() {
std::cout << "[C++] Finalizing Python..." << std::endl;
interpreter.reset();
std::cout << "[C++] Done" << std::endl;
}

} // extern "C"
14 changes: 14 additions & 0 deletions prototypes/fortran-cpp-python/src/cpp/python_bridge.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifndef PYTHON_BRIDGE_HPP
#define PYTHON_BRIDGE_HPP

extern "C" {

int initialize_python();

int call_main_python(const float* data, int size, float* result);

void finalize_python();

}

#endif // PYTHON_BRIDGE_HPP
76 changes: 76 additions & 0 deletions prototypes/fortran-cpp-python/src/fortran/fortran_main.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
program fortran_main
use iso_c_binding
implicit none

! Interface to C++ functions
interface
function initialize_python() bind(C, name="initialize_python")
use iso_c_binding
integer(c_int) :: initialize_python
end function initialize_python

function call_main_python(input_data, size, output_data) bind(C, name="call_main_python")
use iso_c_binding
real(c_float), dimension(*), intent(in) :: input_data
integer(c_int), value, intent(in) :: size
real(c_float), intent(out) :: output_data
integer(c_int) :: call_main_python
end function call_main_python

subroutine finalize_python() bind(C, name="finalize_python")
use iso_c_binding
end subroutine finalize_python
end interface

! Local variables
integer(c_int), parameter :: array_size = 5
integer(c_int) :: status
real(c_float), dimension(array_size) :: input_data
real(c_float) :: output_data
integer(c_int) :: i

print *, "========================================"
print *, "Fortran Main - Entry Point"
print *, "========================================"

! Initialize input data
do i = 1, array_size
input_data(i) = real(i, kind=c_float)
end do

! Initialize Python
print *, ""
print *, "[Fortran] Initializing..."
status = initialize_python()
if (status /= 0) then
print *, "[Fortran] ERROR: Initialization failed"
stop 1
end if
print *, "[Fortran] Initialization successful"

! Test call python operation
print *, ""
print *, "--- Testing Call Python Operation ---"
print *, "[Fortran] Sending data:"
do i = 1, array_size
print *, " data(", i, ") =", input_data(i)
end do

status = call_main_python(input_data, array_size, output_data)
if (status /= 0) then
print *, "[Fortran] ERROR: Operation failed"
else
print *, "[Fortran] Result:", output_data
end if

! Cleanup
print *, ""
print *, "--- Cleanup ---"
call finalize_python()

print *, ""
print *, "========================================"
print *, "Fortran Main - Complete"
print *, "========================================"

end program fortran_main
26 changes: 26 additions & 0 deletions prototypes/fortran-cpp-python/src/python/python_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
Python main is a lightweight entry point
"""


def initialize():
"""
Nothing to do here ...
"""
print("[Python] Dispatcher initialized")
return {"status": "ready"}


def call_main_python(data, size):
"""
Dispatch to some torch calculation
"""
print(f"[Python] Dispatching tensor operation (size={size})")

# we are importing here so we don't need to make the
# cpp bridge aware of this stuff and such
Comment on lines +20 to +21
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

[nitpick] Comment text should be capitalized and properly formatted. 'we' should be 'We' and 'cpp' should be 'C++'.

Suggested change
# we are importing here so we don't need to make the
# cpp bridge aware of this stuff and such
# We are importing here so we don't need to make the
# C++ bridge aware of this stuff and such

Copilot uses AI. Check for mistakes.
from python_torch import compute_tensor_operation as impl

result = impl(data, size)
print(f"[Python] Tensor operation complete: {result}")
return result
27 changes: 27 additions & 0 deletions prototypes/fortran-cpp-python/src/python/python_torch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
Some computations in pytorch
"""

import torch
import numpy as np

# Print torch info on module import
print(f"[Python] Module loaded - torch version: {torch.__version__}")
print(f"[Python] CUDA available: {torch.cuda.is_available()}")


def compute_tensor_operation(data, size):
"""
Perform a PyTorch tensor operation
"""
print(f"[Python] Computing tensor operation on {size} elements")

tensor = torch.from_numpy(np.array(data, dtype=np.float32))

print(f"[Python] Input tensor: {tensor}")

result_value = torch.sum(tensor).item()

print(f"[Python] Result: {result_value}")

return result_value
Loading