Skip to content
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
133 changes: 133 additions & 0 deletions IMPLEMENTATION_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Wildcard Type System Implementation - Summary

## Problem Statement
The original system only supported float type for wildcard arrays. The requirement was to enable support for additional types (uint8_t and notStupidBool_t) by allocating char arrays and casting before use.

## Solution Overview
Implemented a comprehensive typed wildcard system with the following approach:

### 1. Type System Design
- Added `WILDCARD_TYPE` enum with three types: FLOAT, UINT8, BOOL
- Type information stored as uint8_t arrays in DEMSimParams structure
- Helper function `getWildcardTypeSize()` to retrieve size of each type

### 2. Storage Implementation
- Changed all wildcard pointers from `float*` to `scratch_t*` (char*)
- Wildcards allocated as byte arrays sized according to actual type
- Type information propagated through initialization chain

### 3. API Extensions
- New methods with explicit type specification:
- `SetPerContactWildcardsWithTypes()`
- `SetPerOwnerWildcardsWithTypes()`
- `SetPerGeometryWildcardsWithTypes()`
- Original methods maintain backward compatibility (default to FLOAT)

### 4. Type-Safe Access
- Device-side macros for type-safe casting:
- `DEME_CONTACT_WILDCARD_FLOAT/UINT8/BOOL()`
- `DEME_OWNER_WILDCARD_FLOAT/UINT8/BOOL()`
- `DEME_GEO_WILDCARD_FLOAT/UINT8/BOOL()`

### 5. Data Migration Updates
- Updated allocation code to compute byte sizes based on type
- Modified resize operations to handle different element sizes
- Updated initialization code to cast float inputs to target types

## Files Modified

### Core System Files
1. `src/DEM/Defines.h` - Added type enum, helper functions, and accessor macros
2. `src/DEM/dT.h` - Updated storage types and function signatures
3. `src/DEM/dT.cpp` - Updated allocation, initialization, and migration logic
4. `src/DEM/kT.h` - Updated function signatures for type propagation
5. `src/DEM/kT.cpp` - Updated setSimParams to handle type information

### API Files
6. `src/DEM/AuxClasses.h` - Added type storage and new API methods
7. `src/DEM/AuxClasses.cpp` - Implemented new API methods
8. `src/DEM/APIPrivate.cpp` - Updated initialization calls

### Documentation
9. `TYPED_WILDCARDS.md` - Comprehensive user guide and examples

## Key Benefits

### Memory Efficiency
- UINT8 and BOOL types use 1 byte vs 4 bytes for float
- 75% memory reduction for non-float wildcards
- Improved cache efficiency on GPU

### Semantic Clarity
- Boolean flags naturally represented as BOOL type
- Integer counters and IDs as UINT8 (0-255 range)
- More self-documenting code

### Backward Compatibility
- Existing code continues to work without modifications
- Default type is FLOAT for all legacy calls
- No breaking changes to existing API

## Design Decisions

### 1. Char Array Base Storage
Following the problem statement, we use char* arrays as the base storage type. This allows:
- Single allocation/deallocation path
- Type flexibility through casting
- Minimal changes to existing infrastructure

### 2. Type Metadata in DEMSimParams
Storing type information as uint8_t arrays in DEMSimParams enables:
- Type information available in JIT-compiled kernels
- Simple serialization/deserialization
- No complex template machinery

### 3. Macro-Based Access
Using macros for wildcard access provides:
- Zero runtime overhead
- Clear syntax in CUDA kernels
- Compile-time type safety

### 4. Float Input Conversion
User input remains as float vectors because:
- Maintains compatibility with existing batch API
- Conversion happens once at initialization
- Simplifies user API (no template complexity)

## Testing Considerations

Due to CUDA requirement, full testing was not possible in this environment. Recommended tests:

1. **Unit Tests**
- Test wildcard allocation with different types
- Verify size calculations
- Test type casting correctness

2. **Integration Tests**
- Run existing demos to verify backward compatibility
- Create test with mixed-type wildcards
- Verify memory usage reduction

3. **Performance Tests**
- Benchmark memory usage with UINT8/BOOL vs FLOAT
- Measure cache performance impact
- Compare simulation speed

## Future Enhancements

1. **Additional Types**: Support int32_t, double, int16_t
2. **Direct Type Input**: Allow typed data in clump batches
3. **Type Inference**: Automatically detect type from initial values
4. **Vectorization**: SIMD optimizations for smaller types

## Security Considerations

No security vulnerabilities introduced:
- Proper bounds checking maintained
- Type casting is explicit and controlled
- No unsafe pointer arithmetic beyond existing patterns
- Memory allocation remains managed through existing infrastructure

## Conclusion

The implementation successfully extends the wildcard system to support multiple types while maintaining backward compatibility and following the design approach outlined in the problem statement. The solution provides significant memory efficiency gains and better semantic representation of data.
184 changes: 184 additions & 0 deletions TYPED_WILDCARDS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# Typed Wildcard Arrays in DEM-Engine

## Overview

DEM-Engine now supports multiple data types for wildcard arrays, allowing you to associate different types of properties with contacts, owners, and geometries. Previously, all wildcards were stored as `float` arrays. Now you can use:

- **FLOAT** - Standard floating-point values (default, backward compatible)
- **UINT8** - Unsigned 8-bit integers (0-255)
- **BOOL** - Boolean values using `notStupidBool_t`

This enhancement enables more efficient memory usage and better semantic representation of properties like flags, counters, or state indicators.

## Backward Compatibility

All existing code continues to work without changes. The default type for wildcards is `FLOAT`, so:

```cpp
// This continues to work as before - uses FLOAT type by default
my_force_model->SetPerContactWildcards({"delta_time", "delta_tan_x", "delta_tan_y", "delta_tan_z"});
```

## Using Typed Wildcards

### New API Methods

Three new methods allow explicit type specification:

```cpp
void SetPerContactWildcardsWithTypes(const std::unordered_map<std::string, WILDCARD_TYPE>& wildcards_with_types);
void SetPerOwnerWildcardsWithTypes(const std::unordered_map<std::string, WILDCARD_TYPE>& wildcards_with_types);
void SetPerGeometryWildcardsWithTypes(const std::unordered_map<std::string, WILDCARD_TYPE>& wildcards_with_types);
```

### Example Usage

#### Contact Wildcards with Mixed Types

```cpp
#include <DEM/API.h>

// Create a force model
auto my_force_model = DEMSim.DefineForceModel();

// Define contact wildcards with different types
std::unordered_map<std::string, WILDCARD_TYPE> contact_wildcards = {
{"delta_time", WILDCARD_TYPE::FLOAT}, // Time elapsed (float)
{"delta_tan_x", WILDCARD_TYPE::FLOAT}, // Tangential displacement X
{"delta_tan_y", WILDCARD_TYPE::FLOAT}, // Tangential displacement Y
{"delta_tan_z", WILDCARD_TYPE::FLOAT}, // Tangential displacement Z
{"contact_broken", WILDCARD_TYPE::BOOL}, // Boolean flag for broken contacts
{"collision_count", WILDCARD_TYPE::UINT8} // Counter for number of collisions (0-255)
};

my_force_model->SetPerContactWildcardsWithTypes(contact_wildcards);
```

#### Owner Wildcards with Boolean Flags

```cpp
// Define owner wildcards
std::unordered_map<std::string, WILDCARD_TYPE> owner_wildcards = {
{"cohesion_strength", WILDCARD_TYPE::FLOAT}, // Cohesion parameter
{"is_damaged", WILDCARD_TYPE::BOOL}, // Damage state flag
{"damage_level", WILDCARD_TYPE::UINT8} // Damage level (0-255)
};

my_force_model->SetPerOwnerWildcardsWithTypes(owner_wildcards);
```

#### Geometry Wildcards with Charge Values

```cpp
// Define geometry wildcards for electrostatic simulation
std::unordered_map<std::string, WILDCARD_TYPE> geo_wildcards = {
{"charge", WILDCARD_TYPE::FLOAT}, // Electric charge
{"is_conductor", WILDCARD_TYPE::BOOL}, // Conductor flag
{"material_id", WILDCARD_TYPE::UINT8} // Material identifier
};

my_force_model->SetPerGeometryWildcardsWithTypes(geo_wildcards);
```

## Accessing Typed Wildcards in Custom Force Models

### Device-Side Macros

When writing custom force models (CUDA kernels), use these macros to access wildcards:

```cpp
// For FLOAT wildcards - direct access
float delta_time = DEME_CONTACT_WILDCARD_FLOAT(simParams, contactWildcards, 0, myContactID);

// For UINT8 wildcards
uint8_t collision_count = DEME_CONTACT_WILDCARD_UINT8(simParams, contactWildcards, 5, myContactID);

// For BOOL wildcards
notStupidBool_t is_broken = DEME_CONTACT_WILDCARD_BOOL(simParams, contactWildcards, 4, myContactID);

// Generic macro that automatically casts based on type info (returns float)
float value = DEME_CONTACT_WILDCARD(simParams, contactWildcards, wcNum, myContactID);
```

Similar macros exist for owner and geometry wildcards:
- `DEME_OWNER_WILDCARD_FLOAT/UINT8/BOOL`
- `DEME_GEO_WILDCARD_FLOAT/UINT8/BOOL`

## Benefits

1. **Memory Efficiency**: Using `UINT8` or `BOOL` types reduces memory usage compared to `float`
- `UINT8`: 1 byte vs 4 bytes (75% reduction)
- `BOOL`: 1 byte vs 4 bytes (75% reduction)

2. **Semantic Clarity**: Boolean flags and integer counters are more clearly expressed
- `is_damaged` as BOOL is clearer than storing 0.0/1.0 as float
- `damage_level` as UINT8 naturally ranges from 0-255

3. **Performance**: Smaller data types can improve cache efficiency on GPU

## Implementation Details

### Internal Storage

Internally, all wildcard arrays are now stored as `char*` (scratch_t*) arrays. The actual interpretation of the data is controlled by type information stored in `DEMSimParams`:

```cpp
struct DEMSimParams {
// ...
uint8_t contactWildcardTypes[DEME_MAX_WILDCARD_NUM];
uint8_t ownerWildcardTypes[DEME_MAX_WILDCARD_NUM];
uint8_t geoWildcardTypes[DEME_MAX_WILDCARD_NUM];
// ...
};
```

### Type Casting

When accessing wildcard data, the system automatically casts the byte array to the appropriate pointer type:

```cpp
// For FLOAT type (4 bytes per element)
float* float_array = (float*)wildcard_base_ptr;
float value = float_array[index];

// For UINT8 type (1 byte per element)
uint8_t* uint8_array = (uint8_t*)wildcard_base_ptr;
uint8_t value = uint8_array[index];

// For BOOL type (1 byte per element)
notStupidBool_t* bool_array = (notStupidBool_t*)wildcard_base_ptr;
notStupidBool_t value = bool_array[index];
```

## Migration Guide

### From Old API to New API

If you want to leverage the new type system:

**Before:**
```cpp
my_force_model->SetPerContactWildcards({"delta_time", "contact_flag"});
```

**After:**
```cpp
std::unordered_map<std::string, WILDCARD_TYPE> contact_wildcards = {
{"delta_time", WILDCARD_TYPE::FLOAT},
{"contact_flag", WILDCARD_TYPE::BOOL} // More efficient than float
};
my_force_model->SetPerContactWildcardsWithTypes(contact_wildcards);
```

## Limitations

1. Maximum of `DEME_MAX_WILDCARD_NUM` (currently 16) wildcards of each category
2. Supported types are limited to FLOAT, UINT8, and BOOL
3. User-provided input data (in clump batches) is still stored as float and converted during initialization

## Future Enhancements

Potential future improvements:
- Support for additional types (int32, double, etc.)
- Direct support for typed input data in clump batches
- Automatic type inference from initial values
1 change: 1 addition & 0 deletions _codeql_detected_source_root
6 changes: 4 additions & 2 deletions src/DEM/APIPrivate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1158,11 +1158,13 @@ void DEMSolver::setSimParams() {
dT->setSimParams(nvXp2, nvYp2, nvZp2, l, m_voxelSize, m_binSize, nbX, nbY, nbZ, m_boxLBF, m_user_box_min,
m_user_box_max, G, m_ts_size, m_expand_factor, m_approx_max_vel, m_expand_safety_multi,
m_expand_base_vel, m_force_model->m_contact_wildcards, m_force_model->m_owner_wildcards,
m_force_model->m_geo_wildcards);
m_force_model->m_geo_wildcards, m_force_model->m_contact_wildcard_types,
m_force_model->m_owner_wildcard_types, m_force_model->m_geo_wildcard_types);
kT->setSimParams(nvXp2, nvYp2, nvZp2, l, m_voxelSize, m_binSize, nbX, nbY, nbZ, m_boxLBF, m_user_box_min,
m_user_box_max, G, m_ts_size, m_expand_factor, m_approx_max_vel, m_expand_safety_multi,
m_expand_base_vel, m_force_model->m_contact_wildcards, m_force_model->m_owner_wildcards,
m_force_model->m_geo_wildcards);
m_force_model->m_geo_wildcards, m_force_model->m_contact_wildcard_types,
m_force_model->m_owner_wildcard_types, m_force_model->m_geo_wildcard_types);
}

void DEMSolver::allocateGPUArrays() {
Expand Down
Loading