001/* *********************************************************************** *
002 * project: org.matsim.*
003 *                                                                         *
004 * *********************************************************************** *
005 *                                                                         *
006 * copyright       : (C) 2012 by the members listed in the COPYING,        *
007 *                   LICENSE and WARRANTY file.                            *
008 * email           : info at matsim dot org                                *
009 *                                                                         *
010 * *********************************************************************** *
011 *                                                                         *
012 *   This program is free software; you can redistribute it and/or modify  *
013 *   it under the terms of the GNU General Public License as published by  *
014 *   the Free Software Foundation; either version 2 of the License, or     *
015 *   (at your option) any later version.                                   *
016 *   See also COPYING, LICENSE and WARRANTY file                           *
017 *                                                                         *
018 * *********************************************************************** */
019
020package org.matsim.contrib.cadyts.general;
021
022import cadyts.calibrators.analytical.AnalyticalCalibrator;
023import cadyts.measurements.SingleLinkMeasurement;
024import cadyts.measurements.SingleLinkMeasurement.TYPE;
025import org.apache.log4j.Logger;
026import org.matsim.api.core.v01.Id;
027import org.matsim.core.config.Config;
028import org.matsim.core.config.ConfigUtils;
029import org.matsim.core.gbl.MatsimRandom;
030import org.matsim.core.utils.misc.Time;
031import org.matsim.counts.Count;
032import org.matsim.counts.Counts;
033import org.matsim.counts.Volume;
034
035import java.util.Map;
036
037/**
038 * @author nagel
039 * @author mrieser
040 */
041public final class CadytsBuilderImpl {
042        private static Logger log = Logger.getLogger( CadytsBuilderImpl.class ) ;
043
044        private CadytsBuilderImpl(){} // do not instantiate
045
046        public static <T> AnalyticalCalibrator<T> buildCalibratorAndAddMeasurements(final Config config, final Counts<T> occupCounts,
047                                                                                                                                                                LookUpItemFromId<T> lookUp, Class<T> idType) {
048                
049                if (occupCounts.getCounts().size() == 0) {
050                        log.warn("Counts container is empty.");
051                }
052
053                CadytsConfigGroup cadytsConfig = ConfigUtils.addOrGetModule(config, CadytsConfigGroup.GROUP_NAME, CadytsConfigGroup.class);
054
055                AnalyticalCalibrator<T> matsimCalibrator = buildCalibrator(config);
056                
057                int multiple = cadytsConfig.getTimeBinSize() / 3600 ; // e.g. "3" when timeBinSize_s = 3*3600 = 10800
058
059                // If I remember correctly, the following is trying to get around the fact that the counts time bins are fixed at hourly, but we want to
060                // be able to be more flexible.  As a first step, time bins which are multiples from 1 hour are allowed, say "3".  In order to get a somewhat
061                // meaningful connection to the counts format, it will add up all entries from the corresponding 3 hours before giving this to cadyts, i.e.
062                // you may keep a higher resolution counts file but tell cadyts to work on longer time intervals. kai, dec'13
063
064                // yyyy However, it seems that some of this did not work: We are using "hourly" counts, and dividing the multi-hour values by
065                // the number of hours. ???????
066                
067                // yyyyyy I am currently of the opinion that the multi-hour version should be decoupled from the counts format.  There is a
068                // cadyts file format which allows setting mult-hour measurements, and that seems a lot more direct than trying to use a file
069                // format/data structure which is really not meant for this.  kai, dec'13
070                
071                //add counts data into calibrator
072                int numberOfAddedMeasurements = 0 ;
073                for (Map.Entry<Id<T>, Count<T>> entry : occupCounts.getCounts().entrySet()) {
074                        // (loop over all counting "items" (usually locations/stations)
075                        
076                        T item = lookUp.getItem(Id.create(entry.getKey(), idType)) ;
077                        if ( item==null ) {
078                                throw new RuntimeException("item is null; entry=" + entry + " idType=" + idType ) ;
079                        }
080                        int timeBinIndex = 0 ; // starting with zero which is different from the counts file!!!
081                        int startTimeOfBin_s = -1 ;
082                        double count = -1 ;
083                        for (Volume volume : entry.getValue().getVolumes().values()){
084                                // (loop over the different time slots)
085                                
086                                if ( timeBinIndex%multiple == 0 ) {
087                                        // (i.e. first timeBinIndex belonging to given bin)
088
089                                        startTimeOfBin_s = (volume.getHourOfDayStartingWithOne()-1)*3600 ;
090                                        count = 0 ;
091                                }
092                                count += volume.getValue() ;
093                                if ( ! ( (timeBinIndex%multiple) == (multiple-1) ) ) {
094                                        log.warn( " NOT adding measurement: timeBinIndex: " + timeBinIndex + "; multiple: " + multiple ) ;
095                                } else {
096                                        // (i.e. last timeBinIndex belonging to given bin)
097                                        
098                                        int endTimeOfBin_s   = volume.getHourOfDayStartingWithOne()*3600 - 1 ;
099                                        if ( !( cadytsConfig.getStartTime() <= startTimeOfBin_s && endTimeOfBin_s <= cadytsConfig.getEndTime()) ) {
100                                                log.warn( " NOT adding measurement: cadytsConfigStartTime: " + cadytsConfig.getStartTime() + "; startTimeOfBin_s: " + startTimeOfBin_s +
101                                                                "; endTimeOfBin_s: " + endTimeOfBin_s + "; cadytsConfigEndTime: " + cadytsConfig.getEndTime() );
102                                        } else { //add volumes for each bin to calibrator
103                                                numberOfAddedMeasurements++ ;
104//                                              matsimCalibrator.addMeasurement(item, startTimeOfBin_s, endTimeOfBin_s, count/multiple, SingleLinkMeasurement.TYPE.FLOW_VEH_H);
105                                                matsimCalibrator.addMeasurement(item, startTimeOfBin_s, endTimeOfBin_s, count, SingleLinkMeasurement.TYPE.COUNT_VEH );
106
107                                                // changed this from FLOW_VEH_H to COUNT_VEH on 30/jul/2012 since this is no longer "hourly".  
108                                                // kai/manuel, jul'12
109                                                // Despite the above comment, I am finding this with FLOW_VEH_H.  Why?  kai, feb'13
110                                                // yyyyyy For the test case, this seems to produce weird results.  The expected counts are 1 and 5, the expected result is 0 and 4.
111                                                // Possibly, it divides 1 and 5 by 2 and then has 0.5 and 2.5 for the given hour, in which case 1 and 3 would be best?????
112                                                // kai, feb'13
113                                        }
114                                }
115                                timeBinIndex++ ;
116                        }
117                }
118
119        if ( numberOfAddedMeasurements==0 ) {
120                        log.warn("No measurements were added.");
121        }
122                
123        if ( matsimCalibrator.getProportionalAssignment() ) {
124                throw new RuntimeException("Gunnar says that this may not work so do not set to true. kai, sep'14") ;
125        }
126                return matsimCalibrator;
127        }
128
129        public static <T> AnalyticalCalibrator<T> buildCalibrator(final Config config) {
130                CadytsConfigGroup cadytsConfig = ConfigUtils.addOrGetModule(config, CadytsConfigGroup.GROUP_NAME, CadytsConfigGroup.class ) ;
131                
132                //get timeBinSize_s and validate it
133                if ((Time.MIDNIGHT % cadytsConfig.getTimeBinSize())!= 0 ){
134                        throw new RuntimeException("Cadyts requires a divisor of 86400 as time bin size value .");
135                }
136                if ( (cadytsConfig.getTimeBinSize() % 3600) != 0 ) {
137                        throw new RuntimeException("At this point, time bin sizes need to be multiples of 3600.  This is not a restriction " +
138                                        "of Cadyts, but of the counts file format, which only allows for hourly inputs") ;
139                }
140                
141                
142                AnalyticalCalibrator<T> matsimCalibrator = new AnalyticalCalibrator<>(
143                                config.controler().getOutputDirectory() + "/cadyts.log",
144                                MatsimRandom.getLocalInstance().nextLong(),cadytsConfig.getTimeBinSize()
145                                 ) ;
146
147                matsimCalibrator.setRegressionInertia(cadytsConfig.getRegressionInertia()) ;
148                matsimCalibrator.setMinStddev(cadytsConfig.getMinFlowStddev_vehPerHour(), TYPE.FLOW_VEH_H);
149                matsimCalibrator.setMinStddev(cadytsConfig.getMinFlowStddev_vehPerHour(), TYPE.COUNT_VEH);
150                matsimCalibrator.setFreezeIteration(cadytsConfig.getFreezeIteration());
151                matsimCalibrator.setPreparatoryIterations(cadytsConfig.getPreparatoryIterations());
152                matsimCalibrator.setVarianceScale(cadytsConfig.getVarianceScale());
153
154                matsimCalibrator.setBruteForce(cadytsConfig.useBruteForce());
155                // I don't think this has an influence on any of the variants we are using. (Has an influence only when plan choice is left
156                // completely to cadyts, rather than just taking the score offsets.) kai, dec'13
157                // More formally, one would need to use the selectPlan() method of AnalyticalCalibrator which we are, however, not using. kai, mar'14
158                if ( matsimCalibrator.getBruteForce() ) {
159                        log.warn("setting bruteForce==true for calibrator, but this won't do anything in the way the cadyts matsim integration is set up. kai, mar'14") ;
160                }
161                
162                matsimCalibrator.setStatisticsFile(config.controler().getOutputDirectory() + "/calibration-stats.txt");
163                return matsimCalibrator;
164        }
165        
166}