Commit 3328adf0 authored by Wohlgemuth, Jason's avatar Wohlgemuth, Jason
Browse files

feat: Create hardware module and build out resources data model

parent 99b1fe0c
Loading
Loading
Loading
Loading
Loading
+467 −0
Original line number Diff line number Diff line
//! Hardware resource, architecture, and vendor types
use derive_more::Display;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

/// Hardware architecture or microarchitecture identifier
#[derive(Clone, Debug, Display, Deserialize, PartialEq, Serialize, JsonSchema)]
pub enum Architecture {
    /// x86-64 instruction set (Intel/AMD)
    #[display("x86_64")]
    #[serde(alias = "x86_64", alias = "x86-64", alias = "amd64")]
    X86_64,
    /// 64-bit ARM instruction set
    #[display("AArch64")]
    AArch64,
    /// RISC-V open instruction set architecture
    #[display("RISC-V")]
    RiscV,
    /// MIPS instruction set architecture
    #[display("MIPS")]
    Mips,
    /// IBM PowerPC architecture
    #[display("PowerPC")]
    PowerPc,
    /// NVIDIA Ampere microarchitecture (A100, RTX 30xx)
    #[display("Ampere")]
    Ampere,
    /// NVIDIA Hopper microarchitecture (H100, H200)
    #[display("Hopper")]
    Hopper,
    /// NVIDIA Ada Lovelace microarchitecture (RTX 40xx)
    #[display("Ada Lovelace")]
    AdaLovelace,
    /// NVIDIA Blackwell microarchitecture (RTX 50xx, B100)
    #[display("Blackwell")]
    Blackwell,
    /// NVIDIA Turing microarchitecture (RTX 20xx)
    #[display("Turing")]
    Turing,
    /// NVIDIA Volta microarchitecture (V100)
    #[display("Volta")]
    Volta,
    /// AMD RDNA 2 microarchitecture (RX 6xxx)
    #[display("RDNA2")]
    Rdna2,
    /// AMD RDNA 3 microarchitecture (RX 7xxx)
    #[display("RDNA3")]
    Rdna3,
    /// AMD CDNA 2 microarchitecture (MI250)
    #[display("CDNA2")]
    Cdna2,
    /// AMD CDNA 3 microarchitecture (MI300)
    #[display("CDNA3")]
    Cdna3,
    /// Intel Xe graphics architecture
    #[display("Xe")]
    Xe,
    /// Google TPU v4
    #[display("TPU v4")]
    TpuV4,
    /// Google TPU v5e
    #[display("TPU v5e")]
    TpuV5e,
    /// Google TPU v5p
    #[display("TPU v5p")]
    TpuV5p,
    /// Qualcomm Hexagon processor (NPU/DSP)
    #[display("Hexagon")]
    Hexagon,
    /// Apple Neural Engine
    #[display("Neural Engine")]
    NeuralEngine,
    /// Texas Instruments C66x DSP
    #[display("C66x")]
    C66x,
    /// Cadence Tensilica Xtensa DSP
    #[display("Xtensa")]
    Xtensa,
    /// AMD/Xilinx Virtex UltraScale+ FPGA
    #[display("Virtex UltraScale+")]
    VirtexUltraScalePlus,
    /// AMD/Xilinx Zynq SoC FPGA
    #[display("Zynq")]
    Zynq,
    /// Intel/Altera Cyclone V FPGA
    #[display("Cyclone V")]
    CycloneV,
    /// Intel/Altera Stratix V FPGA
    #[display("Stratix V")]
    StratixV,
    /// Intel Agilex FPGA
    #[display("Agilex")]
    Agilex,
    /// Other or unspecified architecture
    #[display("{}", _0)]
    Other(String),
}
/// GPU compute backend or API
#[derive(Clone, Debug, Display, Deserialize, Serialize, JsonSchema)]
pub enum Backend {
    /// NVIDIA CUDA parallel computing platform
    #[display("CUDA")]
    Cuda,
    /// AMD ROCm open compute platform
    #[display("ROCm")]
    RoCm,
    /// Khronos OpenCL open standard
    #[display("OpenCL")]
    OpenCl,
    /// Apple Metal graphics and compute API
    #[display("Metal")]
    Metal,
    /// Khronos Vulkan graphics and compute API
    #[display("Vulkan")]
    Vulkan,
    /// Intel oneAPI SYCL
    #[display("SYCL")]
    Sycl,
    /// W3C WebGPU API
    #[display("WebGPU")]
    WebGpu,
    /// Other or unspecified backend
    #[display("{}", _0)]
    Other(String),
}
/// Enumeration for hardware resources used by technology
#[derive(Clone, Debug, Serialize, JsonSchema)]
pub enum Resource {
    /// Central processing unit for "classical" computing
    CPU {
        /// Instruction set architecture
        #[serde(alias = "arch")]
        architecture: Option<Architecture>,
        /// Number of physical cores required (if applicable)
        cores: Option<u32>,
        /// Minimum RAM in GB (if applicable)
        #[serde(alias = "ram")]
        memory: Option<u32>,
        /// Number of threads required (if applicable)
        threads: Option<u32>,
        /// Hardware vendor (e.g., Intel, AMD, ARM)
        vendor: Option<Vendor>,
    },
    /// Graphics processing unit
    GPU {
        /// GPU microarchitecture
        #[serde(alias = "arch")]
        architecture: Option<Architecture>,
        /// Compute backend or API
        backend: Option<Backend>,
        /// NVIDIA CUDA compute capability (e.g., 8.0 for A100)
        compute_capability: Option<f32>,
        /// Number of GPUs required (if applicable)
        count: Option<u32>,
        /// VRAM in GB (if applicable)
        #[serde(alias = "vram")]
        memory: Option<u32>,
        /// GPU model name (e.g., "H100", "RTX 4090")
        name: Option<String>,
        /// Hardware vendor (e.g., NVIDIA, AMD, Intel)
        vendor: Option<Vendor>,
    },
    /// Tensor processing unit
    TPU {
        /// TPU generation
        #[serde(alias = "arch")]
        architecture: Option<Architecture>,
        /// Number of TPU chips required (if applicable)
        count: Option<u32>,
        /// High-bandwidth memory in GB (if applicable)
        #[serde(alias = "mem")]
        memory: Option<u32>,
        /// Hardware vendor (e.g., Google)
        vendor: Option<Vendor>,
    },
    /// Neural processing unit
    ///
    /// A specialized chip designed for the matrix multiplication calculations that most AI models rely on
    NPU {
        /// NPU architecture
        #[serde(alias = "arch")]
        architecture: Option<Architecture>,
        /// Number of NPUs required (if applicable)
        count: Option<u32>,
        /// On-chip SRAM in MB (if applicable)
        #[serde(alias = "mem")]
        memory: Option<u32>,
        /// Hardware vendor (e.g., Qualcomm, Apple, Intel)
        vendor: Option<Vendor>,
    },
    /// Application-specific integrated circuit
    ///
    /// A custom-designed chip built to perform one particular function or a narrow set of functions, rather than acting as a general-purpose processor like a CPU or GPU
    ASIC {
        /// ASIC architecture or design family
        #[serde(alias = "arch")]
        architecture: Option<Architecture>,
        /// Intended purpose of the ASIC (e.g., "Bitcoin mining", "video encoding")
        purpose: Option<String>,
        /// Hardware vendor or designer
        vendor: Option<Vendor>,
    },
    /// Digital signal processor
    ///
    /// A specialized microprocessor optimized to perform fast mathematical operations on digital signals in real time
    DSP {
        /// DSP architecture
        #[serde(alias = "arch")]
        architecture: Option<Architecture>,
        /// Clock frequency in MHz (if applicable)
        #[serde(alias = "freq")]
        frequency: Option<u32>,
        /// Hardware vendor (e.g., Qualcomm, Texas Instruments)
        vendor: Option<Vendor>,
    },
    /// Field-programmable gate array
    ///
    /// A reconfigurable integrated circuit that lets you implement custom digital hardware circuits after manufacturing, rather than having a fixed function like a CPU or ASIC
    FPGA {
        /// FPGA architecture or product family
        #[serde(alias = "arch")]
        architecture: Option<Architecture>,
        /// Number of logic elements or LUTs available
        logic_elements: Option<u32>,
        /// Block RAM in KB (if applicable)
        #[serde(alias = "mem")]
        memory: Option<u32>,
        /// Hardware vendor (e.g., Intel, AMD)
        vendor: Option<Vendor>,
    },
    /// Neuromorphic compute
    Neuromorphic,
    /// Quantum computing (e.g., NISQ, etc.)
    Quantum,
    /// Unknown, unspecified, or otherwise unclassified resource
    Other(String),
}
/// Hardware vendor or manufacturer
#[derive(Clone, Debug, Display, Deserialize, Serialize, JsonSchema)]
pub enum Vendor {
    /// Advanced Micro Devices
    #[display("AMD")]
    AMD,
    /// Apple Inc.
    #[display("Apple")]
    Apple,
    /// Arm Holdings
    #[display("ARM")]
    ARM,
    /// Google LLC
    #[display("Google")]
    Google,
    /// Intel Corporation
    #[display("Intel")]
    Intel,
    /// NVIDIA Corporation
    #[display("NVIDIA")]
    NVIDIA,
    /// Qualcomm Incorporated
    #[display("Qualcomm")]
    Qualcomm,
    /// Other or unspecified vendor
    #[display("{}", _0)]
    Other(String),
}
impl<'de> Deserialize<'de> for Resource {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::de::Deserializer<'de>,
    {
        struct ResourceVisitor;
        impl<'de> serde::de::Visitor<'de> for ResourceVisitor {
            type Value = Resource;
            fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
                formatter.write_str(r#"a resource string (e.g. "CPU") or object (e.g. {"CPU": {...}})"#)
            }
            fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Resource, E> {
                match value {
                    | "CPU" | "cpu" => Ok(Resource::CPU {
                        architecture: None,
                        cores: None,
                        memory: None,
                        threads: None,
                        vendor: None,
                    }),
                    | "GPU" | "gpu" => Ok(Resource::GPU {
                        architecture: None,
                        backend: None,
                        compute_capability: None,
                        count: None,
                        memory: None,
                        name: None,
                        vendor: None,
                    }),
                    | "TPU" | "tpu" => Ok(Resource::TPU {
                        architecture: None,
                        count: None,
                        memory: None,
                        vendor: None,
                    }),
                    | "NPU" | "npu" => Ok(Resource::NPU {
                        architecture: None,
                        count: None,
                        memory: None,
                        vendor: None,
                    }),
                    | "ASIC" | "asic" => Ok(Resource::ASIC {
                        architecture: None,
                        purpose: None,
                        vendor: None,
                    }),
                    | "DSP" | "dsp" => Ok(Resource::DSP {
                        architecture: None,
                        frequency: None,
                        vendor: None,
                    }),
                    | "FPGA" | "fpga" => Ok(Resource::FPGA {
                        architecture: None,
                        logic_elements: None,
                        memory: None,
                        vendor: None,
                    }),
                    | "Neuromorphic" | "neuromorphic" => Ok(Resource::Neuromorphic),
                    | "Quantum" | "quantum" => Ok(Resource::Quantum),
                    | other => Ok(Resource::Other(other.to_string())),
                }
            }
            fn visit_map<M: serde::de::MapAccess<'de>>(self, mut map: M) -> Result<Resource, M::Error> {
                let tag: String = map.next_key()?.ok_or_else(|| serde::de::Error::custom("expected resource variant key"))?;
                match tag.as_str() {
                    | "CPU" => {
                        #[derive(Deserialize)]
                        struct F {
                            #[serde(alias = "arch")]
                            architecture: Option<Architecture>,
                            cores: Option<u32>,
                            memory: Option<u32>,
                            threads: Option<u32>,
                            vendor: Option<Vendor>,
                        }
                        let f: F = map.next_value()?;
                        Ok(Resource::CPU {
                            architecture: f.architecture,
                            cores: f.cores,
                            memory: f.memory,
                            threads: f.threads,
                            vendor: f.vendor,
                        })
                    }
                    | "GPU" => {
                        #[derive(Deserialize)]
                        struct F {
                            #[serde(alias = "arch")]
                            architecture: Option<Architecture>,
                            backend: Option<Backend>,
                            compute_capability: Option<f32>,
                            count: Option<u32>,
                            memory: Option<u32>,
                            name: Option<String>,
                            vendor: Option<Vendor>,
                        }
                        let f: F = map.next_value()?;
                        Ok(Resource::GPU {
                            architecture: f.architecture,
                            backend: f.backend,
                            compute_capability: f.compute_capability,
                            count: f.count,
                            memory: f.memory,
                            name: f.name,
                            vendor: f.vendor,
                        })
                    }
                    | "TPU" => {
                        #[derive(Deserialize)]
                        struct F {
                            #[serde(alias = "arch")]
                            architecture: Option<Architecture>,
                            count: Option<u32>,
                            memory: Option<u32>,
                            vendor: Option<Vendor>,
                        }
                        let f: F = map.next_value()?;
                        Ok(Resource::TPU {
                            architecture: f.architecture,
                            count: f.count,
                            memory: f.memory,
                            vendor: f.vendor,
                        })
                    }
                    | "NPU" => {
                        #[derive(Deserialize)]
                        struct F {
                            #[serde(alias = "arch")]
                            architecture: Option<Architecture>,
                            count: Option<u32>,
                            memory: Option<u32>,
                            vendor: Option<Vendor>,
                        }
                        let f: F = map.next_value()?;
                        Ok(Resource::NPU {
                            architecture: f.architecture,
                            count: f.count,
                            memory: f.memory,
                            vendor: f.vendor,
                        })
                    }
                    | "ASIC" => {
                        #[derive(Deserialize)]
                        struct F {
                            #[serde(alias = "arch")]
                            architecture: Option<Architecture>,
                            purpose: Option<String>,
                            vendor: Option<Vendor>,
                        }
                        let f: F = map.next_value()?;
                        Ok(Resource::ASIC {
                            architecture: f.architecture,
                            purpose: f.purpose,
                            vendor: f.vendor,
                        })
                    }
                    | "DSP" => {
                        #[derive(Deserialize)]
                        struct F {
                            #[serde(alias = "arch")]
                            architecture: Option<Architecture>,
                            frequency: Option<u32>,
                            vendor: Option<Vendor>,
                        }
                        let f: F = map.next_value()?;
                        Ok(Resource::DSP {
                            architecture: f.architecture,
                            frequency: f.frequency,
                            vendor: f.vendor,
                        })
                    }
                    | "FPGA" => {
                        #[derive(Deserialize)]
                        struct F {
                            #[serde(alias = "arch")]
                            architecture: Option<Architecture>,
                            logic_elements: Option<u32>,
                            memory: Option<u32>,
                            vendor: Option<Vendor>,
                        }
                        let f: F = map.next_value()?;
                        Ok(Resource::FPGA {
                            architecture: f.architecture,
                            logic_elements: f.logic_elements,
                            memory: f.memory,
                            vendor: f.vendor,
                        })
                    }
                    | "Neuromorphic" | "Quantum" => {
                        let _: serde::de::IgnoredAny = map.next_value()?;
                        self.visit_str(&tag)
                    }
                    | other => {
                        let _: serde::de::IgnoredAny = map.next_value()?;
                        Ok(Resource::Other(other.to_string()))
                    }
                }
            }
        }
        deserializer.deserialize_any(ResourceVisitor)
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -2,6 +2,8 @@
//!
//! Here you'll find everything needed to build and use the research activity data schema, including metadata fields, section information, media objects, formats, and functions that power ACORN CLI commands.
//!
pub mod hardware;
pub use hardware::{Architecture, Resource, Vendor};

#[cfg(feature = "std")]
use crate::prelude::PathBuf;
+2 −1
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@ use crate::io::ApiResult;
use crate::io::{image_paths, parent, read_file, write_file, FromPath, InputOutput};
#[cfg(feature = "std")]
use crate::prelude::PathBuf;
use crate::schema::hardware::Resource;
use crate::schema::namespaces::{bibo, codemeta, dcat, dcterms, schema_org, DEFAULT_RAID_SCHEMA_URI, DEFAULT_ROR_SCHEMA_URI};
use crate::schema::pid::PersistentIdentifierConvert;
use crate::schema::standard::cff::{Agent, Cff, Identifier, IdentifierType, Person};
@@ -19,7 +20,7 @@ use crate::util::constants::{
    MAX_LENGTH_SECTION_MISSION,
};
use crate::util::constants::{MAX_LENGTH_APPROACH, MAX_LENGTH_CAPABILIY, MAX_LENGTH_IMPACT, MAX_LENGTH_RESEARCH_AREA};
use crate::util::{frontmatter_and_body, LinkedData, Resource, ToMarkdown, ToProse};
use crate::util::{frontmatter_and_body, LinkedData, ToMarkdown, ToProse};
#[cfg(feature = "std")]
use crate::util::{Constant, Label};
#[cfg(feature = "std")]
+28 −12
Original line number Diff line number Diff line
use crate::io::InputOutput;
use crate::prelude::PathBuf;
use crate::schema::hardware::{Architecture, Resource, Vendor};
use crate::schema::research_activity::*;
use crate::schema::standard::cff::{Agent, Cff, IdentifierType};
use crate::schema::*;
use crate::util::{Resource, Vendor};
use pretty_assertions::assert_eq;

fn fixtures_dir() -> PathBuf {
@@ -274,14 +274,16 @@ fn test_metadata_with_resources() {
        .identifier("gpu-project".to_string())
        .resources(vec![
            Resource::GPU {
                architecture: Some("Ampere".to_string()),
                architecture: Some(Architecture::Ampere),
                backend: None,
                compute_capability: Some(8.0),
                count: Some(4),
                memory: Some(80),
                name: None,
                vendor: Some(Vendor::NVIDIA),
            },
            Resource::CPU {
                architecture: Some("x86_64".to_string()),
                architecture: Some(Architecture::X86_64),
                cores: Some(32),
                memory: Some(256),
                threads: Some(64),
@@ -311,12 +313,20 @@ fn test_metadata_with_resources() {
fn test_metadata_with_minimal_resources() {
    let meta = ResearchActivityMetadata::init()
        .identifier("quantum-project".to_string())
        .resources(vec![Resource::Quantum, Resource::FPGA])
        .resources(vec![
            Resource::Quantum,
            Resource::FPGA {
                architecture: None,
                logic_elements: None,
                memory: None,
                vendor: None,
            },
        ])
        .build();
    let resources = meta.resources.expect("resources should be present");
    assert_eq!(resources.len(), 2);
    assert!(matches!(resources[0], Resource::Quantum));
    assert!(matches!(resources[1], Resource::FPGA));
    assert!(matches!(resources[1], Resource::FPGA { .. }));
}
#[test]
fn test_metadata_without_resources() {
@@ -329,9 +339,11 @@ fn test_metadata_resources_roundtrip() {
        .identifier("roundtrip-test".to_string())
        .resources(vec![Resource::GPU {
            architecture: None,
            backend: None,
            compute_capability: None,
            count: Some(1),
            memory: Some(24),
            name: None,
            vendor: Some(Vendor::Intel),
        }])
        .build();
@@ -377,12 +389,13 @@ fn test_deserialize_metadata_with_gpu_resources() {
    assert!(matches!(
        &resources[0],
        Resource::GPU {
            architecture: Some(arch),
            architecture: Some(Architecture::Hopper),
            compute_capability: Some(cc),
            count: Some(8),
            memory: Some(80),
            vendor: Some(Vendor::NVIDIA),
        } if arch == "Hopper" && (*cc - 9.0_f32).abs() < f32::EPSILON
            ..
        } if (*cc - 9.0_f32).abs() < f32::EPSILON
    ));
}
#[test]
@@ -426,8 +439,8 @@ fn test_deserialize_metadata_with_mixed_resources() {
            ..
        }
    ));
    assert!(matches!(resources[1], Resource::TPU));
    assert!(matches!(resources[2], Resource::FPGA));
    assert!(matches!(resources[1], Resource::TPU { .. }));
    assert!(matches!(resources[2], Resource::FPGA { .. }));
    assert!(matches!(
        &resources[3],
        Resource::GPU {
@@ -466,6 +479,7 @@ fn test_deserialize_metadata_with_partial_gpu_fields() {
            count: None,
            memory: Some(24),
            vendor: None,
            ..
        }
    ));
}
@@ -485,9 +499,9 @@ fn test_deserialize_metadata_with_unit_resources() {
    assert_eq!(resources.len(), 6);
    assert!(matches!(resources[0], Resource::Quantum));
    assert!(matches!(resources[1], Resource::Neuromorphic));
    assert!(matches!(resources[2], Resource::NPU));
    assert!(matches!(resources[3], Resource::ASIC));
    assert!(matches!(resources[4], Resource::DSP));
    assert!(matches!(resources[2], Resource::NPU { .. }));
    assert!(matches!(resources[3], Resource::ASIC { .. }));
    assert!(matches!(resources[4], Resource::DSP { .. }));
    assert!(matches!(&resources[5], Resource::Other(s) if s == "unique-hardware"));
}
#[test]
@@ -525,6 +539,7 @@ fn test_deserialize_metadata_with_empty_cpu_and_gpu() {
            count: None,
            memory: None,
            vendor: None,
            ..
        }
    ));
}
@@ -560,6 +575,7 @@ fn test_deserialize_metadata_with_string_cpu_and_gpu() {
            count: None,
            memory: None,
            vendor: None,
            ..
        }
    ));
}
+1 −173

File changed.

Preview size limit exceeded, changes collapsed.

Loading