Skip to content

Commit

Permalink
#102: implemented fixed step smoothing support +
Browse files Browse the repository at this point in the history
working on #100, #101, #99 working on path based implementation. now working with exp transformed weibit as well as MNL (verified).
  • Loading branch information
markr committed Feb 8, 2024
1 parent 585d5c0 commit f7a9bd1
Show file tree
Hide file tree
Showing 19 changed files with 239 additions and 70 deletions.
22 changes: 10 additions & 12 deletions src/main/java/org/goplanit/assignment/TrafficAssignment.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.goplanit.sdinteraction.smoothing.Smoothing;
import org.goplanit.supply.networkloading.NetworkLoading;
import org.goplanit.utils.exceptions.PlanItException;
import org.goplanit.utils.exceptions.PlanItRunTimeException;
import org.goplanit.utils.id.IdGroupingToken;
import org.goplanit.utils.misc.LoggingUtils;
import org.goplanit.utils.network.layer.macroscopic.MacroscopicLinkSegment;
Expand Down Expand Up @@ -123,28 +124,25 @@ protected String createLoggingPrefix(int iterationIndex) {
/**
* Check if any components are undefined, if so throw exception
*
* @throws PlanItException thrown if any components are undefined
*/
protected void checkForEmptyComponents() throws PlanItException {
protected void checkForEmptyComponents() {
// input components
PlanItException.throwIf(demands == null, "Demand is null");
PlanItException.throwIf(physicalNetwork == null, "Network is null");
PlanItException.throwIf(zoning == null, "Zoning is null");
PlanItRunTimeException.throwIf(demands == null, "Demand is null");
PlanItRunTimeException.throwIf(physicalNetwork == null, "Network is null");
PlanItRunTimeException.throwIf(zoning == null, "Zoning is null");
// traffic assignment components
PlanItException.throwIf(getSmoothing() == null, "Smoothing is null");
PlanItException.throwIf(getGapFunction() == null, "GapFunction is null");
PlanItException.throwIf(getPhysicalCost() == null, "PhysicalCost is null");
PlanItException.throwIf(getVirtualCost() == null, "VirtualCost is null");
PlanItRunTimeException.throwIf(getSmoothing() == null, "Smoothing is null");
PlanItRunTimeException.throwIf(getGapFunction() == null, "GapFunction is null");
PlanItRunTimeException.throwIf(getPhysicalCost() == null, "PhysicalCost is null");
PlanItRunTimeException.throwIf(getVirtualCost() == null, "VirtualCost is null");

}

/**
* Verify if the create traffic assignment (sbu)components are compatible with each other and the created transport network. Called before starting the simulation and after the
* transport network has been generated from physical and virtual network. So this is called after the build of the assignment instance
*
* @throws PlanItException thrown if the components are not compatible
*/
protected abstract void verifyComponentCompatibility() throws PlanItException;
protected abstract void verifyComponentCompatibility();

/**
* Verify if the traffic assignment inputs (components which are provided upon creation and not subcomponents that are created as part of the build process of the assignment are
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,8 @@ public GapFunctionConfigurator<? extends GapFunction> createAndRegisterGapFuncti
*
* @param smoothingType the type of smoothing component to be created
* @return Smoothing configuration object
* @throws PlanItException thrown if there is an error
*/
public SmoothingConfigurator<? extends Smoothing> createAndRegisterSmoothing(final String smoothingType) throws PlanItException {
public SmoothingConfigurator<? extends Smoothing> createAndRegisterSmoothing(final String smoothingType) {
smoothingConfigurator = SmoothingConfiguratorFactory.createConfigurator(smoothingType);
return smoothingConfigurator;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public class AlgorithmB extends StaticTrafficAssignment {
* {@inheritDoc}
*/
@Override
protected void verifyComponentCompatibility() throws PlanItException {
protected void verifyComponentCompatibility() {
// TODO Auto-generated method stub
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ protected void verifyNetworkDemandZoningCompatibility() throws PlanItException {
* {@inheritDoc}
*/
@Override
protected void verifyComponentCompatibility() throws PlanItException {
protected void verifyComponentCompatibility(){
// not implemented yet
}

Expand Down
9 changes: 5 additions & 4 deletions src/main/java/org/goplanit/assignment/ltm/sltm/StaticLtm.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.goplanit.od.demand.OdDemands;
import org.goplanit.output.adapter.OutputTypeAdapter;
import org.goplanit.output.enums.OutputType;
import org.goplanit.sdinteraction.smoothing.FixedStepSmoothing;
import org.goplanit.sdinteraction.smoothing.IterationBasedSmoothing;
import org.goplanit.sdinteraction.smoothing.MSASmoothing;
import org.goplanit.utils.exceptions.PlanItException;
Expand Down Expand Up @@ -204,18 +205,18 @@ private void persistIterationResults(TimePeriod timePeriod, Mode theMode, boolea
* {@inheritDoc}
*/
@Override
protected void verifyComponentCompatibility() throws PlanItException {
protected void verifyComponentCompatibility(){
super.verifyComponentCompatibility();

/* gap function check */
PlanItException.throwIf(!(getGapFunction() instanceof LinkBasedRelativeDualityGapFunction),
PlanItRunTimeException.throwIf(!(getGapFunction() instanceof LinkBasedRelativeDualityGapFunction),
"%sStatic LTM only supports a link based relative gap function (for equilibration) at the moment, but found %s", LoggingUtils.runIdPrefix(getId()),
getGapFunction().getClass().getCanonicalName());

/* smoothing check */
PlanItException.throwIf(!(getSmoothing() instanceof MSASmoothing), "%sStatic LTM only supports MSA smoothing at the moment, but found %s", LoggingUtils.runIdPrefix(getId()),
PlanItRunTimeException.throwIf(!(getSmoothing() instanceof MSASmoothing || getSmoothing() instanceof FixedStepSmoothing),
"%sStatic LTM only supports MSA or FixedStep smoothing at the moment, but found %s", LoggingUtils.runIdPrefix(getId()),
getSmoothing().getClass().getCanonicalName());

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public StaticLtmConfigurator() throws PlanItException {
//

/**
* Dis or enable enforcing any storage constraints on link(segments)
* Dis-or enable enforcing any storage constraints on link(segments)
*
* @param flag to set
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,13 @@ public boolean performIteration(final Mode theMode, final double[] prevCosts, fi
double newtonStep =
(highCostPathCurrPerceivedCost - lowCostPathCurrPerceivedCost) / newtonStepDenominator;

// todo apply smoothing instead of multiplying by 0.25 (revert to always being iteration based for now I would think + implement fixed smoothing step option to use)
double newLowCostPathProbability = Math.min(1, ((lowCostPath.getPathChoiceProbability() * demand) + 0.25 * newtonStep)/demand);
double newHighCostPathProbability = Math.max(0, ((highCostPath.getPathChoiceProbability() * demand) - 0.25 * newtonStep)/demand);
var currLowCostDemand = lowCostPath.getPathChoiceProbability() * demand;
var proposedLowCostDemand = currLowCostDemand + newtonStep;
double newLowCostPathProbability = Math.min(1, smoothing.execute(currLowCostDemand, proposedLowCostDemand)/demand);

var currHighCostDemand = highCostPath.getPathChoiceProbability() * demand;
var proposedHighCostDemand = currHighCostDemand - newtonStep;
double newHighCostPathProbability = Math.max(0, smoothing.execute(currHighCostDemand, proposedHighCostDemand)/demand);

//7. prep for i + 1 iteration
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ public class StaticLtmSettings {
*/
private Boolean enforceMaxEntropyFlowSolution = ENFORCE_FLOW_PROPORTIONAL_SOLUTION_DEFAULT;

/** default setting for assignment is to apply an origin-based bush-based type of implementation over a path based one */
public static StaticLtmType DEFAULT_SLTM_TYPE = StaticLtmType.DESTINATION_BUSH_BASED;
/** default setting for assignment is to apply a path based rather than an origin-based bush-based type of implementation*/
//public static StaticLtmType DEFAULT_SLTM_TYPE = StaticLtmType.DESTINATION_BUSH_BASED;
public static StaticLtmType DEFAULT_SLTM_TYPE = StaticLtmType.PATH_BASED;

/** default setting for enforcing a flow proportional solution when possible */
public static boolean ENFORCE_FLOW_PROPORTIONAL_SOLUTION_DEFAULT = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,12 @@ protected void verifyNetworkDemandZoningCompatibility() throws PlanItException {

/**
* Verify if a supported gap function is used
*
* @throws PlanItException thrown if the components are not compatible
*/
@Override
protected void verifyComponentCompatibility() throws PlanItException {
protected void verifyComponentCompatibility() {

/* gap function check */
PlanItException.throwIf(!(getGapFunction() instanceof LinkBasedRelativeDualityGapFunction),
PlanItRunTimeException.throwIf(!(getGapFunction() instanceof LinkBasedRelativeDualityGapFunction),
"Traditional static assignment only supports link based relative duality gap function at the moment, but found %s", getGapFunction().getClass().getCanonicalName());

}
Expand Down
32 changes: 10 additions & 22 deletions src/main/java/org/goplanit/choice/weibit/Weibit.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,12 @@ public double[] computeProbabilities(double[] alternativeCosts) {

/**********************************************************************************
* In case of more than one route, calculate the probability for every route via
* (normlly there is a - sign, but because our costs are positive, we can leave it out as it is only there to make results positive)
*
*
* ( 1/-general_cost_current_path)^scale
* ( 1/general_cost_current_path)^scale
* ----------------------------------
* SUM_candidates_p: ((1/-general_cost_of_path_p))^scale
* SUM_candidates_p: ((1/general_cost_of_path_p))^scale
* ********************************************************************************/

/* construct denominator: offset by min cost to avoid floating point overflow errors */
Expand Down Expand Up @@ -89,14 +90,9 @@ public double computePerceivedCost(double absoluteCost, double demand, boolean a
}
return Math.log(absoluteCost) + 1/getScalingFactor() * Math.log(demand);
}else{
if(demand < 1){
// todo current exp transform does not solve issue since it increases in size between 0-1 --> requires multiplication by scaling factor to avoid this
// todo: this before led to incorrect calculation, probably bug, so when working revisit and update to include this approach again
// todo: UPDATE DERIVATIVE method below accordingly to keep consistent
LOGGER.warning("exp transformed solution does not cope with small demand, switch-off demand component of cost, DO NOT USE IN PRODUCTION");
return absoluteCost;
}
return absoluteCost * Math.pow(demand, 1/getScalingFactor());
// exp transform and multiply by scaling factor to avoid issues for demand and cost below 1.
// exp(scale*ln(abs_cost) + ln(demand)) = exp(scaling factor*ln(abs_cost) * (exp(ln(demand)) = abs_cost^scale * demand
return Math.pow(absoluteCost, getScalingFactor()) * demand;
}
}

Expand Down Expand Up @@ -132,19 +128,11 @@ public double computeDPerceivedCostDFlow(double dAbsoluteCostDFlow, double absol
// f' + g' =
return dAbsoluteCostDFlow/absoluteCost + 1/(scalingFactor * demand);
}else{
if(demand < 1){
// todo current exp transform does not solve issue since it increases in size between 0-1 --> requires multiplication by scaling factor to avoid this
// todo: this before led to incorrect calculation, probably bug, so when working revisit and update to include this approach again
// todo: UPDATE DERIVATIVE method below accordingly to keep consistent
// so d(exp(ln(abs_cost))) is what is left after exp transforming the original function when setting demand portion to 0 --> we get d_abs_cost
LOGGER.warning("exp transformed solution does not cope with demand <1 yet, use only non-demand component of cost for derivative, DO NOT USE IN PRODUCTION");
return dAbsoluteCostDFlow;
}
// abs_cost * demand^(1/scale)
// f = abs_cost, g = demand^(1/scale)
// f' = dAbsoluteCostDFlow, g' = (1/scale)*demand^((1/scale)-1)
// TODO: do it proper so we do not get in trouble with low demands: abs_cost^scale * demand
// f = abs_cost*scale, g = demand
// f' = scale*dAbsoluteCostDFlow, g' = 1
// chain rule: f' * g + f * g'
return dAbsoluteCostDFlow * Math.pow(demand, 1/getScalingFactor()) + absoluteCost * (1/getScalingFactor() * Math.pow(demand, (1/getScalingFactor())-1));
return dAbsoluteCostDFlow * getScalingFactor() * demand + Math.pow(absoluteCost, getScalingFactor());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import org.goplanit.path.choice.StochasticPathChoice;
import org.goplanit.choice.ChoiceModel;
import org.goplanit.choice.logit.MultinomialLogit;
import org.goplanit.sdinteraction.smoothing.FixedStepSmoothing;
import org.goplanit.sdinteraction.smoothing.MSASmoothing;
import org.goplanit.sdinteraction.smoothing.Smoothing;
import org.goplanit.service.routed.RoutedServices;
Expand Down Expand Up @@ -116,6 +117,7 @@ private static void registerDefaultImplementations() {
registerPlanitComponentType(TraditionalStaticAssignment.class);
registerPlanitComponentType(StaticLtm.class);
registerPlanitComponentType(MSASmoothing.class);
registerPlanitComponentType(FixedStepSmoothing.class);
registerPlanitComponentType(Demands.class);
registerPlanitComponentType(RoutedServices.class);
registerPlanitComponentType(MacroscopicNetwork.class);
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/goplanit/gap/GapFunctionConfigurator.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import org.goplanit.utils.exceptions.PlanItException;

/**
* Base class for all vgap function configurator implementations
* Base class for all gap function configurator implementations
*
* @author markr
*
Expand All @@ -13,7 +13,7 @@
public class GapFunctionConfigurator<T extends GapFunction> extends Configurator<T> {

/**
* the configurator for the stop critetion
* the configurator for the stop criterion
*/
protected final StopCriterionConfigurator stopCriterionConfigurator;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package org.goplanit.sdinteraction.smoothing;

import org.goplanit.utils.id.IdGroupingToken;

import java.util.HashMap;
import java.util.Map;

/**
* Fixed step smoothing implementation
*
* @author markr
*
*/
public class FixedStepSmoothing extends Smoothing {

/** generated UID */
private static final long serialVersionUID = -3016251188673804117L;

/**
* Step size
*/
protected double stepSize = DEFAULT_STEP_SIZE;

/**
* The default step size to use
*/
public static final double DEFAULT_STEP_SIZE = 0.25;

/**
* Constructor
*
* @param groupId contiguous id generation within this group for instances of this class
*/
public FixedStepSmoothing(IdGroupingToken groupId) {
super(groupId);
}

/**
* Copy constructor
*
* @param other to copy
* @param deepCopy when true, create a deep copy, shallow copy otherwise
*/
public FixedStepSmoothing(FixedStepSmoothing other, boolean deepCopy) {
super(other, deepCopy);
this.stepSize = other.stepSize;
}

/**
* Set the new fixed step size to use
* @param stepSize
*/
public void setStepSize(double stepSize) {
this.stepSize = stepSize;
}

/**
* Get the fixed step size
* @return stepSize
*/
public double getStepSize(double stepSize) {
return this.stepSize;
}

/**
* {@inheritDoc}
*/
@Override
public double execute(final double previousValue, final double proposedValue) {
return (1 - stepSize) * previousValue + stepSize * proposedValue;
}

/**
* Update stepSize
*/
@Override
public void updateStepSize() {
// N/A
}

/**
* {@inheritDoc}
*/
@Override
public double[] execute(final double[] previousValues, final double[] proposedValues, final int numberOfValues) {
final double[] smoothedValues = new double[numberOfValues];
for (int i = 0; i < numberOfValues; ++i) {
smoothedValues[i] = (1 - stepSize) * previousValues[i] + stepSize * proposedValues[i];
}
return smoothedValues;
}

/**
* {@inheritDoc}
*/
@Override
public FixedStepSmoothing shallowClone() {
return new FixedStepSmoothing(this, false);
}

/**
* {@inheritDoc}
*/
@Override
public FixedStepSmoothing deepClone() {
return new FixedStepSmoothing(this, true);
}

/**
* {@inheritDoc}
*/
@Override
public void reset() {
this.stepSize = DEFAULT_STEP_SIZE;
}

/**
* {@inheritDoc}
*/
@Override
public Map<String, String> collectSettingsAsKeyValueMap() {
var settingsMap = new HashMap<String, String>();
settingsMap.put("fixed step-size", "" + stepSize);
return settingsMap;
}

}
Loading

0 comments on commit f7a9bd1

Please sign in to comment.