fts_core/models/
demand.rs

1mod constant;
2mod curve;
3
4pub use constant::{Constant, RawConstant};
5pub use curve::{Curve, Point};
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8use utoipa::ToSchema;
9
10/// A demand curve over (rate, price).
11///
12/// DemandCurve represents either:
13/// - A non-increasing, piecewise-linear demand curve assigning a cost to each rate in its domain, or
14/// - A simple, "flat" demand curve assining a constant cost to each rate in its domain.
15///
16/// This is the core component that defines how a bidder values different trade outcomes.
17#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ToSchema)]
18#[serde(untagged, try_from = "RawDemandCurve", into = "RawDemandCurve")]
19// TODO: Utoipa doesn't fully support all the Serde annotations,
20// so we injected `untagged` (which Serde will ignore given the presence of
21// of `try_from` and `into`), then inline the actual fields. This appears
22// to correctly generate the OpenAPI schema, but we should revisit.
23pub enum DemandCurve {
24    /// A piecewise linear demand curve defined by points
25    Curve(#[schema(inline)] Curve),
26    /// A constant constraint enforcing a specific trade quantity at a price
27    Constant(#[schema(inline)] Constant),
28}
29
30impl DemandCurve {
31    /// Return the domain of the demand curve (min and max rates)
32    pub fn domain(&self) -> (f64, f64) {
33        match self {
34            Self::Constant(constant) => constant.domain(),
35            Self::Curve(curve) => curve.domain(),
36        }
37    }
38
39    /// Convert the curve data into a solver-specific representation
40    pub fn as_solver(&self, scale: f64) -> Vec<fts_solver::Point> {
41        match self {
42            Self::Constant(constant) => constant.as_solver(scale),
43            Self::Curve(curve) => curve.as_solver(scale),
44        }
45    }
46}
47
48/// An error type for the ways in which the provided utility function may be invalid.
49#[derive(Error, Debug)]
50pub enum ValidationError {
51    /// Error when a curve's definition is invalid
52    #[error("invalid demand curve: {0}")]
53    Curve(#[from] curve::ValidationError),
54    /// Error when a constant curve's definition is invalid
55    #[error("invalid constant curve: {0}")]
56    Constant(#[from] constant::ValidationError),
57}
58
59/// The "DTO" type for the utility
60///
61/// This enum represents the raw data formats accepted in API requests for defining costs.
62#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
63#[serde(untagged)]
64pub enum RawDemandCurve {
65    /// A sequence of points defining a piecewise linear demand curve
66    Curve(Vec<Point>),
67    /// A raw constant constraint definition
68    Constant(RawConstant),
69}
70
71impl TryFrom<RawDemandCurve> for DemandCurve {
72    type Error = ValidationError;
73
74    fn try_from(value: RawDemandCurve) -> Result<Self, Self::Error> {
75        match value {
76            RawDemandCurve::Curve(curve) => Ok(DemandCurve::Curve(curve.try_into()?)),
77            RawDemandCurve::Constant(constant) => Ok(DemandCurve::Constant(constant.try_into()?)),
78        }
79    }
80}
81
82impl From<DemandCurve> for RawDemandCurve {
83    fn from(value: DemandCurve) -> Self {
84        match value {
85            DemandCurve::Curve(curve) => RawDemandCurve::Curve(curve.into()),
86            DemandCurve::Constant(constant) => RawDemandCurve::Constant(constant.into()),
87        }
88    }
89}