arup-group/pam

All `Person` attributes default to `java.lang.String` when saving to MATSim xml

KasiaKoz opened this issue · 4 comments

All attributes for a Person are currently saved as java.lang.String in the MATSim xml files:
https://github.com/arup-group/pam/blob/main/pam/write.py#L177

Sometimes MATSim requires different java types. Below is an error caused by running the multimodal contrib, which is expecting Person's age to be saved as java.lang.Integer for example:

  | java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
  | at org.matsim.contrib.multimodal.router.util.WalkTravelTime.setPerson(WalkTravelTime.java:276) ~[columbus-2.1.0-jar-with-dependencies.jar:2.1.0]
  | at org.matsim.contrib.multimodal.router.util.WalkTravelTime.getLinkTravelTime(WalkTravelTime.java:139) ~[columbus-2.1.0-jar-with-dependencies.jar:2.1.0]
  | at org.matsim.core.router.AStarEuclidean.addToPendingNodes(AStarEuclidean.java:152) ~[columbus-2.1.0-jar-with-dependencies.jar:2.1.0]
  | at org.matsim.core.router.Dijkstra.relaxNodeLogic(Dijkstra.java:438) ~[columbus-2.1.0-jar-with-dependencies.jar:2.1.0]
  | at org.matsim.core.router.Dijkstra.relaxNode(Dijkstra.java:409) ~[columbus-2.1.0-jar-with-dependencies.jar:2.1.0]
  | at org.matsim.core.router.AStarLandmarks.relaxNode(AStarLandmarks.java:137) ~[columbus-2.1.0-jar-with-dependencies.jar:2.1.0]
  | at org.matsim.core.router.Dijkstra.searchLogic(Dijkstra.java:317) ~[columbus-2.1.0-jar-with-dependencies.jar:2.1.0]
  | at org.matsim.core.router.Dijkstra.calcLeastCostPath(Dijkstra.java:234) ~[columbus-2.1.0-jar-with-dependencies.jar:2.1.0]
  | at org.matsim.core.router.AStarLandmarks.calcLeastCostPath(AStarLandmarks.java:124) ~[columbus-2.1.0-jar-with-dependencies.jar:2.1.0]
  | at org.matsim.core.router.NetworkRoutingModule.calcRoute(NetworkRoutingModule.java:108) ~[columbus-2.1.0-jar-with-dependencies.jar:2.1.0]
  | at org.matsim.core.router.TripRouter.calcRoute(TripRouter.java:182) ~[columbus-2.1.0-jar-with-dependencies.jar:2.1.0]
  | at org.matsim.core.router.PlanRouter.run(PlanRouter.java:101) ~[columbus-2.1.0-jar-with-dependencies.jar:2.1.0]
  | at org.matsim.core.population.algorithms.PersonPrepareForSim.run(PersonPrepareForSim.java:219) ~[columbus-2.1.0-jar-with-dependencies.jar:2.1.0]
  | at org.matsim.core.population.algorithms.ParallelPersonAlgorithmUtils$PersonAlgoThread.run(ParallelPersonAlgorithmUtils.java:145) ~[columbus-2.1.0-jar-with-dependencies.jar:2.1.0]
  | at java.lang.Thread.run(Thread.java:834) ~[?:?]

We had a go at 'guessing' java types from python types in genet: arup-group/genet#124, master/genet/utils/java_dtypes.py, which may be of use here.

Now also found on a recent project:

java.lang.ClassCastException: class java.lang.String cannot be cast to class org.matsim.vehicles.PersonVehicles (java.lang.String is in module java.base of loader 'bootstrap'; org.matsim.vehicles.PersonVehicles is in unnamed module of loader 'app')

person attributes after reading and writing out through pam have resulted in:

<attribute class="java.lang.String" name="vehicles">{"car":"person_vehicle_id"}</attribute>

instead of expected

<attribute name="vehicles" class="org.matsim.vehicles.PersonVehicles">{"car":"person_vehicle_id"}</attribute>

/cc @fredshone @Theodore-Chatziioannou

thanks Kasia, we now have a BIP (branch in progress); write-leg-attributes which includes a fix for this specific case in the write_matsim (v12) function:

attributes = et.SubElement(person_xml, 'attributes', {})
            for k, v in person.attributes.items():
                if k == "vehicles":  # todo make something more robust for future 'special' classes
                    attribute = et.SubElement(
                        attributes, 'attribute', {'class': 'org.matsim.vehicles.PersonVehicles', 'name': str(k)}
                        )
                else:
                    attribute = et.SubElement(
                        attributes, 'attribute', {'class': 'java.lang.String', 'name': str(k)}
                        )
                attribute.text = str(v)

This seems to be a working fix but is obviously a bit trash to maintain and will require us to treat the "vehicles" attribute as special/protected forever after.

We also have a type issue in one of the new leg attributes:

                        for k, v in component.attributes.items():
                            if k == 'enterVehicleTime':  # todo make something more robust for future 'special' classes
                                attribute = et.SubElement(
                                attributes, 'attribute', {'class': 'java.lang.Double', 'name': str(k)}
                                )
                            else:
                                attribute = et.SubElement(
                                    attributes, 'attribute', {'class': 'java.lang.String', 'name': str(k)}
                                    )
                            attribute.text = str(v)

This one would be fixed by your type mapping. But i will also look to roll out more broadly for "attributes".

fixed badly by 163