ScenarioParser.java

  1. package org.opentrafficsim.road.network.factory.xml.parser;

  2. import java.io.Serializable;
  3. import java.util.ArrayList;
  4. import java.util.LinkedHashMap;
  5. import java.util.LinkedHashSet;
  6. import java.util.List;
  7. import java.util.Map;
  8. import java.util.Set;
  9. import java.util.function.Supplier;

  10. import org.djunits.value.vdouble.scalar.Dimensionless;
  11. import org.djutils.base.Identifiable;
  12. import org.djutils.eval.Eval;
  13. import org.djutils.eval.RetrieveValue;
  14. import org.opentrafficsim.road.network.factory.xml.CircularDependencyException;
  15. import org.opentrafficsim.xml.bindings.types.ExpressionType;
  16. import org.opentrafficsim.xml.generated.Demand;
  17. import org.opentrafficsim.xml.generated.InputParameters;
  18. import org.opentrafficsim.xml.generated.ModelIdReferralType;
  19. import org.opentrafficsim.xml.generated.ScenarioType;
  20. import org.opentrafficsim.xml.generated.Scenarios;

  21. /**
  22.  * Parser of scenario tags.
  23.  * <p>
  24.  * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  25.  * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  26.  * </p>
  27.  * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  28.  * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
  29.  * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  30.  */
  31. public class ScenarioParser
  32. {

  33.     /**
  34.      * Parse input parameters for scenario.
  35.      * @param scenarios Scenarios; scenarios tag.
  36.      * @param scenario String; name of scenario tp parse.
  37.      * @return Eval; expression evaluator for all expression in XML.
  38.      */
  39.     public static Eval parseInputParameters(final Scenarios scenarios, final String scenario)
  40.     {
  41.         if (scenarios == null)
  42.         {
  43.             return new Eval();
  44.         }
  45.         ScenariosWrapper scenariosWrapper = new ScenariosWrapper()
  46.         {
  47.             /** {@inheritDoc} */
  48.             @Override
  49.             public Iterable<ParameterWrapper> getDefaultInputParameters()
  50.             {
  51.                 return getInputParameterIterator(scenarios.getDefaultInputParameters());
  52.             }

  53.             /** {@inheritDoc} */
  54.             @Override
  55.             public Iterable<ParameterWrapper> getScenarioInputParameters()
  56.             {
  57.                 for (ScenarioType scenarioTag : scenarios.getScenario())
  58.                 {
  59.                     if (scenarioTag.getId().equals(scenario))
  60.                     {
  61.                         return getInputParameterIterator(scenarioTag.getInputParameters());
  62.                     }
  63.                 }
  64.                 return null;
  65.             }
  66.         };
  67.         return parseInputParameters(scenariosWrapper);
  68.     }

  69.     /**
  70.      * Parse input parameters for scenario.
  71.      * @param scenariosWrapper ScenariosWrapper; scenarios wrapper, from XML or Xsd Tree nodes in editor.
  72.      * @return Eval; expression evaluator for all expression in XML.
  73.      */
  74.     public static Eval parseInputParameters(final ScenariosWrapper scenariosWrapper)
  75.     {
  76.         Map<String, Supplier<?>> defaultsMap = new LinkedHashMap<>();
  77.         ParameterMap defaults = new ParameterMap(defaultsMap);
  78.         Eval eval = new Eval().setRetrieveValue(defaults);
  79.         parseInputParameters(scenariosWrapper.getDefaultInputParameters(), defaultsMap, defaults);
  80.         if (scenariosWrapper.getScenarioInputParameters() != null)
  81.         {
  82.             Map<String, Supplier<?>> inputParametersMap = new LinkedHashMap<>();
  83.             ParameterMap inputParameters = new ParameterMap(inputParametersMap);
  84.             defaults.setScenarioMap(inputParameters);
  85.             parseInputParameters(scenariosWrapper.getScenarioInputParameters(), inputParametersMap, defaults);
  86.         }
  87.         // test whether values can be obtained successfully (might throw CircularDependencyException)
  88.         for (ParameterWrapper parameter : scenariosWrapper.getDefaultInputParameters())
  89.         {
  90.             String id = parameter.getId();
  91.             eval.evaluate(id.substring(1, id.length() - 1));
  92.         }
  93.         if (scenariosWrapper.getScenarioInputParameters() != null)
  94.         {
  95.             for (ParameterWrapper parameter : scenariosWrapper.getScenarioInputParameters())
  96.             {
  97.                 String id = parameter.getId();
  98.                 eval.evaluate(id.substring(1, id.length() - 1));
  99.             }
  100.         }
  101.         return eval;
  102.     }

  103.     /**
  104.      * Creates parsable parameters from an InputParameters XML tag (default or of a scenario).
  105.      * @param inputParameters InputParameters; parameters XML tag.
  106.      * @return ITerable&lt;ParameterWrapper&gt;; parameters in generic form for parsing.
  107.      */
  108.     private static Iterable<ParameterWrapper> getInputParameterIterator(final InputParameters inputParameters)
  109.     {
  110.         List<ParameterWrapper> parameters = new ArrayList<>();
  111.         if (inputParameters == null)
  112.         {
  113.             return parameters;
  114.         }
  115.         for (Serializable parameter : inputParameters.getDurationOrLengthOrSpeed())
  116.         {
  117.             if (parameter instanceof org.opentrafficsim.xml.generated.InputParameters.Duration)
  118.             {
  119.                 org.opentrafficsim.xml.generated.InputParameters.Duration p =
  120.                         (org.opentrafficsim.xml.generated.InputParameters.Duration) parameter;
  121.                 parameters.add(new ParameterWrapper(p.getId(), p.getValue()));
  122.             }
  123.             else if (parameter instanceof org.opentrafficsim.xml.generated.InputParameters.Length)
  124.             {
  125.                 org.opentrafficsim.xml.generated.InputParameters.Length p =
  126.                         (org.opentrafficsim.xml.generated.InputParameters.Length) parameter;
  127.                 parameters.add(new ParameterWrapper(p.getId(), p.getValue()));
  128.             }
  129.             else if (parameter instanceof org.opentrafficsim.xml.generated.InputParameters.Speed)
  130.             {
  131.                 org.opentrafficsim.xml.generated.InputParameters.Speed p =
  132.                         (org.opentrafficsim.xml.generated.InputParameters.Speed) parameter;
  133.                 parameters.add(new ParameterWrapper(p.getId(), p.getValue()));
  134.             }
  135.             else if (parameter instanceof org.opentrafficsim.xml.generated.InputParameters.Acceleration)
  136.             {
  137.                 org.opentrafficsim.xml.generated.InputParameters.Acceleration p =
  138.                         (org.opentrafficsim.xml.generated.InputParameters.Acceleration) parameter;
  139.                 parameters.add(new ParameterWrapper(p.getId(), p.getValue()));
  140.             }
  141.             else if (parameter instanceof org.opentrafficsim.xml.generated.InputParameters.LinearDensity)
  142.             {
  143.                 org.opentrafficsim.xml.generated.InputParameters.LinearDensity p =
  144.                         (org.opentrafficsim.xml.generated.InputParameters.LinearDensity) parameter;
  145.                 parameters.add(new ParameterWrapper(p.getId(), p.getValue()));
  146.             }
  147.             else if (parameter instanceof org.opentrafficsim.xml.generated.InputParameters.Frequency)
  148.             {
  149.                 org.opentrafficsim.xml.generated.InputParameters.Frequency p =
  150.                         (org.opentrafficsim.xml.generated.InputParameters.Frequency) parameter;
  151.                 parameters.add(new ParameterWrapper(p.getId(), p.getValue()));
  152.             }
  153.             else if (parameter instanceof org.opentrafficsim.xml.generated.InputParameters.Double)
  154.             {
  155.                 org.opentrafficsim.xml.generated.InputParameters.Double p =
  156.                         (org.opentrafficsim.xml.generated.InputParameters.Double) parameter;
  157.                 parameters.add(new ParameterWrapper(p.getId(), p.getValue()));
  158.             }
  159.             else if (parameter instanceof org.opentrafficsim.xml.generated.InputParameters.Fraction)
  160.             {
  161.                 org.opentrafficsim.xml.generated.InputParameters.Fraction p =
  162.                         (org.opentrafficsim.xml.generated.InputParameters.Fraction) parameter;
  163.                 parameters.add(new ParameterWrapper(p.getId(), p.getValue()));
  164.             }
  165.             else if (parameter instanceof org.opentrafficsim.xml.generated.InputParameters.Integer)
  166.             {
  167.                 org.opentrafficsim.xml.generated.InputParameters.Integer p =
  168.                         (org.opentrafficsim.xml.generated.InputParameters.Integer) parameter;
  169.                 parameters.add(new ParameterWrapper(p.getId(), p.getValue()));
  170.             }
  171.             else if (parameter instanceof org.opentrafficsim.xml.generated.InputParameters.Boolean)
  172.             {
  173.                 org.opentrafficsim.xml.generated.InputParameters.Boolean p =
  174.                         (org.opentrafficsim.xml.generated.InputParameters.Boolean) parameter;
  175.                 parameters.add(new ParameterWrapper(p.getId(), p.getValue()));
  176.             }
  177.             else if (parameter instanceof org.opentrafficsim.xml.generated.InputParameters.String)
  178.             {
  179.                 org.opentrafficsim.xml.generated.InputParameters.String p =
  180.                         (org.opentrafficsim.xml.generated.InputParameters.String) parameter;
  181.                 parameters.add(new ParameterWrapper(p.getId(), p.getValue()));
  182.             }
  183.             else if (parameter instanceof org.opentrafficsim.xml.generated.InputParameters.Class)
  184.             {
  185.                 org.opentrafficsim.xml.generated.InputParameters.Class p =
  186.                         (org.opentrafficsim.xml.generated.InputParameters.Class) parameter;
  187.                 parameters.add(new ParameterWrapper(p.getId(), p.getValue()));
  188.             }
  189.         }
  190.         return parameters;
  191.     }

  192.     /**
  193.      * Parse input parameters.
  194.      * @param inputParameters Iterable&lt;ParameterWrapper&gt;; xml tag.
  195.      * @param map Map&lt;String, Supplier&lt;?&gt;&gt;; map that underlines inputParameters.
  196.      * @param retrieve ParameterMap; value retrieval.
  197.      */
  198.     private static void parseInputParameters(final Iterable<ParameterWrapper> inputParameters,
  199.             final Map<String, Supplier<?>> map, final ParameterMap retrieve)
  200.     {
  201.         Eval eval = new Eval().setRetrieveValue(retrieve);
  202.         for (ParameterWrapper parameter : inputParameters)
  203.         {
  204.             // need to create a new Eval each time, as input parameters may depend on others
  205.             // NOTE: if Eval has additional user defined functions or unit parsers, that's not included here
  206.             String id = parameter.getId();
  207.             map.put(id.substring(1, id.length() - 1), () -> parameter.get().get(eval));
  208.         }
  209.     }

  210.     /**
  211.      * Parse model ID referrals.
  212.      * @param scenario List&lt;ScenarioType&gt;; scenario
  213.      * @param demand Demand; demand
  214.      * @param eval Eval; expression evaluator.
  215.      * @return map from ID to ID
  216.      */
  217.     public static final Map<String, String> parseModelIdReferral(final List<ScenarioType> scenario, final Demand demand,
  218.             final Eval eval)
  219.     {
  220.         // TODO: use run to select scenario (probably outside this class, and accept a single Scenario
  221.         Map<String, String> map = new LinkedHashMap<>();
  222.         for (ModelIdReferralType modelIdReferral : demand.getModelIdReferral())
  223.         {
  224.             map.put(modelIdReferral.getId(), modelIdReferral.getModelId().get(eval));
  225.         }
  226.         // overwrite with scenario level ID referrals
  227.         if (!scenario.isEmpty())
  228.         {
  229.             for (ModelIdReferralType modelIdReferral : scenario.get(0).getModelIdReferral())
  230.             {
  231.                 map.put(modelIdReferral.getId(), modelIdReferral.getModelId().get(eval));
  232.             }
  233.         }
  234.         return map;
  235.     }

  236.     /**
  237.      * Generic scenario form for parsing.
  238.      * <p>
  239.      * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  240.      * <br>
  241.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  242.      * </p>
  243.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  244.      */
  245.     public static interface ScenariosWrapper
  246.     {
  247.         /**
  248.          * Returns default parameters in generic form for parsing.
  249.          * @return Iterable&lt;ParameterWrapper&gt;; default parameters in generic form for parsing.
  250.          */
  251.         Iterable<ParameterWrapper> getDefaultInputParameters();

  252.         /**
  253.          * Returns scenario parameters in generic form for parsing.
  254.          * @return Iterable&lt;ParameterWrapper&gt;; scenario parameters in generic form for parsing.
  255.          */
  256.         Iterable<ParameterWrapper> getScenarioInputParameters();
  257.     }

  258.     /**
  259.      * Generic parameters for for parsing.
  260.      * <p>
  261.      * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  262.      * <br>
  263.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  264.      * </p>
  265.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  266.      * @param id String; id.
  267.      * @param value ExpressionType&lt;?&gt;; value expression type.
  268.      */
  269.     public static record ParameterWrapper(String id, ExpressionType<?> value)
  270.             implements Identifiable, Supplier<ExpressionType<?>>
  271.     {
  272.         /** {@inheritDoc} */
  273.         @Override
  274.         public String getId()
  275.         {
  276.             return this.id();
  277.         }

  278.         /** {@inheritDoc} */
  279.         @Override
  280.         public ExpressionType<?> get()
  281.         {
  282.             return this.value();
  283.         }
  284.     }

  285.     /**
  286.      * Wraps parameters to provide for expressions.
  287.      * <p>
  288.      * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  289.      * <br>
  290.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  291.      * </p>
  292.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  293.      */
  294.     private static class ParameterMap implements RetrieveValue
  295.     {
  296.         /** Map of name to suppliers (constant or distribution). */
  297.         private final Map<String, Supplier<?>> map;

  298.         /** Set of currently looked up values, to detect circular dependency. */
  299.         private final Set<String> lookingUp = new LinkedHashSet<>();

  300.         /** More scenario input parameters. */
  301.         private ParameterMap scenario;

  302.         /**
  303.          * Constructor.
  304.          * @param map Map&lt;String, Supplier&lt;?&gt;&gt;; map that underlines input parameters.
  305.          */
  306.         public ParameterMap(final Map<String, Supplier<?>> map)
  307.         {
  308.             this.map = map;
  309.         }

  310.         /**
  311.          * Set scenario input parameters.
  312.          * @param scenario ParameterMap; parameter map of the scenario.
  313.          */
  314.         public void setScenarioMap(final ParameterMap scenario)
  315.         {
  316.             this.scenario = scenario;
  317.         }

  318.         /** {@inheritDoc} */
  319.         @Override
  320.         public Object lookup(final String name)
  321.         {
  322.             if (!this.lookingUp.add(name))
  323.             {
  324.                 throw new CircularDependencyException("Parameter " + name + " is part of a circular dependency.");
  325.             }
  326.             Object value;
  327.             if (this.scenario != null && this.scenario.map.containsKey(name))
  328.             {
  329.                 value = this.scenario.map.get(name).get();
  330.             }
  331.             else if (this.map.containsKey(name))
  332.             {
  333.                 value = this.map.get(name).get();
  334.             }
  335.             else
  336.             {
  337.                 this.lookingUp.remove(name);
  338.                 throw new RuntimeException("Parameter " + name + " not available.");
  339.             }
  340.             this.lookingUp.remove(name);
  341.             if (value instanceof Double)
  342.             {
  343.                 return Dimensionless.instantiateSI((Double) value);
  344.             }
  345.             return value;
  346.         }
  347.     }
  348. }