[15273] | 1 | using System.Collections.Generic;
|
---|
| 2 |
|
---|
| 3 | namespace HeuristicLab.Problems.ProgramSynthesis.Push.Encoding {
|
---|
[15275] | 4 | using System;
|
---|
| 5 | using System.Linq;
|
---|
[15273] | 6 |
|
---|
| 7 | using HeuristicLab.Common;
|
---|
| 8 | using HeuristicLab.Core;
|
---|
[15275] | 9 | using HeuristicLab.Data;
|
---|
[15273] | 10 | using HeuristicLab.Optimization;
|
---|
[15275] | 11 | using HeuristicLab.Parameters;
|
---|
[15273] | 12 | using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
|
---|
[15275] | 13 | using HeuristicLab.PluginInfrastructure;
|
---|
| 14 | using HeuristicLab.Problems.ProgramSynthesis.Base.Erc;
|
---|
| 15 | using HeuristicLab.Problems.ProgramSynthesis.Push.Configuration;
|
---|
| 16 | using HeuristicLab.Problems.ProgramSynthesis.Push.Crossover;
|
---|
| 17 | using HeuristicLab.Problems.ProgramSynthesis.Push.Manipulator;
|
---|
[15273] | 18 | using HeuristicLab.Problems.ProgramSynthesis.Push.SolutionCreator;
|
---|
| 19 |
|
---|
| 20 | [Item("PlushEncoding", "Describes an linear push (Plush) encoding.")]
|
---|
| 21 | [StorableClass]
|
---|
| 22 | public class PlushEncoding : Encoding<IPlushCreator> {
|
---|
[15275] | 23 |
|
---|
| 24 | public PlushEncoding() : this("Plush") { }
|
---|
| 25 |
|
---|
| 26 | public PlushEncoding(string name) : base(name) {
|
---|
| 27 | minLengthParameter = new FixedValueParameter<IntValue>(Name + ".MinLength", new IntValue(25));
|
---|
| 28 | maxLengthParameter = new FixedValueParameter<IntValue>(Name + ".MaxLength", new IntValue(100));
|
---|
| 29 | minCloseParameter = new FixedValueParameter<IntValue>(Name + ".MinClose", new IntValue(0));
|
---|
| 30 | maxCloseParameter = new FixedValueParameter<IntValue>(Name + ".MaxClose", new IntValue(3));
|
---|
| 31 | closeBiasLevelParameter = new FixedValueParameter<DoubleValue>(Name + ".CloseBiasLevel", new DoubleValue(3d));
|
---|
| 32 | instructionsParameter = new ValueParameter<IExpressionsConfiguration>(Name + ".Instructions");
|
---|
| 33 | ercOptionsParameter = new ValueParameter<ErcOptions>(Name + ".ErcOptions");
|
---|
| 34 | inInstructionProbabilityParameter = new FixedValueParameter<PercentValue>(Name + ".InInstructionProbability");
|
---|
| 35 |
|
---|
| 36 | Parameters.Add(instructionsParameter);
|
---|
| 37 | Parameters.Add(ercOptionsParameter);
|
---|
| 38 | Parameters.Add(inInstructionProbabilityParameter);
|
---|
| 39 |
|
---|
| 40 | SolutionCreator = new PlushCreator();
|
---|
| 41 | RegisterParameterEvents();
|
---|
| 42 | DiscoverOperators();
|
---|
| 43 | }
|
---|
| 44 |
|
---|
[15273] | 45 | public PlushEncoding(bool deserializing)
|
---|
| 46 | : base(deserializing) {
|
---|
| 47 | }
|
---|
| 48 |
|
---|
[15275] | 49 | public PlushEncoding(PlushEncoding original, Cloner cloner)
|
---|
[15273] | 50 | : base(original, cloner) {
|
---|
[15275] | 51 | minLengthParameter = cloner.Clone(original.minLengthParameter);
|
---|
| 52 | maxLengthParameter = cloner.Clone(original.maxLengthParameter);
|
---|
[15273] | 53 | }
|
---|
| 54 |
|
---|
[15275] | 55 | [StorableHook(HookType.AfterDeserialization)]
|
---|
| 56 | private void AfterDeserialization() {
|
---|
| 57 | RegisterParameterEvents();
|
---|
| 58 | DiscoverOperators();
|
---|
[15273] | 59 | }
|
---|
| 60 |
|
---|
| 61 | public override IDeepCloneable Clone(Cloner cloner) {
|
---|
| 62 | return new PlushEncoding(this, cloner);
|
---|
| 63 | }
|
---|
| 64 |
|
---|
[15275] | 65 | #region events
|
---|
| 66 |
|
---|
| 67 | private void OnMinLengthParameterChanged() {
|
---|
| 68 | RegisterMinLengthParameterEvents();
|
---|
| 69 | ConfigureOperators(Operators);
|
---|
| 70 | }
|
---|
| 71 | private void OnMaxLengthParameterChanged() {
|
---|
| 72 | RegisterMaxLengthParameterEvents();
|
---|
| 73 | ConfigureOperators(Operators);
|
---|
| 74 | }
|
---|
| 75 | private void OnMinCloseParameterChanged() {
|
---|
| 76 | RegisterMinCloseParameterEvents();
|
---|
| 77 | ConfigureOperators(Operators);
|
---|
| 78 | }
|
---|
| 79 | private void OnMaxCloseParameterChanged() {
|
---|
| 80 | RegisterMaxCloseParameterEvents();
|
---|
| 81 | ConfigureOperators(Operators);
|
---|
| 82 | }
|
---|
| 83 | private void OnCloseBiasLevelParameterChanged() {
|
---|
| 84 | RegisterCloseBiasLevelParameterEvents();
|
---|
| 85 | ConfigureOperators(Operators);
|
---|
| 86 | }
|
---|
| 87 | private void OnErcOptionsParameterChanged() {
|
---|
| 88 | RegisterErcOptionsParameterEvents();
|
---|
| 89 | ConfigureOperators(Operators);
|
---|
| 90 | }
|
---|
| 91 | private void OnInstructionsParameterChanged() {
|
---|
| 92 | RegisterInstructionsParameterEvents();
|
---|
| 93 | ConfigureOperators(Operators);
|
---|
| 94 | }
|
---|
| 95 | private void OnInInstructionProbabilityParameterChanged() {
|
---|
| 96 | RegisterInInstructionProbabilityParameterEvents();
|
---|
| 97 | ConfigureOperators(Operators);
|
---|
| 98 | }
|
---|
| 99 |
|
---|
| 100 | private void RegisterParameterEvents() {
|
---|
| 101 | RegisterMinLengthParameterEvents();
|
---|
| 102 | RegisterMaxLengthParameterEvents();
|
---|
| 103 | RegisterMinCloseParameterEvents();
|
---|
| 104 | RegisterMaxCloseParameterEvents();
|
---|
| 105 | RegisterCloseBiasLevelParameterEvents();
|
---|
| 106 | RegisterErcOptionsParameterEvents();
|
---|
| 107 | RegisterInstructionsParameterEvents();
|
---|
| 108 | RegisterInInstructionProbabilityParameterEvents();
|
---|
| 109 | }
|
---|
| 110 |
|
---|
| 111 | private void RegisterMinLengthParameterEvents() {
|
---|
| 112 | MinLengthParameter.ValueChanged += (o, s) => ConfigureOperators(Operators);
|
---|
| 113 | MinLengthParameter.Value.ValueChanged += (o, s) => ConfigureOperators(Operators);
|
---|
| 114 | }
|
---|
| 115 |
|
---|
| 116 | private void RegisterMaxLengthParameterEvents() {
|
---|
| 117 | MaxLengthParameter.ValueChanged += (o, s) => ConfigureOperators(Operators);
|
---|
| 118 | MaxLengthParameter.Value.ValueChanged += (o, s) => ConfigureOperators(Operators);
|
---|
| 119 | }
|
---|
| 120 |
|
---|
| 121 | private void RegisterMinCloseParameterEvents() {
|
---|
| 122 | MinCloseParameter.ValueChanged += (o, s) => ConfigureOperators(Operators);
|
---|
| 123 | MinCloseParameter.Value.ValueChanged += (o, s) => ConfigureOperators(Operators);
|
---|
| 124 | }
|
---|
| 125 |
|
---|
| 126 | private void RegisterMaxCloseParameterEvents() {
|
---|
| 127 | MaxCloseParameter.ValueChanged += (o, s) => ConfigureOperators(Operators);
|
---|
| 128 | MaxCloseParameter.Value.ValueChanged += (o, s) => ConfigureOperators(Operators);
|
---|
| 129 | }
|
---|
| 130 |
|
---|
| 131 | private void RegisterCloseBiasLevelParameterEvents() {
|
---|
| 132 | CloseBiasLevelParameter.ValueChanged += (o, s) => ConfigureOperators(Operators);
|
---|
| 133 | CloseBiasLevelParameter.Value.ValueChanged += (o, s) => ConfigureOperators(Operators);
|
---|
| 134 | }
|
---|
| 135 |
|
---|
| 136 | private void RegisterInInstructionProbabilityParameterEvents() {
|
---|
| 137 | InInstructionProbabilityParameter.ValueChanged += (o, s) => ConfigureOperators(Operators);
|
---|
| 138 | InInstructionProbabilityParameter.Value.ValueChanged += (o, s) => ConfigureOperators(Operators);
|
---|
| 139 | }
|
---|
| 140 |
|
---|
| 141 | private void RegisterErcOptionsParameterEvents() {
|
---|
| 142 | ErcOptionsParameter.ValueChanged += (o, s) => ConfigureOperators(Operators);
|
---|
| 143 | }
|
---|
| 144 |
|
---|
| 145 | private void RegisterInstructionsParameterEvents() {
|
---|
| 146 | InstructionsParameter.ValueChanged += (o, s) => ConfigureOperators(Operators);
|
---|
| 147 | }
|
---|
| 148 |
|
---|
| 149 | #endregion
|
---|
| 150 |
|
---|
| 151 | #region Encoding Parameters
|
---|
| 152 | [Storable]
|
---|
| 153 | private IFixedValueParameter<IntValue> minLengthParameter;
|
---|
| 154 | public IFixedValueParameter<IntValue> MinLengthParameter
|
---|
| 155 | {
|
---|
| 156 | get { return minLengthParameter; }
|
---|
| 157 | set
|
---|
| 158 | {
|
---|
| 159 | if (value == null) throw new ArgumentNullException("Min length parameter must not be null.");
|
---|
| 160 | if (value.Value == null) throw new ArgumentNullException("Min length parameter value must not be null.");
|
---|
| 161 | if (minLengthParameter == value) return;
|
---|
| 162 |
|
---|
| 163 | if (minLengthParameter != null) Parameters.Remove(minLengthParameter);
|
---|
| 164 | minLengthParameter = value;
|
---|
| 165 | Parameters.Add(minLengthParameter);
|
---|
| 166 | OnMinLengthParameterChanged();
|
---|
| 167 | }
|
---|
| 168 | }
|
---|
| 169 |
|
---|
| 170 | [Storable]
|
---|
| 171 | private IFixedValueParameter<IntValue> maxLengthParameter;
|
---|
| 172 | public IFixedValueParameter<IntValue> MaxLengthParameter
|
---|
| 173 | {
|
---|
| 174 | get { return maxLengthParameter; }
|
---|
| 175 | set
|
---|
| 176 | {
|
---|
| 177 | if (value == null) throw new ArgumentNullException("Max length parameter must not be null.");
|
---|
| 178 | if (value.Value == null) throw new ArgumentNullException("Max length parameter value must not be null.");
|
---|
| 179 | if (maxLengthParameter == value) return;
|
---|
| 180 |
|
---|
| 181 | if (maxLengthParameter != null) Parameters.Remove(maxLengthParameter);
|
---|
| 182 | maxLengthParameter = value;
|
---|
| 183 | Parameters.Add(maxLengthParameter);
|
---|
| 184 | OnMaxLengthParameterChanged();
|
---|
| 185 | }
|
---|
| 186 | }
|
---|
| 187 |
|
---|
| 188 | [Storable]
|
---|
| 189 | private IFixedValueParameter<IntValue> minCloseParameter;
|
---|
| 190 | public IFixedValueParameter<IntValue> MinCloseParameter
|
---|
| 191 | {
|
---|
| 192 | get { return minCloseParameter; }
|
---|
| 193 | set
|
---|
| 194 | {
|
---|
| 195 | if (value == null) throw new ArgumentNullException("Min close parameter must not be null.");
|
---|
| 196 | if (value.Value == null) throw new ArgumentNullException("Min close parameter value must not be null.");
|
---|
| 197 | if (minCloseParameter == value) return;
|
---|
| 198 |
|
---|
| 199 | if (minCloseParameter != null) Parameters.Remove(minCloseParameter);
|
---|
| 200 | minCloseParameter = value;
|
---|
| 201 | Parameters.Add(minCloseParameter);
|
---|
| 202 | OnMinCloseParameterChanged();
|
---|
| 203 | }
|
---|
| 204 | }
|
---|
| 205 |
|
---|
| 206 | [Storable]
|
---|
| 207 | private IFixedValueParameter<IntValue> maxCloseParameter;
|
---|
| 208 | public IFixedValueParameter<IntValue> MaxCloseParameter
|
---|
| 209 | {
|
---|
| 210 | get { return maxCloseParameter; }
|
---|
| 211 | set
|
---|
| 212 | {
|
---|
| 213 | if (value == null) throw new ArgumentNullException("Max close parameter must not be null.");
|
---|
| 214 | if (value.Value == null) throw new ArgumentNullException("Max close parameter value must not be null.");
|
---|
| 215 | if (maxCloseParameter == value) return;
|
---|
| 216 |
|
---|
| 217 | if (maxCloseParameter != null) Parameters.Remove(maxCloseParameter);
|
---|
| 218 | maxCloseParameter = value;
|
---|
| 219 | Parameters.Add(maxCloseParameter);
|
---|
| 220 | OnMaxCloseParameterChanged();
|
---|
| 221 | }
|
---|
| 222 | }
|
---|
| 223 |
|
---|
| 224 | [Storable]
|
---|
| 225 | private IFixedValueParameter<DoubleValue> closeBiasLevelParameter;
|
---|
| 226 | public IFixedValueParameter<DoubleValue> CloseBiasLevelParameter
|
---|
| 227 | {
|
---|
| 228 | get { return closeBiasLevelParameter; }
|
---|
| 229 | set
|
---|
| 230 | {
|
---|
| 231 | if (value == null) throw new ArgumentNullException("Close bias level parameter must not be null.");
|
---|
| 232 | if (value.Value == null) throw new ArgumentNullException("Close bias level parameter value must not be null.");
|
---|
| 233 | if (closeBiasLevelParameter == value) return;
|
---|
| 234 |
|
---|
| 235 | if (closeBiasLevelParameter != null) Parameters.Remove(closeBiasLevelParameter);
|
---|
| 236 | closeBiasLevelParameter = value;
|
---|
| 237 | Parameters.Add(closeBiasLevelParameter);
|
---|
| 238 | OnCloseBiasLevelParameterChanged();
|
---|
| 239 | }
|
---|
| 240 | }
|
---|
| 241 |
|
---|
| 242 | [Storable]
|
---|
| 243 | private IFixedValueParameter<PercentValue> inInstructionProbabilityParameter;
|
---|
| 244 | public IFixedValueParameter<PercentValue> InInstructionProbabilityParameter
|
---|
| 245 | {
|
---|
| 246 | get { return inInstructionProbabilityParameter; }
|
---|
| 247 | set
|
---|
| 248 | {
|
---|
| 249 | if (value == null) throw new ArgumentNullException("In instruciton probability parameter must not be null.");
|
---|
| 250 | if (value.Value == null) throw new ArgumentNullException("In instruciton probability parameter value must not be null.");
|
---|
| 251 | if (inInstructionProbabilityParameter == value) return;
|
---|
| 252 |
|
---|
| 253 | if (inInstructionProbabilityParameter != null) Parameters.Remove(inInstructionProbabilityParameter);
|
---|
| 254 | inInstructionProbabilityParameter = value;
|
---|
| 255 | Parameters.Add(inInstructionProbabilityParameter);
|
---|
| 256 | OnInInstructionProbabilityParameterChanged();
|
---|
| 257 | }
|
---|
| 258 | }
|
---|
| 259 |
|
---|
| 260 | [Storable]
|
---|
| 261 | private IValueParameter<IExpressionsConfiguration> instructionsParameter;
|
---|
| 262 | public IValueParameter<IExpressionsConfiguration> InstructionsParameter
|
---|
| 263 | {
|
---|
| 264 | get { return instructionsParameter; }
|
---|
| 265 | set
|
---|
| 266 | {
|
---|
| 267 | if (value == null) throw new ArgumentNullException("Instructions paramter must not be null");
|
---|
| 268 | if (instructionsParameter == value) return;
|
---|
| 269 |
|
---|
| 270 | if (instructionsParameter != null) Parameters.Remove(instructionsParameter);
|
---|
| 271 | instructionsParameter = value;
|
---|
| 272 | Parameters.Add(instructionsParameter);
|
---|
| 273 | OnInstructionsParameterChanged();
|
---|
| 274 | }
|
---|
| 275 | }
|
---|
| 276 |
|
---|
| 277 | [Storable]
|
---|
| 278 | private IValueParameter<ErcOptions> ercOptionsParameter;
|
---|
| 279 | public IValueParameter<ErcOptions> ErcOptionsParameter
|
---|
| 280 | {
|
---|
| 281 | get { return ercOptionsParameter; }
|
---|
| 282 | set
|
---|
| 283 | {
|
---|
| 284 | if (value == null) throw new ArgumentNullException("ErcOptions paramter must not be null");
|
---|
| 285 | if (ercOptionsParameter == value) return;
|
---|
| 286 |
|
---|
| 287 | if (ercOptionsParameter != null) Parameters.Remove(ercOptionsParameter);
|
---|
| 288 | ercOptionsParameter = value;
|
---|
| 289 | Parameters.Add(ercOptionsParameter);
|
---|
| 290 | OnErcOptionsParameterChanged();
|
---|
| 291 | }
|
---|
| 292 | }
|
---|
| 293 |
|
---|
| 294 | public IExpressionsConfiguration Instructions
|
---|
| 295 | {
|
---|
| 296 | get { return InstructionsParameter.Value; }
|
---|
| 297 | set { InstructionsParameter.Value = value; }
|
---|
| 298 | }
|
---|
| 299 |
|
---|
| 300 | public ErcOptions ErcOptions
|
---|
| 301 | {
|
---|
| 302 | get { return ErcOptionsParameter.Value; }
|
---|
| 303 | set { ErcOptionsParameter.Value = value; }
|
---|
| 304 | }
|
---|
| 305 |
|
---|
| 306 | public int MinLength
|
---|
| 307 | {
|
---|
| 308 | get { return MinLengthParameter.Value.Value; }
|
---|
| 309 | set { MinLengthParameter.Value.Value = value; }
|
---|
| 310 | }
|
---|
| 311 |
|
---|
| 312 | public int MaxLength
|
---|
| 313 | {
|
---|
| 314 | get { return MaxLengthParameter.Value.Value; }
|
---|
| 315 | set { MaxLengthParameter.Value.Value = value; }
|
---|
| 316 | }
|
---|
| 317 |
|
---|
| 318 | public int MinClose
|
---|
| 319 | {
|
---|
| 320 | get { return MinCloseParameter.Value.Value; }
|
---|
| 321 | set { MinCloseParameter.Value.Value = value; }
|
---|
| 322 | }
|
---|
| 323 |
|
---|
| 324 | public int MaxClose
|
---|
| 325 | {
|
---|
| 326 | get { return MaxCloseParameter.Value.Value; }
|
---|
| 327 | set { MaxCloseParameter.Value.Value = value; }
|
---|
| 328 | }
|
---|
| 329 |
|
---|
| 330 | public double InInstructionProbability
|
---|
| 331 | {
|
---|
| 332 | get { return InInstructionProbabilityParameter.Value.Value; }
|
---|
| 333 | set { InInstructionProbabilityParameter.Value.Value = value; }
|
---|
| 334 | }
|
---|
| 335 | #endregion
|
---|
| 336 |
|
---|
| 337 | #region Operator Discovery
|
---|
| 338 | private static readonly IEnumerable<Type> encodingSpecificOperatorTypes;
|
---|
| 339 | static PlushEncoding() {
|
---|
| 340 | encodingSpecificOperatorTypes = new List<Type>() {
|
---|
| 341 | typeof (IPlushOperator),
|
---|
| 342 | typeof (IPlushCreator),
|
---|
| 343 | typeof (IPlushCrossover),
|
---|
| 344 | typeof (IPlushManipulator),
|
---|
| 345 | };
|
---|
| 346 | }
|
---|
| 347 |
|
---|
| 348 | private void DiscoverOperators() {
|
---|
| 349 | var assembly = typeof(IPlushOperator).Assembly;
|
---|
| 350 | var discoveredTypes = ApplicationManager.Manager.GetTypes(encodingSpecificOperatorTypes, assembly, true, false, false);
|
---|
| 351 | var operators = discoveredTypes.Select(t => (IOperator)Activator.CreateInstance(t));
|
---|
| 352 | var newOperators = operators.Except(Operators, new TypeEqualityComparer<IOperator>()).ToList();
|
---|
| 353 |
|
---|
| 354 | ConfigureOperators(newOperators);
|
---|
| 355 | foreach (var @operator in newOperators)
|
---|
| 356 | AddOperator(@operator);
|
---|
| 357 | }
|
---|
| 358 | #endregion
|
---|
| 359 |
|
---|
[15273] | 360 | public override void ConfigureOperators(IEnumerable<IOperator> operators) {
|
---|
[15275] | 361 | ConfigureCreators(operators.OfType<IPlushCreator>());
|
---|
| 362 | ConfigureCrossovers(operators.OfType<IPlushCrossover>());
|
---|
| 363 | ConfigureManipulators(operators.OfType<IPlushManipulator>());
|
---|
[15273] | 364 | }
|
---|
| 365 |
|
---|
[15275] | 366 | private void ConfigureCreators(IEnumerable<IPlushCreator> creators) {
|
---|
| 367 | foreach (var creator in creators) {
|
---|
| 368 | creator.PlushVectorParameter.ActualName = Name;
|
---|
| 369 | creator.MinLengthParameter.ActualName = MinLengthParameter.Name;
|
---|
| 370 | creator.MaxLengthParameter.ActualName = MaxLengthParameter.Name;
|
---|
| 371 | creator.MinCloseParameter.ActualName = MinCloseParameter.Name;
|
---|
| 372 | creator.MaxCloseParameter.ActualName = MinCloseParameter.Name;
|
---|
| 373 | creator.ErcOptionsParameter.ActualName = ErcOptionsParameter.Name;
|
---|
| 374 | creator.InstructionsParameter.ActualName = InstructionsParameter.Name;
|
---|
| 375 | creator.InInstructionProbabilityParameter.ActualName = InInstructionProbabilityParameter.Name;
|
---|
| 376 | }
|
---|
| 377 | }
|
---|
| 378 |
|
---|
| 379 | private void ConfigureCrossovers(IEnumerable<IPlushCrossover> crossovers) {
|
---|
| 380 | foreach (var crossover in crossovers) {
|
---|
| 381 | crossover.ChildParameter.ActualName = Name;
|
---|
| 382 | crossover.ParentsParameter.ActualName = Name;
|
---|
| 383 | }
|
---|
| 384 | }
|
---|
| 385 |
|
---|
| 386 | private void ConfigureManipulators(IEnumerable<IPlushManipulator> manipulators) {
|
---|
| 387 | foreach (var manipulator in manipulators) {
|
---|
| 388 | manipulator.PlushVectorParameter.ActualName = Name;
|
---|
| 389 | manipulator.PlushVectorParameter.Hidden = true;
|
---|
| 390 |
|
---|
| 391 | manipulator.ErcOptionsParameter.ActualName = ErcOptionsParameter.Name;
|
---|
| 392 | manipulator.InstructionsParameter.ActualName = InstructionsParameter.Name;
|
---|
| 393 | manipulator.InInstructionProbabilityParameter.ActualName = InInstructionProbabilityParameter.Name;
|
---|
| 394 | }
|
---|
| 395 | }
|
---|
[15273] | 396 | }
|
---|
[15275] | 397 |
|
---|
| 398 | public static class IndividualExtensionMethods {
|
---|
| 399 | public static PlushVector PlushVector(this Individual individual) {
|
---|
| 400 | var encoding = individual.GetEncoding<PlushEncoding>();
|
---|
| 401 | return individual.PlushVector(encoding.Name);
|
---|
| 402 | }
|
---|
| 403 |
|
---|
| 404 | public static PlushVector PlushVector(this Individual individual, string name) {
|
---|
| 405 | return (PlushVector)individual[name];
|
---|
| 406 | }
|
---|
| 407 | }
|
---|
[15273] | 408 | }
|
---|