feat: use AI to migrate project (no fix now)
This commit is contained in:
20
kernel/Cargo.lock
generated
20
kernel/Cargo.lock
generated
@@ -114,6 +114,8 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
|||||||
name = "lcrconn"
|
name = "lcrconn"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"strum",
|
||||||
|
"strum_macros",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -156,6 +158,24 @@ version = "0.11.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.28.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.28.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.118"
|
version = "2.0.118"
|
||||||
|
|||||||
@@ -1,3 +1,641 @@
|
|||||||
fn main() {
|
use std::io::{self, Write};
|
||||||
println!("Hello, world!");
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use lcrconn::{
|
||||||
|
from_human_readable_value, to_human_readable_value, BfsResolver, Circuit, CircuitDeviceScale,
|
||||||
|
DatasetCollection, DeviceKind, JointKind, LcrConnError, LutResolver, Request, Resolver,
|
||||||
|
Response, ResponsePriority, MAX_RESPONSE_CNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Command-line arguments
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// The resolver for the app.
|
||||||
|
#[derive(Clone, Debug, clap::ValueEnum)]
|
||||||
|
pub enum AppResolver {
|
||||||
|
/// The look-up table resolver.
|
||||||
|
#[value(name = "lut")]
|
||||||
|
Lut,
|
||||||
|
/// The BFS resolver.
|
||||||
|
#[value(name = "bfs")]
|
||||||
|
Bfs,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The configuration for the app.
|
||||||
|
struct AppConfig {
|
||||||
|
/// The resolver for the app.
|
||||||
|
resolver: AppResolver,
|
||||||
|
/// The path to the resistor dataset file.
|
||||||
|
resistor_dataset: PathBuf,
|
||||||
|
/// The path to the capacitor dataset file.
|
||||||
|
capacitor_dataset: PathBuf,
|
||||||
|
/// The path to the inductor dataset file.
|
||||||
|
inductor_dataset: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the resistor, capacitor, or inductor circuit which has the closest value
|
||||||
|
/// for your given value within at most 3 devices.
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(
|
||||||
|
name = "LCR Connector",
|
||||||
|
about = "Get the resistor, capacitor, or inductor circuit which has the closest value for your given value within at most 3 devices."
|
||||||
|
)]
|
||||||
|
struct Args {
|
||||||
|
/// The resolver you want to use.
|
||||||
|
#[arg(short = 's', long)]
|
||||||
|
resolver: AppResolver,
|
||||||
|
|
||||||
|
/// The path to the resistor dataset file.
|
||||||
|
#[arg(short = 'r', long, value_name = "RESISTOR.TXT")]
|
||||||
|
resistor_dataset: PathBuf,
|
||||||
|
|
||||||
|
/// The path to the inductor dataset file.
|
||||||
|
#[arg(short = 'l', long, value_name = "INDUCTOR.TXT")]
|
||||||
|
inductor_dataset: PathBuf,
|
||||||
|
|
||||||
|
/// The path to the capacitor dataset file.
|
||||||
|
#[arg(short = 'c', long, value_name = "CAPACITOR.TXT")]
|
||||||
|
capacitor_dataset: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Args> for AppConfig {
|
||||||
|
fn from(args: Args) -> Self {
|
||||||
|
Self {
|
||||||
|
resolver: args.resolver,
|
||||||
|
resistor_dataset: args.resistor_dataset,
|
||||||
|
capacitor_dataset: args.capacitor_dataset,
|
||||||
|
inductor_dataset: args.inductor_dataset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Interactive command enums
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// The command for the main menu.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
enum MainCmd {
|
||||||
|
Query,
|
||||||
|
Help,
|
||||||
|
Exit,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_main_cmd(s: &str) -> Option<MainCmd> {
|
||||||
|
match s {
|
||||||
|
"query" => Some(MainCmd::Query),
|
||||||
|
"help" => Some(MainCmd::Help),
|
||||||
|
"exit" => Some(MainCmd::Exit),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The device choice for query.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
enum QueryDeviceChoice {
|
||||||
|
Resistor,
|
||||||
|
Capacitor,
|
||||||
|
Inductor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryDeviceChoice {
|
||||||
|
fn parse(s: &str) -> Option<Self> {
|
||||||
|
match s {
|
||||||
|
"r" => Some(Self::Resistor),
|
||||||
|
"c" => Some(Self::Capacitor),
|
||||||
|
"l" => Some(Self::Inductor),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_device_kind(self) -> DeviceKind {
|
||||||
|
match self {
|
||||||
|
Self::Resistor => DeviceKind::Resistor,
|
||||||
|
Self::Capacitor => DeviceKind::Capacitor,
|
||||||
|
Self::Inductor => DeviceKind::Inductor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The sort priority for query results.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
enum QuerySortPriority {
|
||||||
|
LessDevices,
|
||||||
|
MoreAccuracy,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QuerySortPriority {
|
||||||
|
fn parse(s: &str) -> Option<Self> {
|
||||||
|
match s {
|
||||||
|
"l" => Some(Self::LessDevices),
|
||||||
|
"a" => Some(Self::MoreAccuracy),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_response_priority(self) -> ResponsePriority {
|
||||||
|
match self {
|
||||||
|
Self::LessDevices => ResponsePriority::LessDevices,
|
||||||
|
Self::MoreAccuracy => ResponsePriority::MoreAccuracy,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The command for the page viewer.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
enum PageViewerCmd {
|
||||||
|
PreviousPage,
|
||||||
|
NextPage,
|
||||||
|
Quit,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PageViewerCmd {
|
||||||
|
fn parse(s: &str) -> Option<Self> {
|
||||||
|
match s {
|
||||||
|
"f" => Some(Self::PreviousPage),
|
||||||
|
"b" => Some(Self::NextPage),
|
||||||
|
"q" => Some(Self::Quit),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Input utilities
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// Read a single line from stdin, trimmed of surrounding whitespace.
|
||||||
|
fn read_line() -> String {
|
||||||
|
let mut line = String::new();
|
||||||
|
io::stdin()
|
||||||
|
.read_line(&mut line)
|
||||||
|
.expect("Failed to read from stdin");
|
||||||
|
line.trim().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a plain float value.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `user_value` - The value to parse.
|
||||||
|
/// * `checker` - A function that checks if the input is valid.
|
||||||
|
/// It takes a float as input and returns a bool. True means the input is valid,
|
||||||
|
/// otherwise False.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The parsed value if it is valid, otherwise `None`.
|
||||||
|
fn parse_plain_float(user_value: &str, checker: impl Fn(&f64) -> bool) -> Option<f64> {
|
||||||
|
// try parsing it first
|
||||||
|
let value = user_value.parse::<f64>().ok()?;
|
||||||
|
// then check it by checker
|
||||||
|
if checker(&value) {
|
||||||
|
Some(value)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a human-readable device value.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `user_value` - The value to parse.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The parsed value if it is valid and positive, otherwise `None`.
|
||||||
|
fn parse_human_readable_value(user_value: &str) -> Option<f64> {
|
||||||
|
// parse it
|
||||||
|
let value = from_human_readable_value(user_value).ok()?;
|
||||||
|
// then check its range
|
||||||
|
if value > 0.0 {
|
||||||
|
Some(value)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Response display utilities
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// Get the unit string for a device kind.
|
||||||
|
fn get_device_unit(device_kind: DeviceKind) -> &'static str {
|
||||||
|
match device_kind {
|
||||||
|
DeviceKind::Resistor => "\u{2126}",
|
||||||
|
DeviceKind::Capacitor => "F",
|
||||||
|
DeviceKind::Inductor => "H",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format a device value for display in the circuit graph.
|
||||||
|
fn to_circuit_graph_value(value: f64, device_kind: DeviceKind) -> String {
|
||||||
|
// Remove sign and append device unit
|
||||||
|
let hr = to_human_readable_value(value);
|
||||||
|
let without_sign = &hr[1..];
|
||||||
|
format!("{}{}", without_sign, get_device_unit(device_kind))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format a device value for the plan header.
|
||||||
|
fn to_plan_head_value(value: f64, device_kind: DeviceKind) -> String {
|
||||||
|
// Remove sign and append device unit
|
||||||
|
let hr = to_human_readable_value(value);
|
||||||
|
let without_sign = &hr[1..];
|
||||||
|
format!("{}{}", without_sign, get_device_unit(device_kind))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format a difference value for the plan header.
|
||||||
|
fn to_plan_head_diff(value: f64, device_kind: DeviceKind) -> String {
|
||||||
|
// Keep the sign and append device unit
|
||||||
|
format!("{}{}", to_human_readable_value(value), get_device_unit(device_kind))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Format a relative difference as percentage.
|
||||||
|
fn to_plan_head_diff_pct(value: f64) -> String {
|
||||||
|
// Keep the sign and format it as percentage style without trailing device unit
|
||||||
|
format!("{:.2}%", value * 100.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// YYC MARK:
|
||||||
|
// The function showing circuit graph should be maintained carefully.
|
||||||
|
// First, we want they are show in console properly,
|
||||||
|
// And we also want they have good code view.
|
||||||
|
//
|
||||||
|
// I notices that the number part of the output of `to_human_readable_value` will only be
|
||||||
|
// "+999.9999" or "+9.9999e+00". So its maximum of its length is 11, considering the possibility,
|
||||||
|
// that the absolute value of exponential part is larger than 99, is close to zero.
|
||||||
|
// After putting the scale unit and device unit together like " nF",
|
||||||
|
// the whole maximum size of the built string is 14.
|
||||||
|
//
|
||||||
|
// So we need pick a larger number and odd number for the space for showing device value,
|
||||||
|
// because odd value can be divided by two so it can be split as two parts equally
|
||||||
|
// for the convenient alignment of some circuit graphs.
|
||||||
|
// My picked value is 16.
|
||||||
|
// So you will see that I use `:^16` for a center alignment to given string.
|
||||||
|
//
|
||||||
|
// After this, we also need set the padding value carefully.
|
||||||
|
// This value should consider the length of f-string syntax, pre-defined chars and required chars.
|
||||||
|
// To make sure a pretty showcase both in code and display.
|
||||||
|
|
||||||
|
/// Illustrate a one-device circuit.
|
||||||
|
fn illustrate_one_device_circuit(circuit: &Circuit, device_kind: DeviceKind) {
|
||||||
|
let dev1 = to_circuit_graph_value(circuit.first_device_value(), device_kind);
|
||||||
|
println!("──[{:^16}]──", dev1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Illustrate a two-device circuit.
|
||||||
|
fn illustrate_two_device_circuit(circuit: &Circuit, device_kind: DeviceKind) -> Result<(), LcrConnError> {
|
||||||
|
let dev1 = to_circuit_graph_value(circuit.first_device_value(), device_kind);
|
||||||
|
let j2 = circuit.second_device_joint()?;
|
||||||
|
let dev2 = to_circuit_graph_value(circuit.second_device_value()?, device_kind);
|
||||||
|
match j2 {
|
||||||
|
JointKind::Series => {
|
||||||
|
println!("──[{:^16}]──[{:^16}]──", dev1, dev2);
|
||||||
|
}
|
||||||
|
JointKind::Parallel => {
|
||||||
|
let sep0 = " ".repeat(6 + (16 - 10));
|
||||||
|
println!(" ┌──[{:^16}]──┐ ", dev1);
|
||||||
|
println!("──┤ {} ├──", sep0);
|
||||||
|
println!(" └──[{:^16}]──┘ ", dev2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Illustrate a three-device circuit.
|
||||||
|
fn illustrate_three_device_circuit(circuit: &Circuit, device_kind: DeviceKind) -> Result<(), LcrConnError> {
|
||||||
|
let dev1 = to_circuit_graph_value(circuit.first_device_value(), device_kind);
|
||||||
|
let j2 = circuit.second_device_joint()?;
|
||||||
|
let dev2 = to_circuit_graph_value(circuit.second_device_value()?, device_kind);
|
||||||
|
let j3 = circuit.third_device_joint()?;
|
||||||
|
let dev3 = to_circuit_graph_value(circuit.third_device_value()?, device_kind);
|
||||||
|
match j2 {
|
||||||
|
JointKind::Series => match j3 {
|
||||||
|
JointKind::Series => {
|
||||||
|
// All in series
|
||||||
|
println!("──[{:^16}]──[{:^16}]──[{:^16}]──", dev1, dev2, dev3);
|
||||||
|
}
|
||||||
|
JointKind::Parallel => {
|
||||||
|
// First series then parallel
|
||||||
|
let sep0 = "\u{2500}".repeat(6 + ((16 - 10) / 2));
|
||||||
|
let sep1 = " ".repeat(6 + 2 * (16 - 10));
|
||||||
|
println!(" ┌──[{:^16}]──[{:^16}]──┐ ", dev1, dev2);
|
||||||
|
println!("──┤ {} ├──", sep1);
|
||||||
|
println!(" └───{}[{:^16}]{}───┘ ", sep0, dev3, sep0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
JointKind::Parallel => match j3 {
|
||||||
|
JointKind::Series => {
|
||||||
|
// First parallel then series
|
||||||
|
let sep0 = " ".repeat(6 + (16 - 10));
|
||||||
|
println!(" {} ┌──[{:^16}]──┐ ", sep0, dev1);
|
||||||
|
println!("──[{:^16}]──┤ {} ├──", dev3, sep0);
|
||||||
|
println!(" {} └──[{:^16}]──┘ ", sep0, dev2);
|
||||||
|
}
|
||||||
|
JointKind::Parallel => {
|
||||||
|
// All in parallel
|
||||||
|
println!(" ┌──[{:^16}]──┐ ", dev1);
|
||||||
|
println!("──┼──[{:^16}]──┼──", dev2);
|
||||||
|
println!(" └──[{:^16}]──┘ ", dev3);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Illustrate a circuit based on its device scale.
|
||||||
|
fn illustrate_circuit(circuit: &Circuit, device_kind: DeviceKind) -> Result<(), LcrConnError> {
|
||||||
|
match circuit.device_scale() {
|
||||||
|
CircuitDeviceScale::One => {
|
||||||
|
illustrate_one_device_circuit(circuit, device_kind);
|
||||||
|
}
|
||||||
|
CircuitDeviceScale::Two => {
|
||||||
|
illustrate_two_device_circuit(circuit, device_kind)?;
|
||||||
|
}
|
||||||
|
CircuitDeviceScale::Three => {
|
||||||
|
illustrate_three_device_circuit(circuit, device_kind)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Illustrate a response item.
|
||||||
|
fn illustrate_response(response: &Response, index: usize) -> Result<(), LcrConnError> {
|
||||||
|
let item = &response[index];
|
||||||
|
let device_kind = response.device_kind();
|
||||||
|
// print header
|
||||||
|
println!(
|
||||||
|
"Plan {:<4} Value: {:<16} Diff: {} ({})",
|
||||||
|
index + 1,
|
||||||
|
to_plan_head_value(item.value(), device_kind),
|
||||||
|
to_plan_head_diff(item.difference(), device_kind),
|
||||||
|
to_plan_head_diff_pct(item.relative_difference()),
|
||||||
|
);
|
||||||
|
// print circuit graph
|
||||||
|
illustrate_circuit(item.circuit(), device_kind)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// App
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// The app.
|
||||||
|
struct App {
|
||||||
|
/// The resolver for the app.
|
||||||
|
resolver: Box<dyn Resolver>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
/// Create a new app with the given configuration.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`DatasetCollection::from_file`] and [`LutResolver::new`].
|
||||||
|
fn new(config: AppConfig) -> Result<Self, LcrConnError> {
|
||||||
|
let datasets = DatasetCollection::from_file(
|
||||||
|
&config.resistor_dataset,
|
||||||
|
&config.capacitor_dataset,
|
||||||
|
&config.inductor_dataset,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let resolver: Box<dyn Resolver> = match config.resolver {
|
||||||
|
AppResolver::Lut => Box::new(LutResolver::new(&datasets)?),
|
||||||
|
AppResolver::Bfs => Box::new(BfsResolver::new(datasets)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self { resolver })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the app.
|
||||||
|
fn run(&self) -> Result<(), LcrConnError> {
|
||||||
|
println!("LCR Connector");
|
||||||
|
println!("Type \"help\" for more info. Type \"exit\" to quit.");
|
||||||
|
self.op_main()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Subcommand Processors
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
fn op_main(&self) -> Result<(), LcrConnError> {
|
||||||
|
loop {
|
||||||
|
match self.accept_command(parse_main_cmd) {
|
||||||
|
MainCmd::Query => self.op_query()?,
|
||||||
|
MainCmd::Help => {
|
||||||
|
println!("LCR Connector Help:");
|
||||||
|
println!();
|
||||||
|
println!("query: do a query.");
|
||||||
|
println!("help: show all command.");
|
||||||
|
println!("exit: exit this app.");
|
||||||
|
}
|
||||||
|
MainCmd::Exit => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op_query(&self) -> Result<(), LcrConnError> {
|
||||||
|
// collecting request infos
|
||||||
|
println!("What are you connecting?");
|
||||||
|
println!("r: resistor");
|
||||||
|
println!("l: inductor");
|
||||||
|
println!("c: capacitor");
|
||||||
|
let device_kind = self
|
||||||
|
.accept_command(QueryDeviceChoice::parse)
|
||||||
|
.to_device_kind();
|
||||||
|
|
||||||
|
println!("Your target value?");
|
||||||
|
println!("Example: \"2.1k\", \"0.75m\", \"3.2M\" and etc.");
|
||||||
|
let target_value = self.accept_device_value();
|
||||||
|
|
||||||
|
println!("Your tolerance?");
|
||||||
|
println!("It can be absolute value like \"2.1k\".");
|
||||||
|
println!("Or relative value to your target value like \"19.5%\".");
|
||||||
|
let tolerance = self.accept_device_value_tolerance(target_value);
|
||||||
|
|
||||||
|
println!("How to sort result?");
|
||||||
|
println!("a: more accuracy");
|
||||||
|
println!("l: less component");
|
||||||
|
let response_priority = self
|
||||||
|
.accept_command(QuerySortPriority::parse)
|
||||||
|
.to_response_priority();
|
||||||
|
|
||||||
|
println!("How may result are you expected?");
|
||||||
|
let count_limit = self.accept_count_value();
|
||||||
|
|
||||||
|
// build request and ask resolver
|
||||||
|
let request = Request::new(
|
||||||
|
device_kind,
|
||||||
|
target_value,
|
||||||
|
tolerance,
|
||||||
|
response_priority,
|
||||||
|
count_limit,
|
||||||
|
)?;
|
||||||
|
let response = self.resolver.resolve(&request)?;
|
||||||
|
|
||||||
|
// use page viewer to show result
|
||||||
|
self.op_page_viewer(&response)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn op_page_viewer(&self, response: &Response) -> Result<(), LcrConnError> {
|
||||||
|
let cnt = response.len();
|
||||||
|
if cnt == 0 {
|
||||||
|
println!("Sorry, no result!");
|
||||||
|
println!("Please consider adjusting your requirements and try again.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
const ITEMS_PER_PAGE: usize = 10;
|
||||||
|
let all_page = cnt / ITEMS_PER_PAGE;
|
||||||
|
let mut current_page = 0usize;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// print list
|
||||||
|
for i in 0..ITEMS_PER_PAGE - 1 {
|
||||||
|
// build index and check it
|
||||||
|
let index = current_page * (ITEMS_PER_PAGE - 1) + i;
|
||||||
|
if index >= cnt {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// and print it
|
||||||
|
illustrate_response(response, index)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// print page footer
|
||||||
|
println!();
|
||||||
|
println!("Page {} of {}.", current_page + 1, all_page + 1);
|
||||||
|
println!("f: previous page. b: next page. q: quit this viewer.");
|
||||||
|
// check command
|
||||||
|
match self.accept_command(PageViewerCmd::parse) {
|
||||||
|
PageViewerCmd::PreviousPage => current_page = current_page.saturating_sub(1),
|
||||||
|
PageViewerCmd::NextPage => current_page = all_page.min(current_page + 1),
|
||||||
|
PageViewerCmd::Quit => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Command Utilities
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
/// Accept a command from the user.
|
||||||
|
///
|
||||||
|
/// Loops until a valid command is entered.
|
||||||
|
fn accept_command<T>(&self, parser: impl Fn(&str) -> Option<T>) -> T {
|
||||||
|
loop {
|
||||||
|
self.show_prompt_arrow();
|
||||||
|
let words = read_line();
|
||||||
|
if words.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match parser(&words) {
|
||||||
|
Some(cmd) => return cmd,
|
||||||
|
None => println!("Unknown command, please try again."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accept a count value from the user.
|
||||||
|
fn accept_count_value(&self) -> usize {
|
||||||
|
loop {
|
||||||
|
self.show_prompt_arrow();
|
||||||
|
let words = read_line();
|
||||||
|
if words.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match words.parse::<usize>() {
|
||||||
|
Ok(value) => {
|
||||||
|
if value > MAX_RESPONSE_CNT || value == 0 {
|
||||||
|
println!("Wrong value, please try again.");
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
println!("Wrong value, please try again.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accept a device value from the user.
|
||||||
|
fn accept_device_value(&self) -> f64 {
|
||||||
|
loop {
|
||||||
|
self.show_prompt_arrow();
|
||||||
|
let words = read_line();
|
||||||
|
if words.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = parse_human_readable_value(&words);
|
||||||
|
match value {
|
||||||
|
Some(v) => return v,
|
||||||
|
None => println!("Wrong value, please try again."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accept a tolerance value from the user.
|
||||||
|
///
|
||||||
|
/// The tolerance can be an absolute value (like "2.1k") or a percentage
|
||||||
|
/// relative to the target value (like "19.5%").
|
||||||
|
fn accept_device_value_tolerance(&self, target_value: f64) -> f64 {
|
||||||
|
loop {
|
||||||
|
self.show_prompt_arrow();
|
||||||
|
let words = read_line();
|
||||||
|
if words.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let value: Option<f64> = if let Some(pct_str) = words.strip_suffix('%') {
|
||||||
|
let value = parse_plain_float(pct_str, |x| *x >= 0.0 && *x <= 100.0);
|
||||||
|
value.map(|v| v / 100.0 * target_value)
|
||||||
|
} else {
|
||||||
|
parse_human_readable_value(&words)
|
||||||
|
};
|
||||||
|
|
||||||
|
match value {
|
||||||
|
Some(v) => return v,
|
||||||
|
None => println!("Wrong value, please try again."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_prompt_arrow(&self) {
|
||||||
|
print!("> ");
|
||||||
|
io::stdout().flush().expect("Failed to flush stdout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Entry point
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = Args::parse();
|
||||||
|
let config = AppConfig::from(args);
|
||||||
|
|
||||||
|
let app = match App::new(config) {
|
||||||
|
Ok(app) => app,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(e) = app.run() {
|
||||||
|
eprintln!("Error: {}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,3 +5,5 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
strum = "=0.28.0"
|
||||||
|
strum_macros = "=0.28.0"
|
||||||
|
|||||||
512
kernel/lcrconn/src/common.rs
Normal file
512
kernel/lcrconn/src/common.rs
Normal file
@@ -0,0 +1,512 @@
|
|||||||
|
use strum::IntoEnumIterator;
|
||||||
|
use strum_macros::EnumIter;
|
||||||
|
use thiserror::Error as TeError;
|
||||||
|
|
||||||
|
// /// The error thrown by LCR Connector.
|
||||||
|
// #[derive(Debug, TeError)]
|
||||||
|
// pub enum LcrConnError {
|
||||||
|
// #[error("Device value must be greater than 0")]
|
||||||
|
// InvalidDeviceValue,
|
||||||
|
|
||||||
|
// #[error("Third device cannot exist without second device")]
|
||||||
|
// ThirdDeviceWithoutSecond,
|
||||||
|
|
||||||
|
// #[error("No second device")]
|
||||||
|
// NoSecondDevice,
|
||||||
|
|
||||||
|
// #[error("No third device")]
|
||||||
|
// NoThirdDevice,
|
||||||
|
|
||||||
|
// #[error("Invalid value {0} in dataset")]
|
||||||
|
// InvalidDatasetValue(f64),
|
||||||
|
|
||||||
|
// #[error("Unexpected empty string in dataset item")]
|
||||||
|
// EmptyDatasetItem,
|
||||||
|
|
||||||
|
// #[error("Duplicate item {0} in standard value list")]
|
||||||
|
// DuplicateDatasetItem(String),
|
||||||
|
|
||||||
|
// #[error("Empty standard value list is not allowed")]
|
||||||
|
// EmptyDataset,
|
||||||
|
|
||||||
|
// #[error("Invalid value {0} for target value in request")]
|
||||||
|
// InvalidTargetValue(f64),
|
||||||
|
|
||||||
|
// #[error("Invalid value {0} for tolerance in request")]
|
||||||
|
// InvalidTolerance(f64),
|
||||||
|
|
||||||
|
// #[error("Too large or too less value {0} for response count limit in request")]
|
||||||
|
// InvalidCountLimit(usize),
|
||||||
|
|
||||||
|
// #[error("Invalid human readable value: {0}")]
|
||||||
|
// InvalidHumanReadableValue(String),
|
||||||
|
|
||||||
|
// #[error(transparent)]
|
||||||
|
// Io(#[from] std::io::Error),
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// The kind of device.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum DeviceKind {
|
||||||
|
/// Resistor device.
|
||||||
|
Resistor,
|
||||||
|
/// Capacitor device.
|
||||||
|
Capacitor,
|
||||||
|
/// Inductor device.
|
||||||
|
Inductor,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The joint type between 2 devices.
|
||||||
|
#[derive(Debug, Clone, Copy, EnumIter)]
|
||||||
|
pub enum JointKind {
|
||||||
|
/// Series connection.
|
||||||
|
Series,
|
||||||
|
/// Parallel connection.
|
||||||
|
Parallel,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JointKind {
|
||||||
|
/// Flip the joint kind from series to parallel or vice versa.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The flipped joint kind.
|
||||||
|
pub fn flip(self) -> Self {
|
||||||
|
match self {
|
||||||
|
JointKind::Series => JointKind::Parallel,
|
||||||
|
JointKind::Parallel => JointKind::Series,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The part of circuit composed of two devices and the joint kind.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SubCircuit {
|
||||||
|
/// The value of the device.
|
||||||
|
device_value: f64,
|
||||||
|
/// The joint kind between this device and the next device.
|
||||||
|
joint_kind: JointKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubCircuit {
|
||||||
|
/// Initialize subcircuit with given device value and joint kind.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function will panic if given device value is equal or lower than zero.
|
||||||
|
pub fn new(device_value: f64, joint_kind: JointKind) -> Self {
|
||||||
|
// Make sure value is greater than zero.
|
||||||
|
assert!(
|
||||||
|
device_value > 0f64,
|
||||||
|
"given device value {} should greater than zero",
|
||||||
|
device_value
|
||||||
|
);
|
||||||
|
// Okey, build and return self
|
||||||
|
Self {
|
||||||
|
device_value,
|
||||||
|
joint_kind,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the joint value.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `value` - The value computed from previous devices.
|
||||||
|
/// * `device_kind` - The kind of the device.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The joint value computed.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`LcrConnError::InvalidDeviceValue`] if any device value is not greater than 0.
|
||||||
|
pub fn compute(&self, value: f64, device_kind: DeviceKind) -> Result<f64, LcrConnError> {
|
||||||
|
if self.device_value <= 0.0 || value <= 0.0 {
|
||||||
|
return Err(LcrConnError::InvalidDeviceValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We perform series connect for: series resistor, series inductor and parallel capacitor.
|
||||||
|
// We perform parallel connect for: parallel resistor, parallel inductor and series capacitor.
|
||||||
|
let joint_kind = if device_kind == DeviceKind::Capacitor {
|
||||||
|
self.joint_kind.flip()
|
||||||
|
} else {
|
||||||
|
self.joint_kind
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(match joint_kind {
|
||||||
|
JointKind::Series => self.device_value + value,
|
||||||
|
JointKind::Parallel => (self.device_value * value) / (self.device_value + value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the device value.
|
||||||
|
pub fn device_value(&self) -> f64 {
|
||||||
|
self.device_value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the joint kind.
|
||||||
|
pub fn joint_kind(&self) -> JointKind {
|
||||||
|
self.joint_kind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The scale of devices in the circuit.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum CircuitDeviceScale {
|
||||||
|
/// One device.
|
||||||
|
One,
|
||||||
|
/// Two devices.
|
||||||
|
Two,
|
||||||
|
/// Three devices.
|
||||||
|
Three,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CircuitDeviceScale {
|
||||||
|
/// Convert circuit device scale to device count.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The device count.
|
||||||
|
pub fn to_device_count(self) -> usize {
|
||||||
|
match self {
|
||||||
|
CircuitDeviceScale::One => 1,
|
||||||
|
CircuitDeviceScale::Two => 2,
|
||||||
|
CircuitDeviceScale::Three => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The circuit composed of multiple joints.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Circuit {
|
||||||
|
/// The value of the first device.
|
||||||
|
first_device_value: f64,
|
||||||
|
/// The second device and its joint property.
|
||||||
|
second_device_subckt: Option<SubCircuit>,
|
||||||
|
/// The third device and its joint property.
|
||||||
|
third_device_subckt: Option<SubCircuit>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Circuit {
|
||||||
|
/// Initialize the circuit.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `first_device_value` - The value of the first device.
|
||||||
|
/// * `second_device_subckt` - The second device and its joint property.
|
||||||
|
/// * `third_device_subckt` - The third device and its joint property.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`LcrConnError::ThirdDeviceWithoutSecond`] if a third device is provided
|
||||||
|
/// without a second device.
|
||||||
|
pub fn new(
|
||||||
|
first_device_value: f64,
|
||||||
|
second_device_subckt: Option<SubCircuit>,
|
||||||
|
third_device_subckt: Option<SubCircuit>,
|
||||||
|
) -> Result<Self, LcrConnError> {
|
||||||
|
if second_device_subckt.is_none() && third_device_subckt.is_some() {
|
||||||
|
return Err(LcrConnError::ThirdDeviceWithoutSecond);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
first_device_value,
|
||||||
|
second_device_subckt,
|
||||||
|
third_device_subckt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a circuit from a single device.
|
||||||
|
pub fn from_one_device(device1_value: f64) -> Self {
|
||||||
|
Self {
|
||||||
|
first_device_value: device1_value,
|
||||||
|
second_device_subckt: None,
|
||||||
|
third_device_subckt: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a circuit from two devices.
|
||||||
|
pub fn from_two_devices(
|
||||||
|
device1_value: f64,
|
||||||
|
device2_value: f64,
|
||||||
|
device2_joint: JointKind,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
first_device_value: device1_value,
|
||||||
|
second_device_subckt: Some(SubCircuit::new(device2_value, device2_joint)),
|
||||||
|
third_device_subckt: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a circuit from three devices.
|
||||||
|
pub fn from_three_devices(
|
||||||
|
device1_value: f64,
|
||||||
|
device2_value: f64,
|
||||||
|
device2_joint: JointKind,
|
||||||
|
device3_value: f64,
|
||||||
|
device3_joint: JointKind,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
first_device_value: device1_value,
|
||||||
|
second_device_subckt: Some(SubCircuit::new(device2_value, device2_joint)),
|
||||||
|
third_device_subckt: Some(SubCircuit::new(device3_value, device3_joint)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the circuit value.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `device_kind` - The kind of the device.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The circuit value.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`LcrConnError::InvalidDeviceValue`] if any device value is not greater than 0.
|
||||||
|
pub fn compute(&self, device_kind: DeviceKind) -> Result<f64, LcrConnError> {
|
||||||
|
if self.first_device_value <= 0.0 {
|
||||||
|
return Err(LcrConnError::InvalidDeviceValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut value = self.first_device_value;
|
||||||
|
if let Some(subckt) = &self.second_device_subckt {
|
||||||
|
value = subckt.compute(value, device_kind)?;
|
||||||
|
} else {
|
||||||
|
return Ok(value);
|
||||||
|
}
|
||||||
|
if let Some(subckt) = &self.third_device_subckt {
|
||||||
|
value = subckt.compute(value, device_kind)?;
|
||||||
|
}
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the device scale.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The device scale.
|
||||||
|
pub fn device_scale(&self) -> CircuitDeviceScale {
|
||||||
|
if self.third_device_subckt.is_some() {
|
||||||
|
CircuitDeviceScale::Three
|
||||||
|
} else if self.second_device_subckt.is_some() {
|
||||||
|
CircuitDeviceScale::Two
|
||||||
|
} else {
|
||||||
|
CircuitDeviceScale::One
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the value of the first device.
|
||||||
|
pub fn first_device_value(&self) -> f64 {
|
||||||
|
self.first_device_value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the joint kind of the second device.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`LcrConnError::NoSecondDevice`] if there is no second device.
|
||||||
|
pub fn second_device_joint(&self) -> Result<JointKind, LcrConnError> {
|
||||||
|
self.second_device_subckt
|
||||||
|
.map(|s| s.joint_kind())
|
||||||
|
.ok_or(LcrConnError::NoSecondDevice)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the value of the second device.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`LcrConnError::NoSecondDevice`] if there is no second device.
|
||||||
|
pub fn second_device_value(&self) -> Result<f64, LcrConnError> {
|
||||||
|
self.second_device_subckt
|
||||||
|
.map(|s| s.device_value())
|
||||||
|
.ok_or(LcrConnError::NoSecondDevice)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the joint kind of the third device.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`LcrConnError::NoThirdDevice`] if there is no third device.
|
||||||
|
pub fn third_device_joint(&self) -> Result<JointKind, LcrConnError> {
|
||||||
|
self.third_device_subckt
|
||||||
|
.map(|s| s.joint_kind())
|
||||||
|
.ok_or(LcrConnError::NoThirdDevice)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the value of the third device.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`LcrConnError::NoThirdDevice`] if there is no third device.
|
||||||
|
pub fn third_device_value(&self) -> Result<f64, LcrConnError> {
|
||||||
|
self.third_device_subckt
|
||||||
|
.map(|s| s.device_value())
|
||||||
|
.ok_or(LcrConnError::NoThirdDevice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The helper for circuit value computation.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct CircuitValueTrait {
|
||||||
|
/// The kind of the device.
|
||||||
|
device_kind: DeviceKind,
|
||||||
|
/// The target value.
|
||||||
|
target_value: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CircuitValueTrait {
|
||||||
|
pub fn new(device_kind: DeviceKind, target_value: f64) -> Self {
|
||||||
|
Self {
|
||||||
|
device_kind,
|
||||||
|
target_value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The value of this circuit.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `circuit` - The circuit for computation.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The value.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`Circuit::compute`].
|
||||||
|
pub fn value(&self, circuit: &Circuit) -> Result<f64, LcrConnError> {
|
||||||
|
circuit.compute(self.device_kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The signed difference between the target value and the value of this circuit.
|
||||||
|
///
|
||||||
|
/// Positive value indicates that the value of this circuit is greater than the target value.
|
||||||
|
/// Negative value indicates that the value of this circuit is less than the target value.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `circuit` - The circuit for computation.
|
||||||
|
/// * `value` - The value of the circuit computed by the [`value`](Self::value) method
|
||||||
|
/// for reducing computation steps, or `None` if you request this method to compute the value.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The signed difference.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`Circuit::compute`].
|
||||||
|
pub fn difference(&self, circuit: &Circuit, value: Option<f64>) -> Result<f64, LcrConnError> {
|
||||||
|
let value = match value {
|
||||||
|
Some(v) => v,
|
||||||
|
None => self.value(circuit)?,
|
||||||
|
};
|
||||||
|
Ok(value - self.target_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The unsigned difference between the target value and the value of this circuit.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `circuit` - The circuit for computation.
|
||||||
|
/// * `value` - The value of the circuit computed by the [`value`](Self::value) method
|
||||||
|
/// for reducing computation steps, or `None` if you request this method to compute the value.
|
||||||
|
/// * `difference` - The difference of the circuit computed by the
|
||||||
|
/// [`difference`](Self::difference) method for reducing computation steps,
|
||||||
|
/// or `None` if you request this method to compute the difference.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The unsigned difference.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`Circuit::compute`].
|
||||||
|
pub fn unsigned_difference(
|
||||||
|
&self,
|
||||||
|
circuit: &Circuit,
|
||||||
|
value: Option<f64>,
|
||||||
|
difference: Option<f64>,
|
||||||
|
) -> Result<f64, LcrConnError> {
|
||||||
|
let diff = match difference {
|
||||||
|
Some(d) => d,
|
||||||
|
None => self.difference(circuit, value)?,
|
||||||
|
};
|
||||||
|
Ok(diff.abs())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The signed relative difference between the target value and the value of this circuit.
|
||||||
|
///
|
||||||
|
/// Positive value indicates that the value of this circuit is greater than the target value.
|
||||||
|
/// Negative value indicates that the value of this circuit is less than the target value.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `circuit` - The circuit for computation.
|
||||||
|
/// * `value` - The value of the circuit computed by the [`value`](Self::value) method
|
||||||
|
/// for reducing computation steps, or `None` if you request this method to compute the value.
|
||||||
|
/// * `difference` - The difference of the circuit computed by the
|
||||||
|
/// [`difference`](Self::difference) method for reducing computation steps,
|
||||||
|
/// or `None` if you request this method to compute the difference.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The signed relative difference.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`Circuit::compute`].
|
||||||
|
pub fn relative_difference(
|
||||||
|
&self,
|
||||||
|
circuit: &Circuit,
|
||||||
|
value: Option<f64>,
|
||||||
|
difference: Option<f64>,
|
||||||
|
) -> Result<f64, LcrConnError> {
|
||||||
|
let diff = match difference {
|
||||||
|
Some(d) => d,
|
||||||
|
None => self.difference(circuit, value)?,
|
||||||
|
};
|
||||||
|
Ok(diff / self.target_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The unsigned relative difference between the target value and the value of this circuit.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `circuit` - The circuit for computation.
|
||||||
|
/// * `value` - The value of the circuit computed by the [`value`](Self::value) method
|
||||||
|
/// for reducing computation steps, or `None` if you request this method to compute the value.
|
||||||
|
/// * `difference` - The difference of the circuit computed by the
|
||||||
|
/// [`difference`](Self::difference) method for reducing computation steps,
|
||||||
|
/// or `None` if you request this method to compute the difference.
|
||||||
|
/// * `relative_difference` - The relative difference of the circuit computed by the
|
||||||
|
/// [`relative_difference`](Self::relative_difference) method for reducing computation steps,
|
||||||
|
/// or `None` if you request this method to compute the relative difference.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The unsigned relative difference.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`Circuit::compute`].
|
||||||
|
pub fn unsigned_relative_difference(
|
||||||
|
&self,
|
||||||
|
circuit: &Circuit,
|
||||||
|
value: Option<f64>,
|
||||||
|
difference: Option<f64>,
|
||||||
|
relative_difference: Option<f64>,
|
||||||
|
) -> Result<f64, LcrConnError> {
|
||||||
|
let rel_diff = match relative_difference {
|
||||||
|
Some(rd) => rd,
|
||||||
|
None => self.relative_difference(circuit, value, difference)?,
|
||||||
|
};
|
||||||
|
Ok(rel_diff.abs())
|
||||||
|
}
|
||||||
|
}
|
||||||
445
kernel/lcrconn/src/dataset.rs
Normal file
445
kernel/lcrconn/src/dataset.rs
Normal file
@@ -0,0 +1,445 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use crate::common::LcrConnError;
|
||||||
|
|
||||||
|
/// An item in the dataset.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct DatasetItem {
|
||||||
|
/// The actual value of this item.
|
||||||
|
pub value: f64,
|
||||||
|
/// The string form of this value given from original input for re-saving.
|
||||||
|
pub str_value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DatasetItem {
|
||||||
|
/// Create a new dataset item with validation.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`LcrConnError::InvalidDatasetValue`] if the value is not greater than 0.
|
||||||
|
/// Returns [`LcrConnError::EmptyDatasetItem`] if the string value is empty.
|
||||||
|
pub fn new(value: f64, str_value: String) -> Result<Self, LcrConnError> {
|
||||||
|
if value <= 0.0 {
|
||||||
|
return Err(LcrConnError::InvalidDatasetValue(value));
|
||||||
|
}
|
||||||
|
if str_value.is_empty() {
|
||||||
|
return Err(LcrConnError::EmptyDatasetItem);
|
||||||
|
}
|
||||||
|
Ok(Self { value, str_value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A list holding available standard values for resistor, capacitor or inductor.
|
||||||
|
///
|
||||||
|
/// Standard values is a collection of all possible values of specific device manufactured
|
||||||
|
/// by electronic factory. In reality, it also can be replaced by all possible values of
|
||||||
|
/// specific device provided by your laboratory. For example, your laboratory only provide
|
||||||
|
/// resistor with 100 Ohm and 4.7k Ohm. This list will only contain 100 and 4.7k.
|
||||||
|
pub struct Dataset {
|
||||||
|
/// A list of available device gauge values.
|
||||||
|
items: Vec<DatasetItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dataset {
|
||||||
|
/// Create a dataset from an iterable of stringified values.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`LcrConnError::DuplicateDatasetItem`] if duplicate values are found.
|
||||||
|
/// Returns [`LcrConnError::EmptyDataset`] if the iterable produces no items.
|
||||||
|
/// Returns [`LcrConnError::InvalidHumanReadableValue`] if a value cannot be parsed.
|
||||||
|
pub fn from_iterable<I, S>(str_values: I) -> Result<Self, LcrConnError>
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = S>,
|
||||||
|
S: Into<String>,
|
||||||
|
{
|
||||||
|
// Check string form value one by one
|
||||||
|
let mut items: Vec<DatasetItem> = Vec::new();
|
||||||
|
let mut seen: HashSet<f64> = HashSet::new();
|
||||||
|
|
||||||
|
for str_value_raw in str_values {
|
||||||
|
let str_value = str_value_raw.into();
|
||||||
|
// Try parsing value
|
||||||
|
let value = from_human_readable_value(&str_value)?;
|
||||||
|
// Check and update set
|
||||||
|
if !seen.insert(value) {
|
||||||
|
return Err(LcrConnError::DuplicateDatasetItem(str_value));
|
||||||
|
}
|
||||||
|
// Add into result
|
||||||
|
items.push(DatasetItem::new(value, str_value)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check empty case
|
||||||
|
if items.is_empty() {
|
||||||
|
return Err(LcrConnError::EmptyDataset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ok, assign it
|
||||||
|
Ok(Self { items })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a dataset from a block of text.
|
||||||
|
///
|
||||||
|
/// Each non-empty line (after trimming whitespace) is treated as a value.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`Dataset::from_iterable`].
|
||||||
|
pub fn from_text(text: &str) -> Result<Self, LcrConnError> {
|
||||||
|
let lines: Vec<String> = text
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.trim().to_string())
|
||||||
|
.filter(|line| !line.is_empty())
|
||||||
|
.collect();
|
||||||
|
Self::from_iterable(lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a dataset from a file.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`LcrConnError::Io`] if the file cannot be read.
|
||||||
|
/// See [`Dataset::from_iterable`] for other errors.
|
||||||
|
pub fn from_file(path: impl AsRef<Path>) -> Result<Self, LcrConnError> {
|
||||||
|
let text = std::fs::read_to_string(path)?;
|
||||||
|
Self::from_text(&text)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The preset dataset for resistors.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`Dataset::from_iterable`].
|
||||||
|
pub fn resistor_preset() -> Result<Self, LcrConnError> {
|
||||||
|
Self::from_iterable([
|
||||||
|
"100", "220", "270", "390", "470", "680", "1k", "1.2k", "1.5k", "2.2k", "3.3k",
|
||||||
|
"4.7k", "6.8k", "10k", "47k", "100k", "1M",
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The preset dataset for capacitors.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`Dataset::from_iterable`].
|
||||||
|
pub fn capacitor_preset() -> Result<Self, LcrConnError> {
|
||||||
|
Self::from_iterable([
|
||||||
|
"10p", "22p", "33p", "47p", "68p", "100p", "150p", "220p", "330p", "470p", "560p",
|
||||||
|
"1u", "2.2u", "3.3u", "4.7u", "10u", "22u", "47u", "100u", "220u", "470u",
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The preset dataset for inductors.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`Dataset::from_iterable`].
|
||||||
|
pub fn inductor_preset() -> Result<Self, LcrConnError> {
|
||||||
|
Self::from_iterable([
|
||||||
|
"0.1u", "0.15u", "0.47u", "0.68u", "1u", "1.5u", "2.2u", "3.3u", "4.7u", "6.8u",
|
||||||
|
"8.2u", "10u", "15u", "22u", "33u", "47u", "68u", "100u",
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the string form of all values joined by newlines.
|
||||||
|
pub fn save_text(&self) -> String {
|
||||||
|
self.items
|
||||||
|
.iter()
|
||||||
|
.map(|i| i.str_value.as_str())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save all values to a file.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`LcrConnError::Io`] if the file cannot be written.
|
||||||
|
pub fn save_file(&self, path: impl AsRef<Path>) -> Result<(), LcrConnError> {
|
||||||
|
std::fs::write(path, self.save_text())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the available standard values as an iterator of `f64`.
|
||||||
|
pub fn values(&self) -> impl Iterator<Item = f64> + '_ {
|
||||||
|
self.items.iter().map(|i| i.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the underlying dataset items as a slice.
|
||||||
|
pub fn items(&self) -> &[DatasetItem] {
|
||||||
|
&self.items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The collection holding all standard values for resistor, capacitor and inductor respectively.
|
||||||
|
pub struct DatasetCollection {
|
||||||
|
/// A list of available device gauge values for resistor.
|
||||||
|
resistor: Dataset,
|
||||||
|
/// A list of available device gauge values for capacitor.
|
||||||
|
capacitor: Dataset,
|
||||||
|
/// A list of available device gauge values for inductor.
|
||||||
|
inductor: Dataset,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DatasetCollection {
|
||||||
|
pub fn new(resistor: Dataset, capacitor: Dataset, inductor: Dataset) -> Self {
|
||||||
|
Self {
|
||||||
|
resistor,
|
||||||
|
capacitor,
|
||||||
|
inductor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load the standard values for resistor, capacitor and inductor respectively from iterables.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `resistor` - The iterable to load available standard values for resistor.
|
||||||
|
/// * `capacitor` - The iterable to load available standard values for capacitor.
|
||||||
|
/// * `inductor` - The iterable to load available standard values for inductor.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`Dataset::from_iterable`].
|
||||||
|
pub fn from_iterable<I1, S1, I2, S2, I3, S3>(
|
||||||
|
resistor: I1,
|
||||||
|
capacitor: I2,
|
||||||
|
inductor: I3,
|
||||||
|
) -> Result<Self, LcrConnError>
|
||||||
|
where
|
||||||
|
I1: IntoIterator<Item = S1>,
|
||||||
|
S1: Into<String>,
|
||||||
|
I2: IntoIterator<Item = S2>,
|
||||||
|
S2: Into<String>,
|
||||||
|
I3: IntoIterator<Item = S3>,
|
||||||
|
S3: Into<String>,
|
||||||
|
{
|
||||||
|
Ok(Self {
|
||||||
|
resistor: Dataset::from_iterable(resistor)?,
|
||||||
|
capacitor: Dataset::from_iterable(capacitor)?,
|
||||||
|
inductor: Dataset::from_iterable(inductor)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load the standard values from strings.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `resistor` - The string to load available standard values for resistor.
|
||||||
|
/// * `capacitor` - The string to load available standard values for capacitor.
|
||||||
|
/// * `inductor` - The string to load available standard values for inductor.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`Dataset::from_text`].
|
||||||
|
pub fn from_text(resistor: &str, capacitor: &str, inductor: &str) -> Result<Self, LcrConnError> {
|
||||||
|
Ok(Self {
|
||||||
|
resistor: Dataset::from_text(resistor)?,
|
||||||
|
capacitor: Dataset::from_text(capacitor)?,
|
||||||
|
inductor: Dataset::from_text(inductor)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load the standard values from files.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `resistor` - The file to load available standard values for resistor.
|
||||||
|
/// * `capacitor` - The file to load available standard values for capacitor.
|
||||||
|
/// * `inductor` - The file to load available standard values for inductor.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`Dataset::from_file`].
|
||||||
|
pub fn from_file(
|
||||||
|
resistor: impl AsRef<Path>,
|
||||||
|
capacitor: impl AsRef<Path>,
|
||||||
|
inductor: impl AsRef<Path>,
|
||||||
|
) -> Result<Self, LcrConnError> {
|
||||||
|
Ok(Self {
|
||||||
|
resistor: Dataset::from_file(resistor)?,
|
||||||
|
capacitor: Dataset::from_file(capacitor)?,
|
||||||
|
inductor: Dataset::from_file(inductor)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The preset dataset collection for all devices.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`Dataset::from_iterable`].
|
||||||
|
pub fn devices_preset() -> Result<Self, LcrConnError> {
|
||||||
|
Ok(Self {
|
||||||
|
resistor: Dataset::resistor_preset()?,
|
||||||
|
capacitor: Dataset::capacitor_preset()?,
|
||||||
|
inductor: Dataset::inductor_preset()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the string form of all values.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// A tuple of strings for resistor, capacitor and inductor respectively.
|
||||||
|
pub fn save_text(&self) -> (String, String, String) {
|
||||||
|
(
|
||||||
|
self.resistor.save_text(),
|
||||||
|
self.capacitor.save_text(),
|
||||||
|
self.inductor.save_text(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Save all values to files.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `resistor` - The file to save available standard values for resistor.
|
||||||
|
/// * `capacitor` - The file to save available standard values for capacitor.
|
||||||
|
/// * `inductor` - The file to save available standard values for inductor.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`LcrConnError::Io`] if any file cannot be written.
|
||||||
|
pub fn save_file(
|
||||||
|
&self,
|
||||||
|
resistor: impl AsRef<Path>,
|
||||||
|
capacitor: impl AsRef<Path>,
|
||||||
|
inductor: impl AsRef<Path>,
|
||||||
|
) -> Result<(), LcrConnError> {
|
||||||
|
self.resistor.save_file(resistor)?;
|
||||||
|
self.capacitor.save_file(capacitor)?;
|
||||||
|
self.inductor.save_file(inductor)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the dataset for resistor.
|
||||||
|
pub fn resistor(&self) -> &Dataset {
|
||||||
|
&self.resistor
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the dataset for capacitor.
|
||||||
|
pub fn capacitor(&self) -> &Dataset {
|
||||||
|
&self.capacitor
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the dataset for inductor.
|
||||||
|
pub fn inductor(&self) -> &Dataset {
|
||||||
|
&self.inductor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert human readable value to float.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `strl` - The human readable value.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The parsed float value.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`LcrConnError::InvalidHumanReadableValue`] if the input string is not a valid number.
|
||||||
|
pub fn from_human_readable_value(strl: &str) -> Result<f64, LcrConnError> {
|
||||||
|
let strl = strl.trim();
|
||||||
|
|
||||||
|
let (num_part, multiplier) = if let Some(stripped) = strl.strip_suffix('n') {
|
||||||
|
(stripped, 1e-12)
|
||||||
|
} else if let Some(stripped) = strl.strip_suffix('p') {
|
||||||
|
(stripped, 1e-9)
|
||||||
|
} else if let Some(stripped) = strl.strip_suffix('u') {
|
||||||
|
(stripped, 1e-6)
|
||||||
|
} else if let Some(stripped) = strl.strip_suffix('m') {
|
||||||
|
(stripped, 1e-3)
|
||||||
|
} else if let Some(stripped) = strl.strip_suffix('k') {
|
||||||
|
(stripped, 1e3)
|
||||||
|
} else if let Some(stripped) = strl.strip_suffix('M') {
|
||||||
|
(stripped, 1e6)
|
||||||
|
} else if let Some(stripped) = strl.strip_suffix('G') {
|
||||||
|
(stripped, 1e9)
|
||||||
|
} else {
|
||||||
|
(strl, 1.0)
|
||||||
|
};
|
||||||
|
|
||||||
|
num_part
|
||||||
|
.parse::<f64>()
|
||||||
|
.map(|v| v * multiplier)
|
||||||
|
.map_err(|_| LcrConnError::InvalidHumanReadableValue(strl.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The unit scale for human readable value.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum UnitScale {
|
||||||
|
NanoLower,
|
||||||
|
Nano,
|
||||||
|
Micro,
|
||||||
|
Milli,
|
||||||
|
None,
|
||||||
|
Kilo,
|
||||||
|
Mega,
|
||||||
|
Giga,
|
||||||
|
GigaHigher,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the unit scale of human readable value.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `v` - The value.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The unit scale.
|
||||||
|
pub fn get_human_readable_value_scale(v: f64) -> UnitScale {
|
||||||
|
let v = v.abs();
|
||||||
|
if v < 1e-12 {
|
||||||
|
UnitScale::NanoLower
|
||||||
|
} else if v < 1e-9 {
|
||||||
|
UnitScale::Nano
|
||||||
|
} else if v < 1e-6 {
|
||||||
|
UnitScale::Micro
|
||||||
|
} else if v < 1e-3 {
|
||||||
|
UnitScale::Milli
|
||||||
|
} else if v < 1e3 {
|
||||||
|
UnitScale::None
|
||||||
|
} else if v < 1e6 {
|
||||||
|
UnitScale::Kilo
|
||||||
|
} else if v < 1e9 {
|
||||||
|
UnitScale::Mega
|
||||||
|
} else if v < 1e12 {
|
||||||
|
UnitScale::Giga
|
||||||
|
} else {
|
||||||
|
UnitScale::GigaHigher
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert float value to human readable value.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `v` - The float value.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The human readable value.
|
||||||
|
pub fn to_human_readable_value(v: f64) -> String {
|
||||||
|
let scale = get_human_readable_value_scale(v);
|
||||||
|
match scale {
|
||||||
|
UnitScale::NanoLower => format!("{:+.4e} n", v / 1e-12),
|
||||||
|
UnitScale::Nano => format!("{:+.4f} p", v / 1e-9),
|
||||||
|
UnitScale::Micro => format!("{:+.4f} u", v / 1e-6),
|
||||||
|
UnitScale::Milli => format!("{:+.4f} m", v / 1e-3),
|
||||||
|
UnitScale::None => {
|
||||||
|
// YYC MARK:
|
||||||
|
// The space of this format string is by design
|
||||||
|
// for keeping the same style with other format strings.
|
||||||
|
format!("{:+.4} ", v)
|
||||||
|
}
|
||||||
|
UnitScale::Kilo => format!("{:+.4f} k", v / 1e3),
|
||||||
|
UnitScale::Mega => format!("{:+.4f} M", v / 1e6),
|
||||||
|
UnitScale::Giga => format!("{:+.4f} G", v / 1e9),
|
||||||
|
UnitScale::GigaHigher => format!("{:+.4e} G", v / 1e9),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
pub fn add(left: u64, right: u64) -> u64 {
|
pub mod common;
|
||||||
left + right
|
pub mod dataset;
|
||||||
}
|
pub mod query;
|
||||||
|
pub mod resolver;
|
||||||
|
|
||||||
#[cfg(test)]
|
pub use common::{
|
||||||
mod tests {
|
Circuit, CircuitDeviceScale, CircuitValueTrait, DeviceKind, JointKind, LcrConnError, SubCircuit,
|
||||||
use super::*;
|
};
|
||||||
|
pub use dataset::{
|
||||||
#[test]
|
from_human_readable_value, get_human_readable_value_scale, to_human_readable_value, Dataset,
|
||||||
fn it_works() {
|
DatasetCollection, DatasetItem, UnitScale,
|
||||||
let result = add(2, 2);
|
};
|
||||||
assert_eq!(result, 4);
|
pub use query::{Request, Response, ResponseItem, ResponsePriority, MAX_RESPONSE_CNT};
|
||||||
}
|
pub use resolver::{BfsResolver, LutResolver, Resolver};
|
||||||
}
|
|
||||||
|
|||||||
256
kernel/lcrconn/src/query.rs
Normal file
256
kernel/lcrconn/src/query.rs
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::ops::Index;
|
||||||
|
|
||||||
|
use crate::common::{Circuit, CircuitValueTrait, DeviceKind, LcrConnError};
|
||||||
|
|
||||||
|
/// The priority of the result.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ResponsePriority {
|
||||||
|
/// Less devices is the first priority.
|
||||||
|
LessDevices,
|
||||||
|
/// More accuracy is the first priority.
|
||||||
|
MoreAccuracy,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The maximum count for the response item count passed in request.
|
||||||
|
pub const MAX_RESPONSE_CNT: usize = 50;
|
||||||
|
|
||||||
|
/// All request information for the resolver.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Request {
|
||||||
|
/// The kind of device to resolve.
|
||||||
|
pub device_kind: DeviceKind,
|
||||||
|
/// The target value of the device.
|
||||||
|
pub target_value: f64,
|
||||||
|
/// The tolerance of the device in absolute value.
|
||||||
|
pub tolerance: f64,
|
||||||
|
/// The priority principle when sorting response items.
|
||||||
|
pub response_priority: ResponsePriority,
|
||||||
|
/// The limited count of results.
|
||||||
|
pub count_limit: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Request {
|
||||||
|
/// Create a new request with validation.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`LcrConnError::InvalidTargetValue`] if the target value is not greater than 0.
|
||||||
|
/// Returns [`LcrConnError::InvalidTolerance`] if the tolerance is negative.
|
||||||
|
/// Returns [`LcrConnError::InvalidCountLimit`] if the count limit is 0 or exceeds
|
||||||
|
/// [`MAX_RESPONSE_CNT`].
|
||||||
|
pub fn new(
|
||||||
|
device_kind: DeviceKind,
|
||||||
|
target_value: f64,
|
||||||
|
tolerance: f64,
|
||||||
|
response_priority: ResponsePriority,
|
||||||
|
count_limit: usize,
|
||||||
|
) -> Result<Self, LcrConnError> {
|
||||||
|
if target_value <= 0.0 {
|
||||||
|
return Err(LcrConnError::InvalidTargetValue(target_value));
|
||||||
|
}
|
||||||
|
if tolerance < 0.0 {
|
||||||
|
return Err(LcrConnError::InvalidTolerance(tolerance));
|
||||||
|
}
|
||||||
|
if count_limit == 0 || count_limit > MAX_RESPONSE_CNT {
|
||||||
|
return Err(LcrConnError::InvalidCountLimit(count_limit));
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
device_kind,
|
||||||
|
target_value,
|
||||||
|
tolerance,
|
||||||
|
response_priority,
|
||||||
|
count_limit,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The possible solution given by the resolver.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ResponseItem {
|
||||||
|
/// The circuit of this response item.
|
||||||
|
circuit: Circuit,
|
||||||
|
/// The device count of this circuit.
|
||||||
|
device_count: usize,
|
||||||
|
/// The value of this circuit.
|
||||||
|
value: f64,
|
||||||
|
/// The signed difference between the target value and the value of this circuit.
|
||||||
|
///
|
||||||
|
/// Positive value indicates that the value of this circuit is greater than the target value.
|
||||||
|
/// Negative value indicates that the value of this circuit is less than the target value.
|
||||||
|
difference: f64,
|
||||||
|
/// The unsigned difference between the target value and the value of this circuit.
|
||||||
|
unsigned_difference: f64,
|
||||||
|
/// The signed relative difference between the target value and the value of this circuit.
|
||||||
|
///
|
||||||
|
/// Positive value indicates that the value of this circuit is greater than the target value.
|
||||||
|
/// Negative value indicates that the value of this circuit is less than the target value.
|
||||||
|
relative_difference: f64,
|
||||||
|
/// The unsigned relative difference between the target value and the value of this circuit.
|
||||||
|
unsigned_relative_difference: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseItem {
|
||||||
|
/// Create a new response item by computing all values eagerly.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`CircuitValueTrait::value`].
|
||||||
|
pub fn new(circuit: Circuit, cv_trait: &CircuitValueTrait) -> Result<Self, LcrConnError> {
|
||||||
|
let value = cv_trait.value(&circuit)?;
|
||||||
|
let difference = cv_trait.difference(&circuit, Some(value))?;
|
||||||
|
let unsigned_difference = cv_trait.unsigned_difference(&circuit, None, Some(difference))?;
|
||||||
|
let relative_difference = cv_trait.relative_difference(&circuit, None, Some(difference))?;
|
||||||
|
let unsigned_relative_difference =
|
||||||
|
cv_trait.unsigned_relative_difference(&circuit, None, None, Some(relative_difference))?;
|
||||||
|
let device_count = circuit.device_scale().to_device_count();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
circuit,
|
||||||
|
device_count,
|
||||||
|
value,
|
||||||
|
difference,
|
||||||
|
unsigned_difference,
|
||||||
|
relative_difference,
|
||||||
|
unsigned_relative_difference,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The circuit of this response item.
|
||||||
|
pub fn circuit(&self) -> &Circuit {
|
||||||
|
&self.circuit
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The device count of this circuit.
|
||||||
|
pub fn device_count(&self) -> usize {
|
||||||
|
self.device_count
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The value of this circuit.
|
||||||
|
pub fn value(&self) -> f64 {
|
||||||
|
self.value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The signed difference between the target value and the value of this circuit.
|
||||||
|
///
|
||||||
|
/// Positive value indicates that the value of this circuit is greater than the target value.
|
||||||
|
/// Negative value indicates that the value of this circuit is less than the target value.
|
||||||
|
pub fn difference(&self) -> f64 {
|
||||||
|
self.difference
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The unsigned difference between the target value and the value of this circuit.
|
||||||
|
pub fn unsigned_difference(&self) -> f64 {
|
||||||
|
self.unsigned_difference
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The signed relative difference between the target value and the value of this circuit.
|
||||||
|
///
|
||||||
|
/// Positive value indicates that the value of this circuit is greater than the target value.
|
||||||
|
/// Negative value indicates that the value of this circuit is less than the target value.
|
||||||
|
pub fn relative_difference(&self) -> f64 {
|
||||||
|
self.relative_difference
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The unsigned relative difference between the target value and the value of this circuit.
|
||||||
|
pub fn unsigned_relative_difference(&self) -> f64 {
|
||||||
|
self.unsigned_relative_difference
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The collection of possible solutions given by the resolver.
|
||||||
|
///
|
||||||
|
/// For getting the response items, please use `response[index]` or `response.get(index)`.
|
||||||
|
/// For iterating the response items, please use the `into_iter()` method.
|
||||||
|
/// For getting the count of response items, please use the `len()` method.
|
||||||
|
pub struct Response {
|
||||||
|
/// The kind of device of this response.
|
||||||
|
device_kind: DeviceKind,
|
||||||
|
/// The sorted items by priority and difference.
|
||||||
|
sorted_items: Vec<ResponseItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Response {
|
||||||
|
/// Create a new response from request and candidate circuits.
|
||||||
|
///
|
||||||
|
/// The candidates are sorted by the priority specified in the request and then truncated
|
||||||
|
/// to the count limit.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`ResponseItem::new`].
|
||||||
|
pub fn new(
|
||||||
|
request: &Request,
|
||||||
|
candidates: impl IntoIterator<Item = Circuit>,
|
||||||
|
) -> Result<Self, LcrConnError> {
|
||||||
|
let cv_trait = CircuitValueTrait::new(request.device_kind, request.target_value);
|
||||||
|
|
||||||
|
let mut items: Vec<ResponseItem> = candidates
|
||||||
|
.into_iter()
|
||||||
|
.map(|c| ResponseItem::new(c, &cv_trait))
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
|
||||||
|
// Sort by different strategy
|
||||||
|
match request.response_priority {
|
||||||
|
ResponsePriority::LessDevices => {
|
||||||
|
items.sort_by(|a, b| {
|
||||||
|
a.device_count
|
||||||
|
.cmp(&b.device_count)
|
||||||
|
.then_with(|| {
|
||||||
|
a.unsigned_difference
|
||||||
|
.partial_cmp(&b.unsigned_difference)
|
||||||
|
.unwrap_or(Ordering::Equal)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ResponsePriority::MoreAccuracy => {
|
||||||
|
items.sort_by(|a, b| {
|
||||||
|
a.unsigned_difference
|
||||||
|
.partial_cmp(&b.unsigned_difference)
|
||||||
|
.unwrap_or(Ordering::Equal)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cut item by limit
|
||||||
|
items.truncate(request.count_limit);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
device_kind: request.device_kind,
|
||||||
|
sorted_items: items,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The kind of device of this response.
|
||||||
|
pub fn device_kind(&self) -> DeviceKind {
|
||||||
|
self.device_kind
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The number of response items.
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.sorted_items.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the response is empty.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.sorted_items.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a response item by index.
|
||||||
|
pub fn get(&self, index: usize) -> Option<&ResponseItem> {
|
||||||
|
self.sorted_items.get(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over response items by reference.
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = &ResponseItem> {
|
||||||
|
self.sorted_items.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<usize> for Response {
|
||||||
|
type Output = ResponseItem;
|
||||||
|
|
||||||
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
&self.sorted_items[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
481
kernel/lcrconn/src/resolver/bfs.rs
Normal file
481
kernel/lcrconn/src/resolver/bfs.rs
Normal file
@@ -0,0 +1,481 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::collections::BinaryHeap;
|
||||||
|
use std::iter::FusedIterator;
|
||||||
|
|
||||||
|
use super::Resolver;
|
||||||
|
use crate::common::{Circuit, CircuitValueTrait, DeviceKind, JointKind, LcrConnError};
|
||||||
|
use crate::dataset::{Dataset, DatasetCollection, DatasetItem};
|
||||||
|
use crate::query::{Request, Response};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Lazy iterator structs for circuit generation
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// YYC MARK:
|
||||||
|
// Some circuit are equivalent in topology.
|
||||||
|
// If we deduplicate these equaivalent circuit in building result,
|
||||||
|
// there are too complex works.
|
||||||
|
// So we should deduplicated these equivalent circuit at the beginning,
|
||||||
|
// i.e. when generating them.
|
||||||
|
// So following iterator structs are taking this job.
|
||||||
|
|
||||||
|
/// Iterator over all possible one-device circuits without repeating equivalent topology.
|
||||||
|
pub struct OneDeviceCircuitIter<'a> {
|
||||||
|
items: &'a [DatasetItem],
|
||||||
|
pos: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> OneDeviceCircuitIter<'a> {
|
||||||
|
pub fn new(items: &'a [DatasetItem]) -> Self {
|
||||||
|
Self { items, pos: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for OneDeviceCircuitIter<'_> {
|
||||||
|
type Item = Circuit;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.pos < self.items.len() {
|
||||||
|
// Every single device is unique so we directly output them.
|
||||||
|
// This feature is insured by dataset itself.
|
||||||
|
let circuit = Circuit::from_one_device(self.items[self.pos].value);
|
||||||
|
self.pos += 1;
|
||||||
|
Some(circuit)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FusedIterator for OneDeviceCircuitIter<'_> {}
|
||||||
|
|
||||||
|
/// Iterator over all possible two-device circuits without repeating equivalent topology.
|
||||||
|
pub struct TwoDeviceCircuitIter<'a> {
|
||||||
|
items: &'a [DatasetItem],
|
||||||
|
i: usize,
|
||||||
|
j: usize,
|
||||||
|
joint_idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TwoDeviceCircuitIter<'a> {
|
||||||
|
pub fn new(items: &'a [DatasetItem]) -> Self {
|
||||||
|
Self {
|
||||||
|
items,
|
||||||
|
i: 0,
|
||||||
|
j: 0,
|
||||||
|
joint_idx: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for TwoDeviceCircuitIter<'_> {
|
||||||
|
type Item = Circuit;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let n = self.items.len();
|
||||||
|
if n == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if self.joint_idx < JointKind::ALL.len() {
|
||||||
|
let jk = JointKind::ALL[self.joint_idx];
|
||||||
|
self.joint_idx += 1;
|
||||||
|
// The two devices in this circuit is always swapable,
|
||||||
|
// so we iterate them without repeating.
|
||||||
|
return Some(Circuit::from_two_devices(
|
||||||
|
self.items[self.i].value,
|
||||||
|
self.items[self.j].value,
|
||||||
|
jk,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advance to next combination
|
||||||
|
self.joint_idx = 0;
|
||||||
|
self.j += 1;
|
||||||
|
if self.j >= n {
|
||||||
|
self.i += 1;
|
||||||
|
self.j = self.i;
|
||||||
|
if self.i >= n {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FusedIterator for TwoDeviceCircuitIter<'_> {}
|
||||||
|
|
||||||
|
/// Iterator over three-device circuits where both joints share the same type.
|
||||||
|
///
|
||||||
|
/// In this case, all 3 devices are swapable and are iterated without repeating.
|
||||||
|
pub struct ThreeDeviceSameJointIter<'a> {
|
||||||
|
items: &'a [DatasetItem],
|
||||||
|
i: usize,
|
||||||
|
j: usize,
|
||||||
|
k: usize,
|
||||||
|
joint_idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ThreeDeviceSameJointIter<'a> {
|
||||||
|
pub fn new(items: &'a [DatasetItem]) -> Self {
|
||||||
|
Self {
|
||||||
|
items,
|
||||||
|
i: 0,
|
||||||
|
j: 0,
|
||||||
|
k: 0,
|
||||||
|
joint_idx: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for ThreeDeviceSameJointIter<'_> {
|
||||||
|
type Item = Circuit;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let n = self.items.len();
|
||||||
|
if n == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if self.joint_idx < JointKind::ALL.len() {
|
||||||
|
let jk = JointKind::ALL[self.joint_idx];
|
||||||
|
self.joint_idx += 1;
|
||||||
|
return Some(Circuit::from_three_devices(
|
||||||
|
self.items[self.i].value,
|
||||||
|
self.items[self.j].value,
|
||||||
|
jk,
|
||||||
|
self.items[self.k].value,
|
||||||
|
jk,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.joint_idx = 0;
|
||||||
|
self.k += 1;
|
||||||
|
if self.k >= n {
|
||||||
|
self.j += 1;
|
||||||
|
self.k = self.j;
|
||||||
|
if self.j >= n {
|
||||||
|
self.i += 1;
|
||||||
|
self.j = self.i;
|
||||||
|
self.k = self.i;
|
||||||
|
if self.i >= n {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FusedIterator for ThreeDeviceSameJointIter<'_> {}
|
||||||
|
|
||||||
|
/// Iterator over three-device circuits where the two joint types differ.
|
||||||
|
///
|
||||||
|
/// In this case, the first 2 devices are swapable and are iterated without repeating,
|
||||||
|
/// while the third device iterates over all values independently.
|
||||||
|
pub struct ThreeDeviceDiffJointIter<'a> {
|
||||||
|
items: &'a [DatasetItem],
|
||||||
|
i: usize,
|
||||||
|
j: usize,
|
||||||
|
k: usize,
|
||||||
|
joint_idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ThreeDeviceDiffJointIter<'a> {
|
||||||
|
pub fn new(items: &'a [DatasetItem]) -> Self {
|
||||||
|
Self {
|
||||||
|
items,
|
||||||
|
i: 0,
|
||||||
|
j: 0,
|
||||||
|
k: 0,
|
||||||
|
joint_idx: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for ThreeDeviceDiffJointIter<'_> {
|
||||||
|
type Item = Circuit;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let n = self.items.len();
|
||||||
|
if n == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if self.joint_idx < JointKind::ALL.len() {
|
||||||
|
let j = JointKind::ALL[self.joint_idx];
|
||||||
|
self.joint_idx += 1;
|
||||||
|
return Some(Circuit::from_three_devices(
|
||||||
|
self.items[self.i].value,
|
||||||
|
self.items[self.j].value,
|
||||||
|
j,
|
||||||
|
self.items[self.k].value,
|
||||||
|
j.flip(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.joint_idx = 0;
|
||||||
|
self.k += 1;
|
||||||
|
if self.k >= n {
|
||||||
|
self.j += 1;
|
||||||
|
self.k = 0;
|
||||||
|
if self.j >= n {
|
||||||
|
self.i += 1;
|
||||||
|
self.j = self.i;
|
||||||
|
self.k = 0;
|
||||||
|
if self.i >= n {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FusedIterator for ThreeDeviceDiffJointIter<'_> {}
|
||||||
|
|
||||||
|
/// Type alias for the chained three-device circuit iterator.
|
||||||
|
pub type ThreeDeviceCircuitIter<'a> = std::iter::Chain<
|
||||||
|
ThreeDeviceSameJointIter<'a>,
|
||||||
|
ThreeDeviceDiffJointIter<'a>,
|
||||||
|
>;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// BfsItem
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// The entry used in BFS iteration storing circuit and value.
|
||||||
|
pub struct BfsItem {
|
||||||
|
/// The circuit represented by this item.
|
||||||
|
circuit: Circuit,
|
||||||
|
/// The computed value of the circuit.
|
||||||
|
value: f64,
|
||||||
|
/// The unsigned difference between the target value and the value of this circuit.
|
||||||
|
unsigned_difference: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BfsItem {
|
||||||
|
/// Create a new BFS item by computing values eagerly.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`CircuitValueTrait::value`].
|
||||||
|
pub fn new(circuit: Circuit, cv_trait: &CircuitValueTrait) -> Result<Self, LcrConnError> {
|
||||||
|
let value = cv_trait.value(&circuit)?;
|
||||||
|
let unsigned_difference = cv_trait.unsigned_difference(&circuit, Some(value))?;
|
||||||
|
Ok(Self {
|
||||||
|
circuit,
|
||||||
|
value,
|
||||||
|
unsigned_difference,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The circuit represented by this item.
|
||||||
|
pub fn circuit(&self) -> &Circuit {
|
||||||
|
&self.circuit
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The computed value of the circuit.
|
||||||
|
pub fn value(&self) -> f64 {
|
||||||
|
self.value
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The unsigned difference between the target value and the value of this circuit.
|
||||||
|
pub fn unsigned_difference(&self) -> f64 {
|
||||||
|
self.unsigned_difference
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consume this item and return the inner circuit.
|
||||||
|
pub fn into_circuit(self) -> Circuit {
|
||||||
|
self.circuit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// ResultBucket
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// An item stored in a [`ResultBucket`].
|
||||||
|
struct ResultBucketItem {
|
||||||
|
/// The score associated with this item.
|
||||||
|
score: f64,
|
||||||
|
/// The underlying BfsItem.
|
||||||
|
item: BfsItem,
|
||||||
|
/// Monotonic counter used as a tiebreaker when scores are equal,
|
||||||
|
/// ensuring that BinaryHeap never compares BfsItem directly.
|
||||||
|
seq: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResultBucketItem {
|
||||||
|
fn new(score: f64, item: BfsItem, seq: usize) -> Self {
|
||||||
|
Self { score, item, seq }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for ResultBucketItem {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.score == other.score && self.seq == other.seq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for ResultBucketItem {}
|
||||||
|
|
||||||
|
impl PartialOrd for ResultBucketItem {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for ResultBucketItem {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
// BinaryHeap is a max-heap: the greatest element is at the top.
|
||||||
|
// We want the entry with the largest score at the top.
|
||||||
|
match self.score.partial_cmp(&other.score) {
|
||||||
|
Some(Ordering::Equal) | None => self.seq.cmp(&other.seq),
|
||||||
|
Some(ord) => ord,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A bounded bucket that keeps up to N entries with the smallest scores.
|
||||||
|
///
|
||||||
|
/// When the bucket is full, inserting a new item only succeeds if its score
|
||||||
|
/// is less than the current maximum; the maximum is then evicted.
|
||||||
|
pub struct ResultBucket {
|
||||||
|
/// Maximum number of items the bucket can hold.
|
||||||
|
n: usize,
|
||||||
|
/// Max-heap of [`ResultBucketItem`].
|
||||||
|
/// The entry with the largest score sits at index 0.
|
||||||
|
heap: BinaryHeap<ResultBucketItem>,
|
||||||
|
/// Monotonic counter fed to each [`ResultBucketItem`] as a tiebreaker,
|
||||||
|
/// preventing BinaryHeap from comparing BfsItem on score collisions.
|
||||||
|
counter: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResultBucket {
|
||||||
|
/// Create a new bucket that holds at most `n` items.
|
||||||
|
pub fn new(n: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
n,
|
||||||
|
heap: BinaryHeap::new(),
|
||||||
|
counter: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The number of items currently in the bucket.
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.heap.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the bucket is empty.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.heap.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a [`BfsItem`] with the given score.
|
||||||
|
///
|
||||||
|
/// If the bucket is not yet full the item is always inserted.
|
||||||
|
/// Otherwise the item is only inserted when `score` is smaller
|
||||||
|
/// than the largest score currently in the bucket; the entry
|
||||||
|
/// with the largest score is then evicted.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// `true` if the item was inserted, `false` otherwise.
|
||||||
|
pub fn insert(&mut self, item: BfsItem, score: f64) -> bool {
|
||||||
|
let entry = ResultBucketItem::new(score, item, self.counter);
|
||||||
|
if self.heap.len() < self.n {
|
||||||
|
self.heap.push(entry);
|
||||||
|
self.counter += 1;
|
||||||
|
true
|
||||||
|
} else if score >= self.heap.peek().unwrap().score {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
*self.heap.peek_mut().unwrap() = entry;
|
||||||
|
self.counter += 1;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consume the bucket and return all stored items.
|
||||||
|
pub fn into_items(self) -> Vec<BfsItem> {
|
||||||
|
self.heap.into_iter().map(|entry| entry.item).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// BfsResolver
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/// A resolver that uses brute-force search to find the best matching circuits.
|
||||||
|
pub struct BfsResolver {
|
||||||
|
/// The datasets for all device kinds.
|
||||||
|
datasets: DatasetCollection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BfsResolver {
|
||||||
|
/// Create a new BFS resolver with the given datasets.
|
||||||
|
pub fn new(datasets: DatasetCollection) -> Self {
|
||||||
|
Self { datasets }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate all possible circuits with one device without repeating equivalent topology.
|
||||||
|
pub fn iter_one_device_circuit(dataset: &Dataset) -> OneDeviceCircuitIter<'_> {
|
||||||
|
OneDeviceCircuitIter::new(dataset.items())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate all possible circuits with two devices without repeating equivalent topology.
|
||||||
|
pub fn iter_two_devices_circuit(dataset: &Dataset) -> TwoDeviceCircuitIter<'_> {
|
||||||
|
TwoDeviceCircuitIter::new(dataset.items())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate all possible circuits with three devices without repeating equivalent topology.
|
||||||
|
pub fn iter_three_devices_circuit(dataset: &Dataset) -> ThreeDeviceCircuitIter<'_> {
|
||||||
|
ThreeDeviceSameJointIter::new(dataset.items())
|
||||||
|
.chain(ThreeDeviceDiffJointIter::new(dataset.items()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pick_dataset(&self, device_kind: DeviceKind) -> &Dataset {
|
||||||
|
match device_kind {
|
||||||
|
DeviceKind::Resistor => self.datasets.resistor(),
|
||||||
|
DeviceKind::Capacitor => self.datasets.capacitor(),
|
||||||
|
DeviceKind::Inductor => self.datasets.inductor(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resolver for BfsResolver {
|
||||||
|
fn resolve(&self, request: &Request) -> Result<Response, LcrConnError> {
|
||||||
|
// Pick dataset from collection
|
||||||
|
let dataset = self.pick_dataset(request.device_kind);
|
||||||
|
|
||||||
|
// Iterate circuit item one by one
|
||||||
|
let mut bucket = ResultBucket::new(request.count_limit);
|
||||||
|
let cv_trait = CircuitValueTrait::new(request.device_kind, request.target_value);
|
||||||
|
|
||||||
|
let circuits = Self::iter_one_device_circuit(dataset)
|
||||||
|
.chain(Self::iter_two_devices_circuit(dataset))
|
||||||
|
.chain(Self::iter_three_devices_circuit(dataset));
|
||||||
|
|
||||||
|
for circuit in circuits {
|
||||||
|
let item = BfsItem::new(circuit, &cv_trait)?;
|
||||||
|
// If circuit absolute difference is out of tolerance, skip it directly.
|
||||||
|
if item.unsigned_difference() > request.tolerance {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Put it into bucket
|
||||||
|
bucket.insert(item, item.unsigned_difference());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return result
|
||||||
|
let circuits: Vec<Circuit> = bucket
|
||||||
|
.into_items()
|
||||||
|
.into_iter()
|
||||||
|
.map(BfsItem::into_circuit)
|
||||||
|
.collect();
|
||||||
|
Response::new(request, circuits)
|
||||||
|
}
|
||||||
|
}
|
||||||
156
kernel/lcrconn/src/resolver/lut.rs
Normal file
156
kernel/lcrconn/src/resolver/lut.rs
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use super::bfs::BfsResolver;
|
||||||
|
use super::Resolver;
|
||||||
|
use crate::common::{Circuit, CircuitValueTrait, DeviceKind, LcrConnError};
|
||||||
|
use crate::dataset::{Dataset, DatasetCollection};
|
||||||
|
use crate::query::{Request, Response};
|
||||||
|
|
||||||
|
/// An item in the lookup table.
|
||||||
|
pub struct LutItem {
|
||||||
|
/// The circuit represented by this item.
|
||||||
|
circuit: Circuit,
|
||||||
|
/// The value of this circuit.
|
||||||
|
value: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LutItem {
|
||||||
|
/// Create a new LUT item by computing the circuit value.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`Circuit::compute`].
|
||||||
|
pub fn new(circuit: Circuit, device_kind: DeviceKind) -> Result<Self, LcrConnError> {
|
||||||
|
let value = circuit.compute(device_kind)?;
|
||||||
|
Ok(Self { circuit, value })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The circuit represented by this item.
|
||||||
|
pub fn circuit(&self) -> &Circuit {
|
||||||
|
&self.circuit
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The value of this circuit.
|
||||||
|
pub fn value(&self) -> f64 {
|
||||||
|
self.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A resolver that uses a lookup table to find the best matching circuit.
|
||||||
|
pub struct LutResolver {
|
||||||
|
/// The lookup table for resistors.
|
||||||
|
resistor_lut: Vec<LutItem>,
|
||||||
|
/// The lookup table for capacitors.
|
||||||
|
capacitor_lut: Vec<LutItem>,
|
||||||
|
/// The lookup table for inductors.
|
||||||
|
inductor_lut: Vec<LutItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LutResolver {
|
||||||
|
/// Create a new LUT resolver by building lookup tables from the given datasets.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`LutItem::new`].
|
||||||
|
pub fn new(datasets: &DatasetCollection) -> Result<Self, LcrConnError> {
|
||||||
|
Ok(Self {
|
||||||
|
resistor_lut: Self::build_lut(datasets.resistor(), DeviceKind::Resistor)?,
|
||||||
|
capacitor_lut: Self::build_lut(datasets.capacitor(), DeviceKind::Capacitor)?,
|
||||||
|
inductor_lut: Self::build_lut(datasets.inductor(), DeviceKind::Inductor)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_lut(dataset: &Dataset, device_kind: DeviceKind) -> Result<Vec<LutItem>, LcrConnError> {
|
||||||
|
let mut lut: Vec<LutItem> = Vec::new();
|
||||||
|
|
||||||
|
let circuits = BfsResolver::iter_one_device_circuit(dataset)
|
||||||
|
.chain(BfsResolver::iter_two_devices_circuit(dataset))
|
||||||
|
.chain(BfsResolver::iter_three_devices_circuit(dataset));
|
||||||
|
|
||||||
|
for circuit in circuits {
|
||||||
|
lut.push(LutItem::new(circuit, device_kind)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
lut.sort_by(|a, b| a.value.partial_cmp(&b.value).unwrap_or(Ordering::Equal));
|
||||||
|
Ok(lut)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pick_lut(&self, device_kind: DeviceKind) -> &[LutItem] {
|
||||||
|
match device_kind {
|
||||||
|
DeviceKind::Resistor => &self.resistor_lut,
|
||||||
|
DeviceKind::Capacitor => &self.capacitor_lut,
|
||||||
|
DeviceKind::Inductor => &self.inductor_lut,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resolver for LutResolver {
|
||||||
|
fn resolve(&self, request: &Request) -> Result<Response, LcrConnError> {
|
||||||
|
let lut = self.pick_lut(request.device_kind);
|
||||||
|
let target = request.target_value;
|
||||||
|
let count_limit = request.count_limit;
|
||||||
|
let mut bucket: Vec<Circuit> = Vec::new();
|
||||||
|
|
||||||
|
// Locate the insertion point of target in the sorted LUT.
|
||||||
|
// left/right start at the two nearest neighbours and expand outward.
|
||||||
|
let idx = lut.partition_point(|item| item.value < target);
|
||||||
|
|
||||||
|
// Expand outward non-symmetrically: at each step compare the two
|
||||||
|
// candidates on each side and advance the one that is closer to the
|
||||||
|
// target. This guarantees items are visited in strictly increasing
|
||||||
|
// difference order, so the first N items within tolerance are exactly
|
||||||
|
// the N best matches.
|
||||||
|
let mut left = idx as isize - 1;
|
||||||
|
let mut right = idx as isize;
|
||||||
|
let lut_len = lut.len() as isize;
|
||||||
|
|
||||||
|
let cv_trait = CircuitValueTrait::new(request.device_kind, target);
|
||||||
|
|
||||||
|
while left >= 0 || right < lut_len {
|
||||||
|
if bucket.len() >= count_limit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let go_left = if left < 0 {
|
||||||
|
false
|
||||||
|
} else if right >= lut_len {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let left_item = &lut[left as usize];
|
||||||
|
let left_diff =
|
||||||
|
cv_trait.unsigned_difference(left_item.circuit(), Some(left_item.value()))?;
|
||||||
|
let right_item = &lut[right as usize];
|
||||||
|
let right_diff = cv_trait
|
||||||
|
.unsigned_difference(right_item.circuit(), Some(right_item.value()))?;
|
||||||
|
left_diff <= right_diff
|
||||||
|
};
|
||||||
|
|
||||||
|
let item = if go_left {
|
||||||
|
let item = &lut[left as usize];
|
||||||
|
left -= 1;
|
||||||
|
item
|
||||||
|
} else {
|
||||||
|
let item = &lut[right as usize];
|
||||||
|
right += 1;
|
||||||
|
item
|
||||||
|
};
|
||||||
|
|
||||||
|
let diff = cv_trait.unsigned_difference(item.circuit(), Some(item.value()))?;
|
||||||
|
// Since the LUT is sorted, values on each side only move further
|
||||||
|
// from target as we advance. Once one side exceeds tolerance,
|
||||||
|
// the rest of that side is guaranteed out of range — disable it.
|
||||||
|
if diff > request.tolerance {
|
||||||
|
if go_left {
|
||||||
|
left = -1;
|
||||||
|
} else {
|
||||||
|
right = lut_len;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket.push(item.circuit().clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::new(request, bucket)
|
||||||
|
}
|
||||||
|
}
|
||||||
26
kernel/lcrconn/src/resolver/mod.rs
Normal file
26
kernel/lcrconn/src/resolver/mod.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
pub mod bfs;
|
||||||
|
pub mod lut;
|
||||||
|
|
||||||
|
use crate::common::LcrConnError;
|
||||||
|
use crate::query::{Request, Response};
|
||||||
|
|
||||||
|
/// Abstract base trait for all resolvers.
|
||||||
|
pub trait Resolver {
|
||||||
|
/// Resolve the request and return the response.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `request` - The request to resolve.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// The response containing the best matching circuits.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// See [`Circuit::compute`](crate::common::Circuit::compute).
|
||||||
|
fn resolve(&self, request: &Request) -> Result<Response, LcrConnError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use bfs::BfsResolver;
|
||||||
|
pub use lut::LutResolver;
|
||||||
Reference in New Issue
Block a user