starfall/astronomy/close_binary_star/constraints/
mod.rs1use rand::prelude::*;
2use std::default::Default;
3
4use crate::astronomy::close_binary_star::constants::*;
5use crate::astronomy::close_binary_star::error::Error;
6use crate::astronomy::close_binary_star::CloseBinaryStar;
7use crate::astronomy::star::constraints::Constraints as StarConstraints;
8
9#[derive(Clone, Copy, Debug, PartialEq)]
21pub struct Constraints {
22 pub minimum_combined_mass: Option<f64>,
24 pub maximum_combined_mass: Option<f64>,
26 pub minimum_individual_mass: Option<f64>,
28 pub maximum_individual_mass: Option<f64>,
30 pub minimum_average_separation: Option<f64>,
32 pub maximum_average_separation: Option<f64>,
34 pub minimum_orbital_eccentricity: Option<f64>,
36 pub maximum_orbital_eccentricity: Option<f64>,
38 pub minimum_age: Option<f64>,
40 pub maximum_age: Option<f64>,
42 pub enforce_habitability: bool,
44 pub star_constraints: Option<StarConstraints>,
46}
47
48impl Constraints {
49 #[named]
51 pub fn habitable() -> Self {
52 trace_enter!();
53 let minimum_combined_mass = Some(MINIMUM_HABITABLE_COMBINED_MASS);
54 let maximum_combined_mass = Some(MAXIMUM_HABITABLE_COMBINED_MASS);
55 let minimum_individual_mass = Some(MINIMUM_HABITABLE_INDIVIDUAL_MASS);
56 let maximum_individual_mass = Some(MAXIMUM_HABITABLE_INDIVIDUAL_MASS);
57 let minimum_orbital_eccentricity = Some(MINIMUM_HABITABLE_ORBITAL_ECCENTRICITY);
58 let maximum_orbital_eccentricity = Some(MAXIMUM_HABITABLE_ORBITAL_ECCENTRICITY);
59 let maximum_average_separation = Some(MAXIMUM_HABITABLE_AVERAGE_SEPARATION);
60 let minimum_age = Some(MINIMUM_HABITABLE_AGE);
61 let enforce_habitability = true;
62 let star_constraints = Some(StarConstraints::habitable());
63 let result = Self {
64 minimum_combined_mass,
65 maximum_combined_mass,
66 minimum_individual_mass,
67 maximum_individual_mass,
68 maximum_average_separation,
69 minimum_orbital_eccentricity,
70 maximum_orbital_eccentricity,
71 minimum_age,
72 enforce_habitability,
73 star_constraints,
74 ..Constraints::default()
75 };
76 trace_var!(result);
77 trace_exit!();
78 result
79 }
80
81 #[named]
83 pub fn generate<R: Rng + ?Sized>(&self, rng: &mut R) -> Result<CloseBinaryStar, Error> {
84 trace_enter!();
85 let mut minimum_combined_mass = self.minimum_combined_mass.unwrap_or(MINIMUM_COMBINED_MASS);
86 trace_var!(minimum_combined_mass);
87 let maximum_combined_mass = self.maximum_combined_mass.unwrap_or(MAXIMUM_COMBINED_MASS);
88 trace_var!(maximum_combined_mass);
89 let minimum_individual_mass = self.minimum_individual_mass.unwrap_or(MINIMUM_INDIVIDUAL_MASS);
90 trace_var!(minimum_individual_mass);
91 let maximum_individual_mass = self.maximum_individual_mass.unwrap_or(MAXIMUM_INDIVIDUAL_MASS);
92 trace_var!(maximum_individual_mass);
93 let minimum_orbital_eccentricity = self
94 .minimum_orbital_eccentricity
95 .unwrap_or(MINIMUM_ORBITAL_ECCENTRICITY);
96 trace_var!(minimum_orbital_eccentricity);
97 let maximum_orbital_eccentricity = self
98 .maximum_orbital_eccentricity
99 .unwrap_or(MAXIMUM_ORBITAL_ECCENTRICITY);
100 trace_var!(maximum_orbital_eccentricity);
101 let minimum_average_separation = self.minimum_average_separation.unwrap_or(MINIMUM_AVERAGE_SEPARATION);
102 trace_var!(minimum_average_separation);
103 let maximum_average_separation = self.maximum_average_separation.unwrap_or(MAXIMUM_AVERAGE_SEPARATION);
104 trace_var!(maximum_average_separation);
105 let orbital_eccentricity = rng.gen_range(minimum_orbital_eccentricity..maximum_orbital_eccentricity);
106 trace_var!(orbital_eccentricity);
107 let average_separation = rng.gen_range(minimum_average_separation..maximum_average_separation);
108 trace_var!(average_separation);
109 let combined_mass;
110 let primary_mass;
111 let secondary_mass;
112 let mut primary_constraints;
113 let mut secondary_constraints;
114 if self.enforce_habitability {
115 let bare_minimum =
116 (1.1 * (4.0 * maximum_average_separation * (1.0 + orbital_eccentricity)).powf(2.0)).powf(1.0 / 4.0);
117 if minimum_combined_mass < bare_minimum {
118 minimum_combined_mass = 1.1 * bare_minimum;
119 }
120 primary_constraints = self.star_constraints.unwrap_or_else(StarConstraints::habitable);
121 secondary_constraints = self.star_constraints.unwrap_or_else(StarConstraints::habitable);
122 } else {
123 primary_constraints = self.star_constraints.unwrap_or_default();
124 secondary_constraints = self.star_constraints.unwrap_or_default();
125 }
126 let (primary, secondary) = {
127 combined_mass = rng.gen_range(minimum_combined_mass..maximum_combined_mass);
128 let half = combined_mass / 2.0;
129 let mut top = combined_mass - MINIMUM_HABITABLE_INDIVIDUAL_MASS;
130 if self.enforce_habitability && top > maximum_individual_mass {
131 top = maximum_individual_mass;
132 }
133 primary_mass = rng.gen_range(half..top);
134 secondary_mass = combined_mass - primary_mass;
135 primary_constraints.minimum_mass = Some(0.999 * primary_mass);
136 primary_constraints.maximum_mass = Some(1.001 * primary_mass);
137 secondary_constraints.minimum_mass = Some(0.999 * secondary_mass);
138 secondary_constraints.maximum_mass = Some(1.001 * secondary_mass);
139 let mut primary = primary_constraints.generate(rng)?;
140 let mut secondary = secondary_constraints.generate(rng)?;
141 let minimum_age = match self.enforce_habitability {
142 true => MINIMUM_HABITABLE_AGE,
143 false => 0.1 * primary.life_expectancy,
144 };
145 trace_var!(minimum_age);
146 let maximum_age = 0.9 * primary.life_expectancy;
147 trace_var!(maximum_age);
148 let current_age = rng.gen_range(minimum_age..maximum_age);
149 trace_var!(current_age);
150 primary.current_age = current_age;
151 secondary.current_age = current_age;
152 (primary, secondary)
153 };
154 trace_var!(primary);
155 trace_var!(secondary);
156 let result = CloseBinaryStar::from_stars(rng, primary, secondary, average_separation, orbital_eccentricity)?;
157 trace_var!(result);
158 trace_exit!();
159 Ok(result)
160 }
161}
162
163impl Default for Constraints {
164 #[named]
166 fn default() -> Self {
167 trace_enter!();
168 let minimum_combined_mass = Some(MINIMUM_COMBINED_MASS);
169 let maximum_combined_mass = Some(MAXIMUM_COMBINED_MASS);
170 let minimum_individual_mass = Some(MINIMUM_INDIVIDUAL_MASS);
171 let maximum_individual_mass = Some(MAXIMUM_INDIVIDUAL_MASS);
172 let minimum_average_separation = None;
173 let maximum_average_separation = None;
174 let minimum_orbital_eccentricity = Some(MINIMUM_ORBITAL_ECCENTRICITY);
175 let maximum_orbital_eccentricity = Some(MAXIMUM_ORBITAL_ECCENTRICITY);
176 let minimum_age = None;
177 let maximum_age = None;
178 let enforce_habitability = false;
179 let star_constraints = None;
180 let result = Self {
181 minimum_combined_mass,
182 maximum_combined_mass,
183 minimum_individual_mass,
184 maximum_individual_mass,
185 minimum_average_separation,
186 maximum_average_separation,
187 minimum_orbital_eccentricity,
188 maximum_orbital_eccentricity,
189 minimum_age,
190 maximum_age,
191 enforce_habitability,
192 star_constraints,
193 };
194 trace_var!(result);
195 trace_exit!();
196 result
197 }
198}
199
200#[cfg(test)]
201pub mod test {
202
203 use rand::prelude::*;
204
205 use super::*;
206 use crate::test::*;
207
208 #[named]
209 #[test]
210 pub fn test_default() -> Result<(), Error> {
211 init();
212 trace_enter!();
213 let mut rng = thread_rng();
214 trace_var!(rng);
215 let binary = &Constraints::default().generate(&mut rng)?;
216 trace_var!(binary);
217 print_var!(binary);
218 trace_exit!();
219 Ok(())
220 }
221
222 #[named]
223 #[test]
224 pub fn test_habitable() -> Result<(), Error> {
225 init();
226 trace_enter!();
227 let mut rng = thread_rng();
228 trace_var!(rng);
229 let binary = &Constraints::habitable().generate(&mut rng)?;
230 trace_var!(binary);
231 print_var!(binary);
232 trace_exit!();
233 Ok(())
234 }
235
236 #[named]
237 #[test]
238 pub fn test_default_bulk() -> Result<(), Error> {
239 init();
240 trace_enter!();
241 let mut rng = thread_rng();
242 trace_var!(rng);
243 let mut success = 0;
244 let trials = 1000;
245 let mut counter = 0;
246 loop {
247 match &Constraints::default().generate(&mut rng) {
248 Ok(_binary) => success += 1,
249 Err(error) => print_var!(error),
250 }
251 counter += 1;
252 if counter >= trials {
253 break;
254 }
255 }
256 print_var!(success);
257 trace_exit!();
258 Ok(())
259 }
260
261 #[named]
262 #[test]
263 pub fn test_habitable_bulk() -> Result<(), Error> {
264 init();
265 trace_enter!();
266 let mut rng = thread_rng();
267 trace_var!(rng);
268 let mut success = 0;
269 let trials = 1000;
270 let mut counter = 0;
271 loop {
272 match &Constraints::habitable().generate(&mut rng) {
273 Ok(_binary) => success += 1,
274 Err(error) => print_var!(error),
275 }
276 counter += 1;
277 if counter >= trials {
278 break;
279 }
280 }
281 print_var!(success);
282 assert_eq!(counter, trials);
283 trace_exit!();
284 Ok(())
285 }
286}