TAPLite 0.1.0
An open-source, cross-platform, lightweight, and fast Python                path engine for networks encoded in GMNS
License: Apache License 2.0

### **Refactoring `TAPLite.h`**

1. **Encapsulate in a Namespace:**
   - Wrap all function declarations and global variables in a namespace (e.g., `TAPLite`) to avoid name conflicts.

2. **Move Global Variables into a Class:**
   - Use a class, `TAPLiteManager`, to encapsulate state variables and methods related to traffic assignment.

3. **Provide Clear Access Modifiers:**
   - Split member variables and helper functions into `private` and `public` sections.

4. **Add Comments:**
   - Provide clear, structured comments for each function and variable.

---

### **Modified `TAPLite.h`**
```cpp
#pragma once

#ifndef TAPLITE_H
#define TAPLITE_H

#include <string>
#include <vector>
#include <cstdio> // For FILE*
#include <map>
#include <memory>

namespace TAPLite {

// Constants
const int BUFFERSIZE = 1000;
const int MAX_NO_BISECT_ITERATION = 5;

// Manager class for TAPLite
class TAPLiteManager {
public:
    // Constructor and Destructor
    TAPLiteManager();
    ~TAPLiteManager();

    // Initialization methods
    void Initialize(int number_of_modes, int number_of_zones);
    void ReadSettingsFile(const std::string& filepath);
    void ReadModeTypeFile(const std::string& filepath);

    // Data loading methods
    int GetNumberOfNodesFromFile(const std::string& nodeFile, int& number_of_zones, int& first_thru_node);
    int GetNumberOfLinksFromFile(const std::string& linkFile);

    // Assignment methods
    void FindMinCostRoutes();
    void AllOrNothingAssign(int iteration_no);
    void UpdateLinkCosts();
    void VolumeDifference();
    void LinksSDLineSearch();
    void UpdateVolume();

    // Logging methods
    void LogSummary(const std::string& message);
    void LogIteration(int iteration_no, double system_tt, double least_tt, double gap);

private:
    // Member variables
    int number_of_modes_;
    int number_of_zones_;
    int total_iterations_;
    FILE* summary_log_file_;
    // Add other relevant private variables for TAPLite

    // Helper methods
    void ExitMessage(const std::string& message);
};

} // namespace TAPLite

#endif // TAPLITE_H
```

---

### **Refactoring `TAPLite.cpp`**

1. **Organize into Methods:**
   - Move global functions into the `TAPLiteManager` class.

2. **Remove Redundancy:**
   - Use member variables instead of passing global state between functions.

3. **Improve Error Handling:**
   - Use `std::runtime_error` for throwing exceptions and logging.

4. **Add Inline Comments:**
   - Clarify the purpose of key logic.

---

### **Modified `TAPLite.cpp`**
```cpp
#include "TAPLite.h"
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <sstream>
#include <cmath>

namespace TAPLite {

// Constructor
TAPLiteManager::TAPLiteManager() : number_of_modes_(0), number_of_zones_(0), total_iterations_(0), summary_log_file_(nullptr) {}

// Destructor
TAPLiteManager::~TAPLiteManager() {
    if (summary_log_file_) {
        fclose(summary_log_file_);
    }
}

// Initialization
void TAPLiteManager::Initialize(int number_of_modes, int number_of_zones) {
    number_of_modes_ = number_of_modes;
    number_of_zones_ = number_of_zones;
    std::cout << "Initialized with " << number_of_modes_ << " modes and " << number_of_zones_ << " zones." << std::endl;
}

void TAPLiteManager::ReadSettingsFile(const std::string& filepath) {
    std::ifstream file(filepath);
    if (!file) {
        throw std::runtime_error("Failed to open settings file: " + filepath);
    }
    // Parse settings...
    file.close();
}

void TAPLiteManager::ReadModeTypeFile(const std::string& filepath) {
    std::ifstream file(filepath);
    if (!file) {
        throw std::runtime_error("Failed to open mode type file: " + filepath);
    }
    // Parse mode types...
    file.close();
}

// Data loading
int TAPLiteManager::GetNumberOfNodesFromFile(const std::string& nodeFile, int& number_of_zones, int& first_thru_node) {
    std::ifstream file(nodeFile);
    if (!file) {
        throw std::runtime_error("Failed to open node file: " + nodeFile);
    }
    // Parse nodes...
    file.close();
    return 100; // Example return value
}

int TAPLiteManager::GetNumberOfLinksFromFile(const std::string& linkFile) {
    std::ifstream file(linkFile);
    if (!file) {
        throw std::runtime_error("Failed to open link file: " + linkFile);
    }
    // Parse links...
    file.close();
    return 200; // Example return value
}

// Assignment methods
void TAPLiteManager::FindMinCostRoutes() {
    std::cout << "Finding minimum cost routes..." << std::endl;
    // Implement logic
}

void TAPLiteManager::AllOrNothingAssign(int iteration_no) {
    std::cout << "Performing all-or-nothing assignment for iteration " << iteration_no << std::endl;
    // Implement logic
}

void TAPLiteManager::UpdateLinkCosts() {
    std::cout << "Updating link costs..." << std::endl;
    // Implement logic
}

void TAPLiteManager::VolumeDifference() {
    std::cout << "Calculating volume differences..." << std::endl;
    // Implement logic
}

void TAPLiteManager::LinksSDLineSearch() {
    std::cout << "Performing SD line search..." << std::endl;
    // Implement logic
}

void TAPLiteManager::UpdateVolume() {
    std::cout << "Updating volume values..." << std::endl;
    // Implement logic
}

// Logging methods
void TAPLiteManager::LogSummary(const std::string& message) {
    if (summary_log_file_) {
        fprintf(summary_log_file_, "%s\n", message.c_str());
    }
}

void TAPLiteManager::LogIteration(int iteration_no, double system_tt, double least_tt, double gap) {
    if (summary_log_file_) {
        fprintf(summary_log_file_,
                "Iteration %d: System TT = %.2f, Least TT = %.2f, Gap = %.2f%%\n",
                iteration_no, system_tt, least_tt, gap);
    }
}

// Helper
void TAPLiteManager::ExitMessage(const std::string& message) {
    throw std::runtime_error(message);
}

} // namespace TAPLite
```

---

### **Key Points**
- **Encapsulation:** All logic is encapsulated in the `TAPLiteManager` class.
- **Error Handling:** Exceptions (`std::runtime_error`) are used for error reporting.
- **Namespaces:** All functions and classes are wrapped in the `TAPLite` namespace.
- **Logging:** `LogSummary` and `LogIteration` handle logging cleanly.
- **Future Expansion:** You can now easily extend the `TAPLiteManager` class with new methods.


## **Step-by-Step Instructions**

### **1. Reorganize Your C++ Codebase**

#### **a. Create a Proper Directory Structure**
Organize your files into meaningful directories:
```
TAPLite/
â”œâ”€â”€ CMakeLists.txt           # CMake configuration file
â”œâ”€â”€ bindings.cpp             # Pybind11 bindings
â”œâ”€â”€ core/                    # Core logic files
â”‚   â”œâ”€â”€ TAPLite.h            # Header file for TAPLite
â”‚   â””â”€â”€ TAPLite.cpp          # Implementation file for TAPLite
â””â”€â”€ tests/                   # Test files
    â””â”€â”€ test_main.cpp        # Test runner (optional)
```

#### **b. Update Header Files**
Make sure your header files only contain declarations (no implementations). For example, `TAPLite.h` should look like this:
```cpp
#pragma once

#ifndef TAPLITE_H
#define TAPLITE_H

#include <cstdio>
#include <vector>
#include <string>

// Constants
#define BUFFERSIZE 1000
#define MAX_NO_BISECT_ITERATION 100

// Function declarations
void read_settings_file();
void read_mode_type_file();
int get_number_of_nodes_from_node_file(int& number_of_zones, int& l_FirstThruNode);
int get_number_of_links_from_link_file();
void Init(int number_of_modes, int no_zones);
void InitializeLinkIndices(int number_of_modes, int no_zones, int TotalAssignIterations);
void FindMinCostRoutes();
void All_or_Nothing_Assign();
void UpdateLinkCost();
void VolumeDifference();
void LinksSDLineSearch();
void UpdateVolume();
extern FILE* summary_log_file;

#endif
```

#### **c. Keep Implementation in `.cpp` Files**
Ensure all implementations are placed in `TAPLite.cpp`. Here's an example:
```cpp
#include "TAPLite.h"
#include <iostream>

void read_settings_file() {
    std::cout << "Reading settings file..." << std::endl;
}

void Init(int number_of_modes, int no_zones) {
    std::cout << "Initializing with " << number_of_modes << " modes and " << no_zones << " zones." << std::endl;
}

// Implement other functions similarly...
```

#### **d. Add Comments and Logging**
- Add meaningful comments to your code for better readability.
- Use a logging mechanism (e.g., `std::cerr` or `std::ofstream`) for debug output instead of `printf`.

---

### **2. Write Pybind11 Bindings**

Create a `bindings.cpp` file to expose the core functionalities of TAPLite to Python. Here's an example:
```cpp
#include <pybind11/pybind11.h>
#include "core/TAPLite.h"

namespace py = pybind11;

PYBIND11_MODULE(TAPLite, m) {
    m.doc() = "Python bindings for TAPLite traffic assignment model";

    m.def("read_settings_file", &read_settings_file, "Reads the settings file for TAPLite");
    m.def("initialize", &Init, "Initializes TAPLite", py::arg("number_of_modes"), py::arg("no_zones"));
    m.def("get_number_of_nodes", &get_number_of_nodes_from_node_file, "Gets the number of nodes");
    m.def("get_number_of_links", &get_number_of_links_from_link_file, "Gets the number of links");
    // Add bindings for other functions...
}
```

---

### **3. Write a Clean CMakeLists.txt**

Use CMake to manage your build process. Here's a complete `CMakeLists.txt` file:
```cmake
cmake_minimum_required(VERSION 3.14)
project(TAPLite VERSION 0.1.0 LANGUAGES CXX)

# Set C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Include directories
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/core)

# Python and pybind11
find_package(Python3 REQUIRED COMPONENTS Interpreter Development)
find_package(pybind11 REQUIRED)

# OpenMP support (optional)
find_package(OpenMP)
if(OpenMP_FOUND)
    message(STATUS "OpenMP found, enabling OpenMP support")
    add_definitions(${OpenMP_CXX_FLAGS})
endif()

# Source files
set(SOURCES
    bindings.cpp
    core/TAPLite.cpp
)

# Define Python module
pybind11_add_module(TAPLite ${SOURCES})

# Link OpenMP if found
if(OpenMP_FOUND)
    target_link_libraries(TAPLite PRIVATE OpenMP::OpenMP_CXX)
endif()
```

---

### **4. Build and Install Locally**

#### **a. Create a Build Directory**
Run the following commands in your project root:
```bash
mkdir build
cd build
```

#### **b. Configure the Project**
Run CMake to generate build files:
```bash
cmake -G "Visual Studio 17 2022" -A x64 ..
```

#### **c. Build the Project**
Build the Python extension module:
```bash
cmake --build . --config Release
```

#### **d. Install Locally**
To install the module in your local Python environment:
```bash
cmake --install . --prefix $(python -m site --user-site)
```

---

### **5. Test the Python Module**

#### **a. Test Installation**
Start a Python interpreter and import your module:
```python
import TAPLite
TAPLite.read_settings_file()
```

#### **b. Add Unit Tests**
Write unit tests in Python to verify functionality. Example:
```python
import TAPLite

def test_initialization():
    TAPLite.initialize(3, 10)
    print("Initialization successful!")

test_initialization()
```

---

### **6. Debug Common Issues**

#### **a. Argument Mismatch in Bindings**
Ensure the function signatures in `bindings.cpp` match the declarations in `TAPLite.h`.

#### **b. Pybind11 Errors**
Check that `pybind11` is correctly installed and available in your Python environment:
```bash
pip install pybind11
```

#### **c. Compiler Warnings**
- Suppress deprecation warnings with `/D_CRT_SECURE_NO_WARNINGS` for MSVC.
- Ensure all format specifiers in `printf`/`fprintf` match the data types.

---

### **7. Enhance and Maintain**

- **Documentation**: Document all functions, arguments, and expected behavior.
- **Testing**: Add comprehensive tests for edge cases and failure scenarios.
- **Code Quality**: Use a linter or formatter for consistent coding style.