1use super::{BinaryParameters, BinaryRecord, GroupCount, PureParameters};
2use crate::{FeosResult, parameter::PureRecord};
3use nalgebra::DVector;
4use num_traits::Zero;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Serialize, Deserialize, Clone, Debug)]
10pub struct AssociationRecord<A> {
11 #[serde(skip_serializing_if = "String::is_empty")]
12 #[serde(default)]
13 pub id: String,
14 #[serde(flatten)]
15 pub parameters: Option<A>,
16 #[serde(skip_serializing_if = "f64::is_zero")]
18 #[serde(default)]
19 pub na: f64,
20 #[serde(skip_serializing_if = "f64::is_zero")]
22 #[serde(default)]
23 pub nb: f64,
24 #[serde(skip_serializing_if = "f64::is_zero")]
26 #[serde(default)]
27 pub nc: f64,
28}
29
30impl<A> AssociationRecord<A> {
31 pub fn new(parameters: Option<A>, na: f64, nb: f64, nc: f64) -> Self {
32 Self::with_id(Default::default(), parameters, na, nb, nc)
33 }
34
35 pub fn with_id(id: String, parameters: Option<A>, na: f64, nb: f64, nc: f64) -> Self {
36 Self {
37 id,
38 parameters,
39 na,
40 nb,
41 nc,
42 }
43 }
44}
45
46#[derive(Serialize, Deserialize, Clone, Debug)]
48pub struct BinaryAssociationRecord<A> {
49 #[serde(skip_serializing_if = "String::is_empty")]
51 #[serde(default)]
52 pub id1: String,
53 #[serde(skip_serializing_if = "String::is_empty")]
55 #[serde(default)]
56 pub id2: String,
57 #[serde(flatten)]
59 pub parameters: A,
60}
61
62impl<A> BinaryAssociationRecord<A> {
63 pub fn new(parameters: A) -> Self {
64 Self::with_id(Default::default(), Default::default(), parameters)
65 }
66
67 pub fn with_id(id1: String, id2: String, parameters: A) -> Self {
68 Self {
69 id1,
70 id2,
71 parameters,
72 }
73 }
74}
75
76#[derive(Clone, Debug)]
78pub struct AssociationSite {
79 pub assoc_comp: usize,
81 pub id: String,
83 pub n: f64,
85}
86
87impl AssociationSite {
88 fn new(assoc_comp: usize, id: String, n: f64) -> Self {
89 Self { assoc_comp, id, n }
90 }
91}
92
93pub trait CombiningRule<P> {
96 fn combining_rule(comp_i: &P, comp_j: &P, parameters_i: &Self, parameters_j: &Self) -> Self;
97}
98
99impl<P> CombiningRule<P> for () {
100 fn combining_rule(_: &P, _: &P, _: &Self, _: &Self) {}
101}
102
103#[derive(Clone)]
106pub struct AssociationParameters<A> {
107 pub component_index: DVector<usize>,
108 pub sites_a: Vec<AssociationSite>,
109 pub sites_b: Vec<AssociationSite>,
110 pub sites_c: Vec<AssociationSite>,
111 pub binary_ab: Vec<BinaryParameters<A, ()>>,
112 pub binary_cc: Vec<BinaryParameters<A, ()>>,
113}
114
115impl<A: Clone> AssociationParameters<A> {
116 pub fn new<P, B>(
117 pure_records: &[PureRecord<P, A>],
118 binary_records: &[BinaryRecord<usize, B, A>],
119 ) -> FeosResult<Self>
120 where
121 A: CombiningRule<P>,
122 {
123 let mut sites_a = Vec::new();
124 let mut sites_b = Vec::new();
125 let mut sites_c = Vec::new();
126 let mut pars_a = Vec::new();
127 let mut pars_b = Vec::new();
128 let mut pars_c = Vec::new();
129
130 for (i, record) in pure_records.iter().enumerate() {
131 for site in record.association_sites.iter() {
132 if site.na > 0.0 {
133 sites_a.push(AssociationSite::new(i, site.id.clone(), site.na));
134 pars_a.push(&site.parameters);
135 }
136 if site.nb > 0.0 {
137 sites_b.push(AssociationSite::new(i, site.id.clone(), site.nb));
138 pars_b.push(&site.parameters);
139 }
140 if site.nc > 0.0 {
141 sites_c.push(AssociationSite::new(i, site.id.clone(), site.nc));
142 pars_c.push(&site.parameters);
143 }
144 }
145 }
146
147 let record_map: HashMap<_, _> = binary_records
148 .iter()
149 .flat_map(|br| {
150 br.association_sites.iter().flat_map(|a| {
151 [
152 ((br.id1, br.id2, &a.id1, &a.id2), &a.parameters),
153 ((br.id2, br.id1, &a.id2, &a.id1), &a.parameters),
154 ]
155 })
156 })
157 .collect();
158
159 let mut binary_ab = Vec::new();
160 for ((a, site_a), pa) in sites_a.iter().enumerate().zip(&pars_a) {
161 for ((b, site_b), pb) in sites_b.iter().enumerate().zip(&pars_b) {
162 if let Some(&record) =
163 record_map.get(&(site_a.assoc_comp, site_b.assoc_comp, &site_a.id, &site_b.id))
164 {
165 binary_ab.push(BinaryParameters::new(a, b, record.clone(), ()));
166 } else if let (Some(pa), Some(pb)) = (pa, pb) {
167 binary_ab.push(BinaryParameters::new(
168 a,
169 b,
170 A::combining_rule(
171 &pure_records[site_a.assoc_comp].model_record,
172 &pure_records[site_b.assoc_comp].model_record,
173 pa,
174 pb,
175 ),
176 (),
177 ));
178 }
179 }
180 }
181
182 let mut binary_cc = Vec::new();
183 for ((a, site_a), pa) in sites_c.iter().enumerate().zip(&pars_c) {
184 for ((b, site_b), pb) in sites_c.iter().enumerate().zip(&pars_c) {
185 if let Some(&record) =
186 record_map.get(&(site_a.assoc_comp, site_b.assoc_comp, &site_a.id, &site_b.id))
187 {
188 binary_cc.push(BinaryParameters::new(a, b, record.clone(), ()));
189 } else if let (Some(pa), Some(pb)) = (pa, pb) {
190 binary_cc.push(BinaryParameters::new(
191 a,
192 b,
193 A::combining_rule(
194 &pure_records[site_a.assoc_comp].model_record,
195 &pure_records[site_b.assoc_comp].model_record,
196 pa,
197 pb,
198 ),
199 (),
200 ));
201 }
202 }
203 }
204
205 let component_index = DVector::from_vec((0..pure_records.len()).collect());
206
207 Ok(Self {
208 component_index,
209 sites_a,
210 sites_b,
211 sites_c,
212 binary_ab,
213 binary_cc,
214 })
215 }
216
217 pub fn new_hetero<P, C: GroupCount>(
218 groups: &[PureParameters<P, C>],
219 association_sites: &[Vec<AssociationRecord<A>>],
220 binary_records: &[BinaryParameters<Vec<BinaryAssociationRecord<A>>, ()>],
221 ) -> FeosResult<Self>
222 where
223 A: CombiningRule<P>,
224 {
225 let mut sites_a = Vec::new();
226 let mut sites_b = Vec::new();
227 let mut sites_c = Vec::new();
228 let mut pars_a = Vec::new();
229 let mut pars_b = Vec::new();
230 let mut pars_c = Vec::new();
231
232 for (i, (record, sites)) in groups.iter().zip(association_sites).enumerate() {
233 for site in sites.iter() {
234 if site.na > 0.0 {
235 let na = site.na * record.count.into_f64();
236 sites_a.push(AssociationSite::new(i, site.id.clone(), na));
237 pars_a.push(&site.parameters)
238 }
239 if site.nb > 0.0 {
240 let nb = site.nb * record.count.into_f64();
241 sites_b.push(AssociationSite::new(i, site.id.clone(), nb));
242 pars_b.push(&site.parameters)
243 }
244 if site.nc > 0.0 {
245 let nc = site.nc * record.count.into_f64();
246 sites_c.push(AssociationSite::new(i, site.id.clone(), nc));
247 pars_c.push(&site.parameters)
248 }
249 }
250 }
251
252 let record_map: HashMap<_, _> = binary_records
253 .iter()
254 .flat_map(|br| {
255 br.model_record.iter().flat_map(|a| {
256 [
257 ((br.id1, br.id2, &a.id1, &a.id2), &a.parameters),
258 ((br.id2, br.id1, &a.id2, &a.id1), &a.parameters),
259 ]
260 })
261 })
262 .collect();
263
264 let mut binary_ab = Vec::new();
265 for ((a, site_a), pa) in sites_a.iter().enumerate().zip(&pars_a) {
266 for ((b, site_b), pb) in sites_b.iter().enumerate().zip(&pars_b) {
267 if let Some(&record) =
268 record_map.get(&(site_a.assoc_comp, site_b.assoc_comp, &site_a.id, &site_b.id))
269 {
270 binary_ab.push(BinaryParameters::new(a, b, record.clone(), ()));
271 } else if let (Some(pa), Some(pb)) = (pa, pb) {
272 binary_ab.push(BinaryParameters::new(
273 a,
274 b,
275 A::combining_rule(
276 &groups[site_a.assoc_comp].model_record,
277 &groups[site_b.assoc_comp].model_record,
278 pa,
279 pb,
280 ),
281 (),
282 ));
283 }
284 }
285 }
286
287 let mut binary_cc = Vec::new();
288 for ((a, site_a), pa) in sites_c.iter().enumerate().zip(&pars_c) {
289 for ((b, site_b), pb) in sites_c.iter().enumerate().zip(&pars_c) {
290 if let Some(&record) =
291 record_map.get(&(site_a.assoc_comp, site_b.assoc_comp, &site_a.id, &site_b.id))
292 {
293 binary_cc.push(BinaryParameters::new(a, b, record.clone(), ()));
294 } else if let (Some(pa), Some(pb)) = (pa, pb) {
295 binary_cc.push(BinaryParameters::new(
296 a,
297 b,
298 A::combining_rule(
299 &groups[site_a.assoc_comp].model_record,
300 &groups[site_b.assoc_comp].model_record,
301 pa,
302 pb,
303 ),
304 (),
305 ));
306 }
307 }
308 }
309
310 let component_index =
311 DVector::from_vec(groups.iter().map(|pr| pr.component_index).collect());
312
313 Ok(Self {
314 component_index,
315 sites_a,
316 sites_b,
317 sites_c,
318 binary_ab,
319 binary_cc,
320 })
321 }
322
323 pub fn is_empty(&self) -> bool {
324 (self.sites_a.is_empty() | self.sites_b.is_empty()) & self.sites_c.is_empty()
325 }
326}